Overhaul VMExtractor, adding FastPacket floppy support

This commit is contained in:
RichardG867
2022-04-13 18:39:12 -03:00
parent 60b9f04525
commit 54d3a83784
2 changed files with 114 additions and 38 deletions

View File

@@ -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

BIN
vm/UNLZEXE.EXE Normal file

Binary file not shown.