Minor stylistic fixes and README updates.

This commit is contained in:
auREAX 2012-11-21 05:02:57 +01:00
parent 412886cb77
commit a50e17589b
2 changed files with 27 additions and 25 deletions

View file

@ -6,7 +6,7 @@ Currently, only writing to RPAv2/RPAv3 archives is supported.
Usage Usage
----- -----
rpatool [-l] [-x] [-c] [-d] [-a] [-o OUTFILE] [-2] [-3] [-k KEY] rpatool [-l|-x|-c|-d] [-a] [-o OUTFILE] [-2] [-3] [-k KEY]
[-p COUNT] [-h] [-v] [-V] [-p COUNT] [-h] [-v] [-V]
ARCHIVE [FILE [FILE ...]] ARCHIVE [FILE [FILE ...]]
@ -15,7 +15,7 @@ Usage
ARCHIVE The Ren'py archive file to operate on ARCHIVE The Ren'py archive file to operate on
FILE Zero or more files to operate on FILE Zero or more files to operate on
action arguments: actions:
-l, --list List files in archive ARCHIVE -l, --list List files in archive ARCHIVE
-x, --extract Extract FILEs from ARCHIVE -x, --extract Extract FILEs from ARCHIVE
-c, --create Creative ARCHIVE from FILEs -c, --create Creative ARCHIVE from FILEs

48
rpatool
View file

