Exemplo n.º 1
0
        public static void ConvertTo(this MEPackage package, MEGame newGame, string tfcPath = null, bool preserveMaterialInstances = false)
        {
            MEGame oldGame         = package.Game;
            var    prePropBinary   = new List <byte[]>(package.ExportCount);
            var    propCollections = new List <PropertyCollection>(package.ExportCount);
            var    postPropBinary  = new List <ObjectBinary>(package.ExportCount);

            if (oldGame == MEGame.ME1 && newGame != MEGame.ME1)
            {
                int idx = package.Names.IndexOf("BIOC_Base");
                if (idx >= 0)
                {
                    package.replaceName(idx, "SFXGame");
                }
            }
            else if (newGame == MEGame.ME1)
            {
                int idx = package.Names.IndexOf("SFXGame");
                if (idx >= 0)
                {
                    package.replaceName(idx, "BIOC_Base");
                }
            }

            //fix up Default_ package.Imports
            if (newGame == MEGame.ME3)
            {
                using IMEPackage core    = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "Core.pcc"));
                using IMEPackage engine  = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "Engine.pcc"));
                using IMEPackage sfxGame = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "SFXGame.pcc"));
                foreach (ImportEntry defImp in package.Imports.Where(imp => imp.ObjectName.Name.StartsWith("Default_")).ToList())
                {
                    string     packageName = defImp.FullPath.Split('.')[0];
                    IMEPackage pck         = packageName switch
                    {
                        "Core" => core,
                        "Engine" => engine,
                        "SFXGame" => sfxGame,
                        _ => null
                    };
                    if (pck != null && pck.Exports.FirstOrDefault(exp => exp.ObjectName == defImp.ObjectName) is ExportEntry defExp)
                    {
                        List <IEntry> impChildren = defImp.GetChildren();
                        List <IEntry> expChildren = defExp.GetChildren();
                        foreach (IEntry expChild in expChildren)
                        {
                            if (impChildren.FirstOrDefault(imp => imp.ObjectName == expChild.ObjectName) is ImportEntry matchingImp)
                            {
                                impChildren.Remove(matchingImp);
                            }
                            else
                            {
                                package.AddImport(new ImportEntry(package)
                                {
                                    idxLink     = defImp.UIndex,
                                    ClassName   = expChild.ClassName,
                                    ObjectName  = expChild.ObjectName,
                                    PackageFile = defImp.PackageFile
                                });
                            }
                        }

                        foreach (IEntry impChild in impChildren)
                        {
                            EntryPruner.TrashEntries(package, impChild.GetAllDescendants().Prepend(impChild));
                        }
                    }
                }
            }

            //purge MaterialExpressions
            if (newGame == MEGame.ME3)
            {
                var entriesToTrash = new List <IEntry>();
                foreach (ExportEntry mat in package.Exports.Where(exp => exp.ClassName == "Material").ToList())
                {
                    entriesToTrash.AddRange(mat.GetAllDescendants());
                }
                EntryPruner.TrashEntries(package, entriesToTrash.ToHashSet());
            }

            EntryPruner.TrashIncompatibleEntries(package, oldGame, newGame);

            foreach (ExportEntry export in package.Exports)
            {
                //convert stack, or just get the pre-prop binary if no stack
                prePropBinary.Add(ExportBinaryConverter.ConvertPrePropBinary(export, newGame));

                PropertyCollection props = export.ClassName == "Class" ? null : EntryPruner.RemoveIncompatibleProperties(package, export.GetProperties(), export.ClassName, newGame);
                propCollections.Add(props);

                //convert binary data
                postPropBinary.Add(ExportBinaryConverter.ConvertPostPropBinary(export, newGame, props));

                //writes header in whatever format is correct for newGame
                export.RegenerateHeader(newGame, true);
            }

            package.setGame(newGame);

            for (int i = 0; i < package.Exports.Count; i++)
            {
                package.Exports[i].WritePrePropsAndPropertiesAndBinary(prePropBinary[i], propCollections[i], postPropBinary[i]);
            }

            if (newGame != MEGame.ME3)  //Fix Up Textures before Materials
            {
                foreach (ExportEntry texport in package.Exports.Where(exp => exp.IsTexture()))
                {
                    texport.WriteProperty(new BoolProperty(true, "NeverStream"));
                }
            }
            else if (package.Exports.Any(exp => exp.IsTexture() && Texture2D.GetTexture2DMipInfos(exp, null)
                                         .Any(mip => mip.storageType == StorageTypes.pccLZO ||
                                              mip.storageType == StorageTypes.pccZlib)))
            {
                //ME3 can't deal with compressed textures in a pcc, so we'll need to stuff them into a tfc
                tfcPath ??= Path.ChangeExtension(package.FilePath, "tfc");
                string tfcName = Path.GetFileNameWithoutExtension(tfcPath);
                using var tfc = new FileStream(tfcPath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
                Guid tfcGuid;
                if (tfc.Length >= 16)
                {
                    tfcGuid = tfc.ReadGuid();
                    tfc.SeekEnd();
                }
                else
                {
                    tfcGuid = Guid.NewGuid();
                    tfc.WriteGuid(tfcGuid);
                }

                foreach (ExportEntry texport in package.Exports.Where(exp => exp.IsTexture()))
                {
                    List <Texture2DMipInfo> mips = Texture2D.GetTexture2DMipInfos(texport, null);
                    var offsets = new List <int>();
                    foreach (Texture2DMipInfo mipInfo in mips)
                    {
                        if (mipInfo.storageType == StorageTypes.pccLZO || mipInfo.storageType == StorageTypes.pccZlib)
                        {
                            offsets.Add((int)tfc.Position);
                            byte[] mip = mipInfo.storageType == StorageTypes.pccLZO
                                ? TextureCompression.CompressTexture(Texture2D.GetTextureData(mipInfo, texport.Game), StorageTypes.extZlib)
                                : Texture2D.GetTextureData(mipInfo, texport.Game, decompress: false);
                            tfc.WriteFromBuffer(mip);
                        }
                    }
                    offsets.Add((int)tfc.Position);
                    texport.WriteBinary(ExportBinaryConverter.ConvertTexture2D(texport, package.Game, offsets, StorageTypes.extZlib));
                    texport.WriteProperty(new NameProperty(tfcName, "TextureFileCacheName"));
                    texport.WriteProperty(tfcGuid.ToGuidStructProp("TFCFileGuid"));
                }
            }
            if (oldGame == MEGame.ME3 && newGame != MEGame.ME3)
            {
                int idx = package.Names.IndexOf("location");
                if (idx >= 0)
                {
                    package.replaceName(idx, "Location");
                }
            }
            else if (newGame == MEGame.ME3)
            {
                int idx = package.Names.IndexOf("Location");
                if (idx >= 0)
                {
                    package.replaceName(idx, "location");
                }
            }

            if (newGame == MEGame.ME3) //Special handling where materials have been ported between games.
            {
                //change all materials to default material, but try to preserve diff and norm textures
                using var resourcePCC = MEPackageHandler.OpenMEPackageFromStream(ME3ExplorerCoreUtilities.GetCustomAppResourceStream(MEGame.ME3));
                var defaultmaster = resourcePCC.Exports.First(exp => exp.ObjectName == "NormDiffMaterial");
                var materiallist  = package.Exports.Where(exp => exp.ClassName == "Material" || exp.ClassName == "MaterialInstanceConstant").ToList();
                foreach (var mat in materiallist)
                {
                    Debug.WriteLine($"Fixing up {mat.FullPath}");
                    var      masterMat        = defaultmaster;
                    var      hasDefaultMaster = true;
                    UIndex[] textures         = Array.Empty <UIndex>();
                    if (mat.ClassName == "Material")
                    {
                        textures = ObjectBinary.From <Material>(mat).SM3MaterialResource.UniformExpressionTextures;
                        switch (mat.FullPath)
                        {
                        case "BioT_Volumetric.LAG_MM_Volumetric":
                        case "BioT_Volumetric.LAG_MM_FalloffSphere":
                        case "BioT_LevelMaster.Materials.Opaque_MM":
                        case "BioT_LevelMaster.Materials.GUI_Lit_MM":
                        case "BioT_LevelMaster.Materials.Signage.MM_GUIMaster_Emissive":
                        case "BioT_LevelMaster.Materials.Signage.MM_GUIMaster_Emissive_Fallback":
                        case "BioT_LevelMaster.Materials.Opaque_Standard_MM":
                        case "BioT_LevelMaster.Tech_Inset_MM":
                        case "BioT_LevelMaster.Tech_Border_MM":
                        case "BioT_LevelMaster.Brushed_Metal":
                            masterMat        = resourcePCC.Exports.First(exp => exp.FullPath == mat.FullPath);
                            hasDefaultMaster = false;
                            break;

                        default:
                            break;
                        }
                    }
                    else if (mat.GetProperty <BoolProperty>("bHasStaticPermutationResource")?.Value == true)
                    {
                        if (mat.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentProp && package.GetEntry(parentProp.Value) is IEntry parent && parent.ClassName == "Material")
                        {
                            switch (parent.FullPath)
                            {
                            case "BioT_LevelMaster.Materials.Opaque_MM":
                                masterMat        = resourcePCC.Exports.First(exp => exp.FullPath == "Materials.Opaque_MM_INST");
                                hasDefaultMaster = false;
                                break;

                            case "BIOG_APL_MASTER_MATERIAL.Placeable_MM":
                                masterMat        = resourcePCC.Exports.First(exp => exp.FullPath == "Materials.Placeable_MM_INST");
                                hasDefaultMaster = false;
                                break;

                            case "BioT_LevelMaster.Materials.Opaque_Standard_MM":
                                masterMat        = resourcePCC.Exports.First(exp => exp.FullPath == "Materials.Opaque_Standard_MM_INST");
                                hasDefaultMaster = false;
                                break;

                            default:
                                textures = ObjectBinary.From <MaterialInstance>(mat).SM3StaticPermutationResource.UniformExpressionTextures;
                                break;
                            }

                            if (!hasDefaultMaster && mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> texParams)
                            {
                                textures = texParams.Select(structProp => new UIndex(structProp.GetProp <ObjectProperty>("ParameterValue")?.Value ?? 0)).ToArray();
                            }
                        }
                    }
                    else if (preserveMaterialInstances)
                    {
                        continue;
                    }
                    else if (mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> texParams)
                    {
                        textures = texParams.Select(structProp => new UIndex(structProp.GetProp <ObjectProperty>("ParameterValue")?.Value ?? 0)).ToArray();
                    }
                    else if (mat.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentProp && package.GetEntry(parentProp.Value) is ExportEntry parent && parent.ClassName == "Material")
                    {
                        textures = ObjectBinary.From <Material>(parent).SM3MaterialResource.UniformExpressionTextures;
                    }

                    if (hasDefaultMaster)
                    {
                        EntryImporter.ReplaceExportDataWithAnother(masterMat, mat);
                        int norm = 0;
                        int diff = 0;
                        foreach (UIndex texture in textures)
                        {
                            if (package.GetEntry(texture) is IEntry tex)
                            {
                                if (diff == 0 && tex.ObjectName.Name.Contains("diff", StringComparison.OrdinalIgnoreCase))
                                {
                                    diff = texture;
                                }
                                else if (norm == 0 && tex.ObjectName.Name.Contains("norm", StringComparison.OrdinalIgnoreCase))
                                {
                                    norm = texture;
                                }
                            }
                        }
                        if (diff == 0)
                        {
                            diff = EntryImporter.GetOrAddCrossImportOrPackage("EngineMaterials.DefaultDiffuse", resourcePCC, package).UIndex;
                        }

                        var matBin = ObjectBinary.From <Material>(mat);
                        matBin.SM3MaterialResource.UniformExpressionTextures = new UIndex[] { norm, diff };
                        mat.WriteBinary(matBin);
                        mat.Class = package.Imports.First(imp => imp.ObjectName == "Material");
                    }
                    else if (mat.ClassName == "Material")
                    {
                        var mmparent = EntryImporter.GetOrAddCrossImportOrPackage(masterMat.ParentFullPath, resourcePCC, package);
                        EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, masterMat, package, mmparent, true, out IEntry targetexp);
                        mat.ReplaceAllReferencesToThisOne(targetexp);
                        EntryPruner.TrashEntryAndDescendants(mat);
                    }
                    else if (mat.ClassName == "MaterialInstanceConstant")
                    {
                        try
                        {
                            var matprops        = mat.GetProperties();
                            var parentlightguid = masterMat.GetProperty <StructProperty>("ParentLightingGuid");
                            matprops.AddOrReplaceProp(parentlightguid);
                            var mguid = masterMat.GetProperty <StructProperty>("m_Guid");
                            matprops.AddOrReplaceProp(mguid);
                            var lguid = masterMat.GetProperty <StructProperty>("LightingGuid");
                            matprops.AddOrReplaceProp(lguid);
                            var    masterBin          = ObjectBinary.From <MaterialInstance>(masterMat);
                            var    matBin             = ObjectBinary.From <MaterialInstance>(mat);
                            var    staticResTextures3 = masterBin.SM3StaticPermutationResource.UniformExpressionTextures.ToList();
                            var    newtextures3       = new List <UIndex>();
                            var    staticResTextures2 = masterBin.SM2StaticPermutationResource.UniformExpressionTextures.ToList();
                            var    newtextures2       = new List <UIndex>();
                            IEntry norm = null;
                            IEntry diff = null;
                            IEntry spec = null;
                            foreach (var texref in textures)
                            {
                                IEntry texEnt  = package.GetEntry(texref);
                                string texName = texEnt?.ObjectName ?? "None";
                                if (texName.ToLowerInvariant().Contains("norm"))
                                {
                                    norm = texEnt;
                                }
                                else if (texName.ToLowerInvariant().Contains("diff"))
                                {
                                    diff = texEnt;
                                }
                                else if (texName.ToLowerInvariant().Contains("spec"))
                                {
                                    spec = texEnt;
                                }
                                else if (texName.ToLowerInvariant().Contains("msk"))
                                {
                                    spec = texEnt;
                                }
                            }

                            foreach (var texidx in staticResTextures2)
                            {
                                var    masterTxt = resourcePCC.GetEntry(texidx);
                                IEntry newTxtEnt = masterTxt;
                                switch (masterTxt?.ObjectName.Name)
                                {
                                case "DefaultDiffuse":
                                    if (diff != null)
                                    {
                                        newTxtEnt = diff;
                                    }
                                    break;

                                case "DefaultNormal":
                                    if (norm != null)
                                    {
                                        newTxtEnt = norm;
                                    }
                                    break;

                                case "Gray":      //Spec
                                    if (spec != null)
                                    {
                                        newTxtEnt = spec;
                                    }
                                    break;

                                default:
                                    break;
                                }

                                var newtexidx = package.Exports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0;
                                if (newtexidx == 0)
                                {
                                    newtexidx = package.Imports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0;
                                }
                                if (newTxtEnt == masterTxt && newtexidx == 0)
                                {
                                    var texparent = EntryImporter.GetOrAddCrossImportOrPackage(newTxtEnt.ParentFullPath, resourcePCC, package);
                                    EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, newTxtEnt, package, texparent, true, out IEntry newtext);
                                    newtextures2.Add(newtext?.UIndex ?? 0);
                                }
                                else
                                {
                                    newtextures2.Add(newtexidx);
                                }
                            }

                            foreach (var texidx in staticResTextures3)
                            {
                                var    masterTxt = resourcePCC.GetEntry(texidx);
                                IEntry newTxtEnt = masterTxt;
                                switch (masterTxt?.ObjectName)
                                {
                                case "DefaultDiffuse":
                                    if (diff != null)
                                    {
                                        newTxtEnt = diff;
                                    }
                                    break;

                                case "DefaultNormal":
                                    if (norm != null)
                                    {
                                        newTxtEnt = norm;
                                    }
                                    break;

                                case "Gray":      //Spec
                                    if (spec != null)
                                    {
                                        newTxtEnt = spec;
                                    }
                                    break;

                                default:
                                    break;
                                }
                                var newtexidx = package.Exports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0;
                                if (newtexidx == 0)
                                {
                                    newtexidx = package.Imports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0;
                                }
                                if (newTxtEnt == masterTxt && newtexidx == 0)
                                {
                                    var texparent = EntryImporter.GetOrAddCrossImportOrPackage(newTxtEnt.ParentFullPath, resourcePCC, package);
                                    EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, newTxtEnt, package, texparent, true, out IEntry newtext);
                                    newtextures3.Add(newtext?.UIndex ?? 0);
                                }
                                else
                                {
                                    newtextures3.Add(newtexidx);
                                }
                            }
                            masterBin.SM2StaticPermutationResource.UniformExpressionTextures = newtextures2.ToArray();
                            masterBin.SM3StaticPermutationResource.UniformExpressionTextures = newtextures3.ToArray();
                            mat.WritePropertiesAndBinary(matprops, masterBin);
                        }
                        catch
                        {
                            Debug.WriteLine("MaterialInstanceConversion error");
                        }
                    }
                }
            }
        }
    }
