Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 9 additions & 58 deletions bink/__init__.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,11 @@
"""Load the Bink C library."""
import os
import platform
import ctypes.util
"""Python bindings for the Blade Ink C API."""

def _load_library():
# First, look for a bundled bink shared object on the lib folder.
system = platform.system()
library_name = None
if system == 'Windows':
library_name = 'bink.dll'
elif system == 'Darwin':
library_name = 'libbink.dylib'
else:
library_name = 'libbink.so'
from ._ffi import (BINK_ERROR_ERROR, BINK_ERROR_WARNING, BINK_FAIL,
BINK_FAIL_INVALID_ARGUMENT, BINK_FAIL_INVALID_UTF8,
BINK_FAIL_NUL_BYTE, BINK_FAIL_NULL_POINTER,
BINK_FAIL_PANIC, BINK_OK, BINK_VALUE_BOOL,
BINK_VALUE_DIVERT_TARGET, BINK_VALUE_FLOAT, BINK_VALUE_INT,
BINK_VALUE_LIST, BINK_VALUE_STRING,
BINK_VALUE_VARIABLE_POINTER, LIB)

arch = "x86_64/"

if platform.machine() == "arm64":
arch = "arm64/"

_filename = os.path.join(os.path.dirname(__file__), 'native/' + arch + library_name)

# If no bundled shared object is found, look for a system-wide installed one.
if not os.path.exists(_filename):
# on windows all ctypes does when checking for the library
# is to append .dll to the end and look for an exact match
# within any entry in PATH.
_filename = ctypes.util.find_library('bink')

if _filename is None:
if platform.system() == 'Windows':
# Check current working directory for dll as ctypes fails to do so
_filename = os.path.join(os.path.realpath('.'), "bink.dll")
else:
_filename = library_name

try:
#print("lib filename: ", _filename)
lib = ctypes.CDLL(_filename)
except (OSError, TypeError) as exc:
lib = None
raise RuntimeError('bink library not found') from exc
return lib

LIB = _load_library()

LIB.bink_story_new.argtypes = [
ctypes.POINTER(
ctypes.c_void_p), ctypes.c_char_p, ctypes.POINTER(
ctypes.c_char_p)]
LIB.bink_story_new.restype = ctypes.c_int

LIB.bink_story_can_continue.argtypes = [
ctypes.c_void_p, ctypes.POINTER(ctypes.c_bool)]
LIB.bink_story_can_continue.restype = ctypes.c_int

