Learn to decompile GameCube assembly into byte-matching C.
Go from never having read a register to matching real Star Fox Adventures functions — instruction for instruction. You write C, the real 2001 compiler grades it live.
Start from zero
254
Lessons
0
Solved
Mastery0%
Graded by the realmwcceppc.exe Functions from the live SFA-Decomp project
The Curriculum
Read the asm · write the C · the compiler grades it byte-for-byte.
Solved Attempted Not started Concept (reading) Difficulty 1–5
I0/6
Warm-up
Learn to read the machine
II0/164
Core idioms
Every shape C compiles into
An Add Then a SubtractTwo Adds, ReassociatedA Subtract Then an AddA Three-Instruction ChainMultiplying Two RegistersMultiply by a Power of TwoMultiply by a Small ConstantAn Affine ExpressionUnsigned Divide by a Power of TwoSigned Divide by a Power of TwoReal DivisionModulo Has No InstructionMultiply Then AddPrecedence Changes the OrderTwo Products, SubtractedA Three-Instruction Mixed ChainWhen a Multiply Is a ShiftA Shift Inside a Mixed ChainDivide Then AddWhen a Divide Is a ShiftA Shift-Divide in a ChainDivide and Multiply, Then SubtractMultiply and Divide CombinedAll Four Arithmetic Operators
Masking Bits With ANDCombining Bits With ORFlipping Bits With XORSetting a Single Flag BitClearing a Bit: The rlwinm SurpriseTesting Whether a Bit Is SetShifting Left by a ConstantLogical Right Shift (Unsigned)Arithmetic Right Shift (Signed)Shifting by a Variable AmountExtracting a Bitfield in One rlwinmPacking Two Values with a Shift and ORMerging Two Fields: rlwinm + rlwimiChaining XOR Across Three ValuesBit Select (Mux): AND, ANDC, ORCapstone: Pack Three Values into One Word
Returning a Comparison as a BoolNot-Equal Is Its Own IdiomIf / Else: The Compare Feeds a BranchTwo's Complement, srawi, and andc by HandConceptClamping to Zero, BranchlesslySigned Compare: cmpwUnsigned Compare: cmplwComparing Against a Constant: cmpwi vs cmplwiThe Guard Clause / Early ReturnTernary MaxTernary MinShort-Circuit && and ||Switch: The Compare ChainClamping Between Two BoundsThe if / else-if LadderA Range Test with &&Rejecting Out-of-Range with ||A Multi-Condition GuardCapstone: A Guarded, Clamped Update
The Anatomy of a Counted LoopWhile Is the Same LoopDo-While: The Tightest LoopCounting Down Is CheaperWalking an Array by IndexFinding the MaximumWalking a Pointer to a SentinelBreaking Out EarlyA Mental Model: The Five Parts of a LoopConceptStrength-Reduced InductionA Loop With More Work InsideNested LoopsNesting Over a 2-D ArrayWhen the Inner Bound Follows the OuterCapstone: Counting Pairs in a Grid Scan
Loading a ByteLoading a HalfwordSigned Loads Sign-ExtendStoring a Byte TruncatesStoring a Halfwordu8, Not char (The Spurious extsb)Widening: Zero vs Sign ExtendTruncating With a MaskCasts That Sign-ExtendCasting Between Signed WidthsThe Compare Opcode Follows the TypeBumping a Byte CounterRead, Scale, TruncateA Signed Halfword Read-Modify-WriteCombining Two WidthsMixed Signedness in One ExpressionCapstone: Widths and Signs in One Function
Dereferencing a PointerStoring Through a PointerA Constant Index Becomes a DisplacementWriting at a Constant IndexPointer Arithmetic Is ScaledA Variable Index Needs Scaling Then IndexingByte Arrays Need No ShiftHalfword Arrays Shift by OneWalking a StringComparing Two PointersGuarding Against NULLSwapping Through PointersReading Two Elements and Combining ThemScaling One Element Before CombiningDereference, Then Compute With an ArgumentIndexing Neighbors From a Computed OffsetCombining Two Arrays at the Same IndexCapstone: Several Dereferences in One Expression
Reading a Struct FieldWriting a Struct FieldNarrow Fields: Byte and Halfword LoadsStoring a Byte FieldAlignment Padding Shifts an OffsetCombining Two FieldsComputing Across Three FieldsNested Structs Flatten to One OffsetCombining Fields Across Nested StructsArrays of Structs: Scaling the IndexAn Array Inside a StructUnions Overlay the Same BytesA Single-Bit Flag: li; rlwimiMulti-Bit Bitfield WritesReading a Bitfield: rlwinm ExtractWalking a Linked ListCalling Through a Function PointerCopying a Whole StructEight-Byte Alignment Copies Through Float RegistersBig Structs Copy in a LoopCombining a Copy With a Field UpdateCapstone: Reading a Whole Struct
Floats Live in a Different Register FileSingle-Precision MultiplySubtraction Keeps Its OrderFloating-Point Division Is RealDividing by a Constant Becomes a MultiplyDoubles Drop the 's'★ The Spurious frsp: f32 vs double HelpersLoading a Float Constant from the SDA★ Fused Multiply-AddInteger to Float: The Magic-Number TrickFloat to Int: fctiwz and the Store/Load DanceComparing Floats: fcmpo Feeding a BranchAbsolute Value and NegationChaining Two Float AddsMixing Add and Subtract in One ChainA Weighted Sum Folds Into One fmaddsThe Lerp Idiom — fsubs Feeding fmaddsTwo Products Summed — the 2D Dot ProductAccumulating Three Products — the 3D Dot ProductPicking the Larger: fcmpo + Conditional fmrClamping Between Two ConstantsMixing f32 and f64 — Double Math, then frsp★ Capstone: A Float Step With a Clamp
III0/80
The real ABI
Frames, globals, optimizer, 64-bit
The Integer Argument RegistersReaching the Fifth ArgumentFloats Use Their Own RegistersA Leaf Has No Stack FrameThe Stack Frame and the Link RegisterMarshalling Arguments for a CallSurviving a Call: Saved RegistersDeclaration Order Colors the RegistersReturning a Called Result DirectlyWhen Arguments Spill to the StackChaining: Keep a Value, Marshal the RestChaining: Pipe One Call's Result Into the NextChaining: Two Survivors Feeding a Final CallChaining: The Whole ABI in One Function
Reading a Global Through the Small Data AreaWriting a GlobalThe Opcode Follows the TypeA Global Float and the Second Small Data AreaFloat Literals Become Pooled ConstantsTaking an Address: SDA li vs. the @ha/@l PairIndexing a Global ArrayRead Two Globals, Compute, Write BackA Float Global Times a Pooled LiteralRead Wide, Store NarrowWriting Into a Global ArrayIndex an Array, Narrow the ResultRead-Modify-Write an Array Element★ A Float Array Element, Scaled Into a Global★ Capstone: A Lighting-Update Function
What -O4,p Actually DoesInstruction Scheduling: Hiding LatencyThe Peephole Optimizer: Dot-Form Merging#pragma peephole off: Unfusing the Merge#pragma scheduling off: Freezing the OrderScheduling Floating-Point Workfp_contract: Fused Multiply-AddStrength Reduction in a LoopCommon-Subexpression EliminationChaining: CSE Feeds Strength ReductionChaining: Scheduling Plus a Dot-MergeChaining: FP Scheduling and fp_contract at Arity ThreeChaining: A Reused Product Inside a Fused Multiply-AddCapstone: Scheduling Meets fp_contract
Switch: The Jump TableTable or Chain? The Density RulePaired-Single FPR Saves in the PrologueWhere Inline asm{} Earns Its Place: psq_l / psq_stEnums Are int-Sized: Recovery Is NamingVolatile Defeats CSE: Two Reads, Two LoadsVolatile Hardware RegistersChain: An Enum Switched Through the TableChain: Switch on a Hardware RegisterChain: Enum Guard, Then a Volatile SumCapstone: A Volatile-Guarded Enum State MachineCapstone II: Saved Floats Dispatched Through a Switch
Two Registers Make a long longConceptAdding 64-bit Integers (addc / adde)Subtracting 64-bit Integers (subfc / subfe)The Odd-Register Alignment RuleThe Downcast FingerprintMultiplying 64-bit IntegersDivision Calls an IntrinsicShifts Call an Intrinsic TooBitwise Ops Are Just Two HalvesComparing 64-bit IntegersChaining: Carry Into a BorrowChaining: A Bitwise Pair Feeds a Carry ChainChaining: A Downcast Prunes the ChainChaining: A Sum Feeds a 64-bit CompareChaining: Carry, Borrow, and Mask in One Body
IV0/14
Proving ground
Real Star Fox Adventures functions, start to finish
A Real Setter: State, a Float Nudge, and a Boolean ReturnCopying a Position With a BiasA Flag Toggle Behind a Helper CallClamping a Float Into RangeA Loop Over an Event ArrayOrbital Math: fmadds, fdivs, and Saved Float RegistersA Phase State MachineA Per-Frame Update: Rotation, a Flag, and a BranchGuarded Hit Detection: NULL Chains and a Type CheckThe Capstone: A Full Reset-to-Idlepeephole off: When You Must Write the (s16) Cast YourselfTwo Running Sums, Woven by Hand: Steering the ,p SchedulerParam Inversion: Who Gets the Saved RegisterDeclaration Order Is Register Order: Coloring Saved Regs by Hand