repository cleanup

This commit is contained in:
wish
2022-07-29 03:25:23 +10:00
parent a0be6c627c
commit 2c0e7a5267
645 changed files with 996 additions and 903 deletions

View File

@@ -0,0 +1,105 @@
package deltacomp
import (
"bytes"
"fmt"
"io"
)
func checkReadUint8(r *bytes.Reader) (uint8, error) {
b, err := r.ReadByte()
if err != nil {
return 0, err
}
return b, nil
}
func checkReadUint16(r *bytes.Reader) (uint16, error) {
data := make([]byte, 2)
n, err := r.Read(data)
if err != nil {
return 0, err
} else if n != len(data) {
return 0, io.EOF
}
return uint16(data[0])<<8 | uint16(data[1]), nil
}
func readCount(r *bytes.Reader) (int, error) {
var count int
count8, err := checkReadUint8(r)
if err != nil {
return 0, err
}
count = int(count8)
if count == 0 {
count16, err := checkReadUint16(r)
if err != nil {
return 0, err
}
count = int(count16)
}
return int(count), nil
}
// ApplyDataDiff applies a delta data diff patch onto given base data.
func ApplyDataDiff(diff []byte, baseData []byte) []byte {
// Make a copy of the base data to return,
// (probably just make this modify the given slice in the future).
baseCopy := make([]byte, len(baseData))
copy(baseCopy, baseData)
patch := bytes.NewReader(diff)
// The very first matchCount is +1 more than it should be, so we start at -1.
dataOffset := -1
for {
// Read the amount of matching bytes.
matchCount, err := readCount(patch)
if err != nil {
// No more data
break
}
dataOffset += matchCount
// Read the amount of differing bytes.
differentCount, err := readCount(patch)
if err != nil {
// No more data
break
}
differentCount--
// Grow slice if it's required
if len(baseCopy) < dataOffset {
fmt.Printf("Slice smaller than data offset, growing slice...")
baseCopy = append(baseCopy, make([]byte, (dataOffset+differentCount)-len(baseData))...)
} else {
length := len(baseCopy[dataOffset:])
if length < differentCount {
length -= differentCount
baseCopy = append(baseCopy, make([]byte, length)...)
}
}
// Apply the patch bytes.
for i := 0; i < differentCount; i++ {
b, err := checkReadUint8(patch)
if err != nil {
panic("Invalid or misunderstood patch format!")
}
baseCopy[dataOffset+i] = b
}
dataOffset += differentCount - 1
}
return baseCopy
}

View File

@@ -0,0 +1,113 @@
package deltacomp
import (
"bytes"
"encoding/hex"
"fmt"
"io/ioutil"
"testing"
"erupe-ce/server/channelserver/compression/nullcomp"
)
var tests = []struct {
before string
patches []string
after string
}{
{
"hunternavi_0_before.bin",
[]string{
"hunternavi_0_patch_0.bin",
"hunternavi_0_patch_1.bin",
},
"hunternavi_0_after.bin",
},
{
// From "Character Progression 1 Creation-NPCs-Tours"
"hunternavi_1_before.bin",
[]string{
"hunternavi_1_patch_0.bin",
"hunternavi_1_patch_1.bin",
"hunternavi_1_patch_2.bin",
"hunternavi_1_patch_3.bin",
"hunternavi_1_patch_4.bin",
"hunternavi_1_patch_5.bin",
"hunternavi_1_patch_6.bin",
"hunternavi_1_patch_7.bin",
"hunternavi_1_patch_8.bin",
"hunternavi_1_patch_9.bin",
"hunternavi_1_patch_10.bin",
"hunternavi_1_patch_11.bin",
"hunternavi_1_patch_12.bin",
"hunternavi_1_patch_13.bin",
"hunternavi_1_patch_14.bin",
"hunternavi_1_patch_15.bin",
"hunternavi_1_patch_16.bin",
"hunternavi_1_patch_17.bin",
"hunternavi_1_patch_18.bin",
"hunternavi_1_patch_19.bin",
"hunternavi_1_patch_20.bin",
"hunternavi_1_patch_21.bin",
"hunternavi_1_patch_22.bin",
"hunternavi_1_patch_23.bin",
"hunternavi_1_patch_24.bin",
},
"hunternavi_1_after.bin",
},
{
// From "Progress Gogo GRP Grind 9 and Armor Upgrades and Partner Equip and Lost Cat and Manager talk and Pugi Order"
// Not really sure this one counts as a valid test as the input and output are exactly the same. The patches cancel each other out.
"platedata_0_before.bin",
[]string{
"platedata_0_patch_0.bin",
"platedata_0_patch_1.bin",
},
"platedata_0_after.bin",
},
}
func readTestDataFile(filename string) []byte {
data, err := ioutil.ReadFile(fmt.Sprintf("./test_data/%s", filename))
if err != nil {
panic(err)
}
return data
}
func TestDeltaPatch(t *testing.T) {
for k, tt := range tests {
testname := fmt.Sprintf("delta_patch_test_%d", k)
t.Run(testname, func(t *testing.T) {
// Load the test binary data.
beforeData, err := nullcomp.Decompress(readTestDataFile(tt.before))
if err != nil {
t.Error(err)
}
var patches [][]byte
for _, patchName := range tt.patches {
patchData := readTestDataFile(patchName)
patches = append(patches, patchData)
}
afterData, err := nullcomp.Decompress(readTestDataFile(tt.after))
if err != nil {
t.Error(err)
}
// Now actually test calling ApplyDataDiff.
data := beforeData
// Apply the patches in order.
for i, patch := range patches {
fmt.Println("patch index: ", i)
data = ApplyDataDiff(patch, data)
}
if !bytes.Equal(data, afterData) {
t.Errorf("got out\n\t%s\nwant\n\t%s", hex.Dump(data), hex.Dump(afterData))
}
})
}
}

View File

@@ -0,0 +1,98 @@
package nullcomp
import (
"bytes"
"io"
)
// Decompress decompresses null-compressesed data.
func Decompress(compData []byte) ([]byte, error) {
r := bytes.NewReader(compData)
header := make([]byte, 16)
n, err := r.Read(header)
if err != nil {
return nil, err
} else if n != len(header) {
return nil, err
}
// Just return the data if it doesn't contain the cmp header.
if !bytes.Equal(header, []byte("cmp\x2020110113\x20\x20\x20\x00")) {
return compData, nil
}
var output []byte
for {
b, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if b == 0 {
// If it's a null byte, then the next byte is how many nulls to add.
nullCount, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
output = append(output, make([]byte, int(nullCount))...)
} else {
output = append(output, b)
}
}
return output, nil
}
// Compress null compresses give given data.
func Compress(rawData []byte) ([]byte, error) {
r := bytes.NewReader(rawData)
var output []byte
output = append(output, []byte("cmp\x2020110113\x20\x20\x20\x00")...)
for {
b, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if b == 0 {
output = append(output, []byte{0x00}...)
// read to get null count
nullCount := 1
for {
i, err := r.ReadByte()
if err == io.EOF {
output = append(output, []byte{byte(nullCount)}...)
break
} else if i != 0 && nullCount != 0 {
r.UnreadByte()
output = append(output, []byte{byte(nullCount)}...)
break
} else if i != 0 && nullCount == 0 {
r.UnreadByte()
output = output[:len(output)-2]
output = append(output, []byte{byte(0xFF)}...)
break
} else if err != nil {
return nil, err
}
nullCount++
// Flush the null-count if it gets to 255, start on the next null count.
if nullCount == 255 {
output = append(output, []byte{0xFF, 0x00}...)
nullCount = 0
}
}
} else {
output = append(output, b)
}
}
return output, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetAchievement)
achievementStruct := []struct {
ID uint8 // Main ID
Unk0 uint8 // always FF
Unk1 uint16 // 0x05 0x00
Unk2 uint32 // 0x01 0x0A 0x05 0x00
}{
{ID: 0, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 1, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 2, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 3, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 4, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 5, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 6, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 7, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 8, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 9, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 10, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 11, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 12, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 13, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 14, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 15, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 16, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 17, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 18, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 19, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 20, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 21, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 22, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 23, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 24, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 25, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 26, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 27, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 28, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 29, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 30, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 31, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 32, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 33, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 34, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 35, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 36, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 37, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 38, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 39, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 40, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 41, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 42, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 43, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 44, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 45, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 46, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 47, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 48, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 49, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 50, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 51, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 52, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 53, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 54, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 55, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 56, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 57, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 58, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 59, Unk0: 0xFF, Unk1: 0, Unk2: 0},
}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(achievementStruct))) // Entry count
for _, entry := range achievementStruct {
resp.WriteUint8(entry.ID)
resp.WriteUint8(entry.Unk0)
resp.WriteUint16(entry.Unk1)
resp.WriteUint32(entry.Unk2)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfSetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetCaAchievementHist)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfResetAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPaymentAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetCaAchievement(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,18 @@
package channelserver
import "erupe-ce/network/mhfpacket"
func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign)
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateCampaign)
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyCampaign)
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}

View File

@@ -0,0 +1,11 @@
package channelserver
import (
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfCaravanMyScore(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfCaravanRanking(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfCaravanMyRank(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,282 @@
package channelserver
import (
"fmt"
"math"
"math/rand"
"strings"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
)
// MSG_SYS_CAST[ED]_BINARY types enum
const (
BinaryMessageTypeState = 0
BinaryMessageTypeChat = 1
BinaryMessageTypeMailNotify = 4
BinaryMessageTypeEmote = 6
)
// MSG_SYS_CAST[ED]_BINARY broadcast types enum
const (
BroadcastTypeTargeted = 0x01
BroadcastTypeStage = 0x03
BroadcastTypeSemaphore = 0x06
BroadcastTypeWorld = 0x0a
)
func sendServerChatMessage(s *Session, message string) {
// Make the inside of the casted binary
bf := byteframe.NewByteFrame()
bf.SetLE()
msgBinChat := &binpacket.MsgBinChat{
Unk0: 0,
Type: 5,
Flags: 0x80,
Message: message,
SenderName: "Erupe",
}
msgBinChat.Build(bf)
castedBin := &mhfpacket.MsgSysCastedBinary{
CharID: s.charID,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}
s.QueueSendMHF(castedBin)
}
func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCastBinary)
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 {
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
_ = tmp.ReadBytes(9)
tmp.SetLE()
frame := tmp.ReadUint32()
sendServerChatMessage(s, fmt.Sprintf("TIME : %d'%d.%03d (%dframe)", frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame))
}
}
// Parse out the real casted binary payload
var msgBinTargeted *binpacket.MsgBinTargeted
var authorLen, msgLen uint16
var msg []byte
isDiceCommand := false
if pkt.MessageType == BinaryMessageTypeChat {
tmp.SetLE()
tmp.Seek(int64(0), 0)
_ = tmp.ReadUint32()
authorLen = tmp.ReadUint16()
msgLen = tmp.ReadUint16()
msg = tmp.ReadNullTerminatedBytes()
}
// Customise payload
realPayload := pkt.RawDataPayload
if pkt.BroadcastType == BroadcastTypeTargeted {
tmp.SetBE()
tmp.Seek(int64(0), 0)
msgBinTargeted = &binpacket.MsgBinTargeted{}
err := msgBinTargeted.Parse(tmp)
if err != nil {
s.logger.Warn("Failed to parse targeted cast binary")
return
}
realPayload = msgBinTargeted.RawDataPayload
} else if pkt.MessageType == BinaryMessageTypeChat {
if msgLen == 6 && string(msg) == "@dice" {
isDiceCommand = true
roll := byteframe.NewByteFrame()
roll.WriteInt16(1) // Unk
roll.SetLE()
roll.WriteUint16(4) // Unk
roll.WriteUint16(authorLen)
rand.Seed(time.Now().UnixNano())
dice := fmt.Sprintf("%d", rand.Intn(100)+1)
roll.WriteUint16(uint16(len(dice) + 1))
roll.WriteNullTerminatedBytes([]byte(dice))
roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes())
realPayload = roll.Data()
}
}
// Make the response to forward to the other client(s).
resp := &mhfpacket.MsgSysCastedBinary{
CharID: s.charID,
BroadcastType: pkt.BroadcastType, // (The client never uses Type0 upon receiving)
MessageType: pkt.MessageType,
RawDataPayload: realPayload,
}
// Send to the proper recipients.
switch pkt.BroadcastType {
case BroadcastTypeWorld:
s.server.WorldcastMHF(resp, s)
case BroadcastTypeStage:
if isDiceCommand {
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
} else {
s.stage.BroadcastMHF(resp, s)
}
case BroadcastTypeSemaphore:
if pkt.MessageType == 1 {
var session *Semaphore
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
session = s.server.semaphore["hs_l0u3B51J9k3"]
} else if _, exists := s.server.semaphore["hs_l0u3B5129k3"]; exists {
session = s.server.semaphore["hs_l0u3B5129k3"]
} else if _, exists := s.server.semaphore["hs_l0u3B512Ak3"]; exists {
session = s.server.semaphore["hs_l0u3B512Ak3"]
}
(*session).BroadcastMHF(resp, s)
} else {
s.Lock()
if s.stage != nil {
s.stage.BroadcastMHF(resp, s)
}
s.Unlock()
}
case BroadcastTypeTargeted:
for _, targetID := range (*msgBinTargeted).TargetCharIDs {
char := s.server.FindSessionByCharID(targetID)
if char != nil {
char.QueueSendMHF(resp)
}
}
default:
s.Lock()
haveStage := s.stage != nil
if haveStage {
s.stage.BroadcastMHF(resp, s)
}
s.Unlock()
}
// Handle chat
if pkt.MessageType == BinaryMessageTypeChat {
bf := byteframe.NewByteFrameFromBytes(realPayload)
// IMPORTANT! Casted binary objects are sent _as they are in memory_,
// this means little endian for LE CPUs, might be different for PS3/PS4/PSP/XBOX.
bf.SetLE()
chatMessage := &binpacket.MsgBinChat{}
chatMessage.Parse(bf)
fmt.Printf("Got chat message: %+v\n", chatMessage)
// Set account rights
if strings.HasPrefix(chatMessage.Message, "!rights") {
var v uint32
n, err := fmt.Sscanf(chatMessage.Message, "!rights %d", &v)
if err != nil || n != 1 {
sendServerChatMessage(s, "Error in command. Format: !rights n")
} else {
_, err = s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
if err == nil {
sendServerChatMessage(s, fmt.Sprintf("Set rights integer: %d", v))
}
}
}
// Discord integration
if chatMessage.Type == binpacket.ChatTypeLocal || chatMessage.Type == binpacket.ChatTypeParty {
s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
}
// RAVI COMMANDS V2
if strings.HasPrefix(chatMessage.Message, "!ravi") {
if checkRaviSemaphore(s) {
s.server.raviente.Lock()
if !strings.HasPrefix(chatMessage.Message, "!ravi ") {
sendServerChatMessage(s, "No Raviente command specified!")
} else {
if strings.HasPrefix(chatMessage.Message, "!ravi start") {
if s.server.raviente.register.startTime == 0 {
s.server.raviente.register.startTime = s.server.raviente.register.postTime
sendServerChatMessage(s, "The Great Slaying will begin in a moment")
s.notifyall()
} else {
sendServerChatMessage(s, "The Great Slaying has already begun!")
}
} else if strings.HasPrefix(chatMessage.Message, "!ravi sm") || strings.HasPrefix(chatMessage.Message, "!ravi setmultiplier") {
var num uint16
n, numerr := fmt.Sscanf(chatMessage.Message, "!ravi sm %d", &num)
if numerr != nil || n != 1 {
sendServerChatMessage(s, "Error in command. Format: !ravi sm n")
} else if s.server.raviente.state.damageMultiplier == 1 {
if num > 65535 {
sendServerChatMessage(s, "Raviente multiplier too high, defaulting to 20x")
s.server.raviente.state.damageMultiplier = 65535
} else {
sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier set to %dx", num))
s.server.raviente.state.damageMultiplier = uint32(num)
}
} else {
sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier is already set to %dx!", s.server.raviente.state.damageMultiplier))
}
} else if strings.HasPrefix(chatMessage.Message, "!ravi cm") || strings.HasPrefix(chatMessage.Message, "!ravi checkmultiplier") {
sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier is currently %dx", s.server.raviente.state.damageMultiplier))
} else if strings.HasPrefix(chatMessage.Message, "!ravi sr") || strings.HasPrefix(chatMessage.Message, "!ravi sendres") {
if s.server.raviente.state.stateData[28] > 0 {
sendServerChatMessage(s, "Sending resurrection support!")
s.server.raviente.state.stateData[28] = 0
} else {
sendServerChatMessage(s, "Resurrection support has not been requested!")
}
} else if strings.HasPrefix(chatMessage.Message, "!ravi ss") || strings.HasPrefix(chatMessage.Message, "!ravi sendsed") {
sendServerChatMessage(s, "Sending sedation support if requested!")
// Total BerRavi HP
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
s.server.raviente.support.supportData[1] = HP
} else if strings.HasPrefix(chatMessage.Message, "!ravi rs") || strings.HasPrefix(chatMessage.Message, "!ravi reqsed") {
sendServerChatMessage(s, "Requesting sedation support!")
// Total BerRavi HP
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
s.server.raviente.support.supportData[1] = HP + 12
} else {
sendServerChatMessage(s, "Raviente command not recognised!")
}
}
} else {
sendServerChatMessage(s, "No one has joined the Great Slaying!")
}
s.server.raviente.Unlock()
}
// END RAVI COMMANDS V2
if strings.HasPrefix(chatMessage.Message, "!tele ") {
var x, y int16
n, err := fmt.Sscanf(chatMessage.Message, "!tele %d %d", &x, &y)
if err != nil || n != 2 {
sendServerChatMessage(s, "Invalid command. Usage:\"!tele 500 500\"")
} else {
sendServerChatMessage(s, fmt.Sprintf("Teleporting to %d %d", x, y))
// Make the inside of the casted binary
payload := byteframe.NewByteFrame()
payload.SetLE()
payload.WriteUint8(2) // SetState type(position == 2)
payload.WriteInt16(x) // X
payload.WriteInt16(y) // Y
payloadBytes := payload.Data()
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{
CharID: s.charID,
MessageType: BinaryMessageTypeState,
RawDataPayload: payloadBytes,
})
}
}
}
}
func handleMsgSysCastedBinary(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,132 @@
package channelserver
import (
"database/sql"
"encoding/binary"
"erupe-ce/network/mhfpacket"
"erupe-ce/server/channelserver/compression/nullcomp"
"go.uber.org/zap"
)
const (
CharacterSaveRPPointer = 0x22D16
)
type CharacterSaveData struct {
CharID uint32
Name string
RP uint16
IsNewCharacter bool
// Use provided setter/getter
baseSaveData []byte
}
func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error) {
result, err := s.server.db.Query("SELECT id, savedata, is_new_character, name FROM characters WHERE id = $1", charID)
if err != nil {
s.logger.Error("failed to retrieve save data for character", zap.Error(err), zap.Uint32("charID", charID))
return nil, err
}
defer result.Close()
saveData := &CharacterSaveData{}
var compressedBaseSave []byte
if !result.Next() {
s.logger.Error("no results found for character save data", zap.Uint32("charID", charID))
return nil, err
}
err = result.Scan(&saveData.CharID, &compressedBaseSave, &saveData.IsNewCharacter, &saveData.Name)
if err != nil {
s.logger.Error(
"failed to retrieve save data for character",
zap.Error(err),
zap.Uint32("charID", charID),
)
return nil, err
}
if compressedBaseSave == nil {
return saveData, nil
}
decompressedBaseSave, err := nullcomp.Decompress(compressedBaseSave)
if err != nil {
s.logger.Error("Failed to decompress savedata from db", zap.Error(err))
return nil, err
}
saveData.SetBaseSaveData(decompressedBaseSave)
return saveData, nil
}
func (save *CharacterSaveData) Save(s *Session, transaction *sql.Tx) error {
// We need to update the save data byte array before we save it back to the DB
save.updateSaveDataWithStruct()
compressedData, err := save.CompressedBaseData(s)
if err != nil {
return err
}
updateSQL := "UPDATE characters SET savedata=$1, is_new_character=$3 WHERE id=$2"
if transaction != nil {
_, err = transaction.Exec(updateSQL, compressedData, save.CharID, save.IsNewCharacter)
} else {
_, err = s.server.db.Exec(updateSQL, compressedData, save.CharID, save.IsNewCharacter)
}
if err != nil {
s.logger.Error("failed to save character data", zap.Error(err), zap.Uint32("charID", save.CharID))
return err
}
return nil
}
func (save *CharacterSaveData) CompressedBaseData(s *Session) ([]byte, error) {
compressedData, err := nullcomp.Compress(save.baseSaveData)
if err != nil {
s.logger.Error("failed to compress saveData", zap.Error(err), zap.Uint32("charID", save.CharID))
return nil, err
}
return compressedData, nil
}
func (save *CharacterSaveData) BaseSaveData() []byte {
return save.baseSaveData
}
func (save *CharacterSaveData) SetBaseSaveData(data []byte) {
save.baseSaveData = data
// After setting the new save byte array, we can extract the values to update our struct
// This will be useful when we save it back, we use the struct values to overwrite the saveData
save.updateStructWithSaveData()
}
// This will update the save struct with the values stored in the raw savedata arrays
func (save *CharacterSaveData) updateSaveDataWithStruct() {
rpBytes := make([]byte, 2)
binary.LittleEndian.PutUint16(rpBytes, save.RP)
copy(save.baseSaveData[CharacterSaveRPPointer:CharacterSaveRPPointer+2], rpBytes)
}
// This will update the character save struct with the values stored in the raw savedata arrays
func (save *CharacterSaveData) updateStructWithSaveData() {
save.RP = binary.LittleEndian.Uint16(save.baseSaveData[CharacterSaveRPPointer : CharacterSaveRPPointer+2])
}
func handleMsgMhfSexChanger(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSexChanger)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}

View File

@@ -0,0 +1,110 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysEnumerateClient)
s.server.stagesLock.RLock()
stage, ok := s.server.stages[pkt.StageID]
if !ok {
s.server.stagesLock.RUnlock()
s.logger.Warn("Can't enumerate clients for stage that doesn't exist!", zap.String("stageID", pkt.StageID))
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
s.server.stagesLock.RUnlock()
// Read-lock the stage and make the response with all of the charID's in the stage.
resp := byteframe.NewByteFrame()
stage.RLock()
var clients []uint32
switch pkt.Unk1 {
case 1: // Not ready
for cid, ready := range stage.reservedClientSlots {
if !ready {
clients = append(clients, cid)
}
}
case 2: // Ready
for cid, ready := range stage.reservedClientSlots {
if ready {
clients = append(clients, cid)
}
}
}
resp.WriteUint16(uint16(len(clients)))
for _, cid := range clients {
resp.WriteUint32(cid)
}
stage.RUnlock()
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
s.logger.Debug("MsgSysEnumerateClient Done!")
}
func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfListMember)
var csv string
var count uint32
resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // Blacklist count
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err != nil {
panic(err)
}
cids := stringsupport.CSVElems(csv)
for _, cid := range cids {
var name string
err = s.server.db.QueryRow("SELECT name FROM characters WHERE id=$1", cid).Scan(&name)
if err != nil {
continue
}
count++
resp.WriteUint32(uint32(cid))
resp.WriteUint32(16)
resp.WriteBytes(stringsupport.PaddedString(name, 16, true))
}
resp.Seek(0, 0)
resp.WriteUint32(count)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfOprMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOprMember)
var csv string
if pkt.Blacklist {
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err != nil {
panic(err)
}
if pkt.Operation {
csv = stringsupport.CSVRemove(csv, int(pkt.CharID))
} else {
csv = stringsupport.CSVAdd(csv, int(pkt.CharID))
}
s.server.db.Exec("UPDATE characters SET blocked=$1 WHERE id=$2", csv, s.charID)
} else { // Friendlist
err := s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err != nil {
panic(err)
}
if pkt.Operation {
csv = stringsupport.CSVRemove(csv, int(pkt.CharID))
} else {
csv = stringsupport.CSVAdd(csv, int(pkt.CharID))
}
s.server.db.Exec("UPDATE characters SET friends=$1 WHERE id=$2", csv, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfShutClient(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysHideClient(s *Session, p mhfpacket.MHFPacket) {}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,345 @@
package channelserver
import (
"fmt"
"sort"
"strings"
"github.com/bwmarrin/discordgo"
)
type Character struct {
ID uint32 `db:"id"`
IsFemale bool `db:"is_female"`
IsNewCharacter bool `db:"is_new_character"`
Name string `db:"name"`
UnkDescString string `db:"unk_desc_string"`
HRP uint16 `db:"hrp"`
GR uint16 `db:"gr"`
WeaponType uint16 `db:"weapon_type"`
LastLogin uint32 `db:"last_login"`
}
var weapons = []string{
"<:gs:970861408227049477>",
"<:hbg:970861408281563206>",
"<:hm:970861408239628308>",
"<:lc:970861408298352660>",
"<:sns:970861408319315988>",
"<:lbg:970861408327725137>",
"<:ds:970861408277368883>",
"<:ls:970861408319311872>",
"<:hh:970861408222863400>",
"<:gl:970861408327720980>",
"<:bw:970861408294174780>",
"<:tf:970861408424177744>",
"<:sw:970861408340283472>",
"<:ms:970861408411594842>",
}
func (s *Server) getCharacterForUser(uid int) (*Character, error) {
character := Character{}
err := s.db.Get(&character, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE id = $1", uid)
if err != nil {
return nil, err
}
return &character, nil
}
func CountChars(s *Server) string {
count := 0
for _, stage := range s.stages {
count += len(stage.clients)
}
message := fmt.Sprintf("Server [%s]: %d players;", s.name, count)
return message
}
type ListPlayer struct {
CharName string
InQuest bool
WeaponEmoji string
QuestEmoji string
StageName string
}
func (p *ListPlayer) toString(length int) string {
status := ""
if p.InQuest {
status = "(in quest " + p.QuestEmoji + ")"
} else {
status = p.StageName
}
missingSpace := length - len(p.CharName)
whitespaces := strings.Repeat(" ", missingSpace+5)
return fmt.Sprintf("%s %s %s %s", p.WeaponEmoji, p.CharName, whitespaces, status)
}
func getPlayerList(s *Server) ([]ListPlayer, int) {
list := []ListPlayer{}
questEmojis := []string{
":white_circle:",
":red_circle:",
":blue_circle:",
":brown_circle:",
":green_circle:",
":purple_circle:",
":yellow_circle:",
":orange_circle:",
}
bigNameLen := 0
for _, stage := range s.stages {
if len(stage.clients) == 0 {
continue
}
questEmoji := ":black_circle:"
if len(questEmojis) > 0 {
questEmoji = questEmojis[len(questEmojis)-1]
questEmojis = questEmojis[:len(questEmojis)-1]
}
isQuest := stage.isQuest()
for client := range stage.clients {
char, err := s.getCharacterForUser(int(client.charID))
if err == nil {
if len(char.Name) > bigNameLen {
bigNameLen = len(char.Name)
}
list = append(list, ListPlayer{
CharName: char.Name,
InQuest: isQuest,
QuestEmoji: questEmoji,
WeaponEmoji: weapons[char.WeaponType],
StageName: stage.GetName(),
})
}
}
}
return list, bigNameLen
}
func PlayerList(s *Server) string {
list := ""
count := 0
listPlayers, bigNameLen := getPlayerList(s)
sort.SliceStable(listPlayers, func(i, j int) bool {
return listPlayers[i].CharName < listPlayers[j].CharName
})
for _, lp := range listPlayers {
list += lp.toString(bigNameLen) + "\n"
count++
}
message := fmt.Sprintf("<:SnS:822963937360347148> Frontier Hunters Online: [%s ] <:switcha:822963906401533992> \n============== Total %d =============\n", s.name, count)
message += list
return message
}
func debug(s *Server) string {
list := ""
for _, stage := range s.stages {
if !stage.isQuest() && len(stage.objects) == 0 {
continue
}
list += fmt.Sprintf(" -> Stage: %s StageId: %s\n", stage.GetName(), stage.id)
isQuest := "false"
hasDeparted := "false"
if stage.isQuest() {
isQuest = "true"
}
list += fmt.Sprintf(" '-> isQuest: %s\n", isQuest)
if stage.isQuest() {
if len(stage.clients) > 0 {
hasDeparted = "true"
}
list += fmt.Sprintf(" '-> isDeparted: %s\n", hasDeparted)
list += fmt.Sprintf(" '-> reserveSlots (%d/%d)\n", len(stage.reservedClientSlots), stage.maxPlayers)
for charid, _ := range stage.reservedClientSlots {
char, err := s.getCharacterForUser(int(charid))
if err == nil {
list += fmt.Sprintf(" '-> %s\n", char.Name)
}
}
}
list += " '-> objects: \n"
for _, obj := range stage.objects {
objInfo := fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z)
list += fmt.Sprintf(" '-> ObjectId: %d - %s\n", obj.id, objInfo)
}
}
message := fmt.Sprintf("Objects in Server: [%s ]\n", s.name)
message += list
return message
}
func questlist(s *Server) string {
list := ""
for _, stage := range s.stages {
if !stage.isQuest() {
continue
}
hasDeparted := ""
if len(stage.clients) > 0 {
hasDeparted = " - departed"
}
list += fmt.Sprintf(" '-> StageId: %s (%d/%d) %s - %s\n", stage.id, len(stage.reservedClientSlots), stage.maxPlayers, hasDeparted, stage.createdAt)
for charid, _ := range stage.reservedClientSlots {
char, err := s.getCharacterForUser(int(charid))
if err == nil {
list += fmt.Sprintf(" '-> %s\n", char.Name)
}
}
}
message := fmt.Sprintf("Quests in Server: [%s ]\n", s.name)
message += list
return message
}
func removeStageById(s *Server, stageId string) string {
if s.stages[stageId] != nil {
delete(s.stages, stageId)
return "Stage deleted!"
}
return "Stage not found!"
}
func cleanStr(str string) string {
return strings.ToLower(strings.Trim(str, " "))
}
func getCharInfo(server *Server, charName string) string {
var s *Stage
var c *Session
for _, stage := range server.stages {
for client := range stage.clients {
if client.Name == "" {
continue
}
if cleanStr(client.Name) == cleanStr(charName) {
s = stage
c = client
}
}
}
if s == nil {
return "Character not found"
}
objInfo := ""
obj := server.FindObjectByChar(c.charID)
// server.logger.Info("Found object: %+v", zap.Object("obj", obj))
if obj != nil {
objInfo = fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z)
}
return fmt.Sprintf("Character: %s\nStage: %s\nStageId: %s\n%s", c.Name, s.GetName(), s.id, objInfo)
}
func (s *Server) isDiscordAdmin(ds *discordgo.Session, m *discordgo.MessageCreate) bool {
for _, role := range m.Member.Roles {
for _, id := range s.erupeConfig.Discord.DevRoles {
if id == role {
return true
}
}
}
return false
}
// onDiscordMessage handles receiving messages from discord and forwarding them ingame.
func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) {
// Ignore messages from our bot, or ones that are not in the correct channel.
if m.Author.ID == ds.State.User.ID || !s.enable {
return
}
// Ignore other channels in devMode
if s.erupeConfig.Discord.DevMode && m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
return
}
args := strings.Split(m.Content, " ")
commandName := args[0]
// Move to slash commadns
if commandName == "!players" {
ds.ChannelMessageSend(m.ChannelID, PlayerList(s))
return
}
if commandName == "-char" {
if len(args) < 2 {
ds.ChannelMessageSend(m.ChannelID, "Usage: !char <char name>")
return
}
charName := strings.Join(args[1:], " ")
ds.ChannelMessageSend(m.ChannelID, getCharInfo(s, charName))
return
}
if commandName == "!debug" && s.isDiscordAdmin(ds, m) {
ds.ChannelMessageSend(m.ChannelID, debug(s))
return
}
if commandName == "!questlist" && s.isDiscordAdmin(ds, m) {
ds.ChannelMessageSend(m.ChannelID, questlist(s))
return
}
if commandName == "!remove-stage" && s.isDiscordAdmin(ds, m) {
if len(args) < 2 {
ds.ChannelMessageSend(m.ChannelID, "Usage: !remove-stage <stage id>")
return
}
stageId := strings.Join(args[1:], " ")
ds.ChannelMessageSend(m.ChannelID, removeStageById(s, stageId))
return
}
if m.ChannelID == s.erupeConfig.Discord.RealtimeChannelID {
message := fmt.Sprintf("[DISCORD] %s: %s", m.Author.Username, m.Content)
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
}
}

