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

Compare commits

..

15 Commits

Author SHA1 Message Date
c08c429b99 Add NewSector function 2025-06-22 20:08:53 +02:00
4ab7f0a588 Add date/time format constants 2025-06-22 19:20:41 +02:00
7cf3fd93ac Record parsing 2025-06-22 19:14:22 +02:00
d22b209ca5 Row parsing 2025-06-22 18:43:06 +02:00
7359962d98 Add calculation constants 2025-06-16 11:40:09 +02:00
c261a15041 Parse field repetition count 2025-06-16 11:29:48 +02:00
cc10a1e7b4 Parse table columns 2025-06-15 21:09:58 +02:00
f7fef208f8 Debug flag 2025-06-15 21:09:39 +02:00
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
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
11 changed files with 805 additions and 318 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.23.0'
- name: Build
run: go build -v ./...
working-directory: fmp
- name: Test
run: go test -v ./...
working-directory: fmp

Binary file not shown.

View File

@ -1,7 +1,6 @@
package fmp
type FmpError string
type FmpChunkType uint8
func (e FmpError) Error() string { return string(e) }
@ -16,41 +15,305 @@ 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_POP
FMP_CHUNK_NOOP
FmpDateLayout = "02/01/2006"
FmpTimeLayout = "15:04:05"
FmpDateTimeLayout = "02/01/2006 15:04:05"
)
type FmpChunkType uint8
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
FmpChunkSimpleData FmpChunkType = iota
FmpChunkSegmentedData
FmpChunkSimpleKeyValue
FmpChunkLongKeyValue
FmpChunkPathPush
FmpChunkPathPushLong
FmpChunkPathPop
FmpChunkNoop
)
type FmpFieldType uint8
const (
FmpFieldSimple FmpFieldType = 1
FmpFieldCalculation FmpFieldType = 2
FmpFieldScript FmpFieldType = 3
)
type FmpFieldStorageType uint8
const (
FmpFieldStorageRegular FmpFieldStorageType = 0
FmpFieldStorageGlobal FmpFieldStorageType = 1
FmpFieldStorageCalculation FmpFieldStorageType = 8
FmpFieldStorageUnstoredCalculation FmpFieldStorageType = 10
)
type FmpDataType uint8
const (
FmpDataText FmpDataType = 1
FmpDataNumber FmpDataType = 2
FmpDataDate FmpDataType = 3
FmpDataTime FmpDataType = 4
FmpDataTS FmpDataType = 5
FmpDataContainer FmpDataType = 6
)
type FmpAutoEnterOption uint8
const (
FmpAutoEnterData FmpAutoEnterOption = iota
FmpAutoEnterSerialNumber
FmpAutoEnterCalculation
FmpAutoEnterCalculationReplacingExistingValue
FmpAutoEnterFromLastVisitedRecord
FmpAutoEnterCreateDate
FmpAutoEnterCreateTime
FmpAutoEnterCreateTS
FmpAutoEnterCreateName
FmpAutoEnterCreateAccountName
FmpAutoEnterModDate
FmpAutoEnterModTime
FmpAutoEnterModTS
FmpAutoEnterModName
FmpAutoEnterModAccountName
)
var autoEnterPresetMap = map[uint8]FmpAutoEnterOption{
0: FmpAutoEnterCreateDate,
1: FmpAutoEnterCreateTime,
2: FmpAutoEnterCreateTS,
3: FmpAutoEnterCreateName,
4: FmpAutoEnterCreateAccountName,
5: FmpAutoEnterModDate,
6: FmpAutoEnterModTime,
7: FmpAutoEnterModTS,
8: FmpAutoEnterModName,
9: FmpAutoEnterModAccountName,
}
var autoEnterOptionMap = map[uint8]FmpAutoEnterOption{
2: FmpAutoEnterSerialNumber,
4: FmpAutoEnterData,
8: FmpAutoEnterCalculation,
16: FmpAutoEnterFromLastVisitedRecord,
32: FmpAutoEnterCalculation,
136: FmpAutoEnterCalculationReplacingExistingValue,
}
type FmpCalculationOperator byte
const (
FmpCalcOperatorAdd FmpCalculationOperator = '+'
FmpCalcOperatorSubtract FmpCalculationOperator = '-'
FmpCalcOperatorMultiply FmpCalculationOperator = '*'
FmpCalcOperatorDivide FmpCalculationOperator = '/'
FmpCalcOperatorConcatenate FmpCalculationOperator = '&'
)
// var calcOperatorMap = map[uint8]FmpCalculationOperator{
// 0x25: FmpCalcOperatorAdd,
// 0x26: FmpCalcOperatorSubtract,
// 0x27: FmpCalcOperatorMultiply,
// 0x28: FmpCalcOperatorDivide,
// 0x50: FmpCalcOperatorConcatenate,
// }
type FmpScriptStepType uint64
const (
FmpScriptPerformScript FmpScriptStepType = 1
FmpScriptSaveCopyAsXML 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

@ -3,8 +3,35 @@ package fmp
import (
"fmt"
"os"
"slices"
)
const debugging = false
func debug(str string, args ...interface{}) {
if debugging {
fmt.Printf(str+"\n", args...)
}
}
func dump(data []byte) {
if debugging {
for _, b := range data {
fmt.Printf("%02x ", b)
}
fmt.Println()
}
}
func dumpPath(path []uint64) {
if debugging {
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 {
@ -54,7 +81,15 @@ func (c *FmpChunk) String() string {
func (dict *FmpDict) string(parentPath string) string {
s := ""
for k, v := range *dict {
keys := make([]uint64, 0, len(*dict))
for k := range *dict {
keys = append(keys, k)
}
slices.Sort(keys)
for _, k := range keys {
v := (*dict)[k]
s += fmt.Sprintf("%v%v: %v\n", parentPath, k, string(v.Value))
if v.Children != nil {

58
fmp/fmp_dict.go Normal file
View File

@ -0,0 +1,58 @@
package fmp
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) GetChildren(path ...uint64) *FmpDict {
ent := dict.GetEntry(path...)
if ent != nil {
return ent.Children
}
return &FmpDict{}
}
func (dict *FmpDict) set(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

@ -20,17 +20,31 @@ const (
hbamSize = len(hbamSequence)
)
type FmpFile struct {
VersionDate time.Time
CreatorName string
FileSize uint
Sectors []*FmpSector
Chunks []*FmpChunk
Dictionary *FmpDict
tables []*FmpTable
numSectors uint64 // Excludes the header sector
currentSectorID uint64
stream *os.File
}
func OpenFile(path string) (*FmpFile, error) {
info, err := os.Stat(path)
if err != nil {
return nil, err
}
stream, err := os.Open(path)
stream, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
return nil, err
}
defer stream.Close()
ctx := &FmpFile{stream: stream, Dictionary: &FmpDict{}}
if err := ctx.readHeader(); err != nil {
@ -40,7 +54,6 @@ func OpenFile(path string) (*FmpFile, error) {
ctx.FileSize = uint(info.Size())
ctx.numSectors = uint64((ctx.FileSize / sectorSize) - 1)
ctx.Sectors = make([]*FmpSector, 0)
currentPath := make([]uint64, 0)
ctx.stream.Seek(2*sectorSize, io.SeekStart)
for {
@ -55,7 +68,7 @@ func OpenFile(path string) (*FmpFile, error) {
ctx.Sectors = append(ctx.Sectors, sector)
if sector.ID != 0 {
err = sector.processChunks(ctx.Dictionary, &currentPath)
err = sector.processChunks(ctx.Dictionary)
if err != nil {
return nil, err
}
@ -72,9 +85,14 @@ func OpenFile(path string) (*FmpFile, error) {
}
}
ctx.readTables()
return ctx, nil
}
func (ctx *FmpFile) Close() {
ctx.stream.Close()
}
func (ctx *FmpFile) readHeader() error {
buf := make([]byte, headerSize)
_, err := ctx.stream.Read(buf)
@ -100,7 +118,7 @@ func (ctx *FmpFile) readHeader() error {
}
func (ctx *FmpFile) readSector() (*FmpSector, error) {
println("---------- Reading sector", ctx.currentSectorID)
debug("---------- Reading sector %d", ctx.currentSectorID)
buf := make([]byte, sectorHeaderSize)
n, err := ctx.stream.Read(buf)
@ -115,8 +133,8 @@ func (ctx *FmpFile) readSector() (*FmpSector, error) {
ID: ctx.currentSectorID,
Deleted: buf[0] > 0,
Level: uint8(buf[1]),
PrevID: parseVarUint64(buf[4 : 4+4]),
NextID: parseVarUint64(buf[8 : 8+4]),
PrevID: decodeVarUint64(buf[4 : 4+4]),
NextID: decodeVarUint64(buf[8 : 8+4]),
Chunks: make([]*FmpChunk, 0),
}
@ -135,3 +153,101 @@ func (ctx *FmpFile) readSector() (*FmpSector, error) {
}
return sector, nil
}
func (ctx *FmpFile) readTables() {
tables := make([]*FmpTable, 0)
ent := ctx.Dictionary.GetEntry(3, 16, 5)
for path, tableEnt := range *ent.Children {
if path < 128 {
continue
}
table := &FmpTable{
ID: path,
Name: decodeString(tableEnt.Children.GetValue(16)),
Columns: map[uint64]*FmpColumn{},
Records: map[uint64]*FmpRecord{},
}
tables = append(tables, table)
for colPath, colEnt := range *ctx.Dictionary.GetChildren(table.ID, 3, 5) {
name := decodeString(colEnt.Children.GetValue(16))
flags := colEnt.Children.GetValue(2)
column := &FmpColumn{
Index: colPath,
Name: name,
Type: FmpFieldType(flags[0]),
DataType: FmpDataType(flags[1]),
StorageType: FmpFieldStorageType(flags[9]),
Repetitions: flags[25],
Indexed: flags[8] == 128,
}
if flags[11] == 1 {
column.AutoEnter = autoEnterPresetMap[flags[4]]
} else {
column.AutoEnter = autoEnterOptionMap[flags[11]]
}
table.Columns[column.Index] = column
}
for recPath, recEnt := range *ctx.Dictionary.GetChildren(table.ID, 5) {
record := &FmpRecord{Index: recPath, Values: make(map[uint64]string)}
table.Records[record.Index] = record
if recPath > table.lastRecordID {
table.lastRecordID = recPath
}
for colIndex, value := range *recEnt.Children {
record.Values[colIndex] = decodeString(value.Value)
}
}
}
ctx.tables = tables
}
func (ctx *FmpFile) NewSector() (*FmpSector, error) {
id := uint64(len(ctx.Sectors)) + 1
prevID := id - 2
ctx.Sectors[prevID].NextID = uint64(id)
_, err := ctx.stream.WriteAt(encodeUint(4, int(id)), int64((id-1)*sectorSize)+4)
if err != nil {
return nil, err
}
sector := &FmpSector{
ID: uint64(id),
Deleted: false,
Level: 0,
PrevID: prevID,
NextID: 0,
Chunks: make([]*FmpChunk, 0),
}
ctx.Sectors = append(ctx.Sectors, sector)
sectorBuf := make([]byte, sectorSize)
sectorBuf[0] = 0 // deleted
sectorBuf[1] = 0 // level
writeToSlice(sectorBuf, 4, encodeUint(4, int(prevID))...)
writeToSlice(sectorBuf, 8, encodeUint(4, int(id))...)
_, err = ctx.stream.WriteAt(sectorBuf, int64((id+1)*sectorSize))
if err != nil {
return nil, err
}
return sector, nil
}
func (ctx *FmpFile) setValue(path []uint64, value []byte) {
// ctx.Dictionary.set(path, value)
}

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 {
@ -19,22 +33,17 @@ func (sect *FmpSector) readChunks() error {
chunk, err := sect.readChunk(sect.Payload)
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 {
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 {
println("break1")
break
}
if err != nil {
println(hex.EncodeToString(sect.Payload))
println("break2")
debug("chunk error at sector %d", sect.ID)
dump(sect.Payload)
return err
}
if chunk == nil {
println("break3")
break
}
if chunk.Length == 0 {
@ -51,55 +60,49 @@ func (sect *FmpSector) readChunks() error {
return nil
}
func (sect *FmpSector) processChunks(dict *FmpDict, currentPath *[]uint64) error {
func (sect *FmpSector) processChunks(dict *FmpDict) error {
err := sect.readChunks()
if err != nil {
return err
}
currentPath := make([]uint64, 0)
for _, chunk := range sect.Chunks {
switch chunk.Type {
case FMP_CHUNK_PATH_PUSH:
*currentPath = append(*currentPath, uint64(chunk.Value[0]))
case FmpChunkPathPush, FmpChunkPathPushLong:
currentPath = append(currentPath, decodeVarUint64(chunk.Value))
dumpPath(currentPath)
case FMP_CHUNK_PATH_POP:
if len(*currentPath) > 0 {
*currentPath = (*currentPath)[:len(*currentPath)-1]
case FmpChunkPathPop:
if len(currentPath) > 0 {
currentPath = (currentPath)[:len(currentPath)-1]
}
case FMP_CHUNK_SIMPLE_DATA:
dict.set(*currentPath, chunk.Value)
case FmpChunkSimpleData:
dict.set(currentPath, chunk.Value)
case FMP_CHUNK_SEGMENTED_DATA:
case FmpChunkSegmentedData:
// Todo: take index into account
dict.set(
*currentPath,
append(dict.getValue(*currentPath), chunk.Value...),
currentPath,
append(dict.GetValue(currentPath...), chunk.Value...),
)
case FMP_CHUNK_SIMPLE_KEY_VALUE:
case FmpChunkSimpleKeyValue:
dict.set(
append(*currentPath, uint64(chunk.Key)),
append(currentPath, uint64(chunk.Key)),
chunk.Value,
)
case FMP_CHUNK_LONG_KEY_VALUE:
case FmpChunkLongKeyValue:
dict.set(
append(*currentPath, uint64(chunk.Key)), // todo: ??
append(currentPath, uint64(chunk.Key)), // todo: ??
chunk.Value,
)
case FMP_CHUNK_NOOP:
case FmpChunkNoop:
// noop
}
if chunk.Delayed {
if len(*currentPath) == 0 {
println("warning: delayed pop without path")
} else {
*currentPath = (*currentPath)[:len(*currentPath)-1]
}
}
}
return nil
}
@ -112,182 +115,149 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
chunk := &FmpChunk{}
chunkCode := payload[0]
if (chunkCode & 0xC0) == 0xC0 {
chunkCode &= 0x3F
chunk.Delayed = true
}
switch chunkCode {
case 0x00:
chunk.Length = 2
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[1:chunk.Length]
case 0x01:
chunk.Length = 3
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
case 0x01, 0x02, 0x03, 0x04, 0x05:
chunk.Length = 2 + 2*uint64(chunkCode-0x01) + addIf(chunkCode == 0x01, 1)
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = uint64(payload[1])
chunk.Value = payload[2:chunk.Length]
case 0x06:
valueLength := uint64(payload[2])
chunk.Length = 3 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Length = 3 + uint64(payload[2])
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = uint64(payload[1])
chunk.Value = payload[3:chunk.Length]
case 0x07:
valueLength := parseVarUint64(payload[2 : 2+2])
valueLength := decodeVarUint64(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.Key = parseVarUint64(payload[1 : 1+2])
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = decodeVarUint64(payload[1 : 1+2])
chunk.Value = payload[3:chunk.Length]
case 0x0A, 0x0B, 0x0C, 0x0D:
valueLength := uint64(2 * (chunkCode - 0x09))
chunk.Length = 3 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Key = parseVarUint64(payload[1 : 1+2])
chunk.Length = 3 + 2*uint64(chunkCode-0x09)
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = decodeVarUint64(payload[1 : 1+2])
chunk.Value = payload[3:chunk.Length]
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 {
chunk.Length = 7
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[2:chunk.Length]
break
}
valueLength := uint64(payload[2])
chunk.Length = 4 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
chunk.Key = parseVarUint64(payload[1 : 1+2])
chunk.Length = 4 + uint64(payload[3])
chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = decodeVarUint64(payload[1 : 1+2])
chunk.Value = payload[4:chunk.Length]
case 0x0F:
valueLength := parseVarUint64(payload[3 : 3+2])
chunk.Length = min(5+valueLength, uint64(len(payload)))
chunk.Type = FMP_CHUNK_SEGMENTED_DATA
chunk.Index = parseVarUint64(payload[1 : 1+2])
valueLength := decodeVarUint64(payload[3 : 3+2])
chunk.Length = uint64(len(payload))
if chunk.Length > 5+valueLength {
return nil, ErrBadChunk
}
chunk.Type = FmpChunkSegmentedData
chunk.Index = decodeVarUint64(payload[1 : 1+2])
chunk.Value = payload[5:chunk.Length]
case 0x10, 0x11:
valueLength := 3 + (uint64(chunkCode) - 0x10)
chunk.Length = 1 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Length = 4 + addIf(chunkCode == 0x11, 1)
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[1:chunk.Length]
case 0x12, 0x13, 0x14, 0x15:
valueLength := 1 + 2*(uint64(chunkCode)-0x10)
chunk.Length = 1 + valueLength
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Length = 4 + 2*(uint64(chunkCode)-0x11)
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[1:chunk.Length]
case 0x16:
valueLength := uint64(payload[4])
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Length = 5 + valueLength
chunk.Key = parseVarUint64(payload[1 : 1+3])
chunk.Length = 5 + uint64(payload[4])
chunk.Type = FmpChunkLongKeyValue
chunk.Key = decodeVarUint64(payload[1 : 1+3])
chunk.Value = payload[5:chunk.Length]
case 0x17:
valueLength := parseVarUint64(payload[4 : 4+2])
chunk.Length = 6 + valueLength
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Key = parseVarUint64(payload[1 : 1+3])
chunk.Length = 6 + decodeVarUint64(payload[4:4+2])
chunk.Type = FmpChunkLongKeyValue
chunk.Key = decodeVarUint64(payload[1 : 1+3])
chunk.Value = payload[6:chunk.Length]
case 0x19:
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Length = 2
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 0x19, 0x1A, 0x1B, 0x1C, 0x1D:
valueLength := uint64(payload[1])
chunk.Length = 2 + valueLength + 2*uint64(chunkCode-0x19) + addIf(chunkCode == 0x19, 1)
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.Key = parseVarUint64(payload[2 : 2+keyLength])
chunk.Type = FmpChunkLongKeyValue
chunk.Key = decodeVarUint64(payload[2 : 2+keyLength])
chunk.Value = payload[2+keyLength+1 : chunk.Length]
case 0x1F:
keyLength := uint64(uint64(payload[1]))
valueLength := parseVarUint64(payload[2+keyLength : 2+keyLength+2+1])
keyLength := uint64(payload[1])
valueLength := decodeVarUint64(payload[2+keyLength : 2+keyLength+2+1])
chunk.Length = 2 + keyLength + 2 + valueLength
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
chunk.Key = parseVarUint64(payload[2 : 2+keyLength])
chunk.Type = FmpChunkLongKeyValue
chunk.Key = decodeVarUint64(payload[2 : 2+keyLength])
chunk.Value = payload[2+keyLength+2 : chunk.Length]
case 0x20:
case 0x20, 0xE0:
if payload[1] == 0xFE {
chunk.Length = 10
chunk.Type = FMP_CHUNK_PATH_PUSH
chunk.Value = payload[1:chunk.Length]
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
chunk.Type = FMP_CHUNK_SIMPLE_DATA
chunk.Length = 2 + uint64(payload[1])
chunk.Type = FmpChunkSimpleData
chunk.Value = payload[1:chunk.Length]
case 0x28:
chunk.Length = 3
chunk.Type = FMP_CHUNK_PATH_PUSH
chunk.Value = payload[1:chunk.Length]
case 0x30:
chunk.Length = 4
chunk.Type = FMP_CHUNK_PATH_PUSH
case 0x28, 0x30:
chunk.Length = 3 + addIf(chunkCode == 0x30, 1)
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
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:

View File

@ -1,47 +1,63 @@
package fmp
func (ctx *FmpFile) Tables() []*FmpTable {
tables := make([]*FmpTable, 0)
type FmpTable struct {
ID uint64
Name string
Columns map[uint64]*FmpColumn
Records map[uint64]*FmpRecord
for key, ent := range *ctx.Dictionary {
if key != 3 {
continue
}
println("Found a 3")
lastRecordID uint64
}
for key, ent = range *ent.Children {
if key != 16 {
continue
}
println("Found a 3.16")
type FmpColumn struct {
Index uint64
Name string
Type FmpFieldType
DataType FmpDataType
StorageType FmpFieldStorageType
AutoEnter FmpAutoEnterOption
Repetitions uint8
Indexed bool
}
for key, ent = range *ent.Children {
if key != 5 {
continue
}
println("Found a 3.16.5")
type FmpRecord struct {
Table *FmpTable
Index uint64
Values map[uint64]string
}
for tablePath := range *ent.Children {
if key >= 128 {
continue
}
// Found a table!
println("Found a table at 3.16.5.", tablePath)
}
}
func (ctx *FmpFile) Table(name string) *FmpTable {
for _, table := range ctx.tables {
if table.Name == name {
return table
}
}
// 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 nil
}
func (t *FmpTable) Column(name string) *FmpColumn {
for _, column := range t.Columns {
if column.Name == name {
return column
}
}
return nil
}
func (t *FmpTable) NewRecord(values map[string]string) (*FmpRecord, error) {
vals := make(map[uint64]string)
for k, v := range values {
col := t.Column(k)
vals[col.Index] = v
}
id := t.lastRecordID + 1
t.lastRecordID = id
t.Records[id] = &FmpRecord{Table: t, Index: id, Values: vals}
return t.Records[id], nil
}
func (r *FmpRecord) Value(name string) string {
return r.Values[r.Table.Column(name).Index]
}

View File

@ -1,17 +1,22 @@
package fmp
import "testing"
import (
"slices"
"testing"
)
func TestOpenFile(t *testing.T) {
f, err := OpenFile("../files/Untitled.fmp12")
if err != nil {
t.Fatal(err)
}
if f.FileSize != 393216 {
t.Errorf("expected file size to be 393216, got %d", f.FileSize)
defer f.Close()
if f.FileSize != 229376 {
t.Errorf("expected file size to be 229376, got %d", f.FileSize)
}
if f.numSectors != 95 {
t.Errorf("expected number of sectors to be 95, got %d", f.numSectors)
if f.numSectors != 55 {
t.Errorf("expected number of sectors to be 55, got %d", f.numSectors)
}
if f.CreatorName != "Pro 12.0" {
t.Errorf("expected application name to be 'Pro 12.0', got '%s'", f.CreatorName)
@ -19,6 +24,7 @@ func TestOpenFile(t *testing.T) {
if f.VersionDate.Format("2006-01-02") != "2025-01-11" {
t.Errorf("expected version date to be '2025-01-11', got '%s'", f.VersionDate.Format("2006-01-02"))
}
f.ToDebugFile("../private/output")
}
@ -27,16 +33,81 @@ func TestTables(t *testing.T) {
if err != nil {
t.Fatal(err)
}
tables := f.Tables()
defer f.Close()
if len(tables) != 1 || tables[0].Name != "Untitled" {
tablesString := ""
for i, table := range tables {
tablesString += table.Name
if i < len(tables)-1 {
tablesString += ", "
}
}
t.Errorf("expected tables to be 'Untitled', got '%s'", tablesString)
expectedNames := []string{"Untitled"}
tableNames := []string{}
for _, table := range f.tables {
tableNames = append(tableNames, table.Name)
}
if !slicesHaveSameElements(tableNames, expectedNames) {
t.Errorf("tables do not match")
}
table := f.Table("Untitled")
if table == nil {
t.Errorf("expected table to exist, but it does not")
return
}
if table.Name != "Untitled" {
t.Errorf("expected table name to be 'Untitled', but it is '%s'", table.Name)
}
if len(table.Records) != 3 {
t.Errorf("expected table to have 3 records, but it has %d", len(table.Records))
}
if table.Records[1].Values[1] != "629FAA83-50D8-401F-A560-C8D45217D17B" {
t.Errorf("first record has an incorrect ID '%s'", table.Records[0].Values[0])
}
col := table.Column("PrimaryKey")
if col == nil {
t.Errorf("expected column to exist, but it does not")
return
}
if col.Name != "PrimaryKey" {
t.Errorf("expected column name to be 'PrimaryKey', but it is '%s'", col.Name)
}
if col.Type != FmpFieldSimple {
t.Errorf("expected field type to be simple, but it is not")
}
if col.DataType != FmpDataText {
t.Errorf("expected field data type to be text, but it is not")
}
if col.StorageType != FmpFieldStorageRegular {
t.Errorf("expected field storage type to be regular, but it is not")
}
if col.Repetitions != 1 {
t.Errorf("expected field repetition count to be 1, but it is %d", col.Repetitions)
}
if !col.Indexed {
t.Errorf("expected field to be indexed, but it is not")
}
if col.AutoEnter != FmpAutoEnterCalculationReplacingExistingValue {
t.Errorf("expected field to have auto enter calculation replacing existing value, but it does not")
}
newRecord, err := table.NewRecord(map[string]string{"PrimaryKey": "629FAA83-50D8-401F-A560-C8D45217D17B"})
if newRecord == nil || err != nil {
t.Errorf("expected new record to be created, but it is nil")
return
}
if newRecord.Index != 4 {
t.Errorf("expected new record index to be 4, but it is %d", newRecord.Index)
}
if newRecord.Value("PrimaryKey") != "629FAA83-50D8-401F-A560-C8D45217D17B" {
t.Errorf("expected new record primary key to be '629FAA83-50D8-401F-A560-C8D45217D17B', but it is '%s'", newRecord.Value("PrimaryKey"))
}
}
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
}

View File

@ -1,91 +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
Delayed bool
}
type FmpDict map[uint64]*FmpDictEntry
type FmpDictEntry struct {
Value []byte
Children *FmpDict
}
type FmpTable struct {
Name string
}
func (dict *FmpDict) get(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
}
}
return nil
}
func (dict *FmpDict) getValue(path []uint64) []byte {
ent := dict.get(path)
if ent != nil {
return ent.Value
}
return nil
}
func (dict *FmpDict) set(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,6 +1,13 @@
package fmp
func parseVarUint64(payload []byte) uint64 {
func addIf(cond bool, val uint64) uint64 {
if cond {
return val
}
return 0
}
func decodeVarUint64(payload []byte) uint64 {
var length uint64
n := min(len(payload), 8) // clamp to uint64
for i := range n {
@ -10,10 +17,25 @@ func parseVarUint64(payload []byte) uint64 {
return length
}
func decodeByteSeq(payload []byte) string {
func decodeString(payload []byte) string {
result := ""
for i := range payload {
result += string(payload[i] ^ 0x5A)
}
return result
}
func encodeUint(size uint, value int) []byte {
result := make([]byte, size)
for i := range size {
result[i] = byte(value & 0xFF)
value >>= 8
}
return result
}
func writeToSlice(slice []byte, start int, payload ...byte) {
for i := range payload {
slice[start+i] = payload[i]
}
}