Skip to content

Commit

Permalink
Add version 3 of package with old version 2 contents
Browse files Browse the repository at this point in the history
Due to the way golang handles major versions > v1, the package path *must* contain the major version in the import/module path. This means that importing module version 2 did not work because there was no v2 in the path.

To get around this while still maintaining semantic versioning, a package version 3 was created in top-level directory 'v3'. This means import paths for v3 tags will now contain the 'v3' suffix as expected.

Additional enhancements were made to the package for v3. Since a breaking change was going to be introduced anyway, some of the package files were reorganized to remove the verbose 'pkg/eolib/...' imports - the directories are all at the top of v3 together now.
  • Loading branch information
ethanmoffat committed Jun 7, 2024
1 parent 3a9f9b2 commit 9526960
Show file tree
Hide file tree
Showing 69 changed files with 36,603 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "eo-protocol"]
path = eo-protocol
url = https://www.github.com/cirras/eo-protocol
[submodule "eo-protocol-v3"]
path = v3/eo-protocol
url = https://www.github.com/cirras/eo-protocol
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ install:

generate:
@go install ./cmd/protocol-gen
@go generate ./...
@go generate .
@go install ./v3/cmd/protocol-gen-v3
@go generate ./v3

clean:
@rm $$(go env GOPATH)/bin/protocol-gen
Expand Down
2 changes: 1 addition & 1 deletion package.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// eolibgo is the top-level package for the Go implementation of eolib.
//
// More details are available in the eolib subdirectory: [pkg/github.com/ethanmoffat/eolib-go/pkg/eolib]
// See the v3 version for more details: [pkg/github.com/ethanmoffat/eolib-go/v3]
package eolibgo
129 changes: 129 additions & 0 deletions v3/cmd/protocol-gen-v3/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package main

import (
"encoding/xml"
"flag"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"strings"

"github.com/ethanmoffat/eolib-go/v3/internal/codegen"
eoxml "github.com/ethanmoffat/eolib-go/v3/internal/xml"
)

var inputDir string
var outputDir string

func main() {
flag.StringVar(&inputDir, "i", "eo-protocol", "The input directory for eo-protocol files.")
flag.StringVar(&outputDir, "o", "protocol", "The output directory for generated code.")
flag.Parse()

if _, err := os.Stat(inputDir); err != nil {
fmt.Printf("error: input directory %s does not exist\n", inputDir)
os.Exit(1)
}

if _, err := os.Stat(outputDir); err != nil {
fmt.Printf("error: output directory %s does not exist\n", outputDir)
os.Exit(1)
}

fmt.Printf("Using parameters:\n inputDir: %s\n outputDir: %s\n", inputDir, outputDir)

protocolFiles := []string{}
filepath.WalkDir(path.Join(inputDir, "xml"), func(currentPath string, d fs.DirEntry, err error) error {
if path.Ext(currentPath) == ".xml" {
relativeDir := strings.ReplaceAll(currentPath, path.Join(inputDir, "xml"), "")
protocolFiles = append(protocolFiles, strings.ReplaceAll(relativeDir, "/protocol.xml", ""))
}
return nil
})

dirToPackageName := map[string]string{
"map": "eomap",
"net": "net",
"net/client": "client",
"net/server": "server",
"pub": "pub",
"pub/server": "serverpub",
"": "protocol",
}

var fullSpec eoxml.Protocol // all XML specs in a single place, for type lookups
var protocs []eoxml.Protocol // each individual protoc file
for _, file := range protocolFiles {
fullInputPath := path.Join(inputDir, "xml", file, "protocol.xml")

fp, err := os.Open(fullInputPath)
if err != nil {
fmt.Printf("error opening file: %v\n", err)
os.Exit(1)
}
defer fp.Close()

bytes, err := io.ReadAll(fp)
if err != nil {
fmt.Printf("error reading file: %v\n", err)
os.Exit(1)
}

var next eoxml.Protocol
if err := xml.Unmarshal(bytes, &next); err != nil {
fmt.Printf("error unmarshalling xml: %v\n", err)
os.Exit(1)
}

for i := range next.Enums {
next.Enums[i].Package = dirToPackageName[strings.Trim(file, string(os.PathSeparator))]
next.Enums[i].PackagePath = file
}

for i := range next.Structs {
next.Structs[i].Package = dirToPackageName[strings.Trim(file, string(os.PathSeparator))]
next.Structs[i].PackagePath = file
}

for i := range next.Packets {
next.Packets[i].Package = dirToPackageName[strings.Trim(file, string(os.PathSeparator))]
next.Packets[i].PackagePath = file
}

fullSpec.Enums = append(fullSpec.Enums, next.Enums...)
fullSpec.Structs = append(fullSpec.Structs, next.Structs...)
fullSpec.Packets = append(fullSpec.Packets, next.Packets...)

protocs = append(protocs, next)
}

for i, file := range protocolFiles {
protoc := protocs[i]

if err := protoc.Validate(); err != nil {
fmt.Printf("error validating unmarshalled xml: %v\n", err)
os.Exit(1)
}

fullOutputPath := path.Join(outputDir, file)

fmt.Printf("generating code :: %s\n", file)
fmt.Printf(" %3d enums\n", len(protoc.Enums))
if err := codegen.GenerateEnums(fullOutputPath, protoc.Enums); err != nil {
fmt.Printf(" error generating enums: %v\n", err)
}

fmt.Printf(" %3d structs\n", len(protoc.Structs))
if err := codegen.GenerateStructs(fullOutputPath, protoc.Structs, fullSpec); err != nil {
fmt.Printf(" error generating structs: %v\n", err)
}

fmt.Printf(" %3d packets\n", len(protoc.Packets))
if err := codegen.GeneratePackets(fullOutputPath, protoc.Packets, fullSpec); err != nil {
fmt.Printf(" error generating packets: %v\n", err)
}
}
}
98 changes: 98 additions & 0 deletions v3/data/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package data

