├── .flake8 ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── mypy.ini ├── requirements.txt ├── reversebox ├── __init__.py ├── checksum │ ├── __init__.py │ ├── checksum_adler32.py │ ├── checksum_cocos2d_pvr.py │ ├── checksum_fletcher16.py │ ├── checksum_fletcher32.py │ ├── checksum_internet_ipv4_header.py │ ├── checksum_sum8.py │ ├── checksum_sum8_2s_complement.py │ ├── checksum_unix_sum_bsd16.py │ ├── checksum_unix_sum_sysv.py │ └── checksum_xor8.py ├── common │ ├── __init__.py │ ├── common.py │ ├── logger.py │ └── numpy_helper_functions.py ├── compression │ ├── __init__.py │ ├── compression_jcalg1.py │ ├── compression_lzo.py │ ├── compression_mio0.py │ ├── compression_re_tiyoruga_dat.py │ ├── compression_refpack.py │ └── compression_zlib.py ├── crc │ ├── __init__.py │ ├── crc16_arc.py │ ├── crc16_ccitt.py │ ├── crc16_dnp.py │ ├── crc16_ea_crcf.py │ ├── crc16_kermit.py │ ├── crc16_modbus.py │ ├── crc16_sick.py │ ├── crc32_asobo.py │ ├── crc32_iso_hdlc.py │ ├── crc64_asobo.py │ ├── crc64_go_iso.py │ ├── crc8.py │ └── crc_unix_cksum.py ├── encryption │ ├── __init__.py │ ├── encryption_hatch_engine.py │ ├── encryption_rot13.py │ ├── encryption_xor_basic.py │ ├── encryption_xor_basic_key_guesser.py │ ├── encryption_xor_gianas_return_zda.py │ └── encryption_xor_retro64_eco.py ├── hash │ ├── __init__.py │ ├── hash_additive.py │ ├── hash_ap.py │ ├── hash_djb2.py │ ├── hash_fnv.py │ ├── hash_jenkins_one_at_a_time.py │ ├── hash_md2.py │ ├── hash_md5.py │ ├── hash_murmur3.py │ ├── hash_pivotal_games_dat.py │ ├── hash_sdbm.py │ ├── hash_sha1.py │ └── hash_sha2.py ├── image │ ├── __init__.py │ ├── byte_swap.py │ ├── common.py │ ├── compression │ │ ├── __init__.py │ │ ├── compression_gst.py │ │ ├── compression_lzw.py │ │ ├── compression_packbits.py │ │ ├── compression_rle_executioners.py │ │ ├── compression_rle_tga.py │ │ └── compression_zlib.py │ ├── decoders │ │ ├── __init__.py │ │ ├── bumpmap_decoder.py │ │ ├── compressed_decoder_encoder.py │ │ ├── gst_decoder.py │ │ ├── n64_decoder.py │ │ └── yuv_decoder.py │ ├── encoders │ │ ├── __init__.py │ │ └── gst_encoder.py │ ├── file_formats │ │ ├── __init__.py │ │ └── image_dds.py │ ├── image_decoder.py │ ├── image_encoder.py │ ├── image_formats.py │ ├── palettes │ │ ├── __init__.py │ │ ├── palette_random.py │ │ └── palette_vga.py │ ├── pillow_wrapper.py │ └── swizzling │ │ ├── __init__.py │ │ ├── swizzle_3ds.py │ │ ├── swizzle_bc.py │ │ ├── swizzle_cmpr.py │ │ ├── swizzle_gamecube.py │ │ ├── swizzle_gst.py │ │ ├── swizzle_morton.py │ │ ├── swizzle_morton_ps4.py │ │ ├── swizzle_ps2.py │ │ ├── swizzle_ps2_4bit.py │ │ ├── swizzle_ps2_suba.py │ │ ├── swizzle_psp.py │ │ ├── swizzle_psvita_dreamcast.py │ │ ├── swizzle_switch.py │ │ ├── swizzle_wii_u.py │ │ └── swizzle_x360.py ├── io_files │ ├── __init__.py │ ├── bytes_handler.py │ ├── bytes_helper_functions.py │ ├── check_file.py │ ├── file_handler.py │ ├── mod_handler.py │ └── translation_text_handler.py └── libs │ ├── DirectXTex.dll │ ├── __init__.py │ └── refpack.dll ├── setup.py ├── tests ├── __init__.py ├── common.py ├── test_hash │ ├── test_hash_additive.py │ ├── test_hash_ap.py │ ├── test_hash_djb2.py │ ├── test_hash_fnv.py │ ├── test_hash_jenkins_one_at_a_time.py │ ├── test_hash_md2.py │ ├── test_hash_md5.py │ ├── test_hash_murmur3.py │ ├── test_hash_pivotal_games_dat.py │ ├── test_hash_sdbm.py │ ├── test_hash_sha1.py │ └── test_hash_sha2.py ├── tests_checksum │ ├── checksum_files │ │ ├── cg001_i01.png │ │ └── cg001_i01_decrypted.png │ ├── test_checksum_adler32.py │ ├── test_checksum_cocos2d_pvr.py │ ├── test_checksum_fletcher16.py │ ├── test_checksum_fletcher32.py │ ├── test_checksum_internet_ipv4_header.py │ ├── test_checksum_sum8.py │ ├── test_checksum_sum8_2s_complement.py │ ├── test_checksum_unix_sum_bsd16.py │ ├── test_checksum_unix_sum_sysv.py │ └── test_checksum_xor8.py ├── tests_common │ └── test_common.py ├── tests_compression │ ├── compression_data │ │ ├── fake_file01_zlib_compressed.bin │ │ ├── fake_file01_zlib_uncompressed.bin │ │ ├── fake_file02_lzo_compressed.bin │ │ ├── fake_file02_lzo_uncompressed.bin │ │ ├── fake_file03_refpack_compressed.bin │ │ └── fake_file03_refpack_uncompressed.bin │ ├── test_compression_jcalg1.py │ ├── test_compression_lzo.py │ ├── test_compression_refpack.py │ └── test_compression_zlib.py ├── tests_crc │ ├── test_crc16_arc.py │ ├── test_crc16_ccitt.py │ ├── test_crc16_dnp.py │ ├── test_crc16_ea_crcf.py │ ├── test_crc16_kermit.py │ ├── test_crc16_modbus.py │ ├── test_crc16_sick.py │ ├── test_crc32_asobo.py │ ├── test_crc32_iso_hdlc.py │ ├── test_crc64_go_iso.py │ ├── test_crc8.py │ ├── test_crc8_cdma_2000.py │ ├── test_crc8_darc.py │ └── test_crc_unix_cksum.py ├── tests_encryption │ ├── test_encryption_rot13.py │ ├── test_encryption_xor_basic.py │ ├── test_encryption_xor_basic_key_guesser.py │ ├── test_encryption_xor_gianas_return_zda.py │ └── test_encryption_xor_retro64_eco.py ├── tests_image │ ├── image_files │ │ ├── compression_packbits.bin │ │ ├── compression_rle_tga.bin │ │ ├── compression_zlib.bin │ │ ├── ea_sample_ARGB8888_256x28.bin │ │ ├── ea_sample_BGRA5551_32x32.bin │ │ ├── ea_sample_PAL4_RGBA8888_256x256.bin │ │ ├── ea_sample_PAL4_RGBA8888_256x256_palette.bin │ │ ├── ea_sample_PAL8_RGBA8888_150x300.bin │ │ ├── ea_sample_PAL8_RGBA8888_150x300_palette.bin │ │ ├── ea_sample_RGBA5551_128x128.bin │ │ ├── ea_sample_RGBT5551_512x256.bin │ │ ├── ea_sample_RGBX4444_240x136.bin │ │ ├── monkey_ABGR4444.bin │ │ ├── monkey_BGR565.bin │ │ ├── monkey_BGR888.bin │ │ ├── monkey_BGRA4444.bin │ │ ├── monkey_BGRA8888.bin │ │ ├── monkey_RGB565.bin │ │ ├── monkey_RGB888.bin │ │ ├── monkey_RGBA8888.bin │ │ ├── monkey_dxt1.bin │ │ ├── monkey_dxt3.bin │ │ ├── monkey_dxt5.bin │ │ ├── pal_i8a8_bgra8888.bin │ │ ├── swizzle_3ds.bin │ │ ├── swizzle_GST121.bin │ │ ├── swizzle_GST121.pal │ │ ├── swizzle_GST122.bin │ │ ├── swizzle_GST122.pal │ │ ├── swizzle_dreamcast_GRAY8.bin │ │ ├── swizzle_gamecube_bluebar.bin │ │ ├── swizzle_morton_monkey.bin │ │ ├── swizzle_ps2_4bit_monkey_data.bin │ │ ├── swizzle_ps2_4bit_monkey_palette.bin │ │ ├── swizzle_ps2_4bit_sample2_512x512_data.bin │ │ ├── swizzle_ps2_4bit_sample2_512x512_pal.bin │ │ ├── swizzle_ps2_8bit_monkey_data.bin │ │ ├── swizzle_ps2_8bit_monkey_palette.bin │ │ ├── swizzle_ps2_8bit_sample1_512_x256_data.bin │ │ ├── swizzle_ps2_8bit_sample1_512_x256_pal.bin │ │ ├── swizzle_ps2_busted_4bpp_256x64.bin │ │ ├── swizzle_ps2_palette.bin │ │ ├── swizzle_ps2_suba_16bit_data.bin │ │ ├── swizzle_ps2_suba_16bit_pal.bin │ │ ├── swizzle_ps2_suba_8bit_data.bin │ │ ├── swizzle_ps2_suba_8bit_pal.bin │ │ ├── swizzle_ps4_DXT5.bin │ │ ├── swizzle_ps4_to_align_780x256.bin │ │ ├── swizzle_ps_vita_font_dmg0.bin │ │ ├── swizzle_ps_vita_monkey_BGRA8888.bin │ │ ├── swizzle_ps_vita_monkey_dxt5.bin │ │ ├── swizzle_ps_vita_tex_all.bin │ │ ├── swizzle_psp_RGBA8888.bin │ │ ├── swizzle_psp_bubbles_data.bin │ │ ├── swizzle_psp_bubbles_palette.bin │ │ ├── swizzle_switch_DXT5.bin │ │ ├── swizzle_switch_GRAY8_320x512.bin │ │ ├── swizzle_wii_u_monkey_sample_type0.bin │ │ ├── swizzle_xbox360_fairy_with_big_boobs.bin │ │ ├── swizzle_xbox360_mt_framework.tex │ │ └── swizzle_xbox360_sample1.bin │ ├── test_common_functions.py │ ├── test_decode_encode_compressed.py │ ├── test_decode_encode_generic.py │ ├── test_decode_encode_indexed.py │ ├── test_decompress_compress_packbits.py │ ├── test_decompress_compress_rle_tga.py │ ├── test_decompress_compress_zlib.py │ ├── test_palette.py │ ├── test_swizzle_3ds.py │ ├── test_swizzle_GST.py │ ├── test_swizzle_dreamcast.py │ ├── test_swizzle_gamecube.py │ ├── test_swizzle_morton.py │ ├── test_swizzle_ps2.py │ ├── test_swizzle_ps2_palette.py │ ├── test_swizzle_ps4.py │ ├── test_swizzle_ps_vita.py │ ├── test_swizzle_psp.py │ ├── test_swizzle_switch.py │ ├── test_swizzle_wii_u.py │ └── test_swizzle_x360.py └── tests_io │ ├── data │ ├── fake_file1.xml │ ├── fake_file2.txt │ ├── fake_file3_le.bin │ ├── fake_file4_be.bin │ ├── fake_file5_multi_endian.bin │ ├── fake_file6_le.bin │ ├── fake_file7_check_size_11_bytes.bin │ ├── fake_file8_translation_after.bin │ ├── fake_file8_translation_after.bin.backup │ ├── fake_file8_translation_after.po │ ├── fake_file8_translation_before.bin │ ├── fake_file8_translation_before.po │ ├── fake_file9_mod_after.bin │ ├── fake_file9_mod_after.bin.backup │ ├── fake_file9_mod_before.bin │ ├── fake_file9_output_after │ │ ├── aaa.txt │ │ ├── bbb │ │ │ └── bbb.txt │ │ └── ccc.txt │ └── fake_file9_output_before │ │ ├── aaa.txt │ │ ├── bbb │ │ └── bbb.txt │ │ └── ccc.txt │ ├── test_bytes_handler.py │ ├── test_bytes_helper_functions.py │ ├── test_bytes_helper_functions_performance.py │ ├── test_check_file.py │ ├── test_check_file_size.py │ ├── test_mod_handler.py │ ├── test_read_file.py │ ├── test_translation_text_handler.py │ └── test_write_file.py └── upload_package.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | extend-ignore = E501, E303, E266, E203 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .mypy_cache/ 3 | .pytest_cache/ 4 | venv/ 5 | .idea/ 6 | dist/ 7 | build/ 8 | *.egg-info/ 9 | log.txt 10 | .coverage 11 | htmlcov/ 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | python: python3.11 3 | 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v5.0.0 7 | hooks: 8 | - id: check-added-large-files 9 | args: ['--maxkb=10000'] 10 | - id: end-of-file-fixer 11 | exclude: (tests_io/data|tests_image/image_files)/.* 12 | - id: trailing-whitespace 13 | exclude: (tests_io/data|tests_image/image_files)/.* 14 | - id: check-docstring-first 15 | - id: mixed-line-ending 16 | exclude: (tests_io/data|tests_image/image_files)/.* 17 | - id: requirements-txt-fixer 18 | - id: check-executables-have-shebangs 19 | - repo: https://github.com/pycqa/isort 20 | rev: 5.13.2 21 | hooks: 22 | - id: isort 23 | args: ["--profile", "black"] 24 | name: isort (python) 25 | - repo: https://github.com/psf/black 26 | rev: 24.10.0 27 | hooks: 28 | - id: black 29 | exclude: (tests_io/data|tests_image/image_files)/.* 30 | - repo: https://github.com/pycqa/flake8 31 | rev: 7.1.1 32 | hooks: 33 | - id: flake8 34 | - repo: https://github.com/pre-commit/mirrors-mypy 35 | rev: v1.13.0 36 | hooks: 37 | - id: mypy 38 | additional_dependencies: [tokenize-rt==6.1.0] 39 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | python_version = 3.11 3 | exclude = venv 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | crc==7.1.0 2 | faker==33.1.0 3 | hashbase==1.1.5 4 | lzokay==1.1.8 5 | matplotlib==3.10.0 6 | mmh3==5.0.1 7 | numpy==2.1.3 8 | Pillow==10.4.0 9 | polib==1.2.0 10 | pre-commit==4.1.0 11 | pytest==8.3.3 12 | pytest-cov==6.0.0 13 | setuptools==75.6.0 14 | twine==5.1.1 15 | types-setuptools==75.6.0.20241126 16 | -------------------------------------------------------------------------------- /reversebox/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/__init__.py -------------------------------------------------------------------------------- /reversebox/checksum/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/checksum/__init__.py -------------------------------------------------------------------------------- /reversebox/checksum/checksum_adler32.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | MOD_ADLER = 65521 7 | 8 | 9 | class Adler32Handler: 10 | def __init__(self): 11 | pass 12 | 13 | def calculate_adler32(self, input_data: bytes) -> int: 14 | a = 1 15 | b = 0 16 | for input_byte in input_data: 17 | a = (a + input_byte) % MOD_ADLER 18 | b = (b + a) % MOD_ADLER 19 | return (b << 16) | a 20 | -------------------------------------------------------------------------------- /reversebox/checksum/checksum_cocos2d_pvr.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import numpy as np 7 | 8 | # implementation of "ZipUtils::checksumPvr" from cocos2d engine 9 | # tested on "cg001_i01.png" file from jp.okakichi.chanran (Android) 10 | 11 | 12 | class Cocos2dPVRChecksumHandler: 13 | def __init__(self): 14 | pass 15 | 16 | def calculate_cocos2d_pvr_checksum( 17 | self, input_data: bytes, data_len: int, endianess: str = "<" 18 | ) -> int: 19 | cs = np.uint32(0) 20 | cslen: int = 128 21 | 22 | data_len = min(data_len, cslen) 23 | 24 | for i in range(data_len): 25 | byte_data = input_data[i * 4 : i * 4 + 4] 26 | int_data = np.frombuffer(byte_data, dtype=f"{endianess}u4")[0] 27 | cs ^= np.uint32(int_data) 28 | 29 | return int(cs) 30 | -------------------------------------------------------------------------------- /reversebox/checksum/checksum_fletcher16.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class Fletcher16Handler: 8 | def __init__(self): 9 | pass 10 | 11 | def calculate_fletcher16(self, input_data: bytes) -> int: 12 | sum1 = 0 13 | sum2 = 0 14 | for input_byte in input_data: 15 | sum1 = (sum1 + input_byte) % 255 16 | sum2 = (sum2 + sum1) % 255 17 | return (sum2 << 8) | sum1 18 | -------------------------------------------------------------------------------- /reversebox/checksum/checksum_fletcher32.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | # fmt: off 7 | 8 | 9 | class Fletcher32Handler: 10 | def __init__(self): 11 | pass 12 | 13 | def calculate_fletcher32(self, input_data: bytes) -> int: 14 | words = (input_data[i:i+2] for i in range(0, len(input_data), 2)) 15 | sum1 = sum2 = 0 16 | for word in words: 17 | sum1 = (sum1 + int.from_bytes(word, "little")) % 0xFFFF 18 | sum2 = (sum2 + sum1) % 0xFFFF 19 | return (sum2 << 16) | sum1 20 | 21 | # fmt: on 22 | -------------------------------------------------------------------------------- /reversebox/checksum/checksum_internet_ipv4_header.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class InternetIPv4headerChecksumHandler: 8 | def __init__(self): 9 | pass 10 | 11 | def calculate_internet_ipv4_header_checksum(self, input_data: bytes) -> int: 12 | checksum = 0 13 | pointer = 0 14 | data_size = len(input_data) 15 | 16 | while data_size > 1: 17 | checksum += input_data[pointer] + (input_data[pointer + 1] << 8) 18 | data_size -= 2 19 | pointer += 2 20 | 21 | if data_size: # when data size is odd 22 | checksum += input_data[pointer] 23 | 24 | checksum = (checksum >> 16) + (checksum & 0xFFFF) 25 | checksum += checksum >> 16 26 | 27 | return (~checksum) & 0xFFFF 28 | -------------------------------------------------------------------------------- /reversebox/checksum/checksum_sum8.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class Sum8Handler: 8 | def __init__(self): 9 | pass 10 | 11 | def calculate_sum8(self, input_data: bytes) -> int: 12 | s = 0 13 | for input_byte in input_data: 14 | s += input_byte & 0xFF 15 | checksum = s & 0xFF 16 | return checksum 17 | -------------------------------------------------------------------------------- /reversebox/checksum/checksum_sum8_2s_complement.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class Sum82sComplementHandler: 8 | def __init__(self): 9 | pass 10 | 11 | def calculate_sum8_2s_complement(self, input_data: bytes) -> int: 12 | s = 0 13 | for input_byte in input_data: 14 | s += input_byte & 0xFF 15 | checksum = (0x100 - s) & 0xFF 16 | return checksum 17 | -------------------------------------------------------------------------------- /reversebox/checksum/checksum_unix_sum_bsd16.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class UnixSumBSD16Handler: 8 | def __init__(self): 9 | pass 10 | 11 | def calculate_unix_sum_bsd16(self, input_data: bytes) -> int: 12 | checksum = 0 13 | for byte in input_data: 14 | checksum = (checksum >> 1) + ((checksum & 1) << 15) 15 | checksum += byte 16 | checksum &= 0xFFFF 17 | return checksum 18 | -------------------------------------------------------------------------------- /reversebox/checksum/checksum_unix_sum_sysv.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | # https://en.wikipedia.org/wiki/Sum_(Unix) 7 | # https://en.wikipedia.org/wiki/SYSV_checksum 8 | 9 | 10 | class UnixSumSysVHandler: 11 | def __init__(self): 12 | pass 13 | 14 | def calculate_unix_sum_sysv(self, input_data: bytes) -> int: 15 | s = 0 16 | for input_byte in input_data: 17 | s += input_byte & 0xFF 18 | r = (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) & 0xFFFFFFFF 19 | checksum = (r & 0xFFFF) + (r >> 16) & 0xFFFFFFFF 20 | return checksum 21 | -------------------------------------------------------------------------------- /reversebox/checksum/checksum_xor8.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class Xor8Handler: 8 | def __init__(self): 9 | pass 10 | 11 | def calculate_xor8(self, input_data: bytes) -> int: 12 | s = 0 13 | for input_byte in input_data: 14 | s ^= input_byte & 0xFF 15 | checksum = s & 0xFF 16 | return checksum 17 | -------------------------------------------------------------------------------- /reversebox/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/common/__init__.py -------------------------------------------------------------------------------- /reversebox/common/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022-2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os.path 7 | from pathlib import Path 8 | 9 | 10 | def convert_int_to_hex_string(input_value: int) -> str: 11 | return "0x%02X" % int(input_value) 12 | 13 | 14 | # e.g. b'\xE5\xFF' --> 'E5 FF' 15 | def convert_bytes_to_hex_string(input_bytes: bytes) -> str: 16 | return " ".join(f"{byte_value:02x}".upper() for byte_value in input_bytes) 17 | 18 | 19 | def convert_hex_string_to_int(input_string: str) -> int: 20 | return int(input_string, 16) 21 | 22 | 23 | def calculate_padding_length(input_length: int, div: int) -> int: 24 | return (div - (input_length % div)) % div 25 | 26 | 27 | def get_length_with_padding(input_length: int, div: int): 28 | padding_length: int = calculate_padding_length(input_length, div) 29 | return input_length + padding_length 30 | 31 | 32 | # e.g. "aaa" and 2 --> "aaa\x00\x00" 33 | def fill_data_with_padding(input_data: bytes, padding_length: int) -> bytes: 34 | for i in range(padding_length): 35 | input_data += b"\x00" 36 | return input_data 37 | 38 | 39 | # e.g. "aaa" and 5 --> "aaa\x00\x00" 40 | def fill_data_with_padding_to_desired_length( 41 | input_data: bytes, desired_padding_length: int 42 | ) -> bytes: 43 | if len(input_data) > desired_padding_length: 44 | raise Exception("Data too big or desired padding too low!") 45 | elif len(input_data) == desired_padding_length: 46 | return input_data 47 | 48 | padding_length: int = desired_padding_length - len(input_data) 49 | return fill_data_with_padding(input_data, padding_length) 50 | 51 | 52 | def convert_bits_str_to_int(input_bits_str: str) -> int: 53 | return int(input_bits_str, 2) 54 | 55 | 56 | def convert_int_to_bool(input_number: int) -> bool: 57 | return bool(input_number) 58 | 59 | 60 | # e.g. "file.dds" --> ".dds" 61 | def get_file_extension(input_filename: str) -> str: 62 | return os.path.splitext(input_filename)[-1] 63 | 64 | 65 | # e.g. "file.dds" --> DDS 66 | def get_file_extension_uppercase(input_filename: str) -> str: 67 | return os.path.splitext(input_filename)[-1].strip(".").upper() 68 | 69 | 70 | def get_dll_path(dll_name: str) -> str: 71 | dll_path: str = str( 72 | Path(__file__).parents[1].resolve().joinpath("libs").joinpath(dll_name) 73 | ) 74 | return dll_path 75 | 76 | 77 | # e.g. 5307904 --> "5.06 MB" 78 | def convert_from_bytes_to_mb_string(bytes_value: int) -> str: 79 | result: float = bytes_value / 1024 / 1024 80 | return str(round(result, 2)) + " MB" 81 | -------------------------------------------------------------------------------- /reversebox/common/logger.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import logging 7 | 8 | 9 | def get_logger(name): 10 | logger = logging.getLogger(name) 11 | 12 | c_handler = logging.StreamHandler() 13 | f_handler = logging.FileHandler("log.txt") 14 | logger.setLevel(logging.DEBUG) 15 | 16 | log_format = ( 17 | "%(asctime)s - %(name)s - line %(lineno)d - %(levelname)s - %(message)s" 18 | ) 19 | datetime_format = "%Y-%m-%d %H:%M:%S" 20 | c_format = logging.Formatter(log_format, datetime_format) 21 | f_format = logging.Formatter(log_format, datetime_format) 22 | c_handler.setFormatter(c_format) 23 | f_handler.setFormatter(f_format) 24 | 25 | logger.addHandler(c_handler) 26 | logger.addHandler(f_handler) 27 | 28 | return logger 29 | -------------------------------------------------------------------------------- /reversebox/common/numpy_helper_functions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | def string_to_byte_array(input_string: str) -> np.ndarray: 10 | return np.array([ord(c) for c in input_string], dtype=np.uint8) 11 | 12 | 13 | def bytes_to_byte_array(input_bytes: bytes) -> np.ndarray: 14 | return np.array([b for b in input_bytes], dtype=np.uint8) 15 | -------------------------------------------------------------------------------- /reversebox/compression/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/compression/__init__.py -------------------------------------------------------------------------------- /reversebox/compression/compression_jcalg1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class JCALG1Handler: 8 | def compress_data(self, input_data: bytes) -> bytes: 9 | # jcalg1_dll_path = str(Path(__file__).parents[1].resolve().joinpath("libs").joinpath("JCALG1.dll")) 10 | # jcalg1_dll_file = ctypes.cdll.LoadLibrary(jcalg1_dll_path) 11 | 12 | # TODO - JCALG1.dll is 32-bit, so it needs to be recompiled to 64-bit first or completely rewritten 13 | 14 | return b"123" 15 | -------------------------------------------------------------------------------- /reversebox/compression/compression_lzo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import lzokay 7 | 8 | 9 | class LZOHandler: 10 | def compress_data(self, input_data: bytes) -> bytes: 11 | return lzokay.compress(input_data) 12 | 13 | def decompress_data(self, compressed_data: bytes) -> bytes: 14 | return lzokay.decompress(compressed_data) 15 | -------------------------------------------------------------------------------- /reversebox/compression/compression_mio0.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from typing import Literal 7 | 8 | # Mio0 compression 9 | # used in N64 games 10 | # https://hack64.net/wiki/doku.php?id=super_mario_64:mio0 11 | 12 | 13 | class Mio0Handler: 14 | HEADER_SIZE = 0x10 15 | 16 | def compress_data(self, input_data: bytes) -> bytes: 17 | return input_data # TODO - implement this 18 | 19 | def read_layout_bit(self, data, bit_idx): 20 | byte_index = bit_idx // 8 21 | bit_mod_offset = bit_idx % 8 22 | return data[self.HEADER_SIZE + byte_index] & (1 << (7 - bit_mod_offset)) 23 | 24 | def decompress_data( 25 | self, compressed_data: bytes, endianess: Literal["little", "big"] = "big" 26 | ) -> bytes: 27 | signature: str = str(compressed_data[0x0:0x4], "ascii") 28 | 29 | if signature != "MIO0": 30 | raise ValueError('Invalid header, not starting with "MIO0"') 31 | 32 | decompressed_data_size = int.from_bytes(compressed_data[4:8], endianess) 33 | compressed_data_offset = int.from_bytes(compressed_data[8:12], endianess) 34 | uncompressed_data_offset = int.from_bytes(compressed_data[12:16], endianess) 35 | 36 | output_byte_array = bytearray() 37 | output_index = 0 38 | layout_bit_index = 0 39 | 40 | ci = 0 41 | ui = 0 42 | 43 | while output_index < decompressed_data_size: 44 | layout_bit = self.read_layout_bit(compressed_data, layout_bit_index) 45 | is_uncompressed = layout_bit > 0 46 | layout_bit_index += 1 47 | 48 | if output_index >= decompressed_data_size: 49 | break 50 | if is_uncompressed: 51 | output_byte_array.append(compressed_data[uncompressed_data_offset + ui]) 52 | ui += 1 53 | output_index += 1 54 | else: 55 | len_idx_bytes = compressed_data[ 56 | compressed_data_offset + ci : compressed_data_offset + ci + 2 57 | ] 58 | ci += 2 59 | 60 | length = ((len_idx_bytes[0] & 0xF0) >> 4) + 3 61 | index = ((len_idx_bytes[0] & 0xF) << 8) + (len_idx_bytes[1] + 1) 62 | 63 | if length < 3 or length > 18: 64 | raise Exception(f"wrong length value: {length}") 65 | 66 | if index < 1 or index > 4096: 67 | raise Exception(f"wrong index value: {index}") 68 | 69 | for i in range(length): 70 | output_byte_array.append(output_byte_array[output_index - index]) 71 | output_index += 1 72 | 73 | return bytes(output_byte_array) 74 | -------------------------------------------------------------------------------- /reversebox/compression/compression_re_tiyoruga_dat.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | # Compression used in "Data1.dat" archive file 7 | # from game "Re:Tiyoruga" 8 | # https://doujinstyle.com/?p=page&type=2&id=100 9 | 10 | 11 | # fmt: off 12 | # mypy: ignore-errors 13 | 14 | def decompress_block(input_data: bytes, data_offset: int) -> tuple: 15 | compression_flag: int = input_data[data_offset] 16 | decompressed_block: bytearray = bytearray() 17 | process_block: bytes = bytearray(64) 18 | start_data_offset: int = data_offset 19 | data_offset += 1 20 | 21 | if compression_flag == 1: 22 | 23 | # initialize process block 24 | process_offset: int = 0 25 | for i in range(8): 26 | for x in range(8): 27 | processed_value: int = int(((1 << x) & input_data[data_offset+i]) != 0) 28 | process_block[process_offset] = processed_value 29 | process_offset += 1 30 | 31 | data_offset += 8 32 | 33 | # decompress block data 34 | for j in range(64): 35 | if process_block[j]: 36 | for k in range(16): 37 | decompressed_block.extend(b'\x00') 38 | else: 39 | for m in range(16): 40 | decompressed_block.append(input_data[data_offset]) 41 | data_offset += 1 42 | 43 | elif compression_flag == 0: 44 | decompressed_block.extend(input_data[data_offset:data_offset + 1024]) 45 | data_offset += 1024 46 | else: 47 | raise Exception("Not supported compression flag!") 48 | 49 | processed_data_size: int = data_offset - start_data_offset 50 | return decompressed_block, processed_data_size 51 | 52 | 53 | def decompress_data(input_data: bytes, uncompressed_data_size: int) -> bytes: 54 | output_decompressed_data: bytearray = bytearray() 55 | input_data_offset: int = 0 56 | 57 | while input_data_offset < len(input_data): 58 | decompressed_data, processed_bytes_count = decompress_block(input_data, input_data_offset) 59 | output_decompressed_data.extend(decompressed_data) 60 | input_data_offset += processed_bytes_count 61 | 62 | if len(output_decompressed_data) > uncompressed_data_size: 63 | return output_decompressed_data[:uncompressed_data_size] 64 | else: 65 | return output_decompressed_data 66 | -------------------------------------------------------------------------------- /reversebox/compression/compression_zlib.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import zlib 7 | 8 | 9 | class ZLIBHandler: 10 | def compress_data(self, input_data: bytes) -> bytes: 11 | return zlib.compress(input_data) 12 | 13 | def decompress_data(self, compressed_data: bytes) -> bytes: 14 | return zlib.decompress(compressed_data) 15 | -------------------------------------------------------------------------------- /reversebox/crc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/crc/__init__.py -------------------------------------------------------------------------------- /reversebox/crc/crc16_arc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | CRC_START_16 = 0x0000 7 | CRC_POLY_16 = 0xA001 8 | 9 | 10 | class CRC16Handler: 11 | def __init__(self): 12 | self.crc16_tab_calculated: bool = False 13 | self.crc16_tab = [] 14 | 15 | def calculate_crc16(self, input_data: bytes) -> int: 16 | crc: int = CRC_START_16 17 | 18 | if not self.crc16_tab_calculated: 19 | self.init_crc16_tab() 20 | 21 | for byte in input_data: 22 | short_c = 0x00FF & byte 23 | tmp = crc ^ short_c 24 | crc = (crc >> 8) ^ self.crc16_tab[tmp & 0xFF] 25 | 26 | return crc & 0xFFFF 27 | 28 | def init_crc16_tab(self): 29 | for i in range(256): 30 | crc = 0 31 | c = i 32 | for j in range(8): 33 | if (crc ^ c) & 0x0001: 34 | crc = (crc >> 1) ^ CRC_POLY_16 35 | else: 36 | crc = crc >> 1 37 | 38 | c = c >> 1 39 | 40 | self.crc16_tab.append(crc) 41 | 42 | self.crc16_tab_calculated = True 43 | -------------------------------------------------------------------------------- /reversebox/crc/crc16_ccitt.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | CRC_POLY_CCITT = 0x1021 7 | 8 | CRC_START_CCITT_FFFF = 0xFFFF 9 | CRC_START_CCITT_1D0F = 0x1D0F 10 | CRC_START_CCITT_XMODEM = 0x0000 11 | 12 | 13 | class CRC16CCITTHandler: 14 | def __init__(self): 15 | self.crc16_ccitt_calculated: bool = False 16 | self.crc16_ccitt_tab = [] 17 | pass 18 | 19 | def calculate_crc16_ccitt_ffff(self, input_data: bytes) -> int: 20 | return self.calculate_crc16_ccitt_basic(input_data, CRC_START_CCITT_FFFF) 21 | 22 | def calculate_crc16_ccitt_1d0f(self, input_data: bytes) -> int: 23 | return self.calculate_crc16_ccitt_basic(input_data, CRC_START_CCITT_1D0F) 24 | 25 | def calculate_crc16_ccitt_xmodem(self, input_data: bytes) -> int: 26 | return self.calculate_crc16_ccitt_basic(input_data, CRC_START_CCITT_XMODEM) 27 | 28 | def calculate_crc16_ccitt_basic(self, input_data: bytes, crc_start: int) -> int: 29 | crc: int = crc_start 30 | 31 | if not self.crc16_ccitt_calculated: 32 | self.init_crc16_ccitt_tab() 33 | 34 | for byte in input_data: 35 | short_c = 0x00FF & byte 36 | tmp = (crc >> 8) ^ short_c 37 | crc = (crc << 8) ^ self.crc16_ccitt_tab[tmp & 0xFF] 38 | 39 | return crc & 0xFFFF 40 | 41 | def init_crc16_ccitt_tab(self): 42 | for i in range(256): 43 | crc = 0 44 | c = i << 8 45 | for j in range(8): 46 | if (crc ^ c) & 0x8000: 47 | crc = (crc << 1) ^ CRC_POLY_CCITT 48 | else: 49 | crc = crc << 1 50 | 51 | c = c << 1 52 | 53 | self.crc16_ccitt_tab.append(crc) 54 | 55 | self.crc16_ccitt_calculated = True 56 | -------------------------------------------------------------------------------- /reversebox/crc/crc16_dnp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | CRC_START_DNP = 0x0000 7 | CRC_POLY_DNP = 0xA6BC 8 | 9 | 10 | class CRC16DNPHandler: 11 | def __init__(self): 12 | self.crc16_dnp_tab_calculated: bool = False 13 | self.crc16_dnp_tab = [] 14 | 15 | def calculate_crc16_dnp(self, input_data: bytes) -> int: 16 | crc: int = CRC_START_DNP 17 | 18 | if not self.crc16_dnp_tab_calculated: 19 | self.init_crc16_dnp_tab() 20 | 21 | for byte in input_data: 22 | short_c = 0x00FF & byte 23 | tmp = crc ^ short_c 24 | crc = (crc >> 8) ^ self.crc16_dnp_tab[tmp & 0xFF] 25 | 26 | crc = ~crc 27 | low_byte = (crc & 0xFF00) >> 8 28 | high_byte = (crc & 0x00FF) << 8 29 | crc = low_byte | high_byte 30 | return crc 31 | 32 | def init_crc16_dnp_tab(self): 33 | for i in range(256): 34 | crc = 0 35 | c = i 36 | for j in range(8): 37 | if (crc ^ c) & 0x0001: 38 | crc = (crc >> 1) ^ CRC_POLY_DNP 39 | else: 40 | crc = crc >> 1 41 | 42 | c = c >> 1 43 | 44 | self.crc16_dnp_tab.append(crc) 45 | 46 | self.crc16_dnp_tab_calculated = True 47 | -------------------------------------------------------------------------------- /reversebox/crc/crc16_kermit.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | CRC_START_KERMIT = 0x0000 7 | CRC_POLY_KERMIT = 0x8408 8 | 9 | 10 | class CRC16KermitHandler: 11 | def __init__(self): 12 | self.crc16_kermit_tab_calculated: bool = False 13 | self.crc16_kermit_tab = [] 14 | 15 | def calculate_crc16_kermit(self, input_data: bytes) -> int: 16 | crc: int = CRC_START_KERMIT 17 | 18 | if not self.crc16_kermit_tab_calculated: 19 | self.init_crc16_kermit_tab() 20 | 21 | for byte in input_data: 22 | short_c = 0x00FF & byte 23 | tmp = crc ^ short_c 24 | crc = (crc >> 8) ^ self.crc16_kermit_tab[tmp & 0xFF] 25 | 26 | low_byte = (crc & 0xFF00) >> 8 27 | high_byte = (crc & 0x00FF) << 8 28 | crc = low_byte | high_byte 29 | return crc 30 | 31 | def init_crc16_kermit_tab(self): 32 | for i in range(256): 33 | crc = 0 34 | c = i 35 | for j in range(8): 36 | if (crc ^ c) & 0x0001: 37 | crc = (crc >> 1) ^ CRC_POLY_KERMIT 38 | else: 39 | crc = crc >> 1 40 | 41 | c = c >> 1 42 | 43 | self.crc16_kermit_tab.append(crc) 44 | 45 | self.crc16_kermit_tab_calculated = True 46 | -------------------------------------------------------------------------------- /reversebox/crc/crc16_modbus.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | CRC_START_MODBUS = 0xFFFF 7 | CRC_POLY_MODBUS = 0xA001 8 | 9 | 10 | class CRC16MODBUSHandler: 11 | def __init__(self): 12 | self.crc16_modbus_tab_calculated: bool = False 13 | self.crc16_modbus_tab = [] 14 | 15 | def calculate_crc16_modbus(self, input_data: bytes) -> int: 16 | crc: int = CRC_START_MODBUS 17 | 18 | if not self.crc16_modbus_tab_calculated: 19 | self.init_crc16_modbus_tab() 20 | 21 | for byte in input_data: 22 | short_c = 0x00FF & byte 23 | tmp = crc ^ short_c 24 | crc = (crc >> 8) ^ self.crc16_modbus_tab[tmp & 0xFF] 25 | 26 | return crc & 0xFFFF 27 | 28 | def init_crc16_modbus_tab(self): 29 | for i in range(256): 30 | crc = 0 31 | c = i 32 | for j in range(8): 33 | if (crc ^ c) & 0x0001: 34 | crc = (crc >> 1) ^ CRC_POLY_MODBUS 35 | else: 36 | crc = crc >> 1 37 | 38 | c = c >> 1 39 | 40 | self.crc16_modbus_tab.append(crc) 41 | 42 | self.crc16_modbus_tab_calculated = True 43 | -------------------------------------------------------------------------------- /reversebox/crc/crc16_sick.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | CRC_START_SICK = 0x0000 7 | CRC_POLY_SICK = 0x8005 8 | 9 | 10 | class CRC16SICKHandler: 11 | def __init__(self): 12 | pass 13 | 14 | def calculate_crc16_sick(self, input_data: bytes) -> int: 15 | crc: int = CRC_START_SICK 16 | short_p = 0 17 | 18 | for byte in input_data: 19 | short_c = 0x00FF & byte 20 | 21 | if crc & 0x8000: 22 | crc = (crc << 1) ^ CRC_POLY_SICK 23 | else: 24 | crc = crc << 1 25 | 26 | crc ^= short_c | short_p 27 | short_p = short_c << 8 28 | 29 | low_byte = (crc & 0xFF00) >> 8 30 | high_byte = (crc & 0x00FF) << 8 31 | crc = low_byte | high_byte 32 | 33 | return crc 34 | -------------------------------------------------------------------------------- /reversebox/crc/crc32_iso_hdlc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | CRC_START_32 = 0xFFFFFFFF 7 | CRC_POLY_32 = 0xEDB88320 8 | 9 | 10 | class CRC32Handler: 11 | def __init__(self): 12 | self.crc32_tab_calculated: bool = False 13 | self.crc32_tab = [] 14 | 15 | def calculate_crc32(self, input_data: bytes) -> int: 16 | crc: int = CRC_START_32 17 | 18 | if not self.crc32_tab_calculated: 19 | self.init_crc32_tab() 20 | 21 | for byte in input_data: 22 | long_c = 0x000000FF & byte 23 | tmp = crc ^ long_c 24 | crc = (crc >> 8) ^ self.crc32_tab[tmp & 0xFF] 25 | 26 | crc ^= 0xFFFFFFFF 27 | return crc & 0xFFFFFFFF 28 | 29 | def init_crc32_tab(self): 30 | for i in range(256): 31 | crc = i 32 | for j in range(8): 33 | if crc & 0x00000001: 34 | crc = (crc >> 1) ^ CRC_POLY_32 35 | else: 36 | crc = crc >> 1 37 | 38 | self.crc32_tab.append(crc) 39 | 40 | self.crc32_tab_calculated = True 41 | -------------------------------------------------------------------------------- /reversebox/crc/crc64_go_iso.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from crc import Calculator, Configuration 7 | 8 | 9 | class CRC64GoISOHandler: 10 | def __init__(self): 11 | pass 12 | 13 | def calculate_crc64(self, input_data: bytes) -> int: 14 | config = Configuration( 15 | width=64, 16 | polynomial=0x000000000000001B, 17 | init_value=0xFFFFFFFFFFFFFFFF, 18 | final_xor_value=0xFFFFFFFFFFFFFFFF, 19 | reverse_input=True, 20 | reverse_output=True, 21 | ) 22 | calculator = Calculator(config) 23 | return calculator.checksum(input_data) 24 | -------------------------------------------------------------------------------- /reversebox/crc/crc8.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from crc import Calculator, Configuration 7 | 8 | CRC_START_8 = 0x00 9 | CRC_POLY_8 = 0x07 10 | 11 | CRC_START_8_CDMA_2000 = 0xFF 12 | CRC_POLY_8_CDMA_2000 = 0x9B 13 | 14 | 15 | class CRC8Handler: 16 | def __init__(self): 17 | pass 18 | 19 | def calculate_crc8(self, input_data: bytes) -> int: 20 | return self.calculate_crc8_base(input_data, CRC_START_8, CRC_POLY_8) 21 | 22 | def calculate_crc8_cdma_2000(self, input_data: bytes) -> int: 23 | return self.calculate_crc8_base( 24 | input_data, CRC_START_8_CDMA_2000, CRC_POLY_8_CDMA_2000 25 | ) 26 | 27 | def calculate_crc8_darc(self, input_data: bytes) -> int: 28 | config = Configuration( 29 | width=8, 30 | polynomial=0x39, 31 | init_value=0x00, 32 | final_xor_value=0x00, 33 | reverse_input=True, 34 | reverse_output=True, 35 | ) 36 | calculator = Calculator(config) 37 | return calculator.checksum(input_data) 38 | 39 | def calculate_crc8_base( 40 | self, input_data: bytes, crc_start: int, crc_poly: int 41 | ) -> int: 42 | crc = crc_start 43 | for byte in input_data: 44 | crc ^= byte 45 | for _ in range(8): 46 | if crc & 0x80: 47 | crc = (crc << 1) ^ crc_poly 48 | else: 49 | crc <<= 1 50 | crc &= 0xFF 51 | return crc 52 | -------------------------------------------------------------------------------- /reversebox/encryption/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/encryption/__init__.py -------------------------------------------------------------------------------- /reversebox/encryption/encryption_hatch_engine.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from reversebox.crc.crc32_iso_hdlc import CRC32Handler 7 | from reversebox.io_files.bytes_helper_functions import set_uint32, set_uint64 8 | 9 | # Encryption algorithm used in Hatch Engine games 10 | # e.g. Data.hatch archive from "Sonic Galactic" (Demo 2) game 11 | 12 | 13 | def get_hash_for_filename(filename: str) -> int: 14 | crc_handler = CRC32Handler() 15 | filename_hash: int = crc_handler.calculate_crc32(filename.encode("ascii")) 16 | return filename_hash 17 | 18 | 19 | def decrypt_hatch_data(f_data: bytes, filename_hash: int, f_size: int) -> bytes: 20 | crc_handler = CRC32Handler() 21 | keyA: bytearray = bytearray(16) 22 | keyB: bytearray = bytearray(16) 23 | decrypted_data: bytearray = bytearray(f_data) 24 | 25 | encoded_filename_hash: bytes = set_uint32(filename_hash, "<") 26 | encoded_size_value: bytes = set_uint64(f_size, "<") 27 | size_hash: int = crc_handler.calculate_crc32(encoded_size_value) 28 | encoded_size_hash: bytes = set_uint32(size_hash, "<") 29 | 30 | for i in range(4): 31 | keyA[i * 4 : (i + 1) * 4] = encoded_filename_hash 32 | 33 | for i in range(4): 34 | keyB[i * 4 : (i + 1) * 4] = encoded_size_hash 35 | 36 | swap_nibbles: int = 0 37 | index_keyA: int = 0 38 | index_keyB: int = 8 39 | 40 | xor_value = (f_size >> 2) & 0x7F 41 | 42 | for x in range(f_size): 43 | temp = decrypted_data[x] 44 | 45 | temp ^= xor_value ^ keyB[index_keyB] 46 | index_keyB += 1 47 | 48 | if swap_nibbles: 49 | temp = ((temp & 0x0F) << 4) | ((temp & 0xF0) >> 4) 50 | 51 | temp ^= keyA[index_keyA] 52 | index_keyA += 1 53 | 54 | decrypted_data[x] = temp 55 | 56 | if index_keyA <= 15: 57 | if index_keyB > 12: 58 | index_keyB = 0 59 | swap_nibbles ^= 1 60 | elif index_keyB <= 8: 61 | index_keyA = 0 62 | swap_nibbles ^= 1 63 | else: 64 | xor_value = (xor_value + 2) & 0x7F 65 | if swap_nibbles: 66 | swap_nibbles = 0 67 | index_keyA = xor_value % 7 68 | index_keyB = (xor_value % 12) + 2 69 | else: 70 | swap_nibbles = 1 71 | index_keyA = (xor_value % 12) + 3 72 | index_keyB = xor_value % 7 73 | 74 | return decrypted_data 75 | -------------------------------------------------------------------------------- /reversebox/encryption/encryption_rot13.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import struct 7 | 8 | 9 | def rot13_cipher_encrypt(input_data: bytes, key: bytes) -> bytes: 10 | if len(input_data) == 0: 11 | return b"" 12 | if len(key) == 0: 13 | return input_data 14 | 15 | result: bytes = b"" 16 | key_count: int = 0 17 | for input_byte in input_data: 18 | if key_count >= len(key): 19 | key_count = 0 20 | 21 | key_char = key[key_count] 22 | key_count += 1 23 | rot_result = struct.pack("B", (key_char + input_byte) & 0xFF) 24 | result += rot_result 25 | 26 | return result 27 | 28 | 29 | def rot13_cipher_decrypt(input_data: bytes, key: bytes) -> bytes: 30 | if len(input_data) == 0: 31 | return b"" 32 | if len(key) == 0: 33 | return input_data 34 | 35 | result: bytes = b"" 36 | key_count: int = 0 37 | for input_byte in input_data: 38 | if key_count >= len(key): 39 | key_count = 0 40 | 41 | key_char = key[key_count] 42 | key_count += 1 43 | rot_result = struct.pack("B", (input_byte - key_char) & 0xFF) 44 | result += rot_result 45 | 46 | return result 47 | -------------------------------------------------------------------------------- /reversebox/encryption/encryption_xor_basic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022-2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | def xor_cipher_basic(input_data: bytes, key: bytes) -> bytes: 8 | if len(input_data) == 0: 9 | return b"" 10 | if len(key) == 0: 11 | return input_data 12 | 13 | result: bytearray = bytearray(len(input_data)) 14 | key_count: int = 0 15 | data_count: int = 0 16 | for input_byte in input_data: 17 | if key_count >= len(key): 18 | key_count = 0 19 | 20 | key_char = key[key_count] 21 | key_count += 1 22 | result[data_count] = key_char ^ input_byte 23 | data_count += 1 24 | 25 | return result 26 | -------------------------------------------------------------------------------- /reversebox/encryption/encryption_xor_basic_key_guesser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | # Ver Date Author Comment 7 | # v0.1 13.06.2020 Bartlomiej Duda Initial version 8 | # v0.2 26.11.2020 Bartlomiej Duda Added "expected result" and "test xor" 9 | # v0.3 25.09.2021 Bartlomiej Duda Added multibyte xor option 10 | # v0.4 17.03.2024 Bartlomiej Duda Rewritten for ReverseBox 11 | 12 | 13 | import struct 14 | from typing import Optional 15 | 16 | from reversebox.encryption.encryption_xor_basic import xor_cipher_basic 17 | 18 | 19 | def xor_basic_guess_key( 20 | encrypted_xor_data: bytes, decrypted_xor_data: bytes, max_xor_key_length_length: int 21 | ) -> Optional[bytes]: 22 | """ 23 | Function for guessing XOR keys 24 | """ 25 | xor_key = b"\x00" 26 | 27 | xor_range = 255**max_xor_key_length_length 28 | 29 | for i in range(xor_range): 30 | # xoring data 31 | xor_res = xor_cipher_basic(encrypted_xor_data, xor_key) 32 | if xor_res == decrypted_xor_data: 33 | return xor_key # XOR key has been found! 34 | 35 | # increment key 36 | i_xor_key: int = int.from_bytes(xor_key, "little") 37 | i_xor_key += 1 38 | if i_xor_key <= 255: 39 | xor_key = struct.pack("B", i_xor_key) 40 | elif i_xor_key > 255 and i_xor_key <= 255**2: 41 | xor_key = struct.pack(" 255**2 and i_xor_key <= 255**4: 43 | xor_key = struct.pack(" bytes: 16 | xor_res = b"\xBB" 17 | data_size = len(input_data) 18 | out_data = bytearray() 19 | 20 | for curr_offset in range(data_size): 21 | data_byte = struct.pack("B", input_data[curr_offset]) 22 | xor_res = bytes(a ^ b for a, b in zip(xor_res, cycle(data_byte))) 23 | out_data.extend(xor_res) 24 | return out_data 25 | 26 | 27 | def xor_zda_encrypt_data(input_data: bytes) -> bytes: 28 | data_size = len(input_data) 29 | out_data = bytearray() 30 | key_data: bytes = b"\xBB" + input_data 31 | 32 | for curr_offset in range(data_size): 33 | xor_res = bytes( 34 | a ^ b 35 | for a, b in zip( 36 | struct.pack("B", input_data[curr_offset]), 37 | struct.pack("B", key_data[curr_offset]), 38 | ) 39 | ) 40 | out_data.extend(xor_res) 41 | return out_data 42 | -------------------------------------------------------------------------------- /reversebox/encryption/encryption_xor_retro64_eco.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import struct 7 | 8 | # XOR Cipher used in Retro64 *.ECO files 9 | # Read more here: http://wiki.xentax.com/index.php/Retro64_ECO 10 | 11 | 12 | def xor_cipher_retro64_eco(input_data: bytes, key: int) -> bytes: 13 | if len(input_data) == 0: 14 | return b"" 15 | 16 | result: bytes = b"" 17 | for raw_byte in input_data: 18 | new_key = (201 * key + 11) % 0x7FFF 19 | key = new_key 20 | decrypted_byte = raw_byte ^ (new_key % 0xFF) 21 | result += struct.pack("B", decrypted_byte) 22 | 23 | return result 24 | -------------------------------------------------------------------------------- /reversebox/hash/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/hash/__init__.py -------------------------------------------------------------------------------- /reversebox/hash/hash_additive.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | # Additive Hash 10 | # https://www.burtleburtle.net/bob/hash/doobs.html 11 | class AdditiveHashHandler: 12 | def __init__(self): 13 | pass 14 | 15 | @staticmethod 16 | def calculate_additive_hash_from_string(input_string: str, prime: int) -> int: 17 | hash_value = np.uint32(len(input_string)) 18 | for i in range(len(input_string)): 19 | hash_value += np.uint8(ord(input_string[i])) 20 | return int(np.uint32(hash_value) % np.uint32(prime)) 21 | 22 | @staticmethod 23 | def calculate_additive_hash_from_bytes(input_bytes: bytes, prime: int) -> int: 24 | hash_value = np.uint32(len(input_bytes)) 25 | for i in range(len(input_bytes)): 26 | hash_value += np.uint8(input_bytes[i]) 27 | return int(np.uint32(hash_value) % np.uint32(prime)) 28 | -------------------------------------------------------------------------------- /reversebox/hash/hash_ap.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class APHandler: 8 | def __init__(self): 9 | pass 10 | 11 | @staticmethod 12 | def calculate_ap_hash_from_string(input_string: str) -> int: 13 | hash_value = 0xAAAAAAAA 14 | i = 0 15 | for char in input_string: 16 | if i & 1 == 0: 17 | hash_value ^= (hash_value << 7) ^ ord(char) * (hash_value >> 3) 18 | else: 19 | hash_value ^= ~((hash_value << 11) + ord(char) ^ (hash_value >> 5)) 20 | i += 1 21 | return hash_value & 0xFFFFFFFF 22 | 23 | @staticmethod 24 | def calculate_ap_hash_from_bytes(input_bytes: bytes) -> int: 25 | hash_value = 0xAAAAAAAA 26 | i = 0 27 | for char in input_bytes: 28 | if i & 1 == 0: 29 | hash_value ^= (hash_value << 7) ^ char * (hash_value >> 3) 30 | else: 31 | hash_value ^= ~((hash_value << 11) + char ^ (hash_value >> 5)) 32 | i += 1 33 | return hash_value & 0xFFFFFFFF 34 | -------------------------------------------------------------------------------- /reversebox/hash/hash_djb2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | # Bernstein's DJB2 hash 8 | # used in EA BIG VIV EB archives to calculate hash from file paths 9 | class DJB2Handler: 10 | def __init__(self): 11 | pass 12 | 13 | @staticmethod 14 | def calculate_djb2_hash_from_string(input_string: str) -> int: 15 | h = 5381 16 | m = 0xFFFFFFFFFFFFFFFF 17 | for c in input_string: 18 | h = (((h * 33) & m) + ord(c)) & m 19 | return h & m 20 | 21 | @staticmethod 22 | def calculate_djb2_hash_from_bytes(input_bytes: bytes) -> int: 23 | h = 5381 24 | m = 0xFFFFFFFFFFFFFFFF 25 | for b in input_bytes: 26 | h = (((h * 33) & m) + b) & m 27 | return h & m 28 | -------------------------------------------------------------------------------- /reversebox/hash/hash_fnv.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import struct 7 | import sys 8 | 9 | FNV_32_PRIME: int = 0x01000193 10 | FNV_64_PRIME: int = 0x100000001B3 11 | 12 | FNV0_32_INIT: int = 0 13 | FNV0_64_INIT: int = 0 14 | FNV1_32_INIT: int = 0x811C9DC5 15 | FNV1_32A_INIT: int = FNV1_32_INIT 16 | FNV1_64_INIT: int = 0xCBF29CE484222325 17 | FNV1_64A_INIT: int = FNV1_64_INIT 18 | 19 | 20 | if sys.version_info[0] == 3: 21 | _get_byte = lambda c: c # noqa: E731 22 | else: 23 | _get_byte = ord 24 | 25 | 26 | class FNVHashHandler: 27 | def __init__(self): 28 | pass 29 | 30 | def fnv1(self, data: bytes, hval_init, fnv_prime, fnv_size) -> int: 31 | hash_value: int = hval_init 32 | for byte in data: 33 | hash_value = (hash_value * fnv_prime) % fnv_size 34 | hash_value = hash_value ^ _get_byte(byte) 35 | return hash_value 36 | 37 | def fnva(self, data: bytes, hval_init, fnv_prime, fnv_size) -> int: 38 | hash_value: int = hval_init 39 | for byte in data: 40 | hash_value = hash_value ^ _get_byte(byte) 41 | hash_value = (hash_value * fnv_prime) % fnv_size 42 | return hash_value 43 | 44 | def fnv0_32(self, data: bytes, hval_init=FNV0_32_INIT) -> bytes: 45 | return struct.pack(" bytes: 48 | return struct.pack(" bytes: 53 | return struct.pack(" bytes: 56 | return struct.pack(" bytes: 59 | return struct.pack(" bytes: 62 | return struct.pack(" int: 21 | byte_array = string_to_byte_array(input_string) 22 | h = np.uint32(0) 23 | for byte in byte_array: 24 | h += np.uint8(byte) 25 | h += np.uint32(h) << np.uint8(10) 26 | h ^= np.uint32(h) >> np.uint8(6) 27 | h += np.uint32(h) << np.uint8(3) 28 | h ^= np.uint32(h) >> np.uint8(11) 29 | h += np.uint32(h) << np.uint8(15) 30 | return int(h) 31 | 32 | @staticmethod 33 | def calculate_jenkins_one_at_a_time_hash_from_bytes(input_bytes: bytes) -> int: 34 | byte_array = bytes_to_byte_array(input_bytes) 35 | h = np.uint32(0) 36 | for byte in byte_array: 37 | h += np.uint8(byte) 38 | h += np.uint32(h) << np.uint8(10) 39 | h ^= np.uint32(h) >> np.uint8(6) 40 | h += np.uint32(h) << np.uint8(3) 41 | h ^= np.uint32(h) >> np.uint8(11) 42 | h += np.uint32(h) << np.uint8(15) 43 | return int(h) 44 | -------------------------------------------------------------------------------- /reversebox/hash/hash_md2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from hashbase import MD2 7 | 8 | 9 | class MD2Handler: 10 | def __init__(self): 11 | pass 12 | 13 | @staticmethod 14 | def calculate_md2_hash(input_data: bytes, encoding_type: str = "utf8") -> bytes: 15 | return bytes.fromhex( 16 | MD2().generate_hash(input_data.decode(encoding=encoding_type)) 17 | ) 18 | -------------------------------------------------------------------------------- /reversebox/hash/hash_md5.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import hashlib 7 | 8 | 9 | class MD5Handler: 10 | def __init__(self): 11 | pass 12 | 13 | @staticmethod 14 | def calculate_md5_hash(input_data: bytes) -> bytes: 15 | return hashlib.md5(input_data).digest() 16 | -------------------------------------------------------------------------------- /reversebox/hash/hash_murmur3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import mmh3 7 | 8 | 9 | class Murmur3Handler: 10 | def __init__(self): 11 | pass 12 | 13 | @staticmethod 14 | def calculate_murmur3_hash_from_string(key: str, seed: int = 0xDEADBEEF) -> int: 15 | return mmh3.hash(key=key, seed=seed, signed=False) 16 | 17 | @staticmethod 18 | def calculate_murmur3_hash_from_bytes(key: bytes, seed: int = 0xDEADBEEF) -> int: 19 | return mmh3.hash(key=key, seed=seed, signed=False) 20 | -------------------------------------------------------------------------------- /reversebox/hash/hash_pivotal_games_dat.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import numpy as np 7 | 8 | # Pivotal Games DAT hash 9 | # Used for hashing filenames in DAT archives in games like: 10 | # - Conflict: Desert Storm 11 | # - Conflict: Desert Storm II 12 | # - Conflict: Vietnam 13 | # - Conflict: Global Terror 14 | # - Conflict: Denied Ops 15 | # - The Great Escape 16 | 17 | 18 | class PivotalGamesDATHashHandler: 19 | def __init__(self): 20 | pass 21 | 22 | @staticmethod 23 | def calculate_pivotal_games_dat_hash_from_string(input_string: str) -> int: 24 | j = 0 25 | dw_hash = np.int32(1) 26 | b_counter = np.uint8(1) 27 | dw_blocks = 8 * len(input_string) 28 | 29 | if dw_blocks > 0: 30 | for i in range(dw_blocks): 31 | A = (dw_hash & 0x200000) != 0 32 | B = (dw_hash & 2) != 0 33 | C = (dw_hash & 1) != 0 34 | D = dw_hash < 0 35 | 36 | dw_hash *= 2 37 | 38 | X = (ord(input_string[j]) & b_counter) != 0 39 | 40 | if D ^ (A ^ B ^ C ^ X): 41 | dw_hash |= 1 42 | 43 | b_counter *= 2 44 | if b_counter == 0: 45 | j += 1 46 | b_counter = np.uint8(1) 47 | 48 | return int(np.uint32(dw_hash)) 49 | -------------------------------------------------------------------------------- /reversebox/hash/hash_sdbm.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class SDBMHandler: 8 | def __init__(self): 9 | pass 10 | 11 | # used in Forza Horizon (2012) 12 | @staticmethod 13 | def calculate_sdbm_hash_from_string(input_string: str) -> int: 14 | hash_value = 0 15 | for char in input_string: 16 | hash_value = ord(char) + (hash_value << 6) + (hash_value << 16) - hash_value 17 | return hash_value & 0xFFFFFFFF 18 | 19 | @staticmethod 20 | def calculate_sdbm_hash_from_bytes(input_bytes: bytes) -> int: 21 | hash_value = 0 22 | for char in input_bytes: 23 | hash_value = char + (hash_value << 6) + (hash_value << 16) - hash_value 24 | return hash_value & 0xFFFFFFFF 25 | -------------------------------------------------------------------------------- /reversebox/hash/hash_sha1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import hashlib 7 | 8 | 9 | class SHA1Handler: 10 | def __init__(self): 11 | pass 12 | 13 | @staticmethod 14 | def calculate_sha1_hash(input_data: bytes) -> bytes: 15 | return hashlib.sha1(input_data).digest() 16 | -------------------------------------------------------------------------------- /reversebox/hash/hash_sha2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import hashlib 7 | 8 | 9 | class SHA2Handler: 10 | def __init__(self): 11 | pass 12 | 13 | @staticmethod 14 | def calculate_sha2_256_hash(input_data: bytes) -> bytes: 15 | return hashlib.sha256(input_data).digest() 16 | -------------------------------------------------------------------------------- /reversebox/image/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/image/__init__.py -------------------------------------------------------------------------------- /reversebox/image/byte_swap.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | # fmt: off 7 | 8 | # Byte swapping functions 9 | 10 | 11 | def swap_byte_order_x360(image_data: bytes) -> bytes: 12 | if len(image_data) % 2 != 0: 13 | raise Exception("Data size must be a multiple of 2 bytes!") 14 | 15 | swapped_data: bytearray = bytearray() 16 | for i in range(0, len(image_data), 2): 17 | group = image_data[i: i + 2] 18 | swapped_data.extend(group[::-1]) 19 | return swapped_data 20 | 21 | 22 | def swap_byte_order_gamecube(image_data: bytes, img_width: int, img_height: int) -> bytes: 23 | bw: int = img_width // 4 24 | bh: int = img_height // 4 25 | block_size: int = 8 26 | tile_w: int = bw // 2 27 | tile_h: int = bh // 2 28 | tile_index: int = 0 29 | 30 | swapped_data: bytearray = bytearray(len(image_data)) 31 | 32 | for ty in range(tile_h): 33 | for tx in range(tile_w): 34 | for by in range(2): 35 | for bx in range(2): 36 | # Calculate destination position 37 | dst_x = tx * 2 + bx 38 | dst_y = ty * 2 + by 39 | dst_index = (dst_y * bw + dst_x) * block_size 40 | 41 | # Get source block and convert endianness 42 | src_index = tile_index * block_size 43 | block = image_data[src_index:src_index + block_size] 44 | 45 | # Convert GameCube DXT1 to little-endian DXT1 46 | # Swap color0 and color1 endianness 47 | color0 = (block[0] << 8) | block[1] 48 | color1 = (block[2] << 8) | block[3] 49 | 50 | # Reorder 2-bit indices (bit reversal within each byte) 51 | indices = bytearray(4) 52 | for i in range(4): 53 | b = block[4 + i] 54 | indices[i] = ((b & 0b00000011) << 6) | \ 55 | ((b & 0b00001100) << 2) | \ 56 | ((b & 0b00110000) >> 2) | \ 57 | ((b & 0b11000000) >> 6) 58 | 59 | # Write converted block to output 60 | swapped_data[dst_index] = color0 & 0xff 61 | swapped_data[dst_index + 1] = color0 >> 8 62 | swapped_data[dst_index + 2] = color1 & 0xff 63 | swapped_data[dst_index + 3] = color1 >> 8 64 | swapped_data[dst_index + 4:dst_index + 8] = indices 65 | 66 | tile_index += 1 67 | 68 | return bytes(swapped_data) 69 | -------------------------------------------------------------------------------- /reversebox/image/compression/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/image/compression/__init__.py -------------------------------------------------------------------------------- /reversebox/image/compression/compression_lzw.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from reversebox.common.logger import get_logger 7 | 8 | logger = get_logger(__name__) 9 | 10 | # fmt: off 11 | 12 | # LZW compression 13 | # Used in TIFF files 14 | 15 | 16 | def decompress_lzw(image_data: bytes) -> bytes: 17 | # TODO - implement this 18 | return image_data 19 | 20 | 21 | def compress_lzw(image_data: bytes) -> bytes: 22 | # TODO - implement this 23 | return image_data 24 | -------------------------------------------------------------------------------- /reversebox/image/compression/compression_packbits.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from reversebox.common.logger import get_logger 7 | 8 | logger = get_logger(__name__) 9 | 10 | # fmt: off 11 | 12 | # PackBits (Macintosh RLE) compression 13 | # Used in TIFF and PSD files 14 | 15 | 16 | def decompress_packbits(image_data: bytes) -> bytes: 17 | """ 18 | Decompresses the input bytes using the PackBits algorithm 19 | """ 20 | decompressed_data: bytearray = bytearray() 21 | position: int = 0 22 | 23 | while position < len(image_data): 24 | header_byte = image_data[position] 25 | if header_byte > 127: 26 | header_byte -= 256 27 | position += 1 28 | 29 | if 0 <= header_byte <= 127: 30 | decompressed_data.extend(image_data[position:position + header_byte + 1]) 31 | position += header_byte+1 32 | elif header_byte == -128: 33 | pass 34 | else: 35 | decompressed_data.extend([image_data[position]] * (1 - header_byte)) 36 | position += 1 37 | 38 | return bytes(decompressed_data) 39 | 40 | 41 | def compress_packbits(image_data: bytes) -> bytes: 42 | """ 43 | Compresses the input bytes using the PackBits algorithm 44 | """ 45 | if not image_data: 46 | return b'' 47 | 48 | compressed_data: bytearray = bytearray() 49 | i: int = 0 50 | 51 | while i < len(image_data): 52 | # Look for runs of repeated bytes 53 | run_start = i 54 | while i + 1 < len(image_data) and image_data[i] == image_data[i + 1] and (i - run_start) < 127: 55 | i += 1 56 | 57 | if i > run_start: # We found a run 58 | compressed_data.append(257 - (i - run_start + 1)) # Encode run length (negative value) 59 | compressed_data.append(image_data[run_start]) 60 | i += 1 61 | else: # Handle non-repeating sequence 62 | literal_start = i 63 | while ( 64 | i < len(image_data) 65 | and (i + 1 >= len(image_data) or image_data[i] != image_data[i + 1]) 66 | and (i - literal_start) < 127 67 | ): 68 | i += 1 69 | 70 | compressed_data.append(i - literal_start - 1) # Encode literal length (positive value) 71 | compressed_data.extend(image_data[literal_start:i]) 72 | 73 | return bytes(compressed_data) 74 | -------------------------------------------------------------------------------- /reversebox/image/compression/compression_rle_executioners.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from reversebox.common.logger import get_logger 7 | from reversebox.image.common import convert_bpp_to_bytes_per_pixel 8 | 9 | logger = get_logger(__name__) 10 | 11 | # fmt: off 12 | 13 | # Executioners RLE compression 14 | # https://moddingwiki.shikadi.net/wiki/Executioners_RLE_Format 15 | # very similar to TGA RLE 16 | # used in "Executioners" DOS game in some graphics like "EDLAUGH.VOL", "GR3.VOL" etc. 17 | # extracted from INTRO.VOL archive 18 | 19 | 20 | def decompress_rle_executioners(image_data: bytes, img_width: int, img_height: int, bpp: int) -> bytes: 21 | bytes_per_pixel: int = convert_bpp_to_bytes_per_pixel(bpp) 22 | decompressed_data: bytearray = bytearray(img_width * img_height * bytes_per_pixel) 23 | for i in range(img_width * img_height * bytes_per_pixel): 24 | decompressed_data[i] = 0xFF 25 | 26 | read_pos: int = 0 27 | write_pos: int = 0 28 | while write_pos < (img_width * img_height * bytes_per_pixel): 29 | code = image_data[read_pos] 30 | read_pos += 1 31 | 32 | is_repeat = code & 0x80 != 0 33 | count = (code & 0x7F) 34 | 35 | if is_repeat: # repeated packet 36 | write_pos += count * bytes_per_pixel 37 | else: # raw packet 38 | decompressed_data[write_pos: write_pos + count * bytes_per_pixel] = image_data[read_pos:read_pos + count * bytes_per_pixel] 39 | read_pos += count * bytes_per_pixel 40 | write_pos += count * bytes_per_pixel 41 | 42 | return bytes(decompressed_data) 43 | -------------------------------------------------------------------------------- /reversebox/image/compression/compression_rle_tga.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from reversebox.common.logger import get_logger 7 | 8 | logger = get_logger(__name__) 9 | 10 | # fmt: off 11 | 12 | # TGA RLE compression 13 | # https://en.wikipedia.org/wiki/Truevision_TGA 14 | # https://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf 15 | 16 | 17 | def decompress_rle_tga(image_data: bytes, bpp: int) -> bytes: 18 | bytes_per_pixel = bpp // 8 19 | decompressed_data: list[int] = [] 20 | i = 0 21 | 22 | while i < len(image_data): 23 | header = image_data[i] 24 | i += 1 25 | 26 | packet_type = header & 0x80 27 | count = (header & 0x7F) + 1 28 | 29 | if packet_type: # repeated packet 30 | pixel_data = image_data[i:i + bytes_per_pixel] 31 | i += bytes_per_pixel 32 | decompressed_data.extend(pixel_data * count) 33 | else: # raw packet 34 | pixel_data = image_data[i:i + count * bytes_per_pixel] 35 | i += count * bytes_per_pixel 36 | decompressed_data.extend(pixel_data) 37 | 38 | return bytes(decompressed_data) 39 | 40 | 41 | def compress_rle_tga(image_data: bytes, bpp: int) -> bytes: 42 | bytes_per_pixel = bpp // 8 43 | compressed_data: list[int] = [] 44 | i = 0 45 | 46 | while i < len(image_data): 47 | 48 | # check for repeated pixels 49 | run_start = i 50 | run_length = 1 51 | while (i + bytes_per_pixel < len(image_data) and 52 | image_data[i:i + bytes_per_pixel] == image_data[i + bytes_per_pixel:i + 2 * bytes_per_pixel] and 53 | run_length < 128): 54 | run_length += 1 55 | i += bytes_per_pixel 56 | 57 | if run_length > 1: # repeated packet 58 | compressed_data.append(0x80 | (run_length - 1)) 59 | compressed_data.extend(image_data[run_start:run_start + bytes_per_pixel]) 60 | i += bytes_per_pixel 61 | else: # raw packet 62 | raw_start = i 63 | raw_length = 0 64 | while (i < len(image_data) and raw_length < 128 and 65 | (i + bytes_per_pixel >= len(image_data) or 66 | image_data[i:i + bytes_per_pixel] != image_data[i + bytes_per_pixel:i + 2 * bytes_per_pixel])): 67 | raw_length += 1 68 | i += bytes_per_pixel 69 | 70 | compressed_data.append(raw_length - 1) 71 | compressed_data.extend(image_data[raw_start:raw_start + raw_length * bytes_per_pixel]) 72 | 73 | return bytes(compressed_data) 74 | -------------------------------------------------------------------------------- /reversebox/image/compression/compression_zlib.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import zlib 7 | 8 | from reversebox.common.logger import get_logger 9 | 10 | logger = get_logger(__name__) 11 | 12 | # fmt: off 13 | 14 | # ZLIB (deflate) compression 15 | # Used in TIFF files 16 | 17 | 18 | def decompress_zlib(image_data: bytes) -> bytes: 19 | try: 20 | decompress = zlib.decompressobj(wbits=zlib.MAX_WBITS) 21 | decompressed_data: bytes = decompress.decompress(image_data) 22 | decompressed_data += decompress.flush() 23 | return decompressed_data 24 | except Exception as error: 25 | logger.error(f"Error while decompressing ZLIB data. Error: {error}") 26 | return image_data 27 | 28 | 29 | def compress_zlib(image_data: bytes, compress_level: int = 9) -> bytes: 30 | compress = zlib.compressobj( 31 | level=compress_level, 32 | method=zlib.DEFLATED, 33 | wbits=zlib.MAX_WBITS, 34 | memLevel=zlib.DEF_MEM_LEVEL, 35 | strategy=0 36 | ) 37 | compressed_data = compress.compress(image_data) 38 | compressed_data += compress.flush() 39 | return compressed_data 40 | -------------------------------------------------------------------------------- /reversebox/image/decoders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/image/decoders/__init__.py -------------------------------------------------------------------------------- /reversebox/image/decoders/bumpmap_decoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import math 7 | 8 | from reversebox.common.logger import get_logger 9 | from reversebox.image.image_formats import ImageFormats 10 | 11 | logger = get_logger(__name__) 12 | 13 | # fmt: off 14 | 15 | 16 | class BumpmapDecoder: 17 | 18 | def __init__(self): 19 | pass 20 | 21 | # 16-bit normal map. Each texel consist of a pair of 8-bit values (S and R) 22 | # which represent a normal in spherical coordinates. 23 | # S = polar angle. 0-255 maps to 0-89 degrees. 24 | # R = azimuthal angle. 0-255 maps to 0-359 degrees. 25 | # Used in DTEX Texconv Tool 26 | def _decode_bumpmap_sr_image(self, image_data: bytes, img_width: int, img_height: int) -> bytes: 27 | rgba_data = bytearray(img_width * img_height * 4) 28 | 29 | for i in range(img_width * img_height): 30 | S = image_data[i * 2] 31 | R = image_data[i * 2 + 1] 32 | 33 | polar_angle = S * 89.0 / 255.0 34 | azimuthal_angle = R * 359.0 / 255.0 35 | 36 | polar_radians = math.radians(polar_angle) 37 | azimuthal_radians = math.radians(azimuthal_angle) 38 | 39 | nx = math.sin(polar_radians) * math.cos(azimuthal_radians) 40 | ny = math.sin(polar_radians) * math.sin(azimuthal_radians) 41 | nz = math.cos(polar_radians) 42 | 43 | r_value = int((nx + 1.0) * 127.5) 44 | g_value = int((ny + 1.0) * 127.5) 45 | b_value = int((nz + 1.0) * 127.5) 46 | 47 | rgba_data[i * 4] = r_value 48 | rgba_data[i * 4 + 1] = g_value 49 | rgba_data[i * 4 + 2] = b_value 50 | rgba_data[i * 4 + 3] = 0xFF 51 | 52 | return rgba_data 53 | 54 | def decode_bumpmap_image_main(self, image_data: bytes, img_width: int, img_height: int, image_format: ImageFormats) -> bytes: 55 | 56 | if image_format == ImageFormats.BUMPMAP_SR: 57 | return self._decode_bumpmap_sr_image(image_data, img_width, img_height) 58 | else: 59 | raise Exception(f"Image format not supported by BUMPMAP decoder! Image_format: {image_format}") 60 | -------------------------------------------------------------------------------- /reversebox/image/encoders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/image/encoders/__init__.py -------------------------------------------------------------------------------- /reversebox/image/encoders/gst_encoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from reversebox.common.logger import get_logger 7 | from reversebox.image.image_formats import ImageFormats 8 | 9 | logger = get_logger(__name__) 10 | 11 | # fmt: off 12 | 13 | 14 | class GSTImageEncoder: 15 | """ 16 | Encoder for any PS2 GST images 17 | """ 18 | 19 | def __init__(self): 20 | pass 21 | 22 | gst_data_formats = { 23 | # image_format: (block_width, block_height, detail_bpp) 24 | ImageFormats.GST121: (1, 2, 1), 25 | ImageFormats.GST221: (2, 2, 1), 26 | ImageFormats.GST421: (4, 2, 1), 27 | ImageFormats.GST821: (8, 2, 1), 28 | ImageFormats.GST122: (1, 2, 2), 29 | ImageFormats.GST222: (2, 2, 2), 30 | ImageFormats.GST422: (4, 2, 2), 31 | ImageFormats.GST822: (8, 2, 2), 32 | } 33 | 34 | def encode_gst_image_main(self, image_data: bytes, img_width: int, img_height: int, image_format: ImageFormats, is_swizzled: bool) -> bytes: 35 | block_width, block_height, detail_bpp = self.gst_data_formats[image_format] 36 | 37 | # TODO 38 | return b'' 39 | -------------------------------------------------------------------------------- /reversebox/image/file_formats/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/image/file_formats/__init__.py -------------------------------------------------------------------------------- /reversebox/image/palettes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/image/palettes/__init__.py -------------------------------------------------------------------------------- /reversebox/image/palettes/palette_random.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | # generates palette data with random values 8 | # useful for testing 9 | def generate_random_palette(palette_size: int = 1024) -> bytes: 10 | import random 11 | 12 | random_palette = bytearray(palette_size) 13 | for i in range(palette_size): 14 | random_palette[i] = random.randint(0, 255) 15 | return random_palette 16 | -------------------------------------------------------------------------------- /reversebox/image/pillow_wrapper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import io 7 | 8 | from PIL import Image 9 | 10 | 11 | class PillowWrapper: 12 | def __init__(self): 13 | pass 14 | 15 | def get_pillow_image_from_rgba8888_data( 16 | self, image_data: bytes, img_width: int, img_height: int 17 | ) -> Image: 18 | pillow_image: Image = Image.frombuffer( 19 | "RGBA", 20 | (int(img_width), int(img_height)), 21 | image_data, 22 | "raw", 23 | "RGBA", 24 | 0, 25 | 1, 26 | ) 27 | return pillow_image 28 | 29 | def get_pillow_image_from_dxt_data( 30 | self, 31 | image_data: bytes, 32 | img_width: int, 33 | img_height: int, 34 | decoder_name: str, 35 | decoder_arg: int, 36 | ) -> Image: 37 | pillow_image: Image = Image.frombuffer( 38 | "RGBA", 39 | (img_width, img_height), 40 | image_data, 41 | decoder_name, 42 | decoder_arg, 43 | "", 44 | ) 45 | return pillow_image 46 | 47 | def get_image_data_from_pillow_image(self, pillow_image: Image) -> bytes: 48 | return pillow_image.tobytes() 49 | 50 | def get_pil_image_file_data_for_export( 51 | self, image_data: bytes, img_width: int, img_height, pillow_format: str = "DDS" 52 | ) -> bytes: 53 | pil_image: Image = Image.frombuffer( 54 | "RGBA", 55 | (int(img_width), int(img_height)), 56 | image_data, 57 | "raw", 58 | "RGBA", 59 | 0, 60 | 1, 61 | ) 62 | image_buffer = io.BytesIO() 63 | pil_image.save(image_buffer, pillow_format) 64 | image_buffer.seek(0) 65 | pil_image_file_data: bytes = image_buffer.read() 66 | return pil_image_file_data 67 | 68 | def get_pil_image_file_data_for_export2( 69 | self, pil_image: Image, pillow_format: str = "DDS" 70 | ) -> bytes: 71 | image_buffer = io.BytesIO() 72 | pil_image.save(image_buffer, pillow_format) 73 | image_buffer.seek(0) 74 | pil_image_file_data: bytes = image_buffer.read() 75 | return pil_image_file_data 76 | 77 | def get_pil_rgba_data_for_import(self, file_path: str) -> bytes: 78 | pil_image: Image = Image.open(file_path) 79 | pil_image = pil_image.convert("RGBA") 80 | rgba_data: bytes = pil_image.tobytes() 81 | return rgba_data 82 | -------------------------------------------------------------------------------- /reversebox/image/swizzling/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/image/swizzling/__init__.py -------------------------------------------------------------------------------- /reversebox/image/swizzling/swizzle_3ds.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | # Nintendo 3DS Swizzling 7 | # Can occur in: 8 | # - some MT Framework games on 3DS 9 | # - "Mario & Luigi: Superstar Saga + Bowser's Minions" (3DS) 10 | # - BFLIM image files 11 | 12 | # fmt: off 13 | 14 | 15 | def _convert_3ds(image_data: bytes, img_width: int, img_height: int, bpp: int, swizzle_flag: bool) -> bytes: 16 | l: int = 8 17 | m: int = 4 18 | s: int = 2 19 | strip_size: int = bpp * s // 8 20 | 21 | converted_data = bytearray(img_width * img_height * bpp // 8) 22 | ptr: int = 0 23 | 24 | for y in range(0, img_height, l): 25 | for x in range(0, img_width, l): 26 | for y1 in range(0, l, m): 27 | for x1 in range(0, l, m): 28 | for y2 in range(0, m, s): 29 | for x2 in range(0, m, s): 30 | for y3 in range(s): 31 | idx = (((y + y1 + y2 + y3) * img_width) + x + x1 + x2) * bpp // 8 32 | if not swizzle_flag: 33 | converted_data[idx: idx+strip_size] = image_data[ptr: ptr + strip_size] 34 | else: 35 | converted_data[ptr: ptr+strip_size] = image_data[idx: idx + strip_size] 36 | ptr += strip_size 37 | 38 | return converted_data 39 | 40 | 41 | def unswizzle_3ds(image_data: bytes, img_width: int, img_height: int, bpp: int) -> bytes: 42 | return _convert_3ds(image_data, img_width, img_height, bpp, False) 43 | 44 | 45 | def swizzle_3ds(image_data: bytes, img_width: int, img_height: int, bpp: int) -> bytes: 46 | return _convert_3ds(image_data, img_width, img_height, bpp, True) 47 | -------------------------------------------------------------------------------- /reversebox/image/swizzling/swizzle_bc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from reversebox.image.common import crop_image, get_storage_wh 7 | 8 | # BC Swizzle 9 | # Used in some WII games like Mario Kart Wii 10 | 11 | # fmt: off 12 | 13 | 14 | def unswizzle_bc(image_data: bytes, image_width: int, image_height: int, block_width: int, block_height: int, bpp: int) -> bytes: 15 | strip_size: int = bpp * block_width // 8 16 | _width, _height = get_storage_wh(image_width, image_height, block_width, block_height) 17 | unswizzled_image_data = bytearray(_width * _height * bpp // 8) 18 | ptr: int = 0 19 | 20 | for y in range(0, _height, block_height): 21 | for x in range(0, _width, block_width): 22 | for y2 in range(block_height): 23 | idx = (((y + y2) * _width) + x) * bpp // 8 24 | unswizzled_image_data[idx: idx + strip_size] = image_data[ptr: ptr + strip_size] 25 | ptr += strip_size 26 | 27 | return crop_image(unswizzled_image_data, _width, _height, bpp, image_width, image_height) 28 | -------------------------------------------------------------------------------- /reversebox/image/swizzling/swizzle_cmpr.py: -------------------------------------------------------------------------------- 1 | # TODO - refactor this 2 | # https://wiki.tockdom.com/wiki/Image_Formats#CMPR 3 | def unswizzle_cmpr(pixel_data, width, height): 4 | tile_width = 2 5 | tile_height = 2 6 | dxt_block_size = 8 7 | 8 | num_block_width = width // 8 9 | num_block_height = height // 8 10 | 11 | tile_size = tile_width * tile_height * dxt_block_size 12 | line_size = tile_width * dxt_block_size 13 | 14 | unswizzled_data = bytearray(len(pixel_data)) 15 | 16 | for y in range(0, num_block_height): 17 | for x in range(0, num_block_width): 18 | data_ptr = (y * num_block_width + x) * tile_size 19 | for ty in range(0, tile_height): 20 | cur_height = y * tile_height + ty 21 | dst_index = (cur_height * num_block_width + x) * line_size 22 | src_index = data_ptr + ty * line_size 23 | for p in range(tile_width): 24 | unswizzled_data[dst_index + p * dxt_block_size] = pixel_data[ 25 | src_index + p * dxt_block_size + 1 26 | ] 27 | unswizzled_data[dst_index + p * dxt_block_size + 1] = pixel_data[ 28 | src_index + p * dxt_block_size 29 | ] 30 | unswizzled_data[dst_index + p * dxt_block_size + 2] = pixel_data[ 31 | src_index + p * dxt_block_size + 3 32 | ] 33 | unswizzled_data[dst_index + p * dxt_block_size + 3] = pixel_data[ 34 | src_index + p * dxt_block_size + 2 35 | ] 36 | for i in range(4, 8): 37 | index = pixel_data[src_index + p * dxt_block_size + i] 38 | swap_index = ( 39 | ((index >> 6) & 0x3) 40 | | (((index >> 4) & 0x3) << 2) 41 | | (((index >> 2) & 0x3) << 4) 42 | | ((index & 0x3) << 6) 43 | ) 44 | unswizzled_data[dst_index + p * dxt_block_size + i] = swap_index 45 | 46 | return unswizzled_data 47 | -------------------------------------------------------------------------------- /reversebox/image/swizzling/swizzle_morton.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from reversebox.image.common import convert_bpp_to_bytes_per_pixel 7 | 8 | # Morton Order Texture Swizzling 9 | # https://en.wikipedia.org/wiki/Z-order_curve 10 | # Used in XBOX CLASSIC and PS3 games (e.g. EA XSH files) 11 | 12 | # Swizzling modes: 13 | # block_width_height=1 --> linear formats 14 | # block_width_height=4 --> BC formats, 4x4 blocks 15 | # block_width_height=8 --> BC formats, 8x8 blocks 16 | 17 | # fmt: off 18 | 19 | 20 | def calculate_morton_index(t: int, width: int, height: int) -> int: 21 | num1 = num2 = 1 22 | num3 = t 23 | t_width = width 24 | t_height = height 25 | num6 = num7 = 0 26 | 27 | while t_width > 1 or t_height > 1: 28 | if t_width > 1: 29 | num6 += num2 * (num3 & 1) 30 | num3 >>= 1 31 | num2 *= 2 32 | t_width >>= 1 33 | if t_height > 1: 34 | num7 += num1 * (num3 & 1) 35 | num3 >>= 1 36 | num1 *= 2 37 | t_height >>= 1 38 | 39 | return num7 * width + num6 40 | 41 | 42 | def _convert_morton(pixel_data: bytes, img_width: int, img_height: int, bpp: int, block_width_height, swizzle_flag: bool) -> bytes: 43 | bytes_per_pixel: int = convert_bpp_to_bytes_per_pixel(bpp) 44 | block_data_size: int = bytes_per_pixel * block_width_height * block_width_height 45 | converted_data: bytearray = bytearray(len(pixel_data)) 46 | img_height //= block_width_height 47 | img_width //= block_width_height 48 | source_index: int = 0 49 | 50 | for t in range(img_width * img_height): 51 | index = calculate_morton_index(t, img_width, img_height) 52 | destination_index = block_data_size * index 53 | if not swizzle_flag: 54 | converted_data[destination_index:destination_index + block_data_size] = pixel_data[source_index:source_index + block_data_size] 55 | else: 56 | converted_data[source_index:source_index + block_data_size] = pixel_data[destination_index:destination_index + block_data_size] 57 | source_index += block_data_size 58 | 59 | return converted_data 60 | 61 | 62 | def unswizzle_morton(pixel_data: bytes, img_width: int, img_height: int, bpp: int, block_width_height: int = 1) -> bytes: 63 | return _convert_morton(pixel_data, img_width, img_height, bpp, block_width_height, False) 64 | 65 | 66 | def swizzle_morton(pixel_data: bytes, img_width: int, img_height: int, bpp: int, block_width_height: int = 1) -> bytes: 67 | return _convert_morton(pixel_data, img_width, img_height, bpp, block_width_height, True) 68 | -------------------------------------------------------------------------------- /reversebox/image/swizzling/swizzle_psp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | from reversebox.image.common import get_stride_value 7 | 8 | # fmt: off 9 | 10 | # PSP Texture Swizzling 11 | # Note: Input data has to be 16-bytes aligned to be processed correctly! 12 | 13 | 14 | def _convert_psp(image_data: bytes, img_width: int, img_height: int, bpp: int, swizzle_flag: bool) -> bytes: 15 | converted_data = bytearray(len(image_data)) 16 | output_offset: int = 0 17 | stride: int = get_stride_value(img_width, bpp) 18 | row_blocks: int = stride // 16 19 | 20 | for y in range(img_height): 21 | for x in range(stride): 22 | block_x = x // 16 23 | block_y = y // 8 24 | block_index = block_x + (block_y * row_blocks) 25 | block_address = block_index * 16 * 8 26 | if not swizzle_flag: 27 | converted_data[output_offset] = image_data[block_address + (x - block_x * 16) + ((y - block_y * 8) * 16)] 28 | else: 29 | converted_data[block_address + (x - block_x * 16) + ((y - block_y * 8) * 16)] = image_data[output_offset] 30 | output_offset += 1 31 | 32 | return converted_data 33 | 34 | 35 | def unswizzle_psp(image_data: bytes, img_width: int, img_height: int, bpp: int) -> bytes: 36 | return _convert_psp(image_data, img_width, img_height, bpp, False) 37 | 38 | 39 | def swizzle_psp(image_data: bytes, img_width: int, img_height: int, bpp: int): 40 | return _convert_psp(image_data, img_width, img_height, bpp, True) 41 | -------------------------------------------------------------------------------- /reversebox/io_files/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/io_files/__init__.py -------------------------------------------------------------------------------- /reversebox/io_files/bytes_handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | 7 | class BytesHandler: 8 | def __init__(self, input_bytes: bytes): 9 | self.input_bytes = input_bytes 10 | self.data_size = len(input_bytes) 11 | 12 | def get_bytes(self, offset: int, size: int) -> bytes: 13 | # fmt: off 14 | output_bytes: bytes = self.input_bytes[offset: offset + size] 15 | # fmt: on 16 | return output_bytes 17 | 18 | def fill_to_length(self, expected_length: int, fill_byte: bytes = b"\x00") -> bytes: 19 | if len(fill_byte) > 1: 20 | raise Exception("Fill byte is too long!") 21 | 22 | if len(self.input_bytes) >= expected_length: 23 | raise Exception("Bytes sequence is longer or equal expected length!") 24 | 25 | length_to_fill: int = expected_length - len(self.input_bytes) 26 | fill_value: bytes = b"" 27 | for _ in range(length_to_fill): 28 | fill_value += fill_byte 29 | return self.input_bytes + fill_value 30 | 31 | # TODO - test if this works 32 | def bytes_to_bitstring(self): 33 | binary_string = "".join(format(byte, "08b") for byte in self.input_bytes) 34 | return binary_string 35 | 36 | # TODO - test if this works 37 | def get_int_from_bits(self, n: int, start_bit: int, number_of_bits: int) -> int: 38 | n = n >> (31 - start_bit - number_of_bits) 39 | mask = ~(-1 << number_of_bits) 40 | result = n & mask 41 | return result 42 | -------------------------------------------------------------------------------- /reversebox/io_files/check_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | from typing import Tuple 8 | 9 | 10 | def check_file( 11 | in_file_path: str, 12 | expected_extension: str, 13 | file_should_exist: bool, 14 | create_dirs=False, 15 | ) -> Tuple[str, str]: 16 | if file_should_exist: 17 | if not os.path.isfile(in_file_path): 18 | return "NOT_FILE_ERROR", f"{in_file_path} is not a valid input file path!" 19 | 20 | in_file_extension = os.path.splitext(in_file_path)[1] 21 | if in_file_extension.upper() != expected_extension.upper(): 22 | return ( 23 | f"NOT_{expected_extension.upper()}_ERROR", 24 | f"{in_file_path} is not a valid {expected_extension.upper()} file!", 25 | ) 26 | 27 | if create_dirs: 28 | if not os.path.exists(os.path.dirname(in_file_path)): 29 | try: 30 | os.makedirs(os.path.dirname(in_file_path)) 31 | except FileNotFoundError: 32 | return "CANT_CREATE_DIR_ERROR", "Can't create output directory!" 33 | 34 | return "OK", "" 35 | -------------------------------------------------------------------------------- /reversebox/libs/DirectXTex.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/libs/DirectXTex.dll -------------------------------------------------------------------------------- /reversebox/libs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/libs/__init__.py -------------------------------------------------------------------------------- /reversebox/libs/refpack.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/reversebox/libs/refpack.dll -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | from typing import Final 8 | 9 | import setuptools 10 | 11 | VERSION_NUM: Final[str] = "0.37.1" 12 | 13 | 14 | def get_long_description() -> str: 15 | with open( 16 | os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf8" 17 | ) as readme: 18 | readme_text = readme.read() 19 | return readme_text 20 | 21 | 22 | setuptools.setup( 23 | name="ReverseBox", 24 | version=VERSION_NUM, 25 | author="Bartlomiej Duda", 26 | author_email="ikskoks@gmail.com", 27 | description="A set of functions useful in reverse engineering.", 28 | long_description=get_long_description(), 29 | long_description_content_type="text/markdown", 30 | url="https://github.com/bartlomiejduda/ReverseBox", 31 | classifiers=[ 32 | "Programming Language :: Python :: 3", 33 | "Programming Language :: Python :: 3.6", 34 | "Programming Language :: Python :: 3.7", 35 | "Programming Language :: Python :: 3.8", 36 | "Programming Language :: Python :: 3.9", 37 | "Programming Language :: Python :: 3.10", 38 | "Programming Language :: Python :: 3.11", 39 | "Programming Language :: Python :: 3.12", 40 | "Programming Language :: Python :: 3.13", 41 | "Development Status :: 4 - Beta", 42 | "Topic :: Software Development", 43 | "Topic :: System :: Archiving :: Compression", 44 | "Topic :: Security :: Cryptography", 45 | "Environment :: Console", 46 | "Intended Audience :: Developers", 47 | "Intended Audience :: Science/Research", 48 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 49 | "Operating System :: OS Independent", 50 | "Natural Language :: English", 51 | ], 52 | test_suite="tests", 53 | keywords="ReverseBox, reverse engineering, RE, CRC, Hash, Encryption, Compression, Checksum, Python, image, decode, decoding, " 54 | "RGB, swizzle, swizzling, morton, twiddle, twiddling, texture, UYVY, YUY2, NV21, NV12, RGBA, RGBA8888, RGB565, RGBA8, BGR, " 55 | "grayscale, graphics, color, pixel, convert, converting, YUV, RAW, PSP, PS1, PS2, PS3, PS4, XBOX, X360, gamecube, dreamcast, " 56 | "BC, BC1, BC2, BC3, BC4, BC5, BC6, BC7, DXT1, DXT2, DXT3, PackBits, RLE, Macintosh, Jenkins, murmur, murmur3, one-at-a-time, " 57 | "additive", 58 | python_requires=">=3.6", 59 | install_requires=[ 60 | "lzokay", 61 | "polib", 62 | "crc", 63 | "hashbase", 64 | "pillow", 65 | "mmh3", 66 | ], 67 | packages=setuptools.find_packages(exclude=["tests", "tests.*"]), 68 | package_data={"": ["libs/*.dll"]}, 69 | include_package_data=True, 70 | ) 71 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_hash/test_hash_additive.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.hash.hash_additive import AdditiveHashHandler 10 | from tests.common import BytesHashTestEntry, TextHashTestEntry 11 | 12 | additive_hash_handler = AdditiveHashHandler() 13 | 14 | 15 | # fmt: off 16 | 17 | @pytest.mark.unittest 18 | def test_hash_additive_from_string_to_match_expected_result(): 19 | hash_data_list = [ 20 | TextHashTestEntry(test_string="test", expected_int=452, expected_str="0x1C4", prime=222333), 21 | TextHashTestEntry(test_string="Hello World!", expected_int=1097, expected_str="0x449", prime=222333), 22 | ] 23 | 24 | for hash_entry in hash_data_list: 25 | # check expected result from first execution 26 | test_result = additive_hash_handler.calculate_additive_hash_from_string(hash_entry.test_string, hash_entry.prime) 27 | test_result_str = convert_int_to_hex_string(test_result) 28 | assert test_result == hash_entry.expected_int 29 | assert test_result_str == hash_entry.expected_str 30 | 31 | # check if result is the same after second execution 32 | test_result = additive_hash_handler.calculate_additive_hash_from_string(hash_entry.test_string, hash_entry.prime) 33 | test_result_str = convert_int_to_hex_string(test_result) 34 | assert test_result == hash_entry.expected_int 35 | assert test_result_str == hash_entry.expected_str 36 | 37 | 38 | @pytest.mark.unittest 39 | def test_hash_additive_from_bytes_to_match_expected_result(): 40 | hash_data_list = [ 41 | BytesHashTestEntry(test_bytes=b"test", expected_int=452, expected_str="0x1C4", prime=222333), 42 | BytesHashTestEntry(test_bytes=b"Hello World!", expected_int=1097, expected_str="0x449", prime=222333), 43 | ] 44 | 45 | for hash_entry in hash_data_list: 46 | # check expected result from first execution 47 | test_result = additive_hash_handler.calculate_additive_hash_from_bytes(hash_entry.test_bytes, hash_entry.prime) 48 | test_result_str = convert_int_to_hex_string(test_result) 49 | assert test_result == hash_entry.expected_int 50 | assert test_result_str == hash_entry.expected_str 51 | 52 | # check if result is the same after second execution 53 | test_result = additive_hash_handler.calculate_additive_hash_from_bytes(hash_entry.test_bytes, hash_entry.prime) 54 | test_result_str = convert_int_to_hex_string(test_result) 55 | assert test_result == hash_entry.expected_int 56 | assert test_result_str == hash_entry.expected_str 57 | -------------------------------------------------------------------------------- /tests/test_hash/test_hash_fnv.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.hash.hash_fnv import FNVHashHandler 9 | from tests.common import HashTestEntry 10 | 11 | fnv_handler = FNVHashHandler() 12 | 13 | 14 | @pytest.mark.unittest 15 | def test_hash_fnv1_32_to_match_expected_result(): 16 | fnv_data_list = [ 17 | HashTestEntry( 18 | test_data=b"123", 19 | expected_result=b"\xbb\x07\xd6\x72", 20 | ), 21 | HashTestEntry( 22 | test_data=b"abcd", 23 | expected_result=b"\x75\x73\xDE\xB9", 24 | ), 25 | HashTestEntry( 26 | test_data=b"Secret123@456", 27 | expected_result=b"\xFE\xE7\xFF\x9F", 28 | ), 29 | HashTestEntry( 30 | test_data=b"", 31 | expected_result=b"\xc5\x9d\x1c\x81", 32 | ), 33 | ] 34 | 35 | for fnv_entry in fnv_data_list: 36 | test_result = fnv_handler.fnv1_32(fnv_entry.test_data) 37 | assert test_result == fnv_entry.expected_result 38 | 39 | 40 | @pytest.mark.unittest 41 | def test_hash_fnv1a_32_to_match_expected_result(): 42 | fnv_data_list = [ 43 | HashTestEntry( 44 | test_data=b"123", 45 | expected_result=b"\x1B\x63\x38\x72", 46 | ), 47 | HashTestEntry( 48 | test_data=b"abcd", 49 | expected_result=b"\xBD\x79\x34\xCE", 50 | ), 51 | # TODO - check out why those results are different than in "WinHash" tool 52 | # HashTestEntry( 53 | # test_data=b"Secret123@456", 54 | # expected_result=b"\x74\xB4\x3B\x49", 55 | # ), 56 | # HashTestEntry( 57 | # test_data=b"", 58 | # expected_result=b"\x00\x00\x00\x00", 59 | # ), 60 | ] 61 | 62 | for fnv_entry in fnv_data_list: 63 | test_result = fnv_handler.fnv1a_32(fnv_entry.test_data) 64 | assert test_result == fnv_entry.expected_result 65 | -------------------------------------------------------------------------------- /tests/test_hash/test_hash_md2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.hash.hash_md2 import MD2Handler 9 | from tests.common import HashTestEntry 10 | 11 | md2_handler = MD2Handler() 12 | 13 | 14 | @pytest.mark.unittest 15 | def test_hash_md2_to_match_expected_result(): 16 | md2_data_list = [ 17 | HashTestEntry( 18 | test_data=b"123", 19 | expected_result=b"\xef\x1f\xed\xf5\xd3\x2e\xad\x6b\x7a\xaf\x68\x7d\xe4\xed\x1b\x71", 20 | ), 21 | HashTestEntry( 22 | test_data=b"abcd", 23 | expected_result=b"\xf7\x90\xd2\xa3\x94\x0d\xde\x40\x56\x38\x7c\x78\x7f\x30\x62\xf8", 24 | ), 25 | HashTestEntry( 26 | test_data=b"Secret123@456", 27 | expected_result=b"\xf2\xc8\x23\xd8\xee\x44\x54\xf1\xc4\x20\xb3\x8a\x1c\x1c\xae\x67", 28 | ), 29 | HashTestEntry( 30 | test_data=b"", 31 | expected_result=b"\x83\x50\xe5\xa3\xe2\x4c\x15\x3d\xf2\x27\x5c\x9f\x80\x69\x27\x73", 32 | ), 33 | ] 34 | 35 | for md2_entry in md2_data_list: 36 | test_result = md2_handler.calculate_md2_hash(md2_entry.test_data) 37 | assert test_result == md2_entry.expected_result 38 | -------------------------------------------------------------------------------- /tests/test_hash/test_hash_md5.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.hash.hash_md5 import MD5Handler 9 | from tests.common import HashTestEntry 10 | 11 | md5_handler = MD5Handler() 12 | 13 | 14 | @pytest.mark.unittest 15 | def test_hash_md5_to_match_expected_result(): 16 | md5_data_list = [ 17 | HashTestEntry( 18 | test_data=b"123", 19 | expected_result=b"\x20\x2C\xB9\x62\xAC\x59\x07\x5B\x96\x4B\x07\x15\x2D\x23\x4B\x70", 20 | ), 21 | HashTestEntry( 22 | test_data=b"abcd", 23 | expected_result=b"\xE2\xFC\x71\x4C\x47\x27\xEE\x93\x95\xF3\x24\xCD\x2E\x7F\x33\x1F", 24 | ), 25 | HashTestEntry( 26 | test_data=b"Secret123@456", 27 | expected_result=b"\x7A\xB9\xCE\x89\x8E\x1D\xB2\x18\x99\x44\xB3\xE6\x38\x3A\x58\x6E", 28 | ), 29 | HashTestEntry( 30 | test_data=b"", 31 | expected_result=b"\xD4\x1D\x8C\xD9\x8F\x00\xB2\x04\xE9\x80\x09\x98\xEC\xF8\x42\x7E", 32 | ), 33 | ] 34 | 35 | for md5_entry in md5_data_list: 36 | test_result = md5_handler.calculate_md5_hash(md5_entry.test_data) 37 | assert test_result == md5_entry.expected_result 38 | -------------------------------------------------------------------------------- /tests/test_hash/test_hash_pivotal_games_dat.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.hash.hash_pivotal_games_dat import PivotalGamesDATHashHandler 10 | from tests.common import TextHashTestEntry 11 | 12 | hash_handler = PivotalGamesDATHashHandler() 13 | 14 | 15 | # fmt: off 16 | 17 | @pytest.mark.unittest 18 | def test_hash_pivotal_games_dat_from_string_to_match_expected_result(): 19 | hash_data_list = [ 20 | TextHashTestEntry(test_string="MPD_UH1_HUEY_TAILROTOR.DDS", expected_int=2038369709, expected_str="0x797F0DAD"), 21 | TextHashTestEntry(test_string="mpd_usglasses.dds", expected_int=3525764600, expected_str="0xD226E5F8"), 22 | TextHashTestEntry(test_string="MPD_USGLASSES.DDS", expected_int=3544284667, expected_str="0xD3417DFB"), 23 | TextHashTestEntry(test_string="mpd_usglasses.evo", expected_int=3539855468, expected_str="0xD2FDE86C"), 24 | TextHashTestEntry(test_string="MPD_USWEBBING_03_COMPASSPOUCH_WET.DDS", expected_int=2189375299, expected_str="0x827F3743"), 25 | TextHashTestEntry(test_string="MPD_US_101_DEAD_01.EVO", expected_int=431098015, expected_str="0x19B2089F"), 26 | TextHashTestEntry(test_string="mpd_us_101_dead_02.evo", expected_int=1073481072, expected_str="0x3FFC0570"), 27 | ] 28 | 29 | for hash_entry in hash_data_list: 30 | # check expected result from first execution 31 | test_result = hash_handler.calculate_pivotal_games_dat_hash_from_string(hash_entry.test_string) 32 | test_result_str = convert_int_to_hex_string(test_result) 33 | assert test_result == hash_entry.expected_int 34 | assert test_result_str == hash_entry.expected_str 35 | 36 | # check if result is the same after second execution 37 | test_result = hash_handler.calculate_pivotal_games_dat_hash_from_string(hash_entry.test_string) 38 | test_result_str = convert_int_to_hex_string(test_result) 39 | assert test_result == hash_entry.expected_int 40 | assert test_result_str == hash_entry.expected_str 41 | -------------------------------------------------------------------------------- /tests/test_hash/test_hash_sha1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.hash.hash_sha1 import SHA1Handler 9 | from tests.common import HashTestEntry 10 | 11 | sha1_handler = SHA1Handler() 12 | 13 | 14 | @pytest.mark.unittest 15 | def test_hash_sha1_to_match_expected_result(): 16 | sha1_data_list = [ 17 | HashTestEntry( 18 | test_data=b"123", 19 | expected_result=b"\x40\xbd\x00\x15\x63\x08\x5f\xc3\x51\x65\x32\x9e\xa1\xff\x5c\x5e\xcb\xdb\xbe\xef", 20 | ), 21 | HashTestEntry( 22 | test_data=b"abcd", 23 | expected_result=b"\x81\xfe\x8b\xfe\x87\x57\x6c\x3e\xcb\x22\x42\x6f\x8e\x57\x84\x73\x82\x91\x7a\xcf", 24 | ), 25 | HashTestEntry( 26 | test_data=b"Secret123@456", 27 | expected_result=b"\x95\x97\x91\x1b\x10\x41\x98\xf2\x96\x8c\xc1\xfe\xca\x2e\x6a\x3e\x06\x0c\x15\xb4", 28 | ), 29 | HashTestEntry( 30 | test_data=b"", 31 | expected_result=b"\xda\x39\xa3\xee\x5e\x6b\x4b\x0d\x32\x55\xbf\xef\x95\x60\x18\x90\xaf\xd8\x07\x09", 32 | ), 33 | ] 34 | 35 | for sha1_entry in sha1_data_list: 36 | test_result = sha1_handler.calculate_sha1_hash(sha1_entry.test_data) 37 | assert test_result == sha1_entry.expected_result 38 | -------------------------------------------------------------------------------- /tests/test_hash/test_hash_sha2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.hash.hash_sha2 import SHA2Handler 9 | from tests.common import HashTestEntry 10 | 11 | sha2_handler = SHA2Handler() 12 | 13 | 14 | @pytest.mark.unittest 15 | def test_hash_sha2_256_to_match_expected_result(): 16 | sha2_data_list = [ 17 | HashTestEntry( 18 | test_data=b"123", 19 | expected_result=b"\xa6\x65\xa4\x59\x20\x42\x2f\x9d\x41\x7e\x48\x67\xef\xdc\x4f\xb8\xa0\x4a\x1f\x3f\xff\x1f" 20 | b"\xa0\x7e\x99\x8e\x86\xf7\xf7\xa2\x7a\xe3", 21 | ), 22 | HashTestEntry( 23 | test_data=b"abcd", 24 | expected_result=b"\x88\xd4\x26\x6f\xd4\xe6\x33\x8d\x13\xb8\x45\xfc\xf2\x89\x57\x9d\x20\x9c\x89\x78\x23\xb9" 25 | b"\x21\x7d\xa3\xe1\x61\x93\x6f\x03\x15\x89", 26 | ), 27 | HashTestEntry( 28 | test_data=b"Secret123@456", 29 | expected_result=b"\xc9\xb6\x46\xb7\xd1\xa7\x67\x37\xed\x40\x97\x8e\x35\x10\x8d\xd4\x6e\xde\xb2\x0c\x81\x67" 30 | b"\x3f\x18\xa8\xda\x54\x7b\x01\x5c\xb0\x29", 31 | ), 32 | HashTestEntry( 33 | test_data=b"", 34 | expected_result=b"\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24\x27\xae\x41\xe4\x64\x9b" 35 | b"\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55", 36 | ), 37 | ] 38 | 39 | for sha2_entry in sha2_data_list: 40 | test_result = sha2_handler.calculate_sha2_256_hash(sha2_entry.test_data) 41 | assert test_result == sha2_entry.expected_result 42 | -------------------------------------------------------------------------------- /tests/tests_checksum/checksum_files/cg001_i01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_checksum/checksum_files/cg001_i01.png -------------------------------------------------------------------------------- /tests/tests_checksum/checksum_files/cg001_i01_decrypted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_checksum/checksum_files/cg001_i01_decrypted.png -------------------------------------------------------------------------------- /tests/tests_checksum/test_checksum_cocos2d_pvr.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.checksum.checksum_cocos2d_pvr import Cocos2dPVRChecksumHandler 11 | from reversebox.io_files.file_handler import FileHandler 12 | 13 | cocos2d_pvr_checksum_handler = Cocos2dPVRChecksumHandler() 14 | 15 | # fmt: off 16 | 17 | 18 | @pytest.mark.unittest 19 | def test_checksum_calculate_cocos2d_pcr_checksum_to_match_expected_result(): 20 | 21 | # read from encrypted file 22 | cocos2d_file_path = os.path.join(os.path.dirname(__file__), "checksum_files/cg001_i01.png") 23 | cocos2d_file = FileHandler(cocos2d_file_path, "rb") 24 | cocos2d_file.seek(8) 25 | cocos2d_file.change_endianess("big") 26 | f_checksum: int = cocos2d_file.read_uint32() 27 | f_size: int = cocos2d_file.get_file_size() 28 | checksum_data_size: int = (f_size - 12) // 4 29 | cocos2d_file.close() 30 | 31 | # read from decrypted file 32 | cocos2d_decrypted_file_path = os.path.join(os.path.dirname(__file__), "checksum_files/cg001_i01_decrypted.png") 33 | cocos2d_decrypted_file = FileHandler(cocos2d_decrypted_file_path, "rb") 34 | cocos2d_decrypted_file.seek(12) 35 | read_data_size: int = f_size - 12 36 | decrypted_data: bytes = cocos2d_decrypted_file.read_bytes(read_data_size) 37 | cocos2d_decrypted_file.close() 38 | 39 | # calculate checksum 40 | calculated_checksum: int = cocos2d_pvr_checksum_handler.calculate_cocos2d_pvr_checksum(decrypted_data, checksum_data_size) 41 | assert calculated_checksum == f_checksum 42 | -------------------------------------------------------------------------------- /tests/tests_checksum/test_checksum_fletcher16.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.checksum.checksum_fletcher16 import Fletcher16Handler 9 | from reversebox.common.common import convert_int_to_hex_string 10 | from tests.common import CRCTestEntry 11 | 12 | fletcher16_handler = Fletcher16Handler() 13 | 14 | # fmt: off 15 | 16 | 17 | @pytest.mark.unittest 18 | def test_checksum_calculate_fletcher16_to_match_expected_result(): 19 | crc_data_list = [ 20 | CRCTestEntry(test_data=b"abcd", expected_int=55179, expected_str="0xD78B"), 21 | CRCTestEntry(test_data=b"123456789", expected_int=7902, expected_str="0x1EDE"), 22 | CRCTestEntry(test_data=b"123", expected_int=11158, expected_str="0x2B96"), 23 | CRCTestEntry(test_data=b"Secret123@456", expected_int=45534, expected_str="0xB1DE"), 24 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 25 | CRCTestEntry(test_data=b" ", expected_int=8224, expected_str="0x2020"), 26 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=4454, expected_str="0x1166"), 27 | CRCTestEntry(test_data=b"\x01\x02\x03\x04", expected_int=5130, expected_str="0x140A"), 28 | ] 29 | 30 | for crc_entry in crc_data_list: 31 | # check expected result from first execution 32 | test_result = fletcher16_handler.calculate_fletcher16(crc_entry.test_data) 33 | test_result_str = convert_int_to_hex_string(test_result) 34 | assert test_result == crc_entry.expected_int 35 | assert test_result_str == crc_entry.expected_str 36 | 37 | # check if result is the same after second execution 38 | test_result = fletcher16_handler.calculate_fletcher16(crc_entry.test_data) 39 | test_result_str = convert_int_to_hex_string(test_result) 40 | assert test_result == crc_entry.expected_int 41 | assert test_result_str == crc_entry.expected_str 42 | -------------------------------------------------------------------------------- /tests/tests_checksum/test_checksum_fletcher32.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.checksum.checksum_fletcher32 import Fletcher32Handler 9 | from reversebox.common.common import convert_int_to_hex_string 10 | from tests.common import CRCTestEntry 11 | 12 | fletcher32_handler = Fletcher32Handler() 13 | 14 | # fmt: off 15 | 16 | 17 | @pytest.mark.unittest 18 | def test_checksum_calculate_fletcher32_to_match_expected_result(): 19 | crc_data_list = [ 20 | CRCTestEntry(test_data=b"abcd", expected_int=690407108, expected_str="0x2926C6C4"), 21 | CRCTestEntry(test_data=b"123456789", expected_int=3741963529, expected_str="0xDF09D509"), 22 | CRCTestEntry(test_data=b"123", expected_int=1687499364, expected_str="0x64953264"), 23 | CRCTestEntry(test_data=b"Secret123@456", expected_int=2912220138, expected_str="0xAD94F3EA"), 24 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 25 | CRCTestEntry(test_data=b" ", expected_int=2097184, expected_str="0x200020"), 26 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=3148528554, expected_str="0xBBAABBAA"), 27 | CRCTestEntry(test_data=b"\x01\x02\x03\x04", expected_int=134546948, expected_str="0x8050604"), 28 | ] 29 | 30 | for crc_entry in crc_data_list: 31 | # check expected result from first execution 32 | test_result = fletcher32_handler.calculate_fletcher32(crc_entry.test_data) 33 | test_result_str = convert_int_to_hex_string(test_result) 34 | assert test_result == crc_entry.expected_int 35 | assert test_result_str == crc_entry.expected_str 36 | 37 | # check if result is the same after second execution 38 | test_result = fletcher32_handler.calculate_fletcher32(crc_entry.test_data) 39 | test_result_str = convert_int_to_hex_string(test_result) 40 | assert test_result == crc_entry.expected_int 41 | assert test_result_str == crc_entry.expected_str 42 | -------------------------------------------------------------------------------- /tests/tests_checksum/test_checksum_sum8.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.checksum.checksum_sum8 import Sum8Handler 9 | from reversebox.common.common import convert_int_to_hex_string 10 | from tests.common import CRCTestEntry 11 | 12 | sum8_handler = Sum8Handler() 13 | 14 | # fmt: off 15 | 16 | 17 | 18 | @pytest.mark.unittest 19 | def test_checksum_calculate_sum8_to_match_expected_result(): 20 | crc_data_list = [ 21 | CRCTestEntry(test_data=b"123456789", expected_int=221, expected_str="0xDD"), 22 | CRCTestEntry(test_data=b"Hello", expected_int=244, expected_str="0xF4"), 23 | CRCTestEntry(test_data=b"abcd", expected_int=138, expected_str="0x8A"), 24 | CRCTestEntry(test_data=b"Secret123@123", expected_int=210, expected_str="0xD2"), 25 | CRCTestEntry(test_data=b"\x01\x02\x03\x04\x05", expected_int=15, expected_str="0x0F"), 26 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 27 | CRCTestEntry(test_data=b" ", expected_int=32, expected_str="0x20"), 28 | CRCTestEntry(test_data=b"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", expected_int=140, expected_str="0x8C"), 29 | ] 30 | 31 | for crc_entry in crc_data_list: 32 | # check expected result from first execution 33 | test_result = sum8_handler.calculate_sum8(crc_entry.test_data) 34 | test_result_str = convert_int_to_hex_string(test_result) 35 | assert test_result == crc_entry.expected_int 36 | assert test_result_str == crc_entry.expected_str 37 | 38 | # check if result is the same after second execution 39 | test_result = sum8_handler.calculate_sum8(crc_entry.test_data) 40 | test_result_str = convert_int_to_hex_string(test_result) 41 | assert test_result == crc_entry.expected_int 42 | assert test_result_str == crc_entry.expected_str 43 | 44 | # fmt: on 45 | -------------------------------------------------------------------------------- /tests/tests_checksum/test_checksum_sum8_2s_complement.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.checksum.checksum_sum8_2s_complement import Sum82sComplementHandler 9 | from reversebox.common.common import convert_int_to_hex_string 10 | from tests.common import CRCTestEntry 11 | 12 | sum8_2s_complement_handler = Sum82sComplementHandler() 13 | 14 | # fmt: off 15 | 16 | 17 | 18 | @pytest.mark.unittest 19 | def test_checksum_calculate_sum8_2s_complement_to_match_expected_result(): 20 | crc_data_list = [ 21 | CRCTestEntry(test_data=b"123456789", expected_int=35, expected_str="0x23"), 22 | CRCTestEntry(test_data=b"Hello", expected_int=12, expected_str="0x0C"), 23 | CRCTestEntry(test_data=b"abcd", expected_int=118, expected_str="0x76"), 24 | CRCTestEntry(test_data=b"Secret123@123", expected_int=46, expected_str="0x2E"), 25 | CRCTestEntry(test_data=b"\x01\x02\x03\x04\x05", expected_int=241, expected_str="0xF1"), 26 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 27 | CRCTestEntry(test_data=b" ", expected_int=224, expected_str="0xE0"), 28 | CRCTestEntry(test_data=b"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", expected_int=116, expected_str="0x74"), 29 | ] 30 | 31 | for crc_entry in crc_data_list: 32 | # check expected result from first execution 33 | test_result = sum8_2s_complement_handler.calculate_sum8_2s_complement(crc_entry.test_data) 34 | test_result_str = convert_int_to_hex_string(test_result) 35 | assert test_result == crc_entry.expected_int 36 | assert test_result_str == crc_entry.expected_str 37 | 38 | # check if result is the same after second execution 39 | test_result = sum8_2s_complement_handler.calculate_sum8_2s_complement(crc_entry.test_data) 40 | test_result_str = convert_int_to_hex_string(test_result) 41 | assert test_result == crc_entry.expected_int 42 | assert test_result_str == crc_entry.expected_str 43 | 44 | # fmt: on 45 | -------------------------------------------------------------------------------- /tests/tests_checksum/test_checksum_unix_sum_bsd16.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | from faker import Faker 8 | 9 | from reversebox.checksum.checksum_unix_sum_bsd16 import UnixSumBSD16Handler 10 | from reversebox.common.common import convert_int_to_hex_string 11 | from tests.common import CRCTestEntry 12 | 13 | bsd16_handler = UnixSumBSD16Handler() 14 | fake = Faker() 15 | 16 | # fmt: off 17 | 18 | 19 | @pytest.mark.unittest 20 | def test_checksum_calculate_bsd16_to_match_expected_result(): 21 | crc_data_list = [ 22 | CRCTestEntry(test_data=b"Hello", expected_int=8401, expected_str="0x20D1"), 23 | CRCTestEntry(test_data=b"abcd", expected_int=8378, expected_str="0x20BA"), 24 | CRCTestEntry(test_data=b"123456789", expected_int=53615, expected_str="0xD16F"), 25 | CRCTestEntry(test_data=b"123", expected_int=16472, expected_str="0x4058"), 26 | CRCTestEntry(test_data=b"Secret123@456", expected_int=46332, expected_str="0xB4FC"), 27 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 28 | CRCTestEntry(test_data=b" ", expected_int=32, expected_str="0x20"), 29 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=272, expected_str="0x110"), 30 | CRCTestEntry(test_data=b"\x01\x02\x03\x04", expected_int=8198, expected_str="0x2006"), 31 | CRCTestEntry(test_data=b"!@#$%^&*()", expected_int=28372, expected_str="0x6ED4"), 32 | ] 33 | 34 | for crc_entry in crc_data_list: 35 | # check expected result from first execution 36 | test_result = bsd16_handler.calculate_unix_sum_bsd16(crc_entry.test_data) 37 | test_result_str = convert_int_to_hex_string(test_result) 38 | assert test_result == crc_entry.expected_int 39 | assert test_result_str == crc_entry.expected_str 40 | 41 | # check if result is the same after second execution 42 | test_result = bsd16_handler.calculate_unix_sum_bsd16(crc_entry.test_data) 43 | test_result_str = convert_int_to_hex_string(test_result) 44 | assert test_result == crc_entry.expected_int 45 | assert test_result_str == crc_entry.expected_str 46 | 47 | # fmt: on 48 | -------------------------------------------------------------------------------- /tests/tests_checksum/test_checksum_unix_sum_sysv.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.checksum.checksum_unix_sum_sysv import UnixSumSysVHandler 9 | from reversebox.common.common import convert_int_to_hex_string 10 | from tests.common import CRCTestEntry 11 | 12 | unix_sum_sysv_handler = UnixSumSysVHandler() 13 | 14 | # fmt: off 15 | 16 | # https://en.wikipedia.org/wiki/Sum_(Unix) 17 | # https://en.wikipedia.org/wiki/SYSV_checksum 18 | 19 | 20 | @pytest.mark.unittest 21 | def test_checksum_calculate_unix_sum_sysv_to_match_expected_result(): 22 | crc_data_list = [ 23 | CRCTestEntry(test_data=b"Hello", expected_int=500, expected_str="0x1F4"), 24 | CRCTestEntry(test_data=b"abcd", expected_int=394, expected_str="0x18A"), 25 | CRCTestEntry(test_data=b"Secret123@123", expected_int=978, expected_str="0x3D2"), 26 | CRCTestEntry(test_data=b"\x01\x02\x03\x04\x05", expected_int=15, expected_str="0x0F"), 27 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 28 | CRCTestEntry(test_data=b" ", expected_int=32, expected_str="0x20"), 29 | ] 30 | 31 | for crc_entry in crc_data_list: 32 | # check expected result from first execution 33 | test_result = unix_sum_sysv_handler.calculate_unix_sum_sysv(crc_entry.test_data) 34 | test_result_str = convert_int_to_hex_string(test_result) 35 | assert test_result == crc_entry.expected_int 36 | assert test_result_str == crc_entry.expected_str 37 | 38 | # check if result is the same after second execution 39 | test_result = unix_sum_sysv_handler.calculate_unix_sum_sysv(crc_entry.test_data) 40 | test_result_str = convert_int_to_hex_string(test_result) 41 | assert test_result == crc_entry.expected_int 42 | assert test_result_str == crc_entry.expected_str 43 | 44 | # fmt: on 45 | -------------------------------------------------------------------------------- /tests/tests_checksum/test_checksum_xor8.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.checksum.checksum_xor8 import Xor8Handler 9 | from reversebox.common.common import convert_int_to_hex_string 10 | from tests.common import CRCTestEntry 11 | 12 | xor8_handler = Xor8Handler() 13 | 14 | # fmt: off 15 | 16 | 17 | 18 | @pytest.mark.unittest 19 | def test_checksum_calculate_xor8_to_match_expected_result(): 20 | crc_data_list = [ 21 | CRCTestEntry(test_data=b"123456789", expected_int=49, expected_str="0x31"), 22 | CRCTestEntry(test_data=b"Hello", expected_int=66, expected_str="0x42"), 23 | CRCTestEntry(test_data=b"abcd", expected_int=4, expected_str="0x04"), 24 | CRCTestEntry(test_data=b"Secret123@123", expected_int=118, expected_str="0x76"), 25 | CRCTestEntry(test_data=b"\x01\x02\x03\x04\x05", expected_int=1, expected_str="0x01"), 26 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 27 | CRCTestEntry(test_data=b" ", expected_int=32, expected_str="0x20"), 28 | CRCTestEntry(test_data=b"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", expected_int=0, expected_str="0x00"), 29 | ] 30 | 31 | for crc_entry in crc_data_list: 32 | # check expected result from first execution 33 | test_result = xor8_handler.calculate_xor8(crc_entry.test_data) 34 | test_result_str = convert_int_to_hex_string(test_result) 35 | assert test_result == crc_entry.expected_int 36 | assert test_result_str == crc_entry.expected_str 37 | 38 | # check if result is the same after second execution 39 | test_result = xor8_handler.calculate_xor8(crc_entry.test_data) 40 | test_result_str = convert_int_to_hex_string(test_result) 41 | assert test_result == crc_entry.expected_int 42 | assert test_result_str == crc_entry.expected_str 43 | 44 | # fmt: on 45 | -------------------------------------------------------------------------------- /tests/tests_common/test_common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023-2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import ( 9 | calculate_padding_length, 10 | get_file_extension, 11 | get_file_extension_uppercase, 12 | ) 13 | from tests.common import FileExtensionTestEntry, PaddingTestEntry 14 | 15 | # fmt: off 16 | 17 | 18 | @pytest.mark.unittest 19 | def test_common_calculate_padding(): 20 | padding_data_list = [ 21 | PaddingTestEntry(test_offset=10060, test_div=2048, expected_padding=180), 22 | ] 23 | for padding_entry in padding_data_list: 24 | test_result = calculate_padding_length(padding_entry.test_offset, padding_entry.test_div) 25 | assert test_result == padding_entry.expected_padding 26 | 27 | 28 | @pytest.mark.unittest 29 | def test_common_get_file_extension(): 30 | extension_entries_list = [ 31 | FileExtensionTestEntry(file_name="file1.dds", expected_file_extension=".dds"), 32 | FileExtensionTestEntry(file_name="file2.png", expected_file_extension=".png"), 33 | FileExtensionTestEntry(file_name="file3.bmp.jpg.png.gif", expected_file_extension=".gif"), 34 | FileExtensionTestEntry(file_name="file4", expected_file_extension=""), 35 | ] 36 | for extension_entry in extension_entries_list: 37 | test_result = get_file_extension(extension_entry.file_name) 38 | assert test_result == extension_entry.expected_file_extension 39 | 40 | 41 | @pytest.mark.unittest 42 | def test_common_get_file_extension_uppercase(): 43 | extension_entries_list = [ 44 | FileExtensionTestEntry(file_name="file1.dds", expected_file_extension="DDS"), 45 | FileExtensionTestEntry(file_name="file2.png", expected_file_extension="PNG"), 46 | FileExtensionTestEntry(file_name="file3.bmp.jpg.png.gif", expected_file_extension="GIF"), 47 | FileExtensionTestEntry(file_name="file4", expected_file_extension=""), 48 | ] 49 | for extension_entry in extension_entries_list: 50 | test_result = get_file_extension_uppercase(extension_entry.file_name) 51 | assert test_result == extension_entry.expected_file_extension 52 | 53 | 54 | # fmt: on 55 | -------------------------------------------------------------------------------- /tests/tests_compression/compression_data/fake_file01_zlib_compressed.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_compression/compression_data/fake_file01_zlib_compressed.bin -------------------------------------------------------------------------------- /tests/tests_compression/compression_data/fake_file01_zlib_uncompressed.bin: -------------------------------------------------------------------------------- 1 | AAAABBBBCCCC 2 | -------------------------------------------------------------------------------- /tests/tests_compression/compression_data/fake_file02_lzo_compressed.bin: -------------------------------------------------------------------------------- 1 | AABACA 2 |  -------------------------------------------------------------------------------- /tests/tests_compression/compression_data/fake_file02_lzo_uncompressed.bin: -------------------------------------------------------------------------------- 1 | AAAABBBBCCCC 2 | -------------------------------------------------------------------------------- /tests/tests_compression/compression_data/fake_file03_refpack_compressed.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_compression/compression_data/fake_file03_refpack_compressed.bin -------------------------------------------------------------------------------- /tests/tests_compression/compression_data/fake_file03_refpack_uncompressed.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_compression/compression_data/fake_file03_refpack_uncompressed.bin -------------------------------------------------------------------------------- /tests/tests_compression/test_compression_jcalg1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.compression.compression_jcalg1 import JCALG1Handler 9 | 10 | 11 | @pytest.mark.unittest 12 | def test_open_and_compress_file_with_jcalg1(): 13 | # TODO - this test needs to be rewritten... 14 | test_data = b"ABCD" 15 | jcalg1_handler = JCALG1Handler() 16 | jcalg1_handler.compress_data(test_data) 17 | assert True 18 | -------------------------------------------------------------------------------- /tests/tests_compression/test_compression_lzo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.compression.compression_lzo import LZOHandler 11 | 12 | 13 | @pytest.mark.unittest 14 | def test_open_and_compress_file_with_lzo(): 15 | uncompressed_file_path = os.path.join( 16 | os.path.dirname(__file__), "compression_data\\fake_file02_lzo_uncompressed.bin" 17 | ) 18 | compressed_file_path = os.path.join( 19 | os.path.dirname(__file__), "compression_data\\fake_file02_lzo_compressed.bin" 20 | ) 21 | 22 | uncompressed_input_file_data = open(uncompressed_file_path, "rb").read() 23 | compressed_input_file_data = open(compressed_file_path, "rb").read() 24 | 25 | lzo_handler = LZOHandler() 26 | compressed_data = lzo_handler.compress_data(uncompressed_input_file_data) 27 | 28 | assert compressed_data == compressed_input_file_data 29 | 30 | 31 | @pytest.mark.unittest 32 | def test_open_and_decompress_file_with_lzo(): 33 | uncompressed_file_path = os.path.join( 34 | os.path.dirname(__file__), "compression_data\\fake_file02_lzo_uncompressed.bin" 35 | ) 36 | compressed_file_path = os.path.join( 37 | os.path.dirname(__file__), "compression_data\\fake_file02_lzo_compressed.bin" 38 | ) 39 | 40 | uncompressed_input_file_data = open(uncompressed_file_path, "rb").read() 41 | compressed_input_file_data = open(compressed_file_path, "rb").read() 42 | 43 | lzo_handler = LZOHandler() 44 | uncompressed_data = lzo_handler.decompress_data(compressed_input_file_data) 45 | assert uncompressed_data == uncompressed_input_file_data 46 | -------------------------------------------------------------------------------- /tests/tests_compression/test_compression_refpack.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.compression.compression_refpack import RefpackHandler 11 | 12 | 13 | @pytest.mark.unittest 14 | def test_open_and_compress_file_with_refpack(): 15 | uncompressed_file_path = os.path.join( 16 | os.path.dirname(__file__), 17 | "compression_data\\fake_file03_refpack_uncompressed.bin", 18 | ) 19 | compressed_file_path = os.path.join( 20 | os.path.dirname(__file__), 21 | "compression_data\\fake_file03_refpack_compressed.bin", 22 | ) 23 | 24 | uncompressed_input_file_data = open(uncompressed_file_path, "rb").read() 25 | compressed_input_file_data = open(compressed_file_path, "rb").read() 26 | 27 | refpack_handler = RefpackHandler() 28 | compressed_data = refpack_handler.compress_data(uncompressed_input_file_data) 29 | 30 | assert len(compressed_data) <= len(compressed_input_file_data) + 100 31 | assert len(compressed_data) > 0 32 | assert ( 33 | compressed_data[:2] == compressed_input_file_data[:2] 34 | ) # check for 10FB signature 35 | 36 | 37 | @pytest.mark.unittest 38 | def test_open_and_decompress_file_with_refpack(): 39 | uncompressed_file_path = os.path.join( 40 | os.path.dirname(__file__), 41 | "compression_data\\fake_file03_refpack_uncompressed.bin", 42 | ) 43 | compressed_file_path = os.path.join( 44 | os.path.dirname(__file__), 45 | "compression_data\\fake_file03_refpack_compressed.bin", 46 | ) 47 | 48 | uncompressed_input_file_data = open(uncompressed_file_path, "rb").read() 49 | compressed_input_file_data = open(compressed_file_path, "rb").read() 50 | 51 | refpack_handler = RefpackHandler() 52 | uncompressed_data = refpack_handler.decompress_data(compressed_input_file_data) 53 | assert len(uncompressed_data) == len(uncompressed_input_file_data) 54 | assert type(uncompressed_data) is type(uncompressed_input_file_data) 55 | assert uncompressed_data[:10] == uncompressed_input_file_data[:10] 56 | assert uncompressed_data[-10:] == uncompressed_input_file_data[-10:] 57 | -------------------------------------------------------------------------------- /tests/tests_compression/test_compression_zlib.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.compression.compression_zlib import ZLIBHandler 11 | 12 | 13 | @pytest.mark.unittest 14 | def test_open_and_compress_file_with_zlib(): 15 | uncompressed_file_path = os.path.join( 16 | os.path.dirname(__file__), "compression_data\\fake_file01_zlib_uncompressed.bin" 17 | ) 18 | compressed_file_path = os.path.join( 19 | os.path.dirname(__file__), "compression_data\\fake_file01_zlib_compressed.bin" 20 | ) 21 | 22 | uncompressed_input_file_data = open(uncompressed_file_path, "rb").read() 23 | compressed_input_file_data = open(compressed_file_path, "rb").read() 24 | 25 | zlib_handler = ZLIBHandler() 26 | compressed_data = zlib_handler.compress_data(uncompressed_input_file_data) 27 | 28 | assert compressed_data == compressed_input_file_data 29 | 30 | 31 | @pytest.mark.unittest 32 | def test_open_and_decompress_file_with_zlib(): 33 | uncompressed_file_path = os.path.join( 34 | os.path.dirname(__file__), "compression_data\\fake_file01_zlib_uncompressed.bin" 35 | ) 36 | compressed_file_path = os.path.join( 37 | os.path.dirname(__file__), "compression_data\\fake_file01_zlib_compressed.bin" 38 | ) 39 | 40 | uncompressed_input_file_data = open(uncompressed_file_path, "rb").read() 41 | compressed_input_file_data = open(compressed_file_path, "rb").read() 42 | 43 | zlib_handler = ZLIBHandler() 44 | uncompressed_data = zlib_handler.decompress_data(compressed_input_file_data) 45 | assert uncompressed_data == uncompressed_input_file_data 46 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc16_arc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc16_arc import CRC16Handler 10 | from tests.common import CRCTestEntry 11 | 12 | crc16_handler = CRC16Handler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc16_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry(test_data=b"123456789", expected_int=47933, expected_str="0xBB3D"), 19 | CRCTestEntry(test_data=b"123", expected_int=47620, expected_str="0xBA04"), 20 | CRCTestEntry( 21 | test_data=b"Hello Python", expected_int=44785, expected_str="0xAEF1" 22 | ), 23 | CRCTestEntry( 24 | test_data=b"Secret123@123", expected_int=37307, expected_str="0x91BB" 25 | ), 26 | CRCTestEntry(test_data=b"abcdefgh", expected_int=29737, expected_str="0x7429"), 27 | CRCTestEntry(test_data=b"ABCDEFGH", expected_int=7070, expected_str="0x1B9E"), 28 | CRCTestEntry(test_data=b"@@@@@", expected_int=60197, expected_str="0xEB25"), 29 | CRCTestEntry( 30 | test_data=b"C:\\Users\\user\\Desktop\\audio.wav", 31 | expected_int=40361, 32 | expected_str="0x9DA9", 33 | ), 34 | CRCTestEntry( 35 | test_data=b"/etc/passwd", expected_int=15164, expected_str="0x3B3C" 36 | ), 37 | CRCTestEntry(test_data=b" ", expected_int=55297, expected_str="0xD801"), 38 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=54078, expected_str="0xD33E"), 39 | CRCTestEntry( 40 | test_data=b"\x01\x02\x03\x04", expected_int=4001, expected_str="0xFA1" 41 | ), 42 | ] 43 | 44 | for crc_entry in crc_data_list: 45 | # check expected result from first execution 46 | test_result = crc16_handler.calculate_crc16(crc_entry.test_data) 47 | test_result_str = convert_int_to_hex_string(test_result) 48 | assert test_result == crc_entry.expected_int 49 | assert test_result_str == crc_entry.expected_str 50 | 51 | # check if result is the same after second execution 52 | test_result = crc16_handler.calculate_crc16(crc_entry.test_data) 53 | test_result_str = convert_int_to_hex_string(test_result) 54 | assert test_result == crc_entry.expected_int 55 | assert test_result_str == crc_entry.expected_str 56 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc16_dnp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc16_dnp import CRC16DNPHandler 10 | from tests.common import CRCTestEntry 11 | 12 | crc16_dnp_handler = CRC16DNPHandler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc16_dnp_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry(test_data=b"123456789", expected_int=33514, expected_str="0x82EA"), 19 | CRCTestEntry(test_data=b"123", expected_int=63270, expected_str="0xF726"), 20 | CRCTestEntry(test_data=b"", expected_int=65535, expected_str="0xFFFF"), 21 | CRCTestEntry(test_data=b" ", expected_int=20694, expected_str="0x50D6"), 22 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=39931, expected_str="0x9BFB"), 23 | CRCTestEntry( 24 | test_data=b"\x01\x02\x03\x04", expected_int=46183, expected_str="0xB467" 25 | ), 26 | ] 27 | 28 | for crc_entry in crc_data_list: 29 | # check expected result from first execution 30 | test_result = crc16_dnp_handler.calculate_crc16_dnp(crc_entry.test_data) 31 | test_result_str = convert_int_to_hex_string(test_result) 32 | assert test_result == crc_entry.expected_int 33 | assert test_result_str == crc_entry.expected_str 34 | 35 | # check if result is the same after second execution 36 | test_result = crc16_dnp_handler.calculate_crc16_dnp(crc_entry.test_data) 37 | test_result_str = convert_int_to_hex_string(test_result) 38 | assert test_result == crc_entry.expected_int 39 | assert test_result_str == crc_entry.expected_str 40 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc16_ea_crcf.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc16_ea_crcf import CRCEACRCFHandler 10 | from tests.common import CRCTestEntry 11 | 12 | crc_ea_crcf_handler = CRCEACRCFHandler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc16_ea_crcf_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry(test_data=b"123456789", expected_int=10436, expected_str="0x28C4"), 19 | CRCTestEntry(test_data=b"123", expected_int=32358, expected_str="0x7E66"), 20 | CRCTestEntry(test_data=b"abcd", expected_int=53458, expected_str="0xD0D2"), 21 | CRCTestEntry( 22 | test_data=b"Secret123@123", expected_int=8315, expected_str="0x207B" 23 | ), 24 | CRCTestEntry(test_data=b"", expected_int=64490, expected_str="0xFBEA"), 25 | CRCTestEntry(test_data=b" ", expected_int=22395, expected_str="0x577B"), 26 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=12336, expected_str="0x3030"), 27 | CRCTestEntry( 28 | test_data=b"\x01\x02\x03\x04", expected_int=59108, expected_str="0xE6E4" 29 | ), 30 | ] 31 | 32 | for crc_entry in crc_data_list: 33 | # check expected result from first execution 34 | test_result = crc_ea_crcf_handler.calculate_crc16_ea_crcf(crc_entry.test_data) 35 | test_result_str = convert_int_to_hex_string(test_result) 36 | assert test_result == crc_entry.expected_int 37 | assert test_result_str == crc_entry.expected_str 38 | 39 | # check if result is the same after second execution 40 | test_result = crc_ea_crcf_handler.calculate_crc16_ea_crcf(crc_entry.test_data) 41 | test_result_str = convert_int_to_hex_string(test_result) 42 | assert test_result == crc_entry.expected_int 43 | assert test_result_str == crc_entry.expected_str 44 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc16_kermit.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc16_kermit import CRC16KermitHandler 10 | from tests.common import CRCTestEntry 11 | 12 | crc16_kermit_handler = CRC16KermitHandler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc16_kermit_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry(test_data=b"123456789", expected_int=35105, expected_str="0x8921"), 19 | CRCTestEntry(test_data=b"123", expected_int=30810, expected_str="0x785A"), 20 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 21 | CRCTestEntry(test_data=b" ", expected_int=545, expected_str="0x221"), 22 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=55129, expected_str="0xD759"), 23 | CRCTestEntry( 24 | test_data=b"\x01\x02\x03\x04", expected_int=20421, expected_str="0x4FC5" 25 | ), 26 | ] 27 | 28 | for crc_entry in crc_data_list: 29 | # check expected result from first execution 30 | test_result = crc16_kermit_handler.calculate_crc16_kermit(crc_entry.test_data) 31 | test_result_str = convert_int_to_hex_string(test_result) 32 | assert test_result == crc_entry.expected_int 33 | assert test_result_str == crc_entry.expected_str 34 | 35 | # check if result is the same after second execution 36 | test_result = crc16_kermit_handler.calculate_crc16_kermit(crc_entry.test_data) 37 | test_result_str = convert_int_to_hex_string(test_result) 38 | assert test_result == crc_entry.expected_int 39 | assert test_result_str == crc_entry.expected_str 40 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc16_modbus.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc16_modbus import CRC16MODBUSHandler 10 | from tests.common import CRCTestEntry 11 | 12 | crc16_modbus_handler = CRC16MODBUSHandler() 13 | 14 | # fmt: off 15 | 16 | 17 | @pytest.mark.unittest 18 | def test_crc_calculate_crc16_modbus_to_match_expected_result(): 19 | crc_data_list = [ 20 | CRCTestEntry(test_data=b"123456789", expected_int=19255, expected_str="0x4B37"), 21 | CRCTestEntry(test_data=b"123", expected_int=31349, expected_str="0x7A75"), 22 | CRCTestEntry(test_data=b"abcd", expected_int=7575, expected_str="0x1D97"), 23 | CRCTestEntry(test_data=b"Secret123@123", expected_int=31416, expected_str="0x7AB8"), 24 | CRCTestEntry(test_data=b"", expected_int=65535, expected_str="0xFFFF"), 25 | CRCTestEntry(test_data=b" ", expected_int=39102, expected_str="0x98BE"), 26 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=25407, expected_str="0x633F"), 27 | CRCTestEntry(test_data=b"\x01\x02\x03\x04", expected_int=11169, expected_str="0x2BA1"), 28 | ] 29 | 30 | for crc_entry in crc_data_list: 31 | # check expected result from first execution 32 | test_result = crc16_modbus_handler.calculate_crc16_modbus(crc_entry.test_data) 33 | test_result_str = convert_int_to_hex_string(test_result) 34 | assert test_result == crc_entry.expected_int 35 | assert test_result_str == crc_entry.expected_str 36 | 37 | # check if result is the same after second execution 38 | test_result = crc16_modbus_handler.calculate_crc16_modbus(crc_entry.test_data) 39 | test_result_str = convert_int_to_hex_string(test_result) 40 | assert test_result == crc_entry.expected_int 41 | assert test_result_str == crc_entry.expected_str 42 | 43 | # fmt: on 44 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc16_sick.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc16_sick import CRC16SICKHandler 10 | from tests.common import CRCTestEntry 11 | 12 | crc16_sick_handler = CRC16SICKHandler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc16_sick_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry(test_data=b"123456789", expected_int=22182, expected_str="0x56A6"), 19 | CRCTestEntry(test_data=b"123", expected_int=37712, expected_str="0x9350"), 20 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 21 | CRCTestEntry(test_data=b" ", expected_int=8192, expected_str="0x2000"), 22 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=61355, expected_str="0xEFAB"), 23 | CRCTestEntry( 24 | test_data=b"\x01\x02\x03\x04", expected_int=515, expected_str="0x203" 25 | ), 26 | ] 27 | 28 | for crc_entry in crc_data_list: 29 | # check expected result from first execution 30 | test_result = crc16_sick_handler.calculate_crc16_sick(crc_entry.test_data) 31 | test_result_str = convert_int_to_hex_string(test_result) 32 | assert test_result == crc_entry.expected_int 33 | assert test_result_str == crc_entry.expected_str 34 | 35 | # check if result is the same after second execution 36 | test_result = crc16_sick_handler.calculate_crc16_sick(crc_entry.test_data) 37 | test_result_str = convert_int_to_hex_string(test_result) 38 | assert test_result == crc_entry.expected_int 39 | assert test_result_str == crc_entry.expected_str 40 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc32_asobo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc32_asobo import CRC32AsoboHandler 10 | from tests.common import CRCTestEntry 11 | 12 | crc32_asobo_handler = CRC32AsoboHandler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc32_asobo_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry( 19 | test_data=b"abcd", expected_int=2161055104, expected_str="0x80CF1580" 20 | ), 21 | CRCTestEntry( 22 | test_data=b"123456789", expected_int=4027864427, expected_str="0xF014556B" 23 | ), 24 | CRCTestEntry( 25 | test_data=b"123", expected_int=1589920508, expected_str="0x5EC442FC" 26 | ), 27 | CRCTestEntry( 28 | test_data=b"Secret123@456", 29 | expected_int=3452879509, 30 | expected_str="0xCDCEC295", 31 | ), 32 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 33 | CRCTestEntry( 34 | test_data=b" ", expected_int=2552477408, expected_str="0x9823B6E0" 35 | ), 36 | CRCTestEntry( 37 | test_data=b"\xAA\xBB", expected_int=2704819662, expected_str="0xA13845CE" 38 | ), 39 | CRCTestEntry( 40 | test_data=b"\x01\x02\x03\x04", 41 | expected_int=1026533933, 42 | expected_str="0x3D2FAA2D", 43 | ), 44 | CRCTestEntry( 45 | test_data=b"!@#$%^&*()", expected_int=2350173499, expected_str="0x8C14CD3B" 46 | ), 47 | ] 48 | 49 | for crc_entry in crc_data_list: 50 | # check expected result from first execution 51 | test_result = crc32_asobo_handler.calculate_crc32_asobo(crc_entry.test_data) 52 | test_result_str = convert_int_to_hex_string(test_result) 53 | assert test_result == crc_entry.expected_int 54 | assert test_result_str == crc_entry.expected_str 55 | 56 | # check if result is the same after second execution 57 | test_result = crc32_asobo_handler.calculate_crc32_asobo(crc_entry.test_data) 58 | test_result_str = convert_int_to_hex_string(test_result) 59 | assert test_result == crc_entry.expected_int 60 | assert test_result_str == crc_entry.expected_str 61 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc64_go_iso.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc64_go_iso import CRC64GoISOHandler 10 | from tests.common import CRCTestEntry 11 | 12 | crc64_handler = CRC64GoISOHandler() 13 | 14 | 15 | # fmt: off 16 | @pytest.mark.unittest 17 | def test_crc_calculate_crc64_go_iso_to_match_expected_result(): 18 | crc_data_list = [ 19 | CRCTestEntry(test_data=b"123456789", expected_int=13333283586479230977, expected_str="0xB90956C775A41001"), 20 | CRCTestEntry(test_data=b"123", expected_int=4612164443424423936, expected_str="0x4001B32000000000"), 21 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 22 | CRCTestEntry(test_data=b" ", expected_int=6453658266021920768, expected_str="0x5990000000000000"), 23 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=10758941180113715200, expected_str="0x954F700000000000"), 24 | CRCTestEntry(test_data=b"\x01\x02\x03\x04", expected_int=7583266405215109120, expected_str="0x693D2C9E20000000"), 25 | CRCTestEntry(test_data=b"IHATEMATH", expected_int=2049228168195101230, expected_str="0x1C70522964FE522E") 26 | ] 27 | 28 | for crc_entry in crc_data_list: 29 | # check expected result from first execution 30 | test_result = crc64_handler.calculate_crc64(crc_entry.test_data) 31 | test_result_str = convert_int_to_hex_string(test_result) 32 | assert test_result == crc_entry.expected_int 33 | assert test_result_str == crc_entry.expected_str 34 | 35 | # check if result is the same after second execution 36 | test_result = crc64_handler.calculate_crc64(crc_entry.test_data) 37 | test_result_str = convert_int_to_hex_string(test_result) 38 | assert test_result == crc_entry.expected_int 39 | assert test_result_str == crc_entry.expected_str 40 | 41 | # fmt: on 42 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc8.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc8 import CRC8Handler 10 | from tests.common import CRCTestEntry 11 | 12 | crc8_handler = CRC8Handler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc8_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry(test_data=b"abcd", expected_int=161, expected_str="0xA1"), 19 | CRCTestEntry(test_data=b"123456789", expected_int=244, expected_str="0xF4"), 20 | CRCTestEntry(test_data=b"123", expected_int=192, expected_str="0xC0"), 21 | CRCTestEntry(test_data=b"Secret123@456", expected_int=154, expected_str="0x9A"), 22 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 23 | CRCTestEntry(test_data=b" ", expected_int=224, expected_str="0xE0"), 24 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=178, expected_str="0xB2"), 25 | CRCTestEntry( 26 | test_data=b"\x01\x02\x03\x04", expected_int=227, expected_str="0xE3" 27 | ), 28 | CRCTestEntry(test_data=b"!@#$%^&*()", expected_int=103, expected_str="0x67"), 29 | ] 30 | 31 | for crc_entry in crc_data_list: 32 | # check expected result from first execution 33 | test_result = crc8_handler.calculate_crc8(crc_entry.test_data) 34 | test_result_str = convert_int_to_hex_string(test_result) 35 | assert test_result == crc_entry.expected_int 36 | assert test_result_str == crc_entry.expected_str 37 | 38 | # check if result is the same after second execution 39 | test_result = crc8_handler.calculate_crc8(crc_entry.test_data) 40 | test_result_str = convert_int_to_hex_string(test_result) 41 | assert test_result == crc_entry.expected_int 42 | assert test_result_str == crc_entry.expected_str 43 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc8_cdma_2000.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc8 import CRC8Handler 10 | from tests.common import CRCTestEntry 11 | 12 | crc8_handler = CRC8Handler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc8_cdma_2000_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry(test_data=b"abcd", expected_int=13, expected_str="0x0D"), 19 | CRCTestEntry(test_data=b"123456789", expected_int=218, expected_str="0xDA"), 20 | CRCTestEntry(test_data=b"123", expected_int=23, expected_str="0x17"), 21 | CRCTestEntry(test_data=b"Secret123@456", expected_int=210, expected_str="0xD2"), 22 | CRCTestEntry(test_data=b"", expected_int=255, expected_str="0xFF"), 23 | CRCTestEntry(test_data=b" ", expected_int=31, expected_str="0x1F"), 24 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=29, expected_str="0x1D"), 25 | CRCTestEntry( 26 | test_data=b"\x01\x02\x03\x04", expected_int=103, expected_str="0x67" 27 | ), 28 | CRCTestEntry(test_data=b"!@#$%^&*()", expected_int=167, expected_str="0xA7"), 29 | ] 30 | 31 | for crc_entry in crc_data_list: 32 | # check expected result from first execution 33 | test_result = crc8_handler.calculate_crc8_cdma_2000(crc_entry.test_data) 34 | test_result_str = convert_int_to_hex_string(test_result) 35 | assert test_result == crc_entry.expected_int 36 | assert test_result_str == crc_entry.expected_str 37 | 38 | # check if result is the same after second execution 39 | test_result = crc8_handler.calculate_crc8_cdma_2000(crc_entry.test_data) 40 | test_result_str = convert_int_to_hex_string(test_result) 41 | assert test_result == crc_entry.expected_int 42 | assert test_result_str == crc_entry.expected_str 43 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc8_darc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc8 import CRC8Handler 10 | from tests.common import CRCTestEntry 11 | 12 | crc8_handler = CRC8Handler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc8_darc_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry(test_data=b"abcd", expected_int=192, expected_str="0xC0"), 19 | CRCTestEntry(test_data=b"123456789", expected_int=21, expected_str="0x15"), 20 | CRCTestEntry(test_data=b"123", expected_int=215, expected_str="0xD7"), 21 | CRCTestEntry(test_data=b"Secret123@456", expected_int=180, expected_str="0xB4"), 22 | CRCTestEntry(test_data=b"", expected_int=0, expected_str="0x00"), 23 | CRCTestEntry(test_data=b" ", expected_int=39, expected_str="0x27"), 24 | CRCTestEntry(test_data=b"\xAA\xBB", expected_int=20, expected_str="0x14"), 25 | CRCTestEntry( 26 | test_data=b"\x01\x02\x03\x04", expected_int=2, expected_str="0x02" 27 | ), 28 | CRCTestEntry(test_data=b"!@#$%^&*()", expected_int=255, expected_str="0xFF"), 29 | ] 30 | 31 | for crc_entry in crc_data_list: 32 | # check expected result from first execution 33 | test_result = crc8_handler.calculate_crc8_darc(crc_entry.test_data) 34 | test_result_str = convert_int_to_hex_string(test_result) 35 | assert test_result == crc_entry.expected_int 36 | assert test_result_str == crc_entry.expected_str 37 | 38 | # check if result is the same after second execution 39 | test_result = crc8_handler.calculate_crc8_darc(crc_entry.test_data) 40 | test_result_str = convert_int_to_hex_string(test_result) 41 | assert test_result == crc_entry.expected_int 42 | assert test_result_str == crc_entry.expected_str 43 | -------------------------------------------------------------------------------- /tests/tests_crc/test_crc_unix_cksum.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.common.common import convert_int_to_hex_string 9 | from reversebox.crc.crc_unix_cksum import CRCUnixCKSumHandler 10 | from tests.common import CRCTestEntry 11 | 12 | crc_unix_cksum_handler = CRCUnixCKSumHandler() 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_crc_calculate_crc_unix_cksum_to_match_expected_result(): 17 | crc_data_list = [ 18 | CRCTestEntry( 19 | test_data=b"123456789", expected_int=930766865, expected_str="0x377A6011" 20 | ), 21 | CRCTestEntry( 22 | test_data=b"123", expected_int=1411111867, expected_str="0x541BDBBB" 23 | ), 24 | CRCTestEntry(test_data=b"", expected_int=4294967295, expected_str="0xFFFFFFFF"), 25 | CRCTestEntry( 26 | test_data=b" ", expected_int=3684553838, expected_str="0xDB9DD46E" 27 | ), 28 | CRCTestEntry( 29 | test_data=b"\xAA\xBB", expected_int=1721189355, expected_str="0x669743EB" 30 | ), 31 | CRCTestEntry( 32 | test_data=b"\x01\x02\x03\x04", 33 | expected_int=1587614295, 34 | expected_str="0x5EA11257", 35 | ), 36 | ] 37 | 38 | for crc_entry in crc_data_list: 39 | # check expected result from first execution 40 | test_result = crc_unix_cksum_handler.calculate_crc_cksum(crc_entry.test_data) 41 | test_result_str = convert_int_to_hex_string(test_result) 42 | assert test_result == crc_entry.expected_int 43 | assert test_result_str == crc_entry.expected_str 44 | 45 | # check if result is the same after second execution 46 | test_result = crc_unix_cksum_handler.calculate_crc_cksum(crc_entry.test_data) 47 | test_result_str = convert_int_to_hex_string(test_result) 48 | assert test_result == crc_entry.expected_int 49 | assert test_result_str == crc_entry.expected_str 50 | -------------------------------------------------------------------------------- /tests/tests_encryption/test_encryption_rot13.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.encryption.encryption_rot13 import ( 9 | rot13_cipher_decrypt, 10 | rot13_cipher_encrypt, 11 | ) 12 | from tests.common import ROT13TestEntry 13 | 14 | 15 | @pytest.mark.unittest 16 | def test_encryption_rot13_to_match_expected_result(): 17 | rot_data_list = [ 18 | ROT13TestEntry( 19 | test_data=b"\x01\x02\x03", key=b"abc", expected_result=b"\x62\x64\x66" 20 | ), 21 | ROT13TestEntry( 22 | test_data=b"abcd", 23 | key=b"abc", 24 | expected_result=b"\xC2\xC4\xC6\xC5", 25 | ), 26 | ROT13TestEntry( 27 | test_data=b"Secret123#123", 28 | key=b"\xAA\xAA\xAA\xAA\xBB\xBB", 29 | expected_result=b"\xFD\x0F\x0D\x1C\x20\x2F\xDB\xDC\xDD\xCD\xEC\xED\xDD", 30 | ), 31 | ] 32 | 33 | for test_entry in rot_data_list: 34 | encrypted_test_result = rot13_cipher_encrypt( 35 | test_entry.test_data, test_entry.key 36 | ) 37 | assert encrypted_test_result == test_entry.expected_result 38 | 39 | decrypted_test_result = rot13_cipher_decrypt( 40 | encrypted_test_result, test_entry.key 41 | ) 42 | assert decrypted_test_result == test_entry.test_data 43 | -------------------------------------------------------------------------------- /tests/tests_encryption/test_encryption_xor_basic_key_guesser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.encryption.encryption_xor_basic_key_guesser import xor_basic_guess_key 9 | from tests.common import XORGuesserEntry 10 | 11 | 12 | # fmt: off 13 | @pytest.mark.unittest 14 | def test_encryption_xor_basic_guesser_to_match_expected_xor_key_to_be_found(): 15 | xor_data_list = [ 16 | XORGuesserEntry(encrypted_data=b"\x78\x23", decrypted_data=b"\x38\x63", max_key_length=2, expected_xor_key=b"\x40"), 17 | XORGuesserEntry(encrypted_data=b"\x2A\x13\x07\x37", decrypted_data=b"\x4C\x75\x61\x51", max_key_length=2, expected_xor_key=b"\x66"), # LuaQ file 18 | ] 19 | 20 | for test_entry in xor_data_list: 21 | found_xor_key = xor_basic_guess_key(test_entry.encrypted_data, test_entry.decrypted_data, test_entry.max_key_length) 22 | assert found_xor_key == test_entry.expected_xor_key 23 | # fmt: on 24 | -------------------------------------------------------------------------------- /tests/tests_encryption/test_encryption_xor_gianas_return_zda.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | # fmt: off 7 | 8 | import pytest 9 | 10 | from reversebox.encryption.encryption_xor_gianas_return_zda import ( 11 | xor_zda_decrypt_data, 12 | xor_zda_encrypt_data, 13 | ) 14 | from tests.common import XORTestEntry 15 | 16 | 17 | @pytest.mark.unittest 18 | def test_encryption_xor_gianas_return_zda_to_match_expected_result(): 19 | xor_data_list = [ 20 | XORTestEntry(test_data=b"abcd", key=b"", expected_result=b"\xDA\x03\x01\x07"), 21 | XORTestEntry(test_data=b"BM", key=b"", expected_result=b"\xF9\x0F"), 22 | ] 23 | 24 | for test_entry in xor_data_list: 25 | encrypted_test_result = xor_zda_encrypt_data(test_entry.test_data) 26 | assert encrypted_test_result == test_entry.expected_result 27 | 28 | decrypted_test_result = xor_zda_decrypt_data(encrypted_test_result) 29 | assert decrypted_test_result == test_entry.test_data 30 | 31 | 32 | # fmt: on 33 | -------------------------------------------------------------------------------- /tests/tests_encryption/test_encryption_xor_retro64_eco.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | # fmt: off 7 | import pytest 8 | 9 | from reversebox.encryption.encryption_xor_retro64_eco import xor_cipher_retro64_eco 10 | from tests.common import XORRetro64ECOTestEntry 11 | 12 | # fmt: on 13 | 14 | # Tests for XOR Cipher used in Retro64 *.ECO files 15 | # Read more here: http://wiki.xentax.com/index.php/Retro64_ECO 16 | 17 | 18 | @pytest.mark.unittest 19 | def test_encryption_xor_retro64_to_match_expected_result(): 20 | xor_data_list = [ 21 | XORRetro64ECOTestEntry( 22 | test_data=b"abcd", key=49152, expected_result=b"\x18\xFC\x5C\xEF" 23 | ), 24 | XORRetro64ECOTestEntry( 25 | test_data=b"aaaa", key=49152, expected_result=b"\x18\xFF\x5E\xEA" 26 | ), 27 | XORRetro64ECOTestEntry( 28 | test_data=b"abcd", key=13, expected_result=b"\x2B\x05\xA7\xD5" 29 | ), 30 | XORRetro64ECOTestEntry( 31 | test_data=b"aaaa", key=13, expected_result=b"\x2B\x06\xA5\xD0" 32 | ), 33 | XORRetro64ECOTestEntry( 34 | test_data=b"ABCDEFGH", 35 | key=8000, 36 | expected_result=b"\xC4\xA5\xBA\xDE\x62\x44\x8E\xB6", 37 | ), 38 | XORRetro64ECOTestEntry( 39 | test_data=b"ABCD", key=0, expected_result=b"\x4A\xF4\x44\x8A" 40 | ), 41 | XORRetro64ECOTestEntry( 42 | test_data=b"ABCD", key=1, expected_result=b"\x95\xE4\x41\x88" 43 | ), 44 | XORRetro64ECOTestEntry( 45 | test_data=b"!@#$%", key=1234, expected_result=b"\x1C\x19\xC9\xAA\xB0" 46 | ), 47 | XORRetro64ECOTestEntry(test_data=b"", key=1234, expected_result=b""), 48 | XORRetro64ECOTestEntry(test_data=b"", key=436346, expected_result=b""), 49 | XORRetro64ECOTestEntry( 50 | test_data=b"abcd", key=99999999999, expected_result=b"\x34\x5B\xA9\xA2" 51 | ), 52 | ] 53 | 54 | for test_entry in xor_data_list: 55 | encrypted_test_result = xor_cipher_retro64_eco( 56 | test_entry.test_data, test_entry.key 57 | ) 58 | assert encrypted_test_result == test_entry.expected_result 59 | 60 | decrypted_test_result = xor_cipher_retro64_eco( 61 | encrypted_test_result, test_entry.key 62 | ) 63 | assert decrypted_test_result == test_entry.test_data 64 | -------------------------------------------------------------------------------- /tests/tests_image/image_files/compression_packbits.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/compression_packbits.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/compression_rle_tga.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/compression_rle_tga.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/compression_zlib.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/compression_zlib.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/ea_sample_ARGB8888_256x28.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/ea_sample_ARGB8888_256x28.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/ea_sample_BGRA5551_32x32.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/ea_sample_BGRA5551_32x32.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/ea_sample_PAL4_RGBA8888_256x256.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/ea_sample_PAL4_RGBA8888_256x256.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/ea_sample_PAL4_RGBA8888_256x256_palette.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/ea_sample_PAL4_RGBA8888_256x256_palette.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/ea_sample_PAL8_RGBA8888_150x300.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/ea_sample_PAL8_RGBA8888_150x300.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/ea_sample_PAL8_RGBA8888_150x300_palette.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/ea_sample_PAL8_RGBA8888_150x300_palette.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/ea_sample_RGBA5551_128x128.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/ea_sample_RGBA5551_128x128.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/ea_sample_RGBT5551_512x256.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/ea_sample_RGBT5551_512x256.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/ea_sample_RGBX4444_240x136.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/ea_sample_RGBX4444_240x136.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_ABGR4444.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_ABGR4444.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_BGR565.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_BGR565.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_BGR888.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_BGR888.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_BGRA4444.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_BGRA4444.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_BGRA8888.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_BGRA8888.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_RGB565.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_RGB565.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_RGB888.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_RGB888.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_RGBA8888.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_RGBA8888.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_dxt1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_dxt1.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_dxt3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_dxt3.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/monkey_dxt5.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/monkey_dxt5.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/pal_i8a8_bgra8888.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/pal_i8a8_bgra8888.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_3ds.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_3ds.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_GST121.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_GST121.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_GST121.pal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_GST121.pal -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_GST122.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_GST122.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_GST122.pal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_GST122.pal -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_dreamcast_GRAY8.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_dreamcast_GRAY8.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_gamecube_bluebar.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_gamecube_bluebar.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_morton_monkey.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_morton_monkey.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_4bit_monkey_data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_4bit_monkey_data.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_4bit_monkey_palette.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_4bit_monkey_palette.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_4bit_sample2_512x512_data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_4bit_sample2_512x512_data.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_4bit_sample2_512x512_pal.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_4bit_sample2_512x512_pal.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_8bit_monkey_data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_8bit_monkey_data.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_8bit_monkey_palette.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_8bit_monkey_palette.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_8bit_sample1_512_x256_data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_8bit_sample1_512_x256_data.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_8bit_sample1_512_x256_pal.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_8bit_sample1_512_x256_pal.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_busted_4bpp_256x64.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_busted_4bpp_256x64.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_palette.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_palette.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_suba_16bit_data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_suba_16bit_data.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_suba_16bit_pal.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_suba_16bit_pal.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_suba_8bit_data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_suba_8bit_data.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps2_suba_8bit_pal.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps2_suba_8bit_pal.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps4_DXT5.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps4_DXT5.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps4_to_align_780x256.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps4_to_align_780x256.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps_vita_font_dmg0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps_vita_font_dmg0.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps_vita_monkey_BGRA8888.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps_vita_monkey_BGRA8888.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps_vita_monkey_dxt5.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps_vita_monkey_dxt5.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_ps_vita_tex_all.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_ps_vita_tex_all.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_psp_RGBA8888.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_psp_RGBA8888.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_psp_bubbles_data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_psp_bubbles_data.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_psp_bubbles_palette.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_psp_bubbles_palette.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_switch_DXT5.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_switch_DXT5.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_switch_GRAY8_320x512.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_switch_GRAY8_320x512.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_wii_u_monkey_sample_type0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_wii_u_monkey_sample_type0.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_xbox360_fairy_with_big_boobs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_xbox360_fairy_with_big_boobs.bin -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_xbox360_mt_framework.tex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_xbox360_mt_framework.tex -------------------------------------------------------------------------------- /tests/tests_image/image_files/swizzle_xbox360_sample1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_image/image_files/swizzle_xbox360_sample1.bin -------------------------------------------------------------------------------- /tests/tests_image/test_common_functions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.image.common import get_bpp_for_image_format, is_compressed_image_format 9 | from reversebox.image.image_formats import ImageFormats 10 | 11 | # fmt: off 12 | 13 | 14 | @pytest.mark.imagetest 15 | def test_common_get_bpp_for_image_format(): 16 | for image_format in ImageFormats: 17 | bpp: int = get_bpp_for_image_format(image_format) 18 | assert bpp 19 | assert bpp > 0 20 | assert bpp < 100 21 | 22 | 23 | @pytest.mark.imagetest 24 | def test_common_is_compressed_image_format(): 25 | for image_format in ImageFormats: 26 | result: bool = is_compressed_image_format(image_format) 27 | 28 | if image_format in (ImageFormats.BC1_DXT1, ImageFormats.BC2_DXT3, ImageFormats.BC3_DXT5): 29 | assert result is True 30 | 31 | if image_format in (ImageFormats.RGBA8888, ImageFormats.RGB565): 32 | assert result is False 33 | -------------------------------------------------------------------------------- /tests/tests_image/test_decompress_compress_packbits.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.image.compression.compression_packbits import ( 11 | compress_packbits, 12 | decompress_packbits, 13 | ) 14 | from reversebox.image.image_decoder import ImageDecoder 15 | from reversebox.image.image_formats import ImageFormats 16 | from reversebox.image.pillow_wrapper import PillowWrapper 17 | 18 | # fmt: off 19 | 20 | 21 | @pytest.mark.imagetest 22 | def test_decompress_and_compress_packbits(): 23 | compressed_file_path = os.path.join( 24 | os.path.dirname(__file__), "image_files/compression_packbits.bin" 25 | ) 26 | 27 | compressed_file_data = open(compressed_file_path, "rb").read() 28 | 29 | img_width = 256 30 | img_height = 128 31 | image_format = ImageFormats.RGBA8888 32 | 33 | decompressed_file_data = decompress_packbits(compressed_file_data) 34 | recompressed_file_data = compress_packbits(decompressed_file_data) 35 | re_decompressed_file_data = decompress_packbits(recompressed_file_data) 36 | 37 | # debug start ############################################################################################### 38 | is_debug = False 39 | if is_debug: 40 | image_decoder = ImageDecoder() 41 | wrapper = PillowWrapper() 42 | decoded_image_data: bytes = image_decoder.decode_image( 43 | re_decompressed_file_data, img_width, img_height, image_format 44 | ) 45 | pil_image = wrapper.get_pillow_image_from_rgba8888_data(decoded_image_data, img_width, img_height) 46 | pil_image.show() 47 | # debug end ################################################################################################# 48 | 49 | assert len(decompressed_file_data) == len(re_decompressed_file_data) 50 | assert decompressed_file_data[:100] == re_decompressed_file_data[:100] 51 | assert decompressed_file_data[1000:1100] == re_decompressed_file_data[1000:1100] 52 | assert decompressed_file_data[3000:3100] == re_decompressed_file_data[3000:3100] 53 | assert decompressed_file_data[-100:] == re_decompressed_file_data[-100:] 54 | 55 | # TODO - compressed data not identical, is it correct? 56 | # assert len(compressed_file_data) == len(recompressed_file_data) 57 | # assert compressed_file_data[:100] == recompressed_file_data[:100] 58 | # assert compressed_file_data[1000:1100] == recompressed_file_data[1000:1100] 59 | # assert compressed_file_data[3000:3100] == recompressed_file_data[3000:3100] 60 | # assert compressed_file_data[-100:] == recompressed_file_data[-100:] 61 | -------------------------------------------------------------------------------- /tests/tests_image/test_decompress_compress_rle_tga.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.image.compression.compression_rle_tga import ( 11 | compress_rle_tga, 12 | decompress_rle_tga, 13 | ) 14 | from reversebox.image.image_decoder import ImageDecoder 15 | from reversebox.image.image_formats import ImageFormats 16 | from reversebox.image.pillow_wrapper import PillowWrapper 17 | 18 | # fmt: off 19 | 20 | 21 | @pytest.mark.imagetest 22 | def test_decompress_and_compress_rle_tga(): 23 | compressed_file_path = os.path.join( 24 | os.path.dirname(__file__), "image_files/compression_rle_tga.bin" 25 | ) 26 | 27 | compressed_file_data = open(compressed_file_path, "rb").read() 28 | 29 | img_width = 256 30 | img_height = 128 31 | bpp = 32 32 | image_format = ImageFormats.BGRA8888 33 | 34 | decompressed_file_data = decompress_rle_tga(compressed_file_data, bpp) 35 | recompressed_file_data = compress_rle_tga(decompressed_file_data, bpp) 36 | re_decompressed_file_data = decompress_rle_tga(recompressed_file_data, bpp) 37 | 38 | # debug start ############################################################################################### 39 | is_debug = False 40 | if is_debug: 41 | image_decoder = ImageDecoder() 42 | wrapper = PillowWrapper() 43 | decoded_image_data: bytes = image_decoder.decode_image( 44 | re_decompressed_file_data, img_width, img_height, image_format 45 | ) 46 | pil_image = wrapper.get_pillow_image_from_rgba8888_data(decoded_image_data, img_width, img_height) 47 | pil_image.show() 48 | # debug end ################################################################################################# 49 | 50 | assert len(decompressed_file_data) == len(re_decompressed_file_data) 51 | assert decompressed_file_data[:100] == re_decompressed_file_data[:100] 52 | assert decompressed_file_data[1000:1100] == re_decompressed_file_data[1000:1100] 53 | assert decompressed_file_data[3000:3100] == re_decompressed_file_data[3000:3100] 54 | assert decompressed_file_data[-100:] == re_decompressed_file_data[-100:] 55 | 56 | assert len(compressed_file_data) == len(recompressed_file_data) 57 | assert compressed_file_data[:100] == recompressed_file_data[:100] 58 | assert compressed_file_data[1000:1100] == recompressed_file_data[1000:1100] 59 | assert compressed_file_data[3000:3100] == recompressed_file_data[3000:3100] 60 | assert compressed_file_data[-100:] == recompressed_file_data[-100:] 61 | -------------------------------------------------------------------------------- /tests/tests_image/test_decompress_compress_zlib.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.image.compression.compression_zlib import compress_zlib, decompress_zlib 11 | from reversebox.image.image_decoder import ImageDecoder 12 | from reversebox.image.image_formats import ImageFormats 13 | from reversebox.image.pillow_wrapper import PillowWrapper 14 | 15 | # fmt: off 16 | 17 | 18 | @pytest.mark.imagetest 19 | def test_decompress_and_compress_zlib_image(): 20 | compressed_file_path = os.path.join( 21 | os.path.dirname(__file__), "image_files/compression_zlib.bin" 22 | ) 23 | 24 | compressed_file_data = open(compressed_file_path, "rb").read() 25 | 26 | img_width = 256 27 | img_height = 128 28 | image_format = ImageFormats.RGBA8888 29 | 30 | decompressed_file_data = decompress_zlib(compressed_file_data) 31 | recompressed_file_data = compress_zlib(decompressed_file_data) 32 | re_decompressed_file_data = decompress_zlib(recompressed_file_data) 33 | 34 | # debug start ############################################################################################### 35 | is_debug = False 36 | if is_debug: 37 | image_decoder = ImageDecoder() 38 | wrapper = PillowWrapper() 39 | decoded_image_data: bytes = image_decoder.decode_image( 40 | re_decompressed_file_data, img_width, img_height, image_format 41 | ) 42 | pil_image = wrapper.get_pillow_image_from_rgba8888_data(decoded_image_data, img_width, img_height) 43 | pil_image.show() 44 | # debug end ################################################################################################# 45 | 46 | assert len(decompressed_file_data) == len(re_decompressed_file_data) 47 | assert decompressed_file_data[:100] == re_decompressed_file_data[:100] 48 | assert decompressed_file_data[1000:1100] == re_decompressed_file_data[1000:1100] 49 | assert decompressed_file_data[3000:3100] == re_decompressed_file_data[3000:3100] 50 | assert decompressed_file_data[-100:] == re_decompressed_file_data[-100:] 51 | 52 | assert len(compressed_file_data) == len(recompressed_file_data) 53 | assert compressed_file_data[:100] == recompressed_file_data[:100] 54 | assert compressed_file_data[1000:1100] == recompressed_file_data[1000:1100] 55 | assert compressed_file_data[3000:3100] == recompressed_file_data[3000:3100] 56 | assert compressed_file_data[-100:] == recompressed_file_data[-100:] 57 | -------------------------------------------------------------------------------- /tests/tests_image/test_palette.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.image.palettes.palette_random import generate_random_palette 9 | from reversebox.image.palettes.palette_vga import get_vga_palette 10 | 11 | # fmt: off 12 | 13 | 14 | @pytest.mark.imagetest 15 | def test_get_random_palette(): 16 | palette_1: bytes = generate_random_palette(1024) 17 | assert len(palette_1) == 1024 18 | 19 | palette_2: bytes = generate_random_palette(768) 20 | assert len(palette_2) == 768 21 | 22 | 23 | @pytest.mark.imagetest 24 | def test_get_vga_palette(): 25 | vga_palette: bytes = get_vga_palette() 26 | assert len(vga_palette) == 768 27 | assert vga_palette[0] == 0 28 | assert vga_palette[1] == 0 29 | assert vga_palette[2] == 0 30 | -------------------------------------------------------------------------------- /tests/tests_image/test_swizzle_3ds.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.image.image_decoder import ImageDecoder 11 | from reversebox.image.image_formats import ImageFormats 12 | from reversebox.image.pillow_wrapper import PillowWrapper 13 | from reversebox.image.swizzling.swizzle_3ds import swizzle_3ds, unswizzle_3ds 14 | 15 | # fmt: off 16 | 17 | 18 | @pytest.mark.imagetest 19 | def test_3ds_unswizzle_and_swizzle(): 20 | swizzled_file_path = os.path.join( 21 | os.path.dirname(__file__), "image_files/swizzle_3ds.bin" 22 | ) 23 | 24 | bin_file = open(swizzled_file_path, "rb") 25 | swizzled_file_data = bin_file.read() 26 | 27 | img_width = 64 28 | img_height = 1024 29 | bpp = 16 30 | image_format = ImageFormats.RGBA4444 31 | 32 | unswizzled_file_data = unswizzle_3ds(swizzled_file_data, img_width, img_height, bpp) 33 | 34 | # debug start ############################################################################################### 35 | is_debug = False 36 | if is_debug: 37 | image_decoder = ImageDecoder() 38 | wrapper = PillowWrapper() 39 | decoded_image_data: bytes = image_decoder.decode_image(unswizzled_file_data, img_width, img_height, image_format) 40 | pil_image = wrapper.get_pillow_image_from_rgba8888_data(decoded_image_data, img_width, img_height) 41 | pil_image.show() 42 | # debug end ################################################################################################# 43 | 44 | reswizzled_file_data = swizzle_3ds(unswizzled_file_data, img_width, img_height, bpp) 45 | 46 | assert len(swizzled_file_data) == len(reswizzled_file_data) 47 | assert swizzled_file_data[:100] == reswizzled_file_data[:100] 48 | assert swizzled_file_data[1000:1100] == reswizzled_file_data[1000:1100] 49 | assert swizzled_file_data[3000:3100] == reswizzled_file_data[3000:3100] 50 | assert swizzled_file_data[-100:] == reswizzled_file_data[-100:] 51 | -------------------------------------------------------------------------------- /tests/tests_image/test_swizzle_dreamcast.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.image.image_decoder import ImageDecoder 11 | from reversebox.image.image_formats import ImageFormats 12 | from reversebox.image.pillow_wrapper import PillowWrapper 13 | from reversebox.image.swizzling.swizzle_psvita_dreamcast import ( 14 | swizzle_psvita_dreamcast, 15 | unswizzle_psvita_dreamcast, 16 | ) 17 | 18 | # fmt: off 19 | 20 | 21 | @pytest.mark.imagetest 22 | def test_morton_dreamcast_unswizzle_and_swizzle(): 23 | swizzled_file_path = os.path.join( 24 | os.path.dirname(__file__), "image_files/swizzle_dreamcast_GRAY8.bin" 25 | ) 26 | 27 | win_file = open(swizzled_file_path, "rb") 28 | win_file.seek(0x10d84) 29 | swizzled_file_data = win_file.read(262144) 30 | 31 | img_width = 512 32 | img_height = 512 33 | bpp = 8 34 | image_format = ImageFormats.GRAY8 35 | 36 | unswizzled_file_data = unswizzle_psvita_dreamcast(swizzled_file_data, img_width, img_height, bpp, block_width_height=1) 37 | 38 | # debug start ############################################################################################### 39 | is_debug = False 40 | if is_debug: 41 | image_decoder = ImageDecoder() 42 | wrapper = PillowWrapper() 43 | decoded_image_data: bytes = image_decoder.decode_image( 44 | unswizzled_file_data, img_width, img_height, image_format, 45 | ) 46 | pil_image = wrapper.get_pillow_image_from_rgba8888_data(decoded_image_data, img_width, img_height) 47 | pil_image.show() 48 | # debug end ################################################################################################# 49 | 50 | reswizzled_file_data = swizzle_psvita_dreamcast(unswizzled_file_data, img_width, img_height, bpp, block_width_height=1) 51 | 52 | assert len(swizzled_file_data) == len(reswizzled_file_data) 53 | assert swizzled_file_data[:100] == reswizzled_file_data[:100] 54 | assert swizzled_file_data[1000:1100] == reswizzled_file_data[1000:1100] 55 | assert swizzled_file_data[3000:3100] == reswizzled_file_data[3000:3100] 56 | assert swizzled_file_data[-100:] == reswizzled_file_data[-100:] 57 | -------------------------------------------------------------------------------- /tests/tests_image/test_swizzle_gamecube.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.image.image_decoder import ImageDecoder 11 | from reversebox.image.image_formats import ImageFormats 12 | from reversebox.image.pillow_wrapper import PillowWrapper 13 | from reversebox.image.swizzling.swizzle_gamecube import ( 14 | swizzle_gamecube, 15 | unswizzle_gamecube, 16 | ) 17 | 18 | # fmt: off 19 | 20 | 21 | @pytest.mark.imagetest 22 | def test_gamecube_unswizzle_and_swizzle(): 23 | swizzled_file_path = os.path.join( 24 | os.path.dirname(__file__), "image_files/swizzle_gamecube_bluebar.bin" 25 | ) 26 | 27 | bin_file = open(swizzled_file_path, "rb") 28 | swizzled_file_data = bin_file.read() 29 | bin_file.close() 30 | 31 | img_width = 256 32 | img_height = 28 33 | bpp = 32 34 | image_format = ImageFormats.ARGB8888 35 | 36 | unswizzled_file_data = unswizzle_gamecube( 37 | swizzled_file_data, img_width, img_height, bpp 38 | ) 39 | 40 | # debug start ############################################################################################### 41 | is_debug = False 42 | if is_debug: 43 | image_decoder = ImageDecoder() 44 | wrapper = PillowWrapper() 45 | decoded_image_data: bytes = image_decoder.decode_image( 46 | unswizzled_file_data, img_width, img_height, image_format 47 | ) 48 | pil_image = wrapper.get_pillow_image_from_rgba8888_data(decoded_image_data, img_width, img_height) 49 | pil_image.show() 50 | # debug end ################################################################################################# 51 | 52 | reswizzled_file_data = swizzle_gamecube( 53 | unswizzled_file_data, img_width, img_height, bpp 54 | ) 55 | 56 | assert len(swizzled_file_data) == len(reswizzled_file_data) 57 | assert swizzled_file_data[:100] == reswizzled_file_data[:100] 58 | assert swizzled_file_data[1000:1100] == reswizzled_file_data[1000:1100] 59 | assert swizzled_file_data[3000:3100] == reswizzled_file_data[3000:3100] 60 | assert swizzled_file_data[-100:] == reswizzled_file_data[-100:] 61 | -------------------------------------------------------------------------------- /tests/tests_image/test_swizzle_morton.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.image.image_decoder import ImageDecoder 11 | from reversebox.image.image_formats import ImageFormats 12 | from reversebox.image.pillow_wrapper import PillowWrapper 13 | from reversebox.image.swizzling.swizzle_morton import swizzle_morton, unswizzle_morton 14 | 15 | # fmt: off 16 | 17 | 18 | @pytest.mark.imagetest 19 | def test_morton_unswizzle_and_swizzle(): 20 | swizzled_file_path = os.path.join( 21 | os.path.dirname(__file__), "image_files/swizzle_morton_monkey.bin" 22 | ) 23 | 24 | bin_file = open(swizzled_file_path, "rb") 25 | swizzled_file_data = bin_file.read() 26 | bin_file.close() 27 | 28 | img_width = 256 29 | img_height = 128 30 | bpp = 32 31 | image_format = ImageFormats.BGRA8888 32 | 33 | unswizzled_file_data = unswizzle_morton(swizzled_file_data, img_width, img_height, bpp, block_width_height=1) 34 | 35 | # debug start ############################################################################################### 36 | is_debug = False 37 | if is_debug: 38 | image_decoder = ImageDecoder() 39 | wrapper = PillowWrapper() 40 | decoded_image_data: bytes = image_decoder.decode_image( 41 | unswizzled_file_data, img_width, img_height, image_format 42 | ) 43 | pil_image = wrapper.get_pillow_image_from_rgba8888_data(decoded_image_data, img_width, img_height) 44 | pil_image.show() 45 | # debug end ################################################################################################# 46 | 47 | reswizzled_file_data = swizzle_morton(unswizzled_file_data, img_width, img_height, bpp, block_width_height=1) 48 | 49 | assert len(swizzled_file_data) == len(reswizzled_file_data) 50 | assert swizzled_file_data[:100] == reswizzled_file_data[:100] 51 | assert swizzled_file_data[1000:1100] == reswizzled_file_data[1000:1100] 52 | assert swizzled_file_data[3000:3100] == reswizzled_file_data[3000:3100] 53 | assert swizzled_file_data[-100:] == reswizzled_file_data[-100:] 54 | -------------------------------------------------------------------------------- /tests/tests_image/test_swizzle_ps2_palette.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2024-2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.image.palettes.palette_random import generate_random_palette 11 | from reversebox.image.swizzling.swizzle_ps2 import ( 12 | swizzle_ps2_palette, 13 | unswizzle_ps2_palette, 14 | ) 15 | 16 | # fmt: off 17 | 18 | 19 | @pytest.mark.imagetest 20 | def test_ps2_palette_unswizzle_and_swizzle(): 21 | swizzled_file_path = os.path.join( 22 | os.path.dirname(__file__), "image_files/swizzle_ps2_palette.bin" 23 | ) 24 | 25 | swizzled_palette_file = open(swizzled_file_path, "rb") 26 | swizzled_palette_data = swizzled_palette_file.read() 27 | swizzled_palette_file.close() 28 | 29 | unswizzled_palette_data = unswizzle_ps2_palette(swizzled_palette_data) 30 | reswizzled_palette_data = swizzle_ps2_palette(unswizzled_palette_data) 31 | 32 | assert len(swizzled_palette_data) == len(reswizzled_palette_data) 33 | assert swizzled_palette_data == reswizzled_palette_data 34 | assert swizzled_palette_data != unswizzled_palette_data 35 | 36 | 37 | @pytest.mark.imagetest 38 | def test_ps2_palette_unswizzle_and_swizzle_for_randomly_generated_palette(): 39 | for i in range(10): 40 | random_palette_data = generate_random_palette() 41 | unswizzled_palette_data = unswizzle_ps2_palette(random_palette_data) 42 | reswizzled_palette_data = swizzle_ps2_palette(unswizzled_palette_data) 43 | assert len(random_palette_data) == len(reswizzled_palette_data) 44 | assert random_palette_data == reswizzled_palette_data 45 | assert random_palette_data != unswizzled_palette_data 46 | -------------------------------------------------------------------------------- /tests/tests_image/test_swizzle_wii_u.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2025 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.image.image_decoder import ImageDecoder 11 | from reversebox.image.image_formats import ImageFormats 12 | from reversebox.image.pillow_wrapper import PillowWrapper 13 | from reversebox.image.swizzling.swizzle_wii_u import swizzle_wii_u, unswizzle_wii_u 14 | 15 | # fmt: off 16 | 17 | 18 | @pytest.mark.imagetest 19 | def test_wii_u_unswizzle_and_swizzle_monkey_sample_type0(): 20 | swizzled_file_path = os.path.join( 21 | os.path.dirname(__file__), "image_files/swizzle_wii_u_monkey_sample_type0.bin" 22 | ) 23 | 24 | bin_file = open(swizzled_file_path, "rb") 25 | swizzled_file_data = bin_file.read() 26 | bin_file.close() 27 | 28 | img_width = 256 29 | img_height = 128 30 | image_format = ImageFormats.BC1_DXT1 31 | 32 | unswizzled_file_data = unswizzle_wii_u(img_width, img_height, image_format=0x00000031, tile_mode=4, swizzle_type=851968, pitch=64, 33 | input_data=swizzled_file_data) 34 | 35 | # debug start ############################################################################################### 36 | is_debug = False 37 | if is_debug: 38 | image_decoder = ImageDecoder() 39 | wrapper = PillowWrapper() 40 | decoded_image_data: bytes = image_decoder.decode_compressed_image( 41 | unswizzled_file_data, img_width, img_height, image_format 42 | ) 43 | pil_image = wrapper.get_pillow_image_from_rgba8888_data(decoded_image_data, img_width, img_height) 44 | pil_image.show() 45 | # debug end ################################################################################################# 46 | 47 | reswizzled_file_data = swizzle_wii_u(img_width, img_height, image_format=0x00000031, tile_mode=4, swizzle_type=851968, pitch=64, 48 | input_data=swizzled_file_data) 49 | 50 | assert len(swizzled_file_data) == len(reswizzled_file_data) 51 | # assert swizzled_file_data[:100] == reswizzled_file_data[:100] 52 | # assert swizzled_file_data[1000:1100] == reswizzled_file_data[1000:1100] 53 | # assert swizzled_file_data[3000:3100] == reswizzled_file_data[3000:3100] 54 | # assert swizzled_file_data[-100:] == reswizzled_file_data[-100:] 55 | -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file1.xml: -------------------------------------------------------------------------------- 1 | fake xml data 2 | -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file2.txt: -------------------------------------------------------------------------------- 1 | fake txt data 2 | -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file3_le.bin: -------------------------------------------------------------------------------- 1 | ABCDdef -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file4_be.bin: -------------------------------------------------------------------------------- 1 | DCBAdef -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file5_multi_endian.bin: -------------------------------------------------------------------------------- 1 | AAAAdefBBBBdef -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file6_le.bin: -------------------------------------------------------------------------------- 1 | WB01defABCD -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file7_check_size_11_bytes.bin: -------------------------------------------------------------------------------- 1 | ABCDdef -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file8_translation_after.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_io/data/fake_file8_translation_after.bin -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file8_translation_after.bin.backup: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_io/data/fake_file8_translation_after.bin.backup -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file8_translation_after.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "Project-Id-Version: 1.0\n" 4 | "Report-Msgid-Bugs-To: you@example.com\n" 5 | "POT-Creation-Date: 23/04/2023 13:49:46\n" 6 | "PO-Revision-Date: 23/04/2023 13:49:46\n" 7 | "Last-Translator: you \n" 8 | "Language-Team: English \n" 9 | "Language: pl\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=utf8\n" 12 | 13 | msgctxt "text_to_translate_1" 14 | msgid "Save Game" 15 | msgstr "Zapisz Grę" 16 | 17 | msgctxt "text_to_translate_2" 18 | msgid "Load Game" 19 | msgstr "Wczytaj Grę" 20 | 21 | msgctxt "text_to_translate_3" 22 | msgid "Options" 23 | msgstr "Opcje" 24 | 25 | msgctxt "text_to_translate_4" 26 | msgid "Exit" 27 | msgstr "Wyjście" 28 | 29 | msgctxt "text_to_translate_5" 30 | msgid "Once upon a time there was a brave knight." 31 | msgstr "Dawno temu żył sobie dzielny rycerz." 32 | 33 | msgctxt "text_to_translate_6" 34 | msgid "He was fighting with a monster..." 35 | msgstr "Walczył w potworem..." 36 | 37 | msgctxt "text_to_translate_7" 38 | msgid "Save Game" 39 | msgstr "Zapis Gry" 40 | 41 | msgctxt "text_to_translate_8" 42 | msgid "0123456789" 43 | msgstr "0123456789" 44 | 45 | msgctxt "text_to_translate_9" 46 | msgid "AAAbbbCCC" 47 | msgstr "AAAbbbCCC" 48 | 49 | msgctxt "text_to_translate_10" 50 | msgid "SAVE GAME" 51 | msgstr "ZAPISYWANIE GRY" 52 | 53 | msgctxt "text_to_translate_11" 54 | msgid "Yellow Scarf" 55 | msgstr "Żółty Szalik" 56 | 57 | msgctxt "text_to_translate_12" 58 | msgid "Yellow Jacket" 59 | msgstr "Ciemno Żółta Kurtka" 60 | -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file8_translation_before.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartlomiejduda/ReverseBox/3726b1f9db52665e70ab6b223795d71035a96195/tests/tests_io/data/fake_file8_translation_before.bin -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file8_translation_before.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: 1.0\n" 5 | "Report-Msgid-Bugs-To: you@example.com\n" 6 | "POT-Creation-Date: 21/01/2003 20:06:57\n" 7 | "PO-Revision-Date: 21/01/2003 20:06:57\n" 8 | "Last-Translator: you \n" 9 | "Language-Team: English \n" 10 | "Language: pl\n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=utf8\n" 13 | 14 | #. text_offset=35 | export_length=9 | import_length=None 15 | msgctxt "text_to_translate_1" 16 | msgid "Save Game" 17 | msgstr "" 18 | 19 | #. text_offset=97 | export_length=9 | import_length=None 20 | msgctxt "text_to_translate_2" 21 | msgid "Load Game" 22 | msgstr "" 23 | 24 | #. text_offset=107 | export_length=7 | import_length=None 25 | msgctxt "text_to_translate_3" 26 | msgid "Options" 27 | msgstr "" 28 | 29 | #. text_offset=115 | export_length=4 | import_length=None 30 | msgctxt "text_to_translate_4" 31 | msgid "Exit" 32 | msgstr "" 33 | 34 | #. text_offset=157 | export_length=42 | import_length=None 35 | msgctxt "text_to_translate_5" 36 | msgid "Once upon a time there was a brave knight." 37 | msgstr "" 38 | 39 | #. text_offset=224 | export_length=33 | import_length=None 40 | msgctxt "text_to_translate_6" 41 | msgid "He was fighting with a monster..." 42 | msgstr "" 43 | 44 | #. text_offset=315 | export_length=9 | import_length=None 45 | msgctxt "text_to_translate_7" 46 | msgid "Save Game" 47 | msgstr "" 48 | 49 | #. text_offset=342 | export_length=10 | import_length=None 50 | msgctxt "text_to_translate_8" 51 | msgid "0123456789" 52 | msgstr "" 53 | 54 | #. text_offset=411 | export_length=9 | import_length=None 55 | msgctxt "text_to_translate_9" 56 | msgid "AAAbbbCCC" 57 | msgstr "" 58 | 59 | #. text_offset=422 | export_length=9 | import_length=None 60 | msgctxt "text_to_translate_10" 61 | msgid "SAVE GAME" 62 | msgstr "" 63 | 64 | #. text_offset=434 | export_length=12 | import_length=None 65 | msgctxt "text_to_translate_11" 66 | msgid "Yellow Scarf" 67 | msgstr "" 68 | 69 | #. text_offset=452 | export_length=13 | import_length=17 70 | msgctxt "text_to_translate_12" 71 | msgid "Yellow Jacket" 72 | msgstr "" 73 | -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file9_mod_after.bin: -------------------------------------------------------------------------------- 1 | XXXYY1 2 | ZZZZZZZZZZDDDDDD -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file9_mod_after.bin.backup: -------------------------------------------------------------------------------- 1 | XXXYY1 2 | ZZZZZZZZZZDDDDDD -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file9_mod_before.bin: -------------------------------------------------------------------------------- 1 | AAABBBBBCCCCCCCCCCDDDDDD -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file9_output_after/aaa.txt: -------------------------------------------------------------------------------- 1 | XXX 2 | -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file9_output_after/bbb/bbb.txt: -------------------------------------------------------------------------------- 1 | YY1 2 | -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file9_output_after/ccc.txt: -------------------------------------------------------------------------------- 1 | ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 2 | -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file9_output_before/aaa.txt: -------------------------------------------------------------------------------- 1 | AAA -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file9_output_before/bbb/bbb.txt: -------------------------------------------------------------------------------- 1 | BBBBB -------------------------------------------------------------------------------- /tests/tests_io/data/fake_file9_output_before/ccc.txt: -------------------------------------------------------------------------------- 1 | CCCCCCCCCC -------------------------------------------------------------------------------- /tests/tests_io/test_bytes_handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import pytest 7 | 8 | from reversebox.io_files.bytes_handler import BytesHandler 9 | 10 | 11 | @pytest.mark.unittest 12 | def test_bytes_handler_get_bytes(): 13 | bytes_handler = BytesHandler(b"\x01\x02\x03\x04") 14 | test_bytes = bytes_handler.get_bytes(1, 2) 15 | 16 | assert test_bytes == b"\x02\x03" 17 | 18 | 19 | @pytest.mark.unittest 20 | def test_bytes_handler_get_bits(): 21 | bytes_handler = BytesHandler(b"\x01\x02\x03\x04") 22 | result = bytes_handler.get_int_from_bits(35345345, 16, 8) 23 | 24 | # TODO - adjust this assert 25 | assert result 26 | 27 | 28 | @pytest.mark.unittest 29 | def test_bytes_handler_fill_to_length(): 30 | bytes_handler = BytesHandler(b"\x01\x02\x03\x04") 31 | 32 | test_bytes = bytes_handler.fill_to_length(5) 33 | assert test_bytes == b"\x01\x02\x03\x04\x00" 34 | assert len(test_bytes) == 5 35 | 36 | test_bytes = bytes_handler.fill_to_length(6, b"\xFF") 37 | assert test_bytes == b"\x01\x02\x03\x04\xFF\xFF" 38 | assert len(test_bytes) == 6 39 | 40 | test_bytes = bytes_handler.fill_to_length(7) 41 | assert test_bytes == b"\x01\x02\x03\x04\x00\x00\x00" 42 | assert len(test_bytes) == 7 43 | 44 | with pytest.raises(Exception): 45 | bytes_handler.fill_to_length(5, b"\x00\x01\x02") 46 | 47 | with pytest.raises(Exception): 48 | bytes_handler.fill_to_length(1) 49 | -------------------------------------------------------------------------------- /tests/tests_io/test_check_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.io_files.check_file import check_file 11 | 12 | 13 | @pytest.mark.unittest 14 | def test_check_file_to_match_expected_result(): 15 | file_path = os.path.join(os.path.dirname(__file__), "data\\fake_file1.xml") 16 | code, status = check_file(file_path, ".XML", False) 17 | assert code == "OK" 18 | 19 | file_path = os.path.join(os.path.dirname(__file__), "data\\fake_file2.txt") 20 | code, status = check_file(file_path, ".TXT", False) 21 | assert code == "OK" 22 | 23 | file_path = os.path.join(os.path.dirname(__file__), "data\\fake_file2.txt") 24 | code, status = check_file(file_path, ".XXX", False) 25 | assert code != "OK" 26 | -------------------------------------------------------------------------------- /tests/tests_io/test_check_file_size.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.io_files.file_handler import FileHandler 11 | 12 | 13 | @pytest.mark.unittest 14 | def test_check_file_size(): 15 | file_path = os.path.join( 16 | os.path.dirname(__file__), "data\\fake_file7_check_size_11_bytes.bin" 17 | ) 18 | 19 | file_reader = FileHandler(file_path, "rb") 20 | file_reader.open() 21 | 22 | file_reader.read_str(4, "utf8") 23 | 24 | current_offset1 = file_reader.get_position() 25 | file_size = file_reader.get_file_size() 26 | current_offset2 = file_reader.get_position() 27 | 28 | assert file_size == 11 29 | assert current_offset1 == 4 30 | assert current_offset2 == 4 31 | -------------------------------------------------------------------------------- /tests/tests_io/test_mod_handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2023 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import logging 7 | import os 8 | from typing import List 9 | 10 | import pytest 11 | 12 | # fmt: off 13 | from reversebox.io_files.mod_handler import ModEntry, ModHandler 14 | 15 | mod_memory: List[ModEntry] = [ 16 | ModEntry(file_offset=33, file_size=3, file_relative_path=".\\aaa.txt"), 17 | ModEntry(file_offset=128, file_size=5, file_relative_path="bbb\\bbb.txt"), 18 | ModEntry(file_offset=277, file_size=10, file_relative_path="ccc.txt"), 19 | ] 20 | # fmt: on 21 | 22 | 23 | @pytest.mark.unittest 24 | def test_mod_handler_extract_all_files(): 25 | binary_file_path = os.path.join( 26 | os.path.dirname(__file__), "data\\fake_file9_mod_before.bin" 27 | ) 28 | output_directory = os.path.join( 29 | os.path.dirname(__file__), "data\\fake_file9_output_before" 30 | ) 31 | mod_handler = ModHandler( 32 | mod_memory=mod_memory, 33 | archive_file_path=binary_file_path, 34 | log_level=logging.ERROR, 35 | ) 36 | result = mod_handler.export_all_files(output_directory=output_directory) 37 | assert result 38 | 39 | 40 | @pytest.mark.unittest 41 | def test_mod_handler_import_all_files(): 42 | binary_file_path = os.path.join( 43 | os.path.dirname(__file__), "data\\fake_file9_mod_after.bin" 44 | ) 45 | output_directory = os.path.join( 46 | os.path.dirname(__file__), "data\\fake_file9_output_after" 47 | ) 48 | mod_handler = ModHandler( 49 | mod_memory=mod_memory, 50 | archive_file_path=binary_file_path, 51 | log_level=logging.ERROR, 52 | ) 53 | result = mod_handler.import_all_files(output_directory=output_directory) 54 | assert result 55 | assert os.path.getsize(binary_file_path) == 500 56 | -------------------------------------------------------------------------------- /tests/tests_io/test_write_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright © 2022 Bartłomiej Duda 3 | License: GPL-3.0 License 4 | """ 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from reversebox.io_files.file_handler import FileHandler 11 | 12 | 13 | @pytest.mark.unittest 14 | def test_open_and_write_little_endian_file(): 15 | file_path = os.path.join(os.path.dirname(__file__), "data\\fake_file6_le.bin") 16 | 17 | file_handler = FileHandler(file_path, "wb") 18 | file_handler.open() 19 | 20 | result = file_handler.write_str("WB01") 21 | assert result 22 | result = file_handler.write_uint32(100) 23 | assert result 24 | result = file_handler.write_uint16(101) 25 | assert result 26 | result = file_handler.write_uint8(102) 27 | assert result 28 | result = file_handler.write_bytes(b"\x41\x42\x43\x44") 29 | assert result 30 | 31 | result = file_handler.close() 32 | assert result 33 | -------------------------------------------------------------------------------- /upload_package.py: -------------------------------------------------------------------------------- 1 | # Python script for uploading package to Python index (PyPI) 2 | # Created on 25.06.2022 by Bartlomiej Duda 3 | 4 | import os 5 | import shutil 6 | import subprocess 7 | 8 | try: 9 | shutil.rmtree("./dist") 10 | except FileNotFoundError: 11 | pass 12 | 13 | try: 14 | shutil.rmtree("./ReverseBox.egg-info") 15 | except FileNotFoundError: 16 | pass 17 | 18 | try: 19 | shutil.rmtree("./reversebox/ReverseBox.egg-info") 20 | except FileNotFoundError: 21 | pass 22 | 23 | repository_url = os.environ["REPOSITORY_URL"] 24 | reversebox_username = os.environ["REVERSEBOX_USERNAME"] 25 | reversebox_password = os.environ["REVERSEBOX_PASSWORD"] 26 | 27 | 28 | subprocess.call("python setup.py sdist", shell=True) 29 | subprocess.call( 30 | f"twine upload --repository-url {repository_url} dist/* " 31 | f"--username {reversebox_username} --password {reversebox_password}", 32 | shell=True, 33 | ) 34 | --------------------------------------------------------------------------------