@ -26,25 +26,27 @@ class RenPyArchive:
self.key = key self.key = key
self.verbose = verbose self.verbose = verbose
if file != None: if file is not None:
self.load(file) self.load(file)
else: else:
self.version = version self.version = version
def __del__(self): def __del__(self):
if self.handle != None: if self.handle is not None:
self.handle.close() self.handle.close()
# Determine archive version. # Determine archive version.
def get_version(self): def get_version(self):
self.handle.seek(0) self.handle.seek(0)
magic = self.handle.readline().decode('utf-8') magic = self.handle.readline().decode('utf-8')
if magic.startswith(self.RPA3_MAGIC): if magic.startswith(self.RPA3_MAGIC):
return 3 return 3
elif magic.startswith(self.RPA2_MAGIC): elif magic.startswith(self.RPA2_MAGIC):
return 2 return 2
elif self.file.endswith('.rpi'): elif self.file.endswith('.rpi'):
return 1 return 1
raise ValueError('the given file is not a valid Ren\'Py archive, or an unsupported version') raise ValueError('the given file is not a valid Ren\'Py archive, or an unsupported version')
# Extract file indexes from opened archive. # Extract file indexes from opened archive.
@ -53,17 +55,17 @@ class RenPyArchive:
indexes = None indexes = None
if self.version == 2 or self.version == 3: if self.version == 2 or self.version == 3:
# Fetch metadata # Fetch metadata.
metadata = self.handle.readline() metadata = self.handle.readline()
offset = int(metadata[8:24], 16) offset = int(metadata[8:24], 16)
if self.version == 3: if self.version == 3:
self.key = int(metadata[25:33], 16) self.key = int(metadata[25:33], 16)
# Load in indexes # Load in indexes.
self.handle.seek(offset) self.handle.seek(offset)
indexes = pickle.loads(self.handle.read().decode('zlib')) indexes = pickle.loads(self.handle.read().decode('zlib'))
# Deobfuscate indexes # Deobfuscate indexes.
if self.version == 3: if self.version == 3:
obfuscated_indexes = indexes obfuscated_indexes = indexes
indexes = {} indexes = {}
@ -111,12 +113,12 @@ class RenPyArchive:
def read(self, filename): def read(self, filename):
filename = self.convert_filename(filename) filename = self.convert_filename(filename)
# Check if the file exists in our indexes # Check if the file exists in our indexes.
if not filename in self.files and not filename in self.indexes: if not filename in self.files and not filename in self.indexes:
raise IOError(errno.ENOENT, 'the requested file {0} does not exist in the given Ren\'Py archive'.format(filename)) raise IOError(errno.ENOENT, 'the requested file {0} does not exist in the given Ren\'Py archive'.format(filename))
# If it's in our opened archive index, and our archive handle isn't valid, something is obviously wrong # If it's in our opened archive index, and our archive handle isn't valid, something is obviously wrong.
if not filename in self.files and filename in self.indexes and self.handle == None: if not filename in self.files and filename in self.indexes and self.handle is None:
raise IOError(errno.ENOENT, 'the requested file {0} does not exist in the given Ren\'Py archive'.format(filename)) raise IOError(errno.ENOENT, 'the requested file {0} does not exist in the given Ren\'Py archive'.format(filename))
# Check our simplified internal indexes first, in case someone wants to read a file they added before without saving, for some unholy reason. # Check our simplified internal indexes first, in case someone wants to read a file they added before without saving, for some unholy reason.
@ -138,7 +140,7 @@ class RenPyArchive:
# Modify a file in archive or internal storage. # Modify a file in archive or internal storage.
def change(self, filename, contents): def change(self, filename, contents):
# Our 'change' is basically removing the file from our indexes first, and then re-add it. # Our 'change' is basically removing the file from our indexes first, and then re-adding it.
self.remove(filename) self.remove(filename)
self.add(filename, contents) self.add(filename, contents)
@ -165,7 +167,7 @@ class RenPyArchive:
# Load archive. # Load archive.
def load(self, filename): def load(self, filename):
if self.handle != None: if self.handle is not None:
self.handle.close() self.handle.close()
self.file = filename self.file = filename
self.files = {} self.files = {}
@ -175,20 +177,20 @@ class RenPyArchive:
# Save current state into a new file, merging archive and internal storage, rebuilding indexes, and optionally saving in another format version. # Save current state into a new file, merging archive and internal storage, rebuilding indexes, and optionally saving in another format version.
def save(self, filename = None): def save(self, filename = None):
if filename == None: if filename is None:
filename = self.file filename = self.file
if filename == None: if filename is None:
raise ValueError('no target file found for saving archive') raise ValueError('no target file found for saving archive')
if self.version != 2 and self.version != 3: if self.version != 2 and self.version != 3:
raise ValueError('saving is only supported for version 2 and 3 archives') raise ValueError('saving is only supported for version 2 and 3 archives')
self.verbose_print('Rebuilding archive index...') self.verbose_print('Rebuilding archive index...')
# Fill our own files structure with the files added or changed in this session # Fill our own files structure with the files added or changed in this session.
files = self.files files = self.files
# First, read files from the current archive into our files structure # First, read files from the current archive into our files structure.
for file in self.indexes.keys(): for file in self.indexes.keys():
content = self.read(file) content = self.read(file)
# Remove from indexes array once read, add to our own array # Remove from indexes array once read, add to our own array.
del self.indexes[file] del self.indexes[file]
files[file] = content files[file] = content
@ -232,7 +234,7 @@ class RenPyArchive:
# We're done, close it. # We're done, close it.
archive.close() archive.close()
# Reload the file in our inner database # Reload the file in our inner database.
self.load(filename) self.load(filename)
if __name__ == "__main__": if __name__ == "__main__":
@ -264,20 +266,20 @@ if __name__ == "__main__":
parser.add_argument('-V', '--version', action='version', version='rpatool v0.8', help='Show version information.') parser.add_argument('-V', '--version', action='version', version='rpatool v0.8', help='Show version information.')
arguments = parser.parse_args() arguments = parser.parse_args()
# Determine RPA version # Determine RPA version.
if arguments.two: if arguments.two:
version = 2 version = 2
else: else:
version = 3 version = 3
# Determine RPAv3 key # Determine RPAv3 key.
if 'key' in arguments and arguments.key != None: if 'key' in arguments and arguments.key is not None:
key = int(arguments.key, 16) key = int(arguments.key, 16)
else: else:
key = 0xDEADBEEF key = 0xDEADBEEF
# Determine padding bytes # Determine padding bytes.
if 'padding' in arguments and arguments.padding != None: if 'padding' in arguments and arguments.padding is not None:
padding = int(arguments.padding) padding = int(arguments.padding)
else: else:
padding = 0 padding = 0
@ -288,7 +290,7 @@ if __name__ == "__main__":
output = arguments.archive output = arguments.archive
else: else:
archive = arguments.archive archive = arguments.archive
if 'outfile' in arguments and arguments.outfile != None: if 'outfile' in arguments and arguments.outfile is not None:
output = arguments.outfile output = arguments.outfile
else: else:
# Default output directory for extraction is the current directory. # Default output directory for extraction is the current directory.
@ -298,7 +300,7 @@ if __name__ == "__main__":
output = arguments.archive output = arguments.archive
# Normalize files. # Normalize files.
if len(arguments.files) > 0 and type(arguments.files[0]) == list: if len(arguments.files) > 0 and isinstance(arguments.files[0], list):
arguments.files = arguments.files[0] arguments.files = arguments.files[0]
try: try: