Merge branch 'main' into feature/discord-login

This commit is contained in:
Matthew
2023-11-29 10:52:01 -08:00
committed by GitHub
12 changed files with 183 additions and 92 deletions

View File

@@ -5,6 +5,42 @@ INSERT INTO public.shop_items
VALUES
(5,5,16516,100,1,0,0,1,0,0,0,0),
(5,5,16517,100,1,0,0,1,0,0,0,0),
(6,5,9958,3,3,1,0,0,0,0,0,0),
(6,5,1897,3,1,1,0,0,0,0,0,0),
(6,5,8889,3,1,0,0,1,0,0,0,0),
(6,5,6176,3,6,1,0,0,0,0,0,0),
(6,5,1472,3,10,1,0,0,0,0,0,0),
(6,5,7280,3,3,0,0,1,0,0,0,0),
(6,5,8027,3,30,1,0,0,0,0,0,0),
(6,5,8028,3,30,1,0,0,0,0,0,0),
(6,5,8029,3,30,1,0,0,0,0,0,0),
(6,5,8026,3,30,1,0,0,0,0,0,0),
(6,5,8030,3,30,1,0,0,0,0,0,0),
(6,5,4353,3,30,1,0,0,0,0,0,0),
(6,5,4354,3,30,1,0,0,0,0,0,0),
(6,5,4355,3,30,1,0,0,0,0,0,0),
(6,5,4356,3,30,1,0,0,0,0,0,0),
(6,5,4357,3,30,1,0,0,0,0,0,0),
(6,5,4745,3,30,1,0,0,0,0,0,0),
(6,5,4746,3,30,1,0,0,0,0,0,0),
(6,5,4747,3,30,1,0,0,0,0,0,0),
(6,5,4748,3,30,1,0,0,0,0,0,0),
(6,5,4749,3,30,1,0,0,0,0,0,0),
(6,5,5122,3,30,1,0,0,0,0,0,0),
(6,5,5123,3,30,1,0,0,0,0,0,0),
(6,5,5124,3,30,1,0,0,0,0,0,0),
(6,5,5125,3,30,1,0,0,0,0,0,0),
(6,5,5126,3,30,1,0,0,0,0,0,0),
(6,5,5795,3,30,1,0,0,0,0,0,0),
(6,5,5796,3,30,1,0,0,0,0,0,0),
(6,5,5797,3,30,1,0,0,0,0,0,0),
(6,5,5798,3,30,1,0,0,0,0,0,0),
(6,5,5799,3,30,1,0,0,0,0,0,0),
(6,5,6168,3,30,1,0,0,0,0,0,0),
(6,5,6169,3,30,1,0,0,0,0,0,0),
(6,5,6170,3,30,1,0,0,0,0,0,0),
(6,5,6171,3,30,1,0,0,0,0,0,0),
(6,5,6172,3,30,1,0,0,0,0,0,0),
(7,0,13190,10,1,0,0,0,0,0,0,0),
(7,0,1662,10,1,0,0,0,0,0,0,0),
(7,0,10179,100,1,0,0,0,0,0,0,0);

View File

