Esempio n. 1
0
        // TODO: Also create a wadmaker.config file, if the wad contained fonts or simple images (mipmap textures are the default behavior, so those don't need a config,
        //       unless the user wants to create a wad file and wants different settings for those images such as different dithering, etc.)
        static void ExtractTextures(string inputFilePath, string outputDirectory, bool extractMipmaps, bool overwriteExistingFiles)
        {
            var stopwatch = Stopwatch.StartNew();

            var imageFilesCreated = 0;

            var textures = new List <Texture>();

            if (inputFilePath.EndsWith(".bsp"))
            {
                textures = Bsp.GetEmbeddedTextures(inputFilePath);
            }
            else
            {
                textures = Wad.Load(inputFilePath).Textures;
            }

            CreateDirectory(outputDirectory);

            var isDecalsWad = Path.GetFileName(inputFilePath).ToLowerInvariant() == "decals.wad";

            foreach (var texture in textures)
            {
                var maxMipmap = extractMipmaps ? 4 : 1;
                for (int mipmap = 0; mipmap < maxMipmap; mipmap++)
                {
                    try
                    {
                        var filePath = Path.Combine(outputDirectory, texture.Name + $"{(mipmap > 0 ? ".mipmap" + mipmap : "")}.png");
                        if (!overwriteExistingFiles && File.Exists(filePath))
                        {
                            Log($"WARNING: {filePath} already exist. Skipping texture.");
                            continue;
                        }

                        using (var image = isDecalsWad ? DecalTextureToImage(texture, mipmap) : TextureToImage(texture, mipmap))
                        {
                            image.SaveAsPng(filePath);
                            imageFilesCreated += 1;
                        }
                    }
                    catch (Exception ex)
                    {
                        Log($"ERROR: failed to extract '{texture.Name}'{(mipmap > 0 ? $" (mipmap {mipmap})" : "")}: {ex.GetType().Name}: '{ex.Message}'.");
                    }
                }
            }

            Log($"Extracted {imageFilesCreated} images from {textures.Count} textures from {inputFilePath} to {outputDirectory}, in {stopwatch.Elapsed.TotalSeconds:0.000} seconds.");
        }
