From 54d3a837844dcff2165bfdd0682f66ba9306754c Mon Sep 17 00:00:00 2001 From: RichardG867 Date: Wed, 13 Apr 2022 18:39:12 -0300 Subject: [PATCH] Overhaul VMExtractor, adding FastPacket floppy support --- biostools/extractors.py | 152 ++++++++++++++++++++++++++++++---------- vm/UNLZEXE.EXE | Bin 0 -> 9724 bytes 2 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 vm/UNLZEXE.EXE diff --git a/biostools/extractors.py b/biostools/extractors.py index d4e5daa..1feab71 100644 --- a/biostools/extractors.py +++ b/biostools/extractors.py @@ -2048,6 +2048,7 @@ class VMExtractor(ArchiveExtractor): # Known signatures. self._floppy_pattern = re.compile( + b'''( FastPacket V[0-9])|''' # Siemens Nixdorf FastPacket b''', Sydex, Inc\\. All Rights Reserved\\.|''' # IBM Sydex b'''Disk eXPress Self-Extracting Diskette Image''' # HP DXP ) @@ -2084,8 +2085,15 @@ class VMExtractor(ArchiveExtractor): # Check for cases which require this extractor. # All signatures should be within the first 32 KB or so. extractor = None - if file_header[:2] == b'MZ' and self._floppy_pattern.search(file_header): - extractor = self._extract_floppy + extractor_kwargs = {} + if file_header[:2] == b'MZ': + if file_header[28:32] == b'LZ91': + extractor = self._extract_lzexe + else: + match = self._floppy_pattern.search(file_header) + if match: + extractor = self._extract_floppy + extractor_kwargs['match'] = match elif self._eti_pattern.match(file_header): extractor = self._extract_eti @@ -2098,24 +2106,17 @@ class VMExtractor(ArchiveExtractor): return True # Run extractor. - return extractor(file_path, file_header, dest_dir, dest_dir_0) + return extractor(file_path, file_header, dest_dir, dest_dir_0, **extractor_kwargs) - def _run_qemu(self, dest_dir, deps, hdd=None, floppy=None): + def _run_qemu(self, dest_dir, deps, hdd=None, hdd_snapshot=True, floppy=None, floppy_snapshot=True, vvfat=False, boot='c'): # Copy dependencies. for dep_src, dep_dest in deps: + # Skip dynamically-generated dependencies. + if not dep_src: + continue + try: - dep_dest_path = os.path.join(dest_dir, dep_dest) - if os.path.basename(dep_src) == 'freedos.img' and floppy == None: - # Patch the "dir a:" command out when no floppy image - # is called for. This could be done in a better way. - f = open(dep_src, 'rb') - data = f.read() - f.close() - f = open(dep_dest_path, 'wb') - f.write(data.replace(b'dir a:\r\n', b'rem a:\r\n')) - f.close() - else: - shutil.copy2(dep_src, dep_dest_path) + shutil.copy2(dep_src, os.path.join(dest_dir, dep_dest)) except: try: shutil.rmtree(dest_dir) @@ -2125,23 +2126,33 @@ class VMExtractor(ArchiveExtractor): # Build QEMU arguments. dest_dir_sanitized = dest_dir.replace(',', ',,') - args = [self._qemu_path, '-nographic', '-m', '32'] - if hdd != None: - args += ['-boot', 'c'] - args += ['-drive', 'if=ide,format=raw,file=' + os.path.join(dest_dir_sanitized, deps[hdd][1])] - args += ['-drive', 'if=ide,driver=vvfat,rw=on,dir=' + dest_dir_sanitized] # regular vvfat syntax can't handle : in path - if floppy != None: - args += ['-drive', 'if=floppy,format=raw,file=' + os.path.join(dest_dir_sanitized, deps[floppy][1])] + args = [self._qemu_path, '-m', '32', '-display', 'none', '-vga', 'none', '-boot', boot] + for drive, drive_snapshot, drive_if in ((floppy, floppy_snapshot, 'floppy'), (hdd, hdd_snapshot, 'ide')): + # Don't add this drive if an image was not specified. + if not drive: + # Add dummy floppy to prevent errors if no floppy is specified. + if drive_if == 'floppy': + drive = os.path.join(self._dep_dir, 'floppy.144') + drive_snapshot = True + else: + continue + + # Add drive. + args += ['-drive', 'if=' + drive_if + ',snapshot=' + (drive_snapshot and 'on' or 'off') + ',format=raw,file=' + drive.replace(',', ',,')] + if vvfat: + # Add vvfat if requested. + args += ['-drive', 'if=ide,driver=vvfat,rw=on,dir=' + dest_dir_sanitized] # regular vvfat syntax can't handle : in path # Run QEMU. + import time try: - subprocess.run(args, timeout=30, input=None, stdout=self._devnull, stderr=None) + subprocess.run(args, timeout=60, input=None, stdout=self._devnull, stderr=subprocess.STDOUT, cwd=dest_dir) except: pass # Remove dependencies, except for the floppy image if present. for i in range(len(deps)): - if i == floppy: + if (not floppy_snapshot and deps[i][1] == floppy) or (not hdd_snapshot and deps[i][1] == hdd): continue try: os.remove(os.path.join(dest_dir, deps[i][1])) @@ -2150,7 +2161,7 @@ class VMExtractor(ArchiveExtractor): return True - def _extract_floppy(self, file_path, file_header, dest_dir, dest_dir_0): + def _extract_floppy(self, file_path, file_header, dest_dir, dest_dir_0, *, match): """Extract DOS-based floppy self-extractors.""" # Only support 1.44 MB floppies for now. @@ -2158,13 +2169,19 @@ class VMExtractor(ArchiveExtractor): # Establish dependencies. deps = ( - (os.path.join(self._dep_dir, floppy_media), '\\.img'), # DOS-invalid filenames on purpose, avoids conflicts - (os.path.join(self._dep_dir, 'freedos.img'), '\\\\.img'), + (os.path.join(self._dep_dir, floppy_media), 'floppy.img'), (file_path, 'target.exe') ) + # Create batch file for unattended FastPacket execution. + if match.group(1): + f = open(os.path.join(dest_dir, 'target.bat'), 'wb') + f.write(b'd:target.exe /b a:\r\n') + f.close() + deps += ((None, 'target.bat'), (None, 'target.tmp')) # target.tmp cleans temporary file if left behind + # Run QEMU and stop if it failed. - if not self._run_qemu(dest_dir, deps, hdd=1, floppy=0): + if not self._run_qemu(dest_dir, deps, hdd=os.path.join(self._dep_dir, 'freedos.img'), floppy='floppy.img', floppy_snapshot=False, vvfat=True): return True # Extract image as an archive. @@ -2193,6 +2210,12 @@ class VMExtractor(ArchiveExtractor): def _extract_eti(self, file_path, file_header, dest_dir, dest_dir_0): """Extract Evergreen ETI files.""" + # Establish dependencies. + deps = ( + (os.path.join(self._dep_dir, 'INSTL2O.EXE'), 'INSTL2O.EXE'), + (None, 'TARGET.BAT') + ) + # Read ETI header. in_f = open(file_path, 'rb') header = in_f.read(0x1f) @@ -2254,15 +2277,8 @@ class VMExtractor(ArchiveExtractor): bat_f.write(b'C:\r\n') bat_f.close() - # Establish dependencies. - deps = ( - (os.path.join(self._dep_dir, 'INSTL2O.EXE'), 'INSTL2O.EXE'), - (os.path.join(self._dep_dir, 'freedos.img'), 'freedos.img') - ) - - # Run QEMU and stop if it failed. - if not self._run_qemu(dest_dir, deps, hdd=1, floppy=None): - return True + # Run QEMU. + ret = self._run_qemu(dest_dir, deps, hdd=os.path.join(self._dep_dir, 'freedos.img'), vvfat=True) # Remove leftover files. for fn in ['CONTACT.ETI', 'CONTACT.TXT', 'PREVLANG.DAT', 'TARGET.BAT'] + etis: @@ -2275,6 +2291,10 @@ class VMExtractor(ArchiveExtractor): except: pass + # Stop if QEMU failed. + if not ret: + return False + # Check if anything was extracted. dest_dir_files = os.listdir(dest_dir) if len(dest_dir_files) > 0: @@ -2295,3 +2315,59 @@ class VMExtractor(ArchiveExtractor): return dest_dir else: return True + + def _extract_lzexe(self, file_path, file_header, dest_dir, dest_dir_0): + """Extract LZEXE executables and run them through the same pipeline. This is + required for Siemens Nixdorf FastPacket with its LZEXE-compressed stub.""" + + # Establish dependencies. + deps = ( + (os.path.join(self._dep_dir, 'UNLZEXE.EXE'), 'UNLZEXE.EXE'), + (file_path, 'target.exe'), + (None, 'target.bat') + ) + + # Create batch file for extraction. + f = open(os.path.join(dest_dir, 'target.bat'), 'wb') + f.write(b'D:\r\nUNLZEXE.EXE target.exe target.ulz\r\nC:\r\n') + f.close() + + # Run QEMU and stop if it failed. + if not self._run_qemu(dest_dir, deps, hdd=os.path.join(self._dep_dir, 'freedos.img'), vvfat=True): + return True + + # Stop if decompression was not successful. + decomp_file_path = os.path.join(dest_dir, 'target.ulz') + if not os.path.exists(decomp_file_path): + decomp_file_path = os.path.join(dest_dir, 'TARGET.ULZ') # just in case + if not os.path.exists(decomp_file_path): + return False + + # Read decompressed file. + decomp_file_data = util.read_complement(decomp_file_path) + + # Strip any remains of LZEXE to avoid infinite loops. + if decomp_file_data[28:32] == b'LZ91': + decomp_file_data = decomp_file_data[:28] + b'\xFF' + decomp_file_data[29:] + + # Run this same extractor with detectors pointed at the decompressed data. + ret = self.extract(file_path, decomp_file_data, dest_dir, dest_dir_0) + + # Remove original file. + try: + os.remove(file_path) + except: + pass + + # Stop if extraction was successful. + if ret: + # Remove decompressed file. + try: + os.remove(decomp_file_path) + except: + pass + + return ret + + # Keep the decompressed file around for other extractors to process. + return dest_dir diff --git a/vm/UNLZEXE.EXE b/vm/UNLZEXE.EXE new file mode 100644 index 0000000000000000000000000000000000000000..85a8de43e841971a84c523542901550840212cbe GIT binary patch literal 9724 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 literal 0 HcmV?d00001