From bdd4cf59c137cd98aea9616b61640c7bfefdda9d Mon Sep 17 00:00:00 2001 From: Earthnuker Date: Sun, 8 Oct 2017 01:35:50 +0200 Subject: [PATCH] Added scrapper.py and parse_save.py --- parse_save.py | 24 ++++++ scrapper.py | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 parse_save.py create mode 100644 scrapper.py diff --git a/parse_save.py b/parse_save.py new file mode 100644 index 0000000..6617f91 --- /dev/null +++ b/parse_save.py @@ -0,0 +1,24 @@ +from construct import * +from pprint import pprint +ScrapSaveStr = Struct( + 'length'/Int32ul, + 'data'/String(this.length,encoding='utf-8'), + ) +ScrapSaveVar = Struct( + 'v_name_size'/Int32ul, + 'v_name'/String(lambda ctx: ctx.v_name_size,encoding='utf-8'), + 'v_data_size'/Int32ul, + 'v_data'/String(lambda ctx: ctx.v_data_size,encoding='utf-8'), +) +ScrapSave = 'ScarpSaveGame'/Struct( + 'title'/ScrapSaveStr, + 'id'/ScrapSaveStr, + 'num_vars'/Int32ul, + 'data'/ScrapSaveVar[this.num_vars], + Terminated + ) +with open("Save0.sav", 'rb') as sav_file: + save = ScrapSave.parse_stream(sav_file) + pprint(save) + #for block in save.data: + # print("{}: {}".format(block.v_name, block.v_data)) diff --git a/scrapper.py b/scrapper.py new file mode 100644 index 0000000..53cafd6 --- /dev/null +++ b/scrapper.py @@ -0,0 +1,213 @@ +import argparse +from collections import OrderedDict +import configparser +import glob +import os +import shutil +from construct import * +from tqdm import tqdm +setglobalstringencoding(None) +def find_file(name): + global scrap_dir + for folder in glob.glob(os.path.join(scrap_dir, 'extracted', '*.packed')): + for root, folders, files in os.walk(folder): + for filename in files: + path = os.path.join(root, filename) + if filename == name: + yield path + +def get_config(conf_parse, section, var, default=None): + return conf_parse[section].get(var, default) + + +def patch_config(path, section, var, value): + config = configparser.ConfigParser() + config.read(path) + if get_config(config, section, var) == value: + return + config[section][var] = value + with open(path, 'w') as conf: + config.write(conf) + return True + + +def enable_debug_console_gui(): + '''enable debug console (GUI)''' + for path in find_file('m3d.ini'): + print('Found', path) + return patch_config(path, 'video', 'ConsolaWnd', 'SI') + + +def enable_debug_console_txt(): + '''enable debug console (Text Mode)''' + path = "Test" + for path in find_file('m3d.ini'): + print('Found', path) + return patch_config(path, 'video', 'ConsolaTxt', 'SI') + print(path) + + +patches = [ + enable_debug_console_gui, + enable_debug_console_txt, +] + + +def yn(prompt, default='n'): + c = ['y', 'n'] + default = default.lower() + assert default in c + c[c.index(default)] = c[c.index(default)].upper() + prompt += ' ({}) '.format('/'.join(c)) + return (input(prompt) or default).lower()[0] == 'y' + +ScrapFile = Struct( + 'path'/PascalString(Int32ul), + 'size'/Int32ul, + 'offset'/Int32ul, + 'data'/OnDemandPointer(this.offset,Bytes(this.size)), + ) +DummyFile = Struct( + 'path'/PascalString(Int32ul), + 'size'/Int32ul, + 'offset'/Int32ul, + ) + +PackedHeader = Struct( + Const(b'BFPK'), + Const(b'\0\0\0\0'), + 'files_cnt'/Rebuild(Int32ul,len_(this.files)), + 'files'/ScrapFile[this.files_cnt], + 'offset'/Tell, + ) +DummyHeader = Struct( + Const(b'BFPK'), + Const(b'\0\0\0\0'), + 'files_cnt'/Rebuild(Int32ul,len_(this.files)), + 'files'/DummyFile[this.files_cnt], + 'offset'/Tell, + ) +parser = argparse.ArgumentParser(description='Unpack and Repack .packed files') +parser.add_argument('-u', '--unpack', action='store_true', + help='unpack file to \'extracted\' directory') +parser.add_argument('-r', '--repack', action='store_true', + help='repack file from \'extracted\' directory') +parser.add_argument('-p', '--patch', action='store_true', + help='apply a premade patch') + +parser.add_argument( + '--reset', + action='store_true', + default=False, + help='restore backup') + +parser.add_argument( + 'scrap_dir', + metavar='Scrapland Directory', + type=str, + help='Scrapland installation directory') +options = parser.parse_args() +scrap_dir = os.path.abspath(options.scrap_dir) + +if options.reset: + print('Restoring Backups and removing extracted folder...') + for packed_file in glob.glob(os.path.join(scrap_dir, '*.packed.bak')): + outfile = os.path.basename(packed_file) + orig_filename = outfile[:-4] + if os.path.isfile(outfile): + print('deleting', orig_filename) + os.remove(orig_filename) + print('moving', outfile, '->', orig_filename) + shutil.move(outfile, orig_filename) + target_folder = os.path.join( + 'extracted', os.path.basename(orig_filename)) + print('deleting', target_folder) + shutil.rmtree(target_folder) + if os.path.isdir('extracted'): + input('Press enter to remove rest of extracted folder') + shutil.rmtree('extracted') + exit('Done!') + +if not (options.unpack or options.repack or options.patch): + parser.print_help() + exit() +pstatus = '' +if options.unpack: + if os.path.isdir('extracted'): + print("Removing extracted folder") + shutil.rmtree('extracted') + for packed_file in glob.glob(os.path.join(scrap_dir, '*.packed')): + os.chdir(scrap_dir) + BN=os.path.basename(packed_file) + target_folder = os.path.join( + 'extracted', os.path.basename(packed_file)) + os.makedirs(target_folder, exist_ok=True) + os.chdir(target_folder) + print('Unpacking {}'.format(os.path.basename(packed_file))) + with open(packed_file, 'rb') as pkfile: + data = PackedHeader.parse_stream(pkfile) + print("Offset:",hex(data.offset)) + for file in tqdm(data.files,ascii=True): + folder, filename = os.path.split(file.path) + if folder: + os.makedirs(folder, exist_ok=True) + with open(file.path, 'wb') as outfile: + outfile.write(file.data()) + print('\r' + ' ' * len(pstatus) + '\r', end='', flush=True) + os.chdir(scrap_dir) + +if (options.unpack and options.repack) and not options.patch: + #input('Press enter to rebuild *.packed files from folders in \'extracted\' dir...') # noqa + pass + +if options.patch: + print() + print("Enter Nothing to continue") + for n, patch in enumerate(patches, 1): + print('{}. {}'.format(n, patch.__doc__.strip())) + while 1: + n = input('Patch to apply: ') + if not n: + break + n = int(n) - 1 + if 0 <= n < len(patches): + res = patches[n]() + if res is True: + print('Applied Succesfully!') + elif res is None: + print('Already applied.') + elif res is False: + print('Error') + print() + +def file_gen(files,offset=0): + for real_path,size,path in files: + file=dict( + path=path, + offset=offset, + size=size) + yield file + offset+=file['size'] +def make_header(files,offset=0): + files_list=list(file_gen(files,offset)) + return DummyHeader.build(dict(files=files_list)) + +if options.repack: + for folder in glob.glob(os.path.join(scrap_dir, 'extracted', '*.packed')): + data=[] + filename=os.path.join(scrap_dir,os.path.basename(folder)) + for root,folders,files in os.walk(folder): + for file in sorted(files): + file=os.path.join(root,file) + rel_path=bytes(file.replace(folder, '').replace('\\', '/').lstrip('/'), 'windows-1252') + size=os.stat(file).st_size + data.append((file,size,rel_path)) + print("Found {} files for {}".format(len(data),filename)) + offset=len(make_header(data)) + print("Writing",filename) + header=make_header(data,offset) + with open(filename,"wb") as outfile: + outfile.write(header) + for file,size,rel_path in tqdm(data,ascii=True): + outfile.write(open(file,"rb").read()) +print('Done!')