1
0
mirror of https://github.com/garraflavatra/go-fmp.git synced 2025-06-28 04:25:11 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
bd871b6457 Add CI file 2025-06-14 15:24:34 +02:00
b94fa28ba9 Tables 2025-06-14 15:19:36 +02:00
d9ffc3e573 Debug prints 2025-06-14 14:24:35 +02:00
a7bde87c6f Chunk reading fixes, it works now! 2025-06-14 14:20:38 +02:00
8 changed files with 118 additions and 141 deletions

27
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
- name: Build
run: go build -v ./...
working-directory: fmp
- name: Test
run: go test -v ./...
working-directory: fmp

View File

@ -21,6 +21,7 @@ const (
FMP_CHUNK_SIMPLE_KEY_VALUE FMP_CHUNK_SIMPLE_KEY_VALUE
FMP_CHUNK_LONG_KEY_VALUE FMP_CHUNK_LONG_KEY_VALUE
FMP_CHUNK_PATH_PUSH FMP_CHUNK_PATH_PUSH
FMP_CHUNK_PATH_PUSH_LONG
FMP_CHUNK_PATH_POP FMP_CHUNK_PATH_POP
FMP_CHUNK_NOOP FMP_CHUNK_NOOP
) )

View File

@ -5,6 +5,10 @@ import (
"os" "os"
) )
func debug(str string, args ...interface{}) {
// fmt.Printf(str+"\n", args...)
}
func (f *FmpFile) ToDebugFile(fname string) { func (f *FmpFile) ToDebugFile(fname string) {
f_sectors, err := os.Create(fname + ".sectors") f_sectors, err := os.Create(fname + ".sectors")
if err != nil { if err != nil {

View File

@ -40,7 +40,6 @@ func OpenFile(path string) (*FmpFile, error) {
ctx.FileSize = uint(info.Size()) ctx.FileSize = uint(info.Size())
ctx.numSectors = uint64((ctx.FileSize / sectorSize) - 1) ctx.numSectors = uint64((ctx.FileSize / sectorSize) - 1)
ctx.Sectors = make([]*FmpSector, 0) ctx.Sectors = make([]*FmpSector, 0)
currentPath := make([]uint64, 0)
ctx.stream.Seek(2*sectorSize, io.SeekStart) ctx.stream.Seek(2*sectorSize, io.SeekStart)
for { for {
@ -55,7 +54,7 @@ func OpenFile(path string) (*FmpFile, error) {
ctx.Sectors = append(ctx.Sectors, sector) ctx.Sectors = append(ctx.Sectors, sector)
if sector.ID != 0 { if sector.ID != 0 {
err = sector.processChunks(ctx.Dictionary, &currentPath) err = sector.processChunks(ctx.Dictionary)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,7 +99,7 @@ func (ctx *FmpFile) readHeader() error {
} }
func (ctx *FmpFile) readSector() (*FmpSector, error) { func (ctx *FmpFile) readSector() (*FmpSector, error) {
println("---------- Reading sector", ctx.currentSectorID) debug("---------- Reading sector %d", ctx.currentSectorID)
buf := make([]byte, sectorHeaderSize) buf := make([]byte, sectorHeaderSize)
n, err := ctx.stream.Read(buf) n, err := ctx.stream.Read(buf)

View File

@ -19,22 +19,20 @@ func (sect *FmpSector) readChunks() error {
chunk, err := sect.readChunk(sect.Payload) chunk, err := sect.readChunk(sect.Payload)
if chunk == nil { if chunk == nil {
fmt.Printf("0x%02x (pos %v, unknown)\n", sect.Payload[0], pos) debug("0x%02x (pos %v, unknown)\n", sect.Payload[0], pos)
} else { } else {
fmt.Printf("0x%02x (pos %v, type %v)\n", sect.Payload[0], pos, int(chunk.Type)) debug("0x%02x (pos %v, type %v)\n", sect.Payload[0], pos, int(chunk.Type))
} }
if err == io.EOF { if err == io.EOF {
println("break1")
break break
} }
if err != nil { if err != nil {
println(hex.EncodeToString(sect.Payload)) debug("chunk error at sector %d", sect.ID)
println("break2") debug(hex.EncodeToString(sect.Payload))
return err return err
} }
if chunk == nil { if chunk == nil {
println("break3")
break break
} }
if chunk.Length == 0 { if chunk.Length == 0 {
@ -51,55 +49,54 @@ func (sect *FmpSector) readChunks() error {
return nil return nil
} }
func (sect *FmpSector) processChunks(dict *FmpDict, currentPath *[]uint64) error { func (sect *FmpSector) processChunks(dict *FmpDict) error {
err := sect.readChunks() err := sect.readChunks()
if err != nil { if err != nil {
return err return err
} }
currentPath := make([]uint64, 0)
for _, chunk := range sect.Chunks { for _, chunk := range sect.Chunks {
switch chunk.Type { switch chunk.Type {
case FMP_CHUNK_PATH_PUSH: case FMP_CHUNK_PATH_PUSH, FMP_CHUNK_PATH_PUSH_LONG:
*currentPath = append(*currentPath, uint64(chunk.Value[0])) currentPath = append(currentPath, parseVarUint64(chunk.Value))
s := ""
for _, ent := range currentPath {
s += fmt.Sprintf("%v. ", ent)
}
debug("path: %s", s)
case FMP_CHUNK_PATH_POP: case FMP_CHUNK_PATH_POP:
if len(*currentPath) > 0 { if len(currentPath) > 0 {
*currentPath = (*currentPath)[:len(*currentPath)-1] currentPath = (currentPath)[:len(currentPath)-1]
} }
case FMP_CHUNK_SIMPLE_DATA: case FMP_CHUNK_SIMPLE_DATA:
dict.set(*currentPath, chunk.Value) dict.SetValue(currentPath, chunk.Value)
case FMP_CHUNK_SEGMENTED_DATA: case FMP_CHUNK_SEGMENTED_DATA:
// Todo: take index into account // Todo: take index into account
dict.set( dict.SetValue(
*currentPath, currentPath,
append(dict.getValue(*currentPath), chunk.Value...), append(dict.GetValue(currentPath), chunk.Value...),
) )
case FMP_CHUNK_SIMPLE_KEY_VALUE: case FMP_CHUNK_SIMPLE_KEY_VALUE:
dict.set( dict.SetValue(
append(*currentPath, uint64(chunk.Key)), append(currentPath, uint64(chunk.Key)),
chunk.Value, chunk.Value,
) )
case FMP_CHUNK_LONG_KEY_VALUE: case FMP_CHUNK_LONG_KEY_VALUE:
dict.set( dict.SetValue(
append(*currentPath, uint64(chunk.Key)), // todo: ?? append(currentPath, uint64(chunk.Key)), // todo: ??
chunk.Value, chunk.Value,
) )
case FMP_CHUNK_NOOP: case FMP_CHUNK_NOOP:
// noop // noop
} }
if chunk.Delayed {
if len(*currentPath) == 0 {
println("warning: delayed pop without path")
} else {
*currentPath = (*currentPath)[:len(*currentPath)-1]
}
}
} }
return nil return nil
} }
@ -112,33 +109,20 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
chunk := &FmpChunk{} chunk := &FmpChunk{}
chunkCode := payload[0] chunkCode := payload[0]
if (chunkCode & 0xC0) == 0xC0 {
chunkCode &= 0x3F
chunk.Delayed = true
}
switch chunkCode { switch chunkCode {
case 0x00: case 0x00:
chunk.Length = 2 chunk.Length = 2
chunk.Type = FMP_CHUNK_SIMPLE_DATA chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Value = payload[1:chunk.Length] chunk.Value = payload[1:chunk.Length]
case 0x01: case 0x01, 0x02, 0x03, 0x04, 0x05:
chunk.Length = 3 chunk.Length = 2 + 2*uint64(chunkCode-0x01) + addIf(chunkCode == 0x01, 1)
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Key = uint64(payload[1])
chunk.Value = payload[2:chunk.Length]
case 0x02, 0x03, 0x04, 0x05:
valueLength := uint64(2 * (chunkCode - 1))
chunk.Length = 2 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Key = uint64(payload[1]) chunk.Key = uint64(payload[1])
chunk.Value = payload[2:chunk.Length] chunk.Value = payload[2:chunk.Length]
case 0x06: case 0x06:
valueLength := uint64(payload[2]) chunk.Length = 3 + uint64(payload[2])
chunk.Length = 3 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Key = uint64(payload[1]) chunk.Key = uint64(payload[1])
chunk.Value = payload[3:chunk.Length] chunk.Value = payload[3:chunk.Length]
@ -162,20 +146,12 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
chunk.Value = payload[3:chunk.Length] chunk.Value = payload[3:chunk.Length]
case 0x0A, 0x0B, 0x0C, 0x0D: case 0x0A, 0x0B, 0x0C, 0x0D:
valueLength := uint64(2 * (chunkCode - 0x09)) chunk.Length = 3 + 2*uint64(chunkCode-0x09)
chunk.Length = 3 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Key = parseVarUint64(payload[1 : 1+2]) chunk.Key = parseVarUint64(payload[1 : 1+2])
chunk.Value = payload[3:chunk.Length] chunk.Value = payload[3:chunk.Length]
case 0x0E: case 0x0E:
if payload[1] == 0xFE {
chunk.Length = 10
chunk.Type = FMP_CHUNK_PATH_PUSH
chunk.Value = payload[2:chunk.Length]
break
}
if payload[1] == 0xFF { if payload[1] == 0xFF {
chunk.Length = 7 chunk.Length = 7
chunk.Type = FMP_CHUNK_SIMPLE_DATA chunk.Type = FMP_CHUNK_SIMPLE_DATA
@ -183,55 +159,48 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
break break
} }
valueLength := uint64(payload[2]) chunk.Length = 4 + uint64(payload[3])
chunk.Length = 4 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Key = parseVarUint64(payload[1 : 1+2]) chunk.Key = parseVarUint64(payload[1 : 1+2])
chunk.Value = payload[4:chunk.Length] chunk.Value = payload[4:chunk.Length]
case 0x0F: case 0x0F:
valueLength := parseVarUint64(payload[3 : 3+2]) valueLength := parseVarUint64(payload[3 : 3+2])
chunk.Length = min(5+valueLength, uint64(len(payload))) chunk.Length = uint64(len(payload))
if chunk.Length > 5+valueLength {
return nil, ErrBadChunk
}
chunk.Type = FMP_CHUNK_SEGMENTED_DATA chunk.Type = FMP_CHUNK_SEGMENTED_DATA
chunk.Index = parseVarUint64(payload[1 : 1+2]) chunk.Index = parseVarUint64(payload[1 : 1+2])
chunk.Value = payload[5:chunk.Length] chunk.Value = payload[5:chunk.Length]
case 0x10, 0x11: case 0x10, 0x11:
valueLength := 3 + (uint64(chunkCode) - 0x10) chunk.Length = 4 + addIf(chunkCode == 0x11, 1)
chunk.Length = 1 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_DATA chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Value = payload[1:chunk.Length] chunk.Value = payload[1:chunk.Length]
case 0x12, 0x13, 0x14, 0x15: case 0x12, 0x13, 0x14, 0x15:
valueLength := 1 + 2*(uint64(chunkCode)-0x10) chunk.Length = 4 + 2*(uint64(chunkCode)-0x11)
chunk.Length = 1 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_DATA chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Value = payload[1:chunk.Length] chunk.Value = payload[1:chunk.Length]
case 0x16: case 0x16:
valueLength := uint64(payload[4]) chunk.Length = 5 + uint64(payload[4])
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Length = 5 + valueLength
chunk.Key = parseVarUint64(payload[1 : 1+3]) chunk.Key = parseVarUint64(payload[1 : 1+3])
chunk.Value = payload[5:chunk.Length] chunk.Value = payload[5:chunk.Length]
case 0x17: case 0x17:
valueLength := parseVarUint64(payload[4 : 4+2]) chunk.Length = 6 + parseVarUint64(payload[4:4+2])
chunk.Length = 6 + valueLength
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Key = parseVarUint64(payload[1 : 1+3]) chunk.Key = parseVarUint64(payload[1 : 1+3])
chunk.Value = payload[6:chunk.Length] chunk.Value = payload[6:chunk.Length]
case 0x19: case 0x19, 0x1A, 0x1B, 0x1C, 0x1D:
valueLength := uint64(payload[1])
chunk.Length = 2 + valueLength + 2*uint64(chunkCode-0x19) + addIf(chunkCode == 0x19, 1)
chunk.Type = FMP_CHUNK_SIMPLE_DATA chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Length = 2 chunk.Value = payload[2 : 2+valueLength]
chunk.Value = payload[1:chunk.Length]
case 0x1A, 0x1B, 0x1C, 0x1D:
valueLength := 2 * uint64(chunkCode-0x19)
chunk.Length = 1 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Value = payload[1:chunk.Length]
case 0x1E: case 0x1E:
keyLength := uint64(payload[1]) keyLength := uint64(payload[1])
@ -242,18 +211,18 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
chunk.Value = payload[2+keyLength+1 : chunk.Length] chunk.Value = payload[2+keyLength+1 : chunk.Length]
case 0x1F: case 0x1F:
keyLength := uint64(uint64(payload[1])) keyLength := uint64(payload[1])
valueLength := parseVarUint64(payload[2+keyLength : 2+keyLength+2+1]) valueLength := parseVarUint64(payload[2+keyLength : 2+keyLength+2+1])
chunk.Length = 2 + keyLength + 2 + valueLength chunk.Length = 2 + keyLength + 2 + valueLength
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Key = parseVarUint64(payload[2 : 2+keyLength]) chunk.Key = parseVarUint64(payload[2 : 2+keyLength])
chunk.Value = payload[2+keyLength+2 : chunk.Length] chunk.Value = payload[2+keyLength+2 : chunk.Length]
case 0x20: case 0x20, 0xE0:
if payload[1] == 0xFE { if payload[1] == 0xFE {
chunk.Length = 10 chunk.Length = 10
chunk.Type = FMP_CHUNK_PATH_PUSH chunk.Type = FMP_CHUNK_PATH_PUSH
chunk.Value = payload[1:chunk.Length] chunk.Value = payload[2:chunk.Length]
break break
} }
@ -262,24 +231,19 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
chunk.Value = payload[1:chunk.Length] chunk.Value = payload[1:chunk.Length]
case 0x23: case 0x23:
chunk.Length = 2 chunk.Length = 2 + uint64(payload[1])
chunk.Type = FMP_CHUNK_SIMPLE_DATA chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Value = payload[1:chunk.Length] chunk.Value = payload[1:chunk.Length]
case 0x28: case 0x28, 0x30:
chunk.Length = 3 chunk.Length = 3 + addIf(chunkCode == 0x30, 1)
chunk.Type = FMP_CHUNK_PATH_PUSH
chunk.Value = payload[1:chunk.Length]
case 0x30:
chunk.Length = 4
chunk.Type = FMP_CHUNK_PATH_PUSH chunk.Type = FMP_CHUNK_PATH_PUSH
chunk.Value = payload[1:chunk.Length] chunk.Value = payload[1:chunk.Length]
case 0x38: case 0x38:
valueLength := uint64(payload[1]) valueLength := uint64(payload[1])
chunk.Length = 2 + valueLength chunk.Length = 2 + valueLength
chunk.Type = FMP_CHUNK_PATH_PUSH chunk.Type = FMP_CHUNK_PATH_PUSH_LONG
chunk.Value = payload[2:chunk.Length] chunk.Value = payload[2:chunk.Length]
case 0x3D, 0x40: case 0x3D, 0x40:
@ -296,3 +260,10 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
return chunk, nil return chunk, nil
} }
func addIf(cond bool, val uint64) uint64 {
if cond {
return val
}
return 0
}

View File

@ -2,46 +2,16 @@ package fmp
func (ctx *FmpFile) Tables() []*FmpTable { func (ctx *FmpFile) Tables() []*FmpTable {
tables := make([]*FmpTable, 0) tables := make([]*FmpTable, 0)
ent := ctx.Dictionary.GetEntry([]uint64{3, 16, 5})
for key, ent := range *ctx.Dictionary { for path, tableEnt := range *ent.Children {
if key != 3 { if path < 128 {
continue continue
} }
println("Found a 3") name := decodeByteSeq(tableEnt.Children.GetValue([]uint64{16}))
table := &FmpTable{ID: path, Name: name}
for key, ent = range *ent.Children { tables = append(tables, table)
if key != 16 {
continue
}
println("Found a 3.16")
for key, ent = range *ent.Children {
if key != 5 {
continue
}
println("Found a 3.16.5")
for tablePath := range *ent.Children {
if key >= 128 {
continue
}
// Found a table!
println("Found a table at 3.16.5.", tablePath)
}
}
}
} }
// metaDict := ctx.Dictionary.get([]uint64{4, 1, 7})
// if metaDict == nil {
// return tables
// }
// for _, meta := range *metaDict.Children {
// name := decodeByteSeq(meta.Children.get([]uint64{16}).Value)
// table := &FmpTable{Name: name}
// tables = append(tables, table)
// }
return tables return tables
} }

View File

@ -29,14 +29,16 @@ func TestTables(t *testing.T) {
} }
tables := f.Tables() tables := f.Tables()
if len(tables) != 1 || tables[0].Name != "Untitled" { expected := "Untitled, WayDomains, WayProcesses"
tablesString := "" tablesString := ""
for i, table := range tables { for i, table := range tables {
tablesString += table.Name tablesString += table.Name
if i < len(tables)-1 { if i < len(tables)-1 {
tablesString += ", " tablesString += ", "
}
} }
t.Errorf("expected tables to be 'Untitled', got '%s'", tablesString) }
if tablesString != expected {
t.Errorf("expected tables to be '%s', got '%s'", expected, tablesString)
} }
} }

View File

@ -32,12 +32,11 @@ type FmpSector struct {
} }
type FmpChunk struct { type FmpChunk struct {
Type FmpChunkType Type FmpChunkType
Length uint64 Length uint64
Key uint64 // If Type == FMP_CHUNK_SHORT_KEY_VALUE or FMP_CHUNK_LONG_KEY_VALUE 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 Index uint64 // Segment index, if Type == FMP_CHUNK_SEGMENTED_DATA
Value []byte Value []byte
Delayed bool
} }
type FmpDict map[uint64]*FmpDictEntry type FmpDict map[uint64]*FmpDictEntry
@ -48,10 +47,11 @@ type FmpDictEntry struct {
} }
type FmpTable struct { type FmpTable struct {
ID uint64
Name string Name string
} }
func (dict *FmpDict) get(path []uint64) *FmpDictEntry { func (dict *FmpDict) GetEntry(path []uint64) *FmpDictEntry {
for i, key := range path { for i, key := range path {
_, ok := (*dict)[key] _, ok := (*dict)[key]
if !ok { if !ok {
@ -62,20 +62,23 @@ func (dict *FmpDict) get(path []uint64) *FmpDictEntry {
return (*dict)[key] return (*dict)[key]
} else { } else {
dict = (*dict)[key].Children dict = (*dict)[key].Children
if dict == nil {
return nil
}
} }
} }
return nil return nil
} }
func (dict *FmpDict) getValue(path []uint64) []byte { func (dict *FmpDict) GetValue(path []uint64) []byte {
ent := dict.get(path) ent := dict.GetEntry(path)
if ent != nil { if ent != nil {
return ent.Value return ent.Value
} }
return nil return nil
} }
func (dict *FmpDict) set(path []uint64, value []byte) { func (dict *FmpDict) SetValue(path []uint64, value []byte) {
for i, key := range path { for i, key := range path {
_, ok := (*dict)[key] _, ok := (*dict)[key]
if !ok { if !ok {