public override void ReadFromStream(AwesomeReader ar, ISerializable data)
        {
            var env     = data as Environ;
            int version = ReadMagic(ar, data);

            MiloSerializer.ReadFromStream(ar.BaseStream, env.Draw);

            // Read lights
            var lightCount = ar.ReadInt32();

            env.Lights.Clear();
            env.Lights.AddRange(RepeatFor(lightCount, () => ar.ReadString()));

            env.AmbientColor = new Color4()
            {
                R = ar.ReadSingle(),
                G = ar.ReadSingle(),
                B = ar.ReadSingle(),
                A = ar.ReadSingle()
            };

            env.FogStart = ar.ReadSingle();
            env.FogEnd   = ar.ReadSingle();

            env.FogColor = new Color4()
            {
                R = ar.ReadSingle(),
                G = ar.ReadSingle(),
                B = ar.ReadSingle(),
                A = ar.ReadSingle()
            };

            env.EnableFog = ar.ReadBoolean();
        }
        public override void WriteToStream(AwesomeWriter aw, ISerializable data)
        {
            var env = data as Environ;

            // TODO: Add version check
            var version = Magic();

            aw.Write(version);

            MiloSerializer.WriteToStream(aw.BaseStream, env.Draw);

            // Write lights
            aw.Write((int)env.Lights.Count);
            env.Lights.ForEach(x => aw.Write((string)x));

            // Write ambient color
            aw.Write((float)env.AmbientColor.R);
            aw.Write((float)env.AmbientColor.G);
            aw.Write((float)env.AmbientColor.B);
            aw.Write((float)env.AmbientColor.A);

            // Write fog info
            aw.Write((float)env.FogStart);
            aw.Write((float)env.FogEnd);

            aw.Write((float)env.FogColor.R);
            aw.Write((float)env.FogColor.G);
            aw.Write((float)env.FogColor.B);
            aw.Write((float)env.FogColor.A);

            aw.Write((bool)env.EnableFog);
        }
Esempio n. 3
0
        public override void WriteToStream(AwesomeWriter aw, ISerializable data)
        {
            var cam = data as Cam;

            // TODO: Add version check
            var version = Magic();

            aw.Write(version);

            MiloSerializer.WriteToStream(aw.BaseStream, cam.Trans);
            MiloSerializer.WriteToStream(aw.BaseStream, cam.Draw);

            aw.Write((float)cam.NearPlane);
            aw.Write((float)cam.FarPlane);
            aw.Write((float)cam.FOV);

            // Write screen area
            aw.Write((float)cam.ScreenArea.X);
            aw.Write((float)cam.ScreenArea.Y);
            aw.Write((float)cam.ScreenArea.Width);
            aw.Write((float)cam.ScreenArea.Height);

            // Write z-range
            aw.Write((float)cam.ZRange.X);
            aw.Write((float)cam.ZRange.Y);

            aw.Write((string)cam.TargetTexture);
        }
Esempio n. 4
0
        public override void ReadFromStream(AwesomeReader ar, ISerializable data)
        {
            var cam     = data as Cam;
            int version = ReadMagic(ar, data);

            MiloSerializer.ReadFromStream(ar.BaseStream, cam.Trans);
            MiloSerializer.ReadFromStream(ar.BaseStream, cam.Draw);

            cam.NearPlane = ar.ReadSingle();
            cam.FarPlane  = ar.ReadSingle();
            cam.FOV       = ar.ReadSingle();

            // Read screen area
            cam.ScreenArea = new Rectangle()
            {
                X      = ar.ReadSingle(),
                Y      = ar.ReadSingle(),
                Width  = ar.ReadSingle(),
                Height = ar.ReadSingle()
            };

            // Read z-range
            cam.ZRange = new Vector2()
            {
                X = ar.ReadSingle(),
                Y = ar.ReadSingle()
            };

            cam.TargetTexture = ar.ReadString();
        }
Esempio n. 5
0
 public static byte[] WriteToBytes(this MiloSerializer serializer, ISerializable obj)
 {
     using (var ms = new MemoryStream())
     {
         serializer.WriteToStream(ms, obj);
         return(ms.ToArray());
     }
 }
Esempio n. 6
0
        public static T ReadFromMiloObjectBytes <T>(this MiloSerializer serializer, MiloObjectBytes entry) where T : ISerializable, new()
        {
            using (var ms = new MemoryStream(entry.Data))
            {
                var obj = serializer.ReadFromStream <T>(ms);
                (obj as MiloObject).Name = entry.Name;

                return(obj);
            }
        }
Esempio n. 7
0
        public override void WriteToStream(AwesomeWriter aw, ISerializable data)
        {
            var view = data as View;

            // TODO: Add version check
            var version = Magic();

            aw.Write(version);

            MiloSerializer.WriteToStream(aw.BaseStream, view.Anim);
            MiloSerializer.WriteToStream(aw.BaseStream, view.Trans);
            MiloSerializer.WriteToStream(aw.BaseStream, view.Draw);

            aw.Write((string)view.MainView);
            aw.Write((float)view.LODHeight);
            aw.Write((float)view.LODWidth);
        }
Esempio n. 8
0
        public override void WriteToStream(AwesomeWriter aw, ISerializable data)
        {
            var tex = data as Tex;

            // TODO: Add version check
            var version = Magic();

            aw.Write((int)version);

            if (MiloSerializer.Info.Version >= 25)
            {
                aw.Write((int)1); // Definitely needed!
            }
            if (version >= 10)
            {
                aw.Write(new byte[9]);
            }

            aw.Write((int)tex.Width);
            aw.Write((int)tex.Height);
            aw.Write((int)tex.Bpp);

            aw.Write(tex.ExternalPath);
            aw.Write((float)tex.IndexF);
            aw.Write((int)tex.Index);

            aw.Write((bool)tex.UseExternal);
            if (tex.Bitmap != null)
            {
                MiloSerializer.WriteToStream(aw.BaseStream, tex.Bitmap);
            }

            /*
             * if (!tex.UseExternal && tex.Bitmap != null)
             * {
             *  aw.Write(false);
             *  MiloSerializer.WriteToStream(aw.BaseStream, tex.Bitmap);
             * }
             * else
             * {
             *  aw.Write(true);
             * }*/
        }