Esempio n. 2
0
        static void MakeWad(string inputDirectory, string outputWadFilePath, bool fullRebuild, bool includeSubDirectories)
        {
            var stopwatch = Stopwatch.StartNew();

            var texturesAdded   = 0;
            var texturesUpdated = 0;
            var texturesRemoved = 0;

            var wadMakingSettings = WadMakingSettings.Load(inputDirectory);
            var updateExistingWad = !fullRebuild && File.Exists(outputWadFilePath);
            var wad = updateExistingWad ? Wad.Load(outputWadFilePath) : new Wad();
            var lastWadUpdateTime         = updateExistingWad ? new FileInfo(outputWadFilePath).LastWriteTimeUtc : (DateTime?)null;
            var wadTextureNames           = wad.Textures.Select(texture => texture.Name.ToLowerInvariant()).ToHashSet();
            var conversionOutputDirectory = ExternalConversion.GetConversionOutputDirectory(inputDirectory);
            var isDecalsWad = Path.GetFileNameWithoutExtension(outputWadFilePath).ToLowerInvariant() == "decals";

            // Multiple files can map to the same texture, due to different extensions and upper/lower-case differences.
            // We'll group files by texture name, to make these collisions easy to detect:
            var allInputDirectoryFiles = Directory.EnumerateFiles(inputDirectory, "*", includeSubDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
                                         .Where(path => !ExternalConversion.IsConversionOutputDirectory(path))
                                         .ToHashSet();
            var textureImagePaths = allInputDirectoryFiles
                                    .Where(path => ImageReading.IsSupported(path) || wadMakingSettings.GetTextureSettings(Path.GetFileName(path)).settings.Converter != null)
                                    .Where(path => !path.Contains(".mipmap"))
                                    .Where(path => !WadMakingSettings.IsConfigurationFile(path))
                                    .GroupBy(path => GetTextureName(path));

            // Check for new and updated images:
            try
            {
                foreach (var imagePathsGroup in textureImagePaths)
                {
                    var textureName = imagePathsGroup.Key;
                    if (!IsValidTextureName(textureName))
                    {
                        Log($"WARNING: '{textureName}' is not a valid texture name ({string.Join(", ", imagePathsGroup)}). Skipping file(s).");
                        continue;
                    }
                    else if (textureName.Length > 15)
                    {
                        Log($"WARNING: The name '{textureName}' is too long ({string.Join(", ", imagePathsGroup)}). Skipping file(s).");
                        continue;
                    }
                    else if (imagePathsGroup.Count() > 1)
                    {
                        Log($"WARNING: multiple input files detected for '{textureName}' ({string.Join(", ", imagePathsGroup)}). Skipping files.");
                        continue;
                    }
                    // NOTE: Texture dimensions (which must be multiples of 16) are checked later, in CreateTextureFromImage.


                    var filePath            = imagePathsGroup.Single();
                    var isExistingImage     = wadTextureNames.Contains(textureName.ToLowerInvariant());
                    var isSupportedFileType = ImageReading.IsSupported(filePath);

                    // For files that are not directly supported, we'll include their extension when looking up conversion settings:
                    (var textureSettings, var lastSettingsChangeTime) = wadMakingSettings.GetTextureSettings(isSupportedFileType ? textureName : Path.GetFileName(filePath));
                    if (isExistingImage && updateExistingWad)
                    {
                        // NOTE: A texture will not be rebuilt if one of its mipmap files has been removed. In order to detect such cases,
                        //       WadMaker would need to store additional bookkeeping data, but right now that doesn't seem worth the trouble.
                        // NOTE: Mipmaps must have the same extension as the main image file.
                        var isImageUpdated = GetMipmapFilePaths(filePath)
                                             .Prepend(filePath)
                                             .Where(allInputDirectoryFiles.Contains)
                                             .Select(path => new FileInfo(path).LastWriteTimeUtc)
                                             .Any(dateTime => dateTime > lastWadUpdateTime);
                        if (!isImageUpdated && lastSettingsChangeTime < lastWadUpdateTime)
                        {
                            //Log($"No modifications detected for '{textureName}' ({filePath}). Skipping file.");
                            continue;
                        }
                    }

                    try
                    {
                        var imageFilePath = filePath;
                        if (textureSettings.Converter != null)
                        {
                            if (textureSettings.ConverterArguments == null)
                            {
                                throw new InvalidDataException($"Unable to convert '{filePath}': missing converter arguments.");
                            }

                            imageFilePath = Path.Combine(conversionOutputDirectory, textureName);
                            CreateDirectory(conversionOutputDirectory);

                            var outputFilePaths = ExternalConversion.ExecuteConversionCommand(textureSettings.Converter, textureSettings.ConverterArguments, filePath, imageFilePath, Log);
                            if (imageFilePath.Length < 1)
                            {
                                throw new IOException("Unable to find converter output file. An output file must have the same name as the input file (different extensions are ok).");
                            }

                            var supportedOutputFilePaths = outputFilePaths.Where(ImageReading.IsSupported).ToArray();
                            if (supportedOutputFilePaths.Length < 1)
                            {
                                throw new IOException("The converter did not produce a supported file type.");
                            }
                            else if (supportedOutputFilePaths.Length > 1)
                            {
                                throw new IOException("The converted produced multiple supported file types. Only one output file should be created.");
                            }

                            imageFilePath = supportedOutputFilePaths[0];
                        }

                        // Create texture from image:
                        var texture = CreateTextureFromImage(imageFilePath, textureName, textureSettings, isDecalsWad);

                        if (isExistingImage)
                        {
                            // Update (replace) existing texture:
                            for (int i = 0; i < wad.Textures.Count; i++)
                            {
                                if (wad.Textures[i].Name == texture.Name)
                                {
                                    wad.Textures[i] = texture;
                                    break;
                                }
                            }
                            texturesUpdated += 1;
                            Log($"Updated texture '{textureName}' (from '{filePath}').");
                        }
                        else
                        {
                            // Add new texture:
                            wad.Textures.Add(texture);
                            wadTextureNames.Add(textureName);
                            texturesAdded += 1;
                            Log($"Added texture '{textureName}' (from '{filePath}').");
                        }
                    }
                    catch (Exception ex)
                    {
                        Log($"ERROR: failed to build '{filePath}': {ex.GetType().Name}: '{ex.Message}'.");
                    }
                }

                if (updateExistingWad)
                {
                    // Check for removed images:
                    var directoryTextureNames = textureImagePaths
                                                .Select(group => group.Key)
                                                .ToHashSet();
                    foreach (var textureName in wadTextureNames)
                    {
                        if (!directoryTextureNames.Contains(textureName))
                        {
                            // Delete texture:
                            wad.Textures.Remove(wad.Textures.First(texture => texture.Name.ToLowerInvariant() == textureName));
                            texturesRemoved += 1;
                            Log($"Removed texture '{textureName}'.");
                        }
                    }
                }

                // Finally, save the wad file:
                CreateDirectory(Path.GetDirectoryName(outputWadFilePath));
                wad.Save(outputWadFilePath);
            }
            finally
            {
                try
                {
                    if (Directory.Exists(conversionOutputDirectory))
                    {
                        Directory.Delete(conversionOutputDirectory, true);
                    }
                }
                catch (Exception ex)
                {
                    Log($"WARNING: Failed to delete temporary conversion output directory: {ex.GetType().Name}: '{ex.Message}'.");
                }
            }

            if (updateExistingWad)
            {
                Log($"Updated {outputWadFilePath} from {inputDirectory}: added {texturesAdded}, updated {texturesUpdated} and removed {texturesRemoved} textures, in {stopwatch.Elapsed.TotalSeconds:0.000} seconds.");
            }
            else
            {
                Log($"Created {outputWadFilePath}, with {texturesAdded} textures from {inputDirectory}, in {stopwatch.Elapsed.TotalSeconds:0.000} seconds.");
            }
        }