Ren'Py Password for the replays gallery

WetMelonPlay

Member
Game Developer
Dec 13, 2021
112
388
Hey, everybody. I searched for an answer on the forum but never found one. Maybe I'm just too dumb in programming and couldn't add 2+2.

I took the code for the replays gallery from here - ,

Thank you very much, BadMustard

Purpose: I want to make a password to unlock the gallery. And encrypt it.

Any help would be appreciated.


Code:
## Replay Gallery screen ######################################
## Take care when making changes to this file #################
default persistent.unlocked = False
screen Replayexit():
    zorder 100
    imagebutton:
        auto "images/replay/exit_%s.png"
        action EndReplay()
        yalign .99
        xalign .99
            #yes AUTO create 2 images titled exit_hover.png and exit_idle.png
#add these 2 line below if you want an exit button during the replay(optional)
#this must be added after every label used for replay
#if _in_replay:
#        show screen Replayexit

screen replay_gallery():

    tag menu
    add "black"

    $start = replay_page * 9
    $end = min(start + 9 - 1, len(Replay_items) - 1)

    #grid for images
    grid 3 3:
        xfill True
        yfill True

        for i in range(start, end + 1):
            if renpy.seen_label(Replay_items[i].replay) or persistent.unlocked:
                imagebutton idle Replay_items[i].thumbs:
                    style "replay_button" #delete this line to remove hover
                    action Replay(Replay_items[i].replay, locked=False)
                    xalign 0.5
                    yalign 0.5
            else:
                vbox xalign 0.5 yalign 0.5:
                    image "replay_locked"
                    #image "lockedthumb" #if you're using the gallery use the same image (optional)
        #required to fill in empty grid items (do not change)
        for i in range(end - start + 1, 9):
            null

    #grid for info
    grid 3 3:
        xfill True
        yfill True
        for i in range(start, end + 1):
            if renpy.seen_label(Replay_items[i].replay) or persistent.unlocked:
                hbox:
                    spacing maxthumbx - 20
                    xalign 0.5
                    yalign 0.1
                    text Replay_items[i].name
            else:
                null
        #required to fill in empty grid items (do not change)
        for i in range(end - start + 1, 9):
            null

    #previous/next buttons
    if replay_page > 0:
        textbutton "{color=#fff}Previous{/color}":
            action SetVariable("replay_page", replay_page - 1)
            xalign 0.1
            yalign 0.98
            background "#000"
    if (replay_page + 1) * 9 < len(Replay_items):
        textbutton "{color=#fff}Next{/color}":
            action SetVariable("replay_page", replay_page + 1)
            xalign 0.9
            yalign 0.98
            background "#000"
    #return button
    textbutton "{color=#fff}Return{/color}":
        action Return()
        xalign 0.5
        yalign 0.98
        background "#000"

    if persistent.unlocked:
        textbutton _("{color=#000}Lock{/color}") action ToggleVariable("persistent.unlocked") xalign 0.5 yalign 0.912 background "#fff"
    else:
        textbutton _("{color=#fff}Unlock{/color}") action ToggleVariable("persistent.unlocked") xalign 0.5 yalign 0.912 background "#000"
        
style replay_button:
    hover_background "images/gallery/thumbs/hover.png"
 

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,111
3,387
There is no way to make a truly secure password function in renpy.

Unless you do some deep hacking on the core renpy loader to inject your own RPA encryption, the game scripts are trivially unpackable and viewable using tools like unren_for_all. Additionally, the script loader allows simple overriding of any label or function in the game script, so once someone finds the way to do it, the file to unlock can be easily distributed and just dropped into the game folder even without unpacking the game.

Now if you just want to put a basic password on a gallery, then an approach would be:

1. create a label for "gallery_lock_check"

This label will check a persistent "gallery_unlocked" variable.
If "gallery_unlocked" is already true, show the replay screen.
if it is false, then show the gallery_unlockscreen, and check the screen return value matches your password. If it is a match, set the "gallery_unlocked" value to true and jump to the gallery_lock_check label again. note the password is in plain text in this label code.

2. create a screen gallery_unlock

this has whatever prompt text you want and a text input for the password

3. add a gallery button to your main menu (or wherever) that start the game at your gallery_lock_check label


