From 843b6a9dff99b12d83f07bb33c020f10f3d4bbe4 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 17 Mar 2024 16:39:06 +1100 Subject: [PATCH 01/51] fix quest stampcard --- network/mhfpacket/msg_mhf_stampcard_stamp.go | 2 +- server/channelserver/handlers.go | 85 +++++++++++++++----- 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/network/mhfpacket/msg_mhf_stampcard_stamp.go b/network/mhfpacket/msg_mhf_stampcard_stamp.go index a783b3e91..521e42221 100644 --- a/network/mhfpacket/msg_mhf_stampcard_stamp.go +++ b/network/mhfpacket/msg_mhf_stampcard_stamp.go @@ -37,7 +37,7 @@ func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli } m.Stamps = bf.ReadUint16() bf.ReadUint16() // Zeroed - if _config.ErupeConfig.RealClientMode > _config.Z1 { + if _config.ErupeConfig.RealClientMode >= _config.Z2 { m.Reward1 = uint16(bf.ReadUint32()) m.Reward2 = uint16(bf.ReadUint32()) m.Item1 = uint16(bf.ReadUint32()) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 7188d8bab..d188faba5 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -1045,34 +1045,81 @@ func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } +func getStampcardReward(secondStamp bool, HR uint16, GR uint16) mhfitem.MHFItemStack { + if GR > 0 { + if secondStamp { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 4} + } else { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 1} + } + } else { + if HR >= 300 { + if secondStamp { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 3} + } else { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 1} + } + } else if HR >= 100 { + if secondStamp { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 1} + } else { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 3} + } + } else if HR >= 50 { + if secondStamp { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 3} + } else { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 2} + } + } else { + if secondStamp { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 2} + } else { + return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 1} + } + } + } +} + func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfStampcardStamp) bf := byteframe.NewByteFrame() bf.WriteUint16(pkt.HR) - var stamps uint16 - _ = s.server.db.QueryRow(`SELECT stampcard FROM characters WHERE id = $1`, s.charID).Scan(&stamps) if _config.ErupeConfig.RealClientMode >= _config.G1 { bf.WriteUint16(pkt.GR) } + var stamps, rewardTier, rewardUnk uint16 + reward := mhfitem.MHFItemStack{Item: mhfitem.MHFItem{}} + s.server.db.QueryRow(`UPDATE characters SET stampcard = stampcard + $1 WHERE id = $2 RETURNING stampcard`, pkt.Stamps, s.charID).Scan(&stamps) + bf.WriteUint16(stamps - pkt.Stamps) bf.WriteUint16(stamps) - stamps += pkt.Stamps - bf.WriteUint16(stamps) - s.server.db.Exec(`UPDATE characters SET stampcard = $1 WHERE id = $2`, stamps, s.charID) - if stamps%30 == 0 { - bf.WriteUint16(2) - bf.WriteUint16(pkt.Reward2) - bf.WriteUint16(pkt.Item2) - bf.WriteUint16(pkt.Quantity2) - addWarehouseItem(s, mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item2}, Quantity: pkt.Quantity2}) - } else if stamps%15 == 0 { - bf.WriteUint16(1) - bf.WriteUint16(pkt.Reward1) - bf.WriteUint16(pkt.Item1) - bf.WriteUint16(pkt.Quantity1) - addWarehouseItem(s, mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item1}, Quantity: pkt.Quantity1}) - } else { - bf.WriteBytes(make([]byte, 8)) + + if stamps/30 > (stamps-pkt.Stamps)/30 { + rewardTier = 2 + if _config.ErupeConfig.RealClientMode < _config.Z2 { + rewardUnk = 10 + reward = getStampcardReward(true, pkt.HR, pkt.GR) + } else { + rewardUnk = pkt.Reward2 + reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item2}, Quantity: pkt.Quantity2} + } + addWarehouseItem(s, reward) + } else if stamps/15 > (stamps-pkt.Stamps)/15 { + rewardTier = 1 + if _config.ErupeConfig.RealClientMode < _config.Z2 { + rewardUnk = 10 + reward = getStampcardReward(false, pkt.HR, pkt.GR) + } else { + rewardUnk = pkt.Reward1 + reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item1}, Quantity: pkt.Quantity1} + } + addWarehouseItem(s, reward) } + + bf.WriteUint16(rewardTier) + bf.WriteUint16(rewardUnk) + bf.WriteUint16(reward.Item.ItemID) + bf.WriteUint16(reward.Quantity) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From ef99cc76595e3b3da58ce90b44dd129ed9cb4fdb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:40:55 +0000 Subject: [PATCH 02/51] Bump golang.org/x/net from 0.18.0 to 0.23.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.18.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.18.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 5047c6609..2d0a7e433 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.17.0 + golang.org/x/crypto v0.21.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/text v0.14.0 ) @@ -31,8 +31,8 @@ require ( 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/net v0.18.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.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 d2cc39776..d85d69d60 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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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= @@ -289,8 +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/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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= @@ -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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.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 100ec30fba5dc70ae0a651663a3bd014198a1cd0 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 16 Jun 2024 17:46:12 +1000 Subject: [PATCH 03/51] fix GetCafeDuration --- server/channelserver/handlers_cafe.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index b87bac5ff..2211c5c64 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -4,6 +4,7 @@ import ( "erupe-ce/common/byteframe" "erupe-ce/common/mhfcourse" ps "erupe-ce/common/pascalstring" + _config "erupe-ce/config" "erupe-ce/network/mhfpacket" "fmt" "go.uber.org/zap" @@ -92,10 +93,11 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) { if mhfcourse.CourseExists(30, s.courses) { cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime } - bf.WriteUint32(cafeTime) // Total cafe time - bf.WriteUint16(0) - ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true) - + bf.WriteUint32(cafeTime) + if _config.ErupeConfig.RealClientMode >= _config.G10 { // TODO: Confirm G10, not in G9.1 + bf.WriteUint16(0) + ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true) + } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } From e12f444b8da121e2aa05e8f28d6347a60ff95ebd Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 16 Jun 2024 18:16:12 +1000 Subject: [PATCH 04/51] fix GetCafeDuration --- server/channelserver/handlers_cafe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index 2211c5c64..4449c3316 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -94,7 +94,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) { cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime } bf.WriteUint32(cafeTime) - if _config.ErupeConfig.RealClientMode >= _config.G10 { // TODO: Confirm G10, not in G9.1 + if _config.ErupeConfig.RealClientMode >= _config.Z2 { // TODO: Confirm Z2, not in Z1 bf.WriteUint16(0) ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true) } From 12c7774cc125d0f87a0cf1e05c6b7473b228cf12 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 25 Jun 2024 20:35:56 +1000 Subject: [PATCH 05/51] fix GetCafeDuration --- server/channelserver/handlers_cafe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index 4449c3316..406732371 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -94,7 +94,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) { cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime } bf.WriteUint32(cafeTime) - if _config.ErupeConfig.RealClientMode >= _config.Z2 { // TODO: Confirm Z2, not in Z1 + if _config.ErupeConfig.RealClientMode >= _config.ZZ { bf.WriteUint16(0) ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true) } From dd13713bc15fff2a14ec53e7b36eeb85fd97921e Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 25 Jun 2024 22:50:58 +1000 Subject: [PATCH 06/51] fix parsing SysTerminalLog --- network/mhfpacket/msg_sys_terminal_log.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/mhfpacket/msg_sys_terminal_log.go b/network/mhfpacket/msg_sys_terminal_log.go index 5033346b4..bad160a73 100644 --- a/network/mhfpacket/msg_sys_terminal_log.go +++ b/network/mhfpacket/msg_sys_terminal_log.go @@ -40,8 +40,8 @@ func (m *MsgSysTerminalLog) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client entryCount := int(bf.ReadUint16()) bf.ReadUint16() // Zeroed - var e TerminalLogEntry for i := 0; i < entryCount; i++ { + var e TerminalLogEntry e.Index = bf.ReadUint32() e.Type1 = bf.ReadUint8() e.Type2 = bf.ReadUint8() From 0caaeac3af872a4c1e77e13f0d84b56516a1c9c8 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 15 Jul 2024 01:07:50 +1000 Subject: [PATCH 07/51] initial ngword commit --- common/stringsupport/string_convert.go | 13 +++++++ server/signserver/dsgn_resp.go | 49 +++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/common/stringsupport/string_convert.go b/common/stringsupport/string_convert.go index de4d04364..3fb4bb097 100644 --- a/common/stringsupport/string_convert.go +++ b/common/stringsupport/string_convert.go @@ -29,6 +29,19 @@ func SJISToUTF8(b []byte) string { return string(result) } +func ToNGWord(x string) []uint16 { + var w []uint16 + for _, r := range []rune(x) { + if r > 0xFF { + t := UTF8ToSJIS(string(r)) + w = append(w, uint16(t[1])<<8|uint16(t[0])) + } else { + w = append(w, uint16(r)) + } + } + return w +} + func PaddedString(x string, size uint, t bool) []byte { if t { e := japanese.ShiftJIS.NewEncoder() diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 250bdae46..3ae65a512 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -135,7 +135,54 @@ func (s *Session) makeSignResponse(uid uint32) []byte { bf.WriteUint32(s.server.getLastCID(uid)) bf.WriteUint32(s.server.getUserRights(uid)) - ps.Uint16(bf, "", false) // filters + + namNGWords := []string{"test", "痴女", "てすと"} + msgNGWords := []string{"test", "痴女", "てすと"} + + filters := byteframe.NewByteFrame() + filters.WriteNullTerminatedBytes([]byte("smc")) + smc := byteframe.NewByteFrame() + //smcBytes, _ := hex.DecodeString("3D000000818100000000000029000000816A0000000000002800000081690000000000002100000081490000000000002F000000815E0000000000002B000000817B00000000000026000000819500000000000082500000310000000000000082DA0000837B0000CE00DE0082D9DE00837ADE0082D9814A837A814ACE00814A0000000082D7000083780000CD00DE0082D6DE008377DE0082D6814ACD00814A8377814A0000000082C5000083660000C300DE0082C4DE008365DE0082C4814A8365814AC300814A81A7814A81A7DE0089B3DE0089B3814A0000000082D1000083720000CB00DE0082D0DE008371DE0082D0814A8371814ACB00814A0000000082C7000083680000C400DE0082C6DE008367DE0082C6814A8367814AC400814A84B0DE0084B0814A84A5DE0084A5814A0000000082CE0000836F0000CA00DE0082CDDE00836EDE0094AADE0082CD814A836E814ACA00814A94AA814A0000000082C2DE00836400008363DE0082C2814A8363814AC200DE00C200814A82C3000082C1DE008362DE00AF00DE0082C1814A8362814AAF00814A0000000082D4000083750000CC00DE0083940000B300DE0082A4814A82A4DE008345DE00A900DE0082A3DE0082D3DE008374DE00CC00814A0000000082C0000083610000C100DE0082BFDE008360DE0082BF814A8360814AC100814A90E7814A90E7DE000000000082BE0000835F0000C000DE0082BDDE00835EDE00975BDE0082BD814A835E814AC000814A975B814A0000000082BC0000835D0000BF00DE0082BBDE00835CDE0082BB814A835C814ABF00814A8393DE008393814ADD00814ADD00DE00838ADE00D800DE00D800814A838A814A0000000082BA0000BE00DE0082B9DE00835ADE0082B9814A835A814ABE00814A835B00000000000082B8000083590000BD00DE0082B7DE008358DE0082B7814A8358814ABD00814A0000000082B6000083570000BC00DE0082B5DE008356DE0082B5814A8356814ABC00814A0000000082B4000083550000BB00DE0082B3DE008354DE0082B3814A8354814ABB00814A0000000082B2000083530000BA00DE0082B1DE008352DE0082B1814A8352814ABA00814A0000000082B0000083510000B900DE0082AFDE008350DE0082AF814A8350814AB900814A8396DE008396814A0000000082AE0000834F0000B800DE0082ADDE00834EDE0082AD814A834E814AB800814A0000000082AC0000834D0000B700DE0082ABDE00834CDE0082AB814A834C814AB700814A0000000082AA0000834B0000B600DE008395DE00834ADE0082A9DE0097CDDE008395814A834A814A82A9814A97CD814AB600814A0000000082F0000083920000A60000000000000082ED0000838F0000DC000000838E00000000000082EB0000838D0000DB00000081A000008CFB00000000000082EA0000838C0000DA0000000000000082E90000838B0000D90000000000000082E80000838A0000D80000000000000082E7000083890000D70000000000000082E6000083880000D6000000AE00000082E50000838700000000000082E4000083860000D5000000AD00000082E30000838500000000000082E2000083840000D4000000AC00000082E10000838300000000000082E0000083820000D30000000000000082DF000083810000D20000004D0045000000000082DE000083800000D10000000000000082DD0000837E0000D00000000000000082DC0000837D0000CF0000000000000082D90000837A0000CE0000000000000082D6000083770000CD0000000000000082D3000083740000CC0000000000000082D0000083710000CB0000000000000082CD0000836E0000CA00000094AA00000000000082CC0000836D0000C90000000000000082CB0000836C0000C80000000000000082CA0000836B0000C70000000000000082C90000836A0000C600000093F100000000000082C8000083690000C50000000000000082C6000083670000C400000084B0000084A500000000000082C4000083650000C300000081A7000089B300000000000082C2000083630000C200000082C1000083620000AF0000000000000082BF000083600000C100000090E700000000000082BD0000835E0000C0000000975B00000000000082BB0000835C0000BF0000000000000082B90000835A0000BE0000000000000082B7000083580000BD0000000000000082B5000083560000BC0000000000000082B3000083540000BB0000000000000082B1000083520000BA0000000000000082AF000083500000B9000000839600000000000082AD0000834E0000B80000000000000082AB0000834C0000B70000000000000082A90000834A0000B60000008395000097CD00000000000082A8000083490000B5000000AB00000082A70000834800000000000082A6000083470000B4000000AA00000082A50000834600008D4800000000000082A4000083450000B3000000A900000082A30000834400000000000082A2000083430000B2000000A800000082A100008342000000000000A7000000B1000000829F000082A00000834000008341000000000000815B0000815C0000815D0000816000002D000000817C0000B000000088EA00000000000039000000825800000000000038000000825700000000000037000000825600000000000036000000825500000000000035000000825400000000000034000000825300000000000033000000825200000000000032000000825100000000000082DB0000837C0000CE00DF0082D9DF00837ADF00837A818B82D9818BCE00818B0000000082D8000083790000CD00DF0082D6DF008377DF008377818B82D6818BCD00818B0000000082D5000083760000CC00DF0082D3DF008374DF008374818B82D3818BCC00818B0000000082D2000083730000CB00DF0082D0DF008371DF008371818B82D0818BCB00818B0000000082CF000083700000CA00DF0082CDDF00836EDF00836E818B82CD818BCA00818B94AADF0094AA814B0000000082790000829A00005A0000007A00000083A40000000000008278000082990000590000007900000083B200008454000084850000000000008277000082980000580000007800000083B4000083D4000084560000817E000084870000875D0000000000008276000082970000570000007700000083D6000084590000848A0000848B0000000000008275000082960000560000007600000083CB000083D2000087580000000000008274000082950000550000007500000083CA000081BE0000000000008273000082940000540000007400000083B1000083D100008453000084840000000000002400000053000000730000008190000081E70000827200008293000000000000827100008292000052000000720000008460000084910000000000008270000082910000510000007100000000000000826F000082900000500000007000000083AF000083CF0000845100008482000000000000826E0000828F00004F0000006F000000819B000083AD000083CD0000844F00008480000081FC0000815A000030000000824F000000000000826D0000828E00004E0000006E00000083AB000083C50000DD00000082F100008393000000000000826C0000828D00004D0000006D00000083AA0000844D0000847D000000000000826B0000828C00004C0000006C000000879800007C00000000000000826A0000828B00004B0000006B00000083A8000083C80000844B0000847B00000000000082690000828A00004A0000006A000000000000008268000082890000490000006900000083A7000087540000000000008267000082880000480000006800000083A50000844E0000847E000000000000826500008286000046000000660000000000000082660000828700004700000067000000000000008264000082850000450000006500000083A3000083C300008445000084460000847500008476000081B80000000000008263000082840000440000006400000000000000430000008283000063000000845200008483000082620000818E0000000000004200000082610000828200006200000083C0000083A000008442000084720000848C0000848E000081F30000000000002700000081660000000000004100000082600000610000008281000083BF000040000000819700008470000081F0000084400000839F00000000000022000000816800000000000025000000819300000000000000000000") + //smc.WriteBytes(smcBytes) + filters.SetLE() + filters.WriteUint32(uint32(len(smc.Data()))) + filters.WriteBytes(smc.Data()) + + filters.WriteNullTerminatedBytes([]byte("nam")) + nam := byteframe.NewByteFrame() + nam.SetLE() + for _, word := range namNGWords { + parts := stringsupport.ToNGWord(word) + nam.WriteUint32(uint32(len(parts))) + for _, part := range parts { + nam.WriteUint16(part) + nam.WriteInt16(-1) // TODO: figure out how this value relates to corresponding SMC part + } + nam.WriteUint16(0) + nam.WriteInt16(-1) + } + filters.WriteUint32(uint32(len(nam.Data()))) + filters.WriteBytes(nam.Data()) + + filters.WriteNullTerminatedBytes([]byte("msg")) + msg := byteframe.NewByteFrame() + msg.SetLE() + for _, word := range msgNGWords { + parts := stringsupport.ToNGWord(word) + msg.WriteUint32(uint32(len(parts))) + for _, part := range parts { + msg.WriteUint16(part) + msg.WriteInt16(-1) + } + msg.WriteUint16(0) + msg.WriteInt16(-1) + } + filters.WriteUint32(uint32(len(msg.Data()))) + filters.WriteBytes(msg.Data()) + + bf.WriteUint16(uint16(len(filters.Data()))) + bf.WriteBytes(filters.Data()) + if s.client == VITA || s.client == PS3 || s.client == PS4 { var psnUser string s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser) From 632aa081b96261883e84fe0454737b0587974df4 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 16 Jul 2024 00:57:59 +1000 Subject: [PATCH 08/51] decode SMC table --- common/stringsupport/string_convert.go | 6 +- server/signserver/dsgn_resp.go | 137 ++++++++++++++++++++++++- 2 files changed, 139 insertions(+), 4 deletions(-) diff --git a/common/stringsupport/string_convert.go b/common/stringsupport/string_convert.go index 3fb4bb097..96c14c9ba 100644 --- a/common/stringsupport/string_convert.go +++ b/common/stringsupport/string_convert.go @@ -34,7 +34,11 @@ func ToNGWord(x string) []uint16 { for _, r := range []rune(x) { if r > 0xFF { t := UTF8ToSJIS(string(r)) - w = append(w, uint16(t[1])<<8|uint16(t[0])) + if len(t) > 1 { + w = append(w, uint16(t[1])<<8|uint16(t[0])) + } else { + w = append(w, uint16(t[0])) + } } else { w = append(w, uint16(r)) } diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 3ae65a512..3066dc3d6 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -140,11 +140,142 @@ func (s *Session) makeSignResponse(uid uint32) []byte { msgNGWords := []string{"test", "痴女", "てすと"} filters := byteframe.NewByteFrame() + filters.SetLE() filters.WriteNullTerminatedBytes([]byte("smc")) smc := byteframe.NewByteFrame() - //smcBytes, _ := hex.DecodeString("3D000000818100000000000029000000816A0000000000002800000081690000000000002100000081490000000000002F000000815E0000000000002B000000817B00000000000026000000819500000000000082500000310000000000000082DA0000837B0000CE00DE0082D9DE00837ADE0082D9814A837A814ACE00814A0000000082D7000083780000CD00DE0082D6DE008377DE0082D6814ACD00814A8377814A0000000082C5000083660000C300DE0082C4DE008365DE0082C4814A8365814AC300814A81A7814A81A7DE0089B3DE0089B3814A0000000082D1000083720000CB00DE0082D0DE008371DE0082D0814A8371814ACB00814A0000000082C7000083680000C400DE0082C6DE008367DE0082C6814A8367814AC400814A84B0DE0084B0814A84A5DE0084A5814A0000000082CE0000836F0000CA00DE0082CDDE00836EDE0094AADE0082CD814A836E814ACA00814A94AA814A0000000082C2DE00836400008363DE0082C2814A8363814AC200DE00C200814A82C3000082C1DE008362DE00AF00DE0082C1814A8362814AAF00814A0000000082D4000083750000CC00DE0083940000B300DE0082A4814A82A4DE008345DE00A900DE0082A3DE0082D3DE008374DE00CC00814A0000000082C0000083610000C100DE0082BFDE008360DE0082BF814A8360814AC100814A90E7814A90E7DE000000000082BE0000835F0000C000DE0082BDDE00835EDE00975BDE0082BD814A835E814AC000814A975B814A0000000082BC0000835D0000BF00DE0082BBDE00835CDE0082BB814A835C814ABF00814A8393DE008393814ADD00814ADD00DE00838ADE00D800DE00D800814A838A814A0000000082BA0000BE00DE0082B9DE00835ADE0082B9814A835A814ABE00814A835B00000000000082B8000083590000BD00DE0082B7DE008358DE0082B7814A8358814ABD00814A0000000082B6000083570000BC00DE0082B5DE008356DE0082B5814A8356814ABC00814A0000000082B4000083550000BB00DE0082B3DE008354DE0082B3814A8354814ABB00814A0000000082B2000083530000BA00DE0082B1DE008352DE0082B1814A8352814ABA00814A0000000082B0000083510000B900DE0082AFDE008350DE0082AF814A8350814AB900814A8396DE008396814A0000000082AE0000834F0000B800DE0082ADDE00834EDE0082AD814A834E814AB800814A0000000082AC0000834D0000B700DE0082ABDE00834CDE0082AB814A834C814AB700814A0000000082AA0000834B0000B600DE008395DE00834ADE0082A9DE0097CDDE008395814A834A814A82A9814A97CD814AB600814A0000000082F0000083920000A60000000000000082ED0000838F0000DC000000838E00000000000082EB0000838D0000DB00000081A000008CFB00000000000082EA0000838C0000DA0000000000000082E90000838B0000D90000000000000082E80000838A0000D80000000000000082E7000083890000D70000000000000082E6000083880000D6000000AE00000082E50000838700000000000082E4000083860000D5000000AD00000082E30000838500000000000082E2000083840000D4000000AC00000082E10000838300000000000082E0000083820000D30000000000000082DF000083810000D20000004D0045000000000082DE000083800000D10000000000000082DD0000837E0000D00000000000000082DC0000837D0000CF0000000000000082D90000837A0000CE0000000000000082D6000083770000CD0000000000000082D3000083740000CC0000000000000082D0000083710000CB0000000000000082CD0000836E0000CA00000094AA00000000000082CC0000836D0000C90000000000000082CB0000836C0000C80000000000000082CA0000836B0000C70000000000000082C90000836A0000C600000093F100000000000082C8000083690000C50000000000000082C6000083670000C400000084B0000084A500000000000082C4000083650000C300000081A7000089B300000000000082C2000083630000C200000082C1000083620000AF0000000000000082BF000083600000C100000090E700000000000082BD0000835E0000C0000000975B00000000000082BB0000835C0000BF0000000000000082B90000835A0000BE0000000000000082B7000083580000BD0000000000000082B5000083560000BC0000000000000082B3000083540000BB0000000000000082B1000083520000BA0000000000000082AF000083500000B9000000839600000000000082AD0000834E0000B80000000000000082AB0000834C0000B70000000000000082A90000834A0000B60000008395000097CD00000000000082A8000083490000B5000000AB00000082A70000834800000000000082A6000083470000B4000000AA00000082A50000834600008D4800000000000082A4000083450000B3000000A900000082A30000834400000000000082A2000083430000B2000000A800000082A100008342000000000000A7000000B1000000829F000082A00000834000008341000000000000815B0000815C0000815D0000816000002D000000817C0000B000000088EA00000000000039000000825800000000000038000000825700000000000037000000825600000000000036000000825500000000000035000000825400000000000034000000825300000000000033000000825200000000000032000000825100000000000082DB0000837C0000CE00DF0082D9DF00837ADF00837A818B82D9818BCE00818B0000000082D8000083790000CD00DF0082D6DF008377DF008377818B82D6818BCD00818B0000000082D5000083760000CC00DF0082D3DF008374DF008374818B82D3818BCC00818B0000000082D2000083730000CB00DF0082D0DF008371DF008371818B82D0818BCB00818B0000000082CF000083700000CA00DF0082CDDF00836EDF00836E818B82CD818BCA00818B94AADF0094AA814B0000000082790000829A00005A0000007A00000083A40000000000008278000082990000590000007900000083B200008454000084850000000000008277000082980000580000007800000083B4000083D4000084560000817E000084870000875D0000000000008276000082970000570000007700000083D6000084590000848A0000848B0000000000008275000082960000560000007600000083CB000083D2000087580000000000008274000082950000550000007500000083CA000081BE0000000000008273000082940000540000007400000083B1000083D100008453000084840000000000002400000053000000730000008190000081E70000827200008293000000000000827100008292000052000000720000008460000084910000000000008270000082910000510000007100000000000000826F000082900000500000007000000083AF000083CF0000845100008482000000000000826E0000828F00004F0000006F000000819B000083AD000083CD0000844F00008480000081FC0000815A000030000000824F000000000000826D0000828E00004E0000006E00000083AB000083C50000DD00000082F100008393000000000000826C0000828D00004D0000006D00000083AA0000844D0000847D000000000000826B0000828C00004C0000006C000000879800007C00000000000000826A0000828B00004B0000006B00000083A8000083C80000844B0000847B00000000000082690000828A00004A0000006A000000000000008268000082890000490000006900000083A7000087540000000000008267000082880000480000006800000083A50000844E0000847E000000000000826500008286000046000000660000000000000082660000828700004700000067000000000000008264000082850000450000006500000083A3000083C300008445000084460000847500008476000081B80000000000008263000082840000440000006400000000000000430000008283000063000000845200008483000082620000818E0000000000004200000082610000828200006200000083C0000083A000008442000084720000848C0000848E000081F30000000000002700000081660000000000004100000082600000610000008281000083BF000040000000819700008470000081F0000084400000839F00000000000022000000816800000000000025000000819300000000000000000000") - //smc.WriteBytes(smcBytes) - filters.SetLE() + smc.SetLE() + smcData := []struct { + charGroup [][]rune + }{ + {[][]rune{{'='}, {'='}}}, + {[][]rune{{')'}, {')'}}}, + {[][]rune{{'('}, {'('}}}, + {[][]rune{{'!'}, {'!'}}}, + {[][]rune{{'/'}, {'/'}}}, + {[][]rune{{'+'}, {'+'}}}, + {[][]rune{{'&'}, {'&'}}}, + {[][]rune{{'ぼ'}, {'ボ'}, {'ホ', '゙'}, {'ほ', '゙'}, {'ホ', '゙'}, {'ほ', '゛'}, {'ホ', '゛'}, {'ホ', '゛'}}}, + {[][]rune{{'べ'}, {'ベ'}, {'ヘ', '゙'}, {'へ', '゙'}, {'ヘ', '゙'}, {'へ', '゛'}, {'ヘ', '゛'}, {'ヘ', '゛'}}}, + {[][]rune{{'で'}, {'デ'}, {'テ', '゙'}, {'て', '゙'}, {'テ', '゙'}, {'て', '゛'}, {'テ', '゛'}, {'テ', '゛'}, {'〒', '゛'}, {'〒', '゙'}, {'乙', '゙'}, {'乙', '゛'}}}, + {[][]rune{{'び'}, {'ビ'}, {'ヒ', '゙'}, {'ひ', '゙'}, {'ヒ', '゙'}, {'ひ', '゛'}, {'ヒ', '゛'}, {'ヒ', '゛'}}}, + {[][]rune{{'ど'}, {'ド'}, {'ト', '゙'}, {'と', '゙'}, {'ト', '゙'}, {'と', '゛'}, {'ト', '゛'}, {'ト', '゛'}, {'┣', '゙'}, {'┣', '゛'}, {'├', '゙'}, {'├', '゛'}}}, + {[][]rune{{'ば'}, {'バ'}, {'ハ', '゙'}, {'は', '゙'}, {'ハ', '゙'}, {'八', '゙'}, {'は', '゛'}, {'ハ', '゛'}, {'ハ', '゛'}, {'八', '゛'}}}, + {[][]rune{{'つ', '゙'}, {'ヅ'}, {'ツ', '゙'}, {'つ', '゛'}, {'ツ', '゛'}, {'ツ', '゙'}, {'ツ', '゛'}, {'づ'}, {'っ', '゙'}, {'ッ', '゙'}, {'ッ', '゙'}, {'っ', '゛'}, {'ッ', '゛'}, {'ッ', '゛'}}}, + {[][]rune{{'ぶ'}, {'ブ'}, {'フ', '゙'}, {'ヴ'}, {'ウ', '゙'}, {'う', '゛'}, {'う', '゙'}, {'ウ', '゙'}, {'ゥ', '゙'}, {'ぅ', '゙'}, {'ふ', '゙'}, {'フ', '゙'}, {'フ', '゛'}}}, + {[][]rune{{'ぢ'}, {'ヂ'}, {'チ', '゙'}, {'ち', '゙'}, {'チ', '゙'}, {'ち', '゛'}, {'チ', '゛'}, {'チ', '゛'}, {'千', '゛'}, {'千', '゙'}}}, + {[][]rune{{'だ'}, {'ダ'}, {'タ', '゙'}, {'た', '゙'}, {'タ', '゙'}, {'夕', '゙'}, {'た', '゛'}, {'タ', '゛'}, {'タ', '゛'}, {'夕', '゛'}}}, + {[][]rune{{'ぞ'}, {'ゾ'}, {'ソ', '゙'}, {'そ', '゙'}, {'ソ', '゙'}, {'そ', '゛'}, {'ソ', '゛'}, {'ソ', '゛'}, {'ン', '゙'}, {'ン', '゛'}, {'ン', '゛'}, {'ン', '゙'}, {'リ', '゙'}, {'リ', '゙'}, {'リ', '゛'}, {'リ', '゛'}}}, + {[][]rune{{'ぜ'}, {'セ', '゙'}, {'せ', '゙'}, {'セ', '゙'}, {'せ', '゛'}, {'セ', '゛'}, {'セ', '゛'}, {'ゼ'}}}, + {[][]rune{{'ず'}, {'ズ'}, {'ス', '゙'}, {'す', '゙'}, {'ス', '゙'}, {'す', '゛'}, {'ス', '゛'}, {'ス', '゛'}}}, + {[][]rune{{'じ'}, {'ジ'}, {'シ', '゙'}, {'し', '゙'}, {'シ', '゙'}, {'し', '゛'}, {'シ', '゛'}, {'シ', '゛'}}}, + {[][]rune{{'ざ'}, {'ザ'}, {'サ', '゙'}, {'さ', '゙'}, {'サ', '゙'}, {'さ', '゛'}, {'サ', '゛'}, {'サ', '゛'}}}, + {[][]rune{{'ご'}, {'ゴ'}, {'コ', '゙'}, {'こ', '゙'}, {'コ', '゙'}, {'こ', '゛'}, {'コ', '゛'}, {'コ', '゛'}}}, + {[][]rune{{'げ'}, {'ゲ'}, {'ケ', '゙'}, {'け', '゙'}, {'ケ', '゙'}, {'け', '゛'}, {'ケ', '゛'}, {'ケ', '゛'}, {'ヶ', '゙'}, {'ヶ', '゛'}}}, + {[][]rune{{'ぐ'}, {'グ'}, {'ク', '゙'}, {'く', '゙'}, {'ク', '゙'}, {'く', '゛'}, {'ク', '゛'}, {'ク', '゛'}}}, + {[][]rune{{'ぎ'}, {'ギ'}, {'キ', '゙'}, {'き', '゙'}, {'キ', '゙'}, {'き', '゛'}, {'キ', '゛'}, {'キ', '゛'}}}, + {[][]rune{{'が'}, {'ガ'}, {'カ', '゙'}, {'ヵ', '゙'}, {'カ', '゙'}, {'か', '゙'}, {'力', '゙'}, {'ヵ', '゛'}, {'カ', '゛'}, {'か', '゛'}, {'力', '゛'}, {'カ', '゛'}}}, + {[][]rune{{'を'}, {'ヲ'}, {'ヲ'}}}, + {[][]rune{{'わ'}, {'ワ'}, {'ワ'}, {'ヮ'}}}, + {[][]rune{{'ろ'}, {'ロ'}, {'ロ'}, {'□'}, {'口'}}}, + {[][]rune{{'れ'}, {'レ'}, {'レ'}}}, + {[][]rune{{'る'}, {'ル'}, {'ル'}}}, + {[][]rune{{'り'}, {'リ'}, {'リ'}}}, + {[][]rune{{'ら'}, {'ラ'}, {'ラ'}}}, + {[][]rune{{'よ'}, {'ヨ'}, {'ヨ'}, {'ョ'}, {'ょ'}, {'ョ'}}}, + {[][]rune{{'ゆ'}, {'ユ'}, {'ユ'}, {'ュ'}, {'ゅ'}, {'ュ'}}}, + {[][]rune{{'や'}, {'ヤ'}, {'ヤ'}, {'ャ'}, {'ゃ'}, {'ャ'}}}, + {[][]rune{{'も'}, {'モ'}, {'モ'}}}, + {[][]rune{{'め'}, {'メ'}, {'メ'}, {'M', 'E'}}}, + {[][]rune{{'む'}, {'ム'}, {'ム'}}}, + {[][]rune{{'み'}, {'ミ'}, {'ミ'}}}, + {[][]rune{{'ま'}, {'マ'}, {'マ'}}}, + {[][]rune{{'ほ'}, {'ホ'}, {'ホ'}}}, + {[][]rune{{'へ'}, {'ヘ'}, {'ヘ'}}}, + {[][]rune{{'ふ'}, {'フ'}, {'フ'}}}, + {[][]rune{{'ひ'}, {'ヒ'}, {'ヒ'}}}, + {[][]rune{{'は'}, {'ハ'}, {'ハ'}, {'八'}}}, + {[][]rune{{'の'}, {'ノ'}, {'ノ'}}}, + {[][]rune{{'ね'}, {'ネ'}, {'ネ'}}}, + {[][]rune{{'ぬ'}, {'ヌ'}, {'ヌ'}}}, + {[][]rune{{'に'}, {'ニ'}, {'ニ'}, {'二'}}}, + {[][]rune{{'な'}, {'ナ'}, {'ナ'}}}, + {[][]rune{{'と'}, {'ト'}, {'ト'}, {'┣'}, {'├'}}}, + {[][]rune{{'て'}, {'テ'}, {'テ'}, {'〒'}, {'乙'}}}, + {[][]rune{{'つ'}, {'ツ'}, {'ツ'}, {'っ'}, {'ッ'}, {'ッ'}}}, + {[][]rune{{'ち'}, {'チ'}, {'チ'}, {'千'}}}, + {[][]rune{{'た'}, {'タ'}, {'タ'}, {'夕'}}}, + {[][]rune{{'そ'}, {'ソ'}, {'ソ'}}}, + {[][]rune{{'せ'}, {'セ'}, {'セ'}}}, + {[][]rune{{'す'}, {'ス'}, {'ス'}}}, + {[][]rune{{'し'}, {'シ'}, {'シ'}}}, + {[][]rune{{'さ'}, {'サ'}, {'サ'}}}, + {[][]rune{{'こ'}, {'コ'}, {'コ'}}}, + {[][]rune{{'け'}, {'ケ'}, {'ケ'}, {'ヶ'}}}, + {[][]rune{{'く'}, {'ク'}, {'ク'}}}, + {[][]rune{{'き'}, {'キ'}, {'キ'}}}, + {[][]rune{{'か'}, {'カ'}, {'カ'}, {'ヵ'}, {'力'}}}, + {[][]rune{{'お'}, {'オ'}, {'オ'}, {'ォ'}, {'ぉ'}, {'ォ'}}}, + {[][]rune{{'え'}, {'エ'}, {'エ'}, {'ェ'}, {'ぇ'}, {'ェ'}, {'工'}}}, + {[][]rune{{'う'}, {'ウ'}, {'ウ'}, {'ゥ'}, {'ぅ'}, {'ゥ'}}}, + {[][]rune{{'い'}, {'イ'}, {'イ'}, {'ィ'}, {'ぃ'}, {'ィ'}}}, + {[][]rune{{'あ'}, {'ア'}, {'ァ'}, {'ア'}, {'ぁ'}, {'ァ'}}}, + {[][]rune{{'ー'}, {'―'}, {'‐'}, {'-'}, {'-'}, {'ー'}, {'一'}}}, + {[][]rune{{'9'}, {'9'}}}, + {[][]rune{{'8'}, {'8'}}}, + {[][]rune{{'7'}, {'7'}}}, + {[][]rune{{'6'}, {'6'}}}, + {[][]rune{{'5'}, {'5'}}}, + {[][]rune{{'4'}, {'4'}}}, + {[][]rune{{'3'}, {'3'}}}, + {[][]rune{{'2'}, {'2'}}}, + {[][]rune{{'1'}, {'1'}}}, + {[][]rune{{'ぽ'}, {'ポ'}, {'ホ', '゚'}, {'ほ', '゚'}, {'ホ', '゚'}, {'ホ', '°'}, {'ほ', '°'}, {'ホ', '°'}}}, + {[][]rune{{'ぺ'}, {'ペ'}, {'ヘ', '゚'}, {'へ', '゚'}, {'ヘ', '゚'}, {'ヘ', '°'}, {'へ', '°'}, {'ヘ', '°'}}}, + {[][]rune{{'ぷ'}, {'プ'}, {'フ', '゚'}, {'ふ', '゚'}, {'フ', '゚'}, {'フ', '°'}, {'ふ', '°'}, {'フ', '°'}}}, + {[][]rune{{'ぴ'}, {'ピ'}, {'ヒ', '゚'}, {'ひ', '゚'}, {'ヒ', '゚'}, {'ヒ', '°'}, {'ひ', '°'}, {'ヒ', '°'}}}, + {[][]rune{{'ぱ'}, {'パ'}, {'ハ', '゚'}, {'は', '゚'}, {'ハ', '゚'}, {'ハ', '°'}, {'は', '°'}, {'ハ', '°'}, {'八', '゚'}, {'八', '゜'}}}, + {[][]rune{{'z'}, {'z'}, {'Z'}, {'Z'}, {'Ζ'}}}, + {[][]rune{{'y'}, {'y'}, {'Y'}, {'Y'}, {'Υ'}, {'У'}, {'у'}}}, + {[][]rune{{'x'}, {'x'}, {'X'}, {'X'}, {'Χ'}, {'χ'}, {'Х'}, {'×'}, {'х'}}}, + {[][]rune{{'w'}, {'w'}, {'W'}, {'W'}, {'ω'}, {'Ш'}, {'ш'}, {'щ'}}}, + {[][]rune{{'v'}, {'v'}, {'V'}, {'V'}, {'ν'}, {'υ'}}}, + {[][]rune{{'u'}, {'u'}, {'U'}, {'U'}, {'μ'}, {'∪'}}}, + {[][]rune{{'t'}, {'t'}, {'T'}, {'T'}, {'Τ'}, {'τ'}, {'Т'}, {'т'}}}, + {[][]rune{{'s'}, {'s'}, {'S'}, {'S'}, {'∫'}, {'$'}, {'$'}}}, + {[][]rune{{'r'}, {'r'}, {'R'}, {'R'}, {'Я'}, {'я'}}}, + {[][]rune{{'q'}, {'q'}, {'Q'}, {'Q'}}}, + {[][]rune{{'p'}, {'p'}, {'P'}, {'P'}, {'Ρ'}, {'ρ'}, {'Р'}, {'р'}}}, + {[][]rune{{'o'}, {'o'}, {'O'}, {'O'}, {'○'}, {'Ο'}, {'ο'}, {'О'}, {'о'}, {'◯'}, {'〇'}, {'0'}, {'0'}}}, + {[][]rune{{'n'}, {'n'}, {'N'}, {'N'}, {'Ν'}, {'η'}, {'ン'}, {'ん'}, {'ン'}}}, + {[][]rune{{'m'}, {'m'}, {'M'}, {'M'}, {'Μ'}, {'М'}, {'м'}}}, + {[][]rune{{'l'}, {'l'}, {'L'}, {'L'}, {'|'}}}, + {[][]rune{{'k'}, {'k'}, {'K'}, {'K'}, {'Κ'}, {'κ'}, {'К'}, {'к'}}}, + {[][]rune{{'j'}, {'j'}, {'J'}, {'J'}}}, + {[][]rune{{'i'}, {'i'}, {'I'}, {'I'}, {'Ι'}}}, + {[][]rune{{'h'}, {'h'}, {'H'}, {'H'}, {'Η'}, {'Н'}, {'н'}}}, + {[][]rune{{'f'}, {'f'}, {'F'}, {'F'}}}, + {[][]rune{{'g'}, {'g'}, {'G'}, {'G'}}}, + {[][]rune{{'e'}, {'e'}, {'E'}, {'E'}, {'Ε'}, {'ε'}, {'Е'}, {'Ё'}, {'е'}, {'ё'}, {'∈'}}}, + {[][]rune{{'d'}, {'d'}, {'D'}, {'D'}}}, + {[][]rune{{'c'}, {'c'}, {'C'}, {'С'}, {'с'}, {'C'}, {'℃'}}}, + {[][]rune{{'b'}, {'B'}, {'b'}, {'B'}, {'β'}, {'Β'}, {'В'}, {'в'}, {'ъ'}, {'ь'}, {'♭'}}}, + {[][]rune{{'\''}, {'’'}}}, + {[][]rune{{'a'}, {'A'}, {'a'}, {'A'}, {'α'}, {'@'}, {'@'}, {'а'}, {'Å'}, {'А'}, {'Α'}}}, + {[][]rune{{'"'}, {'”'}}}, + {[][]rune{{'%'}, {'%'}}}, + } + for _, smcGroup := range smcData { + for _, smcPair := range smcGroup.charGroup { + smc.WriteUint16(stringsupport.ToNGWord(string(smcPair[0]))[0]) + if len(smcPair) > 1 { + smc.WriteUint16(stringsupport.ToNGWord(string(smcPair[1]))[0]) + } else { + smc.WriteUint16(0) + } + } + smc.WriteUint32(0) + } + filters.WriteUint32(uint32(len(smc.Data()))) filters.WriteBytes(smc.Data()) From ca38f5671dbc4943d8449ccdca4af18ef9338be3 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 16 Jul 2024 01:12:02 +1000 Subject: [PATCH 09/51] ascii working, sjis not working --- server/signserver/dsgn_resp.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 3066dc3d6..06e58994a 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -287,7 +287,16 @@ func (s *Session) makeSignResponse(uid uint32) []byte { nam.WriteUint32(uint32(len(parts))) for _, part := range parts { nam.WriteUint16(part) - nam.WriteInt16(-1) // TODO: figure out how this value relates to corresponding SMC part + var i int16 + j := int16(-1) + for _, smcGroup := range smcData { + if rune(part) == smcGroup.charGroup[0][0] { + j = i + break + } + i += int16(len(smcGroup.charGroup) + 1) + } + nam.WriteInt16(j) } nam.WriteUint16(0) nam.WriteInt16(-1) From 5de6570510ca3810a9787c7ab89f7b81ea5acbf0 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 16 Jul 2024 01:13:17 +1000 Subject: [PATCH 10/51] ascii working, sjis not working --- server/signserver/dsgn_resp.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 06e58994a..84bb0fa56 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -312,7 +312,16 @@ func (s *Session) makeSignResponse(uid uint32) []byte { msg.WriteUint32(uint32(len(parts))) for _, part := range parts { msg.WriteUint16(part) - msg.WriteInt16(-1) + var i int16 + j := int16(-1) + for _, smcGroup := range smcData { + if rune(part) == smcGroup.charGroup[0][0] { + j = i + break + } + i += int16(len(smcGroup.charGroup) + 1) + } + msg.WriteInt16(j) } msg.WriteUint16(0) msg.WriteInt16(-1) From aa5d95e7c55c84661605f0a3e6f4bcb4c4f545f7 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 22 Jul 2024 23:44:53 +1000 Subject: [PATCH 11/51] fix sjis ngwords --- server/signserver/dsgn_resp.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 84bb0fa56..3d102d52c 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -136,8 +136,8 @@ func (s *Session) makeSignResponse(uid uint32) []byte { bf.WriteUint32(s.server.getLastCID(uid)) bf.WriteUint32(s.server.getUserRights(uid)) - namNGWords := []string{"test", "痴女", "てすと"} - msgNGWords := []string{"test", "痴女", "てすと"} + namNGWords := []string{} + msgNGWords := []string{} filters := byteframe.NewByteFrame() filters.SetLE() @@ -290,7 +290,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte { var i int16 j := int16(-1) for _, smcGroup := range smcData { - if rune(part) == smcGroup.charGroup[0][0] { + if rune(part) == rune(stringsupport.ToNGWord(string(smcGroup.charGroup[0][0]))[0]) { j = i break } @@ -315,7 +315,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte { var i int16 j := int16(-1) for _, smcGroup := range smcData { - if rune(part) == smcGroup.charGroup[0][0] { + if rune(part) == rune(stringsupport.ToNGWord(string(smcGroup.charGroup[0][0]))[0]) { j = i break } From b755de269e388bbda9b0b10f0c505b02c6d0d657 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 23 Jul 2024 00:11:54 +1000 Subject: [PATCH 12/51] add retro stamp rewards --- server/channelserver/handlers.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index d301ad6c9..ed9308904 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -1051,6 +1051,32 @@ func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfStampcardStamp) + + rewards := []struct { + HR uint16 + Item1 uint16 + Quantity1 uint16 + Item2 uint16 + Quantity2 uint16 + }{ + {0, 6164, 1, 6164, 2}, + {50, 6164, 2, 6164, 3}, + {100, 6164, 3, 5392, 1}, + {300, 5392, 1, 5392, 3}, + {999, 5392, 1, 5392, 4}, + } + if _config.ErupeConfig.RealClientMode <= _config.Z1 { + for _, reward := range rewards { + if pkt.HR >= reward.HR { + pkt.Item1 = reward.Item1 + pkt.Quantity1 = reward.Quantity1 + pkt.Item2 = reward.Item2 + pkt.Quantity2 = reward.Quantity2 + break + } + } + } + bf := byteframe.NewByteFrame() bf.WriteUint16(pkt.HR) var stamps uint16 From d29b7d00fc8c90ca9cd62bfad2ba8e9634bb837e Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 23 Jul 2024 21:26:43 +1000 Subject: [PATCH 13/51] fix retro stamp rewards --- server/channelserver/handlers.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index ed9308904..814518576 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -1072,7 +1072,6 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { pkt.Quantity1 = reward.Quantity1 pkt.Item2 = reward.Item2 pkt.Quantity2 = reward.Quantity2 - break } } } From 717d7853422ae331a7dc94ba4f16e35c29b4e614 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 23 Jul 2024 21:56:01 +1000 Subject: [PATCH 14/51] fix possible warehouse error --- server/channelserver/handlers_house.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 5e47d0436..c91660b54 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -367,13 +367,17 @@ func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfOperateWarehouse) +func initializeWarehouse(s *Session) { var t int err := s.server.db.QueryRow("SELECT character_id FROM warehouse WHERE character_id=$1", s.charID).Scan(&t) if err != nil { s.server.db.Exec("INSERT INTO warehouse (character_id) VALUES ($1)", s.charID) } +} + +func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfOperateWarehouse) + initializeWarehouse(s) bf := byteframe.NewByteFrame() bf.WriteUint8(pkt.Operation) switch pkt.Operation { @@ -446,6 +450,7 @@ func addWarehouseEquipment(s *Session, equipment mhfitem.MHFEquipment) { } func warehouseGetItems(s *Session, index uint8) []mhfitem.MHFItemStack { + initializeWarehouse(s) var data []byte var items []mhfitem.MHFItemStack s.server.db.QueryRow(fmt.Sprintf(`SELECT item%d FROM warehouse WHERE character_id=$1`, index), s.charID).Scan(&data) From f545576fc9ad0b9480d1b396f567ede1767c8038 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 24 Jul 2024 23:33:04 +1000 Subject: [PATCH 15/51] merge changes --- server/channelserver/handlers.go | 42 ++------------------------------ 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 52ce7ed7c..cf182dfb5 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -1049,42 +1049,6 @@ func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } -func getStampcardReward(secondStamp bool, HR uint16, GR uint16) mhfitem.MHFItemStack { - if GR > 0 { - if secondStamp { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 4} - } else { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 1} - } - } else { - if HR >= 300 { - if secondStamp { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 3} - } else { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 1} - } - } else if HR >= 100 { - if secondStamp { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 5392}, Quantity: 1} - } else { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 3} - } - } else if HR >= 50 { - if secondStamp { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 3} - } else { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 2} - } - } else { - if secondStamp { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 2} - } else { - return mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 6164}, Quantity: 1} - } - } - } -} - func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfStampcardStamp) @@ -1127,21 +1091,19 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { rewardTier = 2 if _config.ErupeConfig.RealClientMode < _config.Z2 { rewardUnk = 10 - reward = getStampcardReward(true, pkt.HR, pkt.GR) } else { rewardUnk = pkt.Reward2 - reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item2}, Quantity: pkt.Quantity2} } + reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item2}, Quantity: pkt.Quantity2} addWarehouseItem(s, reward) } else if stamps/15 > (stamps-pkt.Stamps)/15 { rewardTier = 1 if _config.ErupeConfig.RealClientMode < _config.Z2 { rewardUnk = 10 - reward = getStampcardReward(false, pkt.HR, pkt.GR) } else { rewardUnk = pkt.Reward1 - reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item1}, Quantity: pkt.Quantity1} } + reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item1}, Quantity: pkt.Quantity1} addWarehouseItem(s, reward) } From 459f382dd75ff481a81016aa983acc9349975448 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 24 Jul 2024 23:41:05 +1000 Subject: [PATCH 16/51] simplify --- network/mhfpacket/msg_mhf_stampcard_stamp.go | 3 +++ server/channelserver/handlers.go | 12 ++---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/network/mhfpacket/msg_mhf_stampcard_stamp.go b/network/mhfpacket/msg_mhf_stampcard_stamp.go index 521e42221..281134c9d 100644 --- a/network/mhfpacket/msg_mhf_stampcard_stamp.go +++ b/network/mhfpacket/msg_mhf_stampcard_stamp.go @@ -44,6 +44,9 @@ func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli m.Item2 = uint16(bf.ReadUint32()) m.Quantity1 = uint16(bf.ReadUint32()) m.Quantity2 = uint16(bf.ReadUint32()) + } else { + m.Reward1 = 10 + m.Reward2 = 10 } return nil } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index cf182dfb5..4f8e89e7c 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -1089,20 +1089,12 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { if stamps/30 > (stamps-pkt.Stamps)/30 { rewardTier = 2 - if _config.ErupeConfig.RealClientMode < _config.Z2 { - rewardUnk = 10 - } else { - rewardUnk = pkt.Reward2 - } + rewardUnk = pkt.Reward2 reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item2}, Quantity: pkt.Quantity2} addWarehouseItem(s, reward) } else if stamps/15 > (stamps-pkt.Stamps)/15 { rewardTier = 1 - if _config.ErupeConfig.RealClientMode < _config.Z2 { - rewardUnk = 10 - } else { - rewardUnk = pkt.Reward1 - } + rewardUnk = pkt.Reward1 reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item1}, Quantity: pkt.Quantity1} addWarehouseItem(s, reward) } From 1ab6940b01142bdef8fad8a27425945ce1999735 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 8 Aug 2024 22:14:35 +1000 Subject: [PATCH 17/51] add extra fields to Distributions --- schemas/patch-schema/23-rework-distributions-2.sql | 6 ++++++ server/channelserver/handlers_distitem.go | 13 +++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 schemas/patch-schema/23-rework-distributions-2.sql diff --git a/schemas/patch-schema/23-rework-distributions-2.sql b/schemas/patch-schema/23-rework-distributions-2.sql new file mode 100644 index 000000000..da6250eb0 --- /dev/null +++ b/schemas/patch-schema/23-rework-distributions-2.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE distribution ADD COLUMN rights INTEGER; +ALTER TABLE distribution ADD COLUMN selection BOOLEAN; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index 078598719..3e2417d34 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -13,6 +13,7 @@ import ( type Distribution struct { ID uint32 `db:"id"` Deadline time.Time `db:"deadline"` + Rights uint32 `db:"rights"` TimesAcceptable uint16 `db:"times_acceptable"` TimesAccepted uint16 `db:"times_accepted"` MinHR int16 `db:"min_hr"` @@ -23,7 +24,7 @@ type Distribution struct { MaxGR int16 `db:"max_gr"` EventName string `db:"event_name"` Description string `db:"description"` - Data []byte `db:"data"` + Selection bool `db:"selection"` } func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { @@ -32,7 +33,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { var itemDists []Distribution bf := byteframe.NewByteFrame() rows, err := s.server.db.Queryx(` - SELECT d.id, event_name, description, times_acceptable, + SELECT d.id, event_name, description, COALESCE(rights, 0) AS rights, COALESCE(selection, false) AS selection, 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, @@ -60,7 +61,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { for _, dist := range itemDists { bf.WriteUint32(dist.ID) bf.WriteUint32(uint32(dist.Deadline.Unix())) - bf.WriteUint32(0) // Unk + bf.WriteUint32(dist.Rights) bf.WriteUint16(dist.TimesAcceptable) bf.WriteUint16(dist.TimesAccepted) if _config.ErupeConfig.RealClientMode >= _config.G9 { @@ -79,7 +80,11 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(0) // Unk } if _config.ErupeConfig.RealClientMode >= _config.G8 { - bf.WriteUint8(0) // Unk + if dist.Selection { + bf.WriteUint8(2) // Selection + } else { + bf.WriteUint8(0) + } } if _config.ErupeConfig.RealClientMode >= _config.G7 { bf.WriteUint16(0) // Unk From 04008fceb8e75346e4d89815e785f289689984dc Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 8 Aug 2024 22:16:09 +1000 Subject: [PATCH 18/51] rewrite ReadMercenaryW handler --- server/channelserver/handlers_mercenary.go | 102 ++++++++++----------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index 19c358c52..8f1f4f018 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -157,60 +157,60 @@ func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfReadMercenaryW) - if pkt.Op > 0 { - bf := byteframe.NewByteFrame() - var pactID uint32 - var name string - var cid uint32 + bf := byteframe.NewByteFrame() - s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID) - if pactID > 0 { - s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid) - bf.WriteUint8(1) // numLends - bf.WriteUint32(pactID) - bf.WriteUint32(cid) - bf.WriteBool(false) // ? - bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix())) - bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix())) - bf.WriteBytes(stringsupport.PaddedString(name, 18, true)) - } else { - bf.WriteUint8(0) - } - - if pkt.Op < 2 { - var loans uint8 - temp := byteframe.NewByteFrame() - rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID) - for rows.Next() { - loans++ - rows.Scan(&name, &cid, &pactID) - temp.WriteUint32(pactID) - temp.WriteUint32(cid) - temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix())) - temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix())) - temp.WriteBytes(stringsupport.PaddedString(name, 18, true)) - } - bf.WriteUint8(loans) - bf.WriteBytes(temp.Data()) - } - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - return - } - var data []byte - var gcp uint32 - s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data) - s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp) - - resp := byteframe.NewByteFrame() - resp.WriteUint16(0) - if len(data) == 0 { - resp.WriteBool(false) + var pactID, cid uint32 + var name string + s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID) + if pactID > 0 { + s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid) + bf.WriteUint8(1) // numLends + bf.WriteUint32(pactID) + bf.WriteUint32(cid) + bf.WriteBool(false) // ? + bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix())) + bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix())) + bf.WriteBytes(stringsupport.PaddedString(name, 18, true)) } else { - resp.WriteBool(true) - resp.WriteBytes(data) + bf.WriteUint8(0) } - resp.WriteUint32(gcp) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + + var loans uint8 + temp := byteframe.NewByteFrame() + if pkt.Op < 2 { + rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID) + for rows.Next() { + err := rows.Scan(&name, &cid, &pactID) + if err != nil { + continue + } + loans++ + temp.WriteUint32(pactID) + temp.WriteUint32(cid) + temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix())) + temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix())) + temp.WriteBytes(stringsupport.PaddedString(name, 18, true)) + } + } + bf.WriteUint8(loans) + bf.WriteBytes(temp.Data()) + + if pkt.Op < 1 { + var data []byte + var gcp uint32 + s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data) + s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp) + + if len(data) == 0 { + bf.WriteBool(false) + } else { + bf.WriteBool(true) + bf.WriteBytes(data) + } + bf.WriteUint32(gcp) + } + + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) { From 7d760bd3b46f362784bcbbbbed5a553c3d018a1c Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 8 Aug 2024 22:16:39 +1000 Subject: [PATCH 19/51] fix EntranceServer clan member list limits --- server/entranceserver/make_resp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index ec0281abc..57b04d0e1 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -86,7 +86,7 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { } } bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) - bf.WriteUint32(60) + bf.WriteUint32(uint32(s.erupeConfig.GameplayOptions.ClanMemberLimits[len(s.erupeConfig.GameplayOptions.ClanMemberLimits)-1][1])) return bf.Data() } From 8a55c5ff8921fcc31214d7c06a3ab2df977d47fc Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 27 Sep 2024 01:12:16 +1000 Subject: [PATCH 20/51] fix inflated festa rewards --- server/channelserver/handlers_festa.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index eecd268e3..d5cba4d90 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -292,7 +292,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { } else { bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MaximumFP) } - bf.WriteUint16(500) + bf.WriteUint16(100) // Reward multiplier (%) var temp uint32 bf.WriteUint16(4) From 2c5896814f0592b2042b57816bb7cdcabc5b8c0b Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 1 Oct 2024 23:01:15 +1000 Subject: [PATCH 21/51] update ico resources --- erupe.ico | Bin 99678 -> 90022 bytes rsrc_windows_amd64.syso | Bin 100166 -> 90452 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/erupe.ico b/erupe.ico index e1358c741f3bffeca6e1550e42f912ec9af94ce8..0ee67fff713cb0b0e1730027122326883ba8b197 100644 GIT binary patch literal 90022 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 literal 99678 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 M^`bCJRvqX60b75xyZ`_I literal 100166 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%0D0f_5|;(%60D2WjFFf{SwY@|7uxu z?bNl)`aW|fT6M?!rL?AxgO2}17XOE4){xYY3-~RnJ^`Ae*rn}_EFZa0YirlU4nIur{^tPq<;5lYSqIRuXrX-?xpLtY%@+sPOYS4yysFdcH*S# zSf6%5NJ#hs{KBr*azOB>w@xUy$(;xGz9hVV;S*u0&CLS&5q96dFuC{0!B-Y9Tr?N! zw1yu(cxc&#In(9(9$uE#U24mh`$nJHn6G81{|^1~*BH;KBtLob=(d?j-OMZds(MIo zNqZ^W9Pu|^2yU?s9|kS!PxN=-=GXGLTa*uzq)nr)TQ$#fel>SR4Z8X ziEsdPJ$}N3J(zD@jQq)C&RAbpPuF?<$pepiOKpX2x3a#{+necFQ&ZCx^I9}Fk%{|s z-90?AFDgqn^_SVfBmEDM-N`>7aV}R|N5>J*6=~e{2pj=q2M@qQU<+jNTtHq&PcQ6c zQAq>V5->;q(2AY=4{G5#Ev*;A^XxEA{`_&{Cl-wv z^gNJn>e?_bO6%(iYEY-PlXGy%Td98i>_D3N9e`U=$*-g(5{d=4e)B&tbCfgr4 zVdSNhXV2ClPjVmtlz*)HSZ-!(Rkv9B80&juYtvMbwJFjHazU_rnh*H|$fjT7IqRz% zN^%jiuI&5RS85w?lIZpleN&@C_(zh}4}1hsU<=p`>_9CjgMGYn_wfAGM*hnEM~>0x^(NhsqehW);+}6zyE3RbbG8{Jo2XS%^KWe2VR3B`0ZS*Tdq9~KQ`d% z*6KZ%PNu^*l;BxVA+Bct(rq;PcdWxL+Wm5GujK51%IEt3U$TFs^`*~x?%cU96UL02IbhZV zxgM9-7mi;xSIx%C`W$RJ826Fv1}lJSK6RiEV#ub4S!t`>Y>!GERxBP~vT8kcl;v3H!XXvcc(!B@AvKQAL0 zQc+%>fb*{a&5hmyv%qjueZ$d%W>37x&))xS@!RwI=ElVv<<9e{j>8RQ z<9s==0o0f854x^gu((&(A^pw@Z=Dh}dGh1;KqMf&YT);|R*v>;liUTq zlBU|js>&*FoTu^(2PuHsfF!UQ^aM1g(gAZ}eKu~{dM&f41nWX7Nd9JXE1MFmb7`jX zz#6pXD_5_DjT<*krFWk`Qdpz@r{qT(oZJ+)2Q*%phPoN;z6rIr~`!jv1cRV-`ia{BC z#}mwH<9OlLty?!NT)1e{`0?Xc4j3?C+2A2VcaER(^VtQ9 zmtI=3e1(+AsngoxSFc%W>)n20Wowt0{kF7;?B6vwvhK{e3sFB$pHVh`;-n&c97}BO zoH--|C)@YrC)vs7djPWOQGnJmUnM^Vav-}lp3Rcn;{c6iY2KOUF1y3m_j&Q+#lROY zp8te$8brr9PviT!;6C8CYLtO1*2#Icv^106Y2)Gkhges6e)cbOr1u^?>N#=JpsOx`{d`SF7eS;Opbqk$42A4hFlUTa9cp5!5a+!at<^c%8qN`rhGwe#e+X#YK^g6-TvURJ~B z>!MzlVGiF8`SO7e_=I}J!!ozBP&f=j!sTj_4IE) zuyb(ysHAc;2y2`Q@O=noZf*`Ns;#Z9Jy8y?Fh>%2SyIv@HMg*a)}y<)xi=u>^e$LW zOy6ojKflV--M7Nh-LKrl#rwUeihlZ$Q|Dr3WMuE+dkRgcqo0uW+g3^ts)7-KY*vtW zt`6Xs%7f0)wP_$4&_=%_{qw;Ga14;Ir*a~_({D(AD%b7c1uzHXjJi7^~UNe5?7z1uUHMcjgXR&wSrC8z^Av^ zJ2{u4ZRe5hxwX_}3x09;nFrQoctpfAkMKC=5)$2F6! z%0J0LHcGmqynnHQ)_poha%}^#fOIGi$PQ>r28+Q^K=P7LrShUWMaMe;)fY_=1wUDW z@76r9v9T?3a&}>w+Bz?7y#gx)c@D!mWev>_(`U?T8b5I|n>O>8mR);~G+9_!CE_=S zVIMA#xe31QlYy~G5{>oR%1`S!s0`>^IUIDHnpfPSYUW&s{=nO^_wO0j&}5aCzEg%x zS))86Q4htCx9;F~bpXel{z;Z$fNWqUkO1VD$Y(hK^5NwB4uh>=H0TGY&XE2K0rg+r z!H>n7nVDy*YTy1y-=MH_bQ0Jx^2>kxBKxDRzJArxyBAt5Ol z9v;5O%)%m>Y#;Fd$o@GyXd6=bP`O|ob(NZiW|E_ma|_mTH7O`52X5TBN#WSBWBa0` zqbH*rNN0bfe<}ki7xL%i&&bXd0B6&?vA-WUg9<=)r38KfV*&M3^8s!AnwsiTTYHCc zC%=#u^wAi;=fu!PWBqYyjkx4h^HZlzS>QdA0+dmU>aAP%#KpzM?CfkeSXo&bV7+b~ zy-Pq?V|}SxNHq2B1naBY%G`FJ@@b0z$wlp&Iw1RQ0S|V*=LtT2mIu;-ELaEDgY%H< zjf(pem2%rx_f<=A9cC@+6^g-nCR%~xNn$;hsWgAt5+Y~uw$>jjIL!7zQ4un zN%nwn!TT5{=2ndw8k*Uj~Te7bvif624GgJUkfxv>-PAdOqFaN*9C8@6d) z(=vZ;h zNq*8f_4}x8pnAUm=!19Qk2aE<+LW(1DyK9ciShihqsNb{lD)&1x1j!4aXQ0$6@0ww z5QJwTn|JR&boGj=L58Kfe+%ZaFrJTKJ~4^VS&6{6r{oqj*!qR#S^I>(!~0(?)?R_^ zrl}K?*0yGsl?~Xk)!RQ#|7CIB$~7DBD<~=&Xld(JELypTP5X5tn>1@Jo3~;w)3R`( zZvfSvId@*u)Wl>s@<;tut_(QYslJZ_dqE^<1l;xpJC9qbXg%40kYuI$PIaQKjnKDW zT+xp)Ciw{!HFaOue%&kBBiRDxa2oY)8^qj@k@Ye#F|RbYv|^UFcFZL(9P6Y@KEJ=# z+U94Kl=G5amEi+|kC?feKa;s>$kwjA$Y#vn!DcMj!dt&(U%is@%_z)0CE#5-W?^Z~ zbaZdCeTPo4X>->L>>%b*3g6D&Asg>>A4Xn#1Fj9^Y<@D%X@O$!^+vXC3M@f>>&0q> zWbY;*1dw0+vcdngI5;{Lpbc+?{FP+C6q2vP`zKsoXhy%i!3BEP(lbDw^**Q>n&oAp z?I-`n`Smo`GS?`_j^z$m9q>K8|!x@{>JK|1}gm0VHoC z_y}lg2G7BL&{qHC8wf#X3nAG6*+U5CIO9>?uVM2oD1$0#B^67o1FoWe3iVkaBM;@! zfW9oB`fpZlKA+B95cQT&R87P>+eS|Q-0~_G_TmMTxNXPQ9+741k1Dd8CI9Xq8s;hU(C>SXZ+g<$l!tE$ z*7(NUjz- zqsDAKf?Iq-qv{MzE%S}d%nQ(8s)p><=-W|yoCQdS7C08iw=b6-K74rZ=`&~j_a8d; z9{r6Jk-x{=RN1{*P! z3Bdcc@iJ=K@lUgJ%OK}Rj9IF!-F=%h^o$yfjZK)Xy?rCj^QaGR?;G5rprV?5Q_JcGmeHl6{WZfWYiGcYvDiiwGN0eNa6E7|KyK>nKY zLv@tiYgNMgBZn~7lK1lRj>Y@#X{aB%xHb~Bm7mIij>#`f1+&0!APVGwB0zDPfjmGP z*_$gM8=*8Qr1Cfk#=*{J?B26y`%JXCYd36E*|YzEzKnuWw2i$(9r}gL%-n)+ZfRMH zw!4w!r@ju}y=>7owX8(lXvu>)hdzJK&Z`=*jmH)79k)Q{fPR4;+CDrZV`x9=TYpGO zFvcdCo&sY~0JyClWsrk8l_1Qo$-+Mz#+YmrE>b(!32^!+8=!qUN4{V%*bDT45g@-v z`X?X91GMo09h1)8fCkV6$G`}T`+DEMfBz?aeZ84$*REBPRZzO?5fWMdJimygzRqVl zrj}LLq@?|64$m_(K_I`ml{GW6aUkEoUcM`1X5N8p{V@e5dDDdN8yeMOXlBJiAUot> zc)twE2sWyNhK5;~TWiGF5>X2_s*C5r&tNh*0N#SOTOJNB z0m>7}PX3pXw*J?8j;_;vV}Kes1E^f7z5BZj_Qiw$F~W1n8|CF6tnse>Z|K)A z#Qfkk+-!_7=o@@2WnR(Ar?FLLBtSXz~$Pwj^|6_B<*puAiKO9AP3C!l&z1Zc|wr@%Ns zvXjk!Q}(aw0r>)I6UiS^8B73+z!pG0FaWqi=NB=qRylRd}vB!F)b* zadl-$Nl8t3hcFfPpW*Bm!cWO5Y(d-BfOpIqX}%WoczpB^_~;kbQXdg^-x!sZ#W!;G zhwkyMMkPbOv6EM$o}o$0-NZ!lcYLH<3k<;;uo^f5(#JiZ2r|G6Z~;sQKY?z5Wa3o) zhap{Wg_HkR97_T!$7z7-m@J@reh2c+`)&2wYxurrG4*4pe$%@`81wUQY3bI`SXNh0 z@41PoX@j<*8DH1J?(IzjvlJBrvuF4&Lt{b3-^Q`|*iX)_U>4qC(7O`)Itt9(#lKlj zLpKBQMCRn=RKVwzAl+hM4km#Gz!OA+G@t=2!7*?eTmkEVFc8%HKNP&pN!&+eK=KQL zWq|CAY)J%61XK=G9={kH8x9#Xc*Ny(+fP))K28_(SLvA)zTeC|!(*EreS@3Swe{-F zF^2cRTm;_1Yl?rGUM8um{?IKfrkNQ7{`U1p5G$M{HY4Bh~;FY+&R zZV>2k;K05p!3aS0 zUm({XOZ~UA+&w>(?7~cIB5E%b}`QTf~-jLnc)syWK7iBBAi!gI1AHKe+?Wavg(A#-o3rfp&vUo9{W^ci)u_mbiAa-doqRggQL=4ni4@@M2&hcR zhDn#4c;5}_-qznxI;3ZLK(dR2r=S4D02x4KOtvS4@nwfzy?XY-whqt#<;P8(m%iY) zy)7yRw#+Rs8onT#J;};rcT+N%)GcGSbnPLwWbFZV6W`wV35jF|#%6qVE&aL``(=6S zFB&tEo33opZfV}K19G*;RUFDkO`dfLe~hIU#(%;X1CamQ2(AK8Kzhjo+8_W}0?HTZ z@UO^EWkB_Y>egs*6#NRP45(ctKU51!z!}gVbdHUU?XY+6o`L7jpO+B7axH!Kl3gvo zEZ@(TZal_TZ9fgpu;rUiusO^3u{kUDF&Q-@=IS58)OC#*zG=qv3{2SaJvZ2|+ojmj zL$}z9!z%2ez7JDzkE@(G>sN_Eg9iV)W5>?v826LxLq5GDsT2H! zNmzt2F_U0+R>zmEzhJ~x9KFTPsJXxfOzLHoG|HFky52H>hZI|k@1w6gtjty)Q)Syk z_4u-OL8ZGy&beVO=N9ar(#!`WJK4c+fMo9iD6e1S#{Lh<{H>el_iZ3sAfIy!Gy}5# zSAgoD2iOTF1_k<$pE-Yp^!jt!#jB5AOI7ztY`7i%Od$U?t8jKk$44OhN|9Ual!iMy zuj|cL9g=TZwpY4w;cgi|y>qCA$yf;hVfZueO-hN z&nu6puyZ=z?4m&c+bw0oRvlMk*Q^4Wu5Uck#NTqTw6d;3f4B&7Q{6iOoPjM^40;37 z&A(cHil6)n`BrL!mVg<6@(=^aCpDs@x{OVg9QS-w}9s(<%7F?o;9cOP8-y z#kW(<#pTt~w0+`R=sj}s3%9%y1@hD1MbL%4Q(Yljk<&D&(9+R;jpvDMlqNxCPrjGx1L@}jpuC>L{OA7DXV2Zs?yWb>tRoYh?9Km zD8Th=zQ~PpTsHqLA*I&})w`bH7eMXcbI<~`v0i)fx^?U4Oqw!v4%S~x*3r@FgSni! zSm*PQ#dqW3?@+Q01-^1;~v<%jHm+J=6B{1T0wsI4Sl!QDrFlU^u;$&kqwKCS5F>9dw= zcO0}DGyt>;@NZN&cnB)Cn8Py8w_+0XH#5U6yKNj4f}s4d;gYuDNk*n{ByKD&&ftU zQwTO7gy#j_FPuNur4wFqq>N6QG>N;P`#pu+xqpi+|EfgV$U!oZT@YX7`&Xs=t$2}k z!zlcRt)Vx*EPxHS_GX{|x^36iuzhPN_kT4hKe|}@NYux9M{#Ffbe;4KpP7D4H4(-w3!x4n}pTmwypTp0w z{DXIbTkre)od^6TuJw0Zd|bX-^E>K$q2PNCkn3}()EaWfe;>A%ry>`AE6o*H|I`2fKKmz~QlCo*(3pn$vNXOW{ZrqY+yA@%7_R*%)Q|toGLr7ezmx7s&w~Mt zV}1r}z$h>j(A+~0K>mT+g8$8B^1n$o(l5ca>s(vT9aFt0|4zCmy^jZ^dmB&+M8Vir zxaSOH0~8MzFPBE!@Jkt@r?s$^GYJ1HY{u_5Bi%1?|KI*UEDO>H>50=Z)o~8;{>K2C zw;c`0-xK1X3Vglsal#uc0b~Oc0JRm=FQ7O{@02F>8-D_nCZ$bz=-8=Kr$uA~-_!5+ zuK)k0f8+6?VSXi)cp`f7fXvK;Z8uRDRSD8C^ zuG0GT>n}{0FyR2=+6lD5Y(V-af5O>7XMD$FajOjobNWVE3T%x#{G~h$z&E@;P-JbkSnN|DlJrmggzkX)0?7XF z`+?u9=ij@I_YL#MxPCpOwg7%X|L04) z>i&%W$DG~te)y?%#dyYB)oM>KkVhx*9?<3iW&r9RP&+_<&<_7XKOnFJr13ve|AWSk zG$npiU)=q8G`NLy4!NjL*Pf0E?)s0$cU@kV5%%`>$x2E}7Kkey2z&u-;PuvhyYHP9 zzf|(0@om|=EAjG;tF@$iyoXzWs6@d^5Dd8O7IvtgNc{ooAJVu}Fg_$ZAh`Yt7jo}~ za3OscfIt}t^vj*+!mqD$_kA5A-TxN5Al(svR$tuxq-$Ye;cczu))n7{-a!nTIM#yr zQGIdxcXoDue&omzTB~v%WO3~Q>3-Ah9SJ`gpZN8w)f6Yye|2?r2gF6Xr#Pu^N!vSc z6wrLaXh3s{G|nVD=!t&GZ^MTVUqSs84#;_d(<>Kp`X&1yopR^7kh}i1zP~<3n*Uw; zpNPB=0{i{0-)?R9sSiixfqwh+*|TTQY%RBb=&#Kuh7B7gLG(VjDJ%9eZ80Q52eSEL>^@vmJ-%?xft@uWsUSBfv%3gf4 z$b#YTBsRpw#aZH8R-|V!kPT=n2A9A*K;r=#SJIf1?109K)FzNmpuPdkCE@QrjgTZb zy>cO^-yii4ZRZqdpDTBy@&Aqf@txV{qPA zpFUlV=zDl+Ht{Wek^X-=wkjXL34zY|q-%ViuNm=Kf#2{qr1i1JtcvU3lkQ2!--?fP zPyGbwzFtB?A`I`?ABl*F&_P^9fVKiaeqbq}eABp){LKbH_CS3C@(GgwwGG60l7+@8 zoZh*R(?8V*vH|Wq7joA*J^xSBKV%cw9rE`>?ZDviqs@j)7-K$Y(4bxLk^AxcAz&1s z{`7CHtfVm;`SE!xmiiFi($|WO>p~Z=Ss4JkQH2i(#qZm0)L$b%ph6o8^LO@3<&OBw{D#- z<`VQcy>cO^U#buPDf*{&9q$+JMSb{x?VSm{SJV3c4{=@dOx!pXB1MU)NE4zmrBq6T z43Scjh;$kaT|w|wRHC21^?JR} z+H22yeV*r8Yp=b(`?RHgyoXXFwbI9?!8pF7^&GzUWb2OYUpuZ{n<=^UI;GngA0E@@ z_!qi$>o$aMja|&PH^M@QbQ=?g;?Iw9Z?Gi#3Ay%XyKi4n?P)LE_VetHek3n|MxeIZ z!D}!H)cq~^4qR^zJP6mrC>S$%@ZdLw4I4HqbdNmrw~t^9i20Gn^8W+=zkENSn0spL z+-r2)4qvFr?<@6>Ht-@WmyF}lZX3IE>4l9fM5hn#xZ{p)__z984!46kM7m?ts8Jp5 z7eqUdYkw!5bkZIADc@|oif>ZHeg6;aXasn!;oDCzt=~toUGUyuGJFZi;}?xLhOUu^ zzP5#qX~z7>V|nPGE9ZEbIQ)C%oL8yz_Zq#{)~#FDL9hOtuXSr$0O3Cw?bRQB7x8=K z`MLD!+O_Kg#~gFaHGCIp@bu}^-_Go5+5g`(4t$#}7xG&ASYk?N-qD|8KOxut94DkL zc@XWt@c7w%Tu6R1yb8U*cFOpF7B~(t13Z6|He);V9N2FB1R4G9Plm3MhrU~Hy><6r zx0p~>{MsLmfa~CY5Xt+)Xcy)icesvup?l<^zhl|9 z0Wm-FSRVD7E9Y2iz5e$5jr}|9utQJIw?dXZ$qxkkO~y`>{j3k*P5N{{T6bx^)PAp4 z4W`mhyp?A*E3_YMI}a`eZ6IRIV#b4?)W`Si)_i_afA0&m0d39Wk+d7zc+YFOSWa0R zI3DVOao`}(7H)!cn#GJ)v?=!+x<(%Q=I7_1V_b~+k;n2}y2pHuv9{=6?0ta!e*1m= z2Ba1D`|Ua3%3zLSvW!>9k0@lF&8s(AVde`bEr-JeG&fYwP_V7o4#_&a;_gdg?7vzvcGWbyWGP zd)%bA-+O`BU&CgHKYn)KJ`4C3Tj8Wh zlho@)&@W$rM`0W+0n-hj4Segq*ynfNGkEV9`-}Smm(y2N!{K0h2W(5UO_O$Z3%m~6 zi1+*2@jg%$j)wOj(kD!OsqfQ|`7U&gJoK$mqeg$9W5@i+V|k1ZuBG~~!!@e^y7Xfz zb8lD0w#--`_3PM|*T?_+YSO!R@55NPBfJKSL4UW-XM#!px)7Q%{&v0NH@4Y6!;k&( zUF8wpPsM!#>wW|3Y+qs~xL@tb`R?D?JTKNK9)EM%X{X&A`%@p`cgQdEy^GPTYchV= zk9!}~+vzb7eUybvUkcj59-vKEfaah*dVS}?8JAyv`A5GL|JC1T*r9vmp}&1Y@153S z{nzC>*K<6#cJ12x7|U$aqJFDYtJbepty+W7@ks8&FJj#fVJSQf`mFx70K9k8hxURR z+6&KgPE~uK^(Xho{}Q>H-_QORP2JCQa6f8h-K*ImxC8EoOdB5htA+76_UCw?^Z2&t z>}k`cX*d6cSHXUO$-ap;^o4~)dWdPywIb}CWk>QFP#0WQ-FHv=_U$_Q~#YS+nK;~597WR*gA+bOAuT8Pc{i)Y9Sdzu|-*FjjY6_^O=>xa~-coCc{l)mPE0JuA z`omZ-eY`9ye1=@x@;D~XUt6^gyAx~+TW9la$jgG)P}S?NQ>V`0&^7YV*Ly?8uO5l{ zk;iglr_V#|BStKUHo?9D$1wi>9%THUV{ZgoK{?1*AMdrIe(j_4|Bh@IzXpBkK`^G8 z-i1yOanu;P16YUJ(`McuXRe(wB=>1-aXg)6#@(J^vY&5ztv(*>-|!unK7}4|EEK&5 z6zA8fRjV6(?}ufPv?=XTJ2K4&?co8?W^GgMhJ!(k)p~Q#2ds-t!E^T-cJN${@u6$v zp|8)cc+Rvq=0_gOtxwShin9TZ@q1{$4#!ph4PY}+v+bcZ?cK*CMvVBXde2siy>}0P zW10169lix7ZJ`V71K~&d&?fL#Fs{(_t=~nwhZOBW8(>*^*dGSNLogdmkH8a|sb$CL z&vbiu-Fb07abM}PLEaO*mPJ>AeFxKxa5HE_wjtWqQur6V0SiDItO)wP=e$0Y$>yHB z*RV;vs}{OO9{Tc~wHJQr{=hLQbly<4W=TP#a*G_O>$^e@}*mFb2+s&%pE;)PuVq zlZrb(p7X!U!e(&Yvb8dQwWHqfQg)s;W?N&@mKws2;PqP%o^xoCId{tIsh2hox(t>&i2L@Xga3YYq4?~@cXL>j z)ZRG#PpAczU^{SM?$f%9dZdY4_Iz=(ovRKMuKg(D;IXV@+|l=pJ=LKtG=WBN6PSL4 zKF|`Lhd95?xn<995%&2wi<*Mx6zLl#3g9)E2#11wfLGx#@VFj7e1Ey~W`4#7)ajST z_q4}f@_&~zZZRj_|62?6H{L~?XMN0rMYrC1>+3#`yHJTa4_TH>`l`NF z73??Ie*OiLK9sS6wEl_rhHQJRd*g=Px_;;pFiusYpa-V}} z18af)8GGjX%)MUqiu%{@hC?gfx$DmVxE$yG7T?3+({8WNaGIojd5%eML zNZ((f4$(iJFJ*G-a`#O)-E0~UgmK6bB!xis?^B$ytdqO%eubr7j{a1 zYMO`vw%^8a@Ar5v{5t)ZOZ*}Zb@BMs6Upk?mYLe zUs~VmgMMc_y(#DeJ3$pV5E@LJIPoUynEUBj>Ytsq{?CFQFbsNv8kPlpKid8DvHs$i zQj)S|%kC*`$lUq%TkOZ%pKs8h!KL2!iO(`Z z_sBzk?Cv_=mHK;HfA0lsS2#E8KyB8P9jLv&sqXrkdg*V`|FX{a1?#C3dMW5_Qv&V zU=b9TK4s?leD}2z&zX<&p2L3J@om~YBaX`?iTPLFe*b?ZbX?Uu^!NU`S+i!twHJSz z{8y!Ui~3>q=ep8U5$i)L*|>AAQjJRSSLiU^pC(h1Sp$o(9tdxGyuc?0&x% z)0Lq-c&_fNIG+z6_d23q;W6!Ni5jbWMQ~sGyLRL{yMV{24u6N@($~zq1e(GqSdv9Q zp9jUI_Z=Isu1`3z<8$gBx)=|fAG(KbtDDFEFC2LBkLULvoOyrM{sAX<8FYn{p(C^c z+b8`ZeCpS<0qsb?G`{L*9>JOI}9SI`dBDj$p+aelT}?%&*F zsJHs>0iB^Q^oBm!>3-ycvs_ND#)H81+QYuEAJl<^;bustZz#BKW4I9(f@uNV02f~b{#ss(7w~jQ$rvA-|1baoN~(DXLLL3{jS}5%vfFBWBb_ufT6<*`}7+$ z&wD>(KsNnAZoSTh&){V^4~)asvvsU*8MEyVpqP6`m8>uZ+X0QTEvVG;B^=e z?|{j+br;wbwgt6MpO-f9SBUs^Jk#@GWzwgt+yQDsBhU`Cn;*esJ)I54{QJQ3O{Qll z90s=~W_k|RYb~BxU*`CI=n}b_2>;76L+F<)x4)Jgzw;g;`iA!7TxMLCHf`G6+>^yfBKB}757q{i**rLClldq(BHg%{d0G)oy=9=CE0`8Yd0H! z?etEd_D91*Fb%9D?O;4u{uI(_I5V`HU11wgfUUU9`0pV)poej4wS#j<3smOQfpfT5b=@X>(tVYKyP>qnb7}&y?WRnx55=8_$8vdD&;~lf z5-@gm1$|fheggFUu`mFfmrNh<`@gO?-gx6l{C4D)c;`Zmeec)zgdJn|Ia^Zq?A`MJuubdUL=zx~N}9Zq`D=f>XqrSwl;b9&l7!D*mg zqW>CmSEe0MBezkL9l*HU8RkJc*^Yjco=N!z&<-ns+iwWD&LQ;oSUbW=p#Ey@aoty> z|1e<;m>Dw?`By$CQ>&wnKk?DyPdxeQsMpAwG-+~`wvZl2soSt=VeNW{FVt?h&wAK* z039FBrswoS`v7TOjTzcNdVa2QoJVPM{&)OK_ABE(xHzF~ZkumCrPDZO>;gN1ZTHId z1J-35W4~?j)=&#xg>-t28P~w<)B?-;@Sac}wgk^XowWh?FWQ+gD5<|Ta1wZ3-#{lA z1*T`<5}0FwWb9v>nachdIB?*r`ss0PPkO3Zi?$D^>zUa8v|6V1SNqDFfq6AyFW4EhL1T$+k>}#R zjW6z7JFs1Fx!TJqVEP(PhU;Mp`~zk|A;0VW%y$F%o!-o3{HCq{)Ht83UeR_T?;k#u z)<4R1>K(DzSYTcI-2e`C92~6vzAu>8)%Qjmn@`XGwaQ(GTsKGJ+cjQ$BHhY#2j~Zn zz&wbg-Rigcap)iZR+0^$~ckOaE+py)g7n=V~wZ1Nd#x zAP!a=8XHsm4PRPUe}813B0c}tDxWdrAG5`88`*DJcJ)PnzhJ5(*e_O#wxB+?m;Z*Q zpnuu+>T9LdKYUVsjQ^EjZ>R!Q;Q%-SE`XmPnJixhuIn~?z;3WB><;d$BG@<47B&O> z{@U5!KpQv>;#vnVZQtK(HGKtFI~L|UQ;v-}MiP3ful>GU_0RImyGUu>n;g~r`{u`= zkhzyPrqB)y;CJaBw;>$DZx!$4-r(!>etoCKW2Wc-+GYDCzE9<`0{b4mv*vrSeyhsy zDW6aB+d6H*y0$Jih7CZUF3Im>xptzxxSiSr>Aov%gWB+T+YYy7T{;&^J zgHCV{6u`k?eAoqc0PVooU|XSGbOwFjGzn%pkxjRgAMd+5)Wg!=7Yz6uzDVkC{p6~D z`v)#h>#z3WdtkCLg~L%l(R2EA6?ew|m`{9MOh)%o$ewK!hnzGvre%YByY zm;uvA`<#MvRx`EN_t%G&)!!HpHsC(A1KV+JKwqu_Qz6oPCLV_N&2Xi5k znzjS&$aqj5)V@M?x+VDsU|PB?EBb4Ec}<-pbdNmhC0)-xGZXvpjtxY=J#x2me=O_S zrAwEN9AiJI#l67E+)rN3INbNH6UT`>j_3H#$h*g`@cce~9xGdRT=5_GKJ>NikI#VP zxar)n0`D0eKlJ&s?sD0L0ce+qxfZU?VUN7{85=$3kt;&(+`jB)AtAKr-1@ zB>mat|A1paf3v;VD_ee^d@(G96;kMtuHRhq)t%p-UBr&|1nq5K7!4-dtY%OR8#q%O z@2gw4?oiuT_t}5IpbSypk;n0_z5b>`NuST^mM`c(=%s!GhD_71J!56{9G&;Z<*@uMA_ z4pNZ?asT(yzhA+S zsTURup5i!z*OtECD2M*Me=;%j_t^s1w7R;-_Ua%0za`W5)$QA7+DY=}a4Y-(>7?de zpdM(G6QD>-rcGaiS|1PEK>aKSlh=X{@K11GnRg+JtB+jz8}n@g*w(mJ0d|6NV0*SB zzgat}9^ZO(9qr`Iq1S(O-c`3Ohw ze#e;EzBh8-1qGA)UNq=MpE>IL-0yr=-1VmS8|NACoN;Y=zXB2a|H{6+PcI9$)B3*s z{4?Ph$dyL3pdn}@jiDcW59wqqm;|STx;KJDpaIkaZOs^S39;}4+yC_ShaTzq{`Ssz z&bG$?UBG_qcCd{(|Kq*`Hgc-^`<(Bb%WwYf!mDpvp}!aP8@b2xK6J>CA#eIFz(oUw zzTAJ%#jjm`*Mpx(`aDW1$9qGezkQbE7)MFl$9dTg@ZCeMtqkb9t7->oA8}vpcYxi% zzM8sR4j;oWCEM`pKrM&BJou&b6$_2aM}hXGE!2V9us>9{AMdw1()E!`f9LxwHM_lW zD<8J?+upo~d4libTGyu!zIJBMVK>f=dMqjLbLBsO>~Yx*U)VqQK8!Y_KktKFx(`nu{Eu0_muKAYd*j?c z6c7)Jeq$QvR9ue!3mjY0cDx?X!(8pT=Rd=7KesDRd*|iS-}giFm;F9YpEYjVc9+Hm zj1{iu`WwP#uss+vjh*(-&xJSPw@8i~7&{t)$F|S5mu-V<#p=+)@z27L)ZqKpp9&y(LAK!5QqYL7FpvY}YisPULO_x^N@8Eg6RjK)5r7HV> zbYT5M=TzIb_8Zv5iQJR+i)Yxm^pE*zJ5YaZiEAtiYN(cB1G(CR&|Q1k1j6Ul-FR>O zz7c+e-#Wd(0Z#?}zq;BRFQfh=_d5~#OOzuI-^-r#SD&LFpL@mV?{n!M z^J9PM+;%&o{h0s!6q+aV;;=DuW zTzOi5pK)gW- z-m|CmiF%COb%@m?9rN*dVT=pM^2kflJ$;;)Xa7_vlcv6sA-4bf$Qui#e(f$9(mML= zSzEa8zWYA+Tc*BmK7Rc8592)2d5l-5_Y>#idxQL*Z7g-=J&t;A1rY<1{vY~>_S%Ws zSIDwI`3Nu$|5Eynh4u$Lrn%e&??O5)V8)a10K_(KSDH3uG3$+2yW;eBjHkHoETrp? z=Y&7`dn*0J-(IJeM>+J1dMznmRsHv?-ROItJNlg8Z;$dnCm!^jpE!?nUXuRV_3fKY zgnAG$-Tqv%ACS~v`|y6qdwb)?Dd5;xY0@XG)DDgW$McQ_`|h_vI?ZE7XD|l4?$O|O zqo7pD`{A~2+m24_;d`^Td+GTll|$zk=ZX3rc=?F$d-NMr>i7GenDu_4gum~1WUoR= z`)QQV8-81%aJXR^ff6KhO?r$FC+6rO=EA)VasOE4}=tovDd z#av~t-)l_k>btYvL#5}JR8H&fb0OaoJM;Yhubz5N-xo^SK9;YB{uy8Qy#vM*tdB_V zFfka;gr0oEbR=Bk*reaRtXaF>d;8X^`%YSapBJBS!U;FC%?Y5N?Fw-(5PblBKlIl& z>>ut0k3%G5NZ6pcW%;dA97~&-58r~{N-6feJD+_O_nn2%Cs!W&NACBSeCBdWuk)Yp zeD--IzTXkLcN;dUP{Qvc55J6lfzNfWyYJ!evg3OCe#ZZ~He=%qh@@YA0s5A9V61G! z`JLu-D%(2mg)8s9@5@!H*L*eX!0}>#Ps+M`g4*v4TZ1vaIQ{kijbImO1!us8FbD=_ z89*+5;BzQ0z0b^h!FK&lh&aY%QL_Di0&M@qIFKt%WWnjs74(5_(B0qMX#Z(F9rr5k zI}7Rc(&f-Sa{CwF&!2qu`OkGa>)dD3>!-^rtG~~5g?E+4IWFb5ksLqf+DzKKb@V-` zzsY${+a7oK{(#gj#;Yq;*=OqRRclNO{f&=~kFst782j^KTTtWmK;KWI?{5s-fqj4u zpmraC_Bab>!|kAbOoQT*dbWVha3tIh>GU--&VlxDBAg5VgW}SC%sdc!z>DBH%;Y`) zdBuG%BXkKJ)A?EFUO3D5g{*VO)pM=yxSRUx`yJ2fy~6!OY!~^z?i^Dn;qPai)3;C_ zd~#BT@cS!D6o$j7Y*Gnr z0l#f)U*L!%n}4VO8(Ub%^%>7;E>hbdWJPp^yI1Y-AnYHg9qW;r)TK6blH2Bg*>hHT$$?@Y@ z9=YSUE93vA)!%0WgN6=!%jXf=Z~R}1?DeXbHXdm)6R*MuF!p&5Zavh8eWAKBfZw*X z@IFSHLjNUUHbL7apPuq|7gxK)6K9OYzF#&GXCdLmWTm=WqJ$P5sVl5 z`seU(s15a?DcBC>O0KVN^50@s`iXhQ2(S0`VwNXoJxIap^w?Fww&iW#;nK!Cp?z`j zIdqJ?VCb-=`n_=@NiO}3%^u_QKK-U?W3gU3FG+vj3vwK=|G=Rc;@EK?$F)TIh=~g! z{Qp~~AA|QHoxW$rWcWLDf#$t>^`bl2r6c!5L;3c~=kA|xn%Mr^cdH27z?QHf6sLdK zfU&_og6o!rZ9yBT2bJI;&===Gt~8zn)uATb3F$PK8Mgh0z$NL0sq(#)tAYF56ST`l zwtIey#W7Cbd9%;&y`RtV9Q)QbY~SryMV+Vf^JrkzbBbJdv>9u(h|zILtDtiH*)R16BOq^%q~jvn0gYffYU)gISFig z?SrXfIZ%IN|6gET(BCEeKlHaB;5x>M&0%XWZfFaY!FY2p+z;<1@Vq|&)2HBa{r_V~ zC;fjSXy0#U7k)?n8oZIk{e1$1U^mzu^5Ji=Ir=u{o!2Ac_q5u9V?U1n`TWd#1O9i! z7g7J|+-LT-AE9gL9(k;P@rZvE4!q*(72Y%WmG!sXKM~Z=`chLhJQ+TO(xxw2(Ku~; zsa`u}QTt87*skxZe^U4Kv^KClYyidr^*0WbhkU3AHJ~M&0LMZz=m)EsUS>n%K|N>y z4WSNHfSti(ZU@@4b}3Ffubg9k;&WL0!J%X58@YOl>)6MO`LTZFN&Q_v){8vW3%@^m zz*U73?Y?b|gx_4mbnOg;h=J-vb_mTe4h7Zv1z?G0bbx7PV#1XM4~l zD;w{9mXm$wVrl3TdPT057hZD3m#%C7KIX^zk^hGJf6n>00d2hnTn$G+E$9g=X$l=-$pq753) zc7$DFPpIa*SH8m?`h~9Pd?4fbw&#wSCEp*2?IO>mf5iPJJx(f=@c(G{FB&-XmCLRe z@saly?!)gkNRG6G`P#besqwTLREG+%59m|22c=2RvC^?%y%;amej^Co)iJGYY0I_; z?knPNXQsaclkrRc*JibWRZY{_&=`1QY6q85_Ie~go9{wi8LB`dxIXGT^h@g=WuN6Z zp0D=y6JvgC7y0VwuMPN4kG~@;L4WGfUs8YD3;T;5yZ4^Zx#xM4x(vGZzrF+HZ^k=y=|08&p*9fxg?O$T z`wzcQxBHEo9XqzkQ7sL--1l%O&eho7eZ%&br+Ryk_-31Aa+=wSNfI z-nwxg`l#*WUqIbf$M3@)>v0vXinxtfBvI(XMxvbtltfGhKgWZvn?pe|Lk zMziy0VJ=vIMX9v(mga9MPd~TcRQrjse)Q{ewfph=AI4w4 z^_#wvCr=(i-~SX&uo)Z*9bq~|n#9E6P#g4r<9`LP9`}R&!TS6Nic6!J>3-GT?Tqd+kXZupRI?TVyd7l!tuK7LD0=!Gkac{+-}HruEzB;bnLerom*!FCSL} z`(^6xZ{7T#ERK`oOYbwL^#$Wu3s|?fw8VD-9Mdn!|3lB77Y%-A$W_;5en;Xv*gj8J z_dxsb*?+8GTK%(jxOCR6S>Ml?F~g4Et#Bor1Sh})Fc0)?V_D5CL&#@9LAGqX>khLa zokla`5Qw<3J=2@Pn$kXOK^s^fj04(${Q+an-#{DO6TF^-LEAQdp9t-wC7zpa5{ zE0Y#wbM3tiGyvuT)yUnFT=oxvxf`N-KxTs)(znie#cAVGyEMvH~5#w%y zt{C~h?iUVx!@iyUd*9*kSy8Uzr03`A?}z`#{l3SrEusAx&%0ZnGPb`cF}|-a|CGJ( zx5$qJW2E(DAK!TJCFuLM5zXN~FntC+A@uLVbVD#+nAZkvz`8dE>yN)h{I_qpN%oip z=@sz&cY+;#ZsR*!>H3dy=pK2GvwMH+_pp3+W4!nK zJK93Th0y=}A;Ugadr^Cz5timXo;Yr4^;i3DeFseE+1sV`SFYlI|AmD9FK~^wgK_FZ zXaNx;^O>#)+JrIS3-}m}1)qTZ17o~xL4T+Lb)XyE0R3Ssj0a<{{%E^g2K3c6!Mf~M z{WpUhpc#A(?}Iix2aW)JU$g;ZL|GUP+DhYW*?C*|u9p83A>Lbx`p=bz9Yo&u!u~%d z`v);Uy?y&`=Zx0|axG7eQpiL<=;a8%{h+eUk$YJ zZy?S~+tmMERt1a!+JMtut9G+JYzZ61|HxQX{d=F+_p4Ire`c?z9qakE_4j^{*RMv6 z9zFUx?osA)O{S;dOgI6Cfazl}zFP;zK4bPaV0~DZ#_|iH4rm{?6V{{gUH$ikbHKVb zy$AE*B>1iLw-1=~|EiQP1?})9Fj)t^!Fc6zG3IIOWx;FKezeuU_>Br}V5eR7em{S= z>K~NW4#HnU&&Xqb&5!H;(ol(7jYrl8Gf(x6K#*zZtOEX z58dG%&=<}Hll9U9)LPB8f7@*9!alV2;Izx~!8%ia>rr1auDVat%V59PV`&%puoe6j z)};Td|N5{Alm***W89CReLn#v?Q%Hm4;8`n{swCAwj02Dfc(q;FU}g(pV(jT@T2cK zq*;smer^5vjb5Q-)4Svsz>C@PW#pm%*GwM|p}$(Imuw81ff`yz+JNyelnOA z!W8%(&W63gc;f!_&2=Gf4WfN*2%e{LXFt%+Js+!m`2Di;eF7rt{F(ZzrF?H?*T1d3ybHIs7|4&KK`_4Z3*P z+rGaW{msPl9sOUuCqSetnJ^Zb()z2nzGQt`A8Mx^*pG`CpsvTk>kw%y6SLqAs1NoN zTwlL(KY42q{Xg0O?alMs9h$*BcoY5!$z;9sf?Xlg=97EP?x$G&{Vn9`>fgRo=PBAi z=pK1m?{v8&{e8AK;+pF-&xW)C_4j{}b1gT*D_}j0hKJx}&__+*g8e%Ez}Tr?MfH%K zHWpZ~_K);?>&>_t$-2B9+}FiW6Lx`ZLBDc;Yf}H%w|1tjdL8Ot2d;+&a1Ts}WST(1 zb5(!aJh#v2Pwqa;lr3Afh2LWE9X{{r<2$TrI|v;kPwTg;<%lb>-?-0@{=VPx@%P<+ z1KRQE80UHQ)mMLH`h_V|rp%Z=J#*O)b3G@5y7z(mz@(1Hg1)N08$nVpNt>{nZ->qG2oP3LN-y?LG$VP7yFnY`Y6;ZX~u z3D6L<)e5i`Xy@)DY#{7uEBXP=`Q7Dp(Z1%21KXF|r7%hOZN%?%9EkbpJhqEG-R{Tw z=k^;ow`@*F#Lxo8Pwe!FK`J4nJcCQ~~KYHS;C9VGQvKmFeJL7zGr zo&eMLFbdwaK>7mI-`J~9MND1Oy2t*L`j_YU`@=O5$@BadXbYc0N7xJY0ArtZkZc32 z2V=sf<;$0E$+QliE8S`h{MFM zsbG{ohS>& z)8ATu&s`16f%-oVk@WZHVLoUBb72Xb4YmW7As@VE{ayQ2ck4oX5X+nJ`;M0U-{<4~ zAG?{~_%Kem^R9c(2tSSA)u;WvwB`7{UV4Agju_XC^|f0MEtKvfA6|~Ii}(#vls#9^ znQKs6Q6sNOv;oW7gRwz>P)qBk5j+8=i7-7gwd_61nb~QxmS9_}9R-}^3y?{v_o z_q)DD@xRe&R~JgefzUhhnD4l@a2@*1I$(W8toK^gz7o^~eg0VZJD5I%XERgF?k7)S zdI0>8op(8TRd651Pi@O%Bz<&EPJ1l<(^$3@gdKNd+T^+K4ox89!t+eO4kp{SC*Vpr z6`F$fV*kgOVm-P4xQ2D`CC7-HJ5C(>SJ=JA%(Q;LrvBPM+=qs~rPV+2{blP)Th^xa z7qwUSLqYqv0`7*l!88|aKfi{BFc6HDu4l47jkmTL?rT%fwth?f)x~Sl?z|>rP{e_e zOizTpVIR;2)ZS$NUS?|fKic&z;5FG6cum%q*CZaxYuq^Q5B+AJ?|y{-tD47mx{`jM z)IF_#*g^CMq8xsIgWtC~w$JFoB;kMQJp40P9&tVV zIs88In4ivLf9d0?zwb5FY1H&K-#y?PEw%VYgE8M2V0-rgMEV~S+QTAP0@j5wzbaG! zeck$qy0xFI-y0*tN0Qq7R@2&m7zfnI^HHOXz;pDR%YrtrJG6n>V44W8WTuw2bEBB9 z2YZ9@Zbz_hqg}aAai7}Q#`XjJ27FR`r_(w_IdsaE|JwT7PnClkAAM(oCe82R-M~Zm zZSs+v`+?9G!WZT-{V|yIgDXKlQ2%_;-!}#8)Vk0Q!Y6(Yzc0?V!VWy2hzVf}+N}Pp zEz7l_otwtNWU#Cc+zLlQMbKV$0q;l9@ z!=}yeZG3dAG5ntFFrFD`|0Cf?FueCpNFBtQ;0d==7)m%TbextsN(_(Fy z=lO*#=(pY@Q~RX(Eo^I+dO#KQODoUH#<8|ojTR)^qNgC!z8fp8_DJSfWE&2X!B}s zU9JtfuF5%Rx5h7Hz}|2GX!DV5@7{;U;nnO+W5K;}I2Z$r1-oUZZBMpUe+m8fIpp}~ z_C2ii3sLXsJp4NHTz((R(|SfZju-2t^Yn2YC-(n)@Q%oPzAHkS3lqS&w-6#31FwT> zpzrH5`i{P|D)qj$Y@_YkS232Uq4w?dNBTB9VN84yCc^yeybsAQhB{yzr~=wTdB@s) zHV}GLYuI9vHju9WD5rJ$t@P(RjL-R7^2B>W+W8xx-Wo*2gRhwG3if}?f$f~Wv$p8C zs^_8o+GnvZmk(-qCq&=>Bc|0@t*wjK;Zb-FTt}wBAgBk%#=ZQlpuVN%)f%;&?6Wt^ zp?9vlB;CVK!r!Bu-Y)De*Kz#L=!F9>{?Yz}s zp$x2Qy{}E%X~(u@w&VKoIbfZb%=^L_pzbEy#7XcJ=ocp2gg4-3=<5H=(FST9+Uj}n znVi4r&!tDqPwN=^rt{U+Ki(JcckT2e{>wSX^&01{hnApCM0$Y<>!v&y`;D3Zm)rfM z{dpbUyLeq*Ukw-yw*96Tp)NFmfiM$HwvkgaQ$PP%%;enh{BI2YFY6nRJo3n7euI(u z-%EbaHFQqr;jd|3)4E4F*Lvyw^#0cqg%ZaL-SvCEKlkM;ue|coJMX-s<|g|+=fM$h z5opsU<3bmx2s?oNC1d9Q^|n83K>gRxUYGs1e9#7ZfWGl191XVfwcs2Gzn{jmZNqXY z-oG<{^2sOP_PtEMfg3uc^R#ZEbFTc?&|kmjoxnG;{k~hdfMcKm90(7?Yv8?3L$Gi3 zH?W=5_l^C36#cyp+W@c2d-?6awqR$_?@xwvLTL1KVVNbE|7$=DQt+(F#nsD`}$1y&mSRA+JN!RwqToVu03e`6<}|$k7u9I^g)(Pfj$qVj~VZM-sbZm-^b$nVf*nOd0n0tHsyatH}|{0ekUf{ z?bX#kwm+k1pLg76R)6EtG zz6C?)hyHKB{r2~qll|7PUt^hVefYldPTl|Lny$?CSs&Jm{Q>JqUug_u;c=J+$#fS5 zZDb#)X+g` zKHsGBe~HC=T;{&d5jNm*-~acXGyBc)j7_OO_kLe;tOBSB_GiRCpY5AAZmj$NLDQAJ zM(f4;(FXJtZD1Er<9c8_Is=x1=`1J@5m)2hDOtbzn)g-qD=I;CcqH3SFBb=T^to_O z#Bc<#~PS9{wxw_n+M`TuEs>p0>-v;}Hx zUFyS!!sYNjJOLHJc%dfgx;3V_zi0#0VsAK#cHpyQJK(dLRvk{A5c>O$u`xfpeX{=D zr~C8Vw1K){fA3uw3bsc(fi~-y#+DHG^`4*Slic5*(<|0T*nst{--NGOuePJ=-2hGo zebD%>Z{@06>pfzCZNgqK7#3vP>xJGI_&mvXb^K0H#D3N(P9N>l{n=No0{y^#s4-p} z6Jt`e_pYzqNOAl7b6)ZF5_VvFpug!$)~m5xy{%8RR}0$>*DtP)o$vbEjWJ{oux*~E z{*Kf7?PQ9*owPRYUwd+Y9@~A!c{ql<4QLk;@6|q+U8eW_XL-f?w(hKN zV}UVVt*u*eS{+>GI>l*korY#^uMHWO%R>k7*+NtA4;?3PyukYh`;fl7&$6^8?#px1 z-aNMFpdDzR;(2MWVUOwU|17UqrzO>ibsqJuKZ*4l)1kfV)`^0vvh()novTyz>51h73AfuGa>KiZRk1W)&IG?*fJ?L zSg@?PtelruJzLDH=L)&XTjb^0N+*|R%dvT0W(Pl)N>`qc*q|Q!U)l0EsqI&`{Lj?( z1*vkPSlsn3O)W1f|Ahry4`rkwZBSG$lPPb`UzT&a8)V8X&f|8bXxcI=r^^M-kHqqV zn9k(NlT7_Re8JCi`eO=Z66JcB!UlhVXE1c z@>AE7st+u9LUMPM3zjR_OKqQ@+W+QBYhlYWvzFUDugvnrl`f|?U;W~Q)GlWIa=A>Z zzBfpfH&543s{Z=_aye~(M4;tP5PM$k1k?6i%<}a5t6TmiZ=JlNM8KN$bGg0&ZNbm} zF=2&rzRQd3eL~*y<$`RvX!omF&N^s8UUvJR9hT!QXRzsVz0`8bMe)b8DkQPnNSQ_> zVF9d8ilupZ{Zr*kF|mAExu9r+l`LltpRkZ@do>~Umo3kV@&?QHUv$Fx%gSM5+1(ZG zu&DXV?y#slOSkQ4a9x(Z$=fV1ugA~j1$l%x{>1X7d4$AoGQob?ESG*eLGn^(U4veI45Et6Nh zdbYG%Ufz0n_3JMy?a?rg?FtIArN6F|XM01+T6>wiEsCbf7fn?!nyOzkbxhIJ-e>nZ QyYsTWWOh@A Date: Thu, 3 Oct 2024 21:56:06 +1000 Subject: [PATCH 22/51] emulate retail semaphore logic --- server/channelserver/handlers_semaphore.go | 13 +++++++++++-- server/channelserver/sys_channel_server.go | 21 +++++---------------- server/channelserver/sys_semaphore.go | 7 +++++-- server/channelserver/sys_session.go | 13 ++++++++++++- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/server/channelserver/handlers_semaphore.go b/server/channelserver/handlers_semaphore.go index 19925c6d6..2088cabea 100644 --- a/server/channelserver/handlers_semaphore.go +++ b/server/channelserver/handlers_semaphore.go @@ -65,6 +65,15 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysCreateAcquireSemaphore) SemaphoreID := pkt.SemaphoreID + if s.server.HasSemaphore(s) { + s.semaphoreMode = !s.semaphoreMode + } + if s.semaphoreMode { + s.semaphoreID[1]++ + } else { + s.semaphoreID[0]++ + } + newSemaphore, exists := s.server.semaphore[SemaphoreID] if !exists { s.server.semaphoreLock.Lock() @@ -77,7 +86,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { maxPlayers: 127, } } else { - s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1) + s.server.semaphore[SemaphoreID] = NewSemaphore(s, SemaphoreID, 1) } newSemaphore = s.server.semaphore[SemaphoreID] s.server.semaphoreLock.Unlock() @@ -103,7 +112,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysAcquireSemaphore) if sema, exists := s.server.semaphore[pkt.SemaphoreID]; exists { - sema.clients[s] = s.charID + sema.host = s bf := byteframe.NewByteFrame() bf.WriteUint32(sema.id) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index a0d1fe1b7..a1b2c093b 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -424,24 +424,13 @@ func (s *Server) FindObjectByChar(charID uint32) *Object { return nil } -func (s *Server) NextSemaphoreID() uint32 { - for { - exists := false - s.semaphoreIndex = s.semaphoreIndex + 1 - if s.semaphoreIndex > 0xFFFF { - s.semaphoreIndex = 1 - } - for _, semaphore := range s.semaphore { - if semaphore.id == s.semaphoreIndex { - exists = true - break - } - } - if !exists { - break +func (s *Server) HasSemaphore(ses *Session) bool { + for _, semaphore := range s.semaphore { + if semaphore.host == ses { + return true } } - return s.semaphoreIndex + return false } func (s *Server) Season() uint8 { diff --git a/server/channelserver/sys_semaphore.go b/server/channelserver/sys_semaphore.go index 78ff963b5..2200877ca 100644 --- a/server/channelserver/sys_semaphore.go +++ b/server/channelserver/sys_semaphore.go @@ -22,15 +22,18 @@ type Semaphore struct { // Max Players for Semaphore maxPlayers uint16 + + host *Session } // NewSemaphore creates a new Semaphore with intialized values -func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore { +func NewSemaphore(s *Session, ID string, MaxPlayers uint16) *Semaphore { sema := &Semaphore{ name: ID, - id: s.NextSemaphoreID(), + id: s.GetSemaphoreID(), clients: make(map[*Session]uint32), maxPlayers: MaxPlayers, + host: s, } return sema } diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index d7e55d621..ca9f5ff6c 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -48,7 +48,9 @@ type Session struct { kqf []byte kqfOverride bool - semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet. + semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet. + semaphoreMode bool + semaphoreID []uint16 // A stack containing the stage movement history (push on enter/move, pop on back) stageMoveStack *stringstack.StringStack @@ -79,6 +81,7 @@ func NewSession(server *Server, conn net.Conn) *Session { sessionStart: TimeAdjusted().Unix(), stageMoveStack: stringstack.New(), ackStart: make(map[uint32]time.Time), + semaphoreID: make([]uint16, 2), } s.SetObjectID() return s @@ -310,6 +313,14 @@ func (s *Session) NextObjectID() uint32 { return bf.ReadUint32() } +func (s *Session) GetSemaphoreID() uint32 { + if s.semaphoreMode { + return 0x000E0000 + uint32(s.semaphoreID[1]) + } else { + return 0x000F0000 + uint32(s.semaphoreID[0]) + } +} + 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) From 2d48d6326353573d7f496b6452427fba7885c1df Mon Sep 17 00:00:00 2001 From: stratic-dev Date: Sat, 5 Oct 2024 04:36:37 +0100 Subject: [PATCH 23/51] added ./bin to ignore when building dockerfile --- .dockerignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..6dd29b7f8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +bin/ \ No newline at end of file From ae3295167111dbdbafaf45f859e5fc507e657767 Mon Sep 17 00:00:00 2001 From: stratic-dev Date: Sat, 5 Oct 2024 22:56:53 +0100 Subject: [PATCH 24/51] Add troubleshooting for setup on docker and add opcode dec and hex to logger --- docker/README.md | 4 ++++ server/channelserver/sys_session.go | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index 3f45a12c0..11c018215 100644 --- a/docker/README.md +++ b/docker/README.md @@ -64,3 +64,7 @@ if you want all the logs and you want it to be in an attached state ```bash docker-compose up ``` + + +# Troubleshooting +Q: My Postgres will not populate. A: You're setup.sh is maybe saved as CRLF it needs to be saved as LF. \ No newline at end of file diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index ca9f5ff6c..a3abd5622 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -15,6 +15,7 @@ import ( "erupe-ce/network" "erupe-ce/network/clientctx" "erupe-ce/network/mhfpacket" + "go.uber.org/zap" ) @@ -275,7 +276,7 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien } else { fmt.Printf("[%s] -> [%s]\n", sender, recipient) } - fmt.Printf("Opcode: %s\n", opcodePID) + fmt.Printf("Opcode: (Dec: %d Hex: 0x%04X Name: %s) \n", opcode, opcode, opcodePID) 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)) From edd357fe502a8d113e19af5fefd61dc6ad5efaea Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 8 Oct 2024 20:42:42 +1100 Subject: [PATCH 25/51] concatenate packets during send --- server/channelserver/handlers_mercenary.go | 1 - server/channelserver/sys_session.go | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index 8f1f4f018..a033ad6b8 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -21,7 +21,6 @@ func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) { data = make([]byte, 9) } doAckBufSucceed(s, pkt.AckHandle, data) - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index ca9f5ff6c..eaef60e18 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -152,18 +152,21 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) { func (s *Session) sendLoop() { var pkt packet + var buffer []byte for { if s.closed { return } 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") - } + buffer = append(buffer, pkt.data...) } - time.Sleep(5 * time.Millisecond) + err := s.cryptConn.SendPacket(append(buffer, []byte{0x00, 0x10}...)) + if err != nil { + s.logger.Warn("Failed to send packet") + } + buffer = buffer[:0] + time.Sleep(100 * time.Millisecond) } } @@ -188,7 +191,7 @@ func (s *Session) recvLoop() { return } s.handlePacketGroup(pkt) - time.Sleep(5 * time.Millisecond) + time.Sleep(100 * time.Millisecond) } } From 34e84f31df557b085d13ad49047b3640a5b751af Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 8 Oct 2024 21:06:52 +1100 Subject: [PATCH 26/51] ignore empty packet buffer --- server/channelserver/sys_session.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index b82f4ca17..e091f78f9 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -162,11 +162,13 @@ func (s *Session) sendLoop() { pkt = <-s.sendPackets buffer = append(buffer, pkt.data...) } - err := s.cryptConn.SendPacket(append(buffer, []byte{0x00, 0x10}...)) - if err != nil { - s.logger.Warn("Failed to send packet") + if len(buffer) > 0 { + err := s.cryptConn.SendPacket(append(buffer, []byte{0x00, 0x10}...)) + if err != nil { + s.logger.Warn("Failed to send packet") + } + buffer = buffer[:0] } - buffer = buffer[:0] time.Sleep(100 * time.Millisecond) } } From 436e30f83d0cfce4a7ce5a1cd3083772c589277e Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 8 Oct 2024 21:07:13 +1100 Subject: [PATCH 27/51] remove duplicate updateRights call --- server/channelserver/handlers.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 4f8e89e7c..c0d141f15 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -143,7 +143,6 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { s.token = pkt.LoginTokenString s.Unlock() - updateRights(s) bf := byteframe.NewByteFrame() bf.WriteUint32(uint32(TimeAdjusted().Unix())) // Unix timestamp From 8191994acbd9776563513ae3f68a099adf9aaaf2 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 8 Oct 2024 22:26:00 +1100 Subject: [PATCH 28/51] add LoopDelay config option --- config.json | 1 + config/config.go | 1 + server/channelserver/sys_session.go | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 2b836ab03..fc5b2a644 100644 --- a/config.json +++ b/config.json @@ -21,6 +21,7 @@ "QuestCacheExpiry": 300, "CommandPrefix": "!", "AutoCreateAccount": true, + "LoopDelay": 50, "DefaultCourses": [1, 23, 24], "EarthStatus": 0, "EarthID": 0, diff --git a/config/config.go b/config/config.go index 52642956b..f7c48f88f 100644 --- a/config/config.go +++ b/config/config.go @@ -81,6 +81,7 @@ type Config struct { QuestCacheExpiry int // Number of seconds to keep quest data cached CommandPrefix string // The prefix for commands AutoCreateAccount bool // Automatically create accounts if they don't exist + LoopDelay int // Delay in milliseconds between each loop iteration DefaultCourses []uint16 EarthStatus int32 EarthID int32 diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index e091f78f9..86552f611 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "encoding/hex" "erupe-ce/common/mhfcourse" + _config "erupe-ce/config" "fmt" "io" "net" @@ -169,7 +170,7 @@ func (s *Session) sendLoop() { } buffer = buffer[:0] } - time.Sleep(100 * time.Millisecond) + time.Sleep(time.Duration(_config.ErupeConfig.LoopDelay) * time.Millisecond) } } @@ -194,7 +195,7 @@ func (s *Session) recvLoop() { return } s.handlePacketGroup(pkt) - time.Sleep(100 * time.Millisecond) + time.Sleep(time.Duration(_config.ErupeConfig.LoopDelay) * time.Millisecond) } } From 4348aa02a86cef29614b77ea7e6b44c50fe30264 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 9 Oct 2024 00:51:51 +1100 Subject: [PATCH 29/51] temporarily fix rasta expiration --- server/channelserver/handlers_mercenary.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index a033ad6b8..611096045 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -166,9 +166,9 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(1) // numLends bf.WriteUint32(pactID) bf.WriteUint32(cid) - bf.WriteBool(false) // ? - bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix())) - bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix())) + bf.WriteBool(true) // Escort enabled + bf.WriteUint32(uint32(TimeAdjusted().Unix())) + bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix())) bf.WriteBytes(stringsupport.PaddedString(name, 18, true)) } else { bf.WriteUint8(0) @@ -186,8 +186,8 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) { loans++ temp.WriteUint32(pactID) temp.WriteUint32(cid) - temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix())) - temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix())) + temp.WriteUint32(uint32(TimeAdjusted().Unix())) + temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix())) temp.WriteBytes(stringsupport.PaddedString(name, 18, true)) } } From a4fa55f9c9e057aa8c9e9564192c8edb7d2f85da Mon Sep 17 00:00:00 2001 From: Brentdbr Date: Sun, 20 Oct 2024 02:26:47 +0200 Subject: [PATCH 30/51] modified NetcafeDefaults to contain the retail Netcafe point rewards. --- schemas/bundled-schema/NetcafeDefaults.sql | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/schemas/bundled-schema/NetcafeDefaults.sql b/schemas/bundled-schema/NetcafeDefaults.sql index 458cfa7b9..dd0b101b2 100644 --- a/schemas/bundled-schema/NetcafeDefaults.sql +++ b/schemas/bundled-schema/NetcafeDefaults.sql @@ -1,13 +1,15 @@ BEGIN; +TRUNCATE public.cafebonus; + INSERT INTO public.cafebonus (time_req, item_type, item_id, quantity) VALUES - (1800, 17, 0, 250), - (3600, 17, 0, 500), - (7200, 17, 0, 1000), - (10800, 17, 0, 1500), - (18000, 17, 0, 1750), - (28800, 17, 0, 3000), - (43200, 17, 0, 4000); + (1800, 17, 0, 50), + (3600, 17, 0, 100), + (7200, 17, 0, 200), + (10800, 17, 0, 300), + (18000, 17, 0, 350), + (28800, 17, 0, 500), + (43200, 17, 0, 500); END; \ No newline at end of file From 7bf2fd5b8fceefc64c1414d114f984c375204fa9 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 9 Nov 2024 17:22:43 +1100 Subject: [PATCH 31/51] revert packet concatenation --- server/channelserver/sys_session.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 86552f611..d7f41c071 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -154,21 +154,13 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) { func (s *Session) sendLoop() { var pkt packet - var buffer []byte for { if s.closed { return } - for len(s.sendPackets) > 0 { - pkt = <-s.sendPackets - buffer = append(buffer, pkt.data...) - } - if len(buffer) > 0 { - err := s.cryptConn.SendPacket(append(buffer, []byte{0x00, 0x10}...)) - if err != nil { - s.logger.Warn("Failed to send packet") - } - buffer = buffer[:0] + err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...)) + if err != nil { + s.logger.Warn("Failed to send packet") } time.Sleep(time.Duration(_config.ErupeConfig.LoopDelay) * time.Millisecond) } From ab7bb0d004837cdd95fa63f1a94823293222e0a8 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 9 Nov 2024 17:39:14 +1100 Subject: [PATCH 32/51] revert packet concatenation --- server/channelserver/sys_session.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index d7f41c071..4c05d2c5f 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -158,9 +158,12 @@ func (s *Session) sendLoop() { if s.closed { return } - 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(time.Duration(_config.ErupeConfig.LoopDelay) * time.Millisecond) } From 4eed6a9738248bf5df15af79ddcbd2cc47f60612 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 9 Nov 2024 18:20:49 +1100 Subject: [PATCH 33/51] add playtime chat command --- config.json | 5 +++++ server/channelserver/handlers_cast_binary.go | 7 +++++++ server/channelserver/handlers_character.go | 7 +++++++ server/channelserver/handlers_data.go | 3 +++ server/channelserver/sys_language.go | 3 +++ server/channelserver/sys_session.go | 3 +++ 6 files changed, 28 insertions(+) diff --git a/config.json b/config.json index fc5b2a644..a1951e791 100644 --- a/config.json +++ b/config.json @@ -170,6 +170,11 @@ "Enabled": true, "Description": "Toggle the Quest timer", "Prefix": "timer" + }, { + "Name": "Playtime", + "Enabled": true, + "Description": "Show your playtime", + "Prefix": "playtime" } ], "Courses": [ diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 17815dc30..e43ff3b1d 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -407,6 +407,13 @@ func parseChatCommand(s *Session, command string) { } else { sendDisabledCommandMessage(s, commands["Discord"]) } + case commands["Playtime"].Prefix: + if commands["Playtime"].Enabled || s.isOp() { + playtime := s.playtime + uint32(time.Now().Sub(s.playtimeTime).Seconds()) + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.playtime, playtime/60/60, playtime/60%60, playtime%60)) + } else { + sendDisabledCommandMessage(s, commands["Playtime"]) + } case commands["Help"].Prefix: if commands["Help"].Enabled || s.isOp() { for _, command := range commands { diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 795fc05e1..8672b94a5 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -23,6 +23,7 @@ const ( pGalleryData // +1748 pToreData // +240 pGardenData // +68 + pPlaytime // +4 pWeaponType // +1 pWeaponID // +2 pHR // +2 @@ -45,6 +46,7 @@ type CharacterSaveData struct { GalleryData []byte ToreData []byte GardenData []byte + Playtime uint32 WeaponType uint8 WeaponID uint16 HR uint16 @@ -59,6 +61,7 @@ func getPointers() map[SavePointer]int { pointers := map[SavePointer]int{pGender: 81, lBookshelfData: 5576} switch _config.ErupeConfig.RealClientMode { case _config.ZZ: + pointers[pPlaytime] = 128356 pointers[pWeaponID] = 128522 pointers[pWeaponType] = 128789 pointers[pHouseTier] = 129900 @@ -74,6 +77,7 @@ func getPointers() map[SavePointer]int { 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.G2, _config.G1: + pointers[pPlaytime] = 92356 pointers[pWeaponID] = 92522 pointers[pWeaponType] = 92789 pointers[pHouseTier] = 93900 @@ -87,6 +91,7 @@ func getPointers() map[SavePointer]int { pointers[pRP] = 106614 pointers[pKQF] = 110720 case _config.F5, _config.F4: + pointers[pPlaytime] = 60356 pointers[pWeaponID] = 60522 pointers[pWeaponType] = 60789 pointers[pHouseTier] = 61900 @@ -98,6 +103,7 @@ func getPointers() map[SavePointer]int { pointers[pGardenData] = 74424 pointers[pRP] = 74614 case _config.S6: + pointers[pPlaytime] = 12356 pointers[pWeaponID] = 12522 pointers[pWeaponType] = 12789 pointers[pHouseTier] = 13900 @@ -231,6 +237,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { 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.Playtime = binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pPlaytime] : save.Pointers[pPlaytime]+4]) save.WeaponType = save.decompSave[save.Pointers[pWeaponType]] save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+2]) diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index fd41e1366..0d41c42ca 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -54,6 +54,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { } characterSaveData.updateStructWithSaveData() + s.playtime = characterSaveData.Playtime + s.playtimeTime = time.Now() + // Bypass name-checker if new if characterSaveData.IsNewCharacter == true { s.Name = characterSaveData.Name diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index aae8706bb..6f24b6f32 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -10,6 +10,7 @@ type i18n struct { noOp string disabled string reload string + playtime string kqf struct { get string set struct { @@ -175,6 +176,8 @@ func getLangStrings(s *Server) i18n { 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.playtime = "Playtime: %d hours %d minutes %d seconds" + 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" diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 4c05d2c5f..a13d0d4ce 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -50,6 +50,9 @@ type Session struct { kqf []byte kqfOverride bool + playtime uint32 + playtimeTime time.Time + semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet. semaphoreMode bool semaphoreID []uint16 From b3305d1185a28f9e1daa5fb4ee2d5a3ec8b7d62d Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 15 Jan 2025 00:38:12 +1100 Subject: [PATCH 34/51] Update handlers_cafe.go --- server/channelserver/handlers_cafe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index 406732371..d5296fc37 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -166,7 +166,7 @@ func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) { FROM characters ch WHERE ch.id = $1 ) >= time_req`, s.charID, TimeAdjusted().Unix()-s.sessionStart) - if err != nil { + if err != nil || !mhfcourse.CourseExists(30, s.courses) { doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } else { for rows.Next() { From d1dfc3fbb1ce0af290a093fe49e24e6bdce0860d Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 18 Feb 2025 03:12:09 +1100 Subject: [PATCH 35/51] packet queue fix proposal --- server/channelserver/handlers.go | 4 +-- server/channelserver/handlers_cast_binary.go | 10 +++--- server/channelserver/handlers_mail.go | 2 +- server/channelserver/handlers_register.go | 4 +-- server/channelserver/handlers_stage.go | 7 ++-- server/channelserver/sys_session.go | 38 +++++++++++--------- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index c0d141f15..da357700d 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -88,7 +88,7 @@ func updateRights(s *Session) { Rights: s.courses, UnkSize: 0, } - s.QueueSendMHF(update) + s.QueueSendMHFNonBlocking(update) } func handleMsgHead(s *Session, p mhfpacket.MHFPacket) {} @@ -192,7 +192,7 @@ func logoutPlayer(s *Session) { for _, sess := range s.server.sessions { for rSlot := range stage.reservedClientSlots { if sess.charID == rSlot && sess.stage != nil && sess.stage.id[3:5] != "Qs" { - sess.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) + sess.QueueSendMHFNonBlocking(&mhfpacket.MsgSysStageDestruct{}) } } } diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index e43ff3b1d..a3f2ecfb6 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -82,7 +82,7 @@ func sendServerChatMessage(s *Session, message string) { RawDataPayload: bf.Data(), } - s.QueueSendMHF(castedBin) + s.QueueSendMHFNonBlocking(castedBin) } func parseChatCommand(s *Session, command string) { @@ -198,7 +198,7 @@ func parseChatCommand(s *Session, command string) { temp.Build(deleteNotif, s.clientContext) } deleteNotif.WriteUint16(uint16(network.MSG_SYS_END)) - s.QueueSend(deleteNotif.Data()) + s.QueueSendNonBlocking(deleteNotif.Data()) time.Sleep(500 * time.Millisecond) reloadNotif := byteframe.NewByteFrame() for _, session := range s.server.sessions { @@ -233,7 +233,7 @@ func parseChatCommand(s *Session, command string) { temp.Build(reloadNotif, s.clientContext) } reloadNotif.WriteUint16(uint16(network.MSG_SYS_END)) - s.QueueSend(reloadNotif.Data()) + s.QueueSendNonBlocking(reloadNotif.Data()) } else { sendDisabledCommandMessage(s, commands["Reload"]) } @@ -381,7 +381,7 @@ func parseChatCommand(s *Session, command string) { payload.WriteInt16(int16(x)) // X payload.WriteInt16(int16(y)) // Y payloadBytes := payload.Data() - s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{ + s.QueueSendMHFNonBlocking(&mhfpacket.MsgSysCastedBinary{ CharID: s.charID, MessageType: BinaryMessageTypeState, RawDataPayload: payloadBytes, @@ -539,7 +539,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { char := s.server.FindSessionByCharID(targetID) if char != nil { - char.QueueSendMHF(resp) + char.QueueSendMHFNonBlocking(resp) } } default: diff --git a/server/channelserver/handlers_mail.go b/server/channelserver/handlers_mail.go index a596d927b..41f721a1e 100644 --- a/server/channelserver/handlers_mail.go +++ b/server/channelserver/handlers_mail.go @@ -185,7 +185,7 @@ func SendMailNotification(s *Session, m *Mail, recipient *Session) { castedBinary.Build(bf, s.clientContext) - recipient.QueueSendMHF(castedBinary) + recipient.QueueSendMHFNonBlocking(castedBinary) } func getCharacterName(s *Session, charID uint32) string { diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index 3d47c3633..895e1c096 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -129,12 +129,12 @@ func (s *Session) notifyRavi() { raviNotif.WriteUint16(0x0010) // End it. if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente { for session := range sema.clients { - session.QueueSend(raviNotif.Data()) + session.QueueSendNonBlocking(raviNotif.Data()) } } else { for session := range sema.clients { if session.charID == s.charID { - session.QueueSend(raviNotif.Data()) + session.QueueSendNonBlocking(raviNotif.Data()) } } } diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 8a1abbc35..d0468fb8c 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -59,7 +59,7 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) { s.Unlock() // Tell the client to cleanup its current stage objects. - s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{}) + s.QueueSendMHFNonBlocking(&mhfpacket.MsgSysCleanupObject{}) // Confirm the stage entry. doAckSimpleSucceed(s, ackHandle, []byte{0x00, 0x00, 0x00, 0x00}) @@ -112,9 +112,8 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) { s.stage.RUnlock() } - newNotif.WriteUint16(0x0010) // End it. if len(newNotif.Data()) > 2 { - s.QueueSend(newNotif.Data()) + s.QueueSendNonBlocking(newNotif.Data()) } } @@ -238,7 +237,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.QueueSendMHFNonBlocking(&mhfpacket.MsgSysStageDestruct{}) } } diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index a13d0d4ce..93bb359b1 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -104,22 +104,9 @@ func (s *Session) Start() { // QueueSend queues a packet (raw []byte) to be sent. func (s *Session) QueueSend(data []byte) { s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name) - select { - case s.sendPackets <- packet{data, false}: - // Enqueued data - default: - s.logger.Warn("Packet queue too full, flushing!") - var tempPackets []packet - for len(s.sendPackets) > 0 { - tempPacket := <-s.sendPackets - if !tempPacket.nonBlocking { - tempPackets = append(tempPackets, tempPacket) - } - } - for _, tempPacket := range tempPackets { - s.sendPackets <- tempPacket - } - s.sendPackets <- packet{data, false} + err := s.cryptConn.SendPacket(append(data, []byte{0x00, 0x10}...)) + if err != nil { + s.logger.Warn("Failed to send packet") } } @@ -146,6 +133,19 @@ func (s *Session) QueueSendMHF(pkt mhfpacket.MHFPacket) { s.QueueSend(bf.Data()) } +// QueueSendMHFNonBlocking queues a MHFPacket to be sent, dropping the packet entirely if the queue is full. +func (s *Session) QueueSendMHFNonBlocking(pkt mhfpacket.MHFPacket) { + // Make the header + bf := byteframe.NewByteFrame() + bf.WriteUint16(uint16(pkt.Opcode())) + + // Build the packet onto the byteframe. + pkt.Build(bf, s.clientContext) + + // Queue it. + s.QueueSendNonBlocking(bf.Data()) +} + // QueueAck is a helper function to queue an MSG_SYS_ACK with the given ack handle and data. func (s *Session) QueueAck(ackHandle uint32, data []byte) { bf := byteframe.NewByteFrame() @@ -158,12 +158,16 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) { func (s *Session) sendLoop() { var pkt packet for { + var buf []byte if s.closed { return } for len(s.sendPackets) > 0 { pkt = <-s.sendPackets - err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...)) + buf = append(buf, pkt.data...) + } + if len(buf) > 0 { + err := s.cryptConn.SendPacket(append(buf, []byte{0x00, 0x10}...)) if err != nil { s.logger.Warn("Failed to send packet") } From 7c61f70590d39de1fc7a7c86da5717e270a0eea9 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 18 Feb 2025 03:16:18 +1100 Subject: [PATCH 36/51] add invalidateSessions --- server/channelserver/sys_channel_server.go | 11 +++++++++++ server/channelserver/sys_session.go | 4 ---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index a1b2c093b..6b6404f52 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -207,6 +207,7 @@ func (s *Server) Start() error { go s.acceptClients() go s.manageSessions() + go s.invalidateSessions() // Start the discord bot for chat integration. if s.erupeConfig.Discord.Enabled && s.discordBot != nil { @@ -278,6 +279,16 @@ func (s *Server) manageSessions() { } } +func (s *Server) invalidateSessions() { + for _, session := range s.sessions { + if time.Now().Add(-30 * time.Second).After(session.lastPacket) { + s.logger.Info("Session timeout", zap.String("Name", session.Name)) + logoutPlayer(session) + } + } + time.Sleep(30 * time.Second) +} + // BroadcastMHF queues a MHFPacket to be sent to all sessions. func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) { // Broadcast the data. diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index a13d0d4ce..7bd2a42ce 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -174,10 +174,6 @@ func (s *Session) sendLoop() { func (s *Session) recvLoop() { for { - if time.Now().Add(-30 * time.Second).After(s.lastPacket) { - logoutPlayer(s) - return - } if s.closed { logoutPlayer(s) return From 51ae16541f5c68702d12109e1680b896059afdcf Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 18 Feb 2025 03:18:54 +1100 Subject: [PATCH 37/51] update workflow --- .github/workflows/go.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e69ee1705..f365508e8 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,16 +10,17 @@ on: - 'go.mod' - 'go.sum' - 'main.go' + - '.github/workflows/go.yml' jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: '1.21' From 0bf39b9caf83308215c1c83860078f133629daa5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:19:53 +0000 Subject: [PATCH 38/51] Bump golang.org/x/net from 0.23.0 to 0.33.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.33.0. - [Commits](https://github.com/golang/net/compare/v0.23.0...v0.33.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2d0a7e433..f8a430ebd 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,9 @@ 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.21.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa - golang.org/x/text v0.14.0 + golang.org/x/text v0.21.0 ) require ( @@ -31,8 +31,8 @@ require ( 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/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.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 d85d69d60..e707b912a 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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 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= @@ -289,8 +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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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= @@ -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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.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= @@ -356,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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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= From d17d97fefc1a441480e3301ee3cedb11d4630427 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 18 Feb 2025 03:20:43 +1100 Subject: [PATCH 39/51] update workflow --- .github/workflows/go.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f365508e8..96c9b083f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -28,7 +28,7 @@ jobs: run: env GOOS=linux GOARCH=amd64 go build -v - name: Upload Linux-amd64 artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Linux-amd64 path: | @@ -43,7 +43,7 @@ jobs: run: env GOOS=windows GOARCH=amd64 go build -v - name: Upload Windows-amd64 artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Windows-amd64 path: | From ba04b79bd8c4548f0efb88046867ed71e7ad98cb Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 23 Feb 2025 19:29:02 +1100 Subject: [PATCH 40/51] partially revert guildMembers query --- server/channelserver/handlers_guild_member.go | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/server/channelserver/handlers_guild_member.go b/server/channelserver/handlers_guild_member.go index f4cb04175..436a6e6cb 100644 --- a/server/channelserver/handlers_guild_member.go +++ b/server/channelserver/handlers_guild_member.go @@ -61,35 +61,41 @@ func (gm *GuildMember) Save(s *Session) error { } const guildMembersSelectSQL = ` -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.hr, - 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 +SELECT + COALESCE(g.id, 0) 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.hr, + c.gr, + c.weapon_id, + c.weapon_type, + CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader, + character.is_applicant + 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 FROM guild_characters gc - LEFT JOIN characters c ON c.id = gc.character_id - LEFT JOIN guilds g ON g.id = gc.guild_id -) AS subquery + ) character + JOIN characters c on character.character_id = c.id + LEFT JOIN guild_characters gc ON gc.character_id = character.character_id + LEFT JOIN guilds g ON g.id = gc.guild_id ` func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) { rows, err := s.server.db.Queryx(fmt.Sprintf(` %s - WHERE guild_id = $1 AND is_applicant = $2 + WHERE character.guild_id = $1 AND is_applicant = $2 `, guildMembersSelectSQL), guildID, applicants) if err != nil { @@ -115,7 +121,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_id=$1", guildMembersSelectSQL), charID) + rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID) if err != nil { s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID)) From 5028355cfcf3f2ad9f8fb4e65138c678e069594d Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 23 Feb 2025 21:46:00 +1100 Subject: [PATCH 41/51] prevent nil pointer in MhfGetGuildManageRight --- server/channelserver/handlers_guild.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 096419137..57ad0acf5 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1526,7 +1526,7 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGuildManageRight) guild, err := GetGuildInfoByCharacterId(s, s.charID) - if guild == nil && s.prevGuildID != 0 { + if guild == nil || s.prevGuildID != 0 { guild, err = GetGuildInfoByID(s, s.prevGuildID) s.prevGuildID = 0 if guild == nil || err != nil { From 3c0d29ed41ec4fc05069704897cd9a9c2ab59e62 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 27 Feb 2025 20:06:05 +1100 Subject: [PATCH 42/51] fix invalidateSessions --- server/channelserver/sys_channel_server.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 6b6404f52..32ec832c8 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -280,13 +280,18 @@ func (s *Server) manageSessions() { } func (s *Server) invalidateSessions() { - for _, session := range s.sessions { - if time.Now().Add(-30 * time.Second).After(session.lastPacket) { - s.logger.Info("Session timeout", zap.String("Name", session.Name)) - logoutPlayer(session) + for { + if s.isShuttingDown { + break } + for _, sess := range s.sessions { + if time.Now().Sub(sess.lastPacket) > time.Second*time.Duration(30) { + s.logger.Info("session timeout", zap.String("Name", sess.Name)) + logoutPlayer(sess) + } + } + time.Sleep(time.Second * 10) } - time.Sleep(30 * time.Second) } // BroadcastMHF queues a MHFPacket to be sent to all sessions. From 3e71c308f4759f46b28c9f99d7222adedefd11c7 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 6 Mar 2025 23:01:22 +1100 Subject: [PATCH 43/51] minor MhfInfoGuild changes --- server/channelserver/handlers_guild.go | 32 +++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 57ad0acf5..30913d482 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -971,14 +971,21 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(0) bf.WriteUint8(0) - bf.WriteBool(!guild.Recruiting) + flags := uint8(0) + if !guild.Recruiting { + flags |= 0x01 + } + //if guild.Suspended { + // flags |= 0x02 + //} + bf.WriteUint8(flags) if characterGuildData == nil || characterGuildData.IsApplicant { - bf.WriteUint16(0x00) + bf.WriteUint16(0) } else if guild.LeaderCharID == s.charID { - bf.WriteUint16(0x01) + bf.WriteUint16(1) } else { - bf.WriteUint16(0x02) + bf.WriteUint16(2) } bf.WriteUint32(uint32(guild.CreatedAt.Unix())) @@ -1103,15 +1110,18 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { } } - type UnkGuildInfo struct { - Unk0 uint8 + type Activity struct { + Pass uint8 Unk1 uint8 Unk2 uint8 } - unkGuildInfo := []UnkGuildInfo{} - bf.WriteUint8(uint8(len(unkGuildInfo))) - for _, info := range unkGuildInfo { - bf.WriteUint8(info.Unk0) + activity := []Activity{ + // 1,0,0 = ok + // 0,0,0 = ng + } + bf.WriteUint8(uint8(len(activity))) + for _, info := range activity { + bf.WriteUint8(info.Pass) bf.WriteUint8(info.Unk1) bf.WriteUint8(info.Unk2) } @@ -1159,7 +1169,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } else { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 5)) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) } } From f2862ea4b8acb7392c229ea4918b1ee0268e5216 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 8 Mar 2025 11:41:57 +1100 Subject: [PATCH 44/51] prevent concurrent map write to questCache --- server/channelserver/handlers_quest.go | 2 ++ server/channelserver/sys_channel_server.go | 1 + 2 files changed, 3 insertions(+) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index a36c8116a..0739f1b07 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -238,8 +238,10 @@ func loadQuestFile(s *Session, questId int) []byte { } questBody.WriteBytes(newStrings.Data()) + s.server.questCacheLock.Lock() s.server.questCacheData[questId] = questBody.Data() s.server.questCacheTime[questId] = time.Now() + s.server.questCacheLock.Unlock() return questBody.Data() } diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 32ec832c8..f62db7e34 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -75,6 +75,7 @@ type Server struct { raviente *Raviente + questCacheLock sync.RWMutex questCacheData map[int][]byte questCacheTime map[int]time.Time } From 8c219be30f82d3bb02dd6fb2451a0577c2adbf6f Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 10 Mar 2025 11:38:00 +1100 Subject: [PATCH 45/51] fix InfoGuild response on = _config.G10 { + bf.WriteUint16(applicant.GR) + } ps.Uint8(bf, applicant.Name, true) } } From 69a2a7ca3b02a27963fab90396681c6f5b08c8d1 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 16 Mar 2025 15:51:10 +1100 Subject: [PATCH 46/51] MhfReadMercenaryW changes --- server/channelserver/handlers_mercenary.go | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index 611096045..7d92a7d86 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -174,9 +174,9 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(0) } - var loans uint8 - temp := byteframe.NewByteFrame() - if pkt.Op < 2 { + if pkt.Op != 2 && pkt.Op != 5 { + var loans uint8 + temp := byteframe.NewByteFrame() rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID) for rows.Next() { err := rows.Scan(&name, &cid, &pactID) @@ -190,23 +190,23 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) { temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix())) temp.WriteBytes(stringsupport.PaddedString(name, 18, true)) } - } - bf.WriteUint8(loans) - bf.WriteBytes(temp.Data()) + bf.WriteUint8(loans) + bf.WriteBytes(temp.Data()) - if pkt.Op < 1 { - var data []byte - var gcp uint32 - s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data) - s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp) + if pkt.Op != 1 && pkt.Op != 4 { + var data []byte + var gcp uint32 + s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data) + s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp) - if len(data) == 0 { - bf.WriteBool(false) - } else { - bf.WriteBool(true) - bf.WriteBytes(data) + if len(data) == 0 { + bf.WriteBool(false) + } else { + bf.WriteBool(true) + bf.WriteBytes(data) + } + bf.WriteUint32(gcp) } - bf.WriteUint32(gcp) } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) From c539905c1dfd84ab75d7e4e1d943e9c76755bb2c Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 16 Mar 2025 23:04:42 +1100 Subject: [PATCH 47/51] implement SysWaitStageBinary timeout --- server/channelserver/handlers_stage.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index d0468fb8c..64a1153ef 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -361,7 +361,7 @@ func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, []byte{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) return } - for { + for i := 0; i < 10; i++ { s.logger.Debug("MsgSysWaitStageBinary before lock and get stage") stage.Lock() stageBinary, gotBinary := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}] @@ -369,13 +369,15 @@ func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) { s.logger.Debug("MsgSysWaitStageBinary after lock and get stage") if gotBinary { doAckBufSucceed(s, pkt.AckHandle, stageBinary) - break + return } else { s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1)) time.Sleep(1 * time.Second) continue } } + s.logger.Warn("MsgSysWaitStageBinary stage binary timeout") + doAckBufSucceed(s, pkt.AckHandle, []byte{}) } else { s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID)) } From 3d0114ce22ff7db977940776c32eac1d4f4941ad Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 1 Apr 2025 20:58:33 +1100 Subject: [PATCH 48/51] fix MhfAcquireCafeItem cost in G1-G5.2 --- network/mhfpacket/msg_mhf_acquire_cafe_item.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/mhfpacket/msg_mhf_acquire_cafe_item.go b/network/mhfpacket/msg_mhf_acquire_cafe_item.go index 04e603a44..9122d2796 100644 --- a/network/mhfpacket/msg_mhf_acquire_cafe_item.go +++ b/network/mhfpacket/msg_mhf_acquire_cafe_item.go @@ -31,7 +31,7 @@ func (m *MsgMhfAcquireCafeItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl m.ItemType = bf.ReadUint16() m.ItemID = bf.ReadUint16() m.Quant = bf.ReadUint16() - if _config.ErupeConfig.RealClientMode >= _config.G1 { + if _config.ErupeConfig.RealClientMode >= _config.G6 { m.PointCost = bf.ReadUint32() } else { m.PointCost = uint32(bf.ReadUint16()) From ef3212da119ec44e4a26d1849f6e2b6e60eb249f Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 10 Apr 2025 23:33:08 +1000 Subject: [PATCH 49/51] Rename fix-weekly-stamps.sql to 24-fix-weekly-stamps.sql --- .../{fix-weekly-stamps.sql => 24-fix-weekly-stamps.sql} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename schemas/patch-schema/{fix-weekly-stamps.sql => 24-fix-weekly-stamps.sql} (96%) diff --git a/schemas/patch-schema/fix-weekly-stamps.sql b/schemas/patch-schema/24-fix-weekly-stamps.sql similarity index 96% rename from schemas/patch-schema/fix-weekly-stamps.sql rename to schemas/patch-schema/24-fix-weekly-stamps.sql index 30f551e5c..88825345f 100644 --- a/schemas/patch-schema/fix-weekly-stamps.sql +++ b/schemas/patch-schema/24-fix-weekly-stamps.sql @@ -3,4 +3,4 @@ BEGIN; ALTER TABLE IF EXISTS public.stamps RENAME hl_next TO hl_checked; ALTER TABLE IF EXISTS public.stamps RENAME ex_next TO ex_checked; -END; \ No newline at end of file +END; From ee7b099deb23786fa6a2bf455fb1c363830a0f79 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 10 Apr 2025 23:33:40 +1000 Subject: [PATCH 50/51] Create 25-fix-rasta-id.sql --- schemas/patch-schema/25-fix-rasta-id.sql | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 schemas/patch-schema/25-fix-rasta-id.sql diff --git a/schemas/patch-schema/25-fix-rasta-id.sql b/schemas/patch-schema/25-fix-rasta-id.sql new file mode 100644 index 000000000..6de8bb6f4 --- /dev/null +++ b/schemas/patch-schema/25-fix-rasta-id.sql @@ -0,0 +1,5 @@ +BEGIN; + +CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq; + +END; From d83c784452d7a7cbaaa0151007f4493f9ac8fa59 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 10 Apr 2025 23:35:59 +1000 Subject: [PATCH 51/51] Create 26-fix-mail.sql --- schemas/patch-schema/26-fix-mail.sql | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 schemas/patch-schema/26-fix-mail.sql diff --git a/schemas/patch-schema/26-fix-mail.sql b/schemas/patch-schema/26-fix-mail.sql new file mode 100644 index 000000000..358ab17e6 --- /dev/null +++ b/schemas/patch-schema/26-fix-mail.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE mail ADD COLUMN IF NOT EXISTS is_sys_message BOOLEAN NOT NULL DEFAULT false; + +END;