mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
perf(channelserver): cache rengoku_data.bin at startup
Load and validate rengoku_data.bin once during server initialization instead of reading it from disk on every client request. The file is static ECD-encrypted config data (~4.9 KB) that never changes at runtime. Validation checks file size and ECD magic bytes, logging a warning if the file is missing or invalid so misconfiguration is caught before any client connects.
This commit is contained in:
@@ -71,8 +71,7 @@ if err != nil {
|
|||||||
**Pattern C — Fail ACK (correct):** Error logged, explicit fail ACK sent. The client shows an appropriate error dialog and stays connected.
|
**Pattern C — Fail ACK (correct):** Error logged, explicit fail ACK sent. The client shows an appropriate error dialog and stays connected.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if err != nil {
|
if data == nil {
|
||||||
s.logger.Error("Failed to read rengoku_data.bin", zap.Error(err))
|
|
||||||
doAckBufFail(s, pkt.AckHandle, nil)
|
doAckBufFail(s, pkt.AckHandle, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
ps "erupe-ce/common/pascalstring"
|
ps "erupe-ce/common/pascalstring"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
@@ -180,10 +178,8 @@ func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfGetRengokuBinary)
|
pkt := p.(*mhfpacket.MsgMhfGetRengokuBinary)
|
||||||
// a (massively out of date) version resides in the game's /dat/ folder or up to date can be pulled from packets
|
data := s.server.rengokuBin
|
||||||
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "rengoku_data.bin"))
|
if data == nil {
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to read rengoku_data.bin", zap.Error(err))
|
|
||||||
doAckBufFail(s, pkt.AckHandle, nil)
|
doAckBufFail(s, pkt.AckHandle, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -520,6 +520,42 @@ func TestEnumerateRengokuRanking_Applicant(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- handleMsgMhfGetRengokuBinary tests ---
|
||||||
|
|
||||||
|
func TestGetRengokuBinary_Cached(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
server.rengokuBin = []byte{0x65, 0x63, 0x64, 0x1a, 0xDE, 0xAD}
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetRengokuBinary{AckHandle: 100}
|
||||||
|
handleMsgMhfGetRengokuBinary(session, pkt)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("Response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRengokuBinary_NilCache(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
server.rengokuBin = nil
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetRengokuBinary{AckHandle: 100}
|
||||||
|
handleMsgMhfGetRengokuBinary(session, pkt)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
// fail ACK was sent — expected
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued (expected fail ACK)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tests consolidated from handlers_coverage3_test.go
|
// Tests consolidated from handlers_coverage3_test.go
|
||||||
|
|
||||||
func TestNonTrivialHandlers_RengokuGo(t *testing.T) {
|
func TestNonTrivialHandlers_RengokuGo(t *testing.T) {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -107,6 +110,8 @@ type Server struct {
|
|||||||
|
|
||||||
questCache *QuestCache
|
questCache *QuestCache
|
||||||
|
|
||||||
|
rengokuBin []byte // Cached rengoku_data.bin (ECD-encrypted, served to clients as-is)
|
||||||
|
|
||||||
handlerTable map[network.PacketID]handlerFunc
|
handlerTable map[network.PacketID]handlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +192,8 @@ func NewServer(config *Config) *Server {
|
|||||||
// MezFes
|
// MezFes
|
||||||
s.stages.Store("sl1Ns462p0a0u0", NewStage("sl1Ns462p0a0u0"))
|
s.stages.Store("sl1Ns462p0a0u0", NewStage("sl1Ns462p0a0u0"))
|
||||||
|
|
||||||
|
s.rengokuBin = loadRengokuBinary(config.ErupeConfig.BinPath, s.logger)
|
||||||
|
|
||||||
s.i18n = getLangStrings(s)
|
s.i18n = getLangStrings(s)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@@ -437,3 +444,31 @@ func (s *Server) Season() uint8 {
|
|||||||
sid := int64(((s.ID & serverIDHighMask) - serverIDBase) / serverIDStride)
|
sid := int64(((s.ID & serverIDHighMask) - serverIDBase) / serverIDStride)
|
||||||
return uint8(((TimeAdjusted().Unix() / secsPerDay) + sid) % 3)
|
return uint8(((TimeAdjusted().Unix() / secsPerDay) + sid) % 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ecdMagic is the first 4 bytes of an ECD-encrypted file (little-endian "ecd\x1a").
|
||||||
|
const ecdMagic = uint32(0x6563641a)
|
||||||
|
|
||||||
|
// loadRengokuBinary reads and validates rengoku_data.bin from binPath.
|
||||||
|
// Returns the raw bytes on success, or nil if the file is missing or invalid.
|
||||||
|
func loadRengokuBinary(binPath string, logger *zap.Logger) []byte {
|
||||||
|
path := filepath.Join(binPath, "rengoku_data.bin")
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("rengoku_data.bin not found, Hunting Road will be unavailable",
|
||||||
|
zap.String("path", path), zap.Error(err))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(data) < 4 {
|
||||||
|
logger.Warn("rengoku_data.bin too small, ignoring",
|
||||||
|
zap.Int("bytes", len(data)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if magic := binary.LittleEndian.Uint32(data[:4]); magic != ecdMagic {
|
||||||
|
logger.Warn("rengoku_data.bin has invalid ECD magic, ignoring",
|
||||||
|
zap.String("expected", "0x6563641a"),
|
||||||
|
zap.String("got", fmt.Sprintf("0x%08x", magic)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logger.Info("Loaded rengoku_data.bin", zap.Int("bytes", len(data)))
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -727,3 +730,65 @@ func TestFindObjectByChar(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- loadRengokuBinary tests ---
|
||||||
|
|
||||||
|
func TestLoadRengokuBinary_ValidECD(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
// Build a minimal valid ECD file: magic + some payload
|
||||||
|
data := make([]byte, 16)
|
||||||
|
binary.LittleEndian.PutUint32(data[:4], ecdMagic)
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "rengoku_data.bin"), data, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, _ := zap.NewDevelopment()
|
||||||
|
result := loadRengokuBinary(dir, logger)
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("Expected non-nil result for valid ECD file")
|
||||||
|
}
|
||||||
|
if len(result) != 16 {
|
||||||
|
t.Errorf("len = %d, want 16", len(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRengokuBinary_MissingFile(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
logger, _ := zap.NewDevelopment()
|
||||||
|
result := loadRengokuBinary(dir, logger)
|
||||||
|
|
||||||
|
if result != nil {
|
||||||
|
t.Errorf("Expected nil for missing file, got %d bytes", len(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRengokuBinary_TooSmall(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "rengoku_data.bin"), []byte{0x65, 0x63}, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, _ := zap.NewDevelopment()
|
||||||
|
result := loadRengokuBinary(dir, logger)
|
||||||
|
|
||||||
|
if result != nil {
|
||||||
|
t.Errorf("Expected nil for too-small file, got %d bytes", len(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRengokuBinary_BadMagic(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
data := make([]byte, 16)
|
||||||
|
binary.LittleEndian.PutUint32(data[:4], 0xDEADBEEF)
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "rengoku_data.bin"), data, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, _ := zap.NewDevelopment()
|
||||||
|
result := loadRengokuBinary(dir, logger)
|
||||||
|
|
||||||
|
if result != nil {
|
||||||
|
t.Errorf("Expected nil for bad magic, got %d bytes", len(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user