[FE8] Writing to memory using events

Read From Address
Write To Address

This is my first ASM hack, so I don’t really know what I’m doing. But the possibilities it opens up are kind of exciting.

Basically, this is a little snippet of ASM that takes a value and writes it to an offset. The value is given by SETVAL 0x2 and the offset is given by SETVAL 0x3. SETVAL 0x4 determines the size of the data: 0 - Word, 1 - Halfword, 2 - Byte (thanks @Lisandra_brave).

For example, I have a talk event that goes:

Text(0x22,0x99A)
REMA
SETVAL 0x2 0x5F //value to write
SETVAL 0x3 0x202BE60 //offset to write to
SETVAL 0x4 0x2 //0x2 means a byte
ASMC 0x8E90001 //execute the routine
ENDA

Result:

Macros for you:
#define Write_Word(value,offset) "SETVAL 0x2 value; SETVAL 0x3 offset; SETVAL 0x4 0x0; ASMC 0xwhatever+1"
#define Write_Short(value,offset) "SETVAL 0x2 value; SETVAL 0x3 offset; SETVAL 0x4 0x1; ASMC 0xwhatever+1"
#define Write_Byte(value,offset) "SETVAL 0x2 value; SETVAL 0x3 offset; SETVAL 0x4 0x2; ASMC 0xwhatever+1"

Now FE8 events are even more powerful than before.

4 Likes

we have the technologyyyyy

No one will call her weak now!

This is pretty neat. Any thoughts on making a version that adds to the value at the offset/could I make one using this? This way you can have events that boost stats like in FE4.

Simple enough; put ldr r0, [r2]; add r1,r0 before each str/strh/strb

I can’t shake the feeling that you should just be using b GetParams instead of bl and popping your registers at the end of each write, instead of making another jump at the end. Especially because you’re using bl to jump to End.
Otherwise, Neat! Now the obvious thing is to make something that can check an arbitrary memory address and compare it to a number you specify.

e: ooooh, you can completely get rid of the different lines if you use setval 0x4 to 0, 1, or 2 depending on what kind of thing you’re writing out. Then you can load whatever’s in 0x4 to r3 when you’re going through loading the setvals and keep chugging from there. Then change the EA custom definitions to change setval 0x4 instead of changing where it’s looking at the ASM to start. Then you can cut out the entire block where it loads something different based on where you call it.

Oh, I see. I don’t know much about optimizing and stuff yet, I’m just happy it actually works.

Is this cleaner? For comparing Slot 2 with the data at Slot 3, setting Slot C to 1 for true or 0 for false:

.thumb
.org 0x00000000

