Add GUI version of scrapper.py, needs PyQt5 and construct
This commit is contained in:
		
							parent
							
								
									88737f29e4
								
							
						
					
					
						commit
						9f69934a32
					
				
					 1 changed files with 667 additions and 0 deletions
				
			
		
							
								
								
									
										667
									
								
								tools/scrapper_gui.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										667
									
								
								tools/scrapper_gui.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,667 @@ | |||
| import sys | ||||
| from PyQt5.QtWidgets import * | ||||
| from PyQt5.QtCore import * | ||||
| from PyQt5.QtGui import * | ||||
| from PyQt5 import QtGui, QtCore, QtWidgets | ||||
| from threading import Thread | ||||
| from queue import Queue | ||||
| from construct import * | ||||
| from glob import glob | ||||
| import os | ||||
| 
 | ||||
| ScrapFile = Struct( | ||||
|     'path'/PrefixedArray(Int32ul, Byte), | ||||
|     'size'/Int32ul, | ||||
|     'offset'/Int32ul, | ||||
| ) | ||||
| 
 | ||||
| PackedFile = Struct( | ||||
|     Const(b'BFPK'), | ||||
|     Const(b'\0\0\0\0'), | ||||
|     'files'/PrefixedArray(Int32ul, ScrapFile), | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def get_path(file): | ||||
|     return str(bytes(file.path), "utf-8", | ||||
|                "backslashreplace") | ||||
| 
 | ||||
| 
 | ||||
| def fsize(n): | ||||
|     idx = 0 | ||||
|     l = ["", "K", "M", "G", "T", "P", "E"] | ||||
|     while n > 1024: | ||||
|         idx += 1 | ||||
|         n /= 1024 | ||||
|     return "{:.02f} {}B".format(n, l[idx]) | ||||
| 
 | ||||
| 
 | ||||
| class Tree(dict): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         self._size = 0 | ||||
|         self.data = None | ||||
|         self.path = None | ||||
|         super(type(self), self).__init__(*args, **kwargs) | ||||
| 
 | ||||
|     def __missing__(self, k): | ||||
|         self[k] = type(self)() | ||||
|         return self[k] | ||||
| 
 | ||||
|     @property | ||||
|     def size(self): | ||||
|         if self.data: | ||||
|             ret = self.data.size | ||||
|         else: | ||||
|             ret = 0 | ||||
|         for v in self.values(): | ||||
|             ret += v.size | ||||
|         return ret | ||||
| 
 | ||||
|     @size.setter | ||||
|     def size(self, s): | ||||
|         self._size = s | ||||
| 
 | ||||
| 
 | ||||
| def load_data(path): | ||||
|     ret = Tree() | ||||
|     fn = os.path.split(path)[-1] | ||||
|     ret[fn] = Tree() | ||||
|     with open(path, "rb") as fh: | ||||
|         data = PackedFile.parse_stream(fh) | ||||
|         for entry in data.files: | ||||
|             path = get_path(entry).split("/") | ||||
|             root = ret[fn] | ||||
|             for elem in path: | ||||
|                 root = root[elem] | ||||
|             root.data = entry | ||||
|     return ret | ||||
| 
 | ||||
| 
 | ||||
| class Patcher(object): | ||||
|     def __init__(self, ed): | ||||
|         self.editor = ed | ||||
| 
 | ||||
|     def debug(self, *args): | ||||
|         "Enable Scengraph Debugging Console" | ||||
|         print("DBG") | ||||
| 
 | ||||
|     def test(self, *args): | ||||
|         "TEST!" | ||||
|         print(self.editor.selected()) | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| class PackerThread(QThread): | ||||
|     signal = pyqtSignal('PyQt_PyObject') | ||||
| 
 | ||||
|     def __init__(self, node, path): | ||||
|         super().__init__() | ||||
| 
 | ||||
|         self.node = node | ||||
|         self.path = path | ||||
| 
 | ||||
|     def func(self, *args, **kwargs): | ||||
|         self.signal.emit(*args, **kwargs) | ||||
| 
 | ||||
|     def run(self): | ||||
|         try: | ||||
|             self.__run() | ||||
|         except: | ||||
|             (type, value, traceback) = sys.exc_info() | ||||
|             print(type, value, traceback) | ||||
|             sys.excepthook(type, value, traceback) | ||||
| 
 | ||||
|     def __run(self): | ||||
|         node = self.node | ||||
|         path = self.path | ||||
|         PAK = node.pak | ||||
|         file_list = [] | ||||
| 
 | ||||
|         self.func(("wtl", "Preprocessing")) | ||||
|         self.func(("range", 0,  0)) | ||||
|         for item in walk(node): | ||||
|             if item.struct: | ||||
|                 file_list.append(item) | ||||
|                 self.func(("upd", get_loc(item)[1], 1)) | ||||
| 
 | ||||
|         header = PackedFile.build(dict(files=[f.struct for f in file_list])) | ||||
|         total_size = len(header) | ||||
|         self.func(("wtl", "Updating offsets")) | ||||
|         self.func(("range", 0,  len(file_list))) | ||||
|         for idx, file in enumerate(file_list): | ||||
|             file.struct.offset = total_size | ||||
|             total_size += file.struct.size | ||||
|             self.func(("upd", get_loc(file)[1], idx)) | ||||
| 
 | ||||
|         header = PackedFile.build(dict(files=[f.struct for f in file_list])) | ||||
|         cnt = 0 | ||||
|         if path: | ||||
|             self.func(("wtl", "Writing output")) | ||||
|             self.func(("range", 0, total_size)) | ||||
|             with open(path, "wb") as of: | ||||
|                 of.write(header) | ||||
|                 written = len(header) | ||||
|                 self.func(("upd", "Header", written)) | ||||
|                 for file in file_list: | ||||
|                     loc = get_loc(file) | ||||
|                     data = get_data(file)[0] | ||||
|                     written += len(data) | ||||
|                     self.func(("upd", get_loc(file)[1], written)) | ||||
|                     of.write(data) | ||||
|             print("XXXXXXXXXXXXXXXXXXXXX") | ||||
|         self.func(("close",)) | ||||
|         return | ||||
| 
 | ||||
| 
 | ||||
| class PackerWindow(QProgressDialog): | ||||
| 
 | ||||
|     def __init__(self, node, path, *args, **kwargs): | ||||
|         self.node = node | ||||
|         self.path = path | ||||
|         self.mtx = QMutex() | ||||
|         super().__init__() | ||||
|         self.initUI() | ||||
|         self.setAutoClose(True) | ||||
|         self.setWindowTitle("Packing {}".format(node.pak)) | ||||
|         self.setCancelButtonText("Stop") | ||||
|         self.run() | ||||
| 
 | ||||
|     def initUI(self): | ||||
|         self.show() | ||||
|         self.ensurePolished() | ||||
|         self.setFixedSize(300, self.height()) | ||||
| 
 | ||||
|     def closeEvent(self, event): | ||||
|         print("BYE") | ||||
|         if self.th: | ||||
|             self.th.wait() | ||||
|         event.accept() | ||||
| 
 | ||||
|     def sig(self, val): | ||||
|         lock = QMutexLocker(self.mtx) | ||||
|         #print("SIG:", val) | ||||
|         typ, *args = val | ||||
|         if typ == "wtl": | ||||
|             self.setWindowTitle(*args) | ||||
|         elif typ == "close": | ||||
|             self.th.wait() | ||||
|             self.th = None | ||||
|             self.close() | ||||
|         elif typ == "range": | ||||
|             self.setRange(*args) | ||||
|         elif typ == "upd": | ||||
|             self.setLabelText(args[0]) | ||||
|             self.setValue(args[1]) | ||||
|         else: | ||||
|             raise ValueError("Unknown signal: {}".format(val)) | ||||
| 
 | ||||
|     def run(self): | ||||
|         self.queue = Queue() | ||||
|         self.th = PackerThread(self.node, self.path) | ||||
|         self.th.signal.connect(self.sig) | ||||
|         self.th.start() | ||||
| 
 | ||||
| 
 | ||||
| class MainWindow(QMainWindow): | ||||
|     def __init__(self, parent=None): | ||||
|         super(MainWindow, self).__init__(parent) | ||||
|         self.setWindowTitle("SMT - Scrap Mod Tool") | ||||
|         self.resize(800, 600) | ||||
|         self.center() | ||||
| 
 | ||||
|         menu_close_all = QAction("Close &All", self) | ||||
|         menu_close_all.setShortcut("Ctrl+Shift+C") | ||||
|         menu_close_all.triggered.connect(self.menu_close_all) | ||||
| 
 | ||||
|         menu_create = QAction("&Create new .packed file", self) | ||||
|         menu_create.setShortcut("Ctrl+N") | ||||
|         menu_create.triggered.connect(self.menu_new) | ||||
| 
 | ||||
|         menu_load = QAction("&Open .packed file", self) | ||||
|         menu_load.setShortcut("Ctrl+O") | ||||
|         menu_load.triggered.connect(self.menu_load) | ||||
| 
 | ||||
|         menu_exit = QAction("&Exit", self) | ||||
|         menu_exit.setShortcut("Ctrl+Q") | ||||
|         menu_exit.triggered.connect(qApp.quit) | ||||
| 
 | ||||
|         menu = self.menuBar() | ||||
|         fileMenu = menu.addMenu('&File') | ||||
|         fileMenu.addAction(menu_load) | ||||
|         fileMenu.addAction(menu_create) | ||||
|         fileMenu.addSeparator() | ||||
|         fileMenu.addAction(menu_close_all) | ||||
|         fileMenu.addSeparator() | ||||
|         fileMenu.addAction(menu_exit) | ||||
| 
 | ||||
|         self.editor = PackedEditor(self) | ||||
|         self.setCentralWidget(self.editor) | ||||
|         self.show() | ||||
|         self.menu_load(sys.argv[1:]) | ||||
| 
 | ||||
|     def menu_new(self): | ||||
|         data = PackedFile.build({'files': []}) | ||||
|         loc = QFileDialog.getSaveFileName( | ||||
|             self, 'Save file', os.getcwd(), "Scrapland Data Files (*.packed)")[0] | ||||
|         with open(loc, "wb") as of: | ||||
|             of.write(data) | ||||
|         self.editor.clear() | ||||
|         self.editor.load(loc) | ||||
| 
 | ||||
|     def menu_load(self, paths=None): | ||||
|         if not paths: | ||||
|             paths = QFileDialog.getOpenFileNames( | ||||
|                 self, 'Open file', os.getcwd(), "Scrapland Data Files (*.packed)")[0] | ||||
|         for path in paths: | ||||
|             path = os.path.abspath(path) | ||||
|             self.editor.load(path) | ||||
| 
 | ||||
|     def menu_close_all(self): | ||||
|         self.editor.clear() | ||||
| 
 | ||||
|     def center(self): | ||||
|         qr = self.frameGeometry() | ||||
|         cp = QDesktopWidget().availableGeometry().center() | ||||
|         qr.moveCenter(cp) | ||||
|         self.move(qr.topLeft()) | ||||
| 
 | ||||
| 
 | ||||
| def get_loc(item): | ||||
|     node = item | ||||
|     path = [] | ||||
|     while node: | ||||
|         path.insert(0, node.text(0)) | ||||
|         node = node.parent() | ||||
|     if path: | ||||
|         file = path.pop(0) | ||||
|     else: | ||||
|         file = None | ||||
|     path = "/".join(path) | ||||
|     return (file, path) | ||||
| 
 | ||||
| 
 | ||||
| def walk(node): | ||||
|     yield node | ||||
|     for child_idx in range(node.childCount()): | ||||
|         yield from walk(node.child(child_idx)) | ||||
| 
 | ||||
| 
 | ||||
| def read_file(path, size=None, offset=None): | ||||
|     data = None | ||||
|     with open(path, "rb") as fh: | ||||
|         if offset: | ||||
|             fh.seek(offset) | ||||
|         data = fh.read(size) | ||||
|     return data | ||||
| 
 | ||||
| 
 | ||||
| def get_data(node, size=None): | ||||
|     bin_data = None | ||||
|     more = None | ||||
|     if node.struct: | ||||
|         if size: | ||||
|             if node.struct.size > size: | ||||
|                 more = True | ||||
|             size = min(node.struct.size, size) | ||||
|         else: | ||||
|             size = node.struct.size | ||||
|         if node.path: | ||||
|             bin_data = read_file(node.path, size) | ||||
|         else: | ||||
|             bin_data = read_file(node.pak, size, node.struct.offset) | ||||
|     return bin_data, more | ||||
| 
 | ||||
| 
 | ||||
| def hexdump(data, addr=0): | ||||
|     res = [] | ||||
|     bin_data = list(data) | ||||
|     while bin_data: | ||||
|         chunk = bin_data[:16] | ||||
|         bin_data = bin_data[16:] | ||||
|         res.append("0x{:04x} | {}".format( | ||||
|             addr, " ".join("{:02x}".format(b) for b in chunk))) | ||||
|         addr += len(chunk) | ||||
|     return "\n".join(res) | ||||
| 
 | ||||
| 
 | ||||
| def build_tree(node, url): | ||||
|     pak = get_loc(node)[0] | ||||
|     subtree = Tree() | ||||
|     path = url.toLocalFile().replace("/", "\\") | ||||
|     if os.path.isfile(path): | ||||
|         full_path = os.path.abspath(path) | ||||
|         path, file = os.path.split(full_path) | ||||
|         loc = os.path.join(get_loc(node)[1], file) | ||||
|         root_node = subtree[pak] | ||||
|         for elem in loc.split("/"): | ||||
|             root_node = root_node[elem] | ||||
|         root_node.data = Container(path=bytes(loc, "utf-8"), size=os.path.getsize( | ||||
|             full_path), offset=0) | ||||
|         root_node.size = root_node.data.size | ||||
|         root_node.path = full_path | ||||
|     else: | ||||
|         for root, _, files in os.walk(path): | ||||
|             root = root.replace("/", os.sep) | ||||
|             for file in files: | ||||
|                 full_path = os.path.join(root, file) | ||||
|                 loc = full_path.replace(path.replace( | ||||
|                     "/", os.sep), "").strip(os.sep) | ||||
|                 loc = os.path.join(os.path.split( | ||||
|                     path)[-1], loc).replace(os.sep, "/") | ||||
|                 root_node = subtree[pak] | ||||
|                 for elem in loc.split("/"): | ||||
|                     root_node = root_node[elem] | ||||
|                 root_node.data = Container(path=bytes(loc, "utf-8"), size=os.path.getsize( | ||||
|                     full_path), offset=0) | ||||
|                 root_node.size = root_node.data.size | ||||
|                 root_node.path = full_path | ||||
|     return subtree[pak] | ||||
| 
 | ||||
| 
 | ||||
| class DataTree(QTreeWidget): | ||||
|     def dragEnterEvent(self, event): | ||||
|         m = event.mimeData() | ||||
|         if m.hasUrls(): | ||||
|             for url in m.urls(): | ||||
|                 if url.isLocalFile(): | ||||
|                     event.accept() | ||||
|                     return | ||||
|         event.accept() | ||||
| 
 | ||||
|     def dragMoveEvent(self, event): | ||||
|         if event.mimeData().hasUrls: | ||||
|             event.setDropAction(QtCore.Qt.CopyAction) | ||||
|         else: | ||||
|             event.setDropAction(QtCore.Qt.MoveAction) | ||||
|         event.accept() | ||||
| 
 | ||||
|     def dropEvent(self, event): | ||||
|         editor = self.window().editor | ||||
|         target = self.itemAt(event.pos()) | ||||
|         if target.struct: | ||||
|             return | ||||
|         print("Drop: ", get_loc(target)) | ||||
|         if event.source(): | ||||
|             selected = event.source().selectedItems() | ||||
|             for item in selected: | ||||
|                 labels = [item.text(i) for i in range(item.columnCount())] | ||||
|                 node = QTreeWidgetItem(target, labels) | ||||
|                 node.struct = item.struct | ||||
|                 node.struct.offset = 0 | ||||
|                 node.path = item.path | ||||
|                 node.pak = item.pak | ||||
|         else: | ||||
|             m = event.mimeData() | ||||
|             if m.hasUrls(): | ||||
|                 for url in m.urls(): | ||||
|                     tree = build_tree(target, url) | ||||
|                     editor.make_subtree(target, tree, target.pak) | ||||
|         editor.update_tree() | ||||
|         event.accept() | ||||
| 
 | ||||
| 
 | ||||
| class DataLoader(QThread): | ||||
|     signal = pyqtSignal('PyQt_PyObject') | ||||
| 
 | ||||
|     def __init__(self, path, subtree=None): | ||||
|         super().__init__() | ||||
|         self.path = path | ||||
| 
 | ||||
|     def run(self): | ||||
|         self.signal.emit((self.path, load_data(self.path))) | ||||
| 
 | ||||
| 
 | ||||
| class DataExtractor(QThread): | ||||
|     signal = pyqtSignal('PyQt_PyObject') | ||||
| 
 | ||||
|     def __init__(self, nodes, dest): | ||||
|         super().__init__() | ||||
|         self.dest = dest | ||||
|         self.nodes = nodes | ||||
| 
 | ||||
|     def run(self): | ||||
|         for node in self.nodes: | ||||
|             for ch in walk(node): | ||||
|                 if ch.struct: | ||||
|                     self.__extract(ch, self.dest) | ||||
|         self.signal.emit("Done!") | ||||
|         return | ||||
| 
 | ||||
|     def __extract(self, node, dest): | ||||
|         path = get_path(node.struct) | ||||
|         self.signal.emit("Extracting {}".format(path)) | ||||
|         folder, file = os.path.split(path) | ||||
|         folder = os.path.join(dest, folder) | ||||
|         folder = folder.replace("/", os.sep).replace("\\", os.sep) | ||||
|         file = os.path.join(folder, file) | ||||
|         os.makedirs(folder, exist_ok=True) | ||||
|         with open(node.pak, "rb") as pak: | ||||
|             with open(file, "wb") as of: | ||||
|                 pak.seek(node.struct.offset) | ||||
|                 of.write(pak.read(node.struct.size)) | ||||
| 
 | ||||
| 
 | ||||
| class PackedEditor(QWidget): | ||||
|     def __init__(self, parent=None): | ||||
|         super(PackedEditor, self).__init__(parent) | ||||
|         self.initUI() | ||||
|         self.data = {} | ||||
|         self.threads = [] | ||||
|         self.log_mutex = QMutex() | ||||
| 
 | ||||
|     def initUI(self): | ||||
|         self.initLayout() | ||||
|         self.show() | ||||
| 
 | ||||
|     def initLayout(self): | ||||
|         grid = QGridLayout() | ||||
|         hgrid = QSplitter(Qt.Horizontal) | ||||
|         vgrid = QSplitter(Qt.Vertical) | ||||
|         patcher_grid = QGridLayout() | ||||
| 
 | ||||
|         self.info_tab_hex = QTextEdit() | ||||
|         self.info_tab_text = QTextEdit() | ||||
|         self.info_tab_info = QTextEdit() | ||||
|         self.info_tabs = QTabWidget() | ||||
| 
 | ||||
|         self.info_tab_hex.setReadOnly(True) | ||||
|         self.info_tab_text.setReadOnly(True) | ||||
|         self.info_tab_info.setReadOnly(True) | ||||
| 
 | ||||
|         self.info_tabs.addTab(self.info_tab_info, "Info") | ||||
|         self.info_tabs.addTab(self.info_tab_text, "Text") | ||||
|         self.info_tabs.addTab(self.info_tab_hex, "Hexdump") | ||||
| 
 | ||||
|         self.util_tabs = QTabWidget() | ||||
| 
 | ||||
|         self.util_tab_log = QTextEdit() | ||||
|         self.util_tab_log.setReadOnly(True) | ||||
| 
 | ||||
|         self.util_tab_patch = QWidget() | ||||
| 
 | ||||
|         self.util_tab_patch.setLayout(patcher_grid) | ||||
|         self.util_tabs.addTab(self.util_tab_log, "Log") | ||||
|         self.util_tabs.addTab(self.util_tab_patch, "Patcher") | ||||
|         self.patcher = Patcher(self) | ||||
|         i = 0 | ||||
|         patcher_grid_w = 0 | ||||
|         for func in dir(self.patcher): | ||||
|             if func.startswith("__") or not hasattr(getattr(self.patcher, func), "__call__"): | ||||
|                 continue | ||||
|             patcher_grid_w += 1 | ||||
|         patcher_grid_w = int(patcher_grid_w**0.5) | ||||
|         for func in dir(self.patcher): | ||||
|             if func.startswith("__") or not hasattr(getattr(self.patcher, func), "__call__"): | ||||
|                 continue | ||||
|             func = getattr(self.patcher, func) | ||||
|             x, y = divmod(i, patcher_grid_w) | ||||
|             button = QPushButton(func.__name__.replace("_", " ").title()) | ||||
|             button.setToolTip(func.__doc__) | ||||
|             button.clicked.connect(func) | ||||
|             patcher_grid.addWidget(button, x, y) | ||||
|             i += 1 | ||||
| 
 | ||||
|         self.tree = DataTree() | ||||
|         self.tree.setHeaderLabels(["Path", "Size", "Offset"]) | ||||
|         self.tree.setSelectionMode(self.tree.ExtendedSelection) | ||||
|         self.tree.currentItemChanged.connect(self.tree_changed) | ||||
|         self.tree.setDragDropMode(self.tree.DragDrop | self.tree.InternalMove) | ||||
|         self.tree.setDragEnabled(True) | ||||
|         self.tree.setAcceptDrops(True) | ||||
|         self.tree.setDropIndicatorShown(True) | ||||
| 
 | ||||
|         hgrid.addWidget(self.tree) | ||||
|         hgrid.addWidget(self.info_tabs) | ||||
|         vgrid.addWidget(hgrid) | ||||
|         vgrid.addWidget(self.util_tabs) | ||||
|         grid.addWidget(vgrid) | ||||
|         self.setLayout(grid) | ||||
| 
 | ||||
|     def info(self, msg): | ||||
|         lock = QMutexLocker(self.log_mutex) | ||||
|         self.util_tab_log.insertPlainText(msg + "\n") | ||||
|         self.util_tab_log.moveCursor(QTextCursor.End) | ||||
| 
 | ||||
|     def load(self, path): | ||||
|         if path in self.data: | ||||
|             return | ||||
|         self.data[path] = DataLoader(path) | ||||
|         self.data[path].signal.connect(self.done_loading) | ||||
|         self.data[path].start() | ||||
|         self.info("Loading {}".format(path)) | ||||
| 
 | ||||
|     def done_loading(self, result): | ||||
|         path, data = result | ||||
|         self.data[path] = data | ||||
|         self.make_subtree(self.tree, data, path) | ||||
| 
 | ||||
|     def clear(self): | ||||
|         self.data.clear() | ||||
|         self.tree.clear() | ||||
|         self.info_tab_hex.setText("") | ||||
|         self.info_tab_text.setText("") | ||||
|         self.info_tab_info.setText("") | ||||
|         self.util_tab_log.setText("") | ||||
| 
 | ||||
|     def make_tree(self): | ||||
|         for pak in self.data: | ||||
|             if isinstance(self.data[pak], DataLoader): | ||||
|                 continue | ||||
|             self.make_subtree(self.tree, self.data[pak], pak) | ||||
| 
 | ||||
|     def update_tree(self, node=None): | ||||
|         total_size = 0 | ||||
|         if node is None: | ||||
|             node = self.tree.invisibleRootItem() | ||||
|         else: | ||||
|             if node.struct: | ||||
|                 total_size += node.struct.size | ||||
|         for child_idx in range(node.childCount()): | ||||
|             total_size += self.update_tree(node.child(child_idx)) | ||||
|         node.setText(1, fsize(total_size)) | ||||
|         return total_size | ||||
| 
 | ||||
|     @classmethod | ||||
|     def make_subtree(cls, tree, data, pak): | ||||
|         total_size = 0 | ||||
|         for name, children in sorted(data.items()): | ||||
|             total_size += children.size | ||||
|             if children.data: | ||||
|                 labels = [name, fsize(children.size), | ||||
|                           hex(children.data.offset)] | ||||
|             else: | ||||
|                 labels = [name, fsize(children.size), ""] | ||||
|             node = QTreeWidgetItem(tree, labels) | ||||
|             node.struct = children.data | ||||
|             node.path = children.path | ||||
|             node.pak = pak | ||||
|             cls.make_subtree(node, children, pak) | ||||
| 
 | ||||
|     def contextMenuEvent(self, event): | ||||
|         cmenu = QMenu(self) | ||||
|         actions = {} | ||||
|         actions[cmenu.addAction("Extract")] = self.extract_handler | ||||
|         actions[cmenu.addAction("Delete")] = self.del_handler | ||||
|         if all(node.parent() == None for node in self.selected()): | ||||
|             actions[cmenu.addAction("Save")] = self.save_handler | ||||
|         try: | ||||
|             act_fn = actions.get( | ||||
|                 cmenu.exec_(self.mapToGlobal(event.pos()))) | ||||
|         except TypeError: | ||||
|             return | ||||
|         if not act_fn: | ||||
|             return | ||||
|         th = act_fn(self.selected()) | ||||
|         if th: | ||||
|             th.signal.connect(self.info) | ||||
|             th.start() | ||||
|             self.threads.append(th) | ||||
| 
 | ||||
|     def extract_handler(self, nodes): | ||||
|         dest = QFileDialog.getExistingDirectory( | ||||
|             self, 'Open file', os.getcwd()) | ||||
|         if not nodes: | ||||
|             return | ||||
|         if not dest: | ||||
|             return | ||||
|         return DataExtractor(nodes, dest) | ||||
| 
 | ||||
|     def make_packed(self, node, path=None): | ||||
|         a = PackerWindow(node, path) | ||||
|         a.exec_() | ||||
| 
 | ||||
|     def save_handler(self, nodes): | ||||
|         for node in nodes: | ||||
|             path = QFileDialog.getSaveFileName( | ||||
|                 self, 'Save file', node.pak, "Scrapland Data Files (*.packed)")[0] | ||||
|             self.make_packed(node, path) | ||||
| 
 | ||||
|     def del_handler(self, nodes): | ||||
|         if not nodes: | ||||
|             return | ||||
|         root = self.tree.invisibleRootItem() | ||||
|         for node in nodes: | ||||
|             (node.parent() or root).removeChild(node) | ||||
|         self.update_tree() | ||||
| 
 | ||||
|     def get_info(self, node): | ||||
|         info = [] | ||||
|         if node.struct: | ||||
|             info.append(("Size", node.struct.size)) | ||||
|             info.append(("Offset", hex(node.struct.offset))) | ||||
|             info.append(("Path", get_path(node.struct))) | ||||
|         ret = "" | ||||
|         for k, v in info: | ||||
|             ret += "{}:\t{}\n".format(k, v) | ||||
|         return ret.strip() | ||||
| 
 | ||||
|     def tree_changed(self, new, old): | ||||
|         if not new: | ||||
|             return | ||||
|         self.info_tab_text.setText("") | ||||
|         self.info_tab_hex.setText("") | ||||
|         self.info_tab_info.setText(self.get_info(new)) | ||||
|         data, more = get_data(new, 1024) | ||||
|         if data: | ||||
|             more_text = "\n<...>" if more else "" | ||||
|             self.info_tab_hex.setText(hexdump(data)+more_text) | ||||
|             try: | ||||
|                 data = str(data, "cp1252") | ||||
|             except UnicodeDecodeError: | ||||
|                 data = repr(data) | ||||
|             self.info_tab_text.setText(data+more_text) | ||||
|             self.info_tab_info.setText(self.get_info(new)) | ||||
| 
 | ||||
|     def selected(self): | ||||
|         return self.tree.selectedItems() | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     app = QApplication(sys.argv) | ||||
|     win = MainWindow() | ||||
|     win.show() | ||||
|     sys.exit(app.exec_()) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue