Code Monkey home page Code Monkey logo

unrpyc's Introduction

Unrpyc, the Ren'py script decompiler.

Unrpyc is a tool to decompile Ren'Py (http://www.renpy.org/) compiled .rpyc script files. It will not extract files from .rpa archives. For that, use rpatool or UnRPA.

Status

master (python 3):Build Status

dev (python 3):Build Status

legacy (python 2):Build Status

legacy-dev (python 2):Build Status

Usage

This tool can either be ran as a command line tool, as a library, or injected into the game itself. To use it as a command line tool, a local python installation is required.

Compatibility

You are currently reading the documentation for the master branch of this tool. Ren'py switched to using Python 3 in Ren'py 8. This required significant changes to the decompiler, and necessitated splitting it into two to maintain support for older games. Development and releases for this tool are now split into the master branch (unrpyc v2, using python 3) and the legacy branch (unrpyc v1, using python 2). Additionally, support for some very ancient ren'py features has been dropped from the master branch to simplify continued development of unrpyc. In practice this means that games before ren'py 6.18.0 are no longer supported by the master branch, and games from before 6.99.10 should use the --no-init-offset option. Any game using ren'py versions before 6.18.0 should instead use the legacy branch of unrpyc, which supports up to and including Ren'py version 7.

When using the injectors (un.rpyc, un.rpy, bytecode.rpyb), compatibility is more stringent, as these tools use the python version bundled by ren'py. Use un.rpyc v2 (2.*.*) for ren'py 8 games, and un.rpyc v1 (1.*.*) for ren'py 7 and 6.

Summarized:

  • unrpyc v2:

    • Requires python 3.9 or above to work.
    • Releases use version numbers 2.*.*
    • Uses branches master for the last release, and dev for development.
    • Command line supports ren'py 8.*.* (most recent) down to 6.18.0 (below 6.99.10 requires --no-init-offset)
    • Injectors (un.rpyc and friends) support only ren'py 8.*.*
  • unrpyc v1:

    • Requires python 2.7 to work.
    • Releases use version numbers 1.*.*
    • Uses branches legacy for the last release, and legacy-dev for development.
    • Command line supports ren'py 7.*.* (most recent) and ren'py 6.*.*.
    • Injectors (un.rpyc and friends) support ren'py 6.*.* and 7.*.*.

Ren'py 5 or earlier are not supported currently.

Command line tool usage

Depending on your system setup, you should use one of the following commands to run the tool:

python unrpyc.py [options] script1 script2 ...
python3 unrpyc.py [options] script1 script2 ...
py -3 unrpyc.py [options] script1 script2 ...
./unrpyc.py [options] script1 script2 ...

Options:

$ py -3 unrpyc.py --help
usage: unrpyc.py [-h] [-c] [-d] [-p {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}]
                 [-t TRANSLATION_FILE] [-T WRITE_TRANSLATION_FILE]
                 [-l LANGUAGE] [--sl1-as-python] [--comparable] [--no-pyexpr]
                 [--no-init-offset] [--try-harder]
                 [--register-sl-displayable SL_CUSTOM_NAMES [SL_CUSTOM_NAMES ...]]
                 file [file ...]

Decompile .rpyc/.rpymc files

positional arguments:
  file                  The filenames to decompile. All .rpyc files in any
                        directories passed or their subdirectories will also
                        be decompiled.

optional arguments:
  -h, --help            show this help message and exit
  -c, --clobber         overwrites existing output files
  -d, --dump            instead of decompiling, pretty print the ast to a file
  -p, --processes
                        use the specified number or processes to
                        decompile. Defaults to the amount of hw threads
                        available minus one, disabled when muliprocessing is
                        unavailable.
  -t TRANSLATION_FILE, --translation-file TRANSLATION_FILE
                        use the specified file to translate during
                        decompilation
  -T WRITE_TRANSLATION_FILE, --write-translation-file WRITE_TRANSLATION_FILE
                        store translations in the specified file instead of
                        decompiling
  -l LANGUAGE, --language LANGUAGE
                        if writing a translation file, the language of the
                        translations to write
  --comparable          Only for dumping, remove several false differences
                        when comparing dumps. This suppresses attributes that
                        are different even when the code is identical, such as
                        file modification times.
  --no-pyexpr           Only for dumping, disable special handling of PyExpr
                        objects, instead printing them as strings. This is
                        useful when comparing dumps from different versions of
                        Ren'Py. It should only be used if necessary, since it
                        will cause loss of information such as line numbers.
  --no-init-offset      By default, unrpyc attempt to guess when init offset
                        statements were used and insert them. This is always safe
                        to do for ren'py 8, but as it is based on a heuristic it
                        can be disabled.
                        The generated code is exactly equivalent, only slightly more cluttered.
  --try-harder          Tries some workarounds against common obfuscation
                        methods. This is a lot slower.
  --register-sl-displayable SL_CUSTOM_NAMES [SL_CUSTOM_NAMES ...]
                        Accepts mapping separated by '=', where the first
                        argument is the name of the user-defined displayable
                        object, and the second argument is a string containing
                        the name of the displayable,potentially followed by a
                        '-', and the amount of children the displayable
                        takes(valid options are '0', '1' or 'many', with
                        'many' being the default)

You can give several .rpyc files on the command line. Each script will be decompiled to a corresponding .rpy on the same directory. Additionally, you can pass directories. All .rpyc files in these directories or their subdirectories will be decompiled. By default, the program will not overwrite existing files, use -c to do that.

This script will try to disassemble all AST nodes. In the case it encounters an unknown node type, which may be caused by an update to Ren'Py somewhere in the future, a warning will be printed and a placeholder inserted in the script when it finds a node it doesn't know how to handle. If you encounter this, please open an issue to alert us of the problem.

For the script to run correctly it is required for the unrpyc.py file to be in the same directory as the modules directory.

Game injection

The tool can be injected directly into a running game by placing either the un.rpyc file or the bytecode.rpyb file from the most recent release into the game directory inside a Ren'py game. When the game is then ran the tool will automatically extract and decompile all game script files into the game directory. The tool writes logs to the file unrpyc.log.txt.

Library usage

You can import the module from python and call unrpyc.decompile_rpyc(filename, ...) directly.

Notes on support

The Ren'py engine has changed a lot through the years. While this tool tries to support all available Ren'py versions since the creation of this tool, we do not actively test it against every engine release. Furthermore the engine does not have perfect backwards compatibility itself, so issues can occur if you try to run decompiled files with different engine releases. Most attention is given to recent engine versions so if you encounter an issues with older games, please report it.

Additionally, with the jump to python 3 in Ren'Py 8, it became difficult to support all ren'py versions with a single tool. Therefore, please consult the compatibility section above to find out which version of the tool you need.

Issue reports

As Ren'py is being continuously developed itself it often occurs that this tool might break on newer engine releases. This is most likely due to us not being aware of these features existing in the first place. To get this fixed you can make an issue report to this repository. However, we work on this tool in our free time and therefore we strongly request performing the following steps when making an issue report.

Before making an issue report:

If you are making an issue report because decompilation errors out, please do the following. If there's simply an error in the decompiled file, you can skip these steps.

  1. Test your .rpyc files with the command line tool and both game injection methods. Please do this directly, do not use wrapper tools incorporating unrpyc for the report.
  2. Run the command line tool with the anti-obfuscation option --try-harder.

When making an issue report:

  1. List the used version of unrpyc and the version of ren'py used to create the .rpyc file you're trying to decompile (and if applicable, what game).
  2. Describe exactly what you're trying to do, and what the issue is (is it not decompiling at all, is there an omission in the decompiled file, or is the decompiled file invalid).
  3. Attach any relevant output produced by the tool (full command line output is preferred, if output is generated attach that as well).
  4. Attach the .rpyc file that is failing to decompile properly.

Please perform all these steps, and write your issue report in legible English. Otherwise it is likely that your issue report will just receive a reminder to follow these steps.

Feature and pull requests

Feature and pull requests are welcome. Feature requests will be handled whenever we feel like it, so if you really want a feature in the tool a pull request is usually the right way to go. Please do your best to conform to the style used by the rest of the code base and only affect what's absolutely necessary, this keeps the process smooth.

Notes on deobfuscation

Recently a lot of modifications of Ren'py have turned up that slightly alter the Ren'py file format to block this tool from working. The tool now includes a basic framework for deobfuscation, but feature requests to create deobfuscation support for specific games are not likely to get a response from us as this is essentially just an arms race, and it's trivial to figure out a way to obfuscate the file that blocks anything that is supported right now. If you make a pull request with it we'll happily put it in mainline or a game-specific branch depending on how many games it affects, but we have little motivation ourselves to put time in this arms race.

https://github.com/CensoredUsername/unrpyc

unrpyc's People

Contributors

al42and avatar artoria2e5 avatar censoredusername avatar einstein95 avatar hyphz avatar iamghost avatar jackmcbarn avatar kapilgain avatar lgd-fr avatar madeddy avatar meishuu avatar mwchase avatar red54 avatar sleeplessone avatar yuriks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unrpyc's Issues

Problem with the "unrpyc.py" line 89

Hello, i'm sorry that I need your help this late (few years later) but I have a problem :

I installed Python 3.6.1 and I enter this command in a normal command terminal :

C:\Users\Administrateur\AppData\Local\Programs\Python\Python36-32\python.exe unrpyc.py script-a1-friday_FR.rpyc

after that, I have a error message :
C:\Users\Administrateur\AppData\Local\Programs\Python\Python36-32\python.exe unrpyc.py script-a1-friday_FR.rpyc
File "unrpyc.py", line 89
print "Decompiling %s to %s..." % (input_filename, out_filename)
^
SyntaxError: invalid syntax

I searched in the file unrpyc.py with Notepad++ but I'm not really good at Python so I need help, please.

unrpyc.py Error

Ran on a fresh install of Python 2.7.

Traceback (most recent call last):
File "unrpyc.py", line 33 in <module>
import decompiler
ImportError: No module named decompiler

What should I do?

Order of keywords is lost inside screen

Minimal test case:

screen testScreen:
    modal False
    window:
        text "foo":
            style "settings_link"
            xalign 0.5
            yalign 0.08
            color "#ffffff"

When compiled, here's the relevant part of its dump:

<renpy.sl2.slast.SLDisplayable 
  .child_or_fixed = False,
  .children = [],
  .default_keywords = {},
  .displayable = <class renpy.text.text.Text>,
  .imagemap = False,
  .keyword = [
      (
          u'style',
          u'"settings_link"'
      ),
      (
          u'xalign',
          u'0.5'
      ),
      (
          u'yalign',
          u'0.08'
      ),
      (
          u'color',
          u'"#ffffff"'
      )
  ],
  .pass_context = False,
  .positional = [u'"foo"'],
  .replaces = True,
  .scope = True,
  .serial = 254,
  .style = 'text'
>

Note that at this point, "style", "xalign", "yalign", and "color" are still in the original order. Here's the decompiled .rpy:

screen testScreen:
    modal False
    window:
        text "foo":
            color "#ffffff"
            xalign 0.5
            style "settings_link"
            yalign 0.08

Now, the ordering of "style", "xalign", "yalign", and "color" has been lost.

Paired property lost on renpy.ast.With

