/// <summary> /// Processes files packed as type 2 (encrypted & compressed) /// </summary> /// <param name="packFilePath">Path to EPK file (name included)</param> /// <param name="saveFilesPath">Path to where the file should be saved (name not included)</param> /// <param name="item">Item from deserialized list</param> /// <param name="packKey">Pack XTEA key</param> /// <param name="hashMismatchFiles">Vector holding files with CRC mismatch</param> /// <param name="errorFiles">Vector holding files ignored</param> /// <returns></returns> public static int ProcessFileType2(string packFilePath, string saveFilesPath, IndexItem item, byte[] packKey, Action<ErrorItem, string> fileLoggingCallback) { int valueToReturn = 0; byte[] fileHeaderBuffer = IOHelper.ReadFileOffsetToLength(packFilePath, item.Offset, 16); // Read FourCC value var fourCc = new byte[4]; Array.Copy(fileHeaderBuffer, 0, fourCc, 0, 4); // Get sizes from header int encryptedSize = IOHelper.ReadIntFromArray(fileHeaderBuffer, 4, 4); int compressedSize = IOHelper.ReadIntFromArray(fileHeaderBuffer, 8, 4); int decompressedSize = IOHelper.ReadIntFromArray(fileHeaderBuffer, 12, 4); if (encryptedSize <= 0 || compressedSize <= 0 || decompressedSize <= 0) { fileLoggingCallback(new ErrorItem(item.Filename, "Invalid encrypted/compressed/decompressed size."), null); return 2; } #region Cap Check if (encryptedSize > 629145600) { fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Max memory allocation reached, tried to allocate: {0} mb", Math.Round(encryptedSize / 1024.0 / 1024.0, 2))), null); return 2; } if (compressedSize > 629145600) { fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Max memory allocation reached, tried to allocate: {0} mb", Math.Round(encryptedSize / 1024.0 / 1024.0, 2))), null); return 2; } if (decompressedSize > 629145600) { fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Max memory allocation reached, tried to allocate: {0} mb", Math.Round(encryptedSize / 1024.0 / 1024.0, 2))), null); return 2; } #endregion // Is header correct? if (!fourCc.SequenceEqual(ConstantsBase.LzoFourCc)) { // Show error msg and cancel operation // AppLog.SendMessage(8, item.Filename, item.ParentFile); TODO fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Invalid FourCC: {0}. Expected: {1} ({2})", BitConverter.ToString(fourCc), BitConverter.ToString(ConstantsBase.LzoFourCc), Encoding.ASCII.GetString(ConstantsBase.LzoFourCc))), ""); return 2; } // Get actual data byte[] rawDataBuffer = IOHelper.ReadFileOffsetToLength(packFilePath, item.Offset + 16, encryptedSize); // Take care of relative path and folders string path = DrivePointManager.RemoveDrivePoints((saveFilesPath + item.Filename).Replace("\\", "/")); // uh, I've found a bug. This should be changed... The program now detects new drive points, for example string foldersPath = path.Substring(0, path.LastIndexOf("/", StringComparison.Ordinal)); // Check if paths exist Directory.CreateDirectory(foldersPath); // Overwrite file TODO SAME AS TYPE 2 if (File.Exists(path)) { if (IOHelper.IsFileLocked(path)) { //WindowLog.LogOutputMessage(Log.LogId.FILE_OPENED_BY_EXTERNAL_PROCESS_LOG, args: path); TODO return 2; } File.Delete(path); } // Create encryption and encryption without header buffers var decryptedBuffer = new byte[encryptedSize]; var decryptedBufferWithoutHeader = new byte[encryptedSize - 4]; // It should never return false if (Xtea.Decrypt(packKey, rawDataBuffer, decryptedBuffer, (uint)encryptedSize) == false) { throw new Exception("INTERNAL ERROR - DECRYPTION FAILED"); } // Get header after decryption var headerAfterDecryption = new byte[4]; Array.Copy(decryptedBuffer, 0, headerAfterDecryption, 0, 4); // Was the encryption key correct? if (!headerAfterDecryption.SequenceEqual(ConstantsBase.LzoFourCc)) { // Send message and return error //WindowLog.LogOutputMessage(Log.LogId.DECRYPTION_FAILED_LOG, args: item.Filename); TODO fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Invalid header after decryption: {0}. Expected: {1} ({2})", BitConverter.ToString(fourCc), BitConverter.ToString(ConstantsBase.LzoFourCc), Encoding.ASCII.GetString(ConstantsBase.LzoFourCc))), ""); valueToReturn = 2; } // Get actual decrypted data Array.Copy(decryptedBuffer, 4, decryptedBufferWithoutHeader, 0, decryptedBuffer.Length - 4); // Check hash if (!CompareCrcHashes(decryptedBufferWithoutHeader, item.CRCHash)) { // Log CRC mismatch // AppLog.SendMessage(2, item.Filename); fileLoggingCallback(null, item.Filename); valueToReturn = 1; } byte[] decompressedData = LzoHelper.DecompressData(decompressedSize, compressedSize, decryptedBufferWithoutHeader); // Write file File.WriteAllBytes(path, decompressedData); return valueToReturn; }
/// <summary> /// Buils EIX and EPK files /// </summary> /// <param name="list">Deserialized list</param> /// <param name="packFilePath">Path to EPK file, including the name</param> /// <param name="unpackedFilesPath">Path to unpacked files</param> /// <param name="indexKey">Index XTEA key</param> /// <param name="packKey">Pack XTEA key</param> /// <param name="errorLogCallBack"></param> /// <param name="progressCallback">Callback if progress updates are needed</param> /// <returns></returns> public static bool BuildIndexAndPackFiles( List<IndexItem> list, string packFilePath, string unpackedFilesPath, byte[] indexKey, byte[] packKey, Action<ErrorItem> errorLogCallBack, Action<int, int> progressCallback, Action fatalErrorCallback) { using (var fStream = new MemoryStream()) { packFilePath = packFilePath.Replace("\\", "/"); // Check if directory exists string directoryPath = packFilePath.Substring(0, packFilePath.LastIndexOf("/")); Directory.CreateDirectory(directoryPath); // Create stream to EPK file var epkStream = new FileStream(EterHelper.ReplaceWithEpkExt(packFilePath), FileMode.Create); // Size from header for each file int decompressedSize = 0; int compressedSize = 0; uint encryptedSize = 0; // File counter to index files int indexCount = 0; // Progress variables double actionProgress = 0; // FileOffset holder (EPK stream's length) int fileOffset = 0; // Write first header to EIX file fStream.Write(ConstantsBase.EterFourCc, 0, ConstantsBase.EterFourCc.Length); fStream.Write(BitConverter.GetBytes(2), 0, 4); fStream.Write(BitConverter.GetBytes(list.Count), 0, 4); try { foreach (IndexItem item in list) { // Loop through items var fileName = new byte[161]; var fileNameCrc = new byte[4]; //Index item's structure (totalizing 192 bytes) #region Byte Holders var fileIndex = new byte[4]; var realDataSize = new byte[4]; var dataSize = new byte[4]; var dataCrc = new byte[4]; var dataOffset = new byte[4]; var padding3B = new byte[3]; #endregion // Set all backslashs to forwards slashes item.Filename = item.Filename.Replace('\\', '/'); // Get raw data byte[] rawData = IOHelper.ReadFile(unpackedFilesPath + item.Filename); // Header sizees int encryptedFileSize = 0; int compressedFileSize = 0; int decompressedFileSize = 0; // Set real data & decompressed size to raw data's lenth realDataSize = BitConverter.GetBytes(rawData.Length); decompressedFileSize = rawData.Length; // Set fileoffset to actual stream's length fileOffset = (int)epkStream.Length; #region File Type Processing // Switch through the 3 possible cases switch (item.PackType) { case 0: // Write data to EPK stream epkStream.Write(rawData, 0, rawData.Length); // Set data size equal to raw data since no compression nor encrypted occured dataSize = BitConverter.GetBytes(rawData.Length); break; case 1: case 2: // Compress data byte[] compressedDataBuffer = LzoHelper.CompressData(rawData.Length, rawData); // Create buffer to hold header + compressedData byte[] compressedDataWithHeaderBuffer = new byte[compressedDataBuffer.Length + 4]; // Copy header and compressedData to previously created buffer Array.Copy(ConstantsBase.LzoFourCc, 0, compressedDataWithHeaderBuffer, 0, 4); Array.Copy(compressedDataBuffer, 0, compressedDataWithHeaderBuffer, 4, compressedDataBuffer.Length); // Set dataSize to compressedSize (since it assumes it's type 1) dataSize = BitConverter.GetBytes(compressedDataWithHeaderBuffer.Length + 16); // Set compressedSize compressedFileSize = compressedDataBuffer.Length; // If type 2 if (item.PackType == 2) { // Get encrypted size (ALWAYS the upper multiple) encryptedFileSize = GetUpperMultiple(compressedDataWithHeaderBuffer.Length); // Resize data to fit encryptedSize Array.Resize(ref compressedDataWithHeaderBuffer, encryptedFileSize); // Encrypt Data Xtea.Encrypt2(ref compressedDataWithHeaderBuffer, packKey); // Set dataSize to encryptedData + header dataSize = BitConverter.GetBytes(compressedDataWithHeaderBuffer.Length + 16); } // Write header of file to EPK stream epkStream.Write(ConstantsBase.LzoFourCc, 0, 4); epkStream.Write(BitConverter.GetBytes(encryptedFileSize), 0, 4); epkStream.Write(BitConverter.GetBytes(compressedFileSize), 0, 4); epkStream.Write(BitConverter.GetBytes(decompressedFileSize), 0, 4); // Write actual data epkStream.Write(compressedDataWithHeaderBuffer, 0, compressedDataWithHeaderBuffer.Length); break; } #endregion #region Building index file // Check if string replacment is needed string virtualPathFile = DrivePointManager.InsertDrivePoints(item.Filename); // Populate byte[] with data fileIndex = BitConverter.GetBytes(item.Index); byte[] fileNameTemp = Encoding.Default.GetBytes(virtualPathFile); fileNameCrc = CrcHelper.GetCrc32HashFromMemoryToByteArray(Encoding.Default.GetBytes(virtualPathFile)); realDataSize = (realDataSize == null) ? BitConverter.GetBytes(item.DiskSize) : realDataSize; dataSize = (dataSize == null) ? BitConverter.GetBytes(item.Size) : dataSize; dataCrc = CrcHelper.GetCrc32HashToByteArray(unpackedFilesPath + item.Filename); dataOffset = BitConverter.GetBytes(fileOffset); var compressedType = (byte)item.PackType; // Check if filename buffer is expectedSize if (fileNameTemp.Length != 161) { Array.Copy(fileNameTemp, 0, fileName, 0, fileNameTemp.Length); } // Write data to EIX's stream fStream.Write(fileIndex, 0, fileIndex.Length); fStream.Write(fileName, 0, 161); fStream.Write(padding3B, 0, padding3B.Length); fStream.Write(fileNameCrc.Reverse().ToArray(), 0, fileNameCrc.Length); fStream.Write(realDataSize, 0, realDataSize.Length); fStream.Write(dataSize, 0, dataSize.Length); fStream.Write(dataCrc.Reverse().ToArray(), 0, dataCrc.Length); fStream.Write(dataOffset, 0, dataOffset.Length); fStream.WriteByte(compressedType); fStream.Write(padding3B, 0, padding3B.Length); indexCount++; #endregion // Update progress actionProgress = (indexCount / (double)list.Count * 100.0); progressCallback(0, (int)actionProgress); } } catch (Exception ex) { //WindowLog.LogExceptioToFile(ex.ToString()); TODO fatalErrorCallback(); throw; } // Assign current stream's lenght to decmopressedSize decompressedSize = (int)fStream.Length; // Buffer to hold compressedData byte[] compressedData = LzoHelper.CompressData(decompressedSize, fStream.ToArray()); // Buffer with compressedData + MCOZ header byte[] compressedDataWithHeader = new byte[compressedData.Length + 4]; // Copy Header to buffer Array.Copy(ConstantsBase.LzoFourCc, 0, compressedDataWithHeader, 0, 4); // Copy data to buffer Array.Copy(compressedData, 0, compressedDataWithHeader, 4, compressedData.Length); // Save compressedSize compressedSize = compressedData.Length; // Save encryptedSize (round to upper multiple) encryptedSize = (uint)GetUpperMultiple(compressedSize + 4); // Resize array to fit new size Array.Resize(ref compressedDataWithHeader, (int)encryptedSize); // Encrypt data Xtea.Encrypt2(ref compressedDataWithHeader, indexKey); // Create buffer to hold final data + header var outputFileBuffer = new byte[compressedDataWithHeader.Length + 16]; // Copy header to buffer Array.Copy(ConstantsBase.LzoFourCc, 0, outputFileBuffer, 0, 4); Array.Copy(BitConverter.GetBytes(encryptedSize), 0, outputFileBuffer, 4, 4); Array.Copy(BitConverter.GetBytes(compressedSize), 0, outputFileBuffer, 8, 4); Array.Copy(BitConverter.GetBytes(decompressedSize), 0, outputFileBuffer, 12, 4); // Copy data to buffer Array.Copy(compressedDataWithHeader, 0, outputFileBuffer, 16, compressedDataWithHeader.Length); // Close stream epkStream.Close(); epkStream.Dispose(); // Save file File.WriteAllBytes(EterHelper.ReplaceWithEixExt(packFilePath), outputFileBuffer); return true; } }
/// <summary> /// Processes files packed as type 1 (LZO compressed) /// </summary> /// <param name="packFilePath">Path to EPK file (name included)</param> /// <param name="saveFilesPath">Path to where the file should be saved (name not included)</param> /// <param name="item">Item from deserialized list</param> /// <param name="hashMismatchFiles">Vector holding files with CRC mismatch</param> /// <param name="errorFiles">Vector holding files ignored</param> /// <returns></returns> public static int ProcessFileType1(string packFilePath, string saveFilesPath, IndexItem item, Action<ErrorItem, string> fileLoggingCallback) { int valueToReturn = 0; byte[] fileHeaderBuffer = IOHelper.ReadFileOffsetToLength(packFilePath, item.Offset, 16); // Read FourCC value byte[] fourCC = new byte[4]; Array.Copy(fileHeaderBuffer, 0, fourCC, 0, 4); // Get sizes from header int encryptedSize = IOHelper.ReadIntFromArray(fileHeaderBuffer, 4, 4); int compressedSize = IOHelper.ReadIntFromArray(fileHeaderBuffer, 8, 4); int decompressedSize = IOHelper.ReadIntFromArray(fileHeaderBuffer, 12, 4); // Compare FourCC header if (!fourCC.SequenceEqual(ConstantsBase.LzoFourCc)) { fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Invalid FourCC: {0}. Expected: {1} ({2})", BitConverter.ToString(fourCC), BitConverter.ToString(ConstantsBase.LzoFourCc), Encoding.ASCII.GetString(ConstantsBase.LzoFourCc))), ""); return 2; } if (encryptedSize != 0 || compressedSize <= 0 || decompressedSize <= 0) { fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Invalid header sizes (enc: {0} / cmpr: {1} / decompr: {2}", encryptedSize, compressedSize, decompressedSize)), ""); return 2; } // Sizes capped at 629145600 #region Cap Check if (encryptedSize > 629145600) { fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Max memory allocation reached, tried to allocate: {0} mb", Math.Round(encryptedSize / 1024.0 / 1024.0, 2))), null); return 2; } if (compressedSize > 629145600) { fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Max memory allocation reached, tried to allocate: {0} mb", Math.Round(compressedSize / 1024.0 / 1024.0, 2))), null); return 2; } if (decompressedSize > 629145600) { fileLoggingCallback(new ErrorItem(item.Filename, String.Format("Max memory allocation reached, tried to allocate: {0} mb", Math.Round(decompressedSize / 1024.0 / 1024.0, 2))), null); return 2; } #endregion // Take care of relative path and folders string path = DrivePointManager.RemoveDrivePoints((saveFilesPath + item.Filename).Replace("\\", "/")); string foldersPath = path.Substring(0, path.LastIndexOf("/", StringComparison.Ordinal)); // Get actual data byte[] rawFileBuffer = IOHelper.ReadFileOffsetToLength(packFilePath, item.Offset + 20, compressedSize); // Target directory exists? Directory.CreateDirectory(foldersPath); // Overwrite file TODO SAME AS TYPE 2 if (File.Exists(path)) { if (IOHelper.IsFileLocked(path)) { //WindowLog.LogOutputMessage(Log.LogId.FILE_OPENED_BY_EXTERNAL_PROCESS_LOG, args: path); TODO return 2; } File.Delete(path); } // Decompress data byte[] decompressedDataBuffer = LzoHelper.DecompressData(decompressedSize, compressedSize, rawFileBuffer); // Check hash if (!CompareCrcHashes(decompressedDataBuffer, item.CRCHash)) { // Log CRC mismatch fileLoggingCallback(null, item.Filename); valueToReturn = 1; } // Write file to disk File.WriteAllBytes(path, decompressedDataBuffer); return valueToReturn; }
/// <summary> /// Normalize index data /// </summary> /// <param name="filePath">File path</param> /// <param name="indexKey">Index XTEA key</param> /// <returns></returns> public static byte[] NormalizeIndexFile(string filePath, byte[] indexKey) { //File exists? if (!File.Exists(filePath)) { //WindowLog.LogOutputMessage(Log.LogId.FILE_NOT_FOUND, args: filePath); TODO throw new FileNotFoundException(filePath); } //Read file byte[] rawFileBuffer = IOHelper.ReadFile(filePath); if (rawFileBuffer.Length < 16) return null; //Read header value var headerCCBuffer = new byte[4]; Array.Copy(rawFileBuffer, 0, headerCCBuffer, 0, 4); int encryptedSize = IOHelper.ReadIntFromArray(rawFileBuffer, 4, 4); int compressedSize = IOHelper.ReadIntFromArray(rawFileBuffer, 8, 4); int decompressedSize = IOHelper.ReadIntFromArray(rawFileBuffer, 12, 4); //Preparing buffer to decrypt var dataPtr = new byte[rawFileBuffer.Length - 16]; Array.Copy(rawFileBuffer, 16, dataPtr, 0, rawFileBuffer.Length - 16); var decryptedBuffer = new byte[encryptedSize]; //Decryption if (Xtea.Decrypt(indexKey, dataPtr, decryptedBuffer, (uint)encryptedSize)) { if (decryptedBuffer.Length < 8) return null; //Decryption failed? Array.Copy(decryptedBuffer, 0, headerCCBuffer, 0, 4); if (!headerCCBuffer.SequenceEqual(ConstantsBase.LzoFourCc)) { throw new ErrorReadingIndexException("Wrong index decryption key"); } //Create buffer without header byte[] afterDecryptionBuffer = new byte[decryptedBuffer.Length - 4]; Array.Copy(decryptedBuffer, 4, afterDecryptionBuffer, 0, decryptedBuffer.Length - 4); //Decompress data byte[] normalizedFileBuffer = LzoHelper.DecompressData(decompressedSize, compressedSize, afterDecryptionBuffer); Array.Copy(normalizedFileBuffer, 0, headerCCBuffer, 0, 4); //Decompression failed? if (!headerCCBuffer.SequenceEqual(ConstantsBase.EterFourCc)) { throw new ErrorReadingIndexException("An internal error occured when decompressing"); } //Return normalized data return normalizedFileBuffer; } //Should NEVER happen return null; }