diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..551152d --- /dev/null +++ b/Program.cs @@ -0,0 +1,681 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nfs2iso2nfs +{ + class Program + { + public const int SECTOR_SIZE = 0x8000; + public const int HEADER_SIZE = 0x200; + public static byte[] WII_COMMON_KEY = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public const int NFS_SIZE = 0xFA00000; + public static bool dec = false; + public static bool enc = false; + public static bool keepFiles = false; + public static string keyFile = "..\\code\\htk.bin"; + public static string isoFile = "game.iso"; + public static string wiiKeyFile = "wii_common_key.bin"; + public static string nfsDir = ""; + + static void Main(string[] args) + { + Console.WriteLine(); + if (checkArgs(args) == -1) + return; + byte[] key = checkKeyFiles(); + if (key == null) + return; + if (dec) + { + byte[] header = getHeader(nfsDir + "\\hif_000000.nfs"); + combineNFSFiles("hif.nfs"); + EnDecryptNFS("hif.nfs", "hif_dec.nfs", key, buildZero(key.Length), false, header); + if (!keepFiles) + File.Delete("hif.nfs"); + unpackNFS("hif_dec.nfs","hif_unpack.nfs", header); + if (!keepFiles) + File.Delete("hif_dec.nfs"); + manipulateISO("hif_unpack.nfs", "game.iso", true); + if (!keepFiles) + File.Delete("hif_unpack.nfs"); + } + else if (enc) + { + long[] size = manipulateISO(isoFile, "hif_unpack.nfs", false); + byte[] header = packNFS("hif_unpack.nfs", "hif_dec.nfs", size); + if (!keepFiles) + File.Delete("hif_unpack.nfs"); + EnDecryptNFS("hif_dec.nfs", "hif.nfs", key, buildZero(key.Length), true, header); + if (!keepFiles) + File.Delete("hif_dec.nfs"); + splitNFSFile("hif.nfs"); + if (!keepFiles) + File.Delete("hif.nfs"); + } + } + + + public static int checkArgs(string[] args) + { + for (int i = 0; i < args.Length; i++) + switch (args[i]) + { + case "-dec": + dec=true; + break; + case "-enc": + enc = true; + break; + case "-keep": + keepFiles = true; + break; + case "-key": + if (i == args.Length) + return -1; + keyFile = args[i+1]; + i++; + break; + case "-wiikey": + if (i == args.Length) + return -1; + wiiKeyFile = args[i+1]; + i++; + break; + case "-iso": + if (i == args.Length) + return -1; + isoFile = args[i + 1]; + i++; + break; + case "-nfs": + if (i == args.Length) + return -1; + nfsDir = args[i + 1]; + i++; + break; + case "-h": + Console.WriteLine("+++++ NFS2ISO2NFS +++++"); + Console.WriteLine(); + Console.WriteLine("-dec Decrypt .nfs files to an .iso file."); + Console.WriteLine("-enc Encrypt an .íso file to -nfs file.s"); + Console.WriteLine("-key Location of AES key file. Default: code\\htk.bin."); + Console.WriteLine("-wiikey Location of Wii Common key file. Default: wii_common_key.bin."); + Console.WriteLine("-iso Location of .iso file. Default: game.iso."); + Console.WriteLine("-nfs Location of .nfs files. Default: current Directory."); + Console.WriteLine("-keep Don't delete the files produced in intermediate steps."); + Console.WriteLine("-help Print this text."); + return -1; + default: + break; + } + + string dir = Directory.GetCurrentDirectory(); + if (!Path.IsPathRooted(keyFile)) + keyFile = dir + "\\" + keyFile; + if (!Path.IsPathRooted(isoFile)) + isoFile = dir + "\\" + isoFile; + if (!Path.IsPathRooted(wiiKeyFile)) + wiiKeyFile = dir + "\\" + wiiKeyFile; + if (!Path.IsPathRooted(nfsDir)) + nfsDir = dir + "\\" + nfsDir; + + + if (dec || ((!dec && !enc) && File.Exists(nfsDir + "\\hif_000000.nfs"))) + { + Console.WriteLine("+++++ NFS2ISO +++++"); + Console.WriteLine(); + if (dec && !enc && !File.Exists(nfsDir + "\\hif_000000.nfs")) + { + Console.WriteLine(".nfs files not found! Exiting..."); + return -1; + } + else if ((!dec && !enc) && File.Exists(nfsDir + "\\hif_000000.nfs")) + { + Console.WriteLine("You haven't specified if you want to use nfs2iso or iso2nfs"); + Console.WriteLine("Found .nfs files! Assuming you want to use nfs2iso..."); + dec = true; + enc = false; + } + } + else if (enc || (((!dec && !enc) || (!dec && !enc)) && File.Exists(isoFile))) + { + Console.WriteLine("+++++ ISO2NFS +++++"); + Console.WriteLine(); + if (!dec && enc && !File.Exists(isoFile)) + { + Console.WriteLine(".iso file not found! Exiting..."); + return -1; + } + else if (((dec && enc) || (!dec && !enc)) && File.Exists(isoFile)) + { + Console.WriteLine("You haven't specified if you want to use nfs2iso or iso2nfs"); + Console.WriteLine("Found .iso file! Assuming you want to use iso2nfs..."); + dec = false; + enc = true; + } + } + else + { + Console.WriteLine("You haven't specified if you want to use nfs2iso or iso2nfs"); + Console.WriteLine("Found neither .iso nor .nfs files! Check -help for usage of this program."); + return -1; + } + return 0; + } + + + public static byte[] checkKeyFiles() + { + Console.WriteLine("Searching for AES key file..."); + if (!File.Exists(keyFile)) + { + Console.WriteLine("Could not find AES key file! Exiting..."); + return null; + } + byte[] key = getKey(keyFile); + if (key == null) + { + Console.WriteLine("AES key file has wrong file size! Exiting..."); + return null; + } + Console.WriteLine("AES key file found!"); + + if (WII_COMMON_KEY[0] != 0xeb) + { + Console.WriteLine("Wii common key not found in source code. Looking for file..."); + if (!File.Exists(wiiKeyFile)) + { + Console.WriteLine("Could not find Wii common key file! Exiting..."); + return null; + } + WII_COMMON_KEY = getKey(wiiKeyFile); + if (key == null) + { + Console.WriteLine("Wii common key file has wrong file size! Exiting..."); + return null; + } + Console.WriteLine("Wii Common Key file found!"); + } + else Console.WriteLine("Wii common key found in source code!"); + return key; + } + + + public static byte[] getKey(string keyDir) + { + using (var keyFile = new BinaryReader(File.OpenRead(keyDir))) + { + long keySize = keyFile.BaseStream.Length; + if (keySize != 16) + return null; + return keyFile.ReadBytes(0x10); + } + } + + + public static byte[] buildZero(int size) + { + byte[] iv = new byte[size]; + for (int i = 0; i < size; i++) + iv[i] = 0; + return iv; + } + + + public static void combineNFSFiles(string outFile) + { + using (var nfs = new BinaryWriter(File.OpenWrite(outFile))) + { + Console.WriteLine("Looking for .nfs files..."); + int nfsNo = -1; + while (File.Exists(nfsDir + "\\hif_" + String.Format("{0:D6}", nfsNo + 1) + ".nfs")) + nfsNo++; + Console.WriteLine((nfsNo + 1) + " .nfs files found!"); + Console.WriteLine("Joining .nfs files..."); + Console.WriteLine(); + for (int i = 0; i <= nfsNo; i++) + { + Console.WriteLine("Processing hif_" + String.Format("{0:D6}", i) + ".nfs..."); + var nfsTemp = new BinaryReader(File.OpenRead(nfsDir + "\\hif_" + String.Format("{0:D6}", i) + ".nfs")); + if (i == 0) + { + nfsTemp.ReadBytes(HEADER_SIZE); + nfs.Write(nfsTemp.ReadBytes((int)nfsTemp.BaseStream.Length - HEADER_SIZE)); + } + else nfs.Write(nfsTemp.ReadBytes((int)nfsTemp.BaseStream.Length)); + } + } + } + + + public static void splitNFSFile(string inFile) + { + using (var nfs = new BinaryReader(File.OpenRead(inFile))) + { + Console.WriteLine(); + long size = nfs.BaseStream.Length; + int i=0; + do + { + Console.WriteLine("Building hif_" + String.Format("{0:D6}", i) + ".nfs..."); + var nfsTemp = new BinaryWriter(File.OpenWrite(Directory.GetCurrentDirectory() + "\\hif_" + String.Format("{0:D6}", i) + ".nfs")); + nfsTemp.Write(nfs.ReadBytes(size > NFS_SIZE ? NFS_SIZE : (int)size)); + size -= NFS_SIZE; + i++; + } while (size > 0); + } + } + + + public static byte[] getHeader(string inFile) + { + using (var file = new BinaryReader(File.OpenRead(inFile))) + { + return file.ReadBytes(0x200); + } + } + + + public static long[] manipulateISO(string InFile, string OutFile, bool enc) + { + using (var er = new BinaryReader(File.OpenRead(InFile))) + using (var ew = new BinaryWriter(File.OpenWrite(OutFile))) + { + long[] sizeInfo = new long[2]; + + Console.WriteLine(); + Console.WriteLine("Read partition table..."); + Console.WriteLine(); + ew.Write(er.ReadBytes(0x40000)); + + byte[] partitionTable = er.ReadBytes(0x20); + ew.Write(partitionTable); + int[,] partitionInfo = new int[2,4]; //first coorfinate number of partitions, second offset of partition table + for (byte i = 0; i < 4; i++) + { + partitionInfo[0,i] = partitionTable[0x0 + 0x8*i] * 0x1000000 + partitionTable[0x1 + 0x8*i] * 0x10000 + partitionTable[0x2 + 0x8*i] * 0x100 + partitionTable[0x3 + 0x8*i]; + Console.WriteLine("Number of " + (i+1) + ". partitions: " + partitionInfo[0,i]); + if (partitionInfo[0, i] == 0) + partitionInfo[1, i] = 0; + else partitionInfo[1,i] = (partitionTable[0x4 + 0x8*i] * 0x1000000 + partitionTable[0x5 + 0x8*i] * 0x10000 + partitionTable[0x6 + 0x8*i] * 0x100 + partitionTable[0x7 + 0x8*i]) * 0x4; + Console.WriteLine("Partition info table offset: 0x" + Convert.ToString(partitionInfo[1,i], 16)); + } + Console.WriteLine(); + partitionInfo = sort(partitionInfo, 4); + byte[][] partitionInfoTable = new byte[4][]; + List partitionOffsetList = new List(); + long curPos = 0x40020; + int k = 0; + for (int i = 0; i < 4; i++) + { + if (partitionInfo[0, i] != 0) + { + ew.Write(er.ReadBytes((int)(partitionInfo[1, i] - curPos))); + curPos += (partitionInfo[1, i] - curPos); + partitionInfoTable[i] = er.ReadBytes(0x8 * partitionInfo[0, i]); + curPos += (0x8 * partitionInfo[0, i]); + for (int j = 0; j < partitionInfo[0, i]; j++) + if (partitionInfoTable[i][0x7 + 0x8 * j] == 0) //check if game partition + { + partitionOffsetList.Add((partitionInfoTable[i][0x0 + 0x8 * j] * 0x1000000 + partitionInfoTable[i][0x1 + 0x8 * j] * 0x10000 + partitionInfoTable[i][0x2 + 0x8 * j] * 0x100 + partitionInfoTable[i][0x3 + 0x8 * j]) * 0x4); + Console.WriteLine("Data partition at offset: 0x" + Convert.ToString(partitionOffsetList[k], 16)); + k++; + } + ew.Write(partitionInfoTable[i]); + } + } + Console.WriteLine(); + int[] partitionOffsets = partitionOffsetList.ToArray(); + partitionOffsets = sort(partitionOffsets, partitionOffsets.Length); + sizeInfo[0] = partitionOffsets[0]; + byte[] IV = new byte[0x10]; + int timer = 0; + int l = 0; + for (int i = 0; i < partitionOffsets.Length; i++) + { + ew.Write(er.ReadBytes((int)(partitionOffsets[i] - curPos))); + curPos += (partitionOffsets[i] - curPos); + ew.Write(er.ReadBytes(0x1BF)); //Write start of partiton + byte[] enc_titlekey = er.ReadBytes(0x10); //read encrypted titlekey + ew.Write(enc_titlekey); //Write encrypted titlekey + ew.Write(er.ReadBytes(0xD)); //Write bytes till titleID + byte[] titleID = er.ReadBytes(0x8); //read titleID + ew.Write(titleID); + for (int j = 0; j < 0x10; j++) //build IV + if (j < 8) + IV[j] = titleID[j]; + else IV[j] = 0x0; + ew.Write(er.ReadBytes(0xC0)); //Write bytes till end of ticket + byte[] partitionHeader = er.ReadBytes(0x1FD5C); + long partitionSize = (long)0x4 * (partitionHeader[0x18] * 0x1000000 + partitionHeader[0x19] * 0x10000 + partitionHeader[0x1A] * 0x100 + partitionHeader[0x1B]); + Console.WriteLine("Partition size: 0x" + Convert.ToString(partitionSize, 16)); + ew.Write(partitionHeader); //Write bytes till start of partition data + curPos += 0x20000; + curPos += partitionSize; + byte[] titlekey = aes_128_cbc(WII_COMMON_KEY, IV, enc_titlekey, false); + Console.WriteLine("Write game partition " + i + "..."); + byte[] Sector = new byte[SECTOR_SIZE]; + while (partitionSize >= SECTOR_SIZE) + { + if (timer == 8000) + { + timer = 0; + l++; + Console.WriteLine((l * 256) + " MB processed..."); + } + timer++; + ew.Write(er.ReadBytes(0x3D0)); + IV = er.ReadBytes(0x10); + ew.Write(IV); + ew.Write(er.ReadBytes(0x20)); + Sector = er.ReadBytes(SECTOR_SIZE - 0x400); + Sector = aes_128_cbc(titlekey, IV, Sector, enc); + ew.Write(Sector); + partitionSize -= SECTOR_SIZE; + } + sizeInfo[1] = curPos - sizeInfo[0]; + if (partitionSize != 0) + Console.WriteLine("Last cluster was not complete. This may be a problem."); + } + if (enc) + { + Console.WriteLine(); + Console.WriteLine("Writing zeros..."); + long rest; + if (curPos > 0x118240000) + rest = 0x1FB4E0000 - curPos; + else rest = 0x118240000 - curPos; + l = 0; + timer = 0; + while (rest > 0) + { + if (timer == 8000) + { + timer = 0; + l++; + Console.WriteLine((l * 256) + " MB processed..."); + } + timer++; + ew.Write(buildZero(rest > SECTOR_SIZE ? SECTOR_SIZE : (int)rest)); + rest -= SECTOR_SIZE; + } + return null; + } + else return sizeInfo; + } + } + + + public static void unpackNFS(string InFile, string OutFile, byte[] header) + { + using (var er = new BinaryReader(File.OpenRead(InFile))) + using (var ew = new BinaryWriter(File.OpenWrite(OutFile))) + { + Console.WriteLine(); + Console.WriteLine("Unpacking nfs..."); + Console.WriteLine(); + int numberOfParts = 0x1000000 * header[0x10] + 0x10000 * header[0x11] + 0x100 * header[0x12] + header[0x13]; + Console.WriteLine(numberOfParts + " parts found..."); + int start, length; + int pos = 0x0; + int j = 0; + for (int i = 0; i < numberOfParts; i++) + { + start = SECTOR_SIZE * (0x1000000 * header[0x14 + i * 0x8] + 0x10000 * header[0x15 + i * 0x8] + 0x100 * header[0x16 + i * 0x8] + header[0x17 + i * 0x8]); + length = SECTOR_SIZE * (0x1000000 * header[0x18 + i * 0x8] + 0x10000 * header[0x19 + i * 0x8] + 0x100 * header[0x1A + i * 0x8] + header[0x1B + i * 0x8]); + j = start - pos; + Console.WriteLine("Writing zero segment " + i + " of size 0x" + Convert.ToString(j, 16)); + while (j > 0) + { + ew.Write(buildZero(SECTOR_SIZE)); + j -= SECTOR_SIZE; + } + Console.WriteLine("Writing data segment " + i + " of size 0x" + Convert.ToString(length, 16)); + j = length; + while (j > 0) + { + ew.Write(er.ReadBytes(SECTOR_SIZE)); + j -= SECTOR_SIZE; + } + pos = start + length; + } + } + } + + + public static byte[] packNFS(string InFile, string OutFile, long[] sizeInfo) + { + using (var er = new BinaryReader(File.OpenRead(InFile))) + using (var ew = new BinaryWriter(File.OpenWrite(OutFile))) + { + Console.WriteLine(); + Console.WriteLine("Generating EGGS header..."); + byte[] header = new byte[0x200]; + for (int i = 0; i < 0x200; i++) + header[i] = 0xff; + + header[0x0]=0x45; + header[0x1]=0x47; + header[0x2]=0x47; + header[0x3]=0x53; + + header[0x4]=0x00; + header[0x5]=0x01; + header[0x6]=0x10; + header[0x7]=0x11; + + header[0x8]=0x00; + header[0x9]=0x00; + header[0xA]=0x00; + header[0xB]=0x00; + + header[0xC]=0x00; + header[0xD]=0x00; + header[0xE]=0x00; + header[0xF]=0x00; + + header[0x10]=0x00; + header[0x11]=0x00; + header[0x12]=0x00; + header[0x13]=0x03; + + header[0x14]=0x00; + header[0x15]=0x00; + header[0x16]=0x00; + header[0x17]=0x00; + + header[0x18]=0x00; + header[0x19]=0x00; + header[0x1A]=0x00; + header[0x1B]=0x01; + + header[0x1C]=0x00; + header[0x1D]=0x00; + header[0x1E]=0x00; + header[0x1F]=0x08; + + header[0x20] = 0x00; + header[0x21] = 0x00; + header[0x22] = 0x00; + header[0x23] = 0x02; + + header[0x24] = (byte)((sizeInfo[0] / 0x8000) / 0x1000000); + header[0x25] = (byte)(((sizeInfo[0] / 0x8000) / 0x10000) % 0x100); + header[0x26] = (byte)(((sizeInfo[0] / 0x8000) / 0x100) % 0x10000); + header[0x27] = (byte)((sizeInfo[0] / 0x8000) % 0x1000000); + + header[0x28] = (byte)((sizeInfo[1] / 0x8000) / 0x1000000); + header[0x29] = (byte)(((sizeInfo[1] / 0x8000) / 0x10000) % 0x100); + header[0x2A] = (byte)(((sizeInfo[1] / 0x8000) / 0x100) % 0x10000); + header[0x2B] = (byte)((sizeInfo[1] / 0x8000) % 0x1000000); + + header[0x1FC] = 0x53; + header[0x1FD] = 0x47; + header[0x1FE] = 0x47; + header[0x1FF] = 0x45; + + Console.WriteLine("Packing nfs..."); + + int numberOfParts = 0x1000000 * header[0x10] + 0x10000 * header[0x11] + 0x100 * header[0x12] + header[0x13]; + Console.WriteLine("Packing " + numberOfParts + " parts..."); + int start, length; + int pos = 0x0; + int j = 0; + for (int i = 0; i < numberOfParts; i++) + { + start = SECTOR_SIZE * (0x1000000 * header[0x14 + i * 0x8] + 0x10000 * header[0x15 + i * 0x8] + 0x100 * header[0x16 + i * 0x8] + header[0x17 + i * 0x8]); + length = SECTOR_SIZE * (0x1000000 * header[0x18 + i * 0x8] + 0x10000 * header[0x19 + i * 0x8] + 0x100 * header[0x1A + i * 0x8] + header[0x1B + i * 0x8]); + j = start - pos; + Console.WriteLine("Delete zero segment " + i + " of size 0x" + Convert.ToString(j, 16)); + while (j > 0) + { + er.ReadBytes(SECTOR_SIZE); + j -= SECTOR_SIZE; + } + Console.WriteLine("Writing data segment " + i + " of size 0x" + Convert.ToString(length, 16)); + j = length; + while (j > 0) + { + ew.Write(er.ReadBytes(SECTOR_SIZE)); + j -= SECTOR_SIZE; + } + pos = start + length; + } + return header; + } + } + + + public static void EnDecryptNFS(string InFile, string OutFile, byte[] key, byte[] iv, bool enc, byte[] header) + { + using (var er = new BinaryReader(File.OpenRead(InFile))) + using (var ew = new BinaryWriter(File.OpenWrite(OutFile))) + { + Console.WriteLine(); + if (enc) + { + Console.WriteLine("Writing EGGS header..."); + ew.Write(header); + Console.WriteLine("Encrypting hif.nfs..."); + } + else + Console.WriteLine("Decrypting hif.nfs..."); + Console.WriteLine(); + byte[] Sector = new byte[SECTOR_SIZE]; + int timer = 0; + int i = 0; + //init size + long leftSize = er.BaseStream.Length; + do + { + if (timer == 8000) + { + timer = 0; + i++; + Console.WriteLine((i * 256) + " MB processed..."); + } + timer++; + Sector = er.ReadBytes(leftSize > SECTOR_SIZE ? SECTOR_SIZE : (int)leftSize); + if (enc) + Sector = aes_128_cbc(key, iv, Sector, true); + else + Sector = aes_128_cbc(key, iv, Sector, false); + + //write it to outfile + ew.Write(Sector); + + //decrease remaining size + leftSize -= SECTOR_SIZE; + + //loop till end of file + } while (leftSize > 0); + } + } + + + public static byte[] aes_128_cbc(byte[] key, byte[] iv, byte[] data, bool enc) + { + byte[] result = new byte[data.Length]; + + try + { + System.Security.Cryptography.RijndaelManaged rm = new System.Security.Cryptography.RijndaelManaged(); + rm.Mode = System.Security.Cryptography.CipherMode.CBC; + rm.Padding = System.Security.Cryptography.PaddingMode.None; + rm.KeySize = 128; + rm.BlockSize = 128; + rm.Key = key; + rm.IV = iv; + + if (enc) + using (System.Security.Cryptography.ICryptoTransform itc = rm.CreateEncryptor()) + { + result = itc.TransformFinalBlock(data, 0, data.Length); + } + else + using (System.Security.Cryptography.ICryptoTransform itc = rm.CreateDecryptor()) + { + result = itc.TransformFinalBlock(data, 0, data.Length); + } + + rm.Clear(); + + return result; + } + catch (System.Security.Cryptography.CryptographicException e) + { + Console.WriteLine("A Cryptographic error occurred: {0}", e.Message); + return null; + } + } + + + public static int[,] sort(int[,] list, int size) + { + int max = 0; + int maxIndex = 0; + int temp; + for (int j = 0; j < size; j++) + { + for (int i = 0; i < size-j; i++) + if (list[1,i] > max) + { + max = list[1,i]; + maxIndex = i; + } + temp = list[0, size - j - 1]; + list[0, size - j - 1] = list[0, maxIndex]; + list[0, maxIndex] = temp; + temp = list[1, size - j - 1]; + list[1, size - j - 1] = list[1, maxIndex]; + list[1, maxIndex] = temp; + } + return list; + } + + + public static int[] sort(int[] list, int size) + { + int max = 0; + int maxIndex = 0; + int temp; + for (int j = 0; j < size; j++) + { + for (int i = 0; i < size - j; i++) + if (list[i] > max) + { + max = list[i]; + maxIndex = i; + } + temp = list[size - j - 1]; + list[size - j - 1] = list[maxIndex]; + list[maxIndex] = temp; + } + return list; + } + } +}