Merge pull request #26 from ZeruLight/merge/feature/festa

merge changes into feature/festa
This commit is contained in:
wish
2022-08-12 00:43:46 +10:00
committed by GitHub
63 changed files with 967 additions and 814 deletions

View File

@@ -4,7 +4,7 @@ on: [push]
jobs:
build:
runs-on: windows-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -14,13 +14,28 @@ jobs:
with:
go-version: 1.18
- name: Build
run: go build -v
- name: Build Linux-amd64
run: env GOOS=linux GOARCH=amd64 go build -v
- name: Upload artifacts
- name: Upload Linux-amd64 artifacts
uses: actions/upload-artifact@v3
with:
name: Erupe
name: Linux-amd64
path: |
./erupe-ce
./config.json
./www/
./savedata/
./bin/
./RoadShopItems.csv
- name: Build Windows-amd64
run: env GOOS=windows GOARCH=amd64 go build -v
- name: Upload Windows-amd64 artifacts
uses: actions/upload-artifact@v3
with:
name: Windows-amd64
path: |
./erupe-ce.exe
./config.json

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2019 The Erupe Developers from Einherjar Team
Copyright (c) 2019 The Erupe Developers, The Erupe Developers from Einherjar Team, ZeruLight
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,8 @@
# Erupe Community Edition
This is a community upload of a community project. The amount of people who worked on it is innumerous, and hard to keep track of. Credits to Andoryuuta, Fist's Team, the French Team, Mai's Team and many others. No matter the relations, these files will remain public and open source, free for all to use and modify.
A pastebin with various links, tips, and FAQ: https://pastebin.com/QqAwZSTC
[A pastebin with various links, tips, and FAQ](https://pastebin.com/QqAwZSTC)
An upload for the quest and scenario files exists here: https://github.com/xl3lackout/MHFZ-Quest-Files
[An upload for the quest and scenario files exists here](https://github.com/xl3lackout/MHFZ-Quest-Files)
(Over 300k+ files)

View File

@@ -11,7 +11,8 @@ func Uint8(bf *byteframe.ByteFrame, x string, t bool) {
e := japanese.ShiftJIS.NewEncoder()
xt, _, err := transform.String(e, x)
if err != nil {
panic(err)
bf.WriteUint8(0)
return
}
x = xt
}
@@ -24,7 +25,8 @@ func Uint16(bf *byteframe.ByteFrame, x string, t bool) {
e := japanese.ShiftJIS.NewEncoder()
xt, _, err := transform.String(e, x)
if err != nil {
panic(err)
bf.WriteUint16(0)
return
}
x = xt
}
@@ -37,7 +39,8 @@ func Uint32(bf *byteframe.ByteFrame, x string, t bool) {
e := japanese.ShiftJIS.NewEncoder()
xt, _, err := transform.String(e, x)
if err != nil {
panic(err)
bf.WriteUint32(0)
return
}
x = xt
}

View File

@@ -106,7 +106,7 @@ func PaddedString(x string, size uint, t bool) []byte {
e := japanese.ShiftJIS.NewEncoder()
xt, _, err := transform.String(e, x)
if err != nil {
panic(err)
return make([]byte, size)
}
x = xt
}

View File

@@ -1,11 +1,12 @@
{
"host_ip": "127.0.0.1",
"bin_path": "bin",
"Host": "127.0.0.1",
"BinPath": "bin",
"DisableSoftCrash": false,
"devmode": true,
"devmodeoptions": {
"serverName" : "",
"hideLoginNotice": false,
"loginNotice": "<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9!<BR><BODY><LEFT><SIZE_2><C_5>Erupe is experimental software<C_7>, we are not liable for any<BR><BODY>issues caused by installing the software!<BR><BODY><BR><BODY><C_4>■Report bugs on Discord!<C_7><BR><BODY><BR><BODY><C_4>■Test everything!<C_7><BR><BODY><BR><BODY><C_4>■Don't talk to softlocking NPCs!<C_7><BR><BODY><BR><BODY><C_4>■Fork the code on GitHub!<C_7><BR><BODY><BR><BODY>Thank you to all of the contributors,<BR><BODY><BR><BODY>this wouldn't exist without you.",
"loginNotice": "<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9 (Patch 1)!<BR><BODY><LEFT><SIZE_2><C_5>Erupe is experimental software<C_7>, we are not liable for any<BR><BODY>issues caused by installing the software!<BR><BODY><BR><BODY><C_4>■Report bugs on Discord!<C_7><BR><BODY><BR><BODY><C_4>■Test everything!<C_7><BR><BODY><BR><BODY><C_4>■Don't talk to softlocking NPCs!<C_7><BR><BODY><BR><BODY><C_4>■Fork the code on GitHub!<C_7><BR><BODY><BR><BODY>Thank you to all of the contributors,<BR><BODY><BR><BODY>this wouldn't exist without you.",
"cleandb": false,
"maxlauncherhr": false,
"LogInboundMessages": false,
@@ -16,6 +17,9 @@
"FestaEvent": -1,
"TournamentEvent": 0,
"MezFesEvent": true,
"MezFesAlt": false,
"DisableMailItems": true,
"DisableTokenCheck": false,
"SaveDumps": {
"Enabled": true,
"OutputDir": "savedata"

View File

@@ -9,8 +9,9 @@ import (
// Config holds the global server-wide config.
type Config struct {
HostIP string `mapstructure:"host_ip"`
BinPath string `mapstructure:"bin_path"`
Host string `mapstructure:"Host"`
BinPath string `mapstructure:"BinPath"`
DisableSoftCrash bool // Disables the 'Press Return to exit' dialog allowing scripts to reboot the server automatically
DevMode bool
DevModeOptions DevModeOptions
@@ -36,6 +37,9 @@ type DevModeOptions struct {
FestaEvent int // Hunter's Festa event status
TournamentEvent int // VS Tournament event status
MezFesEvent bool // MezFes status
MezFesAlt bool // Swaps out Volpakkun for Tokotoko
DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
DisableMailItems bool // Hack to prevent english versions of MHF from crashing
SaveDumps SaveDumpOptions
}
@@ -137,8 +141,8 @@ func LoadConfig() (*Config, error) {
return nil, err
}
if c.HostIP == "" {
c.HostIP = getOutboundIP4().To4().String()
if c.Host == "" {
c.Host = getOutboundIP4().To4().String()
}
return c, nil

36
main.go
View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"math/rand"
"net"
"os"
"os/signal"
"syscall"
@@ -19,6 +19,8 @@ import (
"go.uber.org/zap"
)
var erupeConfig *config.Config
// Temporary DB auto clean on startup for quick development & testing.
func cleanDB(db *sqlx.DB) {
_ = db.MustExec("DELETE FROM guild_characters")
@@ -29,6 +31,7 @@ func cleanDB(db *sqlx.DB) {
}
func main() {
var err error
zapLogger, _ := zap.NewDevelopment()
defer zapLogger.Sync()
logger := zapLogger.Named("main")
@@ -36,7 +39,7 @@ func main() {
logger.Info("Starting Erupe")
// Load the configuration.
erupeConfig, err := config.LoadConfig()
erupeConfig, err = config.LoadConfig()
if err != nil {
preventClose(fmt.Sprintf("Failed to load config: %s", err.Error()))
}
@@ -45,6 +48,19 @@ func main() {
preventClose("Database password is blank")
}
if net.ParseIP(erupeConfig.Host) == nil {
ips, _ := net.LookupIP(erupeConfig.Host)
for _, ip := range ips {
if ip != nil {
erupeConfig.Host = ip.String()
break
}
}
if net.ParseIP(erupeConfig.Host) == nil {
preventClose("Invalid host address")
}
}
// Discord bot
var discordBot *discordbot.DiscordBot = nil
@@ -151,9 +167,6 @@ func main() {
ci := 0
count := 1
for _, ee := range erupeConfig.Entrance.Entries {
rand.Seed(time.Now().UnixNano())
// Randomly generate a season for the World
season := rand.Intn(3) + 1
for _, ce := range ee.Channels {
sid := (4096 + si*256) + (16 + ci)
c := *channelserver.NewServer(&channelserver.Config{
@@ -163,11 +176,17 @@ func main() {
DB: db,
DiscordBot: discordBot,
})
err = c.Start(int(ce.Port))
if ee.IP == "" {
c.IP = erupeConfig.Host
} else {
c.IP = ee.IP
}
c.Port = ce.Port
err = c.Start()
if err != nil {
preventClose(fmt.Sprintf("Failed to start channel server: %s", err.Error()))
} else {
channelQuery += fmt.Sprintf("INSERT INTO servers (server_id, season, current_players) VALUES (%d, %d, 0);", sid, season)
channelQuery += fmt.Sprintf("INSERT INTO servers (server_id, season, current_players) VALUES (%d, %d, 0);", sid, si%3)
channels = append(channels, &c)
logger.Info(fmt.Sprintf("Started channel server %d on port %d", count, ce.Port))
ci++
@@ -209,6 +228,9 @@ func wait() {
}
func preventClose(text string) {
if erupeConfig.DisableSoftCrash {
os.Exit(0)
}
fmt.Println("\nFailed to start Erupe:\n" + text)
go wait()
fmt.Println("\nPress Enter/Return to exit...")

View File

@@ -3,13 +3,18 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfAcquireTitle represents the MSG_MHF_ACQUIRE_TITLE
type MsgMhfAcquireTitle struct{}
type MsgMhfAcquireTitle struct {
AckHandle uint32
Unk0 uint16
Unk1 uint16
TitleID uint16
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfAcquireTitle) Opcode() network.PacketID {
@@ -18,7 +23,11 @@ func (m *MsgMhfAcquireTitle) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfAcquireTitle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint16()
m.Unk1 = bf.ReadUint16()
m.TitleID = bf.ReadUint16()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -2,6 +2,7 @@ package mhfpacket
import (
"errors"
"io"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -30,6 +31,7 @@ const (
type MsgMhfEnumerateGuild struct {
AckHandle uint32
Type EnumerateGuildType
Page uint8
RawDataPayload []byte
}
@@ -42,8 +44,9 @@ func (m *MsgMhfEnumerateGuild) Opcode() network.PacketID {
func (m *MsgMhfEnumerateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Type = EnumerateGuildType(bf.ReadUint8())
m.Page = bf.ReadUint8()
m.RawDataPayload = bf.DataFromCurrent()
bf.Seek(int64(len(bf.Data())-2), 0)
bf.Seek(-2, io.SeekEnd)
return nil
}

View File

@@ -3,13 +3,16 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfEnumerateInvGuild represents the MSG_MHF_ENUMERATE_INV_GUILD
type MsgMhfEnumerateInvGuild struct{}
type MsgMhfEnumerateInvGuild struct {
AckHandle uint32
Unk []byte
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfEnumerateInvGuild) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfEnumerateInvGuild) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfEnumerateInvGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.Unk = bf.ReadBytes(9)
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -3,15 +3,15 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfGetAchievement represents the MSG_MHF_GET_ACHIEVEMENT
type MsgMhfGetAchievement struct {
AckHandle uint32
Unk0 uint32 // id?
CharID uint32
Unk1 uint32 // char?
}
@@ -23,7 +23,7 @@ func (m *MsgMhfGetAchievement) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfGetAchievement) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.CharID = bf.ReadUint32()
m.Unk1 = bf.ReadUint32()
return nil
}

View File

@@ -3,9 +3,9 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
type OperateGuildMemberAction uint8
@@ -23,6 +23,7 @@ type MsgMhfOperateGuildMember struct {
GuildID uint32
CharID uint32
Action uint8
Unk []byte
}
// Opcode returns the ID associated with this packet type.
@@ -36,7 +37,7 @@ func (m *MsgMhfOperateGuildMember) Parse(bf *byteframe.ByteFrame, ctx *clientctx
m.GuildID = bf.ReadUint32()
m.CharID = bf.ReadUint32()
m.Action = bf.ReadUint8()
m.Unk = bf.ReadBytes(3)
return nil
}

View File

@@ -3,9 +3,9 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
type OperateMailOperation uint8
@@ -41,8 +41,7 @@ func (m *MsgMhfOprtMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon
m.Index = bf.ReadUint8()
m.Operation = OperateMailOperation(bf.ReadUint8())
m.Unk0 = bf.ReadUint8()
switch m.Operation {
case OPERATE_MAIL_ACQUIRE_ITEM:
if m.Operation == OPERATE_MAIL_ACQUIRE_ITEM {
m.Amount = bf.ReadUint16()
m.ItemID = bf.ReadUint16()
}

View File

@@ -3,15 +3,15 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfReadMercenaryW represents the MSG_MHF_READ_MERCENARY_W
type MsgMhfReadMercenaryW struct {
AckHandle uint32
Unk0 uint8
Unk0 bool
Unk1 uint8
Unk2 uint16 // Hardcoded 0 in the binary
}
@@ -24,7 +24,7 @@ func (m *MsgMhfReadMercenaryW) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfReadMercenaryW) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8()
m.Unk0 = bf.ReadBool()
m.Unk1 = bf.ReadUint8()
m.Unk2 = bf.ReadUint16()
return nil

View File

@@ -3,16 +3,18 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfSaveMercenary represents the MSG_MHF_SAVE_MERCENARY
type MsgMhfSaveMercenary struct {
AckHandle uint32
DataSize uint32
RawDataPayload []byte
GCP uint32
Unk0 uint32
MercData []byte
Unk1 uint32
}
// Opcode returns the ID associated with this packet type.
@@ -23,8 +25,11 @@ func (m *MsgMhfSaveMercenary) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfSaveMercenary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.DataSize = bf.ReadUint32()
m.RawDataPayload = bf.ReadBytes(uint(m.DataSize))
bf.ReadUint32() // lenData
m.GCP = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.MercData = bf.ReadBytes(uint(bf.ReadUint32()))
m.Unk1 = bf.ReadUint32()
return nil
}

View File

@@ -2,10 +2,11 @@ package mhfpacket
import (
"errors"
"erupe-ce/common/stringsupport"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfSendMail represents the MSG_MHF_SEND_MAIL
@@ -16,8 +17,8 @@ type MsgMhfSendMail struct {
BodyLength uint16
Quantity uint32
ItemID uint16
Subject []byte
Body []byte
Subject string
Body string
}
// Opcode returns the ID associated with this packet type.
@@ -33,8 +34,8 @@ func (m *MsgMhfSendMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon
m.BodyLength = bf.ReadUint16()
m.Quantity = bf.ReadUint32()
m.ItemID = bf.ReadUint16()
m.Subject = bf.ReadNullTerminatedBytes()
m.Body = bf.ReadNullTerminatedBytes()
m.Subject = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
m.Body = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
return nil
}

View File

@@ -3,13 +3,18 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfSetGuildManageRight represents the MSG_MHF_SET_GUILD_MANAGE_RIGHT
type MsgMhfSetGuildManageRight struct{}
type MsgMhfSetGuildManageRight struct {
AckHandle uint32
CharID uint32
Allowed bool
Unk []byte
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfSetGuildManageRight) Opcode() network.PacketID {
@@ -18,7 +23,11 @@ func (m *MsgMhfSetGuildManageRight) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfSetGuildManageRight) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
m.CharID = bf.ReadUint32()
m.Allowed = bf.ReadBool()
m.Unk = bf.ReadBytes(3)
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -3,9 +3,9 @@ package mhfpacket
import (
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfTransitMessage represents the MSG_MHF_TRANSIT_MESSAGE
@@ -13,7 +13,7 @@ type MsgMhfTransitMessage struct {
AckHandle uint32
Unk0 uint8
Unk1 uint8
Unk2 uint16
SearchType uint16
MessageData []byte
}
@@ -27,7 +27,7 @@ func (m *MsgMhfTransitMessage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8()
m.Unk1 = bf.ReadUint8()
m.Unk2 = bf.ReadUint16()
m.SearchType = bf.ReadUint16()
m.MessageData = bf.ReadBytes(uint(bf.ReadUint16()))
return nil
}

View File

@@ -2,14 +2,18 @@ package mhfpacket
import (
"errors"
"erupe-ce/common/bfutil"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgSysAcquireSemaphore represents the MSG_SYS_ACQUIRE_SEMAPHORE
type MsgSysAcquireSemaphore struct{}
type MsgSysAcquireSemaphore struct {
AckHandle uint32
SemaphoreID string
}
// Opcode returns the ID associated with this packet type.
func (m *MsgSysAcquireSemaphore) Opcode() network.PacketID {
@@ -18,7 +22,10 @@ func (m *MsgSysAcquireSemaphore) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgSysAcquireSemaphore) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
SemaphoreIDLength := bf.ReadUint8()
m.SemaphoreID = string(bfutil.UpToNull(bf.ReadBytes(uint(SemaphoreIDLength))))
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -3,8 +3,8 @@ package mhfpacket
import (
"errors"
"erupe-ce/common/byteframe"
"erupe-ce/common/bfutil"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
@@ -13,7 +13,7 @@ import (
type MsgSysEnumerateClient struct {
AckHandle uint32
Unk0 uint8 // Hardcoded 1 in the client
Unk1 uint8
Get uint8
StageID string
}
@@ -26,7 +26,7 @@ func (m *MsgSysEnumerateClient) Opcode() network.PacketID {
func (m *MsgSysEnumerateClient) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8()
m.Unk1 = bf.ReadUint8()
m.Get = bf.ReadUint8()
stageIDLength := bf.ReadUint8()
m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength))))
return nil

View File

@@ -2,18 +2,18 @@ package mhfpacket
import (
"errors"
"erupe-ce/common/stringsupport"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgSysEnumerateStage represents the MSG_SYS_ENUMERATE_STAGE
type MsgSysEnumerateStage struct {
AckHandle uint32
Unk0 uint8 // Hardcoded 1 in the binary
StageIDLength uint8
StageID string // NULL terminated string.
StagePrefix string // NULL terminated string.
}
// Opcode returns the ID associated with this packet type.
@@ -25,8 +25,8 @@ func (m *MsgSysEnumerateStage) Opcode() network.PacketID {
func (m *MsgSysEnumerateStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8()
m.StageIDLength = bf.ReadUint8()
m.StageID = string(bf.ReadBytes(uint(m.StageIDLength)))
bf.ReadUint8()
m.StagePrefix = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
return nil
}

0
patch-schema/.gitkeep Normal file
View File

View File

@@ -0,0 +1,41 @@
BEGIN;
CREATE TABLE IF NOT EXISTS public.achievements
(
id int NOT NULL PRIMARY KEY ,
ach0 int DEFAULT 0,
ach1 int DEFAULT 0,
ach2 int DEFAULT 0,
ach3 int DEFAULT 0,
ach4 int DEFAULT 0,
ach5 int DEFAULT 0,
ach6 int DEFAULT 0,
ach7 int DEFAULT 0,
ach8 int DEFAULT 0,
ach9 int DEFAULT 0,
ach10 int DEFAULT 0,
ach11 int DEFAULT 0,
ach12 int DEFAULT 0,
ach13 int DEFAULT 0,
ach14 int DEFAULT 0,
ach15 int DEFAULT 0,
ach16 int DEFAULT 0,
ach17 int DEFAULT 0,
ach18 int DEFAULT 0,
ach19 int DEFAULT 0,
ach20 int DEFAULT 0,
ach21 int DEFAULT 0,
ach22 int DEFAULT 0,
ach23 int DEFAULT 0,
ach24 int DEFAULT 0,
ach25 int DEFAULT 0,
ach26 int DEFAULT 0,
ach27 int DEFAULT 0,
ach28 int DEFAULT 0,
ach29 int DEFAULT 0,
ach30 int DEFAULT 0,
ach31 int DEFAULT 0,
ach32 int DEFAULT 0
);
END;

View File

@@ -1,11 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.users
ALTER rights SET DEFAULT 14;
ALTER TABLE IF EXISTS public.users
ALTER rights SET NOT NULL;
UPDATE public.users SET rights=14 WHERE rights IS NULL;
END;

View File

@@ -1,5 +0,0 @@
BEGIN;
CREATE SEQUENCE IF NOT EXISTS public.airou_id_seq;
END;

View File

@@ -1,10 +0,0 @@
BEGIN;
CREATE TABLE user_binaries
(
id int PRIMARY KEY,
type2 bytea,
type3 bytea
);
END;

View File

@@ -1,26 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS public.distribution
(
id serial NOT NULL PRIMARY KEY,
character_id int,
type int NOT NULL,
deadline timestamp without time zone,
event_name text NOT NULL DEFAULT 'GM Gift!',
description text NOT NULL DEFAULT '~C05You received a gift!',
times_acceptable int NOT NULL DEFAULT 1,
min_hr int NOT NULL DEFAULT 65535,
max_hr int NOT NULL DEFAULT 65535,
min_sr int NOT NULL DEFAULT 65535,
max_sr int NOT NULL DEFAULT 65535,
min_gr int NOT NULL DEFAULT 65535,
max_gr int NOT NULL DEFAULT 65535,
data bytea NOT NULL
);
CREATE TABLE IF NOT EXISTS public.distributions_accepted
(
distribution_id int,
character_id int
);
END;

View File

@@ -1,26 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook0status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook1status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook2status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook3status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook4status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook5status;
ALTER TABLE IF EXISTS public.gook
DROP COLUMN IF EXISTS gook5;
UPDATE public.gook SET gook0=NULL, gook1=NULL, gook2=NULL, gook3=NULL, gook4=NULL;
END;

View File

@@ -1,56 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.guilds
ADD COLUMN IF NOT EXISTS pugi_name_1 varchar(12) DEFAULT '';
ALTER TABLE IF EXISTS public.guilds
ADD COLUMN IF NOT EXISTS pugi_name_2 varchar(12) DEFAULT '';
ALTER TABLE IF EXISTS public.guilds
ADD COLUMN IF NOT EXISTS pugi_name_3 varchar(12) DEFAULT '';
CREATE TABLE IF NOT EXISTS public.guild_alliances
(
id serial NOT NULL PRIMARY KEY,
name varchar(24) NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT now(),
parent_id int NOT NULL,
sub1_id int,
sub2_id int
);
CREATE TABLE IF NOT EXISTS public.guild_adventures
(
id serial NOT NULL PRIMARY KEY,
guild_id int NOT NULL,
destination int NOT NULL,
charge int NOT NULL DEFAULT 0,
depart int NOT NULL,
return int NOT NULL,
collected_by text NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS public.guild_meals
(
id serial NOT NULL PRIMARY KEY,
guild_id int NOT NULL,
meal_id int NOT NULL,
level int NOT NULL,
expires int NOT NULL
);
CREATE TABLE IF NOT EXISTS public.guild_hunts
(
id serial NOT NULL PRIMARY KEY,
guild_id int NOT NULL,
host_id int NOT NULL,
destination int NOT NULL,
level int NOT NULL,
return int NOT NULL,
acquired bool NOT NULL DEFAULT false,
claimed bool NOT NULL DEFAULT false,
hunters text NOT NULL DEFAULT '',
treasure text NOT NULL DEFAULT '',
hunt_data bytea NOT NULL,
cats_used text NOT NULL
);
END;

View File

@@ -0,0 +1,9 @@
BEGIN;
ALTER TABLE IF EXISTS public.guilds
ADD COLUMN IF NOT EXISTS recruiting bool NOT NULL DEFAULT true;
ALTER TABLE IF EXISTS public.guild_characters
ADD COLUMN IF NOT EXISTS recruiter bool NOT NULL DEFAULT false;
END;

View File

@@ -1,6 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS house bytea;
END;

View File

@@ -0,0 +1,5 @@
BEGIN;
CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq;
END;

View File

@@ -0,0 +1,6 @@
BEGIN;
ALTER TABLE IF EXISTS public.mail
ADD COLUMN IF NOT EXISTS locked boolean NOT NULL DEFAULT false;
END;

View File

@@ -0,0 +1,9 @@
BEGIN;
ALTER TABLE IF EXISTS public.normal_shop_items
DROP COLUMN IF EXISTS enable_weeks;
ALTER TABLE IF EXISTS public.shop_item_state
DROP COLUMN IF EXISTS week;
END;

View File

@@ -1,35 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS public.normal_shop_items
(
shoptype integer,
shopid integer,
itemhash integer not null,
itemid integer,
points integer,
tradequantity integer,
rankreqlow integer,
rankreqhigh integer,
rankreqg integer,
storelevelreq integer,
maximumquantity integer,
boughtquantity integer,
roadfloorsrequired integer,
weeklyfataliskills integer,
enable_weeks character varying(8)
);
ALTER TABLE IF EXISTS public.normal_shop_items
ADD COLUMN IF NOT EXISTS enable_weeks character varying(8);
CREATE TABLE IF NOT EXISTS public.shop_item_state
(
char_id bigint REFERENCES characters (id),
itemhash int UNIQUE NOT NULL,
usedquantity int,
week int
);
ALTER TABLE IF EXISTS public.shop_item_state
ADD COLUMN IF NOT EXISTS week int;
END;

View File

@@ -1,9 +0,0 @@
BEGIN;
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS scenariodata bytea;
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS savefavoritequest bytea;
END;

11
patch-schema/titles.sql Normal file
View File

@@ -0,0 +1,11 @@
BEGIN;
CREATE TABLE IF NOT EXISTS public.titles
(
id int NOT NULL,
char_id int NOT NULL,
unlocked_at timestamp without time zone,
updated_at timestamp without time zone
);
END;

View File

@@ -1,32 +0,0 @@
BEGIN;
DROP TABLE IF EXISTS public.sign_sessions;
CREATE TABLE IF NOT EXISTS public.sign_sessions
(
user_id int NOT NULL,
char_id int,
token varchar(16) NOT NULL,
server_id integer
);
DROP TABLE IF EXISTS public.servers;
CREATE TABLE IF NOT EXISTS public.servers
(
server_id int NOT NULL,
season int NOT NULL,
current_players int NOT NULL
);
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS deleted boolean NOT NULL DEFAULT false;
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS friends text NOT NULL DEFAULT '';
ALTER TABLE IF EXISTS public.characters
ADD COLUMN IF NOT EXISTS blocked text NOT NULL DEFAULT '';
ALTER TABLE IF EXISTS public.users
ADD COLUMN IF NOT EXISTS last_character int DEFAULT 0;
END;

View File

@@ -4,7 +4,11 @@ import (
"bytes"
"encoding/binary"
"encoding/hex"
"erupe-ce/common/stringsupport"
"fmt"
"io"
"net"
"strings"
"io/ioutil"
"math/bits"
@@ -135,19 +139,18 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLogin)
rights := uint32(0x0E)
// 0e with normal sub 4e when having premium
// 01 = Character can take quests at allows
// 02 = Hunter Life, normal quests core sub
// 03 = Extra Course, extra quests, town boxes, QOL course, core sub
// 06 = Premium Course, standard 'premium' which makes ranking etc. faster
// 06 0A 0B = Boost Course, just actually 3 subs combined
// 08 09 1E = N Course, gives you the benefits of being in a netcafe (extra quests, N Points, daily freebies etc.) minimal and pointless
// 0C = N Boost course, ultra luxury course that ruins the game if in use
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", pkt.CharID0).Scan(&rights)
if !s.server.erupeConfig.DevModeOptions.DisableTokenCheck {
var token string
err := s.server.db.QueryRow("SELECT token FROM sign_sessions WHERE token=$1", pkt.LoginTokenString).Scan(&token)
if err != nil {
panic(err)
s.rawConn.Close()
s.logger.Warn(fmt.Sprintf("Invalid login token, offending CID: (%d)", pkt.CharID0))
return
}
}
rights := uint32(0x0E)
s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", pkt.CharID0).Scan(&rights)
s.Lock()
s.charID = pkt.CharID0
@@ -157,7 +160,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp
_, err = s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID)
_, err := s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID)
if err != nil {
panic(err)
}
@@ -342,16 +345,161 @@ func handleMsgSysRightsReload(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfTransitMessage)
// TODO: figure out what this is supposed to return
// probably what world+land the targeted character is on?
// stubbed response will just say user not found
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
resp := byteframe.NewByteFrame()
resp.WriteUint16(0)
var count uint16
switch pkt.SearchType {
case 1: // CID
bf := byteframe.NewByteFrameFromBytes(pkt.MessageData)
CharID := bf.ReadUint32()
for _, c := range s.server.Channels {
for _, session := range c.sessions {
if session.charID == CharID {
count++
sessionName := stringsupport.UTF8ToSJIS(session.Name)
sessionStage := stringsupport.UTF8ToSJIS(session.stageID)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
resp.WriteUint16(c.Port)
resp.WriteUint32(session.charID)
resp.WriteBool(true)
resp.WriteUint8(uint8(len(sessionName) + 1))
resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}])))
resp.WriteBytes(make([]byte, 40))
resp.WriteUint8(uint8(len(sessionStage) + 1))
resp.WriteBytes(make([]byte, 8))
resp.WriteNullTerminatedBytes(sessionName)
resp.WriteBytes(c.userBinaryParts[userBinaryPartID{session.charID, 3}])
resp.WriteNullTerminatedBytes(sessionStage)
}
}
}
case 2: // Name
bf := byteframe.NewByteFrameFromBytes(pkt.MessageData)
bf.ReadUint16() // lenSearchTerm
bf.ReadUint16() // maxResults
bf.ReadUint8() // Unk
searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
for _, c := range s.server.Channels {
for _, session := range c.sessions {
if count == 100 {
break
}
if strings.Contains(session.Name, searchTerm) {
count++
sessionName := stringsupport.UTF8ToSJIS(session.Name)
sessionStage := stringsupport.UTF8ToSJIS(session.stageID)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
resp.WriteUint16(c.Port)
resp.WriteUint32(session.charID)
resp.WriteBool(true)
resp.WriteUint8(uint8(len(sessionName) + 1))
resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{session.charID, 3}])))
resp.WriteBytes(make([]byte, 40))
resp.WriteUint8(uint8(len(sessionStage) + 1))
resp.WriteBytes(make([]byte, 8))
resp.WriteNullTerminatedBytes(sessionName)
resp.WriteBytes(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}])
resp.WriteNullTerminatedBytes(sessionStage)
}
}
}
case 3: // Enumerate Party
bf := byteframe.NewByteFrameFromBytes(pkt.MessageData)
ip := bf.ReadBytes(4)
ipString := fmt.Sprintf("%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0])
port := bf.ReadUint16()
bf.ReadUint16() // lenStage
maxResults := bf.ReadUint16()
bf.ReadBytes(1)
stageID := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
for _, c := range s.server.Channels {
if c.IP == ipString && c.Port == port {
for _, stage := range c.stages {
if stage.id == stageID {
if count == maxResults {
break
}
for session := range stage.clients {
count++
sessionStage := stringsupport.UTF8ToSJIS(session.stageID)
sessionName := stringsupport.UTF8ToSJIS(session.Name)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
resp.WriteUint16(c.Port)
resp.WriteUint32(session.charID)
resp.WriteUint8(uint8(len(sessionStage) + 1))
resp.WriteUint8(uint8(len(sessionName) + 1))
resp.WriteUint8(0)
resp.WriteUint8(7) // lenBinary
resp.WriteBytes(make([]byte, 48))
resp.WriteNullTerminatedBytes(sessionStage)
resp.WriteNullTerminatedBytes(sessionName)
resp.WriteUint16(999) // HR
resp.WriteUint16(999) // GR
resp.WriteBytes([]byte{0x06, 0x10, 0x00}) // Unk
}
}
}
}
}
case 4: // Find Party
bf := byteframe.NewByteFrameFromBytes(pkt.MessageData)
bf.ReadUint8()
maxResults := bf.ReadUint16()
bf.ReadUint8()
bf.ReadUint8()
partyType := bf.ReadUint16()
_ = bf.DataFromCurrent() // Restrictions
var stagePrefix string
switch partyType {
case 0: // Public Bar
stagePrefix = "sl2Ls210"
case 1: // Tokotoko Partnya
stagePrefix = "sl2Ls463"
case 2: // Hunting Prowess Match
stagePrefix = "sl2Ls286"
case 3: // Volpakkun Together
stagePrefix = "sl2Ls465"
case 5: // Quick Party
// Unk
}
for _, c := range s.server.Channels {
for _, stage := range c.stages {
if count == maxResults {
break
}
if strings.HasPrefix(stage.id, stagePrefix) {
count++
sessionStage := stringsupport.UTF8ToSJIS(stage.id)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
resp.WriteUint16(c.Port)
// TODO: This is half right, could be trimmed
resp.WriteUint16(0)
resp.WriteUint16(uint16(len(stage.clients)))
resp.WriteUint16(uint16(len(stage.clients)))
resp.WriteUint16(stage.maxPlayers)
resp.WriteUint16(0)
resp.WriteUint16(uint16(len(stage.clients)))
//
resp.WriteUint16(uint16(len(sessionStage) + 1))
resp.WriteUint8(1)
resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 1}])))
resp.WriteBytes(make([]byte, 16))
resp.WriteNullTerminatedBytes(sessionStage)
resp.WriteBytes([]byte{0x00})
resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 1}])
}
}
}
}
resp.Seek(0, io.SeekStart)
resp.WriteUint16(count)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgCaExchangeItem(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfServerCommand(s *Session, p mhfpacket.MHFPacket) {}
@@ -406,8 +554,6 @@ func handleMsgMhfEnumerateOrder(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfGetExtraInfo(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateUnionItem)
var boxContents []byte

View File

@@ -3,86 +3,139 @@ package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"fmt"
"io"
)
var achievementCurves = [][]int32{
// 0: HR weapon use, Class use, Tore dailies
{5, 15, 30, 50, 100, 150, 200, 300},
// 1: Weapon collector, G wep enhances
{1, 5, 10, 15, 30, 50, 75, 100},
// 2: Festa wins
{1, 2, 3, 4, 5, 6, 7, 8},
// 3: GR weapon use, Sigil crafts
{10, 50, 100, 200, 350, 500, 750, 999},
}
var achievementCurveMap = map[uint8][]int32{
0: achievementCurves[0], 1: achievementCurves[0], 2: achievementCurves[0], 3: achievementCurves[0],
4: achievementCurves[0], 5: achievementCurves[0], 6: achievementCurves[0], 7: achievementCurves[1],
8: achievementCurves[2], 9: achievementCurves[0], 10: achievementCurves[0], 11: achievementCurves[0],
12: achievementCurves[0], 13: achievementCurves[0], 14: achievementCurves[0], 15: achievementCurves[0],
16: achievementCurves[3], 17: achievementCurves[3], 18: achievementCurves[3], 19: achievementCurves[3],
20: achievementCurves[3], 21: achievementCurves[3], 22: achievementCurves[3], 23: achievementCurves[3],
24: achievementCurves[3], 25: achievementCurves[3], 26: achievementCurves[3], 27: achievementCurves[1],
28: achievementCurves[1], 29: achievementCurves[3], 30: achievementCurves[3], 31: achievementCurves[3],
32: achievementCurves[3],
}
type Achievement struct {
Level uint8
Value uint32
NextValue uint16
Required uint32
Updated bool
Progress uint32
Trophy uint8
}
func GetAchData(id uint8, score int32) Achievement {
curve := achievementCurveMap[id]
var ach Achievement
for i, v := range curve {
temp := score - v
if temp < 0 {
ach.Progress = uint32(score)
ach.Required = uint32(curve[i])
switch ach.Level {
case 0:
ach.NextValue = 5
case 1, 2, 3:
ach.NextValue = 10
case 4, 5:
ach.NextValue = 15
case 6:
ach.NextValue = 15
ach.Trophy = 0x40
case 7:
ach.NextValue = 20
ach.Trophy = 0x60
}
return ach
} else {
score = temp
ach.Level++
switch ach.Level {
case 1:
ach.Value += 5
case 2, 3, 4:
ach.Value += 10
case 5, 6, 7:
ach.Value += 15
case 8:
ach.Value += 20
}
}
}
ach.Required = uint32(curve[7])
ach.Trophy = 0x7F
ach.Progress = ach.Required
return ach
}
func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetAchievement)
achievementStruct := []struct {
ID uint8 // Main ID
Unk0 uint8 // always FF
Unk1 uint16 // 0x05 0x00
Unk2 uint32 // 0x01 0x0A 0x05 0x00
}{
{ID: 0, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 1, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 2, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 3, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 4, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 5, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 6, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 7, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 8, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 9, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 10, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 11, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 12, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 13, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 14, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 15, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 16, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 17, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 18, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 19, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 20, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 21, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 22, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 23, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 24, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 25, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 26, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 27, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 28, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 29, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 30, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 31, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 32, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 33, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 34, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 35, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 36, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 37, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 38, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 39, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 40, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 41, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 42, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 43, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 44, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 45, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 46, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 47, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 48, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 49, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 50, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 51, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 52, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 53, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 54, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 55, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 56, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 57, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 58, Unk0: 0xFF, Unk1: 0, Unk2: 0},
{ID: 59, Unk0: 0xFF, Unk1: 0, Unk2: 0},
var exists int
err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID).Scan(&exists)
if err != nil {
s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", pkt.CharID)
}
var scores [33]int32
err = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID).Scan(&scores[0],
&scores[0], &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8],
&scores[9], &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16],
&scores[17], &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24],
&scores[25], &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32])
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20))
return
}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(achievementStruct))) // Entry count
for _, entry := range achievementStruct {
resp.WriteUint8(entry.ID)
resp.WriteUint8(entry.Unk0)
resp.WriteUint16(entry.Unk1)
resp.WriteUint32(entry.Unk2)
var points uint32
resp.WriteBytes(make([]byte, 16))
resp.WriteBytes([]byte{0x02, 0x00, 0x00}) // Unk
var id uint8
entries := uint8(33)
resp.WriteUint8(entries) // Entry count
for id = 0; id < entries; id++ {
achData := GetAchData(id, scores[id])
points += achData.Value
resp.WriteUint8(id)
resp.WriteUint8(achData.Level)
resp.WriteUint16(achData.NextValue)
resp.WriteUint32(achData.Required)
resp.WriteBool(false) // TODO: Notify on rank increase since last checked, see MhfDisplayedAchievement
resp.WriteUint8(achData.Trophy)
/* Trophy bitfield
0000 0000
abcd efgh
B - Bronze (0x40)
B-C - Silver (0x60)
B-H - Gold (0x7F)
*/
resp.WriteUint16(0) // Unk
resp.WriteUint32(achData.Progress)
}
resp.Seek(0, io.SeekStart)
resp.WriteUint32(points)
resp.WriteUint32(points)
resp.WriteUint32(points)
resp.WriteUint32(points)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
@@ -93,11 +146,23 @@ func handleMsgMhfSetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfResetAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAddAchievement)
var exists int
err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", s.charID).Scan(&exists)
if err != nil {
s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", s.charID)
}
s.server.db.Exec(fmt.Sprintf("UPDATE achievements SET ach%d=ach%d+1 WHERE id=$1", pkt.AchievementID, pkt.AchievementID), s.charID)
}
func handleMsgMhfPaymentAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) {
// This is how you would figure out if the rank-up notification needs to occur
}
func handleMsgMhfGetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -16,6 +16,7 @@ import (
const (
BinaryMessageTypeState = 0
BinaryMessageTypeChat = 1
BinaryMessageTypeData = 3
BinaryMessageTypeMailNotify = 4
BinaryMessageTypeEmote = 6
)
@@ -118,7 +119,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
// Send to the proper recipients.
switch pkt.BroadcastType {
case BroadcastTypeWorld:
s.server.WorldcastMHF(resp, s)
s.server.WorldcastMHF(resp, s, nil)
case BroadcastTypeStage:
if isDiceCommand {
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
@@ -129,8 +130,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
if pkt.MessageType == 1 {
raviSema := getRaviSemaphore(s)
if raviSema != "" {
sema := s.server.semaphore[raviSema]
(*sema).BroadcastMHF(resp, s)
s.server.BroadcastMHF(resp, s)
}
} else {
s.server.BroadcastMHF(resp, s)

View File

@@ -24,7 +24,11 @@ func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) {
resp := byteframe.NewByteFrame()
stage.RLock()
var clients []uint32
switch pkt.Unk1 {
switch pkt.Get {
case 0: // All
for _, cid := range stage.clients {
clients = append(clients, cid)
}
case 1: // Not ready
for cid, ready := range stage.reservedClientSlots {
if !ready {

View File

@@ -71,8 +71,8 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
s.myseries.toreData = decompressedData[130228:130468] // 0x1FCB4 + 240
s.myseries.gardenData = decompressedData[142424:142492] // 0x22C58 + 68
isMale := uint8(decompressedData[80]) // 0x50
if isMale == 1 {
isFemale := decompressedData[81] // 0x51
if isFemale == 1 {
_, err = s.server.db.Exec("UPDATE characters SET is_female=true WHERE id=$1", s.charID)
} else {
_, err = s.server.db.Exec("UPDATE characters SET is_female=false WHERE id=$1", s.charID)
@@ -290,21 +290,14 @@ func dumpSaveData(s *Session, data []byte, suffix string) {
func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoaddata)
overrideFile := filepath.Join(".", "bin", "save_override.bin")
var data []byte
if _, err := os.Stat(overrideFile); err == nil {
file, err := os.Open(overrideFile)
if err != nil {
panic(err)
}
data, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin")); err == nil {
data, _ := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin"))
doAckBufSucceed(s, pkt.AckHandle, data)
return
}
var data []byte
err := s.server.db.QueryRow("SELECT savedata FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get savedata from db", zap.Error(err))

View File

@@ -12,7 +12,6 @@ import (
"strings"
"time"
"erupe-ce/common/bfutil"
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
@@ -55,6 +54,7 @@ type Guild struct {
PugiName1 string `db:"pugi_name_1"`
PugiName2 string `db:"pugi_name_2"`
PugiName3 string `db:"pugi_name_3"`
Recruiting bool `db:"recruiting"`
FestivalColour FestivalColour `db:"festival_colour"`
Souls uint32 `db:"souls"`
Rank uint16 `db:"rank"`
@@ -125,6 +125,7 @@ SELECT
pugi_name_1,
pugi_name_2,
pugi_name_3,
recruiting,
CASE WHEN (
SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id
) IS NULL THEN 'none' ELSE (
@@ -518,36 +519,6 @@ func rollbackTransaction(s *Session, transaction *sql.Tx) {
}
}
func FindGuildsByName(s *Session, name string) ([]*Guild, error) {
searchTerm := fmt.Sprintf("%%%s%%", name)
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
WHERE g.name ILIKE $1
`, guildInfoSelectQuery), searchTerm)
if err != nil {
s.logger.Error("failed to find guilds for search term", zap.Error(err), zap.String("searchTerm", name))
return nil, err
}
defer rows.Close()
guilds := make([]*Guild, 0)
for rows.Next() {
guild, err := buildGuildObjectFromDbResult(rows, err, s)
if err != nil {
return nil, err
}
guilds = append(guilds, guild)
}
return guilds, nil
}
func GetGuildInfoByID(s *Session, guildID uint32) (*Guild, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
@@ -676,6 +647,8 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
}
bf.WriteUint32(uint32(response))
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
return
case mhfpacket.OPERATE_GUILD_APPLY:
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
@@ -685,6 +658,8 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
} else {
bf.WriteUint32(guild.LeaderCharID)
}
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
return
case mhfpacket.OPERATE_GUILD_LEAVE:
var err error
@@ -701,81 +676,53 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
}
bf.WriteUint32(uint32(response))
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
return
case mhfpacket.OPERATE_GUILD_DONATE_RANK:
handleDonateRP(s, pkt, bf, guild, false)
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_DENY:
// TODO: close applications for guild
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID)
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_ALLOW:
// TODO: open applications for guild
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
s.server.db.Exec("UPDATE guilds SET recruiting=true WHERE id=$1", guild.ID)
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE:
handleAvoidLeadershipUpdate(s, pkt, true)
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE:
handleAvoidLeadershipUpdate(s, pkt, false)
case mhfpacket.OPERATE_GUILD_UPDATE_COMMENT:
pbf := byteframe.NewByteFrameFromBytes(pkt.UnkData)
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
_ = pbf.ReadUint8() // len
_ = pbf.ReadUint32()
guild.Comment = stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes())
err = guild.Save(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
bf.WriteUint32(0x00)
guild.Save(s)
case mhfpacket.OPERATE_GUILD_UPDATE_MOTTO:
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
guild.SubMotto = pkt.UnkData[3]
guild.MainMotto = pkt.UnkData[4]
err := guild.Save(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
guild.Save(s)
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_1:
handleRenamePugi(s, pkt.UnkData, guild, 1)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_2:
handleRenamePugi(s, pkt.UnkData, guild, 2)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_3:
handleRenamePugi(s, pkt.UnkData, guild, 3)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_1:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
// TODO: decode guild poogie outfits
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_2:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_3:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
case mhfpacket.OPERATE_GUILD_DONATE_EVENT:
handleDonateRP(s, pkt, bf, guild, true)
default:
panic(fmt.Sprintf("unhandled operate guild action '%d'", pkt.Action))
}
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleRenamePugi(s *Session, data []byte, guild *Guild, num int) {
@@ -860,42 +807,45 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
return
}
if pkt.Action == mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT || pkt.Action == mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT {
var mail Mail
switch pkt.Action {
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT:
err = guild.AcceptApplication(s, pkt.CharID)
mail = Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Accepted!",
Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
IsGuildInvite: false,
}
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT:
err = guild.RejectApplication(s, pkt.CharID)
mail = Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Rejected",
Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
IsGuildInvite: false,
}
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
}
doAckSimpleSucceed(s, pkt.AckHandle, nil)
return
}
character, err := GetCharacterGuildData(s, pkt.CharID)
if err != nil || character == nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
switch pkt.Action {
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK:
err = guild.RemoveCharacter(s, pkt.CharID)
mail = Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Kicked",
Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
IsGuildInvite: false,
}
default:
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action))
s.logger.Warn(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action))
}
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
return
} else {
mail.Send(s, nil)
}
doAckSimpleSucceed(s, pkt.AckHandle, nil)
}
@@ -912,6 +862,8 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
}
if err == nil && guild != nil {
s.prevGuildID = guild.ID
guildName := stringsupport.UTF8ToSJIS(guild.Name)
guildComment := stringsupport.UTF8ToSJIS(guild.Comment)
guildLeaderName := stringsupport.UTF8ToSJIS(guild.LeaderName)
@@ -943,7 +895,9 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(guild.SubMotto)
// Unk appears to be static
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
bf.WriteBool(!guild.Recruiting)
if characterGuildData == nil || characterGuildData.IsApplicant {
bf.WriteUint16(0x00)
@@ -1063,19 +1017,22 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
if err != nil {
resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // Count
resp.WriteUint8(5) // Unk, read if count == 0.
resp.WriteUint8(0) // Unk, read if count == 0.
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
if err != nil {
bf.WriteUint16(0)
} else {
bf.WriteUint16(uint16(len(applicants)))
for _, applicant := range applicants {
bf.WriteUint32(applicant.CharID)
bf.WriteUint32(0x05)
bf.WriteUint16(0x0032)
bf.WriteUint8(0x00)
ps.Uint16(bf, applicant.Name, true)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(applicant.HRP)
bf.WriteUint16(applicant.GR)
ps.Uint8(bf, applicant.Name, true)
}
}
bf.WriteUint16(0x0000)
@@ -1134,107 +1091,94 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
switch pkt.Type {
case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
bf.ReadBytes(8)
searchTermLength := bf.ReadUint16()
bf.ReadBytes(1)
searchTerm := bf.ReadBytes(uint(searchTermLength))
var searchTermSafe string
searchTermSafe, err = s.clientContext.StrConv.Decode(bfutil.UpToNull(searchTerm))
if err != nil {
panic(err)
bf.ReadBytes(10)
searchTerm := fmt.Sprintf(`%%%s%%`, stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()))
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE g.name ILIKE $1 OFFSET $2`, guildInfoSelectQuery), searchTerm, pkt.Page*10)
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
guilds, err = FindGuildsByName(s, searchTermSafe)
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
bf.ReadBytes(8)
searchTermLength := bf.ReadUint16()
bf.ReadBytes(1)
searchTerm := bf.ReadBytes(uint(searchTermLength))
var searchTermSafe string
searchTermSafe, err = s.clientContext.StrConv.Decode(bfutil.UpToNull(searchTerm))
if err != nil {
panic(err)
}
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE lc.name ILIKE $1`, guildInfoSelectQuery), searchTermSafe)
if err != nil {
s.logger.Error("Failed to retrieve guild by leader name", zap.Error(err))
} else {
bf.ReadBytes(10)
searchTerm := fmt.Sprintf(`%%%s%%`, stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()))
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE lc.name ILIKE $1 OFFSET $2`, guildInfoSelectQuery), searchTerm, pkt.Page*10)
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
bf.ReadBytes(3)
bf.ReadBytes(2)
ID := bf.ReadUint32()
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE leader_id = $1`, guildInfoSelectQuery), ID)
if err != nil {
s.logger.Error("Failed to retrieve guild by leader ID", zap.Error(err))
} else {
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS:
sorting := bf.ReadUint16()
sorting := bf.ReadUint8()
if sorting == 1 {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count DESC`, guildInfoSelectQuery))
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
} else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count ASC`, guildInfoSelectQuery))
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count ASC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
}
if err != nil {
s.logger.Error("Failed to retrieve guild by member count", zap.Error(err))
} else {
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION:
sorting := bf.ReadUint16()
sorting := bf.ReadUint8()
if sorting == 1 {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id ASC`, guildInfoSelectQuery))
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id ASC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
} else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id DESC`, guildInfoSelectQuery))
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
}
if err != nil {
s.logger.Error("Failed to retrieve guild by registration date", zap.Error(err))
} else {
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK:
sorting := bf.ReadUint16()
sorting := bf.ReadUint8()
if sorting == 1 {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp DESC`, guildInfoSelectQuery))
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
} else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp ASC`, guildInfoSelectQuery))
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp ASC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
}
if err != nil {
s.logger.Error("Failed to retrieve guild by rank", zap.Error(err))
} else {
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
bf.ReadBytes(3)
bf.ReadBytes(2)
mainMotto := bf.ReadUint16()
subMotto := bf.ReadUint16()
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE main_motto = $1 AND sub_motto = $2`, guildInfoSelectQuery), mainMotto, subMotto)
if err != nil {
s.logger.Error("Failed to retrieve guild by motto", zap.Error(err))
} else {
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE main_motto = $1 AND sub_motto = $2 OFFSET $3`, guildInfoSelectQuery), mainMotto, subMotto, pkt.Page*10)
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
//
// Assume the player wants the newest guilds with open recruitment
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE recruiting=true ORDER BY id DESC OFFSET $1`, guildInfoSelectQuery), pkt.Page*10)
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
//
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME:
@@ -1257,23 +1201,21 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
bf = byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(guilds)))
bf.WriteUint8(0x01) // Unk
for _, guild := range guilds {
bf.WriteUint8(0x00) // Unk
bf.WriteUint32(guild.ID)
bf.WriteUint32(guild.LeaderCharID)
bf.WriteUint16(guild.MemberCount)
bf.WriteUint8(0x00) // Unk
bf.WriteUint8(0x00) // Unk
bf.WriteUint16(guild.Rank)
bf.WriteUint16(0x0000) // Unk
bf.WriteUint16(guild.Rank) // OR guilds in alliance
bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
ps.Uint8(bf, guild.Name, true)
ps.Uint8(bf, guild.LeaderName, true)
bf.WriteUint8(0x01) // Unk
bf.WriteBool(!guild.Recruiting)
}
bf.WriteUint8(0x01) // Unk
bf.WriteUint8(0x00) // Unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -1324,6 +1266,10 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
guild, err = GetGuildInfoByCharacterId(s, s.charID)
}
if guild == nil && s.prevGuildID > 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID)
}
if err != nil {
s.logger.Warn("failed to retrieve guild sending no result message")
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
@@ -1417,6 +1363,15 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if guild == nil && s.prevGuildID != 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID)
s.prevGuildID = 0
if guild == nil || err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
}
if err != nil {
s.logger.Warn("failed to respond to manage rights message")
return
@@ -1438,7 +1393,8 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
for _, member := range members {
bf.WriteUint32(member.CharID)
bf.WriteUint32(0x0)
bf.WriteBool(member.Recruiter)
bf.WriteBytes(make([]byte, 3))
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
@@ -1930,9 +1886,16 @@ func handleMsgMhfGenerateUdGuildMap(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfUpdateGuild(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetGuildManageRight)
s.server.db.Exec("UPDATE guild_characters SET recruiter=$1 WHERE character_id=$2", pkt.Allowed, pkt.CharID)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateInvGuild)
stubEnumerateNoResults(s, pkt.AckHandle)
}
func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -17,6 +17,7 @@ type GuildMember struct {
IsApplicant bool `db:"is_applicant"`
OrderIndex uint8 `db:"order_index"`
LastLogin uint32 `db:"last_login"`
Recruiter bool `db:"recruiter"`
AvoidLeadership bool `db:"avoid_leadership"`
IsLeader bool `db:"is_leader"`
HRP uint16 `db:"hrp"`
@@ -44,11 +45,6 @@ func (gm *GuildMember) Save(s *Session) error {
return nil
}
//TODO add the recruiter permission to this check when it exists
func (gm *GuildMember) IsRecruiter() bool {
return gm.IsLeader || gm.IsSubLeader()
}
const guildMembersSelectSQL = `
SELECT
g.id as guild_id,
@@ -58,6 +54,7 @@ SELECT
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,

View File

@@ -21,7 +21,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err)
}
if actorCharGuildData == nil || !actorCharGuildData.IsRecruiter() {
if actorCharGuildData == nil || !actorCharGuildData.Recruiter {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
@@ -104,7 +104,7 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err)
}
if guildCharData == nil || !guildCharData.IsRecruiter() {
if guildCharData == nil || !guildCharData.Recruiter {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
@@ -190,13 +190,15 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
guildInfo, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
}
if guildInfo == nil {
if guildInfo == nil && s.prevGuildID == 0 {
doAckSimpleFail(s, pkt.AckHandle, nil)
return
} else {
guildInfo, err = GetGuildInfoByID(s, s.prevGuildID)
if guildInfo == nil || err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
}
rows, err := s.server.db.Queryx(`

View File

@@ -27,7 +27,7 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
}
bf := byteframe.NewByteFrame()
hunts := 0
rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1", guild.ID)
rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1 AND $2 < return+604800", guild.ID, Time_Current_Adjusted().Unix())
for rows.Next() {
hunt := &TreasureHunt{}
err = rows.StructScan(&hunt)
@@ -51,6 +51,9 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
bf.WriteBytes(hunt.HuntData)
break
} else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 {
if hunts == 30 {
break
}
hunts++
bf.WriteUint32(hunt.HuntID)
bf.WriteUint32(hunt.Destination)

View File

@@ -6,6 +6,8 @@ import (
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
"io"
"time"
)
func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
@@ -291,25 +293,54 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
type Title struct {
ID uint16 `db:"id"`
Acquired time.Time `db:"unlocked_at"`
Updated time.Time `db:"updated_at"`
}
func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateTitle)
var count uint16
bf := byteframe.NewByteFrame()
if pkt.CharID == 0 {
titleCount := 114 // all titles unlocked
bf.WriteUint16(uint16(titleCount)) // title count
bf.WriteUint16(0) // unk
for i := 0; i < titleCount; i++ {
bf.WriteUint16(uint16(i))
bf.WriteUint16(0) // unk
bf.WriteUint32(0) // timestamp acquired
bf.WriteUint32(0) // timestamp updated
}
} else {
bf.WriteUint16(0)
bf.WriteUint16(0) // Unk
rows, err := s.server.db.Queryx("SELECT id, unlocked_at, updated_at FROM titles WHERE char_id=$1", s.charID)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
for rows.Next() {
title := &Title{}
err = rows.StructScan(&title)
if err != nil {
continue
}
count++
bf.WriteUint16(title.ID)
bf.WriteUint16(0) // Unk
bf.WriteUint32(uint32(title.Acquired.Unix()))
bf.WriteUint32(uint32(title.Updated.Unix()))
}
bf.Seek(0, io.SeekStart)
bf.WriteUint16(count)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireTitle)
var exists int
err := s.server.db.QueryRow("SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2", pkt.TitleID, s.charID).Scan(&exists)
if err != nil || exists == 0 {
s.server.db.Exec("INSERT INTO titles VALUES ($1, $2, now(), now())", pkt.TitleID, s.charID)
} else {
s.server.db.Exec("UPDATE titles SET updated_at=now()")
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -2,6 +2,7 @@ package channelserver
import (
"database/sql"
"erupe-ce/common/stringsupport"
"time"
"erupe-ce/common/byteframe"
@@ -275,7 +276,7 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
body := s.clientContext.StrConv.MustEncode(mail.Body)
body := stringsupport.UTF8ToSJIS(mail.Body)
bf.WriteNullTerminatedBytes(body)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
@@ -307,13 +308,11 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
s.mailAccIndex++
itemAttached := m.AttachedItemID != 0
subject := s.clientContext.StrConv.MustEncode(m.Subject)
sender := s.clientContext.StrConv.MustEncode(m.SenderName)
msg.WriteUint32(m.SenderID)
msg.WriteUint32(uint32(m.CreatedAt.Unix()))
msg.WriteUint8(uint8(accIndex))
msg.WriteUint8(accIndex)
msg.WriteUint8(uint8(i))
flags := uint8(0x00)
@@ -329,9 +328,16 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
// System message, hides ID
// flags |= 0x04
// Workaround until EN mail items are patched
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DisableMailItems {
if itemAttached {
flags |= 0x08
}
} else {
if m.AttachedItemReceived {
flags |= 0x08
}
}
if m.IsGuildInvite {
flags |= 0x10
@@ -339,11 +345,10 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
msg.WriteUint8(flags)
msg.WriteBool(itemAttached)
msg.WriteUint8(uint8(len(subject) + 1))
msg.WriteUint8(uint8(len(sender) + 1))
msg.WriteNullTerminatedBytes(subject)
msg.WriteNullTerminatedBytes(sender)
msg.WriteUint8(16)
msg.WriteUint8(21)
msg.WriteBytes(stringsupport.PaddedString(m.Subject, 16, true))
msg.WriteBytes(stringsupport.PaddedString(m.SenderName, 21, true))
if itemAttached {
msg.WriteUint16(m.AttachedItemAmount)
msg.WriteUint16(m.AttachedItemID)
@@ -358,34 +363,22 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex])
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
switch mhfpacket.OperateMailOperation(pkt.Operation) {
switch pkt.Operation {
case mhfpacket.OPERATE_MAIL_DELETE:
err = mail.MarkDeleted(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_LOCK:
err = mail.MarkLocked(s, true)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_UNLOCK:
err = mail.MarkLocked(s, false)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM:
err = mail.MarkAcquired(s)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))

View File

@@ -9,7 +9,6 @@ import (
"go.uber.org/zap"
"io"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
)
@@ -132,65 +131,57 @@ func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x00) // Unk
bf.WriteUint32(rand.Uint32()) // Partner ID?
var nextID uint32
s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID)
bf.WriteUint32(nextID) // New MercID
bf.WriteUint32(0xDEADBEEF) // Unk
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
GCPValue := bf.ReadUint32()
_ = bf.ReadUint32() // unk
MercDataSize := bf.ReadUint32()
MercData := bf.ReadBytes(uint(MercDataSize))
_ = bf.ReadUint32() // unk
if MercDataSize > 0 {
// the save packet has an extra null byte after its size
_, err := s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", MercData[:MercDataSize], s.charID)
if err != nil {
s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
}
}
// gcp value is always present regardless
_, err := s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", GCPValue, s.charID)
if err != nil {
s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
if len(pkt.MercData) > 0 {
s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", pkt.MercData, s.charID)
}
s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", pkt.GCP, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
if pkt.Unk0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
return
}
var data []byte
var gcp uint32
// still has issues
err := s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get savemercenary data from db", zap.Error(err))
}
err = s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp)
if err != nil {
panic(err)
}
if len(data) == 0 {
data = []byte{0x00}
}
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data)
s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp)
resp := byteframe.NewByteFrame()
resp.WriteBytes(data)
resp.WriteUint16(0)
if len(data) == 0 {
resp.WriteBytes(make([]byte, 3))
} else {
resp.WriteBytes(data[1:])
resp.WriteUint32(0) // Unk
}
resp.WriteUint32(gcp)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryM)
// accessing actual rasta data of someone else still unsure of the formatting of this
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
var data []byte
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", pkt.CharID).Scan(&data)
resp := byteframe.NewByteFrame()
if len(data) == 0 {
resp.WriteBool(false)
} else {
resp.WriteBytes(data[4:])
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -119,12 +119,21 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(newSemaphore.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} else {
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
//pkt := p.(*mhfpacket.MsgSysAcquireSemaphore)
pkt := p.(*mhfpacket.MsgSysAcquireSemaphore)
if sema, exists := s.server.semaphore[pkt.SemaphoreID]; exists {
sema.clients[s] = s.charID
bf := byteframe.NewByteFrame()
bf.WriteUint32(sema.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} else {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
}
func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -2,11 +2,8 @@ package channelserver
import (
"encoding/hex"
"fmt"
"strings"
"time"
//"erupe-ce/common/stringsupport"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"github.com/lib/pq"
@@ -14,16 +11,6 @@ import (
"go.uber.org/zap"
)
func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateShop)
// SHOP TYPES:
@@ -162,27 +149,19 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
} else {
_, week := time.Now().ISOWeek()
season := fmt.Sprintf("%d", week%4)
shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills, COALESCE(enable_weeks, '') FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID)
shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID)
if err != nil {
panic(err)
}
var ItemHash, entryCount int
var itemID, Points, TradeQuantity, rankReqLow, rankReqHigh, rankReqG, storeLevelReq, maximumQuantity, boughtQuantity, roadFloorsRequired, weeklyFatalisKills, charQuantity uint16
var itemWeeks string
resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // total defs
for shopEntries.Next() {
err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills, &itemWeeks)
err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills)
if err != nil {
panic(err)
}
if len(itemWeeks) > 0 && !contains(strings.Split(itemWeeks, ","), season) {
continue
}
resp.WriteUint32(uint32(ItemHash))
resp.WriteUint16(0) // unk, always 0 in existing packets
resp.WriteUint16(itemID)
@@ -195,13 +174,9 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
resp.WriteUint16(storeLevelReq)
resp.WriteUint16(maximumQuantity)
if maximumQuantity > 0 {
var itemWeek int
err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0), COALESCE(week,-1) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity, &itemWeek)
err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity)
if err != nil {
resp.WriteUint16(0)
} else if pkt.ShopID == 7 && itemWeek >= 0 && itemWeek != week {
clearShopItemState(s, s.charID, uint32(ItemHash))
resp.WriteUint16(0)
} else {
resp.WriteUint16(charQuantity)
}
@@ -224,7 +199,6 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
}
func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
_, week := time.Now().ISOWeek()
// writing out to an editable shop enumeration
pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop)
if pkt.DataSize == 10 {
@@ -232,10 +206,10 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
_ = bf.ReadUint16() // unk, always 1 in examples
itemHash := bf.ReadUint32()
buyCount := bf.ReadUint32()
_, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity, week)
VALUES ($1,$2,$3,$4) ON CONFLICT (char_id, itemhash)
_, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity)
VALUES ($1,$2,$3) ON CONFLICT (char_id, itemhash)
DO UPDATE SET usedquantity = shop_item_state.usedquantity + $3
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount, week)
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount)
if err != nil {
s.logger.Fatal("Failed to update shop_item_state in db", zap.Error(err))
}
@@ -243,13 +217,6 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func clearShopItemState(s *Session, charId uint32, itemHash uint32) {
_, err := s.server.db.Exec(`DELETE FROM shop_item_state WHERE char_id=$1 AND itemhash=$2`, charId, itemHash)
if err != nil {
s.logger.Fatal("Failed to delete shop_item_state in db", zap.Error(err))
}
}
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
// returns number of times the gacha was played, will need persistent db stuff
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)

