Ren'Py Persistent file

Moffy

Active Member
Sep 25, 2019
919
700
How can i open and edit persistent file in RenPy games?
I want to learn it to be able to unlock the gallery in games.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,365
15,281
How can i open and edit persistent file in RenPy games?
I want to learn it to be able to unlock the gallery in games.
Just use Ren'py's console to change the value of the right variables when you play the game. The right variables being found by looking at the source code of the game.
 

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,490
7,035
How can i open and edit persistent file in RenPy games?
I want to learn it to be able to unlock the gallery in games.
Just use Ren'py's console to change the value of the right variables when you play the game. The right variables being found by looking at the source code of the game.
This very much depends on how the gallery is implemented. In some games, the gallery code uses persistent variables. If you open up the game source code, you'll see references to things like:
Code:
if persistent.abc:
    ...enable the abc image in the gallery
If it's done that way, anne O'nymous method is easy to use. To enable the console, you can add a file to the game named "zzz.rpy" and have that file contain the following:
Code:
init 999 python:
   config.console = True
   config.developer = True
(either that, or edit the "options.rpy" file, and add those lines at the very end.) Note that the second and third lines must be indented with spaces, not the tab key. Actually, only the config.console line is required, but I sometimes use the developer features as well when exploring someone else's game, so...

Once you've done this, pressing the Shift-O (i.e. a capital Oh - that's not a zero) will open up the Ren'py console. You can then type:
Code:
persistent.abc = True
and then close the console by hitting the "Esc" key. That will set the persistent.abc variable, which should unlock that one gallery entry. Repeat for the rest of the gallery entries you want to enable.

Of course, you have to look at the source code of the game in order to figure out what the variable names are and what values need to be set in them.

However, not all galleries are implemented this way. The gallery code I use, for example, relies on some Ren'py functionality that automatically tracks which images have been displayed to the user and which have not, so that I don't have to manage my own persistent variables for that. If you were to look inside one of my games, you'd see something like:
Code:
    image_gallery.button("slot01")
    image_gallery.condition('renpy.seen_image("ch0b_12")')
    image_gallery.unlock_image("ch0b_09")
    image_gallery.unlock_image("ch0b_10")
    image_gallery.unlock_image("ch0b_11_movie")
    image_gallery.unlock_image("ch0b_12")
renpy.seen_image is a function provided by Ren'py that will return True if the image referenced, (ch0b_12 in this case, has been seen by the player during the course of any game. In addition, the unlock_image sets the gallery up so that the gallery will only show an image if it's been seen by the user.

There's no direct way to edit the "what images have been seen" information that Ren'py keeps. At least, if there is, I'm not aware of it. However, you can game the system a bit:
  1. Start a new game (this works best from inside a game, not, for example, from the main menu)
  2. Mod the game, as described earlier, so that the console is enabled
  3. Open the console with Shift-O
  4. For each image, type scene the_image_name
What this does is to show the image in question, replacing whatever image is currently on the screen. Of course, you won't be able to see it well, because it'll be behind the console, but you could close the console and look at each image if you wanted to. More importantly, this will cause Ren'py to update its memory of which images have been shown. Thus, if you typed scene ch0b_12, that would satisfy the renpy.seen_image condition above.

A third way of doing this is to find where the bits you haven't seen are in the game's source code. If there's a label in front of that block of code, you can use the console and type jump the_label_name to go execute that part of the code, which will then play through the section you missed. This is the trickiest approach, because that block of code may depend on variables being set correctly, etc. Don't do this and then save - there's a very good chance that, as a result of this, you'll put the game into a bad state.

Anyway, those are some possible ways of attacking "I want to see that part of the gallery" or "I want to see that fragment of the game I didn't see without having to re-play from the start."
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,365
15,281
There's no direct way to edit the "what images have been seen" information that Ren'py keeps. At least, if there is, I'm not aware of it.
There's one, renpy.game.persistent._seen_images[("NAME_OF_THE_IMAGE",)] = True.
Therefore, renpy.game.persistent._seen_images[("ch0b_12",)] = True would unlock the image in your example.

This said, it's not an easy to use approach, and your use of scene from the console is way easier and faster if you've to do it manually.
 

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,490
7,035
There's one, renpy.game.persistent._seen_images[("NAME_OF_THE_IMAGE",)] = True.
Therefore, renpy.game.persistent._seen_images[("ch0b_12",)] = True would unlock the image in your example.

This said, it's not an easy to use approach, and your use of scene from the console is way easier and faster if you've to do it manually.
Awesome! Thanks for that! In retrospect, of course, there had to be a way of doing it, I had just never gone deep enough into the Ren'py code to figure it out. You, on the other hand, seem to have explored pretty much all the nooks and crannies...
 

Meushi

Well-Known Member
Aug 4, 2017
1,146
12,727
There's no direct way to edit the "what images have been seen" information that Ren'py keeps. At least, if there is, I'm not aware of it.
There's one, renpy.game.persistent._seen_images[("NAME_OF_THE_IMAGE",)] = True.
Therefore, renpy.game.persistent._seen_images[("ch0b_12",)] = True would unlock the image in your example.
Thank you very much, I was going spare trying to unlock a gallery which uses seen_image() conditions until I found this thread.

Then spent ages trying to work out why renpy.game.persistent._seen_images[("name of image",)] = True wouldn't work...

For any other python plebs like me who stumble across this, the round brackets & comma are because when Ren'Py stores the image name, it first does a split() [replaces spaces with commas] then transforms the list that outputs into a tuple (the brackets).

So works when use: renpy.game.persistent._seen_images[tuple("name of image".split())] = True

Which is a painful way to unlock one image, but is handy when iterating over a list of images.
 
  • Like
Reactions: masterdragonson

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,365
15,281
For any other python plebs like me who stumble across this, the round brackets & comma are because when Ren'Py stores the image name, it first does a split() [replaces spaces with commas] then transforms the list that outputs into a tuple (the brackets).
Oops. I don't remembered why it used a tuple, and since it worked when I tried it, to be sure that my memory was correct, didn't payed attention to the fact that the name of the image was a single word ('_' are seen as being a letter). What falsely lead me to the syntax used for screen, that is ( "name of the screen", ( "layer used to display it" ).
Therefore, I goes, for ( "name of the image", "Nothing because there's no layers for images" ).

Thanks for the correction.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,365
15,281
Of course, from inside the game, from the console, you could just type:
Code:
show name_of_image
And I remember that the show statement also accept an expression as argument. Therefore, outside of the console it's possible to use something like :
Python:
label whatever:
    $ lst = [ "someImage", "and some other" ]
    while lst:
        # If put directly as expression, /pop()/ would have no effect.
        $ img = lst.pop()
        scene expression img
        # Be nice with the memory.
        hide expression img
or even like :
Code:
label whatever:
    $ i = 1
    while i < 11:
        scene expression "myImage_{}".format( i )
        hide expression "myImage_{}".format( i )
        $ i += 1
 

Lune44

Member
Apr 9, 2019
338
263
However, not all galleries are implemented this way. The gallery code I use, for example, relies on some Ren'py functionality that automatically tracks which images have been displayed to the user and which have not, so that I don't have to manage my own persistent variables for that. If you were to look inside one of my games, you'd see something like:
Code:
    image_gallery.button("slot01")
    image_gallery.condition('renpy.seen_image("ch0b_12")')
    image_gallery.unlock_image("ch0b_09")
    image_gallery.unlock_image("ch0b_10")
    image_gallery.unlock_image("ch0b_11_movie")
    image_gallery.unlock_image("ch0b_12")
renpy.seen_image is a function provided by Ren'py that will return True if the image referenced, (ch0b_12 in this case, has been seen by the player during the course of any game. In addition, the unlock_image sets the gallery up so that the gallery will only show an image if it's been seen by the user.

This bit you mention here, does this look like a similar method to you?

Code:
def unlock (a):
        if persistent.images is None:
            persistent.images = {}
        if not (a in persistent.images):
            persistent.images[a] = True
            renpy.save_persistent()
I'm trying to unlock the galleries in the "Do You Like Horny Bunnies 1 & 2 Remaster" games and am kinda a newb with ren'py
I also found this in the source code as well:

Code:
    if persistent.hide_top_menu_extra is None:
        persistent.hide_top_menu_extra = False
However, changing that in console only shows said "extra" button on main menu (which is where the gallery is located) for a split second and then it disappears again.
 

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,490
7,035
This bit you mention here, does this look like a similar method to you?

Code:
def unlock (a):
        if persistent.images is None:
            persistent.images = {}
        if not (a in persistent.images):
            persistent.images[a] = True
            renpy.save_persistent()
I'm trying to unlock the galleries in the "Do You Like Horny Bunnies 1 & 2 Remaster" games and am kinda a newb with ren'py
I also found this in the source code as well:

Code:
    if persistent.hide_top_menu_extra is None:
        persistent.hide_top_menu_extra = False
However, changing that in console only shows said "extra" button on main menu (which is where the gallery is located) for a split second and then it disappears again.
So, that first block of code works like this.
  1. The persistent object is "special," in that, unlike traditional Python objects, any member of it will always return a value, even if the value has never been set.
  2. So the test persistent.images is basically saying "if I've never set an image into this before" ("is None"), then make persistent.images be an empty dictionary. The idea is that you want a persistent dictionary, but you don't want to overwrite it if you've already set it up.
  3. Dictionaries, as you might know, map keys to values. if not (a in persistent.images) is testing to see if the key passed in is already in the dictionary. If it isn't, it adds the key, then saves the persistent data.
So, presumably, scattered around through the code are lines like:
Code:
$ unlock("some_image_name")
which unlocks that image.

Elsewhere, in the gallery code itself, there are probably then lines that test to see if an image is unlocked.

So, basically, you have two potential approaches:
  1. You could write some Python code that would call "unlock" for every image in the gallery.
  2. You could modify the gallery code so that the "is it unlocked" test always succeeded, whether or not the "unlock" function had actually been called.
The first approach requires you to find all the values that "unlock" gets called with, of course, but you can probably figure that out from the gallery code. The second approach requires you to replace the existing gallery code with some modified gallery code.
 

Lune44

Member
Apr 9, 2019
338
263
So, that first block of code works like this.
  1. The persistent object is "special," in that, unlike traditional Python objects, any member of it will always return a value, even if the value has never been set.
  2. So the test persistent.images is basically saying "if I've never set an image into this before" ("is None"), then make persistent.images be an empty dictionary. The idea is that you want a persistent dictionary, but you don't want to overwrite it if you've already set it up.
  3. Dictionaries, as you might know, map keys to values. if not (a in persistent.images) is testing to see if the key passed in is already in the dictionary. If it isn't, it adds the key, then saves the persistent data.
So, presumably, scattered around through the code are lines like:
Code:
$ unlock("some_image_name")
which unlocks that image.

Elsewhere, in the gallery code itself, there are probably then lines that test to see if an image is unlocked.

So, basically, you have two potential approaches:
  1. You could write some Python code that would call "unlock" for every image in the gallery.
  2. You could modify the gallery code so that the "is it unlocked" test always succeeded, whether or not the "unlock" function had actually been called.
The first approach requires you to find all the values that "unlock" gets called with, of course, but you can probably figure that out from the gallery code. The second approach requires you to replace the existing gallery code with some modified gallery code.
Okay, thanks! I'll play around with it and see what I can find.