Esempio n. 9
0
        public override void ReadFromStream(AwesomeReader ar, ISerializable data)
        {
            var view    = data as View;
            int version = ReadMagic(ar, data);
            var meta    = ReadMeta(ar);

            MiloSerializer.ReadFromStream(ar.BaseStream, view.Anim);
            MiloSerializer.ReadFromStream(ar.BaseStream, view.Trans);
            MiloSerializer.ReadFromStream(ar.BaseStream, view.Draw);

            if (version >= 11)
            {
                // Read draw group
                var drawableCount = ar.ReadInt32();
                view.Draw.Drawables.Clear();
                view.Draw.Drawables.AddRange(RepeatFor(drawableCount, () => ar.ReadString()));
            }

            view.MainView = ar.ReadString();

            if (version == 11)
            {
                // Has less data at end of file
                return;
            }

            // Ratio is usually 4:3
            view.LODHeight = ar.ReadSingle();
            view.LODWidth  = ar.ReadSingle();

            // 9 extra bytes for v14?

            /*
             * if (view.ScreenHeight > 0.0f && (view.ScreenWidth / view.ScreenHeight) != (4.0f / 3.0f))
             *  throw new Exception($"Aspect ratio should be {(4.0f / 3.0f):F2}, got {(view.ScreenWidth / view.ScreenHeight):F2}");
             */
        }
 public P9SongPrefSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 11