import "github.com/ethanmoffat/eolib-go/v3/utils"

// EncodeNumber encodes a number to a sequence of bytes.
func EncodeNumber(number int) []byte {
value := number

d := 0xFE
if number >= THREE_MAX {
d = value/THREE_MAX + 1
value = value % THREE_MAX
}

c := 0xFE
if number >= SHORT_MAX {
c = value/SHORT_MAX + 1
value = value % SHORT_MAX
}

b := 0xFE
if number >= CHAR_MAX {
b = value/CHAR_MAX + 1
value = value % CHAR_MAX
}

a := value + 1

return []byte{byte(a), byte(b), byte(c), byte(d)}
}

// DecodeNumber decodes a number from a sequence of bytes.
func DecodeNumber(bytes []byte) int {
result := 0
length := utils.Min(len(bytes), 4)

for i := 0; i < length; i++ {
value := int(bytes[i])

if value == 0xFE {
break
}

value--

if i == 0 {
result += value
} else if i == 1 {
result += CHAR_MAX * value
} else if i == 2 {
result += SHORT_MAX * value
} else if i == 3 {
result += THREE_MAX * value
}
}

return result
}

// EncodeString encodes a string by inverting the bytes and then reversing them.
func EncodeString(str []byte) []byte {
inverted := invert(str)
return utils.Reverse(inverted)
}

// DecodeString decodes a string by reversing the bytes and then inverting them.
func DecodeString(bytes []byte) []byte {
reversed := utils.Reverse(bytes)
return invert(reversed)
}

func invert(bytes []byte) []byte {
flippy := len(bytes)%2 == 1

retBytes := make([]byte, len(bytes))
copy(retBytes, bytes)

for i, c := range retBytes {
retBytes[i] = c

f := 0

if flippy {
f = 0x2E
if c >= 0x50 {
f *= -1
}
}

if c >= 0x22 && c <= 0x7E {
retBytes[i] = 0x9F - c - byte(f)
}

flippy = !flippy
}

return retBytes
}
117 changes: 117 additions & 0 deletions v3/data/encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package data_test

import (
"fmt"
"os"
"testing"

"github.com/ethanmoffat/eolib-go/v3/data"
"github.com/stretchr/testify/assert"
"golang.org/x/text/encoding/charmap"
)

type encodeNumberParameters struct {
a byte
b byte
c byte
d byte
number int
}

type encodeStringParameters struct {
decoded string
encoded string
}

var encodeNumberTestCases []encodeNumberParameters
var encodeStringTestCases []encodeStringParameters

