120 lines
2.9 KiB
Go
120 lines
2.9 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
|
|
}
|
|
}
|