ReverseBox 0.35.0__tar.gz → 0.35.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. {reversebox-0.35.0 → reversebox-0.35.2}/PKG-INFO +1 -1
  2. {reversebox-0.35.0 → reversebox-0.35.2}/ReverseBox.egg-info/PKG-INFO +1 -1
  3. {reversebox-0.35.0 → reversebox-0.35.2}/ReverseBox.egg-info/SOURCES.txt +1 -0
  4. reversebox-0.35.2/reversebox/image/byte_swap.py +71 -0
  5. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/image_encoder.py +16 -7
  6. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_x360.py +5 -14
  7. {reversebox-0.35.0 → reversebox-0.35.2}/setup.py +1 -1
  8. {reversebox-0.35.0 → reversebox-0.35.2}/LICENSE +0 -0
  9. {reversebox-0.35.0 → reversebox-0.35.2}/README.md +0 -0
  10. {reversebox-0.35.0 → reversebox-0.35.2}/ReverseBox.egg-info/dependency_links.txt +0 -0
  11. {reversebox-0.35.0 → reversebox-0.35.2}/ReverseBox.egg-info/requires.txt +0 -0
  12. {reversebox-0.35.0 → reversebox-0.35.2}/ReverseBox.egg-info/top_level.txt +0 -0
  13. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/__init__.py +0 -0
  14. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/__init__.py +0 -0
  15. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_adler32.py +0 -0
  16. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_cocos2d_pvr.py +0 -0
  17. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_fletcher16.py +0 -0
  18. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_fletcher32.py +0 -0
  19. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_internet_ipv4_header.py +0 -0
  20. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_sum8.py +0 -0
  21. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_sum8_2s_complement.py +0 -0
  22. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_unix_sum_bsd16.py +0 -0
  23. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_unix_sum_sysv.py +0 -0
  24. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/checksum/checksum_xor8.py +0 -0
  25. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/common/__init__.py +0 -0
  26. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/common/common.py +0 -0
  27. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/common/logger.py +0 -0
  28. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/common/numpy_helper_functions.py +0 -0
  29. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/compression/__init__.py +0 -0
  30. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/compression/compression_jcalg1.py +0 -0
  31. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/compression/compression_lzo.py +0 -0
  32. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/compression/compression_mio0.py +0 -0
  33. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/compression/compression_re_tiyoruga_dat.py +0 -0
  34. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/compression/compression_refpack.py +0 -0
  35. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/compression/compression_zlib.py +0 -0
  36. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/__init__.py +0 -0
  37. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc16_arc.py +0 -0
  38. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc16_ccitt.py +0 -0
  39. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc16_dnp.py +0 -0
  40. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc16_ea_crcf.py +0 -0
  41. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc16_kermit.py +0 -0
  42. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc16_modbus.py +0 -0
  43. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc16_sick.py +0 -0
  44. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc32_asobo.py +0 -0
  45. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc32_iso_hdlc.py +0 -0
  46. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc64_asobo.py +0 -0
  47. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc64_go_iso.py +0 -0
  48. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc8.py +0 -0
  49. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/crc/crc_unix_cksum.py +0 -0
  50. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/encryption/__init__.py +0 -0
  51. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/encryption/encryption_hatch_engine.py +0 -0
  52. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/encryption/encryption_rot13.py +0 -0
  53. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/encryption/encryption_xor_basic.py +0 -0
  54. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/encryption/encryption_xor_basic_key_guesser.py +0 -0
  55. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/encryption/encryption_xor_gianas_return_zda.py +0 -0
  56. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/encryption/encryption_xor_retro64_eco.py +0 -0
  57. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/__init__.py +0 -0
  58. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_additive.py +0 -0
  59. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_ap.py +0 -0
  60. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_djb2.py +0 -0
  61. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_fnv.py +0 -0
  62. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_jenkins_one_at_a_time.py +0 -0
  63. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_md2.py +0 -0
  64. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_md5.py +0 -0
  65. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_murmur3.py +0 -0
  66. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_pivotal_games_dat.py +0 -0
  67. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_sdbm.py +0 -0
  68. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_sha1.py +0 -0
  69. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/hash/hash_sha2.py +0 -0
  70. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/__init__.py +0 -0
  71. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/common.py +0 -0
  72. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/compression/__init__.py +0 -0
  73. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/compression/compression_gst.py +0 -0
  74. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/compression/compression_lzw.py +0 -0
  75. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/compression/compression_packbits.py +0 -0
  76. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/compression/compression_rle_executioners.py +0 -0
  77. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/compression/compression_rle_tga.py +0 -0
  78. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/compression/compression_zlib.py +0 -0
  79. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/decoders/__init__.py +0 -0
  80. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/decoders/bumpmap_decoder.py +0 -0
  81. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/decoders/compressed_decoder.py +0 -0
  82. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/decoders/gst_decoder.py +0 -0
  83. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/decoders/n64_decoder.py +0 -0
  84. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/decoders/yuv_decoder.py +0 -0
  85. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/encoders/__init__.py +0 -0
  86. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/encoders/gst_encoder.py +0 -0
  87. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/file_formats/__init__.py +0 -0
  88. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/file_formats/image_dds.py +0 -0
  89. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/image_decoder.py +0 -0
  90. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/image_formats.py +0 -0
  91. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/palettes/__init__.py +0 -0
  92. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/palettes/palette_random.py +0 -0
  93. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/palettes/palette_vga.py +0 -0
  94. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/pillow_wrapper.py +0 -0
  95. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/__init__.py +0 -0
  96. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_3ds.py +0 -0
  97. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_bc.py +0 -0
  98. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_cmpr.py +0 -0
  99. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_gamecube.py +0 -0
  100. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_gst.py +0 -0
  101. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_morton.py +0 -0
  102. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_morton_ps4.py +0 -0
  103. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_ps2.py +0 -0
  104. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_ps2_4bit.py +0 -0
  105. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_ps2_suba.py +0 -0
  106. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_psp.py +0 -0
  107. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_psvita_dreamcast.py +0 -0
  108. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_switch.py +0 -0
  109. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/image/swizzling/swizzle_wii_u.py +0 -0
  110. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/io_files/__init__.py +0 -0
  111. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/io_files/bytes_handler.py +0 -0
  112. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/io_files/bytes_helper_functions.py +0 -0
  113. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/io_files/check_file.py +0 -0
  114. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/io_files/file_handler.py +0 -0
  115. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/io_files/mod_handler.py +0 -0
  116. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/io_files/translation_text_handler.py +0 -0
  117. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/libs/DirectXTex.dll +0 -0
  118. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/libs/__init__.py +0 -0
  119. {reversebox-0.35.0 → reversebox-0.35.2}/reversebox/libs/refpack.dll +0 -0
  120. {reversebox-0.35.0 → reversebox-0.35.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ReverseBox
3
- Version: 0.35.0
3
+ Version: 0.35.2
4
4
  Summary: A set of functions useful in reverse engineering.
5
5
  Home-page: https://github.com/bartlomiejduda/ReverseBox
6
6
  Author: Bartlomiej Duda
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ReverseBox
3
- Version: 0.35.0
3
+ Version: 0.35.2
4
4
  Summary: A set of functions useful in reverse engineering.
5
5
  Home-page: https://github.com/bartlomiejduda/ReverseBox
6
6
  Author: Bartlomiej Duda
@@ -64,6 +64,7 @@ reversebox/hash/hash_sdbm.py
64
64
  reversebox/hash/hash_sha1.py
65
65
  reversebox/hash/hash_sha2.py
66
66
  reversebox/image/__init__.py
67
+ reversebox/image/byte_swap.py
67
68
  reversebox/image/common.py
68
69
  reversebox/image/image_decoder.py
69
70
  reversebox/image/image_encoder.py
@@ -0,0 +1,71 @@
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
+ if len(image_data) % 8 != 0:
24
+ raise ValueError("The data must be a multiple of 8 bytes (the size of one BC block)")
25
+
26
+ bw: int = img_width // 4
27
+ bh: int = img_height // 4
28
+ block_size: int = 8
29
+ tile_w: int = bw // 2
30
+ tile_h: int = bh // 2
31
+ tile_index: int = 0
32
+
33
+ swapped_data: bytearray = bytearray(len(image_data))
34
+
35
+ for ty in range(tile_h):
36
+ for tx in range(tile_w):
37
+ for by in range(2):
38
+ for bx in range(2):
39
+ # Calculate destination position
40
+ dst_x = tx * 2 + bx
41
+ dst_y = ty * 2 + by
42
+ dst_index = (dst_y * bw + dst_x) * block_size
43
+
44
+ # Get source block and convert endianness
45
+ src_index = tile_index * block_size
46
+ block = image_data[src_index:src_index + block_size]
47
+
48
+ # Convert GameCube DXT1 to little-endian DXT1
49
+ # Swap color0 and color1 endianness
50
+ color0 = (block[0] << 8) | block[1]
51
+ color1 = (block[2] << 8) | block[3]
52
+
53
+ # Reorder 2-bit indices (bit reversal within each byte)
54
+ indices = bytearray(4)
55
+ for i in range(4):
56
+ b = block[4 + i]
57
+ indices[i] = ((b & 0b00000011) << 6) | \
58
+ ((b & 0b00001100) << 2) | \
59
+ ((b & 0b00110000) >> 2) | \
60
+ ((b & 0b11000000) >> 6)
61
+
62
+ # Write converted block to output
63
+ swapped_data[dst_index] = color0 & 0xff
64
+ swapped_data[dst_index + 1] = color0 >> 8
65
+ swapped_data[dst_index + 2] = color1 & 0xff
66
+ swapped_data[dst_index + 3] = color1 >> 8
67
+ swapped_data[dst_index + 4:dst_index + 8] = indices
68
+
69
+ tile_index += 1
70
+
71
+ return bytes(swapped_data)
@@ -320,13 +320,15 @@ class ImageEncoder:
320
320
  else:
321
321
  raise Exception(f"[1] Image_bits_per_pixel={image_bpp} not supported!")
322
322
 
323
- # encode to intermediate image
324
- encoded_intermediate_image: bytes = self._encode_generic(image_data, img_width, img_height, palette_format, image_endianess) # RGBA8888
323
+ texture_data_expected_size: int = len(texture_data)
325
324
 
326
- # colour quantization
327
- pillow_img: Image = PillowWrapper().get_pillow_image_from_rgba8888_data(encoded_intermediate_image, img_width, img_height)
328
- pillow_img = pillow_img.quantize(colors=max_colors_count, method=2).convert("RGBA")
329
- encoded_intermediate_image = pillow_img.tobytes()
325
+ # colour quantization and dithering
326
+ pillow_img: Image = PillowWrapper().get_pillow_image_from_rgba8888_data(image_data, img_width, img_height)
327
+ pillow_img = pillow_img.quantize(colors=max_colors_count, method=2).convert("RGBA", dither=Image.Dither.FLOYDSTEINBERG)
328
+ image_data = pillow_img.tobytes()
329
+
330
+ # encode to intermediate image (e.g. RGBA8888 -> RGB565)
331
+ encoded_intermediate_image: bytes = self._encode_generic(image_data, img_width, img_height, palette_format, image_endianess)
330
332
 
331
333
  # get pixels from intermediate image
332
334
  pixel_int_values: list[int] = []
@@ -350,6 +352,7 @@ class ImageEncoder:
350
352
  raise Exception(f"Not supported aligned colors count for max_colors_count={max_colors_count}") # TODO - support other formats like PAL16
351
353
 
352
354
  palette_data: bytearray = bytearray(aligned_colors_count * palette_bytes_per_pixel)
355
+ palette_data_expected_size: int = len(palette_data)
353
356
 
354
357
  # palette preparation (get unique values for palette)
355
358
  pixel_check_list: List[int] = []
@@ -370,7 +373,6 @@ class ImageEncoder:
370
373
 
371
374
  # encode indices
372
375
  img_entry_number: int = 0
373
-
374
376
  if image_bpp == 4: # PAL4
375
377
  for i in range(0, len(pixel_int_values), 2):
376
378
  pal_entry_number_1: int = pixel_map[pixel_int_values[i]]
@@ -390,6 +392,13 @@ class ImageEncoder:
390
392
  else:
391
393
  raise Exception(f"Not supported img_bpp={image_bpp}!") # TODO - support other formats like PAL16
392
394
 
395
+ # final checks
396
+ if len(palette_data) > palette_data_expected_size or len(palette_data) > 1024:
397
+ raise Exception("Error! Palette data too big!")
398
+
399
+ if len(texture_data) > texture_data_expected_size:
400
+ raise Exception("Error! Texture data too big!")
401
+
393
402
  return texture_data, palette_data
394
403
 
395
404
  def encode_image(self, image_data: bytes, img_width: int, img_height: int, image_format: ImageFormats, image_endianess: str = "little") -> bytes:
@@ -1,24 +1,15 @@
1
1
  """
2
- Copyright © 2024 Bartłomiej Duda
2
+ Copyright © 2024-2025 Bartłomiej Duda
3
3
  License: GPL-3.0 License
4
4
  """
5
5
 
6
+ from reversebox.image.byte_swap import swap_byte_order_x360
7
+
6
8
  # Xbox 360 Texture Swizzling
7
9
 
8
10
  # fmt: off
9
11
 
10
12
 
11
- def _swap_byte_order(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
13
  def _xg_address_2d_tiled_x(block_offset: int, width_in_blocks: int, texel_byte_pitch: int) -> int:
23
14
  aligned_width: int = (width_in_blocks + 31) & ~31
24
15
  log_bpp: int = (texel_byte_pitch >> 2) + ((texel_byte_pitch >> 1) >> (texel_byte_pitch >> 2))
@@ -74,12 +65,12 @@ def _convert_x360_image_data(image_data: bytes, image_width: int, image_height:
74
65
 
75
66
 
76
67
  def unswizzle_x360(image_data: bytes, img_width: int, img_height: int, block_pixel_size: int = 4, texel_byte_pitch: int = 8) -> bytes:
77
- swapped_data: bytes = _swap_byte_order(image_data)
68
+ swapped_data: bytes = swap_byte_order_x360(image_data)
78
69
  unswizzled_data: bytes = _convert_x360_image_data(swapped_data, img_width, img_height, block_pixel_size, texel_byte_pitch, False)
79
70
  return unswizzled_data
80
71
 
81
72
 
82
73
  def swizzle_x360(image_data: bytes, img_width: int, img_height: int, block_pixel_size: int = 4, texel_byte_pitch: int = 8) -> bytes:
83
- swapped_data: bytes = _swap_byte_order(image_data)
74
+ swapped_data: bytes = swap_byte_order_x360(image_data)
84
75
  swizzled_data: bytes = _convert_x360_image_data(swapped_data, img_width, img_height, block_pixel_size, texel_byte_pitch, True)
85
76
  return swizzled_data
@@ -8,7 +8,7 @@ from typing import Final
8
8
 
9
9
  import setuptools
10
10
 
11
- VERSION_NUM: Final[str] = "0.35.0"
11
+ VERSION_NUM: Final[str] = "0.35.2"
12
12
 
13
13
 
14
14
  def get_long_description() -> str:
File without changes
File without changes
File without changes