From d2b5bb72f85db0ba401ece1e181d791e640d6cf7 Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Tue, 17 Feb 2026 17:54:51 +0100 Subject: [PATCH] refactor: extract gametime package, replace fmt.Printf with zap logging Move time utilities (TimeAdjusted, TimeMidnight, TimeWeekStart, TimeWeekNext, TimeGameAbsolute) from channelserver into common/gametime to break the inappropriate dependency where signserver, entranceserver, and api imported the 38K-line channelserver package just for time functions. Replace all fmt.Printf debug logging in sys_session.go and handlers_object.go with structured zap logging for consistent observability. --- common/gametime/gametime.go | 32 ++++ common/gametime/gametime_test.go | 157 ++++++++++++++++ main.go | 3 +- network/clientctx/clientcontext.go | 2 +- network/clientctx/clientcontext_test.go | 21 +-- server/api/endpoints.go | 28 +-- server/api/endpoints_test.go | 8 +- .../compression/deltacomp/deltacomp.go | 4 +- server/channelserver/handlers_object.go | 14 +- server/channelserver/sys_session.go | 34 ++-- server/channelserver/sys_time.go | 32 +--- server/channelserver/sys_time_test.go | 167 ------------------ server/entranceserver/make_resp.go | 8 +- server/signserver/dsgn_resp.go | 12 +- 14 files changed, 258 insertions(+), 264 deletions(-) create mode 100644 common/gametime/gametime.go create mode 100644 common/gametime/gametime_test.go delete mode 100644 server/channelserver/sys_time_test.go diff --git a/common/gametime/gametime.go b/common/gametime/gametime.go new file mode 100644 index 000000000..c1a2b43cd --- /dev/null +++ b/common/gametime/gametime.go @@ -0,0 +1,32 @@ +package gametime + +import ( + "time" +) + +func Adjusted() time.Time { + baseTime := time.Now().In(time.FixedZone("UTC+9", 9*60*60)) + return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), baseTime.Hour(), baseTime.Minute(), baseTime.Second(), baseTime.Nanosecond(), baseTime.Location()) +} + +func Midnight() time.Time { + baseTime := time.Now().In(time.FixedZone("UTC+9", 9*60*60)) + return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location()) +} + +func WeekStart() time.Time { + midnight := Midnight() + offset := int(midnight.Weekday()) - int(time.Monday) + if offset < 0 { + offset += 7 + } + return midnight.Add(-time.Duration(offset) * 24 * time.Hour) +} + +func WeekNext() time.Time { + return WeekStart().Add(time.Hour * 24 * 7) +} + +func GameAbsolute() uint32 { + return uint32((Adjusted().Unix() - 2160) % 5760) +} diff --git a/common/gametime/gametime_test.go b/common/gametime/gametime_test.go new file mode 100644 index 000000000..52e0b6075 --- /dev/null +++ b/common/gametime/gametime_test.go @@ -0,0 +1,157 @@ +package gametime + +import ( + "testing" + "time" +) + +func TestAdjusted(t *testing.T) { + result := Adjusted() + + _, offset := result.Zone() + expectedOffset := 9 * 60 * 60 + if offset != expectedOffset { + t.Errorf("Adjusted() zone offset = %d, want %d (UTC+9)", offset, expectedOffset) + } + + now := time.Now() + diff := result.Sub(now.In(time.FixedZone("UTC+9", 9*60*60))) + if diff < -time.Second || diff > time.Second { + t.Errorf("Adjusted() time differs from expected by %v", diff) + } +} + +func TestMidnight(t *testing.T) { + midnight := Midnight() + + if midnight.Hour() != 0 { + t.Errorf("Midnight() hour = %d, want 0", midnight.Hour()) + } + if midnight.Minute() != 0 { + t.Errorf("Midnight() minute = %d, want 0", midnight.Minute()) + } + if midnight.Second() != 0 { + t.Errorf("Midnight() second = %d, want 0", midnight.Second()) + } + if midnight.Nanosecond() != 0 { + t.Errorf("Midnight() nanosecond = %d, want 0", midnight.Nanosecond()) + } + + _, offset := midnight.Zone() + expectedOffset := 9 * 60 * 60 + if offset != expectedOffset { + t.Errorf("Midnight() zone offset = %d, want %d (UTC+9)", offset, expectedOffset) + } +} + +func TestWeekStart(t *testing.T) { + weekStart := WeekStart() + + if weekStart.Weekday() != time.Monday { + t.Errorf("WeekStart() weekday = %v, want Monday", weekStart.Weekday()) + } + + if weekStart.Hour() != 0 || weekStart.Minute() != 0 || weekStart.Second() != 0 { + t.Errorf("WeekStart() should be at midnight, got %02d:%02d:%02d", + weekStart.Hour(), weekStart.Minute(), weekStart.Second()) + } + + _, offset := weekStart.Zone() + expectedOffset := 9 * 60 * 60 + if offset != expectedOffset { + t.Errorf("WeekStart() zone offset = %d, want %d (UTC+9)", offset, expectedOffset) + } + + midnight := Midnight() + if weekStart.After(midnight) { + t.Errorf("WeekStart() %v should be <= current midnight %v", weekStart, midnight) + } +} + +func TestWeekNext(t *testing.T) { + weekStart := WeekStart() + weekNext := WeekNext() + + expectedNext := weekStart.Add(time.Hour * 24 * 7) + if !weekNext.Equal(expectedNext) { + t.Errorf("WeekNext() = %v, want %v (7 days after WeekStart)", weekNext, expectedNext) + } + + if weekNext.Weekday() != time.Monday { + t.Errorf("WeekNext() weekday = %v, want Monday", weekNext.Weekday()) + } + + if weekNext.Hour() != 0 || weekNext.Minute() != 0 || weekNext.Second() != 0 { + t.Errorf("WeekNext() should be at midnight, got %02d:%02d:%02d", + weekNext.Hour(), weekNext.Minute(), weekNext.Second()) + } + + if !weekNext.After(weekStart) { + t.Errorf("WeekNext() %v should be after WeekStart() %v", weekNext, weekStart) + } +} + +func TestWeekStartSundayEdge(t *testing.T) { + weekStart := WeekStart() + + if weekStart.Weekday() != time.Monday { + t.Errorf("WeekStart() on any day should return Monday, got %v", weekStart.Weekday()) + } +} + +func TestMidnightSameDay(t *testing.T) { + adjusted := Adjusted() + midnight := Midnight() + + if midnight.Year() != adjusted.Year() || + midnight.Month() != adjusted.Month() || + midnight.Day() != adjusted.Day() { + t.Errorf("Midnight() date = %v, want same day as Adjusted() %v", + midnight.Format("2006-01-02"), adjusted.Format("2006-01-02")) + } +} + +func TestWeekDuration(t *testing.T) { + weekStart := WeekStart() + weekNext := WeekNext() + + duration := weekNext.Sub(weekStart) + expectedDuration := time.Hour * 24 * 7 + + if duration != expectedDuration { + t.Errorf("Duration between WeekStart and WeekNext = %v, want %v", duration, expectedDuration) + } +} + +func TestTimeZoneConsistency(t *testing.T) { + adjusted := Adjusted() + midnight := Midnight() + weekStart := WeekStart() + weekNext := WeekNext() + + times := []struct { + name string + time time.Time + }{ + {"Adjusted", adjusted}, + {"Midnight", midnight}, + {"WeekStart", weekStart}, + {"WeekNext", weekNext}, + } + + expectedOffset := 9 * 60 * 60 + for _, tt := range times { + _, offset := tt.time.Zone() + if offset != expectedOffset { + t.Errorf("%s() zone offset = %d, want %d (UTC+9)", tt.name, offset, expectedOffset) + } + } +} + +func TestGameAbsolute(t *testing.T) { + result := GameAbsolute() + + if result >= 5760 { + t.Errorf("GameAbsolute() = %d, should be < 5760", result) + } +} diff --git a/main.go b/main.go index 2f433f524..2e5861072 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "syscall" "time" + "erupe-ce/common/gametime" "erupe-ce/server/api" "erupe-ce/server/channelserver" "erupe-ce/server/discordbot" @@ -142,7 +143,7 @@ func main() { logger.Info("Database: Finished clearing") } - logger.Info(fmt.Sprintf("Server Time: %s", channelserver.TimeAdjusted().String())) + logger.Info(fmt.Sprintf("Server Time: %s", gametime.Adjusted().String())) // Now start our server(s). diff --git a/network/clientctx/clientcontext.go b/network/clientctx/clientcontext.go index 021ae3299..95245888b 100644 --- a/network/clientctx/clientcontext.go +++ b/network/clientctx/clientcontext.go @@ -1,4 +1,4 @@ package clientctx // ClientContext holds contextual data required for packet encoding/decoding. -type ClientContext struct{} // Unused +type ClientContext struct{} diff --git a/network/clientctx/clientcontext_test.go b/network/clientctx/clientcontext_test.go index 2eb333ab5..5ce7ac95d 100644 --- a/network/clientctx/clientcontext_test.go +++ b/network/clientctx/clientcontext_test.go @@ -5,27 +5,8 @@ import ( ) // TestClientContext_Exists verifies that the ClientContext type exists -// and can be instantiated, even though it's currently unused. +// and can be instantiated. func TestClientContext_Exists(t *testing.T) { - // This test documents that ClientContext is currently an empty struct - // and is marked as unused in the codebase. - var ctx ClientContext - - // Verify it's a zero-size struct - _ = ctx - - // Just verify we can create it - ctx2 := ClientContext{} - _ = ctx2 -} - -// TestClientContext_IsEmpty verifies that ClientContext has no fields -func TestClientContext_IsEmpty(t *testing.T) { - // The struct should be empty as marked by the comment "// Unused" - // This test documents the current state of the struct ctx := ClientContext{} _ = ctx - - // If fields are added in the future, this test will need to be updated - // Currently it's just a placeholder/documentation test } diff --git a/server/api/endpoints.go b/server/api/endpoints.go index 4eaac119e..dd2bb856a 100644 --- a/server/api/endpoints.go +++ b/server/api/endpoints.go @@ -6,7 +6,7 @@ import ( "encoding/xml" "errors" _config "erupe-ce/config" - "erupe-ce/server/channelserver" + "erupe-ce/common/gametime" "fmt" "image" "image/jpeg" @@ -77,7 +77,7 @@ type ExportData struct { func (s *APIServer) newAuthData(userID uint32, userRights uint32, userTokenID uint32, userToken string, characters []Character) AuthData { resp := AuthData{ - CurrentTS: uint32(channelserver.TimeAdjusted().Unix()), + CurrentTS: uint32(gametime.Adjusted().Unix()), ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()), EntranceCount: 1, User: User{ @@ -99,9 +99,9 @@ func (s *APIServer) newAuthData(userID uint32, userRights uint32, userTokenID ui stalls[4] = 2 } resp.MezFes = &MezFes{ - ID: uint32(channelserver.TimeWeekStart().Unix()), - Start: uint32(channelserver.TimeWeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()), - End: uint32(channelserver.TimeWeekNext().Unix()), + ID: uint32(gametime.WeekStart().Unix()), + Start: uint32(gametime.WeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()), + End: uint32(gametime.WeekNext().Unix()), SoloTickets: s.erupeConfig.GameplayOptions.MezFesSoloTickets, GroupTickets: s.erupeConfig.GameplayOptions.MezFesGroupTickets, Stalls: stalls, @@ -118,7 +118,7 @@ func (s *APIServer) Launcher(w http.ResponseWriter, r *http.Request) { respData.Messages = s.erupeConfig.API.Messages respData.Links = s.erupeConfig.API.Links w.Header().Add("Content-Type", "application/json") - json.NewEncoder(w).Encode(respData) + _ = json.NewEncoder(w).Encode(respData) } func (s *APIServer) Login(w http.ResponseWriter, r *http.Request) { @@ -140,7 +140,7 @@ func (s *APIServer) Login(w http.ResponseWriter, r *http.Request) { err := s.db.QueryRow("SELECT id, password, rights FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password, &userRights) if err == sql.ErrNoRows { w.WriteHeader(400) - w.Write([]byte("username-error")) + _, _ = w.Write([]byte("username-error")) return } else if err != nil { s.logger.Warn("SQL query error", zap.Error(err)) @@ -149,7 +149,7 @@ func (s *APIServer) Login(w http.ResponseWriter, r *http.Request) { } if bcrypt.CompareHashAndPassword([]byte(password), []byte(reqData.Password)) != nil { w.WriteHeader(400) - w.Write([]byte("password-error")) + _, _ = w.Write([]byte("password-error")) return } @@ -170,7 +170,7 @@ func (s *APIServer) Login(w http.ResponseWriter, r *http.Request) { } respData := s.newAuthData(userID, userRights, userTokenID, userToken, characters) w.Header().Add("Content-Type", "application/json") - json.NewEncoder(w).Encode(respData) + _ = json.NewEncoder(w).Encode(respData) } func (s *APIServer) Register(w http.ResponseWriter, r *http.Request) { @@ -194,7 +194,7 @@ func (s *APIServer) Register(w http.ResponseWriter, r *http.Request) { var pqErr *pq.Error if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" { w.WriteHeader(400) - w.Write([]byte("username-exists-error")) + _, _ = w.Write([]byte("username-exists-error")) return } s.logger.Error("Error checking user", zap.Error(err), zap.String("username", reqData.Username)) @@ -210,7 +210,7 @@ func (s *APIServer) Register(w http.ResponseWriter, r *http.Request) { } respData := s.newAuthData(userID, userRights, userTokenID, userToken, []Character{}) w.Header().Add("Content-Type", "application/json") - json.NewEncoder(w).Encode(respData) + _ = json.NewEncoder(w).Encode(respData) } func (s *APIServer) CreateCharacter(w http.ResponseWriter, r *http.Request) { @@ -239,7 +239,7 @@ func (s *APIServer) CreateCharacter(w http.ResponseWriter, r *http.Request) { character.HR = 7 } w.Header().Add("Content-Type", "application/json") - json.NewEncoder(w).Encode(character) + _ = json.NewEncoder(w).Encode(character) } func (s *APIServer) DeleteCharacter(w http.ResponseWriter, r *http.Request) { @@ -264,7 +264,7 @@ func (s *APIServer) DeleteCharacter(w http.ResponseWriter, r *http.Request) { return } w.Header().Add("Content-Type", "application/json") - json.NewEncoder(w).Encode(struct{}{}) + _ = json.NewEncoder(w).Encode(struct{}{}) } func (s *APIServer) ExportSave(w http.ResponseWriter, r *http.Request) { @@ -293,7 +293,7 @@ func (s *APIServer) ExportSave(w http.ResponseWriter, r *http.Request) { Character: character, } w.Header().Add("Content-Type", "application/json") - json.NewEncoder(w).Encode(save) + _ = json.NewEncoder(w).Encode(save) } func (s *APIServer) ScreenShotGet(w http.ResponseWriter, r *http.Request) { // Get the 'id' parameter from the URL diff --git a/server/api/endpoints_test.go b/server/api/endpoints_test.go index 7f40079c9..81dc3bc8b 100644 --- a/server/api/endpoints_test.go +++ b/server/api/endpoints_test.go @@ -10,7 +10,7 @@ import ( "testing" _config "erupe-ce/config" - "erupe-ce/server/channelserver" + "erupe-ce/common/gametime" "go.uber.org/zap" ) @@ -99,7 +99,7 @@ func TestLauncherEndpointEmptyConfig(t *testing.T) { server.Launcher(recorder, req) var respData LauncherResponse - json.NewDecoder(recorder.Body).Decode(&respData) + _ = json.NewDecoder(recorder.Body).Decode(&respData) if respData.Banners == nil { t.Error("Banners should not be nil, should be empty slice") @@ -355,7 +355,7 @@ func TestScreenShotEndpointDisabled(t *testing.T) { XMLName xml.Name `xml:"result"` Code string `xml:"code"` } - xml.NewDecoder(recorder.Body).Decode(&result) + _ = xml.NewDecoder(recorder.Body).Decode(&result) if result.Code != "400" { t.Errorf("Expected code 400, got %s", result.Code) @@ -573,7 +573,7 @@ func TestNewAuthDataTimestamps(t *testing.T) { authData := server.newAuthData(1, 0, 1, "token", []Character{}) // Timestamps should be reasonable (within last minute and next 30 days) - now := uint32(channelserver.TimeAdjusted().Unix()) + now := uint32(gametime.Adjusted().Unix()) if authData.CurrentTS < now-60 || authData.CurrentTS > now+60 { t.Errorf("CurrentTS not within reasonable range: %d vs %d", authData.CurrentTS, now) } diff --git a/server/channelserver/compression/deltacomp/deltacomp.go b/server/channelserver/compression/deltacomp/deltacomp.go index 0d5aa55be..09d4ccb4b 100644 --- a/server/channelserver/compression/deltacomp/deltacomp.go +++ b/server/channelserver/compression/deltacomp/deltacomp.go @@ -2,8 +2,8 @@ package deltacomp import ( "bytes" - "fmt" "io" + "log" ) func checkReadUint8(r *bytes.Reader) (uint8, error) { @@ -77,7 +77,7 @@ func ApplyDataDiff(diff []byte, baseData []byte) []byte { // Grow slice if it's required if len(baseCopy) < dataOffset { - fmt.Printf("Slice smaller than data offset, growing slice...") + log.Printf("Slice smaller than data offset, growing slice...") baseCopy = append(baseCopy, make([]byte, (dataOffset+differentCount)-len(baseData))...) } else { length := len(baseCopy[dataOffset:]) diff --git a/server/channelserver/handlers_object.go b/server/channelserver/handlers_object.go index 4e8284939..eb9cb1151 100644 --- a/server/channelserver/handlers_object.go +++ b/server/channelserver/handlers_object.go @@ -1,10 +1,10 @@ package channelserver import ( - "fmt" - "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" + + "go.uber.org/zap" ) func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) { @@ -34,7 +34,7 @@ func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) { OwnerCharID: newObj.ownerCharID, } - s.logger.Info(fmt.Sprintf("Broadcasting new object: %s (%d)", s.Name, newObj.id)) + s.logger.Info("Broadcasting new object", zap.String("name", s.Name), zap.Uint32("objectID", newObj.id)) s.stage.BroadcastMHF(dupObjUpdate, s) } @@ -43,7 +43,13 @@ func handleMsgSysDeleteObject(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysPositionObject(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysPositionObject) if s.server.erupeConfig.DebugOptions.LogInboundMessages { - fmt.Printf("[%s] with objectID [%d] move to (%f,%f,%f)\n\n", s.Name, pkt.ObjID, pkt.X, pkt.Y, pkt.Z) + s.logger.Debug("Object position update", + zap.String("name", s.Name), + zap.Uint32("objectID", pkt.ObjID), + zap.Float32("x", pkt.X), + zap.Float32("y", pkt.Y), + zap.Float32("z", pkt.Z), + ) } s.stage.Lock() object, ok := s.stage.objects[s.charID] diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 03b30d153..9d0e70ad2 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -84,7 +84,7 @@ func NewSession(server *Server, conn net.Conn) *Session { rawConn: conn, cryptConn: network.NewCryptConn(conn), sendPackets: make(chan packet, 20), - clientContext: &clientctx.ClientContext{}, // Unused + clientContext: &clientctx.ClientContext{}, lastPacket: time.Now(), objectID: server.getObjectId(), sessionStart: TimeAdjusted().Unix(), @@ -232,8 +232,7 @@ func (s *Session) handlePacketGroup(pktGroup []byte) { // This shouldn't be needed, but it's better to recover and let the connection die than to panic the server. defer func() { if r := recover(); r != nil { - fmt.Printf("[%s]", s.Name) - fmt.Println("Recovered from panic", r) + s.logger.Error("Recovered from panic", zap.String("name", s.Name), zap.Any("panic", r)) } }() @@ -246,13 +245,16 @@ func (s *Session) handlePacketGroup(pktGroup []byte) { // Get the packet parser and handler for this opcode. mhfPkt := mhfpacket.FromOpcode(opcode) if mhfPkt == nil { - fmt.Println("Got opcode which we don't know how to parse, can't parse anymore for this group") + s.logger.Warn("Got opcode which we don't know how to parse, can't parse anymore for this group") return } // Parse the packet. err := mhfPkt.Parse(bf, s.clientContext) if err != nil { - fmt.Printf("\n!!! [%s] %s NOT IMPLEMENTED !!! \n\n\n", s.Name, opcode) + s.logger.Warn("Packet not implemented", + zap.String("name", s.Name), + zap.Stringer("opcode", opcode), + ) return } // Handle the packet. @@ -297,21 +299,23 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien if len(data) >= 6 { ackHandle = binary.BigEndian.Uint32(data[2:6]) } - if t, ok := s.ackStart[ackHandle]; ok { - fmt.Printf("[%s] -> [%s] (%fs)\n", sender, recipient, float64(time.Now().UnixNano()-t.UnixNano())/1000000000) - } else { - fmt.Printf("[%s] -> [%s]\n", sender, recipient) + fields := []zap.Field{ + zap.String("sender", sender), + zap.String("recipient", recipient), + zap.Uint16("opcode_dec", opcode), + zap.String("opcode_hex", fmt.Sprintf("0x%04X", opcode)), + zap.Stringer("opcode_name", opcodePID), + zap.Int("data_bytes", len(data)), + } + if t, ok := s.ackStart[ackHandle]; ok { + fields = append(fields, zap.Duration("ack_latency", time.Since(t))) } - fmt.Printf("Opcode: (Dec: %d Hex: 0x%04X Name: %s) \n", opcode, opcode, opcodePID) if s.server.erupeConfig.DebugOptions.LogMessageData { if len(data) <= s.server.erupeConfig.DebugOptions.MaxHexdumpLength { - fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data)) - } else { - fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data)) + fields = append(fields, zap.String("data", hex.Dump(data))) } - } else { - fmt.Printf("\n") } + s.logger.Debug("Packet", fields...) } func (s *Session) getObjectId() uint32 { diff --git a/server/channelserver/sys_time.go b/server/channelserver/sys_time.go index bae61a1c6..873bd67c6 100644 --- a/server/channelserver/sys_time.go +++ b/server/channelserver/sys_time.go @@ -1,32 +1,12 @@ package channelserver import ( + "erupe-ce/common/gametime" "time" ) -func TimeAdjusted() time.Time { - baseTime := time.Now().In(time.FixedZone("UTC+9", 9*60*60)) - return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), baseTime.Hour(), baseTime.Minute(), baseTime.Second(), baseTime.Nanosecond(), baseTime.Location()) -} - -func TimeMidnight() time.Time { - baseTime := time.Now().In(time.FixedZone("UTC+9", 9*60*60)) - return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location()) -} - -func TimeWeekStart() time.Time { - midnight := TimeMidnight() - offset := int(midnight.Weekday()) - int(time.Monday) - if offset < 0 { - offset += 7 - } - return midnight.Add(-time.Duration(offset) * 24 * time.Hour) -} - -func TimeWeekNext() time.Time { - return TimeWeekStart().Add(time.Hour * 24 * 7) -} - -func TimeGameAbsolute() uint32 { - return uint32((TimeAdjusted().Unix() - 2160) % 5760) -} +func TimeAdjusted() time.Time { return gametime.Adjusted() } +func TimeMidnight() time.Time { return gametime.Midnight() } +func TimeWeekStart() time.Time { return gametime.WeekStart() } +func TimeWeekNext() time.Time { return gametime.WeekNext() } +func TimeGameAbsolute() uint32 { return gametime.GameAbsolute() } diff --git a/server/channelserver/sys_time_test.go b/server/channelserver/sys_time_test.go deleted file mode 100644 index 6fbb5c645..000000000 --- a/server/channelserver/sys_time_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package channelserver - -import ( - "testing" - "time" -) - -func TestTimeAdjusted(t *testing.T) { - result := TimeAdjusted() - - // Should return a time in UTC+9 timezone - _, offset := result.Zone() - expectedOffset := 9 * 60 * 60 // 9 hours in seconds - if offset != expectedOffset { - t.Errorf("TimeAdjusted() zone offset = %d, want %d (UTC+9)", offset, expectedOffset) - } - - // The time should be close to current time (within a few seconds) - now := time.Now() - diff := result.Sub(now.In(time.FixedZone("UTC+9", 9*60*60))) - if diff < -time.Second || diff > time.Second { - t.Errorf("TimeAdjusted() time differs from expected by %v", diff) - } -} - -func TestTimeMidnight(t *testing.T) { - midnight := TimeMidnight() - - // Should be at midnight (hour=0, minute=0, second=0, nanosecond=0) - if midnight.Hour() != 0 { - t.Errorf("TimeMidnight() hour = %d, want 0", midnight.Hour()) - } - if midnight.Minute() != 0 { - t.Errorf("TimeMidnight() minute = %d, want 0", midnight.Minute()) - } - if midnight.Second() != 0 { - t.Errorf("TimeMidnight() second = %d, want 0", midnight.Second()) - } - if midnight.Nanosecond() != 0 { - t.Errorf("TimeMidnight() nanosecond = %d, want 0", midnight.Nanosecond()) - } - - // Should be in UTC+9 timezone - _, offset := midnight.Zone() - expectedOffset := 9 * 60 * 60 - if offset != expectedOffset { - t.Errorf("TimeMidnight() zone offset = %d, want %d (UTC+9)", offset, expectedOffset) - } -} - -func TestTimeWeekStart(t *testing.T) { - weekStart := TimeWeekStart() - - // Should be on Monday (weekday = 1) - if weekStart.Weekday() != time.Monday { - t.Errorf("TimeWeekStart() weekday = %v, want Monday", weekStart.Weekday()) - } - - // Should be at midnight - if weekStart.Hour() != 0 || weekStart.Minute() != 0 || weekStart.Second() != 0 { - t.Errorf("TimeWeekStart() should be at midnight, got %02d:%02d:%02d", - weekStart.Hour(), weekStart.Minute(), weekStart.Second()) - } - - // Should be in UTC+9 timezone - _, offset := weekStart.Zone() - expectedOffset := 9 * 60 * 60 - if offset != expectedOffset { - t.Errorf("TimeWeekStart() zone offset = %d, want %d (UTC+9)", offset, expectedOffset) - } - - // Week start should be before or equal to current midnight - midnight := TimeMidnight() - if weekStart.After(midnight) { - t.Errorf("TimeWeekStart() %v should be <= current midnight %v", weekStart, midnight) - } -} - -func TestTimeWeekNext(t *testing.T) { - weekStart := TimeWeekStart() - weekNext := TimeWeekNext() - - // TimeWeekNext should be exactly 7 days after TimeWeekStart - expectedNext := weekStart.Add(time.Hour * 24 * 7) - if !weekNext.Equal(expectedNext) { - t.Errorf("TimeWeekNext() = %v, want %v (7 days after WeekStart)", weekNext, expectedNext) - } - - // Should also be on Monday - if weekNext.Weekday() != time.Monday { - t.Errorf("TimeWeekNext() weekday = %v, want Monday", weekNext.Weekday()) - } - - // Should be at midnight - if weekNext.Hour() != 0 || weekNext.Minute() != 0 || weekNext.Second() != 0 { - t.Errorf("TimeWeekNext() should be at midnight, got %02d:%02d:%02d", - weekNext.Hour(), weekNext.Minute(), weekNext.Second()) - } - - // Should be in the future relative to week start - if !weekNext.After(weekStart) { - t.Errorf("TimeWeekNext() %v should be after TimeWeekStart() %v", weekNext, weekStart) - } -} - -func TestTimeWeekStartSundayEdge(t *testing.T) { - // When today is Sunday, the calculation should go back to last Monday - // This is tested indirectly by verifying the weekday is always Monday - weekStart := TimeWeekStart() - - // Regardless of what day it is now, week start should be Monday - if weekStart.Weekday() != time.Monday { - t.Errorf("TimeWeekStart() on any day should return Monday, got %v", weekStart.Weekday()) - } -} - -func TestTimeMidnightSameDay(t *testing.T) { - adjusted := TimeAdjusted() - midnight := TimeMidnight() - - // Midnight should be on the same day (year, month, day) - if midnight.Year() != adjusted.Year() || - midnight.Month() != adjusted.Month() || - midnight.Day() != adjusted.Day() { - t.Errorf("TimeMidnight() date = %v, want same day as TimeAdjusted() %v", - midnight.Format("2006-01-02"), adjusted.Format("2006-01-02")) - } -} - -func TestTimeWeekDuration(t *testing.T) { - weekStart := TimeWeekStart() - weekNext := TimeWeekNext() - - // Duration between week boundaries should be exactly 7 days - duration := weekNext.Sub(weekStart) - expectedDuration := time.Hour * 24 * 7 - - if duration != expectedDuration { - t.Errorf("Duration between WeekStart and WeekNext = %v, want %v", duration, expectedDuration) - } -} - -func TestTimeZoneConsistency(t *testing.T) { - adjusted := TimeAdjusted() - midnight := TimeMidnight() - weekStart := TimeWeekStart() - weekNext := TimeWeekNext() - - // All times should be in the same timezone (UTC+9) - times := []struct { - name string - time time.Time - }{ - {"TimeAdjusted", adjusted}, - {"TimeMidnight", midnight}, - {"TimeWeekStart", weekStart}, - {"TimeWeekNext", weekNext}, - } - - expectedOffset := 9 * 60 * 60 - for _, tt := range times { - _, offset := tt.time.Zone() - if offset != expectedOffset { - t.Errorf("%s() zone offset = %d, want %d (UTC+9)", tt.name, offset, expectedOffset) - } - } -} diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index 5e68c62e9..bf461cfaa 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -9,7 +9,7 @@ import ( "net" "erupe-ce/common/byteframe" - "erupe-ce/server/channelserver" + "erupe-ce/common/gametime" ) func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { @@ -41,7 +41,7 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { bf.WriteUint16(0) bf.WriteUint16(uint16(len(si.Channels))) bf.WriteUint8(si.Type) - bf.WriteUint8(uint8(((channelserver.TimeAdjusted().Unix() / 86400) + int64(serverIdx)) % 3)) + bf.WriteUint8(uint8(((gametime.Adjusted().Unix() / 86400) + int64(serverIdx)) % 3)) if s.erupeConfig.RealClientMode >= _config.G1 { bf.WriteUint8(si.Recommended) } @@ -71,7 +71,7 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { bf.WriteUint16(uint16(channelIdx | 16)) bf.WriteUint16(ci.MaxPlayers) var currentPlayers uint16 - s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(¤tPlayers) + _ = s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(¤tPlayers) bf.WriteUint16(currentPlayers) bf.WriteUint16(0) bf.WriteUint16(0) @@ -85,7 +85,7 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte { bf.WriteUint16(12345) } } - bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) + bf.WriteUint32(uint32(gametime.Adjusted().Unix())) // ClanMemberLimits requires at least 1 element with 2 columns to avoid index out of range panics // Use default value (60) if array is empty or last row is too small diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index ee45ba0a2..f019988fb 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -5,7 +5,7 @@ import ( ps "erupe-ce/common/pascalstring" "erupe-ce/common/stringsupport" _config "erupe-ce/config" - "erupe-ce/server/channelserver" + "erupe-ce/common/gametime" "fmt" "strings" "time" @@ -50,7 +50,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte { bf.WriteUint8(uint8(len(chars))) bf.WriteUint32(tokenID) bf.WriteBytes([]byte(sessToken)) - bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) + bf.WriteUint32(uint32(gametime.Adjusted().Unix())) if s.client == PS3 { ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerManifest), false) ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerFile), false) @@ -334,7 +334,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte { if s.client == VITA || s.client == PS3 || s.client == PS4 { var psnUser string - s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser) + _ = s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser) bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true)) } @@ -385,11 +385,11 @@ func (s *Session) makeSignResponse(uid uint32) []byte { } // We can just use the start timestamp as the event ID - bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix())) + bf.WriteUint32(uint32(gametime.WeekStart().Unix())) // Start time - bf.WriteUint32(uint32(channelserver.TimeWeekNext().Add(-time.Duration(s.server.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix())) + bf.WriteUint32(uint32(gametime.WeekNext().Add(-time.Duration(s.server.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix())) // End time - bf.WriteUint32(uint32(channelserver.TimeWeekNext().Unix())) + bf.WriteUint32(uint32(gametime.WeekNext().Unix())) bf.WriteUint8(uint8(len(tickets))) for i := range tickets { bf.WriteUint32(tickets[i])