Here's some simple sample code:
Code:
label gallery_unlock_check:

    if persistent.gallery_unlocked:
        jump gallery
    else:
        call screen gallery_unlock()

    if _return == "secret":
        $ persistent.gallery_unlocked = True
        jump gallery_unlock_check
    else:
        "Wrong password!"

    return

label gallery:
    "your gallery code goes here - call a screen if that's how it is implemented"
    return

screen gallery_unlock():
    tag input_screen
    modal True

    frame:
        xycenter (0.5, 0.5)
        xsize 600
        vbox:
            text "Enter Gallery Password:"
            frame:
                background Solid("#000000")
                xminimum 400 xmaximum 400
                input:
                    length 20
                    size 45
                    color "#c00000"
You also need to link to the gallery from somewhere, like the main menu, which is in "screens.rpy". In my fresh renpy 8 install, it's around line 290.
Code:
#<snip>
        textbutton _("Load") action ShowMenu("load")
        textbutton _("Preferences") action ShowMenu("preferences")

        # add this line
        textbutton _("Gallery") action Start("gallery_unlock_check")

        if _in_replay:
            textbutton _("End Replay") action EndReplay(confirm=True)
        elif not main_menu:
            textbutton _("Main Menu") action MainMenu()
        textbutton _("About") action ShowMenu("about")
 
Last edited:
  • Like
Reactions: peterppp

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,558
2,174
I agree with osanaiko. There's no such thing as a secure password in RenPy.

A while ago, I wrote a RenPy example where someone could create an RSA encrypted password and use the python library to decrypt it easily enough. As secure as you could want it (as much as anything is secure, when and tools like exist).

Except, to bypass it all someone had to do was edit the RenPy script to remove the check entirely or swap the logic to continue if the password was wrong rather than correct. Simplicity itself.
Even if I worked around that, there's still the reality that the 2nd post in the game thread on sites like F95 would likely be "What's the password?" - followed shortly by someone else who will reply with the answer.

I'd question why you want to add a password?
There was a trend a few years ago where developers would disable rollback in their games "to make choices permanent and more meaningful". Thankfully it didn't last too long. But all it did was piss off players. So I'd simply question why you want to make life more difficult for your players? Though perhaps you have a perfectly good reason I haven't thought of.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,129
14,806
There is no way to make a truly secure password function in renpy.

Unless you do some deep hacking on the core renpy loader to inject your own RPA encryption, the game scripts are trivially unpackable and viewable using tools like unren_for_all.
Even if you do this the RPA will still be unpackable with less than a dozen line of code.
Ren'Py unpack them, and for this it need its own function. So, you just need a RPY file with a code that will use those function to access the file content and save it on the disk.



Additionally, the script loader allows simple overriding of any label or function in the game script, so once someone finds the way to do it, the file to unlock can be easily distributed and just dropped into the game folder even without unpacking the game.
Except, to bypass it all someone had to do was edit the RenPy script to remove the check entirely or swap the logic to continue if the password was wrong rather than correct. Simplicity itself.
And this is the main issue.

Whatever the strength of the password, and whatever how complex and obfuscated the code behind it will be, there will always be a way.


Be noted that what you both said is the hardest way, there's a way more basic one:

Players would quickly be annoyed if they have to enter the password every single time. Therefore, there will be a flag turn to True, or whatever, somewhere in the game. It suffice to change its value yourself, without having to care about what is the password and where it is asked.
 

WetMelonPlay

Member
Game Developer
Dec 13, 2021
112
388
There is no way to make a truly secure password function in renpy.

Unless you do some deep hacking on the core renpy loader to inject your own RPA encryption, the game scripts are trivially unpackable and viewable using tools like unren_for_all. Additionally, the script loader allows simple overriding of any label or function in the game script, so once someone finds the way to do it, the file to unlock can be easily distributed and just dropped into the game folder even without unpacking the game.

Now if you just want to put a basic password on a gallery, then an approach would be:

1. create a label for "gallery_lock_check"

This label will check a persistent "gallery_unlocked" variable.
If "gallery_unlocked" is already true, show the replay screen.
if it is false, then show the gallery_unlockscreen, and check the screen return value matches your password. If it is a match, set the "gallery_unlocked" value to true and jump to the gallery_lock_check label again. note the password is in plain text in this label code.

2. create a screen gallery_unlock

this has whatever prompt text you want and a text input for the password

3. add a gallery button to your main menu (or wherever) that start the game at your gallery_lock_check label

I seem to be starting to understand, but not really yet.