Given this original dump:

            <renpy.ast.If 
                .entries = [(
                    u'hour in [22,23,24,0,1,2,3,4,5,6]',
                    [
                        <renpy.ast.With 
                            .expr = 'None',
                            .next = None,
                            .paired = u'dissolve'
                        >,
                        <renpy.ast.Scene 
                            irrelevant stuff snipped
                        >,
                        <renpy.ast.With 
                            .expr = u'dissolve',
                            .next = None,
                            .paired = None
                        >,
                        <renpy.ast.Python 
                            irrelevant stuff snipped
                        >,
                        <renpy.ast.With 
                            .expr = 'None',
                            .next = None,
                            .paired = u'dissolve2'
                        >,
                        <renpy.ast.Show 
                            irrelevant stuff snipped
                        >,
                        <renpy.ast.With 
                            .expr = u'dissolve2',
                            .next = None,
                            .paired = None
                        >,
                        <renpy.ast.Python 
                            irrelevant stuff snipped
                        >,
                        <renpy.ast.Return 
                            .expression = None,
                            .next = None
                        >
                    ]
                )],
                .next = None
            >,

If the rpyc that gave that dump is decompiled and then recompiled, here's the new dump:

            <renpy.ast.If 
                .entries = [(
                    u'hour in [22,23,24,0,1,2,3,4,5,6]',
                    [
                        <renpy.ast.With 
                            .expr = u'None',
                            .next = None,
                            .paired = None
                        >,
                        <renpy.ast.Scene 
                            irrelevant stuff snipped
                        >,
                        <renpy.ast.With 
                            .expr = u'dissolve',
                            .next = None,
                            .paired = None
                        >,
                        <renpy.ast.Python 
                            irrelevant stuff snipped
                        >,
                        <renpy.ast.With 
                            .expr = u'None',
                            .next = None,
                            .paired = None
                        >,
                        <renpy.ast.Show 
                            irrelevant stuff snipped
                        >,
                        <renpy.ast.With 
                            .expr = u'dissolve2',
                            .next = None,
                            .paired = None
                        >,
                        <renpy.ast.Python 
                            irrelevant stuff snipped
                        >,
                        <renpy.ast.Return 
                            .expression = None,
                            .next = None
                        >
                    ]
                )],
                .next = None
            >,

Every .paired inside a With has been changed to None.

Support the rpyc2 format

Starting from ren'py 6.99.5, the .rpyc save format has changed significantly. This format can be distinguished by a new header in the files.

Total of things that have been altered:

New header: RENPY RPC2.
New archive format. The header is followed by a table of slot/offset/length pointing to locations in the file. These locations contain the compressed pickled AST.
Way the AST is pickled. Now uses new instead of setstate. I'm not sure if this is an issue.
Currently the format is used in a simple way. slot 1 contains the old-fashioned rpyc data, slot 2 contains the same data with static transformations (like translation) applied.

Decompiled file crash

Traceback (most recent call last):
File "renpy\execution.py", line 408, in run
node.execute()
File "renpy\ast.py", line 1151, in execute
show_imspec(self.imspec, atl=getattr(self, "atl", None))
File "renpy\ast.py", line 1036, in show_imspec
atl=atl)
File "renpy\exports.py", line 647, in show
sls.add(layer, img, key, zorder, behind, at_list=at_list, name=name, atl=atl, default_transform=default_transform, transient=transient)
File "renpy\display\core.py", line 975, in add
thing._show()
File "renpy\display\transform.py", line 964, in _show
super(ATLTransform, self)._show()
File "renpy\display\transform.py", line 948, in _show
self.update_state()
File "renpy\display\transform.py", line 806, in update_state
fr = self.function(self, self.st, self.at)
File "renpy\atl.py", line 483, in execute
block = self.compile()
File "renpy\atl.py", line 451, in compile
block = self.atl.compile(self.context)
File "renpy\atl.py", line 624, in compile
statements = [ i.compile(ctx) for i in self.statements ]
File "renpy\atl.py", line 871, in compile
values = [ ctx.eval(i) for i in exprs ]
File "renpy\atl.py", line 223, in eval
return eval(expr, renpy.store.dict, self.context) #@undefinedvariable
File "", line 1, in
NameError: name 'knot' is not defined
code: linear 3.0 align knot (0.0, .33) knot (1.0, .66) knot (0.0, 0.0) zoom 1.0
RenPy version: 1749

atl.rawchild in Hate Plus

While decompiling script.dlc2.rpyc in Hate Plus some of the image definitions are decompiled as
TODO atl.RawChild
Will support for atl children be implemented in the near future?

Codegen doesn't make line numbers match

When decompiling SL1 screens, python blocks' linenumbers are wrong, since codegen doesn't respect the Python AST's line numbers when generating source code.

Support Screen Language 2

In ren'py 6.18, screen language was reworked to be an entirely different system and a new statement (showif) was added. Currently we cannot decompile this.

Indentation of some Python code is wrong

Minimal test case:

init:
    python:
        foo = [
            'bar'
        ]

When compiled, this is what the relevant part of the dump looks like:

[<renpy.ast.Init 
    .block = [<renpy.ast.Python 
        .code = <renpy.ast.PyCode 
            .source = u"""
foo = [
            'bar'
        ]
"""

        >
    >]
>]

When decompiled, this is the result:

init:
    python:
        foo = [
                    'bar'
                ]

An error occured while decompiling the first script of Katawa Shoujo

First of all, thank you for this wonderful tool. It works really good. But, when I try to decompile the first script file of Katawa Shoujo, I get an error.
ks

