Skip to content

Commit

Permalink
Merge pull request #7 from Galaco/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Galaco authored Oct 26, 2019
2 parents d34aa55 + 5bb6ae5 commit 3f0c638
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 32 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module github.com/galaco/vtf

go 1.13
78 changes: 50 additions & 28 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,37 @@ import (
"io"
)

const headerSize = 96
const (
vtfSignature = "VTF\x00"
)

var (
// ErrorVtfSignatureMismatch occurs when a stream does not start with the VTF magic signature
ErrorVtfSignatureMismatch = errors.New("header signature does not match: VTF\x00")
// ErrorTextureDepthNotSupported occurs when attempting to parse a stream with depth > 1
ErrorTextureDepthNotSupported = errors.New("only vtf textures with depth 1 are supported")
// ErrorMipmapSizeMismatch occurs when filesize does not match calculated mipmap size
ErrorMipmapSizeMismatch = errors.New("expected data size is smaller than actual")
)

// Reader: Vtf Reader
// Reader reads from a vtf stream
type Reader struct {
stream io.Reader
}

// Read: Reads the vtf image from stream into a usable structure
// ReadHeader reads the header of a texture only.
func (reader *Reader) ReadHeader() (*Header, error) {
buf := bytes.Buffer{}
_, err := buf.ReadFrom(reader.stream)
if err != nil {
return nil, err
}

// Header
return reader.parseHeader(buf.Bytes())
}