View File

@@ -0,0 +1,130 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
type ItemDist struct {
ID uint32 `db:"id"`
Deadline uint32 `db:"deadline"`
TimesAcceptable uint16 `db:"times_acceptable"`
TimesAccepted uint16 `db:"times_accepted"`
MinHR uint16 `db:"min_hr"`
MaxHR uint16 `db:"max_hr"`
MinSR uint16 `db:"min_sr"`
MaxSR uint16 `db:"max_sr"`
MinGR uint16 `db:"min_gr"`
MaxGR uint16 `db:"max_gr"`
EventName string `db:"event_name"`
Description string `db:"description"`
Data []byte `db:"data"`
}
func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateDistItem)
bf := byteframe.NewByteFrame()
distCount := 0
dists, err := s.server.db.Queryx(`
SELECT d.id, event_name, description, times_acceptable,
min_hr, max_hr, min_sr, max_sr, min_gr, max_gr,
(
SELECT count(*)
FROM distributions_accepted da
WHERE d.id = da.distribution_id
AND da.character_id = $1
) AS times_accepted,
CASE
WHEN (EXTRACT(epoch FROM deadline)::int) IS NULL THEN 0
ELSE (EXTRACT(epoch FROM deadline)::int)
END deadline
FROM distribution d
WHERE character_id = $1 AND type = $2 OR character_id IS NULL AND type = $2 ORDER BY id DESC;
`, s.charID, pkt.Unk0)
if err != nil {
s.logger.Error("Error getting distribution data from db", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
for dists.Next() {
distCount++
distData := &ItemDist{}
err = dists.StructScan(&distData)
if err != nil {
s.logger.Error("Error parsing item distribution data", zap.Error(err))
}
bf.WriteUint32(distData.ID)
bf.WriteUint32(distData.Deadline)
bf.WriteUint32(0) // Unk
bf.WriteUint16(distData.TimesAcceptable)
bf.WriteUint16(distData.TimesAccepted)
bf.WriteUint16(0) // Unk
bf.WriteUint16(distData.MinHR)
bf.WriteUint16(distData.MaxHR)
bf.WriteUint16(distData.MinSR)
bf.WriteUint16(distData.MaxSR)
bf.WriteUint16(distData.MinGR)
bf.WriteUint16(distData.MaxGR)
bf.WriteUint32(0) // Unk
bf.WriteUint32(0) // Unk
ps.Uint16(bf, distData.EventName, true)
bf.WriteBytes(make([]byte, 391))
}
resp := byteframe.NewByteFrame()
resp.WriteUint16(uint16(distCount))
resp.WriteBytes(bf.Data())
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
}
func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyDistItem)
if pkt.DistributionID == 0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
} else {
row := s.server.db.QueryRowx("SELECT data FROM distribution WHERE id = $1", pkt.DistributionID)
dist := &ItemDist{}
err := row.StructScan(dist)
if err != nil {
s.logger.Error("Error parsing item distribution data", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
return
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(pkt.DistributionID)
bf.WriteBytes(dist.Data)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
_, err = s.server.db.Exec(`
INSERT INTO public.distributions_accepted
VALUES ($1, $2)
`, pkt.DistributionID, s.charID)
if err != nil {
s.logger.Error("Error updating accepted dist count", zap.Error(err))
}
}
}
func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireDistItem)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetDistDescription(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetDistDescription)
var desc string
err := s.server.db.QueryRow("SELECT description FROM distribution WHERE id = $1", pkt.DistributionID).Scan(&desc)
if err != nil {
s.logger.Error("Error parsing item distribution description", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf := byteframe.NewByteFrame()
ps.Uint16(bf, desc, true)
ps.Uint16(bf, "", false)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -0,0 +1,209 @@
package channelserver
import (
"encoding/hex"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfGetKijuInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetKijuInfo)
// Temporary canned response
data, _ := hex.DecodeString("04965C959782CC8B468EEC00000000000000000000000000000000000000000000815C82A082E782B582DC82A982BA82CC82AB82B682E3815C0A965C959782C682CD96D282E98E7682A281420A95B782AD8ED282C997458B4382F0975E82A682E98142000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001018BAD8C8282CC8B468EEC00000000000000000000000000000000000000000000815C82AB82E582A482B082AB82CC82AB82B682E3815C0A8BAD8C8282C682CD8BAD82A290BA904681420A95B782AD8ED282CC97CD82F08CA482AC909F82DC82B78142200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003138C8B8F5782CC8B468EEC00000000000000000000000000000000000000000000815C82AF82C182B582E382A482CC82AB82B682E3815C0A8C8B8F5782C682CD8A6D8CC582BD82E9904D978A81420A8F5782DF82E982D982C782C98EEB906C82BD82BF82CC90B8905F97CD82C682C882E9814200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041189CC8CEC82CC8B468EEC00000000000000000000000000000000000000000000815C82A482BD82DC82E082E882CC82AB82B682E3815C0A89CC8CEC82C682CD89CC955082CC8CEC82E881420A8F5782DF82E982D982C782C98EEB906C82BD82BF82CC8E7882A682C682C882E9814220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000212")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfSetKiju(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetKiju)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfAddUdPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAddUdPoint)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetUdMyPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdMyPoint)
// Temporary canned response
data, _ := hex.DecodeString("00040000013C000000FA000000000000000000040000007E0000003C02000000000000000000000000000000000000000000000000000002000004CC00000438000000000000000000000000000000000000000000000000000000020000026E00000230000000000000000000020000007D0000007D000000000000000000000000000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfGetUdTotalPointInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdTotalPointInfo)
// Temporary canned response
data, _ := hex.DecodeString("00000000000007A12000000000000F424000000000001E848000000000002DC6C000000000003D090000000000004C4B4000000000005B8D8000000000006ACFC000000000007A1200000000000089544000000000009896800000000000E4E1C00000000001312D0000000000017D78400000000001C9C3800000000002160EC00000000002625A000000000002AEA5400000000002FAF0800000000003473BC0000000000393870000000000042C1D800000000004C4B40000000000055D4A800000000005F5E10000000000008954400000000001C9C3800000000003473BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001020300000000000000000000000000000000000000000000000000000000000000000000000000000000101F1420")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfGetUdSelectedColorInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdSelectedColorInfo)
// Unk
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x01, 0x01, 0x01, 0x02, 0x03, 0x02, 0x00, 0x00})
}
func handleMsgMhfGetUdMonsterPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdMonsterPoint)
monsterPoints := []struct {
MID uint8
Points uint16
}{
{MID: 0x01, Points: 0x3C}, // em1 Rathian
{MID: 0x02, Points: 0x5A}, // em2 Fatalis
{MID: 0x06, Points: 0x14}, // em6 Yian Kut-Ku
{MID: 0x07, Points: 0x50}, // em7 Lao-Shan Lung
{MID: 0x08, Points: 0x28}, // em8 Cephadrome
{MID: 0x0B, Points: 0x3C}, // em11 Rathalos
{MID: 0x0E, Points: 0x3C}, // em14 Diablos
{MID: 0x0F, Points: 0x46}, // em15 Khezu
{MID: 0x11, Points: 0x46}, // em17 Gravios
{MID: 0x14, Points: 0x28}, // em20 Gypceros
{MID: 0x15, Points: 0x3C}, // em21 Plesioth
{MID: 0x16, Points: 0x32}, // em22 Basarios
{MID: 0x1A, Points: 0x32}, // em26 Monoblos
{MID: 0x1B, Points: 0x0A}, // em27 Velocidrome
{MID: 0x1C, Points: 0x0A}, // em28 Gendrome
{MID: 0x1F, Points: 0x0A}, // em31 Iodrome
{MID: 0x21, Points: 0x50}, // em33 Kirin
{MID: 0x24, Points: 0x64}, // em36 Crimson Fatalis
{MID: 0x25, Points: 0x3C}, // em37 Pink Rathian
{MID: 0x26, Points: 0x1E}, // em38 Blue Yian Kut-Ku
{MID: 0x27, Points: 0x28}, // em39 Purple Gypceros
{MID: 0x28, Points: 0x50}, // em40 Yian Garuga
{MID: 0x29, Points: 0x5A}, // em41 Silver Rathalos
{MID: 0x2A, Points: 0x50}, // em42 Gold Rathian
{MID: 0x2B, Points: 0x3C}, // em43 Black Diablos
{MID: 0x2C, Points: 0x3C}, // em44 White Monoblos
{MID: 0x2D, Points: 0x46}, // em45 Red Khezu
{MID: 0x2E, Points: 0x3C}, // em46 Green Plesioth
{MID: 0x2F, Points: 0x50}, // em47 Black Gravios
{MID: 0x30, Points: 0x1E}, // em48 Daimyo Hermitaur
{MID: 0x31, Points: 0x3C}, // em49 Azure Rathalos
{MID: 0x32, Points: 0x50}, // em50 Ashen Lao-Shan Lung
{MID: 0x33, Points: 0x3C}, // em51 Blangonga
{MID: 0x34, Points: 0x28}, // em52 Congalala
{MID: 0x35, Points: 0x50}, // em53 Rajang
{MID: 0x36, Points: 0x6E}, // em54 Kushala Daora
{MID: 0x37, Points: 0x50}, // em55 Shen Gaoren
{MID: 0x3A, Points: 0x50}, // em58 Yama Tsukami
{MID: 0x3B, Points: 0x6E}, // em59 Chameleos
{MID: 0x40, Points: 0x64}, // em64 Lunastra
{MID: 0x41, Points: 0x6E}, // em65 Teostra
{MID: 0x43, Points: 0x28}, // em67 Shogun Ceanataur
{MID: 0x44, Points: 0x0A}, // em68 Bulldrome
{MID: 0x47, Points: 0x6E}, // em71 White Fatalis
{MID: 0x4A, Points: 0xFA}, // em74 Hypnocatrice
{MID: 0x4B, Points: 0xFA}, // em75 Lavasioth
{MID: 0x4C, Points: 0x46}, // em76 Tigrex
{MID: 0x4D, Points: 0x64}, // em77 Akantor
{MID: 0x4E, Points: 0xFA}, // em78 Bright Hypnoc
{MID: 0x4F, Points: 0xFA}, // em79 Lavasioth Subspecies
{MID: 0x50, Points: 0xFA}, // em80 Espinas
{MID: 0x51, Points: 0xFA}, // em81 Orange Espinas
{MID: 0x52, Points: 0xFA}, // em82 White Hypnoc
{MID: 0x53, Points: 0xFA}, // em83 Akura Vashimu
{MID: 0x54, Points: 0xFA}, // em84 Akura Jebia
{MID: 0x55, Points: 0xFA}, // em85 Berukyurosu
{MID: 0x59, Points: 0xFA}, // em89 Pariapuria
{MID: 0x5A, Points: 0xFA}, // em90 White Espinas
{MID: 0x5B, Points: 0xFA}, // em91 Kamu Orugaron
{MID: 0x5C, Points: 0xFA}, // em92 Nono Orugaron
{MID: 0x5E, Points: 0xFA}, // em94 Dyuragaua
{MID: 0x5F, Points: 0xFA}, // em95 Doragyurosu
{MID: 0x60, Points: 0xFA}, // em96 Gurenzeburu
{MID: 0x63, Points: 0xFA}, // em99 Rukodiora
{MID: 0x65, Points: 0xFA}, // em101 Gogomoa
{MID: 0x67, Points: 0xFA}, // em103 Taikun Zamuza
{MID: 0x68, Points: 0xFA}, // em104 Abiorugu
{MID: 0x69, Points: 0xFA}, // em105 Kuarusepusu
{MID: 0x6A, Points: 0xFA}, // em106 Odibatorasu
{MID: 0x6B, Points: 0xFA}, // em107 Disufiroa
{MID: 0x6C, Points: 0xFA}, // em108 Rebidiora
{MID: 0x6D, Points: 0xFA}, // em109 Anorupatisu
{MID: 0x6E, Points: 0xFA}, // em110 Hyujikiki
{MID: 0x6F, Points: 0xFA}, // em111 Midogaron
{MID: 0x70, Points: 0xFA}, // em112 Giaorugu
{MID: 0x72, Points: 0xFA}, // em114 Farunokku
{MID: 0x73, Points: 0xFA}, // em115 Pokaradon
{MID: 0x74, Points: 0xFA}, // em116 Shantien
{MID: 0x77, Points: 0xFA}, // em119 Goruganosu
{MID: 0x78, Points: 0xFA}, // em120 Aruganosu
{MID: 0x79, Points: 0xFA}, // em121 Baruragaru
{MID: 0x7A, Points: 0xFA}, // em122 Zerureusu
{MID: 0x7B, Points: 0xFA}, // em123 Gougarf
{MID: 0x7D, Points: 0xFA}, // em125 Forokururu
{MID: 0x7E, Points: 0xFA}, // em126 Meraginasu
{MID: 0x7F, Points: 0xFA}, // em127 Diorekkusu
{MID: 0x80, Points: 0xFA}, // em128 Garuba Daora
{MID: 0x81, Points: 0xFA}, // em129 Inagami
{MID: 0x82, Points: 0xFA}, // em130 Varusaburosu
{MID: 0x83, Points: 0xFA}, // em131 Poborubarumu
{MID: 0x8B, Points: 0xFA}, // em139 Gureadomosu
{MID: 0x8C, Points: 0xFA}, // em140 Harudomerugu
{MID: 0x8D, Points: 0xFA}, // em141 Toridcless
{MID: 0x8E, Points: 0xFA}, // em142 Gasurabazura
{MID: 0x90, Points: 0xFA}, // em144 Yama Kurai
{MID: 0x92, Points: 0x78}, // em146 Zinogre
{MID: 0x93, Points: 0x78}, // em147 Deviljho
{MID: 0x94, Points: 0x78}, // em148 Brachydios
{MID: 0x96, Points: 0xFA}, // em150 Toa Tesukatora
{MID: 0x97, Points: 0x78}, // em151 Barioth
{MID: 0x98, Points: 0x78}, // em152 Uragaan
{MID: 0x99, Points: 0x78}, // em153 Stygian Zinogre
{MID: 0x9A, Points: 0xFA}, // em154 Guanzorumu
{MID: 0x9E, Points: 0xFA}, // em158 Voljang
{MID: 0x9F, Points: 0x78}, // em159 Nargacuga
{MID: 0xA0, Points: 0xFA}, // em160 Keoaruboru
{MID: 0xA1, Points: 0xFA}, // em161 Zenaserisu
{MID: 0xA2, Points: 0x78}, // em162 Gore Magala
{MID: 0xA4, Points: 0x78}, // em164 Shagaru Magala
{MID: 0xA5, Points: 0x78}, // em165 Amatsu
{MID: 0xA6, Points: 0xFA}, // em166 Elzelion
{MID: 0xA9, Points: 0x78}, // em169 Seregios
{MID: 0xAA, Points: 0xFA}, // em170 Bogabadorumu
}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(monsterPoints)))
for _, mp := range monsterPoints {
resp.WriteUint8(mp.MID)
resp.WriteUint16(mp.Points)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetUdDailyPresentList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdDailyPresentList)
// Temporary canned response
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfGetUdNormaPresentList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdNormaPresentList)
// Temporary canned response
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfAcquireUdItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireUdItem)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetUdRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdRanking)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetUdMyRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdMyRanking)
// Temporary canned response
data, _ := hex.DecodeString("00000515000005150000CEB4000003CE000003CE0000CEB44D49444E494748542D414E47454C0000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}

View File

@@ -0,0 +1,339 @@
package channelserver
import (
"math"
"math/rand"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
timeServerFix "erupe-ce/server/channelserver/timeserver"
)
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegisterEvent)
bf := byteframe.NewByteFrame()
bf.WriteUint8(pkt.Unk2)
bf.WriteUint8(pkt.Unk4)
bf.WriteUint16(0x1142)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReleaseEvent)
// Do this ack manually because it uses a non-(0|1) error code
/*
_ACK_SUCCESS = 0
_ACK_ERROR = 1
_ACK_EINPROGRESS = 16
_ACK_ENOENT = 17
_ACK_ENOSPC = 18
_ACK_ETIMEOUT = 19
_ACK_EINVALID = 64
_ACK_EFAILED = 65
_ACK_ENOMEM = 66
_ACK_ENOTEXIT = 67
_ACK_ENOTREADY = 68
_ACK_EALREADY = 69
_ACK_DISABLE_WORK = 71
*/
s.QueueSendMHF(&mhfpacket.MsgSysAck{
AckHandle: pkt.AckHandle,
IsBufferResponse: false,
ErrorCode: 0x41,
AckData: []byte{0x00, 0x00, 0x00, 0x00},
})
}
func handleMsgMhfEnumerateEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateEvent)
stubEnumerateNoResults(s, pkt.AckHandle)
}
type activeFeature struct {
StartTime time.Time
ActiveFeatures uint32
Unk1 uint16
}
func handleMsgMhfGetWeeklySchedule(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetWeeklySchedule)
persistentEventSchedule := make([]activeFeature, 8) // generate day after weekly restart
for x := -1; x < 7; x++ {
feat := generateActiveWeapons(14) // number of active weapons
// TODO: only generate this once per restart (server should be restarted weekly)
// then load data from db instead of regenerating
persistentEventSchedule[x+1] = activeFeature{
StartTime: Time_Current_Midnight().Add(time.Duration(24*x) * time.Hour),
ActiveFeatures: uint32(feat),
Unk1: 0,
}
}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(persistentEventSchedule))) // Entry count, client only parses the first 7 or 8.
resp.WriteUint32(uint32(Time_Current_Adjusted().Add(-5 * time.Minute).Unix())) // 5 minutes ago server time
for _, es := range persistentEventSchedule {
resp.WriteUint32(uint32(es.StartTime.Unix()))
resp.WriteUint32(es.ActiveFeatures)
resp.WriteUint16(es.Unk1)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func generateActiveWeapons(count int) int {
nums := make([]int, 0)
var result int
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for len(nums) < count {
num := r.Intn(14)
exist := false
for _, v := range nums {
if v == num {
exist = true
break
}
}
if !exist {
nums = append(nums, num)
}
}
for _, num := range nums {
result += int(math.Pow(2, float64(num)))
}
return result
}
type loginBoost struct {
WeekReq, WeekCount uint8
Available bool
Expiration uint32
}
func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetKeepLoginBoostStatus)
var loginBoostStatus []loginBoost
insert := false
boostState, err := s.server.db.Query("SELECT week_req, week_count, available, end_time FROM login_boost_state WHERE char_id=$1 ORDER BY week_req ASC", s.charID)
if err != nil {
panic(err)
}
for boostState.Next() {
var boost loginBoost
err = boostState.Scan(&boost.WeekReq, &boost.WeekCount, &boost.Available, &boost.Expiration)
if err != nil {
panic(err)
}
loginBoostStatus = append(loginBoostStatus, boost)
}
if len(loginBoostStatus) == 0 {
// create default Entries (should only been week 1 with )
insert = true
loginBoostStatus = []loginBoost{
{
WeekReq: 1, // weeks needed
WeekCount: 0, // weeks passed
Available: true, // available
Expiration: 0, //uint32(t.Add(120 * time.Minute).Unix()), // uncomment to enable permanently
},
{
WeekReq: 2,
WeekCount: 0,
Available: true,
Expiration: 0,
},
{
WeekReq: 3,
WeekCount: 0,
Available: true,
Expiration: 0,
},
{
WeekReq: 4,
WeekCount: 0,
Available: true,
Expiration: 0,
},
{
WeekReq: 5,
WeekCount: 0,
Available: true,
Expiration: 0,
},
}
}
resp := byteframe.NewByteFrame()
CurrentWeek := Time_Current_Week_uint8()
for d := range loginBoostStatus {
if CurrentWeek == 1 && loginBoostStatus[d].WeekCount <= 5 {
loginBoostStatus[d].WeekCount = 0
}
if loginBoostStatus[d].WeekReq == CurrentWeek || loginBoostStatus[d].WeekCount != 0 {
loginBoostStatus[d].WeekCount = CurrentWeek
}
if !loginBoostStatus[d].Available && loginBoostStatus[d].WeekCount >= loginBoostStatus[d].WeekReq && uint32(time.Now().In(time.FixedZone("UTC+1", 1*60*60)).Unix()) >= loginBoostStatus[d].Expiration {
loginBoostStatus[d].Expiration = 1
}
if !insert {
_, err := s.server.db.Exec(`UPDATE login_boost_state SET week_count=$1, end_time=$2 WHERE char_id=$3 AND week_req=$4`, loginBoostStatus[d].WeekCount, loginBoostStatus[d].Expiration, s.charID, loginBoostStatus[d].WeekReq)
if err != nil {
panic(err)
}
}
}
for _, v := range loginBoostStatus {
if insert {
_, err := s.server.db.Exec(`INSERT INTO login_boost_state (char_id, week_req, week_count, available, end_time) VALUES ($1,$2,$3,$4,$5)`, s.charID, v.WeekReq, v.WeekCount, v.Available, v.Expiration)
if err != nil {
panic(err)
}
}
resp.WriteUint8(v.WeekReq)
resp.WriteUint8(v.WeekCount)
resp.WriteBool(v.Available)
resp.WriteUint32(v.Expiration)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
// Directly interacts with MhfGetKeepLoginBoostStatus
// TODO: make these states persistent on a per character basis
pkt := p.(*mhfpacket.MsgMhfUseKeepLoginBoost)
var t = time.Now().In(time.FixedZone("UTC+1", 1*60*60))
resp := byteframe.NewByteFrame()
resp.WriteUint8(0)
// response is end timestamp based on input
switch pkt.BoostWeekUsed {
case 1:
t = t.Add(120 * time.Minute)
resp.WriteUint32(uint32(t.Unix()))
case 2:
t = t.Add(240 * time.Minute)
resp.WriteUint32(uint32(t.Unix()))
case 3:
t = t.Add(120 * time.Minute)
resp.WriteUint32(uint32(t.Unix()))
case 4:
t = t.Add(180 * time.Minute)
resp.WriteUint32(uint32(t.Unix()))
case 5:
t = t.Add(240 * time.Minute)
resp.WriteUint32(uint32(t.Unix()))
}
_, err := s.server.db.Exec(`UPDATE login_boost_state SET available='false', end_time=$1 WHERE char_id=$2 AND week_req=$3`, uint32(t.Unix()), s.charID, pkt.BoostWeekUsed)
if err != nil {
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
var t = timeServerFix.Tstatic_midnight()
var event int = s.server.erupeConfig.DevModeOptions.DivaEvent
year, month, day := t.Date()
midnight := time.Date(year, month, day, 0, 0, 0, 0, t.Location())
// Events with time limits are Festival with Sign up, Soul Week and Winners Weeks
// Diva Defense with Prayer, Interception and Song weeks
// Mezeporta Festival with simply 'available' being a weekend thing
resp := byteframe.NewByteFrame()
resp.WriteUint32(0x1d5fda5c) // Unk (1d5fda5c, 0b5397df)
if event == 1 {
resp.WriteUint32(uint32(midnight.Add(24 * 21 * time.Hour).Unix())) // Week 1 Timestamp, Festi start?
} else {
resp.WriteUint32(uint32(midnight.Add(-24 * 21 * time.Hour).Unix())) // Week 1 Timestamp, Festi start?
}
if event == 2 {
resp.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
resp.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
} else {
resp.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
resp.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
}
if event == 3 {
resp.WriteUint32(uint32(midnight.Add((24) * 7 * time.Hour).Unix())) // Diva Defense Interception
resp.WriteUint32(uint32(midnight.Add((24) * 14 * time.Hour).Unix())) // Diva Defense Greeting Song
} else {
resp.WriteUint32(uint32(midnight.Add((-24) * 7 * time.Hour).Unix())) // Diva Defense Interception
resp.WriteUint32(uint32(midnight.Add((-24) * 14 * time.Hour).Unix())) // Diva Defense Greeting Song
}
resp.WriteUint16(0x19) // Unk 00011001
resp.WriteUint16(0x2d) // Unk 00101101
resp.WriteUint16(0x02) // Unk 00000010
resp.WriteUint16(0x02) // Unk 00000010
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdInfo)
// Message that appears on the Diva Defense NPC and triggers the green exclamation mark
udInfos := []struct {
Text string
StartTime time.Time
EndTime time.Time
}{
/*{
Text: " ~C17【Erupe】 is dead event!\n\n■Features\n~C18 Dont bother walking around!\n~C17 Take down your DB by doing \n~C17 nearly anything!",
StartTime: Time_static().Add(time.Duration(-5) * time.Minute), // Event started 5 minutes ago,
EndTime: Time_static().Add(time.Duration(24) * time.Hour), // Event ends in 5 minutes,
}, */
}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(udInfos)))
for _, udInfo := range udInfos {
resp.WriteBytes(fixedSizeShiftJIS(udInfo.Text, 1024))
resp.WriteUint32(uint32(udInfo.StartTime.Unix()))
resp.WriteUint32(uint32(udInfo.EndTime.Unix()))
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostTime)
doAckBufSucceed(s, pkt.AckHandle, []byte{})
updateRights(s)
}
func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetRestrictionEvent)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,232 @@
package channelserver
import (
"encoding/hex"
"math/rand"
"time"
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveMezfesData)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadMezfesData)
resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // Unk
resp.WriteUint8(2) // Count of the next 2 uint32s
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0) // Unk
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRanking)
bf := byteframe.NewByteFrame()
state := s.server.erupeConfig.DevModeOptions.TournamentEvent
// Unk
// Unk
// Start?
// End?
midnight := Time_Current_Midnight()
switch state {
case 1:
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(3 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(12 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(21 * 24 * time.Hour).Unix()))
case 2:
bf.WriteUint32(uint32(midnight.Add(-3 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(9 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(16 * 24 * time.Hour).Unix()))
case 3:
bf.WriteUint32(uint32(midnight.Add(-12 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(-9 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(7 * 24 * time.Hour).Unix()))
default:
bf.WriteBytes(make([]byte, 16))
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
bf.WriteUint16(1)
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
d, _ := hex.DecodeString("031491E631353089F18CF68EAE8EEB97C291E589EF00001200000A54001000000000ED130D949A96B697B393A294B081490000000A55001000010000ED130D949A96B697B393A294B081490000000A56001000020000ED130D949A96B697B393A294B081490000000A57001000030000ED130D949A96B697B393A294B081490000000A58001000040000ED130D949A96B697B393A294B081490000000A59001000050000ED130D949A96B697B393A294B081490000000A5A001000060000ED130D949A96B697B393A294B081490000000A5B001000070000ED130D949A96B697B393A294B081490000000A5C001000080000ED130D949A96B697B393A294B081490000000A5D001000090000ED130D949A96B697B393A294B081490000000A5E0010000A0000ED130D949A96B697B393A294B081490000000A5F0010000B0000ED130D949A96B697B393A294B081490000000A600010000C0000ED130D949A96B697B393A294B081490000000A610010000D0000ED130D949A96B697B393A294B081490000000A620011FFFF0000ED121582DD82F182C882C5949A96B697B393A294B081490000000A63000600EA0000000009834C838C834183570000000A64000600ED000000000B836E838A837D834F838D0000000A65000600EF0000000011834A834E8354839383668381834C83930003000002390006000600000E8CC2906C208B9091E58B9B94740001617E43303581798BA38B5A93E09765817A0A7E433030834E83478358836782C592DE82C182BD8B9B82CC83548343835982F08BA382A40A7E433034817991CE8FDB8B9B817A0A7E433030834C838C8341835781410A836E838A837D834F838D8141834A834E8354839383668381834C83930A7E433037817993FC8FDC8FDC9569817A0A7E4330308B9B947482CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C5000000023A0011000700001297C292632082668B89E8E891CA935694740000ED7E43303581798BA38B5A93E09765817A0A7E43303081E182DD82F182C882C5949A96B697B393A294B0814981E282F00A93AF82B697C2926382C98F8A91AE82B782E934906C82DC82C582CC0A97C2926388F582C582A282A982C9918182AD834E838A834182B782E982A90A82F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303091E631343789F18EEB906C8DD582CC8DB02831816032303088CA290A0A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C50A000000023B001000070000128CC2906C2082668B89E8E891CA935694740001497E43303581798BA38B5A93E09765817A0A7E43303081E1949A96B697B393A294B0814981E282F00A82A282A982C9918182AD834E838A834182B782E982A982F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303089A48ED282CC8381835F838B283188CA290A2F8CF68EAE82CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C500")
bf.WriteBytes(d)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoFesta)
bf := byteframe.NewByteFrame()
state := s.server.erupeConfig.DevModeOptions.FestaEvent
bf.WriteUint32(0xdeadbeef) // festaID
// Registration Week Start
// Introductory Week Start
// Totalling Time
// Reward Festival Start (2.5hrs after totalling)
// 2 weeks after RewardFes (next fes?)
midnight := Time_Current_Midnight()
switch state {
case 1:
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(24 * 7 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(24*14*time.Hour + 150*time.Minute).Unix()))
bf.WriteUint32(uint32(midnight.Add(24*28*time.Hour + 11*time.Hour).Unix()))
case 2:
bf.WriteUint32(uint32(midnight.Add(-24 * 7 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(24 * 7 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(24*7*time.Hour + 150*time.Minute).Unix()))
bf.WriteUint32(uint32(midnight.Add(24 * 21 * time.Hour).Add(11 * time.Hour).Unix()))
case 3:
bf.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(-24*7*time.Hour + 11*time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(150 * time.Minute).Unix()))
bf.WriteUint32(uint32(midnight.Add(24*14*time.Hour + 11*time.Hour).Unix()))
default:
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
bf.WriteUint8(4)
ps.Uint8(bf, "", false)
bf.WriteUint32(0)
bf.WriteUint32(0) // Blue souls
bf.WriteUint32(0) // Red souls
trials := 0
bf.WriteUint16(uint16(trials))
for i := 0; i < trials; i++ {
bf.WriteUint32(uint32(i + 1)) // trialID
bf.WriteUint8(0xFF) // unk
bf.WriteUint8(uint8(i)) // objective
bf.WriteUint32(0x1B) // monID, itemID if deliver
bf.WriteUint16(1) // huntsRemain?
bf.WriteUint16(0) // location
bf.WriteUint16(1) // numSoulsReward
bf.WriteUint8(0xFF) // unk
bf.WriteUint8(0xFF) // monopolised
bf.WriteUint16(0) // unk
}
unk := 0 // static rewards?
bf.WriteUint16(uint16(unk))
for i := 0; i < unk; i++ {
bf.WriteUint32(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint32(0)
bf.WriteBool(false)
}
d, _ := hex.DecodeString("0001D4C001F4000411B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10003000109E54BE54E89B38F970029FDCE04000400001381818D84836C8352819993A294B091D1818100000811B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100030001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100040001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100050001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100060001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10007000109E54BE54E89B38F9700000000000008000001000000000100001388000007D0000003E800000064012C00C8009600640032")
bf.WriteBytes(d)
ps.Uint16(bf, "", false)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
// state festa (U)ser
func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaU)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0) // souls
bf.WriteUint32(0) // unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
// state festa (G)uild
func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaG)
resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // souls
resp.WriteUint32(1) // unk
resp.WriteUint32(1) // unk
resp.WriteUint32(1) // unk, rank?
resp.WriteUint32(1) // unk
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaMember)
bf := byteframe.NewByteFrame()
bf.WriteUint16(0) // numMembers
// uint16 unk
// uint32 charID
// uint32 souls
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
bf := byteframe.NewByteFrame()
rand.Seed(time.Now().UnixNano())
bf.WriteUint32(uint32(rand.Intn(2)))
// Update guild table
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfChargeFesta)
// Update festa state table
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFesta)
// Mark festa as claimed
// Update guild table?
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaPersonalPrize)
// Set prize as claimed
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaIntermediatePrize)
// Set prize as claimed
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
// uint32 numPrizes
// struct festaPrize
// uint32 prizeID
// uint32 prizeTier (1/2/3, 3 = GR)
// uint32 soulsReq
// uint32 unk (00 00 00 07)
// uint32 itemID
// uint32 numItem
// bool claimed
func handleMsgMhfEnumerateFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaPersonalPrize)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfEnumerateFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaIntermediatePrize)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,93 @@
package channelserver
import (
"time"
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
type GuildAdventure struct {
ID uint32 `db:"id"`
Destination uint32 `db:"destination"`
Charge uint32 `db:"charge"`
Depart uint32 `db:"depart"`
Return uint32 `db:"return"`
CollectedBy string `db:"collected_by"`
}
func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadGuildAdventure)
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
data, err := s.server.db.Queryx("SELECT id, destination, charge, depart, return, collected_by FROM guild_adventures WHERE guild_id = $1", guild.ID)
if err != nil {
s.logger.Fatal("Failed to get guild adventures from db", zap.Error(err))
}
temp := byteframe.NewByteFrame()
count := 0
for data.Next() {
count++
adventureData := &GuildAdventure{}
err = data.StructScan(&adventureData)
if err != nil {
s.logger.Fatal("Failed to scan adventure data", zap.Error(err))
}
temp.WriteUint32(adventureData.ID)
temp.WriteUint32(adventureData.Destination)
temp.WriteUint32(adventureData.Charge)
temp.WriteUint32(adventureData.Depart)
temp.WriteUint32(adventureData.Return)
temp.WriteBool(stringsupport.CSVContains(adventureData.CollectedBy, int(s.charID)))
}
bf := byteframe.NewByteFrame()
bf.WriteUint8(uint8(count))
bf.WriteBytes(temp.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventure)
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, depart, return) VALUES ($1, $2, $3, $4)", guild.ID, pkt.Destination, Time_Current_Adjusted().Unix(), Time_Current_Adjusted().Add(6*time.Hour).Unix())
if err != nil {
s.logger.Fatal("Failed to register guild adventure", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireGuildAdventure)
var collectedBy string
err := s.server.db.QueryRow("SELECT collected_by FROM guild_adventures WHERE id = $1", pkt.ID).Scan(&collectedBy)
if err != nil {
s.logger.Fatal("Error parsing adventure collected by", zap.Error(err))
} else {
collectedBy = stringsupport.CSVAdd(collectedBy, int(s.charID))
_, err := s.server.db.Exec("UPDATE guild_adventures SET collected_by = $1 WHERE id = $2", collectedBy, pkt.ID)
if err != nil {
s.logger.Fatal("Failed to collect adventure in db", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfChargeGuildAdventure)
_, err := s.server.db.Exec("UPDATE guild_adventures SET charge = charge + $1 WHERE id = $2", pkt.Amount, pkt.ID)
if err != nil {
s.logger.Fatal("Failed to charge guild adventure", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfRegistGuildAdventureDiva(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventureDiva)
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, charge, depart, return) VALUES ($1, $2, $3, $4, $5)", guild.ID, pkt.Destination, pkt.Charge, Time_Current_Adjusted().Unix(), Time_Current_Adjusted().Add(1*time.Hour).Unix())
if err != nil {
s.logger.Fatal("Failed to register guild adventure", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,157 @@
package channelserver
import (
"fmt"
"time"
"erupe-ce/network/mhfpacket"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
const allianceInfoSelectQuery = `
SELECT
ga.id,
ga.name,
created_at,
parent_id,
CASE
WHEN sub1_id IS NULL THEN 0
ELSE sub1_id
END,
CASE
WHEN sub2_id IS NULL THEN 0
ELSE sub2_id
END
FROM guild_alliances ga
`
type GuildAlliance struct {
ID uint32 `db:"id"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
TotalMembers uint16
ParentGuildID uint32 `db:"parent_id"`
SubGuild1ID uint32 `db:"sub1_id"`
SubGuild2ID uint32 `db:"sub2_id"`
ParentGuild Guild
SubGuild1 Guild
SubGuild2 Guild
}
func GetAllianceData(s *Session, AllianceID uint32) (*GuildAlliance, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
WHERE ga.id = $1
`, allianceInfoSelectQuery), AllianceID)
if err != nil {
s.logger.Error("Failed to retrieve alliance data from database", zap.Error(err))
return nil, err
}
defer rows.Close()
hasRow := rows.Next()
if !hasRow {
return nil, nil
}
return buildAllianceObjectFromDbResult(rows, err, s)
}
func buildAllianceObjectFromDbResult(result *sqlx.Rows, err error, s *Session) (*GuildAlliance, error) {
alliance := &GuildAlliance{}
err = result.StructScan(alliance)
if err != nil {
s.logger.Error("failed to retrieve alliance from database", zap.Error(err))
return nil, err
}
parentGuild, err := GetGuildInfoByID(s, alliance.ParentGuildID)
if err != nil {
s.logger.Fatal("Failed to get parent guild info", zap.Error(err))
} else {
alliance.ParentGuild = *parentGuild
alliance.TotalMembers += parentGuild.MemberCount
}
if alliance.SubGuild1ID > 0 {
subGuild1, err := GetGuildInfoByID(s, alliance.SubGuild1ID)
if err != nil {
s.logger.Fatal("Failed to get sub guild 1 info", zap.Error(err))
} else {
alliance.SubGuild1 = *subGuild1
alliance.TotalMembers += subGuild1.MemberCount
}
}
if alliance.SubGuild2ID > 0 {
subGuild2, err := GetGuildInfoByID(s, alliance.SubGuild2ID)
if err != nil {
s.logger.Fatal("Failed to get sub guild 2 info", zap.Error(err))
} else {
alliance.SubGuild2 = *subGuild2
alliance.TotalMembers += subGuild2.MemberCount
}
}
return alliance, nil
}
func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCreateJoint)
_, err := s.server.db.Exec("INSERT INTO guild_alliances (name, parent_id) VALUES ($1, $2)", pkt.Name, pkt.GuildID)
if err != nil {
s.logger.Fatal("Failed to create guild alliance in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x01, 0x01, 0x01})
}
func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperateJoint)
guild, err := GetGuildInfoByID(s, pkt.GuildID)
if err != nil {
s.logger.Fatal("Failed to get guild info", zap.Error(err))
}
alliance, err := GetAllianceData(s, pkt.AllianceID)
if err != nil {
s.logger.Fatal("Failed to get alliance info", zap.Error(err))
}
switch pkt.Action {
case mhfpacket.OPERATE_JOINT_DISBAND:
if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID {
_, err = s.server.db.Exec("DELETE FROM guild_alliances WHERE id=$1", alliance.ID)
if err != nil {
s.logger.Fatal("Failed to disband alliance", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
s.logger.Warn(
"Non-owner of alliance attempted disband",
zap.Uint32("CharID", s.charID),
zap.Uint32("AllyID", alliance.ID),
)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
case mhfpacket.OPERATE_JOINT_LEAVE:
if guild.LeaderCharID == s.charID {
// delete alliance application
// or leave alliance
} else {
s.logger.Warn(
"Non-owner of guild attempted alliance leave",
zap.Uint32("CharID", s.charID),
)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
default:
panic(fmt.Sprintf("Unhandled operate joint action '%d'", pkt.Action))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
}
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,136 @@
package channelserver
import (
"fmt"
"time"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
type GuildMember struct {
GuildID uint32 `db:"guild_id"`
CharID uint32 `db:"character_id"`
JoinedAt *time.Time `db:"joined_at"`
Name string `db:"name"`
IsApplicant bool `db:"is_applicant"`
OrderIndex uint8 `db:"order_index"`
LastLogin uint32 `db:"last_login"`
AvoidLeadership bool `db:"avoid_leadership"`
IsLeader bool `db:"is_leader"`
HRP uint16 `db:"hrp"`
GR uint16 `db:"gr"`
WeaponID uint16 `db:"weapon_id"`
WeaponType uint16 `db:"weapon_type"`
}
func (gm *GuildMember) IsSubLeader() bool {
return gm.OrderIndex <= 3 && !gm.AvoidLeadership
}
func (gm *GuildMember) Save(s *Session) error {
_, err := s.server.db.Exec("UPDATE guild_characters SET avoid_leadership=$1 WHERE character_id=$2", gm.AvoidLeadership, gm.CharID)
if err != nil {
s.logger.Error(
"failed to update guild member data",
zap.Error(err),
zap.Uint32("charID", gm.CharID),
zap.Uint32("guildID", gm.GuildID),
)
return err
}
return nil
}
//TODO add the recruiter permission to this check when it exists
func (gm *GuildMember) IsRecruiter() bool {
return gm.IsLeader || gm.IsSubLeader()
}
const guildMembersSelectSQL = `
SELECT g.id as guild_id,
joined_at,
c.name,
character.character_id,
coalesce(gc.order_index, 0) as order_index,
c.last_login,
coalesce(gc.avoid_leadership, false) as avoid_leadership,
c.hrp,
c.gr,
c.weapon_id,
c.weapon_type,
character.is_applicant,
CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader
FROM (
SELECT character_id, true as is_applicant, guild_id
FROM guild_applications ga
WHERE ga.application_type = 'applied'
UNION
SELECT character_id, false as is_applicant, guild_id
FROM guild_characters gc
) character
JOIN characters c on character.character_id = c.id
LEFT JOIN guild_characters gc ON gc.character_id = character.character_id
JOIN guilds g ON g.id = character.guild_id
`
func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
WHERE character.guild_id = $1 AND is_applicant = $2
`, guildMembersSelectSQL), guildID, applicants)
if err != nil {
s.logger.Error("failed to retrieve membership data for guild", zap.Error(err), zap.Uint32("guildID", guildID))
return nil, err
}
defer rows.Close()
members := make([]*GuildMember, 0)
for rows.Next() {
member, err := buildGuildMemberObjectFromDBResult(rows, err, s)
if err != nil {
return nil, err
}
members = append(members, member)
}
return members, nil
}
func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)
if err != nil {
s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID))
return nil, err
}
defer rows.Close()
hasRow := rows.Next()
if !hasRow {
return nil, nil
}
return buildGuildMemberObjectFromDBResult(rows, err, s)
}
func buildGuildMemberObjectFromDBResult(rows *sqlx.Rows, err error, s *Session) (*GuildMember, error) {
memberData := &GuildMember{}
err = rows.StructScan(&memberData)
if err != nil {
s.logger.Error("failed to retrieve guild data from database", zap.Error(err))
return nil, err
}
return memberData, nil
}

View File

@@ -0,0 +1,310 @@
package channelserver
import (
"fmt"
"io"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostGuildScout)
actorCharGuildData, err := GetCharacterGuildData(s, s.charID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
if actorCharGuildData == nil || !actorCharGuildData.IsRecruiter() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
guildInfo, err := GetGuildInfoByID(s, actorCharGuildData.GuildID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
hasApplication, err := guildInfo.HasApplicationForCharID(s, pkt.CharID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
if hasApplication {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x04})
return
}
transaction, err := s.server.db.Begin()
if err != nil {
panic(err)
}
err = guildInfo.CreateApplication(s, pkt.CharID, GuildApplicationTypeInvited, transaction)
if err != nil {
rollbackTransaction(s, transaction)
doAckBufFail(s, pkt.AckHandle, nil)
panic(err)
}
senderName, err := getCharacterName(s, s.charID)
if err != nil {
panic(err)
}
mail := &Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Guild! ヽ(・∀・)ノ",
Body: fmt.Sprintf(
"%s has invited you to join the wonderful guild %s, do you accept this challenge?",
senderName,
guildInfo.Name,
),
IsGuildInvite: true,
}
err = mail.Send(s, transaction)
if err != nil {
rollbackTransaction(s, transaction)
doAckBufFail(s, pkt.AckHandle, nil)
return
}
err = transaction.Commit()
if err != nil {
doAckBufFail(s, pkt.AckHandle, nil)
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCancelGuildScout)
guildCharData, err := GetCharacterGuildData(s, s.charID)
if err != nil {
panic(err)
}
if guildCharData == nil || !guildCharData.IsRecruiter() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
guild, err := GetGuildInfoByID(s, guildCharData.GuildID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
err = guild.CancelInvitation(s, pkt.InvitationID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAnswerGuildScout)
guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID)
if err != nil {
panic(err)
}
_, err = guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
if err != nil {
s.logger.Warn(
"could not retrieve guild invitation",
zap.Error(err),
zap.Uint32("guildID", guild.ID),
zap.Uint32("charID", s.charID),
)
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
if pkt.Answer {
err = guild.AcceptApplication(s, s.charID)
} else {
err = guild.RejectApplication(s, s.charID)
}
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
senderName, err := getCharacterName(s, pkt.LeaderID)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
successMail := Mail{
SenderID: pkt.LeaderID,
RecipientID: s.charID,
Subject: "Happy days!",
Body: fmt.Sprintf("You successfully joined %s and should be proud of all you have accomplished.", guild.Name),
IsGuildInvite: false,
SenderName: senderName,
}
err = successMail.Send(s, nil)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x81, 0x7e})
}
func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildScoutList)
guildInfo, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
}
if guildInfo == nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
rows, err := s.server.db.Queryx(`
SELECT c.id, c.name, ga.actor_id
FROM guild_applications ga
JOIN characters c ON c.id = ga.character_id
WHERE ga.guild_id = $1 AND ga.application_type = 'invited'
`, guildInfo.ID)
if err != nil {
s.logger.Error("failed to retrieve scouted characters", zap.Error(err))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
defer rows.Close()
bf := byteframe.NewByteFrame()
bf.SetBE()
// Result count, we will overwrite this later
bf.WriteUint32(0x00)
count := uint32(0)
for rows.Next() {
var charName string
var charID uint32
var actorID uint32
err = rows.Scan(&charID, &charName, &actorID)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
// This seems to be used as a unique ID for the invitation sent
// we can just use the charID and then filter on guild_id+charID when performing operations
// this might be a problem later with mails sent referencing IDs but we'll see.
bf.WriteUint32(charID)
bf.WriteUint32(actorID)
bf.WriteUint32(charID)
bf.WriteUint32(uint32(time.Now().Unix()))
bf.WriteUint16(0x00) // HR?
bf.WriteUint16(0x00) // GR?
charNameBytes, _ := stringsupport.ConvertUTF8ToShiftJIS(charName)
bf.WriteBytes(charNameBytes)
bf.WriteBytes(make([]byte, 32-len(charNameBytes))) // Fixed length string
count++
}
_, err = bf.Seek(0, io.SeekStart)
if err != nil {
panic(err)
}
bf.WriteUint32(count)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetRejectGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRejectGuildScout)
row := s.server.db.QueryRow("SELECT restrict_guild_scout FROM characters WHERE id=$1", s.charID)
var currentStatus bool
err := row.Scan(&currentStatus)
if err != nil {
s.logger.Error(
"failed to retrieve character guild scout status",
zap.Error(err),
zap.Uint32("charID", s.charID),
)
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
response := uint8(0x00)
if currentStatus {
response = 0x01
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, response})
}
func handleMsgMhfSetRejectGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetRejectGuildScout)
_, err := s.server.db.Exec("UPDATE characters SET restrict_guild_scout=$1 WHERE id=$2", pkt.Reject, s.charID)
if err != nil {
s.logger.Error(
"failed to update character guild scout status",
zap.Error(err),
zap.Uint32("charID", s.charID),
)
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
doAckSimpleSucceed(s, pkt.AckHandle, nil)
}

View File

@@ -0,0 +1,175 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
)
type TreasureHunt struct {
HuntID uint32 `db:"id"`
HostID uint32 `db:"host_id"`
Destination uint32 `db:"destination"`
Level uint32 `db:"level"`
Return uint32 `db:"return"`
Acquired bool `db:"acquired"`
Claimed bool `db:"claimed"`
Hunters string `db:"hunters"`
Treasure string `db:"treasure"`
HuntData []byte `db:"hunt_data"`
}
func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildTresure)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
}
bf := byteframe.NewByteFrame()
hunts := 0
rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1", guild.ID)
for rows.Next() {
hunt := &TreasureHunt{}
err = rows.StructScan(&hunt)
// Remove self from other hunter count
hunt.Hunters = stringsupport.CSVRemove(hunt.Hunters, int(s.charID))
if err != nil {
panic(err)
}
if pkt.MaxHunts == 1 {
if hunt.HostID != s.charID || hunt.Acquired {
continue
}
hunts++
bf.WriteUint32(hunt.HuntID)
bf.WriteUint32(hunt.Destination)
bf.WriteUint32(hunt.Level)
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
bf.WriteUint32(hunt.Return)
bf.WriteBool(false)
bf.WriteBool(false)
bf.WriteBytes(hunt.HuntData)
break
} else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 {
hunts++
bf.WriteUint32(hunt.HuntID)
bf.WriteUint32(hunt.Destination)
bf.WriteUint32(hunt.Level)
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
bf.WriteUint32(hunt.Return)
bf.WriteBool(hunt.Claimed)
bf.WriteBool(stringsupport.CSVContains(hunt.Treasure, int(s.charID)))
bf.WriteBytes(hunt.HuntData)
}
}
resp := byteframe.NewByteFrame()
resp.WriteUint16(uint16(hunts))
resp.WriteUint16(uint16(hunts))
resp.WriteBytes(bf.Data())
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegistGuildTresure)
bf := byteframe.NewByteFrameFromBytes(pkt.Data)
huntData := byteframe.NewByteFrame()
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
}
guildCats := getGuildAirouList(s)
destination := bf.ReadUint32()
level := bf.ReadUint32()
huntData.WriteUint32(s.charID)
huntData.WriteBytes(stringsupport.PaddedString(s.Name, 18, true))
catsUsed := ""
for i := 0; i < 5; i++ {
catID := bf.ReadUint32()
huntData.WriteUint32(catID)
if catID > 0 {
catsUsed = stringsupport.CSVAdd(catsUsed, int(catID))
for _, cat := range guildCats {
if cat.CatID == catID {
huntData.WriteBytes(cat.CatName)
break
}
}
huntData.WriteBytes(bf.ReadBytes(9))
}
}
_, err = s.server.db.Exec("INSERT INTO guild_hunts (guild_id, host_id, destination, level, return, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6, $7)",
guild.ID, s.charID, destination, level, Time_Current_Adjusted().Unix(), huntData.Data(), catsUsed)
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresure)
_, err := s.server.db.Exec("UPDATE guild_hunts SET acquired=true WHERE id=$1", pkt.HuntID)
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func treasureHuntUnregister(s *Session) {
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil || guild == nil {
return
}
var huntID int
var hunters string
rows, _ := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID)
for rows.Next() {
rows.Scan(&huntID, &hunters)
hunters = stringsupport.CSVRemove(hunters, int(s.charID))
s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", hunters, huntID)
}
}
func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport)
var csv string
if pkt.State == 0 { // Report registration
// Unregister from all other hunts
treasureHuntUnregister(s)
if pkt.HuntID != 0 {
// Register to selected hunt
err := s.server.db.QueryRow("SELECT hunters FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
if err != nil {
panic(err)
}
csv = stringsupport.CSVAdd(csv, int(s.charID))
_, err = s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", csv, pkt.HuntID)
if err != nil {
panic(err)
}
}
} else if pkt.State == 1 { // Collected by hunter
s.server.db.Exec("UPDATE guild_hunts SET hunters='', claimed=true WHERE id=$1", pkt.HuntID)
} else if pkt.State == 2 { // Claim treasure
err := s.server.db.QueryRow("SELECT treasure FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
if err != nil {
panic(err)
}
csv = stringsupport.CSVAdd(csv, int(s.charID))
_, err = s.server.db.Exec("UPDATE guild_hunts SET treasure=$1 WHERE id=$2", csv, pkt.HuntID)
if err != nil {
panic(err)
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildTresureSouvenir)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
}
func handleMsgMhfAcquireGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresureSouvenir)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,317 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateInterior)
_, err := s.server.db.Exec("UPDATE characters SET house=$1 WHERE id=$2", pkt.InteriorData, s.charID)
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
type HouseData struct {
CharID uint32 `db:"id"`
HRP uint16 `db:"hrp"`
GR uint16 `db:"gr"`
Name string `db:"name"`
}
func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateHouse)
bf := byteframe.NewByteFrame()
var houses []HouseData
switch pkt.Method {
case 1:
var friendsList string
s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&friendsList)
cids := stringsupport.CSVElems(friendsList)
for _, cid := range cids {
house := HouseData{}
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE id=$1", cid)
err := row.StructScan(&house)
if err != nil {
panic(err)
} else {
houses = append(houses, house)
}
}
case 2:
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
break
}
guildMembers, err := GetGuildMembers(s, guild.ID, false)
if err != nil {
break
}
for _, member := range guildMembers {
house := HouseData{}
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE id=$1", member.CharID)
err := row.StructScan(&house)
if err != nil {
panic(err)
} else {
houses = append(houses, house)
}
}
case 3:
house := HouseData{}
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE name=$1", pkt.Name)
err := row.StructScan(&house)
if err != nil {
panic(err)
} else {
houses = append(houses, house)
}
case 4:
house := HouseData{}
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE id=$1", pkt.CharID)
err := row.StructScan(&house)
if err != nil {
panic(err)
} else {
houses = append(houses, house)
}
case 5: // Recent visitors
break
}
var exists int
for _, house := range houses {
for _, session := range s.server.sessions {
if session.charID == house.CharID {
exists++
bf.WriteUint32(house.CharID)
bf.WriteUint8(session.myseries.state)
if len(session.myseries.password) > 0 {
bf.WriteUint8(3)
} else {
bf.WriteUint8(0)
}
bf.WriteUint16(house.HRP)
bf.WriteUint16(house.GR)
ps.Uint8(bf, house.Name, true)
break
}
}
}
resp := byteframe.NewByteFrame()
resp.WriteUint16(uint16(exists))
resp.WriteBytes(bf.Data())
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfUpdateHouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateHouse)
// 01 = closed
// 02 = open anyone
// 03 = open friends
// 04 = open guild
// 05 = open friends+guild
s.myseries.state = pkt.State
s.myseries.password = pkt.Password
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadHouse)
bf := byteframe.NewByteFrame()
if pkt.Destination != 9 && len(pkt.Password) > 0 && pkt.CheckPass {
for _, session := range s.server.sessions {
if session.charID == pkt.CharID && pkt.Password != session.myseries.password {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
}
}
var furniture []byte
err := s.server.db.QueryRow("SELECT house FROM characters WHERE id=$1", pkt.CharID).Scan(&furniture)
if err != nil {
panic(err)
}
if furniture == nil {
furniture = make([]byte, 20)
}
switch pkt.Destination {
case 3: // Others house
for _, session := range s.server.sessions {
if session.charID == pkt.CharID {
bf.WriteBytes(session.myseries.houseTier)
bf.WriteBytes(session.myseries.houseData)
bf.WriteBytes(make([]byte, 19)) // Padding?
bf.WriteBytes(furniture)
}
}
case 4: // Bookshelf
for _, session := range s.server.sessions {
if session.charID == pkt.CharID {
bf.WriteBytes(session.myseries.bookshelfData)
}
}
case 5: // Gallery
for _, session := range s.server.sessions {
if session.charID == pkt.CharID {
bf.WriteBytes(session.myseries.galleryData)
}
}
case 8: // Tore
for _, session := range s.server.sessions {
if session.charID == pkt.CharID {
bf.WriteBytes(session.myseries.toreData)
}
}
case 9: // Own house
bf.WriteBytes(furniture)
case 10: // Garden
for _, session := range s.server.sessions {
if session.charID == pkt.CharID {
bf.WriteBytes(session.myseries.gardenData)
c, d := getGookData(s, pkt.CharID)
bf.WriteUint16(c)
bf.WriteUint16(0)
bf.WriteBytes(d)
}
}
}
if len(bf.Data()) == 0 {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
} else {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
}
func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetMyhouseInfo)
var data []byte
err := s.server.db.QueryRow("SELECT trophy FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
panic(err)
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateMyhouseInfo)
_, err := s.server.db.Exec("UPDATE characters SET trophy=$1 WHERE id=$2", pkt.Unk0, s.charID)
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadDecoMyset)
var data []byte
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get preset decorations savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
//doAckBufSucceed(s, pkt.AckHandle, data)
} else {
// set first byte to 1 to avoid pop up every time without save
body := make([]byte, 0x226)
body[0] = 1
doAckBufSucceed(s, pkt.AckHandle, body)
}
}
func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveDecoMyset)
// 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.Fatal("Failed to get preset decorations savedata from db", zap.Error(err))
} else {
numSets := bf.ReadUint8() // sets being written
// empty save
if len(loadData) == 0 {
loadData = []byte{0x01, 0x00}
}
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}
}
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...)
}
savedSets++
break
}
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
}
}
loadData[1] = savedSets // update set count
}
_, err := s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", loadData, s.charID)
if err != nil {
s.logger.Fatal("Failed to update decomyset savedata in db", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateTitle)
bf := byteframe.NewByteFrame()
if pkt.CharID == s.charID {
titleCount := 114 // all titles unlocked
bf.WriteUint16(uint16(titleCount)) // title count
bf.WriteUint16(0) // unk
for i := 0; i < titleCount; i++ {
bf.WriteUint16(uint16(i))
bf.WriteUint16(0) // unk
bf.WriteUint32(0) // timestamp acquired
bf.WriteUint32(0) // timestamp updated
}
} else {
bf.WriteUint16(0)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,45 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgMhfAddKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
// hunting with both ranks maxed gets you these
pkt := p.(*mhfpacket.MsgMhfAddKouryouPoint)
var points int
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=COALESCE(kouryou_point + $1, $1) WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
if err != nil {
s.logger.Fatal("Failed to update KouryouPoint in db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(uint32(points))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetKouryouPoint)
var points int
err := s.server.db.QueryRow("SELECT COALESCE(kouryou_point, 0) FROM characters WHERE id = $1", s.charID).Scan(&points)
if err != nil {
s.logger.Fatal("Failed to get kouryou_point savedata from db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(uint32(points))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfExchangeKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
// spent at the guildmaster, 10000 a roll
var points int
pkt := p.(*mhfpacket.MsgMhfExchangeKouryouPoint)
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=kouryou_point - $1 WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
if err != nil {
s.logger.Fatal("Failed to update platemyset savedata in db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(uint32(points))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}

View File

@@ -0,0 +1,424 @@
package channelserver
import (
"database/sql"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
type Mail struct {
ID int `db:"id"`
SenderID uint32 `db:"sender_id"`
RecipientID uint32 `db:"recipient_id"`
Subject string `db:"subject"`
Body string `db:"body"`
Read bool `db:"read"`
Deleted bool `db:"deleted"`
Locked bool `db:"locked"`
AttachedItemReceived bool `db:"attached_item_received"`
AttachedItemID uint16 `db:"attached_item"`
AttachedItemAmount uint16 `db:"attached_item_amount"`
CreatedAt time.Time `db:"created_at"`
IsGuildInvite bool `db:"is_guild_invite"`
SenderName string `db:"sender_name"`
}
func (m *Mail) Send(s *Session, transaction *sql.Tx) error {
query := `
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`
var err error
if transaction == nil {
_, err = s.server.db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite)
} else {
_, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite)
}
if err != nil {
s.logger.Error(
"failed to send mail",
zap.Error(err),
zap.Uint32("senderID", m.SenderID),
zap.Uint32("recipientID", m.RecipientID),
zap.String("subject", m.Subject),
zap.String("body", m.Body),
zap.Uint16("itemID", m.AttachedItemID),
zap.Uint16("itemAmount", m.AttachedItemAmount),
zap.Bool("isGuildInvite", m.IsGuildInvite),
)
return err
}
return nil
}
func (m *Mail) MarkRead(s *Session) error {
_, err := s.server.db.Exec(`
UPDATE mail SET read = true WHERE id = $1
`, m.ID)
if err != nil {
s.logger.Error(
"failed to mark mail as read",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func (m *Mail) MarkDeleted(s *Session) error {
_, err := s.server.db.Exec(`
UPDATE mail SET deleted = true WHERE id = $1
`, m.ID)
if err != nil {
s.logger.Error(
"failed to mark mail as deleted",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func (m *Mail) MarkAcquired(s *Session) error {
_, err := s.server.db.Exec(`
UPDATE mail SET attached_item_received = true WHERE id = $1
`, m.ID)
if err != nil {
s.logger.Error(
"failed to mark mail item as claimed",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func (m *Mail) MarkLocked(s *Session, locked bool) error {
_, err := s.server.db.Exec(`
UPDATE mail SET locked = $1 WHERE id = $2
`, locked, m.ID)
if err != nil {
s.logger.Error(
"failed to mark mail as locked",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
rows, err := s.server.db.Queryx(`
SELECT
m.id,
m.sender_id,
m.recipient_id,
m.subject,
m.read,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.deleted,
m.locked,
c.name as sender_name
FROM mail m
JOIN characters c ON c.id = m.sender_id
WHERE recipient_id = $1 AND m.deleted = false
ORDER BY m.created_at DESC, id DESC
LIMIT 32
`, charID)
if err != nil {
s.logger.Error("failed to get mail for character", zap.Error(err), zap.Uint32("charID", charID))
return nil, err
}
defer rows.Close()
allMail := make([]Mail, 0)
for rows.Next() {
mail := Mail{}
err := rows.StructScan(&mail)
if err != nil {
return nil, err
}
allMail = append(allMail, mail)
}
return allMail, nil
}
func GetMailByID(s *Session, ID int) (*Mail, error) {
row := s.server.db.QueryRowx(`
SELECT
m.id,
m.sender_id,
m.recipient_id,
m.subject,
m.read,
m.body,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.deleted,
m.locked,
c.name as sender_name
FROM mail m
JOIN characters c ON c.id = m.sender_id
WHERE m.id = $1
LIMIT 1
`, ID)
mail := &Mail{}
err := row.StructScan(mail)
if err != nil {
s.logger.Error(
"failed to retrieve mail",
zap.Error(err),
zap.Int("mailID", ID),
)
return nil, err
}
return mail, nil
}
func SendMailNotification(s *Session, m *Mail, recipient *Session) {
senderName, err := getCharacterName(s, m.SenderID)
if err != nil {
panic(err)
}
bf := byteframe.NewByteFrame()
notification := &binpacket.MsgBinMailNotify{
SenderName: senderName,
}
notification.Build(bf)
castedBinary := &mhfpacket.MsgSysCastedBinary{
CharID: m.SenderID,
BroadcastType: 0x00,
MessageType: BinaryMessageTypeMailNotify,
RawDataPayload: bf.Data(),
}
castedBinary.Build(bf, s.clientContext)
recipient.QueueSendMHF(castedBinary)
}
func getCharacterName(s *Session, charID uint32) (string, error) {
row := s.server.db.QueryRow("SELECT name FROM characters WHERE id = $1", charID)
charName := ""
err := row.Scan(&charName)
if err != nil {
return "", err
}
return charName, nil
}
func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMail)
mailId := s.mailList[pkt.AccIndex]
if mailId == 0 {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic("attempting to read mail that doesn't exist in session map")
}
mail, err := GetMailByID(s, mailId)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
_ = mail.MarkRead(s)
bf := byteframe.NewByteFrame()
body := s.clientContext.StrConv.MustEncode(mail.Body)
bf.WriteNullTerminatedBytes(body)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfListMail)
mail, err := GetMailListForCharacter(s, s.charID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
if s.mailList == nil {
s.mailList = make([]int, 256)
}
msg := byteframe.NewByteFrame()
msg.WriteUint32(uint32(len(mail)))
startIndex := s.mailAccIndex
for i, m := range mail {
accIndex := startIndex + uint8(i)
s.mailList[accIndex] = m.ID
s.mailAccIndex++
itemAttached := m.AttachedItemID != 0
subject := s.clientContext.StrConv.MustEncode(m.Subject)
sender := s.clientContext.StrConv.MustEncode(m.SenderName)
msg.WriteUint32(m.SenderID)
msg.WriteUint32(uint32(m.CreatedAt.Unix()))
msg.WriteUint8(uint8(accIndex))
msg.WriteUint8(uint8(i))
flags := uint8(0x00)
if m.Read {
flags |= 0x01
}
if m.Locked {
flags |= 0x02
}
// System message, hides ID
// flags |= 0x04
if m.AttachedItemReceived {
flags |= 0x08
}
if m.IsGuildInvite {
flags |= 0x10
}
msg.WriteUint8(flags)
msg.WriteBool(itemAttached)
msg.WriteUint8(uint8(len(subject) + 1))
msg.WriteUint8(uint8(len(sender) + 1))
msg.WriteNullTerminatedBytes(subject)
msg.WriteNullTerminatedBytes(sender)
if itemAttached {
msg.WriteUint16(m.AttachedItemAmount)
msg.WriteUint16(m.AttachedItemID)
}
}
doAckBufSucceed(s, pkt.AckHandle, msg.Data())
}
func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOprtMail)
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex])
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
switch mhfpacket.OperateMailOperation(pkt.Operation) {
case mhfpacket.OPERATE_MAIL_DELETE:
err = mail.MarkDeleted(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_LOCK:
err = mail.MarkLocked(s, true)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_UNLOCK:
err = mail.MarkLocked(s, false)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM:
err = mail.MarkAcquired(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSendMail)
query := `
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`
if pkt.RecipientID == 0 { // Guild mail
g, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
s.logger.Fatal("Failed to get guild info for mail")
}
gm, err := GetGuildMembers(s, g.ID, false)
if err != nil {
s.logger.Fatal("Failed to get guild members for mail")
}
for i := 0; i < len(gm); i++ {
_, err := s.server.db.Exec(query, s.charID, gm[i].CharID, pkt.Subject, pkt.Body, 0, 0, false)
if err != nil {
s.logger.Fatal("Failed to send mail")
}
}
} else {
_, err := s.server.db.Exec(query, s.charID, pkt.RecipientID, pkt.Subject, pkt.Body, pkt.ItemID, pkt.Quantity, false)
if err != nil {
s.logger.Fatal("Failed to send mail")
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,389 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"erupe-ce/server/channelserver/compression/deltacomp"
"erupe-ce/server/channelserver/compression/nullcomp"
"go.uber.org/zap"
"io"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
)
// THERE ARE [PARTENER] [MERCENARY] [OTOMO AIRU]
///////////////////////////////////////////
/// PARTENER //
///////////////////////////////////////////
func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadPartner)
// load partner from database
var data []byte
err := s.server.db.QueryRow("SELECT partner FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get partner savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
// TODO(Andoryuuta): Figure out unusual double ack. One sized, one not.
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePartner)
dumpSaveData(s, pkt.RawDataPayload, "_partner")
_, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Fatal("Failed to update partner savedata in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadLegendDispatch)
data := []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x01, 0x8d, 0x40, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x02, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x04, 0x30, 0x40}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi)
var data []byte
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get hunter navigation savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
// set first byte to 1 to avoid pop up every time without save
body := make([]byte, 0x226)
body[0] = 1
doAckBufSucceed(s, pkt.AckHandle, body)
}
}
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
dumpSaveData(s, pkt.RawDataPayload, "_hunternavi")
if pkt.IsDataDiff {
var data []byte
// Load existing save
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get hunternavi savedata from db", zap.Error(err))
}
// Check if we actually had any hunternavi data, using a blank buffer if not.
// This is requried as the client will try to send a diff after character creation without a prior MsgMhfSaveHunterNavi packet.
if len(data) == 0 {
data = make([]byte, 0x226)
data[0] = 1 // set first byte to 1 to avoid pop up every time without save
}
// Perform diff and compress it to write back to db
s.logger.Info("Diffing...")
saveOutput := deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)
_, err = s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", saveOutput, s.charID)
if err != nil {
s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err))
}
s.logger.Info("Wrote recompressed hunternavi back to DB.")
} else {
// simply update database, no extra processing
_, err := s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
///////////////////////////////////////////
///////////////////////////////////////////
/// MERCENARY //
///////////////////////////////////////////
func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x0A))
}
func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x00) // Unk
bf.WriteUint32(rand.Uint32()) // Partner ID?
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
GCPValue := bf.ReadUint32()
_ = bf.ReadUint32() // unk
MercDataSize := bf.ReadUint32()
MercData := bf.ReadBytes(uint(MercDataSize))
_ = bf.ReadUint32() // unk
if MercDataSize > 0 {
// the save packet has an extra null byte after its size
_, err := s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", MercData[:MercDataSize], s.charID)
if err != nil {
s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
}
}
// gcp value is always present regardless
_, err := s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", GCPValue, s.charID)
if err != nil {
s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
var data []byte
var gcp uint32
// still has issues
err := s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get savemercenary data from db", zap.Error(err))
}
err = s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp)
if err != nil {
panic(err)
}
if len(data) == 0 {
data = []byte{0x00}
}
resp := byteframe.NewByteFrame()
resp.WriteBytes(data)
resp.WriteUint16(0)
resp.WriteUint32(gcp)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryM)
// accessing actual rasta data of someone else still unsure of the formatting of this
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {}
///////////////////////////////////////////
///////////////////////////////////////////
/// OTOMO AIRU //
///////////////////////////////////////////
func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadOtomoAirou)
var data []byte
err := s.server.db.QueryRow("SELECT otomoairou FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get partnyaa savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveOtomoAirou)
decomp, err := nullcomp.Decompress(pkt.RawDataPayload[1:])
if err != nil {
s.logger.Error("Failed to decompress airou", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf := byteframe.NewByteFrameFromBytes(decomp)
cats := bf.ReadUint8()
for i := 0; i < int(cats); i++ {
dataLen := bf.ReadUint32()
catID := bf.ReadUint32()
if catID == 0 {
var nextID uint32
_ = s.server.db.QueryRow("SELECT nextval('airou_id_seq')").Scan(&nextID)
bf.Seek(-4, io.SeekCurrent)
bf.WriteUint32(nextID)
}
_ = bf.ReadBytes(uint(dataLen) - 4)
}
comp, err := nullcomp.Compress(bf.Data())
if err != nil {
s.logger.Error("Failed to compress airou", zap.Error(err))
} else {
comp = append([]byte{0x01}, comp...)
s.server.db.Exec("UPDATE characters SET otomoairou=$1 WHERE id=$2", comp, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateAiroulist)
resp := byteframe.NewByteFrame()
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin")); err == nil {
data, _ := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin"))
resp.WriteBytes(data)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
return
}
airouList := getGuildAirouList(s)
resp.WriteUint16(uint16(len(airouList)))
resp.WriteUint16(uint16(len(airouList)))
for _, cat := range airouList {
resp.WriteUint32(cat.CatID)
resp.WriteBytes(cat.CatName)
resp.WriteUint32(cat.Experience)
resp.WriteUint8(cat.Personality)
resp.WriteUint8(cat.Class)
resp.WriteUint8(cat.WeaponType)
resp.WriteUint16(cat.WeaponID)
resp.WriteUint32(0) // 32 bit unix timestamp, either time at which the cat stops being fatigued or the time at which it started
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
// CatDefinition holds values needed to populate the guild cat list
type CatDefinition struct {
CatID uint32
CatName []byte
CurrentTask uint8
Personality uint8
Class uint8
Experience uint32
WeaponType uint8
WeaponID uint16
}
func getGuildAirouList(s *Session) []CatDefinition {
var guild *Guild
var err error
var guildCats []CatDefinition
// returning 0 cats on any guild issues
// can probably optimise all of the guild queries pretty heavily
guild, err = GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
return guildCats
}
// Get cats used recently
// Retail reset at midday, 12 hours is a midpoint
tempBanDuration := 43200 - (1800) // Minus hunt time
bannedCats := make(map[uint32]int)
var csvTemp string
rows, err := s.server.db.Query(`SELECT cats_used
FROM guild_hunts gh
INNER JOIN characters c
ON gh.host_id = c.id
WHERE c.id=$1 AND gh.return+$2>$3`, s.charID, tempBanDuration, Time_Current_Adjusted().Unix())
if err != nil {
s.logger.Warn("Failed to get recently used airous", zap.Error(err))
}
for rows.Next() {
rows.Scan(&csvTemp)
for i, j := range stringsupport.CSVElems(csvTemp) {
bannedCats[uint32(j)] = i
}
}
// ellie's GetGuildMembers didn't seem to pull leader?
rows, err = s.server.db.Query(`SELECT c.otomoairou
FROM characters c
INNER JOIN guild_characters gc
ON gc.character_id = c.id
WHERE gc.guild_id = $1 AND c.otomoairou IS NOT NULL
ORDER BY c.id ASC
LIMIT 60;`, guild.ID)
if err != nil {
s.logger.Warn("Selecting otomoairou based on guild failed", zap.Error(err))
return guildCats
}
for rows.Next() {
var data []byte
err = rows.Scan(&data)
if err != nil {
s.logger.Warn("select failure", zap.Error(err))
continue
} else if len(data) == 0 {
// non extant cats that aren't null in DB
continue
}
// first byte has cat existence in general, can skip if 0
if data[0] == 1 {
decomp, err := nullcomp.Decompress(data[1:])
if err != nil {
s.logger.Warn("decomp failure", zap.Error(err))
continue
}
bf := byteframe.NewByteFrameFromBytes(decomp)
cats := GetCatDetails(bf)
for _, cat := range cats {
_, exists := bannedCats[cat.CatID]
if cat.CurrentTask == 4 && !exists {
guildCats = append(guildCats, cat)
}
}
}
}
return guildCats
}
func GetCatDetails(bf *byteframe.ByteFrame) []CatDefinition {
catCount := bf.ReadUint8()
cats := make([]CatDefinition, catCount)
for x := 0; x < int(catCount); x++ {
var catDef CatDefinition
// cat sometimes has additional bytes for whatever reason, gift items? timestamp?
// until actual variance is known we can just seek to end based on start
catDefLen := bf.ReadUint32()
catStart, _ := bf.Seek(0, io.SeekCurrent)
catDef.CatID = bf.ReadUint32()
bf.Seek(1, io.SeekCurrent) // unknown value, probably a bool
catDef.CatName = bf.ReadBytes(18) // always 18 len, reads first null terminated string out of section and discards rest
catDef.CurrentTask = bf.ReadUint8()
bf.Seek(16, io.SeekCurrent) // appearance data and what is seemingly null bytes
catDef.Personality = bf.ReadUint8()
catDef.Class = bf.ReadUint8()
bf.Seek(5, io.SeekCurrent) // affection and colour sliders
catDef.Experience = bf.ReadUint32() // raw cat rank points, doesn't have a rank
bf.Seek(1, io.SeekCurrent) // bool for weapon being equipped
catDef.WeaponType = bf.ReadUint8() // weapon type, presumably always 6 for melee?
catDef.WeaponID = bf.ReadUint16() // weapon id
bf.Seek(catStart+int64(catDefLen), io.SeekStart)
cats[x] = catDef
}
return cats
}
///////////////////////////////////////////

View File

@@ -0,0 +1,13 @@
package channelserver
import "erupe-ce/network/mhfpacket"
func handleMsgSysCreateMutex(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysCreateOpenMutex(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDeleteMutex(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysOpenMutex(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysCloseMutex(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,87 @@
package channelserver
import (
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateObject)
s.stage.Lock()
newObj := &Object{
id: s.stage.NextObjectID(),
ownerCharID: s.charID,
x: pkt.X,
y: pkt.Y,
z: pkt.Z,
}
s.stage.objects[s.charID] = newObj
s.stage.Unlock()
// Response to our requesting client.
resp := byteframe.NewByteFrame()
resp.WriteUint32(newObj.id) // New local obj handle.
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
// Duplicate the object creation to all sessions in the same stage.
dupObjUpdate := &mhfpacket.MsgSysDuplicateObject{
ObjID: newObj.id,
X: newObj.x,
Y: newObj.y,
Z: newObj.z,
OwnerCharID: newObj.ownerCharID,
}
s.logger.Info(fmt.Sprintf("Broadcasting new object: %s (%d)", s.Name, s.charID))
s.stage.BroadcastMHF(dupObjUpdate, s)
}
func handleMsgSysDeleteObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysPositionObject(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysPositionObject)
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
fmt.Printf("[%s] with objectID [%d] move to (%f,%f,%f)\n\n", s.Name, pkt.ObjID, pkt.X, pkt.Y, pkt.Z)
}
s.stage.Lock()
object, ok := s.stage.objects[s.charID]
if ok {
object.x = pkt.X
object.y = pkt.Y
object.z = pkt.Z
}
s.stage.Unlock()
// One of the few packets we can just re-broadcast directly.
s.stage.BroadcastMHF(pkt, s)
}
func handleMsgSysRotateObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDuplicateObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysSetObjectBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysSetObjectBinary)
for _, object := range s.stage.objects {
if object.id == pkt.ObjID {
object.binary = pkt.RawDataPayload
}
}
}
func handleMsgSysGetObjectBinary(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysGetObjectOwner(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysUpdateObjectBinary(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysCleanupObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysAddObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDelObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDispObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysHideObject(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,165 @@
package channelserver
import (
"erupe-ce/network/mhfpacket"
"erupe-ce/server/channelserver/compression/deltacomp"
"erupe-ce/server/channelserver/compression/nullcomp"
"go.uber.org/zap"
)
func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadPlateData)
var data []byte
err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get plate data savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{})
}
}
func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePlateData)
dumpSaveData(s, pkt.RawDataPayload, "_platedata")
if pkt.IsDataDiff {
var data []byte
// Load existing save
err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get platedata savedata from db", zap.Error(err))
}
if len(data) > 0 {
// Decompress
s.logger.Info("Decompressing...")
data, err = nullcomp.Decompress(data)
if err != nil {
s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err))
}
} else {
// create empty save if absent
data = make([]byte, 0x1AF20)
}
// Perform diff and compress it to write back to db
s.logger.Info("Diffing...")
saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data))
if err != nil {
s.logger.Fatal("Failed to diff and compress platedata savedata", zap.Error(err))
}
_, err = s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", saveOutput, s.charID)
if err != nil {
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
}
s.logger.Info("Wrote recompressed platedata back to DB.")
} else {
// simply update database, no extra processing
_, err := s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadPlateBox)
var data []byte
err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get sigil box savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{})
}
}
func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePlateBox)
dumpSaveData(s, pkt.RawDataPayload, "_platebox")
if pkt.IsDataDiff {
var data []byte
// Load existing save
err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get sigil box savedata from db", zap.Error(err))
}
// Decompress
if len(data) > 0 {
// Decompress
s.logger.Info("Decompressing...")
data, err = nullcomp.Decompress(data)
if err != nil {
s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err))
}
} else {
// create empty save if absent
data = make([]byte, 0x820)
}
// Perform diff and compress it to write back to db
s.logger.Info("Diffing...")
saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data))
if err != nil {
s.logger.Fatal("Failed to diff and compress savedata", zap.Error(err))
}
_, err = s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", saveOutput, s.charID)
if err != nil {
s.logger.Fatal("Failed to update platebox savedata in db", zap.Error(err))
}
s.logger.Info("Wrote recompressed platebox back to DB.")
} else {
// simply update database, no extra processing
_, err := s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadPlateMyset)
var data []byte
err := s.server.db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get presets sigil savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
blankData := make([]byte, 0x780)
doAckBufSucceed(s, pkt.AckHandle, blankData)
}
}
func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePlateMyset)
// looks to always return the full thing, simply update database, no extra processing
_, err := s.server.db.Exec("UPDATE characters SET platemyset=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Fatal("Failed to update platemyset savedata in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}

View File

@@ -0,0 +1,101 @@
package channelserver
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysGetFile)
// Debug print the request.
if pkt.IsScenario {
fmt.Printf("%+v\n", pkt.ScenarioIdentifer)
filename := fmt.Sprintf("%d_0_0_0_S%d_T%d_C%d", pkt.ScenarioIdentifer.CategoryID, pkt.ScenarioIdentifer.MainID, pkt.ScenarioIdentifer.Flags, pkt.ScenarioIdentifer.ChapterID)
// Read the scenario file.
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("scenarios/%s.bin", filename)))
if err != nil {
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin")); err == nil {
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin"))
if err != nil {
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
// Get quest file.
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename)))
if err != nil {
s.logger.Fatal(fmt.Sprintf("Failed to open quest file: quests/%s.bin", pkt.Filename))
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
}
}
func handleMsgMhfLoadFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadFavoriteQuest)
var data []byte
err := s.server.db.QueryRow("SELECT savefavoritequest FROM characters WHERE id = $1", s.charID).Scan(&data)
if err == nil && len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveFavoriteQuest)
s.server.db.Exec("UPDATE characters SET savefavoritequest=$1 WHERE id=$2", pkt.Data, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
// local files are easier for now, probably best would be to generate dynamically
pkt := p.(*mhfpacket.MsgMhfEnumerateQuest)
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("questlists/list_%d.bin", pkt.QuestList)))
if err != nil {
fmt.Printf("questlists/list_%d.bin", pkt.QuestList)
stubEnumerateNoResults(s, pkt.AckHandle)
} else {
doAckBufSucceed(s, pkt.AckHandle, data)
}
}
func handleMsgMhfEnterTournamentQuest(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdBonusQuestInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdBonusQuestInfo)
udBonusQuestInfos := []struct {
Unk0 uint8
Unk1 uint8
StartTime uint32 // Unix timestamp (seconds)
EndTime uint32 // Unix timestamp (seconds)
Unk4 uint32
Unk5 uint8
Unk6 uint8
}{} // Blank stub array.
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(udBonusQuestInfos)))
for _, q := range udBonusQuestInfos {
resp.WriteUint8(q.Unk0)
resp.WriteUint8(q.Unk1)
resp.WriteUint32(q.StartTime)
resp.WriteUint32(q.EndTime)
resp.WriteUint32(q.Unk4)
resp.WriteUint8(q.Unk5)
resp.WriteUint8(q.Unk6)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}

View File

@@ -0,0 +1,327 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysOperateRegister)
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
s.server.raviente.Lock()
if pkt.SemaphoreID == s.server.raviente.state.semaphoreID {
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
op := bf.ReadUint8()
dest := bf.ReadUint8()
data := bf.ReadUint32()
resp.WriteUint8(1)
resp.WriteUint8(dest)
ref := &s.server.raviente.state.stateData[dest]
damageMultiplier := s.server.raviente.state.damageMultiplier
switch op {
case 2:
resp.WriteUint32(*ref)
if dest == 28 { // Berserk resurrection tracker
resp.WriteUint32(*ref + data)
*ref += data
} else if dest == 17 { // Berserk poison tracker
if damageMultiplier == 1 {
resp.WriteUint32(*ref + data)
*ref += data
} else {
resp.WriteUint32(*ref + data)
}
} else {
resp.WriteUint32(*ref + data*damageMultiplier)
*ref += data * damageMultiplier
}
case 13:
fallthrough
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
}
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
} else if pkt.SemaphoreID == s.server.raviente.support.semaphoreID {
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
op := bf.ReadUint8()
dest := bf.ReadUint8()
data := bf.ReadUint32()
resp.WriteUint8(1)
resp.WriteUint8(dest)
ref := &s.server.raviente.support.supportData[dest]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + data)
*ref += data
case 13:
fallthrough
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
}
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
} else if pkt.SemaphoreID == s.server.raviente.register.semaphoreID {
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
op := bf.ReadUint8()
dest := bf.ReadUint8()
data := bf.ReadUint32()
resp.WriteUint8(1)
resp.WriteUint8(dest)
switch dest {
case 0:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.nextTime = data
case 1:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.startTime = data
case 2:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.killedTime = data
case 3:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.postTime = data
case 4:
ref := &s.server.raviente.register.register[0]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + uint32(data))
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
case 5:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.carveQuest = data
case 6:
ref := &s.server.raviente.register.register[1]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + uint32(data))
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
case 7:
ref := &s.server.raviente.register.register[2]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + uint32(data))
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
case 8:
ref := &s.server.raviente.register.register[3]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + uint32(data))
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
case 9:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.maxPlayers = data
case 10:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.ravienteType = data
case 11:
ref := &s.server.raviente.register.register[4]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + uint32(data))
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
default:
resp.WriteUint32(0)
resp.WriteUint32(0)
}
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
s.notifyall()
s.server.raviente.Unlock()
}
func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLoadRegister)
r := pkt.Unk1
switch r {
case 12:
resp := byteframe.NewByteFrame()
resp.WriteUint8(0)
resp.WriteUint8(12)
resp.WriteUint32(s.server.raviente.register.nextTime)
resp.WriteUint32(s.server.raviente.register.startTime)
resp.WriteUint32(s.server.raviente.register.killedTime)
resp.WriteUint32(s.server.raviente.register.postTime)
resp.WriteUint32(s.server.raviente.register.register[0])
resp.WriteUint32(s.server.raviente.register.carveQuest)
resp.WriteUint32(s.server.raviente.register.register[1])
resp.WriteUint32(s.server.raviente.register.register[2])
resp.WriteUint32(s.server.raviente.register.register[3])
resp.WriteUint32(s.server.raviente.register.maxPlayers)
resp.WriteUint32(s.server.raviente.register.ravienteType)
resp.WriteUint32(s.server.raviente.register.register[4])
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 29:
resp := byteframe.NewByteFrame()
resp.WriteUint8(0)
resp.WriteUint8(29)
for _, v := range s.server.raviente.state.stateData {
resp.WriteUint32(v)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 25:
resp := byteframe.NewByteFrame()
resp.WriteUint8(0)
resp.WriteUint8(25)
for _, v := range s.server.raviente.support.supportData {
resp.WriteUint32(v)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
}
func (s *Session) notifyall() {
var temp mhfpacket.MHFPacket
raviNotif := byteframe.NewByteFrame()
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: s.server.raviente.support.semaphoreID}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: s.server.raviente.state.semaphoreID}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: s.server.raviente.register.semaphoreID}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
raviNotif.WriteUint16(0x0010) // End it.
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
for session := range s.server.semaphore["hs_l0u3B51J9k3"].clients {
session.QueueSend(raviNotif.Data())
}
} else if _, exists := s.server.semaphore["hs_l0u3B5129k3"]; exists {
for session := range s.server.semaphore["hs_l0u3B5129k3"].clients {
session.QueueSend(raviNotif.Data())
}
} else if _, exists := s.server.semaphore["hs_l0u3B512Ak3"]; exists {
for session := range s.server.semaphore["hs_l0u3B512Ak3"].clients {
session.QueueSend(raviNotif.Data())
}
}
}
func checkRaviSemaphore(s *Session) bool {
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
return true
} else if _, exists := s.server.semaphore["hs_l0u3B5129k3"]; exists {
return true
} else if _, exists := s.server.semaphore["hs_l0u3B512Ak3"]; exists {
return true
}
return false
}
//func releaseRaviSemaphore(s *Session) {
// s.server.raviente.Lock()
// if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
// if len(s.server.semaphore["hs_l0u3B51J9k3"].reservedClientSlots) == 0 {
// resetRavi(s)
// }
// }
// if _, exists := s.server.semaphore["hs_l0u3B5129k3"]; exists {
// if len(s.server.semaphore["hs_l0u3B5129k3"].reservedClientSlots) == 0 {
// resetRavi(s)
// }
// }
// if _, exists := s.server.semaphore["hs_l0u3B512Ak3"]; exists {
// if len(s.server.semaphore["hs_l0u3B512Ak3"].reservedClientSlots) == 0 {
// resetRavi(s)
// }
// }
// s.server.raviente.Unlock()
//}
func resetRavi(s *Session) {
s.server.raviente.Lock()
s.server.raviente.register.nextTime = 0
s.server.raviente.register.startTime = 0
s.server.raviente.register.killedTime = 0
s.server.raviente.register.postTime = 0
s.server.raviente.register.ravienteType = 0
s.server.raviente.register.maxPlayers = 0
s.server.raviente.register.carveQuest = 0
s.server.raviente.state.damageMultiplier = 1
s.server.raviente.register.register = []uint32{0, 0, 0, 0, 0}
s.server.raviente.state.stateData = []uint32{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.raviente.support.supportData = []uint32{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.raviente.Unlock()
}
// Unused
func (s *Session) notifyticker() {
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
s.server.semaphoreLock.Lock()
getSemaphore := s.server.semaphore["hs_l0u3B51J9k3"]
s.server.semaphoreLock.Unlock()
if _, exists := getSemaphore.reservedClientSlots[s.charID]; exists {
s.notifyall()
}
}
}
func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,96 @@
package channelserver
import (
"io/ioutil"
"path/filepath"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
// saved every floor on road, holds values such as floors progressed, points etc.
// can be safely handled by the client
pkt := p.(*mhfpacket.MsgMhfSaveRengokuData)
_, err := s.server.db.Exec("UPDATE characters SET rengokudata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Fatal("Failed to update rengokudata savedata in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadRengokuData)
var data []byte
err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get rengokudata savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint16(0)
resp.WriteUint32(0)
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint32(0)
resp.WriteUint32(0) // an extra 4 bytes were missing based on pcaps
resp.WriteUint8(3) // Count of next 3
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint8(3) // Count of next 3
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint8(3) // Count of next 3
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
}
func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRengokuBinary)
// a (massively out of date) version resides in the game's /dat/ folder or up to date can be pulled from packets
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "rengoku_data.bin"))
if err != nil {
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking)
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetRengokuRankingRank(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRengokuRankingRank)
resp := byteframe.NewByteFrame()
resp.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}

View File

@@ -0,0 +1,129 @@
package channelserver
import "erupe-ce/network/mhfpacket"
func handleMsgSysReserve188(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysReserve188)
// Left as raw bytes because I couldn't easily find the request or resp parser function in the binary.
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgSysReserve18B(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysReserve18B)
// Left as raw bytes because I couldn't easily find the request or resp parser function in the binary.
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x3C})
}
func handleMsgSysReserve55(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve56(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve57(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve01(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve02(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve03(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve04(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve05(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve06(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve07(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve0C(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve0D(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve0E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4A(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4B(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4C(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4D(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve5C(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve5E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve5F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve71(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve72(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve73(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve74(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve75(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve76(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve77(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve78(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve79(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve7A(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve7B(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve7C(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve7E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfReserve10F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve180(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve18E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve18F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve19E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve19F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A4(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A6(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A7(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A8(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A9(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AA(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AB(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AC(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AD(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AE(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AF(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve19B(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve192(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve193(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve194(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,49 @@
package channelserver
import (
"encoding/hex"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfGetAdditionalBeatReward(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetAdditionalBeatReward)
// Actual response in packet captures are all just giant batches of null bytes
// I'm assuming this is because it used to be tied to an actual event and
// they never bothered killing off the packet when they made it static
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x104))
}
func handleMsgMhfGetUdRankingRewardList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdRankingRewardList)
// Temporary canned response
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfGetRewardSong(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRewardSong)
// Temporary canned response
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfUseRewardSong(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAddRewardSongCount(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAcquireMonthlyReward(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireMonthlyReward)
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfAcceptReadReward(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,142 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"fmt"
"go.uber.org/zap"
"strings"
"erupe-ce/network/mhfpacket"
)
func removeSessionFromSemaphore(s *Session) {
s.server.semaphoreLock.Lock()
for _, semaphore := range s.server.semaphore {
if _, exists := semaphore.reservedClientSlots[s.charID]; exists {
delete(semaphore.reservedClientSlots, s.charID)
}
if _, exists := semaphore.clients[s]; exists {
delete(semaphore.clients, s)
}
}
s.server.semaphoreLock.Unlock()
}
func handleMsgSysCreateSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateSemaphore)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x03, 0x00, 0x0d})
}
func destructEmptySemaphores(s *Session) {
s.server.semaphoreLock.Lock()
for id, sema := range s.server.semaphore {
if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 {
s.server.semaphoreLock.Unlock()
delete(s.server.semaphore, id)
s.server.semaphoreLock.Lock()
if strings.HasPrefix(id, "hs_l0u3B51") {
releaseRaviSemaphore(s, sema)
}
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
}
}
s.server.semaphoreLock.Unlock()
}
func releaseRaviSemaphore(s *Session, sema *Semaphore) {
if !strings.HasSuffix(sema.id_semaphore, "5") {
delete(sema.reservedClientSlots, s.charID)
delete(sema.clients, s)
}
if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 {
s.logger.Debug("Raviente semaphore is empty, resetting")
resetRavi(s)
}
}
func handleMsgSysDeleteSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysDeleteSemaphore)
sem := pkt.AckHandle
if s.server.semaphore != nil {
destructEmptySemaphores(s)
s.server.semaphoreLock.Lock()
for id, sema := range s.server.semaphore {
if sema.id == sem {
if strings.HasPrefix(id, "hs_l0u3B51") {
releaseRaviSemaphore(s, sema)
s.server.semaphoreLock.Unlock()
return
}
s.server.semaphoreLock.Unlock()
delete(s.server.semaphore, id)
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
return
}
}
s.server.semaphoreLock.Unlock()
}
}
func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateAcquireSemaphore)
SemaphoreID := pkt.SemaphoreID
newSemaphore, exists := s.server.semaphore[SemaphoreID]
fmt.Printf("Got reserve stage req, StageID: %v\n\n", SemaphoreID)
if !exists {
s.server.semaphoreLock.Lock()
if strings.HasPrefix(SemaphoreID, "hs_l0u3B51") {
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 32)
if strings.HasSuffix(SemaphoreID, "3") {
s.server.raviente.state.semaphoreID = s.server.semaphore[SemaphoreID].id
} else if strings.HasSuffix(SemaphoreID, "4") {
s.server.raviente.support.semaphoreID = s.server.semaphore[SemaphoreID].id
} else if strings.HasSuffix(SemaphoreID, "5") {
s.server.raviente.register.semaphoreID = s.server.semaphore[SemaphoreID].id
}
} else {
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1)
}
newSemaphore = s.server.semaphore[SemaphoreID]
s.server.semaphoreLock.Unlock()
}
newSemaphore.Lock()
defer newSemaphore.Unlock()
if _, exists := newSemaphore.reservedClientSlots[s.charID]; exists {
bf := byteframe.NewByteFrame()
bf.WriteUint32(newSemaphore.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} else if uint16(len(newSemaphore.reservedClientSlots)) < newSemaphore.maxPlayers {
newSemaphore.reservedClientSlots[s.charID] = nil
newSemaphore.clients[s] = s.charID
s.Lock()
s.semaphore = newSemaphore
s.Unlock()
bf := byteframe.NewByteFrame()
bf.WriteUint32(newSemaphore.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} else {
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
//pkt := p.(*mhfpacket.MsgSysAcquireSemaphore)
}
func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) {
//pkt := p.(*mhfpacket.MsgSysReleaseSemaphore)
}
func handleMsgSysCheckSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCheckSemaphore)
resp := []byte{0x00, 0x00, 0x00, 0x00}
s.server.semaphoreLock.Lock()
if _, exists := s.server.semaphore[pkt.SemaphoreID]; exists {
resp = []byte{0x00, 0x00, 0x00, 0x01}
}
s.server.semaphoreLock.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, resp)
}

View File

@@ -0,0 +1,738 @@
package channelserver
import (
"encoding/hex"
"fmt"
"strings"
"time"
//"erupe-ce/common/stringsupport"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"github.com/lib/pq"
"github.com/sachaos/lottery"
"go.uber.org/zap"
)
func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateShop)
// SHOP TYPES:
// 01 = Running Gachas, 02 = actual gacha, 04 = N Points, 05 = GCP, 07 = Item to GCP, 08 = Diva Defense, 10 = Hunter's Road
// GACHA FORMAT:
// int32: gacha id
// STORE FORMAT:
// Int16: total item count
// Int16: total item count
// ITEM FORMAT:
// int32: Unique item hash for tracking purchases
// int16: padding?
// int16: Item ID
// int16: padding?
// int16: GCP returns
// int16: Number traded at once
// int16: HR or SR Requirement
// int16: Whichever of the above it isn't
// int16: GR Requirement
// int16: Store level requirement
// int16: Maximum quantity purchasable
// int16: Unk
// int16: Road floors cleared requirement
// int16: Road White Fatalis weekly kills
if pkt.ShopType == 2 {
shopEntries, err := s.server.db.Query("SELECT entryType, itemhash, currType, currNumber, currQuant, percentage, rarityIcon, rollsCount, itemCount, dailyLimit, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1", pkt.ShopID)
if err != nil {
panic(err)
}
var entryType, currType, rarityIcon, rollsCount, itemCount, dailyLimit byte
var currQuant, currNumber, percentage uint16
var itemhash uint32
var itemType, itemId, quantity pq.Int64Array
var entryCount int
resp := byteframe.NewByteFrame()
resp.WriteUint32(pkt.ShopID)
resp.WriteUint16(0) // total defs
for shopEntries.Next() {
err = shopEntries.Scan(&entryType, &itemhash, &currType, &currNumber, &currQuant, &percentage, &rarityIcon, &rollsCount, &itemCount, &dailyLimit, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
if err != nil {
panic(err)
}
resp.WriteUint8(entryType)
resp.WriteUint32(itemhash)
resp.WriteUint8(currType)
resp.WriteUint16(0) // unk, always 0 in existing packets
resp.WriteUint16(currNumber) // it's either item ID or quantity for gacha coins
resp.WriteUint16(currQuant) // only for item ID
resp.WriteUint16(percentage)
resp.WriteUint8(rarityIcon)
resp.WriteUint8(rollsCount)
resp.WriteUint8(itemCount)
resp.WriteUint16(0) // unk, always 0 in existing packets
resp.WriteUint8(dailyLimit)
resp.WriteUint8(0) // unk, always 0 in existing packets
for i := 0; i < int(itemCount); i++ {
resp.WriteUint16(uint16(itemType[i])) // unk, always 0 in existing packets
resp.WriteUint16(uint16(itemId[i])) // unk, always 0 in existing packets
resp.WriteUint16(uint16(quantity[i])) // unk, always 0 in existing packets
}
entryCount++
}
if entryCount == 0 {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
resp.Seek(4, 0)
resp.WriteUint16(uint16(entryCount))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
} else if pkt.ShopType == 1 {
gachaCount := 0
shopEntries, err := s.server.db.Query("SELECT hash, reqGR, reqHR, gachaName, gachaLink0, gachaLink1, COALESCE(gachaLink2, ''), extraIcon, gachaType, hideFlag FROM gacha_shop")
if err != nil {
panic(err)
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
var gachaName, gachaLink0, gachaLink1, gachaLink2 string
var hash, reqGR, reqHR, extraIcon, gachaType int
var hideFlag bool
for shopEntries.Next() {
err = shopEntries.Scan(&hash, &reqGR, &reqHR, &gachaName, &gachaLink0, &gachaLink1, &gachaLink2, &extraIcon, &gachaType, &hideFlag)
if err != nil {
panic(err)
}
resp.WriteUint32(uint32(hash))
resp.WriteUint32(0) // only 0 in known packets
resp.WriteUint32(0) // all of these seem to trigger the 'rank restriction'
resp.WriteUint32(0) // message so they are presumably placeholders for a
resp.WriteUint32(0) // Z Rank or similar that never turned up?
resp.WriteUint32(uint32(reqGR))
resp.WriteUint32(uint32(reqHR))
resp.WriteUint32(0) // only 0 in known packet
stringBytes := append([]byte(gachaName), 0x00)
resp.WriteUint8(byte(len(stringBytes)))
resp.WriteBytes(stringBytes)
stringBytes = append([]byte(gachaLink0), 0x00)
resp.WriteUint8(byte(len(stringBytes)))
resp.WriteBytes(stringBytes)
stringBytes = append([]byte(gachaLink1), 0x00)
resp.WriteUint8(byte(len(stringBytes)))
resp.WriteBytes(stringBytes)
stringBytes = append([]byte(gachaLink2), 0x00)
resp.WriteBool(hideFlag)
resp.WriteUint8(uint8(len(stringBytes)))
resp.WriteBytes(stringBytes)
resp.WriteUint16(uint16(extraIcon))
resp.WriteUint16(uint16(gachaType))
gachaCount++
}
resp.Seek(0, 0)
resp.WriteUint16(uint16(gachaCount))
resp.WriteUint16(uint16(gachaCount))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
} else if pkt.ShopType == 7 {
// GCP conversion store
if pkt.ShopID == 0 {
// Items to GCP exchange. Gou Tickets, Shiten Tickets, GP Tickets
data, _ := hex.DecodeString("000300033a9186fb000033860000000a000100000000000000000000000000000000097fdb1c0000067e0000000a0001000000000000000000000000000000001374db29000027c300000064000100000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
} else if pkt.ShopType == 8 {
// Dive Defense sections
// 00 = normal level limited exchange store, 05 = GCP skill store, 07 = limited quantity exchange
if pkt.ShopID == 5 {
// diva defense skill level limited store
data, _ := hex.DecodeString("001f001f2c9365c1000000010000001e000a0000000000000000000a0000000000001979f1c2000000020000003c000a0000000000000000000a0000000000003e5197df000000030000003c000a0000000000000000000a000000000000219337c0000000040000001e000a0000000000000000000a00000000000009b24c9d000000140000001e000a0000000000000000000a0000000000001f1d496e000000150000001e000a0000000000000000000a0000000000003b918fcb000000160000003c000a0000000000000000000a0000000000000b7fd81c000000170000003c000a0000000000000000000a0000000000001374f239000000180000003c000a0000000000000000000a00000000000026950cba0000001c0000003c000a0000000000000000000a0000000000003797eae70000001d0000003c000a012b000000000000000a00000000000015758ad8000000050000003c00000000000000000000000a0000000000003c7035050000000600000050000a0000000000000001000a00000000000024f3b5560000000700000050000a0000000000000001000a00000000000000b600330000000800000050000a0000000000000001000a0000000000002efdce840000001900000050000a0000000000000001000a0000000000002d9365f10000001a00000050000a0000000000000001000a0000000000001979f3420000001f00000050000a012b000000000001000a0000000000003f5397cf0000002000000050000a012b000000000001000a000000000000319337c00000002100000050000a012b000000000001000a00000000000008b04cbd0000000900000064000a0000000000000002000a0000000000000b1d4b6e0000000a00000064000a0000000000000002000a0000000000003b918feb0000000b00000064000a0000000000000002000a0000000000001b7fd81c0000000c00000064000a0000000000000002000a0000000000001276f2290000000d00000064000a0000000000000002000a00000000000022950cba0000000e000000c8000a0000000000000002000a0000000000003697ead70000000f000001f4000a0000000000000003000a00000000000005758a5800000010000003e8000a0000000000000003000a0000000000003c7035250000001b000001f4000a0000000000010003000a00000000000034f3b5d60000001e00000064000a012b000000000003000a00000000000000b600030000002200000064000a0000000000010003000a000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
} else {
_, week := time.Now().ISOWeek()
season := fmt.Sprintf("%d", week%4)
shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills, COALESCE(enable_weeks, '') FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID)
if err != nil {
panic(err)
}
var ItemHash, entryCount int
var itemID, Points, TradeQuantity, rankReqLow, rankReqHigh, rankReqG, storeLevelReq, maximumQuantity, boughtQuantity, roadFloorsRequired, weeklyFatalisKills, charQuantity uint16
var itemWeeks string
resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // total defs
for shopEntries.Next() {
err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills, &itemWeeks)
if err != nil {
panic(err)
}
if len(itemWeeks) > 0 && !contains(strings.Split(itemWeeks, ","), season) {
continue
}
resp.WriteUint32(uint32(ItemHash))
resp.WriteUint16(0) // unk, always 0 in existing packets
resp.WriteUint16(itemID)
resp.WriteUint16(0) // unk, always 0 in existing packets
resp.WriteUint16(Points) // it's either item ID or quantity for gacha coins
resp.WriteUint16(TradeQuantity) // only for item ID
resp.WriteUint16(rankReqLow)
resp.WriteUint16(rankReqHigh)
resp.WriteUint16(rankReqG)
resp.WriteUint16(storeLevelReq)
resp.WriteUint16(maximumQuantity)
if maximumQuantity > 0 {
var itemWeek int
err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0), COALESCE(week,-1) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity, &itemWeek)
if err != nil {
resp.WriteUint16(0)
} else if pkt.ShopID == 7 && itemWeek >= 0 && itemWeek != week {
clearShopItemState(s, s.charID, uint32(ItemHash))
resp.WriteUint16(0)
} else {
resp.WriteUint16(charQuantity)
}
} else {
resp.WriteUint16(boughtQuantity)
}
resp.WriteUint16(roadFloorsRequired)
resp.WriteUint16(weeklyFatalisKills)
entryCount++
}
if entryCount == 0 {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
resp.Seek(0, 0)
resp.WriteUint16(uint16(entryCount))
resp.WriteUint16(uint16(entryCount))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
}
func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
_, week := time.Now().ISOWeek()
// writing out to an editable shop enumeration
pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop)
if pkt.DataSize == 10 {
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
_ = bf.ReadUint16() // unk, always 1 in examples
itemHash := bf.ReadUint32()
buyCount := bf.ReadUint32()
_, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity, week)
VALUES ($1,$2,$3,$4) ON CONFLICT (char_id, itemhash)
DO UPDATE SET usedquantity = shop_item_state.usedquantity + $3
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount, week)
if err != nil {
s.logger.Fatal("Failed to update shop_item_state in db", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func clearShopItemState(s *Session, charId uint32, itemHash uint32) {
_, err := s.server.db.Exec(`DELETE FROM shop_item_state WHERE char_id=$1 AND itemhash=$2`, charId, itemHash)
if err != nil {
s.logger.Fatal("Failed to delete shop_item_state in db", zap.Error(err))
}
}
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
// returns number of times the gacha was played, will need persistent db stuff
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)
doAckBufSucceed(s, pkt.AckHandle, []byte{0x0A})
}
func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGachaPoint)
var fp, gp, gt uint32
_ = s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_prem, 0), COALESCE(gacha_trial,0) FROM characters WHERE id=$1", s.charID).Scan(&fp, &gp, &gt)
resp := byteframe.NewByteFrame()
resp.WriteUint32(gp) // Real Gacha Points?
resp.WriteUint32(gt) // Trial Gacha Point?
resp.WriteUint32(fp) // Frontier Points?
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
type gachaItem struct {
itemhash uint32
percentage uint16
rarityIcon byte
itemCount byte
itemType pq.Int64Array
itemId pq.Int64Array
quantity pq.Int64Array
}
func (i gachaItem) Weight() int {
return int(i.percentage)
}
func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha)
// needs to query db for input gacha and return a result or number of results
// uint8 number of results
// uint8 item type
// uint16 item id
// uint16 quantity
var currType, rarityIcon, rollsCount, itemCount byte
var currQuant, currNumber, percentage uint16
var itemhash uint32
var itemType, itemId, quantity pq.Int64Array
var items []lottery.Weighter
// get info for updating data and calculating costs
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount)
if err != nil {
panic(err)
}
// get existing items in storage if any
var data []byte
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
if len(data) == 0 {
data = []byte{0x00}
}
// get gacha items and iterate through them for gacha roll
shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash)
if err != nil {
panic(err)
}
for shopEntries.Next() {
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
if err != nil {
panic(err)
}
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
}
// execute rolls, build response and update database
results := byte(0)
resp := byteframe.NewByteFrame()
dbUpdate := byteframe.NewByteFrame()
resp.WriteUint8(0) // results go here later
l := lottery.NewDefaultLottery()
for x := 0; x < int(rollsCount); x++ {
ind := l.Draw(items)
results += items[ind].(*gachaItem).itemCount
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
// items in storage don't get rarity
dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
data = append(data, dbUpdate.Data()...)
dbUpdate.Seek(0, 0)
// response needs all item info and the rarity
resp.WriteBytes(dbUpdate.Data())
resp.WriteUint8(uint8(items[ind].(*gachaItem).rarityIcon))
}
}
resp.Seek(0, 0)
resp.WriteUint8(uint8(results))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
// add claimables to DB
data[0] = data[0] + results
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
if err != nil {
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
}
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
if currType == 19 {
_, err = s.server.db.Exec("UPDATE characters SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end WHERE id=$2", currNumber, s.charID)
}
if err != nil {
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
}
}
func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) {
// should write to database when that's set up
pkt := p.(*mhfpacket.MsgMhfUseGachaPoint)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
var itemValue, quant int
_ = s.server.db.QueryRow("SELECT quant, itemValue FROM fpoint_items WHERE hash=$1", pkt.ItemHash).Scan(&quant, &itemValue)
itemCost := (int(pkt.Quantity) * quant) * itemValue
// also update frontierpoints entry in database
_, err := s.server.db.Exec("UPDATE characters SET frontier_points=frontier_points::int - $1 WHERE id=$2", itemCost, s.charID)
if err != nil {
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
var itemValue, quant int
_ = s.server.db.QueryRow("SELECT quant, itemValue FROM fpoint_items WHERE hash=$1", pkt.ItemHash).Scan(&quant, &itemValue)
itemCost := (int(pkt.Quantity) / quant) * itemValue
// also update frontierpoints entry in database
_, err := s.server.db.Exec("UPDATE characters SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2", itemCost, s.charID)
if err != nil {
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetFpointExchangeList)
//absurd, probably lists every single item to trade to FP?
var buyables int
var sellables int
buyRows, err := s.server.db.Query("SELECT hash,itemType,itemID,quant,itemValue FROM fpoint_items WHERE tradeType=0")
if err != nil {
panic(err)
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
var hash, itemType, itemID, quant, itemValue int
for buyRows.Next() {
err = buyRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue)
if err != nil {
panic("Error in fpoint_items")
}
resp.WriteUint32(uint32(hash))
resp.WriteUint32(0) // this and following only 0 in known packets
resp.WriteUint16(0)
resp.WriteUint8(byte(itemType))
resp.WriteUint16(uint16(itemID))
resp.WriteUint16(uint16(quant))
resp.WriteUint16(uint16(itemValue))
buyables++
}
sellRows, err := s.server.db.Query("SELECT hash,itemType,itemID,quant,itemValue FROM fpoint_items WHERE tradeType=1")
if err != nil {
panic(err)
}
for sellRows.Next() {
err = sellRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue)
if err != nil {
panic("Error in fpoint_items")
}
resp.WriteUint32(uint32(hash))
resp.WriteUint32(0) // this and following only 0 in known packets
resp.WriteUint16(0)
resp.WriteUint8(byte(itemType))
resp.WriteUint16(uint16(itemID))
resp.WriteUint16(uint16(quant))
resp.WriteUint16(uint16(itemValue))
sellables++
}
resp.Seek(0, 0)
resp.WriteUint16(uint16(sellables))
resp.WriteUint16(uint16(buyables))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
results := byte(0)
stepResults := byte(0)
resp := byteframe.NewByteFrame()
rollFrame := byteframe.NewByteFrame()
stepFrame := byteframe.NewByteFrame()
stepData := []byte{}
var currType, rarityIcon, rollsCount, itemCount byte
var currQuant, currNumber, percentage uint16
var itemhash uint32
var itemType, itemId, quantity pq.Int64Array
var items []lottery.Weighter
// get info for updating data and calculating costs
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
if err != nil {
panic(err)
}
// get existing items in storage if any
var data []byte
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
if len(data) == 0 {
data = []byte{0x00}
}
// roll definition includes items with step up gachas that are appended last
for x := 0; x < int(itemCount); x++ {
stepFrame.WriteUint8(uint8(itemType[x]))
stepFrame.WriteUint16(uint16(itemId[x]))
stepFrame.WriteUint16(uint16(quantity[x]))
stepData = append(stepData, stepFrame.Data()...)
stepFrame.WriteUint8(0) // rarity still defined
stepResults++
}
// get gacha items and iterate through them for gacha roll
shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash)
if err != nil {
panic(err)
}
for shopEntries.Next() {
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
if err != nil {
panic(err)
}
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
}
// execute rolls, build response and update database
resp.WriteUint16(0) // results count goes here later
l := lottery.NewDefaultLottery()
for x := 0; x < int(rollsCount); x++ {
ind := l.Draw(items)
results += items[ind].(*gachaItem).itemCount
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
// items in storage don't get rarity
rollFrame.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
data = append(data, rollFrame.Data()...)
rollFrame.Seek(0, 0)
// response needs all item info and the rarity
resp.WriteBytes(rollFrame.Data())
resp.WriteUint8(uint8(items[ind].(*gachaItem).rarityIcon))
}
}
resp.WriteBytes(stepFrame.Data())
resp.Seek(0, 0)
resp.WriteUint8(uint8(results + stepResults))
resp.WriteUint8(uint8(results))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
// add claimables to DB
data = append(data, stepData...)
data[0] = data[0] + results + stepResults
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
if err != nil {
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
}
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
// reduce real if trial don't cover cost
if currType == 19 {
_, err = s.server.db.Exec(`UPDATE characters
SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end,
gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end
WHERE id=$2`, currNumber, s.charID)
}
if err != nil {
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
}
// update step progression
_, err = s.server.db.Exec("UPDATE stepup_state SET step_progression = $1 WHERE char_id = $2", pkt.RollType+1, s.charID)
if err != nil {
s.logger.Fatal("Failed to update step_progression in db", zap.Error(err))
}
}
func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem)
// persistent for claimable items on cat
var data []byte
err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data)
if err != nil {
panic("Failed to get gacha_items")
}
// limit of 36 items are returned
if data[0] > 36 {
outData := make([]byte, 181)
copy(outData, data[0:181])
outData[0] = byte(36)
saveData := append(data[:1], data[181:]...)
saveData[0] = saveData[0] - 36
doAckBufSucceed(s, pkt.AckHandle, outData)
if pkt.Unk0 != 0x2401 {
_, err := s.server.db.Exec("UPDATE characters SET gacha_items = $2 WHERE id = $1", s.charID, saveData)
if err != nil {
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
}
}
} else {
doAckBufSucceed(s, pkt.AckHandle, data)
if pkt.Unk0 != 0x2401 {
_, err := s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID)
if err != nil {
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
}
}
}
}
func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetStepupStatus)
// get the reset time from db
var step_progression int
var step_time time.Time
err := s.server.db.QueryRow(`SELECT COALESCE(step_progression, 0), COALESCE(step_time, $1) FROM stepup_state WHERE char_id = $2 AND shophash = $3`, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), s.charID, pkt.GachaHash).Scan(&step_progression, &step_time)
if err != nil {
s.logger.Fatal("Failed to Select coalesce in db", zap.Error(err))
}
// calculate next midday
var t = time.Now().In(time.FixedZone("UTC+9", 9*60*60))
year, month, day := t.Date()
midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location())
if t.After(midday) {
midday = midday.Add(24 * time.Hour)
}
// after midday or not set
if t.After(step_time) {
step_progression = 0
}
_, err = s.server.db.Exec(`INSERT INTO stepup_state (shophash, step_progression, step_time, char_id)
VALUES ($1,$2,$3,$4) ON CONFLICT (shophash, char_id)
DO UPDATE SET step_progression=$2, step_time=$3
WHERE EXCLUDED.char_id=$4 AND EXCLUDED.shophash=$1`, pkt.GachaHash, step_progression, midday, s.charID)
if err != nil {
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(step_progression))
resp.WriteUint32(uint32(time.Now().In(time.FixedZone("UTC+9", 9*60*60)).Unix()))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) {
// not sure this is used anywhere, free gachas use the MSG_MHF_PLAY_NORMAL_GACHA method in captures
}
func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo)
count := 0
var used_itemhash pq.Int64Array
// pull array of used values
// single sized respone with 0x00 is a valid with no items present
_ = s.server.db.QueryRow("SELECT used_itemhash FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID).Scan((*pq.Int64Array)(&used_itemhash))
resp := byteframe.NewByteFrame()
resp.WriteUint8(0)
for ind := range used_itemhash {
resp.WriteUint32(uint32(used_itemhash[ind]))
resp.WriteUint8(1)
count++
}
resp.Seek(0, 0)
resp.WriteUint8(uint8(count))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha)
// needs to query db for input gacha and return a result or number of results
// uint8 number of results
// uint8 item type
// uint16 item id
// uint16 quantity
var currType, rarityIcon, rollsCount, itemCount byte
var currQuant, currNumber, percentage uint16
var itemhash uint32
var itemType, itemId, quantity, usedItemHash pq.Int64Array
var items []lottery.Weighter
// get info for updating data and calculating costs
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount)
if err != nil {
panic(err)
}
// get existing items in storage if any
var data []byte
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
if len(data) == 0 {
data = []byte{0x00}
}
// get gacha items and iterate through them for gacha roll
shopEntries, err := s.server.db.Query(`SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
FROM gacha_shop_items
WHERE shophash=$1 AND entryType=100
EXCEPT ALL SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
FROM gacha_shop_items gsi JOIN lucky_box_state lbs ON gsi.itemhash = ANY(lbs.used_itemhash)
WHERE lbs.char_id=$2`, pkt.GachaHash, s.charID)
if err != nil {
panic(err)
}
for shopEntries.Next() {
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
if err != nil {
panic(err)
}
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
}
// execute rolls, build response and update database
results := byte(0)
resp := byteframe.NewByteFrame()
dbUpdate := byteframe.NewByteFrame()
resp.WriteUint8(0) // results go here later
l := lottery.NewDefaultLottery()
for x := 0; x < int(rollsCount); x++ {
ind := l.Draw(items)
results += items[ind].(*gachaItem).itemCount
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
// items in storage don't get rarity
dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
data = append(data, dbUpdate.Data()...)
dbUpdate.Seek(0, 0)
// response needs all item info and the rarity
resp.WriteBytes(dbUpdate.Data())
resp.WriteUint8(uint8(items[ind].(*gachaItem).rarityIcon))
usedItemHash = append(usedItemHash, int64(items[ind].(*gachaItem).itemhash))
}
// remove rolled
items = append(items[:ind], items[ind+1:]...)
}
resp.Seek(0, 0)
resp.WriteUint8(uint8(results))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
// add claimables to DB
data[0] = data[0] + results
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
if err != nil {
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
}
// update lucky_box_state
_, err = s.server.db.Exec(` INSERT INTO lucky_box_state (char_id, shophash, used_itemhash)
VALUES ($1,$2,$3) ON CONFLICT (char_id, shophash)
DO UPDATE SET used_itemhash = COALESCE(lucky_box_state.used_itemhash::int[] || $3::int[], $3::int[])
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.shophash=$2`, s.charID, pkt.GachaHash, usedItemHash)
if err != nil {
s.logger.Fatal("Failed to update lucky box state in db", zap.Error(err))
}
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
if currType == 19 {
_, err = s.server.db.Exec(` UPDATE characters
SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end
WHERE id=$2`, currNumber, s.charID)
}
if err != nil {
s.logger.Fatal("Failed to update gacha_trial in db", zap.Error(err))
}
}
func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo)
_, err := s.server.db.Exec("DELETE FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID)
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}

View File

@@ -0,0 +1,364 @@
package channelserver
import (
"time"
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgSysCreateStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateStage)
s.server.Lock()
defer s.server.Unlock()
if _, exists := s.server.stages[pkt.StageID]; exists {
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} else {
stage := NewStage(pkt.StageID)
stage.maxPlayers = uint16(pkt.PlayerCount)
s.server.stages[stage.id] = stage
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgSysStageDestruct(s *Session, p mhfpacket.MHFPacket) {}
func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
s.server.Lock()
stage, exists := s.server.stages[stageID]
s.server.Unlock()
if exists {
stage.Lock()
stage.clients[s] = s.charID
stage.Unlock()
} else { // Create new stage object
s.server.Lock()
s.server.stages[stageID] = NewStage(stageID)
stage = s.server.stages[stageID]
s.server.Unlock()
stage.Lock()
stage.clients[s] = s.charID
stage.Unlock()
}
// Ensure this session no longer belongs to reservations.
if s.stage != nil {
removeSessionFromStage(s)
}
// Save our new stage ID and pointer to the new stage itself.
s.Lock()
s.stageID = string(stageID)
s.stage = s.server.stages[stageID]
s.Unlock()
// Tell the client to cleanup its current stage objects.
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
// Confirm the stage entry.
doAckSimpleSucceed(s, ackHandle, []byte{0x00, 0x00, 0x00, 0x00})
if s.stage != nil { // avoids lock up when using bed for dream quests
// Notify the client to duplicate the existing objects.
s.logger.Info("Sending existing stage objects")
clientDupObjNotif := byteframe.NewByteFrame()
s.stage.RLock()
for _, obj := range s.stage.objects {
cur := &mhfpacket.MsgSysDuplicateObject{
ObjID: obj.id,
X: obj.x,
Y: obj.y,
Z: obj.z,
Unk0: 0,
OwnerCharID: obj.ownerCharID,
}
clientDupObjNotif.WriteUint16(uint16(cur.Opcode()))
cur.Build(clientDupObjNotif, s.clientContext)
}
s.stage.RUnlock()
clientDupObjNotif.WriteUint16(0x0010) // End it.
if len(clientDupObjNotif.Data()) > 2 {
s.QueueSend(clientDupObjNotif.Data())
}
}
}
func destructEmptyStages(s *Session) {
s.server.Lock()
defer s.server.Unlock()
for _, stage := range s.server.stages {
// Destroy empty Quest/My series/Guild stages.
if stage.id[3:5] == "Qs" || stage.id[3:5] == "Ms" || stage.id[3:5] == "Gs" {
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
delete(s.server.stages, stage.id)
s.logger.Debug("Destructed stage", zap.String("stage.id", stage.id))
}
}
}
}
func removeSessionFromStage(s *Session) {
// Remove client from old stage.
s.stage.Lock()
delete(s.stage.clients, s)
delete(s.stage.reservedClientSlots, s.charID)
// Delete old stage objects owned by the client.
s.logger.Info("Sending notification to old stage clients")
for _, object := range s.stage.objects {
if object.ownerCharID == s.charID {
s.stage.BroadcastMHF(&mhfpacket.MsgSysDeleteObject{ObjID: object.id}, s)
delete(s.stage.objects, object.ownerCharID)
}
}
s.stage.Unlock()
destructEmptyStages(s)
destructEmptySemaphores(s)
}
func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysEnterStage)
// Push our current stage ID to the movement stack before entering another one.
if s.stageID == "" {
s.stageMoveStack.Set(pkt.StageID)
} else {
s.stageMoveStack.Push(s.stageID)
s.stageMoveStack.Lock()
}
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
if s.reservationStage != nil {
s.reservationStage = nil
}
doStageTransfer(s, pkt.AckHandle, pkt.StageID)
}
func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysBackStage)
// Transfer back to the saved stage ID before the previous move or enter.
s.stageMoveStack.Unlock()
backStage, err := s.stageMoveStack.Pop()
if err != nil {
panic(err)
}
doStageTransfer(s, pkt.AckHandle, backStage)
}
func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysMoveStage)
// Set a new move stack from the given stage ID if unlocked
if !s.stageMoveStack.Locked {
s.stageMoveStack.Set(pkt.StageID)
}
doStageTransfer(s, pkt.AckHandle, pkt.StageID)
}
func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLockStage)
// TODO(Andoryuuta): What does this packet _actually_ do?
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
s.reservationStage.RLock()
defer s.reservationStage.RUnlock()
for charID := range s.reservationStage.reservedClientSlots {
session := s.server.FindSessionByCharID(charID)
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
}
s.server.Lock()
defer s.server.Unlock()
delete(s.server.stages, s.reservationStage.id)
}
func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysReserveStage)
if stage, exists := s.server.stages[pkt.StageID]; exists {
stage.Lock()
defer stage.Unlock()
if _, exists := stage.reservedClientSlots[s.charID]; exists {
switch pkt.Ready {
case 1: // 0x01
stage.reservedClientSlots[s.charID] = false
case 17: // 0x11
stage.reservedClientSlots[s.charID] = true
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers {
if len(stage.password) > 0 {
if stage.password != s.stagePass {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
}
stage.reservedClientSlots[s.charID] = false
// Save the reservation stage in the session for later use in MsgSysUnreserveStage.
s.Lock()
s.reservationStage = stage
s.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
} else {
s.logger.Error("Failed to get stage", zap.String("StageID", pkt.StageID))
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
}
func handleMsgSysUnreserveStage(s *Session, p mhfpacket.MHFPacket) {
s.Lock()
stage := s.reservationStage
s.reservationStage = nil
s.Unlock()
if stage != nil {
stage.Lock()
if _, exists := stage.reservedClientSlots[s.charID]; exists {
delete(stage.reservedClientSlots, s.charID)
}
stage.Unlock()
}
}
func handleMsgSysSetStagePass(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysSetStagePass)
s.Lock()
stage := s.reservationStage
s.Unlock()
if stage != nil {
stage.Lock()
// Will only exist if host.
if _, exists := stage.reservedClientSlots[s.charID]; exists {
stage.password = pkt.Password
}
stage.Unlock()
} else {
// Store for use on next ReserveStage.
s.Lock()
s.stagePass = pkt.Password
s.Unlock()
}
}
func handleMsgSysSetStageBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysSetStageBinary)
if stage, exists := s.server.stages[pkt.StageID]; exists {
stage.Lock()
stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}] = pkt.RawDataPayload
stage.Unlock()
} else {
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
}
}
func handleMsgSysGetStageBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysGetStageBinary)
if stage, exists := s.server.stages[pkt.StageID]; exists {
stage.Lock()
if binaryData, exists := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}]; exists {
doAckBufSucceed(s, pkt.AckHandle, binaryData)
} else if pkt.BinaryType1 == 4 {
// Unknown binary type that is supposedly generated server side
// Temporary response
doAckBufSucceed(s, pkt.AckHandle, []byte{})
} else {
s.logger.Warn("Failed to get stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
s.logger.Warn("Sending blank stage binary")
doAckBufSucceed(s, pkt.AckHandle, []byte{})
}
stage.Unlock()
} else {
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
}
s.logger.Debug("MsgSysGetStageBinary Done!")
}
func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysWaitStageBinary)
if stage, exists := s.server.stages[pkt.StageID]; exists {
if pkt.BinaryType0 == 1 && pkt.BinaryType1 == 12 {
// This might contain the hunter count, or max player count?
doAckBufSucceed(s, pkt.AckHandle, []byte{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
return
}
for {
s.logger.Debug("MsgSysWaitStageBinary before lock and get stage")
stage.Lock()
stageBinary, gotBinary := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}]
stage.Unlock()
s.logger.Debug("MsgSysWaitStageBinary after lock and get stage")
if gotBinary {
doAckBufSucceed(s, pkt.AckHandle, stageBinary)
break
} else {
s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
time.Sleep(1 * time.Second)
continue
}
}
} else {
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
}
s.logger.Debug("MsgSysWaitStageBinary Done!")
}
func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysEnumerateStage)
// Read-lock the server stage map.
s.server.stagesLock.RLock()
defer s.server.stagesLock.RUnlock()
// Build the response
resp := byteframe.NewByteFrame()
bf := byteframe.NewByteFrame()
var joinable int
for sid, stage := range s.server.stages {
stage.RLock()
defer stage.RUnlock()
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
continue
}
// Check for valid stage type
if sid[3:5] != "Qs" && sid[3:5] != "Ms" {
continue
}
joinable++
resp.WriteUint16(uint16(len(stage.reservedClientSlots))) // Reserved players.
resp.WriteUint16(0) // Unk
resp.WriteUint8(0) // Unk
resp.WriteBool(len(stage.clients) > 0) // Has departed.
resp.WriteUint16(stage.maxPlayers) // Max players.
if len(stage.password) > 0 {
// This byte has also been seen as 1
// The quest is also recognised as locked when this is 2
resp.WriteUint8(3)
} else {
resp.WriteUint8(0)
}
ps.Uint8(resp, sid, false)
}
bf.WriteUint16(uint16(joinable))
bf.WriteBytes(resp.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -0,0 +1,446 @@
package channelserver
import (
"erupe-ce/network"
"erupe-ce/network/mhfpacket"
)
type handlerFunc func(s *Session, p mhfpacket.MHFPacket)
var handlerTable map[network.PacketID]handlerFunc
func init() {
handlerTable = make(map[network.PacketID]handlerFunc)
handlerTable[network.MSG_HEAD] = handleMsgHead
handlerTable[network.MSG_SYS_reserve01] = handleMsgSysReserve01
handlerTable[network.MSG_SYS_reserve02] = handleMsgSysReserve02
handlerTable[network.MSG_SYS_reserve03] = handleMsgSysReserve03
handlerTable[network.MSG_SYS_reserve04] = handleMsgSysReserve04
handlerTable[network.MSG_SYS_reserve05] = handleMsgSysReserve05
handlerTable[network.MSG_SYS_reserve06] = handleMsgSysReserve06
handlerTable[network.MSG_SYS_reserve07] = handleMsgSysReserve07
handlerTable[network.MSG_SYS_ADD_OBJECT] = handleMsgSysAddObject
handlerTable[network.MSG_SYS_DEL_OBJECT] = handleMsgSysDelObject
handlerTable[network.MSG_SYS_DISP_OBJECT] = handleMsgSysDispObject
handlerTable[network.MSG_SYS_HIDE_OBJECT] = handleMsgSysHideObject
handlerTable[network.MSG_SYS_reserve0C] = handleMsgSysReserve0C
handlerTable[network.MSG_SYS_reserve0D] = handleMsgSysReserve0D
handlerTable[network.MSG_SYS_reserve0E] = handleMsgSysReserve0E
handlerTable[network.MSG_SYS_EXTEND_THRESHOLD] = handleMsgSysExtendThreshold
handlerTable[network.MSG_SYS_END] = handleMsgSysEnd
handlerTable[network.MSG_SYS_NOP] = handleMsgSysNop
handlerTable[network.MSG_SYS_ACK] = handleMsgSysAck
handlerTable[network.MSG_SYS_TERMINAL_LOG] = handleMsgSysTerminalLog
handlerTable[network.MSG_SYS_LOGIN] = handleMsgSysLogin
handlerTable[network.MSG_SYS_LOGOUT] = handleMsgSysLogout
handlerTable[network.MSG_SYS_SET_STATUS] = handleMsgSysSetStatus
handlerTable[network.MSG_SYS_PING] = handleMsgSysPing
handlerTable[network.MSG_SYS_CAST_BINARY] = handleMsgSysCastBinary
handlerTable[network.MSG_SYS_HIDE_CLIENT] = handleMsgSysHideClient
handlerTable[network.MSG_SYS_TIME] = handleMsgSysTime
handlerTable[network.MSG_SYS_CASTED_BINARY] = handleMsgSysCastedBinary
handlerTable[network.MSG_SYS_GET_FILE] = handleMsgSysGetFile
handlerTable[network.MSG_SYS_ISSUE_LOGKEY] = handleMsgSysIssueLogkey
handlerTable[network.MSG_SYS_RECORD_LOG] = handleMsgSysRecordLog
handlerTable[network.MSG_SYS_ECHO] = handleMsgSysEcho
handlerTable[network.MSG_SYS_CREATE_STAGE] = handleMsgSysCreateStage
handlerTable[network.MSG_SYS_STAGE_DESTRUCT] = handleMsgSysStageDestruct
handlerTable[network.MSG_SYS_ENTER_STAGE] = handleMsgSysEnterStage
handlerTable[network.MSG_SYS_BACK_STAGE] = handleMsgSysBackStage
handlerTable[network.MSG_SYS_MOVE_STAGE] = handleMsgSysMoveStage
handlerTable[network.MSG_SYS_LEAVE_STAGE] = handleMsgSysLeaveStage
handlerTable[network.MSG_SYS_LOCK_STAGE] = handleMsgSysLockStage
handlerTable[network.MSG_SYS_UNLOCK_STAGE] = handleMsgSysUnlockStage
handlerTable[network.MSG_SYS_RESERVE_STAGE] = handleMsgSysReserveStage
handlerTable[network.MSG_SYS_UNRESERVE_STAGE] = handleMsgSysUnreserveStage
handlerTable[network.MSG_SYS_SET_STAGE_PASS] = handleMsgSysSetStagePass
handlerTable[network.MSG_SYS_WAIT_STAGE_BINARY] = handleMsgSysWaitStageBinary
handlerTable[network.MSG_SYS_SET_STAGE_BINARY] = handleMsgSysSetStageBinary
handlerTable[network.MSG_SYS_GET_STAGE_BINARY] = handleMsgSysGetStageBinary
handlerTable[network.MSG_SYS_ENUMERATE_CLIENT] = handleMsgSysEnumerateClient
handlerTable[network.MSG_SYS_ENUMERATE_STAGE] = handleMsgSysEnumerateStage
handlerTable[network.MSG_SYS_CREATE_MUTEX] = handleMsgSysCreateMutex
handlerTable[network.MSG_SYS_CREATE_OPEN_MUTEX] = handleMsgSysCreateOpenMutex
handlerTable[network.MSG_SYS_DELETE_MUTEX] = handleMsgSysDeleteMutex
handlerTable[network.MSG_SYS_OPEN_MUTEX] = handleMsgSysOpenMutex
handlerTable[network.MSG_SYS_CLOSE_MUTEX] = handleMsgSysCloseMutex
handlerTable[network.MSG_SYS_CREATE_SEMAPHORE] = handleMsgSysCreateSemaphore
handlerTable[network.MSG_SYS_CREATE_ACQUIRE_SEMAPHORE] = handleMsgSysCreateAcquireSemaphore
handlerTable[network.MSG_SYS_DELETE_SEMAPHORE] = handleMsgSysDeleteSemaphore
handlerTable[network.MSG_SYS_ACQUIRE_SEMAPHORE] = handleMsgSysAcquireSemaphore
handlerTable[network.MSG_SYS_RELEASE_SEMAPHORE] = handleMsgSysReleaseSemaphore
handlerTable[network.MSG_SYS_LOCK_GLOBAL_SEMA] = handleMsgSysLockGlobalSema
handlerTable[network.MSG_SYS_UNLOCK_GLOBAL_SEMA] = handleMsgSysUnlockGlobalSema
handlerTable[network.MSG_SYS_CHECK_SEMAPHORE] = handleMsgSysCheckSemaphore
handlerTable[network.MSG_SYS_OPERATE_REGISTER] = handleMsgSysOperateRegister
handlerTable[network.MSG_SYS_LOAD_REGISTER] = handleMsgSysLoadRegister
handlerTable[network.MSG_SYS_NOTIFY_REGISTER] = handleMsgSysNotifyRegister
handlerTable[network.MSG_SYS_CREATE_OBJECT] = handleMsgSysCreateObject
handlerTable[network.MSG_SYS_DELETE_OBJECT] = handleMsgSysDeleteObject
handlerTable[network.MSG_SYS_POSITION_OBJECT] = handleMsgSysPositionObject
handlerTable[network.MSG_SYS_ROTATE_OBJECT] = handleMsgSysRotateObject
handlerTable[network.MSG_SYS_DUPLICATE_OBJECT] = handleMsgSysDuplicateObject
handlerTable[network.MSG_SYS_SET_OBJECT_BINARY] = handleMsgSysSetObjectBinary
handlerTable[network.MSG_SYS_GET_OBJECT_BINARY] = handleMsgSysGetObjectBinary
handlerTable[network.MSG_SYS_GET_OBJECT_OWNER] = handleMsgSysGetObjectOwner
handlerTable[network.MSG_SYS_UPDATE_OBJECT_BINARY] = handleMsgSysUpdateObjectBinary
handlerTable[network.MSG_SYS_CLEANUP_OBJECT] = handleMsgSysCleanupObject
handlerTable[network.MSG_SYS_reserve4A] = handleMsgSysReserve4A
handlerTable[network.MSG_SYS_reserve4B] = handleMsgSysReserve4B
handlerTable[network.MSG_SYS_reserve4C] = handleMsgSysReserve4C
handlerTable[network.MSG_SYS_reserve4D] = handleMsgSysReserve4D
handlerTable[network.MSG_SYS_reserve4E] = handleMsgSysReserve4E
handlerTable[network.MSG_SYS_reserve4F] = handleMsgSysReserve4F
handlerTable[network.MSG_SYS_INSERT_USER] = handleMsgSysInsertUser
handlerTable[network.MSG_SYS_DELETE_USER] = handleMsgSysDeleteUser
handlerTable[network.MSG_SYS_SET_USER_BINARY] = handleMsgSysSetUserBinary
handlerTable[network.MSG_SYS_GET_USER_BINARY] = handleMsgSysGetUserBinary
handlerTable[network.MSG_SYS_NOTIFY_USER_BINARY] = handleMsgSysNotifyUserBinary
handlerTable[network.MSG_SYS_reserve55] = handleMsgSysReserve55
handlerTable[network.MSG_SYS_reserve56] = handleMsgSysReserve56
handlerTable[network.MSG_SYS_reserve57] = handleMsgSysReserve57
handlerTable[network.MSG_SYS_UPDATE_RIGHT] = handleMsgSysUpdateRight
handlerTable[network.MSG_SYS_AUTH_QUERY] = handleMsgSysAuthQuery
handlerTable[network.MSG_SYS_AUTH_DATA] = handleMsgSysAuthData
handlerTable[network.MSG_SYS_AUTH_TERMINAL] = handleMsgSysAuthTerminal
handlerTable[network.MSG_SYS_reserve5C] = handleMsgSysReserve5C
handlerTable[network.MSG_SYS_RIGHTS_RELOAD] = handleMsgSysRightsReload
handlerTable[network.MSG_SYS_reserve5E] = handleMsgSysReserve5E
handlerTable[network.MSG_SYS_reserve5F] = handleMsgSysReserve5F
handlerTable[network.MSG_MHF_SAVEDATA] = handleMsgMhfSavedata
handlerTable[network.MSG_MHF_LOADDATA] = handleMsgMhfLoaddata
handlerTable[network.MSG_MHF_LIST_MEMBER] = handleMsgMhfListMember
handlerTable[network.MSG_MHF_OPR_MEMBER] = handleMsgMhfOprMember
handlerTable[network.MSG_MHF_ENUMERATE_DIST_ITEM] = handleMsgMhfEnumerateDistItem
handlerTable[network.MSG_MHF_APPLY_DIST_ITEM] = handleMsgMhfApplyDistItem
handlerTable[network.MSG_MHF_ACQUIRE_DIST_ITEM] = handleMsgMhfAcquireDistItem
handlerTable[network.MSG_MHF_GET_DIST_DESCRIPTION] = handleMsgMhfGetDistDescription
handlerTable[network.MSG_MHF_SEND_MAIL] = handleMsgMhfSendMail
handlerTable[network.MSG_MHF_READ_MAIL] = handleMsgMhfReadMail
handlerTable[network.MSG_MHF_LIST_MAIL] = handleMsgMhfListMail
handlerTable[network.MSG_MHF_OPRT_MAIL] = handleMsgMhfOprtMail
handlerTable[network.MSG_MHF_LOAD_FAVORITE_QUEST] = handleMsgMhfLoadFavoriteQuest
handlerTable[network.MSG_MHF_SAVE_FAVORITE_QUEST] = handleMsgMhfSaveFavoriteQuest
handlerTable[network.MSG_MHF_REGISTER_EVENT] = handleMsgMhfRegisterEvent
handlerTable[network.MSG_MHF_RELEASE_EVENT] = handleMsgMhfReleaseEvent
handlerTable[network.MSG_MHF_TRANSIT_MESSAGE] = handleMsgMhfTransitMessage
handlerTable[network.MSG_SYS_reserve71] = handleMsgSysReserve71
handlerTable[network.MSG_SYS_reserve72] = handleMsgSysReserve72
handlerTable[network.MSG_SYS_reserve73] = handleMsgSysReserve73
handlerTable[network.MSG_SYS_reserve74] = handleMsgSysReserve74
handlerTable[network.MSG_SYS_reserve75] = handleMsgSysReserve75
handlerTable[network.MSG_SYS_reserve76] = handleMsgSysReserve76
handlerTable[network.MSG_SYS_reserve77] = handleMsgSysReserve77
handlerTable[network.MSG_SYS_reserve78] = handleMsgSysReserve78
handlerTable[network.MSG_SYS_reserve79] = handleMsgSysReserve79
handlerTable[network.MSG_SYS_reserve7A] = handleMsgSysReserve7A
handlerTable[network.MSG_SYS_reserve7B] = handleMsgSysReserve7B
handlerTable[network.MSG_SYS_reserve7C] = handleMsgSysReserve7C
handlerTable[network.MSG_CA_EXCHANGE_ITEM] = handleMsgCaExchangeItem
handlerTable[network.MSG_SYS_reserve7E] = handleMsgSysReserve7E
handlerTable[network.MSG_MHF_PRESENT_BOX] = handleMsgMhfPresentBox
handlerTable[network.MSG_MHF_SERVER_COMMAND] = handleMsgMhfServerCommand
handlerTable[network.MSG_MHF_SHUT_CLIENT] = handleMsgMhfShutClient
handlerTable[network.MSG_MHF_ANNOUNCE] = handleMsgMhfAnnounce
handlerTable[network.MSG_MHF_SET_LOGINWINDOW] = handleMsgMhfSetLoginwindow
handlerTable[network.MSG_SYS_TRANS_BINARY] = handleMsgSysTransBinary
handlerTable[network.MSG_SYS_COLLECT_BINARY] = handleMsgSysCollectBinary
handlerTable[network.MSG_SYS_GET_STATE] = handleMsgSysGetState
handlerTable[network.MSG_SYS_SERIALIZE] = handleMsgSysSerialize
handlerTable[network.MSG_SYS_ENUMLOBBY] = handleMsgSysEnumlobby
handlerTable[network.MSG_SYS_ENUMUSER] = handleMsgSysEnumuser
handlerTable[network.MSG_SYS_INFOKYSERVER] = handleMsgSysInfokyserver
handlerTable[network.MSG_MHF_GET_CA_UNIQUE_ID] = handleMsgMhfGetCaUniqueID
handlerTable[network.MSG_MHF_SET_CA_ACHIEVEMENT] = handleMsgMhfSetCaAchievement
handlerTable[network.MSG_MHF_CARAVAN_MY_SCORE] = handleMsgMhfCaravanMyScore
handlerTable[network.MSG_MHF_CARAVAN_RANKING] = handleMsgMhfCaravanRanking
handlerTable[network.MSG_MHF_CARAVAN_MY_RANK] = handleMsgMhfCaravanMyRank
handlerTable[network.MSG_MHF_CREATE_GUILD] = handleMsgMhfCreateGuild
handlerTable[network.MSG_MHF_OPERATE_GUILD] = handleMsgMhfOperateGuild
handlerTable[network.MSG_MHF_OPERATE_GUILD_MEMBER] = handleMsgMhfOperateGuildMember
handlerTable[network.MSG_MHF_INFO_GUILD] = handleMsgMhfInfoGuild
handlerTable[network.MSG_MHF_ENUMERATE_GUILD] = handleMsgMhfEnumerateGuild
handlerTable[network.MSG_MHF_UPDATE_GUILD] = handleMsgMhfUpdateGuild
handlerTable[network.MSG_MHF_ARRANGE_GUILD_MEMBER] = handleMsgMhfArrangeGuildMember
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_MEMBER] = handleMsgMhfEnumerateGuildMember
handlerTable[network.MSG_MHF_ENUMERATE_CAMPAIGN] = handleMsgMhfEnumerateCampaign
handlerTable[network.MSG_MHF_STATE_CAMPAIGN] = handleMsgMhfStateCampaign
handlerTable[network.MSG_MHF_APPLY_CAMPAIGN] = handleMsgMhfApplyCampaign
handlerTable[network.MSG_MHF_ENUMERATE_ITEM] = handleMsgMhfEnumerateItem
handlerTable[network.MSG_MHF_ACQUIRE_ITEM] = handleMsgMhfAcquireItem
handlerTable[network.MSG_MHF_TRANSFER_ITEM] = handleMsgMhfTransferItem
handlerTable[network.MSG_MHF_MERCENARY_HUNTDATA] = handleMsgMhfMercenaryHuntdata
handlerTable[network.MSG_MHF_ENTRY_ROOKIE_GUILD] = handleMsgMhfEntryRookieGuild
handlerTable[network.MSG_MHF_ENUMERATE_QUEST] = handleMsgMhfEnumerateQuest
handlerTable[network.MSG_MHF_ENUMERATE_EVENT] = handleMsgMhfEnumerateEvent
handlerTable[network.MSG_MHF_ENUMERATE_PRICE] = handleMsgMhfEnumeratePrice
handlerTable[network.MSG_MHF_ENUMERATE_RANKING] = handleMsgMhfEnumerateRanking
handlerTable[network.MSG_MHF_ENUMERATE_ORDER] = handleMsgMhfEnumerateOrder
handlerTable[network.MSG_MHF_ENUMERATE_SHOP] = handleMsgMhfEnumerateShop
handlerTable[network.MSG_MHF_GET_EXTRA_INFO] = handleMsgMhfGetExtraInfo
handlerTable[network.MSG_MHF_UPDATE_INTERIOR] = handleMsgMhfUpdateInterior
handlerTable[network.MSG_MHF_ENUMERATE_HOUSE] = handleMsgMhfEnumerateHouse
handlerTable[network.MSG_MHF_UPDATE_HOUSE] = handleMsgMhfUpdateHouse
handlerTable[network.MSG_MHF_LOAD_HOUSE] = handleMsgMhfLoadHouse
handlerTable[network.MSG_MHF_OPERATE_WAREHOUSE] = handleMsgMhfOperateWarehouse
handlerTable[network.MSG_MHF_ENUMERATE_WAREHOUSE] = handleMsgMhfEnumerateWarehouse
handlerTable[network.MSG_MHF_UPDATE_WAREHOUSE] = handleMsgMhfUpdateWarehouse
handlerTable[network.MSG_MHF_ACQUIRE_TITLE] = handleMsgMhfAcquireTitle
handlerTable[network.MSG_MHF_ENUMERATE_TITLE] = handleMsgMhfEnumerateTitle
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_ITEM] = handleMsgMhfEnumerateGuildItem
handlerTable[network.MSG_MHF_UPDATE_GUILD_ITEM] = handleMsgMhfUpdateGuildItem
handlerTable[network.MSG_MHF_ENUMERATE_UNION_ITEM] = handleMsgMhfEnumerateUnionItem
handlerTable[network.MSG_MHF_UPDATE_UNION_ITEM] = handleMsgMhfUpdateUnionItem
handlerTable[network.MSG_MHF_CREATE_JOINT] = handleMsgMhfCreateJoint
handlerTable[network.MSG_MHF_OPERATE_JOINT] = handleMsgMhfOperateJoint
handlerTable[network.MSG_MHF_INFO_JOINT] = handleMsgMhfInfoJoint
handlerTable[network.MSG_MHF_UPDATE_GUILD_ICON] = handleMsgMhfUpdateGuildIcon
handlerTable[network.MSG_MHF_INFO_FESTA] = handleMsgMhfInfoFesta
handlerTable[network.MSG_MHF_ENTRY_FESTA] = handleMsgMhfEntryFesta
handlerTable[network.MSG_MHF_CHARGE_FESTA] = handleMsgMhfChargeFesta
handlerTable[network.MSG_MHF_ACQUIRE_FESTA] = handleMsgMhfAcquireFesta
handlerTable[network.MSG_MHF_STATE_FESTA_U] = handleMsgMhfStateFestaU
handlerTable[network.MSG_MHF_STATE_FESTA_G] = handleMsgMhfStateFestaG
handlerTable[network.MSG_MHF_ENUMERATE_FESTA_MEMBER] = handleMsgMhfEnumerateFestaMember
handlerTable[network.MSG_MHF_VOTE_FESTA] = handleMsgMhfVoteFesta
handlerTable[network.MSG_MHF_ACQUIRE_CAFE_ITEM] = handleMsgMhfAcquireCafeItem
handlerTable[network.MSG_MHF_UPDATE_CAFEPOINT] = handleMsgMhfUpdateCafepoint
handlerTable[network.MSG_MHF_CHECK_DAILY_CAFEPOINT] = handleMsgMhfCheckDailyCafepoint
handlerTable[network.MSG_MHF_GET_COG_INFO] = handleMsgMhfGetCogInfo
handlerTable[network.MSG_MHF_CHECK_MONTHLY_ITEM] = handleMsgMhfCheckMonthlyItem
handlerTable[network.MSG_MHF_ACQUIRE_MONTHLY_ITEM] = handleMsgMhfAcquireMonthlyItem
handlerTable[network.MSG_MHF_CHECK_WEEKLY_STAMP] = handleMsgMhfCheckWeeklyStamp
handlerTable[network.MSG_MHF_EXCHANGE_WEEKLY_STAMP] = handleMsgMhfExchangeWeeklyStamp
handlerTable[network.MSG_MHF_CREATE_MERCENARY] = handleMsgMhfCreateMercenary
handlerTable[network.MSG_MHF_SAVE_MERCENARY] = handleMsgMhfSaveMercenary
handlerTable[network.MSG_MHF_READ_MERCENARY_W] = handleMsgMhfReadMercenaryW
handlerTable[network.MSG_MHF_READ_MERCENARY_M] = handleMsgMhfReadMercenaryM
handlerTable[network.MSG_MHF_CONTRACT_MERCENARY] = handleMsgMhfContractMercenary
handlerTable[network.MSG_MHF_ENUMERATE_MERCENARY_LOG] = handleMsgMhfEnumerateMercenaryLog
handlerTable[network.MSG_MHF_ENUMERATE_GUACOT] = handleMsgMhfEnumerateGuacot
handlerTable[network.MSG_MHF_UPDATE_GUACOT] = handleMsgMhfUpdateGuacot
handlerTable[network.MSG_MHF_INFO_TOURNAMENT] = handleMsgMhfInfoTournament
handlerTable[network.MSG_MHF_ENTRY_TOURNAMENT] = handleMsgMhfEntryTournament
handlerTable[network.MSG_MHF_ENTER_TOURNAMENT_QUEST] = handleMsgMhfEnterTournamentQuest
handlerTable[network.MSG_MHF_ACQUIRE_TOURNAMENT] = handleMsgMhfAcquireTournament
handlerTable[network.MSG_MHF_GET_ACHIEVEMENT] = handleMsgMhfGetAchievement
handlerTable[network.MSG_MHF_RESET_ACHIEVEMENT] = handleMsgMhfResetAchievement
handlerTable[network.MSG_MHF_ADD_ACHIEVEMENT] = handleMsgMhfAddAchievement
handlerTable[network.MSG_MHF_PAYMENT_ACHIEVEMENT] = handleMsgMhfPaymentAchievement
handlerTable[network.MSG_MHF_DISPLAYED_ACHIEVEMENT] = handleMsgMhfDisplayedAchievement
handlerTable[network.MSG_MHF_INFO_SCENARIO_COUNTER] = handleMsgMhfInfoScenarioCounter
handlerTable[network.MSG_MHF_SAVE_SCENARIO_DATA] = handleMsgMhfSaveScenarioData
handlerTable[network.MSG_MHF_LOAD_SCENARIO_DATA] = handleMsgMhfLoadScenarioData
handlerTable[network.MSG_MHF_GET_BBS_SNS_STATUS] = handleMsgMhfGetBbsSnsStatus
handlerTable[network.MSG_MHF_APPLY_BBS_ARTICLE] = handleMsgMhfApplyBbsArticle
handlerTable[network.MSG_MHF_GET_ETC_POINTS] = handleMsgMhfGetEtcPoints
handlerTable[network.MSG_MHF_UPDATE_ETC_POINT] = handleMsgMhfUpdateEtcPoint
handlerTable[network.MSG_MHF_GET_MYHOUSE_INFO] = handleMsgMhfGetMyhouseInfo
handlerTable[network.MSG_MHF_UPDATE_MYHOUSE_INFO] = handleMsgMhfUpdateMyhouseInfo
handlerTable[network.MSG_MHF_GET_WEEKLY_SCHEDULE] = handleMsgMhfGetWeeklySchedule
handlerTable[network.MSG_MHF_ENUMERATE_INV_GUILD] = handleMsgMhfEnumerateInvGuild
handlerTable[network.MSG_MHF_OPERATION_INV_GUILD] = handleMsgMhfOperationInvGuild
handlerTable[network.MSG_MHF_STAMPCARD_STAMP] = handleMsgMhfStampcardStamp
handlerTable[network.MSG_MHF_STAMPCARD_PRIZE] = handleMsgMhfStampcardPrize
handlerTable[network.MSG_MHF_UNRESERVE_SRG] = handleMsgMhfUnreserveSrg
handlerTable[network.MSG_MHF_LOAD_PLATE_DATA] = handleMsgMhfLoadPlateData
handlerTable[network.MSG_MHF_SAVE_PLATE_DATA] = handleMsgMhfSavePlateData
handlerTable[network.MSG_MHF_LOAD_PLATE_BOX] = handleMsgMhfLoadPlateBox
handlerTable[network.MSG_MHF_SAVE_PLATE_BOX] = handleMsgMhfSavePlateBox
handlerTable[network.MSG_MHF_READ_GUILDCARD] = handleMsgMhfReadGuildcard
handlerTable[network.MSG_MHF_UPDATE_GUILDCARD] = handleMsgMhfUpdateGuildcard
handlerTable[network.MSG_MHF_READ_BEAT_LEVEL] = handleMsgMhfReadBeatLevel
handlerTable[network.MSG_MHF_UPDATE_BEAT_LEVEL] = handleMsgMhfUpdateBeatLevel
handlerTable[network.MSG_MHF_READ_BEAT_LEVEL_ALL_RANKING] = handleMsgMhfReadBeatLevelAllRanking
handlerTable[network.MSG_MHF_READ_BEAT_LEVEL_MY_RANKING] = handleMsgMhfReadBeatLevelMyRanking
handlerTable[network.MSG_MHF_READ_LAST_WEEK_BEAT_RANKING] = handleMsgMhfReadLastWeekBeatRanking
handlerTable[network.MSG_MHF_ACCEPT_READ_REWARD] = handleMsgMhfAcceptReadReward
handlerTable[network.MSG_MHF_GET_ADDITIONAL_BEAT_REWARD] = handleMsgMhfGetAdditionalBeatReward
handlerTable[network.MSG_MHF_GET_FIXED_SEIBATU_RANKING_TABLE] = handleMsgMhfGetFixedSeibatuRankingTable
handlerTable[network.MSG_MHF_GET_BBS_USER_STATUS] = handleMsgMhfGetBbsUserStatus
handlerTable[network.MSG_MHF_KICK_EXPORT_FORCE] = handleMsgMhfKickExportForce
handlerTable[network.MSG_MHF_GET_BREAK_SEIBATU_LEVEL_REWARD] = handleMsgMhfGetBreakSeibatuLevelReward
handlerTable[network.MSG_MHF_GET_WEEKLY_SEIBATU_RANKING_REWARD] = handleMsgMhfGetWeeklySeibatuRankingReward
handlerTable[network.MSG_MHF_GET_EARTH_STATUS] = handleMsgMhfGetEarthStatus
handlerTable[network.MSG_MHF_LOAD_PARTNER] = handleMsgMhfLoadPartner
handlerTable[network.MSG_MHF_SAVE_PARTNER] = handleMsgMhfSavePartner
handlerTable[network.MSG_MHF_GET_GUILD_MISSION_LIST] = handleMsgMhfGetGuildMissionList
handlerTable[network.MSG_MHF_GET_GUILD_MISSION_RECORD] = handleMsgMhfGetGuildMissionRecord
handlerTable[network.MSG_MHF_ADD_GUILD_MISSION_COUNT] = handleMsgMhfAddGuildMissionCount
handlerTable[network.MSG_MHF_SET_GUILD_MISSION_TARGET] = handleMsgMhfSetGuildMissionTarget
handlerTable[network.MSG_MHF_CANCEL_GUILD_MISSION_TARGET] = handleMsgMhfCancelGuildMissionTarget
handlerTable[network.MSG_MHF_LOAD_OTOMO_AIROU] = handleMsgMhfLoadOtomoAirou
handlerTable[network.MSG_MHF_SAVE_OTOMO_AIROU] = handleMsgMhfSaveOtomoAirou
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_TRESURE] = handleMsgMhfEnumerateGuildTresure
handlerTable[network.MSG_MHF_ENUMERATE_AIROULIST] = handleMsgMhfEnumerateAiroulist
handlerTable[network.MSG_MHF_REGIST_GUILD_TRESURE] = handleMsgMhfRegistGuildTresure
handlerTable[network.MSG_MHF_ACQUIRE_GUILD_TRESURE] = handleMsgMhfAcquireGuildTresure
handlerTable[network.MSG_MHF_OPERATE_GUILD_TRESURE_REPORT] = handleMsgMhfOperateGuildTresureReport
handlerTable[network.MSG_MHF_GET_GUILD_TRESURE_SOUVENIR] = handleMsgMhfGetGuildTresureSouvenir
handlerTable[network.MSG_MHF_ACQUIRE_GUILD_TRESURE_SOUVENIR] = handleMsgMhfAcquireGuildTresureSouvenir
handlerTable[network.MSG_MHF_ENUMERATE_FESTA_INTERMEDIATE_PRIZE] = handleMsgMhfEnumerateFestaIntermediatePrize
handlerTable[network.MSG_MHF_ACQUIRE_FESTA_INTERMEDIATE_PRIZE] = handleMsgMhfAcquireFestaIntermediatePrize
handlerTable[network.MSG_MHF_LOAD_DECO_MYSET] = handleMsgMhfLoadDecoMyset
handlerTable[network.MSG_MHF_SAVE_DECO_MYSET] = handleMsgMhfSaveDecoMyset
handlerTable[network.MSG_MHF_reserve10F] = handleMsgMhfReserve10F
handlerTable[network.MSG_MHF_LOAD_GUILD_COOKING] = handleMsgMhfLoadGuildCooking
handlerTable[network.MSG_MHF_REGIST_GUILD_COOKING] = handleMsgMhfRegistGuildCooking
handlerTable[network.MSG_MHF_LOAD_GUILD_ADVENTURE] = handleMsgMhfLoadGuildAdventure
handlerTable[network.MSG_MHF_REGIST_GUILD_ADVENTURE] = handleMsgMhfRegistGuildAdventure
handlerTable[network.MSG_MHF_ACQUIRE_GUILD_ADVENTURE] = handleMsgMhfAcquireGuildAdventure
handlerTable[network.MSG_MHF_CHARGE_GUILD_ADVENTURE] = handleMsgMhfChargeGuildAdventure
handlerTable[network.MSG_MHF_LOAD_LEGEND_DISPATCH] = handleMsgMhfLoadLegendDispatch
handlerTable[network.MSG_MHF_LOAD_HUNTER_NAVI] = handleMsgMhfLoadHunterNavi
handlerTable[network.MSG_MHF_SAVE_HUNTER_NAVI] = handleMsgMhfSaveHunterNavi
handlerTable[network.MSG_MHF_REGIST_SPABI_TIME] = handleMsgMhfRegistSpabiTime
handlerTable[network.MSG_MHF_GET_GUILD_WEEKLY_BONUS_MASTER] = handleMsgMhfGetGuildWeeklyBonusMaster
handlerTable[network.MSG_MHF_GET_GUILD_WEEKLY_BONUS_ACTIVE_COUNT] = handleMsgMhfGetGuildWeeklyBonusActiveCount
handlerTable[network.MSG_MHF_ADD_GUILD_WEEKLY_BONUS_EXCEPTIONAL_USER] = handleMsgMhfAddGuildWeeklyBonusExceptionalUser
handlerTable[network.MSG_MHF_GET_TOWER_INFO] = handleMsgMhfGetTowerInfo
handlerTable[network.MSG_MHF_POST_TOWER_INFO] = handleMsgMhfPostTowerInfo
handlerTable[network.MSG_MHF_GET_GEM_INFO] = handleMsgMhfGetGemInfo
handlerTable[network.MSG_MHF_POST_GEM_INFO] = handleMsgMhfPostGemInfo
handlerTable[network.MSG_MHF_GET_EARTH_VALUE] = handleMsgMhfGetEarthValue
handlerTable[network.MSG_MHF_DEBUG_POST_VALUE] = handleMsgMhfDebugPostValue
handlerTable[network.MSG_MHF_GET_PAPER_DATA] = handleMsgMhfGetPaperData
handlerTable[network.MSG_MHF_GET_NOTICE] = handleMsgMhfGetNotice
handlerTable[network.MSG_MHF_POST_NOTICE] = handleMsgMhfPostNotice
handlerTable[network.MSG_MHF_GET_BOOST_TIME] = handleMsgMhfGetBoostTime
handlerTable[network.MSG_MHF_POST_BOOST_TIME] = handleMsgMhfPostBoostTime
handlerTable[network.MSG_MHF_GET_BOOST_TIME_LIMIT] = handleMsgMhfGetBoostTimeLimit
handlerTable[network.MSG_MHF_POST_BOOST_TIME_LIMIT] = handleMsgMhfPostBoostTimeLimit
handlerTable[network.MSG_MHF_ENUMERATE_FESTA_PERSONAL_PRIZE] = handleMsgMhfEnumerateFestaPersonalPrize
handlerTable[network.MSG_MHF_ACQUIRE_FESTA_PERSONAL_PRIZE] = handleMsgMhfAcquireFestaPersonalPrize
handlerTable[network.MSG_MHF_GET_RAND_FROM_TABLE] = handleMsgMhfGetRandFromTable
handlerTable[network.MSG_MHF_GET_CAFE_DURATION] = handleMsgMhfGetCafeDuration
handlerTable[network.MSG_MHF_GET_CAFE_DURATION_BONUS_INFO] = handleMsgMhfGetCafeDurationBonusInfo
handlerTable[network.MSG_MHF_RECEIVE_CAFE_DURATION_BONUS] = handleMsgMhfReceiveCafeDurationBonus
handlerTable[network.MSG_MHF_POST_CAFE_DURATION_BONUS_RECEIVED] = handleMsgMhfPostCafeDurationBonusReceived
handlerTable[network.MSG_MHF_GET_GACHA_POINT] = handleMsgMhfGetGachaPoint
handlerTable[network.MSG_MHF_USE_GACHA_POINT] = handleMsgMhfUseGachaPoint
handlerTable[network.MSG_MHF_EXCHANGE_FPOINT_2_ITEM] = handleMsgMhfExchangeFpoint2Item
handlerTable[network.MSG_MHF_EXCHANGE_ITEM_2_FPOINT] = handleMsgMhfExchangeItem2Fpoint
handlerTable[network.MSG_MHF_GET_FPOINT_EXCHANGE_LIST] = handleMsgMhfGetFpointExchangeList
handlerTable[network.MSG_MHF_PLAY_STEPUP_GACHA] = handleMsgMhfPlayStepupGacha
handlerTable[network.MSG_MHF_RECEIVE_GACHA_ITEM] = handleMsgMhfReceiveGachaItem
handlerTable[network.MSG_MHF_GET_STEPUP_STATUS] = handleMsgMhfGetStepupStatus
handlerTable[network.MSG_MHF_PLAY_FREE_GACHA] = handleMsgMhfPlayFreeGacha
handlerTable[network.MSG_MHF_GET_TINY_BIN] = handleMsgMhfGetTinyBin
handlerTable[network.MSG_MHF_POST_TINY_BIN] = handleMsgMhfPostTinyBin
handlerTable[network.MSG_MHF_GET_SENYU_DAILY_COUNT] = handleMsgMhfGetSenyuDailyCount
handlerTable[network.MSG_MHF_GET_GUILD_TARGET_MEMBER_NUM] = handleMsgMhfGetGuildTargetMemberNum
handlerTable[network.MSG_MHF_GET_BOOST_RIGHT] = handleMsgMhfGetBoostRight
handlerTable[network.MSG_MHF_START_BOOST_TIME] = handleMsgMhfStartBoostTime
handlerTable[network.MSG_MHF_POST_BOOST_TIME_QUEST_RETURN] = handleMsgMhfPostBoostTimeQuestReturn
handlerTable[network.MSG_MHF_GET_BOX_GACHA_INFO] = handleMsgMhfGetBoxGachaInfo
handlerTable[network.MSG_MHF_PLAY_BOX_GACHA] = handleMsgMhfPlayBoxGacha
handlerTable[network.MSG_MHF_RESET_BOX_GACHA_INFO] = handleMsgMhfResetBoxGachaInfo
handlerTable[network.MSG_MHF_GET_SEIBATTLE] = handleMsgMhfGetSeibattle
handlerTable[network.MSG_MHF_POST_SEIBATTLE] = handleMsgMhfPostSeibattle
handlerTable[network.MSG_MHF_GET_RYOUDAMA] = handleMsgMhfGetRyoudama
handlerTable[network.MSG_MHF_POST_RYOUDAMA] = handleMsgMhfPostRyoudama
handlerTable[network.MSG_MHF_GET_TENROUIRAI] = handleMsgMhfGetTenrouirai
handlerTable[network.MSG_MHF_POST_TENROUIRAI] = handleMsgMhfPostTenrouirai
handlerTable[network.MSG_MHF_POST_GUILD_SCOUT] = handleMsgMhfPostGuildScout
handlerTable[network.MSG_MHF_CANCEL_GUILD_SCOUT] = handleMsgMhfCancelGuildScout
handlerTable[network.MSG_MHF_ANSWER_GUILD_SCOUT] = handleMsgMhfAnswerGuildScout
handlerTable[network.MSG_MHF_GET_GUILD_SCOUT_LIST] = handleMsgMhfGetGuildScoutList
handlerTable[network.MSG_MHF_GET_GUILD_MANAGE_RIGHT] = handleMsgMhfGetGuildManageRight
handlerTable[network.MSG_MHF_SET_GUILD_MANAGE_RIGHT] = handleMsgMhfSetGuildManageRight
handlerTable[network.MSG_MHF_PLAY_NORMAL_GACHA] = handleMsgMhfPlayNormalGacha
handlerTable[network.MSG_MHF_GET_DAILY_MISSION_MASTER] = handleMsgMhfGetDailyMissionMaster
handlerTable[network.MSG_MHF_GET_DAILY_MISSION_PERSONAL] = handleMsgMhfGetDailyMissionPersonal
handlerTable[network.MSG_MHF_SET_DAILY_MISSION_PERSONAL] = handleMsgMhfSetDailyMissionPersonal
handlerTable[network.MSG_MHF_GET_GACHA_PLAY_HISTORY] = handleMsgMhfGetGachaPlayHistory
handlerTable[network.MSG_MHF_GET_REJECT_GUILD_SCOUT] = handleMsgMhfGetRejectGuildScout
handlerTable[network.MSG_MHF_SET_REJECT_GUILD_SCOUT] = handleMsgMhfSetRejectGuildScout
handlerTable[network.MSG_MHF_GET_CA_ACHIEVEMENT_HIST] = handleMsgMhfGetCaAchievementHist
handlerTable[network.MSG_MHF_SET_CA_ACHIEVEMENT_HIST] = handleMsgMhfSetCaAchievementHist
handlerTable[network.MSG_MHF_GET_KEEP_LOGIN_BOOST_STATUS] = handleMsgMhfGetKeepLoginBoostStatus
handlerTable[network.MSG_MHF_USE_KEEP_LOGIN_BOOST] = handleMsgMhfUseKeepLoginBoost
handlerTable[network.MSG_MHF_GET_UD_SCHEDULE] = handleMsgMhfGetUdSchedule
handlerTable[network.MSG_MHF_GET_UD_INFO] = handleMsgMhfGetUdInfo
handlerTable[network.MSG_MHF_GET_KIJU_INFO] = handleMsgMhfGetKijuInfo
handlerTable[network.MSG_MHF_SET_KIJU] = handleMsgMhfSetKiju
handlerTable[network.MSG_MHF_ADD_UD_POINT] = handleMsgMhfAddUdPoint
handlerTable[network.MSG_MHF_GET_UD_MY_POINT] = handleMsgMhfGetUdMyPoint
handlerTable[network.MSG_MHF_GET_UD_TOTAL_POINT_INFO] = handleMsgMhfGetUdTotalPointInfo
handlerTable[network.MSG_MHF_GET_UD_BONUS_QUEST_INFO] = handleMsgMhfGetUdBonusQuestInfo
handlerTable[network.MSG_MHF_GET_UD_SELECTED_COLOR_INFO] = handleMsgMhfGetUdSelectedColorInfo
handlerTable[network.MSG_MHF_GET_UD_MONSTER_POINT] = handleMsgMhfGetUdMonsterPoint
handlerTable[network.MSG_MHF_GET_UD_DAILY_PRESENT_LIST] = handleMsgMhfGetUdDailyPresentList
handlerTable[network.MSG_MHF_GET_UD_NORMA_PRESENT_LIST] = handleMsgMhfGetUdNormaPresentList
handlerTable[network.MSG_MHF_GET_UD_RANKING_REWARD_LIST] = handleMsgMhfGetUdRankingRewardList
handlerTable[network.MSG_MHF_ACQUIRE_UD_ITEM] = handleMsgMhfAcquireUdItem
handlerTable[network.MSG_MHF_GET_REWARD_SONG] = handleMsgMhfGetRewardSong
handlerTable[network.MSG_MHF_USE_REWARD_SONG] = handleMsgMhfUseRewardSong
handlerTable[network.MSG_MHF_ADD_REWARD_SONG_COUNT] = handleMsgMhfAddRewardSongCount
handlerTable[network.MSG_MHF_GET_UD_RANKING] = handleMsgMhfGetUdRanking
handlerTable[network.MSG_MHF_GET_UD_MY_RANKING] = handleMsgMhfGetUdMyRanking
handlerTable[network.MSG_MHF_ACQUIRE_MONTHLY_REWARD] = handleMsgMhfAcquireMonthlyReward
handlerTable[network.MSG_MHF_GET_UD_GUILD_MAP_INFO] = handleMsgMhfGetUdGuildMapInfo
handlerTable[network.MSG_MHF_GENERATE_UD_GUILD_MAP] = handleMsgMhfGenerateUdGuildMap
handlerTable[network.MSG_MHF_GET_UD_TACTICS_POINT] = handleMsgMhfGetUdTacticsPoint
handlerTable[network.MSG_MHF_ADD_UD_TACTICS_POINT] = handleMsgMhfAddUdTacticsPoint
handlerTable[network.MSG_MHF_GET_UD_TACTICS_RANKING] = handleMsgMhfGetUdTacticsRanking
handlerTable[network.MSG_MHF_GET_UD_TACTICS_REWARD_LIST] = handleMsgMhfGetUdTacticsRewardList
handlerTable[network.MSG_MHF_GET_UD_TACTICS_LOG] = handleMsgMhfGetUdTacticsLog
handlerTable[network.MSG_MHF_GET_EQUIP_SKIN_HIST] = handleMsgMhfGetEquipSkinHist
handlerTable[network.MSG_MHF_UPDATE_EQUIP_SKIN_HIST] = handleMsgMhfUpdateEquipSkinHist
handlerTable[network.MSG_MHF_GET_UD_TACTICS_FOLLOWER] = handleMsgMhfGetUdTacticsFollower
handlerTable[network.MSG_MHF_SET_UD_TACTICS_FOLLOWER] = handleMsgMhfSetUdTacticsFollower
handlerTable[network.MSG_MHF_GET_UD_SHOP_COIN] = handleMsgMhfGetUdShopCoin
handlerTable[network.MSG_MHF_USE_UD_SHOP_COIN] = handleMsgMhfUseUdShopCoin
handlerTable[network.MSG_MHF_GET_ENHANCED_MINIDATA] = handleMsgMhfGetEnhancedMinidata
handlerTable[network.MSG_MHF_SET_ENHANCED_MINIDATA] = handleMsgMhfSetEnhancedMinidata
handlerTable[network.MSG_MHF_SEX_CHANGER] = handleMsgMhfSexChanger
handlerTable[network.MSG_MHF_GET_LOBBY_CROWD] = handleMsgMhfGetLobbyCrowd
handlerTable[network.MSG_SYS_reserve180] = handleMsgSysReserve180
handlerTable[network.MSG_MHF_GUILD_HUNTDATA] = handleMsgMhfGuildHuntdata
handlerTable[network.MSG_MHF_ADD_KOURYOU_POINT] = handleMsgMhfAddKouryouPoint
handlerTable[network.MSG_MHF_GET_KOURYOU_POINT] = handleMsgMhfGetKouryouPoint
handlerTable[network.MSG_MHF_EXCHANGE_KOURYOU_POINT] = handleMsgMhfExchangeKouryouPoint
handlerTable[network.MSG_MHF_GET_UD_TACTICS_BONUS_QUEST] = handleMsgMhfGetUdTacticsBonusQuest
handlerTable[network.MSG_MHF_GET_UD_TACTICS_FIRST_QUEST_BONUS] = handleMsgMhfGetUdTacticsFirstQuestBonus
handlerTable[network.MSG_MHF_GET_UD_TACTICS_REMAINING_POINT] = handleMsgMhfGetUdTacticsRemainingPoint
handlerTable[network.MSG_SYS_reserve188] = handleMsgSysReserve188
handlerTable[network.MSG_MHF_LOAD_PLATE_MYSET] = handleMsgMhfLoadPlateMyset
handlerTable[network.MSG_MHF_SAVE_PLATE_MYSET] = handleMsgMhfSavePlateMyset
handlerTable[network.MSG_SYS_reserve18B] = handleMsgSysReserve18B
handlerTable[network.MSG_MHF_GET_RESTRICTION_EVENT] = handleMsgMhfGetRestrictionEvent
handlerTable[network.MSG_MHF_SET_RESTRICTION_EVENT] = handleMsgMhfSetRestrictionEvent
handlerTable[network.MSG_SYS_reserve18E] = handleMsgSysReserve18E
handlerTable[network.MSG_SYS_reserve18F] = handleMsgSysReserve18F
handlerTable[network.MSG_MHF_GET_TREND_WEAPON] = handleMsgMhfGetTrendWeapon
handlerTable[network.MSG_MHF_UPDATE_USE_TREND_WEAPON_LOG] = handleMsgMhfUpdateUseTrendWeaponLog
handlerTable[network.MSG_SYS_reserve192] = handleMsgSysReserve192
handlerTable[network.MSG_SYS_reserve193] = handleMsgSysReserve193
handlerTable[network.MSG_SYS_reserve194] = handleMsgSysReserve194
handlerTable[network.MSG_MHF_SAVE_RENGOKU_DATA] = handleMsgMhfSaveRengokuData
handlerTable[network.MSG_MHF_LOAD_RENGOKU_DATA] = handleMsgMhfLoadRengokuData
handlerTable[network.MSG_MHF_GET_RENGOKU_BINARY] = handleMsgMhfGetRengokuBinary
handlerTable[network.MSG_MHF_ENUMERATE_RENGOKU_RANKING] = handleMsgMhfEnumerateRengokuRanking
handlerTable[network.MSG_MHF_GET_RENGOKU_RANKING_RANK] = handleMsgMhfGetRengokuRankingRank
handlerTable[network.MSG_MHF_ACQUIRE_EXCHANGE_SHOP] = handleMsgMhfAcquireExchangeShop
handlerTable[network.MSG_SYS_reserve19B] = handleMsgSysReserve19B
handlerTable[network.MSG_MHF_SAVE_MEZFES_DATA] = handleMsgMhfSaveMezfesData
handlerTable[network.MSG_MHF_LOAD_MEZFES_DATA] = handleMsgMhfLoadMezfesData
handlerTable[network.MSG_SYS_reserve19E] = handleMsgSysReserve19E
handlerTable[network.MSG_SYS_reserve19F] = handleMsgSysReserve19F
handlerTable[network.MSG_MHF_UPDATE_FORCE_GUILD_RANK] = handleMsgMhfUpdateForceGuildRank
handlerTable[network.MSG_MHF_RESET_TITLE] = handleMsgMhfResetTitle
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_MESSAGE_BOARD] = handleMsgMhfEnumerateGuildMessageBoard
handlerTable[network.MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD] = handleMsgMhfUpdateGuildMessageBoard
handlerTable[network.MSG_SYS_reserve1A4] = handleMsgSysReserve1A4
handlerTable[network.MSG_MHF_REGIST_GUILD_ADVENTURE_DIVA] = handleMsgMhfRegistGuildAdventureDiva
handlerTable[network.MSG_SYS_reserve1A6] = handleMsgSysReserve1A6
handlerTable[network.MSG_SYS_reserve1A7] = handleMsgSysReserve1A7
handlerTable[network.MSG_SYS_reserve1A8] = handleMsgSysReserve1A8
handlerTable[network.MSG_SYS_reserve1A9] = handleMsgSysReserve1A9
handlerTable[network.MSG_SYS_reserve1AA] = handleMsgSysReserve1AA
handlerTable[network.MSG_SYS_reserve1AB] = handleMsgSysReserve1AB
handlerTable[network.MSG_SYS_reserve1AC] = handleMsgSysReserve1AC
handlerTable[network.MSG_SYS_reserve1AD] = handleMsgSysReserve1AD
handlerTable[network.MSG_SYS_reserve1AE] = handleMsgSysReserve1AE
handlerTable[network.MSG_SYS_reserve1AF] = handleMsgSysReserve1AF
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
package channelserver
import "erupe-ce/network/mhfpacket"
func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAcquireTournament(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,63 @@
package channelserver
import (
"encoding/hex"
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetTowerInfo)
var data []byte
var err error
/*
type:
1 == TOWER_RANK_POINT,
2 == GET_OWN_TOWER_SKILL
3 == ?
4 == TOWER_TOUHA_HISTORY
5 = ?
[] = type
req
resp
01 1d 01 fc 00 09 [00 00 00 01] 00 00 00 02 00 00 00 00
00 12 01 fc 00 09 01 00 00 18 0a 21 8e ad 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00
01 1d 01 fc 00 0a [00 00 00 02] 00 00 00 00 00 00 00 00
00 12 01 fc 00 0a 01 00 00 94 0a 21 8e ad 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01 1d 01 ff 00 0f [00 00 00 04] 00 00 00 00 00 00 00 00
00 12 01 ff 00 0f 01 00 00 24 0a 21 8e ad 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01 1d 01 fc 00 0b [00 00 00 05] 00 00 00 00 00 00 00 00
00 12 01 fc 00 0b 01 00 00 10 0a 21 8e ad 00 00 00 00 00 00 00 00 00 00 00 00
*/
switch pkt.InfoType {
case mhfpacket.TowerInfoTypeTowerRankPoint:
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000000000000000")
case mhfpacket.TowerInfoTypeGetOwnTowerSkill:
//data, err = hex.DecodeString("0A218EAD000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000001C0000000500050000000000020000000000000000000000000000000000030003000000000003000500050000000300030003000300030003000200030001000300020002000300010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
case mhfpacket.TowerInfoTypeUnk3:
panic("No known response values for TowerInfoTypeUnk3")
case mhfpacket.TowerInfoTypeTowerTouhaHistory:
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000000000000000000000000000000000000000")
case mhfpacket.TowerInfoTypeUnk5:
data, err = hex.DecodeString("0A218EAD000000000000000000000000")
}
if err != nil {
stubGetNoResults(s, pkt.AckHandle)
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,122 @@
package channelserver
import (
"encoding/base64"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgSysInsertUser(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDeleteUser(s *Session, p mhfpacket.MHFPacket) {}
func broadcastNewUser(s *Session) {
s.logger.Debug(fmt.Sprintf("Broadcasting new user: %s (%d)", s.Name, s.charID))
clientNotif := byteframe.NewByteFrame()
var temp mhfpacket.MHFPacket
for _, session := range s.server.sessions {
if session == s || !session.binariesDone {
continue
}
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
clientNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(clientNotif, s.clientContext)
for i := 0; i < 3; i++ {
temp = &mhfpacket.MsgSysNotifyUserBinary{
CharID: session.charID,
BinaryType: uint8(i + 1),
}
clientNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(clientNotif, s.clientContext)
}
}
s.QueueSend(clientNotif.Data())
serverNotif := byteframe.NewByteFrame()
temp = &mhfpacket.MsgSysInsertUser{CharID: s.charID}
serverNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(serverNotif, s.clientContext)
for i := 0; i < 3; i++ {
temp = &mhfpacket.MsgSysNotifyUserBinary{
CharID: s.charID,
BinaryType: uint8(i + 1),
}
serverNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(serverNotif, s.clientContext)
}
for _, session := range s.server.sessions {
if session == s || !session.binariesDone {
continue
}
session.QueueSend(serverNotif.Data())
}
}
func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysSetUserBinary)
s.server.userBinaryPartsLock.Lock()
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload
s.server.userBinaryPartsLock.Unlock()
// Insert user once all binary parts exist
if !s.binariesDone {
for i := 0; i < 3; i++ {
_, exists := s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: uint8(i + 1)}]
if !exists {
return
}
}
s.binariesDone = true
broadcastNewUser(s)
return
}
msg := &mhfpacket.MsgSysNotifyUserBinary{
CharID: s.charID,
BinaryType: pkt.BinaryType,
}
s.server.BroadcastMHF(msg, s)
}
func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysGetUserBinary)
// Try to get the data.
s.server.userBinaryPartsLock.RLock()
defer s.server.userBinaryPartsLock.RUnlock()
data, ok := s.server.userBinaryParts[userBinaryPartID{charID: pkt.CharID, index: pkt.BinaryType}]
resp := byteframe.NewByteFrame()
// If we can't get the real data, use a placeholder.
if !ok {
if pkt.BinaryType == 1 {
// Stub name response with character ID
resp.WriteBytes([]byte(fmt.Sprintf("CID%d", s.charID)))
resp.WriteUint8(0) // NULL terminator.
} else if pkt.BinaryType == 2 {
data, err := base64.StdEncoding.DecodeString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBn8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAwAAAAAAAAAAAAAABAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")
if err != nil {
panic(err)
}
resp.WriteBytes(data)
} else if pkt.BinaryType == 3 {
data, err := base64.StdEncoding.DecodeString("AQAAA2ea5P8ATgEA/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBn8AAAAAAAAAAAABAKAMAAAAAAAAAAAAACgAAAAAAAAAAAABAsQOAAAAAAAAAAABA6UMAAAAAAAAAAABBKAMAAAAAAAAAAABBToNAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAgACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
if err != nil {
panic(err)
}
resp.WriteBytes(data)
}
} else {
resp.WriteBytes(data)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgSysNotifyUserBinary(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,413 @@
package channelserver
import (
"fmt"
"net"
"sync"
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/config"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"erupe-ce/server/discordbot"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
type StageIdType = string
const (
// GlobalStage is the stage that is used for all users.
MezeportaStageId StageIdType = "sl1Ns200p0a0u0"
GuildHallLv1StageId StageIdType = "sl1Ns202p0a0u0"
GuildHallLv2StageId StageIdType = "sl1Ns203p0a0u0"
GuildHallLv3StageId StageIdType = "sl1Ns204p0a0u0"
PugiFarmStageId StageIdType = "sl1Ns205p0a0u0"
RastaBarStageId StageIdType = "sl1Ns211p0a0u0"
PalloneCaravanStageId StageIdType = "sl1Ns260p0a0u0"
GookFarmStageId StageIdType = "sl1Ns265p0a0u0"
DivaFountainStageId StageIdType = "sl2Ns379p0a0u0"
DivaHallStageId StageIdType = "sl1Ns445p0a0u0"
MezFesStageId StageIdType = "sl1Ns462p0a0u0"
)
// Config struct allows configuring the server.
type Config struct {
ID uint16
Logger *zap.Logger
DB *sqlx.DB
DiscordBot *discordbot.DiscordBot
ErupeConfig *config.Config
Name string
Enable bool
}
// Map key type for a user binary part.
type userBinaryPartID struct {
charID uint32
index uint8
}
// Server is a MHF channel server.
type Server struct {
sync.Mutex
Channels []*Server
ID uint16
logger *zap.Logger
db *sqlx.DB
erupeConfig *config.Config
acceptConns chan net.Conn
deleteConns chan net.Conn
sessions map[net.Conn]*Session
listener net.Listener // Listener that is created when Server.Start is called.
isShuttingDown bool
stagesLock sync.RWMutex
stages map[string]*Stage
// UserBinary
userBinaryPartsLock sync.RWMutex
userBinaryParts map[userBinaryPartID][]byte
// Semaphore
semaphoreLock sync.RWMutex
semaphore map[string]*Semaphore
semaphoreIndex uint32
// Discord chat integration
discordBot *discordbot.DiscordBot
name string
enable bool
raviente *Raviente
}
type Raviente struct {
sync.Mutex
register *RavienteRegister
state *RavienteState
support *RavienteSupport
}
type RavienteRegister struct {
semaphoreID uint32
nextTime uint32
startTime uint32
postTime uint32
killedTime uint32
ravienteType uint32
maxPlayers uint32
carveQuest uint32
register []uint32
}
type RavienteState struct {
semaphoreID uint32
damageMultiplier uint32
stateData []uint32
}
type RavienteSupport struct {
semaphoreID uint32
supportData []uint32
}
// Set up the Raviente variables for the server
func NewRaviente() *Raviente {
ravienteRegister := &RavienteRegister{
nextTime: 0,
startTime: 0,
killedTime: 0,
postTime: 0,
ravienteType: 0,
maxPlayers: 0,
carveQuest: 0,
}
ravienteState := &RavienteState{
damageMultiplier: 1,
}
ravienteSupport := &RavienteSupport{}
ravienteRegister.register = []uint32{0, 0, 0, 0, 0}
ravienteState.stateData = []uint32{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}
ravienteSupport.supportData = []uint32{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}
raviente := &Raviente{
register: ravienteRegister,
state: ravienteState,
support: ravienteSupport,
}
return raviente
}
// NewServer creates a new Server type.
func NewServer(config *Config) *Server {
s := &Server{
ID: config.ID,
logger: config.Logger,
db: config.DB,
erupeConfig: config.ErupeConfig,
acceptConns: make(chan net.Conn),
deleteConns: make(chan net.Conn),
sessions: make(map[net.Conn]*Session),
stages: make(map[string]*Stage),
userBinaryParts: make(map[userBinaryPartID][]byte),
semaphore: make(map[string]*Semaphore),
semaphoreIndex: 0,
discordBot: config.DiscordBot,
name: config.Name,
enable: config.Enable,
raviente: NewRaviente(),
}
// Mezeporta
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
// Guild Hall LV1
s.stages["sl1Ns202p0a0u0"] = NewStage("sl1Ns202p0a0u0")
// Guild Hall LV2
s.stages["sl1Ns203p0a0u0"] = NewStage("sl1Ns203p0a0u0")
// Guild Hall LV3
s.stages["sl1Ns204p0a0u0"] = NewStage("sl1Ns204p0a0u0")
// Pugi Farm
s.stages["sl1Ns205p0a0u0"] = NewStage("sl1Ns205p0a0u0")
// Rasta bar stage
s.stages["sl1Ns211p0a0u0"] = NewStage("sl1Ns211p0a0u0")
// Pallone Carvan
s.stages["sl1Ns260p0a0u0"] = NewStage("sl1Ns260p0a0u0")
// Gook Farm
s.stages["sl1Ns265p0a0u0"] = NewStage("sl1Ns265p0a0u0")
// Diva fountain / prayer fountain.
s.stages["sl2Ns379p0a0u0"] = NewStage("sl2Ns379p0a0u0")
// Diva Hall
s.stages["sl1Ns445p0a0u0"] = NewStage("sl1Ns445p0a0u0")
// MezFes
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
return s
}
// Start starts the server in a new goroutine.
func (s *Server) Start(port int) error {
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return err
}
s.listener = l
go s.acceptClients()
go s.manageSessions()
// Start the discord bot for chat integration.
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
s.discordBot.Session.AddHandler(s.onDiscordMessage)
}
return nil
}
// Shutdown tries to shut down the server gracefully.
func (s *Server) Shutdown() {
s.Lock()
s.isShuttingDown = true
s.Unlock()
s.listener.Close()
close(s.acceptConns)
}
func (s *Server) acceptClients() {
for {
conn, err := s.listener.Accept()
if err != nil {
s.Lock()
shutdown := s.isShuttingDown
s.Unlock()
if shutdown {
break
} else {
s.logger.Warn("Error accepting client", zap.Error(err))
continue
}
}
s.acceptConns <- conn
}
}
func (s *Server) manageSessions() {
for {
select {
case newConn := <-s.acceptConns:
// Gracefully handle acceptConns channel closing.
if newConn == nil {
s.Lock()
shutdown := s.isShuttingDown
s.Unlock()
if shutdown {
return
}
}
session := NewSession(s, newConn)
s.Lock()
s.sessions[newConn] = session
s.Unlock()
session.Start()
case delConn := <-s.deleteConns:
s.Lock()
delete(s.sessions, delConn)
s.Unlock()
}
}
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions.
func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
// Broadcast the data.
for _, session := range s.sessions {
if session == ignoredSession || !session.binariesDone {
continue
}
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, session.clientContext)
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
session.QueueSendNonBlocking(bf.Data())
}
}
func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
for _, c := range s.Channels {
for _, session := range c.sessions {
if session == ignoredSession || !session.binariesDone {
continue
}
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
pkt.Build(bf, session.clientContext)
session.QueueSendNonBlocking(bf.Data())
}
}
}
// BroadcastChatMessage broadcasts a simple chat message to all the sessions.
func (s *Server) BroadcastChatMessage(message string) {
bf := byteframe.NewByteFrame()
bf.SetLE()
msgBinChat := &binpacket.MsgBinChat{
Unk0: 0,
Type: 5,
Flags: 0x80,
Message: message,
SenderName: s.name,
}
msgBinChat.Build(bf)
s.BroadcastMHF(&mhfpacket.MsgSysCastedBinary{
CharID: 0xFFFFFFFF,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}, nil)
}
func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type uint8) {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint16(0) // Unk
bf.WriteUint16(0x43) // Data len
bf.WriteUint16(3) // Unk len
var text string
switch _type {
case 2:
text = "<Great Slaying: Berserk> is being held!"
case 4:
text = "<Great Slaying: Extreme> is being held!"
case 5:
text = "<Great Slaying: Berserk Practice> is being held!"
default:
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
}
ps.Uint16(bf, text, false)
bf.WriteBytes([]byte{0x5F, 0x53, 0x00})
bf.WriteUint32(ip) // IP address
bf.WriteUint16(port) // Port
bf.WriteUint16(0) // Unk
bf.WriteNullTerminatedBytes(stage)
bf.WriteBytes(make([]byte, 17))
s.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{
CharID: 0x00000000,
BroadcastType: BroadcastTypeSemaphore,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}, nil)
}
func (s *Server) DiscordChannelSend(charName string, content string) {
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
message := fmt.Sprintf("**%s** : %s", charName, content)
s.discordBot.RealtimeChannelSend(message)
}
}
func (s *Server) FindSessionByCharID(charID uint32) *Session {
for _, c := range s.Channels {
c.stagesLock.RLock()
for _, stage := range c.stages {
stage.RLock()
for client := range stage.clients {
if client.charID == charID {
stage.RUnlock()
return client
}
}
stage.RUnlock()
}
c.stagesLock.RUnlock()
}
return nil
}
func (s *Server) FindObjectByChar(charID uint32) *Object {
s.stagesLock.RLock()
defer s.stagesLock.RUnlock()
for _, stage := range s.stages {
stage.RLock()
for objId := range stage.objects {
obj := stage.objects[objId]
if obj.ownerCharID == charID {
stage.RUnlock()
return obj
}
}
stage.RUnlock()
}
return nil
}
func (s *Server) NextSemaphoreID() uint32 {
s.semaphoreIndex = s.semaphoreIndex + 1
return s.semaphoreIndex
}

View File

@@ -0,0 +1,76 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"sync"
)
// Stage holds stage-specific information
type Semaphore struct {
sync.RWMutex
// Stage ID string
id_semaphore string
id uint32
// Map of session -> charID.
// These are clients that are CURRENTLY in the stage
clients map[*Session]uint32
// Map of charID -> interface{}, only the key is used, value is always nil.
reservedClientSlots map[uint32]interface{}
// Max Players for Semaphore
maxPlayers uint16
}
// NewStage creates a new stage with intialized values.
func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore {
sema := &Semaphore{
id_semaphore: ID,
id: s.NextSemaphoreID(),
clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]interface{}),
maxPlayers: MaxPlayers,
}
return sema
}
func (s *Semaphore) BroadcastRavi(pkt mhfpacket.MHFPacket) {
// Broadcast the data.
for session := range s.clients {
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, session.clientContext)
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
session.QueueSendNonBlocking(bf.Data())
}
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage.
func (s *Semaphore) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
// Broadcast the data.
for session := range s.clients {
if session == ignoredSession {
continue
}
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, session.clientContext)
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
session.QueueSendNonBlocking(bf.Data())
}
}

View File

@@ -0,0 +1,259 @@
package channelserver
import (
"encoding/hex"
"fmt"
"io"
"net"
"sync"
"erupe-ce/common/byteframe"
"erupe-ce/common/stringstack"
"erupe-ce/common/stringsupport"
"erupe-ce/network"
"erupe-ce/network/clientctx"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
"golang.org/x/text/encoding/japanese"
)
// Session holds state for the channel server connection.
type Session struct {
sync.Mutex
logger *zap.Logger
server *Server
rawConn net.Conn
cryptConn *network.CryptConn
sendPackets chan []byte
clientContext *clientctx.ClientContext
myseries MySeries
stageID string
stage *Stage
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
stagePass string // Temporary storage
binariesDone bool
charID uint32
logKey []byte
sessionStart int64
rights uint32
token string
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet.
// A stack containing the stage movement history (push on enter/move, pop on back)
stageMoveStack *stringstack.StringStack
// Accumulated index used for identifying mail for a client
// I'm not certain why this is used, but since the client is sending it
// I want to rely on it for now as it might be important later.
mailAccIndex uint8
// Contains the mail list that maps accumulated indexes to mail IDs
mailList []int
// For Debuging
Name string
}
type MySeries struct {
houseTier []byte
houseData []byte
bookshelfData []byte
galleryData []byte
toreData []byte
gardenData []byte
state uint8
password string
}
// NewSession creates a new Session type.
func NewSession(server *Server, conn net.Conn) *Session {
s := &Session{
logger: server.logger.Named(conn.RemoteAddr().String()),
server: server,
rawConn: conn,
cryptConn: network.NewCryptConn(conn),
sendPackets: make(chan []byte, 20),
clientContext: &clientctx.ClientContext{
StrConv: &stringsupport.StringConverter{
Encoding: japanese.ShiftJIS,
},
},
binariesDone: false,
sessionStart: Time_Current_Adjusted().Unix(),
stageMoveStack: stringstack.New(),
}
return s
}
// Start starts the session packet send and recv loop(s).
func (s *Session) Start() {
go func() {
s.logger.Info("Channel server got connection!", zap.String("remoteaddr", s.rawConn.RemoteAddr().String()))
// Unlike the sign and entrance server,
// the client DOES NOT initalize the channel connection with 8 NULL bytes.
go s.sendLoop()
s.recvLoop()
}()
}
// QueueSend queues a packet (raw []byte) to be sent.
func (s *Session) QueueSend(data []byte) {
bf := byteframe.NewByteFrameFromBytes(data[:2])
s.logMessage(bf.ReadUint16(), data, "Server", s.Name)
s.sendPackets <- data
}
// QueueSendNonBlocking queues a packet (raw []byte) to be sent, dropping the packet entirely if the queue is full.
func (s *Session) QueueSendNonBlocking(data []byte) {
select {
case s.sendPackets <- data:
// Enqueued properly.
default:
// Couldn't enqueue, likely something wrong with the connection.
s.logger.Warn("Dropped packet for session because of full send buffer, something is probably wrong")
}
}
// QueueSendMHF queues a MHFPacket to be sent.
func (s *Session) QueueSendMHF(pkt mhfpacket.MHFPacket) {
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, s.clientContext)
// Queue it.
s.QueueSend(bf.Data())
}
// QueueAck is a helper function to queue an MSG_SYS_ACK with the given ack handle and data.
func (s *Session) QueueAck(ackHandle uint32, data []byte) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(network.MSG_SYS_ACK))
bf.WriteUint32(ackHandle)
bf.WriteBytes(data)
s.QueueSend(bf.Data())
}
func (s *Session) sendLoop() {
for {
// TODO(Andoryuuta): Test making this into a buffered channel and grouping the packet together before sending.
rawPacket := <-s.sendPackets
if rawPacket == nil {
s.logger.Debug("Got nil from s.SendPackets, exiting send loop")
return
}
// Make a copy of the data.
terminatedPacket := make([]byte, len(rawPacket))
copy(terminatedPacket, rawPacket)
// Append the MSG_SYS_END tailing opcode.
terminatedPacket = append(terminatedPacket, []byte{0x00, 0x10}...)
s.cryptConn.SendPacket(terminatedPacket)
}
}
func (s *Session) recvLoop() {
for {
pkt, err := s.cryptConn.ReadPacket()
if err == io.EOF {
s.logger.Info(fmt.Sprintf("[%s] Disconnected", s.Name))
logoutPlayer(s)
return
}
if err != nil {
s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err))
logoutPlayer(s)
return
}
s.handlePacketGroup(pkt)
}
}
func (s *Session) handlePacketGroup(pktGroup []byte) {
bf := byteframe.NewByteFrameFromBytes(pktGroup)
opcodeUint16 := bf.ReadUint16()
opcode := network.PacketID(opcodeUint16)
// This shouldn't be needed, but it's better to recover and let the connection die than to panic the server.
defer func() {
if r := recover(); r != nil {
fmt.Printf("[%s]", s.Name)
fmt.Println("Recovered from panic", r)
}
}()
s.logMessage(opcodeUint16, pktGroup, s.Name, "Server")
if opcode == network.MSG_SYS_LOGOUT {
s.rawConn.Close()
}
// Get the packet parser and handler for this opcode.
mhfPkt := mhfpacket.FromOpcode(opcode)
if mhfPkt == nil {
fmt.Println("Got opcode which we don't know how to parse, can't parse anymore for this group")
return
}
// Parse the packet.
err := mhfPkt.Parse(bf, s.clientContext)
if err != nil {
fmt.Printf("\n!!! [%s] %s NOT IMPLEMENTED !!! \n\n\n", s.Name, opcode)
return
}
// Handle the packet.
handlerTable[opcode](s, mhfPkt)
// If there is more data on the stream that the .Parse method didn't read, then read another packet off it.
remainingData := bf.DataFromCurrent()
if len(remainingData) >= 2 {
s.handlePacketGroup(remainingData)
}
}
func ignored(opcode network.PacketID) bool {
ignoreList := []network.PacketID{
network.MSG_SYS_END,
network.MSG_SYS_PING,
network.MSG_SYS_NOP,
network.MSG_SYS_TIME,
network.MSG_SYS_EXTEND_THRESHOLD,
network.MSG_SYS_POSITION_OBJECT,
network.MSG_MHF_ENUMERATE_QUEST,
network.MSG_MHF_SAVEDATA,
}
set := make(map[network.PacketID]struct{}, len(ignoreList))
for _, s := range ignoreList {
set[s] = struct{}{}
}
_, r := set[opcode]
return r
}
func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipient string) {
if !s.server.erupeConfig.DevMode {
return
}
if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
return
} else if !s.server.erupeConfig.DevModeOptions.LogInboundMessages {
return
}
opcodePID := network.PacketID(opcode)
if ignored(opcodePID) {
return
}
fmt.Printf("[%s] -> [%s]\n", sender, recipient)
fmt.Printf("Opcode: %s\n", opcodePID)
if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength {
fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data))
} else {
fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data))
}
}

View File

@@ -0,0 +1,134 @@
package channelserver
import (
"sync"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
// Object holds infomation about a specific object.
type Object struct {
sync.RWMutex
id uint32
ownerCharID uint32
x, y, z float32
binary []byte
}
// stageBinaryKey is a struct used as a map key for identifying a stage binary part.
type stageBinaryKey struct {
id0 uint8
id1 uint8
}
// Stage holds stage-specific information
type Stage struct {
sync.RWMutex
// Stage ID string
id string
// Objects
objects map[uint32]*Object
objectIndex uint32
// Map of session -> charID.
// These are clients that are CURRENTLY in the stage
clients map[*Session]uint32
// Map of charID -> bool, key represents whether they are ready
// These are clients that aren't in the stage, but have reserved a slot (for quests, etc).
reservedClientSlots map[uint32]bool
// These are raw binary blobs that the stage owner sets,
// other clients expect the server to echo them back in the exact same format.
rawBinaryData map[stageBinaryKey][]byte
maxPlayers uint16
password string
createdAt string
}
// NewStage creates a new stage with intialized values.
func NewStage(ID string) *Stage {
s := &Stage{
id: ID,
clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]bool),
objects: make(map[uint32]*Object),
objectIndex: 0,
rawBinaryData: make(map[stageBinaryKey][]byte),
maxPlayers: 4,
createdAt: time.Now().Format("01-02-2006 15:04:05"),
}
return s
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage.
func (s *Stage) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
// Broadcast the data.
for session := range s.clients {
if session == ignoredSession || !session.binariesDone {
continue
}
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, session.clientContext)
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
session.QueueSendNonBlocking(bf.Data())
}
}
func (s *Stage) isCharInQuestByID(charID uint32) bool {
if _, exists := s.reservedClientSlots[charID]; exists {
return exists
}
return false
}
func (s *Stage) isQuest() bool {
return len(s.reservedClientSlots) > 0
}
func (s *Stage) GetName() string {
switch s.id {
case MezeportaStageId:
return "Mezeporta"
case GuildHallLv1StageId:
return "Guild Hall Lv1"
case GuildHallLv2StageId:
return "Guild Hall Lv2"
case GuildHallLv3StageId:
return "Guild Hall Lv3"
case PugiFarmStageId:
return "Pugi Farm"
case RastaBarStageId:
return "Rasta Bar"
case PalloneCaravanStageId:
return "Pallone Caravan"
case GookFarmStageId:
return "Gook Farm"
case DivaFountainStageId:
return "Diva Fountain"
case DivaHallStageId:
return "Diva Hall"
case MezFesStageId:
return "Mez Fes"
default:
return ""
}
}
func (s *Stage) NextObjectID() uint32 {
s.objectIndex = s.objectIndex + 1
return s.objectIndex
}

View File

@@ -0,0 +1,48 @@
package channelserver
import (
"fmt"
"time"
)
var (
Offset = 9
YearAdjust = -7
MonthAdjust = 0
DayAdjust = 0
)
var (
TimeStatic = time.Time{}
)
func Time_Current() time.Time {
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60))
return baseTime
}
func Time_Current_Adjusted() time.Time {
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust)
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), baseTime.Hour(), baseTime.Minute(), baseTime.Second(), baseTime.Nanosecond(), baseTime.Location())
}
func Time_Current_Midnight() time.Time {
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust)
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location())
}
func Time_Current_Week_uint8() uint8 {
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust)
_, thisWeek := baseTime.ISOWeek()
_, beginningOfTheMonth := time.Date(baseTime.Year(), baseTime.Month(), 1, 0, 0, 0, 0, baseTime.Location()).ISOWeek()
return uint8(1 + thisWeek - beginningOfTheMonth)
}
func Time_static() time.Time {
if TimeStatic.IsZero() {
TimeStatic = Time_Current_Adjusted()
}
return TimeStatic
}

View File

@@ -0,0 +1,87 @@
package timeserver
import (
"time"
)
var DoOnce_midnight = false
var DoOnce_t2 = false
var DoOnce_t = false
var Fix_midnight = time.Time{}
var Fix_t2 = time.Time{}
var Fix_t = time.Time{}
var Pfixtimer time.Duration
var Pnewtime = 0
var yearsFixed = -7
func PFadd_time() time.Duration {
Pnewtime = Pnewtime + 24
Pfixtimer = time.Duration(Pnewtime)
return Pfixtimer
}
func Time_static() time.Time {
if !DoOnce_t {
DoOnce_t = true
// Force to 201x
tFix1 := time.Now()
tFix2 := tFix1.AddDate(yearsFixed, 0, 0)
Fix_t = tFix2.In(time.FixedZone("UTC+1", 1*60*60))
}
return Fix_t
}
func Tstatic_midnight() time.Time {
if !DoOnce_midnight {
DoOnce_midnight = true
// Force to 201x
tFix1 := time.Now()
tFix2 := tFix1.AddDate(yearsFixed, 0, 0)
var tFix = tFix2.In(time.FixedZone("UTC+1", 1*60*60))
yearFix, monthFix, dayFix := tFix2.Date()
Fix_midnight = time.Date(yearFix, monthFix, dayFix, 0, 0, 0, 0, tFix.Location()).Add(time.Hour)
}
return Fix_midnight
}
func Time_midnight() time.Time {
// Force to 201x
t1 := time.Now()
t2 := t1.AddDate(yearsFixed, 0, 0)
var t = t2.In(time.FixedZone("UTC+1", 1*60*60))
year, month, day := t2.Date()
midnight := time.Date(year, month, day, 0, 0, 0, 0, t.Location()).Add(time.Hour)
return midnight
}
func TimeCurrent() time.Time {
// Force to 201x
t1 := time.Now()
t2 := t1.AddDate(yearsFixed, 0, 0)
var t = t2.In(time.FixedZone("UTC+1", 1*60*60))
return t
}
func Time_Current_Week_uint8() uint8 {
beginningOfTheMonth := time.Date(TimeCurrent().Year(), TimeCurrent().Month(), 1, 1, 1, 1, 1, time.UTC)
_, thisWeek := TimeCurrent().ISOWeek()
_, beginningWeek := beginningOfTheMonth.ISOWeek()
return uint8(1 + thisWeek - beginningWeek)
}
func Time_Current_Week_uint32() uint32 {
beginningOfTheMonth := time.Date(TimeCurrent().Year(), TimeCurrent().Month(), 1, 1, 1, 1, 1, time.UTC)
_, thisWeek := TimeCurrent().ISOWeek()
_, beginningWeek := beginningOfTheMonth.ISOWeek()
result := 1 + thisWeek - beginningWeek
return uint32(result)
}
func Detect_Day() bool {
switch time.Now().Weekday() {
case time.Wednesday:
return true
}
return false
}