lattyware / unrpa Goto Github PK
View Code? Open in Web Editor NEWA program to extract files from the RPA archive format.
Home Page: http://www.lattyware.co.uk/projects/unrpa/
License: GNU General Public License v3.0
A program to extract files from the RPA archive format.
Home Page: http://www.lattyware.co.uk/projects/unrpa/
License: GNU General Public License v3.0
Traceback (most recent call last):
File "unrpa", line 158, in extract_files
with open(os.path.join(self.path, path), "wb") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'C:\Users\ajddavid452\Documents\unrpa-1.5.3\output\'
btw I am running on 1.6, this happened on 1.5.3 and than I updated to 1.6 and it still happened
G:\torrent-games\HitomisSickPleasure-0.39-pc\HitomisSickPleasure-0.39-pc\game>rpaExtract.exe archive.rpa
rpaExtract v2, using unrpa by Lattyware licensed under GPLv3.0 see readme for details and code
rpaExtract extracting to
archive.rpa STARTING...
something weird happened, here is the error (hopefully):
Auto-detection of the version for this archived failed—it is likely this archive is a version not supported. Try updating unrpa, or submitting a request for support at https://github.com/Lattyware/unrpa/issues/new?template=new-archive-version.mdHeader: “RPA-9.0 8491b40ae0c3aa11 32ff59cb”
First, try moving all of this to C:\rpaExtract\ or a similarly simple path and try there again
If that still does not work, tell iwanPlays what the error was and what game you were trying to extract
Press any key to continue . . .
G:\torrent-games\HitomisSickPleasure-0.39-pc\HitomisSickPleasure-0.39-pc\game>
link to game on f95zone:
https://f95zone.to/threads/hitomis-sick-pleasure-v0-39-pantsudelver.139487/
Please copy and paste or screenshot the complete output from unrpa if it gave any.
Where it is legal and possible to do so, please:
renpy/loader.py
.Hello Lattyware
I have a game I want to unpack, but the format seems to be custom
This game Renpy's version should be 7.3.5.606
In loader.py I know what code it
As seen in the picture(I have to obliterate the code name,the full name has been sent to gmail),Although it is very similar to the encryption method of RPA-3.0, the red circle of OFFSET is different
“��R P A - 3 . 0 0 0 0 0 0 0 0 0 0 0 1 2 1 8 1 2 d e a d b e e f ”
unrpa 1.5 (latest release)
Python 3.6.3 (latest release)
I always got this error when I run the command:
AttributeError: 'str' object has no attribute 'decode'
So I think it may caused by this line:
item_path = item.decode("utf-8")
I'm unfamiliar with Python so I do a search and found that there's no need to decode str object in Python 3 and it's already unicode object.
After I change this line:
item_path = item.decode("utf-8")
To this:
item_path = item
It worked without error messages anymore.
I receive the following output
LookupError: 'zlib' is not a text encoding; use codecs.decode() to handle arbitrary codecs
Using unrpa 2.0.1 (latest from pip) receiving the following:
Auto-detection of the version for this archived failed—it is likely this archive is a version not supported. Try updating unrpa, or submitting a bug report. Header: “RPA-3.2 000000000058accd F 42424240”
It fails to install for me on debian current stable (bullseye)
$py -3 -m pip install "unrpa"
/usr/bin/py:16: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
from collections import Iterable
usage: py [-x] [-l] [-c PRE_CMD] [-C POST_CMD] [-V] [-h] [expression]
py: error: unrecognized arguments: -m pip install unrpa
with pythonpy 0.4.11b-3
UnRPA is unable to unpack old Renpy archives with rpi extension (RPA-1.0 possibly)
Konsole output:
$ python3.7 -m unrpa /home/olli/Development/rpy/tstrpa/old/images.rpi
Extracting files from /home/olli/Development/rpy/tstrpa/old/images.rpi.
There was an error while trying to extract a file from the archive.
If you wish to try and extract as much from the archive as possible, please use --continue-on-error.
Error Detail: Traceback (most recent call last):
File "/home/olli/.local/lib/python3.7/site-packages/unrpa/__init__.py", line 134, in extract_files
version.postprocess(file_view, output_file)
File "/home/olli/.local/lib/python3.7/site-packages/unrpa/versions/version.py", line 24, in postprocess
for segment in iter(source.read1, b""):
File "/home/olli/.local/lib/python3.7/site-packages/unrpa/view.py", line 20, in read1
return self.base_read(lambda source: source.read1, amount)
File "/home/olli/.local/lib/python3.7/site-packages/unrpa/view.py", line 34, in base_read
return self.base_read(method, amount)
File "/home/olli/.local/lib/python3.7/site-packages/unrpa/view.py", line 37, in base_read
raise Exception("End of archive reached before the file should end.")
Exception: End of archive reached before the file should end.
Steps to reproduce the behaviour:
Alternate trys with a random location brings same outcome. e.g.
Successful extraction of archive content.
Can be acquired here: Games|Renpy
Good examples from this source would be "Wings" or "Dual Hearts". (DL < 10 MiB)
There are two Archive files for this RPA version: 'images.rpa' and 'images.rpi'
*rpi holds imo the index values and *.rpa the file data. It looks for me like UnRPA expects the data content in the rpi file.
When I'm trying to install it with trizen
from the AUR it says, that the public key is unknown.
Newer archives (Ren'Py 8) fail to extract when using Python 3.7, because the built-in pickle library does not support the pickle 5 format.
Unrpa crashes with the message
File "unrpa\__main__.py", line 196, in <module>
File "unrpa\__main__.py", line 189, in main
File "unrpa\__init__.py", line 123, in extract_files
File "unrpa\__init__.py", line 217, in get_index
ValueError: unsupported pickle protocol: 5
There are two options to address this issue: either require Python 3.8.3+ or - for better compatibility - use the pickle5 library to make the newer format available on older versions of python. (Preferably as a dynamic import, so python 3.8 and newer don't require an additional dependency.)
I recently downloaded this script, along with Python 3.7.0. Yet I have no idea what I'm doing; I keep trying to open unrpa
, but it keeps giving me an invalid syntax error. Without an instruction manual or something, I'm driving completely blind.
Ran into this particular archive version today. Header looks as follows:
RPA-4.0 0000000027d75111 00000042
Made with Ren'Py.MY_HEADER
Followed by 36 bytes which appear to be 9 32-bit integers with low values (1,45,417,2,462,417,0,0,0).
Looks like key = parts[1] and offset = parts[2] but offset is wrong (compressed data starts at offset 96, after those 9 integers).
Making it parse the header was easy enough but there seems to be some pickle-related issues.
First pickle.loads() complained about being unable to find module renpy. So I added sys.path.append(".") and all was good.
Then pickle.loads() complained about being unable to load module cPickle. Okay... I tried adding a blank module manually to sys.modules[] but no dice.
My python isn't that great... if someone could add support for this new version I would appreciate it.
when I used it to unrap a file, CMD said "unrpa: error: file doesn't look like an archive, if you are sure it is, use -f.". But I don't know how to use '-f' to solve this problem. What should I do?
RPA-3.1 200000000002d5a062 2042424242
Using Python 3.6.5, unrpa 1.5.2, and Windows 10.
When running unrpa with a specific archive, I get the following error: "TypeError: must be str, not bytes."
Using the --continue-on-error flag resulted in nothing being retrieved from the archive besides an empty folder.
Full traceback is below. Thanks for the help.
Traceback (most recent call last):
File "unrpa", line 61, in extract_files
raw_file = self.extract_file(path, data, file_number, total_files)
File "unrpa", line 85, in extract_file
raw_file = start + f.read(dlen - len(start))
TypeError: must be str, not bytes:
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "unrpa", line 187, in
extractor.extract_files()
File "unrpa", line 72, in extract_files
"use the --continue-on-error flag.") from e
Exception: There was an error while trying to extract a file. See the nested exception for more. If you wish to try and extract as much from the archive as possible, please use the --continue-on-error flag.
Hey Latty ! You key is outdated since 2020-08-07, make new one or renew current, also you may use some other way to validate AUR package, since it requires manual key download.
unrpa images.rpa
Extracting files from images.rpa.
Auto-detection of the version for this archived failed—it is likely this archive is a version not supported. Try updating unrpa, or submitting a request for support at https://github.com/Lattyware/unrpa/issues/new?template=new-archive-version.mdHeader: “��~�E�;����R”
You can try using --force to force a specific version rather than relying on auto-detection.
unrpa images.rpi
Extracting files from images.rpi.
There was an error while trying to extract a file from the archive.
If you wish to try and extract as much from the archive as possible, please use --continue-on-error.
Error Detail: Traceback (most recent call last):
File "D:\BuildEnv\python\lib\site-packages\unrpa\__init__.py", line 134, in extract_files
version.postprocess(file_view, output_file)
File "D:\BuildEnv\python\lib\site-packages\unrpa\versions\version.py", line 24, in postprocess
for segment in iter(source.read1, b""):
File "D:\BuildEnv\python\lib\site-packages\unrpa\view.py", line 20, in read1
return self.base_read(lambda source: source.read1, amount)
File "D:\BuildEnv\python\lib\site-packages\unrpa\view.py", line 34, in base_read
return self.base_read(method, amount)
File "D:\BuildEnv\python\lib\site-packages\unrpa\view.py", line 37, in base_read
raise Exception("End of archive reached before the file should end.")
Exception: End of archive reached before the file should end.
images.rpa
images.rpi
loader.py
Tengo un problema, y es que aunque consiga copilar archivos en .rpa o .rpy, cuando los abro no hay nada dentro, estan vacios, alguien puede ayudarme?
I have a problem, and it is that although I manage to compile files in .rpa or .rpy, when I open them there is nothing inside, they are empty, can someone help me?
Here is the header for a Ren'py game. Thanks
I type unrpa -v -p /media/500GB/RPA_EXTRACTION /path/to/the/file.rpa
press enter and it reads that files have been extracted along with their path inside the rpa file but the output directory is empty. Where did the files go? They're not in the dir where the rpa file is either, I checked it several times.
In windows 10, Python 3.7
I installed unrpa using PIP. It looks like version 2.1.0 is installed.
C:\Users\User1>pip install "unrpa"
Requirement already satisfied: unrpa in c:\users\lorenpearson\appdata\local\programs\python\python37\lib\site-packages (2.1.0)
Then I checked version
C:\Users\User1>unrpa --version
unrpa 2.0.1
Why isn't the version 2.1.0?
RPA-3.1 00000000002d5a062 042424242
Looks like a custom format
Renpy's version should be 7.0.1.123
It keep saying "SyntaxError: unexpected character after line continuation character" for no reason, someone help? (also I am noob)
Homebrew is probably the most used package manager on macOS. Would be great to get unrpa added:
It appears some Ren'Py games are starting to ship with custom loader scripts for a non-standard variant of RPA archives. unrpa
currently can't deal with these archives.
The archives that do this seen in the wild seem to be identifiable as they begin with a ZiX-12B
header, not the expected RPA-3.0
/RPA-2.0
. This appears to be an in-house obfuscation technique.
The route to decoding these files is to use uncompyle6
to turn 'loader.pyo' from the game the archive comes from into readable code. This should allow you to modify unrpa
to load the archive. It appears to use a compiled cython module called _string
to perform parts of the process.
It appears the system is to use a hard-coded hey in the loader. Ideally, we could identify this type of archive by the header and offer additional tooling to extract that key, alongside an option to manually set the key as an argument.
(This is the root cause of #13).
Edit: There is a script to make extracting these possible, but proper support isn't here yet. See below for details on how to extract an archive of this type now.
Edit: For transparency, I will note I worked out who the developer was who created this technique, and had their name listed here previously. At their request, I have removed a direct reference to them from this post, as it's not really relevant. The partial support for the format and the documentation of the effort here will remain up, however. I am still happy to accept pull requests to solve this issue properly and add full support to unrpa.
rpaExtract v3, using unrpa by Lattyware licensed under GPLv3.0 see readme for details and code
rpaExtract extracting to C:\Users\playg\OneDrive\Escritorio\a
archive.rpa STARTING...
something weird happened, here is the error (hopefully):
Auto-detection of the version for this archived failed—it is likely this archive is a version not supported. Try updating unrpa, or submitting a request for support at https://github.com/Lattyware/unrpa/issues/new?template=new-archive-version.mdHeader: “RPA-9.1 12acac407b5c514d”
Forcing RPA-3.0
rpaExtract extracting to C:\Users***\OneDrive\Escritorio\a
archive.rpa STARTING...
something weird happened, here is the error (hopefully):
list index out of range
First, try moving all of this to C:\rpaExtract\ or a similarly simple path and try there again
If that still does not work, tell iwanPlays what the error was and what game you were trying to extract
Presione una tecla para continuar . . .
I ran unrpa archive.rpa
and received:
Auto-detection of the version for this archived failed—it is likely this archive is a version not supported. Try updating unrpa, or submitting a request for support at https://github.com/Lattyware/unrpa/issues/new?template=new-archive-version.mdHeader: “R4598P5757A 000000009ee33010 42424242” You can try using --force to force a specific version rather than relying on auto-detection.
Trying to use the force option, I ran unrpa --force archive.rpa
and I received:
usage: unrpa [-h] [-v] [-s] [-l | -t] [-p PATH] [-m] [--version] [--continue-on-error] [-f VERSION] [-o OFFSET] [-k KEY]
FILENAME [FILENAME ...]
unrpa: error: the following arguments are required: FILENAME
the code unrpa -mp have an error that output folder is change to output file make to error is a directory
Steps to reproduce the behaviour:
A clear and concise description of what you expected to happen.
Where it is legal and possible to do so, please:
renpy/loader.py
.If applicable, add screenshots to help explain your problem, or examples of incorrectly extracted files.
python --version
to see]unrpa --version
to see]Add any other context about the problem here.
The optparse is deprecated 8 years ago. Please migrate the use of optparse to argparse.
https://www.python.org/dev/peps/pep-0389/
Actually my reason is that I am packaging unrpa to Debian package, which requires manpage for your script. I am using sphinx to generate the documents from the binary, with sphinx-argparse, but found out you are using optparse which is not compatible to it.
It seems, this script is not fully supports "python 3.x" . I've got this error in Python 3.3.7
File "f:\unrpa.py", line 207, in get_index
index = pickle.loads(zlib.decompress(f.read()), encoding="bytes")
LookupError: unknown encoding: bytes
The reason is simple: python 3.3 doesn't support encoding="bytes" , only encoding="ASCII" So this script requires python 3.4 or later.
Extracting files from C:\Temp\DSCS-0.1.1-win\game\dscs.rpa.
[0.00%] modules\0005_core\keymap.rpyc
There was an error while trying to extract a file from the archive.
If you wish to try and extract as much from the archive as possible, please use --continue-on-error.
Error Detail: Traceback (most recent call last):
File "C:\Users\omega\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\LocalCache\local-packages\Python37\site-packages\unrpa_init_.py", line 134, in extract_files
version.postprocess(file_view, output_file)
File "C:\Users\omega\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\LocalCache\local-packages\Python37\site-packages\unrpa\versions\version.py", line 24, in postprocess
for segment in iter(source.read1, b""):
File "C:\Users\omega\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\LocalCache\local-packages\Python37\site-packages\unrpa\view.py", line 20, in read1
return self.base_read(lambda source: source.read1, amount)
File "C:\Users\omega\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\LocalCache\local-packages\Python37\site-packages\unrpa\view.py", line 34, in base_read
return self.base_read(method, amount)
File "C:\Users\omega\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\LocalCache\local-packages\Python37\site-packages\unrpa\view.py", line 37, in base_read
raise Exception("End of archive reached before the file should end.")
Exception: End of archive reached before the file should end.
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
from renpy.compat import *
import renpy, os.path, sys, types, threading, zlib, re, io, unicodedata
from renpy.compat.pickle import loads
from renpy.webloader import DownloadNeeded
(b'').encode(b'utf-8')
def get_path(fn):
fn = os.path.join(renpy.config.gamedir, fn)
dn = os.path.dirname(fn)
try:
if not os.path.exists(dn):
os.makedirs(dn)
except:
pass
return fn
if renpy.android:
import android.apk
expansion = os.environ.get(b'ANDROID_EXPANSION', None)
if expansion is not None:
print(b'Using expansion file', expansion)
apks = [
android.apk.APK(apk=expansion, prefix=b'assets/x-game/'),
android.apk.APK(apk=expansion, prefix=b'assets/x-renpy/x-common/')]
game_apks = [
apks[0]]
else:
print(b'Not using expansion file.')
apks = [
android.apk.APK(prefix=b'assets/x-game/'),
android.apk.APK(prefix=b'assets/x-renpy/x-common/')]
game_apks = [
apks[0]]
else:
apks = []
game_apks = []
archives = []
old_config_archives = None
lower_map = {}
archive_handlers = []
class RPAv3ArchiveHandler(object):
@staticmethod
def get_supported_extensions():
return [b'.rpa']
@staticmethod
def get_supported_headers():
return [b'RPA-3.0 ']
@staticmethod
def read_index(infile):
l = infile.read(40)
offset = int(l[8:24], 16)
key = int(l[25:33], 16)
infile.seek(offset)
index = loads(zlib.decompress(infile.read()))
for k in index.keys():
if len(index[k][0]) == 2:
index[k] = [ (offset ^ key ^ 3735929054, dlen ^ key ^ 3735929054) for offset, dlen in index[k] ]
else:
index[k] = [ (offset ^ key ^ 3735929054, dlen ^ key ^ 3735929054, start) for offset, dlen, start in index[k] ]
return index
archive_handlers.append(RPAv3ArchiveHandler)
class RPAv2ArchiveHandler(object):
@staticmethod
def get_supported_extensions():
return [b'.rpa']
@staticmethod
def get_supported_headers():
return [b'RPA-2.0 ']
@staticmethod
def read_index(infile):
l = infile.read(24)
offset = int(l[8:], 16)
infile.seek(offset)
index = loads(zlib.decompress(infile.read()))
return index
archive_handlers.append(RPAv2ArchiveHandler)
class RPAv1ArchiveHandler(object):
@staticmethod
def get_supported_extensions():
return [b'.rpi']
@staticmethod
def get_supported_headers():
return [b'x\x9c']
@staticmethod
def read_index(infile):
return loads(zlib.decompress(infile.read()))
archive_handlers.append(RPAv1ArchiveHandler)
def index_archives():
global archives
global old_config_archives
if old_config_archives == renpy.config.archives:
return
else:
old_config_archives = renpy.config.archives[:]
lower_map.clear()
cleardirfiles()
archives = []
max_header_length = 0
for handler in archive_handlers:
for header in handler.get_supported_headers():
header_len = len(header)
if header_len > max_header_length:
max_header_length = header_len
archive_extensions = []
for handler in archive_handlers:
for ext in handler.get_supported_extensions():
if ext not in archive_extensions:
archive_extensions.append(ext)
for prefix in renpy.config.archives:
for ext in archive_extensions:
fn = None
f = None
try:
fn = transfn(prefix + ext)
f = open(fn, b'rb')
except:
continue
with f:
file_header = f.read(max_header_length)
for handler in archive_handlers:
try:
archive_handled = False
for header in handler.get_supported_headers():
if file_header.startswith(header):
f.seek(0, 0)
index = handler.read_index(f)
archives.append((prefix + ext, index))
archive_handled = True
break
if archive_handled == True:
break
except:
raise
for dir, fn in listdirfiles():
lower_map[unicodedata.normalize(b'NFC', fn.lower())] = fn
for fn in remote_files:
lower_map[unicodedata.normalize(b'NFC', fn.lower())] = fn
return
def walkdir(dir):
rv = []
if not os.path.exists(dir) and not renpy.config.developer:
return rv
for i in os.listdir(dir):
if i[0] == b'.':
continue
try:
i = renpy.exports.fsdecode(i)
except:
continue
if os.path.isdir(dir + b'/' + i):
for fn in walkdir(dir + b'/' + i):
rv.append(i + b'/' + fn)
else:
rv.append(i)
return rv
game_files = []
common_files = []
loadable_cache = {}
remote_files = {}
def cleardirfiles():
global common_files
global game_files
game_files = []
common_files = []
scandirfiles_callbacks = []
def scandirfiles():
seen = set()
def add(dn, fn, files, seen):
fn = unicode(fn)
if fn in seen:
return
if fn.startswith(b'cache/'):
return
if fn.startswith(b'saves/'):
return
files.append((dn, fn))
seen.add(fn)
loadable_cache[unicodedata.normalize(b'NFC', fn.lower())] = True
for i in scandirfiles_callbacks:
i(add, seen)
def scandirfiles_from_apk(add, seen):
for apk in apks:
if apk not in game_apks:
files = common_files
else:
files = game_files
for f in apk.list():
f = (b'/').join(i[2:] for i in f.split(b'/'))
add(None, f, files, seen)
return
if renpy.android:
scandirfiles_callbacks.append(scandirfiles_from_apk)
def scandirfiles_from_remote_file(add, seen):
index_filename = os.path.join(renpy.config.gamedir, b'renpyweb_remote_files.txt')
if os.path.exists(index_filename):
files = game_files
with open(index_filename, b'rb') as (remote_index):
while True:
f = remote_index.readline()
metadata = remote_index.readline()
if f == b'' or metadata == b'':
break
f = f.rstrip(b'\r\n')
metadata = metadata.rstrip(b'\r\n')
entry_type, entry_size = metadata.split(b' ')
if entry_type == b'image':
entry_size = [ int(i) for i in entry_size.split(b',') ]
add(b'/game', f, files, seen)
remote_files[f] = {b'type': entry_type, b'size': entry_size}
if renpy.emscripten or os.environ.get(b'RENPY_SIMULATE_DOWNLOAD', False):
scandirfiles_callbacks.append(scandirfiles_from_remote_file)
def scandirfiles_from_filesystem(add, seen):
for i in renpy.config.searchpath:
if renpy.config.commondir and i == renpy.config.commondir:
files = common_files
else:
files = game_files
i = os.path.join(renpy.config.basedir, i)
for j in walkdir(i):
add(i, j, files, seen)
scandirfiles_callbacks.append(scandirfiles_from_filesystem)
def scandirfiles_from_archives(add, seen):
files = game_files
for _prefix, index in archives:
for j in index:
add(None, j, files, seen)
return
scandirfiles_callbacks.append(scandirfiles_from_archives)
def listdirfiles(common=True):
if not game_files and not common_files:
scandirfiles()
if common:
return game_files + common_files
else:
return list(game_files)
class SubFile(object):
def __init__(self, fn, base, length, start):
self.fn = fn
self.f = None
self.base = base
self.offset = 0
self.length = length
self.start = start
if not self.start:
self.name = fn
else:
self.name = None
return
def open(self):
self.f = open(self.fn, b'rb')
self.f.seek(self.base)
def __enter__(self):
return self
def __exit__(self, _type, value, tb):
self.close()
return False
def read(self, length=None):
if self.f is None:
self.open()
maxlength = self.length - self.offset
if length is not None:
length = min(length, maxlength)
else:
length = maxlength
rv1 = self.start[self.offset:self.offset + length]
length -= len(rv1)
self.offset += len(rv1)
if length:
rv2 = self.f.read(length)
self.offset += len(rv2)
else:
rv2 = b''
return rv1 + rv2
def readline(self, length=None):
if self.f is None:
self.open()
maxlength = self.length - self.offset
if length is not None:
length = min(length, maxlength)
else:
length = maxlength
if self.offset < len(self.start):
rv = b''
while length:
c = self.read(1)
rv += c
if c == b'\n':
break
length -= 1
return rv
rv = self.f.readline(length)
self.offset += len(rv)
return rv
def readlines(self, length=None):
rv = []
while True:
l = self.readline(length)
if not l:
break
if length is not None:
length -= len(l)
if l < 0:
break
rv.append(l)
return rv
def xreadlines(self):
return self
def __iter__(self):
return self
def __next__(self):
rv = self.readline()
if not rv:
raise StopIteration()
return rv
next = __next__
def flush(self):
pass
def seek(self, offset, whence=0):
if self.f is None:
self.open()
if whence == 0:
offset = offset
elif whence == 1:
offset = self.offset + offset
elif whence == 2:
offset = self.length + offset
if offset > self.length:
offset = self.length
self.offset = offset
offset = offset - len(self.start)
if offset < 0:
offset = 0
self.f.seek(offset + self.base)
return
def tell(self):
return self.offset
def close(self):
if self.f is not None:
self.f.close()
self.f = None
return
def write(self, s):
raise Exception(b'Write not supported by SubFile')
open_file = open
if b'RENPY_FORCE_SUBFILE' in os.environ:
def open_file(name, mode):
f = open(name, mode)
f.seek(0, 2)
length = f.tell()
f.seek(0, 0)
return SubFile(f, 0, length, b'')
file_open_callbacks = []
def load_core(name):
name = lower_map.get(unicodedata.normalize(b'NFC', name.lower()), name)
for i in file_open_callbacks:
rv = i(name)
if rv is not None:
return rv
return
def load_from_file_open_callback(name):
if renpy.config.file_open_callback:
return renpy.config.file_open_callback(name)
else:
return
file_open_callbacks.append(load_from_file_open_callback)
def load_from_filesystem(name):
if not renpy.config.force_archives:
try:
fn = transfn(name)
return open_file(fn, b'rb')
except:
pass
return
file_open_callbacks.append(load_from_filesystem)
def load_from_apk(name):
for apk in apks:
prefixed_name = (b'/').join(b'x-' + i for i in name.split(b'/'))
try:
return apk.open(prefixed_name)
except IOError:
pass
return
if renpy.android:
file_open_callbacks.append(load_from_apk)
def load_from_archive(name):
for prefix, index in archives:
if name not in index:
continue
afn = transfn(prefix)
data = []
if len(index[name]) == 1:
t = index[name][0]
if len(t) == 2:
offset, dlen = t
start = b''
else:
offset, dlen, start = t
rv = SubFile(afn, offset, dlen, start)
else:
with open(afn, b'rb') as (f):
for offset, dlen in index[name]:
f.seek(offset)
data.append(f.read(dlen))
rv = io.BytesIO((b'').join(data))
return rv
return
file_open_callbacks.append(load_from_archive)
def load_from_remote_file(name):
if name in remote_files:
raise DownloadNeeded(relpath=name, rtype=remote_files[name][b'type'], size=remote_files[name][b'size'])
return
if renpy.emscripten or os.environ.get(b'RENPY_SIMULATE_DOWNLOAD', False):
file_open_callbacks.append(load_from_remote_file)
def check_name(name):
if renpy.config.reject_backslash and b'\\' in name:
raise Exception(b"Backslash in filename, use '/' instead: %r" % name)
if renpy.config.reject_relative:
split = name.split(b'/')
if b'.' in split or b'..' in split:
raise Exception(b"Filenames may not contain relative directories like '.' and '..': %r" % name)
def get_prefixes(tl=True):
rv = []
if tl:
language = renpy.game.preferences.language
else:
language = None
for prefix in renpy.config.search_prefixes:
if language is not None:
rv.append(renpy.config.tl_directory + b'/' + language + b'/' + prefix)
rv.append(prefix)
return rv
def load(name, tl=True):
if renpy.display.predict.predicting:
if threading.current_thread().name == b'MainThread':
if not (renpy.emscripten or os.environ.get(b'RENPY_SIMULATE_DOWNLOAD', False)):
raise Exception((b'Refusing to open {} while predicting.').format(name))
if renpy.config.reject_backslash and b'\\' in name:
raise Exception(b"Backslash in filename, use '/' instead: %r" % name)
name = re.sub(b'/+', b'/', name).lstrip(b'/')
for p in get_prefixes(tl):
rv = load_core(p + name)
if rv is not None:
return rv
raise IOError(b"Couldn't find file '%s'." % name)
return
def loadable_core(name):
name = lower_map.get(unicodedata.normalize(b'NFC', name.lower()), name)
if name in loadable_cache:
return loadable_cache[name]
try:
transfn(name)
loadable_cache[name] = True
return True
except:
pass
for apk in apks:
prefixed_name = (b'/').join(b'x-' + i for i in name.split(b'/'))
if prefixed_name in apk.info:
loadable_cache[name] = True
return True
for _prefix, index in archives:
if name in index:
loadable_cache[name] = True
return True
if name in remote_files:
loadable_cache[name] = True
return name
loadable_cache[name] = False
return False
def loadable(name):
name = name.lstrip(b'/')
if renpy.config.loadable_callback is not None and renpy.config.loadable_callback(name):
return True
else:
for p in get_prefixes():
if loadable_core(p + name):
return True
return False
def transfn(name):
name = name.lstrip(b'/')
if renpy.config.reject_backslash and b'\\' in name:
raise Exception(b"Backslash in filename, use '/' instead: %r" % name)
name = lower_map.get(unicodedata.normalize(b'NFC', name.lower()), name)
if isinstance(name, bytes):
name = name.decode(b'utf-8')
for d in renpy.config.searchpath:
fn = os.path.join(renpy.config.basedir, d, name)
add_auto(fn)
if os.path.isfile(fn):
return fn
raise Exception(b"Couldn't find file '%s'." % name)
hash_cache = dict()
def get_hash(name):
rv = hash_cache.get(name, None)
if rv is not None:
return rv
else:
rv = 0
try:
f = load(name)
while True:
data = f.read(1048576)
if not data:
break
rv = zlib.adler32(data, rv)
except:
pass
hash_cache[name] = rv
return rv
class RenpyImporter(object):
def __init__(self, prefix=b''):
self.prefix = prefix
def translate(self, fullname, prefix=None):
if prefix is None:
prefix = self.prefix
try:
if not isinstance(fullname, str):
fullname = fullname.decode(b'utf-8')
fn = prefix + fullname.replace(b'.', b'/')
except:
return
if loadable(fn + b'.py'):
return fn + b'.py'
else:
if loadable(fn + b'/__init__.py'):
return fn + b'/__init__.py'
return
def find_module(self, fullname, path=None):
if path is not None:
for i in path:
if self.translate(fullname, i):
return RenpyImporter(i)
if self.translate(fullname):
return self
else:
return
def load_module(self, fullname):
filename = self.translate(fullname, self.prefix)
pyname = pystr(fullname)
mod = sys.modules.setdefault(pyname, types.ModuleType(pyname))
mod.__name__ = pyname
mod.__file__ = filename
mod.__loader__ = self
if filename.endswith(b'__init__.py'):
mod.__path__ = [
filename[:-len(b'__init__.py')]]
for encoding in [b'utf-8', b'latin-1']:
try:
source = load(filename).read().decode(encoding)
if source and source[0] == b'\ufeff':
source = source[1:]
source = source.encode(b'raw_unicode_escape')
source = source.replace(b'\r', b'')
code = compile(source, filename, b'exec', renpy.python.old_compile_flags, 1)
break
except:
if encoding == b'latin-1':
raise
exec code in mod.__dict__
return sys.modules[fullname]
def get_data(self, filename):
return load(filename).read()
meta_backup = []
def add_python_directory(path):
if path and not path.endswith(b'/'):
path = path + b'/'
sys.meta_path.insert(0, RenpyImporter(path))
def init_importer():
meta_backup[:] = sys.meta_path
add_python_directory(b'python-packages/')
add_python_directory(b'')
def quit_importer():
sys.meta_path[:] = meta_backup
needs_autoreload = set()
auto_mtimes = {}
auto_thread = None
auto_quit_flag = True
auto_lock = threading.Condition()
auto_blacklisted = renpy.object.Sentinel(b'auto_blacklisted')
def auto_mtime(fn):
try:
return os.path.getmtime(fn)
except:
return
return
def add_auto(fn, force=False):
fn = fn.replace(b'\\', b'/')
if not renpy.autoreload:
return
if fn in auto_mtimes and not force:
return
for e in renpy.config.autoreload_blacklist:
if fn.endswith(e):
with auto_lock:
auto_mtimes[fn] = auto_blacklisted
return
mtime = auto_mtime(fn)
with auto_lock:
auto_mtimes[fn] = mtime
def auto_thread_function():
global auto_quit_flag
global needs_autoreload
while True:
with auto_lock:
auto_lock.wait(1.5)
if auto_quit_flag:
return
items = list(auto_mtimes.items())
for fn, mtime in items:
if mtime is auto_blacklisted:
continue
if auto_mtime(fn) != mtime:
with auto_lock:
if auto_mtime(fn) != auto_mtimes[fn]:
needs_autoreload.add(fn)
def check_autoreload():
while needs_autoreload:
fn = next(iter(needs_autoreload))
mtime = auto_mtime(fn)
with auto_lock:
needs_autoreload.discard(fn)
auto_mtimes[fn] = mtime
if not renpy.autoreload:
return
for regex, func in renpy.config.autoreload_functions:
if re.search(regex, fn, re.I):
fn = os.path.relpath(fn, renpy.config.gamedir).replace(b'\\', b'/')
func(fn)
break
else:
renpy.exports.reload_script()
def auto_init():
global auto_quit_flag
global auto_thread
global needs_autoreload
needs_autoreload = set()
if not renpy.autoreload:
return
auto_quit_flag = False
auto_thread = threading.Thread(target=auto_thread_function)
auto_thread.daemon = True
auto_thread.start()
def auto_quit():
global auto_quit_flag
if auto_thread is None:
return
else:
auto_quit_flag = True
with auto_lock:
auto_lock.notify_all()
auto_thread.join()
return
It seems a random number is added to the key and offset to determine the index in the archive extractor.
NSFW link to game
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.