0
 public MatSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
        public override void WriteToStream(AwesomeWriter aw, ISerializable data)
        {
            var dir = data as MiloObjectDir;

            aw.Write((int)Magic());

            // Sort entries using sort order defined in games
            var sortedEntries = dir.Entries
                                .OrderBy(x => GetEntryTypeSortValue(x.Type))
                                .ToList();

            if (Magic() >= 24)
            {
                // Write extra info
                var dirEntry = dir.Extras["DirectoryEntry"] as MiloObject;
                aw.Write((string)dirEntry.Type);
                aw.Write((string)dirEntry.Name);

                // Calculate hash + blob sizes
                var hashCount = (sortedEntries.Count + 1) * 2;
                var blobSize  = sortedEntries.Select(x => x.Name.Length + 1).Sum() + (dirEntry.Name.Length + 1);

                aw.Write((int)hashCount);
                aw.Write((int)blobSize);
            }

            // Write entries
            aw.Write((int)sortedEntries.Count);
            foreach (var entry in sortedEntries)
            {
                // Used to preserve file name
                var dirtyName = UnsanitizeFileName(entry.Name);

                aw.Write((string)entry.Type);
                aw.Write((string)dirtyName);
            }

            if (Magic() >= 24)
            {
                var dirEntryRaw = dir.Extras["DirectoryEntry"] as ISerializable;

                if (Magic() == 25 &&
                    dir.Type == "ObjectDir" &&
                    dirEntryRaw is MiloObjectDirEntry dirEntry)
                {
                    // Hack for project 9
                    aw.Write((int)dirEntry.Version);
                    aw.Write((int)dirEntry.SubVersion);
                    aw.Write((string)dirEntry.ProjectName);

                    // Write matrices
                    aw.Write((int)7);
                    foreach (var mat in Enumerable
                             .Range(0, 7)
                             .Select(x => Matrix4.Identity()))
                    {
                        aw.Write((float)mat.M11);
                        aw.Write((float)mat.M12);
                        aw.Write((float)mat.M13);

                        aw.Write((float)mat.M21);
                        aw.Write((float)mat.M22);
                        aw.Write((float)mat.M23);

                        aw.Write((float)mat.M31);
                        aw.Write((float)mat.M32);
                        aw.Write((float)mat.M33);

                        aw.Write((float)mat.M41);
                        aw.Write((float)mat.M42);
                        aw.Write((float)mat.M43);
                    }

                    // Constants? I hope
                    aw.Write((int)0);
                    aw.Write((bool)true);
                    aw.Write((int)0);

                    // Write imported milo paths
                    if (!(dirEntry.ImportedMiloPaths is null))
                    {
                        aw.Write((int)dirEntry.ImportedMiloPaths.Length);

                        foreach (var path in dirEntry.ImportedMiloPaths)
                        {
                            aw.Write((string)path);
                        }
                    }
                    else
                    {
                        aw.Write((int)0);
                    }

                    // Might be different depending on dir being root/nested
                    // Root: false, Nested: true
                    aw.Write((bool)(dirEntry.SubDirectories.Count <= 0)); // TODO: Use a better way to determine if nested

                    // Write sub directory names
                    aw.Write((int)dirEntry.SubDirectories.Count);
                    foreach (var subName in dirEntry
                             .SubDirectories
                             .Select(x => $"{x.Name}.milo")
                             .Reverse()) // Seems to be reverse order of serialization
                    {
                        aw.Write((string)subName);
                    }

                    // Write sub directory data
                    foreach (var subDir in dirEntry.SubDirectories)
                    {
                        WriteToStream(aw, subDir);
                    }

                    aw.BaseStream.Position += 13; // Zero'd bytes
                }
                else
                {
                    MiloSerializer.WriteToStream(aw.BaseStream, dirEntryRaw);
                }

                aw.Write(ADDE_PADDING);
            }
 public MiloObjectDirSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 14
0
 public FontSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 15
0
        public override void ReadFromStream(AwesomeReader ar, ISerializable data)
        {
            var mesh    = data as Mesh;
            int version = ReadMagic(ar, data);
            var meta    = ReadMeta(ar);

            MiloSerializer.ReadFromStream(ar.BaseStream, mesh.Trans);
            MiloSerializer.ReadFromStream(ar.BaseStream, mesh.Draw);

            mesh.Material = ar.ReadString();
            mesh.MainMesh = ar.ReadString();

            mesh.Unknown = ar.ReadInt32();
            switch (mesh.Unknown)
            {
            case 0:
            case 31:
            case 33:
            case 37:
            case 63:
                break;

            default:
                throw new Exception($"Unexpected number, got {mesh.Unknown}");
            }

            var num = ar.ReadInt32();

            if (!(num == 0 || num == 1))
            {
                throw new Exception($"This should be 0 or 1, got {num}");
            }

            num = ar.ReadByte();
            if (num != 0)
            {
                throw new Exception($"This should be 0, got {num}");
            }

            // Read vertices
            var count = ar.ReadInt32();

            if (version >= 36)
            {
                ar.BaseStream.Position += 9;                // Skips unknown stuff
            }
            mesh.Vertices.Clear();
            mesh.Vertices.AddRange(RepeatFor(count, () =>
            {
                var vertex = new Vertex3();

                vertex.X = ar.ReadSingle();
                vertex.Y = ar.ReadSingle();
                vertex.Z = ar.ReadSingle();
                if (version == 34)
                {
                    ar.BaseStream.Position += 4;                // Skip W for RB1
                }
                if (version < 35)
                {
                    // Single precision
                    vertex.NormalX = ar.ReadSingle();
                    vertex.NormalY = ar.ReadSingle();
                    vertex.NormalZ = ar.ReadSingle();

                    if (version == 34)
                    {
                        ar.BaseStream.Position += 4;                // Skip W for RB1
                    }
                    vertex.ColorR = ar.ReadSingle();
                    vertex.ColorG = ar.ReadSingle();
                    vertex.ColorB = ar.ReadSingle();
                    vertex.ColorA = ar.ReadSingle();
                    vertex.U      = ar.ReadSingle();
                    vertex.V      = ar.ReadSingle();

                    if (version == 34)
                    {
                        ar.BaseStream.Position += 24;                // Skip unknown bytes for RB1
                    }
                }
                else
                {
                    // Half precision
                    vertex.U = ar.ReadHalf();
                    vertex.V = ar.ReadHalf();

                    // Not sure what this value is but it's usually pretty high
                    ar.BaseStream.Position += 8;

                    // Skip reading normals for now
                    //vertex.NormalX = ar.ReadHalf();
                    //vertex.NormalY = ar.ReadHalf();
                    //vertex.NormalZ = ar.ReadHalf();

                    vertex.NormalX = 1.0f;
                    vertex.NormalY = 1.0f;
                    vertex.NormalZ = 1.0f;

                    vertex.ColorR = ar.ReadByte();
                    vertex.ColorG = ar.ReadByte();
                    vertex.ColorB = ar.ReadByte();
                    vertex.ColorA = ar.ReadByte();

                    // Skip unknown bytes
                    ar.BaseStream.Position += 8;
                }

                return(vertex);
            }));

            // Read face indicies
            count = ar.ReadInt32();
            mesh.Faces.Clear();
            mesh.Faces.AddRange(RepeatFor(count, () => new Face()
            {
                V1 = ar.ReadUInt16(),
                V2 = ar.ReadUInt16(),
                V3 = ar.ReadUInt16(),
            }));

            // Read groups
            count = ar.ReadInt32();
            var groupSizes = RepeatFor(count, () => ar.ReadByte()).ToArray();

            if (groupSizes.Select(x => (int)x).Sum() != mesh.Faces.Count)
            {
                throw new Exception("Sum should equal count of faces");
            }

            var charCount = ar.ReadInt32();

            ar.BaseStream.Position -= 4;

            // Read bones
            mesh.Bones.Clear();
            if (charCount > 0)
            {
                if (version >= 36)
                {
                    // Uses variable length bone count
                    ar.BaseStream.Position += 4;

                    mesh.Bones
                    .AddRange(RepeatFor(charCount, () => new Bone()
                    {
                        Name = ar.ReadString(),
                        Mat  = ReadMatrix(ar)
                    }));
                }
                else
                {
                    // Uses constant length bone count
                    const int boneCount = 4;                                                     // Always 4?
                    var       boneNames = RepeatFor(boneCount, () => ar.ReadString()).ToArray(); // Either 3 or none (Last one is always empty?)
                    var       boneMats  = RepeatFor(boneCount, () => ReadMatrix(ar)).ToArray();

                    for (int i = 0; i < boneCount; i++)
                    {
                        mesh.Bones.Add(new Bone()
                        {
                            Name = boneNames[i],
                            Mat  = boneMats[i]
                        });
                    }
                }
            }
            else
            {
                // Skips zero
                ar.BaseStream.Position += 4;
            }

            if (version == 36)
            {
                ar.BaseStream.Position += 1;
            }
            else if (version >= 37)
            {
                ar.BaseStream.Position += 2;
            }

            mesh.Groups.Clear();
            if (count <= 0 || groupSizes[0] <= 0 || ar.BaseStream.Length == ar.BaseStream.Position)
            {
                mesh.Groups.AddRange(groupSizes
                                     .Select(x => new FaceGroup()
                {
                    Size           = x,
                    Sections       = new List <int>(),
                    VertexIndicies = new List <int>()
                }));
                return;
            }

            // Read face groups
            mesh.Groups.AddRange(Enumerable.Range(0, count).Select(x =>
            {
                var sectionCount = ar.ReadInt32();
                var vertCount    = ar.ReadInt32();

                return(new FaceGroup()
                {
                    Size = groupSizes[x],
                    Sections = Enumerable.Range(0, sectionCount)
                               .Select(y => ar.ReadInt32())
                               .ToList(),
                    VertexIndicies = Enumerable.Range(0, vertCount)
                                     .Select(y => (int)ar.ReadUInt16())
                                     .ToList(),
                });
            }));
        }
Esempio n. 16
0
        public void Parse(Ark2DirOptions op)
        {
            var scriptRegex      = new Regex("(?i).((dtb)|(dta)|(([A-Z]+)(_dta_)([A-Z0-9]+)))$");
            var scriptForgeRegex = new Regex("(?i)(_dta_)([A-Z0-9]+)$");
            var csvRegex         = new Regex("(?i).csv_([A-Z0-9]+)$");

            var dtaRegex     = new Regex("(?i).dta$");
            var textureRegex = new Regex("(?i).((bmp)|(png))(_[A-Z0-9]+)$");
            var miloRegex    = new Regex("(?i).((gh)|(milo)|(rnd))(_[A-Z0-9]+)?$");

            var genPathedFile    = new Regex(@"(?i)(([^\/\\]+[\/\\])*)(gen[\/\\])([^\/\\]+)$");
            var platformExtRegex = new Regex(@"(?i)_([A-Z0-9]+)$");

            Archive ark;
            int     arkVersion;
            bool    arkEncrypted;

            if (Directory.Exists(op.InputPath))
            {
                // Open as directory
                ark = ArkFileSystem.FromDirectory(op.InputPath);

                // TODO: Get from args probably
                arkVersion   = 10;
                arkEncrypted = true;
            }
            else
            {
                // Open as ark
                var arkFile = ArkFile.FromFile(op.InputPath);
                arkVersion   = (int)arkFile.Version;
                arkEncrypted = arkFile.Encrypted;

                ark = arkFile;
            }

            var scriptsToConvert = ark.Entries
                                   .Where(x => op.ConvertScripts &&
                                          arkVersion >= 3 && // Amp dtbs not supported right now
                                          scriptRegex.IsMatch(x.FullPath))
                                   .ToList();

            var csvsToConvert = ark.Entries
                                .Where(x => op.ConvertScripts &&
                                       csvRegex.IsMatch(x.FullPath))
                                .ToList();

            var texturesToConvert = ark.Entries
                                    .Where(x => op.ConvertTextures &&
                                           textureRegex.IsMatch(x.FullPath))
                                    .ToList();

            var milosToInflate = ark.Entries
                                 .Where(x => op.InflateMilos &&
                                        !op.ExtractMilos &&
                                        miloRegex.IsMatch(x.FullPath))
                                 .ToList();

            var milosToExtract = ark.Entries
                                 .Where(x => op.ExtractMilos &&
                                        miloRegex.IsMatch(x.FullPath))
                                 .ToList();

            var entriesToExtract = ark.Entries
                                   .Where(x => op.ExtractAll)
                                   .Except(scriptsToConvert)
                                   .Except(texturesToConvert)
                                   .Except(milosToInflate)
                                   .ToList();

            foreach (var arkEntry in entriesToExtract)
            {
                var filePath = ExtractEntry(ark, arkEntry, CombinePath(op.OutputPath, arkEntry.FullPath));
                Console.WriteLine($"Wrote \"{filePath}\"");
            }

            // Create temp path
            var tempDir = Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));

            if (Directory.Exists(tempDir))
            {
                Directory.Delete(tempDir, true);
            }

            foreach (var textureEntry in texturesToConvert)
            {
                using var arkEntryStream = ark.GetArkEntryFileStream(textureEntry);

                var filePath = CombinePath(op.OutputPath, textureEntry.FullPath);
                var pngPath  = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(filePath)), Path.GetFileNameWithoutExtension(filePath) + ".png");

                // Removes gen sub directory
                if (genPathedFile.IsMatch(pngPath))
                {
                    var match = genPathedFile.Match(pngPath);
                    pngPath = $"{match.Groups[1]}{match.Groups[4]}";
                }

                var info = new SystemInfo()
                {
                    Version   = 10,
                    Platform  = Platform.PS2,
                    BigEndian = false
                };

                var serializer = new MiloSerializer(info);
                var bitmap     = serializer.ReadFromStream <HMXBitmap>(arkEntryStream);

                bitmap.SaveAs(info, pngPath);
                Console.WriteLine($"Wrote \"{pngPath}\"");
            }

            foreach (var miloEntry in milosToInflate)
            {
                var filePath = ExtractEntry(ark, miloEntry, CombinePath(op.OutputPath, miloEntry.FullPath));

                // Inflate milo
                var milo = MiloFile.ReadFromFile(filePath);
                milo.Structure = BlockStructure.MILO_A;
                milo.WriteToFile(filePath);

                Console.WriteLine($"Wrote \"{filePath}\"");
            }

            foreach (var miloEntry in milosToExtract)
            {
                var filePath = ExtractEntry(ark, miloEntry, CombinePath(op.OutputPath, miloEntry.FullPath));
                var dirPath  = Path.GetDirectoryName(filePath);

                var tempPath = filePath + "_temp";
                File.Move(filePath, tempPath, true);

                var extPath = Path.Combine(
                    Path.GetDirectoryName(filePath),
                    Path.GetFileName(filePath));

                var state = new AppState(dirPath);
                state.ExtractMiloContents(
                    Path.GetFileName(tempPath),
                    extPath,
                    op.ConvertTextures);

                // TODO: Refactor IDirectory and remove temp file write/delete
                File.Delete(tempPath);

                Console.WriteLine($"Wrote \"{extPath}\"");
            }

            foreach (var csvEntry in csvsToConvert)
            {
                var csvStream = ark.GetArkEntryFileStream(csvEntry);
                var csv       = CSVFile.FromForgeCSVStream(csvStream);

                // Write to file
                var csvPath = CombinePath(op.OutputPath, csvEntry.FullPath);
                csvPath = platformExtRegex.Replace(csvPath, "");

                csv.SaveToFileAsCSV(csvPath);
                Console.WriteLine($"Wrote \"{csvPath}\"");
            }

            var successDtas = 0;

            foreach (var scriptEntry in scriptsToConvert)
            {
                // Just extract file if dta script
                if (dtaRegex.IsMatch(scriptEntry.FullPath))
                {
                    var filePath = ExtractEntry(ark, scriptEntry, CombinePath(op.OutputPath, scriptEntry.FullPath));
                    Console.WriteLine($"Wrote \"{filePath}\"");
                    continue;
                }

                // Creates output path
                var dtaPath = CombinePath(op.OutputPath, scriptEntry.FullPath);
                dtaPath = !scriptForgeRegex.IsMatch(dtaPath)
                    ? $"{dtaPath.Substring(0, dtaPath.Length - 1)}a" // Simply change b -> a
                    : scriptForgeRegex.Replace(dtaPath, "");

                // Removes gen sub directory
                if (genPathedFile.IsMatch(dtaPath))
                {
                    var match = genPathedFile.Match(dtaPath);
                    dtaPath = $"{match.Groups[1]}{match.Groups[4]}";
                }

                var tempDtbPath = ExtractEntry(ark, scriptEntry, Path.Combine(tempDir, Path.GetRandomFileName()));

                try
                {
                    ScriptHelper.ConvertDtbToDta(tempDtbPath, tempDir, arkEncrypted, arkVersion, dtaPath, op.IndentSize);
                    Console.WriteLine($"Wrote \"{dtaPath}\"");
                    successDtas++;
                }
                catch (DTBParseException ex)
                {
                    Console.WriteLine($"Unable to convert to script, skipping \'{scriptEntry.FullPath}\'");
                    if (File.Exists(dtaPath))
                    {
                        File.Delete(dtaPath);
                    }
                }
                catch (Exception ex)
                {
                }
            }

            if (scriptsToConvert.Count > 0)
            {
                Console.WriteLine($"Converted {successDtas} of {scriptsToConvert.Count} scripts");
            }

            // Clean up temp files
            if (Directory.Exists(tempDir))
            {
                Directory.Delete(tempDir, true);
            }
        }
