Scaled Experience Formula

In Gen III, the games used a flat experience formula that went like this:

a = 1 if wild, 1.5 if versus battle
t = 1.5 if traded Pokemon, 1 otherwise
b = Base experience yield of defeated Pokemon
e = Lucky Egg boost (1.5)
L = level of defeated Pokemon
p = Pass Power or O-Power bonus
f = Affection heart bonus
v = 1.2 if Pokemon is past the level where it should have evolved (but hasn't evolved), 1 otherwise.
s = If no Pokémon in the party is holding an Exp. Share...
•The number of Pokémon that participated in the battle and have not fainted
If at least one Pokémon in the party is holding an Exp. Share...
•Twice the number of Pokémon that participated and have not fainted, when calculating the experience of a Pokémon that participated in battle
•Twice the number of Pokémon holding an Exp. Share, when calculating the experience of a Pokémon holding Exp. Share
(copy-pasted from Bulbapedia)

Obviously in FireRed/LeafGreen, the only one of these things that matters is L, b, a, e, t and s.

But in Gen V, the games used a scaled formula that took the level difference between you and your opponent into account.

Letters mean the same thing as before. Lp is the winning Pokemon's level

This next routine isn't quite the same as in Gen V, but it does take you and your opponent's levels into account when determining experience.

The formula it uses goes like this:

Here are the heavily commented routines:

Fire Red
Spoiler:
/*Insert 00 48 00 47 XX XX XX 08 at 0x21CE8*/
.text
.align 2
.thumb
.thumb_func

Main:
push {r0-r7}

GetVictorLevel:

/*Gets party position and adds 100*X based on it*/
/*Then gets level and compares it to 0 and 100*/
/*If the Pokemon is level 100, it won’t gain exp.*/
/*If the Pokemon is level 0 (they don’t exist) they won’t gain exp. either*/
/*The level zero check is in case you use an exp. All patch*/
/*If you do, it won’t give exp. to blank party spaces*/

ldr r0, [r5]
ldrb r1, [r0, #0x10]
mov r0, #0x64
mul r0, r1
ldr r1, .FirstPoke
add r0, r0, r1
mov r1, #0x38
bl Decrypt
cmp r0, #0x64
beq Noexp.
cmp r0, #0x0
beq Noexp.
mov r6, r0 /*puts your level in r6*/

CheckIfFainted:

/*Gets current HP of each pokemon and compares it to 0*/
/*If it’s 0, the Pokemon won’t gain exp.*/

ldr r0, [r5]
ldrb r1, [r0, #0x10]
mov r0, #0x64
mul r0, r1
ldr r1, .FirstPoke
add r0, r0, r1
mov r1, #0x39
bl Decrypt
cmp r0, #0x0
beq Noexp.

GetBaseexp.:

/*Gets base exp. from base stats table*/
/*It does this by getting the species of the Defending Pokemon*/
/*Then it multiplies the index number by 0x1c (or 28)*/
/*It adds this to the offset of the Base Stat table*/
/*Then it adds 9 to the offset of the Pokemon’s Base Stats to get base exp.*/

ldr r0, .Defender
ldrb r0, [r0]
mov r4, #0x58
ldr r1, .UserBank
mul r4, r0
add r0, r1, r4
ldrh r1, [r0] /*species index*/
ldr r0, .BaseStatTable
mov r2, #0x1C
mul r1, r2
add r0, r0, r1
mov r2, #0x9
add r0, r0, r2
ldrb r4, [r0] /*puts base exp. in r4*/

GetOpponentLevel:

/*Gets opponent's level and puts into r5*/

ldr r0, .Defender
ldrb r0, [r0]
cmp r0, #0x1
beq Level1
b Level2

Level1:

/*It goes here if the target’s user bank number was 1*/
/*This is always the opponent in a single battle*/
/*In doubles this is the Pokemon on the right*/

mov r1, #0x64
mul r0, r1
ldr r2, .FirstFoeMinus1 /*The RAM offset of the opponent’s first Pokemon - 100*/
b GetTheLevel

Level2:

/*This is the Pokemon on the left in Double Battles*/
/*They have a user bank number of 3*/

mov r1, #0x64
mul r0, r1
ldr r2, .FirstFoeMinus2 /*The RAM offset of the opponent’s first Pokemon - 300*/

GetTheLevel:

/*Gets the RAM for the opponent’s first or second Pokemon*/
/*Depending on the user bank number*/

add r0, r0, r2
mov r1, #0x38
bl Decrypt
mov r5, r0

Multiply:

/*r4 = base exp..*/
/*r5 = foe’s level*/
/*This multiplies the base exp. by the opponent’s level*/
/*Then it multiplies the result by 20*/
/*Then it divides the whole thing by 100*/
/*This is the same as dividing it by 5*/
/*r0 is the top number, r1 is the bottom number*/

mul r4, r5
mov r1, r4 /*r1 is B x L*/
mov r0, #0x14
mul r0, r1 /*r0 = B x L*/
mov r1, #0x64 /*r1 = 100*/
bl Divide /*divides by 100*/
mov r3, r0

LevelStuff:

/*This multiplies the foe’s level by 2*/
/*Then adds 10 to the result*/
/*Then it multiplies that result by itself to square it*/
/*This is the top part of the level ratio*/

mov r2, #0x2
mov r1, r5 /*move foe level to r1*/
mul r1, r2 /*multiply foe level by 2*/
mov r2, #0xA
add r1, r1, r2 /*add 10 to 2 x Foe Level*/
mul r1, r1 /*multiply (2L+10) by itself*/

BottomLevel:

/*r5 = foe’s level*/
/*r6 = your level*/
/*This add your level to the opponent’s level*/
/*Then adds ten to that sum*/
/*multiplies by itself to square it*/

add r5, r5, r6
add r5, r5, r2
mul r5, r5

DivideAgain:

/*r1 = (2L+10)^2*/
/*r5 = (L+Lp+10)^2*/
/*r6 = (B x L) / 5*/
/*Stores answer to r0*/
/*Then moves answer to r2*/

mov r0, r1
mov r1, r5
mov r6, r3 /*move BL/5 to r6*/
mov r3, #0x64 /*multiply top by 100*/
mul r0, r3
bl Divide /*Divide top by bottom*/
mov r2, r0

DivideSomeMore:

/*r6 = BL/5*/
/*r2 = Level Ratio*/
/*Multiplies the 2nd term by 100*/
/*Then divides whole thing by 100*/

mul r6, r2 /*multiply BL/5 by (100*2ndTerm)*/
mov r0, r6
mov r1, #0x64
bl Divide /*Divides (BL/5)*(100*2ndTerm) by 100*/

LoadIntoExp:

/*Stores the final amount into the address at r1*/
/*This address at r1 is used for the final results of things*/
/*I believe the damage calculation for moves uses the same RAM address*/

ldr r1, .Storage /*stores value into exp.*/
mov r2, r9
str r0, [r1]
mov r9, r1

ReturnToItemCheck:

/*This returns to right before the exp.. Share check*/

pop {r0-r7}
ldr r1, .Return2
bx r1

Divide:
ldr r2, .Divider
bx r2

Decrypt:
ldr r2, .Decrypter
bx r2

Noexp.:

/*This returns to right before the skip exp.. gain function*/

pop {r0-r7}
ldr r1, .Return1
bx r1

.align 2
.Return1: .word 0x08021CFF
.Return2: .word 0x08021DA9
.Decrypter: .word 0x0803FBE9
.Divider: .word 0x081E4019
.FirstPoke: .word 0x02024284
.BaseStatTable: .word 0x08254784 /*Put offset of BST here*/
.Attacker: .word 0x02023D6B
.Defender: .word 0x02023D6C
.UserBank: .word 0x02023BE4
.Storage: .word 0x02023D50
.FirstFoeMinus2: .word 0x02023F64
.FirstFoeMinus1: .word 0x02023FC8

Emerald
Spoiler:
/*Insert 00 48 00 47 XX XX XX 08 at 0x4A5AC*/
.text
.align 2
.thumb
.thumb_func

Main:
push {r0-r7}

GetVictorLevel:

/*Gets party position and adds 100*X based on it*/
/*Then gets level and compares it to 0 and 100*/
/*If the Pokemon is level 100, it won’t gain exp.*/
/*If the Pokemon is level 0 (they don’t exist) they won’t gain exp. either*/
/*The level zero check is in case you use an exp. All patch*/
/*If you do, it won’t give exp. to blank party spaces*/

ldr r0, [r5]
ldrb r1, [r0, #0x10]
mov r0, #0x64
mul r0, r1
ldr r1, .FirstPoke
add r0, r0, r1
mov r1, #0x38
bl Decrypt
cmp r0, #0x64
beq Noexp.
cmp r0, #0x0
beq Noexp.
mov r6, r0 /*puts your level in r6*/

CheckIfFainted:

/*Gets current HP of each pokemon and compares it to 0*/
/*If it’s 0, the Pokemon won’t gain exp.*/

ldr r0, [r5]
ldrb r1, [r0, #0x10]
mov r0, #0x64
mul r0, r1
ldr r1, .FirstPoke
add r0, r0, r1
mov r1, #0x39
bl Decrypt
cmp r0, #0x0
beq Noexp.

GetBaseexp.:

/*Gets base exp. from base stats table*/
/*It does this by getting the species of the Defending Pokemon*/
/*Then it multiplies the index number by 0x1c (or 28)*/
/*It adds this to the offset of the Base Stat table*/
/*Then it adds 9 to the offset of the Pokemon’s Base Stats to get base exp.*/

ldr r0, .Defender
ldrb r0, [r0]
mov r4, #0x58
ldr r1, .UserBank
mul r4, r0
add r0, r1, r4
ldrh r1, [r0] /*species index*/
ldr r0, .BaseStatTable
mov r2, #0x1C
mul r1, r2
add r0, r0, r1
mov r2, #0x9
add r0, r0, r2
ldrb r4, [r0] /*puts base exp. in r4*/

GetOpponentLevel:

/*Gets opponent's level and puts into r5*/

ldr r0, .Defender
ldrb r0, [r0]
cmp r0, #0x1
beq Level1
b Level2

Level1:

/*It goes here if the target’s user bank number was 1*/
/*This is always the opponent in a single battle*/
/*In doubles this is the Pokemon on the right*/

mov r1, #0x64
mul r0, r1
ldr r2, .FirstFoeMinus1 /*The RAM offset of the opponent’s first Pokemon - 100*/
b GetTheLevel

Level2:

/*This is the Pokemon on the left in Double Battles*/
/*They have a user bank number of 3*/

mov r1, #0x64
mul r0, r1
ldr r2, .FirstFoeMinus2 /*The RAM offset of the opponent’s first Pokemon - 300*/

GetTheLevel:

/*Gets the RAM for the opponent’s first or second Pokemon*/
/*Depending on the user bank number*/

add r0, r0, r2
mov r1, #0x38
bl Decrypt
mov r5, r0

Multiply:

/*r4 = base exp..*/
/*r5 = foe’s level*/
/*This multiplies the base exp. by the opponent’s level*/
/*Then it multiplies the result by 20*/
/*Then it divides the whole thing by 100*/
/*This is the same as dividing it by 5*/
/*r0 is the top number, r1 is the bottom number*/

mul r4, r5
mov r1, r4 /*r1 is B x L*/
mov r0, #0x14
mul r0, r1 /*r0 = B x L*/
mov r1, #0x64 /*r1 = 100*/
bl Divide /*divides by 100*/
mov r3, r0

LevelStuff:

/*This multiplies the foe’s level by 2*/
/*Then adds 10 to the result*/
/*Then it multiplies that result by itself to square it*/
/*This is the top part of the level ratio*/

mov r2, #0x2
mov r1, r5 /*move foe level to r1*/
mul r1, r2 /*multiply foe level by 2*/
mov r2, #0xA
add r1, r1, r2 /*add 10 to 2 x Foe Level*/
mul r1, r1 /*multiply (2L+10) by itself*/

BottomLevel:

/*r5 = foe’s level*/
/*r6 = your level*/
/*This add your level to the opponent’s level*/
/*Then adds ten to that sum*/
/*multiplies by itself to square it*/

add r5, r5, r6
add r5, r5, r2
mul r5, r5

DivideAgain:

/*r1 = (2L+10)^2*/
/*r5 = (L+Lp+10)^2*/
/*r6 = (B x L) / 5*/
/*Stores answer to r0*/
/*Then moves answer to r2*/

mov r0, r1
mov r1, r5
mov r6, r3 /*move BL/5 to r6*/
mov r3, #0x64 /*multiply top by 100*/
mul r0, r3
bl Divide /*Divide top by bottom*/
mov r2, r0

DivideSomeMore:

/*r6 = BL/5*/
/*r2 = Level Ratio*/
/*Multiplies the 2nd term by 100*/
/*Then divides whole thing by 100*/

mul r6, r2 /*multiply BL/5 by (100*2ndTerm)*/
mov r0, r6
mov r1, #0x64
bl Divide /*Divides (BL/5)*(100*2ndTerm) by 100*/

LoadIntoExp:

/*Stores the final amount into the address at r1*/
/*This address at r1 is used for the final results of things*/
/*I believe the damage calculation for moves uses the same RAM address*/

ldr r1, .Storage /*stores value into exp.*/
mov r2, r9
str r0, [r1]
mov r9, r1

ReturnToItemCheck:

/*This returns to right before the exp.. Share check*/

pop {r0-r7}
ldr r1, .Return2
bx r1

Divide:
ldr r2, .Divider
bx r2

Decrypt:
ldr r2, .Decrypter
bx r2

Noexp.:

/*This returns to right before the skip exp.. gain function*/

pop {r0-r7}
ldr r1, .Return1
bx r1

.align 2
.Return1: .word 0x0804A5C3
.Return2: .word 0x0804A66B
.Decrypter: .word 0x0806A519
.Divider: .word 0x082E7541
.FirstPoke: .word 0x020244EC
.BaseStatTable: .word 0x083203CC /*Put offset of BST here*/
.Attacker: .word 0x0202420B
.Defender: .word 0x0202420C
.UserBank: .word 0x02024084
.Storage: .word 0x020241F0
.FirstFoeMinus2: .word 0x0202467C
.FirstFoeMinus1: .word 0x020246E0

Just a note, the game will glitch if you gain too much experience at once, specifically if you gain more than 54,621 experience points (there could be a higher cap, but 54,621 experience points is the highest I've been able to go).

(54,621 was the amount of experience a level one Pokemon gained from defeating a level 100 Pokemon with a base exp of 255 in a trainer battle while holding a Lucky Egg that was changed to double experience.)

In order to prevent yourself from gaining too much experience at once, you will want to do either one of two things. The first is to disable gaining 1.5 times experience when using a traded Pokemon. The second is to disable gaining 1.5 times experience when fighting during a trainer battle.

Which you do is up to you. In Gen 7, the trainer battle bonus was taken out, if that affects your decision in any way.

To disable the trainer battle bonus:
Put 07 E0 at 0x21DD8 (FireRed), or 0x4A698 (Emerald)

To disable the traded Pokemon bonus:
Put 1C E0 at 0x21E00 (FireRed) or put 28 E0 at 0x4A6C0 (Emerald)

If you're feeling sad that you can't gain 99,999 experience points, don't be too disheartened. 54,621 experience points is enough bring a level one Pokemon to somewhere in their 30s depending on their growth rate. It should be plenty.

This is compatible with Think0028's Exp. All patch as well. If you're using that you might want to change the offset at .Return2 to 0x21DBB. That's where the Lucky Egg check is. You'll skip the Exp. Share check, which isn't a problem if you're using an Exp. All patch anyway. I originally developed this routine with that patch and that's where I returned to and it worked fine.

That same offset in Emerald is 0x4A67B. I don't know if there's an equivalent patch for Emerald or not, but I've been providing offsets for both games this whole time, might as well do it here too just in case. :)
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo