Gift: A Open-source Udon Decompiler
-
A Open-source Udon Decompiler
This is a opensource decompiler for Udon bytecode that attempts to reconstruct readable C#-like pseudocode, along with parsing public variables.
Repo: https://github.com/ParaN3xus/udon-decompiler
Docs: https://udon-decompiler.paran3x.us/Features
- Core functionialities
- Decompile Udon bytecode into readable C#-like pseudocode
- Parse and reconstruct public variables
- Unity-free decompilation workflow
After a one-time setup step (extracting Udon module information using Unity), the rest of the process no longer depends on Unity or the VRChat SDK
Everything can be done with two executables (<10MB total) - Some less documented features that are already usable:
- Udon disassembly output
- Modifying assembly and patching it back into the original program
Comparison with existing tools
There are a few existing open-source attempts at Udon decompilation:
However, both of these projects appear to have been inactive for quite some time.
I haven’t thoroughly evaluated UdonFlat’s output quality, but judging from its scope and codebase size, it likely does not aim for deeper reconstruction.
With UdonSharpRE, I’ve personally run into cases where it would completely hang on certain scripts, which might be related to less robust control flow handling. Even in cases where it does produce output, the structure and readability are generally not as strong as what this project is trying to achieve.
I’ve also had some discussions with authors of other (unreleased) decompilers, and while I can’t make definitive claims, from comparing output fragments it seems this decompiler performs better in certain scenarios.
Example 1 (Decompiling the FISH! world)
I will demonstrate the full workflow using the FISH! world:
- Assume the decompiler is already installed
- Obtain & decrypt world cache (or acquire
.vrcw), dump serialized programs & public variables

- Decompile programs and parse variables

Output (the
__data-dumpeddirectory):Example 2 (selected functions from
DataServerManager.csin the Massive Medal Pusher world)Comparison of the original asset, disassembly, and decompiled output of selected functions from
DataServerManager.csin the Massive Medal Pusher worldOriginal asset
It’s just a regular Unity asset YAML that contains serializedProgramCompressedBytes. It’s quite uninteresting, so it’s omitted here.Disassembly
entry__start: ; @ 0x00000000 PUSH __const_SystemUInt32_0 PUSH __gintnl_SystemUInt32_0 JUMP addr_0x00000138 ; call function_0(); addr_0x00000018: ; @ 0x00000018 PUSH __0__MedalTex__ret PUSH __lcl_t_SystemString_0 COPY ; __lcl_t_SystemString_0 = __0__MedalTex__ret; PUSH __const_SystemString_0 PUSH __const_SystemString_1 PUSH __lcl_t_SystemString_0 PUSH __const_SystemString_2 PUSH __lcl_s_SystemString_0 EXTERN heap_0x0000033F ; __lcl_s_SystemString_0 = System.String.Format(__const_SystemString_0, __const_SystemString_1, __lcl_t_SystemString_0, __const_SystemString_2); PUSH __gintnl_SystemUInt32_1 PUSH __const_SystemString_3 PUSH __1__intnlparam COPY ; __1__intnlparam = __const_SystemString_3; JUMP addr_0x00004838 ; call function_11(); addr_0x00000080: ; @ 0x00000080 PUSH __0__intnlparam PUSH userSecretL COPY ; userSecretL = __0__intnlparam; PUSH __gintnl_SystemUInt32_2 PUSH __intnl_VRCSDKBaseVRCPlayerApi_0 EXTERN heap_0x00000340 ; __intnl_VRCSDKBaseVRCPlayerApi_0 = VRC.SDKBase.Networking.LocalPlayer; PUSH __intnl_VRCSDKBaseVRCPlayerApi_0 PUSH __intnl_SystemString_0 EXTERN heap_0x00000341 ; __intnl_SystemString_0 = __intnl_VRCSDKBaseVRCPlayerApi_0.displayName; PUSH __lcl_s_SystemString_0 PUSH __0_s__param COPY ; __0_s__param = __lcl_s_SystemString_0; PUSH __intnl_SystemString_0 PUSH __0_userId__param COPY ; __0_userId__param = __intnl_SystemString_0; PUSH userSecretA PUSH __0_userSecretA__param COPY ; __0_userSecretA__param = userSecretA; JUMP addr_0x0000390C ; call function_9(); addr_0x00000108: ; @ 0x00000108 PUSH __0_userSecretA__param PUSH userSecretA COPY ; userSecretA = __0_userSecretA__param; PUSH __intnl_returnJump_SystemUInt32_0 COPY ; __intnl_returnJump_SystemUInt32_0 = __const_SystemUInt32_0; JUMP_INDIRECT __intnl_returnJump_SystemUInt32_0 PUSH __const_SystemUInt32_0 addr_0x00000138: ; @ 0x00000138 PUSH medalTex9999 PUSH __intnl_UnityEngineTransform_0 EXTERN heap_0x00000342 ; __intnl_UnityEngineTransform_0 = medalTex9999.transform; PUSH __intnl_UnityEngineTransform_0 PUSH __const_SystemType_0 PUSH __lcl_rend_UnityEngineRenderer_0 EXTERN heap_0x00000343 ; __lcl_rend_UnityEngineRenderer_0 = __intnl_UnityEngineTransform_0.GetComponent(__const_SystemType_0); PUSH __lcl_rend_UnityEngineRenderer_0 PUSH __intnl_UnityEngineMaterial_0 EXTERN heap_0x00000344 ; __intnl_UnityEngineMaterial_0 = __lcl_rend_UnityEngineRenderer_0.material; PUSH __intnl_UnityEngineMaterial_0 PUSH __lcl_col_UnityEngineColor_0 EXTERN heap_0x00000345 ; __lcl_col_UnityEngineColor_0 = __intnl_UnityEngineMaterial_0.color; PUSH __lcl_col_UnityEngineColor_0 PUSH __intnl_SystemSingle_0 EXTERN heap_0x00000346 ; __intnl_SystemSingle_0 = __lcl_col_UnityEngineColor_0.r; PUSH __intnl_SystemSingle_0 PUSH __const_SystemSingle_0 PUSH __intnl_SystemSingle_1 EXTERN heap_0x00000347 ; __intnl_SystemSingle_1 = __intnl_SystemSingle_0 * __const_SystemSingle_0; PUSH __intnl_SystemSingle_1 PUSH __intnl_SystemInt32_0 EXTERN heap_0x00000348 ; __intnl_SystemInt32_0 = UnityEngine.Mathf.RoundToInt(__intnl_SystemSingle_1); PUSH __intnl_SystemInt32_0 PUSH __const_SystemInt32_0 PUSH __const_SystemInt32_1 PUSH __lcl_r_SystemInt32_0 EXTERN heap_0x00000349 ; __lcl_r_SystemInt32_0 = UnityEngine.Mathf.Clamp(__intnl_SystemInt32_0, __const_SystemInt32_0, __const_SystemInt32_1); PUSH __lcl_col_UnityEngineColor_0 PUSH __intnl_SystemSingle_2 EXTERN heap_0x0000034A ; __intnl_SystemSingle_2 = __lcl_col_UnityEngineColor_0.g; PUSH __intnl_SystemSingle_2 PUSH __const_SystemSingle_0 PUSH __intnl_SystemSingle_3 EXTERN heap_0x00000347 ; __intnl_SystemSingle_3 = __intnl_SystemSingle_2 * __const_SystemSingle_0; PUSH __intnl_SystemSingle_3 PUSH __intnl_SystemInt32_1 EXTERN heap_0x00000348 ; __intnl_SystemInt32_1 = UnityEngine.Mathf.RoundToInt(__intnl_SystemSingle_3); PUSH __intnl_SystemInt32_1 PUSH __const_SystemInt32_0 PUSH __const_SystemInt32_1 PUSH __lcl_g_SystemInt32_0 EXTERN heap_0x00000349 ; __lcl_g_SystemInt32_0 = UnityEngine.Mathf.Clamp(__intnl_SystemInt32_1, __const_SystemInt32_0, __const_SystemInt32_1); PUSH __lcl_col_UnityEngineColor_0 PUSH __intnl_SystemSingle_4 EXTERN heap_0x0000034B ; __intnl_SystemSingle_4 = __lcl_col_UnityEngineColor_0.b; PUSH __intnl_SystemSingle_4 PUSH __const_SystemSingle_0 PUSH __intnl_SystemSingle_5 EXTERN heap_0x00000347 ; __intnl_SystemSingle_5 = __intnl_SystemSingle_4 * __const_SystemSingle_0; PUSH __intnl_SystemSingle_5 PUSH __intnl_SystemInt32_2 EXTERN heap_0x00000348 ; __intnl_SystemInt32_2 = UnityEngine.Mathf.RoundToInt(__intnl_SystemSingle_5); PUSH __intnl_SystemInt32_2 PUSH __const_SystemInt32_0 PUSH __const_SystemInt32_1 PUSH __lcl_b_SystemInt32_0 EXTERN heap_0x00000349 ; __lcl_b_SystemInt32_0 = UnityEngine.Mathf.Clamp(__intnl_SystemInt32_2, __const_SystemInt32_0, __const_SystemInt32_1); PUSH __const_SystemString_4 PUSH __lcl_r_SystemInt32_0 PUSH __lcl_g_SystemInt32_0 PUSH __lcl_b_SystemInt32_0 PUSH __0__MedalTex__ret EXTERN heap_0x0000033F ; __0__MedalTex__ret = System.String.Format(__const_SystemString_4, __lcl_r_SystemInt32_0, __lcl_g_SystemInt32_0, __lcl_b_SystemInt32_0); PUSH __intnl_returnJump_SystemUInt32_0 COPY JUMP_INDIRECT __intnl_returnJump_SystemUInt32_0 PUSH __intnl_returnJump_SystemUInt32_0 COPY JUMP_INDIRECT __intnl_returnJump_SystemUInt32_0Decompiled C#-like output (class definition and member variable declarations have been manually omitted)
public void _start() { System.String __lcl_t_SystemString_0 = null; System.String __lcl_s_SystemString_0 = null; System.String __intnl_SystemString_0 = null; function_0(); __lcl_t_SystemString_0 = __0__MedalTex__ret; __lcl_s_SystemString_0 = System.String.Format("{0}{1}{2}", "GodSh1lbe", __lcl_t_SystemString_0, "Shirbe334zako"); __1__intnlparam = "qWRuTqiyLYJUwwG44DdF537cPEyDPf"; function_11(); userSecretL = __0__intnlparam; __intnl_SystemString_0 = VRC.SDKBase.Networking.LocalPlayer.displayName; __0_s__param = __lcl_s_SystemString_0; __0_userId__param = __intnl_SystemString_0; __0_userSecretA__param = userSecretA; function_9(); userSecretA = __0_userSecretA__param; return; } void function_0() { UnityEngine.Renderer __lcl_rend_UnityEngineRenderer_0 = null; UnityEngine.Color __lcl_col_UnityEngineColor_0 = new UnityEngine.Color(0.0f, 0.0f, 0.0f, 0.0f); System.Int32 __lcl_r_SystemInt32_0 = 0; System.Int32 __lcl_g_SystemInt32_0 = 0; System.Int32 __lcl_b_SystemInt32_0 = 0; __lcl_rend_UnityEngineRenderer_0 = medalTex9999.transform.GetComponent( typeof(UnityEngine.Renderer) /* UnityEngine.CoreModule */); __lcl_col_UnityEngineColor_0 = __lcl_rend_UnityEngineRenderer_0.material.color; __lcl_r_SystemInt32_0 = UnityEngine.Mathf.Clamp( UnityEngine.Mathf.RoundToInt(__lcl_col_UnityEngineColor_0.r * 255.0f), 0, 255); __lcl_g_SystemInt32_0 = UnityEngine.Mathf.Clamp( UnityEngine.Mathf.RoundToInt(__lcl_col_UnityEngineColor_0.g * 255.0f), 0, 255); __lcl_b_SystemInt32_0 = UnityEngine.Mathf.Clamp( UnityEngine.Mathf.RoundToInt(__lcl_col_UnityEngineColor_0.b * 255.0f), 0, 255); __0__MedalTex__ret = System.String.Format("{0:D2}{1:D2}{2:D2}", __lcl_r_SystemInt32_0, __lcl_g_SystemInt32_0, __lcl_b_SystemInt32_0); return; }Disclaimer / Intended Use
This project is intended for learning and research, please respect original creators’ rights and follow VRChat's Terms of Service and applicable laws.
Project status
This project is still under active development, but it is already usable:
- Only nightly builds are currently available
- There are many unfinished todos
Reporting issues & contributing
If you encounter bugs or incorrect output, please report them following docs/user-en/reporting-issues
Contributions are very welcome:
- bug reports
- PRs / improvements
If you want to contribute to this project, check: docs/dev/status and docs/dev/contributing
- Core functionialities
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login