CensoredUsername / unrpyc

A ren'py script decompiler
Other
837 stars 149 forks source link

Error Decompiling Dialogue #91

Closed scriptomancer closed 4 years ago

scriptomancer commented 4 years ago

Hello, I'm so new to this so forgive all my ignorance, but I recently used unrpyc to decompile a VN and had a strange issue. The .rpy files are entirely readable except for dialogue. I can see all the artwork and transition code, but all lines of dialogue show this error:

pass # <<<COULD NOT DECOMPILE: Unknown AST node: <class 'store.AdjSay'>>>>

In googling the issue, I saw your posts #70 and #71 and thought it was probably a lost cause, but it was so incredibly specific and widespread (that is the only error I get, always with the dialogue and ALL the dialogue) that I thought I would double-check with you and see if there was anything that could be done. If not, sorry for wasting your time.

CensoredUsername commented 4 years ago

Hey, thanks for the good issue description. It's indeed similar to #70, the game has defined a custom AST class somewhere in a .rpy file. if you find a definition for class AdjSay somewhere in the code of the game I can give you some pointers on how to mod unrpyc to support that specific game (it's probably in a file named 00something.rpy), but as it's not a ren'py feature, there's no use of having it in the main repo.

Basically it means the game defined a custom statement, using the internal Ren'Py API for that. Said statement is then used in the script files which causes that line to be printed at every spot that custom statement is used.

scriptomancer commented 4 years ago

Thanks so much for getting back to me! Well, as you suggested, I went digging around and, tada, I found a file called customStatements.rpy that reads "class AdjSay(renpy.ast.Node):" and then has a long stretch of code defining it. Thanks so much for pointing me at that. Any information you could give me on how to mod unrpyc so I can get this working would be so very appreciated!

CensoredUsername commented 4 years ago

The process is like defining any other node in unrpyc's base decompiler. The code for this is located in unrpyc/decompiler/__init__.py. There's a few gotchas.

First, at line 30, modify the lines

magic.fake_package(b"renpy")
import renpy

to

magic.fake_package(b"renpy")
magic.fake_package(b"store")
import renpy
import store

This will allow you to use types from the store module as if you're inside ren'py's environment.

Then, you'll have to add support for the store.AdjSay ast node in the Decompiler class in this file. Considering the name of the class it will probably have similar syntax to the regular ren'py statement, which is implemented in the function print_say in this class. So next to that, you can define a function to handle the ast node produced by your game with the following syntax:

@dispatch(store.AdjSay)
def print_adjsay(self, ast):
    #add code to handle the node in the argument `ast` here.
    pass

That's all I can help you with as I have no idea of how your game defines that statement.

One final tip: You can inspect the raw abstract syntax tree nodes stored in any .rpyc file by running unrpyc with the -d option. It will output a file `.txtwith a representation of the contained data. If you search it forstore.AdjSay` you'll should be able to see exactly what data is contained in the node.

scriptomancer commented 4 years ago

Thank you so much for the information, and for the instructions. My experience with python has been to install it and run your decompiler, so again, I'm sorry for my ignorance here... I've added what you've told me to add, but I just want to clarify one part:

add code to handle the node in the argument ast here.

I'm not sure what code to put in there. Is it the definition? It just seems rather large, so I'm a little confused if I input the whole thing or maybe I'm entirely off-base. I attached a copy of the customStatements.rpy in .txt format. I'm not trying to foist the work off on you by any means. I'm just looking for clarification.

CensoredUsername commented 4 years ago

Hey,

So, some of the code you have in customStatements.rpy is responsible for parsing the missing statements into the store.AdjSay nodes in the ast. It's unfortunately hooking quite deep in ren'py internals. I can't see at a glance what it's exactly doing either. Now at that comment, you'll have to insert code that does the reverse: You take the store.AdjSay node and have to emit the text of the statement.

What I can tell you is that the function that parses text into the AdjSay node is parse_sayStatement at line 159. It parses text in a custom dialogue format and returns an AdjSay instance. You'll have to write the reverse of that function, taking the AdjSay instance and using self.write calls in Decompiler to emit the relevant text. Doesn't look like the parser is too difficult luckily.

scriptomancer commented 4 years ago

Okay, thanks so much for the information. I am going to try to figure this out..

scriptomancer commented 4 years ago

Thanks to your information, a friend of mine was able to figure it out! Thank you again for all of your help!

CensoredUsername commented 4 years ago

Cool!