├── .gitignore ├── IDB-FORMAT.md ├── LICENSE ├── README.md ├── include └── idb3.hpp └── tests ├── test-idb3.cpp ├── unittestframework.h └── unittests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudio,c++ 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,c++ 4 | 5 | ### C++ ### 6 | # Prerequisites 7 | *.d 8 | 9 | # Compiled Object files 10 | *.slo 11 | *.lo 12 | *.o 13 | *.obj 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Linker files 20 | *.ilk 21 | 22 | # Debugger Files 23 | *.pdb 24 | 25 | # Compiled Dynamic libraries 26 | *.so 27 | *.dylib 28 | *.dll 29 | 30 | # Fortran module files 31 | *.mod 32 | *.smod 33 | 34 | # Compiled Static libraries 35 | *.lai 36 | *.la 37 | *.a 38 | *.lib 39 | 40 | # Executables 41 | *.exe 42 | *.out 43 | *.app 44 | 45 | ### VisualStudio ### 46 | ## Ignore Visual Studio temporary files, build results, and 47 | ## files generated by popular Visual Studio add-ons. 48 | ## 49 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 50 | 51 | # User-specific files 52 | *.rsuser 53 | *.suo 54 | *.user 55 | *.userosscache 56 | *.sln.docstates 57 | 58 | # User-specific files (MonoDevelop/Xamarin Studio) 59 | *.userprefs 60 | 61 | # Mono auto generated files 62 | mono_crash.* 63 | 64 | # Build results 65 | [Dd]ebug/ 66 | [Dd]ebugPublic/ 67 | [Rr]elease/ 68 | [Rr]eleases/ 69 | x64/ 70 | x86/ 71 | [Ww][Ii][Nn]32/ 72 | [Aa][Rr][Mm]/ 73 | [Aa][Rr][Mm]64/ 74 | bld/ 75 | [Bb]in/ 76 | [Oo]bj/ 77 | [Ll]og/ 78 | [Ll]ogs/ 79 | 80 | # Visual Studio 2015/2017 cache/options directory 81 | .vs/ 82 | # Uncomment if you have tasks that create the project's static files in wwwroot 83 | #wwwroot/ 84 | 85 | # Visual Studio 2017 auto generated files 86 | Generated\ Files/ 87 | 88 | # MSTest test Results 89 | [Tt]est[Rr]esult*/ 90 | [Bb]uild[Ll]og.* 91 | 92 | # NUnit 93 | *.VisualState.xml 94 | TestResult.xml 95 | nunit-*.xml 96 | 97 | # Build Results of an ATL Project 98 | [Dd]ebugPS/ 99 | [Rr]eleasePS/ 100 | dlldata.c 101 | 102 | # Benchmark Results 103 | BenchmarkDotNet.Artifacts/ 104 | 105 | # .NET Core 106 | project.lock.json 107 | project.fragment.lock.json 108 | artifacts/ 109 | 110 | # ASP.NET Scaffolding 111 | ScaffoldingReadMe.txt 112 | 113 | # StyleCop 114 | StyleCopReport.xml 115 | 116 | # Files built by Visual Studio 117 | *_i.c 118 | *_p.c 119 | *_h.h 120 | *.meta 121 | *.iobj 122 | *.ipdb 123 | *.pgc 124 | *.pgd 125 | *.rsp 126 | *.sbr 127 | *.tlb 128 | *.tli 129 | *.tlh 130 | *.tmp 131 | *.tmp_proj 132 | *_wpftmp.csproj 133 | *.log 134 | *.vspscc 135 | *.vssscc 136 | .builds 137 | *.pidb 138 | *.svclog 139 | *.scc 140 | 141 | # Chutzpah Test files 142 | _Chutzpah* 143 | 144 | # Visual C++ cache files 145 | ipch/ 146 | *.aps 147 | *.ncb 148 | *.opendb 149 | *.opensdf 150 | *.sdf 151 | *.cachefile 152 | *.VC.db 153 | *.VC.VC.opendb 154 | 155 | # Visual Studio profiler 156 | *.psess 157 | *.vsp 158 | *.vspx 159 | *.sap 160 | 161 | # Visual Studio Trace Files 162 | *.e2e 163 | 164 | # TFS 2012 Local Workspace 165 | $tf/ 166 | 167 | # Guidance Automation Toolkit 168 | *.gpState 169 | 170 | # ReSharper is a .NET coding add-in 171 | _ReSharper*/ 172 | *.[Rr]e[Ss]harper 173 | *.DotSettings.user 174 | 175 | # TeamCity is a build add-in 176 | _TeamCity* 177 | 178 | # DotCover is a Code Coverage Tool 179 | *.dotCover 180 | 181 | # AxoCover is a Code Coverage Tool 182 | .axoCover/* 183 | !.axoCover/settings.json 184 | 185 | # Coverlet is a free, cross platform Code Coverage Tool 186 | coverage*[.json, .xml, .info] 187 | 188 | # Visual Studio code coverage results 189 | *.coverage 190 | *.coveragexml 191 | 192 | # NCrunch 193 | _NCrunch_* 194 | .*crunch*.local.xml 195 | nCrunchTemp_* 196 | 197 | # MightyMoose 198 | *.mm.* 199 | AutoTest.Net/ 200 | 201 | # Web workbench (sass) 202 | .sass-cache/ 203 | 204 | # Installshield output folder 205 | [Ee]xpress/ 206 | 207 | # DocProject is a documentation generator add-in 208 | DocProject/buildhelp/ 209 | DocProject/Help/*.HxT 210 | DocProject/Help/*.HxC 211 | DocProject/Help/*.hhc 212 | DocProject/Help/*.hhk 213 | DocProject/Help/*.hhp 214 | DocProject/Help/Html2 215 | DocProject/Help/html 216 | 217 | # Click-Once directory 218 | publish/ 219 | 220 | # Publish Web Output 221 | *.[Pp]ublish.xml 222 | *.azurePubxml 223 | # Note: Comment the next line if you want to checkin your web deploy settings, 224 | # but database connection strings (with potential passwords) will be unencrypted 225 | *.pubxml 226 | *.publishproj 227 | 228 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 229 | # checkin your Azure Web App publish settings, but sensitive information contained 230 | # in these scripts will be unencrypted 231 | PublishScripts/ 232 | 233 | # NuGet Packages 234 | *.nupkg 235 | # NuGet Symbol Packages 236 | *.snupkg 237 | # The packages folder can be ignored because of Package Restore 238 | **/[Pp]ackages/* 239 | # except build/, which is used as an MSBuild target. 240 | !**/[Pp]ackages/build/ 241 | # Uncomment if necessary however generally it will be regenerated when needed 242 | #!**/[Pp]ackages/repositories.config 243 | # NuGet v3's project.json files produces more ignorable files 244 | *.nuget.props 245 | *.nuget.targets 246 | 247 | # Microsoft Azure Build Output 248 | csx/ 249 | *.build.csdef 250 | 251 | # Microsoft Azure Emulator 252 | ecf/ 253 | rcf/ 254 | 255 | # Windows Store app package directories and files 256 | AppPackages/ 257 | BundleArtifacts/ 258 | Package.StoreAssociation.xml 259 | _pkginfo.txt 260 | *.appx 261 | *.appxbundle 262 | *.appxupload 263 | 264 | # Visual Studio cache files 265 | # files ending in .cache can be ignored 266 | *.[Cc]ache 267 | # but keep track of directories ending in .cache 268 | !?*.[Cc]ache/ 269 | 270 | # Others 271 | ClientBin/ 272 | ~$* 273 | *~ 274 | *.dbmdl 275 | *.dbproj.schemaview 276 | *.jfm 277 | *.pfx 278 | *.publishsettings 279 | orleans.codegen.cs 280 | 281 | # Including strong name files can present a security risk 282 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 283 | #*.snk 284 | 285 | # Since there are multiple workflows, uncomment next line to ignore bower_components 286 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 287 | #bower_components/ 288 | 289 | # RIA/Silverlight projects 290 | Generated_Code/ 291 | 292 | # Backup & report files from converting an old project file 293 | # to a newer Visual Studio version. Backup files are not needed, 294 | # because we have git ;-) 295 | _UpgradeReport_Files/ 296 | Backup*/ 297 | UpgradeLog*.XML 298 | UpgradeLog*.htm 299 | ServiceFabricBackup/ 300 | *.rptproj.bak 301 | 302 | # SQL Server files 303 | *.mdf 304 | *.ldf 305 | *.ndf 306 | 307 | # Business Intelligence projects 308 | *.rdl.data 309 | *.bim.layout 310 | *.bim_*.settings 311 | *.rptproj.rsuser 312 | *- [Bb]ackup.rdl 313 | *- [Bb]ackup ([0-9]).rdl 314 | *- [Bb]ackup ([0-9][0-9]).rdl 315 | 316 | # Microsoft Fakes 317 | FakesAssemblies/ 318 | 319 | # GhostDoc plugin setting file 320 | *.GhostDoc.xml 321 | 322 | # Node.js Tools for Visual Studio 323 | .ntvs_analysis.dat 324 | node_modules/ 325 | 326 | # Visual Studio 6 build log 327 | *.plg 328 | 329 | # Visual Studio 6 workspace options file 330 | *.opt 331 | 332 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 333 | *.vbw 334 | 335 | # Visual Studio LightSwitch build output 336 | **/*.HTMLClient/GeneratedArtifacts 337 | **/*.DesktopClient/GeneratedArtifacts 338 | **/*.DesktopClient/ModelManifest.xml 339 | **/*.Server/GeneratedArtifacts 340 | **/*.Server/ModelManifest.xml 341 | _Pvt_Extensions 342 | 343 | # Paket dependency manager 344 | .paket/paket.exe 345 | paket-files/ 346 | 347 | # FAKE - F# Make 348 | .fake/ 349 | 350 | # CodeRush personal settings 351 | .cr/personal 352 | 353 | # Python Tools for Visual Studio (PTVS) 354 | __pycache__/ 355 | *.pyc 356 | 357 | # Cake - Uncomment if you are using it 358 | # tools/** 359 | # !tools/packages.config 360 | 361 | # Tabs Studio 362 | *.tss 363 | 364 | # Telerik's JustMock configuration file 365 | *.jmconfig 366 | 367 | # BizTalk build output 368 | *.btp.cs 369 | *.btm.cs 370 | *.odx.cs 371 | *.xsd.cs 372 | 373 | # OpenCover UI analysis results 374 | OpenCover/ 375 | 376 | # Azure Stream Analytics local run output 377 | ASALocalRun/ 378 | 379 | # MSBuild Binary and Structured Log 380 | *.binlog 381 | 382 | # NVidia Nsight GPU debugger configuration file 383 | *.nvuser 384 | 385 | # MFractors (Xamarin productivity tool) working folder 386 | .mfractor/ 387 | 388 | # Local History for Visual Studio 389 | .localhistory/ 390 | 391 | # BeatPulse healthcheck temp database 392 | healthchecksdb 393 | 394 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 395 | MigrationBackup/ 396 | 397 | # Ionide (cross platform F# VS Code tools) working folder 398 | .ionide/ 399 | 400 | # Fody - auto-generated XML schema 401 | FodyWeavers.xsd 402 | 403 | ### VisualStudio Patch ### 404 | # Additional files built by Visual Studio 405 | *.tlog 406 | 407 | # End of https://www.toptal.com/developers/gitignore/api/visualstudio,c++ 408 | -------------------------------------------------------------------------------- /IDB-FORMAT.md: -------------------------------------------------------------------------------- 1 | 2 | IDApro databases 3 | ================== 4 | 5 | An IDApro database consists of one large file which contains several sections. 6 | At the start of the `idb` or `i64` file there is a list of fileoffsets pointing 7 | to these sections. Sections can optionally be stored compressed. 8 | When a database is opened by IDA the sections are extracted from the main data file 9 | and stored in separate files. When you only need to read from the database, and don't 10 | want to change anything, the splitting into `id0`, `id1`, `nam` and `til` files is not 11 | nescesary, IDApro does this anyway, since it expect the user to make changes to the database. 12 | Very old IDApro versions ( v1.6 and v2.0 ) store the sections separately, such that 13 | there could only be one database per directory. 14 | 15 | | index | extension | contents | 16 | | :---- | :-------- | :------------------------- | 17 | | 0 | id0 | A btree key/value database | 18 | | 1 | id1 | Flags for each byte | 19 | | 2 | nam | A list of named offsets | 20 | | 3 | seg | Unknown | 21 | | 4 | til | Type information | 22 | | 5 | id2 | Unknown | 23 | 24 | Older ida versions don't have the id2 file. 25 | Newer ida versions don't have the seg file. 26 | Newer ida versions use 64 bit file offsets, so IDA can support files larger than 4GB. 27 | There is no difference in the IDB header between 32 bit ( with `.idb` extension ) and 64 bit ( with `.i64` extension ) databases. 28 | 29 | ## ID0 section. 30 | 31 | The ID0 sections contains a b-tree database, This is a single large key-value database, like leveldb. 32 | There are three main groups of key types: 33 | 34 | * Bookkeeping, so IDApro can quickly decide what the next free nodeid is. These keys all start with a '$' (dollar) sign. 35 | * `$ MAX LINK` 36 | * `$ MAX NODE` 37 | * `$ NET DESC` 38 | * Nodes, keys starting with a '.' (dot). 39 | * followed by an address, or internal nodeid. 40 | * 32 bit databases use 32 bit addresses, 64 bit databases use 64 bit addresses here. 41 | * internal nodeid's have the upper 8 bits set to one, 42 | so `0xFF000000` for a 32 bit database, or `0xFF00000000000000` for a 64 bit database. 43 | * a tag, `A` for altvals, `S` for supvals, etc. See netnode.hpp in the idasdk. 44 | * optionally followed by an index or hashkey value, depending on the tag. 45 | * both the address and index value are encoded in bigendian byte order. 46 | * Name index, keys starting with an `N`, followed by a name. 47 | The value being a 32 or 64 bit offset. 48 | * names up to 511 are encoded as plain strings. longer names start with a NUL byte, followed by a blob index. 49 | pointing to a blob at special nodeid `0xFF000000(00000000)`. 50 | * the maximum name length is 32 * 1024 characters. 51 | * Very old ida versions had keys starting with lowercase 'n', and '-' (minus). 52 | * The maximum key size if 512 bytes, including dots, 'N', etc. 53 | 54 | The range of internal nodeid's is the reason you cannot have code or data in 55 | your disassembly at addresses starting with `0xFF000000(00000000)`. IDA will allow you to 56 | create such segments manually. Doing so will usually result in corrupted databases. 57 | 58 | There are two types of names: 59 | * Internal, pointing to internal nodeid's. Examples: `$ structs`, `Root Node`. Most have a space in them. 60 | * Labels, pointing to addresses in the disassembly. 61 | 62 | The maximum value size is 1024 bytes. 63 | Several types of values: 64 | * Integers, encoded in little endian byte order. 65 | * Strings are sometimes NUL terminated, sometimes not. 66 | * In several cases structured information is stored in a _packed_ format, see below. 67 | 68 | ### packed values 69 | 70 | Packed values are used among others for structure and segment definitions. 71 | 72 | In packed data: 73 | * Values in the range 0x00-0x7f are stored in a single byte. 74 | * Values in the range 0x80-0x3fff are stored ORRED with 0x8000. 75 | * Values in the range 0x4000-0x1fffffff are stored ORRED with 0xC000000. 76 | * Larger 32 bit values are stored prefixed with a 0xFF byte. 77 | * 64 bit values are stored as two consecutive numbers. 78 | * All values are stored in big-endian byte order. 79 | 80 | 81 | ### The B-tree format 82 | 83 | The file is organised in 8kbyte pages, where the first page contains a header with 84 | pagesize, pointer to a list of free pages, pointer to the root of the page tree, 85 | the number of records, and number of pages. 86 | 87 | There are two types of pages, leaf pages, which don't contain pointers to other 88 | pages, but only key-value records. And index pages, with a _preceeding_ 89 | pointer, and where all key-value records contain a pointer to a page where all 90 | keys in the pointed-to page have values greater than the key containing the 91 | page pointer. This makes it very efficient to lookup records by key. 92 | 93 | The page tree looks like this. Between brackets are key values, the pointer marked 94 | with a `*` (STAR) is the _preceeding_ pointer. Values are not shown. 95 | 96 | 97 | *-------->[00] 98 | *------>[02]---+ [01] 99 | root ->[08]---+ [05]-+ | 100 | [17]-+ | | +--->[03] 101 | | | | [04] 102 | | | | 103 | | | +----->[06] 104 | | | [07] 105 | | | 106 | | | *-------->[09] 107 | | +->[11]---+ [10] 108 | | [14]-+ | 109 | | | +--->[12] 110 | | | [13] 111 | | | 112 | | +----->[15] 113 | | [16] 114 | | 115 | | *-------->[18] 116 | +--->[20]---+ [19] 117 | [23]-+ | 118 | | +--->[21] 119 | | [22] 120 | | 121 | +----->[24] 122 | [25] 123 | 124 | 125 | 126 | 127 | Each page has a small header, with a pointer to a preceeding page, and a record count. 128 | For Leaf pages the _preceeding_ pointer is zero. 129 | 130 | Following the header there is an index containing offsets to the actual records in the page, 131 | and a pointer to the next level index or leaf page. 132 | The records are stored as _keylength_, keydata, _datalength_, data. 133 | All records in the level below an index are guaranteed to have a key greater than the key 134 | in the index. 135 | 136 | In leaf pages consecutive entries will often have keys which are very similar. The index stores 137 | an offset into the key from which the keys differ, only the part that differs is stored. 138 | 139 | | key | binary representation | compressed key 140 | | :--------------------------------- | :---------------------- | :------------------ 141 | | ('.', 0xFF000002, 'N') | 2eff0000024e | (0, 2eff0000024e) 142 | | ('.', 0xFF000002, 'S', 0x00000001) | 2eff0000025300000001 | (5, 5300000001) 143 | | ('.', 0xFF000002, 'S', 0x00000002) | 2eff0000025300000002 | (9, 02) 144 | 145 | 146 | ## The ID1 section 147 | 148 | The ID1 section contains the flag values as returned by the idc `GetFlags` function. 149 | It starts with a list of file regions, followed by flags for each byte. 150 | 151 | 152 | ## Netnodes 153 | 154 | The highlevel view of the `ID0` database is that of netnodes, as partially documented 155 | in the idasdk. 156 | 157 | The most important nodes are: 158 | * `Root Node` 159 | * lists: `$ structs`, `$ enums`, `$ scripts` 160 | * the values in a list are stored in the altnodes of the list node. 161 | * the values are one more than the actual nodeid pointed to: 162 | a list pointing to struct id's 0xff000bf6, 0xff000c01 163 | would contain : 0xff000bf7, 0xff000c02 164 | * `$ funcs` 165 | * `$ fileregions`, `$ segs`, '$ srareas' 166 | * '$ entry points' 167 | 168 | 169 | ### structs 170 | 171 | The main struct node: 172 | 173 | | node | contents 174 | | :--- | :---- 175 | | (id, 'N') | the struct name 176 | | (id, 'M', 0) | packed member info, nodeids for members. 177 | 178 | 179 | The struct member nodes: 180 | 181 | | node | contents 182 | | :--- | :---- 183 | | (id, 'N') | the member name 184 | | (id, 'M', 0) | packed member info 185 | | (id, 'A', 3) | enum id 186 | | (id, 'A', 11) | struct id 187 | | (id, 'A', 16) | string type 188 | | (id, 'S', 0) | member comment 189 | | (id, 'S', 1) | repeatable member comment 190 | | (id, 'S', 9) | offset spec 191 | | (id, 'S', 0x3000) | typeinfo 192 | 193 | 194 | ### history 195 | 196 | The `$ curlocs` list contains several location histories: 197 | 198 | For example, the `$ IDA View-A` netnode contains the following keys: 199 | * `A 0` - highest history supval item 200 | * `A 1` - number of history items 201 | * `A 2` - object type: `idaplace_t` 202 | * `S ` - packed history item: itemlinenr, ea\_t, int, int, colnr, rownr 203 | 204 | 205 | 206 | ### normal addresses 207 | 208 | In the SDK, in the file `nalt.hpp` there are many more items defined. 209 | These are some of the regularly used ones. 210 | 211 | | key | value | description 212 | | :-- | :---- | :---------- 213 | | (addr, 'D', fromaddr) | reftype | data xref from 214 | | (addr, 'd', toaddr) | reftype | data xref to 215 | | (addr, 'X', fromaddr) | reftype | code xref from 216 | | (addr, 'x', toaddr) | reftype | code xref to 217 | | (addr, 'N') | string | global label 218 | | (addr, 'A', 1) | jumptableid+1 | jumptable target 219 | | (addr, 'A', 2) | nodeid+1 | hexrays info 220 | | (addr, 'A', 3) | structid+1 | data type 221 | | (addr, 'A', 8) | dword | additional flags 222 | | (addr, 'A', 0xB) | enumid+1 | first operand enum type 223 | | (addr, 'A', 0x10) | dword | string type 224 | | (addr, 'A', 0x11) | dword | align type 225 | | (addr, 'S', 0) | string | comment 226 | | (addr, 'S', 1) | string | repeatable comment 227 | | (addr, 'S', 4) | data | constant pool reference 228 | | (addr, 'S', 5) | data | array 229 | | (addr, 'S', 8) | data | jumptable info 230 | | (addr, 'S', 9) | packed | first operand offset spec 231 | | (addr, 'S', 0xA) | packed | second operand offset spec 232 | | (addr, 'S', 0x1B) | data | ? 233 | | (addr, 'S', 1000+linenr) | string | anterior comment 234 | | (addr, 'S', 0x1000) | packed | SP change point 235 | | (addr, 'S', 0x3000) | data | function prototype 236 | | (addr, 'S', 0x3001) | data | argument list 237 | | (addr, 'S', 0x4000+n) | packed blob | register renaming 238 | | (addr, 'S', 0x5000) | packed blob | function's local labels 239 | | (addr, 'S', 0x6000) | data | register args 240 | | (addr, 'S', 0x7000) | packed | function tails 241 | | (addr, 'S', 0x7000) | dword | tail backreference 242 | | 243 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Willem Hengeveld 4 | Copyright (c) 2021 Vitaly Maslov 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the Software), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, andor sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IDB3 LIBRARY 2 | Library for reading IDA Pro databases. 3 | 4 | ## About 5 | 6 | The header file `idb3.h` contains a library for reading from IDA Pro databases. 7 | 8 | ## File format 9 | 10 | [File format description](IDB-FORMAT.md) 11 | 12 | ## Library 13 | 14 | ### Types 15 | 16 | #### IDBFile 17 | 18 | Class for accessing sections of an `.idb` or `.i64` file. 19 | 20 | Constructor Parameters: 21 | 22 | * `std::shared_ptr` ( typedefed to `stream_ptr` ) 23 | 24 | Methods: 25 | 26 | * `stream_ptr getsection(int)` 27 | 28 | #### ID0File, ID1File, NAMFile 29 | 30 | Constructor Parameters: 31 | 32 | * `IDBFile& idb` 33 | * `stream_ptr` 34 | 35 | Constant 36 | 37 | * `INDEX` - the argument for `idb.getsection` 38 | 39 | #### ID0File 40 | 41 | Methods 42 | 43 | * `Cursor find(relation_t, nodeid, ...)` 44 | * `...` can be: 45 | * tag, index 46 | * tag, hash 47 | * tag 48 | * `Cursor find(relation_t, std::string key)` 49 | * `std::string blob(nodeid, tag, ...)` 50 | * `uint64_t node(std::string name)` 51 | 52 | * `bool is64bit()` 53 | * `true` for `.i64` files. 54 | 55 | * `uint64_t nodebase()` 56 | * return `0xFF000000(00000000)` for 32/64 bit databases. 57 | 58 | * `void enumlist(uint64_t nodeid, char tag, CB cb)` 59 | * call `cb` for each value in the list. 60 | 61 | Convenience Methods 62 | 63 | * `std::string getdata(ARGS...args)` 64 | * `std::string getstr(ARGS...args)` 65 | * `uint64_t getuint(ARGS...args)` 66 | * `uint64_t getuint(BtreeBase::Cursor& c)` 67 | * `std::string getname(uint64_t node)` 68 | 69 | #### ID1File 70 | 71 | Methods 72 | 73 | * `uint32_t GetFlags(uint64_t ea)` 74 | 75 | 76 | #### NAMFile 77 | 78 | Methods 79 | 80 | * `uint64_t findname(uint64_t ea)` 81 | 82 | 83 | #### Cursor 84 | 85 | Methods 86 | 87 | * `void next()` 88 | * move cursor to the next btree record 89 | * `void prev()` 90 | * move cursor to the previous btree record 91 | * `bool eof()` 92 | * did we reach the start/end of the btree? 93 | * `std::string getkey()` 94 | * return the key pointed to by the cursor 95 | * `std::string getval()` 96 | * return the value pointed to by the cursor 97 | 98 | ### Options 99 | 100 | Define `IDB_ZLIB_COMPRESSION_SUPPORT` to enable support compressed databases (requires `zlib` library). 101 | 102 | ### Example 103 | 104 | ```c++ 105 | #include 106 | 107 | IDBFile idb(std::make_shared("database.i64", ios::binary)); 108 | ID0File id0(idb, idb.getsection(ID0File::INDEX)); 109 | 110 | uint64_t loadernode = id0.node("$ loader name"); 111 | std::cout << "Loader:" << '\t' << '\t' 112 | << id0.getstr(loadernode, 'S', 0) << " - " 113 | << id0.getstr(loadernode, 'S', 1) << std::endl; 114 | 115 | uint64_t rootNode = id0.node("Root Node"); 116 | 117 | uint32_t version = id0.getuint(rootNode, 'A', -1); 118 | uint32_t crc = id0.getuint(rootNode, 'A', -5); 119 | time_t time = id0.getuint(rootNode, 'A', -2); 120 | std::string str_version = id0.getstr(rootNode, 'S', 1303); 121 | 122 | std::cout << "IDA Version:" << '\t' << version << "[" << str_version << "]" << std::endl 123 | << "Time:" << '\t' << '\t' << time << std::endl 124 | << "CRC:" << '\t' << '\t' << std::hex << crc << std::endl; 125 | ``` 126 | 127 | ## Credits 128 | 129 | The idb3 library was written by Willem Hengeveld , and uses in [idbutil](https://github.com/nlitsme/idbutil) project. It is distributed under the MIT License. 130 | 131 | ## Third party 132 | 133 | Using the [zlib library](https://zlib.net) for compressed databases. 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /include/idb3.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * idb3.hpp is a header-library for accessing IDA Pro databases. 3 | * 4 | * Author: Willem Hengeveld 5 | * Contributed: Vitaly Maslov 6 | * 7 | * Toplevel class: IDBFile, use getsection to get a stream to the desired section, 8 | * Then create an ID0File, ID1File, NAMFile for that section. 9 | 10 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 14 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 16 | * SOFTWARE. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #ifdef _WIN32 32 | #undef min 33 | #undef max 34 | #endif 35 | 36 | #ifdef IDB_ZLIB_COMPRESSION_SUPPORT 37 | #include 38 | #endif 39 | 40 | #ifdef _DEBUG 41 | #define dbgprint(...) printf(__VA_ARGS__) 42 | #else 43 | #define dbgprint(...) 44 | #endif 45 | 46 | // common types 47 | typedef std::vector bytes_t; 48 | 49 | // a sharedptr, so i can pass an istream around without 50 | // worrying about who owns it. 51 | typedef std::shared_ptr stream_ptr; 52 | typedef std::shared_ptr bytes_ptr; 53 | 54 | // memory stream from memory 55 | class imemstream : std::streambuf, public std::istream { 56 | std::streampos _size; 57 | public: 58 | imemstream(uint8_t* base, size_t size) : _size(size), 59 | std::istream(static_cast(this)) 60 | { 61 | char* p(reinterpret_cast(base)); 62 | this->setg(p, p, p + size); 63 | } 64 | protected: 65 | std::streampos seekoff(std::streamoff off, std::ios_base::seekdir dir, 66 | std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) 67 | { 68 | if (dir == std::ios_base::cur) 69 | setg(eback(), gptr() + off, egptr()); 70 | else if (dir == std::ios_base::end) 71 | setg(eback(), egptr() + off, egptr()); 72 | else if (dir == std::ios_base::beg) 73 | setg(eback(), eback() + off, egptr()); 74 | return gptr() - eback(); 75 | } 76 | std::streampos seekpos(std::streampos sp, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) 77 | { 78 | if (sp < 0 || sp > _size) 79 | return -1; 80 | return seekoff(sp, std::ios_base::beg, which); 81 | } 82 | }; 83 | 84 | // simple hex printer 85 | template 86 | std::string str_hex(const T& value) 87 | { 88 | std::stringstream str; 89 | str << std::hex << std::setfill('0') << std::setw(sizeof(T::value_type) * 2);; 90 | for (const auto& val : value) 91 | { 92 | if (sizeof(T::value_type) == 1) 93 | str << static_cast(val); 94 | else 95 | str << val; 96 | } 97 | return str.str(); 98 | } 99 | 100 | #ifdef IDB_ZLIB_COMPRESSION_SUPPORT 101 | 102 | // zlib rec 103 | #define CHUNK 16384 104 | 105 | int zlib_decompress(stream_ptr& source, uint64_t size, bytes_ptr& dest) 106 | { 107 | int ret; 108 | unsigned have; 109 | z_stream strm; 110 | uint8_t in[CHUNK]; 111 | uint8_t out[CHUNK]; 112 | uint32_t count = 0; 113 | uint64_t position = 0; 114 | 115 | strm.zalloc = Z_NULL; 116 | strm.zfree = Z_NULL; 117 | strm.opaque = Z_NULL; 118 | strm.avail_in = 0; 119 | strm.next_in = Z_NULL; 120 | 121 | ret = inflateInit(&strm); 122 | if (ret != Z_OK) return ret; 123 | 124 | do 125 | { 126 | if (position >= size) 127 | break; 128 | 129 | count = static_cast(std::min(uint64_t(CHUNK), size - position)); 130 | source->read(reinterpret_cast(in), count); 131 | position += count; 132 | 133 | strm.next_in = in; 134 | strm.avail_in = count; 135 | 136 | do 137 | { 138 | strm.avail_out = CHUNK; 139 | strm.next_out = out; 140 | ret = inflate(&strm, Z_NO_FLUSH); 141 | 142 | if (ret == Z_STREAM_ERROR) 143 | { 144 | inflateEnd(&strm); 145 | throw std::exception("zlib decompression fail"); 146 | } 147 | 148 | switch (ret) 149 | { 150 | case Z_NEED_DICT: 151 | ret = Z_DATA_ERROR; 152 | case Z_DATA_ERROR: 153 | case Z_MEM_ERROR: 154 | inflateEnd(&strm); 155 | return ret; 156 | } 157 | have = CHUNK - strm.avail_out; 158 | dest->insert(dest->end(), out, out + have); 159 | 160 | } while (strm.avail_out == 0); 161 | } 162 | while (ret != Z_STREAM_END); 163 | 164 | inflateEnd(&strm); 165 | return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; 166 | } 167 | 168 | #endif 169 | 170 | // create vector from `n` invocations of `f` 171 | template 172 | std::vector getvec(int n, FN f) 173 | { 174 | std::vector v; 175 | while (n--) 176 | v.push_back(f()); 177 | return v; 178 | } 179 | 180 | //////////////////////////////////////////////////////////////////////// 181 | // Sometimes i need to pass backinserter iterators as pair 182 | // These functions make that possible. 183 | 184 | // ANY - backinserter == INT_MAX -> always enough space after a backinserter 185 | template 186 | int operator-(T lhs, typename std::back_insert_iterator rhs) 187 | { 188 | return INT_MAX; 189 | } 190 | 191 | // backinserter += INT -> does nothing 192 | template 193 | typename std::back_insert_iterator& operator+=(typename std::back_insert_iterator& rhs, int n) 194 | { 195 | return rhs; 196 | } 197 | 198 | 199 | 200 | // streamhelper: get little/big endian integers of various sizes from a stream 201 | // There are functions for 8, 16, 32, 64 bit little/big endian unsigned integers. 202 | // And a function for reading a database dependent word (64bit for .i64, 32bit for .idb) 203 | template 204 | class streamhelper { 205 | ISPTR _is; 206 | int _wordsize; // the wordsize of the current database 207 | public: 208 | streamhelper(ISPTR is, int wordsize) 209 | : _is(is), _wordsize(wordsize) 210 | { 211 | _is->exceptions(std::istream::failbit | std::istream::badbit); 212 | } 213 | uint8_t get8() 214 | { 215 | auto c = _is->get(); 216 | if (c==-1) 217 | throw std::exception("EOF"); 218 | return (uint8_t)c; 219 | } 220 | uint16_t get16le() 221 | { 222 | uint8_t lo = get8(); 223 | uint8_t hi = get8(); 224 | return (hi<<8) | lo; 225 | } 226 | uint16_t get16be() 227 | { 228 | uint8_t hi = get8(); 229 | uint8_t lo = get8(); 230 | return (hi<<8) | lo; 231 | } 232 | 233 | uint32_t get32le() 234 | { 235 | uint16_t lo = get16le(); 236 | uint16_t hi = get16le(); 237 | return (hi<<16) | lo; 238 | } 239 | uint32_t get32be() 240 | { 241 | uint16_t hi = get16be(); 242 | uint16_t lo = get16be(); 243 | return (hi<<16) | lo; 244 | } 245 | uint64_t get64le() 246 | { 247 | uint32_t lo = get32le(); 248 | uint32_t hi = get32le(); 249 | return (uint64_t(hi)<<32) | lo; 250 | } 251 | uint64_t get64be() 252 | { 253 | uint32_t hi = get32be(); 254 | uint32_t lo = get32be(); 255 | return (uint64_t(hi)<<32) | lo; 256 | } 257 | 258 | 259 | // function used to get the right wordsize for either .i64 or .idb file. 260 | uint64_t getword() 261 | { 262 | if (_wordsize==4) 263 | return get32le(); 264 | else if (_wordsize==8) 265 | return get64le(); 266 | throw std::exception("unsupported wordsize"); 267 | } 268 | std::string getdata(int n) 269 | { 270 | std::string str(n, char(0)); 271 | auto m = _is->readsome(&str.front(), n); 272 | str.resize(m); 273 | dbgprint("getdata -> %s\n", str_hex(str).c_str()); 274 | return str; 275 | } 276 | void seekg( std::istream::off_type off, std::ios_base::seekdir dir) 277 | { 278 | _is->seekg(off, dir); 279 | } 280 | void seekg( std::istream::pos_type pos ) 281 | { 282 | _is->seekg(pos); 283 | } 284 | }; 285 | 286 | // function for creating a streamhelper. 287 | template 288 | auto makehelper(ISPTR is, int wordsize = 0) 289 | { 290 | return streamhelper(is, wordsize); 291 | } 292 | 293 | 294 | // EndianTools: a collection of static functions for reading/writing 295 | // little/big endian integers of various sizes from an iterator range 296 | struct EndianTools { 297 | template 298 | static void set8(P first, P last, uint8_t w) 299 | { 300 | if (last - first < 1) 301 | throw std::exception("not enough space"); 302 | *first = w; 303 | } 304 | template 305 | static void setbe16(P first, P last, T w) 306 | { 307 | P p = first; 308 | if (last - p < 2) 309 | throw std::exception("not enough space"); 310 | set8(p, last, w >> 8); p += 1; 311 | set8(p, last, w); 312 | } 313 | template 314 | static void setbe32(P first, P last, T w) 315 | { 316 | P p = first; 317 | if (last - p < 4) 318 | throw std::exception("not enough space"); 319 | setbe16(p, last, w >> 16); p += 2; 320 | setbe16(p, last, w); 321 | } 322 | template 323 | static void setbe64(P first, P last, T w) 324 | { 325 | P p = first; 326 | if (last - p < 8) 327 | throw std::exception("not enough space"); 328 | setbe32(p, last, w >> 32); p += 4; 329 | setbe32(p, last, w); 330 | } 331 | template 332 | static void setle16(P first, P last, T w) 333 | { 334 | P p = first; 335 | if (last - p < 2) 336 | throw std::exception("not enough space"); 337 | set8(p, last, w); p += 1; 338 | set8(p, last, w >> 8); 339 | } 340 | template 341 | static void setle32(P first, P last, T w) 342 | { 343 | P p = first; 344 | if (last - p < 4) 345 | throw std::exception("not enough space"); 346 | setle16(p, last, w); p += 2; 347 | setle16(p, last, w>>16); 348 | } 349 | template 350 | static void setle64(P first, P last, T w) 351 | { 352 | P p = first; 353 | if (last - p < 8) 354 | throw std::exception("not enough space"); 355 | setle32(p, last, w); p += 4; 356 | setle32(p, last, w>>32); 357 | } 358 | 359 | template 360 | static uint8_t get8(P first, P last) 361 | { 362 | if (first >= last) 363 | throw std::exception("not enough space"); 364 | return *first; 365 | } 366 | 367 | template 368 | static uint16_t getbe16(P first, P last) 369 | { 370 | P p = first; 371 | if (last - p < 2) 372 | throw std::exception("not enough space"); 373 | uint8_t hi = get8(p, last); p += 1; 374 | uint8_t lo = get8(p, last); 375 | 376 | return (uint16_t(hi)<<8) | lo; 377 | } 378 | template 379 | static uint32_t getbe32(P first, P last) 380 | { 381 | P p = first; 382 | if (last - p < 4) 383 | throw std::exception("not enough space"); 384 | uint16_t hi =getbe16(p, last); p += 2; 385 | uint16_t lo =getbe16(p, last); 386 | 387 | return (uint32_t(hi)<<16) | lo; 388 | } 389 | template 390 | static uint64_t getbe64(P first, P last) 391 | { 392 | P p = first; 393 | if (last - p < 8) 394 | throw std::exception("not enough space"); 395 | uint32_t hi =getbe32(p, last); p += 4; 396 | uint32_t lo =getbe32(p, last); 397 | 398 | return (uint64_t(hi)<<32) | lo; 399 | } 400 | template 401 | static uint16_t getle16(P first, P last) 402 | { 403 | P p = first; 404 | if (last - p < 2) 405 | throw std::exception("not enough space"); 406 | uint8_t lo = get8(p, last); p += 1; 407 | uint8_t hi = get8(p, last); 408 | 409 | return (uint16_t(hi)<<8) | lo; 410 | } 411 | template 412 | static uint32_t getle32(P first, P last) 413 | { 414 | P p = first; 415 | if (last - p < 4) 416 | throw std::exception("not enough space"); 417 | uint16_t lo = getle16(p, last); p += 2; 418 | uint16_t hi = getle16(p, last); 419 | 420 | return (uint32_t(hi)<<16) | lo; 421 | } 422 | template 423 | static uint64_t getle64(P first, P last) 424 | { 425 | P p = first; 426 | if (last - p < 8) 427 | throw std::exception("not enough space"); 428 | uint32_t lo =getle32(p, last); p += 4; 429 | uint32_t hi =getle32(p, last); 430 | 431 | return (uint64_t(hi)<<32) | lo; 432 | } 433 | 434 | }; 435 | 436 | // stream buffer for sectionstream 437 | // This is the class doing the actual work for sectionstream. 438 | // This presents a view of a section of a random access stream. 439 | class sectionbuffer : public std::streambuf { 440 | stream_ptr _is; 441 | 442 | std::streamoff _first; 443 | std::streamoff _last; 444 | 445 | std::streampos _curpos; 446 | 447 | public: 448 | sectionbuffer(stream_ptr is, uint64_t first, uint64_t last) 449 | : _is(is), _first(first), _last(last), _curpos(0) 450 | { 451 | _is->seekg(_first); 452 | } 453 | protected: 454 | std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) 455 | { 456 | std::streampos newpos; 457 | switch(way) 458 | { 459 | case std::ios_base::beg: 460 | newpos = off; 461 | break; 462 | case std::ios_base::cur: 463 | newpos = _curpos + off; 464 | break; 465 | case std::ios_base::end: 466 | newpos = (_last-_first) + off; 467 | break; 468 | default: 469 | throw std::ios_base::failure("bad seek direction"); 470 | } 471 | return seekpos(newpos, which); 472 | } 473 | 474 | std::streampos seekpos(std::streampos sp, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) 475 | { 476 | if (sp<0 || sp > (_last-_first)) 477 | return -1; 478 | _is->seekg(sp+_first); 479 | return _curpos = sp; 480 | } 481 | std::streamsize showmanyc() 482 | { 483 | return (_last-_first)-_curpos; 484 | } 485 | std::streamsize xsgetn(char_type* s, std::streamsize n) 486 | { 487 | if (n<=0 || _curpos >= (_last-_first)) 488 | return 0; 489 | auto want = std::min(std::streamsize(_last-_first - _curpos), n); 490 | //auto got = _is->readsome(s, want); 491 | _is->read(s, want); auto got = want; 492 | _curpos += got; 493 | 494 | return got; 495 | } 496 | int_type underflow() 497 | { 498 | if (_curpos >= (_last-_first)) 499 | return traits_type::eof(); 500 | int r = _is->peek(); 501 | return r; 502 | } 503 | int_type uflow() 504 | { 505 | if (_curpos >= (_last-_first)) 506 | return traits_type::eof(); 507 | int r = _is->get(); 508 | if (r==traits_type::eof()) 509 | return traits_type::eof(); 510 | _curpos+=1; 511 | return r; 512 | } 513 | friend class sectionstream; 514 | }; 515 | // istream restricted to a section of a seakable stream 516 | class sectionstream : public std::istream { 517 | sectionbuffer _buf; 518 | public: 519 | template 520 | sectionstream(ISPTR is, uint64_t from, uint64_t size) 521 | : std::istream(nullptr), _buf(is, from, from+size) 522 | { 523 | init(&_buf); 524 | } 525 | }; 526 | 527 | /////////////////////////////////////////////////////////////// 528 | // read .idb file, returns sectionstreams for sections 529 | // 530 | // IDBFile knows how to read sections from all types of IDApro databases. 531 | // 532 | // Compression is not yet supported. 533 | class IDBFile { 534 | stream_ptr _is; 535 | uint32_t _magic; 536 | int _fileversion; 537 | std::vector _offsets; 538 | std::vector _checksums; 539 | std::unordered_map _buffer; 540 | public: 541 | enum { 542 | MAGIC_IDA2 = 0x32414449, 543 | MAGIC_IDA1 = 0x31414449, 544 | MAGIC_IDA0 = 0x30414449, 545 | }; 546 | IDBFile(stream_ptr is) 547 | : _is(is), _magic(0), _fileversion(-1) 548 | { 549 | readheader(); 550 | } 551 | uint32_t magic() const { return _magic; } 552 | 553 | void readheader() 554 | { 555 | auto s = makehelper(_is); 556 | _magic = s.get32le(); 557 | /*zero = */ s.get16le(); 558 | auto values = getvec(6, [&](){ return s.get32le(); }); 559 | if (values[5]!=0xaabbccdd) { 560 | _fileversion = 0; 561 | for (auto v : values) 562 | _offsets.push_back(v); 563 | _offsets[5] = 0; 564 | _checksums.resize(6); 565 | return; 566 | } 567 | _fileversion = s.get16le(); 568 | 569 | if (_fileversion < 5) { 570 | /*auto unknown =*/ s.get32le(); 571 | for (auto v : values) 572 | _offsets.push_back(v); 573 | _offsets.pop_back(); 574 | 575 | _checksums = getvec(5, [&](){ return s.get32le(); }); 576 | uint32_t idsofs = s.get32le(); 577 | uint32_t idscheck = _fileversion==1 ? s.get16le() : s.get32le(); 578 | _offsets.push_back(idsofs); 579 | _checksums.push_back(idscheck); 580 | 581 | // in filever==4 there is more in the .idb header: 582 | // 0x5c, 0, 0, , 128*NUL 583 | } 584 | else { 585 | // ver 5, 6 : 64 bit fileptrs 586 | _offsets.push_back((uint64_t(values[1])<<32)|values[0]); 587 | _offsets.push_back((uint64_t(values[3])<<32)|values[2]); 588 | _offsets.push_back(s.get64le()); 589 | _offsets.push_back(s.get64le()); 590 | _offsets.push_back(s.get64le()); 591 | _checksums = getvec(5, [&](){ return s.get32le(); }); 592 | _offsets.push_back(s.get64le()); 593 | _checksums.push_back(s.get32le()); 594 | 595 | // more data in the .idb header: 596 | // 0x7c, 0, 0, , 128*NUL 597 | } 598 | } 599 | void dump() 600 | { 601 | printf("IDB v%d, m=%08x\n", _fileversion, _magic); 602 | for (unsigned int i=0 ; iseekg(_offsets[i]); 608 | auto s = makehelper(_is); 609 | auto comp = s.get8(); 610 | uint64_t size = _fileversion<5 ? s.get32le() : s.get64le(); 611 | uint64_t ofs = _offsets[i] + (_fileversion<5 ? 5 : 9); 612 | 613 | return std::make_tuple(comp, ofs, size); 614 | } 615 | 616 | stream_ptr getsection(int i) 617 | { 618 | auto info = getinfo(i); 619 | 620 | uint8_t comp = std::get<0>(info); 621 | uint64_t off = std::get<1>(info); 622 | uint64_t size = std::get<2>(info); 623 | 624 | if (comp == 2) 625 | { 626 | #ifdef IDB_ZLIB_COMPRESSION_SUPPORT 627 | auto buffer = get_buffer(i); 628 | 629 | _is->seekg(off, std::ios::beg); 630 | if (zlib_decompress(_is, size, buffer) != Z_OK) 631 | throw std::exception("decompression fail"); 632 | 633 | return std::make_shared(buffer->data(), buffer->size()); 634 | #else 635 | throw std::exception("compression not supported"); 636 | #endif 637 | } 638 | else if (comp == 0) 639 | { 640 | return std::make_shared(_is, off, size); 641 | } 642 | else 643 | throw std::exception("unsupported section encoding"); 644 | } 645 | 646 | // Flushes the uncompressed section buffers 647 | void flush() 648 | { 649 | for (auto buf : _buffer) 650 | buf.second->clear(); 651 | } 652 | private: 653 | bytes_ptr get_buffer(int i) 654 | { 655 | if (_buffer.find(i) == _buffer.end()) 656 | _buffer[i] = std::make_shared(); 657 | return _buffer[i]; 658 | } 659 | }; 660 | 661 | // search relation 662 | enum relation_t { 663 | REL_LESS, 664 | REL_LESS_EQUAL, 665 | REL_EQUAL, 666 | REL_GREATER_EQUAL, 667 | REL_GREATER, 668 | REL_RECURSE, 669 | }; 670 | 671 | // baseclass for Btree Pages 672 | // baseclass for Btree database, subclassed by v1.5, v1.6, v2.0 673 | class BasePage { 674 | protected: 675 | stream_ptr _is; 676 | int _pagesize; 677 | uint32_t _nr; 678 | uint32_t _preceeding; 679 | int _count; 680 | 681 | // item for the entry table 682 | class Entry { 683 | public: 684 | uint32_t pagenr; 685 | int indent; 686 | int recofs; 687 | 688 | Entry() : pagenr(0), indent(0), recofs(0) { } 689 | Entry(Entry&& e) : pagenr(e.pagenr), indent(e.indent), recofs(e.recofs) { } 690 | }; 691 | std::vector _index; 692 | std::vector _keys; // only for leaf pages 693 | 694 | // IntIter, used to be able to use upper_bound on `_index` 695 | class IntIter : public std::iterator { 696 | int _ix; 697 | public: 698 | IntIter(int x) : _ix(x) { } 699 | IntIter() : _ix(0) { } 700 | IntIter(const IntIter& i) : _ix(i._ix) { } 701 | 702 | bool operator==(const IntIter& rhs) {return _ix==rhs._ix;} 703 | bool operator!=(const IntIter& rhs) {return _ix!=rhs._ix;} 704 | 705 | int operator*() const {return _ix;} 706 | int operator[](int i) {return _ix+i;} 707 | 708 | IntIter& operator++() {++_ix;return *this;} 709 | IntIter operator++(int) {IntIter tmp(*this); operator++(); return tmp;} 710 | IntIter& operator--() {--_ix;return *this;} 711 | IntIter operator--(int) {IntIter tmp(*this); operator--(); return tmp;} 712 | 713 | IntIter& operator+=(int n) { _ix += n; return *this; } 714 | IntIter& operator-=(int n) { _ix -= n; return *this; } 715 | 716 | friend IntIter operator+(int n, IntIter p) { return p+=n; } 717 | friend IntIter operator+(IntIter p, int n) { return p+=n; } 718 | friend IntIter operator-(IntIter p, int n) { return p-=n; } 719 | friend int operator-(const IntIter& p, const IntIter& q) { return p._ix-q._ix; } 720 | 721 | bool operator<(const IntIter& rhs) { return _ix(const IntIter& rhs) { return _ix>rhs._ix; } 724 | bool operator>=(const IntIter& rhs) { return _ix>=rhs._ix; } 725 | 726 | }; 727 | 728 | // unused, iterator returning Entry's 729 | class PageIter : public std::iterator { 730 | BasePage* _page; 731 | int _ix; 732 | public: 733 | PageIter(BasePage*page, int ix) : _page(page), _ix(ix) { } 734 | PageIter() : _page(nullptr), _ix(0) { } 735 | PageIter(const PageIter& i) : _page(i._page), _ix(i._ix) { } 736 | 737 | bool operator==(const PageIter& rhs) {return _ix==rhs._ix;} 738 | bool operator!=(const PageIter& rhs) {return _ix!=rhs._ix;} 739 | 740 | Entry& operator*() {return _page->getent(_ix);} 741 | Entry& operator[](int i) {return _page->getent(_ix+i);} 742 | 743 | PageIter& operator++() {++_ix;return *this;} 744 | PageIter operator++(int) {PageIter tmp(*this); operator++(); return tmp;} 745 | PageIter& operator--() {--_ix;return *this;} 746 | PageIter operator--(int) {PageIter tmp(*this); operator--(); return tmp;} 747 | 748 | PageIter& operator+=(int n) { _ix += n; return *this; } 749 | PageIter& operator-=(int n) { _ix -= n; return *this; } 750 | 751 | friend PageIter operator+(int n, PageIter p) { return p+=n; } 752 | friend PageIter operator+(PageIter p, int n) { return p+=n; } 753 | friend PageIter operator-(PageIter p, int n) { return p-=n; } 754 | friend int operator-(const PageIter& p, const PageIter& q) { return p._ix-q._ix; } 755 | 756 | bool operator<(const PageIter& rhs) { return _ix(const PageIter& rhs) { return _ix>rhs._ix; } 759 | bool operator>=(const PageIter& rhs) { return _ix>=rhs._ix; } 760 | }; 761 | 762 | public: 763 | BasePage(stream_ptr is, uint32_t nr, int pagesize) 764 | : _is(is), _pagesize(pagesize), _nr(nr), _preceeding(0), _count(0) 765 | { 766 | } 767 | virtual ~BasePage() {} 768 | uint32_t nr() const { return _nr; } 769 | 770 | bool isindex() const { return _preceeding!=0; } 771 | bool isleaf() const { return _preceeding==0; } 772 | 773 | size_t indexsize() const { return _index.size(); } 774 | 775 | virtual Entry readent() = 0; 776 | 777 | void dump() 778 | { 779 | if (_preceeding) 780 | printf("prec = %05x\n", _preceeding); 781 | for (unsigned int i=0 ; i<_index.size() ; i++) 782 | printf("%s = %s\n", 783 | str_hex(getkey(i)).c_str(), 784 | str_hex(getval(i)).c_str()); 785 | } 786 | 787 | void readindex() 788 | { 789 | for (int i=0 ; i<_count ; i++) 790 | _index.emplace_back(readent()); 791 | //print("got %d entries\n", _index.size()); 792 | 793 | if (isleaf()) 794 | readkeys(); 795 | } 796 | // for a leafpage, calculate all key values 797 | void readkeys() 798 | { 799 | auto s = makehelper(_is); 800 | std::string key; 801 | for (auto & ent : _index) { 802 | _is->seekg(ent.recofs); 803 | int klen = s.get16le(); 804 | key.resize(klen+ent.indent); 805 | _is->read(&key[ent.indent], klen); 806 | 807 | dbgprint("key i=%d, l=%d -> %s\n", ent.indent, klen, str_hex(key).c_str()); 808 | _keys.push_back(key); 809 | } 810 | } 811 | 812 | // get the subpage for the item at positon `i` 813 | uint32_t getpage(int i) const 814 | { 815 | if (!isindex()) 816 | throw std::exception("getpage called on leaf"); 817 | if (i < 0) 818 | return _preceeding; 819 | if (i >= _index.size()) 820 | { 821 | printf("#%06x i=%d, max=%d\n", _nr, i, _index.size()); 822 | throw std::exception("page: i too large"); 823 | } 824 | return _index[i].pagenr; 825 | } 826 | 827 | // get key for the item at position `i` 828 | std::string getkey(int i) 829 | { 830 | auto& ent = getent(i); 831 | if (isindex()) 832 | { 833 | _is->seekg(ent.recofs); 834 | auto s = makehelper(_is); 835 | int klen = s.get16le(); 836 | 837 | dbgprint("indexkey(%d) -> l=%d\n", i, klen); 838 | return s.getdata(klen); 839 | } 840 | else if (isleaf()) { 841 | dbgprint("leafkey(%d)\n", i); 842 | return _keys[i]; 843 | } 844 | throw std::exception("not a leaf of index"); 845 | } 846 | // get value for the item at position `i` 847 | std::string getval(int i) 848 | { 849 | auto& ent = getent(i); 850 | _is->seekg(ent.recofs); 851 | auto s = makehelper(_is); 852 | int klen = s.get16le(); 853 | _is->seekg(klen, std::ios_base::cur); 854 | int vlen = s.get16le(); 855 | 856 | dbgprint("%04x: val(%d), kl=%d, vl=%d\n", ent.recofs, i, klen, vlen); 857 | return s.getdata(vlen); 858 | } 859 | 860 | Entry& getent(int i) 861 | { 862 | if (i < 0 || i >= _index.size()) 863 | throw std::exception("invalid key index"); 864 | 865 | return _index[i]; 866 | } 867 | 868 | // unused 869 | //auto begin() { return PageIter(this, 0); } 870 | //auto end() { return PageIter(this, _count); } 871 | 872 | struct result { 873 | relation_t act; 874 | int index; 875 | 876 | bool operator==(const result& rhs) const { return act==rhs.act && index==rhs.index; } 877 | bool operator!=(const result& rhs) const { return !(*this==rhs); } 878 | 879 | friend std::ostream& operator<<(std::ostream& os, const result& res) 880 | { 881 | os << '{'; 882 | switch(res.act) 883 | { 884 | case REL_LESS: os << "<"; break; 885 | case REL_LESS_EQUAL: os << "<="; break; 886 | case REL_EQUAL: os << "=="; break; 887 | case REL_GREATER_EQUAL: os << ">="; break; 888 | case REL_GREATER: os << ">"; break; 889 | case REL_RECURSE: os << "r"; break; 890 | default: os << "?"; 891 | } 892 | os << res.index; 893 | os << '}'; 894 | return os; 895 | } 896 | }; 897 | 898 | // search for the key in this page. 899 | // getkey(index) ... act ... key 900 | result find(const std::string& key) 901 | { 902 | //auto i = std::upper_bound(begin(), end(), key, [](const std::string& key, const Entry& ent){ return false; }); 903 | auto i = std::upper_bound(IntIter(0), IntIter(_count), key, [this](const std::string& key, int ix){ return key < this->getkey(ix); }); 904 | 905 | if (i==IntIter(0)) { 906 | if (isindex()) 907 | return {REL_RECURSE, -1}; 908 | return {REL_GREATER, 0}; // index[0] > key 909 | } 910 | --i; 911 | int ix = i-IntIter(0); 912 | if (getkey(ix) == key) 913 | return {REL_EQUAL, ix}; 914 | if (isindex()) 915 | return {REL_RECURSE, ix}; 916 | return {REL_LESS, ix}; // index[ix] < key 917 | } 918 | }; 919 | typedef std::shared_ptr Page_ptr; 920 | 921 | 922 | // baseclass for Btree database, subclassed by v1.5, v1.6, v2.0 923 | class BtreeBase { 924 | protected: 925 | stream_ptr _is; 926 | uint32_t _firstindex; 927 | uint32_t _pagesize; 928 | uint32_t _firstfree; 929 | uint32_t _reccount; 930 | uint32_t _pagecount; 931 | public: 932 | class Cursor { 933 | BtreeBase *_bt; 934 | struct ent { 935 | Page_ptr page; 936 | int index; 937 | 938 | ent(Page_ptr page, int index) 939 | : page(page), index(index) 940 | { 941 | } 942 | ent() : index(0) { } 943 | 944 | bool operator==(const ent& rhs) const { return page==rhs.page && index==rhs.index; } 945 | bool operator!=(const ent& rhs) const { return !(*this==rhs); } 946 | }; 947 | std::vector _stack; 948 | 949 | void dump() const { 950 | std::stringstream x; 951 | for (auto& ent : _stack) 952 | { 953 | char buf[24] = { 0 }; 954 | snprintf(buf, sizeof(buf), " %05x:%d", ent.page->nr(), ent.index); 955 | x << buf; 956 | } 957 | std::cout << x.str() << std::endl; 958 | } 959 | public: 960 | Cursor(BtreeBase *bt) 961 | : _bt(bt) 962 | { 963 | } 964 | 965 | void clear() 966 | { 967 | _stack.clear(); 968 | } 969 | void next() 970 | { 971 | if (eof()) 972 | throw std::exception("cursor:EOF"); 973 | auto ent = _stack.back(); _stack.pop_back(); 974 | if (ent.page->isleaf()) { 975 | // from leaf move towards root 976 | ent.index++; 977 | while (!_stack.empty() && ent.index==ent.page->indexsize()) { 978 | ent = _stack.back(); _stack.pop_back(); 979 | ent.index++; 980 | } 981 | if (ent.indexindexsize()) { 982 | add(ent.page, ent.index); 983 | } 984 | } 985 | else { 986 | // from node move towards leaf 987 | add(ent.page, ent.index); 988 | ent.page = _bt->readpage(ent.page->getpage(ent.index)); 989 | while (ent.page->isindex()) { 990 | ent.index = -1; 991 | add(ent.page, ent.index); 992 | ent.page = _bt->readpage(ent.page->getpage(ent.index)); 993 | } 994 | ent.index = 0; 995 | add(ent.page, ent.index); 996 | } 997 | } 998 | void prev() 999 | { 1000 | if (eof()) 1001 | throw std::exception("cursor:EOF"); 1002 | auto ent = _stack.back(); _stack.pop_back(); 1003 | ent.index--; 1004 | if (ent.page->isleaf()) { 1005 | while (!_stack.empty() && ent.index<0) { 1006 | ent = _stack.back(); _stack.pop_back(); 1007 | } 1008 | if (ent.index>=0) 1009 | add(ent.page, ent.index); 1010 | } 1011 | else { 1012 | add(ent.page, ent.index); 1013 | while (ent.page->isindex()) { 1014 | ent.page = _bt->readpage(ent.page->getpage(ent.index)); 1015 | ent.index = ent.page->indexsize()-1; 1016 | add(ent.page, ent.index); 1017 | } 1018 | } 1019 | } 1020 | bool eof() const 1021 | { 1022 | return _stack.empty(); 1023 | } 1024 | 1025 | // for Btree.find to create cursor. 1026 | void add(Page_ptr page, int index) 1027 | { 1028 | _stack.emplace_back(page, index); 1029 | } 1030 | 1031 | // getting key/value from cursor pos 1032 | auto getkey() const 1033 | { 1034 | if (eof()) 1035 | throw std::exception("cursor:EOF"); 1036 | auto ent = _stack.back(); 1037 | return ent.page->getkey(ent.index); 1038 | } 1039 | auto getval() const 1040 | { 1041 | if (eof()) 1042 | throw std::exception("cursor:EOF"); 1043 | auto ent = _stack.back(); 1044 | return ent.page->getval(ent.index); 1045 | } 1046 | 1047 | bool operator==(const Cursor& rhs) const { return _stack == rhs._stack; } 1048 | bool operator!=(const Cursor& rhs) const { return !(*this==rhs); } 1049 | 1050 | bool operator<(const Cursor& rhs) const { return getkey() < rhs.getkey(); } 1051 | }; 1052 | 1053 | 1054 | BtreeBase(stream_ptr is) : _is(is) { } 1055 | virtual ~BtreeBase() { } 1056 | 1057 | virtual int version() const = 0; 1058 | virtual void readheader() = 0; 1059 | virtual Page_ptr makepage(int nr) = 0; 1060 | 1061 | Page_ptr readpage(int nr) 1062 | { 1063 | auto page = makepage(nr); 1064 | page->readindex(); 1065 | return page; 1066 | } 1067 | 1068 | void dump() 1069 | { 1070 | printf("btree v%02d ff=%d, pg=%d, root=%05x, #recs=%d #pgs=%d\n", 1071 | version(), _firstfree, _pagesize, _firstindex, _reccount, _pagecount); 1072 | 1073 | dumptree(_firstindex); 1074 | } 1075 | void dumptree(int nr) 1076 | { 1077 | auto page = readpage(nr); 1078 | page->dump(); 1079 | 1080 | if (page->isindex()) { 1081 | dumptree(page->getpage(-1)); 1082 | for (unsigned int i=0 ; iindexsize() ; i++) 1083 | dumptree(page->getpage(i)); 1084 | } 1085 | } 1086 | 1087 | stream_ptr pagestream(int nr) 1088 | { 1089 | return std::make_shared(_is, nr*_pagesize, _pagesize); 1090 | } 1091 | 1092 | Cursor find(relation_t rel, const std::string& key) 1093 | { 1094 | auto page = readpage(_firstindex); 1095 | 1096 | Cursor cursor(this); 1097 | relation_t act; 1098 | while (true) 1099 | { 1100 | auto res = page->find(key); 1101 | dbgprint("bt.find %d : %d\n", res.act, res.index); 1102 | cursor.add(page, res.index); 1103 | if (res.act != REL_RECURSE) 1104 | { 1105 | act = res.act; 1106 | break; 1107 | } 1108 | page = readpage(page->getpage(res.index)); 1109 | } 1110 | 1111 | if (act == rel) { 1112 | dbgprint("same -> pass\n"); 1113 | // pass 1114 | } 1115 | else if (rel==REL_EQUAL && act!=REL_EQUAL) { 1116 | cursor.clear(); 1117 | dbgprint("not equal -> empty\n"); 1118 | } 1119 | else if ((rel==REL_LESS_EQUAL || rel==REL_GREATER_EQUAL) && act==REL_EQUAL) { 1120 | dbgprint("want: <=/>=, got: == -> pass\n"); 1121 | // pass 1122 | } 1123 | else if ((rel==REL_GREATER || rel==REL_GREATER_EQUAL) && act==REL_LESS) { 1124 | dbgprint("want: >/>=, got: < -> next\n"); 1125 | cursor.next(); 1126 | } 1127 | else if (rel==REL_GREATER && act==REL_EQUAL) { 1128 | dbgprint("want: >, got: == -> next\n"); 1129 | cursor.next(); 1130 | } 1131 | else if ((rel==REL_LESS || rel==REL_LESS_EQUAL) && act==REL_GREATER) { 1132 | dbgprint("want: -> prev\n"); 1133 | cursor.prev(); 1134 | } 1135 | else if (rel==REL_LESS && act==REL_EQUAL) { 1136 | dbgprint("want: <, got: == -> prev\n"); 1137 | cursor.prev(); 1138 | } 1139 | 1140 | return cursor; 1141 | } 1142 | }; 1143 | class Page15 : public BasePage { 1144 | public: 1145 | Page15(stream_ptr is, uint32_t nr, int pagesize) 1146 | : BasePage(is, nr, pagesize) 1147 | { 1148 | auto s = makehelper(_is); 1149 | _preceeding = s.get16le(); 1150 | _count = s.get16le(); 1151 | } 1152 | 1153 | virtual Entry readent() 1154 | { 1155 | auto s = makehelper(_is); 1156 | if (isindex()) { 1157 | Entry ent; 1158 | ent.pagenr = s.get16le(); 1159 | ent.recofs = s.get16le()+1; 1160 | dbgprint("@%04x: ix ent15 %08x %04x\n", (int)_is->tellg(), ent.pagenr, ent.recofs); 1161 | return ent; 1162 | } 1163 | else if (isleaf()) { 1164 | Entry ent; 1165 | ent.indent = s.get8(); 1166 | /*ent.unknown = */s.get8(); 1167 | ent.recofs = s.get16le()+1; 1168 | dbgprint("@%04x: lf ent15 %+4d %04x\n", (int)_is->tellg(), ent.indent, ent.recofs); 1169 | return ent; 1170 | } 1171 | throw "page not a index or leaf"; 1172 | } 1173 | }; 1174 | 1175 | class Btree15 : public BtreeBase { 1176 | public: 1177 | Btree15(stream_ptr is) : BtreeBase(is) { } 1178 | int version() const { return 15; } 1179 | 1180 | void readheader() 1181 | { 1182 | _is->seekg(0); 1183 | auto s = makehelper(_is); 1184 | _firstfree = s.get16le(); 1185 | _pagesize = s.get16le(); 1186 | _firstindex = s.get16le(); 1187 | _reccount = s.get32le(); 1188 | _pagecount = s.get16le(); 1189 | } 1190 | 1191 | virtual Page_ptr makepage(int nr) 1192 | { 1193 | dbgprint("page15\n"); 1194 | return std::make_shared(pagestream(nr), nr, _pagesize); 1195 | } 1196 | }; 1197 | 1198 | class Page16 : public BasePage { 1199 | public: 1200 | Page16(stream_ptr is, uint32_t nr, int pagesize) 1201 | : BasePage(is, nr, pagesize) 1202 | { 1203 | auto s = makehelper(_is); 1204 | _preceeding = s.get32le(); 1205 | _count = s.get16le(); 1206 | } 1207 | virtual Entry readent() 1208 | { 1209 | auto s = makehelper(_is); 1210 | if (isindex()) { 1211 | Entry ent; 1212 | ent.pagenr = s.get32le(); 1213 | ent.recofs = s.get16le()+1; 1214 | dbgprint("@%04x: ix ent16 %08x %04x\n", (int)_is->tellg(), ent.pagenr, ent.recofs); 1215 | return ent; 1216 | } 1217 | else if (isleaf()) { 1218 | Entry ent; 1219 | ent.indent = s.get8(); 1220 | /*ent.unknown = */s.get8(); 1221 | /*ent.unknown1 = */s.get16le(); 1222 | ent.recofs = s.get16le()+1; 1223 | dbgprint("@%04x: lf ent16 %+4d %04x\n", (int)_is->tellg(), ent.indent, ent.recofs); 1224 | return ent; 1225 | } 1226 | throw std::exception("page not a index or leaf"); 1227 | } 1228 | 1229 | }; 1230 | 1231 | class Btree16 : public BtreeBase { 1232 | public: 1233 | Btree16(stream_ptr is) : BtreeBase(is) { } 1234 | int version() const { return 16; } 1235 | 1236 | void readheader() 1237 | { 1238 | _is->seekg(0); 1239 | auto s = makehelper(_is); 1240 | _firstfree = s.get32le(); 1241 | _pagesize = s.get16le(); 1242 | _firstindex = s.get32le(); 1243 | _reccount = s.get32le(); 1244 | _pagecount = s.get32le(); 1245 | } 1246 | virtual Page_ptr makepage(int nr) 1247 | { 1248 | dbgprint("page16\n"); 1249 | return std::make_shared(pagestream(nr), nr, _pagesize); 1250 | } 1251 | 1252 | }; 1253 | // v2 b-tree pages - since idav6.7 1254 | class Page20 : public Page16 { 1255 | public: 1256 | Page20(stream_ptr is, int nr, int pagesize) 1257 | : Page16(is, nr, pagesize) 1258 | { 1259 | } 1260 | virtual Entry readent() 1261 | { 1262 | auto s = makehelper(_is); 1263 | if (isindex()) 1264 | { 1265 | Entry ent; 1266 | ent.pagenr = s.get32le(); 1267 | ent.recofs = s.get16le(); 1268 | dbgprint("@%04x: ix ent20 %08x %04x\n", (int)_is->tellg(), ent.pagenr, ent.recofs); 1269 | return ent; 1270 | } 1271 | else if (isleaf()) 1272 | { 1273 | Entry ent; 1274 | ent.indent = s.get16le(); 1275 | /*ent.unknown = */s.get16le(); 1276 | ent.recofs = s.get16le(); 1277 | 1278 | dbgprint("@%04x: lf ent20 %+4d %04x\n", (int)_is->tellg(), ent.indent, ent.recofs); 1279 | return ent; 1280 | } 1281 | throw std::exception("page not a index or leaf"); 1282 | } 1283 | 1284 | }; 1285 | class Btree20 : public Btree16 { 1286 | public: 1287 | Btree20(stream_ptr is) : Btree16(is) { } 1288 | int version() const { return 20; } 1289 | virtual Page_ptr makepage(int nr) 1290 | { 1291 | dbgprint("page20\n"); 1292 | return std::make_shared(pagestream(nr), nr, _pagesize); 1293 | } 1294 | }; 1295 | 1296 | 1297 | // determine which btree type to create for the id0 stream. 1298 | inline std::unique_ptr MakeBTree(stream_ptr is) 1299 | { 1300 | std::unique_ptr bt; 1301 | is->seekg(0); 1302 | char data[64]; 1303 | is->read(data, 64); 1304 | 1305 | dbgprint("mkbt: %s\n", str_hex(std::string(data, data+64)).c_str()); 1306 | 1307 | if (std::equal(data+13, data+13+25, "B-tree v 1.5 (C) Pol 1990")) 1308 | { 1309 | bt = std::make_unique(is); 1310 | } 1311 | else if (std::equal(data+19, data+19+25, "B-tree v 1.6 (C) Pol 1990")) 1312 | { 1313 | bt = std::make_unique(is); 1314 | } 1315 | else if (std::equal(data+19, data+19+9, "B-tree v2")) 1316 | { 1317 | bt = std::make_unique(is); 1318 | } 1319 | else 1320 | { 1321 | throw std::exception("unknown btree version"); 1322 | } 1323 | 1324 | bt->readheader(); 1325 | 1326 | return bt; 1327 | } 1328 | 1329 | // NodeKeys is used to create btree keys with the right format 1330 | // for the current database. 1331 | class NodeKeys { 1332 | int _w; 1333 | public: 1334 | NodeKeys(int wordsize) 1335 | : _w(wordsize) 1336 | {} 1337 | 1338 | template 1339 | void setwordle(P first, P last, uint64_t w) 1340 | { 1341 | if (_w==8) 1342 | EndianTools::setle64(first, last, w); 1343 | else if (_w==4) 1344 | EndianTools::setle32(first, last, w); 1345 | } 1346 | template 1347 | void setwordbe(P first, P last, uint64_t w) 1348 | { 1349 | if (_w==8) 1350 | EndianTools::setbe64(first, last, w); 1351 | else if (_w==4) 1352 | EndianTools::setbe32(first, last, w); 1353 | } 1354 | template 1355 | int make_name_key(P first, P last, uint64_t id) 1356 | { 1357 | if (last - first < 1 + _w) 1358 | throw std::exception("not enough space"); 1359 | P p = first; 1360 | *p++ = 'N'; 1361 | setwordbe(p, last, id); p += _w; 1362 | 1363 | return p - first; 1364 | } 1365 | template 1366 | int make_name_key(P first, P last, const std::string& name) 1367 | { 1368 | if (last - first < 1 + name.size()) 1369 | throw std::exception("not enough space"); 1370 | P p = first; 1371 | *p++ = 'N'; 1372 | std::copy(name.begin(), name.end(), p); 1373 | p += name.size(); 1374 | 1375 | return p - first; 1376 | } 1377 | 1378 | template 1379 | int make_node_key(P first, P last, uint64_t nodeid) 1380 | { 1381 | if (last - first < 1 + _w) 1382 | throw std::exception("not enough space"); 1383 | P p = first; 1384 | *p++ = '.'; 1385 | setwordbe(p, last, nodeid); p += _w; 1386 | 1387 | return p - first; 1388 | } 1389 | template 1390 | int make_node_key(P first, P last, uint64_t nodeid, char tag) 1391 | { 1392 | if (last - first < 2 + _w) 1393 | throw std::exception("not enough space"); 1394 | P p = first; 1395 | *p++ = '.'; 1396 | setwordbe(p, last, nodeid); p += _w; 1397 | *p++ = tag; 1398 | return p - first; 1399 | } 1400 | template 1401 | int make_node_key(P first, P last, uint64_t nodeid, char tag, const std::string& hashkey) 1402 | { 1403 | if (last - first < 2 + _w + hashkey.size()) 1404 | throw std::exception("not enough space"); 1405 | P p = first; 1406 | *p++ = '.'; 1407 | setwordbe(p, last, nodeid); p += _w; 1408 | *p++ = tag; // usually 'H' 1409 | std::copy(hashkey.begin(), hashkey.end(), p); 1410 | p += hashkey.size(); 1411 | 1412 | return p - first; 1413 | } 1414 | template 1415 | int make_node_key(P first, P last, uint64_t nodeid, char tag, T index) 1416 | { 1417 | if (last - first < 2 + 2 * _w) 1418 | throw std::exception("not enough space"); 1419 | P p = first; 1420 | *p++ = '.'; 1421 | setwordbe(p, last, nodeid); p += _w; 1422 | *p++ = tag; 1423 | setwordbe(p, last, index); p += _w; 1424 | 1425 | return p - first; 1426 | } 1427 | 1428 | template 1429 | V make_name_key(uint64_t id) 1430 | { 1431 | V key; 1432 | make_name_key(back_inserter(key), back_inserter(key), id); 1433 | return key; 1434 | } 1435 | template 1436 | V make_name_key(const std::string& name) 1437 | { 1438 | V key; 1439 | make_name_key(back_inserter(key), back_inserter(key), name); 1440 | return key; 1441 | } 1442 | 1443 | template 1444 | V make_node_key(uint64_t nodeid) 1445 | { 1446 | V key; 1447 | make_node_key(back_inserter(key), back_inserter(key), nodeid); 1448 | return key; 1449 | } 1450 | template 1451 | V make_node_key(uint64_t nodeid, char tag) 1452 | { 1453 | V key; 1454 | make_node_key(back_inserter(key), back_inserter(key), nodeid, tag); 1455 | return key; 1456 | } 1457 | template 1458 | V make_node_key(uint64_t nodeid, char tag, const std::string& hashkey) 1459 | { 1460 | V key; 1461 | make_node_key(back_inserter(key), back_inserter(key), nodeid, tag, hashkey); 1462 | return key; 1463 | } 1464 | template 1465 | V make_node_key(uint64_t nodeid, char tag, T index) 1466 | { 1467 | V key; 1468 | make_node_key(back_inserter(key), back_inserter(key), nodeid, tag, index); 1469 | return key; 1470 | } 1471 | }; 1472 | 1473 | // convert node values to integer or string. 1474 | struct NodeValues { 1475 | static uint64_t getuint(const std::string& str) 1476 | { 1477 | switch(str.size()) 1478 | { 1479 | case 1: 1480 | return EndianTools::get8(str.begin(), str.end()); 1481 | case 2: 1482 | return EndianTools::getle16(str.begin(), str.end()); 1483 | case 4: 1484 | return EndianTools::getle32(str.begin(), str.end()); 1485 | case 8: 1486 | return EndianTools::getle64(str.begin(), str.end()); 1487 | 1488 | } 1489 | throw std::exception("unsupported int type"); 1490 | } 1491 | static uint64_t getuintbe(const std::string& str) 1492 | { 1493 | switch(str.size()) 1494 | { 1495 | case 1: 1496 | return EndianTools::get8(str.begin(), str.end()); 1497 | case 2: 1498 | return EndianTools::getbe16(str.begin(), str.end()); 1499 | case 4: 1500 | return EndianTools::getbe32(str.begin(), str.end()); 1501 | case 8: 1502 | return EndianTools::getbe64(str.begin(), str.end()); 1503 | 1504 | } 1505 | throw std::exception("unsupported int type"); 1506 | } 1507 | static int64_t getint(const std::string& str) 1508 | { 1509 | return (int64_t)getuint(str); 1510 | } 1511 | static std::string getstr(std::string data) 1512 | { 1513 | // strip terminating zeroes 1514 | while (!data.empty() && data.back()==0) 1515 | data.pop_back(); 1516 | return data; 1517 | } 1518 | }; 1519 | 1520 | // provide access to the main part of the IDApro database. 1521 | // 1522 | // use 'find', 'node' and 'blob' to access nodes in the database. 1523 | class ID0File { 1524 | std::unique_ptr _bt; 1525 | uint64_t _nodebase; 1526 | int _wordsize; 1527 | 1528 | public: 1529 | enum { INDEX = 0 }; // argument for idb.getsection() 1530 | 1531 | ID0File(IDBFile& idb, stream_ptr is) 1532 | : _bt(MakeBTree(is)) 1533 | { 1534 | if (idb.magic() == IDBFile::MAGIC_IDA2) 1535 | _wordsize = 8; 1536 | else 1537 | _wordsize = 4; 1538 | 1539 | _nodebase = uint64_t(0xFF)<<((_wordsize-1)*8); 1540 | } 1541 | uint64_t nodebase() const { return _nodebase; } 1542 | bool is64bit() const { return _wordsize==8; } 1543 | void dump() 1544 | { 1545 | _bt->dump(); 1546 | } 1547 | 1548 | // function for creating a node key for the current database. 1549 | template 1550 | std::string makekey(ARGS...args) 1551 | { 1552 | NodeKeys nk(_wordsize); 1553 | return nk.make_node_key(args...); 1554 | } 1555 | 1556 | // function for creating a name key for the current database. 1557 | template 1558 | std::string makename(ARGS...args) 1559 | { 1560 | NodeKeys nk(_wordsize); 1561 | return nk.make_name_key(args...); 1562 | } 1563 | 1564 | // search for records in the current database by key. 1565 | // relation gives the desired relation: 1566 | // REL_LESS : return records less than the key. 1567 | // 1568 | // returns a cursor object. 1569 | auto find(relation_t rel, const std::string& key) 1570 | { 1571 | return _bt->find(rel, key); 1572 | } 1573 | 1574 | // search for records in the current database. 1575 | // relation gives the desired relation: 1576 | // REL_LESS : return records less than the key. 1577 | // 1578 | // returns a cursor object. 1579 | template 1580 | auto find(relation_t rel, uint64_t nodeid, ARGS...args) 1581 | { 1582 | return _bt->find(rel, makekey(nodeid, args...)); 1583 | } 1584 | 1585 | // return a blob object as a string. 1586 | std::string blob(uint64_t nodeid, char tag, uint64_t startid = 0, uint64_t lastid = 0xFFFFFFFF) 1587 | { 1588 | auto c = _bt->find(REL_GREATER_EQUAL, makekey(nodeid, tag, startid)); 1589 | auto endkey = makekey(nodeid, tag, lastid); 1590 | 1591 | std::string blob; 1592 | while (c.getkey() <= endkey) { 1593 | blob += c.getval(); 1594 | c.next(); 1595 | } 1596 | 1597 | return blob; 1598 | } 1599 | 1600 | // finds the nodeid by name. 1601 | // 1602 | // names can be labels like 'sub_1234', but also internal names like '$ structs', or 'Root Name' 1603 | uint64_t node(const std::string& name) 1604 | { 1605 | auto c = _bt->find(REL_EQUAL, makename(name)); 1606 | if (c.eof()) 1607 | return 0; 1608 | return NodeValues::getint(c.getval()); 1609 | } 1610 | 1611 | // callback is called for each nodeid in the list. 1612 | // examples of lists: '$ structs', '$ enums' 1613 | template 1614 | void enumlist(uint64_t nodeid, char tag, CB cb) 1615 | { 1616 | auto c = _bt->find(REL_GREATER_EQUAL, makekey(nodeid, tag)); 1617 | auto endkey = makekey(nodeid, tag+1); 1618 | while (c.getkey() <= endkey) 1619 | cb(NodeValues::getint(c.getval())); 1620 | } 1621 | 1622 | // 'easy' interface: return empty when record not found. 1623 | // otherwise directly return the value. 1624 | template 1625 | std::string getdata(ARGS...args) 1626 | { 1627 | auto c = find(REL_EQUAL, makekey(args...)); 1628 | if (c.eof()) 1629 | return {}; 1630 | return c.getval(); 1631 | } 1632 | template 1633 | std::string getstr(ARGS...args) 1634 | { 1635 | // until ida6.7 strings were stored zero terminated. 1636 | auto c = find(REL_EQUAL, makekey(args...)); 1637 | if (c.eof()) 1638 | return {}; 1639 | return NodeValues::getstr(c.getval()); 1640 | } 1641 | template 1642 | uint64_t getuint(ARGS...args) 1643 | { 1644 | auto c = find(REL_EQUAL, makekey(args...)); 1645 | if (c.eof()) 1646 | return {}; 1647 | return NodeValues::getuint(c.getval()); 1648 | } 1649 | uint64_t getuint(BtreeBase::Cursor& c) 1650 | { 1651 | return NodeValues::getuint(c.getval()); 1652 | } 1653 | 1654 | // returns the node name, resolves long names. 1655 | std::string getname(uint64_t node) 1656 | { 1657 | auto c = find(REL_EQUAL, makekey(node, 'N')); 1658 | if (c.eof()) 1659 | return {}; 1660 | auto val = c.getval(); 1661 | if (val.empty()) 1662 | return {}; 1663 | if (val[0]==0) { 1664 | val.erase(val.begin()); 1665 | // bigname 1666 | uint64_t nameid = NodeValues::getuintbe(val); 1667 | val = blob(_nodebase, 'S', nameid*256, nameid*256+32); 1668 | } 1669 | return NodeValues::getstr(val); 1670 | } 1671 | }; 1672 | 1673 | #ifndef BADADDR 1674 | #define BADADDR uint64_t(-1) 1675 | #endif 1676 | 1677 | // the ID1File contains information on segments, and stores the flags for each byte. 1678 | // basically this is the data for the idc GetFlags(ea) function. 1679 | class ID1File { 1680 | struct segment { 1681 | uint64_t start_ea; 1682 | uint64_t end_ea; 1683 | uint64_t id1ofs; 1684 | }; 1685 | typedef std::vector segmentlist_t; 1686 | segmentlist_t _segments; 1687 | 1688 | stream_ptr _is; 1689 | int _wordsize; 1690 | 1691 | private: 1692 | 1693 | void open() 1694 | { 1695 | auto s = makehelper(_is, _wordsize); 1696 | s.seekg(0); 1697 | uint32_t magic = s.get32le(); 1698 | if ((magic & 0xFFF0FFFF) == 0x306156) { // Va0 .. Va4 1699 | uint16_t nsegments = s.get16le(); 1700 | uint16_t npages = s.get16le(); 1701 | (void)npages; // value not used 1702 | 1703 | _segments.resize(nsegments); 1704 | for (unsigned i=0 ; i _namedoffsets; 1814 | mutable bool _namesloaded; 1815 | 1816 | stream_ptr _is; 1817 | int _wordsize; 1818 | 1819 | uint32_t _nnames; 1820 | uint64_t _listofs; 1821 | public: 1822 | enum { INDEX = 2 }; // argument for idb.getsection() 1823 | 1824 | NAMFile(IDBFile& idb, stream_ptr is) 1825 | : _namesloaded(false), _is(is) 1826 | { 1827 | if (idb.magic() == IDBFile::MAGIC_IDA2) 1828 | _wordsize = 8; 1829 | else 1830 | _wordsize = 4; 1831 | 1832 | open(); 1833 | } 1834 | 1835 | void open() 1836 | { 1837 | auto s = makehelper(_is, _wordsize); 1838 | s.seekg(0); 1839 | uint32_t magic = s.get32le(); 1840 | 1841 | if ((magic & 0xFFF0FFFF) == 0x306156) // Va0 .. Va4 1842 | { 1843 | uint16_t npages = s.get16le(); // nr of chunks 1844 | uint16_t eof = s.get16le(); 1845 | uint64_t unknown = s.getword(); 1846 | (void)npages; (void)eof; // value not used 1847 | 1848 | _nnames = s.getword(); // nr of names 1849 | _listofs = s.getword(); // page size 1850 | 1851 | dbgprint("nam: np=%d, eof=%d, nn=%d, ofs=%08x\n", 1852 | npages, eof, _nnames, _listofs); 1853 | if (unknown) 1854 | printf("!! nam.unknown=%I64x\n", unknown); 1855 | } 1856 | else if (magic == 0x2a4156) 1857 | { 1858 | uint32_t unk1 = s.get32le(); // 3 1859 | uint32_t npages = s.get32le(); // nr of chunks 1860 | uint32_t unk2 = s.get32le(); // 0x800 1861 | uint32_t eof = s.get32le(); // 0x15 1862 | uint64_t unknown = s.getword(); // 0 1863 | (void)unk1; (void)npages; (void)unk2; (void)eof; (void)unknown; // values not used 1864 | 1865 | _listofs = 0x2000; 1866 | _nnames = s.getword(); // nr of names 1867 | 1868 | dbgprint("nam: np=%d, eof=%d, nn=%d, ofs=%16x\n", 1869 | npages, eof, _nnames, _listofs); 1870 | } 1871 | else 1872 | { 1873 | throw std::exception("invalid NAM"); 1874 | } 1875 | 1876 | if (_wordsize==8) 1877 | _nnames /= 2; 1878 | } 1879 | void loadoffsets() const 1880 | { 1881 | if (_namesloaded) 1882 | return; 1883 | _namedoffsets.reserve(_nnames); 1884 | 1885 | auto s = makehelper(_is, _wordsize); 1886 | s.seekg(_listofs); 1887 | for (unsigned i=0 ; i<_nnames ; i++) 1888 | _namedoffsets.push_back(s.getword()); 1889 | 1890 | _namesloaded = true; 1891 | } 1892 | int numnames() const 1893 | { 1894 | loadoffsets(); 1895 | return _namedoffsets.size(); 1896 | } 1897 | template 1898 | void enumerate(FN fn) const 1899 | { 1900 | loadoffsets(); 1901 | std::for_each(_namedoffsets.begin(), _namedoffsets.end(), fn); 1902 | } 1903 | // finds nearest named item 1904 | uint64_t findname(uint64_t ea) const 1905 | { 1906 | loadoffsets(); 1907 | if (_namedoffsets.empty()) 1908 | return BADADDR; 1909 | auto i= std::upper_bound(_namedoffsets.begin(), _namedoffsets.end(), ea); 1910 | if (i==_namedoffsets.begin()) 1911 | { 1912 | // address before first: return first named item 1913 | return *i; 1914 | } 1915 | i--; 1916 | return *i; 1917 | } 1918 | 1919 | uint64_t firstnamed() const 1920 | { 1921 | loadoffsets(); 1922 | if (_namedoffsets.empty()) 1923 | return BADADDR; 1924 | return *_namedoffsets.begin(); 1925 | } 1926 | }; 1927 | 1928 | // packs/unpacks structured data 1929 | class BaseUnpacker { 1930 | public: 1931 | virtual bool eof() const = 0; 1932 | virtual uint16_t next16() = 0; 1933 | virtual uint32_t next32() = 0; 1934 | virtual uint64_t nextword() = 0; 1935 | virtual ~BaseUnpacker() { } 1936 | }; 1937 | template 1938 | class Unpacker : public BaseUnpacker { 1939 | P _p; 1940 | P _last; 1941 | bool _use64; 1942 | public: 1943 | 1944 | Unpacker(P first, P last, bool use64) 1945 | : _p(first), _last(last), _use64(use64) 1946 | { 1947 | } 1948 | bool eof() const 1949 | { 1950 | return _p>=_last; 1951 | } 1952 | 1953 | /* 1954 | * 7 bit - values 0 .. 0x7f 1955 | * 14 bit - values 0x80 .. 0x3fff, orred with 0x8000 1956 | * 0xFF + 2 byte - any 16 bit val 1957 | */ 1958 | uint16_t next16() 1959 | { 1960 | if (_p>=_last) throw std::exception("unpack: no data"); 1961 | uint8_t byte = *_p++; 1962 | if (byte==0xff) { 1963 | if (_p+2>_last) throw std::exception("unpack: no data"); 1964 | uint16_t value = EndianTools::getbe16(_p, _last); 1965 | _p += 2; 1966 | return value; 1967 | } 1968 | if (byte<0x80) 1969 | return byte; 1970 | _p--; 1971 | if (_p+2>_last) throw "unpack: no data"; 1972 | uint16_t value = EndianTools::getbe16(_p, _last) & 0x3FFF; _p += 2; 1973 | return value; 1974 | } 1975 | 1976 | /* 1977 | * 7 bit - values 0 .. 0x7f 1978 | * 14 bit - values 0x80 .. 0x3fff, orred with 0x8000 1979 | * 29 bit - values 0x4000 .. 0x1fffffff, orred with 0xc0000000 1980 | * 1981 | * 0xFF + 4 byte - any 32 bit val - in .idb 1982 | * 1983 | * in .i64, 64 bit values are encoded as the low 32bit value followed by high 1984 | * 32 bit value using any of the above encodings. So the byte order is kind of twisted. 1985 | * 1986 | */ 1987 | uint32_t next32() 1988 | { 1989 | if (_p>=_last) throw "unpack: no data"; 1990 | uint8_t byte = *_p++; 1991 | if (byte==0xff) { 1992 | if (_p+4>_last) throw "unpack: no data"; 1993 | uint32_t value = EndianTools::getbe32(_p, _last); 1994 | _p += 4; 1995 | return value; 1996 | } 1997 | if (byte<0x80) { 1998 | return byte; 1999 | } 2000 | _p--; 2001 | if (byte<0xc0) { 2002 | if (_p+2>_last) throw "unpack: no data"; 2003 | uint32_t value = EndianTools::getbe16(_p, _last) & 0x3FFF; _p += 2; 2004 | return value; 2005 | } 2006 | if (_p+4>_last) throw "unpack: no data"; 2007 | uint32_t value = EndianTools::getbe32(_p, _last) & 0x1FFFFFFF; _p += 4; 2008 | return value; 2009 | 2010 | } 2011 | uint64_t nextword() 2012 | { 2013 | uint64_t lo = next32(); 2014 | if (_use64) 2015 | { 2016 | uint64_t hi = next32(); 2017 | return lo|(hi<<32); 2018 | } 2019 | return lo; 2020 | } 2021 | 2022 | }; 2023 | template 2024 | static void idaunpack(I first, I last, O out) 2025 | { 2026 | Unpacker p(first, last, false); 2027 | 2028 | while (!p.eof()) 2029 | *out++ = p.next32(); 2030 | } 2031 | 2032 | template 2033 | Unpacker

