[
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.10)\nproject(idbutil)\nlist(APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/cmake_find\")\ninclude(boilerplate)\n\nfind_package(idasdk REQUIRED)\nfind_package(cpputils REQUIRED)\nfind_package(libgmp)\n\nadd_library(idblib INTERFACE)\ntarget_include_directories(idblib INTERFACE include)\n\nif(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR BUILD_TOOLS)\n    add_subdirectory(tools)\nendif()\ninclude(CTest)\nif(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING OR BUILD_ALL_TESTS)\n    add_subdirectory(tests)\nendif()\n\n\n"
  },
  {
    "path": "Findidbutil.cmake",
    "content": "if (TARGET idbutil)\n    return()\nendif()\nfind_path(IDBUTIL_DIR NAMES include/idblib/idb3.h PATHS ${CMAKE_SOURCE_DIR}/symlinks/idbutil)\nif(IDBUTIL_DIR STREQUAL \"IDBUTIL_DIR-NOTFOUND\")\n    include(FetchContent)\n    FetchContent_Populate(idbutil\n        GIT_REPOSITORY https://github.com/nlitsme/idbutil)\n    set(IDBUTIL_DIR ${idbutil_SOURCE_DIR})\nelse()\n    set(idbutil_BINARY_DIR ${CMAKE_BINARY_DIR}/idbutil-build)\nendif()\n\nadd_subdirectory(${IDBUTIL_DIR} ${idbutil_BINARY_DIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(idbutil REQUIRED_VARS IDBUTIL_DIR)\n\n\n"
  },
  {
    "path": "IDB-FORMAT.md",
    "content": "\nIDApro databases\n==================\n\nAn IDApro database consists of one large file which contains several sections.\nAt the start of the `idb` or `i64` file there is a list of fileoffsets pointing\nto these sections.  Sections can optionally be stored compressed.\nWhen a database is opened by IDA the sections are extracted from the main data file\nand stored in separate files. When you only need to read from the database, and don't\nwant to change anything, the splitting into `id0`, `id1`, `nam` and `til` files is not\nnescesary, IDApro does this anyway, since it expect the user to make changes to the database.\nVery old IDApro versions ( v1.6 and v2.0 ) store the sections separately, such that\nthere could only be one database per directory.\n\n| index | extension | contents                   |\n| :---- | :-------- | :------------------------- |\n|   0   |  id0      | A btree key/value database |\n|   1   |  id1      | Flags for each byte        |\n|   2   |  nam      | A list of named offsets    |\n|   3   |  seg      | Unknown                    |\n|   4   |  til      | Type information           |\n|   5   |  id2      | Unknown                    |\n\nOlder ida versions don't have the id2 file.\nNewer ida versions don't have the seg file.\nNewer ida versions use 64 bit file offsets, so IDA can support files larger than 4GB.\nThere is no difference in the IDB header between 32 bit ( with `.idb` extension ) and 64 bit ( with `.i64` extension ) databases.\n\n## ID0 section.\n\nThe ID0 sections contains a b-tree database, This is a single large key-value database, like leveldb.\nThere are three main groups of key types:\n\n * Bookkeeping, so IDApro can quickly decide what the next free nodeid is. These keys all start with a '$' (dollar) sign.\n    * `$ MAX LINK`\n    * `$ MAX NODE`\n    * `$ NET DESC`\n * Nodes, keys starting with a '.' (dot).\n    * followed by an address, or internal nodeid.\n       * 32 bit databases use 32 bit addresses, 64 bit databases use 64 bit addresses here.\n       * internal nodeid's have the upper 8 bits set to one,\n         so `0xFF000000` for a 32 bit database, or `0xFF00000000000000` for a 64 bit database.\n    * a tag,  `A` for altvals, `S` for supvals, etc. See netnode.hpp in the idasdk.\n    * optionally followed by an index or hashkey value, depending on the tag.\n    * both the address and index value are encoded in bigendian byte order.\n * Name index, keys starting with an `N`, followed by a name.\n   The value being a 32 or 64 bit offset.\n    * names up to 511 are encoded as plain strings. longer names start with a NUL byte, followed by a blob index.\n      pointing to a blob at special nodeid `0xFF000000(00000000)`.\n    * the maximum name length is 32 * 1024 characters.\n * Very old ida versions had keys starting with lowercase 'n', and '-' (minus).\n * The maximum key size if 512 bytes, including dots, 'N', etc.\n\nThe range of internal nodeid's is the reason you cannot have code or data in\nyour disassembly at addresses starting with `0xFF000000(00000000)`. IDA will allow you to\ncreate such segments manually. Doing so will usually result in corrupted databases.\n\nThere are two types of names:\n * Internal, pointing to internal nodeid's. Examples: `$ structs`, `Root Node`. Most have a space in them.\n * Labels, pointing to addresses in the disassembly.\n\nThe maximum value size is 1024 bytes.\nSeveral types of values:\n * Integers, encoded in little endian byte order.\n * Strings are sometimes NUL terminated, sometimes not.\n * In several cases structured information is stored in a _packed_ format, see below.\n\n### packed values\n\nPacked values are used among others for structure and segment definitions.\n\nIn packed data:\n * Values in the range 0x00-0x7f are stored in a single byte.\n * Values in the range 0x80-0x3fff are stored ORRED with 0x8000.\n * Values in the range 0x4000-0x1fffffff are stored ORRED with 0xC000000.\n * Larger 32 bit values are stored prefixed with a 0xFF byte.\n * 64 bit values are stored as two consecutive numbers.\n * All values are stored in big-endian byte order.\n\n\n### The B-tree format\n\nThe file is organised in 8kbyte pages, where the first page contains a header with\npagesize, pointer to a list of free pages, pointer to the root of the page tree,\nthe number of records, and number of pages.\n\nThere are two types of pages, leaf pages, which don't contain pointers to other\npages, but only key-value records. And index pages, with a _preceeding_\npointer, and where all key-value records contain a pointer to a page where all\nkeys in the pointed-to page have values greater than the key containing the\npage pointer.  This makes it very efficient to lookup records by key.\n\nThe page tree looks like this. Between brackets are key values, the pointer marked\nwith a `*` (STAR) is the _preceeding_ pointer. Values are not shown.\n\n\n                       *-------->[00]\n             *------>[02]---+    [01]\n    root ->[08]---+  [05]-+ |    \n           [17]-+ |       | +--->[03]\n                | |       |      [04]\n                | |       |      \n                | |       +----->[06]\n                | |              [07]\n                | |\n                | |    *-------->[09]\n                | +->[11]---+    [10]\n                |    [14]-+ |  \n                |         | +--->[12]\n                |         |      [13]\n                |         |\n                |         +----->[15]\n                |                [16]\n                |       \n                |      *-------->[18]\n                +--->[20]---+    [19]\n                     [23]-+ |  \n                          | +--->[21]\n                          |      [22]\n                          |\n                          +----->[24]\n                                 [25]\n\n\n\n\nEach page has a small header, with a pointer to a preceeding page, and a record count.\nFor Leaf pages the _preceeding_ pointer is zero.\n\nFollowing the header there is an index containing offsets to the actual records in the page,\nand a pointer to the next level index or leaf page.\nThe records are stored as _keylength_, keydata, _datalength_, data.\nAll records in the level below an index are guaranteed to have a key greater than the key\nin the index.\n\nIn leaf pages consecutive entries will often have keys which are very similar. The index stores\nan offset into the key from which the keys differ, only the part that differs is stored.\n\n| key                                | binary representation   | compressed key\n| :--------------------------------- | :---------------------- | :------------------\n| ('.', 0xFF000002, 'N')             | 2eff0000024e            | (0, 2eff0000024e)         \n| ('.', 0xFF000002, 'S', 0x00000001) | 2eff0000025300000001    | (5, 5300000001)\n| ('.', 0xFF000002, 'S', 0x00000002) | 2eff0000025300000002    | (9, 02)\n\n\n## The ID1 section\n\nThe ID1 section contains the flag values as returned by the idc `GetFlags` function.\nIt starts with a list of file regions, followed by flags for each byte.\n\n\n## Netnodes\n\nThe highlevel view of the `ID0` database is that of netnodes, as partially documented\nin the idasdk.\n\nThe most important nodes are:\n * `Root Node`\n * lists: `$ structs`, `$ enums`, `$ scripts`\n   * the values in a list are stored in the altnodes of the list node.\n   * the values are one more than the actual nodeid pointed to:\n     a list pointing to struct id's 0xff000bf6, 0xff000c01\n     would contain : 0xff000bf7, 0xff000c02\n * `$ funcs`\n * `$ fileregions`, `$ segs`, '$ srareas'\n * '$ entry points'\n\n\n### structs\n\nThe main struct node:\n\n| node | contents\n| :--- | :----\n| (id, 'N')    | the struct name\n| (id, 'M', 0) | packed member info, nodeids for members.\n\n\nThe struct member nodes:\n\n| node | contents\n| :--- | :----\n| (id, 'N')    | the member name\n| (id, 'M', 0) | packed member info\n| (id, 'A', 3) | enum id\n| (id, 'A', 11) | struct id\n| (id, 'A', 16) | string type\n| (id, 'S', 0) | member comment\n| (id, 'S', 1) | repeatable member comment\n| (id, 'S', 9) | offset spec\n| (id, 'S', 0x3000) | typeinfo\n\n\n### history\n\nThe `$ curlocs` list contains several location histories:\n\nFor example, the `$ IDA View-A` netnode contains the following keys:\n * `A 0` - highest history supval item \n * `A 1` - number of history items\n * `A 2` - object type: `idaplace_t`\n * `S <num>` - packed history item: itemlinenr, ea\\_t, int, int, colnr, rownr\n\n \n\n### normal addresses\n\nIn the SDK, in the file `nalt.hpp` there are many more items defined.\nThese are some of the regularly used ones.\n\n| key | value | description\n| :-- | :---- | :----------\n| (addr, 'D', fromaddr) | reftype | data xref from\n| (addr, 'd', toaddr) | reftype | data xref to\n| (addr, 'X', fromaddr) | reftype | code xref from\n| (addr, 'x', toaddr) | reftype | code xref to\n| (addr, 'N')         | string | global label\n| (addr, 'A', 1)      | jumptableid+1 | jumptable target\n| (addr, 'A', 2)      | nodeid+1 | hexrays info\n| (addr, 'A', 3)      | structid+1  | data type\n| (addr, 'A', 8)      | dword    | additional flags\n| (addr, 'A', 0xB)    | enumid+1 | first operand enum type\n| (addr, 'A', 0x10)   | dword  | string type\n| (addr, 'A', 0x11)   | dword  | align type\n| (addr, 'S', 0)      | string | comment\n| (addr, 'S', 1)      | string | repeatable comment\n| (addr, 'S', 4)      | data   | constant pool reference\n| (addr, 'S', 5)      | data   | array\n| (addr, 'S', 8)      | data   | jumptable info\n| (addr, 'S', 9)      | packed | first operand offset spec\n| (addr, 'S', 0xA)    | packed | second operand offset spec\n| (addr, 'S', 0x1B)    | data  |  ?\n| (addr, 'S', 1000+linenr) | string | anterior comment\n| (addr, 'S', 0x1000) | packed | SP change point\n| (addr, 'S', 0x3000) | data | function prototype\n| (addr, 'S', 0x3001) | data | argument list\n| (addr, 'S', 0x4000+n) | packed blob | register renaming\n| (addr, 'S', 0x5000) | packed blob | function's local labels\n| (addr, 'S', 0x6000) | data | register args\n| (addr, 'S', 0x7000) | packed | function tails\n| (addr, 'S', 0x7000) | dword  | tail backreference\n| \n"
  },
  {
    "path": "Jenkinsfile",
    "content": "pipeline {\n  agent { label \"windows\" }\n  stages {\n    stage(\"clean\") { steps { sh '''git clean -dfx''' } }\n    stage(\"windows-build\") {\n      steps { \nsh '''#!/bin/bash\nset -e\n. /c/local/msvcenv.sh\nexport BOOST_ROOT=c:/local/boost_1_74_0\nexport IDASDK=c:/local/idasdk_pro82\nmake vc\n'''\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Willem Hengeveld <itsme@xs4all.nl>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "CMAKEARGS+=$(if $(D),-DCMAKE_BUILD_TYPE=Debug,-DCMAKE_BUILD_TYPE=Release)\nCMAKEARGS+=$(if $(COV),-DOPT_COV=1)\nCMAKEARGS+=$(if $(PROF),-DOPT_PROF=1)\nCMAKEARGS+=$(if $(LIBCXX),-DOPT_LIBCXX=1)\n\nCMAKE=cmake\nJOBSFLAG=$(filter -j%,$(MAKEFLAGS))\ncmake:\n\t$(CMAKE) -B build . $(CMAKEARGS)\n\t$(MAKE) -C build  $(JOBSFLAG) $(if $(V),VERBOSE=1)\n\nvc:\n\t\"C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin/cmake.exe\" -G\"Visual Studio 16 2019\" -B build . $(CMAKEARGS)\n\t\"C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/MSBuild/Current/Bin/amd64/MSBuild.exe\" build/*.sln -t:Rebuild\n\nctest: TEST=1\nctest: cmake\n\tcd build && ctest --verbose\n\n\nclean: \n\t$(RM) -r build CMakeFiles CMakeCache.txt CMakeOutput.log\n\n\n"
  },
  {
    "path": "Makefile.linux",
    "content": "cmake:\n\tcmake -B build . $(if $(D),-DCMAKE_BUILD_TYPE=Debug,-DCMAKE_BUILD_TYPE=Release) $(CMAKEARGS)\n\t$(MAKE) -C build $(if $(V),VERBOSE=1)\n\n# either specify `idasdk` and `idabin` in this config file,\n# or pass them as variables on the make commandline.\n-include ../idacfg.mk\n\nspace=$(empty) $(empty)\nescapespaces=$(subst $(space),\\ ,$1)\nfileexists=$(wildcard $(call escapespaces,$1))\n\nifeq ($(call fileexists,$(idasdk)/include/ida.hpp),)\n$(error The `idasdk` variable does not point to a directory containing an include directory with the ida headers.)\nendif\n\nifneq ($(wildcard $(SystemRoot)/explorer.exe $(SYSTEMROOT)/explorer.exe),)\nOSTYPE=windows\nCFLAGS+=-D__NT__=1\n\nIDA64LIB=$(idasdk)/lib/x64_win_vc_64/ida.lib\nIDA32LIB=$(idasdk)/lib/x64_win_vc_32/ida.lib\nL=.dll\nO=.obj\nDLLFLAGS=/dll\nendif\nifneq ($(wildcard /System/Library/Extensions),)\nOSTYPE=darwin\nCFLAGS+=-D__MAC__=1\n\nIDA64LIB=$(idasdk)/lib/x64_mac_clang_64/libida64.dylib\nIDA32LIB=$(idasdk)/lib/x64_mac_clang_32/libida.dylib\nL=.dylib\nO=.o\nDLLFLAGS=-dynamiclib\nendif\nifneq ($(wildcard /sbin/modprobe),)\nOSTYPE=linux\nCFLAGS+=-D__LINUX__=1\n\nIDA64LIB=$(idasdk)/lib/x64_linux_gcc_64/libida64.so\nIDA32LIB=$(idasdk)/lib/x64_linux_gcc_32/libida.so\nL=.so\nO=.o\nDLLFLAGS=--shared\nendif\n\nAPPS=idbtool unittests\nall: $(APPS)\nclean: \n\t$(RM) $(APPS)  $(wildcard *.o *.obj)\n\t$(RM) -r build CMakeFiles CMakeCache.txt CMakeOutput.log\n\nunittests: $(notdir $(subst .cpp,$(O),$(wildcard tests/*.cpp)))\n\t$(CXX) $(LDFLAGS) -o $@  $^\nidbtool: idbtool$(O)\n\nldflags_idbtool=-lz -L/usr/local/lib -lgmp\n\nCFLAGS+=-fPIC $(if $(D),-O0,-O3) -g -Wall -I /usr/local/include -I submodules/cpputils -I $(idasdk)/include/ -I .\n\nCFLAGS+=-DUSE_STANDARD_FILE_FUNCTIONS  \nCFLAGS+=-DUSE_DANGEROUS_FUNCTIONS\nifneq ($(OSTYPE),windows)\nCFLAGS+=-DHAVE_LIBGMP\nendif\n\n# .. todo: on windows doctest build fails with DOCTEST_CHECK_THROWS - ident not found.\nCFLAGS+=-DUSE_DOCTEST\n#CFLAGS+=-DUSE_CATCH\nCFLAGS+=-DNOMINMAX -DWIN32_LEAN_AND_MEAN\n\nLDFLAGS+=-g -Wall\n\nifeq ($(OSTYPE),windows)\nCFLAGS+=-std:c++17\nelse\nCFLAGS+=-std=c++17\nendif\n\n%$(O): tests/%.cpp\n\t$(CXX) $(CFLAGS) -c $^ -o $@\n\n%$(O): %.cpp\n\t$(CXX) -c $^ -o $@ $(cflags_$(basename $(notdir $@))) $(CFLAGS)\n\n%: %$(O)\n\t$(CXX)    $^ -o $@ $(ldflags_$(basename $(notdir $@))) $(LDFLAGS)\n\ninstall:\n\tcp idbtool ~/bin/\n\npull:\n\tgit submodule update --recursive --remote\n\n\n"
  },
  {
    "path": "README.md",
    "content": "IDBTOOL\n=======\n\nA tool for extracting information from IDA databases.\n`idbtool` knows how to handle databases from all IDA versions since v2.0, both `i64` and `idb` files.\nYou can also use `idbtool` to recover information from unclosed databases.\n\n`idbtool` works without change with IDA v7.0.\n\n\nMuch faster than loading a file in IDA\n--------------------------------------\n\nWith idbtool you can search thousands of .idb files in seconds.\n\nMore precisely: on my laptop it takes:\n\n *  1.5 seonds to extract 143 idc scripts from 119 idb and i64 files.\n *  3.8 seonds to print idb info for 441 files.\n *  5.6 seconds to extract 281 enums containing 4726 members from 35 files.\n * 67.8 seconds to extract 5942 structs containing 33672 members from 265 files.\n\nLoading an approximately 5 Gbyte idb file in IDA, takes about 45 minutes.\nWhile idb3.h takes basically no time at all, no more than a few milliseconds.\n\n\nDownload\n========\n\nTwo versions of this tool exist:\n\nOne written in python\n * https://github.com/nlitsme/pyidbutil\n\nOne written in C++\n * https://github.com/nlitsme/idbutil\n\nBoth repositories contain a library which can be used for reading `.idb` or `.i64` files.\n\n\nAn IDA Pro plugin making use of `idb3.h` can be found here:\n * https://github.com/nlitsme/idbimport\n\nThis is a plugin making it easy to copy scripts, structs or enums from recent ida databases.\n\n\nUsage\n=====\n\nUsage: \n\n    idbtool [options] [database file(s)] [-- address-list]\n\n * `-n` or `--names`  will list all named values in the database.\n * `-s` or `--scripts` will list all scripts stored in the database.\n * `-u` or `--structs` will list all structs stored in the database.\n * `-e` or `--enums` will list all enums stored in the database.\n * `-i` or `--info` will print some general info about the database. \n\n * `-a`  list all names, including ..todo..\n * `-d`  dump btree page tree contents.\n * `--inc`, `--dec` list all records in ascending / descending order.\n * `-q` or `--query` search specific records in the database.\n * `-m` or `--limit` limit the number of results returned by `-q`.\n\nAll addresses after `--` will be printed as `symbol+offset`.\n\nQuery\n-----\n\nQueries need to be specified last on the command line.\n\nExample:\n\n    idbtool [database file(s)]  --query  \"Root Node;V\"\n\nWill list the source binary for all the databases specified on the command line.\n\nA query is a string with the following format:\n\n * [==,<=,>=,<,>]  - optional relation, default: ==\n * a base node key:\n    * a DOT followed by the numeric value of the nodeid.\n    * a HASH followed by the numeric value of the system-nodeid.\n    * a QUESTION followed by the name of the node. -> a 'N'ame node\n    * the name of the node.  -> the name is resolved, results in a '.'Dot node\n * an optional tag ( A for Alt, S for Supval, etc )\n * an optional index value\n\nExample queries:\n * `Root Node;V` -> prints record containing the source binary name\n * `?Root Node` -> prints the Name record pointing to the root\n * `>Root Node` -> prints the first 10 records starting with the root node id.\n * `<Root Node` -> prints the 10 records startng with the records before the root node.\n * `.0xff000001;N` -> prints the root node name entry.\n * `#1;N` -> prints the root node name entry.\n\nList the highest node and following record in the database in two different ways,\nthe first: starting at the first record below `ffc00000`, and listing the next.\nThe second: starting at the first record after `ffc00000`, and listing the previous:\n * `--query \"<#0xc00000\"  --limit 2 --inc -v`\n * `--query \">#0xc00000\"  --limit 2 --dec -v`\n\nNote that this should be the nodeid in the `$ MAX NODE` record.\n\nList the last two records:\n * `--limit 2 --dec  -v`\n\nList the first two records, the `$ MAX LINK` and `$ MAX NODE` records:\n * `--limit 2 --inc -v`\n\n\nA full database dump\n--------------------\n\nSeveral methods exist for printing all records in the database. This may be useful if\nyou want to investigate more of IDA''s internals. But can also be useful in recovering\ndata from corrupted databases.\n\n * `--inc`, `--dec` can be used to enumerate all b-tree records in either forward, or backward direction.\n * `--id0`  walks the page tree, instead of the b-tree, printing the contents of each page\n\n\nLIBRARY\n=======\n\nThe header file `idb3.h` contains a library for reading from IDA Pro databases.\n\n\n## IDBFile\n\nClass for accessing sections of an `.idb` or `.i64` file.\n\nConstructor Parameters:\n * `std::shared_ptr<std::istream>` ( typedefed to `stream_ptr` )\n\nMethods:\n * `stream_ptr getsection(int)`\n\n \n\n## ID0File, ID1File, NAMFile\n\nConstructor Parameters:\n * `IDBFile& idb`\n * `stream_ptr`\n\nConstant\n * `INDEX`  - the argument for `idb.getsection`\n\n## ID0File\n\nMethods\n * `Cursor find(relation_t, nodeid, ...)` \n    * `...`  can be: \n       * tag, index\n       * tag, hash\n       * tag\n * `Cursor find(relation_t, std::string key)`\n * `std::string blob(nodeid, tag, ...)`\n * `uint64_t node(std::string name)`\n\n * `bool is64bit()`\n    * `true` for `.i64` files.\n\n * `uint64_t nodebase()`\n    * return `0xFF000000(00000000)` for 32/64 bit databases.\n\n * `void enumlist(uint64_t nodeid, char tag, CB cb)`\n    * call `cb` for each value in the list.\n\nConvenience Methods\n * `std::string getdata(ARGS...args)`\n * `std::string getstr(ARGS...args)`\n * `uint64_t getuint(ARGS...args)`\n * `uint64_t getuint(BtreeBase::Cursor& c)`\n * `std::string getname(uint64_t node)`\n\n\n\n## ID1File\n\nMethods\n * `uint32_t GetFlags(uint64_t ea)`\n\n\n## NAMFile\n\nMethods\n * `uint64_t findname(uint64_t ea)`\n\n\n## Cursor\n\nMethods\n * `void next()`\n    * move cursor to the next btree record\n * `void prev()`\n    * move cursor to the previous btree record\n * `bool eof()`\n    * did we reach the start/end of the btree?\n * `std::string `getkey()`\n    * return the key pointed to by the cursor\n * `std::string `getval()`\n    * return the value pointed to by the cursor\n\nTODO\n====\n\n * add option to list all comments stored in the database\n * support compressed sections\n * add option to list flags for a list of addresses.\n\nAuthor\n======\n\nWillem Hengeveld <itsme@xs4all.nl>\n\n"
  },
  {
    "path": "cmake_find/Findcpputils.cmake",
    "content": "if (TARGET cpputils)\n    return()\nendif()\n\n# NOTE: you can avoid downloading cpputils, by symlinking to a downloaded version here:\nfind_path(CPPUTILS_DIR NAMES include/cpputils/string-lineenum.h PATHS ${CMAKE_SOURCE_DIR}/symlinks/cpputils)\nif(CPPUTILS_DIR STREQUAL \"CPPUTILS_DIR-NOTFOUND\")\n    include(FetchContent)\n    FetchContent_Populate(cpputils\n        GIT_REPOSITORY https://github.com/nlitsme/cpputils)\n    set(CPPUTILS_DIR ${cpputils_SOURCE_DIR})\nelse()\n    set(cpputils_BINARY_DIR ${CMAKE_BINARY_DIR}/cpputils-build)\nendif()\n\nadd_subdirectory(${CPPUTILS_DIR} ${cpputils_BINARY_DIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(cpputils REQUIRED_VARS CPPUTILS_DIR)\n"
  },
  {
    "path": "cmake_find/Finddoctest.cmake",
    "content": "list(APPEND CMAKE_MODULE_PATH \"/usr/lib/cmake/doctest/\")\nif (TARGET doctest)\n    return()\nendif()\nif (TARGET doctest::doctest)\n    return()\nendif()\nfile(GLOB DOCTEST_DOCTEST_DIRS /usr/include /usr/local/include /usr/local/opt/doctest/include)\nfind_path(DOCTEST_DOCTEST_DIR NAMES doctest/doctest.h PATHS ${DOCTEST_DOCTEST_DIRS})\nif (DOCTEST_DOCTEST_DIR STREQUAL \"DOCTEST_DOCTEST_DIR-NOTFOUND\")\n    include(FetchContent)\n    FetchContent_Populate(\n      doctest\n      GIT_REPOSITORY https://github.com/doctest/doctest.git\n    )\n    list(APPEND CMAKE_MODULE_PATH \"${doctest_SOURCE_DIR}/scripts/cmake/\")\n    set(DOCTEST_DOCTEST_DIR ${doctest_SOURCE_DIR})\nelse()\n    set(doctest_BINARY_DIR ${CMAKE_BINARY_DIR}/doctest-build)\nendif()\n\nadd_library(doctest INTERFACE)\ntarget_include_directories(doctest INTERFACE ${DOCTEST_DOCTEST_DIR})\nadd_library(doctest::doctest ALIAS doctest)\n"
  },
  {
    "path": "cmake_find/Findidasdk.cmake",
    "content": "if (TARGET idasdk)\n    return()\nendif()\n# note: this depends partially on my local install\nfind_path(IDASDK_PATH NAMES include/netnode.hpp PATHS\n    $ENV{IDASDK}\n    $ENV{HOME}/src/idasdk_pro82\n    $ENV{HOME}/src/idasdk_pro80\n    c:/local/idasdk_pro82\n    c:/local/idasdk77)\nif (IDASDK_PATH STREQUAL \"IDASDK_PATH-NOTFOUND\")\n    message(FATAL_ERROR \"IDASDK not found on ${CMAKE_SYSTEM_NAME}.\")\nendif()\nif(WIN32)\n    # note that for windows both libs have the same name.\n    find_library(IDALIB32 ida ${IDASDK_PATH}/lib/x64_win_vc_32 ${IDASDK_PATH}/lib/x64_win_vc_32_pro)\n    find_library(IDALIB64 ida ${IDASDK_PATH}/lib/x64_win_vc_64 ${IDASDK_PATH}/lib/x64_win_vc_64_pro)\nelseif(LINUX)\n    find_library(IDALIB32 ida   ${IDASDK_PATH}/lib/x64_linux_gcc_32 ${IDASDK_PATH}/lib/x64_linux_gcc_32_pro)\n    find_library(IDALIB64 ida64 ${IDASDK_PATH}/lib/x64_linux_gcc_64 ${IDASDK_PATH}/lib/x64_linux_gcc_64_pro)\nelseif(DARWIN)\n    # now this depends on the host, better would be to set\n    # CMAKE_OSX_ARCHITECTURES to arm64 for the arm build.\n    if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL x86_64)\n        find_library(IDALIB32 ida   ${IDASDK_PATH}/lib/x64_mac_clang_32 ${IDASDK_PATH}/lib/x64_mac_clang_32_pro)\n        find_library(IDALIB64 ida64 ${IDASDK_PATH}/lib/x64_mac_clang_64 ${IDASDK_PATH}/lib/x64_mac_clang_64_pro)\n    else()\n        find_library(IDALIB32 ida   ${IDASDK_PATH}/lib/arm64_mac_clang_32 ${IDASDK_PATH}/lib/arm64_mac_clang_32_pro)\n        find_library(IDALIB64 ida64 ${IDASDK_PATH}/lib/arm64_mac_clang_64 ${IDASDK_PATH}/lib/arm64_mac_clang_64_pro)\n    endif()\nendif()\nif (IDALIB64 STREQUAL \"IDALIB64-NOTFOUND\")\n    message(FATAL_ERROR \"could not find libida64\")\nendif()\nif (IDALIB32 STREQUAL \"IDALIB32-NOTFOUND\")\n    message(FATAL_ERROR \"could not find libida\")\nendif()\nmessage(STATUS \"found ida headers at: ${IDASDK_PATH}/include\")\nmessage(STATUS \"found ida32 lib  at: ${IDALIB32}\")\nmessage(STATUS \"found ida64 lib  at: ${IDALIB64}\")\n\nadd_library(idasdk INTERFACE)\ntarget_include_directories(idasdk INTERFACE ${IDASDK_PATH}/include)\ntarget_compile_definitions(idasdk INTERFACE MAXSTR=1024)\n# since ida v7 all builds are 64 bit\ntarget_compile_definitions(idasdk INTERFACE __X64__)\n\nif (LINUX)\n    target_compile_definitions(idasdk INTERFACE __LINUX__=1)\nelseif (DARWIN)\n    target_compile_definitions(idasdk INTERFACE __MAC__=1)\nelseif (WIN32)\n    target_compile_definitions(idasdk INTERFACE __NT__=1)\nendif()\n# this prevents idasdk:fpro.h to redefine all stdio stuff to 'dont_use_XXX'\ntarget_compile_definitions(idasdk INTERFACE USE_STANDARD_FILE_FUNCTIONS)\n# this prevents idasdk:pro.h to redefine all string functions to 'dont_use_XXX'\ntarget_compile_definitions(idasdk INTERFACE USE_DANGEROUS_FUNCTIONS)\n# disallow obsolete sdk functions.\ntarget_compile_definitions(idasdk INTERFACE NO_OBSOLETE_FUNCS)\ntarget_compile_definitions(idasdk INTERFACE __DEFINE_ROOT_NODE__)\ntarget_compile_definitions(idasdk INTERFACE __DEFINE_INF__)\ntarget_compile_definitions(idasdk INTERFACE __DEFINE_PH__)\n\n# __EA64__=1   - for ida64   -> handled by choosing idasdk / idasdk64\n#    * this chooses between sizeof(ea_t) == 4 or 8\n\n# __IDP__    for processor modules  -> also needs win32: -export:LPH\n# __PLUGIN__ for plugins\n\n\nadd_library(idasdk32 INTERFACE)\ntarget_link_libraries(idasdk32 INTERFACE idasdk ${IDALIB32})\nadd_library(idasdk64 INTERFACE)\ntarget_link_libraries(idasdk64 INTERFACE idasdk ${IDALIB64})\ntarget_compile_definitions(idasdk64 INTERFACE __EA64__=1)\n\n"
  },
  {
    "path": "cmake_find/Findlibgmp.cmake",
    "content": "if (${libgmp_FOUND})\n    return()\nendif()\nif (TARGET libgmp)\n    return()\nendif()\n\nfind_path(GMPINC_DIR NAMES gmp.h gmpxx.h PATHS /usr/include /usr/local/include)\nfind_library(GMPLIB_DIR NAMES gmp libgmp PATHS /usr/lib /usr/local/lib)\n\nif(NOT GMPINC_DIR STREQUAL \"GMPINC_DIR-NOTFOUND\" AND NOT GMPLIB_DIR STREQUAL \"GMPLIB_DIR-NOTFOUND\")\n    add_library(libgmp INTERFACE)\n    target_link_libraries(libgmp INTERFACE ${GMPLIB_DIR})\n    target_include_directories(libgmp INTERFACE ${GMPINC_DIR})\n    target_compile_definitions(libgmp INTERFACE HAVE_LIBGMP)\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(libgmp REQUIRED_VARS GMPINC_DIR GMPLIB_DIR)\n\n\n"
  },
  {
    "path": "cmake_find/boilerplate.cmake",
    "content": "option(OPT_STL_DEBUGGING \"Build with STL debugging\"  OFF)\noption(OPT_PROF \"Build for profiling\"  OFF)\noption(OPT_COV \"Build for code coverage\"  OFF)\noption(OPT_LIBCXX \"Build with libcxx\"  OFF)\noption(OPT_MODULES \"use c++20 modules\"  OFF)\noption(OPT_ANALYZE \"add -fanalyzer\"  OFF)\noption(OPT_SYMBOLS \"With symbols\" OFF)\noption(OPT_SANITIZE \"With -fsanitize\" OFF)\noption(OPT_TSAN \"With thread sanitizer\" OFF)\noption(OPT_ASAN \"With address sanitizer\" OFF)\noption(OPT_CLANG_TIDY \"With clang-tidy checks\" OFF)\noption(OPT_COMPILE_COMMANDS \"Generate compile_commands.json\" OFF)\noption(OPT_INSTALL_HEADERS \"Export header files for INSTALL target\" OFF)\noption(OPT_DISABLE_CMAKE_SANITY_CHECK \"Disable CMake call sanity checks (ex: OpenWrt)\" OFF)\noption(OPT_DISABLE_DEVEL_INSTALL \"Disable all development install targets (ex: Win NSIS installer)\" OFF)\n\nif (${CMAKE_SYSTEM_NAME} MATCHES \"Linux\")\n    set(LINUX TRUE)\nelseif (${CMAKE_SYSTEM_NAME} MATCHES \"Darwin\")\n    set(DARWIN TRUE)\n    if (${CMAKE_OSX_SYSROOT} MATCHES \"/iPhoneOS.platform\")\n        set(IPHONE TRUE)\n    elseif (${CMAKE_OSX_SYSROOT} MATCHES \"/iPhoneSimulator.platform\")\n        set(IPHONESIM TRUE)\n    elseif (${CMAKE_OSX_SYSROOT} MATCHES \"/MacOSX.platform\")\n        set(MACOS TRUE)\n    else()\n        message(FATAL_ERROR \"Unsupported apple platform\")\n    endif()\nelseif (${CMAKE_SYSTEM_NAME} MATCHES \"iOS\")\n    set(DARWIN TRUE)\n    if (${CMAKE_OSX_SYSROOT} MATCHES \"/iPhoneOS.platform\")\n        set(IPHONE TRUE)\n    elseif (${CMAKE_OSX_SYSROOT} MATCHES \"/iPhoneSimulator.platform\")\n        set(IPHONESIM TRUE)\n    endif()\nelseif (${CMAKE_SYSTEM_NAME} MATCHES \"FreeBSD\")\n    set(FREEBSD TRUE)\nendif()\n\nif (NOT OPT_DISABLE_CMAKE_SANITY_CHECK)\n    # checking if we are called in the correct way:\n    #  with a -B argument.  and without a cache file in the source directory.\n    if (CMAKE_CACHEFILE_DIR STREQUAL \"${CMAKE_SOURCE_DIR}\")\n        message(FATAL_ERROR \"\\nUnexpected CMakeCache.txt file in the source directory. Please remove it.\")\n        return()\n    endif()\n\n    if (EXISTS ${CMAKE_BINARY_DIR}/CMakeLists.txt)\n        message(FATAL_ERROR \"\\nRun cmake with an explicit -B buildpath\")\n        return()\n    endif()\nendif()\n\nif (OPT_ANALYZE)\n    if (CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\")\n        # see https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Static-Analyzer-Options.html#index-analyzer\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fanalyzer\")\n    elseif (CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\")\n        # https://clang.llvm.org/docs/UsersManual.html\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} --analyze\")\n    endif()\nendif()\n\nif (OPT_ASAN AND OPT_TSAN)\n    message(FATAL_ERROR \"Only one sanitizer can be active at a time\")\nelseif (OPT_ASAN)\n    # https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Instrumentation-Options.html#index-fsanitize_003daddress\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=undefined\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=address\")\n    #set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=thread\")\n    #set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=dataflow\")\nelseif(OPT_TSAN)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=thread\")\nendif()\n\nif (OPT_CLANG_TIDY)\n  # clang-tidy supports a range of different checks. For a list of all available\n  # checks, check the clang-tidy website:\n  #   https://clang.llvm.org/extra/clang-tidy/checks/list.html\n  # To enable only certain checks, we disable all of them first and then select\n  #  - clang-analyzer-*   => Clang Static Analyzer\n  #  - bugprone-*         => bug-prone code constructs (except bugprone-easily-swappable-parameters, bugprone-suspicious-include)\n  #  - cert-*             => CERT Secure Coding Guidelines\n  #  - concurrency-*      => General concurrency checks\n  #  - performance-*      => General performance checks\n  #  - portability-*      => General portability checks\n  set(CLANG_TIDY_CHECKS \"clang-analyzer-*,bugprone-*,-bugprone-easily-swappable-parameters,-bugprone-suspicious-include,cert-*,concurrency-*,performance-*,portability=*\")\n  set(CMAKE_CXX_CLANG_TIDY \"clang-tidy;--extra-arg-before=-std=c++${CMAKE_CXX_STANDARD};-checks=-*,${CLANG_TIDY_CHECKS}\")\nendif()\n\nif (OPT_LIBCXX)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -stdlib=libc++\")\nendif()\n\nif (OPT_STL_DEBUGGING)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -D_GLIBCXX_DEBUG\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -D_LIBCPP_DEBUG_LEVEL=1\")\nendif()\n\nif (OPT_PROF)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -pg \")\nendif()\n\nif (OPT_SYMBOLS)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -g \")\nendif()\nif (CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-c++11-narrowing\")\nendif()\n\nif (OPT_COV)\n    if (CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\")\n        message(STATUS \"gcc code coverage\")\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -ftest-coverage -fprofile-arcs \")\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -ftest-coverage -fprofile-arcs \")\n    elseif (CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\")\n        message(STATUS \"llvm code coverage\")\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping -fdebug-info-for-profiling\")\n        #set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -mllvm -inline-threshold=100000\")\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -fprofile-instr-generate -fcoverage-mapping\")\n    else()\n        message(STATUS \"don't know how to add code coverage for ${CMAKE_CXX_COMPILER_ID }\")\n    endif()\nendif()\nif(OPT_STATIC)\n    set(LIBSTYLE STATIC)\n    set(CMAKE_POSITION_INDEPENDENT_CODE True)\nelse()\n    set(LIBSTYLE SHARED)\nendif()\n\nif (OPT_COMPILE_COMMANDS)\n    set(CMAKE_EXPORT_COMPILE_COMMANDS ON)\nendif()\n\nif (OPT_DISABLE_DEVEL_INSTALL)\n  set(MAY_EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL)\nendif()\n\n\n# Project wide warning/error settings\nif(MSVC)\n    # /W0 suppresses all warnings\n    # /W1 displays level 1 (severe) warnings (default in command line)\n    # /W2 displays level 1 and level 2 (significant) warnings.\n    # /W3 displays level 1, level 2, and level 3 (production quality) warnings (default in IDE)\n    # /W4 displays level 1, level 2, and level 3 warnings, and all level 4 (informational) warnings that aren't off by default\n    add_compile_options(/W1)\nelse()\n    # Exclude the following ones for now:\n    #   -Wunused-parameter: we have delegate classes with stub methods (with unused parameters)\n    #   -Wempty-body: occurs in release builds as there are if-cases which only contain a logmsg expression\n    #   -Wunused-variable, -Wunused-value:  occurs in release builds for parameters of a logmsg expression\n    add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-empty-body -Wno-unused-value -Wno-unused-variable)\nendif()\n\nif(MSVC)\n    # /MP = multithreaded build\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /MP\")\n    # /utf-8 = utf8 source and execution\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /utf-8\")\n    # NOBITMAP - avoid error in mmreg.h\n    # NOMINMAX - remove 'max()' macro from global namespace\n    # NOGDI - ...\n    add_definitions(-DNOMINMAX -DNOGDI -DNOBITMAP -DWIN32_LEAN_AND_MEAN)\n    add_definitions(-DWIN32)\n    add_definitions(-D__STDC_WANT_SECURE_LIB__=1)\n\n    # Executables need to resolve path to dlls (RPATH is not available on Windows). This could be done\n    # either by using PATH env. variable or keeping dlls alongside with executables\n    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG   ${CMAKE_BINARY_DIR}/bin)\n    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)\nendif()\nif (OPT_MODULES)\n    if (CMAKE_COMPILER_IS_GNUCXX)\n        set(CMAKE_CXX_FLAGS -fmodules-ts)\n    else()\n        set(CMAKE_CXX_FLAGS -fmodules -fbuiltin-module-map)\n    endif()\nendif()\n\n"
  },
  {
    "path": "cmake_find/dumpvars.cmake",
    "content": "# include this file to get a full dump of all cmake variables to stdout.\n\nfunction(dump_cmake_variables)\n    get_cmake_property(_variableNames VARIABLES)\n    list (SORT _variableNames)\n    foreach (_variableName ${_variableNames})\n        if (ARGV0)\n            unset(MATCHED)\n            string(REGEX MATCH ${ARGV0} MATCHED ${_variableName})\n            if (NOT MATCHED)\n                continue()\n            endif()\n        endif() \n        message(STATUS \"${_variableName}=${${_variableName}}\")\n    endforeach()\nendfunction()\n\nlist(APPEND InterestingProps AUTOGEN_ORIGIN_DEPENDS AUTOMOC_COMPILER_PREDEFINES AUTOMOC_MACRO_NAMES AUTOMOC_PATH_PREFIX BINARY_DIR BUILD_WITH_INSTALL_RPATH EXCLUDE_FROM_ALL EXPORT_COMPILE_COMMANDS FOLDER HEADER_SETS IMPORTED IMPORTED_GLOBAL INCLUDE_DIRECTORIES INSTALL_RPATH INSTALL_RPATH_USE_LINK_PATH INTERFACE_HEADER_SETS INTERFACE_INCLUDE_DIRECTORIES INTERFACE_LINK_LIBRARIES ISPC_HEADER_SUFFIX LINK_LIBRARIES PCH_INSTANTIATE_TEMPLATES PCH_WARN_INVALID POSITION_INDEPENDENT_CODE RULE_LAUNCH_CUSTOM SKIP_BUILD_RPATH SOURCES SOURCE_DIR UNITY_BUILD_BATCH_SIZE UNITY_BUILD_MODE)\ninclude(CMakePrintHelpers)\nfunction(dump_targets)\n    get_directory_property(_tlist BUILDSYSTEM_TARGETS)\n    message(STATUS \"bs: ${_tlist}\")\n    foreach (t ${_tlist})\n        message(STATUS \"----  ${t}\")\n        get_target_property(_slist ${t} SOURCES)\n        foreach (s ${_slist})\n            message(STATUS \"  ${s}\")\n        endforeach()\n        cmake_print_properties(TARGETS ${t} PROPERTIES ${InterestingProps})\n    endforeach()\nendfunction()\n\n\n"
  },
  {
    "path": "getcontrib.sh",
    "content": "mkdir -p contrib\ncurl -s -o contrib/doctest.h https://raw.githubusercontent.com/onqtam/doctest/master/doctest/doctest.h\ncurl -s -o contrib/catch.hpp https://raw.githubusercontent.com/catchorg/Catch2/master/single_include/catch2/catch.hpp\n"
  },
  {
    "path": "include/idblib/idb3.h",
    "content": "/*\n *  idb3.h  is a library for accessing IDApro databases.\n *\n * Author: Willem Hengeveld <itsme@xs4all.nl>\n *\n *\n * Toplevel class: IDBFile, use getsection to get a stream to the desired section,\n * Then create an ID0File, ID1File, NAMFile for that section.\n * \n */\n#include <iostream>\n#include <sstream>\n#include <vector>\n#include <set>\n#include <cassert>\n#include <climits>\n#include <algorithm>\n#include <memory>\n#include <cpputils/formatter.h>\n\n#define dbgprint(...)\n\n// a sharedptr, so i can pass an istream around without\n// worrying about who owns it.\ntypedef std::shared_ptr<std::istream> stream_ptr;\n\n// create vector from `n`  invocations of `f`\ntemplate<typename T, typename FN>\nstd::vector<T> getvec(int n, FN f)\n{\n    std::vector<T> v;\n    while (n--)\n        v.push_back(f());\n    return v;\n}\n\n\n////////////////////////////////////////////////////////////////////////\n// Sometimes i need to pass backinserter iterators as <first, last> pair\n// These functions make that possible.\n\n// ANY - backinserter == INT_MAX -> always enough space after a backinserter\ntemplate<typename T, typename C>\nint operator-(T lhs, typename std::back_insert_iterator<C> rhs)\n{\n    return INT_MAX;\n}\n\n// backinserter += INT  -> does nothing\ntemplate<typename C>\ntypename std::back_insert_iterator<C>& operator+=(typename std::back_insert_iterator<C>& rhs, int n)\n{\n    return rhs;\n}\n\n\n\n// streamhelper: get little/big endian integers of various sizes from a stream\n// There are functions for 8, 16, 32, 64 bit little/big endian unsigned integers.\n// And a function for reading a database dependent word (64bit for .i64, 32bit for .idb)\ntemplate<typename ISPTR>\nclass streamhelper {\n    ISPTR _is;\n    int _wordsize;  // the wordsize of the current database\npublic:\n    streamhelper(ISPTR is, int wordsize)\n        : _is(is), _wordsize(wordsize)\n    {\n        _is->exceptions(std::istream::failbit | std::istream::badbit);\n    }\n    uint8_t get8()\n    {\n        auto c = _is->get();\n        if (c==-1)\n            throw \"EOF\";\n        return (uint8_t)c;\n    }\n    uint16_t get16le()\n    {\n        uint8_t lo = get8();\n        uint8_t hi = get8();\n        return (hi<<8) | lo;\n    }\n    uint16_t get16be()\n    {\n        uint8_t hi = get8();\n        uint8_t lo = get8();\n        return (hi<<8) | lo;\n    }\n\n    uint32_t get32le()\n    {\n        uint16_t lo = get16le();\n        uint16_t hi = get16le();\n        return (hi<<16) | lo;\n    }\n    uint32_t get32be()\n    {\n        uint16_t hi = get16be();\n        uint16_t lo = get16be();\n        return (hi<<16) | lo;\n    }\n    uint64_t get64le()\n    {\n        uint32_t lo = get32le();\n        uint32_t hi = get32le();\n        return (uint64_t(hi)<<32) | lo;\n    }\n    uint64_t get64be()\n    {\n        uint32_t hi = get32be();\n        uint32_t lo = get32be();\n        return (uint64_t(hi)<<32) | lo;\n    }\n\n\n    // function used to get the right wordsize for either .i64 or .idb file.\n    uint64_t getword()\n    {\n        if (_wordsize==4)\n            return get32le();\n        else if (_wordsize==8)\n            return get64le();\n        throw \"unsupported wordsize\";\n    }\n    std::string getdata(int n)\n    {\n        std::string str(n, char(0));\n        auto m = _is->readsome(&str.front(), n);\n        str.resize(m);\n        dbgprint(\"getdata -> %b\\n\", str);\n        return str;\n    }\n    void seekg( std::istream::off_type off, std::ios_base::seekdir dir)\n    {\n        _is->seekg(off, dir);\n    }\n    void seekg( std::istream::pos_type pos )\n    {\n        _is->seekg(pos);\n    }\n};\n\n// function for creating a streamhelper.\ntemplate<typename ISPTR>\nauto makehelper(ISPTR is, int wordsize = 0)\n{\n    return streamhelper<ISPTR>(is, wordsize);\n}\n\n\n// EndianTools: a collection of static functions for reading/writing\n// little/big endian integers of various sizes from an iterator range\nstruct EndianTools {\n    template<typename P>\n    static void set8(P first, P last, uint8_t w)\n    {\n        if (last-first < 1)\n            throw \"not enough space\";\n        *first = w;\n    }\n    template<typename P, typename T>\n    static void setbe16(P first, P last, T w)\n    {\n        P p = first;\n        if (last-p < 2)\n            throw \"not enough space\";\n        set8(p, last, w>>8);  p += 1;\n        set8(p, last, w);\n    }\n    template<typename P, typename T>\n    static void setbe32(P first, P last, T w)\n    {\n        P p = first;\n        if (last-p < 4)\n            throw \"not enough space\";\n        setbe16(p, last, w>>16);  p += 2;\n        setbe16(p, last, w);\n    }\n    template<typename P, typename T>\n    static void setbe64(P first, P last, T w)\n    {\n        P p = first;\n        if (last-p < 8)\n            throw \"not enough space\";\n        setbe32(p, last, w>>32);  p += 4;\n        setbe32(p, last, w);\n    }\n    template<typename P, typename T>\n    static void setle16(P first, P last, T w)\n    {\n        P p = first;\n        if (last-p < 2)\n            throw \"not enough space\";\n        set8(p, last, w);  p += 1;\n        set8(p, last, w>>8);\n    }\n    template<typename P, typename T>\n    static void setle32(P first, P last, T w)\n    {\n        P p = first;\n        if (last-p < 4)\n            throw \"not enough space\";\n        setle16(p, last, w);      p += 2;\n        setle16(p, last, w>>16);\n    }\n    template<typename P, typename T>\n    static void setle64(P first, P last, T w)\n    {\n        P p = first;\n        if (last-p < 8)\n            throw \"not enough space\";\n        setle32(p, last, w);        p += 4;\n        setle32(p, last, w>>32);\n    }\n\n    template<typename P>\n    static uint8_t get8(P first, P last)\n    {\n        if (first>=last)\n            throw \"not enough space\";\n        return *first;\n    }\n\n    template<typename P>\n    static uint16_t getbe16(P first, P last)\n    {\n        P p = first;\n        if (last-p < 2)\n            throw \"not enough space\";\n        uint8_t hi =get8(p, last);   p += 1;\n        uint8_t lo =get8(p, last);\n\n        return (uint16_t(hi)<<8) | lo;\n    }\n    template<typename P>\n    static uint32_t getbe32(P first, P last)\n    {\n        P p = first;\n        if (last-p < 4)\n            throw \"not enough space\";\n        uint16_t hi =getbe16(p, last);   p += 2;\n        uint16_t lo =getbe16(p, last);\n\n        return (uint32_t(hi)<<16) | lo;\n    }\n    template<typename P>\n    static uint64_t getbe64(P first, P last)\n    {\n        P p = first;\n        if (last-p < 8)\n            throw \"not enough space\";\n        uint32_t hi =getbe32(p, last);   p += 4;\n        uint32_t lo =getbe32(p, last);\n\n        return (uint64_t(hi)<<32) | lo;\n    }\n    template<typename P>\n    static uint16_t getle16(P first, P last)\n    {\n        P p = first;\n        if (last-p < 2)\n            throw \"not enough space\";\n        uint8_t lo =get8(p, last);    p += 1;\n        uint8_t hi =get8(p, last);\n\n        return (uint16_t(hi)<<8) | lo;\n    }\n    template<typename P>\n    static uint32_t getle32(P first, P last)\n    {\n        P p = first;\n        if (last-p < 4)\n            throw \"not enough space\";\n        uint16_t lo =getle16(p, last);   p += 2;\n        uint16_t hi =getle16(p, last);\n\n        return (uint32_t(hi)<<16) | lo;\n    }\n    template<typename P>\n    static uint64_t getle64(P first, P last)\n    {\n        P p = first;\n        if (last-p < 8)\n            throw \"not enough space\";\n        uint32_t lo =getle32(p, last);   p += 4;\n        uint32_t hi =getle32(p, last);\n\n        return (uint64_t(hi)<<32) | lo;\n    }\n\n};\n\n// stream buffer for sectionstream\n// This is the class doing the actual work for sectionstream.\n// This presents a view of a section of a random access stream.\nclass sectionbuffer : public std::streambuf {\n    stream_ptr _is;\n\n    std::streamoff _first;\n    std::streamoff _last;\n\n    std::streampos _curpos;\n\npublic:\n    sectionbuffer(stream_ptr is,  uint64_t first, uint64_t last)\n        : _is(is), _first(first), _last(last), _curpos(0)\n    {\n        _is->seekg(_first);\n    }\nprotected:\n    std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)\n    {\n        std::streampos newpos;\n        switch(way)\n        {\n        case std::ios_base::beg:\n            newpos = off;\n            break;\n        case std::ios_base::cur:\n            newpos = _curpos + off;\n            break;\n        case std::ios_base::end:\n            newpos = (_last-_first) + off;\n            break;\n        default:\n            throw std::ios_base::failure(\"bad seek direction\");\n        }\n        return seekpos(newpos, which);\n    }\n\n    std::streampos seekpos(std::streampos sp, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)\n    {\n        if (sp<0 || sp > (_last-_first))\n            return -1;\n        _is->seekg(sp+_first);\n        return _curpos = sp;\n    }\n    std::streamsize showmanyc()\n    {\n        return (_last-_first)-_curpos;\n    }\n    std::streamsize xsgetn(char_type* s, std::streamsize n)\n    {\n        if (n<=0 || _curpos >= (_last-_first))\n            return 0;\n        auto want = std::min(std::streamsize(_last-_first - _curpos), n);\n        //auto got = _is->readsome(s, want);\n        _is->read(s, want);  auto got = want;\n        _curpos += got;\n\n        return got;\n    }\n    int_type underflow()\n    {\n        if (_curpos >= (_last-_first))\n            return traits_type::eof();\n        int r = _is->peek();\n        return r;\n    }\n    int_type uflow()\n    {\n        if (_curpos >= (_last-_first))\n            return traits_type::eof();\n        int r = _is->get();\n        if (r==traits_type::eof())\n            return traits_type::eof();\n        _curpos+=1;\n        return r;\n    }\n};\n// istream restricted to a section of a seakable stream\nclass sectionstream : public std::istream {\n    sectionbuffer _buf;\npublic:\n    template<typename ISPTR>\n    sectionstream(ISPTR is, uint64_t from, uint64_t size)\n        : std::istream(nullptr), _buf(is, from, from+size)\n    {\n        init(&_buf);\n    }\n};\n\n///////////////////////////////////////////////////////////////\n// read .idb file, returns sectionstreams for sections\n//\n// IDBFile knows how to read sections from all types of IDApro databases.\n//\n// Compression is not yet supported.\nclass IDBFile {\n    stream_ptr _is;\n    uint32_t _magic;\n    int _fileversion;\n    std::vector<uint64_t> _offsets;\n    std::vector<uint32_t> _checksums;\npublic:\n    enum {\nMAGIC_IDA2 = 0x32414449,\nMAGIC_IDA1 = 0x31414449,\nMAGIC_IDA0 = 0x30414449,\n    };\n    IDBFile(stream_ptr is)\n        : _is(is), _magic(0), _fileversion(-1)\n    {\n        readheader();\n    }\n    uint32_t magic() const { return _magic; }\n\n    void readheader()\n    {\n        auto s = makehelper(_is);\n        _magic = s.get32le();\n        /*zero = */ s.get16le();\n        auto values = getvec<uint32_t>(6, [&](){ return s.get32le(); });\n        if (values[5]!=0xaabbccdd) {\n            _fileversion = 0;\n            for (auto v : values)\n                _offsets.push_back(v);\n            _offsets[5] = 0;\n            _checksums.resize(6);\n            return;\n        }\n        _fileversion = s.get16le();\n\n        if (_fileversion < 5) {\n            /*auto unknown =*/ s.get32le();\n            for (auto v : values)\n                _offsets.push_back(v);\n            _offsets.pop_back();\n\n            _checksums = getvec<uint32_t>(5, [&](){ return s.get32le(); });\n            uint32_t idsofs = s.get32le();\n            uint32_t idscheck = _fileversion==1 ? s.get16le() : s.get32le();\n            _offsets.push_back(idsofs);\n            _checksums.push_back(idscheck);\n\n            // in filever==4 there is more in the .idb header:\n            // 0x5c, 0, 0, <md5>, 128*NUL\n        }\n        else {\n            // ver 5, 6 : 64 bit fileptrs\n            _offsets.push_back((uint64_t(values[1])<<32)|values[0]);\n            _offsets.push_back((uint64_t(values[3])<<32)|values[2]);\n            _offsets.push_back(s.get64le());\n            _offsets.push_back(s.get64le());\n            _offsets.push_back(s.get64le());\n            _checksums = getvec<uint32_t>(5, [&](){ return s.get32le(); });\n            _offsets.push_back(s.get64le());\n            _checksums.push_back(s.get32le());\n\n            // more data in the .idb header:\n            // 0x7c, 0, 0, <md5>, 128*NUL\n        }\n    }\n    void dump()\n    {\n        print(\"IDB v%d, m=%08x\\n\", _fileversion, _magic);\n        for (unsigned int i=0 ; i<std::max(_offsets.size(), _checksums.size()) ; i++)\n            print(\"%d: %10llx %08x\\n\", i, i<_offsets.size() ? _offsets[i] : -1, i<_checksums.size() ? _checksums[i] : -1);\n    }\n    auto getinfo(int i)\n    {\n        _is->seekg(_offsets[i]);\n        auto s = makehelper(_is);\n        auto comp = s.get8();\n        uint64_t size = _fileversion<5 ?  s.get32le() : s.get64le();\n        uint64_t ofs = _offsets[i] + (_fileversion<5 ?  5 : 9);\n\n        return std::make_tuple(comp, ofs, size);\n    }\n\n    stream_ptr getsection(int i)\n    {\n        auto info = getinfo(i);\n        if (std::get<0>(info))\n            throw \"compression not supported\";\n        return std::make_shared<sectionstream>(_is, std::get<1>(info), std::get<2>(info));\n    }\n};\n\n// search relation\nenum relation_t {\n    REL_LESS,\n    REL_LESS_EQUAL,\n    REL_EQUAL,\n    REL_GREATER_EQUAL,\n    REL_GREATER,\n    REL_RECURSE,\n};\n\n// baseclass for Btree Pages\n// baseclass for Btree database, subclassed by v1.5, v1.6, v2.0\nclass BasePage {\nprotected:\n    stream_ptr _is;\n    int _pagesize;\n    uint32_t _nr;\n    uint32_t _preceeding;\n    int _count;\n\n    // item for the entry table\n    class Entry {\n    public:\n        uint32_t pagenr;\n        int indent;\n        int recofs;\n\n        Entry() : pagenr(0), indent(0), recofs(0) { }\n        Entry(Entry&& e) : pagenr(e.pagenr), indent(e.indent), recofs(e.recofs) { }\n    };\n    std::vector<Entry> _index;\n    std::vector<std::string> _keys; // only for leaf pages\n\n    // IntIter, used to be able to use upper_bound on `_index`\n    class IntIter : public std::iterator<std::random_access_iterator_tag, int> {\n        int _ix;\n    public:\n        IntIter(int x)  : _ix(x) { }\n        IntIter() : _ix(0) { }\n        IntIter(const IntIter& i) : _ix(i._ix) { }\n\n        bool operator==(const IntIter& rhs) {return _ix==rhs._ix;}\n        bool operator!=(const IntIter& rhs) {return _ix!=rhs._ix;}\n\n        int operator*() const {return _ix;}\n        int operator[](int i) {return _ix+i;}\n\n        IntIter& operator++() {++_ix;return *this;}\n        IntIter operator++(int) {IntIter tmp(*this); operator++(); return tmp;}\n        IntIter& operator--() {--_ix;return *this;}\n        IntIter operator--(int) {IntIter tmp(*this); operator--(); return tmp;}\n\n        IntIter& operator+=(int n) {  _ix += n; return *this; }\n        IntIter& operator-=(int n) {  _ix -= n; return *this; }\n\n        friend IntIter operator+(int n, IntIter p) {  return p+=n; }\n        friend IntIter operator+(IntIter p, int n) {  return p+=n; }\n        friend IntIter operator-(IntIter p, int n) {  return p-=n; }\n        friend int operator-(const IntIter& p, const IntIter& q) {  return p._ix-q._ix; }\n\n        bool operator<(const IntIter& rhs) { return _ix<rhs._ix; }\n        bool operator<=(const IntIter& rhs) { return _ix<=rhs._ix; }\n        bool operator>(const IntIter& rhs) { return _ix>rhs._ix; }\n        bool operator>=(const IntIter& rhs) { return _ix>=rhs._ix; }\n\n    };\n\n    // unused, iterator returning Entry's\n    class PageIter : public std::iterator<std::random_access_iterator_tag, Entry> {\n        BasePage* _page;\n        int _ix;\n    public:\n        PageIter(BasePage*page, int ix) : _page(page), _ix(ix) { }\n        PageIter() : _page(nullptr), _ix(0) { }\n        PageIter(const PageIter& i) : _page(i._page), _ix(i._ix) { }\n\n        bool operator==(const PageIter& rhs) {return _ix==rhs._ix;}\n        bool operator!=(const PageIter& rhs) {return _ix!=rhs._ix;}\n\n        Entry& operator*() {return _page->getent(_ix);}\n        Entry& operator[](int i) {return _page->getent(_ix+i);}\n\n        PageIter& operator++() {++_ix;return *this;}\n        PageIter operator++(int) {PageIter tmp(*this); operator++(); return tmp;}\n        PageIter& operator--() {--_ix;return *this;}\n        PageIter operator--(int) {PageIter tmp(*this); operator--(); return tmp;}\n\n        PageIter& operator+=(int n) {  _ix += n; return *this; }\n        PageIter& operator-=(int n) {  _ix -= n; return *this; }\n\n        friend PageIter operator+(int n, PageIter p) {  return p+=n; }\n        friend PageIter operator+(PageIter p, int n) {  return p+=n; }\n        friend PageIter operator-(PageIter p, int n) {  return p-=n; }\n        friend int operator-(const PageIter& p, const PageIter& q) {  return p._ix-q._ix; }\n\n        bool operator<(const PageIter& rhs) { return _ix<rhs._ix; }\n        bool operator<=(const PageIter& rhs) { return _ix<=rhs._ix; }\n        bool operator>(const PageIter& rhs) { return _ix>rhs._ix; }\n        bool operator>=(const PageIter& rhs) { return _ix>=rhs._ix; }\n    };\n\npublic:\n    BasePage(stream_ptr  is, uint32_t nr, int pagesize)\n        : _is(is), _pagesize(pagesize), _nr(nr), _preceeding(0), _count(0)\n    {\n    }\n    virtual ~BasePage() {}\n    uint32_t nr() const { return _nr; }\n\n    bool isindex() const { return _preceeding!=0; }\n    bool isleaf() const { return _preceeding==0; }\n\n    size_t indexsize() const { return _index.size(); }\n\n    virtual Entry readent() = 0;\n\n    void dump()\n    {\n        if (_preceeding)\n            print(\"prec = %05x\\n\", _preceeding);\n        for (unsigned int i=0 ; i<_index.size() ; i++)\n            print(\"%-b = %-b\\n\", getkey(i), getval(i));\n    }\n\n    void readindex()\n    {\n        for (int i=0 ; i<_count ; i++)\n            _index.emplace_back(readent());\n        //print(\"got %d entries\\n\", _index.size());\n\n        if (isleaf())\n            readkeys();\n    }\n    // for a leafpage, calculate all key values\n    void readkeys()\n    {\n        auto s = makehelper(_is);\n        std::string key;\n        for (auto & ent : _index) {\n            _is->seekg(ent.recofs);\n            int klen = s.get16le();\n            key.resize(klen+ent.indent);\n            _is->read(&key[ent.indent], klen);\n\n            dbgprint(\"key i=%d, l=%d -> %b\\n\", ent.indent, klen, key);\n            _keys.push_back(key);\n        }\n    }\n\n    // get the subpage for the item at positon `i`\n    uint32_t getpage(int i) const\n    {\n        if (!isindex())\n            throw \"getpage called on leaf\";\n        if (i<0)\n            return _preceeding;\n        if (i>=_index.size()) {\n            print(\"#%06x i=%d, max=%d\\n\", _nr, i, _index.size());\n            throw \"page: i too large\";\n        }\n        return _index[i].pagenr;\n    }\n\n    // get key for the item at position `i`\n    std::string getkey(int i)\n    {\n        auto& ent = getent(i);\n        if (isindex()) {\n            _is->seekg(ent.recofs);\n            auto s = makehelper(_is);\n            int klen = s.get16le();\n\n            dbgprint(\"indexkey(%d) -> l=%d\\n\", i, klen);\n            return s.getdata(klen);\n        }\n        else if (isleaf()) {\n            dbgprint(\"leafkey(%d)\\n\", i);\n            return _keys[i];\n        }\n        throw \"not a leaf of index\";\n    }\n    // get value for the item at position `i`\n    std::string getval(int i)\n    {\n        auto& ent = getent(i);\n        _is->seekg(ent.recofs);\n        auto s = makehelper(_is);\n        int klen = s.get16le();\n        _is->seekg(klen, std::ios_base::cur);\n        int vlen = s.get16le();\n\n        dbgprint(\"%04x: val(%d), kl=%d, vl=%d\\n\", ent.recofs, i, klen, vlen);\n        return s.getdata(vlen);\n    }\n\n    Entry& getent(int i) {\n        if (i<0 || i>=_index.size())\n            throw \"invalid key index\";\n\n        return _index[i];\n    }\n\n    // unused\n    //auto begin() { return PageIter(this, 0); }\n    //auto end() { return PageIter(this, _count); }\n\n    struct result {\n        relation_t act;\n        int index;\n\n        bool operator==(const result& rhs) const { return act==rhs.act && index==rhs.index; }\n        bool operator!=(const result& rhs) const { return !(*this==rhs); }\n\n        friend std::ostream& operator<<(std::ostream& os, const result& res)\n        {\n            os << '{';\n            switch(res.act)\n            {\n                case REL_LESS:           os << \"<\"; break;\n                case REL_LESS_EQUAL:     os << \"<=\"; break;\n                case REL_EQUAL:          os << \"==\"; break;\n                case REL_GREATER_EQUAL:  os << \">=\"; break;\n                case REL_GREATER:        os << \">\"; break;\n                case REL_RECURSE:        os << \"r\"; break;\n                         default:        os << \"?\";\n            }\n            os << res.index;\n            os << '}';\n            return os;\n        }\n    };\n\n    // search for the key in this page.\n    // getkey(index) ... act ... key\n    result find(const std::string& key)\n    {\n        //auto i = std::upper_bound(begin(), end(), key, [](const std::string& key, const Entry& ent){  return false; });\n        auto i = std::upper_bound(IntIter(0), IntIter(_count), key, [this](const std::string& key, int ix){  return key < this->getkey(ix); });\n\n        if (i==IntIter(0)) {\n            if (isindex())\n                return {REL_RECURSE, -1};\n            return {REL_GREATER, 0}; // index[0] > key\n        }\n        --i;\n        int ix = i-IntIter(0);\n        if (getkey(ix) == key)\n            return {REL_EQUAL, ix};\n        if (isindex())\n            return {REL_RECURSE, ix};\n        return {REL_LESS, ix};       // index[ix] < key\n    }\n};\ntypedef std::shared_ptr<BasePage> Page_ptr;\n\n\n// baseclass for Btree database, subclassed by v1.5, v1.6, v2.0\nclass BtreeBase {\nprotected:\n    stream_ptr _is;\n    uint32_t _firstindex;\n    uint32_t _pagesize;\n    uint32_t _firstfree;\n    uint32_t _reccount;\n    uint32_t _pagecount;\npublic:\n    class Cursor {\n        BtreeBase *_bt;\n        struct ent {\n            Page_ptr page;\n            int index;\n\n            ent(Page_ptr page, int index)\n                : page(page), index(index)\n            {\n            }\n            ent() : index(0) { }\n\n            bool operator==(const ent& rhs) const { return page==rhs.page && index==rhs.index; }\n            bool operator!=(const ent& rhs) const { return !(*this==rhs); }\n        };\n        std::vector<ent> _stack;\n\n        void dump() const {\n            std::stringstream x;\n            for (auto& ent : _stack)\n                x << stringformat(\" %05x:%d\", ent.page->nr(), ent.index);\n            std::cout << x.str() << std::endl;\n        }\n    public:\n        Cursor(BtreeBase *bt)\n            : _bt(bt)\n        {\n        }\n\n        void clear()\n        {\n            _stack.clear();\n        }\n        void next()\n        {\n            if (eof())\n                throw \"cursor:EOF\";\n            auto ent = _stack.back(); _stack.pop_back();\n            if (ent.page->isleaf()) {\n                // from leaf move towards root\n                ent.index++;\n                while (!_stack.empty() && ent.index==ent.page->indexsize()) {\n                    ent = _stack.back(); _stack.pop_back();\n                    ent.index++;\n                }\n                if (ent.index<ent.page->indexsize()) {\n                    add(ent.page, ent.index);\n                }\n            }\n            else {\n                // from node move towards leaf\n                add(ent.page, ent.index);\n                ent.page = _bt->readpage(ent.page->getpage(ent.index));\n                while (ent.page->isindex()) {\n                    ent.index = -1;\n                    add(ent.page, ent.index);\n                    ent.page = _bt->readpage(ent.page->getpage(ent.index));\n                }\n                ent.index = 0;\n                add(ent.page, ent.index);\n            }\n        }\n        void prev()\n        {\n            if (eof())\n                throw \"cursor:EOF\";\n            auto ent = _stack.back(); _stack.pop_back();\n            ent.index--;\n            if (ent.page->isleaf()) {\n                while (!_stack.empty() && ent.index<0) {\n                    ent = _stack.back(); _stack.pop_back();\n                }\n                if (ent.index>=0)\n                    add(ent.page, ent.index);\n            }\n            else {\n                add(ent.page, ent.index);\n                while (ent.page->isindex()) {\n                    ent.page = _bt->readpage(ent.page->getpage(ent.index));\n                    ent.index = ent.page->indexsize()-1;\n                    add(ent.page, ent.index);\n                }\n            }\n        }\n        bool eof() const\n        {\n            return _stack.empty();\n        }\n\n        // for Btree.find to create cursor.\n        void add(Page_ptr page, int index)\n        {\n            _stack.emplace_back(page, index);\n        }\n\n        // getting key/value from cursor pos\n        auto getkey() const\n        {\n            if (eof())\n                throw \"cursor:EOF\";\n            auto ent = _stack.back();\n            return ent.page->getkey(ent.index);\n        }\n        auto getval() const\n        {\n            if (eof())\n                throw \"cursor:EOF\";\n            auto ent = _stack.back();\n            return ent.page->getval(ent.index);\n        }\n\n        bool operator==(const Cursor& rhs) const { return _stack == rhs._stack; }\n        bool operator!=(const Cursor& rhs) const { return !(*this==rhs); }\n\n        bool operator<(const Cursor& rhs) const { return getkey() < rhs.getkey(); }\n    };\n\n\n    BtreeBase(stream_ptr  is) : _is(is) { }\n    virtual ~BtreeBase() { }\n\n    virtual int version() const = 0;\n    virtual void readheader() = 0;\n    virtual Page_ptr  makepage(int nr) = 0;\n\n    Page_ptr readpage(int nr)\n    {\n        auto page = makepage(nr);\n        page->readindex();\n        return page;\n    }\n\n    void dump()\n    {\n        print(\"btree v%02d ff=%d, pg=%d, root=%05x, #recs=%d #pgs=%d\\n\",\n                version(), _firstfree, _pagesize, _firstindex, _reccount, _pagecount);\n\n        dumptree(_firstindex);\n    }\n    void dumptree(int nr)\n    {\n        auto page = readpage(nr);\n        page->dump();\n\n        if (page->isindex()) {\n            dumptree(page->getpage(-1));\n            for (unsigned int i=0 ; i<page->indexsize() ; i++)\n                dumptree(page->getpage(i));\n        }\n    }\n\n    stream_ptr pagestream(int nr)\n    {\n        return std::make_shared<sectionstream>(_is, nr*_pagesize, _pagesize);\n    }\n\n    Cursor find(relation_t rel, const std::string& key)\n    {\n        auto page = readpage(_firstindex);\n\n        Cursor cursor(this);\n        relation_t act;\n        while (true)\n        {\n            auto res = page->find(key);\n            dbgprint(\"bt.find %d : %d\\n\", res.act, res.index);\n            cursor.add(page, res.index);\n            if (res.act != REL_RECURSE)\n            {\n                act = res.act;\n                break;\n            }\n            page = readpage(page->getpage(res.index));\n        }\n\n        if (act == rel) {\n            dbgprint(\"same -> pass\\n\");\n            // pass\n        }\n        else if (rel==REL_EQUAL && act!=REL_EQUAL) {\n            cursor.clear();\n            dbgprint(\"not equal -> empty\\n\");\n        }\n        else if ((rel==REL_LESS_EQUAL || rel==REL_GREATER_EQUAL) && act==REL_EQUAL) {\n            dbgprint(\"want: <=/>=, got: == -> pass\\n\");\n            // pass\n        }\n        else if ((rel==REL_GREATER || rel==REL_GREATER_EQUAL) && act==REL_LESS) {\n            dbgprint(\"want: >/>=, got: < -> next\\n\");\n            cursor.next();\n        }\n        else if (rel==REL_GREATER  && act==REL_EQUAL) {\n            dbgprint(\"want: >, got: == -> next\\n\");\n            cursor.next();\n        }\n        else if ((rel==REL_LESS || rel==REL_LESS_EQUAL) && act==REL_GREATER) {\n            dbgprint(\"want: </<=, got: > -> prev\\n\");\n            cursor.prev();\n        }\n        else if (rel==REL_LESS  && act==REL_EQUAL) {\n            dbgprint(\"want: <, got: == -> prev\\n\");\n            cursor.prev();\n        }\n\n        return cursor;\n    }\n};\nclass Page15 : public BasePage {\npublic:\n    Page15(stream_ptr  is, uint32_t nr, int pagesize)\n        : BasePage(is, nr, pagesize)\n    {\n        auto s = makehelper(_is);\n        _preceeding = s.get16le();\n        _count = s.get16le();\n    }\n\n    virtual Entry readent()\n    {\n        auto s = makehelper(_is);\n        if (isindex()) {\n            Entry ent;\n            ent.pagenr = s.get16le();\n            ent.recofs = s.get16le()+1;\n            dbgprint(\"@%04x: ix ent15 %08x %04x\\n\", (int)_is->tellg(), ent.pagenr, ent.recofs);\n            return ent;\n        }\n        else if (isleaf()) {\n            Entry ent;\n            ent.indent = s.get8();\n            /*ent.unknown = */s.get8();\n            ent.recofs = s.get16le()+1;\n            dbgprint(\"@%04x: lf ent15 %+4d %04x\\n\", (int)_is->tellg(), ent.indent, ent.recofs);\n            return ent;\n        }\n        throw \"page not a index or leaf\";\n    }\n};\n\nclass Btree15 : public BtreeBase {\npublic:\n    Btree15(stream_ptr  is) : BtreeBase(is) { }\n    int version() const { return 15; }\n\n    void readheader()\n    {\n        _is->seekg(0);\n        auto s = makehelper(_is);\n        _firstfree = s.get16le();\n        _pagesize = s.get16le();\n        _firstindex = s.get16le();\n        _reccount = s.get32le();\n        _pagecount = s.get16le();\n    }\n\n    virtual Page_ptr makepage(int nr) \n    {\n        dbgprint(\"page15\\n\");\n        return std::make_shared<Page15>(pagestream(nr), nr, _pagesize);\n    }\n};\n\nclass Page16 : public BasePage {\npublic:\n    Page16(stream_ptr  is, uint32_t nr, int pagesize)\n        : BasePage(is, nr, pagesize)\n    {\n        auto s = makehelper(_is);\n        _preceeding = s.get32le();\n        _count = s.get16le();\n    }\n    virtual Entry readent()\n    {\n        auto s = makehelper(_is);\n        if (isindex()) {\n            Entry ent;\n            ent.pagenr = s.get32le();\n            ent.recofs = s.get16le()+1;\n            dbgprint(\"@%04x: ix ent16 %08x %04x\\n\", (int)_is->tellg(), ent.pagenr, ent.recofs);\n            return ent;\n        }\n        else if (isleaf()) {\n            Entry ent;\n            ent.indent = s.get8();\n            /*ent.unknown = */s.get8();\n            /*ent.unknown1 = */s.get16le();\n            ent.recofs = s.get16le()+1;\n            dbgprint(\"@%04x: lf ent16 %+4d %04x\\n\", (int)_is->tellg(), ent.indent, ent.recofs);\n            return ent;\n        }\n        throw \"page not a index or leaf\";\n    }\n\n};\n\nclass Btree16 : public BtreeBase {\npublic:\n    Btree16(stream_ptr  is) : BtreeBase(is) { }\n    int version() const { return 16; }\n\n    void readheader()\n    {\n        _is->seekg(0);\n        auto s = makehelper(_is);\n        _firstfree = s.get32le();\n        _pagesize = s.get16le();\n        _firstindex = s.get32le();\n        _reccount = s.get32le();\n        _pagecount = s.get32le();\n    }\n    virtual Page_ptr makepage(int nr) \n    {\n        dbgprint(\"page16\\n\");\n        return std::make_shared<Page16>(pagestream(nr), nr, _pagesize);\n    }\n\n};\n// v2 b-tree pages - since idav6.7\nclass Page20 : public Page16 {\npublic:\n    Page20(stream_ptr  is, int nr, int pagesize)\n        : Page16(is, nr, pagesize)\n    {\n    }\n    virtual Entry readent()\n    {\n        auto s = makehelper(_is);\n        if (isindex()) {\n            Entry ent;\n            ent.pagenr = s.get32le();\n            ent.recofs = s.get16le();\n            dbgprint(\"@%04x: ix ent20 %08x %04x\\n\", (int)_is->tellg(), ent.pagenr, ent.recofs);\n            return ent;\n        }\n        else if (isleaf()) {\n            Entry ent;\n            ent.indent = s.get16le();\n            /*ent.unknown = */s.get16le();\n            ent.recofs = s.get16le();\n\n            dbgprint(\"@%04x: lf ent20 %+4d %04x\\n\", (int)_is->tellg(), ent.indent, ent.recofs);\n            return ent;\n        }\n        throw \"page not a index or leaf\";\n    }\n\n};\nclass Btree20 : public Btree16 {\npublic:\n    Btree20(stream_ptr  is) : Btree16(is) { }\n    int version() const { return 20; }\n    virtual Page_ptr makepage(int nr) \n    {\n        dbgprint(\"page20\\n\");\n        return std::make_shared<Page20>(pagestream(nr), nr, _pagesize);\n    }\n};\n\n\n// determine which btree type to create for the id0 stream.\ninline std::unique_ptr<BtreeBase> MakeBTree(stream_ptr  is)\n{\n    std::unique_ptr<BtreeBase> bt;\n    is->seekg(0);\n    char data[64];\n    is->read(data, 64);\n\n    dbgprint(\"mkbt: %b\\n\", std::string(data, data+64));\n\n    if (std::equal(data+13, data+13+25, \"B-tree v 1.5 (C) Pol 1990\")) {\n        bt = std::make_unique<Btree15>(is);\n    }\n    else if (std::equal(data+19, data+19+25, \"B-tree v 1.6 (C) Pol 1990\")) {\n        bt = std::make_unique<Btree16>(is);\n    }\n    else if (std::equal(data+19, data+19+9, \"B-tree v2\")) {\n        bt = std::make_unique<Btree20>(is);\n    }\n    else {\n        throw \"unknown btree version\";\n    }\n\n    bt->readheader();\n\n    return bt;\n}\n\n// NodeKeys is used to create btree keys with the right format\n// for the current database.\nclass NodeKeys {\n    int _w;\npublic:\n    NodeKeys(int wordsize)\n        : _w(wordsize)\n    {\n    }\n\n\n    template<typename P>\n    void setwordle(P first, P last, uint64_t w)\n    {\n        if (_w==8)\n            EndianTools::setle64(first, last, w);\n        else if (_w==4)\n            EndianTools::setle32(first, last, w);\n    }\n    template<typename P>\n    void setwordbe(P first, P last, uint64_t w)\n    {\n        if (_w==8)\n            EndianTools::setbe64(first, last, w);\n        else if (_w==4)\n            EndianTools::setbe32(first, last, w);\n    }\n    template<typename P>\n    int make_name_key(P first, P last, uint64_t id)\n    {\n        if (last-first<1+_w)\n            throw \"not enough space\";\n        P p = first;\n        *p++ = 'N';\n        setwordbe(p, last, id); p += _w;\n\n        return p-first;\n    }\n    template<typename P>\n    int make_name_key(P first, P last, const std::string& name)\n    {\n        if (last-first<1+name.size())\n            throw \"not enough space\";\n        P p = first;\n        *p++ = 'N';\n        std::copy(name.begin(), name.end(), p);\n        p += name.size();\n\n        return p-first;\n    }\n\n    template<typename P>\n    int make_node_key(P first, P last, uint64_t nodeid)\n    {\n        if (last-first<1+_w)\n            throw \"not enough space\";\n        P p = first;\n        *p++ = '.';\n        setwordbe(p, last, nodeid); p += _w;\n\n        return p-first;\n    }\n    template<typename P>\n    int make_node_key(P first, P last, uint64_t nodeid, char tag)\n    {\n        if (last-first<2+_w)\n            throw \"not enough space\";\n        P p = first;\n        *p++ = '.';\n        setwordbe(p, last, nodeid); p += _w;\n        *p++ = tag;\n        return p-first;\n    }\n    template<typename P>\n    int make_node_key(P first, P last, uint64_t nodeid, char tag, const std::string& hashkey)\n    {\n        if (last-first<2+_w+hashkey.size())\n            throw \"not enough space\";\n        P p = first;\n        *p++ = '.';\n        setwordbe(p, last, nodeid); p += _w;\n        *p++ = tag;  // usually 'H'\n        std::copy(hashkey.begin(), hashkey.end(), p);\n        p += hashkey.size();\n\n        return p-first;\n    }\n    template<typename P, typename T>\n    int make_node_key(P first, P last, uint64_t nodeid, char tag, T index)\n    {\n        if (last-first<2+2*_w)\n            throw \"not enough space\";\n        P p = first;\n        *p++ = '.';\n        setwordbe(p, last, nodeid); p += _w;\n        *p++ = tag;\n        setwordbe(p, last, index); p += _w;\n\n        return p-first;\n    }\n\n    template<typename V>\n    V make_name_key(uint64_t id)\n    {\n        V key;\n        make_name_key(back_inserter(key), back_inserter(key), id);\n        return key;\n    }\n    template<typename V>\n    V make_name_key(const std::string& name)\n    {\n        V key;\n        make_name_key(back_inserter(key), back_inserter(key), name);\n        return key;\n    }\n\n    template<typename V>\n    V make_node_key(uint64_t nodeid)\n    {\n        V key;\n        make_node_key(back_inserter(key), back_inserter(key), nodeid);\n        return key;\n    }\n    template<typename V>\n    V make_node_key(uint64_t nodeid, char tag)\n    {\n        V key;\n        make_node_key(back_inserter(key), back_inserter(key), nodeid, tag);\n        return key;\n    }\n    template<typename V>\n    V make_node_key(uint64_t nodeid, char tag, const std::string& hashkey)\n    {\n        V key;\n        make_node_key(back_inserter(key), back_inserter(key), nodeid, tag, hashkey);\n        return key;\n    }\n    template<typename V, typename T>\n    V make_node_key(uint64_t nodeid, char tag, T index)\n    {\n        V key;\n        make_node_key(back_inserter(key), back_inserter(key), nodeid, tag, index);\n        return key;\n    }\n};\n\n// convert node values to integer or string.\nstruct NodeValues {\n    static uint64_t getuint(const std::string& str)\n    {\n        switch(str.size()) {\n            case 1:\n                return EndianTools::get8(str.begin(), str.end());\n            case 2:\n                return EndianTools::getle16(str.begin(), str.end());\n            case 4:\n                return EndianTools::getle32(str.begin(), str.end());\n            case 8:\n                return EndianTools::getle64(str.begin(), str.end());\n\n        }\n        throw \"unsupported int type\";\n    }\n    static uint64_t getuintbe(const std::string& str)\n    {\n        switch(str.size()) {\n            case 1:\n                return EndianTools::get8(str.begin(), str.end());\n            case 2:\n                return EndianTools::getbe16(str.begin(), str.end());\n            case 4:\n                return EndianTools::getbe32(str.begin(), str.end());\n            case 8:\n                return EndianTools::getbe64(str.begin(), str.end());\n\n        }\n        throw \"unsupported int type\";\n    }\n    static int64_t getint(const std::string& str)\n    {\n        return (int64_t)getuint(str);\n    }\n    static std::string getstr(std::string data)\n    {\n        // strip terminating zeroes\n        while (!data.empty() && data.back()==0)\n            data.pop_back();\n        return data;\n    }\n};\n\n// provide access to the main part of the IDApro database.\n//\n// use 'find', 'node' and 'blob' to access nodes in the database.\nclass ID0File {\n    std::unique_ptr<BtreeBase> _bt;\n    uint64_t _nodebase;\n    int _wordsize;\n\npublic:\n    enum { INDEX = 0 };  // argument for idb.getsection()\n\n    ID0File(IDBFile& idb, stream_ptr  is)\n        : _bt(MakeBTree(is))\n    {\n        if (idb.magic() == IDBFile::MAGIC_IDA2)\n            _wordsize = 8;\n        else\n            _wordsize = 4;\n\n        _nodebase = uint64_t(0xFF)<<((_wordsize-1)*8);\n    }\n    uint64_t nodebase() const { return _nodebase; }\n    bool is64bit() const { return _wordsize==8; }\n    void dump()\n    {\n        _bt->dump();\n    }\n\n    // function for creating a node key for the current database.\n    template<typename...ARGS>\n    std::string makekey(ARGS...args)\n    {\n        NodeKeys nk(_wordsize);\n        return nk.make_node_key<std::string>(args...);\n    }\n\n    // function for creating a name key for the current database.\n    template<typename...ARGS>\n    std::string makename(ARGS...args)\n    {\n        NodeKeys nk(_wordsize);\n        return nk.make_name_key<std::string>(args...);\n    }\n\n    // search for records in the current database by key.\n    // relation gives the desired relation:\n    // REL_LESS  : return records less than the key.\n    //\n    // returns a cursor object.\n    auto find(relation_t rel, const std::string& key)\n    {\n        return _bt->find(rel, key);\n    }\n\n    // search for records in the current database.\n    // relation gives the desired relation:\n    // REL_LESS  : return records less than the key.\n    //\n    // returns a cursor object.\n    template<typename...ARGS>\n    auto find(relation_t rel, uint64_t nodeid, ARGS...args)\n    {\n        return _bt->find(rel, makekey(nodeid, args...));\n    }\n\n    // return a blob object as a string.\n    std::string blob(uint64_t nodeid, char tag, uint64_t startid = 0, uint64_t lastid = 0xFFFFFFFF)\n    {\n        auto c = _bt->find(REL_GREATER_EQUAL, makekey(nodeid, tag, startid));\n        auto endkey =  makekey(nodeid, tag, lastid);\n\n        std::string blob;\n        while (c.getkey() <= endkey) {\n            blob += c.getval();\n            c.next();\n        }\n\n        return blob;\n    }\n\n    // finds the nodeid by name.\n    //\n    // names can be labels like 'sub_1234', but also internal names like '$ structs', or 'Root Name'\n    uint64_t node(const std::string& name)\n    {\n        auto c = _bt->find(REL_EQUAL, makename(name));\n        if (c.eof())\n            return 0;\n        return NodeValues::getint(c.getval());\n    }\n\n    // callback is called for each nodeid in the list.\n    // examples of lists: '$ structs', '$ enums'\n    template<typename CB>\n    void enumlist(uint64_t nodeid, char tag, CB cb)\n    {\n        auto c = _bt->find(REL_GREATER_EQUAL, makekey(nodeid, tag));\n        auto endkey = makekey(nodeid, tag+1);\n        while (c.getkey() <= endkey)\n            cb(NodeValues::getint(c.getval()));\n    }\n\n    // 'easy' interface: return empty when record not found.\n    // otherwise directly return the value.\n    template<typename...ARGS>\n    std::string getdata(ARGS...args)\n    {\n        auto c = find(REL_EQUAL, makekey(args...));\n        if (c.eof())\n            return {};\n        return c.getval();\n    }\n    template<typename...ARGS>\n    std::string getstr(ARGS...args)\n    {\n        // until ida6.7 strings were stored zero terminated.\n        auto c = find(REL_EQUAL, makekey(args...));\n        if (c.eof())\n            return {};\n        return NodeValues::getstr(c.getval());\n    }\n    template<typename...ARGS>\n    uint64_t getuint(ARGS...args)\n    {\n        auto c = find(REL_EQUAL, makekey(args...));\n        if (c.eof())\n            return {};\n        return NodeValues::getuint(c.getval());\n    }\n    uint64_t getuint(BtreeBase::Cursor& c)\n    {\n        return NodeValues::getuint(c.getval());\n    }\n\n    // returns the node name, resolves long names.\n    std::string getname(uint64_t node)\n    {\n        auto c = find(REL_EQUAL, makekey(node, 'N'));\n        if (c.eof())\n            return {};\n        auto val = c.getval();\n        if (val.empty())\n            return {};\n        if (val[0]==0) {\n            val.erase(val.begin());\n            // bigname\n            uint64_t nameid = NodeValues::getuintbe(val);\n            val = blob(_nodebase, 'S', nameid*256, nameid*256+32);\n        }\n        return NodeValues::getstr(val);\n    }\n};\n\n#ifndef BADADDR \n#define BADADDR uint64_t(-1)\n#endif\n\n// the ID1File contains information on segments, and stores the flags for each byte.\n// basically this is the data for the idc GetFlags(ea)  function.\nclass ID1File {\n    struct segment {\n        uint32_t start_ea;\n        uint32_t end_ea;\n        uint32_t id1ofs;\n    };\n    typedef std::vector<segment> segmentlist_t;\n    segmentlist_t _segments;\n\n    stream_ptr _is;\n    int _wordsize;\n\nprivate:\n\n    void open()\n    {\n        auto s = makehelper(_is, _wordsize);\n        s.seekg(0);\n        uint32_t magic = s.get32le();\n        if ((magic&0xFFF0FFFF)==0x306156) { // Va0 .. Va4\n            uint16_t nsegments = s.get16le();\n            uint16_t npages    = s.get16le();\n            (void)npages; // value not used\n\n            _segments.resize(nsegments);\n            for (unsigned i=0 ; i<nsegments ; i++) {\n                _segments[i].start_ea = s.getword();\n                _segments[i].end_ea   = s.getword();\n                _segments[i].id1ofs   = s.getword();\n            }\n        }\n        else if (magic==0x2a4156) {\n            uint32_t unk1      = s.get32le();      // 3\n            uint32_t nsegments = s.get32le(); \n            uint32_t unk2      = s.get32le();      // 0x800 -- # addrs per page?\n            uint32_t npages    = s.get32le();\n            (void)unk1; (void)unk2;  (void)npages;  // values not used\n\n            _segments.resize(nsegments);\n            uint32_t ofs= 0x2000;\n            for (unsigned i=0 ; i<nsegments ; i++) {\n                auto &seg= _segments[i];\n                seg.start_ea = s.getword();\n                seg.end_ea   = s.getword();\n                seg.id1ofs= ofs;\n\n                ofs += 4*(seg.end_ea-seg.start_ea);\n            }\n        }\n        else {\n            throw \"invalid id1\";\n        }\n    }\n    segmentlist_t::const_iterator find_segment(uint64_t ea) const\n    {\n        for (segmentlist_t::const_iterator i=_segments.begin() ; i!=_segments.end() ; i++)\n        {\n            if ((*i).start_ea <= ea && ea<(*i).end_ea)\n                return i;\n        }\n        return _segments.end();\n    }\n\npublic:\n    enum { INDEX = 1 };  // argument for idb.getsection()\n\n    ID1File(IDBFile& idb, stream_ptr  is)\n        : _is(is)\n    {\n        if (idb.magic() == IDBFile::MAGIC_IDA2)\n            _wordsize = 8;\n        else\n            _wordsize = 4;\n\n        open();\n    }\n    void dump_info()\n    {\n        print(\"id1: %d segments\\n\", _segments.size());\n        for (unsigned i=0 ; i<_segments.size() ; i++)\n            print(\"  %08x-%08x @ %08x\\n\", _segments[i].start_ea, _segments[i].end_ea, _segments[i].id1ofs);\n    }\n\n    uint32_t GetFlags(uint64_t ea) const\n    {\n        // for optimization maybe i need to implement some kind of flags page caching\n        segmentlist_t::const_iterator i= find_segment(ea);\n        if (i==_segments.end())\n            return 0;\n\n        auto s = makehelper(_is, _wordsize);\n        s.seekg((*i).id1ofs+4*(ea-(*i).start_ea));\n\n        return s.get32le();\n    }\n    uint64_t FirstSeg()\n    {\n        if (_segments.empty())\n            return BADADDR;\n        return _segments.front().start_ea;\n    }\n    uint64_t NextSeg(uint64_t ea)\n    {\n        segmentlist_t::const_iterator i= find_segment(ea);\n        if (i==_segments.end())\n            return BADADDR;\n        i++;\n        if (i==_segments.end())\n            return BADADDR;\n        return (*i).start_ea;\n    }\n    uint64_t SegStart(uint64_t ea)\n    {\n        segmentlist_t::const_iterator i= find_segment(ea);\n        if (i==_segments.end())\n            return BADADDR;\n        return (*i).start_ea;\n    }\n    uint64_t SegEnd(uint64_t ea)\n    {\n        segmentlist_t::const_iterator i= find_segment(ea);\n        if (i==_segments.end())\n            return BADADDR;\n        return (*i).end_ea;\n    }\n};\n\n// NAMFile keeps a list of named items.\nclass NAMFile {\nprivate:\n    mutable std::vector<uint64_t> _namedoffsets;\n    mutable bool _namesloaded;\n\n    stream_ptr _is;\n    int _wordsize;\n\n    uint32_t _nnames;\n    uint64_t _listofs;\npublic:\n    enum { INDEX = 2 };  // argument for idb.getsection()\n\n    NAMFile(IDBFile& idb, stream_ptr  is)\n        : _namesloaded(false), _is(is)\n    {\n        if (idb.magic() == IDBFile::MAGIC_IDA2)\n            _wordsize = 8;\n        else\n            _wordsize = 4;\n\n\n        open();\n    }\n\n    void open()\n    {\n        auto s = makehelper(_is, _wordsize);\n        s.seekg(0);\n        uint32_t magic = s.get32le();\n\n        if ((magic&0xFFF0FFFF)==0x306156) { // Va0 .. Va4\n            uint16_t npages = s.get16le();  // nr of chunks\n            uint16_t eof = s.get16le();\n            uint64_t unknown = s.getword();\n            (void)npages; (void)eof; // value not used\n\n            _nnames = s.getword();  // nr of names\n            _listofs = s.getword(); // page size\n\n            dbgprint(\"nam: np=%d, eof=%d, nn=%d, ofs=%08x\\n\",\n                    npages, eof, _nnames, _listofs);\n            if (unknown)\n                print(\"!! nam.unknown=%08x\\n\", unknown);\n        }\n        else if (magic==0x2a4156) {\n            uint32_t unk1 = s.get32le();    // 3\n            uint32_t npages = s.get32le();  // nr of chunks\n            uint32_t unk2 = s.get32le();    // 0x800\n            uint32_t eof = s.get32le();     // 0x15\n            uint64_t unknown = s.getword(); // 0\n            (void)unk1; (void)npages;  (void)unk2; (void)eof; (void)unknown; // values not used\n\n            _listofs = 0x2000;\n            _nnames = s.getword();  // nr of names\n\n            dbgprint(\"nam: np=%d, eof=%d, nn=%d, ofs=%08x\\n\",\n                    npages, eof, _nnames, _listofs);\n        }\n        else {\n            throw \"invalid NAM\";\n        }\n\n        if (_wordsize==8)\n            _nnames /= 2;\n    }\n    void loadoffsets() const\n    {\n        if (_namesloaded)\n            return;\n        _namedoffsets.reserve(_nnames);\n\n        auto s = makehelper(_is, _wordsize);\n        s.seekg(_listofs);\n        for (unsigned i=0 ; i<_nnames ; i++)\n            _namedoffsets.push_back(s.getword());\n\n        _namesloaded = true;\n    }\n    int numnames() const\n    {\n        loadoffsets();\n        return _namedoffsets.size();\n    }\n    template<typename FN>\n    void enumerate(FN fn) const\n    {\n        loadoffsets();\n        std::for_each(_namedoffsets.begin(), _namedoffsets.end(), fn);\n    }\n    // finds nearest named item\n    uint64_t findname(uint64_t ea) const\n    {\n        loadoffsets();\n        if (_namedoffsets.empty())\n            return BADADDR;\n        auto i= std::upper_bound(_namedoffsets.begin(), _namedoffsets.end(), ea);\n        if (i==_namedoffsets.begin()) {\n            // address before first: return first named item\n            return *i;\n        }\n        i--;\n        return *i;\n    }\n\n    uint64_t firstnamed() const\n    {\n        loadoffsets();\n        if (_namedoffsets.empty())\n            return BADADDR;\n        return *_namedoffsets.begin();\n    }\n};\n\n// packs/unpacks structured data\nclass BaseUnpacker  {\npublic:\n    virtual bool eof() const = 0;\n    virtual uint16_t next16() = 0;\n    virtual uint32_t next32() = 0;\n    virtual uint64_t nextword() = 0;\n    virtual ~BaseUnpacker() { }\n};\ntemplate<typename P>\nclass Unpacker : public BaseUnpacker {\n    P _p;\n    P _last;\n    bool _use64;\n    public:\n\n    Unpacker(P first, P last, bool use64)\n        : _p(first), _last(last), _use64(use64)\n    {\n    }\n    bool eof() const\n    {\n        return _p>=_last;\n    }\n\n    /*\n     *  7 bit  - values 0 .. 0x7f\n     *  14 bit - values 0x80 .. 0x3fff,  orred with 0x8000\n     *  0xFF + 2 byte - any 16 bit val\n     */\n    uint16_t next16()\n    {\n        if (_p>=_last)  throw \"unpack: no data\";\n        uint8_t byte = *_p++;\n        if (byte==0xff) {\n            if (_p+2>_last)  throw \"unpack: no data\";\n            uint16_t value = EndianTools::getbe16(_p, _last);\n            _p += 2;\n            return value;\n        }\n        if (byte<0x80)\n            return byte;\n        _p--;\n        if (_p+2>_last)  throw \"unpack: no data\";\n        uint16_t value = EndianTools::getbe16(_p, _last) & 0x3FFF;  _p += 2;\n        return value;\n    }\n\n    /*\n     *  7 bit - values 0 .. 0x7f\n     *  14 bit - values 0x80 .. 0x3fff,  orred with 0x8000\n     *  29 bit - values 0x4000 .. 0x1fffffff, orred with 0xc0000000\n     *\n     *  0xFF + 4 byte - any 32 bit val  - in .idb\n     *\n     *  in .i64, 64 bit values are encoded as the low 32bit value followed by high\n     *  32 bit value using any of the above encodings. So the byte order is kind of twisted.\n     *\n     */\n    uint32_t next32()\n    {\n        if (_p>=_last)  throw \"unpack: no data\";\n        uint8_t byte = *_p++;\n        if (byte==0xff) {\n            if (_p+4>_last)  throw \"unpack: no data\";\n            uint32_t value = EndianTools::getbe32(_p, _last);\n            _p += 4;\n            return value;\n        }\n        if (byte<0x80) {\n            return byte;\n        }\n        _p--;\n        if (byte<0xc0) {\n            if (_p+2>_last)  throw \"unpack: no data\";\n            uint32_t value = EndianTools::getbe16(_p, _last) & 0x3FFF;  _p += 2;\n            return value;\n        }\n        if (_p+4>_last)  throw \"unpack: no data\";\n        uint32_t value = EndianTools::getbe32(_p, _last) & 0x1FFFFFFF;  _p += 4;\n        return value;\n\n    }\n    uint64_t nextword()\n    {\n        uint64_t lo = next32();\n        if (_use64)\n        {\n            uint64_t hi = next32();\n            return lo|(hi<<32);\n        }\n        return lo;\n    }\n\n};\ntemplate<typename I, typename O>\nstatic void idaunpack(I first, I last, O out)\n{\n    Unpacker<I> p(first, last, false);\n    \n    while (!p.eof())\n        *out++ = p.next32();\n}\n\ntemplate<typename P>\nUnpacker<P> makeunpacker(P first, P last, bool use64)\n{\n    return Unpacker<P>(first, last, use64);\n}\n\ntypedef std::vector<uint32_t> DwordVector;\n\n// used mostly in lists, where the stored value is one less than the actually used value.\n// lists like: $enums, $structs, $scripts, values of enums, masks of bitfields, values of bitmasks\n// backref of bitfield value to mask.\ninline uint64_t minusone(uint64_t id)\n{\n    if (id) return id-1;\n    return 0;\n}\n\n// 'd'  xref-from  -> points to used type\n// 'D'  xref-to    -> points to type users\n\nclass StructMember {\n    /*\n     *    (membernode, N)          = struct.member-name\n     *    (membernode, A, 3)       = structid+1\n     *    (membernode, A, 8)       = \n     *    (membernode, A, 11)      = enumid+1\n     *    (membernode, A, 16)      = flag?  -- 4:variable length flag?\n     *    (membernode, S, 0x3000)  = type (set with 'Y')\n     *    (membernode, S, 0x3001)  = names used in 'type'\n     *    (membernode, S, 5)       = array type?\n     *    (membernode, S, 9)       = offset-type\n     *    (membernode, D, address) = xref-type\n     *    (membernode, d, structid) = xref-type   -- for sub-structs\n     */\n    ID0File& _id0;\n    uint64_t _nodeid;\n    uint64_t _skip;  // nr of bytes to skip before this member\n    uint64_t _size;  // size in bytes of this member\n    uint32_t _flags;\n    uint32_t _props;\n    uint64_t _ofs;\npublic:\n    StructMember(ID0File& id0, BaseUnpacker& spec)\n        : _id0(id0)\n    {\n        _nodeid = spec.nextword();\n        _skip = spec.nextword();\n        _size = spec.nextword();\n        _flags = spec.next32();\n        _props = spec.next32();\n        _ofs = 0;\n    }\n    void setofs(uint64_t ofs) { _ofs = ofs; }\n\n    uint64_t nodeid() const { return _nodeid + _id0.nodebase(); }\n    uint64_t skip() const { return _skip; }\n    uint64_t size() const { return _size; }\n    uint32_t flags() const { return _flags; }\n    uint32_t props() const { return _props; }\n    uint64_t offset() const { return _ofs; }\n\n    std::string name() const { return _id0.getname(nodeid()); }\n\n    uint64_t enumid() const { return minusone(_id0.getuint(nodeid(), 'A', 11)); }\n    uint64_t structid() const { return minusone(_id0.getuint(nodeid(), 'A', 3)); }\n    std::string comment(bool repeatable) const { return _id0.getstr(nodeid(), 'S', repeatable ? 1 : 0); }\n    std::string ptrinfo() const { return _id0.getdata(nodeid(), 'S', 9); }\n\n    // types from typeinfo:\n        // 11 00  _BYTE\n        // 32 00  char\n        // 22 00  unsigned __int8\n        // 02 00  __int8\n        // ='WORD\"  WORD\n        // 03 00  short\n        // 07 00  int\n        // 03 00  __int16\n        // 28 00  _BOOL2\n        // 10 00  _WORD\n    std::string typeinfo() const { return _id0.getdata(nodeid(), 'S', 0x3000); }\n};\n\n// access structs and struct members.\nclass Struct {\n    /*\n     *    (structnode, N)          = structname\n     *    (structnode, D, address) = xref-type\n     *    (structnode, M, 0)       = packed struct info\n     *    (structnode, S, 27)      = packed value(addr, byte)\n     */\n    ID0File& _id0;\n    uint64_t _nodeid;\n\n    uint32_t _flags;\n    std::vector<StructMember> _members;\n    uint32_t _seqnr;\n\n    uint32_t _size;\n\n    class Iterator : public std::iterator<std::random_access_iterator_tag, StructMember> {\n        const Struct* _s;\n        int _ix;\n    public:\n        Iterator(const Struct*s, int ix) : _s(s), _ix(ix) { }\n        Iterator() : _s(nullptr), _ix(0) { }\n        Iterator(const Iterator& i) : _s(i._s), _ix(i._ix) { }\n\n        bool operator==(const Iterator& rhs) {return _ix==rhs._ix;}\n        bool operator!=(const Iterator& rhs) {return _ix!=rhs._ix;}\n\n        const StructMember& operator*() {return _s->member(_ix);}\n        const StructMember& operator[](int i) {return _s->member(_ix+i);}\n\n        Iterator& operator++() {++_ix;return *this;}\n        Iterator operator++(int) {Iterator tmp(*this); operator++(); return tmp;}\n        Iterator& operator--() {--_ix;return *this;}\n        Iterator operator--(int) {Iterator tmp(*this); operator--(); return tmp;}\n\n        Iterator& operator+=(int n) {  _ix += n; return *this; }\n        Iterator& operator-=(int n) {  _ix -= n; return *this; }\n\n        friend Iterator operator+(int n, Iterator p) {  return p+=n; }\n        friend Iterator operator+(Iterator p, int n) {  return p+=n; }\n        friend Iterator operator-(Iterator p, int n) {  return p-=n; }\n        friend int operator-(const Iterator& p, const Iterator& q) {  return p._ix-q._ix; }\n\n        bool operator<(const Iterator& rhs) { return _ix<rhs._ix; }\n        bool operator<=(const Iterator& rhs) { return _ix<=rhs._ix; }\n        bool operator>(const Iterator& rhs) { return _ix>rhs._ix; }\n        bool operator>=(const Iterator& rhs) { return _ix>=rhs._ix; }\n    };\n\npublic:\n    Struct(ID0File& id0, uint64_t nodeid)\n        : _id0(id0), _nodeid(nodeid)\n    {\n        auto spec = _id0.blob(_nodeid, 'M');\n        auto p = makeunpacker(spec.begin(), spec.end(), _id0.is64bit());\n\n        _flags = p.next32();\n        uint32_t nmember = p.next32();\n        uint64_t ofs = 0;\n        while (nmember--) {\n            _members.emplace_back(_id0, p);\n            ofs += _members.back().skip();\n            _members.back().setofs(ofs);\n            ofs += _members.back().size();\n        }\n        _size = ofs;\n        if (!p.eof())\n            _seqnr = p.next32();\n        else\n            _seqnr = 0;\n    }\n    std::string name() const { return _id0.getname(_nodeid); }\n    std::string comment(bool repeatable) const { return _id0.getstr(_nodeid, 'S', repeatable ? 1 : 0); }\n    int nmembers() const { return _members.size(); }\n    uint32_t flags() const { return _flags; }\n    uint32_t seqnr() const { return _seqnr; }\n    uint32_t size() const { return _size; }\n\n    Iterator begin() const { return Iterator(this, 0); }\n    Iterator end() const { return Iterator(this, nmembers()); }\n    const StructMember& member(int ix) const { return _members[ix]; }\n\n\n};\n\nclass EnumMember {\n    /*\n     *   (membernode, N)      = membername\n     *   (membernode, A, -2)  = enumnode + 1\n     *   (membernode, A, -3)  = member value\n     */\n    ID0File& _id0;\n    uint64_t _nodeid;\n    uint64_t _value;\npublic:\n    EnumMember(ID0File& id0, uint64_t nodeid)\n        : _id0(id0), _nodeid(nodeid)\n    {\n        _value = _id0.getuint(_nodeid, 'A', -3);\n    }\n\n    // 'A', -2  -> points to enum node\n    uint64_t nodeid() const { return _nodeid; }\n    uint64_t value() const { return _value; }\n    std::string name() const { return _id0.getname(nodeid()); }\n\n    std::string comment(bool repeatable) const { return _id0.getstr(nodeid(), 'S', repeatable ? 1 : 0); }\n\n};\n// get properties of an enum.\n// bitfields and enums are both in the '$ enums' list.\nclass Enum {\n    /*\n     *     (enumnode, N)     = enum-name\n     *     (enumnode, A, -1) = nr of values\n     *     (enumnode, A, -3) = representation\n     *     (enumnode, A, -5) = flags: bitfield, hidden, ...\n     *     (enumnode, A, -8) = \n     *     (enumnode, E, value) = valuenode + 1\n     */\n    ID0File& _id0;\n    uint64_t _nodeid;\n\npublic:\n    Enum(ID0File& id0, uint64_t nodeid)\n        : _id0(id0), _nodeid(nodeid)\n    {\n    }\n    uint64_t nodeid() const { return _nodeid; }\n    uint64_t count() const { return _id0.getuint(_nodeid, 'A', -1); }\n\n    // >>20 : 0x11=hex, 0x22=dec, 0x77=oct, 0x66=bin, 0x33=char\n    // values: FF_0NUMx|FF_1NUMx   x=H,D,O,B,CHAR\n    //\n    // >>16 : 0x2  = signed : FF_SIGN\n    uint32_t representation() const { return _id0.getuint(_nodeid, 'A', -3); }\n\n    // bit0 = bitfield  ENUM_FLAGS_IS_BF\n    // bit1 = hidden    ENUM_FLAGS_HIDDEN   \n    // bit2 = fromtil   ENUM_FLAGS_FROMTIL\n    // bit5-3 =  width 0..7 = (0,1,2,4,8,16,32,64)\n    // bit6 = ghost     ENUM_FLAGS_GHOST\n    uint32_t flags() const { return _id0.getuint(_nodeid, 'A', -5); }\n\n    // 'A',-8  -> index in $enums list\n\n    std::string name() const { return _id0.getname(_nodeid); }\n    std::string comment(bool repeatable) const { return _id0.getstr(_nodeid, 'S', repeatable ? 1 : 0); }\n\n    auto first() const { return _id0.find(REL_GREATER_EQUAL, _id0.makekey(_nodeid, 'E')); }\n    std::string lastkey() const { return _id0.makekey(_nodeid, 'F'); }\n\n    EnumMember getvalue(BtreeBase::Cursor& c) const\n    {\n        return EnumMember(_id0, minusone(_id0.getuint(c)));\n    }\n};\n\nclass BitfieldValue {\n    ID0File& _id0;\n    uint64_t _nodeid;\n    uint64_t _value;\n    uint64_t _mask;\npublic:\n    BitfieldValue(ID0File& id0, uint64_t nodeid)\n        : _id0(id0), _nodeid(nodeid)\n    {\n        _value = _id0.getuint(_nodeid, 'A', -3);\n        _mask = _id0.getuint(_nodeid, 'A', -6) - 1;\n    }\n\n    uint64_t nodeid() const { return _nodeid; }\n    std::string name() const { return _id0.getname(nodeid()); }\n\n    std::string comment(bool repeatable) const { return _id0.getstr(nodeid(), 'S', repeatable ? 1 : 0); }\n    uint64_t value() const { return _value; }\n    uint64_t mask() const { return _mask; }\n\n    // 'A', -2  -> points to enum node\n    // 'A', -6  -> minusone -> maskid from : (enum, 'm', maskid)\n};\nclass BitfieldMask {\n    ID0File& _id0;\n    uint64_t _nodeid;\n    uint64_t _mask;\n\npublic:\n    BitfieldMask(ID0File& id0, uint64_t nodeid, uint64_t mask)\n        : _id0(id0), _nodeid(nodeid), _mask(mask)\n    {\n    }\n//  BitfieldMask(const BitfieldMask& bf)\n//      : _id0(bf._id0), _nodeid(bf._nodeid), _mask(bf._mask)\n//  {\n//  }\n\n    uint64_t nodeid() const { return _nodeid; }\n    std::string name() const { return _id0.getname(nodeid()); }\n    std::string comment(bool repeatable) const { return _id0.getstr(nodeid(), 'S', repeatable ? 1 : 0); }\n\n    uint64_t mask() const { return _mask; }\n\n    auto first() const { return _id0.find(REL_GREATER_EQUAL, _id0.makekey(_nodeid, 'E')); }\n    std::string lastkey() const { return _id0.makekey(_nodeid, 'F'); }\n\n    BitfieldValue getvalue(BtreeBase::Cursor& c) const\n    {\n        return BitfieldValue(_id0, minusone(_id0.getuint(c)));\n    }\n};\n\n// get properties of a bitfield.\n// bitfields and enums are both in the '$ enums' list.\nclass Bitfield {\n    ID0File& _id0;\n    uint64_t _nodeid;\n\npublic:\n    Bitfield(ID0File& id0, uint64_t nodeid)\n        : _id0(id0), _nodeid(nodeid)\n    {\n    }\n    uint64_t count() const { return _id0.getuint(_nodeid, 'A', -1); }\n\n    // >>20 : 0x11=hex, 0x22=dec, 0x77=oct, 0x66=bin, 0x33=char\n    // values: FF_0NUMx|FF_1NUMx   x=H,D,O,B,CHAR\n    //\n    // >>16 : 0x2  = signed : FF_SIGN\n    uint32_t representation() const { return _id0.getuint(_nodeid, 'A', -3); }\n\n    // bit0 = bitfield  ENUM_FLAGS_IS_BF\n    // bit1 = hidden    ENUM_FLAGS_HIDDEN   \n    // bit2 = fromtil   ENUM_FLAGS_FROMTIL\n    // bit5-3 =  width 0..7 = (0,1,2,4,8,16,32,64)\n    // bit6 = ghost     ENUM_FLAGS_GHOST\n    uint32_t flags() const { return _id0.getuint(_nodeid, 'A', -5); }\n\n    // 'A',-8  -> index in $enums list\n\n    std::string name() const { return _id0.getname(_nodeid); }\n    std::string comment(bool repeatable) const { return _id0.getstr(_nodeid, 'S', repeatable ? 1 : 0); }\n\n    // for bitmasks there is an extra level: 'm'  in between.\n    auto first() const { return _id0.find(REL_GREATER_EQUAL, _id0.makekey(_nodeid, 'm')); }\n    std::string lastkey() const { return _id0.makekey(_nodeid, 'n'); }\n\n    BitfieldMask getmask(BtreeBase::Cursor& c)\n    {\n        auto key = c.getkey();\n        uint64_t mask;\n\n        // get the mask from the key index,\n        // ... not really nescesary, since we can also get the mask\n        // from the BitfieldValue : node('A',-6) - 1\n        if (_id0.is64bit() ) {\n            assert(key.size()==18);\n            mask = EndianTools::getbe64(&key[10], &key[10]+8);\n        }\n        else {\n            assert(key.size()==10);\n            mask = EndianTools::getbe32(&key[6], &key[6]+4);\n        }\n\n        return BitfieldMask(_id0, minusone(_id0.getuint(c)), mask);\n    }\n\n};\n\n// access scripts by nodeid,\n// use name(), language() and body()  to access\n// the contents of the script.\nclass Script {\n    ID0File& _id0;\n    uint64_t _nodeid;\n\npublic:\n    Script(ID0File& id0, uint64_t nodeid)\n        : _id0(id0), _nodeid(nodeid)\n    {\n    }\n    std::string name() const { return _id0.getstr(_nodeid, 'S', 0); }\n    std::string language() const { return _id0.getstr(_nodeid, 'S', 1); }\n    std::string body() const { return NodeValues::getstr(_id0.blob(_nodeid, 'X')); }\n};\ntemplate<typename T>\nclass List {\n    /*\n     *  (listnode, 'N')           = listname\n     *  (listnode, 'A', -1)       = list size      <-- not for '$ scriptsnippets'\n     *  (listnode, 'A', seqnr)    = itemnode+1\n     *\n     *  (listnode, 'Y', itemnode) = seqnr          <-- only for '$ enums'\n     *\n     *  (listnode, 'Y', 0)        = list size      <-- only for '$ scriptsnippets'\n     *  (listnode, 'Y', 1)        = ?              <-- only for '$ scriptsnippets'\n     */\n    ID0File& _id0;\n    BtreeBase::Cursor _c;\n    std::string _endkey;\npublic:\n    List(ID0File& id0, uint64_t nodeid)\n        : _id0(id0), _c(_id0.find(REL_GREATER, _id0.makekey(nodeid, 'A')))\n    {\n        _endkey = _id0.makekey(nodeid, 'A', -1);\n    }\n    bool eof() const { return !(_c.getkey() < _endkey); }\n    T next() \n    { \n        uint64_t id = minusone(_id0.getuint(_c));\n        _c.next();\n        return T(_id0, id);\n    }\n};\n"
  },
  {
    "path": "tests/CMakeLists.txt",
    "content": "find_package(doctest REQUIRED)\n\nfile(GLOB UnittestSrc *.cpp)\nadd_executable(idbutil_unittests ${UnittestSrc})\nset_property(TARGET idbutil_unittests PROPERTY OUTPUT_NAME unittests)\ntarget_link_libraries(idbutil_unittests PRIVATE cpputils idblib doctest::doctest)\ntarget_compile_definitions(idbutil_unittests PRIVATE WITH_DOCTEST)\n\ninclude(CTest)\ninclude(doctest OPTIONAL RESULT_VARIABLE res)\nif (res STREQUAL NOTFOUND)\n    add_test(NAME IdbutilTest COMMAND idbutil_unittests)\nelse()\n    doctest_discover_tests(idbutil_unittests)\nendif()\n"
  },
  {
    "path": "tests/test-idb3.cpp",
    "content": "#include \"unittestframework.h\"\n\n#include <climits>\n#include <idblib/idb3.h>\n\nstd::string CreateTestIndexPage(int pagesize)\n{\n    std::string page(pagesize, char(0));\n\n    auto  oi = page.begin();\n    auto  ei = page.begin() + pagesize/2;\n\n    auto  od = page.begin() + pagesize/2;\n    auto  ed = page.end();\n\n    auto et = EndianTools();\n    et.setle32(oi, ei, 122); oi += 4;\n    et.setle16(oi, ei, 3); oi += 2;\n\n    auto addkv = [&](const std::string& key, const std::string& val, int pagenr) {\n        et.setle32(oi, ei, pagenr); oi += 4;\n        et.setle16(oi, ei, od-page.begin()); oi += 2;\n\n        et.setle16(od, ed, key.size()); od += 2;\n        std::copy(key.begin(), key.end(), od);  od += key.size();\n        et.setle16(od, ed, val.size()); od += 2;\n        std::copy(val.begin(), val.end(), od);  od += val.size();\n    };\n    addkv(\"Nabcde\", std::string{\"\\x01\\x00\\x00\\xFF\",4}, 123);\n    addkv(\"Nbcdef\", std::string{\"\\x02\\x00\\x00\\xFF\",4}, 125);\n    addkv(\"Ncdef\",  std::string{\"\\x03\\x00\\x00\\xFF\",4}, 127);\n\n    return page;\n}\n\nTEST_CASE(\"TestIndexPage\") {\n    auto tstdata = CreateTestIndexPage(2048);\n    auto page = std::make_unique<Page20>(std::make_shared<std::stringstream>(tstdata), 1, 2048);\n    page->readindex();\n\n    CHECK( page->isindex() == true );\n    CHECK( page->isleaf() == false );\n\n    CHECK( page->getpage(-1) == 122 );\n    CHECK( page->getpage(0) == 123 );\n    CHECK( page->getpage(1) == 125 );\n    CHECK( page->getpage(2) == 127 );\n\n    CHECK_THROWS( page->getpage(3) );\n\n    CHECK( page->getkey(0) == \"Nabcde\" );\n    CHECK( page->getkey(1) == \"Nbcdef\" );\n    CHECK( page->getkey(2) == \"Ncdef\" );\n\n    CHECK_THROWS( page->getkey(3) );\n\n    CHECK_FALSE(page->getval(0) == std::string{\"fail\"});\n\n    CHECK( page->getval(0) == (std::string{\"\\x01\\x00\\x00\\xFF\",4}) );\n    CHECK( page->getval(1) == (std::string{\"\\x02\\x00\\x00\\xFF\",4}) );\n    CHECK( page->getval(2) == (std::string{\"\\x03\\x00\\x00\\xFF\",4}) );\n\n    CHECK_FALSE(page->find(\"fail\") == (BasePage::result{REL_EQUAL,2}));\n\n    CHECK( page->find(\"N\") == (BasePage::result{REL_RECURSE,-1}) );\n    CHECK( page->find(\"Nabcde\") == (BasePage::result{REL_EQUAL,0}) );\n    CHECK( page->find(\"Nbcdef\") == (BasePage::result{REL_EQUAL,1}) );\n    CHECK( page->find(\"Nbzzzz\") == (BasePage::result{REL_RECURSE,1}) );\n    CHECK( page->find(\"Ncdef\") == (BasePage::result{REL_EQUAL,2}) );\n    CHECK( page->find(\"Nzzzz\") == (BasePage::result{REL_RECURSE,2}) );\n}\nstd::string CreateTestLeafPage(int pagesize)\n{\n    std::string page(pagesize, char(0));\n\n    auto  oi = page.begin();\n    auto  ei = page.begin() + pagesize/2;\n\n    auto  od = page.begin() + pagesize/2;\n    auto  ed = page.end();\n\n    auto et = EndianTools();\n    et.setle32(oi, ei, 0); oi += 4;\n    et.setle16(oi, ei, 3); oi += 2;\n\n    auto addkv = [&](const std::string& key, const std::string& val, int indent) {\n        et.setle32(oi, ei, indent); oi += 4;\n        et.setle16(oi, ei, od-page.begin()); oi += 2;\n\n        et.setle16(od, ed, key.size()-indent); od += 2;\n        std::copy(key.begin()+indent, key.end(), od);  od += key.size()-indent;\n        et.setle16(od, ed, val.size()); od += 2;\n        std::copy(val.begin(), val.end(), od);  od += val.size();\n    };\n    addkv(\"Nabcde\", std::string{\"\\x01\\x00\\x00\\xFF\",4}, 0);\n    addkv(\"Nbcdef\", std::string{\"\\x02\\x00\\x00\\xFF\",4}, 1);\n    addkv(\"Ncdef\",  std::string{\"\\x03\\x00\\x00\\xFF\",4}, 1);\n\n    return page;\n}\nvoid TestLeafPage()\n{\n    auto tstdata = CreateTestLeafPage(2048);\n    auto page = std::make_unique<Page20>(std::make_shared<std::stringstream>(tstdata), 1, 2048);\n    page->readindex();\n\n    CHECK( page->isindex() == false );\n    CHECK( page->isleaf() == true );\n\n    CHECK_THROWS( page->getpage(0) );\n\n    CHECK( page->getkey(0) == \"Nabcde\" );\n    CHECK( page->getkey(1) == \"Nbcdef\" );\n    CHECK( page->getkey(2) == \"Ncdef\" );\n\n    CHECK_THROWS( page->getkey(3) );\n\n    CHECK_FALSE( page->getval(0) == std::string{\"fail\"} );\n\n    CHECK( page->getval(0) == (std::string{\"\\x01\\x00\\x00\\xFF\",4}) );\n    CHECK( page->getval(1) == (std::string{\"\\x02\\x00\\x00\\xFF\",4}) );\n    CHECK( page->getval(2) == (std::string{\"\\x03\\x00\\x00\\xFF\",4}) );\n\n    CHECK_FALSE( page->find(\"fail\") == (BasePage::result{REL_EQUAL,2}) );\n\n    CHECK( page->find(\"N\") == (BasePage::result{REL_GREATER,0}) );\n    CHECK( page->find(\"Nabcde\") == (BasePage::result{REL_EQUAL,0}) );\n    CHECK( page->find(\"Nbcdef\") == (BasePage::result{REL_EQUAL,1}) );\n    CHECK( page->find(\"Nbzzzz\") == (BasePage::result{REL_LESS,1}) );\n    CHECK( page->find(\"Ncdef\") == (BasePage::result{REL_EQUAL,2}) );\n    CHECK( page->find(\"Nzzzz\") == (BasePage::result{REL_LESS,2}) );\n}\n/* streamhelper unittest */\nTEST_CASE(\"test_streamhelper\")\n{\n    auto f = makehelper(std::make_shared<std::stringstream>(\"3456789a\"));\n\n    // read various chunks of the stream.\n    CHECK( f.getdata(3) == \"345\" );\n    CHECK( f.getdata(8) == \"6789a\" );\n    CHECK( f.getdata(8) == \"\" );\n    f.seekg(-1, std::ios_base::end);\n    CHECK( f.getdata(8) == \"a\" );\n    f.seekg(3);\n    CHECK( f.getdata(2) == \"67\" );\n    f.seekg(-2,std::ios_base::cur);\n    CHECK( f.getdata(2) == \"67\" );\n    f.seekg(2,std::ios_base::cur);\n    CHECK( f.getdata(2) == \"a\" );\n\n    f.seekg(0);\n    CHECK( f.get32le() == 0x36353433 );\n    CHECK( f.get32be() == 0x37383961 );\n\n    // seek to end of stream\n    f.seekg(8);\n\n    // read at EOF should return an empty string.\n    CHECK( f.getdata(1) == \"\" );\n\n    // seek beyond end of stream should throw an exception\n    CHECK_THROWS( f.seekg(9) );\n    CHECK_THROWS( f.getdata(1) );\n}\n/* unittest for EndianTools */\nTEST_CASE(\"test_EndianTools\")\n{\n    EndianTools et;\n\n    uint8_t b[64];\n    int i;\n    for (i=0 ; i<64 ; i++)\n        b[i] = 0xAA;\n\n    CHECK_THROWS( et.setle64(b, b+7, 0x12345678LL) );\n\n    et.setle64(b, b+8, 0x9abcdef12345678LL);\n\n    uint8_t little_endian_number[9] = { 0x78, 0x56, 0x34, 0x12, 0xef, 0xcd, 0xab, 0x09, 0xAA };\n    CHECK(std::equal(b, b+9, little_endian_number));\n\n    CHECK_THROWS( et.getle64(b, b+7) );\n    CHECK(et.getle64(b, b+8)==0x9abcdef12345678LL);\n\n    et.setbe64(b, b+8, 0x9abcdef12345678LL);\n\n    uint8_t big_endian_number[9] = { 0x09,0xab,0xcd,0xef,0x12,0x34,0x56,0x78,0xAA };\n    CHECK(std::equal(b, b+9, big_endian_number));\n\n    CHECK_THROWS( et.getbe64(b, b+7) );\n    CHECK(et.getbe64(b, b+8)==0x9abcdef12345678LL);\n}\n/* unittest for sectionstream */\nTEST_CASE(\"test_StreamSection\")\n{\n    auto f = makehelper(std::make_shared<sectionstream>(std::make_shared<std::stringstream>(\"0123456789abcdef\"), 3, 8));\n\n    CHECK( f.getdata(3) == \"345\" );   // should return what was asked for.\n    CHECK( f.getdata(8) == \"6789a\" ); // should return less than requested\n    CHECK( f.getdata(8) == \"\" );      // should return nothing\n    f.seekg(-1, std::ios_base::end);\n    CHECK( f.getdata(8) == \"a\" );\n    f.seekg(3);\n    CHECK( f.getdata(2) == \"67\" );\n    f.seekg(-2,std::ios_base::cur);\n    CHECK( f.getdata(2) == \"67\" );\n    f.seekg(2,std::ios_base::cur);\n    CHECK( f.getdata(2) == \"a\" );\n\n    f.seekg(8);\n    CHECK( f.getdata(1) == \"\" );\n    //CHECK_THROWS( f.seekg(9) );\n}\n\nTEST_CASE(\"test_NodeValues\")\n{\n    CHECK( NodeValues::getuint(\"\\x12\\x34\\x45\\x56\\x67\\x78\\x89\\x9a\") == 0x9a89786756453412 );\n    CHECK( NodeValues::getuint(\"\\x12\\x34\\x45\\x56\") == 0x56453412 );\n    CHECK( NodeValues::getuint(\"\\x12\\x34\") == 0x3412 );\n    CHECK( NodeValues::getuint(\"\\x12\") == 0x12 );\n}\n\nTEST_CASE(\"test_Packer\")\n{\n    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);\n\n    DwordVector nums;\n    idaunpack(val.begin(), val.end(), std::back_inserter(nums));\n\n    DwordVector wrong = { 0x00 };\n    CHECK( nums != wrong );\n    CHECK_FALSE( nums == wrong );\n\n    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 };\n\n    CHECK(nums == check);\n}\n\n\n"
  },
  {
    "path": "tests/unittestframework.h",
    "content": "/*\n * catch is available from: https://github.com/catchorg/Catch2\n * doctest is available from https://github.com/onqtam/doctest\n *\n *\n * Doctest is almost a drop-in replacement for Catch.\n * Though Catch has a few more features, and works without any restrictions,\n * doctest has much faster compilation times, our 44 unittests build\n * takes about 13 minutes to build with catch, or about 3.5 minutes when\n * using doctest.\n *\n */\n#if !defined(USE_CATCH) && !defined(USE_DOCTEST)\n#define USE_DOCTEST\n#endif\n\n#ifdef USE_CATCH\n#ifdef UNITTESTMAIN\n#define CATCH_CONFIG_MAIN \n#endif\n//#define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER\n#define CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS\n#if __has_include(<catch2/catch.hpp>)\n#include <catch2/catch.hpp>\n#elif __has_include(\"single_include/catch.hpp\")\n#include \"single_include/catch.hpp\"\n#elif __has_include(\"contrib/catch.hpp\")\n#include \"contrib/catch.hpp\"\n#else\n#error  \"Could not find catch.hpp\"\n#endif\n\n#define SKIPTEST  , \"[!hide]\"\n\n// doctest has suites, catch doesn't.\n#define TEST_SUITE(x)  namespace\n\n#elif defined(USE_DOCTEST)\n#ifdef UNITTESTMAIN\n#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN\n#endif\n#include <ostream>\n#if __has_include(<doctest/doctest.h>)\n#include <doctest/doctest.h>\n#elif __has_include(\"contrib/doctest.h\")\n#include \"contrib/doctest.h\"\n#elif __has_include(\"single_include/doctest.h\")\n#include \"single_include/doctest.h\"\n#else\n#error  \"Could not find doctest.h\"\n#endif\n#define SECTION(...) SUBCASE(__VA_ARGS__)\n#define SKIPTEST  * doctest::skip(true)\n\n#define CHECK_THAT(a, b) \n#else\n#error define either USE_CATCH or USE_DOCTEST\n#endif\n\n#define IN_UNITTEST 1\n"
  },
  {
    "path": "tests/unittests.cpp",
    "content": "#define UNITTESTMAIN\n#include \"unittestframework.h\"\n\n// including all here again, so we will catch linking errors.\n#include <idblib/idb3.h>\n\nTEST_CASE(\"main\") {\n    CHECK(true);\n}\n\n"
  },
  {
    "path": "tools/CMakeLists.txt",
    "content": "add_executable(idbtool idbtool.cpp)\ntarget_link_libraries(idbtool PRIVATE idasdk idblib cpputils)\nif (TARGET gmp)\n    target_link_libraries(idbtool PRIVATE gmp)\nendif()\n"
  },
  {
    "path": "tools/idbtool.cpp",
    "content": "/*\n * idbtool: a tool for viewing idb files without running ida.\n *\n * Author: Willem Hengeveld <itsme@xs4all.nl>\n *\n */\n#include <fstream>\n#include <iostream>\n#include <memory>\n#include <algorithm>\n#include <climits>\n#include <idblib/idb3.h>\n#include <cpputils/argparse.h>\n\n#ifdef HAVE_LIBGMP\n#include <gmpxx.h>\n#endif\n#include <cpputils/formatter.h>\n#include <cpputils/stringlibrary.h>\n\nint verbose = 0;\n\n#ifdef HAVE_LIBGMP\n/*\n *  decode license info from idb\n */\n\n// defines for making the intention of our mpz_import parameters clear to the reader.\n#define ORDER_LS_FIRST -1\n#define ORDER_MS_FIRST 1\n\n// function for converting a range of bytes to a gmp bignum object.\ninline mpz_class lstompz(const uint8_t *first, const uint8_t *last)\n{\n    mpz_class m;\n    int endian= 0;  // endianness does not matter for bytes\n    int nails= 0;   // all bits used\n    int order= ORDER_LS_FIRST;\n    mpz_import(m.get_mpz_t(), last-first, order, sizeof(uint8_t), endian, nails, first);\n\n    return m;\n}\n\n// function for converting a list of bytes to a gmp bignum object.\ninline mpz_class lstompz(const std::string& v)\n{\n    return lstompz((uint8_t*)&v[0], (uint8_t*)&v[0]+v.size());\n}\n\n// function for converting a gmp bignum object to a list of bytes.\ninline std::string mpztoms(int requiredbytes, const mpz_class& m)\n{\n    int endian= 0;  // endianness does not matter for bytes\n    int nails= 0;   // all bits used\n    int order= ORDER_MS_FIRST;\n\n    if (m<0)\n        print(\"PROBLEM: can't convert negative mpz to bytes\\n\");\n\n    size_t n= mpz_sizeinbase(m.get_mpz_t(), 256);\n    if (requiredbytes==0)\n        requiredbytes= n;\n    std::string v(requiredbytes, char(0));\n    mpz_export(&v[v.size()-n], &n, order, sizeof(uint8_t), endian, nails, m.get_mpz_t());\n    return v;\n}\n\n// decrypt the contents of the '$ original user' node.\nstd::string decryptuser(const std::string& encvector)\n{\n    mpz_class exp(0x13);\n    uint8_t modbytes[]= {\n0xED,0xFD,0x42,0x5C,0xF9,0x78,0x54,0x6E,0x89,0x11,0x22,0x58,0x84,0x43,0x6C,0x57, 0x14,0x05,0x25,0x65,0x0B,0xCF,0x6E,0xBF,0xE8,0x0E,0xDB,0xC5,0xFB,0x1D,0xE6,0x8F,\n0x4C,0x66,0xC2,0x9C,0xB2,0x2E,0xB6,0x68,0x78,0x8A,0xFC,0xB0,0xAB,0xBB,0x71,0x80, 0x44,0x58,0x4B,0x81,0x0F,0x89,0x70,0xCD,0xDF,0x22,0x73,0x85,0xF7,0x5D,0x5D,0xDD,\n0xD9,0x1D,0x4F,0x18,0x93,0x7A,0x08,0xAA,0x83,0xB2,0x8C,0x49,0xD1,0x2D,0xC9,0x2E, 0x75,0x05,0xBB,0x38,0x80,0x9E,0x91,0xBD,0x0F,0xBD,0x2F,0x2E,0x6A,0xB1,0xD2,0xE3,\n0x3C,0x0C,0x55,0xD5,0xBD,0xDD,0x47,0x8E,0xE8,0xBF,0x84,0x5F,0xCE,0xF3,0xC8,0x2B, 0x9D,0x29,0x29,0xEC,0xB7,0x1F,0x4D,0x1B,0x3D,0xB9,0x6E,0x3A,0x8E,0x7A,0xAF,0x93,\n    };\n    mpz_class mod= lstompz(modbytes, modbytes+sizeof(modbytes));\n    mpz_class val= lstompz(encvector);\n\n    mpz_class res;\n    mpz_powm(res.get_mpz_t(), val.get_mpz_t(), exp.get_mpz_t(), mod.get_mpz_t());\n\n    return mpztoms(sizeof(modbytes)-1, res);\n}\n#endif\n\n/*\n *  dump structs + unions\n */\nvoid dumpstructmember(const StructMember& mem)\n{\n    print(\"     %02x %02x %08x %02x: %-40s\", \n            mem.skip(), mem.size(), mem.flags(), mem.props(),\n            mem.name());\n\n    uint64_t enumid = mem.enumid();\n    if (enumid)\n        print(\" enum %08x\", enumid);\n\n    uint64_t structid = mem.structid();\n    if (structid)\n        print(\" struct %08x\", structid);\n\n    auto ptrinfo = mem.ptrinfo();\n    if (!ptrinfo.empty())\n        print(\" ptr %b\", ptrinfo);\n\n    auto type= mem.typeinfo();\n    if (!type.empty())\n        print(\" type %b\", type);\n\n    print(\"\\n\");\n    return;\n}\n\n\nvoid dumpstruct(const Struct& s)\n{\n    print(\"struct %s,   0x%x, 0x%x\\n\", s.name(), s.flags(), s.seqnr());\n    for (const auto& mem : s)\n        dumpstructmember(mem);\n}\n\n/*\n * dump bitfields\n */\nvoid dumpbfvalue(const BitfieldValue& val)\n{\n    print(\"   %16x %s\\n\", val.value(), val.name());\n}\nvoid dumpbfmask(const BitfieldMask& msk)\n{\n    print(\"    mask %x\", msk.mask());\n    auto name = msk.name();\n    if (!name.empty())\n        print(\" - %s\", name);\n    print(\"\\n\");\n\n    auto c = msk.first();\n    while (c.getkey() < msk.lastkey()) {\n        dumpbfvalue(msk.getvalue(c));\n        c.next();\n    }\n}\nvoid dumpbitfield(ID0File & id0, uint64_t bfnode)\n{\n    Bitfield e(id0, bfnode);\n    print(\"bitfield %s, 0x%x, 0x%x, 0x%x\\n\", e.name(), e.count(), e.representation(), e.flags());\n    auto c = e.first();\n    while (c.getkey() < e.lastkey()) {\n        dumpbfmask(e.getmask(c));\n        c.next();\n    }\n}\n\n/*\n * dump enums\n */\nvoid dumpenummember(const EnumMember& e)\n{\n    print(\"    %08x %s\\n\", e.value(), e.name());\n}\nvoid dumpenum(ID0File& id0, const Enum& e)\n{\n    if (e.flags()&1) {\n        dumpbitfield(id0, e.nodeid());\n        return;\n    }\n\n    print(\"enum %s, 0x%x, 0x%x, 0x%x\\n\", e.name(), e.count(), e.representation(), e.flags());\n    auto c = e.first();\n    while (c.getkey() < e.lastkey()) {\n        dumpenummember(e.getvalue(c));\n        c.next();\n    }\n\n}\n\n/*\n *  print list of structs / enums\n */\nvoid printidbstructs(ID0File& id0)\n{\n    auto list = List<Struct>(id0, id0.node(\"$ structs\"));\n\n    while (!list.eof())\n        try {\n            dumpstruct(list.next());\n        }\n        catch(const char*msg)\n        {\n            print(\"struct entry with error found\\n\");\n        }\n}\nvoid printidbenums(ID0File& id0)\n{\n    auto list = List<Enum>(id0, id0.node(\"$ enums\"));\n\n    while (!list.eof())\n        dumpenum(id0, list.next());\n}\n\n\n\n\nvoid printcomments(ID0File& id0)\n{\n// todo\n// tool which lists all comments found in the database\n\n/*\n -- nalt.hpp: NSUP_CMT = 0\n .{0X00001ddd 53 supvals 0} => \"normal comment\" 00\n\n -- nalt.hpp: NSUP_REPCMT = 1\n .{0X00001dd7 53 supvals 1} => \"repeatable comment\" 00\n\n -- lines.hpp: E_PREV = 1000\n .{0X00001ddd 53 supvals 1000} => \"anterior comment\" 00\n .{0X00001ddd 53 supvals 1001} => \"more\" 00\n .{0X00001ddd 53 supvals 1002} => \"and another anterior\" 00\n\n -- lines.hpp: E_NEXT = 2000\n .{0X00001ddd 53 supvals 2000} => \"posterior comment\" 00\n .{0X00001ddd 53 supvals 2001} => \"more\" 00\n .{0X00001ddd 53 supvals 2002} => \"and another posterior\" 00\n*/\n\n\n}\n\n\n\n\n/*\n .{0X00001dd9  name} => \"globallabel\" 00\n N:\"globallabel\" => 0x00001dd9\n\n .. func prop\n -- nalt.hpp: NSUP_LLABEL\n .{0X00001bb0 53 supvals 0x5000} => {82 27 0a:\"locallabel\"}\n*/\n\n\nvoid printnames(ID0File& id0, ID1File& id1, NAMFile& nam, bool listall)\n{\n    nam.enumerate([&](uint64_t ea){\n        uint64_t f= id1.GetFlags(ea);\n        std::string name= id0.getname(ea);\n        if (listall || !(f&0x8000))\n            print(\"%08x: [%08x] %s\\n\", ea, f, name);\n\n        // todo: filter out nullsub, jpt_XXX, thunks (j_...)\n    });\n}\n\n// print each address in the form: <label> + offset\nvoid printaddrs(ID0File& id0, ID1File& id1, NAMFile& nam, const std::vector<uint64_t>& addrs)\n{\n    for (uint64_t ea : addrs) {\n        auto seg0 = id1.SegStart(ea);\n        auto seg1 = id1.SegEnd(ea);\n\n        std::string segspec;\n        if (seg0!=BADADDR) {\n            if (seg0==ea)\n                segspec = stringformat(\"seg:%08x start\", seg0);\n            else if (seg1==ea)\n                segspec = stringformat(\"seg:%08x end\", seg0);\n            else\n                segspec = stringformat(\"seg:%08x+0x%x\", seg0, ea-seg0);\n        }\n        else {\n            segspec = \"not in a seg\";\n        }\n\n        std::string namespec;\n        uint64_t fea = nam.findname(ea);\n        if (fea == BADADDR) {\n            // no names in database\n            namespec = \"-\";\n        }\n        else {\n            std::string name= id0.getname(fea);\n            if (fea==ea) {\n                // found a name for this address\n\n                namespec = stringformat(\"%s\", name);\n            }\n            else if (fea < ea) {\n                // found name before this address\n                namespec = stringformat(\"%s+0x%x\", name, ea-fea);\n            }\n            else {\n                // found name after this address\n                namespec = stringformat(\"%s-0x%x\", name, fea-ea);\n            }\n        }\n        print(\"%08x: %-23s %s\\n\", ea, segspec, namespec);\n    }\n}\n\n/*\n * print all idc/python scripts\n */\nvoid dumpscript(const Script& scr)\n{\n    print(\"======= %s %s =======\\n%s\\n\", scr.language(), scr.name(), scr.body());\n}\n\nvoid printidbscripts(ID0File& id0)\n{\n    auto list = List<Script>(id0, id0.node(\"$ scriptsnippets\"));\n\n    while (!list.eof())\n        dumpscript(list.next());\n}\n\n/*\n * print license info\n */\nstd::string timestring(uint32_t t)\n{\n    if (t==0)\n        return \"                \";\n    time_t date = t;\n    struct tm tm= *localtime(&date);\n    return stringformat(\"%04d-%02d-%02d %02d:%02d\", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min);\n}\n\nvoid dumplicense(const char *tag, const std::string& user)\n{\n    if (user.size()<127)\n        return;\n\n    auto et = EndianTools();\n\n    // if non zero data at offset 106, assume this is an invalid license blob.\n    if (et.getle32(&user[106], &user[110]))\n        return;\n\n    uint16_t licver= et.getle16(&user[2], &user[0]+user.size());\n    if (licver==0) {\n        // before v5.3\n        auto ts = et.getle32(&user[4], &user[8]);\n        if (ts) {\n            std::string licensee= (char*)&user[20];\n            auto fl = et.getle32(&user[16], &user[20]);\n            print(\"%s %s [%08x]  %s\\n\", tag, timestring(ts), fl, licensee);\n        }\n        else {\n            auto ts = et.getle32(&user[0x17], &user[0x1b]);\n            auto fl = et.getle32(&user[0x23], &user[0x27]);\n            std::string licensee= (char*)&user[0x27];\n            print(\"%s %s [%08x]  %s\\n\", tag, timestring(ts), fl, licensee);\n\n        }\n    }\n    else {\n        std::string licensee= (char*)&user[34];\n        print(\"%sv%04d %s ... %s   %02x-%02x%02x-%02x%02x-%02x  %s\\n\",\n                tag, licver,\n                timestring(et.getle32(&user[16], &user[0]+user.size())),\n                timestring(et.getle32(&user[16+8], &user[0]+user.size())),\n                (int)(uint8_t)user[28], (int)(uint8_t)user[29], (int)(uint8_t)user[30], (int)(uint8_t)user[31], (int)(uint8_t)user[32], (int)(uint8_t)user[33],\n                licensee);\n    }\n}\n\n/*\n * print idb info from the rootnode\n */\nvoid printidbinfo(ID0File& id0)\n{\n    uint64_t loadernode= id0.node(\"$ loader name\");\n    print(\"loader: %s  %s\\n\", id0.getstr(loadernode, 'S', 0), id0.getstr(loadernode, 'S', 1));\n\n    uint64_t rootnode= id0.node(\"Root Node\");\n    std::string params= id0.getdata(rootnode, 'S', 0x41b994);\n    std::string cpu{&params[5], &params[5+8]};\n    auto nulpos = cpu.find(char(0));\n    if (nulpos != cpu.npos)\n        cpu.resize(nulpos);\n    print(\"cpu: %-8s,  idaversion=%04d: %s\\n\", cpu,\n            id0.getuint(rootnode, 'A', -1), id0.getstr(rootnode, 'S', 1303));\n    print(\"nopens=%d, ctime=%s, crc=%08x, binary md5=%b\\n\", \n            id0.getuint(rootnode, 'A', -4),\n            timestring(id0.getuint(rootnode, 'A', -2)),\n            id0.getuint(rootnode, 'A', -5),\n            id0.getdata(rootnode, 'S', 1302));\n\n#ifdef HAVE_LIBGMP\n    std::string originaluser= id0.getdata(id0.node(\"$ original user\"), 'S', 0);\n//  this is privkey encrypted ( decrypts to user1 contents )\n//  note: number is stored LSB first\n    std::string user0= decryptuser(originaluser);\n\n    std::string user1= id0.getdata(id0.node(\"$ user1\"), 'S', 0);\n    if (verbose)\n        print(\"\\n%b\\n%b\\n\", user0, user1);\n\n    dumplicense(\"orig: \", user0);\n    dumplicense(\"curr: \", user1);\n#endif\n}\n\n/*\n * print all nodes in sequential order\n */\nvoid dumpnodes(ID0File& id0, bool ascending, int limit)\n{\n    auto c = ascending ? id0.find(REL_GREATER_EQUAL, \"\")\n                       : id0.find(REL_LESS_EQUAL, \"\\xFF\\xFF\\xFF\\xFF\");\n    while (!c.eof() && limit!=0)\n    {\n        print(\"%-b = %-b\\n\", c.getkey(), c.getval());\n        if (ascending)\n            c.next();\n        else\n            c.prev();\n\n        if (limit>0)\n            limit--;\n    }\n}\n\n/*\n * perform simple queries on the .idb database\n */\nenum {FL_EQ=1, FL_GT=2, FL_LT=4};\n\n// translate ==, =>, <=, <. >  tokens to REL_xxx constants, passed to `id0.find`\nrelation_t xlat_relation(int flags)\n{\n    if (flags==(FL_EQ|FL_GT)) return REL_GREATER_EQUAL;\n    if (flags==(FL_GT)) return REL_GREATER;\n    if (flags==(FL_EQ|FL_LT)) return REL_LESS_EQUAL;\n    if (flags==(FL_LT)) return REL_LESS;\n    if (flags==FL_EQ) return REL_EQUAL;\n    if (flags==0) return REL_EQUAL;\n    throw \"unknown relation\";\n}\n\n// parses the part of the key after the relation into a node key.\n// optionally looksup a node by name.\nstd::string createkey(ID0File& id0, const char *key, const char *keyend)\n{\n    // .<nodeid>;tag;idx   -> '.'<nodeid><tag><idx>\n    // ?<nodename>         -> 'N'<name>\n    // <nodename>;tag;idx  -> lookup name -> '.'<nodeid><tag><idx>\n    if (key==keyend)\n        return \"\";\n    if (*key=='?')\n        return id0.makename(std::string(key+1, keyend));\n    auto i0 = std::find(key, keyend, ';');\n    uint64_t nodeid;\n    if (*key=='.' || *key=='#') {\n        // a numeric node id.\n        auto r = parseunsigned(key+1, i0, 0);\n        if (r.second!=i0)\n            throw \"invalid keyspec\";\n        nodeid = r.first;\n        if (*key=='#')\n            nodeid += id0.nodebase();\n    }\n    else {\n        // lookup the node by name\n        nodeid = id0.node(std::string(key, i0));\n    }\n    if (i0==keyend)\n        return id0.makekey(nodeid);\n    if (i0+1==keyend)\n        throw \"invalid keyspec\";\n    char tag = i0[1];\n    auto i1 = std::find(i0+1, keyend, ';');\n    if (i1==keyend)\n        return id0.makekey(nodeid, tag);\n    if (i1+1==keyend)\n        throw \"invalid keyspec\";\n    if (tag=='H')\n        return id0.makekey(nodeid, tag, std::string(i1+1, keyend));\n    auto r = parseunsigned(i1+1, keyend, 0);\n    if (r.second!=keyend)\n        throw \"invalid keyspec\";\n    uint64_t ix = r.first;\n\n    return id0.makekey(nodeid, tag, ix);\n}\n\n// parse and execute the query.\nvoid queryidb(ID0File& id0, const std::string& query, bool ascending, int limit)\n{\n    if (query.size()<=2)\n        return;\n    int flags = 0;\n    const char *key = nullptr;\n    switch(query[0])\n    {\n        case '=': flags |= FL_EQ; break;\n        case '>': flags |= FL_GT; break;\n        case '<': flags |= FL_LT; break;\n        default: key = &query[0];\n    }\n    switch(query[1])\n    {\n        case '=': flags |= FL_EQ; break;\n        case '>': flags |= FL_GT; break;\n        case '<': flags |= FL_LT; break;\n        default: if (!key) key = &query[1];\n    }\n    if (!key) key = &query[2];\n    if (flags==0) flags = FL_EQ;\n\n    auto c = id0.find(xlat_relation(flags), createkey(id0, key, &query[0]+query.size()));\n\n    while (!c.eof() && limit!=0)\n    {\n        print(\"%-b = %-b\\n\", c.getkey(), c.getval());\n        if (flags == FL_EQ)\n            break;\n        if (ascending)\n            c.next();\n        else\n            c.prev();\n        if (limit>0)\n            limit--;\n    }\n}\n\nvoid usage()\n{\n    printf(\"idbtool OPTIONS  <files> [-- ADDRLIST]\\n\");\n    printf(\"    -s | --scripts    print all scripts\\n\"); //  -c | --comments   print all comments\n    printf(\"    -t | --structs    print all structs          -i | --info       print database info\\n\");\n    printf(\"    -e | --enums      print all enums            -d | --id0        low level db dump\\n\");\n    printf(\"    -n | --names      print generated names      -inc | --inc      dump all records in ascending order\\n\");\n    printf(\"    -a                print all names            -dec | --dec      dump all records in descending order\\n\");\n    printf(\"when the ADDRLIST is specified, the addresses in the list are printed as 'name+offset'\\n\");\n\n    printf(\"    -q | --query  QUERY                          -m LIMIT          number of records printed\\n\");\n    printf(\"example queries:\\n\");\n    printf(\"  * '?Root Node' -> prints the Name node pointing to the root\\n\");\n    printf(\"  * '>Root Node' -> prints the first 10 records after the root node\\n\");\n    printf(\"  * '<Root Node' -> prints the 10 records startng with the recordsbefore the rootnode.\\n\");\n    printf(\"  * '.0xff000001;N' -> prints rootnode name entry.\\n\");\n    printf(\"  * '#1;N' -> prints rootnode name entry.\\n\");\n\n}\n\n#define LISTALL_NAMES     1\n#define PRINT_NAMES       2\n#define PRINT_SCRIPTS     4\n#define PRINT_STRUCTS     8\n#define PRINT_COMMENTS   16\n#define PRINT_ENUMS      32\n#define PRINT_INFO       64\n#define DUMP_ASCENDING  128\n#define DUMP_DESCENDING 256\n#define DUMP_DATABASE   512\n#define QUERY_IDB      1024\n\n// perform the options specified on the commandline on a specific idb file.\nvoid processidb(const std::string& fn, int flags, const std::string& query, const std::vector<uint64_t>& addrs, int limit)\n{\n    IDBFile idb(std::make_shared<std::ifstream>(fn));\n    ID0File id0(idb, idb.getsection(ID0File::INDEX));\n    ID1File id1(idb, idb.getsection(ID1File::INDEX));\n    NAMFile nam(idb, idb.getsection(NAMFile::INDEX));\n\n    if (flags&PRINT_INFO)\n        printidbinfo(id0);\n    if (flags&PRINT_SCRIPTS)\n        printidbscripts(id0);\n    if (flags&PRINT_COMMENTS)\n        printcomments(id0);\n    if (flags&PRINT_STRUCTS)\n        printidbstructs(id0);\n    if (flags&PRINT_ENUMS)\n        printidbenums(id0);\n    if (flags&PRINT_NAMES)\n        printnames(id0, id1, nam, flags&LISTALL_NAMES);\n\n    if (!addrs.empty())\n        printaddrs(id0, id1, nam, addrs);\n\n    if (flags&QUERY_IDB)\n        queryidb(id0, query, !(flags&DUMP_DESCENDING), limit);\n    else if (flags&(DUMP_ASCENDING|DUMP_DESCENDING))\n        dumpnodes(id0, flags&DUMP_ASCENDING, limit);\n\n    if (flags&DUMP_DATABASE) {\n        id1.dump_info();\n        id0.dump();\n    }\n}\n\n\nint main(int argc, char**argv)\n{\n    bool addingidbs=true;\n    std::vector<std::string> idbnames;\n    std::vector<uint64_t> addrs;\n    std::string query;\n    int limit = -1;\n\n    int flags= 0;\n\n    for (auto& arg : ArgParser(argc, argv))\n       switch (arg.option())\n       {\n            case 'v': verbose = arg.count(); break;\n            case '-': if (arg.match(\"--verbose\")) verbose = 1;\n                      else if (arg.match(\"--names\"))   flags |= PRINT_NAMES;\n                      else if (arg.match(\"--scripts\")) flags |= PRINT_SCRIPTS;\n                      else if (arg.match(\"--structs\")) flags |= PRINT_STRUCTS;\n                      else if (arg.match(\"--enums\"))   flags |= PRINT_ENUMS;\n                      //else if (arg.match(\"--comments\"))flags |= PRINT_COMMENTS;\n                      else if (arg.match(\"--id0\"))     flags |= DUMP_DATABASE;\n                      else if (arg.match(\"--info\"))    flags |= PRINT_INFO;\n                      else if (arg.match(\"--limit\"))   limit = arg.getint();\n                      else if (arg.match(\"--query\")) {\n                          flags |= QUERY_IDB;\n                          query = arg.getstr();\n                      }\n                      else if (arg.match(\"--inc\")) flags |= DUMP_ASCENDING;\n                      else if (arg.match(\"--dec\")) flags |= DUMP_DESCENDING;\n                      else if (arg.optionterminator()) {\n                          // '--' separates the db list from the addr list.\n                          addingidbs= false;\n                      }\n                      break;\n            case 'a': flags |= LISTALL_NAMES; break;\n            case 'n': flags |= PRINT_NAMES; break;\n            case 's': flags |= PRINT_SCRIPTS; break;\n            case 'u': flags |= PRINT_STRUCTS; break;\n            //case 'c': flags |= PRINT_COMMENTS; break;\n            case 'e': flags |= PRINT_ENUMS; break;\n            case 'd': if (arg.match(\"-dec\"))\n                          flags |= DUMP_DESCENDING;\n                      else\n                          flags |= DUMP_DATABASE; \n                      break;\n            case 'i': if (arg.match(\"-inc\"))\n                          flags |= DUMP_ASCENDING;\n                      else if (arg.match(\"-id0\"))\n                          flags |= DUMP_DATABASE; \n                      else\n                          flags |= PRINT_INFO;\n                      break;\n            case 'q': flags |= QUERY_IDB;\n                      query = arg.getstr();\n                      break;\n            case 'm': limit = arg.getint();\n                      break;\n            default:\n                      usage();\n                      return 1;\n           case -1:\n                if (addingidbs)\n                    idbnames.push_back(arg.getstr());\n                else\n                    addrs.push_back(arg.getint());\n\n    }\n    if (idbnames.empty()) {\n        usage();\n        return 1;\n    }\n\n    for (auto const&arg : idbnames)\n    {\n        if (idbnames.size()>1)\n            print(\"==> %s <==\\n\", arg);\n        try {\n        processidb(arg, flags, query, addrs, limit);\n        }\n        catch(const std::exception & e) {\n            print(\"EXCEPTION: %s\\n\", e.what());\n        }\n        catch(const char * msg) {\n            print(\"ERROR: %s\\n\", msg);\n        }\n        if (idbnames.size()>1)\n            print(\"\\n\");\n    }\n\n    return 0;\n}\n\n/*  -- ida type id's\noff_210FA0      dd offset aVoid         ; \"void\"              \n                dd offset a__int8       ; \"__int8\"            0x01\n                dd offset a__int16_0    ; \"__int16\"           0x02\n                dd offset a__int32_0    ; \"__int32\"           0x03\n                dd offset a__int64_0    ; \"__int64\"           0x04\n                dd offset a__int128_0   ; \"__int128\"          0x05\n                dd offset aChar         ; \"char\"              0x06\n                dd offset aInt          ; \"int\"               0x07\n                dd offset aBool         ; \"bool\"              0x08\n                dd offset a_bool1       ; \"_BOOL1\"            0x09\n                dd offset a_bool2       ; \"_BOOL2\"            0x0a\n                dd offset a_bool4       ; \"_BOOL4\"            0x0b\n                dd offset aFloat        ; \"float\"             0x0c\n                dd offset aDouble       ; \"double\"            0x0d\n                dd offset aLongDouble   ; \"long double\"       0x0e\n                dd offset aShortFloat   ; \"short float\"       0x0f\n                dd offset a__seg        ; \"__seg\"             0x10\n                dd offset a_unknown     ; \"_UNKNOWN\"          0x11\n                dd offset a_byte        ; \"_BYTE\"             0x12\n                dd offset a_word        ; \"_WORD\"             0x13\n                dd offset a_dword       ; \"_DWORD\"            0x14\n                dd offset a_qword       ; \"_QWORD\"            0x15\n                dd offset a_oword       ; \"_OWORD\"            0x16\n                dd offset a_tbyte       ; \"_TBYTE\"            0x17\n                dd offset aSigned_0     ; \"signed \"           0x18\n                dd offset aUnsigned     ; \"unsigned \"         0x19\n                dd offset a__cdecl      ; \"__cdecl\"           0x1a\n                dd offset a__stdcall    ; \"__stdcall\"         0x1b\n                dd offset a__pascal     ; \"__pascal\"          0x1c\n                dd offset a__fastcall   ; \"__fastcall\"        0x1d\n                dd offset a__thiscall   ; \"__thiscall\"        0x1e\n                dd offset asc_1C1708    ; \"\"                  0x1f\n                dd offset a__userpurge  ; \"__userpurge\"       0x20\n                dd offset a__usercall   ; \"__usercall\"        0x21\n                dd offset a__int8_0     ; \": __int8 \"         0x22\n                dd offset a__int16      ; \": __int16 \"        0x23\n                dd offset a__int32      ; \": __int32 \"        0x24\n                dd offset a__int64      ; \": __int64 \"        0x25\n                dd offset a__int128     ; \": __int128 \"       0x26\n                dd offset a__int256     ; \": __int256 \"       0x27\n                dd offset a__int512     ; \": __int512 \"       0x28\n\n */\n\n"
  }
]