CompareValues:
push {r0-r3,lr}
ldr r0, Slot_Pointers @Slot 2 address
ldr r1, [r0] @Load the value of Slot 2 into r1
add r0, #0x4
ldr r2, [r0] @Load the address at Slot 3 into r2
ldr r3, [r2] @Load the value at that address into r3
cmp r3,r1
beq Return_True
mov r1, #0x0
str r1, [r0,#0x9] @set Slot 0xC to 0 for false
pop {r0-r3,pc}

Return_True:
mov r1, #0x1
str r1, [r0,#0x9] @set Slot 0xC to 1 for true
pop {r0-r3,pc}

.align
Slot_Pointers:
.long 0x030004C0

If you do it in arm instead of thumb you can use

cmp r3,r1
ldreq r4, =0x1
ldrne r4, =0x0
str r4, [r0,#0xC]

At least, I believe that’ll work, I’m not terribly good at testing. Also, for loading a register with a particular number, you can use ldr rx, =0x* instead of making a label and loading the label.

If you’re using thumb, yeah, that’s pretty clean.

Of course, you’ll probably want a second opinion, since I’m not terribly good at this.

[spoiler=irrelevant side note]I should really bully coerce ask someone to make me a test map for testing ASM, since I’m actually really terrible at eventing.[/spoiler]

My simple template, made in Eventiel forever ago:

[spoiler=Basic Events for FE8]

#include EAstdlib.event

EventPointerTable(0x07,ThisChapter)

ORG 0xE80000

ThisChapter:
POIN TurnBasedEvents
POIN CharacterBasedEvents
POIN LocationBasedEvents
POIN MiscBasedEvents
POIN Dunno Dunno Dunno
POIN Tutorial
POIN TrapData TrapData
POIN Units Units
POIN $0 $0 $0 $0 $0 $0
POIN BeginningScene EndingScene

Units:
UNIT Eirika EirikaLord 0x00 Level(1, Ally, 0) [7,5] 0x00 0x00 0x0 0x00 [Rapier] NoAI 
UNIT Seth Paladin 0x00 Level(1, Ally, 0) [6,5] 0x00 0x00 0x0 0x00 [SteelSword, SilverLance] NoAI 
UNIT ONeill Fighter 0x00 Level(1, Enemy, 0) [12,8] 0x2 0x00 0x0 0x00 [IronAxe] DefaultAI 
UNIT


TurnBasedEvents:
END_MAIN

CharacterBasedEvents:
CharacterEventBothWays(0x7,EirikaSethTalk,Eirika,Seth)
END_MAIN

LocationBasedEvents:
END_MAIN

MiscBasedEvents:
CauseGameOverIfLordDies
AFEV 0x0 EndingScene 0x06
END_MAIN

Dunno:
//DO NOT TOUCH
WORD $00

Tutorial:
//DO NOT TOUCH
WORD $00

TrapData:
END_MAIN

ALIGN 4

BeginningScene:
LOAD1 0x1 Units
ENUN
ENDA

EndingScene:
MNC2 0x01
ENDA

// Events
EirikaSethTalk:
Text(0x908)
REMA
ENDA


// Manual Movement

// Scripted Fights

// Units

// Shop Data

MESSAGE Events end at offset currentOffset

[/spoiler]

  • You don’t need to save r0-r3; the standard calling convention is that r0-r3 are saved (if necessary) by the caller, and r4 onward by you. Also you only need to save lr when you’re going to bl (and thus overwrite it), so that gets skipped too. To return from the function, since you don’t have anything on the stack to pop into pc, you use the normal approach: bx lr.

  • Probably more elegant to use r0 for the value that you write back, since it’s the “return value” from the function. I’m guessing it gets ignored anyway, but yeah.

  • You can combine the “tails” of the function with clever use of branching.

  • You don’t need to increment the pointer, because ldr accepts a constant (immediate) offset value too.

My version (not tested, totally off the top of my head):

.thumb
.org 0x00000000
ldr r0, #0x0 @ False (default)
ldr r1, Slot_Pointers
ldr r2, [r1] @ reference value
ldr r3, [r1, #0x4] @ location to check
ldr r3, [r3] @ value to compare
cmp r3, r1
bne False
mov r0, #0x1 @ True
False:
str r0, [r1, 0xC] @ Copy return value into slot 0xC
bx lr
.align
Slot_Pointers:
.long 0x030004C0

You can, but it’s just an assembler shortcut. IIRC you also need a directive like .pool or something in this case to tell it where to put the constant that it’s loading. (There’s no getting around the fact that you aren’t going to fit a 4-byte constant value-to-load into a 2-byte opcode :slight_smile: )

Reading through the documentation, it’s a better idea to bx lr than to push lr pop pc. BX can change the processor from arm to thumb, and vice versa, so if you’re calling out to a thumb function from a arm function using bx, and you push/pop to get back, you’re going back to your original code in thumb state, which could cause problems.

Incidentally, do you know if the GBA FEs are more in arm or thumb?

I tried that initially, but it gives Error: invalid offset, value too big (0x00000004). Also that should be #0x1 because Rd = [Rn + (#<<2)]EDIT: this is not true, it does not in fact multiply by 4 and that’s why I was getting the error

As for pushing r0-r3, I was being safe because “FE8 is weird”.

Thanks for the tips though, this has been very educational.

Strange, my compiler will only accept multiples of 4 when compiling it in thumb state. In arm, it compiles fine.
Time to bust out the documentation.

Actually, I think it might be my mistake, multiples of 4 don’t raise the error. So does that mean the documentation is wrong and it doesn’t left shift twice?

EDIT: Yep, the documentation seems to be wrong and @zahlman is right. Here’s my cleaned up version of the original code:

.thumb
.org 0x00000000

@@ DESCRIPTION:
@@ This FE8 hack will write the value of Slot 2 into the address at Slot 3.
@@ Slot 4 value 0/1/2 determines word/short/byte.

Event_Writer:
push {r0-r3} @optional?
ldr r0, Slot_Pointers
ldr r1, [r0, #0x8] @Load the value of Slot 2 into r1
ldr r2, [r0, #0xC] @Load the value of Slot 3 into r2
ldr r3, [r0, #0x10] @Load the value of Slot 4 into r3
cmp r3,#0x0       @if 0, write a word
beq Write
cmp r3,#0x1       @if 1, write a short
beq Write_S
cmp r3,#0x2       @if 2, write a byte
beq Write_B
b End

Write:
str r1, [r2,#0x0] @write the value of Slot 2 into the offset at Slot 3
b End

Write_S:
strh r1, [r2,#0x0]
b End

Write_B:
strb r1, [r2,#0x0]

End:
pop {r0-r3} @optional?
bx lr

.align
Slot_Pointers:
.long 0x030004B8 @address of slot 0

Actually, returning true if the values match is a bit limited. Here’s a version that takes an address in Slot 3 and writes the value there directly into Slot 2. As before, Slot 4 determines the length of the data.

.thumb
.org 0x00000000

@@ DESCRIPTION:
@@ This FE8 hack will write the value of an offset stored in Slot 3 into Slot 2.
@@ Slot 4 value 0/1/2 determines word/short/byte.

Event_Reader:
push {r0-r3} @optional?
ldr r0, Slot_Pointers
ldr r1, [r0, #0xC] @Load the address stored in Slot 3 into r1
ldr r3, [r0, #0x10] @Load the value of Slot 4 into r3
cmp r3,#0x0       @if 0, read a word
beq Read
cmp r3,#0x1       @if 1, read a short
beq Read_S
cmp r3,#0x2       @if 2, read a byte
beq Read_B
b End

Read:
ldr r2, [r1] @Load the value at that address into r2
str r2, [r0,#0x8] @write the value into Slot 3
b End

Read_S:
ldrh r2, [r1]
str r2, [r0,#0x8]
b End

Read_B:
ldrb r2, [r1]
str r2, [r0,#0x8]

End:
pop {r0-r3} @optional?
bx lr

.align
Slot_Pointers:
.long 0x030004B8 @address of slot 0

This way you can use all those conditionals like IFGT, IFLT, IFGE, IFLE. Or you could modify the value using SLOTS_ADD or whatever, and then write it back. Much more powerful.

It has to be divisible by 4 because ldr is, by default, looking at word lengths.

Yes, but the doc says it’s Rd = [Rn + (#<<2)] instead of Rd = [Rn + #], which is why I thought it automatically multiplies by 4.

POP {PC} ignores the least significant bit of the return address (processor
remains in thumb state even if bit0 was cleared), when intending to return with
optional mode switch, use a POP/BX combination (eg. POP {R3} / BX R3).

Huh, well then.

ARM9: POP {PC} copies the LSB to thumb bit (switches to ARM if bit0=0).

IOW, it would work on NDS, but not GBA.

That said, there’s also a weird/neat thing you can do in thumb code where, from a word-aligned address, you do bx pc, and align again after that. The effect is to switch to arm mode and keep going in place.

As for the multiples of 4 thing, I guess it depends on what assembler you’re using. Internally, there’s a 5-bit value, and it counts the number of bytes/halfwords/words according to the size of data you’re loading. So either the assembler expects you to supply the ‘raw’ value that gets stuffed in the opcode, or else it expects the total # of bytes and it does the math for you. And yeah, when it says “# << 2”, it means you do the left-shifting. Apparently.

bx-ing pc works because the program counter on arm devices is where it’s grabbing data from, not where it’s executing. The processor fetches>decodes>executes, and pc is at the fetch, so bx pc will jump you two instructions ahead.

Yep, you got it.