makeunpacker(P first, P last, bool use64) 2034 | { 2035 | return Unpacker

(first, last, use64); 2036 | } 2037 | 2038 | typedef std::vector DwordVector; 2039 | 2040 | // used mostly in lists, where the stored value is one less than the actually used value. 2041 | // lists like: $enums, $structs, $scripts, values of enums, masks of bitfields, values of bitmasks 2042 | // backref of bitfield value to mask. 2043 | inline uint64_t minusone(uint64_t id) 2044 | { 2045 | if (id) return id-1; 2046 | return 0; 2047 | } 2048 | 2049 | // 'd' xref-from -> points to used type 2050 | // 'D' xref-to -> points to type users 2051 | 2052 | class StructMember { 2053 | /* 2054 | * (membernode, N) = struct.member-name 2055 | * (membernode, A, 3) = structid+1 2056 | * (membernode, A, 8) = 2057 | * (membernode, A, 11) = enumid+1 2058 | * (membernode, A, 16) = flag? -- 4:variable length flag? 2059 | * (membernode, S, 0x3000) = type (set with 'Y') 2060 | * (membernode, S, 0x3001) = names used in 'type' 2061 | * (membernode, S, 5) = array type? 2062 | * (membernode, S, 9) = offset-type 2063 | * (membernode, D, address) = xref-type 2064 | * (membernode, d, structid) = xref-type -- for sub-structs 2065 | */ 2066 | ID0File& _id0; 2067 | uint64_t _nodeid; 2068 | uint64_t _skip; // nr of bytes to skip before this member 2069 | uint64_t _size; // size in bytes of this member 2070 | uint32_t _flags; 2071 | uint32_t _props; 2072 | uint64_t _ofs; 2073 | public: 2074 | StructMember(ID0File& id0, BaseUnpacker& spec) 2075 | : _id0(id0) 2076 | { 2077 | _nodeid = spec.nextword(); 2078 | _skip = spec.nextword(); 2079 | _size = spec.nextword(); 2080 | _flags = spec.next32(); 2081 | _props = spec.next32(); 2082 | _ofs = 0; 2083 | } 2084 | void setofs(uint64_t ofs) { _ofs = ofs; } 2085 | 2086 | uint64_t nodeid() const { return _nodeid + _id0.nodebase(); } 2087 | uint64_t skip() const { return _skip; } 2088 | uint64_t size() const { return _size; } 2089 | uint32_t flags() const { return _flags; } 2090 | uint32_t props() const { return _props; } 2091 | uint64_t offset() const { return _ofs; } 2092 | 2093 | std::string name() const { return _id0.getname(nodeid()); } 2094 | 2095 | uint64_t enumid() const { return minusone(_id0.getuint(nodeid(), 'A', 11)); } 2096 | uint64_t structid() const { return minusone(_id0.getuint(nodeid(), 'A', 3)); } 2097 | std::string comment(bool repeatable) const { return _id0.getstr(nodeid(), 'S', repeatable ? 1 : 0); } 2098 | std::string ptrinfo() const { return _id0.getdata(nodeid(), 'S', 9); } 2099 | 2100 | // types from typeinfo: 2101 | // 11 00 _BYTE 2102 | // 32 00 char 2103 | // 22 00 unsigned __int8 2104 | // 02 00 __int8 2105 | // ='WORD" WORD 2106 | // 03 00 short 2107 | // 07 00 int 2108 | // 03 00 __int16 2109 | // 28 00 _BOOL2 2110 | // 10 00 _WORD 2111 | std::string typeinfo() const { return _id0.getdata(nodeid(), 'S', 0x3000); } 2112 | }; 2113 | 2114 | // access structs and struct members. 2115 | class Struct { 2116 | /* 2117 | * (structnode, N) = structname 2118 | * (structnode, D, address) = xref-type 2119 | * (structnode, M, 0) = packed struct info 2120 | * (structnode, S, 27) = packed value(addr, byte) 2121 | */ 2122 | ID0File& _id0; 2123 | uint64_t _nodeid; 2124 | 2125 | uint32_t _flags; 2126 | std::vector _members; 2127 | uint32_t _seqnr; 2128 | 2129 | uint32_t _size; 2130 | 2131 | class Iterator : public std::iterator { 2132 | const Struct* _s; 2133 | int _ix; 2134 | public: 2135 | Iterator(const Struct*s, int ix) : _s(s), _ix(ix) { } 2136 | Iterator() : _s(nullptr), _ix(0) { } 2137 | Iterator(const Iterator& i) : _s(i._s), _ix(i._ix) { } 2138 | 2139 | bool operator==(const Iterator& rhs) {return _ix==rhs._ix;} 2140 | bool operator!=(const Iterator& rhs) {return _ix!=rhs._ix;} 2141 | 2142 | const StructMember& operator*() {return _s->member(_ix);} 2143 | const StructMember& operator[](int i) {return _s->member(_ix+i);} 2144 | 2145 | Iterator& operator++() {++_ix;return *this;} 2146 | Iterator operator++(int) {Iterator tmp(*this); operator++(); return tmp;} 2147 | Iterator& operator--() {--_ix;return *this;} 2148 | Iterator operator--(int) {Iterator tmp(*this); operator--(); return tmp;} 2149 | 2150 | Iterator& operator+=(int n) { _ix += n; return *this; } 2151 | Iterator& operator-=(int n) { _ix -= n; return *this; } 2152 | 2153 | friend Iterator operator+(int n, Iterator p) { return p+=n; } 2154 | friend Iterator operator+(Iterator p, int n) { return p+=n; } 2155 | friend Iterator operator-(Iterator p, int n) { return p-=n; } 2156 | friend int operator-(const Iterator& p, const Iterator& q) { return p._ix-q._ix; } 2157 | 2158 | bool operator<(const Iterator& rhs) { return _ix(const Iterator& rhs) { return _ix>rhs._ix; } 2161 | bool operator>=(const Iterator& rhs) { return _ix>=rhs._ix; } 2162 | }; 2163 | 2164 | public: 2165 | Struct(ID0File& id0, uint64_t nodeid) 2166 | : _id0(id0), _nodeid(nodeid) 2167 | { 2168 | auto spec = _id0.blob(_nodeid, 'M'); 2169 | auto p = makeunpacker(spec.begin(), spec.end(), _id0.is64bit()); 2170 | 2171 | _flags = p.next32(); 2172 | uint32_t nmember = p.next32(); 2173 | uint64_t ofs = 0; 2174 | while (nmember--) { 2175 | _members.emplace_back(_id0, p); 2176 | ofs += _members.back().skip(); 2177 | _members.back().setofs(ofs); 2178 | ofs += _members.back().size(); 2179 | } 2180 | _size = ofs; 2181 | if (!p.eof()) 2182 | _seqnr = p.next32(); 2183 | else 2184 | _seqnr = 0; 2185 | } 2186 | std::string name() const { return _id0.getname(_nodeid); } 2187 | std::string comment(bool repeatable) const { return _id0.getstr(_nodeid, 'S', repeatable ? 1 : 0); } 2188 | int nmembers() const { return _members.size(); } 2189 | uint32_t flags() const { return _flags; } 2190 | uint32_t seqnr() const { return _seqnr; } 2191 | uint32_t size() const { return _size; } 2192 | 2193 | Iterator begin() const { return Iterator(this, 0); } 2194 | Iterator end() const { return Iterator(this, nmembers()); } 2195 | const StructMember& member(int ix) const { return _members[ix]; } 2196 | 2197 | 2198 | }; 2199 | 2200 | class EnumMember { 2201 | /* 2202 | * (membernode, N) = membername 2203 | * (membernode, A, -2) = enumnode + 1 2204 | * (membernode, A, -3) = member value 2205 | */ 2206 | ID0File& _id0; 2207 | uint64_t _nodeid; 2208 | uint64_t _value; 2209 | public: 2210 | EnumMember(ID0File& id0, uint64_t nodeid) 2211 | : _id0(id0), _nodeid(nodeid) 2212 | { 2213 | _value = _id0.getuint(_nodeid, 'A', -3); 2214 | } 2215 | 2216 | // 'A', -2 -> points to enum node 2217 | uint64_t nodeid() const { return _nodeid; } 2218 | uint64_t value() const { return _value; } 2219 | std::string name() const { return _id0.getname(nodeid()); } 2220 | 2221 | std::string comment(bool repeatable) const { return _id0.getstr(nodeid(), 'S', repeatable ? 1 : 0); } 2222 | 2223 | }; 2224 | // get properties of an enum. 2225 | // bitfields and enums are both in the '$ enums' list. 2226 | class Enum { 2227 | /* 2228 | * (enumnode, N) = enum-name 2229 | * (enumnode, A, -1) = nr of values 2230 | * (enumnode, A, -3) = representation 2231 | * (enumnode, A, -5) = flags: bitfield, hidden, ... 2232 | * (enumnode, A, -8) = 2233 | * (enumnode, E, value) = valuenode + 1 2234 | */ 2235 | ID0File& _id0; 2236 | uint64_t _nodeid; 2237 | 2238 | public: 2239 | Enum(ID0File& id0, uint64_t nodeid) 2240 | : _id0(id0), _nodeid(nodeid) 2241 | { 2242 | } 2243 | uint64_t nodeid() const { return _nodeid; } 2244 | uint64_t count() const { return _id0.getuint(_nodeid, 'A', -1); } 2245 | 2246 | // >>20 : 0x11=hex, 0x22=dec, 0x77=oct, 0x66=bin, 0x33=char 2247 | // values: FF_0NUMx|FF_1NUMx x=H,D,O,B,CHAR 2248 | // 2249 | // >>16 : 0x2 = signed : FF_SIGN 2250 | uint32_t representation() const { return _id0.getuint(_nodeid, 'A', -3); } 2251 | 2252 | // bit0 = bitfield ENUM_FLAGS_IS_BF 2253 | // bit1 = hidden ENUM_FLAGS_HIDDEN 2254 | // bit2 = fromtil ENUM_FLAGS_FROMTIL 2255 | // bit5-3 = width 0..7 = (0,1,2,4,8,16,32,64) 2256 | // bit6 = ghost ENUM_FLAGS_GHOST 2257 | uint32_t flags() const { return _id0.getuint(_nodeid, 'A', -5); } 2258 | 2259 | // 'A',-8 -> index in $enums list 2260 | 2261 | std::string name() const { return _id0.getname(_nodeid); } 2262 | std::string comment(bool repeatable) const { return _id0.getstr(_nodeid, 'S', repeatable ? 1 : 0); } 2263 | 2264 | auto first() const { return _id0.find(REL_GREATER_EQUAL, _id0.makekey(_nodeid, 'E')); } 2265 | std::string lastkey() const { return _id0.makekey(_nodeid, 'F'); } 2266 | 2267 | EnumMember getvalue(BtreeBase::Cursor& c) const 2268 | { 2269 | return EnumMember(_id0, minusone(_id0.getuint(c))); 2270 | } 2271 | }; 2272 | 2273 | class BitfieldValue { 2274 | ID0File& _id0; 2275 | uint64_t _nodeid; 2276 | uint64_t _value; 2277 | uint64_t _mask; 2278 | public: 2279 | BitfieldValue(ID0File& id0, uint64_t nodeid) 2280 | : _id0(id0), _nodeid(nodeid) 2281 | { 2282 | _value = _id0.getuint(_nodeid, 'A', -3); 2283 | _mask = _id0.getuint(_nodeid, 'A', -6) - 1; 2284 | } 2285 | 2286 | uint64_t nodeid() const { return _nodeid; } 2287 | std::string name() const { return _id0.getname(nodeid()); } 2288 | 2289 | std::string comment(bool repeatable) const { return _id0.getstr(nodeid(), 'S', repeatable ? 1 : 0); } 2290 | uint64_t value() const { return _value; } 2291 | uint64_t mask() const { return _mask; } 2292 | 2293 | // 'A', -2 -> points to enum node 2294 | // 'A', -6 -> minusone -> maskid from : (enum, 'm', maskid) 2295 | }; 2296 | class BitfieldMask { 2297 | ID0File& _id0; 2298 | uint64_t _nodeid; 2299 | uint64_t _mask; 2300 | 2301 | public: 2302 | BitfieldMask(ID0File& id0, uint64_t nodeid, uint64_t mask) 2303 | : _id0(id0), _nodeid(nodeid), _mask(mask) 2304 | { 2305 | } 2306 | // BitfieldMask(const BitfieldMask& bf) 2307 | // : _id0(bf._id0), _nodeid(bf._nodeid), _mask(bf._mask) 2308 | // { 2309 | // } 2310 | 2311 | uint64_t nodeid() const { return _nodeid; } 2312 | std::string name() const { return _id0.getname(nodeid()); } 2313 | std::string comment(bool repeatable) const { return _id0.getstr(nodeid(), 'S', repeatable ? 1 : 0); } 2314 | 2315 | uint64_t mask() const { return _mask; } 2316 | 2317 | auto first() const { return _id0.find(REL_GREATER_EQUAL, _id0.makekey(_nodeid, 'E')); } 2318 | std::string lastkey() const { return _id0.makekey(_nodeid, 'F'); } 2319 | 2320 | BitfieldValue getvalue(BtreeBase::Cursor& c) const 2321 | { 2322 | return BitfieldValue(_id0, minusone(_id0.getuint(c))); 2323 | } 2324 | }; 2325 | 2326 | // get properties of a bitfield. 2327 | // bitfields and enums are both in the '$ enums' list. 2328 | class Bitfield { 2329 | ID0File& _id0; 2330 | uint64_t _nodeid; 2331 | 2332 | public: 2333 | Bitfield(ID0File& id0, uint64_t nodeid) 2334 | : _id0(id0), _nodeid(nodeid) 2335 | { 2336 | } 2337 | uint64_t count() const { return _id0.getuint(_nodeid, 'A', -1); } 2338 | 2339 | // >>20 : 0x11=hex, 0x22=dec, 0x77=oct, 0x66=bin, 0x33=char 2340 | // values: FF_0NUMx|FF_1NUMx x=H,D,O,B,CHAR 2341 | // 2342 | // >>16 : 0x2 = signed : FF_SIGN 2343 | uint32_t representation() const { return _id0.getuint(_nodeid, 'A', -3); } 2344 | 2345 | // bit0 = bitfield ENUM_FLAGS_IS_BF 2346 | // bit1 = hidden ENUM_FLAGS_HIDDEN 2347 | // bit2 = fromtil ENUM_FLAGS_FROMTIL 2348 | // bit5-3 = width 0..7 = (0,1,2,4,8,16,32,64) 2349 | // bit6 = ghost ENUM_FLAGS_GHOST 2350 | uint32_t flags() const { return _id0.getuint(_nodeid, 'A', -5); } 2351 | 2352 | // 'A',-8 -> index in $enums list 2353 | 2354 | std::string name() const { return _id0.getname(_nodeid); } 2355 | std::string comment(bool repeatable) const { return _id0.getstr(_nodeid, 'S', repeatable ? 1 : 0); } 2356 | 2357 | // for bitmasks there is an extra level: 'm' in between. 2358 | auto first() const { return _id0.find(REL_GREATER_EQUAL, _id0.makekey(_nodeid, 'm')); } 2359 | std::string lastkey() const { return _id0.makekey(_nodeid, 'n'); } 2360 | 2361 | BitfieldMask getmask(BtreeBase::Cursor& c) 2362 | { 2363 | auto key = c.getkey(); 2364 | uint64_t mask; 2365 | 2366 | // get the mask from the key index, 2367 | // ... not really nescesary, since we can also get the mask 2368 | // from the BitfieldValue : node('A',-6) - 1 2369 | if (_id0.is64bit() ) { 2370 | assert(key.size()==18); 2371 | mask = EndianTools::getbe64(&key[10], &key[10]+8); 2372 | } 2373 | else { 2374 | assert(key.size()==10); 2375 | mask = EndianTools::getbe32(&key[6], &key[6]+4); 2376 | } 2377 | 2378 | return BitfieldMask(_id0, minusone(_id0.getuint(c)), mask); 2379 | } 2380 | 2381 | }; 2382 | 2383 | // access scripts by nodeid, 2384 | // use name(), language() and body() to access 2385 | // the contents of the script. 2386 | class Script { 2387 | ID0File& _id0; 2388 | uint64_t _nodeid; 2389 | 2390 | public: 2391 | Script(ID0File& id0, uint64_t nodeid) 2392 | : _id0(id0), _nodeid(nodeid) 2393 | { 2394 | } 2395 | std::string name() const { return _id0.getstr(_nodeid, 'S', 0); } 2396 | std::string language() const { return _id0.getstr(_nodeid, 'S', 1); } 2397 | std::string body() const { return NodeValues::getstr(_id0.blob(_nodeid, 'X')); } 2398 | }; 2399 | template 2400 | class List { 2401 | /* 2402 | * (listnode, 'N') = listname 2403 | * (listnode, 'A', -1) = list size <-- not for '$ scriptsnippets' 2404 | * (listnode, 'A', seqnr) = itemnode+1 2405 | * 2406 | * (listnode, 'Y', itemnode) = seqnr <-- only for '$ enums' 2407 | * 2408 | * (listnode, 'Y', 0) = list size <-- only for '$ scriptsnippets' 2409 | * (listnode, 'Y', 1) = ? <-- only for '$ scriptsnippets' 2410 | */ 2411 | ID0File& _id0; 2412 | BtreeBase::Cursor _c; 2413 | std::string _endkey; 2414 | public: 2415 | List(ID0File& id0, uint64_t nodeid) 2416 | : _id0(id0), _c(_id0.find(REL_GREATER, _id0.makekey(nodeid, 'A'))) 2417 | { 2418 | _endkey = _id0.makekey(nodeid, 'A', -1); 2419 | } 2420 | bool eof() const { return !(_c.getkey() < _endkey); } 2421 | T next() 2422 | { 2423 | uint64_t id = minusone(_id0.getuint(_c)); 2424 | _c.next(); 2425 | return T(_id0, id); 2426 | } 2427 | }; 2428 | -------------------------------------------------------------------------------- /tests/test-idb3.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | #include 4 | #include 5 | 6 | std::string CreateTestIndexPage(int pagesize) 7 | { 8 | std::string page(pagesize, char(0)); 9 | 10 | auto oi = page.begin(); 11 | auto ei = page.begin() + pagesize/2; 12 | 13 | auto od = page.begin() + pagesize/2; 14 | auto ed = page.end(); 15 | 16 | auto et = EndianTools(); 17 | et.setle32(oi, ei, 122); oi += 4; 18 | et.setle16(oi, ei, 3); oi += 2; 19 | 20 | auto addkv = [&](const std::string& key, const std::string& val, int pagenr) { 21 | et.setle32(oi, ei, pagenr); oi += 4; 22 | et.setle16(oi, ei, od-page.begin()); oi += 2; 23 | 24 | et.setle16(od, ed, key.size()); od += 2; 25 | std::copy(key.begin(), key.end(), od); od += key.size(); 26 | et.setle16(od, ed, val.size()); od += 2; 27 | std::copy(val.begin(), val.end(), od); od += val.size(); 28 | }; 29 | addkv("Nabcde", std::string{"\x01\x00\x00\xFF",4}, 123); 30 | addkv("Nbcdef", std::string{"\x02\x00\x00\xFF",4}, 125); 31 | addkv("Ncdef", std::string{"\x03\x00\x00\xFF",4}, 127); 32 | 33 | return page; 34 | } 35 | 36 | TEST_CASE("TestIndexPage") { 37 | auto tstdata = CreateTestIndexPage(2048); 38 | auto page = std::make_unique(std::make_shared(tstdata), 1, 2048); 39 | page->readindex(); 40 | 41 | CHECK( page->isindex() == true ); 42 | CHECK( page->isleaf() == false ); 43 | 44 | CHECK( page->getpage(-1) == 122 ); 45 | CHECK( page->getpage(0) == 123 ); 46 | CHECK( page->getpage(1) == 125 ); 47 | CHECK( page->getpage(2) == 127 ); 48 | 49 | CHECK_THROWS( page->getpage(3) ); 50 | 51 | CHECK( page->getkey(0) == "Nabcde" ); 52 | CHECK( page->getkey(1) == "Nbcdef" ); 53 | CHECK( page->getkey(2) == "Ncdef" ); 54 | 55 | CHECK_THROWS( page->getkey(3) ); 56 | 57 | CHECK_FALSE(page->getval(0) == std::string{"fail"}); 58 | 59 | CHECK( page->getval(0) == (std::string{"\x01\x00\x00\xFF",4}) ); 60 | CHECK( page->getval(1) == (std::string{"\x02\x00\x00\xFF",4}) ); 61 | CHECK( page->getval(2) == (std::string{"\x03\x00\x00\xFF",4}) ); 62 | 63 | CHECK_FALSE(page->find("fail") == (BasePage::result{REL_EQUAL,2})); 64 | 65 | CHECK( page->find("N") == (BasePage::result{REL_RECURSE,-1}) ); 66 | CHECK( page->find("Nabcde") == (BasePage::result{REL_EQUAL,0}) ); 67 | CHECK( page->find("Nbcdef") == (BasePage::result{REL_EQUAL,1}) ); 68 | CHECK( page->find("Nbzzzz") == (BasePage::result{REL_RECURSE,1}) ); 69 | CHECK( page->find("Ncdef") == (BasePage::result{REL_EQUAL,2}) ); 70 | CHECK( page->find("Nzzzz") == (BasePage::result{REL_RECURSE,2}) ); 71 | } 72 | std::string CreateTestLeafPage(int pagesize) 73 | { 74 | std::string page(pagesize, char(0)); 75 | 76 | auto oi = page.begin(); 77 | auto ei = page.begin() + pagesize/2; 78 | 79 | auto od = page.begin() + pagesize/2; 80 | auto ed = page.end(); 81 | 82 | auto et = EndianTools(); 83 | et.setle32(oi, ei, 0); oi += 4; 84 | et.setle16(oi, ei, 3); oi += 2; 85 | 86 | auto addkv = [&](const std::string& key, const std::string& val, int indent) { 87 | et.setle32(oi, ei, indent); oi += 4; 88 | et.setle16(oi, ei, od-page.begin()); oi += 2; 89 | 90 | et.setle16(od, ed, key.size()-indent); od += 2; 91 | std::copy(key.begin()+indent, key.end(), od); od += key.size()-indent; 92 | et.setle16(od, ed, val.size()); od += 2; 93 | std::copy(val.begin(), val.end(), od); od += val.size(); 94 | }; 95 | addkv("Nabcde", std::string{"\x01\x00\x00\xFF",4}, 0); 96 | addkv("Nbcdef", std::string{"\x02\x00\x00\xFF",4}, 1); 97 | addkv("Ncdef", std::string{"\x03\x00\x00\xFF",4}, 1); 98 | 99 | return page; 100 | } 101 | void TestLeafPage() 102 | { 103 | auto tstdata = CreateTestLeafPage(2048); 104 | auto page = std::make_unique(std::make_shared(tstdata), 1, 2048); 105 | page->readindex(); 106 | 107 | CHECK( page->isindex() == false ); 108 | CHECK( page->isleaf() == true ); 109 | 110 | CHECK_THROWS( page->getpage(0) ); 111 | 112 | CHECK( page->getkey(0) == "Nabcde" ); 113 | CHECK( page->getkey(1) == "Nbcdef" ); 114 | CHECK( page->getkey(2) == "Ncdef" ); 115 | 116 | CHECK_THROWS( page->getkey(3) ); 117 | 118 | CHECK_FALSE( page->getval(0) == std::string{"fail"} ); 119 | 120 | CHECK( page->getval(0) == (std::string{"\x01\x00\x00\xFF",4}) ); 121 | CHECK( page->getval(1) == (std::string{"\x02\x00\x00\xFF",4}) ); 122 | CHECK( page->getval(2) == (std::string{"\x03\x00\x00\xFF",4}) ); 123 | 124 | CHECK_FALSE( page->find("fail") == (BasePage::result{REL_EQUAL,2}) ); 125 | 126 | CHECK( page->find("N") == (BasePage::result{REL_GREATER,0}) ); 127 | CHECK( page->find("Nabcde") == (BasePage::result{REL_EQUAL,0}) ); 128 | CHECK( page->find("Nbcdef") == (BasePage::result{REL_EQUAL,1}) ); 129 | CHECK( page->find("Nbzzzz") == (BasePage::result{REL_LESS,1}) ); 130 | CHECK( page->find("Ncdef") == (BasePage::result{REL_EQUAL,2}) ); 131 | CHECK( page->find("Nzzzz") == (BasePage::result{REL_LESS,2}) ); 132 | } 133 | /* streamhelper unittest */ 134 | TEST_CASE("test_streamhelper") 135 | { 136 | auto f = makehelper(std::make_shared("3456789a")); 137 | 138 | // read various chunks of the stream. 139 | CHECK( f.getdata(3) == "345" ); 140 | CHECK( f.getdata(8) == "6789a" ); 141 | CHECK( f.getdata(8) == "" ); 142 | f.seekg(-1, std::ios_base::end); 143 | CHECK( f.getdata(8) == "a" ); 144 | f.seekg(3); 145 | CHECK( f.getdata(2) == "67" ); 146 | f.seekg(-2,std::ios_base::cur); 147 | CHECK( f.getdata(2) == "67" ); 148 | f.seekg(2,std::ios_base::cur); 149 | CHECK( f.getdata(2) == "a" ); 150 | 151 | f.seekg(0); 152 | CHECK( f.get32le() == 0x36353433 ); 153 | CHECK( f.get32be() == 0x37383961 ); 154 | 155 | // seek to end of stream 156 | f.seekg(8); 157 | 158 | // read at EOF should return an empty string. 159 | CHECK( f.getdata(1) == "" ); 160 | 161 | // seek beyond end of stream should throw an exception 162 | CHECK_THROWS( f.seekg(9) ); 163 | CHECK_THROWS( f.getdata(1) ); 164 | } 165 | /* unittest for EndianTools */ 166 | TEST_CASE("test_EndianTools") 167 | { 168 | EndianTools et; 169 | 170 | uint8_t b[64]; 171 | int i; 172 | for (i=0 ; i<64 ; i++) 173 | b[i] = 0xAA; 174 | 175 | CHECK_THROWS( et.setle64(b, b+7, 0x12345678LL) ); 176 | 177 | et.setle64(b, b+8, 0x9abcdef12345678LL); 178 | 179 | uint8_t little_endian_number[9] = { 0x78, 0x56, 0x34, 0x12, 0xef, 0xcd, 0xab, 0x09, 0xAA }; 180 | CHECK(std::equal(b, b+9, little_endian_number)); 181 | 182 | CHECK_THROWS( et.getle64(b, b+7) ); 183 | CHECK(et.getle64(b, b+8)==0x9abcdef12345678LL); 184 | 185 | et.setbe64(b, b+8, 0x9abcdef12345678LL); 186 | 187 | uint8_t big_endian_number[9] = { 0x09,0xab,0xcd,0xef,0x12,0x34,0x56,0x78,0xAA }; 188 | CHECK(std::equal(b, b+9, big_endian_number)); 189 | 190 | CHECK_THROWS( et.getbe64(b, b+7) ); 191 | CHECK(et.getbe64(b, b+8)==0x9abcdef12345678LL); 192 | } 193 | /* unittest for sectionstream */ 194 | TEST_CASE("test_StreamSection") 195 | { 196 | auto f = makehelper(std::make_shared(std::make_shared("0123456789abcdef"), 3, 8)); 197 | 198 | CHECK( f.getdata(3) == "345" ); // should return what was asked for. 199 | CHECK( f.getdata(8) == "6789a" ); // should return less than requested 200 | CHECK( f.getdata(8) == "" ); // should return nothing 201 | f.seekg(-1, std::ios_base::end); 202 | CHECK( f.getdata(8) == "a" ); 203 | f.seekg(3); 204 | CHECK( f.getdata(2) == "67" ); 205 | f.seekg(-2,std::ios_base::cur); 206 | CHECK( f.getdata(2) == "67" ); 207 | f.seekg(2,std::ios_base::cur); 208 | CHECK( f.getdata(2) == "a" ); 209 | 210 | f.seekg(8); 211 | CHECK( f.getdata(1) == "" ); 212 | //CHECK_THROWS( f.seekg(9) ); 213 | } 214 | 215 | TEST_CASE("test_NodeValues") 216 | { 217 | CHECK( NodeValues::getuint("\x12\x34\x45\x56\x67\x78\x89\x9a") == 0x9a89786756453412 ); 218 | CHECK( NodeValues::getuint("\x12\x34\x45\x56") == 0x56453412 ); 219 | CHECK( NodeValues::getuint("\x12\x34") == 0x3412 ); 220 | CHECK( NodeValues::getuint("\x12") == 0x12 ); 221 | } 222 | 223 | TEST_CASE("test_Packer") 224 | { 225 | std::string val("\x00\x04\x88\xf1\x00\x04\xc0\x20\x00\x04\x01\x88\xf2\x00\x04\xc0\x20\x00\x04\x01\x88\xf3\x00\x04\xc0\x25\x50\x04\x11\x88\xf4\x00\x04\xc0\x25\x50\x04\x11\x02", 39); 226 | 227 | DwordVector nums; 228 | idaunpack(val.begin(), val.end(), std::back_inserter(nums)); 229 | 230 | DwordVector wrong = { 0x00 }; 231 | CHECK( nums != wrong ); 232 | CHECK_FALSE( nums == wrong ); 233 | 234 | DwordVector check = { 0x00,0x04,0x8f1,0x00,0x04,0x0200004,0x01,0x8f2,0x00,0x04,0x0200004,0x01,0x8f3,0x00,0x04,0x0255004,0x11,0x8f4,0x00,0x04,0x0255004,0x11,0x02 }; 235 | 236 | CHECK(nums == check); 237 | } 238 | 239 | 240 | -------------------------------------------------------------------------------- /tests/unittestframework.h: -------------------------------------------------------------------------------- 1 | /* 2 | * catch is available from: https://github.com/catchorg/Catch2 3 | * doctest is available from https://github.com/onqtam/doctest 4 | * 5 | * 6 | * Doctest is almost a drop-in replacement for Catch. 7 | * Though Catch has a few more features, and works without any restrictions, 8 | * doctest has much faster compilation times, our 44 unittests build 9 | * takes about 13 minutes to build with catch, or about 3.5 minutes when 10 | * using doctest. 11 | * 12 | */ 13 | #if !defined(WITH_CATCH) && !defined(WITH_DOCTEST) 14 | #define WITH_DOCTEST 15 | #endif 16 | 17 | #ifdef WITH_CATCH 18 | #ifdef UNITTESTMAIN 19 | #define CATCH_CONFIG_MAIN 20 | #endif 21 | #define CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS 22 | #include "contrib/catch.hpp" 23 | 24 | #define SKIPTEST , "[!hide]" 25 | 26 | // doctest has suites, catch doesn't. 27 | #define TEST_SUITE(x) namespace 28 | 29 | #elif defined(WITH_DOCTEST) 30 | #ifdef UNITTESTMAIN 31 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 32 | #endif 33 | #include 34 | #include "contrib/doctest.h" 35 | #define SECTION(...) SUBCASE(__VA_ARGS__) 36 | #define SKIPTEST * doctest::skip(true) 37 | 38 | #define CHECK_THAT(a, b) 39 | #else 40 | #error define either WITH_CATCH or WITH_DOCTEST 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /tests/unittests.cpp: -------------------------------------------------------------------------------- 1 | #define UNITTESTMAIN 2 | #include "unittestframework.h" 3 | 4 | // including all here again, so we will catch linking errors. 5 | #include 6 | 7 | TEST_CASE("main") { 8 | CHECK(true); 9 | } 10 | 11 | --------------------------------------------------------------------------------