@@ -14,6 +14,7 @@
"ClientMode": "ZZ",
"QuestCacheExpiry": 300,
"ProxyPort": 0,
"CommandPrefix": "!",
"DevMode": true,
"DevModeOptions": {
"AutoCreateAccount": true,
@@ -30,7 +31,7 @@
"QuestDebugTools": false,
"EarthStatusOverride": 0,
"EarthIDOverride": 0,
"EarthMonsterOverride": 0,
"EarthMonsterOverride": [0, 0, 0, 0],
"SaveDumps": {
"Enabled": true,
"OutputDir": "save-backups"
@@ -82,32 +83,44 @@
},
"Commands": [
{
"Name": "Help",
"Enabled": true,
"Description": "Show enabled chat commands",
"Prefix": "help"
}, {
"Name": "Rights",
"Enabled": false,
"Description": "Overwrite the Rights value on your account",
"Prefix": "rights"
}, {
"Name": "Raviente",
"Enabled": true,
"Description": "Various Raviente siege commands",
"Prefix": "ravi"
}, {
"Name": "Teleport",
"Enabled": false,
"Description": "Teleport to specified coordinates",
"Prefix": "tele"
}, {
"Name": "Reload",
"Enabled": true,
"Description": "Reload all players in your Land",
"Prefix": "reload"
}, {
"Name": "KeyQuest",
"Enabled": false,
"Description": "Overwrite your HR Key Quest progress",
"Prefix": "kqf"
}, {
"Name": "Course",
"Enabled": true,
"Description": "Toggle Courses on your account",
"Prefix": "course"
}, {
"Name": "PSN",
"Enabled": true,
"Description": "Link a PlayStation Network ID to your account",
"Prefix": "psn"
}, {
"Name": "Discord",

View File

@@ -81,6 +81,7 @@ type Config struct {
RealClientMode Mode
QuestCacheExpiry int // Number of seconds to keep quest data cached
ProxyPort uint16 // Forces the game to connect to a channel server proxy
CommandPrefix string // The prefix for commands
DevMode bool
DevModeOptions DevModeOptions
@@ -111,7 +112,7 @@ type DevModeOptions struct {
QuestDebugTools bool // Enable various quest debug logs
EarthStatusOverride int32
EarthIDOverride int32
EarthMonsterOverride int32
EarthMonsterOverride []int32
SaveDumps SaveDumpOptions
}
@@ -175,6 +176,7 @@ type DiscordRealTime struct {
type Command struct {
Name string
Enabled bool
Description string
Prefix string
}

View File

@@ -14,7 +14,7 @@ type MsgMhfEnumerateShop struct {
AckHandle uint32
ShopType uint8 // 1 running gachas, 10 normal shop extensions, 8 Diva Defense shop
ShopID uint32
Unk2 uint16 // 00 80 running gachas, 00 20 normal shop
Limit uint16
Unk3 uint8
Unk4 uint8
Unk5 uint32
@@ -30,7 +30,7 @@ func (m *MsgMhfEnumerateShop) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie
m.AckHandle = bf.ReadUint32()
m.ShopType = bf.ReadUint8()
m.ShopID = bf.ReadUint32()
m.Unk2 = bf.ReadUint16()
m.Limit = bf.ReadUint16()
m.Unk3 = bf.ReadUint8()
if _config.ErupeConfig.RealClientMode >= _config.G2 {
m.Unk4 = bf.ReadUint8()

View File

@@ -3,13 +3,22 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfPostSeibattle represents the MSG_MHF_POST_SEIBATTLE
type MsgMhfPostSeibattle struct{}
type MsgMhfPostSeibattle struct {
AckHandle uint32
Unk0 uint8
Unk1 uint8
Unk2 uint32
Unk3 uint8
Unk4 uint16
Unk5 uint16
Unk6 uint8
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfPostSeibattle) Opcode() network.PacketID {
@@ -18,7 +27,15 @@ func (m *MsgMhfPostSeibattle) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfPostSeibattle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8()
m.Unk1 = bf.ReadUint8()
m.Unk2 = bf.ReadUint32()
m.Unk3 = bf.ReadUint8()
m.Unk4 = bf.ReadUint16()
m.Unk5 = bf.ReadUint16()
m.Unk6 = bf.ReadUint8()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -992,10 +992,17 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride)
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride)
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthMonsterOverride)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
for i, m := range s.server.erupeConfig.DevModeOptions.EarthMonsterOverride {
if _config.ErupeConfig.RealClientMode <= _config.G9 {
if i == 3 {
break
}
}
if i == 4 {
break
}
bf.WriteInt32(m)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -1195,7 +1202,10 @@ func handleMsgMhfGetSeibattle(s *Session, p mhfpacket.MHFPacket) {
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostSeibattle)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetDailyMissionMaster(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -85,7 +85,7 @@ func sendServerChatMessage(s *Session, message string) {
}
func parseChatCommand(s *Session, command string) {
args := strings.Split(command[1:], " ")
args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ")
switch args[0] {
case commands["PSN"].Prefix:
if commands["PSN"].Enabled {
@@ -344,6 +344,15 @@ func parseChatCommand(s *Session, command string) {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], discordToken))
} else {
sendDisabledCommandMessage(s, commands["Discord"])
case commands["Help"].Prefix:
if commands["Help"].Enabled {
for _, command := range commands {
if command.Enabled {
sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description))
}
}
} else {
sendDisabledCommandMessage(s, commands["Help"])
}
}
}
@@ -418,7 +427,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
bf.SetLE()
chatMessage := &binpacket.MsgBinChat{}
chatMessage.Parse(bf)
if strings.HasPrefix(chatMessage.Message, "!") {
if strings.HasPrefix(chatMessage.Message, s.server.erupeConfig.CommandPrefix) {
parseChatCommand(s, chatMessage.Message)
return
}

View File

@@ -72,7 +72,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
if s.server.erupeConfig.RealClientMode >= _config.Z2 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32))
} else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
@@ -84,7 +84,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
timestamps = generateDivaTimestamps(s, start, false)
}
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
if s.server.erupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint32(id)
}
for i := range timestamps {

View File

@@ -262,66 +262,55 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveDecoMyset)
// TODO: Backwards compatibility for DecoMyset
if s.server.erupeConfig.RealClientMode < _config.ZZ {
var temp []byte
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&temp)
if err != nil {
s.logger.Error("Failed to load decomyset", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
// https://gist.github.com/Andoryuuta/9c524da7285e4b5ca7e52e0fc1ca1daf
var loadData []byte
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[1:]) // skip first unk byte
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&loadData)
if err != nil {
s.logger.Error("Failed to load decomyset", zap.Error(err))
// Version handling
bf := byteframe.NewByteFrame()
var size uint
if s.server.erupeConfig.RealClientMode >= _config.G10 {
size = 76
bf.WriteUint8(1)
} else {
numSets := bf.ReadUint8() // sets being written
// empty save
if len(loadData) == 0 {
loadData = []byte{0x01, 0x00}
size = 68
bf.WriteUint8(0)
}
savedSets := loadData[1] // existing saved sets
// no sets, new slice with just first 2 bytes for appends later
if savedSets == 0 {
loadData = []byte{0x01, 0x00}
// Handle nil data
if len(temp) == 0 {
temp = append(bf.Data(), uint8(0))
}
for i := 0; i < int(numSets); i++ {
writeSet := bf.ReadUint16()
dataChunk := bf.ReadBytes(76)
setBytes := append([]byte{uint8(writeSet >> 8), uint8(writeSet & 0xff)}, dataChunk...)
for x := 0; true; x++ {
if x == int(savedSets) {
// appending set
if loadData[len(loadData)-1] == 0x10 {
// sanity check for if there was a messy manual import
loadData = append(loadData[:len(loadData)-2], setBytes...)
} else {
loadData = append(loadData, setBytes...)
// Build a map of set data
sets := make(map[uint16][]byte)
oldSets := byteframe.NewByteFrameFromBytes(temp[2:])
for i := uint8(0); i < temp[1]; i++ {
index := oldSets.ReadUint16()
sets[index] = oldSets.ReadBytes(size)
}
savedSets++
break
// Overwrite existing sets
newSets := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[2:])
for i := uint8(0); i < pkt.RawDataPayload[1]; i++ {
index := newSets.ReadUint16()
sets[index] = newSets.ReadBytes(size)
}
currentSet := loadData[3+(x*78)]
if int(currentSet) == int(writeSet) {
// replacing a set
loadData = append(loadData[:2+(x*78)], append(setBytes, loadData[2+((x+1)*78):]...)...)
break
} else if int(currentSet) > int(writeSet) {
// inserting before current set
loadData = append(loadData[:2+((x)*78)], append(setBytes, loadData[2+((x)*78):]...)...)
savedSets++
break
// Serialise the set data
bf.WriteUint8(uint8(len(sets)))
for u, b := range sets {
bf.WriteUint16(u)
bf.WriteBytes(b)
}
}
loadData[1] = savedSets // update set count
}
dumpSaveData(s, loadData, "decomyset")
_, err := s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", loadData, s.charID)
if err != nil {
s.logger.Error("Failed to save decomyset", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
dumpSaveData(s, bf.Data(), "decomyset")
s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", bf.Data(), s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
type Title struct {

View File

@@ -10,13 +10,13 @@ import (
type ShopItem struct {
ID uint32 `db:"id"`
ItemID uint16 `db:"item_id"`
ItemID uint32 `db:"item_id"`
Cost uint32 `db:"cost"`
Quantity uint16 `db:"quantity"`
MinHR uint16 `db:"min_hr"`
MinSR uint16 `db:"min_sr"`
MinGR uint16 `db:"min_gr"`
StoreLevel uint16 `db:"store_level"`
StoreLevel uint8 `db:"store_level"`
MaxQuantity uint16 `db:"max_quantity"`
UsedQuantity uint16 `db:"used_quantity"`
RoadFloors uint16 `db:"road_floors"`
@@ -61,20 +61,31 @@ func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem) {
bf.WriteUint16(uint16(len(items)))
bf.WriteUint16(uint16(len(items)))
for _, item := range items {
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint32(item.ID)
bf.WriteUint16(0)
bf.WriteUint16(item.ItemID)
}
bf.WriteUint32(item.ItemID)
bf.WriteUint32(item.Cost)
bf.WriteUint16(item.Quantity)
bf.WriteUint16(item.MinHR)
bf.WriteUint16(item.MinSR)
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint16(item.MinGR)
bf.WriteUint16(item.StoreLevel)
}
bf.WriteUint8(0) // Unk
bf.WriteUint8(item.StoreLevel)
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint16(item.MaxQuantity)
bf.WriteUint16(item.UsedQuantity)
}
if _config.ErupeConfig.RealClientMode == _config.Z1 {
bf.WriteUint8(uint8(item.RoadFloors))
bf.WriteUint8(uint8(item.RoadFatalis))
} else if _config.ErupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint16(item.RoadFloors)
bf.WriteUint16(item.RoadFatalis)
}
}
}
func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem {
@@ -241,6 +252,9 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
case 10: // Item shop, 0-8
bf := byteframe.NewByteFrame()
items := getShopItems(s, pkt.ShopType, pkt.ShopID)
if len(items) > int(pkt.Limit) {
items = items[:pkt.Limit]
}
writeShopItems(bf, items)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -171,8 +171,8 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
// Transfer back to the saved stage ID before the previous move or enter.
backStage, err := s.stageMoveStack.Pop()
if err != nil {
panic(err)
if backStage == "" || err != nil {
backStage = "sl1Ns200p0a0u0"
}
if _, exists := s.stage.reservedClientSlots[s.charID]; exists {

View File

@@ -126,8 +126,9 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
skills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills)
s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID)
case 7:
s.server.db.Exec(`UPDATE tower SET tr=$1, trp=trp+$2, block1=block1+$3 WHERE char_id=$4`, pkt.TR, pkt.TRP, pkt.Block1, s.charID)
case 1, 7:
// This might give too much TSP? No idea what the rate is supposed to be
s.server.db.Exec(`UPDATE tower SET tr=$1, trp=COALESCE(trp, 0)+$2, tsp=COALESCE(tsp, 0)+$3, block1=COALESCE(block1, 0)+$4 WHERE char_id=$5`, pkt.TR, pkt.TRP, pkt.Cost, pkt.Block1, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}