Let's Make a Skill (ASM)

Setting up (windows)

Summary

Install devkitPro to its default path of c:\devkitPro | Wiki

devkitPro: assembles code

image

Download lyn.exe by @StanH and place it into c:\devkitPro\ | FEU Thread

lyn.exe: links code / QoL

Install notepad++ or sublime for editing text and setup EA highlighting & asm highlighting.

notepad++: QoL over notepad

image

Install grepWin

grepWin: find things within a buildfile easier

image

Install GithubDesktop and create an account

GithubDesktop: so we can see your edits

Fork the SkillSystemBuildfile and make your copy public.

SkillSystemBuildfile: Begin testing asm within seconds; febuilder is very slow for this

It is assumed you already have the following:
FEBuilderGBA | no$gba (debug)

Other reading

Summary

https://feuniverse.us/t/ok-fine-lets-learn-how-to-make-skills-skill-creation-tutorial/7774
https://feuniverse.us/t/gbafe-assembly-for-dummies-by-dummies/3563

Opcode Glossary

Summary

Refer back to this as needed, or read @Tequila’s overview here.

Load Store LoadSigned
Byte ldrb strb ldsb
Short ldrh strh ldsh
Word ldr str
Stack pop push
Branching
cmp compare
b always
beq equal bne not equal
blt less than ble less or equal
bgt greater than bge greater or equal
Calling a…
Buildfile func: bl
Vanilla func: blh or .short 0xf800
Returning
bx
Math
add sub mul swi 6 (divide)
Logical Shifts
Left lsl
Right lsr asr
Logic
orr use bits from either
and keep bits in common
bic remove bits in common
neg x = 0-x
mvn !(x)

And mov, of course. There are other opcodes that exist, but these are some common ones to get started with.

Your first ASM

Summary

All of that is scary, can’t I just use FEBuilder?

Sure, let’s start there.

In the disassembler we can look at asm (originally generated by C code) and edit it. The wait command (along with many others) has UsabilityAlways:
image

mov r0, #0x1 at the end of a function is typically return true; while mov r0, #0 at the end is return false;. (Menu options actually return 1 for display, 2 for greyed out and 3 to not show up.)

image
We can easily make this inline change of return 2; so that Wait now shows up as greyed out.

image

However, we cannot add lines of code with inline edits. For example, if we wanted to grey it out for Seth but nobody else, we’d need more lines of code to do that. (To expand code, we’d need to create a new function and poin to it, or hook the code.)

Registers & Mov

Summary

The scratch registers are r0, r1, r2, and r3 which we can use for math.
We often store variables like x = 5; in r4, r5, r6, and r7.
The other registers don’t really matter for now, though you may notice lr / r14 being pushed at the start of functuons.

mov sets the register to a value.
Eg.
Set r0 to whatever is in r1 by doing mov r0, r1.
Set r0 to some direct value up to 255 by doing mov r0, #12.

image
For example, the Vulnerary Heal Amount patch simply edits the value in the mov r1, #10 command at 0x2FEC6.

image
If I directly edit this line to #33 and reload the patch, it’ll also show me 33.

image
Most patches with a single value are just a mov command you’re editing. (If you go edit it in the disassembler, then it counts as having done asm! :partying_face:)

Stat Getters

Summary

Located in SkillSystem\EngineHacks\Necessary\StatGetters\statname.event with specific skill effects in SkillSystem\EngineHacks\SkillSystem\Skills\StatModifierSkills, these simple routines get the stats of units with any penalties or bonuses included. For example, rescuing a unit halves your Spd/Skl.

image
I could add a routine called I_AM_SUPER_STRONG to Power.event to change a unit’s Strength bonus.

.thumb
.macro blh to, reg=r3
  ldr \reg, =\to
  mov lr, \reg
  .short 0xf800
.endm
.type I_AM_SUPER_STRONG, %function 
.global I_AM_SUPER_STRONG
I_AM_SUPER_STRONG: 
push {r4-r7, lr} 
@ stat getters are given the current stat value in r0 
@ stat getters are given the relevant unit in r1 

@ < my code > 
mov r0, #99
@ < my code > 

pop {r4-r7}
pop {r1} 
bx r1 
.ltorg 

You don’t need to understand push, pop, .thumb, .ltorg, etc. for now. All you need to do is copy exactly what I wrote, only editing the @ < my code > section and the I_AM_SUPER_STRONG name.


