Quest Godot Encryption Part Deux

Godot Version

4.4.1

Question

Has anyone encountered Quest approval for a 3D game using encryption and getting dinged for the >4 second load time?

So previously I solved the bugs/issues with encryption in Godot specifically for the Quest: Encryption on Quest

Recently I sought approval for my game in the Quest store. I got dinged because in using encryption it caused the load time of the Engine (and therefore my loading page) to take 8-10 seconds (obb was 400mb).

The way the engine boots up, if you use the expansion pack, you can’t render ANYTHING in Godot until this boot sequence is completed. Since all files are placed into an external OBB file which needs to be decrypted, the Engine cannot initialize yet.

Those fields which ostensibly seem like you can make exceptions for certain files to remain in the APK do not work as some might think (i.e. if you make decryption exceptions, they still go into the obb, but are not encrypted, wohwah).

I thought perhaps I could use an Android-specific loading page in the Android template. I am quite familiar with coding Android apps so I proceeded to add a little loading page. I swiftly realized that because the app is in immersive mode these do not work and are simply ignored or cause crashes.

I thought, “hey, let’s try writing frames directly to the Vulkan API to create a gimpy loading page while Godot does its thing.” It seems that you cannot touch Vulkan. Quest Vulkan is unavailable until Android creates the native window and Meta/OpenXR owns the surface, so boot-time Vulkan calls are invalid by definition and do some pretty weird things to the quest immersive home or crash. The only place I found that I can eventually screw with Vulkan is at the end of Engine init which defeats the purpose.

The last option was to refactor the boot sequence. This is what I did.

1–I first created a new option under encryption which endures in each preset config–Use Boot PCK.

2–Then I refactored a project putting all my autoloads into /boot/ and created a small loading page. I had to refactor all of my autoloads so that ready() sets process to false and created an init_autoload() which I can invoke later. I set my initial page in the project settings to point to this loading page.

3–I refactored the encryption process. If this boot preset is selected, I ignore /boot/ files in the main obb as well as the project.binary and create an encrypted boot.pck which is placed at /assets/boot.pck. (Unrelated, but I also made it so that if vr_splash.png is found in /boot/ it moves it to /assets/vr_splash.png–this is needed by Meta/Quest to show by default as soon as you launch the app as your default splash image. I had created a shell script to take the APK, unzip, add the vr_splash and zip it back up and sign it, not fun, since Godot by default deletes everything in /assets/ when creating the apk).

4–I changed the Android template so that if /assets/boot.pck is found it copies the encrypted file over to a real location on the quest (this is necessary in order to open the file normally so you can decrypt using the default decryption routine). Then I pass it to the quest startup as the main-pack.

5–At this point the Engine loads with this small boot.pck and displays the loading page. This happens consistently in 2-3 seconds. Then the decryption of the big fat obb happens like normal.

6–I created two JNI bridge functions: one to report decryption progress and another to signal when decryption has completed. I added these guys to my loading page gdscript to poll. I can now display accurate progress (progress is marked by a percentage of the files processed in the big fat obb), and when decryption is complete, I invoke my autoload init functions and switch to my main scene without a hitch.

So for anyone who wants to ship on the quest and use decryption, you may run into this problem. I’ve found that roughly 100mb = 1 second of additional load time for the engine (if decrypting an obb and not using a boot.pck).

If you don’t wish to go down this route, you could also do something silly. You can add android widget toasts in your startup process to get past the Quest 4 second rule. I’ve found Toasts are the only things which seem to render on the Android side (use all caps and no symbols–Quest’s system toast font and text renderer are optimized for uppercase ASCII, while lowercase glyphs and symbols could fall back to inconsistent fonts or code paths). This is how I initially got approved on Quest, but it is ugly and amateur-looking, but works, if the approver is in a good mood. :stuck_out_tongue:

2 Likes

I know very very few people care, but I had to share my progress just in case someone searches for encryption in the forum.

I got my huge main game working with this paradigm (haven’t touched it in a few months) and it has a 1.3gig expansion pack. Time to my loading page is solid at 3.1 seconds (this doesn’t seem to vary at all) which is perfect for Quest approval. I don’t think I can shave off any seconds from this unless I jump up and down and scream “hurry.”

First log which prints as soon as the game launches

12-31 13:26:26.029 10299 10299 V WTF     : GODOTAPP in on create for godot

A log in my _ready() at the time of my loading scene rendering.

12-31 13:26:29.207 10299 10355 I godot   : WTF102 about to call main pack decryption

I have been up and down the godot engine initialization to get this working. For anyone who does care or wonder how this can work with the Godot engine. My boot.pck by necessity has to load all global classes, gdscripts, autoloads and dependencies, but even storing all of the gd candy isn’t very large:

âžś  temp_game_apk du -hs assets/boot.pck
1.5M assets/boot.pck

I tried it without the gdscripts and used the refresh_global_class_list() from project_settings, but this caused very strange behavior so I just walk the game tree on boot.pck export and include all of the gdscripts.

A summary of the timing of the boot just in case anyone is curious where all that time (3.1 seconds) comes from (I didn’t want to share all of the logs, just major events in the process):

12-31 13:26:26.029 10299 10299 V WTF     : GODOTAPP in on create for godot
12-31 13:26:26.277 10299 10299 I WTF     : FRAGMENT performEngineInitialization() entered
12-31 13:26:26.546 10299 10299 V WTF     : Godot native layer initialization completed: true
12-31 13:26:26.548 10299 10299 E WTF     : WTF Main::setup entered
12-31 13:26:26.581 10299 10299 I WTF     : core/io/file_access_pack.cpp PackedSourcePCK::try_open_pack We are starting reading the boot pack
12-31 13:26:26.604 10299 10299 I WTF     : project_settings.cpp refreshing global classes
...a lot of files being loaded...
12-31 13:26:28.277 10299 10355 V WTF     : Main: Load Platforms
12-31 13:26:28.277 10299 10355 V WTF     : Main: Load Physics
12-31 13:26:28.300 10299 10355 V WTF     : Main: Done
12-31 13:26:29.051 10299 10355 I WTF     : MATCHED FILE 'boot/boot_init.tscn.remap' (inside pack)
12-31 13:26:29.110 10299 10355 V WTF     : OnGodotSetupCompleted
12-31 13:26:29.207 10299 10355 I godot   : WTF102 about to call main pack decryption
12-31 13:26:29.208 10299 10355 V WTF     : OnGodotMainLoopStarted

What a royal pain in the ass. All this to get a stupid loading scene with encryption.

One last thing, when I turned off all my debug logs, the load time is consistently at 2.2 seconds, noice.

1 Like