mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-02-06 01:57:38 +01:00
Merge branch 'refs/heads/main' into feature/hunting-tournament
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,4 +7,5 @@ savedata/*/
|
||||
*.exe
|
||||
*.lnk
|
||||
*.bat
|
||||
/docker/db-data
|
||||
/docker/db-data
|
||||
screenshots/*
|
||||
19
AUTHORS.md
19
AUTHORS.md
@@ -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
|
||||
The project is currently developed under https://github.com/ZeruLight/Erupe
|
||||
|
||||
## History of development
|
||||
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)
|
||||
* 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 is assigned for each commit within the git history, which is stored in these git repos:
|
||||
* https://github.com/ZeruLight/Erupe
|
||||
* https://github.com/Ellie42/Erupe
|
||||
* https://github.com/Ellie42/Erupe
|
||||
* https://github.com/ricochhet/Erupe-Legacy
|
||||
* 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
|
||||
if somebody can provide information, please contact us, so that we can make this history available.
|
||||
Note the divergence between Ellie42's branch and xl3lackout's where history has been lost.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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.
|
||||
12
README.md
12
README.md
@@ -1,6 +1,6 @@
|
||||
# Erupe
|
||||
|
||||
## Client Compatiblity
|
||||
## Client Compatibility
|
||||
### Platforms
|
||||
- PC
|
||||
- PlayStation 3
|
||||
@@ -34,15 +34,15 @@ If you want to modify or compile Erupe yourself, please read on.
|
||||
|
||||
## 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
|
||||
|
||||
We source control the following schemas:
|
||||
- Initialisation Schemas: These initialise the application database to a clean install from a specific version.
|
||||
- Update Schemas: These are update files they should be ran in order of version to get to the latest 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.
|
||||
- Bundled Schemas: These are demo reference files to allow servers to be able to roll their own shops, distributions gachas and scenarios set ups.
|
||||
- Initialization Schema: This initializes the application database to a specific version (9.1.0).
|
||||
- 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 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 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.
|
||||
|
||||
|
||||
175
common/mhfitem/mhfitem.go
Normal file
175
common/mhfitem/mhfitem.go
Normal 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()
|
||||
}
|
||||
@@ -5,18 +5,19 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var RNG = NewRNG()
|
||||
|
||||
// Generate returns an alphanumeric token of specified length
|
||||
func Generate(length int) string {
|
||||
rng := RNG()
|
||||
var chars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
b := make([]rune, length)
|
||||
for i := range b {
|
||||
b[i] = chars[rng.Intn(len(chars))]
|
||||
b[i] = chars[RNG.Intn(len(chars))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// RNG returns a new RNG generator
|
||||
func RNG() *rand.Rand {
|
||||
// NewRNG returns a new NewRNG generator
|
||||
func NewRNG() *rand.Rand {
|
||||
return rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
}
|
||||
|
||||
15
config.json
15
config.json
@@ -9,7 +9,13 @@
|
||||
],
|
||||
"PatchServerManifest": "",
|
||||
"PatchServerFile": "",
|
||||
"ScreenshotAPIURL": "",
|
||||
"Screenshots":{
|
||||
"Enabled":true,
|
||||
"Host":"127.0.0.1",
|
||||
"Port":8080,
|
||||
"OutputDir":"screenshots",
|
||||
"UploadQuality":100
|
||||
},
|
||||
"DeleteOnSaveCorruption": false,
|
||||
"ClientMode": "ZZ",
|
||||
"QuestCacheExpiry": 300,
|
||||
@@ -46,7 +52,8 @@
|
||||
}
|
||||
},
|
||||
"GameplayOptions": {
|
||||
"FeaturedWeapons": 1,
|
||||
"MinFeatureWeapons": 0,
|
||||
"MaxFeatureWeapons": 1,
|
||||
"MaximumNP": 100000,
|
||||
"MaximumRP": 50000,
|
||||
"MaximumFP": 120000,
|
||||
@@ -188,8 +195,8 @@
|
||||
"Enabled": true,
|
||||
"Port": 53312
|
||||
},
|
||||
"SignV2": {
|
||||
"Enabled": false,
|
||||
"API": {
|
||||
"Enabled": true,
|
||||
"Port": 8080,
|
||||
"PatchServer": "",
|
||||
"Banners": [],
|
||||
|
||||
@@ -75,7 +75,6 @@ type Config struct {
|
||||
LoginNotices []string // MHFML string of the login notices displayed
|
||||
PatchServerManifest string // Manifest 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
|
||||
ClientMode string
|
||||
RealClientMode Mode
|
||||
@@ -87,16 +86,18 @@ type Config struct {
|
||||
EarthID int32
|
||||
EarthMonsters []int32
|
||||
SaveDumps SaveDumpOptions
|
||||
DebugOptions DebugOptions
|
||||
GameplayOptions GameplayOptions
|
||||
Discord Discord
|
||||
Commands []Command
|
||||
Courses []Course
|
||||
Database Database
|
||||
Sign Sign
|
||||
SignV2 SignV2
|
||||
Channel Channel
|
||||
Entrance Entrance
|
||||
Screenshots ScreenshotsOptions
|
||||
|
||||
DebugOptions DebugOptions
|
||||
GameplayOptions GameplayOptions
|
||||
Discord Discord
|
||||
Commands []Command
|
||||
Courses []Course
|
||||
Database Database
|
||||
Sign Sign
|
||||
API API
|
||||
Channel Channel
|
||||
Entrance Entrance
|
||||
}
|
||||
|
||||
type SaveDumpOptions struct {
|
||||
@@ -105,6 +106,14 @@ type SaveDumpOptions struct {
|
||||
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.
|
||||
type DebugOptions struct {
|
||||
CleanDB bool // Automatically wipes the DB on server reset.
|
||||
@@ -132,7 +141,8 @@ type CapLinkOptions struct {
|
||||
|
||||
// GameplayOptions has various gameplay modifiers
|
||||
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
|
||||
MaximumRP uint16 // Maximum number of RP held by a player
|
||||
MaximumFP uint32 // Maximum number of FP held by a player
|
||||
@@ -227,29 +237,29 @@ type Sign struct {
|
||||
Port int
|
||||
}
|
||||
|
||||
// SignV2 holds the new sign server config
|
||||
type SignV2 struct {
|
||||
// API holds server config
|
||||
type API struct {
|
||||
Enabled bool
|
||||
Port int
|
||||
PatchServer string
|
||||
Banners []SignV2Banner
|
||||
Messages []SignV2Message
|
||||
Links []SignV2Link
|
||||
Banners []APISignBanner
|
||||
Messages []APISignMessage
|
||||
Links []APISignLink
|
||||
}
|
||||
|
||||
type SignV2Banner struct {
|
||||
type APISignBanner struct {
|
||||
Src string `json:"src"` // Displayed image URL
|
||||
Link string `json:"link"` // Link accessed on click
|
||||
}
|
||||
|
||||
type SignV2Message struct {
|
||||
type APISignMessage struct {
|
||||
Message string `json:"message"` // Displayed message
|
||||
Date int64 `json:"date"` // Displayed date
|
||||
Kind int `json:"kind"` // 0 for 'Default', 1 for 'New'
|
||||
Link string `json:"link"` // Link accessed on click
|
||||
}
|
||||
|
||||
type SignV2Link struct {
|
||||
type APISignLink struct {
|
||||
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.
|
||||
Link string `json:"link"` // Link accessed on click
|
||||
@@ -351,6 +361,10 @@ func LoadConfig() (*Config, error) {
|
||||
c.RealClientMode = ZZ
|
||||
}
|
||||
|
||||
if c.GameplayOptions.MinFeatureWeapons > c.GameplayOptions.MaxFeatureWeapons {
|
||||
c.GameplayOptions.MinFeatureWeapons = c.GameplayOptions.MaxFeatureWeapons
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ services:
|
||||
build:
|
||||
context: ../
|
||||
volumes:
|
||||
- ./config.json:/app/erupe/config.json
|
||||
- ./bin:/app/erupe/bin
|
||||
- ../config.json:/app/erupe/config.json
|
||||
- ../bin:/app/erupe/bin
|
||||
- ./savedata:/app/erupe/savedata
|
||||
ports:
|
||||
# (Make sure these match config.json)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
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
22
main.go
@@ -10,11 +10,11 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"erupe-ce/server/api"
|
||||
"erupe-ce/server/channelserver"
|
||||
"erupe-ce/server/discordbot"
|
||||
"erupe-ce/server/entranceserver"
|
||||
"erupe-ce/server/signserver"
|
||||
"erupe-ce/server/signv2server"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
@@ -181,21 +181,21 @@ func main() {
|
||||
}
|
||||
|
||||
// New Sign server
|
||||
var newSignServer *signv2server.Server
|
||||
if config.SignV2.Enabled {
|
||||
newSignServer = signv2server.NewServer(
|
||||
&signv2server.Config{
|
||||
var ApiServer *api.APIServer
|
||||
if config.API.Enabled {
|
||||
ApiServer = api.NewAPIServer(
|
||||
&api.Config{
|
||||
Logger: logger.Named("sign"),
|
||||
ErupeConfig: _config.ErupeConfig,
|
||||
DB: db,
|
||||
})
|
||||
err = newSignServer.Start()
|
||||
err = ApiServer.Start()
|
||||
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 {
|
||||
logger.Info("SignV2: Disabled")
|
||||
logger.Info("API: Disabled")
|
||||
}
|
||||
|
||||
var channels []*channelserver.Server
|
||||
@@ -273,8 +273,8 @@ func main() {
|
||||
signServer.Shutdown()
|
||||
}
|
||||
|
||||
if config.SignV2.Enabled {
|
||||
newSignServer.Shutdown()
|
||||
if config.API.Enabled {
|
||||
ApiServer.Shutdown()
|
||||
}
|
||||
|
||||
if config.Entrance.Enabled {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// MsgMhfEnumerateWarehouse represents the MSG_MHF_ENUMERATE_WAREHOUSE
|
||||
type MsgMhfEnumerateWarehouse struct {
|
||||
AckHandle uint32
|
||||
BoxType string
|
||||
BoxType uint8
|
||||
BoxIndex uint8
|
||||
}
|
||||
|
||||
@@ -23,15 +23,10 @@ func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID {
|
||||
// Parse parses the packet from binary
|
||||
func (m *MsgMhfEnumerateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
boxType := bf.ReadUint8()
|
||||
switch boxType {
|
||||
case 0:
|
||||
m.BoxType = "item"
|
||||
case 1:
|
||||
m.BoxType = "equip"
|
||||
}
|
||||
m.BoxType = bf.ReadUint8()
|
||||
m.BoxIndex = bf.ReadUint8()
|
||||
_ = bf.ReadUint16()
|
||||
bf.ReadUint8() // Zeroed
|
||||
bf.ReadUint8() // Zeroed
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
type MsgMhfOperateWarehouse struct {
|
||||
AckHandle uint32
|
||||
Operation uint8
|
||||
BoxType string
|
||||
BoxType uint8
|
||||
BoxIndex uint8
|
||||
Name string
|
||||
}
|
||||
@@ -27,17 +27,13 @@ func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID {
|
||||
func (m *MsgMhfOperateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.Operation = bf.ReadUint8()
|
||||
boxType := bf.ReadUint8()
|
||||
switch boxType {
|
||||
case 0:
|
||||
m.BoxType = "item"
|
||||
case 1:
|
||||
m.BoxType = "equip"
|
||||
}
|
||||
m.BoxType = bf.ReadUint8()
|
||||
m.BoxIndex = bf.ReadUint8()
|
||||
_ = bf.ReadUint8() // lenName
|
||||
_ = bf.ReadUint16() // Unk
|
||||
m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
|
||||
lenName := bf.ReadUint8()
|
||||
bf.ReadUint16() // Zeroed
|
||||
if lenName > 0 {
|
||||
m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ func (m *MsgMhfStampcardStamp) Opcode() network.PacketID {
|
||||
func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.HR = bf.ReadUint16()
|
||||
m.GR = bf.ReadUint16()
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G1 {
|
||||
m.GR = bf.ReadUint16()
|
||||
}
|
||||
m.Stamps = bf.ReadUint16()
|
||||
bf.ReadUint16() // Zeroed
|
||||
if _config.ErupeConfig.RealClientMode > _config.Z1 {
|
||||
|
||||
@@ -31,8 +31,8 @@ func (m *MsgMhfUpdateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.EntryCount = bf.ReadUint16()
|
||||
bf.ReadUint16() // Zeroed
|
||||
var temp Goocoo
|
||||
for i := 0; i < int(m.EntryCount); i++ {
|
||||
var temp Goocoo
|
||||
temp.Index = bf.ReadUint32()
|
||||
for j := 0; j < 22; j++ {
|
||||
temp.Data1 = append(temp.Data1, bf.ReadInt16())
|
||||
|
||||
@@ -2,24 +2,18 @@ package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"erupe-ce/common/mhfitem"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
type Item struct {
|
||||
Unk0 uint32
|
||||
ItemID uint16
|
||||
Amount uint16
|
||||
Unk1 uint32
|
||||
}
|
||||
|
||||
// MsgMhfUpdateGuildItem represents the MSG_MHF_UPDATE_GUILD_ITEM
|
||||
type MsgMhfUpdateGuildItem struct {
|
||||
AckHandle uint32
|
||||
GuildID uint32
|
||||
Items []Item
|
||||
AckHandle uint32
|
||||
GuildID uint32
|
||||
UpdatedItems []mhfitem.MHFItemStack
|
||||
}
|
||||
|
||||
// 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 {
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.GuildID = bf.ReadUint32()
|
||||
itemCount := int(bf.ReadUint16())
|
||||
changes := int(bf.ReadUint16())
|
||||
bf.ReadUint8() // Zeroed
|
||||
bf.ReadUint8() // Zeroed
|
||||
m.Items = make([]Item, itemCount)
|
||||
|
||||
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()
|
||||
for i := 0; i < changes; i++ {
|
||||
m.UpdatedItems = append(m.UpdatedItems, mhfitem.ReadWarehouseItem(bf))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"erupe-ce/common/mhfitem"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
@@ -10,8 +11,8 @@ import (
|
||||
|
||||
// MsgMhfUpdateUnionItem represents the MSG_MHF_UPDATE_UNION_ITEM
|
||||
type MsgMhfUpdateUnionItem struct {
|
||||
AckHandle uint32
|
||||
Items []Item
|
||||
AckHandle uint32
|
||||
UpdatedItems []mhfitem.MHFItemStack
|
||||
}
|
||||
|
||||
// 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
|
||||
func (m *MsgMhfUpdateUnionItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
itemCount := int(bf.ReadUint16())
|
||||
changes := int(bf.ReadUint16())
|
||||
bf.ReadUint8() // Zeroed
|
||||
bf.ReadUint8() // Zeroed
|
||||
m.Items = make([]Item, itemCount)
|
||||
|
||||
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()
|
||||
for i := 0; i < changes; i++ {
|
||||
m.UpdatedItems = append(m.UpdatedItems, mhfitem.ReadWarehouseItem(bf))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,25 +3,18 @@ package mhfpacket
|
||||
import (
|
||||
"errors"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/mhfitem"
|
||||
"erupe-ce/network"
|
||||
"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
|
||||
type MsgMhfUpdateWarehouse struct {
|
||||
AckHandle uint32
|
||||
BoxType string
|
||||
BoxIndex uint8
|
||||
Updates []WarehouseStack
|
||||
AckHandle uint32
|
||||
BoxType uint8
|
||||
BoxIndex uint8
|
||||
UpdatedItems []mhfitem.MHFItemStack
|
||||
UpdatedEquipment []mhfitem.MHFEquipment
|
||||
}
|
||||
|
||||
// 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
|
||||
func (m *MsgMhfUpdateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
boxType := bf.ReadUint8()
|
||||
switch boxType {
|
||||
case 0:
|
||||
m.BoxType = "item"
|
||||
case 1:
|
||||
m.BoxType = "equip"
|
||||
}
|
||||
m.BoxType = bf.ReadUint8()
|
||||
m.BoxIndex = bf.ReadUint8()
|
||||
changes := int(bf.ReadUint16())
|
||||
var stackUpdate WarehouseStack
|
||||
bf.ReadUint8() // Zeroed
|
||||
bf.ReadUint8() // Zeroed
|
||||
for i := 0; i < changes; i++ {
|
||||
switch boxType {
|
||||
switch m.BoxType {
|
||||
case 0:
|
||||
stackUpdate.ID = bf.ReadUint32()
|
||||
stackUpdate.Index = bf.ReadUint16()
|
||||
stackUpdate.ItemID = bf.ReadUint16()
|
||||
stackUpdate.Quantity = bf.ReadUint16()
|
||||
_ = bf.ReadUint16() // Unk
|
||||
m.Updates = append(m.Updates, stackUpdate)
|
||||
m.UpdatedItems = append(m.UpdatedItems, mhfitem.ReadWarehouseItem(bf))
|
||||
case 1:
|
||||
stackUpdate.ID = bf.ReadUint32()
|
||||
stackUpdate.Index = bf.ReadUint16()
|
||||
stackUpdate.EquipType = bf.ReadUint16()
|
||||
stackUpdate.ItemID = bf.ReadUint16()
|
||||
stackUpdate.Data = bf.ReadBytes(56)
|
||||
m.Updates = append(m.Updates, stackUpdate)
|
||||
m.UpdatedEquipment = append(m.UpdatedEquipment, mhfitem.ReadWarehouseEquipment(bf))
|
||||
}
|
||||
}
|
||||
_ = bf.ReadUint16()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,4 +2,12 @@ BEGIN;
|
||||
|
||||
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;
|
||||
6
schemas/patch-schema/20-reset-warehouses.sql
Normal file
6
schemas/patch-schema/20-reset-warehouses.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
BEGIN;
|
||||
|
||||
UPDATE guilds SET item_box=NULL;
|
||||
UPDATE users SET item_box=NULL;
|
||||
|
||||
END;
|
||||
5
schemas/patch-schema/21-rename-hrp.sql
Normal file
5
schemas/patch-schema/21-rename-hrp.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters RENAME hrp TO hr;
|
||||
|
||||
END;
|
||||
6
schemas/patch-schema/22-clan-changing-room.sql
Normal file
6
schemas/patch-schema/22-clan-changing-room.sql
Normal 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;
|
||||
6
schemas/patch-schema/fix-weekly-stamps.sql
Normal file
6
schemas/patch-schema/fix-weekly-stamps.sql
Normal 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;
|
||||
@@ -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;
|
||||
@@ -1,8 +1,8 @@
|
||||
package signv2server
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"erupe-ce/config"
|
||||
_config "erupe-ce/config"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -21,8 +21,8 @@ type Config struct {
|
||||
ErupeConfig *_config.Config
|
||||
}
|
||||
|
||||
// Server is the MHF custom launcher sign server.
|
||||
type Server struct {
|
||||
// APIServer is Erupes Standard API interface
|
||||
type APIServer struct {
|
||||
sync.Mutex
|
||||
logger *zap.Logger
|
||||
erupeConfig *_config.Config
|
||||
@@ -31,9 +31,9 @@ type Server struct {
|
||||
isShuttingDown bool
|
||||
}
|
||||
|
||||
// NewServer creates a new Server type.
|
||||
func NewServer(config *Config) *Server {
|
||||
s := &Server{
|
||||
// NewAPIServer creates a new Server type.
|
||||
func NewAPIServer(config *Config) *APIServer {
|
||||
s := &APIServer{
|
||||
logger: config.Logger,
|
||||
erupeConfig: config.ErupeConfig,
|
||||
db: config.DB,
|
||||
@@ -43,7 +43,7 @@ func NewServer(config *Config) *Server {
|
||||
}
|
||||
|
||||
// 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.
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/launcher", s.Launcher)
|
||||
@@ -52,9 +52,11 @@ func (s *Server) Start() error {
|
||||
r.HandleFunc("/character/create", s.CreateCharacter)
|
||||
r.HandleFunc("/character/delete", s.DeleteCharacter)
|
||||
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)
|
||||
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)
|
||||
go func() {
|
||||
@@ -74,7 +76,7 @@ func (s *Server) Start() error {
|
||||
}
|
||||
|
||||
// Shutdown exits the server gracefully.
|
||||
func (s *Server) Shutdown() {
|
||||
func (s *APIServer) Shutdown() {
|
||||
s.logger.Debug("Shutting down")
|
||||
|
||||
s.Lock()
|
||||
@@ -1,4 +1,4 @@
|
||||
package signv2server
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"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
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
@@ -32,7 +32,7 @@ func (s *Server) createNewUser(ctx context.Context, username string, password st
|
||||
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)
|
||||
var tid uint32
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -53,10 +53,10 @@ func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, err
|
||||
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
|
||||
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,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -68,17 +68,17 @@ func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character,
|
||||
err = s.db.GetContext(ctx, &character, `
|
||||
INSERT INTO characters (
|
||||
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)
|
||||
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()),
|
||||
)
|
||||
}
|
||||
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
|
||||
err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew)
|
||||
if err != nil {
|
||||
@@ -92,11 +92,11 @@ func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint
|
||||
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
|
||||
err := s.db.SelectContext(
|
||||
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
|
||||
WHERE user_id = $1 AND deleted = false AND is_new_character = false ORDER BY id ASC`,
|
||||
uid,
|
||||
@@ -107,7 +107,7 @@ func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Charac
|
||||
return characters, nil
|
||||
}
|
||||
|
||||
func (s *Server) getReturnExpiry(uid uint32) time.Time {
|
||||
func (s *APIServer) getReturnExpiry(uid uint32) time.Time {
|
||||
var returnExpiry, lastLogin time.Time
|
||||
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) {
|
||||
@@ -124,7 +124,7 @@ func (s *Server) getReturnExpiry(uid uint32) time.Time {
|
||||
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)
|
||||
result := make(map[string]interface{})
|
||||
err := row.MapScan(result)
|
||||
@@ -1,15 +1,24 @@
|
||||
package signv2server
|
||||
package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/server/channelserver"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/lib/pq"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -21,9 +30,9 @@ const (
|
||||
)
|
||||
|
||||
type LauncherResponse struct {
|
||||
Banners []_config.SignV2Banner `json:"banners"`
|
||||
Messages []_config.SignV2Message `json:"messages"`
|
||||
Links []_config.SignV2Link `json:"links"`
|
||||
Banners []_config.APISignBanner `json:"banners"`
|
||||
Messages []_config.APISignMessage `json:"messages"`
|
||||
Links []_config.APISignLink `json:"links"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
@@ -37,7 +46,7 @@ type Character struct {
|
||||
Name string `json:"name"`
|
||||
IsFemale bool `json:"isFemale" db:"is_female"`
|
||||
Weapon uint32 `json:"weapon" db:"weapon_type"`
|
||||
HR uint32 `json:"hr" db:"hrp"`
|
||||
HR uint32 `json:"hr" db:"hr"`
|
||||
GR uint32 `json:"gr"`
|
||||
LastLogin int32 `json:"lastLogin" db:"last_login"`
|
||||
}
|
||||
@@ -66,7 +75,7 @@ type ExportData struct {
|
||||
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{
|
||||
CurrentTS: uint32(channelserver.TimeAdjusted().Unix()),
|
||||
ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()),
|
||||
@@ -77,7 +86,7 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
|
||||
Token: userToken,
|
||||
},
|
||||
Characters: characters,
|
||||
PatchServer: s.erupeConfig.SignV2.PatchServer,
|
||||
PatchServer: s.erupeConfig.API.PatchServer,
|
||||
Notices: []string{},
|
||||
}
|
||||
if s.erupeConfig.DebugOptions.MaxLauncherHR {
|
||||
@@ -103,16 +112,16 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
|
||||
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
|
||||
respData.Banners = s.erupeConfig.SignV2.Banners
|
||||
respData.Messages = s.erupeConfig.SignV2.Messages
|
||||
respData.Links = s.erupeConfig.SignV2.Links
|
||||
respData.Banners = s.erupeConfig.API.Banners
|
||||
respData.Messages = s.erupeConfig.API.Messages
|
||||
respData.Links = s.erupeConfig.API.Links
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
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()
|
||||
var reqData struct {
|
||||
Username string `json:"username"`
|
||||
@@ -164,7 +173,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
|
||||
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()
|
||||
var reqData struct {
|
||||
Username string `json:"username"`
|
||||
@@ -204,7 +213,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
|
||||
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()
|
||||
var reqData struct {
|
||||
Token string `json:"token"`
|
||||
@@ -233,7 +242,7 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
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()
|
||||
var reqData struct {
|
||||
Token string `json:"token"`
|
||||
@@ -258,7 +267,7 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
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()
|
||||
var reqData struct {
|
||||
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")
|
||||
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
37
server/api/utils.go
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package channelserver
|
||||
import (
|
||||
"encoding/binary"
|
||||
"erupe-ce/common/mhfcourse"
|
||||
"erupe-ce/common/mhfitem"
|
||||
"erupe-ce/common/mhfmon"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/common/stringsupport"
|
||||
@@ -812,93 +813,33 @@ func handleMsgMhfEnumeratePrice(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfGetExtraInfo(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateUnionItem)
|
||||
var boxContents []byte
|
||||
bf := byteframe.NewByteFrame()
|
||||
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))
|
||||
bf.WriteBytes(make([]byte, 4))
|
||||
} else {
|
||||
if len(boxContents) == 0 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
func userGetItems(s *Session) []mhfitem.MHFItemStack {
|
||||
var data []byte
|
||||
var items []mhfitem.MHFItemStack
|
||||
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)
|
||||
if len(data) > 0 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
box.ReadUint16() // Unused
|
||||
for i := 0; i < int(numStacks); i++ {
|
||||
items = append(items, mhfitem.ReadWarehouseItem(box))
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateUnionItem)
|
||||
// Get item cache from DB
|
||||
var boxContents []byte
|
||||
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))
|
||||
}
|
||||
newStacks := mhfitem.DiffItemStacks(userGetItems(s), pkt.UpdatedItems)
|
||||
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)
|
||||
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) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCheckWeeklyStamp)
|
||||
weekCurrentStart := TimeWeekStart()
|
||||
weekNextStart := TimeWeekNext()
|
||||
var total, redeemed, updated uint16
|
||||
var nextClaim time.Time
|
||||
err := s.server.db.QueryRow(fmt.Sprintf("SELECT %s_next FROM stamps WHERE character_id=$1", pkt.StampType), s.charID).Scan(&nextClaim)
|
||||
var lastCheck time.Time
|
||||
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 {
|
||||
s.server.db.Exec("INSERT INTO stamps (character_id, hl_next, ex_next) VALUES ($1, $2, $2)", s.charID, weekNextStart)
|
||||
nextClaim = weekNextStart
|
||||
lastCheck = TimeAdjusted()
|
||||
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
|
||||
}
|
||||
|
||||
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.WriteUint16(total)
|
||||
bf.WriteUint16(redeemed)
|
||||
bf.WriteUint16(updated)
|
||||
bf.WriteUint32(0) // Unk
|
||||
bf.WriteUint32(uint32(weekCurrentStart.Unix()))
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint32(uint32(TimeWeekStart().Unix()))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeWeeklyStamp)
|
||||
var total, redeemed uint16
|
||||
var tktStack mhfpacket.WarehouseStack
|
||||
if pkt.Unk1 == 0xA { // Yearly Sub Ex
|
||||
var tktStack mhfitem.MHFItemStack
|
||||
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)
|
||||
tktStack = mhfpacket.WarehouseStack{ItemID: 0x08A2, Quantity: 1}
|
||||
tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 2210}, Quantity: 1}
|
||||
} 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)
|
||||
if pkt.StampType == "hl" {
|
||||
tktStack = mhfpacket.WarehouseStack{ItemID: 0x065E, Quantity: 5}
|
||||
tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 1630}, Quantity: 5}
|
||||
} 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.WriteUint16(total)
|
||||
bf.WriteUint16(redeemed)
|
||||
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()))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
@@ -1103,9 +1048,11 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStampcardStamp)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(pkt.HR)
|
||||
bf.WriteUint16(pkt.GR)
|
||||
var stamps uint16
|
||||
_ = 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)
|
||||
stamps += pkt.Stamps
|
||||
bf.WriteUint16(stamps)
|
||||
@@ -1115,13 +1062,13 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint16(pkt.Reward2)
|
||||
bf.WriteUint16(pkt.Item2)
|
||||
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 {
|
||||
bf.WriteUint16(1)
|
||||
bf.WriteUint16(pkt.Reward1)
|
||||
bf.WriteUint16(pkt.Item1)
|
||||
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 {
|
||||
bf.WriteBytes(make([]byte, 8))
|
||||
}
|
||||
|
||||
@@ -7,35 +7,47 @@ import (
|
||||
"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) {
|
||||
//Post Screenshot pauses till this succeedes
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBbsUserStatus)
|
||||
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)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
// Checks the status of Bultin Board Server to see if authenticated
|
||||
func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBbsSnsStatus)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(200)
|
||||
bf.WriteUint32(401)
|
||||
bf.WriteUint32(401)
|
||||
bf.WriteUint32(200) //200 Success //4XX Authentication has expired Please re-authenticate //5XX
|
||||
bf.WriteUint32(401) //unk http status?
|
||||
bf.WriteUint32(401) //unk http status?
|
||||
bf.WriteUint32(0)
|
||||
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) {
|
||||
pkt := p.(*mhfpacket.MsgMhfApplyBbsArticle)
|
||||
bf := byteframe.NewByteFrame()
|
||||
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.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())
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/mhfcourse"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
@@ -92,10 +93,11 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
|
||||
if mhfcourse.CourseExists(30, s.courses) {
|
||||
cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime
|
||||
}
|
||||
bf.WriteUint32(cafeTime) // Total cafe time
|
||||
bf.WriteUint16(0)
|
||||
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
|
||||
|
||||
bf.WriteUint32(cafeTime)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.ZZ {
|
||||
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())
|
||||
}
|
||||
|
||||
|
||||
@@ -478,7 +478,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
m := binpacket.MsgBinChat{
|
||||
Type: BinaryMessageTypeChat,
|
||||
Flags: 4,
|
||||
Message: fmt.Sprintf(`%d`, token.RNG().Intn(100)+1),
|
||||
Message: fmt.Sprintf(`%d`, token.RNG.Intn(100)+1),
|
||||
SenderName: author,
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
@@ -25,7 +25,7 @@ const (
|
||||
pGardenData // +68
|
||||
pWeaponType // +1
|
||||
pWeaponID // +2
|
||||
pHRP // +2
|
||||
pHR // +2
|
||||
pGRP // +4
|
||||
pKQF // +8
|
||||
lBookshelfData
|
||||
@@ -47,7 +47,7 @@ type CharacterSaveData struct {
|
||||
GardenData []byte
|
||||
WeaponType uint8
|
||||
WeaponID uint16
|
||||
HRP uint16
|
||||
HR uint16
|
||||
GR uint16
|
||||
KQF []byte
|
||||
|
||||
@@ -63,7 +63,7 @@ func getPointers() map[SavePointer]int {
|
||||
pointers[pWeaponType] = 128789
|
||||
pointers[pHouseTier] = 129900
|
||||
pointers[pToreData] = 130228
|
||||
pointers[pHRP] = 130550
|
||||
pointers[pHR] = 130550
|
||||
pointers[pGRP] = 130556
|
||||
pointers[pHouseData] = 130561
|
||||
pointers[pBookshelfData] = 139928
|
||||
@@ -78,10 +78,10 @@ func getPointers() map[SavePointer]int {
|
||||
pointers[pWeaponType] = 92789
|
||||
pointers[pHouseTier] = 93900
|
||||
pointers[pToreData] = 94228
|
||||
pointers[pHRP] = 94550
|
||||
pointers[pHR] = 94550
|
||||
pointers[pGRP] = 94556
|
||||
pointers[pHouseData] = 94561
|
||||
pointers[pBookshelfData] = 103928
|
||||
pointers[pBookshelfData] = 89118 // TODO: fix bookshelf data pointer
|
||||
pointers[pGalleryData] = 104064
|
||||
pointers[pGardenData] = 106424
|
||||
pointers[pRP] = 106614
|
||||
@@ -91,9 +91,9 @@ func getPointers() map[SavePointer]int {
|
||||
pointers[pWeaponType] = 60789
|
||||
pointers[pHouseTier] = 61900
|
||||
pointers[pToreData] = 62228
|
||||
pointers[pHRP] = 62550
|
||||
pointers[pHR] = 62550
|
||||
pointers[pHouseData] = 62561
|
||||
pointers[pBookshelfData] = 57118 // This pointer only half works
|
||||
pointers[pBookshelfData] = 57118 // TODO: fix bookshelf data pointer
|
||||
pointers[pGalleryData] = 72064
|
||||
pointers[pGardenData] = 74424
|
||||
pointers[pRP] = 74614
|
||||
@@ -102,9 +102,9 @@ func getPointers() map[SavePointer]int {
|
||||
pointers[pWeaponType] = 12789
|
||||
pointers[pHouseTier] = 13900
|
||||
pointers[pToreData] = 14228
|
||||
pointers[pHRP] = 14550
|
||||
pointers[pHR] = 14550
|
||||
pointers[pHouseData] = 14561
|
||||
pointers[pBookshelfData] = 9118 // Probably same here
|
||||
pointers[pBookshelfData] = 9118 // TODO: fix bookshelf data pointer
|
||||
pointers[pGalleryData] = 24064
|
||||
pointers[pGardenData] = 26424
|
||||
pointers[pRP] = 26614
|
||||
@@ -174,8 +174,8 @@ func (save *CharacterSaveData) Save(s *Session) {
|
||||
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
|
||||
`, save.compSave, save.HRP, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID)
|
||||
_, 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.HR, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID)
|
||||
if err != nil {
|
||||
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.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
|
||||
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 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])))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ func handleMsgMhfGetWeeklySchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
var temp activeFeature
|
||||
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() {
|
||||
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
|
||||
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)
|
||||
var result int
|
||||
for len(nums) < count {
|
||||
rng := token.RNG()
|
||||
num := rng.Intn(_max)
|
||||
num := token.RNG.Intn(_max)
|
||||
exist := false
|
||||
for _, v := range nums {
|
||||
if v == num {
|
||||
|
||||
@@ -559,7 +559,7 @@ func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
team := uint32(token.RNG().Intn(2))
|
||||
team := uint32(token.RNG.Intn(2))
|
||||
switch team {
|
||||
case 0:
|
||||
s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'blue')", guild.ID)
|
||||
|
||||
@@ -3,9 +3,9 @@ package channelserver
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"erupe-ce/common/mhfitem"
|
||||
_config "erupe-ce/config"
|
||||
"fmt"
|
||||
"math"
|
||||
@@ -51,6 +51,8 @@ type Guild struct {
|
||||
MemberCount uint16 `db:"member_count"`
|
||||
RankRP uint32 `db:"rank_rp"`
|
||||
EventRP uint32 `db:"event_rp"`
|
||||
RoomRP uint16 `db:"room_rp"`
|
||||
RoomExpiry time.Time `db:"room_expiry"`
|
||||
Comment string `db:"comment"`
|
||||
PugiName1 string `db:"pugi_name_1"`
|
||||
PugiName2 string `db:"pugi_name_2"`
|
||||
@@ -153,6 +155,8 @@ SELECT
|
||||
g.name,
|
||||
rank_rp,
|
||||
event_rp,
|
||||
room_rp,
|
||||
COALESCE(room_expiry, '1970-01-01') AS room_expiry,
|
||||
main_motto,
|
||||
sub_motto,
|
||||
created_at,
|
||||
@@ -706,7 +710,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
bf.WriteUint32(uint32(response))
|
||||
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:
|
||||
s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID)
|
||||
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
|
||||
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:
|
||||
// TODO: Where does this go?
|
||||
quantity := uint16(pkt.Data1.ReadUint32())
|
||||
bf.WriteBytes(handleDonateRP(s, quantity, guild, 2))
|
||||
case mhfpacket.OperateGuildDonateEvent:
|
||||
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?
|
||||
s.server.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, quantity, s.charID)
|
||||
case mhfpacket.OperateGuildEventExchange:
|
||||
@@ -794,20 +799,37 @@ func handleChangePugi(s *Session, outfit uint8, guild *Guild, num int) {
|
||||
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.WriteUint32(0)
|
||||
saveData, err := GetCharacterSaveData(s, s.charID)
|
||||
if err != nil {
|
||||
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(¤tRP)
|
||||
if currentRP+amount >= 30 {
|
||||
amount = 30 - currentRP
|
||||
resetRoom = true
|
||||
}
|
||||
}
|
||||
saveData.RP -= amount
|
||||
saveData.Save(s)
|
||||
updateSQL := "UPDATE guilds SET rank_rp = rank_rp + $1 WHERE id = $2"
|
||||
if isEvent {
|
||||
updateSQL = "UPDATE guilds SET event_rp = event_rp + $1 WHERE id = $2"
|
||||
switch _type {
|
||||
case 0:
|
||||
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.WriteUint32(uint32(saveData.RP))
|
||||
return bf.Data()
|
||||
@@ -1001,8 +1023,8 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint8(limit)
|
||||
|
||||
bf.WriteUint32(55000)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint16(0) // Changing Room RP
|
||||
bf.WriteUint32(uint32(guild.RoomExpiry.Unix()))
|
||||
bf.WriteUint16(guild.RoomRP)
|
||||
bf.WriteUint16(0) // Ignored
|
||||
|
||||
if guild.AllianceID > 0 {
|
||||
@@ -1075,7 +1097,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
for _, applicant := range applicants {
|
||||
bf.WriteUint32(applicant.CharID)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint16(applicant.HRP)
|
||||
bf.WriteUint16(applicant.HR)
|
||||
bf.WriteUint16(applicant.GR)
|
||||
ps.Uint8(bf, applicant.Name, true)
|
||||
}
|
||||
@@ -1435,7 +1457,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
for _, member := range guildMembers {
|
||||
bf.WriteUint32(member.CharID)
|
||||
bf.WriteUint16(member.HRP)
|
||||
bf.WriteUint16(member.HR)
|
||||
if s.server.erupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteUint16(member.GR)
|
||||
}
|
||||
@@ -1554,100 +1576,34 @@ func handleMsgMhfGetGuildTargetMemberNum(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem)
|
||||
var boxContents []byte
|
||||
bf := byteframe.NewByteFrame()
|
||||
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))
|
||||
bf.WriteBytes(make([]byte, 4))
|
||||
} else {
|
||||
if len(boxContents) == 0 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
func guildGetItems(s *Session, guildID uint32) []mhfitem.MHFItemStack {
|
||||
var data []byte
|
||||
var items []mhfitem.MHFItemStack
|
||||
s.server.db.QueryRow(`SELECT item_box FROM guilds WHERE id=$1`, guildID).Scan(&data)
|
||||
if len(data) > 0 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
box.ReadUint16() // Unused
|
||||
for i := 0; i < int(numStacks); i++ {
|
||||
items = append(items, mhfitem.ReadWarehouseItem(box))
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return items
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
ItemId uint16
|
||||
Amount uint16
|
||||
func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem)
|
||||
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) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateGuildItem)
|
||||
|
||||
// Get item cache from DB
|
||||
var boxContents []byte
|
||||
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})
|
||||
newStacks := mhfitem.DiffItemStacks(guildGetItems(s, pkt.GuildID), pkt.UpdatedItems)
|
||||
s.server.db.Exec(`UPDATE guilds SET item_box=$1 WHERE id=$2`, mhfitem.SerializeWarehouseItems(newStacks), pkt.GuildID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -22,7 +22,7 @@ type GuildMember struct {
|
||||
Recruiter bool `db:"recruiter"`
|
||||
AvoidLeadership bool `db:"avoid_leadership"`
|
||||
IsLeader bool `db:"is_leader"`
|
||||
HRP uint16 `db:"hrp"`
|
||||
HR uint16 `db:"hr"`
|
||||
GR uint16 `db:"gr"`
|
||||
WeaponID uint16 `db:"weapon_id"`
|
||||
WeaponType uint8 `db:"weapon_type"`
|
||||
@@ -74,7 +74,7 @@ SELECT * FROM (
|
||||
c.last_login,
|
||||
COALESCE(recruiter, false) AS recruiter,
|
||||
COALESCE(avoid_leadership, false) AS avoid_leadership,
|
||||
c.hrp,
|
||||
c.hr,
|
||||
c.gr,
|
||||
c.weapon_id,
|
||||
c.weapon_type,
|
||||
|
||||
@@ -204,7 +204,7 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
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
|
||||
JOIN characters c ON c.id = ga.character_id
|
||||
WHERE ga.guild_id = $1 AND ga.application_type = 'invited'
|
||||
@@ -230,9 +230,9 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
|
||||
for rows.Next() {
|
||||
var charName string
|
||||
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 {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
@@ -246,8 +246,8 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint32(actorID)
|
||||
bf.WriteUint32(charID)
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
bf.WriteUint16(hrp) // HR?
|
||||
bf.WriteUint16(gr) // GR?
|
||||
bf.WriteUint16(HR) // HR?
|
||||
bf.WriteUint16(GR) // GR?
|
||||
bf.WriteBytes(stringsupport.PaddedString(charName, 32, true))
|
||||
count++
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/mhfitem"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/common/token"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
@@ -45,7 +47,7 @@ func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
type HouseData struct {
|
||||
CharID uint32 `db:"id"`
|
||||
HRP uint16 `db:"hrp"`
|
||||
HR uint16 `db:"hr"`
|
||||
GR uint16 `db:"gr"`
|
||||
Name string `db:"name"`
|
||||
HouseState uint8 `db:"house_state"`
|
||||
@@ -57,7 +59,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(0)
|
||||
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`
|
||||
switch pkt.Method {
|
||||
case 1:
|
||||
@@ -90,7 +92,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
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`
|
||||
house := HouseData{}
|
||||
rows, _ := s.server.db.Queryx(houseQuery, fmt.Sprintf(`%%%s%%`, pkt.Name))
|
||||
@@ -118,7 +120,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteUint16(house.HRP)
|
||||
bf.WriteUint16(house.HR)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteUint16(house.GR)
|
||||
}
|
||||
@@ -406,7 +408,12 @@ func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
case 1:
|
||||
bf.WriteUint8(0)
|
||||
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:
|
||||
bf.WriteUint32(0) // Usage renewal time, >1 = disabled
|
||||
bf.WriteUint16(10000) // Usages
|
||||
@@ -424,81 +431,63 @@ func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func addWarehouseGift(s *Session, boxType string, giftStack mhfpacket.WarehouseStack) {
|
||||
giftBox := getWarehouseBox(s, boxType, 10)
|
||||
if boxType == "item" {
|
||||
exists := false
|
||||
for i, stack := range giftBox {
|
||||
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 addWarehouseItem(s *Session, item mhfitem.MHFItemStack) {
|
||||
giftBox := warehouseGetItems(s, 10)
|
||||
item.WarehouseID = token.RNG.Uint32()
|
||||
giftBox = append(giftBox, item)
|
||||
s.server.db.Exec("UPDATE warehouse SET item10=$1 WHERE character_id=$2", mhfitem.SerializeWarehouseItems(giftBox), 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
|
||||
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 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
stacks := make([]mhfpacket.WarehouseStack, numStacks)
|
||||
box.ReadUint16() // Unused
|
||||
for i := 0; i < int(numStacks); i++ {
|
||||
if boxType == "item" {
|
||||
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)
|
||||
}
|
||||
items = append(items, mhfitem.ReadWarehouseItem(box))
|
||||
}
|
||||
return stacks
|
||||
} else {
|
||||
return make([]mhfpacket.WarehouseStack, 0)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func boxToBytes(stacks []mhfpacket.WarehouseStack, boxType string) []byte {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(len(stacks)))
|
||||
for i, stack := range stacks {
|
||||
if boxType == "item" {
|
||||
bf.WriteUint32(stack.ID)
|
||||
bf.WriteUint16(uint16(i + 1))
|
||||
bf.WriteUint16(stack.ItemID)
|
||||
bf.WriteUint16(stack.Quantity)
|
||||
bf.WriteUint16(0)
|
||||
} else {
|
||||
bf.WriteUint32(stack.ID)
|
||||
bf.WriteUint16(uint16(i + 1))
|
||||
bf.WriteUint16(stack.EquipType)
|
||||
bf.WriteUint16(stack.ItemID)
|
||||
bf.WriteBytes(stack.Data)
|
||||
func warehouseGetEquipment(s *Session, index uint8) []mhfitem.MHFEquipment {
|
||||
var data []byte
|
||||
var equipment []mhfitem.MHFEquipment
|
||||
s.server.db.QueryRow(fmt.Sprintf(`SELECT equip%d FROM warehouse WHERE character_id=$1`, index), s.charID).Scan(&data)
|
||||
if len(data) > 0 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
box.ReadUint16() // Unused
|
||||
for i := 0; i < int(numStacks); i++ {
|
||||
equipment = append(equipment, mhfitem.ReadWarehouseEquipment(box))
|
||||
}
|
||||
}
|
||||
bf.WriteUint16(0)
|
||||
return bf.Data()
|
||||
return equipment
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateWarehouse)
|
||||
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
|
||||
if len(box) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, boxToBytes(box, pkt.BoxType))
|
||||
bf := byteframe.NewByteFrame()
|
||||
switch 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 {
|
||||
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) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateWarehouse)
|
||||
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
|
||||
// Update existing stacks
|
||||
var newStacks []mhfpacket.WarehouseStack
|
||||
for _, update := range pkt.Updates {
|
||||
exists := false
|
||||
if pkt.BoxType == "item" {
|
||||
for i, stack := range box {
|
||||
if stack.Index == update.Index {
|
||||
switch pkt.BoxType {
|
||||
case 0:
|
||||
newStacks := mhfitem.DiffItemStacks(warehouseGetItems(s, pkt.BoxIndex), pkt.UpdatedItems)
|
||||
s.server.db.Exec(fmt.Sprintf(`UPDATE warehouse SET item%d=$1 WHERE character_id=$2`, pkt.BoxIndex), mhfitem.SerializeWarehouseItems(newStacks), s.charID)
|
||||
case 1:
|
||||
var fEquip []mhfitem.MHFEquipment
|
||||
oEquips := warehouseGetEquipment(s, pkt.BoxIndex)
|
||||
for _, uEquip := range pkt.UpdatedEquipment {
|
||||
exists := false
|
||||
for i := range oEquips {
|
||||
if oEquips[i].WarehouseID == uEquip.WarehouseID {
|
||||
exists = true
|
||||
box[i].Quantity = update.Quantity
|
||||
// Will set removed items to 0
|
||||
oEquips[i].ItemID = uEquip.ItemID
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i, stack := range box {
|
||||
if stack.Index == update.Index {
|
||||
exists = true
|
||||
box[i].ItemID = update.ItemID
|
||||
break
|
||||
}
|
||||
if !exists {
|
||||
uEquip.WarehouseID = token.RNG.Uint32()
|
||||
fEquip = append(fEquip, uEquip)
|
||||
}
|
||||
}
|
||||
if exists == false {
|
||||
newStacks = append(newStacks, update)
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
for _, oEquip := range oEquips {
|
||||
if oEquip.ItemID > 0 {
|
||||
fEquip = append(fEquip, oEquip)
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
|
||||
bf.WriteBool(true)
|
||||
}
|
||||
bf.WriteUint16(0) // Unk
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G1 {
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G2 {
|
||||
bf.WriteUint32(mark)
|
||||
}
|
||||
bf.WriteUint16(0) // Unk
|
||||
@@ -578,7 +578,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
tuneValues = temp
|
||||
|
||||
tuneLimit := 770
|
||||
if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G1 {
|
||||
tuneLimit = 256
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G3 {
|
||||
tuneLimit = 283
|
||||
|
||||
@@ -148,9 +148,24 @@ func removeSessionFromStage(s *Session) {
|
||||
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) {
|
||||
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.
|
||||
if s.stage != nil {
|
||||
s.stage.Lock()
|
||||
@@ -175,6 +190,12 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
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 {
|
||||
delete(s.stage.reservedClientSlots, s.charID)
|
||||
}
|
||||
@@ -188,6 +209,12 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
for _, c := range s.Channels {
|
||||
for _, session := range c.sessions {
|
||||
|
||||
@@ -59,7 +59,7 @@ func NewStage(ID string) *Stage {
|
||||
objects: make(map[uint32]*Object),
|
||||
objectIndex: 0,
|
||||
rawBinaryData: make(map[stageBinaryKey][]byte),
|
||||
maxPlayers: 4,
|
||||
maxPlayers: 127,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package discordbot
|
||||
|
||||
import (
|
||||
"erupe-ce/config"
|
||||
_config "erupe-ce/config"
|
||||
"regexp"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"go.uber.org/zap"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var Commands = []*discordgo.ApplicationCommand{
|
||||
@@ -113,7 +114,6 @@ func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReplaceTextAll(text string, regex *regexp.Regexp, handler func(input string) string) string {
|
||||
result := regex.ReplaceAllFunc([]byte(text), func(s []byte) []byte {
|
||||
input := regex.ReplaceAllString(string(s), `$1`)
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s *Server) newUserChara(uid uint32) error {
|
||||
_, err = s.db.Exec(`
|
||||
INSERT INTO characters (
|
||||
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)`,
|
||||
uid,
|
||||
uint32(time.Now().Unix()),
|
||||
@@ -63,7 +63,7 @@ type character struct {
|
||||
IsNewCharacter bool `db:"is_new_character"`
|
||||
Name string `db:"name"`
|
||||
UnkDescString string `db:"unk_desc_string"`
|
||||
HRP uint16 `db:"hrp"`
|
||||
HR uint16 `db:"hr"`
|
||||
GR uint16 `db:"gr"`
|
||||
WeaponType uint16 `db:"weapon_type"`
|
||||
LastLogin uint32 `db:"last_login"`
|
||||
@@ -71,7 +71,7 @@ type character struct {
|
||||
|
||||
func (s *Server) getCharactersForUser(uid uint32) ([]character, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/server/channelserver"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *Session) makeSignResponse(uid uint32) []byte {
|
||||
@@ -38,25 +39,24 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
|
||||
return bf.Data()
|
||||
}
|
||||
|
||||
bf.WriteUint8(uint8(SIGN_SUCCESS)) // resp_code
|
||||
if (s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "") || s.client == PS3 {
|
||||
bf.WriteUint8(2)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
if s.client == PS3 && (s.server.erupeConfig.PatchServerFile == "" || s.server.erupeConfig.PatchServerManifest == "") {
|
||||
bf.WriteUint8(uint8(SIGN_EABORT))
|
||||
return bf.Data()
|
||||
}
|
||||
|
||||
bf.WriteUint8(uint8(SIGN_SUCCESS))
|
||||
bf.WriteUint8(2) // patch server count
|
||||
bf.WriteUint8(1) // entrance server count
|
||||
bf.WriteUint8(uint8(len(chars)))
|
||||
bf.WriteUint32(tokenID)
|
||||
bf.WriteBytes([]byte(sessToken))
|
||||
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))
|
||||
if s.client == PS3 {
|
||||
ps.Uint8(bf, fmt.Sprintf(`ps3-%s.zerulight.cc`, s.server.erupeConfig.Language), 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.PatchServerManifest), false)
|
||||
ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerFile), false)
|
||||
} else {
|
||||
if s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "" {
|
||||
ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false)
|
||||
ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false)
|
||||
}
|
||||
ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false)
|
||||
ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false)
|
||||
}
|
||||
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)
|
||||
@@ -70,14 +70,11 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
|
||||
lastPlayed = 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 {
|
||||
bf.WriteUint16(999)
|
||||
} else {
|
||||
bf.WriteUint16(char.HRP)
|
||||
bf.WriteUint16(char.HR)
|
||||
}
|
||||
|
||||
bf.WriteUint16(char.WeaponType) // Weapon, 0-13.
|
||||
bf.WriteUint32(char.LastLogin) // Last login date, unix timestamp in seconds.
|
||||
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.getUserRights(uid))
|
||||
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
|
||||
s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser)
|
||||
bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true))
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -20,6 +21,7 @@ const (
|
||||
PC100 client = iota
|
||||
VITA
|
||||
PS3
|
||||
PS4
|
||||
WIIU
|
||||
)
|
||||
|
||||
@@ -56,6 +58,9 @@ func (s *Session) handlePacket(pkt []byte) error {
|
||||
switch reqType[:len(reqType)-3] {
|
||||
case "DLTSKEYSIGN:", "DSGN:", "SIGN:":
|
||||
s.handleDSGN(bf)
|
||||
case "PS4SGN:":
|
||||
s.client = PS4
|
||||
s.handlePSSGN(bf)
|
||||
case "PS3SGN:":
|
||||
s.client = PS3
|
||||
s.handlePSSGN(bf)
|
||||
@@ -127,13 +132,16 @@ func (s *Session) handleWIIUSGN(bf *byteframe.ByteFrame) {
|
||||
|
||||
func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) {
|
||||
// Prevent reading malformed request
|
||||
if len(bf.DataFromCurrent()) < 128 {
|
||||
s.sendCode(SIGN_EABORT)
|
||||
return
|
||||
if s.client != PS4 {
|
||||
if len(bf.DataFromCurrent()) < 128 {
|
||||
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())
|
||||
var uid uint32
|
||||
err := s.server.db.QueryRow(`SELECT id FROM users WHERE psn_id = $1`, s.psn).Scan(&uid)
|
||||
|
||||
Reference in New Issue
Block a user