Compression updates

This commit is contained in:
Tim Sarbin 2019-10-25 15:06:47 -04:00
parent 036d35956e
commit cb5c068279
7 changed files with 693 additions and 18 deletions

59
Common/BitStream.go Normal file
View File

@ -0,0 +1,59 @@
package Common
import "log"
// BitStream is a utility class for reading groups of bits from a stream
type BitStream struct {
data []byte
dataPosition int
current int
bitCount int
}
func CreateBitStream(newData []byte) *BitStream {
result := &BitStream{
data: newData,
dataPosition: 0,
current: 0,
bitCount: 0,
}
return result
}
func (v *BitStream) ReadBits(bitCount int) int {
if bitCount > 16 {
log.Panic("Maximum BitCount is 16")
}
if !v.EnsureBits(bitCount) {
return -1
}
result := v.current & (0xffff >> (16 - bitCount))
v.WasteBits(bitCount)
return result
}
func (v *BitStream) PeekByte() int {
if !v.EnsureBits(8) {
return -1
}
return v.current & 0xff
}
func (v *BitStream) EnsureBits(bitCount int) bool {
if bitCount <= v.bitCount {
return true
}
if v.dataPosition >= len(v.data) {
return false
}
nextvalue := v.data[v.dataPosition]
v.dataPosition++
v.current |= int(nextvalue) << v.bitCount
v.bitCount += 8
return true
}
func (v *BitStream) WasteBits(bitCount int) {
v.current >>= bitCount
v.bitCount -= bitCount
}

33
Common/BitStream_test.go Normal file
View File

@ -0,0 +1,33 @@
package Common
import (
"testing"
)
func TestBitStreamBits(t *testing.T) {
data := []byte{0xAA}
bitStream := CreateBitStream(data)
shouldBeOne := 0
for i := 0; i < 8; i++ {
bit := bitStream.ReadBits(1)
if bit != shouldBeOne {
t.Fatalf("Expected %d but got %d on iteration %d", shouldBeOne, bit, i)
}
if shouldBeOne == 1 {
shouldBeOne = 0
} else {
shouldBeOne = 1
}
}
}
func TestBitStreamBytes(t *testing.T) {
data := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0x12, 0x34, 0x56, 0x78}
bitStream := CreateBitStream(data)
for i := 0; i < 8; i++ {
b := byte(bitStream.ReadBits(8))
if b != data[i] {
t.Fatalf("Expected %d but got %d on iteration %d", data[i], b, i)
}
}
}

372
Compression/Huffman.go Normal file
View File

@ -0,0 +1,372 @@
//
// MpqHuffman.go based on the origina CS file
//
// Authors:
// Foole (fooleau@gmail.com)
// Tim Sarbin (tim.sarbin@gmail.com) (go translation)
//
// (C) 2006 Foole (fooleau@gmail.com)
// Based on code from StormLib by Ladislav Zezula and ShadowFlare
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
package Compression
import (
"bytes"
"log"
"github.com/essial/OpenDiablo2/Common"
)
// linkedNode is a node which is both hierachcical (parent/child) and doubly linked (next/prev)
type linkedNode struct {
DecompressedValue int
Weight int
Parent *linkedNode
Child0 *linkedNode
Prev *linkedNode
Next *linkedNode
}
func CreateLinkedNode(decompVal, weight int) *linkedNode {
result := &linkedNode{
DecompressedValue: decompVal,
Weight: weight,
}
return result
}
func (v *linkedNode) GetChild1() *linkedNode {
return v.Child0.Prev
}
func (v *linkedNode) Insert(other *linkedNode) *linkedNode {
// 'Next' should have a lower weight we should return the lower weight
if other.Weight <= v.Weight {
// insert before
if v.Next != nil {
v.Next.Prev = other
other.Next = v.Next
}
v.Next = other
other.Prev = v
return other
}
if v.Prev == nil {
// Insert after
other.Prev = nil
v.Prev = other
other.Next = v
} else {
v.Prev.Insert(other)
}
return v
}
var sPrime = [][]byte{
{ // Compression type 0
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
}, { // Compression type 1
0x54, 0x16, 0x16, 0x0D, 0x0C, 0x08, 0x06, 0x05, 0x06, 0x05, 0x06, 0x03, 0x04, 0x04, 0x03, 0x05,
0x0E, 0x0B, 0x14, 0x13, 0x13, 0x09, 0x0B, 0x06, 0x05, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02,
0x0D, 0x07, 0x09, 0x06, 0x06, 0x04, 0x03, 0x02, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02,
0x09, 0x06, 0x04, 0x04, 0x04, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x04,
0x08, 0x03, 0x04, 0x07, 0x09, 0x05, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02,
0x06, 0x0A, 0x08, 0x08, 0x06, 0x07, 0x04, 0x03, 0x04, 0x04, 0x02, 0x02, 0x04, 0x02, 0x03, 0x03,
0x04, 0x03, 0x07, 0x07, 0x09, 0x06, 0x04, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02,
0x0A, 0x02, 0x02, 0x03, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x03, 0x05, 0x02, 0x03,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x03, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x04, 0x04, 0x04, 0x07, 0x09, 0x08, 0x0C, 0x02,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x03,
0x04, 0x01, 0x02, 0x04, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x4B,
}, { // Compression type 2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x27, 0x00, 0x00, 0x23, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x06, 0x0E, 0x10, 0x04,
0x06, 0x08, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x01, 0x01, 0x02, 0x01, 0x01,
0x01, 0x04, 0x02, 0x04, 0x02, 0x02, 0x02, 0x01, 0x01, 0x04, 0x01, 0x01, 0x02, 0x03, 0x03, 0x02,
0x03, 0x01, 0x03, 0x06, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01,
0x01, 0x29, 0x07, 0x16, 0x12, 0x40, 0x0A, 0x0A, 0x11, 0x25, 0x01, 0x03, 0x17, 0x10, 0x26, 0x2A,
0x10, 0x01, 0x23, 0x23, 0x2F, 0x10, 0x06, 0x07, 0x02, 0x09, 0x01, 0x01, 0x01, 0x01, 0x01,
}, { // Compression type 3
0xFF, 0x0B, 0x07, 0x05, 0x0B, 0x02, 0x02, 0x02, 0x06, 0x02, 0x02, 0x01, 0x04, 0x02, 0x01, 0x03,
0x09, 0x01, 0x01, 0x01, 0x03, 0x04, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
0x05, 0x01, 0x01, 0x01, 0x0D, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
0x0A, 0x04, 0x02, 0x01, 0x06, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01,
0x05, 0x02, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03,
0x01, 0x03, 0x01, 0x01, 0x02, 0x05, 0x01, 0x01, 0x04, 0x03, 0x05, 0x01, 0x03, 0x01, 0x03, 0x03,
0x02, 0x01, 0x04, 0x03, 0x0A, 0x06, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x02, 0x01, 0x0A, 0x02, 0x05, 0x01, 0x01, 0x02, 0x07, 0x02, 0x17, 0x01, 0x05, 0x01, 0x01,
0x0E, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x06, 0x02, 0x01, 0x04, 0x05, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x07, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11,
}, { // Compression type 4
0xFF, 0xFB, 0x98, 0x9A, 0x84, 0x85, 0x63, 0x64, 0x3E, 0x3E, 0x22, 0x22, 0x13, 0x13, 0x18, 0x17,
}, { // Compression type 5
0xFF, 0xF1, 0x9D, 0x9E, 0x9A, 0x9B, 0x9A, 0x97, 0x93, 0x93, 0x8C, 0x8E, 0x86, 0x88, 0x80, 0x82,
0x7C, 0x7C, 0x72, 0x73, 0x69, 0x6B, 0x5F, 0x60, 0x55, 0x56, 0x4A, 0x4B, 0x40, 0x41, 0x37, 0x37,
0x2F, 0x2F, 0x27, 0x27, 0x21, 0x21, 0x1B, 0x1C, 0x17, 0x17, 0x13, 0x13, 0x10, 0x10, 0x0D, 0x0D,
0x0B, 0x0B, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x04, 0x19, 0x18,
}, { // Compression type 6
0xC3, 0xCB, 0xF5, 0x41, 0xFF, 0x7B, 0xF7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xBF, 0xCC, 0xF2, 0x40, 0xFD, 0x7C, 0xF7, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7A, 0x46,
}, { // Compression type 7
0xC3, 0xD9, 0xEF, 0x3D, 0xF9, 0x7C, 0xE9, 0x1E, 0xFD, 0xAB, 0xF1, 0x2C, 0xFC, 0x5B, 0xFE, 0x17,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xBD, 0xD9, 0xEC, 0x3D, 0xF5, 0x7D, 0xE8, 0x1D, 0xFB, 0xAE, 0xF0, 0x2C, 0xFB, 0x5C, 0xFF, 0x18,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x70, 0x6C,
}, { // Compression type 8
0xBA, 0xC5, 0xDA, 0x33, 0xE3, 0x6D, 0xD8, 0x18, 0xE5, 0x94, 0xDA, 0x23, 0xDF, 0x4A, 0xD1, 0x10,
0xEE, 0xAF, 0xE4, 0x2C, 0xEA, 0x5A, 0xDE, 0x15, 0xF4, 0x87, 0xE9, 0x21, 0xF6, 0x43, 0xFC, 0x12,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xB0, 0xC7, 0xD8, 0x33, 0xE3, 0x6B, 0xD6, 0x18, 0xE7, 0x95, 0xD8, 0x23, 0xDB, 0x49, 0xD0, 0x11,
0xE9, 0xB2, 0xE2, 0x2B, 0xE8, 0x5C, 0xDD, 0x15, 0xF1, 0x87, 0xE7, 0x20, 0xF7, 0x44, 0xFF, 0x13,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x5F, 0x9E,
},
}
func decode(input *Common.BitStream, head *linkedNode) *linkedNode {
node := head
for node.Child0 != nil {
bit := input.ReadBits(1)
if bit == -1 {
log.Fatal("unexpected end of file")
}
if bit == 0 {
node = node.Child0
continue
}
node = node.GetChild1()
}
return node
}
func buildList(primeData []byte) *linkedNode {
root := CreateLinkedNode(256, 1)
root = root.Insert(CreateLinkedNode(257, 1))
for i := 0; i < len(primeData); i++ {
if primeData[i] != 0 {
root = root.Insert(CreateLinkedNode(i, int(primeData[i])))
}
}
return root
}
func insertNode(tail *linkedNode, decomp int) *linkedNode {
parent := tail
result := tail.Prev // This will be the new tail after the tree is updated
temp := CreateLinkedNode(parent.DecompressedValue, parent.Weight)
temp.Parent = parent
newnode := CreateLinkedNode(decomp, 0)
newnode.Parent = parent
parent.Child0 = newnode
tail.Next = temp
temp.Prev = tail
newnode.Prev = temp
temp.Next = newnode
adjustTree(newnode)
// TODO: For compression type 0, AdjustTree should be called
// once for every value written and only once here
adjustTree(newnode)
return result
}
// This increases the weight of the new node and its antecendants
// and adjusts the tree if needed
func adjustTree(newNode *linkedNode) {
current := newNode
for current != nil {
current.Weight++
var insertpoint *linkedNode
var prev *linkedNode
// Go backwards thru the list looking for the insertion point
insertpoint = current
for true {
prev = insertpoint.Prev
if prev == nil {
break
}
if prev.Weight >= current.Weight {
break
}
insertpoint = prev
}
// No insertion point found
if insertpoint == current {
current = current.Parent
continue
}
// The following code basicly swaps insertpoint with current
// remove insert point
if insertpoint.Prev != nil {
insertpoint.Prev.Next = insertpoint.Next
}
insertpoint.Next.Prev = insertpoint.Prev
// Insert insertpoint after current
insertpoint.Next = current.Next
insertpoint.Prev = current
if current.Next != nil {
current.Next.Prev = insertpoint
}
current.Next = insertpoint
// remove current
current.Prev.Next = current.Next
current.Next.Prev = current.Prev
// insert current after prev
if prev == nil {
log.Fatal("previous frame not defined!")
}
temp := prev.Next
current.Next = temp
current.Prev = prev
temp.Prev = current
prev.Next = current
// Set up parent/child links
currentparent := current.Parent
insertparent := insertpoint.Parent
if currentparent.Child0 == current {
currentparent.Child0 = insertpoint
}
if currentparent != insertparent && insertparent.Child0 == insertpoint {
insertparent.Child0 = current
}
current.Parent = insertparent
insertpoint.Parent = currentparent
current = current.Parent
}
}
func buildTree(tail *linkedNode) *linkedNode {
current := tail
for current != nil {
child0 := current
child1 := current.Prev
if child1 == nil {
break
}
parent := CreateLinkedNode(0, child0.Weight+child1.Weight)
parent.Child0 = child0
child0.Parent = parent
child1.Parent = parent
current.Insert(parent)
current = current.Prev.Prev
}
return current
}
func HuffmanDecompress(data []byte) []byte {
comptype := data[0]
if comptype == 0 {
log.Panic("compression type 0 is not currently supported")
}
tail := buildList(sPrime[comptype])
head := buildTree(tail)
var outputstream bytes.Buffer
bitstream := Common.CreateBitStream(data[1:])
var decoded int
for true {
node := decode(bitstream, head)
decoded = node.DecompressedValue
switch decoded {
case 256:
break
case 257:
newvalue := bitstream.ReadBits(8)
outputstream.WriteByte(byte(newvalue))
tail = insertNode(tail, newvalue)
break
default:
outputstream.WriteByte(byte(decoded))
break
}
if decoded == 256 {
break
}
}
return outputstream.Bytes()
}

