HenryEx

Disgaea PC save unpacking/repacking

Jul 29th, 2017
513
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # Disgaea PC / Disgaea 2 PC save game decompression
  2. # De-XORs and decompresses save files from these games.
  3. # Leaves headers intact, so actual save data starts at offset 0x44.
  4. #
  5. # Saves decompressed with this script can be repacked as well with
  6. # pseudo-compression to allow loading modified save files.
  7. #
  8. # Written by HenryEx
  9. # version 2
  10. #
  11. # script for QuickBMS http://quickbms.aluigi.org
  12.  
  13. ################################################
  14. # set up virtual memory file for save data
  15. print "Preparing..."
  16. get FILENAME filename 0
  17. get FILESIZE asize 0
  18. if FILESIZE < 0x44
  19. print "[!] Error: File too small! Exiting..."
  20. CleanExit
  21. endif
  22. log MEMORY_FILE 0 0 # MEMORY_FILE is the working copy for de-/encryption
  23. log MEMORY_FILE2 0 0 # MEMORY_FILE2 will be the target for de-/recompression
  24. putvarchr MEMORY_FILE 0x100000 0
  25. putvarchr MEMORY_FILE2 0x100000 0
  26. log MEMORY_FILE 0 0 # reset MF1
  27. log MEMORY_FILE2 0 0 # reset MF2
  28.  
  29. getDString HEADSTART 0x20
  30. get XORKEY long
  31. get XORUNK1 short
  32. get XORUNK2 short
  33. get XORCHUNKS long # num of XOR'd integers in file
  34. get FSIZE_COMP_A long # savedata size with YKCMP header minus padding (for XOR ints)
  35. SavePos FSTART 0
  36. getDString MAGIC 8 0 # check for YKCMP_V1 string if already de-crypted / -compressed
  37.  
  38. if MAGIC = "YKCMP_V1"
  39. ################################################
  40. # Pseudo re-compress save
  41. print "File seems to be a decompressed save, will attempt to pseudo re-compress and encrypt it."
  42.  
  43. get ARCHIVE_VERSION long 0
  44. if ARCHIVE_VERSION != 4
  45. print "[!] Unexpected archive version: %ARCHIVE_VERSION%! Exiting..."
  46. CleanExit
  47. endif
  48. get FSIZE_COMP_B long 0 # comp. filesize without 0x30 decryption header, minus padding
  49. get FSIZE_TARGET long 0 # target filesize without all headers after decomp.
  50. SavePos FSTART 0
  51.  
  52. # set up compression stuff and prepare MF2
  53. print "Setting up file in memory..."
  54. putct "YKCMP_V1" string -1 MEMORY_FILE2
  55. put ARCHIVE_VERSION long MEMORY_FILE2
  56. put FSIZE_COMP_B long MEMORY_FILE2 # place holder, needs to be updated after recompression
  57. math FSIZE_TARGET = FILESIZE
  58. math FSIZE_TARGET - 0x44
  59. put FSIZE_TARGET long MEMORY_FILE2
  60. set FBYTES long FSIZE_TARGET
  61. math POS = FSTART # starting position to read bytes from, should be 0x44
  62.  
  63. # start pseudo compressing into MF2
  64. print "Pseudo re-compressing save data..."
  65. append # append mode ON
  66. for FBYTES = FBYTES != 0 # loop while num of bytes to process is not 0
  67. if FBYTES > 0x7F
  68. set READLEN byte 0x7F
  69. math FBYTES - 0x7F
  70. else
  71. set READLEN byte FBYTES
  72. math FBYTES = 0
  73. endif
  74.  
  75. put READLEN byte MEMORY_FILE2 # put byte length to straight copy
  76. log MEMORY_FILE2 POS READLEN 0 # append [READLEN] bytes to MF2
  77. math POS + READLEN # increment read offset
  78. next
  79. append # append mode OFF
  80.  
  81. get FSIZE_COMP_B asize MEMORY_FILE2
  82. math FSIZE_COMP_A = FSIZE_COMP_B
  83. putvarchr MEMORY_FILE2 0xC FSIZE_COMP_B long # update header value
  84.  
  85. ################################################
  86. # Encrypt save file in MF1
  87.  
  88. print "Setting up encryption..."
  89. xmath PAD "4 - ( FSIZE_COMP_A % 4 )" # num of bytes for padding
  90. if PAD > 0
  91. for i = 0 < PAD
  92. put 0 byte MEMORY_FILE2 # pad file with 0 for 32-bit alignment
  93. next i
  94. endif
  95. xmath FSIZE "FSIZE_COMP_A + PAD"
  96. xmath XORCHUNKS "FSIZE / 4"
  97. # print "Padding needed: %PAD%. Padded filesize is %FSIZE%. Xor chunks: %XORCHUNKS%."
  98.  
  99. # Set up MF1
  100. putDString HEADSTART 0x20 MEMORY_FILE
  101. put XORKEY long MEMORY_FILE
  102. put XORUNK1 short MEMORY_FILE
  103. put XORUNK2 short MEMORY_FILE
  104. put XORCHUNKS long MEMORY_FILE
  105. put FSIZE_COMP_A long MEMORY_FILE
  106. SavePos FSTART MEMORY_FILE
  107. append # append mode ON
  108. log MEMORY_FILE 0 FSIZE MEMORY_FILE2 # put recomp. save after encryption header
  109. append # append mode OFF
  110.  
  111. print "Encrypting File..."
  112. # XOR save file with key
  113. for i = 0 < XORCHUNKS
  114. xmath POS "(i * 4) + FSTART" # get current file position
  115.  
  116. getvarchr DATA MEMORY_FILE POS long
  117. math DATA u^ XORKEY
  118. putvarchr MEMORY_FILE POS DATA long
  119. next i
  120.  
  121. get ENDSIZE asize MEMORY_FILE
  122.  
  123. ################################################
  124. # Save file to disk
  125.  
  126. string FILENAME $ "save" # last occurrence + searched string
  127. print "Exporting re-compressed save to %FILENAME%"
  128. log FILENAME 0 ENDSIZE MEMORY_FILE
  129.  
  130. CleanExit
  131.  
  132. else
  133. ################################################
  134. # Decrypt save
  135.  
  136. print "Decrypting File..."
  137. # XOR save file with key
  138. log MEMORY_FILE 0 FILESIZE 0
  139. for i = 0 < XORCHUNKS
  140. xmath POS "(i * 4) + FSTART" # get current file position
  141.  
  142. getvarchr DATA MEMORY_FILE POS long
  143. math DATA u^ XORKEY
  144. putvarchr MEMORY_FILE POS DATA long
  145. next i
  146.  
  147. goto FSTART MEMORY_FILE
  148. getDString MAGIC 8 MEMORY_FILE
  149. if MAGIC != "YKCMP_V1"
  150. print "[!] Unexpected magic string: %MAGIC%! Decryption might have failed. Exiting..."
  151. CleanExit
  152. endif
  153.  
  154.  
  155. ################################################
  156. # Decompress save
  157.  
  158. print "Begin Decompression..."
  159. get ARCHIVE_VERSION long MEMORY_FILE
  160. if ARCHIVE_VERSION != 4
  161. print "[!] Unexpected archive version: %ARCHIVE_VERSION%! Exiting..."
  162. CleanExit
  163. endif
  164. get FSIZE_COMP_B long MEMORY_FILE # comp. filesize without 0x30 decryption header, minus padding
  165. get FSIZE_TARGET long MEMORY_FILE # target filesize without all headers after decomp.
  166. SavePos FSTART 0
  167.  
  168. # set up decompression and prepare MF2
  169. print "Setting up file in memory..."
  170. putDString HEADSTART 0x20 MEMORY_FILE2
  171. put XORKEY long MEMORY_FILE2
  172. put XORUNK1 short MEMORY_FILE2
  173. put XORUNK2 short MEMORY_FILE2
  174. put XORCHUNKS long MEMORY_FILE2
  175. put FSIZE_COMP_A long MEMORY_FILE2
  176. putct "YKCMP_V1" string -1 MEMORY_FILE2
  177. put ARCHIVE_VERSION long MEMORY_FILE2
  178. put FSIZE_COMP_B long MEMORY_FILE2
  179. put FSIZE_TARGET long MEMORY_FILE2
  180. set FBYTES long FSIZE_COMP_B
  181. math FBYTES + 0x30 # num. of compressed bytes + XOR & YKCMP header
  182.  
  183. math POS = FSTART # offset after the YKCMP_V1 header (save data start) for MF2
  184.  
  185. # start decompressing into MF2
  186. print "Decompressing save data..."
  187. for i = 68 < FBYTES # i works as byte offset, start at offset 0x44
  188. goto i MEMORY_FILE
  189. get A_BYTE byte MEMORY_FILE
  190.  
  191. if A_BYTE >= 0xE0 # read data like XX XY YY
  192. get B_BYTE byte MEMORY_FILE
  193. get C_BYTE byte MEMORY_FILE
  194. set READLEN long A_BYTE
  195. math READLEN & 0x1F # remove 0xE0 from X
  196. math READLEN < 4
  197. xmath READLEN "READLEN + (B_BYTE > 4)"
  198. math READLEN + 3
  199. set SEEKBACK long B_BYTE
  200. math SEEKBACK & 0x0F
  201. math SEEKBACK < 8
  202. math SEEKBACK + C_BYTE
  203. math SEEKBACK + 1
  204. # print "Offset %i|h4%: byte %A_BYTE|2h% is >= 0xE0! Next bytes %B_BYTE|2h% %C_BYTE|2h%. Look back by %SEEKBACK% and copy %READLEN% bytes to %POS|6h%!"
  205. math i + 2 # advance counter of processed bytes
  206. elif A_BYTE >= 0xC0 # read data like XX YY
  207. get B_BYTE byte MEMORY_FILE
  208. math READLEN = A_BYTE
  209. math READLEN & 0x3F # remove 0xC0 from X
  210. math READLEN + 2
  211. math SEEKBACK = B_BYTE
  212. math SEEKBACK + 1
  213. # print "Offset %i|h4%: byte %A_BYTE|2h% is >= 0xC0! Next byte %B_BYTE|2h%. Look back by %SEEKBACK% and copy %READLEN% bytes to %POS|6h%!"
  214. math i + 1 # advance counter of processed bytes
  215. elif A_BYTE >= 0x80 # read data like XY
  216. math READLEN = A_BYTE
  217. math READLEN > 4
  218. math READLEN & 3 # remove 0x80 from X
  219. math READLEN + 1
  220. math SEEKBACK = A_BYTE
  221. math SEEKBACK & 0x0F
  222. math SEEKBACK + 1
  223. # print "Offset %i|h4%: byte %A_BYTE|2h% is >= 0x80! Look back by %SEEKBACK% and copy %READLEN% bytes to %POS|6h%!"
  224. else # byte is < 0x80, straight copy next bytes MF1 -> MF2
  225. # print "Offset %i|h4%: byte %A_BYTE|2h% is < 0x80! Straight copy %A_BYTE% bytes to %POS|6h%!"
  226. for j = 0 < A_BYTE
  227. math i + 1
  228. getvarchr DATA MEMORY_FILE1 i byte
  229. put DATA byte MEMORY_FILE2
  230. next j
  231. endif
  232.  
  233. if A_BYTE >= 0x80 # Copy bytes within MF2 via lookback
  234.  
  235. math POS - SEEKBACK
  236.  
  237. for j = 0 < READLEN
  238. getvarchr DATA MEMORY_FILE2 POS byte
  239. put DATA byte MEMORY_FILE2
  240. math POS + 1
  241. next j
  242. endif
  243.  
  244. get POS asize MEMORY_FILE2
  245. next i
  246.  
  247. # check if filesize matches?
  248. math ENDSIZE = POS
  249. math POS - 68
  250. if FSIZE_TARGET != POS
  251. print "WARNING! Target filesize doesn't match real filesize!"
  252. endif
  253.  
  254. ################################################
  255. # Save file to disk
  256.  
  257. string FILENAME P= "dec_%FILENAME%"
  258. print "Exporting decompressed save to %FILENAME%"
  259. log FILENAME 0 ENDSIZE MEMORY_FILE2
  260.  
  261. CleanExit
  262.  
  263. endif
RAW Paste Data