├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── binary_reader ├── __init__.py └── binary_reader.py ├── setup.cfg └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # IPython 77 | profile_default/ 78 | ipython_config.py 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | .dmypy.json 111 | dmypy.json 112 | 113 | # Pyre type checker 114 | .pyre/ 115 | test.py 116 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 SutandoTsukai181 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyBinaryReader 2 | A python module for basic binary file IO. 3 | 4 | This is something I wrote to use in my personal projects, but feel free to use it if you like! 5 | 6 | # Installation 7 | `pip install binary-reader` (add `--upgrade` to update) 8 | 9 | # Usage 10 | ```py 11 | from binary_reader import BinaryReader 12 | ``` 13 | 14 | Here is some example usage on creating and using a BinaryReader to read a small part of the DDS header: 15 | 16 | ```py 17 | f = open("example.dds", "rb") # Open a file in binary mode 18 | 19 | reader = BinaryReader(f.read()) # Create a new BinaryReader from the file buffer (we can close the file afterwards) 20 | 21 | # Read the magic as a UTF-8 encoded string and compare it to the correct magic 22 | if reader.read_str(4) != 'DDS ': 23 | raise Exception('Incorrect magic.') 24 | 25 | size = reader.read_uint32() # Read the next 4 bytes as an unsigned 32-bit integer 26 | ``` 27 | 28 | Another example on using BinaryReader features to navigate through a buffer: 29 | 30 | ```py 31 | some_offset = reader.read_uint32() # Read an offset that we want to move to 32 | 33 | prev_pos = reader.pos() # Store the current position so we can return back later 34 | reader.seek(some_offset) # Set the current position in the file to that offset 35 | 36 | vector = reader.read_float(3) # Read 3 consecutive 32-bit floats, return them as a tuple 37 | 38 | reader.seek(prev_pos) # Go back to the previous position 39 | 40 | reader.seek(4, whence=1) # In addition to absolute seeking, we can also seek relative to the current position... 41 | 42 | reader.seek(0, whence=2) # And relative to the end of the buffer. This will set the position to be the last index in the buffer 43 | 44 | ``` 45 | 46 | In addition to reading, you can also write to a new buffer: 47 | ```py 48 | writer = BinaryReader() # Create a new BinaryReader (bytearray buffer is initialized automatically) 49 | 50 | writer.set_endian(is_big_endian=True) # Set the endianness to big endian 51 | 52 | writer.write_str('MGIC') 53 | writer.write_uint32(20) 54 | 55 | writer.align(0x10) # Align the buffer to 0x10 (in this case, will append 8 bytes to the buffer) 56 | 57 | writer.pad(0xF0) # Add 0xF0 bytes of padding 58 | print(writer.size()) # This should print 256 (0x100) 59 | 60 | # Write the buffer to a new file 61 | with open('examplefile', 'wb') as f: 62 | f.write(writer.buffer()) 63 | ``` 64 | 65 | These are the types that can be used with BinaryReader. Just add `read_` or `write_` before the type to get the method name: 66 | ``` 67 | uint8, int8, 68 | uint16, int16, half_float 69 | uint32, int32, float 70 | uint64, int64, 71 | bytes, str 72 | ``` 73 | 74 | # License 75 | This project uses the MIT License, so feel free to include it in whatever you want. 76 | -------------------------------------------------------------------------------- /binary_reader/__init__.py: -------------------------------------------------------------------------------- 1 | from .binary_reader import BinaryReader, BrStruct, Endian, Whence 2 | -------------------------------------------------------------------------------- /binary_reader/binary_reader.py: -------------------------------------------------------------------------------- 1 | __author__ = "SutandoTsukai181" 2 | __copyright__ = "Copyright 2021, SutandoTsukai181" 3 | __license__ = "MIT" 4 | __version__ = "1.4.3" 5 | 6 | import struct 7 | from contextlib import contextmanager 8 | from enum import Flag, IntEnum 9 | from typing import Tuple, Union 10 | 11 | FMT = dict() 12 | for c in ["b", "B", "s"]: 13 | FMT[c] = 1 14 | for c in ["h", "H", "e"]: 15 | FMT[c] = 2 16 | for c in ["i", "I", "f"]: 17 | FMT[c] = 4 18 | for c in ["q", "Q"]: 19 | FMT[c] = 8 20 | 21 | 22 | class Endian(Flag): 23 | LITTLE = False 24 | BIG = True 25 | 26 | 27 | class Whence(IntEnum): 28 | BEGIN = 0 29 | CUR = 1 30 | END = 2 31 | 32 | 33 | class BrStruct: 34 | """Base class for objects passed to BinaryReader's `read_struct` and `write_struct` methods.\n 35 | Any type passed to `read_struct` and any object passed to `write_struct` must inherit from this class.\n 36 | Override `__br_read__` and `__br_write__` methods from this class to set up BinaryReader to read your classes.\n""" 37 | 38 | def __init__(self) -> None: 39 | """If this class will be used with BinaryReader's `read_struct` method, then this method MUST receive zero arguments after `self`.\n 40 | """ 41 | pass 42 | 43 | def __br_read__(self, br: 'BinaryReader', *args) -> None: 44 | """Called once when `BinaryReader.read_struct` is called on this class.\n 45 | This method must accept at least 1 parameter (other than `self`).\n 46 | The first parameter will be the BinaryReader instance which `read_struct` was called from. 47 | This parameter can be used to `read` the attributes of object.\n 48 | This method can take any number of parameters after the required first parameter. 49 | The additional arguments corresponding to these parameters should be passed to `BinaryReader.read_struct` after the `count` argument.\n 50 | """ 51 | pass 52 | 53 | def __br_write__(self, br: 'BinaryReader', *args) -> None: 54 | """Called once when `BinaryReader.write_struct` is called on an instance of this class.\n 55 | This method must accept at least 1 parameter (other than `self`).\n 56 | The first parameter will be the BinaryReader instance which `write_struct` was called from. 57 | This parameter can be used to `write` the attributes of object.\n 58 | This method can take any number of parameters after the required first parameter. 59 | The additional arguments corresponding to these parameters should be passed to `BinaryReader.write_struct` after the `value` argument.\n 60 | """ 61 | pass 62 | 63 | 64 | class BinaryReader: 65 | """A buffer reader/writer containing a mutable bytearray.\n 66 | Allows reading and writing various data types, while advancing the position of the buffer on each operation.""" 67 | __buf: bytearray 68 | __idx: int 69 | __endianness: Endian 70 | __encoding: str 71 | 72 | def __init__(self, buffer: bytearray = bytearray(), endianness: Endian = Endian.LITTLE, encoding='utf-8'): 73 | """Constructs a BinaryReader with the given buffer, endianness, and encoding and sets its position to 0.\n 74 | If buffer is not given, a new bytearray() is created. If endianness is not given, it is set to little endian.\n 75 | Default encoding is UTF-8. Will throw an exception if encoding is unknown. 76 | """ 77 | self.__buf = bytearray(buffer) 78 | self.__endianness = endianness 79 | self.__idx = 0 80 | self.set_encoding(encoding) 81 | 82 | def __enter__(self): 83 | return self 84 | 85 | def __exit__(self, exc_type, exc_val, exc_tb): 86 | self.__buf.clear() 87 | 88 | def pos(self) -> int: 89 | """Returns the current position in the buffer.""" 90 | return self.__idx 91 | 92 | def __past_eof(self, index: int) -> bool: 93 | return index > self.size() 94 | 95 | def past_eof(self) -> bool: 96 | """Returns True if the current position is after the end of file.""" 97 | return self.__past_eof(self.pos()) 98 | 99 | def eof(self) -> bool: 100 | """Returns True if the current position is at/after the end of file.""" 101 | return self.__past_eof(self.pos() + 1) 102 | 103 | def size(self) -> int: 104 | """Returns the size of the buffer.""" 105 | return len(self.__buf) 106 | 107 | def buffer(self) -> bytearray: 108 | """Returns the buffer as a bytearray.""" 109 | return bytearray(self.__buf) 110 | 111 | def pad(self, size: int) -> None: 112 | """Pads the buffer by 0s with the given size and advances the buffer position.\n 113 | Will advance the buffer position only if the position was at the end of the buffer. 114 | """ 115 | if self.__idx == self.size(): 116 | self.__idx += size 117 | 118 | self.extend([0] * size) 119 | 120 | def align_pos(self, size: int) -> int: 121 | """Aligns the current position to the given size.\n 122 | Advances the current position by (size - (current_position % size)), but only if it is not aligned.\n 123 | Returns the number of bytes skipped. 124 | """ 125 | skipped = 0 126 | 127 | if self.pos() % size: 128 | skipped = size - (self.pos() % size) 129 | self.seek(skipped, Whence.CUR) 130 | 131 | return skipped 132 | 133 | def align(self, size: int) -> int: 134 | """Aligns the buffer to the given size.\n 135 | Extends the buffer from its end by (size - (buffer_size % size)), but only if it is not aligned.\n 136 | Will advance the buffer position only if the position was at the end of the buffer.\n 137 | Returns the number of bytes padded. 138 | """ 139 | pad = 0 140 | 141 | if self.size() % size: 142 | pad = size - (self.size() % size) 143 | self.pad(pad) 144 | 145 | return pad 146 | 147 | def extend(self, buffer: bytearray) -> None: 148 | """Extends the BinaryReader's buffer with the given buffer.\n 149 | Does not advance buffer position. 150 | """ 151 | self.__buf.extend(buffer) 152 | 153 | def trim(self, size: int) -> int: 154 | """Trims the buffer to the given size.\n 155 | If size is greater than the buffer's length, no bytes will be removed.\n 156 | If the position of the buffer was in the trimmed range, it will be set to the end of the buffer.\n 157 | Returns the number of bytes removed. 158 | """ 159 | trimmed = 0 160 | 161 | if size >= 0: 162 | trimmed = self.size() - size 163 | 164 | if (trimmed > 0): 165 | self.__buf = self.__buf[:size] 166 | if (self.__idx > size): 167 | self.__idx = self.size() 168 | else: 169 | trimmed = 0 170 | 171 | return trimmed 172 | 173 | def seek(self, offset: int, whence: Whence = Whence.BEGIN) -> None: 174 | """Changes the current position of the buffer by the given offset.\n 175 | The seek is determined relative to the whence:\n 176 | Whence.BEGIN will seek relative to the start.\n 177 | Whence.CUR will seek relative to the current position.\n 178 | Whence.END will seek relative to the end (offset should be positive). 179 | """ 180 | new_offset = self.__idx 181 | 182 | if whence == Whence.BEGIN: 183 | new_offset = offset 184 | elif whence == Whence.CUR: 185 | new_offset = self.__idx + offset 186 | elif whence == Whence.END: 187 | new_offset = len(self.__buf) - offset 188 | else: 189 | raise Exception('BinaryReader Error: invalid whence value.') 190 | 191 | if self.__past_eof(new_offset) or new_offset < 0: 192 | raise Exception( 193 | 'BinaryReader Error: cannot seek farther than buffer length.') 194 | 195 | self.__idx = new_offset 196 | 197 | @contextmanager 198 | def seek_to(self, offset: int, whence: Whence = Whence.BEGIN) -> 'BinaryReader': 199 | """Same as `seek(offset, whence)`, but can be used with the `with` statement in a new context.\n 200 | Upon returning to the old context, the original position of the buffer before the `with` statement will be restored.\n 201 | Will return a reference of the BinaryReader to be used for `as` in the `with` statement.\n 202 | The original BinaryReader that this was called from can still be used instead of the return value. 203 | """ 204 | prev_pos = self.__idx 205 | self.seek(offset, whence) 206 | yield self 207 | 208 | self.__idx = prev_pos 209 | 210 | def set_endian(self, endianness: Endian) -> None: 211 | """Sets the endianness of the BinaryReader.""" 212 | self.__endianness = endianness 213 | 214 | def set_encoding(self, encoding: str) -> None: 215 | """Sets the default encoding of the BinaryReader when reading/writing strings.\n 216 | Will throw an exception if the encoding is unknown. 217 | """ 218 | str.encode('', encoding) 219 | self.__encoding = encoding 220 | 221 | @staticmethod 222 | def is_iterable(x) -> bool: 223 | return hasattr(x, '__iter__') and not isinstance(x, (str, bytes)) 224 | 225 | def __read_type(self, format: str, count=1): 226 | i = self.__idx 227 | new_offset = self.__idx + (FMT[format] * count) 228 | 229 | end = ">" if self.__endianness else "<" 230 | 231 | if self.__past_eof(new_offset): 232 | raise Exception( 233 | 'BinaryReader Error: cannot read farther than buffer length.') 234 | 235 | self.__idx = new_offset 236 | return struct.unpack_from(end + str(count) + format, self.__buf, i) 237 | 238 | def read_bytes(self, size=1) -> bytes: 239 | """Reads a bytes object with the given size from the current position.""" 240 | return self.__read_type("s", size)[0] 241 | 242 | def read_str(self, size=None, encoding=None) -> str: 243 | """Reads a string with the given size from the current position.\n 244 | If size is not given, will read until the first null byte (which the position will be set after).\n 245 | If encoding is `None` (default), will use the BinaryReader's encoding. 246 | """ 247 | encode = encoding or self.__encoding 248 | 249 | if size is None: 250 | string = bytearray() 251 | while self.__idx < len(self.__buf): 252 | string.append(self.__buf[self.__idx]) 253 | self.__idx += 1 254 | if string[-1] == 0: 255 | break 256 | 257 | return string.split(b'\x00', 1)[0].decode(encode) 258 | 259 | if size < 0: 260 | raise ValueError('size cannot be negative') 261 | 262 | return self.read_bytes(size).split(b'\x00', 1)[0].decode(encode) 263 | 264 | def read_str_to_token(self, token: str, encoding=None) -> str: 265 | """Reads a string until a string token is found.\n 266 | If encoding is `None` (default), will use the BinaryReader's encoding. 267 | """ 268 | encode = encoding or self.__encoding 269 | 270 | i = 0 271 | string = bytearray() 272 | token_bytes = token.encode(encode) 273 | token_size = len(token_bytes) 274 | while self.__idx < len(self.__buf): 275 | string.append(self.__buf[self.__idx]) 276 | self.__idx += 1 277 | if token_bytes == string[i : i + token_size]: 278 | break 279 | if len(string) >= token_size: 280 | i += 1 281 | 282 | return string.split(b'\x00', 1)[0].decode(encode) 283 | 284 | def read_int64(self, count=None) -> Union[int, Tuple[int]]: 285 | """Reads a signed 64-bit integer.\n 286 | If count is given, will return a tuple of values instead of 1 value. 287 | """ 288 | if count is not None: 289 | return self.__read_type("q", count) 290 | return self.__read_type("q")[0] 291 | 292 | def read_uint64(self, count=None) -> Union[int, Tuple[int]]: 293 | """Reads an unsigned 64-bit integer.\n 294 | If count is given, will return a tuple of values instead of 1 value. 295 | """ 296 | if count is not None: 297 | return self.__read_type("Q", count) 298 | return self.__read_type("Q")[0] 299 | 300 | def read_int32(self, count=None) -> Union[int, Tuple[int]]: 301 | """Reads a signed 32-bit integer.\n 302 | If count is given, will return a tuple of values instead of 1 value. 303 | """ 304 | if count is not None: 305 | return self.__read_type("i", count) 306 | return self.__read_type("i")[0] 307 | 308 | def read_uint32(self, count=None) -> Union[int, Tuple[int]]: 309 | """Reads an unsigned 32-bit integer.\n 310 | If count is given, will return a tuple of values instead of 1 value. 311 | """ 312 | if count is not None: 313 | return self.__read_type("I", count) 314 | return self.__read_type("I")[0] 315 | 316 | def read_int16(self, count=None) -> Union[int, Tuple[int]]: 317 | """Reads a signed 16-bit integer.\n 318 | If count is given, will return a tuple of values instead of 1 value. 319 | """ 320 | if count is not None: 321 | return self.__read_type("h", count) 322 | return self.__read_type("h")[0] 323 | 324 | def read_uint16(self, count=None) -> Union[int, Tuple[int]]: 325 | """Reads an unsigned 16-bit integer.\n 326 | If count is given, will return a tuple of values instead of 1 value. 327 | """ 328 | if count is not None: 329 | return self.__read_type("H", count) 330 | return self.__read_type("H")[0] 331 | 332 | def read_int8(self, count=None) -> Union[int, Tuple[int]]: 333 | """Reads a signed 8-bit integer.\n 334 | If count is given, will return a tuple of values instead of 1 value. 335 | """ 336 | if count is not None: 337 | return self.__read_type("b", count) 338 | return self.__read_type("b")[0] 339 | 340 | def read_uint8(self, count=None) -> Union[int, Tuple[int]]: 341 | """Reads an unsigned 8-bit integer.\n 342 | If count is given, will return a tuple of values instead of 1 value. 343 | """ 344 | if count is not None: 345 | return self.__read_type("B", count) 346 | return self.__read_type("B")[0] 347 | 348 | def read_float(self, count=None) -> Union[float, Tuple[float]]: 349 | """Reads a 32-bit float.\n 350 | If count is given, will return a tuple of values instead of 1 value. 351 | """ 352 | if count is not None: 353 | return self.__read_type("f", count) 354 | return self.__read_type("f")[0] 355 | 356 | def read_half_float(self, count=None) -> Union[float, Tuple[float]]: 357 | """Reads a 16-bit float (half-float).\n 358 | If count is given, will return a tuple of values instead of 1 value. 359 | """ 360 | if count is not None: 361 | return self.__read_type("e", count) 362 | return self.__read_type("e")[0] 363 | 364 | def read_struct(self, cls: type, count=None, *args) -> BrStruct: 365 | """Creates and returns an instance of the given `cls` after calling its `__br_read__` method.\n 366 | `cls` must be a subclass of BrStruct.\n 367 | If count is given, will return a tuple of values instead of 1 value.\n 368 | Additional arguments given after `count` will be passed to the `__br_read__` method of `cls`.\n 369 | """ 370 | if not (cls and issubclass(cls, BrStruct)): 371 | raise Exception( 372 | f'BinaryReader Error: {cls} is not a subclass of BrStruct.') 373 | 374 | if count is not None: 375 | result = [] 376 | 377 | for _ in range(count): 378 | br_struct = cls() 379 | br_struct.__br_read__(self, *args) 380 | result.append(br_struct) 381 | 382 | return tuple(result) 383 | 384 | br_struct = cls() 385 | br_struct.__br_read__(self, *args) 386 | 387 | return br_struct 388 | 389 | def __write_type(self, format: str, value, is_iterable: bool) -> None: 390 | i = self.__idx 391 | 392 | end = ">" if self.__endianness else "<" 393 | 394 | count = 1 395 | if is_iterable or type(value) is bytes: 396 | count = len(value) 397 | 398 | if i + (FMT[format] * count) > len(self.__buf): 399 | self.pad(FMT[format] * count) 400 | else: 401 | self.__idx += FMT[format] * count 402 | 403 | if is_iterable: 404 | struct.pack_into(end + str(count) + format, self.__buf, i, *value) 405 | else: 406 | struct.pack_into(end + str(count) + format, self.__buf, i, value) 407 | 408 | def write_bytes(self, value: bytes) -> None: 409 | """Writes a bytes object to the buffer.""" 410 | self.__write_type("s", value, is_iterable=False) 411 | 412 | def write_str(self, string: str, null=False, encoding=None) -> int: 413 | """Writes a whole string to the buffer.\n 414 | If null is `True`, will append a null byte (`0x00`) after the string.\n 415 | If encoding is `None` (default), will use the BinaryReader's encoding.\n 416 | Returns the number of bytes written (including the null byte if it was added). 417 | """ 418 | bytes_obj = string.encode(encoding or self.__encoding) + (b'\x00' if null else b'') 419 | self.write_bytes(bytes_obj) 420 | return len(bytes_obj) 421 | 422 | def write_str_fixed(self, string: str, size: int, encoding=None) -> None: 423 | """Writes a whole string with the given size to the buffer.\n 424 | If the string's size after being encoded is less than size, the remaining size will be filled with null bytes.\n 425 | If it's more than size, the encoded bytes will be trimmed to size.\n 426 | If encoding is `None` (default), will use the BinaryReader's encoding. 427 | """ 428 | 429 | if size < 0: 430 | raise ValueError('size cannot be negative') 431 | 432 | self.write_bytes(string.encode(encoding or self.__encoding)[:size].ljust(size, b'\x00')) 433 | 434 | def write_int64(self, value: int) -> None: 435 | """Writes a signed 64-bit integer.\n 436 | If value is iterable, will write all of the elements in the given iterable. 437 | """ 438 | self.__write_type("q", value, self.is_iterable(value)) 439 | 440 | def write_uint64(self, value: int) -> None: 441 | """Writes an unsigned 64-bit integer.\n 442 | If value is iterable, will write all of the elements in the given iterable. 443 | """ 444 | self.__write_type("Q", value, self.is_iterable(value)) 445 | 446 | def write_int32(self, value: int) -> None: 447 | """Writes a signed 32-bit integer.\n 448 | If value is iterable, will write all of the elements in the given iterable. 449 | """ 450 | self.__write_type("i", value, self.is_iterable(value)) 451 | 452 | def write_uint32(self, value: int) -> None: 453 | """Writes an unsigned 32-bit integer.\n 454 | If value is iterable, will write all of the elements in the given iterable. 455 | """ 456 | self.__write_type("I", value, self.is_iterable(value)) 457 | 458 | def write_int16(self, value: int) -> None: 459 | """Writes a signed 16-bit integer.\n 460 | If value is iterable, will write all of the elements in the given iterable. 461 | """ 462 | self.__write_type("h", value, self.is_iterable(value)) 463 | 464 | def write_uint16(self, value: int) -> None: 465 | """Writes an unsigned 16-bit integer.\n 466 | If value is iterable, will write all of the elements in the given iterable. 467 | """ 468 | self.__write_type("H", value, self.is_iterable(value)) 469 | 470 | def write_int8(self, value: int) -> None: 471 | """Writes a signed 8-bit integer.\n 472 | If value is iterable, will write all of the elements in the given iterable. 473 | """ 474 | self.__write_type("b", value, self.is_iterable(value)) 475 | 476 | def write_uint8(self, value: int) -> None: 477 | """Writes an unsigned 8-bit integer.\n 478 | If value is iterable, will write all of the elements in the given iterable. 479 | """ 480 | self.__write_type("B", value, self.is_iterable(value)) 481 | 482 | def write_float(self, value: float) -> None: 483 | """Writes a 32-bit float.\n 484 | If value is iterable, will write all of the elements in the given iterable. 485 | """ 486 | self.__write_type("f", value, self.is_iterable(value)) 487 | 488 | def write_half_float(self, value: float) -> None: 489 | """Writes a 16-bit float (half-float).\n 490 | If value is iterable, will write all of the elements in the given iterable. 491 | """ 492 | self.__write_type("e", value, self.is_iterable(value)) 493 | 494 | def write_struct(self, value: BrStruct, *args) -> None: 495 | """Calls the given value's `__br_write__` method.\n 496 | `value` must be an instance of a class that inherits BrStruct.\n 497 | If value is iterable, will call the `__br_write__` method of all elements in the given iterable.\n 498 | Additional arguments given after `value` will be passed to the `__br_write__` method of `value`.\n 499 | """ 500 | if not isinstance(value, BrStruct) and not (self.is_iterable(value) and all(isinstance(e, BrStruct) for e in value)): 501 | raise Exception( 502 | f'BinaryReader Error: {value} is not an instance of BrStruct.') 503 | 504 | if self.is_iterable(value): 505 | for s in value: 506 | s.__br_write__(self, *args) 507 | else: 508 | value.__br_write__(self, *args) 509 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # Inside of setup.cfg 2 | [metadata] 3 | description-file = README.md 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open('README.md', encoding='utf-8') as f: 4 | long_description = f.read() 5 | 6 | setup( 7 | name = 'binary_reader', 8 | packages = ['binary_reader'], 9 | version = '1.4.3', 10 | license='MIT', 11 | description = 'A python module for basic binary file IO.', 12 | long_description=long_description, 13 | long_description_content_type='text/markdown', 14 | author = 'SutandoTsukai181', 15 | author_email = 'mosamaeldeeb@gmail.com', 16 | url = 'https://github.com/SutandoTsukai181/PyBinaryReader', 17 | download_url = 'https://github.com/SutandoTsukai181/PyBinaryReader/archive/refs/tags/v1.4.3.tar.gz', 18 | keywords = ['BINARY', 'IO', 'STRUCT'], 19 | install_requires=[], 20 | classifiers=[ 21 | 'Development Status :: 5 - Production/Stable', 22 | 'Intended Audience :: Developers', 23 | 'Topic :: Software Development :: Build Tools', 24 | 'License :: OSI Approved :: MIT License', 25 | 'Programming Language :: Python :: 3.5', 26 | 'Programming Language :: Python :: 3.8', 27 | ], 28 | ) 29 | --------------------------------------------------------------------------------