I want to make a password request specifically for the "Unlock" button.

I'd question why you want to add a password?
There was a trend a few years ago where developers would disable rollback in their games "to make choices permanent and more meaningful". Thankfully it didn't last too long. But all it did was piss off players. So I'd simply question why you want to make life more difficult for your players? Though perhaps you have a perfectly good reason I haven't thought of.

I'd like to only give out the password to my top patrons. If there is an easy way to encrypt it I'd be happy, if not, that's fine.
Hypothetically, if I create a script0.rpy file with a gallery in it and the variable responsible for the password is named $ den_romance2, 80% of people won't climb further into the files.
As for the information on the forum, in general I don't care if they will leak the password here or not. First of all I want to make a bonus for my patrons.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,129
14,806
First of all I want to make a bonus for my patrons.
Then don't waste your time with a password.

Put everything related to the gallery into a dedicated RPY file, that you'll only include in the Patreon version you'll present to the patrons who've the right tiers. And add something like:
Python:
        if renpy.loadable( "gallery.rpy" ):
            textbutton _("Gallery") action ShowMenu("gallery")
in the screen named "navigation".

It's what 95% of the creators are doing.
 

WetMelonPlay

Member
Game Developer
Dec 13, 2021
112
388
Then don't waste your time with a password.

Put everything related to the gallery into a dedicated RPY file, that you'll only include in the Patreon version you'll present to the patrons who've the right tiers. And add something like:
Python:
        if renpy.loadable( "gallery.rpy" ):
            textbutton _("Gallery") action ShowMenu("gallery")
in the screen named "navigation".

It's what 95% of the creators are doing.
Yes, but I wouldn't want to have different versions. That's why I'm looking for a password solution. I'm sure it's done simply enough. But alas my knowledge is lacking.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,558
2,174
Yes, but I wouldn't want to have different versions. [...] I'm sure it's done simply enough. But alas my knowledge is lacking.

It's much simpler than you might imagine.

See:

As an example:

Python:
# --- within file: options.rpy ---

    build.classify('**~', None)
    build.classify("**.rpy", None)
    build.classify('**.bak', None)
    build.classify('**.psd', None)
    build.classify('**/.**', None)
    build.classify('**/#**', None)
    build.classify('**/thumbs.db', None)

    build.documentation('*.html')
    build.documentation('*.txt')

    # Declare four archives.
    build.archive("scripts", "all")
    build.archive("images", "all")
    build.archive("sounds", "all")
    build.archive("patreon", "bonus")

    # Put bonus content into the Patreon archive.
    build.classify("game/bonus/**", "patreon")
    build.classify("game/images/bonus/**", "patreon")

    # Put script files into the scripts archive.
    build.classify("game/**.ttf", "scripts")
    build.classify("game/**.rpyc", "scripts")

    # Put images into the images archive.
    build.classify("game/**.jpg", "images")
    build.classify("game/**.png", "images")
    build.classify("game/**.avi", "images")
    build.classify("game/**.mp4", "images")
    build.classify("game/**.webp", "images")
    build.classify("game/**.webm", "images")

    # Put sounds into the sounds archive.
    build.classify("game/**.ogg", "sounds")
    build.classify("game/**.wav", "sounds")
    build.classify("game/**.mp3", "sounds")

    build.package("patreon-pc", "zip", "windows linux renpy all bonus", "(Patreon) PC: Windows and Linux")
    build.package("patreon-mac", "zip", "mac renpy all bonus", "(Patreon) Macintosh")

    build.include_i686 = False

# Just copy-paste this into your options.rpy file, to replace the similar lines already in that file.

For this to work, you need only put your "Patreon only" content within the /game/bonus/ (scripts) and /game/images/bonus/ (images) sub-folders. Those folders won't be included in the "vanilla" version of the game (as long as you copy/paste the code from above).

When you build your game from the RenPy launcher, you'll get two extra options marked "(Patreon) ...". Only the Patreon builds will include the patreon.rpa file - which in turn only contains stuff from the two bonus folders I have assumed will contain the images and script files for your Patreon only content.

Combine that with the if renpy.loadable( "filename.rpyc" ): type checks... and you're all sorted.
I would recommend checking for rpyc files, since those are what RenPy "runs". .rpa and .rpy files need not be present for your game to run. Images are another good one to check, since they are version independent.

