Merge branch 'main' into feature/warehouse-v2

This commit is contained in:
wish
2024-02-20 17:50:04 +11:00
84 changed files with 1894 additions and 3345 deletions

View File

@@ -32,7 +32,7 @@ func stubEnumerateNoResults(s *Session, ackHandle uint32) {
func doAckEarthSucceed(s *Session, ackHandle uint32, data []*byteframe.ByteFrame) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(s.server.erupeConfig.DevModeOptions.EarthIDOverride))
bf.WriteUint32(uint32(s.server.erupeConfig.EarthID))
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(uint32(len(data)))
@@ -128,7 +128,7 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLogin)
if !s.server.erupeConfig.DevModeOptions.DisableTokenCheck {
if !s.server.erupeConfig.DebugOptions.DisableTokenCheck {
var token string
err := s.server.db.QueryRow("SELECT token FROM sign_sessions ss INNER JOIN public.users u on ss.user_id = u.id WHERE token=$1 AND ss.id=$2 AND u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.LoginTokenString, pkt.LoginTokenNumber, pkt.CharID0).Scan(&token)
if err != nil {
@@ -1148,9 +1148,9 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(TimeWeekStart().Unix())) // Start
bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride)
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride)
for i, m := range s.server.erupeConfig.DevModeOptions.EarthMonsterOverride {
bf.WriteInt32(s.server.erupeConfig.EarthStatus)
bf.WriteInt32(s.server.erupeConfig.EarthID)
for i, m := range s.server.erupeConfig.EarthMonsters {
if _config.ErupeConfig.RealClientMode <= _config.G9 {
if i == 3 {
break

View File

@@ -94,7 +94,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
}
bf.WriteUint32(cafeTime) // Total cafe time
bf.WriteUint16(0)
ps.Uint16(bf, fmt.Sprintf(s.server.dict["cafeReset"], int(cafeReset.Month()), cafeReset.Day()), true)
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -1,8 +1,10 @@
package channelserver
import (
"crypto/rand"
"encoding/hex"
"erupe-ce/common/byteframe"
"erupe-ce/common/mhfcid"
"erupe-ce/common/mhfcourse"
"erupe-ce/common/token"
"erupe-ce/config"
@@ -58,7 +60,7 @@ func init() {
}
func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.disabled, cmd.Name))
}
func sendServerChatMessage(s *Session, message string) {
@@ -86,28 +88,97 @@ func sendServerChatMessage(s *Session, message string) {
func parseChatCommand(s *Session, command string) {
args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ")
switch args[0] {
case commands["Ban"].Prefix:
if s.isOp() {
if len(args) > 1 {
var expiry time.Time
if len(args) > 2 {
var length int
var unit string
n, err := fmt.Sscanf(args[2], `%d%s`, &length, &unit)
if err == nil && n == 2 {
switch unit {
case "s", "second", "seconds":
expiry = time.Now().Add(time.Duration(length) * time.Second)
case "m", "mi", "minute", "minutes":
expiry = time.Now().Add(time.Duration(length) * time.Minute)
case "h", "hour", "hours":
expiry = time.Now().Add(time.Duration(length) * time.Hour)
case "d", "day", "days":
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24)
case "mo", "month", "months":
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 30)
case "y", "year", "years":
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 365)
}
} else {
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
return
}
}
cid := mhfcid.ConvertCID(args[1])
if cid > 0 {
var uid uint32
var uname string
err := s.server.db.QueryRow(`SELECT id, username FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, cid).Scan(&uid, &uname)
if err == nil {
if expiry.IsZero() {
s.server.db.Exec(`INSERT INTO bans VALUES ($1)
ON CONFLICT (user_id) DO UPDATE SET expires=NULL`, uid)
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname))
} else {
s.server.db.Exec(`INSERT INTO bans VALUES ($1, $2)
ON CONFLICT (user_id) DO UPDATE SET expires=$2`, uid, expiry)
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname)+fmt.Sprintf(s.server.i18n.commands.ban.length, expiry.Format(time.DateTime)))
}
s.server.DisconnectUser(uid)
} else {
sendServerChatMessage(s, s.server.i18n.commands.ban.noUser)
}
} else {
sendServerChatMessage(s, s.server.i18n.commands.ban.invalid)
}
} else {
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
}
} else {
sendServerChatMessage(s, s.server.i18n.commands.noOp)
}
case commands["Timer"].Prefix:
if commands["Timer"].Enabled || s.isOp() {
var state bool
s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&state)
s.server.db.Exec(`UPDATE users u SET timer=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, !state, s.charID)
if state {
sendServerChatMessage(s, s.server.i18n.commands.timer.disabled)
} else {
sendServerChatMessage(s, s.server.i18n.commands.timer.enabled)
}
} else {
sendDisabledCommandMessage(s, commands["Timer"])
}
case commands["PSN"].Prefix:
if commands["PSN"].Enabled {
if commands["PSN"].Enabled || s.isOp() {
if len(args) > 1 {
var exists int
s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists)
if exists == 0 {
_, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID)
if err == nil {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], args[1]))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.success, args[1]))
}
} else {
sendServerChatMessage(s, s.server.dict["commandPSNExists"])
sendServerChatMessage(s, s.server.i18n.commands.psn.exists)
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.error, commands["PSN"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["PSN"])
}
case commands["Reload"].Prefix:
if commands["Reload"].Enabled {
sendServerChatMessage(s, s.server.dict["commandReload"])
if commands["Reload"].Enabled || s.isOp() {
sendServerChatMessage(s, s.server.i18n.commands.reload)
var temp mhfpacket.MHFPacket
deleteNotif := byteframe.NewByteFrame()
for _, object := range s.stage.objects {
@@ -167,21 +238,21 @@ func parseChatCommand(s *Session, command string) {
sendDisabledCommandMessage(s, commands["Reload"])
}
case commands["KeyQuest"].Prefix:
if commands["KeyQuest"].Enabled {
if commands["KeyQuest"].Enabled || s.isOp() {
if s.server.erupeConfig.RealClientMode < _config.G10 {
sendServerChatMessage(s, s.server.dict["commandKqfVersion"])
sendServerChatMessage(s, s.server.i18n.commands.kqf.version)
} else {
if len(args) > 1 {
if args[1] == "get" {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.get, s.kqf))
} else if args[1] == "set" {
if len(args) > 2 && len(args[2]) == 16 {
hexd, _ := hex.DecodeString(args[2])
s.kqf = hexd
s.kqfOverride = true
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"])
sendServerChatMessage(s, s.server.i18n.commands.kqf.set.success)
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.set.error, commands["KeyQuest"].Prefix))
}
}
}
@@ -190,23 +261,23 @@ func parseChatCommand(s *Session, command string) {
sendDisabledCommandMessage(s, commands["KeyQuest"])
}
case commands["Rights"].Prefix:
if commands["Rights"].Enabled {
if commands["Rights"].Enabled || s.isOp() {
if len(args) > 1 {
v, _ := strconv.Atoi(args[1])
_, 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(s.server.dict["commandRightsSuccess"], v))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v))
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Rights"])
}
case commands["Course"].Prefix:
if commands["Course"].Enabled {
if commands["Course"].Enabled || s.isOp() {
if len(args) > 1 {
for _, course := range mhfcourse.Courses() {
for _, alias := range course.Aliases() {
@@ -224,11 +295,11 @@ func parseChatCommand(s *Session, command string) {
})
if ei != -1 {
delta = uint32(-1 * math.Pow(2, float64(course.ID)))
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases()[0]))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.disabled, course.Aliases()[0]))
}
} else {
delta = uint32(math.Pow(2, float64(course.ID)))
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases()[0]))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.enabled, course.Aliases()[0]))
}
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
if err == nil {
@@ -236,71 +307,71 @@ func parseChatCommand(s *Session, command string) {
}
updateRights(s)
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases()[0]))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.locked, course.Aliases()[0]))
}
return
}
}
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.error, commands["Course"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Course"])
}
case commands["Raviente"].Prefix:
if commands["Raviente"].Enabled {
if commands["Raviente"].Enabled || s.isOp() {
if len(args) > 1 {
if s.server.getRaviSemaphore() != nil {
switch args[1] {
case "start":
if s.server.raviente.register[1] == 0 {
s.server.raviente.register[1] = s.server.raviente.register[3]
sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.success)
s.notifyRavi()
} else {
sendServerChatMessage(s, s.server.dict["commandRaviStartError"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.error)
}
case "cm", "check", "checkmultiplier", "multiplier":
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.GetRaviMultiplier()))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ravi.multiplier, s.server.GetRaviMultiplier()))
case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed":
if s.server.erupeConfig.RealClientMode == _config.ZZ {
switch args[1] {
case "sr", "sendres", "resurrection":
if s.server.raviente.state[28] > 0 {
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.success)
s.server.raviente.state[28] = 0
} else {
sendServerChatMessage(s, s.server.dict["commandRaviResError"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.error)
}
case "ss", "sendsed":
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.sed.success)
// Total BerRavi HP
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
s.server.raviente.support[1] = HP
case "rs", "reqsed":
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.request)
// Total BerRavi HP
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
s.server.raviente.support[1] = HP + 1
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviVersion"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.version)
}
default:
sendServerChatMessage(s, s.server.dict["commandRaviError"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.noPlayers)
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviError"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
}
} else {
sendDisabledCommandMessage(s, commands["Raviente"])
}
case commands["Teleport"].Prefix:
if commands["Teleport"].Enabled {
if commands["Teleport"].Enabled || s.isOp() {
if len(args) > 2 {
x, _ := strconv.ParseInt(args[1], 10, 16)
y, _ := strconv.ParseInt(args[2], 10, 16)
@@ -315,17 +386,31 @@ func parseChatCommand(s *Session, command string) {
MessageType: BinaryMessageTypeState,
RawDataPayload: payloadBytes,
})
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.success, x, y))
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.error, commands["Teleport"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Teleport"])
}
case commands["Discord"].Prefix:
if commands["Discord"].Enabled || s.isOp() {
var _token string
err := s.server.db.QueryRow(`SELECT discord_token FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&_token)
if err != nil {
randToken := make([]byte, 4)
rand.Read(randToken)
_token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:])
s.server.db.Exec(`UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, _token, s.charID)
}
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.discord.success, _token))
} else {
sendDisabledCommandMessage(s, commands["Discord"])
}
case commands["Help"].Prefix:
if commands["Help"].Enabled {
if commands["Help"].Enabled || s.isOp() {
for _, command := range commands {
if command.Enabled {
if command.Enabled || s.isOp() {
sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description))
}
}
@@ -341,14 +426,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
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))
var timer bool
s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&timer)
if timer {
_ = tmp.ReadBytes(9)
tmp.SetLE()
frame := tmp.ReadUint32()
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.timer, frame/30/60/60, frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame))
}
}
}
if s.server.erupeConfig.DevModeOptions.QuestDebugTools == true && s.server.erupeConfig.DevMode {
if s.server.erupeConfig.DebugOptions.QuestTools {
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x02 && len(pkt.RawDataPayload) > 32 {
// This is only correct most of the time
tmp.ReadBytes(20)
@@ -362,24 +451,20 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
// Parse out the real casted binary payload
var msgBinTargeted *binpacket.MsgBinTargeted
var authorLen, msgLen uint16
var msg []byte
isDiceCommand := false
var message, author string
var returnToSender bool
if pkt.MessageType == BinaryMessageTypeChat {
tmp.SetLE()
tmp.Seek(int64(0), 0)
_ = tmp.ReadUint32()
authorLen = tmp.ReadUint16()
msgLen = tmp.ReadUint16()
msg = tmp.ReadNullTerminatedBytes()
tmp.Seek(8, 0)
message = string(tmp.ReadNullTerminatedBytes())
author = string(tmp.ReadNullTerminatedBytes())
}
// Customise payload
realPayload := pkt.RawDataPayload
if pkt.BroadcastType == BroadcastTypeTargeted {
tmp.SetBE()
tmp.Seek(int64(0), 0)
tmp.Seek(0, 0)
msgBinTargeted = &binpacket.MsgBinTargeted{}
err := msgBinTargeted.Parse(tmp)
if err != nil {
@@ -388,18 +473,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
}
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)
dice := fmt.Sprintf("%d", token.RNG().Intn(100)+1)
roll.WriteUint16(uint16(len(dice) + 1))
roll.WriteNullTerminatedBytes([]byte(dice))
roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes())
realPayload = roll.Data()
if message == "@dice" {
returnToSender = true
m := binpacket.MsgBinChat{
Type: BinaryMessageTypeChat,
Flags: 4,
Message: fmt.Sprintf(`%d`, token.RNG().Intn(100)+1),
SenderName: author,
}
bf := byteframe.NewByteFrame()
bf.SetLE()
m.Build(bf)
realPayload = bf.Data()
} else {
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
bf.SetLE()
@@ -428,8 +513,8 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
case BroadcastTypeWorld:
s.server.WorldcastMHF(resp, s, nil)
case BroadcastTypeStage:
if isDiceCommand {
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
if returnToSender {
s.stage.BroadcastMHF(resp, nil)
} else {
s.stage.BroadcastMHF(resp, s)
}

View File

@@ -97,6 +97,17 @@ func getPointers() map[SavePointer]int {
pointers[pGalleryData] = 72064
pointers[pGardenData] = 74424
pointers[pRP] = 74614
case _config.S6:
pointers[pWeaponID] = 12522
pointers[pWeaponType] = 12789
pointers[pHouseTier] = 13900
pointers[pToreData] = 14228
pointers[pHRP] = 14550
pointers[pHouseData] = 14561
pointers[pBookshelfData] = 9118 // Probably same here
pointers[pGalleryData] = 24064
pointers[pGardenData] = 26424
pointers[pRP] = 26614
}
if _config.ErupeConfig.RealClientMode == _config.G5 {
pointers[lBookshelfData] = 5548
@@ -212,7 +223,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
save.Gender = false
}
if !save.IsNewCharacter {
if _config.ErupeConfig.RealClientMode >= _config.F4 {
if _config.ErupeConfig.RealClientMode >= _config.S6 {
save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2])
save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5]
save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195]

View File

@@ -1,6 +1,7 @@
package channelserver
import (
"erupe-ce/common/mhfmon"
"erupe-ce/common/stringsupport"
_config "erupe-ce/config"
"fmt"
@@ -45,7 +46,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
if s.server.erupeConfig.DevModeOptions.SaveDumps.RawEnabled {
if s.server.erupeConfig.SaveDumps.RawEnabled {
dumpSaveData(s, saveData, "raw-savedata")
}
s.logger.Info("Updating save with blob")
@@ -112,11 +113,11 @@ func grpToGR(n int) uint16 {
}
func dumpSaveData(s *Session, data []byte, suffix string) {
if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled {
if !s.server.erupeConfig.SaveDumps.Enabled {
return
} else {
dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID))
path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID), fmt.Sprintf("%d_%s.bin", s.charID, suffix))
dir := filepath.Join(s.server.erupeConfig.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID))
path := filepath.Join(s.server.erupeConfig.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID), fmt.Sprintf("%d_%s.bin", s.charID, suffix))
_, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
@@ -1042,34 +1043,34 @@ func handleMsgMhfGetPaperData(s *Session, p mhfpacket.MHFPacket) {
{1105, 1, 10, 500, 0, 0, 0},
{1105, 2, 10, 500, 0, 0, 0},
// setServerBoss
{2001, 1, 17, 58, 0, 6, 700},
{2001, 1, 20, 58, 0, 3, 200},
{2001, 1, 22, 58, 0, 7, 250},
{2001, 1, 27, 58, 0, 1, 100},
{2001, 1, 53, 58, 0, 8, 1000},
{2001, 1, 67, 58, 0, 9, 500},
{2001, 1, 68, 58, 0, 2, 150},
{2001, 1, 74, 58, 0, 4, 200},
{2001, 1, 75, 58, 0, 5, 500},
{2001, 1, 76, 58, 0, 10, 800},
{2001, 1, 80, 58, 0, 11, 900},
{2001, 1, 89, 58, 0, 12, 600},
{2001, 2, 17, 60, 0, 6, 700},
{2001, 2, 20, 60, 0, 3, 200},
{2001, 2, 22, 60, 0, 7, 350},
{2001, 2, 27, 60, 0, 1, 100},
{2001, 2, 39, 60, 0, 13, 200},
{2001, 2, 40, 60, 0, 15, 600},
{2001, 2, 53, 60, 0, 8, 1000},
{2001, 2, 67, 60, 0, 2, 500},
{2001, 2, 68, 60, 0, 9, 150},
{2001, 2, 74, 60, 0, 4, 200},
{2001, 2, 75, 60, 0, 5, 500},
{2001, 2, 76, 60, 0, 10, 800},
{2001, 2, 80, 60, 0, 11, 900},
{2001, 2, 81, 60, 0, 14, 900},
{2001, 2, 89, 60, 0, 12, 600},
{2001, 2, 94, 60, 0, 16, 1000},
{2001, 1, mhfmon.Gravios, 58, 0, 6, 700},
{2001, 1, mhfmon.Gypceros, 58, 0, 3, 200},
{2001, 1, mhfmon.Basarios, 58, 0, 7, 250},
{2001, 1, mhfmon.Velocidrome, 58, 0, 1, 100},
{2001, 1, mhfmon.Rajang, 58, 0, 8, 1000},
{2001, 1, mhfmon.ShogunCeanataur, 58, 0, 9, 500},
{2001, 1, mhfmon.Bulldrome, 58, 0, 2, 150},
{2001, 1, mhfmon.Hypnocatrice, 58, 0, 4, 200},
{2001, 1, mhfmon.Lavasioth, 58, 0, 5, 500},
{2001, 1, mhfmon.Tigrex, 58, 0, 10, 800},
{2001, 1, mhfmon.Espinas, 58, 0, 11, 900},
{2001, 1, mhfmon.Pariapuria, 58, 0, 12, 600},
{2001, 2, mhfmon.Gravios, 60, 0, 6, 700},
{2001, 2, mhfmon.Gypceros, 60, 0, 3, 200},
{2001, 2, mhfmon.Basarios, 60, 0, 7, 350},
{2001, 2, mhfmon.Velocidrome, 60, 0, 1, 100},
{2001, 2, mhfmon.PurpleGypceros, 60, 0, 13, 200},
{2001, 2, mhfmon.YianGaruga, 60, 0, 15, 600},
{2001, 2, mhfmon.Rajang, 60, 0, 8, 1000},
{2001, 2, mhfmon.ShogunCeanataur, 60, 0, 2, 500},
{2001, 2, mhfmon.Bulldrome, 60, 0, 9, 150},
{2001, 2, mhfmon.Hypnocatrice, 60, 0, 4, 200},
{2001, 2, mhfmon.Lavasioth, 60, 0, 5, 500},
{2001, 2, mhfmon.Tigrex, 60, 0, 10, 800},
{2001, 2, mhfmon.Espinas, 60, 0, 11, 900},
{2001, 2, mhfmon.BurningEspinas, 60, 0, 14, 900},
{2001, 2, mhfmon.Pariapuria, 60, 0, 12, 600},
{2001, 2, mhfmon.Dyuragaua, 60, 0, 16, 1000},
}
case 6:
paperData = []PaperData{

View File

@@ -3,6 +3,7 @@ package channelserver
import (
"fmt"
"github.com/bwmarrin/discordgo"
"golang.org/x/crypto/bcrypt"
"sort"
"strings"
"unicode"
@@ -66,10 +67,56 @@ func getCharacterList(s *Server) string {
return message
}
// onInteraction handles slash commands
func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCreate) {
switch i.Interaction.ApplicationCommandData().Name {
case "link":
var temp string
err := s.db.QueryRow(`UPDATE users SET discord_id = $1 WHERE discord_token = $2 RETURNING discord_id`, i.Member.User.ID, i.ApplicationCommandData().Options[0].StringValue()).Scan(&temp)
if err == nil {
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Your Erupe account was linked successfully.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
} else {
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Failed to link Erupe account.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
}
case "password":
password, _ := bcrypt.GenerateFromPassword([]byte(i.ApplicationCommandData().Options[0].StringValue()), 10)
_, err := s.db.Exec(`UPDATE users SET password = $1 WHERE discord_id = $2`, password, i.Member.User.ID)
if err == nil {
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Your Erupe account password has been updated.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
} else {
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Failed to update Erupe account password.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
}
}
}
// 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.Bot || m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
// Ignore messages from bots, or messages that are not in the correct channel.
if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RelayChannel.RelayChannelID {
return
}
@@ -79,11 +126,24 @@ func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCre
}
return r
}, m.Author.Username))
for i := 0; i < 8-len(m.Author.Username); i++ {
paddedName += " "
}
message := s.discordBot.NormalizeDiscordMessage(fmt.Sprintf("[D] %s > %s", paddedName, m.Content))
if len(message) > s.erupeConfig.Discord.RelayChannel.MaxMessageLength {
return
}
message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content)
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
var messages []string
lineLength := 61
for i := 0; i < len(message); i += lineLength {
end := i + lineLength
if end > len(message) {
end = len(message)
}
messages = append(messages, message[i:end])
}
for i := range messages {
s.BroadcastChatMessage(messages[i])
}
}

