fix(repo): detect silent save failures + partial daily mission stubs

SaveColumn and SaveMercenary now check RowsAffected() and return a
wrapped ErrCharacterNotFound when 0 rows are updated, preventing
silent data loss when a character ID is missing or mismatched.
AdjustInt already detects this via its RETURNING scan — no change.

Daily mission packet structs (Get/SetDailyMission*) now parse the
AckHandle instead of returning NOT IMPLEMENTED, letting handlers
send empty-list success ACKs and avoiding client softlocks.

Also adds tests for dashboard stats endpoint and for five guild
repo methods (SetAllianceRecruiting, RolloverDailyRP,
AddWeeklyBonusUsers, InsertKillLog, ClearTreasureHunt) that
had no coverage.
This commit is contained in:
Houmgaor
2026-03-21 01:49:28 +01:00
parent c43be33680
commit 106cf85eb7
8 changed files with 471 additions and 23 deletions

View File

@@ -8,17 +8,22 @@ import (
"erupe-ce/network/clientctx"
)
// MsgMhfGetDailyMissionMaster represents the MSG_MHF_GET_DAILY_MISSION_MASTER
type MsgMhfGetDailyMissionMaster struct{}
// MsgMhfGetDailyMissionMaster requests the server-side daily mission master list.
// Full request payload beyond the AckHandle is not yet reverse-engineered.
type MsgMhfGetDailyMissionMaster struct {
AckHandle uint32
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfGetDailyMissionMaster) Opcode() network.PacketID {
return network.MSG_MHF_GET_DAILY_MISSION_MASTER
}
// Parse parses the packet from binary
// Parse parses the packet from binary.
// Only the AckHandle is parsed; additional fields are unknown.
func (m *MsgMhfGetDailyMissionMaster) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -8,17 +8,22 @@ import (
"erupe-ce/network/clientctx"
)
// MsgMhfGetDailyMissionPersonal represents the MSG_MHF_GET_DAILY_MISSION_PERSONAL
type MsgMhfGetDailyMissionPersonal struct{}
// MsgMhfGetDailyMissionPersonal requests the character's personal daily mission progress.
// Full request payload beyond the AckHandle is not yet reverse-engineered.
type MsgMhfGetDailyMissionPersonal struct {
AckHandle uint32
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfGetDailyMissionPersonal) Opcode() network.PacketID {
return network.MSG_MHF_GET_DAILY_MISSION_PERSONAL
}
// Parse parses the packet from binary
// Parse parses the packet from binary.
// Only the AckHandle is parsed; additional fields are unknown.
func (m *MsgMhfGetDailyMissionPersonal) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -8,17 +8,22 @@ import (
"erupe-ce/network/clientctx"
)
// MsgMhfSetDailyMissionPersonal represents the MSG_MHF_SET_DAILY_MISSION_PERSONAL
type MsgMhfSetDailyMissionPersonal struct{}
// MsgMhfSetDailyMissionPersonal writes the character's personal daily mission progress.
// Full request payload beyond the AckHandle is not yet reverse-engineered.
type MsgMhfSetDailyMissionPersonal struct {
AckHandle uint32
}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfSetDailyMissionPersonal) Opcode() network.PacketID {
return network.MSG_MHF_SET_DAILY_MISSION_PERSONAL
}
// Parse parses the packet from binary
// Parse parses the packet from binary.
// Only the AckHandle is parsed; additional fields are unknown.
func (m *MsgMhfSetDailyMissionPersonal) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED")
m.AckHandle = bf.ReadUint32()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -22,8 +22,6 @@ func TestParseSmallNotImplemented(t *testing.T) {
{"MsgMhfEnterTournamentQuest", &MsgMhfEnterTournamentQuest{}},
{"MsgMhfGetCaAchievementHist", &MsgMhfGetCaAchievementHist{}},
{"MsgMhfGetCaUniqueID", &MsgMhfGetCaUniqueID{}},
{"MsgMhfGetDailyMissionMaster", &MsgMhfGetDailyMissionMaster{}},
{"MsgMhfGetDailyMissionPersonal", &MsgMhfGetDailyMissionPersonal{}},
{"MsgMhfGetRestrictionEvent", &MsgMhfGetRestrictionEvent{}},
{"MsgMhfKickExportForce", &MsgMhfKickExportForce{}},
{"MsgMhfPaymentAchievement", &MsgMhfPaymentAchievement{}},
@@ -32,7 +30,6 @@ func TestParseSmallNotImplemented(t *testing.T) {
{"MsgMhfResetAchievement", &MsgMhfResetAchievement{}},
{"MsgMhfResetTitle", &MsgMhfResetTitle{}},
{"MsgMhfSetCaAchievement", &MsgMhfSetCaAchievement{}},
{"MsgMhfSetDailyMissionPersonal", &MsgMhfSetDailyMissionPersonal{}},
{"MsgMhfSetUdTacticsFollower", &MsgMhfSetUdTacticsFollower{}},
{"MsgMhfStampcardPrize", &MsgMhfStampcardPrize{}},
{"MsgMhfUpdateForceGuildRank", &MsgMhfUpdateForceGuildRank{}},