Created
May 12, 2017 06:35
-
-
Save sirtony/6d3e89aaf082113af653da7933b39723 to your computer and use it in GitHub Desktop.
RenPy archive unpacker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from pickle import loads as unpickle | |
from zlib import decompress | |
from argparse import ArgumentParser | |
from sys import exit, stderr | |
import os | |
def _main(): | |
parser = ArgumentParser( description = "RenPy Arhive (.rpa) unpacker" ) | |
parser.add_argument( "-o", "--output", required = False, type = str, dest = "output", metavar = "dir", help = "The directory to output files to" ) | |
parser.add_argument( "-i", "--input", required = True, type = str, dest = "input", metavar = "path", help = "The archive to unpack" ) | |
args = parser.parse_args() | |
args.input = os.path.normpath( args.input ) | |
if args.output is not None: | |
args.output = os.path.normpath( args.output ) | |
else: | |
base = os.path.dirname( args.input ) | |
name, _ = os.path.splitext( os.path.basename( args.input ) ) | |
args.output = os.path.join( base, f'{name}_data' ) | |
if not _file_exists( args.input ): | |
_err( "input file not found" ) | |
return 1 | |
if not _dir_exists( args.output ): | |
os.makedirs( args.output ) | |
with open( args.input, "rb" ) as rpa: | |
unpack = _compose( | |
rpa.read, | |
decompress, | |
unpickle | |
) | |
magic, offset, key = rpa.readline().split() | |
if magic != b"RPA-3.0": | |
_err( "invalid RenPy file or version" ) | |
return 2 | |
offset = int( offset, 16 ) | |
key = int( key, 16 ) | |
rpa.seek( offset ) | |
index = unpack() | |
for name in index.keys(): | |
print( "unpacking {}".format( name ) ) | |
dest = os.path.join( args.output, os.path.normpath( name ) ) | |
dir = os.path.dirname( dest ) | |
if not _dir_exists( dir ): | |
os.makedirs( dir ) | |
offset = index[name][0][0] ^ key | |
size = index[name][0][1] ^ key | |
head = index[name][0][2] | |
rpa.seek( offset ) | |
with open( dest, "wb" ) as f: | |
f.write( bytes( head, 'utf-8' ) ) | |
f.write( rpa.read( size ) ) | |
f.flush() | |
return 0 | |
def _err( *a, **kw ): | |
print( *a, file = stderr, **kw ) | |
def _file_exists( path ): | |
return os.path.exists( path ) and os.path.isfile( path ) | |
def _dir_exists( path ): | |
return os.path.exists( path ) and os.path.isdir( path ) | |
def _compose( *funcs ): | |
def composed( *args ): | |
first = funcs[0] | |
result = first( *args ) | |
for func in funcs[1:]: | |
if result is not None: | |
result = func( result ) | |
else: | |
result = func() | |
return result | |
return composed | |
if __name__ == "__main__": | |
code = _main() | |
exit( code ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment