Merge branch 'ZeruLight:main' into main

This commit is contained in:
Matthew
2023-07-16 15:56:49 -04:00
committed by GitHub
19 changed files with 694 additions and 286 deletions

134
README.md
View File

@@ -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 ## 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. If you want to modify or compile Erupe yourself, please read on.
### Requirements ## Requirements
- [Go](https://go.dev/dl/) - [Go](https://go.dev/dl/)
- [PostgreSQL](https://www.postgresql.org/download/) - [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). 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. 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. 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. 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 ## Resources
- [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z) - [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z)
- [PewPewDojo Discord](https://discord.gg/CFnzbhQ) - [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

View File

@@ -3,13 +3,18 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfAcquireItem represents the MSG_MHF_ACQUIRE_ITEM // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfAcquireItem) Opcode() network.PacketID { func (m *MsgMhfAcquireItem) Opcode() network.PacketID {
@@ -18,7 +23,13 @@ func (m *MsgMhfAcquireItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfAcquireItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -3,13 +3,16 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfAcquireTournament represents the MSG_MHF_ACQUIRE_TOURNAMENT // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfAcquireTournament) Opcode() network.PacketID { func (m *MsgMhfAcquireTournament) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfAcquireTournament) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfAcquireTournament) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,17 +1,18 @@
package mhfpacket package mhfpacket
import ( import (
"errors"
"erupe-ce/common/byteframe"
"erupe-ce/network" "erupe-ce/network"
"erupe-ce/network/clientctx" "erupe-ce/network/clientctx"
"erupe-ce/common/byteframe"
) )
// MsgMhfApplyCampaign represents the MSG_MHF_APPLY_CAMPAIGN // MsgMhfApplyCampaign represents the MSG_MHF_APPLY_CAMPAIGN
type MsgMhfApplyCampaign struct { type MsgMhfApplyCampaign struct {
AckHandle uint32 AckHandle uint32
Unk0 uint8 Unk0 uint32
Unk1 uint8 Unk1 uint16
Unk2 uint16 Unk2 []byte
} }
// Opcode returns the ID associated with this packet type. // 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 // Parse parses the packet from binary
func (m *MsgMhfApplyCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfApplyCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8() m.Unk0 = bf.ReadUint32()
m.Unk1 = bf.ReadUint8() m.Unk1 = bf.ReadUint16()
m.Unk2 = bf.ReadUint16() m.Unk2 = bf.ReadBytes(16)
return nil return nil
} }
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.
func (m *MsgMhfApplyCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfApplyCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
bf.WriteUint32(m.AckHandle) return errors.New("NOT IMPLEMENTED")
bf.WriteUint8(m.Unk0)
bf.WriteUint8(m.Unk1)
bf.WriteUint16(m.Unk2)
return nil
} }

View File

@@ -3,13 +3,17 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfEntryTournament represents the MSG_MHF_ENTRY_TOURNAMENT // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfEntryTournament) Opcode() network.PacketID { func (m *MsgMhfEntryTournament) Opcode() network.PacketID {
@@ -18,7 +22,10 @@ func (m *MsgMhfEntryTournament) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfEntryTournament) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,17 +1,16 @@
package mhfpacket package mhfpacket
import ( import (
"erupe-ce/common/byteframe"
"erupe-ce/network" "erupe-ce/network"
"erupe-ce/network/clientctx" "erupe-ce/network/clientctx"
"erupe-ce/common/byteframe"
) )
// MsgMhfEnumerateCampaign represents the MSG_MHF_ENUMERATE_CAMPAIGN // MsgMhfEnumerateCampaign represents the MSG_MHF_ENUMERATE_CAMPAIGN
type MsgMhfEnumerateCampaign struct { type MsgMhfEnumerateCampaign struct {
AckHandle uint32 AckHandle uint32
Unk0 uint8 Unk0 uint16
Unk1 uint8 Unk1 uint16
Unk2 uint16
} }
// Opcode returns the ID associated with this packet type. // 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 // Parse parses the packet from binary
func (m *MsgMhfEnumerateCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfEnumerateCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8() m.Unk0 = bf.ReadUint16()
m.Unk1 = bf.ReadUint8() m.Unk1 = bf.ReadUint16()
m.Unk2 = bf.ReadUint16()
return nil return nil
} }
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.
func (m *MsgMhfEnumerateCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfEnumerateCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
bf.WriteUint32(m.AckHandle) bf.WriteUint32(m.AckHandle)
bf.WriteUint8(m.Unk0) bf.WriteUint16(m.Unk0)
bf.WriteUint8(m.Unk1) bf.WriteUint16(m.Unk1)
bf.WriteUint16(m.Unk2)
return nil return nil
} }

View File

@@ -3,13 +3,18 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfEnumerateItem represents the MSG_MHF_ENUMERATE_ITEM // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfEnumerateItem) Opcode() network.PacketID { func (m *MsgMhfEnumerateItem) Opcode() network.PacketID {
@@ -18,7 +23,11 @@ func (m *MsgMhfEnumerateItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfEnumerateItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -11,33 +11,33 @@ import (
type OperateGuildAction uint8 type OperateGuildAction uint8
const ( const (
OPERATE_GUILD_DISBAND = 0x01 OperateGuildDisband = iota + 1
OPERATE_GUILD_APPLY = 0x02 OperateGuildApply
OPERATE_GUILD_LEAVE = 0x03 OperateGuildLeave
OPERATE_GUILD_RESIGN = 0x04 OperateGuildResign
OPERATE_GUILD_SET_APPLICATION_DENY = 0x05 OperateGuildSetApplicationDeny
OPERATE_GUILD_SET_APPLICATION_ALLOW = 0x06 OperateGuildSetApplicationAllow
OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE = 0x07 OperateGuildSetAvoidLeadershipTrue
OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE = 0x08 OperateGuildSetAvoidLeadershipFalse
OPERATE_GUILD_UPDATE_COMMENT = 0x09 OperateGuildUpdateComment
OPERATE_GUILD_DONATE_RANK = 0x0a OperateGuildDonateRank
OPERATE_GUILD_UPDATE_MOTTO = 0x0b OperateGuildUpdateMotto
OPERATE_GUILD_RENAME_PUGI_1 = 0x0c OperateGuildRenamePugi1
OPERATE_GUILD_RENAME_PUGI_2 = 0x0d OperateGuildRenamePugi2
OPERATE_GUILD_RENAME_PUGI_3 = 0x0e OperateGuildRenamePugi3
OPERATE_GUILD_CHANGE_PUGI_1 = 0x0f OperateGuildChangePugi1
OPERATE_GUILD_CHANGE_PUGI_2 = 0x10 OperateGuildChangePugi2
OPERATE_GUILD_CHANGE_PUGI_3 = 0x11 OperateGuildChangePugi3
OPERATE_GUILD_UNLOCK_OUTFIT = 0x12 OperateGuildUnlockOutfit
// 0x13 Unk OperateGuildDonateRoom
// 0x14 Unk OperateGuildGraduateRookie
OPERATE_GUILD_DONATE_EVENT = 0x15 OperateGuildDonateEvent
OPERATE_GUILD_EVENT_EXCHANGE = 0x16 OperateGuildEventExchange
// 0x17 Unk OperateGuildUnknown // I don't think this op exists
// 0x18 Unk OperateGuildGraduateReturn
OPERATE_GUILD_CHANGE_DIVA_PUGI_1 = 0x19 OperateGuildChangeDivaPugi1
OPERATE_GUILD_CHANGE_DIVA_PUGI_2 = 0x1a OperateGuildChangeDivaPugi2
OPERATE_GUILD_CHANGE_DIVA_PUGI_3 = 0x1b OperateGuildChangeDivaPugi3
) )
// MsgMhfOperateGuild represents the MSG_MHF_OPERATE_GUILD // MsgMhfOperateGuild represents the MSG_MHF_OPERATE_GUILD

View File

@@ -1,18 +1,18 @@
package mhfpacket package mhfpacket
import ( import (
"errors"
"erupe-ce/network/clientctx" "erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
) )
// MsgMhfStateCampaign represents the MSG_MHF_STATE_CAMPAIGN // MsgMhfStateCampaign represents the MSG_MHF_STATE_CAMPAIGN
type MsgMhfStateCampaign struct { type MsgMhfStateCampaign struct {
AckHandle uint32 AckHandle uint32
Unk0 uint8 CampaignID uint32
Unk1 uint8 Unk1 uint16
Unk2 uint16
} }
// Opcode returns the ID associated with this packet type. // 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 // Parse parses the packet from binary
func (m *MsgMhfStateCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfStateCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8() m.CampaignID = bf.ReadUint32()
m.Unk1 = bf.ReadUint8() m.Unk1 = bf.ReadUint16()
m.Unk2 = bf.ReadUint16()
return nil return nil
} }
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.
func (m *MsgMhfStateCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfStateCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
bf.WriteUint32(m.AckHandle) return errors.New("NOT IMPLEMENTED")
bf.WriteUint8(m.Unk0)
bf.WriteUint8(m.Unk1)
bf.WriteUint16(m.Unk2)
return nil
} }

