diff --git a/.gitignore b/.gitignore index d680a30..353c4bd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ bin build local *.log +config.cfg diff --git a/common.go b/common.go index 0676487..0430dcf 100644 --- a/common.go +++ b/common.go @@ -9,6 +9,7 @@ import ( "strings" "time" "unicode" + "unicode/utf8" ) // TReadBuffer @@ -83,7 +84,11 @@ func (ReadBuffer *TReadBuffer) ReadString() string { Result := "" if ReadBuffer.CanRead(Length) { - Result = string(ReadBuffer.Buffer[ReadBuffer.Position:][:Length]) + // IMPORTANT(fusion): The game server uses LATIN1 encoding, which forces + // the query manager to use LATIN1 encoding for text, at least on the + // protocol level. + Input := ReadBuffer.Buffer[ReadBuffer.Position:][:Length] + Result = string(Latin1ToUTF8(Input)) } ReadBuffer.Position += Length return Result @@ -157,7 +162,11 @@ func (WriteBuffer *TWriteBuffer) Write32BE(Value uint32) { } func (WriteBuffer *TWriteBuffer) WriteString(String string) { - Length := len(String) + // IMPORTANT(fusion): The game server uses LATIN1 encoding, which forces + // the query manager to use LATIN1 encoding for text, at least on the + // protocol level. + Output := UTF8ToLatin1([]byte(String)) + Length := len(Output) if Length < 0xFFFF { WriteBuffer.Write16(uint16(Length)) } else { @@ -166,7 +175,7 @@ func (WriteBuffer *TWriteBuffer) WriteString(String string) { } if WriteBuffer.CanWrite(Length) { - copy(WriteBuffer.Buffer[WriteBuffer.Position:], String) + copy(WriteBuffer.Buffer[WriteBuffer.Position:], Output) } WriteBuffer.Position += Length } @@ -370,7 +379,7 @@ func WorldTypeString(Type int) string { } } -func TimestampString(Timestamp int) string { +func FormatTimestamp(Timestamp int) string { String := "Never" if Timestamp > 0 { Time := time.Unix(int64(Timestamp), 0) @@ -378,3 +387,53 @@ func TimestampString(Timestamp int) string { } return String } + +func FormatDurationSince(Timestamp int) string { + String := "N/A" + if Timestamp > 0{ + Duration := time.Since(time.Unix(int64(Timestamp), 0)) + String = Duration.Truncate(time.Second).String() + } + return String +} + +func UTF8FindNextLeadingByte(Buffer []byte) int { + Offset := 0 + for Offset < len(Buffer) { + // NOTE(fusion): Allow the first byte to be a leading byte, in case we + // just want to advance from one leading byte to another. + if(Offset > 0 && utf8.RuneStart(Buffer[Offset])){ + break + } + Offset += 1 + } + return Offset +} + +func UTF8ToLatin1(Buffer []byte) []byte { + ReadPos := 0 + Result := []byte{} + for ReadPos < len(Buffer) { + Codepoint, Size := utf8.DecodeRune(Buffer[ReadPos:]) + if Codepoint != utf8.RuneError { + ReadPos += Size + }else{ + ReadPos += UTF8FindNextLeadingByte(Buffer[ReadPos:]) + } + + if Codepoint >= 0 && Codepoint <= 0xFF { + Result = append(Result, byte(Codepoint)) + }else{ + Result = append(Result, '?') + } + } + return Result +} + +func Latin1ToUTF8(Buffer []byte) []byte { + Result := []byte{} + for ReadPos := range Buffer { + Result = utf8.AppendRune(Result, rune(Buffer[ReadPos])) + } + return Result +} diff --git a/config.cfg b/config.cfg.dist similarity index 100% rename from config.cfg rename to config.cfg.dist diff --git a/main.go b/main.go index 679096f..e2d16ad 100644 --- a/main.go +++ b/main.go @@ -563,7 +563,7 @@ func HandleWorld(Context *THttpRequestContext) { } func main() { - g_Log.Print("Tibia Web Server v0.1") + g_Log.Print("Tibia Web Server v0.2") if !ReadConfig("config.cfg", WebKVCallback) { return } diff --git a/query.go b/query.go index 907e77f..3abca9a 100644 --- a/query.go +++ b/query.go @@ -45,8 +45,10 @@ type ( Type string NumPlayers int MaxPlayers int - OnlineRecord int - OnlineRecordTime string + OnlinePeak int + OnlinePeakTimestamp int + LastStartup int + LastShutdown int } TAccountSummary struct { @@ -77,7 +79,7 @@ type ( Level int Profession string Residence string - LastLogin string + LastLogin int PremiumDays int Online bool Deleted bool @@ -389,7 +391,7 @@ func (Connection *TQueryManagerConnection) GetCharacterProfile(CharacterName str Character.Level = int(ReadBuffer.Read16()) Character.Profession = ReadBuffer.ReadString() Character.Residence = ReadBuffer.ReadString() - Character.LastLogin = TimestampString(int(ReadBuffer.Read32())) + Character.LastLogin = int(ReadBuffer.Read32()) Character.PremiumDays = int(ReadBuffer.Read16()) Character.Online = ReadBuffer.ReadFlag() Character.Deleted = ReadBuffer.ReadFlag() @@ -422,8 +424,10 @@ func (Connection *TQueryManagerConnection) GetWorlds() (Result int, Worlds []TWo Worlds[Index].Type = WorldTypeString(int(ReadBuffer.Read8())) Worlds[Index].NumPlayers = int(ReadBuffer.Read16()) Worlds[Index].MaxPlayers = int(ReadBuffer.Read16()) - Worlds[Index].OnlineRecord = int(ReadBuffer.Read16()) - Worlds[Index].OnlineRecordTime = TimestampString(int(ReadBuffer.Read32())) + Worlds[Index].OnlinePeak = int(ReadBuffer.Read16()) + Worlds[Index].OnlinePeakTimestamp = int(ReadBuffer.Read32()) + Worlds[Index].LastStartup = int(ReadBuffer.Read32()) + Worlds[Index].LastShutdown = int(ReadBuffer.Read32()) } } default: diff --git a/templates.go b/templates.go index 1f47782..5281c14 100644 --- a/templates.go +++ b/templates.go @@ -58,7 +58,13 @@ var ( func InitTemplates() bool { var Err error - g_Templates, Err = template.ParseGlob("templates/*.tmpl") + + CustomFuncs := template.FuncMap{ + "FormatTimestamp": FormatTimestamp, + "FormatDurationSince": FormatDurationSince, + } + + g_Templates, Err = template.New("").Funcs(CustomFuncs).ParseGlob("templates/*.tmpl") if Err != nil { g_LogErr.Printf("Failed to parse templates: %v", Err) return false diff --git a/templates/character_create.tmpl b/templates/character_create.tmpl index c243f2d..1a235dd 100644 --- a/templates/character_create.tmpl +++ b/templates/character_create.tmpl @@ -1,6 +1,6 @@ {{template "_header.tmpl" .Common}}
-

Create Account

+

Create Character

diff --git a/templates/character_profile.tmpl b/templates/character_profile.tmpl index 824e908..2711e03 100644 --- a/templates/character_profile.tmpl +++ b/templates/character_profile.tmpl @@ -35,7 +35,7 @@ Last Login: - {{.LastLogin}} + {{FormatTimestamp .LastLogin}} Account Status: diff --git a/templates/world_info.tmpl b/templates/world_info.tmpl index 578a0f7..353b674 100644 --- a/templates/world_info.tmpl +++ b/templates/world_info.tmpl @@ -16,13 +16,24 @@ {{.NumPlayers}} - Online Record: - {{if .OnlineRecord}} - {{.OnlineRecord}} players (on {{.OnlineRecordTime}}) + Online Peak: + {{if eq .OnlinePeak 1}} + {{.OnlinePeak}} player (on {{FormatTimestamp .OnlinePeakTimestamp}}) + {{else if gt .OnlinePeak 1}} + {{.OnlinePeak}} players (on {{FormatTimestamp .OnlinePeakTimestamp}}) {{else}} None {{end}} + + {{if gt .LastStartup .LastShutdown}} + Uptime: + {{FormatDurationSince .LastStartup}} + {{else}} + Downtime: + {{FormatDurationSince .LastShutdown}} + {{end}} + Kill Statistics {{else}} diff --git a/templates/world_list.tmpl b/templates/world_list.tmpl index de9786c..07494b6 100644 --- a/templates/world_list.tmpl +++ b/templates/world_list.tmpl @@ -7,12 +7,18 @@ Name Type Players Online + Status {{range .Worlds}} {{.Name}} {{.Type}} {{.NumPlayers}} + {{if gt .LastStartup .LastShutdown}} + Online + {{else}} + Offline + {{end}} {{end}}