moto-flash-data/motolol/cpe/cpe.go
2025-01-20 13:10:35 +01:00

251 lines
6.8 KiB
Go

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
}
*/