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 }