Making patches for games

simple_human

Newbie
Dec 2, 2018
70
2,630
Hi everyone!!!
In this thread I'm learning how to make a patch (es)

From what I already know, there is a step-by-step guide on how to make a patch for most Ren'Py games.
It's not as complicated as it may seem, and following the instructions, anyone can handle it.

There are two methods to patch the game:

Method 1. Changing lines directly in the game files:
You don't have permission to view the spoiler content. Log in or register now.

Method 2. Separate patch(recommend):
You don't have permission to view the spoiler content. Log in or register now.
  • Additional Function: Call a character by a nickname
    You don't have permission to view the spoiler content. Log in or register now.

p.s. I have no coding knowledge, only know a few functions.
If there are any remarks or tips on how to improve the code, I would be glad to know them
 
Last edited:

clowns234

Engaged Member
Game Developer
May 2, 2021
3,056
4,745
Something like this would probably work:
Code:
if ssn == "":
    $ ssn = "Best friend"
    if renpy.has_label('ssn_names'):
        call ssn_names


label ssn_names:
    $ ssn = "Sister"
    return
'ssn_names' would be in the ipatch.rpy file.

Otherwise, you could go with this and have the patch overwrite the file that contains ssn_names:
Code:
if ssn == "":
     call ssn_names
Original file:
Code:
label ssn_names:
    $ ssn = "Best friend"
return
The patch file would overwrite the original file that has ssn_names
Code:
label ssn_names:
    $ ssn = "Sister"
    return
 
Last edited:

DiviDreamer

Member
Aug 29, 2020
262
228
If you need to use just one file without overwriting original game use override:

Code:
init -1:
    define config.label_overrides = {'start':'start_patch'}

label start_patch:
    #do your stuff here
 

DiviDreamer

Member
Aug 29, 2020
262
228
{'start':'start_patch'}
This part only sample for you to understand how it work

your attached file label starts with "label introduction4" so its must be like {'introduction4':'introduction4_patch'}
your label introduction4_patch must be copy of original (it's part of the game) edit code you need
just understand that it's switch one label with another that's all
 
  • Like
Reactions: simple_human

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,279
15,117
the whole phrase consists of one [bn]!
[...]
Does anyone know how to solve this problem?
How is this a problem ?

It's a pure question of logic, there's absolutely no need to have some coding knowledge to answer your question.


What you want to do is to replace the whole phrase, and to replace it only when it's the whole phrase.

Then what you have to do is... to replace the whole phrase, and to replace it only when it's the whole phrase.


Python:
init python:
    def replace_text(text):
        if text == "[bn]!":
            return "Nikki"
        else:
            return text

    config.say_menu_text_filter = replace_text

Maybe someone knows some code that will replace the whole line (for me it would be great), for example, that instead of the character "i", the character "g" will speak.

Or maybe that will ignore the line for the game.

Or some other code...
All of this, but what does this have to do with the question ?
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,279
15,117
Yes it works... but it replaces in other cases where there is [bn]! and I only need to replace in this one particular case
*sigh*

Python:
init python:
    def replace_text(text):
        if text == "I'm going crazy!":
            store.replaceNext = True
        elif text == "[bn]!" and store.replaceNext:
            store.replaceNext = False
            return "Nikki"

        return text

    config.say_menu_text_filter = replace_text

default replaceNext = False
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,279
15,117

DiviDreamer

Member
Aug 29, 2020
262
228
Hi all !
I'm facing a problem while making a patch for the game "The Last Romantic". I don't know how to change the age of the character

original game code
Code:
screen charscreen():
    zorder 150
    default auntparams = {'age': 30, 'weight': None, 'height' : None, 'bust' : 5, 'birth' : __('Обезьяна'), 'dessert' : None, 'relationship' : relationship_aunt, 'tasks' : aunttasks, 'zodiac': None, 'lust': lust_aunt, 'depravity' : depravity_aunt}
View attachment 2619591 to this -> View attachment 2619592
Does anyone know how to do this?
I don't want to replace or edit the original files, I want the patch to do everything.
This is simple 2d array, don't have time to download and check that game but this may help
 
  • Like
Reactions: simple_human

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,279
15,117
Does anyone know how to do this?
I don't want to replace or edit the original files, I want the patch to do everything.
I'm tempted to answer that it's not possible, but it's not totally true. It's more: "It's possible but it rely on something really dirty that need to be handled with care by people having a strong knowledge regarding what they are doing, or there's the cheap version that do it, but with more limitations".

"auntparams" is defined directly in the screen, what mean that it can be addressed only from the screen itself and, with some limitation, from the father and children screens.
And it's here that come the dirty part, you can't add a father or children screens just like that. You need to directly edit Ren'Py's internal for this, what can easily break everything.

Or there's the said "cheap version":
You copy the screen in another rpy file, and edit it in this file. As long as the combination path+name of this file come after (in alphabetic order) the one hosting the original screen, Ren'Py will use your screen instead.



This is simple 2d array, [...]
No, it's dictionary, something more advanced that a 2D array with, among other things, none ordered values and index (the keys) that can be any kind of values, from a string to an object.
This is a 2D array: [ [ 0, 1, 2, 3, 4 ], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4] ]

