1
0
mirror of https://github.com/garraflavatra/go-fmp.git synced 2025-06-27 20:15:11 +00:00

Compare commits

...

3 Commits

Author SHA1 Message Date
10285084eb Restructure, add script step constants 2025-06-14 16:28:41 +02:00
53134a0d09 Fix table test 2025-06-14 15:30:42 +02:00
cc8a602b51 Fix Go version in CI file 2025-06-14 15:25:23 +02:00
9 changed files with 396 additions and 193 deletions

View File

@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
go-version: '1.23.0'
- name: Build
run: go build -v ./...

View File

@ -1,7 +1,9 @@
package fmp
type FmpError string
type FmpChunkType uint8
type FmpCollation uint8
type FmpScriptStepType uint8
type FmpError string
func (e FmpError) Error() string { return string(e) }
@ -16,42 +18,237 @@ var (
)
const (
FMP_CHUNK_SIMPLE_DATA FmpChunkType = iota
FMP_CHUNK_SEGMENTED_DATA
FMP_CHUNK_SIMPLE_KEY_VALUE
FMP_CHUNK_LONG_KEY_VALUE
FMP_CHUNK_PATH_PUSH
FMP_CHUNK_PATH_PUSH_LONG
FMP_CHUNK_PATH_POP
FMP_CHUNK_NOOP
FmpChunkSimpleData FmpChunkType = iota
FmpChunkSegmentedData FmpChunkType = iota
FmpChunkSimpleKeyValue FmpChunkType = iota
FmpChunkLongKeyValue FmpChunkType = iota
FmpChunkPathPush FmpChunkType = iota
FmpChunkPathPushLong FmpChunkType = iota
FmpChunkPathPop FmpChunkType = iota
FmpChunkNoop FmpChunkType = iota
)
const (
FMP_COLLATION_ENGLISH = 0x00
FMP_COLLATION_FRENCH = 0x01
FMP_COLLATION_GERMAN = 0x03
FMP_COLLATION_ITALIAN = 0x04
FMP_COLLATION_DUTCH = 0x05
FMP_COLLATION_SWEDISH = 0x07
FMP_COLLATION_SPANISH = 0x08
FMP_COLLATION_DANISH = 0x09
FMP_COLLATION_PORTUGUESE = 0x0A
FMP_COLLATION_NORWEGIAN = 0x0C
FMP_COLLATION_FINNISH = 0x11
FMP_COLLATION_GREEK = 0x14
FMP_COLLATION_ICELANDIC = 0x15
FMP_COLLATION_TURKISH = 0x18
FMP_COLLATION_ROMANIAN = 0x27
FMP_COLLATION_POLISH = 0x2a
FMP_COLLATION_HUNGARIAN = 0x2b
FMP_COLLATION_RUSSIAN = 0x31
FMP_COLLATION_CZECH = 0x38
FMP_COLLATION_UKRAINIAN = 0x3e
FMP_COLLATION_CROATIAN = 0x42
FMP_COLLATION_CATALAN = 0x49
FMP_COLLATION_FINNISH_ALT = 0x62
FMP_COLLATION_SWEDISH_ALT = 0x63
FMP_COLLATION_GERMAN_ALT = 0x64
FMP_COLLATION_SPANISH_ALT = 0x65
FMP_COLLATION_ASCII = 0x66
FmpCollationEnglish FmpCollation = 0x00
FmpCollationFrench FmpCollation = 0x01
FmpCollationGerman FmpCollation = 0x03
FmpCollationItalian FmpCollation = 0x04
FmpCollationDutch FmpCollation = 0x05
FmpCollationSwedish FmpCollation = 0x07
FmpCollationSpanish FmpCollation = 0x08
FmpCollationDanish FmpCollation = 0x09
FmpCollationPortuguese FmpCollation = 0x0A
FmpCollationNorwegian FmpCollation = 0x0C
FmpCollationFinnish FmpCollation = 0x11
FmpCollationGreek FmpCollation = 0x14
FmpCollationIcelandic FmpCollation = 0x15
FmpCollationTurkish FmpCollation = 0x18
FmpCollationRomanian FmpCollation = 0x27
FmpCollationPolish FmpCollation = 0x2a
FmpCollationHungarian FmpCollation = 0x2b
FmpCollationRussian FmpCollation = 0x31
FmpCollationCzech FmpCollation = 0x38
FmpCollationUkrainian FmpCollation = 0x3e
FmpCollationCroatian FmpCollation = 0x42
FmpCollationCatalan FmpCollation = 0x49
FmpCollationFinnishAlt FmpCollation = 0x62
FmpCollationSwedishAlt FmpCollation = 0x63
FmpCollationGermanAlt FmpCollation = 0x64
FmpCollationSpanishAlt FmpCollation = 0x65
FmpCollationAscii FmpCollation = 0x66
)
const (
FmpScriptPerformScript FmpScriptStepType = 1
FmpScriptSaveACopyAsXml FmpScriptStepType = 3
FmpScriptGoToNextField FmpScriptStepType = 4
FmpScriptGoToPreviousField FmpScriptStepType = 5
FmpScriptGoToLayout FmpScriptStepType = 6
FmpScriptNewRecordRequest FmpScriptStepType = 7
FmpScriptDuplicateRecordRequest FmpScriptStepType = 8
FmpScriptDeleteRecordRequest FmpScriptStepType = 9
FmpScriptDeleteAllRecords FmpScriptStepType = 10
FmpScriptInsertFromIndex FmpScriptStepType = 11
FmpScriptInsertFromLastVisited FmpScriptStepType = 12
FmpScriptInsertCurrentDate FmpScriptStepType = 13
FmpScriptInsertCurrentTime FmpScriptStepType = 14
FmpScriptGoToRecordRequestPage FmpScriptStepType = 16
FmpScriptGoToField FmpScriptStepType = 17
FmpScriptCheckSelection FmpScriptStepType = 18
FmpScriptCheckRecord FmpScriptStepType = 19
FmpScriptCheckFoundSet FmpScriptStepType = 20
FmpScriptUnsortRecords FmpScriptStepType = 21
FmpScriptEnterFindMode FmpScriptStepType = 22
FmpScriptShowAllRecords FmpScriptStepType = 23
FmpScriptModifyLastFind FmpScriptStepType = 24
FmpScriptOmitRecord FmpScriptStepType = 25
FmpScriptOmitMultipleRecords FmpScriptStepType = 26
FmpScriptShowOmmitedOnly FmpScriptStepType = 27
FmpScriptPerformFind FmpScriptStepType = 28
FmpScriptShowHideToolbars FmpScriptStepType = 29
FmpScriptViewAs FmpScriptStepType = 30
FmpScriptAdjustWindow FmpScriptStepType = 31
FmpScriptOpenHelp FmpScriptStepType = 32
FmpScriptOpenFile FmpScriptStepType = 33
FmpScriptCloseFile FmpScriptStepType = 34
FmpScriptImportRecords FmpScriptStepType = 35
FmpScriptExportRecords FmpScriptStepType = 36
FmpScriptSaveACopyAs FmpScriptStepType = 37
FmpScriptOpenManageDatabase FmpScriptStepType = 38
FmpScriptSortRecords FmpScriptStepType = 39
FmpScriptRelookupFieldContents FmpScriptStepType = 40
FmpScriptEnterPreviewMode FmpScriptStepType = 41
FmpScriptPrintSetup FmpScriptStepType = 42
FmpScriptPrint FmpScriptStepType = 43
FmpScriptExitApplication FmpScriptStepType = 44
FmpScriptUndoRedo FmpScriptStepType = 45
FmpScriptCut FmpScriptStepType = 46
FmpScriptCopy FmpScriptStepType = 47
FmpScriptPaste FmpScriptStepType = 48
FmpScriptClear FmpScriptStepType = 49
FmpScriptSelectAll FmpScriptStepType = 50
FmpScriptRevertRecordRequest FmpScriptStepType = 51
FmpScriptEnterBrowserMode FmpScriptStepType = 55
FmpScriptInsertPicture FmpScriptStepType = 56
FmpScriptSendEvent FmpScriptStepType = 57
FmpScriptInsertCurrentUserName FmpScriptStepType = 60
FmpScriptInsertText FmpScriptStepType = 61
FmpScriptPauseResumeScript FmpScriptStepType = 62
FmpScriptSendMail FmpScriptStepType = 63
FmpScriptSendDdeExecute FmpScriptStepType = 64
FmpScriptDialPhone FmpScriptStepType = 65
FmpScriptSpeak FmpScriptStepType = 66
FmpScriptPerformApplescript FmpScriptStepType = 67
FmpScriptIf FmpScriptStepType = 68
FmpScriptElse FmpScriptStepType = 69
FmpScriptEndIf FmpScriptStepType = 70
FmpScriptLoop FmpScriptStepType = 71
FmpScriptExitLoopIf FmpScriptStepType = 72
FmpScriptEndLoop FmpScriptStepType = 73
FmpScriptGoToRelatedRecord FmpScriptStepType = 74
FmpScriptCommitRecordsRequests FmpScriptStepType = 75
FmpScriptSetField FmpScriptStepType = 76
FmpScriptInsertCalculatedResult FmpScriptStepType = 77
FmpScriptFreezeWindow FmpScriptStepType = 79
FmpScriptRefreshWindow FmpScriptStepType = 80
FmpScriptScrollWindow FmpScriptStepType = 81
FmpScriptNewFile FmpScriptStepType = 82
FmpScriptChangePassword FmpScriptStepType = 83
FmpScriptSetMultiUser FmpScriptStepType = 84
FmpScriptAllowUserAbort FmpScriptStepType = 85
FmpScriptSetErrorCapture FmpScriptStepType = 86
FmpScriptShowCustomDialog FmpScriptStepType = 87
FmpScriptOpenScriptWorkspace FmpScriptStepType = 88
FmpScriptBlankLineComment FmpScriptStepType = 89
FmpScriptHaltScript FmpScriptStepType = 90
FmpScriptReplaceFieldContents FmpScriptStepType = 91
FmpScriptShowHideTextRuler FmpScriptStepType = 92
FmpScriptBeep FmpScriptStepType = 93
FmpScriptSetUseSystemFormats FmpScriptStepType = 94
FmpScriptRecoverFile FmpScriptStepType = 95
FmpScriptSaveACopyAsAddOnPackage FmpScriptStepType = 96
FmpScriptSetZoomLevel FmpScriptStepType = 97
FmpScriptCopyAllRecordsRequests FmpScriptStepType = 98
FmpScriptGoToPortalRow FmpScriptStepType = 99
FmpScriptCopyRecordRequest FmpScriptStepType = 101
FmpScriptFluchCacheToDisk FmpScriptStepType = 102
FmpScriptExitScript FmpScriptStepType = 103
FmpScriptDeletePortalRow FmpScriptStepType = 104
FmpScriptOpenPreferences FmpScriptStepType = 105
FmpScriptCorrectWord FmpScriptStepType = 106
FmpScriptSpellingOptions FmpScriptStepType = 107
FmpScriptSelectDictionaries FmpScriptStepType = 108
FmpScriptEditUserDictionary FmpScriptStepType = 109
FmpScriptOpenUrl FmpScriptStepType = 111
FmpScriptOpenManageValueLists FmpScriptStepType = 112
FmpScriptOpenSharing FmpScriptStepType = 113
FmpScriptOpenFileOptions FmpScriptStepType = 114
FmpScriptAllowFormattingBar FmpScriptStepType = 115
FmpScriptSetNextSerialValue FmpScriptStepType = 116
FmpScriptExecuteSql FmpScriptStepType = 117
FmpScriptOpenHosts FmpScriptStepType = 118
FmpScriptMoveResizeWindow FmpScriptStepType = 119
FmpScriptArrangeAllWindows FmpScriptStepType = 120
FmpScriptCloseWindow FmpScriptStepType = 121
FmpScriptNewWindow FmpScriptStepType = 122
FmpScriptSelectWindow FmpScriptStepType = 123
FmpScriptSetWindowTitle FmpScriptStepType = 124
FmpScriptElseIf FmpScriptStepType = 125
FmpScriptConstrainFoundSet FmpScriptStepType = 126
FmpScriptExtendFoundSet FmpScriptStepType = 127
FmpScriptPerformFindReplace FmpScriptStepType = 128
FmpScriptOpenFindReplace FmpScriptStepType = 129
FmpScriptSetSelection FmpScriptStepType = 130
FmpScriptInsertFile FmpScriptStepType = 131
FmpScriptExportFieldContents FmpScriptStepType = 132
FmpScriptOpenRecordRequest FmpScriptStepType = 133
FmpScriptAddAccount FmpScriptStepType = 134
FmpScriptDeleteAccount FmpScriptStepType = 135
FmpScriptResetAccountPassword FmpScriptStepType = 136
FmpScriptEnableAccount FmpScriptStepType = 137
FmpScriptRelogin FmpScriptStepType = 138
FmpScriptConvertFile FmpScriptStepType = 139
FmpScriptOpenManageDataSources FmpScriptStepType = 140
FmpScriptSetVariable FmpScriptStepType = 141
FmpScriptInstallMenuSet FmpScriptStepType = 142
FmpScriptSaveRecordsAsExcel FmpScriptStepType = 143
FmpScriptSaveRecordsAsPdf FmpScriptStepType = 144
FmpScriptGoToObject FmpScriptStepType = 145
FmpScriptSetWebViewer FmpScriptStepType = 146
FmpScriptSetFieldByName FmpScriptStepType = 147
FmpScriptInstallOntimerScript FmpScriptStepType = 148
FmpScriptOpenEditSavedFinds FmpScriptStepType = 149
FmpScriptPerformQuickFind FmpScriptStepType = 150
FmpScriptOpenManageLayouts FmpScriptStepType = 151
FmpScriptSaveRecordsAsSnapshotLink FmpScriptStepType = 152
FmpScriptSortRecordsByField FmpScriptStepType = 154
FmpScriptFindMatchingRecords FmpScriptStepType = 155
FmpScriptManageContainers FmpScriptStepType = 156
FmpScriptInstallPluginFile FmpScriptStepType = 157
FmpScriptInsertPdf FmpScriptStepType = 158
FmpScriptInsertAudioVideo FmpScriptStepType = 159
FmpScriptInsertFromUrl FmpScriptStepType = 160
FmpScriptInsertFromDevice FmpScriptStepType = 161
FmpScriptPerformScriptOnServer FmpScriptStepType = 164
FmpScriptOpenManageThemes FmpScriptStepType = 165
FmpScriptShowHideMenubar FmpScriptStepType = 166
FmpScriptRefreshObject FmpScriptStepType = 167
FmpScriptSetLayoutObjectAnimation FmpScriptStepType = 168
FmpScriptClosePopover FmpScriptStepType = 169
FmpScriptOpenUploadToHost FmpScriptStepType = 172
FmpScriptEnableTouchKeyboard FmpScriptStepType = 174
FmpScriptPerformJavascriptInWebViewer FmpScriptStepType = 175
FmpScriptCommentedOut FmpScriptStepType = 176
FmpScriptAvplayerPlay FmpScriptStepType = 177
FmpScriptAvplayerSetPlaybackState FmpScriptStepType = 178
FmpScriptAvplayerSetOptions FmpScriptStepType = 179
FmpScriptRefreshPortal FmpScriptStepType = 180
FmpScriptGetFolderPath FmpScriptStepType = 181
FmpScriptTruncateTable FmpScriptStepType = 182
FmpScriptOpenFavorites FmpScriptStepType = 183
FmpScriptConfigureRegionMonitorScript FmpScriptStepType = 185
FmpScriptConfigureLocalNotification FmpScriptStepType = 187
FmpScriptGetFileExists FmpScriptStepType = 188
FmpScriptGetFileSize FmpScriptStepType = 189
FmpScriptCreateDataFile FmpScriptStepType = 190
FmpScriptOpenDataFile FmpScriptStepType = 191
FmpScriptWriteToDataFile FmpScriptStepType = 192
FmpScriptReadFromDataFile FmpScriptStepType = 193
FmpScriptGetDataFilePosition FmpScriptStepType = 194
FmpScriptSetDataFilePosition FmpScriptStepType = 195
FmpScriptCloseDataFile FmpScriptStepType = 196
FmpScriptDeleteFile FmpScriptStepType = 197
FmpScriptRenameFile FmpScriptStepType = 199
FmpScriptSetErrorLogging FmpScriptStepType = 200
FmpScriptConfigureNfcReading FmpScriptStepType = 201
FmpScriptConfigureMachineLearningModel FmpScriptStepType = 202
FmpScriptExecuteFilemakerDataApi FmpScriptStepType = 203
FmpScriptOpenTransaction FmpScriptStepType = 205
FmpScriptCommitTransaction FmpScriptStepType = 206
FmpScriptRevertTransaction FmpScriptStepType = 207
FmpScriptSetSessionIdentifier FmpScriptStepType = 208
FmpScriptSetDictionary FmpScriptStepType = 209
FmpScriptPerformScriptOnServerWithCallback FmpScriptStepType = 210
FmpScriptTriggerClarisConnectFlow FmpScriptStepType = 211
FmpScriptAssert FmpScriptStepType = 255
)

