From e5703617bb09ceba1c7bfe88d6ca993a8a858ba1 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 28 Feb 2024 19:29:06 +1100 Subject: [PATCH 01/10] add support for Clan Changing Room --- .../patch-schema/22-clan-changing-room.sql | 6 ++++ server/channelserver/handlers_guild.go | 35 +++++++++++++------ 2 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 schemas/patch-schema/22-clan-changing-room.sql diff --git a/schemas/patch-schema/22-clan-changing-room.sql b/schemas/patch-schema/22-clan-changing-room.sql new file mode 100644 index 000000000..4af9ef18a --- /dev/null +++ b/schemas/patch-schema/22-clan-changing-room.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE guilds ADD COLUMN IF NOT EXISTS room_rp INT DEFAULT 0; +ALTER TABLE guilds ADD COLUMN IF NOT EXISTS room_expiry TIMESTAMP WITHOUT TIME ZONE; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 5613bd53b..4c5ed1ab5 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -51,6 +51,8 @@ type Guild struct { MemberCount uint16 `db:"member_count"` RankRP uint32 `db:"rank_rp"` EventRP uint32 `db:"event_rp"` + RoomRP uint16 `db:"room_rp"` + RoomExpiry time.Time `db:"room_expiry"` Comment string `db:"comment"` PugiName1 string `db:"pugi_name_1"` PugiName2 string `db:"pugi_name_2"` @@ -153,6 +155,8 @@ SELECT g.name, rank_rp, event_rp, + room_rp, + COALESCE(room_expiry, '1970-01-01') AS room_expiry, main_motto, sub_motto, created_at, @@ -706,7 +710,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint32(uint32(response)) case mhfpacket.OperateGuildDonateRank: - bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, false)) + bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, 0)) case mhfpacket.OperateGuildSetApplicationDeny: s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID) case mhfpacket.OperateGuildSetApplicationAllow: @@ -747,10 +751,11 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { // TODO: This doesn't implement blocking, if someone unlocked the same outfit at the same time s.server.db.Exec(`UPDATE guilds SET pugi_outfits=pugi_outfits+$1 WHERE id=$2`, int(math.Pow(float64(pkt.Data1.ReadUint32()), 2)), guild.ID) case mhfpacket.OperateGuildDonateRoom: - // TODO: Where does this go? + quantity := uint16(pkt.Data1.ReadUint32()) + bf.WriteBytes(handleDonateRP(s, quantity, guild, 2)) case mhfpacket.OperateGuildDonateEvent: quantity := uint16(pkt.Data1.ReadUint32()) - bf.WriteBytes(handleDonateRP(s, quantity, guild, true)) + bf.WriteBytes(handleDonateRP(s, quantity, guild, 1)) // TODO: Move this value onto rp_yesterday and reset to 0... daily? s.server.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, quantity, s.charID) case mhfpacket.OperateGuildEventExchange: @@ -794,7 +799,7 @@ func handleChangePugi(s *Session, outfit uint8, guild *Guild, num int) { guild.Save(s) } -func handleDonateRP(s *Session, amount uint16, guild *Guild, isEvent bool) []byte { +func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte { bf := byteframe.NewByteFrame() bf.WriteUint32(0) saveData, err := GetCharacterSaveData(s, s.charID) @@ -803,11 +808,21 @@ func handleDonateRP(s *Session, amount uint16, guild *Guild, isEvent bool) []byt } saveData.RP -= amount saveData.Save(s) - updateSQL := "UPDATE guilds SET rank_rp = rank_rp + $1 WHERE id = $2" - if isEvent { - updateSQL = "UPDATE guilds SET event_rp = event_rp + $1 WHERE id = $2" + switch _type { + case 0: + s.server.db.Exec(`UPDATE guilds SET rank_rp = rank_rp + $1 WHERE id = $2`, amount, guild.ID) + case 1: + s.server.db.Exec(`UPDATE guilds SET event_rp = event_rp + $1 WHERE id = $2`, amount, guild.ID) + case 2: + var currentRP uint16 + s.server.db.QueryRow(`SELECT room_rp FROM guilds WHERE id = $1`, guild.ID).Scan(¤tRP) + if currentRP+amount >= 30 { + s.server.db.Exec(`UPDATE guilds SET room_rp = 0 WHERE id = $1`, guild.ID) + s.server.db.Exec(`UPDATE guilds SET room_expiry = $1 WHERE id = $2`, TimeAdjusted().Add(time.Hour*24*7), guild.ID) + } else { + s.server.db.Exec(`UPDATE guilds SET room_rp = room_rp + $1 WHERE id = $2`, amount, guild.ID) + } } - s.server.db.Exec(updateSQL, amount, guild.ID) bf.Seek(0, 0) bf.WriteUint32(uint32(saveData.RP)) return bf.Data() @@ -1001,8 +1016,8 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(limit) bf.WriteUint32(55000) - bf.WriteUint32(0) - bf.WriteUint16(0) // Changing Room RP + bf.WriteUint32(uint32(guild.RoomExpiry.Unix())) + bf.WriteUint16(guild.RoomRP) bf.WriteUint16(0) // Ignored if guild.AllianceID > 0 { From 846f8d669347d37e93bb3e3cea855c82d01bf6e6 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 28 Feb 2024 21:24:55 +1100 Subject: [PATCH 02/10] retain excess Room RP --- server/channelserver/handlers_guild.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 4c5ed1ab5..096419137 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -806,6 +806,15 @@ func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte { if err != nil { return bf.Data() } + var resetRoom bool + if _type == 2 { + var currentRP uint16 + s.server.db.QueryRow(`SELECT room_rp FROM guilds WHERE id = $1`, guild.ID).Scan(¤tRP) + if currentRP+amount >= 30 { + amount = 30 - currentRP + resetRoom = true + } + } saveData.RP -= amount saveData.Save(s) switch _type { @@ -814,9 +823,7 @@ func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte { case 1: s.server.db.Exec(`UPDATE guilds SET event_rp = event_rp + $1 WHERE id = $2`, amount, guild.ID) case 2: - var currentRP uint16 - s.server.db.QueryRow(`SELECT room_rp FROM guilds WHERE id = $1`, guild.ID).Scan(¤tRP) - if currentRP+amount >= 30 { + if resetRoom { s.server.db.Exec(`UPDATE guilds SET room_rp = 0 WHERE id = $1`, guild.ID) s.server.db.Exec(`UPDATE guilds SET room_expiry = $1 WHERE id = $2`, TimeAdjusted().Add(time.Hour*24*7), guild.ID) } else { From 734a982689a8c5528f5f2b6a075d2797f381095c Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Mar 2024 21:37:25 +1100 Subject: [PATCH 03/10] proofreading --- AUTHORS.md | 19 ++++++++++--------- README.md | 12 ++++++------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 618d34264..5617c5308 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,29 +1,30 @@ -# List of AUTHORS who contributed over time to the Erupe project +# List of authors who contributed to Erupe ## Point of current development The project is currently developed under https://github.com/ZeruLight/Erupe ## History of development Development of this project dates back to 2019, and was developed under various umbrellas over time: -* Cappuccino (Fist/Ando/Ellie42) (The Erupe Developers), 2019-2020 (https://github.com/Ellie42/Erupe / https://github.com/ricochhet/Erupe-Legacy) (Still active closed source) +* Cappuccino (Fist/Ando/Ellie42) ("The Erupe Developers"), 2019-2020 (https://github.com/Ellie42/Erupe / https://github.com/ricochhet/Erupe-Legacy) (Still active closed source) * Einherjar Team, ????-2022 Feb (There is no git history for this period, this team's work was taken and used as a foundation for future repositories) * Community Edition, 2022 (https://github.com/xl3lackout/Erupe) -* Zerulight, 2022-2023 (https://github.com/ZeruLight/Erupe) +* sekaiwish Fork, 2022 (https://github.com/sekaiwish/Erupe) +* ZeruLight, 2022-2023 (https://github.com/ZeruLight/Erupe) ## Authorship of the code Authorship is assigned for each commit within the git history, which is stored in these git repos: * https://github.com/ZeruLight/Erupe -* https://github.com/Ellie42/Erupe +* https://github.com/Ellie42/Erupe * https://github.com/ricochhet/Erupe-Legacy * https://github.com/xl3lackout/Erupe - -Note there is a divergence between Ellie42s branch and xl3lackout where history has been lost. -Unfortunately, we have no detailed information on the history of the Erupe pre-2022 -if somebody can provide information, please contact us, so that we can make this history available. +Note the divergence between Ellie42's branch and xl3lackout's where history has been lost. + +Unfortunately, we have no detailed information on the history of Erupe before 2022. +If somebody can provide information, please contact us, so that we can make this history available. ## Exceptions with third-party libraries The third-party libraries have their own way of addressing authorship and the authorship of commits importing/updating a third-party library reflects who did the importing instead of who wrote the code within the commit. -The Authors of third-party libraries are not explicitly mentioned, and usually is possible to obtain from the files belonging to the third-party libraries. \ No newline at end of file +The authors of third-party libraries are not explicitly mentioned, and usually is possible to obtain from the files belonging to the third-party libraries. \ No newline at end of file diff --git a/README.md b/README.md index b9be1fb05..8ac37b22b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Erupe -## Client Compatiblity +## Client Compatibility ### Platforms - PC - PlayStation 3 @@ -34,15 +34,15 @@ If you want to modify or compile Erupe yourself, please read on. ## Docker -Please see the readme in [docker/README.md](./docker/README.md). At the moment this is only really good for quick installs and checking out development not for production. +Please see [docker/README.md](./docker/README.md). This is intended for quick installs and development, not for production. ## Schemas We source control the following schemas: -- Initialisation Schemas: These initialise the application database to a clean install from a specific version. -- Update Schemas: These are update files they should be ran in order of version to get to the latest schema. -- Patch Schemas: These are for development and should be ran from the lastest available update schema or initial schema. These eventually get condensed into `Update Schemas` and then deleted when updated to a new version. -- Bundled Schemas: These are demo reference files to allow servers to be able to roll their own shops, distributions gachas and scenarios set ups. +- Initialization Schema: This initializes the application database to a specific version (9.1.0). +- Update Schemas: These are update files that should be ran on top of the initialization schema. +- Patch Schemas: These are for development and should be run after running all initialization and update schema. These get condensed into `Update Schemas` and deleted when updated to a new release. +- Bundled Schemas: These are demo reference files to give servers standard set-ups. Note: Patch schemas are subject to change! You should only be using them if you are following along with development. From d9479ea8635fe136450614aef3827fd6d347c149 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 2 Mar 2024 21:39:23 +1100 Subject: [PATCH 04/10] move initialization schema --- .../9.1-init.sql => init.sql} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename schemas/{initialisation-schema/9.1-init.sql => init.sql} (100%) diff --git a/schemas/initialisation-schema/9.1-init.sql b/schemas/init.sql similarity index 100% rename from schemas/initialisation-schema/9.1-init.sql rename to schemas/init.sql From fac68a2b4bb87a58f4a0a55ecce2d141d95e8121 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 6 Mar 2024 22:17:27 +1100 Subject: [PATCH 05/10] fix UpdateGuacot --- network/mhfpacket/msg_mhf_update_guacot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/mhfpacket/msg_mhf_update_guacot.go b/network/mhfpacket/msg_mhf_update_guacot.go index 729c84547..2afcbad5c 100644 --- a/network/mhfpacket/msg_mhf_update_guacot.go +++ b/network/mhfpacket/msg_mhf_update_guacot.go @@ -31,8 +31,8 @@ func (m *MsgMhfUpdateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien m.AckHandle = bf.ReadUint32() m.EntryCount = bf.ReadUint16() bf.ReadUint16() // Zeroed - var temp Goocoo for i := 0; i < int(m.EntryCount); i++ { + var temp Goocoo temp.Index = bf.ReadUint32() for j := 0; j < 22; j++ { temp.Data1 = append(temp.Data1, bf.ReadInt16()) From 89c1db4712b503b51d447918fac50805864e1464 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 9 Mar 2024 14:58:12 +1100 Subject: [PATCH 06/10] remove PS3 Patch Server default --- server/signserver/dsgn_resp.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 2f491e8c0..a8a85e1cb 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -38,25 +38,24 @@ func (s *Session) makeSignResponse(uid uint32) []byte { return bf.Data() } - bf.WriteUint8(uint8(SIGN_SUCCESS)) // resp_code - if (s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "") || s.client == PS3 { - bf.WriteUint8(2) - } else { - bf.WriteUint8(0) + if s.client == PS3 && (s.server.erupeConfig.PatchServerFile == "" || s.server.erupeConfig.PatchServerManifest == "") { + bf.WriteUint8(uint8(SIGN_EABORT)) + return bf.Data() } + + bf.WriteUint8(uint8(SIGN_SUCCESS)) + bf.WriteUint8(2) // patch server count bf.WriteUint8(1) // entrance server count bf.WriteUint8(uint8(len(chars))) bf.WriteUint32(tokenID) bf.WriteBytes([]byte(sessToken)) bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) if s.client == PS3 { - ps.Uint8(bf, fmt.Sprintf(`ps3-%s.zerulight.cc`, s.server.erupeConfig.Language), false) - ps.Uint8(bf, fmt.Sprintf(`ps3-%s.zerulight.cc`, s.server.erupeConfig.Language), false) + ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerManifest), false) + ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerManifest), false) } else { - if s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "" { - ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false) - ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false) - } + ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false) + ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false) } if strings.Split(s.rawConn.RemoteAddr().String(), ":")[0] == "127.0.0.1" { ps.Uint8(bf, fmt.Sprintf("127.0.0.1:%d", s.server.erupeConfig.Entrance.Port), false) From bfb22951f22eb4470e15f235acf213a0736c824e Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 10 Mar 2024 11:05:41 +1100 Subject: [PATCH 07/10] fix PatchServer response --- server/signserver/dsgn_resp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index a8a85e1cb..452b02475 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -52,7 +52,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte { bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) if s.client == PS3 { ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerManifest), false) - ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerManifest), false) + ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerFile), false) } else { ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false) ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false) From b08c41a886c61850c60b5969e3386f14b7a1e86b Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 10 Mar 2024 18:26:26 +1100 Subject: [PATCH 08/10] enforce Stage.maxPlayers on EnterStage --- server/channelserver/handlers_stage.go | 7 +++++++ server/channelserver/sys_stage.go | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index bc0b3689e..372b147ca 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -151,6 +151,13 @@ func removeSessionFromStage(s *Session) { func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) + if stage, exists := s.server.stages[pkt.StageID]; exists { + if len(stage.reservedClientSlots) == int(stage.maxPlayers) { + doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01}) + return + } + } + // Push our current stage ID to the movement stack before entering another one. if s.stage != nil { s.stage.Lock() diff --git a/server/channelserver/sys_stage.go b/server/channelserver/sys_stage.go index dbfcbb7c3..b0f94a09a 100644 --- a/server/channelserver/sys_stage.go +++ b/server/channelserver/sys_stage.go @@ -59,7 +59,7 @@ func NewStage(ID string) *Stage { objects: make(map[uint32]*Object), objectIndex: 0, rawBinaryData: make(map[stageBinaryKey][]byte), - maxPlayers: 4, + maxPlayers: 127, } return s } From 5284fe55cd46350173ea8c861fde3868a5f685d2 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 10 Mar 2024 18:33:55 +1100 Subject: [PATCH 09/10] enforce Stage.maxPlayers on EnterStage --- server/channelserver/handlers_stage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 372b147ca..33bc014fe 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -152,7 +152,7 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) if stage, exists := s.server.stages[pkt.StageID]; exists { - if len(stage.reservedClientSlots) == int(stage.maxPlayers) { + if len(stage.reservedClientSlots)+len(stage.clients) == int(stage.maxPlayers) { doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01}) return } From 19aadc6e10807c5cd2dec613a86d52a2c3cdf045 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 10 Mar 2024 19:50:21 +1100 Subject: [PATCH 10/10] enforce Stage.maxPlayers on MoveStage & BackStage --- server/channelserver/handlers_stage.go | 27 +++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 33bc014fe..e3196bc44 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -148,14 +148,19 @@ func removeSessionFromStage(s *Session) { destructEmptySemaphores(s) } +func isStageFull(s *Session, StageID string) bool { + if stage, exists := s.server.stages[StageID]; exists { + return len(stage.reservedClientSlots)+len(stage.clients) >= int(stage.maxPlayers) + } + return false +} + func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) - if stage, exists := s.server.stages[pkt.StageID]; exists { - if len(stage.reservedClientSlots)+len(stage.clients) == int(stage.maxPlayers) { - doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01}) - return - } + if isStageFull(s, pkt.StageID) { + doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01}) + return } // Push our current stage ID to the movement stack before entering another one. @@ -182,6 +187,12 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { backStage = "sl1Ns200p0a0u0" } + if isStageFull(s, backStage) { + s.stageMoveStack.Push(backStage) + doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01}) + return + } + if _, exists := s.stage.reservedClientSlots[s.charID]; exists { delete(s.stage.reservedClientSlots, s.charID) } @@ -195,6 +206,12 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysMoveStage) + + if isStageFull(s, pkt.StageID) { + doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01}) + return + } + doStageTransfer(s, pkt.AckHandle, pkt.StageID) }