But anyway the main problem is that it's declared in the screen...
 
  • Like
Reactions: simple_human

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,279
15,117
Code:
        frame = inspect.currentframe()
        who = frame.f_back.f_locals.get('who')
I don't know if it while solve your issue, but Ren'Py have a _last_say_who variable that do the same. The difference is that instead of returning the object, it return the string. Therefore to know if it's the MC who's currently speaking if _last_say_who == player.name: should be enough.

Of course, this if "player" is the ADVCharacter object used for the MC.
 
  • Like
Reactions: simple_human

simple_human

Newbie
Dec 2, 2018
70
2,630
I don't know if it while solve your issue, but Ren'Py have a _last_say_who variable that do the same. The difference is that instead of returning the object, it return the string. Therefore to know if it's the MC who's currently speaking if _last_say_who == player.name: should be enough.

Of course, this if "player" is the ADVCharacter object used for the MC.
Thank you for your reply!
It doesn't seem to be an ADVCharacter, am I understanding this right?

define player = CharacterPulse('[player_name]',color="#0984e3",image="player")

but I tried anyway, it didn't work:
Code:
init python:
    import inspect

    def replace_text(text):
        frame = inspect.currentframe()
        who = frame.f_back.f_locals.get('who')

        if _last_say_who == player.name: ### or  if _last_say_who == player_name:   if _last_say_who == '[player_name]':


            text = text.replace("[yasuka.n]", "{color=#a29bfe}Mom{/color}")

        return text

    config.say_menu_text_filter = replace_text
    config.replace_text = replace_text

a game file with characters
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,279
15,117
It doesn't seem to be an ADVCharacter, am I understanding this right?
Hmm, from the way it's used, it looks like a personal class that inherit from it. But the fact that in your original code, who do not match with player seem to indicate that there's more behind ; like some kind of deported proxying.

What game is it ?
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,279
15,117
this game, for decompiling game files you need to use this UnRen
Oh god, this guy is a full real jerk...
Why do people continue to cipher their Ren'Py games ? Do they still haven't understood that there's absolutely no point in this, except to piss off regular users and make the others laugh their ass out ?

So, as shown in scripts_custom/characterpulse.rpy (qui est effectivement une grosse bouse), his CharacterPulse class effectively inherit directly from renpy.character.ADVCharacter.

Therefore I dug a bit, and it's in fact my fault. _last_say_who do not host the ADVCharacter.name value, but the name of the ADVCharacter object. So, the right condition is _last_say_who == "player".



But well, it was an interesting error due to its side effect.
He spent hours to come to all his obfuscation levels, all this for absolutely nothing. Once you see that the RPYC files are ciphered, two minutes are more than enough to tweak unrpyc and make it works with them.
And there's really no better feeling that undoing so easily a dick move that, to quote him, "was a b*tch to make" ; lol, the guy censor his own comment in an adult game.

Well, now time for the effective lunch break and, again, sorry for my error.
 
  • Red Heart
Reactions: simple_human

simple_human

Newbie
Dec 2, 2018
70
2,630
Hi everyone!!!
I've been trying to make a patch to replace the label for some time now. I know this one:
Python:
#patch.rpy
init python:

    config.label_overrides['ma_selene_talk'] = 'newstart'

label newstart:

    play music "music/ep2/Mattia Vlad Morleo - Light Waltz.mp3" fadeout 2.0 fadein 3.0
    scene talk1
    with Fade(2.5,0,2.5)
...
But, it doesn't replace label unless there's a "jump" before it.
I can't find any way to replace "label ma_selene_talk."
Python:
#original script.rpy
...
    cl "Oh... my..."
    scene raya_store28
    with vpunch
    cl "{b}MOTHERFUCKERS{/b}!"
    $ renpy.end_replay()
    ##Because there's no "jump ma_selene_talk" here, the patch doesn't work.

label ma_selene_talk:

    play music "music/ep2/Mattia Vlad Morleo - Light Waltz.mp3" fadeout 2.0 fadein 3.0
    scene ma_selene_talk1
    with Fade(2.5,0,2.5)
    ma "How do you know... did you find the proof you were looking for?"
...
Dev barely uses "jump".

Please advise if anyone knows how to solve this.
Maybe use some other method or extend the one I used?
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,279
15,117
Please advise if anyone knows how to solve this.
I haven't tried it, mostly because I never had to, but there's a way to do this. It rely on that is called each time Ren'Py enter a new label, even through an implicit branching like in that case.
But it's a really dirty way to do this, for two reasons:

Firstly, because it literally hijack Ren'Py mid-process.
With config.label_overrides, Ren'Py is told to branch to a label, and before doing this it will look if there's a redirection defined. But here, Ren'Py will be already entering into the label when you'll order him to go to another label. It will break nothing, but it's not clean at all.