View File

@ -9,6 +9,20 @@ func debug(str string, args ...interface{}) {
// fmt.Printf(str+"\n", args...)
}
func dump(data []byte) {
for _, b := range data {
fmt.Printf("%02x ", b)
}
fmt.Println()
}
func dumpPath(path []uint64) {
for _, p := range path {
fmt.Printf("%v. ", p)
}
fmt.Println()
}
func (f *FmpFile) ToDebugFile(fname string) {
f_sectors, err := os.Create(fname + ".sectors")
if err != nil {

View File

@ -20,6 +20,20 @@ const (
hbamSize = len(hbamSequence)
)
type FmpFile struct {
VersionDate time.Time
CreatorName string
FileSize uint
Sectors []*FmpSector
Chunks []*FmpChunk
Dictionary *FmpDict
numSectors uint64 // Excludes the header sector
currentSectorID uint64
stream io.ReadSeeker
}
func OpenFile(path string) (*FmpFile, error) {
info, err := os.Stat(path)
if err != nil {

View File

@ -1,10 +1,24 @@
package fmp
import (
"encoding/hex"
"fmt"
"io"
)
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
}
func (sect *FmpSector) readChunks() error {
if len(sect.Chunks) > 0 {
@ -24,12 +38,9 @@ func (sect *FmpSector) readChunks() error {
debug("0x%02x (pos %v, type %v)\n", sect.Payload[0], pos, int(chunk.Type))
}
if err == io.EOF {
break
}
if err != nil {
debug("chunk error at sector %d", sect.ID)
debug(hex.EncodeToString(sect.Payload))
dump(sect.Payload)
return err
}
if chunk == nil {
@ -58,43 +69,38 @@ func (sect *FmpSector) processChunks(dict *FmpDict) error {
currentPath := make([]uint64, 0)
for _, chunk := range sect.Chunks {
switch chunk.Type {
case FMP_CHUNK_PATH_PUSH, FMP_CHUNK_PATH_PUSH_LONG:
case FmpChunkPathPush, FmpChunkPathPushLong:
currentPath = append(currentPath, parseVarUint64(chunk.Value))
dumpPath(currentPath)
s := ""
for _, ent := range currentPath {
s += fmt.Sprintf("%v. ", ent)
}
debug("path: %s", s)
case FMP_CHUNK_PATH_POP:
case FmpChunkPathPop:
if len(currentPath) > 0 {
currentPath = (currentPath)[:len(currentPath)-1]
}
case FMP_CHUNK_SIMPLE_DATA:
case FmpChunkSimpleData:
dict.SetValue(currentPath, chunk.Value)
case FMP_CHUNK_SEGMENTED_DATA:
case FmpChunkSegmentedData:
// Todo: take index into account
dict.SetValue(
currentPath,
append(dict.GetValue(currentPath), chunk.Value...),
)
case FMP_CHUNK_SIMPLE_KEY_VALUE:
case FmpChunkSimpleKeyValue:
dict.SetValue(
append(currentPath, uint64(chunk.Key)),
chunk.Value,
)
case FMP_CHUNK_LONG_KEY_VALUE:
case FmpChunkLongKeyValue:
dict.SetValue(
append(currentPath, uint64(chunk.Key)), // todo: ??
chunk.Value,
)
case FMP_CHUNK_NOOP:
case FmpChunkNoop:
// noop
}
}
@ -112,55 +118,55 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
switch chunkCode {
case 0x00:
chunk.Length = 2
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[1:chunk.Length]
case 0x01, 0x02, 0x03, 0x04, 0x05:
chunk.Length = 2 + 2*uint64(chunkCode-0x01) + addIf(chunkCode == 0x01, 1)
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = uint64(payload[1])
chunk.Value = payload[2:chunk.Length]
case 0x06:
chunk.Length = 3 + uint64(payload[2])
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = uint64(payload[1])
chunk.Value = payload[3:chunk.Length]
case 0x07:
valueLength := parseVarUint64(payload[2 : 2+2])
chunk.Length = min(4+valueLength, uint64(len(payload)))
chunk.Type = FMP_CHUNK_SEGMENTED_DATA
chunk.Type = FmpChunkSegmentedData
chunk.Index = uint64(payload[1])
chunk.Value = payload[4:chunk.Length]
case 0x08:
chunk.Length = 3
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[1:chunk.Length]
case 0x09:
chunk.Length = 4
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+2])
chunk.Value = payload[3:chunk.Length]
case 0x0A, 0x0B, 0x0C, 0x0D:
chunk.Length = 3 + 2*uint64(chunkCode-0x09)
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+2])
chunk.Value = payload[3:chunk.Length]
case 0x0E:
if payload[1] == 0xFF {
chunk.Length = 7
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[2:chunk.Length]
break
}
chunk.Length = 4 + uint64(payload[3])
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+2])
chunk.Value = payload[4:chunk.Length]
@ -170,43 +176,43 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
if chunk.Length > 5+valueLength {
return nil, ErrBadChunk
}
chunk.Type = FMP_CHUNK_SEGMENTED_DATA
chunk.Type = FmpChunkSegmentedData
chunk.Index = parseVarUint64(payload[1 : 1+2])
chunk.Value = payload[5:chunk.Length]
case 0x10, 0x11:
chunk.Length = 4 + addIf(chunkCode == 0x11, 1)
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[1:chunk.Length]
case 0x12, 0x13, 0x14, 0x15:
chunk.Length = 4 + 2*(uint64(chunkCode)-0x11)
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[1:chunk.Length]
case 0x16:
chunk.Length = 5 + uint64(payload[4])
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Type = FmpChunkLongKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+3])
chunk.Value = payload[5:chunk.Length]
case 0x17:
chunk.Length = 6 + parseVarUint64(payload[4:4+2])
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Type = FmpChunkLongKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+3])
chunk.Value = payload[6:chunk.Length]
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 = FmpChunkSimpleData
chunk.Value = payload[2 : 2+valueLength]
case 0x1E:
keyLength := uint64(payload[1])
valueLength := uint64(payload[2+keyLength])
chunk.Length = 2 + keyLength + 1 + valueLength
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Type = FmpChunkLongKeyValue
chunk.Key = parseVarUint64(payload[2 : 2+keyLength])
chunk.Value = payload[2+keyLength+1 : chunk.Length]
@ -214,44 +220,44 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
keyLength := uint64(payload[1])
valueLength := parseVarUint64(payload[2+keyLength : 2+keyLength+2+1])
chunk.Length = 2 + keyLength + 2 + valueLength
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Type = FmpChunkLongKeyValue
chunk.Key = parseVarUint64(payload[2 : 2+keyLength])
chunk.Value = payload[2+keyLength+2 : chunk.Length]
case 0x20, 0xE0:
if payload[1] == 0xFE {
chunk.Length = 10
chunk.Type = FMP_CHUNK_PATH_PUSH
chunk.Type = FmpChunkPathPush
chunk.Value = payload[2:chunk.Length]
break
}
chunk.Length = 2
chunk.Type = FMP_CHUNK_PATH_PUSH
chunk.Type = FmpChunkPathPush
chunk.Value = payload[1:chunk.Length]
case 0x23:
chunk.Length = 2 + uint64(payload[1])
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[1:chunk.Length]
case 0x28, 0x30:
chunk.Length = 3 + addIf(chunkCode == 0x30, 1)
chunk.Type = FMP_CHUNK_PATH_PUSH
chunk.Type = FmpChunkPathPush
chunk.Value = payload[1:chunk.Length]
case 0x38:
valueLength := uint64(payload[1])
chunk.Length = 2 + valueLength
chunk.Type = FMP_CHUNK_PATH_PUSH_LONG
chunk.Type = FmpChunkPathPushLong
chunk.Value = payload[2:chunk.Length]
case 0x3D, 0x40:
chunk.Type = FMP_CHUNK_PATH_POP
chunk.Type = FmpChunkPathPop
chunk.Length = 1
case 0x80:
chunk.Type = FMP_CHUNK_NOOP
chunk.Type = FmpChunkNoop
chunk.Length = 1
default:
@ -260,10 +266,3 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
return chunk, nil
}
func addIf(cond bool, val uint64) uint64 {
if cond {
return val
}
return 0
}

