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.
175 lines
4.0 KiB
Go
175 lines
4.0 KiB
Go
package s19
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
)
|
|
|
|
type S19Reader struct {
|
|
FilePath string
|
|
File *os.File
|
|
OsSize int64
|
|
}
|
|
|
|
func NewS19Reader(filePath string) (*S19Reader, error) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stat, err := file.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &S19Reader{
|
|
FilePath: filePath,
|
|
File: file,
|
|
OsSize: stat.Size(),
|
|
}, nil
|
|
}
|
|
|
|
func (fr *S19Reader) ReadAllRecords() (records []*SRecord, err error) {
|
|
_, err = fr.File.Seek(0, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
scanner := bufio.NewScanner(fr.File)
|
|
for scanner.Scan() {
|
|
if len(scanner.Text()) < 10 {
|
|
continue
|
|
}
|
|
record, err := parseSRecord(scanner.Text())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records = append(records, record)
|
|
//log.Debug().Msg(fmt.Sprint(record))
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return records, nil
|
|
}
|
|
|
|
func parseSRecord(line string) (*SRecord, error) {
|
|
if len(line) < 10 || line[0] != 'S' {
|
|
return nil, fmt.Errorf("invalid S-record format '%s'", line)
|
|
}
|
|
|
|
record := &SRecord{}
|
|
record.Type = line[:2]
|
|
|
|
// Extract byte count (hexadecimal, 2 characters)
|
|
count, err := strconv.ParseInt(line[2:4], 16, 8)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid byte count: %v", err)
|
|
}
|
|
record.Count = int(count)
|
|
|
|
// Address length depends on the type
|
|
var addressLength int
|
|
switch record.Type {
|
|
case "S0", "S1", "S9": // Address is 2 bytes
|
|
addressLength = 4
|
|
case "S2", "S8": // Address is 3 bytes
|
|
addressLength = 6
|
|
case "S3", "S7": // Address is 4 bytes
|
|
addressLength = 8
|
|
default:
|
|
return nil, fmt.Errorf("unsupported record type: %s", record.Type)
|
|
}
|
|
|
|
// Extract and parse the address
|
|
if len(line) < 4+addressLength {
|
|
return nil, errors.New("record is too short for address")
|
|
}
|
|
address, err := strconv.ParseUint(line[4:4+addressLength], 16, 32)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid address: %v", err)
|
|
}
|
|
record.Address = uint32(address)
|
|
|
|
// Extract data and checksum
|
|
dataStart := 4 + addressLength
|
|
dataEnd := len(line) - 2 // Exclude checksum
|
|
if dataEnd > dataStart {
|
|
record.Data, err = hex.DecodeString(line[dataStart:dataEnd])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid data: %v", err)
|
|
}
|
|
}
|
|
|
|
// Validate checksum
|
|
checksum, err := strconv.ParseUint(line[len(line)-2:], 16, 8)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid checksum: %v", err)
|
|
}
|
|
if !validateChecksum(line, byte(checksum)) {
|
|
return nil, errors.New("checksum validation failed")
|
|
}
|
|
|
|
return record, nil
|
|
}
|
|
|
|
func (fr *S19Reader) Close() error {
|
|
return fr.File.Close()
|
|
}
|
|
|
|
func (*S19Reader) DetectAddressRanges(records []*SRecord) []AddressRange {
|
|
// Sort records by address
|
|
sort.Slice(records, func(i, j int) bool {
|
|
return records[i].Address < records[j].Address
|
|
})
|
|
|
|
var ranges []AddressRange
|
|
var currentRange *AddressRange
|
|
|
|
for sliceIndex, record := range records {
|
|
if currentRange == nil {
|
|
// Start a new range
|
|
currentRange = &AddressRange{
|
|
SliceStart: uint32(sliceIndex),
|
|
SliceEnd: 0,
|
|
StartAddress: record.Address,
|
|
EndAddress: record.Address + uint32(len(record.Data)) - 1,
|
|
Size: uint32(len(record.Data)),
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Check if the current record is contiguous with the previous range
|
|
if record.Address == currentRange.EndAddress+1 {
|
|
currentRange.EndAddress = record.Address + uint32(len(record.Data)) - 1
|
|
currentRange.Size += uint32(len(record.Data))
|
|
currentRange.SliceEnd = uint32(sliceIndex)
|
|
} else {
|
|
// Save the current range and start a new one
|
|
if currentRange.SliceEnd == 0 {
|
|
currentRange.SliceEnd = currentRange.SliceStart
|
|
}
|
|
ranges = append(ranges, *currentRange)
|
|
currentRange = &AddressRange{
|
|
SliceStart: uint32(sliceIndex),
|
|
SliceEnd: 0,
|
|
StartAddress: record.Address,
|
|
EndAddress: record.Address + uint32(len(record.Data)) - 1,
|
|
Size: uint32(len(record.Data)),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Append the last range
|
|
if currentRange != nil {
|
|
ranges = append(ranges, *currentRange)
|
|
}
|
|
|
|
return ranges
|
|
}
|