View File

@@ -70,8 +70,8 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
}
var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
if s.server.erupeConfig.DebugOptions.DivaOverride >= 0 {
if s.server.erupeConfig.DebugOptions.DivaOverride == 0 {
if s.server.erupeConfig.RealClientMode >= _config.Z2 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
} else {
@@ -79,7 +79,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
}
return
}
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true)
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.DivaOverride), true)
} else {
timestamps = generateDivaTimestamps(s, start, false)
}

View File

@@ -36,7 +36,7 @@ func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRanking)
bf := byteframe.NewByteFrame()
state := s.server.erupeConfig.DevModeOptions.TournamentEvent
state := s.server.erupeConfig.DebugOptions.TournamentOverride
// Unk
// Unk
// Start?
@@ -95,8 +95,9 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
func cleanupFesta(s *Session) {
s.server.db.Exec("DELETE FROM events WHERE event_type='festa'")
s.server.db.Exec("DELETE FROM festa_registrations")
s.server.db.Exec("DELETE FROM festa_submissions")
s.server.db.Exec("DELETE FROM festa_prizes_accepted")
s.server.db.Exec("UPDATE guild_characters SET souls=0, trial_vote=NULL")
s.server.db.Exec("UPDATE guild_characters SET trial_vote=NULL")
}
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
@@ -141,13 +142,13 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
}
type FestaTrial struct {
ID uint32 `db:"id"`
Objective uint16 `db:"objective"`
GoalID uint32 `db:"goal_id"`
TimesReq uint16 `db:"times_req"`
Locale uint16 `db:"locale_req"`
Reward uint16 `db:"reward"`
Monopoly FestivalColour `db:"monopoly"`
ID uint32 `db:"id"`
Objective uint16 `db:"objective"`
GoalID uint32 `db:"goal_id"`
TimesReq uint16 `db:"times_req"`
Locale uint16 `db:"locale_req"`
Reward uint16 `db:"reward"`
Monopoly FestivalColor `db:"monopoly"`
Unk uint16
}
@@ -173,12 +174,12 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
}
var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 {
if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 {
if s.server.erupeConfig.DebugOptions.FestaOverride >= 0 {
if s.server.erupeConfig.DebugOptions.FestaOverride == 0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true)
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.FestaOverride), true)
} else {
timestamps = generateFestaTimestamps(s, start, false)
}
@@ -233,8 +234,10 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(trial.TimesReq)
bf.WriteUint16(trial.Locale)
bf.WriteUint16(trial.Reward)
bf.WriteInt16(int16(FestivalColourCodes[trial.Monopoly]))
bf.WriteUint16(trial.Unk)
bf.WriteInt16(FestivalColorCodes[trial.Monopoly])
if _config.ErupeConfig.RealClientMode >= _config.F4 { // Not in S6.0
bf.WriteUint16(trial.Unk)
}
}
// The Winner and Loser Armor IDs are missing
@@ -291,26 +294,49 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
}
bf.WriteUint16(500)
categoryWinners := uint16(0) // NYI
bf.WriteUint16(categoryWinners)
for i := uint16(0); i < categoryWinners; i++ {
bf.WriteUint32(0) // Guild ID
bf.WriteUint16(i + 1) // Category ID
bf.WriteUint16(0) // Festa Team
ps.Uint8(bf, "", true) // Guild Name
}
dailyWinners := uint16(0) // NYI
bf.WriteUint16(dailyWinners)
for i := uint16(0); i < dailyWinners; i++ {
bf.WriteUint32(0) // Guild ID
bf.WriteUint16(i + 1) // Category ID
bf.WriteUint16(0) // Festa Team
ps.Uint8(bf, "", true) // Guild Name
var temp uint32
bf.WriteUint16(4)
for i := uint16(0); i < 4; i++ {
var guildID uint32
var guildName string
var guildTeam = FestivalColorNone
s.server.db.QueryRow(`
SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _
FROM festa_submissions fs
LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id
LEFT JOIN guilds g ON fs.guild_id = g.id
WHERE fs.trial_type = $1
GROUP BY fs.guild_id, g.name, fr.team
ORDER BY _ DESC LIMIT 1
`, i+1).Scan(&guildID, &guildName, &guildTeam, &temp)
bf.WriteUint32(guildID)
bf.WriteUint16(i + 1)
bf.WriteInt16(FestivalColorCodes[guildTeam])
ps.Uint8(bf, guildName, true)
}
bf.WriteUint16(7)
for i := uint16(0); i < 7; i++ {
var guildID uint32
var guildName string
var guildTeam = FestivalColorNone
offset := 86400 * uint32(i)
s.server.db.QueryRow(`
SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _
FROM festa_submissions fs
LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id
LEFT JOIN guilds g ON fs.guild_id = g.id
WHERE EXTRACT(EPOCH FROM fs.timestamp)::int > $1 AND EXTRACT(EPOCH FROM fs.timestamp)::int < $2
GROUP BY fs.guild_id, g.name, fr.team
ORDER BY _ DESC LIMIT 1
`, timestamps[1]+offset, timestamps[1]+offset+86400).Scan(&guildID, &guildName, &guildTeam, &temp)
bf.WriteUint32(guildID)
bf.WriteUint16(i + 1)
bf.WriteInt16(FestivalColorCodes[guildTeam])
ps.Uint8(bf, guildName, true)
}
bf.WriteUint32(0) // Clan goal
// Final bonus rates
bf.WriteUint32(1) // 5000-Infinity?
bf.WriteUint32(5000) // 5000+ souls
bf.WriteUint32(2000) // 2000-4999 souls
bf.WriteUint32(1000) // 1000-1999 souls
@@ -321,7 +347,9 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(100) // Normal rate
bf.WriteUint16(50) // 50% penalty
ps.Uint16(bf, "", false)
if _config.ErupeConfig.RealClientMode >= _config.G52 {
ps.Uint16(bf, "", false)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -349,7 +377,6 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
bf.WriteBool(false)
bf.WriteBool(true)
}
bf.WriteUint16(0) // Unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -364,18 +391,18 @@ func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
resp := byteframe.NewByteFrame()
if err != nil || guild == nil || applicant {
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0xFFFFFFFF)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteInt32(0)
resp.WriteInt32(-1)
resp.WriteInt32(0)
resp.WriteInt32(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
return
}
resp.WriteUint32(guild.Souls)
resp.WriteUint32(1) // unk
resp.WriteUint32(1) // unk
resp.WriteUint32(1) // unk, rank?
resp.WriteUint32(1) // unk
resp.WriteInt32(1) // unk
resp.WriteInt32(1) // unk, rank?
resp.WriteInt32(1) // unk
resp.WriteInt32(1) // unk
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
@@ -391,15 +418,26 @@ func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(members)))
bf.WriteUint16(0) // Unk
sort.Slice(members, func(i, j int) bool {
return members[i].Souls > members[j].Souls
})
var validMembers []*GuildMember
for _, member := range members {
if member.Souls > 0 {
validMembers = append(validMembers, member)
}
}
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(validMembers)))
bf.WriteUint16(0) // Unk
for _, member := range validMembers {
bf.WriteUint32(member.CharID)
bf.WriteUint32(member.Souls)
if _config.ErupeConfig.RealClientMode <= _config.Z1 {
bf.WriteUint16(uint16(member.Souls))
bf.WriteUint16(0)
} else {
bf.WriteUint32(member.Souls)
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -431,7 +469,14 @@ func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfChargeFesta)
s.server.db.Exec("UPDATE guild_characters SET souls=souls+$1 WHERE character_id=$2", pkt.Souls, s.charID)
tx, _ := s.server.db.Begin()
for i := range pkt.Souls {
if pkt.Souls[i] == 0 {
continue
}
_, _ = tx.Exec(`INSERT INTO festa_submissions VALUES ($1, $2, $3, $4, now())`, s.charID, pkt.GuildID, i, pkt.Souls[i])
}
_ = tx.Commit()
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -21,18 +21,18 @@ import (
"go.uber.org/zap"
)
type FestivalColour string
type FestivalColor string
const (
FestivalColourNone FestivalColour = "none"
FestivalColourBlue FestivalColour = "blue"
FestivalColourRed FestivalColour = "red"
FestivalColorNone FestivalColor = "none"
FestivalColorBlue FestivalColor = "blue"
FestivalColorRed FestivalColor = "red"
)
var FestivalColourCodes = map[FestivalColour]int8{
FestivalColourNone: -1,
FestivalColourBlue: 0,
FestivalColourRed: 1,
var FestivalColorCodes = map[FestivalColor]int16{
FestivalColorNone: -1,
FestivalColorBlue: 0,
FestivalColorRed: 1,
}
type GuildApplicationType string
@@ -43,27 +43,27 @@ const (
)
type Guild struct {
ID uint32 `db:"id"`
Name string `db:"name"`
MainMotto uint8 `db:"main_motto"`
SubMotto uint8 `db:"sub_motto"`
CreatedAt time.Time `db:"created_at"`
MemberCount uint16 `db:"member_count"`
RankRP uint32 `db:"rank_rp"`
EventRP uint32 `db:"event_rp"`
Comment string `db:"comment"`
PugiName1 string `db:"pugi_name_1"`
PugiName2 string `db:"pugi_name_2"`
PugiName3 string `db:"pugi_name_3"`
PugiOutfit1 uint8 `db:"pugi_outfit_1"`
PugiOutfit2 uint8 `db:"pugi_outfit_2"`
PugiOutfit3 uint8 `db:"pugi_outfit_3"`
PugiOutfits uint32 `db:"pugi_outfits"`
Recruiting bool `db:"recruiting"`
FestivalColour FestivalColour `db:"festival_colour"`
Souls uint32 `db:"souls"`
AllianceID uint32 `db:"alliance_id"`
Icon *GuildIcon `db:"icon"`
ID uint32 `db:"id"`
Name string `db:"name"`
MainMotto uint8 `db:"main_motto"`
SubMotto uint8 `db:"sub_motto"`
CreatedAt time.Time `db:"created_at"`
MemberCount uint16 `db:"member_count"`
RankRP uint32 `db:"rank_rp"`
EventRP uint32 `db:"event_rp"`
Comment string `db:"comment"`
PugiName1 string `db:"pugi_name_1"`
PugiName2 string `db:"pugi_name_2"`
PugiName3 string `db:"pugi_name_3"`
PugiOutfit1 uint8 `db:"pugi_outfit_1"`
PugiOutfit2 uint8 `db:"pugi_outfit_2"`
PugiOutfit3 uint8 `db:"pugi_outfit_3"`
PugiOutfits uint32 `db:"pugi_outfits"`
Recruiting bool `db:"recruiting"`
FestivalColor FestivalColor `db:"festival_colour"`
Souls uint32 `db:"souls"`
AllianceID uint32 `db:"alliance_id"`
Icon *GuildIcon `db:"icon"`
GuildLeader
}
@@ -967,7 +967,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(uint8(len(guildLeaderName)))
bf.WriteBytes(guildName)
bf.WriteBytes(guildComment)
bf.WriteInt8(FestivalColourCodes[guild.FestivalColour])
bf.WriteInt8(int8(FestivalColorCodes[guild.FestivalColor]))
bf.WriteUint32(guild.RankRP)
bf.WriteBytes(guildLeaderName)
bf.WriteUint32(0) // Unk
@@ -1427,7 +1427,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(guild.MemberCount)
bf.WriteUint16(uint16(len(guildMembers)))
sort.Slice(guildMembers[:], func(i, j int) bool {
return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex
@@ -1460,7 +1460,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
}
if guild.AllianceID > 0 {
bf.WriteUint16(alliance.TotalMembers - guild.MemberCount)
bf.WriteUint16(alliance.TotalMembers - uint16(len(guildMembers)))
if guild.ID != alliance.ParentGuildID {
mems, err := GetGuildMembers(s, alliance.ParentGuildID, false)
if err != nil {

View File

@@ -61,41 +61,35 @@ func (gm *GuildMember) Save(s *Session) error {
}
const guildMembersSelectSQL = `
SELECT
g.id as guild_id,
joined_at,
coalesce(souls, 0) as souls,
COALESCE(rp_today, 0) AS rp_today,
COALESCE(rp_yesterday, 0) AS rp_yesterday,
c.name,
character.character_id,
coalesce(gc.order_index, 0) as order_index,
c.last_login,
coalesce(gc.recruiter, false) as recruiter,
coalesce(gc.avoid_leadership, false) as avoid_leadership,
c.hrp,
c.gr,
c.weapon_id,
c.weapon_type,
character.is_applicant,
CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader
FROM (
SELECT character_id, true as is_applicant, guild_id
FROM guild_applications ga
WHERE ga.application_type = 'applied'
UNION
SELECT character_id, false as is_applicant, guild_id
SELECT * FROM (
SELECT
g.id AS guild_id,
joined_at,
COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls,
COALESCE(rp_today, 0) AS rp_today,
COALESCE(rp_yesterday, 0) AS rp_yesterday,
c.name,
c.id AS character_id,
COALESCE(order_index, 0) AS order_index,
c.last_login,
COALESCE(recruiter, false) AS recruiter,
COALESCE(avoid_leadership, false) AS avoid_leadership,
c.hrp,
c.gr,
c.weapon_id,
c.weapon_type,
EXISTS(SELECT 1 FROM guild_applications ga WHERE ga.character_id=c.id AND application_type='applied') AS is_applicant,
CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader
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
LEFT JOIN characters c ON c.id = gc.character_id
LEFT JOIN guilds g ON g.id = gc.guild_id
) AS subquery
`
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
WHERE guild_id = $1 AND is_applicant = $2
`, guildMembersSelectSQL), guildID, applicants)
if err != nil {
@@ -121,7 +115,7 @@ func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMembe
}
func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character_id=$1", guildMembersSelectSQL), charID)
if err != nil {
s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID))

View File

@@ -60,9 +60,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
mail := &Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: s.server.dict["guildInviteName"],
Subject: s.server.i18n.guild.invite.title,
Body: fmt.Sprintf(
s.server.dict["guildInvite"],
s.server.i18n.guild.invite.body,
guildInfo.Name,
),
IsGuildInvite: true,
@@ -146,30 +146,30 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
err = guild.AcceptApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
Subject: s.server.dict["guildInviteSuccessName"],
Body: fmt.Sprintf(s.server.dict["guildInviteSuccess"], guild.Name),
Subject: s.server.i18n.guild.invite.success.title,
Body: fmt.Sprintf(s.server.i18n.guild.invite.success.body, guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
Subject: s.server.dict["guildInviteAcceptedName"],
Body: fmt.Sprintf(s.server.dict["guildInviteAccepted"], guild.Name),
Subject: s.server.i18n.guild.invite.accepted.title,
Body: fmt.Sprintf(s.server.i18n.guild.invite.accepted.body, guild.Name),
IsSystemMessage: true,
})
} else {
err = guild.RejectApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
Subject: s.server.dict["guildInviteRejectName"],
Body: fmt.Sprintf(s.server.dict["guildInviteReject"], guild.Name),
Subject: s.server.i18n.guild.invite.rejected.title,
Body: fmt.Sprintf(s.server.i18n.guild.invite.rejected.body, guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
Subject: s.server.dict["guildInviteDeclined"],
Body: fmt.Sprintf(s.server.dict["guildInviteDeclined"], guild.Name),
Subject: s.server.i18n.guild.invite.declined.title,
Body: fmt.Sprintf(s.server.i18n.guild.invite.declined.body, guild.Name),
IsSystemMessage: true,
})
}

View File

@@ -42,7 +42,7 @@ 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 {
if s.server.erupeConfig.DebugOptions.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()

View File

@@ -2,6 +2,7 @@ package channelserver
import (
"database/sql"
"encoding/binary"
"erupe-ce/common/byteframe"
"erupe-ce/common/decryption"
ps "erupe-ce/common/pascalstring"
@@ -16,11 +17,82 @@ import (
"go.uber.org/zap"
)
type tuneValue struct {
ID uint16
Value uint16
}
func findSubSliceIndices(data []byte, sub []byte) []int {
var indices []int
lenSub := len(sub)
for i := 0; i < len(data); i++ {
if i+lenSub > len(data) {
break
}
if equal(data[i:i+lenSub], sub) {
indices = append(indices, i)
}
}
return indices
}
func equal(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func BackportQuest(data []byte) []byte {
wp := binary.LittleEndian.Uint32(data[0:4]) + 96
rp := wp + 4
for i := uint32(0); i < 6; i++ {
if i != 0 {
wp += 4
rp += 8
}
copy(data[wp:wp+4], data[rp:rp+4])
}
fillLength := uint32(108)
if _config.ErupeConfig.RealClientMode <= _config.S6 {
fillLength = 44
} else if _config.ErupeConfig.RealClientMode <= _config.F5 {
fillLength = 52
} else if _config.ErupeConfig.RealClientMode <= _config.G101 {
fillLength = 76
}
copy(data[wp:wp+fillLength], data[rp:rp+fillLength])
if _config.ErupeConfig.RealClientMode <= _config.G91 {
patterns := [][]byte{
{0x0A, 0x00, 0x01, 0x33, 0xD7, 0x00}, // 10% Armor Sphere -> Stone
{0x06, 0x00, 0x02, 0x33, 0xD8, 0x00}, // 6% Armor Sphere+ -> Iron Ore
{0x0A, 0x00, 0x03, 0x33, 0xD7, 0x00}, // 10% Adv Armor Sphere -> Stone
{0x06, 0x00, 0x04, 0x33, 0xDB, 0x00}, // 6% Hard Armor Sphere -> Dragonite Ore
{0x0A, 0x00, 0x05, 0x33, 0xD9, 0x00}, // 10% Heaven Armor Sphere -> Earth Crystal
{0x06, 0x00, 0x06, 0x33, 0xDB, 0x00}, // 6% True Armor Sphere -> Dragonite Ore
}
for i := range patterns {
j := findSubSliceIndices(data, patterns[i][0:4])
for k := range j {
copy(data[j[k]+2:j[k]+4], patterns[i][4:6])
}
}
}
return data
}
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysGetFile)
if pkt.IsScenario {
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug(
"Scenario",
zap.Uint8("CategoryID", pkt.ScenarioIdentifer.CategoryID),
@@ -40,7 +112,7 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
}
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug(
"Quest",
zap.String("Filename", pkt.Filename),
@@ -58,6 +130,9 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, data)
return
}
if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport {
data = BackportQuest(decryption.UnpackSimple(data))
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
}
@@ -119,25 +194,39 @@ func loadQuestFile(s *Session, questId int) []byte {
}
decrypted := decryption.UnpackSimple(file)
if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport {
decrypted = BackportQuest(decrypted)
}
fileBytes := byteframe.NewByteFrameFromBytes(decrypted)
fileBytes.SetLE()
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
// The 320 bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers.
questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(320))
bodyLength := 320
if _config.ErupeConfig.RealClientMode <= _config.S6 {
bodyLength = 160
} else if _config.ErupeConfig.RealClientMode <= _config.F5 {
bodyLength = 168
} else if _config.ErupeConfig.RealClientMode <= _config.G101 {
bodyLength = 192
} else if _config.ErupeConfig.RealClientMode <= _config.Z1 {
bodyLength = 224
}
// The n bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers.
questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(uint(bodyLength)))
questBody.SetLE()
// Find the master quest string pointer
questBody.Seek(40, 0)
fileBytes.Seek(int64(questBody.ReadUint32()), 0)
questBody.Seek(40, 0)
// Overwrite it
questBody.WriteUint32(320)
questBody.WriteUint32(uint32(bodyLength))
questBody.Seek(0, 2)
// Rewrite the quest strings and their pointers
var tempString []byte
newStrings := byteframe.NewByteFrame()
tempPointer := 352
tempPointer := bodyLength + 32
for i := 0; i < 8; i++ {
questBody.WriteUint32(uint32(tempPointer))
temp := int64(fileBytes.Index())
@@ -163,7 +252,7 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
data := loadQuestFile(s, questId)
if data == nil {
return nil, fmt.Errorf("failed to load quest file")
return nil, fmt.Errorf(fmt.Sprintf("failed to load quest file (%d)", questId))
}
bf := byteframe.NewByteFrame()
@@ -249,6 +338,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
err = rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDays, &inactiveDays)
if err != nil {
s.logger.Error("Failed to scan event quest row", zap.Error(err))
continue
}
@@ -277,15 +367,17 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
// Check if the quest is currently active
if currentTime.Before(startTime) || currentTime.After(startTime.Add(time.Duration(activeDays)*24*time.Hour)) {
break
continue
}
}
data, err := makeEventQuest(s, rows)
if err != nil {
s.logger.Error("Failed to make event quest", zap.Error(err))
continue
} else {
if len(data) > 896 || len(data) < 352 {
s.logger.Error("Invalid quest data length", zap.Int("len", len(data)))
continue
} else {
totalCount++
@@ -302,11 +394,6 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
tx.Commit()
}
type tuneValue struct {
ID uint16
Value uint16
}
tuneValues := []tuneValue{
{ID: 20, Value: 1},
{ID: 26, Value: 1},
@@ -319,43 +406,46 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
{ID: 67, Value: 1},
{ID: 80, Value: 1},
{ID: 94, Value: 1},
{ID: 1010, Value: 300},
{ID: 1011, Value: 300},
{ID: 1012, Value: 300},
{ID: 1013, Value: 300},
{ID: 1014, Value: 200},
{ID: 1015, Value: 200},
{ID: 1021, Value: 400},
{ID: 1023, Value: 8},
{ID: 1024, Value: 150},
{ID: 1025, Value: 1},
{ID: 1026, Value: 999}, // get_grank_cap
{ID: 1027, Value: 100},
{ID: 1028, Value: 100},
{ID: 1030, Value: 8},
{ID: 1031, Value: 100},
{ID: 1032, Value: 0}, // isValid_partner
{ID: 1044, Value: 200}, // get_rate_tload_time_out
{ID: 1045, Value: 0}, // get_rate_tower_treasure_preset
{ID: 1046, Value: 99},
{ID: 1048, Value: 0}, // get_rate_tower_log_disable
{ID: 1049, Value: 10}, // get_rate_tower_gem_max
{ID: 1050, Value: 1}, // get_rate_tower_gem_set
{ID: 1051, Value: 200},
{ID: 1052, Value: 200},
{ID: 1063, Value: 50000},
{ID: 1064, Value: 50000},
{ID: 1065, Value: 25000},
{ID: 1066, Value: 25000},
{ID: 1067, Value: 90}, // get_lobby_member_upper_for_making_room Lv1?
{ID: 1068, Value: 80}, // get_lobby_member_upper_for_making_room Lv2?
{ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3?
{ID: 1072, Value: 300}, // get_rate_premium_ravi_tama
{ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama
{ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama
{ID: 1078, Value: 0},
{ID: 1079, Value: 1},
{ID: 1080, Value: 1},
{ID: 1001, Value: 100}, // get_hrp_rate
{ID: 1010, Value: 300}, // get_hrp_rate_netcafe
{ID: 1011, Value: 300}, // get_zeny_rate_netcafe
{ID: 1012, Value: 300}, // get_hrp_rate_ncource
{ID: 1013, Value: 300}, // get_zeny_rate_ncource
{ID: 1014, Value: 200}, // get_hrp_rate_premium
{ID: 1015, Value: 200}, // get_zeny_rate_premium
{ID: 1021, Value: 400}, // get_gcp_rate_assist
{ID: 1023, Value: 8}, // unused?
{ID: 1024, Value: 150}, // get_hrp_rate_ptbonus
{ID: 1025, Value: 1}, // isValid_stampcard
{ID: 1026, Value: 999}, // get_grank_cap
{ID: 1027, Value: 100}, // get_exchange_rate_festa
{ID: 1028, Value: 100}, // get_exchange_rate_cafe
{ID: 1030, Value: 8}, // get_gquest_cap
{ID: 1031, Value: 100}, // get_exchange_rate_guild (GCP)
{ID: 1032, Value: 0}, // isValid_partner
{ID: 1044, Value: 200}, // get_rate_tload_time_out
{ID: 1045, Value: 0}, // get_rate_tower_treasure_preset
{ID: 1046, Value: 99}, // get_hunter_life_cap
{ID: 1048, Value: 0}, // get_rate_tower_hint_sec
{ID: 1049, Value: 10}, // get_rate_tower_gem_max
{ID: 1050, Value: 1}, // get_rate_tower_gem_set
{ID: 1051, Value: 200}, // get_pallone_score_rate_premium
{ID: 1052, Value: 200}, // get_trp_rate_premium
{ID: 1063, Value: 50000}, // get_nboost_quest_point_from_hrank
{ID: 1064, Value: 50000}, // get_nboost_quest_point_from_srank
{ID: 1065, Value: 25000}, // get_nboost_quest_point_from_grank
{ID: 1066, Value: 25000}, // get_nboost_quest_point_from_gsrank
{ID: 1067, Value: 90}, // get_lobby_member_upper_for_making_room Lv1?
{ID: 1068, Value: 80}, // get_lobby_member_upper_for_making_room Lv2?
{ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3?
{ID: 1072, Value: 300}, // get_rate_premium_ravi_tama
{ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama
{ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama
{ID: 1078, Value: 0}, // isCapped_tenrou_irai
{ID: 1079, Value: 1}, // get_add_tower_level_assist
{ID: 1080, Value: 1}, // get_tune_add_tower_level_w_assist_nboost
// get_tune_secret_book_item
{ID: 1081, Value: 1},
{ID: 1082, Value: 4},
{ID: 1083, Value: 2},
@@ -380,14 +470,17 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
{ID: 1102, Value: 5},
{ID: 1103, Value: 2},
{ID: 1104, Value: 10},
{ID: 1145, Value: 200},
{ID: 1146, Value: 0}, // isTower_invisible
{ID: 1147, Value: 0}, // isVenom_playable
{ID: 1149, Value: 20},
{ID: 1152, Value: 1130},
{ID: 1154, Value: 0}, // isDisabled_object_season
{ID: 1158, Value: 1},
{ID: 1160, Value: 300},
{ID: 1145, Value: 200}, // get_ud_point_rate_premium
{ID: 1146, Value: 0}, // isTower_invisible
{ID: 1147, Value: 0}, // isVenom_playable
{ID: 1149, Value: 20}, // get_ud_break_parts_point
{ID: 1152, Value: 1130}, // unused?
{ID: 1154, Value: 0}, // isDisabled_object_season
{ID: 1158, Value: 1}, // isDelivery_venom_ult_quest
{ID: 1160, Value: 300}, // get_rate_premium_ravi_g_enhance_tama
// unknown
{ID: 1162, Value: 1},
{ID: 1163, Value: 3},
{ID: 1164, Value: 5},
@@ -407,240 +500,6 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
{ID: 1178, Value: 10},
{ID: 1179, Value: 2},
{ID: 1180, Value: 5},
{ID: 3000, Value: 100},
{ID: 3001, Value: 100},
{ID: 3002, Value: 100},
{ID: 3003, Value: 100},
{ID: 3004, Value: 100},
{ID: 3005, Value: 100},
{ID: 3006, Value: 100},
{ID: 3007, Value: 100},
{ID: 3008, Value: 100},
{ID: 3009, Value: 100},
{ID: 3010, Value: 100},
{ID: 3011, Value: 100},
{ID: 3012, Value: 100},
{ID: 3013, Value: 100},
{ID: 3014, Value: 100},
{ID: 3015, Value: 100},
{ID: 3016, Value: 100},
{ID: 3017, Value: 100},
{ID: 3018, Value: 100},
{ID: 3019, Value: 100},
{ID: 3020, Value: 100},
{ID: 3021, Value: 100},
{ID: 3022, Value: 100},
{ID: 3023, Value: 100},
{ID: 3024, Value: 100},
{ID: 3025, Value: 100},
{ID: 3286, Value: 200},
{ID: 3287, Value: 200},
{ID: 3288, Value: 200},
{ID: 3289, Value: 200},
{ID: 3290, Value: 200},
{ID: 3291, Value: 200},
{ID: 3292, Value: 200},
{ID: 3293, Value: 200},
{ID: 3294, Value: 200},
{ID: 3295, Value: 200},
{ID: 3296, Value: 200},
{ID: 3297, Value: 200},
{ID: 3298, Value: 200},
{ID: 3299, Value: 200},
{ID: 3300, Value: 200},
{ID: 3301, Value: 200},
{ID: 3302, Value: 200},
{ID: 3303, Value: 200},
{ID: 3304, Value: 200},
{ID: 3305, Value: 200},
{ID: 3306, Value: 200},
{ID: 3307, Value: 200},
{ID: 3308, Value: 200},
{ID: 3309, Value: 200},
{ID: 3310, Value: 200},
{ID: 3311, Value: 200},
{ID: 3312, Value: 300},
{ID: 3313, Value: 300},
{ID: 3314, Value: 300},
{ID: 3315, Value: 300},
{ID: 3316, Value: 300},
{ID: 3317, Value: 300},
{ID: 3318, Value: 300},
{ID: 3319, Value: 300},
{ID: 3320, Value: 300},
{ID: 3321, Value: 300},
{ID: 3322, Value: 300},
{ID: 3323, Value: 300},
{ID: 3324, Value: 300},
{ID: 3325, Value: 300},
{ID: 3326, Value: 300},
{ID: 3327, Value: 300},
{ID: 3328, Value: 300},
{ID: 3329, Value: 300},
{ID: 3330, Value: 300},
{ID: 3331, Value: 300},
{ID: 3332, Value: 300},
{ID: 3333, Value: 300},
{ID: 3334, Value: 300},
{ID: 3335, Value: 300},
{ID: 3336, Value: 300},
{ID: 3337, Value: 300},
{ID: 3338, Value: 100},
{ID: 3339, Value: 100},
{ID: 3340, Value: 100},
{ID: 3341, Value: 100},
{ID: 3342, Value: 100},
{ID: 3343, Value: 100},
{ID: 3344, Value: 100},
{ID: 3345, Value: 100},
{ID: 3346, Value: 100},
{ID: 3347, Value: 100},
{ID: 3348, Value: 100},
{ID: 3349, Value: 100},
{ID: 3350, Value: 100},
{ID: 3351, Value: 100},
{ID: 3352, Value: 100},
{ID: 3353, Value: 100},
{ID: 3354, Value: 100},
{ID: 3355, Value: 100},
{ID: 3356, Value: 100},
{ID: 3357, Value: 100},
{ID: 3358, Value: 100},
{ID: 3359, Value: 100},
{ID: 3360, Value: 100},
{ID: 3361, Value: 100},
{ID: 3362, Value: 100},
{ID: 3363, Value: 100},
{ID: 3364, Value: 100},
{ID: 3365, Value: 100},
{ID: 3366, Value: 100},
{ID: 3367, Value: 100},
{ID: 3368, Value: 100},
{ID: 3369, Value: 100},
{ID: 3370, Value: 100},
{ID: 3371, Value: 100},
{ID: 3372, Value: 100},
{ID: 3373, Value: 100},
{ID: 3374, Value: 100},
{ID: 3375, Value: 100},
{ID: 3376, Value: 100},
{ID: 3377, Value: 100},
{ID: 3378, Value: 100},
{ID: 3379, Value: 100},
{ID: 3380, Value: 100},
{ID: 3381, Value: 100},
{ID: 3382, Value: 100},
{ID: 3383, Value: 100},
{ID: 3384, Value: 100},
{ID: 3385, Value: 100},
{ID: 3386, Value: 100},
{ID: 3387, Value: 100},
{ID: 3388, Value: 100},
{ID: 3389, Value: 100},
{ID: 3390, Value: 100},
{ID: 3391, Value: 100},
{ID: 3392, Value: 100},
{ID: 3393, Value: 100},
{ID: 3394, Value: 100},
{ID: 3395, Value: 100},
{ID: 3396, Value: 100},
{ID: 3397, Value: 100},
{ID: 3398, Value: 100},
{ID: 3399, Value: 100},
{ID: 3400, Value: 100},
{ID: 3401, Value: 100},
{ID: 3402, Value: 100},
{ID: 3416, Value: 100},
{ID: 3417, Value: 100},
{ID: 3418, Value: 100},
{ID: 3419, Value: 100},
{ID: 3420, Value: 100},
{ID: 3421, Value: 100},
{ID: 3422, Value: 100},
{ID: 3423, Value: 100},
{ID: 3424, Value: 100},
{ID: 3425, Value: 100},
{ID: 3426, Value: 100},
{ID: 3427, Value: 100},
{ID: 3428, Value: 100},
{ID: 3442, Value: 100},
{ID: 3443, Value: 100},
{ID: 3444, Value: 100},
{ID: 3445, Value: 100},
{ID: 3446, Value: 100},
{ID: 3447, Value: 100},
{ID: 3448, Value: 100},
{ID: 3449, Value: 100},
{ID: 3450, Value: 100},
{ID: 3451, Value: 100},
{ID: 3452, Value: 100},
{ID: 3453, Value: 100},
{ID: 3454, Value: 100},
{ID: 3468, Value: 100},
{ID: 3469, Value: 100},
{ID: 3470, Value: 100},
{ID: 3471, Value: 100},
{ID: 3472, Value: 100},
{ID: 3473, Value: 100},
{ID: 3474, Value: 100},
{ID: 3475, Value: 100},
{ID: 3476, Value: 100},
{ID: 3477, Value: 100},
{ID: 3478, Value: 100},
{ID: 3479, Value: 100},
{ID: 3480, Value: 100},
{ID: 3494, Value: 0},
{ID: 3495, Value: 0},
{ID: 3496, Value: 0},
{ID: 3497, Value: 0},
{ID: 3498, Value: 0},
{ID: 3499, Value: 0},
{ID: 3500, Value: 0},
{ID: 3501, Value: 0},
{ID: 3502, Value: 0},
{ID: 3503, Value: 0},
{ID: 3504, Value: 0},
{ID: 3505, Value: 0},
{ID: 3506, Value: 0},
{ID: 3520, Value: 0},
{ID: 3521, Value: 0},
{ID: 3522, Value: 0},
{ID: 3523, Value: 0},
{ID: 3524, Value: 0},
{ID: 3525, Value: 0},
{ID: 3526, Value: 0},
{ID: 3527, Value: 0},
{ID: 3528, Value: 0},
{ID: 3529, Value: 0},
{ID: 3530, Value: 0},
{ID: 3531, Value: 0},
{ID: 3532, Value: 0},
{ID: 3546, Value: 0},
{ID: 3547, Value: 0},
{ID: 3548, Value: 0},
{ID: 3549, Value: 0},
{ID: 3550, Value: 0},
{ID: 3551, Value: 0},
{ID: 3552, Value: 0},
{ID: 3553, Value: 0},
{ID: 3554, Value: 0},
{ID: 3555, Value: 0},
{ID: 3556, Value: 0},
{ID: 3557, Value: 0},
{ID: 3558, Value: 0},
{ID: 3572, Value: 0},
{ID: 3573, Value: 0},
{ID: 3574, Value: 0},
{ID: 3575, Value: 0},
{ID: 3576, Value: 0},
{ID: 3577, Value: 0},
{ID: 3578, Value: 0},
{ID: 3579, Value: 0},
{ID: 3580, Value: 0},
{ID: 3581, Value: 0},
{ID: 3582, Value: 0},
{ID: 3583, Value: 0},
{ID: 3584, Value: 0},
}
tuneValues = append(tuneValues, tuneValue{1020, uint16(s.server.erupeConfig.GameplayOptions.GCPMultiplier * 100)})
@@ -653,65 +512,70 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
if s.server.erupeConfig.GameplayOptions.EnableKaijiEvent {
tuneValues = append(tuneValues, tuneValue{1106, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1106, 0})
}
if s.server.erupeConfig.GameplayOptions.EnableHiganjimaEvent {
tuneValues = append(tuneValues, tuneValue{1144, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1144, 0})
}
if s.server.erupeConfig.GameplayOptions.EnableNierEvent {
tuneValues = append(tuneValues, tuneValue{1153, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1153, 0})
}
if s.server.erupeConfig.GameplayOptions.DisableRoad {
tuneValues = append(tuneValues, tuneValue{1155, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1155, 0})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier * 100)})
}
// get_hrp_rate_from_rank
tuneValues = append(tuneValues, getTuneValueRange(3000, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplier*100))...)
tuneValues = append(tuneValues, getTuneValueRange(3338, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplierNC*100))...)
// get_srp_rate_from_rank
tuneValues = append(tuneValues, getTuneValueRange(3013, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplier*100))...)
tuneValues = append(tuneValues, getTuneValueRange(3351, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplierNC*100))...)
// get_grp_rate_from_rank
tuneValues = append(tuneValues, getTuneValueRange(3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier*100))...)
tuneValues = append(tuneValues, getTuneValueRange(3364, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplierNC*100))...)
// get_gsrp_rate_from_rank
tuneValues = append(tuneValues, getTuneValueRange(3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier*100))...)
tuneValues = append(tuneValues, getTuneValueRange(3377, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplierNC*100))...)
// get_zeny_rate_from_hrank
tuneValues = append(tuneValues, getTuneValueRange(3052, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplier*100))...)
tuneValues = append(tuneValues, getTuneValueRange(3390, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplierNC*100))...)
// get_zeny_rate_from_grank
tuneValues = append(tuneValues, getTuneValueRange(3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier*100))...)
tuneValues = append(tuneValues, getTuneValueRange(3416, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplierNC*100))...)
// get_reward_rate_from_hrank
tuneValues = append(tuneValues, getTuneValueRange(3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier*100))...)
tuneValues = append(tuneValues, getTuneValueRange(3442, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplierNC*100))...)
// get_reward_rate_from_grank
tuneValues = append(tuneValues, getTuneValueRange(3130, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplier*100))...)
tuneValues = append(tuneValues, getTuneValueRange(3468, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplierNC*100))...)
// get_lottery_rate_from_hrank
tuneValues = append(tuneValues, getTuneValueRange(3156, 0)...)
tuneValues = append(tuneValues, getTuneValueRange(3494, 0)...)
// get_lottery_rate_from_grank
tuneValues = append(tuneValues, getTuneValueRange(3182, 0)...)
tuneValues = append(tuneValues, getTuneValueRange(3520, 0)...)
// get_hagi_rate_from_hrank
tuneValues = append(tuneValues, getTuneValueRange(3208, s.server.erupeConfig.GameplayOptions.ExtraCarves)...)
tuneValues = append(tuneValues, getTuneValueRange(3546, s.server.erupeConfig.GameplayOptions.ExtraCarvesNC)...)
// get_hagi_rate_from_grank
tuneValues = append(tuneValues, getTuneValueRange(3234, s.server.erupeConfig.GameplayOptions.GExtraCarves)...)
tuneValues = append(tuneValues, getTuneValueRange(3572, s.server.erupeConfig.GameplayOptions.GExtraCarvesNC)...)
// get_nboost_transcend_rate_from_hrank
tuneValues = append(tuneValues, getTuneValueRange(3286, 200)...)
tuneValues = append(tuneValues, getTuneValueRange(3312, 300)...)
// get_nboost_transcend_rate_from_grank
tuneValues = append(tuneValues, getTuneValueRange(3299, 200)...)
tuneValues = append(tuneValues, getTuneValueRange(3325, 300)...)
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier * 100)})
var temp []tuneValue
for i := range tuneValues {
if tuneValues[i].Value > 0 {
temp = append(temp, tuneValues[i])
}
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3052, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3130, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3156, s.server.erupeConfig.GameplayOptions.ExtraCarves})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3182, s.server.erupeConfig.GameplayOptions.ExtraCarves})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3208, s.server.erupeConfig.GameplayOptions.ExtraCarves})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3234, s.server.erupeConfig.GameplayOptions.ExtraCarves})
}
offset := uint16(time.Now().Unix())
bf.WriteUint16(offset)
tuneValues = temp
tuneLimit := 770
if _config.ErupeConfig.RealClientMode <= _config.F5 {
@@ -737,6 +601,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
tuneValues = tuneValues[:tuneLimit]
}
offset := uint16(time.Now().Unix())
bf.WriteUint16(offset)
bf.WriteUint16(uint16(len(tuneValues)))
for i := range tuneValues {
bf.WriteUint16(tuneValues[i].ID ^ offset)
@@ -778,6 +645,14 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func getTuneValueRange(start uint16, value uint16) []tuneValue {
var tv []tuneValue
for i := uint16(0); i < 13; i++ {
tv = append(tv, tuneValue{start + i, value})
}
return tv
}
func handleMsgMhfEnterTournamentQuest(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdBonusQuestInfo(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -157,7 +157,6 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
s.stage.reservedClientSlots[s.charID] = false
s.stage.Unlock()
s.stageMoveStack.Push(s.stage.id)
s.stageMoveStack.Lock()
}
if s.reservationStage != nil {
@@ -171,7 +170,6 @@ 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 backStage == "" || err != nil {
backStage = "sl1Ns200p0a0u0"
@@ -190,12 +188,6 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysMoveStage)
// Set a new move stack from the given stage ID
if !s.stageMoveStack.Locked {
s.stageMoveStack.Set(pkt.StageID)
}
doStageTransfer(s, pkt.AckHandle, pkt.StageID)
}

View File

@@ -1,8 +1,10 @@
package channelserver
import (
_config "erupe-ce/config"
"fmt"
"go.uber.org/zap"
"strings"
"time"
"erupe-ce/common/byteframe"
@@ -16,8 +18,8 @@ type TowerInfoTRP struct {
}
type TowerInfoSkill struct {
TSP int32
Unk1 []int16 // 40
TSP int32
Skills []int16 // 64
}
type TowerInfoHistory struct {
@@ -32,6 +34,14 @@ type TowerInfoLevel struct {
Unk3 int32
}
func EmptyTowerCSV(len int) string {
temp := make([]string, len)
for i := range temp {
temp[i] = "0"
}
return strings.Join(temp, ",")
}
func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetTowerInfo)
var data []*byteframe.ByteFrame
@@ -44,21 +54,24 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
towerInfo := TowerInfo{
TRP: []TowerInfoTRP{{0, 0}},
Skill: []TowerInfoSkill{{0, make([]int16, 40)}},
Skill: []TowerInfoSkill{{0, make([]int16, 64)}},
History: []TowerInfoHistory{{make([]int16, 5), make([]int16, 5)}},
Level: []TowerInfoLevel{{0, 0, 0, 0}, {0, 0, 0, 0}},
}
tempSkills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), skills FROM tower WHERE char_id=$1
`, s.charID).Scan(&towerInfo.TRP[0].TR, &towerInfo.TRP[0].TRP, &towerInfo.Skill[0].TSP, &towerInfo.Level[0].Floors, &towerInfo.Level[1].Floors, &tempSkills)
var tempSkills string
err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), COALESCE(skills, $1) FROM tower WHERE char_id=$2
`, EmptyTowerCSV(64), s.charID).Scan(&towerInfo.TRP[0].TR, &towerInfo.TRP[0].TRP, &towerInfo.Skill[0].TSP, &towerInfo.Level[0].Floors, &towerInfo.Level[1].Floors, &tempSkills)
if err != nil {
s.server.db.Exec(`INSERT INTO tower (char_id) VALUES ($1)`, s.charID)
}
if _config.ErupeConfig.RealClientMode <= _config.G7 {
towerInfo.Level = towerInfo.Level[:1]
}
for i, skill := range stringsupport.CSVElems(tempSkills) {
towerInfo.Skill[0].Unk1[i] = int16(skill)
towerInfo.Skill[0].Skills[i] = int16(skill)
}
switch pkt.InfoType {
@@ -73,8 +86,8 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
for _, skills := range towerInfo.Skill {
bf := byteframe.NewByteFrame()
bf.WriteInt32(skills.TSP)
for i := range skills.Unk1 {
bf.WriteInt16(skills.Unk1[i])
for i := range skills.Skills {
bf.WriteInt16(skills.Skills[i])
}
data = append(data, bf)
}
@@ -89,7 +102,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
}
data = append(data, bf)
}
case 5:
case 3, 5:
for _, level := range towerInfo.Level {
bf := byteframe.NewByteFrame()
bf.WriteInt32(level.Floors)
@@ -105,7 +118,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug(
p.Opcode().String(),
zap.Uint32("InfoType", pkt.InfoType),
@@ -123,8 +136,8 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
switch pkt.InfoType {
case 2:
skills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills)
var skills string
s.server.db.QueryRow(`SELECT COALESCE(skills, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(64), s.charID).Scan(&skills)
s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID)
case 1, 7:
// This might give too much TSP? No idea what the rate is supposed to be
@@ -328,7 +341,7 @@ func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug(
p.Opcode().String(),
zap.Uint8("Unk0", pkt.Unk0),
@@ -412,10 +425,10 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
gemInfo := []GemInfo{}
gemHistory := []GemHistory{}
tempGems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&tempGems)
var tempGems string
s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&tempGems)
for i, v := range stringsupport.CSVElems(tempGems) {
gemInfo = append(gemInfo, GemInfo{uint16(((i / 5) * 256) + ((i % 5) + 1)), uint16(v)})
gemInfo = append(gemInfo, GemInfo{uint16((i / 5 << 8) + (i%5 + 1)), uint16(v)})
}
switch pkt.Unk0 {
@@ -442,7 +455,7 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostGemInfo)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug(
p.Opcode().String(),
zap.Uint32("Op", pkt.Op),
@@ -455,11 +468,11 @@ func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
)
}
gems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&gems)
var gems string
s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&gems)
switch pkt.Op {
case 1: // Add gem
i := int(((pkt.Gem / 256) * 5) + (((pkt.Gem - ((pkt.Gem / 256) * 256)) - 1) % 5))
i := int((pkt.Gem >> 8 * 5) + (pkt.Gem - pkt.Gem&0xFF00 - 1%5))
s.server.db.Exec(`UPDATE tower SET gems=$1 WHERE char_id=$2`, stringsupport.CSVSetIndex(gems, i, stringsupport.CSVGetIndex(gems, i)+int(pkt.Quantity)), s.charID)
case 2: // Transfer gem
// no way im doing this for now

View File

@@ -57,7 +57,7 @@ type Server struct {
stages map[string]*Stage
// Used to map different languages
dict map[string]string
i18n i18n
// UserBinary
userBinaryPartsLock sync.RWMutex
@@ -192,7 +192,7 @@ func NewServer(config *Config) *Server {
// MezFes
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
s.dict = getLangStrings(s)
s.i18n = getLangStrings(s)
return s
}
@@ -211,6 +211,7 @@ func (s *Server) Start() error {
// Start the discord bot for chat integration.
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
s.discordBot.Session.AddHandler(s.onDiscordMessage)
s.discordBot.Session.AddHandler(s.onInteraction)
}
return nil
@@ -336,13 +337,13 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
var text string
switch _type {
case 2:
text = s.dict["ravienteBerserk"]
text = s.i18n.raviente.berserk
case 3:
text = s.dict["ravienteExtreme"]
text = s.i18n.raviente.extreme
case 4:
text = s.dict["ravienteExtremeLimited"]
text = s.i18n.raviente.extremeLimited
case 5:
text = s.dict["ravienteBerserkSmall"]
text = s.i18n.raviente.berserkSmall
default:
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
}
@@ -377,6 +378,26 @@ func (s *Server) FindSessionByCharID(charID uint32) *Session {
return nil
}
func (s *Server) DisconnectUser(uid uint32) {
var cid uint32
var cids []uint32
rows, _ := s.db.Query(`SELECT id FROM characters WHERE user_id=$1`, uid)
for rows.Next() {
rows.Scan(&cid)
cids = append(cids, cid)
}
for _, c := range s.Channels {
for _, session := range c.sessions {
for _, cid := range cids {
if session.charID == cid {
session.rawConn.Close()
break
}
}
}
}
}
func (s *Server) FindObjectByChar(charID uint32) *Object {
s.stagesLock.RLock()
defer s.stagesLock.RUnlock()

View File

@@ -1,114 +1,238 @@
package channelserver
func getLangStrings(s *Server) map[string]string {
strings := make(map[string]string)
type i18n struct {
language string
cafe struct {
reset string
}
timer string
commands struct {
noOp string
disabled string
reload string
kqf struct {
get string
set struct {
error string
success string
}
version string
}
rights struct {
error string
success string
}
course struct {
error string
disabled string
enabled string
locked string
}
teleport struct {
error string
success string
}
psn struct {
error string
success string
exists string
}
discord struct {
success string
}
ban struct {
success string
noUser string
invalid string
error string
length string
}
timer struct {
enabled string
disabled string
}
ravi struct {
noCommand string
start struct {
success string
error string
}
multiplier string
res struct {
success string
error string
}
sed struct {
success string
}
request string
error string
noPlayers string
version string
}
}
raviente struct {
berserk string
extreme string
extremeLimited string
berserkSmall string
}
guild struct {
invite struct {
title string
body string
success struct {
title string
body string
}
accepted struct {
title string
body string
}
rejected struct {
title string
body string
}
declined struct {
title string
body string
}
}
}
}
func getLangStrings(s *Server) i18n {
var i i18n
switch s.erupeConfig.Language {
case "jp":
strings["language"] = "日本語"
strings["cafeReset"] = "%d/%dにリセット"
i.language = "日本語"
i.cafe.reset = "%d/%dにリセット"
i.timer = "タイマー:%02d'%02d\"%02d.%03d (%df)"
strings["commandDisabled"] = "%sのコマンドは無効です"
strings["commandReload"] = "リロードします"
strings["commandKqfGet"] = "現在のキークエストフラグ:%x"
strings["commandKqfSetError"] = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx"
strings["commandKqfSetSuccess"] = "キークエストのフラグが更新されました。ワールド/ランドを移動してください"
strings["commandKqfVersion"] = "This command is disabled prior to MHFG10"
strings["commandRightsError"] = "コース更新コマンドエラー 例:%s x"
strings["commandRightsSuccess"] = "コース情報を更新しました:%d"
strings["commandCourseError"] = "コース確認コマンドエラー 例:%s <name>"
strings["commandCourseDisabled"] = "%sコースは無効です"
strings["commandCourseEnabled"] = "%sコースは効です"
strings["commandCourseLocked"] = "%sコースはロックされています"
strings["commandTeleportError"] = "テレポートコマンドエラー 構文:%s x y"
strings["commandTeleportSuccess"] = "%d %dにテレポート"
strings["commandPSNError"] = "PSN連携コマンドエラー %s <psn id>"
strings["commandPSNSuccess"] = "PSN「%s」が連携されています"
strings["commandPSNExists"] = "PSNは既存のユーザに接続されています"
i.commands.noOp = "You don't have permission to use this command"
i.commands.disabled = "%sのコマンドは無効です"
i.commands.reload = "リロードします"
i.commands.kqf.get = "現在のキークエストフラグ:%x"
i.commands.kqf.set.error = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx"
i.commands.kqf.set.success = "キークエストのフラグが更新されました。ワールド/ランドを移動してください"
i.commands.kqf.version = "This command is disabled prior to MHFG10"
i.commands.rights.error = "コース更新コマンドエラー 例:%s x"
i.commands.rights.success = "コース情報を更新しました:%d"
i.commands.course.error = "コース確認コマンドエラー 例:%s <name>"
i.commands.course.disabled = "%sコースは効です"
i.commands.course.enabled = "%sコースは有効です"
i.commands.course.locked = "%sコースはロックされています"
i.commands.teleport.error = "テレポートコマンドエラー 構文:%s x y"
i.commands.teleport.success = "%d %dにテレポート"
i.commands.psn.error = "PSN連携コマンドエラー %s <psn id>"
i.commands.psn.success = "PSN「%s」が連携されています"
i.commands.psn.exists = "PSNは既存のユーザに接続されています"
strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません"
strings["commandRaviStartSuccess"] = "大討伐を開始します"
strings["commandRaviStartError"] = "大討伐は既に開催されています"
strings["commandRaviMultiplier"] = "ラヴィダメージ倍率:x%.2f"
strings["commandRaviResSuccess"] = "復活支援を実行します"
strings["commandRaviResError"] = "復活支援は実行されませんでした"
strings["commandRaviSedSuccess"] = "鎮静支援を実行します"
strings["commandRaviRequest"] = "鎮静支援を要請します"
strings["commandRaviError"] = "ラヴィコマンドが認識されません"
strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません"
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
i.commands.discord.success = "あなたのDiscordトークン%s"
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!"
i.commands.ban.noUser = "Could not find user"
i.commands.ban.success = "Successfully banned %s"
i.commands.ban.invalid = "Invalid Character ID"
i.commands.ban.error = "Error in command. Format: %s <id> [length]"
i.commands.ban.length = " until %s"
strings["guildInviteName"] = "猟団勧誘のご案内"
strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
i.commands.ravi.noCommand = "ラヴィコマンドが指定されていません"
i.commands.ravi.start.success = "大討伐を開始します"
i.commands.ravi.start.error = "大討伐は既に開催されています"
i.commands.ravi.multiplier = "ラヴィダメージ倍率:x%.2f"
i.commands.ravi.res.success = "復活支援を実行します"
i.commands.ravi.res.error = "復活支援は実行されませんでした"
i.commands.ravi.sed.success = "鎮静支援を実行します"
i.commands.ravi.request = "鎮静支援を要請します"
i.commands.ravi.error = "ラヴィコマンドが認識されません"
i.commands.ravi.noPlayers = "誰も大討伐に参加していません"
i.commands.ravi.version = "This command is disabled outside of MHFZZ"
strings["guildInviteSuccessName"] = "成功"
strings["guildInviteSuccess"] = "あなたは「%s」に参加できました"
i.raviente.berserk = "<大討伐:猛狂期>が開催されました!"
i.raviente.extreme = "<大討伐:猛狂期【極】>が開催されました"
i.raviente.extremeLimited = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
i.raviente.berserkSmall = "<大討伐:猛狂期(小数)>が開催されました!"
strings["guildInviteAcceptedName"] = "承諾されました"
strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。"
i.guild.invite.title = "猟団勧誘のご案内"
i.guild.invite.body = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
strings["guildInviteRejectName"] = "却下しました"
strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。"
i.guild.invite.success.title = "成功"
i.guild.invite.success.body = "あなたは「%s」に参加できました。"
strings["guildInviteDeclinedName"] = "辞退しました"
strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。"
i.guild.invite.accepted.title = "承諾されました"
i.guild.invite.accepted.body = "招待した狩人が「%s」への招待を承諾しました。"
i.guild.invite.rejected.title = "却下しました"
i.guild.invite.rejected.body = "あなたは「%s」への参加を却下しました。"
i.guild.invite.declined.title = "辞退しました"
i.guild.invite.declined.body = "招待した狩人が「%s」への招待を辞退しました。"
default:
strings["language"] = "English"
strings["cafeReset"] = "Resets on %d/%d"
i.language = "English"
i.cafe.reset = "Resets on %d/%d"
i.timer = "Time: %02d:%02d:%02d.%03d (%df)"
strings["commandDisabled"] = "%s command is disabled"
strings["commandReload"] = "Reloading players..."
strings["commandKqfGet"] = "KQF: %x"
strings["commandKqfSetError"] = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
strings["commandKqfSetSuccess"] = "KQF set, please switch Land/World"
strings["commandKqfVersion"] = "This command is disabled prior to MHFG10"
strings["commandRightsError"] = "Error in command. Format: %s x"
strings["commandRightsSuccess"] = "Set rights integer: %d"
strings["commandCourseError"] = "Error in command. Format: %s <name>"
strings["commandCourseDisabled"] = "%s Course disabled"
strings["commandCourseEnabled"] = "%s Course enabled"
strings["commandCourseLocked"] = "%s Course is locked"
strings["commandTeleportError"] = "Error in command. Format: %s x y"
strings["commandTeleportSuccess"] = "Teleporting to %d %d"
strings["commandPSNError"] = "Error in command. Format: %s <psn id>"
strings["commandPSNSuccess"] = "Connected PSN ID: %s"
strings["commandPSNExists"] = "PSN ID is connected to another account!"
i.commands.noOp = "You don't have permission to use this command"
i.commands.disabled = "%s command is disabled"
i.commands.reload = "Reloading players..."
i.commands.kqf.get = "KQF: %x"
i.commands.kqf.set.error = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
i.commands.kqf.set.success = "KQF set, please switch Land/World"
i.commands.kqf.version = "This command is disabled prior to MHFG10"
i.commands.rights.error = "Error in command. Format: %s x"
i.commands.rights.success = "Set rights integer: %d"
i.commands.course.error = "Error in command. Format: %s <name>"
i.commands.course.disabled = "%s Course disabled"
i.commands.course.enabled = "%s Course enabled"
i.commands.course.locked = "%s Course is locked"
i.commands.teleport.error = "Error in command. Format: %s x y"
i.commands.teleport.success = "Teleporting to %d %d"
i.commands.psn.error = "Error in command. Format: %s <psn id>"
i.commands.psn.success = "Connected PSN ID: %s"
i.commands.psn.exists = "PSN ID is connected to another account!"
strings["commandRaviNoCommand"] = "No Raviente command specified!"
strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment"
strings["commandRaviStartError"] = "The Great Slaying has already begun!"
strings["commandRaviMultiplier"] = "Raviente multiplier is currently %.2fx"
strings["commandRaviResSuccess"] = "Sending resurrection support!"
strings["commandRaviResError"] = "Resurrection support has not been requested!"
strings["commandRaviSedSuccess"] = "Sending sedation support if requested!"
strings["commandRaviRequest"] = "Requesting sedation support!"
strings["commandRaviError"] = "Raviente command not recognised!"
strings["commandRaviNoPlayers"] = "No one has joined the Great Slaying!"
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
i.commands.discord.success = "Your Discord token: %s"
strings["ravienteBerserk"] = "<Great Slaying: Berserk> is being held!"
strings["ravienteExtreme"] = "<Great Slaying: Extreme> is being held!"
strings["ravienteExtremeLimited"] = "<Great Slaying: Extreme (Limited)> is being held!"
strings["ravienteBerserkSmall"] = "<Great Slaying: Berserk (Small)> is being held!"
i.commands.ban.noUser = "Could not find user"
i.commands.ban.success = "Successfully banned %s"
i.commands.ban.invalid = "Invalid Character ID"
i.commands.ban.error = "Error in command. Format: %s <id> [length]"
i.commands.ban.length = " until %s"
strings["guildInviteName"] = "Invitation!"
strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?"
i.commands.timer.enabled = "Quest timer enabled"
i.commands.timer.disabled = "Quest timer disabled"
strings["guildInviteSuccessName"] = "Success!"
strings["guildInviteSuccess"] = "You have successfully joined\n「%s」."
i.commands.ravi.noCommand = "No Raviente command specified!"
i.commands.ravi.start.success = "The Great Slaying will begin in a moment"
i.commands.ravi.start.error = "The Great Slaying has already begun!"
i.commands.ravi.multiplier = "Raviente multiplier is currently %.2fx"
i.commands.ravi.res.success = "Sending resurrection support!"
i.commands.ravi.res.error = "Resurrection support has not been requested!"
i.commands.ravi.sed.success = "Sending sedation support if requested!"
i.commands.ravi.request = "Requesting sedation support!"
i.commands.ravi.error = "Raviente command not recognised!"
i.commands.ravi.noPlayers = "No one has joined the Great Slaying!"
i.commands.ravi.version = "This command is disabled outside of MHFZZ"
strings["guildInviteAcceptedName"] = "Accepted"
strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」."
i.raviente.berserk = "<Great Slaying: Berserk> is being held!"
i.raviente.extreme = "<Great Slaying: Extreme> is being held!"
i.raviente.extremeLimited = "<Great Slaying: Extreme (Limited)> is being held!"
i.raviente.berserkSmall = "<Great Slaying: Berserk (Small)> is being held!"
strings["guildInviteRejectName"] = "Rejected"
strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」."
i.guild.invite.title = "Invitation!"
i.guild.invite.body = "You have been invited to join\n「%s」\nDo you want to accept?"
strings["guildInviteDeclinedName"] = "Declined"
strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」."
i.guild.invite.success.title = "Success!"
i.guild.invite.success.body = "You have successfully joined\n「%s」."
i.guild.invite.accepted.title = "Accepted"
i.guild.invite.accepted.body = "The recipient accepted your invitation to join\n「%s」."
i.guild.invite.rejected.title = "Rejected"
i.guild.invite.rejected.body = "You rejected the invitation to join\n「%s」."
i.guild.invite.declined.title = "Declined"
i.guild.invite.declined.body = "The recipient declined your invitation to join\n「%s」."
}
return strings
return i
}

View File

@@ -86,13 +86,11 @@ func NewSession(server *Server, conn net.Conn) *Session {
// Start starts the session packet send and recv loop(s).
func (s *Session) Start() {
go func() {
s.logger.Debug("New 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()
}()
s.logger.Debug("New 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()
go s.recvLoop()
}
// QueueSend queues a packet (raw []byte) to be sent.
@@ -150,16 +148,19 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) {
}
func (s *Session) sendLoop() {
var pkt packet
for {
if s.closed {
return
}
pkt := <-s.sendPackets
err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...))
if err != nil {
s.logger.Warn("Failed to send packet")
for len(s.sendPackets) > 0 {
pkt = <-s.sendPackets
err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...))
if err != nil {
s.logger.Warn("Failed to send packet")
}
}
time.Sleep(10 * time.Millisecond)
time.Sleep(5 * time.Millisecond)
}
}
@@ -178,14 +179,13 @@ func (s *Session) recvLoop() {
s.logger.Info(fmt.Sprintf("[%s] Disconnected", s.Name))
logoutPlayer(s)
return
}
if err != nil {
} else if err != nil {
s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err))
logoutPlayer(s)
return
}
s.handlePacketGroup(pkt)
time.Sleep(10 * time.Millisecond)
time.Sleep(5 * time.Millisecond)
}
}
@@ -253,13 +253,9 @@ func ignored(opcode network.PacketID) bool {
}
func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipient string) {
if !s.server.erupeConfig.DevMode {
if sender == "Server" && !s.server.erupeConfig.DebugOptions.LogOutboundMessages {
return
}
if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
return
} else if sender != "Server" && !s.server.erupeConfig.DevModeOptions.LogInboundMessages {
} else if sender != "Server" && !s.server.erupeConfig.DebugOptions.LogInboundMessages {
return
}
@@ -277,8 +273,8 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
fmt.Printf("[%s] -> [%s]\n", sender, recipient)
}
fmt.Printf("Opcode: %s\n", opcodePID)
if s.server.erupeConfig.DevModeOptions.LogMessageData {
if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength {
if s.server.erupeConfig.DebugOptions.LogMessageData {
if len(data) <= s.server.erupeConfig.DebugOptions.MaxHexdumpLength {
fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data))
} else {
fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data))
@@ -313,3 +309,12 @@ func (s *Session) NextObjectID() uint32 {
bf.Seek(0, 0)
return bf.ReadUint32()
}
func (s *Session) isOp() bool {
var op bool
err := s.server.db.QueryRow(`SELECT op FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&op)
if err == nil && op {
return true
}
return false
}

View File

@@ -7,12 +7,39 @@ import (
"regexp"
)
var Commands = []*discordgo.ApplicationCommand{
{
Name: "link",
Description: "Link your Erupe account to Discord",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "token",
Description: "The token provided by the Discord command in-game",
Required: true,
},
},
},
{
Name: "password",
Description: "Change your Erupe account password",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "password",
Description: "Your new password",
Required: true,
},
},
},
}
type DiscordBot struct {
Session *discordgo.Session
config *_config.Config
logger *zap.Logger
MainGuild *discordgo.Guild
RealtimeChannel *discordgo.Channel
Session *discordgo.Session
config *_config.Config
logger *zap.Logger
MainGuild *discordgo.Guild
RelayChannel *discordgo.Channel
}
type Options struct {
@@ -28,18 +55,22 @@ func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) {
return nil, err
}
realtimeChannel, err := session.Channel(options.Config.Discord.RealtimeChannelID)
var relayChannel *discordgo.Channel
if options.Config.Discord.RelayChannel.Enabled {
relayChannel, err = session.Channel(options.Config.Discord.RelayChannel.RelayChannelID)
}
if err != nil {
options.Logger.Fatal("Discord failed to create realtimeChannel", zap.Error(err))
options.Logger.Fatal("Discord failed to create relayChannel", zap.Error(err))
return nil, err
}
discordBot = &DiscordBot{
config: options.Config,
logger: options.Logger,
Session: session,
RealtimeChannel: realtimeChannel,
config: options.Config,
logger: options.Logger,
Session: session,
RelayChannel: relayChannel,
}
return
@@ -51,7 +82,7 @@ func (bot *DiscordBot) Start() (err error) {
return
}
// Replace all mentions to real name from the message.
// NormalizeDiscordMessage replaces all mentions to real name from the message.
func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`)
emojiRegex := regexp.MustCompile(`(?:<a?)?:(\w+):(?:\d{18}>)?`)
@@ -74,7 +105,11 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
}
func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {
_, err = bot.Session.ChannelMessageSend(bot.RealtimeChannel.ID, message)
if bot.RelayChannel == nil {
return
}
_, err = bot.Session.ChannelMessageSend(bot.RelayChannel.ID, message)
return
}

