Merge remote-tracking branch 'origin/main' into feature/psn-link

# Conflicts:
#	server/signserver/session.go
This commit is contained in:
wish
2023-08-02 19:50:03 +10:00
28 changed files with 3174 additions and 197 deletions

2372
bin/quests/desktop.ini Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,292 @@
BEGIN;
-- Ripped quests
INSERT INTO public.event_quests (max_players, quest_type, quest_id, mark) VALUES
(0,9,40060,0),
(0,9,40079,0),
(0,9,40080,0),
(0,9,40081,0),
(0,9,40133,0),
(0,9,40134,0),
(0,9,40135,0),
(0,9,40136,0),
(0,9,40137,0),
(0,9,40138,0),
(0,9,40142,0),
(0,9,40143,0),
(0,9,40161,0),
(0,9,40162,0),
(4,9,40173,0),
(4,9,40174,0),
(0,9,40201,0),
(0,9,40218,0),
(4,43,40236,1),
(4,28,40241,1),
(0,8,50534,0),
(4,18,50852,1),
(4,18,50940,1),
(4,18,51024,1),
(4,18,51025,1),
(4,18,51026,1),
(4,18,51027,1),
(4,38,51052,9),
(4,38,51053,9),
(4,18,51059,1),
(4,38,51107,9),
(4,24,51125,0),
(1,24,51126,0),
(4,24,51127,0),
(4,24,51128,0),
(4,24,51129,0),
(4,26,53034,1),
(4,18,53140,1),
(4,18,53187,1),
(4,18,53201,1),
(1,18,53253,1),
(4,26,53307,1),
(4,24,53314,0),
(4,24,53315,0),
(4,24,53316,0),
(4,24,53317,0),
(4,24,53318,0),
(4,24,53319,0),
(4,24,53320,0),
(4,24,53321,0),
(4,24,53324,0),
(1,18,53326,2),
(4,31,54244,0),
(0,8,54425,0),
(4,28,54449,1),
(4,28,54593,1),
(4,28,54594,1),
(4,28,54603,1),
(4,28,54604,1),
(4,28,54605,1),
(4,28,54606,1),
(1,28,54608,0),
(1,28,54609,0),
(32,40,54751,0),
(32,40,54752,0),
(32,40,54753,0),
(32,40,54754,0),
(32,40,54755,0),
(32,40,54756,0),
(32,40,54757,0),
(32,40,54758,0),
(32,40,54759,0),
(32,40,54760,0),
(32,40,54761,0),
(4,28,54801,0),
(4,28,55002,1),
(4,28,55195,0),
(4,28,55202,0),
(4,28,55203,0),
(4,28,55204,0),
(0,8,55369,0),
(4,28,55464,1),
(4,43,55513,1),
(4,28,55529,0),
(4,28,55532,0),
(1,28,55536,0),
(1,28,55537,0),
(32,50,55596,0),
(32,50,55597,0),
(32,50,55598,0),
(32,50,55599,0),
(32,50,55601,0),
(32,50,55602,0),
(32,50,55603,0),
(32,50,55604,0),
(32,50,55605,0),
(32,50,55606,0),
(32,50,55607,0),
(4,28,55619,0),
(4,28,55670,1),
(4,39,55679,9),
(4,39,55680,9),
(4,43,55691,1),
(4,43,55692,1),
(4,43,55693,1),
(4,43,55694,1),
(4,43,55695,1),
(4,43,55696,1),
(4,43,55697,1),
(4,43,55698,1),
(1,43,55728,1),
(4,43,55738,1),
(0,8,55767,0),
(0,8,55768,0),
(4,28,55771,1),
(4,39,55772,9),
(8,51,55796,0),
(8,51,55797,0),
(8,51,55798,0),
(8,51,55799,0),
(8,51,55801,0),
(8,51,55802,0),
(8,51,55803,0),
(8,51,55804,0),
(8,51,55805,0),
(8,51,55806,0),
(8,51,55807,0),
(1,28,55808,0),
(0,8,55870,0),
(0,8,55872,0),
(0,8,55879,0),
(0,8,55880,0),
(0,8,55881,0),
(0,8,55882,0),
(4,28,55896,1),
(0,8,55897,0),
(0,8,55899,0),
(0,8,55901,0),
(0,8,55902,0),
(0,8,55903,0),
(0,8,55904,0),
(0,8,55905,0),
(0,8,55906,0),
(0,8,55907,0),
(0,8,55908,0),
(0,8,55909,0),
(0,8,55910,0),
(0,8,55911,0),
(0,8,55912,0),
(4,39,55916,9),
(4,39,55917,9),
(4,39,55918,9),
(4,39,55919,9),
(4,28,55920,0),
(4,39,55921,9),
(4,39,55922,9),
(4,43,55923,1),
(4,43,55924,1),
(4,43,55925,1),
(4,43,55926,1),
(4,43,55929,1),
(4,43,55930,1),
(4,43,55931,1),
(4,43,55932,1),
(4,28,55935,0),
(4,28,55936,0),
(4,28,55937,0),
(4,28,55938,0),
(4,28,55939,0),
(4,28,55948,0),
(4,28,55949,0),
(4,28,55950,0),
(4,28,55951,0),
(1,28,55963,0),
(4,28,55964,1),
(4,28,55967,1),
(4,43,56042,1),
(4,43,56056,1),
(4,43,56058,1),
(4,43,56059,1),
(4,43,56063,1),
(4,43,56064,1),
(4,43,56076,4),
(4,43,56077,4),
(4,43,56078,4),
(4,43,56079,4),
(4,43,56080,4),
(4,43,56125,1),
(4,24,56134,0),
(4,24,56135,0),
(4,24,56138,0),
(4,24,56139,0),
(4,24,56141,0),
(4,24,56142,0),
(4,28,56143,1),
(4,43,56144,1),
(4,43,56145,1),
(0,8,56146,0),
(4,28,56147,1),
(4,24,56148,0),
(1,24,56149,0),
(4,43,56150,1),
(4,43,56151,1),
(4,43,56154,1),
(4,43,56155,1),
(4,43,56156,1),
(4,28,56157,1),
(1,28,56158,1),
(4,28,56159,1),
(4,48,58043,1),
(4,46,58050,0),
(4,46,58051,0),
(4,46,58052,0),
(4,46,58053,0),
(4,46,58054,0),
(4,46,58055,0),
(4,46,58056,0),
(4,46,58057,0),
(4,46,58058,0),
(4,46,58059,0),
(4,46,58060,0),
(4,46,58061,0),
(4,46,58062,0),
(4,46,58063,0),
(4,46,58064,0),
(4,46,58065,0),
(4,46,58066,0),
(4,46,58067,0),
(4,46,58068,0),
(4,46,58069,0),
(4,46,58070,0),
(4,46,58071,0),
(4,46,58072,0),
(4,46,58074,0),
(4,46,58075,0),
(4,46,58076,0),
(4,46,58077,0),
(4,46,58078,0),
(4,47,58079,0),
(4,47,58080,0),
(4,47,58081,0),
(4,47,58082,0),
(4,47,58083,0),
(4,46,58088,0),
(4,46,58089,0),
(4,46,58090,0),
(4,46,58091,0),
(4,46,58096,0),
(4,46,58097,0),
(4,46,58098,0),
(4,46,58099,0),
(4,46,58101,0),
(4,46,58102,1),
(4,46,58103,1),
(4,46,58104,1),
(4,46,58105,1),
(4,46,58106,1),
(4,46,58107,1),
(4,46,58108,1),
(4,46,58109,1),
(4,46,58112,1),
(4,46,58113,1),
(4,46,58114,1),
(4,46,58115,1),
(4,46,58118,0),
(4,46,58119,0),
(4,46,58120,0),
(4,46,58121,0),
(4,46,58122,0),
(4,46,58123,0),
(4,46,58125,1),
(4,46,58126,1),
(4,46,58127,1),
(4,46,58128,1),
(4,13,61050,0),
(4,13,61051,0),
(4,13,61053,0),
(4,13,61055,0),
(2,13,61067,0),
(4,13,61068,0),
(2,13,61070,0),
(4,13,61071,0),
(8,22,62101,0),
(8,16,62104,0),
(8,16,62105,0),
(8,16,62108,0),
(1,18,62910,1);
END;

