Skip to content

Commit

Permalink
feat(unrpa): add unrpa support
Browse files Browse the repository at this point in the history
  • Loading branch information
cnfatal committed Jan 18, 2025
1 parent 44db486 commit 0c0b913
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 17 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ pip install .
Decompile a file:

```sh
rpycdec <path to rpyc file or dir>
rpycdec decompile <path to rpyc file or dir>
```

Extract RPA archive

```sh
rpycdec unrpa <path to rpa file>
```

### Library usage
Expand All @@ -43,6 +49,9 @@ from rpycdec import decompile, translate
# decompile a file
decompile(input_file, output_file)

# extract RPA archive
extract_rpa(file_steam, output_dir)

# decompile and translate a file
translate(input_file, output_file)
```
Expand All @@ -53,7 +62,7 @@ Pull requests are welcome. For major changes, please open an issue first to disc

## FAQ

- **Q: It always raise pickle `import ** \nModuleNotFoundError: No module named '***'` error.**
- **Q: It always raise pickle `import ** \nModuleNotFoundError: No module named '**\*'` error.**

A: It's because the our fake packages("renpy","store") is not contains the object you want to decompile. Please open an issue and tell us the renpy version and the rpyc file you want to decompile. Join our telegram group to get help also be better.

Expand Down
1 change: 1 addition & 0 deletions src/rpycdec/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .decompile import decompile
from .translate import translate
from .cli import main
from .rpa import extract_rpa
38 changes: 35 additions & 3 deletions src/rpycdec/cli.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,55 @@
import argparse
import logging
from rpycdec.decompile import decompile
from rpycdec.rpa import extract_rpa


logger = logging.getLogger(__name__)


def decompile_files(srcs: list[str]):
"""
decompile rpyc file or directory.
"""
for src in srcs:
decompile(src)


def extract_rpa_files(srcs: list[str]):
"""
extract rpa archive.
"""
for src in srcs:
with open(src, "rb") as f:
extract_rpa(f)


def main():
"""
command line tool entry.
"""
logging.basicConfig(level=logging.INFO)

argparser = argparse.ArgumentParser()
argparser.add_argument(
"--verbose", "-v", action="store_true", help="verbose output"
)
argparser.add_argument("src", nargs=1, help="rpyc file or directory")
subparsers = argparser.add_subparsers(
title="subcommands", dest="command", help="subcommand help"
)

decompile_parser = subparsers.add_parser("decompile", help="decompile rpyc file")
decompile_parser.add_argument("src", nargs=1, help="rpyc file or directory")
decompile_parser.set_defaults(func=decompile_files)

unrpa_parser = subparsers.add_parser("unrpa", help="extract rpa archive")
unrpa_parser.add_argument("src", nargs=1, help="rpa archive")
unrpa_parser.set_defaults(func=extract_rpa_files)

args = argparser.parse_args()
if args.verbose:
logger.setLevel(logging.DEBUG)
for src in args.src:
decompile(src)
if not args.command:
argparser.print_help()
return
args.func(args.src)
23 changes: 11 additions & 12 deletions src/rpycdec/rpa.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from io import BufferedIOBase


def read_str(data: BufferedIOBase, eol: int = 0x00) -> bytes:
def read_util(data: BufferedIOBase, util: int = 0x00) -> bytes:
content = bytearray()
while True:
c = data.read(1)
if c[0] == eol:
if c[0] == util:
break
content += c
return content
Expand All @@ -22,16 +22,13 @@ def to_start(start: str | None | bytes) -> bytes:
return start.encode("latin-1")


def extract_rpa(r: BufferedIOBase, dir: str):
header = read_str(r).decode("utf-8")
lines = header.splitlines()
metadata = lines[0].split()
magic, metadata = metadata[0], metadata[1:]
index_offset, metadata = int(metadata[0], 16), metadata[1:]
if magic == "RPA-3.0":
key, metadata = int(metadata[0], 16), metadata[1:0]
else:
raise Exception("magic %s not supported" % magic)
def extract_rpa(r: BufferedIOBase, dir: str | None = None):
magic = read_util(r, 0x20)
if magic != b"RPA-3.0":
print("Not a Ren'Py archive.")
return
index_offset = int(read_util(r, 0x20), 16)
key = int(read_util(r, 0x0A).decode(), 16)

# read index
r.seek(index_offset)
Expand All @@ -42,6 +39,8 @@ def extract_rpa(r: BufferedIOBase, dir: str):
for offset, dlen, *left in v
]

if not dir:
dir = os.path.splitext(r.name)[0]
for filename, entries in index.items():
data = bytearray()
for offset, dlen, start in entries:
Expand Down

0 comments on commit 0c0b913

Please sign in to comment.