adam's stuff

Indiana Jones and the Great Circle Save Encryption

— 16 mins

I just finished The Great Circle (mostly) and due to an annoying bug, can’t 100% it, as a collectable is seemingly uncollectable once progressing past a certain point in Sukhothai. I thought, maybe, the save files can be edited and I can just go do a sneaky and go ‘fix’ that, but instead spent many hours learning about JPEGs. No story spoilers here, just photos of cats.

# Decrypting Save Data

I absolutely detest cryptography and it makes my brain hurt, so I first assumed that there was no encryption and it was just compressed, which isn’t totally unusual in games - but no such luck. But what if I break a save file and see if I get some useful error? I replaced a save file with garbage data instead, and thankfully, I got a useful error in the console:

[4671] WARNING: idSecureAEADCipher: BCryptDecrypt failed: c000a002

Awesome, so we know it’s BCrypt underneath and we have a error code we can look up, which is STATUS_AUTH_TAG_MISMATCH which is expected given we fed it garbage. But the idSecureAEADCipher was already useful, we’re likely looking at AES in GCM mode - but we don’t know the key size, is there a nonce, what the key/associated text is (and if they’re the same or different), the list goes on. I love cryptography, a lot.

Anyway, given that there’s a useful log message, what if we search for idSecureAEADCipher in the executable:

.rdata  00000001434E5830    idSecureAEADCipher::SetCipher: Invalid aeadCipher value : %d
.rdata  00000001434E5870    idSecureAEADCipher::SetCipher: negative tagLength
.rdata  00000001434E58F0    idSecureAEADCipher: BCryptSetProperty( BCRYPT_CHAINING_MODE ) failed: %x
.rdata  00000001434E5960    idSecureAEADCipher: BCryptGetProperty( BCRYPT_AUTH_TAG_LENGTH ) failed: %x
.rdata  00000001434E59B0    idSecureAEADCipher::SetCipher: Invalid tagLength: %lld (valid range: [%d, %d])
.rdata  00000001434E5A00    idSecureAEADCipher: BCryptGetProperty( BCRYPT_BLOCK_LENGTH ) failed: %x
.rdata  00000001434E5A48    idSecureAEADCipher::Encrypt: Not initialized
.rdata  00000001434E5A80    idSecureAEADCipher::Encrypt: nonce must be exactly 12 bytes (bcrypt restriction)
.rdata  00000001434E5AE0    idSecureAEADCipher::Encrypt: Failed to allocate encrypted buffer
.rdata  00000001434E5B28    idSecureAEADCipher: BCryptEncrypt failed: %x
.rdata  00000001434E5B60    idSecureAEADCipher::Encrypt: Unexpected number of bytes written (%d instead of %d)
.rdata  00000001434E5BB8    idSecureAEADCipher::Decrypt: Not initialized
.rdata  00000001434E5BF0    idSecureAEADCipher::Decrypt: nonce must be exactly 12 bytes (bcrypt restriction)
.rdata  00000001434E5C48    idSecureAEADCipher::Decrypt: cipherText too short (%lld < %d)
.rdata  00000001434E5C90    idSecureAEADCipher::Decrypt: Failed to allocate decrypted buffer
.rdata  00000001434E5CD8    idSecureAEADCipher: BCryptDecrypt failed: %x
.rdata  00000001434E5D10    idSecureAEADCipher::Decrypt: Unexpected number of bytes written (%d instead of %d)

Saucy. So we have a 12 byte nonce, let’s try a search a bit less specific and see what other decrypt functions we can find:

.rdata  00000001434E5578	idSecureBlockCipher::Decrypt: Not initialized
.rdata  00000001434E55B0	idSecureBlockCipher::Decrypt: Failed to allocate decrypted buffer
.rdata  00000001434E55F8	idSecureBlockCipher: BCryptDecrypt failed: %x
.rdata  00000001434E5628	idSecureBlockCipher::Decrypt: Too many bytes written (%d > %d)
.rdata  00000001434E5750	idSecureBlockCipher::DecryptWithOSPadding: Not initialized
.rdata  00000001434E5790	idSecureBlockCipher::DecryptWithOSPadding: Failed to allocate decrypted buffer
.rdata  00000001434E57E0	idSecureBlockCipher::DecryptWithOSPadding: Too many bytes written (%d > %d)
.rdata  00000001434E5BB8	idSecureAEADCipher::Decrypt: Not initialized
.rdata  00000001434E5BF0	idSecureAEADCipher::Decrypt: nonce must be exactly 12 bytes (bcrypt restriction)
.rdata  00000001434E5C48	idSecureAEADCipher::Decrypt: cipherText too short (%lld < %d)
.rdata  00000001434E5C90	idSecureAEADCipher::Decrypt: Failed to allocate decrypted buffer
.rdata  00000001434E5CD8	idSecureAEADCipher: BCryptDecrypt failed: %x
.rdata  00000001434E5D10	idSecureAEADCipher::Decrypt: Unexpected number of bytes written (%d instead of %d)
.rdata  0000000143544BB8	decrypt error
.rdata  0000000143552E90	decryption( 1 )
.rdata  0000000143552EA0	decryption( 2 )

There’s a separate block cipher, but I assume that’s not relevant here given the error we had previously, and I hadn’t seen decrypt error yet, so we’ll try the decryption( 1 ) result and see what’s there, and there’s two xrefs to that string and the decryption( 2 ) string, the first of which seems DOOM related for some reason, but the second is more The Great Circle related:

.text:0000000140B251AD                 mov     rcx, [rcx+10h]
.text:0000000140B251B1                 lea     r9, [rsp+390h+var_368]
.text:0000000140B251B6                 lea     r8, [rsp+390h+var_340]
.text:0000000140B251BB                 lea     rdx, aSukhothai_0 ; "SUKHOTHAI"
.text:0000000140B251C2                 call    sub_140A918B0
.text:0000000140B251C7                 test    al, al
.text:0000000140B251C9                 jnz     loc_140B252A5
.text:0000000140B251CF                 lea     rcx, aDecryption1 ; "decryption( 1 )"
.text:0000000140B251D6                 call    sub_140A91AF0

The other version had PAINELEMENTAL instead of SUKHOTHAI for whatever reason, everything was otherwise the same. Doesn’t seem to be called/used. Anyway, if we breakpoint at :$A918B0 and look at what’s passed through from the caller, it looks like it’s passing my SteamID64, SUKHOTHAI again and one of the filenames in my save slot:

RCX : 0000019D0383B870     "76561198042042069"
RDX : 00007FF7A32FBA40     "SUKHOTHAI"
R11 : 00000022433FF538     "game_duration.dat"

Following that, it does a bunch of confusing string copy bullshit and fuckery through virtual calls and otherwise which is head hurting juice, but it looks to eventually concatenate the three into one string like the following: 76561198042042069SUKHOTHAIgame_duration.dat. Is this the key? It looks like that the decryption( 2 ) works basically the same too, so not really sure why it exists at this stage. I would normally attempt to just try and decrypt it with that and even see if it’s right, but I don’t know how the key is derived. The concatenated string might just be the additional data to prevent save sharing and the key is hard coded or derived from the additional data, perhaps via hashing the it or a subset thereof.

However, in the spirit of laziness, and given that there’s DOOM (Eternal?) code in here because of course there is, what happens if you search GitHub for PAINELEMENTAL? 2.4k code results and the first page had nothing useful, but the second page had a delicious nugget of information, which makes all prior work almost redundant: https://github.com/GoobyCorp/DOOMSaveManager/blob/bf20be5d56755e5ce7ce38ff5705d665e287052e/DoomEternalSavePath.cs#L58

if (Platform == DoomEternalSavePlatform.BethesdaNet && Encrypted)
    fileData = Crypto.DecryptAndVerify($"{Identifier}PAINELEMENTAL{Path.GetFileName(single)}", fileData);
else if (Platform == DoomEternalSavePlatform.Steam && Encrypted)
    fileData = Crypto.DecryptAndVerify($"{Identifier}MANCUBUS{Path.GetFileName(single)}", fileData);

Which is the same key format we found before. A quick look at the DecryptAndVerify function tells us everything that we needed to know, AES AEAD with a 12 byte nonce which is stored before the cipher text, and the same additional data is used as the key when SHA256’d. It doesn’t look like in this case that there’s any platform-specific key material this time, as all code paths seem to always use SUKHOTHAI as the game-specific key, but client builds are also platform specific as the client build information specifically states it’s a Steam build:

