refactor(channelserver): replace magic numbers with named protocol constants

Extract numeric literals into named constants across quest handling,
save data parsing, rengoku skill layout, diva event timing, guild info,
achievement trophies, RP accrual rates, and semaphore IDs. Adds
constants_quest.go for quest-related constants shared across functions.

Pure rename/extract with zero behavior change.
This commit is contained in:
Houmgaor
2026-02-20 19:50:28 +01:00
parent bf983966a0
commit 7c444b023b
6 changed files with 124 additions and 88 deletions

View File

@@ -12,8 +12,8 @@ const (
// Event quest binary frame offsets // Event quest binary frame offsets
const ( const (
questFrameTimeFlagOffset = 25 questFrameTimeFlagOffset = 25
questFrameVariant3Offset = 175 questFrameVariant3Offset = 175
) )
// Quest body lengths per game version // Quest body lengths per game version

View File

@@ -12,6 +12,12 @@ import (
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
// Guild sentinel and cost constants
const (
guildNotJoinedSentinel = uint32(0xFFFFFFFF)
guildRoomMaxRP = uint32(55000)
)
func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoGuild) pkt := p.(*mhfpacket.MsgMhfInfoGuild)
@@ -32,7 +38,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
guildLeaderName := stringsupport.UTF8ToSJIS(guild.LeaderName) guildLeaderName := stringsupport.UTF8ToSJIS(guild.LeaderName)
characterGuildData, err := GetCharacterGuildData(s, s.charID) characterGuildData, err := GetCharacterGuildData(s, s.charID)
characterJoinedAt := uint32(0xFFFFFFFF) characterJoinedAt := guildNotJoinedSentinel
if characterGuildData != nil && characterGuildData.JoinedAt != nil { if characterGuildData != nil && characterGuildData.JoinedAt != nil {
characterJoinedAt = uint32(characterGuildData.JoinedAt.Unix()) characterJoinedAt = uint32(characterGuildData.JoinedAt.Unix())
@@ -123,7 +129,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
} }
bf.WriteUint8(limit) bf.WriteUint8(limit)
bf.WriteUint32(55000) bf.WriteUint32(guildRoomMaxRP)
bf.WriteUint32(uint32(guild.RoomExpiry.Unix())) bf.WriteUint32(uint32(guild.RoomExpiry.Unix()))
bf.WriteUint16(guild.RoomRP) bf.WriteUint16(guild.RoomRP)
bf.WriteUint16(0) // Ignored bf.WriteUint16(0) // Ignored

View File

@@ -223,24 +223,24 @@ func loadQuestFile(s *Session, questId int) []byte {
fileBytes.SetLE() fileBytes.SetLE()
_, _ = fileBytes.Seek(int64(fileBytes.ReadUint32()), 0) _, _ = fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
bodyLength := 320 bodyLength := questBodyLenZZ
if s.server.erupeConfig.RealClientMode <= _config.S6 { if s.server.erupeConfig.RealClientMode <= _config.S6 {
bodyLength = 160 bodyLength = questBodyLenS6
} else if s.server.erupeConfig.RealClientMode <= _config.F5 { } else if s.server.erupeConfig.RealClientMode <= _config.F5 {
bodyLength = 168 bodyLength = questBodyLenF5
} else if s.server.erupeConfig.RealClientMode <= _config.G101 { } else if s.server.erupeConfig.RealClientMode <= _config.G101 {
bodyLength = 192 bodyLength = questBodyLenG101
} else if s.server.erupeConfig.RealClientMode <= _config.Z1 { } else if s.server.erupeConfig.RealClientMode <= _config.Z1 {
bodyLength = 224 bodyLength = questBodyLenZ1
} }
// The n bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers. // The n 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(uint(bodyLength))) questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(uint(bodyLength)))
questBody.SetLE() questBody.SetLE()
// Find the master quest string pointer // Find the master quest string pointer
_, _ = questBody.Seek(40, 0) _, _ = questBody.Seek(questStringPointerOff, 0)
_, _ = fileBytes.Seek(int64(questBody.ReadUint32()), 0) _, _ = fileBytes.Seek(int64(questBody.ReadUint32()), 0)
_, _ = questBody.Seek(40, 0) _, _ = questBody.Seek(questStringPointerOff, 0)
// Overwrite it // Overwrite it
questBody.WriteUint32(uint32(bodyLength)) questBody.WriteUint32(uint32(bodyLength))
_, _ = questBody.Seek(0, 2) _, _ = questBody.Seek(0, 2)
@@ -248,8 +248,8 @@ func loadQuestFile(s *Session, questId int) []byte {
// Rewrite the quest strings and their pointers // Rewrite the quest strings and their pointers
var tempString []byte var tempString []byte
newStrings := byteframe.NewByteFrame() newStrings := byteframe.NewByteFrame()
tempPointer := bodyLength + 32 tempPointer := bodyLength + questStringTablePadding
for i := 0; i < 8; i++ { for i := 0; i < questStringCount; i++ {
questBody.WriteUint32(uint32(tempPointer)) questBody.WriteUint32(uint32(tempPointer))
temp := int64(fileBytes.Index()) temp := int64(fileBytes.Index())
_, _ = fileBytes.Seek(int64(fileBytes.ReadUint32()), 0) _, _ = fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
@@ -284,21 +284,21 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
bf.WriteUint32(0) // Unk bf.WriteUint32(0) // Unk
bf.WriteUint8(0) // Unk bf.WriteUint8(0) // Unk
switch questType { switch questType {
case 16: case QuestTypeRegularRaviente:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers) bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers)
case 22: case QuestTypeViolentRaviente:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ViolentRavienteMaxPlayers) bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ViolentRavienteMaxPlayers)
case 40: case QuestTypeBerserkRaviente:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.BerserkRavienteMaxPlayers) bf.WriteUint8(s.server.erupeConfig.GameplayOptions.BerserkRavienteMaxPlayers)
case 50: case QuestTypeExtremeRaviente:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ExtremeRavienteMaxPlayers) bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ExtremeRavienteMaxPlayers)
case 51: case QuestTypeSmallBerserkRavi:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.SmallBerserkRavienteMaxPlayers) bf.WriteUint8(s.server.erupeConfig.GameplayOptions.SmallBerserkRavienteMaxPlayers)
default: default:
bf.WriteUint8(maxPlayers) bf.WriteUint8(maxPlayers)
} }
bf.WriteUint8(questType) bf.WriteUint8(questType)
if questType == 9 { if questType == QuestTypeSpecialTool {
bf.WriteBool(false) bf.WriteBool(false)
} else { } else {
bf.WriteBool(true) bf.WriteBool(true)
@@ -314,9 +314,9 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
// Time Flag Replacement // Time Flag Replacement
// Bitset Structure: b8 UNK, b7 Required Objective, b6 UNK, b5 Night, b4 Day, b3 Cold, b2 Warm, b1 Spring // Bitset Structure: b8 UNK, b7 Required Objective, b6 UNK, b5 Night, b4 Day, b3 Cold, b2 Warm, b1 Spring
// if the byte is set to 0 the game choses the quest file corresponding to whatever season the game is on // if the byte is set to 0 the game choses the quest file corresponding to whatever season the game is on
_, _ = bf.Seek(25, 0) _, _ = bf.Seek(questFrameTimeFlagOffset, 0)
flagByte := bf.ReadUint8() flagByte := bf.ReadUint8()
_, _ = bf.Seek(25, 0) _, _ = bf.Seek(questFrameTimeFlagOffset, 0)
if s.server.erupeConfig.GameplayOptions.SeasonOverride { if s.server.erupeConfig.GameplayOptions.SeasonOverride {
bf.WriteUint8(flagByte & 0b11100000) bf.WriteUint8(flagByte & 0b11100000)
} else { } else {
@@ -332,10 +332,10 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
// Bitset Structure Quest Variant 2: b8 Road, b7 High Conquest, b6 Fixed Difficulty, b5 No Active Feature, b4 Timer, b3 No Cuff, b2 No Halk Pots, b1 Low Conquest // Bitset Structure Quest Variant 2: b8 Road, b7 High Conquest, b6 Fixed Difficulty, b5 No Active Feature, b4 Timer, b3 No Cuff, b2 No Halk Pots, b1 Low Conquest
// Bitset Structure Quest Variant 3: b8 No Sigils, b7 UNK, b6 Interception, b5 Zenith, b4 No GP Skills, b3 No Simple Mode?, b2 GSR to GR, b1 No Reward Skills // Bitset Structure Quest Variant 3: b8 No Sigils, b7 UNK, b6 Interception, b5 Zenith, b4 No GP Skills, b3 No Simple Mode?, b2 GSR to GR, b1 No Reward Skills
_, _ = bf.Seek(175, 0) _, _ = bf.Seek(questFrameVariant3Offset, 0)
questVariant3 := bf.ReadUint8() questVariant3 := bf.ReadUint8()
questVariant3 &= 0b11011111 // disable Interception flag questVariant3 &= 0b11011111 // disable Interception flag
_, _ = bf.Seek(175, 0) _, _ = bf.Seek(questFrameVariant3Offset, 0)
bf.WriteUint8(questVariant3) bf.WriteUint8(questVariant3)
_, _ = bf.Seek(0, 2) _, _ = bf.Seek(0, 2)
@@ -400,7 +400,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
s.logger.Error("Failed to make event quest", zap.Error(err)) s.logger.Error("Failed to make event quest", zap.Error(err))
continue continue
} else { } else {
if len(data) > 896 || len(data) < 352 { if len(data) > questDataMaxLen || len(data) < questDataMinLen {
s.logger.Error("Invalid quest data length", zap.Int("len", len(data))) s.logger.Error("Invalid quest data length", zap.Int("len", len(data)))
continue continue
} else { } else {
@@ -601,25 +601,25 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
} }
tuneValues = temp tuneValues = temp
tuneLimit := 770 tuneLimit := tuneLimitZZ
if s.server.erupeConfig.RealClientMode <= _config.G1 { if s.server.erupeConfig.RealClientMode <= _config.G1 {
tuneLimit = 256 tuneLimit = tuneLimitG1
} else if s.server.erupeConfig.RealClientMode <= _config.G3 { } else if s.server.erupeConfig.RealClientMode <= _config.G3 {
tuneLimit = 283 tuneLimit = tuneLimitG3
} else if s.server.erupeConfig.RealClientMode <= _config.GG { } else if s.server.erupeConfig.RealClientMode <= _config.GG {
tuneLimit = 315 tuneLimit = tuneLimitGG
} else if s.server.erupeConfig.RealClientMode <= _config.G61 { } else if s.server.erupeConfig.RealClientMode <= _config.G61 {
tuneLimit = 332 tuneLimit = tuneLimitG61
} else if s.server.erupeConfig.RealClientMode <= _config.G7 { } else if s.server.erupeConfig.RealClientMode <= _config.G7 {
tuneLimit = 339 tuneLimit = tuneLimitG7
} else if s.server.erupeConfig.RealClientMode <= _config.G81 { } else if s.server.erupeConfig.RealClientMode <= _config.G81 {
tuneLimit = 396 tuneLimit = tuneLimitG81
} else if s.server.erupeConfig.RealClientMode <= _config.G91 { } else if s.server.erupeConfig.RealClientMode <= _config.G91 {
tuneLimit = 694 tuneLimit = tuneLimitG91
} else if s.server.erupeConfig.RealClientMode <= _config.G101 { } else if s.server.erupeConfig.RealClientMode <= _config.G101 {
tuneLimit = 704 tuneLimit = tuneLimitG101
} else if s.server.erupeConfig.RealClientMode <= _config.Z2 { } else if s.server.erupeConfig.RealClientMode <= _config.Z2 {
tuneLimit = 750 tuneLimit = tuneLimitZ2
} }
if len(tuneValues) > tuneLimit { if len(tuneValues) > tuneLimit {
tuneValues = tuneValues[:tuneLimit] tuneValues = tuneValues[:tuneLimit]

View File

@@ -14,18 +14,31 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
// Rengoku save blob layout offsets
const (
rengokuSkillSlotsStart = 0x1B
rengokuSkillSlotsEnd = 0x21
rengokuSkillValuesStart = 0x2E
rengokuSkillValuesEnd = 0x3A
rengokuPointsStart = 0x3B
rengokuPointsEnd = 0x47
rengokuMaxStageMpOffset = 71
rengokuMinPayloadSize = 91
rengokuMaxPayloadSize = 4096
)
// rengokuSkillsZeroed checks if the skill slot IDs (offsets 0x1B-0x20) and // rengokuSkillsZeroed checks if the skill slot IDs (offsets 0x1B-0x20) and
// equipped skill values (offsets 0x2E-0x39) are all zero in a rengoku save blob. // equipped skill values (offsets 0x2E-0x39) are all zero in a rengoku save blob.
func rengokuSkillsZeroed(data []byte) bool { func rengokuSkillsZeroed(data []byte) bool {
if len(data) < 0x3A { if len(data) < rengokuSkillValuesEnd {
return true return true
} }
for _, b := range data[0x1B:0x21] { for _, b := range data[rengokuSkillSlotsStart:rengokuSkillSlotsEnd] {
if b != 0 { if b != 0 {
return false return false
} }
} }
for _, b := range data[0x2E:0x3A] { for _, b := range data[rengokuSkillValuesStart:rengokuSkillValuesEnd] {
if b != 0 { if b != 0 {
return false return false
} }
@@ -35,10 +48,10 @@ func rengokuSkillsZeroed(data []byte) bool {
// rengokuHasPoints checks if any skill point allocation (offsets 0x3B-0x46) is nonzero. // rengokuHasPoints checks if any skill point allocation (offsets 0x3B-0x46) is nonzero.
func rengokuHasPoints(data []byte) bool { func rengokuHasPoints(data []byte) bool {
if len(data) < 0x47 { if len(data) < rengokuPointsEnd {
return false return false
} }
for _, b := range data[0x3B:0x47] { for _, b := range data[rengokuPointsStart:rengokuPointsEnd] {
if b != 0 { if b != 0 {
return true return true
} }
@@ -51,15 +64,15 @@ func rengokuHasPoints(data []byte) bool {
// preserving the skills that the client failed to populate due to a race // preserving the skills that the client failed to populate due to a race
// condition during area transitions (see issue #85). // condition during area transitions (see issue #85).
func rengokuMergeSkills(dst, src []byte) { func rengokuMergeSkills(dst, src []byte) {
copy(dst[0x1B:0x21], src[0x1B:0x21]) copy(dst[rengokuSkillSlotsStart:rengokuSkillSlotsEnd], src[rengokuSkillSlotsStart:rengokuSkillSlotsEnd])
copy(dst[0x2E:0x3A], src[0x2E:0x3A]) copy(dst[rengokuSkillValuesStart:rengokuSkillValuesEnd], src[rengokuSkillValuesStart:rengokuSkillValuesEnd])
} }
func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
// Saved every floor on road, holds values such as floors progressed, points etc. // Saved every floor on road, holds values such as floors progressed, points etc.
// Can be safely handled by the client. // Can be safely handled by the client.
pkt := p.(*mhfpacket.MsgMhfSaveRengokuData) pkt := p.(*mhfpacket.MsgMhfSaveRengokuData)
if len(pkt.RawDataPayload) < 91 || len(pkt.RawDataPayload) > 4096 { if len(pkt.RawDataPayload) < rengokuMinPayloadSize || len(pkt.RawDataPayload) > rengokuMaxPayloadSize {
s.logger.Warn("Rengoku payload size out of range", zap.Int("len", len(pkt.RawDataPayload))) s.logger.Warn("Rengoku payload size out of range", zap.Int("len", len(pkt.RawDataPayload)))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
@@ -72,10 +85,10 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
// path triggers a rengoku save BEFORE the load response has been parsed into // path triggers a rengoku save BEFORE the load response has been parsed into
// the character data area. This produces a save with zeroed skill fields but // the character data area. This produces a save with zeroed skill fields but
// preserved point totals. Detect this pattern and merge existing skill data. // preserved point totals. Detect this pattern and merge existing skill data.
if len(saveData) >= 0x47 && rengokuSkillsZeroed(saveData) && rengokuHasPoints(saveData) { if len(saveData) >= rengokuPointsEnd && rengokuSkillsZeroed(saveData) && rengokuHasPoints(saveData) {
var existing []byte var existing []byte
if err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id=$1", s.charID).Scan(&existing); err == nil { if err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id=$1", s.charID).Scan(&existing); err == nil {
if len(existing) >= 0x47 && !rengokuSkillsZeroed(existing) { if len(existing) >= rengokuPointsEnd && !rengokuSkillsZeroed(existing) {
s.logger.Info("Rengoku save has zeroed skills with invested points, preserving existing skills", s.logger.Info("Rengoku save has zeroed skills with invested points, preserving existing skills",
zap.Uint32("charID", s.charID)) zap.Uint32("charID", s.charID))
merged := make([]byte, len(saveData)) merged := make([]byte, len(saveData))
@@ -106,7 +119,7 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
return return
} }
bf := byteframe.NewByteFrameFromBytes(saveData) bf := byteframe.NewByteFrameFromBytes(saveData)
_, _ = bf.Seek(71, 0) _, _ = bf.Seek(rengokuMaxStageMpOffset, 0)
maxStageMp := bf.ReadUint32() maxStageMp := bf.ReadUint32()
maxScoreMp := bf.ReadUint32() maxScoreMp := bf.ReadUint32()
_, _ = bf.Seek(4, 1) _, _ = bf.Seek(4, 1)

View File

@@ -500,12 +500,12 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
case 1, 2, 3: // usersearchidx, usersearchname, lobbysearchname case 1, 2, 3: // usersearchidx, usersearchname, lobbysearchname
// Snapshot matching sessions under lock, then build response outside locks. // Snapshot matching sessions under lock, then build response outside locks.
type sessionResult struct { type sessionResult struct {
charID uint32 charID uint32
name []byte name []byte
stageID []byte stageID []byte
ip net.IP ip net.IP
port uint16 port uint16
userBin3 []byte userBin3 []byte
} }
var results []sessionResult var results []sessionResult
@@ -656,15 +656,15 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
} }
// Snapshot matching stages under lock, then build response outside locks. // Snapshot matching stages under lock, then build response outside locks.
type stageResult struct { type stageResult struct {
ip net.IP ip net.IP
port uint16 port uint16
clientCount int clientCount int
reserved int reserved int
maxPlayers uint16 maxPlayers uint16
stageID string stageID string
stageData []int16 stageData []int16
rawBinData0 []byte rawBinData0 []byte
rawBinData1 []byte rawBinData1 []byte
} }
var stageResults []stageResult var stageResults []stageResult

View File

@@ -13,20 +13,20 @@ import (
type SavePointer int type SavePointer int
const ( const (
pGender = iota // +1 pGender = iota
pRP // +2 pRP
pHouseTier // +5 pHouseTier
pHouseData // +195 pHouseData
pBookshelfData // +lBookshelfData pBookshelfData
pGalleryData // +1748 pGalleryData
pToreData // +240 pToreData
pGardenData // +68 pGardenData
pPlaytime // +4 pPlaytime
pWeaponType // +1 pWeaponType
pWeaponID // +2 pWeaponID
pHR // +2 pHR
pGRP // +4 pGRP
pKQF // +8 pKQF
lBookshelfData lBookshelfData
) )
@@ -146,16 +146,33 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() {
rpBytes := make([]byte, 2) rpBytes := make([]byte, 2)
binary.LittleEndian.PutUint16(rpBytes, save.RP) binary.LittleEndian.PutUint16(rpBytes, save.RP)
if save.Mode >= _config.F4 { if save.Mode >= _config.F4 {
copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+saveFieldRP], rpBytes)
} }
if save.Mode >= _config.G10 { if save.Mode >= _config.G10 {
copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF) copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+saveFieldKQF], save.KQF)
} }
} }
// This will update the save struct with the values stored in the character save // This will update the save struct with the values stored in the character save
// Save data field sizes
const (
saveFieldRP = 2
saveFieldHouseTier = 5
saveFieldHouseData = 195
saveFieldGallery = 1748
saveFieldTore = 240
saveFieldGarden = 68
saveFieldPlaytime = 4
saveFieldWeaponID = 2
saveFieldHR = 2
saveFieldGRP = 4
saveFieldKQF = 8
saveFieldNameOffset = 88
saveFieldNameLen = 12
)
func (save *CharacterSaveData) updateStructWithSaveData() { func (save *CharacterSaveData) updateStructWithSaveData() {
save.Name, _ = stringsupport.SJISToUTF8(bfutil.UpToNull(save.decompSave[88:100])) save.Name, _ = stringsupport.SJISToUTF8(bfutil.UpToNull(save.decompSave[saveFieldNameOffset : saveFieldNameOffset+saveFieldNameLen]))
if save.decompSave[save.Pointers[pGender]] == 1 { if save.decompSave[save.Pointers[pGender]] == 1 {
save.Gender = true save.Gender = true
} else { } else {
@@ -163,24 +180,24 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
} }
if !save.IsNewCharacter { if !save.IsNewCharacter {
if save.Mode >= _config.S6 { if save.Mode >= _config.S6 {
save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+saveFieldRP])
save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+saveFieldHouseTier]
save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+saveFieldHouseData]
save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+save.Pointers[lBookshelfData]] save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+save.Pointers[lBookshelfData]]
save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748] save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+saveFieldGallery]
save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240] save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+saveFieldTore]
save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68] save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+saveFieldGarden]
save.Playtime = binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pPlaytime] : save.Pointers[pPlaytime]+4]) save.Playtime = binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pPlaytime] : save.Pointers[pPlaytime]+saveFieldPlaytime])
save.WeaponType = save.decompSave[save.Pointers[pWeaponType]] save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+saveFieldWeaponID])
save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+2]) save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+saveFieldHR])
if save.Mode >= _config.G1 { if save.Mode >= _config.G1 {
if save.HR == uint16(999) { if save.HR == uint16(999) {
save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))) save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+saveFieldGRP])))
} }
} }
if save.Mode >= _config.G10 { if save.Mode >= _config.G10 {
save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+saveFieldKQF]
} }
} }
} }