In fact, even if it gives me this error, it also decompiles some of the file, but incomplete; only till when it comes to the first menu label.
I've tried to decompile German, Japanese, Russian etc. versions of the same file as well, but it always gives me the same error.
My operating system is Windows 8.1 64-bit, and I've installed Python 2.7 as stated in the Read Me file.
Sadly, I'm not that experienced in programming, and Python, so I don't know how to solve it. :(

Handle init offset better

Currently, if an rpy uses init offset, when we decompile it, every single init block will be explicitly specified with the offset. We should come up with some heuristic for detecting this (sort of like we do with SL2 has blocks) and outputting an init offset of our own to make our output look neater.

"rpymc"->"rpym" Ren'Py script module support

Does un.rpyc intentionally not support decompiling Ren'Py script modules loaded with renpy.load_module (not to be confused with renpy.loader.load_module)? Information on this can be found in its documentation and the older wiki.

I ask this, because I could not find this support in un.rpyc (although I could not see why unrpyc.decompiler would not support such) and according a quick blame search in Ren'Py source, it has supported such for upwards of 10-12 years (I did not attempt to dig deeper; the codebase has moved around and it is quite possible such modules were supported even earlier in Ren'Py). According to the older wiki this feature was introduced in 6.6.0, released February 10, 2008 (making it a little less than ten years old).

It should be noted that renpy.script treats rpy/rpyc scripts and rpym/rpymc script modules very similarly and more specifically renpy.script.load_file handles rpy/rpym and rpyc/rpymc in exactly the same way.

Whitespace inside parentheses causes additional sets of parentheses to be generated

Given this original source:

screen testScreen:
    modal True
    imagemap:
        hotspot (0, 100, 500, 50):
            clicked Play("foo","foo.ogg")
        hotspot (100,100,500,50):
            clicked Play("bar","bar.ogg")
        hotspot (200,100,500,50):
            clicked Play("baz", "baz.ogg")

When compiled and decompiled, this is the result:

screen testScreen:
    modal True
    imagemap:
        hotspot ((0, 100, 500, 50)):
            clicked Play("foo","foo.ogg")
        hotspot (100,100,500,50):
            clicked Play("bar","bar.ogg")
        hotspot (200,100,500,50):
            clicked (Play("baz", "baz.ogg"))

Note that the parentheses that contained whitespace in the original code have been doubled in the result.

Unknow AST Node when unrpycing 'Sakura Spirit' -screens.rpyc

The full code shows below:


init -2:
<<>>
<<>>
screen say:
modal False
default side_image = None
default two_window = True
if (not two_window):
window:
id 'window'
vbox:
style 'say_vbox'
if who:
text who:
id 'who'
text what:
id 'what'
else:
vbox:
style 'say_two_window_vbox'
if who:
window:
style 'say_who_window'
text who:
id 'who'
window:
id 'window'
vbox:
style 'say_vbox'
text what:
id 'what'
if side_image:
add side_image
else:
add SideImage():
xalign 0.0
yalign 1.0
if show_quick_menu:
use quick_menu

init -2:
<<>>
<<>>
screen choice:
modal False
window:
xalign 0.5
style 'menu_window'
yalign 0.5
vbox:
style 'menu'
spacing 5
for caption, action, chosen in items:
if action:
if ('(tworows)' in caption):
$ caption = caption.replace('(tworows)', '')
button:
action action
style 'menu_choice_button1'
xoffset -500
text caption:
style 'menu_choice1'
else:
button:
action action
ypadding 20
style 'menu_choice_button'
at (slidesmooth2(0.2, 0.4, 0, 20))
text caption:
style 'menu_choice'
else:
text caption:
style 'menu_caption'

init -2:
$ config.narrator_menu = True
<<>>
<<>>
<<>>
screen input:
modal False
window:
style 'input_window'
vbox:
text prompt:
style 'input_prompt'
input:
style 'input_text'
id 'input'
use quick_menu

screen nvl:
modal False
window:
style 'nvl_window'
vbox:
style 'nvl_vbox'
for who, what, who_id, what_id, window_id in dialogue:
window:
id window_id
hbox:
spacing 10
if (who is not None):
text who:
id who_id
text what:
id what_id
if items:
vbox:
id 'menu'
for caption, action, chosen in items:
if action:
button:
action action
style 'nvl_menu_choice_button'
text caption:
style 'nvl_menu_choice'
else:
text caption:
style 'nvl_dialogue'
add SideImage():
xalign 0.0
yalign 1.0
use quick_menu

screen main_menu:
tag menu
modal False
window:
style 'mm_root'
frame:
xalign 0.17
yalign 0.2
ypadding 25
style_group 'mm'
background 'gui/mm_frame.png'
xpadding 25
vbox:
spacing 20
textbutton (('Start Game')):
action ([Play('sound', 'sfx/entergame.ogg'), Start()])
at (v_smooth(0.1, 0.6, 50, 0))
textbutton (
('Load Game')):
action ShowMenu('load')
at (v_smooth(0.2, 0.6, 50, 0))
textbutton _('Preferences'):
action ShowMenu('preferences')
at (v_smooth(0.3, 0.6, 50, 0))
textbutton _('Gallery'):
action ShowMenu('gallery')
at (v_smooth(0.4, 0.6, 50, 0))
textbutton _('Help'):
action ShowMenu('help')
at (v_smooth(0.5, 0.6, 50, 0))
textbutton _('Quit'):
action Quit(confirm=False)
at (v_smooth(0.6, 0.6, 50, 0))
add 'mm_logo'

init -2:
<<>>
<<>>
screen navigation:
modal False
window:
style 'gm_root'
frame:
xalign 1.0
yalign 0.5
ypadding 30
yoffset -41
style_group 'nav'
at (slidesmooth2(0, 0.6, 300, 0))
background 'gui/nav_frame.png'
xpadding 20
xoffset -20
vbox:
spacing 10
textbutton ('Return'):
action Return()
at (mm_smooth(0.3, 0.6, 150, 0))
textbutton ('Preferences'):
action ShowMenu('preferences')
at (mm_smooth(0.3, 0.6, 150, 0))
textbutton (
('Save Game')):
action ShowMenu('save')
at (mm_smooth(0.3, 0.6, 150, 0))
textbutton (
('Load Game')):
action ShowMenu('load')
at (mm_smooth(0.3, 0.6, 150, 0))
textbutton (_('Main Menu')):
action MainMenu()
at (mm_smooth(0.3, 0.6, 150, 0))
textbutton _('Help'):
action ShowMenu('help')
at (mm_smooth(0.3, 0.6, 150, 0))
textbutton _('Quit'):
action Quit()
at (mm_smooth(0.3, 0.6, 150, 0))

init -2:
<<>>
<<>>
screen file_picker:
modal False
frame:
style 'file_picker_frame'
yalign 0.5
ypadding 15
yoffset -42
at (slidesmooth2(0, 0.6, -200, 0))
background 'gui/pref_frame.png'
xpadding 15
xoffset 40
vbox:
hbox:
style_group 'file_pickernav'
textbutton _('Previous'):
action FilePagePrevious()
at (qm_smooth(0.5, 0.6, 0, -50))
textbutton _('Auto'):
action FilePage('auto')
at (qm_smooth(0.5, 0.6, 0, -50))
textbutton _('Quick'):
action FilePage('quick')
at (qm_smooth(0.5, 0.6, 0, -50))
for i in range(1, 9):
textbutton str(i):
action FilePage(i)
at (qm_smooth(0.5, 0.6, 0, -50))
textbutton ('Next'):
action FilePageNext()
at (qm_smooth(0.5, 0.6, 0, -50))
$ columns = 2
$ rows = 4
grid columns rows:
style_group 'file_picker'
spacing 16
transpose True
for i in range(1, ((columns * rows) + 1)):
button:
action FileAction(i)
at (v_smooth(0.5, 0.6, 0, -50))
hbox:
add FileScreenshot(i)
$ file_name = FileSlotName(i, (columns * rows))
$ file_time = FileTime(i, empty=
('Empty Slot.'))
$ save_name = FileSaveName(i)
text ('[file_name]. [file_time!t]\n[save_name!t]')
key 'save_delete':
action FileDelete(i)

screen save:
tag menu
modal False
use navigation
use file_picker
frame:
at (slidesmooth2(0, 0.6, -200, 0))
background 'gui/label_save.png'

screen load:
tag menu
modal False
use navigation
use file_picker
frame:
at (slidesmooth2(0, 0.6, -200, 0))
background 'gui/label_load.png'

init -2:
python:

    config.thumbnail_width = 100
    config.thumbnail_height = 60

init -2:
<<>>
<<>>
<<>>
<<>>
<<>>
screen preferences:
tag menu
modal False
use navigation
frame:
at (slidesmooth2(0, 0.6, -200, 0))
background 'gui/label_prefs.png'
frame:
xalign 1.0
yalign 0.5
right_margin 400
ypadding 15
yoffset -90
at (slidesmooth2(0, 0.6, -200, 0))
background 'gui/pref_frame.png'
xpadding 15
xoffset 40
grid 2 1:
style_group 'pref'
xoffset 50
xfill True
yoffset 30
vbox:
frame:
style_group 'pref'
vbox:
spacing 15
label ('Display')
hbox:
spacing 15
textbutton ('Fullscreen'):
action (Preference('display', 'fullscreen'))
at (v_smooth(0.5, 0.6, 0, -20))
textbutton ('Window'):
action (Preference('display', 'window'))
at (v_smooth(0.5, 0.6, 0, -20))
frame:
style_group 'pref'
vbox:
label ('Transitions')
hbox:
spacing 15
textbutton ('All'):
action (Preference('transitions', 'all'))
at (v_smooth(0.5, 0.6, 0, -20))
textbutton ('None'):
action (Preference('transitions', 'none'))
at (v_smooth(0.5, 0.6, 0, -20))
frame:
style_group 'pref'
vbox:
label (
('After Choices'))
hbox:
spacing 15
textbutton (
('Stop Skipping')):
action (Preference('after choices', 'stop'))
at (v_smooth(0.5, 0.6, 0, -20))
textbutton (
('Keep Skipping')):
action (Preference('after choices', 'skip'))
at (v_smooth(0.5, 0.6, 0, -20))
frame:
style_group 'pref'
vbox:
label ('Skip')
hbox:
spacing 15
textbutton (
('Seen Messages')):
action (Preference('skip', 'seen'))
at (v_smooth(0.5, 0.6, 0, -20))
textbutton (
('All Messages')):
action (Preference('skip', 'all'))
at (v_smooth(0.5, 0.6, 0, -20))
frame:
style_group 'pref'
vbox:
textbutton (
('Begin Skipping')):
action Skip()
at (v_smooth(0.5, 0.6, 0, -20))
vbox:
frame:
style_group 'pref'
vbox:
label (
('Music Volume'))
bar:
value (Preference('music volume'))
frame:
style_group 'pref'
vbox:
label (('Sound Volume'))
bar:
value (Preference('sound volume'))
if config.sample_sound:
textbutton ('Test'):
action (Play('sound', config.sample_sound))
style 'soundtest_button'
at (v_smooth(0.5, 0.6, 0, -20))
if config.has_voice:
frame:
style_group 'pref'
vbox:
label (
('Voice Volume'))
bar:
value (Preference('voice volume'))
textbutton (
('Voice Sustain')):
action (Preference('voice sustain', 'toggle'))
at (v_smooth(0.5, 0.6, 0, -20))
if config.sample_voice:
textbutton ('Test'):
action (Play('voice', config.sample_voice))
style 'soundtest_button'
at (v_smooth(0.5, 0.6, 0, -20))
frame:
style_group 'pref'
vbox:
label (
('Text Speed'))
bar:
value (Preference('text speed'))
frame:
style_group 'pref'
vbox:
label (('Auto-Forward Time'))
bar:
value (Preference('auto-forward time'))
if config.has_voice:
textbutton (
('Wait for Voice')):
action (Preference('wait for voice', 'toggle'))
at (v_smooth(0.5, 0.6, 0, -20))