View File

@@ -2,6 +2,7 @@ package channelserver
import (
"fmt"
"strings"
"time"
"erupe-ce/common/byteframe"
@@ -121,7 +122,7 @@ func destructEmptyStages(s *Session) {
defer s.server.Unlock()
for _, stage := range s.server.stages {
// Destroy empty Quest/My series/Guild stages.
if stage.id[3:5] == "Qs" || stage.id[3:5] == "Ms" || stage.id[3:5] == "Gs" {
if stage.id[3:5] == "Qs" || stage.id[3:5] == "Ms" || stage.id[3:5] == "Gs" || stage.id[3:5] == "Ls" {
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
delete(s.server.stages, stage.id)
s.logger.Debug("Destructed stage", zap.String("stage.id", stage.id))
@@ -198,10 +199,12 @@ func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLockStage)
// TODO(Andoryuuta): What does this packet _actually_ do?
// I think this is supposed to mark a stage as no longer able to accept client reservations
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
if s.reservationStage != nil {
s.reservationStage.RLock()
defer s.reservationStage.RUnlock()
@@ -210,12 +213,12 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
}
s.server.Lock()
defer s.server.Unlock()
delete(s.server.stages, s.reservationStage.id)
}
destructEmptyStages(s)
}
func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysReserveStage)
if stage, exists := s.server.stages[pkt.StageID]; exists {
@@ -366,8 +369,7 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) {
continue
}
// Check for valid stage type
if sid[3:5] != "Qs" && sid[3:5] != "Ms" {
if !strings.Contains(stage.id, pkt.StagePrefix) {
continue
}

View File

@@ -3,7 +3,6 @@ package channelserver
import (
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
@@ -17,7 +16,8 @@ func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) {
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload
s.server.userBinaryPartsLock.Unlock()
err := s.server.db.QueryRow("SELECT type1 FROM user_binaries WHERE id=$1", s.charID)
var exists []byte
err := s.server.db.QueryRow("SELECT type2 FROM user_binaries WHERE id=$1", s.charID).Scan(&exists)
if err != nil {
s.server.db.Exec("INSERT INTO user_binaries (id) VALUES ($1)", s.charID)
}
@@ -39,25 +39,18 @@ func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) {
s.server.userBinaryPartsLock.RLock()
defer s.server.userBinaryPartsLock.RUnlock()
data, ok := s.server.userBinaryParts[userBinaryPartID{charID: pkt.CharID, index: pkt.BinaryType}]
resp := byteframe.NewByteFrame()
// If we can't get the real data, try to get it from the database.
if !ok {
var data []byte
rows, _ := s.server.db.Queryx(fmt.Sprintf("SELECT type%d FROM user_binaries WHERE id=$1", pkt.BinaryType), pkt.CharID)
for rows.Next() {
rows.Scan(&data)
resp.WriteBytes(data)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
return
}
err := s.server.db.QueryRow(fmt.Sprintf("SELECT type%d FROM user_binaries WHERE id=$1", pkt.BinaryType), pkt.CharID).Scan(&data)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
} else {
resp.WriteBytes(data)
doAckBufSucceed(s, pkt.AckHandle, data)
}
} else {
doAckBufSucceed(s, pkt.AckHandle, data)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgSysNotifyUserBinary(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -54,6 +54,8 @@ type Server struct {
sync.Mutex
Channels []*Server
ID uint16
IP string
Port uint16
logger *zap.Logger
db *sqlx.DB
erupeConfig *config.Config
@@ -196,8 +198,8 @@ func NewServer(config *Config) *Server {
}
// Start starts the server in a new goroutine.
func (s *Server) Start(port int) error {
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
func (s *Server) Start() error {
l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Port))
if err != nil {
return err
}
@@ -295,8 +297,11 @@ func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session)
}
}
func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session, ignoredChannel *Server) {
for _, c := range s.Channels {
if c == ignoredChannel {
continue
}
for _, session := range c.sessions {
if session == ignoredSession {
continue
@@ -357,7 +362,7 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
BroadcastType: BroadcastTypeServer,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}, nil)
}, nil, s)
}
func (s *Server) DiscordChannelSend(charName string, content string) {

View File

@@ -33,6 +33,7 @@ type Session struct {
stage *Stage
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
stagePass string // Temporary storage
prevGuildID uint32 // Stores the last GuildID used in InfoGuild
charID uint32
logKey []byte
sessionStart int64

View File

@@ -28,7 +28,7 @@ func encodeServerInfo(config *config.Config, s *Server) []byte {
panic(err)
}
if si.IP == "" {
si.IP = config.HostIP
si.IP = config.Host
}
bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4()))
bf.WriteUint16(16 + uint16(serverIdx))