View File

@@ -13,12 +13,8 @@ type MsgMhfUpdateBeatLevel struct {
AckHandle uint32 AckHandle uint32
Unk1 uint32 Unk1 uint32
Unk2 uint32 Unk2 uint32
MonsterData []byte Data1 []int32
Unk3 uint8 Data2 []int32
Unk4 uint32
Unk5 uint16
Unk6 uint8
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -31,11 +27,12 @@ func (m *MsgMhfUpdateBeatLevel) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk1 = bf.ReadUint32() m.Unk1 = bf.ReadUint32()
m.Unk2 = bf.ReadUint32() m.Unk2 = bf.ReadUint32()
m.MonsterData = bf.ReadBytes(uint(120)) for i := 0; i < 16; i++ {
m.Unk3 = bf.ReadUint8() m.Data1 = append(m.Data1, bf.ReadInt32())
m.Unk4 = bf.ReadUint32() }
m.Unk5 = bf.ReadUint16() for i := 0; i < 16; i++ {
m.Unk6 = bf.ReadUint8() m.Data2 = append(m.Data2, bf.ReadInt32())
}
return nil return nil
} }

View File

@@ -552,10 +552,6 @@ func handleMsgSysInfokyserver(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetCaUniqueID(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) { func handleMsgMhfTransferItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfTransferItem) pkt := p.(*mhfpacket.MsgMhfTransferItem)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})

View File

@@ -1,18 +1,173 @@
package channelserver 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) { func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign) 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) { func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateCampaign) 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) { func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyCampaign) 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))
} }

View File

@@ -151,7 +151,7 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() {
if _config.ErupeConfig.RealClientMode == _config.ZZ { if _config.ErupeConfig.RealClientMode == _config.ZZ {
copy(save.decompSave[pointerRP:pointerRP+2], rpBytes) copy(save.decompSave[pointerRP:pointerRP+2], rpBytes)
copy(save.decompSave[pointerKQF:pointerKQF+8], save.KQF) 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[pointerRPZ:pointerRPZ+2], rpBytes)
copy(save.decompSave[pointerKQFZ:pointerKQFZ+8], save.KQF) 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.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[pointerGRP : pointerGRP+4]))
} }
save.KQF = save.decompSave[pointerKQF : pointerKQF+8] 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.RP = binary.LittleEndian.Uint16(save.decompSave[pointerRPZ : pointerRPZ+2])
save.HouseTier = save.decompSave[pointerHouseTierZ : pointerHouseTierZ+5] save.HouseTier = save.decompSave[pointerHouseTierZ : pointerHouseTierZ+5]
save.HouseData = save.decompSave[pointerHouseDataZ : pointerHouseDataZ+195] save.HouseData = save.decompSave[pointerHouseDataZ : pointerHouseDataZ+195]

View File