View File

@@ -138,6 +138,10 @@ func (b *ByteFrame) DataFromCurrent() []byte {
return b.buf[b.index:b.usedSize] return b.buf[b.index:b.usedSize]
} }
func (b *ByteFrame) Index() uint {
return b.index
}
// SetLE sets the byte order to litte endian. // SetLE sets the byte order to litte endian.
func (b *ByteFrame) SetLE() { func (b *ByteFrame) SetLE() {
b.byteOrder = binary.LittleEndian b.byteOrder = binary.LittleEndian

109
common/decryption/jpk.go Normal file
View File

@@ -0,0 +1,109 @@
package decryption
/*
This code is HEAVILY based from
https://github.com/Chakratos/ReFrontier/blob/master/ReFrontier/Unpack.cs
*/
import (
"erupe-ce/common/byteframe"
"io"
)
var mShiftIndex = 0
var mFlag = byte(0)
func UnpackSimple(data []byte) []byte {
mShiftIndex = 0
mFlag = byte(0)
bf := byteframe.NewByteFrameFromBytes(data)
bf.SetLE()
header := bf.ReadUint32()
if header == 0x1A524B4A {
bf.Seek(0x2, io.SeekCurrent)
jpkType := bf.ReadUint16()
switch jpkType {
case 3:
startOffset := bf.ReadInt32()
outSize := bf.ReadInt32()
outBuffer := make([]byte, outSize)
bf.Seek(int64(startOffset), io.SeekStart)
ProcessDecode(bf, outBuffer)
return outBuffer
}
}
return data
}
func ProcessDecode(data *byteframe.ByteFrame, outBuffer []byte) {
outIndex := 0
for int(data.Index()) < len(data.Data()) && outIndex < len(outBuffer)-1 {
if JPKBitShift(data) == 0 {
outBuffer[outIndex] = ReadByte(data)
outIndex++
continue
} else {
if JPKBitShift(data) == 0 {
length := (JPKBitShift(data) << 1) | JPKBitShift(data)
off := ReadByte(data)
JPKCopy(outBuffer, int(off), int(length)+3, &outIndex)
continue
} else {
hi := ReadByte(data)
lo := ReadByte(data)
length := int(hi&0xE0) >> 5
off := ((int(hi) & 0x1F) << 8) | int(lo)
if length != 0 {
JPKCopy(outBuffer, off, length+2, &outIndex)
continue
} else {
if JPKBitShift(data) == 0 {
length := (JPKBitShift(data) << 3) | (JPKBitShift(data) << 2) | (JPKBitShift(data) << 1) | JPKBitShift(data)
JPKCopy(outBuffer, off, int(length)+2+8, &outIndex)
continue
} else {
temp := ReadByte(data)
if temp == 0xFF {
for i := 0; i < off+0x1B; i++ {
outBuffer[outIndex] = ReadByte(data)
outIndex++
continue
}
} else {
JPKCopy(outBuffer, off, int(temp)+0x1a, &outIndex)
}
}
}
}
}
}
}
func JPKBitShift(data *byteframe.ByteFrame) byte {
mShiftIndex--
if mShiftIndex < 0 {
mShiftIndex = 7
mFlag = ReadByte(data)
}
return (byte)((mFlag >> mShiftIndex) & 1)
}
func JPKCopy(outBuffer []byte, offset int, length int, index *int) {
for i := 0; i < length; i++ {
outBuffer[*index] = outBuffer[*index-offset-1]
*index++
}
}
func ReadByte(bf *byteframe.ByteFrame) byte {
value := bf.ReadUint8()
return value
}

View File

@@ -17,8 +17,8 @@
"AutoCreateAccount": true, "AutoCreateAccount": true,
"CleanDB": false, "CleanDB": false,
"MaxLauncherHR": false, "MaxLauncherHR": false,
"LogInboundMessages": false, "LogInboundMessages": true,
"LogOutboundMessages": false, "LogOutboundMessages": true,
"MaxHexdumpLength": 256, "MaxHexdumpLength": 256,
"DivaEvent": 0, "DivaEvent": 0,
"FestaEvent": -1, "FestaEvent": -1,
@@ -48,6 +48,11 @@
"DailyQuestAllowance": 1, "DailyQuestAllowance": 1,
"MezfesSoloTickets": 10, "MezfesSoloTickets": 10,
"MezfesGroupTickets": 4, "MezfesGroupTickets": 4,
"RegularRavienteMaxPlayers": 8,
"ViolentRavienteMaxPlayers": 8,
"BerserkRavienteMaxPlayers": 32,
"ExtremeRavienteMaxPlayers": 32,
"SmallBerserkRavienteMaxPlayers": 8,
"GUrgentRate": 10, "GUrgentRate": 10,
"GCPMultiplier": 1.00, "GCPMultiplier": 1.00,
"GRPMultiplier": 1.00, "GRPMultiplier": 1.00,
@@ -59,7 +64,8 @@
"EnableKaijiEvent": false, "EnableKaijiEvent": false,
"EnableHiganjimaEvent": false, "EnableHiganjimaEvent": false,
"EnableNierEvent": false, "EnableNierEvent": false,
"DisableRoad": false "DisableRoad": false,
"SeasonOverride": false
}, },
"Discord": { "Discord": {
"Enabled": false, "Enabled": false,

View File

@@ -133,6 +133,11 @@ type GameplayOptions struct {
DailyQuestAllowance uint32 // Number of Daily Quests to allow daily DailyQuestAllowance uint32 // Number of Daily Quests to allow daily
MezfesSoloTickets uint32 // Number of solo tickets given weekly MezfesSoloTickets uint32 // Number of solo tickets given weekly
MezfesGroupTickets uint32 // Number of group tickets given weekly MezfesGroupTickets uint32 // Number of group tickets given weekly
RegularRavienteMaxPlayers uint8
ViolentRavienteMaxPlayers uint8
BerserkRavienteMaxPlayers uint8
ExtremeRavienteMaxPlayers uint8
SmallBerserkRavienteMaxPlayers uint8
GUrgentRate uint16 // Adjusts the rate of G Urgent quests spawning GUrgentRate uint16 // Adjusts the rate of G Urgent quests spawning
GCPMultiplier float32 // Adjusts the multiplier of GCP rewarded for quest completion GCPMultiplier float32 // Adjusts the multiplier of GCP rewarded for quest completion
GRPMultiplier float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion GRPMultiplier float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion
@@ -145,6 +150,7 @@ type GameplayOptions struct {
EnableHiganjimaEvent bool // Enables the Higanjima event in the Rasta Bar EnableHiganjimaEvent bool // Enables the Higanjima event in the Rasta Bar
EnableNierEvent bool // Enables the Nier event in the Rasta Bar EnableNierEvent bool // Enables the Nier event in the Rasta Bar
DisableRoad bool // Disables the Hunting Road DisableRoad bool // Disables the Hunting Road
SeasonOverride bool // Overrides the Quest Season with the current Mezeporta Season
} }
// Discord holds the discord integration config. // Discord holds the discord integration config.

View File

@@ -222,7 +222,7 @@ func main() {
if err != nil { if err != nil {
preventClose(fmt.Sprintf("Channel: Failed to start, %s", err.Error())) preventClose(fmt.Sprintf("Channel: Failed to start, %s", err.Error()))
} else { } else {
channelQuery += fmt.Sprintf(`INSERT INTO servers (server_id, season, current_players, world_name, world_description, land) VALUES (%d, %d, 0, '%s', '%s', %d);`, sid, si%3, ee.Name, ee.Description, i+1) channelQuery += fmt.Sprintf(`INSERT INTO servers (server_id, current_players, world_name, world_description, land) VALUES (%d, 0, '%s', '%s', %d);`, sid, ee.Name, ee.Description, i+1)
channels = append(channels, &c) channels = append(channels, &c)
logger.Info(fmt.Sprintf("Channel %d (%d): Started successfully", count, ce.Port)) logger.Info(fmt.Sprintf("Channel %d (%d): Started successfully", count, ce.Port))
ci++ ci++

View File

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

View File

@@ -3,16 +3,16 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfUpdateUseTrendWeaponLog represents the MSG_MHF_UPDATE_USE_TREND_WEAPON_LOG // MsgMhfUpdateUseTrendWeaponLog represents the MSG_MHF_UPDATE_USE_TREND_WEAPON_LOG
type MsgMhfUpdateUseTrendWeaponLog struct { type MsgMhfUpdateUseTrendWeaponLog struct {
AckHandle uint32 AckHandle uint32
Unk0 uint8 WeaponType uint8
Unk1 uint16 // Weapon/item ID probably? WeaponID uint16
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -23,8 +23,8 @@ func (m *MsgMhfUpdateUseTrendWeaponLog) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfUpdateUseTrendWeaponLog) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfUpdateUseTrendWeaponLog) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8() m.WeaponType = bf.ReadUint8()
m.Unk1 = bf.ReadUint16() m.WeaponID = bf.ReadUint16()
return nil return nil
} }

View File

@@ -14,7 +14,11 @@ type TerminalLogEntry struct {
Index uint32 Index uint32
Type1 uint8 Type1 uint8
Type2 uint8 Type2 uint8
Data []int16 Unk0 int16
Unk1 int32
Unk2 int32
Unk3 int32
Unk4 []int32
} }
// MsgSysTerminalLog represents the MSG_SYS_TERMINAL_LOG // MsgSysTerminalLog represents the MSG_SYS_TERMINAL_LOG
@@ -38,17 +42,19 @@ func (m *MsgSysTerminalLog) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client
m.EntryCount = bf.ReadUint16() m.EntryCount = bf.ReadUint16()
m.Unk0 = bf.ReadUint16() m.Unk0 = bf.ReadUint16()
values := 15
if _config.ErupeConfig.RealClientMode <= _config.F5 {
values = 7
}
for i := 0; i < int(m.EntryCount); i++ { for i := 0; i < int(m.EntryCount); i++ {
e := &TerminalLogEntry{} e := &TerminalLogEntry{}
e.Index = bf.ReadUint32() e.Index = bf.ReadUint32()
e.Type1 = bf.ReadUint8() e.Type1 = bf.ReadUint8()
e.Type2 = bf.ReadUint8() e.Type2 = bf.ReadUint8()
for j := 0; j < values; j++ { e.Unk0 = bf.ReadInt16()
e.Data = append(e.Data, bf.ReadInt16()) e.Unk1 = bf.ReadInt32()
e.Unk2 = bf.ReadInt32()
e.Unk3 = bf.ReadInt32()
if _config.ErupeConfig.RealClientMode >= _config.G1 {
for j := 0; j < 4; j++ {
e.Unk4 = append(e.Unk4, bf.ReadInt32())
}
} }
m.Entries = append(m.Entries, e) m.Entries = append(m.Entries, e)
} }

View File

@@ -0,0 +1,14 @@
BEGIN;
create table if not exists event_quests
(
id serial primary key,
max_players integer,
quest_type integer not null,
quest_id integer not null,
mark integer
);
ALTER TABLE IF EXISTS public.servers DROP COLUMN IF EXISTS season;
END;

View File

@@ -0,0 +1,7 @@
CREATE TABLE public.trend_weapons
(
weapon_id integer NOT NULL,
weapon_type integer NOT NULL,
count integer DEFAULT 0,
PRIMARY KEY (weapon_id)
);

View File

@@ -0,0 +1,6 @@
BEGIN;
ALTER TABLE IF EXISTS public.gacha_entries
ADD COLUMN name text;
END;

View File

@@ -111,7 +111,12 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) {
s.server.logger.Info("SysTerminalLog", s.server.logger.Info("SysTerminalLog",
zap.Uint8("Type1", pkt.Entries[i].Type1), zap.Uint8("Type1", pkt.Entries[i].Type1),
zap.Uint8("Type2", pkt.Entries[i].Type2), zap.Uint8("Type2", pkt.Entries[i].Type2),
zap.Int16s("Data", pkt.Entries[i].Data)) zap.Int16("Unk0", pkt.Entries[i].Unk0),
zap.Int32("Unk1", pkt.Entries[i].Unk1),
zap.Int32("Unk2", pkt.Entries[i].Unk2),
zap.Int32("Unk3", pkt.Entries[i].Unk3),
zap.Int32s("Unk4", pkt.Entries[i].Unk4),
)
} }
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
resp.WriteUint32(pkt.LogID + 1) // LogID to use for requests after this. resp.WriteUint32(pkt.LogID + 1) // LogID to use for requests after this.
@@ -177,6 +182,7 @@ func logoutPlayer(s *Session) {
delete(s.server.sessions, s.rawConn) delete(s.server.sessions, s.rawConn)
} }
s.rawConn.Close() s.rawConn.Close()
delete(s.server.objectIDs, s)
s.server.Unlock() s.server.Unlock()
for _, stage := range s.server.stages { for _, stage := range s.server.stages {
@@ -1691,7 +1697,14 @@ func handleMsgMhfGetEarthValue(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfDebugPostValue(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfDebugPostValue(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetRandFromTable(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetRandFromTable(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRandFromTable)
bf := byteframe.NewByteFrame()
for i := uint16(0); i < pkt.Results; i++ {
bf.WriteUint32(0)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetSenyuDailyCount(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetSenyuDailyCount(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetSenyuDailyCount) pkt := p.(*mhfpacket.MsgMhfGetSenyuDailyCount)
@@ -1847,61 +1860,49 @@ func handleMsgMhfGetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfSetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
func equipSkinHistSize() int {
size := 3200
if _config.ErupeConfig.RealClientMode <= _config.Z2 {
size = 2560
}
if _config.ErupeConfig.RealClientMode <= _config.Z1 {
size = 1280
}
return size
}
func handleMsgMhfGetEquipSkinHist(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEquipSkinHist) pkt := p.(*mhfpacket.MsgMhfGetEquipSkinHist)
// Transmog / reskin system, bitmask of 3200 bytes length size := equipSkinHistSize()
// presumably divided by 5 sections for 5120 armour IDs covered
// +10,000 for actual ID to be unlocked by each bit
// Returning 3200 bytes of FF just unlocks everything for now
var data []byte var data []byte
err := s.server.db.QueryRow("SELECT COALESCE(skin_hist::bytea, $2::bytea) FROM characters WHERE id = $1", s.charID, make([]byte, 0xC80)).Scan(&data) err := s.server.db.QueryRow("SELECT COALESCE(skin_hist::bytea, $2::bytea) FROM characters WHERE id = $1", s.charID, make([]byte, size)).Scan(&data)
if err != nil { if err != nil {
s.logger.Error("Failed to load skin_hist", zap.Error(err)) s.logger.Error("Failed to load skin_hist", zap.Error(err))
data = make([]byte, 3200) data = make([]byte, size)
} }
doAckBufSucceed(s, pkt.AckHandle, data) doAckBufSucceed(s, pkt.AckHandle, data)
} }
func handleMsgMhfUpdateEquipSkinHist(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateEquipSkinHist) pkt := p.(*mhfpacket.MsgMhfUpdateEquipSkinHist)
// sends a raw armour ID back that needs to be mapped into the persistent bitmask above (-10,000) size := equipSkinHistSize()
var data []byte var data []byte
err := s.server.db.QueryRow("SELECT COALESCE(skin_hist, $2) FROM characters WHERE id = $1", s.charID, make([]byte, 0xC80)).Scan(&data) err := s.server.db.QueryRow("SELECT COALESCE(skin_hist, $2) FROM characters WHERE id = $1", s.charID, make([]byte, size)).Scan(&data)
if err != nil { if err != nil {
s.logger.Error("Failed to save skin_hist", zap.Error(err)) s.logger.Error("Failed to get skin_hist", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
} }
var bit int bit := int(pkt.ArmourID) - 10000
var startByte int startByte := (size / 5) * int(pkt.MogType)
switch pkt.MogType {
case 0: // legs
bit = int(pkt.ArmourID) - 10000
startByte = 0
case 1:
bit = int(pkt.ArmourID) - 10000
startByte = 640
case 2:
bit = int(pkt.ArmourID) - 10000
startByte = 1280
case 3:
bit = int(pkt.ArmourID) - 10000
startByte = 1920
case 4:
bit = int(pkt.ArmourID) - 10000
startByte = 2560
}
// psql set_bit could also work but I couldn't get it working // psql set_bit could also work but I couldn't get it working
byteInd := (bit / 8) byteInd := bit / 8
bitInByte := bit % 8 bitInByte := bit % 8
data[startByte+byteInd] |= bits.Reverse8((1 << uint(bitInByte))) data[startByte+byteInd] |= bits.Reverse8(1 << uint(bitInByte))
dumpSaveData(s, data, "skinhist") dumpSaveData(s, data, "skinhist")
_, err = s.server.db.Exec("UPDATE characters SET skin_hist=$1 WHERE id=$2", data, s.charID) s.server.db.Exec("UPDATE characters SET skin_hist=$1 WHERE id=$2", data, s.charID)
if err != nil { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} }
func handleMsgMhfGetUdShopCoin(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetUdShopCoin(s *Session, p mhfpacket.MHFPacket) {
@@ -1945,20 +1946,45 @@ func handleMsgMhfGetLobbyCrowd(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x320)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x320))
} }
type TrendWeapon struct {
WeaponType uint8
WeaponID uint16
}
func handleMsgMhfGetTrendWeapon(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetTrendWeapon(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetTrendWeapon) pkt := p.(*mhfpacket.MsgMhfGetTrendWeapon)
// TODO (Fist): Work out actual format limitations, seems to be final upgrade trendWeapons := [14][3]TrendWeapon{}
// for weapons and it traverses its upgrade tree to recommend base as final for i := uint8(0); i < 14; i++ {
// 423C correlates with most popular magnet spike in use on JP rows, err := s.server.db.Query(`SELECT weapon_id FROM trend_weapons WHERE weapon_type=$1 ORDER BY count DESC LIMIT 3`, i)
// 2A 00 3C 44 00 3C 76 00 3F EA 01 0F 20 01 0F 50 01 0F F8 02 3C 7E 02 3D if err != nil {
// F3 02 40 2A 03 3D 65 03 3F 2A 03 40 36 04 3D 59 04 41 E7 04 43 3E 05 0A continue
// ED 05 0F 4C 05 0F F2 06 3A FE 06 41 E8 06 41 FA 07 3B 02 07 3F ED 07 40 }
// 24 08 3D 37 08 3F 66 08 41 EC 09 3D 38 09 3F 8A 09 41 EE 0A 0E 78 0A 0F j := 0
// AA 0A 0F F9 0B 3E 2E 0B 41 EF 0B 42 FB 0C 41 F0 0C 43 3F 0C 43 EE 0D 41 F1 0D 42 10 0D 42 3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 for rows.Next() {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0xA9)) trendWeapons[i][j].WeaponType = i
rows.Scan(&trendWeapons[i][j].WeaponID)
j++
}
}
x := uint8(0)
bf := byteframe.NewByteFrame()
bf.WriteUint8(0)
for _, weaponType := range trendWeapons {
for _, weapon := range weaponType {
bf.WriteUint8(weapon.WeaponType)
bf.WriteUint16(weapon.WeaponID)
x++
}
}
bf.Seek(0, 0)
bf.WriteUint8(x)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfUpdateUseTrendWeaponLog(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateUseTrendWeaponLog(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateUseTrendWeaponLog) pkt := p.(*mhfpacket.MsgMhfUpdateUseTrendWeaponLog)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) s.server.db.Exec(`INSERT INTO trend_weapons (weapon_id, weapon_type, count) VALUES ($1, $2, 1) ON CONFLICT (weapon_id) DO
UPDATE SET count = trend_weapons.count+1`, pkt.WeaponID, pkt.WeaponType)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }

View File

@@ -239,19 +239,19 @@ func dumpSaveData(s *Session, data []byte, suffix string) {
_, err := os.Stat(dir) _, err := os.Stat(dir)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = os.Mkdir(dir, os.ModePerm) err = os.MkdirAll(dir, os.ModePerm)
if err != nil { if err != nil {
s.logger.Warn("Error dumping savedata, could not create folder") s.logger.Error("Error dumping savedata, could not create folder")
return return
} }
} else { } else {
s.logger.Warn("Error dumping savedata") s.logger.Error("Error dumping savedata")
return return
} }
} }
err = os.WriteFile(path, data, 0644) err = os.WriteFile(path, data, 0644)
if err != nil { if err != nil {
s.logger.Warn("Error dumping savedata, could not write file", zap.Error(err)) s.logger.Error("Error dumping savedata, could not write file", zap.Error(err))
} }
} }
} }