Edit: And in practical terms, about 2 hours after you post your "Patreon only" version for your patreons... that exact same version will turn up here on F95 and other sites.

And yes, sorry, none of that answers your password question.
 
Last edited:

WetMelonPlay

Member
Game Developer
Dec 13, 2021
112
388
There is no way to make a truly secure password function in renpy.

Unless you do some deep hacking on the core renpy loader to inject your own RPA encryption, the game scripts are trivially unpackable and viewable using tools like unren_for_all. Additionally, the script loader allows simple overriding of any label or function in the game script, so once someone finds the way to do it, the file to unlock can be easily distributed and just dropped into the game folder even without unpacking the game.

Now if you just want to put a basic password on a gallery, then an approach would be:

1. create a label for "gallery_lock_check"

This label will check a persistent "gallery_unlocked" variable.
If "gallery_unlocked" is already true, show the replay screen.
if it is false, then show the gallery_unlockscreen, and check the screen return value matches your password. If it is a match, set the "gallery_unlocked" value to true and jump to the gallery_lock_check label again. note the password is in plain text in this label code.

2. create a screen gallery_unlock

this has whatever prompt text you want and a text input for the password

3. add a gallery button to your main menu (or wherever) that start the game at your gallery_lock_check label


Here's some simple sample code:
Code:
label gallery_unlock_check:

    if persistent.gallery_unlocked:
        jump gallery
    else:
        call screen gallery_unlock()

    if _return == "secret":
        $ persistent.gallery_unlocked = True
        jump gallery_unlock_check
    else:
        "Wrong password!"

    return

label gallery:
    "your gallery code goes here - call a screen if that's how it is implemented"
    return

screen gallery_unlock():
    tag input_screen
    modal True

    frame:
        xycenter (0.5, 0.5)
        xsize 600
        vbox:
            text "Enter Gallery Password:"
            frame:
                background Solid("#000000")
                xminimum 400 xmaximum 400
                input:
                    length 20
                    size 45
                    color "#c00000"
You also need to link to the gallery from somewhere, like the main menu, which is in "screens.rpy". In my fresh renpy 8 install, it's around line 290.
Code:
#<snip>
        textbutton _("Load") action ShowMenu("load")
        textbutton _("Preferences") action ShowMenu("preferences")

        # add this line
        textbutton _("Gallery") action Start("gallery_unlock_check")

        if _in_replay:
            textbutton _("End Replay") action EndReplay(confirm=True)
        elif not main_menu:
            textbutton _("Main Menu") action MainMenu()
        textbutton _("About") action ShowMenu("about")
To be honest, I still haven't figured out how to put it here.
Code:
if persistent.unlocked:
        textbutton _("{color=#000}Lock{/color}") action ToggleVariable("persistent.unlocked") xalign 0.5 yalign 0.912 background "#fff"
    else:
        textbutton _("{color=#fff}Unlock{/color}") action ToggleVariable("persistent.unlocked") xalign 0.5 yalign 0.912 background "#000"
 

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,111
3,387
I think that is a button to re-lock (or re-unlock) the gallery. It would go on your gallery screen, so it can only be accessed if you already know the gallery password.

I don't know why you would want that, to be honest. How often would a player want to re-lock their unlocked gallery?

BTW if you really want to use it with my simple example code, all three of the persistent.unlocked should change to persistent.gallery_unlocked
 

WetMelonPlay

Member
Game Developer
Dec 13, 2021
112
388
I think that is a button to re-lock (or re-unlock) the gallery. It would go on your gallery screen, so it can only be accessed if you already know the gallery password.

I don't know why you would want that, to be honest. How often would a player want to re-lock their unlocked gallery?

BTW if you really want to use it with my simple example code, all three of the persistent.unlocked should change to persistent.gallery_unlocked
I just wanted to make a password for the unlock button. So that when you click on it, a window with password input would pop up.

I'll remove the "Lock" button.
 

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,111
3,387
okay, so if i understand this:

1. you will have a link to a gallery screen somewhere (main menu?) that is always available to players

2. the contents on the gallery screen will all be locked unless either the persistent "unlocked" flag is True OR the player has seen the event in question

3. there is a button on the gallery screen "unlock". when pressed, it should open a password input overlay screen

4. if the correct password is input then the persistent unlocked flag is set to True. if it is wrong, then a "wrong password try again" or similar message should be shown.
 

WetMelonPlay

