1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-10-31 16:27:18 -04:00
OpenDiablo2/MPQ/MPQStream.go

256 lines
7.4 KiB
Go
Raw Normal View History

2019-10-25 19:37:04 -04:00
package MPQ
2019-10-24 09:31:59 -04:00
import (
"bytes"
"compress/zlib"
"encoding/binary"
"fmt"
2019-10-26 20:09:33 -04:00
"log"
2019-10-25 15:06:47 -04:00
"strings"
2019-10-24 09:31:59 -04:00
"github.com/JoshVarga/blast"
"github.com/OpenDiablo2/OpenDiablo2/Common"
"github.com/OpenDiablo2/OpenDiablo2/Compression"
2019-10-24 09:31:59 -04:00
)
2019-10-25 19:37:04 -04:00
// Stream represents a stream of data in an MPQ archive
type Stream struct {
2019-10-24 09:31:59 -04:00
MPQData MPQ
2019-10-25 19:37:04 -04:00
BlockTableEntry BlockTableEntry
2019-10-24 09:31:59 -04:00
FileName string
EncryptionSeed uint32
BlockPositions []uint32
CurrentPosition uint32
CurrentData []byte
CurrentBlockIndex uint32
BlockSize uint32
}
2019-10-25 19:37:04 -04:00
// CreateStream creates an MPQ stream
func CreateStream(mpq MPQ, blockTableEntry BlockTableEntry, fileName string) *Stream {
result := &Stream{
2019-10-24 09:31:59 -04:00
MPQData: mpq,
BlockTableEntry: blockTableEntry,
CurrentBlockIndex: 0xFFFFFFFF,
}
2019-10-25 15:06:47 -04:00
fileSegs := strings.Split(fileName, `\`)
result.EncryptionSeed = hashString(fileSegs[len(fileSegs)-1], 3)
2019-10-26 20:09:33 -04:00
if result.BlockTableEntry.HasFlag(FileFixKey) {
2019-10-24 09:31:59 -04:00
result.EncryptionSeed = (result.EncryptionSeed + result.BlockTableEntry.FilePosition) ^ result.BlockTableEntry.UncompressedFileSize
}
result.BlockSize = 0x200 << result.MPQData.Data.BlockSize
if result.BlockTableEntry.HasFlag(FilePatchFile) {
log.Fatal("Patching is not supported")
}
2019-10-26 20:09:33 -04:00
if (result.BlockTableEntry.HasFlag(FileCompress) || result.BlockTableEntry.HasFlag(FileImplode)) && !result.BlockTableEntry.HasFlag(FileSingleUnit) {
2019-10-24 09:31:59 -04:00
result.loadBlockOffsets()
}
return result
}
2019-10-25 19:37:04 -04:00
func (v *Stream) loadBlockOffsets() {
2019-10-24 09:31:59 -04:00
blockPositionCount := ((v.BlockTableEntry.UncompressedFileSize + v.BlockSize - 1) / v.BlockSize) + 1
v.BlockPositions = make([]uint32, blockPositionCount)
v.MPQData.File.Seek(int64(v.BlockTableEntry.FilePosition), 0)
binary.Read(v.MPQData.File, binary.LittleEndian, &v.BlockPositions)
blockPosSize := blockPositionCount << 2
2019-10-26 20:09:33 -04:00
if v.BlockTableEntry.HasFlag(FileEncrypted) {
2019-10-24 09:31:59 -04:00
decrypt(v.BlockPositions, v.EncryptionSeed-1)
if v.BlockPositions[0] != blockPosSize {
panic("Decryption of MPQ failed!")
}
if v.BlockPositions[1] > v.BlockSize+blockPosSize {
panic("Decryption of MPQ failed!")
}
}
}
2019-10-25 19:37:04 -04:00
func (v *Stream) Read(buffer []byte, offset, count uint32) uint32 {
2019-10-26 20:09:33 -04:00
if v.BlockTableEntry.HasFlag(FileSingleUnit) {
2019-10-24 09:31:59 -04:00
return v.readInternalSingleUnit(buffer, offset, count)
}
toRead := count
readTotal := uint32(0)
for toRead > 0 {
read := v.readInternal(buffer, offset, count)
if read == 0 {
break
}
readTotal += read
offset += read
toRead -= read
}
return readTotal
}
2019-10-25 19:37:04 -04:00
func (v *Stream) readInternalSingleUnit(buffer []byte, offset, count uint32) uint32 {
2019-10-24 09:31:59 -04:00
if len(v.CurrentData) == 0 {
v.loadSingleUnit()
}
2019-10-25 19:12:42 -04:00
bytesToCopy := Common.Min(uint32(len(v.CurrentData))-v.CurrentPosition, count)
2019-10-24 09:31:59 -04:00
copy(buffer[offset:offset+bytesToCopy], v.CurrentData[v.CurrentPosition:v.CurrentPosition+bytesToCopy])
v.CurrentPosition += bytesToCopy
return bytesToCopy
}
2019-10-25 19:37:04 -04:00
func (v *Stream) readInternal(buffer []byte, offset, count uint32) uint32 {
2019-10-24 09:31:59 -04:00
v.bufferData()
localPosition := v.CurrentPosition % v.BlockSize
2019-10-26 20:09:33 -04:00
bytesToCopy := Common.MinInt32(int32(len(v.CurrentData))-int32(localPosition), int32(count))
2019-10-24 09:31:59 -04:00
if bytesToCopy <= 0 {
return 0
}
2019-10-26 20:09:33 -04:00
copy(buffer[offset:offset+uint32(bytesToCopy)], v.CurrentData[localPosition:localPosition+uint32(bytesToCopy)])
v.CurrentPosition += uint32(bytesToCopy)
return uint32(bytesToCopy)
2019-10-24 09:31:59 -04:00
}
2019-10-25 19:37:04 -04:00
func (v *Stream) bufferData() {
2019-10-24 09:31:59 -04:00
requiredBlock := uint32(v.CurrentPosition / v.BlockSize)
if requiredBlock == v.CurrentBlockIndex {
return
}
2019-10-25 19:12:42 -04:00
expectedLength := Common.Min(v.BlockTableEntry.UncompressedFileSize-(requiredBlock*v.BlockSize), v.BlockSize)
2019-10-24 09:31:59 -04:00
v.CurrentData = v.loadBlock(requiredBlock, expectedLength)
v.CurrentBlockIndex = requiredBlock
}
2019-10-25 19:37:04 -04:00
func (v *Stream) loadSingleUnit() {
2019-10-24 09:31:59 -04:00
fileData := make([]byte, v.BlockSize)
v.MPQData.File.Seek(int64(v.MPQData.Data.HeaderSize), 0)
binary.Read(v.MPQData.File, binary.LittleEndian, &fileData)
if v.BlockSize == v.BlockTableEntry.UncompressedFileSize {
v.CurrentData = fileData
return
}
v.CurrentData = decompressMulti(fileData, v.BlockTableEntry.UncompressedFileSize)
}
2019-10-25 19:37:04 -04:00
func (v *Stream) loadBlock(blockIndex, expectedLength uint32) []byte {
2019-10-24 09:31:59 -04:00
var (
offset uint32
toRead uint32
)
2019-10-26 20:09:33 -04:00
if v.BlockTableEntry.HasFlag(FileCompress) || v.BlockTableEntry.HasFlag(FileImplode) {
2019-10-24 09:31:59 -04:00
offset = v.BlockPositions[blockIndex]
toRead = v.BlockPositions[blockIndex+1] - offset
} else {
offset = blockIndex * v.BlockSize
toRead = expectedLength
}
offset += v.BlockTableEntry.FilePosition
data := make([]byte, toRead)
v.MPQData.File.Seek(int64(offset), 0)
binary.Read(v.MPQData.File, binary.LittleEndian, &data)
2019-10-26 20:09:33 -04:00
if v.BlockTableEntry.HasFlag(FileEncrypted) && v.BlockTableEntry.UncompressedFileSize > 3 {
2019-10-24 09:31:59 -04:00
if v.EncryptionSeed == 0 {
panic("Unable to determine encryption key")
}
decryptBytes(data, blockIndex+v.EncryptionSeed)
}
2019-10-26 20:09:33 -04:00
if v.BlockTableEntry.HasFlag(FileCompress) && (toRead != expectedLength) {
if !v.BlockTableEntry.HasFlag(FileSingleUnit) {
2019-10-24 09:31:59 -04:00
data = decompressMulti(data, expectedLength)
} else {
data = pkDecompress(data)
}
}
2019-10-26 20:09:33 -04:00
if v.BlockTableEntry.HasFlag(FileImplode) && (toRead != expectedLength) {
2019-10-24 09:31:59 -04:00
data = pkDecompress(data)
}
return data
}
func decompressMulti(data []byte, expectedLength uint32) []byte {
copmressionType := data[0]
switch copmressionType {
case 1: // Huffman
panic("huffman decompression not supported")
case 2: // ZLib/Deflate
return deflate(data[1:])
case 8: // PKLib/Impode
return pkDecompress(data[1:])
case 0x10: // BZip2
panic("bzip2 decompression not supported")
case 0x80: // IMA ADPCM Stereo
2019-10-27 12:48:25 -04:00
return Compression.WavDecompress(data[1:], 2)
2019-10-24 09:31:59 -04:00
//return MpqWavCompression.Decompress(sinput, 2);
2019-10-27 12:48:25 -04:00
//panic("ima adpcm sterio decompression not supported")
2019-10-24 09:31:59 -04:00
case 0x40: // IMA ADPCM Mono
//return MpqWavCompression.Decompress(sinput, 1)
panic("mpq wav decompression not supported")
case 0x12:
panic("lzma decompression not supported")
// Combos
case 0x22:
// TODO: sparse then zlib
panic("sparse decompression + deflate decompression not supported")
case 0x30:
// TODO: sparse then bzip2
panic("sparse decompression + bzip2 decompression not supported")
case 0x41:
2019-10-25 15:06:47 -04:00
sinput := Compression.HuffmanDecompress(data[1:])
sinput = Compression.WavDecompress(sinput, 1)
tmp := make([]byte, len(sinput))
copy(tmp, sinput)
return tmp
2019-10-24 09:31:59 -04:00
case 0x48:
//byte[] result = PKDecompress(sinput, outputLength);
//return MpqWavCompression.Decompress(new MemoryStream(result), 1);
panic("pk + mpqwav decompression not supported")
case 0x81:
2019-10-25 15:06:47 -04:00
sinput := Compression.HuffmanDecompress(data[1:])
2019-10-25 17:09:07 -04:00
sinput = Compression.WavDecompress(sinput, 2)
2019-10-25 15:06:47 -04:00
tmp := make([]byte, len(sinput))
copy(tmp, sinput)
return tmp
2019-10-24 09:31:59 -04:00
case 0x88:
//byte[] result = PKDecompress(sinput, outputLength);
//return MpqWavCompression.Decompress(new MemoryStream(result), 2);
panic("pk + wav decompression not supported")
default:
panic(fmt.Sprintf("decompression not supported for unknown compression type %X", copmressionType))
}
}
func deflate(data []byte) []byte {
b := bytes.NewReader(data)
r, err := zlib.NewReader(b)
if err != nil {
panic(err)
}
buffer := new(bytes.Buffer)
2019-10-26 20:09:33 -04:00
_, err = buffer.ReadFrom(r)
if err != nil {
log.Panic(err)
}
err = r.Close()
if err != nil {
log.Panic(err)
}
2019-10-24 09:31:59 -04:00
return buffer.Bytes()
}
func pkDecompress(data []byte) []byte {
b := bytes.NewReader(data)
r, err := blast.NewReader(b)
if err != nil {
panic(err)
}
buffer := new(bytes.Buffer)
2019-10-26 20:09:33 -04:00
_, err = buffer.ReadFrom(r)
if err != nil {
panic(err)
}
err = r.Close()
if err != nil {
panic(err)
}
2019-10-24 09:31:59 -04:00
return buffer.Bytes()
}