From cb5c06827961a88ba87fa2877829e5f0671071ab Mon Sep 17 00:00:00 2001 From: Tim Sarbin Date: Fri, 25 Oct 2019 15:06:47 -0400 Subject: [PATCH] Compression updates --- Common/BitStream.go | 59 +++++++ Common/BitStream_test.go | 33 ++++ Compression/Huffman.go | 372 +++++++++++++++++++++++++++++++++++++++ Compression/Wav.go | 141 +++++++++++++++ Engine.go | 71 +++++++- MPQStream.go | 25 ++- SceneMainMenu.go | 10 +- 7 files changed, 693 insertions(+), 18 deletions(-) create mode 100644 Common/BitStream.go create mode 100644 Common/BitStream_test.go create mode 100644 Compression/Huffman.go create mode 100644 Compression/Wav.go diff --git a/Common/BitStream.go b/Common/BitStream.go new file mode 100644 index 00000000..4a17d0aa --- /dev/null +++ b/Common/BitStream.go @@ -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 +} diff --git a/Common/BitStream_test.go b/Common/BitStream_test.go new file mode 100644 index 00000000..a371e06a --- /dev/null +++ b/Common/BitStream_test.go @@ -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) + } + } +} diff --git a/Compression/Huffman.go b/Compression/Huffman.go new file mode 100644 index 00000000..b5a5b191 --- /dev/null +++ b/Compression/Huffman.go @@ -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() +} diff --git a/Compression/Wav.go b/Compression/Wav.go new file mode 100644 index 00000000..76c90a13 --- /dev/null +++ b/Compression/Wav.go @@ -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() +} diff --git a/Engine.go b/Engine.go index 3da50c12..785aa281 100644 --- a/Engine.go +++ b/Engine.go @@ -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() + }() +} diff --git a/MPQStream.go b/MPQStream.go index 40812241..8cf71b41 100644 --- a/MPQStream.go +++ b/MPQStream.go @@ -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); diff --git a/SceneMainMenu.go b/SceneMainMenu.go index 3405fb30..fdc8ee2a 100644 --- a/SceneMainMenu.go +++ b/SceneMainMenu.go @@ -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 + } }