// Read parses vtf image from stream into a usable structure
// The only error to expect would be if mipmap data size overflows the total file size; normally
// due to tampered Header data.
func (reader *Reader) Read() (*Vtf, error) {
Expand All @@ -32,7 +55,7 @@ func (reader *Reader) Read() (*Vtf, error) {
return nil, err
}

// Tesources - in vtf 7.3+ only
// Resources - in vtf 7.3+ only
resourceData, err := reader.parseOtherResourceData(header, buf.Bytes())
if err != nil {
return nil, err
Expand All @@ -45,7 +68,7 @@ func (reader *Reader) Read() (*Vtf, error) {
}

// Mipmaps
highResImage, err := reader.readMipmaps(header, buf.Bytes()[header.HeaderSize+uint32(len(lowResImage)):])
highResImage, err := reader.readMipmaps(header, buf.Bytes())
if err != nil {
return nil, err
}
Expand All @@ -58,12 +81,11 @@ func (reader *Reader) Read() (*Vtf, error) {
}, nil
}

// parseHeader: Parse vtf Header.
// parseHeader reads vtf Header.
func (reader *Reader) parseHeader(buffer []byte) (*Header, error) {

// We know Header is 96 bytes
// We know Header is 96 bytes maximum
// Note it *may* not be someday...
headerBytes := make([]byte, headerSize)
headerBytes := make([]byte, 96)

// Read bytes equal to Header size
byteReader := bytes.NewReader(buffer)
Expand All @@ -79,14 +101,14 @@ func (reader *Reader) parseHeader(buffer []byte) (*Header, error) {
if err != nil {
return nil, err
}
if string(header.Signature[:4]) != "VTF\x00" {
return nil, errors.New("Header signature does not match: VTF\x00")
if string(header.Signature[:4]) != vtfSignature {
return nil, ErrorVtfSignatureMismatch
}

return &header, nil
}

// parseOtherResourceData: Returns resource data for 7.3+ images
// parseOtherResourceData reads resource data for 7.3+ images
func (reader *Reader) parseOtherResourceData(header *Header, buffer []byte) ([]byte, error) {
// validate Header version
if (header.Version[0]*10+header.Version[1] < 73) || header.NumResource == 0 {
Expand All @@ -98,7 +120,7 @@ func (reader *Reader) parseOtherResourceData(header *Header, buffer []byte) ([]b
return []byte{}, nil
}

// readLowResolutionMipmap: Reads the low resolution texture information
// readLowResolutionMipmap reads the low resolution texture information
// This is normally what you see previewed in Hammer texture browser.
// The largest axis should always be 16 wide/tall. The smallest can be any value,
// but is padded out to divisible by 4 for Dxt1 compressionn reasons
Expand All @@ -109,7 +131,7 @@ func (reader *Reader) readLowResolutionMipmap(header *Header, buffer []byte) ([]
format.Dxt1)

imgBuffer := make([]byte, bufferSize)
byteReader := bytes.NewReader(buffer[headerSize : headerSize+bufferSize])
byteReader := bytes.NewReader(buffer[int(header.HeaderSize) : int(header.HeaderSize)+bufferSize])
sectionReader := io.NewSectionReader(byteReader, 0, int64(bufferSize))
_, err := sectionReader.Read(imgBuffer)
if err != nil {
Expand All @@ -119,52 +141,52 @@ func (reader *Reader) readLowResolutionMipmap(header *Header, buffer []byte) ([]
return imgBuffer, nil
}

// readMipmaps: Read all mipmaps
// readMipmaps reads all mipmaps
// Returned format is a bit odd, but is just a set of flat arrays containing arrays:
// mipmap[frame[face[slice[RGBA]]]
func (reader *Reader) readMipmaps(header *Header, buffer []byte) ([][][][][]byte, error) {
func (reader *Reader) readMipmaps(header *Header, buffer []byte) ([][][][][]uint8, error) {
if header.Depth > 1 {
return [][][][][]byte{}, errors.New("only vtf textures with depth 1 are supported")
return [][][][][]uint8{}, ErrorTextureDepthNotSupported
}

depth := header.Depth

// This shouldn't ever happen, yet it occasionally seems to
// Occurs in texture versions before 7.2
if depth == 0 {
depth = 1
}

// Only support 1 ZSlice. No known Source game can use > 1 zslices
numZSlice := uint16(1)
bufferOffset := 0
bufferEnd := len(buffer)

storedFormat := format.Format(header.HighResImageFormat)
mipmapSizes := internal.ComputeMipmapSizes(int(header.MipmapCount), int(header.Width), int(header.Height))

// Iterate mipmap; smallest to largest
mipMaps := make([][][][][]byte, header.MipmapCount)
for mipmapIdx := uint8(0); mipmapIdx < header.MipmapCount; mipmapIdx++ {
mipMaps := make([][][][][]uint8, header.MipmapCount)
for mipmapIdx := int8(header.MipmapCount - 1); mipmapIdx >= int8(0); mipmapIdx-- {
// Frame by frame; first to last
frames := make([][][][]byte, header.Frames)
frames := make([][][][]uint8, header.Frames)
for frameIdx := uint16(0); frameIdx < header.Frames; frameIdx++ {
faces := make([][][]byte, 1)
faces := make([][][]uint8, 1)
// Face by face; first to last
// @TODO is this correct to use depth? How to know how many faces there are
for faceIdx := uint16(0); faceIdx < depth; faceIdx++ {
zSlices := make([][]byte, 1)
zSlices := make([][]uint8, 1)
// Z Slice by Z Slice; first to last
// @TODO wtf is a z slice, and how do we know how many there are
for sliceIdx := uint16(0); sliceIdx < numZSlice; sliceIdx++ {
bufferSize := internal.ComputeSizeOfMipmapData(
mipmapSizes[mipmapIdx][0],
mipmapSizes[mipmapIdx][1],
storedFormat)
if len(buffer) < bufferOffset+bufferSize {
return mipMaps, errors.New("expected data size is smaller than actual")
if len(buffer) < bufferEnd-bufferSize {
return mipMaps, ErrorMipmapSizeMismatch
}
img := buffer[bufferOffset : bufferOffset+bufferSize]
img := buffer[bufferEnd-bufferSize : bufferEnd]

bufferOffset += bufferSize
bufferEnd -= bufferSize
zSlices[sliceIdx] = img
}
faces[faceIdx] = zSlices
Expand Down
13 changes: 9 additions & 4 deletions vtf.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type Vtf struct {
header Header
resources []byte
lowResolutionImageData []uint8
highResolutionImageData [][][][][]byte //[]mipmap[]frame[]face[]slice
highResolutionImageData [][][][][]uint8 //[]mipmap[]frame[]face[]slice
}

// Header returns vtf Header
Expand All @@ -20,13 +20,18 @@ func (vtf *Vtf) LowResImageData() []uint8 {
}

// HighResImageData returns all data for all mipmaps
func (vtf *Vtf) HighResImageData() [][][][][]byte {
func (vtf *Vtf) HighResImageData() [][][][][]uint8 {
return vtf.highResolutionImageData
}

// Image returns raw data of the first frame of the highest resolution mipmap
func (vtf *Vtf) Image() []uint8 {
return vtf.HighestResolutionImageForFrame(0)
}

// MipmapsForFrame returns all mipmap sizes for a single frame
func (vtf *Vtf) MipmapsForFrame(frame int) [][]byte {
ret := make([][]byte, vtf.header.MipmapCount)
func (vtf *Vtf) MipmapsForFrame(frame int) [][]uint8 {
ret := make([][]uint8, vtf.header.MipmapCount)

for idx, mipmap := range vtf.highResolutionImageData {
ret[idx] = mipmap[frame][0][0]
Expand Down

0 comments on commit 3f0c638

Please sign in to comment.