[14] ------ Build Information ------
	Host Name: [redacted]
	Bam compile specification: x64_vulkansteam-shippingretail
	Binary Build:
		Version: 1.0.0
		Target: retail
		Name: 20241212-152648-147754_jasper-olive
		Branch: relic-stabilization
		Requestor: user:autocompiler:forge
		URL: https://www.forge.prod-arn-mg.idtech.services/builds/binaries/20241212-152648-147754_jasper-olive
		Relevant CLs: 6c4a5934fa50e9ce96b2534f4eb8e8f708f0bbc1
	Package:
		Name: 20241212-153103-147754_chrome-nailset
		Requestor: user:pierre.willbo:9d0dd
		URL: https://www.forge.prod-arn-mg.idtech.services/builds/packages/20241212-153103-147754_chrome-nailset
		Relevant CLs: 595616
	Disc Layout:
		Name: 20241212-153103-147754_ruthenium-twinkie
	Candidate:
		Name: 20241212-153103-147754_rotten-cat

There’s a not-insignificant chance that the keys are different per platform, but there’s also a good chance they’re the same. Either way, with what we know it’s pretty easy to find the missing key. Maybe it’s also to easier to quickly guess each place name if SUKHOTHAI doesn’t work too.

With all that, I quickly slapped together some code based on what I found to decrypt a file based on the key we found in the executable and the code that GitHub search bestowed upon me, as a few more pages and I probably would’ve stopped looking. The following code works the best in a REPL like LINQPad :)

var sha256 = SHA256.Create();

string buildAssocData(string ident, string filename, string key = "SUKHOTHAI") => $"{ident}{key}{filename}";

byte[] decrypt(string key, byte[] data)
{
	var nonce = data[..12].ToArray();
	var cipherText = data[12..].ToArray();
	
	var addDataBytes = Encoding.UTF8.GetBytes(key);
	var keyHash = sha256.ComputeHash(addDataBytes);
	
	var blockCipher = new GcmBlockCipher(new AesEngine());
	var keyParams = new AeadParameters(new KeyParameter(keyHash, 0, 16), 128, nonce, addDataBytes);
	blockCipher.Init(false, keyParams);
	
	var textLen = blockCipher.GetOutputSize(cipherText.Length);
	var buf = new byte[textLen];
	var ret = blockCipher.ProcessBytes(cipherText, 0, cipherText.Length, buf, 0);
	blockCipher.DoFinal(buf, ret);
	
	return buf;
}

// usage

var p = @"C:\Program Files (x86)\Steam\userdata\...\2677660\remote\GAME-SLOT0\game.details";
Path.GetFileName(p).Dump();
var b = File.ReadAllBytes(p);
var key = buildAssocData("[SteamID64]", Path.GetFileName(p));

var c = decrypt(key, b);
Encoding.UTF8.GetString(c).Dump();

The result:

game.details
actionSettingAIPrespotMultiplier=1
actionSettingMaxMeleeAttackers=1
actionSettingMaxRangedAttackers=1
actionSettingMeleeAutoparry=0
actionSettingPlayerIncomingDamage=1
actionSettingStealthHUD=1
actionSettingStealthHUDThroughWalls=0
adventureSettingObjectiveMarker=0
adventureSettingObjectiveName=0
checkpointImage=
checksum=1852318942
completed=0
dateCreated=1734775685
disguise=soldier
gameDifficulty=1
gameVersion=1145896964
localSaveTimeStamp=1735222791
mapDesc=
mapName=game/relic/vatican/vatican/vatican
nextMapLayers=
nextMapName=
puzzleDifficulty=1
revisit=1
settingCameraFraming=1
settingSubtitleBacking=1
settingSubtitleClosedCaptions=0
settingSubtitleFontSize=0
settingSubtitles=1
settingSubtitleShowSpeaker=1
settingUIFontSize=0
startedPlaying=1
time=70566

# A Journey Through Save Data

Annoyed about the collectable bug, I started digging through the save files, which can be located for Steam owners here: C:\Program Files (x86)\Steam\userdata\<account number>\2677660\remote\. If you’re on game pass or something else, google is probably your friend. There’ll be at least two folders in here (assuming you’ve played the game), a PROFILE and GAME-SLOT0 directory. PROFILE stores a single file, profile_pc.dat which stores a bunch of key-value keys (but not the values from a cursory glance), settings, cutscenes (?) and some playfab keys - nothing remarkable.