View File

@@ -12,7 +12,7 @@ import (
func serverList(s *Server, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w,
`<?xml version="1.0"?><server_groups><group idx='0' nam='Erupe' ip='%s' port="%d"/></server_groups>`,
s.erupeConfig.HostIP,
s.erupeConfig.Host,
s.erupeConfig.Sign.Port,
)
}

View File

@@ -87,7 +87,7 @@ type character struct {
}
func (s *Server) getCharactersForUser(uid int) ([]character, error) {
characters := []character{}
characters := make([]character, 0)
err := s.db.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE user_id = $1 AND deleted = false", uid)
if err != nil {
return nil, err
@@ -126,7 +126,7 @@ func (s *Server) getFriendsForCharacters(chars []character) []members {
friendQuery += " OR id="
}
}
charFriends := []members{}
charFriends := make([]members, 0)
err = s.db.Select(&charFriends, friendQuery)
if err != nil {
continue
@@ -153,7 +153,7 @@ func (s *Server) getGuildmatesForCharacters(chars []character) []members {
if err != nil {
continue
}
charGuildmates := []members{}
charGuildmates := make([]members, 0)
err = s.db.Select(&charGuildmates, "SELECT character_id AS id, c.name FROM guild_characters gc JOIN characters c ON c.id = gc.character_id WHERE guild_id=$1 AND character_id!=$2", guildID, char.ID)
if err != nil {
continue

View File

@@ -47,7 +47,7 @@ func (s *Session) makeSignInResp(uid int) []byte {
bf.WriteUint32(0xFFFFFFFF) // login_token_number
bf.WriteBytes([]byte(token)) // login_token
bf.WriteUint32(uint32(time.Now().Unix())) // current time
ps.Uint8(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.HostIP, s.server.erupeConfig.Entrance.Port), false)
ps.Uint8(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.Host, s.server.erupeConfig.Entrance.Port), false)
lastPlayed := uint32(0)
for _, char := range chars {
@@ -128,7 +128,7 @@ func (s *Session) makeSignInResp(uid int) []byte {
bf.WriteUint32(0x0A5197DF)
mezfes := s.server.erupeConfig.DevModeOptions.MezFesEvent
alt := false
alt := s.server.erupeConfig.DevModeOptions.MezFesAlt
if mezfes {
// Start time
bf.WriteUint32(uint32(channelserver.Time_Current_Adjusted().Add(-5 * time.Minute).Unix()))
@@ -136,7 +136,7 @@ func (s *Session) makeSignInResp(uid int) []byte {
bf.WriteUint32(uint32(channelserver.Time_Current_Adjusted().Add(24 * time.Hour * 7).Unix()))
bf.WriteUint8(2) // Unk
bf.WriteUint32(20) // Single tickets
bf.WriteUint32(0) // Group tickets
bf.WriteUint32(10) // Group tickets
bf.WriteUint8(8) // Stalls open
bf.WriteUint8(0xA) // Unk
bf.WriteUint8(0x3) // Pachinko

View File

@@ -72,6 +72,15 @@
</div>
<ul class="article">
<li>
<div class="date">2022-08-02</div>
<div class="body">
<a
href="javascript:toggleModal('openLink',&quot;https://discord.com/channels/368424389416583169/929509970624532511/1003985850255818762&quot;);"
onclick="soundOk()">Server Update 9 Released!
</a>
</div>
</li>
<li>
<div class="date">2022-05-03</div>
<div class="body">
<a

View File

@@ -1,5 +1,6 @@
var __mhf_launcher = {};
var loginScreen = true;
var loggingIn = false;
var doingAuto = false;
var uids;
var selectedUid;
@@ -259,6 +260,11 @@ function switchPrompt() {
}
function doLogin(option) {
if (loggingIn) {
return;
} else {
loggingIn = true;
}
let username = document.getElementById('username').value;
let password = document.getElementById('password').value;
if (username == '') {
@@ -289,6 +295,7 @@ function checkAuth() {
setTimeout(checkAuth, 10);
return;
} else if (loginResult == 'AUTH_SUCCESS') {
loggingIn = false;
saveAccount();
addLog('Connected.', 'good');
if (doingAuto) {
@@ -300,6 +307,7 @@ function checkAuth() {
switchPrompt();
}
} else {
loggingIn = false;
addLog('Error logging in: '+loginResult+':'+window.external.getSignResult(), 'error');
}
document.getElementById('processing').style.display = 'none';
@@ -479,8 +487,26 @@ function doEval() {
function init() {
document.addEventListener('keypress', function(e) {
if (e.key == '~') {
switch (e.key) {
case '~':
document.getElementById('dev').style.display = 'block';
break;
case 'Enter':
if (loginScreen) {
doLogin()
} else {
soundLogin();launch()
}
break;
case ',':
if (!loginScreen) {
soundOk();charselScrollUp()
}
break;
case '.':
if (!loginScreen) {
soundOk();charselScrollDown()
}
}
});
let unselectable = document.getElementsByClassName('unselectable');