Skip to content

Latest commit

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

il2cpp-to-csharp-skill

中文

A skill for restoring readable C# from Unity IL2CPP binaries. It ships two independent backends that share the same goal and the same Il2CppDumper artifacts:

BackendEnvironmentDocs
IDAIDA Pro + IDA Pro MCPida/workflow.md
GhidraGhidra GUI or analyzeHeadlessghidra/workflow.md

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.

Backend selection: the root SKILL.md is a thin dispatcher. It asks the user to specify which backend to use, then follows that backend's workflow.md. The two backends never mix instructions, so neither workflow is diluted.

Output quality depends on the AI model and the available context. Restored code is for reference only and should be manually reviewed against the decompiler output, DummyDll stubs, and runtime behavior.

Requirements

  • IDA backend: IDA Pro with IDA Pro MCP enabled.
  • Ghidra backend: Ghidra (GUI or analyzeHeadless).
  • Both: Il2CppDumper output — script.json, stringliteral.json, DummyDll/ or dump.cs — and Python 3 for the bundled helper scripts.

Installation

Copy the skill into its own directory under the client's skills root.

Copy into <skill-directory>/
SKILL.md, ida/, ghidra/

The root SKILL.md is a dispatcher — it asks the user to specify which backend to use and follows that backend's instructions. Both ida/ and ghidra/ ship together; the dispatcher uses only one at runtime.

Installing through an agent

Give your agent this prompt; no local clone is required:

Install the skill from https://github.com/MetaMikuAI/il2cpp-to-csharp-skill. Clone the repo directly into this client's skills root as a directory named `il2cpp-to-csharp`. If the destination already exists, inspect it and ask before replacing or merging. Verify the final layout and tell me whether a restart or new session is needed.

Preparation

  1. Open the IL2CPP binary, usually GameAssembly.dll, in IDA Pro and wait for initial analysis to finish.
  2. Run Il2CppDumper's IDA processing script, usually ida_with_struct_py3.py, inside IDA on the current database.
  3. Open DummyDll/Assembly-CSharp.dll in dnSpy and export it as a C# project. This gives the agent readable class, method, field, enum, generic, and [FieldOffset] stubs.
  4. Keep stringliteral.json available and provide its local path when string resolution is needed.

Usage

Install this skill as il2cpp-to-csharp, then ask the agent to restore one function at a time:

Use $il2cpp-to-csharp to restore <address or name>.
<IDA or Ghidra> is ready. stringliteral.json is at <path>. The DummyDll stub project is at <path>.

The address can be a VA (0x180000000), an RVA (rva:0x123456), or a function name. For the Ghidra backend, also include the project location, project name, and program name.

Example

example 1

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();}

example 2

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);}

example 3 — Ghidra backend

The same OverrideSave method as example 1, queried through the Ghidra backend instead of IDA:

python3 scripts/ghidra_query.py query \
--project-location /path/to/ghidra-projects \
--project-name game \
--program UnityFramework \
decompile name:ResultScene_UI_ResultSceneSavePannel__OverrideSave_d__16__MoveNext

Ghidra decompiler C (abridged from the complete decompile.c; class-init guards and builder plumbing elided)

voidResultScene_UI_ResultSceneSavePannel__OverrideSave_d__16__MoveNext
(ResultScene_UI_ResultSceneSavePannel__OverrideSave_d__16_o*this, MethodInfo*method)
{
Cysharp_Threading_Tasks_UniTask_ou__1;
if (this->fields.__1__state==0) {
u__1=this->fields.__u__1;
this->fields.__u__1=0;
this->fields.__1__state=-1;
LAB_00123456:
UniTask_Awaiter__GetResult(&u__1);
ResultScene_UI_ResultSceneSavePannel__ClosePanel(this->fields.__4__this, 0);
this->fields.__1__state=-2;
return;
}
this->fields.__4__this->fields.m_BlockSubPanelCommand=1;
ResultScene_UI_ResultSceneSavePannel__HidePanel(this->fields.__4__this, 0);
ResultScene_UI_ResultSceneSavePannel__SavePlayerDataCoreAsync
(&u__1, this->fields.__4__this, this->fields.index, 0);
if (UniTask_Awaiter__IsCompleted(&u__1) ==0) {
this->fields.__1__state=0;
this->fields.__u__1=u__1;
AsyncUniTaskVoidMethodBuilder__AwaitUnsafeOnCompleted(&this->fields.__t__builder, &u__1, this);
return;
}
goto LAB_00123456;
}

Restored (same method as example 1 — both backends converge on the same source):

privateasyncUniTaskVoidOverrideSave(intindex){m_BlockSubPanelCommand=true;HidePanel();awaitSavePlayerDataCoreAsync(index);ClosePanel();}

All names and addresses above are example placeholders, matching the skill's convention; for real work use the current binary's Ghidra output.

Repository Layout

SKILL.md Dispatcher: backend selection + shared rules (start here)
scripts/ Shared tools: lookup_strings.py, field_offset.py, query_script_json.py
ida/ IDA backend
workflow.md IDA workflow: ida-usage.md, ida-quirks.md, strings.md,
helpers.md, compiler-patterns.md
ghidra/ Ghidra backend
workflow.md Ghidra workflow: ghidra-setup.md, ghidra-query.md,
ghidra-quirks.md, strings.md, helpers.md,
string-formatting.md, lambdas-closures.md, linq-generics.md,
coroutines.md, async.md, runtime-exceptions.md,
runtime-memory.md, scripts/, agents/

Run shared scripts from the root scripts/ directory. Ghidra-specific scripts live under ghidra/scripts/; run them from within ghidra/.

About

A skill for BepInEx il2cpp modders.

Resources

Stars

26 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages