Table of Contents

EAHD

This is a compression format created by MBL Research and used by EA Games.


The header takes up the first 5 bytes of the compressed file

Offset#BytesDescription
0x02Magic Number 0x10FB
0x23Uncompressed size

Decompression Code

Python

EAHD parser class

eahd.py
  1. class EAHD:
  2. def __init__(self, src):
  3. self.src = src
  4.  
  5. def __bytes_to_int24(self, arr, offset):
  6. return int.from_bytes(arr[offset:offset+3], byteorder='big')
  7.  
  8. def __bytes_to_int_reversed(self, arr, offset):
  9. return int.from_bytes(arr[offset:offset+4], byteorder='little')
  10.  
  11. def __memset(self, dst, dst_offset, value, amount):
  12. for i in range(amount):
  13. dst[dst_offset + i] = value
  14.  
  15. def __memcpy(self, dst, dst_offset, dst_backwards_offset, amount):
  16. src_offset = dst_offset - dst_backwards_offset
  17. for i in range(amount):
  18. dst[dst_offset + i] = dst[src_offset + i]
  19.  
  20. def __fetch_bytes(self, dst, dst_offset, dst_backwards_offset, amount):
  21. if dst_backwards_offset > 1:
  22. self.__memcpy(dst, dst_offset, dst_backwards_offset, amount)
  23. else:
  24. self.__memset(dst, dst_offset, dst[dst_offset - dst_backwards_offset], amount)
  25. return dst_offset + amount
  26.  
  27. def __copy_literal(self, dst, dst_offset, src, src_offset, amount):
  28. for i in range(amount):
  29. dst[dst_offset + i] = src[src_offset + i]
  30.  
  31. def parse(self):
  32. decompressed_size = self.__bytes_to_int24(self.src, 0x2)
  33. dst = [0] * decompressed_size # Decompressed data list
  34. src_offset = 0x5
  35. dst_offset = 0
  36. while True:
  37. cmd = self.__bytes_to_int_reversed(self.src, src_offset)
  38. if cmd & 0x80 == 0: # Covers commands 00 to 7F
  39. src_offset += 2
  40. # Copy bytes from the compressed data array
  41. amount_to_copy = cmd & 0x3
  42. self.__copy_literal(dst, dst_offset, self.src, src_offset, amount_to_copy)
  43. src_offset += amount_to_copy
  44. dst_offset += amount_to_copy
  45. # Fetch bytes from previous decompressed data
  46. backward_fetch_offset = ((cmd << 3) & 0x300) + ((cmd >> 8) & 0xFF) + 1
  47. amount_of_bytes_to_fetch = ((cmd >> 2) & 0x7) + 3
  48. dst_offset = self.__fetch_bytes(dst, dst_offset, backward_fetch_offset, amount_of_bytes_to_fetch)
  49. continue
  50. if cmd & 0x40 == 0: # Covers commands 0x80 to 0xBF
  51. # Copy bytes from the compressed data array
  52. src_offset += 3
  53. amount_to_copy = ((cmd >> 8) >> 6) & 0x3
  54. self.__copy_literal(dst, dst_offset, self.src, src_offset, amount_to_copy)
  55. src_offset += amount_to_copy
  56. dst_offset += amount_to_copy
  57. # Fetch bytes from previous decompressed data
  58. backward_fetch_offset = (((cmd >> 8) << 8) & 0x3F00) + ((cmd >> 16) & 0xFF) + 1
  59. amount_of_bytes_to_fetch = (cmd & 0x3F) + 4
  60. dst_offset = self.__fetch_bytes(dst, dst_offset, backward_fetch_offset, amount_of_bytes_to_fetch)
  61. continue
  62. if cmd & 0x20 == 0: # Covers commands 0xC0 to 0xDF
  63. src_offset += 4
  64. amount_to_copy = cmd & 0x3
  65. self.__copy_literal(dst, dst_offset, self.src, src_offset, amount_to_copy)
  66. src_offset += amount_to_copy
  67. dst_offset += amount_to_copy
  68. # Fetch bytes from previous decompressed data
  69. backward_fetch_offset = ((cmd << 12) & 0x10000) + (((cmd >> 8) << 8) & 0xFF00) + 1 + ((cmd >> 16) & 0xFF)
  70. amount_of_bytes_to_fetch = ((cmd << 6) & 0x300) + ((cmd >> 24) & 0xFF) + 5
  71. dst_offset = self.__fetch_bytes(dst, dst_offset, backward_fetch_offset, amount_of_bytes_to_fetch)
  72. continue
  73. src_offset += 1
  74. if (cmd & 0xFF) < 0xFC: # Covers commands 0xE0 to 0xFB
  75. # Copy bytes from the compressed data array
  76. amount_to_copy = ((cmd & 0x1F) + 1) * 4
  77. self.__copy_literal(dst, dst_offset, self.src, src_offset, amount_to_copy)
  78. src_offset += amount_to_copy
  79. dst_offset += amount_to_copy
  80. continue
  81. if cmd & 3 != 0: # Copy any left-over bytes
  82. # Copy bytes from the compressed data array
  83. amount_to_copy = cmd & 0x3
  84. self.__copy_literal(dst, dst_offset, self.src, src_offset, amount_to_copy)
  85. src_offset += amount_to_copy
  86. dst_offset += amount_to_copy
  87. break # A command of 0xFC, 0xFD, 0xFE, or 0xFF will end the decompression routine.
  88. return dst

Using the EAHD parser

eahd_parser.py
  1. from eahd import EAHD
  2.  
  3. parser = EAHD(bytes) # 'bytes' is the list of compressed bytes.
  4. uncompressed_bytes = parser.parse()