-
-
Save tuxuser/d57087288d06bf406c2295aef16aea31 to your computer and use it in GitHub Desktop.
| """ | |
| Extracts individual files from Kerbal Space Program (KSP) Xbox / Playstation Savegame blobs | |
| Dependency: | |
| - dissect.cstruct (`pip install dissect.cstruct`) | |
| """ | |
| import io | |
| import os | |
| import argparse | |
| import pathlib | |
| import struct | |
| import lzma | |
| from dissect import cstruct | |
| TYPES = cstruct.cstruct() | |
| TYPES.load(""" | |
| struct KSP_BLOB_ENTRY { | |
| UINT EntryLen; | |
| BYTE Padding; | |
| BYTE FilenameLen; | |
| BYTE Padding2; | |
| BYTE LastFileMarker; | |
| CHAR Filename[FilenameLen]; | |
| BYTE Data[EntryLen]; | |
| }; | |
| """) | |
| def read_u32(data: bytes, offset: int) -> int: | |
| return struct.unpack("<I", data[offset:offset+4])[0] | |
| def decompress(data: bytes) -> bytes: | |
| context = lzma.LZMADecompressor( | |
| format=lzma.FORMAT_RAW, | |
| filters=[ | |
| {"id": lzma.FILTER_LZMA1}, | |
| ] | |
| ) | |
| return context.decompress(data) | |
| def extract_file(inputfile: io.BufferedReader, outputdir: pathlib.Path, dryrun: bool) -> None: | |
| inputfile.seek(0, os.SEEK_END) | |
| total_filesize = inputfile.tell() | |
| inputfile.seek(0, os.SEEK_SET) | |
| while True: | |
| parsed = TYPES.KSP_BLOB_ENTRY(inputfile) | |
| # Did we reach EOF yet? | |
| if parsed.LastFileMarker: | |
| assert parsed.Filename == b"" | |
| assert inputfile.tell() == total_filesize | |
| break | |
| # Strip leading "\" of filename and null terminator | |
| filename = parsed.Filename.decode('utf-8').strip()[1:-1] | |
| compressed = False | |
| if filename.endswith(".cmp"): | |
| compressed = True | |
| filename = filename[:-4] | |
| target_filepath = outputdir.joinpath(pathlib.PureWindowsPath(filename)) | |
| if not target_filepath.parent.exists() and not dryrun: | |
| target_filepath.parent.mkdir(parents=True, exist_ok=True) | |
| if not dryrun: | |
| compressed_data = parsed.Data.dumps() | |
| if compressed: | |
| compressed_length = len(compressed_data) | |
| uncompressed_length = read_u32(compressed_data, 5) | |
| print(f"{target_filepath} ({compressed_length=:X} {uncompressed_length=:X})") | |
| without_header = compressed_data[9:] | |
| data = decompress(without_header) | |
| assert len(data) == uncompressed_length, "Mismatch of decompressed data size" | |
| else: | |
| data = compressed_data | |
| with io.open(target_filepath, "wb") as f: | |
| f.write(data) | |
| else: | |
| print(target_filepath) | |
| def main() -> None: | |
| parser = argparse.ArgumentParser("KSP Savegame blob extractor") | |
| parser.add_argument("inputfile", help="Input file", type=argparse.FileType('rb')) | |
| parser.add_argument("outputdir", help="Output directory") | |
| parser.add_argument("--dry", action="store_true", help="Dry-Run (no extraction, no folder/file creation)") | |
| args = parser.parse_args() | |
| extract_file(args.inputfile, pathlib.Path(args.outputdir), args.dry) | |
| if __name__ == '__main__': | |
| main() |
Some documentation would be appreciated, currently on linux and I always get an error saying it got less bytes than expected. I may just be misunderstanding the script but I was trying to decompress/deblob a craft file from the playstation 4 so that I may use it on PC
I used a tool to decrypt PS4 saves and everything seems like a regular file but has .cmp at the end of it, which I assumed to mean compressed or something similair. My first test was a craft cover (.png), and when that didn't work, I tried a .craft
Sorry I cannot say anything for the PS4 version, this script was based on the Xbox One save game format.
I will correct the description until proven otherwise ^^
Usage is simply:
python ksp_savegames_extract.py compressed-file output-directory
PS: feel free to upload a compressed ps4 file here, I can take a look eventually.
Some documentation would be appreciated, currently on linux and I always get an error saying it got less bytes than expected. I may just be misunderstanding the script but I was trying to decompress/deblob a craft file from the playstation 4 so that I may use it on PC