2025-06-11 21:52:52 +02:00
|
|
|
package fmp
|
|
|
|
|
2025-06-14 16:28:41 +02:00
|
|
|
type FmpSector struct {
|
|
|
|
ID uint64
|
|
|
|
Level uint8
|
|
|
|
Deleted bool
|
|
|
|
PrevID uint64
|
|
|
|
NextID uint64
|
|
|
|
Prev *FmpSector
|
|
|
|
Next *FmpSector
|
|
|
|
Payload []byte
|
|
|
|
Chunks []*FmpChunk
|
|
|
|
}
|
|
|
|
|
|
|
|
type FmpChunk struct {
|
|
|
|
Type FmpChunkType
|
|
|
|
Length uint64
|
|
|
|
Key uint64 // If Type == FMP_CHUNK_SHORT_KEY_VALUE or FMP_CHUNK_LONG_KEY_VALUE
|
|
|
|
Index uint64 // Segment index, if Type == FMP_CHUNK_SEGMENTED_DATA
|
|
|
|
Value []byte
|
|
|
|
}
|
2025-06-13 19:54:17 +02:00
|
|
|
|
|
|
|
func (sect *FmpSector) readChunks() error {
|
2025-06-13 19:57:43 +02:00
|
|
|
if len(sect.Chunks) > 0 {
|
|
|
|
panic("chunks already read")
|
|
|
|
}
|
2025-06-13 19:54:17 +02:00
|
|
|
for {
|
2025-06-13 22:17:32 +02:00
|
|
|
pos := (sect.ID+1)*sectorSize - uint64(len(sect.Payload))
|
|
|
|
|
|
|
|
if sect.Payload[0] == 0x00 && sect.Payload[1] == 0x00 {
|
|
|
|
break
|
|
|
|
}
|
2025-06-13 19:54:17 +02:00
|
|
|
|
|
|
|
chunk, err := sect.readChunk(sect.Payload)
|
|
|
|
if chunk == nil {
|
2025-06-14 14:24:35 +02:00
|
|
|
debug("0x%02x (pos %v, unknown)\n", sect.Payload[0], pos)
|
2025-06-13 19:54:17 +02:00
|
|
|
} else {
|
2025-06-14 14:24:35 +02:00
|
|
|
debug("0x%02x (pos %v, type %v)\n", sect.Payload[0], pos, int(chunk.Type))
|
2025-06-13 19:54:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
2025-06-14 14:24:35 +02:00
|
|
|
debug("chunk error at sector %d", sect.ID)
|
2025-06-14 16:28:41 +02:00
|
|
|
dump(sect.Payload)
|
2025-06-13 19:54:17 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if chunk == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if chunk.Length == 0 {
|
|
|
|
panic("chunk length not set")
|
|
|
|
}
|
|
|
|
|
|
|
|
sect.Chunks = append(sect.Chunks, chunk)
|
|
|
|
sect.Payload = sect.Payload[min(chunk.Length, uint64(len(sect.Payload))):]
|
|
|
|
|
|
|
|
if len(sect.Payload) == 0 || (len(sect.Payload) == 1 && sect.Payload[0] == 0x00) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2025-06-14 15:19:36 +02:00
|
|
|
func (sect *FmpSector) processChunks(dict *FmpDict) error {
|
2025-06-13 20:40:34 +02:00
|
|
|
err := sect.readChunks()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2025-06-14 15:19:36 +02:00
|
|
|
currentPath := make([]uint64, 0)
|
2025-06-13 19:54:17 +02:00
|
|
|
for _, chunk := range sect.Chunks {
|
|
|
|
switch chunk.Type {
|
2025-06-14 16:28:41 +02:00
|
|
|
case FmpChunkPathPush, FmpChunkPathPushLong:
|
2025-06-22 19:14:22 +02:00
|
|
|
currentPath = append(currentPath, decodeVarUint64(chunk.Value))
|
2025-06-14 16:28:41 +02:00
|
|
|
dumpPath(currentPath)
|
2025-06-14 15:19:36 +02:00
|
|
|
|
2025-06-14 16:28:41 +02:00
|
|
|
case FmpChunkPathPop:
|
2025-06-14 15:19:36 +02:00
|
|
|
if len(currentPath) > 0 {
|
|
|
|
currentPath = (currentPath)[:len(currentPath)-1]
|
2025-06-13 19:54:17 +02:00
|
|
|
}
|
|
|
|
|
2025-06-14 16:28:41 +02:00
|
|
|
case FmpChunkSimpleData:
|
2025-06-14 15:19:36 +02:00
|
|
|
dict.SetValue(currentPath, chunk.Value)
|
2025-06-13 19:54:17 +02:00
|
|
|
|
2025-06-14 16:28:41 +02:00
|
|
|
case FmpChunkSegmentedData:
|
2025-06-13 19:54:17 +02:00
|
|
|
// Todo: take index into account
|
2025-06-14 15:19:36 +02:00
|
|
|
dict.SetValue(
|
|
|
|
currentPath,
|
2025-06-15 21:09:58 +02:00
|
|
|
append(dict.GetValue(currentPath...), chunk.Value...),
|
2025-06-13 19:54:17 +02:00
|
|
|
)
|
|
|
|
|
2025-06-14 16:28:41 +02:00
|
|
|
case FmpChunkSimpleKeyValue:
|
2025-06-14 15:19:36 +02:00
|
|
|
dict.SetValue(
|
|
|
|
append(currentPath, uint64(chunk.Key)),
|
2025-06-13 19:54:17 +02:00
|
|
|
chunk.Value,
|
|
|
|
)
|
|
|
|
|
2025-06-14 16:28:41 +02:00
|
|
|
case FmpChunkLongKeyValue:
|
2025-06-14 15:19:36 +02:00
|
|
|
dict.SetValue(
|
|
|
|
append(currentPath, uint64(chunk.Key)), // todo: ??
|
2025-06-13 19:54:17 +02:00
|
|
|
chunk.Value,
|
|
|
|
)
|
|
|
|
|
2025-06-14 16:28:41 +02:00
|
|
|
case FmpChunkNoop:
|
2025-06-13 19:54:17 +02:00
|
|
|
// noop
|
|
|
|
}
|
|
|
|
}
|
2025-06-13 20:40:34 +02:00
|
|
|
return nil
|
2025-06-13 19:54:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
// https://github.com/evanmiller/fmptools/blob/02eb770e59e0866dab213d80e5f7d88e17648031/HACKING
|
|
|
|
// https://github.com/Rasmus20B/fmplib/blob/66245e5269275724bacfe1437fb1f73bc587a2f3/src/fmp_format/chunk.rs#L57-L60
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
chunk := &FmpChunk{}
|
2025-06-13 17:23:58 +02:00
|
|
|
chunkCode := payload[0]
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 17:23:58 +02:00
|
|
|
switch chunkCode {
|
2025-06-13 14:44:32 +02:00
|
|
|
case 0x00:
|
|
|
|
chunk.Length = 2
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleData
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[1:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
2025-06-14 14:20:38 +02:00
|
|
|
case 0x01, 0x02, 0x03, 0x04, 0x05:
|
|
|
|
chunk.Length = 2 + 2*uint64(chunkCode-0x01) + addIf(chunkCode == 0x01, 1)
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleKeyValue
|
2025-06-13 14:44:32 +02:00
|
|
|
chunk.Key = uint64(payload[1])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[2:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x06:
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Length = 3 + uint64(payload[2])
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleKeyValue
|
2025-06-13 14:44:32 +02:00
|
|
|
chunk.Key = uint64(payload[1])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[3:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x07:
|
2025-06-22 19:14:22 +02:00
|
|
|
valueLength := decodeVarUint64(payload[2 : 2+2])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Length = min(4+valueLength, uint64(len(payload)))
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSegmentedData
|
2025-06-13 14:44:32 +02:00
|
|
|
chunk.Index = uint64(payload[1])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[4:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x08:
|
|
|
|
chunk.Length = 3
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleData
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[1:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x09:
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Length = 4
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleKeyValue
|
2025-06-22 19:14:22 +02:00
|
|
|
chunk.Key = decodeVarUint64(payload[1 : 1+2])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[3:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x0A, 0x0B, 0x0C, 0x0D:
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Length = 3 + 2*uint64(chunkCode-0x09)
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleKeyValue
|
2025-06-22 19:14:22 +02:00
|
|
|
chunk.Key = decodeVarUint64(payload[1 : 1+2])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[3:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x0E:
|
|
|
|
if payload[1] == 0xFF {
|
|
|
|
chunk.Length = 7
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleData
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[2:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Length = 4 + uint64(payload[3])
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleKeyValue
|
2025-06-22 19:14:22 +02:00
|
|
|
chunk.Key = decodeVarUint64(payload[1 : 1+2])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[4:chunk.Length]
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
case 0x0F:
|
2025-06-22 19:14:22 +02:00
|
|
|
valueLength := decodeVarUint64(payload[3 : 3+2])
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Length = uint64(len(payload))
|
|
|
|
if chunk.Length > 5+valueLength {
|
|
|
|
return nil, ErrBadChunk
|
|
|
|
}
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSegmentedData
|
2025-06-22 19:14:22 +02:00
|
|
|
chunk.Index = decodeVarUint64(payload[1 : 1+2])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[5:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x10, 0x11:
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Length = 4 + addIf(chunkCode == 0x11, 1)
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleData
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[1:chunk.Length]
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
case 0x12, 0x13, 0x14, 0x15:
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Length = 4 + 2*(uint64(chunkCode)-0x11)
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleData
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[1:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x16:
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Length = 5 + uint64(payload[4])
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkLongKeyValue
|
2025-06-22 19:14:22 +02:00
|
|
|
chunk.Key = decodeVarUint64(payload[1 : 1+3])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[5:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x17:
|
2025-06-22 19:14:22 +02:00
|
|
|
chunk.Length = 6 + decodeVarUint64(payload[4:4+2])
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkLongKeyValue
|
2025-06-22 19:14:22 +02:00
|
|
|
chunk.Key = decodeVarUint64(payload[1 : 1+3])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[6:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
2025-06-14 14:20:38 +02:00
|
|
|
case 0x19, 0x1A, 0x1B, 0x1C, 0x1D:
|
|
|
|
valueLength := uint64(payload[1])
|
|
|
|
chunk.Length = 2 + valueLength + 2*uint64(chunkCode-0x19) + addIf(chunkCode == 0x19, 1)
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleData
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Value = payload[2 : 2+valueLength]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x1E:
|
2025-06-13 16:28:44 +02:00
|
|
|
keyLength := uint64(payload[1])
|
|
|
|
valueLength := uint64(payload[2+keyLength])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Length = 2 + keyLength + 1 + valueLength
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkLongKeyValue
|
2025-06-22 19:14:22 +02:00
|
|
|
chunk.Key = decodeVarUint64(payload[2 : 2+keyLength])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[2+keyLength+1 : chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x1F:
|
2025-06-14 14:20:38 +02:00
|
|
|
keyLength := uint64(payload[1])
|
2025-06-22 19:14:22 +02:00
|
|
|
valueLength := decodeVarUint64(payload[2+keyLength : 2+keyLength+2+1])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Length = 2 + keyLength + 2 + valueLength
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkLongKeyValue
|
2025-06-22 19:14:22 +02:00
|
|
|
chunk.Key = decodeVarUint64(payload[2 : 2+keyLength])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[2+keyLength+2 : chunk.Length]
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-14 14:20:38 +02:00
|
|
|
case 0x20, 0xE0:
|
2025-06-13 14:44:32 +02:00
|
|
|
if payload[1] == 0xFE {
|
|
|
|
chunk.Length = 10
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkPathPush
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Value = payload[2:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
break
|
|
|
|
}
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
chunk.Length = 2
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkPathPush
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[1:chunk.Length]
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
case 0x23:
|
2025-06-14 14:20:38 +02:00
|
|
|
chunk.Length = 2 + uint64(payload[1])
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkSimpleData
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[1:chunk.Length]
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-14 14:20:38 +02:00
|
|
|
case 0x28, 0x30:
|
|
|
|
chunk.Length = 3 + addIf(chunkCode == 0x30, 1)
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkPathPush
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[1:chunk.Length]
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
case 0x38:
|
2025-06-13 16:28:44 +02:00
|
|
|
valueLength := uint64(payload[1])
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Length = 2 + valueLength
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkPathPushLong
|
2025-06-13 17:23:58 +02:00
|
|
|
chunk.Value = payload[2:chunk.Length]
|
2025-06-13 14:44:32 +02:00
|
|
|
|
|
|
|
case 0x3D, 0x40:
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkPathPop
|
2025-06-13 14:44:32 +02:00
|
|
|
chunk.Length = 1
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
case 0x80:
|
2025-06-14 16:28:41 +02:00
|
|
|
chunk.Type = FmpChunkNoop
|
2025-06-13 14:44:32 +02:00
|
|
|
chunk.Length = 1
|
2025-06-11 21:52:52 +02:00
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
default:
|
|
|
|
return nil, ErrBadChunk
|
2025-06-11 21:52:52 +02:00
|
|
|
}
|
|
|
|
|
2025-06-13 14:44:32 +02:00
|
|
|
return chunk, nil
|
2025-06-11 21:52:52 +02:00
|
|
|
}
|