package cpe import ( "bufio" "encoding/binary" "errors" "fmt" "io" "os" ) type CPEFileSection struct { Name string Size int Data []byte } type CPEMetadata struct { Segments map[string]CPEFileSection } type CPEReader struct { FileHandle *os.File streamReader *bufio.Reader cursorOffset uint32 Metadata CPEMetadata //h header } const ( CPE_FILE_IDENTIFIER string = "CPDFile" CPE_FILE_IDENTIFIER_SIZE int = 7 CPE_FILE_VER int = 2 CPE_FILE_VER_SIZE int = 1 CPE_ENCRYPTED_FILE_VER int = 3 CPE_FILE_SUBVER int = 0 CPE_FILE_SUBVER_SIZE int = 1 CPE_FILE_HEADER_LENGTH int = 9 CPE_SECTION_NAME_SIZE int = 3 CPE_SECTION_LEN_SIZE int = 4 CPE_SECTION_COUNT int = 3 CPE_SECTION_EXT string = "EXT" CPE_SECTION_CP string = "CPS" CPE_SECTION_LLP string = "LLP" CPE_SECTION_RADIOPASS string = "RPP" CPE_SECTION_TONEFILE string = "TON" ) func NewReader(fileName string) (_ CPEReader, err error) { return CPEReader{}.NewReader(fileName) } func (r CPEReader) NewReader(fileName string) (_ CPEReader, err error) { r.FileHandle, err = os.Open(fileName) if err != nil { return r, err } r.streamReader = bufio.NewReader(r.FileHandle) return r, nil } func (r *CPEReader) Close() { r.FileHandle.Close() } func (r *CPEReader) ValidateHeader() (err error) { // first7Chars := make([]byte, 7) _, err = io.ReadFull(r.streamReader, first7Chars) if err != nil { return err } if string(first7Chars) != "CPDFile" { return fmt.Errorf("missing 'cpdfile' header") } versionByte, err := r.streamReader.ReadByte() if err != nil { return err } if int(versionByte) != CPE_FILE_VER { if int(versionByte) == CPE_ENCRYPTED_FILE_VER { return fmt.Errorf("file is encrypted (version %d)", versionByte) } return fmt.Errorf("file-version %d not supported", versionByte) } // waste 1 byte r.streamReader.Discard(1) return nil } func (r *CPEReader) readSegment() (fileSection CPEFileSection, err error) { sectionNameBytes := make([]byte, 3) _, err = io.ReadFull(r.streamReader, sectionNameBytes) if err != nil { return fileSection, err } fileSection.Name = string(sectionNameBytes) sectionSizeBytes := make([]byte, 4) _, err = io.ReadFull(r.streamReader, sectionSizeBytes) if err != nil { return fileSection, err } fileSection.Size = int(binary.LittleEndian.Uint32(sectionSizeBytes)) fileSection.Data = make([]byte, fileSection.Size) _, err = io.ReadFull(r.streamReader, fileSection.Data) if err != nil { return fileSection, err } return fileSection, nil } func (r *CPEReader) ReadData() (err error) { r.Metadata.Segments = make(map[string]CPEFileSection) for { nextSegment, err := r.readSegment() if err != nil { if errors.Is(err, io.EOF) { return nil } return err } r.Metadata.Segments[nextSegment.Name] = nextSegment //fmt.Printf("Segment Found: %s (%d bytes)\n", nextSegment.Name, nextSegment.Size) } } /* func (r *CPEReader) 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) 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 *CPEReader) 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 *CPEReader) 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 *CPEReader) GetOffset() (offset uint32) { return r.cursorOffset } func (r *CPEReader) 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 *CPEReader) 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 } */