public void Deserialize(IMEPackage pcc) { PropertyCollection properties = export.GetProperties(); int off; switch (pcc.Game) { case MEGame.ME3: off = export.propsEnd() + 8; break; case MEGame.ME2: off = export.propsEnd() + 0x28; break; default: throw new Exception("Can oly read WwiseStreams for ME3 and ME2!"); } ValueOffset = off; DataSize = BitConverter.ToInt32(memory, off); DataOffset = BitConverter.ToInt32(memory, off + 4); NameProperty nameProp = properties.GetProp <NameProperty>("Filename"); FileName = nameProp?.Value; Id = properties.GetProp <IntProperty>("Id"); }
public static StorageTypes GetTopMipStorageType(ExportEntry exportEntry) { MemoryStream ms = new MemoryStream(exportEntry.Data); ms.Seek(exportEntry.propsEnd(), SeekOrigin.Begin); if (exportEntry.FileRef.Game != Mod.MEGame.ME3) { ms.Skip(4); //BulkDataFlags ms.Skip(4); //ElementCount int bulkDataSize = ms.ReadInt32(); ms.Seek(4, SeekOrigin.Current); // position in the package ms.Skip(bulkDataSize); //skips over thumbnail png, if it exists } var mips = new List <Texture2DMipInfo>(); int numMipMaps = ms.ReadInt32(); for (int l = 0; l < numMipMaps; l++) { var storageType = (StorageTypes)ms.ReadInt32(); if (storageType != StorageTypes.empty) { return(storageType); } var uncompressedSize = ms.ReadInt32(); var compressedSize = ms.ReadInt32(); var externalOffset = ms.ReadInt32(); var localExportOffset = (int)ms.Position; } return(StorageTypes.empty); }
public ME3FaceFXAnimSet(IMEPackage Pcc, ExportEntry Entry) { pcc = Pcc; export = Entry; int start = export.propsEnd() + 4; SerializingContainer Container = new SerializingContainer(new MemoryStream(export.Data.Skip(start).ToArray())); Container.isLoading = true; Serialize(Container); }
public void Save() { MemoryStream m = new MemoryStream(); SerializingContainer Container = new SerializingContainer(m); Container.isLoading = false; Serialize(Container); m = Container.Memory; MemoryStream res = new MemoryStream(); int start = export.propsEnd(); res.Write(export.Data, 0, start); res.WriteInt32((int)m.Length); res.WriteStream(m); res.WriteInt32(0); export.Data = res.ToArray(); }
public static ObjectBinary ConvertPostPropBinary(ExportEntry export, MEGame newGame, PropertyCollection newProps) { if (export.propsEnd() == export.DataSize) { return(Array.Empty <byte>()); } if (export.IsTexture()) { return(ConvertTexture2D(export, newGame)); } if (From(export) is ObjectBinary objbin) { if (objbin is AnimSequence animSeq) { animSeq.UpdateProps(newProps, newGame); } return(objbin); } switch (export.ClassName) { case "DirectionalLightComponent": case "PointLightComponent": case "SkyLightComponent": case "SphericalHarmonicLightComponent": case "SpotLightComponent": case "DominantSpotLightComponent": case "DominantPointLightComponent": case "DominantDirectionalLightComponent": if (newGame == MEGame.UDK) { return(Array.Empty <byte>()); } else if (export.Game == MEGame.UDK && newGame != MEGame.UDK) { return(new byte[8]); } break; } //no conversion neccesary return(export.GetBinaryData()); }
public static List <string> Relink(ExportEntry sourceExport, ExportEntry relinkingExport, OrderedMultiValueDictionary <IEntry, IEntry> crossPCCObjectMappingList, bool importExportDependencies = false) { var relinkFailedReport = new List <string>(); IMEPackage sourcePcc = sourceExport.FileRef; //Relink stack if (relinkingExport.HasStack) { byte[] stack = relinkingExport.GetStack(); int uIndex = BitConverter.ToInt32(stack, 0); string relinkResult = relinkUIndex(sourceExport.FileRef, relinkingExport, ref uIndex, "Stack: Node", crossPCCObjectMappingList, "", importExportDependencies); if (relinkResult is null) { stack.OverwriteRange(0, BitConverter.GetBytes(uIndex)); } else { relinkFailedReport.Add(relinkResult); } uIndex = BitConverter.ToInt32(stack, 4); relinkResult = relinkUIndex(sourceExport.FileRef, relinkingExport, ref uIndex, "Stack: StateNode", crossPCCObjectMappingList, "", importExportDependencies); if (relinkResult is null) { stack.OverwriteRange(4, BitConverter.GetBytes(uIndex)); } else { relinkFailedReport.Add(relinkResult); } relinkingExport.SetStack(stack); } //Relink Properties PropertyCollection transplantProps = sourceExport.GetProperties(); relinkFailedReport.AddRange(relinkPropertiesRecursive(sourcePcc, relinkingExport, transplantProps, crossPCCObjectMappingList, "", importExportDependencies)); relinkingExport.WriteProperties(transplantProps); //Relink Binary try { if (relinkingExport.Game != sourcePcc.Game && (relinkingExport.IsClass || relinkingExport.ClassName == "State" || relinkingExport.ClassName == "Function")) { relinkFailedReport.Add($"{relinkingExport.UIndex} {relinkingExport.FullPath} binary relinking failed. Cannot port {relinkingExport.ClassName} between games!"); return(relinkFailedReport); } if (ObjectBinary.From(relinkingExport) is ObjectBinary objBin) { List <(UIndex, string)> indices = objBin.GetUIndexes(relinkingExport.FileRef.Game); foreach ((UIndex uIndex, string propName) in indices) { string result = relinkUIndex(sourcePcc, relinkingExport, ref uIndex.value, $"(Binary Property: {propName})", crossPCCObjectMappingList, "", importExportDependencies); if (result != null) { relinkFailedReport.Add(result); } } //UStruct is abstract baseclass for Class, State, and Function, and can have script in it if (objBin is UStruct uStructBinary && uStructBinary.ScriptBytes.Length > 0) { if (relinkingExport.Game == MEGame.ME3) { (List <Token> tokens, _) = Bytecode.ParseBytecode(uStructBinary.ScriptBytes, sourceExport); foreach (Token token in tokens) { relinkFailedReport.AddRange(RelinkToken(token, uStructBinary.ScriptBytes, sourceExport, relinkingExport, crossPCCObjectMappingList, importExportDependencies)); } } else { relinkFailedReport.Add($"{relinkingExport.UIndex} {relinkingExport.FullPath} binary relinking failed. {relinkingExport.ClassName} contains script, " + $"which cannot be relinked for {relinkingExport.Game}"); } } relinkingExport.setBinaryData(objBin.ToBytes(relinkingExport.FileRef, relinkingExport.DataOffset + relinkingExport.propsEnd())); return(relinkFailedReport); } byte[] binarydata = relinkingExport.getBinaryData(); if (binarydata.Length > 0) { switch (relinkingExport.ClassName) { //todo: make a WwiseEvent ObjectBinary class case "WwiseEvent": { void relinkAtPosition(int binaryPosition, string propertyName) { int uIndex = BitConverter.ToInt32(binarydata, binaryPosition); string relinkResult = relinkUIndex(sourcePcc, relinkingExport, ref uIndex, propertyName, crossPCCObjectMappingList, "", importExportDependencies); if (relinkResult is null) { binarydata.OverwriteRange(binaryPosition, BitConverter.GetBytes(uIndex)); } else { relinkFailedReport.Add(relinkResult); } } if (relinkingExport.FileRef.Game == MEGame.ME3) { int count = BitConverter.ToInt32(binarydata, 0); for (int j = 0; j < count; j++) { relinkAtPosition(4 + (j * 4), $"(Binary Property: WwiseStreams[{j}])"); } relinkingExport.setBinaryData(binarydata); } else if (relinkingExport.FileRef.Game == MEGame.ME2) { int parsingPos = 4; int linkCount = BitConverter.ToInt32(binarydata, parsingPos); parsingPos += 4; for (int j = 0; j < linkCount; j++) { int bankcount = BitConverter.ToInt32(binarydata, parsingPos); parsingPos += 4; for (int k = 0; k < bankcount; k++) { relinkAtPosition(parsingPos, $"(Binary Property: link[{j}].WwiseBanks[{k}])"); parsingPos += 4; } int wwisestreamcount = BitConverter.ToInt32(binarydata, parsingPos); parsingPos += 4; for (int k = 0; k < wwisestreamcount; k++) { relinkAtPosition(parsingPos, $"(Binary Property: link[{j}].WwiseStreams[{k}])"); parsingPos += 4; } } relinkingExport.setBinaryData(binarydata); } } break; case "DominantDirectionalLightComponent": case "SphericalHarmonicLightComponent": case "DominantPointLightComponent": case "StaticLightCollectionActor": case "DominantSpotLightComponent": case "DirectionalLightComponent": case "StaticMeshCollectionActor": case "TerrainWeightMapTexture": case "PhysicsAssetInstance": case "PointLightComponent": case "ShadowMapTexture2D": case "SpotLightComponent": case "LightMapTexture2D": case "SkyLightComponent": case "TextureFlipBook": case "BrushComponent": case "FaceFXAnimSet": case "TextureMovie": case "AnimSequence": case "RB_BodySetup": case "MorphTarget": case "ShadowMap1D": case "WwiseStream": case "WwiseBank": case "Texture2D": //these classes have binary but do not need relinking break; default: if (binarydata.Any(b => b != 0)) { relinkFailedReport.Add($"{relinkingExport.UIndex} {relinkingExport.FullPath} has unparsed binary. " + $"This binary may contain items that need to be relinked. Come to the Discord server " + $"(click ME3Tweaks logo in main window for invite) and ask devs to parse this class."); } break; } } } catch (Exception e) when(!App.IsDebug) { relinkFailedReport.Add($"{relinkingExport.UIndex} {relinkingExport.FullPath} binary relinking failed due to exception: {e.Message}"); } return(relinkFailedReport); }
public static List <Texture2DMipInfo> GetTexture2DMipInfos(ExportEntry exportEntry, string cacheName) { MemoryStream ms = new MemoryStream(exportEntry.Data); ms.Seek(exportEntry.propsEnd(), SeekOrigin.Begin); if (exportEntry.FileRef.Game != Mod.MEGame.ME3) { ms.Skip(4); //BulkDataFlags ms.Skip(4); //ElementCount int bulkDataSize = ms.ReadInt32(); ms.Seek(4, SeekOrigin.Current); // position in the package ms.Skip(bulkDataSize); //skips over thumbnail png, if it exists } var mips = new List <Texture2DMipInfo>(); int numMipMaps = ms.ReadInt32(); for (int l = 0; l < numMipMaps; l++) { Texture2DMipInfo mip = new Texture2DMipInfo { Export = exportEntry, index = l, storageType = (StorageTypes)ms.ReadInt32(), uncompressedSize = ms.ReadInt32(), compressedSize = ms.ReadInt32(), externalOffset = ms.ReadInt32(), localExportOffset = (int)ms.Position, TextureCacheName = cacheName //If this is ME1, this will simply be ignored in the setter }; switch (mip.storageType) { case StorageTypes.pccUnc: ms.Seek(mip.uncompressedSize, SeekOrigin.Current); break; case StorageTypes.pccLZO: case StorageTypes.pccZlib: ms.Seek(mip.compressedSize, SeekOrigin.Current); break; } mip.width = ms.ReadInt32(); mip.height = ms.ReadInt32(); if (mip.width == 4 && mips.Exists(m => m.width == mip.width)) { mip.width = mips.Last().width / 2; } if (mip.height == 4 && mips.Exists(m => m.height == mip.height)) { mip.height = mips.Last().height / 2; } if (mip.width == 0) { mip.width = 1; } if (mip.height == 0) { mip.height = 1; } mips.Add(mip); } return(mips); }
public static bool RandomizeExport(ExportEntry export, RandomizationOption option) { if (!CanRandomize(export)) { return(false); } var game = export.FileRef.Game; byte[] data = export.Data; try { var TrackOffsets = export.GetProperty <ArrayProperty <IntProperty> >("CompressedTrackOffsets"); var animsetData = export.GetProperty <ObjectProperty>("m_pBioAnimSetData"); if (animsetData.Value <= 0) { //Debug.WriteLine("trackdata is an import skipping"); return(false); } // don't randomize; var boneList = export.FileRef.GetUExport(animsetData.Value).GetProperty <ArrayProperty <NameProperty> >("TrackBoneNames"); Enum.TryParse(export.GetProperty <EnumProperty>("RotationCompressionFormat").Value.Name, out AnimationCompressionFormat rotCompression); int offset = export.propsEnd(); //ME2 SPECIFIC offset += 16; //3 0's, 1 offset of data point int binLength = BitConverter.ToInt32(data, offset); //var LengthNode = new BinInterpNode //{ // Header = $"0x{offset:X4} AnimBinary length: {binLength}", // Name = "_" + offset, // Tag = NodeType.StructLeafInt //}; //offset += 4; //subnodes.Add(LengthNode); var animBinStart = offset; int bone = 0; for (int i = 0; i < TrackOffsets.Count; i++) { var bonePosOffset = TrackOffsets[i].Value; i++; var bonePosCount = TrackOffsets[i].Value; var boneName = boneList[bone].Value; bool doSomething = shouldRandomizeBone(boneName, option); //POSKEYS for (int j = 0; j < bonePosCount; j++) { offset = animBinStart + bonePosOffset + j * 12; //Key # //var PosKeys = new BinInterpNode //{ // Header = $"0x{offset:X5} PosKey {j}", // Name = "_" + offset, // Tag = NodeType.Unknown //}; //BoneID.Items.Add(PosKeys); var posX = BitConverter.ToSingle(data, offset); if (doSomething) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(posX - (posX * .3f), posX + (posX * .3f)))); } //PosKeys.Items.Add(new BinInterpNode //{ // Header = $"0x{offset:X5} X: {posX} ", // Name = "_" + offset, // Tag = NodeType.StructLeafFloat //}); offset += 4; var posY = BitConverter.ToSingle(data, offset); if (doSomething) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(posY - (posY * .3f), posY + (posY * .3f)))); } //PosKeys.Items.Add(new BinInterpNode //{ // Header = $"0x{offset:X5} Y: {posY} ", // Name = "_" + offset, // Tag = NodeType.StructLeafFloat //}); offset += 4; var posZ = BitConverter.ToSingle(data, offset); if (doSomething) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(posZ - (posZ * .3f), posZ + (posZ * .3f)))); } //PosKeys.Items.Add(new BinInterpNode //{ // Header = $"0x{offset:X5} Z: {posZ} ", // Name = "_" + offset, // Tag = NodeType.StructLeafFloat //}); offset += 4; } var lookat = boneName.Name.Contains("lookat"); i++; var boneRotOffset = TrackOffsets[i].Value; i++; var boneRotCount = TrackOffsets[i].Value; int l = 12; // 12 length of rotation by default var offsetRotX = boneRotOffset; var offsetRotY = boneRotOffset; var offsetRotZ = boneRotOffset; var offsetRotW = boneRotOffset; for (int j = 0; j < boneRotCount; j++) { float rotX = 0; float rotY = 0; float rotZ = 0; float rotW = 0; switch (rotCompression) { case AnimationCompressionFormat.ACF_None: l = 16; offset = animBinStart + boneRotOffset + j * l; offsetRotX = offset; rotX = BitConverter.ToSingle(data, offset); if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotX - (rotX * .1f), rotX + (rotX * .1f)))); } offset += 4; offsetRotY = offset; rotY = BitConverter.ToSingle(data, offset); if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotY - (rotY * .1f), rotY + (rotY * .1f)))); } offset += 4; offsetRotZ = offset; rotZ = BitConverter.ToSingle(data, offset); if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotZ - (rotZ * .1f), rotZ + (rotZ * .1f)))); } offset += 4; offsetRotW = offset; rotW = BitConverter.ToSingle(data, offset); if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotW - (rotW * .1f), rotW + (rotW * .1f)))); } offset += 4; break; case AnimationCompressionFormat.ACF_Float96NoW: offset = animBinStart + boneRotOffset + j * l; offsetRotX = offset; rotX = BitConverter.ToSingle(data, offset); if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotX - (rotX * .1f), rotX + (rotX * .1f)))); } offset += 4; offsetRotY = offset; rotY = BitConverter.ToSingle(data, offset); if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotY - (rotY * .1f), rotY + (rotY * .1f)))); } offset += 4; offsetRotZ = offset; rotZ = BitConverter.ToSingle(data, offset); if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotZ - (rotZ * .1f), rotZ + (rotZ * .1f)))); } offset += 4; break; case AnimationCompressionFormat.ACF_Fixed48NoW: // normalized quaternion with 3 16-bit fixed point fields //FQuat r; //r.X = (X - 32767) / 32767.0f; //r.Y = (Y - 32767) / 32767.0f; //r.Z = (Z - 32767) / 32767.0f; //RESTORE_QUAT_W(r); //break; case AnimationCompressionFormat.ACF_Fixed32NoW: // normalized quaternion with 11/11/10-bit fixed point fields //FQuat r; //r.X = X / 1023.0f - 1.0f; //r.Y = Y / 1023.0f - 1.0f; //r.Z = Z / 511.0f - 1.0f; //RESTORE_QUAT_W(r); //break; case AnimationCompressionFormat.ACF_IntervalFixed32NoW: //FQuat r; //r.X = (X / 1023.0f - 1.0f) * Ranges.X + Mins.X; //r.Y = (Y / 1023.0f - 1.0f) * Ranges.Y + Mins.Y; //r.Z = (Z / 511.0f - 1.0f) * Ranges.Z + Mins.Z; //RESTORE_QUAT_W(r); //break; case AnimationCompressionFormat.ACF_Float32NoW: //FQuat r; //int _X = data >> 21; // 11 bits //int _Y = (data >> 10) & 0x7FF; // 11 bits //int _Z = data & 0x3FF; // 10 bits //*(unsigned*)&r.X = ((((_X >> 7) & 7) + 123) << 23) | ((_X & 0x7F | 32 * (_X & 0xFFFFFC00)) << 16); //*(unsigned*)&r.Y = ((((_Y >> 7) & 7) + 123) << 23) | ((_Y & 0x7F | 32 * (_Y & 0xFFFFFC00)) << 16); //*(unsigned*)&r.Z = ((((_Z >> 6) & 7) + 123) << 23) | ((_Z & 0x3F | 32 * (_Z & 0xFFFFFE00)) << 17); //RESTORE_QUAT_W(r); break; case AnimationCompressionFormat.ACF_BioFixed48: offset = animBinStart + boneRotOffset + j * l; const float shift = 0.70710678118f; const float scale = 1.41421356237f; offsetRotX = offset; rotX = (data[0] & 0x7FFF) / 32767.0f * scale - shift; if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotX - (rotX * .1f), rotX + (rotX * .1f)))); } offset += 4; offsetRotY = offset; rotY = (data[1] & 0x7FFF) / 32767.0f * scale - shift; if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotY - (rotY * .1f), rotY + (rotY * .1f)))); } offset += 4; offsetRotZ = offset; rotZ = (data[2] & 0x7FFF) / 32767.0f * scale - shift; if (lookat) { data.OverwriteRange(offset, BitConverter.GetBytes(ThreadSafeRandom.NextFloat(rotZ - (rotZ * .1f), rotZ + (rotZ * .1f)))); } //float w = 1.0f - (rotX * rotX + rotY * rotY + rotZ * rotZ); //w = w >= 0.0f ? (float)Math.Sqrt(w) : 0.0f; //int s = ((data[0] >> 14) & 2) | ((data[1] >> 15) & 1); break; } if (rotCompression == AnimationCompressionFormat.ACF_BioFixed48 || rotCompression == AnimationCompressionFormat.ACF_Float96NoW || rotCompression == AnimationCompressionFormat.ACF_None) { //randomize here? //var RotKeys = new BinInterpNode //{ // Header = $"0x{offsetRotX:X5} RotKey {j}", // Name = "_" + offsetRotX, // Tag = NodeType.Unknown //}; //BoneID.Items.Add(RotKeys); //RotKeys.Items.Add(new BinInterpNode //{ // Header = $"0x{offsetRotX:X5} RotX: {rotX} ", // Name = "_" + offsetRotX, // Tag = NodeType.StructLeafFloat //}); //RotKeys.Items.Add(new BinInterpNode //{ // Header = $"0x{offsetRotY:X5} RotY: {rotY} ", // Name = "_" + offsetRotY, // Tag = NodeType.StructLeafFloat //}); //RotKeys.Items.Add(new BinInterpNode //{ // Header = $"0x{offsetRotZ:X5} RotZ: {rotZ} ", // Name = "_" + offsetRotZ, // Tag = NodeType.StructLeafFloat //}); if (rotCompression == AnimationCompressionFormat.ACF_None) { //RotKeys.Items.Add(new BinInterpNode //{ // Header = $"0x{offsetRotW:X5} RotW: {rotW} ", // Name = "_" + offsetRotW, // Tag = NodeType.StructLeafFloat //}); } } } bone++; } } catch (Exception ex) { Debug.WriteLine("Error reading animsequence: " + ex.Message + ". Skipping"); } export.Data = data; //write back return(true); }
public static T From <T>(ExportEntry export) where T : ObjectBinary, new() { var t = new T { Export = export }; t.Serialize(new SerializingContainer2(export.GetReadOnlyBinaryStream(), export.FileRef, true, export.DataOffset + export.propsEnd())); return(t); }
public Bio2DA(ExportEntry export) { //Console.WriteLine("Loading " + export.ObjectName); this.export = export; IMEPackage pcc = export.FileRef; byte[] data = export.Data; RowNames = new List <string>(); if (export.ClassName == "Bio2DA") { const string rowLabelsVar = "m_sRowLabel"; var props = export.GetProperty <ArrayProperty <NameProperty> >(rowLabelsVar); if (props != null) { foreach (NameProperty n in props) { RowNames.Add(n.ToString()); } } else { Console.WriteLine("Unable to find row names property!"); Debugger.Break(); return; } } else { var props = export.GetProperty <ArrayProperty <IntProperty> >("m_lstRowNumbers");//Bio2DANumberedRows if (props != null) { foreach (IntProperty n in props) { RowNames.Add(n.Value.ToString()); } } else { Debug.WriteLine("Unable to find row names property (m_lstRowNumbers)!"); //Debugger.Break(); return; } } //Get Columns ColumnNames = new List <string>(); int colcount = BitConverter.ToInt32(data, data.Length - 4); int currentcoloffset = 0; while (colcount >= 0) { currentcoloffset += 4; int colindex = BitConverter.ToInt32(data, data.Length - currentcoloffset); currentcoloffset += 8; //names in this case don't use nameindex values. int nameindex = BitConverter.ToInt32(data, data.Length - currentcoloffset); string name = pcc.GetNameEntry(nameindex); ColumnNames.Insert(0, name); colcount--; } Cells = new Bio2DACell[RowCount, ColumnCount]; currentcoloffset += 4; //column count. int infilecolcount = BitConverter.ToInt32(data, data.Length - currentcoloffset); //start of binary data int binstartoffset = export.propsEnd(); //arrayheader + nonenamesize + number of items in this list int curroffset = binstartoffset; int cellcount = BitConverter.ToInt32(data, curroffset); if (cellcount > 0) { curroffset += 4; for (int rowindex = 0; rowindex < RowCount; rowindex++) { for (int colindex = 0; colindex < ColumnCount && curroffset < data.Length - currentcoloffset; colindex++) { byte dataType = data[curroffset]; curroffset++; int dataSize = dataType == (byte)Bio2DACell.Bio2DADataType.TYPE_NAME ? 8 : 4; byte[] celldata = new byte[dataSize]; Buffer.BlockCopy(data, curroffset, celldata, 0, dataSize); Bio2DACell cell = new Bio2DACell(pcc, curroffset, dataType, celldata); Cells[rowindex, colindex] = cell; curroffset += dataSize; } } PopulatedCellCount = RowCount * ColumnCount; //Required for edits to write correct count if SaveToExport } else { IsIndexed = true; curroffset += 4; //theres a 0 here for some reason cellcount = BitConverter.ToInt32(data, curroffset); curroffset += 4; //curroffset += 4; while (PopulatedCellCount < cellcount) { int index = BitConverter.ToInt32(data, curroffset); int row = index / ColumnCount; int col = index % ColumnCount; curroffset += 4; byte dataType = data[curroffset]; int dataSize = dataType == (byte)Bio2DACell.Bio2DADataType.TYPE_NAME ? 8 : 4; curroffset++; var celldata = new byte[dataSize]; Buffer.BlockCopy(data, curroffset, celldata, 0, dataSize); Bio2DACell cell = new Bio2DACell(pcc, curroffset, dataType, celldata); this[row, col] = cell; curroffset += dataSize; } } //Console.WriteLine("Finished loading " + export.ObjectName); }
public void Write2DAToExport() { using (var stream = new MemoryStream()) { //Cell count if (IsIndexed) { //Indexed ones seem to have 0 at start stream.WriteBytes(BitConverter.GetBytes(0)); } stream.WriteBytes(BitConverter.GetBytes(PopulatedCellCount)); //Write cell data for (int rowindex = 0; rowindex < RowCount; rowindex++) { for (int colindex = 0; colindex < ColumnCount; colindex++) { Bio2DACell cell = Cells[rowindex, colindex]; if (cell != null) { if (IsIndexed) { //write index int index = (rowindex * ColumnCount) + colindex; //+1 because they are not zero based indexes since they are numerals stream.WriteBytes(BitConverter.GetBytes(index)); } stream.WriteByte((byte)cell.Type); stream.WriteBytes(cell.Data); } else { if (IsIndexed) { //this is a blank cell. It is not present in the table. continue; } else { Debug.WriteLine("THIS SHOULDN'T OCCUR!"); Debugger.Break(); throw new Exception("A non-indexed Bio2DA cannot have null cells."); } } } } //Write Columns if (!IsIndexed) { stream.WriteBytes(BitConverter.GetBytes(0)); //seems to be a 0 before column definitions } //Console.WriteLine("Columns defs start at " + stream.Position.ToString("X6")); stream.WriteBytes(BitConverter.GetBytes(ColumnCount)); for (int colindex = 0; colindex < ColumnCount; colindex++) { //Console.WriteLine("Writing column definition " + columnNames[colindex]); int nameIndexForCol = export.FileRef.FindNameOrAdd(ColumnNames[colindex]); stream.WriteBytes(BitConverter.GetBytes(nameIndexForCol)); stream.WriteBytes(BitConverter.GetBytes(0)); //second half of name reference in 2da is always zero since they're always indexed at 0 stream.WriteBytes(BitConverter.GetBytes(colindex)); } int propsEnd = export.propsEnd(); byte[] binarydata = stream.ToArray(); //Todo: Rewrite properties here PropertyCollection props = new PropertyCollection(); if (export.ClassName == "Bio2DA") { var indicies = new ArrayProperty <NameProperty>("m_sRowLabel"); foreach (var rowname in RowNames) { indicies.Add(new NameProperty(rowname)); } props.Add(indicies); } else { var indices = new ArrayProperty <IntProperty>("m_lstRowNumbers"); foreach (var rowname in RowNames) { indices.Add(new IntProperty(int.Parse(rowname))); } props.Add(indices); } MemoryStream propsStream = new MemoryStream(); props.WriteTo(propsStream, export.FileRef); MemoryStream currentDataStream = new MemoryStream(export.Data); byte[] propertydata = propsStream.ToArray(); int propertyStartOffset = export.GetPropertyStart(); var newExportData = new byte[propertyStartOffset + propertydata.Length + binarydata.Length]; Buffer.BlockCopy(export.Data, 0, newExportData, 0, propertyStartOffset); propertydata.CopyTo(newExportData, propertyStartOffset); binarydata.CopyTo(newExportData, propertyStartOffset + propertydata.Length); //Console.WriteLine("Old data size: " + export.Data.Length); //Console.WriteLine("NEw data size: " + newExportData.Length); //This assumes the input and output data sizes are the same. We should not assume this with new functionality //if (export.Data.Length != newExportData.Length) //{ // Debug.WriteLine("FILES ARE WRONG SIZE"); // Debugger.Break(); //} export.Data = newExportData; } }
public static void ScanStuff(PackageEditorWPF pewpf) { //var filePaths = MELoadedFiles.GetOfficialFiles(MEGame.ME3);//.Concat(MELoadedFiles.GetOfficialFiles(MEGame.ME2));//.Concat(MELoadedFiles.GetOfficialFiles(MEGame.ME1)); //var filePaths = MELoadedFiles.GetAllFiles(game); /*"Core.pcc", "Engine.pcc", "GameFramework.pcc", "GFxUI.pcc", "WwiseAudio.pcc", "SFXOnlineFoundation.pcc", "SFXGame.pcc" */ var filePaths = new[] { "Core.pcc", "Engine.pcc", "GameFramework.pcc", "GFxUI.pcc", "WwiseAudio.pcc", "SFXOnlineFoundation.pcc" }.Select(f => Path.Combine(ME3Directory.CookedPCPath, f)); var interestingExports = new List <EntryStringPair>(); var foundClasses = new HashSet <string>(); //new HashSet<string>(BinaryInterpreterWPF.ParsableBinaryClasses); var foundProps = new Dictionary <string, string>(); var unkOpcodes = new List <int>();//Enumerable.Range(0x5B, 8).ToList(); unkOpcodes.Add(0); unkOpcodes.Add(1); var unkOpcodesInfo = unkOpcodes.ToDictionary(i => i, i => new OpcodeInfo()); var comparisonDict = new Dictionary <string, (byte[] original, byte[] newData)>(); var extraInfo = new HashSet <string>(); pewpf.IsBusy = true; pewpf.BusyText = "Scanning"; Task.Run(() => { //preload base files for faster scanning using var baseFiles = MEPackageHandler.OpenMEPackages(EntryImporter.FilesSafeToImportFrom(MEGame.ME3) .Select(f => Path.Combine(ME3Directory.CookedPCPath, f))); baseFiles.Add( MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "BIOP_MP_COMMON.pcc"))); foreach (string filePath in filePaths) { //ScanShaderCache(filePath); //ScanMaterials(filePath); //ScanStaticMeshComponents(filePath); //ScanLightComponents(filePath); //ScanLevel(filePath); //if (findClass(filePath, "ShaderCache", true)) break; //findClassesWithBinary(filePath); ScanScripts2(filePath); //if (interestingExports.Count > 0) //{ // break; //} //if (resolveImports(filePath)) break; } }).ContinueWithOnUIThread(prevTask => { pewpf.IsBusy = false; interestingExports.Add(new EntryStringPair(null, string.Join("\n", extraInfo))); var listDlg = new ListDialog(interestingExports, "Interesting Exports", "", pewpf) { DoubleClickEntryHandler = entryItem => { if (entryItem?.Entry is IEntry entryToSelect) { PackageEditorWPF p = new PackageEditorWPF(); p.Show(); p.LoadFile(entryToSelect.FileRef.FilePath, entryToSelect.UIndex); p.Activate(); if (comparisonDict.TryGetValue($"{entryToSelect.UIndex} {entryToSelect.FileRef.FilePath}", out (byte[] original, byte[] newData)val)) { File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "original.bin"), val.original); File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "new.bin"), val.newData); } } } }; listDlg.Show(); }); #region extra scanning functions bool findClass(string filePath, string className, bool withBinary = false) { Debug.WriteLine($" {filePath}"); using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath)) { //if (!pcc.IsCompressed) return false; var exports = pcc.Exports.Where(exp => !exp.IsDefaultObject && exp.IsA(className)); foreach (ExportEntry exp in exports) { try { //Debug.WriteLine($"{exp.UIndex}: {filePath}"); var originalData = exp.Data; exp.WriteBinary(ObjectBinary.From(exp)); var newData = exp.Data; if (!originalData.SequenceEqual(newData)) { interestingExports.Add(exp); File.WriteAllBytes( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "original.bin"), originalData); File.WriteAllBytes( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "new.bin"), newData); return(true); } } catch (Exception exception) { Console.WriteLine(exception); interestingExports.Add(new EntryStringPair(exp, $"{exception}")); return(true); } } } return(false); } void findClassesWithBinary(string filePath) { using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath)) { foreach (ExportEntry exp in pcc.Exports.Where(exp => !exp.IsDefaultObject)) { try { if (!foundClasses.Contains(exp.ClassName) && exp.propsEnd() < exp.DataSize) { if (ObjectBinary.From(exp) != null) { foundClasses.Add(exp.ClassName); } else if (exp.GetBinaryData().Any(b => b != 0)) { foundClasses.Add(exp.ClassName); interestingExports.Add(exp); } } } catch (Exception exception) { Console.WriteLine(exception); interestingExports.Add(new EntryStringPair(exp, $"{exp.UIndex}: {filePath}\n{exception}")); } } } } void ScanShaderCache(string filePath) { using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath)) { ExportEntry shaderCache = pcc.Exports.FirstOrDefault(exp => exp.ClassName == "ShaderCache"); if (shaderCache == null) { return; } int oldDataOffset = shaderCache.DataOffset; try { MemoryStream binData = new MemoryStream(shaderCache.Data); binData.JumpTo(shaderCache.propsEnd() + 1); int nameList1Count = binData.ReadInt32(); binData.Skip(nameList1Count * 12); int namelist2Count = binData.ReadInt32(); //namelist2 binData.Skip(namelist2Count * 12); int shaderCount = binData.ReadInt32(); for (int i = 0; i < shaderCount; i++) { binData.Skip(24); int nextShaderOffset = binData.ReadInt32() - oldDataOffset; binData.Skip(14); if (binData.ReadInt32() != 1111577667) //CTAB { interestingExports.Add(new EntryStringPair(null, $"{binData.Position - 4}: {filePath}")); return; } binData.JumpTo(nextShaderOffset); } int vertexFactoryMapCount = binData.ReadInt32(); binData.Skip(vertexFactoryMapCount * 12); int materialShaderMapCount = binData.ReadInt32(); for (int i = 0; i < materialShaderMapCount; i++) { binData.Skip(16); int switchParamCount = binData.ReadInt32(); binData.Skip(switchParamCount * 32); int componentMaskParamCount = binData.ReadInt32(); //if (componentMaskParamCount != 0) //{ // interestingExports.Add($"{i}: {filePath}"); // return; //} binData.Skip(componentMaskParamCount * 44); int normalParams = binData.ReadInt32(); if (normalParams != 0) { interestingExports.Add(new EntryStringPair(null, $"{i}: {filePath}")); return; } binData.Skip(normalParams * 29); int unrealVersion = binData.ReadInt32(); int licenseeVersion = binData.ReadInt32(); if (unrealVersion != 684 || licenseeVersion != 194) { interestingExports.Add(new EntryStringPair(null, $"{binData.Position - 8}: {filePath}")); return; } int nextMaterialShaderMapOffset = binData.ReadInt32() - oldDataOffset; binData.JumpTo(nextMaterialShaderMapOffset); } } catch (Exception exception) { Console.WriteLine(exception); interestingExports.Add(new EntryStringPair(null, $"{filePath}\n{exception}")); } } } void ScanScripts(string filePath) { using IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath); foreach (ExportEntry exp in pcc.Exports.Where(exp => !exp.IsDefaultObject)) { try { if ((exp.ClassName == "State" || exp.ClassName == "Function") && ObjectBinary.From(exp) is UStruct uStruct) { byte[] data = exp.Data; (_, List <BytecodeSingularToken> tokens) = Bytecode.ParseBytecode(uStruct.ScriptBytes, exp); foreach (var token in tokens) { if (token.CurrentStack.Contains("UNKNOWN") || token.OpCodeString.Contains("UNKNOWN")) { interestingExports.Add(exp); } if (unkOpcodes.Contains(token.OpCode)) { int refUIndex = EndianReader.ToInt32(data, token.StartPos + 1, pcc.Endian); IEntry entry = pcc.GetEntry(refUIndex); if (entry != null && (entry.ClassName == "ByteProperty")) { var info = unkOpcodesInfo[token.OpCode]; info.Usages.Add(pcc.FilePath, exp.UIndex, token.StartPos); info.PropTypes.Add(refUIndex switch { 0 => "Null", _ when entry != null => entry.ClassName, _ => "Invalid" }); if (entry != null) { if (entry.Parent == exp) { info.PropLocations.Add("Local"); } else if (entry.Parent == (exp.Parent.ClassName == "State" ? exp.Parent.Parent : exp.Parent)) { info.PropLocations.Add("ThisClass"); } else if (entry.Parent.ClassName == "Function") { info.PropLocations.Add("OtherFunction"); } else if (exp.Parent.IsA(entry.Parent.ObjectName)) { info.PropLocations.Add("AncestorClass"); } else { info.PropLocations.Add("OtherClass"); } } } } }