@@ -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) { func handleMsgMhfEnumerateEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateEvent) 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 { type activeFeature struct {

View File

@@ -616,13 +616,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperateGuild) pkt := p.(*mhfpacket.MsgMhfOperateGuild)
guild, err := GetGuildInfoByID(s, pkt.GuildID) guild, err := GetGuildInfoByID(s, pkt.GuildID)
if err != nil {
return
}
characterGuildInfo, err := GetCharacterGuildData(s, s.charID) characterGuildInfo, err := GetCharacterGuildData(s, s.charID)
if err != nil { if err != nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return return
@@ -631,22 +625,19 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
switch pkt.Action { switch pkt.Action {
case mhfpacket.OPERATE_GUILD_DISBAND: case mhfpacket.OperateGuildDisband:
response := 1
if guild.LeaderCharID != s.charID { if guild.LeaderCharID != s.charID {
s.logger.Warn(fmt.Sprintf("character '%d' is attempting to manage guild '%d' without permission", s.charID, guild.ID)) 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) err = guild.Disband(s)
response := 0x01
if err != nil { if err != nil {
// All successful acks return 0x01, assuming 0x00 is failure response = 0
response = 0x00 }
} }
bf.WriteUint32(uint32(response)) bf.WriteUint32(uint32(response))
case mhfpacket.OPERATE_GUILD_RESIGN: case mhfpacket.OperateGuildResign:
guildMembers, err := GetGuildMembers(s, guild.ID, false) guildMembers, err := GetGuildMembers(s, guild.ID, false)
if err == nil { if err == nil {
sort.Slice(guildMembers[:], func(i, j int) bool { sort.Slice(guildMembers[:], func(i, j int) bool {
@@ -665,25 +656,22 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
} }
guild.Save(s) guild.Save(s)
} }
case mhfpacket.OPERATE_GUILD_APPLY: case mhfpacket.OperateGuildApply:
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil) err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
if err == nil { if err == nil {
bf.WriteUint32(guild.LeaderCharID) bf.WriteUint32(guild.LeaderCharID)
} else {
bf.WriteUint32(0)
} }
case mhfpacket.OPERATE_GUILD_LEAVE: case mhfpacket.OperateGuildLeave:
var err error
if characterGuildInfo.IsApplicant { if characterGuildInfo.IsApplicant {
err = guild.RejectApplication(s, s.charID) err = guild.RejectApplication(s, s.charID)
} else { } else {
err = guild.RemoveCharacter(s, s.charID) err = guild.RemoveCharacter(s, s.charID)
} }
response := 1
response := 0x01
if err != nil { if err != nil {
// All successful acks return 0x01, assuming 0x00 is failure response = 0
response = 0x00
} else { } else {
mail := Mail{ mail := Mail{
RecipientID: s.charID, RecipientID: s.charID,
@@ -693,26 +681,25 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
} }
mail.Send(s, nil) mail.Send(s, nil)
} }
bf.WriteUint32(uint32(response)) bf.WriteUint32(uint32(response))
case mhfpacket.OPERATE_GUILD_DONATE_RANK: case mhfpacket.OperateGuildDonateRank:
bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, false)) 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) 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) 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) handleAvoidLeadershipUpdate(s, pkt, true)
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE: case mhfpacket.OperateGuildSetAvoidLeadershipFalse:
handleAvoidLeadershipUpdate(s, pkt, false) handleAvoidLeadershipUpdate(s, pkt, false)
case mhfpacket.OPERATE_GUILD_UPDATE_COMMENT: case mhfpacket.OperateGuildUpdateComment:
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() { if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
guild.Comment = stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes()) guild.Comment = stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
guild.Save(s) guild.Save(s)
case mhfpacket.OPERATE_GUILD_UPDATE_MOTTO: case mhfpacket.OperateGuildUpdateMotto:
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() { if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return return
@@ -721,27 +708,29 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
guild.SubMotto = pkt.Data1.ReadUint8() guild.SubMotto = pkt.Data1.ReadUint8()
guild.MainMotto = pkt.Data1.ReadUint8() guild.MainMotto = pkt.Data1.ReadUint8()
guild.Save(s) guild.Save(s)
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_1: case mhfpacket.OperateGuildRenamePugi1:
handleRenamePugi(s, pkt.Data2, guild, 1) handleRenamePugi(s, pkt.Data2, guild, 1)
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_2: case mhfpacket.OperateGuildRenamePugi2:
handleRenamePugi(s, pkt.Data2, guild, 2) handleRenamePugi(s, pkt.Data2, guild, 2)
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_3: case mhfpacket.OperateGuildRenamePugi3:
handleRenamePugi(s, pkt.Data2, guild, 3) handleRenamePugi(s, pkt.Data2, guild, 3)
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_1: case mhfpacket.OperateGuildChangePugi1:
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 1) 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) 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) 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 // 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) 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()) quantity := uint16(pkt.Data1.ReadUint32())
bf.WriteBytes(handleDonateRP(s, quantity, guild, true)) bf.WriteBytes(handleDonateRP(s, quantity, guild, true))
// TODO: Move this value onto rp_yesterday and reset to 0... daily? // 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) 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()) rp := uint16(pkt.Data1.ReadUint32())
var balance uint32 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) 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) bf.WriteUint8(guild.SubMotto)
// Unk appears to be static // 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) bf.WriteBool(!guild.Recruiting)
@@ -953,28 +947,39 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(FestivalColourCodes[guild.FestivalColour]) bf.WriteUint8(FestivalColourCodes[guild.FestivalColour])
bf.WriteUint32(guild.RankRP) bf.WriteUint32(guild.RankRP)
bf.WriteBytes(guildLeaderName) bf.WriteBytes(guildLeaderName)
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00}) // Unk bf.WriteUint32(0) // Unk
bf.WriteBool(false) // isReturnGuild bf.WriteBool(false) // isReturnGuild
bf.WriteBool(false) // earnedSpecialHall bf.WriteBool(false) // earnedSpecialHall
bf.WriteBytes([]byte{0x02, 0x02}) // Unk bf.WriteUint8(2)
bf.WriteUint32(guild.EventRP) bf.WriteUint8(2)
bf.WriteUint32(guild.EventRP) // Skipped if last byte is <2?
ps.Uint8(bf, guild.PugiName1, true) ps.Uint8(bf, guild.PugiName1, true)
ps.Uint8(bf, guild.PugiName2, true) ps.Uint8(bf, guild.PugiName2, true)
ps.Uint8(bf, guild.PugiName3, true) ps.Uint8(bf, guild.PugiName3, true)
bf.WriteUint8(guild.PugiOutfit1) bf.WriteUint8(guild.PugiOutfit1)
bf.WriteUint8(guild.PugiOutfit2) bf.WriteUint8(guild.PugiOutfit2)
bf.WriteUint8(guild.PugiOutfit3) bf.WriteUint8(guild.PugiOutfit3)
if s.server.erupeConfig.RealClientMode >= _config.Z1 {
bf.WriteUint8(guild.PugiOutfit1) bf.WriteUint8(guild.PugiOutfit1)
bf.WriteUint8(guild.PugiOutfit2) bf.WriteUint8(guild.PugiOutfit2)
bf.WriteUint8(guild.PugiOutfit3) bf.WriteUint8(guild.PugiOutfit3)
}
bf.WriteUint32(guild.PugiOutfits) bf.WriteUint32(guild.PugiOutfits)
// Unk flags if guild.Rank >= 3 {
bf.WriteUint8(0x3C) // also seen as 0x32 on JP and 0x64 on TW 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{ bf.WriteUint32(55000)
0x00, 0x00, 0xD6, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, bf.WriteUint32(0)
}) bf.WriteUint16(0) // Changing Room RP
bf.WriteUint16(0)
if guild.AllianceID > 0 { if guild.AllianceID > 0 {
alliance, err := GetAllianceData(s, guild.AllianceID) alliance, err := GetAllianceData(s, guild.AllianceID)
@@ -984,7 +989,8 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(alliance.ID) bf.WriteUint32(alliance.ID)
bf.WriteUint32(uint32(alliance.CreatedAt.Unix())) bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
bf.WriteUint16(alliance.TotalMembers) bf.WriteUint16(alliance.TotalMembers)
bf.WriteUint16(0) // Unk0 bf.WriteUint8(0)
bf.WriteUint8(0)
ps.Uint16(bf, alliance.Name, true) ps.Uint16(bf, alliance.Name, true)
if alliance.SubGuild1ID > 0 { if alliance.SubGuild1ID > 0 {
if alliance.SubGuild2ID > 0 { if alliance.SubGuild2ID > 0 {
@@ -1044,29 +1050,46 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(uint16(len(applicants))) bf.WriteUint16(uint16(len(applicants)))
for _, applicant := range applicants { for _, applicant := range applicants {
bf.WriteUint32(applicant.CharID) bf.WriteUint32(applicant.CharID)
bf.WriteUint16(0) bf.WriteUint32(0)
bf.WriteUint16(0)
bf.WriteUint16(applicant.HRP) bf.WriteUint16(applicant.HRP)
bf.WriteUint16(applicant.GR) bf.WriteUint16(applicant.GR)
ps.Uint8(bf, applicant.Name, true) 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)
}
/* type AllianceInvite struct {
alliance application format GuildID uint32
uint16 numapplicants (above) LeaderID uint32
Unk0 uint16
uint32 guild id Unk1 uint16
uint32 guild leader id (for mail) Members uint16
uint32 unk (always null in pcap) GuildName string
uint16 member count LeaderName string
uint16 len guild name }
string nullterm guild name allianceInvites := []AllianceInvite{}
uint16 len guild leader name bf.WriteUint8(uint8(len(allianceInvites)))
string nullterm guild leader name 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 { if guild.Icon != nil {
bf.WriteUint8(uint8(len(guild.Icon.Parts))) bf.WriteUint8(uint8(len(guild.Icon.Parts)))
@@ -1084,7 +1107,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(p.PosY) bf.WriteUint16(p.PosY)
} }
} else { } else {
bf.WriteUint8(0x00) bf.WriteUint8(0)
} }
bf.WriteUint8(0) // Unk bf.WriteUint8(0) // Unk
@@ -1348,7 +1371,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
if guild != nil { if guild != nil {
isApplicant, _ := guild.HasApplicationForCharID(s, s.charID) isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
if isApplicant { if isApplicant {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
return return
} }
} }
@@ -1390,7 +1413,9 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
for _, member := range guildMembers { for _, member := range guildMembers {
bf.WriteUint32(member.CharID) bf.WriteUint32(member.CharID)
bf.WriteUint16(member.HRP) bf.WriteUint16(member.HRP)
if s.server.erupeConfig.RealClientMode > _config.G7 {
bf.WriteUint16(member.GR) bf.WriteUint16(member.GR)
}
if s.server.erupeConfig.RealClientMode < _config.ZZ { if s.server.erupeConfig.RealClientMode < _config.ZZ {
// Magnet Spike crash workaround // Magnet Spike crash workaround
bf.WriteUint16(0) bf.WriteUint16(0)
@@ -1456,7 +1481,6 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildManageRight) pkt := p.(*mhfpacket.MsgMhfGetGuildManageRight)
guild, err := GetGuildInfoByCharacterId(s, s.charID) guild, err := GetGuildInfoByCharacterId(s, s.charID)
if guild == nil && s.prevGuildID != 0 { if guild == nil && s.prevGuildID != 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID) guild, err = GetGuildInfoByID(s, s.prevGuildID)
s.prevGuildID = 0 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 := byteframe.NewByteFrame()
bf.WriteUint16(0x00) // Unk bf.WriteUint32(uint32(guild.MemberCount))
bf.WriteUint16(0x00) // Member count
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
bf := byteframe.NewByteFrame()
bf.WriteUint16(0x00) // Unk
bf.WriteUint16(guild.MemberCount)
members, _ := GetGuildMembers(s, guild.ID, false) members, _ := GetGuildMembers(s, guild.ID, false)
for _, member := range members { for _, member := range members {
bf.WriteUint32(member.CharID) bf.WriteUint32(member.CharID)
bf.WriteBool(member.Recruiter) bf.WriteBool(member.Recruiter)
bf.WriteBytes(make([]byte, 3)) bf.WriteBytes(make([]byte, 3))
} }
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }

View File

@@ -4,6 +4,7 @@ import (
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport" "erupe-ce/common/stringsupport"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"fmt" "fmt"
"go.uber.org/zap" "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)) s.logger.Error("Failed to load decomyset", zap.Error(err))
} }
if len(data) == 0 { if len(data) == 0 {
if s.server.erupeConfig.RealClientMode <= _config.G7 {
data = []byte{0x00, 0x00}
}
data = []byte{0x01, 0x00} data = []byte{0x01, 0x00}
} }
doAckBufSucceed(s, pkt.AckHandle, data) doAckBufSucceed(s, pkt.AckHandle, data)

View File

@@ -7,54 +7,124 @@ import (
"time" "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) { func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoTournament) pkt := p.(*mhfpacket.MsgMhfInfoTournament)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
tournamentInfo0 := []TournamentInfo0{}
tournamentInfo21 := []TournamentInfo21{}
tournamentInfo22 := []TournamentInfo22{}
switch pkt.Unk0 { switch pkt.Unk0 {
case 0: 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(0)
bf.WriteUint32(uint32(len(tournamentInfo0)))
bf.WriteUint32(uint32(time.Now().Add(time.Hour * -10).Unix())) for _, tinfo := range tournamentInfo0 {
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) bf.WriteUint32(tinfo.ID)
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) bf.WriteUint32(tinfo.MaxPlayers)
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) bf.WriteUint32(tinfo.CurrentPlayers)
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) bf.WriteUint16(tinfo.Unk1)
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix())) bf.WriteUint16(tinfo.TextColor)
bf.WriteUint32(tinfo.Unk2)
bf.WriteBool(true) // Unk bf.WriteUint32(uint32(tinfo.Time1.Unix()))
bf.WriteBool(false) // Cafe-only bf.WriteUint32(uint32(tinfo.Time2.Unix()))
bf.WriteUint32(uint32(tinfo.Time3.Unix()))
bf.WriteUint32(0) // Min HR bf.WriteUint32(uint32(tinfo.Time4.Unix()))
bf.WriteUint32(0) // Max HR bf.WriteUint32(uint32(tinfo.Time5.Unix()))
bf.WriteUint32(uint32(tinfo.Time6.Unix()))
ps.Uint8(bf, "Test", false) 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()) 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())
}

View File

@@ -131,9 +131,6 @@ func (s *Server) getFriendsForCharacters(chars []character) []members {
} }
friends = append(friends, charFriends...) friends = append(friends, charFriends...)
} }
if len(friends) > 255 { // Uint8
friends = friends[:255]
}
return friends return friends
} }
@@ -153,15 +150,12 @@ func (s *Server) getGuildmatesForCharacters(chars []character) []members {
if err != nil { if err != nil {
continue continue
} }
for i, _ := range charGuildmates { for i := range charGuildmates {
charGuildmates[i].CID = char.ID charGuildmates[i].CID = char.ID
} }
guildmates = append(guildmates, charGuildmates...) guildmates = append(guildmates, charGuildmates...)
} }
} }
if len(guildmates) > 255 { // Uint8
guildmates = guildmates[:255]
}
return guildmates return guildmates
} }

View File

@@ -88,8 +88,13 @@ func (s *Session) makeSignResponse(uid int) []byte {
friends := s.server.getFriendsForCharacters(chars) friends := s.server.getFriendsForCharacters(chars)
if len(friends) == 0 { if len(friends) == 0 {
bf.WriteUint8(0) bf.WriteUint8(0)
} else {
if len(friends) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(friends)))
} else { } else {
bf.WriteUint8(uint8(len(friends))) bf.WriteUint8(uint8(len(friends)))
}
for _, friend := range friends { for _, friend := range friends {
bf.WriteUint32(friend.CID) bf.WriteUint32(friend.CID)
bf.WriteUint32(friend.ID) bf.WriteUint32(friend.ID)
@@ -100,8 +105,13 @@ func (s *Session) makeSignResponse(uid int) []byte {
guildmates := s.server.getGuildmatesForCharacters(chars) guildmates := s.server.getGuildmatesForCharacters(chars)
if len(guildmates) == 0 { if len(guildmates) == 0 {
bf.WriteUint8(0) bf.WriteUint8(0)
} else {
if len(guildmates) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(guildmates)))
} else { } else {
bf.WriteUint8(uint8(len(guildmates))) bf.WriteUint8(uint8(len(guildmates)))
}
for _, guildmate := range guildmates { for _, guildmate := range guildmates {
bf.WriteUint32(guildmate.CID) bf.WriteUint32(guildmate.CID)
bf.WriteUint32(guildmate.ID) bf.WriteUint32(guildmate.ID)
@@ -113,7 +123,9 @@ func (s *Session) makeSignResponse(uid int) []byte {
bf.WriteBool(false) bf.WriteBool(false)
} else { } else {
bf.WriteBool(true) bf.WriteBool(true)
ps.Uint32(bf, strings.Join(s.server.erupeConfig.LoginNotices[:], "<PAGE>"), true) bf.WriteUint8(0)
bf.WriteUint8(0)
ps.Uint16(bf, strings.Join(s.server.erupeConfig.LoginNotices[:], "<PAGE>"), true)
} }
bf.WriteUint32(s.server.getLastCID(uid)) bf.WriteUint32(s.server.getLastCID(uid))
@@ -133,12 +145,13 @@ func (s *Session) makeSignResponse(uid int) []byte {
bf.WriteUint16(0x4E20) bf.WriteUint16(0x4E20)
ps.Uint16(bf, "", false) // unk ipv4 ps.Uint16(bf, "", false) // unk ipv4
bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix())) bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix()))
bf.WriteUint32(0x00000000) bf.WriteUint32(0)
bf.WriteUint32(0x0A5197DF) // unk id
mezfes := s.server.erupeConfig.DevModeOptions.MezFesEvent mezfes := s.server.erupeConfig.DevModeOptions.MezFesEvent
alt := s.server.erupeConfig.DevModeOptions.MezFesAlt alt := s.server.erupeConfig.DevModeOptions.MezFesAlt
if mezfes { if mezfes {
// We can just use the start timestamp as the event ID
bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix()))
// Start time // Start time
bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix())) bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix()))
// End time // End time
@@ -147,21 +160,22 @@ func (s *Session) makeSignResponse(uid int) []byte {
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesSoloTickets) bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesSoloTickets)
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesGroupTickets) bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesGroupTickets)
bf.WriteUint8(8) // Stalls open bf.WriteUint8(8) // Stalls open
bf.WriteUint8(0xA) // Unk bf.WriteUint8(10) // Stall Map
bf.WriteUint8(0x3) // Pachinko bf.WriteUint8(3) // Pachinko
bf.WriteUint8(0x6) // Nyanrendo bf.WriteUint8(6) // Nyanrendo
bf.WriteUint8(0x9) // Point stall bf.WriteUint8(9) // Point stall
if alt { if alt {
bf.WriteUint8(0x2) // Tokotoko bf.WriteUint8(2) // Tokotoko Partnya
} else { } else {
bf.WriteUint8(0x4) // Volpakkun bf.WriteUint8(4) // Volpakkun Together
} }
bf.WriteUint8(0x8) // Battle cats bf.WriteUint8(8) // Dokkan Battle Cats
bf.WriteUint8(0x5) // Gook bf.WriteUint8(5) // Goocoo Scoop
bf.WriteUint8(0x7) // Honey bf.WriteUint8(7) // Honey Panic
} else { } else {
bf.WriteUint32(0) bf.WriteUint32(0)
bf.WriteUint32(0) bf.WriteUint32(0)
bf.WriteUint32(0)
} }
return bf.Data() return bf.Data()
} }