View File

@@ -4,6 +4,7 @@ import (
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"go.uber.org/zap" "go.uber.org/zap"
) )

View File

@@ -10,25 +10,9 @@ import (
func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateObject) pkt := p.(*mhfpacket.MsgSysCreateObject)
// Prevent reusing an object index
var nextID uint32
for {
exists := false
nextID = s.stage.NextObjectID()
for _, object := range s.stage.objects {
if object.id == nextID {
exists = true
break
}
}
if exists == false {
break
}
}
s.stage.Lock() s.stage.Lock()
newObj := &Object{ newObj := &Object{
id: nextID, id: s.NextObjectID(),
ownerCharID: s.charID, ownerCharID: s.charID,
x: pkt.X, x: pkt.X,
y: pkt.Y, y: pkt.Y,

View File

@@ -1,14 +1,18 @@
package channelserver package channelserver
import ( import (
"database/sql"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/common/decryption"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"fmt" "fmt"
"go.uber.org/zap"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"go.uber.org/zap"
) )
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
@@ -34,13 +38,6 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
return return
} }
doAckBufSucceed(s, pkt.AckHandle, data) doAckBufSucceed(s, pkt.AckHandle, data)
} else {
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin")); err == nil {
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin"))
if err != nil {
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, data)
} else { } else {
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode { if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
s.logger.Debug( s.logger.Debug(
@@ -48,7 +45,11 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
zap.String("Filename", pkt.Filename), zap.String("Filename", pkt.Filename),
) )
} }
// Get quest file.
if s.server.erupeConfig.GameplayOptions.SeasonOverride {
pkt.Filename = seasonConversion(s, pkt.Filename)
}
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename)))
if err != nil { if err != nil {
s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename))
@@ -58,6 +59,27 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
} }
doAckBufSucceed(s, pkt.AckHandle, data) doAckBufSucceed(s, pkt.AckHandle, data)
} }
}
func questSuffix(s *Session) string {
// Determine the letter to append for day / night
var timeSet string
if TimeGameAbsolute() > 2880 {
timeSet = "d"
} else {
timeSet = "n"
}
return fmt.Sprintf("%s%d", timeSet, s.server.Season())
}
func seasonConversion(s *Session, questFile string) string {
filename := fmt.Sprintf("%s%s", questFile[:5], questSuffix(s))
// Return original file if file doesn't exist
if _, err := os.Stat(filename); err == nil {
return filename
} else {
return questFile
} }
} }
@@ -79,34 +101,115 @@ func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} }
func loadQuestFile(s *Session, questId int) []byte {
file, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%05dd0.bin", questId)))
if err != nil {
return nil
}
decrypted := decryption.UnpackSimple(file)
fileBytes := byteframe.NewByteFrameFromBytes(decrypted)
fileBytes.SetLE()
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
// The 320 bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers.
questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(320))
questBody.SetLE()
// Find the master quest string pointer
questBody.Seek(40, 0)
fileBytes.Seek(int64(questBody.ReadUint32()), 0)
questBody.Seek(40, 0)
// Overwrite it
questBody.WriteUint32(320)
questBody.Seek(0, 2)
// Rewrite the quest strings and their pointers
var tempString []byte
newStrings := byteframe.NewByteFrame()
tempPointer := 352
for i := 0; i < 8; i++ {
questBody.WriteUint32(uint32(tempPointer))
temp := int64(fileBytes.Index())
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
tempString = fileBytes.ReadNullTerminatedBytes()
fileBytes.Seek(temp+4, 0)
tempPointer += len(tempString) + 1
newStrings.WriteNullTerminatedBytes(tempString)
}
questBody.WriteBytes(newStrings.Data())
return questBody.Data()
}
func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
var id, mark uint32
var questId int
var maxPlayers, questType uint8
rows.Scan(&id, &maxPlayers, &questType, &questId, &mark)
data := loadQuestFile(s, questId)
if data == nil {
return nil, fmt.Errorf("failed to load quest file")
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(id)
bf.WriteUint32(0)
bf.WriteUint8(0) // Indexer
switch questType {
case 16:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers)
case 22:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ViolentRavienteMaxPlayers)
case 40:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.BerserkRavienteMaxPlayers)
case 50:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ExtremeRavienteMaxPlayers)
case 51:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.SmallBerserkRavienteMaxPlayers)
default:
bf.WriteUint8(maxPlayers)
}
bf.WriteUint8(questType)
if questType == 9 {
bf.WriteBool(false)
} else {
bf.WriteBool(true)
}
bf.WriteUint16(0)
bf.WriteUint32(mark)
bf.WriteUint16(0)
bf.WriteUint16(uint16(len(data)))
bf.WriteBytes(data)
ps.Uint8(bf, "", true) // What is this string for?
return bf.Data(), nil
}
func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateQuest) pkt := p.(*mhfpacket.MsgMhfEnumerateQuest)
var totalCount, returnedCount uint16 var totalCount, returnedCount uint16
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint16(0) bf.WriteUint16(0)
filepath.Walk(fmt.Sprintf("%s/events/", s.server.erupeConfig.BinPath), func(path string, info os.FileInfo, err error) error {
rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id")
for rows.Next() {
data, err := makeEventQuest(s, rows)
if err != nil { if err != nil {
return err continue
} else if info.IsDir() {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
} else { } else {
if len(data) > 850 || len(data) < 400 { if len(data) > 896 || len(data) < 352 {
return nil // Could be more or less strict with size limits continue
} else { } else {
totalCount++ totalCount++
if totalCount > pkt.Offset && len(bf.Data()) < 60000 { if totalCount > pkt.Offset && len(bf.Data()) < 60000 {
returnedCount++ returnedCount++
bf.WriteBytes(data) bf.WriteBytes(data)
return nil continue
}
} }
} }
} }
return nil
})
type tuneValue struct { type tuneValue struct {
ID uint16 ID uint16
@@ -537,7 +640,8 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
{false, 10000}, {false, 10000},
} }
bf.WriteUint16(uint16(len(vsQuestItems))) bf.WriteUint16(uint16(len(vsQuestItems)))
bf.WriteUint32(uint32(len(vsQuestBets))) bf.WriteUint16(0) // Unk array of uint16s
bf.WriteUint16(uint16(len(vsQuestBets)))
bf.WriteUint16(0) // Unk bf.WriteUint16(0) // Unk
for i := range vsQuestItems { for i := range vsQuestItems {
@@ -554,6 +658,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(pkt.Offset) bf.WriteUint16(pkt.Offset)
bf.Seek(0, io.SeekStart) bf.Seek(0, io.SeekStart)
bf.WriteUint16(returnedCount) bf.WriteUint16(returnedCount)
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }

View File

@@ -85,13 +85,13 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
if !exists { if !exists {
s.server.semaphoreLock.Lock() s.server.semaphoreLock.Lock()
if strings.HasPrefix(SemaphoreID, "hs_l0u3B5") { if strings.HasPrefix(SemaphoreID, "hs_l0u3B5") {
suffix, _ := strconv.ParseUint(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:], 10, 32) suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:])
s.server.semaphore[SemaphoreID] = &Semaphore{ s.server.semaphore[SemaphoreID] = &Semaphore{
id_semaphore: pkt.SemaphoreID, id_semaphore: pkt.SemaphoreID,
id: uint32(suffix + 1), id: uint32(suffix + 1),
clients: make(map[*Session]uint32), clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]interface{}), reservedClientSlots: make(map[uint32]interface{}),
maxPlayers: 32, maxPlayers: 127,
} }
} else { } else {
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1) s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1)

