Last active
February 7, 2025 05:14
-
-
Save junron/9e203a745095e793f92922c4e208e9ff to your computer and use it in GitHub Desktop.
C Types for Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import real_ctypes | |
arr = (char[16])(b'I love Python+C!') | |
print("A big integer:", (uint128)(arr)) | |
arr2 = (long[:])(arr) | |
print("Chunked:", arr2) | |
arr2 += 1 | |
print("Back to bytes, but offset:", bytes((char[:])(arr2))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import __main__ | |
from ctypes import py_object | |
import builtins | |
import typing | |
class context: | |
print_style = 'hex' | |
allow_excess = True | |
class IntWrapperMeta(type): | |
def __new__(cls, name, bases, namespace): | |
int_returning_methods = [ | |
'__add__', '__radd__', '__sub__', '__rsub__', | |
'__mul__', '__rmul__', '__floordiv__', '__rfloordiv__', | |
'__mod__', '__rmod__', '__pow__', '__rpow__', | |
'__lshift__', '__rlshift__', '__rshift__', '__rrshift__', | |
'__and__', '__rand__', '__or__', '__ror__', | |
'__xor__', '__rxor__', '__neg__', '__pos__', | |
'__abs__' | |
] | |
for method_name in int_returning_methods: | |
if method_name not in namespace: | |
def make_wrapped_method(method_name): | |
def wrapped_method(self, *args, **kwargs): | |
original_method = getattr(int, method_name) | |
args = [arg.val if isinstance(arg, IntTypeVal) else arg for arg in args] | |
kwargs = {k:arg.val if isinstance(arg, IntTypeVal) else arg for k,arg in kwargs.items()} | |
result = original_method(self.val, *args, **kwargs) | |
return IntTypeVal(self.int_type, result) | |
return wrapped_method | |
namespace[method_name] = make_wrapped_method(method_name) | |
return super().__new__(cls, name, bases, namespace) | |
class IntArray: | |
def __init__(self, int_type: "IntType", length): | |
self.int_type = int_type | |
self.length = length | |
def __call__(self, arg) -> typing.Any: | |
if isinstance(arg, IntArrayVal): | |
arg = arg.to_bytes() | |
elif isinstance(arg, IntTypeVal): | |
arg = arg.to_bytes() | |
elif type(arg) == str: | |
arg = arg.encode() | |
assert type(arg) == bytes | |
sz = self.int_type.size | |
if self.length is None: | |
if not context.allow_excess: | |
assert len(arg) % sz == 0, f"Input length {len(arg)} not divisible by item size {sz}" | |
self.length = len(arg) // sz | |
if context.allow_excess: | |
arg = arg[:sz * self.length] | |
assert len(arg) == sz * self.length, f"Input length {len(arg)} != required length {sz * self.length}" | |
arr = [] | |
for i in range(self.length): | |
data = arg[i*sz:(i+1)*sz] | |
arr.append(self.int_type.from_bytes(data)) | |
return IntArrayVal(self, arr) | |
def __repr__(self) -> builtins.str: | |
return f"{self.int_type}[{self.length}]" | |
class IntType: | |
def __init__(self, signed, size, byte_order): | |
self.signed = signed | |
self.size = size | |
self.byte_order = byte_order | |
def __getitem__(self, index): | |
if isinstance(index, slice): | |
index = None | |
return IntArray(self, index) | |
def __call__(self, arg): | |
if type(arg) == int: | |
return IntTypeVal(self, arg) | |
if isinstance(arg, IntTypeVal): | |
arg = arg.to_bytes() | |
elif isinstance(arg, IntArrayVal): | |
arg = arg.to_bytes() | |
if type(arg) == bytes: | |
return self.from_bytes(arg) | |
raise TypeError(f"Cannot cast {arg} to {self}") | |
def from_bytes(self, b): | |
if context.allow_excess: | |
b = b[:self.size] | |
assert len(b) == self.size, f"Input size {len(b)} != integer size {self.size}" | |
return IntTypeVal(self, int.from_bytes(b, self.byte_order, signed=self.signed)) | |
def __repr__(self): | |
return f"{'' if self.signed else 'u'}int{self.size*8}{'' if self.byte_order == 'little' else 'be'}" | |
class IntTypeVal(int, metaclass=IntWrapperMeta): | |
def __new__(cls, int_type: IntType, val: int): | |
self = super().__new__(cls, val) | |
self.int_type = int_type | |
self.val = self._cast(val) | |
return self | |
def __init__(self, int_type: IntType, val: int): | |
self.int_type = int_type | |
self.val = self._cast(val) | |
def _cast(self, val): | |
bit_count = 8*self.int_type.size | |
if not self.int_type.signed: | |
while val < 0: | |
val += pow(2, bit_count) | |
return val % pow(2, bit_count) | |
while val > pow(2, bit_count - 1): | |
val -= pow(2, bit_count) | |
while val < -pow(2, bit_count-1): | |
val += pow(2, bit_count) | |
return val | |
def to_bytes(self): | |
return self.val.to_bytes(self.int_type.size, self.int_type.byte_order, signed=self.int_type.signed) | |
def __repr__(self): | |
if context.print_style == 'dec': | |
return repr(self.val) | |
elif context.print_style == 'hex': | |
return hex(self.val) | |
raise ValueError(f"Invalid print style {context.print_style}") | |
class IntArrayVal(list): | |
def __new__(cls, t: IntArray, val: list["IntArrayVal"]): | |
self = super().__new__(cls, val) | |
self.t = t | |
self.val = val | |
return self | |
def __init__(self, a, b): | |
super().__init__(b) | |
def to_bytes(self): | |
out = b"" | |
for v in self: | |
out += v.to_bytes() | |
return out | |
def __add__(self, value): | |
assert type(value) == int | |
assert value >= 0 | |
assert value < len(self) | |
new_type = IntArray(self.t.int_type, len(self) - value) | |
return IntArrayVal(new_type, self[value:]) | |
def __iadd__(self, value): | |
if type(value) == int: | |
return self + value | |
return super().__iadd__(value) | |
def __repr__(self) -> builtins.str: | |
return repr(self.t) + " {" + (", ".join(repr(x) for x in self)) + "}" | |
obase = py_object.from_address(id(__main__.__dict__) + 8) | |
class fglobals(dict): | |
__slots__ = () | |
def __getitem__(self, key, dict=dict, obase=obase): | |
mapping = { | |
"char": "int8", | |
"short": "int16", | |
"int": "int32", | |
"long": "int64", | |
"byte": "uint8", | |
"word": "uint16", | |
"dword": "uint32", | |
"qword": "uint64", | |
} | |
try: | |
obase.value = dict | |
return self[key] | |
except: | |
try: | |
return getattr(builtins, key) | |
except: | |
k = key | |
signed = True | |
byte_order = 'little' | |
if k.startswith("u"): | |
k = k[1:] | |
signed = False | |
if k in mapping: | |
k = mapping[k] | |
if k.startswith("u"): | |
k = k[1:] | |
signed = False | |
if k[:3] == "int": | |
if k.endswith("be"): | |
byte_order = 'big' | |
k = k[:-2] | |
size = int(k[3:]) | |
assert size % 8 == 0 | |
size //= 8 | |
assert size > 0 | |
return IntType(signed, size, byte_order) | |
else: | |
raise NameError(f"Name '{key}' not found!") | |
finally: | |
obase.value = __class__ | |
obase.value = fglobals |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import real_ctypes | |
arr = (char[16])(b'I love Python+C!') | |
int1 = (uint128)(arr) | |
int2 = (uint128be)(int(int1)) | |
rev = (char[:])(int2) | |
print(bytes(rev)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment