A skill for restoring readable C# from Unity IL2CPP binaries with IDA Pro MCP, Il2CppDumper output, DummyDll stubs, and stringliteral.json.
This is not a one-click decompiler. It helps an agent analyze user-provided VAs or function names and reconstruct C# while preserving strings, switch branches, lambdas, LINQ, async/coroutine state machines, and IL2CPP-specific quirks.
Output quality depends on the AI model and the available context. Restored code is for reference only and should be manually reviewed against IDA, DummyDll stubs, and runtime behavior.
- IDA Pro with IDA Pro MCP enabled
- Il2CppDumper output:
script.json,stringliteral.json, andDummyDll/ - Python 3 for bundled helper scripts
- Open the IL2CPP binary, usually
GameAssembly.dll, in IDA Pro and wait for initial analysis to finish. - Run Il2CppDumper's IDA processing script, usually
ida_with_struct_py3.py, inside IDA on the current database. - Open
DummyDll/Assembly-CSharp.dllin dnSpy and export it as a C# project. This gives the agent readable class, method, field, enum, generic, and[FieldOffset]stubs. - Keep
stringliteral.jsonavailable and provide its local path when string resolution is needed.
Install or copy this folder as a skill named il2cpp-to-csharp-skill, then ask the agent to restore one function at a time:
Use $il2cpp-to-csharp-skill to restore 0x180000000.
IDA Pro MCP is ready. The dnSpy-exported stub project from DummyDll/Assembly-CSharp.dll is at C:\path\to\DummyDllExport, and stringliteral.json is at C:\path\to\stringliteral.json.
IDA
// local variable allocation has failed, the output may be wrong!Cysharp_Threading_Tasks_UniTaskVoid_oResultScene_UI_ResultSceneSavePannel__OverrideSave(ResultScene_UI_ResultSceneSavePannel_o*this,int32_tindex,constMethodInfo*method){ResultScene_UI_ResultSceneSavePannel__OverrideSave_d__16_ostateMachine;// [rsp+20h] [rbp-38h] BYREFif(!byte_18440B475){sub_18033D260(&Method_Cysharp_Threading_Tasks_CompilerServices_AsyncUniTaskVoidMethodBuilder_Start_ResultSceneSavePannel__OverrideSave_d__16___,*(_QWORD*)&index);
byte_18440B475 =1;}
memset(&stateMachine.fields.__1__state+1,0,44);
sub_18033CD60(&stateMachine.fields.__t__builder,0LL);
stateMachine.fields.__4__this=this;
sub_18033CD60(&stateMachine.fields.__4__this,this);
stateMachine.fields.index=index;
stateMachine.fields.__1__state=-1;Cysharp_Threading_Tasks_CompilerServices_AsyncUniTaskVoidMethodBuilder__Start_ResultSceneSavePannel__OverrideSave_d__16_((Cysharp_Threading_Tasks_CompilerServices_AsyncUniTaskVoidMethodBuilder_o)&stateMachine.fields.__t__builder,&stateMachine,Method_Cysharp_Threading_Tasks_CompilerServices_AsyncUniTaskVoidMethodBuilder_Start_ResultSceneSavePannel__OverrideSave_d__16___);return0;}voidResultScene_UI_ResultSceneSavePannel__OverrideSave_d__16__MoveNext(ResultScene_UI_ResultSceneSavePannel__OverrideSave_d__16_o*this,constMethodInfo*method){struct ResultScene_UI_ResultSceneSavePannel_o *_4__this;// rdi
__int64 v4;// rdx
__int64 v5;// rcx
__int64 v6;// rdxstruct Cysharp_Threading_Tasks_UniTask_Awaiter_o v7;// xmm6
__int64 v8;// rdx
__int64 v9;// rcx
__int64 v10;// rsi
__int64 v11;// raxstruct Cysharp_Threading_Tasks_CompilerServices_IStateMachineRunner_o *runner;// r8
__int64 v13;// rax
Cysharp_Threading_Tasks_UniTask_o u__1;// [rsp+30h] [rbp-28h] BYREFif(!byte_18440BEC7 ){sub_18033D260(&Method_Cysharp_Threading_Tasks_CompilerServices_AsyncUniTaskVoidMethodBuilder_AwaitUnsafeOnCompleted_UniTask_Awaiter__ResultSceneSavePannel__OverrideSave_d__16___,method);
byte_18440BEC7 =1;}_4__this=this->fields.__4__this;if(!this->fields.__1__state){u__1=(Cysharp_Threading_Tasks_UniTask_o)this->fields.__u__1;this->fields.__u__1=0LL;this->fields.__1__state=-1;LABEL_5:sub_180406670(&u__1,0LL);if(!_4__this)sub_18033D420(v5,v4);DEYU_AdpUISystem_PanelCollection_UIPanelBaseImpl__ClosePanel((DEYU_AdpUISystem_PanelCollection_UIPanelBaseImpl_o*)_4__this,0LL);this->fields.__1__state=-2;sub_1804065D0(&this->fields.__t__builder,0LL);return;}
if (!_4__this)
sub_18033D420(this,method);
_4__this->fields.m_BlockSubPanelCommand=1;
DEYU_AdpUISystem_PanelCollection_UIPanelBaseImpl__HidePanel((DEYU_AdpUISystem_PanelCollection_UIPanelBaseImpl_o*)_4__this,0LL);
u__1 =*ResultScene_UI_ResultSceneSavePannel__SavePlayerDataCoreAsync(&u__1,_4__this,this->fields.index,0LL);
sub_18033CD60(&u__1,0LL);
v7 =(structCysharp_Threading_Tasks_UniTask_Awaiter_o)u__1;if(!byte_18440B3D8){
sub_18033D260(&Cysharp_Threading_Tasks_IUniTaskSource_TypeInfo,v6);
byte_18440B3D8 =1;}
if (!v7.fields.task.fields.source||(unsignedint)sub_180004110(0LL,Cysharp_Threading_Tasks_IUniTaskSource_TypeInfo,v7.fields.task.fields.source,(unsignedint)_mm_extract_epi16((__m128i)v7,4))){goto LABEL_5;}this->fields.__1__state=0;this->fields.__u__1=v7;sub_18033CD60(&this->fields.__u__1,0LL);v10=Method_Cysharp_Threading_Tasks_CompilerServices_AsyncUniTaskVoidMethodBuilder_AwaitUnsafeOnCompleted_UniTask_Awaiter__ResultSceneSavePannel__OverrideSave_d__16___;if(!byte_18440BF77){sub_18033D260(&Cysharp_Threading_Tasks_CompilerServices_IStateMachineRunner_TypeInfo,v8);byte_18440BF77=1;}if(!this->fields.__t__builder.fields.runner){v11=*(_QWORD*)(*(_QWORD*)(v10+56)+8LL);if((*(_BYTE*)(v11+306)&1)==0)v11=sub_180365730(*(_QWORD*)(*(_QWORD*)(v10+56)+8LL));if(!*(_DWORD*)(v11+224))
il2cpp_runtime_class_init(v11);(*(void(__fastcall**)(ResultScene_UI_ResultSceneSavePannel__OverrideSave_d__16_o*,structCysharp_Threading_Tasks_CompilerServices_AsyncUniTaskVoidMethodBuilder_o*,_QWORD))(**(_QWORD**)(v10 +56)+8LL))(this,&this->fields.__t__builder,**(_QWORD**)(v10+56));}runner=this->fields.__t__builder.fields.runner;if(!runner)
sub_18033D420(v9,v8);
v13 = sub_180002C50(0LL,Cysharp_Threading_Tasks_CompilerServices_IStateMachineRunner_TypeInfo,runner);
sub_180406800(&u__1,v13,*(_QWORD*)(*(_QWORD*)(v10+56)+24LL));}GPT 5.5 (Codex)
privateasyncUniTaskVoidOverrideSave(intindex){m_BlockSubPanelCommand=true;HidePanel();awaitSavePlayerDataCoreAsync(index);ClosePanel();}Opus 4.6 (Copilot)
privateasyncUniTaskVoidOverrideSave(intindex)// By Opus 4.7{this.m_BlockSubPanelCommand=true;HidePanel();awaitSavePlayerDataCoreAsync(index);ClosePanel();}mimo-v2.5-pro (Claude Code)
// ResultScene.UI.ResultSceneSavePannelpublicasyncUniTaskVoidOverrideSave(intindex){m_BlockSubPanelCommand=true;HidePanel();awaitSavePlayerDataCoreAsync(index);ClosePanel();}DeepSeek v4 pro (Claude Code) 1 retry
asyncUniTaskVoidOverrideSave(intindex){m_BlockSubPanelCommand=true;HidePanel();awaitSavePlayerDataCoreAsync(index);ClosePanel();}Gemini 3.1 Pro (Copilot)
privateasyncUniTaskVoidOverrideSave(intindex){this.m_BlockSubPanelCommand=true;base.HidePanel();awaitthis.SavePlayerDataCoreAsync(index);base.ClosePanel();}GPT 5.5 (Codex)
protectedoverrideIEnumeratorOnPositiveBuffExecute(SpellExecutionContextspellExecutionContext){List<int>targetSeats=EventManager.Instance.TargetTablesAny(1000).Except(EventManager.Instance.TargetTablesGuests(1000)).ToList();if(targetSeats.Count<=0)yieldbreak;List<NormalGuest>allNGuests=DataBaseCharacter.GetAllNormalGuests().Where(x =>/* __c.<OnPositiveBuffExecute>b__15_0, predicate not resolved by IDA MCP */true).ToList();GuestsManagerguestManager=GuestsManager.Instance;Vector3guestPosition=spellExecutionContext.GuestPosition.Value;Vector3tablePosition=spellExecutionContext.GuestsController.DeskCode==-1?default:GetGuestTable(spellExecutionContext.GuestsController.DeskCode);Vector3aliceHand=guestPosition+newVector3(tablePosition.x>guestPosition.x?handOffset.x:-handOffset.x,handOffset.y,0f);booldollEnd=false;PlayAudio(rewardSFX);foreach(intseatintargetSeats){GameObjectdoll=Instantiate(rewardDoll);doll.transform.position=aliceHand;UIElementClusteruiElementCluster=doll.GetComponent<UIElementCluster>();Vector3targetPosition=GetGuestTable(seat)+newVector3(0f,-0.75f,0f);uiElementCluster.GetObject<SpriteRenderer>(0).flipX=targetPosition.x>aliceHand.x;EventManager.Instance.StartCoroutine(SetLinenear(doll,uiElementCluster,seat));EventManager.Instance.StartCoroutine(LerpPosition(doll.transform,()=>targetPosition,1f));}yieldreturnUnityCoroutineExtension.OneSecondDelay;dollEnd=true;yieldreturnUnityCoroutineExtension.OneSecondDelay;IEnumeratorSetLinenear(GameObjectdoll,UIElementClusteruiElementCluster,intseat){LineRendererlineRenderer=uiElementCluster.GetObject<LineRenderer>(0);lineRenderer.SetPosition(1,aliceHand);while(!dollEnd){Vector3position=doll.transform.position;lineRenderer.SetPosition(0,position+newVector3(0f,0.5f,0f));yieldreturnnull;}Animatoranimator=doll.GetComponent<Animator>();if(EventManager.Instance.ShouldSkip){animator.Play("AliceRewardDoll2");yieldreturnUnityCoroutineExtension.HalfSecondDelay;}else{animator.Play("AliceRewardDoll1");SpriteMaskmask1=uiElementCluster.GetObject<SpriteMask>(0);SpriteMaskmask2=uiElementCluster.GetObject<SpriteMask>(1);NormalGuestguest1=allNGuests.RandomSelectOne();NormalGuestguest2=allNGuests.RandomSelectOne();SpriteRendererdollSprite=uiElementCluster.GetObject<SpriteRenderer>(0);mask1.sprite=guest1.CharacterPixel.m_AnimatedSprites[1];mask2.sprite=guest2.CharacterPixel.m_AnimatedSprites[1];yieldreturnUnityCoroutineExtension.QuarterSecondDelay;dollSprite.maskInteraction=SpriteMaskInteraction.VisibleInsideMask;yieldreturnUnityCoroutineExtension.HalfSecondDelay;guestManager.SpawnNormalGuestGroup(new[]{guest1,guest2},doll.transform.position,GuestGroupController.LeaveType.Fading,seat,false);yieldreturnUnityCoroutineExtension.QuarterSecondDelay;}Destroy(doll);}}Opus 4.7 (Copilot)
protectedoverrideIEnumeratorOnPositiveBuffExecute(SpellExecutionContextspellExecutionContext)// By Opus 4.7{// NOTE(By Opus 4.7): cached __c.<OnPositiveBuffExecute>b__15_0 - Where predicate body// not directly resolvable from IDA (Method$ pointer indirection); preserved as// a placeholder lambda. Heuristic: filters unsuitable guests for the reward duo.List<NormalGuest>allNGuests=DataBaseCharacter.GetAllNormalGuests().Where(g =>/* TODO(By Opus 4.7): unresolved __c.<OnPositiveBuffExecute>b__15_0 */true).ToList();GuestsManagerguestManager=MonoSingleton<GuestsManager>.Instance;Vector3guestPos=spellExecutionContext.GuestPosition.Value;floatxOffset;SpecialGuestsControllerguestsController=spellExecutionContext.GuestsController;if(guestsController.DeskCode==-1||GetGuestTable(guestsController.DeskCode).x>spellExecutionContext.GuestPosition.Value.x){xOffset=this.handOffset.x;}else{xOffset=-this.handOffset.x;}Vector3aliceHand=newVector3(guestPos.x+xOffset,guestPos.y+this.handOffset.y,guestPos.z+0f);booldollEnd=false;PlayAudio(this.rewardSFX);List<int>tablesToReward=EventManager.Instance.TargetTablesAny(1000).Except(EventManager.Instance.TargetTablesGuests(1000)).ToList();if(tablesToReward.Count<=0)yieldbreak;foreach(intseatintablesToReward){intcapturedSeat=seat;GameObjectdoll=Instantiate(this.rewardDoll);UIElementClusteruiElementCluster=doll.GetComponent<UIElementCluster>();doll.transform.position=aliceHand;Vector3tablePos=GetGuestTable(capturedSeat);Vector3capturedTarget=newVector3(tablePos.x+0f,tablePos.y-0.75f,tablePos.z+0f);uiElementCluster.GetObject<SpriteRenderer>(0).flipX=capturedTarget.x>aliceHand.x;EventManager.Instance.StartCoroutine(SetLinenear_1(doll,uiElementCluster,capturedSeat));// original: <>c__DisplayClass15_1.<OnPositiveBuffExecute>b__2 returns capturedTargetEventManager.Instance.StartCoroutine(LerpPosition(doll.transform,()=>capturedTarget,1f));}yieldreturnUnityCoroutineExtension.OneSecondDelay;dollEnd=true;yieldreturnUnityCoroutineExtension.OneSecondDelay;yieldbreak;// original: <OnPositiveBuffExecute>g__SetLinenear_1|15_1 (iterator state machine)IEnumeratorSetLinenear_1(GameObjectdoll,UIElementClusteruiElementCluster,intseat){LineRendererlineRenderer=uiElementCluster.GetObject<LineRenderer>(0);lineRenderer.SetPosition(1,aliceHand);while(!dollEnd){Vector3dollPos=doll.transform.position;lineRenderer.SetPosition(0,newVector3(dollPos.x+0f,dollPos.y+0.5f,dollPos.z+0f));yieldreturnnull;}Animatoranimator=doll.GetComponent<Animator>();// NOTE(By Opus 4.7): BYTE4(EventManager.Instance[40].monitor) - an unresolved// boolean field on EventManager that picks between the two reward animations.if(/* TODO(By Opus 4.7): unresolved EventManager flag */false){animator.Play("AliceRewardDoll2");yieldreturnUnityCoroutineExtension.HalfSecondDelay;}else{animator.Play("AliceRewardDoll1");SpriteMaskspriteMask1=uiElementCluster.GetObject<SpriteMask>(0);SpriteMaskspriteMask2=uiElementCluster.GetObject<SpriteMask>(1);NormalGuestguest1=allNGuests.RandomSelectOne();NormalGuestguest2=allNGuests.RandomSelectOne();SpriteRendererdollSprite=uiElementCluster.GetObject<SpriteRenderer>(0);// NOTE(By Opus 4.7): NormalGuest vtable slot 6 returning an object whose// field at +0x28 (relative to its Fields) is a Sprite. Mapped here as a// portrait-style accessor; concrete member name unresolved.spriteMask1.sprite=guest1.GetVisual()/* TODO(By Opus 4.7): vtable._6 */.Sprites[0];spriteMask2.sprite=guest2.GetVisual()/* TODO(By Opus 4.7): vtable._6 */.Sprites[0];yieldreturnUnityCoroutineExtension.QuarterSecondDelay;dollSprite.maskInteraction=SpriteMaskInteraction.VisibleInsideMask;yieldreturnUnityCoroutineExtension.HalfSecondDelay;guestManager.SpawnNormalGuestGroup(newNormalGuest[]{guest1,guest2},doll.transform.position,true,seat,false);yieldreturnUnityCoroutineExtension.QuarterSecondDelay;}UnityEngine.Object.Destroy(doll);}}mimo-v2.5-pro (Claude Code)
// GameData.Core.Collections.NightSceneUtility.SkillCollection.Spell_AlicepublicoverrideIEnumeratorOnPositiveBuffExecute(SpellExecutionContextspellExecutionContext){// State machine: _OnPositiveBuffExecute_d__15// Captures: this, spellExecutionContext// (coroutine body below)yieldreturnnull;// placeholder — see MoveNext}// Spell_Alice.OnPositiveBuffExecute (coroutine body)privateIEnumeratorOnPositiveBuffExecuteCoroutine(SpellExecutionContextspellExecutionContext){varclosure=newDisplayClass15_0();closure._this=this;// Get empty tables (target tables minus occupied tables)varemptyTables=EventManager.Instance.TargetTablesAny(1000,null,null).Except(EventManager.Instance.TargetTablesGuests(1000,null,null)).ToList();if(emptyTables.Count<=0)yieldbreak;// Get all normal guests and filterclosure.allNGuests=DataBaseCharacter.GetAllNormalGuests().Where(/* b__15_0: predicate — likely guest.IsNormal / guest.IsSeated check */).ToList();closure.guestManager=GuestsManager.Instance;// Calculate Alice's hand position with directional offsetvarguestPos=spellExecutionContext.GuestPosition.Value;vardeskCode=spellExecutionContext.GuestsController.DeskCode;floatxOffset;if(deskCode==-1||GetGuestTable(deskCode).x>guestPos.x)xOffset=this.offsetX;// field at +0x28 (offset 10 from base)elsexOffset=-this.offsetX;closure.aliceHand=newVector3(guestPos.x+xOffset,guestPos.y+this.offsetY,// field at +0x2C (offset 11 from base)guestPos.z);closure.dollEnd=false;PlayAudio(this.audioClip);// field at +0x30 (offset 6 from base)foreach(vartableIndexinemptyTables){// Spawn doll prefab at aliceHand positionvardoll=Instantiate(this.dollPrefab);// field at +0x20 (offset 4 from base)doll.transform.position=closure.aliceHand;varuiCluster=doll.GetComponent<UIElementCluster>();vardollSprite=uiCluster.GetObject<SpriteRenderer>(0);varguestTablePos=GetGuestTable(tableIndex);vartargetPos=newVector3(guestTablePos.x,guestTablePos.y-0.75f,guestTablePos.z);// Flip sprite based on directiondollSprite.flipX=targetPos.x>closure.aliceHand.x;// Start line-drawing coroutineEventManager.Instance.StartCoroutine(SetLinenear(closure,doll,uiCluster,tableIndex));// Start movement coroutine (LerpPosition)EventManager.Instance.StartCoroutine(LerpPosition(doll.transform,()=>/* b__2: returns targetPos */));}yieldreturnUnityCoroutineExtension.OneSecondDelay;closure.dollEnd=true;yieldreturnUnityCoroutineExtension.OneSecondDelay;}// Spell_Alice.DisplayClass15_0.SetLinenear (local function / coroutine)privateIEnumeratorSetLinenear(GameObjectdoll,UIElementClusteruiElementCluster,intseat){// State machine: _SetLinenear_1_dvarlineRenderer=uiElementCluster.GetObject<LineRenderer>(0);lineRenderer.SetPosition(1,this.aliceHand);while(!this.dollEnd){vardollPos=doll.transform.position;lineRenderer.SetPosition(0,newVector3(dollPos.x,dollPos.y+0.5f,dollPos.z));yieldreturnnull;}// Get animator and play appropriate animationvaranimator=doll.GetComponent<Animator>();if(/* game state condition — checks a boolean field on EventManager */){animator.Play("w");// StringLiteral_4133yieldreturnUnityCoroutineExtension.HalfSecondDelay;}else{animator.Play("u");// StringLiteral_4126// Set up sprite masks with random guest portraitsvarmask1=uiElementCluster.GetObject<SpriteMask>(0);varmask2=uiElementCluster.GetObject<SpriteMask>(1);varguest1=allNGuests.RandomSelectOne();varguest2=allNGuests.RandomSelectOne();vardollSprite=uiElementCluster.GetObject<SpriteRenderer>(0);// Access guest sprite via vtable slot 6varspriteData1=guest1./* vtable._6 */();if(spriteData1!=null&&spriteData1.Length>1)mask1.sprite=spriteData1[1];varspriteData2=guest2./* vtable._6 */();if(spriteData2!=null&&spriteData2.Length>1)mask2.sprite=spriteData2[1];yieldreturnUnityCoroutineExtension.QuarterSecondDelay;// Set mask interaction and waitdollSprite.maskInteraction=SpriteMaskInteraction.VisibleInsideMask;yieldreturnUnityCoroutineExtension.HalfSecondDelay;// Spawn guest group at doll positionvarguests=newNormalGuest[]{guest1,guest2};vardollPosition=doll.transform.position;guestManager.SpawnNormalGuestGroup(guests,dollPosition,true,seat,false);yieldreturnUnityCoroutineExtension.QuarterSecondDelay;}// CleanupDestroy(doll);}