Adding modding folder with a hacked build.
This commit is contained in:
@@ -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())
|
||||
}
|
||||
Reference in New Issue
Block a user