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
@@ -0,0 +1,84 @@
package compression
import (
"io"
"github.com/inkyblackness/hacked/ss1/serial"
)
type compressor struct {
coder serial.Coder
writer *WordWriter
dictBuffer dictEntryBuffer
overtime int
dictionary *dictEntry
dictionarySize int
curEntry *dictEntry
}
// NewCompressor creates a new compressor instance over a writer.
func NewCompressor(target io.Writer) io.WriteCloser {
coder := serial.NewEncoder(target)
obj := &compressor{
coder: coder,
writer: NewWordWriter(coder),
dictionary: rootDictEntry(),
dictionarySize: 0,
overtime: 0}
obj.resetDictionary()
return obj
}
func (obj *compressor) resetDictionary() {
obj.dictionarySize = 0
for i := 0; i < 0x100; i++ {
obj.dictionary.Add(byte(i), Word(i), obj.dictBuffer.entry(Word(i)))
}
obj.curEntry = obj.dictionary
}
func (obj *compressor) Close() error {
obj.writer.Write(obj.curEntry.key)
obj.writer.Close()
return obj.coder.FirstError()
}
func (obj *compressor) Write(p []byte) (n int, err error) {
for _, input := range p {
obj.addByte(input)
}
return len(p), obj.coder.FirstError()
}
func (obj *compressor) addByte(value byte) {
nextEntry := obj.curEntry.next[int(value)]
if nextEntry != nil {
obj.curEntry = nextEntry
} else {
obj.writer.Write(obj.curEntry.key)
key := Word(int(literalLimit) + obj.dictionarySize)
if key < Reset {
obj.curEntry.Add(value, key, obj.dictBuffer.entry(key))
obj.dictionarySize++
} else {
obj.onKeySaturation()
}
obj.curEntry = obj.dictionary.next[value]
}
}
func (obj *compressor) onKeySaturation() {
obj.overtime++
if obj.overtime > 1000 {
obj.writer.Write(Reset)
obj.resetDictionary()
obj.overtime = 0
}
}
@@ -0,0 +1,68 @@
package compression_test
import (
"bytes"
"io"
"testing"
"github.com/inkyblackness/hacked/ss1/resource/lgres/internal/compression"
"github.com/inkyblackness/hacked/ss1/serial"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type CompressorSuite struct {
suite.Suite
store *serial.ByteStore
compressor io.WriteCloser
}
func TestCompressorSuite(t *testing.T) {
suite.Run(t, new(CompressorSuite))
}
func (suite *CompressorSuite) SetupTest() {
suite.store = serial.NewByteStore()
suite.compressor = compression.NewCompressor(suite.store)
}
func (suite *CompressorSuite) TestWriteCompressesFirstReocurrence() {
suite.givenWrittenData([]byte{0x00, 0x01})
suite.givenWrittenData([]byte{0x00, 0x01})
err := suite.compressor.Close()
assert.Nil(suite.T(), err, "no error expected")
suite.thenWordsShouldBe(compression.Word(0x0000), compression.Word(0x0001), compression.Word(0x0100))
}
func (suite *CompressorSuite) TestWriteCompressesTest1() {
suite.givenWrittenData([]byte{0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x01})
err := suite.compressor.Close()
assert.Nil(suite.T(), err, "no error expected")
suite.thenWordsShouldBe(
compression.Word(0x0000), compression.Word(0x0001), compression.Word(0x0000),
compression.Word(0x0002), compression.Word(0x0101), compression.Word(0x0001))
}
func (suite *CompressorSuite) givenWrittenData(data []byte) {
written, err := suite.compressor.Write(data)
require.Nil(suite.T(), err, "no error expected writing")
require.Equal(suite.T(), len(data), written, "expected all bytes to be written")
}
func (suite *CompressorSuite) thenWordsShouldBe(expected ...compression.Word) {
source := bytes.NewReader(suite.store.Data())
reader := compression.NewWordReader(serial.NewDecoder(source))
var words []compression.Word
for read := reader.Read(); read != compression.EndOfStream; read = reader.Read() {
words = append(words, read)
}
assert.Equal(suite.T(), expected, words)
}
@@ -0,0 +1,129 @@
package compression
import (
"io"
"github.com/inkyblackness/hacked/ss1/serial"
)
type decompressor struct {
coder serial.Coder
reader *WordReader
isEndOfStream bool
dictBuffer dictEntryBuffer
dictionary *dictEntry
dictionarySize int
lastEntry *dictEntry
lookup []*dictEntry
scratch []byte
leftover []byte
}
// NewDecompressor creates a new decompressor instance over a reader.
func NewDecompressor(source io.Reader) io.Reader {
coder := serial.NewDecoder(source)
obj := &decompressor{
coder: coder,
reader: NewWordReader(coder),
dictionary: rootDictEntry(),
scratch: make([]byte, 1024)}
obj.resetDictionary()
return obj
}
func (obj *decompressor) resetDictionary() {
obj.dictionarySize = 0
obj.lookup = make([]*dictEntry, 1024)
obj.dictionary = rootDictEntry()
for i := 0; i < 0x100; i++ {
entry := obj.dictionary.Add(byte(i), Word(i), obj.dictBuffer.entry(Word(i)))
obj.lookup[Word(i)] = entry
}
obj.lastEntry = obj.dictionary
}
func (obj *decompressor) Read(p []byte) (n int, err error) {
requested := len(p)
for n < requested && !obj.isEndOfStream && obj.coder.FirstError() == nil {
n += obj.takeFromLeftover(p[n:])
if n < requested {
obj.readNextWord()
n += obj.takeFromLeftover(p[n:])
}
}
err = obj.coder.FirstError()
if err == nil && obj.isEndOfStream {
err = io.EOF
}
return
}
func (obj *decompressor) takeFromLeftover(target []byte) (provided int) {
requested := len(target)
available := len(obj.leftover)
if available > 0 && requested > 0 {
provided = available
if provided > requested {
provided = requested
}
copy(target[0:provided], obj.leftover)
obj.leftover = obj.leftover[provided:]
}
return
}
func (obj *decompressor) readNextWord() {
nextWord := obj.reader.Read()
if obj.lastEntry.depth > len(obj.scratch) {
obj.scratch = make([]byte, len(obj.scratch)+1024)
}
obj.leftover = obj.scratch[:obj.lastEntry.depth]
obj.lastEntry.Data(obj.leftover)
switch nextWord {
case EndOfStream:
obj.isEndOfStream = true
case Reset:
obj.resetDictionary()
default:
var nextEntry *dictEntry
if int(nextWord) < len(obj.lookup) {
nextEntry = obj.lookup[int(nextWord)]
}
switch {
case nextEntry != nil:
if obj.lastEntry.depth > 0 {
obj.addToDictionary(nextEntry.FirstByte())
}
obj.lastEntry = nextEntry
case nextWord >= literalLimit:
nextValue := obj.lastEntry.FirstByte()
obj.addToDictionary(nextValue)
obj.lastEntry = obj.lastEntry.next[nextValue]
default:
nextValue := byte(nextWord)
obj.addToDictionary(nextValue)
obj.lastEntry = obj.dictionary.next[nextValue]
}
}
}
func (obj *decompressor) addToDictionary(value byte) {
key := Word(int(literalLimit) + obj.dictionarySize)
nextEntry := obj.lastEntry.Add(value, key, obj.dictBuffer.entry(key))
if int(key) >= len(obj.lookup) {
newLookup := make([]*dictEntry, len(obj.lookup)+1024)
copy(newLookup, obj.lookup)
obj.lookup = newLookup
}
obj.lookup[key] = nextEntry
obj.dictionarySize++
}
@@ -0,0 +1,125 @@
package compression_test
import (
"bytes"
"errors"
"fmt"
"io"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/inkyblackness/hacked/ss1/resource/lgres/internal/compression"
"github.com/inkyblackness/hacked/ss1/serial"
)
type DecompressorSuite struct {
suite.Suite
store *serial.ByteStore
compressor io.WriteCloser
}
func TestDecompressorSuite(t *testing.T) {
suite.Run(t, new(DecompressorSuite))
}
func (suite *DecompressorSuite) SetupTest() {
}
func (suite *DecompressorSuite) TestDecompressTest1() {
input := []byte{0x00, 0x01, 0x00, 0x01}
suite.verify(input)
}
func (suite *DecompressorSuite) TestDecompressTest2() {
input := []byte{0x00, 0x01, 0x00, 0x01, 0x00, 0x01}
suite.verify(input)
}
func (suite *DecompressorSuite) TestDecompressTest3() {
suite.verify([]byte{})
}
func (suite *DecompressorSuite) TestDecompressTest4() {
input := []byte{0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x01}
suite.verify(input)
}
func (suite *DecompressorSuite) TestDecompressTest5() {
input := []byte{0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x01, 0x02, 0x01, 0x02, 0x01, 0x00, 0x01, 0x02}
suite.verify(input)
}
func (suite *DecompressorSuite) TestDecompressTestRandom() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for testCase := 0; testCase < 100; testCase++ {
input := make([]byte, r.Intn(1024))
for i := 0; i < len(input); i++ {
input[i] = byte(r.Intn(256))
}
suite.verify(input)
}
}
func (suite *DecompressorSuite) TestDecompressHandlesDictionaryResets() {
suite.writeWords(0x0001, 0x0002, 0x0100, compression.Reset, 0x0003, 0x0004, 0x0100, compression.EndOfStream)
suite.verifyOutput([]byte{0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x03, 0x04})
}
func (suite *DecompressorSuite) TestDecompressHandlesSelfReferencingWords() {
suite.writeWords(0x0001, 0x0002, 0x0101, compression.EndOfStream)
suite.verifyOutput([]byte{0x01, 0x02, 0x02, 0x02})
}
func (suite *DecompressorSuite) writeWords(values ...compression.Word) {
suite.store = serial.NewByteStore()
coder := serial.NewEncoder(suite.store)
writer := compression.NewWordWriter(coder)
for _, value := range values {
writer.Write(value)
}
writer.Close()
}
func (suite *DecompressorSuite) verify(input []byte) {
suite.store = serial.NewByteStore()
suite.compressor = compression.NewCompressor(serial.NewEncoder(suite.store))
n, err := suite.compressor.Write(input)
assert.Nil(suite.T(), err, "no error expected writing")
assert.Equal(suite.T(), len(input), n, "invalid number written")
err = suite.compressor.Close()
assert.Nil(suite.T(), err, "no error expected closing")
suite.verifyOutput(input)
}
func (suite *DecompressorSuite) verifyOutput(expected []byte) {
output := suite.buffer(len(expected))
source := bytes.NewReader(suite.store.Data())
decompressor := compression.NewDecompressor(source)
read, err := decompressor.Read(output)
assert.True(suite.T(), (err == nil) || errors.Is(err, io.EOF), fmt.Sprintf("unexpected error: %v", err))
assert.Equal(suite.T(), len(output), read, "unexpected number of bytes read")
assert.Equal(suite.T(), expected, output)
}
func (suite *DecompressorSuite) buffer(byteCount int) []byte {
result := make([]byte, byteCount)
for i := range result {
result[i] = 0xFF
}
return result
}
@@ -0,0 +1,50 @@
package compression
type dictEntry struct {
prev *dictEntry
depth int
next [256]*dictEntry
key Word
value byte
first byte
used bool
}
func rootDictEntry() *dictEntry {
return &dictEntry{prev: nil, depth: 0, value: 0x00, key: Reset}
}
var emptyList [256]*dictEntry
func (entry *dictEntry) Add(value byte, key Word, newEntry *dictEntry) *dictEntry {
newEntry.prev = entry
newEntry.depth = entry.depth + 1
newEntry.value = value
newEntry.key = key
newEntry.first = entry.first
if newEntry.used {
copy(newEntry.next[:], emptyList[:])
}
newEntry.used = true
entry.next[value] = newEntry
if entry.depth == 0 {
newEntry.first = value
}
return newEntry
}
func (entry *dictEntry) Data(bytes []byte) {
cur := entry
for i := entry.depth - 1; i >= 0; i-- {
bytes[i] = cur.value
cur = cur.prev
}
}
func (entry *dictEntry) FirstByte() byte {
return entry.first
}
@@ -0,0 +1,21 @@
package compression
// dictEntryBucketSizeBits governs how big one bucket in the buffer is.
const dictEntryBucketSizeBits = 8
type dictEntryBucket [1 << dictEntryBucketSizeBits]dictEntry
type dictEntryBuffer struct {
buckets []*dictEntryBucket
}
func (buffer *dictEntryBuffer) entry(key Word) *dictEntry {
bucketIndex := int(key) >> dictEntryBucketSizeBits
index := int(key & ((1 << dictEntryBucketSizeBits) - 1))
for bucketIndex >= len(buffer.buckets) {
buffer.buckets = append(buffer.buckets, new(dictEntryBucket))
}
return &buffer.buckets[bucketIndex][index]
}
@@ -0,0 +1,14 @@
package compression
// Word is a compression stream entry.
type Word uint16
const (
bitsPerWord = uint(14)
// EndOfStream indicates the last word in the compressed stream.
EndOfStream = Word(0x3FFF)
// Reset indicates a reset of the dictionary.
Reset = Word(0x3FFE)
literalLimit = Word(0x0100)
)
@@ -0,0 +1,41 @@
package compression
import "github.com/inkyblackness/hacked/ss1/serial"
// WordReader provides word instances from a serialized stream.
type WordReader struct {
coder serial.Coder
buffer byte
bufferBitsAvailable uint
}
// NewWordReader returns a new instance.
func NewWordReader(coder serial.Coder) *WordReader {
reader := &WordReader{coder: coder, buffer: 0x00, bufferBitsAvailable: 0}
return reader
}
// Read returns the next word from the stream.
func (reader *WordReader) Read() (value Word) {
remaining := bitsPerWord
for remaining > reader.bufferBitsAvailable {
value = Word((uint(value) << reader.bufferBitsAvailable) | uint(reader.buffer))
remaining -= reader.bufferBitsAvailable
reader.bufferByte()
}
value = Word((uint(value) << remaining) | uint(reader.buffer)>>(8-remaining))
reader.buffer &= 1<<(8-remaining) - 1
reader.bufferBitsAvailable -= remaining
return
}
func (reader *WordReader) bufferByte() {
reader.coder.Code(&reader.buffer)
reader.bufferBitsAvailable = 8
}
@@ -0,0 +1,56 @@
package compression_test
import (
"bytes"
"testing"
"github.com/inkyblackness/hacked/ss1/resource/lgres/internal/compression"
"github.com/inkyblackness/hacked/ss1/serial"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type WordReaderSuite struct {
suite.Suite
reader *compression.WordReader
}
func TestWordReaderSuite(t *testing.T) {
suite.Run(t, new(WordReaderSuite))
}
func (suite *WordReaderSuite) TestReadCanReadFirstWord() {
suite.givenReaderFrom()
assert.Equal(suite.T(), compression.EndOfStream, suite.reader.Read())
}
func (suite *WordReaderSuite) TestReadCanReadSeveralWords() {
input := []compression.Word{
compression.Word(0x3FFF), compression.Word(0x0000), compression.Word(0x3FFF),
compression.Word(0x0000), compression.Word(0x2001), compression.Word(0x1234),
}
suite.givenReaderFrom(input...)
output := make([]compression.Word, len(input)+1)
for i := 0; i < len(output); i++ {
output[i] = suite.reader.Read()
}
assert.Equal(suite.T(), append(input, compression.EndOfStream), output)
}
func (suite *WordReaderSuite) givenReaderFrom(words ...compression.Word) {
store := serial.NewByteStore()
encoder := serial.NewEncoder(store)
writer := compression.NewWordWriter(encoder)
for _, value := range words {
writer.Write(value)
}
writer.Close()
source := bytes.NewReader(store.Data())
suite.reader = compression.NewWordReader(serial.NewDecoder(source))
}
@@ -0,0 +1,59 @@
package compression
import "github.com/inkyblackness/hacked/ss1/serial"
// WordWriter serializes a stream of word entries.
type WordWriter struct {
coder serial.Coder
bufferedBits uint
scratch uint32
outBuffer [1024]byte
outUsed int
}
// NewWordWriter returns a new instance.
func NewWordWriter(coder serial.Coder) *WordWriter {
writer := &WordWriter{coder: coder, bufferedBits: 0, scratch: 0}
return writer
}
// Close finishes the stream of words.
func (writer *WordWriter) Close() {
writer.Write(EndOfStream)
if writer.bufferedBits > 0 {
writer.writeByte(byte(writer.scratch >> 16))
}
writer.writeByte(byte(0x00))
writer.flushBuffer()
}
// Write adds the given word to the stream.
func (writer *WordWriter) Write(value Word) {
writer.scratch |= uint32(value) << ((16 - bitsPerWord) + (8 - writer.bufferedBits))
writer.bufferedBits += bitsPerWord
writer.writeByte(byte(writer.scratch >> 16))
writer.scratch <<= 8
writer.bufferedBits -= 8
if writer.bufferedBits >= 8 {
writer.writeByte(byte(writer.scratch >> 16))
writer.scratch <<= 8
writer.bufferedBits -= 8
}
}
func (writer *WordWriter) writeByte(value byte) {
writer.outBuffer[writer.outUsed] = value
writer.outUsed++
if writer.outUsed >= len(writer.outBuffer) {
writer.flushBuffer()
}
}
func (writer *WordWriter) flushBuffer() {
writer.coder.Code(writer.outBuffer[:writer.outUsed])
writer.outUsed = 0
}
@@ -0,0 +1,49 @@
package compression_test
import (
"testing"
"github.com/inkyblackness/hacked/ss1/resource/lgres/internal/compression"
"github.com/inkyblackness/hacked/ss1/serial"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type WordWriterSuite struct {
suite.Suite
writer *compression.WordWriter
store *serial.ByteStore
}
func TestWordWriterSuite(t *testing.T) {
suite.Run(t, new(WordWriterSuite))
}
func (suite *WordWriterSuite) SetupTest() {
suite.store = serial.NewByteStore()
coder := serial.NewEncoder(suite.store)
suite.writer = compression.NewWordWriter(coder)
}
func (suite *WordWriterSuite) TestCloseWritesEndOfStreamMarkerAndTrailingZeroByte() {
suite.writer.Close()
assert.Equal(suite.T(), []byte{0xFF, 0xFC, 0x00}, suite.store.Data())
}
func (suite *WordWriterSuite) TestCloseWritesRemainderOnlyIfNotEmpty() {
suite.writer.Write(compression.Word(0x0000))
suite.writer.Write(compression.Word(0x0000))
suite.writer.Write(compression.Word(0x0000))
suite.writer.Close()
assert.Equal(suite.T(), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00}, suite.store.Data())
}
func (suite *WordWriterSuite) TestWriteAndCloseLinesUpBits() {
suite.writer.Write(compression.Word(0x1FFE)) // 0111111 1111110
suite.writer.Close() // 1111111 1111111
assert.Equal(suite.T(), []byte{0x7F, 0xFB, 0xFF, 0xF0, 0x00}, suite.store.Data())
}
@@ -0,0 +1,119 @@
package compression_test
import (
"flag"
"io"
"math/rand"
"os"
"runtime/pprof"
"testing"
"github.com/inkyblackness/hacked/ss1/resource/lgres/internal/compression"
"github.com/inkyblackness/hacked/ss1/serial"
)
// To run a profile, run it with the following command:
// go test -bench . -args -cpuprofile=prof
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
func rawData(size int) []byte {
data := make([]byte, size)
rand.Read(data) // nolint:gas
return data
}
func initProfiling(b *testing.B, nameSuffix string) func() {
b.Helper()
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile + "-" + b.Name() + nameSuffix)
if err != nil {
b.Fatal(err)
}
_ = pprof.StartCPUProfile(f)
return func() { pprof.StopCPUProfile() }
}
return func() {}
}
func BenchmarkRawDataStorage(b *testing.B) {
profileStop := initProfiling(b, "")
defer profileStop()
data := rawData(1024 * 1024)
b.ResetTimer()
for run := 0; run < b.N; run++ {
encoder := serial.NewEncoder(serial.NewByteStore())
_, _ = encoder.Write(data)
}
}
func benchmarkCompression(b *testing.B, size int, nameSuffix string) {
b.Helper()
profileStop := initProfiling(b, nameSuffix)
defer profileStop()
data := rawData(size)
b.ResetTimer()
for run := 0; run < b.N; run++ {
compressor := compression.NewCompressor(serial.NewByteStore())
_, _ = compressor.Write(data)
_ = compressor.Close()
}
}
func BenchmarkCompression1KB(b *testing.B) {
benchmarkCompression(b, 1024, "1KB")
}
func BenchmarkCompression16KB(b *testing.B) {
benchmarkCompression(b, 1024*16, "16KB")
}
func BenchmarkCompression128KB(b *testing.B) {
benchmarkCompression(b, 1024*128, "128KB")
}
func BenchmarkCompression512KB(b *testing.B) {
benchmarkCompression(b, 1024*512, "512KB")
}
func BenchmarkCompression1024KB(b *testing.B) {
benchmarkCompression(b, 1024*1024, "1024KB")
}
func benchmarkCompressionDecompression(b *testing.B, size int, nameSuffix string) {
b.Helper()
profileStop := initProfiling(b, nameSuffix)
defer profileStop()
data := rawData(size)
output := make([]byte, len(data))
b.ResetTimer()
for run := 0; run < b.N; run++ {
store := serial.NewByteStore()
compressor := compression.NewCompressor(store)
_, _ = compressor.Write(data)
_ = compressor.Close()
_, _ = store.Seek(0, io.SeekStart)
decompressor := compression.NewDecompressor(store)
_, _ = decompressor.Read(output)
}
}
func BenchmarkCompressionDecompression1KB(b *testing.B) {
benchmarkCompressionDecompression(b, 1024, "1KB")
}
func BenchmarkCompressionDecompression16KB(b *testing.B) {
benchmarkCompressionDecompression(b, 1024*16, "16KB")
}
func BenchmarkCompressionDecompression128KB(b *testing.B) {
benchmarkCompressionDecompression(b, 1024*128, "128KB")
}
func BenchmarkCompressionDecompression512KB(b *testing.B) {
benchmarkCompressionDecompression(b, 1024*512, "512KB")
}
func BenchmarkCompressionDecompression1024KB(b *testing.B) {
benchmarkCompressionDecompression(b, 1024*1024, "1024KB")
}
@@ -0,0 +1,4 @@
/*
Package compression implements the LG resource file compression algorithm.
*/
package compression
@@ -0,0 +1,17 @@
package format
const (
// HeaderString is the start of an LG Res file.
HeaderString = "LG Res File v2\r\n"
// CommentTerminator terminates the header string.
CommentTerminator = byte(0x1A)
// ResourceDirectoryFileOffsetPos is the byte offset for the pointer to the directory.
ResourceDirectoryFileOffsetPos = 0x7C
// BoundarySize is the byte interval at which entries must be placed.
BoundarySize = 4
// ResourceTypeFlagCompressed indicates a compressed resource.
ResourceTypeFlagCompressed = byte(0x01)
// ResourceTypeFlagCompound indicates a compound resource.
ResourceTypeFlagCompound = byte(0x02)
)