It's a complicated process, and one that i'll get around to looking at in a little while once i'm finished with the current checksum part of the app.
Immortal_no1 said:That is an interesting app, i will have to play about with it a little bit and see how useful it might be.
With the file correctly mapped it should be possible to extract the files from within, although any replacing of the files will have to reverse the process. Who wrote the application? is there source available?
I wrote it. The src sorry I have to clean it a bit befor I releas it. There are many commentars that are not good etc. and I have made it togetter with a app to get the offsets for the files and other blocks out of that file I clean it up a bit and releas it.
ADD:Sorry for may bad english
ADD2: the other app http://www.mediafire.com/?vufn0s5q2j9akfc src follow soon
@ADD2: virtual_3ds_save_file_analyser.exe in must be a virtual save
ADD3:But first I update the 3dssaveresorter to work with newer games
ADD4: it should result in something like this
Partition0:
hashtabeloffset 2000
SAVEoffset 3000
SAVEsize D000
Partition1:
hashtabeloffset 10000
SAVEoffset 11000
SAVEsize D000
SAVE0:
savestatoffs 3400
filetabeloffs 3600
rootname: files: 2
system.dat (0) start: 3800 size: 22
save00.bin (1) start: 3a00 size: 14dc
SAVE1:
savestatoffs 11400
filetabeloffs 11600
rootname: files: 2
system.dat (0) start: 11800 size: 22
save00.bin (1) start: 11a00 size: 14dc
so with a hex editor you can now easy extract the files (this is a Zelda save)
ADD5: src of the 3dssaveresorter
CODE static void Main(string[] args)
{
FileStream instream = new FileStream(args[0],FileMode.Open);
FileStream outstream = new FileStream(args[1], FileMode.Create);
UInt32 chucksize = 0x1000;//Convert.ToUInt32(args[3], 16);
UInt32 joffset = (UInt32)((instream.Length / chucksize) * 0xA);//Convert.ToUInt32(args[3], 16);
//we ignore all header_entrys since this is only for beta stuff
Byte[,] sectors = new Byte[0x10000,3]; // virt_sec , phys_sec , virt_realloc_cnt
Byte[,] usedsectors = new Byte[(UInt32)(instream.Length / chucksize), 3];
instream.Seek(joffset,SeekOrigin.Begin);
int size = 0;
for(int i = 0; instream.Position < 0xFFF;i++)
{
sectors[i, 0] = (Byte)instream.ReadByte();
instream.ReadByte();
sectors[i, 1] = (Byte)instream.ReadByte();
instream.Seek(2, SeekOrigin.Current);
sectors[i, 2] = (Byte)instream.ReadByte();
instream.Seek(0x1A, SeekOrigin.Current);
if (sectors[i, 1] != 0xFF) Console.WriteLine(sectors[i, 1].ToString() + " -> " + sectors[i, 0].ToString() + "(" + sectors[i, 2].ToString() + ")");
size++;
}
Console.ReadLine();
for (int i = 0; i < (UInt32)(instream.Length / chucksize); i++)
{
for (int i2 = 0; i2 < size; i2++)
{
if (sectors[i2, 1] != 0xFF) // no 0xFF
{
if (usedsectors[i, 2]

Immortal_no1 said:Good job on the App, nicely written source, is that in .NET?
Seems like you have a pretty good understanding of the save structure...
I have a nice collection, i'll post a .rar with the saves at some point soon, i'm actually going to have to play the games that i have now rather than leaving them in there nice pristine condition![]()
Immortal_no1 said:ichichfly, by using your application, can you explain to me where the locations for the parts that differ are. so we want to find :
02 16 1C 0A 03 03
12 15 08 0F 03 03
00 14 0B 06 03 03
00 13 19 0B 03 03
00 0F 15 19 03 03
As at these points they have different values. If we know where the location of these parts are we may be able to find out how the values are generated.
48
C0
47
3A 47
Save3-CCCCCCCC-Analysis.pdf
Immortal_no1 said:Right, thanks for that, that helps. The locations 1C000 8000 and 15000 helps out, as the data at the address 8000 is where the scores are saved, the data at 1C000 I thinkl was the location of the crc and the location of 15000 I think is the save bloc for the filesystem... might be wrong on that part, I'm doing this from memory as I'm writing on my mobile.
Do you mind if I rewrite your app for integration into the Save De/Encrypter? Hopefully we may be able to get the extraction and importing of the save data working.
I've written an application that will run through the save file and it's searching for the checksums that we don't yet have. The search shoiuld finish tomorrow night so I'll update this post then.
Preliminary searches has found the DIFI / DISA hashes.
@Do you mind if I rewrite your app for integration into the Save De/Encrypter? Hopefully we may be able to get the extraction and importing of the save data working.
you are allowed to add it (you can also integrat the virtual save game analyser)
ADD: the src of the virtual save game analyser that gets the positions it is still not good but I fixed some things up now more games should work
CODEclass Program
{
static void Main(string[] args)
{
uint[] partoffset = new uint[0x10];
uint[] partsize = new uint[0x10];
uint[] parthashsize = new uint[0x10];
UInt16[] savestatoffs = new UInt16[0x10];
uint[] filetabeltoffs = new uint[0x10];
int i = 0;
FileStream instream = new FileStream(args[0],FileMode.Open);
instream.Seek(0x200, SeekOrigin.Begin);
while (true) //file system search
{
if (instream.ReadByte() == 0x44 && instream.ReadByte() == 0x49 && instream.ReadByte() == 0x46 && instream.ReadByte() == 0x49) //is DIFI
{
if (i == 0)
{
partoffset[0] = 0x2000;
instream.Seek(0x9C - 4, SeekOrigin.Current);
Byte[] buffer = new Byte[4];
instream.Read(buffer, 0, 4);
parthashsize = BitConverter.ToUInt32(buffer, 0);
instream.Seek(4, SeekOrigin.Current);
instream.Read(buffer, 0, 4);
partsize = BitConverter.ToUInt32(buffer, 0);
Console.WriteLine("Partition" + i.ToString() + ":");
Console.WriteLine("hashtabeloffset " + Convert.ToString(partoffset, 16).ToUpper());
Console.WriteLine("SAVEoffset " + Convert.ToString(partoffset + parthashsize, 16).ToUpper());
Console.WriteLine("SAVEsize " + Convert.ToString(partsize, 16).ToUpper());
Console.WriteLine("");
instream.Seek(0x88, SeekOrigin.Current);
}
else
{
partoffset = partoffset[i - 1] + parthashsize[i - 1] + partsize[i - 1];
if (partoffset % 0x1000 != 0) partoffset = (partoffset / 0x1000) * 0x1000 + 0x1000;
instream.Seek(0x9C - 4, SeekOrigin.Current);
Byte[] buffer = new Byte[4];
instream.Read(buffer, 0, 4);
parthashsize = BitConverter.ToUInt32(buffer, 0);
instream.Seek(4, SeekOrigin.Current);
instream.Read(buffer, 0, 4);
partsize = BitConverter.ToUInt32(buffer, 0);
Console.WriteLine("Partition" + i.ToString() + ":");
Console.WriteLine("hashtabeloffset " + Convert.ToString(partoffset, 16).ToUpper());
Console.WriteLine("SAVEoffset " + Convert.ToString(partoffset + parthashsize, 16).ToUpper());
Console.WriteLine("SAVEsize " + Convert.ToString(partsize, 16).ToUpper());
Console.WriteLine("");
}
}
else
{
break;
}
i++;
}
for (int i2 = 0; i != i2; i2++)
{
instream.Seek(partoffset[i2] + parthashsize[i2], SeekOrigin.Begin);
if (instream.ReadByte() == 0x53 && instream.ReadByte() == 0x41 && instream.ReadByte() == 0x56 && instream.ReadByte() == 0x45) //is SAVE
{
Console.WriteLine("SAVE" + i2.ToString() + ":");
instream.Seek(0x58 - 4, SeekOrigin.Current);
Byte[] buffer = new Byte[4];
Byte[] buffer1 = new Byte[2];
instream.Read(buffer1, 0, 2);
savestatoffs[i2] = BitConverter.ToUInt16(buffer1, 0);
instream.Seek(0x12, SeekOrigin.Current);
instream.Read(buffer, 0, 4);
filetabeltoffs[i2] = BitConverter.ToUInt32(buffer, 0) * 0x200;
Console.WriteLine("savestatoffs " + Convert.ToString(savestatoffs[i2] + partoffset[i2] + parthashsize[i2], 16).ToUpper());
Console.WriteLine("filetabeloffs " + Convert.ToString(savestatoffs[i2] + filetabeltoffs[i2] + partoffset[i2] + parthashsize[i2], 16).ToUpper());
Console.WriteLine("");
if ((savestatoffs[i2] + filetabeltoffs[i2]) == 0)
{
Console.WriteLine("calculated filetabel offset wrong");
}
else
{
instream.Seek(savestatoffs[i2] + filetabeltoffs[i2] + partoffset[i2] + parthashsize[i2], SeekOrigin.Begin);
instream.Read(buffer, 0, 4);
uint files = BitConverter.ToUInt32(buffer, 0) - 1;
Byte[] name = new byte[0x10];
instream.Read(name, 0, 0x10);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
Console.WriteLine("rootname:" + enc.GetString(name) + " files: " + files.ToString());
instream.Seek(0x20 - 4, SeekOrigin.Current);
for (int currfile = 0; currfile < files; currfile++)
{
instream.Read(buffer, 0, 4);
uint currentfiles = BitConverter.ToUInt32(buffer, 0) - 1;
instream.Read(name, 0, 0x10);
instream.Read(buffer, 0, 4);
uint index = BitConverter.ToUInt32(buffer, 0);
instream.Seek(4, SeekOrigin.Current);
instream.Read(buffer, 0, 4);
uint offset = BitConverter.ToUInt32(buffer, 0);
instream.Read(buffer, 0, 4);
uint curfilesize = BitConverter.ToUInt32(buffer, 0);
if (currentfiles == 0) Console.WriteLine(enc.GetString(name) + "(" + index.ToString() + ")" + " start: " + Convert.ToString(offset * 0x200 + savestatoffs[i2] + partoffset[i2] + parthashsize[i2], 16) + " size: " + Convert.ToString(curfilesize, 16));
else Console.WriteLine(" " + enc.GetString(name) + " (next " + currentfiles.ToString() + " files)");
instream.Seek(0xC, SeekOrigin.Current);
}
}
Console.WriteLine("");
}
else
{
Console.WriteLine("SAVE" + i2.ToString() + " is not there");
}
}
Console.ReadLine();
}
public static UInt16 Swap(UInt16 input)
{
return ((UInt16)(
((0xFF00 & input) >> 8) |
((0x00FF & input) > 24) |
((0x00FF0000 & input) >> 8) |
((0x0000FF00 & input)
Immortal_no1 said:Anyone have any ideas on how the checksums for the header data is created?
looking at the numbers at the bottom of the page in the file here
My checksum search application is going to take a long time to go through all the possible permutations
as i think there's only a few thousand trillion. I'm going to leave it running and see what we find.
Immortal_no1 said:I wonder if the hex values are added together and then a modulus is used. I'll write a test app to see if that works. Otherwise I'm starting to run out of ideas...
Immortal_no1 said:I wonder if the hex values are added together and then a modulus is used. I'll write a test app to see if that works. Otherwise I'm starting to run out of ideas...
CollosalPokemon said:Immortal_no1 said:I wonder if the hex values are added together and then a modulus is used. I'll write a test app to see if that works. Otherwise I'm starting to run out of ideas...
I'm not sure if this'll help or not but I don't think we need to modify any blocks (ie 0xB000 -> 0xBFFF, 0x4000 -> 0x4FFF, or w/e) found in the current 3DS Save DeEncryptor because I tested it by copying just the blocks and checksums found with the experimental menu from one save to a different one (same game) and it still ended up corrupted.
Basically whatever needs to be done now to finish modifying the saves is simply not in the blocks found by the experimental menu (ie 0xB000 -> 0xBFFF, 0x4000 -> 0x4FFF, or w/e), or at least, that's the case for Legend of Zelda: Ocarina Of Time 3D. I think it has something to do with the data inbetween the blocks found in the experimental menu.