Member
Game Developer
Dec 13, 2021
112
388
okay, so if i understand this:

1. you will have a link to a gallery screen somewhere (main menu?) that is always available to players

2. the contents on the gallery screen will all be locked unless either the persistent "unlocked" flag is True OR the player has seen the event in question

3. there is a button on the gallery screen "unlock". when pressed, it should open a password input overlay screen

4. if the correct password is input then the persistent unlocked flag is set to True. if it is wrong, then a "wrong password try again" or similar message should be shown.
Yes, it is in the main menu.
 

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,111
3,387
Here's a working example. You will need to change the label called by the Gallery button in the main menu to "gallery":

If you appreciate my help put my name in your game credits :poop:

Code:
label gallery:
    call screen gallery()
    return

label gallery_unlock_check:

    $ error_text = ""

    label .gallery_unlock_jump:
    call screen gallery_unlock(error_text)

    if _return == "secret":
        $ persistent.gallery_unlocked = True
        jump gallery
    elif _return == "user_gave_up":
        jump gallery
    else:
        $ error_text = "Wrong password!"
        jump .gallery_unlock_jump

screen gallery():
    frame:
        xycenter (0.5, 0.5)
        xsize 0.9
        ysize 0.9
        vbox:
            xfill True
            yfill True
            text "gallery code in here"
            text "Each image should have two conditions either of which will allow it to be shown:\n *gallery_unlocked* OR (the specific image's persistent flag or *seen_label* statement)"

            if not persistent.gallery_unlocked:
                frame:
                    xalign 0.5
                    textbutton "Unlock gallery" text_align 0.5:
                        action Call("gallery_unlock_check")
            frame:
                xalign 0.5
                textbutton "Return" text_align 0.5:
                    action Return()

screen gallery_unlock(message):
    tag input_screen
    modal True
    default password = ""
    default input_val = ScreenVariableInputValue("password")
    frame:
        xycenter (0.5, 0.5)
        xsize 600
        vbox:
            text "Enter Gallery Password:"
            frame:
                background Solid("#000000")
                xminimum 400 xmaximum 400
                hbox:
                    input:
                        value input_val
                        length 20
                        size 45
                        color "#c00000"
            if message:
                text "[message]"
            frame:
                textbutton "Submit" text_align 0.5:
                    action Return(password)
            frame:
                textbutton "Give up" text_align 0.5:
                    action Return("user_gave_up")
 

WetMelonPlay

Member
Game Developer
Dec 13, 2021
112
388
Here's a working example. You will need to change the label called by the Gallery button in the main menu to "gallery":

If you appreciate my help put my name in your game credits :poop:

Code:
label gallery:
    call screen gallery()
    return

label gallery_unlock_check:

    $ error_text = ""

    label .gallery_unlock_jump:
    call screen gallery_unlock(error_text)

    if _return == "secret":
        $ persistent.gallery_unlocked = True
        jump gallery
    elif _return == "user_gave_up":
        jump gallery
    else:
        $ error_text = "Wrong password!"
        jump .gallery_unlock_jump

screen gallery():
    frame:
        xycenter (0.5, 0.5)
        xsize 0.9
        ysize 0.9
        vbox:
            xfill True
            yfill True
            text "gallery code in here"
            text "Each image should have two conditions either of which will allow it to be shown:\n *gallery_unlocked* OR (the specific image's persistent flag or *seen_label* statement)"

            if not persistent.gallery_unlocked:
                frame:
                    xalign 0.5
                    textbutton "Unlock gallery" text_align 0.5:
                        action Call("gallery_unlock_check")
            frame:
                xalign 0.5
                textbutton "Return" text_align 0.5:
                    action Return()

screen gallery_unlock(message):
    tag input_screen
    modal True
    default password = ""
    default input_val = ScreenVariableInputValue("password")
    frame:
        xycenter (0.5, 0.5)
        xsize 600
        vbox:
            text "Enter Gallery Password:"
            frame:
                background Solid("#000000")
                xminimum 400 xmaximum 400
                hbox:
                    input:
                        value input_val
                        length 20
                        size 45
                        color "#c00000"
            if message:
                text "[message]"
            frame:
                textbutton "Submit" text_align 0.5:
                    action Return(password)
            frame:
                textbutton "Give up" text_align 0.5:
                    action Return("user_gave_up")
No problem. I'll try your code first.