Exemplo n.º 2
0
        public static string Replace(this Texture2D t2d, Image image, PropertyCollection props, string fileSourcePath = null, string forcedTFCName = null)
        {
            string      errors       = "";
            var         textureCache = forcedTFCName ?? t2d.GetTopMip().TextureCacheName;
            string      fmt          = t2d.TextureFormat;
            PixelFormat pixelFormat  = Image.getPixelFormatType(fmt);

            t2d.RemoveEmptyMipsFromMipList();

            // Not sure what this does?
            // Remove all but one mip?
            //if (Export.Game == MEGame.ME1 && texture.mipMapsList.Count < 6)
            //{
            //    for (int i = texture.mipMapsList.Count - 1; i != 0; i--)
            //        texture.mipMapsList.RemoveAt(i);
            //}
            PixelFormat newPixelFormat = pixelFormat;

            //Changing Texture Type. Not sure what this is, exactly.
            //if (mod.markConvert)
            //    newPixelFormat = changeTextureType(pixelFormat, image.pixelFormat, ref package, ref texture);


            if (!image.checkDDSHaveAllMipmaps() || t2d.Mips.Count > 1 && image.mipMaps.Count() <= 1 || image.pixelFormat != newPixelFormat)
            //(!mod.markConvert && image.pixelFormat != pixelFormat))
            {
                bool dxt1HasAlpha  = false;
                byte dxt1Threshold = 128;
                if (pixelFormat == PixelFormat.DXT1 && props.GetProp <EnumProperty>("CompressionSettings") is EnumProperty compressionSettings && compressionSettings.Value.Name == "TC_OneBitAlpha")
                {
                    dxt1HasAlpha = true;
                    if (image.pixelFormat == PixelFormat.ARGB ||
                        image.pixelFormat == PixelFormat.DXT3 ||
                        image.pixelFormat == PixelFormat.DXT5)
                    {
                        errors += "Warning: Texture was converted from full alpha to binary alpha." + Environment.NewLine;
                    }
                }

                //Generate lower mips
                image.correctMips(newPixelFormat, dxt1HasAlpha, dxt1Threshold);
            }

            if (t2d.Mips.Count == 1)
            {
                var topMip = image.mipMaps[0];
                image.mipMaps.Clear();
                image.mipMaps.Add(topMip);
            }
            else
            {
                // remove lower mipmaps from source image which not exist in game data
                //Not sure what this does since we just generated most of these mips
                for (int t = 0; t < image.mipMaps.Count(); t++)
                {
                    if (image.mipMaps[t].origWidth <= t2d.Mips[0].width &&
                        image.mipMaps[t].origHeight <= t2d.Mips[0].height &&
                        t2d.Mips.Count > 1)
                    {
                        if (!t2d.Mips.Exists(m => m.width == image.mipMaps[t].origWidth && m.height == image.mipMaps[t].origHeight))
                        {
                            image.mipMaps.RemoveAt(t--);
                        }
                    }
                }

                // put empty mips if missing
                for (int t = 0; t < t2d.Mips.Count; t++)
                {
                    if (t2d.Mips[t].width <= image.mipMaps[0].origWidth &&
                        t2d.Mips[t].height <= image.mipMaps[0].origHeight)
                    {
                        if (!image.mipMaps.Exists(m => m.origWidth == t2d.Mips[t].width && m.origHeight == t2d.Mips[t].height))
                        {
                            MipMap mipmap = new MipMap(t2d.Mips[t].width, t2d.Mips[t].height, pixelFormat);
                            image.mipMaps.Add(mipmap);
                        }
                    }
                }
            }

            //if (!texture.properties.exists("LODGroup"))
            //    texture.properties.setByteValue("LODGroup", "TEXTUREGROUP_Character", "TextureGroup", 1025);
            List <byte[]> compressedMips = new List <byte[]>();

            for (int m = 0; m < image.mipMaps.Count(); m++)
            {
                if (t2d.Export.Game == MEGame.ME2)
                {
                    compressedMips.Add(TextureCompression.CompressTexture(image.mipMaps[m].data, StorageTypes.extLZO)); //LZO
                }
                else
                {
                    compressedMips.Add(TextureCompression.CompressTexture(image.mipMaps[m].data, StorageTypes.extZlib)); //ZLib
                }
            }

            List <Texture2DMipInfo> mipmaps = new List <Texture2DMipInfo>();

            for (int m = 0; m < image.mipMaps.Count(); m++)
            {
                Texture2DMipInfo mipmap = new Texture2DMipInfo();
                mipmap.Export           = t2d.Export;
                mipmap.width            = image.mipMaps[m].origWidth;
                mipmap.height           = image.mipMaps[m].origHeight;
                mipmap.TextureCacheName = textureCache;
                if (t2d.Mips.Exists(x => x.width == mipmap.width && x.height == mipmap.height))
                {
                    var oldMip = t2d.Mips.First(x => x.width == mipmap.width && x.height == mipmap.height);
                    mipmap.storageType = oldMip.storageType;
                }
                else
                {
                    mipmap.storageType = t2d.Mips[0].storageType;
                    if (t2d.Mips.Count() > 1)
                    {
                        //Will implement later. ME3Explorer won't support global relinking, that's MEM's job.
                        //if (Export.Game == MEGame.ME1 && matched.linkToMaster == -1)
                        //{
                        //    if (mipmap.storageType == StorageTypes.pccUnc)
                        //    {
                        //        mipmap.storageType = StorageTypes.pccLZO;
                        //    }
                        //}
                        //else if (Export.Game == MEGame.ME1 && matched.linkToMaster != -1)
                        //{
                        //    if (mipmap.storageType == StorageTypes.pccUnc ||
                        //        mipmap.storageType == StorageTypes.pccLZO ||
                        //        mipmap.storageType == StorageTypes.pccZlib)
                        //    {
                        //        mipmap.storageType = StorageTypes.extLZO;
                        //    }
                        //}
                        //else
                    }
                }

                //ME2,ME3: Force compression type (not implemented yet)
                if (t2d.Export.Game == MEGame.ME3)
                {
                    if (mipmap.storageType == StorageTypes.extLZO) //ME3 LZO -> ZLIB
                    {
                        mipmap.storageType = StorageTypes.extZlib;
                    }
                    if (mipmap.storageType == StorageTypes.pccLZO) //ME3 PCC LZO -> PCCZLIB
                    {
                        mipmap.storageType = StorageTypes.pccZlib;
                    }
                    if (mipmap.storageType == StorageTypes.extUnc) //ME3 Uncomp -> ZLib
                    {
                        mipmap.storageType = StorageTypes.extZlib;
                    }
                    //Leave here for future. WE might need this after dealing with double compression
                    //if (mipmap.storageType == StorageTypes.pccUnc && mipmap.width > 32) //ME3 Uncomp -> ZLib
                    //    mipmap.storageType = StorageTypes.pccZlib;
                    if (mipmap.storageType == StorageTypes.pccUnc && m < image.mipMaps.Count() - 6 && textureCache != null) //Moving texture to store externally.
                    {
                        mipmap.storageType = StorageTypes.extZlib;
                    }
                }
                else if (t2d.Export.Game == MEGame.ME2)
                {
                    if (mipmap.storageType == StorageTypes.extZlib) //ME2 ZLib -> LZO
                    {
                        mipmap.storageType = StorageTypes.extLZO;
                    }
                    if (mipmap.storageType == StorageTypes.pccZlib) //M2 ZLib -> LZO
                    {
                        mipmap.storageType = StorageTypes.pccLZO;
                    }
                    if (mipmap.storageType == StorageTypes.extUnc) //ME2 Uncomp -> LZO
                    {
                        mipmap.storageType = StorageTypes.extLZO;
                    }
                    //Leave here for future. We might neable this after dealing with double compression
                    //if (mipmap.storageType == StorageTypes.pccUnc && mipmap.width > 32) //ME2 Uncomp -> LZO
                    //    mipmap.storageType = StorageTypes.pccLZO;
                    if (mipmap.storageType == StorageTypes.pccUnc && m < image.mipMaps.Count() - 6 && textureCache != null) //Moving texture to store externally. make sure bottom 6 are pcc stored
                    {
                        mipmap.storageType = StorageTypes.extLZO;
                    }
                }


                //Investigate. this has something to do with archive storage types
                //if (mod.arcTexture != null)
                //{
                //    if (mod.arcTexture[m].storageType != mipmap.storageType)
                //    {
                //        mod.arcTexture = null;
                //    }
                //}

                mipmap.width  = image.mipMaps[m].width;
                mipmap.height = image.mipMaps[m].height;
                mipmaps.Add(mipmap);
                if (t2d.Mips.Count() == 1)
                {
                    break;
                }
            }

            #region MEM code comments. Should probably leave for reference

            //if (texture.properties.exists("TextureFileCacheName"))
            //{
            //    string archive = texture.properties.getProperty("TextureFileCacheName").valueName;
            //    if (mod.arcTfcDLC && mod.arcTfcName != archive)
            //        mod.arcTexture = null;

            //    if (mod.arcTexture == null)
            //    {
            //        archiveFile = Path.Combine(GameData.MainData, archive + ".tfc");
            //        if (matched.path.ToLowerInvariant().Contains("\\dlc"))
            //        {
            //            mod.arcTfcDLC = true;
            //            string DLCArchiveFile = Path.Combine(Path.GetDirectoryName(GameData.GamePath + matched.path), archive + ".tfc");
            //            if (File.Exists(DLCArchiveFile))
            //                archiveFile = DLCArchiveFile;
            //            else if (!File.Exists(archiveFile))
            //            {
            //                List<string> files = Directory.GetFiles(GameData.bioGamePath, archive + ".tfc",
            //                    SearchOption.AllDirectories).Where(item => item.EndsWith(".tfc", StringComparison.OrdinalIgnoreCase)).ToList();
            //                if (files.Count == 1)
            //                    archiveFile = files[0];
            //                else if (files.Count == 0)
            //                {
            //                    using (FileStream fs = new FileStream(DLCArchiveFile, FileMode.CreateNew, FileAccess.Write))
            //                    {
            //                        fs.WriteFromBuffer(texture.properties.getProperty("TFCFileGuid").valueStruct);
            //                    }
            //                    archiveFile = DLCArchiveFile;
            //                    newTfcFile = true;
            //                }
            //                else
            //                    throw new Exception("More instnces of TFC file: " + archive + ".tfc");
            //            }
            //        }
            //        else
            //        {
            //            mod.arcTfcDLC = false;
            //        }

            //        // check if texture fit in old space
            //        for (int mip = 0; mip < image.mipMaps.Count(); mip++)
            //        {
            //            Texture.MipMap testMipmap = new Texture.MipMap();
            //            testMipmap.width = image.mipMaps[mip].origWidth;
            //            testMipmap.height = image.mipMaps[mip].origHeight;
            //            if (ExistMipmap(testMipmap.width, testMipmap.height))
            //                testMipmap.storageType = texture.getMipmap(testMipmap.width, testMipmap.height).storageType;
            //            else
            //            {
            //                oldSpace = false;
            //                break;
            //            }

            //            if (testMipmap.storageType == StorageTypes.extZlib ||
            //                testMipmap.storageType == StorageTypes.extLZO)
            //            {
            //                Texture.MipMap oldTestMipmap = texture.getMipmap(testMipmap.width, testMipmap.height);
            //                if (mod.cacheCprMipmaps[mip].Length > oldTestMipmap.compressedSize)
            //                {
            //                    oldSpace = false;
            //                    break;
            //                }
            //            }
            //            if (texture.mipMapsList.Count() == 1)
            //                break;
            //        }

            //        long fileLength = new FileInfo(archiveFile).Length;
            //        if (!oldSpace && fileLength + 0x5000000 > 0x80000000)
            //        {
            //            archiveFile = "";
            //            foreach (TFCTexture newGuid in guids)
            //            {
            //                archiveFile = Path.Combine(GameData.MainData, newGuid.name + ".tfc");
            //                if (!File.Exists(archiveFile))
            //                {
            //                    texture.properties.setNameValue("TextureFileCacheName", newGuid.name);
            //                    texture.properties.setStructValue("TFCFileGuid", "Guid", newGuid.guid);
            //                    using (FileStream fs = new FileStream(archiveFile, FileMode.CreateNew, FileAccess.Write))
            //                    {
            //                        fs.WriteFromBuffer(newGuid.guid);
            //                    }
            //                    newTfcFile = true;
            //                    break;
            //                }
            //                else
            //                {
            //                    fileLength = new FileInfo(archiveFile).Length;
            //                    if (fileLength + 0x5000000 < 0x80000000)
            //                    {
            //                        texture.properties.setNameValue("TextureFileCacheName", newGuid.name);
            //                        texture.properties.setStructValue("TFCFileGuid", "Guid", newGuid.guid);
            //                        break;
            //                    }
            //                }
            //                archiveFile = "";
            //            }
            //            if (archiveFile == "")
            //                throw new Exception("No free TFC texture file!");
            //        }
            //    }
            //    else
            //    {
            //        texture.properties.setNameValue("TextureFileCacheName", mod.arcTfcName);
            //        texture.properties.setStructValue("TFCFileGuid", "Guid", mod.arcTfcGuid);
            //    }
            //}

            #endregion

            int allextmipssize = 0;

            for (int m = 0; m < image.mipMaps.Count(); m++)
            {
                Texture2DMipInfo x = mipmaps[m];
                var compsize       = image.mipMaps[m].data.Length;

                if (x.storageType == StorageTypes.extZlib ||
                    x.storageType == StorageTypes.extLZO ||
                    x.storageType == StorageTypes.extUnc)
                {
                    allextmipssize += compsize; //compsize on Unc textures is same as LZO/ZLib
                }
            }
            //todo: check to make sure TFC will not be larger than 2GiB
            Guid tfcGuid       = Guid.NewGuid(); //make new guid as storage
            bool locallyStored = mipmaps[0].storageType == StorageTypes.pccUnc || mipmaps[0].storageType == StorageTypes.pccZlib || mipmaps[0].storageType == StorageTypes.pccLZO;
            for (int m = 0; m < image.mipMaps.Count(); m++)
            {
                Texture2DMipInfo mipmap = mipmaps[m];
                //if (mipmap.width > 32)
                //{
                //    if (mipmap.storageType == StorageTypes.pccUnc)
                //    {
                //        mipmap.storageType = Export.Game == MEGame.ME2 ? StorageTypes.pccLZO : StorageTypes.pccZlib;
                //    }
                //    if (mipmap.storageType == StorageTypes.extUnc)
                //    {
                //        mipmap.storageType = Export.Game == MEGame.ME2 ? StorageTypes.extLZO : StorageTypes.extZlib;
                //    }
                //}

                mipmap.uncompressedSize = image.mipMaps[m].data.Length;
                if (t2d.Export.Game == MEGame.ME1)
                {
                    if (mipmap.storageType == StorageTypes.pccLZO ||
                        mipmap.storageType == StorageTypes.pccZlib)
                    {
                        mipmap.Mip            = compressedMips[m];
                        mipmap.compressedSize = mipmap.Mip.Length;
                    }
                    else if (mipmap.storageType == StorageTypes.pccUnc)
                    {
                        mipmap.compressedSize = mipmap.uncompressedSize;
                        mipmap.Mip            = image.mipMaps[m].data;
                    }
                    else
                    {
                        throw new Exception("Unknown mip storage type!");
                    }
                }
                else
                {
                    if (mipmap.storageType == StorageTypes.extZlib ||
                        mipmap.storageType == StorageTypes.extLZO)
                    {
                        if (compressedMips.Count != image.mipMaps.Count())
                        {
                            throw new Exception("Amount of compressed mips does not match number of mips of incoming image!");
                        }
                        mipmap.Mip            = compressedMips[m];
                        mipmap.compressedSize = mipmap.Mip.Length;
                    }


                    if (mipmap.storageType == StorageTypes.pccUnc ||
                        mipmap.storageType == StorageTypes.extUnc)
                    {
                        mipmap.compressedSize = mipmap.uncompressedSize;
                        mipmap.Mip            = image.mipMaps[m].data;
                    }

                    if (mipmap.storageType == StorageTypes.pccLZO || mipmap.storageType == StorageTypes.pccZlib)
                    {
                        mipmap.Mip            = compressedMips[m];
                        mipmap.compressedSize = mipmap.Mip.Length;
                    }

                    if (mipmap.storageType == StorageTypes.extZlib ||
                        mipmap.storageType == StorageTypes.extLZO ||
                        mipmap.storageType == StorageTypes.extUnc)
                    {
                        if (!string.IsNullOrEmpty(mipmap.TextureCacheName) && mipmap.Export.Game != MEGame.ME1)
                        {
                            //Check local dir
                            string tfcarchive            = mipmap.TextureCacheName + ".tfc";
                            var    localDirectoryTFCPath = Path.Combine(Path.GetDirectoryName(mipmap.Export.FileRef.FilePath), tfcarchive);
                            if (File.Exists(localDirectoryTFCPath))
                            {
                                try
                                {
                                    using (FileStream fs = new FileStream(localDirectoryTFCPath, FileMode.Open, FileAccess.ReadWrite))
                                    {
                                        tfcGuid = fs.ReadGuid();
                                        fs.Seek(0, SeekOrigin.End);
                                        mipmap.externalOffset = (int)fs.Position;
                                        fs.Write(mipmap.Mip, 0, mipmap.compressedSize);
                                    }
                                }
                                catch (Exception e)
                                {
                                    throw new Exception("Problem appending to TFC file " + tfcarchive + ": " + e.Message);
                                }
                                continue;
                            }

                            //Check game
                            var gameFiles = MELoadedFiles.GetFilesLoadedInGame(mipmap.Export.Game, includeTFCs: true);
                            if (gameFiles.TryGetValue(tfcarchive, out string archiveFile))
                            {
                                try
                                {
                                    using (FileStream fs = new FileStream(archiveFile, FileMode.Open, FileAccess.ReadWrite))
                                    {
                                        tfcGuid = fs.ReadGuid();
                                        fs.Seek(0, SeekOrigin.End);
                                        mipmap.externalOffset = (int)fs.Position;
                                        fs.Write(mipmap.Mip, 0, mipmap.compressedSize);
                                    }
                                }
                                catch (Exception e)
                                {
                                    throw new Exception("Problem appending to TFC file " + archiveFile + ": " + e.Message);
                                }
                                continue;
                            }


                            //Cache not found. Make new TFC
                            try
                            {
                                using (FileStream fs = new FileStream(localDirectoryTFCPath, FileMode.OpenOrCreate, FileAccess.Write))
                                {
                                    fs.WriteGuid(tfcGuid);
                                    mipmap.externalOffset = (int)fs.Position;
                                    fs.Write(mipmap.Mip, 0, mipmap.compressedSize);
                                }
                            }
                            catch (Exception e)
                            {
                                throw new Exception("Problem creating new TFC file " + tfcarchive + ": " + e.Message);
                            }
                            continue;
                        }
                    }
                }
                mipmaps[m] = mipmap;
                if (t2d.Mips.Count() == 1)
                {
                    break;
                }
            }

            t2d.ReplaceMips(mipmaps);

            //Set properties


            // The bottom 6 mips are apparently always pcc stored. If there is less than 6 mips, set neverstream to true, which tells game
            // and toolset to never look into archives for mips.
            //if (Export.Game == MEGame.ME2 || Export.Game == MEGame.ME3)
            //{
            //    if (texture.properties.exists("TextureFileCacheName"))
            //    {
            //        if (texture.mipMapsList.Count < 6)
            //        {
            //            mipmap.storageType = StorageTypes.pccUnc;
            //            texture.properties.setBoolValue("NeverStream", true);
            //        }
            //        else
            //        {
            //            if (Export.Game == MEGame.ME2)
            //                mipmap.storageType = StorageTypes.extLZO;
            //            else
            //                mipmap.storageType = StorageTypes.extZlib;
            //        }
            //    }
            //}

            var hasNeverStream = props.GetProp <BoolProperty>("NeverStream") != null;
            if (locallyStored)
            {
                // Rules for default neverstream
                // 1. Must be Package Stored
                // 2. Must have at least 6 not empty mips
                if (mipmaps.Count >= 6)
                {
                    props.AddOrReplaceProp(new BoolProperty(true, "NeverStream"));
                }
                // Side case: NeverStream property was already set, we should respect the value
                // But won't that always be set here?
                // Is there a time where we should remove NeverStream? I can't see any logical way
                // neverstream would be here
            }

            if (mipmaps.Count < 6)
            {
                props.RemoveNamedProperty("NeverStream");
            }

            if (!locallyStored)
            {
                props.AddOrReplaceProp(tfcGuid.ToGuidStructProp("TFCFileGuid"));
                if (mipmaps[0].storageType == StorageTypes.extLZO || mipmaps[0].storageType == StorageTypes.extUnc || mipmaps[0].storageType == StorageTypes.extZlib)
                {
                    //Requires texture cache name
                    props.AddOrReplaceProp(new NameProperty(textureCache, "TextureFileCacheName"));
                }
                else
                {
                    //Should not have texture cache name
                    var cacheProp = props.GetProp <NameProperty>("TextureFileCacheName");
                    if (cacheProp != null)
                    {
                        props.Remove(cacheProp);
                    }
                }
            }
            else
            {
                props.RemoveNamedProperty("TFCFileGuid");
            }

            props.AddOrReplaceProp(new IntProperty(t2d.Mips.First().width, "SizeX"));
            props.AddOrReplaceProp(new IntProperty(t2d.Mips.First().height, "SizeY"));
            if (t2d.Export.Game < MEGame.ME3 && fileSourcePath != null)
            {
                props.AddOrReplaceProp(new StrProperty(fileSourcePath, "SourceFilePath"));
                props.AddOrReplaceProp(new StrProperty(File.GetLastWriteTimeUtc(fileSourcePath).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), "SourceFileTimestamp"));
            }

            var mipTailIdx = props.GetProp <IntProperty>("MipTailBaseIdx");
            if (mipTailIdx != null)
            {
                mipTailIdx.Value = t2d.Mips.Count - 1;
            }

            EndianReader mem = new EndianReader(new MemoryStream())
            {
                Endian = t2d.Export.FileRef.Endian
            };
            props.WriteTo(mem.Writer, t2d.Export.FileRef);
            mem.Position = 0;
            var test      = PropertyCollection.ReadProps(t2d.Export, mem.BaseStream, "Texture2D", true, true); //do not set properties as this may interfere with some other code. may change later.
            int propStart = t2d.Export.GetPropertyStart();
            var pos       = mem.Position;
            mem.Position = 0;
            byte[] propData = mem.ToArray();
            if (t2d.Export.Game == MEGame.ME3)
            {
                t2d.Export.Data = t2d.Export.Data.Take(propStart).Concat(propData).Concat(t2d.SerializeNewData()).ToArray();
            }
            else
            {
                var array    = t2d.Export.Data.Take(propStart).Concat(propData).ToArray();
                var testdata = new MemoryStream(array);
                var test2    = PropertyCollection.ReadProps(t2d.Export, testdata, "Texture2D", true, true, t2d.Export); //do not set properties as this may interfere with some other code. may change later.
                                                                                                                        //ME2 post-data is this right?
                t2d.Export.Data = t2d.Export.Data.Take(propStart).Concat(propData).Concat(t2d.SerializeNewData()).ToArray();
            }

            //using (MemoryStream newData = new MemoryStream())
            //{
            //    newData.WriteFromBuffer(texture.properties.toArray());
            //    newData.WriteFromBuffer(texture.toArray(0, false)); // filled later
            //    package.setExportData(matched.exportID, newData.ToArray());
            //}

            //using (MemoryStream newData = new MemoryStream())
            //{
            //    newData.WriteFromBuffer(texture.properties.toArray());
            //    newData.WriteFromBuffer(texture.toArray(package.exportsTable[matched.exportID].dataOffset + (uint)newData.Position));
            //    package.setExportData(matched.exportID, newData.ToArray());
            //}

            //Since this is single replacement, we don't want to relink to master
            //We want to ensure names are different though, will have to implement into UI
            //if (Export.Game == MEGame.ME1)
            //{
            //    if (matched.linkToMaster == -1)
            //        mod.masterTextures.Add(texture.mipMapsList, entryMap.listIndex);
            //}
            //else
            //{
            //    if (triggerCacheArc)
            //    {
            //        mod.arcTexture = texture.mipMapsList;
            //        mod.arcTfcGuid = texture.properties.getProperty("TFCFileGuid").valueStruct;
            //        mod.arcTfcName = texture.properties.getProperty("TextureFileCacheName").valueName;
            //    }
            //}


            return(errors);
        }