init -2:
<<>>
<<>>
<<>>
<<>>
<<>>
<<>>
screen yesno_prompt:
modal True
window:
ypadding 20
style 'gm_root'
xpadding 20
frame:
yanchor 0
xmargin 0.05
xfill True
ypadding 0.05
ypos 0.1
style_group 'yesno'
at (slidesmooth2(0, 0.4, 0, 30))
background (Frame('gui/quitbox.png', 5, 5))
vbox:
xalign 0.5
spacing 30
yalign 0.5
label _(message):
xalign 0.5
hbox:
xalign 0.5
spacing 100
textbutton _('Yes'):
action yes_action
at (v_smooth(0.5, 0.6, 0, -20))
textbutton _('No'):
action no_action
at (v_smooth(0.5, 0.6, 0, -20))
key 'game_menu':
action no_action

init -2:
<<>>
<<>>
<<>>
screen quick_menu:
modal False
hbox:
xalign 0.8
yalign 1.0
spacing 5
yoffset -140
style_group 'quick'
textbutton _('Back'):
action Rollback()
at (qm_smooth(0.5, 0.6, 0, -50))
textbutton _('Save'):
action ShowMenu('save')
at (qm_smooth(0.5, 0.6, 0, -50))
textbutton _('Q.Save'):
action QuickSave()
at (qm_smooth(0.5, 0.6, 0, -50))
textbutton _('Q.Load'):
action QuickLoad()
at (qm_smooth(0.5, 0.6, 0, -50))
textbutton _('Skip'):
action Skip()
at (qm_smooth(0.5, 0.6, 0, -50))
textbutton _('Auto'):
action (Preference('auto-forward', 'toggle'))
at (qm_smooth(0.5, 0.6, 0, -50))
textbutton _('Prefs'):
action ShowMenu('preferences')
at (qm_smooth(0.5, 0.6, 0, -50))

init -2:
<<>>
<<>>
screen help:
tag menu
modal False
use navigation
frame:
at (slidesmooth2(0, 0.6, -200, 0))
background 'gui/label_help.png'
frame:
style 'default'
yoffset 84
at (slidesmooth2(0, 0.6, -200, 0))
background 'gui/pref_frame.png'
xoffset 40
add 'gui/help_bg.png':
at (v_smooth(0.5, 0.6, 0, -20))


Seems like some button-and-fonts-showing code(without this unknow part, the pervious art button disappeared and the font become renpy-default). could you please help me handle this problem? Thx!

Failed Decompilation

A failed attempt to decompile a .rpyc file. Log:

Error while decompiling ./quests.rpyc
Traceback (most recent call last):
File "unrpyc.py", line 131, in worker
no_pyexpr=args.no_pyexpr, comparable=args.comparable, translator=translator)
File "unrpyc.py", line 104, in decompile_rpyc
translator=translator)
File "unrpyc/decompiler/init.py", line 46, in pprint
decompile_python=decompile_python, translator=translator).dump(ast, indent_level)
File "unrpyc/decompiler/init.py", line 85, in dump
super(Decompiler, self).dump(ast, indent_level, skip_indent_until_write=True)
File "unrpyc/decompiler/util.py", line 28, in dump
self.print_nodes(ast)
File "unrpyc/decompiler/util.py", line 108, in print_nodes
self.print_node(node)
File "unrpyc/decompiler/init.py", line 99, in print_node
self.dispatch.get(type(ast), type(self).print_unknown)(self, ast)
File "unrpyc/decompiler/init.py", line 418, in print_label
reconstruct_paraminfo(ast.parameters),
AttributeError: 'Label' object has no attribute 'parameters'

The file in question.
https://mega.nz/#!TRoDUDgA!CXZdwoZs_nuO7q280ApwX7tlbxPh6AhFc8146QQuvF0

Transfer of Repository

To: @CensoredUsername

There's no messaging function anymore in GitHub, and your commiter email seemed to be a throwaway fake account, so I'm having to contact you through the issue tracker here...

I wanted to first thank you for your work in maintaining and keeping unrpyc updated all this time. I didn't have much interest in it or ren'py past the initial PoC, but I'm glad someone stepped up to the task. Every now and then I get people thanking me for writing it, since it's been useful for them.

So I'm bringing this up and asking if you'd like to have the repository transferred to your account. I don't know if you care about the recognition, but it just seems like the natural thing to do at this point. If you disagree I'll keep it as is. Let me know your thoughts on the matter.

Operator precedence in python code is ignored

I noticed this because I had a screen which had a text element that was probably
"%.2f" % (float1 - (float2 - float3))
originally but was decompiled to
"%.2f" % float1 - float2 - float3
This is of course wrong and leads to an exception because Python interprets this as
((("%.2f" % float1) - float2) - float3)
and string minus float is not defined.

The problem seems to be in modules.codegen.SourceGenerator.visit_BinOp, which just prints "left_operator operation right_operator", which only works out if no brackets were used in the original source.

Adding
self.write('(')
at the beginning and
self.write(')')
at the end of the method seems to produce code that's more correct but less readable because it also contains a lot of pointless brackets. No idea how a good solution would look like, I just started looking into this code 2 hours ago.

matter

it doesn't word in my computer.
what's"renpy folder"?
I just don't know where's "renpy module"
manythx for the works

[REGRESSION] Required parentheses are missing in SL1

As a result of f983608, the following code decompiles incorrectly in SL1:

screen testScreen:
    modal False
    vbox:
        text ('Some variable is %s' % someVar)

This is the result of decompiling it:

screen testScreen:
    modal False
    vbox:
        text 'Some variable is %s' % someVar

The decompiled output isn't valid: "expected a keyword argument, colon, or end of line."

    text 'Some variable is %s' % someVar
                               ^

"has" blocks not used when decompiling SL2

If SL2 code could have been a "has" block, it should be made a "has" block when decompiling. This doesn't show up at all in the AST, so it's not a high priority, but it makes the code cleaner to look at.

Issues with decompiling a .rpyc file

Hi,

I am attempting to decompile .rpyc files for a game called "Demon Master Chris". The game runs fine until i decompile battle.rpy from battle.rpyc, at which point it will get an error when entering combat.

Error message :

I'm sorry, but an uncaught exception occurred.

While running game code:
File "game/dungeon.rpy", line 20, in script call
File "game/dungeon.rpy", line 20, in script call
File "game/dungeon.rpy", line 103, in script call
File "game/dungeon.rpy", line 20, in script call
File "game/dungeon.rpy", line 103, in script call
File "game/battle.rpy", line 17, in script call
File "game/battle.rpy", line 22, in script
Exception: Required parameter talk has no value.

-- Full Traceback ------------------------------------------------------------

Full traceback:
File "D:\DemonMasterChris\renpy\execution.py", line 288, in run
node.execute()
File "D:\DemonMasterChris\renpy\ast.py", line 666, in execute
values = apply_arguments(self.parameters, renpy.store._args, renpy.store._kwargs)
File "D:\DemonMasterChris\renpy\ast.py", line 593, in apply_arguments
raise Exception("Required parameter %s has no value." % name)
Exception: Required parameter talk has no value.

Windows-7-6.1.7601-SP1
Ren'Py 6.15.7.374
DMC 1.10

I did not edit battle.rpy at all and this was the only file i decompiled (not sure why its mentioning dungeon.rpy). But seeing as how the error shows up only after i decompile battle.rpy form battle.rpyc, and i did nothing else, im thinking the decompier is messing up somehow....

Log of decompiling

D:\DemonMasterChris>unrpyc.py --clobber "game\battle.rpyc"
D:\DemonMasterChris\renpy\ast.py:33: DeprecationWarning: the md5 module is depre
cated; use hashlib instead
import md5
D:\DemonMasterChris\renpy\python.py:33: DeprecationWarning: the sets module is d
eprecated
import sets
Decompiling game\battle.rpyc to game\battle.rpy...
Decompilation of 1 script file successful

Add support for init label

Ren'Py supports an "init label" statement. Its own AST block is completely indistinguishable from a regular "label" statement. However, things that normally automatically generate an init block if they're not in one (image, define, default, transform, screen, testcase, translate strings, and style) won't do so inside of an init label. We need to detect if any of those exist outside of an init block inside each label, and if so, generate an init label instead.

Add support for testcase statement

Ren'Py git now supports a testcase statement, which has a sub-language of its own like screens do. We should support decompiling it (though probably not until after it's present in a release, in case there's any breaking changes to it until then).

Translate statements are un-combined

Given the following original source:

translate piglatin strings:
    old "foo"
    new "oofay"
    old "bar"
    new "arbay"
translate piglatin strings:
    old "baz"
    new "azbay"
    old "qux"
    new "xquay"

Here's what the dump of the compiled code looks like:

[
    <renpy.ast.Init 
        .block = [
            <renpy.ast.TranslateString 
                .language = u'piglatin',
                .new = u'oofay',
                .next = None,
                .old = u'foo'
            >,
            <renpy.ast.TranslateString 
                .language = u'piglatin',
                .new = u'arbay',
                .next = None,
                .old = u'bar'
            >
        ],
        .next = None,
        .priority = 0
    >,
    <renpy.ast.Init 
        .block = [
            <renpy.ast.TranslateString 
                .language = u'piglatin',
                .new = u'azbay',
                .next = None,
                .old = u'baz'
            >,
            <renpy.ast.TranslateString 
                .language = u'piglatin',
                .new = u'xquay',
                .next = None,
                .old = u'qux'
            >
        ],
        .next = None,
        .priority = 0
    >
]

Note that there are two of renpy.ast.Init, one for each time "translate piglatin strings:" appeared in the original source. When the code is decompiled, here's the result:

translate piglatin strings:
    old "foo"
    new "oofay"
translate piglatin strings:
    old "bar"
    new "arbay"
translate piglatin strings:
    old "baz"
    new "azbay"
translate piglatin strings:
    old "qux"
    new "xquay"

The text "translate piglatin strings:" has been unnecessarily repeated. For comparison, here's what the dump of that version looks like:

[
    <renpy.ast.Init 
        .block = [<renpy.ast.TranslateString 
            .language = u'piglatin',
            .new = u'oofay',
            .next = None,
            .old = u'foo'
        >],
        .next = None,
        .priority = 0
    >,
    <renpy.ast.Init 
        .block = [<renpy.ast.TranslateString 
            .language = u'piglatin',
            .new = u'arbay',
            .next = None,
            .old = u'bar'
        >],
        .next = None,
        .priority = 0
    >,
    <renpy.ast.Init 
        .block = [<renpy.ast.TranslateString 
            .language = u'piglatin',
            .new = u'azbay',
            .next = None,
            .old = u'baz'
        >],
        .next = None,
        .priority = 0
    >,
    <renpy.ast.Init 
        .block = [<renpy.ast.TranslateString 
            .language = u'piglatin',
            .new = u'xquay',
            .next = None,
            .old = u'qux'
        >],
        .next = None,
        .priority = 0
    >
]

Special-cased keywords in screens aren't handled correctly

Given the following original .rpy:

screen testScreen:
    variant "Small"
    zorder 0
    modal False
screen testScreen2:
    variant "Small"
    modal False
    zorder 0