GAME-SLOT0 (or whatever slot index you’re using) is where the actual save data lives. *.details are a simple key-value store for basic information, typically used for disguise, have you revisited an area, the map name, current checkpoint amongst other things. This is stored individually for each level, and there’s also a ‘parent’ game.details file that stores difficulty, subtitle settings, current map name (so you can do the fancy menu to in-game thing) and some other settings:

actionSettingAIPrespotMultiplier=1
actionSettingMaxMeleeAttackers=1
actionSettingMaxRangedAttackers=1
actionSettingMeleeAutoparry=0
actionSettingPlayerIncomingDamage=1
actionSettingStealthHUD=1
actionSettingStealthHUDThroughWalls=0

No idea what these do, haven’t tried playing with them. I suspect they’re set by the difficulty settings accordingly. Here’s college_day_cb67fe49.details:

checkpoint=script_script_spawnpoint_revisit
checksum=-1216986065
dateCreated=1734776674
disguise=professor
introSeen=1
layers=game/revisit
localSaveTimeStamp=1734879741
mapName=game/relic/college/college_day
revisited=1

The *.dat files that accompany the *.details files are more interesting, they store the entirety of game state of an entire map in the file. A non-exhaustive list is quest state, collectables, boat position, door states, puzzle states, interactables and ragdolls. If you search the executable for SAVEGAME_JOB_NAME you’ll find 521 jobs which saves a specific type of component state, but it doesn’t look like a lot of them are used.

The final thing I’d like to talk about that I haven’t mentioned yet, is that there is a photocamera directory in the slot directory. This contains every photo of a point of interest you take a photo of in game (as if you take a photo randomly, you get an end-of-film angry noise from the camera). But everything that the game wants you to take a photo of is in there, and while unfortunately, each photo is only 1280x768, there’s also some film camera filtering going on with exposure, grain, (ugly) chromatic aberration and generally looking a bit shit, they fit pretty well with the vibe and era of the game.

cat
Signor Smushki, the distinguished gentleman

Decrypting all these is pretty straightforward too, with the same decrypt function from earlier:

var path = @"C:\Program Files (x86)\Steam\userdata\...\2677660\remote\GAME-SLOT0\photocamera\";
foreach(var p in Directory.EnumerateFiles(path))
{
	Path.GetFileName(p).Dump();
	var b = File.ReadAllBytes(p);
	var key = buildAssocData("[SteamID64]", Path.GetFileName(p));
	
	var c = decrypt(key, b);
	Util.Image(c).Dump();
}

These will be shown in the results pane in LINQPad, and you can just copy/save any images out of there that you’d like (or write them to disk directly).

The same images are actually used to populate Indi’s journal from disk, so you could replace the images with your own. One thing to note with encrypting file contents here, is that BCrypt doesn’t like data that has odd sizes, it must be padded out to a 4/8 byte boundary. The easiest way to do this is just resize the array before you encrypt it, like so:

Array.Resize(ref data, (data.Length + 7) & ~7);

4 bytes should work too, but I didn’t try it; not really sure why I picked 8 bytes in hindsight. So, some quickly fixed encryption code would look like this:

byte[] EncryptAndDigest(string aad, byte[] data)
{
	var nonce = new byte[12];
	new Random().NextBytes(nonce);
	
	Array.Resize(ref data, (data.Length + 7) & ~7);

	var aadBytes = Encoding.UTF8.GetBytes(aad);
	var aadHash = sha256.ComputeHash(aadBytes);
	var cipher = new GcmBlockCipher(new AesEngine());
	var cipherParams = new AeadParameters(new KeyParameter(aadHash, 0, 16), 128, nonce, aadBytes);
	cipher.Init(true, cipherParams);
	
	var len = cipher.GetOutputSize(data.Length);
	var ciphertext = new byte[len];
	
	var retLen = cipher.ProcessBytes(data, 0, data.Length, ciphertext, 0);
	cipher.DoFinal(ciphertext, retLen);

	var output = new byte[nonce.Length + ciphertext.Length];
	Buffer.BlockCopy(nonce, 0, output, 0, nonce.Length);
	Buffer.BlockCopy(ciphertext, 0, output, nonce.Length, ciphertext.Length);
	
	return output;
}

