sdbtool 0.3.0__tar.gz → 0.3.2__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.2/.github/workflows/python-publish.yml +54 -0
- {sdbtool-0.3.0 → sdbtool-0.3.2}/PKG-INFO +1 -1
- {sdbtool-0.3.0 → sdbtool-0.3.2}/pyproject.toml +1 -1
- sdbtool-0.3.2/sdbtool/__init__.py +25 -0
- sdbtool-0.3.2/sdbtool/apphelp/__init__.py +162 -0
- sdbtool-0.3.2/sdbtool/apphelp/winapi.py +128 -0
- {sdbtool-0.3.0 → sdbtool-0.3.2}/sdbtool/cli.py +2 -2
- sdbtool-0.3.2/sdbtool/xml.py +139 -0
- sdbtool-0.3.0/sdbtool/__init__.py +0 -131
- sdbtool-0.3.0/sdbtool/apphelp.py +0 -174
- {sdbtool-0.3.0 → sdbtool-0.3.2}/.github/workflows/python-test.yml +0 -0
- {sdbtool-0.3.0 → sdbtool-0.3.2}/.gitignore +0 -0
- {sdbtool-0.3.0 → sdbtool-0.3.2}/LICENSE.txt +0 -0
- {sdbtool-0.3.0 → sdbtool-0.3.2}/README.md +0 -0
- {sdbtool-0.3.0 → sdbtool-0.3.2}/sdbtool/__main__.py +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish Python Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
release-build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install the latest version of uv and set the python version
|
|
18
|
+
uses: astral-sh/setup-uv@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.10"
|
|
21
|
+
|
|
22
|
+
- name: Build release distributions
|
|
23
|
+
run: |
|
|
24
|
+
uv build
|
|
25
|
+
|
|
26
|
+
- name: Upload distributions
|
|
27
|
+
uses: actions/upload-artifact@v4
|
|
28
|
+
with:
|
|
29
|
+
name: release-dists
|
|
30
|
+
path: dist/
|
|
31
|
+
|
|
32
|
+
pypi-publish:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
needs:
|
|
35
|
+
- release-build
|
|
36
|
+
permissions:
|
|
37
|
+
id-token: write
|
|
38
|
+
|
|
39
|
+
# Dedicated environments with protections for publishing are strongly recommended.
|
|
40
|
+
# For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
|
|
41
|
+
environment:
|
|
42
|
+
name: pypi
|
|
43
|
+
|
|
44
|
+
steps:
|
|
45
|
+
- name: Retrieve release distributions
|
|
46
|
+
uses: actions/download-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: release-dists
|
|
49
|
+
path: dist/
|
|
50
|
+
|
|
51
|
+
- name: Publish release distributions to PyPI
|
|
52
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
53
|
+
with:
|
|
54
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
|
|
8
|
+
import sdbtool.apphelp as apphelp
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from sdbtool.xml import XmlTagVisitor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def sdb2xml(input_file: str, output_stream):
|
|
15
|
+
with apphelp.SdbDatabase(input_file, apphelp.PathType.DOS_PATH) as db:
|
|
16
|
+
if not db:
|
|
17
|
+
print(f"Failed to open database at '{input_file}'")
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
visitor = XmlTagVisitor(output_stream, Path(input_file).name)
|
|
21
|
+
root = db.root()
|
|
22
|
+
if root is None:
|
|
23
|
+
print(f"No root tag found in database '{input_file}'")
|
|
24
|
+
return
|
|
25
|
+
root.accept(visitor)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PROJECT: sdbtool
|
|
3
|
+
LICENSE: MIT (https://spdx.org/licenses/MIT)
|
|
4
|
+
PURPOSE: High level interface to the AppHelp API for reading SDB files.
|
|
5
|
+
COPYRIGHT: Copyright 2025 Mark Jansen <mark.jansen@reactos.org>
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ctypes import c_void_p
|
|
9
|
+
from enum import IntEnum
|
|
10
|
+
import sdbtool.apphelp.winapi as apphelp
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PathType(IntEnum):
|
|
14
|
+
DOS_PATH = 0
|
|
15
|
+
NT_PATH = 1
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
TAGID_NULL = 0x0
|
|
19
|
+
TAGID_ROOT = 0x0
|
|
20
|
+
_TAGID_ROOT = 12
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TagType(IntEnum):
|
|
24
|
+
"""Enumeration of tag types."""
|
|
25
|
+
|
|
26
|
+
NULL = 0x1000
|
|
27
|
+
BYTE = 0x2000
|
|
28
|
+
WORD = 0x3000
|
|
29
|
+
DWORD = 0x4000
|
|
30
|
+
QWORD = 0x5000
|
|
31
|
+
STRINGREF = 0x6000
|
|
32
|
+
LIST = 0x7000
|
|
33
|
+
STRING = 0x8000
|
|
34
|
+
BINARY = 0x9000
|
|
35
|
+
MASK = 0xF000
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
TAG_NULL = 0x0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get_tag_type(tag: int) -> TagType:
|
|
42
|
+
"""Extracts the type from a tag."""
|
|
43
|
+
return TagType(tag & TagType.MASK)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def tag_to_string(tag: int) -> str:
|
|
47
|
+
"""Converts a tag to its string representation."""
|
|
48
|
+
return apphelp.SdbTagToString(tag)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Tag:
|
|
52
|
+
def __init__(self, db: "SdbDatabase", tag_id: int):
|
|
53
|
+
self.db = db
|
|
54
|
+
self.tag_id = tag_id
|
|
55
|
+
if tag_id == TAGID_ROOT:
|
|
56
|
+
self.tag = TAG_NULL
|
|
57
|
+
self.name = "SDB"
|
|
58
|
+
self.type = TagType.LIST
|
|
59
|
+
else:
|
|
60
|
+
self.tag = apphelp.SdbGetTagFromTagID(self._ensure_db_handle(), tag_id)
|
|
61
|
+
self.name = apphelp.SdbTagToString(self.tag)
|
|
62
|
+
self.type = _get_tag_type(self.tag)
|
|
63
|
+
|
|
64
|
+
def _ensure_db_handle(self) -> c_void_p:
|
|
65
|
+
"""Ensures that the database handle is initialized."""
|
|
66
|
+
if self.db._handle is None:
|
|
67
|
+
raise ValueError("Database handle is not initialized")
|
|
68
|
+
return self.db._handle
|
|
69
|
+
|
|
70
|
+
def tags(self):
|
|
71
|
+
self._ensure_db_handle()
|
|
72
|
+
child = apphelp.SdbGetFirstChild(self._ensure_db_handle(), self.tag_id)
|
|
73
|
+
while child != 0:
|
|
74
|
+
yield Tag(self.db, child)
|
|
75
|
+
child = apphelp.SdbGetNextChild(
|
|
76
|
+
self._ensure_db_handle(), self.tag_id, child
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def as_word(self, default: int = 0) -> int:
|
|
80
|
+
"""Returns the tag value as a word (16-bit integer)."""
|
|
81
|
+
if self.type != TagType.WORD:
|
|
82
|
+
raise ValueError(f"Tag {self.name} is not a WORD type")
|
|
83
|
+
return apphelp.SdbReadWORDTag(self._ensure_db_handle(), self.tag_id, default)
|
|
84
|
+
|
|
85
|
+
def as_dword(self, default: int = 0) -> int:
|
|
86
|
+
"""Returns the tag value as a dword (32-bit integer)."""
|
|
87
|
+
if self.type != TagType.DWORD:
|
|
88
|
+
raise ValueError(f"Tag {self.name} is not a DWORD type")
|
|
89
|
+
return apphelp.SdbReadDWORDTag(self._ensure_db_handle(), self.tag_id, default)
|
|
90
|
+
|
|
91
|
+
def as_qword(self, default: int = 0) -> int:
|
|
92
|
+
"""Returns the tag value as a qword (64-bit integer)."""
|
|
93
|
+
if self.type != TagType.QWORD:
|
|
94
|
+
raise ValueError(f"Tag {self.name} is not a QWORD type")
|
|
95
|
+
return apphelp.SdbReadQWORDTag(self._ensure_db_handle(), self.tag_id, default)
|
|
96
|
+
|
|
97
|
+
def as_bytes(self) -> bytes:
|
|
98
|
+
"""Returns the tag value as bytes."""
|
|
99
|
+
if self.type != TagType.BINARY:
|
|
100
|
+
raise ValueError(f"Tag {self.name} is not a BINARY type")
|
|
101
|
+
return apphelp.SdbReadBinaryTag(self._ensure_db_handle(), self.tag_id)
|
|
102
|
+
|
|
103
|
+
def as_string(self) -> str:
|
|
104
|
+
"""Returns the tag value as a string."""
|
|
105
|
+
if self.type not in (TagType.STRING, TagType.STRINGREF):
|
|
106
|
+
raise ValueError(f"Tag {self.name} is not a STRING or STRINGREF type")
|
|
107
|
+
ptr = apphelp.SdbGetStringTagPtr(self._ensure_db_handle(), self.tag_id)
|
|
108
|
+
return ptr if ptr is not None else ""
|
|
109
|
+
|
|
110
|
+
def accept(self, visitor: "TagVisitor"):
|
|
111
|
+
"""Accepts a visitor for this tag."""
|
|
112
|
+
if self.type == TagType.LIST:
|
|
113
|
+
visitor.visit_list_begin(self)
|
|
114
|
+
for child in self.tags():
|
|
115
|
+
child.accept(visitor)
|
|
116
|
+
visitor.visit_list_end(self)
|
|
117
|
+
else:
|
|
118
|
+
# For non-list tags, we just visit this tag
|
|
119
|
+
visitor.visit(self)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class TagVisitor:
|
|
123
|
+
def visit(self, tag: Tag):
|
|
124
|
+
"""Visit a tag. Override this method in subclasses."""
|
|
125
|
+
raise NotImplementedError("Subclasses must implement visit method")
|
|
126
|
+
|
|
127
|
+
def visit_list_begin(self, tag: Tag):
|
|
128
|
+
"""Visit a list tag. Override this method in subclasses."""
|
|
129
|
+
raise NotImplementedError("Subclasses must implement visit_list_begin method")
|
|
130
|
+
|
|
131
|
+
def visit_list_end(self, tag: Tag):
|
|
132
|
+
"""Visit the end of a list tag. Override this method in subclasses."""
|
|
133
|
+
raise NotImplementedError("Subclasses must implement visit_list_end method")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class SdbDatabase:
|
|
137
|
+
def __init__(self, path: str, path_type: PathType):
|
|
138
|
+
self.path = path
|
|
139
|
+
self.path_type = path_type
|
|
140
|
+
self._handle = apphelp.SdbOpenDatabase(path, path_type)
|
|
141
|
+
self._root = None
|
|
142
|
+
|
|
143
|
+
def root(self) -> Tag | None:
|
|
144
|
+
if self._root is None and self._handle is not None:
|
|
145
|
+
self._root = Tag(self, TAGID_ROOT)
|
|
146
|
+
return self._root
|
|
147
|
+
|
|
148
|
+
def close(self):
|
|
149
|
+
if self._handle:
|
|
150
|
+
apphelp.SdbCloseDatabase(self._handle)
|
|
151
|
+
self._handle = None
|
|
152
|
+
|
|
153
|
+
def __bool__(self):
|
|
154
|
+
if self._handle is None:
|
|
155
|
+
return False
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
def __enter__(self):
|
|
159
|
+
return self
|
|
160
|
+
|
|
161
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
162
|
+
self.close()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PROJECT: sdbtool
|
|
3
|
+
LICENSE: MIT (https://spdx.org/licenses/MIT)
|
|
4
|
+
PURPOSE: winapi interface to the AppHelp API for reading SDB files.
|
|
5
|
+
COPYRIGHT: Copyright 2025 Mark Jansen <mark.jansen@reactos.org>
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ctypes import (
|
|
9
|
+
windll,
|
|
10
|
+
c_void_p,
|
|
11
|
+
c_uint16,
|
|
12
|
+
c_uint32,
|
|
13
|
+
c_wchar_p,
|
|
14
|
+
POINTER,
|
|
15
|
+
c_ubyte,
|
|
16
|
+
c_uint64,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
APPHELP = windll.apphelp
|
|
20
|
+
|
|
21
|
+
# PDB WINAPI SdbOpenDatabase(LPCWSTR path, PATH_TYPE type);
|
|
22
|
+
APPHELP.SdbOpenDatabase.argtypes = [c_wchar_p, c_uint32]
|
|
23
|
+
APPHELP.SdbOpenDatabase.restype = c_void_p
|
|
24
|
+
|
|
25
|
+
# void WINAPI SdbCloseDatabase(PDB);
|
|
26
|
+
APPHELP.SdbCloseDatabase.argtypes = [c_void_p]
|
|
27
|
+
|
|
28
|
+
# TAGID WINAPI SdbGetFirstChild(PDB pdb, TAGID parent);
|
|
29
|
+
APPHELP.SdbGetFirstChild.argtypes = [c_void_p, c_uint32]
|
|
30
|
+
APPHELP.SdbGetFirstChild.restype = c_uint32
|
|
31
|
+
|
|
32
|
+
# TAGID WINAPI SdbGetNextChild(PDB pdb, TAGID parent, TAGID prev_child);
|
|
33
|
+
APPHELP.SdbGetNextChild.argtypes = [c_void_p, c_uint32, c_uint32]
|
|
34
|
+
APPHELP.SdbGetNextChild.restype = c_uint32
|
|
35
|
+
|
|
36
|
+
# TAG WINAPI SdbGetTagFromTagID(PDB pdb, TAGID tagid);
|
|
37
|
+
APPHELP.SdbGetTagFromTagID.argtypes = [c_void_p, c_uint32]
|
|
38
|
+
APPHELP.SdbGetTagFromTagID.restype = c_uint16
|
|
39
|
+
|
|
40
|
+
# LPCWSTR WINAPI SdbTagToString(TAG tag);
|
|
41
|
+
APPHELP.SdbTagToString.argtypes = [c_uint16]
|
|
42
|
+
APPHELP.SdbTagToString.restype = c_wchar_p
|
|
43
|
+
|
|
44
|
+
# WORD WINAPI SdbReadDWORDTag(PDB pdb, TAGID tagid, WORD ret);
|
|
45
|
+
APPHELP.SdbReadWORDTag.argtypes = [c_void_p, c_uint32, c_uint16]
|
|
46
|
+
APPHELP.SdbReadWORDTag.restype = c_uint16
|
|
47
|
+
|
|
48
|
+
# DWORD WINAPI SdbReadDWORDTag(PDB pdb, TAGID tagid, DWORD ret);
|
|
49
|
+
APPHELP.SdbReadDWORDTag.argtypes = [c_void_p, c_uint32, c_uint32]
|
|
50
|
+
APPHELP.SdbReadDWORDTag.restype = c_uint32
|
|
51
|
+
|
|
52
|
+
# QWORD WINAPI SdbReadQWORDTag(PDB pdb, TAGID tagid, QWORD ret);
|
|
53
|
+
APPHELP.SdbReadQWORDTag.argtypes = [c_void_p, c_uint32, c_uint64]
|
|
54
|
+
APPHELP.SdbReadQWORDTag.restype = c_uint64
|
|
55
|
+
|
|
56
|
+
# DWORD WINAPI SdbGetTagDataSize(PDB pdb, TAGID tagid);
|
|
57
|
+
APPHELP.SdbGetTagDataSize.argtypes = [c_void_p, c_uint32]
|
|
58
|
+
APPHELP.SdbGetTagDataSize.restype = c_uint32
|
|
59
|
+
|
|
60
|
+
# BOOL WINAPI SdbReadBinaryTag(PDB pdb, TAGID tagid, PBYTE buffer, DWORD size);
|
|
61
|
+
APPHELP.SdbReadBinaryTag.argtypes = [c_void_p, c_uint32, POINTER(c_ubyte), c_uint32]
|
|
62
|
+
APPHELP.SdbReadBinaryTag.restype = c_uint32
|
|
63
|
+
|
|
64
|
+
# LPWSTR WINAPI SdbGetStringTagPtr(PDB pdb, TAGID tagid);
|
|
65
|
+
APPHELP.SdbGetStringTagPtr.argtypes = [c_void_p, c_uint32]
|
|
66
|
+
APPHELP.SdbGetStringTagPtr.restype = c_wchar_p
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def SdbOpenDatabase(path: str, path_type: int) -> c_void_p:
|
|
70
|
+
"""Open a database at the specified path."""
|
|
71
|
+
return APPHELP.SdbOpenDatabase(path, path_type)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def SdbCloseDatabase(db: c_void_p):
|
|
75
|
+
"""Close the specified database."""
|
|
76
|
+
APPHELP.SdbCloseDatabase(db)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def SdbGetFirstChild(db: c_void_p, parent: int) -> int:
|
|
80
|
+
"""Get the first child tag of the specified parent."""
|
|
81
|
+
return APPHELP.SdbGetFirstChild(db, parent)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def SdbGetNextChild(db: c_void_p, parent: int, prev_child: int) -> int:
|
|
85
|
+
"""Get the next child tag of the specified parent."""
|
|
86
|
+
return APPHELP.SdbGetNextChild(db, parent, prev_child)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def SdbGetTagFromTagID(db: c_void_p, tag_id: int) -> int:
|
|
90
|
+
"""Get the tag from the specified tag ID."""
|
|
91
|
+
return APPHELP.SdbGetTagFromTagID(db, tag_id)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def SdbTagToString(tag: int) -> str:
|
|
95
|
+
"""Convert a tag to its string representation."""
|
|
96
|
+
return APPHELP.SdbTagToString(tag)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def SdbReadWORDTag(db: c_void_p, tag_id: int, default: int = 0) -> int:
|
|
100
|
+
"""Read a WORD tag from the database."""
|
|
101
|
+
return APPHELP.SdbReadWORDTag(db, tag_id, default)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def SdbReadDWORDTag(db: c_void_p, tag_id: int, default: int = 0) -> int:
|
|
105
|
+
"""Read a DWORD tag from the database."""
|
|
106
|
+
return APPHELP.SdbReadDWORDTag(db, tag_id, default)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def SdbReadQWORDTag(db: c_void_p, tag_id: int, default: int = 0) -> int:
|
|
110
|
+
"""Read a QWORD tag from the database."""
|
|
111
|
+
return APPHELP.SdbReadQWORDTag(db, tag_id, default)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def SdbReadBinaryTag(db: c_void_p, tag_id: int) -> bytes:
|
|
115
|
+
"""Read a binary tag from the database."""
|
|
116
|
+
size = APPHELP.SdbGetTagDataSize(db, tag_id)
|
|
117
|
+
if size == 0:
|
|
118
|
+
return b""
|
|
119
|
+
data = (c_ubyte * size)()
|
|
120
|
+
result = APPHELP.SdbReadBinaryTag(db, tag_id, data, size)
|
|
121
|
+
if result == 0:
|
|
122
|
+
raise ValueError(f"Failed to read binary tag 0x{tag_id:x}")
|
|
123
|
+
return bytes(data)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def SdbGetStringTagPtr(db: c_void_p, tag_id: int) -> str:
|
|
127
|
+
"""Get the string pointer of the specified tag."""
|
|
128
|
+
return APPHELP.SdbGetStringTagPtr(db, tag_id)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from sdbtool import
|
|
1
|
+
from sdbtool import sdb2xml
|
|
2
2
|
import click
|
|
3
3
|
|
|
4
4
|
@click.command()
|
|
@@ -11,4 +11,4 @@ import click
|
|
|
11
11
|
help="Path to the output XML file, or '-' for stdout.",
|
|
12
12
|
)
|
|
13
13
|
def cli(input_file, output):
|
|
14
|
-
|
|
14
|
+
sdb2xml(input_file, output)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PROJECT: sdbtool
|
|
3
|
+
LICENSE: MIT (https://spdx.org/licenses/MIT)
|
|
4
|
+
PURPOSE: Xml writer + visitor for SDB files.
|
|
5
|
+
COPYRIGHT: Copyright 2025 Mark Jansen <mark.jansen@reactos.org>
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from sdbtool.apphelp import TagVisitor, Tag, TagType, tag_to_string
|
|
9
|
+
from xml.sax.saxutils import escape, quoteattr
|
|
10
|
+
from base64 import b64encode
|
|
11
|
+
|
|
12
|
+
INDENT_DEPTH = 2 # Number of spaces for each indentation level in XML output
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def tagtype_to_xmltype(tag_type: TagType) -> str | None:
|
|
16
|
+
switch = {
|
|
17
|
+
TagType.BYTE: "xs:byte",
|
|
18
|
+
TagType.WORD: "xs:unsignedShort",
|
|
19
|
+
TagType.DWORD: "xs:unsignedInt",
|
|
20
|
+
TagType.QWORD: "xs:unsignedLong",
|
|
21
|
+
TagType.STRINGREF: "xs:string",
|
|
22
|
+
TagType.STRING: "xs:string",
|
|
23
|
+
TagType.BINARY: "xs:base64Binary",
|
|
24
|
+
}
|
|
25
|
+
return switch.get(tag_type, None)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class XmlWriter:
|
|
29
|
+
def __init__(self, stream):
|
|
30
|
+
self._stream = stream
|
|
31
|
+
|
|
32
|
+
def write_xml_declaration(self):
|
|
33
|
+
"""Write the XML declaration at the start of the document."""
|
|
34
|
+
self._stream.write('<?xml version="1.0" encoding="utf-8" standalone="yes"?>')
|
|
35
|
+
|
|
36
|
+
def indent(self, level):
|
|
37
|
+
"""Return the indentation string for the given level."""
|
|
38
|
+
self._stream.write("\n")
|
|
39
|
+
self._stream.write(" " * (level * INDENT_DEPTH))
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
def open(self, name, attrib=None):
|
|
43
|
+
"""Open an XML tag with the given name and attributes."""
|
|
44
|
+
self._stream.write(f"<{name}")
|
|
45
|
+
if attrib:
|
|
46
|
+
for key, value in attrib.items():
|
|
47
|
+
self._stream.write(f' {key}={quoteattr(value)}')
|
|
48
|
+
self._stream.write(">")
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def close(self, name):
|
|
52
|
+
"""Close an XML tag with the given name."""
|
|
53
|
+
self._stream.write(f"</{name}>")
|
|
54
|
+
|
|
55
|
+
def empty_tag(self, name):
|
|
56
|
+
"""Write an empty XML tag with the given name and attributes."""
|
|
57
|
+
self._stream.write(f"<{name} />")
|
|
58
|
+
|
|
59
|
+
def write(self, text):
|
|
60
|
+
"""Write text content to the XML stream."""
|
|
61
|
+
self._stream.write(text)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class XmlTagVisitor(TagVisitor):
|
|
65
|
+
def __init__(self, stream, input_filename: str):
|
|
66
|
+
"""Initialize the XML tag visitor with a filename."""
|
|
67
|
+
self.writer = XmlWriter(stream)
|
|
68
|
+
self.writer.write_xml_declaration()
|
|
69
|
+
self._depth = 0
|
|
70
|
+
self._input_filename = input_filename
|
|
71
|
+
self._indent_on_close = False
|
|
72
|
+
|
|
73
|
+
def visit_list_begin(self, tag: Tag):
|
|
74
|
+
"""Visit the beginning of a list tag."""
|
|
75
|
+
|
|
76
|
+
attrs = None
|
|
77
|
+
if self._depth == 0:
|
|
78
|
+
attrs = {
|
|
79
|
+
"xmlns:xs": "http://www.w3.org/2001/XMLSchema",
|
|
80
|
+
"file": self._input_filename,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
self.writer.indent(self._depth).open(tag.name, attrs)
|
|
84
|
+
self._depth += 1
|
|
85
|
+
self._indent_on_close = False
|
|
86
|
+
|
|
87
|
+
def visit_list_end(self, tag: Tag):
|
|
88
|
+
"""Visit the end of a list tag."""
|
|
89
|
+
self._depth -= 1
|
|
90
|
+
if self._indent_on_close:
|
|
91
|
+
self.writer.indent(self._depth)
|
|
92
|
+
self.writer.close(tag.name)
|
|
93
|
+
self._indent_on_close = True
|
|
94
|
+
|
|
95
|
+
def visit(self, tag: Tag):
|
|
96
|
+
"""Visit a tag."""
|
|
97
|
+
self._indent_on_close = True
|
|
98
|
+
if tag.type == TagType.NULL:
|
|
99
|
+
self.writer.indent(self._depth).empty_tag(tag.name)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
attrs = {}
|
|
103
|
+
if tag.type != TagType.LIST:
|
|
104
|
+
typename = tagtype_to_xmltype(tag.type)
|
|
105
|
+
if typename is not None:
|
|
106
|
+
attrs["type"] = typename
|
|
107
|
+
else:
|
|
108
|
+
raise ValueError(f"Unknown tag type: {tag.type} for tag {tag.name}")
|
|
109
|
+
|
|
110
|
+
self.writer.indent(self._depth).open(tag.name, attrs)
|
|
111
|
+
self._write_tag_value(tag)
|
|
112
|
+
self.writer.close(tag.name)
|
|
113
|
+
|
|
114
|
+
def _write_tag_value(self, tag: Tag):
|
|
115
|
+
"""Write the value of a tag based on its type."""
|
|
116
|
+
if tag.type == TagType.BYTE:
|
|
117
|
+
self.writer.write(
|
|
118
|
+
"<!-- UNHANDLED BYTE TAG, please report this at https://github.com/learn-more/sdbtool -->"
|
|
119
|
+
)
|
|
120
|
+
elif tag.type == TagType.WORD:
|
|
121
|
+
value = tag.as_word()
|
|
122
|
+
self.writer.write(f"{value}")
|
|
123
|
+
if tag.name in ("INDEX_TAG", "INDEX_KEY"):
|
|
124
|
+
self.writer.write(f"<!-- {tag_to_string(value)} -->")
|
|
125
|
+
elif tag.type == TagType.DWORD:
|
|
126
|
+
self.writer.write(f"{tag.as_dword()}")
|
|
127
|
+
elif tag.type == TagType.QWORD:
|
|
128
|
+
self.writer.write(f"{tag.as_qword()}")
|
|
129
|
+
elif tag.type in (TagType.STRINGREF, TagType.STRING):
|
|
130
|
+
val = tag.as_string()
|
|
131
|
+
if val:
|
|
132
|
+
self.writer.write(f"{escape(val)}")
|
|
133
|
+
elif tag.type == TagType.BINARY:
|
|
134
|
+
data = tag.as_bytes()
|
|
135
|
+
if data:
|
|
136
|
+
base64_data = b64encode(data).decode("utf-8")
|
|
137
|
+
self.writer.write(base64_data)
|
|
138
|
+
else:
|
|
139
|
+
raise ValueError(f"Unknown tag type: {tag.type} for tag {tag.name}")
|
|
@@ -1,131 +0,0 @@
|
|
|
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())
|
sdbtool-0.3.0/sdbtool/apphelp.py
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|