diff --git a/README.md b/README.md index 669f37573..ede542a20 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,15 @@ -# Erupe Community Edition +# Erupe + +## Client Compatiblity +### Platforms +- PC +- PlayStation 3 +- PlayStation Vita +- Wii U (Up to Z2) +### Versions +- ZZ +- Z2 +- Z1 ## Setup @@ -6,24 +17,131 @@ If you are only looking to install Erupe, please use [a pre-compiled binary](htt If you want to modify or compile Erupe yourself, please read on. -### Requirements +## Requirements - [Go](https://go.dev/dl/) - [PostgreSQL](https://www.postgresql.org/download/) -### Installation +## Installation 1. Bring up a fresh database by using the [backup file attached with the latest release](https://github.com/ZeruLight/Erupe/releases/latest/download/SCHEMA.sql). 2. Run each script under [patch-schema](./patch-schema) as they introduce newer schema. 3. Edit [config.json](./config.json) such that the database password matches your PostgreSQL setup. 4. Run `go build` or `go run .` to compile Erupe. -### Note - -- You will need to acquire and install the client files and quest binaries separately. - ## Resources - [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z) - [PewPewDojo Discord](https://discord.gg/CFnzbhQ) -- [Community FAQ Pastebin](https://pastebin.com/QqAwZSTC) + +## Configuration +This portion of the documentation goes over the `config.json` file. + +### General Configuration + +| Variable | Description | Default | Options | +|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|---------------------------------| +| Host | The IP or host address the server is running from | 127.0.0.1 | | +| BinPath | The bin path folder is where you place files needed for various parts of the game such as scenario and quest files | bin | | +| Language | This is the language the server will run in. Only English `en` and Japanese `ja` are available, if you wish to contribute to tranlation, get in touch | en | en/jp | +| DisableSoftCrash | | false | | +| HideLoginNotice | This hides the notices that appear on login from `LoginNotices` | true | | +| LoginNotices | This is where you place notices for users, you can have multiple notices | | | +| PatchServerManifest | | | | +| PatchServerFile | | | | +| ScreenshotAPIURL | This is the URL you want user sreenshots to go to | | | +| DeleteOnSaveCorruption | This option deletes a users save from the database if they corrupt it, can be used as punishment for cheaters | false | | +| ClientMode | This tells the server what client version it should target | ZZ | Check compatible versions above | +| DevMode | This enables DevModeOptions to be configured | true | | + +### `DevModeOptions` Configuraiton + +| Variable | Description | Default | Options | +|----------------------|---------------------------------------------------------------------------------------------|----------|----------------------------------| +| AutoCreateAccount | This allows users that don't exist to auto create there account from initial login | true | | +| CleanDB | This cleans the database down | false | | +| MaxLauncherHR | This sets the launcher value to HR7 to allow you to break World HR requirements | false | | +| LogInboundMessages | This will allow inbound messages to be logged to stdout | false | | +| LogOutboundMessages | This will allow outbound messages to be logged to stdout | false | | +| MaxHexdumpLength | This is the maximum amount of hex bytes that will be dumped to stdout | 0 | | +| DivaEvent | This overrides the Diva event stage in game | 2 | 0/1/2/3/-1 | +| FestaEvent | This overrides the Hunter Festival event stage in game | 2 | 0/1/2/3/-1 | +| TournamentEvent | This overrides the Hunter Tournament event stage in game | 2 | 0/1/2/3/-1 | +| MezFesEvent | Enables whether the MezFes event & World are active | true | | +| MezFesAlt | Switches the multiplayer MezFes event | false | | +| DisableTokenCheck | This disables the random token that is generated at login from being checked, very insecure | false | | +| QuestDebugTools | Enable various quest debug logs | false | | +| EarthStatusOverride | Enables Pallone Fest, Tower and Conquest War events | 0 | 2=Conquest, 11=Pallone, 21=Tower | +| EarthIDOverride | A random event ID | 0 | | +| EarthMonsterOverride | Sets the ID of the monster targeted in the Conquest War | 0 | | +| SaveDumps.Enables | Enables save dumps to a folder that is set at `SaveDumps.OutputDir` | true | | +| SaveDumps.OutputDir | The folder that save dumps are saved to | savedata | | + +### `GameplayOptions` Configuraiton + +| Variable | Description | Default | Options | +|----------------------|-----------------------------------------------------------------------------|---------|---------| +| FeaturedWeapons | Number of Active Feature weapons to generate daily | 0 | | +| MaximumNP | Maximum number of NP held by a player | 100000 | | +| MaximumRP | Maximum number of RP held by a player | 100000 | | +| DisableLoginBoost | Disables the Login Boost system | false | | +| DisableBoostTime | Disables the daily NetCafe Boost Time | false | | +| BoostTimeDuration | The number of minutes NetCafe Boost Time lasts for | 120 | | +| GuildMealDuration | The number of minutes a Guild Meal can be activated for after cooking | 60 | | +| BonusQuestAllowance | Number of Bonus Point Quests to allow daily | 3 | | +| DailyQuestAllowance | Number of Daily Quests to allow daily | 1 | | +| MezfesSoloTickets | Number of solo tickets given weekly | 10 | | +| MezfesGroupTickets | Number of group tickets given weekly | 4 | | +| GUrgentRate | Adjusts the rate of G Urgent quests spawning | 10 | | +| GCPMultiplier | Adjusts the multiplier of GCP rewarded for quest completion | 1.00 | | +| GRPMultiplier | Adjusts the multiplier of G Rank Points rewarded for quest completion | 1.00 | | +| GSRPMultiplier | Adjusts the multiplier of G Skill Rank Points rewarded for quest completion | 1.00 | | +| GZennyMultiplier | Adjusts the multiplier of G Zenny rewarded for quest completion | 1.00 | | +| MaterialMultiplier | Adjusts the multiplier of Monster Materials rewarded for quest completion | 1.00 | | +| ExtraCarves | Grant n extra chances to carve ALL carcasses | 0 | | +| DisableHunterNavi | Disables the Hunter Navi | false | | +| EnableHiganjimaEvent | Enables the Higanjima event in the Rasta Bar | false | | +| EnableNierEvent | Enables the Nier event in the Rasta Bar | false | | +| DisableRoad | Disables the Hunting Road | false | | + +### Discord +There is limited Discord capability in Erupe. The feature allows you to replay messages from your server into a channel. +This may be either be removed or revamped in a future version. + +### Commands +There are several chat commands that can be turned on and off. Most of them are really for admins or debugging purposes. + +| Name | command | Description | Options | +|----------|----------------|--------------------------------------------|---------------------| +| Rights | !rights VALUE | Sets the rights integer for your account | | +| Teleport | !tele X,Y | Teleports user to specific x,y coordinate | | +| Reload | !reload | Reloads all users and character objects | | +| KeyQuest | !kqf FLAGS | Sets the Key Quest Flag for your character | | +| Course | !course OPTION | Enables/Disables a course for your account | HL,EX,Premium,Boost | +| PSN | !psn USERNAME | Links the specified PSN to your account | | + +### Ravi Sub Commands +| Name | command | Description | +|----------|----------------------------------|-------------------------------| +| Raviente | !ravi start | Starts Ravi Event | +| Raviente | !ravi cm / !ravi checkmultiplier | Checks Ravi Damage Multiplier | +| Raviente | !ravi ss | Send Sedation Support | +| Raviente | !ravi sr | Send Resurrection Support | +| Raviente | !ravi rs | Request Sedation Support | + + +## World `Entries` config + +| Config Item | Description | Options | +|-------------|------------------|------------------------------------------------------------| +| Type | Server type. | 1=Normal, 2=Cities, 3=Newbie, 4=Tavern, 5=Return, 6=MezFes | +| Season | Server activity. | 0=Green/Breeding, 1=Orange/Warm, 2=Blue/Cold | + +### `Recommend` +This sets the types of quest that can be ordered from a world. +* 0 = All quests +* 1 = Up to 2 star quests +* 2 = Up to 4 star quests +* 4 = All Quests in HR (Enables G Experience Tab) +* 5 = Only G rank quests +* 6 = Mini games world there is no place to order quests \ No newline at end of file diff --git a/network/mhfpacket/msg_mhf_acquire_item.go b/network/mhfpacket/msg_mhf_acquire_item.go index 96d96ab11..de86ce846 100644 --- a/network/mhfpacket/msg_mhf_acquire_item.go +++ b/network/mhfpacket/msg_mhf_acquire_item.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" ) // MsgMhfAcquireItem represents the MSG_MHF_ACQUIRE_ITEM -type MsgMhfAcquireItem struct{} +type MsgMhfAcquireItem struct { + AckHandle uint32 + Unk0 uint16 + Length uint16 + Unk1 []uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfAcquireItem) Opcode() network.PacketID { @@ -18,7 +23,13 @@ func (m *MsgMhfAcquireItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfAcquireItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Unk0 = bf.ReadUint16() + m.Length = bf.ReadUint16() + for i := 0; i < int(m.Length); i++ { + m.Unk1 = append(m.Unk1, bf.ReadUint32()) + } + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_acquire_tournament.go b/network/mhfpacket/msg_mhf_acquire_tournament.go index d1d293288..2e65e0984 100644 --- a/network/mhfpacket/msg_mhf_acquire_tournament.go +++ b/network/mhfpacket/msg_mhf_acquire_tournament.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" ) // MsgMhfAcquireTournament represents the MSG_MHF_ACQUIRE_TOURNAMENT -type MsgMhfAcquireTournament struct{} +type MsgMhfAcquireTournament struct { + AckHandle uint32 + TournamentID uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfAcquireTournament) Opcode() network.PacketID { @@ -18,7 +21,9 @@ func (m *MsgMhfAcquireTournament) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfAcquireTournament) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.TournamentID = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_apply_campaign.go b/network/mhfpacket/msg_mhf_apply_campaign.go index 04f506a44..b39dab499 100644 --- a/network/mhfpacket/msg_mhf_apply_campaign.go +++ b/network/mhfpacket/msg_mhf_apply_campaign.go @@ -1,17 +1,18 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfApplyCampaign represents the MSG_MHF_APPLY_CAMPAIGN type MsgMhfApplyCampaign struct { AckHandle uint32 - Unk0 uint8 - Unk1 uint8 - Unk2 uint16 + Unk0 uint32 + Unk1 uint16 + Unk2 []byte } // Opcode returns the ID associated with this packet type. @@ -22,17 +23,13 @@ func (m *MsgMhfApplyCampaign) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfApplyCampaign) 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.Unk0 = bf.ReadUint32() + m.Unk1 = bf.ReadUint16() + m.Unk2 = bf.ReadBytes(16) return nil } // Build builds a binary packet from the current data. func (m *MsgMhfApplyCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint32(m.AckHandle) - bf.WriteUint8(m.Unk0) - bf.WriteUint8(m.Unk1) - bf.WriteUint16(m.Unk2) - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/network/mhfpacket/msg_mhf_entry_tournament.go b/network/mhfpacket/msg_mhf_entry_tournament.go index dbd600cab..6f6550cf0 100644 --- a/network/mhfpacket/msg_mhf_entry_tournament.go +++ b/network/mhfpacket/msg_mhf_entry_tournament.go @@ -1,15 +1,19 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfEntryTournament represents the MSG_MHF_ENTRY_TOURNAMENT -type MsgMhfEntryTournament struct{} +type MsgMhfEntryTournament struct { + AckHandle uint32 + TournamentID uint32 + Unk0 uint8 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfEntryTournament) Opcode() network.PacketID { @@ -18,7 +22,10 @@ func (m *MsgMhfEntryTournament) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEntryTournament) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.TournamentID = bf.ReadUint32() + m.Unk0 = bf.ReadUint8() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_enumerate_campaign.go b/network/mhfpacket/msg_mhf_enumerate_campaign.go index a5e39a1cd..d06075077 100644 --- a/network/mhfpacket/msg_mhf_enumerate_campaign.go +++ b/network/mhfpacket/msg_mhf_enumerate_campaign.go @@ -1,17 +1,16 @@ package mhfpacket import ( + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfEnumerateCampaign represents the MSG_MHF_ENUMERATE_CAMPAIGN type MsgMhfEnumerateCampaign struct { AckHandle uint32 - Unk0 uint8 - Unk1 uint8 - Unk2 uint16 + Unk0 uint16 + Unk1 uint16 } // Opcode returns the ID associated with this packet type. @@ -22,17 +21,15 @@ func (m *MsgMhfEnumerateCampaign) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateCampaign) 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.Unk0 = bf.ReadUint16() + m.Unk1 = bf.ReadUint16() return nil } // Build builds a binary packet from the current data. func (m *MsgMhfEnumerateCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { bf.WriteUint32(m.AckHandle) - bf.WriteUint8(m.Unk0) - bf.WriteUint8(m.Unk1) - bf.WriteUint16(m.Unk2) + bf.WriteUint16(m.Unk0) + bf.WriteUint16(m.Unk1) return nil } diff --git a/network/mhfpacket/msg_mhf_enumerate_item.go b/network/mhfpacket/msg_mhf_enumerate_item.go index b12276703..fea4f3371 100644 --- a/network/mhfpacket/msg_mhf_enumerate_item.go +++ b/network/mhfpacket/msg_mhf_enumerate_item.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" ) // MsgMhfEnumerateItem represents the MSG_MHF_ENUMERATE_ITEM -type MsgMhfEnumerateItem struct{} +type MsgMhfEnumerateItem struct { + AckHandle uint32 + Unk0 uint16 + Unk1 uint16 + CampaignID uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfEnumerateItem) Opcode() network.PacketID { @@ -18,7 +23,11 @@ func (m *MsgMhfEnumerateItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateItem) 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.CampaignID = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_operate_guild.go b/network/mhfpacket/msg_mhf_operate_guild.go index a79719290..96803e89c 100644 --- a/network/mhfpacket/msg_mhf_operate_guild.go +++ b/network/mhfpacket/msg_mhf_operate_guild.go @@ -11,33 +11,33 @@ import ( type OperateGuildAction uint8 const ( - OPERATE_GUILD_DISBAND = 0x01 - OPERATE_GUILD_APPLY = 0x02 - OPERATE_GUILD_LEAVE = 0x03 - OPERATE_GUILD_RESIGN = 0x04 - OPERATE_GUILD_SET_APPLICATION_DENY = 0x05 - OPERATE_GUILD_SET_APPLICATION_ALLOW = 0x06 - OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE = 0x07 - OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE = 0x08 - OPERATE_GUILD_UPDATE_COMMENT = 0x09 - OPERATE_GUILD_DONATE_RANK = 0x0a - OPERATE_GUILD_UPDATE_MOTTO = 0x0b - OPERATE_GUILD_RENAME_PUGI_1 = 0x0c - OPERATE_GUILD_RENAME_PUGI_2 = 0x0d - OPERATE_GUILD_RENAME_PUGI_3 = 0x0e - OPERATE_GUILD_CHANGE_PUGI_1 = 0x0f - OPERATE_GUILD_CHANGE_PUGI_2 = 0x10 - OPERATE_GUILD_CHANGE_PUGI_3 = 0x11 - OPERATE_GUILD_UNLOCK_OUTFIT = 0x12 - // 0x13 Unk - // 0x14 Unk - OPERATE_GUILD_DONATE_EVENT = 0x15 - OPERATE_GUILD_EVENT_EXCHANGE = 0x16 - // 0x17 Unk - // 0x18 Unk - OPERATE_GUILD_CHANGE_DIVA_PUGI_1 = 0x19 - OPERATE_GUILD_CHANGE_DIVA_PUGI_2 = 0x1a - OPERATE_GUILD_CHANGE_DIVA_PUGI_3 = 0x1b + OperateGuildDisband = iota + 1 + OperateGuildApply + OperateGuildLeave + OperateGuildResign + OperateGuildSetApplicationDeny + OperateGuildSetApplicationAllow + OperateGuildSetAvoidLeadershipTrue + OperateGuildSetAvoidLeadershipFalse + OperateGuildUpdateComment + OperateGuildDonateRank + OperateGuildUpdateMotto + OperateGuildRenamePugi1 + OperateGuildRenamePugi2 + OperateGuildRenamePugi3 + OperateGuildChangePugi1 + OperateGuildChangePugi2 + OperateGuildChangePugi3 + OperateGuildUnlockOutfit + OperateGuildDonateRoom + OperateGuildGraduateRookie + OperateGuildDonateEvent + OperateGuildEventExchange + OperateGuildUnknown // I don't think this op exists + OperateGuildGraduateReturn + OperateGuildChangeDivaPugi1 + OperateGuildChangeDivaPugi2 + OperateGuildChangeDivaPugi3 ) // MsgMhfOperateGuild represents the MSG_MHF_OPERATE_GUILD diff --git a/network/mhfpacket/msg_mhf_state_campaign.go b/network/mhfpacket/msg_mhf_state_campaign.go index 35cca070d..ab6342c55 100644 --- a/network/mhfpacket/msg_mhf_state_campaign.go +++ b/network/mhfpacket/msg_mhf_state_campaign.go @@ -1,18 +1,18 @@ package mhfpacket import ( + "errors" "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" ) // MsgMhfStateCampaign represents the MSG_MHF_STATE_CAMPAIGN type MsgMhfStateCampaign struct { - AckHandle uint32 - Unk0 uint8 - Unk1 uint8 - Unk2 uint16 + AckHandle uint32 + CampaignID uint32 + Unk1 uint16 } // Opcode returns the ID associated with this packet type. @@ -23,17 +23,12 @@ func (m *MsgMhfStateCampaign) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfStateCampaign) 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.CampaignID = bf.ReadUint32() + m.Unk1 = bf.ReadUint16() return nil } // Build builds a binary packet from the current data. func (m *MsgMhfStateCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint32(m.AckHandle) - bf.WriteUint8(m.Unk0) - bf.WriteUint8(m.Unk1) - bf.WriteUint16(m.Unk2) - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/network/mhfpacket/msg_mhf_update_beat_level.go b/network/mhfpacket/msg_mhf_update_beat_level.go index 84f41ae61..e00d50c93 100644 --- a/network/mhfpacket/msg_mhf_update_beat_level.go +++ b/network/mhfpacket/msg_mhf_update_beat_level.go @@ -1,45 +1,42 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/common/byteframe" - "erupe-ce/network" - "erupe-ce/network/clientctx" + "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfUpdateBeatLevel represents the MSG_MHF_UPDATE_BEAT_LEVEL type MsgMhfUpdateBeatLevel struct { - AckHandle uint32 - Unk1 uint32 - Unk2 uint32 - MonsterData []byte - Unk3 uint8 - Unk4 uint32 - Unk5 uint16 - Unk6 uint8 - + AckHandle uint32 + Unk1 uint32 + Unk2 uint32 + Data1 []int32 + Data2 []int32 } // Opcode returns the ID associated with this packet type. func (m *MsgMhfUpdateBeatLevel) Opcode() network.PacketID { - return network.MSG_MHF_UPDATE_BEAT_LEVEL + return network.MSG_MHF_UPDATE_BEAT_LEVEL } // Parse parses the packet from binary func (m *MsgMhfUpdateBeatLevel) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.Unk1 = bf.ReadUint32() - m.Unk2 = bf.ReadUint32() - m.MonsterData = bf.ReadBytes(uint(120)) - m.Unk3 = bf.ReadUint8() - m.Unk4 = bf.ReadUint32() - m.Unk5 = bf.ReadUint16() - m.Unk6 = bf.ReadUint8() - return nil + m.AckHandle = bf.ReadUint32() + m.Unk1 = bf.ReadUint32() + m.Unk2 = bf.ReadUint32() + for i := 0; i < 16; i++ { + m.Data1 = append(m.Data1, bf.ReadInt32()) + } + for i := 0; i < 16; i++ { + m.Data2 = append(m.Data2, bf.ReadInt32()) + } + return nil } // Build builds a binary packet from the current data. func (m *MsgMhfUpdateBeatLevel) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + return errors.New("NOT IMPLEMENTED") } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 4647b8021..a8a37d181 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -552,10 +552,6 @@ func handleMsgSysInfokyserver(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetCaUniqueID(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfEnumerateItem(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfAcquireItem(s *Session, p mhfpacket.MHFPacket) {} - func handleMsgMhfTransferItem(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfTransferItem) doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) diff --git a/server/channelserver/handlers_campaign.go b/server/channelserver/handlers_campaign.go index 8522adab2..2041b484b 100644 --- a/server/channelserver/handlers_campaign.go +++ b/server/channelserver/handlers_campaign.go @@ -1,18 +1,173 @@ package channelserver -import "erupe-ce/network/mhfpacket" +import ( + "erupe-ce/common/byteframe" + ps "erupe-ce/common/pascalstring" + "erupe-ce/common/stringsupport" + "erupe-ce/network/mhfpacket" + "time" +) + +type CampaignEvent struct { + ID uint32 + Unk0 uint32 + MinHR int16 + MaxHR int16 + MinSR int16 + MaxSR int16 + MinGR int16 + MaxGR int16 + Unk1 uint16 + Unk2 uint8 + Unk3 uint8 + Unk4 uint16 + Unk5 uint16 + Start time.Time + End time.Time + Unk6 uint8 + String0 string + String1 string + String2 string + String3 string + Link string + Prefix string + Categories []uint16 +} + +type CampaignCategory struct { + ID uint16 + Type uint8 + Title string + Description string +} + +type CampaignLink struct { + CategoryID uint16 + CampaignID uint32 +} func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign) - doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + bf := byteframe.NewByteFrame() + + events := []CampaignEvent{} + categories := []CampaignCategory{} + var campaignLinks []CampaignLink + + if len(events) > 255 { + bf.WriteUint8(255) + bf.WriteUint16(uint16(len(events))) + } else { + bf.WriteUint8(uint8(len(events))) + } + for _, event := range events { + bf.WriteUint32(event.ID) + bf.WriteUint32(event.Unk0) + bf.WriteInt16(event.MinHR) + bf.WriteInt16(event.MaxHR) + bf.WriteInt16(event.MinSR) + bf.WriteInt16(event.MaxSR) + bf.WriteInt16(event.MinGR) + bf.WriteInt16(event.MaxGR) + bf.WriteUint16(event.Unk1) + bf.WriteUint8(event.Unk2) + bf.WriteUint8(event.Unk3) + bf.WriteUint16(event.Unk4) + bf.WriteUint16(event.Unk5) + bf.WriteUint32(uint32(event.Start.Unix())) + bf.WriteUint32(uint32(event.End.Unix())) + bf.WriteUint8(event.Unk6) + ps.Uint8(bf, event.String0, true) + ps.Uint8(bf, event.String1, true) + ps.Uint8(bf, event.String2, true) + ps.Uint8(bf, event.String3, true) + ps.Uint8(bf, event.Link, true) + for i := range event.Categories { + campaignLinks = append(campaignLinks, CampaignLink{event.Categories[i], event.ID}) + } + } + + if len(events) > 255 { + bf.WriteUint8(255) + bf.WriteUint16(uint16(len(events))) + } else { + bf.WriteUint8(uint8(len(events))) + } + for _, event := range events { + bf.WriteUint32(event.ID) + bf.WriteUint8(1) // Always 1? + bf.WriteBytes([]byte(event.Prefix)) + } + + if len(categories) > 255 { + bf.WriteUint8(255) + bf.WriteUint16(uint16(len(categories))) + } else { + bf.WriteUint8(uint8(len(categories))) + } + for _, category := range categories { + bf.WriteUint16(category.ID) + bf.WriteUint8(category.Type) + xTitle := stringsupport.UTF8ToSJIS(category.Title) + xDescription := stringsupport.UTF8ToSJIS(category.Description) + bf.WriteUint8(uint8(len(xTitle))) + bf.WriteUint8(uint8(len(xDescription))) + bf.WriteBytes(xTitle) + bf.WriteBytes(xDescription) + } + + if len(campaignLinks) > 255 { + bf.WriteUint8(255) + bf.WriteUint16(uint16(len(campaignLinks))) + } else { + bf.WriteUint8(uint8(len(campaignLinks))) + } + for _, link := range campaignLinks { + bf.WriteUint16(link.CategoryID) + bf.WriteUint32(link.CampaignID) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfStateCampaign) - doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + bf := byteframe.NewByteFrame() + bf.WriteUint16(1) + bf.WriteUint16(0) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfApplyCampaign) - doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + bf := byteframe.NewByteFrame() + bf.WriteUint32(1) + doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) +} + +func handleMsgMhfEnumerateItem(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfEnumerateItem) + items := []struct { + Unk0 uint32 + Unk1 uint16 + Unk2 uint16 + Unk3 uint16 + Unk4 uint32 + Unk5 uint32 + }{} + bf := byteframe.NewByteFrame() + bf.WriteUint16(uint16(len(items))) + for _, item := range items { + bf.WriteUint32(item.Unk0) + bf.WriteUint16(item.Unk1) + bf.WriteUint16(item.Unk2) + bf.WriteUint16(item.Unk3) + bf.WriteUint32(item.Unk4) + bf.WriteUint32(item.Unk5) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} + +func handleMsgMhfAcquireItem(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfAcquireItem) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 01d9dc73a..7a4c164fd 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -151,7 +151,7 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() { if _config.ErupeConfig.RealClientMode == _config.ZZ { copy(save.decompSave[pointerRP:pointerRP+2], rpBytes) copy(save.decompSave[pointerKQF:pointerKQF+8], save.KQF) - } else { + } else if _config.ErupeConfig.RealClientMode >= _config.Z1 { copy(save.decompSave[pointerRPZ:pointerRPZ+2], rpBytes) copy(save.decompSave[pointerKQFZ:pointerKQFZ+8], save.KQF) } @@ -181,7 +181,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[pointerGRP : pointerGRP+4])) } save.KQF = save.decompSave[pointerKQF : pointerKQF+8] - } else { + } else if _config.ErupeConfig.RealClientMode >= _config.Z1 { save.RP = binary.LittleEndian.Uint16(save.decompSave[pointerRPZ : pointerRPZ+2]) save.HouseTier = save.decompSave[pointerHouseTierZ : pointerHouseTierZ+5] save.HouseData = save.decompSave[pointerHouseDataZ : pointerHouseDataZ+195] diff --git a/server/channelserver/handlers_event.go b/server/channelserver/handlers_event.go index ff84b4a7a..7797bd6fc 100644 --- a/server/channelserver/handlers_event.go +++ b/server/channelserver/handlers_event.go @@ -48,9 +48,41 @@ func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) { }) } +type Event struct { + Unk0 uint16 + Unk1 uint16 + Unk2 uint16 + Unk3 uint16 + Unk4 uint16 + Unk5 uint32 + Unk6 uint32 + Unk7 []uint16 +} + func handleMsgMhfEnumerateEvent(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateEvent) - stubEnumerateNoResults(s, pkt.AckHandle) + bf := byteframe.NewByteFrame() + + events := []Event{} + + bf.WriteUint8(uint8(len(events))) + for _, event := range events { + bf.WriteUint16(event.Unk0) + bf.WriteUint16(event.Unk1) + bf.WriteUint16(event.Unk2) + bf.WriteUint16(event.Unk3) + bf.WriteUint16(event.Unk4) + bf.WriteUint32(event.Unk5) + bf.WriteUint32(event.Unk6) + if event.Unk0 == 2 { + bf.WriteUint8(uint8(len(event.Unk7))) + for _, u := range event.Unk7 { + bf.WriteUint16(u) + } + } + } + + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } type activeFeature struct { diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index c05b15f68..0858d66c2 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -616,13 +616,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfOperateGuild) guild, err := GetGuildInfoByID(s, pkt.GuildID) - - if err != nil { - return - } - characterGuildInfo, err := GetCharacterGuildData(s, s.charID) - if err != nil { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) return @@ -631,22 +625,19 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() switch pkt.Action { - case mhfpacket.OPERATE_GUILD_DISBAND: + case mhfpacket.OperateGuildDisband: + response := 1 if guild.LeaderCharID != s.charID { s.logger.Warn(fmt.Sprintf("character '%d' is attempting to manage guild '%d' without permission", s.charID, guild.ID)) - return + response = 0 + } else { + err = guild.Disband(s) + if err != nil { + response = 0 + } } - - err = guild.Disband(s) - response := 0x01 - - if err != nil { - // All successful acks return 0x01, assuming 0x00 is failure - response = 0x00 - } - bf.WriteUint32(uint32(response)) - case mhfpacket.OPERATE_GUILD_RESIGN: + case mhfpacket.OperateGuildResign: guildMembers, err := GetGuildMembers(s, guild.ID, false) if err == nil { sort.Slice(guildMembers[:], func(i, j int) bool { @@ -665,25 +656,22 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { } guild.Save(s) } - case mhfpacket.OPERATE_GUILD_APPLY: + case mhfpacket.OperateGuildApply: err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil) - if err == nil { bf.WriteUint32(guild.LeaderCharID) + } else { + bf.WriteUint32(0) } - case mhfpacket.OPERATE_GUILD_LEAVE: - var err error - + case mhfpacket.OperateGuildLeave: if characterGuildInfo.IsApplicant { err = guild.RejectApplication(s, s.charID) } else { err = guild.RemoveCharacter(s, s.charID) } - - response := 0x01 + response := 1 if err != nil { - // All successful acks return 0x01, assuming 0x00 is failure - response = 0x00 + response = 0 } else { mail := Mail{ RecipientID: s.charID, @@ -693,26 +681,25 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { } mail.Send(s, nil) } - bf.WriteUint32(uint32(response)) - case mhfpacket.OPERATE_GUILD_DONATE_RANK: + case mhfpacket.OperateGuildDonateRank: bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, false)) - case mhfpacket.OPERATE_GUILD_SET_APPLICATION_DENY: + case mhfpacket.OperateGuildSetApplicationDeny: s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID) - case mhfpacket.OPERATE_GUILD_SET_APPLICATION_ALLOW: + case mhfpacket.OperateGuildSetApplicationAllow: s.server.db.Exec("UPDATE guilds SET recruiting=true WHERE id=$1", guild.ID) - case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE: + case mhfpacket.OperateGuildSetAvoidLeadershipTrue: handleAvoidLeadershipUpdate(s, pkt, true) - case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE: + case mhfpacket.OperateGuildSetAvoidLeadershipFalse: handleAvoidLeadershipUpdate(s, pkt, false) - case mhfpacket.OPERATE_GUILD_UPDATE_COMMENT: + case mhfpacket.OperateGuildUpdateComment: if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) return } guild.Comment = stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes()) guild.Save(s) - case mhfpacket.OPERATE_GUILD_UPDATE_MOTTO: + case mhfpacket.OperateGuildUpdateMotto: if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) return @@ -721,27 +708,29 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { guild.SubMotto = pkt.Data1.ReadUint8() guild.MainMotto = pkt.Data1.ReadUint8() guild.Save(s) - case mhfpacket.OPERATE_GUILD_RENAME_PUGI_1: + case mhfpacket.OperateGuildRenamePugi1: handleRenamePugi(s, pkt.Data2, guild, 1) - case mhfpacket.OPERATE_GUILD_RENAME_PUGI_2: + case mhfpacket.OperateGuildRenamePugi2: handleRenamePugi(s, pkt.Data2, guild, 2) - case mhfpacket.OPERATE_GUILD_RENAME_PUGI_3: + case mhfpacket.OperateGuildRenamePugi3: handleRenamePugi(s, pkt.Data2, guild, 3) - case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_1: + case mhfpacket.OperateGuildChangePugi1: handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 1) - case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_2: + case mhfpacket.OperateGuildChangePugi2: handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 2) - case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_3: + case mhfpacket.OperateGuildChangePugi3: handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 3) - case mhfpacket.OPERATE_GUILD_UNLOCK_OUTFIT: + case mhfpacket.OperateGuildUnlockOutfit: // TODO: This doesn't implement blocking, if someone unlocked the same outfit at the same time s.server.db.Exec(`UPDATE guilds SET pugi_outfits=pugi_outfits+$1 WHERE id=$2`, int(math.Pow(float64(pkt.Data1.ReadUint32()), 2)), guild.ID) - case mhfpacket.OPERATE_GUILD_DONATE_EVENT: + case mhfpacket.OperateGuildDonateRoom: + // TODO: Where does this go? + case mhfpacket.OperateGuildDonateEvent: quantity := uint16(pkt.Data1.ReadUint32()) bf.WriteBytes(handleDonateRP(s, quantity, guild, true)) // TODO: Move this value onto rp_yesterday and reset to 0... daily? s.server.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, quantity, s.charID) - case mhfpacket.OPERATE_GUILD_EVENT_EXCHANGE: + case mhfpacket.OperateGuildEventExchange: rp := uint16(pkt.Data1.ReadUint32()) var balance uint32 s.server.db.QueryRow(`UPDATE guilds SET event_rp=event_rp-$1 WHERE id=$2 RETURNING event_rp`, rp, guild.ID).Scan(&balance) @@ -930,7 +919,12 @@ 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}) + bf.WriteUint8(0) + bf.WriteUint8(0) + bf.WriteUint8(0) + bf.WriteUint8(0) + bf.WriteUint8(0) + bf.WriteUint8(0) bf.WriteBool(!guild.Recruiting) @@ -953,28 +947,39 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(FestivalColourCodes[guild.FestivalColour]) bf.WriteUint32(guild.RankRP) bf.WriteBytes(guildLeaderName) - bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00}) // Unk - bf.WriteBool(false) // isReturnGuild - bf.WriteBool(false) // earnedSpecialHall - bf.WriteBytes([]byte{0x02, 0x02}) // Unk - bf.WriteUint32(guild.EventRP) + bf.WriteUint32(0) // Unk + bf.WriteBool(false) // isReturnGuild + bf.WriteBool(false) // earnedSpecialHall + bf.WriteUint8(2) + bf.WriteUint8(2) + bf.WriteUint32(guild.EventRP) // Skipped if last byte is <2? ps.Uint8(bf, guild.PugiName1, true) ps.Uint8(bf, guild.PugiName2, true) ps.Uint8(bf, guild.PugiName3, true) bf.WriteUint8(guild.PugiOutfit1) bf.WriteUint8(guild.PugiOutfit2) bf.WriteUint8(guild.PugiOutfit3) - bf.WriteUint8(guild.PugiOutfit1) - bf.WriteUint8(guild.PugiOutfit2) - bf.WriteUint8(guild.PugiOutfit3) + if s.server.erupeConfig.RealClientMode >= _config.Z1 { + bf.WriteUint8(guild.PugiOutfit1) + bf.WriteUint8(guild.PugiOutfit2) + bf.WriteUint8(guild.PugiOutfit3) + } bf.WriteUint32(guild.PugiOutfits) - // Unk flags - bf.WriteUint8(0x3C) // also seen as 0x32 on JP and 0x64 on TW + if guild.Rank >= 3 { + bf.WriteUint8(40) + } else if guild.Rank >= 7 { + bf.WriteUint8(50) + } else if guild.Rank >= 10 { + bf.WriteUint8(60) + } else { + bf.WriteUint8(30) + } - bf.WriteBytes([]byte{ - 0x00, 0x00, 0xD6, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }) + bf.WriteUint32(55000) + bf.WriteUint32(0) + bf.WriteUint16(0) // Changing Room RP + bf.WriteUint16(0) if guild.AllianceID > 0 { alliance, err := GetAllianceData(s, guild.AllianceID) @@ -984,7 +989,8 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(alliance.ID) bf.WriteUint32(uint32(alliance.CreatedAt.Unix())) bf.WriteUint16(alliance.TotalMembers) - bf.WriteUint16(0) // Unk0 + bf.WriteUint8(0) + bf.WriteUint8(0) ps.Uint16(bf, alliance.Name, true) if alliance.SubGuild1ID > 0 { if alliance.SubGuild2ID > 0 { @@ -1044,29 +1050,46 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(uint16(len(applicants))) for _, applicant := range applicants { bf.WriteUint32(applicant.CharID) - bf.WriteUint16(0) - bf.WriteUint16(0) + bf.WriteUint32(0) bf.WriteUint16(applicant.HRP) bf.WriteUint16(applicant.GR) ps.Uint8(bf, applicant.Name, true) } } - bf.WriteUint16(0x0000) // lenAllianceApplications + type UnkGuildInfo struct { + Unk0 uint8 + Unk1 uint8 + Unk2 uint8 + } + unkGuildInfo := []UnkGuildInfo{} + bf.WriteUint8(uint8(len(unkGuildInfo))) + for _, info := range unkGuildInfo { + bf.WriteUint8(info.Unk0) + bf.WriteUint8(info.Unk1) + bf.WriteUint8(info.Unk2) + } - /* - alliance application format - uint16 numapplicants (above) - - uint32 guild id - uint32 guild leader id (for mail) - uint32 unk (always null in pcap) - uint16 member count - uint16 len guild name - string nullterm guild name - uint16 len guild leader name - string nullterm guild leader name - */ + type AllianceInvite struct { + GuildID uint32 + LeaderID uint32 + Unk0 uint16 + Unk1 uint16 + Members uint16 + GuildName string + LeaderName string + } + allianceInvites := []AllianceInvite{} + bf.WriteUint8(uint8(len(allianceInvites))) + for _, invite := range allianceInvites { + bf.WriteUint32(invite.GuildID) + bf.WriteUint32(invite.LeaderID) + bf.WriteUint16(invite.Unk0) + bf.WriteUint16(invite.Unk1) + bf.WriteUint16(invite.Members) + ps.Uint16(bf, invite.GuildName, true) + ps.Uint16(bf, invite.LeaderName, true) + } if guild.Icon != nil { bf.WriteUint8(uint8(len(guild.Icon.Parts))) @@ -1084,7 +1107,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(p.PosY) } } else { - bf.WriteUint8(0x00) + bf.WriteUint8(0) } bf.WriteUint8(0) // Unk @@ -1348,7 +1371,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { if guild != nil { isApplicant, _ := guild.HasApplicationForCharID(s, s.charID) if isApplicant { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2)) return } } @@ -1390,7 +1413,9 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { for _, member := range guildMembers { bf.WriteUint32(member.CharID) bf.WriteUint16(member.HRP) - bf.WriteUint16(member.GR) + if s.server.erupeConfig.RealClientMode > _config.G7 { + bf.WriteUint16(member.GR) + } if s.server.erupeConfig.RealClientMode < _config.ZZ { // Magnet Spike crash workaround bf.WriteUint16(0) @@ -1456,7 +1481,6 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGuildManageRight) guild, err := GetGuildInfoByCharacterId(s, s.charID) - if guild == nil && s.prevGuildID != 0 { guild, err = GetGuildInfoByID(s, s.prevGuildID) s.prevGuildID = 0 @@ -1466,31 +1490,14 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) { } } - if err != nil { - s.logger.Warn("failed to respond to manage rights message") - return - } else if guild == nil { - bf := byteframe.NewByteFrame() - bf.WriteUint16(0x00) // Unk - bf.WriteUint16(0x00) // Member count - - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - return - } - bf := byteframe.NewByteFrame() - - bf.WriteUint16(0x00) // Unk - bf.WriteUint16(guild.MemberCount) - + bf.WriteUint32(uint32(guild.MemberCount)) members, _ := GetGuildMembers(s, guild.ID, false) - for _, member := range members { bf.WriteUint32(member.CharID) bf.WriteBool(member.Recruiter) bf.WriteBytes(make([]byte, 3)) } - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index ab150f180..52cdb4fa9 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -4,6 +4,7 @@ import ( "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" "erupe-ce/common/stringsupport" + _config "erupe-ce/config" "erupe-ce/network/mhfpacket" "fmt" "go.uber.org/zap" @@ -249,6 +250,9 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) { s.logger.Error("Failed to load decomyset", zap.Error(err)) } if len(data) == 0 { + if s.server.erupeConfig.RealClientMode <= _config.G7 { + data = []byte{0x00, 0x00} + } data = []byte{0x01, 0x00} } doAckBufSucceed(s, pkt.AckHandle, data) diff --git a/server/channelserver/handlers_tournament.go b/server/channelserver/handlers_tournament.go index 84c2c8e8f..87fc95330 100644 --- a/server/channelserver/handlers_tournament.go +++ b/server/channelserver/handlers_tournament.go @@ -7,54 +7,124 @@ import ( "time" ) +type TournamentInfo0 struct { + ID uint32 + MaxPlayers uint32 + CurrentPlayers uint32 + Unk1 uint16 + TextColor uint16 + Unk2 uint32 + Time1 time.Time + Time2 time.Time + Time3 time.Time + Time4 time.Time + Time5 time.Time + Time6 time.Time + Unk3 uint8 + Unk4 uint8 + MinHR uint32 + MaxHR uint32 + Unk5 string + Unk6 string +} + +type TournamentInfo21 struct { + Unk0 uint32 + Unk1 uint32 + Unk2 uint32 + Unk3 uint8 +} + +type TournamentInfo22 struct { + Unk0 uint32 + Unk1 uint32 + Unk2 uint32 + Unk3 uint8 + Unk4 string +} + func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfInfoTournament) bf := byteframe.NewByteFrame() + tournamentInfo0 := []TournamentInfo0{} + tournamentInfo21 := []TournamentInfo21{} + tournamentInfo22 := []TournamentInfo22{} + switch pkt.Unk0 { case 0: - bf.WriteUint32(uint32(TimeAdjusted().Unix())) - bf.WriteUint32(0) // Tied to schedule ID? - case 1: - - bf.WriteBytes(make([]byte, 21)) - ps.Uint8(bf, "", false) - break - - bf.WriteUint32(0xACEDCAFE) - - bf.WriteUint32(5) // Active schedule? - - bf.WriteUint32(1) // Schedule ID? - - bf.WriteUint32(32) // Max players - bf.WriteUint32(0) // Registered players - - bf.WriteUint16(0) - bf.WriteUint16(2) // Color code for schedule item bf.WriteUint32(0) - - bf.WriteUint32(uint32(time.Now().Add(time.Hour * -10).Unix())) - bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) - bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) - bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) - bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) - bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) - - bf.WriteBool(true) // Unk - bf.WriteBool(false) // Cafe-only - - bf.WriteUint32(0) // Min HR - bf.WriteUint32(0) // Max HR - - ps.Uint8(bf, "Test", false) - - // ... + bf.WriteUint32(uint32(len(tournamentInfo0))) + for _, tinfo := range tournamentInfo0 { + bf.WriteUint32(tinfo.ID) + bf.WriteUint32(tinfo.MaxPlayers) + bf.WriteUint32(tinfo.CurrentPlayers) + bf.WriteUint16(tinfo.Unk1) + bf.WriteUint16(tinfo.TextColor) + bf.WriteUint32(tinfo.Unk2) + bf.WriteUint32(uint32(tinfo.Time1.Unix())) + bf.WriteUint32(uint32(tinfo.Time2.Unix())) + bf.WriteUint32(uint32(tinfo.Time3.Unix())) + bf.WriteUint32(uint32(tinfo.Time4.Unix())) + bf.WriteUint32(uint32(tinfo.Time5.Unix())) + bf.WriteUint32(uint32(tinfo.Time6.Unix())) + bf.WriteUint8(tinfo.Unk3) + bf.WriteUint8(tinfo.Unk4) + bf.WriteUint32(tinfo.MinHR) + bf.WriteUint32(tinfo.MaxHR) + ps.Uint8(bf, tinfo.Unk5, true) + ps.Uint16(bf, tinfo.Unk6, true) + } + case 1: + bf.WriteUint32(uint32(TimeAdjusted().Unix())) + bf.WriteUint32(0) // Registered ID + bf.WriteUint32(0) + bf.WriteUint32(0) + bf.WriteUint8(0) + bf.WriteUint32(0) + ps.Uint8(bf, "", true) + case 2: + bf.WriteUint32(0) + bf.WriteUint32(uint32(len(tournamentInfo21))) + for _, info := range tournamentInfo21 { + bf.WriteUint32(info.Unk0) + bf.WriteUint32(info.Unk1) + bf.WriteUint32(info.Unk2) + bf.WriteUint8(info.Unk3) + } + bf.WriteUint32(uint32(len(tournamentInfo22))) + for _, info := range tournamentInfo22 { + bf.WriteUint32(info.Unk0) + bf.WriteUint32(info.Unk1) + bf.WriteUint32(info.Unk2) + bf.WriteUint8(info.Unk3) + ps.Uint8(bf, info.Unk4, true) + } } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } -func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfEntryTournament) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} -func handleMsgMhfAcquireTournament(s *Session, p mhfpacket.MHFPacket) {} +type TournamentReward struct { + Unk0 uint16 + Unk1 uint16 + Unk2 uint16 +} + +func handleMsgMhfAcquireTournament(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfAcquireTournament) + rewards := []TournamentReward{} + bf := byteframe.NewByteFrame() + bf.WriteUint8(uint8(len(rewards))) + for _, reward := range rewards { + bf.WriteUint16(reward.Unk0) + bf.WriteUint16(reward.Unk1) + bf.WriteUint16(reward.Unk2) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index 3d7e34318..2720e07cd 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -131,9 +131,6 @@ func (s *Server) getFriendsForCharacters(chars []character) []members { } friends = append(friends, charFriends...) } - if len(friends) > 255 { // Uint8 - friends = friends[:255] - } return friends } @@ -153,15 +150,12 @@ func (s *Server) getGuildmatesForCharacters(chars []character) []members { if err != nil { continue } - for i, _ := range charGuildmates { + for i := range charGuildmates { charGuildmates[i].CID = char.ID } guildmates = append(guildmates, charGuildmates...) } } - if len(guildmates) > 255 { // Uint8 - guildmates = guildmates[:255] - } return guildmates } diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 65874b683..948a5a011 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -89,7 +89,12 @@ func (s *Session) makeSignResponse(uid int) []byte { if len(friends) == 0 { bf.WriteUint8(0) } else { - bf.WriteUint8(uint8(len(friends))) + if len(friends) > 255 { + bf.WriteUint8(255) + bf.WriteUint16(uint16(len(friends))) + } else { + bf.WriteUint8(uint8(len(friends))) + } for _, friend := range friends { bf.WriteUint32(friend.CID) bf.WriteUint32(friend.ID) @@ -101,7 +106,12 @@ func (s *Session) makeSignResponse(uid int) []byte { if len(guildmates) == 0 { bf.WriteUint8(0) } else { - bf.WriteUint8(uint8(len(guildmates))) + if len(guildmates) > 255 { + bf.WriteUint8(255) + bf.WriteUint16(uint16(len(guildmates))) + } else { + bf.WriteUint8(uint8(len(guildmates))) + } for _, guildmate := range guildmates { bf.WriteUint32(guildmate.CID) bf.WriteUint32(guildmate.ID) @@ -113,7 +123,9 @@ func (s *Session) makeSignResponse(uid int) []byte { bf.WriteBool(false) } else { bf.WriteBool(true) - ps.Uint32(bf, strings.Join(s.server.erupeConfig.LoginNotices[:], ""), true) + bf.WriteUint8(0) + bf.WriteUint8(0) + ps.Uint16(bf, strings.Join(s.server.erupeConfig.LoginNotices[:], ""), true) } bf.WriteUint32(s.server.getLastCID(uid)) @@ -133,12 +145,13 @@ func (s *Session) makeSignResponse(uid int) []byte { bf.WriteUint16(0x4E20) ps.Uint16(bf, "", false) // unk ipv4 bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix())) - bf.WriteUint32(0x00000000) - bf.WriteUint32(0x0A5197DF) // unk id + bf.WriteUint32(0) mezfes := s.server.erupeConfig.DevModeOptions.MezFesEvent alt := s.server.erupeConfig.DevModeOptions.MezFesAlt if mezfes { + // We can just use the start timestamp as the event ID + bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix())) // Start time bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix())) // End time @@ -146,22 +159,23 @@ func (s *Session) makeSignResponse(uid int) []byte { bf.WriteUint8(2) // Unk bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesSoloTickets) bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesGroupTickets) - bf.WriteUint8(8) // Stalls open - bf.WriteUint8(0xA) // Unk - bf.WriteUint8(0x3) // Pachinko - bf.WriteUint8(0x6) // Nyanrendo - bf.WriteUint8(0x9) // Point stall + bf.WriteUint8(8) // Stalls open + bf.WriteUint8(10) // Stall Map + bf.WriteUint8(3) // Pachinko + bf.WriteUint8(6) // Nyanrendo + bf.WriteUint8(9) // Point stall if alt { - bf.WriteUint8(0x2) // Tokotoko + bf.WriteUint8(2) // Tokotoko Partnya } else { - bf.WriteUint8(0x4) // Volpakkun + bf.WriteUint8(4) // Volpakkun Together } - bf.WriteUint8(0x8) // Battle cats - bf.WriteUint8(0x5) // Gook - bf.WriteUint8(0x7) // Honey + bf.WriteUint8(8) // Dokkan Battle Cats + bf.WriteUint8(5) // Goocoo Scoop + bf.WriteUint8(7) // Honey Panic } else { bf.WriteUint32(0) bf.WriteUint32(0) + bf.WriteUint32(0) } return bf.Data() }