Ren'Py How to make a not so noticeable patch?

User1377022

New Member
Feb 13, 2022
6
0
I'm making a game, which is going to have extra content and I want to put it in a patch.
The problem is that the tutorials for patches like this
It would be too easy to see for patreon, I want to make one a bit more like the patch of this game https://f95zone.to/threads/corrupted-kingdoms-v0-13-4-arcgames.31912/
I tried to see the code for the patch but I couldn't find it, so how can I make a patch like that?
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
The two solutions you probably need are:

and .

The first allows you to create a script which replaces certain words, phrases or even whole sentences with another.

The second allows you to replace sections of code with an alternative version of the same code without breaking the game.

Both are usually implemented by creating extra .rpy files which aren't deployed with the main game and are instead added later as a "patch". This can be a single file or any number of separate files.

There's a post here about implementing both:

Edit: lol. The same one that Anne just linked.

I also wrote an example of the smtf a while back too:
https://f95zone.to/threads/renaming-the-characters.95716/post-6769832

Keep in mind that Patreon probably aren't looking at code. At least, not initially. They only start looking if someone reports a game to them (or maybe they also have a script searching their own site for specific keywords)... and apparently we live in a world where people can't differentiate between a real 18yr old girl and a 3 week old group of pixels. They also feel that it's okay to ban things which aren't illegal. That pressure has extended to the credit card processing companies, who Patreon need to stay in business.