BINK_OK = 0
BINK_FAIL = 1
BINK_FAIL_NULL_POINTER = 2
__all__ = [name for name in globals() if name.startswith("BINK_")] + ["LIB"]
98 changes: 98 additions & 0 deletions bink/_ffi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Low-level ctypes declarations shared by the public wrappers."""
import ctypes
import ctypes.util
import os
import platform

BINK_OK, BINK_FAIL, BINK_FAIL_NULL_POINTER, BINK_FAIL_INVALID_UTF8, BINK_FAIL_NUL_BYTE, BINK_FAIL_PANIC, BINK_FAIL_INVALID_ARGUMENT = range(7)
BINK_ERROR_WARNING, BINK_ERROR_ERROR = range(2)
BINK_VALUE_BOOL, BINK_VALUE_INT, BINK_VALUE_FLOAT, BINK_VALUE_STRING, BINK_VALUE_LIST, BINK_VALUE_DIVERT_TARGET, BINK_VALUE_VARIABLE_POINTER = range(7)


def _load_library():
names = {"Windows": "bink.dll", "Darwin": "libbink.dylib"}
library_name = names.get(platform.system(), "libbink.so")
arch = "arm64" if platform.machine() in ("arm64", "aarch64") else "x86_64"
filename = os.path.join(os.path.dirname(__file__), "native", arch, library_name)
if not os.path.exists(filename):
filename = ctypes.util.find_library("bink") or library_name
try:
return ctypes.CDLL(filename)
except (OSError, TypeError) as exc:
raise RuntimeError("bink library not found") from exc


LIB = _load_library()
P = ctypes.c_void_p
CP = ctypes.POINTER(ctypes.c_char_p)
SZ = ctypes.c_size_t


def _declare(name, args, restype=ctypes.c_int):
fn = getattr(LIB, name)
fn.argtypes, fn.restype = args, restype


for _name, _args in {
"bink_story_new": [ctypes.POINTER(P), ctypes.c_char_p, CP],
"bink_story_can_continue": [P, ctypes.POINTER(ctypes.c_bool), CP],
"bink_story_cont": [P, CP, CP], "bink_story_continue_maximally": [P, CP, CP],
"bink_story_continue_async": [P, ctypes.c_float, ctypes.POINTER(ctypes.c_bool), CP],
"bink_story_get_current_text": [P, CP, CP],
"bink_story_get_current_choices": [P, ctypes.POINTER(P), ctypes.POINTER(SZ), CP],
"bink_story_choose_choice_index": [P, SZ, CP],
"bink_story_get_current_tags": [P, ctypes.POINTER(P), ctypes.POINTER(SZ), CP],
"bink_story_get_global_tags": [P, ctypes.POINTER(P), ctypes.POINTER(SZ), CP],
"bink_story_get_tags_for_content_at_path": [P, ctypes.c_char_p, ctypes.POINTER(P), ctypes.POINTER(SZ), CP],
"bink_story_choose_path_string": [P, ctypes.c_char_p, CP],
"bink_story_choose_path_string_with_args": [P, ctypes.c_char_p, ctypes.c_bool, P, CP],
"bink_story_evaluate_function": [P, ctypes.c_char_p, P, ctypes.POINTER(P), CP, CP],
"bink_story_load_state": [P, ctypes.c_char_p, CP], "bink_story_save_state": [P, CP, CP],
"bink_story_reset_state": [P, CP],
"bink_story_get_visit_count_at_path_string": [P, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int32), CP],
"bink_story_get_current_path": [P, CP, CP], "bink_story_build_string_of_hierarchy": [P, CP, CP],
"bink_choices_get_text": [P, SZ, CP, CP], "bink_choices_get_tags": [P, SZ, ctypes.POINTER(P), ctypes.POINTER(SZ), CP],
"bink_tags_get": [P, SZ, CP, CP],
"bink_value_new_bool": [ctypes.c_bool, ctypes.POINTER(P), CP], "bink_value_new_int": [ctypes.c_int32, ctypes.POINTER(P), CP],
"bink_value_new_float": [ctypes.c_float, ctypes.POINTER(P), CP], "bink_value_new_string": [ctypes.c_char_p, ctypes.POINTER(P), CP],
"bink_value_get_bool": [P, ctypes.POINTER(ctypes.c_bool), CP], "bink_value_get_int": [P, ctypes.POINTER(ctypes.c_int32), CP],
"bink_value_get_float": [P, ctypes.POINTER(ctypes.c_float), CP], "bink_value_get_string": [P, CP, CP],
"bink_value_get_kind": [P, ctypes.POINTER(ctypes.c_int), CP],
"bink_value_array_new": [ctypes.POINTER(P), CP], "bink_value_array_push": [P, P, CP],
"bink_list_new": [ctypes.POINTER(P), CP], "bink_story_list_new_from_origin": [P, ctypes.c_char_p, ctypes.POINTER(P), CP],
"bink_story_list_new_from_item": [P, ctypes.c_char_p, ctypes.POINTER(P), CP], "bink_list_add_item": [P, ctypes.c_char_p, ctypes.c_int32, CP],
"bink_list_get_count": [P, ctypes.POINTER(SZ), CP], "bink_list_get_item": [P, SZ, CP, ctypes.POINTER(ctypes.c_int32), CP],
"bink_list_get_origin_count": [P, ctypes.POINTER(SZ), CP], "bink_list_get_origin": [P, SZ, CP, CP],
"bink_value_new_list": [P, ctypes.POINTER(P), CP], "bink_value_get_list": [P, ctypes.POINTER(P), CP],
"bink_var_get": [P, ctypes.c_char_p, ctypes.POINTER(P), CP], "bink_var_set": [P, ctypes.c_char_p, P, CP],
"bink_story_switch_flow": [P, ctypes.c_char_p, CP], "bink_story_remove_flow": [P, ctypes.c_char_p, CP],
"bink_story_switch_to_default_flow": [P, CP], "bink_story_set_allow_external_function_fallbacks": [P, ctypes.c_bool, CP],
"bink_unbind_external_function": [P, ctypes.c_char_p, CP],
"bink_fun_args_count": [P, ctypes.POINTER(SZ), CP], "bink_fun_args_get": [P, SZ, ctypes.POINTER(P), CP],
}.items():
_declare(_name, _args)

for _name, _args in {"bink_story_free": [P], "bink_choices_free": [P], "bink_tags_free": [P], "bink_value_free": [P], "bink_value_array_free": [P], "bink_list_free": [P], "bink_cstring_free": [ctypes.c_char_p]}.items():
_declare(_name, _args, None)


def check(result, error):
if result == BINK_OK:
return
message = error.value.decode("utf-8") if error.value else "Blade Ink FFI error"
if error.value:
LIB.bink_cstring_free(error)
raise RuntimeError(message)


def call(name, *args):
error = ctypes.c_char_p()
check(getattr(LIB, name)(*args, ctypes.byref(error)), error)


def take_string(value):
try:
return value.value.decode("utf-8") if value.value else ""
finally:
if value.value:
LIB.bink_cstring_free(value)
85 changes: 29 additions & 56 deletions bink/choices.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,36 @@
# pylint: disable=E1101, R0903
"""Handle Ink Choices."""
"""Choice collections returned by a story."""
import ctypes
from bink import LIB, BINK_OK


class ChoicesIterator:
"""Iterator for choices."""
def __init__(self, choices):
self._choices = choices
self._index = 0

def __next__(self):
if self._index >= len(self._choices):
raise StopIteration

self._index += 1
return self._choices[self._index - 1]
from ._ffi import LIB, call, take_string
from .tags import Tags


class Choices:
"""List of story choices."""
def __init__(self, choices, c_len: int):
self._choices = choices
self._len = c_len

def __len__(self) -> int:
"""Returns the number of choices."""
return self._len

def __bool__(self) -> bool:
return self._len != 0

def __iter__(self):
return ChoicesIterator(self)

def __getitem__(self, idx: int) -> str:
"""Returns the choice text"""

if not isinstance(idx, int):
raise TypeError

if idx < 0 or idx >= self._len:
raise IndexError

return self.get_text(idx)

def get_text(self, idx) -> str:
"""Returns the choice text."""
text = ctypes.c_char_p()
ret = LIB.bink_choices_get_text(self._choices, idx, ctypes.byref(text))

if ret != BINK_OK:
raise RuntimeError(
"Error getting choice text, index out of bounds?")

result = text.value.decode('utf-8')
LIB.bink_cstring_free(text)

return result
"""An owned sequence of choice text, with access to each choice's tags."""
def __init__(self, pointer, length):
self._choices, self._len = pointer, length

