1. Reading the Data.000
The structure of Data.000 is as follows:
Code:
public List<IndexList> IndexEntry = new List<IndexEntry>();
IndexEntry Class
Code:
public class IndexEntry { int fileHash_length -- length of the filehash string string fileHash -- string of the fileHash_length int offset -- beginning position of this file in the data.000 int dataId -- The data.xxx file this entry belongs to }
2. Using the afore mentioned Data.000 structure you will simply use a binaryreader (c#) to load the data.000 file into a List<IndexEntry> of data000 entries
Load Class:
Code:
public void Load(string filePath) { string filePath = Directory.GetCurrentDirectory(); if (File.Exists(filePath)) { using (FileStream fs = new FileStream(File.Open(filePath, FileMode.Open, FileAccess.Read)) { using (BinaryReader br = new BinaryReader(fs, Encoding.Default)) { IndexEntry data000_entry = new IndexEntry(); byte[] array = br.ReadByte(); XOR.Cipher(ref array ref b); byte[] filename_bytes = br.ReadBytes(array[0]); XOR.Cipher(ref filename_bytes, ref b); byte[] values = br.ReadBytes(8); XOR.Cipher(ref values, ref b); data000_entry.fileHash = Encoding.Default.GetString(filename_bytes); data000_entry.offset = BitConverter.ToInt32(values , 0); data000_entry.size = BitConverter.ToInt32(values , 4); data000_entry.dataId = getID(data000_entry.FileHash); indexList.Add(data.000_entry); } } } }
XOR Class
Code:
public class XOR { static byte[] s_CipherTable = new byte[] { 119, 232, 94, 236, 183, 78, 193, 135, 79, 230, 245, 60, 31, 179, 21, 67, 106, 73, 48, 166, 191, 83, 168, 53, 91, 229, 158, 14, 65, 236, 34, 184, 212, 128, 164, 140, 206, 101, 19, 29, 75, 8, 90, 106, 187, 111, 173, 37, 184, 221, 204, 119, 48, 116, 172, 140, 90, 74, 154, 155, 54, 188, 83, 10, 60, 248, 150, 11, 93, 170, 40, 169, 178, 130, 19, 110, 241, 193, 147, 169, 158, 95, 32, 207, 212, 204, 91, 46, 22, 245, 201, 76, 178, 28, 87, 238, 20, 237, 249, 114, 151, 34, 27, 74, 164, 46, 184, 150, 239, 75, 63, 142, 171, 96, 93, 127, 44, 184, 173, 67, 173, 118, 143, 95, 146, 230, 78, 167, 212, 71, 25, 107, 105, 52, 181, 14, 98, 109, 164, 82, 185, 227, 224, 100, 67, 61, 227, 112, 245, 144, 179, 162, 6, 66, 2, 152, 41, 80, 63, 253, 151, 88, 104, 1, 140, 30, 15, 239, 139, 179, 65, 68, 150, 33, 168, 218, 94, 139, 74, 83, 27, 253, 245, 33, 63, 247, 186, 104, 71, 249, 101, 223, 82, 206, 224, 222, 236, 239, 205, 119, 162, 14, 188, 56, 47, 100, 18, 141, 240, 92, 224, 11, 89, 214, 45, 153, 205, 231, 1, 21, 224, 103, 244, 50, 53, 212, 17, 33, 195, 222, 152, 101, 237, 84, 157, 28, 185, 176, 170, 169, 12, 138, 180, 102, 96, 225, 255, 46, 200, 0, 67, 169, 103, 55, 219, 156, 119, 232, 94, 236, 183, 78, 193, 135, 79, 230, 245, 60, 31, 179, 21, 67, 106, 73, 48, 166, 191, 83, 168, 53, 91, 229, 158, 14, 65, 236, 34, 184, 212, 128, 164, 140, 206, 101, 19, 29, 75, 8, 90, 106, 187, 111, 173, 37, 184, 221, 204, 119, 48, 116, 172, 140, 90, 74, 154, 155, 54, 188, 83, 10, 60, 248, 150, 11, 93, 170, 40, 169, 178, 130, 19, 110, 241, 193, 147, 169, 158, 95, 32, 207, 212, 204, 91, 46, 22, 245, 201, 76, 178, 28, 87, 238, 20, 237, 249, 114, 151, 34, 27, 74, 164, 46, 184, 150, 239, 75, 63, 142, 171, 96, 93, 127, 44, 184, 173, 67, 173, 118, 143, 95, 146, 230, 78, 167, 212, 71, 25, 107, 105, 52, 181, 14, 98, 109, 164, 82, 185, 227, 224, 100, 67, 61, 227, 112, 245, 144, 179, 162, 6, 66, 2, 152, 41, 80, 63, 253, 151, 88, 104, 1, 140, 30, 15, 239, 139, 179, 65, 68, 150, 33, 168, 218, 94, 139, 74, 83, 27, 253, 245, 33, 63, 247, 186, 104, 71, 249, 101, 223, 82, 206, 224, 222, 236, 239, 205, 119, 162, 14, 188, 56, 47, 100, 18, 141, 240, 92, 224, 11, 89, 214, 45, 153, 205, 231, 1, 21, 224, 103, 244, 50, 53, 212, 17, 33, 195, 222, 152, 101, 237, 84, 157, 28, 185, 176, 170, 169, 12, 138, 180, 102, 96, 225, 255, 46, 200, 0, 67, 169, 103, 55, 219, 156 }; public static void Cipher(ref byte[] buffer, ref byte index) { for (int i = 0; i < buffer.Length; i++) { byte[] expr_0C_cp_0 = buffer; int expr_0C_cp_1 = i; expr_0C_cp_0[expr_0C_cp_1] ^= s_CipherTable[(int)index]; index += 1; } } public static bool Encrypted(string ext) { return !(ext == "mp3") && !(ext == "ogg") && !(ext == "raw") && !(ext == "dds") && !(ext == "tga") && !(ext == "naf") && !(ext == "nx3") && !(ext == "cob") && !(ext == "nfm"); } }
Code:
public static int getID(string hash) { byte[] bytes = encoding.GetBytes(hash.ToLower()); int num = 0; for (int i = 0; i < bytes.Length; i++) { num = (num << 5) - num + (int)bytes[i]; } if (num < 0) { num *= -1; } return num % 8 + 1; }
3. This will give you a List<dataIndexEntry> which you can the iterate through.
(You can tell if an incoming update is bigger by comparing update file size againt the data000_entry.size)
if the update file size > the data000_entry.size then you will append it's data.xxx file starting with the ending offset (see Update method for example)
Update Class
Code:
/// <summary> /// Updates an existing entry in the data.xxx file system /// </summary> /// <param name="fileName">[hashed] File name to be overwritten</param> /// <param name="fileBytes">byte[] replacing current byte[]</param> /// <param name="indexList">Current list of data.000 entries</param> /// <param name="append">Determines if file should be written to the end of it's data.xxx</param> /// <returns>Altered indexList</returns> public static bool Update(string fileName, byte[] fileBytes, ref List<IndexEntry> indexList, bool append) { IndexEntry tmpEntry_new = new IndexEntry(); IndexEntry tmpEntry_existing = indexList.Find(i => i.FileHash == fileName); string physicalPath; tmpEntry_new.FileHash = fileName; tmpEntry_new.DataID = getID(fileName); physicalPath = Path.Combine(Directory.GetCurrentDirectory(), string.Concat("data.00", tmpEntry_new.DataID)); //Check for physical data.xxx if (File.Exists(physicalPath)) { if (append) //File is larger than stored, it is defaulting to append { tmpEntry_new.Offset = (int)new FileInfo(physicalPath).Length; } else //File is being overwritten { tmpEntry_new.Offset = tmpEntry_existing.Offset; } //Define the size of the new file in the replacment IndexEntry tmpEntry_new.Size = fileBytes.Length; //Delete old entry from the indexList indexList.Remove(tmpEntry_existing); //Add the new files entry into the indexList indexList.Add(tmpEntry_new); //If the file is to be encrypted, encrypt it if (XOR.Encrypted(Path.GetExtension(StringCipher.Decode(fileName.Remove(0, 1))))) { byte b = 0; //Apply the XOR cipher to the byte array XOR.Cipher(ref fileBytes, ref b); } //Write the file to the appropriate data.xxx file WriteFile(fileBytes, physicalPath, tmpEntry_new); return true; } else { MessageBox.Show("Failed to locate data file", "Data Load Exception", MessageBoxButtons.OK, MessageBoxIcon.Error); } return false; }
WriteFile Class
Code:
/// <summary> /// Writes a file as byte[] to the data.xxx files /// </summary> /// <param name="fileBytes">byte array of file being written</param> /// <param name="physicalPath">physical path to write the data file</param> /// <param name="newFileInfo">container of information regarding new file</param> /// <returns>bool value indicating success or failure</returns> static bool WriteFile(byte[] newFileBytes, string physicalPath, IndexEntry newFileInfo) { try { //Write the file using provided information using (FileStream fs = new FileStream(physicalPath, FileMode.Open, FileAccess.Write)) { using (BinaryWriter bw = new BinaryWriter(fs, Encoding.Default)) { bw.BaseStream.Position = (long)newFileInfo.Offset; bw.Write(newFileBytes); } } return true; } catch (Exception ex) { System.Windows.Forms.MessageBox.Show(ex.Message, "Data000 Exception Occured", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } }
StringCipher Class
Code:
public class StringCipher { private static byte[] s_CipherTable = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 103, 32, 0, 38, 119, 44, 108, 78, 88, 79, 0, 55, 46, 37, 101, 0, 56, 95, 93, 35, 80, 49, 45, 36, 86, 91, 0, 89, 0, 94, 0, 0, 75, 125, 106, 48, 64, 71, 83, 41, 65, 120, 121, 54, 57, 69, 70, 123, 87, 98, 61, 82, 118, 116, 104, 50, 52, 77, 40, 107, 0, 109, 97, 43, 126, 68, 39, 67, 33, 74, 73, 100, 66, 85, 96, 113, 102, 112, 72, 81, 51, 76, 110, 111, 90, 105, 114, 115, 117, 59, 122, 99, 0, 84, 53, 0 }; private static byte[] s_CharTable = new byte[] { 94, 38, 84, 95, 78, 115, 100, 123, 120, 111, 53, 118, 96, 114, 79, 89, 86, 43, 44, 105, 73, 85, 35, 107, 67, 74, 113, 56, 36, 39, 126, 76, 48, 80, 93, 70, 101, 66, 110, 45, 65, 117, 40, 112, 88, 72, 90, 104, 119, 68, 121, 50, 125, 97, 103, 87, 71, 55, 75, 61, 98, 81, 59, 83, 82, 116, 41, 52, 54, 108, 64, 106, 69, 37, 57, 33, 99, 49, 91, 51, 102, 109, 77, 122, 0 }; public static bool IsEncoded(string hash) { return StringCipher.Encode(StringCipher.Decode(hash)) == hash; } public static string Encode(string name) { char[] array = name.ToLower().ToCharArray(); int num = 0; for (int i = 0; i < array.Length; i++) { num = (int)(array[i] * '\u0011') + num + 1; } num = (array.Length + num & 31); if (num == 0) { num = 32; } char c = (char)StringCipher.s_CharTable[num]; int num2 = num; for (int j = 0; j < array.Length; j++) { num = (int)array[j]; for (int k = 0; k < num2; k++) { num = (int)StringCipher.s_CipherTable[num]; } num2 = (num2 + 1 + (int)(array[j] * '\u0011') & 31); if (num2 == 0) { num2 = 32; } array[j] = (char)num; } if (array.Length > 4) { int num3 = (int)Math.Floor(0.33000001311302191 * (double)array.Length); int num4 = (int)Math.Floor(0.6600000262260437 * (double)array.Length); char c2 = array[num4]; char c3 = array[num3]; array[num4] = array[0]; array[num3] = array[1]; array[0] = c2; array[1] = c3; } num = 0; for (int l = 0; l < array.Length; l++) { num += (int)array[l]; } char c4 = (char)StringCipher.s_CharTable[num % 84]; return c4 + new string(array) + c; } public static string Decode(string hash) { if (hash.Length > 0) { char[] array = hash.Substring(1, hash.Length - 2).ToCharArray(); if (array.Length > 4) { int num = (int)Math.Floor(0.33000001311302191 * (double)array.Length); int num2 = (int)Math.Floor(0.6600000262260437 * (double)array.Length); char c = array[num2]; char c2 = array[num]; array[num2] = array[0]; array[num] = array[1]; array[0] = c; array[1] = c2; } int num3 = Array.IndexOf<byte>(StringCipher.s_CharTable, (byte)hash[hash.Length - 1], 0, StringCipher.s_CharTable.Length); int num4 = num3; for (int i = 0; i < array.Length; i++) { num3 = (int)array[i]; for (int j = 0; j < num4; j++) { int num5 = Array.IndexOf<byte>(StringCipher.s_CipherTable, (byte)num3, 0, StringCipher.s_CipherTable.Length); if (num5 < StringCipher.s_CipherTable.Length) { num3 = num5; } else { num3 = 255; } } array[i] = (char)num3; num4 = (1 + num4 + 17 * num3 & 31); if (num4 == 0) { num4 = 32; } } return new string(array); } return ""; } }
4. With the above Update method the data.xxx file will be updated appriopriately and a corrected Data.000 file will be returned (which you should then write to disk) [ The reverse of loading ]
Save Class
Code:
/// <summary> /// Saves the provided indexList into a ready to use data.000 index /// </summary> /// <param name="indexList">list of indexs to assemble data.000 with</param> /// <returns>bool value indicating success or failure</returns> public static bool Save(List<IndexEntry> indexList, List<BlankIndexEntry> blankIndexList) { try { if (File.Exists("data.000")) { File.Delete("data.000"); } using (BinaryWriter bw = new BinaryWriter(File.Open("data.000", FileMode.Create), Encoding.Default)) { new StringCipher(); byte b = 0; foreach (IndexEntry indexEntry in indexList) { byte[] buffer = new byte[] { Convert.ToByte(indexEntry.FileHash.Length) }; XOR.Cipher(ref buffer, ref b); bw.Write(buffer); byte[] bytes = Encoding.Default.GetBytes(indexEntry.FileHash); XOR.Cipher(ref bytes, ref b); bw.Write(bytes); byte[] array = new byte[8]; Buffer.BlockCopy(BitConverter.GetBytes(indexEntry.Offset), 0, array, 0, 4); Buffer.BlockCopy(BitConverter.GetBytes(indexEntry.Size), 0, array, 4, 4); XOR.Cipher(ref array, ref b); bw.Write(array); } } return true; } catch { return false; } }
You now know how to update the clients data.xxx files EXACTLY as the retail launcher does (I won't explain the retail launcher as Glandu2 has done it before, just google it.)
Again I would just like to give a phenomenal thanks to c1ph3r and Pyrok and Glandu and Exiled for making this community better and helping me to be a better developer!
Coming soon a new Auth Emu by Glandu2 and I will explain in full the OTP Login process!