If you do go down the patch file(s) route, make sure your patch is distributed by a username that is completely unrelated to any name linked directly to your game. One developer who patched their game in a similar way was brought to task by Patreon when someone reported that the patch (which wasn't mentioned or hosted by Patreon) was being shared on 3rd party sites by someone with the same username as the Patreon account.

The config.say_menu_text_filter works by replacing words like "Landlady" with "Mom", then gets more complex from there.

The config.label_overrides works by effectively renaming labels. The original code may have something like jump label_a. But the overridden code renames label_a to label_a_alt and adds a replacement label_a. As long as both bits of code do something like jump label_b at the end, the new code effectively replaces the old code.

The important thing about config.label_overrides is to add label statements as often as you can. Then add jump {next_label} at the end of any code block you think you are likely to replace. A good habit is to add a jump at the end of every code block, which makes things more flexible when you come to patch it.

One personal bugbear of mine though... If you are doing the whole "Landlady" thing... people don't call their land lady "Landlady"... she is "Helen" or "Tara" or "Nicola" or "Mrs Coldridge".

You can also override character definitions in your patch file too. Character() definitions are just a set of define statements normally done at init 0. If you nest your overridden characters at init 1 (or init 999), the version at the higher init level will be used.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
Edit: lol. The same one that Anne just linked.
With the ping-pong between Rich and me, it's probably the most complete thread on the subject.

The only thing possibly missing is patching through the use of custom text tags. But it's also the most complicated method, so... There's also the possibility to directly overwrite labels (config.allow_duplicate_labels, undocumented boolean) that isn't presented. But once again it's not a great loss since config.label_overrides offer more possibilities and can be reverted without the need to remove a file.
 
  • Like
Reactions: User1377022

User1377022

New Member
Feb 13, 2022
6
0
Thank you for your answers, that solves some of my questions, but I still have others.

Do these methods work to add complete events?
Example 100 images 400 lines of dialogue in the patch?

How do I add this patch to the game?
Do I have to put as in the other example persistent.patch_installed = False?
If so, isn't there another way where I don't have to write in the script.rpy that there is a patch?

Can the patch be updated with new content without breaking the game?
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
Do these methods work to add complete events?
Example 100 images 400 lines of dialogue in the patch?

Yes.
But...

It depends on how you are handling your event system.

If you've got your events stored in a (or tuple or dict or set), then all your patch needs to do is expand that list.

If you've your events to react to a players choices, then you might need to use the label override solution above to have an alternate (expanded) version of that event choice logic in the patch code. Then have those choices jump to labels that only exist in the patch code. As long as that expanded code jumps back into the main script somewhere at some point, it'll be fine. Doesn't matter if that's 1 image and 4 lines of code or 1,000 images and 8,000 lines of code.

You could do something we haven't talked about yet, which is to check if the label exists before jumping to it. will do that for you. So you have code in your main game that will check if the label exists and go there if it does. But if the label doesn't exist, it will do something else. If that label ONLY exists in the patch file, then it will only be invoked if the patch file(s) are present.

If you didn't want to include the 100 extra images, you could put all the images in a subfolder of /images/ like /images/patch/ and then exclude them during the build process by adding a line like this one to your options.rpy:
build.classify('**/patch/**', None)
I haven't tested it, but I think it should work exactly like that.


How do I add this patch to the game?

Up to you.
Keep in mind RenPy doesn't care where the source code is, as long as it is in a file with the file extension .rpy.
You could call your patch file patch.rpy or patch1.rpy or cuthbert.rpy.

In the two examples I gave in my original post, they were both using either init: or init python:, both of which are run during game startup. RenPy allows for multiple init: or init python: blocks in ANY source file. If you had 10 different init: blocks across 6 different .rpy files, it would just run each one in turn (You can force the order by using init {number}, where the "number" is a number from -999 to +999... RenPy will run them from lowest to highest. If you omit the number, it defaults to init 0).

But, as above... it depends.
Depending on how you've written your code may change how you implement your patch.
You just need to play around with it all a bit until you understand it, then adapt your code as you see fit. As with most things related to RenPy... there is no single right answer... and the best answer is usually the one you understand the most.


Do I have to put as in the other example persistent.patch_installed = False?

Erm... No... I mean No?

You can if you want. Some people would code it that way - but I thought one of your goals was to hide evidence of patch existing if someone looked at your code. A game with a massive number of if persistent.is_patched == False: checks kinda gives the game away, even if the can't see the patched version. But as I said in the previous post, I'm not sure people at Patreon are really digging that deep. More like they're relying on screenshots sent to them by "concerned citizens".


If so, isn't there another way where I don't have to write in the script.rpy that there is a patch?

All the examples in those threads quoted earlier would go in a separate script file that wouldn't be included with the "public"/"sanitized" version of the game. Just the replacements (be it replacement words, replacement sentences or entire replacement blocks of code).

You could remove/rename the patch file(s) before building your game.
Or you could add lines like:
build.classify('**/patch.rpy*', None)
... in the relevant part of your options.rpy file to force the build process to exclude both the .rpy and .rpyc files when it builds the game for distribution. You could make it a little less obvious by calling the file extras.rpy instead of patch.rpy... or as I said earlier cuthbert.rpy.


Can the patch be updated with new content without breaking the game?

Yes. Probably.
Again, it depends... in this case, probably dependant on how well you understand how these patching mechanics work.
Like anything else in life... if you don't understand it... you'll probably fuck it up at some point. That's fine. That's how we learn.

The thing I will add is my usual disclaimer... If you can't quite follow it... play around with it a bit. If you still don't understand it after that... forget it and do it another way... In the case, even if that other way is "code everything in the main script file and hope Patreon doesn't look at the script"... Or "Just write the version of the game you WANTED to write in the first place... and don't monetize it using Patreon. Subscribestar will still quite happily take their cut if your MC is tag teaming your twin sisters with dad and mom going at it in the corner.

Also keep in mind that RenPy only needs the .rpyc files to run your game. You could exclude all the .rpy source files when building your game too. Not that it's especially hard to recreate them. But if you're worried about someone reading your source code... just don't include it within the publicly distributed version of the game.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
Regarding "persistent.patch"
Erm... No... I mean No?

You can if you want.
No, never, he cannot, it's forbidden.

It's a persistent value, it will stay forever... this while the patch relying on an external file can be present, or not.

One should never ever use a persistent value when what he is doing is not guaranteed to be persistent itself. And a patch is not something designed to be persistent, even this kind of patch.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
No, never, he cannot, it's forbidden.

It's a persistent value, it will stay forever... this while the patch relying on an external file can be present, or not.

One should never ever use a persistent value when what he is doing is not guaranteed to be persistent itself. And a patch is not something designed to be persistent, even this kind of patch.

I did kinda presume that it was a solution seen in another game or another thread. In so much as it seemed inconsistent with the goal of not coding "patch" stuff into the main script.

Which is not to say he couldn't do it. Hence my "No. No?"

I guess I presumed that any solution that uses persistent.is_patched is relying on a single patch file that only contains persistent.is_patched = True, probably within a init 1 python:... and nothing else. Again, completely incompatible with his stated goal, since all the other code necessary to make this work would be in the main script.

But doing it that way means that when the next release comes out, the game is still "patched" whether or not the patch is reapplied or not. The main script can still check it, without setting any other value, since persistent objects that don't exist at least return None - and therefore won't break the game if the patch file isn't present. It also avoids having to think about what happens when a player save in the middle of "patched" bit of content, but then loads that save when the next release comes out, but the player hasn't applied the patch.

If I'm honest, if I were writing a game even a year ago... this is the way I'd likely write it. Primarily because I think it's the simplest solution for a new programmer. Just if persistent.is_patched: ... do one thing, else do something else. I personally don't foresee anyone who sought out a patch wanting to play an unpatched version in the future... and I'd likely ship the game including all the "banned by Patreon" content, but with it all initially switched off. And yes, that would make it more difficult for me if my imaginary game were reported to their Trust and Safety Team. Thankfully, I've learned more complex ways to do things since then AND I'm not actually writing a game, so it's easy to throw away the future Patreon money I was never going to be getting anyway. But for pure simplicities sake, this is the way I would have done it.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
But doing it that way means that when the next release comes out, the game is still "patched" whether or not the patch is reapplied or not.
It also mean that you can not have an unpatched play anymore, even if you want, since persistent value are also global. Once you patched the game, it's for life.

Even with this approach it would still be better to use a regular variable, define patched = False in the game, and define patched = True in the patch (with a name making it proceeded after). It would works in the same way, but offer to the player the possibility to have an unpatched play whenever he want.