303 lines
7.1 KiB
Go
303 lines
7.1 KiB
Go
package firmware
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/buffer"
|
|
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/compression"
|
|
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/crc"
|
|
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/encryption"
|
|
"golang.org/x/text/encoding/charmap"
|
|
"io"
|
|
"slices"
|
|
)
|
|
|
|
const SerialNumberSize = 16
|
|
const MarkerSize = 8
|
|
const MarkerPhyton = "Phyton\x00\x00"
|
|
const MarkerAlmaCode = "AlmaCode"
|
|
|
|
const BaseAddress = 0x8000000
|
|
|
|
type ASFirmwareHeader struct {
|
|
Description [256]byte
|
|
DateTime DateTime
|
|
DataSize uint32
|
|
}
|
|
|
|
func (h ASFirmwareHeader) DescriptionString() string {
|
|
decoder := charmap.Windows1251.NewDecoder()
|
|
buf, err := decoder.Bytes(zeroTerminatedSlice(h.Description[:]))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(buf)
|
|
}
|
|
|
|
func zeroTerminatedSlice(data []byte) []byte {
|
|
return data[:max(0, slices.Index(data, 0))]
|
|
}
|
|
|
|
type ASFileHeader struct {
|
|
Marker [MarkerSize]byte
|
|
HeaderSize uint32
|
|
DateTime DateTime
|
|
BufferSize uint32
|
|
SerialNumber [SerialNumberSize]byte
|
|
VersionLow uint8
|
|
VersionHigh uint8
|
|
|
|
// FileCount only available when IsAlmaCode() == true
|
|
FileCount uint32
|
|
FirmwareHeaderSize uint32
|
|
Compressed uint8
|
|
Reserved uint8
|
|
CRC32 uint32
|
|
UpdateCRC32 uint32
|
|
}
|
|
|
|
func (h ASFileHeader) IsAlmaCode() bool {
|
|
return bytes.Compare(h.Marker[:], []byte(MarkerAlmaCode)) == 0
|
|
}
|
|
|
|
type ASBlockHeader struct {
|
|
HeaderSize uint32
|
|
Size uint32
|
|
Addr uint32
|
|
|
|
SizeAligned uint32
|
|
}
|
|
|
|
type Block struct {
|
|
Header ASBlockHeader
|
|
Block encryption.EncryptedBlock
|
|
}
|
|
|
|
type Entry struct {
|
|
Header ASFirmwareHeader
|
|
Blocks Blocks
|
|
compressed bool
|
|
}
|
|
|
|
func (entry Entry) Code() []byte {
|
|
// Runs https://www.st.com/en/microcontrollers-microprocessors/stm32l475vc.html
|
|
// https://youtu.be/-IsAlSwFWIA?t=508
|
|
var buf []byte
|
|
for _, b := range entry.Blocks {
|
|
decBlock := slices.Clone(b.Block)
|
|
|
|
err := decBlock.Decrypt(encryption.NewFlashKeyMaterial(nil), !entry.compressed)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if entry.compressed {
|
|
data, err := compression.FirmwareBlockDecompress(decBlock.DataBlock()[:b.Header.Size])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
calculatedCrc := crc.CalculateCRC(data)
|
|
|
|
crc1, _ := decBlock.CRC()
|
|
|
|
if calculatedCrc != crc1 {
|
|
panic("wrong CRC")
|
|
}
|
|
|
|
newLength := int(b.Header.Addr) - BaseAddress + len(data)
|
|
if len(buf) < newLength {
|
|
buf = append(buf, make([]byte, newLength-len(buf))...)
|
|
}
|
|
|
|
copy(buf[int(b.Header.Addr)-BaseAddress:], data)
|
|
} else {
|
|
newLength := int(b.Header.Addr) - BaseAddress + len(decBlock.DataBlock())
|
|
if len(buf) < newLength {
|
|
buf = append(buf, make([]byte, newLength-len(buf))...)
|
|
}
|
|
|
|
copy(buf[int(b.Header.Addr)-BaseAddress:], decBlock.DataBlock())
|
|
}
|
|
}
|
|
|
|
return buf
|
|
}
|
|
|
|
type Firmware struct {
|
|
FileHeader ASFileHeader
|
|
|
|
Entries []Entry
|
|
|
|
Data []byte
|
|
}
|
|
|
|
type Blocks []Block
|
|
|
|
func BlocksFromData(data []byte) (result Blocks) {
|
|
inputData := buffer.PanicBuffer(data)
|
|
|
|
for len(inputData) > 0 {
|
|
var blockHeader ASBlockHeader
|
|
blockHeader.HeaderSize = inputData.ReadUint32()
|
|
|
|
//i := int32(-836261582)
|
|
blockHeader.Size = inputData.ReadUint32() ^ BlockHeaderSizeKey
|
|
//i2 := int32(-1294620572)
|
|
blockHeader.Addr = inputData.ReadUint32() ^ BlockHeaderAddrKey
|
|
blockHeader.SizeAligned = blockHeader.Size
|
|
//i3 := int32(-8)
|
|
if (blockHeader.SizeAligned % 8) != 0 {
|
|
blockHeader.SizeAligned = (blockHeader.SizeAligned & uint32(0xFFFFFFF8)) + 8
|
|
}
|
|
|
|
encryptedBlock := encryption.NewEncryptedBlock(int(blockHeader.SizeAligned))
|
|
if n := inputData.Read(encryptedBlock); n != len(encryptedBlock) {
|
|
panic(io.ErrUnexpectedEOF)
|
|
}
|
|
|
|
result = append(result, Block{
|
|
Header: blockHeader,
|
|
Block: encryptedBlock,
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (fw Firmware) Length() int {
|
|
if fw.FileHeader.IsAlmaCode() {
|
|
panic("not implemented")
|
|
}
|
|
return len(fw.Data) - int(fw.FileHeader.HeaderSize)
|
|
}
|
|
|
|
func LoadFirmware(buf []byte) (f *Firmware, err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
var ok bool
|
|
if err, ok = e.(error); !ok {
|
|
err = fmt.Errorf("%s", e)
|
|
}
|
|
}
|
|
}()
|
|
|
|
fw := &Firmware{
|
|
Data: buf,
|
|
}
|
|
dataBuf := buffer.Buffer(buf)
|
|
_, err = dataBuf.Read(fw.FileHeader.Marker[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
isAlmaCode := bytes.Compare(fw.FileHeader.Marker[:], []byte(MarkerAlmaCode)) == 0
|
|
|
|
if !isAlmaCode && bytes.Compare(fw.FileHeader.Marker[:], []byte(MarkerPhyton)) != 0 {
|
|
return nil, errors.New("unsupported header")
|
|
}
|
|
|
|
fw.FileHeader.HeaderSize, err = dataBuf.ReadUint32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataBuf = buf[len(fw.FileHeader.Marker)+4 : fw.FileHeader.HeaderSize]
|
|
|
|
var dateTime uint32
|
|
dateTime, err = dataBuf.ReadUint32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fw.FileHeader.DateTime = DateTime(dateTime)
|
|
fw.FileHeader.BufferSize, err = dataBuf.ReadUint32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = dataBuf.Read(fw.FileHeader.SerialNumber[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = func() error {
|
|
fw.FileHeader.VersionLow, err = dataBuf.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fw.FileHeader.VersionHigh, err = dataBuf.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fw.FileHeader.FileCount, err = dataBuf.ReadUint32()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fw.FileHeader.FirmwareHeaderSize, err = dataBuf.ReadUint32()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fw.FileHeader.Compressed, err = dataBuf.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fw.FileHeader.Reserved, err = dataBuf.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fw.FileHeader.CRC32, err = dataBuf.ReadUint32()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}(); err != nil {
|
|
if err != io.EOF {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if fw.FileHeader.IsAlmaCode() {
|
|
for fileN := uint32(0); fileN < fw.FileHeader.FileCount; fileN++ {
|
|
offset := fw.FileHeader.HeaderSize + fw.FileHeader.FirmwareHeaderSize*fileN
|
|
fileBuffer := buffer.Buffer(buf[offset : offset+fw.FileHeader.FirmwareHeaderSize])
|
|
var entry Entry
|
|
_, err = fileBuffer.Read(entry.Header.Description[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dateTime, err = fileBuffer.ReadUint32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entry.Header.DateTime = DateTime(dateTime)
|
|
entry.Header.DataSize, err = fileBuffer.ReadUint32()
|
|
entry.compressed = fw.FileHeader.Compressed > 0
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fw.Entries = append(fw.Entries, entry)
|
|
}
|
|
|
|
offset := fw.FileHeader.HeaderSize + fw.FileHeader.FirmwareHeaderSize*fw.FileHeader.FileCount
|
|
for i, entry := range fw.Entries {
|
|
fw.Entries[i].Blocks = BlocksFromData(fw.Data[offset : offset+entry.Header.DataSize])
|
|
offset += entry.Header.DataSize
|
|
}
|
|
|
|
if offset != uint32(len(fw.Data)) {
|
|
return nil, errors.New("data lingering after read")
|
|
}
|
|
} else {
|
|
fw.Entries = append(fw.Entries, Entry{
|
|
Header: ASFirmwareHeader{
|
|
DateTime: fw.FileHeader.DateTime,
|
|
DataSize: uint32(len(fw.Data[fw.FileHeader.HeaderSize:])),
|
|
},
|
|
Blocks: BlocksFromData(fw.Data[fw.FileHeader.HeaderSize:]),
|
|
compressed: fw.FileHeader.Compressed > 0,
|
|
})
|
|
}
|
|
|
|
fw.FileHeader.UpdateCRC32 = crc.CalculateCRC(fw.Data)
|
|
|
|
return fw, nil
|
|
}
|