Secondly, because config.label_callback is not a list but a single value.
Therefore, there could be only one code behind it... And it happen that there's mods, tools, and even games, that already use it.
This mean that you MUST hook your code, looking at the current value, keeping it if it's not None, then call it every time your own code is called.

Globally, it works that way:
Python:
init python:

    myHijack = { "oldLabel1": "newLabel1",
               "oldLabel1": "newLabel1" }

    #  Parameters are the name of the label, and a flag telling if the branching is 
    # abnormal or not.
    #   /!\ /abnormal/ is the name flag as used in Ren'Py core, but the name do not
    # match the expected value. It will be /True/ if the label is normally called or jumped
    # to (what feel normal), and /False/ for implicit branching and return to "call ... from ..."
    # statement (what feel abnormal).
    def labelCallback( name, abnormal ):

        #  If it's possibly an implicit branching, and the label is in our redirection list,
        # ask Ren'Py to jump to our label instead of continuing to process the current
        # label.
        if not abnormal and name in myHijack:
            renpy.jump( myHijack[name] )

        # Then call the previous callback, if there's one.
        if previousLabelCallback:
            previousLabelCallback( name, abnormal )


    previousLabelCallback = config.label_callback
    config.label_callback = labelCallback
 
  • Red Heart
Reactions: simple_human

simple_human

Newbie
Dec 2, 2018
70
2,630
I haven't tried it, mostly because I never had to, but there's a way to do this. It rely on that is called each time Ren'Py enter a new label, even through an implicit branching like in that case.
But it's a really dirty way to do this, for two reasons:

Firstly, because it literally hijack Ren'Py mid-process.
With config.label_overrides, Ren'Py is told to branch to a label, and before doing this it will look if there's a redirection defined. But here, Ren'Py will be already entering into the label when you'll order him to go to another label. It will break nothing, but it's not clean at all.

Secondly, because config.label_callback is not a list but a single value.
Therefore, there could be only one code behind it... And it happen that there's mods, tools, and even games, that already use it.
This mean that you MUST hook your code, looking at the current value, keeping it if it's not None, then call it every time your own code is called.

Globally, it works that way:
Python:
init python:

    myHijack = { "oldLabel1": "newLabel1",
               "oldLabel1": "newLabel1" }

    #  Parameters are the name of the label, and a flag telling if the branching is
    # abnormal or not.
    #   /!\ /abnormal/ is the name flag as used in Ren'Py core, but the name do not
    # match the expected value. It will be /True/ if the label is normally called or jumped
    # to (what feel normal), and /False/ for implicit branching and return to "call ... from ..."
    # statement (what feel abnormal).
    def labelCallback( name, abnormal ):

        #  If it's possibly an implicit branching, and the label is in our redirection list,
        # ask Ren'Py to jump to our label instead of continuing to process the current
        # label.
        if not abnormal and name in myHijack:
            renpy.jump( myHijack[name] )

        # Then call the previous callback, if there's one.
        if previousLabelCallback:
            previousLabelCallback( name, abnormal )


    previousLabelCallback = config.label_callback
    config.label_callback = labelCallback
Thank you so much, that helped!
Checked in game, works fine, everything goes smoothly:)
 

ZLZK

Member
Modder
Jul 2, 2017
275
589
Firstly, because it literally hijack Ren'Py mid-process.
With config.label_overrides, Ren'Py is told to branch to a label, and before doing this it will look if there's a redirection defined. But here, Ren'Py will be already entering into the label when you'll order him to go to another label. It will break nothing, but it's not clean at all.
I have tested it and it doesn't work like that.
config.label_overrides is called only when there is no jump/return.
I mean it's called after label end, if it was not interrupted by jump or return.


Also using renpy.jump inside callback makes it look for redirection.

So, I don't know what you are talking about.

EDIT: Never mind, it works like you said.
But then, why does it not work with jump?

Seems like there is: pre_label > label.
And jump jumps to label part right away.

So if callback is in pre_label and goes to label part of other label.
Then it works the same.
Just that second part is different label.
 
Last edited:
  • Like
Reactions: simple_human

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,279
15,117
But then, why does it not work with jump?
What "jump" ? The statement or the python equivalent ? And what don't works with it ?

But well, I ask while knowing that both jump and renpy.jump() take count of both config.label_overrides and config.label_callback.


Seems like there is: pre_label > label.
And jump jumps to label part right away.

So if callback is in pre_label and goes to label part of other label.
Then it works the same.
Just that second part is different label.
Did this even mean something ?

The code is an AST, and Ren'Py just pass from a node to another, linearly by default. It's what permit "implicit jump", but also what make broken code like:
Python:
label whatever:
    default myVar = 42

    scene background
    show girl happy

    MC "Hi beauty."

    screen whatever():
       [...]

    GIRL "You're talking to me ?"
works.
Both default and screen are stored independently, therefore while browsing the AST, Ren'Py only encounter the significant nodes.

Then, time to time, where there's a if or while, or when there's a jump or a call, Ren'Py is forced to branch to a node that isn't the following one.

And that's all. There's no "pre_label" or whatever.
 
  • Like
Reactions: simple_human