Closed Bexa2 closed 2 years ago
vars(ast[-1])[b'linenumber']
would be a hack but it would have to be done in many places for all attributes.
Went ahead and tried replacing every time it failed just to see if it'd decompile after all. It did.
Just an example of what I've done.
I can see a lot of things wrong with the replacements I've made, like lines 323 and 324:
with ast.code
being now vars(ast)[b'varna']
, I'm not even sure what I did, but for some reason it works.
both_options.zip
I just realized....
While it does work, the hacked version it's missing the config.
in the statements.
So it's define version = "6.0"
instead of define config.version = "6.0"
I wonder if Python 3.9's ast module would be any useful.
Fuck me, I replaced all <ast_object>.attribute
with vars(<ast_object>)[b'attribute']
and all [not] hasattr(<ast_object>, attribute)
with b'attribute' [not] in vars(<ast_object>)
and now it turns out that the dict vars()
returns has b"" string as keys in some games and "" string as keys in others.
Edit:
Had to wrap all string in a self.bstring("attribute")
function that returns string or bytes(string)
based on the check isinstance(list(vars(ast[0]).keys())[0], bytes)
at Decompiler.dump()
It works, having different errors now, but it works!
Edit:
And now a game is throwing errors on utils.py
, I'll just make 'say_get_code()' return always return " ".join(rv)
right at the beginning.
Yep, that's the result of ren'py slowly transitioning from py2 to py3. Somewhere along the line all identifiers in the codebase became unicode.
vars(something)[attrname] is rather inefficient btw, iirc it creates a copy of the backing dict. It's easier to just do getattr(something, attrname)
Also some other people have already worked on a py3 port that went pretty far, I just haven't had the time to look over it.
The problem is that when vars(ast)
returns a dictionary with b'' keys hasattr(ast, '') will return false, even if the attribute is there and I can't use b'' to check because hasattr rejects it.
print(f"byte string { isinstance(list(vars(ast).keys())[0], bytes) }") # byte string True
print(hasattr(ast, 'linenumber')) # False
print(hasattr(ast, b'linenumber')) # TypeError: hasattr(): attribute name must be string
When it doesn't return a dict with b'' keys then it's fine
print(f"byte string { isinstance(list(vars(ast).keys())[0], bytes) }") # byte string False
print(hasattr(ast, 'linenumber')) # True
I'll take a look at those python 3 versions to see how they do it.
Finally "found" what was wrong.
Million thanks to @madeddy
On magic.py
she changes the default argument to safe_loads(encoding=)
from bytes
to utf-8
. (I wouldn't have found this myself)
This can also be set on unrpyc.py
on read_ast_from_file
I can add a try catch, try bytes, check for AttributeError then try utf-8
So to get my script working all I had to do was change imports, pass encoding
to decompile_rpyc
, read_ast_from_file
and deobfuscate.read_ast
and so on until it reaches safe_loads
it's default value being 'bytes', change unicode
to str
and also changed startswith("RENPY RPC2")
to startswith(b"RENPY RPC2")
.
I think that's all.
Now moving on to RPG Maker.
This should be a options.rpyc options.zip
The file does start with
RENPY RPC2
The ast array looks like this:
And this is what I see if I pprint the objects:
I know this is supposed to be used with python2 so feel free to ignore it, I'm just curious as to why it's failing.