View File

@@ -3,6 +3,7 @@ package channelserver
import ( import (
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"math/rand" "math/rand"
) )
@@ -40,13 +41,14 @@ type GachaEntry struct {
EntryType uint8 `db:"entry_type"` EntryType uint8 `db:"entry_type"`
ID uint32 `db:"id"` ID uint32 `db:"id"`
ItemType uint8 `db:"item_type"` ItemType uint8 `db:"item_type"`
ItemNumber uint16 `db:"item_number"` ItemNumber uint32 `db:"item_number"`
ItemQuantity uint16 `db:"item_quantity"` ItemQuantity uint16 `db:"item_quantity"`
Weight float64 `db:"weight"` Weight float64 `db:"weight"`
Rarity uint8 `db:"rarity"` Rarity uint8 `db:"rarity"`
Rolls uint8 `db:"rolls"` Rolls uint8 `db:"rolls"`
FrontierPoints uint16 `db:"frontier_points"` FrontierPoints uint16 `db:"frontier_points"`
DailyLimit uint8 `db:"daily_limit"` DailyLimit uint8 `db:"daily_limit"`
Name string `db:"name"`
} }
type GachaItem struct { type GachaItem struct {
@@ -108,6 +110,12 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
// 8: special item // 8: special item
switch pkt.ShopType { switch pkt.ShopType {
case 1: // Running gachas case 1: // Running gachas
// Fundamentally, gacha works completely differently, just hide it for now.
if _config.ErupeConfig.RealClientMode <= _config.G7 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
var count uint16 var count uint16
shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop") shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop")
if err != nil { if err != nil {
@@ -151,7 +159,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(pkt.ShopID) bf.WriteUint32(pkt.ShopID)
var gachaType int var gachaType int
s.server.db.QueryRow(`SELECT gacha_type FROM gacha_shop WHERE id = $1`, pkt.ShopID).Scan(&gachaType) s.server.db.QueryRow(`SELECT gacha_type FROM gacha_shop WHERE id = $1`, pkt.ShopID).Scan(&gachaType)
entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID) entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID)
if err != nil { if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
@@ -168,8 +176,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(gachaEntry.EntryType) bf.WriteUint8(gachaEntry.EntryType)
bf.WriteUint32(gachaEntry.ID) bf.WriteUint32(gachaEntry.ID)
bf.WriteUint8(gachaEntry.ItemType) bf.WriteUint8(gachaEntry.ItemType)
bf.WriteUint16(0) bf.WriteUint32(gachaEntry.ItemNumber)
bf.WriteUint16(gachaEntry.ItemNumber)
bf.WriteUint16(gachaEntry.ItemQuantity) bf.WriteUint16(gachaEntry.ItemQuantity)
if gachaType >= 4 { // If box if gachaType >= 4 { // If box
bf.WriteUint16(1) bf.WriteUint16(1)
@@ -197,7 +204,11 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(gachaEntry.FrontierPoints) bf.WriteUint16(gachaEntry.FrontierPoints)
bf.WriteUint8(gachaEntry.DailyLimit) bf.WriteUint8(gachaEntry.DailyLimit)
if gachaEntry.EntryType < 10 {
ps.Uint8(bf, gachaEntry.Name, true)
} else {
bf.WriteUint8(0) bf.WriteUint8(0)
}
bf.WriteBytes(temp.Data()) bf.WriteBytes(temp.Data())
} }
bf.Seek(4, 0) bf.Seek(4, 0)

View File

@@ -47,6 +47,7 @@ type Server struct {
acceptConns chan net.Conn acceptConns chan net.Conn
deleteConns chan net.Conn deleteConns chan net.Conn
sessions map[net.Conn]*Session sessions map[net.Conn]*Session
objectIDs map[*Session]uint16
listener net.Listener // Listener that is created when Server.Start is called. listener net.Listener // Listener that is created when Server.Start is called.
isShuttingDown bool isShuttingDown bool
@@ -152,6 +153,7 @@ func NewServer(config *Config) *Server {
acceptConns: make(chan net.Conn), acceptConns: make(chan net.Conn),
deleteConns: make(chan net.Conn), deleteConns: make(chan net.Conn),
sessions: make(map[net.Conn]*Session), sessions: make(map[net.Conn]*Session),
objectIDs: make(map[*Session]uint16),
stages: make(map[string]*Stage), stages: make(map[string]*Stage),
userBinaryParts: make(map[userBinaryPartID][]byte), userBinaryParts: make(map[userBinaryPartID][]byte),
semaphore: make(map[string]*Semaphore), semaphore: make(map[string]*Semaphore),
@@ -405,3 +407,8 @@ func (s *Server) NextSemaphoreID() uint32 {
} }
return s.semaphoreIndex return s.semaphoreIndex
} }
func (s *Server) Season() uint8 {
sid := int64(((s.ID & 0xFF00) - 4096) / 256)
return uint8(((TimeAdjusted().Unix() / 86400) + sid) % 3)
}

View File

@@ -34,6 +34,7 @@ type Session struct {
clientContext *clientctx.ClientContext clientContext *clientctx.ClientContext
lastPacket time.Time lastPacket time.Time
objectIndex uint16
userEnteredStage bool // If the user has entered a stage before userEnteredStage bool // If the user has entered a stage before
stageID string stageID string
stage *Stage stage *Stage
@@ -78,6 +79,7 @@ func NewSession(server *Server, conn net.Conn) *Session {
sessionStart: TimeAdjusted().Unix(), sessionStart: TimeAdjusted().Unix(),
stageMoveStack: stringstack.New(), stageMoveStack: stringstack.New(),
} }
s.SetObjectID()
return s return s
} }
@@ -268,3 +270,29 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data)) fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data))
} }
} }
func (s *Session) SetObjectID() {
for i := uint16(1); i < 127; i++ {
exists := false
for _, j := range s.server.objectIDs {
if i == j {
exists = true
break
}
}
if !exists {
s.server.objectIDs[s] = i
return
}
}
s.server.objectIDs[s] = 0
}
func (s *Session) NextObjectID() uint32 {
bf := byteframe.NewByteFrame()
bf.WriteUint16(s.server.objectIDs[s])
s.objectIndex++
bf.WriteUint16(s.objectIndex)
bf.Seek(0, 0)
return bf.ReadUint32()
}