screen testScreen3:
    zorder 0
    variant "Small"

When compiled, here's the relevant bits of the dump:

[
    <renpy.ast.Init 
        .block = [<renpy.ast.Screen 
            .screen = <renpy.sl2.slast.SLScreen 
                .keyword = [
                    (
                        u'variant',
                        u'"Small"'
                    ),
                    (
                        u'zorder',
                        u'0'
                    ),
                    (
                        u'modal',
                        u'False'
                    )
                ],
                .modal = u'False',
                .name = u'testScreen',
                .predict = 'None',
                .tag = None,
                .variant = u'"Small"',
                .zorder = u'0'
            >
        >]
    >,
    <renpy.ast.Init 
        .block = [<renpy.ast.Screen 
            .screen = <renpy.sl2.slast.SLScreen 
                .keyword = [
                    (
                        u'variant',
                        u'"Small"'
                    ),
                    (
                        u'modal',
                        u'False'
                    ),
                    (
                        u'zorder',
                        u'0'
                    )
                ],
                .modal = u'False',
                .name = u'testScreen2',
                .predict = 'None',
                .tag = None,
                .variant = u'"Small"',
                .zorder = u'0'
            >
        >]
    >,
    <renpy.ast.Init 
        .block = [<renpy.ast.Screen 
            .screen = <renpy.sl2.slast.SLScreen 
                .keyword = [
                    (
                        u'zorder',
                        u'0'
                    ),
                    (
                        u'variant',
                        u'"Small"'
                    )
                ],
                .modal = 'False',
                .name = u'testScreen3',
                .predict = 'None',
                .tag = None,
                .variant = u'"Small"',
                .zorder = u'0'
            >
        >]
    >
]

Note that the original presence or absence, and order, of each keyword is maintained in .keywords. Here's the result when decompiled back to source:

screen testScreen:
    modal False
    variant "Small"
screen testScreen2:
    modal False
    variant "Small"
screen testScreen3:
    modal False
    variant "Small"

The information about ordering of the keywords, as well as presence or absence of default values, has been lost. (I assume this happens with tag and predict as well).

Some statements missing when decompiling screens

Minimal test case:

screen testScreen():
    modal False
    imagemap:
        if 1 + 1 == 2:
            auto "true%s.png"
        else:
            auto "false%s.png"

When compiled and then decompiled, this is the result:

screen testScreen():
    modal False
    imagemap:
        if 1 + 1 == 2:
        else:

Here's the relevant piece of the dump:

<renpy.sl2.slast.SLIf
    .entries = [
        (
            u'1 + 1 == 2',
            <renpy.sl2.slast.SLBlock
                .children = [],
                .keyword = [(
                    u'auto',
                    u'"true%s.png"'
                )],
                .location = (
                    u'game/good.rpy',
                    4
                ),
                .serial = 255
            >
        ),
        ( 
            None,
            <renpy.sl2.slast.SLBlock
                .children = [],
                .keyword = [(
                    u'auto',
                    u'"false%s.png"'
                )],
                .location = (
                    u'game/good.rpy',
                    6
                ),
                .serial = 256
            >
        )
    ],
    .location = (
        u'game/good.rpy',
        4
    ),
    .serial = 254
>

AttributeError: 'RevertableList' object has no attribute 'extend'

Decompiling intro.rpyc to intro.txt...
Error while decompiling intro.rpyc:
Traceback (most recent call last):
File "/usr/bin/unrpyc", line 131, in worker
no_pyexpr=args.no_pyexpr, comparable=args.comparable, translator=translator, init_offset=args.init_offset)
File "/usr/bin/unrpyc", line 96, in decompile_rpyc
ast = read_ast_from_file(in_file)
File "/usr/bin/unrpyc", line 79, in read_ast_from_file
data, stmts = magic.safe_loads(raw_contents, class_factory, {"_ast"})
File "/usr/local/lib/python2.7/dist-packages/decompiler/magic.py", line 599, in safe_loads
encoding=encoding, errors=errors).load()
File "/usr/lib/python2.7/pickle.py", line 864, in load
dispatchkey
File "/usr/lib/python2.7/pickle.py", line 1195, in load_appends
list.extend(stack[mark + 1:])
AttributeError: 'RevertableList' object has no attribute 'extend'

Extraneous pass statement inserted after call

Minimal test case:

label foo:
    call bar
label bar:
    return

When compiled and decompiled, here's the result:

label foo:
    call bar
    pass
label bar:
    return

Every successive compilation and decompilation causes an additional "pass" statement to be inserted.

Translate while decompiling

Games are occasionally written in languages other than English, and English is then supported just as any other translated language. This works fine in the games, but it makes it really hard to understand the .rpy, since it's all in the source language. It would be nice if there were a way to load the translations into unrpyc, such that it would append the translated text in a comment after the lines, or maybe even replace the original text with translated text.

Keep getting 'Permission Denied'

Hi there,
I'm trying to use unrpyc, I just keep getting the same ''Permission Denied" after every file I try to extract. I'm obviously doing something wrong, don't suppose you could give me a step-by-step for a noobie?
I would really appreciate it, thank you.

importing renpy is broken under OSX (and possibly python versions higher than the renpy python version)

The current importing mechanism relies on setting the current working directory to the proper folder ("windows-i686", "linux-i686", "linux-x86_64" or "darwin-x86_64") so the dynamic libraries can be loaded. This however doesn't work under OSX, where it expects the libraries to be in the executable folder, which will fail)

Further, the cython parts of renpy are built only for a specific python version, so it may be required to run unrpyc with the python executable in renpy.

help me about this

Thanks you for your tool, it is very good!
i had convert file rpyc to rpy => it is OK!
now, i would like to rpy to rpyc file, how to do it! please help me this about
thanks for watching!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.