I paste my code into Guts.asm, drag it onto AssembleLyn.bat (or sometimes AssembleArm.bat) and run makehack.cmd.

image

Now I check ingame to see that everybody’s Str is indeed boosted to 99.
mGBA_0XwlN98wjn

Great! It works. Now let’s make it conditional. I’ve decided I want a superboss. Thought you could survive him? Get rekt, kid.

If we look at the Teq Doq or StanH’s FE-Clib we can see the Unit Struct, also seen in the FEBuilder Debugger’s “etc” tab.


Since we’re given the unit struct in r1 for all stat getter functions, we can load any information from here as desired. We have ldr that loads a WORD (4 bytes), ldrh loads a Halfword / SHORT (2 bytes), and ldrb loads a byte.

C

int I_AM_SUPER_STRONG(int value, struct Unit* unit) { 
    if (unit->level == 20) { return 99; }
    return value; 
} 

asm

@ < my code > 
@ r0 = value 
@ r1 = UnitStruct
.equ LevelOffset, 0x8
ldrb r2, [r1, #LevelOffset] @ Get the level of the unit
mov r3, #20 @ level we're comparing it to 
cmp r2, r3 @ compare
bne Skip @ Branch (skip) if Not Equal 
mov r0, #99 @ 
Skip: 
@ < my code > 

We also need cmp, which compares two values and we need branching (which all start with the letter b). If the level byte we load is not #20, then skip to the end. This would make it so level 20s have 99 Str. Let’s instead make it only apply to O’Neill and make it (Str*2+5).

int I_AM_SUPER_STRONG(int value, struct Unit* unit) { 
    if (unit->pCharacterData->number == 0x68) { return ((value*2)+5); }
    return value; 
} 

pCharacterData is the data in the Character Editor within FEBuilder, so we ldr that.
Then we’ll ldrb the 4th byte (ID) to see if it is 0x68.

@ < my code > 
@ r0 = value 
@ r1 = UnitStruct
.equ pCharacterData, 0x0
.equ unitID, 0x4 
ldr r2, [r1, #pCharacterData] @ Get the address of the CharEditorEntry
ldrb r2, [r2, #unitID] @ Get the unit id from the CharEditorEntry
mov r3, #0x68 @ unit ID we're powering up 
cmp r2, r3 @ compare
bne Skip @ Branch (skip) if Not Equal 
mov r1, #2 @ amount we will multiply by 
mul r0, r1 @ multiply r0 by r1 
add r0, #5 @ add #5 to r0
Skip: 
@ < my code > 

Finally we’ll mul the value by #2, then add #5. Now O’Neill has his Str doubled + 5, but nobody else does.
image
Using unit id #0x68 directly in the code is called hardcoding. It’s typically better practice to use a definition and label in your .event file, but we’ll talk more about that later.

How do I add replace a function like this via FEBuilder? I’m allergic to buildfiles.

It’s easier in a buildfile/custom build. Not my fault…

Summary
  • Generate a .sym file by using Ctrl+F5 to run No$GBA in FEBuilder.
  • Find pPowerModifiers and open that address in the hex editor.

    prAddUnitPower looks like this for me and is the first function in pPowerModifiers: D0 F9 B7 08 is the same as 8B7F9D0 that we see as the address in the disassembler but it’s written in Little Endian (basically backwards).
  • Choose one of these functions to replace before you get to WORD 0.
  • Insert your ASM at some address, and overwrite one of these addresses with your asm’s address.

But really, you should probably do this via a buildfile to test. You may find it easier to do this via a CustomBuild.

Heal Loop

Summary

The HpRestoration calc loop, found in SkillSystem\EngineHacks\Necessary\CalcLoops\HPRestorationCalcLoop with specific skills in SkillSystem_FE8\EngineHacks\SkillSystem\Skills\HPRestorationSkills restores a percentage of health at the start of your turn.

Let’s add a function called GamesharkCode_Heal.
image

image

And copy paste AssemblyLyn from somewhere (eg. Amaterasu’s folder).
image

We’ll start with the same code template as before, only changing its name:

int GamesharkCode_Heal(struct Unit* unit, int value) { 
    return 99;
} 
.thumb
.macro blh to, reg=r3
  ldr \reg, =\to
  mov lr, \reg
  .short 0xf800
.endm
.type GamesharkCode_Heal, %function 
.global GamesharkCode_Heal
GamesharkCode_Heal: 
push {r4-r7, lr} 
@ HpRestoration Loop is given the relevant unit in r0 
@ HpRestoration Loop is given the current % to heal by in r1


@ < my code > 
mov r0, #99
@ < my code > 
@ HpRestoration Loop returns the % of Hp to heal in r0

pop {r4-r7}
pop {r1} 
bx r1 
.ltorg 

Please note that this time r0 gives us the unit and r1 gives us the % to heal by.

image

When I test it ingame, I can see that all units heal by 99% of their max hp each turn.

mGBA_qEMpfwUb4P

Let’s Organize!

Typically at the start of a function you’ll save any parameters to your registers 4-7 as variables.

@ < my code > 
mov r4, r0 @ unit struct is now in r4
mov r5, r1 @ % to heal is now in r5 

This way, you can call other functions.

mov r0, r4 
bl IsUnitPlayer @ given r0 = unit struct, return true if they are a player. False if not. 

The other functions can be vanilla ones, from the same file (static), or global within the buildfile. Let’s make IsUnitPlayer a static function. At the bottom of our file (after .ltorg), we’ll write more code with our template but omit the .type name, %function and the .global name parts:

int IsUnitPlayer(struct Unit* unit) { 
    return (unit->index < 0x40);
} 
IsUnitPlayer: 
push {r4-r7, lr} 
@ given r0 = unit struct, return true if they are a player. False if not.

@ < my code > 
.equ Divide, 0x80D18FC @ Vanilla function to divide r0 by r1 
.equ DeploymentByte, 0xB @ Found from the TeqDoq
@ (0x01-0x3F for player, 0x40-0x7F for NPC, 0x80-0xBF for enemy)
mov r4, r0 @ unit 
ldrb r0, [r4, #DeploymentByte] @ Load the allegiance/deployment byte
mov r1, #0x40 @ NPCs start here
cmp r0, r1 @ compare 
bge ReturnFalse @ Branch if Greater or Equal than
mov r0, #1 @ true that they are a player 
b Exit_IsUnitPlayer @ Branch (jump) to this label 
ReturnFalse: 
mov r0, #0 
Exit_IsUnitPlayer: 
@ < my code > 

pop {r4-r7}
pop {r1} 
bx r1 
.ltorg 

We ldrb to check their allegiance, cmp it with #0x40, and we bge (Branch if Greater or Equal) to ReturnFalse. I also omitted .thumb and the .macro part, as we only need to have those once in our file. Even if you don’t fully understand the function we called, the point stands that we can organize our code more easily by making generic functions that can be reused, or by using vanilla functions.

Let’s finish our original function, now.

int GamesharkCode_Heal(struct Unit* unit, int value) { 
    if (IsUnitPlayer(unit)) { return (value*75/100); }
    return value;
} 
@ < my code > 
.equ Divide, 0x80D18FC @ Vanilla function to divide r0 by r1 
.equ DeploymentByte, 0xB @ Found from the TeqDoq
@ (0x01-0x3F for player, 0x40-0x7F for NPC, 0x80-0xBF for enemy)
mov r4, r0 @ unit struct is now in r4
mov r5, r1 @ % to heal is now in r5 
mov r0, r4 
bl IsUnitPlayer @ given r0 = unit struct, return true if they are a player. False if not. 
cmp r0, #0 @ did IsUnitPlayer return false?
beq Exit @ if it returned false, branch to Exit
mov r0, r5 @ % to heal by 
mov r1, #4 @ amount to divide by 
blh Divide, r3 @ Vanilla function to divide r0 by r1 
@swi 6        @ this would also work here instead of the vanilla divide function 
sub r5, r0 @ r5 = r5-r0
Exit: 
mov r0, r5 @ % to heal is returned in r0 
@ < my code > 

Previously we used mul and add, so this time I divided and used sub (subtract). This makes it so player units now heal 100% - (100% / 4): 75% of what they used to heal. So a Fort would heal you 15% instead of 20% each turn.

What the heck? Why doesn’t division get its own opcode?

Division is complicated, so it has its own vanilla code for it. When we call a vanilla function, we use blh instead of bl. This is because bl can only reach code within approx. 1 mb (max rom size is 32 mb). However, blh uses up a register - by default, r3. (Functions that take 4 or more parameters use r3, so for them you’d need to do blh FunctionName, r4.)

mGBA_jJk6ggRudq
(7/48 hp is restored instead of 9/48: it works.)

What is swi 6?

The GBA itself can also perform division, but this is done by performing a SoftWare Interrupt. Division is slow compared with other commands, so it is often avoided with a little trickery. I’m not sure whether swi 6 or the vanilla function is faster, but logical shifts are much faster when possible. This is where we move digits. For example in decimal, 72 would become 720 moving left one place, or 72 would become 7 moving right one place. But because the computer works in binary / hexadecimal, shifting left is the same as multiplying it by 2, and right, dividing by 2.

mov r0, #16 
lsr r0, #1 @ (logical shift right)
@ 16 / (2^1) = 8 
@ r0 = 8 

lsl r0, #1 @ (logical shift left)
@ 8 * (2^1) = 16 
@ r0 = 16 

lsr r0, #2 @ r0 = 4 
lsl r0, #2 @ r0 = 16

In this case, we could use lsr to put r5 divided by 4 into r0.
Eg.

mov r0, r5 
lsr r0, #2 @ divide by 4 

Finally, since we never actually used r6 and r7, we don’t need to push/pop those. Eg.

push {r4-r5, lr} 
@ ... 
pop {r4-r5} 
pop {r1} 
bx r1 
.ltorg 
18 Likes

Prebattle Loop

Summary

In SkillSystem\EngineHacks\Necessary\CalcLoops\PreBattleCalcLoop we poin to each routine to run, and we place specific skills into SkillSystem_FE8\EngineHacks\SkillSystem\Skills\PreBattleSkills. These skills affect battle numbers in battle and in the preview:
image
They’re also accounted for in the stat screen and by the Modular Minimug Box for things such as your Atk or Hit. They do not return a value in r0 - instead they load, edit, and store values related to the battle. Most of the relevant values here are HalfWords / SHORTs, so we use ldsh and strh for them.

Let’s make a function that doubles your avoid, but takes away 5 battle defense (or Res: battle defense also includes terrain, support bonuses, skills, etc.)

void Elusive(struct BattleUnit* unit1, struct BattleUnit* unit2) { 
    if (unit1->battleDefense < 5) { unit1->battleDefense = 0; }
    else { unit1->battleDefense = (unit1->battleDefense)-5; }
    unit1->battleAvoidRate = unit1->battleAvoidRate*2;
} 

Note that in most cases of subtraction, we could run into an underflow issue. 0-5 = 0xFFFFFFFB, so let’s just make sure the minimum is 0 because it’s easier that way.

.thumb
.macro blh to, reg=r3
  ldr \reg, =\to
  mov lr, \reg
  .short 0xf800
.endm
.type Elusive, %function 
.global Elusive
Elusive: 
push {r4-r7, lr} 
@ < my code > 
.equ battleDefense, 0x5C
.equ battleAvoidRate, 0x62
mov r4, r0 @ unit1 
mov r5, r1 @ unit2
mov r2, #battleDefense
ldsh r0, [r4, r2] @ load unit1's battle defense 
mov r1, #5 
cmp r0, r1 @ compare def with 5 
blt SetToZero @ Branch if r0 is Less Than r1 to SetToZero
sub r0, r1 @ take away 5 
b DoubleAvoid
SetToZero:
mov r0, #0 

StoreDef: 
mov r2, #battleDefense
strh r0, [r4, r2] @ store the defense back 

mov r2, #battleAvoidRate 
ldsh r0, [r4, r2] @ load the avoid 
lsl r0, #1 @ Logical Shift Left 1 place. Or multiply by two. 
strh r0, [r4, r2] @ store it back 
@ < my code > 

pop {r4-r7}
pop {r0} @ returns nothing, so good form to pop / bx to r0. 
bx r0 
.ltorg 

The only new idea here is that we’re using strh instead of returning a value in r0.

Load Store LoadSigned
Byte ldrb strb ldsb
Short ldrh strh ldsh
Word ldr str

hey Vesly what the heck! there are like 15 different options here, what do I use?

Documentation is your friend. FE-Clib has stuff like this written down:

	/* 4A */ u16 weaponBefore; // ldrh 
	/* 4C */ u32 weaponAttributes; // ldr 
	/* 50 */ u8 weaponType; // ldrb 
	/* 52 */ s8 canCounter; // ldsb 
	/* 5C */ short battleDefense; // ldsh 

u is unsigned while s is signed. The number indicates how many bits. Each byte is 8 bits. So u8 is ldrb, s8 is ldsb, u16 is ldrh, short is ldsh, and u32 is ldr.

If you find this too confusing, then try and stick to modifying existing skills and/or copying snippets of code for now.

Let’s test!
image
Okay, doubling avoid might be a bit overpowered.

Postbattle Loop

Summary

The Postbattle loop in SkillSystem\EngineHacks\Necessary\CalcLoops\PostBattleCalcLoop with specific skills in SkillSystem\EngineHacks\SkillSystem\Skills\PostBattleSkills is for effects after attacking.

image
I feel like cheating some more, so let’s make player units get healed after attacking.

The post battle skills give you the current unit in r4, the defending unit in r5, and the action struct in r6. This is poor convention and makes the C code a little awkward. Good convention is to pass any parameters in r0, r1, r2, etc. and it’s what C code expects.

void ElixirMePls(void) { 
    struct Unit* attackingUnit = GetUnit(gActiveUnit->index); 
    struct Unit* defendingUnit = GetUnit(gBattleTarget->index); 
    if (gActionData->unitActionType == UNIT_ACTION_COMBAT) { // if they attacked 
        if (attackingUnit->curHP > 0) { // if they have any health left
            if (attackingUnit->index < 0x40) { // player only 
                attackingUnit->curHP = attackingUnit->maxHP; // restore to full hp 
            }
        }
    }
} 

This has a few branches to end without doing anything unless the criteria is met.

.thumb
.macro blh to, reg=r3
  ldr \reg, =\to
  mov lr, \reg
  .short 0xf800
.endm
.type ElixirMePls, %function 
.global ElixirMePls
ElixirMePls: 
push {r4-r7, lr} 
@ attacking unit already in r4 
@ defending unit already in r5 
@ action struct already in r6 
@ < my code > 
.equ maxHP, 0x12 
.equ curHP, 0x13 
.equ UNIT_ACTION_COMBAT, 0x2
.equ unitActionType, 0x11
.equ DeploymentByte, 0xB @ Found from the TeqDoq
@check if attacked this turn
ldrb r0, [r6, #unitActionType]	@action taken this turn
cmp	r0, #UNIT_ACTION_COMBAT @attack
bne	End @ if the action was not attacking, End 
ldrb r0, [r4, #DeploymentByte]
mov r1, #0x40 @ start of NPCs 
cmp r0, r1 
bge End @ if the attacker is an NPC or Enemy, branch to the end 
ldrb r0, [r4, #curHP] 
cmp r0, #0 
ble End @ If we have no hp left, End 
ldrb r0, [r4, #maxHP] @ load max hp 
strb r0, [r4, #curHP]  @ store current hp 
End: 
@ < my code > 
pop {r4-r7}
pop {r0} @ returns nothing, so good form to pop / bx to r0. 
bx r0 
.ltorg 

And it works! Nothing new - just now the effect is after acting. It is called the “PostBattle” loop, but Canto is here too. I’m not actually sure if we actually need the “PostAction” loop as well. :man_shrugging:
mGBA_hB9czgpvvE

Midbattle Loop

Summary

The “Proc” loop, located in SkillSystem\EngineHacks\Necessary\CalcLoops\BattleProcCalcLoop with specific skills in SkillSystem\EngineHacks\SkillSystem\Skills\ProcSkills has effects mid-battle.

Note: “PROC” here means “Programmed Random OCcurrence” - that is, a random chance of these effects activating. Be careful not to confuse proc skills with procs (processes), which is the system used to wait until specific code has finished (eg. wait for an animation to finish). I didn’t name these things :sweat_smile:

Let’s make an epic boss that you can’t even damage!


We’ll write similar code to our stat getter function that made O’Neill have 99 Str.


.type Immune, %function 
.global Immune
Immune: 
push {r4-r7, lr} 

@ < my code > 
mov r4, r0 @attacker
mov r5, r1 @defender
mov r6, r2 @battle buffer
mov r7, r3 @battle data
.equ pCharacterData, 0x0
.equ unitID, 0x4 
ldr r2, [r5, #pCharacterData] @ Get the address of the CharEditorEntry from the defender 
ldrb r2, [r2, #unitID] @ Get the unit id from the CharEditorEntry
mov r3, #0x68 @ unit ID we're powering up 
cmp r2, r3 @ compare
bne End @ Branch (skip) if Not Equal 
mov r0, #0 @ damage to deal 
strb r0, [r7, #4] @ battle data + 0x04 is where the current damage to deal is 
End: 
@ < my code > 

pop {r4-r7}
pop {r0} 
bx r0 
.ltorg 

It’s always calculating damage/hit/etc. for the current attacker (they take turns), so we check if the current defender is O’Neill, and if so, set the battle buffer + 0x04 (damage) to 0. And it works, of course.
mGBA_gjuBMdp7QU

I’ll note, however, that these skills generally have more code than just the damage. So let’s gloss over these parts.

  1. If another skill activated or we missed, End.
ldr     r0,[r2]           @r0 = battle buffer                @ 0802B40A 6800     
lsl     r0,r0,#0xD                @ 0802B40C 0340     
lsr     r0,r0,#0xD        @Without damage data                @ 0802B40E 0B40     
mov r1, #0xC0 @skill flag
lsl r1, #8 @0xC000
add r1, #2 @miss @@@@OR BRAVE??????
tst r0, r1
bne End
  1. We roll the die based on some stat as a percentage to activate.
ldrb r0, [r4, #0x16] @speed stat as activation rate
mov r1, r4 @skill user
blh d100Result
cmp r0, #1
bne End 
  1. Set a flag so skills later in the loop can check this flag. If any other skill activated, then do nothing.
@if we proc, set the offensive skill flag
ldr     r2,[r6]    
lsl     r1,r2,#0xD                @ 0802B42C 0351     
lsr     r1,r1,#0xD                @ 0802B42E 0B49     
mov     r0, #0x40
lsl     r0, #8           @0x4000, attacker skill activated
orr     r1, r0
ldr     r0,=#0xFFF80000                @ 0802B434 4804     
and     r0,r2                @ 0802B436 4010     
orr     r0,r1                @ 0802B438 4308     
str     r0,[r6]                @ 0802B43A 6018  

ldrb  r0, =Some_Skill_ID
strb  r0, [r6,#4] 

You don’t have to make mid-battle skills a % chance to activate, but if you do, you may wish to change which stat is the % chance in #2 above. Additionally, at the end of #3 you need to store the skill ID used. Otherwise, you can simply copy paste these snippets of code into your own skill.

2 Likes

But wait…

Summary

I thought you were going to teach me to make skills. These are all stupid functions that affect everyone!

Okay, fine. The only difference between these functions and a skill is a few lines of code: we call the SkillTester function with the parameter of unit struct and Skill ID, which tells us true or false.

@ < my code > 
mov r0, r4 @ unit struct 
mov r1, #1 @ Skill ID (Canto is probably 1) 
bl SkillTester
cmp r0, #0 
beq End
@ code effect 

End: 
@ < my code> 

Now since we don’t want to hardcode our skill ID, we can instead load the name of the skill.

mov r0, r4 @ unit struct 
ldr r1, =CantoID 
mov r2, #0xFF 
and r1, r2 
bl SkillTester
cmp r0, #0 
beq End
@ code effect 

End:

That’s all there is to it! There are a number of ways to load the skill ID into r1 for SkillTester, but this works fine.

hey what about icons, descriptions, etc.?

I mean you don’t really need those, they’re just aesthetics. @Sme goes over that part here, and you should probably read the rest of the guide, too.

Optimizations & Convention

Summary

There are several places in this guide where I have intentionally written poor code that lacks optimizations in order to make it easier for beginners to understand. If you want help with your code, don’t worry - others will point out poor code to you. But if you want to share the code (eg. to add skills to SkillSys master), it is expected that you make an attempt to have readable, fast code. For example, IsUnitPlayer could be optimized a lot (and doesn’t even need to be a separate function).

IsUnitPlayer: 
@ given r0 = unit struct, return 0 if not a player
.equ DeploymentByte, 0xB
ldrb r1, [r0, #DeploymentByte] 
lsr r0, r1, #6 @ allegiance only 
mvn r0, r0 @ turn 0 into non-zero and vice versa
bx lr

We didn’t actually need r4-r7, so pushing / popping them wastes a few cycles. (Around 10 - approx. 280,000 make a frame.) We didn’t use bl, blh / .short 0xf800, or any other calls to other functions, so we didn’t need to push/pop lr either. No need to branch either!

Some tips:

  • Don’t push/pop registers you don’t use.
  • pop r0 and bx to r0 instead of r1 if you are not returning a value in r0.
  • Comment your code!
  • Create functions to call within your code

There are many other ways to make small optimizations that I’m sure you’ll learn over time. But for now, I hope this has helped with learning how to delve into asm and make some basic skills! Good luck!

2 Likes

Reserved

1 Like