# Replacing Journal Photos

Replacing the photocamera images is Type 3 Fun, as the game is very specific on the size, format, and probably other image bullshit that shouldn’t matter but it does. I haven’t had much luck actually getting the game to load the images just by giving it a correctly sized JPEG, but it will not log any errors if it fails for some reason, it will just not work instead and you get a black texture. The only error you will get is if the size is wrong, which is helpful if you get confused between 1280x720 and 1280x768 like I did. I suspect there’s an in-house lightweight implementation of a JPEG encoder/decoder that’s used for this (or generally, in engine) and likes things in a certain way.

Wondering why it’s still not working and I’m just getting nothingness textures, I had a bit of a look at the differences in the JPEG I was giving it and what I had decrypted, and while the data in the headers is the same, the structure is completely different. I tried with a few different tools and photo editing applications and never got anything close to how they encode JPEGs.

First up is a decrypted image from the game itself:

Name											Offset	Size
struct JPGFILE jpgfile							0h		16B31h
	enum M_ID SOIMarker							0h		2h
	struct APP0 app0							2h		12h
	struct DQT dqt								14h		86h
		enum M_ID marker						14h		2h
		WORD szSection							16h		2h
		struct QuanTable qtable[0]				18h		41h
		struct QuanTable qtable[1]				59h		41h
	struct SOFx sof0							9Ah		13h
		enum M_ID marker						9Ah		2h
		WORD szSection							9Ch		2h
		ubyte precision							9Eh		1h
		WORD Y_image							9Fh		2h
		WORD X_image							A1h		2h
		ubyte nr_comp							A3h		1h
		struct COMPS comp[3]					A4h		9h
	struct DHT dht								ADh		1A4h
		enum M_ID marker						ADh		2h
		WORD szSection							AFh		2h
		struct Huffmann_Table huff_table[0]		B1h		1Dh
		struct Huffmann_Table huff_table[1]		CEh		B3h
		struct Huffmann_Table huff_table[2]		181h	1Dh
		struct Huffmann_Table huff_table[3]		19Eh	B3h
	struct SOS scanStart						251h	Eh
	char scanData[92366]						25Fh	168CEh
	enum M_ID EOIMarker							16B2Dh	2h
	char unknownPadding[2]						16B2Fh	2h

Versus the seemingly more typical JPEG structure and the structure of the test image I’m trying to load:

Name											Offset	Size
struct JPGFILE jpgfile							0h		2EDF3h
	enum M_ID SOIMarker							0h		2h
	struct APP0 app0							2h		12h
	struct DQT dqt[0]							14h		45h
		enum M_ID marker						14h		2h
		WORD szSection							16h		2h
		struct QuanTable qtable					18h		41h
	struct DQT dqt[1]							59h		45h
		enum M_ID marker						59h		2h
		WORD szSection							5Bh		2h
		struct QuanTable qtable					5Dh		41h
	struct SOFx sof0							9Eh		13h
		enum M_ID marker						9Eh		2h
		WORD szSection							A0h		2h
		ubyte precision							A2h		1h
		WORD Y_image							A3h		2h
		WORD X_image							A5h		2h
		ubyte nr_comp							A7h		1h
		struct COMPS comp[3]					A8h		9h
	struct DHT dht[0]							B1h		21h
		enum M_ID marker						B1h		2h
		WORD szSection							B3h		2h
		struct Huffmann_Table huff_table		B5h		1Dh
	struct DHT dht[1]							D2h		B7h
		enum M_ID marker						D2h		2h
		WORD szSection							D4h		2h
		struct Huffmann_Table huff_table		D6h		B3h
	struct DHT dht[2]							189h	21h
		enum M_ID marker						189h	2h
		WORD szSection							18Bh	2h
		struct Huffmann_Table huff_table		18Dh	1Dh
	struct DHT dht[3]							1AAh	B7h
		enum M_ID marker						1AAh	2h
		WORD szSection							1ACh	2h
		struct Huffmann_Table huff_table		1AEh	B3h
	struct SOS scanStart						261h	Eh
	char scanData[191362]						26Fh	2EB82h
	enum M_ID EOIMarker							2EDF1h	2h

