bethkit 1.0.0__py3-none-win_amd64.whl

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.
@@ -0,0 +1,568 @@
1
+ """
2
+ Copyright (c) Modding Forge
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import ctypes
7
+ import os
8
+ import sys
9
+ import threading
10
+ from collections.abc import Callable
11
+ from pathlib import Path
12
+
13
+ from ._types import (
14
+ BethkitFieldValue,
15
+ BethkitGlobalFormId,
16
+ BethkitNamedField,
17
+ BethkitSlice,
18
+ )
19
+
20
+ _lib: ctypes.CDLL | None = None
21
+ _lib_lock: threading.Lock = threading.Lock()
22
+
23
+ _c = ctypes.c_char_p
24
+ _vp = ctypes.c_void_p
25
+ _sz = ctypes.c_size_t
26
+ _i32 = ctypes.c_int32
27
+ _u8 = ctypes.c_uint8
28
+ _u16 = ctypes.c_uint16
29
+ _u32 = ctypes.c_uint32
30
+ _u64 = ctypes.c_uint64
31
+ _i64 = ctypes.c_int64
32
+ _f32 = ctypes.c_float
33
+ _bl = ctypes.c_bool
34
+
35
+
36
+ def _find_library() -> Path:
37
+ """
38
+ Locate the bethkit shared library.
39
+
40
+ Checks the ``BETHKIT_LIB`` environment variable first, then looks
41
+ next to the package directory, then falls back to a bare name for
42
+ the OS loader.
43
+
44
+ Returns:
45
+ Path: Resolved path to the library file.
46
+ """
47
+
48
+ env = os.environ.get("BETHKIT_LIB")
49
+ if env:
50
+ return Path(env)
51
+
52
+ if sys.platform == "win32":
53
+ candidates = ["bethkit_ffi.dll"]
54
+ elif sys.platform == "darwin":
55
+ candidates = ["libbethkit_ffi.dylib"]
56
+ else:
57
+ candidates = ["libbethkit_ffi.so"]
58
+
59
+ pkg_dir = Path(__file__).parent
60
+ for name in candidates:
61
+ for search_dir in (pkg_dir, pkg_dir.parent):
62
+ path = search_dir / name
63
+ if path.exists():
64
+ return path
65
+
66
+ return Path(candidates[0])
67
+
68
+
69
+ def load_lib() -> ctypes.CDLL:
70
+ """
71
+ Load the bethkit shared library (thread-safe singleton).
72
+
73
+ Returns the already-loaded library on subsequent calls.
74
+
75
+ Returns:
76
+ ctypes.CDLL: The loaded native library handle.
77
+
78
+ Raises:
79
+ BethkitLibraryNotFoundError: If the shared library file cannot
80
+ be found or loaded.
81
+ """
82
+
83
+ global _lib
84
+ if _lib is not None:
85
+ return _lib
86
+ with _lib_lock:
87
+ if _lib is None:
88
+ from .._error import BethkitLibraryNotFoundError
89
+
90
+ path = _find_library()
91
+ try:
92
+ if sys.platform == "win32" and path.is_absolute():
93
+ with os.add_dll_directory(str(path.parent)):
94
+ loaded = ctypes.CDLL(str(path))
95
+ else:
96
+ loaded = ctypes.CDLL(str(path))
97
+ except OSError as exc:
98
+ raise BethkitLibraryNotFoundError(
99
+ f"Cannot load bethkit native library '{path}': {exc}. "
100
+ "Place bethkit_ffi.dll / libbethkit_ffi.so / "
101
+ "libbethkit_ffi.dylib next to the package or set the "
102
+ "BETHKIT_LIB environment variable."
103
+ ) from exc
104
+ _declare(loaded)
105
+ _lib = loaded
106
+ return _lib
107
+
108
+
109
+ def last_error(lib: ctypes.CDLL) -> str:
110
+ """
111
+ Return the last error message for the current thread.
112
+
113
+ Must be called immediately after a failing FFI call because the
114
+ thread-local error buffer is overwritten by the next FFI call.
115
+
116
+ Args:
117
+ lib (ctypes.CDLL): Loaded bethkit native library handle.
118
+
119
+ Returns:
120
+ str: The error text, or ``"unknown error"`` if none is set.
121
+ """
122
+
123
+ msg: bytes | None = lib.bethkit_last_error()
124
+ if msg:
125
+ return msg.decode("utf-8")
126
+ return "unknown error"
127
+
128
+
129
+ def raise_last_error(lib: ctypes.CDLL) -> None:
130
+ """
131
+ Raise :class:`~bethkit.BethkitNativeError` with the last FFI error.
132
+
133
+ Args:
134
+ lib (ctypes.CDLL): Loaded bethkit native library handle.
135
+
136
+ Raises:
137
+ BethkitNativeError: Always raised with the current thread-local
138
+ error text.
139
+ """
140
+
141
+ from .._error import BethkitNativeError
142
+
143
+ raise BethkitNativeError(last_error(lib))
144
+
145
+
146
+ def copy_and_free_str(
147
+ ptr: int,
148
+ free_fn: Callable[[int], None],
149
+ lib: ctypes.CDLL,
150
+ ) -> str:
151
+ """
152
+ Copy the UTF-8 string at *ptr* into Python and free the native buffer.
153
+
154
+ This is the correct pattern for owned ``char*`` values returned by
155
+ the native library: copy first, then free, never hold the raw pointer.
156
+
157
+ Args:
158
+ ptr (int): Non-null ``c_void_p`` value pointing to the string.
159
+ free_fn (Callable[[int], None]): The matching ``*_free`` function
160
+ to call after copying.
161
+ lib (ctypes.CDLL): Loaded native library (unused here but kept
162
+ for uniform call-site signature).
163
+
164
+ Returns:
165
+ str: The decoded UTF-8 string.
166
+ """
167
+
168
+ try:
169
+ return ctypes.string_at(ptr).decode("utf-8")
170
+ finally:
171
+ free_fn(ptr)
172
+
173
+
174
+ def enc(s: Path) -> bytes:
175
+ """
176
+ Encode a filesystem path to a NUL-compatible UTF-8 bytes object.
177
+
178
+ Args:
179
+ s (Path): Filesystem path to encode.
180
+
181
+ Returns:
182
+ bytes: UTF-8 encoded path bytes.
183
+ """
184
+
185
+ return str(s).encode("utf-8")
186
+
187
+
188
+ def senc(s: str) -> bytes:
189
+ """
190
+ Encode a plain string to a NUL-compatible UTF-8 bytes object.
191
+
192
+ Args:
193
+ s (str): String to encode.
194
+
195
+ Returns:
196
+ bytes: UTF-8 encoded bytes.
197
+ """
198
+
199
+ return s.encode("utf-8")
200
+
201
+
202
+ def _declare(lib: ctypes.CDLL) -> None:
203
+ """
204
+ Declare argtypes and restype for every exported bethkit function.
205
+
206
+ Args:
207
+ lib (ctypes.CDLL): The freshly loaded native library handle.
208
+ """
209
+
210
+ lib.bethkit_last_error.restype = _c
211
+ lib.bethkit_last_error.argtypes = []
212
+
213
+ lib.bethkit_bytes_free.restype = None
214
+ lib.bethkit_bytes_free.argtypes = [ctypes.POINTER(_u8), _sz]
215
+
216
+ lib.bethkit_plugin_open.restype = _vp
217
+ lib.bethkit_plugin_open.argtypes = [_c, _i32]
218
+
219
+ lib.bethkit_plugin_open_from_bytes.restype = _vp
220
+ lib.bethkit_plugin_open_from_bytes.argtypes = [
221
+ ctypes.POINTER(_u8), _sz, _i32
222
+ ]
223
+
224
+ lib.bethkit_plugin_free.restype = None
225
+ lib.bethkit_plugin_free.argtypes = [_vp]
226
+
227
+ lib.bethkit_plugin_kind.restype = _i32
228
+ lib.bethkit_plugin_kind.argtypes = [_vp]
229
+
230
+ lib.bethkit_plugin_is_localized.restype = _bl
231
+ lib.bethkit_plugin_is_localized.argtypes = [_vp]
232
+
233
+ lib.bethkit_plugin_master_count.restype = _sz
234
+ lib.bethkit_plugin_master_count.argtypes = [_vp]
235
+
236
+ lib.bethkit_plugin_master_get.restype = _c
237
+ lib.bethkit_plugin_master_get.argtypes = [_vp, _sz]
238
+
239
+ lib.bethkit_plugin_description.restype = _c
240
+ lib.bethkit_plugin_description.argtypes = [_vp]
241
+
242
+ lib.bethkit_plugin_group_count.restype = _sz
243
+ lib.bethkit_plugin_group_count.argtypes = [_vp]
244
+
245
+ lib.bethkit_plugin_group_get.restype = _vp
246
+ lib.bethkit_plugin_group_get.argtypes = [_vp, _sz]
247
+
248
+ lib.bethkit_plugin_find_record.restype = _vp
249
+ lib.bethkit_plugin_find_record.argtypes = [_vp, _u32]
250
+
251
+ lib.bethkit_record_signature.restype = _i32
252
+ lib.bethkit_record_signature.argtypes = [_vp, ctypes.POINTER(_u8)]
253
+
254
+ lib.bethkit_record_form_id.restype = _u32
255
+ lib.bethkit_record_form_id.argtypes = [_vp]
256
+
257
+ lib.bethkit_record_flags.restype = _u32
258
+ lib.bethkit_record_flags.argtypes = [_vp]
259
+
260
+ lib.bethkit_record_form_version.restype = _u16
261
+ lib.bethkit_record_form_version.argtypes = [_vp]
262
+
263
+ lib.bethkit_record_editor_id.restype = _vp
264
+ lib.bethkit_record_editor_id.argtypes = [_vp]
265
+
266
+ lib.bethkit_record_editor_id_free.restype = None
267
+ lib.bethkit_record_editor_id_free.argtypes = [_vp]
268
+
269
+ lib.bethkit_record_subrecord_count.restype = _i64
270
+ lib.bethkit_record_subrecord_count.argtypes = [_vp]
271
+
272
+ lib.bethkit_record_subrecord_get.restype = _vp
273
+ lib.bethkit_record_subrecord_get.argtypes = [_vp, _sz]
274
+
275
+ lib.bethkit_record_subrecord_find.restype = _vp
276
+ lib.bethkit_record_subrecord_find.argtypes = [_vp, ctypes.POINTER(_u8)]
277
+
278
+ lib.bethkit_subrecord_signature.restype = _i32
279
+ lib.bethkit_subrecord_signature.argtypes = [_vp, ctypes.POINTER(_u8)]
280
+
281
+ lib.bethkit_subrecord_bytes.restype = BethkitSlice
282
+ lib.bethkit_subrecord_bytes.argtypes = [_vp]
283
+
284
+ lib.bethkit_subrecord_as_u8.restype = _i32
285
+ lib.bethkit_subrecord_as_u8.argtypes = [_vp, ctypes.POINTER(_u8)]
286
+
287
+ lib.bethkit_subrecord_as_u16.restype = _i32
288
+ lib.bethkit_subrecord_as_u16.argtypes = [_vp, ctypes.POINTER(_u16)]
289
+
290
+ lib.bethkit_subrecord_as_u32.restype = _i32
291
+ lib.bethkit_subrecord_as_u32.argtypes = [_vp, ctypes.POINTER(_u32)]
292
+
293
+ lib.bethkit_subrecord_as_f32.restype = _i32
294
+ lib.bethkit_subrecord_as_f32.argtypes = [_vp, ctypes.POINTER(_f32)]
295
+
296
+ lib.bethkit_subrecord_as_zstring.restype = _vp
297
+ lib.bethkit_subrecord_as_zstring.argtypes = [_vp]
298
+
299
+ lib.bethkit_zstring_free.restype = None
300
+ lib.bethkit_zstring_free.argtypes = [_vp]
301
+
302
+ lib.bethkit_group_type.restype = _i32
303
+ lib.bethkit_group_type.argtypes = [_vp]
304
+
305
+ lib.bethkit_group_child_count.restype = _sz
306
+ lib.bethkit_group_child_count.argtypes = [_vp]
307
+
308
+ lib.bethkit_group_child_is_record.restype = _bl
309
+ lib.bethkit_group_child_is_record.argtypes = [_vp, _sz]
310
+
311
+ lib.bethkit_group_child_as_record.restype = _vp
312
+ lib.bethkit_group_child_as_record.argtypes = [_vp, _sz]
313
+
314
+ lib.bethkit_group_child_as_group.restype = _vp
315
+ lib.bethkit_group_child_as_group.argtypes = [_vp, _sz]
316
+
317
+ lib.bethkit_archive_open.restype = _vp
318
+ lib.bethkit_archive_open.argtypes = [_c]
319
+
320
+ lib.bethkit_archive_free.restype = None
321
+ lib.bethkit_archive_free.argtypes = [_vp]
322
+
323
+ lib.bethkit_archive_format_name.restype = _c
324
+ lib.bethkit_archive_format_name.argtypes = [_vp]
325
+
326
+ lib.bethkit_archive_file_count.restype = _sz
327
+ lib.bethkit_archive_file_count.argtypes = [_vp]
328
+
329
+ lib.bethkit_archive_entry_get.restype = _vp
330
+ lib.bethkit_archive_entry_get.argtypes = [_vp, _sz]
331
+
332
+ lib.bethkit_archive_entry_path.restype = _vp
333
+ lib.bethkit_archive_entry_path.argtypes = [_vp]
334
+
335
+ lib.bethkit_archive_entry_path_free.restype = None
336
+ lib.bethkit_archive_entry_path_free.argtypes = [_vp]
337
+
338
+ lib.bethkit_archive_entry_uncompressed_size.restype = _u32
339
+ lib.bethkit_archive_entry_uncompressed_size.argtypes = [_vp]
340
+
341
+ lib.bethkit_archive_extract.restype = ctypes.POINTER(_u8)
342
+ lib.bethkit_archive_extract.argtypes = [_vp, _c, ctypes.POINTER(_sz)]
343
+
344
+ lib.bethkit_archive_extract_to_file.restype = _i32
345
+ lib.bethkit_archive_extract_to_file.argtypes = [_vp, _c, _c]
346
+
347
+ lib.bethkit_bsa_writer_new.restype = _vp
348
+ lib.bethkit_bsa_writer_new.argtypes = [_i32]
349
+
350
+ lib.bethkit_bsa_writer_free.restype = None
351
+ lib.bethkit_bsa_writer_free.argtypes = [_vp]
352
+
353
+ lib.bethkit_bsa_writer_set_compress.restype = _i32
354
+ lib.bethkit_bsa_writer_set_compress.argtypes = [_vp, _bl]
355
+
356
+ lib.bethkit_bsa_writer_set_embed_names.restype = _i32
357
+ lib.bethkit_bsa_writer_set_embed_names.argtypes = [_vp, _bl]
358
+
359
+ lib.bethkit_bsa_writer_add.restype = _i32
360
+ lib.bethkit_bsa_writer_add.argtypes = [
361
+ _vp, _c, ctypes.POINTER(_u8), _sz
362
+ ]
363
+
364
+ lib.bethkit_bsa_writer_write_to.restype = _i32
365
+ lib.bethkit_bsa_writer_write_to.argtypes = [_vp, _c]
366
+
367
+ lib.bethkit_ba2_gnrl_writer_new.restype = _vp
368
+ lib.bethkit_ba2_gnrl_writer_new.argtypes = [_i32]
369
+
370
+ lib.bethkit_ba2_gnrl_writer_free.restype = None
371
+ lib.bethkit_ba2_gnrl_writer_free.argtypes = [_vp]
372
+
373
+ lib.bethkit_ba2_gnrl_writer_add.restype = _i32
374
+ lib.bethkit_ba2_gnrl_writer_add.argtypes = [
375
+ _vp, _c, ctypes.POINTER(_u8), _sz
376
+ ]
377
+
378
+ lib.bethkit_ba2_gnrl_writer_write_to.restype = _i32
379
+ lib.bethkit_ba2_gnrl_writer_write_to.argtypes = [_vp, _c]
380
+
381
+ lib.bethkit_ba2_dx10_writer_new.restype = _vp
382
+ lib.bethkit_ba2_dx10_writer_new.argtypes = [_i32]
383
+
384
+ lib.bethkit_ba2_dx10_writer_free.restype = None
385
+ lib.bethkit_ba2_dx10_writer_free.argtypes = [_vp]
386
+
387
+ lib.bethkit_ba2_dx10_writer_add.restype = _i32
388
+ lib.bethkit_ba2_dx10_writer_add.argtypes = [
389
+ _vp, _c, ctypes.POINTER(_u8), _sz
390
+ ]
391
+
392
+ lib.bethkit_ba2_dx10_writer_write_to.restype = _i32
393
+ lib.bethkit_ba2_dx10_writer_write_to.argtypes = [_vp, _c]
394
+
395
+ lib.bethkit_load_order_new.restype = _vp
396
+ lib.bethkit_load_order_new.argtypes = []
397
+
398
+ lib.bethkit_load_order_free.restype = None
399
+ lib.bethkit_load_order_free.argtypes = [_vp]
400
+
401
+ lib.bethkit_load_order_push.restype = _i32
402
+ lib.bethkit_load_order_push.argtypes = [_vp, _c, _i32]
403
+
404
+ lib.bethkit_load_order_len.restype = _sz
405
+ lib.bethkit_load_order_len.argtypes = [_vp]
406
+
407
+ lib.bethkit_load_order_resolve.restype = _i32
408
+ lib.bethkit_load_order_resolve.argtypes = [
409
+ _vp, _u32, _c, ctypes.POINTER(BethkitGlobalFormId)
410
+ ]
411
+
412
+ lib.bethkit_plugin_cache_new.restype = _vp
413
+ lib.bethkit_plugin_cache_new.argtypes = []
414
+
415
+ lib.bethkit_plugin_cache_free.restype = None
416
+ lib.bethkit_plugin_cache_free.argtypes = [_vp]
417
+
418
+ lib.bethkit_plugin_cache_add.restype = _i32
419
+ lib.bethkit_plugin_cache_add.argtypes = [_vp, _c, _vp]
420
+
421
+ lib.bethkit_plugin_cache_len.restype = _sz
422
+ lib.bethkit_plugin_cache_len.argtypes = [_vp]
423
+
424
+ lib.bethkit_plugin_cache_record_count.restype = _sz
425
+ lib.bethkit_plugin_cache_record_count.argtypes = [_vp]
426
+
427
+ lib.bethkit_plugin_cache_resolve.restype = _vp
428
+ lib.bethkit_plugin_cache_resolve.argtypes = [_vp, _c, _u32]
429
+
430
+ lib.bethkit_plugin_cache_find_by_editor_id.restype = _vp
431
+ lib.bethkit_plugin_cache_find_by_editor_id.argtypes = [
432
+ _vp, _c, ctypes.POINTER(BethkitGlobalFormId)
433
+ ]
434
+
435
+ lib.bethkit_string_table_new.restype = _vp
436
+ lib.bethkit_string_table_new.argtypes = [_i32]
437
+
438
+ lib.bethkit_string_table_open.restype = _vp
439
+ lib.bethkit_string_table_open.argtypes = [_c]
440
+
441
+ lib.bethkit_string_table_free.restype = None
442
+ lib.bethkit_string_table_free.argtypes = [_vp]
443
+
444
+ lib.bethkit_string_table_kind.restype = _i32
445
+ lib.bethkit_string_table_kind.argtypes = [_vp]
446
+
447
+ lib.bethkit_string_table_len.restype = _sz
448
+ lib.bethkit_string_table_len.argtypes = [_vp]
449
+
450
+ lib.bethkit_string_table_get.restype = ctypes.POINTER(_u8)
451
+ lib.bethkit_string_table_get.argtypes = [_vp, _u32, ctypes.POINTER(_sz)]
452
+
453
+ lib.bethkit_string_table_insert.restype = _i32
454
+ lib.bethkit_string_table_insert.argtypes = [
455
+ _vp, _u32, ctypes.POINTER(_u8), _sz
456
+ ]
457
+
458
+ lib.bethkit_string_table_insert_new.restype = _i32
459
+ lib.bethkit_string_table_insert_new.argtypes = [
460
+ _vp, ctypes.POINTER(_u8), _sz, ctypes.POINTER(_u32)
461
+ ]
462
+
463
+ lib.bethkit_string_table_remove.restype = _bl
464
+ lib.bethkit_string_table_remove.argtypes = [_vp, _u32]
465
+
466
+ lib.bethkit_string_table_write_to_file.restype = _i32
467
+ lib.bethkit_string_table_write_to_file.argtypes = [_vp, _c]
468
+
469
+ lib.bethkit_localization_set_new.restype = _vp
470
+ lib.bethkit_localization_set_new.argtypes = []
471
+
472
+ lib.bethkit_localization_set_open.restype = _vp
473
+ lib.bethkit_localization_set_open.argtypes = [_c, _c]
474
+
475
+ lib.bethkit_localization_set_free.restype = None
476
+ lib.bethkit_localization_set_free.argtypes = [_vp]
477
+
478
+ lib.bethkit_localization_set_get.restype = ctypes.POINTER(_u8)
479
+ lib.bethkit_localization_set_get.argtypes = [
480
+ _vp, _i32, _u32, ctypes.POINTER(_sz)
481
+ ]
482
+
483
+ lib.bethkit_localization_set_set.restype = _i32
484
+ lib.bethkit_localization_set_set.argtypes = [
485
+ _vp, _i32, _u32, ctypes.POINTER(_u8), _sz
486
+ ]
487
+
488
+ lib.bethkit_localization_set_write.restype = _i32
489
+ lib.bethkit_localization_set_write.argtypes = [_vp, _c, _c]
490
+
491
+ lib.bethkit_schema_registry_sse.restype = _vp
492
+ lib.bethkit_schema_registry_sse.argtypes = []
493
+
494
+ lib.bethkit_schema_registry_has.restype = _bl
495
+ lib.bethkit_schema_registry_has.argtypes = [_vp, ctypes.POINTER(_u8)]
496
+
497
+ lib.bethkit_record_view_new.restype = _vp
498
+ lib.bethkit_record_view_new.argtypes = [_vp, ctypes.POINTER(_u8), _bl]
499
+
500
+ lib.bethkit_record_view_free.restype = None
501
+ lib.bethkit_record_view_free.argtypes = [_vp]
502
+
503
+ lib.bethkit_record_view_field_count.restype = _sz
504
+ lib.bethkit_record_view_field_count.argtypes = [_vp]
505
+
506
+ lib.bethkit_record_view_field_get.restype = ctypes.POINTER(BethkitNamedField)
507
+ lib.bethkit_record_view_field_get.argtypes = [_vp, _sz]
508
+
509
+ lib.bethkit_field_entries_len.restype = _sz
510
+ lib.bethkit_field_entries_len.argtypes = [_vp]
511
+
512
+ lib.bethkit_field_entries_get.restype = ctypes.POINTER(BethkitNamedField)
513
+ lib.bethkit_field_entries_get.argtypes = [_vp, _sz]
514
+
515
+ lib.bethkit_field_entries_free.restype = None
516
+ lib.bethkit_field_entries_free.argtypes = [_vp]
517
+
518
+ lib.bethkit_field_values_len.restype = _sz
519
+ lib.bethkit_field_values_len.argtypes = [_vp]
520
+
521
+ lib.bethkit_field_values_get.restype = ctypes.POINTER(BethkitFieldValue)
522
+ lib.bethkit_field_values_get.argtypes = [_vp, _sz]
523
+
524
+ lib.bethkit_field_values_free.restype = None
525
+ lib.bethkit_field_values_free.argtypes = [_vp]
526
+
527
+ lib.bethkit_plugin_writer_new.restype = _vp
528
+ lib.bethkit_plugin_writer_new.argtypes = [_i32, _f32]
529
+
530
+ lib.bethkit_plugin_writer_free.restype = None
531
+ lib.bethkit_plugin_writer_free.argtypes = [_vp]
532
+
533
+ lib.bethkit_plugin_writer_add_group.restype = _i32
534
+ lib.bethkit_plugin_writer_add_group.argtypes = [_vp, _vp]
535
+
536
+ lib.bethkit_plugin_writer_write_to_file.restype = _i32
537
+ lib.bethkit_plugin_writer_write_to_file.argtypes = [_vp, _c]
538
+
539
+ lib.bethkit_plugin_writer_write_to_bytes.restype = ctypes.POINTER(_u8)
540
+ lib.bethkit_plugin_writer_write_to_bytes.argtypes = [
541
+ _vp, ctypes.POINTER(_sz)
542
+ ]
543
+
544
+ lib.bethkit_writable_group_new.restype = _vp
545
+ lib.bethkit_writable_group_new.argtypes = [ctypes.POINTER(_u8), _i32]
546
+
547
+ lib.bethkit_writable_group_free.restype = None
548
+ lib.bethkit_writable_group_free.argtypes = [_vp]
549
+
550
+ lib.bethkit_writable_group_add_record.restype = _i32
551
+ lib.bethkit_writable_group_add_record.argtypes = [_vp, _vp]
552
+
553
+ lib.bethkit_writable_group_add_group.restype = _i32
554
+ lib.bethkit_writable_group_add_group.argtypes = [_vp, _vp]
555
+
556
+ lib.bethkit_writable_record_new.restype = _vp
557
+ lib.bethkit_writable_record_new.argtypes = [
558
+ ctypes.POINTER(_u8), _u32, _u32, _u16
559
+ ]
560
+
561
+ lib.bethkit_writable_record_free.restype = None
562
+ lib.bethkit_writable_record_free.argtypes = [_vp]
563
+
564
+ lib.bethkit_writable_record_add_subrecord.restype = _i32
565
+ lib.bethkit_writable_record_add_subrecord.argtypes = [
566
+ _vp, ctypes.POINTER(_u8), ctypes.POINTER(_u8), _sz
567
+ ]
568
+
bethkit/_ffi/_types.py ADDED
@@ -0,0 +1,97 @@
1
+ """ctypes Structure and Union definitions mirroring bethkit.h."""
2
+ from __future__ import annotations
3
+
4
+ import ctypes
5
+
6
+
7
+ class BethkitSlice(ctypes.Structure):
8
+ """A non-owning view of a byte slice passed across the FFI boundary."""
9
+
10
+ _fields_ = [
11
+ ("ptr", ctypes.POINTER(ctypes.c_uint8)),
12
+ ("len", ctypes.c_size_t),
13
+ ]
14
+
15
+
16
+ class BethkitGlobalFormId(ctypes.Structure):
17
+ """A globally unique FormID (plugin name + 24-bit object ID)."""
18
+
19
+ _fields_ = [
20
+ # Borrowed from the owning LoadOrder / PluginCache.
21
+ ("plugin_name", ctypes.c_char_p),
22
+ ("object_id", ctypes.c_uint32),
23
+ ]
24
+
25
+
26
+ class BethkitTypedFormId(ctypes.Structure):
27
+ """A FormID together with the record signatures it is allowed to reference."""
28
+
29
+ _fields_ = [
30
+ ("raw", ctypes.c_uint32),
31
+ # Pointer to a static array of 4-byte signatures; ctypes inserts the
32
+ # necessary 4-byte padding before this field automatically.
33
+ ("allowed_sigs", ctypes.c_void_p),
34
+ ("allowed_count", ctypes.c_size_t),
35
+ ]
36
+
37
+
38
+ class BethkitEnumVal(ctypes.Structure):
39
+ """An enumeration field value with its raw integer and optional name."""
40
+
41
+ _fields_ = [
42
+ ("value", ctypes.c_int64),
43
+ # Points to static memory; never free.
44
+ ("name", ctypes.c_char_p),
45
+ ]
46
+
47
+
48
+ class BethkitFlagsVal(ctypes.Structure):
49
+ """A flags field value with the raw integer and the names of active bits."""
50
+
51
+ _fields_ = [
52
+ ("raw_value", ctypes.c_uint64),
53
+ # Heap-allocated array of static string pointers; freed with the view.
54
+ ("active_names", ctypes.POINTER(ctypes.c_char_p)),
55
+ ("active_count", ctypes.c_size_t),
56
+ ]
57
+
58
+
59
+ class BethkitFieldValuePayload(ctypes.Union):
60
+ """The payload union inside BethkitFieldValue."""
61
+
62
+ _fields_ = [
63
+ ("int_val", ctypes.c_int64),
64
+ ("float_val", ctypes.c_double),
65
+ # Borrowed from the owning view; never free.
66
+ ("str_val", ctypes.c_char_p),
67
+ ("form_id", ctypes.c_uint32),
68
+ ("form_id_typed", BethkitTypedFormId),
69
+ ("bytes", BethkitSlice),
70
+ ("enum_val", BethkitEnumVal),
71
+ ("flags_val", BethkitFlagsVal),
72
+ # Owned; freed with bethkit_field_entries_free (or via view free).
73
+ ("struct_entries", ctypes.c_void_p),
74
+ # Owned; freed with bethkit_field_values_free (or via view free).
75
+ ("array_values", ctypes.c_void_p),
76
+ ("localized_id", ctypes.c_uint32),
77
+ ("_pad", ctypes.c_uint64),
78
+ ]
79
+
80
+
81
+ class BethkitFieldValue(ctypes.Structure):
82
+ """A decoded field value stored as a tagged union."""
83
+
84
+ _fields_ = [
85
+ ("kind", ctypes.c_int32),
86
+ ("payload", BethkitFieldValuePayload),
87
+ ]
88
+
89
+
90
+ class BethkitNamedField(ctypes.Structure):
91
+ """A named field snapshot inside a BethkitRecordView or BethkitFieldEntries."""
92
+
93
+ _fields_ = [
94
+ # Points to static memory; never free.
95
+ ("name", ctypes.c_char_p),
96
+ ("value", BethkitFieldValue),
97
+ ]
@@ -0,0 +1,16 @@
1
+ """
2
+ Copyright (c) Modding Forge
3
+
4
+ Archive subpackage — reading and writing BSA and BA2 archive files.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from .archive import Archive, ArchiveEntry, Ba2Dx10Writer, Ba2GnrlWriter, BsaWriter
9
+
10
+ __all__ = [
11
+ "Archive",
12
+ "ArchiveEntry",
13
+ "Ba2Dx10Writer",
14
+ "Ba2GnrlWriter",
15
+ "BsaWriter",
16
+ ]