• Our software update is now concluded. You will need to reset your password to log in. In order to do this, you will have to click "Log in" in the top right corner and then "Forgot your password?".
  • Welcome to PokéCommunity! Register now and join one of the best fan communities on the 'net to talk Pokémon and more! We are not affiliated with The Pokémon Company or Nintendo.

ASM Workshop

Blah

Free supporter
1,924
Posts
11
Years
  • So I practice at task 1 all night (literally haven't went to sleep).

    To no avail I still can't seem to figure out how you would loop it and
    do the comparison of 6 possible Pokemon's CHP and THP.
    Nor could I understand just how to keep track of the slot of the Pokemon ergo 0-5.

    Here is what I came up with
    Partial Routine(if it can be called even that)
    Spoiler:


    I tested it in a clean rom as well and it does not work in any sense... I couldn't even get the game to bug out or freeze >_< sadly hahahaha

    Well any advice and help is much appreciated an thank you for your time

    Ps also thank you FBI Agent for this thread

    Oh man there's quite a bit wrong here. I think that looking at the solution video will probably be much more clearer as to how to achieve the routine's functionality.

    Speaking of solution videos, I made a solution video for task #1. I'm thinking the video is too long, and too indept. Would you guys prefer briefer and less detailed versions for the rest of the tasks?

     

    Touched

    Resident ASMAGICIAN
    625
    Posts
    9
    Years
    • Age 122
    • Seen Feb 1, 2018
    So I practice at task 1 all night (literally haven't went to sleep).

    To no avail I still can't seem to figure out how you would loop it and
    do the comparison of 6 possible Pokemon's CHP and THP.
    Nor could I understand just how to keep track of the slot of the Pokemon ergo 0-5.

    Here is what I came up with
    Partial Routine(if it can be called even that)
    Spoiler:


    I tested it in a clean rom as well and it does not work in any sense... I couldn't even get the game to bug out or freeze >_< sadly hahahaha

    Well any advice and help is much appreciated an thank you for your time

    Ps also thank you FBI Agent for this thread

    Please COMMENT your code so that your intention is clear. If you add comments, we can at least try to understand your reasoning and be of greater assistance.
    Also, some life advice: sleep. A good night's rest can make the solution yesterday's impossible problem clear. The same goes for taking regular breaks - your mind works out solutions even when you're not actively thinking about something.

    Now, lets get onto your problem. From glancing at your code, it's clear you are missing the point of task one, which I'm guessing was to teach you how to use a loop. You are just copying and pasting code for each of the 6 Pokemon. A loop is perfect for this kind of problem, as you just reuse code perform one action to perform that action multiple times. Below is the anatomy of a basic loop to help you understand the concept.

    Code:
    @ Basic loop (FOR loop)
    
    @ Set r0 to 0. We're keeping track of how many iterations we've done, 
    @ starting from 0
    @ This is the INITIALISATION phase
    
    mov r0, #0
    
    @ Use a label to mark the start of the loop so the assembler knows where
    @ to jump back to
    loop_start:
    @ The BODY of the loop goes here. This is the code we will be repeating.
    @ typically we will use the counter in r0 in conjunction with some 
    @ basic arithmetic to keep track of a memory address or something
    
    @ Now we INCREMENT the counter to mark that ONE iteration is complete
    add r0, #1
    
    @ Now comes the CONDITION
    @ The condition tells the loop when it can exit. Without the condition, 
    @ the loop would repeat indefinitely. This is (in most cases) undesirable 
    @ as it would cause the game to freeze
    cmp r0, #6
    
    @ Repeat the loop if the counter is LESS THAN 6
    blt loop_start
    
    @ We are now out of the loop
    @ Code to execute after the loop is complete

    EDIT:
    Oh man there's quite a bit wrong here. I think that looking at the solution video will probably be much more clearer as to how to achieve the routine's functionality.

    Speaking of solution videos, I made a solution video for task #1. I'm thinking the video is too long, and too indept. Would you guys prefer briefer and less detailed versions for the rest of the tasks?

    That video seems to be private?
     
    Last edited:

    Blah

    Free supporter
    1,924
    Posts
    11
    Years
  • Please COMMENT your code so that your intention is clear. If you add comments, we can at least try to understand your reasoning and be of greater assistance.
    Also, some life advice: sleep. A good night's rest can make the solution yesterday's impossible problem clear. The same goes for taking regular breaks - your mind works out solutions even when you're not actively thinking about something.

    Now, lets get onto your problem. From glancing at your code, it's clear you are missing the point of task one, which I'm guessing was to teach you how to use a loop. You are just copying and pasting code for each of the 6 Pokemon. A loop is perfect for this kind of problem, as you just reuse code perform one action to perform that action multiple times. Below is the anatomy of a basic loop to help you understand the concept.

    Code:
    @ Basic loop (FOR loop)
    
    @ Set r0 to 0. We're keeping track of how many iterations we've done, 
    @ starting from 0
    @ This is the INITIALISATION phase
    
    mov r0, #0
    
    @ Use a label to mark the start of the loop so the assembler knows where
    @ to jump back to
    loop_start:
    @ The BODY of the loop goes here. This is the code we will be repeating.
    @ typically we will use the counter in r0 in conjunction with some 
    @ basic arithmetic to keep track of a memory address or something
    
    @ Now we INCREMENT the counter to mark that ONE iteration is complete
    add r0, #1
    
    @ Now comes the CONDITION
    @ The condition tells the loop when it can exit. Without the condition, 
    @ the loop would repeat indefinitely. This is (in most cases) undesirable 
    @ as it would cause the game to freeze
    cmp r0, #6
    
    @ Repeat the loop if the counter is LESS THAN 6
    blt loop_start
    
    @ We are now out of the loop
    @ Code to execute after the loop is complete

    EDIT:

    That video seems to be private?
    idk why its private by default. I've fixed it now, sorry :c
     
    794
    Posts
    10
    Years
  • This thread seems very promising. Thanks FBI!
    Earlier, I did two first tasks, but I made some mistakes in them and looked up solutions. That's why I tried to do the challenge task on my own and I think I got it right. I tested my routine and it works fine but after deleting a pokemon weird things happen. I can't do anything about it though.
    My code:
    Spoiler:

    I know that my code could be smaller but I don't have any idea how that could be done without messing it up.
     

    Touched

    Resident ASMAGICIAN
    625
    Posts
    9
    Years
    • Age 122
    • Seen Feb 1, 2018
    This thread seems very promising. Thanks FBI!
    Earlier, I did two first tasks, but I made some mistakes in them and looked up solutions. That's why I tried to do the challenge task on my own and I think I got it right. I tested my routine and it works fine but after deleting a pokemon weird things happen. I can't do anything about it though.
    My code:
    Spoiler:

    I know that my code could be smaller but I don't have any idea how that could be done without messing it up.

    Good job and congratulations on completing your first ASM challenge ^^

    What weird things happen? To make your code neater (and more maintainable) you can calculate the amount to add to the party address instead of the whole "check_which_one" thing. Since you have the slot number in r1 and r0 is free:
    Code:
    mov r0, #100
    mul r1, r0 @ Multiply slot by 100

    Then you can load the base address (0x02024284) and add this value to it:
    Code:
    ldr r0, =0x02024284
    add r0, r1
    
    @ Continue
    b delete

    Note I use MUL only because 100 is not trivially computable. MUL is an expensive operation and thus should only be used where necessary (otherwise use bitshifts and addition/subtraction).

    Other optimizations include:
    • You don't need the push and pop. You never need to push r0-r3 to the stack at the start of a function.
    • 100 is divisible by 4. Use str instead of strb to conserve write operations.

    EDIT: I noticed one error:
    At the top you do:
    Code:
        ldr r1, =(0x020370D0) @var 0x800D aka LastResult
        ldr r1, [r1]@we get the value of that var
    That second ldr should be a ldrh. Variables are always halfwords. You're going to read too much data into r1 rather than just the data in 0x800D like you intended.
     
    Last edited:

    Blah

    Free supporter
    1,924
    Posts
    11
    Years
  • Ahem, just going to do a small text tutorial for tasks 2 and 3, then introduce the next tasks in another post. I've included comments inside the routines. Take the time to read those if you're unsure still. Make sure to ask questions as they come up.

    Task #2:

    Code:
    .text
    .align 2
    .thumb
    .thumb_func
    
    main:
    	push {r0-r1, lr}
    
    	@load address of trainer data's pointer
    	ldr r0, =(0x300500C)
    
    	@get pointer to trainer data
    	@for more info about trainer data's structure see the hints
    	ldr r0, [r0]
    
    	@r0 contains address of trainer's name we write characters into that address
    	@first char is BC or "B" in ASCII
    	mov r1, #0xBC
    	@we store the char in R1 into the address in r0
    	strb r1, [r0]
    
    	@second char is D5 or "A" in ASCII
    	mov r1, #0xD5
    
    	@you'll notice, the second character comes after the first, so we need to add 1
    	@to the address in R0. This is because R0 is a pointer to the first char of the name
    	@using strb to add the one is an easy and efficient way to do that
    	@[r0, #0x1] means add 1 to r0. I.e the next char
    	strb r1, [r0, #0x1]
    
    	
    	mov r1, #0xDF
    	@you'll notice here we're adding 2, for the third char
    	@the concept of storing the byte is exactly the same
    	strb r1, [r0, #0x2]
    	mov r1, #0xD5
    	strb r1, [r0,#0x3]
    	pop {r0-r1, pc}
    
    .align 2

    Task #3:

    Code:
    .text
    .align 2
    .thumb
    .thumb_func
    
    main:
    	push {r0-r2, lr}
    
    	@load halfword from var 0x8000
    	@0x20370B8 is the address of this var
    	ldr r2, =(0x20370B8)
    	ldrb r2, [r2]
    
    	@0x2024284 is the address of the first Pokemon's data structure
    	ldr r0, =(0x2024284)
    
    	@do: 0x2024284 + (100 * value of var 0x8000)
    	@this gives the slot number of the pokemon we want to delete since
    	@each Pokemon takes 100 or 0x64 bytes in the RAM. Take a second to let that sink in.
    	mov r1, #0x64
    	mul r2, r2, r1
    	add r0, r0, r2
    	
    @when the code gets here, we've established which Pokemon to delete
    @the address to their data will be in r0
    eraseLoop:
    	@in RAM, free space is 0x0
    	@here we write 0s into where the Pokemon's data would be
    	@notice we're using strb, so we're writing 1 byte at a time
    	mov r2, #0x0
    	strb r2, [r0]
    
    	@r1 was 0x64 before the erase loop section is being called.
    	@during each iteration of this loop, we subtract 1 from r1, effectively making it decrement in value
    	@once r1 is 0x1, then we must've written 0s for 64 bytes. So if  R1 is 0x1, then we're done "deleting" the mon
    	cmp r1, #0x1
    	beq end
    
    	@here is the R1 subtraction mentioned earlier
    	sub r1, r1, #0x1
    	@although we subtract r1, we need to increment the address in r0 to which we'r writing 0s to.
    	@we don't want to write a 0x0 byte to the same address every time, we want a sequence of 0x64 0s
    	add r0, r0, #0x1
    
    	@loop again. This loop doesn't exit till r1 is equal to 0x1
    	b eraseLoop
    	
    end:
    	pop {r0-r2, pc}
    
    .align 2
     
    3,044
    Posts
    9
    Years
  • Here's what I've got so far:

    Spoiler: Task 1
    .text
    .align 2
    .thumb
    .thumb_func


    main:
    push {r0-r3, lr}
    ldr r0, =(0x2024284 + 0x56)
    mov r3, #0x0

    loop:
    cmp r3, #0x6
    beq GetSlot
    ldrh r1, [r0]
    ldrh r2, [r0, #0x2]
    cmp r2, r1
    bne GetSlot
    add r3, r1, #0x1
    add r0, r0, #0x64
    b loop

    GetSlot:
    ldr r0, =(0x20370D0)
    strh r3, [r0]

    end:
    pop {r0-r3 pc}

    .align 2

    Spoiler: Task 2
    .text
    .align 2
    .thumb
    .thumb_func

    main:
    push {r0-r1, lr}
    ldr r0, playerData
    ldrh r1, [r0]

    mov r1, #0xBC
    strb r0, [r1]
    add r1, r1, #0x1
    mov r1, #0xD5
    strb r0, [r1]
    add r1, r1, #0x1
    mov r1, 0xDF
    strb r0, [r1]
    add r1, r1, #0x1
    mov r1, #0xD5
    strb r0, [r1]
    add r1, r1, #0x1
    mov r1, #0xFF
    strb r0, [r1]

    pop {r0-r1, pc}

    .align 2

    .playerData:
    .word 0x300500C


    I'm gonna start with task 3 later. Boy, ASM is sure hard :P Time for studying.
     
    Last edited:

    daniilS

    busy trying to do stuff not done yet
    409
    Posts
    10
    Years
    • Seen Jan 29, 2024
    Here's what I've got so far:

    Spoiler: Task 1
    .text
    .align 2
    .thumb
    .thumb_func


    main:
    push {r0-r3, lr}
    ldr r0, =(0x2024284 + 0x56)
    mov r3, #0x0

    loop:
    cmp r3, #0x6
    beq GetSlot
    ldrh r1, [r0]
    ldrh r2, [r0, #0x2]
    cmp r2, r1
    bne GetSlot
    add r3, r1, #0x1
    add r0, r0, #0x64
    b loop

    GetSlot:
    ldr r0, =(0x20370D0)
    strh r3, [r0]

    end:
    pop {r0-r3 pc}

    .align 2

    Spoiler: Task 2
    .text
    .align 2
    .thumb
    .thumb_func

    main:
    push {r0-r1, lr}
    ldr r0, playerData
    ldrh r1, [r0]

    mov r1, #0xBC
    strb r0, [r1, #0x1] @I'm not too sure about this. Does it add 1?
    mov r1, #0xD5
    strb r0, [r1, #0x1]
    mov r1, 0xDF
    strb r0, [r1, #0x1]
    mov r1, #0xD5
    strb r0, [r1, #0x1]
    mov r1, #0xFF
    strb r0, [r1]

    pop {r0-r1, pc}

    .align 2

    .playerData:
    .word 0x300500C


    I'm gonna start with task 3 later. Boy, ASM is sure hard :P Time for studying.

    strb r0, [r1, #0x1] doesn't increment r1 (in case of doubt, check GBATEK).
     

    Touched

    Resident ASMAGICIAN
    625
    Posts
    9
    Years
    • Age 122
    • Seen Feb 1, 2018
    FBI stop wasting stack space :(. You don't need to push r0-r3 at the start of a function because ARM calling convention uses those to pass function arguments and thus expects those to be messed up on return anyway. Also, you only have to push LR when you have something that overwrites it in the code (BL). You should properly explain what the stack is - and what it is for. Also, you might want to give an example of a routine that shows push/pop being used in the middle - some people seem to think that they can ONLY be used at the start and end of a routine.

    This brings me onto a second point. There seems to be a culture here of just writing ASM. But what happened to reading it? We would be useless at writing human languages if we didn't read enough, so why should code be any different? Learning to read and understand other people's code is probably the best way to learn - so encouraging beginners to read and debug the existing game code should be essential.
    Currently the research side of ASM hacking is shrouded in mystery - no one outside of our little clique is even aware of IDA - or if they are, they don't know how to use it. This seems to elevate us to almost God-like status simply because we can reverse engineer and find things out for ourselves.

    Maybe we should all collaborate and do live Skype/Hangouts lessons or something to work on fixing these issues.
     
    3,044
    Posts
    9
    Years
  • strb r0, [r1, #0x1] doesn't increment r1 (in case of doubt, check GBATEK).

    ah, so I was wrong. thanks! I'm gonna go edit it. I have 3 hours of free time, so good luck me. :P

    edit: corrected task 2.
     
    Last edited:

    Blah

    Free supporter
    1,924
    Posts
    11
    Years
  • FBI stop wasting stack space :(. You don't need to push r0-r3 at the start of a function because ARM calling convention uses those to pass function arguments and thus expects those to be messed up on return anyway. Also, you only have to push LR when you have something that overwrites it in the code (BL). You should properly explain what the stack is - and what it is for. Also, you might want to give an example of a routine that shows push/pop being used in the middle - some people seem to think that they can ONLY be used at the start and end of a routine.

    This brings me onto a second point. There seems to be a culture here of just writing ASM. But what happened to reading it? We would be useless at writing human languages if we didn't read enough, so why should code be any different? Learning to read and understand other people's code is probably the best way to learn - so encouraging beginners to read and debug the existing game code should be essential.
    Currently the research side of ASM hacking is shrouded in mystery - no one outside of our little clique is even aware of IDA - or if they are, they don't know how to use it. This seems to elevate us to almost God-like status simply because we can reverse engineer and find things out for ourselves.

    Maybe we should all collaborate and do live Skype/Hangouts lessons or something to work on fixing these issues.

    Gahh, you skip too many steps dear Touched. I'll explain things slowly and introduce concepts as they show up. For the first week I let the pushing and popping of r0-r3 slide. I'm just making an effort to keep things simple atm, more of that stuff you were talking about when I get to functions. I think that without understanding how function calling works, just saying r0-r3 shouldn't be pushed/popped is pretty bad for understanding.

    As for the collab video stuff, how would one record a skype session? I'm not sure that's possible, also if we did a skype session I would have to put forth great effort to keep my language PG13. I'm ruling out doing it live because everyone's timezones are different, sometimes drastically. If you could figure out something, I'd love to follow through~
     

    Touched

    Resident ASMAGICIAN
    625
    Posts
    9
    Years
    • Age 122
    • Seen Feb 1, 2018
    Gahh, you skip too many steps dear Touched. I'll explain things slowly and introduce concepts as they show up. For the first week I let the pushing and popping of r0-r3 slide. I'm just making an effort to keep things simple atm, more of that stuff you were talking about when I get to functions. I think that without understanding how function calling works, just saying r0-r3 shouldn't be pushed/popped is pretty bad for understanding.

    Fair enough - as long as you introduce it soon. An IDA lesson where you dissect and explain a routine might be worth considering.

    As for the collab video stuff, how would one record a skype session? I'm not sure that's possible, also if we did a skype session I would have to put forth great effort to keep my language PG13. I'm ruling out doing it live because everyone's timezones are different, sometimes drastically. If you could figure out something, I'd love to follow through~

    I don't think we need keep our language PG13 since it wouldn't really be affiliated with the community - I would also have a hard time censoring myself. Maybe we shouldn't record them and instead take the time to learn more about the questions everyone has; we can then compile a FAQ or something afterwards. Also, the timezones don't seem to stop people joining in on the IRC. Maybe we should find out if people are interested first and what zone they are in before attempting to solve that problem though?
     

    Blah

    Free supporter
    1,924
    Posts
    11
    Years
  • Week 2 (kinda)

    Writing your own functions, and calling other functions.

    Before you begin to attempt to write your own function, it's important you understand how function calling and functions in general, are normally handled.
    A good example for the starting ASM-er is the string copy function.

    Before we progress there's still some things worth mentioning about strings in ASM. Strings, are like words in real life. They're a sequence of individual characters which are strung together to make a word. Strings can also be multiple words, with the " " or space character seperating two sets of characters.
    Basically, on a low level, every alphanumeric character has a hex equivalent byte value. When these bytes are strung together they can make a word. For example:
    Code:
    "Hello" in hex is "C2 D9 E0 E0 E3"
    Notice each character has a corresponding byte. "e" is 0xD9.

    Like this, many bytes are strung together to make a sentence.
    Code:
    Hello, how are you feeling today?
    C2D9E0E0E3B800DCE3EB00D5E6D900EDE3E900DAD9D9E0DDE2DB00E8E3D8D5EDAC
    But, using this convention, how do we know we've reached the end of the string? Well, strings in ASM are typically 0xFF terminated. In otherwords, when an 0xFF is read, that generally means the string has reached it's end. You may see in languages like C, this is represented with the character '\0'.

    Finally, the last thing to understand about strings in ASM, is that they're not stored into registers as values. Strings in ASM aren't always guarenteed to have an even number of characters. So you can't read a string using ldr or ldrh because you risk potentially having alignment issues (which is something we'll talk about another day). Because of these alignment issues, strings are read 1 byte at a time.
    But how do we know when or how to get the next Character. Normally, when processing a string, the pointer to the string is kept in a register.

    Lets say the pointer to my string "Hello" was kept in r0. What the pointer of the string really is, is a pointer to the first character of the string. So if r0 = 08740003 then 0x8740003 is the pointer to the "H" in "Hello". So how do we get the next character? Well the next character is simply whatever the address to the start is, plus 1.
    This is because characters only take up one byte. Adding one to the pointer to the first character would simply just give you the pointer of the second character. You know that you're at the end of the string when the value at the pointer is 0xFF.
    That concludes my mini-lecture about strings :P

    Some pretex to the str_cpy function, so you can understand what it does. Basically it just copies an 0xFF terminated string given the address to the start of the string, to another writable address.
    It's mainly used when you want to copy a string from the ROM into RAM, for example, a Pokemon's name.

    Once again to clarify, the function string copy, takes two parameters. The second parameter is the location of the string (pointer) and the first parameter is the place to put the string given in the second parameter.

    Code:
    .text
    .align 2
    .thumb
    .thumb_func
    
    main:
    	push {lr}
    	mov r3, r0
    	b str_copy
    	
    storeChar:
    	strb r2, [r3]
    	add r3, r3, #0x1
    	add r1, r1, #0x1
    	
    getChar:
    	ldrb r2,[r1]
    	mov r0, r2
    	cmp r0, #0xFF
    	bne storeChar
    	mov r0, #0xFF
    	strb r0,[r3]
    	mov r0, r3
    	@pop {r1}
    	@bx r1
    	pop {pc}
    	
    .align 2

    So above is the function string copy I explained prior. Before analyzing the function there are a few notes which I'd like to add about functions in general, and specifically this one.

    First of all, you'll notice that I've commmented out
    Code:
    pop {r1}
    bx r1
    These instructions are almost equivalent to pop {pc}. Actually I'd argue they may be better, because they guarentee that we're return in thumb mode. Anyways the whole concept of poping an unpushed register then bx-ing to it
    is still a little ahead of our feable minds. We'll just assume that these couple of instructions are equivalent to pop {pc}.

    The second thing to notice, is that this function is using registers r0-r3, however it hasn't pushed or popped them. By ASM standards, functions can assume that r0-r3 are parameters. As the function caller, you must assume that registers below r4 will be modified by the function. This implies that a function is free to locally modify these registers.
    To recap, r0-r3 are assumed to be always modified by the function, r0-r3 are also parameters to the function, if the function takes parameters.
    So now, if it's assumed that r0-r3 are overwritten, there is no point in pushing them and wasting space on the stack, so we generally don't. You only really ever push a register if you want to save it's value. But function standards suggest there is no point in saving the value, so we don't push.

    Finally, the order of the parameters are the order of the registers. That is the first parameter would be in r0, the second in r1 and so on. Note that functions have a maximum about of 4 parameters (r0-r3) for now. We will look at functions with more than 4 at a later time.

    The rest of this function, besides the pushing and popping, is very straight forward. I've tried to use meaningful label names. Before progressing further, attempt to write comments for the function. The comments should be meaningful. Don't try to teach ASM to the reader, instead try and explain what's happening in the function.

    Commented version (see after you've written your own comments).
    Spoiler:


    The last thing about this function, which I hope you noticed, is this line here:
    Code:
    mov r0, r3
    pop {pc}

    Remember that we said the caller of the function assumes that r0-r3 have been completely modified? Well, that's true, but sometimes r0-r3 are modified into something meaningful and useful. Whats happened in this case is that the string copy function has also conveniantly placed the destination it's copied the string into in r0.
    As you may be able to guess, r0-r3 are also return values of the function, if the function has any. If the function doesn't return anything, then r0-r3 are assumed to be messed up and random :)

    Recap:
    Functions take parameters in r0-r3. r0 = first param, r1 = second param, ...ect
    Functions return possible return values in r0-r3
    Functions will not push r0-r3, so assume they are modified/random values when it returns, if not return values


    Keeping these newly learned facts inmind, lets design our own function. We want to make a function that does something useful, but abides by normal function rules so it's usable in current game code.
    I think one of the best function to write for this task, which the game is conveniantly missing, is a function which does RAM to RAM comparisons.
    That is, a function which checks if two areas of RAM/ROM are equal/the same.

    Now that we've got a goal for a function we want to write, we need to identify key parts of the function which we'll be writing. Consider:
    1) What parameters will this function take?
    2) What is the return value, if any?

    Answers (look after you decide):
    Spoiler:


    Simple enough right? Lets get cracking on the code itself.


    Code:
    .text
    .align 2
    .thumb
    .thumb_func
    
    main:
    	push {r4, lr}
    	
    getVals:
    	cmp r2, #0x0
    	beq equal
    	ldrb r3, [r0]
    	ldrb r4, [r1]
    	cmp r3, r4
    	bne notEqual
    	sub r2, r2, #0x1
    	add r0, r0, #0x1
    	add r1, r1, #0x1
    	b getVals
    	
    equal:
    	mov r0, #0x0
    	b end
    	
    notEqual:
    	mov r0, #0x1	
    	
    end:
    	pop {r4, pc}
    	
    .align 2

    The code is very much similar to the str_cpy function we were analyzing at the start. The only real differences are that we're not looking at 0xFF terminated strings. Instead we're examining bytes uptil the size matches our size parameter OR at some points the bytes don't match.
    How do you test if this code works? In most programs, it's sufficient to test max, empty and the average case. So if we test that it works when:
    Size = 0, Size = 40, Size = 100. Note that 100 bytes is definitely not the max case. However, due to the nature of this program, the max size case is simply the highest size of an unsigned int a register is capable of holding. However, our ROMs aren't big enough to hold 2 distinct data structures of said size. So I'm assuming that the code will work for something greater if it works for 100. A reasonable assumption given the nature of the code.

    The last sort of issue or question, would probably be as to why we're only modifying the size field for the rest cases. Well, the byte reading regardless doesn't change, it's a looped algorithm (actually if you're into real analysis, you can mathematically prove that it doesn't matter: hint use induction).

    Calling functions:

    Until now, the only way you've been calling a function is probably using the "callasm" scripting command. Though, you may know that ASM routines/functions are also called within other routines. For example, when I'm displaying the trainer card, various routines which handle the graphics are called, other routines which handle the display text, and status checking routines (for the stickers and badges) are called as well.
    Like this, we may have the need to call function inside routines themselves. How is this done?

    First of all, lets take a look at the difference between calling and jumping to a routine. When you jump to some code, we go directly from a certain area of code right to your routine. Doing this is often done by modifying the program counter directly via mov pc, or using bx, like so:

    Code:
    @Jumping while directly modifying pc
    @Note that this way, you don't need to add +1 to the offset
    @however, you must GUARENTEE that the routine is jumping to
    @a thumb routine. mov pc can also go to ARM code
    ldr r1, =(0x8Address)
    mov pc, r1 
    
    
    @jumping using bx
    @this is my preferred way of jumping, and the way you should use
    ldr r1, =(0x8Address +1)
    bx r1

    There is one sort of, short comming in these methods. Consider that I just want to use a function then continue my own, something like this:

    Code:
    push {lr}
    @use function that returns a Pokemon who is not full HP
    @use another function to buffer that Pokemon's name
    pop {pc}

    In the above case, we have no way of returning back to where we were if we called the function to return the Pokemon who's not full HP. Ingame, most functions are called using "bl" which is the branch with link command. It writes to the link register the address it's branching from. Normally at the start of a function, the link register is pushed via push {lr}, so when a pop {pc} happens we'd return to whatever the link register was pre-push.
    That allows for code like this:

    Code:
    main:
    	push {lr}
    	bl func1
    	bl func2
    	pop {pc}
    	
    	
    func1:
    	push {lr}
    	@do stuff
    	pop {pc}
    	
    func2:
    	push {lr}
    	@do stuff
    	pop {pc}

    That's quite simple to understand I think, so I'm going to introduce another problem. the "bl" command is limited in the distance it can branch to, however, it's the only branching command that writes to the link register.
    Is there a smart way for us to combine the "bx" command which does the long distance jump, and the bl to write to the link register?

    solution:
    Code:
    ldr r1, =(0x8FunctionAddress +1)
    bl linker
    @more code here
    
    linker:
    	bx r1

    Take a second to try and understand how this is working. Where is the link register written to? What's the next executed command after the function returns?
    bl linker, writes to the link register. So when we write to the link register we're also branching to the label "linker". However, at linker, we have just a simple bx r1. Which is a long distance branch. By combining the two commands, we've made some code that calls far away functions AND writes to the link register. Thus supporting "returning".

    Now we know how to call functions in our routines, as well as how to make functions in general. We can now begin the tasks for this week.

    Task 1)
    Write a function which iteratively displays the nth fibonacci number. You don't need to worry about the recursive solution. Assume Fib(0) and Fib(1) = 1.
    Ultimately, variable 0x8000 (address 0x20370B8) will have the value "n" in it to represent the fibonacci number you want to retrieve. Note that functions return in r0-r3, but in this case, we will just write to var 0x8000 the return value. The reason for this is easy testing via script.
    Feel free to hardcode the first two values. Recall, Fib(n) = Fib(n-1) + (Fib n-2)

    Task 2)
    There is a PRNG or pseudo random number generator in FireRed. It's located at 0x8044EC8. The function doesn't take any parameters, but has a return value in r0. The return value
    is the random number. Make a routine which will call the function, and save the function's return value in variable 0x8000

    Task 3)
    Combine Task 1 and Task 2. Make a function which calls Task 2 to generate a random number and save it in 0x8000. Then call the function made in task 1 to generate the fibonacci number given by the random function.
    Note that this function is only four lines long. It should take only 15 seconds to write :)


    Challenge)
    Generate a random number and check if that random number is a fibonacci number. Display in a script, "[number] is not a fibonacci number" or "[number] is a fibonacci number.
    Script:
    Code:
    #dyn 0x740000
    #org @start
    lock
    faceplayer
    callasm 0x8[Func +1] @adjust this offset
    compare 0x8000 0x0
    if == jump @match
    storenumber 0x0 0x8000
    msgbox @nomatch
    callstd MSG_NORMAL
    release
    end
    
    #org @match
    storenumber 0x0 0x8000
    msgbox @matching
    callstd MSG_NORMAL
    release
    end
    
    #org @matching
    = \v\h02 is a fib number
    
    #org @nomatch
    = \v\h02 is not a fib number

    Next time we will look at calling your own functions from existing functions, i.e hooking. Hopefully we'll also get some good exposure to IDA when that happens~
     
    Last edited:
    794
    Posts
    10
    Years
  • Another week, another challenges, yay
    So, I was able to successfully write the first routine. I think however, that I could have written it better. I didn't use this formula: Fib(n) = Fib(n-1) + (Fib n-2). Instead, I did it my own way. Anyway, the full solution for task 1 for week 2 :
    Spoiler:
     

    jiangzhengwenjzw

    now working on katam
    181
    Posts
    11
    Years
    • Seen today
    Generate a random number and check if that random number is a fibonacci number. Display in a script, "[number] is not a fibonacci number" or "[number] is a fibonacci number.

    I've written the challenge task but it should have some errors as I haven't adjusted it thoroughly and I haven't tested it yet.
    Here's the code:
    Spoiler:
    This one will write 0 or 1 to 0x8001, and the random number to 0x8000 so it's simple to use it in scripts by "buffernumber" command.

    I will test and fix it when I have free time.
     
    Last edited:

    kearnseyboy6

    Aussie's Toughest Mudder
    300
    Posts
    15
    Years
    • Seen Jun 22, 2019
    Task 1: Assuming the iterations can be chosen by the user, this routine should do it (not exactly 4 lines yet)

    Spoiler:

    Task 2: Not sure if this is structured the most efficient way, but you have to bl don't you?

    Spoiler:


    Task 3: Just a quick put together, not sure how the pnrg works, so I didn't really do a check on say 65000 iterations.

    Spoiler:

    Challenge: I am really happy with this one, especially the logic with checking it isn't a fibonacci number. Here's the logic:
    Spoiler:


    Spoiler:
     
    Last edited:

    jiangzhengwenjzw

    now working on katam
    181
    Posts
    11
    Years
    • Seen today
    Task 1: Assuming the iterations can be chosen by the user, this routine should do it (not exactly 4 lines yet)

    Spoiler:

    Task 2: Not sure if this is structured the most efficient way, but you have to bl don't you?

    Spoiler:


    Task 3: Just a quick put together, not sure how the pnrg works, so I didn't really do a check on say 65000 iterations.

    Spoiler:

    Challenge: I am really happy with this one, especially the logic with checking it isn't a fibonacci number. Here's the logic:
    Spoiler:


    Spoiler:
    Hey, I'm a newbie and I haven't seen all of your routine but there's some obvious error in it.
    Firstly you should add 1 to the pointer of prng because it's a thumb function. (Sometimes you misspelled it into pnrg, as well)
    Then to load 5 into r0, you can use mov r0, #0x5. The immediate number can be up to 0xFF. (This will save space)
    The way you prevent an infinite loop from happening is better than mine, though. :) (I should have considered it thoroughly before posting it)
     
    Last edited:

    kearnseyboy6

    Aussie's Toughest Mudder
    300
    Posts
    15
    Years
    • Seen Jun 22, 2019
    Hey, I'm a newbie and I haven't seen all of your routine but there's some obvious error in it.
    Firstly you should add 1 to the pointer of prng because it's a thumb function. (Sometimes you misspelled it into pnrg, as well)
    Then to load 5 into r0, you can use mov r0, #0x5. The immediate number can be up to 0xFF. (This will save space)
    The way you prevent an infinite loop from happening is better than mine, though. :) (I should have considered it thoroughly before posting it)
    1) Yes you are right, I should have added the +1. I sure did spell pnrg differently too!
    2) The idea behind that was to get the user to pick a number then load it into r0, but that is still wrong because I only loaded the address:
    Code:
    ldr r0, address
    ldrb r0, [r0]

    Thanks for pulling me up on that btw :)
     
    39
    Posts
    8
    Years
    • Seen Jul 29, 2018
    Hey guys, i guess that comes a bit late but i just found this thread yesterday. I solved the second task of the first week:

    Spoiler:


    i guess i will go on and make the current weeks tasks next...
     

    Blah

    Free supporter
    1,924
    Posts
    11
    Years
  • Unfortunately this project became unfeasible rather quickly. The reason was because people were posting untested routines here. If you don't know if it even compiles, then I would have to compile -> insert -> test it ->diagnose error -> report back to you. Doing this for everyone's routine is something I couldn't be asked to do. For now it is easy, but when things got more complicated, it'd be ridiculous :)
     
    Back
    Top