View File

@ -1,5 +1,10 @@
package fmp
type FmpTable struct {
ID uint64
Name string
}
func (ctx *FmpFile) Tables() []*FmpTable {
tables := make([]*FmpTable, 0)
ent := ctx.Dictionary.GetEntry([]uint64{3, 16, 5})

View File

@ -29,16 +29,13 @@ func TestTables(t *testing.T) {
}
tables := f.Tables()
expected := "Untitled, WayDomains, WayProcesses"
tablesString := ""
for i, table := range tables {
tablesString += table.Name
if i < len(tables)-1 {
tablesString += ", "
}
expectedNames := []string{"Untitled", "WayDomains", "WayProcesses"}
tableNames := []string{}
for _, table := range tables {
tableNames = append(tableNames, table.Name)
}
if tablesString != expected {
t.Errorf("expected tables to be '%s', got '%s'", expected, tablesString)
if !slicesHaveSameElements(tableNames, expectedNames) {
t.Errorf("tables do not match")
}
}

View File

@ -1,94 +0,0 @@
package fmp
import (
"io"
"time"
)
type FmpFile struct {
VersionDate time.Time
CreatorName string
FileSize uint
Sectors []*FmpSector
Chunks []*FmpChunk
Dictionary *FmpDict
numSectors uint64 // Excludes the header sector
currentSectorID uint64
stream io.ReadSeeker
}
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
}
type FmpDict map[uint64]*FmpDictEntry
type FmpDictEntry struct {
Value []byte
Children *FmpDict
}
type FmpTable struct {
ID uint64
Name string
}
func (dict *FmpDict) GetEntry(path []uint64) *FmpDictEntry {
for i, key := range path {
_, ok := (*dict)[key]
if !ok {
return nil
}
if i == len(path)-1 {
return (*dict)[key]
} else {
dict = (*dict)[key].Children
if dict == nil {
return nil
}
}
}
return nil
}
func (dict *FmpDict) GetValue(path []uint64) []byte {
ent := dict.GetEntry(path)
if ent != nil {
return ent.Value
}
return nil
}
func (dict *FmpDict) SetValue(path []uint64, value []byte) {
for i, key := range path {
_, ok := (*dict)[key]
if !ok {
(*dict)[key] = &FmpDictEntry{Children: &FmpDict{}}
}
if i == len(path)-1 {
(*dict)[key].Value = value
} else {
dict = (*dict)[key].Children
}
}
}

View File

@ -1,5 +1,56 @@
package fmp
import "slices"
type FmpDict map[uint64]*FmpDictEntry
type FmpDictEntry struct {
Value []byte
Children *FmpDict
}
func (dict *FmpDict) GetEntry(path []uint64) *FmpDictEntry {
for i, key := range path {
_, ok := (*dict)[key]
if !ok {
return nil
}
if i == len(path)-1 {
return (*dict)[key]
} else {
dict = (*dict)[key].Children
if dict == nil {
return nil
}
}
}
return nil
}
func (dict *FmpDict) GetValue(path []uint64) []byte {
ent := dict.GetEntry(path)
if ent != nil {
return ent.Value
}
return nil
}
func (dict *FmpDict) SetValue(path []uint64, value []byte) {
for i, key := range path {
_, ok := (*dict)[key]
if !ok {
(*dict)[key] = &FmpDictEntry{Children: &FmpDict{}}
}
if i == len(path)-1 {
(*dict)[key].Value = value
} else {
dict = (*dict)[key].Children
}
}
}
func parseVarUint64(payload []byte) uint64 {
var length uint64
n := min(len(payload), 8) // clamp to uint64
@ -17,3 +68,23 @@ func decodeByteSeq(payload []byte) string {
}
return result
}
func addIf(cond bool, val uint64) uint64 {
if cond {
return val
}
return 0
}
func slicesHaveSameElements[Type comparable](a, b []Type) bool {
if len(a) != len(b) {
return false
}
for _, av := range a {
found := slices.Contains(b, av)
if !found {
return false
}
}
return true
}