Adding modding folder with a hacked build.

This commit is contained in:
ml
2026-09-26 08:53:08 +02:00
parent cdb960dc71
commit c91080aba7
755 changed files with 50424 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
package serial
import (
"io"
)
const bufferCapacityIncrement = 1024 * 1024
// ByteStore is implementing a ReadWriteSeeker storing data in memory.
type ByteStore struct {
data []byte
offset int
}
// NewByteStore returns a new byte store instance.
func NewByteStore() *ByteStore {
return NewByteStoreFromData(make([]byte, 0, bufferCapacityIncrement))
}
// NewByteStoreFromData returns a new ByteStore instance wrapping the provided data.
func NewByteStoreFromData(data []byte) *ByteStore {
store := &ByteStore{
data: data,
offset: 0,
}
return store
}
// Data returns the current data buffer.
func (store *ByteStore) Data() []byte {
return store.data
}
// Seek implements the Seeker interface.
func (store *ByteStore) Seek(offset int64, whence int) (int64, error) {
switch whence {
case 0:
store.offset = int(offset)
case 1:
store.offset += int(offset)
case 2:
store.offset = len(store.data) + int(offset)
}
return int64(store.offset), nil
}
// Read implements the Reader interface.
func (store *ByteStore) Read(p []byte) (n int, err error) {
size := len(p)
n = len(store.data) - store.offset
if n > size {
n = size
}
if n < size && size > 0 {
err = io.EOF
}
copy(p, store.data[store.offset:store.offset+n])
store.offset += n
return
}
// Write implements the Writer interface.
func (store *ByteStore) Write(p []byte) (n int, err error) {
size := len(p)
store.ensureAvailable(size)
copy(store.data[store.offset:], p)
store.offset += size
return size, nil
}
func (store *ByteStore) ensureAvailable(size int) {
expected := store.offset + size
available := len(store.data)
if expected > available {
if expected > cap(store.data) {
old := store.data
store.data = make([]byte, expected, expected+bufferCapacityIncrement)
copy(store.data, old)
} else {
store.data = append(store.data, make([]byte, expected-available)...)
}
}
}
+7
View File
@@ -0,0 +1,7 @@
package serial
// Codable is something than can be serialized with a Coder.
type Codable interface {
// Code requests to serialize the Codable via the provided Coder.
Code(coder Coder)
}
+12
View File
@@ -0,0 +1,12 @@
package serial
// Coder represents an Encoder/Decoder for binary data.
type Coder interface {
// FirstError returns the first error this coder encountered.
FirstError() error
// Code serializes the given value.
// Any error state will be provided via FirstError(). Code will do nothing
// if the coder is already in error state.
Code(value interface{})
}
+58
View File
@@ -0,0 +1,58 @@
package serial
import (
"encoding/binary"
"errors"
"io"
)
// Decoder is for decoding from a reader.
type Decoder struct {
source io.Reader
firstError error
offset uint32
}
// NewDecoder creates a new Decoder from given source.
func NewDecoder(source io.Reader) *Decoder {
return &Decoder{source: source}
}
// FirstError returns the error this Decoder encountered the first time.
func (coder *Decoder) FirstError() error {
return coder.firstError
}
// CurPos returns the current decoding position, in bytes.
func (coder *Decoder) CurPos() uint32 {
return coder.offset
}
// Code serializes the given value in little endian format using binary.Read().
func (coder *Decoder) Code(value interface{}) {
if coder.firstError != nil {
return
}
if codable, isCodable := value.(Codable); isCodable {
codable.Code(coder)
} else {
coder.firstError = binary.Read(coder, binary.LittleEndian, value)
}
}
// Read reads the next bytes from the underlying source.
func (coder *Decoder) Read(data []byte) (read int, err error) {
if coder.firstError != nil {
return 0, coder.firstError
}
read, err = coder.source.Read(data)
coder.offset += uint32(read)
isErrEOF := errors.Is(err, io.EOF)
expectedAmountReturned := read == len(data)
errorCanBeIgnored := isErrEOF && expectedAmountReturned
if (coder.firstError == nil) && !errorCanBeIgnored {
coder.firstError = err
}
return
}
+138
View File
@@ -0,0 +1,138 @@
package serial_test
import (
"bytes"
"io"
"testing"
"github.com/inkyblackness/hacked/ss1/serial"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type DecoderSuite struct {
suite.Suite
errorBuf *errorBuffer
coder *serial.Decoder
}
func TestDecoderSuite(t *testing.T) {
suite.Run(t, new(DecoderSuite))
}
func (suite *DecoderSuite) SetupTest() {
suite.errorBuf = nil
suite.coder = nil
}
func (suite *DecoderSuite) TestImplementsCoderInterface() {
instance := interface{}(suite.coder)
_, ok := instance.(serial.Coder)
assert.True(suite.T(), ok)
}
func (suite *DecoderSuite) TestCodeUint32() {
suite.whenDecodingFrom([]byte{0x78, 0x56, 0x34, 0x12})
var value uint32
suite.coder.Code(&value)
assert.Equal(suite.T(), uint32(0x12345678), value)
}
func (suite *DecoderSuite) TestCodeUint16() {
suite.whenDecodingFrom([]byte{0x45, 0x23})
var value uint16
suite.coder.Code(&value)
assert.Equal(suite.T(), uint16(0x2345), value)
}
func (suite *DecoderSuite) TestCodeByte() {
suite.whenDecodingFrom([]byte{0xAB})
var value byte
suite.coder.Code(&value)
assert.Equal(suite.T(), byte(0xAB), value)
}
func (suite *DecoderSuite) TestCodeByteSlice() {
suite.whenDecodingFrom([]byte{0x78, 0x12, 0x34})
value := make([]byte, 3)
suite.coder.Code(value)
assert.Equal(suite.T(), []byte{0x78, 0x12, 0x34}, value)
}
func (suite *DecoderSuite) TestFirstErrorReturnsFirstError() {
suite.whenDecodingWithErrors()
data := uint32(0)
suite.coder.Code(&data)
suite.errorBuf.errorOnNextCall = true
suite.coder.Code(uint16(0))
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 2")
}
func (suite *DecoderSuite) TestFirstErrorIgnoresFurtherErrors() {
suite.whenDecodingWithErrors()
suite.errorBuf.errorOnNextCall = true
suite.coder.Code(uint32(0))
suite.errorBuf.errorOnNextCall = true
suite.coder.Code(uint32(0))
assert.Equal(suite.T(), suite.errorBuf.callCounter, 1)
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1")
}
func (suite *DecoderSuite) TestImplementsReaderInterface() {
instance := interface{}(suite.coder)
_, ok := instance.(io.Reader)
assert.True(suite.T(), ok)
}
func (suite *DecoderSuite) TestReadFromReader() {
suite.whenDecodingFrom([]byte{0x78, 0x12, 0x34})
value := make([]byte, 3)
read, err := suite.coder.Read(value)
assert.Nil(suite.T(), err, "no error expected")
assert.Equal(suite.T(), 3, read, "unexpected length read")
assert.Equal(suite.T(), []byte{0x78, 0x12, 0x34}, value)
}
func (suite *DecoderSuite) TestReadHandlesErrors() {
suite.whenDecodingWithErrors()
suite.errorBuf.errorOnNextCall = true
_, _ = suite.coder.Read(make([]byte, 3))
_, _ = suite.coder.Read(make([]byte, 5))
assert.Equal(suite.T(), 1, suite.errorBuf.callCounter)
assert.NotNil(suite.T(), suite.coder.FirstError())
}
func (suite *DecoderSuite) TestCodeWithCodable() {
codable := new(MockedCodable)
suite.whenDecodingFrom([]byte{0x78, 0x12, 0x34})
suite.coder.Code(codable)
assert.Equal(suite.T(), suite.coder, codable.calledCoder, "Should be called with coder")
}
func (suite *DecoderSuite) whenDecodingFrom(data []byte) {
source := bytes.NewReader(data)
suite.coder = serial.NewDecoder(source)
}
func (suite *DecoderSuite) whenDecodingWithErrors() {
suite.errorBuf = new(errorBuffer)
suite.coder = serial.NewDecoder(suite.errorBuf)
}
+56
View File
@@ -0,0 +1,56 @@
package serial
import (
"encoding/binary"
"io"
)
// Encoder implements the Coder interface to write to a writer.
// It also implements the Writer interface.
type Encoder struct {
target io.Writer
firstError error
offset uint32
}
// NewEncoder creates and returns a fresh Encoder.
func NewEncoder(target io.Writer) *Encoder {
return &Encoder{target: target}
}
// FirstError returns the error this Encoder encountered the first time.
func (coder *Encoder) FirstError() error {
return coder.firstError
}
// CurPos returns the current encoding position, in bytes.
func (coder *Encoder) CurPos() uint32 {
return coder.offset
}
// Code serializes the given value in little endian format using binary.Write().
func (coder *Encoder) Code(value interface{}) {
if coder.firstError != nil {
return
}
if codable, isCodable := value.(Codable); isCodable {
codable.Code(coder)
} else {
coder.firstError = binary.Write(coder, binary.LittleEndian, value)
}
}
// Write serializes the given data to the contained writer.
// If the encoder is in the error state, the result of FirstError() is
// returned and nothing else is done.
func (coder *Encoder) Write(data []byte) (written int, err error) {
if coder.firstError != nil {
return 0, coder.firstError
}
written, err = coder.target.Write(data)
coder.offset += uint32(written)
if coder.firstError == nil {
coder.firstError = err
}
return
}
+157
View File
@@ -0,0 +1,157 @@
package serial_test
import (
"io"
"testing"
"github.com/inkyblackness/hacked/ss1/serial"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type EncoderSuite struct {
suite.Suite
store *serial.ByteStore
coder *serial.Encoder
}
func TestEncoderSuite(t *testing.T) {
suite.Run(t, new(EncoderSuite))
}
func (suite *EncoderSuite) SetupTest() {
suite.store = serial.NewByteStore()
suite.coder = serial.NewEncoder(suite.store)
}
func (suite *EncoderSuite) TestImplementsCoderInterface() {
instance := interface{}(suite.coder)
_, ok := instance.(serial.Coder)
assert.True(suite.T(), ok)
}
func (suite *EncoderSuite) TestDataOnEmptyEncoderReturnsEmptyArray() {
suite.verifyData(make([]byte, 0))
}
func (suite *EncoderSuite) TestCodeUint32Pointer() {
var value uint32 = 0x12345678
suite.verifyCodedData(&value, []byte{0x78, 0x56, 0x34, 0x12})
}
func (suite *EncoderSuite) TestCodeUint32() {
var value uint32 = 0x12345678
suite.verifyCodedData(value, []byte{0x78, 0x56, 0x34, 0x12})
}
func (suite *EncoderSuite) TestCodeUint16Pointer() {
var value uint16 = 0x3456
suite.verifyCodedData(&value, []byte{0x56, 0x34})
}
func (suite *EncoderSuite) TestCodeUint16() {
var value uint16 = 0x3456
suite.verifyCodedData(value, []byte{0x56, 0x34})
}
func (suite *EncoderSuite) TestCodeBytePointer() {
var value byte = 0x42
suite.verifyCodedData(&value, []byte{0x42})
}
func (suite *EncoderSuite) TestCodeByte() {
var value byte = 0x42
suite.verifyCodedData(value, []byte{0x42})
}
func (suite *EncoderSuite) TestCodeByteSlice() {
value := []byte{0x01, 0x02, 0x03}
suite.verifyCodedData(value, []byte{0x01, 0x02, 0x03})
}
func (suite *EncoderSuite) TestFirstErrorByCode() {
var target errorBuffer
suite.coder = serial.NewEncoder(&target)
suite.coder.Code(uint16(0))
target.errorOnNextCall = true
suite.coder.Code(uint32(0))
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 2")
}
func (suite *EncoderSuite) TestCodeDoesNothingOnPreviousError() {
var target errorBuffer
suite.coder = serial.NewEncoder(&target)
target.errorOnNextCall = true
suite.coder.Code(uint32(0))
target.errorOnNextCall = true
suite.coder.Code(uint32(0))
assert.Equal(suite.T(), target.callCounter, 1)
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1")
}
func (suite *EncoderSuite) TestImplementsWriterInterface() {
instance := interface{}(suite.coder)
_, ok := instance.(io.Writer)
assert.True(suite.T(), ok)
}
func (suite *EncoderSuite) TestWriteToWriter() {
written, err := suite.coder.Write([]byte{0x0A, 0x0B, 0x0C})
assert.Nil(suite.T(), err, "no error expected")
assert.Equal(suite.T(), 3, written, "unexpected length written")
suite.verifyData([]byte{0x0A, 0x0B, 0x0C})
}
func (suite *EncoderSuite) TestWriteDoesNothingOnPreviousError() {
var target errorBuffer
suite.coder = serial.NewEncoder(&target)
target.errorOnNextCall = true
suite.coder.Code(uint32(0))
_, _ = suite.coder.Write([]byte{0x0A, 0x0B, 0x0C})
assert.Equal(suite.T(), target.callCounter, 1)
}
func (suite *EncoderSuite) TestWriteReturnsFirstErrorOnPreviousError() {
var target errorBuffer
suite.coder = serial.NewEncoder(&target)
target.errorOnNextCall = true
suite.coder.Code(uint32(0))
n, err := suite.coder.Write([]byte{0x0A, 0x0B, 0x0C})
assert.Equal(suite.T(), 0, n, "Should return zero written bytes")
assert.EqualError(suite.T(), err, "errorBuffer on call number 1")
}
func (suite *EncoderSuite) TestWriteOnErrorCase() {
var target errorBuffer
suite.coder = serial.NewEncoder(&target)
target.errorOnNextCall = true
target.errorByteCount = 2
n, err := suite.coder.Write([]byte{0x0A, 0x0B, 0x0C})
assert.Equal(suite.T(), 2, n, "Should return count of successfully written bytes")
assert.EqualError(suite.T(), err, "errorBuffer on call number 1")
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1", "should set first error")
}
func (suite *EncoderSuite) TestCodeWithCodable() {
codable := new(MockedCodable)
suite.coder.Code(codable)
assert.Equal(suite.T(), suite.coder, codable.calledCoder, "Should be called with coder")
}
func (suite *EncoderSuite) verifyData(expected []byte) {
assert.Nil(suite.T(), suite.coder.FirstError())
result := suite.store.Data()
assert.Equal(suite.T(), expected, result)
}
func (suite *EncoderSuite) verifyCodedData(value interface{}, expected []byte) {
suite.coder.Code(value)
suite.verifyData(expected)
}
@@ -0,0 +1,11 @@
package serial_test
import "github.com/inkyblackness/hacked/ss1/serial"
type MockedCodable struct {
calledCoder serial.Coder
}
func (codable *MockedCodable) Code(coder serial.Coder) {
codable.calledCoder = coder
}
+9
View File
@@ -0,0 +1,9 @@
package serial
// Positioner specifies a type that knows about a position which can be modified.
type Positioner interface {
// CurPos gets the current position in the data.
CurPos() uint32
// SetCurPos sets the current position in the data.
SetCurPos(offset uint32)
}
@@ -0,0 +1,9 @@
package serial
// PositioningCoder is a coder that also knows about positioning.
// Any error state regarding repositioning will be provided via FirstError().
// SetCurPos will do nothing if the coder is already in error state.
type PositioningCoder interface {
Positioner
Coder
}
@@ -0,0 +1,27 @@
package serial
import "io"
// PositioningDecoder is a Decoder with positioning capabilities.
type PositioningDecoder struct {
Decoder
seeker io.Seeker
}
// NewPositioningDecoder creates a new PositioningDecoder from given reader.
func NewPositioningDecoder(source io.ReadSeeker) *PositioningDecoder {
return &PositioningDecoder{Decoder: Decoder{source: source, offset: 0}, seeker: source}
}
// SetCurPos changes the encoding position to the specified absolute offset.
func (coder *PositioningDecoder) SetCurPos(offset uint32) {
if coder.firstError != nil {
return
}
_, coder.firstError = coder.seeker.Seek(int64(offset), io.SeekStart)
if coder.firstError != nil {
return
}
coder.offset = offset
}
@@ -0,0 +1,64 @@
package serial_test
import (
"bytes"
"testing"
"github.com/inkyblackness/hacked/ss1/serial"
"github.com/stretchr/testify/assert"
)
func TestPositioningDecoderSetCurPosRepositionsReadOffset(t *testing.T) {
var source = bytes.NewReader([]byte{0x78, 0x12, 0x34})
coder := serial.NewPositioningDecoder(source)
arrayValue := make([]byte, 3)
var intValue uint16
coder.Code(arrayValue)
coder.SetCurPos(1)
coder.Code(&intValue)
assert.Equal(t, uint16(0x3412), intValue)
}
func TestPositioningDecoderCurPosReturnsCurrentOffset(t *testing.T) {
var source = bytes.NewReader([]byte{0x78, 0x12, 0x34})
coder := serial.NewPositioningDecoder(source)
coder.Code(uint16(0))
assert.Equal(t, uint32(2), coder.CurPos())
}
func TestPositioningDecoderFirstErrorBySetCurPos(t *testing.T) {
var target errorBuffer
coder := serial.NewPositioningDecoder(&target)
data := uint32(0)
coder.Code(&data)
target.errorOnNextCall = true
coder.SetCurPos(0)
assert.EqualError(t, coder.FirstError(), "errorBuffer on call number 2")
}
func TestPositioningDecoderSetCurPosDoesNothingOnPreviousError(t *testing.T) {
var target errorBuffer
coder := serial.NewPositioningDecoder(&target)
target.errorOnNextCall = true
coder.Code(uint32(0))
target.errorOnNextCall = true
coder.SetCurPos(0)
assert.Equal(t, target.callCounter, 1)
assert.EqualError(t, coder.FirstError(), "errorBuffer on call number 1")
}
func TestCurPosIsNotChangedOnSetError(t *testing.T) {
var target errorBuffer
coder := serial.NewPositioningDecoder(&target)
target.errorOnNextCall = true
coder.SetCurPos(4)
assert.Equal(t, uint32(0), coder.CurPos())
}
@@ -0,0 +1,57 @@
package serial
import (
"io"
"github.com/inkyblackness/hacked/ss1"
)
// PositioningEncoder is an Encoder with positioning capabilities.
type PositioningEncoder struct {
Encoder
seeker io.Seeker
}
// NewPositioningEncoder returns an Encoder that also implements the Positioner interface.
// The new Encoder starts with its zero position at the current position in the writer.
func NewPositioningEncoder(target io.WriteSeeker) *PositioningEncoder {
return &PositioningEncoder{Encoder: Encoder{target: target}, seeker: target}
}
// SetCurPos changes the encoding position to the specified absolute offset.
func (coder *PositioningEncoder) SetCurPos(offset uint32) {
if coder.firstError != nil {
return
}
_, coder.firstError = coder.seeker.Seek(int64(offset)-int64(coder.offset), io.SeekCurrent)
if coder.firstError != nil {
return
}
coder.offset = offset
}
const (
errInvalidWhence ss1.StringError = "seek: invalid whence"
errSeekBeforeStart ss1.StringError = "seek: seeking before start"
)
// Seek repositions the current encoding offset.
// This implementation does not support a whence value of io.SeekEnd.
func (coder *PositioningEncoder) Seek(offset int64, whence int) (int64, error) {
var newPosition int64
switch whence {
default:
return int64(coder.offset), errInvalidWhence
case io.SeekStart:
newPosition = offset
case io.SeekCurrent:
newPosition = int64(coder.offset) + offset
}
if newPosition < 0 {
return int64(coder.offset), errSeekBeforeStart
}
coder.SetCurPos(uint32(newPosition))
return int64(coder.offset), coder.firstError
}
@@ -0,0 +1,161 @@
package serial_test
import (
"io"
"testing"
"github.com/inkyblackness/hacked/ss1/serial"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type PositioningEncoderSuite struct {
suite.Suite
coder *serial.PositioningEncoder
store *serial.ByteStore
}
func TestPositioningEncoderSuite(t *testing.T) {
suite.Run(t, new(PositioningEncoderSuite))
}
func (suite *PositioningEncoderSuite) SetupTest() {
suite.store = serial.NewByteStore()
suite.coder = serial.NewPositioningEncoder(suite.store)
}
func (suite *PositioningEncoderSuite) TestImplementsPositioningCoderInterface() {
instance := interface{}(suite.coder)
_, ok := instance.(serial.PositioningCoder)
assert.True(suite.T(), ok)
}
func (suite *PositioningEncoderSuite) TestCurPosReturnsCurrentOffset() {
suite.coder.Code(uint32(0))
assert.Equal(suite.T(), uint32(4), suite.coder.CurPos())
}
func (suite *PositioningEncoderSuite) TestSetCurPosRepositionsWritePointer() {
suite.coder.Code(uint32(0))
suite.coder.SetCurPos(0)
suite.coder.Code(uint32(0x13243546))
result := suite.store.Data()
assert.Equal(suite.T(), []byte{0x46, 0x35, 0x24, 0x13}, result)
}
func (suite *PositioningEncoderSuite) TestFirstErrorBySetCurPos() {
var target errorBuffer
suite.coder = serial.NewPositioningEncoder(&target)
suite.coder.Code(uint32(0))
target.errorOnNextCall = true
suite.coder.SetCurPos(0)
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 2")
}
func (suite *PositioningEncoderSuite) TestSetCurPosDoesNothingOnPreviousError() {
var target errorBuffer
suite.coder = serial.NewPositioningEncoder(&target)
target.errorOnNextCall = true
suite.coder.Code(uint32(0))
target.errorOnNextCall = true
suite.coder.SetCurPos(0)
assert.Equal(suite.T(), target.callCounter, 1)
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1")
}
func (suite *PositioningEncoderSuite) TestCurPosIsNotChangedOnSetError() {
var target errorBuffer
suite.coder = serial.NewPositioningEncoder(&target)
target.errorOnNextCall = true
suite.coder.SetCurPos(4)
assert.Equal(suite.T(), uint32(0), suite.coder.CurPos())
}
func (suite *PositioningEncoderSuite) TestCurPosIsRelativeToConstructionTime() {
_, _ = suite.store.Write([]byte{0x01, 0x02, 0x03})
assert.Equal(suite.T(), uint32(0), suite.coder.CurPos(), "CurPos should be zero at the start")
suite.coder.Code(uint16(0xAAAA))
suite.coder.SetCurPos(1)
suite.coder.Code(byte(0xCC))
result := suite.store.Data()
assert.Equal(suite.T(), []byte{0x01, 0x02, 0x03, 0xAA, 0xCC}, result, "SetCurPos did not work relative to original start")
}
func (suite *PositioningEncoderSuite) TestImplementsSeekerInterface() {
instance := interface{}(suite.coder)
_, ok := instance.(io.Seeker)
assert.True(suite.T(), ok)
}
func (suite *PositioningEncoderSuite) TestSeekReturnsErrorForWrongWhence() {
pos, err := suite.coder.Seek(1, 1234)
assert.Equal(suite.T(), int64(0), pos, "zero position should be returned")
assert.EqualError(suite.T(), err, "seek: invalid whence")
}
func (suite *PositioningEncoderSuite) TestSeekAbsolute() {
suite.coder.Code(uint32(0))
pos, err := suite.coder.Seek(1, io.SeekStart)
assert.Nil(suite.T(), err, "no error expected")
assert.Equal(suite.T(), int64(1), pos, "invalid position returned")
suite.coder.Code(uint16(0xAABB))
result := suite.store.Data()
assert.Equal(suite.T(), []byte{0x00, 0xBB, 0xAA, 0x00}, result)
assert.Equal(suite.T(), uint32(3), suite.coder.CurPos(), "should update current position")
}
func (suite *PositioningEncoderSuite) TestSeekRelative() {
suite.coder.Code(uint32(0))
pos, err := suite.coder.Seek(-1, io.SeekCurrent)
assert.Equal(suite.T(), int64(3), pos, "should return new position")
assert.Nil(suite.T(), err, "should have no error")
suite.coder.Code(uint16(0xAABB))
result := suite.store.Data()
assert.Equal(suite.T(), []byte{0x00, 0x00, 0x00, 0xBB, 0xAA}, result)
assert.Equal(suite.T(), uint32(5), suite.coder.CurPos(), "should update current position")
}
func (suite *PositioningEncoderSuite) TestSeekDoesNothingOnPreviousError() {
var target errorBuffer
suite.coder = serial.NewPositioningEncoder(&target)
target.errorOnNextCall = true
suite.coder.Code(uint32(0))
_, _ = suite.coder.Seek(0, io.SeekStart)
assert.Equal(suite.T(), target.callCounter, 1)
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1")
}
func (suite *PositioningEncoderSuite) TestSeekRegistersReturnedError() {
var target errorBuffer
suite.coder = serial.NewPositioningEncoder(&target)
suite.coder.Code(uint32(0))
target.errorOnNextCall = true
_, _ = suite.coder.Seek(1, io.SeekStart)
pos, err := suite.coder.Seek(1, io.SeekStart)
assert.Equal(suite.T(), int64(4), pos, "Should return old position value for seek")
assert.EqualError(suite.T(), err, "errorBuffer on call number 2")
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 2", "should set first error")
}
func (suite *PositioningEncoderSuite) TestSeekReturnsErrorForSeekBeforeStart() {
pos, err := suite.coder.Seek(-1, io.SeekStart)
assert.Equal(suite.T(), int64(0), pos, "Should return old position value for seek")
assert.EqualError(suite.T(), err, "seek: seeking before start")
}
@@ -0,0 +1,10 @@
package serial
import "io"
// SeekingReadCloser combines the interfaces of a Reader, a Seeker and a Closer.
type SeekingReadCloser interface {
io.Reader
io.Seeker
io.Closer
}
@@ -0,0 +1,10 @@
package serial
import "io"
// SeekingWriteCloser combines the interfaces of a Writer, a Seeker and a Closer.
type SeekingWriteCloser interface {
io.Writer
io.Seeker
io.Closer
}
+4
View File
@@ -0,0 +1,4 @@
/*
Package serial provides serialization utilities for nested serialization.
*/
package serial
@@ -0,0 +1,56 @@
package serial_test
import (
"fmt"
"io"
"github.com/inkyblackness/hacked/ss1"
)
type errorBuffer struct {
position int64
callCounter int
errorOnNextCall bool
errorByteCount int
}
func (buf *errorBuffer) check() (err error) {
buf.callCounter++
if buf.errorOnNextCall {
buf.errorOnNextCall = false
err = ss1.StringError(fmt.Sprintf("errorBuffer on call number %v", buf.callCounter))
}
return
}
func (buf *errorBuffer) Seek(offset int64, whence int) (int64, error) {
err := buf.check()
if err != nil {
return 0, err
}
switch whence {
case io.SeekStart:
buf.position = offset
case io.SeekCurrent:
buf.position += offset
case io.SeekEnd:
panic("SeekEnd not supported")
}
return buf.position, nil
}
func (buf *errorBuffer) Read(p []byte) (n int, err error) {
err = buf.check()
if err != nil {
return buf.errorByteCount, err
}
return len(p), nil
}
func (buf *errorBuffer) Write(p []byte) (n int, err error) {
err = buf.check()
if err != nil {
return buf.errorByteCount, err
}
return len(p), nil
}
+177
View File
@@ -0,0 +1,177 @@
package rle
import "io"
// Compress compresses the given byte array into the given writer.
// The optional reference array is used as a delta basis. If provided, bytes will be skipped
// where the data equals the reference.
func Compress(writer io.Writer, data []byte, reference []byte) error {
end := len(data)
refLen := len(reference)
ref := func(index int) byte {
if index < refLen {
return reference[index]
}
return 0x00
}
countIdenticalBytes := func(from int) int {
index := from
for (index < end) && (data[index] == ref(index)) {
index++
}
return index - from
}
countConstantBytes := func(from int, value byte) int {
index := from
for (index < end) && (data[index] == value) {
index++
}
return index - from
}
{
trailingSkip := 0
for temp := end - 1; (temp >= 0) && (data[temp] == ref(temp)); temp-- {
trailingSkip++
}
end -= trailingSkip % 0x7FFF
}
start := 0
for start < end {
identicalByteCount := countIdenticalBytes(start)
constByteCount := countConstantBytes(start, data[start])
switch {
case identicalByteCount > 3:
err := skipBytes(writer, identicalByteCount)
if err != nil {
return err
}
start += identicalByteCount
case constByteCount > 3:
err := writeConstant(writer, constByteCount, data[start])
if err != nil {
return err
}
start += constByteCount
default:
diffByteCount := 0
abort := false
for (start+diffByteCount) < end && !abort {
nextIdenticalByteCount := countIdenticalBytes(start + diffByteCount)
nextConstByteCount := countConstantBytes(start+diffByteCount, data[start+diffByteCount])
if nextIdenticalByteCount < 4 && nextConstByteCount < 4 {
if nextIdenticalByteCount > nextConstByteCount {
diffByteCount += nextIdenticalByteCount
} else {
diffByteCount += nextConstByteCount
}
} else {
abort = true
}
}
err := writeRaw(writer, data[start:start+diffByteCount])
if err != nil {
return err
}
start += diffByteCount
}
}
return writeExtended(writer, 0x0000)
}
func writeExtended(writer io.Writer, control uint16, extra ...byte) error {
_, err := writer.Write([]byte{0x80, byte(control & 0xFF), byte((control >> 8) & 0xFF)})
if err != nil {
return err
}
_, err = writer.Write(extra)
return err
}
func skipBytes(writer io.Writer, size int) error {
remain := size
for remain > 0 {
if remain < 0x80 {
_, err := writer.Write([]byte{byte(0x80 + remain)})
if err != nil {
return err
}
remain = 0
} else {
lenControl := 0x7FFF
if remain < lenControl {
lenControl = remain
}
err := writeExtended(writer, uint16(lenControl))
if err != nil {
return err
}
remain -= lenControl
}
}
return nil
}
func writeConstant(writer io.Writer, size int, value byte) error {
start := 0
for start < size {
remain := size - start
if remain < 0x100 {
_, err := writer.Write([]byte{0x00, byte(remain), value})
if err != nil {
return err
}
start = size
} else {
lenControl := 0x3FFF
if remain < lenControl {
lenControl = remain
}
err := writeExtended(writer, 0xC000+uint16(lenControl), value)
if err != nil {
return err
}
start += lenControl
}
}
return nil
}
func writeRaw(writer io.Writer, data []byte) error {
end := len(data)
start := 0
for start < end {
remain := end - start
if remain < 0x80 {
_, err := writer.Write([]byte{byte(remain)})
if err != nil {
return err
}
_, err = writer.Write(data[start:])
if err != nil {
return err
}
start = end
} else {
lenControl := 0x3FFF
if remain < lenControl {
lenControl = remain
}
err := writeExtended(writer, 0x8000+uint16(lenControl), data[start:start+lenControl]...)
if err != nil {
return err
}
start += lenControl
}
}
return nil
}
@@ -0,0 +1,79 @@
package rle_test
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/inkyblackness/hacked/ss1/serial/rle"
)
func TestCompressEmptyArrayResultsInTerminator(t *testing.T) {
writer := bytes.NewBuffer(nil)
err := rle.Compress(writer, nil, nil)
assert.Nil(t, err, "no error expected")
assert.Equal(t, []byte{0x80, 0x00, 0x00}, writer.Bytes())
}
func TestCompressOfZeroBytesLong(t *testing.T) {
writer := bytes.NewBuffer(nil)
input := make([]byte, 1000)
input[len(input)-1] = 1
err := rle.Compress(writer, input, nil)
assert.Nil(t, err, "no error expected")
assert.Equal(t, []byte{0x80, 0xE7, 0x03, 0x01, 0x01, 0x80, 0x0, 0x0}, writer.Bytes())
}
func TestCompressOfEqualBytesLong(t *testing.T) {
writer := bytes.NewBuffer(nil)
input := make([]byte, 1000)
for i := 0; i < len(input); i++ {
input[i] = 0xAA
}
input[len(input)-1] = 1
err := rle.Compress(writer, input, nil)
assert.Nil(t, err, "no error expected")
assert.Equal(t, []byte{0x80, 0xE7, 0xC3, 0xAA, 0x01, 0x01, 0x80, 0x0, 0x0}, writer.Bytes())
}
func TestCompressOfEqualBytesShort(t *testing.T) {
writer := bytes.NewBuffer(nil)
input := make([]byte, 40)
for i := 0; i < len(input); i++ {
input[i] = 0xAA
}
input[len(input)-1] = 1
err := rle.Compress(writer, input, nil)
assert.Nil(t, err, "no error expected")
assert.Equal(t, []byte{0x00, 0x27, 0xAA, 0x01, 0x01, 0x80, 0x0, 0x0}, writer.Bytes())
}
func TestCompressOfRandomBytesShort(t *testing.T) {
writer := bytes.NewBuffer(nil)
input := make([]byte, 10)
for i := 0; i < len(input); i++ {
input[i] = byte(i)
}
err := rle.Compress(writer, input, nil)
assert.Nil(t, err, "no error expected")
assert.Equal(t, []byte{0x0A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x80, 0x00, 0x00}, writer.Bytes())
}
func TestCompressWithIdenticalReference(t *testing.T) {
writer := bytes.NewBuffer(nil)
input := []byte{0xAA, 0xBB, 0xCC, 0xDD}
reference := input
err := rle.Compress(writer, input, reference)
assert.Nil(t, err, "no error expected")
assert.Equal(t, []byte{0x80, 0x00, 0x00}, writer.Bytes())
}
func TestCompressWithPartlyIdenticalReference(t *testing.T) {
writer := bytes.NewBuffer(nil)
input := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}
reference := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x0A}
err := rle.Compress(writer, input, reference)
assert.Nil(t, err, "no error expected")
assert.Equal(t, []byte{0x85, 0x01, 0xFF, 0x80, 0x00, 0x00}, writer.Bytes())
}
@@ -0,0 +1,67 @@
package rle
import (
"io"
"github.com/inkyblackness/hacked/ss1"
)
const (
errUndefinedCase ss1.StringError = "undefined case 80 nn C0"
)
// Decompress decompresses from the given reader and writes into the provided output buffer.
// The output buffer must be pre-allocated.
// If it contains non-zero data, this data may be preserved if the compressed data specifies to.
func Decompress(reader io.Reader, output []byte) (err error) {
outIndex := 0
done := false
nextByte := func() byte {
zz := []byte{0x00}
_, err = reader.Read(zz)
return zz[0]
}
for !done && (err == nil) {
first := nextByte()
switch {
case first == 0x00:
nn := nextByte()
zz := nextByte()
outIndex += writeBytesOfValue(output[outIndex:outIndex+int(nn)], func() byte { return zz })
case first < 0x80:
outIndex += writeBytesOfValue(output[outIndex:outIndex+int(first)], nextByte)
case first == 0x80:
control := uint16(nextByte())
control += uint16(nextByte()) << 8
switch {
case control == 0x0000:
done = true
case control < 0x8000:
outIndex += int(control)
case control < 0xC000:
outIndex += writeBytesOfValue(output[outIndex:outIndex+int(control&0x3FFF)], nextByte)
case (control & 0xFF00) == 0xC000:
err = errUndefinedCase
default:
zz := nextByte()
outIndex += writeBytesOfValue(output[outIndex:outIndex+int(control&0x3FFF)], func() byte { return zz })
}
default:
outIndex += int(first & 0x7F)
}
}
return
}
func writeBytesOfValue(buffer []byte, producer func() byte) int {
count := len(buffer)
for i := 0; i < count; i++ {
buffer[i] = producer()
}
return count
}
@@ -0,0 +1,146 @@
package rle_test
import (
"bytes"
"testing"
"github.com/inkyblackness/hacked/ss1/serial/rle"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecompressEmptyArrayReturnsError(t *testing.T) {
err := rle.Decompress(bytes.NewReader(nil), make([]byte, 100))
assert.NotNil(t, err)
}
func TestDecompress800000IsEndOfStream(t *testing.T) {
var result []byte
err := rle.Decompress(bytes.NewReader([]byte{0x80, 0x00, 0x00}), result)
assert.Nil(t, err)
}
func TestDecompress800000AppendsRemainingZeroes(t *testing.T) {
result := make([]byte, 10)
err := rle.Decompress(bytes.NewReader([]byte{0x80, 0x00, 0x00}), result)
require.Nil(t, err)
assert.Equal(t, make([]byte, 10), result)
}
func TestDecompress800000IsConsumedAtEnd(t *testing.T) {
var result []byte
reader := bytes.NewReader([]byte{0x80, 0x00, 0x00})
err := rle.Decompress(reader, result)
require.Nil(t, err)
pos, _ := reader.Seek(0, 1)
assert.Equal(t, int64(3), pos)
}
func TestDecompress00WritesNNBytesOfColorZZ(t *testing.T) {
result := make([]byte, 5)
err := rle.Decompress(bytes.NewReader([]byte{0x00, 0x05, 0xCC, 0x80, 0x00, 0x00}), result)
require.Nil(t, err)
assert.Equal(t, []byte{0xCC, 0xCC, 0xCC, 0xCC, 0xCC}, result)
}
func TestDecompress00ReturnsErrorIfZZIsMissing(t *testing.T) {
err := rle.Decompress(bytes.NewReader([]byte{0x00, 0x05}), make([]byte, 5))
assert.NotNil(t, err)
}
func TestDecompress00ReturnsErrorIfNNIsMissing(t *testing.T) {
err := rle.Decompress(bytes.NewReader([]byte{0x00}), make([]byte, 5))
assert.NotNil(t, err)
}
func TestDecompressNNLess80WritesNNFollowingBytes(t *testing.T) {
result := make([]byte, 2)
err := rle.Decompress(bytes.NewReader([]byte{0x02, 0xAA, 0xBB, 0x80, 0x00, 0x00}), result)
require.Nil(t, err)
assert.Equal(t, []byte{0xAA, 0xBB}, result)
}
func TestDecompressNNLess80ReturnsErrorIfEndOfFile(t *testing.T) {
err := rle.Decompress(bytes.NewReader([]byte{0x02, 0xAA}), make([]byte, 2))
assert.NotNil(t, err)
}
func TestDecompress80NNNNSkipsBytes(t *testing.T) {
expected := make([]byte, 0x123)
result := make([]byte, 0x123)
for i := 0; i < len(result); i++ {
result[i] = byte(i)
}
copy(expected, result)
err := rle.Decompress(bytes.NewReader([]byte{0x80, 0x23, 0x01, 0x80, 0x00, 0x00}), result)
require.Nil(t, err)
assert.Equal(t, expected, result)
}
func TestDecompress80CopiesNNBytes(t *testing.T) {
input := bytes.NewBuffer(nil)
input.Write([]byte{0x80, 0x04, 0x80})
input.Write([]byte{0x01, 0x02, 0x03, 0x04})
input.Write([]byte{0x80, 0x00, 0x00})
result := make([]byte, 4)
err := rle.Decompress(bytes.NewReader(input.Bytes()), result)
require.Nil(t, err)
assert.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, result)
}
func TestDecompress80CopiesNNBytesExtended(t *testing.T) {
input := bytes.NewBuffer(nil)
expected := make([]byte, 0x3FFF)
input.Write([]byte{0x80, 0xFF, 0xBF})
input.Write(expected)
input.Write([]byte{0x80, 0x00, 0x00})
result := make([]byte, len(expected))
err := rle.Decompress(bytes.NewReader(input.Bytes()), result)
require.Nil(t, err)
assert.Equal(t, expected, result)
}
func TestDecompress80ReturnsErrorForUndefinedCase(t *testing.T) {
input := bytes.NewBuffer(nil)
input.Write([]byte{0x80, 0x00, 0xC0})
input.Write([]byte{0x80, 0x00, 0x00})
err := rle.Decompress(bytes.NewReader(input.Bytes()), make([]byte, 1))
assert.NotNil(t, err)
}
func TestDecompress80WritesNNBytesOfValue(t *testing.T) {
input := bytes.NewBuffer(nil)
expected := make([]byte, 0x3FFF)
for index := range expected {
expected[index] = 0xCD
}
input.Write([]byte{0x80, 0xFF, 0xFF, 0xCD})
input.Write([]byte{0x80, 0x00, 0x00})
result := make([]byte, len(expected))
err := rle.Decompress(bytes.NewReader(input.Bytes()), result)
require.Nil(t, err)
assert.Equal(t, expected, result)
}
func TestDecompressNNMore80WritesZeroes(t *testing.T) {
result := make([]byte, 3)
err := rle.Decompress(bytes.NewReader([]byte{0x83, 0x80, 0x00, 0x00}), result)
require.Nil(t, err)
assert.Equal(t, []byte{0, 0, 0}, result)
}
@@ -0,0 +1,83 @@
package rle_test
import (
"bytes"
"math/rand"
"testing"
"time"
"github.com/inkyblackness/hacked/ss1/serial/rle"
)
func benchmarkRecompression(b *testing.B, size int, chanceLimit byte, nameSuffix string, seed int64) {
b.Helper()
input := make([]byte, size)
reference := make([]byte, size)
var chance [1]byte
rand.Seed(seed) // nolint:gas
b.ResetTimer()
for run := 0; run < b.N; run++ {
b.StopTimer()
rand.Read(input) // nolint:gas
copy(reference, input)
for i := 0; i < size; i++ {
rand.Read(chance[:]) // nolint:gas
if chance[0] < chanceLimit {
input[i] = chance[0]
}
}
buf := bytes.NewBuffer(nil)
b.StartTimer()
err := rle.Compress(buf, input, reference)
if err != nil {
b.Errorf("Failed compression for %s in run %d of seed %v", nameSuffix, run, seed)
}
err = rle.Decompress(bytes.NewReader(buf.Bytes()), reference)
if err != nil {
b.Errorf("Failed decompression for %s in run %d of seed %v", nameSuffix, run, seed)
}
if !bytes.Equal(input, reference) {
b.Errorf("Data mismatch in %s in run %d of seed %v", nameSuffix, run, seed)
}
}
}
func BenchmarkRecompression64KB_10(b *testing.B) {
benchmarkRecompression(b, 1024*64, 10, "64KB,10", time.Now().UnixNano())
}
func BenchmarkRecompression64KB_128(b *testing.B) {
benchmarkRecompression(b, 1024*64, 128, "64KB,128", time.Now().UnixNano())
}
func BenchmarkRecompression1KB_0(b *testing.B) {
benchmarkRecompression(b, 1024, 0, "1KB,0", time.Now().UnixNano())
}
func BenchmarkRecompression1KB_10(b *testing.B) {
benchmarkRecompression(b, 1024, 10, "1KB,10", time.Now().UnixNano())
}
func BenchmarkRecompression1KB_128(b *testing.B) {
benchmarkRecompression(b, 1024, 128, "1KB,128", time.Now().UnixNano())
}
func BenchmarkRecompression1KB_255(b *testing.B) {
benchmarkRecompression(b, 1024, 255, "1KB,255", time.Now().UnixNano())
}
func BenchmarkRecompression128B_10(b *testing.B) {
benchmarkRecompression(b, 128, 10, "128B,10", time.Now().UnixNano())
}
func BenchmarkRecompression128B_128(b *testing.B) {
benchmarkRecompression(b, 128, 128, "128B,128", time.Now().UnixNano())
}
func BenchmarkRecompression128B_255(b *testing.B) {
benchmarkRecompression(b, 128, 255, "128B,255", time.Now().UnixNano())
}