Files
questshock/modding/hacked/ss1/resource/lgres/internal/compression/Decompressor.go
T

130 lines
3.0 KiB
Go

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++
}