func TestMain(m *testing.M) {
encodeNumberTestCases = []encodeNumberParameters{
{0x01, 0xFE, 0xFE, 0xFE, 0},
{0x02, 0xFE, 0xFE, 0xFE, 1},
{0x1D, 0xFE, 0xFE, 0xFE, 28},
{0x65, 0xFE, 0xFE, 0xFE, 100},
{0x81, 0xFE, 0xFE, 0xFE, 128},
{0xFD, 0xFE, 0xFE, 0xFE, 252},
{0x01, 0x02, 0xFE, 0xFE, 253},
{0x02, 0x02, 0xFE, 0xFE, 254},
{0x03, 0x02, 0xFE, 0xFE, 255},
{0x7E, 0x7F, 0xFE, 0xFE, 32003},
{0x7F, 0x7F, 0xFE, 0xFE, 32004},
{0x80, 0x7F, 0xFE, 0xFE, 32005},
{0xFD, 0xFD, 0xFE, 0xFE, 64008},
{0x01, 0x01, 0x02, 0xFE, 64009},
{0x02, 0x01, 0x02, 0xFE, 64010},
{0xB0, 0x3A, 0x9D, 0xFE, 10_000_000},
{0xFD, 0xFD, 0xFD, 0xFE, 16_194_276},
{0x01, 0x01, 0x01, 0x02, 16_194_277},
{0x02, 0x01, 0x01, 0x02, 16_194_278},
{0x7E, 0x7F, 0x7F, 0x7F, 2_048_576_039},
{0x7F, 0x7F, 0x7F, 0x7F, 2_048_576_040},
{0x80, 0x7F, 0x7F, 0x7F, 2_048_576_041},
{0xFC, 0xFD, 0xFD, 0xFD, int(4097152079)},
{0xFD, 0xFD, 0xFD, 0xFD, int(4097152080)},
}

encodeStringTestCases = []encodeStringParameters{
{"Hello, World!", "!;a-^H s^3a:)"},
{"We're ¼ of the way there, so ¾ is remaining.", "C8_6_6l2h- ,d ¾ ^, sh-h7Y T>V h7Y g0 ¼ :[xhH"},
{"64² = 4096", ";fAk b ²=i"},
{"© FÒÖ BÃR BÅZ 2014", "=nAm EÅ] MÃ] ÖÒY ©"},
{"Öxxö Xööx \"Lëïth Säë\" - \"Ÿ\"", "OŸO D OëäL 7YïëSO UööG öU'Ö"},
{"Padded with 0xFFÿÿÿÿÿÿÿÿ", "ÿÿÿÿÿÿÿÿ+YUo 7Y6V i:i;lO"},
}

os.Exit(m.Run())
}

func TestEncodeNumber(t *testing.T) {
for _, tc := range encodeNumberTestCases {
t.Run(fmt.Sprintf("%d should encode to [%d, %d, %d, %d]", tc.number, tc.a, tc.b, tc.c, tc.d),
func(t *testing.T) {
actual := data.EncodeNumber(tc.number)
assert.Equal(t, []byte{tc.a, tc.b, tc.c, tc.d}, actual)
})
}
}

func TestDecodeNumber(t *testing.T) {
for _, tc := range encodeNumberTestCases {
t.Run(fmt.Sprintf("[%d, %d, %d, %d] should decode to %d", tc.a, tc.b, tc.c, tc.d, tc.number),
func(t *testing.T) {
actual := data.DecodeNumber([]byte{tc.a, tc.b, tc.c, tc.d})
assert.Equal(t, tc.number, actual)
})
}
}

func TestEncodeString(t *testing.T) {
for _, tc := range encodeStringTestCases {
t.Run(fmt.Sprintf("%s should encode to %s", tc.decoded, tc.encoded),
func(t *testing.T) {
bytes := toBytes(tc.decoded)
encoded := data.EncodeString(bytes)
assert.Equal(t, toBytes(tc.encoded), encoded)
})
}
}

func TestDecodeString(t *testing.T) {
for _, tc := range encodeStringTestCases {
t.Run(fmt.Sprintf("%s should decode to %s", tc.encoded, tc.decoded),
func(t *testing.T) {
bytes := toBytes(tc.encoded)
decoded := data.DecodeString(bytes)
assert.Equal(t, toBytes(tc.decoded), decoded)
})
}
}

func toBytes(input string) (ret []byte) {
for _, r := range input {
next, _ := charmap.Windows1252.EncodeRune(r)
ret = append(ret, next)
}
return
}
9 changes: 9 additions & 0 deletions v3/data/limits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package data

// Numeric maximum values for different EO data sizes
const (
CHAR_MAX int = 253 // represents the maximum value of an EO char (1-byte encoded integer)
SHORT_MAX int = CHAR_MAX * CHAR_MAX // represents the maximum value of an EO short (2-byte encoded integer)
THREE_MAX int = CHAR_MAX * CHAR_MAX * CHAR_MAX // represents the maximum value of an EO three (3-byte encoded integer)
INT_MAX int = SHORT_MAX * SHORT_MAX // represents the maximum value of an EO int (4-byte encoded integer)
)
2 changes: 2 additions & 0 deletions v3/data/package.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Packate data provides utilities to read and write EO data types.
package data
Loading

0 comments on commit 9526960

Please sign in to comment.