Merge branch 'refs/heads/main' into feature/hunting-tournament

This commit is contained in:
wish
2024-06-25 21:00:47 +10:00
47 changed files with 826 additions and 560 deletions

3
.gitignore vendored
View File

@@ -7,4 +7,5 @@ savedata/*/
*.exe *.exe
*.lnk *.lnk
*.bat *.bat
/docker/db-data /docker/db-data
screenshots/*

View File

@@ -1,29 +1,30 @@
# List of AUTHORS who contributed over time to the Erupe project # List of authors who contributed to Erupe
## Point of current development ## Point of current development
The project is currently developed under https://github.com/ZeruLight/Erupe The project is currently developed under https://github.com/ZeruLight/Erupe
## History of development ## History of development
Development of this project dates back to 2019, and was developed under various umbrellas over time: Development of this project dates back to 2019, and was developed under various umbrellas over time:
* Cappuccino (Fist/Ando/Ellie42) (The Erupe Developers), 2019-2020 (https://github.com/Ellie42/Erupe / https://github.com/ricochhet/Erupe-Legacy) (Still active closed source) * Cappuccino (Fist/Ando/Ellie42) ("The Erupe Developers"), 2019-2020 (https://github.com/Ellie42/Erupe / https://github.com/ricochhet/Erupe-Legacy) (Still active closed source)
* Einherjar Team, ????-2022 Feb (There is no git history for this period, this team's work was taken and used as a foundation for future repositories) * Einherjar Team, ????-2022 Feb (There is no git history for this period, this team's work was taken and used as a foundation for future repositories)
* Community Edition, 2022 (https://github.com/xl3lackout/Erupe) * Community Edition, 2022 (https://github.com/xl3lackout/Erupe)
* Zerulight, 2022-2023 (https://github.com/ZeruLight/Erupe) * sekaiwish Fork, 2022 (https://github.com/sekaiwish/Erupe)
* ZeruLight, 2022-2023 (https://github.com/ZeruLight/Erupe)
## Authorship of the code ## Authorship of the code
Authorship is assigned for each commit within the git history, which is stored in these git repos: Authorship is assigned for each commit within the git history, which is stored in these git repos:
* https://github.com/ZeruLight/Erupe * https://github.com/ZeruLight/Erupe
* https://github.com/Ellie42/Erupe * https://github.com/Ellie42/Erupe
* https://github.com/ricochhet/Erupe-Legacy * https://github.com/ricochhet/Erupe-Legacy
* https://github.com/xl3lackout/Erupe * https://github.com/xl3lackout/Erupe
Note there is a divergence between Ellie42s branch and xl3lackout where history has been lost.
Unfortunately, we have no detailed information on the history of the Erupe pre-2022 Note the divergence between Ellie42's branch and xl3lackout's where history has been lost.
if somebody can provide information, please contact us, so that we can make this history available.
Unfortunately, we have no detailed information on the history of Erupe before 2022.
If somebody can provide information, please contact us, so that we can make this history available.
## Exceptions with third-party libraries ## Exceptions with third-party libraries
The third-party libraries have their own way of addressing authorship and the authorship of commits importing/updating The third-party libraries have their own way of addressing authorship and the authorship of commits importing/updating
a third-party library reflects who did the importing instead of who wrote the code within the commit. a third-party library reflects who did the importing instead of who wrote the code within the commit.
The Authors of third-party libraries are not explicitly mentioned, and usually is possible to obtain from the files belonging to the third-party libraries. The authors of third-party libraries are not explicitly mentioned, and usually is possible to obtain from the files belonging to the third-party libraries.

View File

@@ -1,6 +1,6 @@
# Erupe # Erupe
## Client Compatiblity ## Client Compatibility
### Platforms ### Platforms
- PC - PC
- PlayStation 3 - PlayStation 3
@@ -34,15 +34,15 @@ If you want to modify or compile Erupe yourself, please read on.
## Docker ## Docker
Please see the readme in [docker/README.md](./docker/README.md). At the moment this is only really good for quick installs and checking out development not for production. Please see [docker/README.md](./docker/README.md). This is intended for quick installs and development, not for production.
## Schemas ## Schemas
We source control the following schemas: We source control the following schemas:
- Initialisation Schemas: These initialise the application database to a clean install from a specific version. - Initialization Schema: This initializes the application database to a specific version (9.1.0).
- Update Schemas: These are update files they should be ran in order of version to get to the latest schema. - Update Schemas: These are update files that should be ran on top of the initialization schema.
- Patch Schemas: These are for development and should be ran from the lastest available update schema or initial schema. These eventually get condensed into `Update Schemas` and then deleted when updated to a new version. - Patch Schemas: These are for development and should be run after running all initialization and update schema. These get condensed into `Update Schemas` and deleted when updated to a new release.
- Bundled Schemas: These are demo reference files to allow servers to be able to roll their own shops, distributions gachas and scenarios set ups. - Bundled Schemas: These are demo reference files to give servers standard set-ups.
Note: Patch schemas are subject to change! You should only be using them if you are following along with development. Note: Patch schemas are subject to change! You should only be using them if you are following along with development.

175
common/mhfitem/mhfitem.go Normal file
View File

@@ -0,0 +1,175 @@
package mhfitem
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/token"
_config "erupe-ce/config"
)
type MHFItem struct {
ItemID uint16
}
type MHFSigilEffect struct {
ID uint16
Level uint16
}
type MHFSigil struct {
Effects []MHFSigilEffect
Unk0 uint8
Unk1 uint8
Unk2 uint8
Unk3 uint8
}
type MHFEquipment struct {
WarehouseID uint32
ItemType uint8
Unk0 uint8
ItemID uint16
Level uint16
Decorations []MHFItem
Sigils []MHFSigil
Unk1 uint16
}
type MHFItemStack struct {
WarehouseID uint32
Item MHFItem
Quantity uint16
Unk0 uint32
}
func ReadWarehouseItem(bf *byteframe.ByteFrame) MHFItemStack {
var item MHFItemStack
item.WarehouseID = bf.ReadUint32()
if item.WarehouseID == 0 {
item.WarehouseID = token.RNG.Uint32()
}
item.Item.ItemID = bf.ReadUint16()
item.Quantity = bf.ReadUint16()
item.Unk0 = bf.ReadUint32()
return item
}
func DiffItemStacks(o []MHFItemStack, u []MHFItemStack) []MHFItemStack {
// o = old, u = update, f = final
var f []MHFItemStack
for _, uItem := range u {
exists := false
for i := range o {
if o[i].WarehouseID == uItem.WarehouseID {
exists = true
o[i].Quantity = uItem.Quantity
}
}
if !exists {
uItem.WarehouseID = token.RNG.Uint32()
f = append(f, uItem)
}
}
for _, oItem := range o {
if oItem.Quantity > 0 {
f = append(f, oItem)
}
}
return f
}
func (is MHFItemStack) ToBytes() []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint32(is.WarehouseID)
bf.WriteUint16(is.Item.ItemID)
bf.WriteUint16(is.Quantity)
bf.WriteUint32(is.Unk0)
return bf.Data()
}
func SerializeWarehouseItems(i []MHFItemStack) []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(i)))
bf.WriteUint16(0) // Unused
for _, j := range i {
bf.WriteBytes(j.ToBytes())
}
return bf.Data()
}
func ReadWarehouseEquipment(bf *byteframe.ByteFrame) MHFEquipment {
var equipment MHFEquipment
equipment.Decorations = make([]MHFItem, 3)
equipment.Sigils = make([]MHFSigil, 3)
for i := 0; i < 3; i++ {
equipment.Sigils[i].Effects = make([]MHFSigilEffect, 3)
}
equipment.WarehouseID = bf.ReadUint32()
if equipment.WarehouseID == 0 {
equipment.WarehouseID = token.RNG.Uint32()
}
equipment.ItemType = bf.ReadUint8()
equipment.Unk0 = bf.ReadUint8()
equipment.ItemID = bf.ReadUint16()
equipment.Level = bf.ReadUint16()
for i := 0; i < 3; i++ {
equipment.Decorations[i].ItemID = bf.ReadUint16()
}
if _config.ErupeConfig.RealClientMode >= _config.G1 {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
equipment.Sigils[i].Effects[j].ID = bf.ReadUint16()
}
for j := 0; j < 3; j++ {
equipment.Sigils[i].Effects[j].Level = bf.ReadUint16()
}
equipment.Sigils[i].Unk0 = bf.ReadUint8()
equipment.Sigils[i].Unk1 = bf.ReadUint8()
equipment.Sigils[i].Unk2 = bf.ReadUint8()
equipment.Sigils[i].Unk3 = bf.ReadUint8()
}
}
if _config.ErupeConfig.RealClientMode >= _config.Z1 {
equipment.Unk1 = bf.ReadUint16()
}
return equipment
}
func (e MHFEquipment) ToBytes() []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint32(e.WarehouseID)
bf.WriteUint8(e.ItemType)
bf.WriteUint8(e.Unk0)
bf.WriteUint16(e.ItemID)
bf.WriteUint16(e.Level)
for i := 0; i < 3; i++ {
bf.WriteUint16(e.Decorations[i].ItemID)
}
if _config.ErupeConfig.RealClientMode >= _config.G1 {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
bf.WriteUint16(e.Sigils[i].Effects[j].ID)
}
for j := 0; j < 3; j++ {
bf.WriteUint16(e.Sigils[i].Effects[j].Level)
}
bf.WriteUint8(e.Sigils[i].Unk0)
bf.WriteUint8(e.Sigils[i].Unk1)
bf.WriteUint8(e.Sigils[i].Unk2)
bf.WriteUint8(e.Sigils[i].Unk3)
}
}
if _config.ErupeConfig.RealClientMode >= _config.Z1 {
bf.WriteUint16(e.Unk1)
}
return bf.Data()
}
func SerializeWarehouseEquipment(i []MHFEquipment) []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(i)))
bf.WriteUint16(0) // Unused
for _, j := range i {
bf.WriteBytes(j.ToBytes())
}
return bf.Data()
}

View File

@@ -5,18 +5,19 @@ import (
"time" "time"
) )
var RNG = NewRNG()
// Generate returns an alphanumeric token of specified length // Generate returns an alphanumeric token of specified length
func Generate(length int) string { func Generate(length int) string {
rng := RNG()
var chars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") var chars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
b := make([]rune, length) b := make([]rune, length)
for i := range b { for i := range b {
b[i] = chars[rng.Intn(len(chars))] b[i] = chars[RNG.Intn(len(chars))]
} }
return string(b) return string(b)
} }
// RNG returns a new RNG generator // NewRNG returns a new NewRNG generator
func RNG() *rand.Rand { func NewRNG() *rand.Rand {
return rand.New(rand.NewSource(time.Now().UnixNano())) return rand.New(rand.NewSource(time.Now().UnixNano()))
} }

View File

@@ -9,7 +9,13 @@
], ],
"PatchServerManifest": "", "PatchServerManifest": "",
"PatchServerFile": "", "PatchServerFile": "",
"ScreenshotAPIURL": "", "Screenshots":{
"Enabled":true,
"Host":"127.0.0.1",
"Port":8080,
"OutputDir":"screenshots",
"UploadQuality":100
},
"DeleteOnSaveCorruption": false, "DeleteOnSaveCorruption": false,
"ClientMode": "ZZ", "ClientMode": "ZZ",
"QuestCacheExpiry": 300, "QuestCacheExpiry": 300,
@@ -46,7 +52,8 @@
} }
}, },
"GameplayOptions": { "GameplayOptions": {
"FeaturedWeapons": 1, "MinFeatureWeapons": 0,
"MaxFeatureWeapons": 1,
"MaximumNP": 100000, "MaximumNP": 100000,
"MaximumRP": 50000, "MaximumRP": 50000,
"MaximumFP": 120000, "MaximumFP": 120000,
@@ -188,8 +195,8 @@
"Enabled": true, "Enabled": true,
"Port": 53312 "Port": 53312
}, },
"SignV2": { "API": {
"Enabled": false, "Enabled": true,
"Port": 8080, "Port": 8080,
"PatchServer": "", "PatchServer": "",
"Banners": [], "Banners": [],

View File

@@ -75,7 +75,6 @@ type Config struct {
LoginNotices []string // MHFML string of the login notices displayed LoginNotices []string // MHFML string of the login notices displayed
PatchServerManifest string // Manifest patch server override PatchServerManifest string // Manifest patch server override
PatchServerFile string // File patch server override PatchServerFile string // File patch server override
ScreenshotAPIURL string // Destination for screenshots uploaded to BBS
DeleteOnSaveCorruption bool // Attempts to save corrupted data will flag the save for deletion DeleteOnSaveCorruption bool // Attempts to save corrupted data will flag the save for deletion
ClientMode string ClientMode string
RealClientMode Mode RealClientMode Mode
@@ -87,16 +86,18 @@ type Config struct {
EarthID int32 EarthID int32
EarthMonsters []int32 EarthMonsters []int32
SaveDumps SaveDumpOptions SaveDumps SaveDumpOptions
DebugOptions DebugOptions Screenshots ScreenshotsOptions
GameplayOptions GameplayOptions
Discord Discord DebugOptions DebugOptions
Commands []Command GameplayOptions GameplayOptions
Courses []Course Discord Discord
Database Database Commands []Command
Sign Sign Courses []Course
SignV2 SignV2 Database Database
Channel Channel Sign Sign
Entrance Entrance API API
Channel Channel
Entrance Entrance
} }
type SaveDumpOptions struct { type SaveDumpOptions struct {
@@ -105,6 +106,14 @@ type SaveDumpOptions struct {
OutputDir string OutputDir string
} }
type ScreenshotsOptions struct {
Enabled bool
Host string // Destination for screenshots uploaded to BBS
Port uint32 // Port for screenshots API
OutputDir string
UploadQuality int //Determines the upload quality to the server
}
// DebugOptions holds various debug/temporary options for use while developing Erupe. // DebugOptions holds various debug/temporary options for use while developing Erupe.
type DebugOptions struct { type DebugOptions struct {
CleanDB bool // Automatically wipes the DB on server reset. CleanDB bool // Automatically wipes the DB on server reset.
@@ -132,7 +141,8 @@ type CapLinkOptions struct {
// GameplayOptions has various gameplay modifiers // GameplayOptions has various gameplay modifiers
type GameplayOptions struct { type GameplayOptions struct {
FeaturedWeapons int // Number of Active Feature weapons to generate daily MinFeatureWeapons int // Minimum number of Active Feature weapons to generate daily
MaxFeatureWeapons int // Maximum number of Active Feature weapons to generate daily
MaximumNP int // Maximum number of NP held by a player MaximumNP int // Maximum number of NP held by a player
MaximumRP uint16 // Maximum number of RP held by a player MaximumRP uint16 // Maximum number of RP held by a player
MaximumFP uint32 // Maximum number of FP held by a player MaximumFP uint32 // Maximum number of FP held by a player
@@ -227,29 +237,29 @@ type Sign struct {
Port int Port int
} }
// SignV2 holds the new sign server config // API holds server config
type SignV2 struct { type API struct {
Enabled bool Enabled bool
Port int Port int
PatchServer string PatchServer string
Banners []SignV2Banner Banners []APISignBanner
Messages []SignV2Message Messages []APISignMessage
Links []SignV2Link Links []APISignLink
} }
type SignV2Banner struct { type APISignBanner struct {
Src string `json:"src"` // Displayed image URL Src string `json:"src"` // Displayed image URL
Link string `json:"link"` // Link accessed on click Link string `json:"link"` // Link accessed on click
} }
type SignV2Message struct { type APISignMessage struct {
Message string `json:"message"` // Displayed message Message string `json:"message"` // Displayed message
Date int64 `json:"date"` // Displayed date Date int64 `json:"date"` // Displayed date
Kind int `json:"kind"` // 0 for 'Default', 1 for 'New' Kind int `json:"kind"` // 0 for 'Default', 1 for 'New'
Link string `json:"link"` // Link accessed on click Link string `json:"link"` // Link accessed on click
} }
type SignV2Link struct { type APISignLink struct {
Name string `json:"name"` // Displayed name Name string `json:"name"` // Displayed name
Icon string `json:"icon"` // Displayed icon. It will be cast as a monochrome color as long as it is transparent. Icon string `json:"icon"` // Displayed icon. It will be cast as a monochrome color as long as it is transparent.
Link string `json:"link"` // Link accessed on click Link string `json:"link"` // Link accessed on click
@@ -351,6 +361,10 @@ func LoadConfig() (*Config, error) {
c.RealClientMode = ZZ c.RealClientMode = ZZ
} }
if c.GameplayOptions.MinFeatureWeapons > c.GameplayOptions.MaxFeatureWeapons {
c.GameplayOptions.MinFeatureWeapons = c.GameplayOptions.MaxFeatureWeapons
}
return c, nil return c, nil
} }

View File

@@ -42,8 +42,8 @@ services:
build: build:
context: ../ context: ../
volumes: volumes:
- ./config.json:/app/erupe/config.json - ../config.json:/app/erupe/config.json
- ./bin:/app/erupe/bin - ../bin:/app/erupe/bin
- ./savedata:/app/erupe/savedata - ./savedata:/app/erupe/savedata
ports: ports:
# (Make sure these match config.json) # (Make sure these match config.json)

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
set -e set -e
echo "INIT!" echo "INIT!"
pg_restore --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --verbose /schemas/initialisation-schema/9.1-init.sql pg_restore --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --verbose /schemas/init.sql

22
main.go
View File

@@ -10,11 +10,11 @@ import (
"syscall" "syscall"
"time" "time"
"erupe-ce/server/api"
"erupe-ce/server/channelserver" "erupe-ce/server/channelserver"
"erupe-ce/server/discordbot" "erupe-ce/server/discordbot"
"erupe-ce/server/entranceserver" "erupe-ce/server/entranceserver"
"erupe-ce/server/signserver" "erupe-ce/server/signserver"
"erupe-ce/server/signv2server"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
_ "github.com/lib/pq" _ "github.com/lib/pq"
@@ -181,21 +181,21 @@ func main() {
} }
// New Sign server // New Sign server
var newSignServer *signv2server.Server var ApiServer *api.APIServer
if config.SignV2.Enabled { if config.API.Enabled {
newSignServer = signv2server.NewServer( ApiServer = api.NewAPIServer(
&signv2server.Config{ &api.Config{
Logger: logger.Named("sign"), Logger: logger.Named("sign"),
ErupeConfig: _config.ErupeConfig, ErupeConfig: _config.ErupeConfig,
DB: db, DB: db,
}) })
err = newSignServer.Start() err = ApiServer.Start()
if err != nil { if err != nil {
preventClose(fmt.Sprintf("SignV2: Failed to start, %s", err.Error())) preventClose(fmt.Sprintf("API: Failed to start, %s", err.Error()))
} }
logger.Info("SignV2: Started successfully") logger.Info("API: Started successfully")
} else { } else {
logger.Info("SignV2: Disabled") logger.Info("API: Disabled")
} }
var channels []*channelserver.Server var channels []*channelserver.Server
@@ -273,8 +273,8 @@ func main() {
signServer.Shutdown() signServer.Shutdown()
} }
if config.SignV2.Enabled { if config.API.Enabled {
newSignServer.Shutdown() ApiServer.Shutdown()
} }
if config.Entrance.Enabled { if config.Entrance.Enabled {

View File

@@ -11,7 +11,7 @@ import (
// MsgMhfEnumerateWarehouse represents the MSG_MHF_ENUMERATE_WAREHOUSE // MsgMhfEnumerateWarehouse represents the MSG_MHF_ENUMERATE_WAREHOUSE
type MsgMhfEnumerateWarehouse struct { type MsgMhfEnumerateWarehouse struct {
AckHandle uint32 AckHandle uint32
BoxType string BoxType uint8
BoxIndex uint8 BoxIndex uint8
} }
@@ -23,15 +23,10 @@ func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfEnumerateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfEnumerateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
boxType := bf.ReadUint8() m.BoxType = bf.ReadUint8()
switch boxType {
case 0:
m.BoxType = "item"
case 1:
m.BoxType = "equip"
}
m.BoxIndex = bf.ReadUint8() m.BoxIndex = bf.ReadUint8()
_ = bf.ReadUint16() bf.ReadUint8() // Zeroed
bf.ReadUint8() // Zeroed
return nil return nil
} }

View File

@@ -13,7 +13,7 @@ import (
type MsgMhfOperateWarehouse struct { type MsgMhfOperateWarehouse struct {
AckHandle uint32 AckHandle uint32
Operation uint8 Operation uint8
BoxType string BoxType uint8
BoxIndex uint8 BoxIndex uint8
Name string Name string
} }
@@ -27,17 +27,13 @@ func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID {
func (m *MsgMhfOperateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfOperateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Operation = bf.ReadUint8() m.Operation = bf.ReadUint8()
boxType := bf.ReadUint8() m.BoxType = bf.ReadUint8()
switch boxType {
case 0:
m.BoxType = "item"
case 1:
m.BoxType = "equip"
}
m.BoxIndex = bf.ReadUint8() m.BoxIndex = bf.ReadUint8()
_ = bf.ReadUint8() // lenName lenName := bf.ReadUint8()
_ = bf.ReadUint16() // Unk bf.ReadUint16() // Zeroed
m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) if lenName > 0 {
m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
}
return nil return nil
} }

View File

@@ -32,7 +32,9 @@ func (m *MsgMhfStampcardStamp) Opcode() network.PacketID {
func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.HR = bf.ReadUint16() m.HR = bf.ReadUint16()
m.GR = bf.ReadUint16() if _config.ErupeConfig.RealClientMode >= _config.G1 {
m.GR = bf.ReadUint16()
}
m.Stamps = bf.ReadUint16() m.Stamps = bf.ReadUint16()
bf.ReadUint16() // Zeroed bf.ReadUint16() // Zeroed
if _config.ErupeConfig.RealClientMode > _config.Z1 { if _config.ErupeConfig.RealClientMode > _config.Z1 {

View File

@@ -31,8 +31,8 @@ func (m *MsgMhfUpdateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.EntryCount = bf.ReadUint16() m.EntryCount = bf.ReadUint16()
bf.ReadUint16() // Zeroed bf.ReadUint16() // Zeroed
var temp Goocoo
for i := 0; i < int(m.EntryCount); i++ { for i := 0; i < int(m.EntryCount); i++ {
var temp Goocoo
temp.Index = bf.ReadUint32() temp.Index = bf.ReadUint32()
for j := 0; j < 22; j++ { for j := 0; j < 22; j++ {
temp.Data1 = append(temp.Data1, bf.ReadInt16()) temp.Data1 = append(temp.Data1, bf.ReadInt16())

View File

@@ -2,24 +2,18 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/common/mhfitem"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network" "erupe-ce/network"
"erupe-ce/network/clientctx" "erupe-ce/network/clientctx"
) )
type Item struct {
Unk0 uint32
ItemID uint16
Amount uint16
Unk1 uint32
}
// MsgMhfUpdateGuildItem represents the MSG_MHF_UPDATE_GUILD_ITEM // MsgMhfUpdateGuildItem represents the MSG_MHF_UPDATE_GUILD_ITEM
type MsgMhfUpdateGuildItem struct { type MsgMhfUpdateGuildItem struct {
AckHandle uint32 AckHandle uint32
GuildID uint32 GuildID uint32
Items []Item UpdatedItems []mhfitem.MHFItemStack
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -31,18 +25,12 @@ func (m *MsgMhfUpdateGuildItem) Opcode() network.PacketID {
func (m *MsgMhfUpdateGuildItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfUpdateGuildItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.GuildID = bf.ReadUint32() m.GuildID = bf.ReadUint32()
itemCount := int(bf.ReadUint16()) changes := int(bf.ReadUint16())
bf.ReadUint8() // Zeroed bf.ReadUint8() // Zeroed
bf.ReadUint8() // Zeroed bf.ReadUint8() // Zeroed
m.Items = make([]Item, itemCount) for i := 0; i < changes; i++ {
m.UpdatedItems = append(m.UpdatedItems, mhfitem.ReadWarehouseItem(bf))
for i := 0; i < itemCount; i++ {
m.Items[i].Unk0 = bf.ReadUint32()
m.Items[i].ItemID = bf.ReadUint16()
m.Items[i].Amount = bf.ReadUint16()
m.Items[i].Unk1 = bf.ReadUint32()
} }
return nil return nil
} }

View File

@@ -2,6 +2,7 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/common/mhfitem"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network" "erupe-ce/network"
@@ -10,8 +11,8 @@ import (
// MsgMhfUpdateUnionItem represents the MSG_MHF_UPDATE_UNION_ITEM // MsgMhfUpdateUnionItem represents the MSG_MHF_UPDATE_UNION_ITEM
type MsgMhfUpdateUnionItem struct { type MsgMhfUpdateUnionItem struct {
AckHandle uint32 AckHandle uint32
Items []Item UpdatedItems []mhfitem.MHFItemStack
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -22,18 +23,12 @@ func (m *MsgMhfUpdateUnionItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfUpdateUnionItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfUpdateUnionItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
itemCount := int(bf.ReadUint16()) changes := int(bf.ReadUint16())
bf.ReadUint8() // Zeroed bf.ReadUint8() // Zeroed
bf.ReadUint8() // Zeroed bf.ReadUint8() // Zeroed
m.Items = make([]Item, itemCount) for i := 0; i < changes; i++ {
m.UpdatedItems = append(m.UpdatedItems, mhfitem.ReadWarehouseItem(bf))
for i := 0; i < itemCount; i++ {
m.Items[i].Unk0 = bf.ReadUint32()
m.Items[i].ItemID = bf.ReadUint16()
m.Items[i].Amount = bf.ReadUint16()
m.Items[i].Unk1 = bf.ReadUint32()
} }
return nil return nil
} }

View File

@@ -3,25 +3,18 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/common/mhfitem"
"erupe-ce/network" "erupe-ce/network"
"erupe-ce/network/clientctx" "erupe-ce/network/clientctx"
) )
type WarehouseStack struct {
ID uint32
Index uint16
EquipType uint16
ItemID uint16
Quantity uint16
Data []byte
}
// MsgMhfUpdateWarehouse represents the MSG_MHF_UPDATE_WAREHOUSE // MsgMhfUpdateWarehouse represents the MSG_MHF_UPDATE_WAREHOUSE
type MsgMhfUpdateWarehouse struct { type MsgMhfUpdateWarehouse struct {
AckHandle uint32 AckHandle uint32
BoxType string BoxType uint8
BoxIndex uint8 BoxIndex uint8
Updates []WarehouseStack UpdatedItems []mhfitem.MHFItemStack
UpdatedEquipment []mhfitem.MHFEquipment
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -32,35 +25,19 @@ func (m *MsgMhfUpdateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfUpdateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfUpdateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
boxType := bf.ReadUint8() m.BoxType = bf.ReadUint8()
switch boxType {
case 0:
m.BoxType = "item"
case 1:
m.BoxType = "equip"
}
m.BoxIndex = bf.ReadUint8() m.BoxIndex = bf.ReadUint8()
changes := int(bf.ReadUint16()) changes := int(bf.ReadUint16())
var stackUpdate WarehouseStack bf.ReadUint8() // Zeroed
bf.ReadUint8() // Zeroed
for i := 0; i < changes; i++ { for i := 0; i < changes; i++ {
switch boxType { switch m.BoxType {
case 0: case 0:
stackUpdate.ID = bf.ReadUint32() m.UpdatedItems = append(m.UpdatedItems, mhfitem.ReadWarehouseItem(bf))
stackUpdate.Index = bf.ReadUint16()
stackUpdate.ItemID = bf.ReadUint16()
stackUpdate.Quantity = bf.ReadUint16()
_ = bf.ReadUint16() // Unk
m.Updates = append(m.Updates, stackUpdate)
case 1: case 1:
stackUpdate.ID = bf.ReadUint32() m.UpdatedEquipment = append(m.UpdatedEquipment, mhfitem.ReadWarehouseEquipment(bf))
stackUpdate.Index = bf.ReadUint16()
stackUpdate.EquipType = bf.ReadUint16()
stackUpdate.ItemID = bf.ReadUint16()
stackUpdate.Data = bf.ReadBytes(56)
m.Updates = append(m.Updates, stackUpdate)
} }
} }
_ = bf.ReadUint16()
return nil return nil
} }

View File

@@ -2,4 +2,12 @@ BEGIN;
ALTER TABLE users ADD COLUMN IF NOT EXISTS psn_id TEXT; ALTER TABLE users ADD COLUMN IF NOT EXISTS psn_id TEXT;
ALTER TABLE public.sign_sessions ADD COLUMN id SERIAL;
ALTER TABLE public.sign_sessions ADD CONSTRAINT sign_sessions_pkey PRIMARY KEY (id);
ALTER TABLE public.sign_sessions ALTER COLUMN user_id DROP NOT NULL;
ALTER TABLE public.sign_sessions ADD COLUMN psn_id TEXT;
END; END;

View File

@@ -0,0 +1,6 @@
BEGIN;
UPDATE guilds SET item_box=NULL;
UPDATE users SET item_box=NULL;
END;

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE IF EXISTS public.characters RENAME hrp TO hr;
END;

View File

@@ -0,0 +1,6 @@
BEGIN;
ALTER TABLE guilds ADD COLUMN IF NOT EXISTS room_rp INT DEFAULT 0;
ALTER TABLE guilds ADD COLUMN IF NOT EXISTS room_expiry TIMESTAMP WITHOUT TIME ZONE;
END;

View File

@@ -0,0 +1,6 @@
BEGIN;
ALTER TABLE IF EXISTS public.stamps RENAME hl_next TO hl_checked;
ALTER TABLE IF EXISTS public.stamps RENAME ex_next TO ex_checked;
END;

View File

@@ -1,11 +0,0 @@
BEGIN;
ALTER TABLE public.sign_sessions ADD COLUMN id SERIAL;
ALTER TABLE public.sign_sessions ADD CONSTRAINT sign_sessions_pkey PRIMARY KEY (id);
ALTER TABLE public.sign_sessions ALTER COLUMN user_id DROP NOT NULL;
ALTER TABLE public.sign_sessions ADD COLUMN psn_id TEXT;
END;

View File

@@ -1,8 +1,8 @@
package signv2server package api
import ( import (
"context" "context"
"erupe-ce/config" _config "erupe-ce/config"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
@@ -21,8 +21,8 @@ type Config struct {
ErupeConfig *_config.Config ErupeConfig *_config.Config
} }
// Server is the MHF custom launcher sign server. // APIServer is Erupes Standard API interface
type Server struct { type APIServer struct {
sync.Mutex sync.Mutex
logger *zap.Logger logger *zap.Logger
erupeConfig *_config.Config erupeConfig *_config.Config
@@ -31,9 +31,9 @@ type Server struct {
isShuttingDown bool isShuttingDown bool
} }
// NewServer creates a new Server type. // NewAPIServer creates a new Server type.
func NewServer(config *Config) *Server { func NewAPIServer(config *Config) *APIServer {
s := &Server{ s := &APIServer{
logger: config.Logger, logger: config.Logger,
erupeConfig: config.ErupeConfig, erupeConfig: config.ErupeConfig,
db: config.DB, db: config.DB,
@@ -43,7 +43,7 @@ func NewServer(config *Config) *Server {
} }
// Start starts the server in a new goroutine. // Start starts the server in a new goroutine.
func (s *Server) Start() error { func (s *APIServer) Start() error {
// Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth. // Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth.
r := mux.NewRouter() r := mux.NewRouter()
r.HandleFunc("/launcher", s.Launcher) r.HandleFunc("/launcher", s.Launcher)
@@ -52,9 +52,11 @@ func (s *Server) Start() error {
r.HandleFunc("/character/create", s.CreateCharacter) r.HandleFunc("/character/create", s.CreateCharacter)
r.HandleFunc("/character/delete", s.DeleteCharacter) r.HandleFunc("/character/delete", s.DeleteCharacter)
r.HandleFunc("/character/export", s.ExportSave) r.HandleFunc("/character/export", s.ExportSave)
r.HandleFunc("/api/ss/bbs/upload.php", s.ScreenShot)
r.HandleFunc("/api/ss/bbs/{id}", s.ScreenShotGet)
handler := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))(r) handler := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))(r)
s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, handler) s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, handler)
s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.SignV2.Port) s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.API.Port)
serveError := make(chan error, 1) serveError := make(chan error, 1)
go func() { go func() {
@@ -74,7 +76,7 @@ func (s *Server) Start() error {
} }
// Shutdown exits the server gracefully. // Shutdown exits the server gracefully.
func (s *Server) Shutdown() { func (s *APIServer) Shutdown() {
s.logger.Debug("Shutting down") s.logger.Debug("Shutting down")
s.Lock() s.Lock()

View File

@@ -1,4 +1,4 @@
package signv2server package api
import ( import (
"context" "context"
@@ -10,7 +10,7 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
func (s *Server) createNewUser(ctx context.Context, username string, password string) (uint32, uint32, error) { func (s *APIServer) createNewUser(ctx context.Context, username string, password string) (uint32, uint32, error) {
// Create salted hash of user password // Create salted hash of user password
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil { if err != nil {
@@ -32,7 +32,7 @@ func (s *Server) createNewUser(ctx context.Context, username string, password st
return id, rights, err return id, rights, err
} }
func (s *Server) createLoginToken(ctx context.Context, uid uint32) (uint32, string, error) { func (s *APIServer) createLoginToken(ctx context.Context, uid uint32) (uint32, string, error) {
loginToken := token.Generate(16) loginToken := token.Generate(16)
var tid uint32 var tid uint32
err := s.db.QueryRowContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id", uid, loginToken).Scan(&tid) err := s.db.QueryRowContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id", uid, loginToken).Scan(&tid)
@@ -42,7 +42,7 @@ func (s *Server) createLoginToken(ctx context.Context, uid uint32) (uint32, stri
return tid, loginToken, nil return tid, loginToken, nil
} }
func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, error) { func (s *APIServer) userIDFromToken(ctx context.Context, token string) (uint32, error) {
var userID uint32 var userID uint32
err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID) err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@@ -53,10 +53,10 @@ func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, err
return userID, nil return userID, nil
} }
func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character, error) { func (s *APIServer) createCharacter(ctx context.Context, userID uint32) (Character, error) {
var character Character var character Character
err := s.db.GetContext(ctx, &character, err := s.db.GetContext(ctx, &character,
"SELECT id, name, is_female, weapon_type, hrp, gr, last_login FROM characters WHERE is_new_character = true AND user_id = $1 LIMIT 1", "SELECT id, name, is_female, weapon_type, hr, gr, last_login FROM characters WHERE is_new_character = true AND user_id = $1 LIMIT 1",
userID, userID,
) )
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@@ -68,17 +68,17 @@ func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character,
err = s.db.GetContext(ctx, &character, ` err = s.db.GetContext(ctx, &character, `
INSERT INTO characters ( INSERT INTO characters (
user_id, is_female, is_new_character, name, unk_desc_string, user_id, is_female, is_new_character, name, unk_desc_string,
hrp, gr, weapon_type, last_login hr, gr, weapon_type, last_login
) )
VALUES ($1, false, true, '', '', 0, 0, 0, $2) VALUES ($1, false, true, '', '', 0, 0, 0, $2)
RETURNING id, name, is_female, weapon_type, hrp, gr, last_login`, RETURNING id, name, is_female, weapon_type, hr, gr, last_login`,
userID, uint32(time.Now().Unix()), userID, uint32(time.Now().Unix()),
) )
} }
return character, err return character, err
} }
func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint32) error { func (s *APIServer) deleteCharacter(ctx context.Context, userID uint32, charID uint32) error {
var isNew bool var isNew bool
err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew) err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew)
if err != nil { if err != nil {
@@ -92,11 +92,11 @@ func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint
return err return err
} }
func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Character, error) { func (s *APIServer) getCharactersForUser(ctx context.Context, uid uint32) ([]Character, error) {
var characters []Character var characters []Character
err := s.db.SelectContext( err := s.db.SelectContext(
ctx, &characters, ` ctx, &characters, `
SELECT id, name, is_female, weapon_type, hrp, gr, last_login SELECT id, name, is_female, weapon_type, hr, gr, last_login
FROM characters FROM characters
WHERE user_id = $1 AND deleted = false AND is_new_character = false ORDER BY id ASC`, WHERE user_id = $1 AND deleted = false AND is_new_character = false ORDER BY id ASC`,
uid, uid,
@@ -107,7 +107,7 @@ func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Charac
return characters, nil return characters, nil
} }
func (s *Server) getReturnExpiry(uid uint32) time.Time { func (s *APIServer) getReturnExpiry(uid uint32) time.Time {
var returnExpiry, lastLogin time.Time var returnExpiry, lastLogin time.Time
s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid) s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid)
if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) { if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) {
@@ -124,7 +124,7 @@ func (s *Server) getReturnExpiry(uid uint32) time.Time {
return returnExpiry return returnExpiry
} }
func (s *Server) exportSave(ctx context.Context, uid uint32, cid uint32) (map[string]interface{}, error) { func (s *APIServer) exportSave(ctx context.Context, uid uint32, cid uint32) (map[string]interface{}, error) {
row := s.db.QueryRowxContext(ctx, "SELECT * FROM characters WHERE id=$1 AND user_id=$2", cid, uid) row := s.db.QueryRowxContext(ctx, "SELECT * FROM characters WHERE id=$1 AND user_id=$2", cid, uid)
result := make(map[string]interface{}) result := make(map[string]interface{})
err := row.MapScan(result) err := row.MapScan(result)

View File

@@ -1,15 +1,24 @@
package signv2server package api
import ( import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"encoding/xml"
"errors" "errors"
_config "erupe-ce/config" _config "erupe-ce/config"
"erupe-ce/server/channelserver" "erupe-ce/server/channelserver"
"fmt"
"image"
"image/jpeg"
"io"
"net/http" "net/http"
"os"
"path/filepath"
"regexp"
"strings" "strings"
"time" "time"
"github.com/gorilla/mux"
"github.com/lib/pq" "github.com/lib/pq"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@@ -21,9 +30,9 @@ const (
) )
type LauncherResponse struct { type LauncherResponse struct {
Banners []_config.SignV2Banner `json:"banners"` Banners []_config.APISignBanner `json:"banners"`
Messages []_config.SignV2Message `json:"messages"` Messages []_config.APISignMessage `json:"messages"`
Links []_config.SignV2Link `json:"links"` Links []_config.APISignLink `json:"links"`
} }
type User struct { type User struct {
@@ -37,7 +46,7 @@ type Character struct {
Name string `json:"name"` Name string `json:"name"`
IsFemale bool `json:"isFemale" db:"is_female"` IsFemale bool `json:"isFemale" db:"is_female"`
Weapon uint32 `json:"weapon" db:"weapon_type"` Weapon uint32 `json:"weapon" db:"weapon_type"`
HR uint32 `json:"hr" db:"hrp"` HR uint32 `json:"hr" db:"hr"`
GR uint32 `json:"gr"` GR uint32 `json:"gr"`
LastLogin int32 `json:"lastLogin" db:"last_login"` LastLogin int32 `json:"lastLogin" db:"last_login"`
} }
@@ -66,7 +75,7 @@ type ExportData struct {
Character map[string]interface{} `json:"character"` Character map[string]interface{} `json:"character"`
} }
func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint32, userToken string, characters []Character) AuthData { func (s *APIServer) newAuthData(userID uint32, userRights uint32, userTokenID uint32, userToken string, characters []Character) AuthData {
resp := AuthData{ resp := AuthData{
CurrentTS: uint32(channelserver.TimeAdjusted().Unix()), CurrentTS: uint32(channelserver.TimeAdjusted().Unix()),
ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()), ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()),
@@ -77,7 +86,7 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
Token: userToken, Token: userToken,
}, },
Characters: characters, Characters: characters,
PatchServer: s.erupeConfig.SignV2.PatchServer, PatchServer: s.erupeConfig.API.PatchServer,
Notices: []string{}, Notices: []string{},
} }
if s.erupeConfig.DebugOptions.MaxLauncherHR { if s.erupeConfig.DebugOptions.MaxLauncherHR {
@@ -103,16 +112,16 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
return resp return resp
} }
func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) { func (s *APIServer) Launcher(w http.ResponseWriter, r *http.Request) {
var respData LauncherResponse var respData LauncherResponse
respData.Banners = s.erupeConfig.SignV2.Banners respData.Banners = s.erupeConfig.API.Banners
respData.Messages = s.erupeConfig.SignV2.Messages respData.Messages = s.erupeConfig.API.Messages
respData.Links = s.erupeConfig.SignV2.Links respData.Links = s.erupeConfig.API.Links
w.Header().Add("Content-Type", "application/json") w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(respData) json.NewEncoder(w).Encode(respData)
} }
func (s *Server) Login(w http.ResponseWriter, r *http.Request) { func (s *APIServer) Login(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var reqData struct { var reqData struct {
Username string `json:"username"` Username string `json:"username"`
@@ -164,7 +173,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(respData) json.NewEncoder(w).Encode(respData)
} }
func (s *Server) Register(w http.ResponseWriter, r *http.Request) { func (s *APIServer) Register(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var reqData struct { var reqData struct {
Username string `json:"username"` Username string `json:"username"`
@@ -204,7 +213,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(respData) json.NewEncoder(w).Encode(respData)
} }
func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) { func (s *APIServer) CreateCharacter(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var reqData struct { var reqData struct {
Token string `json:"token"` Token string `json:"token"`
@@ -233,7 +242,7 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(character) json.NewEncoder(w).Encode(character)
} }
func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { func (s *APIServer) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var reqData struct { var reqData struct {
Token string `json:"token"` Token string `json:"token"`
@@ -258,7 +267,7 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(struct{}{}) json.NewEncoder(w).Encode(struct{}{})
} }
func (s *Server) ExportSave(w http.ResponseWriter, r *http.Request) { func (s *APIServer) ExportSave(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var reqData struct { var reqData struct {
Token string `json:"token"` Token string `json:"token"`
@@ -286,3 +295,118 @@ func (s *Server) ExportSave(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json") w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(save) json.NewEncoder(w).Encode(save)
} }
func (s *APIServer) ScreenShotGet(w http.ResponseWriter, r *http.Request) {
// Get the 'id' parameter from the URL
token := mux.Vars(r)["id"]
var tokenPattern = regexp.MustCompile(`[A-Za-z0-9]+`)
if !tokenPattern.MatchString(token) || token == "" {
http.Error(w, "Not Valid Token", http.StatusBadRequest)
}
// Open the image file
safePath := s.erupeConfig.Screenshots.OutputDir
path := filepath.Join(safePath, fmt.Sprintf("%s.jpg", token))
result, err := verifyPath(path, safePath)
if err != nil {
fmt.Println("Error " + err.Error())
} else {
fmt.Println("Canonical: " + result)
file, err := os.Open(result)
if err != nil {
http.Error(w, "Image not found", http.StatusNotFound)
return
}
defer file.Close()
// Set content type header to image/jpeg
w.Header().Set("Content-Type", "image/jpeg")
// Copy the image content to the response writer
if _, err := io.Copy(w, file); err != nil {
http.Error(w, "Unable to send image", http.StatusInternalServerError)
return
}
}
}
func (s *APIServer) ScreenShot(w http.ResponseWriter, r *http.Request) {
// Create a struct representing the XML result
type Result struct {
XMLName xml.Name `xml:"result"`
Code string `xml:"code"`
}
// Set the Content-Type header to specify that the response is in XML format
w.Header().Set("Content-Type", "text/xml")
result := Result{Code: "200"}
if !s.erupeConfig.Screenshots.Enabled {
result = Result{Code: "400"}
} else {
if r.Method != http.MethodPost {
result = Result{Code: "405"}
}
// Get File from Request
file, _, err := r.FormFile("img")
if err != nil {
result = Result{Code: "400"}
}
var tokenPattern = regexp.MustCompile(`[A-Za-z0-9]+`)
token := r.FormValue("token")
if !tokenPattern.MatchString(token) || token == "" {
result = Result{Code: "401"}
}
// Validate file
img, _, err := image.Decode(file)
if err != nil {
result = Result{Code: "400"}
}
safePath := s.erupeConfig.Screenshots.OutputDir
path := filepath.Join(safePath, fmt.Sprintf("%s.jpg", token))
verified, err := verifyPath(path, safePath)
if err != nil {
result = Result{Code: "500"}
} else {
_, err = os.Stat(safePath)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(safePath, os.ModePerm)
if err != nil {
s.logger.Error("Error writing screenshot, could not create folder")
result = Result{Code: "500"}
}
} else {
s.logger.Error("Error writing screenshot")
result = Result{Code: "500"}
}
}
// Create or open the output file
outputFile, err := os.Create(verified)
if err != nil {
result = Result{Code: "500"}
}
defer outputFile.Close()
// Encode the image and write it to the file
err = jpeg.Encode(outputFile, img, &jpeg.Options{Quality: s.erupeConfig.Screenshots.UploadQuality})
if err != nil {
s.logger.Error("Error writing screenshot, could not write file", zap.Error(err))
result = Result{Code: "500"}
}
}
}
// Marshal the struct into XML
xmlData, err := xml.Marshal(result)
if err != nil {
http.Error(w, "Unable to marshal XML", http.StatusInternalServerError)
return
}
// Write the XML response with a 200 status code
w.WriteHeader(http.StatusOK)
w.Write(xmlData)
}

37
server/api/utils.go Normal file
View File

@@ -0,0 +1,37 @@
package api
import (
"errors"
"fmt"
"path/filepath"
)
func inTrustedRoot(path string, trustedRoot string) error {
for path != "/" {
path = filepath.Dir(path)
if path == trustedRoot {
return nil
}
}
return errors.New("path is outside of trusted root")
}
func verifyPath(path string, trustedRoot string) (string, error) {
c := filepath.Clean(path)
fmt.Println("Cleaned path: " + c)
r, err := filepath.EvalSymlinks(c)
if err != nil {
fmt.Println("Error " + err.Error())
return c, errors.New("Unsafe or invalid path specified")
}
err = inTrustedRoot(r, trustedRoot)
if err != nil {
fmt.Println("Error " + err.Error())
return r, errors.New("Unsafe or invalid path specified")
} else {
return r, nil
}
}

View File

@@ -3,6 +3,7 @@ package channelserver
import ( import (
"encoding/binary" "encoding/binary"
"erupe-ce/common/mhfcourse" "erupe-ce/common/mhfcourse"
"erupe-ce/common/mhfitem"
"erupe-ce/common/mhfmon" "erupe-ce/common/mhfmon"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport" "erupe-ce/common/stringsupport"
@@ -812,93 +813,33 @@ func handleMsgMhfEnumeratePrice(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfGetExtraInfo(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetExtraInfo(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) { func userGetItems(s *Session) []mhfitem.MHFItemStack {
pkt := p.(*mhfpacket.MsgMhfEnumerateUnionItem) var data []byte
var boxContents []byte var items []mhfitem.MHFItemStack
bf := byteframe.NewByteFrame() s.server.db.QueryRow(`SELECT item_box FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&data)
err := s.server.db.QueryRow("SELECT item_box FROM users, characters WHERE characters.id = $1 AND users.id = characters.user_id", int(s.charID)).Scan(&boxContents) if len(data) > 0 {
if err != nil { box := byteframe.NewByteFrameFromBytes(data)
s.logger.Error("Failed to get shared item box contents from db", zap.Error(err)) numStacks := box.ReadUint16()
bf.WriteBytes(make([]byte, 4)) box.ReadUint16() // Unused
} else { for i := 0; i < int(numStacks); i++ {
if len(boxContents) == 0 { items = append(items, mhfitem.ReadWarehouseItem(box))
bf.WriteBytes(make([]byte, 4))
} else {
amount := len(boxContents) / 4
bf.WriteUint16(uint16(amount))
bf.WriteUint32(0x00)
bf.WriteUint16(0x00)
for i := 0; i < amount; i++ {
bf.WriteUint32(binary.BigEndian.Uint32(boxContents[i*4 : i*4+4]))
if i+1 != amount {
bf.WriteUint64(0x00)
}
}
} }
} }
return items
}
func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateUnionItem)
items := userGetItems(s)
bf := byteframe.NewByteFrame()
bf.WriteBytes(mhfitem.SerializeWarehouseItems(items))
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateUnionItem) pkt := p.(*mhfpacket.MsgMhfUpdateUnionItem)
// Get item cache from DB newStacks := mhfitem.DiffItemStacks(userGetItems(s), pkt.UpdatedItems)
var boxContents []byte s.server.db.Exec(`UPDATE users u SET item_box=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, mhfitem.SerializeWarehouseItems(newStacks), s.charID)
var oldItems []Item
err := s.server.db.QueryRow("SELECT item_box FROM users, characters WHERE characters.id = $1 AND users.id = characters.user_id", int(s.charID)).Scan(&boxContents)
if err != nil {
s.logger.Error("Failed to get shared item box contents from db", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
} else {
amount := len(boxContents) / 4
oldItems = make([]Item, amount)
for i := 0; i < amount; i++ {
oldItems[i].ItemId = binary.BigEndian.Uint16(boxContents[i*4 : i*4+2])
oldItems[i].Amount = binary.BigEndian.Uint16(boxContents[i*4+2 : i*4+4])
}
}
// Update item stacks
newItems := make([]Item, len(oldItems))
copy(newItems, oldItems)
for i := 0; i < len(pkt.Items); i++ {
for j := 0; j <= len(oldItems); j++ {
if j == len(oldItems) {
var newItem Item
newItem.ItemId = pkt.Items[i].ItemID
newItem.Amount = pkt.Items[i].Amount
newItems = append(newItems, newItem)
break
}
if pkt.Items[i].ItemID == oldItems[j].ItemId {
newItems[j].Amount = pkt.Items[i].Amount
break
}
}
}
// Delete empty item stacks
for i := len(newItems) - 1; i >= 0; i-- {
if int(newItems[i].Amount) == 0 {
copy(newItems[i:], newItems[i+1:])
newItems[len(newItems)-1] = make([]Item, 1)[0]
newItems = newItems[:len(newItems)-1]
}
}
// Create new item cache
bf := byteframe.NewByteFrame()
for i := 0; i < len(newItems); i++ {
bf.WriteUint16(newItems[i].ItemId)
bf.WriteUint16(newItems[i].Amount)
}
// Upload new item cache
_, err = s.server.db.Exec("UPDATE users SET item_box = $1 FROM characters WHERE users.id = characters.user_id AND characters.id = $2", bf.Data(), int(s.charID))
if err != nil {
s.logger.Error("Failed to update shared item box contents in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
@@ -906,50 +847,54 @@ func handleMsgMhfGetCogInfo(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfCheckWeeklyStamp(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfCheckWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckWeeklyStamp) pkt := p.(*mhfpacket.MsgMhfCheckWeeklyStamp)
weekCurrentStart := TimeWeekStart()
weekNextStart := TimeWeekNext()
var total, redeemed, updated uint16 var total, redeemed, updated uint16
var nextClaim time.Time var lastCheck time.Time
err := s.server.db.QueryRow(fmt.Sprintf("SELECT %s_next FROM stamps WHERE character_id=$1", pkt.StampType), s.charID).Scan(&nextClaim) err := s.server.db.QueryRow(fmt.Sprintf("SELECT %s_checked FROM stamps WHERE character_id=$1", pkt.StampType), s.charID).Scan(&lastCheck)
if err != nil { if err != nil {
s.server.db.Exec("INSERT INTO stamps (character_id, hl_next, ex_next) VALUES ($1, $2, $2)", s.charID, weekNextStart) lastCheck = TimeAdjusted()
nextClaim = weekNextStart s.server.db.Exec("INSERT INTO stamps (character_id, hl_checked, ex_checked) VALUES ($1, $2, $2)", s.charID, TimeAdjusted())
} else {
s.server.db.Exec(fmt.Sprintf(`UPDATE stamps SET %s_checked=$1 WHERE character_id=$2`, pkt.StampType), TimeAdjusted(), s.charID)
} }
if nextClaim.Before(weekCurrentStart) {
s.server.db.Exec(fmt.Sprintf("UPDATE stamps SET %s_total=%s_total+1, %s_next=$1 WHERE character_id=$2", pkt.StampType, pkt.StampType, pkt.StampType), weekNextStart, s.charID) if lastCheck.Before(TimeWeekStart()) {
s.server.db.Exec(fmt.Sprintf("UPDATE stamps SET %s_total=%s_total+1 WHERE character_id=$1", pkt.StampType, pkt.StampType), s.charID)
updated = 1 updated = 1
} }
s.server.db.QueryRow(fmt.Sprintf("SELECT %s_total, %s_redeemed FROM stamps WHERE character_id=$1", pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed) s.server.db.QueryRow(fmt.Sprintf("SELECT %s_total, %s_redeemed FROM stamps WHERE character_id=$1", pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint16(total) bf.WriteUint16(total)
bf.WriteUint16(redeemed) bf.WriteUint16(redeemed)
bf.WriteUint16(updated) bf.WriteUint16(updated)
bf.WriteUint32(0) // Unk bf.WriteUint16(0)
bf.WriteUint32(uint32(weekCurrentStart.Unix())) bf.WriteUint16(0)
bf.WriteUint32(uint32(TimeWeekStart().Unix()))
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfExchangeWeeklyStamp) pkt := p.(*mhfpacket.MsgMhfExchangeWeeklyStamp)
var total, redeemed uint16 var total, redeemed uint16
var tktStack mhfpacket.WarehouseStack var tktStack mhfitem.MHFItemStack
if pkt.Unk1 == 0xA { // Yearly Sub Ex if pkt.Unk1 == 10 { // Yearly Sub Ex
s.server.db.QueryRow("UPDATE stamps SET hl_total=hl_total-48, hl_redeemed=hl_redeemed-48 WHERE character_id=$1 RETURNING hl_total, hl_redeemed", s.charID).Scan(&total, &redeemed) s.server.db.QueryRow("UPDATE stamps SET hl_total=hl_total-48, hl_redeemed=hl_redeemed-48 WHERE character_id=$1 RETURNING hl_total, hl_redeemed", s.charID).Scan(&total, &redeemed)
tktStack = mhfpacket.WarehouseStack{ItemID: 0x08A2, Quantity: 1} tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 2210}, Quantity: 1}
} else { } else {
s.server.db.QueryRow(fmt.Sprintf("UPDATE stamps SET %s_redeemed=%s_redeemed+8 WHERE character_id=$1 RETURNING %s_total, %s_redeemed", pkt.StampType, pkt.StampType, pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed) s.server.db.QueryRow(fmt.Sprintf("UPDATE stamps SET %s_redeemed=%s_redeemed+8 WHERE character_id=$1 RETURNING %s_total, %s_redeemed", pkt.StampType, pkt.StampType, pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed)
if pkt.StampType == "hl" { if pkt.StampType == "hl" {
tktStack = mhfpacket.WarehouseStack{ItemID: 0x065E, Quantity: 5} tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 1630}, Quantity: 5}
} else { } else {
tktStack = mhfpacket.WarehouseStack{ItemID: 0x065F, Quantity: 5} tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 1631}, Quantity: 5}
} }
} }
addWarehouseGift(s, "item", tktStack) addWarehouseItem(s, tktStack)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint16(total) bf.WriteUint16(total)
bf.WriteUint16(redeemed) bf.WriteUint16(redeemed)
bf.WriteUint16(0) bf.WriteUint16(0)
bf.WriteUint32(0) // Unk, but has possible values bf.WriteUint16(tktStack.Item.ItemID)
bf.WriteUint16(tktStack.Quantity)
bf.WriteUint32(uint32(TimeWeekStart().Unix())) bf.WriteUint32(uint32(TimeWeekStart().Unix()))
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
@@ -1103,9 +1048,11 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStampcardStamp) pkt := p.(*mhfpacket.MsgMhfStampcardStamp)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint16(pkt.HR) bf.WriteUint16(pkt.HR)
bf.WriteUint16(pkt.GR)
var stamps uint16 var stamps uint16
_ = s.server.db.QueryRow(`SELECT stampcard FROM characters WHERE id = $1`, s.charID).Scan(&stamps) _ = s.server.db.QueryRow(`SELECT stampcard FROM characters WHERE id = $1`, s.charID).Scan(&stamps)
if _config.ErupeConfig.RealClientMode >= _config.G1 {
bf.WriteUint16(pkt.GR)
}
bf.WriteUint16(stamps) bf.WriteUint16(stamps)
stamps += pkt.Stamps stamps += pkt.Stamps
bf.WriteUint16(stamps) bf.WriteUint16(stamps)
@@ -1115,13 +1062,13 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(pkt.Reward2) bf.WriteUint16(pkt.Reward2)
bf.WriteUint16(pkt.Item2) bf.WriteUint16(pkt.Item2)
bf.WriteUint16(pkt.Quantity2) bf.WriteUint16(pkt.Quantity2)
addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item2, Quantity: pkt.Quantity2}) addWarehouseItem(s, mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item2}, Quantity: pkt.Quantity2})
} else if stamps%15 == 0 { } else if stamps%15 == 0 {
bf.WriteUint16(1) bf.WriteUint16(1)
bf.WriteUint16(pkt.Reward1) bf.WriteUint16(pkt.Reward1)
bf.WriteUint16(pkt.Item1) bf.WriteUint16(pkt.Item1)
bf.WriteUint16(pkt.Quantity1) bf.WriteUint16(pkt.Quantity1)
addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item1, Quantity: pkt.Quantity1}) addWarehouseItem(s, mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item1}, Quantity: pkt.Quantity1})
} else { } else {
bf.WriteBytes(make([]byte, 8)) bf.WriteBytes(make([]byte, 8))
} }

View File

@@ -7,35 +7,47 @@ import (
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
) )
// Handler BBS handles all the interactions with the for the screenshot sending to bulitin board functionality. For it to work it requires the API to be hosted somehwere. This implementation supports discord.
// Checks the status of the user to see if they can use Bulitin Board yet
func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {
//Post Screenshot pauses till this succeedes
pkt := p.(*mhfpacket.MsgMhfGetBbsUserStatus) pkt := p.(*mhfpacket.MsgMhfGetBbsUserStatus)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(200) bf.WriteUint32(200) //HTTP Status Codes //200 Success //404 You wont be able to post for a certain amount of time after creating your character //401/500 A error occured server side
bf.WriteUint32(0) bf.WriteUint32(0)
bf.WriteUint32(0) bf.WriteUint32(0)
bf.WriteUint32(0) bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
// Checks the status of Bultin Board Server to see if authenticated
func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBbsSnsStatus) pkt := p.(*mhfpacket.MsgMhfGetBbsSnsStatus)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(200) bf.WriteUint32(200) //200 Success //4XX Authentication has expired Please re-authenticate //5XX
bf.WriteUint32(401) bf.WriteUint32(401) //unk http status?
bf.WriteUint32(401) bf.WriteUint32(401) //unk http status?
bf.WriteUint32(0) bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
// Tells the game client what host port and gives the bultin board article a token
func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyBbsArticle) pkt := p.(*mhfpacket.MsgMhfApplyBbsArticle)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
articleToken := token.Generate(40) articleToken := token.Generate(40)
bf.WriteUint32(200)
bf.WriteUint32(80) bf.WriteUint32(200) //http status //200 success //4XX An error occured server side
bf.WriteUint32(s.server.erupeConfig.Screenshots.Port)
bf.WriteUint32(0) bf.WriteUint32(0)
bf.WriteUint32(0) bf.WriteUint32(0)
bf.WriteBytes(stringsupport.PaddedString(articleToken, 64, false)) bf.WriteBytes(stringsupport.PaddedString(articleToken, 64, false))
bf.WriteBytes(stringsupport.PaddedString(s.server.erupeConfig.ScreenshotAPIURL, 64, false)) bf.WriteBytes(stringsupport.PaddedString(s.server.erupeConfig.Screenshots.Host, 64, false))
//pkt.unk1[3] == Changes sometimes?
if s.server.erupeConfig.Screenshots.Enabled && s.server.erupeConfig.Discord.Enabled {
s.server.DiscordScreenShotSend(pkt.Name, pkt.Title, pkt.Description, articleToken)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }

View File

@@ -4,6 +4,7 @@ import (
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/common/mhfcourse" "erupe-ce/common/mhfcourse"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"fmt" "fmt"
"go.uber.org/zap" "go.uber.org/zap"
@@ -92,10 +93,11 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
if mhfcourse.CourseExists(30, s.courses) { if mhfcourse.CourseExists(30, s.courses) {
cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime
} }
bf.WriteUint32(cafeTime) // Total cafe time bf.WriteUint32(cafeTime)
bf.WriteUint16(0) if _config.ErupeConfig.RealClientMode >= _config.ZZ {
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true) bf.WriteUint16(0)
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }

View File

@@ -478,7 +478,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
m := binpacket.MsgBinChat{ m := binpacket.MsgBinChat{
Type: BinaryMessageTypeChat, Type: BinaryMessageTypeChat,
Flags: 4, Flags: 4,
Message: fmt.Sprintf(`%d`, token.RNG().Intn(100)+1), Message: fmt.Sprintf(`%d`, token.RNG.Intn(100)+1),
SenderName: author, SenderName: author,
} }
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()

View File

@@ -25,7 +25,7 @@ const (
pGardenData // +68 pGardenData // +68
pWeaponType // +1 pWeaponType // +1
pWeaponID // +2 pWeaponID // +2
pHRP // +2 pHR // +2
pGRP // +4 pGRP // +4
pKQF // +8 pKQF // +8
lBookshelfData lBookshelfData
@@ -47,7 +47,7 @@ type CharacterSaveData struct {
GardenData []byte GardenData []byte
WeaponType uint8 WeaponType uint8
WeaponID uint16 WeaponID uint16
HRP uint16 HR uint16
GR uint16 GR uint16
KQF []byte KQF []byte
@@ -63,7 +63,7 @@ func getPointers() map[SavePointer]int {
pointers[pWeaponType] = 128789 pointers[pWeaponType] = 128789
pointers[pHouseTier] = 129900 pointers[pHouseTier] = 129900
pointers[pToreData] = 130228 pointers[pToreData] = 130228
pointers[pHRP] = 130550 pointers[pHR] = 130550
pointers[pGRP] = 130556 pointers[pGRP] = 130556
pointers[pHouseData] = 130561 pointers[pHouseData] = 130561
pointers[pBookshelfData] = 139928 pointers[pBookshelfData] = 139928
@@ -78,10 +78,10 @@ func getPointers() map[SavePointer]int {
pointers[pWeaponType] = 92789 pointers[pWeaponType] = 92789
pointers[pHouseTier] = 93900 pointers[pHouseTier] = 93900
pointers[pToreData] = 94228 pointers[pToreData] = 94228
pointers[pHRP] = 94550 pointers[pHR] = 94550
pointers[pGRP] = 94556 pointers[pGRP] = 94556
pointers[pHouseData] = 94561 pointers[pHouseData] = 94561
pointers[pBookshelfData] = 103928 pointers[pBookshelfData] = 89118 // TODO: fix bookshelf data pointer
pointers[pGalleryData] = 104064 pointers[pGalleryData] = 104064
pointers[pGardenData] = 106424 pointers[pGardenData] = 106424
pointers[pRP] = 106614 pointers[pRP] = 106614
@@ -91,9 +91,9 @@ func getPointers() map[SavePointer]int {
pointers[pWeaponType] = 60789 pointers[pWeaponType] = 60789
pointers[pHouseTier] = 61900 pointers[pHouseTier] = 61900
pointers[pToreData] = 62228 pointers[pToreData] = 62228
pointers[pHRP] = 62550 pointers[pHR] = 62550
pointers[pHouseData] = 62561 pointers[pHouseData] = 62561
pointers[pBookshelfData] = 57118 // This pointer only half works pointers[pBookshelfData] = 57118 // TODO: fix bookshelf data pointer
pointers[pGalleryData] = 72064 pointers[pGalleryData] = 72064
pointers[pGardenData] = 74424 pointers[pGardenData] = 74424
pointers[pRP] = 74614 pointers[pRP] = 74614
@@ -102,9 +102,9 @@ func getPointers() map[SavePointer]int {
pointers[pWeaponType] = 12789 pointers[pWeaponType] = 12789
pointers[pHouseTier] = 13900 pointers[pHouseTier] = 13900
pointers[pToreData] = 14228 pointers[pToreData] = 14228
pointers[pHRP] = 14550 pointers[pHR] = 14550
pointers[pHouseData] = 14561 pointers[pHouseData] = 14561
pointers[pBookshelfData] = 9118 // Probably same here pointers[pBookshelfData] = 9118 // TODO: fix bookshelf data pointer
pointers[pGalleryData] = 24064 pointers[pGalleryData] = 24064
pointers[pGardenData] = 26424 pointers[pGardenData] = 26424
pointers[pRP] = 26614 pointers[pRP] = 26614
@@ -174,8 +174,8 @@ func (save *CharacterSaveData) Save(s *Session) {
save.compSave = save.decompSave save.compSave = save.decompSave
} }
_, err := s.server.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=false, hrp=$2, gr=$3, is_female=$4, weapon_type=$5, weapon_id=$6 WHERE id=$7 _, err := s.server.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=false, hr=$2, gr=$3, is_female=$4, weapon_type=$5, weapon_id=$6 WHERE id=$7
`, save.compSave, save.HRP, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID) `, save.compSave, save.HR, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID)
if err != nil { if err != nil {
s.logger.Error("Failed to update savedata", zap.Error(err), zap.Uint32("charID", save.CharID)) s.logger.Error("Failed to update savedata", zap.Error(err), zap.Uint32("charID", save.CharID))
} }
@@ -233,9 +233,9 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68] save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68]
save.WeaponType = save.decompSave[save.Pointers[pWeaponType]] save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2])
save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2]) save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+2])
if _config.ErupeConfig.RealClientMode >= _config.G1 { if _config.ErupeConfig.RealClientMode >= _config.G1 {
if save.HRP == uint16(999) { if save.HR == uint16(999) {
save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))) save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])))
} }
} }

View File

@@ -66,7 +66,8 @@ func handleMsgMhfGetWeeklySchedule(s *Session, p mhfpacket.MHFPacket) {
var temp activeFeature var temp activeFeature
err := s.server.db.QueryRowx(`SELECT start_time, featured FROM feature_weapon WHERE start_time=$1`, t).StructScan(&temp) err := s.server.db.QueryRowx(`SELECT start_time, featured FROM feature_weapon WHERE start_time=$1`, t).StructScan(&temp)
if err != nil || temp.StartTime.IsZero() { if err != nil || temp.StartTime.IsZero() {
temp = generateFeatureWeapons(s.server.erupeConfig.GameplayOptions.FeaturedWeapons) weapons := token.RNG.Intn(s.server.erupeConfig.GameplayOptions.MaxFeatureWeapons-s.server.erupeConfig.GameplayOptions.MinFeatureWeapons+1) + s.server.erupeConfig.GameplayOptions.MinFeatureWeapons
temp = generateFeatureWeapons(weapons)
temp.StartTime = t temp.StartTime = t
s.server.db.Exec(`INSERT INTO feature_weapon VALUES ($1, $2)`, temp.StartTime, temp.ActiveFeatures) s.server.db.Exec(`INSERT INTO feature_weapon VALUES ($1, $2)`, temp.StartTime, temp.ActiveFeatures)
} }
@@ -101,8 +102,7 @@ func generateFeatureWeapons(count int) activeFeature {
nums := make([]int, 0) nums := make([]int, 0)
var result int var result int
for len(nums) < count { for len(nums) < count {
rng := token.RNG() num := token.RNG.Intn(_max)
num := rng.Intn(_max)
exist := false exist := false
for _, v := range nums { for _, v := range nums {
if v == num { if v == num {

View File

@@ -559,7 +559,7 @@ func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
team := uint32(token.RNG().Intn(2)) team := uint32(token.RNG.Intn(2))
switch team { switch team {
case 0: case 0:
s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'blue')", guild.ID) s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'blue')", guild.ID)

View File

@@ -3,9 +3,9 @@ package channelserver
import ( import (
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"encoding/binary"
"encoding/json" "encoding/json"
"errors" "errors"
"erupe-ce/common/mhfitem"
_config "erupe-ce/config" _config "erupe-ce/config"
"fmt" "fmt"
"math" "math"
@@ -51,6 +51,8 @@ type Guild struct {
MemberCount uint16 `db:"member_count"` MemberCount uint16 `db:"member_count"`
RankRP uint32 `db:"rank_rp"` RankRP uint32 `db:"rank_rp"`
EventRP uint32 `db:"event_rp"` EventRP uint32 `db:"event_rp"`
RoomRP uint16 `db:"room_rp"`
RoomExpiry time.Time `db:"room_expiry"`
Comment string `db:"comment"` Comment string `db:"comment"`
PugiName1 string `db:"pugi_name_1"` PugiName1 string `db:"pugi_name_1"`
PugiName2 string `db:"pugi_name_2"` PugiName2 string `db:"pugi_name_2"`
@@ -153,6 +155,8 @@ SELECT
g.name, g.name,
rank_rp, rank_rp,
event_rp, event_rp,
room_rp,
COALESCE(room_expiry, '1970-01-01') AS room_expiry,
main_motto, main_motto,
sub_motto, sub_motto,
created_at, created_at,
@@ -706,7 +710,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
} }
bf.WriteUint32(uint32(response)) bf.WriteUint32(uint32(response))
case mhfpacket.OperateGuildDonateRank: case mhfpacket.OperateGuildDonateRank:
bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, false)) bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, 0))
case mhfpacket.OperateGuildSetApplicationDeny: case mhfpacket.OperateGuildSetApplicationDeny:
s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID) s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID)
case mhfpacket.OperateGuildSetApplicationAllow: case mhfpacket.OperateGuildSetApplicationAllow:
@@ -747,10 +751,11 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
// TODO: This doesn't implement blocking, if someone unlocked the same outfit at the same time // TODO: This doesn't implement blocking, if someone unlocked the same outfit at the same time
s.server.db.Exec(`UPDATE guilds SET pugi_outfits=pugi_outfits+$1 WHERE id=$2`, int(math.Pow(float64(pkt.Data1.ReadUint32()), 2)), guild.ID) s.server.db.Exec(`UPDATE guilds SET pugi_outfits=pugi_outfits+$1 WHERE id=$2`, int(math.Pow(float64(pkt.Data1.ReadUint32()), 2)), guild.ID)
case mhfpacket.OperateGuildDonateRoom: case mhfpacket.OperateGuildDonateRoom:
// TODO: Where does this go? quantity := uint16(pkt.Data1.ReadUint32())
bf.WriteBytes(handleDonateRP(s, quantity, guild, 2))
case mhfpacket.OperateGuildDonateEvent: case mhfpacket.OperateGuildDonateEvent:
quantity := uint16(pkt.Data1.ReadUint32()) quantity := uint16(pkt.Data1.ReadUint32())
bf.WriteBytes(handleDonateRP(s, quantity, guild, true)) bf.WriteBytes(handleDonateRP(s, quantity, guild, 1))
// TODO: Move this value onto rp_yesterday and reset to 0... daily? // TODO: Move this value onto rp_yesterday and reset to 0... daily?
s.server.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, quantity, s.charID) s.server.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, quantity, s.charID)
case mhfpacket.OperateGuildEventExchange: case mhfpacket.OperateGuildEventExchange:
@@ -794,20 +799,37 @@ func handleChangePugi(s *Session, outfit uint8, guild *Guild, num int) {
guild.Save(s) guild.Save(s)
} }
func handleDonateRP(s *Session, amount uint16, guild *Guild, isEvent bool) []byte { func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(0) bf.WriteUint32(0)
saveData, err := GetCharacterSaveData(s, s.charID) saveData, err := GetCharacterSaveData(s, s.charID)
if err != nil { if err != nil {
return bf.Data() return bf.Data()
} }
var resetRoom bool
if _type == 2 {
var currentRP uint16
s.server.db.QueryRow(`SELECT room_rp FROM guilds WHERE id = $1`, guild.ID).Scan(&currentRP)
if currentRP+amount >= 30 {
amount = 30 - currentRP
resetRoom = true
}
}
saveData.RP -= amount saveData.RP -= amount
saveData.Save(s) saveData.Save(s)
updateSQL := "UPDATE guilds SET rank_rp = rank_rp + $1 WHERE id = $2" switch _type {
if isEvent { case 0:
updateSQL = "UPDATE guilds SET event_rp = event_rp + $1 WHERE id = $2" s.server.db.Exec(`UPDATE guilds SET rank_rp = rank_rp + $1 WHERE id = $2`, amount, guild.ID)
case 1:
s.server.db.Exec(`UPDATE guilds SET event_rp = event_rp + $1 WHERE id = $2`, amount, guild.ID)
case 2:
if resetRoom {
s.server.db.Exec(`UPDATE guilds SET room_rp = 0 WHERE id = $1`, guild.ID)
s.server.db.Exec(`UPDATE guilds SET room_expiry = $1 WHERE id = $2`, TimeAdjusted().Add(time.Hour*24*7), guild.ID)
} else {
s.server.db.Exec(`UPDATE guilds SET room_rp = room_rp + $1 WHERE id = $2`, amount, guild.ID)
}
} }
s.server.db.Exec(updateSQL, amount, guild.ID)
bf.Seek(0, 0) bf.Seek(0, 0)
bf.WriteUint32(uint32(saveData.RP)) bf.WriteUint32(uint32(saveData.RP))
return bf.Data() return bf.Data()
@@ -1001,8 +1023,8 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(limit) bf.WriteUint8(limit)
bf.WriteUint32(55000) bf.WriteUint32(55000)
bf.WriteUint32(0) bf.WriteUint32(uint32(guild.RoomExpiry.Unix()))
bf.WriteUint16(0) // Changing Room RP bf.WriteUint16(guild.RoomRP)
bf.WriteUint16(0) // Ignored bf.WriteUint16(0) // Ignored
if guild.AllianceID > 0 { if guild.AllianceID > 0 {
@@ -1075,7 +1097,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
for _, applicant := range applicants { for _, applicant := range applicants {
bf.WriteUint32(applicant.CharID) bf.WriteUint32(applicant.CharID)
bf.WriteUint32(0) bf.WriteUint32(0)
bf.WriteUint16(applicant.HRP) bf.WriteUint16(applicant.HR)
bf.WriteUint16(applicant.GR) bf.WriteUint16(applicant.GR)
ps.Uint8(bf, applicant.Name, true) ps.Uint8(bf, applicant.Name, true)
} }
@@ -1435,7 +1457,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
for _, member := range guildMembers { for _, member := range guildMembers {
bf.WriteUint32(member.CharID) bf.WriteUint32(member.CharID)
bf.WriteUint16(member.HRP) bf.WriteUint16(member.HR)
if s.server.erupeConfig.RealClientMode >= _config.G10 { if s.server.erupeConfig.RealClientMode >= _config.G10 {
bf.WriteUint16(member.GR) bf.WriteUint16(member.GR)
} }
@@ -1554,100 +1576,34 @@ func handleMsgMhfGetGuildTargetMemberNum(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) { func guildGetItems(s *Session, guildID uint32) []mhfitem.MHFItemStack {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem) var data []byte
var boxContents []byte var items []mhfitem.MHFItemStack
bf := byteframe.NewByteFrame() s.server.db.QueryRow(`SELECT item_box FROM guilds WHERE id=$1`, guildID).Scan(&data)
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", pkt.GuildID).Scan(&boxContents) if len(data) > 0 {
if err != nil { box := byteframe.NewByteFrameFromBytes(data)
s.logger.Error("Failed to get guild item box contents from db", zap.Error(err)) numStacks := box.ReadUint16()
bf.WriteBytes(make([]byte, 4)) box.ReadUint16() // Unused
} else { for i := 0; i < int(numStacks); i++ {
if len(boxContents) == 0 { items = append(items, mhfitem.ReadWarehouseItem(box))
bf.WriteBytes(make([]byte, 4))
} else {
amount := len(boxContents) / 4
bf.WriteUint16(uint16(amount))
bf.WriteUint32(0x00)
bf.WriteUint16(0x00)
for i := 0; i < amount; i++ {
bf.WriteUint32(binary.BigEndian.Uint32(boxContents[i*4 : i*4+4]))
if i+1 != amount {
bf.WriteUint64(0x00)
}
}
} }
} }
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) return items
} }
type Item struct { func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
ItemId uint16 pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem)
Amount uint16 items := guildGetItems(s, pkt.GuildID)
bf := byteframe.NewByteFrame()
bf.WriteBytes(mhfitem.SerializeWarehouseItems(items))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateGuildItem) pkt := p.(*mhfpacket.MsgMhfUpdateGuildItem)
newStacks := mhfitem.DiffItemStacks(guildGetItems(s, pkt.GuildID), pkt.UpdatedItems)
// Get item cache from DB s.server.db.Exec(`UPDATE guilds SET item_box=$1 WHERE id=$2`, mhfitem.SerializeWarehouseItems(newStacks), pkt.GuildID)
var boxContents []byte doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
var oldItems []Item
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", pkt.GuildID).Scan(&boxContents)
if err != nil {
s.logger.Error("Failed to get guild item box contents from db", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
} else {
amount := len(boxContents) / 4
oldItems = make([]Item, amount)
for i := 0; i < amount; i++ {
oldItems[i].ItemId = binary.BigEndian.Uint16(boxContents[i*4 : i*4+2])
oldItems[i].Amount = binary.BigEndian.Uint16(boxContents[i*4+2 : i*4+4])
}
}
// Update item stacks
newItems := make([]Item, len(oldItems))
copy(newItems, oldItems)
for i := 0; i < len(pkt.Items); i++ {
for j := 0; j <= len(oldItems); j++ {
if j == len(oldItems) {
var newItem Item
newItem.ItemId = pkt.Items[i].ItemID
newItem.Amount = pkt.Items[i].Amount
newItems = append(newItems, newItem)
break
}
if pkt.Items[i].ItemID == oldItems[j].ItemId {
newItems[j].Amount = pkt.Items[i].Amount
break
}
}
}
// Delete empty item stacks
for i := len(newItems) - 1; i >= 0; i-- {
if int(newItems[i].Amount) == 0 {
copy(newItems[i:], newItems[i+1:])
newItems[len(newItems)-1] = make([]Item, 1)[0]
newItems = newItems[:len(newItems)-1]
}
}
// Create new item cache
bf := byteframe.NewByteFrame()
for i := 0; i < len(newItems); i++ {
bf.WriteUint16(newItems[i].ItemId)
bf.WriteUint16(newItems[i].Amount)
}
// Upload new item cache
_, err = s.server.db.Exec("UPDATE guilds SET item_box = $1 WHERE id = $2", bf.Data(), pkt.GuildID)
if err != nil {
s.logger.Error("Failed to update guild item box contents in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} }
func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -22,7 +22,7 @@ type GuildMember struct {
Recruiter bool `db:"recruiter"` Recruiter bool `db:"recruiter"`
AvoidLeadership bool `db:"avoid_leadership"` AvoidLeadership bool `db:"avoid_leadership"`
IsLeader bool `db:"is_leader"` IsLeader bool `db:"is_leader"`
HRP uint16 `db:"hrp"` HR uint16 `db:"hr"`
GR uint16 `db:"gr"` GR uint16 `db:"gr"`
WeaponID uint16 `db:"weapon_id"` WeaponID uint16 `db:"weapon_id"`
WeaponType uint8 `db:"weapon_type"` WeaponType uint8 `db:"weapon_type"`
@@ -74,7 +74,7 @@ SELECT * FROM (
c.last_login, c.last_login,
COALESCE(recruiter, false) AS recruiter, COALESCE(recruiter, false) AS recruiter,
COALESCE(avoid_leadership, false) AS avoid_leadership, COALESCE(avoid_leadership, false) AS avoid_leadership,
c.hrp, c.hr,
c.gr, c.gr,
c.weapon_id, c.weapon_id,
c.weapon_type, c.weapon_type,

View File

@@ -204,7 +204,7 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
} }
rows, err := s.server.db.Queryx(` rows, err := s.server.db.Queryx(`
SELECT c.id, c.name, c.hrp, c.gr, ga.actor_id SELECT c.id, c.name, c.hr, c.gr, ga.actor_id
FROM guild_applications ga FROM guild_applications ga
JOIN characters c ON c.id = ga.character_id JOIN characters c ON c.id = ga.character_id
WHERE ga.guild_id = $1 AND ga.application_type = 'invited' WHERE ga.guild_id = $1 AND ga.application_type = 'invited'
@@ -230,9 +230,9 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
for rows.Next() { for rows.Next() {
var charName string var charName string
var charID, actorID uint32 var charID, actorID uint32
var hrp, gr uint16 var HR, GR uint16
err = rows.Scan(&charID, &charName, &hrp, &gr, &actorID) err = rows.Scan(&charID, &charName, &HR, &GR, &actorID)
if err != nil { if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleFail(s, pkt.AckHandle, nil)
@@ -246,8 +246,8 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(actorID) bf.WriteUint32(actorID)
bf.WriteUint32(charID) bf.WriteUint32(charID)
bf.WriteUint32(uint32(TimeAdjusted().Unix())) bf.WriteUint32(uint32(TimeAdjusted().Unix()))
bf.WriteUint16(hrp) // HR? bf.WriteUint16(HR) // HR?
bf.WriteUint16(gr) // GR? bf.WriteUint16(GR) // GR?
bf.WriteBytes(stringsupport.PaddedString(charName, 32, true)) bf.WriteBytes(stringsupport.PaddedString(charName, 32, true))
count++ count++
} }

View File

@@ -2,8 +2,10 @@ package channelserver
import ( import (
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/common/mhfitem"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport" "erupe-ce/common/stringsupport"
"erupe-ce/common/token"
_config "erupe-ce/config" _config "erupe-ce/config"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"fmt" "fmt"
@@ -45,7 +47,7 @@ func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
type HouseData struct { type HouseData struct {
CharID uint32 `db:"id"` CharID uint32 `db:"id"`
HRP uint16 `db:"hrp"` HR uint16 `db:"hr"`
GR uint16 `db:"gr"` GR uint16 `db:"gr"`
Name string `db:"name"` Name string `db:"name"`
HouseState uint8 `db:"house_state"` HouseState uint8 `db:"house_state"`
@@ -57,7 +59,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint16(0) bf.WriteUint16(0)
var houses []HouseData var houses []HouseData
houseQuery := `SELECT c.id, hrp, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password houseQuery := `SELECT c.id, hr, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE c.id=$1` FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE c.id=$1`
switch pkt.Method { switch pkt.Method {
case 1: case 1:
@@ -90,7 +92,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
} }
} }
case 3: case 3:
houseQuery = `SELECT c.id, hrp, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password houseQuery = `SELECT c.id, hr, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE name ILIKE $1` FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE name ILIKE $1`
house := HouseData{} house := HouseData{}
rows, _ := s.server.db.Queryx(houseQuery, fmt.Sprintf(`%%%s%%`, pkt.Name)) rows, _ := s.server.db.Queryx(houseQuery, fmt.Sprintf(`%%%s%%`, pkt.Name))
@@ -118,7 +120,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
} else { } else {
bf.WriteUint8(0) bf.WriteUint8(0)
} }
bf.WriteUint16(house.HRP) bf.WriteUint16(house.HR)
if _config.ErupeConfig.RealClientMode >= _config.G10 { if _config.ErupeConfig.RealClientMode >= _config.G10 {
bf.WriteUint16(house.GR) bf.WriteUint16(house.GR)
} }
@@ -406,7 +408,12 @@ func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
case 1: case 1:
bf.WriteUint8(0) bf.WriteUint8(0)
case 2: case 2:
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%dname=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), pkt.Name, s.charID) switch pkt.BoxType {
case 0:
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET item%dname=$1 WHERE character_id=$2", pkt.BoxIndex), pkt.Name, s.charID)
case 1:
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET equip%dname=$1 WHERE character_id=$2", pkt.BoxIndex), pkt.Name, s.charID)
}
case 3: case 3:
bf.WriteUint32(0) // Usage renewal time, >1 = disabled bf.WriteUint32(0) // Usage renewal time, >1 = disabled
bf.WriteUint16(10000) // Usages bf.WriteUint16(10000) // Usages
@@ -424,81 +431,63 @@ func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func addWarehouseGift(s *Session, boxType string, giftStack mhfpacket.WarehouseStack) { func addWarehouseItem(s *Session, item mhfitem.MHFItemStack) {
giftBox := getWarehouseBox(s, boxType, 10) giftBox := warehouseGetItems(s, 10)
if boxType == "item" { item.WarehouseID = token.RNG.Uint32()
exists := false giftBox = append(giftBox, item)
for i, stack := range giftBox { s.server.db.Exec("UPDATE warehouse SET item10=$1 WHERE character_id=$2", mhfitem.SerializeWarehouseItems(giftBox), s.charID)
if stack.ItemID == giftStack.ItemID {
exists = true
giftBox[i].Quantity += giftStack.Quantity
break
}
}
if exists == false {
giftBox = append(giftBox, giftStack)
}
} else {
giftBox = append(giftBox, giftStack)
}
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s10=$1 WHERE character_id=$2", boxType), boxToBytes(giftBox, boxType), s.charID)
} }
func getWarehouseBox(s *Session, boxType string, boxIndex uint8) []mhfpacket.WarehouseStack { func addWarehouseEquipment(s *Session, equipment mhfitem.MHFEquipment) {
giftBox := warehouseGetEquipment(s, 10)
equipment.WarehouseID = token.RNG.Uint32()
giftBox = append(giftBox, equipment)
s.server.db.Exec("UPDATE warehouse SET equip10=$1 WHERE character_id=$2", mhfitem.SerializeWarehouseEquipment(giftBox), s.charID)
}
func warehouseGetItems(s *Session, index uint8) []mhfitem.MHFItemStack {
var data []byte var data []byte
s.server.db.QueryRow(fmt.Sprintf("SELECT %s%d FROM warehouse WHERE character_id=$1", boxType, boxIndex), s.charID).Scan(&data) var items []mhfitem.MHFItemStack
s.server.db.QueryRow(fmt.Sprintf(`SELECT item%d FROM warehouse WHERE character_id=$1`, index), s.charID).Scan(&data)
if len(data) > 0 { if len(data) > 0 {
box := byteframe.NewByteFrameFromBytes(data) box := byteframe.NewByteFrameFromBytes(data)
numStacks := box.ReadUint16() numStacks := box.ReadUint16()
stacks := make([]mhfpacket.WarehouseStack, numStacks) box.ReadUint16() // Unused
for i := 0; i < int(numStacks); i++ { for i := 0; i < int(numStacks); i++ {
if boxType == "item" { items = append(items, mhfitem.ReadWarehouseItem(box))
stacks[i].ID = box.ReadUint32()
stacks[i].Index = box.ReadUint16()
stacks[i].ItemID = box.ReadUint16()
stacks[i].Quantity = box.ReadUint16()
box.ReadUint16()
} else {
stacks[i].ID = box.ReadUint32()
stacks[i].Index = box.ReadUint16()
stacks[i].EquipType = box.ReadUint16()
stacks[i].ItemID = box.ReadUint16()
stacks[i].Data = box.ReadBytes(56)
}
} }
return stacks
} else {
return make([]mhfpacket.WarehouseStack, 0)
} }
return items
} }
func boxToBytes(stacks []mhfpacket.WarehouseStack, boxType string) []byte { func warehouseGetEquipment(s *Session, index uint8) []mhfitem.MHFEquipment {
bf := byteframe.NewByteFrame() var data []byte
bf.WriteUint16(uint16(len(stacks))) var equipment []mhfitem.MHFEquipment
for i, stack := range stacks { s.server.db.QueryRow(fmt.Sprintf(`SELECT equip%d FROM warehouse WHERE character_id=$1`, index), s.charID).Scan(&data)
if boxType == "item" { if len(data) > 0 {
bf.WriteUint32(stack.ID) box := byteframe.NewByteFrameFromBytes(data)
bf.WriteUint16(uint16(i + 1)) numStacks := box.ReadUint16()
bf.WriteUint16(stack.ItemID) box.ReadUint16() // Unused
bf.WriteUint16(stack.Quantity) for i := 0; i < int(numStacks); i++ {
bf.WriteUint16(0) equipment = append(equipment, mhfitem.ReadWarehouseEquipment(box))
} else {
bf.WriteUint32(stack.ID)
bf.WriteUint16(uint16(i + 1))
bf.WriteUint16(stack.EquipType)
bf.WriteUint16(stack.ItemID)
bf.WriteBytes(stack.Data)
} }
} }
bf.WriteUint16(0) return equipment
return bf.Data()
} }
func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateWarehouse) pkt := p.(*mhfpacket.MsgMhfEnumerateWarehouse)
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex) bf := byteframe.NewByteFrame()
if len(box) > 0 { switch pkt.BoxType {
doAckBufSucceed(s, pkt.AckHandle, boxToBytes(box, pkt.BoxType)) case 0:
items := warehouseGetItems(s, pkt.BoxIndex)
bf.WriteBytes(mhfitem.SerializeWarehouseItems(items))
case 1:
equipment := warehouseGetEquipment(s, pkt.BoxIndex)
bf.WriteBytes(mhfitem.SerializeWarehouseEquipment(equipment))
}
if bf.Index() > 0 {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else { } else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
@@ -506,49 +495,34 @@ func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateWarehouse) pkt := p.(*mhfpacket.MsgMhfUpdateWarehouse)
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex) switch pkt.BoxType {
// Update existing stacks case 0:
var newStacks []mhfpacket.WarehouseStack newStacks := mhfitem.DiffItemStacks(warehouseGetItems(s, pkt.BoxIndex), pkt.UpdatedItems)
for _, update := range pkt.Updates { s.server.db.Exec(fmt.Sprintf(`UPDATE warehouse SET item%d=$1 WHERE character_id=$2`, pkt.BoxIndex), mhfitem.SerializeWarehouseItems(newStacks), s.charID)
exists := false case 1:
if pkt.BoxType == "item" { var fEquip []mhfitem.MHFEquipment
for i, stack := range box { oEquips := warehouseGetEquipment(s, pkt.BoxIndex)
if stack.Index == update.Index { for _, uEquip := range pkt.UpdatedEquipment {
exists := false
for i := range oEquips {
if oEquips[i].WarehouseID == uEquip.WarehouseID {
exists = true exists = true
box[i].Quantity = update.Quantity // Will set removed items to 0
oEquips[i].ItemID = uEquip.ItemID
break break
} }
} }
} else { if !exists {
for i, stack := range box { uEquip.WarehouseID = token.RNG.Uint32()
if stack.Index == update.Index { fEquip = append(fEquip, uEquip)
exists = true
box[i].ItemID = update.ItemID
break
}
} }
} }
if exists == false { for _, oEquip := range oEquips {
newStacks = append(newStacks, update) if oEquip.ItemID > 0 {
} fEquip = append(fEquip, oEquip)
}
// Append new stacks
for _, stack := range newStacks {
box = append(box, stack)
}
// Slice empty stacks
var cleanedBox []mhfpacket.WarehouseStack
for _, stack := range box {
if pkt.BoxType == "item" {
if stack.Quantity > 0 {
cleanedBox = append(cleanedBox, stack)
}
} else {
if stack.ItemID != 0 {
cleanedBox = append(cleanedBox, stack)
} }
} }
s.server.db.Exec(fmt.Sprintf(`UPDATE warehouse SET equip%d=$1 WHERE character_id=$2`, pkt.BoxIndex), mhfitem.SerializeWarehouseEquipment(fEquip), s.charID)
} }
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%d=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), boxToBytes(cleanedBox, pkt.BoxType), s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }

View File

@@ -280,7 +280,7 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
bf.WriteBool(true) bf.WriteBool(true)
} }
bf.WriteUint16(0) // Unk bf.WriteUint16(0) // Unk
if _config.ErupeConfig.RealClientMode >= _config.G1 { if _config.ErupeConfig.RealClientMode >= _config.G2 {
bf.WriteUint32(mark) bf.WriteUint32(mark)
} }
bf.WriteUint16(0) // Unk bf.WriteUint16(0) // Unk
@@ -578,7 +578,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
tuneValues = temp tuneValues = temp
tuneLimit := 770 tuneLimit := 770
if _config.ErupeConfig.RealClientMode <= _config.F5 { if _config.ErupeConfig.RealClientMode <= _config.G1 {
tuneLimit = 256 tuneLimit = 256
} else if _config.ErupeConfig.RealClientMode <= _config.G3 { } else if _config.ErupeConfig.RealClientMode <= _config.G3 {
tuneLimit = 283 tuneLimit = 283

View File

@@ -148,9 +148,24 @@ func removeSessionFromStage(s *Session) {
destructEmptySemaphores(s) destructEmptySemaphores(s)
} }
func isStageFull(s *Session, StageID string) bool {
if stage, exists := s.server.stages[StageID]; exists {
if _, exists := stage.reservedClientSlots[s.charID]; exists {
return false
}
return len(stage.reservedClientSlots)+len(stage.clients) >= int(stage.maxPlayers)
}
return false
}
func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysEnterStage) pkt := p.(*mhfpacket.MsgSysEnterStage)
if isStageFull(s, pkt.StageID) {
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
return
}
// Push our current stage ID to the movement stack before entering another one. // Push our current stage ID to the movement stack before entering another one.
if s.stage != nil { if s.stage != nil {
s.stage.Lock() s.stage.Lock()
@@ -175,6 +190,12 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
backStage = "sl1Ns200p0a0u0" backStage = "sl1Ns200p0a0u0"
} }
if isStageFull(s, backStage) {
s.stageMoveStack.Push(backStage)
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
return
}
if _, exists := s.stage.reservedClientSlots[s.charID]; exists { if _, exists := s.stage.reservedClientSlots[s.charID]; exists {
delete(s.stage.reservedClientSlots, s.charID) delete(s.stage.reservedClientSlots, s.charID)
} }
@@ -188,6 +209,12 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysMoveStage) pkt := p.(*mhfpacket.MsgSysMoveStage)
if isStageFull(s, pkt.StageID) {
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
return
}
doStageTransfer(s, pkt.AckHandle, pkt.StageID) doStageTransfer(s, pkt.AckHandle, pkt.StageID)
} }

View File

@@ -367,6 +367,14 @@ func (s *Server) DiscordChannelSend(charName string, content string) {
} }
} }
func (s *Server) DiscordScreenShotSend(charName string, title string, description string, articleToken string) {
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
imageUrl := fmt.Sprintf("%s:%d/api/ss/bbs/%s", s.erupeConfig.Screenshots.Host, s.erupeConfig.Screenshots.Port, articleToken)
message := fmt.Sprintf("**%s**: %s - %s %s", charName, title, description, imageUrl)
s.discordBot.RealtimeChannelSend(message)
}
}
func (s *Server) FindSessionByCharID(charID uint32) *Session { func (s *Server) FindSessionByCharID(charID uint32) *Session {
for _, c := range s.Channels { for _, c := range s.Channels {
for _, session := range c.sessions { for _, session := range c.sessions {

View File

@@ -59,7 +59,7 @@ func NewStage(ID string) *Stage {
objects: make(map[uint32]*Object), objects: make(map[uint32]*Object),
objectIndex: 0, objectIndex: 0,
rawBinaryData: make(map[stageBinaryKey][]byte), rawBinaryData: make(map[stageBinaryKey][]byte),
maxPlayers: 4, maxPlayers: 127,
} }
return s return s
} }

View File

@@ -1,10 +1,11 @@
package discordbot package discordbot
import ( import (
"erupe-ce/config" _config "erupe-ce/config"
"regexp"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"go.uber.org/zap" "go.uber.org/zap"
"regexp"
) )
var Commands = []*discordgo.ApplicationCommand{ var Commands = []*discordgo.ApplicationCommand{
@@ -113,7 +114,6 @@ func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {
return return
} }
func ReplaceTextAll(text string, regex *regexp.Regexp, handler func(input string) string) string { func ReplaceTextAll(text string, regex *regexp.Regexp, handler func(input string) string) string {
result := regex.ReplaceAllFunc([]byte(text), func(s []byte) []byte { result := regex.ReplaceAllFunc([]byte(text), func(s []byte) []byte {
input := regex.ReplaceAllString(string(s), `$1`) input := regex.ReplaceAllString(string(s), `$1`)

View File

@@ -27,7 +27,7 @@ func (s *Server) newUserChara(uid uint32) error {
_, err = s.db.Exec(` _, err = s.db.Exec(`
INSERT INTO characters ( INSERT INTO characters (
user_id, is_female, is_new_character, name, unk_desc_string, user_id, is_female, is_new_character, name, unk_desc_string,
hrp, gr, weapon_type, last_login) hr, gr, weapon_type, last_login)
VALUES($1, False, True, '', '', 0, 0, 0, $2)`, VALUES($1, False, True, '', '', 0, 0, 0, $2)`,
uid, uid,
uint32(time.Now().Unix()), uint32(time.Now().Unix()),
@@ -63,7 +63,7 @@ type character struct {
IsNewCharacter bool `db:"is_new_character"` IsNewCharacter bool `db:"is_new_character"`
Name string `db:"name"` Name string `db:"name"`
UnkDescString string `db:"unk_desc_string"` UnkDescString string `db:"unk_desc_string"`
HRP uint16 `db:"hrp"` HR uint16 `db:"hr"`
GR uint16 `db:"gr"` GR uint16 `db:"gr"`
WeaponType uint16 `db:"weapon_type"` WeaponType uint16 `db:"weapon_type"`
LastLogin uint32 `db:"last_login"` LastLogin uint32 `db:"last_login"`
@@ -71,7 +71,7 @@ type character struct {
func (s *Server) getCharactersForUser(uid uint32) ([]character, error) { func (s *Server) getCharactersForUser(uid uint32) ([]character, error) {
characters := make([]character, 0) 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 ORDER BY id", uid) err := s.db.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = $1 AND deleted = false ORDER BY id", uid)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -7,9 +7,10 @@ import (
_config "erupe-ce/config" _config "erupe-ce/config"
"erupe-ce/server/channelserver" "erupe-ce/server/channelserver"
"fmt" "fmt"
"go.uber.org/zap"
"strings" "strings"
"time" "time"
"go.uber.org/zap"
) )
func (s *Session) makeSignResponse(uid uint32) []byte { func (s *Session) makeSignResponse(uid uint32) []byte {
@@ -38,25 +39,24 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
return bf.Data() return bf.Data()
} }
bf.WriteUint8(uint8(SIGN_SUCCESS)) // resp_code if s.client == PS3 && (s.server.erupeConfig.PatchServerFile == "" || s.server.erupeConfig.PatchServerManifest == "") {
if (s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "") || s.client == PS3 { bf.WriteUint8(uint8(SIGN_EABORT))
bf.WriteUint8(2) return bf.Data()
} else {
bf.WriteUint8(0)
} }
bf.WriteUint8(uint8(SIGN_SUCCESS))
bf.WriteUint8(2) // patch server count
bf.WriteUint8(1) // entrance server count bf.WriteUint8(1) // entrance server count
bf.WriteUint8(uint8(len(chars))) bf.WriteUint8(uint8(len(chars)))
bf.WriteUint32(tokenID) bf.WriteUint32(tokenID)
bf.WriteBytes([]byte(sessToken)) bf.WriteBytes([]byte(sessToken))
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))
if s.client == PS3 { if s.client == PS3 {
ps.Uint8(bf, fmt.Sprintf(`ps3-%s.zerulight.cc`, s.server.erupeConfig.Language), false) ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerManifest), false)
ps.Uint8(bf, fmt.Sprintf(`ps3-%s.zerulight.cc`, s.server.erupeConfig.Language), false) ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerFile), false)
} else { } else {
if s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "" { ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false)
ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false) ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false)
ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false)
}
} }
if strings.Split(s.rawConn.RemoteAddr().String(), ":")[0] == "127.0.0.1" { if strings.Split(s.rawConn.RemoteAddr().String(), ":")[0] == "127.0.0.1" {
ps.Uint8(bf, fmt.Sprintf("127.0.0.1:%d", s.server.erupeConfig.Entrance.Port), false) ps.Uint8(bf, fmt.Sprintf("127.0.0.1:%d", s.server.erupeConfig.Entrance.Port), false)
@@ -70,14 +70,11 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
lastPlayed = char.ID lastPlayed = char.ID
} }
bf.WriteUint32(char.ID) bf.WriteUint32(char.ID)
// Exp, HR[x] is split by 0, 1, 30, 50, 99, 299, 998, 999
if s.server.erupeConfig.DebugOptions.MaxLauncherHR { if s.server.erupeConfig.DebugOptions.MaxLauncherHR {
bf.WriteUint16(999) bf.WriteUint16(999)
} else { } else {
bf.WriteUint16(char.HRP) bf.WriteUint16(char.HR)
} }
bf.WriteUint16(char.WeaponType) // Weapon, 0-13. bf.WriteUint16(char.WeaponType) // Weapon, 0-13.
bf.WriteUint32(char.LastLogin) // Last login date, unix timestamp in seconds. bf.WriteUint32(char.LastLogin) // Last login date, unix timestamp in seconds.
bf.WriteBool(char.IsFemale) // Sex, 0=male, 1=female. bf.WriteBool(char.IsFemale) // Sex, 0=male, 1=female.
@@ -139,7 +136,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
bf.WriteUint32(s.server.getLastCID(uid)) bf.WriteUint32(s.server.getLastCID(uid))
bf.WriteUint32(s.server.getUserRights(uid)) bf.WriteUint32(s.server.getUserRights(uid))
ps.Uint16(bf, "", false) // filters ps.Uint16(bf, "", false) // filters
if s.client == VITA || s.client == PS3 { if s.client == VITA || s.client == PS3 || s.client == PS4 {
var psnUser string var psnUser string
s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser) s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser)
bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true)) bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true))

View File

@@ -11,6 +11,7 @@ import (
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network" "erupe-ce/network"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -20,6 +21,7 @@ const (
PC100 client = iota PC100 client = iota
VITA VITA
PS3 PS3
PS4
WIIU WIIU
) )
@@ -56,6 +58,9 @@ func (s *Session) handlePacket(pkt []byte) error {
switch reqType[:len(reqType)-3] { switch reqType[:len(reqType)-3] {
case "DLTSKEYSIGN:", "DSGN:", "SIGN:": case "DLTSKEYSIGN:", "DSGN:", "SIGN:":
s.handleDSGN(bf) s.handleDSGN(bf)
case "PS4SGN:":
s.client = PS4
s.handlePSSGN(bf)
case "PS3SGN:": case "PS3SGN:":
s.client = PS3 s.client = PS3
s.handlePSSGN(bf) s.handlePSSGN(bf)
@@ -127,13 +132,16 @@ func (s *Session) handleWIIUSGN(bf *byteframe.ByteFrame) {
func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) { func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) {
// Prevent reading malformed request // Prevent reading malformed request
if len(bf.DataFromCurrent()) < 128 { if s.client != PS4 {
s.sendCode(SIGN_EABORT) if len(bf.DataFromCurrent()) < 128 {
return s.sendCode(SIGN_EABORT)
return
}
_ = bf.ReadNullTerminatedBytes() // VITA = 0000000256, PS3 = 0000000255
_ = bf.ReadBytes(2) // VITA = 1, PS3 = !
_ = bf.ReadBytes(82)
} }
_ = bf.ReadNullTerminatedBytes() // VITA = 0000000256, PS3 = 0000000255
_ = bf.ReadBytes(2) // VITA = 1, PS3 = !
_ = bf.ReadBytes(82)
s.psn = string(bf.ReadNullTerminatedBytes()) s.psn = string(bf.ReadNullTerminatedBytes())
var uid uint32 var uid uint32
err := s.server.db.QueryRow(`SELECT id FROM users WHERE psn_id = $1`, s.psn).Scan(&uid) err := s.server.db.QueryRow(`SELECT id FROM users WHERE psn_id = $1`, s.psn).Scan(&uid)