141
Compression/Wav.go Normal file
View File

@ -0,0 +1,141 @@
package Compression
import (
"bufio"
"bytes"
"encoding/binary"
"io"
)
var sLookup = []int{
0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,
0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F,
0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,
0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F,
0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133,
0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,
0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583,
0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0,
0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B,
0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462,
0x7FFF,
}
var sLookup2 = []int{
-1, 0, -1, 4, -1, 2, -1, 6,
-1, 1, -1, 5, -1, 3, -1, 7,
-1, 1, -1, 5, -1, 3, -1, 7,
-1, 2, -1, 4, -1, 6, -1, 8,
}
func WavDecompress(data []byte, channelCount int) []byte {
Array1 := []int{0x2c, 0x2c}
Array2 := make([]int, channelCount)
input := bytes.NewReader(data)
var output bytes.Buffer
outputWriter := bufio.NewWriter(&output)
input.ReadByte()
shift, _ := input.ReadByte()
for i := 0; i < channelCount; i++ {
temp := int16(0)
binary.Read(input, binary.LittleEndian, &temp)
Array2[i] = int(temp)
binary.Write(outputWriter, binary.LittleEndian, &temp)
}
channel := channelCount - 1
pos, _ := input.Seek(0, io.SeekCurrent)
input.Seek(pos, io.SeekStart)
for pos < int64(input.Len()) {
value, _ := input.ReadByte()
if channelCount == 2 {
channel = 1 - channel
}
if (value & 0x80) != 0 {
switch value & 0x7f {
case 0:
if Array1[channel] != 0 {
Array1[channel]--
}
d := int16(Array2[channel])
binary.Write(outputWriter, binary.LittleEndian, &d)
break
case 1:
Array1[channel] += 8
if Array1[channel] > 0x58 {
Array1[channel] = 0x58
}
if channelCount == 2 {
channel = 1 - channel
}
break
case 2:
break
default:
Array1[channel] -= 8
if Array1[channel] < 0 {
Array1[channel] = 0
}
if channelCount == 2 {
channel = 1 - channel
}
break
}
} else {
temp1 := sLookup[Array1[channel]]
temp2 := temp1 >> shift
if (value & 1) != 0 {
temp2 += temp1 >> 0
}
if (value & 2) != 0 {
temp2 += temp1 >> 1
}
if (value & 4) != 0 {
temp2 += temp1 >> 2
}
if (value & 8) != 0 {
temp2 += temp1 >> 3
}
if (value & 0x10) != 0 {
temp2 += temp1 >> 4
}
if (value & 0x20) != 0 {
temp2 += temp1 >> 5
}
temp3 := Array2[channel]
if (value & 0x40) != 0 {
temp3 -= temp2
if temp3 <= -32768 {
temp3 = -32768
}
} else {
temp3 += temp2
if temp3 >= 32767 {
temp3 = 32767
}
}
Array2[channel] = temp3
d := int16(temp3)
binary.Write(outputWriter, binary.LittleEndian, &d)
Array1[channel] += sLookup2[value&0x1f]
if Array1[channel] < 0 {
Array1[channel] = 0
} else {
if Array1[channel] > 0x58 {
Array1[channel] = 0x58
}
}
}
}
return output.Bytes()
}