Exemplo n.º 3
0
        public void ConvertTo(MEGame newGame)
        {
            MEGame oldGame         = Game;
            var    prePropBinary   = new List <byte[]>(ExportCount);
            var    propCollections = new List <PropertyCollection>(ExportCount);
            var    postPropBinary  = new List <ObjectBinary>(ExportCount);

            if (oldGame == MEGame.ME1 && newGame != MEGame.ME1)
            {
                int idx = names.IndexOf("BIOC_Base");
                if (idx >= 0)
                {
                    names[idx] = "SFXGame";
                }
            }
            else if (newGame == MEGame.ME1)
            {
                int idx = names.IndexOf("SFXGame");
                if (idx >= 0)
                {
                    names[idx] = "BIOC_Base";
                }
            }

            //fix up Default_ imports
            if (newGame == MEGame.ME3)
            {
                using (IMEPackage core = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.cookedPath, "Core.pcc")))
                    using (IMEPackage engine = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.cookedPath, "Engine.pcc")))
                        using (IMEPackage sfxGame = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.cookedPath, "SFXGame.pcc")))
                        {
                            foreach (ImportEntry defImp in imports.Where(imp => imp.ObjectName.Name.StartsWith("Default_")).ToList())
                            {
                                string     packageName = defImp.FullPath.Split('.')[0];
                                IMEPackage pck         = packageName == "Core" ? core : packageName == "Engine" ? engine : packageName == "SFXGame" ? sfxGame : null;
                                if (pck != null && pck.Exports.FirstOrDefault(exp => exp.ObjectName == defImp.ObjectName) is ExportEntry defExp)
                                {
                                    var impChildren = defImp.GetChildren();
                                    var expChildren = defExp.GetChildren();
                                    foreach (IEntry expChild in expChildren)
                                    {
                                        if (impChildren.FirstOrDefault(imp => imp.ObjectName == expChild.ObjectName) is ImportEntry matchingImp)
                                        {
                                            impChildren.Remove(matchingImp);
                                        }
                                        else
                                        {
                                            AddImport(new ImportEntry(this)
                                            {
                                                idxLink     = defImp.UIndex,
                                                ClassName   = expChild.ClassName,
                                                ObjectName  = expChild.ObjectName,
                                                PackageFile = defImp.PackageFile
                                            });
                                        }
                                    }

                                    foreach (IEntry impChild in impChildren)
                                    {
                                        EntryPruner.TrashEntries(this, impChild.GetAllDescendants().Prepend(impChild));
                                    }
                                }
                            }
                        }
            }

            //purge MaterialExpressions
            if (newGame == MEGame.ME3)
            {
                var entriesToTrash = new List <IEntry>();
                foreach (ExportEntry mat in exports.Where(exp => exp.ClassName == "Material").ToList())
                {
                    entriesToTrash.AddRange(mat.GetAllDescendants());
                }
                EntryPruner.TrashEntries(this, entriesToTrash.ToHashSet());
            }

            EntryPruner.TrashIncompatibleEntries(this, oldGame, newGame);

            foreach (ExportEntry export in exports)
            {
                //convert stack, or just get the pre-prop binary if no stack
                prePropBinary.Add(ExportBinaryConverter.ConvertPrePropBinary(export, newGame));

                if (export.ClassName == "Class")
                {
                    propCollections.Add(null);
                }
                else
                {
                    //read in all properties in the old format, and remove ones that are incompatible with newGame
                    propCollections.Add(EntryPruner.RemoveIncompatibleProperties(this, export.GetProperties(), export.ClassName, newGame));
                }

                //convert binary data
                postPropBinary.Add(ExportBinaryConverter.ConvertPostPropBinary(export, newGame));

                //writes header in whatever format is correct for newGame
                export.RegenerateHeader(newGame, true);
            }

            Game = newGame;

            for (int i = 0; i < exports.Count; i++)
            {
                var newData = new MemoryStream();
                newData.WriteFromBuffer(prePropBinary[i]);
                //write back properties in new format
                propCollections[i]?.WriteTo(newData, this);

                postPropBinary[i].WriteTo(newData, this, exports[i].DataOffset + exports[i].propsEnd()); //should do this again during Save to get offsets correct
                //might not matter though

                exports[i].Data = newData.ToArray();
            }

            if (newGame == MEGame.ME3)
            {
                //change all materials to default material, but try to preserve diff and norm textures
                using var resourcePCC = MEPackageHandler.OpenME3Package(App.CustomResourceFilePath(MEGame.ME3));
                var normDiffMat = resourcePCC.Exports.First(exp => exp.ObjectName == "NormDiffMaterial");

                foreach (ExportEntry mat in exports.Where(exp => exp.ClassName == "Material" || exp.ClassName == "MaterialInstanceConstant"))
                {
                    UIndex[] textures = Array.Empty <UIndex>();
                    if (mat.ClassName == "Material")
                    {
                        textures = ObjectBinary.From <Material>(mat).SM3MaterialResource.UniformExpressionTextures;
                    }
                    else if (mat.GetProperty <BoolProperty>("bHasStaticPermutationResource")?.Value == true)
                    {
                        textures = ObjectBinary.From <MaterialInstance>(mat).SM3StaticPermutationResource.UniformExpressionTextures;
                    }
                    else if (mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> texParams)
                    {
                        textures = texParams.Select(structProp => new UIndex(structProp.GetProp <ObjectProperty>("ParameterValue")?.Value ?? 0)).ToArray();
                    }
                    else if (mat.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentProp && GetEntry(parentProp.Value) is ExportEntry parent && parent.ClassName == "Material")
                    {
                        textures = ObjectBinary.From <Material>(parent).SM3MaterialResource.UniformExpressionTextures;
                    }

                    EntryImporter.ReplaceExportDataWithAnother(normDiffMat, mat);
                    int norm = 0;
                    int diff = 0;
                    foreach (UIndex texture in textures)
                    {
                        if (GetEntry(texture) is IEntry tex)
                        {
                            if (diff == 0 && tex.ObjectName.Name.Contains("diff", StringComparison.OrdinalIgnoreCase))
                            {
                                diff = texture;
                            }
                            else if (norm == 0 && tex.ObjectName.Name.Contains("norm", StringComparison.OrdinalIgnoreCase))
                            {
                                norm = texture;
                            }
                        }
                    }
                    if (diff == 0)
                    {
                        diff = EntryImporter.GetOrAddCrossImportOrPackage("EngineMaterials.DefaultDiffuse", resourcePCC, this).UIndex;
                    }

                    var matBin = ObjectBinary.From <Material>(mat);
                    matBin.SM3MaterialResource.UniformExpressionTextures = new UIndex[] { norm, diff };
                    mat.setBinaryData(matBin.ToBytes(this));
                    mat.Class = imports.First(imp => imp.ObjectName == "Material");
                }
            }

            if (newGame != MEGame.ME3)
            {
                foreach (ExportEntry texport in exports.Where(exp => exp.IsTexture()))
                {
                    texport.WriteProperty(new BoolProperty(true, "NeverStream"));
                }
            }
            else if (exports.Any(exp => exp.IsTexture() && Texture2D.GetTexture2DMipInfos(exp, null)
                                 .Any(mip => mip.storageType == StorageTypes.pccLZO ||
                                      mip.storageType == StorageTypes.pccZlib)))
            {
                //ME3 can't deal with compressed textures in a pcc, so we'll need to stuff them into a tfc
                string tfcName = Path.GetFileNameWithoutExtension(FilePath);
                using var tfc = File.OpenWrite(Path.ChangeExtension(FilePath, "tfc"));
                Guid tfcGuid = Guid.NewGuid();
                tfc.WriteGuid(tfcGuid);

                foreach (ExportEntry texport in exports.Where(exp => exp.IsTexture()))
                {
                    List <Texture2DMipInfo> mips = Texture2D.GetTexture2DMipInfos(texport, null);
                    var offsets = new List <int>();
                    foreach (Texture2DMipInfo mipInfo in mips)
                    {
                        if (mipInfo.storageType == StorageTypes.pccLZO || mipInfo.storageType == StorageTypes.pccZlib)
                        {
                            offsets.Add((int)tfc.Position);
                            byte[] mip;
                            if (mipInfo.storageType == StorageTypes.pccLZO)
                            {
                                mip = TextureCompression.CompressTexture(Texture2D.GetTextureData(mipInfo), StorageTypes.extZlib);
                            }
                            else
                            {
                                mip = Texture2D.GetTextureData(mipInfo, false);
                            }
                            tfc.WriteFromBuffer(mip);
                        }
                    }
                    offsets.Add((int)tfc.Position);
                    texport.setBinaryData(ExportBinaryConverter.ConvertTexture2D(texport, Game, offsets, StorageTypes.extZlib));
                    texport.WriteProperty(new NameProperty(tfcName, "TextureFileCacheName"));
                    texport.WriteProperty(tfcGuid.ToGuidStructProp("TFCFileGuid"));
                }
            }
        }