def __len__(self): return self._len
def __bool__(self): return bool(self._len)
def __iter__(self): return (self[index] for index in range(self._len))

def _index(self, index):
if not isinstance(index, int): raise TypeError("choice index must be an integer")
if index < 0: index += self._len
if not 0 <= index < self._len: raise IndexError("choice index out of range")
return index

def __getitem__(self, index):
value = ctypes.c_char_p()
call("bink_choices_get_text", self._choices, self._index(index), ctypes.byref(value))
return take_string(value)

def get_tags(self, index):
pointer, length = ctypes.c_void_p(), ctypes.c_size_t()
call("bink_choices_get_tags", self._choices, self._index(index), ctypes.byref(pointer), ctypes.byref(length))
return Tags(pointer, length.value)

def __del__(self):
LIB.bink_choices_free(self._choices)
if getattr(self, "_choices", None):
LIB.bink_choices_free(self._choices)
self._choices = None
Binary file modified bink/native/arm64/libbink.dylib
Binary file not shown.
Binary file modified bink/native/arm64/libbink.so
Binary file not shown.
Binary file modified bink/native/x86_64/bink.dll
Binary file not shown.
Binary file modified bink/native/x86_64/libbink.dylib
Binary file not shown.
Binary file modified bink/native/x86_64/libbink.so
Binary file not shown.
Loading
Loading