sdbtool 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sdbtool-0.3.0/.github/workflows/python-test.yml +26 -0
- sdbtool-0.3.0/.gitignore +11 -0
- sdbtool-0.3.0/LICENSE.txt +21 -0
- sdbtool-0.3.0/PKG-INFO +73 -0
- sdbtool-0.3.0/README.md +51 -0
- sdbtool-0.3.0/pyproject.toml +49 -0
- sdbtool-0.3.0/sdbtool/__init__.py +131 -0
- sdbtool-0.3.0/sdbtool/__main__.py +4 -0
- sdbtool-0.3.0/sdbtool/apphelp.py +174 -0
- sdbtool-0.3.0/sdbtool/cli.py +14 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "main" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: windows-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install the latest version of uv and set the python version
|
|
21
|
+
uses: astral-sh/setup-uv@v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
- name: Test with python ${{ matrix.python-version }}
|
|
26
|
+
run: uv run --frozen pytest
|
sdbtool-0.3.0/.gitignore
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mark Jansen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sdbtool-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sdbtool
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Dump SDB file to xml using apphelp.dll
|
|
5
|
+
Project-URL: Homepage, https://pypi.org/project/sdbtool/
|
|
6
|
+
Project-URL: Changelog, https://github.com/learn-more/sdbtool/releases
|
|
7
|
+
Project-URL: Issues, https://github.com/learn-more/sdbtool/issues
|
|
8
|
+
Project-URL: CI, https://github.com/learn-more/sdbtool/actions
|
|
9
|
+
Project-URL: Repository, https://github.com/learn-more/sdbtool
|
|
10
|
+
Author-email: Mark Jansen <mark.jansen@reactos.org>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE.txt
|
|
13
|
+
Keywords: appcompat,apphelp,sdb,sdb2xml,sdbtool,shim,shimeng
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: click>=8.2.1
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# sdbtool
|
|
24
|
+
|
|
25
|
+
A tool for converting Microsoft Application Compatibility Database (SDB) files to XML format.
|
|
26
|
+
|
|
27
|
+
## Table of Contents
|
|
28
|
+
|
|
29
|
+
1. [Features](#features)
|
|
30
|
+
1. [Getting Started](#getting-started)
|
|
31
|
+
1. [Contributing](#contributing)
|
|
32
|
+
1. [License](#license)
|
|
33
|
+
|
|
34
|
+
## Features<a id="features"></a>
|
|
35
|
+
|
|
36
|
+
- Parses SDB files used by Windows for application compatibility.
|
|
37
|
+
- Converts SDB data into readable XML.
|
|
38
|
+
- Useful for analysis, migration, or documentation.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Getting Started<a id="getting-started"></a>
|
|
42
|
+
|
|
43
|
+
### Installation
|
|
44
|
+
|
|
45
|
+
Sdbtool is available as [`sdbtool`](https://pypi.org/project/sdbtool/) on PyPI.
|
|
46
|
+
|
|
47
|
+
Invoke sdbtool directly with [`uvx`](https://docs.astral.sh/uv/):
|
|
48
|
+
|
|
49
|
+
```shell
|
|
50
|
+
uvx sdbtool your.sdb # Convert the file 'your.sdb' to xml, and print it to the console
|
|
51
|
+
uvx sdbtool your.sdb --output your.xml # Convert the file 'your.sdb' to xml, and write it to 'your.xml'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or install sdbtool with `uv` (recommended), `pip`, or `pipx`:
|
|
55
|
+
|
|
56
|
+
```shell
|
|
57
|
+
# With uv.
|
|
58
|
+
uv tool install sdbtool@latest # Install sdbtool globally.
|
|
59
|
+
|
|
60
|
+
# With pip.
|
|
61
|
+
pip install sdbtool
|
|
62
|
+
|
|
63
|
+
# With pipx.
|
|
64
|
+
pipx install sdbtool
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Contributing<a id="contributing"></a>
|
|
68
|
+
|
|
69
|
+
Contributions are welcome! Please open issues or submit pull requests.
|
|
70
|
+
|
|
71
|
+
## License<a id="license"></a>
|
|
72
|
+
|
|
73
|
+
This project is licensed under the MIT License.
|
sdbtool-0.3.0/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# sdbtool
|
|
2
|
+
|
|
3
|
+
A tool for converting Microsoft Application Compatibility Database (SDB) files to XML format.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Features](#features)
|
|
8
|
+
1. [Getting Started](#getting-started)
|
|
9
|
+
1. [Contributing](#contributing)
|
|
10
|
+
1. [License](#license)
|
|
11
|
+
|
|
12
|
+
## Features<a id="features"></a>
|
|
13
|
+
|
|
14
|
+
- Parses SDB files used by Windows for application compatibility.
|
|
15
|
+
- Converts SDB data into readable XML.
|
|
16
|
+
- Useful for analysis, migration, or documentation.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Getting Started<a id="getting-started"></a>
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
Sdbtool is available as [`sdbtool`](https://pypi.org/project/sdbtool/) on PyPI.
|
|
24
|
+
|
|
25
|
+
Invoke sdbtool directly with [`uvx`](https://docs.astral.sh/uv/):
|
|
26
|
+
|
|
27
|
+
```shell
|
|
28
|
+
uvx sdbtool your.sdb # Convert the file 'your.sdb' to xml, and print it to the console
|
|
29
|
+
uvx sdbtool your.sdb --output your.xml # Convert the file 'your.sdb' to xml, and write it to 'your.xml'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or install sdbtool with `uv` (recommended), `pip`, or `pipx`:
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
# With uv.
|
|
36
|
+
uv tool install sdbtool@latest # Install sdbtool globally.
|
|
37
|
+
|
|
38
|
+
# With pip.
|
|
39
|
+
pip install sdbtool
|
|
40
|
+
|
|
41
|
+
# With pipx.
|
|
42
|
+
pipx install sdbtool
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Contributing<a id="contributing"></a>
|
|
46
|
+
|
|
47
|
+
Contributions are welcome! Please open issues or submit pull requests.
|
|
48
|
+
|
|
49
|
+
## License<a id="license"></a>
|
|
50
|
+
|
|
51
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sdbtool"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "Dump SDB file to xml using apphelp.dll"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Mark Jansen", email = "mark.jansen@reactos.org" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"click>=8.2.1",
|
|
13
|
+
]
|
|
14
|
+
keywords = ["sdb", "sdbtool", "sdb2xml", "shim", "apphelp", "appcompat", "shimeng"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Operating System :: Microsoft :: Windows",
|
|
20
|
+
"Topic :: Utilities",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://pypi.org/project/sdbtool/"
|
|
25
|
+
Changelog = "https://github.com/learn-more/sdbtool/releases"
|
|
26
|
+
Issues = "https://github.com/learn-more/sdbtool/issues"
|
|
27
|
+
CI = "https://github.com/learn-more/sdbtool/actions"
|
|
28
|
+
Repository = "https://github.com/learn-more/sdbtool"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
sdbtool = "sdbtool.cli:cli"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["hatchling"]
|
|
35
|
+
build-backend = "hatchling.build"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.sdist]
|
|
38
|
+
exclude = [
|
|
39
|
+
".vscode",
|
|
40
|
+
".python-version",
|
|
41
|
+
"uv.lock",
|
|
42
|
+
"tests"
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[dependency-groups]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest>=8.4.1",
|
|
48
|
+
]
|
|
49
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'''
|
|
2
|
+
PROJECT: sdbtool
|
|
3
|
+
LICENSE: MIT (https://spdx.org/licenses/MIT)
|
|
4
|
+
PURPOSE: Entrypoint of the sdbtool tool, which converts SDB files to XML format.
|
|
5
|
+
COPYRIGHT: Copyright 2025 Mark Jansen <mark.jansen@reactos.org>
|
|
6
|
+
'''
|
|
7
|
+
import sdbtool.apphelp as apphelp
|
|
8
|
+
from base64 import b64encode
|
|
9
|
+
from xml.sax.saxutils import escape
|
|
10
|
+
|
|
11
|
+
INDENT_DEPTH = 2
|
|
12
|
+
|
|
13
|
+
def tagtype_to_xmltype(tag_type: int) -> str|None:
|
|
14
|
+
switch = {
|
|
15
|
+
apphelp.TAG_TYPE_BYTE: "xs:byte",
|
|
16
|
+
apphelp.TAG_TYPE_WORD: "xs:unsignedShort",
|
|
17
|
+
apphelp.TAG_TYPE_DWORD: "xs:unsignedInt",
|
|
18
|
+
apphelp.TAG_TYPE_QWORD: "xs:unsignedLong",
|
|
19
|
+
apphelp.TAG_TYPE_STRINGREF: "xs:string",
|
|
20
|
+
apphelp.TAG_TYPE_STRING: "xs:string",
|
|
21
|
+
apphelp.TAG_TYPE_BINARY: "xs:base64Binary",
|
|
22
|
+
}
|
|
23
|
+
return switch.get(tag_type, None)
|
|
24
|
+
|
|
25
|
+
class Xml:
|
|
26
|
+
def __init__(self, stream, name, indent, attrib=None, xmltag=False):
|
|
27
|
+
self._stream = stream
|
|
28
|
+
self.name = name
|
|
29
|
+
self.indent = indent
|
|
30
|
+
self.attrib = attrib or {}
|
|
31
|
+
self._has_children = False
|
|
32
|
+
self._xmltag = xmltag
|
|
33
|
+
self._delay = None
|
|
34
|
+
self._closed = False
|
|
35
|
+
|
|
36
|
+
def node(self, name, attrib=None):
|
|
37
|
+
self._has_children = True
|
|
38
|
+
return Xml(self._stream, name, self.indent + 1, attrib)
|
|
39
|
+
|
|
40
|
+
def flush(self):
|
|
41
|
+
if self._delay:
|
|
42
|
+
self._stream.write(self._delay)
|
|
43
|
+
self._delay = None
|
|
44
|
+
|
|
45
|
+
def write(self, text):
|
|
46
|
+
self.flush()
|
|
47
|
+
self._stream.write(text)
|
|
48
|
+
|
|
49
|
+
def close(self):
|
|
50
|
+
assert self._delay is not None, "Xml.close() called when tag has content"
|
|
51
|
+
self._stream.write(" />")
|
|
52
|
+
self._delay = None
|
|
53
|
+
self._closed = True
|
|
54
|
+
|
|
55
|
+
def __enter__(self):
|
|
56
|
+
if self._xmltag:
|
|
57
|
+
self.write('<?xml version="1.0" encoding="utf-8" standalone="yes"?>')
|
|
58
|
+
self._xmltag = False
|
|
59
|
+
self.write("\n")
|
|
60
|
+
if self.indent:
|
|
61
|
+
self.write(" " * (self.indent*INDENT_DEPTH))
|
|
62
|
+
self.write(f"<{self.name}")
|
|
63
|
+
for key, value in self.attrib.items():
|
|
64
|
+
self.write(f' {key}="{value}"')
|
|
65
|
+
|
|
66
|
+
self._delay = ">"
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
70
|
+
if self._closed:
|
|
71
|
+
return
|
|
72
|
+
if self._has_children:
|
|
73
|
+
self.write("\n")
|
|
74
|
+
if self.indent:
|
|
75
|
+
self.write(" " * (self.indent*INDENT_DEPTH))
|
|
76
|
+
self.write(f"</{self.name}>")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def dump_tag(node, tag):
|
|
80
|
+
if tag.type == apphelp.TAG_TYPE_LIST:
|
|
81
|
+
node.flush()
|
|
82
|
+
for childtag in tag.tags():
|
|
83
|
+
attrs = {}
|
|
84
|
+
if childtag.type != apphelp.TAG_TYPE_LIST and childtag.type != apphelp.TAG_TYPE_NULL:
|
|
85
|
+
typename = tagtype_to_xmltype(childtag.type)
|
|
86
|
+
assert typename is not None, f"Unknown tag type: {childtag.tag}"
|
|
87
|
+
attrs["type"] = typename
|
|
88
|
+
with node.node(childtag.name, attrs) as childnode:
|
|
89
|
+
dump_tag(childnode, childtag)
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
assert next(tag.tags(), None) is None, f"Tag {tag.name} is not a list but has children"
|
|
93
|
+
|
|
94
|
+
if tag.type == apphelp.TAG_TYPE_NULL:
|
|
95
|
+
node.close()
|
|
96
|
+
elif tag.type == apphelp.TAG_TYPE_BYTE:
|
|
97
|
+
node.write("<!-- UNHANDLED BYTE TAG, please report this at https://github.com/learn-more/sdbtool -->")
|
|
98
|
+
elif tag.type == apphelp.TAG_TYPE_WORD:
|
|
99
|
+
val = tag.as_word()
|
|
100
|
+
node.write(f"{val}")
|
|
101
|
+
if node.name in ("INDEX_TAG", "INDEX_KEY"):
|
|
102
|
+
node.write(f"<!-- {apphelp.tag_to_string(val)} -->")
|
|
103
|
+
elif tag.type == apphelp.TAG_TYPE_DWORD:
|
|
104
|
+
node.write(f"{tag.as_dword()}")
|
|
105
|
+
elif tag.type == apphelp.TAG_TYPE_QWORD:
|
|
106
|
+
node.write(f"{tag.as_qword()}")
|
|
107
|
+
elif tag.type in (apphelp.TAG_TYPE_STRINGREF, apphelp.TAG_TYPE_STRING):
|
|
108
|
+
val = escape(tag.as_string())
|
|
109
|
+
node.write(f"{val}")
|
|
110
|
+
elif tag.type == apphelp.TAG_TYPE_BINARY:
|
|
111
|
+
data = tag.as_bytes()
|
|
112
|
+
if not data:
|
|
113
|
+
node.close()
|
|
114
|
+
else:
|
|
115
|
+
base64_data = b64encode(data).decode('utf-8')
|
|
116
|
+
node.write(base64_data)
|
|
117
|
+
else:
|
|
118
|
+
raise ValueError(f"Unknown tag type: {tag.type} for tag {tag.name}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def convert(input_file: str, output_stream):
|
|
122
|
+
with apphelp.SdbDatabase(input_file, apphelp.PathType.DOS_PATH) as db:
|
|
123
|
+
if not db:
|
|
124
|
+
print(f"Failed to open database at '{input_file}'")
|
|
125
|
+
return
|
|
126
|
+
attrs = {
|
|
127
|
+
"xmlns:xs": "http://www.w3.org/2001/XMLSchema",
|
|
128
|
+
"path": input_file,
|
|
129
|
+
}
|
|
130
|
+
with Xml(output_stream, "SDB", 0, attrs, xmltag=True) as node:
|
|
131
|
+
dump_tag(node, db.root())
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'''
|
|
2
|
+
PROJECT: sdbtool
|
|
3
|
+
LICENSE: MIT (https://spdx.org/licenses/MIT)
|
|
4
|
+
PURPOSE: interface to the AppHelp API for reading SDB files.
|
|
5
|
+
COPYRIGHT: Copyright 2025 Mark Jansen <mark.jansen@reactos.org>
|
|
6
|
+
'''
|
|
7
|
+
from ctypes import windll, c_void_p, c_uint16, c_uint32, c_wchar_p, POINTER, c_ubyte, c_uint64
|
|
8
|
+
from enum import IntEnum
|
|
9
|
+
|
|
10
|
+
class PathType(IntEnum):
|
|
11
|
+
DOS_PATH = 0
|
|
12
|
+
NT_PATH = 1
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
TAGID_NULL = 0x0
|
|
16
|
+
TAGID_ROOT = 0x0
|
|
17
|
+
_TAGID_ROOT = 12
|
|
18
|
+
|
|
19
|
+
TAG_TYPE_MASK = 0xF000
|
|
20
|
+
|
|
21
|
+
TAG_TYPE_NULL = 0x1000
|
|
22
|
+
TAG_TYPE_BYTE = 0x2000
|
|
23
|
+
TAG_TYPE_WORD = 0x3000
|
|
24
|
+
TAG_TYPE_DWORD = 0x4000
|
|
25
|
+
TAG_TYPE_QWORD = 0x5000
|
|
26
|
+
TAG_TYPE_STRINGREF = 0x6000
|
|
27
|
+
TAG_TYPE_LIST = 0x7000
|
|
28
|
+
TAG_TYPE_STRING = 0x8000
|
|
29
|
+
TAG_TYPE_BINARY = 0x9000
|
|
30
|
+
TAG_NULL = 0x0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
APPHELP = windll.apphelp
|
|
34
|
+
|
|
35
|
+
# PDB WINAPI SdbOpenDatabase(LPCWSTR path, PATH_TYPE type);
|
|
36
|
+
APPHELP.SdbOpenDatabase.argtypes = [c_wchar_p, c_uint32]
|
|
37
|
+
APPHELP.SdbOpenDatabase.restype = c_void_p
|
|
38
|
+
|
|
39
|
+
# void WINAPI SdbCloseDatabase(PDB);
|
|
40
|
+
APPHELP.SdbCloseDatabase.argtypes = [c_void_p]
|
|
41
|
+
|
|
42
|
+
# TAGID WINAPI SdbGetFirstChild(PDB pdb, TAGID parent);
|
|
43
|
+
APPHELP.SdbGetFirstChild.argtypes = [c_void_p, c_uint32]
|
|
44
|
+
APPHELP.SdbGetFirstChild.restype = c_uint32
|
|
45
|
+
|
|
46
|
+
# TAGID WINAPI SdbGetNextChild(PDB pdb, TAGID parent, TAGID prev_child);
|
|
47
|
+
APPHELP.SdbGetNextChild.argtypes = [c_void_p, c_uint32, c_uint32]
|
|
48
|
+
APPHELP.SdbGetNextChild.restype = c_uint32
|
|
49
|
+
|
|
50
|
+
# TAG WINAPI SdbGetTagFromTagID(PDB pdb, TAGID tagid);
|
|
51
|
+
APPHELP.SdbGetTagFromTagID.argtypes = [c_void_p, c_uint32]
|
|
52
|
+
APPHELP.SdbGetTagFromTagID.restype = c_uint16
|
|
53
|
+
|
|
54
|
+
# LPCWSTR WINAPI SdbTagToString(TAG tag);
|
|
55
|
+
APPHELP.SdbTagToString.argtypes = [c_uint16]
|
|
56
|
+
APPHELP.SdbTagToString.restype = c_wchar_p
|
|
57
|
+
|
|
58
|
+
# WORD WINAPI SdbReadDWORDTag(PDB pdb, TAGID tagid, WORD ret);
|
|
59
|
+
APPHELP.SdbReadWORDTag.argtypes = [c_void_p, c_uint32, c_uint16]
|
|
60
|
+
APPHELP.SdbReadWORDTag.restype = c_uint16
|
|
61
|
+
|
|
62
|
+
# DWORD WINAPI SdbReadDWORDTag(PDB pdb, TAGID tagid, DWORD ret);
|
|
63
|
+
APPHELP.SdbReadDWORDTag.argtypes = [c_void_p, c_uint32, c_uint32]
|
|
64
|
+
APPHELP.SdbReadDWORDTag.restype = c_uint32
|
|
65
|
+
|
|
66
|
+
# QWORD WINAPI SdbReadQWORDTag(PDB pdb, TAGID tagid, QWORD ret);
|
|
67
|
+
APPHELP.SdbReadQWORDTag.argtypes = [c_void_p, c_uint32, c_uint64]
|
|
68
|
+
APPHELP.SdbReadQWORDTag.restype = c_uint64
|
|
69
|
+
|
|
70
|
+
# DWORD WINAPI SdbGetTagDataSize(PDB pdb, TAGID tagid);
|
|
71
|
+
APPHELP.SdbGetTagDataSize.argtypes = [c_void_p, c_uint32]
|
|
72
|
+
APPHELP.SdbGetTagDataSize.restype = c_uint32
|
|
73
|
+
|
|
74
|
+
# BOOL WINAPI SdbReadBinaryTag(PDB pdb, TAGID tagid, PBYTE buffer, DWORD size);
|
|
75
|
+
APPHELP.SdbReadBinaryTag.argtypes = [c_void_p, c_uint32, POINTER(c_ubyte), c_uint32]
|
|
76
|
+
APPHELP.SdbReadBinaryTag.restype = c_uint32
|
|
77
|
+
|
|
78
|
+
# LPWSTR WINAPI SdbGetStringTagPtr(PDB pdb, TAGID tagid);
|
|
79
|
+
APPHELP.SdbGetStringTagPtr.argtypes = [c_void_p, c_uint32]
|
|
80
|
+
APPHELP.SdbGetStringTagPtr.restype = c_wchar_p
|
|
81
|
+
|
|
82
|
+
def _get_tag_type(tag: int) -> int:
|
|
83
|
+
"""Extracts the type from a tag."""
|
|
84
|
+
return tag & TAG_TYPE_MASK
|
|
85
|
+
|
|
86
|
+
def tag_to_string(tag: int) -> str:
|
|
87
|
+
"""Converts a tag to its string representation."""
|
|
88
|
+
return APPHELP.SdbTagToString(tag)
|
|
89
|
+
|
|
90
|
+
class Tag:
|
|
91
|
+
def __init__(self, db: 'SdbDatabase', tag_id: int):
|
|
92
|
+
self.db = db
|
|
93
|
+
self.tag_id = tag_id
|
|
94
|
+
if tag_id == TAGID_ROOT:
|
|
95
|
+
self.tag = TAG_NULL
|
|
96
|
+
self.name = 'SDB'
|
|
97
|
+
self.type = TAG_TYPE_LIST
|
|
98
|
+
else:
|
|
99
|
+
self.tag = APPHELP.SdbGetTagFromTagID(db.handle, tag_id)
|
|
100
|
+
self.name = APPHELP.SdbTagToString(self.tag)
|
|
101
|
+
self.type = _get_tag_type(self.tag)
|
|
102
|
+
|
|
103
|
+
def tags(self):
|
|
104
|
+
child = APPHELP.SdbGetFirstChild(self.db.handle, self.tag_id)
|
|
105
|
+
while child != 0:
|
|
106
|
+
yield Tag(self.db, child)
|
|
107
|
+
child = APPHELP.SdbGetNextChild(self.db.handle, self.tag_id, child)
|
|
108
|
+
|
|
109
|
+
def as_word(self, default: int = 0) -> int:
|
|
110
|
+
"""Returns the tag value as a word (16-bit integer)."""
|
|
111
|
+
if self.type != TAG_TYPE_WORD:
|
|
112
|
+
raise ValueError(f"Tag {self.name} is not a WORD type")
|
|
113
|
+
return APPHELP.SdbReadWORDTag(self.db.handle, self.tag_id, default)
|
|
114
|
+
|
|
115
|
+
def as_dword(self, default: int = 0) -> int:
|
|
116
|
+
"""Returns the tag value as a dword (32-bit integer)."""
|
|
117
|
+
if self.type != TAG_TYPE_DWORD:
|
|
118
|
+
raise ValueError(f"Tag {self.name} is not a DWORD type")
|
|
119
|
+
return APPHELP.SdbReadDWORDTag(self.db.handle, self.tag_id, default)
|
|
120
|
+
|
|
121
|
+
def as_qword(self, default: int = 0) -> int:
|
|
122
|
+
"""Returns the tag value as a qword (64-bit integer)."""
|
|
123
|
+
if self.type != TAG_TYPE_QWORD:
|
|
124
|
+
raise ValueError(f"Tag {self.name} is not a QWORD type")
|
|
125
|
+
return APPHELP.SdbReadQWORDTag(self.db.handle, self.tag_id, default)
|
|
126
|
+
|
|
127
|
+
def as_bytes(self) -> bytes:
|
|
128
|
+
"""Returns the tag value as bytes."""
|
|
129
|
+
if self.type != TAG_TYPE_BINARY:
|
|
130
|
+
raise ValueError(f"Tag {self.name} is not a BINARY type")
|
|
131
|
+
size = APPHELP.SdbGetTagDataSize(self.db.handle, self.tag_id)
|
|
132
|
+
if size == 0:
|
|
133
|
+
return b''
|
|
134
|
+
data = (c_ubyte * size)()
|
|
135
|
+
result = APPHELP.SdbReadBinaryTag(self.db.handle, self.tag_id, data, size)
|
|
136
|
+
if result == 0:
|
|
137
|
+
raise ValueError(f"Failed to read binary tag {self.name}")
|
|
138
|
+
return bytes(data)
|
|
139
|
+
|
|
140
|
+
def as_string(self) -> str:
|
|
141
|
+
"""Returns the tag value as a string."""
|
|
142
|
+
if self.type not in (TAG_TYPE_STRING, TAG_TYPE_STRINGREF):
|
|
143
|
+
raise ValueError(f"Tag {self.name} is not a STRING or STRINGREF type")
|
|
144
|
+
ptr = APPHELP.SdbGetStringTagPtr(self.db.handle, self.tag_id)
|
|
145
|
+
return ptr if ptr is not None else ''
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class SdbDatabase:
|
|
149
|
+
def __init__(self, path: str, path_type: PathType):
|
|
150
|
+
self.path = path
|
|
151
|
+
self.path_type = path_type
|
|
152
|
+
self.handle = APPHELP.SdbOpenDatabase(path, path_type)
|
|
153
|
+
self._root = None
|
|
154
|
+
|
|
155
|
+
def root(self):
|
|
156
|
+
if self._root is None and self.handle is not None:
|
|
157
|
+
self._root = Tag(self, TAGID_ROOT)
|
|
158
|
+
return self._root
|
|
159
|
+
|
|
160
|
+
def close(self):
|
|
161
|
+
if self.handle:
|
|
162
|
+
APPHELP.SdbCloseDatabase(self.handle)
|
|
163
|
+
self.handle = None
|
|
164
|
+
|
|
165
|
+
def __bool__(self):
|
|
166
|
+
if self.handle is None:
|
|
167
|
+
return False
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
def __enter__(self):
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
174
|
+
self.close()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from sdbtool import convert
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
@click.command()
|
|
5
|
+
@click.version_option()
|
|
6
|
+
@click.argument("input_file", type=click.Path(exists=True, dir_okay=False))
|
|
7
|
+
@click.option(
|
|
8
|
+
"--output",
|
|
9
|
+
type=click.File("w", encoding="utf-8"),
|
|
10
|
+
default="-",
|
|
11
|
+
help="Path to the output XML file, or '-' for stdout.",
|
|
12
|
+
)
|
|
13
|
+
def cli(input_file, output):
|
|
14
|
+
convert(input_file, output)
|