private ME2Package(string path) { DebugOutput.PrintLn("Load file : " + path); FileName = Path.GetFullPath(path); MemoryStream tempStream = new MemoryStream(); if (!File.Exists(FileName)) { throw new FileNotFoundException("PCC file not found"); } using (FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read)) { FileInfo tempInfo = new FileInfo(FileName); tempStream.WriteFromStream(fs, tempInfo.Length); if (tempStream.Length != tempInfo.Length) { throw new FileLoadException("File not fully read in. Try again later"); } } tempStream.Seek(12, SeekOrigin.Begin); int tempNameSize = tempStream.ReadValueS32(); tempStream.Seek(64 + tempNameSize, SeekOrigin.Begin); int tempGenerations = tempStream.ReadValueS32(); tempStream.Seek(36 + tempGenerations * 12, SeekOrigin.Current); int tempPos = (int)tempStream.Position; tempStream.Seek(0, SeekOrigin.Begin); header = tempStream.ReadBytes(tempPos); tempStream.Seek(0, SeekOrigin.Begin); if (magic != ZBlock.magic && magic.Swap() != ZBlock.magic) { DebugOutput.PrintLn("Magic number incorrect: " + magic); throw new FormatException("This is not a pcc file. The magic number is incorrect."); } MemoryStream listsStream; if (IsCompressed) { DebugOutput.PrintLn("File is compressed"); { listsStream = CompressionHelper.DecompressME1orME2(tempStream); //Correct the header IsCompressed = false; listsStream.Seek(0, SeekOrigin.Begin); listsStream.WriteBytes(header); //Set numblocks to zero listsStream.WriteValueS32(0); //Write the magic number listsStream.WriteValueS32(1026281201); //Write 8 bytes of 0 listsStream.WriteValueS32(0); listsStream.WriteValueS32(0); } } else { DebugOutput.PrintLn("File already decompressed. Reading decompressed data."); listsStream = tempStream; } names = new List <string>(); listsStream.Seek(NameOffset, SeekOrigin.Begin); for (int i = 0; i < NameCount; i++) { int len = listsStream.ReadValueS32(); string s = listsStream.ReadString((uint)(len - 1)); //skipping irrelevant data listsStream.Seek(5, SeekOrigin.Current); names.Add(s); } imports = new List <ImportEntry>(); listsStream.Seek(ImportOffset, SeekOrigin.Begin); for (int i = 0; i < ImportCount; i++) { ImportEntry import = new ImportEntry(this, listsStream); import.Index = i; import.PropertyChanged += importChanged; imports.Add(import); } exports = new List <IExportEntry>(); listsStream.Seek(ExportOffset, SeekOrigin.Begin); for (int i = 0; i < ExportCount; i++) { ME2ExportEntry exp = new ME2ExportEntry(this, listsStream); exp.Index = i; exp.PropertyChanged += exportChanged; exports.Add(exp); } }
private ME1Package(string path) { DebugOutput.PrintLn("Load file : " + path); FileName = Path.GetFullPath(path); MemoryStream tempStream = new MemoryStream(); if (!File.Exists(FileName)) { throw new FileNotFoundException("PCC file not found"); } using (FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read)) { FileInfo tempInfo = new FileInfo(FileName); tempStream.WriteFromStream(fs, tempInfo.Length); if (tempStream.Length != tempInfo.Length) { throw new FileLoadException("File not fully read in. Try again later"); } } tempStream.Seek(12, SeekOrigin.Begin); int tempNameSize = tempStream.ReadValueS32(); tempStream.Seek(64 + tempNameSize, SeekOrigin.Begin); int tempGenerations = tempStream.ReadValueS32(); tempStream.Seek(36 + tempGenerations * 12, SeekOrigin.Current); int tempPos = (int)tempStream.Position + 4; tempStream.Seek(0, SeekOrigin.Begin); header = tempStream.ReadBytes(tempPos); tempStream.Seek(0, SeekOrigin.Begin); if (magic != ZBlock.magic && magic.Swap() != ZBlock.magic) { DebugOutput.PrintLn("Magic number incorrect: " + magic); throw new FormatException("This is not an ME1 Package file. The magic number is incorrect."); } MemoryStream listsStream; if (IsCompressed) { DebugOutput.PrintLn("File is compressed"); listsStream = CompressionHelper.DecompressME1orME2(tempStream); //Correct the header IsCompressed = false; listsStream.Seek(0, SeekOrigin.Begin); listsStream.WriteBytes(header); // Set numblocks to zero listsStream.WriteValueS32(0); //Write the magic number listsStream.WriteBytes(new byte[] { 0xF2, 0x56, 0x1B, 0x4E }); // Write 4 bytes of 0 listsStream.WriteValueS32(0); } else { DebugOutput.PrintLn("File already decompressed. Reading decompressed data."); //listsStream = tempStream; listsStream = new MemoryStream(); tempStream.WriteTo(listsStream); } tempStream.Dispose(); ReadNames(listsStream); ReadImports(listsStream); ReadExports(listsStream); }
/// <summary> /// This method is an alternate way of saving PCCs /// Instead of reconstructing the PCC from the data taken, it instead copies across the existing /// data, appends new exports, updates the export list, changes the namelist location and updates the /// value in the header /// </summary> /// <param name="newFileName">The filename to write to</param> /// public void appendSave(string newFileName) { IEnumerable <IExportEntry> replaceExports; IEnumerable <IExportEntry> appendExports; int lastDataOffset; int max; if (IsAppend) { replaceExports = exports.Where(export => export.DataChanged && export.DataOffset < NameOffset && export.DataSize <= export.OriginalDataSize); appendExports = exports.Where(export => export.DataOffset > NameOffset || (export.DataChanged && export.DataSize > export.OriginalDataSize)); max = exports.Where(exp => exp.DataOffset < NameOffset).Max(e => e.DataOffset); } else { IEnumerable <IExportEntry> changedExports; changedExports = exports.Where(export => export.DataChanged); replaceExports = changedExports.Where(export => export.DataSize <= export.OriginalDataSize); appendExports = changedExports.Except(replaceExports); max = exports.Max(maxExport => maxExport.DataOffset); } IExportEntry lastExport = exports.Find(export => export.DataOffset == max); lastDataOffset = lastExport.DataOffset + lastExport.DataSize; byte[] oldPCC = new byte[lastDataOffset];//Check whether compressed if (IsCompressed) { oldPCC = CompressionHelper.Decompress(FileName).Take(lastDataOffset).ToArray(); IsCompressed = false; } else { using (FileStream oldPccStream = new FileStream(this.FileName, FileMode.Open)) { //Read the original data up to the last export oldPccStream.Read(oldPCC, 0, lastDataOffset); } } //Start writing the new file using (FileStream newPCCStream = new FileStream(newFileName, FileMode.Create)) { newPCCStream.Seek(0, SeekOrigin.Begin); //Write the original file up til the last original export (note that this leaves in all the original exports) newPCCStream.Write(oldPCC, 0, lastDataOffset); //write the in-place export updates foreach (ME2ExportEntry export in replaceExports) { newPCCStream.Seek(export.DataOffset, SeekOrigin.Begin); export.DataSize = export.Data.Length; newPCCStream.WriteBytes(export.Data); } newPCCStream.Seek(lastDataOffset, SeekOrigin.Begin); //Set the new nameoffset and namecounts NameOffset = (int)newPCCStream.Position; NameCount = names.Count; //Then write out the namelist foreach (string name in names) { newPCCStream.WriteValueS32(name.Length + 1); newPCCStream.WriteString(name); newPCCStream.WriteByte(0); newPCCStream.WriteValueS32(-14); } //Write the import list ImportOffset = (int)newPCCStream.Position; ImportCount = imports.Count; foreach (ImportEntry import in imports) { newPCCStream.WriteBytes(import.Header); } //append the new data foreach (ME2ExportEntry export in appendExports) { export.DataOffset = (int)newPCCStream.Position; export.DataSize = export.Data.Length; newPCCStream.Write(export.Data, 0, export.Data.Length); } //Write the export list ExportOffset = (int)newPCCStream.Position; ExportCount = exports.Count; foreach (ME2ExportEntry export in exports) { newPCCStream.WriteBytes(export.Header); } IsAppend = true; //write the updated header newPCCStream.Seek(0, SeekOrigin.Begin); newPCCStream.WriteBytes(header); } AfterSave(); }
/// <summary> /// save PCCObject to file by reconstruction from data /// </summary> /// <param name="path">full path + file name.</param> /// <param name="compress">true if you want a zlib compressed pcc file.</param> public void saveByReconstructing(string path, bool compress) { try { IsCompressed = false; MemoryStream m = new MemoryStream(); m.WriteBytes(header); //name table NameOffset = (int)m.Position; NameCount = names.Count; foreach (string s in names) { m.WriteStringUnicode(s); } //import table ImportOffset = (int)m.Position; ImportCount = imports.Count; foreach (ImportEntry e in imports) { m.WriteBytes(e.Header); } //export table ExportOffset = (int)m.Position; ExportCount = exports.Count; foreach (IExportEntry e in exports) { e.headerOffset = (uint)m.Position; m.WriteBytes(e.Header); } //freezone int FreeZoneSize = FreeZoneEnd - FreeZoneStart; FreeZoneStart = (int)m.Position; m.WriteBytes(new byte[FreeZoneSize]); FreeZoneEnd = expDataBegOffset = (int)m.Position; //export data foreach (IExportEntry e in exports) { e.DataOffset = (int)m.Position; e.DataSize = e.Data.Length; UpdateOffsets(e); m.WriteBytes(e.Data); //update size and offset in already-written header long pos = m.Position; m.Seek(e.headerOffset + 32, SeekOrigin.Begin); m.WriteValueS32(e.DataSize); m.WriteValueS32(e.DataOffset); m.Seek(pos, SeekOrigin.Begin); } //update header m.Seek(0, SeekOrigin.Begin); m.WriteBytes(header); if (compress) { CompressionHelper.CompressAndSave(m, path); } else { File.WriteAllBytes(path, m.ToArray()); } AfterSave(); } catch (Exception ex) { MessageBox.Show("PCC Save error:\n" + ex.Message); } }
/// <summary> /// This method is an alternate way of saving PCCs /// Instead of reconstructing the PCC from the data taken, it instead copies across the existing /// data, appends the name list and import list, appends changed and new exports, and then appends the export list. /// Changed exports with the same datasize or smaller are updaed in place. /// </summary> /// <param name="newFileName">The filename to write to</param> private void appendSave(string newFileName) { IEnumerable <IExportEntry> replaceExports; IEnumerable <IExportEntry> appendExports; int lastDataOffset; int max; if (IsAppend) { replaceExports = exports.Where(export => export.DataChanged && export.DataOffset < NameOffset && export.DataSize <= export.OriginalDataSize); appendExports = exports.Where(export => export.DataOffset > NameOffset || (export.DataChanged && export.DataSize > export.OriginalDataSize)); var exportsBeforeNameTable = exports.Where(exp => exp.DataOffset < NameOffset); if (exportsBeforeNameTable.Count() > 0) { max = exportsBeforeNameTable.Max(e => e.DataOffset); } else { //doesn't seem to be actual append... seems to be some sort of bug with mem/me3explorer mixing, or maybe just me3exp, where sequence = 0 lenght max = exports.Max(maxExport => maxExport.DataOffset); } } else { IEnumerable <IExportEntry> changedExports; changedExports = exports.Where(export => export.DataChanged); replaceExports = changedExports.Where(export => export.DataSize <= export.OriginalDataSize); appendExports = changedExports.Except(replaceExports); max = exports.Max(maxExport => maxExport.DataOffset); } IExportEntry lastExport = exports.Find(export => export.DataOffset == max); lastDataOffset = lastExport.DataOffset + lastExport.DataSize; byte[] oldPCC = new byte[lastDataOffset]; if (IsCompressed) { oldPCC = CompressionHelper.Decompress(FileName).Take(lastDataOffset).ToArray(); IsCompressed = false; } else { using (FileStream oldPccStream = new FileStream(this.FileName, FileMode.Open)) { //Read the original data up to the last export oldPccStream.Read(oldPCC, 0, lastDataOffset); } } //Start writing the new file using (FileStream newPCCStream = new FileStream(newFileName, FileMode.Create)) { newPCCStream.Seek(0, SeekOrigin.Begin); //Write the original file up til the last original export (note that this leaves in all the original exports) newPCCStream.Write(oldPCC, 0, lastDataOffset); //write the in-place export updates foreach (IExportEntry export in replaceExports) { newPCCStream.Seek(export.DataOffset, SeekOrigin.Begin); export.DataSize = export.Data.Length; newPCCStream.WriteBytes(export.Data); } newPCCStream.Seek(lastDataOffset, SeekOrigin.Begin); //Set the new nameoffset and namecounts NameOffset = (int)newPCCStream.Position; NameCount = names.Count; //Write out the namelist foreach (string name in names) { newPCCStream.WriteValueS32(-(name.Length + 1)); newPCCStream.WriteString(name + "\0", (uint)(name.Length + 1) * 2, Encoding.Unicode); } //Write the import list ImportOffset = (int)newPCCStream.Position; ImportCount = imports.Count; foreach (ImportEntry import in imports) { newPCCStream.WriteBytes(import.Header); } //Append the new data foreach (IExportEntry export in appendExports) { export.DataOffset = (int)newPCCStream.Position; export.DataSize = export.Data.Length; UpdateOffsets(export); newPCCStream.WriteBytes(export.Data); } //Write the export list ExportOffset = (int)newPCCStream.Position; ExportCount = exports.Count; foreach (ME3ExportEntry export in exports) { newPCCStream.WriteBytes(export.Header); } IsAppend = true; //write the updated header newPCCStream.Seek(0, SeekOrigin.Begin); newPCCStream.WriteBytes(header); } AfterSave(); }
/// <summary> /// UDKPackage class constructor. It also load namelist, importlist and exportinfo (not exportdata) from udk file /// </summary> /// <param name="UDKPackagePath">full path + file name of desired udk file.</param> /// <param name="create">Create a file instead of reading from disk</param> private UDKPackage(string UDKPackagePath, bool create = false) : base(Path.GetFullPath(UDKPackagePath)) { ME3ExpMemoryAnalyzer.MemoryAnalyzer.AddTrackedMemoryItem($"UDKPackage {Path.GetFileName(UDKPackagePath)}", new WeakReference(this)); if (create) { folderName = "None"; engineVersion = 12791; //reasonable defaults? Flags = EPackageFlags.AllowDownload | EPackageFlags.NoExportsData; return; } using (var fs = File.OpenRead(FilePath)) { #region Header uint magic = fs.ReadUInt32(); if (magic != packageTag) { throw new FormatException("Not an Unreal package!"); } ushort unrealVersion = fs.ReadUInt16(); ushort licenseeVersion = fs.ReadUInt16(); FullHeaderSize = fs.ReadInt32(); int foldernameStrLen = fs.ReadInt32(); //always "None", so don't bother saving result if (foldernameStrLen > 0) { folderName = fs.ReadStringASCIINull(foldernameStrLen); } else { folderName = fs.ReadStringUnicodeNull(foldernameStrLen * -2); } Flags = (EPackageFlags)fs.ReadUInt32(); //if (Flags.HasFlag(EPackageFlags.Compressed)) //{ // throw new FormatException("Cannot read compressed UDK packages!"); //} NameCount = fs.ReadInt32(); NameOffset = fs.ReadInt32(); ExportCount = fs.ReadInt32(); ExportOffset = fs.ReadInt32(); ImportCount = fs.ReadInt32(); ImportOffset = fs.ReadInt32(); DependencyTableOffset = fs.ReadInt32(); importExportGuidsOffset = fs.ReadInt32(); importGuidsCount = fs.ReadInt32(); exportGuidsCount = fs.ReadInt32(); thumbnailTableOffset = fs.ReadInt32(); PackageGuid = fs.ReadGuid(); uint generationsTableCount = fs.ReadUInt32(); if (generationsTableCount > 0) { generationsTableCount--; Gen0ExportCount = fs.ReadInt32(); Gen0NameCount = fs.ReadInt32(); Gen0NetworkedObjectCount = fs.ReadInt32(); } //don't care about other gens, so skip them fs.Skip(generationsTableCount * 12); engineVersion = fs.ReadInt32(); cookedContentVersion = fs.ReadInt32(); //skip compression type chunks. Decompressor will handle that long compressionInfoOffset = fs.Position; fs.SkipInt32(); int numChunks = fs.ReadInt32(); fs.Skip(numChunks * 16); packageSource = fs.ReadUInt32(); //additional packages to cook, and texture allocation, but we don't care about those, so we won't read them in. #endregion Stream inStream = fs; if (IsCompressed && numChunks > 0) { inStream = CompressionHelper.DecompressUDK(fs, compressionInfoOffset); } inStream.JumpTo(NameOffset); for (int i = 0; i < NameCount; i++) { names.Add(inStream.ReadUnrealString()); inStream.Skip(8); } inStream.JumpTo(ImportOffset); for (int i = 0; i < ImportCount; i++) { ImportEntry imp = new ImportEntry(this, inStream) { Index = i }; imp.PropertyChanged += importChanged; imports.Add(imp); } //read exportTable (ExportEntry constructor reads export data) inStream.JumpTo(ExportOffset); for (int i = 0; i < ExportCount; i++) { ExportEntry e = new ExportEntry(this, inStream) { Index = i }; e.PropertyChanged += exportChanged; exports.Add(e); } } }
private MEPackage(string filePath, MEGame forceGame = MEGame.Unknown) : base(Path.GetFullPath(filePath)) { ME3ExpMemoryAnalyzer.MemoryAnalyzer.AddTrackedMemoryItem($"MEPackage {Path.GetFileName(filePath)}", new WeakReference(this)); if (forceGame != MEGame.Unknown) { //new Package Game = forceGame; //reasonable defaults? Flags = EPackageFlags.Cooked | EPackageFlags.AllowDownload | EPackageFlags.DisallowLazyLoading | EPackageFlags.RequireImportsAlreadyLoaded; return; } using (var fs = File.OpenRead(filePath)) { #region Header uint magic = fs.ReadUInt32(); if (magic != packageTag) { throw new FormatException("Not an Unreal package!"); } ushort unrealVersion = fs.ReadUInt16(); ushort licenseeVersion = fs.ReadUInt16(); switch (unrealVersion) { case ME1UnrealVersion when licenseeVersion == ME1LicenseeVersion: Game = MEGame.ME1; break; case ME2UnrealVersion when licenseeVersion == ME2LicenseeVersion: Game = MEGame.ME2; break; case ME3UnrealVersion when licenseeVersion == ME3LicenseeVersion: Game = MEGame.ME3; break; default: throw new FormatException("Not a Mass Effect Package!"); } FullHeaderSize = fs.ReadInt32(); int foldernameStrLen = fs.ReadInt32(); //always "None", so don't bother saving result if (foldernameStrLen > 0) { fs.ReadStringASCIINull(foldernameStrLen); } else { fs.ReadStringUnicodeNull(foldernameStrLen * -2); } Flags = (EPackageFlags)fs.ReadUInt32(); if (Game == MEGame.ME3 && Flags.HasFlag(EPackageFlags.Cooked)) { fs.SkipInt32(); //always 0 } NameCount = fs.ReadInt32(); NameOffset = fs.ReadInt32(); ExportCount = fs.ReadInt32(); ExportOffset = fs.ReadInt32(); ImportCount = fs.ReadInt32(); ImportOffset = fs.ReadInt32(); DependencyTableOffset = fs.ReadInt32(); if (Game == MEGame.ME3) { ImportExportGuidsOffset = fs.ReadInt32(); fs.SkipInt32(); //ImportGuidsCount always 0 fs.SkipInt32(); //ExportGuidsCount always 0 fs.SkipInt32(); //ThumbnailTableOffset always 0 } PackageGuid = fs.ReadGuid(); uint generationsTableCount = fs.ReadUInt32(); if (generationsTableCount > 0) { generationsTableCount--; Gen0ExportCount = fs.ReadInt32(); Gen0NameCount = fs.ReadInt32(); Gen0NetworkedObjectCount = fs.ReadInt32(); } //should never be more than 1 generation, but just in case fs.Skip(generationsTableCount * 12); fs.SkipInt32(); //engineVersion Like unrealVersion and licenseeVersion, these 2 are determined by what game this is, fs.SkipInt32(); //cookedContentVersion so we don't have to read them in if (Game == MEGame.ME2 || Game == MEGame.ME1) { fs.SkipInt32(); //always 0 fs.SkipInt32(); //always 47699 unknown4 = fs.ReadInt32(); fs.SkipInt32(); //always 1 in ME1, always 1966080 in ME2 } unknown6 = fs.ReadInt32(); fs.SkipInt32(); //always -1 in ME1 and ME2, always 145358848 in ME3 if (Game == MEGame.ME1) { fs.SkipInt32(); //always -1 } //skip compression type chunks. Decompressor will handle that fs.SkipInt32(); int numChunks = fs.ReadInt32(); fs.Skip(numChunks * 16); packageSource = fs.ReadUInt32(); if (Game == MEGame.ME2 || Game == MEGame.ME1) { fs.SkipInt32(); //always 0 } //Doesn't need to be written out, so it doesn't need to be read in //keep this here in case one day we learn that this has a purpose /*if (Game == MEGame.ME2 || Game == MEGame.ME3) * { * int additionalPackagesToCookCount = fs.ReadInt32(); * var additionalPackagesToCook = new string[additionalPackagesToCookCount]; * for (int i = 0; i < additionalPackagesToCookCount; i++) * { * int strLen = fs.ReadInt32(); * if (strLen > 0) * { * additionalPackagesToCook[i] = fs.ReadStringASCIINull(strLen); * } * else * { * additionalPackagesToCook[i] = fs.ReadStringUnicodeNull(strLen * -2); * } * } * }*/ #endregion Stream inStream = fs; if (IsCompressed && numChunks > 0) { inStream = Game == MEGame.ME3 ? CompressionHelper.DecompressME3(fs) : CompressionHelper.DecompressME1orME2(fs); } //read namelist inStream.JumpTo(NameOffset); for (int i = 0; i < NameCount; i++) { names.Add(inStream.ReadUnrealString()); if (Game == MEGame.ME1) { inStream.Skip(8); } else if (Game == MEGame.ME2) { inStream.Skip(4); } } //read importTable inStream.JumpTo(ImportOffset); for (int i = 0; i < ImportCount; i++) { ImportEntry imp = new ImportEntry(this, inStream) { Index = i }; imp.PropertyChanged += importChanged; imports.Add(imp); } //read exportTable (ExportEntry constructor reads export data) inStream.JumpTo(ExportOffset); for (int i = 0; i < ExportCount; i++) { ExportEntry e = new ExportEntry(this, inStream) { Index = i }; e.PropertyChanged += exportChanged; exports.Add(e); } if (Game == MEGame.ME1) { ReadLocalTLKs(); } } }
/// <summary> /// This method is an alternate way of saving PCCs /// Instead of reconstructing the PCC from the data taken, it instead copies across the existing /// data, appends the name list and import list, appends changed and new exports, and then appends the export list. /// Changed exports with the same datasize or smaller are updaed in place. /// </summary> /// <param name="newFileName">The filename to write to</param> private void appendSave(string newFileName) { IEnumerable <ME3ExportEntry> replaceExports; IEnumerable <ME3ExportEntry> appendExports; int lastDataOffset; int max; if (IsAppend) { replaceExports = exports.Where(export => export.DataChanged && export.DataOffset < NameOffset && export.DataSize <= export.OriginalDataSize); appendExports = exports.Where(export => export.DataOffset > NameOffset || (export.DataChanged && export.DataSize > export.OriginalDataSize)); max = exports.Where(exp => exp.DataOffset < NameOffset).Max(e => e.DataOffset); } else { IEnumerable <ME3ExportEntry> changedExports; changedExports = exports.Where(export => export.DataChanged); replaceExports = changedExports.Where(export => export.DataSize <= export.OriginalDataSize); appendExports = changedExports.Except(replaceExports); max = exports.Max(maxExport => maxExport.DataOffset); } ME3ExportEntry lastExport = exports.Find(export => export.DataOffset == max); lastDataOffset = lastExport.DataOffset + lastExport.DataSize; byte[] oldPCC = new byte[lastDataOffset]; if (IsCompressed) { oldPCC = CompressionHelper.Decompress(FileName).Take(lastDataOffset).ToArray(); IsCompressed = false; } else { using (FileStream oldPccStream = new FileStream(this.FileName, FileMode.Open)) { //Read the original data up to the last export oldPccStream.Read(oldPCC, 0, lastDataOffset); } } //Start writing the new file using (FileStream newPCCStream = new FileStream(newFileName, FileMode.Create)) { newPCCStream.Seek(0, SeekOrigin.Begin); //Write the original file up til the last original export (note that this leaves in all the original exports) newPCCStream.Write(oldPCC, 0, lastDataOffset); //write the in-place export updates foreach (ME3ExportEntry export in replaceExports) { newPCCStream.Seek(export.DataOffset, SeekOrigin.Begin); export.DataSize = export.Data.Length; newPCCStream.WriteBytes(export.Data); } newPCCStream.Seek(lastDataOffset, SeekOrigin.Begin); //Set the new nameoffset and namecounts NameOffset = (int)newPCCStream.Position; NameCount = names.Count; //Write out the namelist foreach (string name in names) { newPCCStream.WriteValueS32(-(name.Length + 1)); newPCCStream.WriteString(name + "\0", (uint)(name.Length + 1) * 2, Encoding.Unicode); } //Write the import list ImportOffset = (int)newPCCStream.Position; ImportCount = imports.Count; foreach (ImportEntry import in imports) { newPCCStream.WriteBytes(import.header); } //Append the new data foreach (ME3ExportEntry export in appendExports) { export.DataOffset = (int)newPCCStream.Position; export.DataSize = export.Data.Length; //update offsets for pcc-stored audio in wwisestreams if (export.ClassName == "WwiseStream" && export.GetProperty <NameProperty>("Filename") == null) { byte[] binData = export.getBinaryData(); binData.OverwriteRange(12, BitConverter.GetBytes(export.DataOffset + export.propsEnd() + 16)); export.setBinaryData(binData); } newPCCStream.WriteBytes(export.Data); } //Write the export list ExportOffset = (int)newPCCStream.Position; ExportCount = exports.Count; foreach (ME3ExportEntry export in exports) { newPCCStream.WriteBytes(export.header); } IsAppend = true; //write the updated header newPCCStream.Seek(0, SeekOrigin.Begin); newPCCStream.WriteBytes(header); } AfterSave(); }
/// <summary> /// save PCCObject to file by reconstruction from data /// </summary> /// <param name="path">full path + file name.</param> /// <param name="compress">true if you want a zlib compressed pcc file.</param> public void saveByReconstructing(string path, bool compress) { try { this.IsCompressed = false; MemoryStream m = new MemoryStream(); m.WriteBytes(header); //name table NameOffset = (int)m.Position; NameCount = names.Count; foreach (string s in names) { string text = s; if (!text.EndsWith("\0")) { text += "\0"; } m.WriteValueS32(-text.Length); foreach (char c in text) { m.WriteByte((byte)c); m.WriteByte(0); } } //import table ImportOffset = (int)m.Position; ImportCount = imports.Count; foreach (ImportEntry e in imports) { m.WriteBytes(e.header); } //export table ExportOffset = (int)m.Position; ExportCount = exports.Count; for (int i = 0; i < exports.Count; i++) { ME3ExportEntry e = exports[i]; e.headerOffset = (uint)m.Position; m.WriteBytes(e.header); } //freezone int FreeZoneSize = FreeZoneEnd - FreeZoneStart; FreeZoneStart = (int)m.Position; m.Write(new byte[FreeZoneSize], 0, FreeZoneSize); FreeZoneEnd = expDataBegOffset = (int)m.Position; //export data for (int i = 0; i < exports.Count; i++) { ME3ExportEntry e = exports[i]; e.DataOffset = (int)m.Position; e.DataSize = e.Data.Length; //update offsets for pcc-stored audio in wwisestreams if (e.ClassName == "WwiseStream" && e.GetProperty <NameProperty>("Filename") == null) { byte[] binData = e.getBinaryData(); binData.OverwriteRange(12, BitConverter.GetBytes(e.DataOffset + e.propsEnd() + 16)); e.setBinaryData(binData); } m.WriteBytes(e.Data); long pos = m.Position; m.Seek(e.headerOffset + 32, SeekOrigin.Begin); m.WriteValueS32(e.DataSize); m.WriteValueS32(e.DataOffset); m.Seek(pos, SeekOrigin.Begin); } //update header m.Seek(0, SeekOrigin.Begin); m.WriteBytes(header); if (compress) { CompressionHelper.CompressAndSave(m, path); } else { File.WriteAllBytes(path, m.ToArray()); } AfterSave(); } catch (Exception ex) { MessageBox.Show("PCC Save error:\n" + ex.Message); } }
/// <summary> /// PCCObject class constructor. It also loads namelist, importlist, exportinfo, and exportdata from pcc file /// </summary> /// <param name="pccFilePath">full path + file name of desired pcc file.</param> private ME3Package(string pccFilePath) { FileName = Path.GetFullPath(pccFilePath); MemoryStream listsStream; names = new List <string>(); imports = new List <ImportEntry>(); exports = new List <ME3ExportEntry>(); using (FileStream pccStream = File.OpenRead(FileName)) { header = pccStream.ReadBytes(headerSize); if (magic != ZBlock.magic && magic.Swap() != ZBlock.magic) { throw new FormatException("not a pcc file"); } if (lowVers != 684 && highVers != 194) { throw new FormatException("unsupported version"); } if (IsCompressed) { listsStream = CompressionHelper.DecompressME3(pccStream); //correcting the header listsStream.Seek(0, SeekOrigin.Begin); listsStream.Read(header, 0, header.Length); } else { listsStream = new MemoryStream(); pccStream.Seek(0, SeekOrigin.Begin); pccStream.CopyTo(listsStream); } } // fill names list listsStream.Seek(NameOffset, SeekOrigin.Begin); for (int i = 0; i < NameCount; i++) { long currOffset = listsStream.Position; int strLength = listsStream.ReadValueS32(); string str = listsStream.ReadString(strLength * -2, true, Encoding.Unicode); names.Add(str); } // fill import list listsStream.Seek(ImportOffset, SeekOrigin.Begin); for (int i = 0; i < ImportCount; i++) { long offset = listsStream.Position; ImportEntry imp = new ImportEntry(this, listsStream); imp.Index = i; imp.PropertyChanged += importChanged; imports.Add(imp); } // fill export list listsStream.Seek(ExportOffset, SeekOrigin.Begin); byte[] buffer; for (int i = 0; i < ExportCount; i++) { uint expInfoOffset = (uint)listsStream.Position; listsStream.Seek(44, SeekOrigin.Current); int count = listsStream.ReadValueS32(); listsStream.Seek(-48, SeekOrigin.Current); int expInfoSize = 68 + (count * 4); buffer = new byte[expInfoSize]; listsStream.Read(buffer, 0, buffer.Length); ME3ExportEntry e = new ME3ExportEntry(this, buffer, expInfoOffset); long headerEnd = listsStream.Position; buffer = new byte[e.DataSize]; listsStream.Seek(e.DataOffset, SeekOrigin.Begin); listsStream.Read(buffer, 0, buffer.Length); e.Data = buffer; e.DataChanged = false; e.Index = i; e.PropertyChanged += exportChanged; exports.Add(e); listsStream.Seek(headerEnd, SeekOrigin.Begin); } }