# Disgaea PC / Disgaea 2 PC save game decompression # De-XORs and decompresses save files from these games. # Leaves headers intact, so actual save data starts at offset 0x44. # # Saves decompressed with this script can be repacked as well with # pseudo-compression to allow loading modified save files. # # Written by HenryEx # version 2 # # script for QuickBMS http://quickbms.aluigi.org ################################################ # set up virtual memory file for save data print "Preparing..." get FILENAME filename 0 get FILESIZE asize 0 if FILESIZE < 0x44 print "[!] Error: File too small! Exiting..." CleanExit endif log MEMORY_FILE 0 0 # MEMORY_FILE is the working copy for de-/encryption log MEMORY_FILE2 0 0 # MEMORY_FILE2 will be the target for de-/recompression putvarchr MEMORY_FILE 0x100000 0 putvarchr MEMORY_FILE2 0x100000 0 log MEMORY_FILE 0 0 # reset MF1 log MEMORY_FILE2 0 0 # reset MF2 getDString HEADSTART 0x20 get XORKEY long get XORUNK1 short get XORUNK2 short get XORCHUNKS long # num of XOR'd integers in file get FSIZE_COMP_A long # savedata size with YKCMP header minus padding (for XOR ints) SavePos FSTART 0 getDString MAGIC 8 0 # check for YKCMP_V1 string if already de-crypted / -compressed if MAGIC = "YKCMP_V1" ################################################ # Pseudo re-compress save print "File seems to be a decompressed save, will attempt to pseudo re-compress and encrypt it." get ARCHIVE_VERSION long 0 if ARCHIVE_VERSION != 4 print "[!] Unexpected archive version: %ARCHIVE_VERSION%! Exiting..." CleanExit endif get FSIZE_COMP_B long 0 # comp. filesize without 0x30 decryption header, minus padding get FSIZE_TARGET long 0 # target filesize without all headers after decomp. SavePos FSTART 0 # set up compression stuff and prepare MF2 print "Setting up file in memory..." putct "YKCMP_V1" string -1 MEMORY_FILE2 put ARCHIVE_VERSION long MEMORY_FILE2 put FSIZE_COMP_B long MEMORY_FILE2 # place holder, needs to be updated after recompression math FSIZE_TARGET = FILESIZE math FSIZE_TARGET - 0x44 put FSIZE_TARGET long MEMORY_FILE2 set FBYTES long FSIZE_TARGET math POS = FSTART # starting position to read bytes from, should be 0x44 # start pseudo compressing into MF2 print "Pseudo re-compressing save data..." append # append mode ON for FBYTES = FBYTES != 0 # loop while num of bytes to process is not 0 if FBYTES > 0x7F set READLEN byte 0x7F math FBYTES - 0x7F else set READLEN byte FBYTES math FBYTES = 0 endif put READLEN byte MEMORY_FILE2 # put byte length to straight copy log MEMORY_FILE2 POS READLEN 0 # append [READLEN] bytes to MF2 math POS + READLEN # increment read offset next append # append mode OFF get FSIZE_COMP_B asize MEMORY_FILE2 math FSIZE_COMP_A = FSIZE_COMP_B putvarchr MEMORY_FILE2 0xC FSIZE_COMP_B long # update header value ################################################ # Encrypt save file in MF1 print "Setting up encryption..." xmath PAD "4 - ( FSIZE_COMP_A % 4 )" # num of bytes for padding if PAD > 0 for i = 0 < PAD put 0 byte MEMORY_FILE2 # pad file with 0 for 32-bit alignment next i endif xmath FSIZE "FSIZE_COMP_A + PAD" xmath XORCHUNKS "FSIZE / 4" # print "Padding needed: %PAD%. Padded filesize is %FSIZE%. Xor chunks: %XORCHUNKS%." # Set up MF1 putDString HEADSTART 0x20 MEMORY_FILE put XORKEY long MEMORY_FILE put XORUNK1 short MEMORY_FILE put XORUNK2 short MEMORY_FILE put XORCHUNKS long MEMORY_FILE put FSIZE_COMP_A long MEMORY_FILE SavePos FSTART MEMORY_FILE append # append mode ON log MEMORY_FILE 0 FSIZE MEMORY_FILE2 # put recomp. save after encryption header append # append mode OFF print "Encrypting File..." # XOR save file with key for i = 0 < XORCHUNKS xmath POS "(i * 4) + FSTART" # get current file position getvarchr DATA MEMORY_FILE POS long math DATA u^ XORKEY putvarchr MEMORY_FILE POS DATA long next i get ENDSIZE asize MEMORY_FILE ################################################ # Save file to disk string FILENAME $ "save" # last occurrence + searched string print "Exporting re-compressed save to %FILENAME%" log FILENAME 0 ENDSIZE MEMORY_FILE CleanExit else ################################################ # Decrypt save print "Decrypting File..." # XOR save file with key log MEMORY_FILE 0 FILESIZE 0 for i = 0 < XORCHUNKS xmath POS "(i * 4) + FSTART" # get current file position getvarchr DATA MEMORY_FILE POS long math DATA u^ XORKEY putvarchr MEMORY_FILE POS DATA long next i goto FSTART MEMORY_FILE getDString MAGIC 8 MEMORY_FILE if MAGIC != "YKCMP_V1" print "[!] Unexpected magic string: %MAGIC%! Decryption might have failed. Exiting..." CleanExit endif ################################################ # Decompress save print "Begin Decompression..." get ARCHIVE_VERSION long MEMORY_FILE if ARCHIVE_VERSION != 4 print "[!] Unexpected archive version: %ARCHIVE_VERSION%! Exiting..." CleanExit endif get FSIZE_COMP_B long MEMORY_FILE # comp. filesize without 0x30 decryption header, minus padding get FSIZE_TARGET long MEMORY_FILE # target filesize without all headers after decomp. SavePos FSTART 0 # set up decompression and prepare MF2 print "Setting up file in memory..." putDString HEADSTART 0x20 MEMORY_FILE2 put XORKEY long MEMORY_FILE2 put XORUNK1 short MEMORY_FILE2 put XORUNK2 short MEMORY_FILE2 put XORCHUNKS long MEMORY_FILE2 put FSIZE_COMP_A long MEMORY_FILE2 putct "YKCMP_V1" string -1 MEMORY_FILE2 put ARCHIVE_VERSION long MEMORY_FILE2 put FSIZE_COMP_B long MEMORY_FILE2 put FSIZE_TARGET long MEMORY_FILE2 set FBYTES long FSIZE_COMP_B math FBYTES + 0x30 # num. of compressed bytes + XOR & YKCMP header math POS = FSTART # offset after the YKCMP_V1 header (save data start) for MF2 # start decompressing into MF2 print "Decompressing save data..." for i = 68 < FBYTES # i works as byte offset, start at offset 0x44 goto i MEMORY_FILE get A_BYTE byte MEMORY_FILE if A_BYTE >= 0xE0 # read data like XX XY YY get B_BYTE byte MEMORY_FILE get C_BYTE byte MEMORY_FILE set READLEN long A_BYTE math READLEN & 0x1F # remove 0xE0 from X math READLEN < 4 xmath READLEN "READLEN + (B_BYTE > 4)" math READLEN + 3 set SEEKBACK long B_BYTE math SEEKBACK & 0x0F math SEEKBACK < 8 math SEEKBACK + C_BYTE math SEEKBACK + 1 # 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%!" math i + 2 # advance counter of processed bytes elif A_BYTE >= 0xC0 # read data like XX YY get B_BYTE byte MEMORY_FILE math READLEN = A_BYTE math READLEN & 0x3F # remove 0xC0 from X math READLEN + 2 math SEEKBACK = B_BYTE math SEEKBACK + 1 # 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%!" math i + 1 # advance counter of processed bytes elif A_BYTE >= 0x80 # read data like XY math READLEN = A_BYTE math READLEN > 4 math READLEN & 3 # remove 0x80 from X math READLEN + 1 math SEEKBACK = A_BYTE math SEEKBACK & 0x0F math SEEKBACK + 1 # print "Offset %i|h4%: byte %A_BYTE|2h% is >= 0x80! Look back by %SEEKBACK% and copy %READLEN% bytes to %POS|6h%!" else # byte is < 0x80, straight copy next bytes MF1 -> MF2 # print "Offset %i|h4%: byte %A_BYTE|2h% is < 0x80! Straight copy %A_BYTE% bytes to %POS|6h%!" for j = 0 < A_BYTE math i + 1 getvarchr DATA MEMORY_FILE1 i byte put DATA byte MEMORY_FILE2 next j endif if A_BYTE >= 0x80 # Copy bytes within MF2 via lookback math POS - SEEKBACK for j = 0 < READLEN getvarchr DATA MEMORY_FILE2 POS byte put DATA byte MEMORY_FILE2 math POS + 1 next j endif get POS asize MEMORY_FILE2 next i # check if filesize matches? math ENDSIZE = POS math POS - 68 if FSIZE_TARGET != POS print "WARNING! Target filesize doesn't match real filesize!" endif ################################################ # Save file to disk string FILENAME P= "dec_%FILENAME%" print "Exporting decompressed save to %FILENAME%" log FILENAME 0 ENDSIZE MEMORY_FILE2 CleanExit endif