View File

@@ -30,7 +30,7 @@ type Stage struct {
// Objects // Objects
objects map[uint32]*Object objects map[uint32]*Object
objectIndex uint16 objectIndex uint8
// Map of session -> charID. // Map of session -> charID.
// These are clients that are CURRENTLY in the stage // These are clients that are CURRENTLY in the stage
@@ -56,6 +56,7 @@ func NewStage(ID string) *Stage {
clients: make(map[*Session]uint32), clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]bool), reservedClientSlots: make(map[uint32]bool),
objects: make(map[uint32]*Object), objects: make(map[uint32]*Object),
objectIndex: 0,
rawBinaryData: make(map[stageBinaryKey][]byte), rawBinaryData: make(map[stageBinaryKey][]byte),
maxPlayers: 4, maxPlayers: 4,
} }
@@ -94,12 +95,3 @@ func (s *Stage) isCharInQuestByID(charID uint32) bool {
func (s *Stage) isQuest() bool { func (s *Stage) isQuest() bool {
return len(s.reservedClientSlots) > 0 return len(s.reservedClientSlots) > 0
} }
func (s *Stage) NextObjectID() uint32 {
s.objectIndex++
bf := byteframe.NewByteFrame()
bf.WriteUint16(127)
bf.WriteUint16(s.objectIndex)
bf.Seek(0, 0)
return bf.ReadUint32()
}

View File

@@ -23,3 +23,7 @@ func TimeWeekStart() time.Time {
func TimeWeekNext() time.Time { func TimeWeekNext() time.Time {
return TimeWeekStart().Add(time.Hour * 24 * 7) return TimeWeekStart().Add(time.Hour * 24 * 7)
} }
func TimeGameAbsolute() uint32 {
return uint32((TimeAdjusted().Unix() - 2160) % 5760)
}

View File

@@ -12,12 +12,6 @@ import (
"erupe-ce/server/channelserver" "erupe-ce/server/channelserver"
) )
// Server Entries
var season uint8
// Server Channels
var currentplayers uint16
func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
serverInfos := config.Entrance.Entries serverInfos := config.Entrance.Entries
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
@@ -34,11 +28,8 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
continue continue
} }
} }
sid := (4096 + serverIdx*256) + 16
err := s.db.QueryRow("SELECT season FROM servers WHERE server_id=$1", sid).Scan(&season) sid := (4096 + serverIdx*256) * 6000
if err != nil {
season = 0
}
if si.IP == "" { if si.IP == "" {
si.IP = config.Host si.IP = config.Host
} }
@@ -51,7 +42,7 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
bf.WriteUint16(0x0000) bf.WriteUint16(0x0000)
bf.WriteUint16(uint16(len(si.Channels))) bf.WriteUint16(uint16(len(si.Channels)))
bf.WriteUint8(si.Type) bf.WriteUint8(si.Type)
bf.WriteUint8(season) bf.WriteUint8(uint8(((channelserver.TimeAdjusted().Unix() / 86400) + int64(serverIdx)) % 3))
if s.erupeConfig.RealClientMode >= _config.G1 { if s.erupeConfig.RealClientMode >= _config.G1 {
bf.WriteUint8(si.Recommended) bf.WriteUint8(si.Recommended)
} }
@@ -81,18 +72,19 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
bf.WriteUint16(ci.Port) bf.WriteUint16(ci.Port)
bf.WriteUint16(16 + uint16(channelIdx)) bf.WriteUint16(16 + uint16(channelIdx))
bf.WriteUint16(ci.MaxPlayers) bf.WriteUint16(ci.MaxPlayers)
err := s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(&currentplayers) var currentPlayers uint16
if err != nil { s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(&currentPlayers)
currentplayers = 0 bf.WriteUint16(currentPlayers)
} bf.WriteUint16(0) // Unk
bf.WriteUint16(currentplayers) bf.WriteUint16(0) // Unk
bf.WriteUint32(0) bf.WriteUint16(0) // Unk
bf.WriteUint32(0) bf.WriteUint16(0) // Unk
bf.WriteUint32(0) bf.WriteUint16(0) // Unk
bf.WriteUint16(0) // Unk
bf.WriteUint16(319) // Unk bf.WriteUint16(319) // Unk
bf.WriteUint16(252) // Unk bf.WriteUint16(252) // Unk
bf.WriteUint16(248) // Unk bf.WriteUint16(248) // Unk
bf.WriteUint16(0x3039) bf.WriteUint16(12345) // Unk
} }
} }
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))