commit e84f027bd5d09498fbd432e28b09c380f3882f6e Author: Yukai Li Date: Tue Feb 16 01:16:37 2021 -0700 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ce6fdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,340 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb \ No newline at end of file diff --git a/DgfTxmConvert.sln b/DgfTxmConvert.sln new file mode 100644 index 0000000..4a25aa6 --- /dev/null +++ b/DgfTxmConvert.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30611.23 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DgfTxmConvert", "DgfTxmConvert\DgfTxmConvert.csproj", "{71FADE75-254E-4112-A337-4155CD1AB947}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B561502B-1826-4A2F-BC76-2B6181CD6BF8}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {71FADE75-254E-4112-A337-4155CD1AB947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71FADE75-254E-4112-A337-4155CD1AB947}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71FADE75-254E-4112-A337-4155CD1AB947}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71FADE75-254E-4112-A337-4155CD1AB947}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A4F8E0A7-F5A4-4D5E-9E4B-B5BC9A3836CF} + EndGlobalSection +EndGlobal diff --git a/DgfTxmConvert/DgfTxmConvert.csproj b/DgfTxmConvert/DgfTxmConvert.csproj new file mode 100644 index 0000000..c6c02a6 --- /dev/null +++ b/DgfTxmConvert/DgfTxmConvert.csproj @@ -0,0 +1,23 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + ..\..\LibDgf\LibDgf\bin\Debug\netstandard2.0\LibDgf.dll + + + ..\..\LibDgf\LibDgf.Graphics\bin\Debug\netstandard2.0\LibDgf.Graphics.dll + + + + diff --git a/DgfTxmConvert/Program.cs b/DgfTxmConvert/Program.cs new file mode 100644 index 0000000..42880e6 --- /dev/null +++ b/DgfTxmConvert/Program.cs @@ -0,0 +1,402 @@ +using LibDgf.Dat; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections.Generic; +using System.IO; +using Path = System.IO.Path; +using LibDgf.Aqualead; +using LibDgf.Aqualead.Image.Conversion; +using LibDgf; +using LibDgf.Txm; +using LibDgf.Aqualead.Image; +using LibDgf.Aqualead.Texture; +using System.Diagnostics; +using SixLabors.ImageSharp.Processing; +using LibDgf.Graphics; +using System.Threading.Tasks; +using McMaster.Extensions.CommandLineUtils; + +namespace DgfTxmConvert +{ + class Program + { + const string ETC1TOOL_PATH = @"etc1tool.exe"; + + static List imageConverters; + + static int Main(string[] args) + { + imageConverters = new List + { + new PkmConverter(), + //new KtxConverter(), + new PngConverter(), + }; + + var app = new CommandLineApplication + { + Name = Path.GetFileName(Environment.GetCommandLineArgs()[0]), + FullName = "DDG Final TXM Converter" + }; + + app.Command("convert-dat", config => + { + config.FullName = "Convert DAT to images"; + config.Description = "Converts each individual TXM in a DAT into PNGs."; + + var datPathArg = config.Argument("datPath", "Path of the DAT to extract").IsRequired(); + datPathArg.Accepts().ExistingFile(); + config.HelpOption(); + + config.OnExecute(() => + { + ConvertDat(datPathArg.Value); + }); + }); + + app.Command("convert-txm", config => + { + config.FullName = "Convert TXM to image"; + config.Description = "Converts TXM to PNG."; + + var txmPathArg = config.Argument("txmPath", "Path of the TXM to convert").IsRequired(); + txmPathArg.Accepts().ExistingFile(); + config.HelpOption(); + + config.OnExecute(() => + { + ConvertTxm(txmPathArg.Value); + }); + }); + + app.Command("replace-dat", config => + { + config.FullName = "Replace image in DAT"; + config.Description = "Converts list of images and replaces entries in DAT file"; + + var srcDatPath = config.Argument("srcDatPath", "Path of the source DAT").IsRequired(); + srcDatPath.Accepts().ExistingFile(); + var listPath = config.Argument("listPath", "Path of the image replacement list").IsRequired(); + listPath.Accepts().ExistingFile(); + var destDatPath = config.Argument("destDatPath", "Path to output DAT to").IsRequired(); + listPath.Accepts().LegalFilePath(); + config.HelpOption(); + + config.OnExecute(() => + { + ReplaceDatImages(srcDatPath.Value, destDatPath.Value, listPath.Value); + }); + }); + + app.VersionOptionFromAssemblyAttributes(System.Reflection.Assembly.GetExecutingAssembly()); + app.HelpOption(); + + app.OnExecute(() => + { + app.ShowHelp(); + return 1; + }); + + try + { + return app.Execute(args); + } + catch (CommandParsingException ex) + { + Console.Error.WriteLine(ex.Message); + return 1; + } + catch (Exception ex) + { + Console.Error.WriteLine("Error while processing: {0}", ex); + return -1; + } + } + + static void ReplaceDatImages(string srcDatPath, string destDatPath, string replacementList) + { + List tempPaths = new List(); + try + { + using (DatReader dat = new DatReader(File.OpenRead(srcDatPath))) + { + DatBuilder builder = new DatBuilder(dat); + using (StreamReader sr = File.OpenText(replacementList)) + { + while (!sr.EndOfStream) + { + var line = sr.ReadLine(); + var lineSplit = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); + if (lineSplit.Length != 2) + throw new InvalidDataException($"Invalid line \"{line}\"."); + if (!int.TryParse(lineSplit[0], out var imageIndex)) + throw new InvalidDataException($"Invalid index on line \"{line}\"."); + + string tempPath = Path.GetTempFileName(); + tempPaths.Add(tempPath); + using (FileStream fs = File.Create(tempPath)) + { + TxmConversion.ConvertImageToTxm(lineSplit[1], fs); + } + + builder.ReplacementEntries.Add(new DatBuilder.ReplacementEntry + { + Index = imageIndex, + SourceFile = tempPath + }); + } + } + using (FileStream fs = File.Create(destDatPath)) + { + builder.Build(fs); + } + } + } + finally + { + foreach (var path in tempPaths) + { + File.Delete(path); + } + } + } + + + static void ConvertDat(string path) + { + using (Stream fs = Utils.CheckDecompress(File.OpenRead(path))) + using (DatReader dat = new DatReader(fs)) + { + for (int i = 0; i < dat.EntriesCount; ++i) + { + using (MemoryStream subfile = new MemoryStream(dat.GetData(i))) + { + string outPath = path + $"_{i}.png"; + Console.WriteLine(outPath); + try + { + TxmConversion.ConvertTxmToPng(subfile, outPath); + } + catch (NotSupportedException) + { + File.WriteAllBytes(path + $"_{i}.txm", subfile.ToArray()); + throw; + } + } + } + } + } + + static void BulkConvertDat(string inPath, string filter, bool recursive) + { + foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) + { + ConvertDat(path); + } + } + + static void ConvertTxm(string path) + { + using (Stream fs = Utils.CheckDecompress(File.OpenRead(path))) + { + string outPath = Path.ChangeExtension(path, ".png"); + TxmConversion.ConvertTxmToPng(fs, outPath); + } + } + + static void BulkConvertTxm(string inPath, string filter, bool recursive) + { + foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) + { + string outPath = Path.ChangeExtension(path, ".png"); + Console.WriteLine(outPath); + ConvertTxm(path); + } + } + + static void BulkConvertAtx(string inPath, string filter, bool recursive) + { + foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) + { + Console.WriteLine(path); + using (var fs = Utils.CheckDecompress(File.OpenRead(path))) + { + var tex = new AlTexture(); + tex.Read(new BinaryReader(fs)); + + var img = new AlImage(); + using (Stream ms = Utils.CheckDecompress(new MemoryStream(tex.ImageData))) + { + img.Read(new BinaryReader(ms)); + ConvertAig(img, path); + } + } + } + } + + static void BulkConvertAtxMulti(string inPath, string filter, bool recursive) + { + foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) + { + using (var fs = Utils.CheckDecompress(File.OpenRead(path))) + { + var tex = new AlTexture(); + tex.Read(new BinaryReader(fs)); + + if (tex.ChildTextures.Count < 2) continue; + Console.WriteLine(path); + + var img = new AlImage(); + using (Stream ms = Utils.CheckDecompress(new MemoryStream(tex.ImageData))) + { + img.Read(new BinaryReader(ms)); + if (img.PixelFormat != "BGRA") + { + Console.WriteLine("Not BGRA, skipping"); + continue; + } + + using (MemoryStream ims = new MemoryStream(img.Mipmaps[0])) + using (var imgConv = PngConverter.ConvertBgra32(new BinaryReader(ims), (int)img.Width, (int)img.Height)) + { + foreach (var child in tex.ChildTextures) + { + // First mip only + var bounds = child.Bounds[0]; + using (var childImage = new Image(bounds.W, bounds.H)) + { + childImage.Mutate(ctx => ctx.DrawImage(imgConv, new Point(-bounds.X, -bounds.Y), 1f)); + childImage.SaveAsPng($"{Path.ChangeExtension(path, null)}_{child.Id:x8}.png"); + } + } + } + } + } + } + } + + static void BulkConvertAtxToPng(string inPath, string filter, bool recursive) + { + foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) + { + Console.WriteLine(path); + string fileExtension; + + // Export the texture (probably KTX) + using (var fs = Utils.CheckDecompress(File.OpenRead(path))) + { + var tex = new AlTexture(); + tex.Read(new BinaryReader(fs)); + + var img = new AlImage(); + using (Stream ms = Utils.CheckDecompress(new MemoryStream(tex.ImageData))) + { + img.Read(new BinaryReader(ms)); + fileExtension = ConvertAig(img, path); + } + } + + if (fileExtension != ".pkm") continue; + + // Convert main texture to PNG + string mainPath = Path.ChangeExtension(path, fileExtension); + var startInfo = new ProcessStartInfo(ETC1TOOL_PATH); + startInfo.ArgumentList.Add(mainPath); + startInfo.ArgumentList.Add("--decode"); + startInfo.CreateNoWindow = false; + startInfo.UseShellExecute = false; + Process.Start(startInfo).WaitForExit(); + + // Convert alpha texture if present + string altPath = $"{Path.ChangeExtension(path, null)}_alt{fileExtension}"; + if (File.Exists(altPath)) + { + startInfo = new ProcessStartInfo(ETC1TOOL_PATH); + startInfo.ArgumentList.Add(altPath); + startInfo.ArgumentList.Add("--decode"); + startInfo.CreateNoWindow = false; + startInfo.UseShellExecute = false; + Process.Start(startInfo).WaitForExit(); + + var mainPngPath = Path.ChangeExtension(mainPath, ".png"); + var altPngPath = Path.ChangeExtension(altPath, ".png"); + + using (var mainImg = Image.Load(mainPngPath)) + using (var altImg = Image.Load(altPngPath)) + { + using (var mergedImg = MergeAlpha(mainImg, altImg)) + using (FileStream newFs = File.Create(mainPngPath)) + { + mergedImg.SaveAsPng(newFs); + } + } + + File.Delete(altPngPath); + File.Delete(altPath); + } + + File.Delete(mainPath); + } + } + + static Image MergeAlpha(Image main, Image alpha) + { + var newImg = new Image(main.Width, main.Height); + for (int y = 0; y < newImg.Height; ++y) + { + var newRow = newImg.GetPixelRowSpan(y); + var mainRow = main.GetPixelRowSpan(y); + var alphaRow = alpha.GetPixelRowSpan(y); + for (int x = 0; x < newImg.Width; ++x) + { + var mainPix = mainRow[x]; + var alphaPix = alphaRow[x]; + newRow[x] = new Rgba32(mainPix.R, mainPix.G, mainPix.B, alphaPix.R); + } + } + return newImg; + } + + static void BulkConvertAig(string inPath, string filter, bool recursive) + { + foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) + { + Console.WriteLine(path); + using (var fs = Utils.CheckDecompress(File.OpenRead(path))) + { + var img = new AlImage(); + img.Read(new BinaryReader(fs)); + ConvertAig(img, path); + } + } + } + + static string ConvertAig(AlImage img, string path) + { + foreach (var converter in imageConverters) + { + if (converter.CanConvert(img.PixelFormat)) + { + string outPath = Path.ChangeExtension(path, converter.FileExtension); + using (FileStream ofs = File.Create(outPath)) + { + converter.ConvertFromAl(img, ofs); + } + if (converter.HasAlternativeFile(img)) + { + outPath = Path.ChangeExtension(path, null); + outPath = $"{outPath}_alt{converter.FileExtension}"; + using (FileStream ofs = File.Create(outPath)) + { + converter.ConvertFromAlAlt(img, ofs); + } + } + return converter.FileExtension; + } + } + + throw new Exception($"Cannot find converter for {img.PixelFormat}"); + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a02845 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +Densha de Go! Final Texture Converter +===================================== + +This program allows you to extract and inject textures for DDG Final. + +Requirements +------------ +Requires [LibDgf](https://github.com/GMMan/libdgf) + +Usage +----- +The following commands are available. For arguments please use the `--help` +option. + +- `convert-dat`: Unpacks a .DAT container of textures and converts everything + to PNG. +- `convert-txm`: Converts a single .TXM file to PNG. +- `replace-dat`: Imports and replaces a list of images into a .DAT file. + Requires a text file with a line for each texture to be replaced, consisting + of the index number and the path to the corresponding image (can be relative + to working directory). + +Other +----- +There is some code for bulk convert all .DAT/.TXM files in a directory, along +with some code for Aqualead texture/image formats. Modify the program as +necessary yourself to use them.