From 1e62e8bf96f65e954c9f58260ece71c495e8a6de Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 28 Jul 2022 15:34:50 +1000 Subject: [PATCH 01/51] achievements concept --- Erupe/achievements.sql | 41 ++++++ .../channelserver/handlers_achievement.go | 135 ++++++++---------- 2 files changed, 104 insertions(+), 72 deletions(-) create mode 100644 Erupe/achievements.sql diff --git a/Erupe/achievements.sql b/Erupe/achievements.sql new file mode 100644 index 000000000..5c24f9e89 --- /dev/null +++ b/Erupe/achievements.sql @@ -0,0 +1,41 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS public.achievements +( + id int NOT NULL, + ach0 int DEFAULT 0, + ach1 int DEFAULT 0, + ach2 int DEFAULT 0, + ach3 int DEFAULT 0, + ach4 int DEFAULT 0, + ach5 int DEFAULT 0, + ach6 int DEFAULT 0, + ach7 int DEFAULT 0, + ach8 int DEFAULT 0, + ach9 int DEFAULT 0, + ach10 int DEFAULT 0, + ach11 int DEFAULT 0, + ach12 int DEFAULT 0, + ach13 int DEFAULT 0, + ach14 int DEFAULT 0, + ach15 int DEFAULT 0, + ach16 int DEFAULT 0, + ach17 int DEFAULT 0, + ach18 int DEFAULT 0, + ach19 int DEFAULT 0, + ach20 int DEFAULT 0, + ach21 int DEFAULT 0, + ach22 int DEFAULT 0, + ach23 int DEFAULT 0, + ach24 int DEFAULT 0, + ach25 int DEFAULT 0, + ach26 int DEFAULT 0, + ach27 int DEFAULT 0, + ach28 int DEFAULT 0, + ach29 int DEFAULT 0, + ach30 int DEFAULT 0, + ach31 int DEFAULT 0, + ach32 int DEFAULT 0 +); + +END; \ No newline at end of file diff --git a/Erupe/server/channelserver/handlers_achievement.go b/Erupe/server/channelserver/handlers_achievement.go index 7887ab71d..b9197f6ea 100644 --- a/Erupe/server/channelserver/handlers_achievement.go +++ b/Erupe/server/channelserver/handlers_achievement.go @@ -5,83 +5,74 @@ import ( "erupe-ce/network/mhfpacket" ) +var achievementCurves = [][]uint32{ + // 0: HR weapon use, Class use, Tore dailies + {5, 15, 30, 50, 100, 150, 200, 250, 300}, + // 1: Weapon collector, G wep enhances + {1, 3, 5, 15, 30, 50, 75, 100, 150}, + // 2: Festa wins + {1, 2, 3, 4, 5, 6, 7, 8, 9}, + // 3: GR weapon use + {10, 50, 100, 200, 350, 500, 750, 1000, 1500}, + // 4: Armor refinement + {0, 5, 5, 5, 5, 5, 5, 5, 5}, + // 5: Sigil crafts + {0, 50, 50, 50, 50, 50, 50, 50, 50}, +} + +var achievementCurveMap = map[uint8][]uint32{ + 0: achievementCurves[0], 1: achievementCurves[0], 2: achievementCurves[0], 3: achievementCurves[0], + 4: achievementCurves[0], 5: achievementCurves[0], 6: achievementCurves[0], 7: achievementCurves[1], + 8: achievementCurves[2], 9: achievementCurves[0], 10: achievementCurves[0], 11: achievementCurves[0], + 12: achievementCurves[0], 13: achievementCurves[0], 14: achievementCurves[0], 15: achievementCurves[0], + 16: achievementCurves[3], 17: achievementCurves[3], 18: achievementCurves[3], 19: achievementCurves[3], + 20: achievementCurves[3], 21: achievementCurves[3], 22: achievementCurves[3], 23: achievementCurves[3], + 24: achievementCurves[3], 25: achievementCurves[3], 26: achievementCurves[3], 27: achievementCurves[1], + 28: achievementCurves[4], 29: achievementCurves[5], 30: achievementCurves[3], 31: achievementCurves[3], + 32: achievementCurves[3], +} + func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetAchievement) - achievementStruct := []struct { - ID uint8 // Main ID - Unk0 uint8 // always FF - Unk1 uint16 // 0x05 0x00 - Unk2 uint32 // 0x01 0x0A 0x05 0x00 - }{ - {ID: 0, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 1, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 2, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 3, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 4, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 5, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 6, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 7, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 8, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 9, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 10, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 11, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 12, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 13, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 14, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 15, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 16, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 17, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 18, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 19, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 20, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 21, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 22, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 23, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 24, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 25, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 26, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 27, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 28, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 29, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 30, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 31, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 32, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 33, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 34, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 35, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 36, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 37, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 38, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 39, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 40, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 41, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 42, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 43, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 44, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 45, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 46, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 47, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 48, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 49, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 50, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 51, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 52, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 53, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 54, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 55, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 56, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 57, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 58, Unk0: 0xFF, Unk1: 0, Unk2: 0}, - {ID: 59, Unk0: 0xFF, Unk1: 0, Unk2: 0}, + err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", s.charID) + if err != nil { + s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", s.charID) } + + scores := make([]int, 33) + s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", s.charID).Scan(&scores[0], &scores[0], + &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8], &scores[9], + &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16], &scores[17], + &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24], &scores[25], + &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32]) + resp := byteframe.NewByteFrame() - resp.WriteUint8(uint8(len(achievementStruct))) // Entry count - for _, entry := range achievementStruct { - resp.WriteUint8(entry.ID) - resp.WriteUint8(entry.Unk0) - resp.WriteUint16(entry.Unk1) - resp.WriteUint32(entry.Unk2) + points := uint32(69) + resp.WriteUint32(points) + resp.WriteUint32(points) + resp.WriteUint32(points) + resp.WriteUint32(points) + resp.WriteBytes([]byte{0x02, 0x00, 0x00}) + + entries := 34 + resp.WriteUint8(uint8(entries)) // Entry count + for i := 0; i < entries; i++ { + resp.WriteUint8(uint8(i)) // achievement id + resp.WriteUint8(uint8(i)) // level + resp.WriteUint16(20) // point value + resp.WriteUint32(100) // required + resp.WriteUint8(0) + if i < 10 { + resp.WriteUint16(0x7FFF) + } else if i < 20 { + resp.WriteUint16(0x3FFF) + } else { + resp.WriteUint16(0x1FFF) + } + //resp.WriteUint16(0x7F7F) // unk + resp.WriteUint8(0) + resp.WriteUint32(100) // progress } doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } From daa788815f14e67b8186e4d5e3352089967edd34 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 3 Aug 2022 17:23:11 +1000 Subject: [PATCH 02/51] post-SU9 cleanup --- patch-schema/.gitkeep | 0 patch-schema/aio-upgrade.sql | 11 ------ patch-schema/airouv2.sql | 5 --- patch-schema/dbuserbinaries.sql | 10 ------ patch-schema/distitem.sql | 26 -------------- patch-schema/gook.sql | 26 -------------- patch-schema/guild-additions.sql | 56 ----------------------------- patch-schema/houseinterior.sql | 6 ---- patch-schema/road-shop-rotation.sql | 35 ------------------ patch-schema/scenariocounter.sql | 9 ----- patch-schema/tokensessions.sql | 32 ----------------- 11 files changed, 216 deletions(-) create mode 100644 patch-schema/.gitkeep delete mode 100644 patch-schema/aio-upgrade.sql delete mode 100644 patch-schema/airouv2.sql delete mode 100644 patch-schema/dbuserbinaries.sql delete mode 100644 patch-schema/distitem.sql delete mode 100644 patch-schema/gook.sql delete mode 100644 patch-schema/guild-additions.sql delete mode 100644 patch-schema/houseinterior.sql delete mode 100644 patch-schema/road-shop-rotation.sql delete mode 100644 patch-schema/scenariocounter.sql delete mode 100644 patch-schema/tokensessions.sql diff --git a/patch-schema/.gitkeep b/patch-schema/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/patch-schema/aio-upgrade.sql b/patch-schema/aio-upgrade.sql deleted file mode 100644 index add35cff4..000000000 --- a/patch-schema/aio-upgrade.sql +++ /dev/null @@ -1,11 +0,0 @@ -BEGIN; - -ALTER TABLE IF EXISTS public.users - ALTER rights SET DEFAULT 14; - -ALTER TABLE IF EXISTS public.users - ALTER rights SET NOT NULL; - -UPDATE public.users SET rights=14 WHERE rights IS NULL; - -END; \ No newline at end of file diff --git a/patch-schema/airouv2.sql b/patch-schema/airouv2.sql deleted file mode 100644 index e9573bd43..000000000 --- a/patch-schema/airouv2.sql +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN; - -CREATE SEQUENCE IF NOT EXISTS public.airou_id_seq; - -END; diff --git a/patch-schema/dbuserbinaries.sql b/patch-schema/dbuserbinaries.sql deleted file mode 100644 index 012172073..000000000 --- a/patch-schema/dbuserbinaries.sql +++ /dev/null @@ -1,10 +0,0 @@ -BEGIN; - -CREATE TABLE user_binaries -( - id int PRIMARY KEY, - type2 bytea, - type3 bytea -); - -END; \ No newline at end of file diff --git a/patch-schema/distitem.sql b/patch-schema/distitem.sql deleted file mode 100644 index 7e64e620c..000000000 --- a/patch-schema/distitem.sql +++ /dev/null @@ -1,26 +0,0 @@ -BEGIN; -CREATE TABLE IF NOT EXISTS public.distribution -( - id serial NOT NULL PRIMARY KEY, - character_id int, - type int NOT NULL, - deadline timestamp without time zone, - event_name text NOT NULL DEFAULT 'GM Gift!', - description text NOT NULL DEFAULT '~C05You received a gift!', - times_acceptable int NOT NULL DEFAULT 1, - min_hr int NOT NULL DEFAULT 65535, - max_hr int NOT NULL DEFAULT 65535, - min_sr int NOT NULL DEFAULT 65535, - max_sr int NOT NULL DEFAULT 65535, - min_gr int NOT NULL DEFAULT 65535, - max_gr int NOT NULL DEFAULT 65535, - data bytea NOT NULL -); - -CREATE TABLE IF NOT EXISTS public.distributions_accepted -( - distribution_id int, - character_id int -); - -END; \ No newline at end of file diff --git a/patch-schema/gook.sql b/patch-schema/gook.sql deleted file mode 100644 index 92364862d..000000000 --- a/patch-schema/gook.sql +++ /dev/null @@ -1,26 +0,0 @@ -BEGIN; - -ALTER TABLE IF EXISTS public.gook - DROP COLUMN IF EXISTS gook0status; - -ALTER TABLE IF EXISTS public.gook - DROP COLUMN IF EXISTS gook1status; - -ALTER TABLE IF EXISTS public.gook - DROP COLUMN IF EXISTS gook2status; - -ALTER TABLE IF EXISTS public.gook - DROP COLUMN IF EXISTS gook3status; - -ALTER TABLE IF EXISTS public.gook - DROP COLUMN IF EXISTS gook4status; - -ALTER TABLE IF EXISTS public.gook - DROP COLUMN IF EXISTS gook5status; - -ALTER TABLE IF EXISTS public.gook - DROP COLUMN IF EXISTS gook5; - -UPDATE public.gook SET gook0=NULL, gook1=NULL, gook2=NULL, gook3=NULL, gook4=NULL; - -END; \ No newline at end of file diff --git a/patch-schema/guild-additions.sql b/patch-schema/guild-additions.sql deleted file mode 100644 index 8d98ccf82..000000000 --- a/patch-schema/guild-additions.sql +++ /dev/null @@ -1,56 +0,0 @@ -BEGIN; - -ALTER TABLE IF EXISTS public.guilds - ADD COLUMN IF NOT EXISTS pugi_name_1 varchar(12) DEFAULT ''; -ALTER TABLE IF EXISTS public.guilds - ADD COLUMN IF NOT EXISTS pugi_name_2 varchar(12) DEFAULT ''; -ALTER TABLE IF EXISTS public.guilds - ADD COLUMN IF NOT EXISTS pugi_name_3 varchar(12) DEFAULT ''; - -CREATE TABLE IF NOT EXISTS public.guild_alliances -( - id serial NOT NULL PRIMARY KEY, - name varchar(24) NOT NULL, - created_at timestamp without time zone NOT NULL DEFAULT now(), - parent_id int NOT NULL, - sub1_id int, - sub2_id int -); - -CREATE TABLE IF NOT EXISTS public.guild_adventures -( - id serial NOT NULL PRIMARY KEY, - guild_id int NOT NULL, - destination int NOT NULL, - charge int NOT NULL DEFAULT 0, - depart int NOT NULL, - return int NOT NULL, - collected_by text NOT NULL DEFAULT '' -); - -CREATE TABLE IF NOT EXISTS public.guild_meals -( - id serial NOT NULL PRIMARY KEY, - guild_id int NOT NULL, - meal_id int NOT NULL, - level int NOT NULL, - expires int NOT NULL -); - -CREATE TABLE IF NOT EXISTS public.guild_hunts -( - id serial NOT NULL PRIMARY KEY, - guild_id int NOT NULL, - host_id int NOT NULL, - destination int NOT NULL, - level int NOT NULL, - return int NOT NULL, - acquired bool NOT NULL DEFAULT false, - claimed bool NOT NULL DEFAULT false, - hunters text NOT NULL DEFAULT '', - treasure text NOT NULL DEFAULT '', - hunt_data bytea NOT NULL, - cats_used text NOT NULL -); - -END; \ No newline at end of file diff --git a/patch-schema/houseinterior.sql b/patch-schema/houseinterior.sql deleted file mode 100644 index ad0687ce7..000000000 --- a/patch-schema/houseinterior.sql +++ /dev/null @@ -1,6 +0,0 @@ -BEGIN; - -ALTER TABLE IF EXISTS public.characters - ADD COLUMN IF NOT EXISTS house bytea; - -END; \ No newline at end of file diff --git a/patch-schema/road-shop-rotation.sql b/patch-schema/road-shop-rotation.sql deleted file mode 100644 index c4c85482c..000000000 --- a/patch-schema/road-shop-rotation.sql +++ /dev/null @@ -1,35 +0,0 @@ -BEGIN; -CREATE TABLE IF NOT EXISTS public.normal_shop_items -( - shoptype integer, - shopid integer, - itemhash integer not null, - itemid integer, - points integer, - tradequantity integer, - rankreqlow integer, - rankreqhigh integer, - rankreqg integer, - storelevelreq integer, - maximumquantity integer, - boughtquantity integer, - roadfloorsrequired integer, - weeklyfataliskills integer, - enable_weeks character varying(8) -); - -ALTER TABLE IF EXISTS public.normal_shop_items - ADD COLUMN IF NOT EXISTS enable_weeks character varying(8); - -CREATE TABLE IF NOT EXISTS public.shop_item_state -( - char_id bigint REFERENCES characters (id), - itemhash int UNIQUE NOT NULL, - usedquantity int, - week int -); - -ALTER TABLE IF EXISTS public.shop_item_state - ADD COLUMN IF NOT EXISTS week int; - -END; \ No newline at end of file diff --git a/patch-schema/scenariocounter.sql b/patch-schema/scenariocounter.sql deleted file mode 100644 index 4b3b99616..000000000 --- a/patch-schema/scenariocounter.sql +++ /dev/null @@ -1,9 +0,0 @@ -BEGIN; - -ALTER TABLE IF EXISTS public.characters - ADD COLUMN IF NOT EXISTS scenariodata bytea; - -ALTER TABLE IF EXISTS public.characters - ADD COLUMN IF NOT EXISTS savefavoritequest bytea; - -END; \ No newline at end of file diff --git a/patch-schema/tokensessions.sql b/patch-schema/tokensessions.sql deleted file mode 100644 index bb6b4a1f1..000000000 --- a/patch-schema/tokensessions.sql +++ /dev/null @@ -1,32 +0,0 @@ -BEGIN; - -DROP TABLE IF EXISTS public.sign_sessions; -CREATE TABLE IF NOT EXISTS public.sign_sessions -( - user_id int NOT NULL, - char_id int, - token varchar(16) NOT NULL, - server_id integer -); - -DROP TABLE IF EXISTS public.servers; -CREATE TABLE IF NOT EXISTS public.servers -( - server_id int NOT NULL, - season int NOT NULL, - current_players int NOT NULL -); - -ALTER TABLE IF EXISTS public.characters - ADD COLUMN IF NOT EXISTS deleted boolean NOT NULL DEFAULT false; - -ALTER TABLE IF EXISTS public.characters - ADD COLUMN IF NOT EXISTS friends text NOT NULL DEFAULT ''; - -ALTER TABLE IF EXISTS public.characters - ADD COLUMN IF NOT EXISTS blocked text NOT NULL DEFAULT ''; - -ALTER TABLE IF EXISTS public.users - ADD COLUMN IF NOT EXISTS last_character int DEFAULT 0; - -END; From 95104571a6ed3e70eef3321b41e964f83a044380 Mon Sep 17 00:00:00 2001 From: Evotushon <87574203+Evotushon@users.noreply.github.com> Date: Wed, 3 Aug 2022 14:11:02 +0200 Subject: [PATCH 03/51] Rewrite README.md (#14) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c312b8a7e..916c6db7a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Erupe Community Edition + This is a community upload of a community project. The amount of people who worked on it is innumerous, and hard to keep track of. Credits to Andoryuuta, Fist's Team, the French Team, Mai's Team and many others. No matter the relations, these files will remain public and open source, free for all to use and modify. -A pastebin with various links, tips, and FAQ: https://pastebin.com/QqAwZSTC +[A pastebin with various links, tips, and FAQ](https://pastebin.com/QqAwZSTC) -An upload for the quest and scenario files exists here: https://github.com/xl3lackout/MHFZ-Quest-Files -(Over 300k+ files) \ No newline at end of file +[An upload for the quest and scenario files exists here](https://github.com/xl3lackout/MHFZ-Quest-Files) +(Over 300k+ files) From af2cd5cd7ca3e8764b21f6c36a6cdeb4b975c346 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 3 Aug 2022 22:27:14 +1000 Subject: [PATCH 04/51] Update LICENSE Please read the following to understand why the license has been updated. The entity known as 'The Erupe Developers' (currently only known as Fist, Andoryuuta and possibly some others), previously published a source of Erupe that was available under MIT License. This fork was used by the 'Einherjar Team' to modify and redistribute Erupe which is fully available under MIT. However, when the 'Einherjar Team' updated the license, they stated 'The Erupe Developers from Einherjar Team'. However none of the persons under the entity 'The Erupe Developers' are part of the 'Einherjar Team'. This ultimately forfeits the licensing of any work that has been published by the 'Einherjar Team'. The 'Einherjar Team' name will be left in the license as it previously was, this is out of respect for them, their work and the rights of the license itself, however no work exists in this repository that is able to be licensed by the 'Einherjar Team'. 'The Erupe Developers' entity has been separated, as it was in the original license and ZeruLight has also been appended to the list of contributors. Should future contributors like to have their work licensed within this fork of Erupe, they are welcome to append they or the the entity they represent to the license. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 704115aa4..8803130d6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 The Erupe Developers from Einherjar Team +Copyright (c) 2019 The Erupe Developers, The Erupe Developers from Einherjar Team, ZeruLight Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 6424a5c639f02601f3859fcf871547a710cea436 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 01:15:04 +1000 Subject: [PATCH 05/51] support SJIS mail (#15) --- config.json | 1 + config/config.go | 1 + network/mhfpacket/msg_mhf_oprt_mail.go | 33 ++++++++--------- network/mhfpacket/msg_mhf_send_mail.go | 41 +++++++++++---------- server/channelserver/handlers_mail.go | 51 +++++++++++--------------- 5 files changed, 61 insertions(+), 66 deletions(-) diff --git a/config.json b/config.json index 977a68948..2c688ec51 100644 --- a/config.json +++ b/config.json @@ -16,6 +16,7 @@ "FestaEvent": 0, "TournamentEvent": 0, "MezFesEvent": true, + "DisableMailItems": true, "SaveDumps": { "Enabled": true, "OutputDir": "savedata" diff --git a/config/config.go b/config/config.go index 5d6c65838..e4f916321 100644 --- a/config/config.go +++ b/config/config.go @@ -36,6 +36,7 @@ type DevModeOptions struct { FestaEvent int // Hunter's Festa event status TournamentEvent int // VS Tournament event status MezFesEvent bool // MezFes status + DisableMailItems bool // Hack to prevent english versions of MHF from crashing SaveDumps SaveDumpOptions } diff --git a/network/mhfpacket/msg_mhf_oprt_mail.go b/network/mhfpacket/msg_mhf_oprt_mail.go index 5da66f115..2c9e06828 100644 --- a/network/mhfpacket/msg_mhf_oprt_mail.go +++ b/network/mhfpacket/msg_mhf_oprt_mail.go @@ -1,20 +1,20 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) type OperateMailOperation uint8 const ( - OPERATE_MAIL_DELETE = 0x01 - OPERATE_MAIL_LOCK = 0x02 - OPERATE_MAIL_UNLOCK = 0x03 - OPERATE_MAIL_ACQUIRE_ITEM = 0x05 + OPERATE_MAIL_DELETE = 0x01 + OPERATE_MAIL_LOCK = 0x02 + OPERATE_MAIL_UNLOCK = 0x03 + OPERATE_MAIL_ACQUIRE_ITEM = 0x05 ) // MsgMhfOprtMail represents the MSG_MHF_OPRT_MAIL @@ -23,10 +23,10 @@ type MsgMhfOprtMail struct { AccIndex uint8 Index uint8 Operation OperateMailOperation - Unk0 uint8 - Data []byte - Amount uint16 - ItemID uint16 + Unk0 uint8 + Data []byte + Amount uint16 + ItemID uint16 } // Opcode returns the ID associated with this packet type. @@ -40,12 +40,11 @@ func (m *MsgMhfOprtMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon m.AccIndex = bf.ReadUint8() m.Index = bf.ReadUint8() m.Operation = OperateMailOperation(bf.ReadUint8()) - m.Unk0 = bf.ReadUint8() - switch m.Operation { - case OPERATE_MAIL_ACQUIRE_ITEM: - m.Amount = bf.ReadUint16() - m.ItemID = bf.ReadUint16() - } + m.Unk0 = bf.ReadUint8() + if m.Operation == OPERATE_MAIL_ACQUIRE_ITEM { + m.Amount = bf.ReadUint16() + m.ItemID = bf.ReadUint16() + } return nil } diff --git a/network/mhfpacket/msg_mhf_send_mail.go b/network/mhfpacket/msg_mhf_send_mail.go index f6f7c9460..e0f34ba54 100644 --- a/network/mhfpacket/msg_mhf_send_mail.go +++ b/network/mhfpacket/msg_mhf_send_mail.go @@ -1,23 +1,24 @@ package mhfpacket import ( - "errors" + "errors" + "erupe-ce/common/stringsupport" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfSendMail represents the MSG_MHF_SEND_MAIL type MsgMhfSendMail struct { - AckHandle uint32 - RecipientID uint32 - SubjectLength uint16 - BodyLength uint16 - Quantity uint32 - ItemID uint16 - Subject []byte - Body []byte + AckHandle uint32 + RecipientID uint32 + SubjectLength uint16 + BodyLength uint16 + Quantity uint32 + ItemID uint16 + Subject string + Body string } // Opcode returns the ID associated with this packet type. @@ -27,15 +28,15 @@ func (m *MsgMhfSendMail) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfSendMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.RecipientID = bf.ReadUint32() - m.SubjectLength = bf.ReadUint16() - m.BodyLength = bf.ReadUint16() - m.Quantity = bf.ReadUint32() - m.ItemID = bf.ReadUint16() - m.Subject = bf.ReadNullTerminatedBytes() - m.Body = bf.ReadNullTerminatedBytes() - return nil + m.AckHandle = bf.ReadUint32() + m.RecipientID = bf.ReadUint32() + m.SubjectLength = bf.ReadUint16() + m.BodyLength = bf.ReadUint16() + m.Quantity = bf.ReadUint32() + m.ItemID = bf.ReadUint16() + m.Subject = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Body = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + return nil } // Build builds a binary packet from the current data. diff --git a/server/channelserver/handlers_mail.go b/server/channelserver/handlers_mail.go index ac6d5dcae..1e060b245 100644 --- a/server/channelserver/handlers_mail.go +++ b/server/channelserver/handlers_mail.go @@ -2,6 +2,7 @@ package channelserver import ( "database/sql" + "erupe-ce/common/stringsupport" "time" "erupe-ce/common/byteframe" @@ -275,7 +276,7 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() - body := s.clientContext.StrConv.MustEncode(mail.Body) + body := stringsupport.UTF8ToSJIS(mail.Body) bf.WriteNullTerminatedBytes(body) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) @@ -307,13 +308,11 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) { s.mailAccIndex++ itemAttached := m.AttachedItemID != 0 - subject := s.clientContext.StrConv.MustEncode(m.Subject) - sender := s.clientContext.StrConv.MustEncode(m.SenderName) msg.WriteUint32(m.SenderID) msg.WriteUint32(uint32(m.CreatedAt.Unix())) - msg.WriteUint8(uint8(accIndex)) + msg.WriteUint8(accIndex) msg.WriteUint8(uint8(i)) flags := uint8(0x00) @@ -329,8 +328,15 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) { // System message, hides ID // flags |= 0x04 - if m.AttachedItemReceived { - flags |= 0x08 + // Workaround until EN mail items are patched + if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DisableMailItems { + if itemAttached { + flags |= 0x08 + } + } else { + if m.AttachedItemReceived { + flags |= 0x08 + } } if m.IsGuildInvite { @@ -339,11 +345,10 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) { msg.WriteUint8(flags) msg.WriteBool(itemAttached) - msg.WriteUint8(uint8(len(subject) + 1)) - msg.WriteUint8(uint8(len(sender) + 1)) - msg.WriteNullTerminatedBytes(subject) - msg.WriteNullTerminatedBytes(sender) - + msg.WriteUint8(16) + msg.WriteUint8(21) + msg.WriteBytes(stringsupport.PaddedString(m.Subject, 16, true)) + msg.WriteBytes(stringsupport.PaddedString(m.SenderName, 21, true)) if itemAttached { msg.WriteUint16(m.AttachedItemAmount) msg.WriteUint16(m.AttachedItemID) @@ -358,34 +363,22 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) { mail, err := GetMailByID(s, s.mailList[pkt.AccIndex]) if err != nil { - doAckSimpleFail(s, pkt.AckHandle, nil) panic(err) } - switch mhfpacket.OperateMailOperation(pkt.Operation) { + + switch pkt.Operation { case mhfpacket.OPERATE_MAIL_DELETE: err = mail.MarkDeleted(s) - if err != nil { - doAckSimpleFail(s, pkt.AckHandle, nil) - panic(err) - } case mhfpacket.OPERATE_MAIL_LOCK: err = mail.MarkLocked(s, true) - if err != nil { - doAckSimpleFail(s, pkt.AckHandle, nil) - panic(err) - } case mhfpacket.OPERATE_MAIL_UNLOCK: err = mail.MarkLocked(s, false) - if err != nil { - doAckSimpleFail(s, pkt.AckHandle, nil) - panic(err) - } case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM: err = mail.MarkAcquired(s) - if err != nil { - doAckSimpleFail(s, pkt.AckHandle, nil) - panic(err) - } + } + + if err != nil { + panic(err) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) From 3bdc206ff7e6b95fb66613ddbda73b97d115a9f6 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 02:46:04 +1000 Subject: [PATCH 06/51] cast binary fixes --- server/channelserver/handlers_cast_binary.go | 5 ++--- server/channelserver/sys_channel_server.go | 7 +++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index ca3410618..304369295 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -118,7 +118,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { // Send to the proper recipients. switch pkt.BroadcastType { case BroadcastTypeWorld: - s.server.WorldcastMHF(resp, s) + s.server.WorldcastMHF(resp, s, nil) case BroadcastTypeStage: if isDiceCommand { s.stage.BroadcastMHF(resp, nil) // send dice result back to caller @@ -129,8 +129,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { if pkt.MessageType == 1 { raviSema := getRaviSemaphore(s) if raviSema != "" { - sema := s.server.semaphore[raviSema] - (*sema).BroadcastMHF(resp, s) + s.server.BroadcastMHF(resp, s) } } else { s.server.BroadcastMHF(resp, s) diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 9425cdcd5..69335280c 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -295,8 +295,11 @@ func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) } } -func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) { +func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session, ignoredChannel *Server) { for _, c := range s.Channels { + if c == ignoredChannel { + continue + } for _, session := range c.sessions { if session == ignoredSession { continue @@ -357,7 +360,7 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u BroadcastType: BroadcastTypeServer, MessageType: BinaryMessageTypeChat, RawDataPayload: bf.Data(), - }, nil) + }, nil, s) } func (s *Server) DiscordChannelSend(charName string, content string) { From 872a0b37855e391b909ee37618b64de0a2a9ac86 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 03:04:42 +1000 Subject: [PATCH 07/51] treasure expiration and prevent overflow --- server/channelserver/handlers_guild_tresure.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_guild_tresure.go b/server/channelserver/handlers_guild_tresure.go index 92d27f7e9..c5e2fa0dc 100644 --- a/server/channelserver/handlers_guild_tresure.go +++ b/server/channelserver/handlers_guild_tresure.go @@ -27,7 +27,7 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) { } bf := byteframe.NewByteFrame() hunts := 0 - rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1", guild.ID) + rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1 AND $2 < return+604800", guild.ID, Time_Current_Adjusted().Unix()) for rows.Next() { hunt := &TreasureHunt{} err = rows.StructScan(&hunt) @@ -51,6 +51,9 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) { bf.WriteBytes(hunt.HuntData) break } else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 { + if hunts == 30 { + break + } hunts++ bf.WriteUint32(hunt.HuntID) bf.WriteUint32(hunt.Destination) From ba927f877d4611df0a6bee81e2cc2a627e3d5ecb Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 06:43:41 +1000 Subject: [PATCH 08/51] fix guild enumeration and applications --- network/mhfpacket/msg_mhf_enumerate_guild.go | 5 +- .../mhfpacket/msg_mhf_operate_guild_member.go | 11 +- server/channelserver/handlers_guild.go | 166 +++++++++--------- server/channelserver/handlers_guild_scout.go | 12 +- server/channelserver/sys_session.go | 1 + 5 files changed, 97 insertions(+), 98 deletions(-) diff --git a/network/mhfpacket/msg_mhf_enumerate_guild.go b/network/mhfpacket/msg_mhf_enumerate_guild.go index 777017cc4..65edbc555 100644 --- a/network/mhfpacket/msg_mhf_enumerate_guild.go +++ b/network/mhfpacket/msg_mhf_enumerate_guild.go @@ -2,6 +2,7 @@ package mhfpacket import ( "errors" + "io" "erupe-ce/common/byteframe" "erupe-ce/network" @@ -30,6 +31,7 @@ const ( type MsgMhfEnumerateGuild struct { AckHandle uint32 Type EnumerateGuildType + Page uint8 RawDataPayload []byte } @@ -42,8 +44,9 @@ func (m *MsgMhfEnumerateGuild) Opcode() network.PacketID { func (m *MsgMhfEnumerateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Type = EnumerateGuildType(bf.ReadUint8()) + m.Page = bf.ReadUint8() m.RawDataPayload = bf.DataFromCurrent() - bf.Seek(int64(len(bf.Data())-2), 0) + bf.Seek(-2, io.SeekEnd) return nil } diff --git a/network/mhfpacket/msg_mhf_operate_guild_member.go b/network/mhfpacket/msg_mhf_operate_guild_member.go index 62850f63e..7aed7a21d 100644 --- a/network/mhfpacket/msg_mhf_operate_guild_member.go +++ b/network/mhfpacket/msg_mhf_operate_guild_member.go @@ -1,11 +1,11 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) type OperateGuildMemberAction uint8 @@ -23,6 +23,7 @@ type MsgMhfOperateGuildMember struct { GuildID uint32 CharID uint32 Action uint8 + Unk []byte } // Opcode returns the ID associated with this packet type. @@ -36,7 +37,7 @@ func (m *MsgMhfOperateGuildMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx m.GuildID = bf.ReadUint32() m.CharID = bf.ReadUint32() m.Action = bf.ReadUint8() - + m.Unk = bf.ReadBytes(3) return nil } diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index fe5efd114..b0a28ec4b 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -12,7 +12,6 @@ import ( "strings" "time" - "erupe-ce/common/bfutil" "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" "erupe-ce/common/stringsupport" @@ -854,42 +853,45 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) { return } - if pkt.Action == mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT || pkt.Action == mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT { - switch pkt.Action { - case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT: - err = guild.AcceptApplication(s, pkt.CharID) - case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT: - err = guild.RejectApplication(s, pkt.CharID) - } - - if err != nil { - doAckSimpleFail(s, pkt.AckHandle, nil) - } - - doAckSimpleSucceed(s, pkt.AckHandle, nil) - return - } - - character, err := GetCharacterGuildData(s, pkt.CharID) - - if err != nil || character == nil { - doAckSimpleFail(s, pkt.AckHandle, nil) - return - } - + var mail Mail switch pkt.Action { + case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT: + err = guild.AcceptApplication(s, pkt.CharID) + mail = Mail{ + SenderID: s.charID, + RecipientID: pkt.CharID, + Subject: "Accepted!", + Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name), + IsGuildInvite: false, + } + case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT: + err = guild.RejectApplication(s, pkt.CharID) + mail = Mail{ + SenderID: s.charID, + RecipientID: pkt.CharID, + Subject: "Rejected", + Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name), + IsGuildInvite: false, + } case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK: err = guild.RemoveCharacter(s, pkt.CharID) + mail = Mail{ + SenderID: s.charID, + RecipientID: pkt.CharID, + Subject: "Kicked", + Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name), + IsGuildInvite: false, + } default: doAckSimpleFail(s, pkt.AckHandle, nil) - panic(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action)) + s.logger.Warn(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action)) } if err != nil { doAckSimpleFail(s, pkt.AckHandle, nil) - return + } else { + mail.Send(s, nil) } - doAckSimpleSucceed(s, pkt.AckHandle, nil) } @@ -906,6 +908,8 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { } if err == nil && guild != nil { + s.prevGuildID = guild.ID + guildName := stringsupport.UTF8ToSJIS(guild.Name) guildComment := stringsupport.UTF8ToSJIS(guild.Comment) guildLeaderName := stringsupport.UTF8ToSJIS(guild.LeaderName) @@ -1057,19 +1061,22 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { if err != nil { resp := byteframe.NewByteFrame() resp.WriteUint32(0) // Count - resp.WriteUint8(5) // Unk, read if count == 0. + resp.WriteUint8(0) // Unk, read if count == 0. doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } - - bf.WriteUint16(uint16(len(applicants))) - - for _, applicant := range applicants { - bf.WriteUint32(applicant.CharID) - bf.WriteUint32(0x05) - bf.WriteUint16(0x0032) - bf.WriteUint8(0x00) - ps.Uint16(bf, applicant.Name, true) + if err != nil { + bf.WriteUint16(0) + } else { + bf.WriteUint16(uint16(len(applicants))) + for _, applicant := range applicants { + bf.WriteUint32(applicant.CharID) + bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint16(applicant.HRP) + bf.WriteUint16(applicant.GR) + ps.Uint8(bf, applicant.Name, true) + } } bf.WriteUint16(0x0000) @@ -1128,100 +1135,74 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { switch pkt.Type { case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME: - bf.ReadBytes(8) - searchTermLength := bf.ReadUint16() - bf.ReadBytes(1) - searchTerm := bf.ReadBytes(uint(searchTermLength)) - var searchTermSafe string - searchTermSafe, err = s.clientContext.StrConv.Decode(bfutil.UpToNull(searchTerm)) - if err != nil { - panic(err) - } - guilds, err = FindGuildsByName(s, searchTermSafe) + bf.ReadBytes(10) + searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + guilds, err = FindGuildsByName(s, searchTerm) case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME: - bf.ReadBytes(8) - searchTermLength := bf.ReadUint16() - bf.ReadBytes(1) - searchTerm := bf.ReadBytes(uint(searchTermLength)) - var searchTermSafe string - searchTermSafe, err = s.clientContext.StrConv.Decode(bfutil.UpToNull(searchTerm)) - if err != nil { - panic(err) - } - rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE lc.name ILIKE $1`, guildInfoSelectQuery), searchTermSafe) - if err != nil { - s.logger.Error("Failed to retrieve guild by leader name", zap.Error(err)) - } else { + bf.ReadBytes(10) + searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE lc.name ILIKE $1`, guildInfoSelectQuery), searchTerm) + if err == nil { for rows.Next() { guild, _ := buildGuildObjectFromDbResult(rows, err, s) guilds = append(guilds, guild) } } case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID: - bf.ReadBytes(3) + bf.ReadBytes(2) ID := bf.ReadUint32() rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE leader_id = $1`, guildInfoSelectQuery), ID) - if err != nil { - s.logger.Error("Failed to retrieve guild by leader ID", zap.Error(err)) - } else { + if err == nil { for rows.Next() { guild, _ := buildGuildObjectFromDbResult(rows, err, s) guilds = append(guilds, guild) } } case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS: - sorting := bf.ReadUint16() + sorting := bf.ReadUint8() if sorting == 1 { rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count DESC`, guildInfoSelectQuery)) } else { rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count ASC`, guildInfoSelectQuery)) } - if err != nil { - s.logger.Error("Failed to retrieve guild by member count", zap.Error(err)) - } else { + if err == nil { for rows.Next() { guild, _ := buildGuildObjectFromDbResult(rows, err, s) guilds = append(guilds, guild) } } case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION: - sorting := bf.ReadUint16() + sorting := bf.ReadUint8() if sorting == 1 { rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id ASC`, guildInfoSelectQuery)) } else { rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id DESC`, guildInfoSelectQuery)) } - if err != nil { - s.logger.Error("Failed to retrieve guild by registration date", zap.Error(err)) - } else { + if err == nil { for rows.Next() { guild, _ := buildGuildObjectFromDbResult(rows, err, s) guilds = append(guilds, guild) } } case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK: - sorting := bf.ReadUint16() + sorting := bf.ReadUint8() if sorting == 1 { rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp DESC`, guildInfoSelectQuery)) } else { rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp ASC`, guildInfoSelectQuery)) } - if err != nil { - s.logger.Error("Failed to retrieve guild by rank", zap.Error(err)) - } else { + if err == nil { for rows.Next() { guild, _ := buildGuildObjectFromDbResult(rows, err, s) guilds = append(guilds, guild) } } case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO: - bf.ReadBytes(3) + bf.ReadBytes(2) mainMotto := bf.ReadUint16() subMotto := bf.ReadUint16() rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE main_motto = $1 AND sub_motto = $2`, guildInfoSelectQuery), mainMotto, subMotto) - if err != nil { - s.logger.Error("Failed to retrieve guild by motto", zap.Error(err)) - } else { + if err == nil { for rows.Next() { guild, _ := buildGuildObjectFromDbResult(rows, err, s) guilds = append(guilds, guild) @@ -1251,23 +1232,21 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { bf = byteframe.NewByteFrame() bf.WriteUint16(uint16(len(guilds))) + bf.WriteUint8(0x01) // Unk + for _, guild := range guilds { - bf.WriteUint8(0x00) // Unk bf.WriteUint32(guild.ID) bf.WriteUint32(guild.LeaderCharID) bf.WriteUint16(guild.MemberCount) - bf.WriteUint8(0x00) // Unk - bf.WriteUint8(0x00) // Unk - bf.WriteUint16(guild.Rank) + bf.WriteUint16(0x0000) // Unk + bf.WriteUint16(guild.Rank) // OR guilds in alliance bf.WriteUint32(uint32(guild.CreatedAt.Unix())) ps.Uint8(bf, guild.Name, true) ps.Uint8(bf, guild.LeaderName, true) bf.WriteUint8(0x01) // Unk + bf.WriteBool(false) // closed } - bf.WriteUint8(0x01) // Unk - bf.WriteUint8(0x00) // Unk - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } @@ -1318,6 +1297,10 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { guild, err = GetGuildInfoByCharacterId(s, s.charID) } + if guild == nil && s.prevGuildID > 0 { + guild, err = GetGuildInfoByID(s, s.prevGuildID) + } + if err != nil { s.logger.Warn("failed to retrieve guild sending no result message") doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2)) @@ -1411,6 +1394,15 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) { guild, err := GetGuildInfoByCharacterId(s, s.charID) + if guild == nil && s.prevGuildID != 0 { + guild, err = GetGuildInfoByID(s, s.prevGuildID) + s.prevGuildID = 0 + if guild == nil || err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } + } + if err != nil { s.logger.Warn("failed to respond to manage rights message") return diff --git a/server/channelserver/handlers_guild_scout.go b/server/channelserver/handlers_guild_scout.go index 30f41860b..ec04835ef 100644 --- a/server/channelserver/handlers_guild_scout.go +++ b/server/channelserver/handlers_guild_scout.go @@ -190,13 +190,15 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) { guildInfo, err := GetGuildInfoByCharacterId(s, s.charID) - if err != nil { - panic(err) - } - - if guildInfo == nil { + if guildInfo == nil && s.prevGuildID == 0 { doAckSimpleFail(s, pkt.AckHandle, nil) return + } else { + guildInfo, err = GetGuildInfoByID(s, s.prevGuildID) + if guildInfo == nil || err != nil { + doAckSimpleFail(s, pkt.AckHandle, nil) + return + } } rows, err := s.server.db.Queryx(` diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 6270b33aa..20fc432f8 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -33,6 +33,7 @@ type Session struct { stage *Stage reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet. stagePass string // Temporary storage + prevGuildID uint32 // Stores the last GuildID used in InfoGuild charID uint32 logKey []byte sessionStart int64 From 260d8d0dd8aa168bd0b3fda40e93fef4ce959aee Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 07:08:10 +1000 Subject: [PATCH 09/51] guild pagination and cleanup --- server/channelserver/handlers_guild.go | 58 ++++++++------------------ 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index b0a28ec4b..cb362a84a 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -511,36 +511,6 @@ func rollbackTransaction(s *Session, transaction *sql.Tx) { } } -func FindGuildsByName(s *Session, name string) ([]*Guild, error) { - searchTerm := fmt.Sprintf("%%%s%%", name) - - rows, err := s.server.db.Queryx(fmt.Sprintf(` - %s - WHERE g.name ILIKE $1 - `, guildInfoSelectQuery), searchTerm) - - if err != nil { - s.logger.Error("failed to find guilds for search term", zap.Error(err), zap.String("searchTerm", name)) - return nil, err - } - - defer rows.Close() - - guilds := make([]*Guild, 0) - - for rows.Next() { - guild, err := buildGuildObjectFromDbResult(rows, err, s) - - if err != nil { - return nil, err - } - - guilds = append(guilds, guild) - } - - return guilds, nil -} - func GetGuildInfoByID(s *Session, guildID uint32) (*Guild, error) { rows, err := s.server.db.Queryx(fmt.Sprintf(` %s @@ -1136,12 +1106,18 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { switch pkt.Type { case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME: bf.ReadBytes(10) - searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) - guilds, err = FindGuildsByName(s, searchTerm) + searchTerm := fmt.Sprintf(`%%%s%%`, stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE g.name ILIKE $1 OFFSET $2`, guildInfoSelectQuery), searchTerm, pkt.Page*10) + if err == nil { + for rows.Next() { + guild, _ := buildGuildObjectFromDbResult(rows, err, s) + guilds = append(guilds, guild) + } + } case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME: bf.ReadBytes(10) - searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) - rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE lc.name ILIKE $1`, guildInfoSelectQuery), searchTerm) + searchTerm := fmt.Sprintf(`%%%s%%`, stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE lc.name ILIKE $1 OFFSET $2`, guildInfoSelectQuery), searchTerm, pkt.Page*10) if err == nil { for rows.Next() { guild, _ := buildGuildObjectFromDbResult(rows, err, s) @@ -1161,9 +1137,9 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS: sorting := bf.ReadUint8() if sorting == 1 { - rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count DESC`, guildInfoSelectQuery)) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10) } else { - rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count ASC`, guildInfoSelectQuery)) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count ASC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10) } if err == nil { for rows.Next() { @@ -1174,9 +1150,9 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION: sorting := bf.ReadUint8() if sorting == 1 { - rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id ASC`, guildInfoSelectQuery)) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id ASC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10) } else { - rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id DESC`, guildInfoSelectQuery)) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10) } if err == nil { for rows.Next() { @@ -1187,9 +1163,9 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK: sorting := bf.ReadUint8() if sorting == 1 { - rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp DESC`, guildInfoSelectQuery)) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10) } else { - rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp ASC`, guildInfoSelectQuery)) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp ASC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10) } if err == nil { for rows.Next() { @@ -1201,7 +1177,7 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { bf.ReadBytes(2) mainMotto := bf.ReadUint16() subMotto := bf.ReadUint16() - rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE main_motto = $1 AND sub_motto = $2`, guildInfoSelectQuery), mainMotto, subMotto) + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE main_motto = $1 AND sub_motto = $2 OFFSET $3`, guildInfoSelectQuery), mainMotto, subMotto, pkt.Page*10) if err == nil { for rows.Next() { guild, _ := buildGuildObjectFromDbResult(rows, err, s) From 38747d389cf3a915541ab1e66dcea08c555f6029 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 07:31:28 +1000 Subject: [PATCH 10/51] guild recruitment closing --- patch-schema/guild-enumeration.sql | 6 +++ server/channelserver/handlers_guild.go | 52 ++++++++------------------ 2 files changed, 21 insertions(+), 37 deletions(-) create mode 100644 patch-schema/guild-enumeration.sql diff --git a/patch-schema/guild-enumeration.sql b/patch-schema/guild-enumeration.sql new file mode 100644 index 000000000..5ccfb3306 --- /dev/null +++ b/patch-schema/guild-enumeration.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.guilds + ADD COLUMN IF NOT EXISTS recruiting bool NOT NULL DEFAULT true; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index cb362a84a..9dcc64aab 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -54,6 +54,7 @@ type Guild struct { PugiName1 string `db:"pugi_name_1"` PugiName2 string `db:"pugi_name_2"` PugiName3 string `db:"pugi_name_3"` + Recruiting bool `db:"recruiting"` FestivalColour FestivalColour `db:"festival_colour"` Rank uint16 `db:"rank"` AllianceID uint32 `db:"alliance_id"` @@ -123,6 +124,7 @@ SELECT pugi_name_1, pugi_name_2, pugi_name_3, + recruiting, festival_colour, CASE WHEN rank_rp <= 48 THEN rank_rp/24 @@ -639,6 +641,8 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint32(uint32(response)) + doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) + return case mhfpacket.OPERATE_GUILD_APPLY: err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil) @@ -648,6 +652,8 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { } else { bf.WriteUint32(guild.LeaderCharID) } + doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) + return case mhfpacket.OPERATE_GUILD_LEAVE: var err error @@ -664,81 +670,53 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint32(uint32(response)) + doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) + return case mhfpacket.OPERATE_GUILD_DONATE_RANK: handleDonateRP(s, pkt, bf, guild, false) case mhfpacket.OPERATE_GUILD_SET_APPLICATION_DENY: - // TODO: close applications for guild - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return + s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID) case mhfpacket.OPERATE_GUILD_SET_APPLICATION_ALLOW: - // TODO: open applications for guild - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return + s.server.db.Exec("UPDATE guilds SET recruiting=true WHERE id=$1", guild.ID) case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE: handleAvoidLeadershipUpdate(s, pkt, true) case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE: handleAvoidLeadershipUpdate(s, pkt, false) case mhfpacket.OPERATE_GUILD_UPDATE_COMMENT: pbf := byteframe.NewByteFrameFromBytes(pkt.UnkData) - if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) return } - _ = pbf.ReadUint8() // len _ = pbf.ReadUint32() guild.Comment = stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes()) - err = guild.Save(s) - if err != nil { - doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) - return - } - - bf.WriteUint32(0x00) + guild.Save(s) case mhfpacket.OPERATE_GUILD_UPDATE_MOTTO: if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) return } - guild.SubMotto = pkt.UnkData[3] guild.MainMotto = pkt.UnkData[4] - - err := guild.Save(s) - - if err != nil { - doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) - return - } + guild.Save(s) case mhfpacket.OPERATE_GUILD_RENAME_PUGI_1: handleRenamePugi(s, pkt.UnkData, guild, 1) - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return case mhfpacket.OPERATE_GUILD_RENAME_PUGI_2: handleRenamePugi(s, pkt.UnkData, guild, 2) - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return case mhfpacket.OPERATE_GUILD_RENAME_PUGI_3: handleRenamePugi(s, pkt.UnkData, guild, 3) - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_1: - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return + // TODO: decode guild poogie outfits case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_2: - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_3: - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return case mhfpacket.OPERATE_GUILD_DONATE_EVENT: handleDonateRP(s, pkt, bf, guild, true) default: panic(fmt.Sprintf("unhandled operate guild action '%d'", pkt.Action)) } - doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleRenamePugi(s *Session, data []byte, guild *Guild, num int) { @@ -1220,7 +1198,7 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { ps.Uint8(bf, guild.Name, true) ps.Uint8(bf, guild.LeaderName, true) bf.WriteUint8(0x01) // Unk - bf.WriteBool(false) // closed + bf.WriteBool(!guild.Recruiting) } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) From b2ebb8f1d94d2da875264dc4f8974fb99bd80745 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 07:56:31 +1000 Subject: [PATCH 11/51] recruiting guilds, stub EnumerateInvGuild --- .../mhfpacket/msg_mhf_enumerate_inv_guild.go | 17 +++++++++++------ server/channelserver/handlers_guild.go | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/network/mhfpacket/msg_mhf_enumerate_inv_guild.go b/network/mhfpacket/msg_mhf_enumerate_inv_guild.go index 5080fffe2..cf2057bed 100644 --- a/network/mhfpacket/msg_mhf_enumerate_inv_guild.go +++ b/network/mhfpacket/msg_mhf_enumerate_inv_guild.go @@ -1,15 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfEnumerateInvGuild represents the MSG_MHF_ENUMERATE_INV_GUILD -type MsgMhfEnumerateInvGuild struct{} +type MsgMhfEnumerateInvGuild struct { + AckHandle uint32 + Unk []byte +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfEnumerateInvGuild) Opcode() network.PacketID { @@ -18,7 +21,9 @@ func (m *MsgMhfEnumerateInvGuild) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateInvGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Unk = bf.ReadBytes(9) + return nil } // Build builds a binary packet from the current data. diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 9dcc64aab..4265e4a19 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1163,7 +1163,14 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { } } case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING: - // + // Assume the player wants the newest guilds with open recruitment + rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE recruiting=true ORDER BY id DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10) + if err == nil { + for rows.Next() { + guild, _ := buildGuildObjectFromDbResult(rows, err, s) + guilds = append(guilds, guild) + } + } case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME: // case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME: @@ -1872,7 +1879,10 @@ func handleMsgMhfUpdateGuild(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfEnumerateInvGuild) + stubEnumerateNoResults(s, pkt.AckHandle) +} func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {} From dcd6b35478098cb61f3db482ead2c03ff243e7f1 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 09:06:21 +1000 Subject: [PATCH 12/51] implement guild recruiters --- .../msg_mhf_set_guild_manage_right.go | 21 +++++-- patch-schema/guild-enumeration.sql | 3 + server/channelserver/handlers_guild.go | 14 ++++- server/channelserver/handlers_guild_member.go | 56 +++++++++---------- server/channelserver/handlers_guild_scout.go | 4 +- 5 files changed, 58 insertions(+), 40 deletions(-) diff --git a/network/mhfpacket/msg_mhf_set_guild_manage_right.go b/network/mhfpacket/msg_mhf_set_guild_manage_right.go index 5f9b98a2e..3feed2654 100644 --- a/network/mhfpacket/msg_mhf_set_guild_manage_right.go +++ b/network/mhfpacket/msg_mhf_set_guild_manage_right.go @@ -1,15 +1,20 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfSetGuildManageRight represents the MSG_MHF_SET_GUILD_MANAGE_RIGHT -type MsgMhfSetGuildManageRight struct{} +type MsgMhfSetGuildManageRight struct { + AckHandle uint32 + CharID uint32 + Allowed bool + Unk []byte +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfSetGuildManageRight) Opcode() network.PacketID { @@ -18,7 +23,11 @@ func (m *MsgMhfSetGuildManageRight) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfSetGuildManageRight) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.CharID = bf.ReadUint32() + m.Allowed = bf.ReadBool() + m.Unk = bf.ReadBytes(3) + return nil } // Build builds a binary packet from the current data. diff --git a/patch-schema/guild-enumeration.sql b/patch-schema/guild-enumeration.sql index 5ccfb3306..1e7e522a3 100644 --- a/patch-schema/guild-enumeration.sql +++ b/patch-schema/guild-enumeration.sql @@ -3,4 +3,7 @@ BEGIN; ALTER TABLE IF EXISTS public.guilds ADD COLUMN IF NOT EXISTS recruiting bool NOT NULL DEFAULT true; +ALTER TABLE IF EXISTS public.guild_characters + ADD COLUMN IF NOT EXISTS recruiter bool NOT NULL DEFAULT false; + END; \ No newline at end of file diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 4265e4a19..fcaddbaf9 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -889,7 +889,9 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(guild.SubMotto) // Unk appears to be static - bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + + bf.WriteBool(!guild.Recruiting) if characterGuildData == nil || characterGuildData.IsApplicant { bf.WriteUint16(0x00) @@ -1385,7 +1387,8 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) { for _, member := range members { bf.WriteUint32(member.CharID) - bf.WriteUint32(0x0) + bf.WriteBool(member.Recruiter) + bf.WriteBytes(make([]byte, 3)) } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) @@ -1877,7 +1880,12 @@ func handleMsgMhfGenerateUdGuildMap(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfUpdateGuild(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfSetGuildManageRight) + s.server.db.Exec("UPDATE guild_characters SET recruiter=$1 WHERE character_id=$2", pkt.Allowed, pkt.CharID) + // TODO: What is this supposed to return? This works for now + doAckBufSucceed(s, pkt.AckHandle, []byte{0x01}) +} func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateInvGuild) diff --git a/server/channelserver/handlers_guild_member.go b/server/channelserver/handlers_guild_member.go index db9623b4a..5fa83fb6b 100644 --- a/server/channelserver/handlers_guild_member.go +++ b/server/channelserver/handlers_guild_member.go @@ -16,6 +16,7 @@ type GuildMember struct { IsApplicant bool `db:"is_applicant"` OrderIndex uint8 `db:"order_index"` LastLogin uint32 `db:"last_login"` + Recruiter bool `db:"recruiter"` AvoidLeadership bool `db:"avoid_leadership"` IsLeader bool `db:"is_leader"` HRP uint16 `db:"hrp"` @@ -43,36 +44,33 @@ func (gm *GuildMember) Save(s *Session) error { return nil } -//TODO add the recruiter permission to this check when it exists -func (gm *GuildMember) IsRecruiter() bool { - return gm.IsLeader || gm.IsSubLeader() -} - const guildMembersSelectSQL = ` -SELECT g.id as guild_id, - joined_at, - c.name, - character.character_id, - coalesce(gc.order_index, 0) as order_index, - c.last_login, - coalesce(gc.avoid_leadership, false) as avoid_leadership, - c.hrp, - c.gr, - c.weapon_id, - c.weapon_type, - character.is_applicant, - CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader -FROM ( - SELECT character_id, true as is_applicant, guild_id - FROM guild_applications ga - WHERE ga.application_type = 'applied' - UNION - SELECT character_id, false as is_applicant, guild_id - FROM guild_characters gc - ) character - JOIN characters c on character.character_id = c.id - LEFT JOIN guild_characters gc ON gc.character_id = character.character_id - JOIN guilds g ON g.id = character.guild_id +SELECT + g.id as guild_id, + joined_at, + c.name, + character.character_id, + coalesce(gc.order_index, 0) as order_index, + c.last_login, + coalesce(gc.recruiter, false) as recruiter, + coalesce(gc.avoid_leadership, false) as avoid_leadership, + c.hrp, + c.gr, + c.weapon_id, + c.weapon_type, + character.is_applicant, + CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader + FROM ( + SELECT character_id, true as is_applicant, guild_id + FROM guild_applications ga + WHERE ga.application_type = 'applied' + UNION + SELECT character_id, false as is_applicant, guild_id + FROM guild_characters gc + ) character + JOIN characters c on character.character_id = c.id + LEFT JOIN guild_characters gc ON gc.character_id = character.character_id + JOIN guilds g ON g.id = character.guild_id ` func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) { diff --git a/server/channelserver/handlers_guild_scout.go b/server/channelserver/handlers_guild_scout.go index ec04835ef..4bcbb6b91 100644 --- a/server/channelserver/handlers_guild_scout.go +++ b/server/channelserver/handlers_guild_scout.go @@ -21,7 +21,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) { panic(err) } - if actorCharGuildData == nil || !actorCharGuildData.IsRecruiter() { + if actorCharGuildData == nil || !actorCharGuildData.Recruiter { doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) return } @@ -104,7 +104,7 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) { panic(err) } - if guildCharData == nil || !guildCharData.IsRecruiter() { + if guildCharData == nil || !guildCharData.Recruiter { doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) return } From 05db1922d5df8b677b20e0389d717fb5a31980d1 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 10:10:03 +1000 Subject: [PATCH 13/51] launcher improvements --- www/erupe/index.html | 9 +++++++++ www/erupe/js/script.js | 30 ++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/www/erupe/index.html b/www/erupe/index.html index 09047c5e1..57aa1c568 100644 --- a/www/erupe/index.html +++ b/www/erupe/index.html @@ -71,6 +71,15 @@

Important Updates

    +
  • +
    2022-08-02
    + +
  • 2022-05-03
    diff --git a/www/erupe/js/script.js b/www/erupe/js/script.js index 5288414fb..5e75c7aad 100644 --- a/www/erupe/js/script.js +++ b/www/erupe/js/script.js @@ -1,5 +1,6 @@ var __mhf_launcher = {}; var loginScreen = true; +var loggingIn = false; var doingAuto = false; var uids; var selectedUid; @@ -259,6 +260,11 @@ function switchPrompt() { } function doLogin(option) { + if (loggingIn) { + return; + } else { + loggingIn = true; + } let username = document.getElementById('username').value; let password = document.getElementById('password').value; if (username == '') { @@ -289,6 +295,7 @@ function checkAuth() { setTimeout(checkAuth, 10); return; } else if (loginResult == 'AUTH_SUCCESS') { + loggingIn = false; saveAccount(); addLog('Connected.', 'good'); if (doingAuto) { @@ -300,6 +307,7 @@ function checkAuth() { switchPrompt(); } } else { + loggingIn = false; addLog('Error logging in: '+loginResult+':'+window.external.getSignResult(), 'error'); } document.getElementById('processing').style.display = 'none'; @@ -479,8 +487,26 @@ function doEval() { function init() { document.addEventListener('keypress', function(e) { - if (e.key == '~') { - document.getElementById('dev').style.display = 'block'; + switch (e.key) { + case '~': + document.getElementById('dev').style.display = 'block'; + break; + case 'Enter': + if (loginScreen) { + doLogin() + } else { + soundLogin();launch() + } + break; + case ',': + if (!loginScreen) { + soundOk();charselScrollUp() + } + break; + case '.': + if (!loginScreen) { + soundOk();charselScrollDown() + } } }); let unselectable = document.getElementsByClassName('unselectable'); From 2570dda0665f7ca58df9e8d92613239aeab9522a Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 10:34:22 +1000 Subject: [PATCH 14/51] revert road shop changes --- patch-schema/revert-road-shop.sql | 9 +++++ server/channelserver/handlers_shop_gacha.go | 45 +++------------------ 2 files changed, 15 insertions(+), 39 deletions(-) create mode 100644 patch-schema/revert-road-shop.sql diff --git a/patch-schema/revert-road-shop.sql b/patch-schema/revert-road-shop.sql new file mode 100644 index 000000000..830f3aa0f --- /dev/null +++ b/patch-schema/revert-road-shop.sql @@ -0,0 +1,9 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.normal_shop_items + DROP COLUMN IF EXISTS enable_weeks; + +ALTER TABLE IF EXISTS public.shop_item_state + DROP COLUMN IF EXISTS week; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 8eca5b6c1..7ee712c1a 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -2,11 +2,8 @@ package channelserver import ( "encoding/hex" - "fmt" - "strings" "time" - //"erupe-ce/common/stringsupport" "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" "github.com/lib/pq" @@ -14,16 +11,6 @@ import ( "go.uber.org/zap" ) -func contains(s []string, str string) bool { - for _, v := range s { - if v == str { - return true - } - } - - return false -} - func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateShop) // SHOP TYPES: @@ -162,27 +149,19 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } } else { - _, week := time.Now().ISOWeek() - season := fmt.Sprintf("%d", week%4) - shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills, COALESCE(enable_weeks, '') FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID) + shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID) if err != nil { panic(err) } var ItemHash, entryCount int var itemID, Points, TradeQuantity, rankReqLow, rankReqHigh, rankReqG, storeLevelReq, maximumQuantity, boughtQuantity, roadFloorsRequired, weeklyFatalisKills, charQuantity uint16 - var itemWeeks string resp := byteframe.NewByteFrame() resp.WriteUint32(0) // total defs for shopEntries.Next() { - err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills, &itemWeeks) + err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills) if err != nil { panic(err) } - - if len(itemWeeks) > 0 && !contains(strings.Split(itemWeeks, ","), season) { - continue - } - resp.WriteUint32(uint32(ItemHash)) resp.WriteUint16(0) // unk, always 0 in existing packets resp.WriteUint16(itemID) @@ -195,13 +174,9 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint16(storeLevelReq) resp.WriteUint16(maximumQuantity) if maximumQuantity > 0 { - var itemWeek int - err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0), COALESCE(week,-1) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity, &itemWeek) + err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity) if err != nil { resp.WriteUint16(0) - } else if pkt.ShopID == 7 && itemWeek >= 0 && itemWeek != week { - clearShopItemState(s, s.charID, uint32(ItemHash)) - resp.WriteUint16(0) } else { resp.WriteUint16(charQuantity) } @@ -224,7 +199,6 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { } func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) { - _, week := time.Now().ISOWeek() // writing out to an editable shop enumeration pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop) if pkt.DataSize == 10 { @@ -232,10 +206,10 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) { _ = bf.ReadUint16() // unk, always 1 in examples itemHash := bf.ReadUint32() buyCount := bf.ReadUint32() - _, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity, week) - VALUES ($1,$2,$3,$4) ON CONFLICT (char_id, itemhash) + _, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity) + VALUES ($1,$2,$3) ON CONFLICT (char_id, itemhash) DO UPDATE SET usedquantity = shop_item_state.usedquantity + $3 - WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount, week) + WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount) if err != nil { s.logger.Fatal("Failed to update shop_item_state in db", zap.Error(err)) } @@ -243,13 +217,6 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } -func clearShopItemState(s *Session, charId uint32, itemHash uint32) { - _, err := s.server.db.Exec(`DELETE FROM shop_item_state WHERE char_id=$1 AND itemhash=$2`, charId, itemHash) - if err != nil { - s.logger.Fatal("Failed to delete shop_item_state in db", zap.Error(err)) - } -} - func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) { // returns number of times the gacha was played, will need persistent db stuff pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory) From bf851b5c673bd7b7f745f609c7a14d2831d65653 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 10:34:38 +1000 Subject: [PATCH 15/51] add missing mail query --- patch-schema/missing-mail.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 patch-schema/missing-mail.sql diff --git a/patch-schema/missing-mail.sql b/patch-schema/missing-mail.sql new file mode 100644 index 000000000..601c77d4f --- /dev/null +++ b/patch-schema/missing-mail.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.mail + ADD COLUMN IF NOT EXISTS locked boolean NOT NULL DEFAULT false; + +END; \ No newline at end of file From ed11b5ced975765b526b94defe7293b9a10a34e9 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 10:51:31 +1000 Subject: [PATCH 16/51] implement token verification --- config.json | 1 + config/config.go | 1 + server/channelserver/handlers.go | 25 ++++++++++++------------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/config.json b/config.json index 2c688ec51..8115b7c81 100644 --- a/config.json +++ b/config.json @@ -17,6 +17,7 @@ "TournamentEvent": 0, "MezFesEvent": true, "DisableMailItems": true, + "DisableTokenCheck": false, "SaveDumps": { "Enabled": true, "OutputDir": "savedata" diff --git a/config/config.go b/config/config.go index e4f916321..7b353c811 100644 --- a/config/config.go +++ b/config/config.go @@ -36,6 +36,7 @@ type DevModeOptions struct { FestaEvent int // Hunter's Festa event status TournamentEvent int // VS Tournament event status MezFesEvent bool // MezFes status + DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) DisableMailItems bool // Hack to prevent english versions of MHF from crashing SaveDumps SaveDumpOptions } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index e34bd7f7c..143fec436 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -135,20 +135,19 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysLogin) - rights := uint32(0x0E) - // 0e with normal sub 4e when having premium - // 01 = Character can take quests at allows - // 02 = Hunter Life, normal quests core sub - // 03 = Extra Course, extra quests, town boxes, QOL course, core sub - // 06 = Premium Course, standard 'premium' which makes ranking etc. faster - // 06 0A 0B = Boost Course, just actually 3 subs combined - // 08 09 1E = N Course, gives you the benefits of being in a netcafe (extra quests, N Points, daily freebies etc.) minimal and pointless - // 0C = N Boost course, ultra luxury course that ruins the game if in use - err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", pkt.CharID0).Scan(&rights) - if err != nil { - panic(err) + if s.server.erupeConfig.DevMode && !s.server.erupeConfig.DevModeOptions.DisableTokenCheck { + var token string + err := s.server.db.QueryRow("SELECT token FROM sign_sessions WHERE token=$1", pkt.LoginTokenString).Scan(&token) + if err != nil { + s.rawConn.Close() + s.logger.Warn(fmt.Sprintf("Invalid login token, offending CID: (%d)", pkt.CharID0)) + return + } } + rights := uint32(0x0E) + s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", pkt.CharID0).Scan(&rights) + s.Lock() s.charID = pkt.CharID0 s.rights = rights @@ -157,7 +156,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp - _, err = s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID) + _, err := s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID) if err != nil { panic(err) } From e40ac7539c97973dbbb2fdeb83cb7becc16d5e59 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 10:53:38 +1000 Subject: [PATCH 17/51] update login notice --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 8115b7c81..2fc4ffc9d 100644 --- a/config.json +++ b/config.json @@ -5,7 +5,7 @@ "devmodeoptions": { "serverName" : "", "hideLoginNotice": false, - "loginNotice": "
    Welcome to Erupe SU9!
    Erupe is experimental software, we are not liable for any
    issues caused by installing the software!

    ■Report bugs on Discord!

    ■Test everything!

    ■Don't talk to softlocking NPCs!

    ■Fork the code on GitHub!

    Thank you to all of the contributors,

    this wouldn't exist without you.", + "loginNotice": "
    Welcome to Erupe SU9 (Patch 1)!
    Erupe is experimental software, we are not liable for any
    issues caused by installing the software!

    ■Report bugs on Discord!

    ■Test everything!

    ■Don't talk to softlocking NPCs!

    ■Fork the code on GitHub!

    Thank you to all of the contributors,

    this wouldn't exist without you.", "cleandb": false, "maxlauncherhr": false, "LogInboundMessages": false, From 556198af72b6e22df382c122363899b916999d1b Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 21:01:32 +1000 Subject: [PATCH 18/51] softfail on paddedstring and login cleanup --- common/stringsupport/string_convert.go | 2 +- server/channelserver/handlers.go | 2 +- server/signserver/dbutils.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/stringsupport/string_convert.go b/common/stringsupport/string_convert.go index 0a9f4cb10..75951b4ca 100644 --- a/common/stringsupport/string_convert.go +++ b/common/stringsupport/string_convert.go @@ -106,7 +106,7 @@ func PaddedString(x string, size uint, t bool) []byte { e := japanese.ShiftJIS.NewEncoder() xt, _, err := transform.String(e, x) if err != nil { - panic(err) + return make([]byte, 0) } x = xt } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 143fec436..60e120c42 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -135,7 +135,7 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysLogin) - if s.server.erupeConfig.DevMode && !s.server.erupeConfig.DevModeOptions.DisableTokenCheck { + if !s.server.erupeConfig.DevModeOptions.DisableTokenCheck { var token string err := s.server.db.QueryRow("SELECT token FROM sign_sessions WHERE token=$1", pkt.LoginTokenString).Scan(&token) if err != nil { diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index a7750b59f..8f1b89a01 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -87,7 +87,7 @@ type character struct { } func (s *Server) getCharactersForUser(uid int) ([]character, error) { - characters := []character{} + characters := make([]character, 0) err := s.db.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE user_id = $1 AND deleted = false", uid) if err != nil { return nil, err @@ -126,7 +126,7 @@ func (s *Server) getFriendsForCharacters(chars []character) []members { friendQuery += " OR id=" } } - charFriends := []members{} + charFriends := make([]members, 0) err = s.db.Select(&charFriends, friendQuery) if err != nil { continue @@ -153,7 +153,7 @@ func (s *Server) getGuildmatesForCharacters(chars []character) []members { if err != nil { continue } - charGuildmates := []members{} + charGuildmates := make([]members, 0) err = s.db.Select(&charGuildmates, "SELECT character_id AS id, c.name FROM guild_characters gc JOIN characters c ON c.id = gc.character_id WHERE guild_id=$1 AND character_id!=$2", guildID, char.ID) if err != nil { continue From dd883a22168bb48f9c87799c740934c84a0fd8ed Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 21:20:27 +1000 Subject: [PATCH 19/51] Update go.yml --- .github/workflows/go.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cb7c147c4..2f56c0f75 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -4,7 +4,7 @@ on: [push] jobs: build: - runs-on: windows-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -14,13 +14,28 @@ jobs: with: go-version: 1.18 - - name: Build - run: go build -v + - name: Build Linux-amd64 + run: env GOOS=linux GOARCH=amd64 go build -v - - name: Upload artifacts + - name: Upload Linux-amd64 artifacts uses: actions/upload-artifact@v3 with: - name: Erupe + name: Linux-amd64 + path: | + ./erupe-ce + ./config.json + ./www/ + ./savedata/ + ./bin/ + ./RoadShopItems.csv + + - name: Build Windows-amd64 + run: env GOOS=windows GOARCH=amd64 go build -v + + - name: Upload Windows-amd64 artifacts + uses: actions/upload-artifact@v3 + with: + name: Windows-amd64 path: | ./erupe-ce.exe ./config.json From 816ff0eac582d399da5930056cd6bbde03e5743e Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 22:55:32 +1000 Subject: [PATCH 20/51] correct failsafe size on PaddedString transform fail --- common/stringsupport/string_convert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/stringsupport/string_convert.go b/common/stringsupport/string_convert.go index 75951b4ca..84574375b 100644 --- a/common/stringsupport/string_convert.go +++ b/common/stringsupport/string_convert.go @@ -106,7 +106,7 @@ func PaddedString(x string, size uint, t bool) []byte { e := japanese.ShiftJIS.NewEncoder() xt, _, err := transform.String(e, x) if err != nil { - return make([]byte, 0) + return make([]byte, size) } x = xt } From 2f35823e1e3638c52e9506a1fc7e8c399168bdce Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 23:04:55 +1000 Subject: [PATCH 21/51] fix enumerate client and handle type L stages --- network/mhfpacket/msg_sys_enumerate_client.go | 6 +++--- server/channelserver/handlers_clients.go | 6 +++++- server/channelserver/handlers_stage.go | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/network/mhfpacket/msg_sys_enumerate_client.go b/network/mhfpacket/msg_sys_enumerate_client.go index af67fa173..5318549a6 100644 --- a/network/mhfpacket/msg_sys_enumerate_client.go +++ b/network/mhfpacket/msg_sys_enumerate_client.go @@ -3,8 +3,8 @@ package mhfpacket import ( "errors" - "erupe-ce/common/byteframe" "erupe-ce/common/bfutil" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" ) @@ -13,7 +13,7 @@ import ( type MsgSysEnumerateClient struct { AckHandle uint32 Unk0 uint8 // Hardcoded 1 in the client - Unk1 uint8 + Get uint8 StageID string } @@ -26,7 +26,7 @@ func (m *MsgSysEnumerateClient) Opcode() network.PacketID { func (m *MsgSysEnumerateClient) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint8() - m.Unk1 = bf.ReadUint8() + m.Get = bf.ReadUint8() stageIDLength := bf.ReadUint8() m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) return nil diff --git a/server/channelserver/handlers_clients.go b/server/channelserver/handlers_clients.go index 611cd1239..12e8540b3 100644 --- a/server/channelserver/handlers_clients.go +++ b/server/channelserver/handlers_clients.go @@ -24,7 +24,11 @@ func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) { resp := byteframe.NewByteFrame() stage.RLock() var clients []uint32 - switch pkt.Unk1 { + switch pkt.Get { + case 0: // All + for _, cid := range stage.clients { + clients = append(clients, cid) + } case 1: // Not ready for cid, ready := range stage.reservedClientSlots { if !ready { diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index d40c6522a..4fcdcbfc2 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -121,7 +121,7 @@ func destructEmptyStages(s *Session) { defer s.server.Unlock() for _, stage := range s.server.stages { // Destroy empty Quest/My series/Guild stages. - if stage.id[3:5] == "Qs" || stage.id[3:5] == "Ms" || stage.id[3:5] == "Gs" { + if stage.id[3:5] == "Qs" || stage.id[3:5] == "Ms" || stage.id[3:5] == "Gs" || stage.id[3:5] == "Ls" { if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 { delete(s.server.stages, stage.id) s.logger.Debug("Destructed stage", zap.String("stage.id", stage.id)) @@ -367,7 +367,7 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { } // Check for valid stage type - if sid[3:5] != "Qs" && sid[3:5] != "Ms" { + if sid[3:5] != "Qs" && sid[3:5] != "Ms" && sid[3:5] != "Ls" { continue } From cdbc11c4b2034cfe2d9d498c1ad09888c3ba7ed4 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 23:11:19 +1000 Subject: [PATCH 22/51] add option to disable soft-crashing --- config.json | 1 + config/config.go | 7 ++++--- main.go | 8 +++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index 2fc4ffc9d..c5bf94ca3 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,7 @@ { "host_ip": "127.0.0.1", "bin_path": "bin", + "DisableSoftCrash": false, "devmode": true, "devmodeoptions": { "serverName" : "", diff --git a/config/config.go b/config/config.go index 7b353c811..1f9587a50 100644 --- a/config/config.go +++ b/config/config.go @@ -9,9 +9,10 @@ import ( // Config holds the global server-wide config. type Config struct { - HostIP string `mapstructure:"host_ip"` - BinPath string `mapstructure:"bin_path"` - DevMode bool + HostIP string `mapstructure:"host_ip"` + BinPath string `mapstructure:"bin_path"` + DisableSoftCrash bool + DevMode bool DevModeOptions DevModeOptions Discord Discord diff --git a/main.go b/main.go index d39f284ea..c950d3bef 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,8 @@ import ( "go.uber.org/zap" ) +var erupeConfig *config.Config + // Temporary DB auto clean on startup for quick development & testing. func cleanDB(db *sqlx.DB) { _ = db.MustExec("DELETE FROM guild_characters") @@ -29,6 +31,7 @@ func cleanDB(db *sqlx.DB) { } func main() { + var err error zapLogger, _ := zap.NewDevelopment() defer zapLogger.Sync() logger := zapLogger.Named("main") @@ -36,7 +39,7 @@ func main() { logger.Info("Starting Erupe") // Load the configuration. - erupeConfig, err := config.LoadConfig() + erupeConfig, err = config.LoadConfig() if err != nil { preventClose(fmt.Sprintf("Failed to load config: %s", err.Error())) } @@ -209,6 +212,9 @@ func wait() { } func preventClose(text string) { + if erupeConfig.DisableSoftCrash { + os.Exit(0) + } fmt.Println("\nFailed to start Erupe:\n" + text) go wait() fmt.Println("\nPress Enter/Return to exit...") From a7ec76f865a715aad3bd432fe2569a6ffd41dc59 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 23:18:26 +1000 Subject: [PATCH 23/51] document DisableSoftCrash --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 1f9587a50..5009e5aac 100644 --- a/config/config.go +++ b/config/config.go @@ -11,7 +11,7 @@ import ( type Config struct { HostIP string `mapstructure:"host_ip"` BinPath string `mapstructure:"bin_path"` - DisableSoftCrash bool + DisableSoftCrash bool // Disables the 'Press Return to exit' dialog allowing scripts to reboot the server automatically DevMode bool DevModeOptions DevModeOptions From e9cc5cc3e2eec86ba2326722010f4dc3e6495c7c Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 4 Aug 2022 23:52:13 +1000 Subject: [PATCH 24/51] parse host as FQDN or IP --- config.json | 4 ++-- config/config.go | 8 ++++---- main.go | 14 ++++++++++++++ server/entranceserver/make_resp.go | 2 +- server/launcherserver/routes.go | 2 +- server/signserver/dsgn_resp.go | 2 +- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/config.json b/config.json index c5bf94ca3..976756597 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { - "host_ip": "127.0.0.1", - "bin_path": "bin", + "Host": "127.0.0.1", + "BinPath": "bin", "DisableSoftCrash": false, "devmode": true, "devmodeoptions": { diff --git a/config/config.go b/config/config.go index 5009e5aac..8b7daf7ee 100644 --- a/config/config.go +++ b/config/config.go @@ -9,8 +9,8 @@ import ( // Config holds the global server-wide config. type Config struct { - HostIP string `mapstructure:"host_ip"` - BinPath string `mapstructure:"bin_path"` + Host string `mapstructure:"Host"` + BinPath string `mapstructure:"BinPath"` DisableSoftCrash bool // Disables the 'Press Return to exit' dialog allowing scripts to reboot the server automatically DevMode bool @@ -140,8 +140,8 @@ func LoadConfig() (*Config, error) { return nil, err } - if c.HostIP == "" { - c.HostIP = getOutboundIP4().To4().String() + if c.Host == "" { + c.Host = getOutboundIP4().To4().String() } return c, nil diff --git a/main.go b/main.go index c950d3bef..3638a0093 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "math/rand" + "net" "os" "os/signal" "syscall" @@ -48,6 +49,19 @@ func main() { preventClose("Database password is blank") } + if net.ParseIP(erupeConfig.Host) == nil { + ips, _ := net.LookupIP(erupeConfig.Host) + for _, ip := range ips { + if ip != nil { + erupeConfig.Host = ip.String() + break + } + } + if net.ParseIP(erupeConfig.Host) == nil { + preventClose("Invalid host address") + } + } + // Discord bot var discordBot *discordbot.DiscordBot = nil diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index 2a46a3630..c0358faef 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -28,7 +28,7 @@ func encodeServerInfo(config *config.Config, s *Server) []byte { panic(err) } if si.IP == "" { - si.IP = config.HostIP + si.IP = config.Host } bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4())) bf.WriteUint16(16 + uint16(serverIdx)) diff --git a/server/launcherserver/routes.go b/server/launcherserver/routes.go index c951bf08a..59c47bba7 100644 --- a/server/launcherserver/routes.go +++ b/server/launcherserver/routes.go @@ -12,7 +12,7 @@ import ( func serverList(s *Server, w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ``, - s.erupeConfig.HostIP, + s.erupeConfig.Host, s.erupeConfig.Sign.Port, ) } diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 17701002a..fee38c1d3 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -47,7 +47,7 @@ func (s *Session) makeSignInResp(uid int) []byte { bf.WriteUint32(0xFFFFFFFF) // login_token_number bf.WriteBytes([]byte(token)) // login_token bf.WriteUint32(uint32(time.Now().Unix())) // current time - ps.Uint8(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.HostIP, s.server.erupeConfig.Entrance.Port), false) + ps.Uint8(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.Host, s.server.erupeConfig.Entrance.Port), false) lastPlayed := uint32(0) for _, char := range chars { From 08a7b91e11800b9e4460e654aa12da4078b75031 Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 5 Aug 2022 01:57:56 +1000 Subject: [PATCH 25/51] handle TransitMessage --- main.go | 8 ++- network/mhfpacket/msg_mhf_transit_message.go | 28 ++++---- server/channelserver/handlers.go | 73 ++++++++++++++++++-- server/channelserver/sys_channel_server.go | 6 +- 4 files changed, 94 insertions(+), 21 deletions(-) diff --git a/main.go b/main.go index 3638a0093..c5ac726b3 100644 --- a/main.go +++ b/main.go @@ -180,7 +180,13 @@ func main() { DB: db, DiscordBot: discordBot, }) - err = c.Start(int(ce.Port)) + if ee.IP == "" { + c.IP = erupeConfig.Host + } else { + c.IP = ee.IP + } + c.Port = ce.Port + err = c.Start() if err != nil { preventClose(fmt.Sprintf("Failed to start channel server: %s", err.Error())) } else { diff --git a/network/mhfpacket/msg_mhf_transit_message.go b/network/mhfpacket/msg_mhf_transit_message.go index 32dba46d6..1d15c6d42 100644 --- a/network/mhfpacket/msg_mhf_transit_message.go +++ b/network/mhfpacket/msg_mhf_transit_message.go @@ -1,20 +1,20 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfTransitMessage represents the MSG_MHF_TRANSIT_MESSAGE type MsgMhfTransitMessage struct { - AckHandle uint32 - Unk0 uint8 - Unk1 uint8 - Unk2 uint16 - MessageData []byte + AckHandle uint32 + Unk0 uint8 + Unk1 uint8 + SearchType uint16 + MessageData []byte } // Opcode returns the ID associated with this packet type. @@ -24,12 +24,12 @@ func (m *MsgMhfTransitMessage) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfTransitMessage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() - m.Unk1 = bf.ReadUint8() - m.Unk2 = bf.ReadUint16() - m.MessageData = bf.ReadBytes(uint(bf.ReadUint16())) - return nil + m.AckHandle = bf.ReadUint32() + m.Unk0 = bf.ReadUint8() + m.Unk1 = bf.ReadUint8() + m.SearchType = bf.ReadUint16() + m.MessageData = bf.ReadBytes(uint(bf.ReadUint16())) + return nil } // Build builds a binary packet from the current data. diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 60e120c42..b4d979c7e 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -4,7 +4,11 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "erupe-ce/common/stringsupport" "fmt" + "io" + "net" + "strings" "io/ioutil" "math/bits" @@ -341,10 +345,71 @@ func handleMsgSysRightsReload(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfTransitMessage) - // TODO: figure out what this is supposed to return - // probably what world+land the targeted character is on? - // stubbed response will just say user not found - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + // 1 -> CID + // 2 -> Name + // 4 -> Group + resp := byteframe.NewByteFrame() + resp.WriteUint16(0) + var count uint16 + switch pkt.SearchType { + case 1: + bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) + CharID := bf.ReadUint32() + for _, c := range s.server.Channels { + for _, session := range c.sessions { + if session.charID == CharID { + count++ + sessionName := stringsupport.UTF8ToSJIS(session.Name) + sessionStage := stringsupport.UTF8ToSJIS(session.stageID) + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + resp.WriteUint16(c.Port) + resp.WriteUint32(session.charID) + resp.WriteBool(true) + resp.WriteUint8(uint8(len(sessionName) + 1)) + resp.WriteUint16(0x180) // lenUserBinary + resp.WriteBytes(make([]byte, 40)) + resp.WriteUint8(uint8(len(sessionStage) + 1)) + resp.WriteBytes(make([]byte, 8)) + resp.WriteNullTerminatedBytes(sessionName) + resp.WriteBytes(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]) + resp.WriteNullTerminatedBytes(sessionStage) + } + } + } + case 2: + bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) + bf.ReadUint16() // lenSearchTerm + bf.ReadUint16() // maxResults + bf.ReadUint8() // Unk + searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + for _, c := range s.server.Channels { + for _, session := range c.sessions { + if count == 100 { + break + } + if strings.Contains(session.Name, searchTerm) { + count++ + sessionName := stringsupport.UTF8ToSJIS(session.Name) + sessionStage := stringsupport.UTF8ToSJIS(session.stageID) + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + resp.WriteUint16(c.Port) + resp.WriteUint32(session.charID) + resp.WriteBool(true) + resp.WriteUint8(uint8(len(sessionName) + 1)) + resp.WriteUint16(0x180) // lenUserBinary + resp.WriteBytes(make([]byte, 40)) + resp.WriteUint8(uint8(len(sessionStage) + 1)) + resp.WriteBytes(make([]byte, 8)) + resp.WriteNullTerminatedBytes(sessionName) + resp.WriteBytes(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]) + resp.WriteNullTerminatedBytes(sessionStage) + } + } + } + } + resp.Seek(0, io.SeekStart) + resp.WriteUint16(count) + doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } func handleMsgCaExchangeItem(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 69335280c..a23ec2834 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -54,6 +54,8 @@ type Server struct { sync.Mutex Channels []*Server ID uint16 + IP string + Port uint16 logger *zap.Logger db *sqlx.DB erupeConfig *config.Config @@ -196,8 +198,8 @@ func NewServer(config *Config) *Server { } // Start starts the server in a new goroutine. -func (s *Server) Start(port int) error { - l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) +func (s *Server) Start() error { + l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Port)) if err != nil { return err } From 820563dc4c4bad66269399a6627e520b8b6220b7 Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 5 Aug 2022 07:39:37 +1000 Subject: [PATCH 26/51] matchmaking support --- .../mhfpacket/msg_sys_acquire_semaphore.go | 19 ++-- server/channelserver/handlers.go | 102 ++++++++++++++++-- server/channelserver/handlers_semaphore.go | 11 +- server/signserver/dsgn_resp.go | 2 +- 4 files changed, 118 insertions(+), 16 deletions(-) diff --git a/network/mhfpacket/msg_sys_acquire_semaphore.go b/network/mhfpacket/msg_sys_acquire_semaphore.go index 89b974ff4..2be7284d9 100644 --- a/network/mhfpacket/msg_sys_acquire_semaphore.go +++ b/network/mhfpacket/msg_sys_acquire_semaphore.go @@ -1,15 +1,19 @@ package mhfpacket -import ( - "errors" +import ( + "errors" + "erupe-ce/common/bfutil" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgSysAcquireSemaphore represents the MSG_SYS_ACQUIRE_SEMAPHORE -type MsgSysAcquireSemaphore struct{} +type MsgSysAcquireSemaphore struct { + AckHandle uint32 + SemaphoreID string +} // Opcode returns the ID associated with this packet type. func (m *MsgSysAcquireSemaphore) Opcode() network.PacketID { @@ -18,7 +22,10 @@ func (m *MsgSysAcquireSemaphore) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysAcquireSemaphore) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + SemaphoreIDLength := bf.ReadUint8() + m.SemaphoreID = string(bfutil.UpToNull(bf.ReadBytes(uint(SemaphoreIDLength)))) + return nil } // Build builds a binary packet from the current data. diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index b4d979c7e..1cfe9e3ff 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -345,14 +345,11 @@ func handleMsgSysRightsReload(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfTransitMessage) - // 1 -> CID - // 2 -> Name - // 4 -> Group resp := byteframe.NewByteFrame() resp.WriteUint16(0) var count uint16 switch pkt.SearchType { - case 1: + case 1: // CID bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) CharID := bf.ReadUint32() for _, c := range s.server.Channels { @@ -366,17 +363,17 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint32(session.charID) resp.WriteBool(true) resp.WriteUint8(uint8(len(sessionName) + 1)) - resp.WriteUint16(0x180) // lenUserBinary + resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]))) resp.WriteBytes(make([]byte, 40)) resp.WriteUint8(uint8(len(sessionStage) + 1)) resp.WriteBytes(make([]byte, 8)) resp.WriteNullTerminatedBytes(sessionName) - resp.WriteBytes(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]) + resp.WriteBytes(c.userBinaryParts[userBinaryPartID{session.charID, 3}]) resp.WriteNullTerminatedBytes(sessionStage) } } } - case 2: + case 2: // Name bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) bf.ReadUint16() // lenSearchTerm bf.ReadUint16() // maxResults @@ -396,7 +393,7 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint32(session.charID) resp.WriteBool(true) resp.WriteUint8(uint8(len(sessionName) + 1)) - resp.WriteUint16(0x180) // lenUserBinary + resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{session.charID, 3}]))) resp.WriteBytes(make([]byte, 40)) resp.WriteUint8(uint8(len(sessionStage) + 1)) resp.WriteBytes(make([]byte, 8)) @@ -406,6 +403,95 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } } } + case 3: // Enumerate Party + bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) + ip := bf.ReadBytes(4) + ipString := fmt.Sprintf("%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]) + port := bf.ReadUint16() + bf.ReadUint16() // lenStage + maxResults := bf.ReadUint16() + bf.ReadBytes(1) + stageID := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + for _, c := range s.server.Channels { + if c.IP == ipString && c.Port == port { + for _, stage := range c.stages { + if stage.id == stageID { + if count == maxResults { + break + } + for session := range stage.clients { + count++ + sessionStage := stringsupport.UTF8ToSJIS(session.stageID) + sessionName := stringsupport.UTF8ToSJIS(session.Name) + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + resp.WriteUint16(c.Port) + resp.WriteUint32(session.charID) + resp.WriteUint8(uint8(len(sessionStage) + 1)) + resp.WriteUint8(uint8(len(sessionName) + 1)) + resp.WriteUint8(0) + resp.WriteUint8(7) // lenBinary + resp.WriteBytes(make([]byte, 48)) + resp.WriteNullTerminatedBytes(sessionStage) + resp.WriteNullTerminatedBytes(sessionName) + resp.WriteUint16(999) // HR + resp.WriteUint16(999) // GR + resp.WriteBytes([]byte{0x06, 0x10, 0x00}) // Unk + } + } + } + } + } + case 4: // Find Party + bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) + bf.ReadUint8() + maxResults := bf.ReadUint16() + bf.ReadUint8() + bf.ReadUint8() + partyType := bf.ReadUint16() + _ = bf.DataFromCurrent() // Restrictions + var stagePrefix string + switch partyType { + case 0: // Public Bar + stagePrefix = "sl2Ls210" + case 1: // Tokotoko Partnya + stagePrefix = "sl2Ls463" + case 2: // Hunting Prowess Match + stagePrefix = "sl2Ls286" + case 3: // Volpakkun Together + stagePrefix = "sl2Ls465" + case 5: // Quick Party + // Unk + } + for _, c := range s.server.Channels { + for _, stage := range c.stages { + if count == maxResults { + break + } + if strings.HasPrefix(stage.id, stagePrefix) { + count++ + sessionStage := stringsupport.UTF8ToSJIS(stage.id) + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + resp.WriteUint16(c.Port) + + // TODO: This is half right, could be trimmed + resp.WriteUint16(0) + resp.WriteUint16(uint16(len(stage.clients))) + resp.WriteUint16(uint16(len(stage.clients))) + resp.WriteUint16(stage.maxPlayers) + resp.WriteUint16(0) + resp.WriteUint16(uint16(len(stage.clients))) + // + + resp.WriteUint16(uint16(len(sessionStage) + 1)) + resp.WriteUint8(1) + resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 1}]))) + resp.WriteBytes(make([]byte, 16)) + resp.WriteNullTerminatedBytes(sessionStage) + resp.WriteBytes([]byte{0x00}) + resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 1}]) + } + } + } } resp.Seek(0, io.SeekStart) resp.WriteUint16(count) diff --git a/server/channelserver/handlers_semaphore.go b/server/channelserver/handlers_semaphore.go index e7b9834ab..6659bd2da 100644 --- a/server/channelserver/handlers_semaphore.go +++ b/server/channelserver/handlers_semaphore.go @@ -124,7 +124,16 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { } func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { - //pkt := p.(*mhfpacket.MsgSysAcquireSemaphore) + pkt := p.(*mhfpacket.MsgSysAcquireSemaphore) + if sema, exists := s.server.semaphore[pkt.SemaphoreID]; exists { + sema.clients[s] = s.charID + bf := byteframe.NewByteFrame() + bf.WriteUint32(sema.id) + doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) + } else { + doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) + } + } func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index fee38c1d3..80764c408 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -136,7 +136,7 @@ func (s *Session) makeSignInResp(uid int) []byte { bf.WriteUint32(uint32(channelserver.Time_Current_Adjusted().Add(24 * time.Hour * 7).Unix())) bf.WriteUint8(2) // Unk bf.WriteUint32(20) // Single tickets - bf.WriteUint32(0) // Group tickets + bf.WriteUint32(10) // Group tickets bf.WriteUint8(8) // Stalls open bf.WriteUint8(0xA) // Unk bf.WriteUint8(0x3) // Pachinko From 56841a5ab37e59c05f27d2f8f63388a515cf941b Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 5 Aug 2022 07:42:45 +1000 Subject: [PATCH 27/51] add config option to toggle MF MP game --- config.json | 1 + config/config.go | 1 + server/signserver/dsgn_resp.go | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index 976756597..29af1fb8d 100644 --- a/config.json +++ b/config.json @@ -17,6 +17,7 @@ "FestaEvent": 0, "TournamentEvent": 0, "MezFesEvent": true, + "MezFesAlt": false, "DisableMailItems": true, "DisableTokenCheck": false, "SaveDumps": { diff --git a/config/config.go b/config/config.go index 8b7daf7ee..b025e7a0a 100644 --- a/config/config.go +++ b/config/config.go @@ -37,6 +37,7 @@ type DevModeOptions struct { FestaEvent int // Hunter's Festa event status TournamentEvent int // VS Tournament event status MezFesEvent bool // MezFes status + MezFesAlt bool // Swaps out Volpakkun for Tokotoko DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) DisableMailItems bool // Hack to prevent english versions of MHF from crashing SaveDumps SaveDumpOptions diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 80764c408..650188000 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -128,7 +128,7 @@ func (s *Session) makeSignInResp(uid int) []byte { bf.WriteUint32(0x0A5197DF) mezfes := s.server.erupeConfig.DevModeOptions.MezFesEvent - alt := false + alt := s.server.erupeConfig.DevModeOptions.MezFesAlt if mezfes { // Start time bf.WriteUint32(uint32(channelserver.Time_Current_Adjusted().Add(-5 * time.Minute).Unix())) From 09812fa81b220769a06a012c59f65b0f4b8ce4ea Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 5 Aug 2022 16:38:42 +1000 Subject: [PATCH 28/51] stage improvements --- network/mhfpacket/msg_sys_enumerate_stage.go | 20 ++++++++-------- server/channelserver/handlers_stage.go | 24 +++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/network/mhfpacket/msg_sys_enumerate_stage.go b/network/mhfpacket/msg_sys_enumerate_stage.go index 802b385f5..925f8944d 100644 --- a/network/mhfpacket/msg_sys_enumerate_stage.go +++ b/network/mhfpacket/msg_sys_enumerate_stage.go @@ -1,19 +1,19 @@ package mhfpacket -import ( - "errors" +import ( + "errors" + "erupe-ce/common/stringsupport" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgSysEnumerateStage represents the MSG_SYS_ENUMERATE_STAGE type MsgSysEnumerateStage struct { - AckHandle uint32 - Unk0 uint8 // Hardcoded 1 in the binary - StageIDLength uint8 - StageID string // NULL terminated string. + AckHandle uint32 + Unk0 uint8 // Hardcoded 1 in the binary + StageID string // NULL terminated string. } // Opcode returns the ID associated with this packet type. @@ -25,8 +25,8 @@ func (m *MsgSysEnumerateStage) Opcode() network.PacketID { func (m *MsgSysEnumerateStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint8() - m.StageIDLength = bf.ReadUint8() - m.StageID = string(bf.ReadBytes(uint(m.StageIDLength))) + bf.ReadUint8() + m.StageID = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 4fcdcbfc2..81ddb36f4 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -2,6 +2,7 @@ package channelserver import ( "fmt" + "strings" "time" "erupe-ce/common/byteframe" @@ -198,22 +199,24 @@ func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysLockStage) // TODO(Andoryuuta): What does this packet _actually_ do? + // I think this is supposed to mark a stage as no longer able to accept client reservations doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) { - s.reservationStage.RLock() - defer s.reservationStage.RUnlock() + if s.reservationStage != nil { + s.reservationStage.RLock() + defer s.reservationStage.RUnlock() - for charID := range s.reservationStage.reservedClientSlots { - session := s.server.FindSessionByCharID(charID) - session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) + for charID := range s.reservationStage.reservedClientSlots { + session := s.server.FindSessionByCharID(charID) + session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) + } + + delete(s.server.stages, s.reservationStage.id) } - s.server.Lock() - defer s.server.Unlock() - - delete(s.server.stages, s.reservationStage.id) + destructEmptyStages(s) } func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { @@ -366,8 +369,7 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { continue } - // Check for valid stage type - if sid[3:5] != "Qs" && sid[3:5] != "Ms" && sid[3:5] != "Ls" { + if !strings.Contains(stage.id, pkt.StageID) { continue } From 43beb462212c2e764f14b24016cd9536d6842850 Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 5 Aug 2022 16:41:49 +1000 Subject: [PATCH 29/51] rename EnumerateStage variable --- network/mhfpacket/msg_sys_enumerate_stage.go | 8 ++++---- server/channelserver/handlers_stage.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/network/mhfpacket/msg_sys_enumerate_stage.go b/network/mhfpacket/msg_sys_enumerate_stage.go index 925f8944d..a3f125941 100644 --- a/network/mhfpacket/msg_sys_enumerate_stage.go +++ b/network/mhfpacket/msg_sys_enumerate_stage.go @@ -11,9 +11,9 @@ import ( // MsgSysEnumerateStage represents the MSG_SYS_ENUMERATE_STAGE type MsgSysEnumerateStage struct { - AckHandle uint32 - Unk0 uint8 // Hardcoded 1 in the binary - StageID string // NULL terminated string. + AckHandle uint32 + Unk0 uint8 // Hardcoded 1 in the binary + StagePrefix string // NULL terminated string. } // Opcode returns the ID associated with this packet type. @@ -26,7 +26,7 @@ func (m *MsgSysEnumerateStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint8() bf.ReadUint8() - m.StageID = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.StagePrefix = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 81ddb36f4..4bf19d114 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -369,7 +369,7 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { continue } - if !strings.Contains(stage.id, pkt.StageID) { + if !strings.Contains(stage.id, pkt.StagePrefix) { continue } From ea63e3247c2f62084086db41ef17c497b7d8e0ab Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 5 Aug 2022 17:03:30 +1000 Subject: [PATCH 30/51] fix user binary query --- server/channelserver/handlers_users.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_users.go b/server/channelserver/handlers_users.go index fc4914a90..6db6613c0 100644 --- a/server/channelserver/handlers_users.go +++ b/server/channelserver/handlers_users.go @@ -17,7 +17,7 @@ func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) { s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload s.server.userBinaryPartsLock.Unlock() - err := s.server.db.QueryRow("SELECT type1 FROM user_binaries WHERE id=$1", s.charID) + err := s.server.db.QueryRow("SELECT type2 FROM user_binaries WHERE id=$1", s.charID) if err != nil { s.server.db.Exec("INSERT INTO user_binaries (id) VALUES ($1)", s.charID) } From 0b90dfd458274820f7cafecc93072147848d3b8c Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 5 Aug 2022 20:47:20 +1000 Subject: [PATCH 31/51] fix binpath reference when overriding saves --- server/channelserver/handlers_data.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index d0ae083b0..1f18fe262 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -290,21 +290,14 @@ func dumpSaveData(s *Session, data []byte, suffix string) { func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoaddata) - overrideFile := filepath.Join(".", "bin", "save_override.bin") - var data []byte - - if _, err := os.Stat(overrideFile); err == nil { - file, err := os.Open(overrideFile) - if err != nil { - panic(err) - } - data, err := ioutil.ReadAll(file) - if err != nil { - panic(err) - } + if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin")); err == nil { + data, _ := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin")) doAckBufSucceed(s, pkt.AckHandle, data) + return } + var data []byte + err := s.server.db.QueryRow("SELECT savedata FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { s.logger.Fatal("Failed to get savedata from db", zap.Error(err)) From f8ed2ef40dac284a6d557451c7ab8b58c175ea99 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 7 Aug 2022 17:16:54 +1000 Subject: [PATCH 32/51] use sequential seasons --- main.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/main.go b/main.go index c5ac726b3..037a6cbef 100644 --- a/main.go +++ b/main.go @@ -168,9 +168,6 @@ func main() { ci := 0 count := 1 for _, ee := range erupeConfig.Entrance.Entries { - rand.Seed(time.Now().UnixNano()) - // Randomly generate a season for the World - season := rand.Intn(3) + 1 for _, ce := range ee.Channels { sid := (4096 + si*256) + (16 + ci) c := *channelserver.NewServer(&channelserver.Config{ @@ -190,7 +187,7 @@ func main() { if err != nil { preventClose(fmt.Sprintf("Failed to start channel server: %s", err.Error())) } else { - channelQuery += fmt.Sprintf("INSERT INTO servers (server_id, season, current_players) VALUES (%d, %d, 0);", sid, season) + channelQuery += fmt.Sprintf("INSERT INTO servers (server_id, season, current_players) VALUES (%d, %d, 0);", sid, si%3) channels = append(channels, &c) logger.Info(fmt.Sprintf("Started channel server %d on port %d", count, ce.Port)) ci++ From d10dcbc63071374c56cbe9b85242844fddb323dd Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 7 Aug 2022 20:16:21 +1000 Subject: [PATCH 33/51] remove unused import --- main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main.go b/main.go index 037a6cbef..d0b13d649 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "math/rand" "net" "os" "os/signal" From 503e944c2d88686b712c3c59ed811177ff78cc5d Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 8 Aug 2022 04:53:55 +1000 Subject: [PATCH 34/51] enumerate achievement data --- network/mhfpacket/msg_mhf_get_achievement.go | 18 +-- patch-schema/achievements.sql | 2 +- server/channelserver/handlers_achievement.go | 161 +++++++++++++------ 3 files changed, 125 insertions(+), 56 deletions(-) diff --git a/network/mhfpacket/msg_mhf_get_achievement.go b/network/mhfpacket/msg_mhf_get_achievement.go index 4b41ce72b..afa49d0d4 100644 --- a/network/mhfpacket/msg_mhf_get_achievement.go +++ b/network/mhfpacket/msg_mhf_get_achievement.go @@ -1,18 +1,18 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfGetAchievement represents the MSG_MHF_GET_ACHIEVEMENT -type MsgMhfGetAchievement struct{ - AckHandle uint32 - Unk0 uint32 // id? - Unk1 uint32 // char? +type MsgMhfGetAchievement struct { + AckHandle uint32 + CharID uint32 + Unk1 uint32 // char? } // Opcode returns the ID associated with this packet type. @@ -22,8 +22,8 @@ func (m *MsgMhfGetAchievement) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfGetAchievement) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint32() + m.AckHandle = bf.ReadUint32() + m.CharID = bf.ReadUint32() m.Unk1 = bf.ReadUint32() return nil } diff --git a/patch-schema/achievements.sql b/patch-schema/achievements.sql index 5c24f9e89..333ae5ed6 100644 --- a/patch-schema/achievements.sql +++ b/patch-schema/achievements.sql @@ -2,7 +2,7 @@ BEGIN; CREATE TABLE IF NOT EXISTS public.achievements ( - id int NOT NULL, + id int NOT NULL PRIMARY KEY , ach0 int DEFAULT 0, ach1 int DEFAULT 0, ach2 int DEFAULT 0, diff --git a/server/channelserver/handlers_achievement.go b/server/channelserver/handlers_achievement.go index b9197f6ea..a7628ed8f 100644 --- a/server/channelserver/handlers_achievement.go +++ b/server/channelserver/handlers_achievement.go @@ -3,24 +3,23 @@ package channelserver import ( "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" + "fmt" + "go.uber.org/zap" + "io" ) -var achievementCurves = [][]uint32{ +var achievementCurves = [][]int32{ // 0: HR weapon use, Class use, Tore dailies - {5, 15, 30, 50, 100, 150, 200, 250, 300}, + {5, 15, 30, 50, 100, 150, 200, 300}, // 1: Weapon collector, G wep enhances - {1, 3, 5, 15, 30, 50, 75, 100, 150}, + {1, 3, 5, 15, 30, 50, 75, 100}, // 2: Festa wins - {1, 2, 3, 4, 5, 6, 7, 8, 9}, - // 3: GR weapon use - {10, 50, 100, 200, 350, 500, 750, 1000, 1500}, - // 4: Armor refinement - {0, 5, 5, 5, 5, 5, 5, 5, 5}, - // 5: Sigil crafts - {0, 50, 50, 50, 50, 50, 50, 50, 50}, + {1, 2, 3, 4, 5, 6, 7, 8}, + // 3: GR weapon use, Sigil crafts + {10, 50, 100, 200, 350, 500, 750, 999}, } -var achievementCurveMap = map[uint8][]uint32{ +var achievementCurveMap = map[uint8][]int32{ 0: achievementCurves[0], 1: achievementCurves[0], 2: achievementCurves[0], 3: achievementCurves[0], 4: achievementCurves[0], 5: achievementCurves[0], 6: achievementCurves[0], 7: achievementCurves[1], 8: achievementCurves[2], 9: achievementCurves[0], 10: achievementCurves[0], 11: achievementCurves[0], @@ -28,52 +27,119 @@ var achievementCurveMap = map[uint8][]uint32{ 16: achievementCurves[3], 17: achievementCurves[3], 18: achievementCurves[3], 19: achievementCurves[3], 20: achievementCurves[3], 21: achievementCurves[3], 22: achievementCurves[3], 23: achievementCurves[3], 24: achievementCurves[3], 25: achievementCurves[3], 26: achievementCurves[3], 27: achievementCurves[1], - 28: achievementCurves[4], 29: achievementCurves[5], 30: achievementCurves[3], 31: achievementCurves[3], + 28: achievementCurves[1], 29: achievementCurves[3], 30: achievementCurves[3], 31: achievementCurves[3], 32: achievementCurves[3], } +type Achievement struct { + Level uint8 + Value uint32 + NextValue uint16 + Required uint32 + Updated bool + Progress uint32 + Trophy uint8 +} + +func GetAchData(id uint8, score int32) Achievement { + curve := achievementCurveMap[id] + var ach Achievement + for i, v := range curve { + temp := score - v + if temp < 0 { + ach.Progress = uint32(score) + ach.Required = uint32(curve[i]) + switch ach.Level { + case 0: + ach.NextValue = 5 + case 1, 2, 3: + ach.NextValue = 10 + case 4, 5: + ach.NextValue = 15 + case 6: + ach.NextValue = 15 + ach.Trophy = 0x40 + case 7: + ach.NextValue = 20 + ach.Trophy = 0x60 + } + return ach + } else { + score = temp + ach.Level++ + switch ach.Level { + case 1: + ach.Value += 5 + case 2, 3, 4: + ach.Value += 10 + case 5, 6, 7: + ach.Value += 15 + case 8: + ach.Value += 20 + } + } + } + ach.Required = uint32(curve[7]) + ach.Trophy = 0x7F + ach.Progress = ach.Required + return ach +} + func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetAchievement) - err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", s.charID) - if err != nil { - s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", s.charID) + row := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID) + if row != nil { + s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", pkt.CharID) } - scores := make([]int, 33) - s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", s.charID).Scan(&scores[0], &scores[0], - &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8], &scores[9], - &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16], &scores[17], - &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24], &scores[25], - &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32]) + var scores [33]int32 + row = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID) + if row != nil { + err := row.Scan(&scores[0], &scores[0], + &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8], &scores[9], + &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16], &scores[17], + &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24], &scores[25], + &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32]) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20)) + s.logger.Error("ERR@", zap.Error(err)) + return + } + } resp := byteframe.NewByteFrame() - points := uint32(69) - resp.WriteUint32(points) - resp.WriteUint32(points) - resp.WriteUint32(points) - resp.WriteUint32(points) - resp.WriteBytes([]byte{0x02, 0x00, 0x00}) + var points uint32 + resp.WriteBytes(make([]byte, 16)) + resp.WriteBytes([]byte{0x02, 0x00, 0x00}) // Unk - entries := 34 - resp.WriteUint8(uint8(entries)) // Entry count - for i := 0; i < entries; i++ { - resp.WriteUint8(uint8(i)) // achievement id - resp.WriteUint8(uint8(i)) // level - resp.WriteUint16(20) // point value - resp.WriteUint32(100) // required - resp.WriteUint8(0) - if i < 10 { - resp.WriteUint16(0x7FFF) - } else if i < 20 { - resp.WriteUint16(0x3FFF) - } else { - resp.WriteUint16(0x1FFF) - } - //resp.WriteUint16(0x7F7F) // unk - resp.WriteUint8(0) - resp.WriteUint32(100) // progress + var id uint8 + entries := uint8(33) + resp.WriteUint8(entries) // Entry count + for id = 0; id < entries; id++ { + achData := GetAchData(id, scores[id]) + points += achData.Value + resp.WriteUint8(id) + resp.WriteUint8(achData.Level) + resp.WriteUint16(achData.NextValue) + resp.WriteUint32(achData.Required) + resp.WriteBool(false) // level increased notification + resp.WriteUint8(achData.Trophy) + /* Trophy bitfield + 0000 0000 + abcd efgh + B - Bronze (0x40) + B-C - Silver (0x60) + B-H - Gold (0x7F) + */ + resp.WriteUint16(0) // Unk + resp.WriteUint32(achData.Progress) } + resp.Seek(0, io.SeekStart) + resp.WriteUint32(points) + resp.WriteUint32(points) + resp.WriteUint32(points) + resp.WriteUint32(points) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } @@ -84,7 +150,10 @@ func handleMsgMhfSetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfResetAchievement(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfAddAchievement) + s.server.db.Exec(fmt.Sprintf("UPDATE achievements SET ach%d=ach%d+1 WHERE id=$1", pkt.AchievementID, pkt.AchievementID), s.charID) +} func handleMsgMhfPaymentAchievement(s *Session, p mhfpacket.MHFPacket) {} From 781827c6bfe79605581d965d16bc232e1f873f42 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 8 Aug 2022 05:01:18 +1000 Subject: [PATCH 35/51] update curve map --- server/channelserver/handlers_achievement.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_achievement.go b/server/channelserver/handlers_achievement.go index a7628ed8f..ab1f94097 100644 --- a/server/channelserver/handlers_achievement.go +++ b/server/channelserver/handlers_achievement.go @@ -12,7 +12,7 @@ var achievementCurves = [][]int32{ // 0: HR weapon use, Class use, Tore dailies {5, 15, 30, 50, 100, 150, 200, 300}, // 1: Weapon collector, G wep enhances - {1, 3, 5, 15, 30, 50, 75, 100}, + {1, 5, 10, 15, 30, 50, 75, 100}, // 2: Festa wins {1, 2, 3, 4, 5, 6, 7, 8}, // 3: GR weapon use, Sigil crafts From c0bc7c24396c80a66106726a1c285d4767f4c796 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 8 Aug 2022 05:04:06 +1000 Subject: [PATCH 36/51] add comments --- server/channelserver/handlers_achievement.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_achievement.go b/server/channelserver/handlers_achievement.go index ab1f94097..b552101f8 100644 --- a/server/channelserver/handlers_achievement.go +++ b/server/channelserver/handlers_achievement.go @@ -123,7 +123,7 @@ func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint8(achData.Level) resp.WriteUint16(achData.NextValue) resp.WriteUint32(achData.Required) - resp.WriteBool(false) // level increased notification + resp.WriteBool(false) // TODO: Notify on rank increase since last checked, see MhfDisplayedAchievement resp.WriteUint8(achData.Trophy) /* Trophy bitfield 0000 0000 @@ -157,7 +157,9 @@ func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPaymentAchievement(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) { + // This is how you would figure out if the rank-up notification needs to occur +} func handleMsgMhfGetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {} From 49682524917415dc3f0ca960d5f095be7c5f8737 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 8 Aug 2022 18:21:54 +1000 Subject: [PATCH 37/51] initial mercenaries build --- patch-schema/mercenary.sql | 5 ++ server/channelserver/handlers_mercenary.go | 55 ++++++++++------------ 2 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 patch-schema/mercenary.sql diff --git a/patch-schema/mercenary.sql b/patch-schema/mercenary.sql new file mode 100644 index 000000000..f67e4fe4e --- /dev/null +++ b/patch-schema/mercenary.sql @@ -0,0 +1,5 @@ +BEGIN; + +CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index 606579fe5..bbcf5dceb 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -9,7 +9,6 @@ import ( "go.uber.org/zap" "io" "io/ioutil" - "math/rand" "os" "path/filepath" ) @@ -132,8 +131,11 @@ func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() - bf.WriteUint32(0x00) // Unk - bf.WriteUint32(rand.Uint32()) // Partner ID? + var nextID uint32 + s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID) + + bf.WriteUint32(nextID) // New MercID + bf.WriteUint32(0xDEADBEEF) // Unk doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) } @@ -141,24 +143,16 @@ func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveMercenary) bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) - GCPValue := bf.ReadUint32() + GCP := bf.ReadUint32() _ = bf.ReadUint32() // unk MercDataSize := bf.ReadUint32() MercData := bf.ReadBytes(uint(MercDataSize)) _ = bf.ReadUint32() // unk if MercDataSize > 0 { - // the save packet has an extra null byte after its size - _, err := s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", MercData[:MercDataSize], s.charID) - if err != nil { - s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err)) - } - } - // gcp value is always present regardless - _, err := s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", GCPValue, s.charID) - if err != nil { - s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err)) + s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", MercData[:MercDataSize], s.charID) } + s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", GCP, s.charID) doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } @@ -166,31 +160,32 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfReadMercenaryW) var data []byte var gcp uint32 - // still has issues - err := s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data) - if err != nil { - s.logger.Fatal("Failed to get savemercenary data from db", zap.Error(err)) - } - - err = s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp) - if err != nil { - panic(err) - } - if len(data) == 0 { - data = []byte{0x00} - } + 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.WriteBytes(data) resp.WriteUint16(0) + if len(data) == 0 { + resp.WriteBool(false) + } else { + resp.WriteBool(true) + resp.WriteBytes(data) + } resp.WriteUint32(gcp) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfReadMercenaryM) - // accessing actual rasta data of someone else still unsure of the formatting of this - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + var data []byte + s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", pkt.CharID).Scan(&data) + resp := byteframe.NewByteFrame() + if len(data) == 0 { + resp.WriteBool(false) + } else { + resp.WriteBytes(data[4:]) + } + doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {} From 265dc258005d2a9fe06ee1692b0b082f93f23817 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 8 Aug 2022 21:02:07 +1000 Subject: [PATCH 38/51] fix merc saving/loading --- network/mhfpacket/msg_mhf_read_mercenary_w.go | 12 ++++----- network/mhfpacket/msg_mhf_save_mercenary.go | 25 +++++++++++-------- server/channelserver/handlers_mercenary.go | 24 ++++++++---------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/network/mhfpacket/msg_mhf_read_mercenary_w.go b/network/mhfpacket/msg_mhf_read_mercenary_w.go index 7cb0117e7..c80afee14 100644 --- a/network/mhfpacket/msg_mhf_read_mercenary_w.go +++ b/network/mhfpacket/msg_mhf_read_mercenary_w.go @@ -1,17 +1,17 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfReadMercenaryW represents the MSG_MHF_READ_MERCENARY_W type MsgMhfReadMercenaryW struct { AckHandle uint32 - Unk0 uint8 + Unk0 bool Unk1 uint8 Unk2 uint16 // Hardcoded 0 in the binary } @@ -24,7 +24,7 @@ func (m *MsgMhfReadMercenaryW) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfReadMercenaryW) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() + m.Unk0 = bf.ReadBool() m.Unk1 = bf.ReadUint8() m.Unk2 = bf.ReadUint16() return nil diff --git a/network/mhfpacket/msg_mhf_save_mercenary.go b/network/mhfpacket/msg_mhf_save_mercenary.go index a52c973ab..3aa2b0311 100644 --- a/network/mhfpacket/msg_mhf_save_mercenary.go +++ b/network/mhfpacket/msg_mhf_save_mercenary.go @@ -1,18 +1,20 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfSaveMercenary represents the MSG_MHF_SAVE_MERCENARY -type MsgMhfSaveMercenary struct{ - AckHandle uint32 - DataSize uint32 - RawDataPayload []byte +type MsgMhfSaveMercenary struct { + AckHandle uint32 + GCP uint32 + Unk0 uint32 + MercData []byte + Unk1 uint32 } // Opcode returns the ID associated with this packet type. @@ -23,8 +25,11 @@ func (m *MsgMhfSaveMercenary) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfSaveMercenary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.DataSize = bf.ReadUint32() - m.RawDataPayload = bf.ReadBytes(uint(m.DataSize)) + bf.ReadUint32() // lenData + m.GCP = bf.ReadUint32() + m.Unk0 = bf.ReadUint32() + m.MercData = bf.ReadBytes(uint(bf.ReadUint32())) + m.Unk1 = bf.ReadUint32() return nil } diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index bbcf5dceb..37ce9e628 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -142,34 +142,30 @@ func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveMercenary) - bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) - GCP := bf.ReadUint32() - _ = bf.ReadUint32() // unk - MercDataSize := bf.ReadUint32() - MercData := bf.ReadBytes(uint(MercDataSize)) - _ = bf.ReadUint32() // unk - - if MercDataSize > 0 { - s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", MercData[:MercDataSize], s.charID) + if len(pkt.MercData) > 0 { + s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", pkt.MercData, s.charID) } - s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", GCP, s.charID) + s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", pkt.GCP, s.charID) doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfReadMercenaryW) + if pkt.Unk0 { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2)) + 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) + resp.WriteBytes(make([]byte, 3)) } else { - resp.WriteBool(true) - resp.WriteBytes(data) + resp.WriteBytes(data[1:]) + resp.WriteUint32(0) // Unk } resp.WriteUint32(gcp) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) From 483490bbd572921f845d4dd91a8712c3530b3dd7 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 8 Aug 2022 22:17:14 +1000 Subject: [PATCH 39/51] use simplesucceed for existing semaphores --- server/channelserver/handlers_semaphore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/channelserver/handlers_semaphore.go b/server/channelserver/handlers_semaphore.go index 6659bd2da..95af79314 100644 --- a/server/channelserver/handlers_semaphore.go +++ b/server/channelserver/handlers_semaphore.go @@ -119,7 +119,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(newSemaphore.id) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) } else { - doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } } From 032ee1eac73e4ba2c9c07198df3bf2497016636f Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 8 Aug 2022 22:17:44 +1000 Subject: [PATCH 40/51] add missing castbinary messagetype --- server/channelserver/handlers_cast_binary.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 304369295..fbdaf6987 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -16,6 +16,7 @@ import ( const ( BinaryMessageTypeState = 0 BinaryMessageTypeChat = 1 + BinaryMessageTypeData = 3 BinaryMessageTypeMailNotify = 4 BinaryMessageTypeEmote = 6 ) @@ -164,6 +165,17 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { fmt.Printf("Got chat message: %+v\n", chatMessage) + if strings.HasPrefix(chatMessage.Message, "!test ") { + var x uint32 + n, err := fmt.Sscanf(chatMessage.Message, "!test %d", &x) + if err != nil || n != 1 { + sendServerChatMessage(s, "Invalid command. Usage:\"!test X\"") + } else { + s.test = x + sendServerChatMessage(s, fmt.Sprintf("Set value to %d", x)) + } + } + // Set account rights if strings.HasPrefix(chatMessage.Message, "!rights") { var v uint32 From af48d75522bb1f47839260845683e4c170302775 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 9 Aug 2022 13:45:04 +1000 Subject: [PATCH 41/51] remove debug command --- server/channelserver/handlers_cast_binary.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index fbdaf6987..bf0f2f105 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -165,17 +165,6 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { fmt.Printf("Got chat message: %+v\n", chatMessage) - if strings.HasPrefix(chatMessage.Message, "!test ") { - var x uint32 - n, err := fmt.Sscanf(chatMessage.Message, "!test %d", &x) - if err != nil || n != 1 { - sendServerChatMessage(s, "Invalid command. Usage:\"!test X\"") - } else { - s.test = x - sendServerChatMessage(s, fmt.Sprintf("Set value to %d", x)) - } - } - // Set account rights if strings.HasPrefix(chatMessage.Message, "!rights") { var v uint32 From 37e4b42b2370e06f606fa9d51cc0b4505626a16a Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 10 Aug 2022 01:17:00 +1000 Subject: [PATCH 42/51] fix character gender offset --- server/channelserver/handlers_data.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 1f18fe262..e98578da7 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -71,8 +71,8 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { s.myseries.toreData = decompressedData[130228:130468] // 0x1FCB4 + 240 s.myseries.gardenData = decompressedData[142424:142492] // 0x22C58 + 68 - isMale := uint8(decompressedData[80]) // 0x50 - if isMale == 1 { + isFemale := decompressedData[81] // 0x51 + if isFemale == 1 { _, err = s.server.db.Exec("UPDATE characters SET is_female=true WHERE id=$1", s.charID) } else { _, err = s.server.db.Exec("UPDATE characters SET is_female=false WHERE id=$1", s.charID) From 9fe1b1d04ae3bdd332c7f60b7442ac4887224e88 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Aug 2022 02:50:59 +1000 Subject: [PATCH 43/51] initial titles implementation --- network/mhfpacket/msg_mhf_acquire_title.go | 21 ++++++--- patch-schema/titles.sql | 11 +++++ server/channelserver/handlers.go | 4 -- server/channelserver/handlers_house.go | 54 +++++++++++++++++----- 4 files changed, 68 insertions(+), 22 deletions(-) create mode 100644 patch-schema/titles.sql diff --git a/network/mhfpacket/msg_mhf_acquire_title.go b/network/mhfpacket/msg_mhf_acquire_title.go index e521734f8..fe3a5ca95 100644 --- a/network/mhfpacket/msg_mhf_acquire_title.go +++ b/network/mhfpacket/msg_mhf_acquire_title.go @@ -1,15 +1,20 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfAcquireTitle represents the MSG_MHF_ACQUIRE_TITLE -type MsgMhfAcquireTitle struct{} +type MsgMhfAcquireTitle struct { + AckHandle uint32 + Unk0 uint16 + Unk1 uint16 + TitleID uint16 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfAcquireTitle) Opcode() network.PacketID { @@ -18,7 +23,11 @@ func (m *MsgMhfAcquireTitle) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfAcquireTitle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Unk0 = bf.ReadUint16() + m.Unk1 = bf.ReadUint16() + m.TitleID = bf.ReadUint16() + return nil } // Build builds a binary packet from the current data. diff --git a/patch-schema/titles.sql b/patch-schema/titles.sql new file mode 100644 index 000000000..e4a87dc86 --- /dev/null +++ b/patch-schema/titles.sql @@ -0,0 +1,11 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS public.titles +( + id int NOT NULL, + char_id int NOT NULL, + unlocked_at timestamp without time zone, + updated_at timestamp without time zone +); + +END; \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 1cfe9e3ff..58ea6b401 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -500,8 +500,6 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { func handleMsgCaExchangeItem(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {} - func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfServerCommand(s *Session, p mhfpacket.MHFPacket) {} @@ -556,8 +554,6 @@ func handleMsgMhfEnumerateOrder(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetExtraInfo(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {} - func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateUnionItem) var boxContents []byte diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 92d7c853e..7b28582d7 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -6,6 +6,8 @@ import ( "erupe-ce/common/stringsupport" "erupe-ce/network/mhfpacket" "go.uber.org/zap" + "io" + "time" ) func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) { @@ -291,25 +293,53 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } +type Title struct { + ID uint16 `db:"id"` + Acquired time.Time `db:"unlocked_at"` + Updated time.Time `db:"updated_at"` +} + func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateTitle) + var count uint16 bf := byteframe.NewByteFrame() - if pkt.CharID == 0 { - titleCount := 114 // all titles unlocked - bf.WriteUint16(uint16(titleCount)) // title count - bf.WriteUint16(0) // unk - for i := 0; i < titleCount; i++ { - bf.WriteUint16(uint16(i)) - bf.WriteUint16(0) // unk - bf.WriteUint32(0) // timestamp acquired - bf.WriteUint32(0) // timestamp updated - } - } else { - bf.WriteUint16(0) + bf.WriteUint16(0) + bf.WriteUint16(0) // Unk + rows, err := s.server.db.Queryx("SELECT id, unlocked_at, updated_at FROM titles WHERE char_id=$1", s.charID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + return } + for rows.Next() { + title := &Title{} + err = rows.StructScan(&title) + if err != nil { + continue + } + count++ + bf.WriteUint16(title.ID) + bf.WriteUint16(0) // Unk + bf.WriteUint32(uint32(title.Acquired.Unix())) + bf.WriteUint32(uint32(title.Updated.Unix())) + } + bf.Seek(0, io.SeekStart) + bf.WriteUint16(count) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } +func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfAcquireTitle) + err := s.server.db.QueryRow("SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2", pkt.TitleID, s.charID) + if err != nil { + s.server.db.Exec("INSERT INTO titles VALUES ($1, $2, now(), now())", pkt.TitleID, s.charID) + } else { + s.server.db.Exec("UPDATE titles SET updated_at=now()") + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} + +func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {} + func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {} From 2a65977a44ad172c6460c708cf7b79639f21d851 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Aug 2022 02:53:51 +1000 Subject: [PATCH 44/51] prevent panic on invalid string transform --- common/pascalstring/pascalstring.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/common/pascalstring/pascalstring.go b/common/pascalstring/pascalstring.go index 3640db22d..8ad332018 100644 --- a/common/pascalstring/pascalstring.go +++ b/common/pascalstring/pascalstring.go @@ -11,7 +11,8 @@ func Uint8(bf *byteframe.ByteFrame, x string, t bool) { e := japanese.ShiftJIS.NewEncoder() xt, _, err := transform.String(e, x) if err != nil { - panic(err) + bf.WriteUint8(0) + return } x = xt } @@ -24,7 +25,8 @@ func Uint16(bf *byteframe.ByteFrame, x string, t bool) { e := japanese.ShiftJIS.NewEncoder() xt, _, err := transform.String(e, x) if err != nil { - panic(err) + bf.WriteUint16(0) + return } x = xt } @@ -37,7 +39,8 @@ func Uint32(bf *byteframe.ByteFrame, x string, t bool) { e := japanese.ShiftJIS.NewEncoder() xt, _, err := transform.String(e, x) if err != nil { - panic(err) + bf.WriteUint32(0) + return } x = xt } From e89c6f50ae89d950b2d1ca39ee6a7cbcd1d27f71 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Aug 2022 14:58:58 +1000 Subject: [PATCH 45/51] clean up user binary querying --- server/channelserver/handlers_users.go | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/server/channelserver/handlers_users.go b/server/channelserver/handlers_users.go index 6db6613c0..186cf686f 100644 --- a/server/channelserver/handlers_users.go +++ b/server/channelserver/handlers_users.go @@ -39,25 +39,18 @@ func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) { s.server.userBinaryPartsLock.RLock() defer s.server.userBinaryPartsLock.RUnlock() data, ok := s.server.userBinaryParts[userBinaryPartID{charID: pkt.CharID, index: pkt.BinaryType}] - resp := byteframe.NewByteFrame() // If we can't get the real data, try to get it from the database. if !ok { - var data []byte - rows, _ := s.server.db.Queryx(fmt.Sprintf("SELECT type%d FROM user_binaries WHERE id=$1", pkt.BinaryType), pkt.CharID) - for rows.Next() { - rows.Scan(&data) - resp.WriteBytes(data) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - return + err := s.server.db.QueryRow(fmt.Sprintf("SELECT type%d FROM user_binaries WHERE id=$1", pkt.BinaryType), pkt.CharID).Scan(&data) + if err != nil { + doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) + } else { + doAckBufSucceed(s, pkt.AckHandle, data) } - doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - return } else { - resp.WriteBytes(data) + doAckBufSucceed(s, pkt.AckHandle, data) } - - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } func handleMsgSysNotifyUserBinary(s *Session, p mhfpacket.MHFPacket) {} From 33c3865da6d322333d294f563093e980cc0cbfa3 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Aug 2022 15:00:15 +1000 Subject: [PATCH 46/51] remove unused import --- server/channelserver/handlers_users.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/channelserver/handlers_users.go b/server/channelserver/handlers_users.go index 186cf686f..dcab8bf84 100644 --- a/server/channelserver/handlers_users.go +++ b/server/channelserver/handlers_users.go @@ -3,7 +3,6 @@ package channelserver import ( "fmt" - "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" ) From 29d86177791e4255fe408c485974c26d72b21709 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Aug 2022 15:17:50 +1000 Subject: [PATCH 47/51] achievement fallback --- server/channelserver/handlers_achievement.go | 33 +++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/server/channelserver/handlers_achievement.go b/server/channelserver/handlers_achievement.go index b552101f8..5db3853e1 100644 --- a/server/channelserver/handlers_achievement.go +++ b/server/channelserver/handlers_achievement.go @@ -4,7 +4,6 @@ import ( "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" "fmt" - "go.uber.org/zap" "io" ) @@ -88,24 +87,21 @@ func GetAchData(id uint8, score int32) Achievement { func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetAchievement) - row := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID) - if row != nil { + var exists int + err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID).Scan(&exists) + if err != nil { s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", pkt.CharID) } var scores [33]int32 - row = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID) - if row != nil { - err := row.Scan(&scores[0], &scores[0], - &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8], &scores[9], - &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16], &scores[17], - &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24], &scores[25], - &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32]) - if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20)) - s.logger.Error("ERR@", zap.Error(err)) - return - } + err = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID).Scan(&scores[0], + &scores[0], &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8], + &scores[9], &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16], + &scores[17], &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24], + &scores[25], &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32]) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20)) + return } resp := byteframe.NewByteFrame() @@ -152,6 +148,13 @@ func handleMsgMhfResetAchievement(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfAddAchievement) + + var exists int + err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", s.charID).Scan(&exists) + if err != nil { + s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", s.charID) + } + s.server.db.Exec(fmt.Sprintf("UPDATE achievements SET ach%d=ach%d+1 WHERE id=$1", pkt.AchievementID, pkt.AchievementID), s.charID) } From 7dab9e3ae17cc4cb3d60fcf4481cbc2c11101204 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Aug 2022 15:21:04 +1000 Subject: [PATCH 48/51] achievement fallback --- server/channelserver/handlers_achievement.go | 33 +++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/server/channelserver/handlers_achievement.go b/server/channelserver/handlers_achievement.go index b552101f8..5db3853e1 100644 --- a/server/channelserver/handlers_achievement.go +++ b/server/channelserver/handlers_achievement.go @@ -4,7 +4,6 @@ import ( "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" "fmt" - "go.uber.org/zap" "io" ) @@ -88,24 +87,21 @@ func GetAchData(id uint8, score int32) Achievement { func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetAchievement) - row := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID) - if row != nil { + var exists int + err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID).Scan(&exists) + if err != nil { s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", pkt.CharID) } var scores [33]int32 - row = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID) - if row != nil { - err := row.Scan(&scores[0], &scores[0], - &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8], &scores[9], - &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16], &scores[17], - &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24], &scores[25], - &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32]) - if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20)) - s.logger.Error("ERR@", zap.Error(err)) - return - } + err = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID).Scan(&scores[0], + &scores[0], &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8], + &scores[9], &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16], + &scores[17], &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24], + &scores[25], &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32]) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20)) + return } resp := byteframe.NewByteFrame() @@ -152,6 +148,13 @@ func handleMsgMhfResetAchievement(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfAddAchievement) + + var exists int + err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", s.charID).Scan(&exists) + if err != nil { + s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", s.charID) + } + s.server.db.Exec(fmt.Sprintf("UPDATE achievements SET ach%d=ach%d+1 WHERE id=$1", pkt.AchievementID, pkt.AchievementID), s.charID) } From 1b129ac630a68c1ca55095e9df2e333cd9b7f9fc Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Aug 2022 16:03:15 +1000 Subject: [PATCH 49/51] actually prevent leak --- server/channelserver/handlers_users.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_users.go b/server/channelserver/handlers_users.go index dcab8bf84..3bebcc97a 100644 --- a/server/channelserver/handlers_users.go +++ b/server/channelserver/handlers_users.go @@ -16,7 +16,8 @@ func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) { s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload s.server.userBinaryPartsLock.Unlock() - err := s.server.db.QueryRow("SELECT type2 FROM user_binaries WHERE id=$1", s.charID) + var exists []byte + err := s.server.db.QueryRow("SELECT type2 FROM user_binaries WHERE id=$1", s.charID).Scan(&exists) if err != nil { s.server.db.Exec("INSERT INTO user_binaries (id) VALUES ($1)", s.charID) } From 315cc391ff2ef31c3ffd420dd978558603c8f67c Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Aug 2022 18:21:10 +1000 Subject: [PATCH 50/51] fix management right acknowledgement --- server/channelserver/handlers_guild.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index fcaddbaf9..f7aa9cdc5 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1883,8 +1883,7 @@ func handleMsgMhfUpdateGuild(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSetGuildManageRight) s.server.db.Exec("UPDATE guild_characters SET recruiter=$1 WHERE character_id=$2", pkt.Allowed, pkt.CharID) - // TODO: What is this supposed to return? This works for now - doAckBufSucceed(s, pkt.AckHandle, []byte{0x01}) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) { From a0dbe9d8c6043c0e496bc800ba7fd774c2bed4f1 Mon Sep 17 00:00:00 2001 From: wish Date: Thu, 11 Aug 2022 18:47:50 +1000 Subject: [PATCH 51/51] change title db syntax --- server/channelserver/handlers_house.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 7b28582d7..401347b76 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -329,8 +329,9 @@ func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfAcquireTitle) - err := s.server.db.QueryRow("SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2", pkt.TitleID, s.charID) - if err != nil { + var exists int + err := s.server.db.QueryRow("SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2", pkt.TitleID, s.charID).Scan(&exists) + if err != nil || exists == 0 { s.server.db.Exec("INSERT INTO titles VALUES ($1, $2, now(), now())", pkt.TitleID, s.charID) } else { s.server.db.Exec("UPDATE titles SET updated_at=now()")