You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
7.4 KiB
Go

package gma
import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"hash/crc32"
"io"
"os"
)
type GMAReader struct {
FileHandle *os.File
//gmaStream io.Reader
gmaStreamReader *bufio.Reader
cursorOffset uint32
//h header
}
func NewReader(fileName string) (_ GMAReader, err error) {
return GMAReader{}.NewReader(fileName)
}
func (r GMAReader) NewReader(fileName string) (_ GMAReader, err error) {
r.FileHandle, err = os.Open(fileName)
if err != nil {
return r, err
}
r.gmaStreamReader = bufio.NewReader(r.FileHandle)
return r, nil
}
func (r *GMAReader) IsValidGMAD() (isValid bool, err error) {
gmadHeader, err := r.gmaStreamReader.Peek(4)
if err != nil {
return false, err
}
return string(gmadHeader) == "GMAD", nil
}
func (r *GMAReader) IsCompressed() (isValid bool, err error) {
gmadHeader, err := r.gmaStreamReader.Peek(4)
if err != nil {
return false, err
}
return gmadHeader[0] == 93 && gmadHeader[1] == 0 && gmadHeader[1] == gmadHeader[2] && gmadHeader[1] == gmadHeader[3], nil
}
func (r *GMAReader) Close() {
r.FileHandle.Close()
}
func (r *GMAReader) ReadHeader() (_ GMAHeader, err error) {
header := GMAHeader{}
r.gmaStreamReader.Discard(4)
r.cursorOffset += 4
if r.cursorOffset != 4 {
return header, fmt.Errorf("%d invalid offset", r.cursorOffset)
}
// Read the format version
header.FormatVersion, err = r.gmaStreamReader.ReadByte()
if err != nil {
return header, err
}
r.cursorOffset++
// Read the SteamID
steamIDBytes := make([]byte, 8)
_, err = r.gmaStreamReader.Read(steamIDBytes)
if err != nil {
return header, err
}
r.cursorOffset += 8
header.SteamID = binary.LittleEndian.Uint64(steamIDBytes)
// Read the Timestamp
timestampBytes := make([]byte, 8)
_, err = r.gmaStreamReader.Read(timestampBytes)
if err != nil {
return header, err
}
r.cursorOffset += 8
header.Timestamp = binary.LittleEndian.Uint64(timestampBytes)
if header.FormatVersion > 1 {
header.FormatVersionDiscardByte, err = r.gmaStreamReader.ReadByte()
if err != nil {
return header, err
}
r.cursorOffset++
}
// Read the Title
header.Title, err = r.gmaStreamReader.ReadString(byte(0))
if err != nil {
return header, err
}
header.Title = header.Title[:len(header.Title)-1] // remove nullbyte
r.cursorOffset += uint32(len(header.Title) + 1) // Add title length + null byte
// Read the Description
header.Description, err = r.gmaStreamReader.ReadString(byte(0))
if err != nil {
return header, err
}
header.Description = header.Description[:len(header.Description)-1] // remove nullbyte
//fmt.Printf("Desc Start %d\n", r.cursorOffset)
r.cursorOffset += uint32(len(header.Description) + 1) // Add description length + null byte
//fmt.Printf("Desc End %d\n", r.cursorOffset)
// Read the Author
header.Author, err = r.gmaStreamReader.ReadString(byte(0))
if err != nil {
return header, err
}
header.Author = header.Author[:len(header.Author)-1] // remove nullbyte
r.cursorOffset += uint32(len(header.Author) + 1) // Add author length + null byte
// Read the AddonVersion
addonVersionBytes := make([]byte, 4)
_, err = r.gmaStreamReader.Read(addonVersionBytes)
if err != nil {
return header, err
}
r.cursorOffset += 4
header.AddonVersion = int32(binary.LittleEndian.Uint32(addonVersionBytes))
return header, nil
}
func (r *GMAReader) readFileMetadata() (GMAFileMetadata, error) {
metadata := GMAFileMetadata{}
// Read the file name
fileName, err := r.gmaStreamReader.ReadString(byte(0))
if err != nil {
return metadata, err
}
//fmt.Printf("bufio ReadString(byte(0)) = len(%d) data=%x\n", len(fileName), fileName)
/*if len(fileName) == 1 { //fucky retry
fileName, err := r.gmaStreamReader.ReadString(byte(0))
if err != nil {
return metadata, err
}
fmt.Printf("RETRY!! bufio ReadString(byte(0)) = len(%d) data=%x\n", len(fileName), fileName)
}*/
fileName = fileName[:len(fileName)-1] // remove nullbyte that causes go string fuckyness
r.cursorOffset += uint32(len(fileName) + 1) // Add name length + null byte
metadata.FileName = fileName
// Read the file size
fileSizeBytes := make([]byte, 8)
_, err = io.ReadFull(r.gmaStreamReader, fileSizeBytes)
if err != nil {
return metadata, err
}
r.cursorOffset += 8
//fmt.Printf("bufio Read([]byte(4)]) fileSizeBytes = bytesRead(%d) data=%x\n", bytesRead, fileSizeBytes)
metadata.FileSize = int64(binary.LittleEndian.Uint64(fileSizeBytes))
// Read the file crc
crcBytes := make([]byte, 4)
_, err = io.ReadFull(r.gmaStreamReader, crcBytes)
if err != nil {
return metadata, err
}
r.cursorOffset += 4
//fmt.Printf("bufio Read([]byte(4)]) crcBytes = bytesRead(%d) data=%x\n", bytesRead, crcBytes)
metadata.CRC = binary.LittleEndian.Uint32(crcBytes)
// Read the next type
nextTypeBytes := make([]byte, 4)
_, err = io.ReadFull(r.gmaStreamReader, nextTypeBytes)
if err != nil {
return metadata, err
}
r.cursorOffset += 4
metadata.NextType = binary.LittleEndian.Uint32(nextTypeBytes)
//fmt.Printf("bufio Read([]byte(4)]) nextTypeBytes = bytesRead(%d) data=%x\n", bytesRead, nextTypeBytes)
return metadata, nil
}
func (r *GMAReader) ReadAddonCRC(lastOffset int64) (crc uint32, err error) {
limitReader := io.NewSectionReader(r.FileHandle, int64(r.cursorOffset)+lastOffset, int64(4))
crcBytes := make([]byte, 4)
_, err = limitReader.Read(crcBytes)
if err != nil {
return 0, err
}
r.cursorOffset += 4
CRC := binary.LittleEndian.Uint32(crcBytes)
return CRC, nil
}
func (r *GMAReader) ReadFiles() (firstType int32, files []GMAFileMetadata, err error) {
// read nType 4byte
firstTypeBytes := make([]byte, 4)
_, err = r.gmaStreamReader.Read(firstTypeBytes)
if err != nil {
return 0, files, err
}
r.cursorOffset += 4
firstType = int32(binary.LittleEndian.Uint32(firstTypeBytes))
if firstType == 0 {
return 0, files, nil
}
fileOffset := int64(0)
fileNumber := int32(1)
for {
fileMeta, err := r.readFileMetadata()
if err != nil {
if err == io.EOF {
break
}
return firstType, files, err
}
fileMeta.FileNumber = fileNumber
fileMeta.Offset = fileOffset
//fmt.Printf("%s CRC: %d Offset: %d Size: %d\n", fileMeta.FileName, fileMeta.CRC, fileMeta.Offset, fileMeta.FileSize)
//fmt.Printf("[% x]\n", fileMeta.FileName)
files = append(files, fileMeta)
fileOffset += fileMeta.FileSize
fileNumber++
if fileMeta.NextType == 0 {
break
}
}
return firstType, files, nil
}
func (r *GMAReader) GetOffset() (offset uint32) {
return r.cursorOffset
}
func (r *GMAReader) ExtractFileTo(fileMeta GMAFileMetadata, writer io.Writer) (extractMeta GMAExtractionMeta, err error) {
extractMeta.OriginalMeta = fileMeta
// Seek to the specified offset in the reader
limitReader := io.NewSectionReader(r.FileHandle, int64(r.cursorOffset)+fileMeta.Offset, int64(fileMeta.FileSize))
// Copy the specified length of data from the reader to the output file
buf := bytes.NewBuffer(nil)
_, err = io.CopyN(buf, limitReader, int64(fileMeta.FileSize))
if err != nil {
return extractMeta, err
}
shaHasher := sha256.New()
extractMeta.ExtractedCRC = crc32.Checksum(buf.Bytes(), crc32.MakeTable(crc32.IEEE))
shaHasher.Write(buf.Bytes())
extractMeta.ExtractedSHA256 = fmt.Sprintf("%x", shaHasher.Sum(nil))
buf.WriteTo(writer)
return extractMeta, nil
}
func (r *GMAReader) GetSHA256() (hash string, err error) {
shaHasher := sha256.New()
if _, err := io.Copy(shaHasher, r.FileHandle); err != nil {
return "", err
}
return fmt.Sprintf("%x", shaHasher.Sum(nil)), nil
}