Esempio n. 17
0
 public TexSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 18
0
 public DrawSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
        public override void WriteToStream(AwesomeWriter aw, ISerializable data)
        {
            var dir = data as MiloObjectDir;

            aw.Write((int)Magic());

            if (Magic() >= 24)
            {
                // Write extra info
                var dirEntry = dir.Extras["DirectoryEntry"] as MiloObject;
                aw.Write((string)dirEntry.Type);
                aw.Write((string)dirEntry.Name);

                /*
                 * var entryNameLengths = dir.Entries.Select(x => ((string)x.Name).Length).Sum();
                 * aw.Write(0); // Unknown
                 * aw.Write(1 + ((string)dirEntry.Name).Length + dir.Entries.Count + entryNameLengths);
                 */

                if (dir.Extras.TryGetValue("Num1", out var num1))
                {
                    aw.Write((int)num1);
                }
                if (dir.Extras.TryGetValue("Num2", out var num2))
                {
                    aw.Write((int)num2);
                }

                aw.Write((int)dir.Entries.Count);
            }

            foreach (var entry in dir.Entries)
            {
                aw.Write((string)entry.Type);
                aw.Write((string)entry.Name);
            }

            if (Magic() >= 24)
            {
                var dirEntryRaw = dir.Extras["DirectoryEntry"] as ISerializable;

                if (Magic() == 25 &&
                    dir.Type == "ObjectDir" &&
                    dirEntryRaw is MiloObjectDirEntry dirEntry)
                {
                    // Hack for project 9
                    aw.Write((int)dirEntry.Version);
                    aw.Write((int)dirEntry.SubVersion);
                    aw.Write((string)dirEntry.ProjectName);

                    // Write matrices
                    aw.Write((int)7);
                    foreach (var mat in Enumerable
                             .Range(0, 7)
                             .Select(x => Matrix4.Identity()))
                    {
                        aw.Write((float)mat.M11);
                        aw.Write((float)mat.M12);
                        aw.Write((float)mat.M13);

                        aw.Write((float)mat.M21);
                        aw.Write((float)mat.M22);
                        aw.Write((float)mat.M23);

                        aw.Write((float)mat.M31);
                        aw.Write((float)mat.M32);
                        aw.Write((float)mat.M33);

                        aw.Write((float)mat.M41);
                        aw.Write((float)mat.M42);
                        aw.Write((float)mat.M43);
                    }

                    // Constants? I hope
                    aw.Write((int)0);
                    aw.Write((bool)true);
                    aw.Write((int)0);

                    // Write imported milo paths
                    if (!(dirEntry.ImportedMiloPaths is null))
                    {
                        aw.Write((int)dirEntry.ImportedMiloPaths.Length);

                        foreach (var path in dirEntry.ImportedMiloPaths)
                        {
                            aw.Write((string)path);
                        }
                    }
                    else
                    {
                        aw.Write((int)0);
                    }

                    // Might be different depending on dir being root/nested
                    // Root: false, Nested: true
                    aw.Write((bool)(dirEntry.SubDirectories.Count <= 0)); // TODO: Use a better way to determine if nested

                    // Write sub directory names
                    aw.Write((int)dirEntry.SubDirectories.Count);
                    foreach (var subName in dirEntry
                             .SubDirectories
                             .Select(x => $"{x.Name}.milo")
                             .Reverse()) // Seems to be reverse order of serialization
                    {
                        aw.Write((string)subName);
                    }

                    // Write sub directory data
                    foreach (var subDir in dirEntry.SubDirectories)
                    {
                        WriteToStream(aw, subDir);
                    }

                    aw.BaseStream.Position += 13; // Zero'd bytes
                }
                else
                {
                    MiloSerializer.WriteToStream(aw.BaseStream, dirEntryRaw);
                }

                aw.Write(ADDE_PADDING);
            }
Esempio n. 20
0
 public ViewSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 21
0
 public TransSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 22
0
 public EnvironSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 23
0
 public HMXBitmapSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 24
0
 public AnimSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 25
0
        public override void ReadFromStream(AwesomeReader ar, ISerializable data)
        {
            var tex = data as Tex;

            int version = ReadMagic(ar, data);

            // Skips zeros
            if (version >= 10 && MiloSerializer.Info.Version == 24)
            {
                ar.BaseStream.Position += 9; // GH2 PS2
            }
            else if (version >= 10)
            {
                // GH2 360 (13 bytes when no script)
                ar.BaseStream.Position += 4;               // Revision number? Usually 1 or 2
                tex.ScriptName          = ar.ReadString(); // Script name?

                var hasDtb = ar.ReadBoolean();
                if (hasDtb)
                {
                    ar.BaseStream.Position -= 1;
                    tex.Script              = DTBFile.FromStream(ar, DTBEncoding.Classic);
                }

                // Extra meta (comment?)
                var meta = ar.ReadString();

                if (version >= 11)
                {
                    ar.BaseStream.Position += 1; // Not sure why it has an extra byte
                }
            }

            tex.Width  = ar.ReadInt32();
            tex.Height = ar.ReadInt32();
            tex.Bpp    = ar.ReadInt32();

            tex.ExternalPath = ar.ReadString();

            tex.IndexF = ar.ReadSingle();

            if (tex.IndexF < -13.0f || (tex.IndexF > 0.0f && tex.IndexF != 666.0f))
            {
                throw new NotSupportedException($"Expected number between -13.0 <-> 0.0, got {tex.IndexF}");
            }

            tex.Index = ar.ReadInt32();
            switch (tex.Index)
            {
            case 1:
            case 2:
            case 4:
            case 8:     // krpa
            case 34:
                break;

            default:
                throw new NotSupportedException($"Unexpected number, got {tex.Index}");
            }

            tex.UseExternal = ar.ReadBoolean();
            tex.Bitmap      = null;

            if (ar.BaseStream.Position == ar.BaseStream.Length)
            {
                return;
            }

            tex.Bitmap = MiloSerializer.ReadFromStream <HMXBitmap>(ar.BaseStream);
        }
Esempio n. 26
0
        public IActionResult ScanArkPost([FromBody] ScanRequest request)
        {
            _miloContext.Database.EnsureCreated();
            var sw = Stopwatch.StartNew();

            // Updates games (arks)
            var game = _miloContext.Arks.FirstOrDefault(x => x.Title == request.GameTitle &&
                                                        x.Platform == request.Platform &&
                                                        x.Region == request.Region);

            if (game == null)
            {
                // Create game
                game = new Data.MiloEntities.Ark()
                {
                    Title    = request.GameTitle,
                    Platform = request.Platform,
                    Region   = request.Region
                };

                _miloContext.Arks.Add(game);
                _miloContext.SaveChanges();
            }

            var ark = ArkFile.FromFile(request.InputPath);

            game.ArkVersion = (int)ark.Version;
            var miloEntries      = new List <Data.MiloEntities.ArkEntry>();
            var totalMiloEntries = 0;

            // Updates ark entries
            foreach (var arkEntry in ark.Entries)
            {
                var entry = arkEntry as OffsetArkEntry;

                var contextEntry = _miloContext.ArkEntries.FirstOrDefault(x => x.Ark == game && x.Path == entry.FullPath);
                if (contextEntry == null)
                {
                    contextEntry = new Data.MiloEntities.ArkEntry()
                    {
                        Ark  = game,
                        Path = entry.FullPath
                    };

                    _miloContext.ArkEntries.Add(contextEntry);
                    _miloContext.SaveChanges();
                }

                contextEntry.Part         = entry.Part;
                contextEntry.Offset       = entry.Offset;
                contextEntry.Size         = (int)entry.Size;
                contextEntry.InflatedSize = (int)entry.InflatedSize;

                if (_miloRegex.IsMatch(contextEntry.Path))
                {
                    miloEntries.Add(contextEntry);
                }

                _miloContext.Update(contextEntry);
            }

            // Updates milos
            foreach (var miloEntry in miloEntries)
            {
                var arkEntry   = ark.Entries.First(x => x.FullPath == miloEntry.Path);
                var mf         = MiloFile.ReadFromStream(ark.GetArkEntryFileStream(arkEntry));
                var serializer = new MiloSerializer(new SystemInfo()
                {
                    BigEndian = mf.BigEndian, Version = mf.Version
                });
                MiloObjectDir milo;


                using (var ms = new MemoryStream(mf.Data))
                {
                    milo = serializer.ReadFromStream <MiloObjectDir>(ms);
                }

                totalMiloEntries += milo.Entries.Count;

                var contextEntry = _miloContext.Milos.FirstOrDefault(x => x.ArkEntry == miloEntry);
                if (contextEntry == null)
                {
                    contextEntry = new Data.MiloEntities.Milo()
                    {
                        ArkEntry = miloEntry
                    };

                    _miloContext.Milos.Add(contextEntry);
                    _miloContext.SaveChanges();
                }

                contextEntry.Version   = mf.Version;
                contextEntry.TotalSize = mf.Data.Length;

                contextEntry.Name = milo.Name ?? "";
                contextEntry.Type = milo.Type ?? "";

                var dirEntry = milo.Entries
                               .Where(x => ((string)x.Type).EndsWith("Dir") && x is MiloObjectBytes)
                               .Select(x => x as MiloObjectBytes)
                               .FirstOrDefault();

                if (dirEntry != null)
                {
                    contextEntry.Size  = dirEntry.Data.Length;
                    contextEntry.Magic = dirEntry.GetMagic();
                }
                else
                {
                    contextEntry.Size  = -1;
                    contextEntry.Magic = -1;
                }

                // Updates milo entries
                foreach (var mEntry in milo.Entries.Where(x => x is MiloObjectBytes && x != dirEntry).Select(y => y as MiloObjectBytes))
                {
                    var contextMEntry = _miloContext.MiloEntries.FirstOrDefault(x => x.Milo == contextEntry && x.Name == mEntry.Name && x.Type == mEntry.Type);
                    if (contextMEntry == null)
                    {
                        contextMEntry = new Data.MiloEntities.MiloEntry()
                        {
                            Milo = contextEntry
                        };

                        _miloContext.MiloEntries.Add(contextMEntry);
                        _miloContext.SaveChanges();
                    }

                    contextMEntry.Name  = mEntry.Name ?? "";
                    contextMEntry.Type  = mEntry.Type ?? "";
                    contextMEntry.Size  = mEntry.Data.Length;
                    contextMEntry.Magic = mEntry.GetMagic();

                    _miloContext.Update(contextMEntry);
                }

                _miloContext.Update(contextEntry);
            }

            _miloContext.SaveChanges();
            sw.Stop();

            return(Ok(new ScanResult()
            {
                TotalArkEntries = ark.Entries.Count,
                TotalMilos = miloEntries.Count,
                TotalMiloEntries = totalMiloEntries,
                TimeElapsed = sw.ElapsedMilliseconds
            }));
        }
Esempio n. 27
0
 public MeshSerializer(MiloSerializer miloSerializer) : base(miloSerializer)
 {
 }
Esempio n. 28
0
        public IActionResult TestSerialization([FromBody] ScanRequest request, bool testSerialize)
        {
            if (!Directory.Exists(request.InputPath))
            {
                return(BadRequest($"Directory \"{request.InputPath}\" does not exist!"));
            }

            var miloEntryRegex = new Regex(@"([^\\]*([.](rnd)|(gh)))\\([^\\]+)\\([^\\]+)$", RegexOptions.IgnoreCase);
            var miloEntries    = Directory.GetFiles(request.InputPath, "*", SearchOption.AllDirectories)
                                 .Where(x => miloEntryRegex.IsMatch(x))
                                 .Select(y =>
            {
                var match = miloEntryRegex.Match(y);

                var miloArchive   = match.Groups[1].Value;
                var miloEntryType = match.Groups[5].Value;
                var miloEntryName = match.Groups[6].Value;

                return(new
                {
                    FullPath = y,
                    MiloArchive = miloArchive,
                    MiloEntryType = miloEntryType,
                    MiloEntryName = miloEntryName
                });
            })
                                 .ToArray();

            //var groupedEntries = miloEntries.GroupBy(x => x.MiloEntryType).ToDictionary(g => g.Key, g => g.ToList());
            MiloSerializer serializer = new MiloSerializer(new SystemInfo()
            {
                Version = 10, Platform = Platform.PS2, BigEndian = false
            });
            var supportedTypes = new [] { "Cam", "Environ", "Mat", "Mesh", "Tex", "View" };

            var results = miloEntries
                          .Where(w => supportedTypes.Contains(w.MiloEntryType))
                          .OrderBy(x => x.MiloEntryType)
                          .ThenBy(y => y.FullPath)
                          .Select(z =>
            {
                ISerializable data    = null;
                string message        = "";
                bool converted        = false;
                bool perfectSerialize = true;

                try
                {
                    switch (z.MiloEntryType)
                    {
                    case "Cam":
                        data = serializer.ReadFromFile <Cam>(z.FullPath);
                        break;

                    case "Environ":
                        data = serializer.ReadFromFile <Environ>(z.FullPath);
                        break;

                    case "Mat":
                        data = serializer.ReadFromFile <Mat>(z.FullPath);
                        break;

                    case "Mesh":
                        data = serializer.ReadFromFile <Mesh>(z.FullPath);
                        break;

                    case "Tex":
                        data = serializer.ReadFromFile <Tex>(z.FullPath);
                        break;

                    case "View":
                        data = serializer.ReadFromFile <View>(z.FullPath);
                        break;

                    default:
                        throw new Exception("Not Supported");
                    }

                    (data as IMiloObject).Name = z.MiloEntryName;
                    converted = true;
                }
                catch (Exception ex)
                {
                    var trace = new StackTrace(ex, true);
                    var frame = trace.GetFrame(0);
                    var name  = frame.GetMethod().ReflectedType.Name;

                    message = $"{name}: {ex.Message}";
                }

                if (testSerialize)
                {
                    try
                    {
                        byte[] bytes;

                        using (var ms = new MemoryStream())
                        {
                            serializer.WriteToStream(ms, data);
                            bytes = ms.ToArray();
                        }

                        var origBytes = System.IO.File.ReadAllBytes(z.FullPath);

                        if (bytes.Length != origBytes.Length)
                        {
                            throw new Exception("Byte count doesn't match");
                        }

                        for (int i = 0; i < bytes.Length; i++)
                        {
                            if (bytes[i] != origBytes[i])
                            {
                                throw new Exception("Bytes don't match");
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        perfectSerialize = false;
                    }
                }

                return(new { Entry = z, Data = data, Message = message, Converted = converted, Serialized = perfectSerialize });
            }).ToList();

            /*
             * var textures = groupedEntries["Tex"]
             *  .Select(x => serializer.ReadFromFile<Tex>(x.FullPath))
             *  .ToList();
             *
             * var views = groupedEntries["View"]
             *  .Select(x => serializer.ReadFromFile<View>(x.FullPath))
             *  .ToList();
             *
             * var meshes = groupedEntries["Mesh"]
             *  .Select(x => serializer.ReadFromFile<Mesh>(x.FullPath))
             *  .ToList();
             */

            return(Ok(new
            {
                TotalCoverage = results.Count(x => x.Converted) / (double)results.Count,
                TotalScanned = results.Count,
                ByType = results
                         .GroupBy(x => x.Entry.MiloEntryType)
                         .ToDictionary(g => g.Key, g => new
                {
                    Coverage = g.Count(x => x.Converted) / (double)g.Count(),
                    Scanned = g.Count(),
                    //Converted = g
                    //    .Where(x => x.Converted)
                    //    .Select(x => new
                    //    {
                    //        x.Entry.FullPath
                    //    }),
                    NotConverted = g
                                   .Where(x => !x.Converted)
                                   .Select(x => new
                    {
                        x.Entry.FullPath,
                        x.Message
                    })
                }),
                NotSerialized = results
                                .Where(x => !x.Serialized)
                                .Select(y => y.Entry.FullPath)
                                //Converted = results
                                //    .Where(x => x.Converted)
                                //    .Select(x => new
                                //    {
                                //        x.Entry.FullPath
                                //    }),
                                //NotConverted = results
                                //    .Where(x => !x.Converted)
                                //    .Select(x => new
                                //    {
                                //        x.Entry.FullPath,
                                //        x.Message
                                //    })
            }));
        }
Esempio n. 29
0
        public override void WriteToStream(AwesomeWriter aw, ISerializable data)
        {
            var mesh = data as Mesh;

            // TODO: Add version check
            var version = Magic();

            aw.Write(version);

            MiloSerializer.WriteToStream(aw.BaseStream, mesh.Trans);
            MiloSerializer.WriteToStream(aw.BaseStream, mesh.Draw);

            aw.Write((string)mesh.Material);
            aw.Write((string)mesh.MainMesh);
            aw.Write((int)mesh.Unknown);
            aw.Write((int)1);
            aw.Write((byte)0);

            // Write vertices
            aw.Write((int)mesh.Vertices.Count);
            mesh.Vertices.ForEach(x =>
            {
                // TODO: Add switch statement for milo versions
                aw.Write((float)x.X);
                aw.Write((float)x.Y);
                aw.Write((float)x.Z);
                aw.Write((float)x.NormalX);
                aw.Write((float)x.NormalY);
                aw.Write((float)x.NormalZ);
                aw.Write((float)x.ColorR);
                aw.Write((float)x.ColorG);
                aw.Write((float)x.ColorB);
                aw.Write((float)x.ColorA);
                aw.Write((float)x.U);
                aw.Write((float)x.V);
            });

            // Write faces
            aw.Write((int)mesh.Faces.Count);
            mesh.Faces.ForEach(x =>
            {
                aw.Write((ushort)x.V1);
                aw.Write((ushort)x.V2);
                aw.Write((ushort)x.V3);
            });

            // Write group sizes
            aw.Write((int)mesh.Groups.Count);
            mesh.Groups.ForEach(x => aw.Write((byte)x.Size));

            const int boneCount = 4; // Always 4?
            var       bones     = mesh.Bones
                                  .Take(boneCount)
                                  .ToList();

            // Write bones
            if (bones.Count > 0)
            {
                bones.ForEach(x => aw.Write((string)x.Name));
                RepeatFor(boneCount - bones.Count, () => aw.Write((string)""));

                bones.ForEach(x => WriteMatrix(x.Mat, aw));
                RepeatFor(boneCount - bones.Count, () => WriteMatrix(Matrix4.Identity(), aw));
            }
            else
            {
                aw.Write((int)0);
            }

            if (mesh.Groups.Sum(x => x.Sections.Count) == 0 &&
                mesh.Groups.Sum(x => x.VertexIndicies.Count) == 0)
            {
                return;
            }

            // Write group sections + vertex indices
            mesh.Groups.ForEach(x =>
            {
                aw.Write((int)x.Sections.Count);
                aw.Write((int)x.VertexIndicies.Count);

                x.Sections.ForEach(y => aw.Write((int)y));
                x.VertexIndicies.ForEach(y => aw.Write((ushort)y));
            });
        }
Esempio n. 30
0
        public IActionResult ExtractFilesFromArk([FromBody] ScanRequest request, bool extractMilos, bool extractDTAs, bool convertTextures)
        {
            Archive ark;

            if (!System.IO.File.Exists(request.InputPath))
            {
                // Open as directory if available
                if (Directory.Exists(request.InputPath))
                {
                    ark = ArkFileSystem.FromDirectory(request.InputPath);
                }
                else
                {
                    return(BadRequest($"File \"{request.InputPath}\" does not exist!"));
                }
            }
            else
            {
                // Open as archive
                ark = ArkFile.FromFile(request.InputPath);
            }


            if (request.OutputPath == null)
            {
                return(BadRequest($"Output directory cannot be null!"));
            }

            string CombinePath(string basePath, string path)
            {
                // Consistent slash
                basePath = (request.OutputPath ?? "").Replace("/", "\\");
                path     = (path ?? "").Replace("/", "\\");

                Regex dotRegex = new Regex(@"[.]+[\\]");

                if (dotRegex.IsMatch(path))
                {
                    // Replaces dotdot path
                    path = dotRegex.Replace(path, x => $"({x.Value.Substring(0, x.Value.Length - 1)})\\");
                }

                return(Path.Combine(basePath, path));
            }

            string GetNonGenPath(string path)
            {
                // Consistent slash
                path = (path ?? "").Replace("/", "\\");

                Regex genRegex      = new Regex(@"gen\\[^\\]+$", RegexOptions.IgnoreCase);
                Regex platformRegex = new Regex(@"_[^_]+$", RegexOptions.IgnoreCase); // TODO: Revisit for Forge extensions

                if (genRegex.IsMatch(path))
                {
                    var splitPath = path.Split('\\');
                    var dir       = string.Join("\\", splitPath.SkipLast(2));
                    var file      = platformRegex.Replace(splitPath.Last(), "");

                    path = $"{dir}\\{file}";
                }

                return(path);
            }

            void SaveAsFile(MiloObjectBytes miloEntry, string basePath)
            {
                var fileName = SanitizeFileName(miloEntry.Name);
                var filePath = basePath = Path.Combine(basePath, miloEntry.Type, fileName);

                if (!Directory.Exists(Path.GetDirectoryName(filePath)))
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(filePath));
                }

                System.IO.File.WriteAllBytes(filePath, miloEntry.Data);
                Console.WriteLine($"Wrote \"{filePath}\"");
            }

            // Extract everything
            if (!extractDTAs && !extractMilos && !convertTextures)
            {
                foreach (var arkEntry in ark.Entries)
                {
                    var filePath = CombinePath(request.OutputPath, arkEntry.FullPath);

                    if (!Directory.Exists(Path.GetDirectoryName(filePath)))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(filePath));
                    }

                    using (var fs = System.IO.File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write))
                    {
                        using (var stream = ark.GetArkEntryFileStream(arkEntry))
                        {
                            stream.CopyTo(fs);
                        }
                    }

                    Console.WriteLine($"Wrote \"{filePath}\"");
                }

                return(Ok());
            }

            string SanitizeFileName(string fileName)
            {
                // Sanitize file name
                var invalidChars = new Regex($"[{new string(Path.GetInvalidFileNameChars())}]", RegexOptions.IgnoreCase);

                return(invalidChars.Replace(fileName, ""));
            }

            void ProcessMiloArkEntry(ArkEntry miloArkEntry)
            {
                var filePath = CombinePath(request.OutputPath, GetNonGenPath(miloArkEntry.FullPath));

                using (var stream = ark.GetArkEntryFileStream(miloArkEntry))
                {
                    var milo           = MiloFile.ReadFromStream(stream);
                    var miloSerializer = new MiloSerializer(new SystemInfo()
                    {
                        Version   = milo.Version,
                        BigEndian = milo.BigEndian,
                        Platform  = Enum.Parse <Platform>(request.Platform)
                    });

                    var miloDir = new MiloObjectDir();
                    using (var ms = new MemoryStream(milo.Data))
                    {
                        miloSerializer.ReadFromStream(ms, miloDir);
                    }

                    if (convertTextures)
                    {
                        var textureEntries = miloDir.Entries
                                             .Where(x => x.Type == "Tex")
                                             .Select(x => x is Tex ? x as Tex : miloSerializer.ReadFromMiloObjectBytes <Tex>(x as MiloObjectBytes))
                                             .Where(x => x.Bitmap != null && x.Bitmap.RawData?.Length > 0)
                                             .ToList();

                        if (textureEntries.Count <= 0)
                        {
                            return;
                        }

                        foreach (var texEntry in textureEntries)
                        {
                            var entryName = Path.GetFileNameWithoutExtension(SanitizeFileName(texEntry.Name));

                            var pngName = $"{entryName}.png";
                            var pngPath = Path.Combine(filePath, texEntry.Type, pngName);
                            texEntry.Bitmap.SaveAs(miloSerializer.Info, pngPath);

                            Console.WriteLine($"Wrote \"{pngPath}\"");

                            // Write DTA script to file
                            var scriptName = texEntry?.ScriptName ?? "";
                            if (texEntry.Script != null)
                            {
                                var dtaName = (string.IsNullOrEmpty(scriptName))
                                    ? $"{entryName}.dta"
                                    : $"{entryName}_{scriptName}.dta";

                                var dtaPath = Path.Combine(filePath, texEntry.Type, dtaName);

                                var parent = new ParentItem(ParentType.Default);
                                foreach (var item in texEntry.Script.Items)
                                {
                                    parent.Add(item);
                                }

                                texEntry.Script.Items.Clear();
                                texEntry.Script.Items.Add(parent);

                                System.IO.File.WriteAllText(dtaPath, texEntry.Script.ToString());
                                Console.WriteLine($"Wrote \"{dtaPath}\"");
                            }
                        }
                    }

                    if (extractMilos)
                    {
                        if (miloDir.Entries.Count <= 0)
                        {
                            return;
                        }

                        if (!Directory.Exists(Path.GetDirectoryName(filePath)))
                        {
                            Directory.CreateDirectory(Path.GetDirectoryName(filePath));
                        }

                        // Saves milo entries
                        miloDir.Entries.ForEach(x =>
                        {
                            SaveAsFile(x as MiloObjectBytes, filePath);
                            Console.WriteLine($"Wrote \"{filePath}\"");
                        });

                        if (miloDir.Extras.ContainsKey("DirectoryEntry"))
                        {
                            SaveAsFile(miloDir.Extras["DirectoryEntry"] as MiloObjectBytes, filePath);
                            Console.WriteLine($"Wrote \"{filePath}\"");
                        }
                    }
                }
            }

            void ProcessBitmapArkEntry(ArkEntry bitmapArkEntry)
            {
                var arkPath  = GetNonGenPath(bitmapArkEntry.FullPath);
                var filePath = CombinePath(request.OutputPath, $"{Path.GetDirectoryName(arkPath)}\\{Path.GetFileNameWithoutExtension(bitmapArkEntry.FileName)}.png");

                using (var stream = ark.GetArkEntryFileStream(bitmapArkEntry))
                {
                    var miloSerializer = new MiloSerializer(new SystemInfo()
                    {
                        Version   = 25,    // 10 = gh1, 24 = gh2
                        BigEndian = false, // Even on PPC it's LE
                        Platform  = Enum.Parse <Platform>(request.Platform)
                    });

                    var bitmap = miloSerializer.ReadFromStream <HMXBitmap>(stream);
                    bitmap.SaveAs(miloSerializer.Info, filePath);
                    Console.WriteLine($"Wrote \"{filePath}\"");
                }
            }

            // Extract milos
            if (extractMilos || convertTextures)
            {
                var miloEntries = ark.Entries.Where(x => _miloRegex.IsMatch(x.FullPath));

                Parallel.ForEach(miloEntries, (entry) =>
                {
                    ProcessMiloArkEntry(entry);
                });

                //foreach (var entry in miloEntries) ProcessMiloArkEntry(entry);
            }

            // Convert textures
            if (convertTextures)
            {
                var bitmapArkEntries = ark.Entries.Where(x => _bitmapRegex.IsMatch(x.FullPath));

                Parallel.ForEach(bitmapArkEntries, (entry) =>
                {
                    ProcessBitmapArkEntry(entry);
                });

                //foreach (var entry in bitmapArkEntries) ProcessBitmapArkEntry(entry);
            }

            return(Ok());
        }