mirror of
https://github.com/garraflavatra/go-fmp.git
synced 2025-06-28 04:25:11 +00:00
Compare commits
15 Commits
b44c90da30
...
main
Author | SHA1 | Date | |
---|---|---|---|
c08c429b99 | |||
4ab7f0a588 | |||
7cf3fd93ac | |||
d22b209ca5 | |||
7359962d98 | |||
c261a15041 | |||
cc10a1e7b4 | |||
f7fef208f8 | |||
10285084eb | |||
53134a0d09 | |||
cc8a602b51 | |||
bd871b6457 | |||
b94fa28ba9 | |||
d9ffc3e573 | |||
a7bde87c6f |
27
.github/workflows/ci.yml
vendored
Normal file
27
.github/workflows/ci.yml
vendored
Normal 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.
333
fmp/fmp_const.go
333
fmp/fmp_const.go
@ -1,7 +1,6 @@
|
|||||||
package fmp
|
package fmp
|
||||||
|
|
||||||
type FmpError string
|
type FmpError string
|
||||||
type FmpChunkType uint8
|
|
||||||
|
|
||||||
func (e FmpError) Error() string { return string(e) }
|
func (e FmpError) Error() string { return string(e) }
|
||||||
|
|
||||||
@ -16,41 +15,305 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FMP_CHUNK_SIMPLE_DATA FmpChunkType = iota
|
FmpDateLayout = "02/01/2006"
|
||||||
FMP_CHUNK_SEGMENTED_DATA
|
FmpTimeLayout = "15:04:05"
|
||||||
FMP_CHUNK_SIMPLE_KEY_VALUE
|
FmpDateTimeLayout = "02/01/2006 15:04:05"
|
||||||
FMP_CHUNK_LONG_KEY_VALUE
|
|
||||||
FMP_CHUNK_PATH_PUSH
|
|
||||||
FMP_CHUNK_PATH_POP
|
|
||||||
FMP_CHUNK_NOOP
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FmpChunkType uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FMP_COLLATION_ENGLISH = 0x00
|
FmpChunkSimpleData FmpChunkType = iota
|
||||||
FMP_COLLATION_FRENCH = 0x01
|
FmpChunkSegmentedData
|
||||||
FMP_COLLATION_GERMAN = 0x03
|
FmpChunkSimpleKeyValue
|
||||||
FMP_COLLATION_ITALIAN = 0x04
|
FmpChunkLongKeyValue
|
||||||
FMP_COLLATION_DUTCH = 0x05
|
FmpChunkPathPush
|
||||||
FMP_COLLATION_SWEDISH = 0x07
|
FmpChunkPathPushLong
|
||||||
FMP_COLLATION_SPANISH = 0x08
|
FmpChunkPathPop
|
||||||
FMP_COLLATION_DANISH = 0x09
|
FmpChunkNoop
|
||||||
FMP_COLLATION_PORTUGUESE = 0x0A
|
)
|
||||||
FMP_COLLATION_NORWEGIAN = 0x0C
|
|
||||||
FMP_COLLATION_FINNISH = 0x11
|
type FmpFieldType uint8
|
||||||
FMP_COLLATION_GREEK = 0x14
|
|
||||||
FMP_COLLATION_ICELANDIC = 0x15
|
const (
|
||||||
FMP_COLLATION_TURKISH = 0x18
|
FmpFieldSimple FmpFieldType = 1
|
||||||
FMP_COLLATION_ROMANIAN = 0x27
|
FmpFieldCalculation FmpFieldType = 2
|
||||||
FMP_COLLATION_POLISH = 0x2a
|
FmpFieldScript FmpFieldType = 3
|
||||||
FMP_COLLATION_HUNGARIAN = 0x2b
|
)
|
||||||
FMP_COLLATION_RUSSIAN = 0x31
|
|
||||||
FMP_COLLATION_CZECH = 0x38
|
type FmpFieldStorageType uint8
|
||||||
FMP_COLLATION_UKRAINIAN = 0x3e
|
|
||||||
FMP_COLLATION_CROATIAN = 0x42
|
const (
|
||||||
FMP_COLLATION_CATALAN = 0x49
|
FmpFieldStorageRegular FmpFieldStorageType = 0
|
||||||
FMP_COLLATION_FINNISH_ALT = 0x62
|
FmpFieldStorageGlobal FmpFieldStorageType = 1
|
||||||
FMP_COLLATION_SWEDISH_ALT = 0x63
|
FmpFieldStorageCalculation FmpFieldStorageType = 8
|
||||||
FMP_COLLATION_GERMAN_ALT = 0x64
|
FmpFieldStorageUnstoredCalculation FmpFieldStorageType = 10
|
||||||
FMP_COLLATION_SPANISH_ALT = 0x65
|
)
|
||||||
FMP_COLLATION_ASCII = 0x66
|
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
@ -3,8 +3,35 @@ package fmp
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"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) {
|
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 {
|
||||||
@ -54,7 +81,15 @@ func (c *FmpChunk) String() string {
|
|||||||
|
|
||||||
func (dict *FmpDict) string(parentPath string) string {
|
func (dict *FmpDict) string(parentPath string) string {
|
||||||
s := ""
|
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))
|
s += fmt.Sprintf("%v%v: %v\n", parentPath, k, string(v.Value))
|
||||||
|
|
||||||
if v.Children != nil {
|
if v.Children != nil {
|
||||||
|
58
fmp/fmp_dict.go
Normal file
58
fmp/fmp_dict.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
fmp/fmp_file.go
130
fmp/fmp_file.go
@ -20,17 +20,31 @@ const (
|
|||||||
hbamSize = len(hbamSequence)
|
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) {
|
func OpenFile(path string) (*FmpFile, error) {
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, err := os.Open(path)
|
stream, err := os.OpenFile(path, os.O_RDWR, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer stream.Close()
|
|
||||||
|
|
||||||
ctx := &FmpFile{stream: stream, Dictionary: &FmpDict{}}
|
ctx := &FmpFile{stream: stream, Dictionary: &FmpDict{}}
|
||||||
if err := ctx.readHeader(); err != nil {
|
if err := ctx.readHeader(); err != nil {
|
||||||
@ -40,7 +54,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 +68,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, ¤tPath)
|
err = sector.processChunks(ctx.Dictionary)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -72,9 +85,14 @@ func OpenFile(path string) (*FmpFile, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.readTables()
|
||||||
return ctx, nil
|
return ctx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *FmpFile) Close() {
|
||||||
|
ctx.stream.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func (ctx *FmpFile) readHeader() error {
|
func (ctx *FmpFile) readHeader() error {
|
||||||
buf := make([]byte, headerSize)
|
buf := make([]byte, headerSize)
|
||||||
_, err := ctx.stream.Read(buf)
|
_, err := ctx.stream.Read(buf)
|
||||||
@ -100,7 +118,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)
|
||||||
|
|
||||||
@ -115,8 +133,8 @@ func (ctx *FmpFile) readSector() (*FmpSector, error) {
|
|||||||
ID: ctx.currentSectorID,
|
ID: ctx.currentSectorID,
|
||||||
Deleted: buf[0] > 0,
|
Deleted: buf[0] > 0,
|
||||||
Level: uint8(buf[1]),
|
Level: uint8(buf[1]),
|
||||||
PrevID: parseVarUint64(buf[4 : 4+4]),
|
PrevID: decodeVarUint64(buf[4 : 4+4]),
|
||||||
NextID: parseVarUint64(buf[8 : 8+4]),
|
NextID: decodeVarUint64(buf[8 : 8+4]),
|
||||||
Chunks: make([]*FmpChunk, 0),
|
Chunks: make([]*FmpChunk, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,3 +153,101 @@ func (ctx *FmpFile) readSector() (*FmpSector, error) {
|
|||||||
}
|
}
|
||||||
return sector, nil
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,10 +1,24 @@
|
|||||||
package fmp
|
package fmp
|
||||||
|
|
||||||
import (
|
type FmpSector struct {
|
||||||
"encoding/hex"
|
ID uint64
|
||||||
"fmt"
|
Level uint8
|
||||||
"io"
|
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 {
|
func (sect *FmpSector) readChunks() error {
|
||||||
if len(sect.Chunks) > 0 {
|
if len(sect.Chunks) > 0 {
|
||||||
@ -19,22 +33,17 @@ 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 {
|
|
||||||
println("break1")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(hex.EncodeToString(sect.Payload))
|
debug("chunk error at sector %d", sect.ID)
|
||||||
println("break2")
|
dump(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 +60,49 @@ 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 FmpChunkPathPush, FmpChunkPathPushLong:
|
||||||
*currentPath = append(*currentPath, uint64(chunk.Value[0]))
|
currentPath = append(currentPath, decodeVarUint64(chunk.Value))
|
||||||
|
dumpPath(currentPath)
|
||||||
|
|
||||||
case FMP_CHUNK_PATH_POP:
|
case FmpChunkPathPop:
|
||||||
if len(*currentPath) > 0 {
|
if len(currentPath) > 0 {
|
||||||
*currentPath = (*currentPath)[:len(*currentPath)-1]
|
currentPath = (currentPath)[:len(currentPath)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
case FMP_CHUNK_SIMPLE_DATA:
|
case FmpChunkSimpleData:
|
||||||
dict.set(*currentPath, chunk.Value)
|
dict.set(currentPath, chunk.Value)
|
||||||
|
|
||||||
case FMP_CHUNK_SEGMENTED_DATA:
|
case FmpChunkSegmentedData:
|
||||||
// Todo: take index into account
|
// Todo: take index into account
|
||||||
dict.set(
|
dict.set(
|
||||||
*currentPath,
|
currentPath,
|
||||||
append(dict.getValue(*currentPath), chunk.Value...),
|
append(dict.GetValue(currentPath...), chunk.Value...),
|
||||||
)
|
)
|
||||||
|
|
||||||
case FMP_CHUNK_SIMPLE_KEY_VALUE:
|
case FmpChunkSimpleKeyValue:
|
||||||
dict.set(
|
dict.set(
|
||||||
append(*currentPath, uint64(chunk.Key)),
|
append(currentPath, uint64(chunk.Key)),
|
||||||
chunk.Value,
|
chunk.Value,
|
||||||
)
|
)
|
||||||
|
|
||||||
case FMP_CHUNK_LONG_KEY_VALUE:
|
case FmpChunkLongKeyValue:
|
||||||
dict.set(
|
dict.set(
|
||||||
append(*currentPath, uint64(chunk.Key)), // todo: ??
|
append(currentPath, uint64(chunk.Key)), // todo: ??
|
||||||
chunk.Value,
|
chunk.Value,
|
||||||
)
|
)
|
||||||
|
|
||||||
case FMP_CHUNK_NOOP:
|
case FmpChunkNoop:
|
||||||
// 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,182 +115,149 @@ 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 = FmpChunkSimpleData
|
||||||
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.Type = FmpChunkSimpleKeyValue
|
||||||
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.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 = FmpChunkSimpleKeyValue
|
||||||
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]
|
||||||
|
|
||||||
case 0x07:
|
case 0x07:
|
||||||
valueLength := parseVarUint64(payload[2 : 2+2])
|
valueLength := decodeVarUint64(payload[2 : 2+2])
|
||||||
chunk.Length = min(4+valueLength, uint64(len(payload)))
|
chunk.Length = min(4+valueLength, uint64(len(payload)))
|
||||||
chunk.Type = FMP_CHUNK_SEGMENTED_DATA
|
chunk.Type = FmpChunkSegmentedData
|
||||||
chunk.Index = uint64(payload[1])
|
chunk.Index = uint64(payload[1])
|
||||||
chunk.Value = payload[4:chunk.Length]
|
chunk.Value = payload[4:chunk.Length]
|
||||||
|
|
||||||
case 0x08:
|
case 0x08:
|
||||||
chunk.Length = 3
|
chunk.Length = 3
|
||||||
chunk.Type = FMP_CHUNK_SIMPLE_DATA
|
chunk.Type = FmpChunkSimpleData
|
||||||
chunk.Value = payload[1:chunk.Length]
|
chunk.Value = payload[1:chunk.Length]
|
||||||
|
|
||||||
case 0x09:
|
case 0x09:
|
||||||
chunk.Length = 4
|
chunk.Length = 4
|
||||||
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
|
chunk.Type = FmpChunkSimpleKeyValue
|
||||||
chunk.Key = parseVarUint64(payload[1 : 1+2])
|
chunk.Key = decodeVarUint64(payload[1 : 1+2])
|
||||||
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 = FmpChunkSimpleKeyValue
|
||||||
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
|
chunk.Key = decodeVarUint64(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 = FmpChunkSimpleData
|
||||||
chunk.Value = payload[2:chunk.Length]
|
chunk.Value = payload[2:chunk.Length]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
valueLength := uint64(payload[2])
|
chunk.Length = 4 + uint64(payload[3])
|
||||||
chunk.Length = 4 + valueLength
|
chunk.Type = FmpChunkSimpleKeyValue
|
||||||
chunk.Type = FMP_CHUNK_SIMPLE_KEY_VALUE
|
chunk.Key = decodeVarUint64(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 := decodeVarUint64(payload[3 : 3+2])
|
||||||
chunk.Length = min(5+valueLength, uint64(len(payload)))
|
chunk.Length = uint64(len(payload))
|
||||||
chunk.Type = FMP_CHUNK_SEGMENTED_DATA
|
if chunk.Length > 5+valueLength {
|
||||||
chunk.Index = parseVarUint64(payload[1 : 1+2])
|
return nil, ErrBadChunk
|
||||||
|
}
|
||||||
|
chunk.Type = FmpChunkSegmentedData
|
||||||
|
chunk.Index = decodeVarUint64(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 = FmpChunkSimpleData
|
||||||
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 = FmpChunkSimpleData
|
||||||
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 = FmpChunkLongKeyValue
|
||||||
chunk.Length = 5 + valueLength
|
chunk.Key = decodeVarUint64(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 + decodeVarUint64(payload[4:4+2])
|
||||||
chunk.Length = 6 + valueLength
|
chunk.Type = FmpChunkLongKeyValue
|
||||||
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
|
chunk.Key = decodeVarUint64(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:
|
||||||
chunk.Type = FMP_CHUNK_SIMPLE_DATA
|
valueLength := uint64(payload[1])
|
||||||
chunk.Length = 2
|
chunk.Length = 2 + valueLength + 2*uint64(chunkCode-0x19) + addIf(chunkCode == 0x19, 1)
|
||||||
chunk.Value = payload[1:chunk.Length]
|
chunk.Type = FmpChunkSimpleData
|
||||||
|
chunk.Value = payload[2 : 2+valueLength]
|
||||||
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])
|
||||||
valueLength := uint64(payload[2+keyLength])
|
valueLength := uint64(payload[2+keyLength])
|
||||||
chunk.Length = 2 + keyLength + 1 + valueLength
|
chunk.Length = 2 + keyLength + 1 + valueLength
|
||||||
chunk.Type = FMP_CHUNK_LONG_KEY_VALUE
|
chunk.Type = FmpChunkLongKeyValue
|
||||||
chunk.Key = parseVarUint64(payload[2 : 2+keyLength])
|
chunk.Key = decodeVarUint64(payload[2 : 2+keyLength])
|
||||||
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 := decodeVarUint64(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 = FmpChunkLongKeyValue
|
||||||
chunk.Key = parseVarUint64(payload[2 : 2+keyLength])
|
chunk.Key = decodeVarUint64(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 = FmpChunkPathPush
|
||||||
chunk.Value = payload[1:chunk.Length]
|
chunk.Value = payload[2:chunk.Length]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk.Length = 2
|
chunk.Length = 2
|
||||||
chunk.Type = FMP_CHUNK_PATH_PUSH
|
chunk.Type = FmpChunkPathPush
|
||||||
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 = FmpChunkSimpleData
|
||||||
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.Type = FmpChunkPathPush
|
||||||
chunk.Value = payload[1:chunk.Length]
|
|
||||||
|
|
||||||
case 0x30:
|
|
||||||
chunk.Length = 4
|
|
||||||
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 = FmpChunkPathPushLong
|
||||||
chunk.Value = payload[2:chunk.Length]
|
chunk.Value = payload[2:chunk.Length]
|
||||||
|
|
||||||
case 0x3D, 0x40:
|
case 0x3D, 0x40:
|
||||||
chunk.Type = FMP_CHUNK_PATH_POP
|
chunk.Type = FmpChunkPathPop
|
||||||
chunk.Length = 1
|
chunk.Length = 1
|
||||||
|
|
||||||
case 0x80:
|
case 0x80:
|
||||||
chunk.Type = FMP_CHUNK_NOOP
|
chunk.Type = FmpChunkNoop
|
||||||
chunk.Length = 1
|
chunk.Length = 1
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -1,47 +1,63 @@
|
|||||||
package fmp
|
package fmp
|
||||||
|
|
||||||
func (ctx *FmpFile) Tables() []*FmpTable {
|
type FmpTable struct {
|
||||||
tables := make([]*FmpTable, 0)
|
ID uint64
|
||||||
|
Name string
|
||||||
|
Columns map[uint64]*FmpColumn
|
||||||
|
Records map[uint64]*FmpRecord
|
||||||
|
|
||||||
for key, ent := range *ctx.Dictionary {
|
lastRecordID uint64
|
||||||
if key != 3 {
|
}
|
||||||
continue
|
|
||||||
}
|
|
||||||
println("Found a 3")
|
|
||||||
|
|
||||||
for key, ent = range *ent.Children {
|
type FmpColumn struct {
|
||||||
if key != 16 {
|
Index uint64
|
||||||
continue
|
Name string
|
||||||
}
|
Type FmpFieldType
|
||||||
println("Found a 3.16")
|
DataType FmpDataType
|
||||||
|
StorageType FmpFieldStorageType
|
||||||
|
AutoEnter FmpAutoEnterOption
|
||||||
|
Repetitions uint8
|
||||||
|
Indexed bool
|
||||||
|
}
|
||||||
|
|
||||||
for key, ent = range *ent.Children {
|
type FmpRecord struct {
|
||||||
if key != 5 {
|
Table *FmpTable
|
||||||
continue
|
Index uint64
|
||||||
}
|
Values map[uint64]string
|
||||||
println("Found a 3.16.5")
|
}
|
||||||
|
|
||||||
for tablePath := range *ent.Children {
|
func (ctx *FmpFile) Table(name string) *FmpTable {
|
||||||
if key >= 128 {
|
for _, table := range ctx.tables {
|
||||||
continue
|
if table.Name == name {
|
||||||
}
|
return table
|
||||||
|
|
||||||
// Found a table!
|
|
||||||
println("Found a table at 3.16.5.", tablePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
// metaDict := ctx.Dictionary.get([]uint64{4, 1, 7})
|
}
|
||||||
// if metaDict == nil {
|
|
||||||
// return tables
|
func (t *FmpTable) Column(name string) *FmpColumn {
|
||||||
// }
|
for _, column := range t.Columns {
|
||||||
// for _, meta := range *metaDict.Children {
|
if column.Name == name {
|
||||||
// name := decodeByteSeq(meta.Children.get([]uint64{16}).Value)
|
return column
|
||||||
// table := &FmpTable{Name: name}
|
}
|
||||||
// tables = append(tables, table)
|
}
|
||||||
// }
|
return nil
|
||||||
|
}
|
||||||
return tables
|
|
||||||
|
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]
|
||||||
}
|
}
|
||||||
|
101
fmp/fmp_test.go
101
fmp/fmp_test.go
@ -1,17 +1,22 @@
|
|||||||
package fmp
|
package fmp
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestOpenFile(t *testing.T) {
|
func TestOpenFile(t *testing.T) {
|
||||||
f, err := OpenFile("../files/Untitled.fmp12")
|
f, err := OpenFile("../files/Untitled.fmp12")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if f.FileSize != 393216 {
|
defer f.Close()
|
||||||
t.Errorf("expected file size to be 393216, got %d", f.FileSize)
|
|
||||||
|
if f.FileSize != 229376 {
|
||||||
|
t.Errorf("expected file size to be 229376, got %d", f.FileSize)
|
||||||
}
|
}
|
||||||
if f.numSectors != 95 {
|
if f.numSectors != 55 {
|
||||||
t.Errorf("expected number of sectors to be 95, got %d", f.numSectors)
|
t.Errorf("expected number of sectors to be 55, got %d", f.numSectors)
|
||||||
}
|
}
|
||||||
if f.CreatorName != "Pro 12.0" {
|
if f.CreatorName != "Pro 12.0" {
|
||||||
t.Errorf("expected application name to be 'Pro 12.0', got '%s'", f.CreatorName)
|
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" {
|
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"))
|
t.Errorf("expected version date to be '2025-01-11', got '%s'", f.VersionDate.Format("2006-01-02"))
|
||||||
}
|
}
|
||||||
|
|
||||||
f.ToDebugFile("../private/output")
|
f.ToDebugFile("../private/output")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,16 +33,81 @@ func TestTables(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
tables := f.Tables()
|
defer f.Close()
|
||||||
|
|
||||||
if len(tables) != 1 || tables[0].Name != "Untitled" {
|
expectedNames := []string{"Untitled"}
|
||||||
tablesString := ""
|
tableNames := []string{}
|
||||||
for i, table := range tables {
|
for _, table := range f.tables {
|
||||||
tablesString += table.Name
|
tableNames = append(tableNames, table.Name)
|
||||||
if i < len(tables)-1 {
|
}
|
||||||
tablesString += ", "
|
if !slicesHaveSameElements(tableNames, expectedNames) {
|
||||||
}
|
t.Errorf("tables do not match")
|
||||||
}
|
}
|
||||||
t.Errorf("expected tables to be 'Untitled', got '%s'", tablesString)
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,13 @@
|
|||||||
package fmp
|
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
|
var length uint64
|
||||||
n := min(len(payload), 8) // clamp to uint64
|
n := min(len(payload), 8) // clamp to uint64
|
||||||
for i := range n {
|
for i := range n {
|
||||||
@ -10,10 +17,25 @@ func parseVarUint64(payload []byte) uint64 {
|
|||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeByteSeq(payload []byte) string {
|
func decodeString(payload []byte) string {
|
||||||
result := ""
|
result := ""
|
||||||
for i := range payload {
|
for i := range payload {
|
||||||
result += string(payload[i] ^ 0x5A)
|
result += string(payload[i] ^ 0x5A)
|
||||||
}
|
}
|
||||||
return result
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user