1. '''
    2. TESTED for the following builds:
    3. Windows: 2024-2030
    4. Linux: 2024-2030
    5. Can potentially work for builds in between or newer!
    6. Original script: DE YI <https://github.com/deyixtan>
    7. '''
    8. import binascii
    9. import re
    10. import argparse
    11. import os
    12. class Patcher:
    13. '''
    14. INITIAL_LICENSE_CHECK_AOB_ONE
    15. Build: 2024, 2025, 2028
    16. IDA Sig: 80 78 ?? 00 74 3A <- windows
    17. 80 78 ?? 00 74 1B <- linux
    18. ^^ ^^
    19. 48 01
    20. Build 2024: @0x2a80f <- windows
    21. @0x39bf29 <- linux
    22. Build 2025: @0x2a803 <- windows
    23. @0x39bf43 <- linux
    24. Build 2028: @0x2a8ff <- windows
    25. @0x397fe9 <- linux
    26. '''
    27. INITIAL_LICENSE_CHECK_AOB_ONE = {"windows": b"8078..00743a",
    28. "linux": b"8078..00741b",
    29. "patch_one": b"48",
    30. "patch_one_offset": 2,
    31. "patch_two": b"01",
    32. "patch_two_offset": 6}
    33. '''
    34. INITIAL_LICENSE_CHECK_AOB_TWO
    35. Build: 2027, 2030
    36. IDA Sig: 80 38 00 74 3A <- windows
    37. 80 38 00 74 1B <- linux
    38. ^^ ^^
    39. 08 01
    40. Build 2027: @0x2a815 <- windows
    41. @0x39bfe5 <- linux
    42. Build 2030: @0x2a8e9 <- windows
    43. @0x39b005 <- linux
    44. '''
    45. INITIAL_LICENSE_CHECK_AOB_TWO = {"windows": b"803800743a",
    46. "linux": b"803800741b",
    47. "patch": b"0801",
    48. "patch_offset": 2}
    49. '''
    50. PERSISTENT_LICENSE_CHECK_AOB_ONE
    51. Build: 2024, 2025, 2028 (windows)
    52. IDA Sig: C6 ?? ?? 00 C3 48 83 <- windows
    53. C6 ?? ?? 00 C3 ?? 48 89 ?? E8 <- linux
    54. ^^
    55. 01
    56. Build 2024: @0x2f800 <- windows
    57. @0x39d13b <- linux
    58. Build 2025: @0x2f7f3 <- windows
    59. @0x39d151 <- linux
    60. Build 2028: @0x2f92f <- windows
    61. @NaN <- linux
    62. '''
    63. PERSISTENT_LICENSE_CHECK_AOB_ONE = {"windows": b"c6....00c34883",
    64. "linux": b"c6....00c3..4889..e8",
    65. "patch": b"01",
    66. "patch_offset": 6}
    67. '''
    68. PERSISTENT_LICENSE_CHECK_AOB_TWO
    69. Build: 2027, 2030
    70. IDA Sig: C6 ?? 00 C3 CC 48 <- windows
    71. C6 ?? 00 C3 CC ?? 48 <- linux
    72. ^^
    73. 01
    74. Build 2027: @0x2f7fd <- windows
    75. @0x39d1ef <- linux
    76. Build 2030: @0x2edef <- windows
    77. @0x39bc91 <- linux
    78. '''
    79. PERSISTENT_LICENSE_CHECK_AOB_TWO = {"windows": b"c6..00c3cc48",
    80. "linux": b"c6..00c3cc..48",
    81. "patch": b"01",
    82. "patch_offset": 4}
    83. '''
    84. PERSISTENT_LICENSE_CHECK_AOB_THREE
    85. Build: 2028 (linux)
    86. IDA Sig: C6 ?? ?? 00 C3 CC 48 83 <- windows
    87. C6 ?? ?? 00 C3 CC ?? 48 89 ?? E8 <- linux
    88. ^^
    89. 01
    90. Build 2028: @NaN <- windows
    91. @0x3991fc <- linux
    92. '''
    93. PERSISTENT_LICENSE_CHECK_AOB_THREE = {"windows": b"XXX",
    94. "linux": b"c6....00c3cc..4889..e8",
    95. "patch": b"01",
    96. "patch_offset": 6}
    97. '''
    98. THEME_CHECK_AOB_ONE
    99. Build: 2024, 2025, 2028
    100. IDA Sig: 80 78 ?? 00 74 08 0F 57 C0 0F 11 06 EB 0F 48 8D 15 ? ? ? ? 48 89 F1 E8 ? ? ? ? 48 89 F0 48 83 C4 20 5E C3 CC 56 57 <- windows
    101. 80 78 ?? 00 BA ? ? ? ? 48 0F 45 D1 B8 ? ? ? ? 48 0F 45 C1 C3 41 57 <- linux
    102. ^^ ^^
    103. 48 01
    104. Build 2024: @0x2d959 <- windows
    105. @0x39c661 <- linux
    106. Build 2025: @0x2d949 <- windows
    107. @0x39c67b <- linux
    108. Build 2028: @0x2da85 <- windows
    109. @0x398721 <- linux
    110. '''
    111. THEME_CHECK_AOB_ONE = {"windows": b"8078..0074080f57c00f1106eb0f488d15........4889f1e8........4889f04883c4205ec3cc5657",
    112. "linux": b"8078..00ba........480f45d1b8........480f45c1c34157",
    113. "patch_one": b"48",
    114. "patch_one_offset": 2,
    115. "patch_two": b"01",
    116. "patch_two_offset": 6}
    117. '''
    118. THEME_CHECK_AOB_TWO
    119. Build: 2027, 2030 (windows)
    120. IDA Sig: 80 38 00 74 08 0F 57 C0 0F 11 06 EB 0F 48 8D 15 ? ? ? ? 48 89 F1 E8 ? ? ? ? 48 89 F0 48 83 C4 20 5E C3 56 57 <- windows
    121. 80 38 00 BA ? ? ? ? 48 0F 45 D1 B8 ? ? ? ? 48 0F 45 C1 C3 CC 41 57 <- linux
    122. ^^ ^^
    123. 08 01
    124. Build 2027: @0x2d955 <- windows
    125. @0x39c71d <- linux
    126. Build 2030: @0x2cf47 <- windows
    127. @NaN <- linux
    128. '''
    129. THEME_CHECK_AOB_TWO = {"windows": b"80380074080f57c00f1106eb0f488d15........4889f1e8........4889f04883c4205ec35657",
    130. "linux": b"803800ba........480f45d1b8........480f45c1c3cc4157",
    131. "patch": b"0801",
    132. "patch_offset": 2}
    133. '''
    134. THEME_CHECK_AOB_THREE
    135. Build: 2030 (linux)
    136. IDA Sig: 80 38 00 74 08 0F 57 C0 0F 11 06 EB 0F 48 8D 15 ? ? ? ? 48 89 F1 E8 ? ? ? ? 48 89 F0 48 83 C4 20 5E C3 56 57 <- windows
    137. 80 38 00 BA ? ? ? ? 48 0F 45 D1 B8 ? ? ? ? 48 0F 45 C1 C3 CC 53 <- linux
    138. ^^ ^^
    139. 08 01
    140. Build 2030: @NaN <- windows
    141. @0x39b381 <- linux
    142. '''
    143. THEME_CHECK_AOB_THREE = {"windows": b"XXX",
    144. "linux": b"803800ba........480f45d1b8........480f45c1c3cc53",
    145. "patch": b"0801",
    146. "patch_offset": 2}
    147. def __init__(self, file_path):
    148. self.file_path = file_path
    149. # get hex dump of input file
    150. with open(file_path, "rb") as file:
    151. self.hex_dump = bytearray(binascii.hexlify(file.read()))
    152. def patch_file(self):
    153. os_target = self.__get_file_os_target()
    154. if os_target != None and self.__is_initial_license_check_index_valid(os_target) and self.__is_persistent_license_check_index_valid(os_target) and self.__is_theme_check_index_valid(os_target):
    155. # bypass & patch file with changes
    156. self.__patch_initial_license_check(os_target)
    157. self.__patch_persistent_license_check(os_target)
    158. self.__patch_theme_check(os_target)
    159. try:
    160. with open(self.file_path, "wb") as file:
    161. file.write(binascii.unhexlify(self.hex_dump))
    162. except PermissionError:
    163. return False
    164. return True
    165. return False
    166. def __get_file_os_target(self):
    167. if self.hex_dump.startswith(b"4d5a"):
    168. print(" >> Windows version")
    169. return "windows"
    170. elif self.hex_dump.startswith(b"7f454c4602"):
    171. print(" >> Linux version")
    172. return "linux"
    173. else:
    174. print(" >> Unknown version")
    175. return None
    176. def __index_is_valid(self, check_aob):
    177. patterns = re.findall(check_aob, self.hex_dump)
    178. pat_len = len(patterns)
    179. print(" >> found " + str(pat_len) + " occurences (1 expected)")
    180. for p in patterns:
    181. check_index = self.hex_dump.index(p)
    182. print(" >> found @" + hex(int(check_index/2)))
    183. return pat_len == 1
    184. def __is_initial_license_check_index_valid(self, os):
    185. print(" >> looking for INITIAL_LICENSE_CHECK_AOB")
    186. # yes, we are checking for the newer AOBs first, because
    187. # the old one can be found in the newer builds but at the wrong
    188. # location
    189. if self.__index_is_valid(Patcher.INITIAL_LICENSE_CHECK_AOB_ONE[os]):
    190. return True
    191. elif self.__index_is_valid(Patcher.INITIAL_LICENSE_CHECK_AOB_TWO[os]):
    192. return True
    193. else:
    194. return False
    195. def __is_persistent_license_check_index_valid(self, os):
    196. print(" >> looking for PERSISTENT_LICENSE_CHECK_AOB")
    197. if self.__index_is_valid(Patcher.PERSISTENT_LICENSE_CHECK_AOB_ONE[os]):
    198. return True
    199. elif self.__index_is_valid(Patcher.PERSISTENT_LICENSE_CHECK_AOB_TWO[os]):
    200. return True
    201. elif self.__index_is_valid(Patcher.PERSISTENT_LICENSE_CHECK_AOB_THREE[os]):
    202. return True
    203. else:
    204. return False
    205. def __is_theme_check_index_valid(self, os):
    206. print(" >> looking for THEME_CHECK_AOB")
    207. if self.__index_is_valid(Patcher.THEME_CHECK_AOB_ONE[os]):
    208. return True
    209. elif self.__index_is_valid(Patcher.THEME_CHECK_AOB_TWO[os]):
    210. return True
    211. elif self.__index_is_valid(Patcher.THEME_CHECK_AOB_THREE[os]):
    212. return True
    213. else:
    214. return False
    215. def __patch_check(self, check_aob, patch_aob, patch_offset = 0):
    216. # bypass checks
    217. check_index = self.hex_dump.index(re.findall(check_aob, self.hex_dump)[0])
    218. self.hex_dump[check_index+patch_offset:check_index+patch_offset+len(patch_aob)] = patch_aob
    219. def __patch_check_by_index(self, check_index, patch_aob, patch_offset = 0):
    220. # bypass checks
    221. self.hex_dump[check_index+patch_offset:check_index+patch_offset+len(patch_aob)] = patch_aob
    222. def __patch_initial_license_check(self, os):
    223. # bypass license
    224. patterns = re.findall(Patcher.INITIAL_LICENSE_CHECK_AOB_ONE[os], self.hex_dump)
    225. if len(patterns) == 1:
    226. index = self.hex_dump.index(patterns[0])
    227. self.__patch_check_by_index(index, Patcher.INITIAL_LICENSE_CHECK_AOB_ONE["patch_one"], Patcher.INITIAL_LICENSE_CHECK_AOB_ONE["patch_one_offset"])
    228. self.__patch_check_by_index(index, Patcher.INITIAL_LICENSE_CHECK_AOB_ONE["patch_two"], Patcher.INITIAL_LICENSE_CHECK_AOB_ONE["patch_two_offset"])
    229. else:
    230. patterns = re.findall(Patcher.INITIAL_LICENSE_CHECK_AOB_TWO[os], self.hex_dump)
    231. if len(patterns) == 1:
    232. index = self.hex_dump.index(patterns[0])
    233. self.__patch_check_by_index(index, Patcher.INITIAL_LICENSE_CHECK_AOB_TWO["patch"], Patcher.INITIAL_LICENSE_CHECK_AOB_TWO["patch_offset"])
    234. else:
    235. print(" >> error: expected 1 result, but got: " + str(len(patterns)))
    236. def __patch_persistent_license_check(self, os):
    237. # bypass license
    238. patterns = re.findall(Patcher.PERSISTENT_LICENSE_CHECK_AOB_ONE[os], self.hex_dump)
    239. if len(patterns) == 1:
    240. index = self.hex_dump.index(patterns[0])
    241. self.__patch_check_by_index(index, Patcher.PERSISTENT_LICENSE_CHECK_AOB_ONE["patch"], Patcher.PERSISTENT_LICENSE_CHECK_AOB_ONE["patch_offset"])
    242. else:
    243. patterns = re.findall(Patcher.PERSISTENT_LICENSE_CHECK_AOB_TWO[os], self.hex_dump)
    244. if len(patterns) == 1:
    245. index = self.hex_dump.index(patterns[0])
    246. self.__patch_check_by_index(index, Patcher.PERSISTENT_LICENSE_CHECK_AOB_TWO["patch"], Patcher.PERSISTENT_LICENSE_CHECK_AOB_TWO["patch_offset"])
    247. else:
    248. patterns = re.findall(Patcher.PERSISTENT_LICENSE_CHECK_AOB_THREE[os], self.hex_dump)
    249. if len(patterns) == 1:
    250. index = self.hex_dump.index(patterns[0])
    251. self.__patch_check_by_index(index, Patcher.PERSISTENT_LICENSE_CHECK_AOB_THREE["patch"], Patcher.PERSISTENT_LICENSE_CHECK_AOB_THREE["patch_offset"])
    252. else:
    253. print(" >> error: expected 1 result, but got: " + str(len(patterns)))
    254. def __patch_theme_check(self, os):
    255. # bypass theme
    256. patterns = re.findall(Patcher.THEME_CHECK_AOB_ONE[os], self.hex_dump)
    257. if len(patterns) == 1:
    258. index = self.hex_dump.index(patterns[0])
    259. self.__patch_check_by_index(index, Patcher.THEME_CHECK_AOB_ONE["patch_one"], Patcher.THEME_CHECK_AOB_ONE["patch_one_offset"])
    260. self.__patch_check_by_index(index, Patcher.THEME_CHECK_AOB_ONE["patch_two"], Patcher.THEME_CHECK_AOB_ONE["patch_two_offset"])
    261. else:
    262. patterns = re.findall(Patcher.THEME_CHECK_AOB_TWO[os], self.hex_dump)
    263. if len(patterns) == 1:
    264. index = self.hex_dump.index(patterns[0])
    265. self.__patch_check_by_index(index, Patcher.THEME_CHECK_AOB_TWO["patch"], Patcher.THEME_CHECK_AOB_TWO["patch_offset"])
    266. else:
    267. patterns = re.findall(Patcher.THEME_CHECK_AOB_THREE[os], self.hex_dump)
    268. if len(patterns) == 1:
    269. index = self.hex_dump.index(patterns[0])
    270. self.__patch_check_by_index(index, Patcher.THEME_CHECK_AOB_THREE["patch"], Patcher.THEME_CHECK_AOB_THREE["patch_offset"])
    271. else:
    272. print(" >> error: expected 1 result, but got: " + str(len(patterns)))
    273. # script logic
    274. def main():
    275. parser = argparse.ArgumentParser(description="Patch Sublime Merge")
    276. parser.add_argument("file_path", help="file path to sublime merge executable.")
    277. args = parser.parse_args()
    278. print("Patcher >> Starting job...")
    279. # check for valid file path
    280. if (os.path.isfile(args.file_path)):
    281. # performs patch
    282. patcher = Patcher(args.file_path)
    283. result = patcher.patch_file()
    284. if result:
    285. print("Patcher >> Successfully patched '" + args.file_path + "'.")
    286. else:
    287. print("Patcher >> Could not work on input file.")
    288. else:
    289. print("Patcher >> Please input a valid file path.")
    290. # direct execution of the script
    291. if __name__ == "__main__":
    292. main()

    from https://www.board4all.biz/threads/sublime-merge-dev-build-2030-windows-linux.823464/