View File

@ -12,6 +12,8 @@ import (
"github.com/essial/OpenDiablo2/ResourcePaths"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/audio"
"github.com/hajimehoshi/ebiten/audio/wav"
)
// EngineConfig defines the configuration for the engine, loaded from config.json
@ -26,6 +28,13 @@ type EngineConfig struct {
}
// Engine is the core OpenDiablo2 engine
type CursorButton uint8
const (
CursorButtonLeft CursorButton = 1
CursorButtonRight CursorButton = 2
)
type Engine struct {
Settings EngineConfig // Engine configuration settings from json file
Files map[string]string // Map that defines which files are in which MPQs
@ -35,10 +44,13 @@ type Engine struct {
LoadingSprite Sprite // The sprite shown when loading stuff
CursorX int // X position of the cursor
CursorY int // Y position of the cursor
CursorButtons CursorButton // The buttons that are currently being pressed
LoadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays.
CurrentScene Common.SceneInterface // The current scene being rendered
nextScene Common.SceneInterface // The next scene to be loaded at the end of the game loop
fontCache map[string]*MPQFont // The font cash
audioContext *audio.Context // The Audio context
bgmAudio *audio.Player // The audio player
}
// CreateEngine creates and instance of the OpenDiablo2 engine
@ -53,6 +65,11 @@ func CreateEngine() *Engine {
result.mapMpqFiles()
result.loadPalettes()
result.loadSoundEntries()
audioContext, err := audio.NewContext(48000)
if err != nil {
log.Fatal(err)
}
result.audioContext = audioContext
result.CursorSprite = result.LoadSprite(ResourcePaths.CursorDefault, result.Palettes["units"])
result.LoadingSprite = result.LoadSprite(ResourcePaths.LoadingScreen, result.Palettes["loading"])
loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetSize()
@ -65,7 +82,7 @@ func (v *Engine) loadConfigurationFile() {
log.Println("loading configuration file")
configJSON, err := ioutil.ReadFile("config.json")
if err != nil {
panic(err)
log.Fatal(err)
}
var config EngineConfig
@ -81,11 +98,11 @@ func (v *Engine) mapMpqFiles() {
mpqPath := path.Join(v.Settings.MpqPath, mpqFileName)
mpq, err := LoadMPQ(mpqPath)
if err != nil {
panic(err)
log.Fatal(err)
}
fileListText, err := mpq.ReadFile("(listfile)")
if err != nil {
panic(err)
log.Fatal(err)
}
fileList := strings.Split(string(fileListText), "\r\n")
for _, filePath := range fileList {
@ -104,11 +121,12 @@ func (v *Engine) GetFile(fileName string) []byte {
mpqFile := v.Files[strings.ToLower(fileName)]
mpq, err := LoadMPQ(mpqFile)
if err != nil {
panic(err)
log.Fatal(err)
}
blockTableEntry, err := mpq.getFileBlockData(strings.ReplaceAll(fileName, `/`, `\`)[1:])
fileName = strings.ReplaceAll(fileName, `/`, `\`)[1:]
blockTableEntry, err := mpq.getFileBlockData(fileName)
if err != nil {
panic(err)
log.Fatal(err)
}
mpqStream := CreateMPQStream(mpq, blockTableEntry, fileName)
result := make([]byte, blockTableEntry.UncompressedFileSize)
@ -169,11 +187,23 @@ func (v *Engine) updateScene() {
v.CurrentScene.Load()
}
// CursorButtonPressed determines if the specified button has been pressed
func (v *Engine) CursorButtonPressed(button CursorButton) bool {
return v.CursorButtons&button > 0
}
// Update updates the internal state of the engine
func (v *Engine) Update() {
v.updateScene()
if v.CurrentScene == nil {
panic("no scene loaded")
log.Fatal("no scene loaded")
}
v.CursorButtons = 0
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
v.CursorButtons |= CursorButtonLeft
}
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonRight) {
v.CursorButtons |= CursorButtonRight
}
v.CurrentScene.Update()
}
@ -186,7 +216,7 @@ func (v *Engine) Draw(screen *ebiten.Image) {
v.LoadingSprite.Draw(screen)
} else {
if v.CurrentScene == nil {
panic("no scene loaded")
log.Fatal("no scene loaded")
}
v.CurrentScene.Render(screen)
}
@ -210,3 +240,28 @@ func (v *Engine) GetFont(font, palette string) *MPQFont {
v.fontCache[font+"_"+palette] = newFont
return newFont
}
// PlayBGM plays an infinitely looping background track
func (v *Engine) PlayBGM(song string) {
go func() {
if v.bgmAudio != nil {
v.bgmAudio.Close()
}
audioData := v.GetFile(song)
//audioData2, _ := ioutil.ReadFile(`C:\Users\lunat\Desktop\D2\Extracted\data\global\music\introedit.wav`)
//log.Printf("%d, %d", len(audioData), len(audioData2))
d, err := wav.Decode(v.audioContext, audio.BytesReadSeekCloser(audioData))
if err != nil {
log.Fatal(err)
}
//s := audio.NewInfiniteLoop(d, int64(len(audioData)))
v.bgmAudio, err = audio.NewPlayer(v.audioContext, d)
if err != nil {
log.Fatal(err)
}
// Play the infinite-length stream. This never ends.
v.bgmAudio.Rewind()
v.bgmAudio.Play()
}()
}

View File

@ -5,8 +5,10 @@ import (
"compress/zlib"
"encoding/binary"
"fmt"
"strings"
"github.com/JoshVarga/blast"
"github.com/essial/OpenDiablo2/Compression"
)
// MPQStream represents a stream of data in an MPQ archive
@ -23,13 +25,14 @@ type MPQStream struct {
}
// CreateMPQStream creates an MPQ stream
func CreateMPQStream(mpq MPQ, blockTableEntry MPQBlockTableEntry, fileName string) MPQStream {
result := MPQStream{
func CreateMPQStream(mpq MPQ, blockTableEntry MPQBlockTableEntry, fileName string) *MPQStream {
result := &MPQStream{
MPQData: mpq,
BlockTableEntry: blockTableEntry,
CurrentBlockIndex: 0xFFFFFFFF,
}
result.EncryptionSeed = hashString(fileName, 3)
fileSegs := strings.Split(fileName, `\`)
result.EncryptionSeed = hashString(fileSegs[len(fileSegs)-1], 3)
if result.BlockTableEntry.HasFlag(MpqFileFixKey) {
result.EncryptionSeed = (result.EncryptionSeed + result.BlockTableEntry.FilePosition) ^ result.BlockTableEntry.UncompressedFileSize
}
@ -184,17 +187,21 @@ func decompressMulti(data []byte, expectedLength uint32) []byte {
// TODO: sparse then bzip2
panic("sparse decompression + bzip2 decompression not supported")
case 0x41:
//sinput = MpqHuffman.Decompress(sinput);
//return MpqWavCompression.Decompress(sinput, 1);
panic("mpqhuffman decompression not supported")
sinput := Compression.HuffmanDecompress(data[1:])
sinput = Compression.WavDecompress(sinput, 1)
tmp := make([]byte, len(sinput))
copy(tmp, sinput)
return tmp
case 0x48:
//byte[] result = PKDecompress(sinput, outputLength);
//return MpqWavCompression.Decompress(new MemoryStream(result), 1);
panic("pk + mpqwav decompression not supported")
case 0x81:
//sinput = MpqHuffman.Decompress(sinput);
//return MpqWavCompression.Decompress(sinput, 2);
panic("huff + mpqwav decompression not supported")
sinput := Compression.HuffmanDecompress(data[1:])
sinput = Compression.WavDecompress(sinput, 1)
tmp := make([]byte, len(sinput))
copy(tmp, sinput)
return tmp
case 0x88:
//byte[] result = PKDecompress(sinput, outputLength);
//return MpqWavCompression.Decompress(new MemoryStream(result), 2);

View File

@ -18,6 +18,7 @@ type MainMenu struct {
CopyrightLabel *UILabel
CopyrightLabel2 *UILabel
ShowTrademarkScreen bool
LeftButtonHeld bool
}
func CreateMainMenu(engine *Engine) *MainMenu {
@ -30,6 +31,7 @@ func CreateMainMenu(engine *Engine) *MainMenu {
}
func (v *MainMenu) Load() {
v.Engine.PlayBGM(ResourcePaths.BGMTitle)
go func() {
loadStep := 1.0 / 8.0
v.Engine.LoadingProgress = 0
@ -110,5 +112,11 @@ func (v *MainMenu) Render(screen *ebiten.Image) {
}
func (v *MainMenu) Update() {
if v.ShowTrademarkScreen {
if v.Engine.CursorButtonPressed(CursorButtonLeft) {
v.LeftButtonHeld = true
v.ShowTrademarkScreen = false
}
return
}
}