View File

@@ -12,45 +12,40 @@ var (
// CalcSum32 calculates the custom MHF "sum32" checksum of the given data.
func CalcSum32(data []byte) uint32 {
tableIdx0 := int(len(data) & 0xFF)
tableIdx1 := int(data[len(data)>>1] & 0xFF)
tableIdx0 := (len(data) + 1) & 0xFF
tableIdx1 := int((data[len(data)>>1] + 1) & 0xFF)
out := make([]byte, 4)
for i := 0; i < len(data); i++ {
tableIdx0++
tableIdx1++
tmp := byte((_sum32Table1[tableIdx1%9] ^ _sum32Table0[tableIdx0%7]) ^ data[i])
out[i&3] = (out[i&3] + tmp) & 0xFF
key := data[i] ^ _sum32Table0[(tableIdx0+i)%7] ^ _sum32Table1[(tableIdx1+i)%9]
out[i&3] = (out[i&3] + key) & 0xFF
}
return binary.BigEndian.Uint32(out)
}
func rotate(k *uint32) {
*k = uint32(((54323 * uint(*k)) + 1) & 0xFFFFFFFF)
}
// EncryptBin8 encrypts the given data using MHF's "binary8" encryption.
func EncryptBin8(data []byte, key byte) []byte {
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF)
_key := uint32(key)
var output []byte
for i := 0; i < len(data); i++ {
tmp := (_bin8Key[i&7] ^ byte((curKey>>13)&0xFF))
rotate(&_key)
tmp := _bin8Key[i&7] ^ byte((_key>>13)&0xFF)
output = append(output, data[i]^tmp)
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
}
return output
}
// DecryptBin8 decrypts the given MHF "binary8" data.
func DecryptBin8(data []byte, key byte) []byte {
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF)
_key := uint32(key)
var output []byte
for i := 0; i < len(data); i++ {
tmp := (data[i] ^ byte((curKey>>13)&0xFF))
rotate(&_key)
tmp := data[i] ^ byte((_key>>13)&0xFF)
output = append(output, tmp^_bin8Key[i&7])
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
}
return output
}

View File

@@ -111,7 +111,7 @@ func (s *Server) handleEntranceServerConnection(conn net.Conn) {
return
}
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogInboundMessages {
if s.erupeConfig.DebugOptions.LogInboundMessages {
fmt.Printf("[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
}

View File

@@ -29,7 +29,6 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
}
}
sid := (4096 + serverIdx*256) * 6000
if si.IP == "" {
si.IP = config.Host
}
@@ -38,8 +37,8 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
} else {
bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4()))
}
bf.WriteUint16(16 + uint16(serverIdx))
bf.WriteUint16(0x0000)
bf.WriteUint16(uint16(serverIdx | 16))
bf.WriteUint16(0)
bf.WriteUint16(uint16(len(si.Channels)))
bf.WriteUint8(si.Type)
bf.WriteUint8(uint8(((channelserver.TimeAdjusted().Unix() / 86400) + int64(serverIdx)) % 3))
@@ -47,20 +46,15 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
bf.WriteUint8(si.Recommended)
}
if s.erupeConfig.RealClientMode <= _config.F5 {
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...)
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false))
} else if s.erupeConfig.RealClientMode <= _config.GG {
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...)
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
bf.WriteUint8(uint8(len(combined)))
bf.WriteBytes(combined)
fullName := append(append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...), stringsupport.UTF8ToSJIS(si.Description)...)
if s.erupeConfig.RealClientMode >= _config.G1 && s.erupeConfig.RealClientMode <= _config.G5 {
bf.WriteUint8(uint8(len(fullName)))
bf.WriteBytes(fullName)
} else {
bf.WriteUint8(0) // Prevents malformed server name
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...)
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false))
if s.erupeConfig.RealClientMode >= _config.G51 {
bf.WriteUint8(0) // Ignored
}
bf.WriteBytes(stringsupport.PaddedString(string(fullName), 65, false))
}
if s.erupeConfig.RealClientMode >= _config.GG {
@@ -68,31 +62,31 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
}
for channelIdx, ci := range si.Channels {
sid = (4096 + serverIdx*256) + (16 + channelIdx)
if _config.ErupeConfig.DevMode && _config.ErupeConfig.ProxyPort != 0 {
bf.WriteUint16(_config.ErupeConfig.ProxyPort)
sid := (serverIdx<<8 | 4096) + (channelIdx | 16)
if _config.ErupeConfig.DebugOptions.ProxyPort != 0 {
bf.WriteUint16(_config.ErupeConfig.DebugOptions.ProxyPort)
} else {
bf.WriteUint16(ci.Port)
}
bf.WriteUint16(16 + uint16(channelIdx))
bf.WriteUint16(uint16(channelIdx | 16))
bf.WriteUint16(ci.MaxPlayers)
var currentPlayers uint16
s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(&currentPlayers)
bf.WriteUint16(currentPlayers)
bf.WriteUint16(0) // Unk
bf.WriteUint16(0) // Unk
bf.WriteUint16(0) // Unk
bf.WriteUint16(0) // Unk
bf.WriteUint16(0) // Unk
bf.WriteUint16(0) // Unk
bf.WriteUint16(319) // Unk
bf.WriteUint16(252) // Unk
bf.WriteUint16(248) // Unk
bf.WriteUint16(12345) // Unk
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(319) // Unk
bf.WriteUint16(254 - currentPlayers) // Unk
bf.WriteUint16(255 - currentPlayers) // Unk
bf.WriteUint16(12345)
}
}
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))
bf.WriteUint32(0x0000003C)
bf.WriteUint32(60)
return bf.Data()
}
@@ -136,7 +130,7 @@ func makeSv2Resp(config *_config.Config, s *Server, local bool) []byte {
}
rawServerData := encodeServerInfo(config, s, local)
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogOutboundMessages {
if s.erupeConfig.DebugOptions.LogOutboundMessages {
fmt.Printf("[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(rawServerData), hex.Dump(rawServerData))
}
@@ -161,14 +155,14 @@ func makeUsrResp(pkt []byte, s *Server) []byte {
var sid uint16
err := s.db.QueryRow("SELECT(SELECT server_id FROM sign_sessions WHERE char_id=$1) AS _", cid).Scan(&sid)
if err != nil {
resp.WriteBytes(make([]byte, 4))
resp.WriteUint16(0)
} else {
resp.WriteUint16(sid)
resp.WriteUint16(0)
}
resp.WriteUint16(0)
}
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogOutboundMessages {
if s.erupeConfig.DebugOptions.LogOutboundMessages {
fmt.Printf("[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(resp.Data()), hex.Dump(resp.Data()))
}

View File

@@ -198,17 +198,17 @@ func (s *Server) checkToken(uid uint32) (bool, error) {
}
func (s *Server) registerUidToken(uid uint32) (uint32, string, error) {
token := token.Generate(16)
_token := token.Generate(16)
var tid uint32
err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, token).Scan(&tid)
return tid, token, err
err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, _token).Scan(&tid)
return tid, _token, err
}
func (s *Server) registerPsnToken(psn string) (uint32, string, error) {
token := token.Generate(16)
_token := token.Generate(16)
var tid uint32
err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, token).Scan(&tid)
return tid, token, err
err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, _token).Scan(&tid)
return tid, _token, err
}
func (s *Server) validateToken(token string, tokenID uint32) bool {
@@ -229,9 +229,9 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) {
var passDB string
err := s.db.QueryRow(`SELECT id, password FROM users WHERE username = $1`, user).Scan(&uid, &passDB)
if err != nil {
if err == sql.ErrNoRows {
if errors.Is(err, sql.ErrNoRows) {
s.logger.Info("User not found", zap.String("User", user))
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.AutoCreateAccount {
if s.erupeConfig.AutoCreateAccount {
uid, err = s.registerDBAccount(user, pass)
if err == nil {
return uid, SIGN_SUCCESS
@@ -244,6 +244,15 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) {
return 0, SIGN_EABORT
} else {
if bcrypt.CompareHashAndPassword([]byte(passDB), []byte(pass)) == nil {
var bans int
err = s.db.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires IS NULL`, uid).Scan(&bans)
if err == nil && bans > 0 {
return uid, SIGN_EELIMINATE
}
err = s.db.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires > now()`, uid).Scan(&bans)
if err == nil && bans > 0 {
return uid, SIGN_ESUSPEND
}
return uid, SIGN_SUCCESS
}
return 0, SIGN_EPASS

View File

@@ -72,7 +72,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
bf.WriteUint32(char.ID)
// Exp, HR[x] is split by 0, 1, 30, 50, 99, 299, 998, 999
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.MaxLauncherHR {
if s.server.erupeConfig.DebugOptions.MaxLauncherHR {
bf.WriteUint16(999)
} else {
bf.WriteUint16(char.HRP)
@@ -144,20 +144,38 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser)
bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true))
}
bf.WriteUint16(0xCA10)
bf.WriteUint16(0x4E20)
ps.Uint16(bf, "", false) // unk key
bf.WriteUint8(0x00)
bf.WriteUint16(0xCA11)
bf.WriteUint16(0x0001)
bf.WriteUint16(0x4E20)
ps.Uint16(bf, "", false) // unk ipv4
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[0])
if s.server.erupeConfig.DebugOptions.CapLink.Values[0] == 51728 {
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[1])
if s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20000 || s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20002 {
ps.Uint16(bf, s.server.erupeConfig.DebugOptions.CapLink.Key, false)
}
}
caStruct := []struct {
Unk0 uint8
Unk1 uint32
Unk2 string
}{}
bf.WriteUint8(uint8(len(caStruct)))
for i := range caStruct {
bf.WriteUint8(caStruct[i].Unk0)
bf.WriteUint32(caStruct[i].Unk1)
ps.Uint8(bf, caStruct[i].Unk2, false)
}
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[2])
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[3])
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[4])
if s.server.erupeConfig.DebugOptions.CapLink.Values[2] == 51729 && s.server.erupeConfig.DebugOptions.CapLink.Values[3] == 1 && s.server.erupeConfig.DebugOptions.CapLink.Values[4] == 20000 {
ps.Uint16(bf, fmt.Sprintf(`%s:%d`, s.server.erupeConfig.DebugOptions.CapLink.Host, s.server.erupeConfig.DebugOptions.CapLink.Port), false)
}
bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix()))
bf.WriteUint32(0)
tickets := []uint32{
s.server.erupeConfig.GameplayOptions.MezfesSoloTickets,
s.server.erupeConfig.GameplayOptions.MezfesGroupTickets,
s.server.erupeConfig.GameplayOptions.MezFesSoloTickets,
s.server.erupeConfig.GameplayOptions.MezFesGroupTickets,
}
stalls := []uint8{
10, 3, 6, 9, 4, 8, 5, 7,

View File

@@ -37,7 +37,7 @@ type Session struct {
func (s *Session) work() {
pkt, err := s.cryptConn.ReadPacket()
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
if s.server.erupeConfig.DebugOptions.LogInboundMessages {
fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
}
@@ -54,7 +54,7 @@ func (s *Session) handlePacket(pkt []byte) error {
bf := byteframe.NewByteFrameFromBytes(pkt)
reqType := string(bf.ReadNullTerminatedBytes())
switch reqType[:len(reqType)-3] {
case "DLTSKEYSIGN:", "DSGN:":
case "DLTSKEYSIGN:", "DSGN:", "SIGN:":
s.handleDSGN(bf)
case "PS3SGN:":
s.client = PS3
@@ -78,7 +78,7 @@ func (s *Session) handlePacket(pkt []byte) error {
}
default:
s.logger.Warn("Unknown request", zap.String("reqType", reqType))
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
if s.server.erupeConfig.DebugOptions.LogInboundMessages {
fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
}
}
@@ -102,7 +102,7 @@ func (s *Session) authenticate(username string, password string) {
default:
bf.WriteUint8(uint8(resp))
}
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
if s.server.erupeConfig.DebugOptions.LogOutboundMessages {
fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data()))
}
_ = s.cryptConn.SendPacket(bf.Data())

View File

@@ -80,7 +80,7 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
PatchServer: s.erupeConfig.SignV2.PatchServer,
Notices: []string{},
}
if s.erupeConfig.DevModeOptions.MaxLauncherHR {
if s.erupeConfig.DebugOptions.MaxLauncherHR {
for i := range resp.Characters {
resp.Characters[i].HR = 7
}
@@ -93,8 +93,8 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
ID: uint32(channelserver.TimeWeekStart().Unix()),
Start: uint32(channelserver.TimeWeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()),
End: uint32(channelserver.TimeWeekNext().Unix()),
SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets,
GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets,
SoloTickets: s.erupeConfig.GameplayOptions.MezFesSoloTickets,
GroupTickets: s.erupeConfig.GameplayOptions.MezFesGroupTickets,
Stalls: stalls,
}
if !s.erupeConfig.HideLoginNotice {
@@ -226,7 +226,7 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
return
}
if s.erupeConfig.DevModeOptions.MaxLauncherHR {
if s.erupeConfig.DebugOptions.MaxLauncherHR {
character.HR = 7
}
w.Header().Add("Content-Type", "application/json")