2020-09-08 15:45:26 -04:00
|
|
|
package mpq
|
|
|
|
|
|
|
|
import (
|
2020-09-14 14:47:11 -04:00
|
|
|
"fmt"
|
2020-09-08 15:45:26 -04:00
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
|
|
|
)
|
|
|
|
|
2020-09-14 14:47:11 -04:00
|
|
|
const (
|
|
|
|
bufLength = 32
|
|
|
|
)
|
|
|
|
|
2020-09-08 15:45:26 -04:00
|
|
|
// static check that Asset implements Asset
|
|
|
|
var _ asset.Asset = &Asset{}
|
|
|
|
|
|
|
|
// Asset represents a file record within an MPQ archive
|
|
|
|
type Asset struct {
|
2020-09-14 14:47:11 -04:00
|
|
|
stream d2interface.DataStream
|
|
|
|
data []byte
|
2020-09-09 14:35:52 -04:00
|
|
|
path string
|
2020-09-08 15:45:26 -04:00
|
|
|
source *Source
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type returns the asset type
|
|
|
|
func (a *Asset) Type() types.AssetType {
|
|
|
|
return types.Ext2AssetType(filepath.Ext(a.Path()))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Source returns the source of this asset
|
|
|
|
func (a *Asset) Source() asset.Source {
|
|
|
|
return a.source
|
|
|
|
}
|
|
|
|
|
|
|
|
// Path returns the sub-path (within the source) of this asset
|
|
|
|
func (a *Asset) Path() string {
|
2020-09-09 14:35:52 -04:00
|
|
|
return a.path
|
2020-09-08 15:45:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read will read asset data into the given buffer
|
|
|
|
func (a *Asset) Read(buf []byte) (n int, err error) {
|
2021-01-10 02:44:42 -05:00
|
|
|
return a.stream.Read(buf)
|
2020-09-08 15:45:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Seek will seek the read position for the next read operation
|
|
|
|
func (a *Asset) Seek(offset int64, whence int) (n int64, err error) {
|
|
|
|
return a.stream.Seek(offset, whence)
|
|
|
|
}
|
2020-09-09 14:35:52 -04:00
|
|
|
|
2020-09-14 14:47:11 -04:00
|
|
|
// Close will seek the read position for the next read operation
|
|
|
|
func (a *Asset) Close() (err error) {
|
|
|
|
// Calling a.stream.Close() will set the stream to nil, we dont want to do that.
|
|
|
|
// Because this asset gets cached, it may get retrieved again and used, in which
|
|
|
|
// case we will want the stream ready. So, instead of closing, we just seek back to the start.
|
|
|
|
// The garbage collector should get around to it if it ever gets ejected from the cache.
|
|
|
|
_, err = a.Seek(0, 0)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Data returns the raw file data as a slice of bytes
|
|
|
|
func (a *Asset) Data() ([]byte, error) {
|
|
|
|
if a.stream == nil {
|
|
|
|
return nil, fmt.Errorf("asset has no file: %s", a.Path())
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.data != nil {
|
|
|
|
return a.data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
_, seekErr := a.Seek(0, 0)
|
|
|
|
if seekErr != nil {
|
|
|
|
return nil, seekErr
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := make([]byte, bufLength)
|
|
|
|
data := make([]byte, 0)
|
|
|
|
|
|
|
|
for {
|
|
|
|
numBytesRead, readErr := a.Read(buf)
|
|
|
|
|
|
|
|
data = append(data, buf[:numBytesRead]...)
|
|
|
|
|
|
|
|
if readErr != nil || numBytesRead == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
a.data = data
|
|
|
|
|
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the path
|
2020-09-09 14:35:52 -04:00
|
|
|
func (a *Asset) String() string {
|
|
|
|
return a.Path()
|
|
|
|
}
|