Researches on Battle History Password

Battle History Password

At the Battle History page (accessed from the Extras menu after completing the game at least once), hold down Select for a couple of seconds. A password will appear, which doesn’t do anything. In the Japanese version, players could submit this to the official site for a contest of sorts.

From: Hints and Secrets - Serenes Forest

Backup from chat messages history for easy search in the future.








5 Likes

What does this mean for the layman in the audience?

Most new documentation doesn’t really matter to you; the vast majority of the important stuff has some documentation already. You don’t need to follow Documentation if you aren’t interested.

Personally I find this interesting, but I don’t really expect any hacks to get a meaningful use out of it.

@Vesly You misunderstood me, I find this extremely interesting as well. When I asked “what does this meant for the layman in the audience ?” I meant “can you translate this for me since I’m not savy on the topic” It just seemed very interesting but I couldn’t follow since I’m not versed in hacking. Naturally the “layman” referring to just me, or anyone else interested.

In a word, it is an encrypted game time.

Oh okay. Forgive me, I know I sound like a complete scrub but what does the encryption do/access or what can this potentially unveil?

In the Japanese version, players could submit this password to the official site for a contest of sorts. I wonder what it is.

Got it, thanks for explaining

This documentation is not to tell you how to do something for hacking. It is to unveil a secret in game which I found by hacking instead.

2 Likes

(Slightly more than that, the other 15 values it runs sub_809E298 with are obtained using the same function used by the Battle History menu to obtain the information to display, and considering the game time value it’s using only starts counting from startup and isn’t file time it’s likely only included to make it harder to reverse-engineer the encryption using the output code and the rest of the data is what is actually meant to be sent)

1 Like

Some more notes (offsets are all FE7U)

  • Proc handling battle history screen is at 0x8CC5AF0
  • Function within that proc that makes the password is func_809E25E; this does a large amount of stuff, but there are 3 function calls within it near the end relevant to making the password:
    • func_809DFC4(mode,difficulty) gets the relevant battle history data directly from sram and obfuscates it
    • func_809D7B4(5,11) does some Math with the given values and stores the results various places; unsure if this is relevant or what it’s actually doing, but
    • func_809DBD8(func_809DDE4) is the meat of the encoding process, with 4 subroutines of its own and a 5th as the argument:

func_809DDE4 is the hash function mentioned in the prior discord screenshot, this should be the pairs for source value locations & constants passed into func_809D880 (also shown in the prior screenhot):

20144E3, 0x3
20144E4, 0x3
20144E5, 0x3
20144E6, 0x3
20144E7, 0x3
20144E9, 0x8
20144E8, 0x6
20144EC, 0xA
20144EE, 0x6
20144EF, 0x6
20144EB, 0x8
20144F0, 0x18

func_809D800, func_809D9A4, func_809D82C and func_809D9E4 get called a few times each throughout the function as well, all after it calls the hash function. func_809D82C can be seen in the prior discord screenshot as well.

Then here’s some rough pseudocode functions:

void func_809D880( u32 pointer1, u32* pointer2, u32 data, u32 constant) {

	//pointer1 -> stack
	//pointer2 -> r10
	//data -> r8
	//constant -> r9

	data += func_809D82C();
	data = data & (1 << constant);
	u32 iterator = 0;

	if (constant >= iterator) {
		
		while (iterator < constant) {
			pointer1 += (pointer2 / (word @ 20143FC));
			u32 tempVar1 = ((1 << iterator) & data) >> iterator;
			u32 tempVar2 = pointer2 % (word @ 20143FC);
			if (tempVar2 < iterator) tempVar2 = (tempVar2 * 2) + 7;
			tempVar2 = tempVar2 >> 3;
			tempVar2 = tempVar2 << 3;
			tempVar2 = (pointer2 % (word @ 20143FC)) - tempVar2;
			tempVar1 = tempVar1 << tempVar2;
			(byte @ pointer1) = (byte @ pointer1) | tempVar1;
			pointer2 ++;
			iterator ++;
		}	
	}
}
u16 func_809D82C() {
	//despite being treated as a halfword, value @ 2014434 is read & stored as a word
	u16 @ 2014434 *= 13;
	u16 @ 2014434 ++;
	return u16 @ 2014434;
}
func_809DBD8((void*)(func)(u32)(u32)) {
	//initializes some RAM values used in the encoding process
	
	//var -> r3
	//store 0 to allocated stack space
	
	//2014438 -> r6
	//2014404 -> r5
	
	u32 iterator = $20144D7;
	while (iterator >= 2014438) { 
		(byte @ iterator) = 0;
		iterator --;
	}
	
	int i = 0;
	func(&i, (2014438 + [word @ 2014404])); //func_809DDE4($3007D58,$201445E) this is the part that makes the initial code
	//it has the initial values after this call that are referenced below, so this populates this area I think
	
	[halfword @ 20144DE] = u16 func_809D800(i);
	
	[halfword @ 20144DA] = u16 func_809D9A4([word @ 2014404] + 2014438, [halfword @ 20144DE]);
	
	[halfword @ 20144D8] = ([halfword @ 20144DA] + (GetGameClock() >> 3)) & 0x3FF;
	
	//continues at this point from 809DC30, should be doing all the encoding
	
	
}
func_809DFC4(const u32 var1, u32 var2) { //populates struct @ 20144E0

	//var1 -> r5
	//var2 -> r6

	[sp + 0x18] = 0x0000;

	CpuSet([sp + 0x18], $20144E0, 0x100000A); //zeroes out the struct this populates

	if (func_809F224(sp,var1,var2) != 0) {
	
		//func_809F224 definitely populates sp with the values referenced henceforth
	
		[byte @ 20144E0] = var1;
		[byte @ 20144E2] = var2;
		[byte @ 20144E3] = [byte @ sp] & 0x70; 
		[byte @ 20144E4] = [halfword @ sp] & 0x7E;
		[byte @ 20144E5] = ([byte @ sp+1] & 0x18;
		[byte @ 20144E6] = [byte @ sp+1] & 0xE0;
		[byte @ 20144E7] = [byte @ sp+1] & 0xE0;
		[byte @ 20144E9] = ([halfword @ sp+2] >> 7) & 0xFF;
		[byte @ 20144E8] = [halfword @ sp+10] & 0x7F0<U+202C>;
		[byte @ 20144E1] = [byte @ sp+2] & 0x60;
		[halfword @ 20144EC] = [word @ sp+4] & 0x01FF80;
		[byte @ 20144EE] = [byte @ sp+6] & 0x7E;
		[byte @ 20144EF] = [halfword @ sp+6] & 0x1F80;
		[byte @ 20144F0] = ((([word @ sp+8] & 0x001FFFFF) << 3) | [byte @ sp+7] >> 5);
		[byte @ 20144EA] = [byte @ sp+23];
		[byte @ 20144EB] = (([byte @ sp+4] & 0x7F) << 1) | ([byte @ sp+3] >> 7);
		
		return 1;
	
	} else {
		return 0;
	}
	
}
bool func_809F224(destPtr,var1,var2) {

	//r6 = destPtr
	//r7 = var1
	//r5 = var2

	//r4 = 0

	CPUSet([sp+0x94],destPtr,0x0100000C); //initializes the area we're going to put stuff temporarily for this function
	
	CPUSet(... initalizes the destination ...);
	
	//func_809F0D8(&sp) gets battle history data from sram 
	if (func_809F0D8(&sp) != 0) {
		
		
		//assuming var1 and var2 are used to index the battle history data
		//since that function just grabs all of it from sram
		u32 temp = var2 << 1;
		temp += var2;
		temp += var1;
		u32 temp2 = temp << 1;
		temp2 += temp;
		temp2 = temp2 << 3;
		
		//does some ldmia/stmia schenanigans I don't feel like decoding right now,
		//presumably just grabbing the specific subset of data we asked for
		
		//things that this gets called with for args:
		//[0,0] - Lyn normal mode
		//[1,0] - Eliwood normal mode
		//[2,0] - Hector normal mode
		//[0,1] - Lyn hard mode
		//[1,1] - Eliwood hard mode
		//[2,1] - Hector hard mode

		//So, var1 is mode and var2 is difficulty
		//and this is just returning the section of raw data from sram 
		
		return true;
		
		
	} else {
		return false;
	}
}
bool func_809F0D8(u32 dest) {

	if (IsSramWorking()) {
	
		if (dest == 0) dest = gGenericBuffer;
		gReadSramFast(0x0E007044, dest, 0x7044);
	
	} else {
		return false;
	}
	return true;
}	

If you want to try and trace through some stuff, here’s an example of the data returned by func_809F0D8 and the page made from it:

49 19 09 68 0D 4A 89 18 0B F0 0E FA 20 6B 80 68 E1 6A 40 31 09 78 C9 00 49 19 89 88 10 31 49 01 20 22 F9 F7 8F F9 E1 6A 20 6B C8 62 60 6B C8 87 30 BC 01 BC 00 47 00 00 8C A5 02 02 00 00 01 06 10 B5 04 1C E0 6A 81 6C 00 29 0B D0 20 6B 00 7E 08 86 E0 6A 80 6C 00 21 FC F7 C2 FC E0 6A 80 6C 88 F0 A0 FF E0 6A 40 6C 00 28 01 D0 88 F0 9A FF 10 BC 01 BC 00 47 00 00 30 B5 04 1C 0D 1C 04 48 21 1C FC F7 67 FB C4 62 45 63 30 BC 01 BC 00 47 80 09 B9 08 10 B5 00 22

file

func_809F0D8(mode,difficulty) is used every time you change the page on the Battle History screen with the mode & difficulty of whatever page it’s loading. When you generate a password, it uses the mode & difficulty of the current page.

1 Like

I was looking into the obscure FE6 version of this password and only realized this thread is a thing now, so I wanted to post my findings since it seems most of the discussion is about FE7 currently. I want to make a video showing everything in action soon, but I wanted to write this out first for documentation.

Despite what Serenes mentions, I don’t think that FE7’s website ever ended up hosting such a contest, but FE6’s website definitely had one and still has the results up today (though unlinked from the main page). The contest took place for about a month after FE6’s release with 900 participants, and you can see their numerical stats for rankings. Unfortunately part of it ran on Flash Player which is not very usable now, but the individual ranks are still there in text form, and myself from 4 years ago discovering the website was able to note that it apparently mentioned the best stats in each ranking (with Power/Levels being graded by who had the lowest).

Pegasusknight.com mentions this contest existing too, but lists the FE7 Select method on the FE6 section which doesn’t work. People commenting on the website noted the password was a bit hard to input, which was my only lead of proof that there was such a password code publicly available at some point. I imagine it must have mentioned the input code at the time of the contest, but the earliest archive now is from December 2002 and I could not find any other sources from the time that may have noted it down.

Ultimately with some amateur reverse engineering and much guidance from community resources, I figured out you can access the password screen in FE6 on the final ranking screen by pressing Left Left Right Right L L Start. You have 20 frames to input each button which is pretty lenient.
fe6comment1

image

image

Thanks to ghidra I found a few other uses of the function that checks for this input. If you perform the same input while hovering over a team in the Link Arena though, you get an even huger password with Japanese characters. I imagine this must contain all the stats of your characters, but don’t think this was ever officially used for anything either. This input is still recognized all the way through FE7J/FE7U/FE8J/FE8U but it no longer shows a password, it just fades to black and comes back to the same menu.
image

image

I also mentioned in the XMAP thread (XMAP Chapters [FE6] - #2 by Flame) that this also enables trial map transfers (from Player 1) if done from the main menu of the link arena, which was really neat.

Notes:
The function that handles the inputs to activate these screens is at 0x8036D60. It takes an address argument that contains the inputs to compare to.

The proc (I think) that handles inputs on the ranking screen (pressing A or B to leave normally) is at 0x808F22C. It calls a function at 0x8036DEC, which just calls the input function with address 0x85C9A18. This contains the 7 inputs in bitflag form. The link arena long password version uses 0x85C9A08 while the XMAP transfer uses 0x85C99F8, but they all represent the same input anyway.

image

I want to thank and credit these resources: Function Library by MisakaMikoto for pointing out where PrintPassword is located in FE6 which was a big lead, StanHash’s FE6 decompilation for reference points, and FEBuilderGBA/mGBA/ghidra for ultimately figuring things out and debugging how everything works. I don’t really have much experience in this field, but these resources from the community made it pretty accessible to get started on a whim and find some closure to this mystery.

5 Likes