381 lines
8.6 KiB
Go
381 lines
8.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/binary"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
)
|
|
|
|
// TReadBuffer
|
|
// ==============================================================================
|
|
type TReadBuffer struct {
|
|
Buffer []byte
|
|
Position int
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) CanRead(Bytes int) bool {
|
|
return ReadBuffer.Position+Bytes <= len(ReadBuffer.Buffer)
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) Overflowed() bool {
|
|
return ReadBuffer.Position > len(ReadBuffer.Buffer)
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) ReadFlag() bool {
|
|
return ReadBuffer.Read8() != 0
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) Read8() uint8 {
|
|
Result := uint8(0)
|
|
if ReadBuffer.CanRead(1) {
|
|
Result = ReadBuffer.Buffer[ReadBuffer.Position]
|
|
}
|
|
ReadBuffer.Position += 1
|
|
return Result
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) Read16() uint16 {
|
|
Result := uint16(0)
|
|
if ReadBuffer.CanRead(2) {
|
|
Result = binary.LittleEndian.Uint16(ReadBuffer.Buffer[ReadBuffer.Position:])
|
|
}
|
|
ReadBuffer.Position += 2
|
|
return Result
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) Read16BE() uint16 {
|
|
Result := uint16(0)
|
|
if ReadBuffer.CanRead(2) {
|
|
Result = binary.BigEndian.Uint16(ReadBuffer.Buffer[ReadBuffer.Position:])
|
|
}
|
|
ReadBuffer.Position += 2
|
|
return Result
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) Read32() uint32 {
|
|
Result := uint32(0)
|
|
if ReadBuffer.CanRead(4) {
|
|
Result = binary.LittleEndian.Uint32(ReadBuffer.Buffer[ReadBuffer.Position:])
|
|
}
|
|
ReadBuffer.Position += 4
|
|
return Result
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) Read32BE() uint32 {
|
|
Result := uint32(0)
|
|
if ReadBuffer.CanRead(4) {
|
|
Result = binary.BigEndian.Uint32(ReadBuffer.Buffer[ReadBuffer.Position:])
|
|
}
|
|
ReadBuffer.Position += 4
|
|
return Result
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) ReadString() string {
|
|
Length := int(ReadBuffer.Read16())
|
|
if Length == 0xFFFF {
|
|
Length = int(ReadBuffer.Read32())
|
|
}
|
|
|
|
Result := ""
|
|
if ReadBuffer.CanRead(Length) {
|
|
Result = string(ReadBuffer.Buffer[ReadBuffer.Position:][:Length])
|
|
}
|
|
ReadBuffer.Position += Length
|
|
return Result
|
|
}
|
|
|
|
func (ReadBuffer *TReadBuffer) ReadBytes(Count int) []byte {
|
|
Result := []byte(nil)
|
|
if ReadBuffer.CanRead(Count) {
|
|
Result = ReadBuffer.Buffer[ReadBuffer.Position:][:Count]
|
|
}
|
|
ReadBuffer.Position += Count
|
|
return Result
|
|
}
|
|
|
|
// TWriteBuffer
|
|
// ==============================================================================
|
|
type TWriteBuffer struct {
|
|
Buffer []byte
|
|
Position int
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) CanWrite(Bytes int) bool {
|
|
return (WriteBuffer.Position + Bytes) <= len(WriteBuffer.Buffer)
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) Overflowed() bool {
|
|
return WriteBuffer.Position > len(WriteBuffer.Buffer)
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) WriteFlag(Value bool) {
|
|
Value8 := uint8(0)
|
|
if Value {
|
|
Value8 = uint8(1)
|
|
}
|
|
WriteBuffer.Write8(Value8)
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) Write8(Value uint8) {
|
|
if WriteBuffer.CanWrite(1) {
|
|
WriteBuffer.Buffer[WriteBuffer.Position] = Value
|
|
}
|
|
WriteBuffer.Position += 1
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) Write16(Value uint16) {
|
|
if WriteBuffer.CanWrite(2) {
|
|
binary.LittleEndian.PutUint16(WriteBuffer.Buffer[WriteBuffer.Position:], Value)
|
|
}
|
|
WriteBuffer.Position += 2
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) Write16BE(Value uint16) {
|
|
if WriteBuffer.CanWrite(2) {
|
|
binary.BigEndian.PutUint16(WriteBuffer.Buffer[WriteBuffer.Position:], Value)
|
|
}
|
|
WriteBuffer.Position += 2
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) Write32(Value uint32) {
|
|
if WriteBuffer.CanWrite(4) {
|
|
binary.LittleEndian.PutUint32(WriteBuffer.Buffer[WriteBuffer.Position:], Value)
|
|
}
|
|
WriteBuffer.Position += 4
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) Write32BE(Value uint32) {
|
|
if WriteBuffer.CanWrite(4) {
|
|
binary.BigEndian.PutUint32(WriteBuffer.Buffer[WriteBuffer.Position:], Value)
|
|
}
|
|
WriteBuffer.Position += 4
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) WriteString(String string) {
|
|
Length := len(String)
|
|
if Length < 0xFFFF {
|
|
WriteBuffer.Write16(uint16(Length))
|
|
} else {
|
|
WriteBuffer.Write16(0xFFFF)
|
|
WriteBuffer.Write32(uint32(Length))
|
|
}
|
|
|
|
if WriteBuffer.CanWrite(Length) {
|
|
copy(WriteBuffer.Buffer[WriteBuffer.Position:], String)
|
|
}
|
|
WriteBuffer.Position += Length
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) WriteBytes(Bytes []byte) {
|
|
Count := len(Bytes)
|
|
if WriteBuffer.CanWrite(Count) {
|
|
copy(WriteBuffer.Buffer[WriteBuffer.Position:], Bytes)
|
|
}
|
|
WriteBuffer.Position += Count
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) Rewrite16(Position int, Value uint16) {
|
|
if (Position+2) <= WriteBuffer.Position && !WriteBuffer.Overflowed() {
|
|
binary.LittleEndian.PutUint16(WriteBuffer.Buffer[Position:], Value)
|
|
}
|
|
}
|
|
|
|
func (WriteBuffer *TWriteBuffer) Insert32(Position int, Value uint32) {
|
|
if Position <= WriteBuffer.Position {
|
|
if WriteBuffer.CanWrite(4) {
|
|
copy(WriteBuffer.Buffer[Position+4:], WriteBuffer.Buffer[Position:])
|
|
binary.LittleEndian.PutUint32(WriteBuffer.Buffer[Position:], Value)
|
|
}
|
|
WriteBuffer.Position += 4
|
|
}
|
|
}
|
|
|
|
// Config
|
|
// ==============================================================================
|
|
func ParseBoolean(String string) bool {
|
|
return strings.EqualFold(String, "true")
|
|
}
|
|
|
|
func ParseInteger(String string) int {
|
|
Result, Err := strconv.Atoi(String)
|
|
if Err != nil {
|
|
g_LogErr.Printf("Failed to parse integer \"%v\": %v", String, Err)
|
|
}
|
|
return Result
|
|
}
|
|
|
|
func ParseIntegerSuffix(String string) (int, string) {
|
|
SuffixStart := -1
|
|
for Index, Rune := range String {
|
|
if !unicode.IsDigit(Rune) {
|
|
SuffixStart = Index
|
|
break
|
|
}
|
|
}
|
|
|
|
var Value, Suffix string
|
|
if SuffixStart != -1 {
|
|
Value = strings.TrimSpace(String[:SuffixStart])
|
|
Suffix = strings.TrimSpace(String[SuffixStart:])
|
|
} else {
|
|
Value = String
|
|
Suffix = ""
|
|
}
|
|
|
|
Result, Err := strconv.Atoi(Value)
|
|
if Err != nil {
|
|
g_LogErr.Printf("Failed to parse duration \"%v\": %v", String, Err)
|
|
}
|
|
|
|
return Result, Suffix
|
|
}
|
|
|
|
func ParseDuration(String string) time.Duration {
|
|
Value, Suffix := ParseIntegerSuffix(String)
|
|
Result := time.Duration(Value) * time.Millisecond
|
|
if len(Suffix) > 0 {
|
|
switch Suffix[0] {
|
|
case 'S', 's':
|
|
Result = time.Duration(Value) * time.Second
|
|
case 'M', 'm':
|
|
Result = time.Duration(Value) * time.Minute
|
|
case 'H', 'h':
|
|
Result = time.Duration(Value) * time.Hour
|
|
}
|
|
}
|
|
return Result
|
|
}
|
|
|
|
func ParseSize(String string) int {
|
|
Result, Suffix := ParseIntegerSuffix(String)
|
|
if len(Suffix) > 0 {
|
|
switch Suffix[0] {
|
|
case 'K', 'k':
|
|
Result *= 1024
|
|
case 'M', 'm':
|
|
Result *= (1024 * 1024)
|
|
}
|
|
}
|
|
return Result
|
|
}
|
|
|
|
func ParseString(String string) string {
|
|
if len(String) > 2 {
|
|
if String[0] == '"' && String[len(String)-1] == '"' ||
|
|
String[0] == '\'' && String[len(String)-1] == '\'' ||
|
|
String[0] == '`' && String[len(String)-1] == '`' {
|
|
String = String[1 : len(String)-1]
|
|
}
|
|
}
|
|
return String
|
|
}
|
|
|
|
func ReadConfig(FileName string, KVCallback func(string, string)) bool {
|
|
File, Err := os.Open(FileName)
|
|
if Err != nil {
|
|
g_LogErr.Print(Err)
|
|
return false
|
|
}
|
|
defer File.Close()
|
|
|
|
Scanner := bufio.NewScanner(File)
|
|
for LineNumber := 1; Scanner.Scan(); LineNumber += 1 {
|
|
Line := strings.TrimSpace(Scanner.Text())
|
|
if len(Line) == 0 || Line[0] == '#' {
|
|
continue
|
|
}
|
|
|
|
Key, Value, Ok := strings.Cut(Scanner.Text(), "=")
|
|
if !Ok {
|
|
g_LogErr.Printf("%v:%v: No assignment found on non empty line", FileName, LineNumber)
|
|
continue
|
|
}
|
|
|
|
Key = strings.TrimSpace(Key)
|
|
if len(Key) == 0 {
|
|
g_LogErr.Printf("%v:%v: Empty key", FileName, LineNumber)
|
|
continue
|
|
}
|
|
|
|
Value = strings.TrimSpace(Value)
|
|
if len(Value) == 0 {
|
|
g_LogErr.Printf("%v:%v: Empty value", FileName, LineNumber)
|
|
continue
|
|
}
|
|
|
|
KVCallback(Key, Value)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Utility
|
|
// ==============================================================================
|
|
func FileExists(FileName string) bool {
|
|
_, Err := os.Stat(FileName)
|
|
return Err == nil
|
|
}
|
|
|
|
func JoinHostPort(Host string, Port int) string {
|
|
PortString := strconv.Itoa(Port)
|
|
return net.JoinHostPort(Host, PortString)
|
|
}
|
|
|
|
func SplitDiscardEmpty(String string, Sep string) []string {
|
|
var Result []string
|
|
for String != "" {
|
|
SubStr := ""
|
|
Index := strings.Index(String, Sep)
|
|
if Index == -1 {
|
|
SubStr = String
|
|
String = ""
|
|
} else {
|
|
SubStr = String[:Index]
|
|
String = String[Index+1:]
|
|
}
|
|
|
|
if SubStr != "" {
|
|
Result = append(Result, SubStr)
|
|
}
|
|
}
|
|
return Result
|
|
}
|
|
|
|
func SwapAndPop[T any](Slice []T, Index int) []T {
|
|
if len(Slice) == 0 {
|
|
panic("slice is empty")
|
|
}
|
|
|
|
var Zero T
|
|
Slice[Index] = Slice[len(Slice)-1]
|
|
Slice[len(Slice)-1] = Zero
|
|
return Slice[:len(Slice)-1]
|
|
}
|
|
|
|
func WorldTypeString(Type int) string {
|
|
switch Type {
|
|
case 0:
|
|
return "Normal"
|
|
case 1:
|
|
return "Non-PvP"
|
|
case 2:
|
|
return "PvP-Enforced"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
func TimestampString(Timestamp int) string {
|
|
String := "Never"
|
|
if Timestamp > 0 {
|
|
Time := time.Unix(int64(Timestamp), 0)
|
|
String = Time.Format("Jan 02 2006, 15:04:05 MST")
|
|
}
|
|
return String
|
|
}
|