From a6650bb3927761deb0f8c54d9d53c96da05e26e4 Mon Sep 17 00:00:00 2001 From: straticspaff Date: Mon, 3 Jul 2023 20:29:56 +0100 Subject: [PATCH 001/269] feat: added scenario counter driven by database --- patch-schema/scenarios-counter.sql | 9 + server/channelserver/handlers.go | 754 ++--------------------------- 2 files changed, 46 insertions(+), 717 deletions(-) create mode 100644 patch-schema/scenarios-counter.sql diff --git a/patch-schema/scenarios-counter.sql b/patch-schema/scenarios-counter.sql new file mode 100644 index 000000000..3ea2c65b2 --- /dev/null +++ b/patch-schema/scenarios-counter.sql @@ -0,0 +1,9 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS scenario_counter ( + id serial primary key, + scenario_id numeric not null, + category_id numeric not null +); + +END; \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 4647b8021..f853675a1 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -15,8 +15,9 @@ import ( "crypto/rand" "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" - "go.uber.org/zap" "math/bits" + + "go.uber.org/zap" ) // Temporary function to just return no results for a MSG_MHF_ENUMERATE* packet @@ -776,726 +777,45 @@ func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) { } func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) { + type ScenarioCounterItem struct { + MainID uint32 `db:"scenario_id"` + CategoryID uint8 `db:"category_id"` // 0 = basic, 1 = veteran, 3 = other, 6 = pallone, 7 = diva + } + pkt := p.(*mhfpacket.MsgMhfInfoScenarioCounter) - scenarioCounter := []struct { - MainID uint32 - Unk1 uint8 // Bool item exchange? - // 0 = basic, 1 = veteran, 3 = other, 6 = pallone, 7 = diva - CategoryID uint8 - }{ - //000000110000 - { - MainID: 0x00000011, Unk1: 0, CategoryID: 0, - }, - // 0000005D0001 - { - MainID: 0x0000005D, Unk1: 0, CategoryID: 1, - }, - // 0000005C0001 - { - MainID: 0x0000005C, Unk1: 0, CategoryID: 1, - }, - // 000000510001 - { - MainID: 0x00000051, Unk1: 0, CategoryID: 1, - }, - // 0000005B0001 - { - MainID: 0x0000005B, Unk1: 0, CategoryID: 1, - }, - // 0000005A0001 - { - MainID: 0x0000005A, Unk1: 0, CategoryID: 1, - }, - // 000000590001 - { - MainID: 0x00000059, Unk1: 0, CategoryID: 1, - }, - // 000000580001 - { - MainID: 0x00000058, Unk1: 0, CategoryID: 1, - }, - // 000000570001 - { - MainID: 0x00000057, Unk1: 0, CategoryID: 1, - }, - // 000000560001 - { - MainID: 0x00000056, Unk1: 0, CategoryID: 1, - }, - // 000000550001 - { - MainID: 0x00000055, Unk1: 0, CategoryID: 1, - }, - // 000000540001 - { - MainID: 0x00000054, Unk1: 0, CategoryID: 1, - }, - // 000000530001 - { - MainID: 0x00000053, Unk1: 0, CategoryID: 1, - }, - // 000000520001 - { - MainID: 0x00000052, Unk1: 0, CategoryID: 1, - }, - // 000000570103 - { - MainID: 0x00000057, Unk1: 1, CategoryID: 3, - }, - // 000000580103 - { - MainID: 0x00000058, Unk1: 1, CategoryID: 3, - }, - // 000000590103 - { - MainID: 0x00000059, Unk1: 1, CategoryID: 3, - }, - // 0000005A0103 - { - MainID: 0x0000005A, Unk1: 1, CategoryID: 3, - }, - // 0000005B0103 - { - MainID: 0x0000005B, Unk1: 1, CategoryID: 3, - }, - // 0000005C0103 - { - MainID: 0x0000005C, Unk1: 1, CategoryID: 3, - }, - // 000000530103 - { - MainID: 0x00000053, Unk1: 1, CategoryID: 3, - }, - // 000000560103 - { - MainID: 0x00000056, Unk1: 1, CategoryID: 3, - }, - // 0000003C0103 - { - MainID: 0x0000003C, Unk1: 1, CategoryID: 3, - }, - // 0000003A0103 - { - MainID: 0x0000003A, Unk1: 1, CategoryID: 3, - }, - // 0000003B0103 - { - MainID: 0x0000003B, Unk1: 1, CategoryID: 3, - }, - // 0000001B0103 - { - MainID: 0x0000001B, Unk1: 1, CategoryID: 3, - }, - // 000000190103 - { - MainID: 0x00000019, Unk1: 1, CategoryID: 3, - }, - // 0000001A0103 - { - MainID: 0x0000001A, Unk1: 1, CategoryID: 3, - }, - // 000000170103 - { - MainID: 0x00000017, Unk1: 1, CategoryID: 3, - }, - // 000000020103 - { - MainID: 0x00000002, Unk1: 1, CategoryID: 3, - }, - // 000000030103 - { - MainID: 0x00000003, Unk1: 1, CategoryID: 3, - }, - // 000000040103 - { - MainID: 0x00000004, Unk1: 1, CategoryID: 3, - }, - // 0000001F0103 - { - MainID: 0x0000001F, Unk1: 1, CategoryID: 3, - }, - // 000000200103 - { - MainID: 0x00000020, Unk1: 1, CategoryID: 3, - }, - // 000000210103 - { - MainID: 0x00000021, Unk1: 1, CategoryID: 3, - }, - // 000000220103 - { - MainID: 0x00000022, Unk1: 1, CategoryID: 3, - }, - // 000000230103 - { - MainID: 0x00000023, Unk1: 1, CategoryID: 3, - }, - // 000000240103 - { - MainID: 0x00000024, Unk1: 1, CategoryID: 3, - }, - // 000000250103 - { - MainID: 0x00000025, Unk1: 1, CategoryID: 3, - }, - // 000000280103 - { - MainID: 0x00000028, Unk1: 1, CategoryID: 3, - }, - // 000000260103 - { - MainID: 0x00000026, Unk1: 1, CategoryID: 3, - }, - // 000000270103 - { - MainID: 0x00000027, Unk1: 1, CategoryID: 3, - }, - // 000000300103 - { - MainID: 0x00000030, Unk1: 1, CategoryID: 3, - }, - // 0000000C0103 - { - MainID: 0x0000000C, Unk1: 1, CategoryID: 3, - }, - // 0000000D0103 - { - MainID: 0x0000000D, Unk1: 1, CategoryID: 3, - }, - // 0000001E0103 - { - MainID: 0x0000001E, Unk1: 1, CategoryID: 3, - }, - // 0000001D0103 - { - MainID: 0x0000001D, Unk1: 1, CategoryID: 3, - }, - // 0000002E0003 - { - MainID: 0x0000002E, Unk1: 0, CategoryID: 3, - }, - // 000000000004 - { - MainID: 0x00000000, Unk1: 0, CategoryID: 4, - }, - // 000000010004 - { - MainID: 0x00000001, Unk1: 0, CategoryID: 4, - }, - // 000000020004 - { - MainID: 0x00000002, Unk1: 0, CategoryID: 4, - }, - // 000000030004 - { - MainID: 0x00000003, Unk1: 0, CategoryID: 4, - }, - // 000000040004 - { - MainID: 0x00000004, Unk1: 0, CategoryID: 4, - }, - // 000000050004 - { - MainID: 0x00000005, Unk1: 0, CategoryID: 4, - }, - // 000000060004 - { - MainID: 0x00000006, Unk1: 0, CategoryID: 4, - }, - // 000000070004 - { - MainID: 0x00000007, Unk1: 0, CategoryID: 4, - }, - // 000000080004 - { - MainID: 0x00000008, Unk1: 0, CategoryID: 4, - }, - // 000000090004 - { - MainID: 0x00000009, Unk1: 0, CategoryID: 4, - }, - // 0000000A0004 - { - MainID: 0x0000000A, Unk1: 0, CategoryID: 4, - }, - // 0000000B0004 - { - MainID: 0x0000000B, Unk1: 0, CategoryID: 4, - }, - // 0000000C0004 - { - MainID: 0x0000000C, Unk1: 0, CategoryID: 4, - }, - // 0000000D0004 - { - MainID: 0x0000000D, Unk1: 0, CategoryID: 4, - }, - // 0000000E0004 - { - MainID: 0x0000000E, Unk1: 0, CategoryID: 4, - }, - // 000000320005 - { - MainID: 0x00000032, Unk1: 0, CategoryID: 5, - }, - // 000000330005 - { - MainID: 0x00000033, Unk1: 0, CategoryID: 5, - }, - // 000000340005 - { - MainID: 0x00000034, Unk1: 0, CategoryID: 5, - }, - // 000000350005 - { - MainID: 0x00000035, Unk1: 0, CategoryID: 5, - }, - // 000000360005 - { - MainID: 0x00000036, Unk1: 0, CategoryID: 5, - }, - // 000000370005 - { - MainID: 0x00000037, Unk1: 0, CategoryID: 5, - }, - // 000000380005 - { - MainID: 0x00000038, Unk1: 0, CategoryID: 5, - }, - // 0000003A0005 - { - MainID: 0x0000003A, Unk1: 0, CategoryID: 5, - }, - // 0000003F0005 - { - MainID: 0x0000003F, Unk1: 0, CategoryID: 5, - }, - // 000000400005 - { - MainID: 0x00000040, Unk1: 0, CategoryID: 5, - }, - // 000000410005 - { - MainID: 0x00000041, Unk1: 0, CategoryID: 5, - }, - // 000000430005 - { - MainID: 0x00000043, Unk1: 0, CategoryID: 5, - }, - // 000000470005 - { - MainID: 0x00000047, Unk1: 0, CategoryID: 5, - }, - // 0000004B0005 - { - MainID: 0x0000004B, Unk1: 0, CategoryID: 5, - }, - // 0000003D0005 - { - MainID: 0x0000003D, Unk1: 0, CategoryID: 5, - }, - // 000000440005 - { - MainID: 0x00000044, Unk1: 0, CategoryID: 5, - }, - // 000000420005 - { - MainID: 0x00000042, Unk1: 0, CategoryID: 5, - }, - // 0000004C0005 - { - MainID: 0x0000004C, Unk1: 0, CategoryID: 5, - }, - // 000000460005 - { - MainID: 0x00000046, Unk1: 0, CategoryID: 5, - }, - // 0000004D0005 - { - MainID: 0x0000004D, Unk1: 0, CategoryID: 5, - }, - // 000000480005 - { - MainID: 0x00000048, Unk1: 0, CategoryID: 5, - }, - // 0000004A0005 - { - MainID: 0x0000004A, Unk1: 0, CategoryID: 5, - }, - // 000000490005 - { - MainID: 0x00000049, Unk1: 0, CategoryID: 5, - }, - // 0000004E0005 - { - MainID: 0x0000004E, Unk1: 0, CategoryID: 5, - }, - // 000000450005 - { - MainID: 0x00000045, Unk1: 0, CategoryID: 5, - }, - // 0000003E0005 - { - MainID: 0x0000003E, Unk1: 0, CategoryID: 5, - }, - // 0000004F0005 - { - MainID: 0x0000004F, Unk1: 0, CategoryID: 5, - }, - // 000000000106 - { - MainID: 0x00000000, Unk1: 1, CategoryID: 6, - }, - // 000000010106 - { - MainID: 0x00000001, Unk1: 1, CategoryID: 6, - }, - // 000000020106 - { - MainID: 0x00000002, Unk1: 1, CategoryID: 6, - }, - // 000000030106 - { - MainID: 0x00000003, Unk1: 1, CategoryID: 6, - }, - // 000000040106 - { - MainID: 0x00000004, Unk1: 1, CategoryID: 6, - }, - // 000000050106 - { - MainID: 0x00000005, Unk1: 1, CategoryID: 6, - }, - // 000000060106 - { - MainID: 0x00000006, Unk1: 1, CategoryID: 6, - }, - // 000000070106 - { - MainID: 0x00000007, Unk1: 1, CategoryID: 6, - }, - // 000000080106 - { - MainID: 0x00000008, Unk1: 1, CategoryID: 6, - }, - // 000000090106 - { - MainID: 0x00000009, Unk1: 1, CategoryID: 6, - }, - // 000000110106 - { - MainID: 0x00000011, Unk1: 1, CategoryID: 6, - }, - // 0000000A0106 - { - MainID: 0x0000000A, Unk1: 1, CategoryID: 6, - }, - // 0000000B0106 - { - MainID: 0x0000000B, Unk1: 1, CategoryID: 6, - }, - // 0000000C0106 - { - MainID: 0x0000000C, Unk1: 1, CategoryID: 6, - }, - // 0000000D0106 - { - MainID: 0x0000000D, Unk1: 1, CategoryID: 6, - }, - // 0000000E0106 - { - MainID: 0x0000000E, Unk1: 1, CategoryID: 6, - }, - // 0000000F0106 - { - MainID: 0x0000000F, Unk1: 1, CategoryID: 6, - }, - // 000000100106 - { - MainID: 0x00000010, Unk1: 1, CategoryID: 6, - }, - // 000000320107 - { - MainID: 0x00000032, Unk1: 1, CategoryID: 7, - }, - // 000000350107 - { - MainID: 0x00000035, Unk1: 1, CategoryID: 7, - }, - // 0000003E0107 - { - MainID: 0x0000003E, Unk1: 1, CategoryID: 7, - }, - // 000000340107 - { - MainID: 0x00000034, Unk1: 1, CategoryID: 7, - }, - // 000000380107 - { - MainID: 0x00000038, Unk1: 1, CategoryID: 7, - }, - // 000000330107 - { - MainID: 0x00000033, Unk1: 1, CategoryID: 7, - }, - // 000000310107 - { - MainID: 0x00000031, Unk1: 1, CategoryID: 7, - }, - // 000000360107 - { - MainID: 0x00000036, Unk1: 1, CategoryID: 7, - }, - // 000000390107 - { - MainID: 0x00000039, Unk1: 1, CategoryID: 7, - }, - // 000000370107 - { - MainID: 0x00000037, Unk1: 1, CategoryID: 7, - }, - // 0000003D0107 - { - MainID: 0x0000003D, Unk1: 1, CategoryID: 7, - }, - // 0000003A0107 - { - MainID: 0x0000003A, Unk1: 1, CategoryID: 7, - }, - // 0000003C0107 - { - MainID: 0x0000003C, Unk1: 1, CategoryID: 7, - }, - // 0000003B0107 - { - MainID: 0x0000003B, Unk1: 1, CategoryID: 7, - }, - // 0000002A0107 - { - MainID: 0x0000002A, Unk1: 1, CategoryID: 7, - }, - // 000000300107 - { - MainID: 0x00000030, Unk1: 1, CategoryID: 7, - }, - // 000000280107 - { - MainID: 0x00000028, Unk1: 1, CategoryID: 7, - }, - // 000000270107 - { - MainID: 0x00000027, Unk1: 1, CategoryID: 7, - }, - // 0000002B0107 - { - MainID: 0x0000002B, Unk1: 1, CategoryID: 7, - }, - // 0000002E0107 - { - MainID: 0x0000002E, Unk1: 1, CategoryID: 7, - }, - // 000000290107 - { - MainID: 0x00000029, Unk1: 1, CategoryID: 7, - }, - // 0000002C0107 - { - MainID: 0x0000002C, Unk1: 1, CategoryID: 7, - }, - // 0000002D0107 - { - MainID: 0x0000002D, Unk1: 1, CategoryID: 7, - }, - // 0000002F0107 - { - MainID: 0x0000002F, Unk1: 1, CategoryID: 7, - }, - // 000000250107 - { - MainID: 0x00000025, Unk1: 1, CategoryID: 7, - }, - // 000000220107 - { - MainID: 0x00000022, Unk1: 1, CategoryID: 7, - }, - // 000000210107 - { - MainID: 0x00000021, Unk1: 1, CategoryID: 7, - }, - // 000000200107 - { - MainID: 0x00000020, Unk1: 1, CategoryID: 7, - }, - // 0000001C0107 - { - MainID: 0x0000001C, Unk1: 1, CategoryID: 7, - }, - // 0000001A0107 - { - MainID: 0x0000001A, Unk1: 1, CategoryID: 7, - }, - // 000000240107 - { - MainID: 0x00000024, Unk1: 1, CategoryID: 7, - }, - // 000000260107 - { - MainID: 0x00000026, Unk1: 1, CategoryID: 7, - }, - // 000000230107 - { - MainID: 0x00000023, Unk1: 1, CategoryID: 7, - }, - // 0000001B0107 - { - MainID: 0x0000001B, Unk1: 1, CategoryID: 7, - }, - // 0000001E0107 - { - MainID: 0x0000001E, Unk1: 1, CategoryID: 7, - }, - // 0000001F0107 - { - MainID: 0x0000001F, Unk1: 1, CategoryID: 7, - }, - // 0000001D0107 - { - MainID: 0x0000001D, Unk1: 1, CategoryID: 7, - }, - // 000000180107 - { - MainID: 0x00000018, Unk1: 1, CategoryID: 7, - }, - // 000000170107 - { - MainID: 0x00000017, Unk1: 1, CategoryID: 7, - }, - // 000000160107 - { - MainID: 0x00000016, Unk1: 1, CategoryID: 7, - }, - // 000000150107 - // Missing file - // { - // MainID: 0x00000015, Unk1: 1, CategoryID: 7, - // }, - // 000000190107 - { - MainID: 0x00000019, Unk1: 1, CategoryID: 7, - }, - // 000000140107 - // Missing file - // { - // MainID: 0x00000014, Unk1: 1, CategoryID: 7, - // }, - // 000000070107 - // Missing file - // { - // MainID: 0x00000007, Unk1: 1, CategoryID: 7, - // }, - // 000000090107 - // Missing file - // { - // MainID: 0x00000009, Unk1: 1, CategoryID: 7, - // }, - // 0000000D0107 - // Missing file - // { - // MainID: 0x0000000D, Unk1: 1, CategoryID: 7, - // }, - // 000000100107 - // Missing file - // { - // MainID: 0x00000010, Unk1: 1, CategoryID: 7, - // }, - // 0000000C0107 - // Missing file - // { - // MainID: 0x0000000C, Unk1: 1, CategoryID: 7, - // }, - // 0000000E0107 - // Missing file - // { - // MainID: 0x0000000E, Unk1: 1, CategoryID: 7, - // }, - // 0000000F0107 - // Missing file - // { - // MainID: 0x0000000F, Unk1: 1, CategoryID: 7, - // }, - // 000000130107 - // Missing file - // { - // MainID: 0x00000013, Unk1: 1, CategoryID: 7, - // }, - // 0000000A0107 - // Missing file - // { - // MainID: 0x0000000A, Unk1: 1, CategoryID: 7, - // }, - // 000000080107 - // Missing file - // { - // MainID: 0x00000008, Unk1: 1, CategoryID: 7, - // }, - // 0000000B0107 - // Missing file - // { - // MainID: 0x0000000B, Unk1: 1, CategoryID: 7, - // }, - // 000000120107 - // Missing file - // { - // MainID: 0x00000012, Unk1: 1, CategoryID: 7, - // }, - // 000000110107 - // Missing file - // { - // MainID: 0x00000011, Unk1: 1, CategoryID: 7, - // }, - // 000000060107 - // Missing file - // { - // MainID: 0x00000006, Unk1: 1, CategoryID: 7, - // }, - // 000000050107 - // Missing file - // { - // MainID: 0x00000005, Unk1: 1, CategoryID: 7, - // }, - // 000000040107 - // Missing file - // { - // MainID: 0x00000004, Unk1: 1, CategoryID: 7, - // }, - // 000000030107 - { - MainID: 0x00000003, Unk1: 1, CategoryID: 7, - }, - // 000000020107 - { - MainID: 0x00000002, Unk1: 1, CategoryID: 7, - }, - // 000000010107 - { - MainID: 0x00000001, Unk1: 1, CategoryID: 7, - }, - // 000000000107 - { - MainID: 0x00000000, Unk1: 1, CategoryID: 7, - }, + + scenarioData, err := s.server.db.Queryx("SELECT scenario_id, category_id FROM scenario_counter") + if err != nil { + s.logger.Error("Failed to get scenario counter info from db", zap.Error(err)) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } + bf := byteframe.NewByteFrame() + var scenarioCount uint32 + for scenarioData.Next() { + postData := &ScenarioCounterItem{} + err = scenarioData.StructScan(&postData) + if err != nil { + continue + } + scenarioCount++ + bf.WriteUint32(postData.MainID) + // if item exchange + if postData.CategoryID == 3 || postData.CategoryID == 6 || postData.CategoryID == 7 { + bf.WriteUint8(1) + + } else { + bf.WriteUint8(0) + + } + bf.WriteUint8(postData.CategoryID) } - resp := byteframe.NewByteFrame() - resp.WriteUint8(uint8(len(scenarioCounter))) // Entry count - for _, entry := range scenarioCounter { - resp.WriteUint32(entry.MainID) - resp.WriteUint8(entry.Unk1) - resp.WriteUint8(entry.CategoryID) - } + data := byteframe.NewByteFrame() + data.WriteUint8(uint8(scenarioCount)) // Entry count + data.WriteBytes(bf.Data()) + doAckBufSucceed(s, pkt.AckHandle, data.Data()) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) { From b46f0354d61c7b015a4ceee6f1ea1b0e0f7932fe Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 8 Jul 2023 00:41:03 +1000 Subject: [PATCH 002/269] typing and formatting review --- server/channelserver/handlers.go | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index f853675a1..bb6aa75b4 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -776,14 +776,13 @@ func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } +type ScenarioCounterItem struct { + MainID uint32 `db:"scenario_id"` + CategoryID uint8 `db:"category_id"` // 0 = basic, 1 = veteran, 3 = other, 6 = pallone, 7 = diva +} + func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) { - type ScenarioCounterItem struct { - MainID uint32 `db:"scenario_id"` - CategoryID uint8 `db:"category_id"` // 0 = basic, 1 = veteran, 3 = other, 6 = pallone, 7 = diva - } - pkt := p.(*mhfpacket.MsgMhfInfoScenarioCounter) - scenarioData, err := s.server.db.Queryx("SELECT scenario_id, category_id FROM scenario_counter") if err != nil { s.logger.Error("Failed to get scenario counter info from db", zap.Error(err)) @@ -791,31 +790,28 @@ func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) { return } bf := byteframe.NewByteFrame() - var scenarioCount uint32 + var scenarioCount uint8 for scenarioData.Next() { - postData := &ScenarioCounterItem{} - err = scenarioData.StructScan(&postData) + scenario := &ScenarioCounterItem{} + err = scenarioData.StructScan(&scenario) if err != nil { continue } scenarioCount++ - bf.WriteUint32(postData.MainID) + bf.WriteUint32(scenario.MainID) // if item exchange - if postData.CategoryID == 3 || postData.CategoryID == 6 || postData.CategoryID == 7 { - bf.WriteUint8(1) - + if scenario.CategoryID == 3 || scenario.CategoryID == 6 || scenario.CategoryID == 7 { + bf.WriteBool(true) } else { - bf.WriteUint8(0) - + bf.WriteBool(false) } - bf.WriteUint8(postData.CategoryID) + bf.WriteUint8(scenario.CategoryID) } data := byteframe.NewByteFrame() - data.WriteUint8(uint8(scenarioCount)) // Entry count + data.WriteUint8(scenarioCount) data.WriteBytes(bf.Data()) doAckBufSucceed(s, pkt.AckHandle, data.Data()) - } func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) { From 229e9323ba4bfc6579a917cb248984b0445d20da Mon Sep 17 00:00:00 2001 From: straticspaff Date: Fri, 7 Jul 2023 23:38:24 +0100 Subject: [PATCH 003/269] feat: added veteran + beginner scenarios --- bundled-schema/DefaultScenarios.sql | 120 ++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 bundled-schema/DefaultScenarios.sql diff --git a/bundled-schema/DefaultScenarios.sql b/bundled-schema/DefaultScenarios.sql new file mode 100644 index 000000000..d3eb2899a --- /dev/null +++ b/bundled-schema/DefaultScenarios.sql @@ -0,0 +1,120 @@ +BEGIN; + +INSERT INTO public.scenario_counter +(scenario_id, category_id) +VALUES + (0,0), + (1,0), + (2,0), + (3,0), + (4,0), + (5,0), + (6,0), + (7,0), + (8,0), + (9,0), + (10,0), + (11,0), + (12,0), + (13,0), + (14,0), + (15,0), + (16,0), + (17,0), + (18,0), + (19,0), + (0,1), + (1,1), + (2,1), + (3,1), + (4,1), + (5,1), + (6,1), + (7,1), + (8,1), + (9,1), + (10,1), + (11,1), + (12,1), + (13,1), + (16,1), + (17,1), + (19,1), + (21,1), + (22,1), + (23,1), + (27,1), + (28,1), + (29,1), + (30,1), + (31,1), + (32,1), + (33,1), + (34,1), + (35,1), + (36,1), + (37,1), + (38,1), + (39,1), + (41,1), + (42,1), + (43,1), + (46,1), + (47,1), + (48,1), + (49,1), + (58,1), + (59,1), + (60,1), + (61,1), + (62,1), + (63,1), + (64,1), + (65,1), + (66,1), + (67,1), + (68,1), + (69,1), + (70,1), + (77,1), + (79,1), + (81,1), + (82,1), + (83,1), + (84,1), + (85,1), + (86,1), + (87,1), + (88,1), + (89,1), + (90,1), + (92,1), + (93,1), + (13,1), + (14,1), + (15,1), + (18,1), + (20,1), + (24,1), + (25,1), + (26,1), + (40,1), + (50,1), + (51,1), + (52,1), + (53,1), + (54,1), + (55,1), + (56,1), + (57,1), + (71,1), + (72,1), + (73,1), + (74,1), + (75,1), + (76,1), + (78,1), + (80,1), + (91,1); + +END; \ No newline at end of file From 3fc9a6f2ac33d9abcb62afa135215c28e9196346 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 9 Jul 2023 00:34:56 +1000 Subject: [PATCH 004/269] clean up InfoScenarioCounter --- server/channelserver/handlers.go | 43 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index bb6aa75b4..c94003ffb 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -776,42 +776,53 @@ func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } -type ScenarioCounterItem struct { - MainID uint32 `db:"scenario_id"` - CategoryID uint8 `db:"category_id"` // 0 = basic, 1 = veteran, 3 = other, 6 = pallone, 7 = diva +type Scenario struct { + MainID uint32 + // 0 = Basic + // 1 = Veteran + // 3 = Other + // 6 = Pallone + // 7 = Diva + CategoryID uint8 } func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfInfoScenarioCounter) + var scenarios []Scenario + var scenario Scenario scenarioData, err := s.server.db.Queryx("SELECT scenario_id, category_id FROM scenario_counter") if err != nil { + scenarioData.Close() s.logger.Error("Failed to get scenario counter info from db", zap.Error(err)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return } - bf := byteframe.NewByteFrame() - var scenarioCount uint8 for scenarioData.Next() { - scenario := &ScenarioCounterItem{} - err = scenarioData.StructScan(&scenario) + err = scenarioData.Scan(&scenario.MainID, &scenario.CategoryID) if err != nil { continue } - scenarioCount++ + } + + // Trim excess scenarios + if len(scenarios) > 128 { + scenarios = scenarios[:128] + } + + bf := byteframe.NewByteFrame() + bf.WriteUint8(uint8(len(scenarios))) + for _, scenario := range scenarios { bf.WriteUint32(scenario.MainID) - // if item exchange - if scenario.CategoryID == 3 || scenario.CategoryID == 6 || scenario.CategoryID == 7 { + // If item exchange + switch scenario.CategoryID { + case 3, 6, 7: bf.WriteBool(true) - } else { + default: bf.WriteBool(false) } bf.WriteUint8(scenario.CategoryID) } - - data := byteframe.NewByteFrame() - data.WriteUint8(scenarioCount) - data.WriteBytes(bf.Data()) - doAckBufSucceed(s, pkt.AckHandle, data.Data()) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) { From 9886f81b2b62d68cd63fb47931811241c1e790f3 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 9 Jul 2023 01:05:27 +1000 Subject: [PATCH 005/269] fix scenario responses --- server/channelserver/handlers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index c94003ffb..4ff31dab3 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -794,7 +794,7 @@ func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) { if err != nil { scenarioData.Close() s.logger.Error("Failed to get scenario counter info from db", zap.Error(err)) - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) return } for scenarioData.Next() { @@ -802,6 +802,7 @@ func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) { if err != nil { continue } + scenarios = append(scenarios, scenario) } // Trim excess scenarios From f2bf8be1aec115c727ae62783d19250acdc17b50 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Sun, 20 Aug 2023 07:58:08 +0900 Subject: [PATCH 006/269] Auto-cycle event quests in the database --- server/channelserver/handlers_quest.go | 106 ++++++++++++++++++++----- 1 file changed, 87 insertions(+), 19 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index d45b3725b..bc61a955c 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -51,14 +51,25 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { pkt.Filename = seasonConversion(s, pkt.Filename) } - data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) - if err != nil { - s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) - // This will crash the game. + if _config.ErupeConfig.RealClientMode <= _config.F5 { + data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("old_quests/%s.bin", pkt.Filename))) + if err != nil { + s.logger.Error(fmt.Sprintf("Failed to open file: %s/old_quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) + // This will crash the game. + doAckBufSucceed(s, pkt.AckHandle, data) + return + } + doAckBufSucceed(s, pkt.AckHandle, data) + } else { + data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) + if err != nil { + s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) + // This will crash the game. + doAckBufSucceed(s, pkt.AckHandle, data) + return + } doAckBufSucceed(s, pkt.AckHandle, data) - return } - doAckBufSucceed(s, pkt.AckHandle, data) } } @@ -142,11 +153,16 @@ func loadQuestFile(s *Session, questId int) []byte { return questBody.Data() } -func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { +func makeEventQuest(s *Session, rows *sql.Rows, weeklyCycle int, currentCycle int) ([]byte, error) { var id, mark uint32 var questId int var maxPlayers, questType uint8 - rows.Scan(&id, &maxPlayers, &questType, &questId, &mark) + var availableInAllCycles bool + + err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &weeklyCycle, &availableInAllCycles) + if err != nil { + return nil, err + } data := loadQuestFile(s, questId) if data == nil { @@ -195,23 +211,75 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint16(0) - rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id") + // Check weekly_cycle_info to get the current cycle and the last timestamp + var lastCycleUpdate time.Time + var currentCycle int + + if s.server.erupeConfig.DevModeOptions.WeeklyQuestCycle { + err := s.server.db.QueryRow("SELECT current_cycle_number, last_cycle_update_timestamp FROM weekly_cycle_info").Scan(¤tCycle, &lastCycleUpdate) + if err != nil { + if err == sql.ErrNoRows { + // Insert the first data if there isn't + _, err := s.server.db.Exec("INSERT INTO weekly_cycle_info (current_cycle_number, last_cycle_update_timestamp) VALUES ($1, $2)", 1, TimeWeekStart()) + if err != nil { + panic(err) + } + currentCycle = 1 + lastCycleUpdate = TimeWeekStart() + } else { + panic(err) + } + } + } + + // Check if it's time to update the cycle + if lastCycleUpdate.Add(7 * 24 * time.Hour).Before(TimeMidnight()) { + // Update the cycle and the timestamp in the weekly_cycle_info table + newCycle := (currentCycle % 5) + 1 + _, err := s.server.db.Exec("UPDATE weekly_cycle_info SET current_cycle_number = $1, last_cycle_update_timestamp = $2", newCycle, TimeWeekStart()) + if err != nil { + panic(err) + } + currentCycle = newCycle + } + + var tableName = "event_quests" + /* This is an example of how I have to test and avoid issues, might be a thing later? + var tableName string + if _config.ErupeConfig.RealClientMode <= _config.F5 { + tableName = "event_quests_older" + } else { + tableName = "event_quests" + } + */ + + // Check the event_quests_older table to load the current week quests + rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, weekly_cycle, available_in_all_cycles FROM "+tableName+" WHERE weekly_cycle = $1 OR available_in_all_cycles = true ORDER BY quest_id", currentCycle) + if err != nil { + panic(err) + } + defer rows.Close() + + // Process the current week quests for rows.Next() { - data, err := makeEventQuest(s, rows) + var id, mark uint32 + var questId, weeklyCycle int + var maxPlayers, questType uint8 + var availableInAllCycles bool + err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &weeklyCycle, &availableInAllCycles) if err != nil { continue - } else { - if len(data) > 896 || len(data) < 352 { + } + + if (availableInAllCycles && questId != 0) || (questId != 0 && (weeklyCycle == currentCycle || availableInAllCycles)) { + data, err := makeEventQuest(s, rows, weeklyCycle, currentCycle) + if err != nil { continue } else { - totalCount++ - if _config.ErupeConfig.RealClientMode == _config.F5 { - if totalCount > pkt.Offset && len(bf.Data()) < 21550 { - returnedCount++ - bf.WriteBytes(data) - continue - } + if len(data) > 896 || len(data) < 352 { + continue } else { + totalCount++ if totalCount > pkt.Offset && len(bf.Data()) < 60000 { returnedCount++ bf.WriteBytes(data) From 292426bb5d20badc1c4f2af54b644be55e9aeb8f Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Sun, 20 Aug 2023 07:58:59 +0900 Subject: [PATCH 007/269] Update config for auto-cycle --- config/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config.go b/config/config.go index ac7e48af6..c380b7859 100644 --- a/config/config.go +++ b/config/config.go @@ -111,6 +111,7 @@ type DevModeOptions struct { EarthStatusOverride int32 EarthIDOverride int32 EarthMonsterOverride int32 + WeeklyQuestCycle bool SaveDumps SaveDumpOptions } From fe465b5de4262926a6cbbe81ac107bdbbd7a5630 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Sun, 20 Aug 2023 07:59:45 +0900 Subject: [PATCH 008/269] Update config.json for auto-cycle --- config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/config.json b/config.json index d3de778bb..f8ddc2442 100644 --- a/config.json +++ b/config.json @@ -30,6 +30,7 @@ "EarthStatusOverride": 0, "EarthIDOverride": 0, "EarthMonsterOverride": 0, + "WeeklyQuestCycle": false, "SaveDumps": { "Enabled": true, "OutputDir": "save-backups" From f5b40b55b9a84a661227add5faabe57a403a0f12 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Sun, 20 Aug 2023 08:00:39 +0900 Subject: [PATCH 009/269] Update 03-event_quests.sql for auto-cycle --- patch-schema/03-event_quests.sql | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/patch-schema/03-event_quests.sql b/patch-schema/03-event_quests.sql index 94aac0c65..affaf3954 100644 --- a/patch-schema/03-event_quests.sql +++ b/patch-schema/03-event_quests.sql @@ -6,9 +6,17 @@ create table if not exists event_quests max_players integer, quest_type integer not null, quest_id integer not null, - mark integer + mark integer, + weekly_cycle INT, + available_in_all_cycles bool default true ); ALTER TABLE IF EXISTS public.servers DROP COLUMN IF EXISTS season; -END; \ No newline at end of file +CREATE TABLE IF NOT EXISTS weekly_cycle_info ( + id SERIAL PRIMARY KEY, + current_cycle_number INT, + last_cycle_update_timestamp TIMESTAMP WITH TIME ZONE +); + +END; From f643960b6288ce4a62e346db9e18d0b4bfe6845d Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Sun, 20 Aug 2023 08:38:23 +0900 Subject: [PATCH 010/269] Cleaning leftover of personal if-else --- server/channelserver/handlers_quest.go | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index bc61a955c..d3f5a9d7a 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -51,25 +51,14 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { pkt.Filename = seasonConversion(s, pkt.Filename) } - if _config.ErupeConfig.RealClientMode <= _config.F5 { - data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("old_quests/%s.bin", pkt.Filename))) - if err != nil { - s.logger.Error(fmt.Sprintf("Failed to open file: %s/old_quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) - // This will crash the game. - doAckBufSucceed(s, pkt.AckHandle, data) - return - } - doAckBufSucceed(s, pkt.AckHandle, data) - } else { - data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) - if err != nil { - s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) - // This will crash the game. - doAckBufSucceed(s, pkt.AckHandle, data) - return - } + data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) + if err != nil { + s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) + // This will crash the game. doAckBufSucceed(s, pkt.AckHandle, data) + return } + doAckBufSucceed(s, pkt.AckHandle, data) } } From f7c4a1c925719b57a7739da6b3a384ed036f0d56 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Mon, 21 Aug 2023 03:20:46 +0900 Subject: [PATCH 011/269] Update auto-cycle code Cycle amount can now be set in the config.json. Instead of making a new database table it's now using the events table. --- server/channelserver/handlers_quest.go | 42 +++++++++++++++----------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index d3f5a9d7a..11c9c980f 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -205,29 +205,37 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { var currentCycle int if s.server.erupeConfig.DevModeOptions.WeeklyQuestCycle { - err := s.server.db.QueryRow("SELECT current_cycle_number, last_cycle_update_timestamp FROM weekly_cycle_info").Scan(¤tCycle, &lastCycleUpdate) + // Check if the "EventQuests" entry exists in the events table + var eventTypeExists bool + err := s.server.db.QueryRow("SELECT EXISTS (SELECT 1 FROM events WHERE event_type = 'EventQuests')").Scan(&eventTypeExists) if err != nil { - if err == sql.ErrNoRows { - // Insert the first data if there isn't - _, err := s.server.db.Exec("INSERT INTO weekly_cycle_info (current_cycle_number, last_cycle_update_timestamp) VALUES ($1, $2)", 1, TimeWeekStart()) - if err != nil { - panic(err) - } - currentCycle = 1 - lastCycleUpdate = TimeWeekStart() - } else { - panic(err) + fmt.Printf("Error checking for EventQuests entry: %v\n", err) + } + + if !eventTypeExists { + // Insert the initial "EventQuests" entry with the current cycle number + _, err := s.server.db.Exec("INSERT INTO events (event_type, start_time, current_cycle_number) VALUES ($1, $2, $3)", + "EventQuests", TimeWeekStart(), 1) + if err != nil { + fmt.Printf("Error inserting EventQuests entry: %v\n", err) } } + + // Get the current cycle number and last cycle update timestamp from the events table + err = s.server.db.QueryRow("SELECT current_cycle_number, start_time FROM events WHERE event_type = 'EventQuests'").Scan(¤tCycle, &lastCycleUpdate) + if err != nil { + fmt.Printf("Error getting EventQuests entry: %v\n", err) + } } // Check if it's time to update the cycle - if lastCycleUpdate.Add(7 * 24 * time.Hour).Before(TimeMidnight()) { - // Update the cycle and the timestamp in the weekly_cycle_info table - newCycle := (currentCycle % 5) + 1 - _, err := s.server.db.Exec("UPDATE weekly_cycle_info SET current_cycle_number = $1, last_cycle_update_timestamp = $2", newCycle, TimeWeekStart()) + if lastCycleUpdate.Add(time.Duration(s.server.erupeConfig.DevModeOptions.WeeklyCycleAmount) * 24 * time.Hour).Before(TimeMidnight()) { + // Update the cycle and the timestamp in the events table + newCycle := (currentCycle % s.server.erupeConfig.DevModeOptions.WeeklyCycleAmount) + 1 + _, err := s.server.db.Exec("UPDATE events SET current_cycle_number = $1, start_time = $2 WHERE event_type = 'EventQuests'", + newCycle, TimeWeekStart()) if err != nil { - panic(err) + fmt.Printf("Error updating EventQuests entry: %v\n", err) } currentCycle = newCycle } @@ -245,7 +253,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { // Check the event_quests_older table to load the current week quests rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, weekly_cycle, available_in_all_cycles FROM "+tableName+" WHERE weekly_cycle = $1 OR available_in_all_cycles = true ORDER BY quest_id", currentCycle) if err != nil { - panic(err) + fmt.Printf("Error querying event quests: %v\n", err) } defer rows.Close() From 6708c9fc8f1c6f3553405026e9a24d9a5ed3c5a2 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Mon, 21 Aug 2023 03:21:49 +0900 Subject: [PATCH 012/269] Update config.json auto cycle --- config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/config.json b/config.json index f8ddc2442..86273ac40 100644 --- a/config.json +++ b/config.json @@ -31,6 +31,7 @@ "EarthIDOverride": 0, "EarthMonsterOverride": 0, "WeeklyQuestCycle": false, + "WeeklyCycleAmount": 5, "SaveDumps": { "Enabled": true, "OutputDir": "save-backups" From c0d11bea4bc113abcd88591050e21e4171d30ac2 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Mon, 21 Aug 2023 03:22:45 +0900 Subject: [PATCH 013/269] Update config.go auto-cycle --- config/config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index c380b7859..1d5990d37 100644 --- a/config/config.go +++ b/config/config.go @@ -111,7 +111,8 @@ type DevModeOptions struct { EarthStatusOverride int32 EarthIDOverride int32 EarthMonsterOverride int32 - WeeklyQuestCycle bool + WeeklyQuestCycle bool // Enable weekly event quests cycle + WeeklyCycleAmount int // Set the amount of cycles (week) SaveDumps SaveDumpOptions } From 30a215396b3ba199f6882196f4d7c23ccd53f463 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Mon, 21 Aug 2023 03:23:37 +0900 Subject: [PATCH 014/269] Update 03-event_quests.sql auto-cycle --- patch-schema/03-event_quests.sql | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/patch-schema/03-event_quests.sql b/patch-schema/03-event_quests.sql index affaf3954..53c184765 100644 --- a/patch-schema/03-event_quests.sql +++ b/patch-schema/03-event_quests.sql @@ -13,10 +13,7 @@ create table if not exists event_quests ALTER TABLE IF EXISTS public.servers DROP COLUMN IF EXISTS season; -CREATE TABLE IF NOT EXISTS weekly_cycle_info ( - id SERIAL PRIMARY KEY, - current_cycle_number INT, - last_cycle_update_timestamp TIMESTAMP WITH TIME ZONE -); +ALTER TABLE IF EXISTS public.events ADD COLUMN IF NOT EXISTS current_cycle_number int; +ALTER TYPE event_type ADD VALUE 'EventQuests'; END; From 5d156021dceb778ece01b0ec00fee38c65004f5b Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Mon, 21 Aug 2023 08:53:49 +0900 Subject: [PATCH 015/269] Update a condition --- server/channelserver/handlers_quest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 11c9c980f..ed57ab208 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -268,7 +268,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { continue } - if (availableInAllCycles && questId != 0) || (questId != 0 && (weeklyCycle == currentCycle || availableInAllCycles)) { + if questId != 0 && (availableInAllCycles || (weeklyCycle == currentCycle && weeklyCycle != 0)) { data, err := makeEventQuest(s, rows, weeklyCycle, currentCycle) if err != nil { continue From 391e0c9d988ab97101c1522bef5ccb34227f76e2 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Mon, 21 Aug 2023 08:55:05 +0900 Subject: [PATCH 016/269] Update default weeklyCycle value This value cannot be null or the quests won't appear in game. --- patch-schema/03-event_quests.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patch-schema/03-event_quests.sql b/patch-schema/03-event_quests.sql index 53c184765..6bad468fc 100644 --- a/patch-schema/03-event_quests.sql +++ b/patch-schema/03-event_quests.sql @@ -7,7 +7,7 @@ create table if not exists event_quests quest_type integer not null, quest_id integer not null, mark integer, - weekly_cycle INT, + weekly_cycle integer default 1, available_in_all_cycles bool default true ); From d3a6121b2cc64cb554bd094ce95a34ce5515d8e6 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Wed, 23 Aug 2023 05:28:49 +0900 Subject: [PATCH 017/269] Reworke auto-cycle code --- server/channelserver/handlers_quest.go | 80 ++++++++------------------ 1 file changed, 23 insertions(+), 57 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index ed57ab208..6191a4166 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -142,16 +142,12 @@ func loadQuestFile(s *Session, questId int) []byte { return questBody.Data() } -func makeEventQuest(s *Session, rows *sql.Rows, weeklyCycle int, currentCycle int) ([]byte, error) { +func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { var id, mark uint32 - var questId int + var questId, activeDuration, inactiveDuration int var maxPlayers, questType uint8 - var availableInAllCycles bool - - err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &weeklyCycle, &availableInAllCycles) - if err != nil { - return nil, err - } + var startTime time.Time + rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &startTime, &activeDuration, &inactiveDuration) data := loadQuestFile(s, questId) if data == nil { @@ -200,46 +196,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint16(0) - // Check weekly_cycle_info to get the current cycle and the last timestamp - var lastCycleUpdate time.Time - var currentCycle int - - if s.server.erupeConfig.DevModeOptions.WeeklyQuestCycle { - // Check if the "EventQuests" entry exists in the events table - var eventTypeExists bool - err := s.server.db.QueryRow("SELECT EXISTS (SELECT 1 FROM events WHERE event_type = 'EventQuests')").Scan(&eventTypeExists) - if err != nil { - fmt.Printf("Error checking for EventQuests entry: %v\n", err) - } - - if !eventTypeExists { - // Insert the initial "EventQuests" entry with the current cycle number - _, err := s.server.db.Exec("INSERT INTO events (event_type, start_time, current_cycle_number) VALUES ($1, $2, $3)", - "EventQuests", TimeWeekStart(), 1) - if err != nil { - fmt.Printf("Error inserting EventQuests entry: %v\n", err) - } - } - - // Get the current cycle number and last cycle update timestamp from the events table - err = s.server.db.QueryRow("SELECT current_cycle_number, start_time FROM events WHERE event_type = 'EventQuests'").Scan(¤tCycle, &lastCycleUpdate) - if err != nil { - fmt.Printf("Error getting EventQuests entry: %v\n", err) - } - } - - // Check if it's time to update the cycle - if lastCycleUpdate.Add(time.Duration(s.server.erupeConfig.DevModeOptions.WeeklyCycleAmount) * 24 * time.Hour).Before(TimeMidnight()) { - // Update the cycle and the timestamp in the events table - newCycle := (currentCycle % s.server.erupeConfig.DevModeOptions.WeeklyCycleAmount) + 1 - _, err := s.server.db.Exec("UPDATE events SET current_cycle_number = $1, start_time = $2 WHERE event_type = 'EventQuests'", - newCycle, TimeWeekStart()) - if err != nil { - fmt.Printf("Error updating EventQuests entry: %v\n", err) - } - currentCycle = newCycle - } - + currentTime := time.Now() var tableName = "event_quests" /* This is an example of how I have to test and avoid issues, might be a thing later? var tableName string @@ -249,27 +206,36 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { tableName = "event_quests" } */ - - // Check the event_quests_older table to load the current week quests - rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, weekly_cycle, available_in_all_cycles FROM "+tableName+" WHERE weekly_cycle = $1 OR available_in_all_cycles = true ORDER BY quest_id", currentCycle) + // Check the event_quests table to load the quests with rotation system + rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, start_time, active_duration, inactive_duration FROM " + tableName + "") if err != nil { fmt.Printf("Error querying event quests: %v\n", err) } defer rows.Close() - // Process the current week quests for rows.Next() { var id, mark uint32 - var questId, weeklyCycle int + var questId, activeDuration, inactiveDuration int var maxPlayers, questType uint8 - var availableInAllCycles bool - err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &weeklyCycle, &availableInAllCycles) + var startTime time.Time + err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &startTime, &activeDuration, &inactiveDuration) if err != nil { continue } - if questId != 0 && (availableInAllCycles || (weeklyCycle == currentCycle && weeklyCycle != 0)) { - data, err := makeEventQuest(s, rows, weeklyCycle, currentCycle) + // Calculate the rotation time based on start time, active duration, and inactive duration + rotationTime := startTime.Add(time.Duration(activeDuration+inactiveDuration) * 24 * time.Hour) + if currentTime.After(rotationTime) { + // The rotation time has passed, update the start time and reset the rotation + _, err := s.server.db.Exec("UPDATE "+tableName+" SET start_time = $1 WHERE quest_id = $2", rotationTime, questId) + if err != nil { + fmt.Printf("Error updating start time for quest: %v\n", err) + } + } + + // Check if the quest is currently active + if currentTime.After(startTime) && currentTime.Sub(startTime) <= time.Duration(activeDuration)*24*time.Hour { + data, err := makeEventQuest(s, rows) if err != nil { continue } else { From c4f11721c6de5f9ec8c7a6db74b5608225f2fc25 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Wed, 23 Aug 2023 05:30:02 +0900 Subject: [PATCH 018/269] Removed auto-cycle option --- config.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/config.json b/config.json index 86273ac40..d3de778bb 100644 --- a/config.json +++ b/config.json @@ -30,8 +30,6 @@ "EarthStatusOverride": 0, "EarthIDOverride": 0, "EarthMonsterOverride": 0, - "WeeklyQuestCycle": false, - "WeeklyCycleAmount": 5, "SaveDumps": { "Enabled": true, "OutputDir": "save-backups" From 43c59da809902e03c4f41f670d568ddf7da49bac Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Wed, 23 Aug 2023 05:30:27 +0900 Subject: [PATCH 019/269] Removed auto-cycle option --- config/config.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/config/config.go b/config/config.go index 1d5990d37..ac7e48af6 100644 --- a/config/config.go +++ b/config/config.go @@ -111,8 +111,6 @@ type DevModeOptions struct { EarthStatusOverride int32 EarthIDOverride int32 EarthMonsterOverride int32 - WeeklyQuestCycle bool // Enable weekly event quests cycle - WeeklyCycleAmount int // Set the amount of cycles (week) SaveDumps SaveDumpOptions } From 5cd268df23d0298167eb73143b02dcc213d1ccc9 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Wed, 23 Aug 2023 05:31:12 +0900 Subject: [PATCH 020/269] Update 03-event_quests.sql auto-cycle --- patch-schema/03-event_quests.sql | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/patch-schema/03-event_quests.sql b/patch-schema/03-event_quests.sql index 6bad468fc..b8003954f 100644 --- a/patch-schema/03-event_quests.sql +++ b/patch-schema/03-event_quests.sql @@ -7,13 +7,11 @@ create table if not exists event_quests quest_type integer not null, quest_id integer not null, mark integer, - weekly_cycle integer default 1, - available_in_all_cycles bool default true + start_time timestamp with time zone NOT NULL DEFAULT (CURRENT_DATE + interval '0 second'), + active_duration int not null, + inactive_duration int not null ); ALTER TABLE IF EXISTS public.servers DROP COLUMN IF EXISTS season; -ALTER TABLE IF EXISTS public.events ADD COLUMN IF NOT EXISTS current_cycle_number int; -ALTER TYPE event_type ADD VALUE 'EventQuests'; - END; From b74e571beb52982a35fe4dae80012521a0aec485 Mon Sep 17 00:00:00 2001 From: "Ewerton B. S" Date: Wed, 23 Aug 2023 08:07:15 +0900 Subject: [PATCH 021/269] Update handlers_quest.go --- server/channelserver/handlers_quest.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 6191a4166..721ffc831 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -199,17 +199,14 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { currentTime := time.Now() var tableName = "event_quests" /* This is an example of how I have to test and avoid issues, might be a thing later? - var tableName string - if _config.ErupeConfig.RealClientMode <= _config.F5 { + if _config.ErupeConfig.RealClientMode <= _config.F5 { tableName = "event_quests_older" - } else { - tableName = "event_quests" } */ // Check the event_quests table to load the quests with rotation system rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, start_time, active_duration, inactive_duration FROM " + tableName + "") if err != nil { - fmt.Printf("Error querying event quests: %v\n", err) + return } defer rows.Close() @@ -229,7 +226,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { // The rotation time has passed, update the start time and reset the rotation _, err := s.server.db.Exec("UPDATE "+tableName+" SET start_time = $1 WHERE quest_id = $2", rotationTime, questId) if err != nil { - fmt.Printf("Error updating start time for quest: %v\n", err) + return } } From 1685f409e711bfa10aec273921f3cfd5c8dbfb78 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 27 Aug 2023 22:16:51 +1000 Subject: [PATCH 022/269] initial ravi-v3 commit --- network/mhfpacket/msg_sys_load_register.go | 4 +- network/mhfpacket/msg_sys_operate_register.go | 3 +- server/channelserver/handlers_cast_binary.go | 18 +- server/channelserver/handlers_register.go | 281 +++--------------- server/channelserver/sys_channel_server.go | 86 +++--- 5 files changed, 103 insertions(+), 289 deletions(-) diff --git a/network/mhfpacket/msg_sys_load_register.go b/network/mhfpacket/msg_sys_load_register.go index 730616d65..7e1ac5950 100644 --- a/network/mhfpacket/msg_sys_load_register.go +++ b/network/mhfpacket/msg_sys_load_register.go @@ -11,7 +11,7 @@ import ( type MsgSysLoadRegister struct { AckHandle uint32 RegisterID uint32 - Unk1 uint8 + Values uint8 } // Opcode returns the ID associated with this packet type. @@ -23,7 +23,7 @@ func (m *MsgSysLoadRegister) Opcode() network.PacketID { func (m *MsgSysLoadRegister) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.RegisterID = bf.ReadUint32() - m.Unk1 = bf.ReadUint8() + m.Values = bf.ReadUint8() _ = bf.ReadUint8() _ = bf.ReadUint16() return nil diff --git a/network/mhfpacket/msg_sys_operate_register.go b/network/mhfpacket/msg_sys_operate_register.go index 6978609b1..c51a483e3 100644 --- a/network/mhfpacket/msg_sys_operate_register.go +++ b/network/mhfpacket/msg_sys_operate_register.go @@ -25,7 +25,8 @@ func (m *MsgSysOperateRegister) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl m.SemaphoreID = bf.ReadUint32() _ = bf.ReadUint16() dataSize := bf.ReadUint16() - m.RawDataPayload = bf.ReadBytes(uint(dataSize)) + m.RawDataPayload = bf.ReadBytes(uint(dataSize) - 1) + _ = bf.ReadBytes(1) // Null terminator return nil } diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index d93cf1a04..bd6ea053c 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -263,32 +263,32 @@ func parseChatCommand(s *Session, command string) { 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 + if s.server.raviente.register[1] == 0 { + s.server.raviente.register[1] = s.server.raviente.register[3] 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))) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.GetRaviMultiplier())) } else if strings.HasPrefix(command, "!ravi sr") || strings.HasPrefix(command, "!ravi sendres") { - if s.server.raviente.state.stateData[28] > 0 { + if s.server.raviente.state[28] > 0 { sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"]) - s.server.raviente.state.stateData[28] = 0 + s.server.raviente.state[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 + HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4] + s.server.raviente.support[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 + HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4] + s.server.raviente.support[1] = HP + 12 } else { sendServerChatMessage(s, s.server.dict["commandRaviError"]) } diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index 33261a94e..be423d206 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -3,243 +3,69 @@ package channelserver import ( "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" + "go.uber.org/zap" "strings" ) +type RaviUpdate struct { + Op uint8 + Dest uint8 + Data uint32 +} + func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysOperateRegister) + + var raviUpdates []RaviUpdate + var raviUpdate RaviUpdate bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) - s.server.raviente.Lock() - switch pkt.SemaphoreID { - case 4: - resp := byteframe.NewByteFrame() - size := 6 - for i := 0; i < len(bf.Data())-1; i += size { - op := bf.ReadUint8() - dest := bf.ReadUint8() - data := bf.ReadUint32() - resp.WriteUint8(1) - resp.WriteUint8(dest) - ref := &s.server.raviente.state.stateData[dest] - damageMultiplier := s.server.raviente.GetRaviMultiplier(s.server) - switch op { - case 2: - resp.WriteUint32(*ref) - if dest == 28 { // Berserk resurrection tracker - resp.WriteUint32(*ref + data) - *ref += data - } else if dest == 17 { // Berserk poison tracker - if damageMultiplier == 1 { - resp.WriteUint32(*ref + data) - *ref += data - } else { - resp.WriteUint32(*ref) - } - } else { - data = uint32(float64(data) * damageMultiplier) - resp.WriteUint32(*ref + data) - *ref += data - } - case 13: - fallthrough - case 14: - resp.WriteUint32(0) - resp.WriteUint32(data) - *ref = data - } - } - resp.WriteUint8(0) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - case 5: - resp := byteframe.NewByteFrame() - size := 6 - for i := 0; i < len(bf.Data())-1; i += size { - op := bf.ReadUint8() - dest := bf.ReadUint8() - data := bf.ReadUint32() - resp.WriteUint8(1) - resp.WriteUint8(dest) - ref := &s.server.raviente.support.supportData[dest] - switch op { - case 2: - resp.WriteUint32(*ref) - resp.WriteUint32(*ref + data) - *ref += data - case 13: - fallthrough - case 14: - resp.WriteUint32(0) - resp.WriteUint32(data) - *ref = data - } - } - resp.WriteUint8(0) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - case 6: - resp := byteframe.NewByteFrame() - size := 6 - for i := 0; i < len(bf.Data())-1; i += size { - op := bf.ReadUint8() - dest := bf.ReadUint8() - data := bf.ReadUint32() - resp.WriteUint8(1) - resp.WriteUint8(dest) - switch dest { - case 0: - resp.WriteUint32(0) - resp.WriteUint32(data) - s.server.raviente.register.nextTime = data - case 1: - resp.WriteUint32(0) - resp.WriteUint32(data) - s.server.raviente.register.startTime = data - case 2: - resp.WriteUint32(0) - resp.WriteUint32(data) - s.server.raviente.register.killedTime = data - case 3: - resp.WriteUint32(0) - resp.WriteUint32(data) - s.server.raviente.register.postTime = data - case 4: - ref := &s.server.raviente.register.register[0] - switch op { - case 2: - resp.WriteUint32(*ref) - resp.WriteUint32(*ref + data) - *ref += data - case 13: - resp.WriteUint32(0) - resp.WriteUint32(data) - *ref = data - case 14: - resp.WriteUint32(0) - resp.WriteUint32(data) - } - case 5: - resp.WriteUint32(0) - resp.WriteUint32(data) - s.server.raviente.register.carveQuest = data - case 6: - ref := &s.server.raviente.register.register[1] - switch op { - case 2: - resp.WriteUint32(*ref) - resp.WriteUint32(*ref + data) - *ref += data - case 13: - resp.WriteUint32(0) - resp.WriteUint32(data) - *ref = data - case 14: - resp.WriteUint32(0) - resp.WriteUint32(data) - } - case 7: - ref := &s.server.raviente.register.register[2] - switch op { - case 2: - resp.WriteUint32(*ref) - resp.WriteUint32(*ref + data) - *ref += data - case 13: - resp.WriteUint32(0) - resp.WriteUint32(data) - *ref = data - case 14: - resp.WriteUint32(0) - resp.WriteUint32(data) - } - case 8: - ref := &s.server.raviente.register.register[3] - switch op { - case 2: - resp.WriteUint32(*ref) - resp.WriteUint32(*ref + data) - *ref += data - case 13: - resp.WriteUint32(0) - resp.WriteUint32(data) - *ref = data - case 14: - resp.WriteUint32(0) - resp.WriteUint32(data) - } - case 9: - resp.WriteUint32(0) - resp.WriteUint32(data) - s.server.raviente.register.maxPlayers = data - case 10: - resp.WriteUint32(0) - resp.WriteUint32(data) - s.server.raviente.register.ravienteType = data - case 11: - ref := &s.server.raviente.register.register[4] - switch op { - case 2: - resp.WriteUint32(*ref) - resp.WriteUint32(*ref + data) - *ref += data - case 13: - resp.WriteUint32(0) - resp.WriteUint32(data) - *ref = data - case 14: - resp.WriteUint32(0) - resp.WriteUint32(data) - } - default: - resp.WriteUint32(0) - resp.WriteUint32(0) - } - } - resp.WriteUint8(0) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + for i := len(pkt.RawDataPayload) / 6; i > 0; i-- { + raviUpdate.Op = bf.ReadUint8() + raviUpdate.Dest = bf.ReadUint8() + raviUpdate.Data = bf.ReadUint32() + s.logger.Debug("RaviOps", zap.Uint8s("Op/Dest", []uint8{raviUpdate.Op, raviUpdate.Dest}), zap.Uint32s("Sema/Data", []uint32{pkt.SemaphoreID, raviUpdate.Data})) + raviUpdates = append(raviUpdates, raviUpdate) } + bf = byteframe.NewByteFrame() + + var _old, _new uint32 + s.server.raviente.Lock() + for _, update := range raviUpdates { + switch update.Op { + case 2: + _old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, true) + case 13, 14: + _old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, false) + } + bf.WriteUint8(1) + bf.WriteUint8(update.Dest) + bf.WriteUint32(_old) + bf.WriteUint32(_new) + } + s.server.raviente.Unlock() + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente { s.notifyRavi() } - s.server.raviente.Unlock() } func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysLoadRegister) - r := pkt.Unk1 - switch r { - case 12: - resp := byteframe.NewByteFrame() - resp.WriteUint8(0) - resp.WriteUint8(12) - resp.WriteUint32(s.server.raviente.register.nextTime) - resp.WriteUint32(s.server.raviente.register.startTime) - resp.WriteUint32(s.server.raviente.register.killedTime) - resp.WriteUint32(s.server.raviente.register.postTime) - resp.WriteUint32(s.server.raviente.register.register[0]) - resp.WriteUint32(s.server.raviente.register.carveQuest) - resp.WriteUint32(s.server.raviente.register.register[1]) - resp.WriteUint32(s.server.raviente.register.register[2]) - resp.WriteUint32(s.server.raviente.register.register[3]) - resp.WriteUint32(s.server.raviente.register.maxPlayers) - resp.WriteUint32(s.server.raviente.register.ravienteType) - resp.WriteUint32(s.server.raviente.register.register[4]) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - case 29: - resp := byteframe.NewByteFrame() - resp.WriteUint8(0) - resp.WriteUint8(29) - for _, v := range s.server.raviente.state.stateData { - resp.WriteUint32(v) + bf := byteframe.NewByteFrame() + bf.WriteUint8(0) + bf.WriteUint8(pkt.Values) + for i := uint8(0); i < pkt.Values; i++ { + switch pkt.RegisterID { + case 4: + bf.WriteUint32(s.server.raviente.state[i]) + case 5: + bf.WriteUint32(s.server.raviente.support[i]) + case 6: + bf.WriteUint32(s.server.raviente.register[i]) } - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - case 25: - resp := byteframe.NewByteFrame() - resp.WriteUint8(0) - resp.WriteUint8(25) - for _, v := range s.server.raviente.support.supportData { - resp.WriteUint32(v) - } - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func (s *Session) notifyRavi() { @@ -282,18 +108,7 @@ func getRaviSemaphore(s *Server) *Semaphore { } func resetRavi(s *Session) { - s.server.raviente.Lock() - s.server.raviente.register.nextTime = 0 - s.server.raviente.register.startTime = 0 - s.server.raviente.register.killedTime = 0 - s.server.raviente.register.postTime = 0 - s.server.raviente.register.ravienteType = 0 - s.server.raviente.register.maxPlayers = 0 - s.server.raviente.register.carveQuest = 0 - 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} - s.server.raviente.Unlock() + s.server.raviente = NewRaviente() } func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index cad64bbe4..6273a7939 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -76,61 +76,25 @@ type Server struct { type Raviente struct { sync.Mutex - - register *RavienteRegister - state *RavienteState - support *RavienteSupport + register []uint32 + state []uint32 + support []uint32 } -type RavienteRegister struct { - nextTime uint32 - startTime uint32 - postTime uint32 - killedTime uint32 - ravienteType uint32 - maxPlayers uint32 - carveQuest uint32 - register []uint32 -} - -type RavienteState struct { - stateData []uint32 -} - -type RavienteSupport struct { - supportData []uint32 -} - -// Set up the Raviente variables for the server func NewRaviente() *Raviente { - ravienteRegister := &RavienteRegister{ - nextTime: 0, - startTime: 0, - killedTime: 0, - postTime: 0, - ravienteType: 0, - maxPlayers: 0, - carveQuest: 0, - } - 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} - ravienteSupport.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} - raviente := &Raviente{ - register: ravienteRegister, - state: ravienteState, - support: ravienteSupport, + register: make([]uint32, 30), + state: make([]uint32, 30), + support: make([]uint32, 30), } return raviente } -func (r *Raviente) GetRaviMultiplier(s *Server) float64 { +func (s *Server) GetRaviMultiplier() float64 { raviSema := getRaviSemaphore(s) if raviSema != nil { var minPlayers int - if r.register.maxPlayers > 8 { + if s.raviente.register[9] > 8 { minPlayers = 24 } else { minPlayers = 4 @@ -143,6 +107,40 @@ func (r *Raviente) GetRaviMultiplier(s *Server) float64 { return 0 } +func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update bool) (uint32, uint32) { + var prev uint32 + switch semaID { + case 4: + switch index { + case 17, 28: // Ignore res and poison + break + default: + value = uint32(float64(value) * s.GetRaviMultiplier()) + } + prev = s.raviente.state[index] + if prev != 0 && !update { + return prev, prev + } + s.raviente.state[index] += value + return prev, s.raviente.state[index] + case 5: + prev = s.raviente.support[index] + if prev != 0 && !update { + return prev, prev + } + s.raviente.support[index] += value + return prev, s.raviente.support[index] + case 6: + prev = s.raviente.register[index] + if prev != 0 && !update { + return prev, prev + } + s.raviente.register[index] += value + return prev, s.raviente.register[index] + } + return 0, 0 +} + // NewServer creates a new Server type. func NewServer(config *Config) *Server { s := &Server{ From e6f1298935e8554d03ea89ff526059625cffe5e0 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 30 Aug 2023 00:15:52 +1000 Subject: [PATCH 023/269] disable incompatible ravi commands --- server/channelserver/handlers_cast_binary.go | 37 ++++++++++++-------- server/channelserver/sys_language.go | 2 ++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 390a5fc74..6652c748f 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -258,23 +258,30 @@ func parseChatCommand(s *Session, command string) { } case "cm", "check", "checkmultiplier", "multiplier": sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.GetRaviMultiplier())) - case "sr", "sendres", "resurrection": - if s.server.raviente.state[28] > 0 { - sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"]) - s.server.raviente.state[28] = 0 + case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed": + if s.server.erupeConfig.RealClientMode == _config.ZZ { + switch args[1] { + case "sr", "sendres", "resurrection": + if s.server.raviente.state[28] > 0 { + sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"]) + s.server.raviente.state[28] = 0 + } else { + sendServerChatMessage(s, s.server.dict["commandRaviResError"]) + } + case "ss", "sendsed": + sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"]) + // Total BerRavi HP + HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4] + s.server.raviente.support[1] = HP + case "rs", "reqsed": + sendServerChatMessage(s, s.server.dict["commandRaviRequest"]) + // Total BerRavi HP + HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4] + s.server.raviente.support[1] = HP + 1 + } } else { - sendServerChatMessage(s, s.server.dict["commandRaviResError"]) + sendServerChatMessage(s, s.server.dict["commandRaviVersion"]) } - case "ss", "sendsed": - sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"]) - // Total BerRavi HP - HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4] - s.server.raviente.support[1] = HP - case "rs", "reqsed": - sendServerChatMessage(s, s.server.dict["commandRaviRequest"]) - // Total BerRavi HP - HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4] - s.server.raviente.support[1] = HP + 1 default: sendServerChatMessage(s, s.server.dict["commandRaviError"]) } diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index 418e1cecb..dbd48cfb8 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -34,6 +34,7 @@ func getLangStrings(s *Server) map[string]string { strings["commandRaviRequest"] = "鎮静支援を要請します" strings["commandRaviError"] = "ラヴィコマンドが認識されません" strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません" + strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ" strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!" strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!" @@ -85,6 +86,7 @@ func getLangStrings(s *Server) map[string]string { strings["commandRaviRequest"] = "Requesting sedation support!" strings["commandRaviError"] = "Raviente command not recognised!" strings["commandRaviNoPlayers"] = "No one has joined the Great Slaying!" + strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ" strings["ravienteBerserk"] = " is being held!" strings["ravienteExtreme"] = " is being held!" From 81e8d21d4b0fabff41ee786e4a2238a57e8ee70f Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 30 Aug 2023 21:08:40 +1000 Subject: [PATCH 024/269] clean up --- network/mhfpacket/msg_sys_operate_register.go | 3 +-- server/channelserver/handlers_register.go | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/network/mhfpacket/msg_sys_operate_register.go b/network/mhfpacket/msg_sys_operate_register.go index c51a483e3..6978609b1 100644 --- a/network/mhfpacket/msg_sys_operate_register.go +++ b/network/mhfpacket/msg_sys_operate_register.go @@ -25,8 +25,7 @@ func (m *MsgSysOperateRegister) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl m.SemaphoreID = bf.ReadUint32() _ = bf.ReadUint16() dataSize := bf.ReadUint16() - m.RawDataPayload = bf.ReadBytes(uint(dataSize) - 1) - _ = bf.ReadBytes(1) // Null terminator + m.RawDataPayload = bf.ReadBytes(uint(dataSize)) return nil } diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index be423d206..f060b664e 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -3,7 +3,6 @@ package channelserver import ( "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" - "go.uber.org/zap" "strings" ) @@ -18,12 +17,12 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) { var raviUpdates []RaviUpdate var raviUpdate RaviUpdate - bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) + // Strip null terminator + bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[:len(pkt.RawDataPayload)-1]) for i := len(pkt.RawDataPayload) / 6; i > 0; i-- { raviUpdate.Op = bf.ReadUint8() raviUpdate.Dest = bf.ReadUint8() raviUpdate.Data = bf.ReadUint32() - s.logger.Debug("RaviOps", zap.Uint8s("Op/Dest", []uint8{raviUpdate.Op, raviUpdate.Dest}), zap.Uint32s("Sema/Data", []uint32{pkt.SemaphoreID, raviUpdate.Data})) raviUpdates = append(raviUpdates, raviUpdate) } bf = byteframe.NewByteFrame() From 813a3df6a7043c4697feec1cc643fa57eee7d8ed Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 30 Aug 2023 22:18:37 +1000 Subject: [PATCH 025/269] correctly parse RegisterEvent & move handlers --- network/mhfpacket/msg_mhf_register_event.go | 12 +++--- server/channelserver/handlers_event.go | 38 ------------------- server/channelserver/handlers_register.go | 42 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/network/mhfpacket/msg_mhf_register_event.go b/network/mhfpacket/msg_mhf_register_event.go index aaa5b51a8..956c4a399 100644 --- a/network/mhfpacket/msg_mhf_register_event.go +++ b/network/mhfpacket/msg_mhf_register_event.go @@ -1,20 +1,19 @@ package mhfpacket import ( + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfRegisterEvent represents the MSG_MHF_REGISTER_EVENT type MsgMhfRegisterEvent struct { AckHandle uint32 Unk0 uint16 - Unk1 uint8 - Unk2 uint8 + WorldID uint16 + LandID uint16 Unk3 uint8 Unk4 uint8 - Unk5 uint16 } // Opcode returns the ID associated with this packet type. @@ -26,11 +25,10 @@ func (m *MsgMhfRegisterEvent) Opcode() network.PacketID { func (m *MsgMhfRegisterEvent) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint8() - m.Unk2 = bf.ReadUint8() + m.WorldID = bf.ReadUint16() + m.LandID = bf.ReadUint16() m.Unk3 = bf.ReadUint8() m.Unk4 = bf.ReadUint8() - m.Unk5 = bf.ReadUint16() return nil } diff --git a/server/channelserver/handlers_event.go b/server/channelserver/handlers_event.go index 74d0cef13..d01f92a3d 100644 --- a/server/channelserver/handlers_event.go +++ b/server/channelserver/handlers_event.go @@ -10,44 +10,6 @@ import ( "erupe-ce/network/mhfpacket" ) -func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfRegisterEvent) - bf := byteframe.NewByteFrame() - bf.WriteUint8(pkt.Unk2) - bf.WriteUint8(pkt.Unk4) - bf.WriteUint16(0x1142) - doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) -} - -func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfReleaseEvent) - - // Do this ack manually because it uses a non-(0|1) error code - /* - _ACK_SUCCESS = 0 - _ACK_ERROR = 1 - - _ACK_EINPROGRESS = 16 - _ACK_ENOENT = 17 - _ACK_ENOSPC = 18 - _ACK_ETIMEOUT = 19 - - _ACK_EINVALID = 64 - _ACK_EFAILED = 65 - _ACK_ENOMEM = 66 - _ACK_ENOTEXIT = 67 - _ACK_ENOTREADY = 68 - _ACK_EALREADY = 69 - _ACK_DISABLE_WORK = 71 - */ - s.QueueSendMHF(&mhfpacket.MsgSysAck{ - AckHandle: pkt.AckHandle, - IsBufferResponse: false, - ErrorCode: 0x41, - AckData: []byte{0x00, 0x00, 0x00, 0x00}, - }) -} - type Event struct { EventType uint16 Unk1 uint16 diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index f060b664e..911249554 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -6,6 +6,48 @@ import ( "strings" ) +func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfRegisterEvent) + bf := byteframe.NewByteFrame() + if pkt.Unk3 > 0 { + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } + bf.WriteUint8(uint8(pkt.WorldID)) + bf.WriteUint8(uint8(pkt.LandID)) + bf.WriteUint16(0x1142) // Probably random ID + doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) +} + +func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfReleaseEvent) + + // Do this ack manually because it uses a non-(0|1) error code + /* + _ACK_SUCCESS = 0 + _ACK_ERROR = 1 + + _ACK_EINPROGRESS = 16 + _ACK_ENOENT = 17 + _ACK_ENOSPC = 18 + _ACK_ETIMEOUT = 19 + + _ACK_EINVALID = 64 + _ACK_EFAILED = 65 + _ACK_ENOMEM = 66 + _ACK_ENOTEXIT = 67 + _ACK_ENOTREADY = 68 + _ACK_EALREADY = 69 + _ACK_DISABLE_WORK = 71 + */ + s.QueueSendMHF(&mhfpacket.MsgSysAck{ + AckHandle: pkt.AckHandle, + IsBufferResponse: false, + ErrorCode: 0x41, + AckData: []byte{0x00, 0x00, 0x00, 0x00}, + }) +} + type RaviUpdate struct { Op uint8 Dest uint8 From 42abdfb0c7ea7b01b4f52560a9da314a299767d4 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 30 Aug 2023 22:29:49 +1000 Subject: [PATCH 026/269] change getRaviSemaphore scope & handle RegisterEvent --- network/mhfpacket/msg_mhf_release_event.go | 4 ++-- server/channelserver/handlers_cast_binary.go | 4 ++-- server/channelserver/handlers_register.go | 7 ++++--- server/channelserver/sys_channel_server.go | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/network/mhfpacket/msg_mhf_release_event.go b/network/mhfpacket/msg_mhf_release_event.go index 08e507c81..52178279b 100644 --- a/network/mhfpacket/msg_mhf_release_event.go +++ b/network/mhfpacket/msg_mhf_release_event.go @@ -11,7 +11,7 @@ import ( // MsgMhfReleaseEvent represents the MSG_MHF_RELEASE_EVENT type MsgMhfReleaseEvent struct { AckHandle uint32 - Unk0 uint32 + RaviID uint32 Unk1 uint32 } @@ -23,7 +23,7 @@ func (m *MsgMhfReleaseEvent) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfReleaseEvent) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint32() + m.RaviID = bf.ReadUint32() m.Unk1 = bf.ReadUint32() return nil } diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 6652c748f..ded566268 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -246,7 +246,7 @@ func parseChatCommand(s *Session, command string) { case commands["Raviente"].Prefix: if commands["Raviente"].Enabled { if len(args) > 1 { - if getRaviSemaphore(s.server) != nil { + if s.server.getRaviSemaphore() != nil { switch args[1] { case "start": if s.server.raviente.register[1] == 0 { @@ -420,7 +420,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { } case BroadcastTypeServer: if pkt.MessageType == 1 { - if getRaviSemaphore(s.server) != nil { + if s.server.getRaviSemaphore() != nil { s.server.BroadcastMHF(resp, s) } } else { diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index 911249554..57bce3e61 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -9,7 +9,8 @@ import ( func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfRegisterEvent) bf := byteframe.NewByteFrame() - if pkt.Unk3 > 0 { + // Some kind of check if there's already a session + if pkt.Unk3 > 0 && s.server.getRaviSemaphore() == nil { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return } @@ -110,7 +111,7 @@ func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) { } func (s *Session) notifyRavi() { - sema := getRaviSemaphore(s.server) + sema := s.server.getRaviSemaphore() if sema == nil { return } @@ -139,7 +140,7 @@ func (s *Session) notifyRavi() { } } -func getRaviSemaphore(s *Server) *Semaphore { +func (s *Server) getRaviSemaphore() *Semaphore { for _, semaphore := range s.semaphore { if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") { return semaphore diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 6273a7939..0de5650c0 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -91,7 +91,7 @@ func NewRaviente() *Raviente { } func (s *Server) GetRaviMultiplier() float64 { - raviSema := getRaviSemaphore(s) + raviSema := s.getRaviSemaphore() if raviSema != nil { var minPlayers int if s.raviente.register[9] > 8 { From 5b5621a3d8455c6dd8753b97ea62fcc759685cbf Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 30 Aug 2023 23:43:56 +1000 Subject: [PATCH 027/269] fix and rework various Raviente ID systems --- server/channelserver/handlers_register.go | 8 ++------ server/channelserver/handlers_semaphore.go | 8 ++++---- server/channelserver/sys_channel_server.go | 19 +++++++++++-------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index 57bce3e61..2135af6fb 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -16,7 +16,7 @@ func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint8(uint8(pkt.WorldID)) bf.WriteUint8(uint8(pkt.LandID)) - bf.WriteUint16(0x1142) // Probably random ID + bf.WriteUint16(s.server.raviente.id) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) } @@ -142,15 +142,11 @@ func (s *Session) notifyRavi() { func (s *Server) getRaviSemaphore() *Semaphore { for _, semaphore := range s.semaphore { - if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") { + if strings.HasPrefix(semaphore.id_semaphore, "hs_l0") && strings.HasSuffix(semaphore.id_semaphore, "3") { return semaphore } } return nil } -func resetRavi(s *Session) { - s.server.raviente = NewRaviente() -} - func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_semaphore.go b/server/channelserver/handlers_semaphore.go index 503df5110..21b12682e 100644 --- a/server/channelserver/handlers_semaphore.go +++ b/server/channelserver/handlers_semaphore.go @@ -35,7 +35,7 @@ func destructEmptySemaphores(s *Session) { s.server.semaphoreLock.Unlock() delete(s.server.semaphore, id) s.server.semaphoreLock.Lock() - if strings.HasPrefix(id, "hs_l0u3B5") { + if strings.HasPrefix(id, "hs_l0") { releaseRaviSemaphore(s, sema) } s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id)) @@ -49,7 +49,7 @@ func releaseRaviSemaphore(s *Session, sema *Semaphore) { delete(sema.clients, s) if strings.HasSuffix(sema.id_semaphore, "2") && len(sema.clients) == 0 { s.logger.Debug("Main raviente semaphore is empty, resetting") - resetRavi(s) + s.server.resetRaviente() } } @@ -60,7 +60,7 @@ func handleMsgSysDeleteSemaphore(s *Session, p mhfpacket.MHFPacket) { s.server.semaphoreLock.Lock() for id, sema := range s.server.semaphore { if sema.id == pkt.SemaphoreID { - if strings.HasPrefix(id, "hs_l0u3B5") { + if strings.HasPrefix(id, "hs_l0") { releaseRaviSemaphore(s, sema) s.server.semaphoreLock.Unlock() return @@ -84,7 +84,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { fmt.Printf("Got reserve stage req, StageID: %v\n\n", SemaphoreID) if !exists { s.server.semaphoreLock.Lock() - if strings.HasPrefix(SemaphoreID, "hs_l0u3B5") { + if strings.HasPrefix(SemaphoreID, "hs_l0") { suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:]) s.server.semaphore[SemaphoreID] = &Semaphore{ id_semaphore: pkt.SemaphoreID, diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 0de5650c0..8c0e9fe23 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -76,18 +76,17 @@ type Server struct { type Raviente struct { sync.Mutex + id uint16 register []uint32 state []uint32 support []uint32 } -func NewRaviente() *Raviente { - raviente := &Raviente{ - register: make([]uint32, 30), - state: make([]uint32, 30), - support: make([]uint32, 30), - } - return raviente +func (s *Server) resetRaviente() { + s.raviente.id = s.raviente.id + 1 + s.raviente.register = make([]uint32, 30) + s.raviente.state = make([]uint32, 30) + s.raviente.support = make([]uint32, 30) } func (s *Server) GetRaviMultiplier() float64 { @@ -158,7 +157,11 @@ func NewServer(config *Config) *Server { semaphoreIndex: 7, discordBot: config.DiscordBot, name: config.Name, - raviente: NewRaviente(), + raviente: &Raviente{ + register: make([]uint32, 30), + state: make([]uint32, 30), + support: make([]uint32, 30), + }, } // Mezeporta From 9f76d34e46effe3021c2ac37797463c00f659dac Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 30 Aug 2023 23:58:50 +1000 Subject: [PATCH 028/269] fix Raviente ID --- server/channelserver/sys_channel_server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 8c0e9fe23..dd25db23f 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -158,6 +158,7 @@ func NewServer(config *Config) *Server { discordBot: config.DiscordBot, name: config.Name, raviente: &Raviente{ + id: 1, register: make([]uint32, 30), state: make([]uint32, 30), support: make([]uint32, 30), From 0bdd873336ba84f8adebef4a612bf1bacd349256 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Sep 2023 23:52:15 +1000 Subject: [PATCH 029/269] broadcast Raviente party message correctly --- server/channelserver/handlers_cast_binary.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index ded566268..30a377b82 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -420,8 +420,9 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { } case BroadcastTypeServer: if pkt.MessageType == 1 { - if s.server.getRaviSemaphore() != nil { - s.server.BroadcastMHF(resp, s) + raviSema := s.server.getRaviSemaphore() + if raviSema != nil { + raviSema.BroadcastMHF(resp, s) } } else { s.server.BroadcastMHF(resp, s) From 4a35be488cae3a0c2af6c8d2b396c0bc8742c257 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 3 Sep 2023 00:00:08 +1000 Subject: [PATCH 030/269] rework Semaphores --- server/channelserver/handlers_register.go | 14 ++--- server/channelserver/handlers_semaphore.go | 73 ++++++++-------------- server/channelserver/sys_channel_server.go | 20 ++++-- server/channelserver/sys_semaphore.go | 40 +++--------- 4 files changed, 57 insertions(+), 90 deletions(-) diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index 2135af6fb..6a74358aa 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -99,11 +99,11 @@ func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(pkt.Values) for i := uint8(0); i < pkt.Values; i++ { switch pkt.RegisterID { - case 4: + case 0x40000: bf.WriteUint32(s.server.raviente.state[i]) - case 5: + case 0x50000: bf.WriteUint32(s.server.raviente.support[i]) - case 6: + case 0x60000: bf.WriteUint32(s.server.raviente.register[i]) } } @@ -117,13 +117,13 @@ func (s *Session) notifyRavi() { } var temp mhfpacket.MHFPacket raviNotif := byteframe.NewByteFrame() - temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 4} + temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x40000} raviNotif.WriteUint16(uint16(temp.Opcode())) temp.Build(raviNotif, s.clientContext) - temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 5} + temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x50000} raviNotif.WriteUint16(uint16(temp.Opcode())) temp.Build(raviNotif, s.clientContext) - temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 6} + temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x60000} raviNotif.WriteUint16(uint16(temp.Opcode())) temp.Build(raviNotif, s.clientContext) raviNotif.WriteUint16(0x0010) // End it. @@ -142,7 +142,7 @@ func (s *Session) notifyRavi() { func (s *Server) getRaviSemaphore() *Semaphore { for _, semaphore := range s.semaphore { - if strings.HasPrefix(semaphore.id_semaphore, "hs_l0") && strings.HasSuffix(semaphore.id_semaphore, "3") { + if strings.HasPrefix(semaphore.name, "hs_l0") && strings.HasSuffix(semaphore.name, "3") { return semaphore } } diff --git a/server/channelserver/handlers_semaphore.go b/server/channelserver/handlers_semaphore.go index 21b12682e..19925c6d6 100644 --- a/server/channelserver/handlers_semaphore.go +++ b/server/channelserver/handlers_semaphore.go @@ -2,7 +2,6 @@ package channelserver import ( "erupe-ce/common/byteframe" - "fmt" "go.uber.org/zap" "strconv" "strings" @@ -13,9 +12,6 @@ import ( func removeSessionFromSemaphore(s *Session) { s.server.semaphoreLock.Lock() for _, semaphore := range s.server.semaphore { - if _, exists := semaphore.reservedClientSlots[s.charID]; exists { - delete(semaphore.reservedClientSlots, s.charID) - } if _, exists := semaphore.clients[s]; exists { delete(semaphore.clients, s) } @@ -31,48 +27,38 @@ func handleMsgSysCreateSemaphore(s *Session, p mhfpacket.MHFPacket) { func destructEmptySemaphores(s *Session) { s.server.semaphoreLock.Lock() for id, sema := range s.server.semaphore { - if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 { - s.server.semaphoreLock.Unlock() + if len(sema.clients) == 0 { delete(s.server.semaphore, id) - s.server.semaphoreLock.Lock() if strings.HasPrefix(id, "hs_l0") { - releaseRaviSemaphore(s, sema) + s.server.resetRaviente() } - s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id)) + s.logger.Debug("Destructed semaphore", zap.String("sema.name", id)) } } s.server.semaphoreLock.Unlock() } -func releaseRaviSemaphore(s *Session, sema *Semaphore) { - delete(sema.reservedClientSlots, s.charID) - delete(sema.clients, s) - if strings.HasSuffix(sema.id_semaphore, "2") && len(sema.clients) == 0 { - s.logger.Debug("Main raviente semaphore is empty, resetting") - s.server.resetRaviente() - } -} - func handleMsgSysDeleteSemaphore(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysDeleteSemaphore) - if s.server.semaphore != nil { - destructEmptySemaphores(s) - s.server.semaphoreLock.Lock() - for id, sema := range s.server.semaphore { - if sema.id == pkt.SemaphoreID { - if strings.HasPrefix(id, "hs_l0") { - releaseRaviSemaphore(s, sema) - s.server.semaphoreLock.Unlock() - return + destructEmptySemaphores(s) + s.server.semaphoreLock.Lock() + for id, sema := range s.server.semaphore { + if sema.id == pkt.SemaphoreID { + for session := range sema.clients { + if s == session { + delete(sema.clients, s) } - s.server.semaphoreLock.Unlock() + } + if len(sema.clients) == 0 { delete(s.server.semaphore, id) - s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id)) - return + if strings.HasPrefix(id, "hs_l0") { + s.server.resetRaviente() + } + s.logger.Debug("Destructed semaphore", zap.String("sema.name", id)) } } - s.server.semaphoreLock.Unlock() } + s.server.semaphoreLock.Unlock() } func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { @@ -80,18 +66,15 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { SemaphoreID := pkt.SemaphoreID newSemaphore, exists := s.server.semaphore[SemaphoreID] - - fmt.Printf("Got reserve stage req, StageID: %v\n\n", SemaphoreID) if !exists { s.server.semaphoreLock.Lock() if strings.HasPrefix(SemaphoreID, "hs_l0") { suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:]) s.server.semaphore[SemaphoreID] = &Semaphore{ - id_semaphore: pkt.SemaphoreID, - id: uint32(suffix + 1), - clients: make(map[*Session]uint32), - reservedClientSlots: make(map[uint32]interface{}), - maxPlayers: 127, + name: pkt.SemaphoreID, + id: uint32((suffix + 1) * 0x10000), + clients: make(map[*Session]uint32), + maxPlayers: 127, } } else { s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1) @@ -102,22 +85,19 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { newSemaphore.Lock() defer newSemaphore.Unlock() - if _, exists := newSemaphore.reservedClientSlots[s.charID]; exists { - bf := byteframe.NewByteFrame() + bf := byteframe.NewByteFrame() + if _, exists := newSemaphore.clients[s]; exists { bf.WriteUint32(newSemaphore.id) - doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) - } else if uint16(len(newSemaphore.reservedClientSlots)) < newSemaphore.maxPlayers { - newSemaphore.reservedClientSlots[s.charID] = nil + } else if uint16(len(newSemaphore.clients)) < newSemaphore.maxPlayers { newSemaphore.clients[s] = s.charID s.Lock() s.semaphore = newSemaphore s.Unlock() - bf := byteframe.NewByteFrame() bf.WriteUint32(newSemaphore.id) - doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) } else { - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + bf.WriteUint32(0) } + doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { @@ -130,7 +110,6 @@ func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { } else { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) } - } func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index dd25db23f..5a4b29fe9 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -3,6 +3,7 @@ package channelserver import ( "fmt" "net" + "strings" "sync" "erupe-ce/common/byteframe" @@ -83,6 +84,12 @@ type Raviente struct { } func (s *Server) resetRaviente() { + for _, semaphore := range s.semaphore { + if strings.HasPrefix(semaphore.name, "hs_l0") { + return + } + } + s.logger.Debug("All Raviente Semaphores empty, resetting") s.raviente.id = s.raviente.id + 1 s.raviente.register = make([]uint32, 30) s.raviente.state = make([]uint32, 30) @@ -109,7 +116,7 @@ func (s *Server) GetRaviMultiplier() float64 { func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update bool) (uint32, uint32) { var prev uint32 switch semaID { - case 4: + case 0x40000: switch index { case 17, 28: // Ignore res and poison break @@ -122,14 +129,14 @@ func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update boo } s.raviente.state[index] += value return prev, s.raviente.state[index] - case 5: + case 0x50000: prev = s.raviente.support[index] if prev != 0 && !update { return prev, prev } s.raviente.support[index] += value return prev, s.raviente.support[index] - case 6: + case 0x60000: prev = s.raviente.register[index] if prev != 0 && !update { return prev, prev @@ -395,15 +402,16 @@ func (s *Server) NextSemaphoreID() uint32 { for { exists := false s.semaphoreIndex = s.semaphoreIndex + 1 - if s.semaphoreIndex == 0 { - s.semaphoreIndex = 7 // Skip reserved indexes + if s.semaphoreIndex > 0xFFFF { + s.semaphoreIndex = 1 } for _, semaphore := range s.semaphore { if semaphore.id == s.semaphoreIndex { exists = true + break } } - if exists == false { + if !exists { break } } diff --git a/server/channelserver/sys_semaphore.go b/server/channelserver/sys_semaphore.go index 369e481b6..78ff963b5 100644 --- a/server/channelserver/sys_semaphore.go +++ b/server/channelserver/sys_semaphore.go @@ -7,55 +7,35 @@ import ( "sync" ) -// Stage holds stage-specific information +// Semaphore holds Semaphore-specific information type Semaphore struct { sync.RWMutex - // Stage ID string - id_semaphore string + // Semaphore ID string + name string id uint32 // Map of session -> charID. - // These are clients that are CURRENTLY in the stage + // These are clients that are registered to the Semaphore clients map[*Session]uint32 - // Map of charID -> interface{}, only the key is used, value is always nil. - reservedClientSlots map[uint32]interface{} - // Max Players for Semaphore maxPlayers uint16 } -// NewStage creates a new stage with intialized values. +// NewSemaphore creates a new Semaphore with intialized values func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore { sema := &Semaphore{ - id_semaphore: ID, - id: s.NextSemaphoreID(), - clients: make(map[*Session]uint32), - reservedClientSlots: make(map[uint32]interface{}), - maxPlayers: MaxPlayers, + name: ID, + id: s.NextSemaphoreID(), + clients: make(map[*Session]uint32), + maxPlayers: MaxPlayers, } return sema } -func (s *Semaphore) BroadcastRavi(pkt mhfpacket.MHFPacket) { - // Broadcast the data. - for session := range s.clients { - - // Make the header - bf := byteframe.NewByteFrame() - bf.WriteUint16(uint16(pkt.Opcode())) - - // Build the packet onto the byteframe. - pkt.Build(bf, session.clientContext) - - // Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full. - session.QueueSendNonBlocking(bf.Data()) - } -} - -// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage. +// BroadcastMHF queues a MHFPacket to be sent to all sessions in the Semaphore func (s *Semaphore) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) { // Broadcast the data. for session := range s.clients { From 059f1942a9d8fbf06941fb8e91e23d5b72af958c Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 17 Sep 2023 23:45:32 +1000 Subject: [PATCH 031/269] rename instances of gook to goocoo --- network/mhfpacket/msg_mhf_enumerate_guacot.go | 7 +--- network/mhfpacket/msg_mhf_update_guacot.go | 38 ++++++++----------- patch-schema/06-goocoo-rename.sql | 11 ++++++ server/channelserver/handlers.go | 26 +++++++------ 4 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 patch-schema/06-goocoo-rename.sql diff --git a/network/mhfpacket/msg_mhf_enumerate_guacot.go b/network/mhfpacket/msg_mhf_enumerate_guacot.go index 4fcdba523..2f970f938 100644 --- a/network/mhfpacket/msg_mhf_enumerate_guacot.go +++ b/network/mhfpacket/msg_mhf_enumerate_guacot.go @@ -2,7 +2,6 @@ package mhfpacket import ( "errors" - "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" @@ -11,9 +10,8 @@ import ( // MsgMhfEnumerateGuacot represents the MSG_MHF_ENUMERATE_GUACOT type MsgMhfEnumerateGuacot struct { AckHandle uint32 - Unk0 uint16 // Hardcoded 0 in binary + Unk0 uint32 // Hardcoded 0 in binary Unk1 uint16 // Hardcoded 0 in binary - Unk2 uint16 // Hardcoded 0 in binary } // Opcode returns the ID associated with this packet type. @@ -24,9 +22,8 @@ func (m *MsgMhfEnumerateGuacot) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() + m.Unk0 = bf.ReadUint32() m.Unk1 = bf.ReadUint16() - m.Unk2 = bf.ReadUint16() return nil } diff --git a/network/mhfpacket/msg_mhf_update_guacot.go b/network/mhfpacket/msg_mhf_update_guacot.go index 99aa215e2..433854ae3 100644 --- a/network/mhfpacket/msg_mhf_update_guacot.go +++ b/network/mhfpacket/msg_mhf_update_guacot.go @@ -2,27 +2,23 @@ package mhfpacket import ( "errors" - "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" ) -type Gook struct { - Exists bool - Index uint32 - Type uint16 - Data []byte - NameLen uint8 - Name []byte +type Goocoo struct { + Index uint32 + Data1 []uint16 + Data2 []uint32 + Name []byte } // MsgMhfUpdateGuacot represents the MSG_MHF_UPDATE_GUACOT type MsgMhfUpdateGuacot struct { AckHandle uint32 EntryCount uint16 - Unk0 uint16 // Hardcoded 0 in binary - Gooks []Gook + Goocoos []Goocoo } // Opcode returns the ID associated with this packet type. @@ -34,20 +30,18 @@ func (m *MsgMhfUpdateGuacot) Opcode() network.PacketID { func (m *MsgMhfUpdateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.EntryCount = bf.ReadUint16() - m.Unk0 = bf.ReadUint16() + _ = bf.ReadUint16() // Zero + var temp Goocoo for i := 0; i < int(m.EntryCount); i++ { - e := Gook{} - e.Index = bf.ReadUint32() - e.Type = bf.ReadUint16() - e.Data = bf.ReadBytes(50) - e.NameLen = bf.ReadUint8() - e.Name = bf.ReadBytes(uint(e.NameLen)) - if e.Type > 0 { - e.Exists = true - } else { - e.Exists = false + temp.Index = bf.ReadUint32() + for j := 0; j < 22; j++ { + temp.Data1 = append(temp.Data1, bf.ReadUint16()) } - m.Gooks = append(m.Gooks, e) + for j := 0; j < 2; j++ { + temp.Data2 = append(temp.Data2, bf.ReadUint32()) + } + temp.Name = bf.ReadBytes(uint(bf.ReadUint8())) + m.Goocoos = append(m.Goocoos, temp) } return nil } diff --git a/patch-schema/06-goocoo-rename.sql b/patch-schema/06-goocoo-rename.sql new file mode 100644 index 000000000..e72585ab3 --- /dev/null +++ b/patch-schema/06-goocoo-rename.sql @@ -0,0 +1,11 @@ +BEGIN; + +ALTER TABLE gook RENAME TO goocoo; + +ALTER TABLE goocoo RENAME COLUMN gook0 TO goocoo0; +ALTER TABLE goocoo RENAME COLUMN gook1 TO goocoo1; +ALTER TABLE goocoo RENAME COLUMN gook2 TO goocoo2; +ALTER TABLE goocoo RENAME COLUMN gook3 TO goocoo3; +ALTER TABLE goocoo RENAME COLUMN gook4 TO goocoo4; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index abc554d06..517da6e6d 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -822,21 +822,25 @@ func handleMsgMhfEnumerateGuacot(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateGuacot) - for _, gook := range pkt.Gooks { - if !gook.Exists { - s.server.db.Exec(fmt.Sprintf("UPDATE gook SET gook%d=NULL WHERE id=$1", gook.Index), s.charID) + for _, goocoo := range pkt.Goocoos { + if goocoo.Data1[0] == 0 { + s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=NULL WHERE id=$1", goocoo.Index), s.charID) } else { bf := byteframe.NewByteFrame() - bf.WriteUint32(gook.Index) - bf.WriteUint16(gook.Type) - bf.WriteBytes(gook.Data) - bf.WriteUint8(gook.NameLen) - bf.WriteBytes(gook.Name) - s.server.db.Exec(fmt.Sprintf("UPDATE gook SET gook%d=$1 WHERE id=$2", gook.Index), bf.Data(), s.charID) - dumpSaveData(s, bf.Data(), fmt.Sprintf("goocoo-%d", gook.Index)) + bf.WriteUint32(goocoo.Index) + for i := range goocoo.Data1 { + bf.WriteUint16(goocoo.Data1[i]) + } + for i := range goocoo.Data2 { + bf.WriteUint32(goocoo.Data2[i]) + } + bf.WriteUint8(uint8(len(goocoo.Name))) + bf.WriteBytes(goocoo.Name) + s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=$1 WHERE id=$2", goocoo.Index), bf.Data(), s.charID) + dumpSaveData(s, bf.Data(), fmt.Sprintf("goocoo-%d", goocoo.Index)) } } - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) { From 20272382500d79eb0f4889c9b472d047463ac589 Mon Sep 17 00:00:00 2001 From: Samboge Date: Wed, 27 Sep 2023 07:12:27 +0700 Subject: [PATCH 032/269] Fix for Festa Rewards and House Visit MHF-F5 Partial Fix for Forward 5 --- server/channelserver/handlers_character.go | 22 ++++++++++ server/channelserver/handlers_festa.go | 47 +++++++++++++++++----- server/channelserver/handlers_house.go | 5 ++- 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index bdbd2f744..e6276e845 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -83,6 +83,17 @@ func getPointers() map[SavePointer]int { pointers[pGardenData] = 106424 pointers[pRP] = 106614 pointers[pKQF] = 110720 + case _config.F5: + pointers[pWeaponID] = 60522 + pointers[pWeaponType] = 60789 + pointers[pHouseTier] = 61900 + pointers[pToreData] = 60550 + pointers[pHRP] = 62550 + pointers[pHouseData] = 62651 + pointers[pBookshelfData] = 71928 + pointers[pGalleryData] = 72064 + pointers[pGardenData] = 74424 + pointers[pRP] = 74614 } return pointers } @@ -206,6 +217,17 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])) } save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] + } else if _config.ErupeConfig.RealClientMode < _config.G10 { + save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) + save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] + save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] + save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+2576] + save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748] + save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240] + save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68] + save.WeaponType = save.decompSave[save.Pointers[pWeaponType]] + save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) + save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2]) } } return diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 95cc14e0d..4f3807fa2 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -162,6 +162,14 @@ type FestaReward struct { Unk7 uint8 } +type FestaRewardF5 struct { + Unk0 uint8 + Unk1 uint8 + ItemType uint16 + Quantity uint16 + ItemID uint16 +} + func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfInfoFesta) bf := byteframe.NewByteFrame() @@ -254,18 +262,37 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { {5, 0, 13, 0, 0, 0, 0, 0}, //{5, 0, 1, 0, 0, 0, 0, 0}, } - bf.WriteUint16(uint16(len(rewards))) - for _, reward := range rewards { - bf.WriteUint8(reward.Unk0) - bf.WriteUint8(reward.Unk1) - bf.WriteUint16(reward.ItemType) - bf.WriteUint16(reward.Quantity) - bf.WriteUint16(reward.ItemID) - bf.WriteUint16(reward.Unk5) - bf.WriteUint16(reward.Unk6) - bf.WriteUint8(reward.Unk7) + + rewardsF5 := []FestaRewardF5{ + {1, 0, 7, 250, 1520}, + {1, 0, 12, 250, 0}, + {1, 0, 12, 250, 0}, + {1, 0, 12, 250, 0}, + {1, 0, 12, 250, 0}, } + if _config.ErupeConfig.RealClientMode >= _config.G1 { + bf.WriteUint16(uint16(len(rewards))) + for _, reward := range rewards { + bf.WriteUint8(reward.Unk0) + bf.WriteUint8(reward.Unk1) + bf.WriteUint16(reward.ItemType) + bf.WriteUint16(reward.Quantity) + bf.WriteUint16(reward.ItemID) + bf.WriteUint16(reward.Unk5) + bf.WriteUint16(reward.Unk6) + bf.WriteUint8(reward.Unk7) + } + } else if _config.ErupeConfig.RealClientMode == _config.F5{ + bf.WriteUint16(uint16(len(rewardsF5))) + for _, reward := range rewardsF5 { + bf.WriteUint8(reward.Unk0) + bf.WriteUint8(reward.Unk1) + bf.WriteUint16(reward.ItemType) + bf.WriteUint16(reward.Quantity) + bf.WriteUint16(reward.ItemID) + } + } if _config.ErupeConfig.RealClientMode <= _config.G61 { if s.server.erupeConfig.GameplayOptions.MaximumFP > 0xFFFF { s.server.erupeConfig.GameplayOptions.MaximumFP = 0xFFFF diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 4cc53d303..c00c2be73 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -119,7 +119,10 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(0) } bf.WriteUint16(house.HRP) - bf.WriteUint16(house.GR) + //to skip GR for client below G1 + if _config.ErupeConfig.RealClientMode >= _config.G1{ + bf.WriteUint16(house.GR) + } ps.Uint8(bf, house.Name, true) } bf.Seek(0, 0) From 8f1317f49835d3138ad6f9b2a2b9c3064a464d22 Mon Sep 17 00:00:00 2001 From: Samboge Date: Wed, 27 Sep 2023 13:52:29 +0700 Subject: [PATCH 033/269] Fix RP for MHF-F5 forgot to modify updateSaveDataWithStruct() to include forward 5 --- server/channelserver/handlers_character.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index e6276e845..301a17b63 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -190,6 +190,8 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() { if _config.ErupeConfig.RealClientMode >= _config.G10 { copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF) + } else if _config.ErupeConfig.RealClientMode == _config.F5{ + copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) } } From b58eddca854f4130a505ba4300f79620debc5744 Mon Sep 17 00:00:00 2001 From: legayacruise <112596053+legayacruise@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:02:29 -0400 Subject: [PATCH 034/269] Correct instances of "gook" to goocoo Thank you to Samboge for pointing that out. --- server/channelserver/handlers.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 517da6e6d..9450fee43 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -789,20 +789,20 @@ func getGookData(s *Session, cid uint32) (uint16, []byte) { var count uint16 bf := byteframe.NewByteFrame() for i := 0; i < 5; i++ { - err := s.server.db.QueryRow(fmt.Sprintf("SELECT gook%d FROM gook WHERE id=$1", i), cid).Scan(&data) + err := s.server.db.QueryRow(fmt.Sprintf("SELECT goocoo%d FROM goocoo WHERE id=$1", i), cid).Scan(&data) if err != nil { - s.server.db.Exec("INSERT INTO gook (id) VALUES ($1)", s.charID) + s.server.db.Exec("INSERT INTO goocoo (id) VALUES ($1)", s.charID) return 0, bf.Data() } if err == nil && data != nil { count++ if s.charID == cid && count == 1 { - gook := byteframe.NewByteFrameFromBytes(data) - bf.WriteBytes(gook.ReadBytes(4)) - d := gook.ReadBytes(2) + goocoo := byteframe.NewByteFrameFromBytes(data) + bf.WriteBytes(goocoo.ReadBytes(4)) + d := goocoo.ReadBytes(2) bf.WriteBytes(d) bf.WriteBytes(d) - bf.WriteBytes(gook.DataFromCurrent()) + bf.WriteBytes(goocoo.DataFromCurrent()) } else { bf.WriteBytes(data) } From aecb6af9e507a0d2b2c620d58e10cf54339b7bf6 Mon Sep 17 00:00:00 2001 From: Samboge Date: Sun, 1 Oct 2023 08:03:14 +0700 Subject: [PATCH 035/269] My Mission Fix for MHF-F5 Client only sent 322 byte (32 header + 290 byte of data) --- network/mhfpacket/msg_mhf_update_myhouse_info.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/network/mhfpacket/msg_mhf_update_myhouse_info.go b/network/mhfpacket/msg_mhf_update_myhouse_info.go index 469920127..a66bc6aa2 100644 --- a/network/mhfpacket/msg_mhf_update_myhouse_info.go +++ b/network/mhfpacket/msg_mhf_update_myhouse_info.go @@ -6,6 +6,7 @@ import ( "erupe-ce/network/clientctx" "erupe-ce/network" "erupe-ce/common/byteframe" + _config "erupe-ce/config" ) // MsgMhfUpdateMyhouseInfo represents the MSG_MHF_UPDATE_MYHOUSE_INFO @@ -22,7 +23,11 @@ func (m *MsgMhfUpdateMyhouseInfo) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUpdateMyhouseInfo) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadBytes(0x16A) + if _config.ErupeConfig.RealClientMode == _config.F5 { + m.Unk0 = bf.ReadBytes(0x122) + } else { + m.Unk0 = bf.ReadBytes(0x16A) + } return nil } From a6c83141875e8eddbb93f07fafae800604565db7 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 1 Oct 2023 15:54:45 +1100 Subject: [PATCH 036/269] convert some values to decimal --- server/channelserver/handlers_plate.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/channelserver/handlers_plate.go b/server/channelserver/handlers_plate.go index 3f5688184..19fdd84a2 100644 --- a/server/channelserver/handlers_plate.go +++ b/server/channelserver/handlers_plate.go @@ -42,7 +42,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { } } else { // create empty save if absent - data = make([]byte, 0x1AF20) + data = make([]byte, 140000) } // Perform diff and compress it to write back to db @@ -110,7 +110,7 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) { } } else { // create empty save if absent - data = make([]byte, 0x820) + data = make([]byte, 4800) } // Perform diff and compress it to write back to db @@ -147,7 +147,7 @@ func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) { err := s.server.db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", s.charID).Scan(&data) if len(data) == 0 { s.logger.Error("Failed to load platemyset", zap.Error(err)) - data = make([]byte, 0x780) + data = make([]byte, 1920) } doAckBufSucceed(s, pkt.AckHandle, data) } From 1e1790eb845a5bcb681319eb862f48cb5dc3038a Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 1 Oct 2023 23:17:07 +1100 Subject: [PATCH 037/269] fix incorrect case for EnumerateHouse response --- server/channelserver/handlers_house.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index c00c2be73..4676eeb79 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -119,8 +119,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(0) } bf.WriteUint16(house.HRP) - //to skip GR for client below G1 - if _config.ErupeConfig.RealClientMode >= _config.G1{ + if _config.ErupeConfig.RealClientMode >= _config.G10 { bf.WriteUint16(house.GR) } ps.Uint8(bf, house.Name, true) From 5d4a81a84f4b976f98d85fb08c247a68878dbf9d Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 1 Oct 2023 23:18:23 +1100 Subject: [PATCH 038/269] fix BookshelfData length --- server/channelserver/handlers_character.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 301a17b63..36687b11d 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -19,7 +19,7 @@ const ( pRP // +2 pHouseTier // +5 pHouseData // +195 - pBookshelfData // +5576 + pBookshelfData // +lBookshelfData pGalleryData // +1748 pToreData // +240 pGardenData // +68 @@ -28,6 +28,7 @@ const ( pHRP // +2 pGRP // +4 pKQF // +8 + lBookshelfData ) type CharacterSaveData struct { @@ -55,7 +56,7 @@ type CharacterSaveData struct { } func getPointers() map[SavePointer]int { - pointers := map[SavePointer]int{pGender: 81} + pointers := map[SavePointer]int{pGender: 81, lBookshelfData: 5576} switch _config.ErupeConfig.RealClientMode { case _config.ZZ: pointers[pWeaponID] = 128522 @@ -95,6 +96,11 @@ func getPointers() map[SavePointer]int { pointers[pGardenData] = 74424 pointers[pRP] = 74614 } + if _config.ErupeConfig.RealClientMode == _config.G5 { + pointers[lBookshelfData] = 5548 + } else if _config.ErupeConfig.RealClientMode <= _config.GG { + pointers[lBookshelfData] = 4520 + } return pointers } @@ -190,7 +196,7 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() { if _config.ErupeConfig.RealClientMode >= _config.G10 { copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF) - } else if _config.ErupeConfig.RealClientMode == _config.F5{ + } else if _config.ErupeConfig.RealClientMode == _config.F5 { copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) } } @@ -208,7 +214,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] - save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+5576] + save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+save.Pointers[lBookshelfData]] save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748] save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240] save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68] @@ -219,11 +225,11 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])) } save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] - } else if _config.ErupeConfig.RealClientMode < _config.G10 { + } else if _config.ErupeConfig.RealClientMode == _config.F5 { save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] - save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+2576] + save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+save.Pointers[lBookshelfData]] save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748] save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240] save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68] From 2539afb5d34058fae15d5d0a2152bc4c020d0dfe Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 2 Oct 2023 00:45:28 +1100 Subject: [PATCH 039/269] simplify Festa reward data --- server/channelserver/handlers_festa.go | 49 +++++++------------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 4f3807fa2..7ca50d9d1 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -162,14 +162,6 @@ type FestaReward struct { Unk7 uint8 } -type FestaRewardF5 struct { - Unk0 uint8 - Unk1 uint8 - ItemType uint16 - Quantity uint16 - ItemID uint16 -} - func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfInfoFesta) bf := byteframe.NewByteFrame() @@ -235,6 +227,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { } // The Winner and Loser Armor IDs are missing + // Item 7011 may not exist in older versions, remove to prevent crashes rewards := []FestaReward{ {1, 0, 7, 350, 1520, 0, 0, 0}, {1, 0, 7, 1000, 7011, 0, 0, 1}, @@ -263,34 +256,18 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { //{5, 0, 1, 0, 0, 0, 0, 0}, } - rewardsF5 := []FestaRewardF5{ - {1, 0, 7, 250, 1520}, - {1, 0, 12, 250, 0}, - {1, 0, 12, 250, 0}, - {1, 0, 12, 250, 0}, - {1, 0, 12, 250, 0}, - } - - if _config.ErupeConfig.RealClientMode >= _config.G1 { - bf.WriteUint16(uint16(len(rewards))) - for _, reward := range rewards { - bf.WriteUint8(reward.Unk0) - bf.WriteUint8(reward.Unk1) - bf.WriteUint16(reward.ItemType) - bf.WriteUint16(reward.Quantity) - bf.WriteUint16(reward.ItemID) - bf.WriteUint16(reward.Unk5) - bf.WriteUint16(reward.Unk6) - bf.WriteUint8(reward.Unk7) - } - } else if _config.ErupeConfig.RealClientMode == _config.F5{ - bf.WriteUint16(uint16(len(rewardsF5))) - for _, reward := range rewardsF5 { - bf.WriteUint8(reward.Unk0) - bf.WriteUint8(reward.Unk1) - bf.WriteUint16(reward.ItemType) - bf.WriteUint16(reward.Quantity) - bf.WriteUint16(reward.ItemID) + bf.WriteUint16(uint16(len(rewards))) + for _, reward := range rewards { + bf.WriteUint8(reward.Unk0) + bf.WriteUint8(reward.Unk1) + bf.WriteUint16(reward.ItemType) + bf.WriteUint16(reward.Quantity) + bf.WriteUint16(reward.ItemID) + // Not confirmed to be G1 but exists in G3 + if _config.ErupeConfig.RealClientMode >= _config.G1 { + bf.WriteUint16(reward.Unk5) + bf.WriteUint16(reward.Unk6) + bf.WriteUint8(reward.Unk7) } } if _config.ErupeConfig.RealClientMode <= _config.G61 { From aa98e89d773eead437bcaa76eadf7be5727ef5bf Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 2 Oct 2023 01:19:56 +1100 Subject: [PATCH 040/269] cover more versions with Hiden savedata --- .../mhfpacket/msg_mhf_update_myhouse_info.go | 21 ++++++++++++------- server/channelserver/handlers_house.go | 4 ++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/network/mhfpacket/msg_mhf_update_myhouse_info.go b/network/mhfpacket/msg_mhf_update_myhouse_info.go index a66bc6aa2..c5bf26d7a 100644 --- a/network/mhfpacket/msg_mhf_update_myhouse_info.go +++ b/network/mhfpacket/msg_mhf_update_myhouse_info.go @@ -1,18 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" _config "erupe-ce/config" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfUpdateMyhouseInfo represents the MSG_MHF_UPDATE_MYHOUSE_INFO type MsgMhfUpdateMyhouseInfo struct { AckHandle uint32 - Unk0 []byte + Data []byte } // Opcode returns the ID associated with this packet type. @@ -23,10 +23,15 @@ func (m *MsgMhfUpdateMyhouseInfo) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUpdateMyhouseInfo) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - if _config.ErupeConfig.RealClientMode == _config.F5 { - m.Unk0 = bf.ReadBytes(0x122) + if _config.ErupeConfig.RealClientMode >= _config.G10 { + m.Data = bf.ReadBytes(362) + } else if _config.ErupeConfig.RealClientMode >= _config.GG { + m.Data = bf.ReadBytes(338) + } else if _config.ErupeConfig.RealClientMode >= _config.F5 { + // G1 is a guess + m.Data = bf.ReadBytes(314) } else { - m.Unk0 = bf.ReadBytes(0x16A) + m.Data = bf.ReadBytes(290) } return nil } diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 4676eeb79..560e73ec7 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -240,8 +240,8 @@ func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateMyhouseInfo) - s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Unk0, s.charID) - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Data, s.charID) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) { From d389f110a82c22b0e4e68fcccf4afe83073215a0 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 2 Oct 2023 22:09:20 +1100 Subject: [PATCH 041/269] test new pointers --- server/channelserver/handlers_character.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 36687b11d..d4ecd65d6 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -84,14 +84,14 @@ func getPointers() map[SavePointer]int { pointers[pGardenData] = 106424 pointers[pRP] = 106614 pointers[pKQF] = 110720 - case _config.F5: + case _config.F5, _config.F4: pointers[pWeaponID] = 60522 pointers[pWeaponType] = 60789 pointers[pHouseTier] = 61900 - pointers[pToreData] = 60550 + pointers[pToreData] = 62228 pointers[pHRP] = 62550 - pointers[pHouseData] = 62651 - pointers[pBookshelfData] = 71928 + pointers[pHouseData] = 62561 + pointers[pBookshelfData] = 56830 pointers[pGalleryData] = 72064 pointers[pGardenData] = 74424 pointers[pRP] = 74614 @@ -196,7 +196,7 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() { if _config.ErupeConfig.RealClientMode >= _config.G10 { copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF) - } else if _config.ErupeConfig.RealClientMode == _config.F5 { + } else if _config.ErupeConfig.RealClientMode == _config.F5 || _config.ErupeConfig.RealClientMode == _config.F4 { copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) } } @@ -225,7 +225,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])) } save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] - } else if _config.ErupeConfig.RealClientMode == _config.F5 { + } else if _config.ErupeConfig.RealClientMode == _config.F5 || _config.ErupeConfig.RealClientMode == _config.F4 { save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] From 6bc883cc0e7f14328d89f6077b7ba694aaa8a33c Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 2 Oct 2023 23:10:48 +1100 Subject: [PATCH 042/269] update README --- README.md | 115 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index ede542a20..8f17c33ce 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,13 @@ - PlayStation 3 - PlayStation Vita - Wii U (Up to Z2) -### Versions -- ZZ -- Z2 -- Z1 +### Versions (ClientMode) +All versions after HR compression (G10-ZZ) have been tested extensively and have great functionality. +All versions available on Wii U (G3-Z2) have been tested and should have good functionality. +The second oldest found version is Forward.4 (FW.4), this version has basic functionality. +The oldest found version is Season 6.0 (S6.0), however functionality is very limited. + +If you have an **installed** copy of Monster Hunter Frontier on an old hard drive, **please** get in contact so we can archive it! ## Setup @@ -32,50 +35,50 @@ If you want to modify or compile Erupe yourself, please read on. ## Resources - [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z) -- [PewPewDojo Discord](https://discord.gg/CFnzbhQ) +- [Mezeporta Square Discord](https://discord.gg/DnwcpXM488) ## Configuration This portion of the documentation goes over the `config.json` file. ### General Configuration -| Variable | Description | Default | Options | -|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|---------------------------------| -| Host | The IP or host address the server is running from | 127.0.0.1 | | -| BinPath | The bin path folder is where you place files needed for various parts of the game such as scenario and quest files | bin | | -| Language | This is the language the server will run in. Only English `en` and Japanese `ja` are available, if you wish to contribute to tranlation, get in touch | en | en/jp | -| DisableSoftCrash | | false | | -| HideLoginNotice | This hides the notices that appear on login from `LoginNotices` | true | | -| LoginNotices | This is where you place notices for users, you can have multiple notices | | | -| PatchServerManifest | | | | -| PatchServerFile | | | | -| ScreenshotAPIURL | This is the URL you want user sreenshots to go to | | | -| DeleteOnSaveCorruption | This option deletes a users save from the database if they corrupt it, can be used as punishment for cheaters | false | | -| ClientMode | This tells the server what client version it should target | ZZ | Check compatible versions above | -| DevMode | This enables DevModeOptions to be configured | true | | +| Variable | Description | Default | Options | +|------------------------|-----------------------------------------------------------------------------------------------------------------------|-----------|---------------------------------| +| Host | The IP or host address the server is running from | 127.0.0.1 | | +| BinPath | The bin path folder is where you place files needed for various parts of the game such as scenario and quest files | bin | | +| Language | This is the language the server will run in. Only English `en` and Japanese `jp` are available, contributions welcome | en | en/jp | +| DisableSoftCrash | | false | | +| HideLoginNotice | This hides the notices that appear on login from `LoginNotices` | true | | +| LoginNotices | This is where you place notices for users, you can have multiple notices | | | +| PatchServerManifest | | | | +| PatchServerFile | | | | +| ScreenshotAPIURL | This is the URL you want user sreenshots to go to | | | +| DeleteOnSaveCorruption | This option flags a character as deleted if they corrupt it, can be used as punishment for cheaters | false | | +| ClientMode | This tells the server what client version it should target | ZZ | Check compatible versions above | +| DevMode | This enables DevModeOptions to be configured | true | | ### `DevModeOptions` Configuraiton -| Variable | Description | Default | Options | -|----------------------|---------------------------------------------------------------------------------------------|----------|----------------------------------| -| AutoCreateAccount | This allows users that don't exist to auto create there account from initial login | true | | -| CleanDB | This cleans the database down | false | | -| MaxLauncherHR | This sets the launcher value to HR7 to allow you to break World HR requirements | false | | -| LogInboundMessages | This will allow inbound messages to be logged to stdout | false | | -| LogOutboundMessages | This will allow outbound messages to be logged to stdout | false | | -| MaxHexdumpLength | This is the maximum amount of hex bytes that will be dumped to stdout | 0 | | -| DivaEvent | This overrides the Diva event stage in game | 2 | 0/1/2/3/-1 | -| FestaEvent | This overrides the Hunter Festival event stage in game | 2 | 0/1/2/3/-1 | -| TournamentEvent | This overrides the Hunter Tournament event stage in game | 2 | 0/1/2/3/-1 | -| MezFesEvent | Enables whether the MezFes event & World are active | true | | -| MezFesAlt | Switches the multiplayer MezFes event | false | | -| DisableTokenCheck | This disables the random token that is generated at login from being checked, very insecure | false | | -| QuestDebugTools | Enable various quest debug logs | false | | -| EarthStatusOverride | Enables Pallone Fest, Tower and Conquest War events | 0 | 2=Conquest, 11=Pallone, 21=Tower | -| EarthIDOverride | A random event ID | 0 | | -| EarthMonsterOverride | Sets the ID of the monster targeted in the Conquest War | 0 | | -| SaveDumps.Enables | Enables save dumps to a folder that is set at `SaveDumps.OutputDir` | true | | -| SaveDumps.OutputDir | The folder that save dumps are saved to | savedata | | +| Variable | Description | Default | Options | +|----------------------|---------------------------------------------------------------------------------------------|--------------|----------------------------------| +| AutoCreateAccount | This allows users that don't exist to auto create their account from initial login | true | | +| CleanDB | This cleans the database down | false | | +| MaxLauncherHR | This sets the launcher value to HR7 to allow you to break World HR requirements | false | | +| LogInboundMessages | This will allow inbound messages to be logged to stdout | false | | +| LogOutboundMessages | This will allow outbound messages to be logged to stdout | false | | +| MaxHexdumpLength | This is the maximum amount of hex bytes that will be dumped to stdout | 0 | | +| DivaEvent | This overrides the Diva event stage in game | 0 | 0/1/2/3/-1 | +| FestaEvent | This overrides the Hunter Festival event stage in game | -1 | 0/1/2/3/-1 | +| TournamentEvent | This overrides the Hunter Tournament event stage in game | 0 | 0/1/2/3/-1 | +| MezFesEvent | Enables whether the MezFes event & World are active | true | | +| MezFesAlt | Switches the multiplayer MezFes minigame | false | | +| DisableTokenCheck | This disables the random token that is generated at login from being checked, very insecure | false | | +| QuestDebugTools | Enable various quest debug logs | false | | +| EarthStatusOverride | Enables Pallone Fest, Tower and Conquest War events | 0 | 2=Conquest, 11=Pallone, 21=Tower | +| EarthIDOverride | A random event ID | 0 | | +| EarthMonsterOverride | Sets the ID of the monster targeted in the Conquest War | 0 | | +| SaveDumps.Enables | Enables save dumps to a folder that is set at `SaveDumps.OutputDir` | true | | +| SaveDumps.OutputDir | The folder that save dumps are saved to | save-backups | | ### `GameplayOptions` Configuraiton @@ -111,14 +114,14 @@ This may be either be removed or revamped in a future version. ### Commands There are several chat commands that can be turned on and off. Most of them are really for admins or debugging purposes. -| Name | command | Description | Options | -|----------|----------------|--------------------------------------------|---------------------| -| Rights | !rights VALUE | Sets the rights integer for your account | | -| Teleport | !tele X,Y | Teleports user to specific x,y coordinate | | -| Reload | !reload | Reloads all users and character objects | | -| KeyQuest | !kqf FLAGS | Sets the Key Quest Flag for your character | | -| Course | !course OPTION | Enables/Disables a course for your account | HL,EX,Premium,Boost | -| PSN | !psn USERNAME | Links the specified PSN to your account | | +| Name | command | Description | Default | Options | +|----------|----------------|--------------------------------------------|----------|---------------------| +| Rights | !rights VALUE | Sets the rights integer for your account | disabled | | +| Teleport | !tele X,Y | Teleports user to specific x,y coordinate | disabled | | +| Reload | !reload | Reloads all users and character objects | enabled | | +| KeyQuest | !kqf FLAGS | Sets the Key Quest Flag for your character | disabled | | +| Course | !course OPTION | Enables/Disables a course for your account | enabled | HL,EX,Premium,Boost | +| PSN | !psn USERNAME | Links the specified PSN to your account | enabled | | ### Ravi Sub Commands | Name | command | Description | @@ -132,16 +135,16 @@ There are several chat commands that can be turned on and off. Most of them are ## World `Entries` config -| Config Item | Description | Options | -|-------------|------------------|------------------------------------------------------------| -| Type | Server type. | 1=Normal, 2=Cities, 3=Newbie, 4=Tavern, 5=Return, 6=MezFes | -| Season | Server activity. | 0=Green/Breeding, 1=Orange/Warm, 2=Blue/Cold | +| Config Item | Description | Options | +|-------------|---------------|------------------------------------------------------------| +| Type | Server type | 1=Normal, 2=Cities, 3=Newbie, 4=Tavern, 5=Return, 6=MezFes | +| Season | Server season | 0=Green/Breeding, 1=Orange/Warm, 2=Blue/Cold | ### `Recommend` -This sets the types of quest that can be ordered from a world. +This sets the types of Quests that can be ordered from a World. * 0 = All quests -* 1 = Up to 2 star quests -* 2 = Up to 4 star quests +* 1 = Up to 2 star Quests +* 2 = Up to 4 star Quests * 4 = All Quests in HR (Enables G Experience Tab) -* 5 = Only G rank quests -* 6 = Mini games world there is no place to order quests \ No newline at end of file +* 5 = Only G Rank Quests +* 6 = Minigame World \ No newline at end of file From d0431fbc1c9c8f230191ab7f5ee80a9042734b17 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 2 Oct 2023 23:13:19 +1100 Subject: [PATCH 043/269] ignore name-checker if using Season modes --- server/channelserver/handlers_data.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 2d95e97d0..81102b42d 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -2,6 +2,7 @@ package channelserver import ( "erupe-ce/common/stringsupport" + _config "erupe-ce/config" "fmt" "io" "os" @@ -54,7 +55,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { s.Name = characterSaveData.Name } - if characterSaveData.Name == s.Name { + if characterSaveData.Name == s.Name || _config.ErupeConfig.RealClientMode <= _config.S10 { characterSaveData.Save(s) s.logger.Info("Wrote recompressed savedata back to DB.") } else { From 8fbbf1382247e2681893554137983d86c7980e6c Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 3 Oct 2023 00:48:37 +1100 Subject: [PATCH 044/269] use better Bookshelf pointer & simplify pointer assignment --- server/channelserver/handlers_character.go | 26 ++++++++-------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index d4ecd65d6..4c5802293 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -91,7 +91,7 @@ func getPointers() map[SavePointer]int { pointers[pToreData] = 62228 pointers[pHRP] = 62550 pointers[pHouseData] = 62561 - pointers[pBookshelfData] = 56830 + pointers[pBookshelfData] = 57118 // This pointer only half works pointers[pGalleryData] = 72064 pointers[pGardenData] = 74424 pointers[pRP] = 74614 @@ -210,22 +210,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.Gender = false } if !save.IsNewCharacter { - if _config.ErupeConfig.RealClientMode >= _config.G10 { - save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) - save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] - save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] - save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+save.Pointers[lBookshelfData]] - save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748] - save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240] - save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68] - save.WeaponType = save.decompSave[save.Pointers[pWeaponType]] - save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) - save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2]) - if save.HRP == uint16(999) { - save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])) - } - save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] - } else if _config.ErupeConfig.RealClientMode == _config.F5 || _config.ErupeConfig.RealClientMode == _config.F4 { + if (_config.ErupeConfig.RealClientMode >= _config.F4 && _config.ErupeConfig.RealClientMode <= _config.F5) || _config.ErupeConfig.RealClientMode >= _config.G10 { save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] @@ -237,6 +222,13 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2]) } + + if _config.ErupeConfig.RealClientMode >= _config.G10 { + save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] + if save.HRP == uint16(999) { + save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])) + } + } } return } From 0f2edbf0286d97e74c7359f78df032e31859c7ae Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 3 Oct 2023 23:05:45 +1100 Subject: [PATCH 045/269] simplify & fix UpdateRavi --- server/channelserver/sys_channel_server.go | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 5a4b29fe9..1dfef82d0 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -115,6 +115,7 @@ func (s *Server) GetRaviMultiplier() float64 { func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update bool) (uint32, uint32) { var prev uint32 + var dest *[]uint32 switch semaID { case 0x40000: switch index { @@ -123,28 +124,20 @@ func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update boo default: value = uint32(float64(value) * s.GetRaviMultiplier()) } - prev = s.raviente.state[index] - if prev != 0 && !update { - return prev, prev - } - s.raviente.state[index] += value - return prev, s.raviente.state[index] + dest = &s.raviente.state case 0x50000: - prev = s.raviente.support[index] - if prev != 0 && !update { - return prev, prev - } - s.raviente.support[index] += value - return prev, s.raviente.support[index] + dest = &s.raviente.support case 0x60000: - prev = s.raviente.register[index] - if prev != 0 && !update { - return prev, prev - } - s.raviente.register[index] += value - return prev, s.raviente.register[index] + dest = &s.raviente.register + default: + return 0, 0 } - return 0, 0 + if update { + (*dest)[index] += value + } else { + (*dest)[index] = value + } + return prev, (*dest)[index] } // NewServer creates a new Server type. From 80a2822214d92a48f04b2b56f28540ef97c54b89 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 7 Oct 2023 17:33:40 +1100 Subject: [PATCH 046/269] #74 update file names & default scenarios --- bundled-schema/DefaultScenarios.sql | 120 ------------ bundled-schema/ScenarioDefaults.sql | 178 ++++++++++++++++++ ...s-counter.sql => 07-scenarios-counter.sql} | 0 3 files changed, 178 insertions(+), 120 deletions(-) delete mode 100644 bundled-schema/DefaultScenarios.sql create mode 100644 bundled-schema/ScenarioDefaults.sql rename patch-schema/{scenarios-counter.sql => 07-scenarios-counter.sql} (100%) diff --git a/bundled-schema/DefaultScenarios.sql b/bundled-schema/DefaultScenarios.sql deleted file mode 100644 index d3eb2899a..000000000 --- a/bundled-schema/DefaultScenarios.sql +++ /dev/null @@ -1,120 +0,0 @@ -BEGIN; - -INSERT INTO public.scenario_counter -(scenario_id, category_id) -VALUES - (0,0), - (1,0), - (2,0), - (3,0), - (4,0), - (5,0), - (6,0), - (7,0), - (8,0), - (9,0), - (10,0), - (11,0), - (12,0), - (13,0), - (14,0), - (15,0), - (16,0), - (17,0), - (18,0), - (19,0), - (0,1), - (1,1), - (2,1), - (3,1), - (4,1), - (5,1), - (6,1), - (7,1), - (8,1), - (9,1), - (10,1), - (11,1), - (12,1), - (13,1), - (16,1), - (17,1), - (19,1), - (21,1), - (22,1), - (23,1), - (27,1), - (28,1), - (29,1), - (30,1), - (31,1), - (32,1), - (33,1), - (34,1), - (35,1), - (36,1), - (37,1), - (38,1), - (39,1), - (41,1), - (42,1), - (43,1), - (46,1), - (47,1), - (48,1), - (49,1), - (58,1), - (59,1), - (60,1), - (61,1), - (62,1), - (63,1), - (64,1), - (65,1), - (66,1), - (67,1), - (68,1), - (69,1), - (70,1), - (77,1), - (79,1), - (81,1), - (82,1), - (83,1), - (84,1), - (85,1), - (86,1), - (87,1), - (88,1), - (89,1), - (90,1), - (92,1), - (93,1), - (13,1), - (14,1), - (15,1), - (18,1), - (20,1), - (24,1), - (25,1), - (26,1), - (40,1), - (50,1), - (51,1), - (52,1), - (53,1), - (54,1), - (55,1), - (56,1), - (57,1), - (71,1), - (72,1), - (73,1), - (74,1), - (75,1), - (76,1), - (78,1), - (80,1), - (91,1); - -END; \ No newline at end of file diff --git a/bundled-schema/ScenarioDefaults.sql b/bundled-schema/ScenarioDefaults.sql new file mode 100644 index 000000000..ec7b3d99e --- /dev/null +++ b/bundled-schema/ScenarioDefaults.sql @@ -0,0 +1,178 @@ +BEGIN; + +INSERT INTO public.scenario_counter +(scenario_id, category_id) +VALUES + (17,0), + (93,1), + (92,1), + (81,1), + (91,1), + (90,1), + (89,1), + (88,1), + (87,1), + (86,1), + (85,1), + (84,1), + (83,1), + (82,1), + (87,3), + (88,3), + (89,3), + (90,3), + (91,3), + (92,3), + (83,3), + (86,3), + (60,3), + (58,3), + (59,3), + (27,3), + (25,3), + (26,3), + (23,3), + (2,3), + (3,3), + (4,3), + (31,3), + (32,3), + (33,3), + (34,3), + (35,3), + (36,3), + (37,3), + (40,3), + (38,3), + (39,3), + (48,3), + (12,3), + (13,3), + (30,3), + (29,3), + (46,3), + (0,4), + (1,4), + (2,4), + (3,4), + (4,4), + (5,4), + (6,4), + (7,4), + (8,4), + (9,4), + (10,4), + (11,4), + (12,4), + (13,4), + (14,4), + (50,5), + (51,5), + (52,5), + (53,5), + (54,5), + (55,5), + (56,5), + (58,5), + (63,5), + (64,5), + (65,5), + (67,5), + (71,5), + (75,5), + (61,5), + (68,5), + (66,5), + (76,5), + (70,5), + (77,5), + (72,5), + (74,5), + (73,5), + (78,5), + (69,5), + (62,5), + (79,5), + (0,6), + (1,6), + (2,6), + (3,6), + (4,6), + (5,6), + (6,6), + (7,6), + (8,6), + (9,6), + (17,6), + (10,6), + (11,6), + (12,6), + (13,6), + (14,6), + (15,6), + (16,6), + (50,7), + (53,7), + (62,7), + (52,7), + (56,7), + (51,7), + (49,7), + (54,7), + (57,7), + (55,7), + (61,7), + (58,7), + (60,7), + (59,7), + (42,7), + (48,7), + (40,7), + (39,7), + (43,7), + (46,7), + (41,7), + (44,7), + (45,7), + (47,7), + (37,7), + (34,7), + (33,7), + (32,7), + (28,7), + (26,7), + (36,7), + (38,7), + (35,7), + (27,7), + (30,7), + (31,7), + (29,7), + (24,7), + (23,7), + (22,7), + (21,7), + (25,7), + (20,7), + (7,7), + (9,7), + (13,7), + (16,7), + (12,7), + (14,7), + (15,7), + (19,7), + (10,7), + (8,7), + (11,7), + (18,7), + (17,7), + (6,7), + (5,7), + (4,7), + (3,7), + (2,7), + (1,7), + (0,7); + +END; \ No newline at end of file diff --git a/patch-schema/scenarios-counter.sql b/patch-schema/07-scenarios-counter.sql similarity index 100% rename from patch-schema/scenarios-counter.sql rename to patch-schema/07-scenarios-counter.sql From 8e27617727341ac747ae655ca68c6e4497b2a845 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 7 Oct 2023 19:58:49 +1100 Subject: [PATCH 047/269] convert Guild Missions to struct --- server/channelserver/handlers_guild.go | 50 +++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 5fdd2dac1..d578f5b7b 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -4,7 +4,6 @@ import ( "database/sql" "database/sql/driver" "encoding/binary" - "encoding/hex" "encoding/json" "errors" _config "erupe-ce/config" @@ -1723,16 +1722,51 @@ func handleMsgMhfReadGuildcard(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } +type GuildMission struct { + ID uint32 + Unk uint32 + Type uint16 + Goal uint16 + Quantity uint16 + SkipTickets uint16 + GR bool + RewardType uint16 + RewardLevel uint16 +} + func handleMsgMhfGetGuildMissionList(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGuildMissionList) - - decoded, err := hex.DecodeString("000694610000023E000112990023000100000200015DDD232100069462000002F30000005F000C000200000300025DDD232100069463000002EA0000005F0006000100000100015DDD23210006946400000245000000530010000200000400025DDD232100069465000002B60001129B0019000100000200015DDD232100069466000003DC0000001B0010000100000600015DDD232100069467000002DA000112A00019000100000400015DDD232100069468000002A800010DEF0032000200000200025DDD2321000694690000045500000022003C000200000600025DDD23210006946A00000080000122D90046000200000300025DDD23210006946B000001960000003B000A000100000100015DDD23210006946C0000049200000046005A000300000600035DDD23210006946D000000A4000000260018000200000600025DDD23210006946E0000017A00010DE40096000300000100035DDD23210006946F000001BE0000005E0014000200000400025DDD2355000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - - if err != nil { - panic(err) + bf := byteframe.NewByteFrame() + missions := []GuildMission{ + {431201, 574, 1, 4761, 35, 1, false, 2, 1}, + {431202, 755, 0, 95, 12, 2, false, 3, 2}, + {431203, 746, 0, 95, 6, 1, false, 1, 1}, + {431204, 581, 0, 83, 16, 2, false, 4, 2}, + {431205, 694, 1, 4763, 25, 1, false, 2, 1}, + {431206, 988, 0, 27, 16, 1, false, 6, 1}, + {431207, 730, 1, 4768, 25, 1, false, 4, 1}, + {431208, 680, 1, 3567, 50, 2, false, 2, 2}, + {431209, 1109, 0, 34, 60, 2, false, 6, 2}, + {431210, 128, 1, 8921, 70, 2, false, 3, 2}, + {431211, 406, 0, 59, 10, 1, false, 1, 1}, + {431212, 1170, 0, 70, 90, 3, false, 6, 3}, + {431213, 164, 0, 38, 24, 2, false, 6, 2}, + {431214, 378, 1, 3556, 150, 3, false, 1, 3}, + {431215, 446, 0, 94, 20, 2, false, 4, 2}, } - - doAckBufSucceed(s, pkt.AckHandle, decoded) + for _, mission := range missions { + bf.WriteUint32(mission.ID) + bf.WriteUint32(mission.Unk) + bf.WriteUint16(mission.Type) + bf.WriteUint16(mission.Goal) + bf.WriteUint16(mission.Quantity) + bf.WriteUint16(mission.SkipTickets) + bf.WriteBool(mission.GR) + bf.WriteUint16(mission.RewardType) + bf.WriteUint16(mission.RewardLevel) + bf.WriteUint32(uint32(TimeAdjusted().Unix())) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfGetGuildMissionRecord(s *Session, p mhfpacket.MHFPacket) { From f37915bcae365bb3f8ab69ec1f3c291c284f8c3d Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 7 Oct 2023 19:59:37 +1100 Subject: [PATCH 048/269] simplify Guild Huntdata & decimal conversions --- server/channelserver/handlers_guild.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index d578f5b7b..6ef507b84 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1851,14 +1851,14 @@ func handleMsgMhfGetGuildWeeklyBonusMaster(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGuildWeeklyBonusMaster) // Values taken from brand new guild capture - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x28)) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 40)) } func handleMsgMhfGetGuildWeeklyBonusActiveCount(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGuildWeeklyBonusActiveCount) bf := byteframe.NewByteFrame() - bf.WriteUint8(0x3C) // Active count - bf.WriteUint8(0x3C) // Current active count - bf.WriteUint8(0x00) // New active count + bf.WriteUint8(60) // Active count + bf.WriteUint8(60) // Current active count + bf.WriteUint8(0) // New active count doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } @@ -1866,18 +1866,18 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGuildHuntdata) bf := byteframe.NewByteFrame() switch pkt.Operation { - case 0: // Unk - doAckBufSucceed(s, pkt.AckHandle, []byte{}) - case 1: // Get Huntdata + case 0: // Acquire + // Probably mark everything as claimed + case 1: // Enumerate bf.WriteUint8(0) // Entries /* Entry format uint32 UnkID uint32 MonID */ - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - case 2: // Unk, controls glow - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00}) + case 2: // Check + bf.WriteBool(false) } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } type MessageBoardPost struct { From 641032f862d7354c605f6800c6126e6efc05402c Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 8 Oct 2023 19:25:48 +1100 Subject: [PATCH 049/269] implement hunt data logging (partial #82) --- common/mhfmon/mhfmon.go | 366 ++++++++++++++++++++++++ network/mhfpacket/msg_sys_record_log.go | 26 +- patch-schema/08-kill-counts.sql | 12 + server/channelserver/handlers.go | 14 +- server/channelserver/handlers_guild.go | 48 +++- 5 files changed, 445 insertions(+), 21 deletions(-) create mode 100644 common/mhfmon/mhfmon.go create mode 100644 patch-schema/08-kill-counts.sql diff --git a/common/mhfmon/mhfmon.go b/common/mhfmon/mhfmon.go new file mode 100644 index 000000000..183a75bd8 --- /dev/null +++ b/common/mhfmon/mhfmon.go @@ -0,0 +1,366 @@ +package mhfmon + +const ( + Mon0 = iota + Rathian + Fatalis + Kelbi + Mosswine + Bullfango + YianKutKu + LaoShanLung + Cephadrome + Felyne + VeggieElder + Rathalos + Aptonoth + Genprey + Diablos + Khezu + Velociprey + Gravios + Mon18 + Vespoid + Gypceros + Plesioth + Basarios + Melynx + Hornetaur + Apceros + Monoblos + Velocidrome + Gendrome + Mon29 + Ioprey + Iodrome + Mon32 + Kirin + Cephalos + Giaprey + CrimsonFatalis + PinkRathian + BlueYianKutKu + PurpleGypceros + YianGaruga + SilverRathalos + GoldRathian + BlackDiablos + WhiteMonoblos + RedKhezu + GreenPlesioth + BlackGravios + DaimyoHermitaur + AzureRathalos + AshenLaoShanLung + Blangonga + Congalala + Rajang + KushalaDaora + ShenGaoren + GreatThunderbug + Shakalaka + YamaTsukami + Chameleos + RustedKushalaDaora + Blango + Conga + Remobra + Lunastra + Teostra + Hermitaur + ShogunCeanataur + Bulldrome + Anteka + Popo + WhiteFatalis + Mon72 + Ceanataur + Hypnocatrice + Lavasioth + Tigrex + Akantor + BrightHypnoc + RedLavasioth + Espinas + OrangeEspinas + WhiteHypnoc + AqraVashimu + AqraJebia + Berukyurosu + Mon86 + Mon87 + Mon88 + Pariapuria + WhiteEspinas + KamuOrugaron + NonoOrugaron + Raviente + Dyuragaua + Doragyurosu + Gurenzeburu + Burukku + Erupe + Rukodiora + Unknown + Gogomoa + Kokomoa + TaikunZamuza + Abiorugu + Kuarusepusu + Odibatorasu + Disufiroa + Rebidiora + Anorupatisu + Hyujikiki + Midogaron + Giaorugu + MiRu + Farunokku + Pokaradon + Shantien + Pokara + Mon118 + Goruganosu + Aruganosu + Baruragaru + Zerureusu + Gougarf + Uruki + Forokururu + Meraginasu + Diorex + GarubaDaora + Inagami + Varusaburosu + Poborubarumu + Block1Duremudira + Mon133 + Mon134 + Mon135 + Mon136 + Mon137 + Mon138 + Gureadomosu + Harudomerugu + Toridcless + Gasurabazura + Kusubami + YamaKurai + Block2Duremudira + Zinogre + Deviljho + Brachydios + BerserkRaviente + ToaTesukatora + Barioth + Uragaan + StygianZinogre + Guanzorumu + SavageDeviljho + Mon156 + Egyurasu + Voljang + Nargacuga + Keoaruboru + Zenaserisu + GoreMagala + BlinkingNargacuga + ShagaruMagala + Amatsu + Eruzerion + MusouDuremudira + Mon168 + Seregios + Bogabadorumu + Mon171 + MusouBogabadorumu + CostumedUruki + MusouZerureusu + Rappy + KingShakalaka +) + +type Monster struct { + Name string + Large bool +} + +var Monsters = []Monster{ + {"Mon0", false}, + {"Rathian", true}, + {"Fatalis", true}, + {"Kelbi", false}, + {"Mosswine", false}, + {"Bullfango", false}, + {"Yian Kut-Ku", true}, + {"Lao-Shan Lung", true}, + {"Cephadrome", true}, + {"Felyne", false}, + {"Veggie Elder", false}, + {"Rathalos", true}, + {"Aptonoth", false}, + {"Genprey", false}, + {"Diablos", true}, + {"Khezu", true}, + {"Velociprey", false}, + {"Gravios", true}, + {"Mon18", false}, + {"Vespoid", false}, + {"Gypceros", true}, + {"Plesioth", true}, + {"Basarios", true}, + {"Melynx", false}, + {"Hornetaur", false}, + {"Apceros", false}, + {"Monoblos", true}, + {"Velocidrome", true}, + {"Gendrome", true}, + {"Mon29", false}, + {"Ioprey", false}, + {"Iodrome", true}, + {"Mon32", false}, + {"Kirin", true}, + {"Cephalos", false}, + {"Giaprey", false}, + {"Crimson Fatalis", true}, + {"Pink Rathian", true}, + {"Blue Yian Kut-Ku", true}, + {"Purple Gypceros", true}, + {"Yian Garuga", true}, + {"Silver Rathalos", true}, + {"Gold Rathian", true}, + {"Black Diablos", true}, + {"White Monoblos", true}, + {"Red Khezu", true}, + {"Green Plesioth", true}, + {"Black Gravios", true}, + {"Daimyo Hermitaur", true}, + {"Azure Rathalos", true}, + {"Ashen Lao-Shan Lung", true}, + {"Blangonga", true}, + {"Congalala", true}, + {"Rajang", true}, + {"Kushala Daora", true}, + {"Shen Gaoren", true}, + {"Great Thunderbug", false}, + {"Shakalaka", false}, + {"Yama Tsukami", true}, + {"Chameleos", true}, + {"Rusted Kushala Daora", true}, + {"Blango", false}, + {"Conga", false}, + {"Remobra", false}, + {"Lunastra", true}, + {"Teostra", true}, + {"Hermitaur", false}, + {"Shogun Ceanataur", true}, + {"Bulldrome", true}, + {"Anteka", false}, + {"Popo", false}, + {"White Fatalis", true}, + {"Mon72", false}, + {"Ceanataur", false}, + {"Hypnocatrice", true}, + {"Lavasioth", true}, + {"Tigrex", true}, + {"Akantor", true}, + {"Bright Hypnocatrice", true}, + {"Red Lavasioth", true}, + {"Espinas", true}, + {"Orange Espinas", true}, + {"White Hypnocatrice", true}, + {"Aqra Vashimu", true}, + {"Aqra Jebia", true}, + {"Berukyurosu", true}, + {"Mon86", false}, + {"Mon87", false}, + {"Mon88", false}, + {"Pariapuria", true}, + {"White Espinas", true}, + {"Kamu Orugaron", true}, + {"Nono Orugaron", true}, + {"Raviente", true}, // + Violent + {"Dyuragaua", true}, + {"Doragyurosu", true}, + {"Gurenzeburu", true}, + {"Burukku", false}, + {"Erupe", false}, + {"Rukodiora", true}, + {"Unknown", true}, + {"Gogomoa", true}, + {"Kokomoa", false}, + {"Taikun Zamuza", true}, + {"Abiorugu", true}, + {"Kuarusepusu", true}, + {"Odibatorasu", true}, + {"Disufiroa", true}, + {"Rebidiora", true}, + {"Anorupatisu", true}, + {"Hyujikiki", true}, + {"Midogaron", true}, + {"Giaorugu", true}, + {"Mi-Ru", true}, // + Musou + {"Farunokku", true}, + {"Pokaradon", true}, + {"Shantien", true}, + {"Pokara", false}, + {"Mon118", false}, + {"Goruganosu", true}, + {"Aruganosu", true}, + {"Baruragaru", true}, + {"Zerureusu", true}, + {"Gougarf", true}, // Both + {"Uruki", false}, + {"Forokururu", true}, + {"Meraginasu", true}, + {"Diorex", true}, + {"Garuba Daora", true}, + {"Inagami", true}, + {"Varusablos", true}, + {"Poborubarumu", true}, + {"1st Block Duremudira", true}, + {"Mon133", false}, + {"Mon134", false}, + {"Mon135", false}, + {"Mon136", false}, + {"Mon137", false}, + {"Mon138", false}, + {"Gureadomosu", true}, + {"Harudomerugu", true}, + {"Toridcless", true}, + {"Gasurabazura", true}, + {"Kusubami", false}, + {"Yama Kurai", true}, + {"2nd Block Duremudira", true}, + {"Zinogre", true}, + {"Deviljho", true}, + {"Brachydios", true}, + {"Berserk Raviente", true}, + {"Toa Tesukatora", true}, + {"Barioth", true}, + {"Uragaan", true}, + {"Stygian Zinogre", true}, + {"Guanzorumu", true}, + {"Savage Deviljho", true}, // + Starving/Heavenly + {"Mon156", false}, + {"Egyurasu", false}, + {"Voljang", true}, + {"Nargacuga", true}, + {"Keoaruboru", true}, + {"Zenaserisu", true}, + {"Gore Magala", true}, + {"Blinking Nargacuga", true}, + {"Shagaru Magala", true}, + {"Amatsu", true}, + {"Eruzerion", true}, // + Musou + {"Musou Duremudira", true}, + {"Mon168", false}, + {"Seregios", true}, + {"Bogabadorumu", true}, + {"Mon171", false}, + {"Musou Bogabadorumu", true}, + {"Costumed Uruki", false}, + {"Musou Zerureusu", true}, + {"Rappy", false}, + {"King Shakalaka", false}, +} diff --git a/network/mhfpacket/msg_sys_record_log.go b/network/mhfpacket/msg_sys_record_log.go index 4266ec774..dcdf3e5a2 100644 --- a/network/mhfpacket/msg_sys_record_log.go +++ b/network/mhfpacket/msg_sys_record_log.go @@ -1,21 +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" ) // MsgSysRecordLog represents the MSG_SYS_RECORD_LOG type MsgSysRecordLog struct { - AckHandle uint32 - Unk0 uint32 - Unk1 uint16 // Hardcoded 0 - HardcodedDataSize uint16 // Hardcoded 0x4AC - Unk3 uint32 // Some shared ID with MSG_MHF_GET_SEIBATTLE. World ID?? - DataBuf []byte + AckHandle uint32 + Unk0 uint32 + Unk1 uint32 + Data []byte } // Opcode returns the ID associated with this packet type. @@ -27,10 +25,10 @@ func (m *MsgSysRecordLog) Opcode() network.PacketID { func (m *MsgSysRecordLog) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint32() - m.Unk1 = bf.ReadUint16() - m.HardcodedDataSize = bf.ReadUint16() - m.Unk3 = bf.ReadUint32() - m.DataBuf = bf.ReadBytes(uint(m.HardcodedDataSize)) + bf.ReadUint16() // Zeroed + size := bf.ReadUint16() + m.Unk1 = bf.ReadUint32() + m.Data = bf.ReadBytes(uint(size)) return nil } diff --git a/patch-schema/08-kill-counts.sql b/patch-schema/08-kill-counts.sql new file mode 100644 index 000000000..1c170cedd --- /dev/null +++ b/patch-schema/08-kill-counts.sql @@ -0,0 +1,12 @@ +CREATE TABLE public.kill_logs +( + id serial, + character_id integer NOT NULL, + monster integer NOT NULL, + quantity integer NOT NULL, + timestamp timestamp with time zone NOT NULL, + PRIMARY KEY (id) +); + +ALTER TABLE IF EXISTS public.guild_characters + ADD COLUMN box_claimed timestamp with time zone DEFAULT now(); \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 6af061b13..e39942671 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "encoding/hex" "erupe-ce/common/mhfcourse" + "erupe-ce/common/mhfmon" ps "erupe-ce/common/pascalstring" "erupe-ce/common/stringsupport" _config "erupe-ce/config" @@ -305,9 +306,20 @@ func handleMsgSysIssueLogkey(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysRecordLog(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysRecordLog) + if _config.ErupeConfig.RealClientMode == _config.ZZ { + bf := byteframe.NewByteFrameFromBytes(pkt.Data) + bf.Seek(32, 0) + var val uint8 + for i := 0; i < 176; i++ { + val = bf.ReadUint8() + if val > 0 && mhfmon.Monsters[i].Large { + s.server.db.Exec(`INSERT INTO kill_logs (character_id, monster, quantity, timestamp) VALUES ($1, $2, $3, $4)`, s.charID, i, val, TimeAdjusted()) + } + } + } // remove a client returning to town from reserved slots to make sure the stage is hidden from board delete(s.stage.reservedClientSlots, s.charID) - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgSysEcho(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 6ef507b84..a1b4eb397 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1867,15 +1867,51 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() switch pkt.Operation { case 0: // Acquire - // Probably mark everything as claimed + s.server.db.Exec(`UPDATE guild_characters SET box_claimed=$1 WHERE character_id=$2`, TimeAdjusted(), s.charID) case 1: // Enumerate bf.WriteUint8(0) // Entries - /* Entry format - uint32 UnkID - uint32 MonID - */ + rows, err := s.server.db.Query(`SELECT kl.id, kl.monster FROM kill_logs kl + INNER JOIN guild_characters gc ON kl.character_id = gc.character_id + WHERE gc.guild_id=$1 + AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2) + `, pkt.GuildID, s.charID) + if err == nil { + var count uint8 + var huntID, monID uint32 + for rows.Next() { + err = rows.Scan(&huntID, &monID) + if err != nil { + continue + } + count++ + if count > 255 { + count = 255 + rows.Close() + break + } + bf.WriteUint32(huntID) + bf.WriteUint32(monID) + } + bf.Seek(0, 0) + bf.WriteUint8(count) + } case 2: // Check - bf.WriteBool(false) + guild, err := GetGuildInfoByCharacterId(s, s.charID) + if err == nil { + var count uint8 + err = s.server.db.QueryRow(`SELECT COUNT(*) FROM kill_logs kl + INNER JOIN guild_characters gc ON kl.character_id = gc.character_id + WHERE gc.guild_id=$1 + AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2) + `, guild.ID, s.charID).Scan(&count) + if err == nil && count > 0 { + bf.WriteBool(true) + } else { + bf.WriteBool(false) + } + } else { + bf.WriteBool(false) + } } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From 6f9969852560375cecaf1f76e3519a83f70f336a Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 8 Oct 2023 20:37:04 +1100 Subject: [PATCH 050/269] fix indentation --- server/channelserver/handlers_guild.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index a1b4eb397..b8c7c04f6 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1900,10 +1900,10 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) { if err == nil { var count uint8 err = s.server.db.QueryRow(`SELECT COUNT(*) FROM kill_logs kl - INNER JOIN guild_characters gc ON kl.character_id = gc.character_id - WHERE gc.guild_id=$1 - AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2) - `, guild.ID, s.charID).Scan(&count) + INNER JOIN guild_characters gc ON kl.character_id = gc.character_id + WHERE gc.guild_id=$1 + AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2) + `, guild.ID, s.charID).Scan(&count) if err == nil && count > 0 { bf.WriteBool(true) } else { From dd26fc73c3a0f08e066141d54280bdfcf8e19294 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 12 Oct 2023 23:25:35 +1100 Subject: [PATCH 051/269] adjust GUrgentRate definition --- config.json | 2 +- config/config.go | 2 +- server/channelserver/handlers_quest.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index 5ed2ab9fd..bd943d43b 100644 --- a/config.json +++ b/config.json @@ -54,7 +54,7 @@ "BerserkRavienteMaxPlayers": 32, "ExtremeRavienteMaxPlayers": 32, "SmallBerserkRavienteMaxPlayers": 8, - "GUrgentRate": 10, + "GUrgentRate": 0.10, "GCPMultiplier": 1.00, "GRPMultiplier": 1.00, "GSRPMultiplier": 1.00, diff --git a/config/config.go b/config/config.go index b5afc6798..713b08a90 100644 --- a/config/config.go +++ b/config/config.go @@ -139,7 +139,7 @@ type GameplayOptions struct { BerserkRavienteMaxPlayers uint8 ExtremeRavienteMaxPlayers uint8 SmallBerserkRavienteMaxPlayers uint8 - GUrgentRate uint16 // Adjusts the rate of G Urgent quests spawning + GUrgentRate float32 // Adjusts the rate of G Urgent quests spawning GCPMultiplier float32 // Adjusts the multiplier of GCP rewarded for quest completion GRPMultiplier float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion GSRPMultiplier float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index d9ec2bc08..803509f45 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -565,7 +565,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { tuneValues = append(tuneValues, tuneValue{1020, uint16(s.server.erupeConfig.GameplayOptions.GCPMultiplier * 100)}) - tuneValues = append(tuneValues, tuneValue{1029, s.server.erupeConfig.GameplayOptions.GUrgentRate}) + tuneValues = append(tuneValues, tuneValue{1029, uint16(s.server.erupeConfig.GameplayOptions.GUrgentRate * 100)}) if s.server.erupeConfig.GameplayOptions.DisableHunterNavi { tuneValues = append(tuneValues, tuneValue{1037, 1}) From 0de15e84400f20b6ae5849336cd76337deeedcad Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 12 Oct 2023 23:26:12 +1100 Subject: [PATCH 052/269] rewrite ReadMercenaryW parser --- network/mhfpacket/msg_mhf_read_mercenary_w.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/network/mhfpacket/msg_mhf_read_mercenary_w.go b/network/mhfpacket/msg_mhf_read_mercenary_w.go index 3aa9597d9..d70ef4f38 100644 --- a/network/mhfpacket/msg_mhf_read_mercenary_w.go +++ b/network/mhfpacket/msg_mhf_read_mercenary_w.go @@ -13,7 +13,6 @@ type MsgMhfReadMercenaryW struct { AckHandle uint32 Op uint8 Unk1 uint8 - Unk2 uint16 // Hardcoded 0 in the binary } // Opcode returns the ID associated with this packet type. @@ -25,8 +24,9 @@ func (m *MsgMhfReadMercenaryW) Opcode() network.PacketID { func (m *MsgMhfReadMercenaryW) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Op = bf.ReadUint8() - m.Unk1 = bf.ReadUint8() - m.Unk2 = bf.ReadUint16() + m.Unk1 = bf.ReadUint8() // Supposed to be 0 or 1, but always 1 + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed return nil } From 94f9174afad22413b453e078c7ccee473c37e799 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 12 Oct 2023 23:27:54 +1100 Subject: [PATCH 053/269] change Airou struct definition & remove airoulist.bin --- .../channelserver/handlers_guild_tresure.go | 4 +- server/channelserver/handlers_mercenary.go | 52 +++++++------------ 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/server/channelserver/handlers_guild_tresure.go b/server/channelserver/handlers_guild_tresure.go index 3d0918a84..981d8d258 100644 --- a/server/channelserver/handlers_guild_tresure.go +++ b/server/channelserver/handlers_guild_tresure.go @@ -92,8 +92,8 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) { if catID > 0 { catsUsed = stringsupport.CSVAdd(catsUsed, int(catID)) for _, cat := range guildCats { - if cat.CatID == catID { - huntData.WriteBytes(cat.CatName) + if cat.ID == catID { + huntData.WriteBytes(cat.Name) break } } diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index 810786751..25e4e3edc 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -9,8 +9,6 @@ import ( "erupe-ce/server/channelserver/compression/nullcomp" "go.uber.org/zap" "io" - "os" - "path/filepath" "time" ) @@ -299,18 +297,12 @@ func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateAiroulist) resp := byteframe.NewByteFrame() - if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin")); err == nil { - data, _ := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin")) - resp.WriteBytes(data) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - return - } airouList := getGuildAirouList(s) resp.WriteUint16(uint16(len(airouList))) resp.WriteUint16(uint16(len(airouList))) for _, cat := range airouList { - resp.WriteUint32(cat.CatID) - resp.WriteBytes(cat.CatName) + resp.WriteUint32(cat.ID) + resp.WriteBytes(cat.Name) resp.WriteUint32(cat.Experience) resp.WriteUint8(cat.Personality) resp.WriteUint8(cat.Class) @@ -321,11 +313,10 @@ func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } -// CatDefinition holds values needed to populate the guild cat list -type CatDefinition struct { - CatID uint32 - CatName []byte - CurrentTask uint8 +type Airou struct { + ID uint32 + Name []byte + Task uint8 Personality uint8 Class uint8 Experience uint32 @@ -333,10 +324,10 @@ type CatDefinition struct { WeaponID uint16 } -func getGuildAirouList(s *Session) []CatDefinition { +func getGuildAirouList(s *Session) []Airou { var guild *Guild var err error - var guildCats []CatDefinition + var guildCats []Airou // returning 0 cats on any guild issues // can probably optimise all of the guild queries pretty heavily @@ -365,7 +356,6 @@ func getGuildAirouList(s *Session) []CatDefinition { } } - // ellie's GetGuildMembers didn't seem to pull leader? rows, err = s.server.db.Query(`SELECT c.otomoairou FROM characters c INNER JOIN guild_characters gc @@ -381,11 +371,7 @@ func getGuildAirouList(s *Session) []CatDefinition { for rows.Next() { var data []byte err = rows.Scan(&data) - if err != nil { - s.logger.Warn("select failure", zap.Error(err)) - continue - } else if len(data) == 0 { - // non extant cats that aren't null in DB + if err != nil || len(data) == 0 { continue } // first byte has cat existence in general, can skip if 0 @@ -396,10 +382,10 @@ func getGuildAirouList(s *Session) []CatDefinition { continue } bf := byteframe.NewByteFrameFromBytes(decomp) - cats := GetCatDetails(bf) + cats := GetAirouDetails(bf) for _, cat := range cats { - _, exists := bannedCats[cat.CatID] - if cat.CurrentTask == 4 && !exists { + _, exists := bannedCats[cat.ID] + if cat.Task == 4 && !exists { guildCats = append(guildCats, cat) } } @@ -408,20 +394,20 @@ func getGuildAirouList(s *Session) []CatDefinition { return guildCats } -func GetCatDetails(bf *byteframe.ByteFrame) []CatDefinition { +func GetAirouDetails(bf *byteframe.ByteFrame) []Airou { catCount := bf.ReadUint8() - cats := make([]CatDefinition, catCount) + cats := make([]Airou, catCount) for x := 0; x < int(catCount); x++ { - var catDef CatDefinition + var catDef Airou // cat sometimes has additional bytes for whatever reason, gift items? timestamp? // until actual variance is known we can just seek to end based on start catDefLen := bf.ReadUint32() catStart, _ := bf.Seek(0, io.SeekCurrent) - catDef.CatID = bf.ReadUint32() - bf.Seek(1, io.SeekCurrent) // unknown value, probably a bool - catDef.CatName = bf.ReadBytes(18) // always 18 len, reads first null terminated string out of section and discards rest - catDef.CurrentTask = bf.ReadUint8() + catDef.ID = bf.ReadUint32() + bf.Seek(1, io.SeekCurrent) // unknown value, probably a bool + catDef.Name = bf.ReadBytes(18) // always 18 len, reads first null terminated string out of section and discards rest + catDef.Task = bf.ReadUint8() bf.Seek(16, io.SeekCurrent) // appearance data and what is seemingly null bytes catDef.Personality = bf.ReadUint8() catDef.Class = bf.ReadUint8() From 7194cdbc0740ffff3fde1a77b7fe4d1fad79d039 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 12 Oct 2023 23:54:35 +1100 Subject: [PATCH 054/269] optimise GuildTresure handlers --- config.json | 1 + config/config.go | 1 + .../channelserver/handlers_guild_tresure.go | 117 +++++++++--------- 3 files changed, 58 insertions(+), 61 deletions(-) diff --git a/config.json b/config.json index bd943d43b..31629b894 100644 --- a/config.json +++ b/config.json @@ -40,6 +40,7 @@ "MaximumNP": 100000, "MaximumRP": 50000, "MaximumFP": 120000, + "TreasureHuntExpiry": 604800, "DisableLoginBoost": false, "DisableBoostTime": false, "BoostTimeDuration": 120, diff --git a/config/config.go b/config/config.go index 713b08a90..25ac45313 100644 --- a/config/config.go +++ b/config/config.go @@ -125,6 +125,7 @@ type GameplayOptions struct { MaximumNP int // Maximum number of NP held by a player MaximumRP uint16 // Maximum number of RP held by a player MaximumFP uint32 // Maximum number of FP held by a player + TreasureHuntExpiry uint32 // Seconds until a Clan Treasure Hunt will expire DisableLoginBoost bool // Disables the Login Boost system DisableBoostTime bool // Disables the daily NetCafe Boost Time BoostTimeDuration int // The number of minutes NetCafe Boost Time lasts for diff --git a/server/channelserver/handlers_guild_tresure.go b/server/channelserver/handlers_guild_tresure.go index 981d8d258..b856293ab 100644 --- a/server/channelserver/handlers_guild_tresure.go +++ b/server/channelserver/handlers_guild_tresure.go @@ -22,54 +22,54 @@ type TreasureHunt struct { func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateGuildTresure) guild, err := GetGuildInfoByCharacterId(s, s.charID) - if err != nil { - panic(err) + if err != nil || guild == nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } + var hunts []TreasureHunt + var hunt TreasureHunt + rows, err := s.server.db.Queryx(`SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1 AND $2 < return+$3 + `, guild.ID, TimeAdjusted().Unix(), s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry) + if err != nil { + rows.Close() + return } - bf := byteframe.NewByteFrame() - hunts := 0 - rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1 AND $2 < return+604800", guild.ID, TimeAdjusted().Unix()) for rows.Next() { - hunt := &TreasureHunt{} err = rows.StructScan(&hunt) + if err != nil { + continue + } // Remove self from other hunter count hunt.Hunters = stringsupport.CSVRemove(hunt.Hunters, int(s.charID)) - if err != nil { - panic(err) - } if pkt.MaxHunts == 1 { if hunt.HostID != s.charID || hunt.Acquired { continue } - hunts++ - bf.WriteUint32(hunt.HuntID) - bf.WriteUint32(hunt.Destination) - bf.WriteUint32(hunt.Level) - bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters))) - bf.WriteUint32(hunt.Return) - bf.WriteBool(false) - bf.WriteBool(false) - bf.WriteBytes(hunt.HuntData) + hunt.Claimed = false + hunt.Treasure = "" + hunts = append(hunts, hunt) break } else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 { - if hunts == 30 { - break - } - hunts++ - bf.WriteUint32(hunt.HuntID) - bf.WriteUint32(hunt.Destination) - bf.WriteUint32(hunt.Level) - bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters))) - bf.WriteUint32(hunt.Return) - bf.WriteBool(hunt.Claimed) - bf.WriteBool(stringsupport.CSVContains(hunt.Treasure, int(s.charID))) - bf.WriteBytes(hunt.HuntData) + hunts = append(hunts, hunt) } } - resp := byteframe.NewByteFrame() - resp.WriteUint16(uint16(hunts)) - resp.WriteUint16(uint16(hunts)) - resp.WriteBytes(bf.Data()) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + if len(hunts) > 30 { + hunts = hunts[:30] + } + bf := byteframe.NewByteFrame() + bf.WriteUint16(uint16(len(hunts))) + bf.WriteUint16(uint16(len(hunts))) + for _, h := range hunts { + bf.WriteUint32(h.HuntID) + bf.WriteUint32(h.Destination) + bf.WriteUint32(h.Level) + bf.WriteUint32(uint32(stringsupport.CSVLength(h.Hunters))) + bf.WriteUint32(h.Return) + bf.WriteBool(h.Claimed) + bf.WriteBool(stringsupport.CSVContains(h.Treasure, int(s.charID))) + bf.WriteBytes(h.HuntData) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) { @@ -77,8 +77,9 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrameFromBytes(pkt.Data) huntData := byteframe.NewByteFrame() guild, err := GetGuildInfoByCharacterId(s, s.charID) - if err != nil { - panic(err) + if err != nil || guild == nil { + doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) + return } guildCats := getGuildAirouList(s) destination := bf.ReadUint32() @@ -100,20 +101,14 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) { huntData.WriteBytes(bf.ReadBytes(9)) } } - _, err = s.server.db.Exec("INSERT INTO guild_hunts (guild_id, host_id, destination, level, return, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6, $7)", - guild.ID, s.charID, destination, level, TimeAdjusted().Unix(), huntData.Data(), catsUsed) - if err != nil { - panic(err) - } + s.server.db.Exec(`INSERT INTO guild_hunts (guild_id, host_id, destination, level, return, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6, $7) + `, guild.ID, s.charID, destination, level, TimeAdjusted().Unix(), huntData.Data(), catsUsed) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresure) - _, err := s.server.db.Exec("UPDATE guild_hunts SET acquired=true WHERE id=$1", pkt.HuntID) - if err != nil { - panic(err) - } + s.server.db.Exec("UPDATE guild_hunts SET acquired=true WHERE id=$1", pkt.HuntID) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } @@ -126,6 +121,7 @@ func treasureHuntUnregister(s *Session) { var hunters string rows, err := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID) if err != nil { + rows.Close() return } for rows.Next() { @@ -138,41 +134,40 @@ func treasureHuntUnregister(s *Session) { func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport) var csv string - if pkt.State == 0 { // Report registration + switch pkt.State { + case 0: // Report registration // Unregister from all other hunts treasureHuntUnregister(s) if pkt.HuntID != 0 { // Register to selected hunt err := s.server.db.QueryRow("SELECT hunters FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv) if err != nil { - panic(err) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } csv = stringsupport.CSVAdd(csv, int(s.charID)) - _, err = s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", csv, pkt.HuntID) - if err != nil { - panic(err) - } + s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", csv, pkt.HuntID) } - } else if pkt.State == 1 { // Collected by hunter + case 1: // Collected by hunter s.server.db.Exec("UPDATE guild_hunts SET hunters='', claimed=true WHERE id=$1", pkt.HuntID) - } else if pkt.State == 2 { // Claim treasure + case 2: // Claim treasure err := s.server.db.QueryRow("SELECT treasure FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv) if err != nil { - panic(err) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } csv = stringsupport.CSVAdd(csv, int(s.charID)) - _, err = s.server.db.Exec("UPDATE guild_hunts SET treasure=$1 WHERE id=$2", csv, pkt.HuntID) - if err != nil { - panic(err) - } + s.server.db.Exec("UPDATE guild_hunts SET treasure=$1 WHERE id=$2", csv, pkt.HuntID) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfGetGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGuildTresureSouvenir) - - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6)) + bf := byteframe.NewByteFrame() + bf.WriteUint32(0) + bf.WriteUint16(0) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfAcquireGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) { From bf44944884f2ad8b720121b8960d8627a913f0f2 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 14 Oct 2023 14:14:28 +1100 Subject: [PATCH 055/269] more GuildTresure optimisation --- patch-schema/09-fix-guild-treasure.sql | 8 ++++ server/channelserver/handlers.go | 2 +- .../channelserver/handlers_guild_tresure.go | 46 +++---------------- 3 files changed, 16 insertions(+), 40 deletions(-) create mode 100644 patch-schema/09-fix-guild-treasure.sql diff --git a/patch-schema/09-fix-guild-treasure.sql b/patch-schema/09-fix-guild-treasure.sql new file mode 100644 index 000000000..5abfe3271 --- /dev/null +++ b/patch-schema/09-fix-guild-treasure.sql @@ -0,0 +1,8 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.guild_hunts DROP COLUMN IF EXISTS hunters; + +ALTER TABLE IF EXISTS public.guild_characters + ADD COLUMN treasure_hunt integer; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index e39942671..211fcaf79 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -233,7 +233,7 @@ func logoutPlayer(s *Session) { s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID) - treasureHuntUnregister(s) + s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=NULL WHERE character_id=$1`, s.charID) if s.stage == nil { return diff --git a/server/channelserver/handlers_guild_tresure.go b/server/channelserver/handlers_guild_tresure.go index b856293ab..e34e36d23 100644 --- a/server/channelserver/handlers_guild_tresure.go +++ b/server/channelserver/handlers_guild_tresure.go @@ -108,56 +108,24 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresure) - s.server.db.Exec("UPDATE guild_hunts SET acquired=true WHERE id=$1", pkt.HuntID) + s.server.db.Exec(`UPDATE guild_hunts SET acquired=true WHERE id=$1`, pkt.HuntID) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } -func treasureHuntUnregister(s *Session) { - guild, err := GetGuildInfoByCharacterId(s, s.charID) - if err != nil || guild == nil { - return - } - var huntID int - var hunters string - rows, err := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID) - if err != nil { - rows.Close() - return - } - for rows.Next() { - rows.Scan(&huntID, &hunters) - hunters = stringsupport.CSVRemove(hunters, int(s.charID)) - s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", hunters, huntID) - } -} - func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport) var csv string switch pkt.State { case 0: // Report registration - // Unregister from all other hunts - treasureHuntUnregister(s) - if pkt.HuntID != 0 { - // Register to selected hunt - err := s.server.db.QueryRow("SELECT hunters FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv) - if err != nil { - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return - } - csv = stringsupport.CSVAdd(csv, int(s.charID)) - s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", csv, pkt.HuntID) - } + s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=$1 WHERE character_id=$2`, pkt.HuntID, s.charID) case 1: // Collected by hunter - s.server.db.Exec("UPDATE guild_hunts SET hunters='', claimed=true WHERE id=$1", pkt.HuntID) + s.server.db.Exec(`UPDATE guild_hunts SET claimed=true WHERE id=$1;UPDATE guild_characters SET treasure_hunt=NULL WHERE treasure_hunt=$1`, pkt.HuntID) case 2: // Claim treasure - err := s.server.db.QueryRow("SELECT treasure FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv) - if err != nil { - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return + err := s.server.db.QueryRow(`SELECT treasure FROM guild_hunts WHERE id=$1`, pkt.HuntID).Scan(&csv) + if err == nil { + csv = stringsupport.CSVAdd(csv, int(s.charID)) + s.server.db.Exec(`UPDATE guild_hunts SET treasure=$1 WHERE id=$2`, csv, pkt.HuntID) } - csv = stringsupport.CSVAdd(csv, int(s.charID)) - s.server.db.Exec("UPDATE guild_hunts SET treasure=$1 WHERE id=$2", csv, pkt.HuntID) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } From e688fdf3c5ad49a16cab6fb05f9ec0d1b9e9a3c7 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 17 Oct 2023 00:35:06 +1100 Subject: [PATCH 056/269] more GuildTresure optimisation --- network/mhfpacket/msg_mhf_enumerate_guild_tresure.go | 8 ++++---- server/channelserver/handlers_guild_tresure.go | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/network/mhfpacket/msg_mhf_enumerate_guild_tresure.go b/network/mhfpacket/msg_mhf_enumerate_guild_tresure.go index 61475d655..f03202bd4 100644 --- a/network/mhfpacket/msg_mhf_enumerate_guild_tresure.go +++ b/network/mhfpacket/msg_mhf_enumerate_guild_tresure.go @@ -12,7 +12,8 @@ import ( type MsgMhfEnumerateGuildTresure struct { AckHandle uint32 MaxHunts uint16 - Unk uint32 + Unk0 uint16 + Unk1 uint16 } // Opcode returns the ID associated with this packet type. @@ -24,9 +25,8 @@ func (m *MsgMhfEnumerateGuildTresure) Opcode() network.PacketID { func (m *MsgMhfEnumerateGuildTresure) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.MaxHunts = bf.ReadUint16() - // Changes with MaxHunts - // 0 if MaxHunts = 1, 1 if MaxHunts = 30 - m.Unk = bf.ReadUint32() + m.Unk0 = bf.ReadUint16() + m.Unk1 = bf.ReadUint16() return nil } diff --git a/server/channelserver/handlers_guild_tresure.go b/server/channelserver/handlers_guild_tresure.go index e34e36d23..516991595 100644 --- a/server/channelserver/handlers_guild_tresure.go +++ b/server/channelserver/handlers_guild_tresure.go @@ -14,7 +14,7 @@ type TreasureHunt struct { Return uint32 `db:"return"` Acquired bool `db:"acquired"` Claimed bool `db:"claimed"` - Hunters string `db:"hunters"` + Hunters uint32 `db:"hunters"` Treasure string `db:"treasure"` HuntData []byte `db:"hunt_data"` } @@ -28,8 +28,10 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) { } var hunts []TreasureHunt var hunt TreasureHunt - rows, err := s.server.db.Queryx(`SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1 AND $2 < return+$3 - `, guild.ID, TimeAdjusted().Unix(), s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry) + rows, err := s.server.db.Queryx(`SELECT gh.id, gh.host_id, gh.destination, gh.level, gh.return, gh.acquired, gh.claimed, gh.treasure, gh.hunt_data, + (SELECT COUNT(*) FROM guild_characters gc WHERE gc.treasure_hunt = gh.id AND gc.character_id <> $1) AS hunters + FROM guild_hunts gh WHERE gh.guild_id=$2 AND $3 < gh.return+$4 + `, s.charID, guild.ID, TimeAdjusted().Unix(), s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry) if err != nil { rows.Close() return @@ -39,8 +41,6 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) { if err != nil { continue } - // Remove self from other hunter count - hunt.Hunters = stringsupport.CSVRemove(hunt.Hunters, int(s.charID)) if pkt.MaxHunts == 1 { if hunt.HostID != s.charID || hunt.Acquired { continue @@ -63,7 +63,7 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(h.HuntID) bf.WriteUint32(h.Destination) bf.WriteUint32(h.Level) - bf.WriteUint32(uint32(stringsupport.CSVLength(h.Hunters))) + bf.WriteUint32(h.Hunters) bf.WriteUint32(h.Return) bf.WriteBool(h.Claimed) bf.WriteBool(stringsupport.CSVContains(h.Treasure, int(s.charID))) From c4e8cd59992f7e450afbfce77ff1c0705584f950 Mon Sep 17 00:00:00 2001 From: stratic-dev Date: Tue, 24 Oct 2023 01:48:20 +0100 Subject: [PATCH 057/269] Added paxes fix and missing fields to db --- patch-schema/03-event_quests.sql | 6 ++- server/channelserver/handlers_quest.go | 63 ++++++++++++-------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/patch-schema/03-event_quests.sql b/patch-schema/03-event_quests.sql index 94aac0c65..b54758ffe 100644 --- a/patch-schema/03-event_quests.sql +++ b/patch-schema/03-event_quests.sql @@ -3,10 +3,14 @@ BEGIN; create table if not exists event_quests ( id serial primary key, + index_value integer, max_players integer, quest_type integer not null, quest_id integer not null, - mark integer + mark integer, + quest_option integer not null, + + ); ALTER TABLE IF EXISTS public.servers DROP COLUMN IF EXISTS season; diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 803509f45..f0995170a 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -47,10 +47,6 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { ) } - if s.server.erupeConfig.GameplayOptions.SeasonOverride { - pkt.Filename = seasonConversion(s, pkt.Filename) - } - data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) if err != nil { s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) @@ -62,28 +58,6 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { } } -func questSuffix(s *Session) string { - // Determine the letter to append for day / night - var timeSet string - if TimeGameAbsolute() > 2880 { - timeSet = "d" - } else { - timeSet = "n" - } - return fmt.Sprintf("%s%d", timeSet, s.server.Season()) -} - -func seasonConversion(s *Session, questFile string) string { - filename := fmt.Sprintf("%s%s", questFile[:5], questSuffix(s)) - - // Return original file if file doesn't exist - if _, err := os.Stat(filename); err == nil { - return filename - } else { - return questFile - } -} - func handleMsgMhfLoadFavoriteQuest(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadFavoriteQuest) var data []byte @@ -145,8 +119,9 @@ func loadQuestFile(s *Session, questId int) []byte { func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { var id, mark uint32 var questId int - var maxPlayers, questType uint8 - rows.Scan(&id, &maxPlayers, &questType, &questId, &mark) + var indexValue, maxPlayers, questType uint8 + var questOption uint16 + rows.Scan(&id, &indexValue, &maxPlayers, &questType, &questId, &mark, &questOption) data := loadQuestFile(s, questId) if data == nil { @@ -155,8 +130,8 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { bf := byteframe.NewByteFrame() bf.WriteUint32(id) - bf.WriteUint32(0) - bf.WriteUint8(0) // Indexer + bf.WriteUint32(0) //unk + bf.WriteUint8(indexValue) // Indexer switch questType { case 16: bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers) @@ -177,16 +152,36 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { } else { bf.WriteBool(true) } - bf.WriteUint16(0) + bf.WriteUint16(0) //unk counter id possibly? if _config.ErupeConfig.RealClientMode >= _config.G1 { bf.WriteUint32(mark) } - bf.WriteUint16(0) + bf.WriteUint16(0) //possible padding bf.WriteUint16(uint16(len(data))) bf.WriteBytes(data) - ps.Uint8(bf, "", true) // What is this string for? + //Season/RequiredObject Flag Replacement + bf.Seek(0x18, 0) + // Bitset Structure: bit 8 unk, bit 7 required objective,bit 6 unk,bit 5 night,bit 4 day,bit 3 cold,bit 2 warm,bit 1 breeding + // if the byte is set to 0 the game choses the quest file corresponding to whatever season the game is on + if s.server.erupeConfig.GameplayOptions.SeasonOverride { + bf.WriteUint16(0) + } else { + bf.WriteUint16(questOption) + } + + // Bitset Structure Quest Varient 1: bit 8 Hiden, bit 7 Fix HC,bit 6 HC to UL,bit 5 GRank,bit 4 Unk,bit 3 UNK,bit 2 UNK,bit 1 UNK + // Bitset Structure Quest Varient 2: bit 8 UNK, bit 7 No Halk Pots,bit 6 No halk/poogie,bit 5 Timer,bit 4 UNK,bit 3 Fixed Difficulty,bit 2 UNK,bit 1 UNK + // Bitset Structure Quest Varient 3: bit 8 Disable Reward Skill, bit 7 GSR to GR,bit 6 unk,bit 5 Musou?,bit 4 Zenith,bit 3 Inception,bit 2 UNK,bit 1 UNK + bf.Seek(0xAF, 0) + quest_variant_3 := bf.ReadUint8() + quest_variant_3 &= 0b11011111 //disables Inception flag at position 3 for any quests that dont have it set. + bf.Seek(0xAF, 0) + bf.WriteUint8(quest_variant_3) + + ps.Uint8(bf, "", true) // Debug/Notes string for quest return bf.Data(), nil + } func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { @@ -195,7 +190,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint16(0) - rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id") + rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, index_value,quest_type, quest_id,quest_option, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id") for rows.Next() { data, err := makeEventQuest(s, rows) if err != nil { From 2f8d09b09ee06dd0d03e39505ac4fd3329e0a2a9 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 24 Oct 2023 21:21:21 +1100 Subject: [PATCH 058/269] more GuildTresure optimisation --- config/config.go | 1 + main.go | 1 + patch-schema/09-fix-guild-treasure.sql | 18 +++ .../channelserver/handlers_guild_tresure.go | 105 ++++++++++-------- server/channelserver/handlers_mercenary.go | 46 ++++---- 5 files changed, 99 insertions(+), 72 deletions(-) diff --git a/config/config.go b/config/config.go index 25ac45313..65c663511 100644 --- a/config/config.go +++ b/config/config.go @@ -126,6 +126,7 @@ type GameplayOptions struct { MaximumRP uint16 // Maximum number of RP held by a player MaximumFP uint32 // Maximum number of FP held by a player TreasureHuntExpiry uint32 // Seconds until a Clan Treasure Hunt will expire + TreasureHuntPartnyaCooldown uint32 // Seconds until a Partnya can be assigned to another Clan Treasure Hunt DisableLoginBoost bool // Disables the Login Boost system DisableBoostTime bool // Disables the daily NetCafe Boost Time BoostTimeDuration int // The number of minutes NetCafe Boost Time lasts for diff --git a/main.go b/main.go index f549f4bc8..2a538b094 100644 --- a/main.go +++ b/main.go @@ -126,6 +126,7 @@ func main() { // Clear stale data _ = db.MustExec("DELETE FROM sign_sessions") _ = db.MustExec("DELETE FROM servers") + _ = db.MustExec(`UPDATE guild_characters SET treasure_hunt=NULL`) // Clean the DB if the option is on. if config.DevMode && config.DevModeOptions.CleanDB { diff --git a/patch-schema/09-fix-guild-treasure.sql b/patch-schema/09-fix-guild-treasure.sql index 5abfe3271..1c022292f 100644 --- a/patch-schema/09-fix-guild-treasure.sql +++ b/patch-schema/09-fix-guild-treasure.sql @@ -5,4 +5,22 @@ ALTER TABLE IF EXISTS public.guild_hunts DROP COLUMN IF EXISTS hunters; ALTER TABLE IF EXISTS public.guild_characters ADD COLUMN treasure_hunt integer; +ALTER TABLE IF EXISTS public.guild_hunts + ADD COLUMN start timestamp with time zone NOT NULL DEFAULT now(); + +UPDATE guild_hunts SET start=to_timestamp(return); + +ALTER TABLE IF EXISTS public.guild_hunts DROP COLUMN IF EXISTS "return"; + +ALTER TABLE IF EXISTS public.guild_hunts + RENAME claimed TO collected; + +CREATE TABLE public.guild_hunts_claimed +( + hunt_id integer NOT NULL, + character_id integer NOT NULL +); + +ALTER TABLE IF EXISTS public.guild_hunts DROP COLUMN IF EXISTS treasure; + END; \ No newline at end of file diff --git a/server/channelserver/handlers_guild_tresure.go b/server/channelserver/handlers_guild_tresure.go index 516991595..f3f4815e6 100644 --- a/server/channelserver/handlers_guild_tresure.go +++ b/server/channelserver/handlers_guild_tresure.go @@ -4,19 +4,20 @@ import ( "erupe-ce/common/byteframe" "erupe-ce/common/stringsupport" "erupe-ce/network/mhfpacket" + "time" ) type TreasureHunt struct { - HuntID uint32 `db:"id"` - HostID uint32 `db:"host_id"` - Destination uint32 `db:"destination"` - Level uint32 `db:"level"` - Return uint32 `db:"return"` - Acquired bool `db:"acquired"` - Claimed bool `db:"claimed"` - Hunters uint32 `db:"hunters"` - Treasure string `db:"treasure"` - HuntData []byte `db:"hunt_data"` + HuntID uint32 `db:"id"` + HostID uint32 `db:"host_id"` + Destination uint32 `db:"destination"` + Level uint32 `db:"level"` + Start time.Time `db:"start"` + Acquired bool `db:"acquired"` + Collected bool `db:"collected"` + HuntData []byte `db:"hunt_data"` + Hunters uint32 `db:"hunters"` + Claimed bool `db:"claimed"` } func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) { @@ -28,33 +29,39 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) { } var hunts []TreasureHunt var hunt TreasureHunt - rows, err := s.server.db.Queryx(`SELECT gh.id, gh.host_id, gh.destination, gh.level, gh.return, gh.acquired, gh.claimed, gh.treasure, gh.hunt_data, - (SELECT COUNT(*) FROM guild_characters gc WHERE gc.treasure_hunt = gh.id AND gc.character_id <> $1) AS hunters - FROM guild_hunts gh WHERE gh.guild_id=$2 AND $3 < gh.return+$4 - `, s.charID, guild.ID, TimeAdjusted().Unix(), s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry) - if err != nil { - rows.Close() - return - } - for rows.Next() { - err = rows.StructScan(&hunt) + + switch pkt.MaxHunts { + case 1: + err = s.server.db.QueryRowx(`SELECT id, host_id, destination, level, start, hunt_data FROM guild_hunts WHERE host_id=$1 AND acquired=FALSE`, s.charID).StructScan(&hunt) + if err == nil { + hunts = append(hunts, hunt) + } + case 30: + rows, err := s.server.db.Queryx(`SELECT gh.id, gh.host_id, gh.destination, gh.level, gh.start, gh.collected, gh.hunt_data, + (SELECT COUNT(*) FROM guild_characters gc WHERE gc.treasure_hunt = gh.id AND gc.character_id <> $1) AS hunters, + CASE + WHEN ghc.character_id IS NOT NULL THEN true + ELSE false + END AS claimed + FROM guild_hunts gh + LEFT JOIN guild_hunts_claimed ghc ON gh.id = ghc.hunt_id AND ghc.character_id = $1 + WHERE gh.guild_id=$2 AND gh.level=2 AND gh.acquired=TRUE + `, s.charID, guild.ID) if err != nil { - continue - } - if pkt.MaxHunts == 1 { - if hunt.HostID != s.charID || hunt.Acquired { - continue + rows.Close() + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } else { + for rows.Next() { + err = rows.StructScan(&hunt) + if err == nil && hunt.Start.Add(time.Second*time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry)).After(TimeAdjusted()) { + hunts = append(hunts, hunt) + } } - hunt.Claimed = false - hunt.Treasure = "" - hunts = append(hunts, hunt) - break - } else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 { - hunts = append(hunts, hunt) } - } - if len(hunts) > 30 { - hunts = hunts[:30] + if len(hunts) > 30 { + hunts = hunts[:30] + } } bf := byteframe.NewByteFrame() bf.WriteUint16(uint16(len(hunts))) @@ -64,9 +71,9 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(h.Destination) bf.WriteUint32(h.Level) bf.WriteUint32(h.Hunters) - bf.WriteUint32(h.Return) + bf.WriteUint32(uint32(h.Start.Unix())) + bf.WriteBool(h.Collected) bf.WriteBool(h.Claimed) - bf.WriteBool(stringsupport.CSVContains(h.Treasure, int(s.charID))) bf.WriteBytes(h.HuntData) } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) @@ -101,8 +108,8 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) { huntData.WriteBytes(bf.ReadBytes(9)) } } - s.server.db.Exec(`INSERT INTO guild_hunts (guild_id, host_id, destination, level, return, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6, $7) - `, guild.ID, s.charID, destination, level, TimeAdjusted().Unix(), huntData.Data(), catsUsed) + s.server.db.Exec(`INSERT INTO guild_hunts (guild_id, host_id, destination, level, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6) + `, guild.ID, s.charID, destination, level, huntData.Data(), catsUsed) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } @@ -114,27 +121,33 @@ func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport) - var csv string switch pkt.State { case 0: // Report registration s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=$1 WHERE character_id=$2`, pkt.HuntID, s.charID) case 1: // Collected by hunter - s.server.db.Exec(`UPDATE guild_hunts SET claimed=true WHERE id=$1;UPDATE guild_characters SET treasure_hunt=NULL WHERE treasure_hunt=$1`, pkt.HuntID) + s.server.db.Exec(`UPDATE guild_hunts SET collected=true WHERE id=$1`, pkt.HuntID) + s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=NULL WHERE treasure_hunt=$1`, pkt.HuntID) case 2: // Claim treasure - err := s.server.db.QueryRow(`SELECT treasure FROM guild_hunts WHERE id=$1`, pkt.HuntID).Scan(&csv) - if err == nil { - csv = stringsupport.CSVAdd(csv, int(s.charID)) - s.server.db.Exec(`UPDATE guild_hunts SET treasure=$1 WHERE id=$2`, csv, pkt.HuntID) - } + s.server.db.Exec(`INSERT INTO guild_hunts_claimed VALUES ($1, $2)`, pkt.HuntID, s.charID) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } +type TreasureSouvenir struct { + Destination uint32 + Quantity uint32 +} + func handleMsgMhfGetGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGuildTresureSouvenir) bf := byteframe.NewByteFrame() bf.WriteUint32(0) - bf.WriteUint16(0) + souvenirs := []TreasureSouvenir{} + bf.WriteUint16(uint16(len(souvenirs))) + for _, souvenir := range souvenirs { + bf.WriteUint32(souvenir.Destination) + bf.WriteUint32(souvenir.Quantity) + } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index 25e4e3edc..19c358c52 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -325,44 +325,38 @@ type Airou struct { } func getGuildAirouList(s *Session) []Airou { - var guild *Guild - var err error var guildCats []Airou - - // returning 0 cats on any guild issues - // can probably optimise all of the guild queries pretty heavily - guild, err = GetGuildInfoByCharacterId(s, s.charID) + bannedCats := make(map[uint32]int) + guild, err := GetGuildInfoByCharacterId(s, s.charID) if err != nil { return guildCats } - - // Get cats used recently - // Retail reset at midday, 12 hours is a midpoint - tempBanDuration := 43200 - (1800) // Minus hunt time - bannedCats := make(map[uint32]int) - var csvTemp string - rows, err := s.server.db.Query(`SELECT cats_used - FROM guild_hunts gh - INNER JOIN characters c - ON gh.host_id = c.id - WHERE c.id=$1 AND gh.return+$2>$3`, s.charID, tempBanDuration, TimeAdjusted().Unix()) + rows, err := s.server.db.Query(`SELECT cats_used FROM guild_hunts gh + INNER JOIN characters c ON gh.host_id = c.id WHERE c.id=$1 + `, s.charID) if err != nil { s.logger.Warn("Failed to get recently used airous", zap.Error(err)) + return guildCats } + + var csvTemp string + var startTemp time.Time for rows.Next() { - rows.Scan(&csvTemp) - for i, j := range stringsupport.CSVElems(csvTemp) { - bannedCats[uint32(j)] = i + err = rows.Scan(&csvTemp, &startTemp) + if err != nil { + continue + } + if startTemp.Add(time.Second * time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntPartnyaCooldown)).Before(TimeAdjusted()) { + for i, j := range stringsupport.CSVElems(csvTemp) { + bannedCats[uint32(j)] = i + } } } - rows, err = s.server.db.Query(`SELECT c.otomoairou - FROM characters c - INNER JOIN guild_characters gc - ON gc.character_id = c.id + rows, err = s.server.db.Query(`SELECT c.otomoairou FROM characters c + INNER JOIN guild_characters gc ON gc.character_id = c.id WHERE gc.guild_id = $1 AND c.otomoairou IS NOT NULL - ORDER BY c.id ASC - LIMIT 60;`, guild.ID) + ORDER BY c.id LIMIT 60`, guild.ID) if err != nil { s.logger.Warn("Selecting otomoairou based on guild failed", zap.Error(err)) return guildCats From ba50dc419ad68a7f2b00892402c2e4199aed00af Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 24 Oct 2023 21:24:35 +1100 Subject: [PATCH 059/269] fix parsing and handling of DistItem packets --- network/mhfpacket/msg_mhf_apply_dist_item.go | 20 ++++++-------- .../mhfpacket/msg_mhf_enumerate_dist_item.go | 15 +++++------ server/channelserver/handlers_distitem.go | 26 ++++++++++++++++--- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/network/mhfpacket/msg_mhf_apply_dist_item.go b/network/mhfpacket/msg_mhf_apply_dist_item.go index 456d8fb12..f98dbb19a 100644 --- a/network/mhfpacket/msg_mhf_apply_dist_item.go +++ b/network/mhfpacket/msg_mhf_apply_dist_item.go @@ -1,18 +1,19 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfApplyDistItem represents the MSG_MHF_APPLY_DIST_ITEM type MsgMhfApplyDistItem struct { - AckHandle uint32 + AckHandle uint32 DistributionType uint8 - DistributionID uint32 - Unk2 uint32 - Unk3 uint32 + DistributionID uint32 + Unk2 uint32 + Unk3 uint32 } // Opcode returns the ID associated with this packet type. @@ -32,10 +33,5 @@ func (m *MsgMhfApplyDistItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie // Build builds a binary packet from the current data. func (m *MsgMhfApplyDistItem) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint32(m.AckHandle) - bf.WriteUint8(m.DistributionType) - bf.WriteUint32(m.DistributionID) - bf.WriteUint32(m.Unk2) - bf.WriteUint32(m.Unk3) - return nil -} \ No newline at end of file + return errors.New("NOT IMPLEMENTED") +} diff --git a/network/mhfpacket/msg_mhf_enumerate_dist_item.go b/network/mhfpacket/msg_mhf_enumerate_dist_item.go index 778a91ac6..6d0082ec8 100644 --- a/network/mhfpacket/msg_mhf_enumerate_dist_item.go +++ b/network/mhfpacket/msg_mhf_enumerate_dist_item.go @@ -1,17 +1,19 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfEnumerateDistItem represents the MSG_MHF_ENUMERATE_DIST_ITEM type MsgMhfEnumerateDistItem struct { AckHandle uint32 Unk0 uint8 - Unk1 uint16 + Unk1 uint8 Unk2 uint16 + Unk3 []byte } // Opcode returns the ID associated with this packet type. @@ -23,16 +25,13 @@ func (m *MsgMhfEnumerateDistItem) Opcode() network.PacketID { func (m *MsgMhfEnumerateDistItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint8() - m.Unk1 = bf.ReadUint16() + m.Unk1 = bf.ReadUint8() m.Unk2 = bf.ReadUint16() + m.Unk3 = bf.ReadBytes(uint(bf.ReadUint8())) return nil } // Build builds a binary packet from the current data. func (m *MsgMhfEnumerateDistItem) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint32(m.AckHandle) - bf.WriteUint8(m.Unk0) - bf.WriteUint16(m.Unk1) - bf.WriteUint16(m.Unk2) - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index a2979756f..2bde7c60a 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -67,10 +67,28 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(distData.MaxSR) bf.WriteUint16(distData.MinGR) bf.WriteUint16(distData.MaxGR) - bf.WriteUint32(0) // Unk - bf.WriteUint32(0) // Unk - ps.Uint16(bf, distData.EventName, true) - bf.WriteBytes(make([]byte, 391)) + bf.WriteUint8(0) + bf.WriteUint16(0) + bf.WriteUint8(0) + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint8(0) + ps.Uint8(bf, distData.EventName, true) + for i := 0; i < 6; i++ { + for j := 0; j < 13; j++ { + bf.WriteUint8(0) + bf.WriteUint32(0) + } + } + i := uint8(0) + bf.WriteUint8(i) + if i <= 10 { + for j := uint8(0); j < i; j++ { + bf.WriteUint32(0) + bf.WriteUint32(0) + bf.WriteUint32(0) + } + } } resp := byteframe.NewByteFrame() resp.WriteUint16(uint16(distCount)) From 64cb106c70f877545e7b519fbf9e85861c768ae5 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 24 Oct 2023 22:18:09 +1100 Subject: [PATCH 060/269] formatting, strip out unknown values --- patch-schema/03-event_quests.sql | 6 +--- server/channelserver/handlers_quest.go | 47 +++++++++++++------------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/patch-schema/03-event_quests.sql b/patch-schema/03-event_quests.sql index b54758ffe..94aac0c65 100644 --- a/patch-schema/03-event_quests.sql +++ b/patch-schema/03-event_quests.sql @@ -3,14 +3,10 @@ BEGIN; create table if not exists event_quests ( id serial primary key, - index_value integer, max_players integer, quest_type integer not null, quest_id integer not null, - mark integer, - quest_option integer not null, - - + mark integer ); ALTER TABLE IF EXISTS public.servers DROP COLUMN IF EXISTS season; diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index f0995170a..aab84b960 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -119,9 +119,8 @@ func loadQuestFile(s *Session, questId int) []byte { func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { var id, mark uint32 var questId int - var indexValue, maxPlayers, questType uint8 - var questOption uint16 - rows.Scan(&id, &indexValue, &maxPlayers, &questType, &questId, &mark, &questOption) + var maxPlayers, questType uint8 + rows.Scan(&id, &maxPlayers, &questType, &questId, &mark) data := loadQuestFile(s, questId) if data == nil { @@ -130,8 +129,8 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { bf := byteframe.NewByteFrame() bf.WriteUint32(id) - bf.WriteUint32(0) //unk - bf.WriteUint8(indexValue) // Indexer + bf.WriteUint32(0) // Unk + bf.WriteUint8(0) // Unk switch questType { case 16: bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers) @@ -152,36 +151,38 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { } else { bf.WriteBool(true) } - bf.WriteUint16(0) //unk counter id possibly? + bf.WriteUint16(0) // Unk if _config.ErupeConfig.RealClientMode >= _config.G1 { bf.WriteUint32(mark) } - bf.WriteUint16(0) //possible padding + bf.WriteUint16(0) // Unk bf.WriteUint16(uint16(len(data))) bf.WriteBytes(data) - //Season/RequiredObject Flag Replacement - bf.Seek(0x18, 0) - // Bitset Structure: bit 8 unk, bit 7 required objective,bit 6 unk,bit 5 night,bit 4 day,bit 3 cold,bit 2 warm,bit 1 breeding + + // Time Flag Replacement + // Bitset Structure: b8 UNK, b7 Required Objective, b6 UNK, b5 Night, b4 Day, b3 Cold, b2 Warm, b1 Spring // if the byte is set to 0 the game choses the quest file corresponding to whatever season the game is on + bf.Seek(25, 0) + flagByte := bf.ReadUint8() + bf.Seek(25, 0) if s.server.erupeConfig.GameplayOptions.SeasonOverride { - bf.WriteUint16(0) + bf.WriteUint8(flagByte & 0b11100000) } else { - bf.WriteUint16(questOption) + bf.WriteUint8(flagByte) } - // Bitset Structure Quest Varient 1: bit 8 Hiden, bit 7 Fix HC,bit 6 HC to UL,bit 5 GRank,bit 4 Unk,bit 3 UNK,bit 2 UNK,bit 1 UNK - // Bitset Structure Quest Varient 2: bit 8 UNK, bit 7 No Halk Pots,bit 6 No halk/poogie,bit 5 Timer,bit 4 UNK,bit 3 Fixed Difficulty,bit 2 UNK,bit 1 UNK - // Bitset Structure Quest Varient 3: bit 8 Disable Reward Skill, bit 7 GSR to GR,bit 6 unk,bit 5 Musou?,bit 4 Zenith,bit 3 Inception,bit 2 UNK,bit 1 UNK - bf.Seek(0xAF, 0) - quest_variant_3 := bf.ReadUint8() - quest_variant_3 &= 0b11011111 //disables Inception flag at position 3 for any quests that dont have it set. - bf.Seek(0xAF, 0) - bf.WriteUint8(quest_variant_3) + // Bitset Structure Quest Variant 1: b8 UL Fixed, b7 UNK, b6 UNK, b5 UNK, b4 G Rank, b3 HC to UL, b2 Fix HC, b1 Hiden + // Bitset Structure Quest Variant 2: b8 Road, b7 High Conquest, b6 Fixed Difficulty, b5 No Active Feature, b4 Timer, b3 No Cuff, b2 No Halk Pots, b1 Low Conquest + // Bitset Structure Quest Variant 3: b8 No Sigils, b7 UNK, b6 Interception, b5 Zenith, b4 No GP Skills, b3 No Simple Mode?, b2 GSR to GR, b1 No Reward Skills + + bf.Seek(175, 0) + questVariant3 := bf.ReadUint8() + questVariant3 &= 0b11011111 // disable Interception flag + bf.Seek(175, 0) + bf.WriteUint8(questVariant3) ps.Uint8(bf, "", true) // Debug/Notes string for quest - return bf.Data(), nil - } func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { @@ -190,7 +191,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint16(0) - rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, index_value,quest_type, quest_id,quest_option, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id") + rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id") for rows.Next() { data, err := makeEventQuest(s, rows) if err != nil { From 13375fa8e99887e86ea46b6edb860a2a301d10f2 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 24 Oct 2023 23:03:08 +1100 Subject: [PATCH 061/269] fix quest enumeration --- server/channelserver/handlers_quest.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index aab84b960..da3c8166a 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -181,6 +181,7 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { bf.Seek(175, 0) bf.WriteUint8(questVariant3) + bf.Seek(0, 2) ps.Uint8(bf, "", true) // Debug/Notes string for quest return bf.Data(), nil } From 58b401d978885fffcc63ab541447f5067d19730e Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 26 Oct 2023 22:29:42 +1100 Subject: [PATCH 062/269] Update README.md --- README.md | 109 +----------------------------------------------------- 1 file changed, 1 insertion(+), 108 deletions(-) diff --git a/README.md b/README.md index 8f17c33ce..8cf946d0a 100644 --- a/README.md +++ b/README.md @@ -36,115 +36,8 @@ If you want to modify or compile Erupe yourself, please read on. - [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z) - [Mezeporta Square Discord](https://discord.gg/DnwcpXM488) - -## Configuration -This portion of the documentation goes over the `config.json` file. - -### General Configuration - -| Variable | Description | Default | Options | -|------------------------|-----------------------------------------------------------------------------------------------------------------------|-----------|---------------------------------| -| Host | The IP or host address the server is running from | 127.0.0.1 | | -| BinPath | The bin path folder is where you place files needed for various parts of the game such as scenario and quest files | bin | | -| Language | This is the language the server will run in. Only English `en` and Japanese `jp` are available, contributions welcome | en | en/jp | -| DisableSoftCrash | | false | | -| HideLoginNotice | This hides the notices that appear on login from `LoginNotices` | true | | -| LoginNotices | This is where you place notices for users, you can have multiple notices | | | -| PatchServerManifest | | | | -| PatchServerFile | | | | -| ScreenshotAPIURL | This is the URL you want user sreenshots to go to | | | -| DeleteOnSaveCorruption | This option flags a character as deleted if they corrupt it, can be used as punishment for cheaters | false | | -| ClientMode | This tells the server what client version it should target | ZZ | Check compatible versions above | -| DevMode | This enables DevModeOptions to be configured | true | | - -### `DevModeOptions` Configuraiton - -| Variable | Description | Default | Options | -|----------------------|---------------------------------------------------------------------------------------------|--------------|----------------------------------| -| AutoCreateAccount | This allows users that don't exist to auto create their account from initial login | true | | -| CleanDB | This cleans the database down | false | | -| MaxLauncherHR | This sets the launcher value to HR7 to allow you to break World HR requirements | false | | -| LogInboundMessages | This will allow inbound messages to be logged to stdout | false | | -| LogOutboundMessages | This will allow outbound messages to be logged to stdout | false | | -| MaxHexdumpLength | This is the maximum amount of hex bytes that will be dumped to stdout | 0 | | -| DivaEvent | This overrides the Diva event stage in game | 0 | 0/1/2/3/-1 | -| FestaEvent | This overrides the Hunter Festival event stage in game | -1 | 0/1/2/3/-1 | -| TournamentEvent | This overrides the Hunter Tournament event stage in game | 0 | 0/1/2/3/-1 | -| MezFesEvent | Enables whether the MezFes event & World are active | true | | -| MezFesAlt | Switches the multiplayer MezFes minigame | false | | -| DisableTokenCheck | This disables the random token that is generated at login from being checked, very insecure | false | | -| QuestDebugTools | Enable various quest debug logs | false | | -| EarthStatusOverride | Enables Pallone Fest, Tower and Conquest War events | 0 | 2=Conquest, 11=Pallone, 21=Tower | -| EarthIDOverride | A random event ID | 0 | | -| EarthMonsterOverride | Sets the ID of the monster targeted in the Conquest War | 0 | | -| SaveDumps.Enables | Enables save dumps to a folder that is set at `SaveDumps.OutputDir` | true | | -| SaveDumps.OutputDir | The folder that save dumps are saved to | save-backups | | - -### `GameplayOptions` Configuraiton - -| Variable | Description | Default | Options | -|----------------------|-----------------------------------------------------------------------------|---------|---------| -| FeaturedWeapons | Number of Active Feature weapons to generate daily | 0 | | -| MaximumNP | Maximum number of NP held by a player | 100000 | | -| MaximumRP | Maximum number of RP held by a player | 100000 | | -| DisableLoginBoost | Disables the Login Boost system | false | | -| DisableBoostTime | Disables the daily NetCafe Boost Time | false | | -| BoostTimeDuration | The number of minutes NetCafe Boost Time lasts for | 120 | | -| GuildMealDuration | The number of minutes a Guild Meal can be activated for after cooking | 60 | | -| BonusQuestAllowance | Number of Bonus Point Quests to allow daily | 3 | | -| DailyQuestAllowance | Number of Daily Quests to allow daily | 1 | | -| MezfesSoloTickets | Number of solo tickets given weekly | 10 | | -| MezfesGroupTickets | Number of group tickets given weekly | 4 | | -| GUrgentRate | Adjusts the rate of G Urgent quests spawning | 10 | | -| GCPMultiplier | Adjusts the multiplier of GCP rewarded for quest completion | 1.00 | | -| GRPMultiplier | Adjusts the multiplier of G Rank Points rewarded for quest completion | 1.00 | | -| GSRPMultiplier | Adjusts the multiplier of G Skill Rank Points rewarded for quest completion | 1.00 | | -| GZennyMultiplier | Adjusts the multiplier of G Zenny rewarded for quest completion | 1.00 | | -| MaterialMultiplier | Adjusts the multiplier of Monster Materials rewarded for quest completion | 1.00 | | -| ExtraCarves | Grant n extra chances to carve ALL carcasses | 0 | | -| DisableHunterNavi | Disables the Hunter Navi | false | | -| EnableHiganjimaEvent | Enables the Higanjima event in the Rasta Bar | false | | -| EnableNierEvent | Enables the Nier event in the Rasta Bar | false | | -| DisableRoad | Disables the Hunting Road | false | | - -### Discord -There is limited Discord capability in Erupe. The feature allows you to replay messages from your server into a channel. -This may be either be removed or revamped in a future version. - -### Commands -There are several chat commands that can be turned on and off. Most of them are really for admins or debugging purposes. - -| Name | command | Description | Default | Options | -|----------|----------------|--------------------------------------------|----------|---------------------| -| Rights | !rights VALUE | Sets the rights integer for your account | disabled | | -| Teleport | !tele X,Y | Teleports user to specific x,y coordinate | disabled | | -| Reload | !reload | Reloads all users and character objects | enabled | | -| KeyQuest | !kqf FLAGS | Sets the Key Quest Flag for your character | disabled | | -| Course | !course OPTION | Enables/Disables a course for your account | enabled | HL,EX,Premium,Boost | -| PSN | !psn USERNAME | Links the specified PSN to your account | enabled | | - -### Ravi Sub Commands -| Name | command | Description | -|----------|----------------------------------|-------------------------------| -| Raviente | !ravi start | Starts Ravi Event | -| Raviente | !ravi cm / !ravi checkmultiplier | Checks Ravi Damage Multiplier | -| Raviente | !ravi ss | Send Sedation Support | -| Raviente | !ravi sr | Send Resurrection Support | -| Raviente | !ravi rs | Request Sedation Support | - - -## World `Entries` config - -| Config Item | Description | Options | -|-------------|---------------|------------------------------------------------------------| -| Type | Server type | 1=Normal, 2=Cities, 3=Newbie, 4=Tavern, 5=Return, 6=MezFes | -| Season | Server season | 0=Green/Breeding, 1=Orange/Warm, 2=Blue/Cold | - -### `Recommend` -This sets the types of Quests that can be ordered from a World. -* 0 = All quests * 1 = Up to 2 star Quests * 2 = Up to 4 star Quests * 4 = All Quests in HR (Enables G Experience Tab) * 5 = Only G Rank Quests -* 6 = Minigame World \ No newline at end of file +* 6 = Minigame World From 3edc77d3b568a95fe38b81276b51ae97f20e96cb Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 26 Oct 2023 22:30:04 +1100 Subject: [PATCH 063/269] Update README.md --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 8cf946d0a..a494b25da 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,3 @@ If you want to modify or compile Erupe yourself, please read on. - [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z) - [Mezeporta Square Discord](https://discord.gg/DnwcpXM488) -* 1 = Up to 2 star Quests -* 2 = Up to 4 star Quests -* 4 = All Quests in HR (Enables G Experience Tab) -* 5 = Only G Rank Quests -* 6 = Minigame World From 0b75a426822131346f7095deb130b4e2992f9aa5 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 26 Oct 2023 22:31:00 +1100 Subject: [PATCH 064/269] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a494b25da..034e2a4ea 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ - PlayStation Vita - Wii U (Up to Z2) ### Versions (ClientMode) -All versions after HR compression (G10-ZZ) have been tested extensively and have great functionality. -All versions available on Wii U (G3-Z2) have been tested and should have good functionality. -The second oldest found version is Forward.4 (FW.4), this version has basic functionality. -The oldest found version is Season 6.0 (S6.0), however functionality is very limited. +- All versions after HR compression (G10-ZZ) have been tested extensively and have great functionality. +- All versions available on Wii U (G3-Z2) have been tested and should have good functionality. +- The second oldest found version is Forward.4 (FW.4), this version has basic functionality. +- The oldest found version is Season 6.0 (S6.0), however functionality is very limited. If you have an **installed** copy of Monster Hunter Frontier on an old hard drive, **please** get in contact so we can archive it! From 8abefd74411ef602633c100db64d0ec7be781ae4 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 28 Oct 2023 11:53:53 +1100 Subject: [PATCH 065/269] fix null LoadScenarioData response --- server/channelserver/handlers_data.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 81102b42d..74f6de9d0 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -302,7 +302,7 @@ func handleMsgMhfLoadScenarioData(s *Session, p mhfpacket.MHFPacket) { var scenarioData []byte bf := byteframe.NewByteFrame() err := s.server.db.QueryRow("SELECT scenariodata FROM characters WHERE id = $1", s.charID).Scan(&scenarioData) - if err != nil { + if err != nil || len(scenarioData) < 10 { s.logger.Error("Failed to load scenariodata", zap.Error(err)) bf.WriteBytes(make([]byte, 10)) } else { From e75a77e6b8a62473d9bb4f7a197dcd57d6f46970 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 28 Oct 2023 11:55:06 +1100 Subject: [PATCH 066/269] optimise grpToGR --- server/channelserver/handlers_character.go | 2 +- server/channelserver/handlers_data.go | 179 ++++----------------- 2 files changed, 29 insertions(+), 152 deletions(-) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 4c5802293..cf17693d7 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -226,7 +226,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { if _config.ErupeConfig.RealClientMode >= _config.G10 { save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] if save.HRP == uint16(999) { - save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])) + save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))) } } } diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 74f6de9d0..5de1c4f65 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -73,162 +73,39 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } -func grpToGR(n uint32) uint16 { - var gr uint16 - gr = 1 - switch grp := int(n); { - case grp < 208750: // Up to 50 - i := 0 - for { - grp -= 500 - if grp <= 500 { - if grp < 0 { - i-- +func grpToGR(n int) uint16 { + var gr int + a := []int{208750, 593400, 993400, 1400900, 2315900, 3340900, 4505900, 5850900, 7415900, 9230900, 11345900, 100000000} + b := []int{7850, 8000, 8150, 9150, 10250, 11650, 13450, 15650, 18150, 21150, 23950} + c := []int{51, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900} + + for i := 0; i < len(a); i++ { + if n < a[i] { + if i == 0 { + for { + n -= 500 + if n <= 500 { + if n < 0 { + i-- + } + break + } else { + i++ + for j := 0; j < i; j++ { + n -= 150 + } + } } - break + gr = i + 2 } else { - i++ - for j := 0; j < i; j++ { - grp -= 150 - } + n -= a[i-1] + gr = c[i-1] + gr += n / b[i-1] } + break } - gr = uint16(i + 2) - break - case grp < 593400: // 51-99 - grp -= 208750 - i := 51 - for { - if grp < 7850 { - break - } - i++ - grp -= 7850 - } - gr = uint16(i) - break - case grp < 993400: // 100-149 - grp -= 593400 - i := 100 - for { - if grp < 8000 { - break - } - i++ - grp -= 8000 - } - gr = uint16(i) - break - case grp < 1400900: // 150-199 - grp -= 993400 - i := 150 - for { - if grp < 8150 { - break - } - i++ - grp -= 8150 - } - gr = uint16(i) - break - case grp < 2315900: // 200-299 - grp -= 1400900 - i := 200 - for { - if grp < 9150 { - break - } - i++ - grp -= 9150 - } - gr = uint16(i) - break - case grp < 3340900: // 300-399 - grp -= 2315900 - i := 300 - for { - if grp < 10250 { - break - } - i++ - grp -= 10250 - } - gr = uint16(i) - break - case grp < 4505900: // 400-499 - grp -= 3340900 - i := 400 - for { - if grp < 11650 { - break - } - i++ - grp -= 11650 - } - gr = uint16(i) - break - case grp < 5850900: // 500-599 - grp -= 4505900 - i := 500 - for { - if grp < 13450 { - break - } - i++ - grp -= 13450 - } - gr = uint16(i) - break - case grp < 7415900: // 600-699 - grp -= 5850900 - i := 600 - for { - if grp < 15650 { - break - } - i++ - grp -= 15650 - } - gr = uint16(i) - break - case grp < 9230900: // 700-799 - grp -= 7415900 - i := 700 - for { - if grp < 18150 { - break - } - i++ - grp -= 18150 - } - gr = uint16(i) - break - case grp < 11345900: // 800-899 - grp -= 9230900 - i := 800 - for { - if grp < 21150 { - break - } - i++ - grp -= 21150 - } - gr = uint16(i) - break - default: // 900+ - grp -= 11345900 - i := 900 - for { - if grp < 23950 { - break - } - i++ - grp -= 23950 - } - gr = uint16(i) - break } - return gr + return uint16(gr) } func dumpSaveData(s *Session, data []byte, suffix string) { From 60dd5df8bcec580961f8e4f222ed09bcfc3cb8b0 Mon Sep 17 00:00:00 2001 From: rockisch Date: Sun, 29 Oct 2023 21:21:11 -0300 Subject: [PATCH 067/269] Update signv2server to return more data --- server/signv2server/dbutils.go | 104 ++++++++++++++-------------- server/signv2server/endpoints.go | 112 ++++++++++++++++++++++--------- 2 files changed, 135 insertions(+), 81 deletions(-) diff --git a/server/signv2server/dbutils.go b/server/signv2server/dbutils.go index 3c8494c95..fb9711da9 100644 --- a/server/signv2server/dbutils.go +++ b/server/signv2server/dbutils.go @@ -10,26 +10,29 @@ import ( "golang.org/x/crypto/bcrypt" ) -func (s *Server) createNewUser(ctx context.Context, username string, password string) (int, error) { +func (s *Server) createNewUser(ctx context.Context, username string, password string) (uint32, uint32, error) { // Create salted hash of user password passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { - return 0, err + return 0, 0, err } - var id int + var ( + id uint32 + rights uint32 + ) err = s.db.QueryRowContext( ctx, ` INSERT INTO users (username, password, return_expires) VALUES ($1, $2, $3) - RETURNING id + RETURNING id, rights `, username, string(passwordHash), time.Now().Add(time.Hour*24*30), - ).Scan(&id) - return id, err + ).Scan(&id, &rights) + return id, rights, err } -func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error) { +func (s *Server) createLoginToken(ctx context.Context, uid uint32) (string, error) { loginToken := token.Generate(16) _, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken) if err != nil { @@ -38,8 +41,8 @@ func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error) return loginToken, nil } -func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) { - var userID int +func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, error) { + var userID uint32 err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID) if err == sql.ErrNoRows { return 0, fmt.Errorf("invalid login token") @@ -49,65 +52,47 @@ func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) return userID, nil } -func (s *Server) createCharacter(ctx context.Context, userID int) (int, error) { - var charID int - err := s.db.QueryRowContext(ctx, - "SELECT id FROM characters WHERE is_new_character = true AND user_id = $1", +func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character, error) { + var character Character + err := s.db.GetContext(ctx, &character, + "SELECT id, name, is_female, weapon_type, hrp, gr, last_login FROM characters WHERE is_new_character = true AND user_id = $1 LIMIT 1", userID, - ).Scan(&charID) + ) if err == sql.ErrNoRows { - err = s.db.QueryRowContext(ctx, ` + var count int + s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM characters WHERE user_id = $1", userID).Scan(&count) + if count >= 16 { + return character, fmt.Errorf("cannot have more than 16 characters") + } + err = s.db.GetContext(ctx, &character, ` INSERT INTO characters ( user_id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login ) VALUES ($1, false, true, '', '', 0, 0, 0, $2) - RETURNING id`, + RETURNING id, name, is_female, weapon_type, hrp, gr, last_login`, userID, uint32(time.Now().Unix()), - ).Scan(&charID) + ) } - return charID, err + return character, err } -func (s *Server) deleteCharacter(ctx context.Context, userID int, charID int) error { - tx, err := s.db.BeginTx(ctx, nil) +func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint32) error { + var isNew bool + err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew) if err != nil { return err } - defer tx.Rollback() - - _, err = tx.ExecContext( - ctx, ` - DELETE FROM login_boost_state - WHERE char_id = $1`, - charID, - ) - if err != nil { - return err + if isNew { + _, err = s.db.Exec("DELETE FROM characters WHERE id = $1", charID) + } else { + _, err = s.db.Exec("UPDATE characters SET deleted = true WHERE id = $1", charID) } - _, err = tx.ExecContext( - ctx, ` - DELETE FROM guild_characters - WHERE character_id = $1`, - charID, - ) - if err != nil { - return err - } - _, err = tx.ExecContext( - ctx, ` - DELETE FROM characters - WHERE user_id = $1 AND id = $2`, - userID, charID, - ) - if err != nil { - return err - } - return tx.Commit() + return err } -func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character, error) { - characters := make([]Character, 0) +func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Character, error) { + var characters []Character err := s.db.SelectContext( ctx, &characters, ` SELECT id, name, is_female, weapon_type, hrp, gr, last_login @@ -120,3 +105,20 @@ func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character } return characters, nil } + +func (s *Server) getReturnExpiry(uid uint32) time.Time { + var returnExpiry, lastLogin time.Time + s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid) + if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) { + returnExpiry = time.Now().Add(time.Hour * 24 * 30) + s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid) + } else { + err := s.db.Get(&returnExpiry, "SELECT return_expires FROM users WHERE id=$1", uid) + if err != nil { + returnExpiry = time.Now() + s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid) + } + } + s.db.Exec("UPDATE users SET last_login=$1 WHERE id=$2", time.Now(), uid) + return returnExpiry +} diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index eeb7442de..b6b888b54 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -4,7 +4,9 @@ import ( "database/sql" "encoding/json" "errors" + "erupe-ce/server/channelserver" "net/http" + "strings" "time" "github.com/lib/pq" @@ -18,21 +20,78 @@ type LauncherMessage struct { Link string `json:"link"` } +type LauncherResponse struct { + Important []LauncherMessage `json:"important"` + Normal []LauncherMessage `json:"normal"` +} + +type User struct { + Token string `json:"token"` + Rights uint32 `json:"rights"` +} + type Character struct { - ID int `json:"id"` + Id uint32 `json:"id"` Name string `json:"name"` IsFemale bool `json:"isFemale" db:"is_female"` - Weapon int `json:"weapon" db:"weapon_type"` - HR int `json:"hr" db:"hrp"` - GR int `json:"gr"` + Weapon uint32 `json:"weapon" db:"weapon_type"` + Hr uint32 `json:"hr" db:"hrp"` + Gr uint32 `json:"gr"` LastLogin int64 `json:"lastLogin" db:"last_login"` } -func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) { - var respData struct { - Important []LauncherMessage `json:"important"` - Normal []LauncherMessage `json:"normal"` +type MezFes struct { + Id uint32 `json:"id"` + Start uint32 `json:"start"` + End uint32 `json:"end"` + SoloTickets uint32 `json:"soloTickets"` + GroupTickets uint32 `json:"groupTickets"` + Stalls []uint32 `json:"stalls"` +} + +type AuthData struct { + CurrentTs uint32 `json:"currentTs"` + ExpiryTs uint32 `json:"expiryTs"` + EntranceCount uint32 `json:"entranceCount"` + Notifications []string `json:"notifications"` + User User `json:"user"` + Characters []Character `json:"characters"` + MezFes *MezFes `json:"mezFes"` +} + +func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData { + resp := AuthData{ + CurrentTs: uint32(channelserver.TimeAdjusted().Unix()), + ExpiryTs: uint32(s.getReturnExpiry(userID).Unix()), + EntranceCount: 1, + User: User{ + Rights: userRights, + Token: userToken, + }, + Characters: characters, } + if s.erupeConfig.DevModeOptions.MezFesEvent { + stalls := []uint32{10, 3, 6, 9, 4, 8, 5, 7} + if s.erupeConfig.DevModeOptions.MezFesAlt { + stalls[4] = 2 + } + resp.MezFes = &MezFes{ + Id: uint32(channelserver.TimeWeekStart().Unix()), + Start: uint32(channelserver.TimeWeekStart().Unix()), + End: uint32(channelserver.TimeWeekNext().Unix()), + SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets, + GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets, + Stalls: stalls, + } + } + if !s.erupeConfig.HideLoginNotice { + resp.Notifications = append(resp.Notifications, strings.Join(s.erupeConfig.LoginNotices[:], "")) + } + return resp +} + +func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) { + var respData LauncherResponse respData.Important = []LauncherMessage{ { Message: "Server Update 9 Released!", @@ -75,10 +134,11 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { return } var ( - userID int - password string + userID uint32 + userRights uint32 + password string ) - err := s.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password) + err := s.db.QueryRow("SELECT id, password, rights FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password, &userRights) if err == sql.ErrNoRows { w.WriteHeader(400) w.Write([]byte("Username does not exist")) @@ -94,22 +154,19 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { return } - var respData struct { - Token string `json:"token"` - Characters []Character `json:"characters"` - } - respData.Token, err = s.createLoginToken(ctx, userID) + userToken, err := s.createLoginToken(ctx, userID) if err != nil { s.logger.Warn("Error registering login token", zap.Error(err)) w.WriteHeader(500) return } - respData.Characters, err = s.getCharactersForUser(ctx, userID) + characters, err := s.getCharactersForUser(ctx, userID) if err != nil { s.logger.Warn("Error getting characters from DB", zap.Error(err)) w.WriteHeader(500) return } + respData := s.newAuthData(userID, userRights, userToken, characters) w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) @@ -128,7 +185,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { return } s.logger.Info("Creating account", zap.String("username", reqData.Username)) - userID, err := s.createNewUser(ctx, reqData.Username, reqData.Password) + userID, userRights, err := s.createNewUser(ctx, reqData.Username, reqData.Password) if err != nil { var pqErr *pq.Error if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" { @@ -141,15 +198,13 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { return } - var respData struct { - Token string `json:"token"` - } - respData.Token, err = s.createLoginToken(ctx, userID) + userToken, err := s.createLoginToken(ctx, userID) if err != nil { s.logger.Error("Error registering login token", zap.Error(err)) w.WriteHeader(500) return } + respData := s.newAuthData(userID, userRights, userToken, []Character{}) json.NewEncoder(w).Encode(respData) } @@ -165,28 +220,25 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) { return } - var respData struct { - CharID int `json:"id"` - } userID, err := s.userIDFromToken(ctx, reqData.Token) if err != nil { w.WriteHeader(401) return } - respData.CharID, err = s.createCharacter(ctx, userID) + character, err := s.createCharacter(ctx, userID) if err != nil { s.logger.Error("Failed to create character", zap.Error(err), zap.String("token", reqData.Token)) w.WriteHeader(500) return } - json.NewEncoder(w).Encode(respData) + json.NewEncoder(w).Encode(character) } func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var reqData struct { Token string `json:"token"` - CharID int `json:"id"` + CharId uint32 `json:"charId"` } if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) @@ -199,8 +251,8 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) return } - if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil { - s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Int("charID", reqData.CharID)) + if err := s.deleteCharacter(ctx, userID, reqData.CharId); err != nil { + s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharId)) w.WriteHeader(500) return } From 9d9cc0bf5dff927dfef4b9eaa47df8bc32a6f711 Mon Sep 17 00:00:00 2001 From: rockisch Date: Sun, 29 Oct 2023 21:24:51 -0300 Subject: [PATCH 068/269] Use Go's naming recommendations --- server/signv2server/endpoints.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index b6b888b54..c087a70df 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -31,17 +31,17 @@ type User struct { } type Character struct { - Id uint32 `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` IsFemale bool `json:"isFemale" db:"is_female"` Weapon uint32 `json:"weapon" db:"weapon_type"` - Hr uint32 `json:"hr" db:"hrp"` - Gr uint32 `json:"gr"` + HR uint32 `json:"hr" db:"hrp"` + GR uint32 `json:"gr"` LastLogin int64 `json:"lastLogin" db:"last_login"` } type MezFes struct { - Id uint32 `json:"id"` + ID uint32 `json:"id"` Start uint32 `json:"start"` End uint32 `json:"end"` SoloTickets uint32 `json:"soloTickets"` @@ -50,8 +50,8 @@ type MezFes struct { } type AuthData struct { - CurrentTs uint32 `json:"currentTs"` - ExpiryTs uint32 `json:"expiryTs"` + CurrentTS uint32 `json:"currentTs"` + ExpiryTS uint32 `json:"expiryTs"` EntranceCount uint32 `json:"entranceCount"` Notifications []string `json:"notifications"` User User `json:"user"` @@ -61,8 +61,8 @@ type AuthData struct { func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData { resp := AuthData{ - CurrentTs: uint32(channelserver.TimeAdjusted().Unix()), - ExpiryTs: uint32(s.getReturnExpiry(userID).Unix()), + CurrentTS: uint32(channelserver.TimeAdjusted().Unix()), + ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()), EntranceCount: 1, User: User{ Rights: userRights, @@ -76,7 +76,7 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, stalls[4] = 2 } resp.MezFes = &MezFes{ - Id: uint32(channelserver.TimeWeekStart().Unix()), + ID: uint32(channelserver.TimeWeekStart().Unix()), Start: uint32(channelserver.TimeWeekStart().Unix()), End: uint32(channelserver.TimeWeekNext().Unix()), SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets, @@ -238,7 +238,7 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var reqData struct { Token string `json:"token"` - CharId uint32 `json:"charId"` + CharID uint32 `json:"charId"` } if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) @@ -251,8 +251,8 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) return } - if err := s.deleteCharacter(ctx, userID, reqData.CharId); err != nil { - s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharId)) + if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil { + s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharID)) w.WriteHeader(500) return } From 542b8f8bf667f088840658a32fbc880b3d96624f Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 30 Oct 2023 23:23:11 +1100 Subject: [PATCH 069/269] rewrite EnumerateStage --- server/channelserver/handlers_stage.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index e411365d3..258c8b019 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -372,7 +372,6 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { var joinable int for sid, stage := range s.server.stages { stage.RLock() - defer stage.RUnlock() if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 { continue @@ -386,9 +385,12 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint16(uint16(len(stage.reservedClientSlots))) // Reserved players. resp.WriteUint16(0) // Unk - resp.WriteUint8(0) // Unk - resp.WriteBool(len(stage.clients) > 0) // Has departed. - resp.WriteUint16(stage.maxPlayers) // Max players. + if len(stage.clients) > 0 { + bf.WriteUint16(1) + } else { + bf.WriteUint16(0) + } + resp.WriteUint16(stage.maxPlayers) // Max players. if len(stage.password) > 0 { // This byte has also been seen as 1 // The quest is also recognised as locked when this is 2 @@ -397,6 +399,7 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint8(0) } ps.Uint8(resp, sid, false) + stage.RUnlock() } bf.WriteUint16(uint16(joinable)) bf.WriteBytes(resp.Data()) From b526608f4b41eead03df8d22fd7c9738e744908e Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 30 Oct 2023 23:37:07 +1100 Subject: [PATCH 070/269] fix titles not updating correctly --- server/channelserver/handlers_house.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 560e73ec7..f764a8fa1 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -356,11 +356,11 @@ func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfAcquireTitle) var exists int - err := s.server.db.QueryRow("SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2", pkt.TitleID, s.charID).Scan(&exists) + err := s.server.db.QueryRow(`SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2`, pkt.TitleID, s.charID).Scan(&exists) if err != nil || exists == 0 { - s.server.db.Exec("INSERT INTO titles VALUES ($1, $2, now(), now())", pkt.TitleID, s.charID) + s.server.db.Exec(`INSERT INTO titles VALUES ($1, $2, now(), now())`, pkt.TitleID, s.charID) } else { - s.server.db.Exec("UPDATE titles SET updated_at=now()") + s.server.db.Exec(`UPDATE titles SET updated_at=now() WHERE id=$1 AND char_id=$2`, pkt.TitleID, s.charID) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } From ffcf511c20ee58002acc7925a74cc4930010671e Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 30 Oct 2023 23:54:50 +1100 Subject: [PATCH 071/269] simplify UseKeepLoginBoost --- server/channelserver/handlers_event.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/server/channelserver/handlers_event.go b/server/channelserver/handlers_event.go index d01f92a3d..d39b629d9 100644 --- a/server/channelserver/handlers_event.go +++ b/server/channelserver/handlers_event.go @@ -199,15 +199,11 @@ func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint8(0) switch pkt.BoostWeekUsed { - case 1: - fallthrough - case 3: + case 1, 3: expiration = TimeAdjusted().Add(120 * time.Minute) case 4: expiration = TimeAdjusted().Add(180 * time.Minute) - case 2: - fallthrough - case 5: + case 2, 5: expiration = TimeAdjusted().Add(240 * time.Minute) } bf.WriteUint32(uint32(expiration.Unix())) From f9280f483fa1042f5297a63b00a0731443e0af89 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 31 Oct 2023 00:21:05 +1100 Subject: [PATCH 072/269] simplify & handle Mail better --- network/mhfpacket/msg_mhf_list_mail.go | 10 +-- network/mhfpacket/msg_mhf_oprt_mail.go | 14 ++-- network/mhfpacket/msg_mhf_send_mail.go | 5 +- server/channelserver/handlers_mail.go | 95 +++++--------------------- 4 files changed, 32 insertions(+), 92 deletions(-) diff --git a/network/mhfpacket/msg_mhf_list_mail.go b/network/mhfpacket/msg_mhf_list_mail.go index 2dfb351e9..645baf548 100644 --- a/network/mhfpacket/msg_mhf_list_mail.go +++ b/network/mhfpacket/msg_mhf_list_mail.go @@ -1,17 +1,16 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfListMail represents the MSG_MHF_LIST_MAIL type MsgMhfListMail struct { AckHandle uint32 - Unk0 uint32 } // Opcode returns the ID associated with this packet type. @@ -22,7 +21,8 @@ func (m *MsgMhfListMail) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfListMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint32() + bf.ReadUint16() // Zeroed + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_oprt_mail.go b/network/mhfpacket/msg_mhf_oprt_mail.go index 2c9e06828..95ec561ee 100644 --- a/network/mhfpacket/msg_mhf_oprt_mail.go +++ b/network/mhfpacket/msg_mhf_oprt_mail.go @@ -11,10 +11,11 @@ import ( type OperateMailOperation uint8 const ( - OPERATE_MAIL_DELETE = 0x01 - OPERATE_MAIL_LOCK = 0x02 - OPERATE_MAIL_UNLOCK = 0x03 - OPERATE_MAIL_ACQUIRE_ITEM = 0x05 + OperateMailDelete = iota + 1 + OperateMailLock + OperateMailUnlock + OpreateMailNull + OperateMailAcquireItem ) // MsgMhfOprtMail represents the MSG_MHF_OPRT_MAIL @@ -23,7 +24,6 @@ type MsgMhfOprtMail struct { AccIndex uint8 Index uint8 Operation OperateMailOperation - Unk0 uint8 Data []byte Amount uint16 ItemID uint16 @@ -40,8 +40,8 @@ func (m *MsgMhfOprtMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon m.AccIndex = bf.ReadUint8() m.Index = bf.ReadUint8() m.Operation = OperateMailOperation(bf.ReadUint8()) - m.Unk0 = bf.ReadUint8() - if m.Operation == OPERATE_MAIL_ACQUIRE_ITEM { + bf.ReadUint8() // Zeroed + if m.Operation == OperateMailAcquireItem { m.Amount = bf.ReadUint16() m.ItemID = bf.ReadUint16() } diff --git a/network/mhfpacket/msg_mhf_send_mail.go b/network/mhfpacket/msg_mhf_send_mail.go index e0f34ba54..2a21ef93b 100644 --- a/network/mhfpacket/msg_mhf_send_mail.go +++ b/network/mhfpacket/msg_mhf_send_mail.go @@ -15,7 +15,7 @@ type MsgMhfSendMail struct { RecipientID uint32 SubjectLength uint16 BodyLength uint16 - Quantity uint32 + Quantity uint16 ItemID uint16 Subject string Body string @@ -32,7 +32,8 @@ func (m *MsgMhfSendMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon m.RecipientID = bf.ReadUint32() m.SubjectLength = bf.ReadUint16() m.BodyLength = bf.ReadUint16() - m.Quantity = bf.ReadUint32() + bf.ReadUint16() // Zeroed + m.Quantity = bf.ReadUint16() m.ItemID = bf.ReadUint16() m.Subject = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) m.Body = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) diff --git a/server/channelserver/handlers_mail.go b/server/channelserver/handlers_mail.go index 7e9784ee3..a596d927b 100644 --- a/server/channelserver/handlers_mail.go +++ b/server/channelserver/handlers_mail.go @@ -79,57 +79,6 @@ func (m *Mail) MarkRead(s *Session) error { return nil } -func (m *Mail) MarkDeleted(s *Session) error { - _, err := s.server.db.Exec(` - UPDATE mail SET deleted = true WHERE id = $1 - `, m.ID) - - if err != nil { - s.logger.Error( - "failed to mark mail as deleted", - zap.Error(err), - zap.Int("mailID", m.ID), - ) - return err - } - - return nil -} - -func (m *Mail) MarkAcquired(s *Session) error { - _, err := s.server.db.Exec(` - UPDATE mail SET attached_item_received = true WHERE id = $1 - `, m.ID) - - if err != nil { - s.logger.Error( - "failed to mark mail item as claimed", - zap.Error(err), - zap.Int("mailID", m.ID), - ) - return err - } - - return nil -} - -func (m *Mail) MarkLocked(s *Session, locked bool) error { - _, err := s.server.db.Exec(` - UPDATE mail SET locked = $1 WHERE id = $2 - `, locked, m.ID) - - if err != nil { - s.logger.Error( - "failed to mark mail as locked", - zap.Error(err), - zap.Int("mailID", m.ID), - ) - return err - } - - return nil -} - func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) { rows, err := s.server.db.Queryx(` SELECT @@ -256,26 +205,21 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfReadMail) mailId := s.mailList[pkt.AccIndex] - if mailId == 0 { - doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - panic("attempting to read mail that doesn't exist in session map") + doAckBufSucceed(s, pkt.AckHandle, []byte{0}) + return } mail, err := GetMailByID(s, mailId) - if err != nil { - doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - panic(err) + doAckBufSucceed(s, pkt.AckHandle, []byte{0}) + return } - _ = mail.MarkRead(s) - + s.server.db.Exec(`UPDATE mail SET read = true WHERE id = $1`, mail.ID) bf := byteframe.NewByteFrame() - body := stringsupport.UTF8ToSJIS(mail.Body) bf.WriteNullTerminatedBytes(body) - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } @@ -283,10 +227,9 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfListMail) mail, err := GetMailListForCharacter(s, s.charID) - if err != nil { - doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - panic(err) + doAckBufSucceed(s, pkt.AckHandle, []byte{0}) + return } if s.mailList == nil { @@ -354,24 +297,20 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) { mail, err := GetMailByID(s, s.mailList[pkt.AccIndex]) if err != nil { - panic(err) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } switch pkt.Operation { - case mhfpacket.OPERATE_MAIL_DELETE: - err = mail.MarkDeleted(s) - case mhfpacket.OPERATE_MAIL_LOCK: - err = mail.MarkLocked(s, true) - case mhfpacket.OPERATE_MAIL_UNLOCK: - err = mail.MarkLocked(s, false) - case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM: - err = mail.MarkAcquired(s) + case mhfpacket.OperateMailDelete: + s.server.db.Exec(`UPDATE mail SET deleted = true WHERE id = $1`, mail.ID) + case mhfpacket.OperateMailLock: + s.server.db.Exec(`UPDATE mail SET locked = TRUE WHERE id = $1`, mail.ID) + case mhfpacket.OperateMailUnlock: + s.server.db.Exec(`UPDATE mail SET locked = FALSE WHERE id = $1`, mail.ID) + case mhfpacket.OperateMailAcquireItem: + s.server.db.Exec(`UPDATE mail SET attached_item_received = TRUE WHERE id = $1`, mail.ID) } - - if err != nil { - panic(err) - } - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } From cf8a5da0b2045ed4ade801108339575a9b78b71a Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 1 Nov 2023 08:12:00 +1100 Subject: [PATCH 073/269] Update handlers_stage.go --- server/channelserver/handlers_stage.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 258c8b019..983d6e79e 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -374,10 +374,12 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { stage.RLock() if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 { + stage.RUnlock() continue } if !strings.Contains(stage.id, pkt.StagePrefix) { + stage.RUnlock() continue } From be6f55b5a82a24d6e6a2d158bf4ac45aa1b8839d Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 5 Nov 2023 00:31:16 +1100 Subject: [PATCH 074/269] initial distributions rework --- .../mhfpacket/msg_mhf_enumerate_dist_item.go | 4 +- patch-schema/10-rework-distributions.sql | 15 ++ server/channelserver/handlers_distitem.go | 130 ++++++++---------- 3 files changed, 78 insertions(+), 71 deletions(-) create mode 100644 patch-schema/10-rework-distributions.sql diff --git a/network/mhfpacket/msg_mhf_enumerate_dist_item.go b/network/mhfpacket/msg_mhf_enumerate_dist_item.go index 6d0082ec8..4e89c6dfc 100644 --- a/network/mhfpacket/msg_mhf_enumerate_dist_item.go +++ b/network/mhfpacket/msg_mhf_enumerate_dist_item.go @@ -10,7 +10,7 @@ import ( // MsgMhfEnumerateDistItem represents the MSG_MHF_ENUMERATE_DIST_ITEM type MsgMhfEnumerateDistItem struct { AckHandle uint32 - Unk0 uint8 + DistType uint8 Unk1 uint8 Unk2 uint16 Unk3 []byte @@ -24,7 +24,7 @@ func (m *MsgMhfEnumerateDistItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateDistItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() + m.DistType = bf.ReadUint8() m.Unk1 = bf.ReadUint8() m.Unk2 = bf.ReadUint16() m.Unk3 = bf.ReadBytes(uint(bf.ReadUint8())) diff --git a/patch-schema/10-rework-distributions.sql b/patch-schema/10-rework-distributions.sql new file mode 100644 index 000000000..c56a9ff67 --- /dev/null +++ b/patch-schema/10-rework-distributions.sql @@ -0,0 +1,15 @@ +BEGIN; + +-- This will delete all of your old distribution data! +--ALTER TABLE IF EXISTS public.distribution DROP COLUMN IF EXISTS data; + +CREATE TABLE public.distribution_items +( + id serial PRIMARY KEY, + distribution_id integer, + item_type integer, + item_id integer, + quantity integer +); + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index 2bde7c60a..97d9e882e 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -4,24 +4,25 @@ import ( "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" "erupe-ce/network/mhfpacket" + "time" "go.uber.org/zap" ) type ItemDist struct { - ID uint32 `db:"id"` - Deadline uint32 `db:"deadline"` - TimesAcceptable uint16 `db:"times_acceptable"` - TimesAccepted uint16 `db:"times_accepted"` - MinHR uint16 `db:"min_hr"` - MaxHR uint16 `db:"max_hr"` - MinSR uint16 `db:"min_sr"` - MaxSR uint16 `db:"max_sr"` - MinGR uint16 `db:"min_gr"` - MaxGR uint16 `db:"max_gr"` - EventName string `db:"event_name"` - Description string `db:"description"` - Data []byte `db:"data"` + ID uint32 `db:"id"` + Deadline time.Time `db:"deadline"` + TimesAcceptable uint16 `db:"times_acceptable"` + TimesAccepted uint16 `db:"times_accepted"` + MinHR uint16 `db:"min_hr"` + MaxHR uint16 `db:"max_hr"` + MinSR uint16 `db:"min_sr"` + MaxSR uint16 `db:"max_sr"` + MinGR uint16 `db:"min_gr"` + MaxGR uint16 `db:"max_gr"` + EventName string `db:"event_name"` + Description string `db:"description"` + Data []byte `db:"data"` } func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { @@ -37,13 +38,10 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { WHERE d.id = da.distribution_id AND da.character_id = $1 ) AS times_accepted, - CASE - WHEN (EXTRACT(epoch FROM deadline)::int) IS NULL THEN 0 - ELSE (EXTRACT(epoch FROM deadline)::int) - END deadline + COALESCE(deadline, TO_TIMESTAMP(0)) AS deadline FROM distribution d WHERE character_id = $1 AND type = $2 OR character_id IS NULL AND type = $2 ORDER BY id DESC; - `, s.charID, pkt.Unk0) + `, s.charID, pkt.DistType) if err != nil { s.logger.Error("Error getting distribution data from db", zap.Error(err)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) @@ -56,7 +54,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { s.logger.Error("Error parsing item distribution data", zap.Error(err)) } bf.WriteUint32(distData.ID) - bf.WriteUint32(distData.Deadline) + bf.WriteUint32(uint32(distData.Deadline.Unix())) bf.WriteUint32(0) // Unk bf.WriteUint16(distData.TimesAcceptable) bf.WriteUint16(distData.TimesAccepted) @@ -93,67 +91,61 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { resp := byteframe.NewByteFrame() resp.WriteUint16(uint16(distCount)) resp.WriteBytes(bf.Data()) - resp.WriteUint8(0) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } } +type DistributionItem struct { + ItemType uint8 `db:"item_type"` + ID uint32 `db:"id"` + ItemID uint32 `db:"item_id"` + Quantity uint32 `db:"quantity"` +} + func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfApplyDistItem) - if pkt.DistributionID == 0 { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6)) - } else { - row := s.server.db.QueryRowx("SELECT data FROM distribution WHERE id = $1", pkt.DistributionID) - dist := &ItemDist{} - err := row.StructScan(dist) - if err != nil { - s.logger.Error("Error parsing item distribution data", zap.Error(err)) - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6)) - return + bf := byteframe.NewByteFrame() + bf.WriteUint32(pkt.DistributionID) + var distItems []DistributionItem + rows, err := s.server.db.Queryx(`SELECT id, item_id, item_type, quantity FROM distribution_items WHERE distribution_id=$1`, pkt.DistributionID) + if err == nil { + var distItem DistributionItem + for rows.Next() { + err = rows.StructScan(&distItem) + if err != nil { + continue + } + distItems = append(distItems, distItem) } - - 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.WriteUint16(uint16(len(distItems))) + for _, item := range distItems { + bf.WriteUint8(item.ItemType) + bf.WriteUint32(item.ItemID) + bf.WriteUint32(item.Quantity) + bf.WriteUint32(item.ID) + switch item.ItemType { + case 17: + _ = addPointNetcafe(s, int(item.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)", item.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)", item.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)", item.Quantity, s.charID) + case 23: + saveData, err := GetCharacterSaveData(s, s.charID) + if err == nil { + saveData.RP += uint16(item.Quantity) + saveData.Save(s) } } + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - bf := byteframe.NewByteFrame() - bf.WriteUint32(pkt.DistributionID) - bf.WriteBytes(dist.Data) - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - - _, err = s.server.db.Exec(` - INSERT INTO public.distributions_accepted - VALUES ($1, $2) - `, pkt.DistributionID, s.charID) - if err != nil { - s.logger.Error("Error updating accepted dist count", zap.Error(err)) - } + if pkt.DistributionID > 0 { + _, err = s.server.db.Exec(`INSERT INTO public.distributions_accepted VALUES ($1, $2)`, pkt.DistributionID, s.charID) } } From 4edeaedea3f37c1ee4f625924251c0ab258bfae9 Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Sun, 5 Nov 2023 15:23:34 -0500 Subject: [PATCH 075/269] fix: Restore seasons functionality into quests --- server/channelserver/handlers_quest.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index da3c8166a..5c5fb9613 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -47,6 +47,10 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { ) } + if s.server.erupeConfig.GameplayOptions.SeasonOverride { + pkt.Filename = seasonConversion(s, pkt.Filename) + } + data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) if err != nil { s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) @@ -58,6 +62,28 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { } } +func questSuffix(s *Session) string { + // Determine the letter to append for day / night + var timeSet string + if TimeGameAbsolute() > 2880 { + timeSet = "d" + } else { + timeSet = "n" + } + return fmt.Sprintf("%s%d", timeSet, s.server.Season()) +} + +func seasonConversion(s *Session, questFile string) string { + filename := fmt.Sprintf("%s%s", questFile[:5], questSuffix(s)) + + // Return original file if file doesn't exist + if _, err := os.Stat(filename); err == nil { + return filename + } else { + return questFile + } +} + func handleMsgMhfLoadFavoriteQuest(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadFavoriteQuest) var data []byte From 68de64a05f57fccafd68aaaa381433e7b8fbba92 Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Sun, 5 Nov 2023 19:08:02 -0500 Subject: [PATCH 076/269] fix: Fixed issue with seasons not properly displaying on client --- config.json | 10 +++++----- server/channelserver/handlers_quest.go | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/config.json b/config.json index 31629b894..467512410 100644 --- a/config.json +++ b/config.json @@ -17,8 +17,8 @@ "AutoCreateAccount": true, "CleanDB": false, "MaxLauncherHR": false, - "LogInboundMessages": true, - "LogOutboundMessages": true, + "LogInboundMessages": false, + "LogOutboundMessages": false, "MaxHexdumpLength": 256, "DivaEvent": 0, "FestaEvent": -1, @@ -67,7 +67,7 @@ "EnableHiganjimaEvent": false, "EnableNierEvent": false, "DisableRoad": false, - "SeasonOverride": false + "SeasonOverride": true }, "Discord": { "Enabled": false, @@ -120,9 +120,9 @@ ], "Database": { "Host": "localhost", - "Port": 5432, + "Port": 5433, "User": "postgres", - "Password": "", + "Password": "admin", "Database": "erupe" }, "Sign": { diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 5c5fb9613..40c52f6c4 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -51,8 +51,16 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { pkt.Filename = seasonConversion(s, pkt.Filename) } + // custom quests expect there to be only a d0 for quests, rewrite quests to try for d0 if the quest file doesn't exist + if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))); err != nil { + pkt.Filename = fmt.Sprintf("%s%s", pkt.Filename[:5], "d0") + } + + s.logger.Info("Sent " + pkt.Filename) + data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) if err != nil { + s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) // This will crash the game. doAckBufSucceed(s, pkt.AckHandle, data) @@ -77,10 +85,15 @@ func seasonConversion(s *Session, questFile string) string { filename := fmt.Sprintf("%s%s", questFile[:5], questSuffix(s)) // Return original file if file doesn't exist - if _, err := os.Stat(filename); err == nil { + if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", filename))); err == nil { return filename } else { - return questFile + // Load d0 if the regular quest file doesn't exist (Fixes custom quests) + if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", questFile))); err == nil { + return questFile + } + + return fmt.Sprintf("%s%s", questFile[:5], "d0") } } From cce64d40108b0cb16a425ccd8285b954f036d80d Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Sun, 5 Nov 2023 19:19:55 -0500 Subject: [PATCH 077/269] fix: Removed random print from code --- server/channelserver/handlers_quest.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 40c52f6c4..c7961ea20 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -56,8 +56,6 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { pkt.Filename = fmt.Sprintf("%s%s", pkt.Filename[:5], "d0") } - s.logger.Info("Sent " + pkt.Filename) - data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) if err != nil { From 5e760da8bc8c570f7b1296e0f1d41038627773d0 Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Sun, 5 Nov 2023 19:21:12 -0500 Subject: [PATCH 078/269] chore: Removed credentials --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 467512410..aeb04eadf 100644 --- a/config.json +++ b/config.json @@ -120,9 +120,9 @@ ], "Database": { "Host": "localhost", - "Port": 5433, + "Port": 5432, "User": "postgres", - "Password": "admin", + "Password": "", "Database": "erupe" }, "Sign": { From 6ff20858edd01be10042c6a5ab0b5bf9ccf7b52c Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 7 Nov 2023 16:50:39 +1100 Subject: [PATCH 079/269] rewrite EnumerateStage & parse ReserveStage --- network/mhfpacket/msg_sys_reserve_stage.go | 5 ++--- server/channelserver/handlers_stage.go | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/network/mhfpacket/msg_sys_reserve_stage.go b/network/mhfpacket/msg_sys_reserve_stage.go index 13e47c41b..d2f688af4 100644 --- a/network/mhfpacket/msg_sys_reserve_stage.go +++ b/network/mhfpacket/msg_sys_reserve_stage.go @@ -3,7 +3,6 @@ package mhfpacket import ( "errors" "erupe-ce/common/byteframe" - "erupe-ce/common/bfutil" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -24,8 +23,8 @@ func (m *MsgSysReserveStage) Opcode() network.PacketID { func (m *MsgSysReserveStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Ready = bf.ReadUint8() - stageIDLength := bf.ReadUint8() - m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) + _ = bf.ReadUint8() // StageID length + m.StageID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 983d6e79e..be7ab1267 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -367,9 +367,9 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { defer s.server.stagesLock.RUnlock() // Build the response - resp := byteframe.NewByteFrame() bf := byteframe.NewByteFrame() - var joinable int + var joinable uint16 + bf.WriteUint16(0) for sid, stage := range s.server.stages { stage.RLock() @@ -377,34 +377,32 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { stage.RUnlock() continue } - if !strings.Contains(stage.id, pkt.StagePrefix) { stage.RUnlock() continue } - joinable++ - resp.WriteUint16(uint16(len(stage.reservedClientSlots))) // Reserved players. - resp.WriteUint16(0) // Unk + bf.WriteUint16(uint16(len(stage.reservedClientSlots))) + bf.WriteUint16(0) // Unk if len(stage.clients) > 0 { bf.WriteUint16(1) } else { bf.WriteUint16(0) } - resp.WriteUint16(stage.maxPlayers) // Max players. + bf.WriteUint16(stage.maxPlayers) if len(stage.password) > 0 { // This byte has also been seen as 1 // The quest is also recognised as locked when this is 2 - resp.WriteUint8(3) + bf.WriteUint8(2) } else { - resp.WriteUint8(0) + bf.WriteUint8(0) } - ps.Uint8(resp, sid, false) + ps.Uint8(bf, sid, false) stage.RUnlock() } - bf.WriteUint16(uint16(joinable)) - bf.WriteBytes(resp.Data()) + bf.Seek(0, 0) + bf.WriteUint16(joinable) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From 29904d5b9297898b4dfd78d8122908749f41c1f0 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 7 Nov 2023 20:23:02 +1100 Subject: [PATCH 080/269] fix signing of min/max Rank Distributions --- patch-schema/10-rework-distributions.sql | 23 ++++++++++++++++++- server/channelserver/handlers_distitem.go | 28 ++++++++++++----------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/patch-schema/10-rework-distributions.sql b/patch-schema/10-rework-distributions.sql index c56a9ff67..4dc794ad3 100644 --- a/patch-schema/10-rework-distributions.sql +++ b/patch-schema/10-rework-distributions.sql @@ -6,10 +6,31 @@ BEGIN; CREATE TABLE public.distribution_items ( id serial PRIMARY KEY, - distribution_id integer, + distribution_id integer NOT NULL, item_type integer, item_id integer, quantity integer ); +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN min_hr DROP DEFAULT; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN max_hr DROP DEFAULT; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN min_sr DROP DEFAULT; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN max_sr DROP DEFAULT; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN min_gr DROP DEFAULT; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN max_gr DROP DEFAULT; + +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN min_hr DROP NOT NULL; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN max_hr DROP NOT NULL; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN min_sr DROP NOT NULL; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN max_sr DROP NOT NULL; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN min_gr DROP NOT NULL; +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN max_gr DROP NOT NULL; + +UPDATE distribution SET min_hr=NULL WHERE min_hr=65535; +UPDATE distribution SET max_hr=NULL WHERE max_hr=65535; +UPDATE distribution SET min_sr=NULL WHERE min_sr=65535; +UPDATE distribution SET max_sr=NULL WHERE max_sr=65535; +UPDATE distribution SET min_gr=NULL WHERE min_gr=65535; +UPDATE distribution SET max_gr=NULL WHERE max_gr=65535; + END; \ No newline at end of file diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index 97d9e882e..7ccf1c74d 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -14,12 +14,12 @@ type ItemDist struct { Deadline time.Time `db:"deadline"` TimesAcceptable uint16 `db:"times_acceptable"` TimesAccepted uint16 `db:"times_accepted"` - MinHR uint16 `db:"min_hr"` - MaxHR uint16 `db:"max_hr"` - MinSR uint16 `db:"min_sr"` - MaxSR uint16 `db:"max_sr"` - MinGR uint16 `db:"min_gr"` - MaxGR uint16 `db:"max_gr"` + MinHR int16 `db:"min_hr"` + MaxHR int16 `db:"max_hr"` + MinSR int16 `db:"min_sr"` + MaxSR int16 `db:"max_sr"` + MinGR int16 `db:"min_gr"` + MaxGR int16 `db:"max_gr"` EventName string `db:"event_name"` Description string `db:"description"` Data []byte `db:"data"` @@ -31,7 +31,9 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { distCount := 0 dists, err := s.server.db.Queryx(` SELECT d.id, event_name, description, times_acceptable, - min_hr, max_hr, min_sr, max_sr, min_gr, max_gr, + COALESCE(min_hr, -1) AS min_hr, COALESCE(max_hr, -1) AS max_hr, + COALESCE(min_sr, -1) AS min_sr, COALESCE(max_sr, -1) AS max_sr, + COALESCE(min_gr, -1) AS min_gr, COALESCE(max_gr, -1) AS max_gr, ( SELECT count(*) FROM distributions_accepted da @@ -59,12 +61,12 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(distData.TimesAcceptable) bf.WriteUint16(distData.TimesAccepted) bf.WriteUint16(0) // Unk - bf.WriteUint16(distData.MinHR) - bf.WriteUint16(distData.MaxHR) - bf.WriteUint16(distData.MinSR) - bf.WriteUint16(distData.MaxSR) - bf.WriteUint16(distData.MinGR) - bf.WriteUint16(distData.MaxGR) + bf.WriteInt16(distData.MinHR) + bf.WriteInt16(distData.MaxHR) + bf.WriteInt16(distData.MinSR) + bf.WriteInt16(distData.MaxSR) + bf.WriteInt16(distData.MinGR) + bf.WriteInt16(distData.MaxGR) bf.WriteUint8(0) bf.WriteUint16(0) bf.WriteUint8(0) From 14e61fd661127d009293e12b0c967363d4cbbcf9 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 7 Nov 2023 21:07:49 +1100 Subject: [PATCH 081/269] fix Distribution typing, accepting & add demo --- bundled-schema/DistributionDemo.sql | 11 +++++ patch-schema/10-rework-distributions.sql | 2 +- server/channelserver/handlers_distitem.go | 60 +++++++++++++---------- 3 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 bundled-schema/DistributionDemo.sql diff --git a/bundled-schema/DistributionDemo.sql b/bundled-schema/DistributionDemo.sql new file mode 100644 index 000000000..f8b1dc018 --- /dev/null +++ b/bundled-schema/DistributionDemo.sql @@ -0,0 +1,11 @@ +BEGIN; + +-- Adds a Distribution that can be accepted up to 20 times that gives one of Item Type 30 (Item Box extra page) +INSERT INTO distribution (type, event_name, description, times_acceptable) VALUES (1, 'Extra Item Storage', '~C05Adds one new page to your Item Box.', 20); +INSERT INTO distribution_items (distribution_id, item_type, item_id, quantity) VALUES ((SELECT id FROM distribution ORDER BY id DESC LIMIT 1), 30, 0, 1); + +-- Adds a Distribution that can be accepted up to 20 times that gives one of Item Type 31 (Equipment Box extra page) +INSERT INTO distribution (type, event_name, description, times_acceptable) VALUES (1, 'Extra Equipment Storage', '~C05Adds one new page to your Equipment Box.', 20); +INSERT INTO distribution_items (distribution_id, item_type, item_id, quantity) VALUES ((SELECT id FROM distribution ORDER BY id DESC LIMIT 1), 31, 0, 1); + +END; \ No newline at end of file diff --git a/patch-schema/10-rework-distributions.sql b/patch-schema/10-rework-distributions.sql index 4dc794ad3..7945de343 100644 --- a/patch-schema/10-rework-distributions.sql +++ b/patch-schema/10-rework-distributions.sql @@ -7,7 +7,7 @@ CREATE TABLE public.distribution_items ( id serial PRIMARY KEY, distribution_id integer NOT NULL, - item_type integer, + item_type integer NOT NULL, item_id integer, quantity integer ); diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index 7ccf1c74d..97dbfcf15 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -104,13 +104,9 @@ type DistributionItem struct { Quantity uint32 `db:"quantity"` } -func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfApplyDistItem) - - bf := byteframe.NewByteFrame() - bf.WriteUint32(pkt.DistributionID) +func getDistributionItems(s *Session, i uint32) []DistributionItem { var distItems []DistributionItem - rows, err := s.server.db.Queryx(`SELECT id, item_id, item_type, quantity FROM distribution_items WHERE distribution_id=$1`, pkt.DistributionID) + rows, err := s.server.db.Queryx(`SELECT id, item_type, COALESCE(item_id, 0) AS item_id, COALESCE(quantity, 0) AS quantity FROM distribution_items WHERE distribution_id=$1`, i) if err == nil { var distItem DistributionItem for rows.Next() { @@ -121,38 +117,50 @@ func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) { distItems = append(distItems, distItem) } } + return distItems +} + +func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfApplyDistItem) + bf := byteframe.NewByteFrame() + bf.WriteUint32(pkt.DistributionID) + distItems := getDistributionItems(s, pkt.DistributionID) bf.WriteUint16(uint16(len(distItems))) for _, item := range distItems { bf.WriteUint8(item.ItemType) bf.WriteUint32(item.ItemID) bf.WriteUint32(item.Quantity) bf.WriteUint32(item.ID) - switch item.ItemType { - case 17: - _ = addPointNetcafe(s, int(item.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)", item.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)", item.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)", item.Quantity, s.charID) - case 23: - saveData, err := GetCharacterSaveData(s, s.charID) - if err == nil { - saveData.RP += uint16(item.Quantity) - saveData.Save(s) - } - } } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - - if pkt.DistributionID > 0 { - _, err = s.server.db.Exec(`INSERT INTO public.distributions_accepted VALUES ($1, $2)`, pkt.DistributionID, s.charID) - } } func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfAcquireDistItem) + if pkt.DistributionID > 0 { + _, err := s.server.db.Exec(`INSERT INTO public.distributions_accepted VALUES ($1, $2)`, pkt.DistributionID, s.charID) + if err == nil { + distItems := getDistributionItems(s, pkt.DistributionID) + for _, item := range distItems { + switch item.ItemType { + case 17: + _ = addPointNetcafe(s, int(item.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)", item.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)", item.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)", item.Quantity, s.charID) + case 23: + saveData, err := GetCharacterSaveData(s, s.charID) + if err == nil { + saveData.RP += uint16(item.Quantity) + saveData.Save(s) + } + } + } + } + } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } From 317daef04bbe6c30210dd1cb52082c0aeca09fe3 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 7 Nov 2023 21:26:45 +1100 Subject: [PATCH 082/269] rewrite EnumerateDistItem handler --- .../mhfpacket/msg_mhf_enumerate_dist_item.go | 2 +- server/channelserver/handlers_distitem.go | 106 +++++++++--------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/network/mhfpacket/msg_mhf_enumerate_dist_item.go b/network/mhfpacket/msg_mhf_enumerate_dist_item.go index 4e89c6dfc..bf5796702 100644 --- a/network/mhfpacket/msg_mhf_enumerate_dist_item.go +++ b/network/mhfpacket/msg_mhf_enumerate_dist_item.go @@ -26,7 +26,7 @@ func (m *MsgMhfEnumerateDistItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx. m.AckHandle = bf.ReadUint32() m.DistType = bf.ReadUint8() m.Unk1 = bf.ReadUint8() - m.Unk2 = bf.ReadUint16() + m.Unk2 = bf.ReadUint16() // Maximum? Hardcoded to 256 m.Unk3 = bf.ReadBytes(uint(bf.ReadUint8())) return nil } diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index 97dbfcf15..b18961202 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -9,7 +9,7 @@ import ( "go.uber.org/zap" ) -type ItemDist struct { +type Distribution struct { ID uint32 `db:"id"` Deadline time.Time `db:"deadline"` TimesAcceptable uint16 `db:"times_acceptable"` @@ -27,74 +27,72 @@ type ItemDist struct { func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateDistItem) + + var itemDists []Distribution bf := byteframe.NewByteFrame() - distCount := 0 - dists, err := s.server.db.Queryx(` + rows, err := s.server.db.Queryx(` SELECT d.id, event_name, description, times_acceptable, COALESCE(min_hr, -1) AS min_hr, COALESCE(max_hr, -1) AS max_hr, COALESCE(min_sr, -1) AS min_sr, COALESCE(max_sr, -1) AS max_sr, COALESCE(min_gr, -1) AS min_gr, COALESCE(max_gr, -1) AS max_gr, ( - SELECT count(*) - FROM distributions_accepted da - WHERE d.id = da.distribution_id - AND da.character_id = $1 + SELECT count(*) FROM distributions_accepted da + WHERE d.id = da.distribution_id AND da.character_id = $1 ) AS times_accepted, COALESCE(deadline, TO_TIMESTAMP(0)) AS deadline FROM distribution d - WHERE character_id = $1 AND type = $2 OR character_id IS NULL AND type = $2 ORDER BY id DESC; + WHERE character_id = $1 AND type = $2 OR character_id IS NULL AND type = $2 ORDER BY id DESC `, s.charID, pkt.DistType) - if err != nil { - s.logger.Error("Error getting distribution data from db", zap.Error(err)) - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) - } else { - for dists.Next() { - distCount++ - distData := &ItemDist{} - err = dists.StructScan(&distData) + + if err == nil { + var itemDist Distribution + for rows.Next() { + err = rows.StructScan(&itemDist) if err != nil { - s.logger.Error("Error parsing item distribution data", zap.Error(err)) + continue } - bf.WriteUint32(distData.ID) - bf.WriteUint32(uint32(distData.Deadline.Unix())) - bf.WriteUint32(0) // Unk - bf.WriteUint16(distData.TimesAcceptable) - bf.WriteUint16(distData.TimesAccepted) - bf.WriteUint16(0) // Unk - bf.WriteInt16(distData.MinHR) - bf.WriteInt16(distData.MaxHR) - bf.WriteInt16(distData.MinSR) - bf.WriteInt16(distData.MaxSR) - bf.WriteInt16(distData.MinGR) - bf.WriteInt16(distData.MaxGR) - bf.WriteUint8(0) - bf.WriteUint16(0) - bf.WriteUint8(0) - bf.WriteUint16(0) - bf.WriteUint16(0) - bf.WriteUint8(0) - ps.Uint8(bf, distData.EventName, true) - for i := 0; i < 6; i++ { - for j := 0; j < 13; j++ { - bf.WriteUint8(0) - bf.WriteUint32(0) - } - } - i := uint8(0) - bf.WriteUint8(i) - if i <= 10 { - for j := uint8(0); j < i; j++ { - bf.WriteUint32(0) - bf.WriteUint32(0) - bf.WriteUint32(0) - } + itemDists = append(itemDists, itemDist) + } + } + + bf.WriteUint16(uint16(len(itemDists))) + for _, dist := range itemDists { + bf.WriteUint32(dist.ID) + bf.WriteUint32(uint32(dist.Deadline.Unix())) + bf.WriteUint32(0) // Unk + bf.WriteUint16(dist.TimesAcceptable) + bf.WriteUint16(dist.TimesAccepted) + bf.WriteUint16(0) // Unk + bf.WriteInt16(dist.MinHR) + bf.WriteInt16(dist.MaxHR) + bf.WriteInt16(dist.MinSR) + bf.WriteInt16(dist.MaxSR) + bf.WriteInt16(dist.MinGR) + bf.WriteInt16(dist.MaxGR) + bf.WriteUint8(0) + bf.WriteUint16(0) + bf.WriteUint8(0) + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint8(0) + ps.Uint8(bf, dist.EventName, true) + for i := 0; i < 6; i++ { + for j := 0; j < 13; j++ { + bf.WriteUint8(0) + bf.WriteUint32(0) + } + } + i := uint8(0) + bf.WriteUint8(i) + if i <= 10 { + for j := uint8(0); j < i; j++ { + bf.WriteUint32(0) + bf.WriteUint32(0) + bf.WriteUint32(0) } } - resp := byteframe.NewByteFrame() - resp.WriteUint16(uint16(distCount)) - resp.WriteBytes(bf.Data()) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } type DistributionItem struct { From 7eaf37c1ffa193a27b36c2ae3e4445b579d38fee Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 7 Nov 2023 22:00:07 +1100 Subject: [PATCH 083/269] simplify DistributionDemo script --- bundled-schema/DistributionDemo.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundled-schema/DistributionDemo.sql b/bundled-schema/DistributionDemo.sql index f8b1dc018..d5da8688e 100644 --- a/bundled-schema/DistributionDemo.sql +++ b/bundled-schema/DistributionDemo.sql @@ -2,10 +2,10 @@ BEGIN; -- Adds a Distribution that can be accepted up to 20 times that gives one of Item Type 30 (Item Box extra page) INSERT INTO distribution (type, event_name, description, times_acceptable) VALUES (1, 'Extra Item Storage', '~C05Adds one new page to your Item Box.', 20); -INSERT INTO distribution_items (distribution_id, item_type, item_id, quantity) VALUES ((SELECT id FROM distribution ORDER BY id DESC LIMIT 1), 30, 0, 1); +INSERT INTO distribution_items (distribution_id, item_type, quantity) VALUES ((SELECT id FROM distribution ORDER BY id DESC LIMIT 1), 30, 1); -- Adds a Distribution that can be accepted up to 20 times that gives one of Item Type 31 (Equipment Box extra page) INSERT INTO distribution (type, event_name, description, times_acceptable) VALUES (1, 'Extra Equipment Storage', '~C05Adds one new page to your Equipment Box.', 20); -INSERT INTO distribution_items (distribution_id, item_type, item_id, quantity) VALUES ((SELECT id FROM distribution ORDER BY id DESC LIMIT 1), 31, 0, 1); +INSERT INTO distribution_items (distribution_id, item_type, quantity) VALUES ((SELECT id FROM distribution ORDER BY id DESC LIMIT 1), 31, 1); END; \ No newline at end of file From af519a59cf67dad91e15f322beb66b75d1a49b2e Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 7 Nov 2023 16:50:39 +1100 Subject: [PATCH 084/269] rewrite EnumerateStage & parse ReserveStage --- network/mhfpacket/msg_sys_reserve_stage.go | 5 ++--- server/channelserver/handlers_stage.go | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/network/mhfpacket/msg_sys_reserve_stage.go b/network/mhfpacket/msg_sys_reserve_stage.go index 13e47c41b..d2f688af4 100644 --- a/network/mhfpacket/msg_sys_reserve_stage.go +++ b/network/mhfpacket/msg_sys_reserve_stage.go @@ -3,7 +3,6 @@ package mhfpacket import ( "errors" "erupe-ce/common/byteframe" - "erupe-ce/common/bfutil" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -24,8 +23,8 @@ func (m *MsgSysReserveStage) Opcode() network.PacketID { func (m *MsgSysReserveStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Ready = bf.ReadUint8() - stageIDLength := bf.ReadUint8() - m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) + _ = bf.ReadUint8() // StageID length + m.StageID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 983d6e79e..be7ab1267 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -367,9 +367,9 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { defer s.server.stagesLock.RUnlock() // Build the response - resp := byteframe.NewByteFrame() bf := byteframe.NewByteFrame() - var joinable int + var joinable uint16 + bf.WriteUint16(0) for sid, stage := range s.server.stages { stage.RLock() @@ -377,34 +377,32 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { stage.RUnlock() continue } - if !strings.Contains(stage.id, pkt.StagePrefix) { stage.RUnlock() continue } - joinable++ - resp.WriteUint16(uint16(len(stage.reservedClientSlots))) // Reserved players. - resp.WriteUint16(0) // Unk + bf.WriteUint16(uint16(len(stage.reservedClientSlots))) + bf.WriteUint16(0) // Unk if len(stage.clients) > 0 { bf.WriteUint16(1) } else { bf.WriteUint16(0) } - resp.WriteUint16(stage.maxPlayers) // Max players. + bf.WriteUint16(stage.maxPlayers) if len(stage.password) > 0 { // This byte has also been seen as 1 // The quest is also recognised as locked when this is 2 - resp.WriteUint8(3) + bf.WriteUint8(2) } else { - resp.WriteUint8(0) + bf.WriteUint8(0) } - ps.Uint8(resp, sid, false) + ps.Uint8(bf, sid, false) stage.RUnlock() } - bf.WriteUint16(uint16(joinable)) - bf.WriteBytes(resp.Data()) + bf.Seek(0, 0) + bf.WriteUint16(joinable) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From 378dfd03723d2a696b8be617221bd77cc71ceee6 Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Tue, 7 Nov 2023 21:16:03 -0500 Subject: [PATCH 085/269] fix: Fixed issues with improper loading of areas --- server/channelserver/handlers_quest.go | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index c7961ea20..2cd326e3c 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -68,19 +68,8 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { } } -func questSuffix(s *Session) string { - // Determine the letter to append for day / night - var timeSet string - if TimeGameAbsolute() > 2880 { - timeSet = "d" - } else { - timeSet = "n" - } - return fmt.Sprintf("%s%d", timeSet, s.server.Season()) -} - func seasonConversion(s *Session, questFile string) string { - filename := fmt.Sprintf("%s%s", questFile[:5], questSuffix(s)) + filename := fmt.Sprintf("%s%d", questFile[:6], s.server.Season()) // Return original file if file doesn't exist if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", filename))); err == nil { From 611cb2da5bcd96acd658fbbf7eaa3fdf1b6eb94e Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Wed, 8 Nov 2023 13:28:05 -0500 Subject: [PATCH 086/269] feat: Request custom files based on time of day. --- server/channelserver/handlers_quest.go | 42 +++++++++----------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 2cd326e3c..98ce7c8bc 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -51,7 +51,7 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { pkt.Filename = seasonConversion(s, pkt.Filename) } - // custom quests expect there to be only a d0 for quests, rewrite quests to try for d0 if the quest file doesn't exist + // Default to d0 for any other quest that has no alternative versions if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))); err != nil { pkt.Filename = fmt.Sprintf("%s%s", pkt.Filename[:5], "d0") } @@ -71,16 +71,26 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { func seasonConversion(s *Session, questFile string) string { filename := fmt.Sprintf("%s%d", questFile[:6], s.server.Season()) - // Return original file if file doesn't exist + // Return the seasonal file if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", filename))); err == nil { return filename } else { - // Load d0 if the regular quest file doesn't exist (Fixes custom quests) + // Attempt to return the requested quest file if the seasonal file doesn't exist if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", questFile))); err == nil { return questFile } - return fmt.Sprintf("%s%s", questFile[:5], "d0") + // For custom quests, we need to return the day or night version of the quest. + var time string + + if TimeGameAbsolute() > 2880 { + time = "d" + } else { + time = "n" + } + + // Request a file based on day or night + return fmt.Sprintf("%s%s%d", questFile[:5], time, 0) } } @@ -184,30 +194,6 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { bf.WriteUint16(0) // Unk bf.WriteUint16(uint16(len(data))) bf.WriteBytes(data) - - // Time Flag Replacement - // Bitset Structure: b8 UNK, b7 Required Objective, b6 UNK, b5 Night, b4 Day, b3 Cold, b2 Warm, b1 Spring - // if the byte is set to 0 the game choses the quest file corresponding to whatever season the game is on - bf.Seek(25, 0) - flagByte := bf.ReadUint8() - bf.Seek(25, 0) - if s.server.erupeConfig.GameplayOptions.SeasonOverride { - bf.WriteUint8(flagByte & 0b11100000) - } else { - bf.WriteUint8(flagByte) - } - - // Bitset Structure Quest Variant 1: b8 UL Fixed, b7 UNK, b6 UNK, b5 UNK, b4 G Rank, b3 HC to UL, b2 Fix HC, b1 Hiden - // Bitset Structure Quest Variant 2: b8 Road, b7 High Conquest, b6 Fixed Difficulty, b5 No Active Feature, b4 Timer, b3 No Cuff, b2 No Halk Pots, b1 Low Conquest - // Bitset Structure Quest Variant 3: b8 No Sigils, b7 UNK, b6 Interception, b5 Zenith, b4 No GP Skills, b3 No Simple Mode?, b2 GSR to GR, b1 No Reward Skills - - bf.Seek(175, 0) - questVariant3 := bf.ReadUint8() - questVariant3 &= 0b11011111 // disable Interception flag - bf.Seek(175, 0) - bf.WriteUint8(questVariant3) - - bf.Seek(0, 2) ps.Uint8(bf, "", true) // Debug/Notes string for quest return bf.Data(), nil } From 3e4e325675243e3523b0baebe25418059fb639c5 Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Sat, 11 Nov 2023 03:42:08 -0500 Subject: [PATCH 087/269] feat: Implement event quest season/time flag override --- patch-schema/10-event-quest-flags.sql | 5 ++++ server/channelserver/handlers_quest.go | 32 +++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 patch-schema/10-event-quest-flags.sql diff --git a/patch-schema/10-event-quest-flags.sql b/patch-schema/10-event-quest-flags.sql new file mode 100644 index 000000000..24b59db6a --- /dev/null +++ b/patch-schema/10-event-quest-flags.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS flag_override integer NOT NULL DEFAULT -1; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 98ce7c8bc..7eaf45292 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -156,7 +156,8 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { var id, mark uint32 var questId int var maxPlayers, questType uint8 - rows.Scan(&id, &maxPlayers, &questType, &questId, &mark) + var questFlags int8 + rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &questFlags) data := loadQuestFile(s, questId) if data == nil { @@ -194,6 +195,35 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { bf.WriteUint16(0) // Unk bf.WriteUint16(uint16(len(data))) bf.WriteBytes(data) + + // Time Flag Replacement + // Bitset Structure: b8 UNK, b7 Required Objective, b6 UNK, b5 Night, b4 Day, b3 Cold, b2 Warm, b1 Spring + // if the byte is set to 0 the game choses the quest file corresponding to whatever season the game is on + bf.Seek(25, 0) + flagByte := bf.ReadUint8() + bf.Seek(25, 0) + if s.server.erupeConfig.GameplayOptions.SeasonOverride { + bf.WriteUint8(flagByte & 0b11100000) + } else { + // Allow for seasons to be specified in database, otherwise use the one in the file. + if questFlags == -1 { + bf.WriteUint8(flagByte) + } else { + bf.WriteUint8(uint8(questFlags)) + } + } + + // Bitset Structure Quest Variant 1: b8 UL Fixed, b7 UNK, b6 UNK, b5 UNK, b4 G Rank, b3 HC to UL, b2 Fix HC, b1 Hiden + // Bitset Structure Quest Variant 2: b8 Road, b7 High Conquest, b6 Fixed Difficulty, b5 No Active Feature, b4 Timer, b3 No Cuff, b2 No Halk Pots, b1 Low Conquest + // Bitset Structure Quest Variant 3: b8 No Sigils, b7 UNK, b6 Interception, b5 Zenith, b4 No GP Skills, b3 No Simple Mode?, b2 GSR to GR, b1 No Reward Skills + + bf.Seek(175, 0) + questVariant3 := bf.ReadUint8() + questVariant3 &= 0b11011111 // disable Interception flag + bf.Seek(175, 0) + bf.WriteUint8(questVariant3) + + bf.Seek(0, 2) ps.Uint8(bf, "", true) // Debug/Notes string for quest return bf.Data(), nil } From f588d47aa18f19f598800f13331abc6e470b7327 Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Sun, 12 Nov 2023 16:01:19 -0500 Subject: [PATCH 088/269] chore: Reset config files --- config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index aeb04eadf..31629b894 100644 --- a/config.json +++ b/config.json @@ -17,8 +17,8 @@ "AutoCreateAccount": true, "CleanDB": false, "MaxLauncherHR": false, - "LogInboundMessages": false, - "LogOutboundMessages": false, + "LogInboundMessages": true, + "LogOutboundMessages": true, "MaxHexdumpLength": 256, "DivaEvent": 0, "FestaEvent": -1, @@ -67,7 +67,7 @@ "EnableHiganjimaEvent": false, "EnableNierEvent": false, "DisableRoad": false, - "SeasonOverride": true + "SeasonOverride": false }, "Discord": { "Enabled": false, From a9b8bb4c56a315bc768713a6d34906cdf3f25b20 Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Thu, 16 Nov 2023 02:00:01 -0500 Subject: [PATCH 089/269] fix: Added flags to sql query --- config.json | 4 ++-- server/channelserver/handlers_quest.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index 31629b894..5dcb6ef51 100644 --- a/config.json +++ b/config.json @@ -120,9 +120,9 @@ ], "Database": { "Host": "localhost", - "Port": 5432, + "Port": 5433, "User": "postgres", - "Password": "", + "Password": "admin", "Database": "erupe" }, "Sign": { diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 7eaf45292..91a000c5b 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -234,7 +234,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint16(0) - rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id") + rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, flags FROM event_quests ORDER BY quest_id") for rows.Next() { data, err := makeEventQuest(s, rows) if err != nil { From a27d15bff197c446b38c2770a0a13cb84c68f33e Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 16 Nov 2023 02:38:58 -0500 Subject: [PATCH 090/269] style: Fixed inconsistent spacing and cleaned up SQL query strings --- server/channelserver/handlers_quest.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index deefe05ea..df7496f58 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -197,14 +197,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(0) currentTime := time.Now() - var tableName = "event_quests" - /* This is an example of how I have to test and avoid issues, might be a thing later? - if _config.ErupeConfig.RealClientMode <= _config.F5 { - tableName = "event_quests_older" - } - */ + // Check the event_quests table to load the quests with rotation system - rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, start_time, active_duration, inactive_duration FROM " + tableName + "") + rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, start_time, active_duration, inactive_duration FROM event_quests") if err != nil { return } @@ -224,14 +219,14 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { rotationTime := startTime.Add(time.Duration(activeDuration+inactiveDuration) * 24 * time.Hour) if currentTime.After(rotationTime) { // The rotation time has passed, update the start time and reset the rotation - _, err := s.server.db.Exec("UPDATE "+tableName+" SET start_time = $1 WHERE quest_id = $2", rotationTime, questId) + _, err := s.server.db.Exec("UPDATE event_quests SET start_time = $1 WHERE quest_id = $2", rotationTime, questId) if err != nil { return } } // Check if the quest is currently active - if currentTime.After(startTime) && currentTime.Sub(startTime) <= time.Duration(activeDuration)*24*time.Hour { + if currentTime.After(startTime) && currentTime.Sub(startTime) <= time.Duration(activeDuration) * 24 * time.Hour { data, err := makeEventQuest(s, rows) if err != nil { continue From 8c69a98c58285d1e6bd6f354b5a9c500a49ce488 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 16 Nov 2023 21:31:31 +1100 Subject: [PATCH 091/269] remove arbitrary test code --- server/channelserver/handlers_quest.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index da3c8166a..76a615c49 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -202,18 +202,10 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { continue } else { totalCount++ - if _config.ErupeConfig.RealClientMode == _config.F5 { - if totalCount > pkt.Offset && len(bf.Data()) < 21550 { - returnedCount++ - bf.WriteBytes(data) - continue - } - } else { - if totalCount > pkt.Offset && len(bf.Data()) < 60000 { - returnedCount++ - bf.WriteBytes(data) - continue - } + if totalCount > pkt.Offset && len(bf.Data()) < 60000 { + returnedCount++ + bf.WriteBytes(data) + continue } } } From 15253cdc1f84769cfd171d58a73540899f30ee25 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 16 Nov 2023 21:49:43 +1100 Subject: [PATCH 092/269] remove useless CIDs --- server/channelserver/sys_channel_server.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 1dfef82d0..02ede6f29 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -316,7 +316,6 @@ func (s *Server) BroadcastChatMessage(message string) { msgBinChat.Build(bf) s.BroadcastMHF(&mhfpacket.MsgSysCastedBinary{ - CharID: 0xFFFFFFFF, MessageType: BinaryMessageTypeChat, RawDataPayload: bf.Data(), }, nil) @@ -348,7 +347,6 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u bf.WriteUint16(0) // Unk bf.WriteBytes(stage) s.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{ - CharID: 0x00000000, BroadcastType: BroadcastTypeServer, MessageType: BinaryMessageTypeChat, RawDataPayload: bf.Data(), From 72bda06916cd29d63056a2dc2753244b4b07a5b5 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 16 Nov 2023 21:51:28 +1100 Subject: [PATCH 093/269] implement Quest caching --- config.json | 1 + config/config.go | 1 + server/channelserver/handlers_quest.go | 7 +++++++ server/channelserver/sys_channel_server.go | 6 ++++++ 4 files changed, 15 insertions(+) diff --git a/config.json b/config.json index 31629b894..a62b45325 100644 --- a/config.json +++ b/config.json @@ -12,6 +12,7 @@ "ScreenshotAPIURL": "", "DeleteOnSaveCorruption": false, "ClientMode": "ZZ", + "QuestCacheExpiry": 300, "DevMode": true, "DevModeOptions": { "AutoCreateAccount": true, diff --git a/config/config.go b/config/config.go index 65c663511..4c156d35e 100644 --- a/config/config.go +++ b/config/config.go @@ -79,6 +79,7 @@ type Config struct { DeleteOnSaveCorruption bool // Attempts to save corrupted data will flag the save for deletion ClientMode string RealClientMode Mode + QuestCacheExpiry int // Number of seconds to keep quest data cached DevMode bool DevModeOptions DevModeOptions diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 76a615c49..2c82986e7 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -77,6 +77,11 @@ func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) { } func loadQuestFile(s *Session, questId int) []byte { + data, exists := s.server.questCacheData[questId] + if exists && s.server.questCacheTime[questId].Add(time.Duration(s.server.erupeConfig.QuestCacheExpiry)*time.Second).After(time.Now()) { + return data + } + file, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%05dd0.bin", questId))) if err != nil { return nil @@ -113,6 +118,8 @@ func loadQuestFile(s *Session, questId int) []byte { } questBody.WriteBytes(newStrings.Data()) + s.server.questCacheData[questId] = questBody.Data() + s.server.questCacheTime[questId] = time.Now() return questBody.Data() } diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 02ede6f29..d12d713f5 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -5,6 +5,7 @@ import ( "net" "strings" "sync" + "time" "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" @@ -73,6 +74,9 @@ type Server struct { name string raviente *Raviente + + questCacheData map[int][]byte + questCacheTime map[int]time.Time } type Raviente struct { @@ -163,6 +167,8 @@ func NewServer(config *Config) *Server { state: make([]uint32, 30), support: make([]uint32, 30), }, + questCacheData: make(map[int][]byte), + questCacheTime: make(map[int]time.Time), } // Mezeporta From 6c32eae9f24565f61f182f9b59abf1707ca95ee2 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 16 Nov 2023 21:56:48 +1100 Subject: [PATCH 094/269] simplify code --- server/channelserver/handlers_cast_binary.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 30a377b82..168e2f181 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -6,6 +6,7 @@ import ( "erupe-ce/common/mhfcourse" "erupe-ce/common/token" "erupe-ce/config" + "erupe-ce/network" "erupe-ce/network/binpacket" "erupe-ce/network/mhfpacket" "fmt" @@ -125,7 +126,7 @@ func parseChatCommand(s *Session, command string) { deleteNotif.WriteUint16(uint16(temp.Opcode())) temp.Build(deleteNotif, s.clientContext) } - deleteNotif.WriteUint16(0x0010) + deleteNotif.WriteUint16(uint16(network.MSG_SYS_END)) s.QueueSend(deleteNotif.Data()) time.Sleep(500 * time.Millisecond) reloadNotif := byteframe.NewByteFrame() @@ -160,7 +161,7 @@ func parseChatCommand(s *Session, command string) { reloadNotif.WriteUint16(uint16(temp.Opcode())) temp.Build(reloadNotif, s.clientContext) } - reloadNotif.WriteUint16(0x0010) + reloadNotif.WriteUint16(uint16(network.MSG_SYS_END)) s.QueueSend(reloadNotif.Data()) } else { sendDisabledCommandMessage(s, commands["Reload"]) From 34044f72b043089484c2f1852ccde178fcf6b888 Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Thu, 16 Nov 2023 11:07:00 -0500 Subject: [PATCH 095/269] chore: Fix config.json --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 5dcb6ef51..31629b894 100644 --- a/config.json +++ b/config.json @@ -120,9 +120,9 @@ ], "Database": { "Host": "localhost", - "Port": 5433, + "Port": 5432, "User": "postgres", - "Password": "admin", + "Password": "", "Database": "erupe" }, "Sign": { From 2e2d129871089000b52e43f4a379d990b3ca3195 Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Thu, 16 Nov 2023 11:10:46 -0500 Subject: [PATCH 096/269] docs: Fix annotation for custom quest conditions --- server/channelserver/handlers_quest.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 91a000c5b..999519766 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -80,7 +80,8 @@ func seasonConversion(s *Session, questFile string) string { return questFile } - // For custom quests, we need to return the day or night version of the quest. + // If the code reaches this point, it's most likely a custom quest with no seasonal variations in the files. + // Since event quests when seasonal pick day or night and the client requests either one, we need to differentiate between the two to prevent issues. var time string if TimeGameAbsolute() > 2880 { @@ -89,7 +90,7 @@ func seasonConversion(s *Session, questFile string) string { time = "n" } - // Request a file based on day or night + // Request a D0 or N0 file depending on the time of day. The time of day matters since the client will quite a few issues if it's different to the one it requests. return fmt.Sprintf("%s%s%d", questFile[:5], time, 0) } } From 6384d79a7ab00a0bda0cc197b679df9cc9b7548e Mon Sep 17 00:00:00 2001 From: Matthe815 Date: Thu, 16 Nov 2023 11:29:31 -0500 Subject: [PATCH 097/269] fix: Removed unnecessary condition --- server/channelserver/handlers_quest.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 999519766..7c782fb60 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -51,14 +51,8 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { pkt.Filename = seasonConversion(s, pkt.Filename) } - // Default to d0 for any other quest that has no alternative versions - if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))); err != nil { - pkt.Filename = fmt.Sprintf("%s%s", pkt.Filename[:5], "d0") - } - data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) if err != nil { - s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) // This will crash the game. doAckBufSucceed(s, pkt.AckHandle, data) From 800e993c1fa2ebb5fffe27bd79c548a035f2f455 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 17 Nov 2023 21:14:19 -0500 Subject: [PATCH 098/269] sql: Added 11-event_quest_cycling migration sql --- patch-schema/11-event_quest_cycling.sql | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 patch-schema/11-event_quest_cycling.sql diff --git a/patch-schema/11-event_quest_cycling.sql b/patch-schema/11-event_quest_cycling.sql new file mode 100644 index 000000000..cabc928a7 --- /dev/null +++ b/patch-schema/11-event_quest_cycling.sql @@ -0,0 +1,7 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS start_time timestamp with time zone NOT NULL DEFAULT (CURRENT_DATE + interval '0 second'); +ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS active_duration int DEFAULT 4; +ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS inactive_duration int DEFAULT 3; + +END; \ No newline at end of file From 233990f452dc9111cf42a79d2ba7531e5d9b0c01 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 17 Nov 2023 22:18:07 -0500 Subject: [PATCH 099/269] refactor: Change event quest updating to use transactions rather than directly commiting --- server/channelserver/handlers_quest.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index df7496f58..edf7f7ae2 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -205,11 +205,15 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { } defer rows.Close() + // Commit event quest changes to a transaction instead of doing it one by one for to help with performance + transaction, _ := s.server.db.Begin() + for rows.Next() { var id, mark uint32 var questId, activeDuration, inactiveDuration int var maxPlayers, questType uint8 var startTime time.Time + err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &startTime, &activeDuration, &inactiveDuration) if err != nil { continue @@ -219,14 +223,14 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { rotationTime := startTime.Add(time.Duration(activeDuration+inactiveDuration) * 24 * time.Hour) if currentTime.After(rotationTime) { // The rotation time has passed, update the start time and reset the rotation - _, err := s.server.db.Exec("UPDATE event_quests SET start_time = $1 WHERE quest_id = $2", rotationTime, questId) + _, err := transaction.Exec("UPDATE event_quests SET start_time = $1 WHERE quest_id = $2", rotationTime, questId) if err != nil { return } } // Check if the quest is currently active - if currentTime.After(startTime) && currentTime.Sub(startTime) <= time.Duration(activeDuration) * 24 * time.Hour { + if currentTime.After(startTime) && currentTime.Sub(startTime) <= time.Duration(activeDuration)*24*time.Hour { data, err := makeEventQuest(s, rows) if err != nil { continue @@ -245,6 +249,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { } } + // Commit transaction so to write to the database. + transaction.Commit() + type tuneValue struct { ID uint16 Value uint16 From 50d3ec36b25b173c41c35272ffc07d0fb7ccdb43 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 17 Nov 2023 22:21:15 -0500 Subject: [PATCH 100/269] chore: Fix formatting --- server/channelserver/handlers_quest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index edf7f7ae2..011db2617 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -220,7 +220,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { } // Calculate the rotation time based on start time, active duration, and inactive duration - rotationTime := startTime.Add(time.Duration(activeDuration+inactiveDuration) * 24 * time.Hour) + rotationTime := startTime.Add((time.Duration(activeDuration+inactiveDuration) * 24) * time.Hour) if currentTime.After(rotationTime) { // The rotation time has passed, update the start time and reset the rotation _, err := transaction.Exec("UPDATE event_quests SET start_time = $1 WHERE quest_id = $2", rotationTime, questId) From a34a0e42b24efe3bf66a1d55e3417115b34de2b5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 17 Nov 2023 22:21:48 -0500 Subject: [PATCH 101/269] fix: Commit to database via unique index rather than quest_id --- server/channelserver/handlers_quest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 011db2617..e53b2b54d 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -223,7 +223,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { rotationTime := startTime.Add((time.Duration(activeDuration+inactiveDuration) * 24) * time.Hour) if currentTime.After(rotationTime) { // The rotation time has passed, update the start time and reset the rotation - _, err := transaction.Exec("UPDATE event_quests SET start_time = $1 WHERE quest_id = $2", rotationTime, questId) + _, err := transaction.Exec("UPDATE event_quests SET start_time = $1 WHERE id = $2", rotationTime, id) if err != nil { return } From c20380b79d65f3cf72dca01e0ca238496d3d1b8b Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 17 Nov 2023 22:27:50 -0500 Subject: [PATCH 102/269] fix: Continue instead of ending event quest parsing if an error occurs on a single quest --- server/channelserver/handlers_quest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index e53b2b54d..37ae822a8 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -225,7 +225,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { // The rotation time has passed, update the start time and reset the rotation _, err := transaction.Exec("UPDATE event_quests SET start_time = $1 WHERE id = $2", rotationTime, id) if err != nil { - return + continue } } From 490aecd94b5c31e368e01be2193881024cc6d839 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 18 Nov 2023 15:44:59 +1100 Subject: [PATCH 103/269] rewrite comments & change quest flag code --- ...est-flags.sql => 11-event-quest-flags.sql} | 2 +- server/channelserver/handlers_quest.go | 23 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) rename patch-schema/{10-event-quest-flags.sql => 11-event-quest-flags.sql} (62%) diff --git a/patch-schema/10-event-quest-flags.sql b/patch-schema/11-event-quest-flags.sql similarity index 62% rename from patch-schema/10-event-quest-flags.sql rename to patch-schema/11-event-quest-flags.sql index 24b59db6a..5f88d732d 100644 --- a/patch-schema/10-event-quest-flags.sql +++ b/patch-schema/11-event-quest-flags.sql @@ -1,5 +1,5 @@ BEGIN; -ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS flag_override integer NOT NULL DEFAULT -1; +ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS flags integer; END; \ No newline at end of file diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 7c782fb60..fbabec35d 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -70,22 +70,22 @@ func seasonConversion(s *Session, questFile string) string { return filename } else { // Attempt to return the requested quest file if the seasonal file doesn't exist - if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", questFile))); err == nil { + if _, err = os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", questFile))); err == nil { return questFile } // If the code reaches this point, it's most likely a custom quest with no seasonal variations in the files. // Since event quests when seasonal pick day or night and the client requests either one, we need to differentiate between the two to prevent issues. - var time string + var _time string if TimeGameAbsolute() > 2880 { - time = "d" + _time = "d" } else { - time = "n" + _time = "n" } - // Request a D0 or N0 file depending on the time of day. The time of day matters since the client will quite a few issues if it's different to the one it requests. - return fmt.Sprintf("%s%s%d", questFile[:5], time, 0) + // Request a d0 or n0 file depending on the time of day. The time of day matters and issues will occur if it's different to the one it requests. + return fmt.Sprintf("%s%s%d", questFile[:5], _time, 0) } } @@ -149,10 +149,9 @@ func loadQuestFile(s *Session, questId int) []byte { func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { var id, mark uint32 - var questId int + var questId, flags int var maxPlayers, questType uint8 - var questFlags int8 - rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &questFlags) + rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags) data := loadQuestFile(s, questId) if data == nil { @@ -201,10 +200,10 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { bf.WriteUint8(flagByte & 0b11100000) } else { // Allow for seasons to be specified in database, otherwise use the one in the file. - if questFlags == -1 { + if flags < 0 { bf.WriteUint8(flagByte) } else { - bf.WriteUint8(uint8(questFlags)) + bf.WriteUint8(uint8(flags)) } } @@ -229,7 +228,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint16(0) - rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, flags FROM event_quests ORDER BY quest_id") + rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, COALESCE(flags, -1) FROM event_quests ORDER BY quest_id") for rows.Next() { data, err := makeEventQuest(s, rows) if err != nil { From 47ca3023844b6eee1424e142d059622483cc91a2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 18 Nov 2023 03:18:35 -0500 Subject: [PATCH 104/269] feat: Cycle simulation (simulate cycles if beyond a single set) --- config.json | 4 ++-- server/channelserver/handlers_quest.go | 24 ++++++++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/config.json b/config.json index d3de778bb..b175451e4 100644 --- a/config.json +++ b/config.json @@ -119,9 +119,9 @@ ], "Database": { "Host": "localhost", - "Port": 5432, + "Port": 5433, "User": "postgres", - "Password": "", + "Password": "admin", "Database": "erupe" }, "Sign": { diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 37ae822a8..90c477708 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -190,6 +190,14 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { return bf.Data(), nil } +func calculateNumberOfCycles(duration time.Duration, lastStartTime time.Time) int { + timeDifference := time.Now().Sub(lastStartTime) + println(timeDifference.String()) + println(float64(duration)) + numberOfCycles := int(timeDifference.Nanoseconds() / int64(duration)) + return numberOfCycles +} + func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateQuest) var totalCount, returnedCount uint16 @@ -219,14 +227,22 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { continue } + // Count the number of cycles necessary to align quest with actual time. + cycleCount := calculateNumberOfCycles(time.Duration(activeDuration+inactiveDuration)*24*time.Hour, startTime) + // Calculate the rotation time based on start time, active duration, and inactive duration - rotationTime := startTime.Add((time.Duration(activeDuration+inactiveDuration) * 24) * time.Hour) + rotationTime := startTime.Add(time.Duration(activeDuration+inactiveDuration) * 24 * time.Duration(cycleCount) * time.Hour) if currentTime.After(rotationTime) { - // The rotation time has passed, update the start time and reset the rotation - _, err := transaction.Exec("UPDATE event_quests SET start_time = $1 WHERE id = $2", rotationTime, id) + // take the rotationTime and normalize it to midnight + newRotationTime := time.Date(rotationTime.Year(), rotationTime.Month(), rotationTime.Day(), 0, 0, 0, 0, rotationTime.Location()) + newRotationTime = newRotationTime.Add(time.Duration(TimeMidnight().Add(13 * time.Hour).Nanosecond())) + + _, err := transaction.Exec("UPDATE event_quests SET start_time = $1 WHERE id = $2", newRotationTime, id) if err != nil { - continue + transaction.Rollback() // Rollback if an error occurs + break } + startTime = newRotationTime // Set the new start time so the quest can be used immediatelyw } // Check if the quest is currently active From fc57d6368973574a5fb0b8dae14dedb9de874d84 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 19 Nov 2023 00:35:22 +1100 Subject: [PATCH 105/269] update parsing of many packets --- .../mhfpacket/msg_mhf_acquire_guild_tresure.go | 4 ++-- network/mhfpacket/msg_mhf_acquire_title.go | 12 ++++++------ .../mhfpacket/msg_mhf_check_daily_cafepoint.go | 7 +++---- network/mhfpacket/msg_mhf_check_monthly_item.go | 5 +++-- network/mhfpacket/msg_mhf_check_weekly_stamp.go | 3 +-- network/mhfpacket/msg_mhf_create_guild.go | 7 ++----- .../mhfpacket/msg_mhf_displayed_achievement.go | 12 +++++------- network/mhfpacket/msg_mhf_enumerate_event.go | 14 +++++--------- network/mhfpacket/msg_mhf_enumerate_price.go | 14 ++++++-------- network/mhfpacket/msg_mhf_enumerate_ranking.go | 15 +++++++-------- .../mhfpacket/msg_mhf_enumerate_union_item.go | 5 ++--- network/mhfpacket/msg_mhf_get_achievement.go | 3 +-- network/mhfpacket/msg_mhf_get_rengoku_binary.go | 11 +++++------ network/mhfpacket/msg_mhf_info_festa.go | 17 +++++++++-------- network/mhfpacket/msg_mhf_list_member.go | 13 +++++++------ network/mhfpacket/msg_mhf_read_mail.go | 3 +-- network/mhfpacket/msg_mhf_release_event.go | 3 +-- network/mhfpacket/msg_mhf_update_cafepoint.go | 14 ++++++-------- network/mhfpacket/msg_mhf_update_guacot.go | 2 +- network/mhfpacket/msg_mhf_update_guild_icon.go | 12 +++++------- network/mhfpacket/msg_mhf_update_guild_item.go | 17 ++++++++--------- network/mhfpacket/msg_mhf_update_house.go | 8 ++++---- network/mhfpacket/msg_mhf_update_union_item.go | 15 +++++++-------- network/mhfpacket/msg_sys_get_file.go | 2 +- network/mhfpacket/msg_sys_hide_client.go | 15 +++++++-------- network/mhfpacket/msg_sys_issue_logkey.go | 13 ++++++------- network/mhfpacket/msg_sys_rights_reload.go | 14 +++++++------- network/mhfpacket/msg_sys_set_stage_pass.go | 13 ++++++------- network/mhfpacket/msg_sys_terminal_log.go | 16 +++++++--------- server/channelserver/handlers.go | 6 +++--- server/channelserver/handlers_guild.go | 8 ++++---- server/channelserver/handlers_house.go | 14 ++++++++------ 32 files changed, 146 insertions(+), 171 deletions(-) diff --git a/network/mhfpacket/msg_mhf_acquire_guild_tresure.go b/network/mhfpacket/msg_mhf_acquire_guild_tresure.go index 775e98bf5..ac9feb557 100644 --- a/network/mhfpacket/msg_mhf_acquire_guild_tresure.go +++ b/network/mhfpacket/msg_mhf_acquire_guild_tresure.go @@ -12,7 +12,7 @@ import ( type MsgMhfAcquireGuildTresure struct { AckHandle uint32 HuntID uint32 - Unk uint8 + Unk bool } // Opcode returns the ID associated with this packet type. @@ -24,7 +24,7 @@ func (m *MsgMhfAcquireGuildTresure) Opcode() network.PacketID { func (m *MsgMhfAcquireGuildTresure) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.HuntID = bf.ReadUint32() - m.Unk = bf.ReadUint8() + m.Unk = bf.ReadBool() return nil } diff --git a/network/mhfpacket/msg_mhf_acquire_title.go b/network/mhfpacket/msg_mhf_acquire_title.go index fe3a5ca95..9b9dd84dc 100644 --- a/network/mhfpacket/msg_mhf_acquire_title.go +++ b/network/mhfpacket/msg_mhf_acquire_title.go @@ -11,9 +11,7 @@ import ( // MsgMhfAcquireTitle represents the MSG_MHF_ACQUIRE_TITLE type MsgMhfAcquireTitle struct { AckHandle uint32 - Unk0 uint16 - Unk1 uint16 - TitleID uint16 + TitleIDs []uint16 } // Opcode returns the ID associated with this packet type. @@ -24,9 +22,11 @@ func (m *MsgMhfAcquireTitle) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfAcquireTitle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() - m.TitleID = bf.ReadUint16() + titles := int(bf.ReadUint16()) + bf.ReadUint16() // Zeroed + for i := 0; i < titles; i++ { + m.TitleIDs = append(m.TitleIDs, bf.ReadUint16()) + } return nil } diff --git a/network/mhfpacket/msg_mhf_check_daily_cafepoint.go b/network/mhfpacket/msg_mhf_check_daily_cafepoint.go index 6e4c26b97..fee1ec222 100644 --- a/network/mhfpacket/msg_mhf_check_daily_cafepoint.go +++ b/network/mhfpacket/msg_mhf_check_daily_cafepoint.go @@ -1,9 +1,10 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfCheckDailyCafepoint represents the MSG_MHF_CHECK_DAILY_CAFEPOINT @@ -25,7 +26,5 @@ func (m *MsgMhfCheckDailyCafepoint) Parse(bf *byteframe.ByteFrame, ctx *clientct } func (m *MsgMhfCheckDailyCafepoint) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint32(m.AckHandle) - bf.WriteUint32(m.Unk) - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/network/mhfpacket/msg_mhf_check_monthly_item.go b/network/mhfpacket/msg_mhf_check_monthly_item.go index 860725aa4..257e0f855 100644 --- a/network/mhfpacket/msg_mhf_check_monthly_item.go +++ b/network/mhfpacket/msg_mhf_check_monthly_item.go @@ -12,7 +12,6 @@ import ( type MsgMhfCheckMonthlyItem struct { AckHandle uint32 Type uint8 - Unk []byte } // Opcode returns the ID associated with this packet type. @@ -24,7 +23,9 @@ func (m *MsgMhfCheckMonthlyItem) Opcode() network.PacketID { func (m *MsgMhfCheckMonthlyItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Type = bf.ReadUint8() - m.Unk = bf.ReadBytes(3) + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_check_weekly_stamp.go b/network/mhfpacket/msg_mhf_check_weekly_stamp.go index f03b1d1e7..069a17456 100644 --- a/network/mhfpacket/msg_mhf_check_weekly_stamp.go +++ b/network/mhfpacket/msg_mhf_check_weekly_stamp.go @@ -12,7 +12,6 @@ type MsgMhfCheckWeeklyStamp struct { AckHandle uint32 StampType string Unk1 bool - Unk2 uint16 // Hardcoded 0 in the binary } // Opcode returns the ID associated with this packet type. @@ -31,7 +30,7 @@ func (m *MsgMhfCheckWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.C m.StampType = "ex" } m.Unk1 = bf.ReadBool() - m.Unk2 = bf.ReadUint16() + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_create_guild.go b/network/mhfpacket/msg_mhf_create_guild.go index e37267885..e82f7157e 100644 --- a/network/mhfpacket/msg_mhf_create_guild.go +++ b/network/mhfpacket/msg_mhf_create_guild.go @@ -12,8 +12,6 @@ import ( // MsgMhfCreateGuild represents the MSG_MHF_CREATE_GUILD type MsgMhfCreateGuild struct { AckHandle uint32 - Unk0 uint8 - Unk1 uint8 Name string } @@ -25,9 +23,8 @@ func (m *MsgMhfCreateGuild) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfCreateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() - m.Unk1 = bf.ReadUint8() - _ = bf.ReadUint16() // len + bf.ReadUint16() // Zeroed + bf.ReadUint16() // Name length m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_mhf_displayed_achievement.go b/network/mhfpacket/msg_mhf_displayed_achievement.go index 03de31f39..2633c081e 100644 --- a/network/mhfpacket/msg_mhf_displayed_achievement.go +++ b/network/mhfpacket/msg_mhf_displayed_achievement.go @@ -1,15 +1,14 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfDisplayedAchievement represents the MSG_MHF_DISPLAYED_ACHIEVEMENT -type MsgMhfDisplayedAchievement struct { - Unk0 uint8 -} +type MsgMhfDisplayedAchievement struct{} // Opcode returns the ID associated with this packet type. func (m *MsgMhfDisplayedAchievement) Opcode() network.PacketID { @@ -18,12 +17,11 @@ func (m *MsgMhfDisplayedAchievement) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfDisplayedAchievement) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.Unk0 = bf.ReadUint8() + bf.ReadUint8() // Zeroed return nil } // Build builds a binary packet from the current data. func (m *MsgMhfDisplayedAchievement) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint8(m.Unk0) - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/network/mhfpacket/msg_mhf_enumerate_event.go b/network/mhfpacket/msg_mhf_enumerate_event.go index d73f92cdc..6a863f92f 100644 --- a/network/mhfpacket/msg_mhf_enumerate_event.go +++ b/network/mhfpacket/msg_mhf_enumerate_event.go @@ -1,16 +1,15 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfEnumerateEvent represents the MSG_MHF_ENUMERATE_EVENT type MsgMhfEnumerateEvent struct { AckHandle uint32 - Unk0 uint16 // Hardcoded 0 in the binary - Unk1 uint16 // Hardcoded 0 in the binary } // Opcode returns the ID associated with this packet type. @@ -21,15 +20,12 @@ func (m *MsgMhfEnumerateEvent) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateEvent) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() + bf.ReadUint16() // Zeroed + bf.ReadUint16() // Zeroed return nil } // Build builds a binary packet from the current data. func (m *MsgMhfEnumerateEvent) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint32(m.AckHandle) - bf.WriteUint16(m.Unk0) - bf.WriteUint16(m.Unk1) - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/network/mhfpacket/msg_mhf_enumerate_price.go b/network/mhfpacket/msg_mhf_enumerate_price.go index 5dcfa69f3..e36246364 100644 --- a/network/mhfpacket/msg_mhf_enumerate_price.go +++ b/network/mhfpacket/msg_mhf_enumerate_price.go @@ -1,18 +1,16 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfEnumeratePrice represents the MSG_MHF_ENUMERATE_PRICE type MsgMhfEnumeratePrice struct { AckHandle uint32 - Unk0 uint16 // Hardcoded 0 in the binary - Unk1 uint16 // Hardcoded 0 in the binary } // Opcode returns the ID associated with this packet type. @@ -23,8 +21,8 @@ func (m *MsgMhfEnumeratePrice) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumeratePrice) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() + bf.ReadUint16() // Zeroed + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_enumerate_ranking.go b/network/mhfpacket/msg_mhf_enumerate_ranking.go index 4baef5738..a891a0e12 100644 --- a/network/mhfpacket/msg_mhf_enumerate_ranking.go +++ b/network/mhfpacket/msg_mhf_enumerate_ranking.go @@ -1,18 +1,16 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfEnumerateRanking represents the MSG_MHF_ENUMERATE_RANKING type MsgMhfEnumerateRanking struct { AckHandle uint32 - Unk0 uint16 // Hardcoded 0 in the binary - Unk1 uint16 // Hardcoded 0 in the binary } // Opcode returns the ID associated with this packet type. @@ -23,8 +21,9 @@ func (m *MsgMhfEnumerateRanking) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateRanking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() + bf.ReadUint16() // Zeroed + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_enumerate_union_item.go b/network/mhfpacket/msg_mhf_enumerate_union_item.go index 780539b12..38ff89f5c 100644 --- a/network/mhfpacket/msg_mhf_enumerate_union_item.go +++ b/network/mhfpacket/msg_mhf_enumerate_union_item.go @@ -11,7 +11,6 @@ import ( // MsgMhfEnumerateUnionItem represents the MSG_MHF_ENUMERATE_UNION_ITEM type MsgMhfEnumerateUnionItem struct { AckHandle uint32 - Unk0 uint16 } // Opcode returns the ID associated with this packet type. @@ -22,8 +21,8 @@ func (m *MsgMhfEnumerateUnionItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateUnionItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_get_achievement.go b/network/mhfpacket/msg_mhf_get_achievement.go index afa49d0d4..bca41bb7b 100644 --- a/network/mhfpacket/msg_mhf_get_achievement.go +++ b/network/mhfpacket/msg_mhf_get_achievement.go @@ -12,7 +12,6 @@ import ( type MsgMhfGetAchievement struct { AckHandle uint32 CharID uint32 - Unk1 uint32 // char? } // Opcode returns the ID associated with this packet type. @@ -24,7 +23,7 @@ func (m *MsgMhfGetAchievement) Opcode() network.PacketID { func (m *MsgMhfGetAchievement) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.CharID = bf.ReadUint32() - m.Unk1 = bf.ReadUint32() + bf.ReadUint32() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_get_rengoku_binary.go b/network/mhfpacket/msg_mhf_get_rengoku_binary.go index fc43a3718..f7dda97ad 100644 --- a/network/mhfpacket/msg_mhf_get_rengoku_binary.go +++ b/network/mhfpacket/msg_mhf_get_rengoku_binary.go @@ -1,17 +1,16 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfGetRengokuBinary represents the MSG_MHF_GET_RENGOKU_BINARY type MsgMhfGetRengokuBinary struct { AckHandle uint32 - Unk0 uint8 // Hardcoded 0 in binary } // Opcode returns the ID associated with this packet type. @@ -22,7 +21,7 @@ func (m *MsgMhfGetRengokuBinary) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfGetRengokuBinary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() + bf.ReadUint8() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_info_festa.go b/network/mhfpacket/msg_mhf_info_festa.go index 6926f0b8d..0877e6a4c 100644 --- a/network/mhfpacket/msg_mhf_info_festa.go +++ b/network/mhfpacket/msg_mhf_info_festa.go @@ -1,18 +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" ) // MsgMhfInfoFesta represents the MSG_MHF_INFO_FESTA type MsgMhfInfoFesta struct { AckHandle uint32 - Unk0 uint16 // Hardcoded 0 in the binary - Unk1 uint16 // Hardcoded 0 in the binary + Unk0 uint8 } // Opcode returns the ID associated with this packet type. @@ -23,8 +22,10 @@ func (m *MsgMhfInfoFesta) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfInfoFesta) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() + m.Unk0 = bf.ReadUint8() + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_list_member.go b/network/mhfpacket/msg_mhf_list_member.go index 0eaf4ca5f..bee4a4874 100644 --- a/network/mhfpacket/msg_mhf_list_member.go +++ b/network/mhfpacket/msg_mhf_list_member.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" ) // MsgMhfListMember represents the MSG_MHF_LIST_MEMBER type MsgMhfListMember struct { AckHandle uint32 - Unk0 uint16 // Hardcoded 01 00 in the JP client. + Unk0 uint8 // Hardcoded 01 in the JP client. } // Opcode returns the ID associated with this packet type. @@ -22,7 +22,8 @@ func (m *MsgMhfListMember) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfListMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() + m.Unk0 = bf.ReadUint8() + bf.ReadUint8() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_read_mail.go b/network/mhfpacket/msg_mhf_read_mail.go index 1d2b03cf5..957f144f9 100644 --- a/network/mhfpacket/msg_mhf_read_mail.go +++ b/network/mhfpacket/msg_mhf_read_mail.go @@ -19,7 +19,6 @@ type MsgMhfReadMail struct { // This is the index within the current mail list Index uint8 - Unk0 uint16 } // Opcode returns the ID associated with this packet type. @@ -32,7 +31,7 @@ func (m *MsgMhfReadMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon m.AckHandle = bf.ReadUint32() m.AccIndex = bf.ReadUint8() m.Index = bf.ReadUint8() - m.Unk0 = bf.ReadUint16() + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_release_event.go b/network/mhfpacket/msg_mhf_release_event.go index 52178279b..20ebcdba0 100644 --- a/network/mhfpacket/msg_mhf_release_event.go +++ b/network/mhfpacket/msg_mhf_release_event.go @@ -12,7 +12,6 @@ import ( type MsgMhfReleaseEvent struct { AckHandle uint32 RaviID uint32 - Unk1 uint32 } // Opcode returns the ID associated with this packet type. @@ -24,7 +23,7 @@ func (m *MsgMhfReleaseEvent) Opcode() network.PacketID { func (m *MsgMhfReleaseEvent) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.RaviID = bf.ReadUint32() - m.Unk1 = bf.ReadUint32() + bf.ReadUint32() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_update_cafepoint.go b/network/mhfpacket/msg_mhf_update_cafepoint.go index 671e893aa..aea9a43aa 100644 --- a/network/mhfpacket/msg_mhf_update_cafepoint.go +++ b/network/mhfpacket/msg_mhf_update_cafepoint.go @@ -1,18 +1,16 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfUpdateCafepoint represents the MSG_MHF_UPDATE_CAFEPOINT type MsgMhfUpdateCafepoint struct { AckHandle uint32 - Unk0 uint16 // Hardcoded 0 in binary - Unk1 uint16 // Hardcoded 0 in binary } // Opcode returns the ID associated with this packet type. @@ -23,8 +21,8 @@ func (m *MsgMhfUpdateCafepoint) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUpdateCafepoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() + bf.ReadUint16() // Zeroed + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_update_guacot.go b/network/mhfpacket/msg_mhf_update_guacot.go index 433854ae3..685f66f1b 100644 --- a/network/mhfpacket/msg_mhf_update_guacot.go +++ b/network/mhfpacket/msg_mhf_update_guacot.go @@ -30,7 +30,7 @@ func (m *MsgMhfUpdateGuacot) Opcode() network.PacketID { func (m *MsgMhfUpdateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.EntryCount = bf.ReadUint16() - _ = bf.ReadUint16() // Zero + bf.ReadUint16() // Zeroed var temp Goocoo for i := 0; i < int(m.EntryCount); i++ { temp.Index = bf.ReadUint32() diff --git a/network/mhfpacket/msg_mhf_update_guild_icon.go b/network/mhfpacket/msg_mhf_update_guild_icon.go index 6e3a780e5..248bb93ea 100644 --- a/network/mhfpacket/msg_mhf_update_guild_icon.go +++ b/network/mhfpacket/msg_mhf_update_guild_icon.go @@ -25,8 +25,6 @@ type GuildIconMsgPart struct { type MsgMhfUpdateGuildIcon struct { AckHandle uint32 GuildID uint32 - PartCount uint16 - Unk1 uint16 IconParts []GuildIconMsgPart } @@ -39,12 +37,12 @@ func (m *MsgMhfUpdateGuildIcon) Opcode() network.PacketID { func (m *MsgMhfUpdateGuildIcon) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.GuildID = bf.ReadUint32() - m.PartCount = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() + partCount := int(bf.ReadUint16()) + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed + m.IconParts = make([]GuildIconMsgPart, partCount) - m.IconParts = make([]GuildIconMsgPart, m.PartCount) - - for i := 0; i < int(m.PartCount); i++ { + for i := 0; i < partCount; i++ { m.IconParts[i] = GuildIconMsgPart{ Index: bf.ReadUint16(), ID: bf.ReadUint16(), diff --git a/network/mhfpacket/msg_mhf_update_guild_item.go b/network/mhfpacket/msg_mhf_update_guild_item.go index 3eb37a8cb..dbea591ea 100644 --- a/network/mhfpacket/msg_mhf_update_guild_item.go +++ b/network/mhfpacket/msg_mhf_update_guild_item.go @@ -10,7 +10,7 @@ import ( type Item struct { Unk0 uint32 - ItemId uint16 + ItemID uint16 Amount uint16 Unk1 uint32 } @@ -19,9 +19,7 @@ type Item struct { type MsgMhfUpdateGuildItem struct { AckHandle uint32 GuildId uint32 - Amount uint16 - Unk1 uint16 // 0x00 0x00 - Items []Item // Array of updated item IDs + Items []Item } // Opcode returns the ID associated with this packet type. @@ -33,13 +31,14 @@ func (m *MsgMhfUpdateGuildItem) Opcode() network.PacketID { func (m *MsgMhfUpdateGuildItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.GuildId = bf.ReadUint32() - m.Amount = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() - m.Items = make([]Item, int(m.Amount)) + itemCount := int(bf.ReadUint16()) + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed + m.Items = make([]Item, itemCount) - for i := 0; i < int(m.Amount); i++ { + for i := 0; i < itemCount; i++ { m.Items[i].Unk0 = bf.ReadUint32() - m.Items[i].ItemId = bf.ReadUint16() + m.Items[i].ItemID = bf.ReadUint16() m.Items[i].Amount = bf.ReadUint16() m.Items[i].Unk1 = bf.ReadUint32() } diff --git a/network/mhfpacket/msg_mhf_update_house.go b/network/mhfpacket/msg_mhf_update_house.go index 320972673..2c6f0401d 100644 --- a/network/mhfpacket/msg_mhf_update_house.go +++ b/network/mhfpacket/msg_mhf_update_house.go @@ -13,8 +13,7 @@ import ( type MsgMhfUpdateHouse struct { AckHandle uint32 State uint8 - Unk1 uint8 // Always 0x01 - Unk2 uint16 // Always 0x0000 + Unk1 uint8 // Always 0x01 Password string } @@ -28,8 +27,9 @@ func (m *MsgMhfUpdateHouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client m.AckHandle = bf.ReadUint32() m.State = bf.ReadUint8() m.Unk1 = bf.ReadUint8() - m.Unk2 = bf.ReadUint16() - _ = bf.ReadUint8() // Password length + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Password length m.Password = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_mhf_update_union_item.go b/network/mhfpacket/msg_mhf_update_union_item.go index 5e4d83d24..68e8de365 100644 --- a/network/mhfpacket/msg_mhf_update_union_item.go +++ b/network/mhfpacket/msg_mhf_update_union_item.go @@ -11,9 +11,7 @@ import ( // MsgMhfUpdateUnionItem represents the MSG_MHF_UPDATE_UNION_ITEM type MsgMhfUpdateUnionItem struct { AckHandle uint32 - Amount uint16 - Unk1 uint16 // 0x00 0x00 - Items []Item // Array of updated item IDs + Items []Item } // Opcode returns the ID associated with this packet type. @@ -24,13 +22,14 @@ func (m *MsgMhfUpdateUnionItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUpdateUnionItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Amount = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() - m.Items = make([]Item, int(m.Amount)) + itemCount := int(bf.ReadUint16()) + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed + m.Items = make([]Item, itemCount) - for i := 0; i < int(m.Amount); i++ { + for i := 0; i < itemCount; i++ { m.Items[i].Unk0 = bf.ReadUint32() - m.Items[i].ItemId = bf.ReadUint16() + m.Items[i].ItemID = bf.ReadUint16() m.Items[i].Amount = bf.ReadUint16() m.Items[i].Unk1 = bf.ReadUint32() } diff --git a/network/mhfpacket/msg_sys_get_file.go b/network/mhfpacket/msg_sys_get_file.go index 41b2a9029..99af6aa5e 100644 --- a/network/mhfpacket/msg_sys_get_file.go +++ b/network/mhfpacket/msg_sys_get_file.go @@ -4,9 +4,9 @@ import ( "errors" "erupe-ce/common/bfutil" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) type scenarioFileIdentifer struct { diff --git a/network/mhfpacket/msg_sys_hide_client.go b/network/mhfpacket/msg_sys_hide_client.go index e01c22cab..a2c714a41 100644 --- a/network/mhfpacket/msg_sys_hide_client.go +++ b/network/mhfpacket/msg_sys_hide_client.go @@ -1,18 +1,16 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgSysHideClient represents the MSG_SYS_HIDE_CLIENT type MsgSysHideClient struct { Hide bool - Unk0 uint16 // Hardcoded 0 in binary - Unk1 uint8 // Hardcoded 0 in binary } // Opcode returns the ID associated with this packet type. @@ -23,8 +21,9 @@ func (m *MsgSysHideClient) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysHideClient) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.Hide = bf.ReadBool() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint8() + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed return nil } diff --git a/network/mhfpacket/msg_sys_issue_logkey.go b/network/mhfpacket/msg_sys_issue_logkey.go index a22956186..d5bb6522d 100644 --- a/network/mhfpacket/msg_sys_issue_logkey.go +++ b/network/mhfpacket/msg_sys_issue_logkey.go @@ -1,18 +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" ) // MsgSysIssueLogkey represents the MSG_SYS_ISSUE_LOGKEY type MsgSysIssueLogkey struct { AckHandle uint32 - Unk0 uint16 // Hardcoded 00 01 in binary - Unk1 uint16 // Hardcoded 0 in binary. + Unk0 uint16 } // Opcode returns the ID associated with this packet type. @@ -24,7 +23,7 @@ func (m *MsgSysIssueLogkey) Opcode() network.PacketID { func (m *MsgSysIssueLogkey) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_sys_rights_reload.go b/network/mhfpacket/msg_sys_rights_reload.go index 7a8ac06e0..a70be8f38 100644 --- a/network/mhfpacket/msg_sys_rights_reload.go +++ b/network/mhfpacket/msg_sys_rights_reload.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" ) // MsgSysRightsReload represents the MSG_SYS_RIGHTS_RELOAD -type MsgSysRightsReload struct{ +type MsgSysRightsReload struct { AckHandle uint32 - Unk0 byte + Unk0 []byte } // Opcode returns the ID associated with this packet type. @@ -22,7 +22,7 @@ func (m *MsgSysRightsReload) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysRightsReload) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() + m.Unk0 = bf.ReadBytes(uint(bf.ReadUint8())) return nil } diff --git a/network/mhfpacket/msg_sys_set_stage_pass.go b/network/mhfpacket/msg_sys_set_stage_pass.go index 5cdbb2b88..1461241b0 100644 --- a/network/mhfpacket/msg_sys_set_stage_pass.go +++ b/network/mhfpacket/msg_sys_set_stage_pass.go @@ -1,17 +1,16 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgSysSetStagePass represents the MSG_SYS_SET_STAGE_PASS type MsgSysSetStagePass struct { - Unk0 uint8 // Hardcoded 0 in the binary - Password string // NULL-terminated string + Password string // NULL-terminated string } // Opcode returns the ID associated with this packet type. @@ -21,8 +20,8 @@ func (m *MsgSysSetStagePass) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysSetStagePass) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.Unk0 = bf.ReadUint8() - _ = bf.ReadUint8() // Password length + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Password length m.Password = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_terminal_log.go b/network/mhfpacket/msg_sys_terminal_log.go index bc8f3f7c5..5033346b4 100644 --- a/network/mhfpacket/msg_sys_terminal_log.go +++ b/network/mhfpacket/msg_sys_terminal_log.go @@ -23,11 +23,9 @@ type TerminalLogEntry struct { // MsgSysTerminalLog represents the MSG_SYS_TERMINAL_LOG type MsgSysTerminalLog struct { - AckHandle uint32 - LogID uint32 // 0 on the first packet, and the server sends back a value to use for subsequent requests. - EntryCount uint16 - Unk0 uint16 // Hardcoded 0 in the binary - Entries []*TerminalLogEntry + AckHandle uint32 + LogID uint32 // 0 on the first packet, and the server sends back a value to use for subsequent requests. + Entries []TerminalLogEntry } // Opcode returns the ID associated with this packet type. @@ -39,11 +37,11 @@ func (m *MsgSysTerminalLog) Opcode() network.PacketID { func (m *MsgSysTerminalLog) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.LogID = bf.ReadUint32() - m.EntryCount = bf.ReadUint16() - m.Unk0 = bf.ReadUint16() + entryCount := int(bf.ReadUint16()) + bf.ReadUint16() // Zeroed - for i := 0; i < int(m.EntryCount); i++ { - e := &TerminalLogEntry{} + var e TerminalLogEntry + for i := 0; i < entryCount; i++ { e.Index = bf.ReadUint32() e.Type1 = bf.ReadUint8() e.Type2 = bf.ReadUint8() diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 211fcaf79..057881723 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -705,16 +705,16 @@ func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) { // Update item stacks newItems := make([]Item, len(oldItems)) copy(newItems, oldItems) - for i := 0; i < int(pkt.Amount); i++ { + for i := 0; i < len(pkt.Items); i++ { for j := 0; j <= len(oldItems); j++ { if j == len(oldItems) { var newItem Item - newItem.ItemId = pkt.Items[i].ItemId + newItem.ItemId = pkt.Items[i].ItemID newItem.Amount = pkt.Items[i].Amount newItems = append(newItems, newItem) break } - if pkt.Items[i].ItemId == oldItems[j].ItemId { + if pkt.Items[i].ItemID == oldItems[j].ItemId { newItems[j].Amount = pkt.Items[i].Amount break } diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index b8c7c04f6..cf190aa45 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1609,16 +1609,16 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) { // Update item stacks newItems := make([]Item, len(oldItems)) copy(newItems, oldItems) - for i := 0; i < int(pkt.Amount); i++ { + for i := 0; i < len(pkt.Items); i++ { for j := 0; j <= len(oldItems); j++ { if j == len(oldItems) { var newItem Item - newItem.ItemId = pkt.Items[i].ItemId + newItem.ItemId = pkt.Items[i].ItemID newItem.Amount = pkt.Items[i].Amount newItems = append(newItems, newItem) break } - if pkt.Items[i].ItemId == oldItems[j].ItemId { + if pkt.Items[i].ItemID == oldItems[j].ItemId { newItems[j].Amount = pkt.Items[i].Amount break } @@ -1677,7 +1677,7 @@ func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) { icon := &GuildIcon{} - icon.Parts = make([]GuildIconPart, pkt.PartCount) + icon.Parts = make([]GuildIconPart, len(pkt.IconParts)) for i, p := range pkt.IconParts { icon.Parts[i] = GuildIconPart{ diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index f764a8fa1..9dc51995d 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -355,12 +355,14 @@ func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfAcquireTitle) - var exists int - err := s.server.db.QueryRow(`SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2`, pkt.TitleID, s.charID).Scan(&exists) - if err != nil || exists == 0 { - s.server.db.Exec(`INSERT INTO titles VALUES ($1, $2, now(), now())`, pkt.TitleID, s.charID) - } else { - s.server.db.Exec(`UPDATE titles SET updated_at=now() WHERE id=$1 AND char_id=$2`, pkt.TitleID, s.charID) + for _, title := range pkt.TitleIDs { + var exists int + err := s.server.db.QueryRow(`SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2`, title, s.charID).Scan(&exists) + if err != nil || exists == 0 { + s.server.db.Exec(`INSERT INTO titles VALUES ($1, $2, now(), now())`, title, s.charID) + } else { + s.server.db.Exec(`UPDATE titles SET updated_at=now() WHERE id=$1 AND char_id=$2`, title, s.charID) + } } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } From 85fc76edd5f977c176497202cbc48cf5999cf67b Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 19 Nov 2023 02:34:02 +1100 Subject: [PATCH 106/269] update parsing of many packets --- network/mhfpacket/msg_mhf_acquire_festa.go | 25 +++---- .../mhfpacket/msg_mhf_acquire_monthly_item.go | 13 ++-- network/mhfpacket/msg_mhf_announce.go | 5 +- .../mhfpacket/msg_mhf_arrange_guild_member.go | 19 ++---- network/mhfpacket/msg_mhf_create_joint.go | 25 +++---- network/mhfpacket/msg_mhf_entry_festa.go | 22 +++--- .../msg_mhf_enumerate_festa_member.go | 18 ++--- network/mhfpacket/msg_mhf_enumerate_guacot.go | 5 +- network/mhfpacket/msg_mhf_enumerate_guild.go | 18 +++-- .../mhfpacket/msg_mhf_enumerate_guild_item.go | 16 ++--- .../msg_mhf_enumerate_guild_member.go | 21 +++--- network/mhfpacket/msg_mhf_enumerate_house.go | 3 +- .../mhfpacket/msg_mhf_enumerate_inv_guild.go | 16 ++++- .../msg_mhf_exchange_weekly_stamp.go | 3 +- network/mhfpacket/msg_mhf_info_joint.go | 3 +- .../mhfpacket/msg_mhf_load_guild_cooking.go | 14 ++-- network/mhfpacket/msg_mhf_load_house.go | 5 +- .../mhfpacket/msg_mhf_operate_guild_member.go | 4 +- network/mhfpacket/msg_mhf_operate_joint.go | 8 ++- network/mhfpacket/msg_mhf_opr_member.go | 31 +++++---- network/mhfpacket/msg_mhf_register_event.go | 7 +- .../msg_mhf_set_guild_manage_right.go | 3 +- network/mhfpacket/msg_mhf_stampcard_stamp.go | 2 +- network/mhfpacket/msg_mhf_state_festa_g.go | 12 ++-- network/mhfpacket/msg_mhf_state_festa_u.go | 22 +++--- network/mhfpacket/msg_mhf_transfer_item.go | 15 +++-- network/mhfpacket/msg_mhf_transit_message.go | 3 +- .../mhfpacket/msg_mhf_update_guild_item.go | 4 +- network/mhfpacket/msg_mhf_use_gacha_point.go | 22 +++--- network/mhfpacket/msg_mhf_vote_festa.go | 4 +- .../msg_sys_create_acquire_semaphore.go | 5 +- network/mhfpacket/msg_sys_create_semaphore.go | 16 +++-- network/mhfpacket/msg_sys_enumerate_client.go | 5 +- network/mhfpacket/msg_sys_get_stage_binary.go | 5 +- network/mhfpacket/msg_sys_load_register.go | 4 +- network/mhfpacket/msg_sys_login.go | 26 ++++--- network/mhfpacket/msg_sys_operate_register.go | 2 +- network/mhfpacket/msg_sys_set_stage_binary.go | 7 +- .../mhfpacket/msg_sys_wait_stage_binary.go | 5 +- server/channelserver/handlers.go | 2 +- server/channelserver/handlers_clients.go | 67 +++++++++---------- server/channelserver/handlers_guild.go | 33 +++++---- .../channelserver/handlers_guild_alliance.go | 3 +- server/channelserver/handlers_register.go | 2 +- 44 files changed, 272 insertions(+), 278 deletions(-) diff --git a/network/mhfpacket/msg_mhf_acquire_festa.go b/network/mhfpacket/msg_mhf_acquire_festa.go index 30e75d7a4..87cf758f0 100644 --- a/network/mhfpacket/msg_mhf_acquire_festa.go +++ b/network/mhfpacket/msg_mhf_acquire_festa.go @@ -1,19 +1,19 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfAcquireFesta represents the MSG_MHF_ACQUIRE_FESTA type MsgMhfAcquireFesta struct { - AckHandle uint32 - FestaID uint32 - GuildID uint32 - Unk uint16 + AckHandle uint32 + FestaID uint32 + GuildID uint32 + Unk uint8 } // Opcode returns the ID associated with this packet type. @@ -23,11 +23,12 @@ func (m *MsgMhfAcquireFesta) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfAcquireFesta) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.FestaID = bf.ReadUint32() - m.GuildID = bf.ReadUint32() - m.Unk = bf.ReadUint16() - return nil + m.AckHandle = bf.ReadUint32() + m.FestaID = bf.ReadUint32() + m.GuildID = bf.ReadUint32() + m.Unk = bf.ReadUint8() + bf.ReadUint8() // Zeroed + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_acquire_monthly_item.go b/network/mhfpacket/msg_mhf_acquire_monthly_item.go index acc10b42a..d64288f9f 100644 --- a/network/mhfpacket/msg_mhf_acquire_monthly_item.go +++ b/network/mhfpacket/msg_mhf_acquire_monthly_item.go @@ -11,9 +11,9 @@ import ( // MsgMhfAcquireMonthlyItem represents the MSG_MHF_ACQUIRE_MONTHLY_ITEM type MsgMhfAcquireMonthlyItem struct { AckHandle uint32 - Unk0 uint16 - Unk1 uint16 - Unk2 uint32 + Unk0 uint8 + Unk1 uint8 + Unk2 uint16 Unk3 uint32 } @@ -25,10 +25,11 @@ func (m *MsgMhfAcquireMonthlyItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfAcquireMonthlyItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() - m.Unk2 = bf.ReadUint32() + m.Unk0 = bf.ReadUint8() + m.Unk1 = bf.ReadUint8() + m.Unk2 = bf.ReadUint16() m.Unk3 = bf.ReadUint32() + bf.ReadUint32() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_announce.go b/network/mhfpacket/msg_mhf_announce.go index c4c9deb7f..2b1d9ea76 100644 --- a/network/mhfpacket/msg_mhf_announce.go +++ b/network/mhfpacket/msg_mhf_announce.go @@ -14,7 +14,7 @@ type MsgMhfAnnounce struct { IPAddress uint32 Port uint16 StageID []byte - Type uint8 + Data *byteframe.ByteFrame } // Opcode returns the ID associated with this packet type. @@ -31,8 +31,7 @@ func (m *MsgMhfAnnounce) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon _ = bf.ReadUint8() _ = bf.ReadUint8() m.StageID = bf.ReadBytes(32) - _ = bf.ReadUint32() - m.Type = bf.ReadUint8() + m.Data = byteframe.NewByteFrameFromBytes(bf.ReadBytes(uint(bf.ReadUint32()))) return nil } diff --git a/network/mhfpacket/msg_mhf_arrange_guild_member.go b/network/mhfpacket/msg_mhf_arrange_guild_member.go index 3bd4e73f0..066735a10 100644 --- a/network/mhfpacket/msg_mhf_arrange_guild_member.go +++ b/network/mhfpacket/msg_mhf_arrange_guild_member.go @@ -1,9 +1,10 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfArrangeGuildMember represents the MSG_MHF_ARRANGE_GUILD_MEMBER @@ -22,11 +23,11 @@ func (m *MsgMhfArrangeGuildMember) Opcode() network.PacketID { func (m *MsgMhfArrangeGuildMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.GuildID = bf.ReadUint32() - charCount := bf.ReadUint16() - + bf.ReadUint8() // Zeroed + charCount := int(bf.ReadUint8()) m.CharIDs = make([]uint32, charCount) - for i := uint16(0); i < charCount; i++ { + for i := 0; i < charCount; i++ { m.CharIDs[i] = bf.ReadUint32() } @@ -35,13 +36,5 @@ func (m *MsgMhfArrangeGuildMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx // Build builds a binary packet from the current data. func (m *MsgMhfArrangeGuildMember) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint32(m.AckHandle) - bf.WriteUint32(m.GuildID) - bf.WriteUint16(uint16(len(m.CharIDs))) - - for _, charID := range m.CharIDs { - bf.WriteUint32(charID) - } - - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/network/mhfpacket/msg_mhf_create_joint.go b/network/mhfpacket/msg_mhf_create_joint.go index 1a969fc4a..5a9a9f5fd 100644 --- a/network/mhfpacket/msg_mhf_create_joint.go +++ b/network/mhfpacket/msg_mhf_create_joint.go @@ -1,19 +1,19 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/common/stringsupport" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/common/stringsupport" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfCreateJoint represents the MSG_MHF_CREATE_JOINT type MsgMhfCreateJoint struct { - AckHandle uint32 - GuildID uint32 - Name string + AckHandle uint32 + GuildID uint32 + Name string } // Opcode returns the ID associated with this packet type. @@ -23,11 +23,12 @@ func (m *MsgMhfCreateJoint) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfCreateJoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.GuildID = bf.ReadUint32() - _ = bf.ReadUint32() // len - m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) - return nil + m.AckHandle = bf.ReadUint32() + m.GuildID = bf.ReadUint32() + bf.ReadUint16() // Zeroed + bf.ReadUint16() // Name length + m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_entry_festa.go b/network/mhfpacket/msg_mhf_entry_festa.go index 9572cede6..de46b2d0e 100644 --- a/network/mhfpacket/msg_mhf_entry_festa.go +++ b/network/mhfpacket/msg_mhf_entry_festa.go @@ -1,18 +1,18 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfEntryFesta represents the MSG_MHF_ENTRY_FESTA type MsgMhfEntryFesta struct { - AckHandle uint32 - FestaID uint32 - GuildID uint32 + AckHandle uint32 + FestaID uint32 + GuildID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,11 +22,11 @@ func (m *MsgMhfEntryFesta) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEntryFesta) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.FestaID = bf.ReadUint32() - m.GuildID = bf.ReadUint32() - _ = bf.ReadUint16() // Always 0 - return nil + m.AckHandle = bf.ReadUint32() + m.FestaID = bf.ReadUint32() + m.GuildID = bf.ReadUint32() + bf.ReadUint16() // Zeroed + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_enumerate_festa_member.go b/network/mhfpacket/msg_mhf_enumerate_festa_member.go index f0b5cc478..4b90589a9 100644 --- a/network/mhfpacket/msg_mhf_enumerate_festa_member.go +++ b/network/mhfpacket/msg_mhf_enumerate_festa_member.go @@ -1,18 +1,18 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfEnumerateFestaMember represents the MSG_MHF_ENUMERATE_FESTA_MEMBER type MsgMhfEnumerateFestaMember struct { - AckHandle uint32 - FestaID uint32 - GuildID uint32 + AckHandle uint32 + FestaID uint32 + GuildID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,11 +22,11 @@ func (m *MsgMhfEnumerateFestaMember) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateFestaMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() + m.AckHandle = bf.ReadUint32() m.FestaID = bf.ReadUint32() m.GuildID = bf.ReadUint32() - _ = bf.ReadUint16() // Hardcoded 0 in the binary. - return nil + bf.ReadUint16() // Zeroed + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_enumerate_guacot.go b/network/mhfpacket/msg_mhf_enumerate_guacot.go index 2f970f938..71cae7adf 100644 --- a/network/mhfpacket/msg_mhf_enumerate_guacot.go +++ b/network/mhfpacket/msg_mhf_enumerate_guacot.go @@ -10,8 +10,7 @@ import ( // MsgMhfEnumerateGuacot represents the MSG_MHF_ENUMERATE_GUACOT type MsgMhfEnumerateGuacot struct { AckHandle uint32 - Unk0 uint32 // Hardcoded 0 in binary - Unk1 uint16 // Hardcoded 0 in binary + Unk0 uint32 } // Opcode returns the ID associated with this packet type. @@ -23,7 +22,7 @@ func (m *MsgMhfEnumerateGuacot) Opcode() network.PacketID { func (m *MsgMhfEnumerateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint32() - m.Unk1 = bf.ReadUint16() + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_enumerate_guild.go b/network/mhfpacket/msg_mhf_enumerate_guild.go index f90a7cc7b..61ead7870 100644 --- a/network/mhfpacket/msg_mhf_enumerate_guild.go +++ b/network/mhfpacket/msg_mhf_enumerate_guild.go @@ -2,9 +2,7 @@ package mhfpacket import ( "errors" - "erupe-ce/common/bfutil" "erupe-ce/common/byteframe" - "erupe-ce/common/stringsupport" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -34,8 +32,8 @@ type MsgMhfEnumerateGuild struct { Type EnumerateGuildType Page uint8 Sorting bool - Data1 []byte - Data2 string + Data1 *byteframe.ByteFrame + Data2 *byteframe.ByteFrame } // Opcode returns the ID associated with this packet type. @@ -49,12 +47,12 @@ func (m *MsgMhfEnumerateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli m.Type = EnumerateGuildType(bf.ReadUint8()) m.Page = bf.ReadUint8() m.Sorting = bf.ReadBool() - _ = bf.ReadBytes(1) - m.Data1 = bf.ReadBytes(4) - _ = bf.ReadBytes(2) - lenData2 := uint(bf.ReadUint8()) - _ = bf.ReadBytes(1) - m.Data2 = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(lenData2))) + bf.ReadUint8() // Zeroed + m.Data1 = byteframe.NewByteFrameFromBytes(bf.ReadBytes(4)) + bf.ReadUint16() // Zeroed + dataLen := uint(bf.ReadUint8()) + bf.ReadUint8() // Zeroed + m.Data2 = byteframe.NewByteFrameFromBytes(bf.ReadBytes(dataLen)) return nil } diff --git a/network/mhfpacket/msg_mhf_enumerate_guild_item.go b/network/mhfpacket/msg_mhf_enumerate_guild_item.go index 4f538ca5d..6d3516371 100644 --- a/network/mhfpacket/msg_mhf_enumerate_guild_item.go +++ b/network/mhfpacket/msg_mhf_enumerate_guild_item.go @@ -1,18 +1,17 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfEnumerateGuildItem represents the MSG_MHF_ENUMERATE_GUILD_ITEM type MsgMhfEnumerateGuildItem struct { AckHandle uint32 - GuildId uint32 - Unk0 uint16 + GuildID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,9 +21,10 @@ func (m *MsgMhfEnumerateGuildItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateGuildItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.GuildId = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() + m.AckHandle = bf.ReadUint32() + m.GuildID = bf.ReadUint32() + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_enumerate_guild_member.go b/network/mhfpacket/msg_mhf_enumerate_guild_member.go index f15de35f6..e90c431f2 100644 --- a/network/mhfpacket/msg_mhf_enumerate_guild_member.go +++ b/network/mhfpacket/msg_mhf_enumerate_guild_member.go @@ -1,17 +1,17 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfEnumerateGuildMember represents the MSG_MHF_ENUMERATE_GUILD_MEMBER type MsgMhfEnumerateGuildMember struct { - AckHandle uint32 - Unk0 uint16 // Hardcoded 00 01 in the binary - Unk1 uint32 // Alliance related - GuildID uint32 + AckHandle uint32 + AllianceID uint32 + GuildID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,17 +22,14 @@ func (m *MsgMhfEnumerateGuildMember) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateGuildMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint32() + bf.ReadUint8() // Zeroed + bf.ReadUint8() // Always 1 + m.AllianceID = bf.ReadUint32() m.GuildID = bf.ReadUint32() return nil } // Build builds a binary packet from the current data. func (m *MsgMhfEnumerateGuildMember) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint32(m.AckHandle) - bf.WriteUint16(m.Unk0) - bf.WriteUint32(m.Unk1) - bf.WriteUint32(m.GuildID) - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/network/mhfpacket/msg_mhf_enumerate_house.go b/network/mhfpacket/msg_mhf_enumerate_house.go index da6a25de7..41f57323a 100644 --- a/network/mhfpacket/msg_mhf_enumerate_house.go +++ b/network/mhfpacket/msg_mhf_enumerate_house.go @@ -14,7 +14,6 @@ type MsgMhfEnumerateHouse struct { AckHandle uint32 CharID uint32 Method uint8 - Unk uint16 Name string } @@ -28,7 +27,7 @@ func (m *MsgMhfEnumerateHouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli m.AckHandle = bf.ReadUint32() m.CharID = bf.ReadUint32() m.Method = bf.ReadUint8() - m.Unk = bf.ReadUint16() + bf.ReadUint16() // Zeroed lenName := bf.ReadUint8() if lenName > 0 { m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) diff --git a/network/mhfpacket/msg_mhf_enumerate_inv_guild.go b/network/mhfpacket/msg_mhf_enumerate_inv_guild.go index cf2057bed..994c374d8 100644 --- a/network/mhfpacket/msg_mhf_enumerate_inv_guild.go +++ b/network/mhfpacket/msg_mhf_enumerate_inv_guild.go @@ -10,8 +10,13 @@ import ( // MsgMhfEnumerateInvGuild represents the MSG_MHF_ENUMERATE_INV_GUILD type MsgMhfEnumerateInvGuild struct { - AckHandle uint32 - Unk []byte + AckHandle uint32 + Unk uint32 + Operation uint8 + ActiveHours uint8 + DaysActive uint8 + PlayStyle uint8 + GuildRequest uint8 } // Opcode returns the ID associated with this packet type. @@ -22,7 +27,12 @@ func (m *MsgMhfEnumerateInvGuild) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateInvGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk = bf.ReadBytes(9) + m.Unk = bf.ReadUint32() + m.Operation = bf.ReadUint8() + m.ActiveHours = bf.ReadUint8() + m.DaysActive = bf.ReadUint8() + m.PlayStyle = bf.ReadUint8() + m.GuildRequest = bf.ReadUint8() return nil } diff --git a/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go b/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go index 918a870ac..829bb6fb2 100644 --- a/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go +++ b/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go @@ -13,7 +13,6 @@ type MsgMhfExchangeWeeklyStamp struct { AckHandle uint32 StampType string Unk1 uint8 - Unk2 uint16 } // Opcode returns the ID associated with this packet type. @@ -32,7 +31,7 @@ func (m *MsgMhfExchangeWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientct m.StampType = "ex" } m.Unk1 = bf.ReadUint8() - m.Unk2 = bf.ReadUint16() + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_info_joint.go b/network/mhfpacket/msg_mhf_info_joint.go index 17e468c7c..c349c9768 100644 --- a/network/mhfpacket/msg_mhf_info_joint.go +++ b/network/mhfpacket/msg_mhf_info_joint.go @@ -12,7 +12,6 @@ import ( type MsgMhfInfoJoint struct { AckHandle uint32 AllianceID uint32 - Unk uint32 } // Opcode returns the ID associated with this packet type. @@ -24,7 +23,7 @@ func (m *MsgMhfInfoJoint) Opcode() network.PacketID { func (m *MsgMhfInfoJoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.AllianceID = bf.ReadUint32() - m.Unk = bf.ReadUint32() + bf.ReadUint32() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_load_guild_cooking.go b/network/mhfpacket/msg_mhf_load_guild_cooking.go index 515187bc2..0e293c073 100644 --- a/network/mhfpacket/msg_mhf_load_guild_cooking.go +++ b/network/mhfpacket/msg_mhf_load_guild_cooking.go @@ -1,17 +1,17 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfLoadGuildCooking represents the MSG_MHF_LOAD_GUILD_COOKING -type MsgMhfLoadGuildCooking struct{ - AckHandle uint32 - MaxMeals uint8 +type MsgMhfLoadGuildCooking struct { + AckHandle uint32 + MaxMeals uint8 } // Opcode returns the ID associated with this packet type. @@ -22,7 +22,7 @@ func (m *MsgMhfLoadGuildCooking) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfLoadGuildCooking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - _ = bf.ReadUint8() + m.MaxMeals = bf.ReadUint8() return nil } diff --git a/network/mhfpacket/msg_mhf_load_house.go b/network/mhfpacket/msg_mhf_load_house.go index a012b2f1c..138c8af22 100644 --- a/network/mhfpacket/msg_mhf_load_house.go +++ b/network/mhfpacket/msg_mhf_load_house.go @@ -16,7 +16,6 @@ type MsgMhfLoadHouse struct { Destination uint8 // False if already in hosts My Series, in case host updates PW CheckPass bool - Unk3 uint16 // Hardcoded 0 in binary Password string } @@ -31,8 +30,8 @@ func (m *MsgMhfLoadHouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCo m.CharID = bf.ReadUint32() m.Destination = bf.ReadUint8() m.CheckPass = bf.ReadBool() - _ = bf.ReadUint16() - _ = bf.ReadUint8() // Password length + bf.ReadUint16() // Zeroed + bf.ReadUint8() // Password length m.Password = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_mhf_operate_guild_member.go b/network/mhfpacket/msg_mhf_operate_guild_member.go index 7aed7a21d..8daf82dc5 100644 --- a/network/mhfpacket/msg_mhf_operate_guild_member.go +++ b/network/mhfpacket/msg_mhf_operate_guild_member.go @@ -23,7 +23,6 @@ type MsgMhfOperateGuildMember struct { GuildID uint32 CharID uint32 Action uint8 - Unk []byte } // Opcode returns the ID associated with this packet type. @@ -37,7 +36,8 @@ func (m *MsgMhfOperateGuildMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx m.GuildID = bf.ReadUint32() m.CharID = bf.ReadUint32() m.Action = bf.ReadUint8() - m.Unk = bf.ReadBytes(3) + bf.ReadUint8() // Zeroed + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_operate_joint.go b/network/mhfpacket/msg_mhf_operate_joint.go index 1fa360d01..eccb3139d 100644 --- a/network/mhfpacket/msg_mhf_operate_joint.go +++ b/network/mhfpacket/msg_mhf_operate_joint.go @@ -22,7 +22,8 @@ type MsgMhfOperateJoint struct { AllianceID uint32 GuildID uint32 Action OperateJointAction - UnkData *byteframe.ByteFrame + Data1 *byteframe.ByteFrame + Data2 *byteframe.ByteFrame } // Opcode returns the ID associated with this packet type. @@ -36,8 +37,9 @@ func (m *MsgMhfOperateJoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien m.AllianceID = bf.ReadUint32() m.GuildID = bf.ReadUint32() m.Action = OperateJointAction(bf.ReadUint8()) - m.UnkData = byteframe.NewByteFrameFromBytes(bf.DataFromCurrent()) - bf.Seek(int64(len(bf.Data())-2), 0) + dataLen := uint(bf.ReadUint8()) + m.Data1 = byteframe.NewByteFrameFromBytes(bf.ReadBytes(4)) + m.Data2 = byteframe.NewByteFrameFromBytes(bf.ReadBytes(dataLen)) return nil } diff --git a/network/mhfpacket/msg_mhf_opr_member.go b/network/mhfpacket/msg_mhf_opr_member.go index 32641cb39..b0dceaba5 100644 --- a/network/mhfpacket/msg_mhf_opr_member.go +++ b/network/mhfpacket/msg_mhf_opr_member.go @@ -1,20 +1,20 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfOprMember represents the MSG_MHF_OPR_MEMBER type MsgMhfOprMember struct { - AckHandle uint32 - Blacklist bool - Operation bool - Unk uint16 - CharID uint32 + AckHandle uint32 + Blacklist bool + Operation bool + Unk uint16 + CharIDs []uint32 } // Opcode returns the ID associated with this packet type. @@ -24,12 +24,15 @@ func (m *MsgMhfOprMember) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfOprMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.Blacklist = bf.ReadBool() - m.Operation = bf.ReadBool() - m.Unk = bf.ReadUint16() - m.CharID = bf.ReadUint32() - return nil + m.AckHandle = bf.ReadUint32() + m.Blacklist = bf.ReadBool() + m.Operation = bf.ReadBool() + bf.ReadUint8() + chars := int(bf.ReadUint8()) + for i := 0; i < chars; i++ { + m.CharIDs = append(m.CharIDs, bf.ReadUint32()) + } + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_register_event.go b/network/mhfpacket/msg_mhf_register_event.go index 956c4a399..46afb1a2e 100644 --- a/network/mhfpacket/msg_mhf_register_event.go +++ b/network/mhfpacket/msg_mhf_register_event.go @@ -12,8 +12,7 @@ type MsgMhfRegisterEvent struct { Unk0 uint16 WorldID uint16 LandID uint16 - Unk3 uint8 - Unk4 uint8 + Unk1 bool } // Opcode returns the ID associated with this packet type. @@ -27,8 +26,8 @@ func (m *MsgMhfRegisterEvent) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie m.Unk0 = bf.ReadUint16() m.WorldID = bf.ReadUint16() m.LandID = bf.ReadUint16() - m.Unk3 = bf.ReadUint8() - m.Unk4 = bf.ReadUint8() + m.Unk1 = bf.ReadBool() + bf.ReadUint8() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_set_guild_manage_right.go b/network/mhfpacket/msg_mhf_set_guild_manage_right.go index 3feed2654..9eb2d8b21 100644 --- a/network/mhfpacket/msg_mhf_set_guild_manage_right.go +++ b/network/mhfpacket/msg_mhf_set_guild_manage_right.go @@ -13,7 +13,6 @@ type MsgMhfSetGuildManageRight struct { AckHandle uint32 CharID uint32 Allowed bool - Unk []byte } // Opcode returns the ID associated with this packet type. @@ -26,7 +25,7 @@ func (m *MsgMhfSetGuildManageRight) Parse(bf *byteframe.ByteFrame, ctx *clientct m.AckHandle = bf.ReadUint32() m.CharID = bf.ReadUint32() m.Allowed = bf.ReadBool() - m.Unk = bf.ReadBytes(3) + bf.ReadBytes(3) // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_stampcard_stamp.go b/network/mhfpacket/msg_mhf_stampcard_stamp.go index 5ac650ad2..f9da9612e 100644 --- a/network/mhfpacket/msg_mhf_stampcard_stamp.go +++ b/network/mhfpacket/msg_mhf_stampcard_stamp.go @@ -34,7 +34,7 @@ func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli m.HR = bf.ReadUint16() m.GR = bf.ReadUint16() m.Stamps = bf.ReadUint16() - _ = bf.ReadUint16() + bf.ReadUint16() // Zeroed if _config.ErupeConfig.RealClientMode > _config.Z1 { m.Reward1 = uint16(bf.ReadUint32()) m.Reward2 = uint16(bf.ReadUint32()) diff --git a/network/mhfpacket/msg_mhf_state_festa_g.go b/network/mhfpacket/msg_mhf_state_festa_g.go index 86c6526bf..e356b98be 100644 --- a/network/mhfpacket/msg_mhf_state_festa_g.go +++ b/network/mhfpacket/msg_mhf_state_festa_g.go @@ -1,18 +1,18 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfStateFestaG represents the MSG_MHF_STATE_FESTA_G type MsgMhfStateFestaG struct { AckHandle uint32 - FestaID uint32 - GuildID uint32 + FestaID uint32 + GuildID uint32 } // Opcode returns the ID associated with this packet type. @@ -25,7 +25,7 @@ func (m *MsgMhfStateFestaG) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client m.AckHandle = bf.ReadUint32() m.FestaID = bf.ReadUint32() m.GuildID = bf.ReadUint32() - _ = bf.ReadUint16() // Hardcoded 0 in the binary. + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_mhf_state_festa_u.go b/network/mhfpacket/msg_mhf_state_festa_u.go index ef76498bd..013966dba 100644 --- a/network/mhfpacket/msg_mhf_state_festa_u.go +++ b/network/mhfpacket/msg_mhf_state_festa_u.go @@ -1,18 +1,18 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfStateFestaU represents the MSG_MHF_STATE_FESTA_U type MsgMhfStateFestaU struct { - AckHandle uint32 - FestaID uint32 - GuildID uint32 + AckHandle uint32 + FestaID uint32 + GuildID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,11 +22,11 @@ func (m *MsgMhfStateFestaU) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfStateFestaU) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.FestaID = bf.ReadUint32() - m.GuildID = bf.ReadUint32() - _ = bf.ReadUint16() // Hardcoded 0 in the binary. - return nil + m.AckHandle = bf.ReadUint32() + m.FestaID = bf.ReadUint32() + m.GuildID = bf.ReadUint32() + bf.ReadUint16() // Zeroed + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_transfer_item.go b/network/mhfpacket/msg_mhf_transfer_item.go index e3c2cc67c..69dfdb13f 100644 --- a/network/mhfpacket/msg_mhf_transfer_item.go +++ b/network/mhfpacket/msg_mhf_transfer_item.go @@ -1,11 +1,11 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfTransferItem represents the MSG_MHF_TRANSFER_ITEM @@ -15,8 +15,8 @@ type MsgMhfTransferItem struct { // correlate with any item IDs that would make sense to get after quests so // I have no idea what this actually does Unk0 uint32 - Unk1 uint16 // Hardcoded - Unk2 uint16 // Hardcoded + Unk1 uint8 + Unk2 uint16 } // Opcode returns the ID associated with this packet type. @@ -28,7 +28,8 @@ func (m *MsgMhfTransferItem) Opcode() network.PacketID { func (m *MsgMhfTransferItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint32() - m.Unk1 = bf.ReadUint16() + m.Unk1 = bf.ReadUint8() + bf.ReadUint8() // Zeroed m.Unk2 = bf.ReadUint16() return nil } diff --git a/network/mhfpacket/msg_mhf_transit_message.go b/network/mhfpacket/msg_mhf_transit_message.go index 1d15c6d42..1442af08d 100644 --- a/network/mhfpacket/msg_mhf_transit_message.go +++ b/network/mhfpacket/msg_mhf_transit_message.go @@ -12,7 +12,6 @@ import ( type MsgMhfTransitMessage struct { AckHandle uint32 Unk0 uint8 - Unk1 uint8 SearchType uint16 MessageData []byte } @@ -26,7 +25,7 @@ func (m *MsgMhfTransitMessage) Opcode() network.PacketID { func (m *MsgMhfTransitMessage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint8() - m.Unk1 = bf.ReadUint8() + bf.ReadUint8() // Zeroed m.SearchType = bf.ReadUint16() m.MessageData = bf.ReadBytes(uint(bf.ReadUint16())) return nil diff --git a/network/mhfpacket/msg_mhf_update_guild_item.go b/network/mhfpacket/msg_mhf_update_guild_item.go index dbea591ea..ddd7ef6e5 100644 --- a/network/mhfpacket/msg_mhf_update_guild_item.go +++ b/network/mhfpacket/msg_mhf_update_guild_item.go @@ -18,7 +18,7 @@ type Item struct { // MsgMhfUpdateGuildItem represents the MSG_MHF_UPDATE_GUILD_ITEM type MsgMhfUpdateGuildItem struct { AckHandle uint32 - GuildId uint32 + GuildID uint32 Items []Item } @@ -30,7 +30,7 @@ func (m *MsgMhfUpdateGuildItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUpdateGuildItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GuildId = bf.ReadUint32() + m.GuildID = bf.ReadUint32() itemCount := int(bf.ReadUint16()) bf.ReadUint8() // Zeroed bf.ReadUint8() // Zeroed diff --git a/network/mhfpacket/msg_mhf_use_gacha_point.go b/network/mhfpacket/msg_mhf_use_gacha_point.go index 33a4ef143..5245b5eaf 100644 --- a/network/mhfpacket/msg_mhf_use_gacha_point.go +++ b/network/mhfpacket/msg_mhf_use_gacha_point.go @@ -1,19 +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" ) // MsgMhfUseGachaPoint represents the MSG_MHF_USE_GACHA_POINT -type MsgMhfUseGachaPoint struct{ - AckHandle uint32 - Unk0 uint16 // padding? - TrialCoins uint32 - PremiumCoins uint32 +type MsgMhfUseGachaPoint struct { + AckHandle uint32 + Unk0 uint8 + Unk1 uint8 + TrialCoins uint32 + PremiumCoins uint32 } // Opcode returns the ID associated with this packet type. @@ -24,7 +25,8 @@ func (m *MsgMhfUseGachaPoint) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUseGachaPoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() + m.Unk0 = bf.ReadUint8() + m.Unk1 = bf.ReadUint8() m.TrialCoins = bf.ReadUint32() m.PremiumCoins = bf.ReadUint32() return nil diff --git a/network/mhfpacket/msg_mhf_vote_festa.go b/network/mhfpacket/msg_mhf_vote_festa.go index b3c05d5fd..201c1b5ac 100644 --- a/network/mhfpacket/msg_mhf_vote_festa.go +++ b/network/mhfpacket/msg_mhf_vote_festa.go @@ -11,7 +11,7 @@ import ( // MsgMhfVoteFesta represents the MSG_MHF_VOTE_FESTA type MsgMhfVoteFesta struct { AckHandle uint32 - Unk uint32 + FestaID uint32 GuildID uint32 TrialID uint32 } @@ -24,7 +24,7 @@ func (m *MsgMhfVoteFesta) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfVoteFesta) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk = bf.ReadUint32() + m.FestaID = bf.ReadUint32() m.GuildID = bf.ReadUint32() m.TrialID = bf.ReadUint32() return nil diff --git a/network/mhfpacket/msg_sys_create_acquire_semaphore.go b/network/mhfpacket/msg_sys_create_acquire_semaphore.go index 694aaaeed..9e22c50e7 100644 --- a/network/mhfpacket/msg_sys_create_acquire_semaphore.go +++ b/network/mhfpacket/msg_sys_create_acquire_semaphore.go @@ -2,7 +2,6 @@ package mhfpacket import ( "errors" - "erupe-ce/common/bfutil" "erupe-ce/common/byteframe" _config "erupe-ce/config" "erupe-ce/network" @@ -29,8 +28,8 @@ func (m *MsgSysCreateAcquireSemaphore) Parse(bf *byteframe.ByteFrame, ctx *clien if _config.ErupeConfig.RealClientMode >= _config.S7 { // Assuming this was added with Ravi? m.PlayerCount = bf.ReadUint8() } - SemaphoreIDLength := bf.ReadUint8() - m.SemaphoreID = string(bfutil.UpToNull(bf.ReadBytes(uint(SemaphoreIDLength)))) + bf.ReadUint8() // SemaphoreID length + m.SemaphoreID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_create_semaphore.go b/network/mhfpacket/msg_sys_create_semaphore.go index 361802e05..c9b29d2ab 100644 --- a/network/mhfpacket/msg_sys_create_semaphore.go +++ b/network/mhfpacket/msg_sys_create_semaphore.go @@ -2,6 +2,7 @@ package mhfpacket import ( "errors" + _config "erupe-ce/config" "erupe-ce/common/byteframe" "erupe-ce/network" @@ -10,10 +11,10 @@ import ( // MsgSysCreateSemaphore represents the MSG_SYS_CREATE_SEMAPHORE type MsgSysCreateSemaphore struct { - AckHandle uint32 - Unk0 uint16 - DataSize uint16 - RawDataPayload []byte + AckHandle uint32 + Unk0 uint16 + PlayerCount uint8 + SemaphoreID string } // Opcode returns the ID associated with this packet type. @@ -25,8 +26,11 @@ func (m *MsgSysCreateSemaphore) Opcode() network.PacketID { func (m *MsgSysCreateSemaphore) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint16() - m.DataSize = bf.ReadUint16() - m.RawDataPayload = bf.ReadBytes(uint(m.DataSize)) + if _config.ErupeConfig.RealClientMode >= _config.S7 { // Assuming this was added with Ravi? + m.PlayerCount = bf.ReadUint8() + } + bf.ReadUint8() // SemaphoreID length + m.SemaphoreID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_enumerate_client.go b/network/mhfpacket/msg_sys_enumerate_client.go index 5318549a6..06764d65b 100644 --- a/network/mhfpacket/msg_sys_enumerate_client.go +++ b/network/mhfpacket/msg_sys_enumerate_client.go @@ -3,7 +3,6 @@ package mhfpacket import ( "errors" - "erupe-ce/common/bfutil" "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" @@ -27,8 +26,8 @@ func (m *MsgSysEnumerateClient) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint8() m.Get = bf.ReadUint8() - stageIDLength := bf.ReadUint8() - m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) + bf.ReadUint8() // StageID length + m.StageID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_get_stage_binary.go b/network/mhfpacket/msg_sys_get_stage_binary.go index 336f563d5..c2da50122 100644 --- a/network/mhfpacket/msg_sys_get_stage_binary.go +++ b/network/mhfpacket/msg_sys_get_stage_binary.go @@ -2,7 +2,6 @@ package mhfpacket import ( "erupe-ce/common/byteframe" - "erupe-ce/common/bfutil" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -27,8 +26,8 @@ func (m *MsgSysGetStageBinary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli m.BinaryType0 = bf.ReadUint8() m.BinaryType1 = bf.ReadUint8() m.Unk0 = bf.ReadUint32() - stageIDLength := bf.ReadUint8() - m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) + bf.ReadUint8() // StageID length + m.StageID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_load_register.go b/network/mhfpacket/msg_sys_load_register.go index 7e1ac5950..edf4eafb4 100644 --- a/network/mhfpacket/msg_sys_load_register.go +++ b/network/mhfpacket/msg_sys_load_register.go @@ -24,8 +24,8 @@ func (m *MsgSysLoadRegister) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien m.AckHandle = bf.ReadUint32() m.RegisterID = bf.ReadUint32() m.Values = bf.ReadUint8() - _ = bf.ReadUint8() - _ = bf.ReadUint16() + bf.ReadUint8() // Zeroed + bf.ReadUint16() // Zeroed return nil } diff --git a/network/mhfpacket/msg_sys_login.go b/network/mhfpacket/msg_sys_login.go index fc881b991..5f8a7c7cc 100644 --- a/network/mhfpacket/msg_sys_login.go +++ b/network/mhfpacket/msg_sys_login.go @@ -1,24 +1,22 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgSysLogin represents the MSG_SYS_LOGIN type MsgSysLogin struct { - AckHandle uint32 - CharID0 uint32 - LoginTokenNumber uint32 - HardcodedZero0 uint16 - RequestVersion uint16 - CharID1 uint32 - HardcodedZero1 uint16 - LoginTokenStringLength uint16 // Hardcoded to 0x11 - LoginTokenString string + AckHandle uint32 + CharID0 uint32 + LoginTokenNumber uint32 + HardcodedZero0 uint16 + RequestVersion uint16 + CharID1 uint32 + LoginTokenString string } // Opcode returns the ID associated with this packet type. @@ -34,8 +32,8 @@ func (m *MsgSysLogin) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContex m.HardcodedZero0 = bf.ReadUint16() m.RequestVersion = bf.ReadUint16() m.CharID1 = bf.ReadUint32() - m.HardcodedZero1 = bf.ReadUint16() - m.LoginTokenStringLength = bf.ReadUint16() + bf.ReadUint16() // Zeroed + bf.ReadUint16() // Always 11 m.LoginTokenString = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_operate_register.go b/network/mhfpacket/msg_sys_operate_register.go index 6978609b1..75946d340 100644 --- a/network/mhfpacket/msg_sys_operate_register.go +++ b/network/mhfpacket/msg_sys_operate_register.go @@ -23,7 +23,7 @@ func (m *MsgSysOperateRegister) Opcode() network.PacketID { func (m *MsgSysOperateRegister) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.SemaphoreID = bf.ReadUint32() - _ = bf.ReadUint16() + bf.ReadUint16() // Zeroed dataSize := bf.ReadUint16() m.RawDataPayload = bf.ReadBytes(uint(dataSize)) return nil diff --git a/network/mhfpacket/msg_sys_set_stage_binary.go b/network/mhfpacket/msg_sys_set_stage_binary.go index eecee64a1..79832c7bb 100644 --- a/network/mhfpacket/msg_sys_set_stage_binary.go +++ b/network/mhfpacket/msg_sys_set_stage_binary.go @@ -2,7 +2,6 @@ package mhfpacket import ( "erupe-ce/common/byteframe" - "erupe-ce/common/bfutil" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -24,9 +23,9 @@ func (m *MsgSysSetStageBinary) Opcode() network.PacketID { func (m *MsgSysSetStageBinary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.BinaryType0 = bf.ReadUint8() m.BinaryType1 = bf.ReadUint8() - stageIDLength := bf.ReadUint8() // <= 0x20 - dataSize := bf.ReadUint16() // <= 0x400 - m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) + bf.ReadUint8() // StageID length <= 0x20 + dataSize := bf.ReadUint16() // <= 0x400 + m.StageID = string(bf.ReadNullTerminatedBytes()) m.RawDataPayload = bf.ReadBytes(uint(dataSize)) return nil } diff --git a/network/mhfpacket/msg_sys_wait_stage_binary.go b/network/mhfpacket/msg_sys_wait_stage_binary.go index 2a443cc72..5127e53de 100644 --- a/network/mhfpacket/msg_sys_wait_stage_binary.go +++ b/network/mhfpacket/msg_sys_wait_stage_binary.go @@ -2,7 +2,6 @@ package mhfpacket import ( "erupe-ce/common/byteframe" - "erupe-ce/common/bfutil" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -27,8 +26,8 @@ func (m *MsgSysWaitStageBinary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl m.BinaryType0 = bf.ReadUint8() m.BinaryType1 = bf.ReadUint8() m.Unk0 = bf.ReadUint32() - stageIDLength := bf.ReadUint8() - m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) + bf.ReadUint8() // StageID length + m.StageID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 057881723..154dbc100 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -611,7 +611,7 @@ func handleMsgMhfServerCommand(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfAnnounce(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfAnnounce) - s.server.BroadcastRaviente(pkt.IPAddress, pkt.Port, pkt.StageID, pkt.Type) + s.server.BroadcastRaviente(pkt.IPAddress, pkt.Port, pkt.StageID, pkt.Data.ReadUint8()) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } diff --git a/server/channelserver/handlers_clients.go b/server/channelserver/handlers_clients.go index 12e8540b3..e1d2c4a49 100644 --- a/server/channelserver/handlers_clients.go +++ b/server/channelserver/handlers_clients.go @@ -60,20 +60,19 @@ func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) { resp := byteframe.NewByteFrame() resp.WriteUint32(0) // Blacklist count err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv) - if err != nil { - panic(err) - } - cids := stringsupport.CSVElems(csv) - for _, cid := range cids { - var name string - err = s.server.db.QueryRow("SELECT name FROM characters WHERE id=$1", cid).Scan(&name) - if err != nil { - continue + if err == nil { + cids := stringsupport.CSVElems(csv) + for _, cid := range cids { + var name string + err = s.server.db.QueryRow("SELECT name FROM characters WHERE id=$1", cid).Scan(&name) + if err != nil { + continue + } + count++ + resp.WriteUint32(uint32(cid)) + resp.WriteUint32(16) + resp.WriteBytes(stringsupport.PaddedString(name, 16, true)) } - count++ - resp.WriteUint32(uint32(cid)) - resp.WriteUint32(16) - resp.WriteBytes(stringsupport.PaddedString(name, 16, true)) } resp.Seek(0, 0) resp.WriteUint32(count) @@ -83,28 +82,28 @@ func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfOprMember(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfOprMember) var csv string - if pkt.Blacklist { - err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv) - if err != nil { - panic(err) + for _, cid := range pkt.CharIDs { + if pkt.Blacklist { + err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv) + if err == nil { + if pkt.Operation { + csv = stringsupport.CSVRemove(csv, int(cid)) + } else { + csv = stringsupport.CSVAdd(csv, int(cid)) + } + s.server.db.Exec("UPDATE characters SET blocked=$1 WHERE id=$2", csv, s.charID) + } + } else { // Friendlist + err := s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&csv) + if err == nil { + if pkt.Operation { + csv = stringsupport.CSVRemove(csv, int(cid)) + } else { + csv = stringsupport.CSVAdd(csv, int(cid)) + } + s.server.db.Exec("UPDATE characters SET friends=$1 WHERE id=$2", csv, s.charID) + } } - if pkt.Operation { - csv = stringsupport.CSVRemove(csv, int(pkt.CharID)) - } else { - csv = stringsupport.CSVAdd(csv, int(pkt.CharID)) - } - s.server.db.Exec("UPDATE characters SET blocked=$1 WHERE id=$2", csv, s.charID) - } else { // Friendlist - err := s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&csv) - if err != nil { - panic(err) - } - if pkt.Operation { - csv = stringsupport.CSVRemove(csv, int(pkt.CharID)) - } else { - csv = stringsupport.CSVAdd(csv, int(pkt.CharID)) - } - s.server.db.Exec("UPDATE characters SET friends=$1 WHERE id=$2", csv, s.charID) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index cf190aa45..48d2bbadf 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1147,7 +1147,6 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { var alliances []*GuildAlliance var rows *sqlx.Rows var err error - bf := byteframe.NewByteFrameFromBytes(pkt.Data1) if pkt.Type <= 8 { var tempGuilds []*Guild @@ -1164,20 +1163,20 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { switch pkt.Type { case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME: for _, guild := range tempGuilds { - if strings.Contains(guild.Name, pkt.Data2) { + if strings.Contains(guild.Name, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) { guilds = append(guilds, guild) } } case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME: for _, guild := range tempGuilds { - if strings.Contains(guild.LeaderName, pkt.Data2) { + if strings.Contains(guild.LeaderName, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) { guilds = append(guilds, guild) } } case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID: - ID := bf.ReadUint32() + CID := pkt.Data1.ReadUint32() for _, guild := range tempGuilds { - if guild.LeaderCharID == ID { + if guild.LeaderCharID == CID { guilds = append(guilds, guild) } } @@ -1215,15 +1214,15 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { } guilds = tempGuilds case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO: - mainMotto := uint8(bf.ReadUint16()) - subMotto := uint8(bf.ReadUint16()) + mainMotto := uint8(pkt.Data1.ReadUint16()) + subMotto := uint8(pkt.Data1.ReadUint16()) for _, guild := range tempGuilds { if guild.MainMotto == mainMotto && guild.SubMotto == subMotto { guilds = append(guilds, guild) } } case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING: - recruitingMotto := uint8(bf.ReadUint16()) + recruitingMotto := uint8(pkt.Data1.ReadUint16()) for _, guild := range tempGuilds { if guild.MainMotto == recruitingMotto { guilds = append(guilds, guild) @@ -1244,20 +1243,20 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { switch pkt.Type { case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME: for _, alliance := range tempAlliances { - if strings.Contains(alliance.Name, pkt.Data2) { + if strings.Contains(alliance.Name, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) { alliances = append(alliances, alliance) } } case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME: for _, alliance := range tempAlliances { - if strings.Contains(alliance.ParentGuild.LeaderName, pkt.Data2) { + if strings.Contains(alliance.ParentGuild.LeaderName, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) { alliances = append(alliances, alliance) } } case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_ID: - ID := bf.ReadUint32() + CID := pkt.Data1.ReadUint32() for _, alliance := range tempAlliances { - if alliance.ParentGuild.LeaderCharID == ID { + if alliance.ParentGuild.LeaderCharID == CID { alliances = append(alliances, alliance) } } @@ -1291,7 +1290,7 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { return } - bf = byteframe.NewByteFrame() + bf := byteframe.NewByteFrame() if pkt.Type > 8 { hasNextPage := false @@ -1436,7 +1435,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { for _, member := range guildMembers { bf.WriteUint32(member.CharID) bf.WriteUint16(member.HRP) - if s.server.erupeConfig.RealClientMode > _config.G7 { + if s.server.erupeConfig.RealClientMode >= _config.G10 { bf.WriteUint16(member.GR) } if s.server.erupeConfig.RealClientMode < _config.ZZ { @@ -1558,7 +1557,7 @@ func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem) var boxContents []byte bf := byteframe.NewByteFrame() - err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents) + err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", pkt.GuildID).Scan(&boxContents) if err != nil { s.logger.Error("Failed to get guild item box contents from db", zap.Error(err)) bf.WriteBytes(make([]byte, 4)) @@ -1592,7 +1591,7 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) { // Get item cache from DB var boxContents []byte var oldItems []Item - err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents) + err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", pkt.GuildID).Scan(&boxContents) if err != nil { s.logger.Error("Failed to get guild item box contents from db", zap.Error(err)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) @@ -1642,7 +1641,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)) + _, err = s.server.db.Exec("UPDATE guilds SET item_box = $1 WHERE id = $2", bf.Data(), pkt.GuildID) if err != nil { s.logger.Error("Failed to update guild item box contents in db", zap.Error(err)) } diff --git a/server/channelserver/handlers_guild_alliance.go b/server/channelserver/handlers_guild_alliance.go index f27d67026..39dbe13f6 100644 --- a/server/channelserver/handlers_guild_alliance.go +++ b/server/channelserver/handlers_guild_alliance.go @@ -162,8 +162,7 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) { } case mhfpacket.OPERATE_JOINT_KICK: if alliance.ParentGuild.LeaderCharID == s.charID { - _ = pkt.UnkData.ReadUint8() - kickedGuildID := pkt.UnkData.ReadUint32() + kickedGuildID := pkt.Data1.ReadUint32() if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID > 0 { s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, alliance.ID) } else if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID == 0 { diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index 6a74358aa..3d47c3633 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -10,7 +10,7 @@ func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfRegisterEvent) bf := byteframe.NewByteFrame() // Some kind of check if there's already a session - if pkt.Unk3 > 0 && s.server.getRaviSemaphore() == nil { + if pkt.Unk1 && s.server.getRaviSemaphore() == nil { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return } From 1e6675b3f58d101ae71fd2861b7d18e8a071677b Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 19 Nov 2023 04:59:30 +1100 Subject: [PATCH 107/269] add support for Festa Trial voting --- patch-schema/13-festa-trial-votes.sql | 5 ++++ server/channelserver/handlers_festa.go | 34 +++++++++++++++++--------- server/channelserver/handlers_guild.go | 12 ++++----- 3 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 patch-schema/13-festa-trial-votes.sql diff --git a/patch-schema/13-festa-trial-votes.sql b/patch-schema/13-festa-trial-votes.sql new file mode 100644 index 000000000..d9e3d0290 --- /dev/null +++ b/patch-schema/13-festa-trial-votes.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.guild_characters ADD COLUMN trial_vote integer; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 7ca50d9d1..024cee00d 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -96,7 +96,7 @@ func cleanupFesta(s *Session) { s.server.db.Exec("DELETE FROM events WHERE event_type='festa'") s.server.db.Exec("DELETE FROM festa_registrations") s.server.db.Exec("DELETE FROM festa_prizes_accepted") - s.server.db.Exec("UPDATE guild_characters SET souls=0") + s.server.db.Exec("UPDATE guild_characters SET souls=0, trial_vote=NULL") } func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 { @@ -141,13 +141,13 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 { } type FestaTrial struct { - ID uint32 `db:"id"` - Objective uint16 `db:"objective"` - GoalID uint32 `db:"goal_id"` - TimesReq uint16 `db:"times_req"` - Locale uint16 `db:"locale_req"` - Reward uint16 `db:"reward"` - Monopoly uint16 + ID uint32 `db:"id"` + Objective uint16 `db:"objective"` + GoalID uint32 `db:"goal_id"` + TimesReq uint16 `db:"times_req"` + Locale uint16 `db:"locale_req"` + Reward uint16 `db:"reward"` + Monopoly FestivalColour `db:"monopoly"` Unk uint16 } @@ -205,7 +205,19 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { var trials []FestaTrial var trial FestaTrial - rows, _ = s.server.db.Queryx("SELECT * FROM festa_trials") + rows, _ = s.server.db.Queryx(`SELECT ft.*, + COALESCE(CASE + WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id) > + COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id) + THEN CAST('blue' AS public.festival_colour) + WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id) > + COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id) + THEN CAST('red' AS public.festival_colour) + END, CAST('none' AS public.festival_colour)) AS monopoly + FROM public.festa_trials ft + LEFT JOIN public.guild_characters gc ON ft.id = gc.trial_vote + LEFT JOIN public.festa_registrations fr ON gc.guild_id = fr.guild_id + GROUP BY ft.id`) for rows.Next() { err := rows.StructScan(&trial) if err != nil { @@ -221,8 +233,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(trial.TimesReq) bf.WriteUint16(trial.Locale) bf.WriteUint16(trial.Reward) - trial.Monopoly = 0xFFFF // NYI - bf.WriteUint16(trial.Monopoly) + bf.WriteInt16(int16(FestivalColourCodes[trial.Monopoly])) bf.WriteUint16(trial.Unk) } @@ -395,6 +406,7 @@ func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfVoteFesta) + s.server.db.Exec(`UPDATE guild_characters SET trial_vote=$1 WHERE character_id=$2`, pkt.TrialID, s.charID) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 48d2bbadf..7a726832f 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -25,14 +25,14 @@ type FestivalColour string const ( FestivalColourNone FestivalColour = "none" - FestivalColourRed FestivalColour = "red" FestivalColourBlue FestivalColour = "blue" + FestivalColourRed FestivalColour = "red" ) -var FestivalColourCodes = map[FestivalColour]uint8{ - FestivalColourBlue: 0x00, - FestivalColourRed: 0x01, - FestivalColourNone: 0xFF, +var FestivalColourCodes = map[FestivalColour]int8{ + FestivalColourNone: -1, + FestivalColourBlue: 0, + FestivalColourRed: 1, } type GuildApplicationType string @@ -967,7 +967,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(uint8(len(guildLeaderName))) bf.WriteBytes(guildName) bf.WriteBytes(guildComment) - bf.WriteUint8(FestivalColourCodes[guild.FestivalColour]) + bf.WriteInt8(FestivalColourCodes[guild.FestivalColour]) bf.WriteUint32(guild.RankRP) bf.WriteBytes(guildLeaderName) bf.WriteUint32(0) // Unk From e5fa0501b745fc6ea64401152ec5e221a09c21cb Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 19 Nov 2023 20:17:03 +1100 Subject: [PATCH 108/269] add packet time profiling --- server/channelserver/sys_session.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 406ea4384..ff3ad34e0 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -62,8 +62,9 @@ type Session struct { mailList []int // For Debuging - Name string - closed bool + Name string + closed bool + ackStart map[uint32]time.Time } // NewSession creates a new Session type. @@ -78,6 +79,7 @@ func NewSession(server *Server, conn net.Conn) *Session { lastPacket: time.Now(), sessionStart: TimeAdjusted().Unix(), stageMoveStack: stringstack.New(), + ackStart: make(map[uint32]time.Time), } s.SetObjectID() return s @@ -192,6 +194,10 @@ func (s *Session) handlePacketGroup(pktGroup []byte) { s.lastPacket = time.Now() bf := byteframe.NewByteFrameFromBytes(pktGroup) opcodeUint16 := bf.ReadUint16() + if len(bf.Data()) >= 6 { + s.ackStart[bf.ReadUint32()] = time.Now() + bf.Seek(2, io.SeekStart) + } opcode := network.PacketID(opcodeUint16) // This shouldn't be needed, but it's better to recover and let the connection die than to panic the server. @@ -262,7 +268,15 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien if ignored(opcodePID) { return } - fmt.Printf("[%s] -> [%s]\n", sender, recipient) + var ackHandle uint32 + if len(data) >= 6 { + ackHandle = binary.BigEndian.Uint32(data[2:6]) + } + if t, ok := s.ackStart[ackHandle]; ok { + fmt.Printf("[%s] -> [%s] (%fs)\n", sender, recipient, float64(time.Now().UnixNano()-t.UnixNano())/1000000000) + } else { + fmt.Printf("[%s] -> [%s]\n", sender, recipient) + } fmt.Printf("Opcode: %s\n", opcodePID) if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength { fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data)) From d7d3e7c61f354c065b73e653324d3030b2abdd79 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 19 Nov 2023 23:12:58 +1100 Subject: [PATCH 109/269] update parsing of CastBinary & EnterStage --- network/mhfpacket/msg_sys_cast_binary.go | 14 ++++++-------- network/mhfpacket/msg_sys_enter_stage.go | 5 ++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/network/mhfpacket/msg_sys_cast_binary.go b/network/mhfpacket/msg_sys_cast_binary.go index 07935f2aa..f0b0c2bd3 100644 --- a/network/mhfpacket/msg_sys_cast_binary.go +++ b/network/mhfpacket/msg_sys_cast_binary.go @@ -1,17 +1,16 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgSysCastBinary represents the MSG_SYS_CAST_BINARY type MsgSysCastBinary struct { - Unk0 uint16 - Unk1 uint16 + Unk uint32 BroadcastType uint8 MessageType uint8 RawDataPayload []byte @@ -24,8 +23,7 @@ func (m *MsgSysCastBinary) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysCastBinary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() + m.Unk = bf.ReadUint32() m.BroadcastType = bf.ReadUint8() m.MessageType = bf.ReadUint8() dataSize := bf.ReadUint16() diff --git a/network/mhfpacket/msg_sys_enter_stage.go b/network/mhfpacket/msg_sys_enter_stage.go index 977ff486f..5a1ccb8ab 100644 --- a/network/mhfpacket/msg_sys_enter_stage.go +++ b/network/mhfpacket/msg_sys_enter_stage.go @@ -4,7 +4,6 @@ import ( "errors" "erupe-ce/common/byteframe" - "erupe-ce/common/bfutil" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -25,8 +24,8 @@ func (m *MsgSysEnterStage) Opcode() network.PacketID { func (m *MsgSysEnterStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.UnkBool = bf.ReadUint8() - stageIDLength := bf.ReadUint8() - m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) + bf.ReadUint8() + m.StageID = string(bf.ReadNullTerminatedBytes()) return nil } From e066e9566f190ab40f016e7a06a813be1704b259 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 19 Nov 2023 23:13:12 +1100 Subject: [PATCH 110/269] remove duplicate CleanupObject --- server/channelserver/handlers_stage.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index be7ab1267..cf8757b00 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -163,7 +163,6 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { s.stageMoveStack.Lock() } - s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{}) if s.reservationStage != nil { s.reservationStage = nil } From 105a118163c79d5c7f5ff60f6c205e52aefd0f46 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 19 Nov 2023 23:13:34 +1100 Subject: [PATCH 111/269] filter workflow triggers --- .github/workflows/go.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 955febd9a..e69ee1705 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,6 +1,15 @@ name: Build -on: [push] +on: + push: + paths: + - 'common/**' + - 'config/**' + - 'network/**' + - 'server/**' + - 'go.mod' + - 'go.sum' + - 'main.go' jobs: build: From b6cc8c3a1222ebdb7cf43dbc0b06b00706a59655 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 20 Nov 2023 00:01:22 +1100 Subject: [PATCH 112/269] fix incorrect Client Version string --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 4c156d35e..a4cb99eb7 100644 --- a/config/config.go +++ b/config/config.go @@ -58,7 +58,7 @@ const ( ) var versionStrings = []string{"S1.0", "S1.5", "S2.0", "S2.5", "S3.0", "S3.5", "S4.0", "S5.0", "S5.5", "S6.0", "S7.0", - "S8.0", "S8.5", "S9", "S10", "FW.1", "FW.2", "FW.3", "FW.4", "FW.5", "G1", "G2", "G3", "G3.1", "G3.2", "GG", "G5", + "S8.0", "S8.5", "S9.0", "S10", "FW.1", "FW.2", "FW.3", "FW.4", "FW.5", "G1", "G2", "G3", "G3.1", "G3.2", "GG", "G5", "G5.1", "G5.2", "G6", "G6.1", "G7", "G8", "G8.1", "G9", "G9.1", "G10", "G10.1", "Z1", "Z2", "ZZ"} func (m Mode) String() string { From bcf2ba40e50d4da191867c4b699a7a8a36c5d50c Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 20 Nov 2023 00:13:49 +1100 Subject: [PATCH 113/269] fix possible integer bounding issues --- common/stringsupport/string_convert.go | 4 ++-- server/channelserver/handlers_cast_binary.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/stringsupport/string_convert.go b/common/stringsupport/string_convert.go index 452c85321..de4d04364 100644 --- a/common/stringsupport/string_convert.go +++ b/common/stringsupport/string_convert.go @@ -69,7 +69,7 @@ func CSVRemove(csv string, v int) string { func CSVContains(csv string, v int) bool { s := strings.Split(csv, ",") for i := 0; i < len(s); i++ { - j, _ := strconv.ParseInt(s[i], 10, 64) + j, _ := strconv.ParseInt(s[i], 10, 32) if int(j) == v { return true } @@ -92,7 +92,7 @@ func CSVElems(csv string) []int { } s := strings.Split(csv, ",") for i := 0; i < len(s); i++ { - j, _ := strconv.ParseInt(s[i], 10, 64) + j, _ := strconv.ParseInt(s[i], 10, 32) r = append(r, int(j)) } return r diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 168e2f181..15a85f32b 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -298,8 +298,8 @@ func parseChatCommand(s *Session, command string) { case commands["Teleport"].Prefix: if commands["Teleport"].Enabled { if len(args) > 2 { - x, _ := strconv.Atoi(args[1]) - y, _ := strconv.Atoi(args[2]) + x, _ := strconv.ParseInt(args[1], 10, 16) + y, _ := strconv.ParseInt(args[2], 10, 16) payload := byteframe.NewByteFrame() payload.SetLE() payload.WriteUint8(2) // SetState type(position == 2) From e9fa4e5261ae536b0960994ab4463d8719dbdcbe Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 20 Nov 2023 00:49:50 +1100 Subject: [PATCH 114/269] fix LogOutboundMessages not working --- server/channelserver/sys_session.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index ff3ad34e0..a9b89a918 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -260,7 +260,7 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages { return - } else if !s.server.erupeConfig.DevModeOptions.LogInboundMessages { + } else if sender != "Server" && !s.server.erupeConfig.DevModeOptions.LogInboundMessages { return } @@ -281,7 +281,7 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength { fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data)) } else { - fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data)) + fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data)) } } From 405e65346bccc1ef8733bdc581a3b35f4e35eca1 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 20 Nov 2023 00:58:24 +1100 Subject: [PATCH 115/269] add LogMessageData DevModeOption & disable Logging by default --- config.json | 5 +++-- config/config.go | 1 + server/channelserver/sys_session.go | 10 +++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/config.json b/config.json index a62b45325..e3e2b7ea4 100644 --- a/config.json +++ b/config.json @@ -18,8 +18,9 @@ "AutoCreateAccount": true, "CleanDB": false, "MaxLauncherHR": false, - "LogInboundMessages": true, - "LogOutboundMessages": true, + "LogInboundMessages": false, + "LogOutboundMessages": false, + "LogMessageData": false, "MaxHexdumpLength": 256, "DivaEvent": 0, "FestaEvent": -1, diff --git a/config/config.go b/config/config.go index a4cb99eb7..ee0e6b377 100644 --- a/config/config.go +++ b/config/config.go @@ -101,6 +101,7 @@ type DevModeOptions struct { MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds. LogInboundMessages bool // Log all messages sent to the server LogOutboundMessages bool // Log all messages sent to the clients + LogMessageData bool // Log all bytes transferred as a hexdump MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled DivaEvent int // Diva Defense event status FestaEvent int // Hunter's Festa event status diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index a9b89a918..32004151d 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -278,10 +278,14 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien fmt.Printf("[%s] -> [%s]\n", sender, recipient) } fmt.Printf("Opcode: %s\n", opcodePID) - if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength { - fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data)) + if s.server.erupeConfig.DevModeOptions.LogMessageData { + if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength { + fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data)) + } else { + fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data)) + } } else { - fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data)) + fmt.Printf("\n") } } From 3778d03402d452af362ab6984362d04c51ab0a19 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 19 Nov 2023 13:39:59 -0500 Subject: [PATCH 116/269] feat: Default to always active if inactive time is not set --- server/channelserver/handlers_quest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 90c477708..7fc901cd0 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -207,7 +207,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { currentTime := time.Now() // Check the event_quests table to load the quests with rotation system - rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, start_time, active_duration, inactive_duration FROM event_quests") + rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, start_time, COALESCE(active_duration, 1) AS active_duration, COALESCE(inactive_duration, 0) AS inactive_duration FROM event_quests") if err != nil { return } From 6bd2b637a721dde1f0445b85c2110a0da0c359f2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 19 Nov 2023 13:44:11 -0500 Subject: [PATCH 117/269] refactor: Moved quest duration variables to uint8 --- server/channelserver/handlers_quest.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 7fc901cd0..76b3fbef9 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -218,8 +218,8 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { for rows.Next() { var id, mark uint32 - var questId, activeDuration, inactiveDuration int - var maxPlayers, questType uint8 + var questId int + var maxPlayers, questType, activeDuration, inactiveDuration uint8 var startTime time.Time err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &startTime, &activeDuration, &inactiveDuration) From 858a9adb1777265f9a335d12a16ac2fa94845493 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 19 Nov 2023 13:45:30 -0500 Subject: [PATCH 118/269] chore: Removed debug code --- server/channelserver/handlers_quest.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 76b3fbef9..24c5f2035 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -192,8 +192,6 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { func calculateNumberOfCycles(duration time.Duration, lastStartTime time.Time) int { timeDifference := time.Now().Sub(lastStartTime) - println(timeDifference.String()) - println(float64(duration)) numberOfCycles := int(timeDifference.Nanoseconds() / int64(duration)) return numberOfCycles } From ffb0d25a4324575957e6452b226f3aeb5eb21208 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 19 Nov 2023 13:48:43 -0500 Subject: [PATCH 119/269] docs: Fixed and cleaned a bit of the documentation --- server/channelserver/handlers_quest.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 24c5f2035..e329dab1b 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -225,13 +225,13 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { continue } - // Count the number of cycles necessary to align quest with actual time. + // Count the number of cycles necessary to align quest with the correct date range. cycleCount := calculateNumberOfCycles(time.Duration(activeDuration+inactiveDuration)*24*time.Hour, startTime) - // Calculate the rotation time based on start time, active duration, and inactive duration + // Calculate the rotation time based on start time, active duration, and inactive duration. rotationTime := startTime.Add(time.Duration(activeDuration+inactiveDuration) * 24 * time.Duration(cycleCount) * time.Hour) if currentTime.After(rotationTime) { - // take the rotationTime and normalize it to midnight + // take the rotationTime and normalize it to midnight as to align with the ingame message for event quest rotation. newRotationTime := time.Date(rotationTime.Year(), rotationTime.Month(), rotationTime.Day(), 0, 0, 0, 0, rotationTime.Location()) newRotationTime = newRotationTime.Add(time.Duration(TimeMidnight().Add(13 * time.Hour).Nanosecond())) @@ -240,7 +240,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { transaction.Rollback() // Rollback if an error occurs break } - startTime = newRotationTime // Set the new start time so the quest can be used immediatelyw + startTime = newRotationTime // Set the new start time so the quest can be used/remove immediately. } // Check if the quest is currently active From 8dbc54fa1463feaff8b38a4e51eea40a26af113e Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 19 Nov 2023 13:49:19 -0500 Subject: [PATCH 120/269] config: Fixed config back to default values --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index b175451e4..d3de778bb 100644 --- a/config.json +++ b/config.json @@ -119,9 +119,9 @@ ], "Database": { "Host": "localhost", - "Port": 5433, + "Port": 5432, "User": "postgres", - "Password": "admin", + "Password": "", "Database": "erupe" }, "Sign": { From 3b33c1917c82247ee65fb98849b33380c4294ea2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 19 Nov 2023 13:52:03 -0500 Subject: [PATCH 121/269] sql: Remove new migration code from old patch-schema --- patch-schema/03-event_quests.sql | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/patch-schema/03-event_quests.sql b/patch-schema/03-event_quests.sql index b8003954f..1374a3d08 100644 --- a/patch-schema/03-event_quests.sql +++ b/patch-schema/03-event_quests.sql @@ -6,10 +6,7 @@ create table if not exists event_quests max_players integer, quest_type integer not null, quest_id integer not null, - mark integer, - start_time timestamp with time zone NOT NULL DEFAULT (CURRENT_DATE + interval '0 second'), - active_duration int not null, - inactive_duration int not null + mark integer ); ALTER TABLE IF EXISTS public.servers DROP COLUMN IF EXISTS season; From 46587b8d0182d135f000fb4a57275e981901673f Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 19 Nov 2023 14:27:53 -0500 Subject: [PATCH 122/269] fix: Fix variables --- config.json | 4 ++-- server/channelserver/handlers_quest.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index d3de778bb..b175451e4 100644 --- a/config.json +++ b/config.json @@ -119,9 +119,9 @@ ], "Database": { "Host": "localhost", - "Port": 5432, + "Port": 5433, "User": "postgres", - "Password": "", + "Password": "admin", "Database": "erupe" }, "Sign": { diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index e329dab1b..35471269a 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -240,7 +240,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { transaction.Rollback() // Rollback if an error occurs break } - startTime = newRotationTime // Set the new start time so the quest can be used/remove immediately. + startTime = newRotationTime // Set the new start time so the quest can be used/removed immediately. } // Check if the quest is currently active From e141b7980c477cbf5680ba1fe6f6f64a8a602847 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 19 Nov 2023 14:35:20 -0500 Subject: [PATCH 123/269] chore: Fix config --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index b175451e4..d3de778bb 100644 --- a/config.json +++ b/config.json @@ -119,9 +119,9 @@ ], "Database": { "Host": "localhost", - "Port": 5433, + "Port": 5432, "User": "postgres", - "Password": "admin", + "Password": "", "Database": "erupe" }, "Sign": { From 43f8cef35df24df68ffcb606089648ad9ce8370d Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 21 Nov 2023 21:48:15 +1100 Subject: [PATCH 124/269] fix server-side messages using wrong CID --- server/channelserver/handlers_cast_binary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 15a85f32b..dbfafc68c 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -75,7 +75,7 @@ func sendServerChatMessage(s *Session, message string) { msgBinChat.Build(bf) castedBin := &mhfpacket.MsgSysCastedBinary{ - CharID: s.charID, + CharID: 0, MessageType: BinaryMessageTypeChat, RawDataPayload: bf.Data(), } From d4ddf7bc255f6e3df9015b6986603898c93c1fc9 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 21 Nov 2023 23:46:33 +1100 Subject: [PATCH 125/269] initial legacy support for DistItem --- network/mhfpacket/msg_mhf_apply_dist_item.go | 9 +++++++-- network/mhfpacket/msg_mhf_enumerate_dist_item.go | 5 ++++- server/channelserver/handlers_distitem.go | 5 ++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/network/mhfpacket/msg_mhf_apply_dist_item.go b/network/mhfpacket/msg_mhf_apply_dist_item.go index f98dbb19a..a68354d2b 100644 --- a/network/mhfpacket/msg_mhf_apply_dist_item.go +++ b/network/mhfpacket/msg_mhf_apply_dist_item.go @@ -3,6 +3,7 @@ package mhfpacket import ( "errors" "erupe-ce/common/byteframe" + _config "erupe-ce/config" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -26,8 +27,12 @@ func (m *MsgMhfApplyDistItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie m.AckHandle = bf.ReadUint32() m.DistributionType = bf.ReadUint8() m.DistributionID = bf.ReadUint32() - m.Unk2 = bf.ReadUint32() - m.Unk3 = bf.ReadUint32() + if _config.ErupeConfig.RealClientMode >= _config.G8 { + m.Unk2 = bf.ReadUint32() + } + if _config.ErupeConfig.RealClientMode >= _config.G10 { + m.Unk3 = bf.ReadUint32() + } return nil } diff --git a/network/mhfpacket/msg_mhf_enumerate_dist_item.go b/network/mhfpacket/msg_mhf_enumerate_dist_item.go index bf5796702..d4164f1e5 100644 --- a/network/mhfpacket/msg_mhf_enumerate_dist_item.go +++ b/network/mhfpacket/msg_mhf_enumerate_dist_item.go @@ -3,6 +3,7 @@ package mhfpacket import ( "errors" "erupe-ce/common/byteframe" + _config "erupe-ce/config" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -27,7 +28,9 @@ func (m *MsgMhfEnumerateDistItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx. m.DistType = bf.ReadUint8() m.Unk1 = bf.ReadUint8() m.Unk2 = bf.ReadUint16() // Maximum? Hardcoded to 256 - m.Unk3 = bf.ReadBytes(uint(bf.ReadUint8())) + if _config.ErupeConfig.RealClientMode >= _config.Z1 { + m.Unk3 = bf.ReadBytes(uint(bf.ReadUint8())) + } return nil } diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index b18961202..e376cadb9 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -3,6 +3,7 @@ package channelserver import ( "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" + _config "erupe-ce/config" "erupe-ce/network/mhfpacket" "time" @@ -128,7 +129,9 @@ func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(item.ItemType) bf.WriteUint32(item.ItemID) bf.WriteUint32(item.Quantity) - bf.WriteUint32(item.ID) + if _config.ErupeConfig.RealClientMode >= _config.G8 { + bf.WriteUint32(item.ID) + } } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From e39630564e1d62381393cc89a1861ba604803dd3 Mon Sep 17 00:00:00 2001 From: rockisch Date: Wed, 22 Nov 2023 00:01:04 -0300 Subject: [PATCH 126/269] Add dev proxy config --- config.json | 1 + config/config.go | 29 +++++++++++++++-------------- main.go | 12 ++++++++---- server/entranceserver/make_resp.go | 6 +++++- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/config.json b/config.json index e3e2b7ea4..bfa9e04de 100644 --- a/config.json +++ b/config.json @@ -29,6 +29,7 @@ "MezFesAlt": false, "DisableTokenCheck": false, "QuestDebugTools": false, + "ProxyPort": 0, "EarthStatusOverride": 0, "EarthIDOverride": 0, "EarthMonsterOverride": 0, diff --git a/config/config.go b/config/config.go index ee0e6b377..c9a05d3d1 100644 --- a/config/config.go +++ b/config/config.go @@ -96,20 +96,21 @@ type Config struct { // DevModeOptions holds various debug/temporary options for use while developing Erupe. type DevModeOptions struct { - AutoCreateAccount bool // Automatically create accounts if they don't exist - CleanDB bool // Automatically wipes the DB on server reset. - MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds. - LogInboundMessages bool // Log all messages sent to the server - LogOutboundMessages bool // Log all messages sent to the clients - LogMessageData bool // Log all bytes transferred as a hexdump - MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled - DivaEvent int // Diva Defense event status - FestaEvent int // Hunter's Festa event status - TournamentEvent int // VS Tournament event status - MezFesEvent bool // MezFes status - MezFesAlt bool // Swaps out Volpakkun for Tokotoko - DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) - QuestDebugTools bool // Enable various quest debug logs + AutoCreateAccount bool // Automatically create accounts if they don't exist + CleanDB bool // Automatically wipes the DB on server reset. + MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds. + LogInboundMessages bool // Log all messages sent to the server + LogOutboundMessages bool // Log all messages sent to the clients + LogMessageData bool // Log all bytes transferred as a hexdump + MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled + DivaEvent int // Diva Defense event status + FestaEvent int // Hunter's Festa event status + TournamentEvent int // VS Tournament event status + MezFesEvent bool // MezFes status + MezFesAlt bool // Swaps out Volpakkun for Tokotoko + DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) + QuestDebugTools bool // Enable various quest debug logs + ProxyPort uint16 // Forces the game connect to a channel server proxy EarthStatusOverride int32 EarthIDOverride int32 EarthMonsterOverride int32 diff --git a/main.go b/main.go index 2a538b094..724fe3f44 100644 --- a/main.go +++ b/main.go @@ -22,11 +22,13 @@ import ( ) // Temporary DB auto clean on startup for quick development & testing. -func cleanDB(db *sqlx.DB) { +func cleanDB(db *sqlx.DB, config *_config.Config) { _ = db.MustExec("DELETE FROM guild_characters") _ = db.MustExec("DELETE FROM guilds") _ = db.MustExec("DELETE FROM characters") - _ = db.MustExec("DELETE FROM sign_sessions") + if !config.DevMode || config.DevModeOptions.ProxyPort == 0 { + _ = db.MustExec("DELETE FROM sign_sessions") + } _ = db.MustExec("DELETE FROM users") } @@ -124,14 +126,16 @@ func main() { logger.Info("Database: Started successfully") // Clear stale data - _ = db.MustExec("DELETE FROM sign_sessions") + if !config.DevMode || config.DevModeOptions.ProxyPort == 0 { + _ = db.MustExec("DELETE FROM sign_sessions") + } _ = db.MustExec("DELETE FROM servers") _ = db.MustExec(`UPDATE guild_characters SET treasure_hunt=NULL`) // Clean the DB if the option is on. if config.DevMode && config.DevModeOptions.CleanDB { logger.Info("Database: Started clearing...") - cleanDB(db) + cleanDB(db, config) logger.Info("Database: Finished clearing") } diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index eaa023c5b..5ace5a427 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -69,7 +69,11 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { for channelIdx, ci := range si.Channels { sid = (4096 + serverIdx*256) + (16 + channelIdx) - bf.WriteUint16(ci.Port) + if _config.ErupeConfig.DevMode && _config.ErupeConfig.DevModeOptions.ProxyPort != 0 { + bf.WriteUint16(_config.ErupeConfig.DevModeOptions.ProxyPort) + } else { + bf.WriteUint16(ci.Port) + } bf.WriteUint16(16 + uint16(channelIdx)) bf.WriteUint16(ci.MaxPlayers) var currentPlayers uint16 From b32c154fa0404e35ca8959f09689477343fc5cb4 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 22 Nov 2023 19:05:57 +1100 Subject: [PATCH 127/269] decode EnumerateDistItem over multiple versions --- server/channelserver/handlers_distitem.go | 48 ++++++++++++++++------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index e376cadb9..078598719 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -63,33 +63,51 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(0) // Unk bf.WriteUint16(dist.TimesAcceptable) bf.WriteUint16(dist.TimesAccepted) - bf.WriteUint16(0) // Unk + if _config.ErupeConfig.RealClientMode >= _config.G9 { + bf.WriteUint16(0) // Unk + } bf.WriteInt16(dist.MinHR) bf.WriteInt16(dist.MaxHR) bf.WriteInt16(dist.MinSR) bf.WriteInt16(dist.MaxSR) bf.WriteInt16(dist.MinGR) bf.WriteInt16(dist.MaxGR) - bf.WriteUint8(0) - bf.WriteUint16(0) - bf.WriteUint8(0) - bf.WriteUint16(0) - bf.WriteUint16(0) - bf.WriteUint8(0) + if _config.ErupeConfig.RealClientMode >= _config.G7 { + bf.WriteUint8(0) // Unk + } + if _config.ErupeConfig.RealClientMode >= _config.G6 { + bf.WriteUint16(0) // Unk + } + if _config.ErupeConfig.RealClientMode >= _config.G8 { + bf.WriteUint8(0) // Unk + } + if _config.ErupeConfig.RealClientMode >= _config.G7 { + bf.WriteUint16(0) // Unk + bf.WriteUint16(0) // Unk + } + if _config.ErupeConfig.RealClientMode >= _config.G10 { + bf.WriteUint8(0) // Unk + } ps.Uint8(bf, dist.EventName, true) + k := 6 + if _config.ErupeConfig.RealClientMode >= _config.G8 { + k = 13 + } for i := 0; i < 6; i++ { - for j := 0; j < 13; j++ { + for j := 0; j < k; j++ { bf.WriteUint8(0) bf.WriteUint32(0) } } - i := uint8(0) - bf.WriteUint8(i) - if i <= 10 { - for j := uint8(0); j < i; j++ { - bf.WriteUint32(0) - bf.WriteUint32(0) - bf.WriteUint32(0) + if _config.ErupeConfig.RealClientMode >= _config.Z2 { + i := uint8(0) + bf.WriteUint8(i) + if i <= 10 { + for j := uint8(0); j < i; j++ { + bf.WriteUint32(0) + bf.WriteUint32(0) + bf.WriteUint32(0) + } } } } From 5a6ced5a41264927e52c2955f8a48f2deb0b3753 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 22 Nov 2023 21:19:07 +1100 Subject: [PATCH 128/269] document unknown Festa values --- server/channelserver/handlers_festa.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 024cee00d..0ea67fee2 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -309,17 +309,17 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { ps.Uint8(bf, "", true) // Guild Name } - // Unknown values - bf.WriteUint32(1) - bf.WriteUint32(5000) - bf.WriteUint32(2000) - bf.WriteUint32(1000) - bf.WriteUint32(100) - bf.WriteUint16(300) - bf.WriteUint16(200) - bf.WriteUint16(150) - bf.WriteUint16(100) - bf.WriteUint16(50) + // Final bonus rates + bf.WriteUint32(1) // 5000-Infinity? + bf.WriteUint32(5000) // 5000+ souls + bf.WriteUint32(2000) // 2000-4999 souls + bf.WriteUint32(1000) // 1000-1999 souls + bf.WriteUint32(100) // 100-999 souls + bf.WriteUint16(300) // 300% bonus + bf.WriteUint16(200) // 200% bonus + bf.WriteUint16(150) // 150% bonus + bf.WriteUint16(100) // Normal rate + bf.WriteUint16(50) // 50% penalty ps.Uint16(bf, "", false) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) From ab6c86ce8e72fa3a9db233855d652e0655b28605 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 22 Nov 2023 22:54:18 +1100 Subject: [PATCH 129/269] optimise Gacha code & fix rarity bug --- server/channelserver/handlers_shop_gacha.go | 254 +++++++++++--------- 1 file changed, 143 insertions(+), 111 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 38b036f7f..66e7b18ae 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -116,103 +116,113 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { return } - var count uint16 - 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") + rows, 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 } - resp := byteframe.NewByteFrame() - resp.WriteUint32(0) + bf := byteframe.NewByteFrame() var gacha Gacha - for shopEntries.Next() { - err = shopEntries.StructScan(&gacha) - if err != nil { - continue + var gachas []Gacha + for rows.Next() { + err = rows.StructScan(&gacha) + if err == nil { + gachas = append(gachas, gacha) } - resp.WriteUint32(gacha.ID) - resp.WriteBytes(make([]byte, 16)) // Rank restriction - resp.WriteUint32(gacha.MinGR) - resp.WriteUint32(gacha.MinHR) - resp.WriteUint32(0) // only 0 in known packet - ps.Uint8(resp, gacha.Name, true) - 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) - resp.WriteUint16(count) - resp.WriteUint16(count) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + bf.WriteUint16(uint16(len(gachas))) + bf.WriteUint16(uint16(len(gachas))) + for _, g := range gachas { + bf.WriteUint32(g.ID) + bf.WriteUint32(0) // Unknown rank restrictions + bf.WriteUint32(0) + bf.WriteUint32(0) + bf.WriteUint32(0) + bf.WriteUint32(g.MinGR) + bf.WriteUint32(g.MinHR) + bf.WriteUint32(0) // only 0 in known packet + ps.Uint8(bf, g.Name, true) + ps.Uint8(bf, g.URLBanner, false) + ps.Uint8(bf, g.URLFeature, false) + if _config.ErupeConfig.RealClientMode >= _config.G10 { + bf.WriteBool(g.Wide) + ps.Uint8(bf, g.URLThumbnail, false) + } + if g.Recommended { + bf.WriteUint16(2) + } else { + bf.WriteUint16(0) + } + bf.WriteUint8(g.GachaType) + if _config.ErupeConfig.RealClientMode >= _config.G10 { + bf.WriteBool(g.Hidden) + } + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) case 2: // Actual gacha 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, name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID) + rows, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, COALESCE(name, '') AS name 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 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.WriteUint32(gachaEntry.ItemNumber) - bf.WriteUint16(gachaEntry.ItemQuantity) + + var entry GachaEntry + var entries []GachaEntry + var item GachaItem + for rows.Next() { + err = rows.StructScan(&entry) + if err == nil { + entries = append(entries, entry) + } + } + bf.WriteUint16(uint16(len(entries))) + for _, ge := range entries { + var items []GachaItem + bf.WriteUint8(ge.EntryType) + bf.WriteUint32(ge.ID) + bf.WriteUint8(ge.ItemType) + bf.WriteUint32(ge.ItemNumber) + bf.WriteUint16(ge.ItemQuantity) if gachaType >= 4 { // If box bf.WriteUint16(1) } else { - bf.WriteUint16(uint16(gachaEntry.Weight / divisor)) + bf.WriteUint16(uint16(ge.Weight / divisor)) } - bf.WriteUint8(gachaEntry.Rarity) - bf.WriteUint8(gachaEntry.Rolls) + bf.WriteUint8(ge.Rarity) + bf.WriteUint8(ge.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) + rows, err = s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, ge.ID) if err != nil { bf.WriteUint8(0) } else { - for items.Next() { - itemCount++ - items.StructScan(&gachaItem) - temp.WriteUint16(uint16(gachaItem.ItemType)) - temp.WriteUint16(gachaItem.ItemID) - temp.WriteUint16(gachaItem.Quantity) + for rows.Next() { + err = rows.StructScan(&item) + if err == nil { + items = append(items, item) + } } - bf.WriteUint8(itemCount) + bf.WriteUint8(uint8(len(items))) } - bf.WriteUint16(gachaEntry.FrontierPoints) - bf.WriteUint8(gachaEntry.DailyLimit) - if gachaEntry.EntryType < 10 { - ps.Uint8(bf, gachaEntry.Name, true) + bf.WriteUint16(ge.FrontierPoints) + bf.WriteUint8(ge.DailyLimit) + if ge.EntryType < 10 { + ps.Uint8(bf, ge.Name, true) } else { bf.WriteUint8(0) } - bf.WriteBytes(temp.Data()) + for _, gi := range items { + bf.WriteUint16(uint16(gi.ItemType)) + bf.WriteUint16(gi.ItemID) + bf.WriteUint16(gi.Quantity) + } } - bf.Seek(4, 0) - bf.WriteUint16(entryCount) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) case 3: // Hunting Festival Exchange fallthrough @@ -424,7 +434,7 @@ func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha) bf := byteframe.NewByteFrame() - var gachaEntries []GachaEntry + var entries []GachaEntry var entry GachaEntry var rewards []GachaItem var reward GachaItem @@ -433,31 +443,40 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { 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) + + rows, 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) + for rows.Next() { + err = rows.StructScan(&entry) if err != nil { continue } - for items.Next() { - items.StructScan(&reward) + entries = append(entries, entry) + } + + rewardEntries, err := getRandomEntries(entries, rolls, false) + temp := byteframe.NewByteFrame() + for i := range rewardEntries { + rows, 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 rows.Next() { + err = rows.StructScan(&reward) + if err != nil { + continue + } rewards = append(rewards, reward) temp.WriteUint8(reward.ItemType) temp.WriteUint16(reward.ItemID) temp.WriteUint16(reward.Quantity) - temp.WriteUint8(entry.Rarity) + temp.WriteUint8(rewardEntries[i].Rarity) } } + bf.WriteUint8(uint8(len(rewards))) bf.WriteBytes(temp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) @@ -467,7 +486,7 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha) bf := byteframe.NewByteFrame() - var gachaEntries []GachaEntry + var entries []GachaEntry var entry GachaEntry var rewards []GachaItem var reward GachaItem @@ -479,40 +498,49 @@ func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { 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) + + rows, 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) + for rows.Next() { + err = rows.StructScan(&entry) if err != nil { continue } - for items.Next() { - items.StructScan(&reward) + entries = append(entries, entry) + } + + guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType) + rewardEntries, err := getRandomEntries(entries, rolls, false) + temp := byteframe.NewByteFrame() + for i := range rewardEntries { + rows, 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 rows.Next() { + err = rows.StructScan(&reward) + if err != nil { + continue + } rewards = append(rewards, reward) temp.WriteUint8(reward.ItemType) temp.WriteUint16(reward.ItemID) temp.WriteUint16(reward.Quantity) - temp.WriteUint8(entry.Rarity) + temp.WriteUint8(rewardEntries[i].Rarity) } } + bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems))) bf.WriteUint8(uint8(len(rewards))) + for _, item := range guaranteedItems { + bf.WriteUint8(item.ItemType) + bf.WriteUint16(item.ItemID) + bf.WriteUint16(item.Quantity) + bf.WriteUint8(0) + } bf.WriteBytes(temp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) addGachaItem(s, rewards) @@ -561,7 +589,7 @@ func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha) bf := byteframe.NewByteFrame() - var gachaEntries []GachaEntry + var entries []GachaEntry var entry GachaEntry var rewards []GachaItem var reward GachaItem @@ -570,17 +598,18 @@ func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { 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) + rows, 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) + for rows.Next() { + err = rows.StructScan(&entry) + if err == nil { + entries = append(entries, entry) + } } - rewardEntries, err := getRandomEntries(gachaEntries, rolls, true) + rewardEntries, err := getRandomEntries(entries, 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 { @@ -588,16 +617,19 @@ func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { } 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) + err = items.StructScan(&reward) + if err == nil { + rewards = append(rewards, reward) + } } } bf.WriteUint8(uint8(len(rewards))) - bf.WriteBytes(temp.Data()) + for _, r := range rewards { + bf.WriteUint8(r.ItemType) + bf.WriteUint16(r.ItemID) + bf.WriteUint16(r.Quantity) + bf.WriteUint8(0) + } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) addGachaItem(s, rewards) } From 0550fb21b5e38fc53345e7b140714e0bd1968bc4 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 22 Nov 2023 22:59:36 +1100 Subject: [PATCH 130/269] parse & handle PlayFreeGacha --- network/mhfpacket/msg_mhf_play_free_gacha.go | 19 +++++++++++++------ server/channelserver/handlers_shop_gacha.go | 5 ++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/network/mhfpacket/msg_mhf_play_free_gacha.go b/network/mhfpacket/msg_mhf_play_free_gacha.go index c96c5cefc..6d57124f3 100644 --- a/network/mhfpacket/msg_mhf_play_free_gacha.go +++ b/network/mhfpacket/msg_mhf_play_free_gacha.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" ) // MsgMhfPlayFreeGacha represents the MSG_MHF_PLAY_FREE_GACHA -type MsgMhfPlayFreeGacha struct{} +type MsgMhfPlayFreeGacha struct { + AckHandle uint32 + GachaID uint32 + GachaType uint8 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfPlayFreeGacha) Opcode() network.PacketID { @@ -18,7 +22,10 @@ func (m *MsgMhfPlayFreeGacha) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPlayFreeGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.GachaID = bf.ReadUint32() + m.GachaType = bf.ReadUint8() + return nil } // Build builds a binary packet from the current data. diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 66e7b18ae..373eb8028 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -718,5 +718,8 @@ func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) { } 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 + pkt := p.(*mhfpacket.MsgMhfPlayFreeGacha) + bf := byteframe.NewByteFrame() + bf.WriteUint32(1) + doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) } From f9d7f56ccbf3029e6d7137005e0ed697dab2d222 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 22 Nov 2023 23:54:59 +1100 Subject: [PATCH 131/269] fix Frontier Point exchanges --- bundled-schema/FPointItems.sql | 774 ++++++++++---------- patch-schema/14-fix-fpoint-trades.sql | 11 + server/channelserver/handlers_shop_gacha.go | 72 +- 3 files changed, 430 insertions(+), 427 deletions(-) create mode 100644 patch-schema/14-fix-fpoint-trades.sql diff --git a/bundled-schema/FPointItems.sql b/bundled-schema/FPointItems.sql index 918d81e54..7012e6f25 100644 --- a/bundled-schema/FPointItems.sql +++ b/bundled-schema/FPointItems.sql @@ -1,391 +1,391 @@ BEGIN; -INSERT INTO fpoint_items (item_type, item_id, quantity, fpoints, trade_type) VALUES -(7,8895,1,500,0), -(7,8891,1,300,0), -(7,8892,1,300,0), -(7,8893,1,300,0), -(7,8894,1,300,0), -(7,8890,1,10,0), -(7,10354,1,500,0), -(7,11983,1,300,0), -(7,11984,1,300,0), -(7,11985,1,300,0), -(7,11986,1,300,0), -(7,12524,1,500,0), -(7,12470,1,300,0), -(7,12471,1,300,0), -(7,12472,1,300,0), -(7,12473,1,300,0), -(7,2158,2,1,0), -(7,14548,1,500,0), -(7,9509,1,1,0), -(7,9510,1,1,0), -(7,9511,1,1,0), -(7,9512,1,1,0), -(7,9513,1,1,0), -(7,9514,1,1,0), -(7,9515,1,1,0), -(7,10753,1,1,0), -(7,10754,1,1,0), -(7,10755,1,1,0), -(7,10756,1,1,0), -(7,10757,1,1,0), -(7,10758,1,1,0), -(7,10759,1,1,0), -(7,11296,1,1,0), -(7,11297,1,1,0), -(7,11298,1,1,0), -(7,11299,1,1,0), -(7,11300,1,1,0), -(7,12386,1,1,0), -(7,12387,1,1,0), -(7,12388,1,1,0), -(7,12389,1,1,0), -(7,12390,1,1,0), -(7,13034,1,1,0), -(7,13035,1,1,0), -(7,13036,1,1,0), -(7,13037,1,1,0), -(7,13038,1,1,0), -(7,14179,1,1,0), -(7,14180,1,1,0), -(7,14181,1,1,0), -(7,14182,1,1,0), -(7,14183,1,1,0), -(7,13422,1,1,0), -(7,13423,1,1,0), -(7,13424,1,1,0), -(7,13425,1,1,0), -(7,13426,1,1,0), -(7,13427,1,1,0), -(7,9796,1,3,0), -(7,9700,1,3,0), -(7,10380,1,3,0), -(7,10810,1,3,0), -(7,10811,1,3,0), -(7,11436,1,3,0), -(7,9509,1,1,0), -(7,9510,1,1,0), -(7,9511,1,1,0), -(7,9512,1,1,0), -(7,9513,1,1,0), -(7,9514,1,1,0), -(7,9515,1,1,0), -(7,10753,1,1,0), -(7,10754,1,1,0), -(7,10755,1,1,0), -(7,10756,1,1,0), -(7,10757,1,1,0), -(7,10758,1,1,0), -(7,10759,1,1,0), -(7,11296,1,1,0), -(7,11297,1,1,0), -(7,11298,1,1,0), -(7,11299,1,1,0), -(7,11300,1,1,0), -(7,12509,1,3,0), -(7,12386,1,1,0), -(7,12387,1,1,0), -(7,12388,1,1,0), -(7,12389,1,1,0), -(7,12390,1,1,0), -(7,12872,1,3,0), -(7,12873,1,3,0), -(7,12840,1,1,0), -(7,12841,1,1,0), -(7,12874,1,1,0), -(7,12875,1,1,0), -(7,13191,1,3,0), -(7,13177,1,3,0), -(7,13326,1,3,0), -(7,13034,1,1,0), -(7,13035,1,1,0), -(7,13036,1,1,0), -(7,13037,1,1,0), -(7,13038,1,1,0), -(7,13178,1,3,0), -(7,13453,1,3,0), -(7,13449,1,3,0), -(7,13450,1,3,0), -(7,13404,1,3,0), -(7,13422,1,1,0), -(7,13423,1,1,0), -(7,13424,1,1,0), -(7,13425,1,1,0), -(7,13426,1,1,0), -(7,13427,1,1,0), -(7,13791,1,3,0), -(7,14006,1,3,0), -(7,14031,1,3,0), -(7,14032,1,3,0), -(7,13960,1,3,0), -(7,14029,1,3,0), -(7,13956,1,1,0), -(7,13958,1,1,0), -(7,13957,1,1,0), -(7,13959,1,1,0), -(7,13790,1,3,0), -(7,14005,1,3,0), -(7,14010,1,3,0), -(7,14009,1,3,0), -(7,14008,1,3,0), -(7,13965,1,3,0), -(7,14028,1,3,0), -(7,13963,1,3,0), -(7,14026,1,3,0), -(7,13964,1,3,0), -(7,14027,1,3,0), -(7,14069,1,3,0), -(7,14124,1,3,0), -(7,14065,1,1,0), -(7,14066,1,1,0), -(7,14067,1,1,0), -(7,14068,1,1,0), -(7,13962,1,3,0), -(7,14125,1,3,0), -(7,14089,1,3,0), -(7,14090,1,3,0), -(7,14091,1,3,0), -(7,14092,1,3,0), -(7,14194,1,3,0), -(7,14191,1,3,0), -(7,14198,1,3,0), -(7,14197,1,3,0), -(7,14179,1,1,0), -(7,14180,1,1,0), -(7,14181,1,1,0), -(7,14182,1,1,0), -(7,14183,1,1,0), -(7,14196,1,3,0), -(7,14195,1,3,0), -(7,14193,1,3,0), -(7,14192,1,3,0), -(7,14407,1,3,0), -(7,14414,1,3,0), -(7,14406,1,3,0), -(7,14413,1,3,0), -(7,14416,1,3,0), -(7,14549,1,3,0), -(7,14550,1,3,0), -(7,14502,1,3,0), -(7,14507,1,3,0), -(7,14501,1,3,0), -(7,14506,1,3,0), -(7,14500,1,3,0), -(7,14505,1,3,0), -(7,14498,1,3,0), -(7,14659,1,3,0), -(7,14660,1,3,0), -(7,14657,1,1,0), -(7,14658,1,1,0), -(7,11420,1,3,0), -(7,14704,1,3,0), -(7,11288,1,1,0), -(7,11289,1,1,0), -(7,11290,1,1,0), -(7,11291,1,1,0), -(7,10750,1,3,0), -(7,14705,1,3,0), -(7,10633,1,1,0), -(7,10634,1,1,0), -(7,10635,1,1,0), -(7,10636,1,1,0), -(7,14662,1,3,0), -(7,14663,1,3,0), -(7,14665,1,3,0), -(7,14666,1,3,0), -(7,14667,1,3,0), -(7,14668,1,3,0), -(7,14669,1,3,0), -(7,14670,1,3,0), -(7,14671,1,3,0), -(7,14672,1,3,0), -(7,14673,1,3,0), -(7,14674,1,3,0), -(7,14675,1,3,0), -(7,14676,1,3,0), -(7,14677,1,3,0), -(7,14678,1,3,0), -(7,14679,1,3,0), -(7,14680,1,3,0), -(7,14681,1,3,0), -(7,14682,1,3,0), -(7,14683,1,3,0), -(7,14684,1,3,0), -(7,14685,1,3,0), -(7,14686,1,3,0), -(7,14687,1,3,0), -(7,14688,1,3,0), -(7,14689,1,3,0), -(7,14690,1,3,0), -(7,14691,1,3,0), -(7,14692,1,3,0), -(7,14693,1,3,0), -(7,14694,1,3,0), -(7,14695,1,3,0), -(7,14696,1,3,0), -(7,14697,1,3,0), -(7,14698,1,3,0), -(7,14699,1,3,0), -(7,14700,1,3,0), -(7,14314,1,3,0), -(7,14503,1,3,0), -(7,14510,1,3,0), -(7,14904,1,3,0), -(7,14906,1,3,0), -(7,14910,1,1,0), -(7,14912,1,1,0), -(7,14905,1,3,0), -(7,14907,1,3,0), -(7,14911,1,1,0), -(7,14909,1,1,0), -(7,14855,1,3,0), -(7,14894,1,3,0), -(7,14913,1,3,0), -(7,14914,1,3,0), -(7,14891,1,3,0), -(7,14895,1,3,0), -(7,15027,1,3,0), -(7,15028,1,3,0), -(7,15026,1,1,0), -(7,15025,1,1,0), -(7,15024,1,1,0), -(7,15023,1,1,0), -(7,15064,1,3,0), -(7,15065,1,3,0), -(7,15030,1,3,0), -(7,15031,1,3,0), -(7,15062,1,3,0), -(7,15063,1,3,0), -(7,15066,1,3,0), -(7,15067,1,3,0), -(7,15061,1,3,0), -(7,15060,1,3,0), -(7,1227,1,2,0), -(7,13176,1,2,0), -(7,4360,1,2,0), -(7,4358,1,1,0), -(7,15118,1,3,0), -(7,15119,1,3,0), -(7,15113,1,3,0), -(7,15114,1,3,0), -(7,15115,1,3,0), -(7,15116,1,3,0), -(7,15220,1,3,0), -(7,15221,1,3,0), -(7,14126,1,3,0), -(7,15222,1,3,0), -(7,15223,1,3,0), -(7,15224,1,3,0), -(7,15225,1,3,0), -(7,15524,1,3,0), -(7,15525,1,3,0), -(7,15507,1,3,0), -(7,15508,1,3,0), -(7,15285,1,3,0), -(7,15286,1,3,0), -(7,15281,1,1,0), -(7,15282,1,1,0), -(7,15283,1,1,0), -(7,15284,1,1,0), -(7,15776,1,3,0), -(7,15777,1,3,0), -(7,15774,1,3,0), -(7,15775,1,3,0), -(7,15823,1,3,0), -(7,15824,1,3,0), -(7,15343,1,3,0), -(7,15342,1,3,0), -(7,15341,1,3,0), -(7,15340,1,3,0), -(7,15339,1,3,0), -(7,15338,1,3,0), -(7,15337,1,3,0), -(7,15336,1,3,0), -(7,15335,1,3,0), -(7,15334,1,3,0), -(7,15333,1,3,0), -(7,15332,1,3,0), -(7,15331,1,3,0), -(7,15330,1,3,0), -(7,15329,1,3,0), -(7,15328,1,3,0), -(7,15327,1,3,0), -(7,15326,1,3,0), -(7,15325,1,3,0), -(7,15324,1,3,0), -(7,15323,1,3,0), -(7,15322,1,3,0), -(7,15321,1,3,0), -(7,15314,1,3,0), -(7,15312,1,3,0), -(7,15311,1,3,0), -(7,15306,1,3,0), -(7,15307,1,3,0), -(7,15308,1,3,0), -(7,15309,1,3,0), -(7,15310,1,3,0), -(7,15305,1,3,0), -(7,15304,1,3,0), -(7,15303,1,3,0), -(7,15302,1,3,0), -(7,15301,1,3,0), -(7,15300,1,3,0), -(7,15299,1,3,0), -(7,15298,1,3,0), -(7,15297,1,3,0), -(7,15296,1,3,0), -(7,15295,1,3,0), -(7,15293,1,3,0), -(7,15294,1,3,0), -(7,15292,1,3,0), -(7,15291,1,3,0), -(7,15290,1,3,0), -(7,15289,1,3,0), -(7,15315,1,3,0), -(7,15316,1,3,0), -(7,15317,1,3,0), -(7,15318,1,3,0), -(7,15319,1,3,0), -(7,15320,1,3,0), -(7,15819,1,3,0), -(7,15820,1,3,0), -(7,15821,1,3,0), -(7,15822,1,3,0), -(7,16450,1,3,0), -(7,16451,1,3,0), -(7,16459,1,1,0), -(7,16460,1,1,0), -(7,16461,1,1,0), -(7,16462,1,1,0), -(7,16463,1,1,0), -(7,16464,1,1,0), -(7,16465,1,1,0), -(7,16466,1,1,0), -(7,16467,1,1,0), -(7,16468,1,1,0), -(7,16469,1,1,0), -(7,16470,1,1,0), -(7,16471,1,1,0), -(7,16472,1,1,0), -(7,16454,1,3,0), -(7,16455,1,3,0), -(7,16442,1,3,0), -(7,16443,1,3,0), -(7,16342,1,3,0), -(7,16343,1,3,0), -(7,16444,1,3,0), -(7,16445,1,3,0), -(7,16344,1,3,0), -(7,16345,1,3,0), -(7,16352,1,3,0), -(7,16353,1,3,0), -(7,16446,1,3,0), -(7,16447,1,3,0), -(7,16448,1,3,0), -(7,16449,1,3,0), -(7,16348,1,3,0), -(7,16349,1,3,0); +INSERT INTO fpoint_items (item_type, item_id, quantity, fpoints, buyable) VALUES +(7,8895,1,500,true), +(7,8891,1,300,true), +(7,8892,1,300,true), +(7,8893,1,300,true), +(7,8894,1,300,true), +(7,8890,1,10,true), +(7,10354,1,500,true), +(7,11983,1,300,true), +(7,11984,1,300,true), +(7,11985,1,300,true), +(7,11986,1,300,true), +(7,12524,1,500,true), +(7,12470,1,300,true), +(7,12471,1,300,true), +(7,12472,1,300,true), +(7,12473,1,300,true), +(7,2158,2,1,true), +(7,14548,1,500,true), +(7,9509,1,1,true), +(7,9510,1,1,true), +(7,9511,1,1,true), +(7,9512,1,1,true), +(7,9513,1,1,true), +(7,9514,1,1,true), +(7,9515,1,1,true), +(7,10753,1,1,true), +(7,10754,1,1,true), +(7,10755,1,1,true), +(7,10756,1,1,true), +(7,10757,1,1,true), +(7,10758,1,1,true), +(7,10759,1,1,true), +(7,11296,1,1,true), +(7,11297,1,1,true), +(7,11298,1,1,true), +(7,11299,1,1,true), +(7,11300,1,1,true), +(7,12386,1,1,true), +(7,12387,1,1,true), +(7,12388,1,1,true), +(7,12389,1,1,true), +(7,12390,1,1,true), +(7,13034,1,1,true), +(7,13035,1,1,true), +(7,13036,1,1,true), +(7,13037,1,1,true), +(7,13038,1,1,true), +(7,14179,1,1,true), +(7,14180,1,1,true), +(7,14181,1,1,true), +(7,14182,1,1,true), +(7,14183,1,1,true), +(7,13422,1,1,true), +(7,13423,1,1,true), +(7,13424,1,1,true), +(7,13425,1,1,true), +(7,13426,1,1,true), +(7,13427,1,1,true), +(7,9796,1,3,false), +(7,9700,1,3,false), +(7,10380,1,3,false), +(7,10810,1,3,false), +(7,10811,1,3,false), +(7,11436,1,3,false), +(7,9509,1,1,false), +(7,9510,1,1,false), +(7,9511,1,1,false), +(7,9512,1,1,false), +(7,9513,1,1,false), +(7,9514,1,1,false), +(7,9515,1,1,false), +(7,10753,1,1,false), +(7,10754,1,1,false), +(7,10755,1,1,false), +(7,10756,1,1,false), +(7,10757,1,1,false), +(7,10758,1,1,false), +(7,10759,1,1,false), +(7,11296,1,1,false), +(7,11297,1,1,false), +(7,11298,1,1,false), +(7,11299,1,1,false), +(7,11300,1,1,false), +(7,12509,1,3,false), +(7,12386,1,1,false), +(7,12387,1,1,false), +(7,12388,1,1,false), +(7,12389,1,1,false), +(7,12390,1,1,false), +(7,12872,1,3,false), +(7,12873,1,3,false), +(7,12840,1,1,false), +(7,12841,1,1,false), +(7,12874,1,1,false), +(7,12875,1,1,false), +(7,13191,1,3,false), +(7,13177,1,3,false), +(7,13326,1,3,false), +(7,13034,1,1,false), +(7,13035,1,1,false), +(7,13036,1,1,false), +(7,13037,1,1,false), +(7,13038,1,1,false), +(7,13178,1,3,false), +(7,13453,1,3,false), +(7,13449,1,3,false), +(7,13450,1,3,false), +(7,13404,1,3,false), +(7,13422,1,1,false), +(7,13423,1,1,false), +(7,13424,1,1,false), +(7,13425,1,1,false), +(7,13426,1,1,false), +(7,13427,1,1,false), +(7,13791,1,3,false), +(7,14006,1,3,false), +(7,14031,1,3,false), +(7,14032,1,3,false), +(7,13960,1,3,false), +(7,14029,1,3,false), +(7,13956,1,1,false), +(7,13958,1,1,false), +(7,13957,1,1,false), +(7,13959,1,1,false), +(7,13790,1,3,false), +(7,14005,1,3,false), +(7,14010,1,3,false), +(7,14009,1,3,false), +(7,14008,1,3,false), +(7,13965,1,3,false), +(7,14028,1,3,false), +(7,13963,1,3,false), +(7,14026,1,3,false), +(7,13964,1,3,false), +(7,14027,1,3,false), +(7,14069,1,3,false), +(7,14124,1,3,false), +(7,14065,1,1,false), +(7,14066,1,1,false), +(7,14067,1,1,false), +(7,14068,1,1,false), +(7,13962,1,3,false), +(7,14125,1,3,false), +(7,14089,1,3,false), +(7,14090,1,3,false), +(7,14091,1,3,false), +(7,14092,1,3,false), +(7,14194,1,3,false), +(7,14191,1,3,false), +(7,14198,1,3,false), +(7,14197,1,3,false), +(7,14179,1,1,false), +(7,14180,1,1,false), +(7,14181,1,1,false), +(7,14182,1,1,false), +(7,14183,1,1,false), +(7,14196,1,3,false), +(7,14195,1,3,false), +(7,14193,1,3,false), +(7,14192,1,3,false), +(7,14407,1,3,false), +(7,14414,1,3,false), +(7,14406,1,3,false), +(7,14413,1,3,false), +(7,14416,1,3,false), +(7,14549,1,3,false), +(7,14550,1,3,false), +(7,14502,1,3,false), +(7,14507,1,3,false), +(7,14501,1,3,false), +(7,14506,1,3,false), +(7,14500,1,3,false), +(7,14505,1,3,false), +(7,14498,1,3,false), +(7,14659,1,3,false), +(7,14660,1,3,false), +(7,14657,1,1,false), +(7,14658,1,1,false), +(7,11420,1,3,false), +(7,14704,1,3,false), +(7,11288,1,1,false), +(7,11289,1,1,false), +(7,11290,1,1,false), +(7,11291,1,1,false), +(7,10750,1,3,false), +(7,14705,1,3,false), +(7,10633,1,1,false), +(7,10634,1,1,false), +(7,10635,1,1,false), +(7,10636,1,1,false), +(7,14662,1,3,false), +(7,14663,1,3,false), +(7,14665,1,3,false), +(7,14666,1,3,false), +(7,14667,1,3,false), +(7,14668,1,3,false), +(7,14669,1,3,false), +(7,14670,1,3,false), +(7,14671,1,3,false), +(7,14672,1,3,false), +(7,14673,1,3,false), +(7,14674,1,3,false), +(7,14675,1,3,false), +(7,14676,1,3,false), +(7,14677,1,3,false), +(7,14678,1,3,false), +(7,14679,1,3,false), +(7,14680,1,3,false), +(7,14681,1,3,false), +(7,14682,1,3,false), +(7,14683,1,3,false), +(7,14684,1,3,false), +(7,14685,1,3,false), +(7,14686,1,3,false), +(7,14687,1,3,false), +(7,14688,1,3,false), +(7,14689,1,3,false), +(7,14690,1,3,false), +(7,14691,1,3,false), +(7,14692,1,3,false), +(7,14693,1,3,false), +(7,14694,1,3,false), +(7,14695,1,3,false), +(7,14696,1,3,false), +(7,14697,1,3,false), +(7,14698,1,3,false), +(7,14699,1,3,false), +(7,14700,1,3,false), +(7,14314,1,3,false), +(7,14503,1,3,false), +(7,14510,1,3,false), +(7,14904,1,3,false), +(7,14906,1,3,false), +(7,14910,1,1,false), +(7,14912,1,1,false), +(7,14905,1,3,false), +(7,14907,1,3,false), +(7,14911,1,1,false), +(7,14909,1,1,false), +(7,14855,1,3,false), +(7,14894,1,3,false), +(7,14913,1,3,false), +(7,14914,1,3,false), +(7,14891,1,3,false), +(7,14895,1,3,false), +(7,15027,1,3,false), +(7,15028,1,3,false), +(7,15026,1,1,false), +(7,15025,1,1,false), +(7,15024,1,1,false), +(7,15023,1,1,false), +(7,15064,1,3,false), +(7,15065,1,3,false), +(7,15030,1,3,false), +(7,15031,1,3,false), +(7,15062,1,3,false), +(7,15063,1,3,false), +(7,15066,1,3,false), +(7,15067,1,3,false), +(7,15061,1,3,false), +(7,15060,1,3,false), +(7,1227,1,2,false), +(7,13176,1,2,false), +(7,4360,1,2,false), +(7,4358,1,1,false), +(7,15118,1,3,false), +(7,15119,1,3,false), +(7,15113,1,3,false), +(7,15114,1,3,false), +(7,15115,1,3,false), +(7,15116,1,3,false), +(7,15220,1,3,false), +(7,15221,1,3,false), +(7,14126,1,3,false), +(7,15222,1,3,false), +(7,15223,1,3,false), +(7,15224,1,3,false), +(7,15225,1,3,false), +(7,15524,1,3,false), +(7,15525,1,3,false), +(7,15507,1,3,false), +(7,15508,1,3,false), +(7,15285,1,3,false), +(7,15286,1,3,false), +(7,15281,1,1,false), +(7,15282,1,1,false), +(7,15283,1,1,false), +(7,15284,1,1,false), +(7,15776,1,3,false), +(7,15777,1,3,false), +(7,15774,1,3,false), +(7,15775,1,3,false), +(7,15823,1,3,false), +(7,15824,1,3,false), +(7,15343,1,3,false), +(7,15342,1,3,false), +(7,15341,1,3,false), +(7,15340,1,3,false), +(7,15339,1,3,false), +(7,15338,1,3,false), +(7,15337,1,3,false), +(7,15336,1,3,false), +(7,15335,1,3,false), +(7,15334,1,3,false), +(7,15333,1,3,false), +(7,15332,1,3,false), +(7,15331,1,3,false), +(7,15330,1,3,false), +(7,15329,1,3,false), +(7,15328,1,3,false), +(7,15327,1,3,false), +(7,15326,1,3,false), +(7,15325,1,3,false), +(7,15324,1,3,false), +(7,15323,1,3,false), +(7,15322,1,3,false), +(7,15321,1,3,false), +(7,15314,1,3,false), +(7,15312,1,3,false), +(7,15311,1,3,false), +(7,15306,1,3,false), +(7,15307,1,3,false), +(7,15308,1,3,false), +(7,15309,1,3,false), +(7,15310,1,3,false), +(7,15305,1,3,false), +(7,15304,1,3,false), +(7,15303,1,3,false), +(7,15302,1,3,false), +(7,15301,1,3,false), +(7,15300,1,3,false), +(7,15299,1,3,false), +(7,15298,1,3,false), +(7,15297,1,3,false), +(7,15296,1,3,false), +(7,15295,1,3,false), +(7,15293,1,3,false), +(7,15294,1,3,false), +(7,15292,1,3,false), +(7,15291,1,3,false), +(7,15290,1,3,false), +(7,15289,1,3,false), +(7,15315,1,3,false), +(7,15316,1,3,false), +(7,15317,1,3,false), +(7,15318,1,3,false), +(7,15319,1,3,false), +(7,15320,1,3,false), +(7,15819,1,3,false), +(7,15820,1,3,false), +(7,15821,1,3,false), +(7,15822,1,3,false), +(7,16450,1,3,false), +(7,16451,1,3,false), +(7,16459,1,1,false), +(7,16460,1,1,false), +(7,16461,1,1,false), +(7,16462,1,1,false), +(7,16463,1,1,false), +(7,16464,1,1,false), +(7,16465,1,1,false), +(7,16466,1,1,false), +(7,16467,1,1,false), +(7,16468,1,1,false), +(7,16469,1,1,false), +(7,16470,1,1,false), +(7,16471,1,1,false), +(7,16472,1,1,false), +(7,16454,1,3,false), +(7,16455,1,3,false), +(7,16442,1,3,false), +(7,16443,1,3,false), +(7,16342,1,3,false), +(7,16343,1,3,false), +(7,16444,1,3,false), +(7,16445,1,3,false), +(7,16344,1,3,false), +(7,16345,1,3,false), +(7,16352,1,3,false), +(7,16353,1,3,false), +(7,16446,1,3,false), +(7,16447,1,3,false), +(7,16448,1,3,false), +(7,16449,1,3,false), +(7,16348,1,3,false), +(7,16349,1,3,false); END; \ No newline at end of file diff --git a/patch-schema/14-fix-fpoint-trades.sql b/patch-schema/14-fix-fpoint-trades.sql new file mode 100644 index 000000000..c4e698655 --- /dev/null +++ b/patch-schema/14-fix-fpoint-trades.sql @@ -0,0 +1,11 @@ +BEGIN; + +DELETE FROM public.fpoint_items; +ALTER TABLE IF EXISTS public.fpoint_items ALTER COLUMN item_type SET NOT NULL; +ALTER TABLE IF EXISTS public.fpoint_items ALTER COLUMN item_id SET NOT NULL; +ALTER TABLE IF EXISTS public.fpoint_items ALTER COLUMN quantity SET NOT NULL; +ALTER TABLE IF EXISTS public.fpoint_items ALTER COLUMN fpoints SET NOT NULL; +ALTER TABLE IF EXISTS public.fpoint_items DROP COLUMN IF EXISTS trade_type; +ALTER TABLE IF EXISTS public.fpoint_items ADD COLUMN buyable boolean NOT NULL; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 373eb8028..708f424e5 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -664,57 +664,49 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) } +type FPointExchange struct { + ID uint32 `db:"id"` + ItemType uint8 `db:"item_type"` + ItemID uint16 `db:"item_id"` + Quantity uint16 `db:"quantity"` + FPoints uint16 `db:"fpoints"` + Buyable bool `db:"buyable"` +} + func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetFpointExchangeList) - resp := byteframe.NewByteFrame() - resp.WriteUint32(0) - var buyables, sellables uint16 - var id uint32 - var itemType uint8 - var itemID, quantity, fPoints uint16 - buyRows, err := s.server.db.Query("SELECT id,item_type,item_id,quantity,fpoints FROM fpoint_items WHERE trade_type=0") + bf := byteframe.NewByteFrame() + var exchange FPointExchange + var exchanges []FPointExchange + var buyables uint16 + rows, err := s.server.db.Queryx(`SELECT id, item_type, item_id, quantity, fpoints, buyable FROM fpoint_items ORDER BY buyable DESC`) if err == nil { - for buyRows.Next() { - err = buyRows.Scan(&id, &itemType, &itemID, &quantity, &fPoints) + for rows.Next() { + err = rows.StructScan(&exchange) if err != nil { continue } - resp.WriteUint32(id) - resp.WriteUint16(0) - resp.WriteUint16(0) - resp.WriteUint16(0) - resp.WriteUint8(itemType) - resp.WriteUint16(itemID) - resp.WriteUint16(quantity) - resp.WriteUint16(fPoints) - buyables++ - } - } - - sellRows, err := s.server.db.Query("SELECT id,item_type,item_id,quantity,fpoints FROM fpoint_items WHERE trade_type=1") - if err == nil { - for sellRows.Next() { - err = sellRows.Scan(&id, &itemType, &itemID, &quantity, &fPoints) - if err != nil { - continue + if exchange.Buyable { + buyables++ } - resp.WriteUint32(id) - resp.WriteUint16(0) - resp.WriteUint16(0) - resp.WriteUint16(0) - resp.WriteUint8(itemType) - resp.WriteUint16(itemID) - resp.WriteUint16(quantity) - resp.WriteUint16(fPoints) - sellables++ + exchanges = append(exchanges, exchange) } } - resp.Seek(0, 0) - resp.WriteUint16(buyables) - resp.WriteUint16(sellables) + bf.WriteUint16(uint16(len(exchanges))) + bf.WriteUint16(buyables) + for _, e := range exchanges { + bf.WriteUint32(e.ID) + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint8(e.ItemType) + bf.WriteUint16(e.ItemID) + bf.WriteUint16(e.Quantity) + bf.WriteUint16(e.FPoints) + } - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) { From aa77b522331f37a76633c5058957d572ebd354e9 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 23 Nov 2023 00:00:23 +1100 Subject: [PATCH 132/269] add legacy support for Frontier Point exchanges --- server/channelserver/handlers_shop_gacha.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 708f424e5..c18119c6e 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -693,8 +693,13 @@ func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) { exchanges = append(exchanges, exchange) } } - bf.WriteUint16(uint16(len(exchanges))) - bf.WriteUint16(buyables) + if _config.ErupeConfig.RealClientMode <= _config.Z2 { + bf.WriteUint8(uint8(len(exchanges))) + bf.WriteUint8(uint8(buyables)) + } else { + bf.WriteUint16(uint16(len(exchanges))) + bf.WriteUint16(buyables) + } for _, e := range exchanges { bf.WriteUint32(e.ID) bf.WriteUint16(0) From a0b50bdf8d58784ca8698316fe3c72f94e268fd6 Mon Sep 17 00:00:00 2001 From: rockisch Date: Wed, 15 Nov 2023 18:33:12 -0300 Subject: [PATCH 133/269] Implement final changes for custom launcher --- config.json | 3 +- config/config.go | 5 +- server/signv2server/dbutils.go | 10 ++ server/signv2server/endpoints.go | 176 +++++++++++++++++++++++---- server/signv2server/signv2_server.go | 1 + 5 files changed, 166 insertions(+), 29 deletions(-) diff --git a/config.json b/config.json index e3e2b7ea4..93ea5aec4 100644 --- a/config.json +++ b/config.json @@ -133,7 +133,8 @@ }, "SignV2": { "Enabled": false, - "Port": 8080 + "Port": 8080, + "PatchServer": "" }, "Channel": { "Enabled": true diff --git a/config/config.go b/config/config.go index ee0e6b377..a9103e6b7 100644 --- a/config/config.go +++ b/config/config.go @@ -195,8 +195,9 @@ type Sign struct { // SignV2 holds the new sign server config type SignV2 struct { - Enabled bool - Port int + Enabled bool + Port int + PatchServer string } type Channel struct { diff --git a/server/signv2server/dbutils.go b/server/signv2server/dbutils.go index fb9711da9..dde729ac9 100644 --- a/server/signv2server/dbutils.go +++ b/server/signv2server/dbutils.go @@ -122,3 +122,13 @@ func (s *Server) getReturnExpiry(uid uint32) time.Time { s.db.Exec("UPDATE users SET last_login=$1 WHERE id=$2", time.Now(), uid) return returnExpiry } + +func (s *Server) exportSave(ctx context.Context, uid uint32, cid uint32) (map[string]interface{}, error) { + row := s.db.QueryRowxContext(ctx, "SELECT * FROM characters WHERE id=$1 AND user_id=$2", cid, uid) + result := make(map[string]interface{}) + err := row.MapScan(result) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index c087a70df..9ca0488f3 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -14,15 +14,33 @@ import ( "golang.org/x/crypto/bcrypt" ) +const ( + NotificationDefault = iota + NotificationNew +) + +type LauncherBanner struct { + Src string `json:"src"` + Link string `json:"link"` +} + type LauncherMessage struct { Message string `json:"message"` Date int64 `json:"date"` Link string `json:"link"` + Kind int `json:"kind"` +} + +type LauncherLink struct { + Name string `json:"name"` + Link string `json:"link"` + Icon string `json:"icon"` } type LauncherResponse struct { - Important []LauncherMessage `json:"important"` - Normal []LauncherMessage `json:"normal"` + Banners []LauncherBanner `json:"banners"` + Messages []LauncherMessage `json:"messages"` + Links []LauncherLink `json:"links"` } type User struct { @@ -37,7 +55,7 @@ type Character struct { Weapon uint32 `json:"weapon" db:"weapon_type"` HR uint32 `json:"hr" db:"hrp"` GR uint32 `json:"gr"` - LastLogin int64 `json:"lastLogin" db:"last_login"` + LastLogin int32 `json:"lastLogin" db:"last_login"` } type MezFes struct { @@ -53,10 +71,11 @@ type AuthData struct { CurrentTS uint32 `json:"currentTs"` ExpiryTS uint32 `json:"expiryTs"` EntranceCount uint32 `json:"entranceCount"` - Notifications []string `json:"notifications"` + Notices []string `json:"notices"` User User `json:"user"` Characters []Character `json:"characters"` MezFes *MezFes `json:"mezFes"` + PatchServer string `json:"patchServer"` } func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData { @@ -68,7 +87,14 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, Rights: userRights, Token: userToken, }, - Characters: characters, + Characters: characters, + PatchServer: s.erupeConfig.SignV2.PatchServer, + Notices: []string{}, + } + if s.erupeConfig.DevModeOptions.MaxLauncherHR { + for i := range resp.Characters { + resp.Characters[i].HR = 7 + } } if s.erupeConfig.DevModeOptions.MezFesEvent { stalls := []uint32{10, 3, 6, 9, 4, 8, 5, 7} @@ -85,38 +111,102 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, } } if !s.erupeConfig.HideLoginNotice { - resp.Notifications = append(resp.Notifications, strings.Join(s.erupeConfig.LoginNotices[:], "")) + resp.Notices = append(resp.Notices, strings.Join(s.erupeConfig.LoginNotices[:], "")) } return resp } func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) { var respData LauncherResponse - respData.Important = []LauncherMessage{ + respData.Banners = []LauncherBanner{ { - Message: "Server Update 9 Released!", - Date: time.Date(2022, 8, 2, 0, 0, 0, 0, time.UTC).Unix(), + Src: "http://zerulight.cc/launcher/en/images/bnr/1030_0.jpg", + Link: "http://localhost", + }, + { + Src: "http://zerulight.cc/launcher/en/images/bnr/0801_3.jpg", + Link: "http://localhost", + }, + { + Src: "http://zerulight.cc/launcher/en/images/bnr/0705_3.jpg", + Link: "http://localhost", + }, + { + Src: "http://zerulight.cc/launcher/en/images/bnr/1211_11.jpg", + Link: "http://localhost", + }, + { + Src: "http://zerulight.cc/launcher/en/images/bnr/reg_mezefes.jpg", + Link: "http://localhost", + }, + } + respData.Messages = []LauncherMessage{ + { + Message: "Server Update 9.2 — Quest fixes,\nGacha support and tons of bug fixes!", + Date: time.Date(2023, 4, 1, 0, 0, 0, 0, time.UTC).Unix(), Link: "https://discord.com/channels/368424389416583169/929509970624532511/1003985850255818762", + Kind: NotificationNew, }, { - Message: "Eng 2.0 & Ravi Patch Released!", - Date: time.Date(2022, 5, 3, 0, 0, 0, 0, time.UTC).Unix(), + Message: "English Patch 4.1 — Fix \"Unknown\" weapons, NPC changes & Diva Support.", + Date: time.Date(2023, 2, 27, 0, 0, 0, 0, time.UTC).Unix(), Link: "https://discord.com/channels/368424389416583169/929509970624532511/969305400795078656", + Kind: NotificationNew, }, { - Message: "Launcher Patch V1.0 Released!", - Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(), + Message: "Server Update 9.1! Hunter Festival, Return worlds and NetCafe are back!", + Date: time.Date(2022, 11, 4, 0, 0, 0, 0, time.UTC).Unix(), Link: "https://discord.com/channels/368424389416583169/929509970624532511/969286397301248050", + Kind: NotificationDefault, }, - } - respData.Normal = []LauncherMessage{ { - Message: "Join the community Discord for updates!", - Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(), + Message: "Deerby & Supream have been updating Ferias! You can find any and all MHF info/data there!", + Date: time.Date(2022, 7, 7, 0, 0, 0, 0, time.UTC).Unix(), Link: "https://discord.gg/CFnzbhQ", + Kind: NotificationDefault, + }, + { + Message: "Server hosts, get Chakratos' Save Manager! Use it to enhance your Erupe server!", + Date: time.Date(2022, 7, 7, 0, 0, 0, 0, time.UTC).Unix(), + Link: "https://discord.gg/CFnzbhQ", + Kind: NotificationDefault, + }, + { + Message: "Server Update 9.0 is out! Enjoy MezFes and all the other new content!", + Date: time.Date(2022, 8, 2, 0, 0, 0, 0, time.UTC).Unix(), + Link: "https://discord.gg/CFnzbhQ", + Kind: NotificationDefault, + }, + { + Message: "English Community Translation 2 is here! Get the latest translation patch!", + Date: time.Date(2022, 5, 4, 0, 0, 0, 0, time.UTC).Unix(), + Link: "https://discord.gg/CFnzbhQ", + Kind: NotificationDefault, + }, + { + Message: "Join the community Discord for future updates!", + Date: time.Date(2022, 5, 4, 0, 0, 0, 0, time.UTC).Unix(), + Link: "https://discord.gg/CFnzbhQ", + Kind: NotificationDefault, + }, + } + respData.Links = []LauncherLink{ + { + Name: "GitHub", + Link: "https://github.com/ZeruLight/Erupe", + Icon: "https://cdn-icons-png.flaticon.com/512/25/25231.png", + }, + { + Name: "Discord", + Link: "https://discord.gg/DnwcpXM488", + Icon: "https://assets-global.website-files.com/6257adef93867e50d84d30e2/636e0a6a49cf127bf92de1e2_icon_clyde_blurple_RGB.png", + }, + { + Name: "Equal Dragon Weapon Info", + Link: "https://discord.gg/DnwcpXM488", + Icon: "", }, } - w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } @@ -130,7 +220,6 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) w.WriteHeader(400) - w.Write([]byte("Invalid data received")) return } var ( @@ -141,7 +230,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { err := s.db.QueryRow("SELECT id, password, rights FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password, &userRights) if err == sql.ErrNoRows { w.WriteHeader(400) - w.Write([]byte("Username does not exist")) + w.Write([]byte("username-error")) return } else if err != nil { s.logger.Warn("SQL query error", zap.Error(err)) @@ -150,7 +239,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { } if bcrypt.CompareHashAndPassword([]byte(password), []byte(reqData.Password)) != nil { w.WriteHeader(400) - w.Write([]byte("Your password is incorrect")) + w.Write([]byte("password-error")) return } @@ -166,8 +255,10 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) return } + if characters == nil { + characters = []Character{} + } respData := s.newAuthData(userID, userRights, userToken, characters) - w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } @@ -181,7 +272,10 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) w.WriteHeader(400) - w.Write([]byte("Invalid data received")) + return + } + if reqData.Username == "" || reqData.Password == "" { + w.WriteHeader(400) return } s.logger.Info("Creating account", zap.String("username", reqData.Username)) @@ -190,7 +284,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { var pqErr *pq.Error if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" { w.WriteHeader(400) - w.Write([]byte("User already exists")) + w.Write([]byte("username-exists-error")) return } s.logger.Error("Error checking user", zap.Error(err), zap.String("username", reqData.Username)) @@ -205,6 +299,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { return } respData := s.newAuthData(userID, userRights, userToken, []Character{}) + w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } @@ -216,7 +311,6 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) w.WriteHeader(400) - w.Write([]byte("Invalid data received")) return } @@ -231,6 +325,10 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) return } + if s.erupeConfig.DevModeOptions.MaxLauncherHR { + character.HR = 7 + } + w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(character) } @@ -243,7 +341,6 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) w.WriteHeader(400) - w.Write([]byte("Invalid data received")) return } userID, err := s.userIDFromToken(ctx, reqData.Token) @@ -256,5 +353,32 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) return } + w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(struct{}{}) } + +func (s *Server) ExportSave(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var reqData struct { + Token string `json:"token"` + CharID uint32 `json:"charId"` + } + if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { + s.logger.Error("JSON decode error", zap.Error(err)) + w.WriteHeader(400) + return + } + userID, err := s.userIDFromToken(ctx, reqData.Token) + if err != nil { + w.WriteHeader(401) + return + } + save, err := s.exportSave(ctx, userID, reqData.CharID) + if err != nil { + s.logger.Error("Failed to export save", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharID)) + w.WriteHeader(500) + return + } + w.Header().Add("Content-Type", "application/json") + json.NewEncoder(w).Encode(save) +} diff --git a/server/signv2server/signv2_server.go b/server/signv2server/signv2_server.go index c6db00b09..fedbabba2 100644 --- a/server/signv2server/signv2_server.go +++ b/server/signv2server/signv2_server.go @@ -51,6 +51,7 @@ func (s *Server) Start() error { r.HandleFunc("/register", s.Register) r.HandleFunc("/character/create", s.CreateCharacter) r.HandleFunc("/character/delete", s.DeleteCharacter) + r.HandleFunc("/character/export", s.ExportSave) handler := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))(r) s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, handler) s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.SignV2.Port) From 2481d4871fda474eb20f3bbfac9fe95cc23d9284 Mon Sep 17 00:00:00 2001 From: rockisch Date: Thu, 23 Nov 2023 20:54:27 -0300 Subject: [PATCH 134/269] Ensure save export can be changed in the future --- server/signv2server/endpoints.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index 9ca0488f3..a2ab3b151 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -78,6 +78,10 @@ type AuthData struct { PatchServer string `json:"patchServer"` } +type ExportData struct { + Character map[string]interface{} `json:"character"` +} + func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData { resp := AuthData{ CurrentTS: uint32(channelserver.TimeAdjusted().Unix()), @@ -373,12 +377,15 @@ func (s *Server) ExportSave(w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) return } - save, err := s.exportSave(ctx, userID, reqData.CharID) + character, err := s.exportSave(ctx, userID, reqData.CharID) if err != nil { s.logger.Error("Failed to export save", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharID)) w.WriteHeader(500) return } + save := ExportData{ + Character: character, + } w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(save) } From 0481b15b9bb11e5929623a170a99be96092c877d Mon Sep 17 00:00:00 2001 From: rockisch Date: Thu, 23 Nov 2023 22:12:36 -0300 Subject: [PATCH 135/269] Allow signv2 response to be configured --- config.json | 5 +- config/config.go | 21 ++++ server/channelserver/sys_channel_server.go | 2 +- server/signv2server/endpoints.go | 118 ++------------------- 4 files changed, 33 insertions(+), 113 deletions(-) diff --git a/config.json b/config.json index 93ea5aec4..9fcb4ef68 100644 --- a/config.json +++ b/config.json @@ -134,7 +134,10 @@ "SignV2": { "Enabled": false, "Port": 8080, - "PatchServer": "" + "PatchServer": "", + "Banners": [], + "Messages": [], + "Links": [] }, "Channel": { "Enabled": true diff --git a/config/config.go b/config/config.go index a9103e6b7..e992d6594 100644 --- a/config/config.go +++ b/config/config.go @@ -198,6 +198,27 @@ type SignV2 struct { Enabled bool Port int PatchServer string + Banners []SignV2Banner + Messages []SignV2Message + Links []SignV2Link +} + +type SignV2Banner struct { + Src string `json:"src"` // Displayed image URL + Link string `json:"link"` // Link accessed on click +} + +type SignV2Message struct { + Message string `json:"message"` // Displayed message + Date int64 `json:"date"` // Displayed date + Kind int `json:"kind"` // 0 for 'Default', 1 for 'New' + Link string `json:"link"` // Link accessed on click +} + +type SignV2Link struct { + Name string `json:"name"` // Displayed name + Icon string `json:"icon"` // Displayed icon. It will be cast as a monochrome color as long as it is transparent. + Link string `json:"link"` // Link accessed on click } type Channel struct { diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index d12d713f5..70f52e461 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -9,7 +9,7 @@ import ( "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" - "erupe-ce/config" + _config "erupe-ce/config" "erupe-ce/network/binpacket" "erupe-ce/network/mhfpacket" "erupe-ce/server/discordbot" diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index a2ab3b151..cde28591c 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -4,10 +4,10 @@ import ( "database/sql" "encoding/json" "errors" + _config "erupe-ce/config" "erupe-ce/server/channelserver" "net/http" "strings" - "time" "github.com/lib/pq" "go.uber.org/zap" @@ -19,28 +19,10 @@ const ( NotificationNew ) -type LauncherBanner struct { - Src string `json:"src"` - Link string `json:"link"` -} - -type LauncherMessage struct { - Message string `json:"message"` - Date int64 `json:"date"` - Link string `json:"link"` - Kind int `json:"kind"` -} - -type LauncherLink struct { - Name string `json:"name"` - Link string `json:"link"` - Icon string `json:"icon"` -} - type LauncherResponse struct { - Banners []LauncherBanner `json:"banners"` - Messages []LauncherMessage `json:"messages"` - Links []LauncherLink `json:"links"` + Banners []_config.SignV2Banner `json:"banners"` + Messages []_config.SignV2Message `json:"messages"` + Links []_config.SignV2Link `json:"links"` } type User struct { @@ -122,95 +104,9 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) { var respData LauncherResponse - respData.Banners = []LauncherBanner{ - { - Src: "http://zerulight.cc/launcher/en/images/bnr/1030_0.jpg", - Link: "http://localhost", - }, - { - Src: "http://zerulight.cc/launcher/en/images/bnr/0801_3.jpg", - Link: "http://localhost", - }, - { - Src: "http://zerulight.cc/launcher/en/images/bnr/0705_3.jpg", - Link: "http://localhost", - }, - { - Src: "http://zerulight.cc/launcher/en/images/bnr/1211_11.jpg", - Link: "http://localhost", - }, - { - Src: "http://zerulight.cc/launcher/en/images/bnr/reg_mezefes.jpg", - Link: "http://localhost", - }, - } - respData.Messages = []LauncherMessage{ - { - Message: "Server Update 9.2 — Quest fixes,\nGacha support and tons of bug fixes!", - Date: time.Date(2023, 4, 1, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.com/channels/368424389416583169/929509970624532511/1003985850255818762", - Kind: NotificationNew, - }, - { - Message: "English Patch 4.1 — Fix \"Unknown\" weapons, NPC changes & Diva Support.", - Date: time.Date(2023, 2, 27, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.com/channels/368424389416583169/929509970624532511/969305400795078656", - Kind: NotificationNew, - }, - { - Message: "Server Update 9.1! Hunter Festival, Return worlds and NetCafe are back!", - Date: time.Date(2022, 11, 4, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.com/channels/368424389416583169/929509970624532511/969286397301248050", - Kind: NotificationDefault, - }, - { - Message: "Deerby & Supream have been updating Ferias! You can find any and all MHF info/data there!", - Date: time.Date(2022, 7, 7, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.gg/CFnzbhQ", - Kind: NotificationDefault, - }, - { - Message: "Server hosts, get Chakratos' Save Manager! Use it to enhance your Erupe server!", - Date: time.Date(2022, 7, 7, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.gg/CFnzbhQ", - Kind: NotificationDefault, - }, - { - Message: "Server Update 9.0 is out! Enjoy MezFes and all the other new content!", - Date: time.Date(2022, 8, 2, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.gg/CFnzbhQ", - Kind: NotificationDefault, - }, - { - Message: "English Community Translation 2 is here! Get the latest translation patch!", - Date: time.Date(2022, 5, 4, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.gg/CFnzbhQ", - Kind: NotificationDefault, - }, - { - Message: "Join the community Discord for future updates!", - Date: time.Date(2022, 5, 4, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.gg/CFnzbhQ", - Kind: NotificationDefault, - }, - } - respData.Links = []LauncherLink{ - { - Name: "GitHub", - Link: "https://github.com/ZeruLight/Erupe", - Icon: "https://cdn-icons-png.flaticon.com/512/25/25231.png", - }, - { - Name: "Discord", - Link: "https://discord.gg/DnwcpXM488", - Icon: "https://assets-global.website-files.com/6257adef93867e50d84d30e2/636e0a6a49cf127bf92de1e2_icon_clyde_blurple_RGB.png", - }, - { - Name: "Equal Dragon Weapon Info", - Link: "https://discord.gg/DnwcpXM488", - Icon: "", - }, - } + respData.Banners = s.erupeConfig.SignV2.Banners + respData.Messages = s.erupeConfig.SignV2.Messages + respData.Links = s.erupeConfig.SignV2.Links w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } From b823cbf2ae1fb3e70e5364f029679ec6b8479b1d Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 24 Nov 2023 03:25:35 -0500 Subject: [PATCH 136/269] chore: Rename schema to prevent confusion --- .../{11-event_quest_cycling.sql => 12-event_quest_cycling.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename patch-schema/{11-event_quest_cycling.sql => 12-event_quest_cycling.sql} (100%) diff --git a/patch-schema/11-event_quest_cycling.sql b/patch-schema/12-event_quest_cycling.sql similarity index 100% rename from patch-schema/11-event_quest_cycling.sql rename to patch-schema/12-event_quest_cycling.sql From a0970de48c139871dc6474fb112018ccb66c24d8 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 26 Nov 2023 13:02:17 +1100 Subject: [PATCH 137/269] review pass for Event Quest cycling --- patch-schema/12-event_quest_cycling.sql | 7 +- server/channelserver/handlers_quest.go | 90 ++++++++++++------------- 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/patch-schema/12-event_quest_cycling.sql b/patch-schema/12-event_quest_cycling.sql index cabc928a7..1cd803750 100644 --- a/patch-schema/12-event_quest_cycling.sql +++ b/patch-schema/12-event_quest_cycling.sql @@ -1,7 +1,8 @@ BEGIN; -ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS start_time timestamp with time zone NOT NULL DEFAULT (CURRENT_DATE + interval '0 second'); -ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS active_duration int DEFAULT 4; -ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS inactive_duration int DEFAULT 3; +ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS start_time timestamp with time zone NOT NULL DEFAULT now(); +ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS active_duration int; +ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS inactive_duration int; +UPDATE public.event_quests SET active_duration=NULL, inactive_duration=NULL; END; \ No newline at end of file diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 40fab2721..6db5a0c95 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -230,61 +230,57 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { return bf.Data(), nil } -func calculateNumberOfCycles(duration time.Duration, lastStartTime time.Time) int { - timeDifference := time.Now().Sub(lastStartTime) - numberOfCycles := int(timeDifference.Nanoseconds() / int64(duration)) - return numberOfCycles -} - func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateQuest) var totalCount, returnedCount uint16 bf := byteframe.NewByteFrame() bf.WriteUint16(0) - currentTime := time.Now() + rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, COALESCE(flags, -1), start_time, COALESCE(active_duration, 0) AS active_duration, COALESCE(inactive_duration, 0) AS inactive_duration FROM event_quests ORDER BY quest_id") + if err == nil { + currentTime := time.Now() + tx, _ := s.server.db.Begin() - // Check the event_quests table to load the quests with rotation system - rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, COALESCE(flags, -1), start_time, COALESCE(active_duration, 1) AS active_duration, COALESCE(inactive_duration, 0) AS inactive_duration FROM event_quests ORDER BY quest_id") - if err != nil { - return - } - defer rows.Close() + for rows.Next() { + var id, mark uint32 + var questId, flags, activeDuration, inactiveDuration int + var maxPlayers, questType uint8 + var startTime time.Time - // Commit event quest changes to a transaction instead of doing it one by one for to help with performance - transaction, _ := s.server.db.Begin() - - for rows.Next() { - var id, mark uint32 - var questId int - var maxPlayers, flags, questType, activeDuration, inactiveDuration uint8 - var startTime time.Time - - err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDuration, &inactiveDuration) - if err != nil { - continue - } - - // Count the number of cycles necessary to align quest with the correct date range. - cycleCount := calculateNumberOfCycles(time.Duration(activeDuration+inactiveDuration)*24*time.Hour, startTime) - - // Calculate the rotation time based on start time, active duration, and inactive duration. - rotationTime := startTime.Add(time.Duration(activeDuration+inactiveDuration) * 24 * time.Duration(cycleCount) * time.Hour) - if currentTime.After(rotationTime) { - // take the rotationTime and normalize it to midnight as to align with the ingame message for event quest rotation. - newRotationTime := time.Date(rotationTime.Year(), rotationTime.Month(), rotationTime.Day(), 0, 0, 0, 0, rotationTime.Location()) - newRotationTime = newRotationTime.Add(time.Duration(TimeMidnight().Add(13 * time.Hour).Nanosecond())) - - _, err := transaction.Exec("UPDATE event_quests SET start_time = $1 WHERE id = $2", newRotationTime, id) + err = rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDuration, &inactiveDuration) if err != nil { - transaction.Rollback() // Rollback if an error occurs - break + continue + } + + // Use the Event Cycling system + if activeDuration > 0 { + cycleLength := (time.Duration(activeDuration) + time.Duration(inactiveDuration)) * 24 * time.Hour + + // Count the number of full cycles elapsed since the last rotation. + extraCycles := int(currentTime.Sub(startTime) / cycleLength) + + if extraCycles > 0 { + // Calculate the rotation time based on start time, active duration, and inactive duration. + rotationTime := startTime.Add(time.Duration(activeDuration+inactiveDuration) * 24 * time.Hour * time.Duration(extraCycles)) + if currentTime.After(rotationTime) { + // Normalize rotationTime to 12PM JST to align with the in-game events update notification. + newRotationTime := time.Date(rotationTime.Year(), rotationTime.Month(), rotationTime.Day(), 12, 0, 0, 0, TimeAdjusted().Location()) + + _, err = tx.Exec("UPDATE event_quests SET start_time = $1 WHERE id = $2", newRotationTime, id) + if err != nil { + tx.Rollback() // Rollback if an error occurs + break + } + startTime = newRotationTime // Set the new start time so the quest can be used/removed immediately. + } + } + + // Check if the quest is currently active + if currentTime.Before(startTime) || currentTime.After(startTime.Add(time.Duration(activeDuration)*24*time.Hour)) { + break + } } - startTime = newRotationTime // Set the new start time so the quest can be used/removed immediately. - } - // Check if the quest is currently active - if currentTime.After(startTime) && currentTime.Sub(startTime) <= time.Duration(activeDuration)*24*time.Hour { data, err := makeEventQuest(s, rows) if err != nil { continue @@ -301,10 +297,10 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { } } } - } - // Commit transaction so to write to the database. - transaction.Commit() + rows.Close() + tx.Commit() + } type tuneValue struct { ID uint16 From 7e5fd734963daba300604dffb1b5b8e8775a1cff Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 26 Nov 2023 13:06:49 +1100 Subject: [PATCH 138/269] rename Event Quest cycling 'duration' to 'days' --- patch-schema/12-event_quest_cycling.sql | 2 ++ server/channelserver/handlers_quest.go | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/patch-schema/12-event_quest_cycling.sql b/patch-schema/12-event_quest_cycling.sql index 1cd803750..8760bdab4 100644 --- a/patch-schema/12-event_quest_cycling.sql +++ b/patch-schema/12-event_quest_cycling.sql @@ -4,5 +4,7 @@ ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS start_time ti ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS active_duration int; ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS inactive_duration int; UPDATE public.event_quests SET active_duration=NULL, inactive_duration=NULL; +ALTER TABLE IF EXISTS public.event_quests RENAME active_duration TO active_days; +ALTER TABLE IF EXISTS public.event_quests RENAME inactive_duration TO inactive_days; END; \ No newline at end of file diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 6db5a0c95..98226c777 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -236,32 +236,32 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint16(0) - rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, COALESCE(flags, -1), start_time, COALESCE(active_duration, 0) AS active_duration, COALESCE(inactive_duration, 0) AS inactive_duration FROM event_quests ORDER BY quest_id") + rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, COALESCE(flags, -1), start_time, COALESCE(active_days, 0) AS active_days, COALESCE(inactive_days, 0) AS inactive_days FROM event_quests ORDER BY quest_id") if err == nil { currentTime := time.Now() tx, _ := s.server.db.Begin() for rows.Next() { var id, mark uint32 - var questId, flags, activeDuration, inactiveDuration int + var questId, flags, activeDays, inactiveDays int var maxPlayers, questType uint8 var startTime time.Time - err = rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDuration, &inactiveDuration) + err = rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDays, &inactiveDays) if err != nil { continue } // Use the Event Cycling system - if activeDuration > 0 { - cycleLength := (time.Duration(activeDuration) + time.Duration(inactiveDuration)) * 24 * time.Hour + if activeDays > 0 { + cycleLength := (time.Duration(activeDays) + time.Duration(inactiveDays)) * 24 * time.Hour // Count the number of full cycles elapsed since the last rotation. extraCycles := int(currentTime.Sub(startTime) / cycleLength) if extraCycles > 0 { // Calculate the rotation time based on start time, active duration, and inactive duration. - rotationTime := startTime.Add(time.Duration(activeDuration+inactiveDuration) * 24 * time.Hour * time.Duration(extraCycles)) + rotationTime := startTime.Add(time.Duration(activeDays+inactiveDays) * 24 * time.Hour * time.Duration(extraCycles)) if currentTime.After(rotationTime) { // Normalize rotationTime to 12PM JST to align with the in-game events update notification. newRotationTime := time.Date(rotationTime.Year(), rotationTime.Month(), rotationTime.Day(), 12, 0, 0, 0, TimeAdjusted().Location()) @@ -276,7 +276,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { } // Check if the quest is currently active - if currentTime.Before(startTime) || currentTime.After(startTime.Add(time.Duration(activeDuration)*24*time.Hour)) { + if currentTime.Before(startTime) || currentTime.After(startTime.Add(time.Duration(activeDays)*24*time.Hour)) { break } } From 33665130cf1b04d1fab560b3efc08c841f5d3a4b Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 26 Nov 2023 01:22:51 -0500 Subject: [PATCH 139/269] feat: Discord basic implementation --- patch-schema/15-discord-password-resets.sql | 6 ++++++ server/channelserver/handlers_cast_binary.go | 10 ++++++++++ server/channelserver/handlers_discord.go | 21 ++++++++++++++++++++ server/channelserver/sys_channel_server.go | 11 ++++++++++ 4 files changed, 48 insertions(+) create mode 100644 patch-schema/15-discord-password-resets.sql diff --git a/patch-schema/15-discord-password-resets.sql b/patch-schema/15-discord-password-resets.sql new file mode 100644 index 000000000..bd2e83fea --- /dev/null +++ b/patch-schema/15-discord-password-resets.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.users ADD COLUMN discord_token text; +ALTER TABLE IF EXISTS public.users ADD COLUMN discord_id text; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index dbfafc68c..36edbbadb 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -1,6 +1,7 @@ package channelserver import ( + "crypto" "encoding/hex" "erupe-ce/common/byteframe" "erupe-ce/common/mhfcourse" @@ -318,6 +319,15 @@ func parseChatCommand(s *Session, command string) { } else { sendDisabledCommandMessage(s, commands["Teleport"]) } + case commands["Discord"].Prefix: + if commands["Discord"].Enabled { + token := crypto.MD5.New() + _, err := s.server.db.Exec("UPDATE users SET discord_token = ?", token) + if err != nil { + return + } + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscord"], token)) + } } } diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go index 1f3398d18..551c74146 100644 --- a/server/channelserver/handlers_discord.go +++ b/server/channelserver/handlers_discord.go @@ -66,6 +66,27 @@ func getCharacterList(s *Server) string { return message } +// onInteraction handles slash commands +func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCreate) { + switch i.Interaction.ApplicationCommandData().Name { + case "verify": + _, err := s.db.Exec("UPDATE users SET discord_id = ? WHERE discord_token = ?", i.User.ID, i.Interaction.ApplicationCommandData().Options[0].StringValue()) + if err != nil { + return + } + err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Account successfully linked", + }, + }) + if err != nil { + return + } + break + } +} + // onDiscordMessage handles receiving messages from discord and forwarding them ingame. func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) { // Ignore messages from our bot, or ones that are not in the correct channel. diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 70f52e461..f7658c5cd 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -2,6 +2,7 @@ package channelserver import ( "fmt" + "github.com/bwmarrin/discordgo" "net" "strings" "sync" @@ -210,7 +211,17 @@ func (s *Server) Start() error { // Start the discord bot for chat integration. if s.erupeConfig.Discord.Enabled && s.discordBot != nil { + _, err := s.discordBot.Session.ApplicationCommandBulkOverwrite(s.discordBot.Session.State.User.ID, "", []*discordgo.ApplicationCommand{ + { + Name: "verify", + Description: "Verify your account with Discord", + }, + }) + if err != nil { + return err + } s.discordBot.Session.AddHandler(s.onDiscordMessage) + s.discordBot.Session.AddHandler(s.onInteraction) } return nil From b3af01b8037e0c014f503b25fdb66850b3767293 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 26 Nov 2023 18:54:04 +1100 Subject: [PATCH 140/269] use seconds for arbitrary durations in config --- config.json | 4 ++-- config/config.go | 4 ++-- server/channelserver/handlers_cafe.go | 2 +- server/channelserver/handlers_guild.go | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config.json b/config.json index 9fcb4ef68..2a9b06e6f 100644 --- a/config.json +++ b/config.json @@ -45,8 +45,8 @@ "TreasureHuntExpiry": 604800, "DisableLoginBoost": false, "DisableBoostTime": false, - "BoostTimeDuration": 120, - "GuildMealDuration": 60, + "BoostTimeDuration": 7200, + "GuildMealDuration": 3600, "BonusQuestAllowance": 3, "DailyQuestAllowance": 1, "MezfesSoloTickets": 10, diff --git a/config/config.go b/config/config.go index e992d6594..09991c84f 100644 --- a/config/config.go +++ b/config/config.go @@ -131,8 +131,8 @@ type GameplayOptions struct { TreasureHuntPartnyaCooldown uint32 // Seconds until a Partnya can be assigned to another Clan Treasure Hunt DisableLoginBoost bool // Disables the Login Boost system DisableBoostTime bool // Disables the daily NetCafe Boost Time - BoostTimeDuration int // The number of minutes NetCafe Boost Time lasts for - GuildMealDuration int // The number of minutes a Guild Meal can be activated for after cooking + BoostTimeDuration int // Second that the NetCafe Boost Time lasts + GuildMealDuration int // Second that a Guild Meal can be activated for after cooking BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily DailyQuestAllowance uint32 // Number of Daily Quests to allow daily MezfesSoloTickets uint32 // Number of solo tickets given weekly diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index 67b7c3807..9c4b0b732 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -220,7 +220,7 @@ func addPointNetcafe(s *Session, p int) error { func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfStartBoostTime) bf := byteframe.NewByteFrame() - boostLimit := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.BoostTimeDuration) * time.Minute) + boostLimit := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.BoostTimeDuration) * time.Second) if s.server.erupeConfig.GameplayOptions.DisableBoostTime { bf.WriteUint32(0) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 7a726832f..30a150eef 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1831,18 +1831,18 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfRegistGuildCooking) guild, _ := GetGuildInfoByCharacterId(s, s.charID) - currentTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.GuildMealDuration-60) * time.Minute) + startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.GuildMealDuration-3600) * time.Second) if pkt.OverwriteID != 0 { - s.server.db.Exec("UPDATE guild_meals SET meal_id = $1, level = $2, created_at = $3 WHERE id = $4", pkt.MealID, pkt.Success, currentTime, pkt.OverwriteID) + s.server.db.Exec("UPDATE guild_meals SET meal_id = $1, level = $2, created_at = $3 WHERE id = $4", pkt.MealID, pkt.Success, startTime, pkt.OverwriteID) } else { - s.server.db.QueryRow("INSERT INTO guild_meals (guild_id, meal_id, level, created_at) VALUES ($1, $2, $3, $4) RETURNING id", guild.ID, pkt.MealID, pkt.Success, currentTime).Scan(&pkt.OverwriteID) + s.server.db.QueryRow("INSERT INTO guild_meals (guild_id, meal_id, level, created_at) VALUES ($1, $2, $3, $4) RETURNING id", guild.ID, pkt.MealID, pkt.Success, startTime).Scan(&pkt.OverwriteID) } bf := byteframe.NewByteFrame() bf.WriteUint16(1) bf.WriteUint32(pkt.OverwriteID) bf.WriteUint32(uint32(pkt.MealID)) bf.WriteUint32(uint32(pkt.Success)) - bf.WriteUint32(uint32(currentTime.Unix())) + bf.WriteUint32(uint32(startTime.Unix())) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From a0fbfc248b9ea92e1b95472b41d42a322b89b47b Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 26 Nov 2023 19:21:31 +1100 Subject: [PATCH 141/269] fix TimeWeekX inconsistencies & limit MezFes duration --- config.json | 1 + config/config.go | 1 + server/channelserver/handlers.go | 4 ++-- server/channelserver/sys_time.go | 7 +++++-- server/signserver/dsgn_resp.go | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/config.json b/config.json index 2a9b06e6f..e945f8d33 100644 --- a/config.json +++ b/config.json @@ -65,6 +65,7 @@ "MaterialMultiplier": 1.00, "ExtraCarves": 0, "DisableHunterNavi": false, + "MezFesDuration": 172800, "EnableKaijiEvent": false, "EnableHiganjimaEvent": false, "EnableNierEvent": false, diff --git a/config/config.go b/config/config.go index 09991c84f..4d74accce 100644 --- a/config/config.go +++ b/config/config.go @@ -151,6 +151,7 @@ type GameplayOptions struct { MaterialMultiplier float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion ExtraCarves uint16 // Grant n extra chances to carve ALL carcasses DisableHunterNavi bool // Disables the Hunter Navi + MezFesDuration int // Seconds that MezFes will last for weekly (from 12AM Mon backwards) EnableKaijiEvent bool // Enables the Kaiji event in the Rasta Bar EnableHiganjimaEvent bool // Enables the Higanjima event in the Rasta Bar EnableNierEvent bool // Enables the Nier event in the Rasta Bar diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 154dbc100..e0d162b03 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -988,8 +988,8 @@ func handleMsgMhfKickExportForce(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetEarthStatus) bf := byteframe.NewByteFrame() - bf.WriteUint32(uint32(TimeWeekStart().Add(time.Hour * -24).Unix())) // Start - bf.WriteUint32(uint32(TimeWeekNext().Add(time.Hour * 24).Unix())) // End + bf.WriteUint32(uint32(TimeWeekStart().Unix())) // Start + bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride) bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride) bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthMonsterOverride) diff --git a/server/channelserver/sys_time.go b/server/channelserver/sys_time.go index a41b18b2e..bae61a1c6 100644 --- a/server/channelserver/sys_time.go +++ b/server/channelserver/sys_time.go @@ -16,8 +16,11 @@ func TimeMidnight() time.Time { func TimeWeekStart() time.Time { midnight := TimeMidnight() - offset := (int(midnight.Weekday()) - 1) * -24 - return midnight.Add(time.Hour * time.Duration(offset)) + offset := int(midnight.Weekday()) - int(time.Monday) + if offset < 0 { + offset += 7 + } + return midnight.Add(-time.Duration(offset) * 24 * time.Hour) } func TimeWeekNext() time.Time { diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 77ac6468d..450dc7fc7 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -9,6 +9,7 @@ import ( "fmt" "go.uber.org/zap" "strings" + "time" ) func (s *Session) makeSignResponse(uid uint32) []byte { @@ -160,7 +161,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte { // We can just use the start timestamp as the event ID bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix())) // Start time - bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix())) + bf.WriteUint32(uint32(channelserver.TimeWeekNext().Add(-time.Duration(s.server.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix())) // End time bf.WriteUint32(uint32(channelserver.TimeWeekNext().Unix())) bf.WriteUint8(2) // Unk From dfc359f5e211401ca59e64c88892ff299ed992c0 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 26 Nov 2023 19:42:27 +1100 Subject: [PATCH 142/269] structure & change config for MezFes --- config.json | 3 +- config/config.go | 3 +- server/signserver/dsgn_resp.go | 53 +++++++++++++++------------------- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/config.json b/config.json index e945f8d33..85a84a401 100644 --- a/config.json +++ b/config.json @@ -25,8 +25,6 @@ "DivaEvent": 0, "FestaEvent": -1, "TournamentEvent": 0, - "MezFesEvent": true, - "MezFesAlt": false, "DisableTokenCheck": false, "QuestDebugTools": false, "EarthStatusOverride": 0, @@ -66,6 +64,7 @@ "ExtraCarves": 0, "DisableHunterNavi": false, "MezFesDuration": 172800, + "MezFesSwitchMinigame": false, "EnableKaijiEvent": false, "EnableHiganjimaEvent": false, "EnableNierEvent": false, diff --git a/config/config.go b/config/config.go index 4d74accce..4fdf431b1 100644 --- a/config/config.go +++ b/config/config.go @@ -106,8 +106,6 @@ type DevModeOptions struct { DivaEvent int // Diva Defense event status FestaEvent int // Hunter's Festa event status TournamentEvent int // VS Tournament event status - MezFesEvent bool // MezFes status - MezFesAlt bool // Swaps out Volpakkun for Tokotoko DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) QuestDebugTools bool // Enable various quest debug logs EarthStatusOverride int32 @@ -152,6 +150,7 @@ type GameplayOptions struct { ExtraCarves uint16 // Grant n extra chances to carve ALL carcasses DisableHunterNavi bool // Disables the Hunter Navi MezFesDuration int // Seconds that MezFes will last for weekly (from 12AM Mon backwards) + MezFesSwitchMinigame bool // Swaps out Volpakkun Together for Tokotoko Partnya EnableKaijiEvent bool // Enables the Kaiji event in the Rasta Bar EnableHiganjimaEvent bool // Enables the Higanjima event in the Rasta Bar EnableNierEvent bool // Enables the Nier event in the Rasta Bar diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 450dc7fc7..34cc371e2 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -155,35 +155,30 @@ func (s *Session) makeSignResponse(uid uint32) []byte { bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix())) bf.WriteUint32(0) - mezfes := s.server.erupeConfig.DevModeOptions.MezFesEvent - alt := s.server.erupeConfig.DevModeOptions.MezFesAlt - if mezfes { - // We can just use the start timestamp as the event ID - bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix())) - // Start time - bf.WriteUint32(uint32(channelserver.TimeWeekNext().Add(-time.Duration(s.server.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix())) - // End time - bf.WriteUint32(uint32(channelserver.TimeWeekNext().Unix())) - bf.WriteUint8(2) // Unk - bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesSoloTickets) - bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesGroupTickets) - bf.WriteUint8(8) // Stalls open - bf.WriteUint8(10) // Stall Map - bf.WriteUint8(3) // Pachinko - bf.WriteUint8(6) // Nyanrendo - bf.WriteUint8(9) // Point stall - if alt { - bf.WriteUint8(2) // Tokotoko Partnya - } else { - bf.WriteUint8(4) // Volpakkun Together - } - bf.WriteUint8(8) // Dokkan Battle Cats - bf.WriteUint8(5) // Goocoo Scoop - bf.WriteUint8(7) // Honey Panic - } else { - bf.WriteUint32(0) - bf.WriteUint32(0) - bf.WriteUint32(0) + tickets := []uint32{ + s.server.erupeConfig.GameplayOptions.MezfesSoloTickets, + s.server.erupeConfig.GameplayOptions.MezfesGroupTickets, + } + stalls := []uint8{ + 10, 3, 6, 9, 4, 8, 5, 7, + } + if s.server.erupeConfig.GameplayOptions.MezFesSwitchMinigame { + stalls[4] = 2 + } + + // We can just use the start timestamp as the event ID + bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix())) + // Start time + bf.WriteUint32(uint32(channelserver.TimeWeekNext().Add(-time.Duration(s.server.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix())) + // End time + bf.WriteUint32(uint32(channelserver.TimeWeekNext().Unix())) + bf.WriteUint8(uint8(len(tickets))) + for i := range tickets { + bf.WriteUint32(tickets[i]) + } + bf.WriteUint8(uint8(len(stalls))) + for i := range stalls { + bf.WriteUint8(stalls[i]) } return bf.Data() } From 662c137467c4513b2db100667fcdebb0dc7625be Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 26 Nov 2023 20:44:13 +1100 Subject: [PATCH 143/269] update MezFes config for SignV2 --- server/signv2server/endpoints.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index cde28591c..ae5959809 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -8,6 +8,7 @@ import ( "erupe-ce/server/channelserver" "net/http" "strings" + "time" "github.com/lib/pq" "go.uber.org/zap" @@ -82,19 +83,17 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, resp.Characters[i].HR = 7 } } - if s.erupeConfig.DevModeOptions.MezFesEvent { - stalls := []uint32{10, 3, 6, 9, 4, 8, 5, 7} - if s.erupeConfig.DevModeOptions.MezFesAlt { - stalls[4] = 2 - } - resp.MezFes = &MezFes{ - ID: uint32(channelserver.TimeWeekStart().Unix()), - Start: uint32(channelserver.TimeWeekStart().Unix()), - End: uint32(channelserver.TimeWeekNext().Unix()), - SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets, - GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets, - Stalls: stalls, - } + stalls := []uint32{10, 3, 6, 9, 4, 8, 5, 7} + if s.erupeConfig.GameplayOptions.MezFesSwitchMinigame { + stalls[4] = 2 + } + resp.MezFes = &MezFes{ + ID: uint32(channelserver.TimeWeekStart().Unix()), + Start: uint32(channelserver.TimeWeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()), + End: uint32(channelserver.TimeWeekNext().Unix()), + SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets, + GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets, + Stalls: stalls, } if !s.erupeConfig.HideLoginNotice { resp.Notices = append(resp.Notices, strings.Join(s.erupeConfig.LoginNotices[:], "")) From a2f488e5e3f6805a30cd0cf5e5649630c9441f7f Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 26 Nov 2023 20:50:08 +1100 Subject: [PATCH 144/269] move ProxyPort config out of DevMode --- config.json | 2 +- config/config.go | 32 +++++++++++++++--------------- main.go | 2 +- server/entranceserver/make_resp.go | 4 ++-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/config.json b/config.json index bfa9e04de..d6c233470 100644 --- a/config.json +++ b/config.json @@ -13,6 +13,7 @@ "DeleteOnSaveCorruption": false, "ClientMode": "ZZ", "QuestCacheExpiry": 300, + "ProxyPort": 0, "DevMode": true, "DevModeOptions": { "AutoCreateAccount": true, @@ -29,7 +30,6 @@ "MezFesAlt": false, "DisableTokenCheck": false, "QuestDebugTools": false, - "ProxyPort": 0, "EarthStatusOverride": 0, "EarthIDOverride": 0, "EarthMonsterOverride": 0, diff --git a/config/config.go b/config/config.go index c9a05d3d1..5bcea5659 100644 --- a/config/config.go +++ b/config/config.go @@ -79,7 +79,8 @@ type Config struct { DeleteOnSaveCorruption bool // Attempts to save corrupted data will flag the save for deletion ClientMode string RealClientMode Mode - QuestCacheExpiry int // Number of seconds to keep quest data cached + QuestCacheExpiry int // Number of seconds to keep quest data cached + ProxyPort uint16 // Forces the game to connect to a channel server proxy DevMode bool DevModeOptions DevModeOptions @@ -96,21 +97,20 @@ type Config struct { // DevModeOptions holds various debug/temporary options for use while developing Erupe. type DevModeOptions struct { - AutoCreateAccount bool // Automatically create accounts if they don't exist - CleanDB bool // Automatically wipes the DB on server reset. - MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds. - LogInboundMessages bool // Log all messages sent to the server - LogOutboundMessages bool // Log all messages sent to the clients - LogMessageData bool // Log all bytes transferred as a hexdump - MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled - DivaEvent int // Diva Defense event status - FestaEvent int // Hunter's Festa event status - TournamentEvent int // VS Tournament event status - MezFesEvent bool // MezFes status - MezFesAlt bool // Swaps out Volpakkun for Tokotoko - DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) - QuestDebugTools bool // Enable various quest debug logs - ProxyPort uint16 // Forces the game connect to a channel server proxy + AutoCreateAccount bool // Automatically create accounts if they don't exist + CleanDB bool // Automatically wipes the DB on server reset. + MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds. + LogInboundMessages bool // Log all messages sent to the server + LogOutboundMessages bool // Log all messages sent to the clients + LogMessageData bool // Log all bytes transferred as a hexdump + MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled + DivaEvent int // Diva Defense event status + FestaEvent int // Hunter's Festa event status + TournamentEvent int // VS Tournament event status + MezFesEvent bool // MezFes status + MezFesAlt bool // Swaps out Volpakkun for Tokotoko + DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) + QuestDebugTools bool // Enable various quest debug logs EarthStatusOverride int32 EarthIDOverride int32 EarthMonsterOverride int32 diff --git a/main.go b/main.go index 724fe3f44..1413392cf 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ func cleanDB(db *sqlx.DB, config *_config.Config) { _ = db.MustExec("DELETE FROM guild_characters") _ = db.MustExec("DELETE FROM guilds") _ = db.MustExec("DELETE FROM characters") - if !config.DevMode || config.DevModeOptions.ProxyPort == 0 { + if config.ProxyPort == 0 { _ = db.MustExec("DELETE FROM sign_sessions") } _ = db.MustExec("DELETE FROM users") diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index 5ace5a427..4b478fa24 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -69,8 +69,8 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { for channelIdx, ci := range si.Channels { sid = (4096 + serverIdx*256) + (16 + channelIdx) - if _config.ErupeConfig.DevMode && _config.ErupeConfig.DevModeOptions.ProxyPort != 0 { - bf.WriteUint16(_config.ErupeConfig.DevModeOptions.ProxyPort) + if _config.ErupeConfig.DevMode && _config.ErupeConfig.ProxyPort != 0 { + bf.WriteUint16(_config.ErupeConfig.ProxyPort) } else { bf.WriteUint16(ci.Port) } From 3c6067c8a6da4248efc233affb838134dfce15e0 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 26 Nov 2023 23:22:56 +1100 Subject: [PATCH 145/269] port partial fix/mutex-rework --- network/mhfpacket/msg_sys_create_stage.go | 7 ++-- network/mhfpacket/msg_sys_enter_stage.go | 6 +-- network/mhfpacket/msg_sys_enumerate_stage.go | 11 ++--- network/mhfpacket/msg_sys_lock_stage.go | 23 +++++------ network/mhfpacket/msg_sys_unlock_stage.go | 12 +++--- server/channelserver/handlers.go | 6 +-- server/channelserver/handlers_stage.go | 43 +++++++++++--------- server/channelserver/sys_session.go | 1 - server/channelserver/sys_stage.go | 1 + 9 files changed, 53 insertions(+), 57 deletions(-) diff --git a/network/mhfpacket/msg_sys_create_stage.go b/network/mhfpacket/msg_sys_create_stage.go index fe6e533ff..9c11ba46c 100644 --- a/network/mhfpacket/msg_sys_create_stage.go +++ b/network/mhfpacket/msg_sys_create_stage.go @@ -3,7 +3,6 @@ package mhfpacket import ( "errors" "erupe-ce/common/byteframe" - "erupe-ce/common/bfutil" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -13,7 +12,7 @@ type MsgSysCreateStage struct { AckHandle uint32 Unk0 uint8 // Likely only has 1 and 2 as values. PlayerCount uint8 - StageID string // NULL terminated string. + StageID string } // Opcode returns the ID associated with this packet type. @@ -26,8 +25,8 @@ func (m *MsgSysCreateStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint8() m.PlayerCount = bf.ReadUint8() - stageIDLength := bf.ReadUint8() - m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) + bf.ReadUint8() // Length StageID + m.StageID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_enter_stage.go b/network/mhfpacket/msg_sys_enter_stage.go index 5a1ccb8ab..17ba468f2 100644 --- a/network/mhfpacket/msg_sys_enter_stage.go +++ b/network/mhfpacket/msg_sys_enter_stage.go @@ -11,7 +11,7 @@ import ( // MsgSysEnterStage represents the MSG_SYS_ENTER_STAGE type MsgSysEnterStage struct { AckHandle uint32 - UnkBool uint8 + Unk bool StageID string } @@ -23,8 +23,8 @@ func (m *MsgSysEnterStage) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysEnterStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.UnkBool = bf.ReadUint8() - bf.ReadUint8() + m.Unk = bf.ReadBool() // IsQuest? + bf.ReadUint8() // Length StageID m.StageID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_enumerate_stage.go b/network/mhfpacket/msg_sys_enumerate_stage.go index a3f125941..b0d25c099 100644 --- a/network/mhfpacket/msg_sys_enumerate_stage.go +++ b/network/mhfpacket/msg_sys_enumerate_stage.go @@ -2,8 +2,6 @@ package mhfpacket import ( "errors" - "erupe-ce/common/stringsupport" - "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" @@ -12,8 +10,7 @@ import ( // MsgSysEnumerateStage represents the MSG_SYS_ENUMERATE_STAGE type MsgSysEnumerateStage struct { AckHandle uint32 - Unk0 uint8 // Hardcoded 1 in the binary - StagePrefix string // NULL terminated string. + StagePrefix string } // Opcode returns the ID associated with this packet type. @@ -24,9 +21,9 @@ func (m *MsgSysEnumerateStage) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysEnumerateStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() - bf.ReadUint8() - m.StagePrefix = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + bf.ReadUint8() // Always 1 + bf.ReadUint8() // Length StagePrefix + m.StagePrefix = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_lock_stage.go b/network/mhfpacket/msg_sys_lock_stage.go index 14b082596..13867b825 100644 --- a/network/mhfpacket/msg_sys_lock_stage.go +++ b/network/mhfpacket/msg_sys_lock_stage.go @@ -1,20 +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" ) // MsgSysLockStage represents the MSG_SYS_LOCK_STAGE type MsgSysLockStage struct { - AckHandle uint32 - Unk0 uint8 // Hardcoded 1 in the binary - Unk1 uint8 // Hardcoded 1 in the binary - StageIDLength uint8 - StageID string + AckHandle uint32 + StageID string } // Opcode returns the ID associated with this packet type. @@ -25,10 +22,10 @@ func (m *MsgSysLockStage) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysLockStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() - m.Unk1 = bf.ReadUint8() - m.StageIDLength = bf.ReadUint8() - m.StageID = string(bf.ReadBytes(uint(m.StageIDLength))) + bf.ReadUint8() // Always 1 + bf.ReadUint8() // Always 1 + bf.ReadUint8() // Length StageID + m.StageID = string(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_sys_unlock_stage.go b/network/mhfpacket/msg_sys_unlock_stage.go index 74af57424..ec1effdc5 100644 --- a/network/mhfpacket/msg_sys_unlock_stage.go +++ b/network/mhfpacket/msg_sys_unlock_stage.go @@ -1,15 +1,14 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgSysUnlockStage represents the MSG_SYS_UNLOCK_STAGE -type MsgSysUnlockStage struct { - Unk0 uint16 // Hardcoded 0 in the binary. -} +type MsgSysUnlockStage struct{} // Opcode returns the ID associated with this packet type. func (m *MsgSysUnlockStage) Opcode() network.PacketID { @@ -18,12 +17,11 @@ func (m *MsgSysUnlockStage) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysUnlockStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.Unk0 = bf.ReadUint16() + bf.ReadUint16() // Zeroed return nil } // Build builds a binary packet from the current data. func (m *MsgSysUnlockStage) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint16(m.Unk0) - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index e0d162b03..abba0cfe6 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -378,7 +378,7 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { if session.charID == CharID { count++ sessionName := stringsupport.UTF8ToSJIS(session.Name) - sessionStage := stringsupport.UTF8ToSJIS(session.stageID) + sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) resp.WriteUint16(c.Port) resp.WriteUint32(session.charID) @@ -408,7 +408,7 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { if strings.Contains(session.Name, searchTerm) { count++ sessionName := stringsupport.UTF8ToSJIS(session.Name) - sessionStage := stringsupport.UTF8ToSJIS(session.stageID) + sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) resp.WriteUint16(c.Port) resp.WriteUint32(session.charID) @@ -445,7 +445,7 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { hrp := uint16(1) gr := uint16(0) s.server.db.QueryRow("SELECT hrp, gr FROM characters WHERE id=$1", session.charID).Scan(&hrp, &gr) - sessionStage := stringsupport.UTF8ToSJIS(session.stageID) + sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) sessionName := stringsupport.UTF8ToSJIS(session.Name) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) resp.WriteUint16(c.Port) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index cf8757b00..7a12d453e 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -55,7 +55,6 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) { // Save our new stage ID and pointer to the new stage itself. s.Lock() - s.stageID = stageID s.stage = s.server.stages[stageID] s.Unlock() @@ -153,13 +152,13 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) // Push our current stage ID to the movement stack before entering another one. - if s.stageID == "" { + if s.stage.id == "" { 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.Push(s.stage.id) s.stageMoveStack.Lock() } @@ -206,9 +205,12 @@ func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysLockStage) - // TODO(Andoryuuta): What does this packet _actually_ do? - // I think this is supposed to mark a stage as no longer able to accept client reservations - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + if stage, exists := s.server.stages[pkt.StageID]; exists { + stage.Lock() + stage.locked = true + stage.Unlock() + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) { @@ -218,7 +220,9 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) { for charID := range s.reservationStage.reservedClientSlots { session := s.server.FindSessionByCharID(charID) - session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) + if session != nil { + session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) + } } delete(s.server.stages, s.reservationStage.id) @@ -241,6 +245,10 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers { + if stage.locked { + doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) + return + } if len(stage.password) > 0 { if stage.password != s.stagePass { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) @@ -383,20 +391,17 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { joinable++ bf.WriteUint16(uint16(len(stage.reservedClientSlots))) - bf.WriteUint16(0) // Unk - if len(stage.clients) > 0 { - bf.WriteUint16(1) - } else { - bf.WriteUint16(0) - } + bf.WriteUint16(uint16(len(stage.clients))) + bf.WriteUint16(uint16(len(stage.clients))) bf.WriteUint16(stage.maxPlayers) - if len(stage.password) > 0 { - // This byte has also been seen as 1 - // The quest is also recognised as locked when this is 2 - bf.WriteUint8(2) - } else { - bf.WriteUint8(0) + var flags uint8 + if stage.locked { + flags |= 1 } + if len(stage.password) > 0 { + flags |= 2 + } + bf.WriteUint8(flags) ps.Uint8(bf, sid, false) stage.RUnlock() } diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 32004151d..5034f38c2 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -36,7 +36,6 @@ type Session struct { objectIndex uint16 userEnteredStage bool // If the user has entered a stage before - stageID string stage *Stage reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet. stagePass string // Temporary storage diff --git a/server/channelserver/sys_stage.go b/server/channelserver/sys_stage.go index ae8ddd149..dbfcbb7c3 100644 --- a/server/channelserver/sys_stage.go +++ b/server/channelserver/sys_stage.go @@ -47,6 +47,7 @@ type Stage struct { host *Session maxPlayers uint16 password string + locked bool } // NewStage creates a new stage with intialized values. From 226adddc43b5c639ae230ff46b0681dc9fcb55fd Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 26 Nov 2023 16:47:54 -0500 Subject: [PATCH 146/269] feat: Generate hashes for Discord and allow password resets --- config.json | 20 ++++++---- main.go | 32 +++++++++++++++ server/channelserver/handlers_cast_binary.go | 17 ++++++-- server/channelserver/handlers_discord.go | 23 ++++++++++- server/channelserver/handlers_stage.go | 41 +++++++++----------- server/channelserver/sys_channel_server.go | 10 ----- server/channelserver/sys_language.go | 2 + server/channelserver/sys_session.go | 1 + 8 files changed, 100 insertions(+), 46 deletions(-) diff --git a/config.json b/config.json index da75463ee..4063a6df2 100644 --- a/config.json +++ b/config.json @@ -19,9 +19,9 @@ "AutoCreateAccount": true, "CleanDB": false, "MaxLauncherHR": false, - "LogInboundMessages": false, - "LogOutboundMessages": false, - "LogMessageData": false, + "LogInboundMessages": true, + "LogOutboundMessages": true, + "LogMessageData": true, "MaxHexdumpLength": 256, "DivaEvent": 0, "FestaEvent": -1, @@ -73,9 +73,9 @@ "SeasonOverride": false }, "Discord": { - "Enabled": false, - "BotToken": "", - "RealtimeChannelID": "" + "Enabled": true, + "BotToken": "MTAzMTQ2MDI4MDYxOTc2NTgwMA.GGe824._OxF9rtv1O8EjOZI26hATruaF_VZ9YBwuAdS1Y", + "RealtimeChannelID": "645108836423958540" }, "Commands": [ { @@ -106,6 +106,10 @@ "Name": "PSN", "Enabled": true, "Prefix": "psn" + }, { + "Name": "Discord", + "Enabled": true, + "Prefix": "discord" } ], "Courses": [ @@ -125,7 +129,7 @@ "Host": "localhost", "Port": 5432, "User": "postgres", - "Password": "", + "Password": "admin", "Database": "erupe" }, "Sign": { @@ -133,7 +137,7 @@ "Port": 53312 }, "SignV2": { - "Enabled": false, + "Enabled": true, "Port": 8080, "PatchServer": "", "Banners": [], diff --git a/main.go b/main.go index c56d90b0f..7a21a4b7b 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( _config "erupe-ce/config" "fmt" + "github.com/bwmarrin/discordgo" "net" "os" "os/signal" @@ -98,6 +99,37 @@ func main() { } discordBot = bot + + _, err = discordBot.Session.ApplicationCommandBulkOverwrite(discordBot.Session.State.User.ID, "", []*discordgo.ApplicationCommand{ + { + Name: "verify", + Description: "Verify your account with Discord", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "token", + Description: "The access token provided by !discord command within the game client.", + Required: true, + }, + }, + }, + { + Name: "passwordreset", + Description: "Reset your account password on Erupe", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "password", + Description: "The password to change your account to.", + Required: true, + }, + }, + }, + }) + if err != nil { + preventClose(fmt.Sprintf("Discord: Failed to start, %s", err.Error())) + } + logger.Info("Discord: Started successfully") } else { logger.Info("Discord: Disabled") diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 36edbbadb..ea3b291d2 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -2,6 +2,7 @@ package channelserver import ( "crypto" + "encoding/binary" "encoding/hex" "erupe-ce/common/byteframe" "erupe-ce/common/mhfcourse" @@ -321,12 +322,22 @@ func parseChatCommand(s *Session, command string) { } case commands["Discord"].Prefix: if commands["Discord"].Enabled { - token := crypto.MD5.New() - _, err := s.server.db.Exec("UPDATE users SET discord_token = ?", token) + tokenHash := crypto.MD5.New() + tokenSalt := fmt.Sprint(s.charID) + fmt.Sprint(s.server.ID) + tokenData := make([]byte, 4) + binary.LittleEndian.PutUint32(tokenData, uint32(time.Now().Second())) + tokenHash.Write([]byte(fmt.Sprintf("%s%s", tokenSalt, tokenData))) + discordToken := fmt.Sprint(tokenHash)[4:12] + s.logger.Info(discordToken) + _, err := s.server.db.Exec("UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", discordToken, s.charID) if err != nil { + sendServerChatMessage(s, fmt.Sprint("An error occurred while processing this command")) + s.logger.Error(fmt.Sprint(err)) return } - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscord"], token)) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], discordToken)) + } else { + sendDisabledCommandMessage(s, commands["Discord"]) } } } diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go index 551c74146..7482332e0 100644 --- a/server/channelserver/handlers_discord.go +++ b/server/channelserver/handlers_discord.go @@ -3,6 +3,7 @@ package channelserver import ( "fmt" "github.com/bwmarrin/discordgo" + "golang.org/x/crypto/bcrypt" "sort" "strings" "unicode" @@ -70,14 +71,32 @@ func getCharacterList(s *Server) string { func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCreate) { switch i.Interaction.ApplicationCommandData().Name { case "verify": - _, err := s.db.Exec("UPDATE users SET discord_id = ? WHERE discord_token = ?", i.User.ID, i.Interaction.ApplicationCommandData().Options[0].StringValue()) + _, err := s.db.Exec("UPDATE users SET discord_id = $1 WHERE discord_token = $2", i.Member.User.ID, i.ApplicationCommandData().Options[0].StringValue()) if err != nil { return } err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: "Account successfully linked", + Content: "Erupe account successfully linked to Discord account.", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + if err != nil { + return + } + break + case "passwordreset": + password, _ := bcrypt.GenerateFromPassword([]byte(i.ApplicationCommandData().Options[0].StringValue()), 10) + _, err := s.db.Exec("UPDATE users SET password = $1 WHERE discord_id = $2", password, i.Member.User.ID) + if err != nil { + return + } + err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Password has been reset, you may login now.", + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 7a12d453e..cf8757b00 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -55,6 +55,7 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) { // Save our new stage ID and pointer to the new stage itself. s.Lock() + s.stageID = stageID s.stage = s.server.stages[stageID] s.Unlock() @@ -152,13 +153,13 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) // Push our current stage ID to the movement stack before entering another one. - if s.stage.id == "" { + 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.stage.id) + s.stageMoveStack.Push(s.stageID) s.stageMoveStack.Lock() } @@ -205,12 +206,9 @@ func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysLockStage) - if stage, exists := s.server.stages[pkt.StageID]; exists { - stage.Lock() - stage.locked = true - stage.Unlock() - } - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + // TODO(Andoryuuta): What does this packet _actually_ do? + // I think this is supposed to mark a stage as no longer able to accept client reservations + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) { @@ -220,9 +218,7 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) { for charID := range s.reservationStage.reservedClientSlots { session := s.server.FindSessionByCharID(charID) - if session != nil { - session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) - } + session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) } delete(s.server.stages, s.reservationStage.id) @@ -245,10 +241,6 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers { - if stage.locked { - doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) - return - } if len(stage.password) > 0 { if stage.password != s.stagePass { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) @@ -391,17 +383,20 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { joinable++ bf.WriteUint16(uint16(len(stage.reservedClientSlots))) - bf.WriteUint16(uint16(len(stage.clients))) - bf.WriteUint16(uint16(len(stage.clients))) + bf.WriteUint16(0) // Unk + if len(stage.clients) > 0 { + bf.WriteUint16(1) + } else { + bf.WriteUint16(0) + } bf.WriteUint16(stage.maxPlayers) - var flags uint8 - if stage.locked { - flags |= 1 - } if len(stage.password) > 0 { - flags |= 2 + // This byte has also been seen as 1 + // The quest is also recognised as locked when this is 2 + bf.WriteUint8(2) + } else { + bf.WriteUint8(0) } - bf.WriteUint8(flags) ps.Uint8(bf, sid, false) stage.RUnlock() } diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index f7658c5cd..119cbf420 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -2,7 +2,6 @@ package channelserver import ( "fmt" - "github.com/bwmarrin/discordgo" "net" "strings" "sync" @@ -211,15 +210,6 @@ func (s *Server) Start() error { // Start the discord bot for chat integration. if s.erupeConfig.Discord.Enabled && s.discordBot != nil { - _, err := s.discordBot.Session.ApplicationCommandBulkOverwrite(s.discordBot.Session.State.User.ID, "", []*discordgo.ApplicationCommand{ - { - Name: "verify", - Description: "Verify your account with Discord", - }, - }) - if err != nil { - return err - } s.discordBot.Session.AddHandler(s.onDiscordMessage) s.discordBot.Session.AddHandler(s.onInteraction) } diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index dbd48cfb8..cc17ee6a1 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -76,6 +76,8 @@ func getLangStrings(s *Server) map[string]string { strings["commandPSNSuccess"] = "Connected PSN ID: %s" strings["commandPSNExists"] = "PSN ID is connected to another account!" + strings["commandDiscordSuccess"] = "Discord token has been generated: %s" + strings["commandRaviNoCommand"] = "No Raviente command specified!" strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment" strings["commandRaviStartError"] = "The Great Slaying has already begun!" diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 5034f38c2..32004151d 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -36,6 +36,7 @@ type Session struct { objectIndex uint16 userEnteredStage bool // If the user has entered a stage before + stageID string stage *Stage reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet. stagePass string // Temporary storage From d2e9e3d1a9c8e7947745c677c3eabf305c10164e Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 26 Nov 2023 16:49:11 -0500 Subject: [PATCH 147/269] fix: Fix default config (whoops) --- config.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index 4063a6df2..87686a173 100644 --- a/config.json +++ b/config.json @@ -73,9 +73,9 @@ "SeasonOverride": false }, "Discord": { - "Enabled": true, - "BotToken": "MTAzMTQ2MDI4MDYxOTc2NTgwMA.GGe824._OxF9rtv1O8EjOZI26hATruaF_VZ9YBwuAdS1Y", - "RealtimeChannelID": "645108836423958540" + "Enabled": false, + "BotToken": "", + "RealtimeChannelID": "" }, "Commands": [ { @@ -129,7 +129,7 @@ "Host": "localhost", "Port": 5432, "User": "postgres", - "Password": "admin", + "Password": "", "Database": "erupe" }, "Sign": { From 38b57c6d98c0b6d2ee7b5d2984c2cc8473f657c9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 01:08:40 -0500 Subject: [PATCH 148/269] refactor: Change to using rand.Read instead of whatever the hell else was before --- main.go | 2 +- server/channelserver/handlers_cast_binary.go | 15 +++++---------- server/channelserver/handlers_discord.go | 3 ++- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index 7a21a4b7b..35ca98043 100644 --- a/main.go +++ b/main.go @@ -114,7 +114,7 @@ func main() { }, }, { - Name: "passwordreset", + Name: "password", Description: "Reset your account password on Erupe", Options: []*discordgo.ApplicationCommandOption{ { diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index ea3b291d2..89e978ffd 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -1,8 +1,7 @@ package channelserver import ( - "crypto" - "encoding/binary" + "crypto/rand" "encoding/hex" "erupe-ce/common/byteframe" "erupe-ce/common/mhfcourse" @@ -322,14 +321,10 @@ func parseChatCommand(s *Session, command string) { } case commands["Discord"].Prefix: if commands["Discord"].Enabled { - tokenHash := crypto.MD5.New() - tokenSalt := fmt.Sprint(s.charID) + fmt.Sprint(s.server.ID) - tokenData := make([]byte, 4) - binary.LittleEndian.PutUint32(tokenData, uint32(time.Now().Second())) - tokenHash.Write([]byte(fmt.Sprintf("%s%s", tokenSalt, tokenData))) - discordToken := fmt.Sprint(tokenHash)[4:12] - s.logger.Info(discordToken) - _, err := s.server.db.Exec("UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", discordToken, s.charID) + discordToken := make([]byte, 8) + _, err := rand.Read(discordToken) + s.logger.Info(fmt.Sprint(discordToken)) + _, err = s.server.db.Exec("UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", fmt.Sprint(discordToken), s.charID) if err != nil { sendServerChatMessage(s, fmt.Sprint("An error occurred while processing this command")) s.logger.Error(fmt.Sprint(err)) diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go index 7482332e0..9a7dec2a0 100644 --- a/server/channelserver/handlers_discord.go +++ b/server/channelserver/handlers_discord.go @@ -86,10 +86,11 @@ func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCr return } break - case "passwordreset": + case "password": password, _ := bcrypt.GenerateFromPassword([]byte(i.ApplicationCommandData().Options[0].StringValue()), 10) _, err := s.db.Exec("UPDATE users SET password = $1 WHERE discord_id = $2", password, i.Member.User.ID) if err != nil { + s.logger.Error(fmt.Sprint(err)) return } err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ From 523266fc6834634a82c9e4fc2ec6dbdde992587d Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 02:18:01 -0500 Subject: [PATCH 149/269] refactor: Move realtime channels to its own config --- config.json | 5 ++++- config/config.go | 7 ++++++- server/channelserver/handlers_discord.go | 2 +- server/discordbot/discord_bot.go | 10 +++++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index 87686a173..dcccdf069 100644 --- a/config.json +++ b/config.json @@ -75,7 +75,10 @@ "Discord": { "Enabled": false, "BotToken": "", - "RealtimeChannelID": "" + "RealTimeChannel": { + "Enabled": false, + "RealTimeChannelID": "" + } }, "Commands": [ { diff --git a/config/config.go b/config/config.go index 31fedd246..4c1809e40 100644 --- a/config/config.go +++ b/config/config.go @@ -161,8 +161,13 @@ type GameplayOptions struct { // Discord holds the discord integration config. type Discord struct { + Enabled bool + BotToken string + RealTimeChannel DiscordRealTime +} + +type DiscordRealTime struct { Enabled bool - BotToken string RealtimeChannelID string } diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go index 9a7dec2a0..61c7a2a57 100644 --- a/server/channelserver/handlers_discord.go +++ b/server/channelserver/handlers_discord.go @@ -110,7 +110,7 @@ func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCr // onDiscordMessage handles receiving messages from discord and forwarding them ingame. func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) { // Ignore messages from our bot, or ones that are not in the correct channel. - if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID { + if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RealTimeChannel.RealtimeChannelID { return } diff --git a/server/discordbot/discord_bot.go b/server/discordbot/discord_bot.go index c082faf70..0d774fff7 100644 --- a/server/discordbot/discord_bot.go +++ b/server/discordbot/discord_bot.go @@ -28,7 +28,11 @@ func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) { return nil, err } - realtimeChannel, err := session.Channel(options.Config.Discord.RealtimeChannelID) + var realtimeChannel *discordgo.Channel + + if options.Config.Discord.RealTimeChannel.Enabled { + realtimeChannel, err = session.Channel(options.Config.Discord.RealTimeChannel.RealtimeChannelID) + } if err != nil { options.Logger.Fatal("Discord failed to create realtimeChannel", zap.Error(err)) @@ -74,6 +78,10 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string { } func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) { + if bot.RealtimeChannel == nil { + return + } + _, err = bot.Session.ChannelMessageSend(bot.RealtimeChannel.ID, message) return From a4745f05d5d210c49c282411f7f63c2a8e625e09 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 03:03:48 -0500 Subject: [PATCH 150/269] refactor: Clean up random token implementation --- server/channelserver/handlers_cast_binary.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 89e978ffd..1fe81c323 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -321,9 +321,16 @@ func parseChatCommand(s *Session, command string) { } case commands["Discord"].Prefix: if commands["Discord"].Enabled { - discordToken := make([]byte, 8) - _, err := rand.Read(discordToken) - s.logger.Info(fmt.Sprint(discordToken)) + randToken := make([]byte, 4) + + _, err := rand.Read(randToken) + if err != nil { + sendServerChatMessage(s, fmt.Sprint("An error occurred while processing this command")) + s.logger.Error(fmt.Sprint(err)) + return + } + + discordToken := fmt.Sprintf("%x-%x", randToken[:2], randToken[2:]) _, err = s.server.db.Exec("UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", fmt.Sprint(discordToken), s.charID) if err != nil { sendServerChatMessage(s, fmt.Sprint("An error occurred while processing this command")) From 7d630088a4893210a60b80f5fc6f0dc4ae64571d Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 03:23:30 -0500 Subject: [PATCH 151/269] refactor: Fix code formatting and reset config --- config.json | 8 ++++---- server/channelserver/handlers_discord.go | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index dcccdf069..aabc3d62a 100644 --- a/config.json +++ b/config.json @@ -19,9 +19,9 @@ "AutoCreateAccount": true, "CleanDB": false, "MaxLauncherHR": false, - "LogInboundMessages": true, - "LogOutboundMessages": true, - "LogMessageData": true, + "LogInboundMessages": false, + "LogOutboundMessages": false, + "LogMessageData": false, "MaxHexdumpLength": 256, "DivaEvent": 0, "FestaEvent": -1, @@ -140,7 +140,7 @@ "Port": 53312 }, "SignV2": { - "Enabled": true, + "Enabled": false, "Port": 8080, "PatchServer": "", "Banners": [], diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go index 61c7a2a57..1772e2d06 100644 --- a/server/channelserver/handlers_discord.go +++ b/server/channelserver/handlers_discord.go @@ -75,6 +75,7 @@ func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCr if err != nil { return } + err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ @@ -82,17 +83,20 @@ func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCr Flags: discordgo.MessageFlagsEphemeral, }, }) + if err != nil { return } break case "password": password, _ := bcrypt.GenerateFromPassword([]byte(i.ApplicationCommandData().Options[0].StringValue()), 10) + _, err := s.db.Exec("UPDATE users SET password = $1 WHERE discord_id = $2", password, i.Member.User.ID) if err != nil { s.logger.Error(fmt.Sprint(err)) return } + err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ From a77d6d53aa642bf8ed7c6e5fd5ae6d0d02a00b0e Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 03:27:20 -0500 Subject: [PATCH 152/269] refactor: Remove reverted mutex changes --- server/channelserver/handlers_stage.go | 43 ++++++++++++++------------ server/channelserver/sys_session.go | 1 - 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index cf8757b00..7a12d453e 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -55,7 +55,6 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) { // Save our new stage ID and pointer to the new stage itself. s.Lock() - s.stageID = stageID s.stage = s.server.stages[stageID] s.Unlock() @@ -153,13 +152,13 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) // Push our current stage ID to the movement stack before entering another one. - if s.stageID == "" { + if s.stage.id == "" { 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.Push(s.stage.id) s.stageMoveStack.Lock() } @@ -206,9 +205,12 @@ func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysLockStage) - // TODO(Andoryuuta): What does this packet _actually_ do? - // I think this is supposed to mark a stage as no longer able to accept client reservations - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + if stage, exists := s.server.stages[pkt.StageID]; exists { + stage.Lock() + stage.locked = true + stage.Unlock() + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) { @@ -218,7 +220,9 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) { for charID := range s.reservationStage.reservedClientSlots { session := s.server.FindSessionByCharID(charID) - session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) + if session != nil { + session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) + } } delete(s.server.stages, s.reservationStage.id) @@ -241,6 +245,10 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers { + if stage.locked { + doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) + return + } if len(stage.password) > 0 { if stage.password != s.stagePass { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) @@ -383,20 +391,17 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { joinable++ bf.WriteUint16(uint16(len(stage.reservedClientSlots))) - bf.WriteUint16(0) // Unk - if len(stage.clients) > 0 { - bf.WriteUint16(1) - } else { - bf.WriteUint16(0) - } + bf.WriteUint16(uint16(len(stage.clients))) + bf.WriteUint16(uint16(len(stage.clients))) bf.WriteUint16(stage.maxPlayers) - if len(stage.password) > 0 { - // This byte has also been seen as 1 - // The quest is also recognised as locked when this is 2 - bf.WriteUint8(2) - } else { - bf.WriteUint8(0) + var flags uint8 + if stage.locked { + flags |= 1 } + if len(stage.password) > 0 { + flags |= 2 + } + bf.WriteUint8(flags) ps.Uint8(bf, sid, false) stage.RUnlock() } diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 32004151d..5034f38c2 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -36,7 +36,6 @@ type Session struct { objectIndex uint16 userEnteredStage bool // If the user has entered a stage before - stageID string stage *Stage reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet. stagePass string // Temporary storage From 76ba7cb94215e88ebdb4aeb79799800f747fe9bd Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 03:41:48 -0500 Subject: [PATCH 153/269] feat: Custom prefixes and basic help command --- config.json | 6 ++++++ config/config.go | 1 + server/channelserver/handlers_cast_binary.go | 8 +++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index da75463ee..5a6e09216 100644 --- a/config.json +++ b/config.json @@ -14,6 +14,7 @@ "ClientMode": "ZZ", "QuestCacheExpiry": 300, "ProxyPort": 0, + "CommandPrefix": "!", "DevMode": true, "DevModeOptions": { "AutoCreateAccount": true, @@ -78,6 +79,11 @@ "RealtimeChannelID": "" }, "Commands": [ + { + "Name": "Help", + "Enabled": true, + "Prefix": "help" + }, { "Name": "Rights", "Enabled": false, diff --git a/config/config.go b/config/config.go index 31fedd246..5a50a511b 100644 --- a/config/config.go +++ b/config/config.go @@ -81,6 +81,7 @@ type Config struct { RealClientMode Mode QuestCacheExpiry int // Number of seconds to keep quest data cached ProxyPort uint16 // Forces the game to connect to a channel server proxy + CommandPrefix string // The prefix for commands DevMode bool DevModeOptions DevModeOptions diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index dbfafc68c..806c50749 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -318,6 +318,12 @@ func parseChatCommand(s *Session, command string) { } else { sendDisabledCommandMessage(s, commands["Teleport"]) } + case commands["Help"].Prefix: + if commands["Help"].Enabled { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y)) + } else { + sendDisabledCommandMessage(s, commands["Help"]) + } } } @@ -391,7 +397,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { bf.SetLE() chatMessage := &binpacket.MsgBinChat{} chatMessage.Parse(bf) - if strings.HasPrefix(chatMessage.Message, "!") { + if strings.HasPrefix(chatMessage.Message, s.server.erupeConfig.CommandPrefix) { parseChatCommand(s, chatMessage.Message) return } From ce773a6c569f464932d3c7c94b37183ed987726d Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 04:05:15 -0500 Subject: [PATCH 154/269] feat: Finish help command and add description to commands. --- config.json | 8 ++++++++ config/config.go | 7 ++++--- server/channelserver/handlers_cast_binary.go | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index 5a6e09216..e18506f3a 100644 --- a/config.json +++ b/config.json @@ -82,35 +82,43 @@ { "Name": "Help", "Enabled": true, + "Description": "Show all of the commands", "Prefix": "help" }, { "Name": "Rights", "Enabled": false, + "Description": "Directly alter user's applied courses.", "Prefix": "rights" }, { "Name": "Raviente", "Enabled": true, + "Description": "Start or view an ongoing raviante raid.", "Prefix": "ravi" }, { "Name": "Teleport", "Enabled": false, + "Description": "Teleport to a specified stage.", "Prefix": "tele" }, { "Name": "Reload", "Enabled": true, + "Description": "Reload all user sessions (Forces log out)", "Prefix": "reload" }, { "Name": "KeyQuest", "Enabled": false, + "Description": "Allow the overriding of necessary key quests.", "Prefix": "kqf" }, { "Name": "Course", "Enabled": true, + "Description": "Apply/remove user courses based on the name.", "Prefix": "course" }, { "Name": "PSN", "Enabled": true, + "Description": "Link a PSN account to your Erupe account.", "Prefix": "psn" } ], diff --git a/config/config.go b/config/config.go index 5a50a511b..12cb00316 100644 --- a/config/config.go +++ b/config/config.go @@ -169,9 +169,10 @@ type Discord struct { // Command is a channelserver chat command type Command struct { - Name string - Enabled bool - Prefix string + Name string + Enabled bool + Description string + Prefix string } // Course represents a course within MHF diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 806c50749..e74728906 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -10,6 +10,7 @@ import ( "erupe-ce/network/binpacket" "erupe-ce/network/mhfpacket" "fmt" + "golang.org/x/exp/maps" "golang.org/x/exp/slices" "math" "strconv" @@ -320,7 +321,9 @@ func parseChatCommand(s *Session, command string) { } case commands["Help"].Prefix: if commands["Help"].Enabled { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y)) + for _, command := range maps.Values(commands) { + sendServerChatMessage(s, fmt.Sprintf("%s: %s", command.Name, command.Description)) + } } else { sendDisabledCommandMessage(s, commands["Help"]) } From 23bc66eda508c4fe20538b8672527c4e8e88bfa6 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 04:07:44 -0500 Subject: [PATCH 155/269] fix: Consider prefix length when slicing. --- server/channelserver/handlers_cast_binary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index e74728906..990dfb1ce 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -85,7 +85,7 @@ func sendServerChatMessage(s *Session, message string) { } func parseChatCommand(s *Session, command string) { - args := strings.Split(command[1:], " ") + args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ") switch args[0] { case commands["PSN"].Prefix: if commands["PSN"].Enabled { From a846a71ca3d10a4fcea7d6ae373d637dd4a07144 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 27 Nov 2023 20:45:03 +1100 Subject: [PATCH 156/269] rewrite stringstack.go --- common/stringstack/stringstack.go | 19 ++----------------- server/channelserver/handlers_stage.go | 12 +++--------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/common/stringstack/stringstack.go b/common/stringstack/stringstack.go index 5f936e6a2..2e45fd7d1 100644 --- a/common/stringstack/stringstack.go +++ b/common/stringstack/stringstack.go @@ -6,13 +6,12 @@ import ( // StringStack is a basic LIFO "stack" for storing strings. type StringStack struct { - Locked bool - stack []string + stack []string } // New creates a new instance of StringStack func New() *StringStack { - return &StringStack{Locked: false} + return &StringStack{} } // Set sets up a new StringStack @@ -20,20 +19,6 @@ func (s *StringStack) Set(v string) { s.stack = []string{v} } -// Lock freezes the StringStack -func (s *StringStack) Lock() { - if !s.Locked { - s.Locked = true - } -} - -// Unlock unfreezes the StringStack -func (s *StringStack) Unlock() { - if s.Locked { - s.Locked = false - } -} - // Push pushes a string onto the stack. func (s *StringStack) Push(v string) { s.stack = append(s.stack, v) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 7a12d453e..3092db3fd 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -152,14 +152,11 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) // Push our current stage ID to the movement stack before entering another one. - if s.stage.id == "" { - s.stageMoveStack.Set(pkt.StageID) - } else { + if s.stage != nil { s.stage.Lock() s.stage.reservedClientSlots[s.charID] = false s.stage.Unlock() s.stageMoveStack.Push(s.stage.id) - s.stageMoveStack.Lock() } if s.reservationStage != nil { @@ -173,7 +170,6 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysBackStage) // Transfer back to the saved stage ID before the previous move or enter. - s.stageMoveStack.Unlock() backStage, err := s.stageMoveStack.Pop() if err != nil { panic(err) @@ -193,10 +189,8 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysMoveStage) - // Set a new move stack from the given stage ID if unlocked - if !s.stageMoveStack.Locked { - s.stageMoveStack.Set(pkt.StageID) - } + // Set a new move stack from the given stage ID + s.stageMoveStack.Set(pkt.StageID) doStageTransfer(s, pkt.AckHandle, pkt.StageID) } From bc12f4cd3b8e7a5cafd3f25cb3f6c0d6e418b0bc Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 27 Nov 2023 21:03:26 +1100 Subject: [PATCH 157/269] fix DecoMyset responses in legacy versions --- server/channelserver/handlers_house.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 9dc51995d..35ebfa308 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -252,16 +252,21 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) { s.logger.Error("Failed to load decomyset", zap.Error(err)) } if len(data) == 0 { + data = []byte{0x01, 0x00} if s.server.erupeConfig.RealClientMode < _config.G10 { data = []byte{0x00, 0x00} } - data = []byte{0x01, 0x00} } doAckBufSucceed(s, pkt.AckHandle, data) } func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveDecoMyset) + // TODO: Backwards compatibility for DecoMyset + if s.server.erupeConfig.RealClientMode < _config.ZZ { + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } // https://gist.github.com/Andoryuuta/9c524da7285e4b5ca7e52e0fc1ca1daf var loadData []byte bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[1:]) // skip first unk byte From f0744b4040cc82875532e83d8a698e866da6267b Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 27 Nov 2023 21:59:58 +1100 Subject: [PATCH 158/269] disable KQF command before G10 --- server/channelserver/handlers_cast_binary.go | 26 +++++++++++--------- server/channelserver/sys_language.go | 2 ++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index dbfafc68c..b6f260bf1 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -168,17 +168,21 @@ func parseChatCommand(s *Session, command string) { } case commands["KeyQuest"].Prefix: if commands["KeyQuest"].Enabled { - if len(args) > 1 { - if args[1] == "get" { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf)) - } else if args[1] == "set" { - if len(args) > 2 && len(args[2]) == 16 { - hexd, _ := hex.DecodeString(args[2]) - s.kqf = hexd - s.kqfOverride = true - sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"]) - } else { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix)) + if s.server.erupeConfig.RealClientMode < _config.G10 { + sendServerChatMessage(s, s.server.dict["commandKqfVersion"]) + } else { + if len(args) > 1 { + if args[1] == "get" { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf)) + } else if args[1] == "set" { + if len(args) > 2 && len(args[2]) == 16 { + hexd, _ := hex.DecodeString(args[2]) + s.kqf = hexd + s.kqfOverride = true + sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"]) + } else { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix)) + } } } } diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index dbd48cfb8..df74e1212 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -12,6 +12,7 @@ func getLangStrings(s *Server) map[string]string { strings["commandKqfGet"] = "現在のキークエストフラグ:%x" strings["commandKqfSetError"] = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx" strings["commandKqfSetSuccess"] = "キークエストのフラグが更新されました。ワールド/ランドを移動してください" + strings["commandKqfVersion"] = "This command is disabled prior to MHFG10" strings["commandRightsError"] = "コース更新コマンドエラー 例:%s x" strings["commandRightsSuccess"] = "コース情報を更新しました:%d" strings["commandCourseError"] = "コース確認コマンドエラー 例:%s " @@ -64,6 +65,7 @@ func getLangStrings(s *Server) map[string]string { strings["commandKqfGet"] = "KQF: %x" strings["commandKqfSetError"] = "Error in command. Format: %s set xxxxxxxxxxxxxxxx" strings["commandKqfSetSuccess"] = "KQF set, please switch Land/World" + strings["commandKqfVersion"] = "This command is disabled prior to MHFG10" strings["commandRightsError"] = "Error in command. Format: %s x" strings["commandRightsSuccess"] = "Set rights integer: %d" strings["commandCourseError"] = "Error in command. Format: %s " From c152e2d0b93feafd23c6fbe02dc39449b63dc56e Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 27 Nov 2023 22:00:47 +1100 Subject: [PATCH 159/269] verify save pointers in legacy versions G3->G9.1 --- server/channelserver/handlers_character.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index cf17693d7..5c8952316 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -71,7 +71,9 @@ func getPointers() map[SavePointer]int { pointers[pGardenData] = 142424 pointers[pRP] = 142614 pointers[pKQF] = 146720 - case _config.Z2, _config.Z1, _config.G101, _config.G10: + case _config.Z2, _config.Z1, _config.G101, _config.G10, _config.G91, _config.G9, _config.G81, _config.G8, + _config.G7, _config.G61, _config.G6, _config.G52, _config.G51, _config.G5, _config.GG, _config.G32, _config.G31, + _config.G3: pointers[pWeaponID] = 92522 pointers[pWeaponType] = 92789 pointers[pHouseTier] = 93900 From ce849ef06e3aae385ebad92d5d84785da7558187 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 27 Nov 2023 22:09:02 +1100 Subject: [PATCH 160/269] enable save pointers in legacy versions G3->G9.1 --- server/channelserver/handlers_character.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 5c8952316..8d03aff1e 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -195,7 +195,7 @@ func (save *CharacterSaveData) Decompress() error { func (save *CharacterSaveData) updateSaveDataWithStruct() { rpBytes := make([]byte, 2) binary.LittleEndian.PutUint16(rpBytes, save.RP) - if _config.ErupeConfig.RealClientMode >= _config.G10 { + if _config.ErupeConfig.RealClientMode >= _config.G3 { copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF) } else if _config.ErupeConfig.RealClientMode == _config.F5 || _config.ErupeConfig.RealClientMode == _config.F4 { @@ -212,7 +212,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.Gender = false } if !save.IsNewCharacter { - if (_config.ErupeConfig.RealClientMode >= _config.F4 && _config.ErupeConfig.RealClientMode <= _config.F5) || _config.ErupeConfig.RealClientMode >= _config.G10 { + if (_config.ErupeConfig.RealClientMode == _config.F4 || _config.ErupeConfig.RealClientMode == _config.F5) || _config.ErupeConfig.RealClientMode >= _config.G3 { save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] @@ -223,12 +223,13 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.WeaponType = save.decompSave[save.Pointers[pWeaponType]] save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2]) - } - - if _config.ErupeConfig.RealClientMode >= _config.G10 { - save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] - if save.HRP == uint16(999) { - save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))) + if _config.ErupeConfig.RealClientMode >= _config.G3 { + if save.HRP == uint16(999) { + save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))) + } + } + if _config.ErupeConfig.RealClientMode >= _config.G10 { + save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] } } } From 7d0ef7db2337b1627ed2331fdc7f5976762e84e8 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 27 Nov 2023 22:20:17 +1100 Subject: [PATCH 161/269] enable save pointers in legacy versions G1->G2 --- server/channelserver/handlers_character.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 8d03aff1e..9d90cc898 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -73,7 +73,7 @@ func getPointers() map[SavePointer]int { pointers[pKQF] = 146720 case _config.Z2, _config.Z1, _config.G101, _config.G10, _config.G91, _config.G9, _config.G81, _config.G8, _config.G7, _config.G61, _config.G6, _config.G52, _config.G51, _config.G5, _config.GG, _config.G32, _config.G31, - _config.G3: + _config.G3, _config.G2, _config.G1: pointers[pWeaponID] = 92522 pointers[pWeaponType] = 92789 pointers[pHouseTier] = 93900 @@ -195,11 +195,11 @@ func (save *CharacterSaveData) Decompress() error { func (save *CharacterSaveData) updateSaveDataWithStruct() { rpBytes := make([]byte, 2) binary.LittleEndian.PutUint16(rpBytes, save.RP) - if _config.ErupeConfig.RealClientMode >= _config.G3 { + if _config.ErupeConfig.RealClientMode >= _config.F4 { copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) + } + if _config.ErupeConfig.RealClientMode >= _config.G10 { copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF) - } else if _config.ErupeConfig.RealClientMode == _config.F5 || _config.ErupeConfig.RealClientMode == _config.F4 { - copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) } } @@ -212,7 +212,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.Gender = false } if !save.IsNewCharacter { - if (_config.ErupeConfig.RealClientMode == _config.F4 || _config.ErupeConfig.RealClientMode == _config.F5) || _config.ErupeConfig.RealClientMode >= _config.G3 { + if _config.ErupeConfig.RealClientMode >= _config.F4 { save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] @@ -223,7 +223,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.WeaponType = save.decompSave[save.Pointers[pWeaponType]] save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2]) - if _config.ErupeConfig.RealClientMode >= _config.G3 { + if _config.ErupeConfig.RealClientMode >= _config.G1 { if save.HRP == uint16(999) { save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))) } From dcac7b433ef602ef513b5d943d554b6921d2c275 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 27 Nov 2023 23:10:25 +1100 Subject: [PATCH 162/269] update Go dependencies --- go.mod | 31 +++++++++++++----------- go.sum | 76 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 57 insertions(+), 50 deletions(-) diff --git a/go.mod b/go.mod index 93c8f83ad..410eb88b0 100644 --- a/go.mod +++ b/go.mod @@ -4,32 +4,35 @@ go 1.21 require ( github.com/bwmarrin/discordgo v0.27.1 - github.com/gorilla/handlers v1.5.1 - github.com/gorilla/mux v1.8.0 + github.com/gorilla/handlers v1.5.2 + github.com/gorilla/mux v1.8.1 github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 - github.com/spf13/viper v1.16.0 - go.uber.org/zap v1.25.0 - golang.org/x/crypto v0.12.0 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/text v0.12.0 + github.com/spf13/viper v1.17.0 + go.uber.org/zap v1.26.0 + golang.org/x/crypto v0.15.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/text v0.14.0 ) require ( - github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.9 // indirect - github.com/spf13/afero v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sys v0.11.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.14.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9078343f0..1c6fbeb53 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,6 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY= github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -51,21 +49,21 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -127,13 +125,13 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -162,30 +160,34 @@ github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRU github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= -github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -209,8 +211,8 @@ go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -218,8 +220,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -230,8 +232,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -287,6 +289,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -341,9 +345,8 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -353,8 +356,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -498,8 +501,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= From 88652e1dc0c7e44b300767831dd421c836940d65 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 15:32:52 -0500 Subject: [PATCH 163/269] chore: Change command name to show the prefix instead. --- server/channelserver/handlers_cast_binary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index ef9ee8798..10ac2fa7c 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -326,7 +326,7 @@ func parseChatCommand(s *Session, command string) { case commands["Help"].Prefix: if commands["Help"].Enabled { for _, command := range maps.Values(commands) { - sendServerChatMessage(s, fmt.Sprintf("%s: %s", command.Name, command.Description)) + sendServerChatMessage(s, fmt.Sprintf("!%s: %s", command.Prefix, command.Description)) } } else { sendDisabledCommandMessage(s, commands["Help"]) From de5c3addd19b3a4f6af89cd28b08b241200fdeee Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 15:34:28 -0500 Subject: [PATCH 164/269] fix: Show config prefix instead of just ! --- server/channelserver/handlers_cast_binary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 10ac2fa7c..816ee3307 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -326,7 +326,7 @@ func parseChatCommand(s *Session, command string) { case commands["Help"].Prefix: if commands["Help"].Enabled { for _, command := range maps.Values(commands) { - sendServerChatMessage(s, fmt.Sprintf("!%s: %s", command.Prefix, command.Description)) + sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description)) } } else { sendDisabledCommandMessage(s, commands["Help"]) From 7b7b2874c5d58ba3c6eccdb9aba7d258146fb548 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 27 Nov 2023 16:14:32 -0500 Subject: [PATCH 165/269] fix: Fixed description for reload command. --- config.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index e18506f3a..e30514bb6 100644 --- a/config.json +++ b/config.json @@ -84,8 +84,7 @@ "Enabled": true, "Description": "Show all of the commands", "Prefix": "help" - }, - { + }, { "Name": "Rights", "Enabled": false, "Description": "Directly alter user's applied courses.", @@ -103,7 +102,7 @@ }, { "Name": "Reload", "Enabled": true, - "Description": "Reload all user sessions (Forces log out)", + "Description": "Delete and recreate all players within the session.", "Prefix": "reload" }, { "Name": "KeyQuest", From 2199e07be86734d9df9a57167b0adf7aff2339d4 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 28 Nov 2023 22:07:20 +1100 Subject: [PATCH 166/269] simplify SaveDecoMyset --- server/channelserver/handlers_house.go | 94 +++++++++++--------------- 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 35ebfa308..8587ee8aa 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -262,66 +262,50 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveDecoMyset) - // TODO: Backwards compatibility for DecoMyset - if s.server.erupeConfig.RealClientMode < _config.ZZ { + var temp []byte + err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&temp) + if err != nil { + s.logger.Error("Failed to load decomyset", zap.Error(err)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return } - // https://gist.github.com/Andoryuuta/9c524da7285e4b5ca7e52e0fc1ca1daf - var loadData []byte - 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.Error("Failed to load decomyset", zap.Error(err)) - } else { - numSets := bf.ReadUint8() // sets being written - // empty save - if len(loadData) == 0 { - loadData = []byte{0x01, 0x00} - } - savedSets := loadData[1] // existing saved sets - // no sets, new slice with just first 2 bytes for appends later - if savedSets == 0 { - loadData = []byte{0x01, 0x00} - } - for i := 0; i < int(numSets); i++ { - writeSet := bf.ReadUint16() - dataChunk := bf.ReadBytes(76) - setBytes := append([]byte{uint8(writeSet >> 8), uint8(writeSet & 0xff)}, dataChunk...) - for x := 0; true; x++ { - if x == int(savedSets) { - // appending set - if loadData[len(loadData)-1] == 0x10 { - // sanity check for if there was a messy manual import - loadData = append(loadData[:len(loadData)-2], setBytes...) - } else { - loadData = append(loadData, setBytes...) - } - savedSets++ - break - } - currentSet := loadData[3+(x*78)] - if int(currentSet) == int(writeSet) { - // replacing a set - loadData = append(loadData[:2+(x*78)], append(setBytes, loadData[2+((x+1)*78):]...)...) - break - } else if int(currentSet) > int(writeSet) { - // inserting before current set - loadData = append(loadData[:2+((x)*78)], append(setBytes, loadData[2+((x)*78):]...)...) - savedSets++ - break - } - } - loadData[1] = savedSets // update set count - } - dumpSaveData(s, loadData, "decomyset") - _, err := s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", loadData, s.charID) - if err != nil { - s.logger.Error("Failed to save decomyset", zap.Error(err)) - } + // Version handling + bf := byteframe.NewByteFrame() + var size int + if s.server.erupeConfig.RealClientMode >= _config.G10 { + size = 76 + bf.WriteUint8(1) + } else { + size = 68 + bf.WriteUint8(0) } - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + + // Build a map of set data + sets := make(map[uint16][]byte) + oldSets := byteframe.NewByteFrameFromBytes(temp[2:]) + for i := 0; i < len(temp)/size; i++ { + index := oldSets.ReadUint16() + sets[index] = oldSets.ReadBytes(uint(size)) + } + + // Overwrite existing sets + newSets := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[2:]) + for i := uint8(0); i < pkt.RawDataPayload[1]; i++ { + index := newSets.ReadUint16() + sets[index] = newSets.ReadBytes(uint(size)) + } + + // Serialise the set data + bf.WriteUint8(uint8(len(sets))) + for u, b := range sets { + bf.WriteUint16(u) + bf.WriteBytes(b) + } + + dumpSaveData(s, bf.Data(), "decomyset") + s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", bf.Data(), s.charID) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } type Title struct { From 2502b47077ebd677346c8cb80f2476e3a9c2ec11 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 28 Nov 2023 22:25:36 +1100 Subject: [PATCH 167/269] simplify SaveDecoMyset --- server/channelserver/handlers_house.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 8587ee8aa..1cb47ec3e 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -272,7 +272,7 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { // Version handling bf := byteframe.NewByteFrame() - var size int + var size uint if s.server.erupeConfig.RealClientMode >= _config.G10 { size = 76 bf.WriteUint8(1) @@ -284,16 +284,16 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { // Build a map of set data sets := make(map[uint16][]byte) oldSets := byteframe.NewByteFrameFromBytes(temp[2:]) - for i := 0; i < len(temp)/size; i++ { + for i := uint8(0); i < temp[1]; i++ { index := oldSets.ReadUint16() - sets[index] = oldSets.ReadBytes(uint(size)) + sets[index] = oldSets.ReadBytes(size) } // Overwrite existing sets newSets := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[2:]) for i := uint8(0); i < pkt.RawDataPayload[1]; i++ { index := newSets.ReadUint16() - sets[index] = newSets.ReadBytes(uint(size)) + sets[index] = newSets.ReadBytes(size) } // Serialise the set data From acf942075c5663df3ef852f6339e2b21a77699e5 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 28 Nov 2023 23:13:26 +1100 Subject: [PATCH 168/269] use fallback backStage --- server/channelserver/handlers_stage.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 3092db3fd..abd9d3ba5 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -171,8 +171,8 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { // Transfer back to the saved stage ID before the previous move or enter. backStage, err := s.stageMoveStack.Pop() - if err != nil { - panic(err) + if backStage == "" || err != nil { + backStage = "sl1Ns200p0a0u0" } if _, exists := s.stage.reservedClientSlots[s.charID]; exists { From 8d02c9f9071e0fa50889373e048158e3d649869d Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 28 Nov 2023 23:40:47 +1100 Subject: [PATCH 169/269] remove unnecessary dependency change --- server/channelserver/handlers_cast_binary.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 816ee3307..4d3fc639a 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -10,7 +10,6 @@ import ( "erupe-ce/network/binpacket" "erupe-ce/network/mhfpacket" "fmt" - "golang.org/x/exp/maps" "golang.org/x/exp/slices" "math" "strconv" @@ -325,7 +324,7 @@ func parseChatCommand(s *Session, command string) { } case commands["Help"].Prefix: if commands["Help"].Enabled { - for _, command := range maps.Values(commands) { + for _, command := range commands { sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description)) } } else { From 1cd60eb5aee8ea8d7610ee9f327947a647d05600 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 28 Nov 2023 23:41:22 +1100 Subject: [PATCH 170/269] hide disabled commands --- server/channelserver/handlers_cast_binary.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 4d3fc639a..71a8a6f07 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -325,7 +325,9 @@ func parseChatCommand(s *Session, command string) { case commands["Help"].Prefix: if commands["Help"].Enabled { for _, command := range commands { - sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description)) + if command.Enabled { + sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description)) + } } } else { sendDisabledCommandMessage(s, commands["Help"]) From d7b400c812c3fd1781ce38fab35a0738520daeaf Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 28 Nov 2023 23:41:54 +1100 Subject: [PATCH 171/269] revise command descriptions --- config.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config.json b/config.json index e30514bb6..688a9879b 100644 --- a/config.json +++ b/config.json @@ -82,42 +82,42 @@ { "Name": "Help", "Enabled": true, - "Description": "Show all of the commands", + "Description": "Show enabled chat commands", "Prefix": "help" }, { "Name": "Rights", "Enabled": false, - "Description": "Directly alter user's applied courses.", + "Description": "Overwrite the Rights value on your account", "Prefix": "rights" }, { "Name": "Raviente", "Enabled": true, - "Description": "Start or view an ongoing raviante raid.", + "Description": "Various Raviente siege commands", "Prefix": "ravi" }, { "Name": "Teleport", "Enabled": false, - "Description": "Teleport to a specified stage.", + "Description": "Teleport to specified coordinates", "Prefix": "tele" }, { "Name": "Reload", "Enabled": true, - "Description": "Delete and recreate all players within the session.", + "Description": "Reload all players in your Land", "Prefix": "reload" }, { "Name": "KeyQuest", "Enabled": false, - "Description": "Allow the overriding of necessary key quests.", + "Description": "Overwrite your HR Key Quest progress", "Prefix": "kqf" }, { "Name": "Course", "Enabled": true, - "Description": "Apply/remove user courses based on the name.", + "Description": "Toggle Courses on your account", "Prefix": "course" }, { "Name": "PSN", "Enabled": true, - "Description": "Link a PSN account to your Erupe account.", + "Description": "Link a PlayStation Network ID to your account", "Prefix": "psn" } ], From ea981ca70f2813dfa516d6ed251677486e405f29 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 29 Nov 2023 21:36:50 +1100 Subject: [PATCH 172/269] add Koban Shop items to OtherShops.sql --- bundled-schema/OtherShops.sql | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/bundled-schema/OtherShops.sql b/bundled-schema/OtherShops.sql index d25e453b0..3c88bb896 100644 --- a/bundled-schema/OtherShops.sql +++ b/bundled-schema/OtherShops.sql @@ -5,6 +5,42 @@ INSERT INTO public.shop_items VALUES (5,5,16516,100,1,0,0,1,0,0,0,0), (5,5,16517,100,1,0,0,1,0,0,0,0), + (6,5,9958,3,3,1,0,0,0,0,0,0), + (6,5,1897,3,1,1,0,0,0,0,0,0), + (6,5,8889,3,1,0,0,1,0,0,0,0), + (6,5,6176,3,6,1,0,0,0,0,0,0), + (6,5,1472,3,10,1,0,0,0,0,0,0), + (6,5,7280,3,3,0,0,1,0,0,0,0), + (6,5,8027,3,30,1,0,0,0,0,0,0), + (6,5,8028,3,30,1,0,0,0,0,0,0), + (6,5,8029,3,30,1,0,0,0,0,0,0), + (6,5,8026,3,30,1,0,0,0,0,0,0), + (6,5,8030,3,30,1,0,0,0,0,0,0), + (6,5,4353,3,30,1,0,0,0,0,0,0), + (6,5,4354,3,30,1,0,0,0,0,0,0), + (6,5,4355,3,30,1,0,0,0,0,0,0), + (6,5,4356,3,30,1,0,0,0,0,0,0), + (6,5,4357,3,30,1,0,0,0,0,0,0), + (6,5,4745,3,30,1,0,0,0,0,0,0), + (6,5,4746,3,30,1,0,0,0,0,0,0), + (6,5,4747,3,30,1,0,0,0,0,0,0), + (6,5,4748,3,30,1,0,0,0,0,0,0), + (6,5,4749,3,30,1,0,0,0,0,0,0), + (6,5,5122,3,30,1,0,0,0,0,0,0), + (6,5,5123,3,30,1,0,0,0,0,0,0), + (6,5,5124,3,30,1,0,0,0,0,0,0), + (6,5,5125,3,30,1,0,0,0,0,0,0), + (6,5,5126,3,30,1,0,0,0,0,0,0), + (6,5,5795,3,30,1,0,0,0,0,0,0), + (6,5,5796,3,30,1,0,0,0,0,0,0), + (6,5,5797,3,30,1,0,0,0,0,0,0), + (6,5,5798,3,30,1,0,0,0,0,0,0), + (6,5,5799,3,30,1,0,0,0,0,0,0), + (6,5,6168,3,30,1,0,0,0,0,0,0), + (6,5,6169,3,30,1,0,0,0,0,0,0), + (6,5,6170,3,30,1,0,0,0,0,0,0), + (6,5,6171,3,30,1,0,0,0,0,0,0), + (6,5,6172,3,30,1,0,0,0,0,0,0), (7,0,13190,10,1,0,0,0,0,0,0,0), (7,0,1662,10,1,0,0,0,0,0,0,0), (7,0,10179,100,1,0,0,0,0,0,0,0); From e914cf406b9dac4d4202e63c565753046d491045 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 29 Nov 2023 21:37:41 +1100 Subject: [PATCH 173/269] limit EnumerateShop responses --- network/mhfpacket/msg_mhf_enumerate_shop.go | 4 ++-- server/channelserver/handlers_shop_gacha.go | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/network/mhfpacket/msg_mhf_enumerate_shop.go b/network/mhfpacket/msg_mhf_enumerate_shop.go index 153095db4..d57655e98 100644 --- a/network/mhfpacket/msg_mhf_enumerate_shop.go +++ b/network/mhfpacket/msg_mhf_enumerate_shop.go @@ -14,7 +14,7 @@ type MsgMhfEnumerateShop struct { AckHandle uint32 ShopType uint8 // 1 running gachas, 10 normal shop extensions, 8 Diva Defense shop ShopID uint32 - Unk2 uint16 // 00 80 running gachas, 00 20 normal shop + Limit uint16 Unk3 uint8 Unk4 uint8 Unk5 uint32 @@ -30,7 +30,7 @@ func (m *MsgMhfEnumerateShop) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie m.AckHandle = bf.ReadUint32() m.ShopType = bf.ReadUint8() m.ShopID = bf.ReadUint32() - m.Unk2 = bf.ReadUint16() + m.Limit = bf.ReadUint16() m.Unk3 = bf.ReadUint8() if _config.ErupeConfig.RealClientMode >= _config.G2 { m.Unk4 = bf.ReadUint8() diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index c18119c6e..01c87ffbd 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -241,6 +241,9 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { case 10: // Item shop, 0-8 bf := byteframe.NewByteFrame() items := getShopItems(s, pkt.ShopType, pkt.ShopID) + if len(items) > int(pkt.Limit) { + items = items[:pkt.Limit] + } writeShopItems(bf, items) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From c996975bf1b8350788a379116129ffc3063e66bc Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 29 Nov 2023 21:38:32 +1100 Subject: [PATCH 174/269] correct EnumerateShop response --- server/channelserver/handlers_shop_gacha.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 01c87ffbd..297ebb9a0 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -10,13 +10,13 @@ import ( type ShopItem struct { ID uint32 `db:"id"` - ItemID uint16 `db:"item_id"` + ItemID uint32 `db:"item_id"` Cost uint32 `db:"cost"` Quantity uint16 `db:"quantity"` MinHR uint16 `db:"min_hr"` MinSR uint16 `db:"min_sr"` MinGR uint16 `db:"min_gr"` - StoreLevel uint16 `db:"store_level"` + StoreLevel uint8 `db:"store_level"` MaxQuantity uint16 `db:"max_quantity"` UsedQuantity uint16 `db:"used_quantity"` RoadFloors uint16 `db:"road_floors"` @@ -62,14 +62,14 @@ func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem) { bf.WriteUint16(uint16(len(items))) for _, item := range items { bf.WriteUint32(item.ID) - bf.WriteUint16(0) - bf.WriteUint16(item.ItemID) + bf.WriteUint32(item.ItemID) bf.WriteUint32(item.Cost) bf.WriteUint16(item.Quantity) bf.WriteUint16(item.MinHR) bf.WriteUint16(item.MinSR) bf.WriteUint16(item.MinGR) - bf.WriteUint16(item.StoreLevel) + bf.WriteUint8(0) // Unk + bf.WriteUint8(item.StoreLevel) bf.WriteUint16(item.MaxQuantity) bf.WriteUint16(item.UsedQuantity) bf.WriteUint16(item.RoadFloors) From 4a962e2701c9068edabed5122e4e173b9b8ea94a Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 29 Nov 2023 23:14:34 +1100 Subject: [PATCH 175/269] fix SaveDecoMyset on first save --- server/channelserver/handlers_house.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 1cb47ec3e..62561c96d 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -281,6 +281,11 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(0) } + // Handle nil data + if len(temp) == 0 { + temp = append(bf.Data(), uint8(0)) + } + // Build a map of set data sets := make(map[uint16][]byte) oldSets := byteframe.NewByteFrameFromBytes(temp[2:]) From 15739ad0d2a1e9cab40e228af4cf9ecbaa08b905 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 29 Nov 2023 23:15:32 +1100 Subject: [PATCH 176/269] fix PostTowerInfo not incrementing --- server/channelserver/handlers_tower.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_tower.go b/server/channelserver/handlers_tower.go index a9bc1421c..407bc8d55 100644 --- a/server/channelserver/handlers_tower.go +++ b/server/channelserver/handlers_tower.go @@ -126,8 +126,8 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) { skills := "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,0,0,0,0,0,0,0,0,0,0,0" s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills) s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID) - case 7: - s.server.db.Exec(`UPDATE tower SET tr=$1, trp=trp+$2, block1=block1+$3 WHERE char_id=$4`, pkt.TR, pkt.TRP, pkt.Block1, s.charID) + case 1, 7: + s.server.db.Exec(`UPDATE tower SET tr=$1, trp=COALESCE(trp, 0)+$2, block1=COALESCE(block1, 0)+$3 WHERE char_id=$4`, pkt.TR, pkt.TRP, pkt.Block1, s.charID) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } From 4376ed667836976675098360a6d84aa0181678fb Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 29 Nov 2023 23:41:06 +1100 Subject: [PATCH 177/269] partial backport EnumerateShop --- server/channelserver/handlers_shop_gacha.go | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 297ebb9a0..3058fb632 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -61,19 +61,30 @@ func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem) { bf.WriteUint16(uint16(len(items))) bf.WriteUint16(uint16(len(items))) for _, item := range items { - bf.WriteUint32(item.ID) + if _config.ErupeConfig.RealClientMode >= _config.Z2 { + bf.WriteUint32(item.ID) + } bf.WriteUint32(item.ItemID) bf.WriteUint32(item.Cost) bf.WriteUint16(item.Quantity) bf.WriteUint16(item.MinHR) bf.WriteUint16(item.MinSR) - bf.WriteUint16(item.MinGR) + if _config.ErupeConfig.RealClientMode >= _config.Z2 { + bf.WriteUint16(item.MinGR) + } bf.WriteUint8(0) // Unk bf.WriteUint8(item.StoreLevel) - bf.WriteUint16(item.MaxQuantity) - bf.WriteUint16(item.UsedQuantity) - bf.WriteUint16(item.RoadFloors) - bf.WriteUint16(item.RoadFatalis) + if _config.ErupeConfig.RealClientMode >= _config.Z2 { + bf.WriteUint16(item.MaxQuantity) + bf.WriteUint16(item.UsedQuantity) + } + if _config.ErupeConfig.RealClientMode == _config.Z1 { + bf.WriteUint8(uint8(item.RoadFloors)) + bf.WriteUint8(uint8(item.RoadFatalis)) + } else if _config.ErupeConfig.RealClientMode >= _config.Z2 { + bf.WriteUint16(item.RoadFloors) + bf.WriteUint16(item.RoadFatalis) + } } } From 617d600f9a08f2481fd282a3b440dd736c00273f Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 30 Nov 2023 00:07:09 +1100 Subject: [PATCH 178/269] test TSP accumulation --- server/channelserver/handlers_tower.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_tower.go b/server/channelserver/handlers_tower.go index 407bc8d55..4ce0bcc9f 100644 --- a/server/channelserver/handlers_tower.go +++ b/server/channelserver/handlers_tower.go @@ -127,7 +127,8 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) { s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills) s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID) case 1, 7: - s.server.db.Exec(`UPDATE tower SET tr=$1, trp=COALESCE(trp, 0)+$2, block1=COALESCE(block1, 0)+$3 WHERE char_id=$4`, pkt.TR, pkt.TRP, pkt.Block1, s.charID) + // This might give too much TSP? No idea what the rate is supposed to be + s.server.db.Exec(`UPDATE tower SET tr=$1, trp=COALESCE(trp, 0)+$2, tsp=COALESCE(tsp, 0)+$3, block1=COALESCE(block1, 0)+$4 WHERE char_id=$5`, pkt.TR, pkt.TRP, pkt.Cost, pkt.Block1, s.charID) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } From c46a3a78f0a151465aefb9561fdaf9fc51888e0f Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 30 Nov 2023 00:07:36 +1100 Subject: [PATCH 179/269] fix GetEarthStatus response --- server/channelserver/handlers.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index abba0cfe6..c94d78305 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -995,7 +995,9 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) { bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthMonsterOverride) bf.WriteInt32(0) bf.WriteInt32(0) - bf.WriteInt32(0) + if _config.ErupeConfig.RealClientMode >= _config.G91 { + bf.WriteInt32(0) + } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From 58708eaaf2530f9b556a3962608e5d97457452fe Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 30 Nov 2023 00:30:32 +1100 Subject: [PATCH 180/269] fix GetUdSchedule response --- server/channelserver/handlers_diva.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_diva.go b/server/channelserver/handlers_diva.go index 1867bfacd..2c60e9767 100644 --- a/server/channelserver/handlers_diva.go +++ b/server/channelserver/handlers_diva.go @@ -72,7 +72,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) { var timestamps []uint32 if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 { if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 { - if s.server.erupeConfig.RealClientMode <= _config.Z1 { + if s.server.erupeConfig.RealClientMode >= _config.Z2 { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32)) } else { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36)) @@ -84,7 +84,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) { timestamps = generateDivaTimestamps(s, start, false) } - if s.server.erupeConfig.RealClientMode <= _config.Z1 { + if s.server.erupeConfig.RealClientMode >= _config.Z2 { bf.WriteUint32(id) } for i := range timestamps { From 67e791be2be00ba27275c539757d033e981a9996 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 30 Nov 2023 00:31:09 +1100 Subject: [PATCH 181/269] parse & stub PostSeibattle --- network/mhfpacket/msg_mhf_post_seibattle.go | 29 ++++++++++++++++----- server/channelserver/handlers.go | 5 +++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/network/mhfpacket/msg_mhf_post_seibattle.go b/network/mhfpacket/msg_mhf_post_seibattle.go index 7e3e578c4..9c9101747 100644 --- a/network/mhfpacket/msg_mhf_post_seibattle.go +++ b/network/mhfpacket/msg_mhf_post_seibattle.go @@ -1,15 +1,24 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfPostSeibattle represents the MSG_MHF_POST_SEIBATTLE -type MsgMhfPostSeibattle struct{} +type MsgMhfPostSeibattle struct { + AckHandle uint32 + Unk0 uint8 + Unk1 uint8 + Unk2 uint32 + Unk3 uint8 + Unk4 uint16 + Unk5 uint16 + Unk6 uint8 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfPostSeibattle) Opcode() network.PacketID { @@ -18,7 +27,15 @@ func (m *MsgMhfPostSeibattle) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPostSeibattle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Unk0 = bf.ReadUint8() + m.Unk1 = bf.ReadUint8() + m.Unk2 = bf.ReadUint32() + m.Unk3 = bf.ReadUint8() + m.Unk4 = bf.ReadUint16() + m.Unk5 = bf.ReadUint16() + m.Unk6 = bf.ReadUint8() + return nil } // Build builds a binary packet from the current data. diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index c94d78305..f4c106fcd 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -1197,7 +1197,10 @@ func handleMsgMhfGetSeibattle(s *Session, p mhfpacket.MHFPacket) { doAckEarthSucceed(s, pkt.AckHandle, data) } -func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPostSeibattle) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} func handleMsgMhfGetDailyMissionMaster(s *Session, p mhfpacket.MHFPacket) {} From a108a675224eb74e5f776f2582385b43d81c7ea7 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 30 Nov 2023 00:34:51 +1100 Subject: [PATCH 182/269] add support for multiple Conquest War targets --- config.json | 2 +- config/config.go | 2 +- server/channelserver/handlers.go | 15 ++++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/config.json b/config.json index 688a9879b..7b11421a1 100644 --- a/config.json +++ b/config.json @@ -31,7 +31,7 @@ "QuestDebugTools": false, "EarthStatusOverride": 0, "EarthIDOverride": 0, - "EarthMonsterOverride": 0, + "EarthMonsterOverride": [0, 0, 0, 0], "SaveDumps": { "Enabled": true, "OutputDir": "save-backups" diff --git a/config/config.go b/config/config.go index 12cb00316..488321357 100644 --- a/config/config.go +++ b/config/config.go @@ -112,7 +112,7 @@ type DevModeOptions struct { QuestDebugTools bool // Enable various quest debug logs EarthStatusOverride int32 EarthIDOverride int32 - EarthMonsterOverride int32 + EarthMonsterOverride []int32 SaveDumps SaveDumpOptions } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index f4c106fcd..7886e7745 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -992,11 +992,16 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride) bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride) - bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthMonsterOverride) - bf.WriteInt32(0) - bf.WriteInt32(0) - if _config.ErupeConfig.RealClientMode >= _config.G91 { - bf.WriteInt32(0) + for i, m := range s.server.erupeConfig.DevModeOptions.EarthMonsterOverride { + if _config.ErupeConfig.RealClientMode >= _config.G91 { + if i == 3 { + break + } + } + if i == 4 { + break + } + bf.WriteInt32(m) } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From 51e1860a929f97522accd94bf9583d10a6f294e6 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 30 Nov 2023 00:35:39 +1100 Subject: [PATCH 183/269] add support for multiple Conquest War targets --- server/channelserver/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 7886e7745..7445d0f2b 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -993,7 +993,7 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) { bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride) bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride) for i, m := range s.server.erupeConfig.DevModeOptions.EarthMonsterOverride { - if _config.ErupeConfig.RealClientMode >= _config.G91 { + if _config.ErupeConfig.RealClientMode <= _config.G9 { if i == 3 { break } From b14b75ee23405b28dcf389e8a211bf7732aedcac Mon Sep 17 00:00:00 2001 From: rockisch Date: Thu, 30 Nov 2023 01:11:38 -0300 Subject: [PATCH 184/269] Make sure signv2 returns user token ID Noticed when refactoring 'mhf-iel' to support F5 that this info was not mapped correctly. --- server/signv2server/dbutils.go | 9 +++++---- server/signv2server/endpoints.go | 20 +++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/server/signv2server/dbutils.go b/server/signv2server/dbutils.go index dde729ac9..f5ea57846 100644 --- a/server/signv2server/dbutils.go +++ b/server/signv2server/dbutils.go @@ -32,13 +32,14 @@ func (s *Server) createNewUser(ctx context.Context, username string, password st return id, rights, err } -func (s *Server) createLoginToken(ctx context.Context, uid uint32) (string, error) { +func (s *Server) createLoginToken(ctx context.Context, uid uint32) (uint32, string, error) { loginToken := token.Generate(16) - _, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken) + var tid uint32 + err := s.db.QueryRowContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id", uid, loginToken).Scan(&tid) if err != nil { - return "", err + return 0, "", err } - return loginToken, nil + return tid, loginToken, nil } func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, error) { diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index ae5959809..b6cc03515 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -27,8 +27,9 @@ type LauncherResponse struct { } type User struct { - Token string `json:"token"` - Rights uint32 `json:"rights"` + TokenID uint32 `json:"tokenId"` + Token string `json:"token"` + Rights uint32 `json:"rights"` } type Character struct { @@ -65,14 +66,15 @@ type ExportData struct { Character map[string]interface{} `json:"character"` } -func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData { +func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint32, userToken string, characters []Character) AuthData { resp := AuthData{ CurrentTS: uint32(channelserver.TimeAdjusted().Unix()), ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()), EntranceCount: 1, User: User{ - Rights: userRights, - Token: userToken, + Rights: userRights, + TokenID: userTokenID, + Token: userToken, }, Characters: characters, PatchServer: s.erupeConfig.SignV2.PatchServer, @@ -142,7 +144,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { return } - userToken, err := s.createLoginToken(ctx, userID) + userTokenID, userToken, err := s.createLoginToken(ctx, userID) if err != nil { s.logger.Warn("Error registering login token", zap.Error(err)) w.WriteHeader(500) @@ -157,7 +159,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { if characters == nil { characters = []Character{} } - respData := s.newAuthData(userID, userRights, userToken, characters) + respData := s.newAuthData(userID, userRights, userTokenID, userToken, characters) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } @@ -191,13 +193,13 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { return } - userToken, err := s.createLoginToken(ctx, userID) + userTokenID, userToken, err := s.createLoginToken(ctx, userID) if err != nil { s.logger.Error("Error registering login token", zap.Error(err)) w.WriteHeader(500) return } - respData := s.newAuthData(userID, userRights, userToken, []Character{}) + respData := s.newAuthData(userID, userRights, userTokenID, userToken, []Character{}) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } From 4bae0e575841e40d08b54d1683dbfe48f608a8f3 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 30 Nov 2023 23:07:13 +1100 Subject: [PATCH 185/269] add option to alter maximum Clan Members --- config.json | 3 ++- config/config.go | 31 +++++++++++++------------- server/channelserver/handlers_guild.go | 19 ++++++++-------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/config.json b/config.json index 7b11421a1..fb1283ec3 100644 --- a/config.json +++ b/config.json @@ -46,7 +46,8 @@ "DisableLoginBoost": false, "DisableBoostTime": false, "BoostTimeDuration": 7200, - "GuildMealDuration": 3600, + "ClanMealDuration": 3600, + "ClanMemberLimits": [[0, 30], [3, 40], [7, 50], [10, 60]], "BonusQuestAllowance": 3, "DailyQuestAllowance": 1, "MezfesSoloTickets": 10, diff --git a/config/config.go b/config/config.go index 488321357..9b16156d0 100644 --- a/config/config.go +++ b/config/config.go @@ -123,21 +123,22 @@ type SaveDumpOptions struct { // GameplayOptions has various gameplay modifiers type GameplayOptions struct { - FeaturedWeapons int // Number of Active Feature weapons to generate daily - MaximumNP int // Maximum number of NP held by a player - MaximumRP uint16 // Maximum number of RP held by a player - MaximumFP uint32 // Maximum number of FP held by a player - TreasureHuntExpiry uint32 // Seconds until a Clan Treasure Hunt will expire - TreasureHuntPartnyaCooldown uint32 // Seconds until a Partnya can be assigned to another Clan Treasure Hunt - DisableLoginBoost bool // Disables the Login Boost system - DisableBoostTime bool // Disables the daily NetCafe Boost Time - BoostTimeDuration int // Second that the NetCafe Boost Time lasts - GuildMealDuration int // Second that a Guild Meal can be activated for after cooking - BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily - DailyQuestAllowance uint32 // Number of Daily Quests to allow daily - MezfesSoloTickets uint32 // Number of solo tickets given weekly - MezfesGroupTickets uint32 // Number of group tickets given weekly - LowLatencyRaviente bool // Toggles low latency mode for Raviente, can be network intensive + FeaturedWeapons int // Number of Active Feature weapons to generate daily + MaximumNP int // Maximum number of NP held by a player + MaximumRP uint16 // Maximum number of RP held by a player + MaximumFP uint32 // Maximum number of FP held by a player + TreasureHuntExpiry uint32 // Seconds until a Clan Treasure Hunt will expire + TreasureHuntPartnyaCooldown uint32 // Seconds until a Partnya can be assigned to another Clan Treasure Hunt + DisableLoginBoost bool // Disables the Login Boost system + DisableBoostTime bool // Disables the daily NetCafe Boost Time + BoostTimeDuration int // Second that the NetCafe Boost Time lasts + ClanMealDuration int // Second that a Clan Meal can be activated for after cooking + ClanMemberLimits [][]uint8 // Array of maximum Clan Members -> [Rank, Members] + BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily + DailyQuestAllowance uint32 // Number of Daily Quests to allow daily + MezfesSoloTickets uint32 // Number of solo tickets given weekly + MezfesGroupTickets uint32 // Number of group tickets given weekly + LowLatencyRaviente bool // Toggles low latency mode for Raviente, can be network intensive RegularRavienteMaxPlayers uint8 ViolentRavienteMaxPlayers uint8 BerserkRavienteMaxPlayers uint8 diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 30a150eef..8da4102b4 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -989,15 +989,16 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint32(guild.PugiOutfits) - if guild.Rank() >= 3 { - bf.WriteUint8(40) - } else if guild.Rank() >= 7 { - bf.WriteUint8(50) - } else if guild.Rank() >= 10 { - bf.WriteUint8(60) - } else { - bf.WriteUint8(30) + limit := s.server.erupeConfig.GameplayOptions.ClanMemberLimits[0][1] + for _, j := range s.server.erupeConfig.GameplayOptions.ClanMemberLimits { + if guild.Rank() >= uint16(j[0]) { + limit = j[1] + } } + if limit > 100 { + limit = 100 + } + bf.WriteUint8(limit) bf.WriteUint32(55000) bf.WriteUint32(0) @@ -1831,7 +1832,7 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfRegistGuildCooking) guild, _ := GetGuildInfoByCharacterId(s, s.charID) - startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.GuildMealDuration-3600) * time.Second) + startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.ClanMealDuration-3600) * time.Second) if pkt.OverwriteID != 0 { s.server.db.Exec("UPDATE guild_meals SET meal_id = $1, level = $2, created_at = $3 WHERE id = $4", pkt.MealID, pkt.Success, startTime, pkt.OverwriteID) } else { From d50eb923ba4f51477e3e2464dd26d2bfe2755198 Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 1 Dec 2023 00:51:05 +1100 Subject: [PATCH 186/269] validate additional SysLogin data --- server/channelserver/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 7445d0f2b..ea727d842 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -130,7 +130,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { if !s.server.erupeConfig.DevModeOptions.DisableTokenCheck { var token string - err := s.server.db.QueryRow("SELECT token FROM sign_sessions WHERE token=$1", pkt.LoginTokenString).Scan(&token) + err := s.server.db.QueryRow("SELECT token FROM sign_sessions ss INNER JOIN public.users u on ss.user_id = u.id WHERE token=$1 AND ss.id=$2 AND u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.LoginTokenString, pkt.LoginTokenNumber, pkt.CharID0).Scan(&token) if err != nil { s.rawConn.Close() s.logger.Warn(fmt.Sprintf("Invalid login token, offending CID: (%d)", pkt.CharID0)) From 64427fd317bfcb200b5c94727bb17b6512d99b61 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Dec 2023 17:51:20 +1100 Subject: [PATCH 187/269] refactor TransitMessage handler --- server/channelserver/handlers.go | 183 +++++++++++++++++++------------ 1 file changed, 110 insertions(+), 73 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index ea727d842..b804c2026 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -366,36 +366,41 @@ func handleMsgSysRightsReload(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfTransitMessage) + bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) resp := byteframe.NewByteFrame() resp.WriteUint16(0) var count uint16 switch pkt.SearchType { - case 1: // CID - bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) - CharID := bf.ReadUint32() + case 1: // usersearchidx + cid := bf.ReadUint32() for _, c := range s.server.Channels { for _, session := range c.sessions { - if session.charID == CharID { + if session.charID == cid { count++ sessionName := stringsupport.UTF8ToSJIS(session.Name) sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) resp.WriteUint16(c.Port) resp.WriteUint32(session.charID) - resp.WriteBool(true) + resp.WriteUint8(uint8(len(sessionStage) + 1)) resp.WriteUint8(uint8(len(sessionName) + 1)) resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]))) - resp.WriteBytes(make([]byte, 40)) - resp.WriteUint8(uint8(len(sessionStage) + 1)) + + // TODO: These cases might be <=G2 + if _config.ErupeConfig.RealClientMode <= _config.G1 { + resp.WriteBytes(make([]byte, 8)) + } else { + resp.WriteBytes(make([]byte, 40)) + } resp.WriteBytes(make([]byte, 8)) + + resp.WriteNullTerminatedBytes(sessionStage) resp.WriteNullTerminatedBytes(sessionName) resp.WriteBytes(c.userBinaryParts[userBinaryPartID{session.charID, 3}]) - resp.WriteNullTerminatedBytes(sessionStage) } } } - case 2: // Name - bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) + case 2: // usersearchname bf.ReadUint16() // lenSearchTerm bf.ReadUint16() // maxResults bf.ReadUint8() // Unk @@ -411,28 +416,33 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) resp.WriteUint16(c.Port) + resp.WriteUint32(session.charID) - resp.WriteBool(true) + resp.WriteUint8(uint8(len(sessionStage) + 1)) resp.WriteUint8(uint8(len(sessionName) + 1)) resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{session.charID, 3}]))) - resp.WriteBytes(make([]byte, 40)) - resp.WriteUint8(uint8(len(sessionStage) + 1)) + + if _config.ErupeConfig.RealClientMode <= _config.G1 { + resp.WriteBytes(make([]byte, 8)) + } else { + resp.WriteBytes(make([]byte, 40)) + } resp.WriteBytes(make([]byte, 8)) + + resp.WriteNullTerminatedBytes(sessionStage) resp.WriteNullTerminatedBytes(sessionName) resp.WriteBytes(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]) - resp.WriteNullTerminatedBytes(sessionStage) } } } - case 3: // Enumerate Party - bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) + case 3: // lobbysearchname ip := bf.ReadBytes(4) ipString := fmt.Sprintf("%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]) port := bf.ReadUint16() - bf.ReadUint16() // lenStage + bf.ReadUint16() // Len stageID maxResults := bf.ReadUint16() - bf.ReadBytes(1) - stageID := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + bf.ReadUint8() + stageID := string(bf.ReadNullTerminatedBytes()) for _, c := range s.server.Channels { if c.IP == ipString && c.Port == port { for _, stage := range c.stages { @@ -442,9 +452,6 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } for session := range stage.clients { count++ - hrp := uint16(1) - gr := uint16(0) - s.server.db.QueryRow("SELECT hrp, gr FROM characters WHERE id=$1", session.charID).Scan(&hrp, &gr) sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) sessionName := stringsupport.UTF8ToSJIS(session.Name) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) @@ -452,57 +459,60 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint32(session.charID) resp.WriteUint8(uint8(len(sessionStage) + 1)) resp.WriteUint8(uint8(len(sessionName) + 1)) - resp.WriteUint8(0) - resp.WriteUint8(7) // lenBinary - resp.WriteBytes(make([]byte, 48)) + resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{session.charID, 3}]))) + + if _config.ErupeConfig.RealClientMode <= _config.G1 { + resp.WriteBytes(make([]byte, 8)) + } else { + resp.WriteBytes(make([]byte, 40)) + } + resp.WriteBytes(make([]byte, 8)) + resp.WriteNullTerminatedBytes(sessionStage) resp.WriteNullTerminatedBytes(sessionName) - resp.WriteUint16(hrp) - resp.WriteUint16(gr) - resp.WriteBytes([]byte{0x06, 0x10, 0x00}) // Unk + resp.WriteBytes(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]) } } } } } - case 4: // Find Party + case 4: // lobbysearch type FindPartyParams struct { StagePrefix string - RankRestriction uint16 - Targets []uint16 - Unk0 []uint16 - Unk1 []uint16 - QuestID []uint16 + RankRestriction int16 + Targets []int16 + Unk0 []int16 + Unk1 []int16 + QuestID []int16 } findPartyParams := FindPartyParams{ StagePrefix: "sl2Ls210", } - bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) - numParams := int(bf.ReadUint8()) + numParams := bf.ReadUint8() maxResults := bf.ReadUint16() - for i := 0; i < numParams; i++ { + for i := uint8(0); i < numParams; i++ { switch bf.ReadUint8() { case 0: - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.RankRestriction = bf.ReadUint16() + findPartyParams.RankRestriction = bf.ReadInt16() } else { - findPartyParams.RankRestriction = uint16(bf.ReadInt8()) + findPartyParams.RankRestriction = int16(bf.ReadInt8()) } } case 1: - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.Targets = append(findPartyParams.Targets, bf.ReadUint16()) + findPartyParams.Targets = append(findPartyParams.Targets, bf.ReadInt16()) } else { - findPartyParams.Targets = append(findPartyParams.Targets, uint16(bf.ReadInt8())) + findPartyParams.Targets = append(findPartyParams.Targets, int16(bf.ReadInt8())) } } case 2: - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { var value int16 if _config.ErupeConfig.RealClientMode >= _config.Z1 { value = bf.ReadInt16() @@ -523,30 +533,30 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } } case 3: // Unknown - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.Unk0 = append(findPartyParams.Unk0, bf.ReadUint16()) + findPartyParams.Unk0 = append(findPartyParams.Unk0, bf.ReadInt16()) } else { - findPartyParams.Unk0 = append(findPartyParams.Unk0, uint16(bf.ReadInt8())) + findPartyParams.Unk0 = append(findPartyParams.Unk0, int16(bf.ReadInt8())) } } case 4: // Looking for n or already have n - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.Unk1 = append(findPartyParams.Unk1, bf.ReadUint16()) + findPartyParams.Unk1 = append(findPartyParams.Unk1, bf.ReadInt16()) } else { - findPartyParams.Unk1 = append(findPartyParams.Unk1, uint16(bf.ReadInt8())) + findPartyParams.Unk1 = append(findPartyParams.Unk1, int16(bf.ReadInt8())) } } case 5: - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.QuestID = append(findPartyParams.QuestID, bf.ReadUint16()) + findPartyParams.QuestID = append(findPartyParams.QuestID, bf.ReadInt16()) } else { - findPartyParams.QuestID = append(findPartyParams.QuestID, uint16(bf.ReadInt8())) + findPartyParams.QuestID = append(findPartyParams.QuestID, int16(bf.ReadInt8())) } } } @@ -559,37 +569,64 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { if strings.HasPrefix(stage.id, findPartyParams.StagePrefix) { sb3 := byteframe.NewByteFrameFromBytes(stage.rawBinaryData[stageBinaryKey{1, 3}]) sb3.Seek(4, 0) - stageRankRestriction := sb3.ReadUint16() - stageTarget := sb3.ReadUint16() - if stageRankRestriction > findPartyParams.RankRestriction { + + stageDataParams := 8 + if _config.ErupeConfig.RealClientMode <= _config.G10 { + stageDataParams = 4 + } else if _config.ErupeConfig.RealClientMode <= _config.Z1 { + stageDataParams = 6 + } else if _config.ErupeConfig.RealClientMode <= _config.Z2 { + stageDataParams = 7 + } + + var stageData []int16 + for i := 0; i < stageDataParams; i++ { + if _config.ErupeConfig.RealClientMode >= _config.Z1 { + stageData = append(stageData, sb3.ReadInt16()) + } else { + stageData = append(stageData, int16(sb3.ReadInt8())) + } + } + + if stageData[0] > findPartyParams.RankRestriction { continue } + + var hasTarget bool if len(findPartyParams.Targets) > 0 { for _, target := range findPartyParams.Targets { - if target == stageTarget { + if target == stageData[1] { + hasTarget = true break } } - continue + if !hasTarget { + continue + } } + count++ - sessionStage := stringsupport.UTF8ToSJIS(stage.id) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) resp.WriteUint16(c.Port) + resp.WriteUint16(0) // Static? resp.WriteUint16(0) // Unk - resp.WriteUint16(uint16(len(stage.clients))) - resp.WriteUint16(stage.maxPlayers) - resp.WriteUint16(0) // Num clients entered from stage + resp.WriteUint16(uint16(len(stage.reservedClientSlots))) resp.WriteUint16(stage.maxPlayers) + resp.WriteUint16(uint16(len(stage.clients))) // Num clients entered from stage + + resp.WriteUint8(0) // Unk + resp.WriteUint8(uint8(stage.maxPlayers)) resp.WriteUint8(1) // Static? - resp.WriteUint8(uint8(len(sessionStage) + 1)) + resp.WriteUint8(uint8(len(stage.id) + 1)) resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 0}]))) resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 1}]))) - resp.WriteUint16(stageRankRestriction) - resp.WriteUint16(stageTarget) - resp.WriteBytes(make([]byte, 12)) - resp.WriteNullTerminatedBytes(sessionStage) + + for i := range stageData { + resp.WriteInt16(stageData[i]) + } + + resp.WriteNullTerminatedBytes([]byte(stage.id)) resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 0}]) resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 1}]) } From 622b2efa03f299ae08afc209b7a4d8d7da5020d4 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Dec 2023 18:38:33 +1100 Subject: [PATCH 188/269] refactor TransitMessage handler --- server/channelserver/handlers.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index b804c2026..ca39f3ce6 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -570,13 +570,11 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { sb3 := byteframe.NewByteFrameFromBytes(stage.rawBinaryData[stageBinaryKey{1, 3}]) sb3.Seek(4, 0) - stageDataParams := 8 + stageDataParams := 7 if _config.ErupeConfig.RealClientMode <= _config.G10 { stageDataParams = 4 } else if _config.ErupeConfig.RealClientMode <= _config.Z1 { stageDataParams = 6 - } else if _config.ErupeConfig.RealClientMode <= _config.Z2 { - stageDataParams = 7 } var stageData []int16 @@ -623,8 +621,14 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 1}]))) for i := range stageData { - resp.WriteInt16(stageData[i]) + if _config.ErupeConfig.RealClientMode >= _config.Z1 { + resp.WriteInt16(stageData[i]) + } else { + resp.WriteInt8(int8(stageData[i])) + } } + resp.WriteUint8(0) // Unk + resp.WriteUint8(0) // Unk resp.WriteNullTerminatedBytes([]byte(stage.id)) resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 0}]) @@ -633,10 +637,6 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } } } - if (pkt.SearchType == 1 || pkt.SearchType == 3) && count == 0 { - doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - return - } resp.Seek(0, io.SeekStart) resp.WriteUint16(count) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) From df173e9edf16043121a855df706e330924594942 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Dec 2023 20:15:22 +1100 Subject: [PATCH 189/269] refactor TransitMessage handler --- server/channelserver/handlers.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index ca39f3ce6..bac17354b 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -586,8 +586,10 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } } - if stageData[0] > findPartyParams.RankRestriction { - continue + if findPartyParams.RankRestriction >= 0 { + if stageData[0] > findPartyParams.RankRestriction { + continue + } } var hasTarget bool @@ -608,12 +610,12 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint16(c.Port) resp.WriteUint16(0) // Static? - resp.WriteUint16(0) // Unk - resp.WriteUint16(uint16(len(stage.reservedClientSlots))) + resp.WriteUint16(0) // Unk, [0 1 2] + resp.WriteUint16(uint16(len(stage.clients) + len(stage.reservedClientSlots))) resp.WriteUint16(stage.maxPlayers) - resp.WriteUint16(uint16(len(stage.clients))) // Num clients entered from stage + resp.WriteUint16(uint16(len(stage.reservedClientSlots))) - resp.WriteUint8(0) // Unk + resp.WriteUint8(0) // Static? resp.WriteUint8(uint8(stage.maxPlayers)) resp.WriteUint8(1) // Static? resp.WriteUint8(uint8(len(stage.id) + 1)) From 11bac4ecf3b6bd69d5319ed65fcbbcda1843d009 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Dec 2023 21:10:52 +1100 Subject: [PATCH 190/269] handle UnreserveSrg --- network/mhfpacket/msg_mhf_unreserve_srg.go | 15 +++++++++------ server/channelserver/handlers.go | 5 ++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/network/mhfpacket/msg_mhf_unreserve_srg.go b/network/mhfpacket/msg_mhf_unreserve_srg.go index f273662aa..9f545dabd 100644 --- a/network/mhfpacket/msg_mhf_unreserve_srg.go +++ b/network/mhfpacket/msg_mhf_unreserve_srg.go @@ -1,15 +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" ) // MsgMhfUnreserveSrg represents the MSG_MHF_UNRESERVE_SRG -type MsgMhfUnreserveSrg struct{} +type MsgMhfUnreserveSrg struct { + AckHandle uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfUnreserveSrg) Opcode() network.PacketID { @@ -18,7 +20,8 @@ func (m *MsgMhfUnreserveSrg) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUnreserveSrg) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index bac17354b..171bc666e 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -1020,7 +1020,10 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfStampcardPrize(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfUnreserveSrg(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfUnreserveSrg(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfUnreserveSrg) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} func handleMsgMhfKickExportForce(s *Session, p mhfpacket.MHFPacket) {} From a604836b62a846106c2427abc4dcbe7b3d1ec474 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Dec 2023 21:16:07 +1100 Subject: [PATCH 191/269] enumerate missing clients --- server/channelserver/handlers_clients.go | 3 +++ server/channelserver/handlers_stage.go | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_clients.go b/server/channelserver/handlers_clients.go index e1d2c4a49..4add1e9eb 100644 --- a/server/channelserver/handlers_clients.go +++ b/server/channelserver/handlers_clients.go @@ -29,6 +29,9 @@ func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) { for _, cid := range stage.clients { clients = append(clients, cid) } + for cid := range stage.reservedClientSlots { + clients = append(clients, cid) + } case 1: // Not ready for cid, ready := range stage.reservedClientSlots { if !ready { diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index abd9d3ba5..5e26c8697 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -386,7 +386,11 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(uint16(len(stage.reservedClientSlots))) bf.WriteUint16(uint16(len(stage.clients))) - bf.WriteUint16(uint16(len(stage.clients))) + if strings.HasPrefix(stage.id, "sl2Ls") { + bf.WriteUint16(uint16(len(stage.clients) + len(stage.reservedClientSlots))) + } else { + bf.WriteUint16(uint16(len(stage.clients))) + } bf.WriteUint16(stage.maxPlayers) var flags uint8 if stage.locked { From 9d54a9746c9ee8046789b69cebf85bf366ee4cbd Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Dec 2023 21:16:26 +1100 Subject: [PATCH 192/269] remove unused variable --- network/mhfpacket/msg_mhf_opr_member.go | 1 - 1 file changed, 1 deletion(-) diff --git a/network/mhfpacket/msg_mhf_opr_member.go b/network/mhfpacket/msg_mhf_opr_member.go index b0dceaba5..186ccc44d 100644 --- a/network/mhfpacket/msg_mhf_opr_member.go +++ b/network/mhfpacket/msg_mhf_opr_member.go @@ -13,7 +13,6 @@ type MsgMhfOprMember struct { AckHandle uint32 Blacklist bool Operation bool - Unk uint16 CharIDs []uint32 } From af3491b99613502c381020245ee068c8492615d8 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Dec 2023 21:18:00 +1100 Subject: [PATCH 193/269] add TODO --- server/channelserver/handlers.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 171bc666e..1fcbafd0b 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -613,6 +613,7 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint16(0) // Unk, [0 1 2] resp.WriteUint16(uint16(len(stage.clients) + len(stage.reservedClientSlots))) resp.WriteUint16(stage.maxPlayers) + // TODO: Retail returned the number of clients in quests, not workshop/my series resp.WriteUint16(uint16(len(stage.reservedClientSlots))) resp.WriteUint8(0) // Static? From 3d08e64b075c441a35cd26e0bbe565fc72cd1e06 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Dec 2023 21:18:16 +1100 Subject: [PATCH 194/269] add comments --- server/channelserver/handlers_guild.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 8da4102b4..ad5221281 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1003,7 +1003,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(55000) bf.WriteUint32(0) bf.WriteUint16(0) // Changing Room RP - bf.WriteUint16(0) + bf.WriteUint16(0) // Ignored if guild.AllianceID > 0 { alliance, err := GetAllianceData(s, guild.AllianceID) @@ -1013,7 +1013,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(alliance.ID) bf.WriteUint32(uint32(alliance.CreatedAt.Unix())) bf.WriteUint16(alliance.TotalMembers) - bf.WriteUint8(0) + bf.WriteUint8(0) // Ignored bf.WriteUint8(0) ps.Uint16(bf, alliance.Name, true) if alliance.SubGuild1ID > 0 { From d67ad93c63de88f950aef376250975c1cb13b0e6 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 3 Dec 2023 16:36:34 +1100 Subject: [PATCH 195/269] allow TransitMessage to respond to local requests --- server/channelserver/handlers.go | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 1fcbafd0b..c2f22a8f1 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -366,6 +366,12 @@ func handleMsgSysRightsReload(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfTransitMessage) + + local := false + if strings.Split(s.rawConn.RemoteAddr().String(), ":")[0] == "127.0.0.1" { + local = true + } + bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) resp := byteframe.NewByteFrame() resp.WriteUint16(0) @@ -379,7 +385,11 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { count++ sessionName := stringsupport.UTF8ToSJIS(session.Name) sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + if !local { + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + } else { + resp.WriteUint32(0x0100007F) + } resp.WriteUint16(c.Port) resp.WriteUint32(session.charID) resp.WriteUint8(uint8(len(sessionStage) + 1)) @@ -414,7 +424,11 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { count++ sessionName := stringsupport.UTF8ToSJIS(session.Name) sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + if !local { + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + } else { + resp.WriteUint32(0x0100007F) + } resp.WriteUint16(c.Port) resp.WriteUint32(session.charID) @@ -454,7 +468,11 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { count++ sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) sessionName := stringsupport.UTF8ToSJIS(session.Name) - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + if !local { + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + } else { + resp.WriteUint32(0x0100007F) + } resp.WriteUint16(c.Port) resp.WriteUint32(session.charID) resp.WriteUint8(uint8(len(sessionStage) + 1)) @@ -606,7 +624,11 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } count++ - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + if !local { + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + } else { + resp.WriteUint32(0x0100007F) + } resp.WriteUint16(c.Port) resp.WriteUint16(0) // Static? From da022d913babc9b3da038bd783078730e9ea0781 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 3 Dec 2023 17:40:47 +1100 Subject: [PATCH 196/269] refactor TransitMessage handler --- server/channelserver/handlers.go | 164 +++++++++++-------------------- 1 file changed, 55 insertions(+), 109 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index c2f22a8f1..4bce887ab 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -372,126 +372,72 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { local = true } + var maxResults, port, count uint16 + var cid uint32 + var term, ip string bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) + switch pkt.SearchType { + case 1: + maxResults = 1 + cid = bf.ReadUint32() + case 2: + bf.ReadUint16() // term length + maxResults = bf.ReadUint16() + bf.ReadUint8() // Unk + term = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + case 3: + _ip := bf.ReadBytes(4) + ip = fmt.Sprintf("%d.%d.%d.%d", _ip[3], _ip[2], _ip[1], _ip[0]) + port = bf.ReadUint16() + bf.ReadUint16() // term length + maxResults = bf.ReadUint16() + bf.ReadUint8() + term = string(bf.ReadNullTerminatedBytes()) + } + resp := byteframe.NewByteFrame() resp.WriteUint16(0) - var count uint16 switch pkt.SearchType { - case 1: // usersearchidx - cid := bf.ReadUint32() + case 1, 2, 3: // usersearchidx, usersearchname, lobbysearchname for _, c := range s.server.Channels { for _, session := range c.sessions { - if session.charID == cid { - count++ - sessionName := stringsupport.UTF8ToSJIS(session.Name) - sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) - if !local { - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) - } else { - resp.WriteUint32(0x0100007F) - } - resp.WriteUint16(c.Port) - resp.WriteUint32(session.charID) - resp.WriteUint8(uint8(len(sessionStage) + 1)) - resp.WriteUint8(uint8(len(sessionName) + 1)) - resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]))) - - // TODO: These cases might be <=G2 - if _config.ErupeConfig.RealClientMode <= _config.G1 { - resp.WriteBytes(make([]byte, 8)) - } else { - resp.WriteBytes(make([]byte, 40)) - } - resp.WriteBytes(make([]byte, 8)) - - resp.WriteNullTerminatedBytes(sessionStage) - resp.WriteNullTerminatedBytes(sessionName) - resp.WriteBytes(c.userBinaryParts[userBinaryPartID{session.charID, 3}]) - } - } - } - case 2: // usersearchname - bf.ReadUint16() // lenSearchTerm - bf.ReadUint16() // maxResults - bf.ReadUint8() // Unk - searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) - for _, c := range s.server.Channels { - for _, session := range c.sessions { - if count == 100 { + if count == maxResults { break } - if strings.Contains(session.Name, searchTerm) { - count++ - sessionName := stringsupport.UTF8ToSJIS(session.Name) - sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) - if !local { - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) - } else { - resp.WriteUint32(0x0100007F) - } - resp.WriteUint16(c.Port) + if pkt.SearchType == 1 && session.charID != cid { + continue + } + if pkt.SearchType == 2 && !strings.Contains(session.Name, term) { + continue + } + if pkt.SearchType == 3 && session.server.IP != ip && session.server.Port != port && session.stage.id != term { + continue + } + count++ + sessionName := stringsupport.UTF8ToSJIS(session.Name) + sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) + if !local { + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + } else { + resp.WriteUint32(0x0100007F) + } + resp.WriteUint16(c.Port) + resp.WriteUint32(session.charID) + resp.WriteUint8(uint8(len(sessionStage) + 1)) + resp.WriteUint8(uint8(len(sessionName) + 1)) + resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]))) - resp.WriteUint32(session.charID) - resp.WriteUint8(uint8(len(sessionStage) + 1)) - resp.WriteUint8(uint8(len(sessionName) + 1)) - resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{session.charID, 3}]))) - - if _config.ErupeConfig.RealClientMode <= _config.G1 { - resp.WriteBytes(make([]byte, 8)) - } else { - resp.WriteBytes(make([]byte, 40)) - } + // TODO: This case might be <=G2 + if _config.ErupeConfig.RealClientMode <= _config.G1 { resp.WriteBytes(make([]byte, 8)) - - resp.WriteNullTerminatedBytes(sessionStage) - resp.WriteNullTerminatedBytes(sessionName) - resp.WriteBytes(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]) + } else { + resp.WriteBytes(make([]byte, 40)) } - } - } - case 3: // lobbysearchname - ip := bf.ReadBytes(4) - ipString := fmt.Sprintf("%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]) - port := bf.ReadUint16() - bf.ReadUint16() // Len stageID - maxResults := bf.ReadUint16() - bf.ReadUint8() - stageID := string(bf.ReadNullTerminatedBytes()) - for _, c := range s.server.Channels { - if c.IP == ipString && c.Port == port { - for _, stage := range c.stages { - if stage.id == stageID { - if count == maxResults { - break - } - for session := range stage.clients { - count++ - sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) - sessionName := stringsupport.UTF8ToSJIS(session.Name) - if !local { - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) - } else { - resp.WriteUint32(0x0100007F) - } - resp.WriteUint16(c.Port) - resp.WriteUint32(session.charID) - resp.WriteUint8(uint8(len(sessionStage) + 1)) - resp.WriteUint8(uint8(len(sessionName) + 1)) - resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{session.charID, 3}]))) + resp.WriteBytes(make([]byte, 8)) - if _config.ErupeConfig.RealClientMode <= _config.G1 { - resp.WriteBytes(make([]byte, 8)) - } else { - resp.WriteBytes(make([]byte, 40)) - } - resp.WriteBytes(make([]byte, 8)) - - resp.WriteNullTerminatedBytes(sessionStage) - resp.WriteNullTerminatedBytes(sessionName) - resp.WriteBytes(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]) - } - } - } + resp.WriteNullTerminatedBytes(sessionStage) + resp.WriteNullTerminatedBytes(sessionName) + resp.WriteBytes(c.userBinaryParts[userBinaryPartID{session.charID, 3}]) } } case 4: // lobbysearch @@ -507,7 +453,7 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { StagePrefix: "sl2Ls210", } numParams := bf.ReadUint8() - maxResults := bf.ReadUint16() + maxResults = bf.ReadUint16() for i := uint8(0); i < numParams; i++ { switch bf.ReadUint8() { case 0: From 4ce65e47e63f862a62cf7ed1fdeda3d6141c8f1c Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 3 Dec 2023 17:50:01 +1100 Subject: [PATCH 197/269] revert stringstack.go changes --- common/stringstack/stringstack.go | 22 +++++++++++++++++++--- server/channelserver/handlers_stage.go | 6 +++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/common/stringstack/stringstack.go b/common/stringstack/stringstack.go index 2e45fd7d1..9f6b646ae 100644 --- a/common/stringstack/stringstack.go +++ b/common/stringstack/stringstack.go @@ -6,7 +6,8 @@ import ( // StringStack is a basic LIFO "stack" for storing strings. type StringStack struct { - stack []string + Locked bool + stack []string } // New creates a new instance of StringStack @@ -19,6 +20,20 @@ func (s *StringStack) Set(v string) { s.stack = []string{v} } +// Lock freezes the StringStack +func (s *StringStack) Lock() { + if !s.Locked { + s.Locked = true + } +} + +// Unlock unfreezes the StringStack +func (s *StringStack) Unlock() { + if s.Locked { + s.Locked = false + } +} + // Push pushes a string onto the stack. func (s *StringStack) Push(v string) { s.stack = append(s.stack, v) @@ -26,11 +41,12 @@ func (s *StringStack) Push(v string) { // Pop pops a string from the stack. func (s *StringStack) Pop() (string, error) { + var x string if len(s.stack) == 0 { - return "", errors.New("no items on stack") + return x, errors.New("no items on stack") } - x := s.stack[len(s.stack)-1] + x = s.stack[len(s.stack)-1] s.stack = s.stack[:len(s.stack)-1] return x, nil diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 5e26c8697..a9de55b28 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -157,6 +157,7 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { s.stage.reservedClientSlots[s.charID] = false s.stage.Unlock() s.stageMoveStack.Push(s.stage.id) + s.stageMoveStack.Lock() } if s.reservationStage != nil { @@ -170,6 +171,7 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysBackStage) // Transfer back to the saved stage ID before the previous move or enter. + s.stageMoveStack.Unlock() backStage, err := s.stageMoveStack.Pop() if backStage == "" || err != nil { backStage = "sl1Ns200p0a0u0" @@ -190,7 +192,9 @@ func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysMoveStage) // Set a new move stack from the given stage ID - s.stageMoveStack.Set(pkt.StageID) + if !s.stageMoveStack.Locked { + s.stageMoveStack.Set(pkt.StageID) + } doStageTransfer(s, pkt.AckHandle, pkt.StageID) } From 5662564842fe3a27f6e8637a8403257f089dd33e Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 3 Dec 2023 17:57:23 +1100 Subject: [PATCH 198/269] add option to dump raw saves --- config.json | 1 + config/config.go | 5 +++-- server/channelserver/handlers_data.go | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index fb1283ec3..5d66b9e33 100644 --- a/config.json +++ b/config.json @@ -34,6 +34,7 @@ "EarthMonsterOverride": [0, 0, 0, 0], "SaveDumps": { "Enabled": true, + "RawEnabled": false, "OutputDir": "save-backups" } }, diff --git a/config/config.go b/config/config.go index 9b16156d0..153cfedb3 100644 --- a/config/config.go +++ b/config/config.go @@ -117,8 +117,9 @@ type DevModeOptions struct { } type SaveDumpOptions struct { - Enabled bool - OutputDir string + Enabled bool + RawEnabled bool + OutputDir string } // GameplayOptions has various gameplay modifiers diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 5de1c4f65..805fa59f5 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -45,6 +45,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return } + if s.server.erupeConfig.DevModeOptions.SaveDumps.RawEnabled { + dumpSaveData(s, saveData, "raw-savedata") + } s.logger.Info("Updating save with blob") characterSaveData.decompSave = saveData } From ddcef6570fec36844aae309e8e77c2f5ac07a0a4 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 3 Dec 2023 19:05:30 +1100 Subject: [PATCH 199/269] convert EnumeratePrice to use structs --- server/channelserver/handlers.go | 168 +++++++++++++++++++++++++++++-- 1 file changed, 161 insertions(+), 7 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 4bce887ab..60bf06ea7 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -2,7 +2,6 @@ package channelserver import ( "encoding/binary" - "encoding/hex" "erupe-ce/common/mhfcourse" "erupe-ce/common/mhfmon" ps "erupe-ce/common/pascalstring" @@ -648,12 +647,167 @@ func handleMsgMhfTransferItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumeratePrice(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumeratePrice) - //resp := byteframe.NewByteFrame() - //resp.WriteUint16(0) // Entry type 1 count - //resp.WriteUint16(0) // Entry type 2 count - // directly lifted for now because lacking it crashes the counter on having actual events present - data, _ := hex.DecodeString("0000000066000003E800000000007300640100000320000000000006006401000003200000000000300064010000044C00000000007200640100000384000000000034006401000003840000000000140064010000051400000000006E006401000003E8000000000016006401000003E8000000000001006401000003200000000000430064010000057800000000006F006401000003840000000000330064010000044C00000000000B006401000003E800000000000F006401000006400000000000700064010000044C0000000000110064010000057800000000004C006401000003E8000000000059006401000006A400000000006D006401000005DC00000000004B006401000005DC000000000050006401000006400000000000350064010000070800000000006C0064010000044C000000000028006401000005DC00000000005300640100000640000000000060006401000005DC00000000005E0064010000051400000000007B006401000003E80000000000740064010000070800000000006B0064010000025800000000001B0064010000025800000000001C006401000002BC00000000001F006401000006A400000000007900640100000320000000000008006401000003E80000000000150064010000070800000000007A0064010000044C00000000000E00640100000640000000000055006401000007D0000000000002006401000005DC00000000002F0064010000064000000000002A0064010000076C00000000007E006401000002BC0000000000440064010000038400000000005C0064010000064000000000005B006401000006A400000000007D0064010000076C00000000007F006401000005DC0000000000540064010000064000000000002900640100000960000000000024006401000007D0000000000081006401000008340000000000800064010000038400000000001A006401000003E800000000002D0064010000038400000000004A006401000006A400000000005A00640100000384000000000027006401000007080000000000830064010000076C000000000040006401000006400000000000690064010000044C000000000025006401000004B000000000003100640100000708000000000082006401000003E800000000006500640100000640000000000051006401000007D000000000008C0064010000070800000000004D0064010000038400000000004E0064010000089800000000008B006401000004B000000000002E006401000009600000000000920064010000076C00000000008E00640100000514000000000068006401000004B000000000002B006401000003E800000000002C00640100000BB8000000000093006401000008FC00000000009000640100000AF0000000000094006401000006A400000000008D0064010000044C000000000052006401000005DC00000000004F006401000008980000000000970064010000070800000000006A0064010000064000000000005F00640100000384000000000026006401000008FC000000000096006401000007D00000000000980064010000076C000000000041006401000006A400000000003B006401000007080000000000360064010000083400000000009F00640100000A2800000000009A0064010000076C000000000021006401000007D000000000006300640100000A8C0000000000990064010000089800000000009E006401000007080000000000A100640100000C1C0000000000A200640100000C800000000000A400640100000DAC0000000000A600640100000C800000000000A50064010010") - doAckBufSucceed(s, pkt.AckHandle, data) + bf := byteframe.NewByteFrame() + var lbPrices []struct { + Unk0 uint16 + Unk1 uint16 + Unk2 uint32 + } + var wantedList []struct { + Unk0 uint32 + Unk1 uint32 + Unk2 uint32 + Unk3 uint16 + Unk4 uint16 + Unk5 uint16 + Unk6 uint16 + Unk7 uint16 + Unk8 uint16 + Unk9 uint16 + } + gzPrices := []struct { + Unk0 uint16 + Gz uint16 + Unk1 uint16 + Unk2 uint16 + MonID uint16 + Unk3 uint16 + Unk4 uint8 + }{ + {0, 1000, 0, 0, 115, 100, 1}, + {0, 800, 0, 0, 6, 100, 1}, + {0, 800, 0, 0, 48, 100, 1}, + {0, 1100, 0, 0, 114, 100, 1}, + {0, 900, 0, 0, 52, 100, 1}, + {0, 900, 0, 0, 20, 100, 1}, + {0, 1300, 0, 0, 110, 100, 1}, + {0, 1000, 0, 0, 22, 100, 1}, + {0, 1000, 0, 0, 1, 100, 1}, + {0, 800, 0, 0, 67, 100, 1}, + {0, 1400, 0, 0, 111, 100, 1}, + {0, 900, 0, 0, 51, 100, 1}, + {0, 1100, 0, 0, 11, 100, 1}, + {0, 1000, 0, 0, 15, 100, 1}, + {0, 1600, 0, 0, 112, 100, 1}, + {0, 1100, 0, 0, 17, 100, 1}, + {0, 1400, 0, 0, 76, 100, 1}, + {0, 1000, 0, 0, 89, 100, 1}, + {0, 1700, 0, 0, 109, 100, 1}, + {0, 1500, 0, 0, 75, 100, 1}, + {0, 1500, 0, 0, 80, 100, 1}, + {0, 1600, 0, 0, 53, 100, 1}, + {0, 1800, 0, 0, 108, 100, 1}, + {0, 1100, 0, 0, 40, 100, 1}, + {0, 1500, 0, 0, 83, 100, 1}, + {0, 1600, 0, 0, 96, 100, 1}, + {0, 1500, 0, 0, 94, 100, 1}, + {0, 1300, 0, 0, 123, 100, 1}, + {0, 1000, 0, 0, 116, 100, 1}, + {0, 1800, 0, 0, 107, 100, 1}, + {0, 600, 0, 0, 27, 100, 1}, + {0, 600, 0, 0, 28, 100, 1}, + {0, 700, 0, 0, 31, 100, 1}, + {0, 1700, 0, 0, 121, 100, 1}, + {0, 800, 0, 0, 8, 100, 1}, + {0, 1000, 0, 0, 21, 100, 1}, + {0, 1800, 0, 0, 122, 100, 1}, + {0, 1100, 0, 0, 14, 100, 1}, + {0, 1600, 0, 0, 85, 100, 1}, + {0, 2000, 0, 0, 2, 100, 1}, + {0, 1500, 0, 0, 47, 100, 1}, + {0, 1600, 0, 0, 42, 100, 1}, + {0, 1900, 0, 0, 126, 100, 1}, + {0, 700, 0, 0, 68, 100, 1}, + {0, 900, 0, 0, 92, 100, 1}, + {0, 1600, 0, 0, 91, 100, 1}, + {0, 1700, 0, 0, 125, 100, 1}, + {0, 1900, 0, 0, 127, 100, 1}, + {0, 1500, 0, 0, 84, 100, 1}, + {0, 1600, 0, 0, 41, 100, 1}, + {0, 2400, 0, 0, 36, 100, 1}, + {0, 2000, 0, 0, 129, 100, 1}, + {0, 2100, 0, 0, 128, 100, 1}, + {0, 900, 0, 0, 26, 100, 1}, + {0, 1000, 0, 0, 45, 100, 1}, + {0, 900, 0, 0, 74, 100, 1}, + {0, 1700, 0, 0, 90, 100, 1}, + {0, 900, 0, 0, 39, 100, 1}, + {0, 1800, 0, 0, 131, 100, 1}, + {0, 1900, 0, 0, 64, 100, 1}, + {0, 1600, 0, 0, 105, 100, 1}, + {0, 1100, 0, 0, 37, 100, 1}, + {0, 1200, 0, 0, 49, 100, 1}, + {0, 1800, 0, 0, 130, 100, 1}, + {0, 1000, 0, 0, 101, 100, 1}, + {0, 1600, 0, 0, 81, 100, 1}, + {0, 2000, 0, 0, 140, 100, 1}, + {0, 1800, 0, 0, 77, 100, 1}, + {0, 900, 0, 0, 78, 100, 1}, + {0, 2200, 0, 0, 139, 100, 1}, + {0, 1200, 0, 0, 46, 100, 1}, + {0, 2400, 0, 0, 146, 100, 1}, + {0, 1900, 0, 0, 142, 100, 1}, + {0, 1300, 0, 0, 104, 100, 1}, + {0, 1200, 0, 0, 43, 100, 1}, + {0, 1000, 0, 0, 44, 100, 1}, + {0, 3000, 0, 0, 147, 100, 1}, + {0, 2300, 0, 0, 144, 100, 1}, + {0, 2800, 0, 0, 148, 100, 1}, + {0, 1700, 0, 0, 141, 100, 1}, + {0, 1100, 0, 0, 82, 100, 1}, + {0, 1500, 0, 0, 79, 100, 1}, + {0, 2200, 0, 0, 151, 100, 1}, + {0, 1800, 0, 0, 106, 100, 1}, + {0, 1600, 0, 0, 95, 100, 1}, + {0, 900, 0, 0, 38, 100, 1}, + {0, 2300, 0, 0, 150, 100, 1}, + {0, 2000, 0, 0, 152, 100, 1}, + {0, 1900, 0, 0, 65, 100, 1}, + {0, 1700, 0, 0, 59, 100, 1}, + {0, 1800, 0, 0, 54, 100, 1}, + {0, 2100, 0, 0, 159, 100, 1}, + {0, 2600, 0, 0, 154, 100, 1}, + {0, 1900, 0, 0, 33, 100, 1}, + {0, 2000, 0, 0, 99, 100, 1}, + {0, 2700, 0, 0, 153, 100, 1}, + {0, 2200, 0, 0, 158, 100, 1}, + {0, 1800, 0, 0, 161, 100, 1}, + {0, 3100, 0, 0, 162, 100, 1}, + {0, 3200, 0, 0, 164, 100, 1}, + {0, 3500, 0, 0, 166, 100, 1}, + {0, 3200, 0, 0, 165, 100, 1}, + } + + bf.WriteUint16(uint16(len(lbPrices))) + for _, lb := range lbPrices { + bf.WriteUint16(lb.Unk0) + bf.WriteUint16(lb.Unk1) + bf.WriteUint32(lb.Unk2) + } + bf.WriteUint16(uint16(len(wantedList))) + for _, wanted := range wantedList { + bf.WriteUint32(wanted.Unk0) + bf.WriteUint32(wanted.Unk1) + bf.WriteUint32(wanted.Unk2) + bf.WriteUint16(wanted.Unk3) + bf.WriteUint16(wanted.Unk4) + bf.WriteUint16(wanted.Unk5) + bf.WriteUint16(wanted.Unk6) + bf.WriteUint16(wanted.Unk7) + bf.WriteUint16(wanted.Unk8) + bf.WriteUint16(wanted.Unk9) + } + bf.WriteUint8(uint8(len(gzPrices))) + for _, gz := range gzPrices { + bf.WriteUint16(gz.Unk0) + bf.WriteUint16(gz.Gz) + bf.WriteUint16(gz.Unk1) + bf.WriteUint16(gz.Unk2) + bf.WriteUint16(gz.MonID) + bf.WriteUint16(gz.Unk3) + bf.WriteUint8(gz.Unk4) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfEnumerateOrder(s *Session, p mhfpacket.MHFPacket) { From 8a485f3120f55897fe5795d84199b68cab057be2 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 3 Dec 2023 19:35:24 +1100 Subject: [PATCH 200/269] convert EnumeratePrice to use mhfmon enums --- server/channelserver/handlers.go | 204 +++++++++++++++---------------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 60bf06ea7..bae634bed 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -674,108 +674,108 @@ func handleMsgMhfEnumeratePrice(s *Session, p mhfpacket.MHFPacket) { Unk3 uint16 Unk4 uint8 }{ - {0, 1000, 0, 0, 115, 100, 1}, - {0, 800, 0, 0, 6, 100, 1}, - {0, 800, 0, 0, 48, 100, 1}, - {0, 1100, 0, 0, 114, 100, 1}, - {0, 900, 0, 0, 52, 100, 1}, - {0, 900, 0, 0, 20, 100, 1}, - {0, 1300, 0, 0, 110, 100, 1}, - {0, 1000, 0, 0, 22, 100, 1}, - {0, 1000, 0, 0, 1, 100, 1}, - {0, 800, 0, 0, 67, 100, 1}, - {0, 1400, 0, 0, 111, 100, 1}, - {0, 900, 0, 0, 51, 100, 1}, - {0, 1100, 0, 0, 11, 100, 1}, - {0, 1000, 0, 0, 15, 100, 1}, - {0, 1600, 0, 0, 112, 100, 1}, - {0, 1100, 0, 0, 17, 100, 1}, - {0, 1400, 0, 0, 76, 100, 1}, - {0, 1000, 0, 0, 89, 100, 1}, - {0, 1700, 0, 0, 109, 100, 1}, - {0, 1500, 0, 0, 75, 100, 1}, - {0, 1500, 0, 0, 80, 100, 1}, - {0, 1600, 0, 0, 53, 100, 1}, - {0, 1800, 0, 0, 108, 100, 1}, - {0, 1100, 0, 0, 40, 100, 1}, - {0, 1500, 0, 0, 83, 100, 1}, - {0, 1600, 0, 0, 96, 100, 1}, - {0, 1500, 0, 0, 94, 100, 1}, - {0, 1300, 0, 0, 123, 100, 1}, - {0, 1000, 0, 0, 116, 100, 1}, - {0, 1800, 0, 0, 107, 100, 1}, - {0, 600, 0, 0, 27, 100, 1}, - {0, 600, 0, 0, 28, 100, 1}, - {0, 700, 0, 0, 31, 100, 1}, - {0, 1700, 0, 0, 121, 100, 1}, - {0, 800, 0, 0, 8, 100, 1}, - {0, 1000, 0, 0, 21, 100, 1}, - {0, 1800, 0, 0, 122, 100, 1}, - {0, 1100, 0, 0, 14, 100, 1}, - {0, 1600, 0, 0, 85, 100, 1}, - {0, 2000, 0, 0, 2, 100, 1}, - {0, 1500, 0, 0, 47, 100, 1}, - {0, 1600, 0, 0, 42, 100, 1}, - {0, 1900, 0, 0, 126, 100, 1}, - {0, 700, 0, 0, 68, 100, 1}, - {0, 900, 0, 0, 92, 100, 1}, - {0, 1600, 0, 0, 91, 100, 1}, - {0, 1700, 0, 0, 125, 100, 1}, - {0, 1900, 0, 0, 127, 100, 1}, - {0, 1500, 0, 0, 84, 100, 1}, - {0, 1600, 0, 0, 41, 100, 1}, - {0, 2400, 0, 0, 36, 100, 1}, - {0, 2000, 0, 0, 129, 100, 1}, - {0, 2100, 0, 0, 128, 100, 1}, - {0, 900, 0, 0, 26, 100, 1}, - {0, 1000, 0, 0, 45, 100, 1}, - {0, 900, 0, 0, 74, 100, 1}, - {0, 1700, 0, 0, 90, 100, 1}, - {0, 900, 0, 0, 39, 100, 1}, - {0, 1800, 0, 0, 131, 100, 1}, - {0, 1900, 0, 0, 64, 100, 1}, - {0, 1600, 0, 0, 105, 100, 1}, - {0, 1100, 0, 0, 37, 100, 1}, - {0, 1200, 0, 0, 49, 100, 1}, - {0, 1800, 0, 0, 130, 100, 1}, - {0, 1000, 0, 0, 101, 100, 1}, - {0, 1600, 0, 0, 81, 100, 1}, - {0, 2000, 0, 0, 140, 100, 1}, - {0, 1800, 0, 0, 77, 100, 1}, - {0, 900, 0, 0, 78, 100, 1}, - {0, 2200, 0, 0, 139, 100, 1}, - {0, 1200, 0, 0, 46, 100, 1}, - {0, 2400, 0, 0, 146, 100, 1}, - {0, 1900, 0, 0, 142, 100, 1}, - {0, 1300, 0, 0, 104, 100, 1}, - {0, 1200, 0, 0, 43, 100, 1}, - {0, 1000, 0, 0, 44, 100, 1}, - {0, 3000, 0, 0, 147, 100, 1}, - {0, 2300, 0, 0, 144, 100, 1}, - {0, 2800, 0, 0, 148, 100, 1}, - {0, 1700, 0, 0, 141, 100, 1}, - {0, 1100, 0, 0, 82, 100, 1}, - {0, 1500, 0, 0, 79, 100, 1}, - {0, 2200, 0, 0, 151, 100, 1}, - {0, 1800, 0, 0, 106, 100, 1}, - {0, 1600, 0, 0, 95, 100, 1}, - {0, 900, 0, 0, 38, 100, 1}, - {0, 2300, 0, 0, 150, 100, 1}, - {0, 2000, 0, 0, 152, 100, 1}, - {0, 1900, 0, 0, 65, 100, 1}, - {0, 1700, 0, 0, 59, 100, 1}, - {0, 1800, 0, 0, 54, 100, 1}, - {0, 2100, 0, 0, 159, 100, 1}, - {0, 2600, 0, 0, 154, 100, 1}, - {0, 1900, 0, 0, 33, 100, 1}, - {0, 2000, 0, 0, 99, 100, 1}, - {0, 2700, 0, 0, 153, 100, 1}, - {0, 2200, 0, 0, 158, 100, 1}, - {0, 1800, 0, 0, 161, 100, 1}, - {0, 3100, 0, 0, 162, 100, 1}, - {0, 3200, 0, 0, 164, 100, 1}, - {0, 3500, 0, 0, 166, 100, 1}, - {0, 3200, 0, 0, 165, 100, 1}, + {0, 1000, 0, 0, mhfmon.Pokaradon, 100, 1}, + {0, 800, 0, 0, mhfmon.YianKutKu, 100, 1}, + {0, 800, 0, 0, mhfmon.DaimyoHermitaur, 100, 1}, + {0, 1100, 0, 0, mhfmon.Farunokku, 100, 1}, + {0, 900, 0, 0, mhfmon.Congalala, 100, 1}, + {0, 900, 0, 0, mhfmon.Gypceros, 100, 1}, + {0, 1300, 0, 0, mhfmon.Hyujikiki, 100, 1}, + {0, 1000, 0, 0, mhfmon.Basarios, 100, 1}, + {0, 1000, 0, 0, mhfmon.Rathian, 100, 1}, + {0, 800, 0, 0, mhfmon.ShogunCeanataur, 100, 1}, + {0, 1400, 0, 0, mhfmon.Midogaron, 100, 1}, + {0, 900, 0, 0, mhfmon.Blangonga, 100, 1}, + {0, 1100, 0, 0, mhfmon.Rathalos, 100, 1}, + {0, 1000, 0, 0, mhfmon.Khezu, 100, 1}, + {0, 1600, 0, 0, mhfmon.Giaorugu, 100, 1}, + {0, 1100, 0, 0, mhfmon.Gravios, 100, 1}, + {0, 1400, 0, 0, mhfmon.Tigrex, 100, 1}, + {0, 1000, 0, 0, mhfmon.Pariapuria, 100, 1}, + {0, 1700, 0, 0, mhfmon.Anorupatisu, 100, 1}, + {0, 1500, 0, 0, mhfmon.Lavasioth, 100, 1}, + {0, 1500, 0, 0, mhfmon.Espinas, 100, 1}, + {0, 1600, 0, 0, mhfmon.Rajang, 100, 1}, + {0, 1800, 0, 0, mhfmon.Rebidiora, 100, 1}, + {0, 1100, 0, 0, mhfmon.YianGaruga, 100, 1}, + {0, 1500, 0, 0, mhfmon.AqraVashimu, 100, 1}, + {0, 1600, 0, 0, mhfmon.Gurenzeburu, 100, 1}, + {0, 1500, 0, 0, mhfmon.Dyuragaua, 100, 1}, + {0, 1300, 0, 0, mhfmon.Gougarf, 100, 1}, + {0, 1000, 0, 0, mhfmon.Shantien, 100, 1}, + {0, 1800, 0, 0, mhfmon.Disufiroa, 100, 1}, + {0, 600, 0, 0, mhfmon.Velocidrome, 100, 1}, + {0, 600, 0, 0, mhfmon.Gendrome, 100, 1}, + {0, 700, 0, 0, mhfmon.Iodrome, 100, 1}, + {0, 1700, 0, 0, mhfmon.Baruragaru, 100, 1}, + {0, 800, 0, 0, mhfmon.Cephadrome, 100, 1}, + {0, 1000, 0, 0, mhfmon.Plesioth, 100, 1}, + {0, 1800, 0, 0, mhfmon.Zerureusu, 100, 1}, + {0, 1100, 0, 0, mhfmon.Diablos, 100, 1}, + {0, 1600, 0, 0, mhfmon.Berukyurosu, 100, 1}, + {0, 2000, 0, 0, mhfmon.Fatalis, 100, 1}, + {0, 1500, 0, 0, mhfmon.BlackGravios, 100, 1}, + {0, 1600, 0, 0, mhfmon.GoldRathian, 100, 1}, + {0, 1900, 0, 0, mhfmon.Meraginasu, 100, 1}, + {0, 700, 0, 0, mhfmon.Bulldrome, 100, 1}, + {0, 900, 0, 0, mhfmon.NonoOrugaron, 100, 1}, + {0, 1600, 0, 0, mhfmon.KamuOrugaron, 100, 1}, + {0, 1700, 0, 0, mhfmon.Forokururu, 100, 1}, + {0, 1900, 0, 0, mhfmon.Diorex, 100, 1}, + {0, 1500, 0, 0, mhfmon.AqraJebia, 100, 1}, + {0, 1600, 0, 0, mhfmon.SilverRathalos, 100, 1}, + {0, 2400, 0, 0, mhfmon.CrimsonFatalis, 100, 1}, + {0, 2000, 0, 0, mhfmon.Inagami, 100, 1}, + {0, 2100, 0, 0, mhfmon.GarubaDaora, 100, 1}, + {0, 900, 0, 0, mhfmon.Monoblos, 100, 1}, + {0, 1000, 0, 0, mhfmon.RedKhezu, 100, 1}, + {0, 900, 0, 0, mhfmon.Hypnocatrice, 100, 1}, + {0, 1700, 0, 0, mhfmon.WhiteEspinas, 100, 1}, + {0, 900, 0, 0, mhfmon.PurpleGypceros, 100, 1}, + {0, 1800, 0, 0, mhfmon.Poborubarumu, 100, 1}, + {0, 1900, 0, 0, mhfmon.Lunastra, 100, 1}, + {0, 1600, 0, 0, mhfmon.Kuarusepusu, 100, 1}, + {0, 1100, 0, 0, mhfmon.PinkRathian, 100, 1}, + {0, 1200, 0, 0, mhfmon.AzureRathalos, 100, 1}, + {0, 1800, 0, 0, mhfmon.Varusaburosu, 100, 1}, + {0, 1000, 0, 0, mhfmon.Gogomoa, 100, 1}, + {0, 1600, 0, 0, mhfmon.OrangeEspinas, 100, 1}, + {0, 2000, 0, 0, mhfmon.Harudomerugu, 100, 1}, + {0, 1800, 0, 0, mhfmon.Akantor, 100, 1}, + {0, 900, 0, 0, mhfmon.BrightHypnoc, 100, 1}, + {0, 2200, 0, 0, mhfmon.Gureadomosu, 100, 1}, + {0, 1200, 0, 0, mhfmon.GreenPlesioth, 100, 1}, + {0, 2400, 0, 0, mhfmon.Zinogre, 100, 1}, + {0, 1900, 0, 0, mhfmon.Gasurabazura, 100, 1}, + {0, 1300, 0, 0, mhfmon.Abiorugu, 100, 1}, + {0, 1200, 0, 0, mhfmon.BlackDiablos, 100, 1}, + {0, 1000, 0, 0, mhfmon.WhiteMonoblos, 100, 1}, + {0, 3000, 0, 0, mhfmon.Deviljho, 100, 1}, + {0, 2300, 0, 0, mhfmon.YamaKurai, 100, 1}, + {0, 2800, 0, 0, mhfmon.Brachydios, 100, 1}, + {0, 1700, 0, 0, mhfmon.Toridcless, 100, 1}, + {0, 1100, 0, 0, mhfmon.WhiteHypnoc, 100, 1}, + {0, 1500, 0, 0, mhfmon.RedLavasioth, 100, 1}, + {0, 2200, 0, 0, mhfmon.Barioth, 100, 1}, + {0, 1800, 0, 0, mhfmon.Odibatorasu, 100, 1}, + {0, 1600, 0, 0, mhfmon.Doragyurosu, 100, 1}, + {0, 900, 0, 0, mhfmon.BlueYianKutKu, 100, 1}, + {0, 2300, 0, 0, mhfmon.ToaTesukatora, 100, 1}, + {0, 2000, 0, 0, mhfmon.Uragaan, 100, 1}, + {0, 1900, 0, 0, mhfmon.Teostra, 100, 1}, + {0, 1700, 0, 0, mhfmon.Chameleos, 100, 1}, + {0, 1800, 0, 0, mhfmon.KushalaDaora, 100, 1}, + {0, 2100, 0, 0, mhfmon.Nargacuga, 100, 1}, + {0, 2600, 0, 0, mhfmon.Guanzorumu, 100, 1}, + {0, 1900, 0, 0, mhfmon.Kirin, 100, 1}, + {0, 2000, 0, 0, mhfmon.Rukodiora, 100, 1}, + {0, 2700, 0, 0, mhfmon.StygianZinogre, 100, 1}, + {0, 2200, 0, 0, mhfmon.Voljang, 100, 1}, + {0, 1800, 0, 0, mhfmon.Zenaserisu, 100, 1}, + {0, 3100, 0, 0, mhfmon.GoreMagala, 100, 1}, + {0, 3200, 0, 0, mhfmon.ShagaruMagala, 100, 1}, + {0, 3500, 0, 0, mhfmon.Eruzerion, 100, 1}, + {0, 3200, 0, 0, mhfmon.Amatsu, 100, 1}, } bf.WriteUint16(uint16(len(lbPrices))) From b6e7f019de4c306793b04af171e41d5fc7a61a37 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 3 Dec 2023 19:56:18 +1100 Subject: [PATCH 201/269] update mhfmon names --- common/mhfmon/mhfmon.go | 8 ++++---- server/channelserver/handlers.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/mhfmon/mhfmon.go b/common/mhfmon/mhfmon.go index 183a75bd8..e192844fe 100644 --- a/common/mhfmon/mhfmon.go +++ b/common/mhfmon/mhfmon.go @@ -82,7 +82,7 @@ const ( BrightHypnoc RedLavasioth Espinas - OrangeEspinas + BurningEspinas WhiteHypnoc AqraVashimu AqraJebia @@ -91,7 +91,7 @@ const ( Mon87 Mon88 Pariapuria - WhiteEspinas + PearlEspinas KamuOrugaron NonoOrugaron Raviente @@ -267,7 +267,7 @@ var Monsters = []Monster{ {"Bright Hypnocatrice", true}, {"Red Lavasioth", true}, {"Espinas", true}, - {"Orange Espinas", true}, + {"Burning Espinas", true}, {"White Hypnocatrice", true}, {"Aqra Vashimu", true}, {"Aqra Jebia", true}, @@ -276,7 +276,7 @@ var Monsters = []Monster{ {"Mon87", false}, {"Mon88", false}, {"Pariapuria", true}, - {"White Espinas", true}, + {"Pearl Espinas", true}, {"Kamu Orugaron", true}, {"Nono Orugaron", true}, {"Raviente", true}, // + Violent diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index bae634bed..16d5fc2fc 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -730,7 +730,7 @@ func handleMsgMhfEnumeratePrice(s *Session, p mhfpacket.MHFPacket) { {0, 900, 0, 0, mhfmon.Monoblos, 100, 1}, {0, 1000, 0, 0, mhfmon.RedKhezu, 100, 1}, {0, 900, 0, 0, mhfmon.Hypnocatrice, 100, 1}, - {0, 1700, 0, 0, mhfmon.WhiteEspinas, 100, 1}, + {0, 1700, 0, 0, mhfmon.PearlEspinas, 100, 1}, {0, 900, 0, 0, mhfmon.PurpleGypceros, 100, 1}, {0, 1800, 0, 0, mhfmon.Poborubarumu, 100, 1}, {0, 1900, 0, 0, mhfmon.Lunastra, 100, 1}, @@ -739,7 +739,7 @@ func handleMsgMhfEnumeratePrice(s *Session, p mhfpacket.MHFPacket) { {0, 1200, 0, 0, mhfmon.AzureRathalos, 100, 1}, {0, 1800, 0, 0, mhfmon.Varusaburosu, 100, 1}, {0, 1000, 0, 0, mhfmon.Gogomoa, 100, 1}, - {0, 1600, 0, 0, mhfmon.OrangeEspinas, 100, 1}, + {0, 1600, 0, 0, mhfmon.BurningEspinas, 100, 1}, {0, 2000, 0, 0, mhfmon.Harudomerugu, 100, 1}, {0, 1800, 0, 0, mhfmon.Akantor, 100, 1}, {0, 900, 0, 0, mhfmon.BrightHypnoc, 100, 1}, From 4844acee9c96515212aba5cfba3bc7f14b2b6a91 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 4 Dec 2023 20:10:14 +1100 Subject: [PATCH 202/269] fix UpdateEtcPoint logic --- server/channelserver/handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 16d5fc2fc..193ccdd9f 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -1100,10 +1100,10 @@ func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) { column = "promo_points" } - var value int + var value int16 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 { + if value+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) From 7717f2f12a5c7301d5a11480d47dd668eafc3387 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 4 Dec 2023 22:06:43 +1100 Subject: [PATCH 203/269] fix Enumerate/UpdateGuacot --- network/mhfpacket/msg_mhf_update_guacot.go | 10 +---- patch-schema/15-reset-goocoos.sql | 5 +++ server/channelserver/handlers.go | 46 ++++++++-------------- server/channelserver/handlers_house.go | 8 ++-- 4 files changed, 29 insertions(+), 40 deletions(-) create mode 100644 patch-schema/15-reset-goocoos.sql diff --git a/network/mhfpacket/msg_mhf_update_guacot.go b/network/mhfpacket/msg_mhf_update_guacot.go index 685f66f1b..3295d1251 100644 --- a/network/mhfpacket/msg_mhf_update_guacot.go +++ b/network/mhfpacket/msg_mhf_update_guacot.go @@ -9,8 +9,7 @@ import ( type Goocoo struct { Index uint32 - Data1 []uint16 - Data2 []uint32 + Data []byte Name []byte } @@ -34,12 +33,7 @@ func (m *MsgMhfUpdateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien var temp Goocoo for i := 0; i < int(m.EntryCount); i++ { temp.Index = bf.ReadUint32() - for j := 0; j < 22; j++ { - temp.Data1 = append(temp.Data1, bf.ReadUint16()) - } - for j := 0; j < 2; j++ { - temp.Data2 = append(temp.Data2, bf.ReadUint32()) - } + temp.Data = bf.ReadBytes(52) temp.Name = bf.ReadBytes(uint(bf.ReadUint8())) m.Goocoos = append(m.Goocoos, temp) } diff --git a/patch-schema/15-reset-goocoos.sql b/patch-schema/15-reset-goocoos.sql new file mode 100644 index 000000000..ca4d3fa11 --- /dev/null +++ b/patch-schema/15-reset-goocoos.sql @@ -0,0 +1,5 @@ +BEGIN; + +UPDATE goocoo SET goocoo0=NULL, goocoo1=NULL, goocoo2=NULL, goocoo3=NULL, goocoo4=NULL; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 193ccdd9f..fe26d0d6b 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -959,56 +959,44 @@ func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } -func getGookData(s *Session, cid uint32) (uint16, []byte) { - var data []byte - var count uint16 - bf := byteframe.NewByteFrame() +func getGoocooData(s *Session, cid uint32) [][]byte { + var goocoo []byte + var goocoos [][]byte for i := 0; i < 5; i++ { - err := s.server.db.QueryRow(fmt.Sprintf("SELECT goocoo%d FROM goocoo WHERE id=$1", i), cid).Scan(&data) + err := s.server.db.QueryRow(fmt.Sprintf("SELECT goocoo%d FROM goocoo WHERE id=$1", i), cid).Scan(&goocoo) if err != nil { s.server.db.Exec("INSERT INTO goocoo (id) VALUES ($1)", s.charID) - return 0, bf.Data() + return goocoos } - if err == nil && data != nil { - count++ - if s.charID == cid && count == 1 { - goocoo := byteframe.NewByteFrameFromBytes(data) - bf.WriteBytes(goocoo.ReadBytes(4)) - d := goocoo.ReadBytes(2) - bf.WriteBytes(d) - bf.WriteBytes(d) - bf.WriteBytes(goocoo.DataFromCurrent()) - } else { - bf.WriteBytes(data) - } + if err == nil && goocoo != nil { + goocoos = append(goocoos, goocoo) } } - return count, bf.Data() + return goocoos } func handleMsgMhfEnumerateGuacot(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateGuacot) bf := byteframe.NewByteFrame() - count, data := getGookData(s, s.charID) - bf.WriteUint16(count) - bf.WriteBytes(data) + goocoos := getGoocooData(s, s.charID) + bf.WriteUint16(uint16(len(goocoos))) + bf.WriteUint16(0) + for _, goocoo := range goocoos { + bf.WriteBytes(goocoo) + } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateGuacot) for _, goocoo := range pkt.Goocoos { - if goocoo.Data1[0] == 0 { + // TODO: This doesn't seem to indicate deletion? + if goocoo.Data[0] == 0 && goocoo.Data[1] == 0 { s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=NULL WHERE id=$1", goocoo.Index), s.charID) } else { bf := byteframe.NewByteFrame() bf.WriteUint32(goocoo.Index) - for i := range goocoo.Data1 { - bf.WriteUint16(goocoo.Data1[i]) - } - for i := range goocoo.Data2 { - bf.WriteUint32(goocoo.Data2[i]) - } + bf.WriteBytes(goocoo.Data) bf.WriteUint8(uint8(len(goocoo.Name))) bf.WriteBytes(goocoo.Name) s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=$1 WHERE id=$2", goocoo.Index), bf.Data(), s.charID) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 62561c96d..3f6188750 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -215,10 +215,12 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) { bf.WriteBytes(houseFurniture) case 10: // Garden bf.WriteBytes(garden) - c, d := getGookData(s, pkt.CharID) - bf.WriteUint16(c) + goocoos := getGoocooData(s, pkt.CharID) + bf.WriteUint16(uint16(len(goocoos))) bf.WriteUint16(0) - bf.WriteBytes(d) + for _, goocoo := range goocoos { + bf.WriteBytes(goocoo) + } } if len(bf.Data()) == 0 { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) From ef7d46ba2e53d58b27b7a549893b5776779bc93e Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 4 Dec 2023 22:29:58 +1100 Subject: [PATCH 204/269] revert UpdateGuacot parsing --- network/mhfpacket/msg_mhf_update_guacot.go | 10 ++++++++-- server/channelserver/handlers.go | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/network/mhfpacket/msg_mhf_update_guacot.go b/network/mhfpacket/msg_mhf_update_guacot.go index 3295d1251..729c84547 100644 --- a/network/mhfpacket/msg_mhf_update_guacot.go +++ b/network/mhfpacket/msg_mhf_update_guacot.go @@ -9,7 +9,8 @@ import ( type Goocoo struct { Index uint32 - Data []byte + Data1 []int16 + Data2 []uint32 Name []byte } @@ -33,7 +34,12 @@ func (m *MsgMhfUpdateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien var temp Goocoo for i := 0; i < int(m.EntryCount); i++ { temp.Index = bf.ReadUint32() - temp.Data = bf.ReadBytes(52) + for j := 0; j < 22; j++ { + temp.Data1 = append(temp.Data1, bf.ReadInt16()) + } + for j := 0; j < 2; j++ { + temp.Data2 = append(temp.Data2, bf.ReadUint32()) + } temp.Name = bf.ReadBytes(uint(bf.ReadUint8())) m.Goocoos = append(m.Goocoos, temp) } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index fe26d0d6b..441c4a307 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -991,12 +991,17 @@ func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateGuacot) for _, goocoo := range pkt.Goocoos { // TODO: This doesn't seem to indicate deletion? - if goocoo.Data[0] == 0 && goocoo.Data[1] == 0 { + if goocoo.Data1[0] == 0 { s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=NULL WHERE id=$1", goocoo.Index), s.charID) } else { bf := byteframe.NewByteFrame() bf.WriteUint32(goocoo.Index) - bf.WriteBytes(goocoo.Data) + for i := range goocoo.Data1 { + bf.WriteInt16(goocoo.Data1[i]) + } + for i := range goocoo.Data2 { + bf.WriteUint32(goocoo.Data2[i]) + } bf.WriteUint8(uint8(len(goocoo.Name))) bf.WriteBytes(goocoo.Name) s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=$1 WHERE id=$2", goocoo.Index), bf.Data(), s.charID) From 26438306c613308840f686f465e900cd04778b79 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 4 Dec 2023 13:48:17 -0500 Subject: [PATCH 205/269] fix: Missing closing bracket --- server/channelserver/handlers_cast_binary.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index b0646fcce..5f548feab 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -344,6 +344,7 @@ func parseChatCommand(s *Session, command string) { sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], discordToken)) } else { sendDisabledCommandMessage(s, commands["Discord"]) + } case commands["Help"].Prefix: if commands["Help"].Enabled { for _, command := range commands { From a7ac2c0672a9bf23f55c229646cf93e3a1c6b6a2 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 5 Dec 2023 23:48:47 +1100 Subject: [PATCH 206/269] remove comment --- server/channelserver/handlers.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 441c4a307..76d085175 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -990,7 +990,6 @@ func handleMsgMhfEnumerateGuacot(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateGuacot) for _, goocoo := range pkt.Goocoos { - // TODO: This doesn't seem to indicate deletion? if goocoo.Data1[0] == 0 { s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=NULL WHERE id=$1", goocoo.Index), s.charID) } else { From 6686d5914649cc6e0b9cfed270ce6f5ab9d2278e Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 8 Dec 2023 10:47:34 +1100 Subject: [PATCH 207/269] fix UdSchedule debug response --- server/channelserver/handlers_diva.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_diva.go b/server/channelserver/handlers_diva.go index 2c60e9767..42b70f064 100644 --- a/server/channelserver/handlers_diva.go +++ b/server/channelserver/handlers_diva.go @@ -73,9 +73,9 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) { if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 { if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 { if s.server.erupeConfig.RealClientMode >= _config.Z2 { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32)) - } else { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36)) + } else { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32)) } return } From b8f431ae66f17a8b080d51bd5c034fd2210a385c Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 17 Dec 2023 17:47:35 +1100 Subject: [PATCH 208/269] parse CapLink responses --- config.json | 6 ++++++ config/config.go | 8 ++++++++ server/signserver/dsgn_resp.go | 34 ++++++++++++++++++++++++++-------- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/config.json b/config.json index 5d66b9e33..1d5fe76d7 100644 --- a/config.json +++ b/config.json @@ -36,6 +36,12 @@ "Enabled": true, "RawEnabled": false, "OutputDir": "save-backups" + }, + "CapLink": { + "Values": [51728, 20000, 51729, 1, 20000], + "Key": "", + "Host": "", + "Port": 80 } }, "GameplayOptions": { diff --git a/config/config.go b/config/config.go index 153cfedb3..4995c042d 100644 --- a/config/config.go +++ b/config/config.go @@ -114,6 +114,7 @@ type DevModeOptions struct { EarthIDOverride int32 EarthMonsterOverride []int32 SaveDumps SaveDumpOptions + CapLink CapLinkOptions } type SaveDumpOptions struct { @@ -122,6 +123,13 @@ type SaveDumpOptions struct { OutputDir string } +type CapLinkOptions struct { + Values []uint16 + Key string + Host string + Port int +} + // GameplayOptions has various gameplay modifiers type GameplayOptions struct { FeaturedWeapons int // Number of Active Feature weapons to generate daily diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 34cc371e2..25b5650c4 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -144,14 +144,32 @@ func (s *Session) makeSignResponse(uid uint32) []byte { s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser) bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true)) } - bf.WriteUint16(0xCA10) - bf.WriteUint16(0x4E20) - ps.Uint16(bf, "", false) // unk key - bf.WriteUint8(0x00) - bf.WriteUint16(0xCA11) - bf.WriteUint16(0x0001) - bf.WriteUint16(0x4E20) - ps.Uint16(bf, "", false) // unk ipv4 + + bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[0]) + if s.server.erupeConfig.DevModeOptions.CapLink.Values[0] == 51728 { + bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[1]) + if s.server.erupeConfig.DevModeOptions.CapLink.Values[1] == 20000 || s.server.erupeConfig.DevModeOptions.CapLink.Values[1] == 20002 { + ps.Uint16(bf, s.server.erupeConfig.DevModeOptions.CapLink.Key, false) + } + } + caStruct := []struct { + Unk0 uint8 + Unk1 uint32 + Unk2 string + }{} + bf.WriteUint8(uint8(len(caStruct))) + for i := range caStruct { + bf.WriteUint8(caStruct[i].Unk0) + bf.WriteUint32(caStruct[i].Unk1) + ps.Uint8(bf, caStruct[i].Unk2, false) + } + bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[2]) + bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[3]) + bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[4]) + if s.server.erupeConfig.DevModeOptions.CapLink.Values[2] == 51729 && s.server.erupeConfig.DevModeOptions.CapLink.Values[3] == 1 && s.server.erupeConfig.DevModeOptions.CapLink.Values[4] == 20000 { + ps.Uint16(bf, fmt.Sprintf(`%s:%d`, s.server.erupeConfig.DevModeOptions.CapLink.Host, s.server.erupeConfig.DevModeOptions.CapLink.Port), false) + } + bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix())) bf.WriteUint32(0) From c71557851960b7e94908733036e9f7dc33fc046a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 23:59:56 +0000 Subject: [PATCH 209/269] Bump golang.org/x/crypto from 0.15.0 to 0.17.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 410eb88b0..5047c6609 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/lib/pq v1.10.9 github.com/spf13/viper v1.17.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.15.0 + golang.org/x/crypto v0.17.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/text v0.14.0 ) @@ -32,7 +32,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.18.0 // indirect - golang.org/x/sys v0.14.0 // indirect + golang.org/x/sys v0.15.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1c6fbeb53..d2cc39776 100644 --- a/go.sum +++ b/go.sum @@ -220,8 +220,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -345,8 +345,8 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 293122b9a17d29184e244b2cdfa788801fd45a68 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 20 Dec 2023 13:47:08 -0800 Subject: [PATCH 210/269] rm: Unintentional file inclusion --- bin/quests/desktop.ini | 2372 ---------------------------------------- 1 file changed, 2372 deletions(-) delete mode 100644 bin/quests/desktop.ini diff --git a/bin/quests/desktop.ini b/bin/quests/desktop.ini deleted file mode 100644 index a71744976..000000000 --- a/bin/quests/desktop.ini +++ /dev/null @@ -1,2372 +0,0 @@ -[LocalizedFileNames] -23484d1.bin=@23484d1.bin,0 -23484d2.bin=@23484d2.bin,0 -23484n0.bin=@23484n0.bin,0 -23484n1.bin=@23484n1.bin,0 -23484n2.bin=@23484n2.bin,0 -23485d0.bin=@23485d0.bin,0 -23485d1.bin=@23485d1.bin,0 -23485d2.bin=@23485d2.bin,0 -23485n0.bin=@23485n0.bin,0 -23485n1.bin=@23485n1.bin,0 -23485n2.bin=@23485n2.bin,0 -23486d0.bin=@23486d0.bin,0 -23486d1.bin=@23486d1.bin,0 -23486d2.bin=@23486d2.bin,0 -23486n0.bin=@23486n0.bin,0 -23486n1.bin=@23486n1.bin,0 -23486n2.bin=@23486n2.bin,0 -23487d0.bin=@23487d0.bin,0 -23487d1.bin=@23487d1.bin,0 -23487d2.bin=@23487d2.bin,0 -23487n0.bin=@23487n0.bin,0 -23487n1.bin=@23487n1.bin,0 -23487n2.bin=@23487n2.bin,0 -23488d0.bin=@23488d0.bin,0 -23488d1.bin=@23488d1.bin,0 -23488d2.bin=@23488d2.bin,0 -23488n0.bin=@23488n0.bin,0 -23488n1.bin=@23488n1.bin,0 -23488n2.bin=@23488n2.bin,0 -23489d0.bin=@23489d0.bin,0 -23489d1.bin=@23489d1.bin,0 -23489d2.bin=@23489d2.bin,0 -23489n0.bin=@23489n0.bin,0 -23489n1.bin=@23489n1.bin,0 -23489n2.bin=@23489n2.bin,0 -23490d0.bin=@23490d0.bin,0 -23490d1.bin=@23490d1.bin,0 -23490d2.bin=@23490d2.bin,0 -23490n0.bin=@23490n0.bin,0 -23490n1.bin=@23490n1.bin,0 -23490n2.bin=@23490n2.bin,0 -23491d0.bin=@23491d0.bin,0 -23491d1.bin=@23491d1.bin,0 -23491d2.bin=@23491d2.bin,0 -23491n0.bin=@23491n0.bin,0 -23491n1.bin=@23491n1.bin,0 -23491n2.bin=@23491n2.bin,0 -23492d0.bin=@23492d0.bin,0 -23492d1.bin=@23492d1.bin,0 -23492d2.bin=@23492d2.bin,0 -23492n0.bin=@23492n0.bin,0 -23492n1.bin=@23492n1.bin,0 -23492n2.bin=@23492n2.bin,0 -23484d0.bin=@23484d0.bin,0 -23585d0.bin=@23585d0.bin,0 -23585d1.bin=@23585d1.bin,0 -23585d2.bin=@23585d2.bin,0 -23585n0.bin=@23585n0.bin,0 -23585n1.bin=@23585n1.bin,0 -23585n2.bin=@23585n2.bin,0 -23586d0.bin=@23586d0.bin,0 -23586d1.bin=@23586d1.bin,0 -23586d2.bin=@23586d2.bin,0 -23586n0.bin=@23586n0.bin,0 -23586n1.bin=@23586n1.bin,0 -23586n2.bin=@23586n2.bin,0 -23587d0.bin=@23587d0.bin,0 -23587d1.bin=@23587d1.bin,0 -23587d2.bin=@23587d2.bin,0 -23587n0.bin=@23587n0.bin,0 -23587n1.bin=@23587n1.bin,0 -23587n2.bin=@23587n2.bin,0 -23588d0.bin=@23588d0.bin,0 -23588d1.bin=@23588d1.bin,0 -23588d2.bin=@23588d2.bin,0 -23588n0.bin=@23588n0.bin,0 -23588n1.bin=@23588n1.bin,0 -23588n2.bin=@23588n2.bin,0 -23589d0.bin=@23589d0.bin,0 -23589d1.bin=@23589d1.bin,0 -23589d2.bin=@23589d2.bin,0 -23589n0.bin=@23589n0.bin,0 -23589n1.bin=@23589n1.bin,0 -23589n2.bin=@23589n2.bin,0 -23590d0.bin=@23590d0.bin,0 -23590d1.bin=@23590d1.bin,0 -23590d2.bin=@23590d2.bin,0 -23590n0.bin=@23590n0.bin,0 -23590n1.bin=@23590n1.bin,0 -23590n2.bin=@23590n2.bin,0 -23591d0.bin=@23591d0.bin,0 -23591d1.bin=@23591d1.bin,0 -23591d2.bin=@23591d2.bin,0 -23591n0.bin=@23591n0.bin,0 -23591n1.bin=@23591n1.bin,0 -23591n2.bin=@23591n2.bin,0 -23592d0.bin=@23592d0.bin,0 -23592d1.bin=@23592d1.bin,0 -23592d2.bin=@23592d2.bin,0 -23592n0.bin=@23592n0.bin,0 -23592n1.bin=@23592n1.bin,0 -23592n2.bin=@23592n2.bin,0 -23593d0.bin=@23593d0.bin,0 -23593d1.bin=@23593d1.bin,0 -23593d2.bin=@23593d2.bin,0 -23593n0.bin=@23593n0.bin,0 -23593n1.bin=@23593n1.bin,0 -23593n2.bin=@23593n2.bin,0 -23594d0.bin=@23594d0.bin,0 -23594d1.bin=@23594d1.bin,0 -23594d2.bin=@23594d2.bin,0 -23594n0.bin=@23594n0.bin,0 -23594n1.bin=@23594n1.bin,0 -23594n2.bin=@23594n2.bin,0 -23595d0.bin=@23595d0.bin,0 -23595d1.bin=@23595d1.bin,0 -23595d2.bin=@23595d2.bin,0 -23595n0.bin=@23595n0.bin,0 -23595n1.bin=@23595n1.bin,0 -23595n2.bin=@23595n2.bin,0 -23596d0.bin=@23596d0.bin,0 -23596d1.bin=@23596d1.bin,0 -23596d2.bin=@23596d2.bin,0 -23596n0.bin=@23596n0.bin,0 -23596n1.bin=@23596n1.bin,0 -23596n2.bin=@23596n2.bin,0 -23597d0.bin=@23597d0.bin,0 -23597d1.bin=@23597d1.bin,0 -23597d2.bin=@23597d2.bin,0 -23597n0.bin=@23597n0.bin,0 -23597n1.bin=@23597n1.bin,0 -23597n2.bin=@23597n2.bin,0 -23598d0.bin=@23598d0.bin,0 -23598d1.bin=@23598d1.bin,0 -23598d2.bin=@23598d2.bin,0 -23598n0.bin=@23598n0.bin,0 -23598n1.bin=@23598n1.bin,0 -23598n2.bin=@23598n2.bin,0 -23599d0.bin=@23599d0.bin,0 -23599d1.bin=@23599d1.bin,0 -23599d2.bin=@23599d2.bin,0 -23599n0.bin=@23599n0.bin,0 -23599n1.bin=@23599n1.bin,0 -23599n2.bin=@23599n2.bin,0 -23601d0.bin=@23601d0.bin,0 -23601d1.bin=@23601d1.bin,0 -23601d2.bin=@23601d2.bin,0 -23601n0.bin=@23601n0.bin,0 -23601n1.bin=@23601n1.bin,0 -23601n2.bin=@23601n2.bin,0 -23602d0.bin=@23602d0.bin,0 -23602d1.bin=@23602d1.bin,0 -23602d2.bin=@23602d2.bin,0 -23602n0.bin=@23602n0.bin,0 -23602n1.bin=@23602n1.bin,0 -23602n2.bin=@23602n2.bin,0 -23603d0.bin=@23603d0.bin,0 -23603d1.bin=@23603d1.bin,0 -23603d2.bin=@23603d2.bin,0 -23603n0.bin=@23603n0.bin,0 -23603n1.bin=@23603n1.bin,0 -23603n2.bin=@23603n2.bin,0 -23604d0.bin=@23604d0.bin,0 -23604d1.bin=@23604d1.bin,0 -23604d2.bin=@23604d2.bin,0 -23604n0.bin=@23604n0.bin,0 -23604n1.bin=@23604n1.bin,0 -23604n2.bin=@23604n2.bin,0 -23605d0.bin=@23605d0.bin,0 -23605d1.bin=@23605d1.bin,0 -23605d2.bin=@23605d2.bin,0 -23605n0.bin=@23605n0.bin,0 -23605n1.bin=@23605n1.bin,0 -23605n2.bin=@23605n2.bin,0 -40241n0.bin=@40241n0.bin,0 -40241n1.bin=@40241n1.bin,0 -40241n2.bin=@40241n2.bin,0 -54594d0.bin=@54594d0.bin,0 -54594d1.bin=@54594d1.bin,0 -54594d2.bin=@54594d2.bin,0 -54594n0.bin=@54594n0.bin,0 -54594n1.bin=@54594n1.bin,0 -54594n2.bin=@54594n2.bin,0 -54603d0.bin=@54603d0.bin,0 -54603d1.bin=@54603d1.bin,0 -54603d2.bin=@54603d2.bin,0 -54603n0.bin=@54603n0.bin,0 -54603n1.bin=@54603n1.bin,0 -54603n2.bin=@54603n2.bin,0 -40241d0.bin=@40241d0.bin,0 -40241d1.bin=@40241d1.bin,0 -40241d2.bin=@40241d2.bin,0 -23493n0.bin=@23493n0.bin,0 -23493n1.bin=@23493n1.bin,0 -23493n2.bin=@23493n2.bin,0 -23494d0.bin=@23494d0.bin,0 -23494d1.bin=@23494d1.bin,0 -23494d2.bin=@23494d2.bin,0 -23494n0.bin=@23494n0.bin,0 -23494n1.bin=@23494n1.bin,0 -23494n2.bin=@23494n2.bin,0 -23495d0.bin=@23495d0.bin,0 -23495d1.bin=@23495d1.bin,0 -23495d2.bin=@23495d2.bin,0 -23495n0.bin=@23495n0.bin,0 -23495n1.bin=@23495n1.bin,0 -23495n2.bin=@23495n2.bin,0 -23496d0.bin=@23496d0.bin,0 -23496d1.bin=@23496d1.bin,0 -23496d2.bin=@23496d2.bin,0 -23496n0.bin=@23496n0.bin,0 -23496n1.bin=@23496n1.bin,0 -23496n2.bin=@23496n2.bin,0 -23497d0.bin=@23497d0.bin,0 -23497d1.bin=@23497d1.bin,0 -23497d2.bin=@23497d2.bin,0 -23497n0.bin=@23497n0.bin,0 -23497n1.bin=@23497n1.bin,0 -23497n2.bin=@23497n2.bin,0 -23498d0.bin=@23498d0.bin,0 -23498d1.bin=@23498d1.bin,0 -23498d2.bin=@23498d2.bin,0 -23498n0.bin=@23498n0.bin,0 -23498n1.bin=@23498n1.bin,0 -23498n2.bin=@23498n2.bin,0 -23499d0.bin=@23499d0.bin,0 -23499d1.bin=@23499d1.bin,0 -23499d2.bin=@23499d2.bin,0 -23499n0.bin=@23499n0.bin,0 -23499n1.bin=@23499n1.bin,0 -23499n2.bin=@23499n2.bin,0 -23513d0.bin=@23513d0.bin,0 -23513d1.bin=@23513d1.bin,0 -23513d2.bin=@23513d2.bin,0 -23513n0.bin=@23513n0.bin,0 -23513n1.bin=@23513n1.bin,0 -23513n2.bin=@23513n2.bin,0 -23528d0.bin=@23528d0.bin,0 -23528d1.bin=@23528d1.bin,0 -23528d2.bin=@23528d2.bin,0 -23528n0.bin=@23528n0.bin,0 -23528n1.bin=@23528n1.bin,0 -23528n2.bin=@23528n2.bin,0 -23643d0.bin=@23643d0.bin,0 -23643d1.bin=@23643d1.bin,0 -23643d2.bin=@23643d2.bin,0 -23643n0.bin=@23643n0.bin,0 -23643n1.bin=@23643n1.bin,0 -23643n2.bin=@23643n2.bin,0 -23667d0.bin=@23667d0.bin,0 -23667d1.bin=@23667d1.bin,0 -23667d2.bin=@23667d2.bin,0 -23667n0.bin=@23667n0.bin,0 -23667n1.bin=@23667n1.bin,0 -23667n2.bin=@23667n2.bin,0 -55679d0.bin=@55679d0.bin,0 -55679d1.bin=@55679d1.bin,0 -55679d2.bin=@55679d2.bin,0 -55679n0.bin=@55679n0.bin,0 -55679n1.bin=@55679n1.bin,0 -55679n2.bin=@55679n2.bin,0 -55680d0.bin=@55680d0.bin,0 -55680d1.bin=@55680d1.bin,0 -55680d2.bin=@55680d2.bin,0 -55680n0.bin=@55680n0.bin,0 -55680n1.bin=@55680n1.bin,0 -55680n2.bin=@55680n2.bin,0 -55772d0.bin=@55772d0.bin,0 -55772d1.bin=@55772d1.bin,0 -55772d2.bin=@55772d2.bin,0 -55772n0.bin=@55772n0.bin,0 -55772n1.bin=@55772n1.bin,0 -55772n2.bin=@55772n2.bin,0 -55916d0.bin=@55916d0.bin,0 -55916d1.bin=@55916d1.bin,0 -55916d2.bin=@55916d2.bin,0 -55916n0.bin=@55916n0.bin,0 -55916n1.bin=@55916n1.bin,0 -55916n2.bin=@55916n2.bin,0 -23493d0.bin=@23493d0.bin,0 -23493d1.bin=@23493d1.bin,0 -23493d2.bin=@23493d2.bin,0 -23199n2.bin=@23199n2.bin,0 -23201d0.bin=@23201d0.bin,0 -23201d1.bin=@23201d1.bin,0 -23201d2.bin=@23201d2.bin,0 -23201n0.bin=@23201n0.bin,0 -23201n1.bin=@23201n1.bin,0 -23201n2.bin=@23201n2.bin,0 -23202d0.bin=@23202d0.bin,0 -23202d1.bin=@23202d1.bin,0 -23202d2.bin=@23202d2.bin,0 -23202n0.bin=@23202n0.bin,0 -23202n1.bin=@23202n1.bin,0 -23202n2.bin=@23202n2.bin,0 -23203d0.bin=@23203d0.bin,0 -23203d1.bin=@23203d1.bin,0 -23203d2.bin=@23203d2.bin,0 -23203n0.bin=@23203n0.bin,0 -23203n1.bin=@23203n1.bin,0 -23203n2.bin=@23203n2.bin,0 -23204d0.bin=@23204d0.bin,0 -23204d1.bin=@23204d1.bin,0 -23204d2.bin=@23204d2.bin,0 -23204n0.bin=@23204n0.bin,0 -23204n1.bin=@23204n1.bin,0 -23204n2.bin=@23204n2.bin,0 -23205d0.bin=@23205d0.bin,0 -23205d1.bin=@23205d1.bin,0 -23205d2.bin=@23205d2.bin,0 -23205n0.bin=@23205n0.bin,0 -23205n1.bin=@23205n1.bin,0 -23205n2.bin=@23205n2.bin,0 -23206d0.bin=@23206d0.bin,0 -23206d1.bin=@23206d1.bin,0 -23206d2.bin=@23206d2.bin,0 -23206n0.bin=@23206n0.bin,0 -23206n1.bin=@23206n1.bin,0 -23206n2.bin=@23206n2.bin,0 -23207d0.bin=@23207d0.bin,0 -23207d1.bin=@23207d1.bin,0 -23207d2.bin=@23207d2.bin,0 -23207n0.bin=@23207n0.bin,0 -23207n1.bin=@23207n1.bin,0 -23207n2.bin=@23207n2.bin,0 -23208d0.bin=@23208d0.bin,0 -23208d1.bin=@23208d1.bin,0 -23208d2.bin=@23208d2.bin,0 -23208n0.bin=@23208n0.bin,0 -23208n1.bin=@23208n1.bin,0 -23208n2.bin=@23208n2.bin,0 -23209d0.bin=@23209d0.bin,0 -23209d1.bin=@23209d1.bin,0 -23209d2.bin=@23209d2.bin,0 -23209n0.bin=@23209n0.bin,0 -23209n1.bin=@23209n1.bin,0 -23209n2.bin=@23209n2.bin,0 -23210d0.bin=@23210d0.bin,0 -23210d1.bin=@23210d1.bin,0 -23210d2.bin=@23210d2.bin,0 -23210n0.bin=@23210n0.bin,0 -23210n1.bin=@23210n1.bin,0 -23210n2.bin=@23210n2.bin,0 -23253d0.bin=@23253d0.bin,0 -23253d1.bin=@23253d1.bin,0 -23253d2.bin=@23253d2.bin,0 -23253n0.bin=@23253n0.bin,0 -23253n1.bin=@23253n1.bin,0 -23253n2.bin=@23253n2.bin,0 -23254d0.bin=@23254d0.bin,0 -23254d1.bin=@23254d1.bin,0 -23254d2.bin=@23254d2.bin,0 -23254n0.bin=@23254n0.bin,0 -23254n1.bin=@23254n1.bin,0 -23254n2.bin=@23254n2.bin,0 -23269d0.bin=@23269d0.bin,0 -23269d1.bin=@23269d1.bin,0 -23269d2.bin=@23269d2.bin,0 -23269n0.bin=@23269n0.bin,0 -23269n1.bin=@23269n1.bin,0 -23269n2.bin=@23269n2.bin,0 -23270d0.bin=@23270d0.bin,0 -23270d1.bin=@23270d1.bin,0 -23270d2.bin=@23270d2.bin,0 -23270n0.bin=@23270n0.bin,0 -23270n1.bin=@23270n1.bin,0 -23270n2.bin=@23270n2.bin,0 -23271d0.bin=@23271d0.bin,0 -23271d1.bin=@23271d1.bin,0 -23271d2.bin=@23271d2.bin,0 -23271n0.bin=@23271n0.bin,0 -23271n1.bin=@23271n1.bin,0 -23271n2.bin=@23271n2.bin,0 -23272d0.bin=@23272d0.bin,0 -23272d1.bin=@23272d1.bin,0 -23272d2.bin=@23272d2.bin,0 -23272n0.bin=@23272n0.bin,0 -23272n1.bin=@23272n1.bin,0 -23272n2.bin=@23272n2.bin,0 -23273d0.bin=@23273d0.bin,0 -23273d1.bin=@23273d1.bin,0 -23273d2.bin=@23273d2.bin,0 -23273n0.bin=@23273n0.bin,0 -23273n1.bin=@23273n1.bin,0 -23273n2.bin=@23273n2.bin,0 -23274d0.bin=@23274d0.bin,0 -23274d1.bin=@23274d1.bin,0 -23274d2.bin=@23274d2.bin,0 -23274n0.bin=@23274n0.bin,0 -23274n1.bin=@23274n1.bin,0 -23274n2.bin=@23274n2.bin,0 -23280d0.bin=@23280d0.bin,0 -23280d1.bin=@23280d1.bin,0 -23280d2.bin=@23280d2.bin,0 -23280n0.bin=@23280n0.bin,0 -23280n1.bin=@23280n1.bin,0 -23280n2.bin=@23280n2.bin,0 -23281d0.bin=@23281d0.bin,0 -23281d1.bin=@23281d1.bin,0 -23281d2.bin=@23281d2.bin,0 -23281n0.bin=@23281n0.bin,0 -23281n1.bin=@23281n1.bin,0 -23281n2.bin=@23281n2.bin,0 -23303d0.bin=@23303d0.bin,0 -23303d1.bin=@23303d1.bin,0 -23303d2.bin=@23303d2.bin,0 -23303n0.bin=@23303n0.bin,0 -23303n1.bin=@23303n1.bin,0 -23303n2.bin=@23303n2.bin,0 -23304d0.bin=@23304d0.bin,0 -23304d1.bin=@23304d1.bin,0 -23304d2.bin=@23304d2.bin,0 -23304n0.bin=@23304n0.bin,0 -23304n1.bin=@23304n1.bin,0 -23304n2.bin=@23304n2.bin,0 -23323d0.bin=@23323d0.bin,0 -23323d1.bin=@23323d1.bin,0 -23323d2.bin=@23323d2.bin,0 -23323n0.bin=@23323n0.bin,0 -23323n1.bin=@23323n1.bin,0 -23323n2.bin=@23323n2.bin,0 -23326d0.bin=@23326d0.bin,0 -23326d1.bin=@23326d1.bin,0 -23326d2.bin=@23326d2.bin,0 -23326n0.bin=@23326n0.bin,0 -23326n1.bin=@23326n1.bin,0 -23326n2.bin=@23326n2.bin,0 -23199d0.bin=@23199d0.bin,0 -23199d1.bin=@23199d1.bin,0 -23199d2.bin=@23199d2.bin,0 -23199n0.bin=@23199n0.bin,0 -23199n1.bin=@23199n1.bin,0 -23211d2.bin=@23211d2.bin,0 -23211n0.bin=@23211n0.bin,0 -23211n1.bin=@23211n1.bin,0 -23211n2.bin=@23211n2.bin,0 -23212d0.bin=@23212d0.bin,0 -23212d1.bin=@23212d1.bin,0 -23212d2.bin=@23212d2.bin,0 -23212n0.bin=@23212n0.bin,0 -23212n1.bin=@23212n1.bin,0 -23212n2.bin=@23212n2.bin,0 -23213d0.bin=@23213d0.bin,0 -23213d1.bin=@23213d1.bin,0 -23213d2.bin=@23213d2.bin,0 -23213n0.bin=@23213n0.bin,0 -23213n1.bin=@23213n1.bin,0 -23213n2.bin=@23213n2.bin,0 -23214d0.bin=@23214d0.bin,0 -23214d1.bin=@23214d1.bin,0 -23214d2.bin=@23214d2.bin,0 -23214n0.bin=@23214n0.bin,0 -23214n1.bin=@23214n1.bin,0 -23214n2.bin=@23214n2.bin,0 -23215d0.bin=@23215d0.bin,0 -23215d1.bin=@23215d1.bin,0 -23215d2.bin=@23215d2.bin,0 -23215n0.bin=@23215n0.bin,0 -23215n1.bin=@23215n1.bin,0 -23215n2.bin=@23215n2.bin,0 -23216d0.bin=@23216d0.bin,0 -23216d1.bin=@23216d1.bin,0 -23216d2.bin=@23216d2.bin,0 -23216n0.bin=@23216n0.bin,0 -23216n1.bin=@23216n1.bin,0 -23216n2.bin=@23216n2.bin,0 -23217d0.bin=@23217d0.bin,0 -23217d1.bin=@23217d1.bin,0 -23217d2.bin=@23217d2.bin,0 -23217n0.bin=@23217n0.bin,0 -23217n1.bin=@23217n1.bin,0 -23217n2.bin=@23217n2.bin,0 -23218d0.bin=@23218d0.bin,0 -23218d1.bin=@23218d1.bin,0 -23218d2.bin=@23218d2.bin,0 -23218n0.bin=@23218n0.bin,0 -23218n1.bin=@23218n1.bin,0 -23218n2.bin=@23218n2.bin,0 -23219d0.bin=@23219d0.bin,0 -23219d1.bin=@23219d1.bin,0 -23219d2.bin=@23219d2.bin,0 -23219n0.bin=@23219n0.bin,0 -23219n1.bin=@23219n1.bin,0 -23219n2.bin=@23219n2.bin,0 -23220d0.bin=@23220d0.bin,0 -23220d1.bin=@23220d1.bin,0 -23220d2.bin=@23220d2.bin,0 -23220n0.bin=@23220n0.bin,0 -23220n1.bin=@23220n1.bin,0 -23220n2.bin=@23220n2.bin,0 -23221d0.bin=@23221d0.bin,0 -23221d1.bin=@23221d1.bin,0 -23221d2.bin=@23221d2.bin,0 -23221n0.bin=@23221n0.bin,0 -23221n1.bin=@23221n1.bin,0 -23221n2.bin=@23221n2.bin,0 -23223d0.bin=@23223d0.bin,0 -23223d1.bin=@23223d1.bin,0 -23223d2.bin=@23223d2.bin,0 -23223n0.bin=@23223n0.bin,0 -23223n1.bin=@23223n1.bin,0 -23223n2.bin=@23223n2.bin,0 -23255d0.bin=@23255d0.bin,0 -23255d1.bin=@23255d1.bin,0 -23255d2.bin=@23255d2.bin,0 -23255n0.bin=@23255n0.bin,0 -23255n1.bin=@23255n1.bin,0 -23255n2.bin=@23255n2.bin,0 -23256d0.bin=@23256d0.bin,0 -23256d1.bin=@23256d1.bin,0 -23256d2.bin=@23256d2.bin,0 -23256n0.bin=@23256n0.bin,0 -23256n1.bin=@23256n1.bin,0 -23256n2.bin=@23256n2.bin,0 -23282d0.bin=@23282d0.bin,0 -23282d1.bin=@23282d1.bin,0 -23282d2.bin=@23282d2.bin,0 -23282n0.bin=@23282n0.bin,0 -23282n1.bin=@23282n1.bin,0 -23282n2.bin=@23282n2.bin,0 -23283d0.bin=@23283d0.bin,0 -23283d1.bin=@23283d1.bin,0 -23283d2.bin=@23283d2.bin,0 -23283n0.bin=@23283n0.bin,0 -23283n1.bin=@23283n1.bin,0 -23283n2.bin=@23283n2.bin,0 -23305d0.bin=@23305d0.bin,0 -23305d1.bin=@23305d1.bin,0 -23305d2.bin=@23305d2.bin,0 -23305n0.bin=@23305n0.bin,0 -23305n1.bin=@23305n1.bin,0 -23305n2.bin=@23305n2.bin,0 -23320d0.bin=@23320d0.bin,0 -23320d1.bin=@23320d1.bin,0 -23320d2.bin=@23320d2.bin,0 -23320n0.bin=@23320n0.bin,0 -23320n1.bin=@23320n1.bin,0 -23320n2.bin=@23320n2.bin,0 -23321d0.bin=@23321d0.bin,0 -23321d1.bin=@23321d1.bin,0 -23321d2.bin=@23321d2.bin,0 -23321n0.bin=@23321n0.bin,0 -23321n1.bin=@23321n1.bin,0 -23321n2.bin=@23321n2.bin,0 -23324d0.bin=@23324d0.bin,0 -23324d1.bin=@23324d1.bin,0 -23324d2.bin=@23324d2.bin,0 -23324n0.bin=@23324n0.bin,0 -23324n1.bin=@23324n1.bin,0 -23324n2.bin=@23324n2.bin,0 -23350d0.bin=@23350d0.bin,0 -23350d1.bin=@23350d1.bin,0 -23350d2.bin=@23350d2.bin,0 -23350n0.bin=@23350n0.bin,0 -23350n1.bin=@23350n1.bin,0 -23350n2.bin=@23350n2.bin,0 -23351d0.bin=@23351d0.bin,0 -23351d1.bin=@23351d1.bin,0 -23351d2.bin=@23351d2.bin,0 -23351n0.bin=@23351n0.bin,0 -23351n1.bin=@23351n1.bin,0 -23351n2.bin=@23351n2.bin,0 -23369d0.bin=@23369d0.bin,0 -23369d1.bin=@23369d1.bin,0 -23369d2.bin=@23369d2.bin,0 -23369n0.bin=@23369n0.bin,0 -23369n1.bin=@23369n1.bin,0 -23369n2.bin=@23369n2.bin,0 -23370d0.bin=@23370d0.bin,0 -23370d1.bin=@23370d1.bin,0 -23370d2.bin=@23370d2.bin,0 -23370n0.bin=@23370n0.bin,0 -23370n1.bin=@23370n1.bin,0 -23370n2.bin=@23370n2.bin,0 -23399d0.bin=@23399d0.bin,0 -23399d1.bin=@23399d1.bin,0 -23399d2.bin=@23399d2.bin,0 -23399n0.bin=@23399n0.bin,0 -23399n1.bin=@23399n1.bin,0 -23399n2.bin=@23399n2.bin,0 -23401d0.bin=@23401d0.bin,0 -23401d1.bin=@23401d1.bin,0 -23401d2.bin=@23401d2.bin,0 -23401n0.bin=@23401n0.bin,0 -23401n1.bin=@23401n1.bin,0 -23401n2.bin=@23401n2.bin,0 -23211d0.bin=@23211d0.bin,0 -23211d1.bin=@23211d1.bin,0 -23224n1.bin=@23224n1.bin,0 -23224n2.bin=@23224n2.bin,0 -23225d0.bin=@23225d0.bin,0 -23225d1.bin=@23225d1.bin,0 -23225d2.bin=@23225d2.bin,0 -23225n0.bin=@23225n0.bin,0 -23225n1.bin=@23225n1.bin,0 -23225n2.bin=@23225n2.bin,0 -23226d0.bin=@23226d0.bin,0 -23226d1.bin=@23226d1.bin,0 -23226d2.bin=@23226d2.bin,0 -23226n0.bin=@23226n0.bin,0 -23226n1.bin=@23226n1.bin,0 -23226n2.bin=@23226n2.bin,0 -23227d0.bin=@23227d0.bin,0 -23227d1.bin=@23227d1.bin,0 -23227d2.bin=@23227d2.bin,0 -23227n0.bin=@23227n0.bin,0 -23227n1.bin=@23227n1.bin,0 -23227n2.bin=@23227n2.bin,0 -23228d0.bin=@23228d0.bin,0 -23228d1.bin=@23228d1.bin,0 -23228d2.bin=@23228d2.bin,0 -23228n0.bin=@23228n0.bin,0 -23228n1.bin=@23228n1.bin,0 -23228n2.bin=@23228n2.bin,0 -23229d0.bin=@23229d0.bin,0 -23229d1.bin=@23229d1.bin,0 -23229d2.bin=@23229d2.bin,0 -23229n0.bin=@23229n0.bin,0 -23229n1.bin=@23229n1.bin,0 -23229n2.bin=@23229n2.bin,0 -23230d0.bin=@23230d0.bin,0 -23230d1.bin=@23230d1.bin,0 -23230d2.bin=@23230d2.bin,0 -23230n0.bin=@23230n0.bin,0 -23230n1.bin=@23230n1.bin,0 -23230n2.bin=@23230n2.bin,0 -23231d0.bin=@23231d0.bin,0 -23231d1.bin=@23231d1.bin,0 -23231d2.bin=@23231d2.bin,0 -23231n0.bin=@23231n0.bin,0 -23231n1.bin=@23231n1.bin,0 -23231n2.bin=@23231n2.bin,0 -23232d0.bin=@23232d0.bin,0 -23232d1.bin=@23232d1.bin,0 -23232d2.bin=@23232d2.bin,0 -23232n0.bin=@23232n0.bin,0 -23232n1.bin=@23232n1.bin,0 -23232n2.bin=@23232n2.bin,0 -23233d0.bin=@23233d0.bin,0 -23233d1.bin=@23233d1.bin,0 -23233d2.bin=@23233d2.bin,0 -23233n0.bin=@23233n0.bin,0 -23233n1.bin=@23233n1.bin,0 -23233n2.bin=@23233n2.bin,0 -23238d0.bin=@23238d0.bin,0 -23238d1.bin=@23238d1.bin,0 -23238d2.bin=@23238d2.bin,0 -23238n0.bin=@23238n0.bin,0 -23238n1.bin=@23238n1.bin,0 -23238n2.bin=@23238n2.bin,0 -23239d0.bin=@23239d0.bin,0 -23239d1.bin=@23239d1.bin,0 -23239d2.bin=@23239d2.bin,0 -23239n0.bin=@23239n0.bin,0 -23239n1.bin=@23239n1.bin,0 -23239n2.bin=@23239n2.bin,0 -23257d0.bin=@23257d0.bin,0 -23257d1.bin=@23257d1.bin,0 -23257d2.bin=@23257d2.bin,0 -23257n0.bin=@23257n0.bin,0 -23257n1.bin=@23257n1.bin,0 -23257n2.bin=@23257n2.bin,0 -23258d0.bin=@23258d0.bin,0 -23258d1.bin=@23258d1.bin,0 -23258d2.bin=@23258d2.bin,0 -23258n0.bin=@23258n0.bin,0 -23258n1.bin=@23258n1.bin,0 -23258n2.bin=@23258n2.bin,0 -23284d0.bin=@23284d0.bin,0 -23284d1.bin=@23284d1.bin,0 -23284d2.bin=@23284d2.bin,0 -23284n0.bin=@23284n0.bin,0 -23284n1.bin=@23284n1.bin,0 -23284n2.bin=@23284n2.bin,0 -23285d0.bin=@23285d0.bin,0 -23285d1.bin=@23285d1.bin,0 -23285d2.bin=@23285d2.bin,0 -23285n0.bin=@23285n0.bin,0 -23285n1.bin=@23285n1.bin,0 -23285n2.bin=@23285n2.bin,0 -23311d0.bin=@23311d0.bin,0 -23311d1.bin=@23311d1.bin,0 -23311d2.bin=@23311d2.bin,0 -23311n0.bin=@23311n0.bin,0 -23311n1.bin=@23311n1.bin,0 -23311n2.bin=@23311n2.bin,0 -23346d0.bin=@23346d0.bin,0 -23346d1.bin=@23346d1.bin,0 -23346d2.bin=@23346d2.bin,0 -23346n0.bin=@23346n0.bin,0 -23346n1.bin=@23346n1.bin,0 -23346n2.bin=@23346n2.bin,0 -23347d0.bin=@23347d0.bin,0 -23347d1.bin=@23347d1.bin,0 -23347d2.bin=@23347d2.bin,0 -23347n0.bin=@23347n0.bin,0 -23347n1.bin=@23347n1.bin,0 -23347n2.bin=@23347n2.bin,0 -23371d0.bin=@23371d0.bin,0 -23371d1.bin=@23371d1.bin,0 -23371d2.bin=@23371d2.bin,0 -23371n0.bin=@23371n0.bin,0 -23371n1.bin=@23371n1.bin,0 -23371n2.bin=@23371n2.bin,0 -23372d0.bin=@23372d0.bin,0 -23372d1.bin=@23372d1.bin,0 -23372d2.bin=@23372d2.bin,0 -23372n0.bin=@23372n0.bin,0 -23372n1.bin=@23372n1.bin,0 -23372n2.bin=@23372n2.bin,0 -23386d0.bin=@23386d0.bin,0 -23386d1.bin=@23386d1.bin,0 -23386d2.bin=@23386d2.bin,0 -23386n0.bin=@23386n0.bin,0 -23386n1.bin=@23386n1.bin,0 -23386n2.bin=@23386n2.bin,0 -23387d0.bin=@23387d0.bin,0 -23387d1.bin=@23387d1.bin,0 -23387d2.bin=@23387d2.bin,0 -23387n0.bin=@23387n0.bin,0 -23387n1.bin=@23387n1.bin,0 -23387n2.bin=@23387n2.bin,0 -23224d0.bin=@23224d0.bin,0 -23224d1.bin=@23224d1.bin,0 -23224d2.bin=@23224d2.bin,0 -23224n0.bin=@23224n0.bin,0 -23236n0.bin=@23236n0.bin,0 -23236n1.bin=@23236n1.bin,0 -23236n2.bin=@23236n2.bin,0 -23237d0.bin=@23237d0.bin,0 -23237d1.bin=@23237d1.bin,0 -23237d2.bin=@23237d2.bin,0 -23237n0.bin=@23237n0.bin,0 -23237n1.bin=@23237n1.bin,0 -23237n2.bin=@23237n2.bin,0 -23240d0.bin=@23240d0.bin,0 -23240d1.bin=@23240d1.bin,0 -23240d2.bin=@23240d2.bin,0 -23240n0.bin=@23240n0.bin,0 -23240n1.bin=@23240n1.bin,0 -23240n2.bin=@23240n2.bin,0 -23241d0.bin=@23241d0.bin,0 -23241d1.bin=@23241d1.bin,0 -23241d2.bin=@23241d2.bin,0 -23241n0.bin=@23241n0.bin,0 -23241n1.bin=@23241n1.bin,0 -23241n2.bin=@23241n2.bin,0 -23250d0.bin=@23250d0.bin,0 -23250d1.bin=@23250d1.bin,0 -23250d2.bin=@23250d2.bin,0 -23250n0.bin=@23250n0.bin,0 -23250n1.bin=@23250n1.bin,0 -23250n2.bin=@23250n2.bin,0 -23251d0.bin=@23251d0.bin,0 -23251d1.bin=@23251d1.bin,0 -23251d2.bin=@23251d2.bin,0 -23251n0.bin=@23251n0.bin,0 -23251n1.bin=@23251n1.bin,0 -23251n2.bin=@23251n2.bin,0 -23252d0.bin=@23252d0.bin,0 -23252d1.bin=@23252d1.bin,0 -23252d2.bin=@23252d2.bin,0 -23252n0.bin=@23252n0.bin,0 -23252n1.bin=@23252n1.bin,0 -23252n2.bin=@23252n2.bin,0 -23259d0.bin=@23259d0.bin,0 -23259d1.bin=@23259d1.bin,0 -23259d2.bin=@23259d2.bin,0 -23259n0.bin=@23259n0.bin,0 -23259n1.bin=@23259n1.bin,0 -23259n2.bin=@23259n2.bin,0 -23260d0.bin=@23260d0.bin,0 -23260d1.bin=@23260d1.bin,0 -23260d2.bin=@23260d2.bin,0 -23260n0.bin=@23260n0.bin,0 -23260n1.bin=@23260n1.bin,0 -23260n2.bin=@23260n2.bin,0 -23286d0.bin=@23286d0.bin,0 -23286d1.bin=@23286d1.bin,0 -23286d2.bin=@23286d2.bin,0 -23286n0.bin=@23286n0.bin,0 -23286n1.bin=@23286n1.bin,0 -23286n2.bin=@23286n2.bin,0 -23287d0.bin=@23287d0.bin,0 -23287d1.bin=@23287d1.bin,0 -23287d2.bin=@23287d2.bin,0 -23287n0.bin=@23287n0.bin,0 -23287n1.bin=@23287n1.bin,0 -23287n2.bin=@23287n2.bin,0 -23288d0.bin=@23288d0.bin,0 -23288d1.bin=@23288d1.bin,0 -23288d2.bin=@23288d2.bin,0 -23288n0.bin=@23288n0.bin,0 -23288n1.bin=@23288n1.bin,0 -23288n2.bin=@23288n2.bin,0 -23289d0.bin=@23289d0.bin,0 -23289d1.bin=@23289d1.bin,0 -23289d2.bin=@23289d2.bin,0 -23289n0.bin=@23289n0.bin,0 -23289n1.bin=@23289n1.bin,0 -23289n2.bin=@23289n2.bin,0 -23325d0.bin=@23325d0.bin,0 -23325d1.bin=@23325d1.bin,0 -23325d2.bin=@23325d2.bin,0 -23325n0.bin=@23325n0.bin,0 -23325n1.bin=@23325n1.bin,0 -23325n2.bin=@23325n2.bin,0 -23327d0.bin=@23327d0.bin,0 -23327d1.bin=@23327d1.bin,0 -23327d2.bin=@23327d2.bin,0 -23327n0.bin=@23327n0.bin,0 -23327n1.bin=@23327n1.bin,0 -23327n2.bin=@23327n2.bin,0 -23348d0.bin=@23348d0.bin,0 -23348d1.bin=@23348d1.bin,0 -23348d2.bin=@23348d2.bin,0 -23348n0.bin=@23348n0.bin,0 -23348n1.bin=@23348n1.bin,0 -23348n2.bin=@23348n2.bin,0 -23349d0.bin=@23349d0.bin,0 -23349d1.bin=@23349d1.bin,0 -23349d2.bin=@23349d2.bin,0 -23349n0.bin=@23349n0.bin,0 -23349n1.bin=@23349n1.bin,0 -23349n2.bin=@23349n2.bin,0 -23382d0.bin=@23382d0.bin,0 -23382d1.bin=@23382d1.bin,0 -23382d2.bin=@23382d2.bin,0 -23382n0.bin=@23382n0.bin,0 -23382n1.bin=@23382n1.bin,0 -23382n2.bin=@23382n2.bin,0 -23383d0.bin=@23383d0.bin,0 -23383d1.bin=@23383d1.bin,0 -23383d2.bin=@23383d2.bin,0 -23383n0.bin=@23383n0.bin,0 -23383n1.bin=@23383n1.bin,0 -23383n2.bin=@23383n2.bin,0 -23384d0.bin=@23384d0.bin,0 -23384d1.bin=@23384d1.bin,0 -23384d2.bin=@23384d2.bin,0 -23384n0.bin=@23384n0.bin,0 -23384n1.bin=@23384n1.bin,0 -23384n2.bin=@23384n2.bin,0 -23385d0.bin=@23385d0.bin,0 -23385d1.bin=@23385d1.bin,0 -23385d2.bin=@23385d2.bin,0 -23385n0.bin=@23385n0.bin,0 -23385n1.bin=@23385n1.bin,0 -23385n2.bin=@23385n2.bin,0 -23394d0.bin=@23394d0.bin,0 -23394d1.bin=@23394d1.bin,0 -23394d2.bin=@23394d2.bin,0 -23394n0.bin=@23394n0.bin,0 -23394n1.bin=@23394n1.bin,0 -23394n2.bin=@23394n2.bin,0 -23395d0.bin=@23395d0.bin,0 -23395d1.bin=@23395d1.bin,0 -23395d2.bin=@23395d2.bin,0 -23395n0.bin=@23395n0.bin,0 -23395n1.bin=@23395n1.bin,0 -23395n2.bin=@23395n2.bin,0 -23396d0.bin=@23396d0.bin,0 -23396d1.bin=@23396d1.bin,0 -23396d2.bin=@23396d2.bin,0 -23396n0.bin=@23396n0.bin,0 -23396n1.bin=@23396n1.bin,0 -23396n2.bin=@23396n2.bin,0 -23236d0.bin=@23236d0.bin,0 -23236d1.bin=@23236d1.bin,0 -23236d2.bin=@23236d2.bin,0 -23234d2.bin=@23234d2.bin,0 -23234n0.bin=@23234n0.bin,0 -23234n1.bin=@23234n1.bin,0 -23234n2.bin=@23234n2.bin,0 -23235d0.bin=@23235d0.bin,0 -23235d1.bin=@23235d1.bin,0 -23235d2.bin=@23235d2.bin,0 -23235n0.bin=@23235n0.bin,0 -23235n1.bin=@23235n1.bin,0 -23235n2.bin=@23235n2.bin,0 -23246d0.bin=@23246d0.bin,0 -23246d1.bin=@23246d1.bin,0 -23246d2.bin=@23246d2.bin,0 -23246n0.bin=@23246n0.bin,0 -23246n1.bin=@23246n1.bin,0 -23246n2.bin=@23246n2.bin,0 -23247d0.bin=@23247d0.bin,0 -23247d1.bin=@23247d1.bin,0 -23247d2.bin=@23247d2.bin,0 -23247n0.bin=@23247n0.bin,0 -23247n1.bin=@23247n1.bin,0 -23247n2.bin=@23247n2.bin,0 -23248d0.bin=@23248d0.bin,0 -23248d1.bin=@23248d1.bin,0 -23248d2.bin=@23248d2.bin,0 -23248n0.bin=@23248n0.bin,0 -23248n1.bin=@23248n1.bin,0 -23248n2.bin=@23248n2.bin,0 -23249d0.bin=@23249d0.bin,0 -23249d1.bin=@23249d1.bin,0 -23249d2.bin=@23249d2.bin,0 -23249n0.bin=@23249n0.bin,0 -23249n1.bin=@23249n1.bin,0 -23249n2.bin=@23249n2.bin,0 -23261d0.bin=@23261d0.bin,0 -23261d1.bin=@23261d1.bin,0 -23261d2.bin=@23261d2.bin,0 -23261n0.bin=@23261n0.bin,0 -23261n1.bin=@23261n1.bin,0 -23261n2.bin=@23261n2.bin,0 -23262d0.bin=@23262d0.bin,0 -23262d1.bin=@23262d1.bin,0 -23262d2.bin=@23262d2.bin,0 -23262n0.bin=@23262n0.bin,0 -23262n1.bin=@23262n1.bin,0 -23262n2.bin=@23262n2.bin,0 -23267d0.bin=@23267d0.bin,0 -23267d1.bin=@23267d1.bin,0 -23267d2.bin=@23267d2.bin,0 -23267n0.bin=@23267n0.bin,0 -23267n1.bin=@23267n1.bin,0 -23267n2.bin=@23267n2.bin,0 -23268d0.bin=@23268d0.bin,0 -23268d1.bin=@23268d1.bin,0 -23268d2.bin=@23268d2.bin,0 -23268n0.bin=@23268n0.bin,0 -23268n1.bin=@23268n1.bin,0 -23268n2.bin=@23268n2.bin,0 -23275d0.bin=@23275d0.bin,0 -23275d1.bin=@23275d1.bin,0 -23275d2.bin=@23275d2.bin,0 -23275n0.bin=@23275n0.bin,0 -23275n1.bin=@23275n1.bin,0 -23275n2.bin=@23275n2.bin,0 -23309d0.bin=@23309d0.bin,0 -23309d1.bin=@23309d1.bin,0 -23309d2.bin=@23309d2.bin,0 -23309n0.bin=@23309n0.bin,0 -23309n1.bin=@23309n1.bin,0 -23309n2.bin=@23309n2.bin,0 -23310d0.bin=@23310d0.bin,0 -23310d1.bin=@23310d1.bin,0 -23310d2.bin=@23310d2.bin,0 -23310n0.bin=@23310n0.bin,0 -23310n1.bin=@23310n1.bin,0 -23310n2.bin=@23310n2.bin,0 -23345d0.bin=@23345d0.bin,0 -23345d1.bin=@23345d1.bin,0 -23345d2.bin=@23345d2.bin,0 -23345n0.bin=@23345n0.bin,0 -23345n1.bin=@23345n1.bin,0 -23345n2.bin=@23345n2.bin,0 -23397d0.bin=@23397d0.bin,0 -23397d1.bin=@23397d1.bin,0 -23397d2.bin=@23397d2.bin,0 -23397n0.bin=@23397n0.bin,0 -23397n1.bin=@23397n1.bin,0 -23397n2.bin=@23397n2.bin,0 -23398d0.bin=@23398d0.bin,0 -23398d1.bin=@23398d1.bin,0 -23398d2.bin=@23398d2.bin,0 -23398n0.bin=@23398n0.bin,0 -23398n1.bin=@23398n1.bin,0 -23398n2.bin=@23398n2.bin,0 -23403d0.bin=@23403d0.bin,0 -23403d1.bin=@23403d1.bin,0 -23403d2.bin=@23403d2.bin,0 -23403n0.bin=@23403n0.bin,0 -23403n1.bin=@23403n1.bin,0 -23403n2.bin=@23403n2.bin,0 -23404d0.bin=@23404d0.bin,0 -23404d1.bin=@23404d1.bin,0 -23404d2.bin=@23404d2.bin,0 -23404n0.bin=@23404n0.bin,0 -23404n1.bin=@23404n1.bin,0 -23404n2.bin=@23404n2.bin,0 -23425d0.bin=@23425d0.bin,0 -23425d1.bin=@23425d1.bin,0 -23425d2.bin=@23425d2.bin,0 -23425n0.bin=@23425n0.bin,0 -23425n1.bin=@23425n1.bin,0 -23425n2.bin=@23425n2.bin,0 -23426d0.bin=@23426d0.bin,0 -23426d1.bin=@23426d1.bin,0 -23426d2.bin=@23426d2.bin,0 -23426n0.bin=@23426n0.bin,0 -23426n1.bin=@23426n1.bin,0 -23426n2.bin=@23426n2.bin,0 -23433d0.bin=@23433d0.bin,0 -23433d1.bin=@23433d1.bin,0 -23433d2.bin=@23433d2.bin,0 -23433n0.bin=@23433n0.bin,0 -23433n1.bin=@23433n1.bin,0 -23433n2.bin=@23433n2.bin,0 -23434d0.bin=@23434d0.bin,0 -23434d1.bin=@23434d1.bin,0 -23434d2.bin=@23434d2.bin,0 -23434n0.bin=@23434n0.bin,0 -23434n1.bin=@23434n1.bin,0 -23434n2.bin=@23434n2.bin,0 -23234d0.bin=@23234d0.bin,0 -23234d1.bin=@23234d1.bin,0 -23242n0.bin=@23242n0.bin,0 -23242n1.bin=@23242n1.bin,0 -23242n2.bin=@23242n2.bin,0 -23243d0.bin=@23243d0.bin,0 -23243d1.bin=@23243d1.bin,0 -23243d2.bin=@23243d2.bin,0 -23243n0.bin=@23243n0.bin,0 -23243n1.bin=@23243n1.bin,0 -23243n2.bin=@23243n2.bin,0 -23244d0.bin=@23244d0.bin,0 -23244d1.bin=@23244d1.bin,0 -23244d2.bin=@23244d2.bin,0 -23244n0.bin=@23244n0.bin,0 -23244n1.bin=@23244n1.bin,0 -23244n2.bin=@23244n2.bin,0 -23245d0.bin=@23245d0.bin,0 -23245d1.bin=@23245d1.bin,0 -23245d2.bin=@23245d2.bin,0 -23245n0.bin=@23245n0.bin,0 -23245n1.bin=@23245n1.bin,0 -23245n2.bin=@23245n2.bin,0 -23263d0.bin=@23263d0.bin,0 -23263d1.bin=@23263d1.bin,0 -23263d2.bin=@23263d2.bin,0 -23263n0.bin=@23263n0.bin,0 -23263n1.bin=@23263n1.bin,0 -23263n2.bin=@23263n2.bin,0 -23264d0.bin=@23264d0.bin,0 -23264d1.bin=@23264d1.bin,0 -23264d2.bin=@23264d2.bin,0 -23264n0.bin=@23264n0.bin,0 -23264n1.bin=@23264n1.bin,0 -23264n2.bin=@23264n2.bin,0 -23277d0.bin=@23277d0.bin,0 -23277d1.bin=@23277d1.bin,0 -23277d2.bin=@23277d2.bin,0 -23277n0.bin=@23277n0.bin,0 -23277n1.bin=@23277n1.bin,0 -23277n2.bin=@23277n2.bin,0 -23278d0.bin=@23278d0.bin,0 -23278d1.bin=@23278d1.bin,0 -23278d2.bin=@23278d2.bin,0 -23278n0.bin=@23278n0.bin,0 -23278n1.bin=@23278n1.bin,0 -23278n2.bin=@23278n2.bin,0 -23279d0.bin=@23279d0.bin,0 -23279d1.bin=@23279d1.bin,0 -23279d2.bin=@23279d2.bin,0 -23279n0.bin=@23279n0.bin,0 -23279n1.bin=@23279n1.bin,0 -23279n2.bin=@23279n2.bin,0 -23307d0.bin=@23307d0.bin,0 -23307d1.bin=@23307d1.bin,0 -23307d2.bin=@23307d2.bin,0 -23307n0.bin=@23307n0.bin,0 -23307n1.bin=@23307n1.bin,0 -23307n2.bin=@23307n2.bin,0 -23312d0.bin=@23312d0.bin,0 -23312d1.bin=@23312d1.bin,0 -23312d2.bin=@23312d2.bin,0 -23312n0.bin=@23312n0.bin,0 -23312n1.bin=@23312n1.bin,0 -23312n2.bin=@23312n2.bin,0 -23313d0.bin=@23313d0.bin,0 -23313d1.bin=@23313d1.bin,0 -23313d2.bin=@23313d2.bin,0 -23313n0.bin=@23313n0.bin,0 -23313n1.bin=@23313n1.bin,0 -23313n2.bin=@23313n2.bin,0 -23318d0.bin=@23318d0.bin,0 -23318d1.bin=@23318d1.bin,0 -23318d2.bin=@23318d2.bin,0 -23318n0.bin=@23318n0.bin,0 -23318n1.bin=@23318n1.bin,0 -23318n2.bin=@23318n2.bin,0 -23319d0.bin=@23319d0.bin,0 -23319d1.bin=@23319d1.bin,0 -23319d2.bin=@23319d2.bin,0 -23319n0.bin=@23319n0.bin,0 -23319n1.bin=@23319n1.bin,0 -23319n2.bin=@23319n2.bin,0 -23322d0.bin=@23322d0.bin,0 -23322d1.bin=@23322d1.bin,0 -23322d2.bin=@23322d2.bin,0 -23322n0.bin=@23322n0.bin,0 -23322n1.bin=@23322n1.bin,0 -23322n2.bin=@23322n2.bin,0 -23342d0.bin=@23342d0.bin,0 -23342d1.bin=@23342d1.bin,0 -23342d2.bin=@23342d2.bin,0 -23342n0.bin=@23342n0.bin,0 -23342n1.bin=@23342n1.bin,0 -23342n2.bin=@23342n2.bin,0 -23343d0.bin=@23343d0.bin,0 -23343d1.bin=@23343d1.bin,0 -23343d2.bin=@23343d2.bin,0 -23343n0.bin=@23343n0.bin,0 -23343n1.bin=@23343n1.bin,0 -23343n2.bin=@23343n2.bin,0 -23344d0.bin=@23344d0.bin,0 -23344d1.bin=@23344d1.bin,0 -23344d2.bin=@23344d2.bin,0 -23344n0.bin=@23344n0.bin,0 -23344n1.bin=@23344n1.bin,0 -23344n2.bin=@23344n2.bin,0 -23373d0.bin=@23373d0.bin,0 -23373d1.bin=@23373d1.bin,0 -23373d2.bin=@23373d2.bin,0 -23373n0.bin=@23373n0.bin,0 -23373n1.bin=@23373n1.bin,0 -23373n2.bin=@23373n2.bin,0 -23429d0.bin=@23429d0.bin,0 -23429d1.bin=@23429d1.bin,0 -23429d2.bin=@23429d2.bin,0 -23429n0.bin=@23429n0.bin,0 -23429n1.bin=@23429n1.bin,0 -23429n2.bin=@23429n2.bin,0 -23430d0.bin=@23430d0.bin,0 -23430d1.bin=@23430d1.bin,0 -23430d2.bin=@23430d2.bin,0 -23430n0.bin=@23430n0.bin,0 -23430n1.bin=@23430n1.bin,0 -23430n2.bin=@23430n2.bin,0 -23431d0.bin=@23431d0.bin,0 -23431d1.bin=@23431d1.bin,0 -23431d2.bin=@23431d2.bin,0 -23431n0.bin=@23431n0.bin,0 -23431n1.bin=@23431n1.bin,0 -23431n2.bin=@23431n2.bin,0 -23432d0.bin=@23432d0.bin,0 -23432d1.bin=@23432d1.bin,0 -23432d2.bin=@23432d2.bin,0 -23432n0.bin=@23432n0.bin,0 -23432n1.bin=@23432n1.bin,0 -23432n2.bin=@23432n2.bin,0 -23242d0.bin=@23242d0.bin,0 -23242d1.bin=@23242d1.bin,0 -23242d2.bin=@23242d2.bin,0 -23265n2.bin=@23265n2.bin,0 -23266d0.bin=@23266d0.bin,0 -23266d1.bin=@23266d1.bin,0 -23266d2.bin=@23266d2.bin,0 -23266n0.bin=@23266n0.bin,0 -23266n1.bin=@23266n1.bin,0 -23266n2.bin=@23266n2.bin,0 -23306d0.bin=@23306d0.bin,0 -23306d1.bin=@23306d1.bin,0 -23306d2.bin=@23306d2.bin,0 -23306n0.bin=@23306n0.bin,0 -23306n1.bin=@23306n1.bin,0 -23306n2.bin=@23306n2.bin,0 -23308d0.bin=@23308d0.bin,0 -23308d1.bin=@23308d1.bin,0 -23308d2.bin=@23308d2.bin,0 -23308n0.bin=@23308n0.bin,0 -23308n1.bin=@23308n1.bin,0 -23308n2.bin=@23308n2.bin,0 -23314d0.bin=@23314d0.bin,0 -23314d1.bin=@23314d1.bin,0 -23314d2.bin=@23314d2.bin,0 -23314n0.bin=@23314n0.bin,0 -23314n1.bin=@23314n1.bin,0 -23314n2.bin=@23314n2.bin,0 -23367d0.bin=@23367d0.bin,0 -23367d1.bin=@23367d1.bin,0 -23367d2.bin=@23367d2.bin,0 -23367n0.bin=@23367n0.bin,0 -23367n1.bin=@23367n1.bin,0 -23367n2.bin=@23367n2.bin,0 -23368d0.bin=@23368d0.bin,0 -23368d1.bin=@23368d1.bin,0 -23368d2.bin=@23368d2.bin,0 -23368n0.bin=@23368n0.bin,0 -23368n1.bin=@23368n1.bin,0 -23368n2.bin=@23368n2.bin,0 -23380d0.bin=@23380d0.bin,0 -23380d1.bin=@23380d1.bin,0 -23380d2.bin=@23380d2.bin,0 -23380n0.bin=@23380n0.bin,0 -23380n1.bin=@23380n1.bin,0 -23380n2.bin=@23380n2.bin,0 -23381d0.bin=@23381d0.bin,0 -23381d1.bin=@23381d1.bin,0 -23381d2.bin=@23381d2.bin,0 -23381n0.bin=@23381n0.bin,0 -23381n1.bin=@23381n1.bin,0 -23381n2.bin=@23381n2.bin,0 -23392d0.bin=@23392d0.bin,0 -23392d1.bin=@23392d1.bin,0 -23392d2.bin=@23392d2.bin,0 -23392n0.bin=@23392n0.bin,0 -23392n1.bin=@23392n1.bin,0 -23392n2.bin=@23392n2.bin,0 -23393d0.bin=@23393d0.bin,0 -23393d1.bin=@23393d1.bin,0 -23393d2.bin=@23393d2.bin,0 -23393n0.bin=@23393n0.bin,0 -23393n1.bin=@23393n1.bin,0 -23393n2.bin=@23393n2.bin,0 -23402d0.bin=@23402d0.bin,0 -23402d1.bin=@23402d1.bin,0 -23402d2.bin=@23402d2.bin,0 -23402n0.bin=@23402n0.bin,0 -23402n1.bin=@23402n1.bin,0 -23402n2.bin=@23402n2.bin,0 -23424d0.bin=@23424d0.bin,0 -23424d1.bin=@23424d1.bin,0 -23424d2.bin=@23424d2.bin,0 -23424n0.bin=@23424n0.bin,0 -23424n1.bin=@23424n1.bin,0 -23424n2.bin=@23424n2.bin,0 -23427d0.bin=@23427d0.bin,0 -23427d1.bin=@23427d1.bin,0 -23427d2.bin=@23427d2.bin,0 -23427n0.bin=@23427n0.bin,0 -23427n1.bin=@23427n1.bin,0 -23427n2.bin=@23427n2.bin,0 -23428d0.bin=@23428d0.bin,0 -23428d1.bin=@23428d1.bin,0 -23428d2.bin=@23428d2.bin,0 -23428n0.bin=@23428n0.bin,0 -23428n1.bin=@23428n1.bin,0 -23428n2.bin=@23428n2.bin,0 -23514d0.bin=@23514d0.bin,0 -23514d1.bin=@23514d1.bin,0 -23514d2.bin=@23514d2.bin,0 -23514n0.bin=@23514n0.bin,0 -23514n1.bin=@23514n1.bin,0 -23514n2.bin=@23514n2.bin,0 -23515d0.bin=@23515d0.bin,0 -23515d1.bin=@23515d1.bin,0 -23515d2.bin=@23515d2.bin,0 -23515n0.bin=@23515n0.bin,0 -23515n1.bin=@23515n1.bin,0 -23515n2.bin=@23515n2.bin,0 -23626d0.bin=@23626d0.bin,0 -23626d1.bin=@23626d1.bin,0 -23626d2.bin=@23626d2.bin,0 -23626n0.bin=@23626n0.bin,0 -23626n1.bin=@23626n1.bin,0 -23626n2.bin=@23626n2.bin,0 -55016d0.bin=@55016d0.bin,0 -55016d1.bin=@55016d1.bin,0 -55016d2.bin=@55016d2.bin,0 -55016n0.bin=@55016n0.bin,0 -55016n1.bin=@55016n1.bin,0 -55016n2.bin=@55016n2.bin,0 -55202d0.bin=@55202d0.bin,0 -55202d1.bin=@55202d1.bin,0 -55202d2.bin=@55202d2.bin,0 -55202n0.bin=@55202n0.bin,0 -55202n1.bin=@55202n1.bin,0 -55202n2.bin=@55202n2.bin,0 -58073d0.bin=@58073d0.bin,0 -58073d1.bin=@58073d1.bin,0 -58073d2.bin=@58073d2.bin,0 -58073n0.bin=@58073n0.bin,0 -58073n1.bin=@58073n1.bin,0 -58073n2.bin=@58073n2.bin,0 -64551d0.bin=@64551d0.bin,0 -64551d1.bin=@64551d1.bin,0 -64551d2.bin=@64551d2.bin,0 -23265d0.bin=@23265d0.bin,0 -23265d1.bin=@23265d1.bin,0 -23265d2.bin=@23265d2.bin,0 -23265n0.bin=@23265n0.bin,0 -23265n1.bin=@23265n1.bin,0 -23648n1.bin=@23648n1.bin,0 -23648n2.bin=@23648n2.bin,0 -23649d0.bin=@23649d0.bin,0 -23649d1.bin=@23649d1.bin,0 -23649d2.bin=@23649d2.bin,0 -23649n0.bin=@23649n0.bin,0 -23649n1.bin=@23649n1.bin,0 -23649n2.bin=@23649n2.bin,0 -54926d0.bin=@54926d0.bin,0 -54926d1.bin=@54926d1.bin,0 -54926d2.bin=@54926d2.bin,0 -54926n0.bin=@54926n0.bin,0 -54926n1.bin=@54926n1.bin,0 -54926n2.bin=@54926n2.bin,0 -55195d0.bin=@55195d0.bin,0 -55195d1.bin=@55195d1.bin,0 -55195d2.bin=@55195d2.bin,0 -55195n0.bin=@55195n0.bin,0 -55195n1.bin=@55195n1.bin,0 -55195n2.bin=@55195n2.bin,0 -55529d0.bin=@55529d0.bin,0 -55529d1.bin=@55529d1.bin,0 -55529d2.bin=@55529d2.bin,0 -55529n0.bin=@55529n0.bin,0 -55529n1.bin=@55529n1.bin,0 -55529n2.bin=@55529n2.bin,0 -55530d0.bin=@55530d0.bin,0 -55530d1.bin=@55530d1.bin,0 -55530d2.bin=@55530d2.bin,0 -55530n0.bin=@55530n0.bin,0 -55530n1.bin=@55530n1.bin,0 -55530n2.bin=@55530n2.bin,0 -55531d0.bin=@55531d0.bin,0 -55531d1.bin=@55531d1.bin,0 -55531d2.bin=@55531d2.bin,0 -55531n0.bin=@55531n0.bin,0 -55531n1.bin=@55531n1.bin,0 -55531n2.bin=@55531n2.bin,0 -55532d0.bin=@55532d0.bin,0 -55532d1.bin=@55532d1.bin,0 -55532d2.bin=@55532d2.bin,0 -55532n0.bin=@55532n0.bin,0 -55532n1.bin=@55532n1.bin,0 -55532n2.bin=@55532n2.bin,0 -55534d0.bin=@55534d0.bin,0 -55534d1.bin=@55534d1.bin,0 -55534d2.bin=@55534d2.bin,0 -55534n0.bin=@55534n0.bin,0 -55534n1.bin=@55534n1.bin,0 -55534n2.bin=@55534n2.bin,0 -55535d0.bin=@55535d0.bin,0 -55535d1.bin=@55535d1.bin,0 -55535d2.bin=@55535d2.bin,0 -55535n0.bin=@55535n0.bin,0 -55535n1.bin=@55535n1.bin,0 -55535n2.bin=@55535n2.bin,0 -55714d0.bin=@55714d0.bin,0 -55714d1.bin=@55714d1.bin,0 -55714d2.bin=@55714d2.bin,0 -55714n0.bin=@55714n0.bin,0 -55714n1.bin=@55714n1.bin,0 -55714n2.bin=@55714n2.bin,0 -55917d0.bin=@55917d0.bin,0 -55917d1.bin=@55917d1.bin,0 -55917d2.bin=@55917d2.bin,0 -55917n0.bin=@55917n0.bin,0 -55917n1.bin=@55917n1.bin,0 -55917n2.bin=@55917n2.bin,0 -55918d0.bin=@55918d0.bin,0 -55918d1.bin=@55918d1.bin,0 -55918d2.bin=@55918d2.bin,0 -55918n0.bin=@55918n0.bin,0 -55918n1.bin=@55918n1.bin,0 -55918n2.bin=@55918n2.bin,0 -55920d0.bin=@55920d0.bin,0 -55920d1.bin=@55920d1.bin,0 -55920d2.bin=@55920d2.bin,0 -55920n0.bin=@55920n0.bin,0 -55920n1.bin=@55920n1.bin,0 -55920n2.bin=@55920n2.bin,0 -55921d0.bin=@55921d0.bin,0 -55921d1.bin=@55921d1.bin,0 -55921d2.bin=@55921d2.bin,0 -55921n0.bin=@55921n0.bin,0 -55921n1.bin=@55921n1.bin,0 -55921n2.bin=@55921n2.bin,0 -55922d0.bin=@55922d0.bin,0 -55922d1.bin=@55922d1.bin,0 -55922d2.bin=@55922d2.bin,0 -55922n0.bin=@55922n0.bin,0 -55922n1.bin=@55922n1.bin,0 -55922n2.bin=@55922n2.bin,0 -55935d0.bin=@55935d0.bin,0 -55935d1.bin=@55935d1.bin,0 -55935d2.bin=@55935d2.bin,0 -55935n0.bin=@55935n0.bin,0 -55935n1.bin=@55935n1.bin,0 -55935n2.bin=@55935n2.bin,0 -55936d0.bin=@55936d0.bin,0 -55936d1.bin=@55936d1.bin,0 -55936d2.bin=@55936d2.bin,0 -55936n0.bin=@55936n0.bin,0 -55936n1.bin=@55936n1.bin,0 -55936n2.bin=@55936n2.bin,0 -55948d0.bin=@55948d0.bin,0 -55948d1.bin=@55948d1.bin,0 -55948d2.bin=@55948d2.bin,0 -55948n0.bin=@55948n0.bin,0 -55948n1.bin=@55948n1.bin,0 -55948n2.bin=@55948n2.bin,0 -55949d0.bin=@55949d0.bin,0 -55949d1.bin=@55949d1.bin,0 -55949d2.bin=@55949d2.bin,0 -55949n0.bin=@55949n0.bin,0 -55949n1.bin=@55949n1.bin,0 -55949n2.bin=@55949n2.bin,0 -55950d0.bin=@55950d0.bin,0 -55950d1.bin=@55950d1.bin,0 -55950d2.bin=@55950d2.bin,0 -55950n0.bin=@55950n0.bin,0 -55950n1.bin=@55950n1.bin,0 -55950n2.bin=@55950n2.bin,0 -55951d0.bin=@55951d0.bin,0 -55951d1.bin=@55951d1.bin,0 -55951d2.bin=@55951d2.bin,0 -55951n0.bin=@55951n0.bin,0 -55951n1.bin=@55951n1.bin,0 -55951n2.bin=@55951n2.bin,0 -56106d0.bin=@56106d0.bin,0 -56106d1.bin=@56106d1.bin,0 -56106d2.bin=@56106d2.bin,0 -56106n0.bin=@56106n0.bin,0 -56106n1.bin=@56106n1.bin,0 -56106n2.bin=@56106n2.bin,0 -56115d0.bin=@56115d0.bin,0 -56115d1.bin=@56115d1.bin,0 -56115d2.bin=@56115d2.bin,0 -56115n0.bin=@56115n0.bin,0 -56115n1.bin=@56115n1.bin,0 -56115n2.bin=@56115n2.bin,0 -56126d0.bin=@56126d0.bin,0 -56126d1.bin=@56126d1.bin,0 -56126d2.bin=@56126d2.bin,0 -56126n0.bin=@56126n0.bin,0 -56126n1.bin=@56126n1.bin,0 -56126n2.bin=@56126n2.bin,0 -56127d0.bin=@56127d0.bin,0 -56127d1.bin=@56127d1.bin,0 -56127d2.bin=@56127d2.bin,0 -56127n0.bin=@56127n0.bin,0 -56127n1.bin=@56127n1.bin,0 -56127n2.bin=@56127n2.bin,0 -56128d0.bin=@56128d0.bin,0 -56128d1.bin=@56128d1.bin,0 -56128d2.bin=@56128d2.bin,0 -56128n0.bin=@56128n0.bin,0 -56128n1.bin=@56128n1.bin,0 -56128n2.bin=@56128n2.bin,0 -56130d0.bin=@56130d0.bin,0 -56130d1.bin=@56130d1.bin,0 -56130d2.bin=@56130d2.bin,0 -56130n0.bin=@56130n0.bin,0 -56130n1.bin=@56130n1.bin,0 -56130n2.bin=@56130n2.bin,0 -56131d0.bin=@56131d0.bin,0 -56131d1.bin=@56131d1.bin,0 -56131d2.bin=@56131d2.bin,0 -56131n0.bin=@56131n0.bin,0 -56131n1.bin=@56131n1.bin,0 -56131n2.bin=@56131n2.bin,0 -56133d0.bin=@56133d0.bin,0 -56133d1.bin=@56133d1.bin,0 -56133d2.bin=@56133d2.bin,0 -56133n0.bin=@56133n0.bin,0 -56133n1.bin=@56133n1.bin,0 -56133n2.bin=@56133n2.bin,0 -56152d0.bin=@56152d0.bin,0 -56152d1.bin=@56152d1.bin,0 -56152d2.bin=@56152d2.bin,0 -56152n0.bin=@56152n0.bin,0 -56152n1.bin=@56152n1.bin,0 -56152n2.bin=@56152n2.bin,0 -56153d0.bin=@56153d0.bin,0 -56153d1.bin=@56153d1.bin,0 -56153d2.bin=@56153d2.bin,0 -56153n0.bin=@56153n0.bin,0 -56153n1.bin=@56153n1.bin,0 -56153n2.bin=@56153n2.bin,0 -56158d0.bin=@56158d0.bin,0 -56158d1.bin=@56158d1.bin,0 -56158d2.bin=@56158d2.bin,0 -56158n0.bin=@56158n0.bin,0 -56158n1.bin=@56158n1.bin,0 -56158n2.bin=@56158n2.bin,0 -23648d0.bin=@23648d0.bin,0 -23648d1.bin=@23648d1.bin,0 -23648d2.bin=@23648d2.bin,0 -23648n0.bin=@23648n0.bin,0 -23468n0.bin=@23468n0.bin,0 -23468n1.bin=@23468n1.bin,0 -23468n2.bin=@23468n2.bin,0 -23472d0.bin=@23472d0.bin,0 -23472d1.bin=@23472d1.bin,0 -23472d2.bin=@23472d2.bin,0 -23472n0.bin=@23472n0.bin,0 -23472n1.bin=@23472n1.bin,0 -23472n2.bin=@23472n2.bin,0 -23476d0.bin=@23476d0.bin,0 -23476d1.bin=@23476d1.bin,0 -23476d2.bin=@23476d2.bin,0 -23476n0.bin=@23476n0.bin,0 -23476n1.bin=@23476n1.bin,0 -23476n2.bin=@23476n2.bin,0 -23480d0.bin=@23480d0.bin,0 -23480d1.bin=@23480d1.bin,0 -23480d2.bin=@23480d2.bin,0 -23480n0.bin=@23480n0.bin,0 -23480n1.bin=@23480n1.bin,0 -23480n2.bin=@23480n2.bin,0 -23516d0.bin=@23516d0.bin,0 -23516d1.bin=@23516d1.bin,0 -23516d2.bin=@23516d2.bin,0 -23516n0.bin=@23516n0.bin,0 -23516n1.bin=@23516n1.bin,0 -23516n2.bin=@23516n2.bin,0 -23520d0.bin=@23520d0.bin,0 -23520d1.bin=@23520d1.bin,0 -23520d2.bin=@23520d2.bin,0 -23520n0.bin=@23520n0.bin,0 -23520n1.bin=@23520n1.bin,0 -23520n2.bin=@23520n2.bin,0 -23536d0.bin=@23536d0.bin,0 -23536d1.bin=@23536d1.bin,0 -23536d2.bin=@23536d2.bin,0 -23536n0.bin=@23536n0.bin,0 -23536n1.bin=@23536n1.bin,0 -23536n2.bin=@23536n2.bin,0 -23540d0.bin=@23540d0.bin,0 -23540d1.bin=@23540d1.bin,0 -23540d2.bin=@23540d2.bin,0 -23540n0.bin=@23540n0.bin,0 -23540n1.bin=@23540n1.bin,0 -23540n2.bin=@23540n2.bin,0 -23606d0.bin=@23606d0.bin,0 -23606d1.bin=@23606d1.bin,0 -23606d2.bin=@23606d2.bin,0 -23606n0.bin=@23606n0.bin,0 -23606n1.bin=@23606n1.bin,0 -23606n2.bin=@23606n2.bin,0 -23610d0.bin=@23610d0.bin,0 -23610d1.bin=@23610d1.bin,0 -23610d2.bin=@23610d2.bin,0 -23610n0.bin=@23610n0.bin,0 -23610n1.bin=@23610n1.bin,0 -23610n2.bin=@23610n2.bin,0 -23614d0.bin=@23614d0.bin,0 -23614d1.bin=@23614d1.bin,0 -23614d2.bin=@23614d2.bin,0 -23614n0.bin=@23614n0.bin,0 -23614n1.bin=@23614n1.bin,0 -23614n2.bin=@23614n2.bin,0 -23618d0.bin=@23618d0.bin,0 -23618d1.bin=@23618d1.bin,0 -23618d2.bin=@23618d2.bin,0 -23618n0.bin=@23618n0.bin,0 -23618n1.bin=@23618n1.bin,0 -23618n2.bin=@23618n2.bin,0 -23622d0.bin=@23622d0.bin,0 -23622d1.bin=@23622d1.bin,0 -23622d2.bin=@23622d2.bin,0 -23622n0.bin=@23622n0.bin,0 -23622n1.bin=@23622n1.bin,0 -23622n2.bin=@23622n2.bin,0 -23644d0.bin=@23644d0.bin,0 -23644d1.bin=@23644d1.bin,0 -23644d2.bin=@23644d2.bin,0 -23644n0.bin=@23644n0.bin,0 -23644n1.bin=@23644n1.bin,0 -23644n2.bin=@23644n2.bin,0 -23655d0.bin=@23655d0.bin,0 -23655d1.bin=@23655d1.bin,0 -23655d2.bin=@23655d2.bin,0 -23655n0.bin=@23655n0.bin,0 -23655n1.bin=@23655n1.bin,0 -23655n2.bin=@23655n2.bin,0 -23659d0.bin=@23659d0.bin,0 -23659d1.bin=@23659d1.bin,0 -23659d2.bin=@23659d2.bin,0 -23659n0.bin=@23659n0.bin,0 -23659n1.bin=@23659n1.bin,0 -23659n2.bin=@23659n2.bin,0 -23668d0.bin=@23668d0.bin,0 -23668d1.bin=@23668d1.bin,0 -23668d2.bin=@23668d2.bin,0 -23668n0.bin=@23668n0.bin,0 -23668n1.bin=@23668n1.bin,0 -23668n2.bin=@23668n2.bin,0 -23705d0.bin=@23705d0.bin,0 -23705d1.bin=@23705d1.bin,0 -23705d2.bin=@23705d2.bin,0 -23705n0.bin=@23705n0.bin,0 -23705n1.bin=@23705n1.bin,0 -23705n2.bin=@23705n2.bin,0 -23709d0.bin=@23709d0.bin,0 -23709d1.bin=@23709d1.bin,0 -23709d2.bin=@23709d2.bin,0 -23709n0.bin=@23709n0.bin,0 -23709n1.bin=@23709n1.bin,0 -23709n2.bin=@23709n2.bin,0 -23713d0.bin=@23713d0.bin,0 -23713d1.bin=@23713d1.bin,0 -23713d2.bin=@23713d2.bin,0 -23713n0.bin=@23713n0.bin,0 -23713n1.bin=@23713n1.bin,0 -23713n2.bin=@23713n2.bin,0 -23718d0.bin=@23718d0.bin,0 -23718d1.bin=@23718d1.bin,0 -23718d2.bin=@23718d2.bin,0 -23718n0.bin=@23718n0.bin,0 -23718n1.bin=@23718n1.bin,0 -23718n2.bin=@23718n2.bin,0 -40236d0.bin=@40236d0.bin,0 -40236d1.bin=@40236d1.bin,0 -40236d2.bin=@40236d2.bin,0 -40236n0.bin=@40236n0.bin,0 -40236n1.bin=@40236n1.bin,0 -40236n2.bin=@40236n2.bin,0 -55691d0.bin=@55691d0.bin,0 -55691d1.bin=@55691d1.bin,0 -55691d2.bin=@55691d2.bin,0 -55691n0.bin=@55691n0.bin,0 -55691n1.bin=@55691n1.bin,0 -55691n2.bin=@55691n2.bin,0 -55692d0.bin=@55692d0.bin,0 -55692d1.bin=@55692d1.bin,0 -55692d2.bin=@55692d2.bin,0 -55692n0.bin=@55692n0.bin,0 -55692n1.bin=@55692n1.bin,0 -55692n2.bin=@55692n2.bin,0 -55693d0.bin=@55693d0.bin,0 -55693d1.bin=@55693d1.bin,0 -55693d2.bin=@55693d2.bin,0 -55693n0.bin=@55693n0.bin,0 -55693n1.bin=@55693n1.bin,0 -55693n2.bin=@55693n2.bin,0 -55694d0.bin=@55694d0.bin,0 -55694d1.bin=@55694d1.bin,0 -55694d2.bin=@55694d2.bin,0 -55694n0.bin=@55694n0.bin,0 -55694n1.bin=@55694n1.bin,0 -55694n2.bin=@55694n2.bin,0 -55695d0.bin=@55695d0.bin,0 -55695d1.bin=@55695d1.bin,0 -55695d2.bin=@55695d2.bin,0 -55695n0.bin=@55695n0.bin,0 -55695n1.bin=@55695n1.bin,0 -55695n2.bin=@55695n2.bin,0 -55696d0.bin=@55696d0.bin,0 -55696d1.bin=@55696d1.bin,0 -55696d2.bin=@55696d2.bin,0 -55696n0.bin=@55696n0.bin,0 -55696n1.bin=@55696n1.bin,0 -55696n2.bin=@55696n2.bin,0 -55697d0.bin=@55697d0.bin,0 -55697d1.bin=@55697d1.bin,0 -55697d2.bin=@55697d2.bin,0 -55697n0.bin=@55697n0.bin,0 -55697n1.bin=@55697n1.bin,0 -55697n2.bin=@55697n2.bin,0 -55698d0.bin=@55698d0.bin,0 -55698d1.bin=@55698d1.bin,0 -55698d2.bin=@55698d2.bin,0 -55698n0.bin=@55698n0.bin,0 -55698n1.bin=@55698n1.bin,0 -55698n2.bin=@55698n2.bin,0 -55728d0.bin=@55728d0.bin,0 -55728d1.bin=@55728d1.bin,0 -55728d2.bin=@55728d2.bin,0 -55728n0.bin=@55728n0.bin,0 -55728n1.bin=@55728n1.bin,0 -55728n2.bin=@55728n2.bin,0 -55738d0.bin=@55738d0.bin,0 -55738d1.bin=@55738d1.bin,0 -55738d2.bin=@55738d2.bin,0 -55738n0.bin=@55738n0.bin,0 -55738n1.bin=@55738n1.bin,0 -55738n2.bin=@55738n2.bin,0 -55923d0.bin=@55923d0.bin,0 -55923d1.bin=@55923d1.bin,0 -55923d2.bin=@55923d2.bin,0 -55923n0.bin=@55923n0.bin,0 -55923n1.bin=@55923n1.bin,0 -55923n2.bin=@55923n2.bin,0 -55929d0.bin=@55929d0.bin,0 -55929d1.bin=@55929d1.bin,0 -55929d2.bin=@55929d2.bin,0 -55929n0.bin=@55929n0.bin,0 -55929n1.bin=@55929n1.bin,0 -55929n2.bin=@55929n2.bin,0 -56076d0.bin=@56076d0.bin,0 -56076d1.bin=@56076d1.bin,0 -56076d2.bin=@56076d2.bin,0 -56076n0.bin=@56076n0.bin,0 -56076n1.bin=@56076n1.bin,0 -56076n2.bin=@56076n2.bin,0 -56077d0.bin=@56077d0.bin,0 -56077d1.bin=@56077d1.bin,0 -56077d2.bin=@56077d2.bin,0 -56077n0.bin=@56077n0.bin,0 -56077n1.bin=@56077n1.bin,0 -56077n2.bin=@56077n2.bin,0 -56078d0.bin=@56078d0.bin,0 -56078d1.bin=@56078d1.bin,0 -56078d2.bin=@56078d2.bin,0 -56078n0.bin=@56078n0.bin,0 -56078n1.bin=@56078n1.bin,0 -56078n2.bin=@56078n2.bin,0 -56079d0.bin=@56079d0.bin,0 -56079d1.bin=@56079d1.bin,0 -56079d2.bin=@56079d2.bin,0 -56079n0.bin=@56079n0.bin,0 -56079n1.bin=@56079n1.bin,0 -56079n2.bin=@56079n2.bin,0 -56080d0.bin=@56080d0.bin,0 -56080d1.bin=@56080d1.bin,0 -56080d2.bin=@56080d2.bin,0 -56080n0.bin=@56080n0.bin,0 -56080n1.bin=@56080n1.bin,0 -56080n2.bin=@56080n2.bin,0 -56125d0.bin=@56125d0.bin,0 -56125d1.bin=@56125d1.bin,0 -56125d2.bin=@56125d2.bin,0 -56125n0.bin=@56125n0.bin,0 -56125n1.bin=@56125n1.bin,0 -56125n2.bin=@56125n2.bin,0 -56144d0.bin=@56144d0.bin,0 -56144d1.bin=@56144d1.bin,0 -56144d2.bin=@56144d2.bin,0 -56144n0.bin=@56144n0.bin,0 -56144n1.bin=@56144n1.bin,0 -56144n2.bin=@56144n2.bin,0 -56145d0.bin=@56145d0.bin,0 -56145d1.bin=@56145d1.bin,0 -56145d2.bin=@56145d2.bin,0 -56145n0.bin=@56145n0.bin,0 -56145n1.bin=@56145n1.bin,0 -56145n2.bin=@56145n2.bin,0 -56150d0.bin=@56150d0.bin,0 -56150d1.bin=@56150d1.bin,0 -56150d2.bin=@56150d2.bin,0 -56150n0.bin=@56150n0.bin,0 -56150n1.bin=@56150n1.bin,0 -56150n2.bin=@56150n2.bin,0 -56151d0.bin=@56151d0.bin,0 -56151d1.bin=@56151d1.bin,0 -56151d2.bin=@56151d2.bin,0 -56151n0.bin=@56151n0.bin,0 -56151n1.bin=@56151n1.bin,0 -56151n2.bin=@56151n2.bin,0 -56154d0.bin=@56154d0.bin,0 -56154d1.bin=@56154d1.bin,0 -56154d2.bin=@56154d2.bin,0 -56154n0.bin=@56154n0.bin,0 -56154n1.bin=@56154n1.bin,0 -56154n2.bin=@56154n2.bin,0 -56155d0.bin=@56155d0.bin,0 -56155d1.bin=@56155d1.bin,0 -56155d2.bin=@56155d2.bin,0 -56155n0.bin=@56155n0.bin,0 -56155n1.bin=@56155n1.bin,0 -56155n2.bin=@56155n2.bin,0 -56156d0.bin=@56156d0.bin,0 -56156d1.bin=@56156d1.bin,0 -56156d2.bin=@56156d2.bin,0 -56156n0.bin=@56156n0.bin,0 -56156n1.bin=@56156n1.bin,0 -56156n2.bin=@56156n2.bin,0 -23468d0.bin=@23468d0.bin,0 -23468d1.bin=@23468d1.bin,0 -23468d2.bin=@23468d2.bin,0 -23469n2.bin=@23469n2.bin,0 -23473d0.bin=@23473d0.bin,0 -23473d1.bin=@23473d1.bin,0 -23473d2.bin=@23473d2.bin,0 -23473n0.bin=@23473n0.bin,0 -23473n1.bin=@23473n1.bin,0 -23473n2.bin=@23473n2.bin,0 -23477d0.bin=@23477d0.bin,0 -23477d1.bin=@23477d1.bin,0 -23477d2.bin=@23477d2.bin,0 -23477n0.bin=@23477n0.bin,0 -23477n1.bin=@23477n1.bin,0 -23477n2.bin=@23477n2.bin,0 -23481d0.bin=@23481d0.bin,0 -23481d1.bin=@23481d1.bin,0 -23481d2.bin=@23481d2.bin,0 -23481n0.bin=@23481n0.bin,0 -23481n1.bin=@23481n1.bin,0 -23481n2.bin=@23481n2.bin,0 -23517d0.bin=@23517d0.bin,0 -23517d1.bin=@23517d1.bin,0 -23517d2.bin=@23517d2.bin,0 -23517n0.bin=@23517n0.bin,0 -23517n1.bin=@23517n1.bin,0 -23517n2.bin=@23517n2.bin,0 -23521d0.bin=@23521d0.bin,0 -23521d1.bin=@23521d1.bin,0 -23521d2.bin=@23521d2.bin,0 -23521n0.bin=@23521n0.bin,0 -23521n1.bin=@23521n1.bin,0 -23521n2.bin=@23521n2.bin,0 -23537d0.bin=@23537d0.bin,0 -23537d1.bin=@23537d1.bin,0 -23537d2.bin=@23537d2.bin,0 -23537n0.bin=@23537n0.bin,0 -23537n1.bin=@23537n1.bin,0 -23537n2.bin=@23537n2.bin,0 -23541d0.bin=@23541d0.bin,0 -23541d1.bin=@23541d1.bin,0 -23541d2.bin=@23541d2.bin,0 -23541n0.bin=@23541n0.bin,0 -23541n1.bin=@23541n1.bin,0 -23541n2.bin=@23541n2.bin,0 -23607d0.bin=@23607d0.bin,0 -23607d1.bin=@23607d1.bin,0 -23607d2.bin=@23607d2.bin,0 -23607n0.bin=@23607n0.bin,0 -23607n1.bin=@23607n1.bin,0 -23607n2.bin=@23607n2.bin,0 -23611d0.bin=@23611d0.bin,0 -23611d1.bin=@23611d1.bin,0 -23611d2.bin=@23611d2.bin,0 -23611n0.bin=@23611n0.bin,0 -23611n1.bin=@23611n1.bin,0 -23611n2.bin=@23611n2.bin,0 -23615d0.bin=@23615d0.bin,0 -23615d1.bin=@23615d1.bin,0 -23615d2.bin=@23615d2.bin,0 -23615n0.bin=@23615n0.bin,0 -23615n1.bin=@23615n1.bin,0 -23615n2.bin=@23615n2.bin,0 -23619d0.bin=@23619d0.bin,0 -23619d1.bin=@23619d1.bin,0 -23619d2.bin=@23619d2.bin,0 -23619n0.bin=@23619n0.bin,0 -23619n1.bin=@23619n1.bin,0 -23619n2.bin=@23619n2.bin,0 -23623d0.bin=@23623d0.bin,0 -23623d1.bin=@23623d1.bin,0 -23623d2.bin=@23623d2.bin,0 -23623n0.bin=@23623n0.bin,0 -23623n1.bin=@23623n1.bin,0 -23623n2.bin=@23623n2.bin,0 -23645d0.bin=@23645d0.bin,0 -23645d1.bin=@23645d1.bin,0 -23645d2.bin=@23645d2.bin,0 -23645n0.bin=@23645n0.bin,0 -23645n1.bin=@23645n1.bin,0 -23645n2.bin=@23645n2.bin,0 -23656d0.bin=@23656d0.bin,0 -23656d1.bin=@23656d1.bin,0 -23656d2.bin=@23656d2.bin,0 -23656n0.bin=@23656n0.bin,0 -23656n1.bin=@23656n1.bin,0 -23656n2.bin=@23656n2.bin,0 -23660d0.bin=@23660d0.bin,0 -23660d1.bin=@23660d1.bin,0 -23660d2.bin=@23660d2.bin,0 -23660n0.bin=@23660n0.bin,0 -23660n1.bin=@23660n1.bin,0 -23660n2.bin=@23660n2.bin,0 -23669d0.bin=@23669d0.bin,0 -23669d1.bin=@23669d1.bin,0 -23669d2.bin=@23669d2.bin,0 -23669n0.bin=@23669n0.bin,0 -23669n1.bin=@23669n1.bin,0 -23669n2.bin=@23669n2.bin,0 -23706d0.bin=@23706d0.bin,0 -23706d1.bin=@23706d1.bin,0 -23706d2.bin=@23706d2.bin,0 -23706n0.bin=@23706n0.bin,0 -23706n1.bin=@23706n1.bin,0 -23706n2.bin=@23706n2.bin,0 -23710d0.bin=@23710d0.bin,0 -23710d1.bin=@23710d1.bin,0 -23710d2.bin=@23710d2.bin,0 -23710n0.bin=@23710n0.bin,0 -23710n1.bin=@23710n1.bin,0 -23710n2.bin=@23710n2.bin,0 -23714d0.bin=@23714d0.bin,0 -23714d1.bin=@23714d1.bin,0 -23714d2.bin=@23714d2.bin,0 -23714n0.bin=@23714n0.bin,0 -23714n1.bin=@23714n1.bin,0 -23714n2.bin=@23714n2.bin,0 -23719d0.bin=@23719d0.bin,0 -23719d1.bin=@23719d1.bin,0 -23719d2.bin=@23719d2.bin,0 -23719n0.bin=@23719n0.bin,0 -23719n1.bin=@23719n1.bin,0 -23719n2.bin=@23719n2.bin,0 -55513d0.bin=@55513d0.bin,0 -55513d1.bin=@55513d1.bin,0 -55513d2.bin=@55513d2.bin,0 -55513n0.bin=@55513n0.bin,0 -55513n1.bin=@55513n1.bin,0 -55513n2.bin=@55513n2.bin,0 -55924d0.bin=@55924d0.bin,0 -55924d1.bin=@55924d1.bin,0 -55924d2.bin=@55924d2.bin,0 -55924n0.bin=@55924n0.bin,0 -55924n1.bin=@55924n1.bin,0 -55924n2.bin=@55924n2.bin,0 -55930d0.bin=@55930d0.bin,0 -55930d1.bin=@55930d1.bin,0 -55930d2.bin=@55930d2.bin,0 -55930n0.bin=@55930n0.bin,0 -55930n1.bin=@55930n1.bin,0 -55930n2.bin=@55930n2.bin,0 -23469d0.bin=@23469d0.bin,0 -23469d1.bin=@23469d1.bin,0 -23469d2.bin=@23469d2.bin,0 -23469n0.bin=@23469n0.bin,0 -23469n1.bin=@23469n1.bin,0 -23470n2.bin=@23470n2.bin,0 -23474d0.bin=@23474d0.bin,0 -23474d1.bin=@23474d1.bin,0 -23474d2.bin=@23474d2.bin,0 -23474n0.bin=@23474n0.bin,0 -23474n1.bin=@23474n1.bin,0 -23474n2.bin=@23474n2.bin,0 -23478d0.bin=@23478d0.bin,0 -23478d1.bin=@23478d1.bin,0 -23478d2.bin=@23478d2.bin,0 -23478n0.bin=@23478n0.bin,0 -23478n1.bin=@23478n1.bin,0 -23478n2.bin=@23478n2.bin,0 -23482d0.bin=@23482d0.bin,0 -23482d1.bin=@23482d1.bin,0 -23482d2.bin=@23482d2.bin,0 -23482n0.bin=@23482n0.bin,0 -23482n1.bin=@23482n1.bin,0 -23482n2.bin=@23482n2.bin,0 -23518d0.bin=@23518d0.bin,0 -23518d1.bin=@23518d1.bin,0 -23518d2.bin=@23518d2.bin,0 -23518n0.bin=@23518n0.bin,0 -23518n1.bin=@23518n1.bin,0 -23518n2.bin=@23518n2.bin,0 -23522d0.bin=@23522d0.bin,0 -23522d1.bin=@23522d1.bin,0 -23522d2.bin=@23522d2.bin,0 -23522n0.bin=@23522n0.bin,0 -23522n1.bin=@23522n1.bin,0 -23522n2.bin=@23522n2.bin,0 -23538d0.bin=@23538d0.bin,0 -23538d1.bin=@23538d1.bin,0 -23538d2.bin=@23538d2.bin,0 -23538n0.bin=@23538n0.bin,0 -23538n1.bin=@23538n1.bin,0 -23538n2.bin=@23538n2.bin,0 -23542d0.bin=@23542d0.bin,0 -23542d1.bin=@23542d1.bin,0 -23542d2.bin=@23542d2.bin,0 -23542n0.bin=@23542n0.bin,0 -23542n1.bin=@23542n1.bin,0 -23542n2.bin=@23542n2.bin,0 -23608d0.bin=@23608d0.bin,0 -23608d1.bin=@23608d1.bin,0 -23608d2.bin=@23608d2.bin,0 -23608n0.bin=@23608n0.bin,0 -23608n1.bin=@23608n1.bin,0 -23608n2.bin=@23608n2.bin,0 -23612d0.bin=@23612d0.bin,0 -23612d1.bin=@23612d1.bin,0 -23612d2.bin=@23612d2.bin,0 -23612n0.bin=@23612n0.bin,0 -23612n1.bin=@23612n1.bin,0 -23612n2.bin=@23612n2.bin,0 -23616d0.bin=@23616d0.bin,0 -23616d1.bin=@23616d1.bin,0 -23616d2.bin=@23616d2.bin,0 -23616n0.bin=@23616n0.bin,0 -23616n1.bin=@23616n1.bin,0 -23616n2.bin=@23616n2.bin,0 -23620d0.bin=@23620d0.bin,0 -23620d1.bin=@23620d1.bin,0 -23620d2.bin=@23620d2.bin,0 -23620n0.bin=@23620n0.bin,0 -23620n1.bin=@23620n1.bin,0 -23620n2.bin=@23620n2.bin,0 -23624d0.bin=@23624d0.bin,0 -23624d1.bin=@23624d1.bin,0 -23624d2.bin=@23624d2.bin,0 -23624n0.bin=@23624n0.bin,0 -23624n1.bin=@23624n1.bin,0 -23624n2.bin=@23624n2.bin,0 -23646d0.bin=@23646d0.bin,0 -23646d1.bin=@23646d1.bin,0 -23646d2.bin=@23646d2.bin,0 -23646n0.bin=@23646n0.bin,0 -23646n1.bin=@23646n1.bin,0 -23646n2.bin=@23646n2.bin,0 -23657d0.bin=@23657d0.bin,0 -23657d1.bin=@23657d1.bin,0 -23657d2.bin=@23657d2.bin,0 -23657n0.bin=@23657n0.bin,0 -23657n1.bin=@23657n1.bin,0 -23657n2.bin=@23657n2.bin,0 -23661d0.bin=@23661d0.bin,0 -23661d1.bin=@23661d1.bin,0 -23661d2.bin=@23661d2.bin,0 -23661n0.bin=@23661n0.bin,0 -23661n1.bin=@23661n1.bin,0 -23661n2.bin=@23661n2.bin,0 -23670d0.bin=@23670d0.bin,0 -23670d1.bin=@23670d1.bin,0 -23670d2.bin=@23670d2.bin,0 -23670n0.bin=@23670n0.bin,0 -23670n1.bin=@23670n1.bin,0 -23670n2.bin=@23670n2.bin,0 -23707d0.bin=@23707d0.bin,0 -23707d1.bin=@23707d1.bin,0 -23707d2.bin=@23707d2.bin,0 -23707n0.bin=@23707n0.bin,0 -23707n1.bin=@23707n1.bin,0 -23707n2.bin=@23707n2.bin,0 -23711d0.bin=@23711d0.bin,0 -23711d1.bin=@23711d1.bin,0 -23711d2.bin=@23711d2.bin,0 -23711n0.bin=@23711n0.bin,0 -23711n1.bin=@23711n1.bin,0 -23711n2.bin=@23711n2.bin,0 -23715d0.bin=@23715d0.bin,0 -23715d1.bin=@23715d1.bin,0 -23715d2.bin=@23715d2.bin,0 -23715n0.bin=@23715n0.bin,0 -23715n1.bin=@23715n1.bin,0 -23715n2.bin=@23715n2.bin,0 -23720d0.bin=@23720d0.bin,0 -23720d1.bin=@23720d1.bin,0 -23720d2.bin=@23720d2.bin,0 -23720n0.bin=@23720n0.bin,0 -23720n1.bin=@23720n1.bin,0 -23720n2.bin=@23720n2.bin,0 -55925d0.bin=@55925d0.bin,0 -55925d1.bin=@55925d1.bin,0 -55925d2.bin=@55925d2.bin,0 -55925n0.bin=@55925n0.bin,0 -55925n1.bin=@55925n1.bin,0 -55925n2.bin=@55925n2.bin,0 -55931d0.bin=@55931d0.bin,0 -55931d1.bin=@55931d1.bin,0 -55931d2.bin=@55931d2.bin,0 -55931n0.bin=@55931n0.bin,0 -55931n1.bin=@55931n1.bin,0 -55931n2.bin=@55931n2.bin,0 -56042d0.bin=@56042d0.bin,0 -56042d1.bin=@56042d1.bin,0 -56042d2.bin=@56042d2.bin,0 -56042n0.bin=@56042n0.bin,0 -56042n1.bin=@56042n1.bin,0 -56042n2.bin=@56042n2.bin,0 -56063d0.bin=@56063d0.bin,0 -56063d1.bin=@56063d1.bin,0 -56063d2.bin=@56063d2.bin,0 -56063n0.bin=@56063n0.bin,0 -56063n1.bin=@56063n1.bin,0 -56063n2.bin=@56063n2.bin,0 -56064d0.bin=@56064d0.bin,0 -56064d1.bin=@56064d1.bin,0 -56064d2.bin=@56064d2.bin,0 -56064n0.bin=@56064n0.bin,0 -56064n1.bin=@56064n1.bin,0 -56064n2.bin=@56064n2.bin,0 -23470d0.bin=@23470d0.bin,0 -23470d1.bin=@23470d1.bin,0 -23470d2.bin=@23470d2.bin,0 -23470n0.bin=@23470n0.bin,0 -23470n1.bin=@23470n1.bin,0 -23475d0.bin=@23475d0.bin,0 -23475d1.bin=@23475d1.bin,0 -23475d2.bin=@23475d2.bin,0 -23475n0.bin=@23475n0.bin,0 -23475n1.bin=@23475n1.bin,0 -23475n2.bin=@23475n2.bin,0 -23479d0.bin=@23479d0.bin,0 -23479d1.bin=@23479d1.bin,0 -23479d2.bin=@23479d2.bin,0 -23479n0.bin=@23479n0.bin,0 -23479n1.bin=@23479n1.bin,0 -23479n2.bin=@23479n2.bin,0 -23483d0.bin=@23483d0.bin,0 -23483d1.bin=@23483d1.bin,0 -23483d2.bin=@23483d2.bin,0 -23483n0.bin=@23483n0.bin,0 -23483n1.bin=@23483n1.bin,0 -23483n2.bin=@23483n2.bin,0 -23519d0.bin=@23519d0.bin,0 -23519d1.bin=@23519d1.bin,0 -23519d2.bin=@23519d2.bin,0 -23519n0.bin=@23519n0.bin,0 -23519n1.bin=@23519n1.bin,0 -23519n2.bin=@23519n2.bin,0 -23523d0.bin=@23523d0.bin,0 -23523d1.bin=@23523d1.bin,0 -23523d2.bin=@23523d2.bin,0 -23523n0.bin=@23523n0.bin,0 -23523n1.bin=@23523n1.bin,0 -23523n2.bin=@23523n2.bin,0 -23539d0.bin=@23539d0.bin,0 -23539d1.bin=@23539d1.bin,0 -23539d2.bin=@23539d2.bin,0 -23539n0.bin=@23539n0.bin,0 -23539n1.bin=@23539n1.bin,0 -23539n2.bin=@23539n2.bin,0 -23543d0.bin=@23543d0.bin,0 -23543d1.bin=@23543d1.bin,0 -23543d2.bin=@23543d2.bin,0 -23543n0.bin=@23543n0.bin,0 -23543n1.bin=@23543n1.bin,0 -23543n2.bin=@23543n2.bin,0 -23609d0.bin=@23609d0.bin,0 -23609d1.bin=@23609d1.bin,0 -23609d2.bin=@23609d2.bin,0 -23609n0.bin=@23609n0.bin,0 -23609n1.bin=@23609n1.bin,0 -23609n2.bin=@23609n2.bin,0 -23613d0.bin=@23613d0.bin,0 -23613d1.bin=@23613d1.bin,0 -23613d2.bin=@23613d2.bin,0 -23613n0.bin=@23613n0.bin,0 -23613n1.bin=@23613n1.bin,0 -23613n2.bin=@23613n2.bin,0 -23617d0.bin=@23617d0.bin,0 -23617d1.bin=@23617d1.bin,0 -23617d2.bin=@23617d2.bin,0 -23617n0.bin=@23617n0.bin,0 -23617n1.bin=@23617n1.bin,0 -23617n2.bin=@23617n2.bin,0 -23621d0.bin=@23621d0.bin,0 -23621d1.bin=@23621d1.bin,0 -23621d2.bin=@23621d2.bin,0 -23621n0.bin=@23621n0.bin,0 -23621n1.bin=@23621n1.bin,0 -23621n2.bin=@23621n2.bin,0 -23625d0.bin=@23625d0.bin,0 -23625d1.bin=@23625d1.bin,0 -23625d2.bin=@23625d2.bin,0 -23625n0.bin=@23625n0.bin,0 -23625n1.bin=@23625n1.bin,0 -23625n2.bin=@23625n2.bin,0 -23647d0.bin=@23647d0.bin,0 -23647d1.bin=@23647d1.bin,0 -23647d2.bin=@23647d2.bin,0 -23647n0.bin=@23647n0.bin,0 -23647n1.bin=@23647n1.bin,0 -23647n2.bin=@23647n2.bin,0 -23658d0.bin=@23658d0.bin,0 -23658d1.bin=@23658d1.bin,0 -23658d2.bin=@23658d2.bin,0 -23658n0.bin=@23658n0.bin,0 -23658n1.bin=@23658n1.bin,0 -23658n2.bin=@23658n2.bin,0 -23662d0.bin=@23662d0.bin,0 -23662d1.bin=@23662d1.bin,0 -23662d2.bin=@23662d2.bin,0 -23662n0.bin=@23662n0.bin,0 -23662n1.bin=@23662n1.bin,0 -23662n2.bin=@23662n2.bin,0 -23671d0.bin=@23671d0.bin,0 -23671d1.bin=@23671d1.bin,0 -23671d2.bin=@23671d2.bin,0 -23671n0.bin=@23671n0.bin,0 -23671n1.bin=@23671n1.bin,0 -23671n2.bin=@23671n2.bin,0 -23708d0.bin=@23708d0.bin,0 -23708d1.bin=@23708d1.bin,0 -23708d2.bin=@23708d2.bin,0 -23708n0.bin=@23708n0.bin,0 -23708n1.bin=@23708n1.bin,0 -23708n2.bin=@23708n2.bin,0 -23712d0.bin=@23712d0.bin,0 -23712d1.bin=@23712d1.bin,0 -23712d2.bin=@23712d2.bin,0 -23712n0.bin=@23712n0.bin,0 -23712n1.bin=@23712n1.bin,0 -23712n2.bin=@23712n2.bin,0 -23716d0.bin=@23716d0.bin,0 -23716d1.bin=@23716d1.bin,0 -23716d2.bin=@23716d2.bin,0 -23716n0.bin=@23716n0.bin,0 -23716n1.bin=@23716n1.bin,0 -23716n2.bin=@23716n2.bin,0 -23721d0.bin=@23721d0.bin,0 -23721d1.bin=@23721d1.bin,0 -23721d2.bin=@23721d2.bin,0 -23721n0.bin=@23721n0.bin,0 -23721n1.bin=@23721n1.bin,0 -23721n2.bin=@23721n2.bin,0 -55926d0.bin=@55926d0.bin,0 -55926d1.bin=@55926d1.bin,0 -55926d2.bin=@55926d2.bin,0 -55926n0.bin=@55926n0.bin,0 -55926n1.bin=@55926n1.bin,0 -55926n2.bin=@55926n2.bin,0 -55932d0.bin=@55932d0.bin,0 -55932d1.bin=@55932d1.bin,0 -55932d2.bin=@55932d2.bin,0 -55932n0.bin=@55932n0.bin,0 -55932n1.bin=@55932n1.bin,0 -55932n2.bin=@55932n2.bin,0 -23471d0.bin=@23471d0.bin,0 -23471d1.bin=@23471d1.bin,0 -23471d2.bin=@23471d2.bin,0 -23471n0.bin=@23471n0.bin,0 -23471n1.bin=@23471n1.bin,0 -23471n2.bin=@23471n2.bin,0 -21731n0.bin=@21731n0.bin,0 -21731n1.bin=@21731n1.bin,0 -21731n2.bin=@21731n2.bin,0 -21746d0.bin=@21746d0.bin,0 -21746d1.bin=@21746d1.bin,0 -21746d2.bin=@21746d2.bin,0 -21746n0.bin=@21746n0.bin,0 -21746n1.bin=@21746n1.bin,0 -21746n2.bin=@21746n2.bin,0 -21731d0.bin=@21731d0.bin,0 -21731d1.bin=@21731d1.bin,0 -21731d2.bin=@21731d2.bin,0 -54633d0.bin=@54633d0.bin,0 -54633d1.bin=@54633d1.bin,0 -54633d2.bin=@54633d2.bin,0 -54633n0.bin=@54633n0.bin,0 -54633n1.bin=@54633n1.bin,0 -54633n2.bin=@54633n2.bin,0 -54801d0.bin=@54801d0.bin,0 -54801d1.bin=@54801d1.bin,0 -54801d2.bin=@54801d2.bin,0 -54801n0.bin=@54801n0.bin,0 -54801n1.bin=@54801n1.bin,0 -54801n2.bin=@54801n2.bin,0 -55203d0.bin=@55203d0.bin,0 -55203d1.bin=@55203d1.bin,0 -55203d2.bin=@55203d2.bin,0 -55203n0.bin=@55203n0.bin,0 -55203n1.bin=@55203n1.bin,0 -55203n2.bin=@55203n2.bin,0 -55204d0.bin=@55204d0.bin,0 -55204d1.bin=@55204d1.bin,0 -55204d2.bin=@55204d2.bin,0 -55204n0.bin=@55204n0.bin,0 -55204n1.bin=@55204n1.bin,0 -55204n2.bin=@55204n2.bin,0 -55464d0.bin=@55464d0.bin,0 -55464d1.bin=@55464d1.bin,0 -55464d2.bin=@55464d2.bin,0 -55464n0.bin=@55464n0.bin,0 -55464n1.bin=@55464n1.bin,0 -55464n2.bin=@55464n2.bin,0 -55619d0.bin=@55619d0.bin,0 -55619d1.bin=@55619d1.bin,0 -55619d2.bin=@55619d2.bin,0 -55619n0.bin=@55619n0.bin,0 -55619n1.bin=@55619n1.bin,0 -55619n2.bin=@55619n2.bin,0 -55730d0.bin=@55730d0.bin,0 -55730d1.bin=@55730d1.bin,0 -55730d2.bin=@55730d2.bin,0 -55730n0.bin=@55730n0.bin,0 -55730n1.bin=@55730n1.bin,0 -55730n2.bin=@55730n2.bin,0 -55771d0.bin=@55771d0.bin,0 -55771d1.bin=@55771d1.bin,0 -55771d2.bin=@55771d2.bin,0 -55771n0.bin=@55771n0.bin,0 -55771n1.bin=@55771n1.bin,0 -55771n2.bin=@55771n2.bin,0 -55896d0.bin=@55896d0.bin,0 -55896d1.bin=@55896d1.bin,0 -55896d2.bin=@55896d2.bin,0 -55896n0.bin=@55896n0.bin,0 -55896n1.bin=@55896n1.bin,0 -55896n2.bin=@55896n2.bin,0 -55937d0.bin=@55937d0.bin,0 -55937d1.bin=@55937d1.bin,0 -55937d2.bin=@55937d2.bin,0 -55937n0.bin=@55937n0.bin,0 -55937n1.bin=@55937n1.bin,0 -55937n2.bin=@55937n2.bin,0 -55938d0.bin=@55938d0.bin,0 -55938d1.bin=@55938d1.bin,0 -55938d2.bin=@55938d2.bin,0 -55938n0.bin=@55938n0.bin,0 -55938n1.bin=@55938n1.bin,0 -55938n2.bin=@55938n2.bin,0 -55939d0.bin=@55939d0.bin,0 -55939d1.bin=@55939d1.bin,0 -55939d2.bin=@55939d2.bin,0 -55939n0.bin=@55939n0.bin,0 -55939n1.bin=@55939n1.bin,0 -55939n2.bin=@55939n2.bin,0 -55964d0.bin=@55964d0.bin,0 -55964d1.bin=@55964d1.bin,0 -55964d2.bin=@55964d2.bin,0 -55964n0.bin=@55964n0.bin,0 -55964n1.bin=@55964n1.bin,0 -55964n2.bin=@55964n2.bin,0 -55967d0.bin=@55967d0.bin,0 -55967d1.bin=@55967d1.bin,0 -55967d2.bin=@55967d2.bin,0 -55967n0.bin=@55967n0.bin,0 -55967n1.bin=@55967n1.bin,0 -55967n2.bin=@55967n2.bin,0 -56109d0.bin=@56109d0.bin,0 -56109d1.bin=@56109d1.bin,0 -56109d2.bin=@56109d2.bin,0 -56109n0.bin=@56109n0.bin,0 -56109n1.bin=@56109n1.bin,0 -56109n2.bin=@56109n2.bin,0 -56110d0.bin=@56110d0.bin,0 -56110d1.bin=@56110d1.bin,0 -56110d2.bin=@56110d2.bin,0 -56110n0.bin=@56110n0.bin,0 -56110n1.bin=@56110n1.bin,0 -56110n2.bin=@56110n2.bin,0 -56143d0.bin=@56143d0.bin,0 -56143d1.bin=@56143d1.bin,0 -56143d2.bin=@56143d2.bin,0 -56143n0.bin=@56143n0.bin,0 -56143n1.bin=@56143n1.bin,0 -56143n2.bin=@56143n2.bin,0 -56147d0.bin=@56147d0.bin,0 -56147d1.bin=@56147d1.bin,0 -56147d2.bin=@56147d2.bin,0 -56147n0.bin=@56147n0.bin,0 -56147n1.bin=@56147n1.bin,0 -56147n2.bin=@56147n2.bin,0 -56157d0.bin=@56157d0.bin,0 -56157d1.bin=@56157d1.bin,0 -56157d2.bin=@56157d2.bin,0 -56157n0.bin=@56157n0.bin,0 -56157n1.bin=@56157n1.bin,0 -56157n2.bin=@56157n2.bin,0 -56159d0.bin=@56159d0.bin,0 -56159d1.bin=@56159d1.bin,0 -56159d2.bin=@56159d2.bin,0 -56159n0.bin=@56159n0.bin,0 -56159n1.bin=@56159n1.bin,0 -56159n2.bin=@56159n2.bin,0 -64552d0.bin=@64552d0.bin,0 -64552d1.bin=@64552d1.bin,0 -64552d2.bin=@64552d2.bin,0 -64555d0.bin=@64555d0.bin,0 -64555d1.bin=@64555d1.bin,0 -64555d2.bin=@64555d2.bin,0 -64556d0.bin=@64556d0.bin,0 -64556d1.bin=@64556d1.bin,0 -64556d2.bin=@64556d2.bin,0 -51122d0.bin=@51122d0.bin,0 -51122d1.bin=@51122d1.bin,0 -51122d2.bin=@51122d2.bin,0 -51122n0.bin=@51122n0.bin,0 -51122n1.bin=@51122n1.bin,0 -51122n2.bin=@51122n2.bin,0 -55732d0.bin=@55732d0.bin,0 -55732d1.bin=@55732d1.bin,0 -55732d2.bin=@55732d2.bin,0 -55732n0.bin=@55732n0.bin,0 -55732n1.bin=@55732n1.bin,0 -55732n2.bin=@55732n2.bin,0 -55731d0.bin=@55731d0.bin,0 -55731d1.bin=@55731d1.bin,0 -55731d2.bin=@55731d2.bin,0 -55731n0.bin=@55731n0.bin,0 -55731n1.bin=@55731n1.bin,0 -55731n2.bin=@55731n2.bin,0 -55818d0.bin=@55818d0.bin,0 -55818d1.bin=@55818d1.bin,0 -55818d2.bin=@55818d2.bin,0 -55818n0.bin=@55818n0.bin,0 -55818n1.bin=@55818n1.bin,0 -55818n2.bin=@55818n2.bin,0 -56112d0.bin=@56112d0.bin,0 -56112d1.bin=@56112d1.bin,0 -56112d2.bin=@56112d2.bin,0 -56112n0.bin=@56112n0.bin,0 -56112n1.bin=@56112n1.bin,0 -56112n2.bin=@56112n2.bin,0 -56105d0.bin=@56105d0.bin,0 -56105d1.bin=@56105d1.bin,0 -56105d2.bin=@56105d2.bin,0 -56105n0.bin=@56105n0.bin,0 -56105n1.bin=@56105n1.bin,0 -56105n2.bin=@56105n2.bin,0 -58007d2.bin=@58007d2.bin,0 -58008d2.bin=@58008d2.bin,0 -58009d2.bin=@58009d2.bin,0 -58010d2.bin=@58010d2.bin,0 -58129d0.bin=@58129d0.bin,0 -58130d0.bin=@58130d0.bin,0 -58139d0.bin=@58139d0.bin,0 From fd02a12ae9783be85a97aef8776215266a73c0fa Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 31 Dec 2023 10:22:27 +1100 Subject: [PATCH 211/269] add DefaultCourses DevModeOption --- common/mhfcourse/mhfcourse.go | 6 +++++- config.json | 1 + config/config.go | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/common/mhfcourse/mhfcourse.go b/common/mhfcourse/mhfcourse.go index f838e64a5..272d3e3d4 100644 --- a/common/mhfcourse/mhfcourse.go +++ b/common/mhfcourse/mhfcourse.go @@ -1,6 +1,7 @@ package mhfcourse import ( + _config "erupe-ce/config" "math" "sort" "time" @@ -66,7 +67,10 @@ func CourseExists(ID uint16, c []Course) bool { // GetCourseStruct returns a slice of Course(s) from a rights integer func GetCourseStruct(rights uint32) ([]Course, uint32) { - resp := []Course{{ID: 1}, {ID: 23}, {ID: 24}} + var resp []Course + for _, c := range _config.ErupeConfig.DevModeOptions.DefaultCourses { + resp = append(resp, Course{ID: c}) + } s := Courses() sort.Slice(s, func(i, j int) bool { return s[i].ID > s[j].ID diff --git a/config.json b/config.json index 1d5fe76d7..a099936bc 100644 --- a/config.json +++ b/config.json @@ -29,6 +29,7 @@ "TournamentEvent": 0, "DisableTokenCheck": false, "QuestDebugTools": false, + "DefaultCourses": [1, 23, 24], "EarthStatusOverride": 0, "EarthIDOverride": 0, "EarthMonsterOverride": [0, 0, 0, 0], diff --git a/config/config.go b/config/config.go index 4995c042d..7e87fd8b8 100644 --- a/config/config.go +++ b/config/config.go @@ -110,6 +110,7 @@ type DevModeOptions struct { TournamentEvent int // VS Tournament event status DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) QuestDebugTools bool // Enable various quest debug logs + DefaultCourses []uint16 EarthStatusOverride int32 EarthIDOverride int32 EarthMonsterOverride []int32 From 0069a5029f8d5af8d90a5049bbd4bb3e052c21aa Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 31 Dec 2023 11:42:44 +1100 Subject: [PATCH 212/269] decode Festa stuff --- server/channelserver/handlers_festa.go | 31 +++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 0ea67fee2..f92b8d3fc 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -309,8 +309,8 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { ps.Uint8(bf, "", true) // Guild Name } + bf.WriteUint32(0) // Clan goal // Final bonus rates - bf.WriteUint32(1) // 5000-Infinity? bf.WriteUint32(5000) // 5000+ souls bf.WriteUint32(2000) // 2000-4999 souls bf.WriteUint32(1000) // 1000-1999 souls @@ -349,7 +349,6 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) { bf.WriteBool(false) bf.WriteBool(true) } - bf.WriteUint16(0) // Unk doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } @@ -364,18 +363,18 @@ func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) { resp := byteframe.NewByteFrame() if err != nil || guild == nil || applicant { resp.WriteUint32(0) - resp.WriteUint32(0) - resp.WriteUint32(0xFFFFFFFF) - resp.WriteUint32(0) - resp.WriteUint32(0) + resp.WriteInt32(0) + resp.WriteInt32(-1) + resp.WriteInt32(0) + resp.WriteInt32(0) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) return } resp.WriteUint32(guild.Souls) - resp.WriteUint32(1) // unk - resp.WriteUint32(1) // unk - resp.WriteUint32(1) // unk, rank? - resp.WriteUint32(1) // unk + resp.WriteInt32(0) // unk + resp.WriteInt32(1) // unk, rank? + resp.WriteInt32(0) // unk + resp.WriteInt32(0) // unk doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } @@ -391,13 +390,19 @@ func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) return } - bf := byteframe.NewByteFrame() - bf.WriteUint16(uint16(len(members))) - bf.WriteUint16(0) // Unk sort.Slice(members, func(i, j int) bool { return members[i].Souls > members[j].Souls }) + var validMembers []*GuildMember for _, member := range members { + if member.Souls > 0 { + validMembers = append(validMembers, member) + } + } + bf := byteframe.NewByteFrame() + bf.WriteUint16(uint16(len(validMembers))) + bf.WriteUint16(0) // Unk + for _, member := range validMembers { bf.WriteUint32(member.CharID) bf.WriteUint32(member.Souls) } From 52082aaf06f2ed11b43cdea3934968f2d33c2fb6 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 31 Dec 2023 11:43:10 +1100 Subject: [PATCH 213/269] use correct GuildMember length --- server/channelserver/handlers_guild.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index ad5221281..43629e49a 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1427,7 +1427,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() - bf.WriteUint16(guild.MemberCount) + bf.WriteUint16(uint16(len(guildMembers))) sort.Slice(guildMembers[:], func(i, j int) bool { return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex @@ -1460,7 +1460,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { } if guild.AllianceID > 0 { - bf.WriteUint16(alliance.TotalMembers - guild.MemberCount) + bf.WriteUint16(alliance.TotalMembers - uint16(len(guildMembers))) if guild.ID != alliance.ParentGuildID { mems, err := GetGuildMembers(s, alliance.ParentGuildID, false) if err != nil { From 0ea0dc217b816706782a4f323dd573c835b10e17 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 31 Dec 2023 12:51:24 +1100 Subject: [PATCH 214/269] simplify config --- common/mhfcourse/mhfcourse.go | 2 +- config.json | 31 ++++----- config/config.go | 72 ++++++++++---------- main.go | 17 ++--- server/channelserver/handlers.go | 10 +-- server/channelserver/handlers_cast_binary.go | 2 +- server/channelserver/handlers_data.go | 8 +-- server/channelserver/handlers_diva.go | 6 +- server/channelserver/handlers_festa.go | 8 +-- server/channelserver/handlers_object.go | 2 +- server/channelserver/handlers_quest.go | 4 +- server/channelserver/handlers_tower.go | 6 +- server/channelserver/sys_session.go | 12 ++-- server/entranceserver/entrance_server.go | 2 +- server/entranceserver/make_resp.go | 8 +-- server/signserver/dbutils.go | 2 +- server/signserver/dsgn_resp.go | 26 +++---- server/signserver/session.go | 6 +- server/signv2server/endpoints.go | 8 +-- 19 files changed, 109 insertions(+), 123 deletions(-) diff --git a/common/mhfcourse/mhfcourse.go b/common/mhfcourse/mhfcourse.go index 272d3e3d4..332c684dc 100644 --- a/common/mhfcourse/mhfcourse.go +++ b/common/mhfcourse/mhfcourse.go @@ -68,7 +68,7 @@ func CourseExists(ID uint16, c []Course) bool { // GetCourseStruct returns a slice of Course(s) from a rights integer func GetCourseStruct(rights uint32) ([]Course, uint32) { var resp []Course - for _, c := range _config.ErupeConfig.DevModeOptions.DefaultCourses { + for _, c := range _config.ErupeConfig.DefaultCourses { resp = append(resp, Course{ID: c}) } s := Courses() diff --git a/config.json b/config.json index a099936bc..23105d55a 100644 --- a/config.json +++ b/config.json @@ -13,11 +13,18 @@ "DeleteOnSaveCorruption": false, "ClientMode": "ZZ", "QuestCacheExpiry": 300, - "ProxyPort": 0, "CommandPrefix": "!", - "DevMode": true, - "DevModeOptions": { - "AutoCreateAccount": true, + "AutoCreateAccount": true, + "DefaultCourses": [1, 23, 24], + "EarthStatus": 0, + "EarthID": 0, + "EarthMonsters": [0, 0, 0, 0], + "SaveDumps": { + "Enabled": true, + "RawEnabled": false, + "OutputDir": "save-backups" + }, + "DebugOptions": { "CleanDB": false, "MaxLauncherHR": false, "LogInboundMessages": false, @@ -28,16 +35,8 @@ "FestaEvent": -1, "TournamentEvent": 0, "DisableTokenCheck": false, - "QuestDebugTools": false, - "DefaultCourses": [1, 23, 24], - "EarthStatusOverride": 0, - "EarthIDOverride": 0, - "EarthMonsterOverride": [0, 0, 0, 0], - "SaveDumps": { - "Enabled": true, - "RawEnabled": false, - "OutputDir": "save-backups" - }, + "QuestTools": false, + "ProxyPort": 0, "CapLink": { "Values": [51728, 20000, 51729, 1, 20000], "Key": "", @@ -58,8 +57,6 @@ "ClanMemberLimits": [[0, 30], [3, 40], [7, 50], [10, 60]], "BonusQuestAllowance": 3, "DailyQuestAllowance": 1, - "MezfesSoloTickets": 10, - "MezfesGroupTickets": 4, "LowLatencyRaviente": false, "RegularRavienteMaxPlayers": 8, "ViolentRavienteMaxPlayers": 8, @@ -74,6 +71,8 @@ "MaterialMultiplier": 1.00, "ExtraCarves": 0, "DisableHunterNavi": false, + "MezFesSoloTickets": 5, + "MezFesGroupTickets": 1, "MezFesDuration": 172800, "MezFesSwitchMinigame": false, "EnableKaijiEvent": false, diff --git a/config/config.go b/config/config.go index 7e87fd8b8..02c94b030 100644 --- a/config/config.go +++ b/config/config.go @@ -80,42 +80,23 @@ type Config struct { ClientMode string RealClientMode Mode QuestCacheExpiry int // Number of seconds to keep quest data cached - ProxyPort uint16 // Forces the game to connect to a channel server proxy CommandPrefix string // The prefix for commands - DevMode bool - - DevModeOptions DevModeOptions - GameplayOptions GameplayOptions - Discord Discord - Commands []Command - Courses []Course - Database Database - Sign Sign - SignV2 SignV2 - Channel Channel - Entrance Entrance -} - -// DevModeOptions holds various debug/temporary options for use while developing Erupe. -type DevModeOptions struct { - AutoCreateAccount bool // Automatically create accounts if they don't exist - CleanDB bool // Automatically wipes the DB on server reset. - MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds. - LogInboundMessages bool // Log all messages sent to the server - LogOutboundMessages bool // Log all messages sent to the clients - LogMessageData bool // Log all bytes transferred as a hexdump - MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled - DivaEvent int // Diva Defense event status - FestaEvent int // Hunter's Festa event status - TournamentEvent int // VS Tournament event status - DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) - QuestDebugTools bool // Enable various quest debug logs - DefaultCourses []uint16 - EarthStatusOverride int32 - EarthIDOverride int32 - EarthMonsterOverride []int32 - SaveDumps SaveDumpOptions - CapLink CapLinkOptions + AutoCreateAccount bool // Automatically create accounts if they don't exist + DefaultCourses []uint16 + EarthStatus int32 + EarthID int32 + EarthMonsters []int32 + SaveDumps SaveDumpOptions + DebugOptions DebugOptions + GameplayOptions GameplayOptions + Discord Discord + Commands []Command + Courses []Course + Database Database + Sign Sign + SignV2 SignV2 + Channel Channel + Entrance Entrance } type SaveDumpOptions struct { @@ -124,6 +105,23 @@ type SaveDumpOptions struct { OutputDir string } +// DebugOptions holds various debug/temporary options for use while developing Erupe. +type DebugOptions struct { + CleanDB bool // Automatically wipes the DB on server reset. + MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds. + LogInboundMessages bool // Log all messages sent to the server + LogOutboundMessages bool // Log all messages sent to the clients + LogMessageData bool // Log all bytes transferred as a hexdump + MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled + DivaEvent int // Diva Defense event status + FestaEvent int // Hunter's Festa event status + TournamentEvent int // VS Tournament event status + DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) + QuestTools bool // Enable various quest debug logs + ProxyPort uint16 // Forces the game to connect to a channel server proxy + CapLink CapLinkOptions +} + type CapLinkOptions struct { Values []uint16 Key string @@ -146,8 +144,6 @@ type GameplayOptions struct { ClanMemberLimits [][]uint8 // Array of maximum Clan Members -> [Rank, Members] BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily DailyQuestAllowance uint32 // Number of Daily Quests to allow daily - MezfesSoloTickets uint32 // Number of solo tickets given weekly - MezfesGroupTickets uint32 // Number of group tickets given weekly LowLatencyRaviente bool // Toggles low latency mode for Raviente, can be network intensive RegularRavienteMaxPlayers uint8 ViolentRavienteMaxPlayers uint8 @@ -162,6 +158,8 @@ type GameplayOptions struct { MaterialMultiplier float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion ExtraCarves uint16 // Grant n extra chances to carve ALL carcasses DisableHunterNavi bool // Disables the Hunter Navi + MezFesSoloTickets uint32 // Number of solo tickets given weekly + MezFesGroupTickets uint32 // Number of group tickets given weekly MezFesDuration int // Seconds that MezFes will last for weekly (from 12AM Mon backwards) MezFesSwitchMinigame bool // Swaps out Volpakkun Together for Tokotoko Partnya EnableKaijiEvent bool // Enables the Kaiji event in the Rasta Bar diff --git a/main.go b/main.go index c56d90b0f..42da5a7fe 100644 --- a/main.go +++ b/main.go @@ -22,13 +22,10 @@ import ( ) // Temporary DB auto clean on startup for quick development & testing. -func cleanDB(db *sqlx.DB, config *_config.Config) { +func cleanDB(db *sqlx.DB) { _ = db.MustExec("DELETE FROM guild_characters") _ = db.MustExec("DELETE FROM guilds") _ = db.MustExec("DELETE FROM characters") - if config.ProxyPort == 0 { - _ = db.MustExec("DELETE FROM sign_sessions") - } _ = db.MustExec("DELETE FROM users") } @@ -48,11 +45,7 @@ func main() { var zapLogger *zap.Logger config := _config.ErupeConfig - if config.DevMode { - zapLogger, _ = zap.NewDevelopment() - } else { - zapLogger, _ = zap.NewProduction() - } + zapLogger, _ = zap.NewDevelopment() defer zapLogger.Sync() logger := zapLogger.Named("main") @@ -126,16 +119,16 @@ func main() { logger.Info("Database: Started successfully") // Clear stale data - if config.ProxyPort == 0 { + if config.DebugOptions.ProxyPort == 0 { _ = db.MustExec("DELETE FROM sign_sessions") } _ = db.MustExec("DELETE FROM servers") _ = db.MustExec(`UPDATE guild_characters SET treasure_hunt=NULL`) // Clean the DB if the option is on. - if config.DevMode && config.DevModeOptions.CleanDB { + if config.DebugOptions.CleanDB { logger.Info("Database: Started clearing...") - cleanDB(db, config) + cleanDB(db) logger.Info("Database: Finished clearing") } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 76d085175..e2c15a4f8 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -31,7 +31,7 @@ func stubEnumerateNoResults(s *Session, ackHandle uint32) { func doAckEarthSucceed(s *Session, ackHandle uint32, data []*byteframe.ByteFrame) { bf := byteframe.NewByteFrame() - bf.WriteUint32(uint32(s.server.erupeConfig.DevModeOptions.EarthIDOverride)) + bf.WriteUint32(uint32(s.server.erupeConfig.EarthID)) bf.WriteUint32(0) bf.WriteUint32(0) bf.WriteUint32(uint32(len(data))) @@ -127,7 +127,7 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysLogin) - if !s.server.erupeConfig.DevModeOptions.DisableTokenCheck { + if !s.server.erupeConfig.DebugOptions.DisableTokenCheck { var token string err := s.server.db.QueryRow("SELECT token FROM sign_sessions ss INNER JOIN public.users u on ss.user_id = u.id WHERE token=$1 AND ss.id=$2 AND u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.LoginTokenString, pkt.LoginTokenNumber, pkt.CharID0).Scan(&token) if err != nil { @@ -1147,9 +1147,9 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint32(uint32(TimeWeekStart().Unix())) // Start bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End - bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride) - bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride) - for i, m := range s.server.erupeConfig.DevModeOptions.EarthMonsterOverride { + bf.WriteInt32(s.server.erupeConfig.EarthStatus) + bf.WriteInt32(s.server.erupeConfig.EarthID) + for i, m := range s.server.erupeConfig.EarthMonsters { if _config.ErupeConfig.RealClientMode <= _config.G9 { if i == 3 { break diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 71a8a6f07..51141a47f 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -348,7 +348,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { } } - if s.server.erupeConfig.DevModeOptions.QuestDebugTools == true && s.server.erupeConfig.DevMode { + if s.server.erupeConfig.DebugOptions.QuestTools { if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x02 && len(pkt.RawDataPayload) > 32 { // This is only correct most of the time tmp.ReadBytes(20) diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 805fa59f5..8844dbdd8 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -45,7 +45,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return } - if s.server.erupeConfig.DevModeOptions.SaveDumps.RawEnabled { + if s.server.erupeConfig.SaveDumps.RawEnabled { dumpSaveData(s, saveData, "raw-savedata") } s.logger.Info("Updating save with blob") @@ -112,11 +112,11 @@ func grpToGR(n int) uint16 { } func dumpSaveData(s *Session, data []byte, suffix string) { - if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled { + if !s.server.erupeConfig.SaveDumps.Enabled { return } else { - dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID)) - path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID), fmt.Sprintf("%d_%s.bin", s.charID, suffix)) + dir := filepath.Join(s.server.erupeConfig.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID)) + path := filepath.Join(s.server.erupeConfig.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID), fmt.Sprintf("%d_%s.bin", s.charID, suffix)) _, err := os.Stat(dir) if err != nil { if os.IsNotExist(err) { diff --git a/server/channelserver/handlers_diva.go b/server/channelserver/handlers_diva.go index 42b70f064..d7c60331a 100644 --- a/server/channelserver/handlers_diva.go +++ b/server/channelserver/handlers_diva.go @@ -70,8 +70,8 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) { } var timestamps []uint32 - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 { - if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 { + if s.server.erupeConfig.DebugOptions.DivaEvent >= 0 { + if s.server.erupeConfig.DebugOptions.DivaEvent == 0 { if s.server.erupeConfig.RealClientMode >= _config.Z2 { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36)) } else { @@ -79,7 +79,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) { } return } - timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true) + timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.DivaEvent), true) } else { timestamps = generateDivaTimestamps(s, start, false) } diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index f92b8d3fc..d200240e4 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -36,7 +36,7 @@ func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateRanking) bf := byteframe.NewByteFrame() - state := s.server.erupeConfig.DevModeOptions.TournamentEvent + state := s.server.erupeConfig.DebugOptions.TournamentEvent // Unk // Unk // Start? @@ -173,12 +173,12 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { } var timestamps []uint32 - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 { - if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 { + if s.server.erupeConfig.DebugOptions.FestaEvent >= 0 { + if s.server.erupeConfig.DebugOptions.FestaEvent == 0 { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return } - timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true) + timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.FestaEvent), true) } else { timestamps = generateFestaTimestamps(s, start, false) } diff --git a/server/channelserver/handlers_object.go b/server/channelserver/handlers_object.go index 241505c0d..41f28e5d3 100644 --- a/server/channelserver/handlers_object.go +++ b/server/channelserver/handlers_object.go @@ -42,7 +42,7 @@ func handleMsgSysDeleteObject(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysPositionObject(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysPositionObject) - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages { + if s.server.erupeConfig.DebugOptions.LogInboundMessages { fmt.Printf("[%s] with objectID [%d] move to (%f,%f,%f)\n\n", s.Name, pkt.ObjID, pkt.X, pkt.Y, pkt.Z) } s.stage.Lock() diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 98226c777..138199ea4 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -20,7 +20,7 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysGetFile) if pkt.IsScenario { - if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode { + if s.server.erupeConfig.DebugOptions.QuestTools { s.logger.Debug( "Scenario", zap.Uint8("CategoryID", pkt.ScenarioIdentifer.CategoryID), @@ -40,7 +40,7 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { } doAckBufSucceed(s, pkt.AckHandle, data) } else { - if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode { + if s.server.erupeConfig.DebugOptions.QuestTools { s.logger.Debug( "Quest", zap.String("Filename", pkt.Filename), diff --git a/server/channelserver/handlers_tower.go b/server/channelserver/handlers_tower.go index 4ce0bcc9f..7f6bdb3b9 100644 --- a/server/channelserver/handlers_tower.go +++ b/server/channelserver/handlers_tower.go @@ -105,7 +105,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPostTowerInfo) - if s.server.erupeConfig.DevModeOptions.QuestDebugTools { + if s.server.erupeConfig.DebugOptions.QuestTools { s.logger.Debug( p.Opcode().String(), zap.Uint32("InfoType", pkt.InfoType), @@ -328,7 +328,7 @@ func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPostTenrouirai) - if s.server.erupeConfig.DevModeOptions.QuestDebugTools { + if s.server.erupeConfig.DebugOptions.QuestTools { s.logger.Debug( p.Opcode().String(), zap.Uint8("Unk0", pkt.Unk0), @@ -442,7 +442,7 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPostGemInfo) - if s.server.erupeConfig.DevModeOptions.QuestDebugTools { + if s.server.erupeConfig.DebugOptions.QuestTools { s.logger.Debug( p.Opcode().String(), zap.Uint32("Op", pkt.Op), diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 5034f38c2..d49e5264a 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -253,13 +253,9 @@ func ignored(opcode network.PacketID) bool { } func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipient string) { - if !s.server.erupeConfig.DevMode { + if sender == "Server" && !s.server.erupeConfig.DebugOptions.LogOutboundMessages { return - } - - if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages { - return - } else if sender != "Server" && !s.server.erupeConfig.DevModeOptions.LogInboundMessages { + } else if sender != "Server" && !s.server.erupeConfig.DebugOptions.LogInboundMessages { return } @@ -277,8 +273,8 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien fmt.Printf("[%s] -> [%s]\n", sender, recipient) } fmt.Printf("Opcode: %s\n", opcodePID) - if s.server.erupeConfig.DevModeOptions.LogMessageData { - if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength { + if s.server.erupeConfig.DebugOptions.LogMessageData { + if len(data) <= s.server.erupeConfig.DebugOptions.MaxHexdumpLength { fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data)) } else { fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data)) diff --git a/server/entranceserver/entrance_server.go b/server/entranceserver/entrance_server.go index 8b06be0e0..18869304b 100644 --- a/server/entranceserver/entrance_server.go +++ b/server/entranceserver/entrance_server.go @@ -111,7 +111,7 @@ func (s *Server) handleEntranceServerConnection(conn net.Conn) { return } - if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogInboundMessages { + if s.erupeConfig.DebugOptions.LogInboundMessages { fmt.Printf("[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt)) } diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index 4b478fa24..f7f2f433e 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -69,8 +69,8 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { for channelIdx, ci := range si.Channels { sid = (4096 + serverIdx*256) + (16 + channelIdx) - if _config.ErupeConfig.DevMode && _config.ErupeConfig.ProxyPort != 0 { - bf.WriteUint16(_config.ErupeConfig.ProxyPort) + if _config.ErupeConfig.DebugOptions.ProxyPort != 0 { + bf.WriteUint16(_config.ErupeConfig.DebugOptions.ProxyPort) } else { bf.WriteUint16(ci.Port) } @@ -136,7 +136,7 @@ func makeSv2Resp(config *_config.Config, s *Server, local bool) []byte { } rawServerData := encodeServerInfo(config, s, local) - if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogOutboundMessages { + if s.erupeConfig.DebugOptions.LogOutboundMessages { fmt.Printf("[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(rawServerData), hex.Dump(rawServerData)) } @@ -168,7 +168,7 @@ func makeUsrResp(pkt []byte, s *Server) []byte { } } - if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogOutboundMessages { + if s.erupeConfig.DebugOptions.LogOutboundMessages { fmt.Printf("[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(resp.Data()), hex.Dump(resp.Data())) } diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index 751862a49..f23d3bdcc 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -231,7 +231,7 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) { if err != nil { if err == sql.ErrNoRows { s.logger.Info("User not found", zap.String("User", user)) - if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.AutoCreateAccount { + if s.erupeConfig.AutoCreateAccount { uid, err = s.registerDBAccount(user, pass) if err == nil { return uid, SIGN_SUCCESS diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 25b5650c4..715299f8f 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -72,7 +72,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte { bf.WriteUint32(char.ID) // Exp, HR[x] is split by 0, 1, 30, 50, 99, 299, 998, 999 - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.MaxLauncherHR { + if s.server.erupeConfig.DebugOptions.MaxLauncherHR { bf.WriteUint16(999) } else { bf.WriteUint16(char.HRP) @@ -145,11 +145,11 @@ func (s *Session) makeSignResponse(uid uint32) []byte { bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true)) } - bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[0]) - if s.server.erupeConfig.DevModeOptions.CapLink.Values[0] == 51728 { - bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[1]) - if s.server.erupeConfig.DevModeOptions.CapLink.Values[1] == 20000 || s.server.erupeConfig.DevModeOptions.CapLink.Values[1] == 20002 { - ps.Uint16(bf, s.server.erupeConfig.DevModeOptions.CapLink.Key, false) + bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[0]) + if s.server.erupeConfig.DebugOptions.CapLink.Values[0] == 51728 { + bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[1]) + if s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20000 || s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20002 { + ps.Uint16(bf, s.server.erupeConfig.DebugOptions.CapLink.Key, false) } } caStruct := []struct { @@ -163,19 +163,19 @@ func (s *Session) makeSignResponse(uid uint32) []byte { bf.WriteUint32(caStruct[i].Unk1) ps.Uint8(bf, caStruct[i].Unk2, false) } - bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[2]) - bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[3]) - bf.WriteUint16(s.server.erupeConfig.DevModeOptions.CapLink.Values[4]) - if s.server.erupeConfig.DevModeOptions.CapLink.Values[2] == 51729 && s.server.erupeConfig.DevModeOptions.CapLink.Values[3] == 1 && s.server.erupeConfig.DevModeOptions.CapLink.Values[4] == 20000 { - ps.Uint16(bf, fmt.Sprintf(`%s:%d`, s.server.erupeConfig.DevModeOptions.CapLink.Host, s.server.erupeConfig.DevModeOptions.CapLink.Port), false) + bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[2]) + bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[3]) + bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[4]) + if s.server.erupeConfig.DebugOptions.CapLink.Values[2] == 51729 && s.server.erupeConfig.DebugOptions.CapLink.Values[3] == 1 && s.server.erupeConfig.DebugOptions.CapLink.Values[4] == 20000 { + ps.Uint16(bf, fmt.Sprintf(`%s:%d`, s.server.erupeConfig.DebugOptions.CapLink.Host, s.server.erupeConfig.DebugOptions.CapLink.Port), false) } bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix())) bf.WriteUint32(0) tickets := []uint32{ - s.server.erupeConfig.GameplayOptions.MezfesSoloTickets, - s.server.erupeConfig.GameplayOptions.MezfesGroupTickets, + s.server.erupeConfig.GameplayOptions.MezFesSoloTickets, + s.server.erupeConfig.GameplayOptions.MezFesGroupTickets, } stalls := []uint8{ 10, 3, 6, 9, 4, 8, 5, 7, diff --git a/server/signserver/session.go b/server/signserver/session.go index 5806f1108..164ab70e2 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -37,7 +37,7 @@ type Session struct { func (s *Session) work() { pkt, err := s.cryptConn.ReadPacket() - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages { + if s.server.erupeConfig.DebugOptions.LogInboundMessages { fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt)) } @@ -78,7 +78,7 @@ func (s *Session) handlePacket(pkt []byte) error { } default: s.logger.Warn("Unknown request", zap.String("reqType", reqType)) - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages { + if s.server.erupeConfig.DebugOptions.LogInboundMessages { fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt)) } } @@ -102,7 +102,7 @@ func (s *Session) authenticate(username string, password string) { default: bf.WriteUint8(uint8(resp)) } - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogOutboundMessages { + if s.server.erupeConfig.DebugOptions.LogOutboundMessages { fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data())) } _ = s.cryptConn.SendPacket(bf.Data()) diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index b6cc03515..5a0a6c8e7 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -80,7 +80,7 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3 PatchServer: s.erupeConfig.SignV2.PatchServer, Notices: []string{}, } - if s.erupeConfig.DevModeOptions.MaxLauncherHR { + if s.erupeConfig.DebugOptions.MaxLauncherHR { for i := range resp.Characters { resp.Characters[i].HR = 7 } @@ -93,8 +93,8 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3 ID: uint32(channelserver.TimeWeekStart().Unix()), Start: uint32(channelserver.TimeWeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()), End: uint32(channelserver.TimeWeekNext().Unix()), - SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets, - GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets, + SoloTickets: s.erupeConfig.GameplayOptions.MezFesSoloTickets, + GroupTickets: s.erupeConfig.GameplayOptions.MezFesGroupTickets, Stalls: stalls, } if !s.erupeConfig.HideLoginNotice { @@ -226,7 +226,7 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) return } - if s.erupeConfig.DevModeOptions.MaxLauncherHR { + if s.erupeConfig.DebugOptions.MaxLauncherHR { character.HR = 7 } w.Header().Add("Content-Type", "application/json") From 32dee9039ed3b83d687ac96bd43cd10683fd03fc Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 31 Dec 2023 12:54:18 +1100 Subject: [PATCH 215/269] simplify config --- config.json | 6 +++--- config/config.go | 6 +++--- server/channelserver/handlers_diva.go | 6 +++--- server/channelserver/handlers_festa.go | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/config.json b/config.json index 23105d55a..63034a452 100644 --- a/config.json +++ b/config.json @@ -31,9 +31,9 @@ "LogOutboundMessages": false, "LogMessageData": false, "MaxHexdumpLength": 256, - "DivaEvent": 0, - "FestaEvent": -1, - "TournamentEvent": 0, + "DivaOverride": 0, + "FestaOverride": -1, + "TournamentOverride": 0, "DisableTokenCheck": false, "QuestTools": false, "ProxyPort": 0, diff --git a/config/config.go b/config/config.go index 02c94b030..e1248e1f9 100644 --- a/config/config.go +++ b/config/config.go @@ -113,9 +113,9 @@ type DebugOptions struct { LogOutboundMessages bool // Log all messages sent to the clients LogMessageData bool // Log all bytes transferred as a hexdump MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled - DivaEvent int // Diva Defense event status - FestaEvent int // Hunter's Festa event status - TournamentEvent int // VS Tournament event status + DivaOverride int // Diva Defense event status + FestaOverride int // Hunter's Festa event status + TournamentOverride int // VS Tournament event status DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) QuestTools bool // Enable various quest debug logs ProxyPort uint16 // Forces the game to connect to a channel server proxy diff --git a/server/channelserver/handlers_diva.go b/server/channelserver/handlers_diva.go index d7c60331a..7f5b33992 100644 --- a/server/channelserver/handlers_diva.go +++ b/server/channelserver/handlers_diva.go @@ -70,8 +70,8 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) { } var timestamps []uint32 - if s.server.erupeConfig.DebugOptions.DivaEvent >= 0 { - if s.server.erupeConfig.DebugOptions.DivaEvent == 0 { + if s.server.erupeConfig.DebugOptions.DivaOverride >= 0 { + if s.server.erupeConfig.DebugOptions.DivaOverride == 0 { if s.server.erupeConfig.RealClientMode >= _config.Z2 { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36)) } else { @@ -79,7 +79,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) { } return } - timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.DivaEvent), true) + timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.DivaOverride), true) } else { timestamps = generateDivaTimestamps(s, start, false) } diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index d200240e4..4fc5370e1 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -36,7 +36,7 @@ func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateRanking) bf := byteframe.NewByteFrame() - state := s.server.erupeConfig.DebugOptions.TournamentEvent + state := s.server.erupeConfig.DebugOptions.TournamentOverride // Unk // Unk // Start? @@ -173,12 +173,12 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { } var timestamps []uint32 - if s.server.erupeConfig.DebugOptions.FestaEvent >= 0 { - if s.server.erupeConfig.DebugOptions.FestaEvent == 0 { + if s.server.erupeConfig.DebugOptions.FestaOverride >= 0 { + if s.server.erupeConfig.DebugOptions.FestaOverride == 0 { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return } - timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.FestaEvent), true) + timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.FestaOverride), true) } else { timestamps = generateFestaTimestamps(s, start, false) } From 5a8bc3b67afac4cd17c8757b65e16d5407b64edd Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 31 Dec 2023 15:12:20 +1100 Subject: [PATCH 216/269] fix EntranceServer response on G5 --- server/entranceserver/make_resp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index f7f2f433e..481ccae93 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -51,13 +51,13 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...) combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...) bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false)) - } else if s.erupeConfig.RealClientMode <= _config.GG { + } else if s.erupeConfig.RealClientMode <= _config.G5 { combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...) combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...) bf.WriteUint8(uint8(len(combined))) bf.WriteBytes(combined) } else { - bf.WriteUint8(0) // Prevents malformed server name + bf.WriteUint8(0) // Ignored combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...) combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...) bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false)) From 1aa2e360876516bb0e5ef73ea82bc648fe35b396 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 31 Dec 2023 16:30:11 +1100 Subject: [PATCH 217/269] simplify World name concatenation --- server/entranceserver/make_resp.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index 481ccae93..164f63871 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -47,20 +47,15 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { bf.WriteUint8(si.Recommended) } - if s.erupeConfig.RealClientMode <= _config.F5 { - combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...) - combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...) - bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false)) - } else if s.erupeConfig.RealClientMode <= _config.G5 { - combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...) - combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...) - bf.WriteUint8(uint8(len(combined))) - bf.WriteBytes(combined) + fullName := append(append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...), stringsupport.UTF8ToSJIS(si.Description)...) + if s.erupeConfig.RealClientMode >= _config.G1 && s.erupeConfig.RealClientMode <= _config.G5 { + bf.WriteUint8(uint8(len(fullName))) + bf.WriteBytes(fullName) } else { - bf.WriteUint8(0) // Ignored - combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...) - combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...) - bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false)) + if s.erupeConfig.RealClientMode >= _config.G51 { + bf.WriteUint8(0) // Ignored + } + bf.WriteBytes(stringsupport.PaddedString(string(fullName), 65, false)) } if s.erupeConfig.RealClientMode >= _config.GG { From 57bf1ca6e481dc452f1a39974f08eb09fcc17299 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 31 Dec 2023 19:12:33 +1100 Subject: [PATCH 218/269] simplify EntranceServer crypto --- server/entranceserver/crypto.go | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/server/entranceserver/crypto.go b/server/entranceserver/crypto.go index 20a361e7e..5cbc94604 100644 --- a/server/entranceserver/crypto.go +++ b/server/entranceserver/crypto.go @@ -12,45 +12,40 @@ var ( // CalcSum32 calculates the custom MHF "sum32" checksum of the given data. func CalcSum32(data []byte) uint32 { - tableIdx0 := int(len(data) & 0xFF) - tableIdx1 := int(data[len(data)>>1] & 0xFF) - + tableIdx0 := (len(data) + 1) & 0xFF + tableIdx1 := int((data[len(data)>>1] + 1) & 0xFF) out := make([]byte, 4) for i := 0; i < len(data); i++ { - tableIdx0++ - tableIdx1++ - - tmp := byte((_sum32Table1[tableIdx1%9] ^ _sum32Table0[tableIdx0%7]) ^ data[i]) - out[i&3] = (out[i&3] + tmp) & 0xFF + key := _sum32Table0[(tableIdx0+i)%7] ^ _sum32Table1[(tableIdx1+i)%9] + out[i&3] = (out[i&3] + (data[i] ^ key)) & 0xFF } - return binary.BigEndian.Uint32(out) } +func rotate(k *uint32) { + *k = uint32(((54323 * uint(*k)) + 1) & 0xFFFFFFFF) +} + // EncryptBin8 encrypts the given data using MHF's "binary8" encryption. func EncryptBin8(data []byte, key byte) []byte { - curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF) - + _key := uint32(key) var output []byte for i := 0; i < len(data); i++ { - tmp := (_bin8Key[i&7] ^ byte((curKey>>13)&0xFF)) + rotate(&_key) + tmp := _bin8Key[i&7] ^ byte((_key>>13)&0xFF) output = append(output, data[i]^tmp) - curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF) } - return output } // DecryptBin8 decrypts the given MHF "binary8" data. func DecryptBin8(data []byte, key byte) []byte { - curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF) - + _key := uint32(key) var output []byte for i := 0; i < len(data); i++ { - tmp := (data[i] ^ byte((curKey>>13)&0xFF)) + rotate(&_key) + tmp := data[i] ^ byte((_key>>13)&0xFF) output = append(output, tmp^_bin8Key[i&7]) - curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF) } - return output } From 3db6ee7b25134dd580354a1f25f2792232238dd3 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 31 Dec 2023 19:56:20 +1100 Subject: [PATCH 219/269] simplify EntranceServer crypto --- server/entranceserver/crypto.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/entranceserver/crypto.go b/server/entranceserver/crypto.go index 5cbc94604..e9e76f9fa 100644 --- a/server/entranceserver/crypto.go +++ b/server/entranceserver/crypto.go @@ -16,8 +16,8 @@ func CalcSum32(data []byte) uint32 { tableIdx1 := int((data[len(data)>>1] + 1) & 0xFF) out := make([]byte, 4) for i := 0; i < len(data); i++ { - key := _sum32Table0[(tableIdx0+i)%7] ^ _sum32Table1[(tableIdx1+i)%9] - out[i&3] = (out[i&3] + (data[i] ^ key)) & 0xFF + key := data[i] ^ _sum32Table0[(tableIdx0+i)%7] ^ _sum32Table1[(tableIdx1+i)%9] + out[i&3] = (out[i&3] + key) & 0xFF } return binary.BigEndian.Uint32(out) } From 4f5eeb1508d0732ac73e16258d1cf6119ca169af Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 1 Jan 2024 01:03:20 +1100 Subject: [PATCH 220/269] add ico resources --- erupe.ico | Bin 0 -> 99678 bytes rsrc_windows_amd64.syso | Bin 0 -> 100166 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 erupe.ico create mode 100644 rsrc_windows_amd64.syso diff --git a/erupe.ico b/erupe.ico new file mode 100644 index 0000000000000000000000000000000000000000..e1358c741f3bffeca6e1550e42f912ec9af94ce8 GIT binary patch literal 99678 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%0D0f_5|;(%60D2WjFFf{SwY@|7uxu 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 QyYsTWWOh@A Date: Mon, 1 Jan 2024 02:10:27 +1100 Subject: [PATCH 221/269] reuse existing token, JP string & command description --- config.json | 1 + server/channelserver/handlers_cast_binary.go | 22 +++++++------------- server/channelserver/sys_language.go | 4 +++- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/config.json b/config.json index 08bf46fe1..20da75a87 100644 --- a/config.json +++ b/config.json @@ -133,6 +133,7 @@ }, { "Name": "Discord", "Enabled": true, + "Description": "Generate a token to link your Discord account", "Prefix": "discord" } ], diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 7fdfbe437..08ae74d7e 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -325,23 +325,15 @@ func parseChatCommand(s *Session, command string) { } case commands["Discord"].Prefix: if commands["Discord"].Enabled { - randToken := make([]byte, 4) - - _, err := rand.Read(randToken) + var _token string + err := s.server.db.QueryRow(`SELECT discord_token FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&_token) if err != nil { - sendServerChatMessage(s, fmt.Sprint("An error occurred while processing this command")) - s.logger.Error(fmt.Sprint(err)) - return + randToken := make([]byte, 4) + rand.Read(randToken) + _token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:]) + s.server.db.Exec(`UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, _token, s.charID) } - - discordToken := fmt.Sprintf("%x-%x", randToken[:2], randToken[2:]) - _, err = s.server.db.Exec("UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", fmt.Sprint(discordToken), s.charID) - if err != nil { - sendServerChatMessage(s, fmt.Sprint("An error occurred while processing this command")) - s.logger.Error(fmt.Sprint(err)) - return - } - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], discordToken)) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], _token)) } else { sendDisabledCommandMessage(s, commands["Discord"]) } diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index a5f326487..d173637f2 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -25,6 +25,8 @@ func getLangStrings(s *Server) map[string]string { strings["commandPSNSuccess"] = "PSN「%s」が連携されています" strings["commandPSNExists"] = "PSNは既存のユーザに接続されています" + strings["commandDiscordSuccess"] = "あなたのDiscordトークン:%s" + strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません" strings["commandRaviStartSuccess"] = "大討伐を開始します" strings["commandRaviStartError"] = "大討伐は既に開催されています" @@ -78,7 +80,7 @@ func getLangStrings(s *Server) map[string]string { strings["commandPSNSuccess"] = "Connected PSN ID: %s" strings["commandPSNExists"] = "PSN ID is connected to another account!" - strings["commandDiscordSuccess"] = "Discord token has been generated: %s" + strings["commandDiscordSuccess"] = "Your Discord token: %s" strings["commandRaviNoCommand"] = "No Raviente command specified!" strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment" From 0d2863709591a7487e3e23f2b42d2218e5cb15b9 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 1 Jan 2024 21:22:51 +1100 Subject: [PATCH 222/269] support long messages, rename to RelayChannel, move commands out of main --- config.json | 1 + config/config.go | 13 +++--- main.go | 28 +---------- server/channelserver/handlers_discord.go | 23 +++++++-- server/discordbot/discord_bot.go | 59 +++++++++++++++++------- 5 files changed, 70 insertions(+), 54 deletions(-) diff --git a/config.json b/config.json index 20da75a87..15f51d9df 100644 --- a/config.json +++ b/config.json @@ -86,6 +86,7 @@ "BotToken": "", "RealTimeChannel": { "Enabled": false, + "MaxMessageLength": 183, "RealTimeChannelID": "" } }, diff --git a/config/config.go b/config/config.go index 8b1c0764d..3c6f6a0a7 100644 --- a/config/config.go +++ b/config/config.go @@ -171,14 +171,15 @@ type GameplayOptions struct { // Discord holds the discord integration config. type Discord struct { - Enabled bool - BotToken string - RealTimeChannel DiscordRealTime + Enabled bool + BotToken string + RelayChannel DiscordRelay } -type DiscordRealTime struct { - Enabled bool - RealtimeChannelID string +type DiscordRelay struct { + Enabled bool + MaxMessageLength int + RelayChannelID string } // Command is a channelserver chat command diff --git a/main.go b/main.go index 94f3b6e24..a7d368930 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( _config "erupe-ce/config" "fmt" - "github.com/bwmarrin/discordgo" "net" "os" "os/signal" @@ -93,32 +92,7 @@ func main() { discordBot = bot - _, err = discordBot.Session.ApplicationCommandBulkOverwrite(discordBot.Session.State.User.ID, "", []*discordgo.ApplicationCommand{ - { - Name: "verify", - Description: "Verify your account with Discord", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionString, - Name: "token", - Description: "The access token provided by !discord command within the game client.", - Required: true, - }, - }, - }, - { - Name: "password", - Description: "Reset your account password on Erupe", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionString, - Name: "password", - Description: "The password to change your account to.", - Required: true, - }, - }, - }, - }) + _, err = discordBot.Session.ApplicationCommandBulkOverwrite(discordBot.Session.State.User.ID, "", discordbot.Commands) if err != nil { preventClose(fmt.Sprintf("Discord: Failed to start, %s", err.Error())) } diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go index 1772e2d06..8e3ed3cb9 100644 --- a/server/channelserver/handlers_discord.go +++ b/server/channelserver/handlers_discord.go @@ -113,8 +113,8 @@ func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCr // onDiscordMessage handles receiving messages from discord and forwarding them ingame. func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) { - // Ignore messages from our bot, or ones that are not in the correct channel. - if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RealTimeChannel.RealtimeChannelID { + // Ignore messages from bots, or messages that are not in the correct channel. + if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RelayChannel.RelayChannelID { return } @@ -124,11 +124,24 @@ func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCre } return r }, m.Author.Username)) - for i := 0; i < 8-len(m.Author.Username); i++ { paddedName += " " } + message := s.discordBot.NormalizeDiscordMessage(fmt.Sprintf("[D] %s > %s", paddedName, m.Content)) + if len(message) > s.erupeConfig.Discord.RelayChannel.MaxMessageLength { + return + } - message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content) - s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message)) + var messages []string + lineLength := 61 + for i := 0; i < len(message); i += lineLength { + end := i + lineLength + if end > len(message) { + end = len(message) + } + messages = append(messages, message[i:end]) + } + for i := range messages { + s.BroadcastChatMessage(messages[i]) + } } diff --git a/server/discordbot/discord_bot.go b/server/discordbot/discord_bot.go index 0d774fff7..a9b327cc3 100644 --- a/server/discordbot/discord_bot.go +++ b/server/discordbot/discord_bot.go @@ -7,12 +7,39 @@ import ( "regexp" ) +var Commands = []*discordgo.ApplicationCommand{ + { + Name: "link", + Description: "Link your Erupe account to Discord", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "token", + Description: "The token provided by the Discord command in-game", + Required: true, + }, + }, + }, + { + Name: "password", + Description: "Change your Erupe account password", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "password", + Description: "Your new password", + Required: true, + }, + }, + }, +} + type DiscordBot struct { - Session *discordgo.Session - config *_config.Config - logger *zap.Logger - MainGuild *discordgo.Guild - RealtimeChannel *discordgo.Channel + Session *discordgo.Session + config *_config.Config + logger *zap.Logger + MainGuild *discordgo.Guild + RelayChannel *discordgo.Channel } type Options struct { @@ -28,22 +55,22 @@ func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) { return nil, err } - var realtimeChannel *discordgo.Channel + var relayChannel *discordgo.Channel - if options.Config.Discord.RealTimeChannel.Enabled { - realtimeChannel, err = session.Channel(options.Config.Discord.RealTimeChannel.RealtimeChannelID) + if options.Config.Discord.RelayChannel.Enabled { + relayChannel, err = session.Channel(options.Config.Discord.RelayChannel.RelayChannelID) } if err != nil { - options.Logger.Fatal("Discord failed to create realtimeChannel", zap.Error(err)) + options.Logger.Fatal("Discord failed to create relayChannel", zap.Error(err)) return nil, err } discordBot = &DiscordBot{ - config: options.Config, - logger: options.Logger, - Session: session, - RealtimeChannel: realtimeChannel, + config: options.Config, + logger: options.Logger, + Session: session, + RelayChannel: relayChannel, } return @@ -55,7 +82,7 @@ func (bot *DiscordBot) Start() (err error) { return } -// Replace all mentions to real name from the message. +// NormalizeDiscordMessage replaces all mentions to real name from the message. func (bot *DiscordBot) NormalizeDiscordMessage(message string) string { userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`) emojiRegex := regexp.MustCompile(`(?:)?`) @@ -78,11 +105,11 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string { } func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) { - if bot.RealtimeChannel == nil { + if bot.RelayChannel == nil { return } - _, err = bot.Session.ChannelMessageSend(bot.RealtimeChannel.ID, message) + _, err = bot.Session.ChannelMessageSend(bot.RelayChannel.ID, message) return } From 1ed8b97347f1c202d730bb5fc737720ca1510399 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 1 Jan 2024 23:39:29 +1100 Subject: [PATCH 223/269] update onInteraction process --- server/channelserver/handlers_discord.go | 70 ++++++++++++------------ 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go index 8e3ed3cb9..3144b5e7b 100644 --- a/server/channelserver/handlers_discord.go +++ b/server/channelserver/handlers_discord.go @@ -70,44 +70,46 @@ func getCharacterList(s *Server) string { // onInteraction handles slash commands func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCreate) { switch i.Interaction.ApplicationCommandData().Name { - case "verify": - _, err := s.db.Exec("UPDATE users SET discord_id = $1 WHERE discord_token = $2", i.Member.User.ID, i.ApplicationCommandData().Options[0].StringValue()) - if err != nil { - return + case "link": + var temp string + err := s.db.QueryRow(`UPDATE users SET discord_id = $1 WHERE discord_token = $2 RETURNING discord_id`, i.Member.User.ID, i.ApplicationCommandData().Options[0].StringValue()).Scan(&temp) + if err == nil { + ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Your Erupe account was linked successfully.", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + } else { + ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Failed to link Erupe account.", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) } - - err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Erupe account successfully linked to Discord account.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) - - if err != nil { - return - } - break case "password": password, _ := bcrypt.GenerateFromPassword([]byte(i.ApplicationCommandData().Options[0].StringValue()), 10) - - _, err := s.db.Exec("UPDATE users SET password = $1 WHERE discord_id = $2", password, i.Member.User.ID) - if err != nil { - s.logger.Error(fmt.Sprint(err)) - return + _, err := s.db.Exec(`UPDATE users SET password = $1 WHERE discord_id = $2`, password, i.Member.User.ID) + if err == nil { + ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Your Erupe account password has been updated.", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + } else { + ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Failed to update Erupe account password.", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) } - - err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Password has been reset, you may login now.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) - if err != nil { - return - } - break } } From 63388aa4f7c23291292bbaa242ca6c8ce46a3b41 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 2 Jan 2024 19:53:27 +1100 Subject: [PATCH 224/269] re-index patch-schema --- ...discord-password-resets.sql => 16-discord-password-resets.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename patch-schema/{15-discord-password-resets.sql => 16-discord-password-resets.sql} (100%) diff --git a/patch-schema/15-discord-password-resets.sql b/patch-schema/16-discord-password-resets.sql similarity index 100% rename from patch-schema/15-discord-password-resets.sql rename to patch-schema/16-discord-password-resets.sql From 1766b6f2bda1a362baee24503585a3c25af6ce15 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 2 Jan 2024 20:04:40 +1100 Subject: [PATCH 225/269] rewrite EntranceServer response --- server/entranceserver/make_resp.go | 35 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index 164f63871..ec0281abc 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -29,7 +29,6 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { } } - sid := (4096 + serverIdx*256) * 6000 if si.IP == "" { si.IP = config.Host } @@ -38,8 +37,8 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { } else { bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4())) } - bf.WriteUint16(16 + uint16(serverIdx)) - bf.WriteUint16(0x0000) + bf.WriteUint16(uint16(serverIdx | 16)) + bf.WriteUint16(0) bf.WriteUint16(uint16(len(si.Channels))) bf.WriteUint8(si.Type) bf.WriteUint8(uint8(((channelserver.TimeAdjusted().Unix() / 86400) + int64(serverIdx)) % 3)) @@ -63,31 +62,31 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { } for channelIdx, ci := range si.Channels { - sid = (4096 + serverIdx*256) + (16 + channelIdx) + sid := (serverIdx<<8 | 4096) + (channelIdx | 16) if _config.ErupeConfig.DebugOptions.ProxyPort != 0 { bf.WriteUint16(_config.ErupeConfig.DebugOptions.ProxyPort) } else { bf.WriteUint16(ci.Port) } - bf.WriteUint16(16 + uint16(channelIdx)) + bf.WriteUint16(uint16(channelIdx | 16)) bf.WriteUint16(ci.MaxPlayers) var currentPlayers uint16 s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(¤tPlayers) bf.WriteUint16(currentPlayers) - bf.WriteUint16(0) // Unk - bf.WriteUint16(0) // Unk - bf.WriteUint16(0) // Unk - bf.WriteUint16(0) // Unk - bf.WriteUint16(0) // Unk - bf.WriteUint16(0) // Unk - bf.WriteUint16(319) // Unk - bf.WriteUint16(252) // Unk - bf.WriteUint16(248) // Unk - bf.WriteUint16(12345) // Unk + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint16(319) // Unk + bf.WriteUint16(254 - currentPlayers) // Unk + bf.WriteUint16(255 - currentPlayers) // Unk + bf.WriteUint16(12345) } } bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) - bf.WriteUint32(0x0000003C) + bf.WriteUint32(60) return bf.Data() } @@ -156,11 +155,11 @@ func makeUsrResp(pkt []byte, s *Server) []byte { var sid uint16 err := s.db.QueryRow("SELECT(SELECT server_id FROM sign_sessions WHERE char_id=$1) AS _", cid).Scan(&sid) if err != nil { - resp.WriteBytes(make([]byte, 4)) + resp.WriteUint16(0) } else { resp.WriteUint16(sid) - resp.WriteUint16(0) } + resp.WriteUint16(0) } if s.erupeConfig.DebugOptions.LogOutboundMessages { From ca80a98141a1441dcc630b3daf9bbb55775b711f Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 3 Jan 2024 04:22:25 +1100 Subject: [PATCH 226/269] i18n proposal --- server/channelserver/handlers_cafe.go | 2 +- server/channelserver/handlers_cast_binary.go | 60 ++-- server/channelserver/handlers_guild_scout.go | 20 +- server/channelserver/sys_channel_server.go | 12 +- server/channelserver/sys_language.go | 274 ++++++++++++------- 5 files changed, 228 insertions(+), 140 deletions(-) diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index 9c4b0b732..b87bac5ff 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -94,7 +94,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint32(cafeTime) // Total cafe time bf.WriteUint16(0) - ps.Uint16(bf, fmt.Sprintf(s.server.dict["cafeReset"], int(cafeReset.Month()), cafeReset.Day()), true) + ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 08ae74d7e..043403216 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -59,7 +59,7 @@ func init() { } func sendDisabledCommandMessage(s *Session, cmd _config.Command) { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.disabled, cmd.Name)) } func sendServerChatMessage(s *Session, message string) { @@ -95,20 +95,20 @@ func parseChatCommand(s *Session, command string) { if exists == 0 { _, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID) if err == nil { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], args[1])) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.success, args[1])) } } else { - sendServerChatMessage(s, s.server.dict["commandPSNExists"]) + sendServerChatMessage(s, s.server.i18n.commands.psn.exists) } } else { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.error, commands["PSN"].Prefix)) } } else { sendDisabledCommandMessage(s, commands["PSN"]) } case commands["Reload"].Prefix: if commands["Reload"].Enabled { - sendServerChatMessage(s, s.server.dict["commandReload"]) + sendServerChatMessage(s, s.server.i18n.commands.reload) var temp mhfpacket.MHFPacket deleteNotif := byteframe.NewByteFrame() for _, object := range s.stage.objects { @@ -170,19 +170,19 @@ func parseChatCommand(s *Session, command string) { case commands["KeyQuest"].Prefix: if commands["KeyQuest"].Enabled { if s.server.erupeConfig.RealClientMode < _config.G10 { - sendServerChatMessage(s, s.server.dict["commandKqfVersion"]) + sendServerChatMessage(s, s.server.i18n.commands.kqf.version) } else { if len(args) > 1 { if args[1] == "get" { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.get, s.kqf)) } else if args[1] == "set" { if len(args) > 2 && len(args[2]) == 16 { hexd, _ := hex.DecodeString(args[2]) s.kqf = hexd s.kqfOverride = true - sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"]) + sendServerChatMessage(s, s.server.i18n.commands.kqf.set.success) } else { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.set.error, commands["KeyQuest"].Prefix)) } } } @@ -196,12 +196,12 @@ func parseChatCommand(s *Session, command string) { v, _ := strconv.Atoi(args[1]) _, 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)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v)) } else { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix)) } } else { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix)) } } else { sendDisabledCommandMessage(s, commands["Rights"]) @@ -225,11 +225,11 @@ func parseChatCommand(s *Session, command string) { }) if ei != -1 { delta = uint32(-1 * math.Pow(2, float64(course.ID))) - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases()[0])) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.disabled, course.Aliases()[0])) } } else { delta = uint32(math.Pow(2, float64(course.ID))) - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases()[0])) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.enabled, course.Aliases()[0])) } err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt) if err == nil { @@ -237,14 +237,14 @@ func parseChatCommand(s *Session, command string) { } updateRights(s) } else { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases()[0])) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.locked, course.Aliases()[0])) } return } } } } else { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.error, commands["Course"].Prefix)) } } else { sendDisabledCommandMessage(s, commands["Course"]) @@ -257,45 +257,45 @@ func parseChatCommand(s *Session, command string) { case "start": if s.server.raviente.register[1] == 0 { s.server.raviente.register[1] = s.server.raviente.register[3] - sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.start.success) s.notifyRavi() } else { - sendServerChatMessage(s, s.server.dict["commandRaviStartError"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.start.error) } case "cm", "check", "checkmultiplier", "multiplier": - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.GetRaviMultiplier())) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ravi.multiplier, s.server.GetRaviMultiplier())) case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed": if s.server.erupeConfig.RealClientMode == _config.ZZ { switch args[1] { case "sr", "sendres", "resurrection": if s.server.raviente.state[28] > 0 { - sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.res.success) s.server.raviente.state[28] = 0 } else { - sendServerChatMessage(s, s.server.dict["commandRaviResError"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.res.error) } case "ss", "sendsed": - sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.sed.success) // Total BerRavi HP HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4] s.server.raviente.support[1] = HP case "rs", "reqsed": - sendServerChatMessage(s, s.server.dict["commandRaviRequest"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.request) // Total BerRavi HP HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4] s.server.raviente.support[1] = HP + 1 } } else { - sendServerChatMessage(s, s.server.dict["commandRaviVersion"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.version) } default: - sendServerChatMessage(s, s.server.dict["commandRaviError"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.error) } } else { - sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.noPlayers) } } else { - sendServerChatMessage(s, s.server.dict["commandRaviError"]) + sendServerChatMessage(s, s.server.i18n.commands.ravi.error) } } else { sendDisabledCommandMessage(s, commands["Raviente"]) @@ -316,9 +316,9 @@ func parseChatCommand(s *Session, command string) { MessageType: BinaryMessageTypeState, RawDataPayload: payloadBytes, }) - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.success, x, y)) } else { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.error, commands["Teleport"].Prefix)) } } else { sendDisabledCommandMessage(s, commands["Teleport"]) @@ -333,7 +333,7 @@ func parseChatCommand(s *Session, command string) { _token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:]) s.server.db.Exec(`UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, _token, s.charID) } - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], _token)) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.discord.success, _token)) } else { sendDisabledCommandMessage(s, commands["Discord"]) } diff --git a/server/channelserver/handlers_guild_scout.go b/server/channelserver/handlers_guild_scout.go index fceeb037d..3c73b5bec 100644 --- a/server/channelserver/handlers_guild_scout.go +++ b/server/channelserver/handlers_guild_scout.go @@ -60,9 +60,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) { mail := &Mail{ SenderID: s.charID, RecipientID: pkt.CharID, - Subject: s.server.dict["guildInviteName"], + Subject: s.server.i18n.guild.invite.title, Body: fmt.Sprintf( - s.server.dict["guildInvite"], + s.server.i18n.guild.invite.body, guildInfo.Name, ), IsGuildInvite: true, @@ -146,30 +146,30 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) { err = guild.AcceptApplication(s, s.charID) mail = append(mail, Mail{ RecipientID: s.charID, - Subject: s.server.dict["guildInviteSuccessName"], - Body: fmt.Sprintf(s.server.dict["guildInviteSuccess"], guild.Name), + Subject: s.server.i18n.guild.invite.success.title, + Body: fmt.Sprintf(s.server.i18n.guild.invite.success.body, guild.Name), IsSystemMessage: true, }) mail = append(mail, Mail{ SenderID: s.charID, RecipientID: pkt.LeaderID, - Subject: s.server.dict["guildInviteAcceptedName"], - Body: fmt.Sprintf(s.server.dict["guildInviteAccepted"], guild.Name), + Subject: s.server.i18n.guild.invite.accepted.title, + Body: fmt.Sprintf(s.server.i18n.guild.invite.accepted.body, guild.Name), IsSystemMessage: true, }) } else { err = guild.RejectApplication(s, s.charID) mail = append(mail, Mail{ RecipientID: s.charID, - Subject: s.server.dict["guildInviteRejectName"], - Body: fmt.Sprintf(s.server.dict["guildInviteReject"], guild.Name), + Subject: s.server.i18n.guild.invite.rejected.title, + Body: fmt.Sprintf(s.server.i18n.guild.invite.rejected.body, guild.Name), IsSystemMessage: true, }) mail = append(mail, Mail{ SenderID: s.charID, RecipientID: pkt.LeaderID, - Subject: s.server.dict["guildInviteDeclined"], - Body: fmt.Sprintf(s.server.dict["guildInviteDeclined"], guild.Name), + Subject: s.server.i18n.guild.invite.declined.title, + Body: fmt.Sprintf(s.server.i18n.guild.invite.declined.body, guild.Name), IsSystemMessage: true, }) } diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 119cbf420..30d63241f 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -57,7 +57,7 @@ type Server struct { stages map[string]*Stage // Used to map different languages - dict map[string]string + i18n i18n // UserBinary userBinaryPartsLock sync.RWMutex @@ -192,7 +192,7 @@ func NewServer(config *Config) *Server { // MezFes s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0") - s.dict = getLangStrings(s) + s.i18n = getLangStrings(s) return s } @@ -337,13 +337,13 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u var text string switch _type { case 2: - text = s.dict["ravienteBerserk"] + text = s.i18n.raviente.berserk case 3: - text = s.dict["ravienteExtreme"] + text = s.i18n.raviente.extreme case 4: - text = s.dict["ravienteExtremeLimited"] + text = s.i18n.raviente.extremeLimited case 5: - text = s.dict["ravienteBerserkSmall"] + text = s.i18n.raviente.berserkSmall default: s.logger.Error("Unk raviente type", zap.Uint8("_type", _type)) } diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index d173637f2..5feb76444 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -1,118 +1,206 @@ package channelserver -func getLangStrings(s *Server) map[string]string { - strings := make(map[string]string) +type i18n struct { + language string + cafe struct { + reset string + } + commands struct { + disabled string + reload string + kqf struct { + get string + set struct { + error string + success string + } + version string + } + rights struct { + error string + success string + } + course struct { + error string + disabled string + enabled string + locked string + } + teleport struct { + error string + success string + } + psn struct { + error string + success string + exists string + } + discord struct { + success string + } + ravi struct { + noCommand string + start struct { + success string + error string + } + multiplier string + res struct { + success string + error string + } + sed struct { + success string + } + request string + error string + noPlayers string + version string + } + } + raviente struct { + berserk string + extreme string + extremeLimited string + berserkSmall string + } + guild struct { + invite struct { + title string + body string + success struct { + title string + body string + } + accepted struct { + title string + body string + } + rejected struct { + title string + body string + } + declined struct { + title string + body string + } + } + } +} + +func getLangStrings(s *Server) i18n { + var i i18n switch s.erupeConfig.Language { case "jp": - strings["language"] = "日本語" - strings["cafeReset"] = "%d/%dにリセット" + i.language = "日本語" + i.cafe.reset = "%d/%dにリセット" - strings["commandDisabled"] = "%sのコマンドは無効です" - strings["commandReload"] = "リロードします" - strings["commandKqfGet"] = "現在のキークエストフラグ:%x" - strings["commandKqfSetError"] = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx" - strings["commandKqfSetSuccess"] = "キークエストのフラグが更新されました。ワールド/ランドを移動してください" - strings["commandKqfVersion"] = "This command is disabled prior to MHFG10" - 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["commandPSNError"] = "PSN連携コマンドエラー 例:%s " - strings["commandPSNSuccess"] = "PSN「%s」が連携されています" - strings["commandPSNExists"] = "PSNは既存のユーザに接続されています" + i.commands.disabled = "%sのコマンドは無効です" + i.commands.reload = "リロードします" + i.commands.kqf.get = "現在のキークエストフラグ:%x" + i.commands.kqf.set.error = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx" + i.commands.kqf.set.success = "キークエストのフラグが更新されました。ワールド/ランドを移動してください" + i.commands.kqf.version = "This command is disabled prior to MHFG10" + i.commands.rights.error = "コース更新コマンドエラー 例:%s x" + i.commands.rights.success = "コース情報を更新しました:%d" + i.commands.course.error = "コース確認コマンドエラー 例:%s " + i.commands.course.disabled = "%sコースは無効です" + i.commands.course.enabled = "%sコースは有効です" + i.commands.course.locked = "%sコースはロックされています" + i.commands.teleport.error = "テレポートコマンドエラー 構文:%s x y" + i.commands.teleport.success = "%d %dにテレポート" + i.commands.psn.error = "PSN連携コマンドエラー 例:%s " + i.commands.psn.success = "PSN「%s」が連携されています" + i.commands.psn.exists = "PSNは既存のユーザに接続されています" - strings["commandDiscordSuccess"] = "あなたのDiscordトークン:%s" + i.commands.discord.success = "あなたのDiscordトークン:%s" - strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません" - strings["commandRaviStartSuccess"] = "大討伐を開始します" - strings["commandRaviStartError"] = "大討伐は既に開催されています" - strings["commandRaviMultiplier"] = "ラヴィダメージ倍率:x%.2f" - strings["commandRaviResSuccess"] = "復活支援を実行します" - strings["commandRaviResError"] = "復活支援は実行されませんでした" - strings["commandRaviSedSuccess"] = "鎮静支援を実行します" - strings["commandRaviRequest"] = "鎮静支援を要請します" - strings["commandRaviError"] = "ラヴィコマンドが認識されません" - strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません" - strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ" + i.commands.ravi.noCommand = "ラヴィコマンドが指定されていません" + i.commands.ravi.start.success = "大討伐を開始します" + i.commands.ravi.start.error = "大討伐は既に開催されています" + i.commands.ravi.multiplier = "ラヴィダメージ倍率:x%.2f" + i.commands.ravi.res.success = "復活支援を実行します" + i.commands.ravi.res.error = "復活支援は実行されませんでした" + i.commands.ravi.sed.success = "鎮静支援を実行します" + i.commands.ravi.request = "鎮静支援を要請します" + i.commands.ravi.error = "ラヴィコマンドが認識されません" + i.commands.ravi.noPlayers = "誰も大討伐に参加していません" + i.commands.ravi.version = "This command is disabled outside of MHFZZ" - strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!" - strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!" - strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!" - strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!" + i.raviente.berserk = "<大討伐:猛狂期>が開催されました!" + i.raviente.extreme = "<大討伐:猛狂期【極】>が開催されました!" + i.raviente.extremeLimited = "<大討伐:猛狂期【極】(制限付)>が開催されました!" + i.raviente.berserkSmall = "<大討伐:猛狂期(小数)>が開催されました!" - strings["guildInviteName"] = "猟団勧誘のご案内" - strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。" + i.guild.invite.title = "猟団勧誘のご案内" + i.guild.invite.body = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。" - strings["guildInviteSuccessName"] = "成功" - strings["guildInviteSuccess"] = "あなたは「%s」に参加できました。" + i.guild.invite.success.title = "成功" + i.guild.invite.success.body = "あなたは「%s」に参加できました。" - strings["guildInviteAcceptedName"] = "承諾されました" - strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。" + i.guild.invite.accepted.title = "承諾されました" + i.guild.invite.accepted.body = "招待した狩人が「%s」への招待を承諾しました。" - strings["guildInviteRejectName"] = "却下しました" - strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。" + i.guild.invite.rejected.title = "却下しました" + i.guild.invite.rejected.body = "あなたは「%s」への参加を却下しました。" - strings["guildInviteDeclinedName"] = "辞退しました" - strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。" + i.guild.invite.declined.title = "辞退しました" + i.guild.invite.declined.body = "招待した狩人が「%s」への招待を辞退しました。" default: - strings["language"] = "English" - strings["cafeReset"] = "Resets on %d/%d" + i.language = "English" + i.cafe.reset = "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["commandKqfVersion"] = "This command is disabled prior to MHFG10" - 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["commandPSNError"] = "Error in command. Format: %s " - strings["commandPSNSuccess"] = "Connected PSN ID: %s" - strings["commandPSNExists"] = "PSN ID is connected to another account!" + i.commands.disabled = "%s command is disabled" + i.commands.reload = "Reloading players..." + i.commands.kqf.get = "KQF: %x" + i.commands.kqf.set.error = "Error in command. Format: %s set xxxxxxxxxxxxxxxx" + i.commands.kqf.set.success = "KQF set, please switch Land/World" + i.commands.kqf.version = "This command is disabled prior to MHFG10" + i.commands.rights.error = "Error in command. Format: %s x" + i.commands.rights.success = "Set rights integer: %d" + i.commands.course.error = "Error in command. Format: %s " + i.commands.course.disabled = "%s Course disabled" + i.commands.course.enabled = "%s Course enabled" + i.commands.course.locked = "%s Course is locked" + i.commands.teleport.error = "Error in command. Format: %s x y" + i.commands.teleport.success = "Teleporting to %d %d" + i.commands.psn.error = "Error in command. Format: %s " + i.commands.psn.success = "Connected PSN ID: %s" + i.commands.psn.exists = "PSN ID is connected to another account!" - strings["commandDiscordSuccess"] = "Your Discord token: %s" + i.commands.discord.success = "Your Discord token: %s" - 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 %.2fx" - 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["commandRaviVersion"] = "This command is disabled outside of MHFZZ" + i.commands.ravi.noCommand = "No Raviente command specified!" + i.commands.ravi.start.success = "The Great Slaying will begin in a moment" + i.commands.ravi.start.error = "The Great Slaying has already begun!" + i.commands.ravi.multiplier = "Raviente multiplier is currently %.2fx" + i.commands.ravi.res.success = "Sending resurrection support!" + i.commands.ravi.res.error = "Resurrection support has not been requested!" + i.commands.ravi.sed.success = "Sending sedation support if requested!" + i.commands.ravi.request = "Requesting sedation support!" + i.commands.ravi.error = "Raviente command not recognised!" + i.commands.ravi.noPlayers = "No one has joined the Great Slaying!" + i.commands.ravi.version = "This command is disabled outside of MHFZZ" - strings["ravienteBerserk"] = " is being held!" - strings["ravienteExtreme"] = " is being held!" - strings["ravienteExtremeLimited"] = " is being held!" - strings["ravienteBerserkSmall"] = " is being held!" + i.raviente.berserk = " is being held!" + i.raviente.extreme = " is being held!" + i.raviente.extremeLimited = " is being held!" + i.raviente.berserkSmall = " is being held!" - strings["guildInviteName"] = "Invitation!" - strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?" + i.guild.invite.title = "Invitation!" + i.guild.invite.body = "You have been invited to join\n「%s」\nDo you want to accept?" - strings["guildInviteSuccessName"] = "Success!" - strings["guildInviteSuccess"] = "You have successfully joined\n「%s」." + i.guild.invite.success.title = "Success!" + i.guild.invite.success.body = "You have successfully joined\n「%s」." - strings["guildInviteAcceptedName"] = "Accepted" - strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」." + i.guild.invite.accepted.title = "Accepted" + i.guild.invite.accepted.body = "The recipient accepted your invitation to join\n「%s」." - strings["guildInviteRejectName"] = "Rejected" - strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」." + i.guild.invite.rejected.title = "Rejected" + i.guild.invite.rejected.body = "You rejected the invitation to join\n「%s」." - strings["guildInviteDeclinedName"] = "Declined" - strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」." + i.guild.invite.declined.title = "Declined" + i.guild.invite.declined.body = "The recipient declined your invitation to join\n「%s」." } - return strings + return i } From e0615dcd0c76f46a4062aec25027457ecd5b774b Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 3 Jan 2024 19:08:45 +1100 Subject: [PATCH 227/269] add support for operator accounts & bans --- patch-schema/op-accounts.sql | 12 ++++++++++++ server/channelserver/handlers_cast_binary.go | 20 ++++++++++---------- server/channelserver/sys_channel_server.go | 20 ++++++++++++++++++++ server/channelserver/sys_session.go | 9 +++++++++ server/signserver/dbutils.go | 9 +++++++++ 5 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 patch-schema/op-accounts.sql diff --git a/patch-schema/op-accounts.sql b/patch-schema/op-accounts.sql new file mode 100644 index 000000000..bdf5dccd8 --- /dev/null +++ b/patch-schema/op-accounts.sql @@ -0,0 +1,12 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.users ADD COLUMN op boolean; + +CREATE TABLE public.bans +( + user_id integer NOT NULL, + expires timestamp with time zone, + PRIMARY KEY (user_id) +); + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 043403216..016e1f70c 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -88,7 +88,7 @@ func parseChatCommand(s *Session, command string) { args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ") switch args[0] { case commands["PSN"].Prefix: - if commands["PSN"].Enabled { + if commands["PSN"].Enabled || s.isOp() { if len(args) > 1 { var exists int s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists) @@ -107,7 +107,7 @@ func parseChatCommand(s *Session, command string) { sendDisabledCommandMessage(s, commands["PSN"]) } case commands["Reload"].Prefix: - if commands["Reload"].Enabled { + if commands["Reload"].Enabled || s.isOp() { sendServerChatMessage(s, s.server.i18n.commands.reload) var temp mhfpacket.MHFPacket deleteNotif := byteframe.NewByteFrame() @@ -168,7 +168,7 @@ func parseChatCommand(s *Session, command string) { sendDisabledCommandMessage(s, commands["Reload"]) } case commands["KeyQuest"].Prefix: - if commands["KeyQuest"].Enabled { + if commands["KeyQuest"].Enabled || s.isOp() { if s.server.erupeConfig.RealClientMode < _config.G10 { sendServerChatMessage(s, s.server.i18n.commands.kqf.version) } else { @@ -191,7 +191,7 @@ func parseChatCommand(s *Session, command string) { sendDisabledCommandMessage(s, commands["KeyQuest"]) } case commands["Rights"].Prefix: - if commands["Rights"].Enabled { + if commands["Rights"].Enabled || s.isOp() { if len(args) > 1 { v, _ := strconv.Atoi(args[1]) _, 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) @@ -207,7 +207,7 @@ func parseChatCommand(s *Session, command string) { sendDisabledCommandMessage(s, commands["Rights"]) } case commands["Course"].Prefix: - if commands["Course"].Enabled { + if commands["Course"].Enabled || s.isOp() { if len(args) > 1 { for _, course := range mhfcourse.Courses() { for _, alias := range course.Aliases() { @@ -250,7 +250,7 @@ func parseChatCommand(s *Session, command string) { sendDisabledCommandMessage(s, commands["Course"]) } case commands["Raviente"].Prefix: - if commands["Raviente"].Enabled { + if commands["Raviente"].Enabled || s.isOp() { if len(args) > 1 { if s.server.getRaviSemaphore() != nil { switch args[1] { @@ -301,7 +301,7 @@ func parseChatCommand(s *Session, command string) { sendDisabledCommandMessage(s, commands["Raviente"]) } case commands["Teleport"].Prefix: - if commands["Teleport"].Enabled { + if commands["Teleport"].Enabled || s.isOp() { if len(args) > 2 { x, _ := strconv.ParseInt(args[1], 10, 16) y, _ := strconv.ParseInt(args[2], 10, 16) @@ -324,7 +324,7 @@ func parseChatCommand(s *Session, command string) { sendDisabledCommandMessage(s, commands["Teleport"]) } case commands["Discord"].Prefix: - if commands["Discord"].Enabled { + if commands["Discord"].Enabled || s.isOp() { var _token string err := s.server.db.QueryRow(`SELECT discord_token FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&_token) if err != nil { @@ -338,9 +338,9 @@ func parseChatCommand(s *Session, command string) { sendDisabledCommandMessage(s, commands["Discord"]) } case commands["Help"].Prefix: - if commands["Help"].Enabled { + if commands["Help"].Enabled || s.isOp() { for _, command := range commands { - if command.Enabled { + if command.Enabled || s.isOp() { sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description)) } } diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 30d63241f..19bd04123 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -378,6 +378,26 @@ func (s *Server) FindSessionByCharID(charID uint32) *Session { return nil } +func (s *Server) DisconnectUser(uid uint32) { + var cid uint32 + var cids []uint32 + rows, _ := s.db.Query(`SELECT id FROM characters WHERE user_id=$1`, uid) + for rows.Next() { + rows.Scan(&cid) + cids = append(cids, cid) + } + for _, c := range s.Channels { + for _, session := range c.sessions { + for _, cid := range cids { + if session.charID == cid { + session.rawConn.Close() + break + } + } + } + } +} + func (s *Server) FindObjectByChar(charID uint32) *Object { s.stagesLock.RLock() defer s.stagesLock.RUnlock() diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index d49e5264a..a0b30adb3 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -309,3 +309,12 @@ func (s *Session) NextObjectID() uint32 { bf.Seek(0, 0) return bf.ReadUint32() } + +func (s *Session) isOp() bool { + var op bool + err := s.server.db.QueryRow(`SELECT op FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&op) + if err == nil && op { + return true + } + return false +} diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index f23d3bdcc..dadf3f074 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -244,6 +244,15 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) { return 0, SIGN_EABORT } else { if bcrypt.CompareHashAndPassword([]byte(passDB), []byte(pass)) == nil { + var bans int + err = s.db.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires IS NULL`, uid).Scan(&bans) + if err == nil && bans > 0 { + return uid, SIGN_EELIMINATE + } + err = s.db.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires > now()`, uid).Scan(&bans) + if err == nil && bans > 0 { + return uid, SIGN_ESUSPEND + } return uid, SIGN_SUCCESS } return 0, SIGN_EPASS From 2135c443d8b53dd54df5d7815824e320c4002922 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 3 Jan 2024 19:30:57 +1100 Subject: [PATCH 228/269] add ban chat command --- common/mhfcid/mhfcid.go | 54 +++++++++++++++++++ config.json | 5 ++ server/channelserver/handlers_cast_binary.go | 57 ++++++++++++++++++++ server/channelserver/sys_language.go | 22 ++++++++ 4 files changed, 138 insertions(+) create mode 100644 common/mhfcid/mhfcid.go diff --git a/common/mhfcid/mhfcid.go b/common/mhfcid/mhfcid.go new file mode 100644 index 000000000..8b951fd4e --- /dev/null +++ b/common/mhfcid/mhfcid.go @@ -0,0 +1,54 @@ +package mhfcid + +import ( + "math" +) + +// ConvertCID converts a MHF Character ID String to integer +// +// Banned characters: 0, I, O, S +func ConvertCID(ID string) (r uint32) { + if len(ID) != 6 { + return + } + + m := map[rune]uint32{ + '1': 0, + '2': 1, + '3': 2, + '4': 3, + '5': 4, + '6': 5, + '7': 6, + '8': 7, + '9': 8, + 'A': 9, + 'B': 10, + 'C': 11, + 'D': 12, + 'E': 13, + 'F': 14, + 'G': 15, + 'H': 16, + 'J': 17, + 'K': 18, + 'L': 19, + 'M': 20, + 'N': 21, + 'P': 22, + 'Q': 23, + 'R': 24, + 'T': 25, + 'U': 26, + 'V': 27, + 'W': 28, + 'X': 29, + 'Y': 30, + 'Z': 31, + } + + for i, c := range ID { + r += m[c] * uint32(math.Pow(32, float64(i))) + } + return +} diff --git a/config.json b/config.json index 15f51d9df..c0247b9d3 100644 --- a/config.json +++ b/config.json @@ -136,6 +136,11 @@ "Enabled": true, "Description": "Generate a token to link your Discord account", "Prefix": "discord" + }, { + "Name": "Ban", + "Enabled": false, + "Description": "Ban/Temp Ban a user", + "Prefix": "ban" } ], "Courses": [ diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 016e1f70c..771a7be39 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/hex" "erupe-ce/common/byteframe" + "erupe-ce/common/mhfcid" "erupe-ce/common/mhfcourse" "erupe-ce/common/token" "erupe-ce/config" @@ -87,6 +88,62 @@ func sendServerChatMessage(s *Session, message string) { func parseChatCommand(s *Session, command string) { args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ") switch args[0] { + case commands["Ban"].Prefix: + if s.isOp() { + if len(args) > 1 { + var expiry time.Time + if len(args) > 2 { + var length int + var unit string + n, err := fmt.Sscanf(args[2], `%d%s`, &length, &unit) + if err == nil && n == 2 { + switch unit { + case "s", "second", "seconds": + expiry = time.Now().Add(time.Duration(length) * time.Second) + case "m", "mi", "minute", "minutes": + expiry = time.Now().Add(time.Duration(length) * time.Minute) + case "h", "hour", "hours": + expiry = time.Now().Add(time.Duration(length) * time.Hour) + case "d", "day", "days": + expiry = time.Now().Add(time.Duration(length) * time.Hour * 24) + case "mo", "month", "months": + expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 30) + case "y", "year", "years": + expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 365) + } + } else { + sendServerChatMessage(s, s.server.i18n.commands.ban.error) + return + } + } + cid := mhfcid.ConvertCID(args[1]) + if cid > 0 { + var uid uint32 + var uname string + err := s.server.db.QueryRow(`SELECT id, username FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, cid).Scan(&uid, &uname) + if err == nil { + if expiry.IsZero() { + s.server.db.Exec(`INSERT INTO bans VALUES ($1) + ON CONFLICT (user_id) DO UPDATE SET expires=NULL`, uid) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname)) + } else { + s.server.db.Exec(`INSERT INTO bans VALUES ($1, $2) + ON CONFLICT (user_id) DO UPDATE SET expires=$2`, uid, expiry) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname)+fmt.Sprintf(s.server.i18n.commands.ban.length, expiry.Format(time.DateTime))) + } + s.server.DisconnectUser(uid) + } else { + sendServerChatMessage(s, s.server.i18n.commands.ban.noUser) + } + } else { + sendServerChatMessage(s, s.server.i18n.commands.ban.invalid) + } + } else { + sendServerChatMessage(s, s.server.i18n.commands.ban.error) + } + } else { + sendServerChatMessage(s, s.server.i18n.commands.noOp) + } case commands["PSN"].Prefix: if commands["PSN"].Enabled || s.isOp() { if len(args) > 1 { diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index 5feb76444..eae96dc85 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -6,6 +6,7 @@ type i18n struct { reset string } commands struct { + noOp string disabled string reload string kqf struct { @@ -38,6 +39,13 @@ type i18n struct { discord struct { success string } + ban struct { + success string + noUser string + invalid string + error string + length string + } ravi struct { noCommand string start struct { @@ -95,6 +103,7 @@ func getLangStrings(s *Server) i18n { i.language = "日本語" i.cafe.reset = "%d/%dにリセット" + i.commands.noOp = "You don't have permission to use this command" i.commands.disabled = "%sのコマンドは無効です" i.commands.reload = "リロードします" i.commands.kqf.get = "現在のキークエストフラグ:%x" @@ -115,6 +124,12 @@ func getLangStrings(s *Server) i18n { i.commands.discord.success = "あなたのDiscordトークン:%s" + i.commands.ban.noUser = "Could not find user" + i.commands.ban.success = "Successfully banned %s" + i.commands.ban.invalid = "Invalid Character ID" + i.commands.ban.error = "Error in command. Format: %s [length]" + i.commands.ban.length = " until %s" + i.commands.ravi.noCommand = "ラヴィコマンドが指定されていません" i.commands.ravi.start.success = "大討伐を開始します" i.commands.ravi.start.error = "大討伐は既に開催されています" @@ -150,6 +165,7 @@ func getLangStrings(s *Server) i18n { i.language = "English" i.cafe.reset = "Resets on %d/%d" + i.commands.noOp = "You don't have permission to use this command" i.commands.disabled = "%s command is disabled" i.commands.reload = "Reloading players..." i.commands.kqf.get = "KQF: %x" @@ -170,6 +186,12 @@ func getLangStrings(s *Server) i18n { i.commands.discord.success = "Your Discord token: %s" + i.commands.ban.noUser = "Could not find user" + i.commands.ban.success = "Successfully banned %s" + i.commands.ban.invalid = "Invalid Character ID" + i.commands.ban.error = "Error in command. Format: %s [length]" + i.commands.ban.length = " until %s" + i.commands.ravi.noCommand = "No Raviente command specified!" i.commands.ravi.start.success = "The Great Slaying will begin in a moment" i.commands.ravi.start.error = "The Great Slaying has already begun!" From f73bdd7445ca2a5e210d81a1ee2c21a40dcfa052 Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 5 Jan 2024 02:39:25 +1100 Subject: [PATCH 229/269] rewrite CastBinary payload handling --- network/binpacket/msg_bin_chat.go | 3 +- server/channelserver/handlers_cast_binary.go | 40 +++++++++----------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/network/binpacket/msg_bin_chat.go b/network/binpacket/msg_bin_chat.go index 44bd587ca..b39a43795 100644 --- a/network/binpacket/msg_bin_chat.go +++ b/network/binpacket/msg_bin_chat.go @@ -11,7 +11,8 @@ type ChatType uint8 // Chat types const ( - ChatTypeLocal ChatType = 1 + ChatTypeWorld ChatType = 0 + ChatTypeStage = 1 ChatTypeGuild = 2 ChatTypeAlliance = 3 ChatTypeParty = 4 diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 043403216..0f6b736e7 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -377,24 +377,20 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { // Parse out the real casted binary payload var msgBinTargeted *binpacket.MsgBinTargeted - var authorLen, msgLen uint16 - var msg []byte - - isDiceCommand := false + var message, author string + var returnToSender bool if pkt.MessageType == BinaryMessageTypeChat { tmp.SetLE() - tmp.Seek(int64(0), 0) - _ = tmp.ReadUint32() - authorLen = tmp.ReadUint16() - msgLen = tmp.ReadUint16() - msg = tmp.ReadNullTerminatedBytes() + tmp.Seek(8, 0) + message = string(tmp.ReadNullTerminatedBytes()) + author = string(tmp.ReadNullTerminatedBytes()) } // Customise payload realPayload := pkt.RawDataPayload if pkt.BroadcastType == BroadcastTypeTargeted { tmp.SetBE() - tmp.Seek(int64(0), 0) + tmp.Seek(0, 0) msgBinTargeted = &binpacket.MsgBinTargeted{} err := msgBinTargeted.Parse(tmp) if err != nil { @@ -403,18 +399,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { } realPayload = msgBinTargeted.RawDataPayload } else if pkt.MessageType == BinaryMessageTypeChat { - if msgLen == 6 && string(msg) == "@dice" { - isDiceCommand = true - roll := byteframe.NewByteFrame() - roll.WriteInt16(1) // Unk - roll.SetLE() - roll.WriteUint16(4) // Unk - roll.WriteUint16(authorLen) - dice := fmt.Sprintf("%d", token.RNG().Intn(100)+1) - roll.WriteUint16(uint16(len(dice) + 1)) - roll.WriteNullTerminatedBytes([]byte(dice)) - roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes()) - realPayload = roll.Data() + if message == "@dice" { + returnToSender = true + m := binpacket.MsgBinChat{ + Type: BinaryMessageTypeChat, + Flags: 4, + Message: fmt.Sprintf(`%d`, token.RNG().Intn(100)+1), + SenderName: author, + } + bf := byteframe.NewByteFrame() + bf.SetLE() + m.Build(bf) + realPayload = bf.Data() } else { bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) bf.SetLE() From c8e21387c0f8b5b38027e180e6b60d1d4fb8c595 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 6 Jan 2024 17:43:25 +1100 Subject: [PATCH 230/269] rewrite CastBinary payload handling --- server/channelserver/handlers_cast_binary.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 0f6b736e7..97fc34c95 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -439,8 +439,8 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { case BroadcastTypeWorld: s.server.WorldcastMHF(resp, s, nil) case BroadcastTypeStage: - if isDiceCommand { - s.stage.BroadcastMHF(resp, nil) // send dice result back to caller + if returnToSender { + s.stage.BroadcastMHF(resp, nil) } else { s.stage.BroadcastMHF(resp, s) } From ba7321b535d01e4f95d3640a4767c7a30906588b Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 10 Jan 2024 19:29:51 +1100 Subject: [PATCH 231/269] fix stringstack & MoveStage error --- common/stringstack/stringstack.go | 17 +---------------- server/channelserver/handlers_stage.go | 8 -------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/common/stringstack/stringstack.go b/common/stringstack/stringstack.go index 9f6b646ae..a7ddef31e 100644 --- a/common/stringstack/stringstack.go +++ b/common/stringstack/stringstack.go @@ -6,8 +6,7 @@ import ( // StringStack is a basic LIFO "stack" for storing strings. type StringStack struct { - Locked bool - stack []string + stack []string } // New creates a new instance of StringStack @@ -20,20 +19,6 @@ func (s *StringStack) Set(v string) { s.stack = []string{v} } -// Lock freezes the StringStack -func (s *StringStack) Lock() { - if !s.Locked { - s.Locked = true - } -} - -// Unlock unfreezes the StringStack -func (s *StringStack) Unlock() { - if s.Locked { - s.Locked = false - } -} - // Push pushes a string onto the stack. func (s *StringStack) Push(v string) { s.stack = append(s.stack, v) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index a9de55b28..bc0b3689e 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -157,7 +157,6 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { s.stage.reservedClientSlots[s.charID] = false s.stage.Unlock() s.stageMoveStack.Push(s.stage.id) - s.stageMoveStack.Lock() } if s.reservationStage != nil { @@ -171,7 +170,6 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysBackStage) // Transfer back to the saved stage ID before the previous move or enter. - s.stageMoveStack.Unlock() backStage, err := s.stageMoveStack.Pop() if backStage == "" || err != nil { backStage = "sl1Ns200p0a0u0" @@ -190,12 +188,6 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysMoveStage) - - // Set a new move stack from the given stage ID - if !s.stageMoveStack.Locked { - s.stageMoveStack.Set(pkt.StageID) - } - doStageTransfer(s, pkt.AckHandle, pkt.StageID) } From 4ccb3af5acec2ee79eab4b139e0733d7bf64d172 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Jan 2024 23:00:44 +1100 Subject: [PATCH 232/269] simplify cryptography --- network/crypt_conn.go | 13 +++----- network/crypto/crypto.go | 60 +++++++++++++---------------------- network/crypto/crypto_test.go | 4 +-- 3 files changed, 29 insertions(+), 48 deletions(-) diff --git a/network/crypt_conn.go b/network/crypt_conn.go index 2fc302b18..de9181855 100644 --- a/network/crypt_conn.go +++ b/network/crypt_conn.go @@ -4,11 +4,10 @@ import ( "encoding/hex" "errors" _config "erupe-ce/config" + "erupe-ce/network/crypto" "fmt" "io" "net" - - "erupe-ce/network/crypto" ) // CryptConn represents a MHF encrypted two-way connection, @@ -67,7 +66,7 @@ func (cc *CryptConn) ReadPacket() ([]byte, error) { cc.readKeyRot = uint32(cph.KeyRotDelta) * (cc.readKeyRot + 1) } - out, combinedCheck, check0, check1, check2 := crypto.Decrypt(encryptedPacketBody, cc.readKeyRot, nil) + out, combinedCheck, check0, check1, check2 := crypto.Crypto(encryptedPacketBody, cc.readKeyRot, false, nil) if cph.Check0 != check0 || cph.Check1 != check1 || cph.Check2 != check2 { fmt.Printf("got c0 %X, c1 %X, c2 %X\n", check0, check1, check2) fmt.Printf("want c0 %X, c1 %X, c2 %X\n", cph.Check0, cph.Check1, cph.Check2) @@ -77,7 +76,7 @@ func (cc *CryptConn) ReadPacket() ([]byte, error) { // Attempt to bruteforce it. fmt.Println("Crypto out of sync? Attempting bruteforce") for key := byte(0); key < 255; key++ { - out, combinedCheck, check0, check1, check2 = crypto.Decrypt(encryptedPacketBody, 0, &key) + out, combinedCheck, check0, check1, check2 = crypto.Crypto(encryptedPacketBody, 0, false, &key) //fmt.Printf("Key: 0x%X\n%s\n", key, hex.Dump(out)) if cph.Check0 == check0 && cph.Check1 == check1 && cph.Check2 == check2 { fmt.Printf("Bruceforce successful, override key: 0x%X\n", key) @@ -106,7 +105,7 @@ func (cc *CryptConn) SendPacket(data []byte) error { } // Encrypt the data - encData, combinedCheck, check0, check1, check2 := crypto.Encrypt(data, cc.sendKeyRot, nil) + encData, combinedCheck, check0, check1, check2 := crypto.Crypto(data, cc.sendKeyRot, true, nil) header := &CryptPacketHeader{} header.Pf0 = byte(((uint(len(encData)) >> 12) & 0xF3) | 3) @@ -123,9 +122,7 @@ func (cc *CryptConn) SendPacket(data []byte) error { return err } - cc.conn.Write(headerBytes) - cc.conn.Write(encData) - + cc.conn.Write(append(headerBytes, encData...)) cc.sentPackets++ cc.prevSendPacketCombinedCheck = combinedCheck diff --git a/network/crypto/crypto.go b/network/crypto/crypto.go index 87746fa90..59e631786 100644 --- a/network/crypto/crypto.go +++ b/network/crypto/crypto.go @@ -6,46 +6,30 @@ var ( _sharedCryptKey = []byte{0xDD, 0xA8, 0x5F, 0x1E, 0x57, 0xAF, 0xC0, 0xCC, 0x43, 0x35, 0x8F, 0xBB, 0x6F, 0xE6, 0xA1, 0xD6, 0x60, 0xB9, 0x1A, 0xAE, 0x20, 0x49, 0x24, 0x81, 0x21, 0xFE, 0x86, 0x2B, 0x98, 0xB7, 0xB3, 0xD2, 0x91, 0x01, 0x3A, 0x4C, 0x65, 0x92, 0x1C, 0xF4, 0xBE, 0xDD, 0xD9, 0x08, 0xE6, 0x81, 0x98, 0x1B, 0x8D, 0x60, 0xF3, 0x6F, 0xA1, 0x47, 0x24, 0xF1, 0x53, 0x45, 0xC8, 0x7B, 0x88, 0x80, 0x4E, 0x36, 0xC3, 0x0D, 0xC9, 0xD6, 0x8B, 0x08, 0x19, 0x0B, 0xA5, 0xC1, 0x11, 0x4C, 0x60, 0xF8, 0x5D, 0xFC, 0x15, 0x68, 0x7E, 0x32, 0xC0, 0x50, 0xAB, 0x64, 0x1F, 0x8A, 0xD4, 0x08, 0x39, 0x7F, 0xC2, 0xFB, 0xBA, 0x6C, 0xF0, 0xE6, 0xB0, 0x31, 0x10, 0xC1, 0xBF, 0x75, 0x43, 0xBB, 0x18, 0x04, 0x0D, 0xD1, 0x97, 0xF7, 0x23, 0x21, 0x83, 0x8B, 0xCA, 0x25, 0x2B, 0xA3, 0x03, 0x13, 0xEA, 0xAE, 0xFE, 0xF0, 0xEB, 0xFD, 0x85, 0x57, 0x53, 0x65, 0x41, 0x2A, 0x40, 0x99, 0xC0, 0x94, 0x65, 0x7E, 0x7C, 0x93, 0x82, 0xB0, 0xB3, 0xE5, 0xC0, 0x21, 0x09, 0x84, 0xD5, 0xEF, 0x9F, 0xD1, 0x7E, 0xDC, 0x4D, 0xF5, 0x7E, 0xCD, 0x45, 0x3C, 0x7F, 0xF5, 0x59, 0x98, 0xC6, 0x55, 0xFC, 0x9F, 0xA3, 0xB7, 0x74, 0xEE, 0x31, 0x98, 0xE6, 0xB7, 0xBE, 0x26, 0xF4, 0x3C, 0x76, 0xF1, 0x23, 0x7E, 0x02, 0x4E, 0x3C, 0xD1, 0xC7, 0x28, 0x23, 0x73, 0xC4, 0xD9, 0x5E, 0x0D, 0xA1, 0x80, 0xA5, 0xAA, 0x26, 0x0A, 0xA3, 0x44, 0x82, 0x74, 0xE6, 0x3C, 0x44, 0x27, 0x51, 0x0D, 0x5F, 0xC7, 0x9C, 0xD6, 0x63, 0x67, 0xA5, 0x27, 0x97, 0x38, 0xFB, 0x2D, 0xD3, 0xD6, 0x60, 0x25, 0x83, 0x4D, 0x37, 0x5B, 0x40, 0x59, 0x11, 0x77, 0x51, 0x11, 0x14, 0x18, 0x07, 0x63, 0xB1, 0x34, 0x3D, 0xB8, 0x60, 0x13, 0xC2, 0xE8, 0x13, 0x82} ) -// Encrypt encrypts the given data using MHF's custom encryption+checksum method. -// if a overrideByteKey value is supplied (!= nil), it will be used to override the derived/truncated key byte. -func Encrypt(data []byte, key uint32, overrideByteKey *byte) (outputData []byte, combinedCheck uint16, check0 uint16, check1 uint16, check2 uint16) { - return _generalCrypt(data, key, 0, overrideByteKey) -} - -// Decrypt decrypts the given data using MHF's custom decryption+checksum method. -// if a overrideByteKey value is supplied (!= nil), it will be used to override the derived/truncated key byte. -func Decrypt(data []byte, key uint32, overrideByteKey *byte) (outputData []byte, combinedCheck uint16, check0 uint16, check1 uint16, check2 uint16) { - return _generalCrypt(data, key, 1, overrideByteKey) -} - -// _generalCrypt is a generalized MHF crypto function that can perform both encryption and decryption, +// Crypto is a generalized MHF crypto function that can perform both encryption and decryption, // these two crypto operations are combined into a single function because they shared most of their logic. -// encrypt: cryptType==0 -// decrypt: cryptType==1 -func _generalCrypt(data []byte, rotKey uint32, cryptType int, overrideByteKey *byte) ([]byte, uint16, uint16, uint16, uint16) { +func Crypto(data []byte, rotKey uint32, encrypt bool, overrideByteKey *byte) ([]byte, uint16, uint16, uint16, uint16) { cryptKeyTruncByte := byte(((rotKey >> 1) % 999983) & 0xFF) if overrideByteKey != nil { cryptKeyTruncByte = *overrideByteKey } - derivedCryptKey := int32((uint32(len(data)) * (uint32(cryptKeyTruncByte) + 1)) & 0xFFFFFFFF) + derivedCryptKey := (uint32(len(data)) * (uint32(cryptKeyTruncByte) + 1)) & 0xFFFFFFFF sharedBufIdx := byte(1) - accumulator0 := uint32(0) - accumulator1 := uint32(0) - accumulator2 := uint32(0) + var accumulator0, accumulator1, accumulator2 uint32 var outputData []byte - if cryptType == 0 { + if encrypt { for i := 0; i < len(data); i++ { // Do the encryption for this iteration - encKeyIdx := int32(((uint32(derivedCryptKey) >> 10) ^ uint32(data[i])) & 0xFF) - derivedCryptKey = (0x4FD * (derivedCryptKey + 1)) + encKeyIdx := ((derivedCryptKey >> 10) ^ uint32(data[i])) & 0xFF + derivedCryptKey = 1277*derivedCryptKey + 1277 encKeyByte := _encryptKey[encKeyIdx] // Update the checksum accumulators. - accumulator2 = uint32((accumulator2 + (uint32(sharedBufIdx) * uint32(data[i]))) & 0xFFFFFFFF) - accumulator1 = uint32((accumulator1 + uint32(encKeyIdx)) & 0xFFFFFFFF) - accumulator0 = uint32((accumulator0 + (uint32(encKeyByte)<<(i&7))&0xFFFFFFFF) & 0xFFFFFFFF) + accumulator2 = accumulator2 + (uint32(sharedBufIdx) * uint32(data[i])) + accumulator1 = accumulator1 + encKeyIdx + accumulator0 = accumulator0 + uint32(encKeyByte)<<(i&7) // Append the output. outputData = append(outputData, _sharedCryptKey[sharedBufIdx]^encKeyByte) @@ -53,32 +37,32 @@ func _generalCrypt(data []byte, rotKey uint32, cryptType int, overrideByteKey *b // Update the sharedBufIdx for the next iteration. sharedBufIdx = data[i] } - - } else if cryptType == 1 { + } else { for i := 0; i < len(data); i++ { // Do the decryption for this iteration oldSharedBufIdx := sharedBufIdx tIdx := data[i] ^ _sharedCryptKey[sharedBufIdx] decKeyByte := _decryptKey[tIdx] - sharedBufIdx = byte(((uint32(derivedCryptKey) >> 10) ^ uint32(decKeyByte)) & 0xFF) + sharedBufIdx = byte((derivedCryptKey >> 10) ^ uint32(decKeyByte)) // Update the checksum accumulators. - accumulator0 = (accumulator0 + ((uint32(tIdx) << (i & 7)) & 0xFFFFFFFF)) - accumulator1 = (accumulator1 + uint32(decKeyByte)) & 0xFFFFFFFF - accumulator2 = (accumulator2 + ((uint32(oldSharedBufIdx) * uint32(sharedBufIdx)) & 0xFFFFFFFF)) & 0xFFFFFFFF + accumulator0 = accumulator0 + uint32(tIdx)<<(i&7) + accumulator1 = accumulator1 + uint32(decKeyByte) + accumulator2 = accumulator2 + uint32(oldSharedBufIdx)*uint32(sharedBufIdx) // Append the output. outputData = append(outputData, sharedBufIdx) // Update the key pos for next iteration. - derivedCryptKey = (0x4FD * (derivedCryptKey + 1)) + derivedCryptKey = 1277*derivedCryptKey + 1277 } } - combinedCheck := uint16((accumulator1 + (accumulator0 >> 1) + (accumulator2 >> 2)) & 0xFFFF) - check0 := uint16((accumulator0 ^ ((accumulator0 & 0xFFFF0000) >> 16)) & 0xFFFF) - check1 := uint16((accumulator1 ^ ((accumulator1 & 0xFFFF0000) >> 16)) & 0xFFFF) - check2 := uint16((accumulator2 ^ ((accumulator2 & 0xFFFF0000) >> 16)) & 0xFFFF) + var check [4]uint16 + check[0] = uint16(accumulator1 + (accumulator0 >> 1) + (accumulator2 >> 2)) + check[1] = uint16(accumulator0 ^ ((accumulator0 & 0xFFFF0000) >> 16)) + check[2] = uint16(accumulator1 ^ ((accumulator1 & 0xFFFF0000) >> 16)) + check[3] = uint16(accumulator2 ^ ((accumulator2 & 0xFFFF0000) >> 16)) - return outputData, combinedCheck, check0, check1, check2 + return outputData, check[0], check[1], check[2], check[3] } diff --git a/network/crypto/crypto_test.go b/network/crypto/crypto_test.go index 32ff7ee7c..5093e429f 100644 --- a/network/crypto/crypto_test.go +++ b/network/crypto/crypto_test.go @@ -65,7 +65,7 @@ func TestEncrypt(t *testing.T) { for k, tt := range tests { testname := fmt.Sprintf("encrypt_test_%d", k) t.Run(testname, func(t *testing.T) { - out, cc, c0, c1, c2 := Encrypt(tt.decryptedData, tt.key, nil) + out, cc, c0, c1, c2 := Crypto(tt.decryptedData, tt.key, true, nil) if cc != tt.ecc { t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc) } else if c0 != tt.ec0 { @@ -86,7 +86,7 @@ func TestDecrypt(t *testing.T) { for k, tt := range tests { testname := fmt.Sprintf("decrypt_test_%d", k) t.Run(testname, func(t *testing.T) { - out, cc, c0, c1, c2 := Decrypt(tt.encryptedData, tt.key, nil) + out, cc, c0, c1, c2 := Crypto(tt.decryptedData, tt.key, false, nil) if cc != tt.ecc { t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc) } else if c0 != tt.ec0 { From a7bf38388c899b7413134917270f1ddaf53c5562 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Jan 2024 23:01:32 +1100 Subject: [PATCH 233/269] fix package collision --- server/signserver/dbutils.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index f23d3bdcc..c8e140ce6 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -198,17 +198,17 @@ func (s *Server) checkToken(uid uint32) (bool, error) { } func (s *Server) registerUidToken(uid uint32) (uint32, string, error) { - token := token.Generate(16) + _token := token.Generate(16) var tid uint32 - err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, token).Scan(&tid) - return tid, token, err + err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, _token).Scan(&tid) + return tid, _token, err } func (s *Server) registerPsnToken(psn string) (uint32, string, error) { - token := token.Generate(16) + _token := token.Generate(16) var tid uint32 - err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, token).Scan(&tid) - return tid, token, err + err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, _token).Scan(&tid) + return tid, _token, err } func (s *Server) validateToken(token string, tokenID uint32) bool { From 26854760223fbcc1c10fc459b8d108a277f1cd29 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Jan 2024 23:01:53 +1100 Subject: [PATCH 234/269] check against unwrapped error --- server/signserver/dbutils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index c8e140ce6..96503117c 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -229,7 +229,7 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) { var passDB string err := s.db.QueryRow(`SELECT id, password FROM users WHERE username = $1`, user).Scan(&uid, &passDB) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { s.logger.Info("User not found", zap.String("User", user)) if s.erupeConfig.AutoCreateAccount { uid, err = s.registerDBAccount(user, pass) From af29ee637e1b97837cd78fb5a168cdf95d9f79be Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Jan 2024 23:03:53 +1100 Subject: [PATCH 235/269] minor session optimisations --- server/channelserver/sys_session.go | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index d49e5264a..6c958b155 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -86,13 +86,11 @@ func NewSession(server *Server, conn net.Conn) *Session { // Start starts the session packet send and recv loop(s). func (s *Session) Start() { - go func() { - s.logger.Debug("New connection", zap.String("RemoteAddr", s.rawConn.RemoteAddr().String())) - // Unlike the sign and entrance server, - // the client DOES NOT initalize the channel connection with 8 NULL bytes. - go s.sendLoop() - s.recvLoop() - }() + s.logger.Debug("New connection", zap.String("RemoteAddr", s.rawConn.RemoteAddr().String())) + // Unlike the sign and entrance server, + // the client DOES NOT initalize the channel connection with 8 NULL bytes. + go s.sendLoop() + go s.recvLoop() } // QueueSend queues a packet (raw []byte) to be sent. @@ -150,16 +148,19 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) { } func (s *Session) sendLoop() { + var pkt packet for { if s.closed { return } - pkt := <-s.sendPackets - err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...)) - if err != nil { - s.logger.Warn("Failed to send packet") + for len(s.sendPackets) > 0 { + pkt = <-s.sendPackets + err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...)) + if err != nil { + s.logger.Warn("Failed to send packet") + } } - time.Sleep(10 * time.Millisecond) + time.Sleep(5 * time.Millisecond) } } @@ -178,14 +179,13 @@ func (s *Session) recvLoop() { s.logger.Info(fmt.Sprintf("[%s] Disconnected", s.Name)) logoutPlayer(s) return - } - if err != nil { + } else if err != nil { s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err)) logoutPlayer(s) return } s.handlePacketGroup(pkt) - time.Sleep(10 * time.Millisecond) + time.Sleep(5 * time.Millisecond) } } From 1a6a9da308881d4242ef739a0ff83aa779ec705b Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 14 Jan 2024 14:09:51 +1100 Subject: [PATCH 236/269] add logging to EnumerateQuest --- server/channelserver/handlers_quest.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 138199ea4..699830ce4 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -249,6 +249,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { err = rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDays, &inactiveDays) if err != nil { + s.logger.Error("Failed to scan event quest row", zap.Error(err)) continue } @@ -283,9 +284,11 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { data, err := makeEventQuest(s, rows) if err != nil { + s.logger.Error("Failed to make event quest", zap.Error(err)) continue } else { if len(data) > 896 || len(data) < 352 { + s.logger.Error("Invalid quest data length", zap.Int("len", len(data))) continue } else { totalCount++ From 6b54e40cc6891257f020b0e598cd814c38b1a184 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 14 Jan 2024 14:13:11 +1100 Subject: [PATCH 237/269] add tuneValue comments --- server/channelserver/handlers_quest.go | 54 +++++++++++++------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 699830ce4..b403f134d 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -322,40 +322,40 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { {ID: 67, Value: 1}, {ID: 80, Value: 1}, {ID: 94, Value: 1}, - {ID: 1010, Value: 300}, - {ID: 1011, Value: 300}, - {ID: 1012, Value: 300}, - {ID: 1013, Value: 300}, - {ID: 1014, Value: 200}, - {ID: 1015, Value: 200}, - {ID: 1021, Value: 400}, + {ID: 1010, Value: 300}, // get_hrp_rate_netcafe + {ID: 1011, Value: 300}, // get_zeny_rate_netcafe + {ID: 1012, Value: 300}, // get_hrp_rate_ncource + {ID: 1013, Value: 300}, // get_zeny_rate_ncource + {ID: 1014, Value: 200}, // get_hrp_rate_premium + {ID: 1015, Value: 200}, // get_zeny_rate_premium + {ID: 1021, Value: 400}, // get_gcp_rate_assist {ID: 1023, Value: 8}, - {ID: 1024, Value: 150}, + {ID: 1024, Value: 150}, // get_hrp_rate_ptbonus {ID: 1025, Value: 1}, {ID: 1026, Value: 999}, // get_grank_cap - {ID: 1027, Value: 100}, - {ID: 1028, Value: 100}, - {ID: 1030, Value: 8}, - {ID: 1031, Value: 100}, + {ID: 1027, Value: 100}, // get_exchange_rate_festa + {ID: 1028, Value: 100}, // get_exchange_rate_cafe + {ID: 1030, Value: 8}, // get_gquest_cap + {ID: 1031, Value: 100}, // get_exchange_rate_guild (GCP) {ID: 1032, Value: 0}, // isValid_partner {ID: 1044, Value: 200}, // get_rate_tload_time_out {ID: 1045, Value: 0}, // get_rate_tower_treasure_preset - {ID: 1046, Value: 99}, - {ID: 1048, Value: 0}, // get_rate_tower_log_disable - {ID: 1049, Value: 10}, // get_rate_tower_gem_max - {ID: 1050, Value: 1}, // get_rate_tower_gem_set + {ID: 1046, Value: 99}, // get_hunter_life_cap + {ID: 1048, Value: 0}, // get_rate_tower_log_disable + {ID: 1049, Value: 10}, // get_rate_tower_gem_max + {ID: 1050, Value: 1}, // get_rate_tower_gem_set {ID: 1051, Value: 200}, - {ID: 1052, Value: 200}, - {ID: 1063, Value: 50000}, - {ID: 1064, Value: 50000}, - {ID: 1065, Value: 25000}, - {ID: 1066, Value: 25000}, - {ID: 1067, Value: 90}, // get_lobby_member_upper_for_making_room Lv1? - {ID: 1068, Value: 80}, // get_lobby_member_upper_for_making_room Lv2? - {ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3? - {ID: 1072, Value: 300}, // get_rate_premium_ravi_tama - {ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama - {ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama + {ID: 1052, Value: 200}, // get_trp_rate_premium + {ID: 1063, Value: 50000}, // get_nboost_quest_point_from_hrank + {ID: 1064, Value: 50000}, // get_nboost_quest_point_from_srank + {ID: 1065, Value: 25000}, // get_nboost_quest_point_from_grank + {ID: 1066, Value: 25000}, // get_nboost_quest_point_from_gsrank + {ID: 1067, Value: 90}, // get_lobby_member_upper_for_making_room Lv1? + {ID: 1068, Value: 80}, // get_lobby_member_upper_for_making_room Lv2? + {ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3? + {ID: 1072, Value: 300}, // get_rate_premium_ravi_tama + {ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama + {ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama {ID: 1078, Value: 0}, {ID: 1079, Value: 1}, {ID: 1080, Value: 1}, From 59eafbe3d5caca3a2686d144a6648aed72ce377d Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 16 Jan 2024 19:50:41 +1100 Subject: [PATCH 238/269] fix EnumerateQuest rotation --- server/channelserver/handlers_quest.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index b403f134d..084fab95f 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -163,7 +163,7 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { data := loadQuestFile(s, questId) if data == nil { - return nil, fmt.Errorf("failed to load quest file") + return nil, fmt.Errorf(fmt.Sprintf("failed to load quest file (%d)", questId)) } bf := byteframe.NewByteFrame() @@ -278,7 +278,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { // Check if the quest is currently active if currentTime.Before(startTime) || currentTime.After(startTime.Add(time.Duration(activeDays)*24*time.Hour)) { - break + continue } } From 4a7f7b80410d577b0278a4caf2d012c7255b7857 Mon Sep 17 00:00:00 2001 From: stratic-dev Date: Mon, 22 Jan 2024 20:06:40 +0000 Subject: [PATCH 239/269] Added docker and docker-compose --- Dockerfile | 14 +++++++++++ docker/docker-compose.yml | 49 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 Dockerfile create mode 100644 docker/docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..37015b19d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.21-alpine3.19 + +ENV GO111MODULE=on + +WORKDIR /app/erupe + +COPY go.mod . +COPY go.sum . + +RUN go mod download + +COPY . . + +CMD [ "go", "run", "." ] \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..e7ff0e960 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,49 @@ +version: "3.9" +# 1. docker-compose up db pgadmin +# 2. Use pgadmin to restore db and also apply patch-schema +# 3. Configure the config.json example. in docker you can point to the service name for the database i.e db +# 4. In seperate terminal docker-compose up server +# 5. If all went well happy hunting! +services: + db: + image: postgres + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + - POSTGRES_DB=erupe + ports: + - "5432:5432" + pgadmin: + image: dpage/pgadmin4 + restart: always + environment: + PGADMIN_DEFAULT_EMAIL: test@test.com + PGADMIN_DEFAULT_PASSWORD: root + ports: + - "5050:80" + depends_on: + - db + server: + depends_on: + - db + # If using prebuilt container change paths and config + build: + context: ../ + volumes: + - ../config.json:/app/erupe/config.json + - ../bin:/app/erupe/bin + - ../savedata:/app/erupe/savedata + ports: + - "53312:53312" #Sign V1 + - "8080:8080" #Sign V2 + - "53310:53310" #Entrance + # Channels + - "54001:54001" + - "54002:54002" + - "54003:54003" + - "54004:54004" + - "54005:54005" + - "54006:54006" + - "54007:54007" + - "54008:54008" + From 76e62c6af29f103a0edc8b2a66b5c1d5c51f7605 Mon Sep 17 00:00:00 2001 From: stratic-dev Date: Mon, 22 Jan 2024 22:35:45 +0000 Subject: [PATCH 240/269] Added readme and comments to docker-compose --- docker/README.md | 64 +++++++++++++++++++++++++++++++++++++++ docker/docker-compose.yml | 6 ++-- 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 docker/README.md diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..11a5cb04a --- /dev/null +++ b/docker/README.md @@ -0,0 +1,64 @@ +# Docker for erupe + +## Building the container +Run the following from the route of the soruce folder. In this example we give it the tag of dev to seperate it from any other container verions. +```bash +docker build . -t erupe:dev +``` +## Running the container in isolation +This is just running the container. You can do volume mounts into the container for the `config.json` to tell it to communicate to a database. You will need to do this also for other folders such as `bin` and `savedata` +```bash +docker run erupe:dev +``` + +## Docker compose +Docker compose allows you to run multiple containers at once. The docker compose in this folder has 3 things set up. +- postgres +- pg admin (Admin interface to make db changes) +- erupe + +Before we get started you should make sure the database info matches whats in the docker compose file for the environment variables `POSTGRES_PASSWORD`,`POSTGRES_USER` and `POSTGRES_DB`. You can set the host to be the service name `db`. + +Here is a example of what you would put in the config.json if you was to leave the defaults. It is strongly recommended to change the password. +```txt +"Database": { + "Host": "db", + "Port": 5432, + "User": "postgres", + "Password": "password", + "Database": "erupe" + }, +``` + +### Running up the database for the first time +First we need to set up the database. This requires the schema and the patch schemas to be applied. This can be done by runnnig up both the db and pgadmin. + +1. Pull the remote images and build a container image for erupe +```bash +docker-compose pull +docker-compose build +``` +2. Run up pgadmin and login using the username and password provided in `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` note you will need to set up a new connection to the database internally. You will use the same host, database, username and password as above. +```bash +docker-compose run db pgadmin -d +``` +3. Use pgadmin to restore the schema using the restore functionaltiy and they query tool for the patch-schemas. + +4. Now run up the server you should see the server start correctly now. +```bash +docker-compose run server -d +``` + +## Turning off the server safely +```bash +docker-compose stop +``` +## Turning on the server again +This boots the db pgadmin and the server in a detached state +```bash +docker-compose up -d +``` +if you want all the logs and you want it to be in an attached state +```bash +docker-compose up +``` diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e7ff0e960..3503326db 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -8,6 +8,7 @@ services: db: image: postgres environment: + # (Make sure these match config.json) - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password - POSTGRES_DB=erupe @@ -17,8 +18,8 @@ services: image: dpage/pgadmin4 restart: always environment: - PGADMIN_DEFAULT_EMAIL: test@test.com - PGADMIN_DEFAULT_PASSWORD: root + PGADMIN_DEFAULT_EMAIL: user@pgadmin.com + PGADMIN_DEFAULT_PASSWORD: password ports: - "5050:80" depends_on: @@ -34,6 +35,7 @@ services: - ../bin:/app/erupe/bin - ../savedata:/app/erupe/savedata ports: + # (Make sure these match config.json) - "53312:53312" #Sign V1 - "8080:8080" #Sign V2 - "53310:53310" #Entrance From 463ceba555a13e27af64029371021b6a5c44bbc7 Mon Sep 17 00:00:00 2001 From: stratic-dev Date: Mon, 22 Jan 2024 22:45:31 +0000 Subject: [PATCH 241/269] Added github action --- .github/workflows/docker.yml | 48 ++++++++++++++++++++++++++++++++++++ config.json | 4 +-- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 000000000..f49ec5d7c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,48 @@ +name: Create and publish a Docker image + +# Configures this workflow to run every time a tag is created. +on: + push: + tags: + - '*' + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/config.json b/config.json index c0247b9d3..d866c3855 100644 --- a/config.json +++ b/config.json @@ -157,10 +157,10 @@ {"Name": "EXRenewing", "Enabled": true} ], "Database": { - "Host": "localhost", + "Host": "db", "Port": 5432, "User": "postgres", - "Password": "", + "Password": "password", "Database": "erupe" }, "Sign": { From abe47445876c1ddcfa08756679af80d885e1195b Mon Sep 17 00:00:00 2001 From: stratic-dev Date: Wed, 24 Jan 2024 04:35:24 +0000 Subject: [PATCH 242/269] Added db init script. Combined all schemas under schemas. Persisted updates and init --- .gitignore | 3 +- docker/README.md | 34 +-- docker/docker-compose.yml | 32 ++- docker/init/setup.sh | 22 ++ schemas/9.1-init.sql | Bin 0 -> 64231 bytes .../bundled-schema}/DistributionDemo.sql | 0 .../bundled-schema}/DivaShops.sql | 0 .../bundled-schema}/EventQuests.sql | 0 .../bundled-schema}/FPointItems.sql | 0 .../bundled-schema}/FestaDefaults.sql | 0 .../bundled-schema}/GachaDemo.sql | 0 .../bundled-schema}/NetcafeDefaults.sql | 0 .../bundled-schema}/OtherShops.sql | 0 .../bundled-schema}/RoadShopItems.sql | 0 .../bundled-schema}/ScenarioDefaults.sql | 0 .../patch-schema}/.gitkeep | 0 .../patch-schema}/00-psn-id.sql | 0 .../patch-schema}/01-wiiu-key.sql | 0 .../patch-schema}/02-tower.sql | 0 .../patch-schema}/03-event_quests.sql | 0 .../patch-schema}/04-trend-weapons.sql | 0 .../patch-schema}/05-gacha-roll-name.sql | 0 .../patch-schema}/06-goocoo-rename.sql | 0 .../patch-schema}/07-scenarios-counter.sql | 0 .../patch-schema}/08-kill-counts.sql | 0 .../patch-schema}/09-fix-guild-treasure.sql | 0 .../patch-schema}/10-rework-distributions.sql | 0 .../patch-schema}/11-event-quest-flags.sql | 0 .../patch-schema}/12-event_quest_cycling.sql | 0 .../patch-schema}/13-festa-trial-votes.sql | 0 .../patch-schema}/14-fix-fpoint-trades.sql | 0 .../patch-schema}/15-reset-goocoos.sql | 0 .../16-discord-password-resets.sql | 0 .../patch-schema}/op-accounts.sql | 0 .../patch-schema}/psn-link.sql | 0 schemas/update-schema/9.2-update.sql | 241 ++++++++++++++++++ 36 files changed, 309 insertions(+), 23 deletions(-) create mode 100644 docker/init/setup.sh create mode 100644 schemas/9.1-init.sql rename {bundled-schema => schemas/bundled-schema}/DistributionDemo.sql (100%) rename {bundled-schema => schemas/bundled-schema}/DivaShops.sql (100%) rename {bundled-schema => schemas/bundled-schema}/EventQuests.sql (100%) rename {bundled-schema => schemas/bundled-schema}/FPointItems.sql (100%) rename {bundled-schema => schemas/bundled-schema}/FestaDefaults.sql (100%) rename {bundled-schema => schemas/bundled-schema}/GachaDemo.sql (100%) rename {bundled-schema => schemas/bundled-schema}/NetcafeDefaults.sql (100%) rename {bundled-schema => schemas/bundled-schema}/OtherShops.sql (100%) rename {bundled-schema => schemas/bundled-schema}/RoadShopItems.sql (100%) rename {bundled-schema => schemas/bundled-schema}/ScenarioDefaults.sql (100%) rename {patch-schema => schemas/patch-schema}/.gitkeep (100%) rename {patch-schema => schemas/patch-schema}/00-psn-id.sql (100%) rename {patch-schema => schemas/patch-schema}/01-wiiu-key.sql (100%) rename {patch-schema => schemas/patch-schema}/02-tower.sql (100%) rename {patch-schema => schemas/patch-schema}/03-event_quests.sql (100%) rename {patch-schema => schemas/patch-schema}/04-trend-weapons.sql (100%) rename {patch-schema => schemas/patch-schema}/05-gacha-roll-name.sql (100%) rename {patch-schema => schemas/patch-schema}/06-goocoo-rename.sql (100%) rename {patch-schema => schemas/patch-schema}/07-scenarios-counter.sql (100%) rename {patch-schema => schemas/patch-schema}/08-kill-counts.sql (100%) rename {patch-schema => schemas/patch-schema}/09-fix-guild-treasure.sql (100%) rename {patch-schema => schemas/patch-schema}/10-rework-distributions.sql (100%) rename {patch-schema => schemas/patch-schema}/11-event-quest-flags.sql (100%) rename {patch-schema => schemas/patch-schema}/12-event_quest_cycling.sql (100%) rename {patch-schema => schemas/patch-schema}/13-festa-trial-votes.sql (100%) rename {patch-schema => schemas/patch-schema}/14-fix-fpoint-trades.sql (100%) rename {patch-schema => schemas/patch-schema}/15-reset-goocoos.sql (100%) rename {patch-schema => schemas/patch-schema}/16-discord-password-resets.sql (100%) rename {patch-schema => schemas/patch-schema}/op-accounts.sql (100%) rename {patch-schema => schemas/patch-schema}/psn-link.sql (100%) create mode 100644 schemas/update-schema/9.2-update.sql diff --git a/.gitignore b/.gitignore index a36e91559..4101960d2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ vendor/ savedata/*/ *.exe *.lnk -*.bat \ No newline at end of file +*.bat +/docker/db-data \ No newline at end of file diff --git a/docker/README.md b/docker/README.md index 11a5cb04a..3f45a12c0 100644 --- a/docker/README.md +++ b/docker/README.md @@ -17,6 +17,8 @@ Docker compose allows you to run multiple containers at once. The docker compose - pg admin (Admin interface to make db changes) - erupe +We automatically populate the database to the latest version on start. If you you are updating you will need to apply the new schemas manually. + Before we get started you should make sure the database info matches whats in the docker compose file for the environment variables `POSTGRES_PASSWORD`,`POSTGRES_USER` and `POSTGRES_DB`. You can set the host to be the service name `db`. Here is a example of what you would put in the config.json if you was to leave the defaults. It is strongly recommended to change the password. @@ -30,29 +32,29 @@ Here is a example of what you would put in the config.json if you was to leave t }, ``` -### Running up the database for the first time -First we need to set up the database. This requires the schema and the patch schemas to be applied. This can be done by runnnig up both the db and pgadmin. +Place this file within ./docker/config.json + +You will need to do the same for your bins place these in ./docker/bin + +# Setting up the web hosted materials +Clone the Severs repo into ./docker/Severs + +Make sure your hosts are pointing to where this is hosted -1. Pull the remote images and build a container image for erupe -```bash -docker-compose pull -docker-compose build -``` -2. Run up pgadmin and login using the username and password provided in `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` note you will need to set up a new connection to the database internally. You will use the same host, database, username and password as above. -```bash -docker-compose run db pgadmin -d -``` -3. Use pgadmin to restore the schema using the restore functionaltiy and they query tool for the patch-schemas. -4. Now run up the server you should see the server start correctly now. -```bash -docker-compose run server -d -``` ## Turning off the server safely ```bash docker-compose stop ``` + +## Turning off the server destructive +```bash +docker-compose down +``` +Make sure if you want to delete your data you delete the folders that persisted +- ./docker/savedata +- ./docker/db-data ## Turning on the server again This boots the db pgadmin and the server in a detached state ```bash diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 3503326db..a610836ee 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -14,6 +14,15 @@ services: - POSTGRES_DB=erupe ports: - "5432:5432" + volumes: + - ./db-data/:/var/lib/postgresql/data/ + - ../schemas/:/schemas/ + - ./init/setup.sh:/docker-entrypoint-initdb.d/setup.sh + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 pgadmin: image: dpage/pgadmin4 restart: always @@ -23,17 +32,19 @@ services: ports: - "5050:80" depends_on: - - db + db: + condition: service_healthy server: depends_on: - - db + db: + condition: service_healthy # If using prebuilt container change paths and config build: context: ../ volumes: - - ../config.json:/app/erupe/config.json - - ../bin:/app/erupe/bin - - ../savedata:/app/erupe/savedata + - ./config.json:/app/erupe/config.json + - ./bin:/app/erupe/bin + - ./savedata:/app/erupe/savedata ports: # (Make sure these match config.json) - "53312:53312" #Sign V1 @@ -48,4 +59,13 @@ services: - "54006:54006" - "54007:54007" - "54008:54008" - + web: + image: httpd:latest + container_name: my-apache-app + ports: + - '80:80' + volumes: + - ./Servers:/usr/local/apache2/htdocs + depends_on: + db: + condition: service_healthy \ No newline at end of file diff --git a/docker/init/setup.sh b/docker/init/setup.sh new file mode 100644 index 000000000..d9fda8d59 --- /dev/null +++ b/docker/init/setup.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e +echo "INIT!" +pg_restore --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --verbose /schemas/9.1-init.sql + + + +echo "Updating!" + +for file in /schemas/update-schema/* +do + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -1 -f $file +done + + + +echo "Patching!" + +for file in /schemas/patch-schema/* +do + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -1 -f $file +done \ No newline at end of file diff --git a/schemas/9.1-init.sql b/schemas/9.1-init.sql new file mode 100644 index 0000000000000000000000000000000000000000..3ae0ca127112d22ee882d7d7c9bfdebedf6362e4 GIT binary patch literal 64231 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 literal 0 HcmV?d00001 diff --git a/bundled-schema/DistributionDemo.sql b/schemas/bundled-schema/DistributionDemo.sql similarity index 100% rename from bundled-schema/DistributionDemo.sql rename to schemas/bundled-schema/DistributionDemo.sql diff --git a/bundled-schema/DivaShops.sql b/schemas/bundled-schema/DivaShops.sql similarity index 100% rename from bundled-schema/DivaShops.sql rename to schemas/bundled-schema/DivaShops.sql diff --git a/bundled-schema/EventQuests.sql b/schemas/bundled-schema/EventQuests.sql similarity index 100% rename from bundled-schema/EventQuests.sql rename to schemas/bundled-schema/EventQuests.sql diff --git a/bundled-schema/FPointItems.sql b/schemas/bundled-schema/FPointItems.sql similarity index 100% rename from bundled-schema/FPointItems.sql rename to schemas/bundled-schema/FPointItems.sql diff --git a/bundled-schema/FestaDefaults.sql b/schemas/bundled-schema/FestaDefaults.sql similarity index 100% rename from bundled-schema/FestaDefaults.sql rename to schemas/bundled-schema/FestaDefaults.sql diff --git a/bundled-schema/GachaDemo.sql b/schemas/bundled-schema/GachaDemo.sql similarity index 100% rename from bundled-schema/GachaDemo.sql rename to schemas/bundled-schema/GachaDemo.sql diff --git a/bundled-schema/NetcafeDefaults.sql b/schemas/bundled-schema/NetcafeDefaults.sql similarity index 100% rename from bundled-schema/NetcafeDefaults.sql rename to schemas/bundled-schema/NetcafeDefaults.sql diff --git a/bundled-schema/OtherShops.sql b/schemas/bundled-schema/OtherShops.sql similarity index 100% rename from bundled-schema/OtherShops.sql rename to schemas/bundled-schema/OtherShops.sql diff --git a/bundled-schema/RoadShopItems.sql b/schemas/bundled-schema/RoadShopItems.sql similarity index 100% rename from bundled-schema/RoadShopItems.sql rename to schemas/bundled-schema/RoadShopItems.sql diff --git a/bundled-schema/ScenarioDefaults.sql b/schemas/bundled-schema/ScenarioDefaults.sql similarity index 100% rename from bundled-schema/ScenarioDefaults.sql rename to schemas/bundled-schema/ScenarioDefaults.sql diff --git a/patch-schema/.gitkeep b/schemas/patch-schema/.gitkeep similarity index 100% rename from patch-schema/.gitkeep rename to schemas/patch-schema/.gitkeep diff --git a/patch-schema/00-psn-id.sql b/schemas/patch-schema/00-psn-id.sql similarity index 100% rename from patch-schema/00-psn-id.sql rename to schemas/patch-schema/00-psn-id.sql diff --git a/patch-schema/01-wiiu-key.sql b/schemas/patch-schema/01-wiiu-key.sql similarity index 100% rename from patch-schema/01-wiiu-key.sql rename to schemas/patch-schema/01-wiiu-key.sql diff --git a/patch-schema/02-tower.sql b/schemas/patch-schema/02-tower.sql similarity index 100% rename from patch-schema/02-tower.sql rename to schemas/patch-schema/02-tower.sql diff --git a/patch-schema/03-event_quests.sql b/schemas/patch-schema/03-event_quests.sql similarity index 100% rename from patch-schema/03-event_quests.sql rename to schemas/patch-schema/03-event_quests.sql diff --git a/patch-schema/04-trend-weapons.sql b/schemas/patch-schema/04-trend-weapons.sql similarity index 100% rename from patch-schema/04-trend-weapons.sql rename to schemas/patch-schema/04-trend-weapons.sql diff --git a/patch-schema/05-gacha-roll-name.sql b/schemas/patch-schema/05-gacha-roll-name.sql similarity index 100% rename from patch-schema/05-gacha-roll-name.sql rename to schemas/patch-schema/05-gacha-roll-name.sql diff --git a/patch-schema/06-goocoo-rename.sql b/schemas/patch-schema/06-goocoo-rename.sql similarity index 100% rename from patch-schema/06-goocoo-rename.sql rename to schemas/patch-schema/06-goocoo-rename.sql diff --git a/patch-schema/07-scenarios-counter.sql b/schemas/patch-schema/07-scenarios-counter.sql similarity index 100% rename from patch-schema/07-scenarios-counter.sql rename to schemas/patch-schema/07-scenarios-counter.sql diff --git a/patch-schema/08-kill-counts.sql b/schemas/patch-schema/08-kill-counts.sql similarity index 100% rename from patch-schema/08-kill-counts.sql rename to schemas/patch-schema/08-kill-counts.sql diff --git a/patch-schema/09-fix-guild-treasure.sql b/schemas/patch-schema/09-fix-guild-treasure.sql similarity index 100% rename from patch-schema/09-fix-guild-treasure.sql rename to schemas/patch-schema/09-fix-guild-treasure.sql diff --git a/patch-schema/10-rework-distributions.sql b/schemas/patch-schema/10-rework-distributions.sql similarity index 100% rename from patch-schema/10-rework-distributions.sql rename to schemas/patch-schema/10-rework-distributions.sql diff --git a/patch-schema/11-event-quest-flags.sql b/schemas/patch-schema/11-event-quest-flags.sql similarity index 100% rename from patch-schema/11-event-quest-flags.sql rename to schemas/patch-schema/11-event-quest-flags.sql diff --git a/patch-schema/12-event_quest_cycling.sql b/schemas/patch-schema/12-event_quest_cycling.sql similarity index 100% rename from patch-schema/12-event_quest_cycling.sql rename to schemas/patch-schema/12-event_quest_cycling.sql diff --git a/patch-schema/13-festa-trial-votes.sql b/schemas/patch-schema/13-festa-trial-votes.sql similarity index 100% rename from patch-schema/13-festa-trial-votes.sql rename to schemas/patch-schema/13-festa-trial-votes.sql diff --git a/patch-schema/14-fix-fpoint-trades.sql b/schemas/patch-schema/14-fix-fpoint-trades.sql similarity index 100% rename from patch-schema/14-fix-fpoint-trades.sql rename to schemas/patch-schema/14-fix-fpoint-trades.sql diff --git a/patch-schema/15-reset-goocoos.sql b/schemas/patch-schema/15-reset-goocoos.sql similarity index 100% rename from patch-schema/15-reset-goocoos.sql rename to schemas/patch-schema/15-reset-goocoos.sql diff --git a/patch-schema/16-discord-password-resets.sql b/schemas/patch-schema/16-discord-password-resets.sql similarity index 100% rename from patch-schema/16-discord-password-resets.sql rename to schemas/patch-schema/16-discord-password-resets.sql diff --git a/patch-schema/op-accounts.sql b/schemas/patch-schema/op-accounts.sql similarity index 100% rename from patch-schema/op-accounts.sql rename to schemas/patch-schema/op-accounts.sql diff --git a/patch-schema/psn-link.sql b/schemas/patch-schema/psn-link.sql similarity index 100% rename from patch-schema/psn-link.sql rename to schemas/patch-schema/psn-link.sql diff --git a/schemas/update-schema/9.2-update.sql b/schemas/update-schema/9.2-update.sql new file mode 100644 index 000000000..e7dbf699b --- /dev/null +++ b/schemas/update-schema/9.2-update.sql @@ -0,0 +1,241 @@ +BEGIN; + +DROP TABLE IF EXISTS public.fpoint_items; + +CREATE TABLE IF NOT EXISTS public.fpoint_items ( + id serial PRIMARY KEY, + item_type integer, + item_id integer, + quantity integer, + fpoints integer, + trade_type integer +); + +ALTER TABLE IF EXISTS public.characters ADD bonus_quests INT NOT NULL DEFAULT 0; + +ALTER TABLE IF EXISTS public.characters ADD daily_quests INT NOT NULL DEFAULT 0; + +ALTER TABLE IF EXISTS public.characters ADD promo_points INT NOT NULL DEFAULT 0; + +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; + +UPDATE public.characters SET savemercenary = NULL; + +ALTER TABLE IF EXISTS public.characters ADD rasta_id INT; + +ALTER TABLE IF EXISTS public.characters ADD pact_id INT; + +ALTER TABLE IF EXISTS public.characters ADD stampcard INT NOT NULL DEFAULT 0; + +ALTER TABLE IF EXISTS public.characters DROP COLUMN IF EXISTS gacha_prem; + +ALTER TABLE IF EXISTS public.characters DROP COLUMN IF EXISTS gacha_trial; + +ALTER TABLE IF EXISTS public.characters DROP COLUMN IF EXISTS frontier_points; + +ALTER TABLE IF EXISTS public.users ADD IF NOT EXISTS gacha_premium INT; + +ALTER TABLE IF EXISTS public.users ADD IF NOT EXISTS gacha_trial INT; + +ALTER TABLE IF EXISTS public.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 +); + +DROP TABLE IF EXISTS public.login_boost_state; + +CREATE TABLE IF NOT EXISTS public.login_boost ( + char_id INTEGER, + week_req INTEGER, + expiration TIMESTAMP WITH TIME ZONE, + reset TIMESTAMP WITH TIME ZONE +); + +ALTER TABLE IF EXISTS public.characters ADD COLUMN mezfes BYTEA; + +ALTER TABLE IF EXISTS public.characters ALTER COLUMN daily_time TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.characters ALTER COLUMN guild_post_checked TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.characters ALTER COLUMN boost_time TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.characters ADD COLUMN IF NOT EXISTS cafe_reset TIMESTAMP WITHOUT TIME ZONE; + +ALTER TABLE IF EXISTS public.characters ALTER COLUMN cafe_reset TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.distribution ALTER COLUMN deadline TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.events ALTER COLUMN start_time TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.feature_weapon ALTER COLUMN start_time TYPE TIMESTAMP WITH TIME ZONE; + +CREATE TABLE IF NOT EXISTS public.feature_weapon +( + start_time TIMESTAMP WITH TIME ZONE NOT NULL, + featured INTEGER NOT NULL +); + +ALTER TABLE IF EXISTS public.guild_alliances ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.guild_applications ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.guild_characters ALTER COLUMN joined_at TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.guild_posts ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.characters ALTER COLUMN daily_time TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.guilds ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.mail ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.stamps ALTER COLUMN hl_next TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.stamps ALTER COLUMN ex_next TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.titles ALTER COLUMN unlocked_at TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.titles ALTER COLUMN updated_at TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.users ALTER COLUMN last_login TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.users ALTER COLUMN return_expires TYPE TIMESTAMP WITH TIME ZONE; + +ALTER TABLE IF EXISTS public.guild_meals DROP COLUMN IF EXISTS expires; + +ALTER TABLE IF EXISTS public.guild_meals ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE; + +DROP TABLE IF EXISTS public.account_ban; + +DROP TABLE IF EXISTS public.account_history; + +DROP TABLE IF EXISTS public.account_moderation; + +DROP TABLE IF EXISTS public.account_sub; + +DROP TABLE IF EXISTS public.history; + +DROP TABLE IF EXISTS public.questlists; + +DROP TABLE IF EXISTS public.schema_migrations; + +DROP TABLE IF EXISTS public.user_binaries; + +DROP PROCEDURE IF EXISTS raviinit; + +DROP PROCEDURE IF EXISTS ravireset; + +ALTER TABLE IF EXISTS public.normal_shop_items RENAME TO shop_items; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN shoptype TO shop_type; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN shopid TO shop_id; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN itemhash TO id; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN itemid TO item_id; + +ALTER TABLE IF EXISTS public.shop_items ALTER COLUMN points TYPE integer; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN points TO cost; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN tradequantity TO quantity; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN rankreqlow TO min_hr; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN rankreqhigh TO min_sr; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN rankreqg TO min_gr; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN storelevelreq TO store_level; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN maximumquantity TO max_quantity; + +ALTER TABLE IF EXISTS public.shop_items DROP COLUMN IF EXISTS boughtquantity; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN roadfloorsrequired TO road_floors; + +ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN weeklyfataliskills TO road_fatalis; + +ALTER TABLE public.shop_items RENAME CONSTRAINT normal_shop_items_pkey TO shop_items_pkey; + +ALTER TABLE IF EXISTS public.shop_items DROP CONSTRAINT IF EXISTS normal_shop_items_itemhash_key; + +CREATE SEQUENCE IF NOT EXISTS public.shop_items_id_seq; + +ALTER SEQUENCE IF EXISTS public.shop_items_id_seq OWNER TO postgres; + +ALTER TABLE IF EXISTS public.shop_items ALTER COLUMN id SET DEFAULT nextval('shop_items_id_seq'::regclass); + +ALTER SEQUENCE IF EXISTS public.shop_items_id_seq OWNED BY shop_items.id; + +SELECT setval('shop_items_id_seq', (SELECT MAX(id) FROM public.shop_items)); + +DROP TABLE IF EXISTS public.shop_item_state; + +CREATE TABLE IF NOT EXISTS public.shop_items_bought ( + character_id INTEGER, + shop_item_id INTEGER, + bought INTEGER +); + +UPDATE users SET rights = rights-2; + +ALTER TABLE IF EXISTS public.users ALTER COLUMN rights SET DEFAULT 12; + +END; \ No newline at end of file From 851301b088302a955cfa292c448313dc78689376 Mon Sep 17 00:00:00 2001 From: stratic-dev Date: Fri, 26 Jan 2024 17:59:34 +0000 Subject: [PATCH 243/269] Updated readme and added init schemas to folder --- README.md | 14 ++++++++++++++ docker/init/setup.sh | 2 +- .../{ => initialisation-schema}/9.1-init.sql | Bin schemas/initialisation-schema/9.2-init.sql | Bin 0 -> 55992 bytes 4 files changed, 15 insertions(+), 1 deletion(-) rename schemas/{ => initialisation-schema}/9.1-init.sql (100%) create mode 100644 schemas/initialisation-schema/9.2-init.sql diff --git a/README.md b/README.md index 034e2a4ea..b9be1fb05 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,20 @@ If you want to modify or compile Erupe yourself, please read on. 3. Edit [config.json](./config.json) such that the database password matches your PostgreSQL setup. 4. Run `go build` or `go run .` to compile Erupe. +## Docker + +Please see the readme in [docker/README.md](./docker/README.md). At the moment this is only really good for quick installs and checking out development not for production. + +## Schemas + +We source control the following schemas: +- Initialisation Schemas: These initialise the application database to a clean install from a specific version. +- Update Schemas: These are update files they should be ran in order of version to get to the latest schema. +- Patch Schemas: These are for development and should be ran from the lastest available update schema or initial schema. These eventually get condensed into `Update Schemas` and then deleted when updated to a new version. +- Bundled Schemas: These are demo reference files to allow servers to be able to roll their own shops, distributions gachas and scenarios set ups. + +Note: Patch schemas are subject to change! You should only be using them if you are following along with development. + ## Resources - [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z) diff --git a/docker/init/setup.sh b/docker/init/setup.sh index d9fda8d59..b84f83b4d 100644 --- a/docker/init/setup.sh +++ b/docker/init/setup.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e echo "INIT!" -pg_restore --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --verbose /schemas/9.1-init.sql +pg_restore --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --verbose /schemas/initialisation-schema/9.1-init.sql diff --git a/schemas/9.1-init.sql b/schemas/initialisation-schema/9.1-init.sql similarity index 100% rename from schemas/9.1-init.sql rename to schemas/initialisation-schema/9.1-init.sql diff --git a/schemas/initialisation-schema/9.2-init.sql b/schemas/initialisation-schema/9.2-init.sql new file mode 100644 index 0000000000000000000000000000000000000000..229b860a5812817c77b313bae6bee700756d8892 GIT binary patch literal 55992 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 jE2<;(I1-iE| Date: Mon, 29 Jan 2024 21:58:36 -0500 Subject: [PATCH 244/269] fix config mismatch --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index c0247b9d3..1e9e6486d 100644 --- a/config.json +++ b/config.json @@ -84,10 +84,10 @@ "Discord": { "Enabled": false, "BotToken": "", - "RealTimeChannel": { + "RelayChannel": { "Enabled": false, "MaxMessageLength": 183, - "RealTimeChannelID": "" + "RelayChannelID": "" } }, "Commands": [ From 08e6fd2cda3e853beebc88c36239b375420359ab Mon Sep 17 00:00:00 2001 From: stratic-dev Date: Thu, 1 Feb 2024 21:54:40 +0000 Subject: [PATCH 245/269] Delete 9.2-init.sql --- schemas/initialisation-schema/9.2-init.sql | Bin 55992 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 schemas/initialisation-schema/9.2-init.sql diff --git a/schemas/initialisation-schema/9.2-init.sql b/schemas/initialisation-schema/9.2-init.sql deleted file mode 100644 index 229b860a5812817c77b313bae6bee700756d8892..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55992 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 jE2<;(I1-iE| Date: Thu, 1 Feb 2024 21:56:43 +0000 Subject: [PATCH 246/269] Create AUTHORS.md --- AUTHORS.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 AUTHORS.md diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 000000000..618d34264 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,29 @@ +# List of AUTHORS who contributed over time to the Erupe project + +## Point of current development +The project is currently developed under https://github.com/ZeruLight/Erupe + +## History of development +Development of this project dates back to 2019, and was developed under various umbrellas over time: +* Cappuccino (Fist/Ando/Ellie42) (The Erupe Developers), 2019-2020 (https://github.com/Ellie42/Erupe / https://github.com/ricochhet/Erupe-Legacy) (Still active closed source) +* Einherjar Team, ????-2022 Feb (There is no git history for this period, this team's work was taken and used as a foundation for future repositories) +* Community Edition, 2022 (https://github.com/xl3lackout/Erupe) +* Zerulight, 2022-2023 (https://github.com/ZeruLight/Erupe) + +## Authorship of the code +Authorship is assigned for each commit within the git history, which is stored in these git repos: +* https://github.com/ZeruLight/Erupe +* https://github.com/Ellie42/Erupe +* https://github.com/ricochhet/Erupe-Legacy +* https://github.com/xl3lackout/Erupe + +Note there is a divergence between Ellie42s branch and xl3lackout where history has been lost. + +Unfortunately, we have no detailed information on the history of the Erupe pre-2022 +if somebody can provide information, please contact us, so that we can make this history available. + +## Exceptions with third-party libraries +The third-party libraries have their own way of addressing authorship and the authorship of commits importing/updating +a third-party library reflects who did the importing instead of who wrote the code within the commit. + +The Authors of third-party libraries are not explicitly mentioned, and usually is possible to obtain from the files belonging to the third-party libraries. \ No newline at end of file From df9e33bdccc464e2ba7e947d9b4c992188fe95dd Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 8 Feb 2024 18:29:04 +1100 Subject: [PATCH 247/269] fix dirty pr --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index bffde254a..1e9e6486d 100644 --- a/config.json +++ b/config.json @@ -157,10 +157,10 @@ {"Name": "EXRenewing", "Enabled": true} ], "Database": { - "Host": "db", + "Host": "localhost", "Port": 5432, "User": "postgres", - "Password": "password", + "Password": "", "Database": "erupe" }, "Sign": { From bbf4fa247200d8776fd42665126026949d1828a1 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 10 Feb 2024 03:07:13 +1100 Subject: [PATCH 248/269] further tune value configuration --- config.json | 15 + config/config.go | 15 + server/channelserver/handlers_quest.go | 410 ++++++------------------- 3 files changed, 125 insertions(+), 315 deletions(-) diff --git a/config.json b/config.json index 1e9e6486d..9bb641f72 100644 --- a/config.json +++ b/config.json @@ -65,11 +65,26 @@ "SmallBerserkRavienteMaxPlayers": 8, "GUrgentRate": 0.10, "GCPMultiplier": 1.00, + "HRPMultiplier": 1.00, + "HRPMultiplierNC": 1.00, + "SRPMultiplier": 1.00, + "SRPMultiplierNC": 1.00, "GRPMultiplier": 1.00, + "GRPMultiplierNC": 1.00, "GSRPMultiplier": 1.00, + "GSRPMultiplierNC": 1.00, + "ZennyMultiplier": 1.00, + "ZennyMultiplierNC": 1.00, "GZennyMultiplier": 1.00, + "GZennyMultiplierNC": 1.00, "MaterialMultiplier": 1.00, + "MaterialMultiplierNC": 1.00, + "GMaterialMultiplier": 1.00, + "GMaterialMultiplierNC": 1.00, "ExtraCarves": 0, + "ExtraCarvesNC": 0, + "GExtraCarves": 0, + "GExtraCarvesNC": 0, "DisableHunterNavi": false, "MezFesSoloTickets": 5, "MezFesGroupTickets": 1, diff --git a/config/config.go b/config/config.go index 3c6f6a0a7..d2038f150 100644 --- a/config/config.go +++ b/config/config.go @@ -152,11 +152,26 @@ type GameplayOptions struct { SmallBerserkRavienteMaxPlayers uint8 GUrgentRate float32 // Adjusts the rate of G Urgent quests spawning GCPMultiplier float32 // Adjusts the multiplier of GCP rewarded for quest completion + HRPMultiplier float32 // Adjusts the multiplier of Hunter Rank Points rewarded for quest completion + HRPMultiplierNC float32 // Adjusts the multiplier of Hunter Rank Points rewarded for quest completion in a NetCafe + SRPMultiplier float32 // Adjusts the multiplier of Skill Rank Points rewarded for quest completion + SRPMultiplierNC float32 // Adjusts the multiplier of Skill Rank Points rewarded for quest completion in a NetCafe GRPMultiplier float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion + GRPMultiplierNC float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion in a NetCafe GSRPMultiplier float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion + GSRPMultiplierNC float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion in a NetCafe + ZennyMultiplier float32 // Adjusts the multiplier of Zenny rewarded for quest completion + ZennyMultiplierNC float32 // Adjusts the multiplier of Zenny rewarded for quest completion in a NetCafe GZennyMultiplier float32 // Adjusts the multiplier of G Zenny rewarded for quest completion + GZennyMultiplierNC float32 // Adjusts the multiplier of G Zenny rewarded for quest completion in a NetCafe MaterialMultiplier float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion + MaterialMultiplierNC float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion in a NetCafe + GMaterialMultiplier float32 // Adjusts the multiplier of G Rank Monster Materials rewarded for quest completion + GMaterialMultiplierNC float32 // Adjusts the multiplier of G Rank Monster Materials rewarded for quest completion in a NetCafe ExtraCarves uint16 // Grant n extra chances to carve ALL carcasses + ExtraCarvesNC uint16 // Grant n extra chances to carve ALL carcasses in a NetCafe + GExtraCarves uint16 // Grant n extra chances to carve ALL G Rank carcasses + GExtraCarvesNC uint16 // Grant n extra chances to carve ALL G Rank carcasses in a NetCafe DisableHunterNavi bool // Disables the Hunter Navi MezFesSoloTickets uint32 // Number of solo tickets given weekly MezFesGroupTickets uint32 // Number of group tickets given weekly diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 084fab95f..c8bd41f3b 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -16,6 +16,11 @@ import ( "go.uber.org/zap" ) +type tuneValue struct { + ID uint16 + Value uint16 +} + func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysGetFile) @@ -305,11 +310,6 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { tx.Commit() } - type tuneValue struct { - ID uint16 - Value uint16 - } - tuneValues := []tuneValue{ {ID: 20, Value: 1}, {ID: 26, Value: 1}, @@ -322,29 +322,30 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { {ID: 67, Value: 1}, {ID: 80, Value: 1}, {ID: 94, Value: 1}, - {ID: 1010, Value: 300}, // get_hrp_rate_netcafe - {ID: 1011, Value: 300}, // get_zeny_rate_netcafe - {ID: 1012, Value: 300}, // get_hrp_rate_ncource - {ID: 1013, Value: 300}, // get_zeny_rate_ncource - {ID: 1014, Value: 200}, // get_hrp_rate_premium - {ID: 1015, Value: 200}, // get_zeny_rate_premium - {ID: 1021, Value: 400}, // get_gcp_rate_assist - {ID: 1023, Value: 8}, - {ID: 1024, Value: 150}, // get_hrp_rate_ptbonus - {ID: 1025, Value: 1}, - {ID: 1026, Value: 999}, // get_grank_cap - {ID: 1027, Value: 100}, // get_exchange_rate_festa - {ID: 1028, Value: 100}, // get_exchange_rate_cafe - {ID: 1030, Value: 8}, // get_gquest_cap - {ID: 1031, Value: 100}, // get_exchange_rate_guild (GCP) - {ID: 1032, Value: 0}, // isValid_partner - {ID: 1044, Value: 200}, // get_rate_tload_time_out - {ID: 1045, Value: 0}, // get_rate_tower_treasure_preset - {ID: 1046, Value: 99}, // get_hunter_life_cap - {ID: 1048, Value: 0}, // get_rate_tower_log_disable - {ID: 1049, Value: 10}, // get_rate_tower_gem_max - {ID: 1050, Value: 1}, // get_rate_tower_gem_set - {ID: 1051, Value: 200}, + {ID: 1001, Value: 100}, // get_hrp_rate + {ID: 1010, Value: 300}, // get_hrp_rate_netcafe + {ID: 1011, Value: 300}, // get_zeny_rate_netcafe + {ID: 1012, Value: 300}, // get_hrp_rate_ncource + {ID: 1013, Value: 300}, // get_zeny_rate_ncource + {ID: 1014, Value: 200}, // get_hrp_rate_premium + {ID: 1015, Value: 200}, // get_zeny_rate_premium + {ID: 1021, Value: 400}, // get_gcp_rate_assist + {ID: 1023, Value: 8}, // unused? + {ID: 1024, Value: 150}, // get_hrp_rate_ptbonus + {ID: 1025, Value: 1}, // isValid_stampcard + {ID: 1026, Value: 999}, // get_grank_cap + {ID: 1027, Value: 100}, // get_exchange_rate_festa + {ID: 1028, Value: 100}, // get_exchange_rate_cafe + {ID: 1030, Value: 8}, // get_gquest_cap + {ID: 1031, Value: 100}, // get_exchange_rate_guild (GCP) + {ID: 1032, Value: 0}, // isValid_partner + {ID: 1044, Value: 200}, // get_rate_tload_time_out + {ID: 1045, Value: 0}, // get_rate_tower_treasure_preset + {ID: 1046, Value: 99}, // get_hunter_life_cap + {ID: 1048, Value: 0}, // get_rate_tower_hint_sec + {ID: 1049, Value: 10}, // get_rate_tower_gem_max + {ID: 1050, Value: 1}, // get_rate_tower_gem_set + {ID: 1051, Value: 200}, // get_pallone_score_rate_premium {ID: 1052, Value: 200}, // get_trp_rate_premium {ID: 1063, Value: 50000}, // get_nboost_quest_point_from_hrank {ID: 1064, Value: 50000}, // get_nboost_quest_point_from_srank @@ -356,9 +357,11 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { {ID: 1072, Value: 300}, // get_rate_premium_ravi_tama {ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama {ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama - {ID: 1078, Value: 0}, - {ID: 1079, Value: 1}, - {ID: 1080, Value: 1}, + {ID: 1078, Value: 0}, // isCapped_tenrou_irai + {ID: 1079, Value: 1}, // get_add_tower_level_assist + {ID: 1080, Value: 1}, // get_tune_add_tower_level_w_assist_nboost + + // get_tune_secret_book_item {ID: 1081, Value: 1}, {ID: 1082, Value: 4}, {ID: 1083, Value: 2}, @@ -383,14 +386,17 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { {ID: 1102, Value: 5}, {ID: 1103, Value: 2}, {ID: 1104, Value: 10}, - {ID: 1145, Value: 200}, - {ID: 1146, Value: 0}, // isTower_invisible - {ID: 1147, Value: 0}, // isVenom_playable - {ID: 1149, Value: 20}, - {ID: 1152, Value: 1130}, - {ID: 1154, Value: 0}, // isDisabled_object_season - {ID: 1158, Value: 1}, - {ID: 1160, Value: 300}, + + {ID: 1145, Value: 200}, // get_ud_point_rate_premium + {ID: 1146, Value: 0}, // isTower_invisible + {ID: 1147, Value: 0}, // isVenom_playable + {ID: 1149, Value: 20}, // get_ud_break_parts_point + {ID: 1152, Value: 1130}, // unused? + {ID: 1154, Value: 0}, // isDisabled_object_season + {ID: 1158, Value: 1}, // isDelivery_venom_ult_quest + {ID: 1160, Value: 300}, // get_rate_premium_ravi_g_enhance_tama + + // unknown {ID: 1162, Value: 1}, {ID: 1163, Value: 3}, {ID: 1164, Value: 5}, @@ -410,240 +416,6 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { {ID: 1178, Value: 10}, {ID: 1179, Value: 2}, {ID: 1180, Value: 5}, - {ID: 3000, Value: 100}, - {ID: 3001, Value: 100}, - {ID: 3002, Value: 100}, - {ID: 3003, Value: 100}, - {ID: 3004, Value: 100}, - {ID: 3005, Value: 100}, - {ID: 3006, Value: 100}, - {ID: 3007, Value: 100}, - {ID: 3008, Value: 100}, - {ID: 3009, Value: 100}, - {ID: 3010, Value: 100}, - {ID: 3011, Value: 100}, - {ID: 3012, Value: 100}, - {ID: 3013, Value: 100}, - {ID: 3014, Value: 100}, - {ID: 3015, Value: 100}, - {ID: 3016, Value: 100}, - {ID: 3017, Value: 100}, - {ID: 3018, Value: 100}, - {ID: 3019, Value: 100}, - {ID: 3020, Value: 100}, - {ID: 3021, Value: 100}, - {ID: 3022, Value: 100}, - {ID: 3023, Value: 100}, - {ID: 3024, Value: 100}, - {ID: 3025, Value: 100}, - {ID: 3286, Value: 200}, - {ID: 3287, Value: 200}, - {ID: 3288, Value: 200}, - {ID: 3289, Value: 200}, - {ID: 3290, Value: 200}, - {ID: 3291, Value: 200}, - {ID: 3292, Value: 200}, - {ID: 3293, Value: 200}, - {ID: 3294, Value: 200}, - {ID: 3295, Value: 200}, - {ID: 3296, Value: 200}, - {ID: 3297, Value: 200}, - {ID: 3298, Value: 200}, - {ID: 3299, Value: 200}, - {ID: 3300, Value: 200}, - {ID: 3301, Value: 200}, - {ID: 3302, Value: 200}, - {ID: 3303, Value: 200}, - {ID: 3304, Value: 200}, - {ID: 3305, Value: 200}, - {ID: 3306, Value: 200}, - {ID: 3307, Value: 200}, - {ID: 3308, Value: 200}, - {ID: 3309, Value: 200}, - {ID: 3310, Value: 200}, - {ID: 3311, Value: 200}, - {ID: 3312, Value: 300}, - {ID: 3313, Value: 300}, - {ID: 3314, Value: 300}, - {ID: 3315, Value: 300}, - {ID: 3316, Value: 300}, - {ID: 3317, Value: 300}, - {ID: 3318, Value: 300}, - {ID: 3319, Value: 300}, - {ID: 3320, Value: 300}, - {ID: 3321, Value: 300}, - {ID: 3322, Value: 300}, - {ID: 3323, Value: 300}, - {ID: 3324, Value: 300}, - {ID: 3325, Value: 300}, - {ID: 3326, Value: 300}, - {ID: 3327, Value: 300}, - {ID: 3328, Value: 300}, - {ID: 3329, Value: 300}, - {ID: 3330, Value: 300}, - {ID: 3331, Value: 300}, - {ID: 3332, Value: 300}, - {ID: 3333, Value: 300}, - {ID: 3334, Value: 300}, - {ID: 3335, Value: 300}, - {ID: 3336, Value: 300}, - {ID: 3337, Value: 300}, - {ID: 3338, Value: 100}, - {ID: 3339, Value: 100}, - {ID: 3340, Value: 100}, - {ID: 3341, Value: 100}, - {ID: 3342, Value: 100}, - {ID: 3343, Value: 100}, - {ID: 3344, Value: 100}, - {ID: 3345, Value: 100}, - {ID: 3346, Value: 100}, - {ID: 3347, Value: 100}, - {ID: 3348, Value: 100}, - {ID: 3349, Value: 100}, - {ID: 3350, Value: 100}, - {ID: 3351, Value: 100}, - {ID: 3352, Value: 100}, - {ID: 3353, Value: 100}, - {ID: 3354, Value: 100}, - {ID: 3355, Value: 100}, - {ID: 3356, Value: 100}, - {ID: 3357, Value: 100}, - {ID: 3358, Value: 100}, - {ID: 3359, Value: 100}, - {ID: 3360, Value: 100}, - {ID: 3361, Value: 100}, - {ID: 3362, Value: 100}, - {ID: 3363, Value: 100}, - {ID: 3364, Value: 100}, - {ID: 3365, Value: 100}, - {ID: 3366, Value: 100}, - {ID: 3367, Value: 100}, - {ID: 3368, Value: 100}, - {ID: 3369, Value: 100}, - {ID: 3370, Value: 100}, - {ID: 3371, Value: 100}, - {ID: 3372, Value: 100}, - {ID: 3373, Value: 100}, - {ID: 3374, Value: 100}, - {ID: 3375, Value: 100}, - {ID: 3376, Value: 100}, - {ID: 3377, Value: 100}, - {ID: 3378, Value: 100}, - {ID: 3379, Value: 100}, - {ID: 3380, Value: 100}, - {ID: 3381, Value: 100}, - {ID: 3382, Value: 100}, - {ID: 3383, Value: 100}, - {ID: 3384, Value: 100}, - {ID: 3385, Value: 100}, - {ID: 3386, Value: 100}, - {ID: 3387, Value: 100}, - {ID: 3388, Value: 100}, - {ID: 3389, Value: 100}, - {ID: 3390, Value: 100}, - {ID: 3391, Value: 100}, - {ID: 3392, Value: 100}, - {ID: 3393, Value: 100}, - {ID: 3394, Value: 100}, - {ID: 3395, Value: 100}, - {ID: 3396, Value: 100}, - {ID: 3397, Value: 100}, - {ID: 3398, Value: 100}, - {ID: 3399, Value: 100}, - {ID: 3400, Value: 100}, - {ID: 3401, Value: 100}, - {ID: 3402, Value: 100}, - {ID: 3416, Value: 100}, - {ID: 3417, Value: 100}, - {ID: 3418, Value: 100}, - {ID: 3419, Value: 100}, - {ID: 3420, Value: 100}, - {ID: 3421, Value: 100}, - {ID: 3422, Value: 100}, - {ID: 3423, Value: 100}, - {ID: 3424, Value: 100}, - {ID: 3425, Value: 100}, - {ID: 3426, Value: 100}, - {ID: 3427, Value: 100}, - {ID: 3428, Value: 100}, - {ID: 3442, Value: 100}, - {ID: 3443, Value: 100}, - {ID: 3444, Value: 100}, - {ID: 3445, Value: 100}, - {ID: 3446, Value: 100}, - {ID: 3447, Value: 100}, - {ID: 3448, Value: 100}, - {ID: 3449, Value: 100}, - {ID: 3450, Value: 100}, - {ID: 3451, Value: 100}, - {ID: 3452, Value: 100}, - {ID: 3453, Value: 100}, - {ID: 3454, Value: 100}, - {ID: 3468, Value: 100}, - {ID: 3469, Value: 100}, - {ID: 3470, Value: 100}, - {ID: 3471, Value: 100}, - {ID: 3472, Value: 100}, - {ID: 3473, Value: 100}, - {ID: 3474, Value: 100}, - {ID: 3475, Value: 100}, - {ID: 3476, Value: 100}, - {ID: 3477, Value: 100}, - {ID: 3478, Value: 100}, - {ID: 3479, Value: 100}, - {ID: 3480, Value: 100}, - {ID: 3494, Value: 0}, - {ID: 3495, Value: 0}, - {ID: 3496, Value: 0}, - {ID: 3497, Value: 0}, - {ID: 3498, Value: 0}, - {ID: 3499, Value: 0}, - {ID: 3500, Value: 0}, - {ID: 3501, Value: 0}, - {ID: 3502, Value: 0}, - {ID: 3503, Value: 0}, - {ID: 3504, Value: 0}, - {ID: 3505, Value: 0}, - {ID: 3506, Value: 0}, - {ID: 3520, Value: 0}, - {ID: 3521, Value: 0}, - {ID: 3522, Value: 0}, - {ID: 3523, Value: 0}, - {ID: 3524, Value: 0}, - {ID: 3525, Value: 0}, - {ID: 3526, Value: 0}, - {ID: 3527, Value: 0}, - {ID: 3528, Value: 0}, - {ID: 3529, Value: 0}, - {ID: 3530, Value: 0}, - {ID: 3531, Value: 0}, - {ID: 3532, Value: 0}, - {ID: 3546, Value: 0}, - {ID: 3547, Value: 0}, - {ID: 3548, Value: 0}, - {ID: 3549, Value: 0}, - {ID: 3550, Value: 0}, - {ID: 3551, Value: 0}, - {ID: 3552, Value: 0}, - {ID: 3553, Value: 0}, - {ID: 3554, Value: 0}, - {ID: 3555, Value: 0}, - {ID: 3556, Value: 0}, - {ID: 3557, Value: 0}, - {ID: 3558, Value: 0}, - {ID: 3572, Value: 0}, - {ID: 3573, Value: 0}, - {ID: 3574, Value: 0}, - {ID: 3575, Value: 0}, - {ID: 3576, Value: 0}, - {ID: 3577, Value: 0}, - {ID: 3578, Value: 0}, - {ID: 3579, Value: 0}, - {ID: 3580, Value: 0}, - {ID: 3581, Value: 0}, - {ID: 3582, Value: 0}, - {ID: 3583, Value: 0}, - {ID: 3584, Value: 0}, } tuneValues = append(tuneValues, tuneValue{1020, uint16(s.server.erupeConfig.GameplayOptions.GCPMultiplier * 100)}) @@ -656,62 +428,62 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { if s.server.erupeConfig.GameplayOptions.EnableKaijiEvent { tuneValues = append(tuneValues, tuneValue{1106, 1}) - } else { - tuneValues = append(tuneValues, tuneValue{1106, 0}) } if s.server.erupeConfig.GameplayOptions.EnableHiganjimaEvent { tuneValues = append(tuneValues, tuneValue{1144, 1}) - } else { - tuneValues = append(tuneValues, tuneValue{1144, 0}) } if s.server.erupeConfig.GameplayOptions.EnableNierEvent { tuneValues = append(tuneValues, tuneValue{1153, 1}) - } else { - tuneValues = append(tuneValues, tuneValue{1153, 0}) } if s.server.erupeConfig.GameplayOptions.DisableRoad { tuneValues = append(tuneValues, tuneValue{1155, 1}) - } else { - tuneValues = append(tuneValues, tuneValue{1155, 0}) } - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier * 100)}) - } - - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier * 100)}) - } - - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3052, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)}) - } - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)}) - } - - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)}) - } - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3130, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)}) - } - - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3156, s.server.erupeConfig.GameplayOptions.ExtraCarves}) - } - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3182, s.server.erupeConfig.GameplayOptions.ExtraCarves}) - } - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3208, s.server.erupeConfig.GameplayOptions.ExtraCarves}) - } - for i := uint16(0); i < 13; i++ { - tuneValues = append(tuneValues, tuneValue{i + 3234, s.server.erupeConfig.GameplayOptions.ExtraCarves}) - } + // get_hrp_rate_from_rank + tuneValues = append(tuneValues, getTuneValueRange(3000, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplier*100))...) + tuneValues = append(tuneValues, getTuneValueRange(3338, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplierNC*100))...) + // get_srp_rate_from_rank + tuneValues = append(tuneValues, getTuneValueRange(3013, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplier*100))...) + tuneValues = append(tuneValues, getTuneValueRange(3351, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplierNC*100))...) + // get_grp_rate_from_rank + tuneValues = append(tuneValues, getTuneValueRange(3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier*100))...) + tuneValues = append(tuneValues, getTuneValueRange(3364, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplierNC*100))...) + // get_gsrp_rate_from_rank + tuneValues = append(tuneValues, getTuneValueRange(3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier*100))...) + tuneValues = append(tuneValues, getTuneValueRange(3377, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplierNC*100))...) + // get_zeny_rate_from_hrank + tuneValues = append(tuneValues, getTuneValueRange(3052, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplier*100))...) + tuneValues = append(tuneValues, getTuneValueRange(3390, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplierNC*100))...) + // get_zeny_rate_from_grank + tuneValues = append(tuneValues, getTuneValueRange(3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier*100))...) + tuneValues = append(tuneValues, getTuneValueRange(3416, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplierNC*100))...) + // get_reward_rate_from_hrank + tuneValues = append(tuneValues, getTuneValueRange(3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier*100))...) + tuneValues = append(tuneValues, getTuneValueRange(3442, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplierNC*100))...) + // get_reward_rate_from_grank + tuneValues = append(tuneValues, getTuneValueRange(3130, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplier*100))...) + tuneValues = append(tuneValues, getTuneValueRange(3468, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplierNC*100))...) + // get_lottery_rate_from_hrank + tuneValues = append(tuneValues, getTuneValueRange(3156, 0)...) + tuneValues = append(tuneValues, getTuneValueRange(3494, 0)...) + // get_lottery_rate_from_grank + tuneValues = append(tuneValues, getTuneValueRange(3182, 0)...) + tuneValues = append(tuneValues, getTuneValueRange(3520, 0)...) + // get_hagi_rate_from_hrank + tuneValues = append(tuneValues, getTuneValueRange(3208, s.server.erupeConfig.GameplayOptions.ExtraCarves)...) + tuneValues = append(tuneValues, getTuneValueRange(3546, s.server.erupeConfig.GameplayOptions.ExtraCarvesNC)...) + // get_hagi_rate_from_grank + tuneValues = append(tuneValues, getTuneValueRange(3234, s.server.erupeConfig.GameplayOptions.GExtraCarves)...) + tuneValues = append(tuneValues, getTuneValueRange(3572, s.server.erupeConfig.GameplayOptions.GExtraCarvesNC)...) + // get_nboost_transcend_rate_from_hrank + tuneValues = append(tuneValues, getTuneValueRange(3286, 200)...) + tuneValues = append(tuneValues, getTuneValueRange(3312, 300)...) + // get_nboost_transcend_rate_from_grank + tuneValues = append(tuneValues, getTuneValueRange(3299, 200)...) + tuneValues = append(tuneValues, getTuneValueRange(3325, 300)...) offset := uint16(time.Now().Unix()) bf.WriteUint16(offset) @@ -781,6 +553,14 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } +func getTuneValueRange(start uint16, value uint16) []tuneValue { + var tv []tuneValue + for i := uint16(0); i < 13; i++ { + tv = append(tv, tuneValue{start + i, value}) + } + return tv +} + func handleMsgMhfEnterTournamentQuest(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetUdBonusQuestInfo(s *Session, p mhfpacket.MHFPacket) { From 771f240d13286681ee68bb3144b86d9be85be01d Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 10 Feb 2024 17:45:40 +1100 Subject: [PATCH 249/269] implement course 31 --- common/mhfcourse/mhfcourse.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/mhfcourse/mhfcourse.go b/common/mhfcourse/mhfcourse.go index 332c684dc..71ccc0ab7 100644 --- a/common/mhfcourse/mhfcourse.go +++ b/common/mhfcourse/mhfcourse.go @@ -75,7 +75,7 @@ func GetCourseStruct(rights uint32) ([]Course, uint32) { sort.Slice(s, func(i, j int) bool { return s[i].ID > s[j].ID }) - var normalCafeCourseSet, netcafeCourseSet bool + var normalCafeCourseSet, netcafeCourseSet, hidenCourseSet bool for _, course := range s { if rights-course.Value() < 0x80000000 { switch course.ID { @@ -92,6 +92,12 @@ func GetCourseStruct(rights uint32) ([]Course, uint32) { } netcafeCourseSet = true resp = append(resp, Course{ID: 30}) + case 10: + if hidenCourseSet { + break + } + hidenCourseSet = true + resp = append(resp, Course{ID: 31}) } course.Expiry = time.Date(2030, 1, 1, 0, 0, 0, 0, time.FixedZone("UTC+9", 9*60*60)) resp = append(resp, course) From 6ec9d9d8690f1b09c8fa22fdcbeb56976c305fef Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 11 Feb 2024 23:22:42 +1100 Subject: [PATCH 250/269] add AutoBackportQuest DebugOption --- config.json | 1 + config/config.go | 1 + server/channelserver/handlers_quest.go | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/config.json b/config.json index 9bb641f72..c8705a661 100644 --- a/config.json +++ b/config.json @@ -36,6 +36,7 @@ "TournamentOverride": 0, "DisableTokenCheck": false, "QuestTools": false, + "AutoQuestBackport": true, "ProxyPort": 0, "CapLink": { "Values": [51728, 20000, 51729, 1, 20000], diff --git a/config/config.go b/config/config.go index d2038f150..b0094f628 100644 --- a/config/config.go +++ b/config/config.go @@ -118,6 +118,7 @@ type DebugOptions struct { TournamentOverride int // VS Tournament event status DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) QuestTools bool // Enable various quest debug logs + AutoQuestBackport bool // Automatically backport quest files ProxyPort uint16 // Forces the game to connect to a channel server proxy CapLink CapLinkOptions } diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index c8bd41f3b..148908dbf 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -2,6 +2,7 @@ package channelserver import ( "database/sql" + "encoding/binary" "erupe-ce/common/byteframe" "erupe-ce/common/decryption" ps "erupe-ce/common/pascalstring" @@ -21,6 +22,20 @@ type tuneValue struct { Value uint16 } +func BackportQuest(data []byte) []byte { + wp := binary.LittleEndian.Uint32(data[0:4]) + 96 + rp := wp + 4 + for i := uint32(0); i < 6; i++ { + if i != 0 { + wp += 4 + rp += 8 + } + copy(data[wp:wp+4], data[rp:rp+4]) + } + copy(data[wp:wp+180], data[rp:rp+180]) + return data +} + func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysGetFile) @@ -63,6 +78,9 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, data) return } + if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport { + data = BackportQuest(decryption.UnpackSimple(data)) + } doAckBufSucceed(s, pkt.AckHandle, data) } } @@ -124,6 +142,9 @@ func loadQuestFile(s *Session, questId int) []byte { } decrypted := decryption.UnpackSimple(file) + if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport { + decrypted = BackportQuest(decrypted) + } fileBytes := byteframe.NewByteFrameFromBytes(decrypted) fileBytes.SetLE() fileBytes.Seek(int64(fileBytes.ReadUint32()), 0) From 5f370896dff9f01df63fbe50859d4eac20624573 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 14 Feb 2024 17:28:01 +1100 Subject: [PATCH 251/269] clean up Tower responses --- schemas/patch-schema/02-tower.sql | 4 +-- server/channelserver/handlers_tower.go | 47 ++++++++++++++++---------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/schemas/patch-schema/02-tower.sql b/schemas/patch-schema/02-tower.sql index 0697fc2be..732f46c5e 100644 --- a/schemas/patch-schema/02-tower.sql +++ b/schemas/patch-schema/02-tower.sql @@ -7,8 +7,8 @@ CREATE TABLE IF NOT EXISTS tower ( tsp INT, block1 INT, block2 INT, - skills TEXT DEFAULT '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,0,0,0,0,0,0,0,0,0,0,0', - gems TEXT DEFAULT '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,0' + skills TEXT, + gems TEXT ); ALTER TABLE IF EXISTS guild_characters diff --git a/server/channelserver/handlers_tower.go b/server/channelserver/handlers_tower.go index 7f6bdb3b9..e075804e4 100644 --- a/server/channelserver/handlers_tower.go +++ b/server/channelserver/handlers_tower.go @@ -1,8 +1,10 @@ package channelserver import ( + _config "erupe-ce/config" "fmt" "go.uber.org/zap" + "strings" "time" "erupe-ce/common/byteframe" @@ -16,8 +18,8 @@ type TowerInfoTRP struct { } type TowerInfoSkill struct { - TSP int32 - Unk1 []int16 // 40 + TSP int32 + Skills []int16 // 64 } type TowerInfoHistory struct { @@ -32,6 +34,14 @@ type TowerInfoLevel struct { Unk3 int32 } +func EmptyTowerCSV(len int) string { + temp := make([]string, len) + for i := range temp { + temp[i] = "0" + } + return strings.Join(temp, ",") +} + func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetTowerInfo) var data []*byteframe.ByteFrame @@ -44,21 +54,24 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) { towerInfo := TowerInfo{ TRP: []TowerInfoTRP{{0, 0}}, - Skill: []TowerInfoSkill{{0, make([]int16, 40)}}, + Skill: []TowerInfoSkill{{0, make([]int16, 64)}}, History: []TowerInfoHistory{{make([]int16, 5), make([]int16, 5)}}, Level: []TowerInfoLevel{{0, 0, 0, 0}, {0, 0, 0, 0}}, } - tempSkills := "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,0,0,0,0,0,0,0,0,0,0,0" - - err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), skills FROM tower WHERE char_id=$1 - `, s.charID).Scan(&towerInfo.TRP[0].TR, &towerInfo.TRP[0].TRP, &towerInfo.Skill[0].TSP, &towerInfo.Level[0].Floors, &towerInfo.Level[1].Floors, &tempSkills) + var tempSkills string + err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), COALESCE(skills, $1) FROM tower WHERE char_id=$2 + `, EmptyTowerCSV(64), s.charID).Scan(&towerInfo.TRP[0].TR, &towerInfo.TRP[0].TRP, &towerInfo.Skill[0].TSP, &towerInfo.Level[0].Floors, &towerInfo.Level[1].Floors, &tempSkills) if err != nil { s.server.db.Exec(`INSERT INTO tower (char_id) VALUES ($1)`, s.charID) } + if _config.ErupeConfig.RealClientMode <= _config.G7 { + towerInfo.Level = towerInfo.Level[:1] + } + for i, skill := range stringsupport.CSVElems(tempSkills) { - towerInfo.Skill[0].Unk1[i] = int16(skill) + towerInfo.Skill[0].Skills[i] = int16(skill) } switch pkt.InfoType { @@ -73,8 +86,8 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) { for _, skills := range towerInfo.Skill { bf := byteframe.NewByteFrame() bf.WriteInt32(skills.TSP) - for i := range skills.Unk1 { - bf.WriteInt16(skills.Unk1[i]) + for i := range skills.Skills { + bf.WriteInt16(skills.Skills[i]) } data = append(data, bf) } @@ -89,7 +102,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) { } data = append(data, bf) } - case 5: + case 3, 5: for _, level := range towerInfo.Level { bf := byteframe.NewByteFrame() bf.WriteInt32(level.Floors) @@ -123,8 +136,8 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) { switch pkt.InfoType { case 2: - skills := "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,0,0,0,0,0,0,0,0,0,0,0" - s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills) + var skills string + s.server.db.QueryRow(`SELECT COALESCE(skills, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(64), s.charID).Scan(&skills) s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID) case 1, 7: // This might give too much TSP? No idea what the rate is supposed to be @@ -412,8 +425,8 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) { gemInfo := []GemInfo{} gemHistory := []GemHistory{} - tempGems := "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,0" - s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&tempGems) + var tempGems string + s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$1`, EmptyTowerCSV(30), s.charID).Scan(&tempGems) for i, v := range stringsupport.CSVElems(tempGems) { gemInfo = append(gemInfo, GemInfo{uint16(((i / 5) * 256) + ((i % 5) + 1)), uint16(v)}) } @@ -455,8 +468,8 @@ func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) { ) } - gems := "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,0" - s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&gems) + var gems string + s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$1`, EmptyTowerCSV(30), s.charID).Scan(&gems) switch pkt.Op { case 1: // Add gem i := int(((pkt.Gem / 256) * 5) + (((pkt.Gem - ((pkt.Gem / 256) * 256)) - 1) % 5)) From 685f51ecb38d8e39ee7f9131b2cd4aa49498879c Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 14 Feb 2024 18:03:56 +1100 Subject: [PATCH 252/269] clean up Tower responses --- server/channelserver/handlers_tower.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_tower.go b/server/channelserver/handlers_tower.go index e075804e4..e6d4d8849 100644 --- a/server/channelserver/handlers_tower.go +++ b/server/channelserver/handlers_tower.go @@ -426,7 +426,7 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) { gemHistory := []GemHistory{} var tempGems string - s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$1`, EmptyTowerCSV(30), s.charID).Scan(&tempGems) + s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&tempGems) for i, v := range stringsupport.CSVElems(tempGems) { gemInfo = append(gemInfo, GemInfo{uint16(((i / 5) * 256) + ((i % 5) + 1)), uint16(v)}) } @@ -469,7 +469,7 @@ func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) { } var gems string - s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$1`, EmptyTowerCSV(30), s.charID).Scan(&gems) + s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&gems) switch pkt.Op { case 1: // Add gem i := int(((pkt.Gem / 256) * 5) + (((pkt.Gem - ((pkt.Gem / 256) * 256)) - 1) % 5)) From 79cdc28a01b52991e0c85c2e80311f2a8cc74214 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 14 Feb 2024 18:36:06 +1100 Subject: [PATCH 253/269] simplify Gem math --- server/channelserver/handlers_tower.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_tower.go b/server/channelserver/handlers_tower.go index e6d4d8849..8f32a7882 100644 --- a/server/channelserver/handlers_tower.go +++ b/server/channelserver/handlers_tower.go @@ -428,7 +428,7 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) { var tempGems string s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&tempGems) for i, v := range stringsupport.CSVElems(tempGems) { - gemInfo = append(gemInfo, GemInfo{uint16(((i / 5) * 256) + ((i % 5) + 1)), uint16(v)}) + gemInfo = append(gemInfo, GemInfo{uint16((i / 5 << 8) + (i%5 + 1)), uint16(v)}) } switch pkt.Unk0 { @@ -472,7 +472,7 @@ func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) { s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&gems) switch pkt.Op { case 1: // Add gem - i := int(((pkt.Gem / 256) * 5) + (((pkt.Gem - ((pkt.Gem / 256) * 256)) - 1) % 5)) + i := int((pkt.Gem >> 8 * 5) + (pkt.Gem - pkt.Gem&0xFF00 - 1%5)) s.server.db.Exec(`UPDATE tower SET gems=$1 WHERE char_id=$2`, stringsupport.CSVSetIndex(gems, i, stringsupport.CSVGetIndex(gems, i)+int(pkt.Quantity)), s.charID) case 2: // Transfer gem // no way im doing this for now From 18cabd03f1af3cb9532d03c3f08224d905791c21 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 19 Feb 2024 17:32:32 +1100 Subject: [PATCH 254/269] add version case to FestaInfo --- server/channelserver/handlers_festa.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 4fc5370e1..86d704545 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -321,7 +321,9 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(100) // Normal rate bf.WriteUint16(50) // 50% penalty - ps.Uint16(bf, "", false) + if _config.ErupeConfig.RealClientMode >= _config.G52 { + ps.Uint16(bf, "", false) + } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From 377ff14a22980d59f4ed30c83f23d9a7f29cf821 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 19 Feb 2024 17:34:01 +1100 Subject: [PATCH 255/269] use Monster enum on GetPaperData --- server/channelserver/handlers_data.go | 56 +++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 8844dbdd8..0f99b42f3 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -1042,34 +1042,34 @@ func handleMsgMhfGetPaperData(s *Session, p mhfpacket.MHFPacket) { {1105, 1, 10, 500, 0, 0, 0}, {1105, 2, 10, 500, 0, 0, 0}, // setServerBoss - {2001, 1, 17, 58, 0, 6, 700}, - {2001, 1, 20, 58, 0, 3, 200}, - {2001, 1, 22, 58, 0, 7, 250}, - {2001, 1, 27, 58, 0, 1, 100}, - {2001, 1, 53, 58, 0, 8, 1000}, - {2001, 1, 67, 58, 0, 9, 500}, - {2001, 1, 68, 58, 0, 2, 150}, - {2001, 1, 74, 58, 0, 4, 200}, - {2001, 1, 75, 58, 0, 5, 500}, - {2001, 1, 76, 58, 0, 10, 800}, - {2001, 1, 80, 58, 0, 11, 900}, - {2001, 1, 89, 58, 0, 12, 600}, - {2001, 2, 17, 60, 0, 6, 700}, - {2001, 2, 20, 60, 0, 3, 200}, - {2001, 2, 22, 60, 0, 7, 350}, - {2001, 2, 27, 60, 0, 1, 100}, - {2001, 2, 39, 60, 0, 13, 200}, - {2001, 2, 40, 60, 0, 15, 600}, - {2001, 2, 53, 60, 0, 8, 1000}, - {2001, 2, 67, 60, 0, 2, 500}, - {2001, 2, 68, 60, 0, 9, 150}, - {2001, 2, 74, 60, 0, 4, 200}, - {2001, 2, 75, 60, 0, 5, 500}, - {2001, 2, 76, 60, 0, 10, 800}, - {2001, 2, 80, 60, 0, 11, 900}, - {2001, 2, 81, 60, 0, 14, 900}, - {2001, 2, 89, 60, 0, 12, 600}, - {2001, 2, 94, 60, 0, 16, 1000}, + {2001, 1, mhfmon.Gravios, 58, 0, 6, 700}, + {2001, 1, mhfmon.Gypceros, 58, 0, 3, 200}, + {2001, 1, mhfmon.Basarios, 58, 0, 7, 250}, + {2001, 1, mhfmon.Velocidrome, 58, 0, 1, 100}, + {2001, 1, mhfmon.Rajang, 58, 0, 8, 1000}, + {2001, 1, mhfmon.ShogunCeanataur, 58, 0, 9, 500}, + {2001, 1, mhfmon.Bulldrome, 58, 0, 2, 150}, + {2001, 1, mhfmon.Hypnocatrice, 58, 0, 4, 200}, + {2001, 1, mhfmon.Lavasioth, 58, 0, 5, 500}, + {2001, 1, mhfmon.Tigrex, 58, 0, 10, 800}, + {2001, 1, mhfmon.Espinas, 58, 0, 11, 900}, + {2001, 1, mhfmon.Pariapuria, 58, 0, 12, 600}, + {2001, 2, mhfmon.Gravios, 60, 0, 6, 700}, + {2001, 2, mhfmon.Gypceros, 60, 0, 3, 200}, + {2001, 2, mhfmon.Basarios, 60, 0, 7, 350}, + {2001, 2, mhfmon.Velocidrome, 60, 0, 1, 100}, + {2001, 2, mhfmon.PurpleGypceros, 60, 0, 13, 200}, + {2001, 2, mhfmon.YianGaruga, 60, 0, 15, 600}, + {2001, 2, mhfmon.Rajang, 60, 0, 8, 1000}, + {2001, 2, mhfmon.ShogunCeanataur, 60, 0, 2, 500}, + {2001, 2, mhfmon.Bulldrome, 60, 0, 9, 150}, + {2001, 2, mhfmon.Hypnocatrice, 60, 0, 4, 200}, + {2001, 2, mhfmon.Lavasioth, 60, 0, 5, 500}, + {2001, 2, mhfmon.Tigrex, 60, 0, 10, 800}, + {2001, 2, mhfmon.Espinas, 60, 0, 11, 900}, + {2001, 2, mhfmon.BurningEspinas, 60, 0, 14, 900}, + {2001, 2, mhfmon.Pariapuria, 60, 0, 12, 600}, + {2001, 2, mhfmon.Dyuragaua, 60, 0, 16, 1000}, } case 6: paperData = []PaperData{ From 0b3e1f520f37cc9dffa2e23bbc98d569a5c1dfb6 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 19 Feb 2024 17:34:08 +1100 Subject: [PATCH 256/269] use Monster enum on GetPaperData --- server/channelserver/handlers_data.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 0f99b42f3..fd41e1366 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -1,6 +1,7 @@ package channelserver import ( + "erupe-ce/common/mhfmon" "erupe-ce/common/stringsupport" _config "erupe-ce/config" "fmt" From 7549fe63e68530c3f07089e4b90e3d62567bcd02 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 19 Feb 2024 17:35:24 +1100 Subject: [PATCH 257/269] conform Event Quest body size & auto change gathering points --- server/channelserver/handlers_quest.go | 73 ++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 148908dbf..53251f9db 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -22,6 +22,32 @@ type tuneValue struct { Value uint16 } +func findSubSliceIndices(data []byte, sub []byte) []int { + var indices []int + lenSub := len(sub) + for i := 0; i < len(data); i++ { + if i+lenSub > len(data) { + break + } + if equal(data[i:i+lenSub], sub) { + indices = append(indices, i) + } + } + return indices +} + +func equal(a, b []byte) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} + func BackportQuest(data []byte) []byte { wp := binary.LittleEndian.Uint32(data[0:4]) + 96 rp := wp + 4 @@ -32,7 +58,33 @@ func BackportQuest(data []byte) []byte { } copy(data[wp:wp+4], data[rp:rp+4]) } - copy(data[wp:wp+180], data[rp:rp+180]) + + fillLength := uint32(108) + if _config.ErupeConfig.RealClientMode <= _config.S6 { + fillLength = 44 + } else if _config.ErupeConfig.RealClientMode <= _config.F5 { + fillLength = 52 + } else if _config.ErupeConfig.RealClientMode <= _config.G101 { + fillLength = 76 + } + + copy(data[wp:wp+fillLength], data[rp:rp+fillLength]) + if _config.ErupeConfig.RealClientMode <= _config.G91 { + patterns := [][]byte{ + {0x0A, 0x00, 0x01, 0x33, 0xD7, 0x00}, // 10% Armor Sphere -> Stone + {0x06, 0x00, 0x02, 0x33, 0xD8, 0x00}, // 6% Armor Sphere+ -> Iron Ore + {0x0A, 0x00, 0x03, 0x33, 0xD7, 0x00}, // 10% Adv Armor Sphere -> Stone + {0x06, 0x00, 0x04, 0x33, 0xDB, 0x00}, // 6% Hard Armor Sphere -> Dragonite Ore + {0x0A, 0x00, 0x05, 0x33, 0xD9, 0x00}, // 10% Heaven Armor Sphere -> Earth Crystal + {0x06, 0x00, 0x06, 0x33, 0xDB, 0x00}, // 6% True Armor Sphere -> Dragonite Ore + } + for i := range patterns { + j := findSubSliceIndices(data, patterns[i][0:4]) + for k := range j { + copy(data[j[k]+2:j[k]+4], patterns[i][4:6]) + } + } + } return data } @@ -149,21 +201,32 @@ func loadQuestFile(s *Session, questId int) []byte { fileBytes.SetLE() fileBytes.Seek(int64(fileBytes.ReadUint32()), 0) - // The 320 bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers. - questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(320)) + bodyLength := 320 + if _config.ErupeConfig.RealClientMode <= _config.S6 { + bodyLength = 160 + } else if _config.ErupeConfig.RealClientMode <= _config.F5 { + bodyLength = 168 + } else if _config.ErupeConfig.RealClientMode <= _config.G101 { + bodyLength = 192 + } else if _config.ErupeConfig.RealClientMode <= _config.Z1 { + bodyLength = 224 + } + + // The n bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers. + questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(uint(bodyLength))) questBody.SetLE() // Find the master quest string pointer questBody.Seek(40, 0) fileBytes.Seek(int64(questBody.ReadUint32()), 0) questBody.Seek(40, 0) // Overwrite it - questBody.WriteUint32(320) + questBody.WriteUint32(uint32(bodyLength)) questBody.Seek(0, 2) // Rewrite the quest strings and their pointers var tempString []byte newStrings := byteframe.NewByteFrame() - tempPointer := 352 + tempPointer := bodyLength + 32 for i := 0; i < 8; i++ { questBody.WriteUint32(uint32(tempPointer)) temp := int64(fileBytes.Index()) From 864586a40bec9ddab66817c3f3b5a240049edcdd Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 19 Feb 2024 17:37:40 +1100 Subject: [PATCH 258/269] exclude 0 values on EnumerateQuest --- server/channelserver/handlers_quest.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 53251f9db..753e59040 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -569,8 +569,13 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { tuneValues = append(tuneValues, getTuneValueRange(3299, 200)...) tuneValues = append(tuneValues, getTuneValueRange(3325, 300)...) - offset := uint16(time.Now().Unix()) - bf.WriteUint16(offset) + var temp []tuneValue + for i := range tuneValues { + if tuneValues[i].Value > 0 { + temp = append(temp, tuneValues[i]) + } + } + tuneValues = temp tuneLimit := 770 if _config.ErupeConfig.RealClientMode <= _config.F5 { @@ -596,6 +601,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { tuneValues = tuneValues[:tuneLimit] } + offset := uint16(time.Now().Unix()) + bf.WriteUint16(offset) + bf.WriteUint16(uint16(len(tuneValues))) for i := range tuneValues { bf.WriteUint16(tuneValues[i].ID ^ offset) From 183f88654b528c421519622755145e3dbe23d3d0 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 19 Feb 2024 18:12:51 +1100 Subject: [PATCH 259/269] fix InfoFesta response on S6.0 --- server/channelserver/handlers_festa.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 86d704545..c2d6d1a30 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -234,7 +234,9 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(trial.Locale) bf.WriteUint16(trial.Reward) bf.WriteInt16(int16(FestivalColourCodes[trial.Monopoly])) - bf.WriteUint16(trial.Unk) + if _config.ErupeConfig.RealClientMode >= _config.F4 { // Not in S6.0 + bf.WriteUint16(trial.Unk) + } } // The Winner and Loser Armor IDs are missing From 9cfbd924546bf32d4c090374a4da4beb2ff4d9a0 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 19 Feb 2024 18:36:03 +1100 Subject: [PATCH 260/269] add blank Winner entries to InfoFesta --- server/channelserver/handlers_festa.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index c2d6d1a30..e7afcf33d 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -293,22 +293,22 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint16(500) - categoryWinners := uint16(0) // NYI + categoryWinners := uint16(4) // NYI bf.WriteUint16(categoryWinners) for i := uint16(0); i < categoryWinners; i++ { - bf.WriteUint32(0) // Guild ID - bf.WriteUint16(i + 1) // Category ID - bf.WriteUint16(0) // Festa Team - ps.Uint8(bf, "", true) // Guild Name + bf.WriteUint32(0) // Guild ID + bf.WriteUint16(i + 1) // Category ID + bf.WriteInt16(int16(FestivalColourCodes[FestivalColourNone])) // Festa Team + ps.Uint8(bf, "", true) // Guild Name } - dailyWinners := uint16(0) // NYI + dailyWinners := uint16(7) // NYI bf.WriteUint16(dailyWinners) for i := uint16(0); i < dailyWinners; i++ { - bf.WriteUint32(0) // Guild ID - bf.WriteUint16(i + 1) // Category ID - bf.WriteUint16(0) // Festa Team - ps.Uint8(bf, "", true) // Guild Name + bf.WriteUint32(0) // Guild ID + bf.WriteUint16(i + 1) // Category ID + bf.WriteInt16(int16(FestivalColourCodes[FestivalColourNone])) // Festa Team + ps.Uint8(bf, "", true) // Guild Name } bf.WriteUint32(0) // Clan goal From b73f85ef4a346b46d16759c7ca8ec163cd523f2e Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 20 Feb 2024 00:33:38 +1100 Subject: [PATCH 261/269] add Quest timer toggle Chat Command --- config.json | 5 ++++ schemas/patch-schema/timer-toggle.sql | 5 ++++ server/channelserver/handlers_cast_binary.go | 25 ++++++++++++++++---- server/channelserver/sys_language.go | 10 ++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 schemas/patch-schema/timer-toggle.sql diff --git a/config.json b/config.json index c8705a661..48e166824 100644 --- a/config.json +++ b/config.json @@ -157,6 +157,11 @@ "Enabled": false, "Description": "Ban/Temp Ban a user", "Prefix": "ban" + }, { + "Name": "Timer", + "Enabled": true, + "Description": "Toggle the Quest timer", + "Prefix": "timer" } ], "Courses": [ diff --git a/schemas/patch-schema/timer-toggle.sql b/schemas/patch-schema/timer-toggle.sql new file mode 100644 index 000000000..c2bff008f --- /dev/null +++ b/schemas/patch-schema/timer-toggle.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE users ADD COLUMN IF NOT EXISTS timer bool; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index c219e8a42..67e6e3a3a 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -144,6 +144,19 @@ func parseChatCommand(s *Session, command string) { } else { sendServerChatMessage(s, s.server.i18n.commands.noOp) } + case commands["Timer"].Prefix: + if commands["Timer"].Enabled || s.isOp() { + var state bool + s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&state) + s.server.db.Exec(`UPDATE users u SET timer=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, !state, s.charID) + if state { + sendServerChatMessage(s, s.server.i18n.commands.timer.disabled) + } else { + sendServerChatMessage(s, s.server.i18n.commands.timer.enabled) + } + } else { + sendDisabledCommandMessage(s, commands["Timer"]) + } case commands["PSN"].Prefix: if commands["PSN"].Enabled || s.isOp() { if len(args) > 1 { @@ -413,10 +426,14 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 { if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 { - _ = tmp.ReadBytes(9) - tmp.SetLE() - frame := tmp.ReadUint32() - sendServerChatMessage(s, fmt.Sprintf("TIME : %d'%d.%03d (%dframe)", frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame)) + var timer bool + s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&timer) + if timer { + _ = tmp.ReadBytes(9) + tmp.SetLE() + frame := tmp.ReadUint32() + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.timer, frame/30/60/60, frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame)) + } } } diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index eae96dc85..aae8706bb 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -5,6 +5,7 @@ type i18n struct { cafe struct { reset string } + timer string commands struct { noOp string disabled string @@ -46,6 +47,10 @@ type i18n struct { error string length string } + timer struct { + enabled string + disabled string + } ravi struct { noCommand string start struct { @@ -102,6 +107,7 @@ func getLangStrings(s *Server) i18n { case "jp": i.language = "日本語" i.cafe.reset = "%d/%dにリセット" + i.timer = "タイマー:%02d'%02d\"%02d.%03d (%df)" i.commands.noOp = "You don't have permission to use this command" i.commands.disabled = "%sのコマンドは無効です" @@ -164,6 +170,7 @@ func getLangStrings(s *Server) i18n { default: i.language = "English" i.cafe.reset = "Resets on %d/%d" + i.timer = "Time: %02d:%02d:%02d.%03d (%df)" i.commands.noOp = "You don't have permission to use this command" i.commands.disabled = "%s command is disabled" @@ -192,6 +199,9 @@ func getLangStrings(s *Server) i18n { i.commands.ban.error = "Error in command. Format: %s [length]" i.commands.ban.length = " until %s" + i.commands.timer.enabled = "Quest timer enabled" + i.commands.timer.disabled = "Quest timer disabled" + i.commands.ravi.noCommand = "No Raviente command specified!" i.commands.ravi.start.success = "The Great Slaying will begin in a moment" i.commands.ravi.start.error = "The Great Slaying has already begun!" From df062613ebb495c7a43ed4a8cbe61aac58a0c6a6 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 20 Feb 2024 03:08:48 +1100 Subject: [PATCH 262/269] add Save pointers for S6.0 --- server/channelserver/handlers_character.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 9d90cc898..310fa0221 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -97,6 +97,17 @@ func getPointers() map[SavePointer]int { pointers[pGalleryData] = 72064 pointers[pGardenData] = 74424 pointers[pRP] = 74614 + case _config.S6: + pointers[pWeaponID] = 12522 + pointers[pWeaponType] = 12789 + pointers[pHouseTier] = 13900 + pointers[pToreData] = 14228 + pointers[pHRP] = 14550 + pointers[pHouseData] = 14561 + pointers[pBookshelfData] = 9118 // Probably same here + pointers[pGalleryData] = 24064 + pointers[pGardenData] = 26424 + pointers[pRP] = 26614 } if _config.ErupeConfig.RealClientMode == _config.G5 { pointers[lBookshelfData] = 5548 @@ -212,7 +223,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.Gender = false } if !save.IsNewCharacter { - if _config.ErupeConfig.RealClientMode >= _config.F4 { + if _config.ErupeConfig.RealClientMode >= _config.S6 { save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] From 1c4370b9299b03c92eb8e171e4015471d2dade89 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 20 Feb 2024 04:12:22 +1100 Subject: [PATCH 263/269] fix EnumerateFestaMember prior to Z2 --- server/channelserver/handlers_festa.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index e7afcf33d..4b1929169 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -408,7 +408,12 @@ func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(0) // Unk for _, member := range validMembers { bf.WriteUint32(member.CharID) - bf.WriteUint32(member.Souls) + if _config.ErupeConfig.RealClientMode <= _config.Z1 { + bf.WriteUint16(uint16(member.Souls)) + bf.WriteUint16(0) + } else { + bf.WriteUint32(member.Souls) + } } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From d22a7c782f2adff174582cbe1ac02d0cfbc9c152 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 20 Feb 2024 04:16:26 +1100 Subject: [PATCH 264/269] changes to FestivalColor --- server/channelserver/handlers_festa.go | 16 +++---- server/channelserver/handlers_guild.go | 60 +++++++++++++------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 4b1929169..530f3fc7b 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -141,13 +141,13 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 { } type FestaTrial struct { - ID uint32 `db:"id"` - Objective uint16 `db:"objective"` - GoalID uint32 `db:"goal_id"` - TimesReq uint16 `db:"times_req"` - Locale uint16 `db:"locale_req"` - Reward uint16 `db:"reward"` - Monopoly FestivalColour `db:"monopoly"` + ID uint32 `db:"id"` + Objective uint16 `db:"objective"` + GoalID uint32 `db:"goal_id"` + TimesReq uint16 `db:"times_req"` + Locale uint16 `db:"locale_req"` + Reward uint16 `db:"reward"` + Monopoly FestivalColor `db:"monopoly"` Unk uint16 } @@ -233,7 +233,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(trial.TimesReq) bf.WriteUint16(trial.Locale) bf.WriteUint16(trial.Reward) - bf.WriteInt16(int16(FestivalColourCodes[trial.Monopoly])) + bf.WriteInt16(FestivalColorCodes[trial.Monopoly]) if _config.ErupeConfig.RealClientMode >= _config.F4 { // Not in S6.0 bf.WriteUint16(trial.Unk) } diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 43629e49a..7fc90cdf1 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -21,18 +21,18 @@ import ( "go.uber.org/zap" ) -type FestivalColour string +type FestivalColor string const ( - FestivalColourNone FestivalColour = "none" - FestivalColourBlue FestivalColour = "blue" - FestivalColourRed FestivalColour = "red" + FestivalColorNone FestivalColor = "none" + FestivalColorBlue FestivalColor = "blue" + FestivalColorRed FestivalColor = "red" ) -var FestivalColourCodes = map[FestivalColour]int8{ - FestivalColourNone: -1, - FestivalColourBlue: 0, - FestivalColourRed: 1, +var FestivalColorCodes = map[FestivalColor]int16{ + FestivalColorNone: -1, + FestivalColorBlue: 0, + FestivalColorRed: 1, } type GuildApplicationType string @@ -43,27 +43,27 @@ const ( ) type Guild struct { - ID uint32 `db:"id"` - Name string `db:"name"` - MainMotto uint8 `db:"main_motto"` - SubMotto uint8 `db:"sub_motto"` - CreatedAt time.Time `db:"created_at"` - MemberCount uint16 `db:"member_count"` - RankRP uint32 `db:"rank_rp"` - EventRP uint32 `db:"event_rp"` - Comment string `db:"comment"` - PugiName1 string `db:"pugi_name_1"` - PugiName2 string `db:"pugi_name_2"` - PugiName3 string `db:"pugi_name_3"` - PugiOutfit1 uint8 `db:"pugi_outfit_1"` - PugiOutfit2 uint8 `db:"pugi_outfit_2"` - PugiOutfit3 uint8 `db:"pugi_outfit_3"` - PugiOutfits uint32 `db:"pugi_outfits"` - Recruiting bool `db:"recruiting"` - FestivalColour FestivalColour `db:"festival_colour"` - Souls uint32 `db:"souls"` - AllianceID uint32 `db:"alliance_id"` - Icon *GuildIcon `db:"icon"` + ID uint32 `db:"id"` + Name string `db:"name"` + MainMotto uint8 `db:"main_motto"` + SubMotto uint8 `db:"sub_motto"` + CreatedAt time.Time `db:"created_at"` + MemberCount uint16 `db:"member_count"` + RankRP uint32 `db:"rank_rp"` + EventRP uint32 `db:"event_rp"` + Comment string `db:"comment"` + PugiName1 string `db:"pugi_name_1"` + PugiName2 string `db:"pugi_name_2"` + PugiName3 string `db:"pugi_name_3"` + PugiOutfit1 uint8 `db:"pugi_outfit_1"` + PugiOutfit2 uint8 `db:"pugi_outfit_2"` + PugiOutfit3 uint8 `db:"pugi_outfit_3"` + PugiOutfits uint32 `db:"pugi_outfits"` + Recruiting bool `db:"recruiting"` + FestivalColor FestivalColor `db:"festival_colour"` + Souls uint32 `db:"souls"` + AllianceID uint32 `db:"alliance_id"` + Icon *GuildIcon `db:"icon"` GuildLeader } @@ -967,7 +967,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(uint8(len(guildLeaderName))) bf.WriteBytes(guildName) bf.WriteBytes(guildComment) - bf.WriteInt8(FestivalColourCodes[guild.FestivalColour]) + bf.WriteInt8(int8(FestivalColorCodes[guild.FestivalColor])) bf.WriteUint32(guild.RankRP) bf.WriteBytes(guildLeaderName) bf.WriteUint32(0) // Unk From 5bcfe25ede474c2f7437f1143af5d61d56b98e30 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 20 Feb 2024 04:18:16 +1100 Subject: [PATCH 265/269] implement Festa Bonus Categories & Guild Character optimisations --- network/mhfpacket/msg_mhf_charge_festa.go | 32 ++++----- schemas/patch-schema/festa-submissions.sql | 15 +++++ server/channelserver/handlers_festa.go | 65 ++++++++++++++----- server/channelserver/handlers_guild_member.go | 54 +++++++-------- 4 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 schemas/patch-schema/festa-submissions.sql diff --git a/network/mhfpacket/msg_mhf_charge_festa.go b/network/mhfpacket/msg_mhf_charge_festa.go index f5452df73..145c2a3a7 100644 --- a/network/mhfpacket/msg_mhf_charge_festa.go +++ b/network/mhfpacket/msg_mhf_charge_festa.go @@ -1,19 +1,20 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfChargeFesta represents the MSG_MHF_CHARGE_FESTA type MsgMhfChargeFesta struct { - AckHandle uint32 - FestaID uint32 - GuildID uint32 - Souls int + AckHandle uint32 + FestaID uint32 + GuildID uint32 + Souls []uint16 + Auto bool } // Opcode returns the ID associated with this packet type. @@ -23,15 +24,14 @@ func (m *MsgMhfChargeFesta) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfChargeFesta) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.FestaID = bf.ReadUint32() - m.GuildID = bf.ReadUint32() - m.Souls = 0 - for i := bf.ReadUint16(); i > 0; i-- { - m.Souls += int(bf.ReadUint16()) - } - _ = bf.ReadUint8() // Unk - return nil + m.AckHandle = bf.ReadUint32() + m.FestaID = bf.ReadUint32() + m.GuildID = bf.ReadUint32() + for i := bf.ReadUint16(); i > 0; i-- { + m.Souls = append(m.Souls, bf.ReadUint16()) + } + m.Auto = bf.ReadBool() + return nil } // Build builds a binary packet from the current data. diff --git a/schemas/patch-schema/festa-submissions.sql b/schemas/patch-schema/festa-submissions.sql new file mode 100644 index 000000000..d720c587f --- /dev/null +++ b/schemas/patch-schema/festa-submissions.sql @@ -0,0 +1,15 @@ +BEGIN; + +CREATE TABLE festa_submissions ( + character_id int NOT NULL, + guild_id int NOT NULL, + trial_type int NOT NULL, + souls int NOT NULL, + timestamp timestamp with time zone NOT NULL +); + +ALTER TABLE guild_characters DROP COLUMN souls; + +ALTER TYPE festival_colour RENAME TO festival_color; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 530f3fc7b..afab08dfb 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -95,8 +95,9 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) { func cleanupFesta(s *Session) { s.server.db.Exec("DELETE FROM events WHERE event_type='festa'") s.server.db.Exec("DELETE FROM festa_registrations") + s.server.db.Exec("DELETE FROM festa_submissions") s.server.db.Exec("DELETE FROM festa_prizes_accepted") - s.server.db.Exec("UPDATE guild_characters SET souls=0, trial_vote=NULL") + s.server.db.Exec("UPDATE guild_characters SET trial_vote=NULL") } func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 { @@ -293,22 +294,45 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint16(500) - categoryWinners := uint16(4) // NYI - bf.WriteUint16(categoryWinners) - for i := uint16(0); i < categoryWinners; i++ { - bf.WriteUint32(0) // Guild ID - bf.WriteUint16(i + 1) // Category ID - bf.WriteInt16(int16(FestivalColourCodes[FestivalColourNone])) // Festa Team - ps.Uint8(bf, "", true) // Guild Name + var temp uint32 + bf.WriteUint16(4) + for i := uint16(0); i < 4; i++ { + var guildID uint32 + var guildName string + var guildTeam = FestivalColorNone + s.server.db.QueryRow(` + SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _ + FROM festa_submissions fs + LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id + LEFT JOIN guilds g ON fs.guild_id = g.id + WHERE fs.trial_type = $1 + GROUP BY fs.guild_id, g.name, fr.team + ORDER BY _ DESC LIMIT 1 + `, i+1).Scan(&guildID, &guildName, &guildTeam, &temp) + bf.WriteUint32(guildID) + bf.WriteUint16(i + 1) + bf.WriteInt16(FestivalColorCodes[guildTeam]) + ps.Uint8(bf, guildName, true) } - - dailyWinners := uint16(7) // NYI - bf.WriteUint16(dailyWinners) - for i := uint16(0); i < dailyWinners; i++ { - bf.WriteUint32(0) // Guild ID - bf.WriteUint16(i + 1) // Category ID - bf.WriteInt16(int16(FestivalColourCodes[FestivalColourNone])) // Festa Team - ps.Uint8(bf, "", true) // Guild Name + bf.WriteUint16(7) + for i := uint16(0); i < 7; i++ { + var guildID uint32 + var guildName string + var guildTeam = FestivalColorNone + offset := 86400 * uint32(i) + s.server.db.QueryRow(` + SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _ + FROM festa_submissions fs + LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id + LEFT JOIN guilds g ON fs.guild_id = g.id + WHERE EXTRACT(EPOCH FROM fs.timestamp)::int > $1 AND EXTRACT(EPOCH FROM fs.timestamp)::int < $2 + GROUP BY fs.guild_id, g.name, fr.team + ORDER BY _ DESC LIMIT 1 + `, timestamps[1]+offset, timestamps[1]+offset+86400).Scan(&guildID, &guildName, &guildTeam, &temp) + bf.WriteUint32(guildID) + bf.WriteUint16(i + 1) + bf.WriteInt16(FestivalColorCodes[guildTeam]) + ps.Uint8(bf, guildName, true) } bf.WriteUint32(0) // Clan goal @@ -445,7 +469,14 @@ func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfChargeFesta) - s.server.db.Exec("UPDATE guild_characters SET souls=souls+$1 WHERE character_id=$2", pkt.Souls, s.charID) + tx, _ := s.server.db.Begin() + for i := range pkt.Souls { + if pkt.Souls[i] == 0 { + continue + } + _, _ = tx.Exec(`INSERT INTO festa_submissions VALUES ($1, $2, $3, $4, now())`, s.charID, pkt.GuildID, i, pkt.Souls[i]) + } + _ = tx.Commit() doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } diff --git a/server/channelserver/handlers_guild_member.go b/server/channelserver/handlers_guild_member.go index e053e2498..ac64e892a 100644 --- a/server/channelserver/handlers_guild_member.go +++ b/server/channelserver/handlers_guild_member.go @@ -61,41 +61,35 @@ func (gm *GuildMember) Save(s *Session) error { } const guildMembersSelectSQL = ` -SELECT - g.id as guild_id, - joined_at, - coalesce(souls, 0) as souls, - COALESCE(rp_today, 0) AS rp_today, - COALESCE(rp_yesterday, 0) AS rp_yesterday, - c.name, - character.character_id, - coalesce(gc.order_index, 0) as order_index, - c.last_login, - coalesce(gc.recruiter, false) as recruiter, - coalesce(gc.avoid_leadership, false) as avoid_leadership, - c.hrp, - c.gr, - c.weapon_id, - c.weapon_type, - character.is_applicant, - CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader - FROM ( - SELECT character_id, true as is_applicant, guild_id - FROM guild_applications ga - WHERE ga.application_type = 'applied' - UNION - SELECT character_id, false as is_applicant, guild_id +SELECT * FROM ( + SELECT + g.id AS guild_id, + joined_at, + COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls, + COALESCE(rp_today, 0) AS rp_today, + COALESCE(rp_yesterday, 0) AS rp_yesterday, + c.name, + c.id AS character_id, + COALESCE(order_index, 0) AS order_index, + c.last_login, + COALESCE(recruiter, false) AS recruiter, + COALESCE(avoid_leadership, false) AS avoid_leadership, + c.hrp, + c.gr, + c.weapon_id, + c.weapon_type, + EXISTS(SELECT 1 FROM guild_applications ga WHERE ga.character_id=c.id AND application_type='applied') AS is_applicant, + CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader FROM guild_characters gc - ) character - JOIN characters c on character.character_id = c.id - LEFT JOIN guild_characters gc ON gc.character_id = character.character_id - JOIN guilds g ON g.id = character.guild_id + LEFT JOIN characters c ON c.id = gc.character_id + LEFT JOIN guilds g ON g.id = gc.guild_id +) AS subquery ` func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) { rows, err := s.server.db.Queryx(fmt.Sprintf(` %s - WHERE character.guild_id = $1 AND is_applicant = $2 + WHERE guild_id = $1 AND is_applicant = $2 `, guildMembersSelectSQL), guildID, applicants) if err != nil { @@ -121,7 +115,7 @@ func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMembe } func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) { - rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID) + rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character_id=$1", guildMembersSelectSQL), charID) if err != nil { s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID)) From c5905d74d4d7c2f8b38d8a6f7d471855b38c62ae Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 20 Feb 2024 04:19:43 +1100 Subject: [PATCH 266/269] index Patch Schemas --- schemas/patch-schema/{op-accounts.sql => 17-op-accounts.sql} | 0 schemas/patch-schema/{timer-toggle.sql => 18-timer-toggle.sql} | 0 .../{festa-submissions.sql => 19-festa-submissions.sql} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename schemas/patch-schema/{op-accounts.sql => 17-op-accounts.sql} (100%) rename schemas/patch-schema/{timer-toggle.sql => 18-timer-toggle.sql} (100%) rename schemas/patch-schema/{festa-submissions.sql => 19-festa-submissions.sql} (100%) diff --git a/schemas/patch-schema/op-accounts.sql b/schemas/patch-schema/17-op-accounts.sql similarity index 100% rename from schemas/patch-schema/op-accounts.sql rename to schemas/patch-schema/17-op-accounts.sql diff --git a/schemas/patch-schema/timer-toggle.sql b/schemas/patch-schema/18-timer-toggle.sql similarity index 100% rename from schemas/patch-schema/timer-toggle.sql rename to schemas/patch-schema/18-timer-toggle.sql diff --git a/schemas/patch-schema/festa-submissions.sql b/schemas/patch-schema/19-festa-submissions.sql similarity index 100% rename from schemas/patch-schema/festa-submissions.sql rename to schemas/patch-schema/19-festa-submissions.sql From a968f18438d39907200c9a95cffa7b1b36e81e11 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 20 Feb 2024 14:53:59 +1100 Subject: [PATCH 267/269] add support for SIGN requests --- server/signserver/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/signserver/session.go b/server/signserver/session.go index 164ab70e2..e4cbd5537 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -54,7 +54,7 @@ func (s *Session) handlePacket(pkt []byte) error { bf := byteframe.NewByteFrameFromBytes(pkt) reqType := string(bf.ReadNullTerminatedBytes()) switch reqType[:len(reqType)-3] { - case "DLTSKEYSIGN:", "DSGN:": + case "DLTSKEYSIGN:", "DSGN:", "SIGN:": s.handleDSGN(bf) case "PS3SGN:": s.client = PS3 From c3409996efdbc4380fa0721ca2a7f36a2412f8b6 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 20 Feb 2024 15:02:10 +1100 Subject: [PATCH 268/269] rollback unknown Festa values --- server/channelserver/handlers_festa.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index afab08dfb..11a0670ef 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -399,10 +399,10 @@ func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) { return } resp.WriteUint32(guild.Souls) - resp.WriteInt32(0) // unk + resp.WriteInt32(1) // unk resp.WriteInt32(1) // unk, rank? - resp.WriteInt32(0) // unk - resp.WriteInt32(0) // unk + resp.WriteInt32(1) // unk + resp.WriteInt32(1) // unk doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } From d0e727d444bad4176486f43dde1589652f100cb7 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 20 Feb 2024 18:52:56 +1100 Subject: [PATCH 269/269] fix remaining Festa queries --- server/channelserver/handlers_festa.go | 12 ++++++------ server/channelserver/handlers_guild.go | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 11a0670ef..f833a1f4e 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -190,8 +190,8 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { } var blueSouls, redSouls uint32 - s.server.db.QueryRow("SELECT SUM(gc.souls) FROM guild_characters gc INNER JOIN festa_registrations fr ON fr.guild_id = gc.guild_id WHERE fr.team = 'blue'").Scan(&blueSouls) - s.server.db.QueryRow("SELECT SUM(gc.souls) FROM guild_characters gc INNER JOIN festa_registrations fr ON fr.guild_id = gc.guild_id WHERE fr.team = 'red'").Scan(&redSouls) + s.server.db.QueryRow(`SELECT COALESCE(SUM(fs.souls), 0) AS souls FROM festa_registrations fr LEFT JOIN festa_submissions fs ON fr.guild_id = fs.guild_id AND fr.team = 'blue'`).Scan(&blueSouls) + s.server.db.QueryRow(`SELECT COALESCE(SUM(fs.souls), 0) AS souls FROM festa_registrations fr LEFT JOIN festa_submissions fs ON fr.guild_id = fs.guild_id AND fr.team = 'red'`).Scan(&redSouls) bf.WriteUint32(id) for _, timestamp := range timestamps { @@ -210,11 +210,11 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { COALESCE(CASE WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id) > COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id) - THEN CAST('blue' AS public.festival_colour) + THEN CAST('blue' AS public.festival_color) WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id) > COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id) - THEN CAST('red' AS public.festival_colour) - END, CAST('none' AS public.festival_colour)) AS monopoly + THEN CAST('red' AS public.festival_color) + END, CAST('none' AS public.festival_color)) AS monopoly FROM public.festa_trials ft LEFT JOIN public.guild_characters gc ON ft.id = gc.trial_vote LEFT JOIN public.festa_registrations fr ON gc.guild_id = fr.guild_id @@ -366,7 +366,7 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) { return } var souls, exists uint32 - s.server.db.QueryRow("SELECT souls FROM guild_characters WHERE character_id=$1", s.charID).Scan(&souls) + s.server.db.QueryRow(`SELECT COALESCE((SELECT SUM(souls) FROM festa_submissions WHERE character_id=$1), 0)`, s.charID).Scan(&souls) err = s.server.db.QueryRow("SELECT prize_id FROM festa_prizes_accepted WHERE prize_id=0 AND character_id=$1", s.charID).Scan(&exists) bf := byteframe.NewByteFrame() bf.WriteUint32(souls) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 7fc90cdf1..9df9dd2c6 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -60,7 +60,7 @@ type Guild struct { PugiOutfit3 uint8 `db:"pugi_outfit_3"` PugiOutfits uint32 `db:"pugi_outfits"` Recruiting bool `db:"recruiting"` - FestivalColor FestivalColor `db:"festival_colour"` + FestivalColor FestivalColor `db:"festival_color"` Souls uint32 `db:"souls"` AllianceID uint32 `db:"alliance_id"` Icon *GuildIcon `db:"icon"` @@ -157,7 +157,7 @@ SELECT sub_motto, created_at, leader_id, - lc.name as leader_name, + c.name AS leader_name, comment, COALESCE(pugi_name_1, '') AS pugi_name_1, COALESCE(pugi_name_2, '') AS pugi_name_2, @@ -167,8 +167,8 @@ SELECT pugi_outfit_3, pugi_outfits, recruiting, - COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_colour, - (SELECT SUM(souls) FROM guild_characters gc WHERE gc.guild_id = g.id) AS souls, + COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_color, + COALESCE((SELECT SUM(fs.souls) FROM festa_submissions fs WHERE fs.guild_id=g.id), 0) AS souls, COALESCE(( SELECT id FROM guild_alliances ga WHERE ga.parent_id = g.id OR @@ -178,8 +178,8 @@ SELECT icon, (SELECT count(1) FROM guild_characters gc WHERE gc.guild_id = g.id) AS member_count FROM guilds g - JOIN guild_characters lgc ON lgc.character_id = leader_id - JOIN characters lc on leader_id = lc.id + JOIN guild_characters gc ON gc.character_id = leader_id + JOIN characters c on leader_id = c.id ` func (guild *Guild) Save(s *Session) error {