So the key things are that there’s still 2 quantization tables and 4 Huffmann tables, but instead of a single table stored per struct, all of them are stored in a single struct. The images the game produces are smaller too, saving 16 bytes per image, which makes me curious why this isn’t the normal way if this is possible. 16 bytes isn’t much but over thousands of images, that’d be a nice improvement. What’s also curious is basically everything I tried will encode a JPEG like the second format, and never the first. Maybe it’s a compatibility thing and the more ‘standard’ format is more widely accepted, but everything I have loads the decrypted image as expected. Saving/copying the image out of the image viewer a lot of the time will write the image in the second, more standard format, which had me scratching my head before I realised the structure was completely different.

Anyway, that all said, nothing that isn’t too hard to fix in a hex editor. So I manually copied the bytes from my source image into the skeleton of an image that came out of the game, and it still worked in my usual selection of image viewers. The only things I moved across was the scanData and the 4 Huffmann tables and the 2 quantization tables. I initially didn’t copy the SOFx struct, but when the image didn’t work initially, I realised that comp[0] had a different horizontal and vertical sampling configuration. I suspect if this image doesn’t load, it might only like it’s specific sampling factor it uses, which is seemingly consistent (hello what kind of encoding is this???) where the following rules apply but I also only checked like 5 of them because that is scientific enough for me and my sanity is rapidly dwindling:

comp[0]: 2x2
comp[1]: 1x1
comp[2]: 1x1

Whereas my image was 1x1 sampling for each component. But that shouldn’t matter right? I didn’t think I’d ever have to care so much about what the fuck a JPEG is, but rabbit holes be rabbit holes.

wtf

As the proud owner of a freshly hand-minted JPEG that definitely hasn’t cost me any sanity whatsoever, I encrypted the image again and slapped it into the photocamera/ folder…

no worky :(

Fuck. Maybe I need to use the same component sampling factor as they do? Apparently imagemagick can do this via convert, so…

$ identify -verbose angery.jpg | grep sampling-factor
    jpeg:sampling-factor: 1x1,1x1,1x1
$ convert angery.jpg -sampling-factor 4:2:0 angery2.jpg
$ identify -verbose angery2.jpg | grep sampling-factor
    jpeg:sampling-factor: 2x2,1x1,1x1

Great success… but the structure is all wonky again. Out of desperation I was scrolling through the command line args for something that looked like it would do the structure part for me but I don’t think that exists and it’s just an implementation-specific quirk. Sadness. The game refuses to load the image that imagemagick has just spat out too without any changes, so that sorta confirms that they only accept their wacky JPEG structure. Anyway, time to mint a new JPEG by hand again because I don’t think I have the brain cells left to automate this, even though it doesn’t feel like it should be that hard.

The worst possible situation has occured for my love of hex editing, the Huffmann tables are all a different size and it doesn’t fit nicely into the same structure as-is, so I have to do math too now and not just copy paste shit. To clarify, before we had one struct that was 418 bytes in size including the size, but the new Huffmann tables that imagemagick produced were only 173 bytes across the 4 DHT structs.

Anyway, with some basic arithmetic down, 29+75+27+42 = 173 for the old DHT struct sizes, which totally didn’t work the first time because I didn’t remove 3 u16s for the DHT structs and their sizes we no longer had in the file (so 173 - 6 = 167), we have Hand-Minted-JPEG 2: Electric Boogaloo. Works in my photos viewer and Firefox so you know it’s a quality JPEG.

So we encrypt it again, drop it into the photocamera/ folder again, and reload the save and… it doesn’t work, at all. Black. Nothingness. Maybe I’m wrong about it being super pedantic about the structures, but it’s never worked with a vanilla JPEG ever, so I think I’m at least somewhat correct there. I also had a look at some DOOM modding wikis and there doesn’t seem to be anything about JPEGs there, and the only mention of a JPEG on the idStudio docs is about the screenshot requirements for DOOM mods. The JPEG I hand crafted with equal parts love and anger might be really fucked too, but it works in basically everything I opened it with, and even tried error checking it with some random tools that said they did that and they all report that the image is fine.

At this point I feel like it’s something stupid I’m messing up or missing, but I’ve spent more time trying to get a funny JPEG into Indi’s journal as a shitpost than I probably should have, and I don’t want to sit in front of a debugger figuring out how the game loads JPEGs as that sounds even less fun.

If you figure out what the deal with these JPEGs are, I’d probably want to know just so I know how close I was to getting it right.