Item Distribution

Doing the PR for wish, it's his code. Also this is the one used by Chakratos' Save Manager.

P.S.: Sadly it is still crashing for some reason.
This commit is contained in:
Malckyor
2022-06-15 08:11:03 +09:00
committed by GitHub
parent 2d7f55600f
commit f05391ecba
4 changed files with 157 additions and 61 deletions

26
Erupe/distitem.sql Normal file
View File

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

View File

@@ -9,8 +9,8 @@ import (
// MsgMhfApplyDistItem represents the MSG_MHF_APPLY_DIST_ITEM // MsgMhfApplyDistItem represents the MSG_MHF_APPLY_DIST_ITEM
type MsgMhfApplyDistItem struct { type MsgMhfApplyDistItem struct {
AckHandle uint32 AckHandle uint32
Unk0 uint8 DistributionType uint8
RequestType uint32 DistributionID uint32
Unk2 uint32 Unk2 uint32
Unk3 uint32 Unk3 uint32
} }
@@ -23,8 +23,8 @@ func (m *MsgMhfApplyDistItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfApplyDistItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfApplyDistItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8() m.DistributionType = bf.ReadUint8()
m.RequestType = bf.ReadUint32() m.DistributionID = bf.ReadUint32()
m.Unk2 = bf.ReadUint32() m.Unk2 = bf.ReadUint32()
m.Unk3 = bf.ReadUint32() m.Unk3 = bf.ReadUint32()
return nil return nil
@@ -33,9 +33,9 @@ func (m *MsgMhfApplyDistItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.
func (m *MsgMhfApplyDistItem) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfApplyDistItem) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
bf.WriteUint32(m.AckHandle) bf.WriteUint32(m.AckHandle)
bf.WriteUint8(m.Unk0) bf.WriteUint8(m.DistributionType)
bf.WriteUint32(m.RequestType) bf.WriteUint32(m.DistributionID)
bf.WriteUint32(m.Unk2) bf.WriteUint32(m.Unk2)
bf.WriteUint32(m.Unk3) bf.WriteUint32(m.Unk3)
return nil return nil
} }

View File

@@ -1,7 +1,7 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"github.com/Solenataris/Erupe/network/clientctx" "github.com/Solenataris/Erupe/network/clientctx"
"github.com/Solenataris/Erupe/network" "github.com/Solenataris/Erupe/network"
@@ -12,7 +12,7 @@ import (
type MsgMhfGetDistDescription struct{ type MsgMhfGetDistDescription struct{
AckHandle uint32 AckHandle uint32
Unk0 uint8 Unk0 uint8
EntryID uint32 DistributionID uint32
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -24,10 +24,10 @@ func (m *MsgMhfGetDistDescription) Opcode() network.PacketID {
func (m *MsgMhfGetDistDescription) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfGetDistDescription) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8() m.Unk0 = bf.ReadUint8()
m.EntryID = bf.ReadUint32() m.DistributionID = bf.ReadUint32()
return nil return nil
} }
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.
func (m *MsgMhfGetDistDescription) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfGetDistDescription) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED") return errors.New("NOT IMPLEMENTED")
} }

View File

@@ -1,67 +1,137 @@
package channelserver package channelserver
import ( import (
"encoding/hex"
// "io/ioutil"
// "path/filepath"
"github.com/Solenataris/Erupe/network/mhfpacket" "github.com/Solenataris/Erupe/network/mhfpacket"
"github.com/Solenataris/Erupe/common/stringsupport"
"github.com/Andoryuuta/byteframe"
"go.uber.org/zap"
) )
func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { type ItemDist struct {
pkt := p.(*mhfpacket.MsgMhfEnumerateDistItem) ID uint32 `db:"id"`
// uint16 number of entries Deadline uint32 `db:"deadline"`
// 446 entry block TimesAcceptable uint16 `db:"times_acceptable"`
// uint32 claimID TimesAccepted uint16 `db:"times_accepted"`
// 00 00 00 00 00 00 MinHR uint16 `db:"min_hr"`
// uint16 timesClaimable MaxHR uint16 `db:"max_hr"`
// 00 00 00 00 FF FF FF FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00 00 00 MinSR uint16 `db:"min_sr"`
// uint8 stringLength MaxSR uint16 `db:"max_sr"`
// string nullTermString MinGR uint16 `db:"min_gr"`
data, _ := hex.DecodeString("0001000000010000000000000000002000000000FFFFFFFFFFFFFFFFFFFFFFFF0000000000000000002F323020426F7820457870616E73696F6E73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") MaxGR uint16 `db:"max_gr"`
doAckBufSucceed(s, pkt.AckHandle, data) EventName string `db:"event_name"`
Description string `db:"description"`
Data []byte `db:"data"`
}
func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateDistItem)
bf := byteframe.NewByteFrame()
distCount := 0
dists, err := s.server.db.Queryx(`
SELECT d.id, event_name, description, times_acceptable,
min_hr, max_hr, min_sr, max_sr, min_gr, max_gr,
(
SELECT count(*)
FROM distributions_accepted da
WHERE d.id = da.distribution_id
AND da.character_id = $1
) AS times_accepted,
CASE
WHEN (EXTRACT(epoch FROM deadline)::int) IS NULL THEN 0
ELSE (EXTRACT(epoch FROM deadline)::int)
END deadline
FROM distribution d
WHERE character_id = $1 AND type = $2 OR character_id IS NULL AND type = $2 ORDER BY id DESC;
`, s.charID, pkt.Unk0)
if err != nil {
s.logger.Error("Error getting distribution data from db", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
for dists.Next() {
distCount++
distData := &ItemDist{}
err = dists.StructScan(&distData)
if err != nil {
s.logger.Error("Error parsing item distribution data", zap.Error(err))
}
bf.WriteUint32(distData.ID)
bf.WriteUint32(distData.Deadline)
bf.WriteUint32(0) // Unk
bf.WriteUint16(distData.TimesAcceptable)
bf.WriteUint16(distData.TimesAccepted)
bf.WriteUint16(0) // Unk
bf.WriteUint16(distData.MinHR)
bf.WriteUint16(distData.MaxHR)
bf.WriteUint16(distData.MinSR)
bf.WriteUint16(distData.MaxSR)
bf.WriteUint16(distData.MinGR)
bf.WriteUint16(distData.MaxGR)
bf.WriteUint32(0) // Unk
bf.WriteUint32(0) // Unk
eventName, _ := stringsupport.ConvertUTF8ToShiftJIS(distData.EventName)
bf.WriteUint16(uint16(len(eventName)+1))
bf.WriteNullTerminatedBytes(eventName)
bf.WriteBytes(make([]byte, 391))
}
resp := byteframe.NewByteFrame()
resp.WriteUint16(uint16(distCount))
resp.WriteBytes(bf.Data())
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
} }
func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) {
// 0052a49100011f00000000000000010274db99 equipment box page pkt := p.(*mhfpacket.MsgMhfApplyDistItem)
// 0052a48f00011e0000000000000001195dda5c item box page
// 0052a49400010700003ae30000000132d3a4d6 Item ID 3AE3 if pkt.DistributionID == 0 {
// HEADER: doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
// int32: Unique item hash for tracking server side purchases? Swapping across items didn't change image/cost/function etc. } else {
// int16: Number of distributed item types row := s.server.db.QueryRowx("SELECT data FROM distribution WHERE id = $1", pkt.DistributionID)
// ITEM ENTRY dist := &ItemDist{}
// int8: distribution type err := row.StructScan(dist)
// 00 = legs, 01 = Head, 02 = Chest, 03 = Arms, 04 = Waist, 05 = Melee, 06 = Ranged, 07 = Item, 08 == furniture if err != nil {
// ids are wrong shop displays in random order s.logger.Error("Error parsing item distribution data", zap.Error(err))
// 09 = Nothing, 10 = Null Point, 11 = Festi Point, 12 = Zeny, 13 = Null, 14 = Null Points, 15 = My Tore points doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
// 16 = Restyle Point, 17 = N Points, 18 = Nothing, 19 = Gacha Coins, 20 = Trial Gacha Coins, 21 = Frontier points return
// 22 = ?, 23 = Guild Points, 30 = Item Box Page, 31 = Equipment Box Page }
// int16: Unk
// int16: Item when type 07 bf := byteframe.NewByteFrame()
// int16: Unk bf.WriteUint32(0)
// int16: Number delivered in batch bf.WriteBytes(dist.Data)
// int32: Unique item hash for tracking server side purchases? Swapping across items didn't change image/cost/function etc. doAckBufSucceed(s, pkt.AckHandle, bf.Data())
pkt := p.(*mhfpacket.MsgMhfApplyDistItem)
if pkt.RequestType == 0 { _, err = s.server.db.Exec(`
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) INSERT INTO public.distributions_accepted
} else if pkt.RequestType == 0x00000001 { VALUES ($1, $2)
data, _ := hex.DecodeString("0052a494001e0f000000010000000132d3a4d60f000000020000000132d3a4d60f000000030000000132d3a4d60f000000040000000132d3a4d60f000000050000000132d3a4d60f000000060000000132d3a4d60f000000070000000132d3a4d60f000000080000000132d3a4d60f000000090000000132d3a4d60f0000000a0000000132d3a4d60f0000000b0000000132d3a4d60f0000000c0000000132d3a4d60f0000000d0000000132d3a4d60f0000000e0000000132d3a4d60f0000000f0000000132d3a4d60f000000100000000132d3a4d60f000000110000000132d3a4d60f000000120000000132d3a4d60f000000130000000132d3a4d60f000000140000000132d3a4d60f000000150000000132d3a4d60f000000160000000132d3a4d60f000000170000000132d3a4d60f000000180000000132d3a4d60f000000190000000132d3a4d60f0000001a0000000132d3a4d60f0000001b0000000132d3a4d60f0000001c0000000132d3a4d60f0000001d0000000132d3a4d60f0000001e0000000132d3a4d6") `, pkt.DistributionID, s.charID)
doAckBufSucceed(s, pkt.AckHandle, data) if err != nil {
} else { s.logger.Error("Error updating accepted dist count", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) }
} }
} }
func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireDistItem) pkt := p.(*mhfpacket.MsgMhfAcquireDistItem)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
func handleMsgMhfGetDistDescription(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetDistDescription(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetDistDescription) pkt := p.(*mhfpacket.MsgMhfGetDistDescription)
// string for the associated message
data, _ := hex.DecodeString("007E43303547656E65726963204974656D20436C61696D204D6573736167657E4330300D0A596F752067657420736F6D65206B696E64206F66206974656D732070726F6261626C792E00000100") var itemDesc string
//data, _ := hex.DecodeString("0075b750c1c2b17ac1cab652a1757e433035b8cbb3c6bd63c258b169aa41b0c87e433030a1760a0aa175b8cbb3c6bd63c258b169aa41b0c8a176a843c1caa44a31a6b8a141a569c258b169a2b0add30aa8a4a6e2aabaa175b8cbb3c6bd63a176a2b0adb6a143b3cca668a569c258b169a2b4adb6a14300000100") err := s.server.db.QueryRow("SELECT description FROM distribution WHERE id = $1", pkt.DistributionID).Scan(&itemDesc)
doAckBufSucceed(s, pkt.AckHandle, data)
} if err != nil {
s.logger.Error("Error parsing item distribution description", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf := byteframe.NewByteFrame()
description, _ := stringsupport.ConvertUTF8ToShiftJIS(itemDesc)
bf.WriteUint16(uint16(len(description)+1))
bf.WriteNullTerminatedBytes(description)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}