commit 8f7e17251f7785bef4d66d3e7014e17884fb2bd8
Author: windyfairy <39211915+windyfairy@users.noreply.github.com>
Date: Sat May 12 17:00:25 2018 +0900
Initial release
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..13a9b2e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,330 @@
+## 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
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+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/
+**/Properties/launchSettings.json
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.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
+
+# 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
+.cr/
+
+# 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/
diff --git a/README b/README
new file mode 100644
index 0000000..3fd8498
--- /dev/null
+++ b/README
@@ -0,0 +1,29 @@
+## gitadora-texbintool.exe
+Convert archives of tex images.
+```
+usage: gitadora-texbintool.exe [--no-rect/--nr] [--no-split/--ns] input_filename
+--no-rect/--nr: Don't create a rect table (Some games like Jubeat don't use the rect table)
+--no-split/--ns: Don't split images into separate images if they use the rect table
+```
+
+If you specify a .bin file as the `input_filename` then the tool will extract the textures into a folder with the same name as the .bin file.
+If you specify a folder as the `input_filename` then the tool will create a .bin file with the same name as the folder.
+
+`--no-rect` is used for .bin creation. It skips writing the rect section at the end of the bin file.
+
+`--no-split` is used during .bin extraction. Texbins can have multiple files, and within those files have multiple rects/subimages.
+This command will output the original images with a `metadata.xml` file containing the rect information.
+When creating a .bin from a folder with a `metadata.xml`, the `metadata.xml` is used to create the .bin. Any files in the folder not listed in the `metadata.xml` will be ignored.
+If you want to replace a specific subimage without modifying the original image file, you can modify the `ExternalFilename` part of the `metadata.xml` to point to the new image file while updating the X/Y (set to 0) and updating the W/H (set as required).
+
+## gitadora-textool.exe
+Convert individual tex files.
+```
+usage: gitadora-textool.exe input_filename
+```
+
+If you specify a .tex file as the `input_filename` then the tool will convert the .tex to .png.
+
+If you specify a non-.tex file as the `input_filename` then the tool will try to convert the image file to .tex.
+The tool uses C#'s Bitmap class to load images, so any format supported normally by C# should work (PNG, JPG, BMP, etc).
+PNG is the only "officially" supported format but JPG should be safe as well, and probably others too.
diff --git a/gitadora-texbintool/App.config b/gitadora-texbintool/App.config
new file mode 100644
index 0000000..731f6de
--- /dev/null
+++ b/gitadora-texbintool/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gitadora-texbintool/Program.cs b/gitadora-texbintool/Program.cs
new file mode 100644
index 0000000..5e58777
--- /dev/null
+++ b/gitadora-texbintool/Program.cs
@@ -0,0 +1,693 @@
+using System;
+using System.CodeDom;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Serialization;
+using gitadora_textool;
+
+namespace gitadora_texbintool
+{
+ public class EntryInfo
+ {
+ public int Id;
+ public int Hash;
+ public int Offset;
+ public string Filename;
+
+ // For data entry
+ public int Unk1;
+ public int CompSize;
+ public int DataOffset;
+ }
+
+ public struct RectMetadata
+ {
+ public int ImageId;
+ public ushort X;
+ public ushort Y;
+ public ushort W;
+ public ushort H;
+ public EntryInfo Entry;
+ }
+
+ public class TexInfo
+ {
+ public List RectInfo;
+
+ public TexInfo()
+ {
+ RectInfo = new List();
+ }
+ }
+
+ class Program
+ {
+ static int ReadInt32(BinaryReader reader)
+ {
+ var data = reader.ReadBytes(4);
+ Array.Reverse(data);
+ return BitConverter.ToInt32(data, 0);
+ }
+
+ static byte[] Decompress(BinaryReader reader)
+ {
+ var decompSize = ReadInt32(reader);
+ var compSize = ReadInt32(reader);
+
+ if (compSize == 0)
+ {
+ return reader.ReadBytes(decompSize);
+ }
+
+ var compData = reader.ReadBytes(compSize);
+
+ byte[] windowData = new byte[4096];
+ byte[] outputData = new byte[decompSize];
+ int compOffset = 0, decompOffset = 0, window = 4078;
+
+ uint controlByte = 0;
+ int bitCount = 0;
+
+ while (true)
+ {
+ if (bitCount == 0)
+ {
+ if (compOffset >= compSize)
+ {
+ //Console.WriteLine($"compOffset >= compSize: {compOffset} >= {compSize}");
+ break;
+ }
+
+ controlByte = compData[compOffset++];
+ bitCount = 8;
+ }
+
+ if ((controlByte & 0x01) != 0)
+ {
+ if (compOffset >= compSize)
+ {
+ //Console.WriteLine($"compOffset >= compSize: {compOffset} >= {compSize}");
+ break;
+ }
+
+ outputData[decompOffset] = windowData[window] = compData[compOffset];
+ decompOffset++;
+ window++;
+ compOffset++;
+
+ if (decompOffset >= decompSize)
+ {
+ //Console.WriteLine($"decompOffset >= decompSize: {decompOffset} >= {decompSize}");
+ break;
+ }
+
+ window &= 0xfff;
+ }
+ else
+ {
+ if (decompOffset >= decompSize - 1)
+ {
+ //Console.WriteLine($"decompOffset >= decompSize - 1: {decompOffset} >= {decompSize} - 1");
+ break;
+ }
+
+ var slideOffset = (((compData[compOffset + 1] & 0xf0) << 4) | compData[compOffset]) & 0xfff;
+ var slideLength = (compData[compOffset + 1] & 0x0f) + 3;
+ compOffset += 2;
+
+ if (decompOffset + slideLength > decompSize)
+ {
+ slideLength = decompSize - decompOffset;
+ }
+
+ //Console.WriteLine("{0:x8} {1:x8}", slideOffset, slideLength);
+
+ while (slideLength > 0)
+ {
+ outputData[decompOffset] = windowData[window] = windowData[slideOffset];
+ decompOffset++;
+ window++;
+ slideOffset++;
+
+ window &= 0xfff;
+ slideOffset &= 0xfff;
+ slideLength--;
+ }
+ }
+
+ controlByte >>= 1;
+ bitCount--;
+ }
+
+ return outputData;
+ }
+
+ static int CalculateHash(string input)
+ {
+ int hash = 0;
+
+ foreach (var c in input)
+ {
+ for (int i = 0; i <= 5; i++)
+ {
+ hash = (hash >> 31) & 0x4C11DB7 ^ ((hash << 1) | ((c >> i) & 1));
+ }
+ }
+
+ return hash;
+ }
+
+ static void ReadDataEntrySection(BinaryReader reader, int offset, Int64 count, List entries)
+ {
+ reader.BaseStream.Seek(offset, SeekOrigin.Begin);
+
+ for (var i = 0; i < count; i++)
+ {
+ entries[i].Unk1 = reader.ReadInt32();
+ entries[i].CompSize = reader.ReadInt32();
+ entries[i].DataOffset = reader.ReadInt32();
+ }
+ }
+
+ static List ReadNameSection(BinaryReader reader, int offset)
+ {
+ reader.BaseStream.Seek(offset, SeekOrigin.Begin);
+
+ var nampMagic = Encoding.ASCII.GetString(reader.ReadBytes(4));
+ if (nampMagic != "PMAN")
+ {
+ Console.WriteLine("Not a valid name section");
+ Environment.Exit(1);
+ }
+
+ var nampSectionSize = reader.ReadInt32();
+ var unk1 = reader.ReadBytes(8);
+ var fileCount = reader.ReadInt32();
+ var unk2 = reader.ReadBytes(8);
+
+ var stringMetadata = new List();
+ for (int i = 0; i < fileCount; i++)
+ {
+ int hash = reader.ReadInt32();
+ int id = reader.ReadInt32();
+ int strOffset = reader.ReadInt32();
+
+ var backupOffset = reader.BaseStream.Position;
+
+ reader.BaseStream.Seek(offset + strOffset, SeekOrigin.Begin);
+ var strBytes = new List();
+ while (reader.PeekChar() != 0)
+ {
+ strBytes.Add(reader.ReadByte());
+ }
+
+ var str = Encoding.ASCII.GetString(strBytes.ToArray());
+
+ stringMetadata.Add(new EntryInfo() { Offset = strOffset, Id = id, Hash = hash, Filename = str });
+
+ reader.BaseStream.Seek(backupOffset, SeekOrigin.Begin);
+ }
+
+ stringMetadata.Sort((x, y) => x.Id.CompareTo(y.Id));
+
+ return stringMetadata;
+ }
+
+ static List ReadRectEntrySection(BinaryReader reader, int offset)
+ {
+ reader.BaseStream.Seek(offset, SeekOrigin.Begin);
+
+ var rectMagic = Encoding.ASCII.GetString(reader.ReadBytes(4));
+ if (rectMagic != "TCER")
+ {
+ Console.WriteLine("Not a valid rect section");
+ Environment.Exit(1);
+ }
+
+ var unk1 = reader.ReadInt32();
+ var unk2 = reader.ReadInt32();
+ var rectSectionSize = reader.ReadInt32();
+ var layerCount = reader.ReadInt32();
+ var namOffset = reader.ReadInt32();
+ var rectOffset = reader.ReadInt32();
+
+ var stringMetadata = ReadNameSection(reader, offset + namOffset);
+
+ reader.BaseStream.Seek(offset + rectOffset, SeekOrigin.Begin);
+
+ var rectInfoMetadata = new List();
+ for (int i = 0; i < layerCount; i++)
+ {
+ var rect = new RectMetadata();
+ rect.ImageId = reader.ReadInt32();
+ rect.X = reader.ReadUInt16();
+ rect.W = (ushort)(reader.ReadUInt16() - rect.X);
+ rect.Y = reader.ReadUInt16();
+ rect.H = (ushort)(reader.ReadUInt16() - rect.Y);
+ rect.Entry = stringMetadata[i];
+ rectInfoMetadata.Add(rect);
+
+ Console.WriteLine("{0:x4}x{1:x4} {2:x4}x{3:x4}", rect.X, rect.Y, rect.W, rect.H);
+ }
+
+ return rectInfoMetadata;
+ }
+
+ public static string Serialize(T value, string outputFilename = null)
+ {
+ if (value == null)
+ {
+ return string.Empty;
+ }
+ try
+ {
+ var xmlserializer = new XmlSerializer(typeof(T));
+
+ XmlWriterSettings settings = new XmlWriterSettings();
+ settings.Indent = true;
+ settings.IndentChars = ("\t");
+
+ XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
+ namespaces.Add(string.Empty, string.Empty);
+
+ if (outputFilename != null)
+ {
+ using (var writer = XmlWriter.Create(outputFilename, settings))
+ {
+ xmlserializer.Serialize(writer, value, namespaces);
+ }
+ }
+
+ var stringWriter = new StringWriter();
+
+ using (var writer = XmlWriter.Create(stringWriter, settings))
+ {
+ xmlserializer.Serialize(writer, value, namespaces);
+ return stringWriter.ToString();
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new Exception("An error occurred", ex);
+ }
+ }
+
+ public static TexInfo Deserialize(string filename)
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof(TexInfo));
+
+ StreamReader reader = new StreamReader(filename);
+ var texInfoList = (TexInfo)serializer.Deserialize(reader);
+ reader.Close();
+
+ for (var index = 0; index < texInfoList.RectInfo.Count; index++)
+ {
+ texInfoList.RectInfo[index] = new RectInfo
+ {
+ ExternalFilename = texInfoList.RectInfo[index].ExternalFilename,
+ Filename = texInfoList.RectInfo[index].Filename,
+ X = texInfoList.RectInfo[index].X,
+ Y = texInfoList.RectInfo[index].Y,
+ W = (ushort)(texInfoList.RectInfo[index].X + texInfoList.RectInfo[index].W),
+ H = (ushort)(texInfoList.RectInfo[index].Y + texInfoList.RectInfo[index].H),
+ };
+ }
+
+ return texInfoList;
+ }
+
+ static void ParseTexbinFile(string filename, bool splitImages = true)
+ {
+ var outputPath = Path.Combine(Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(filename));
+ Directory.CreateDirectory(outputPath);
+
+ using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open)))
+ {
+ var texpMagic = Encoding.ASCII.GetString(reader.ReadBytes(4));
+
+ if (texpMagic != "PXET")
+ {
+ Console.WriteLine("Not a valid texbin file");
+ Environment.Exit(1);
+ }
+
+ var unk1 = reader.ReadInt32();
+ var unk2 = reader.ReadInt32();
+ var archiveSize = reader.ReadInt32();
+ var unk3 = reader.ReadInt32();
+ var fileCount = reader.ReadInt64();
+ var dataOffset = reader.ReadInt32();
+ var rectOffset = reader.ReadInt32();
+ var unk4 = reader.ReadBytes(0x10);
+ var nameOffset = reader.ReadInt32();
+ var unk5 = reader.ReadInt32();
+ var dataEntryOffset = reader.ReadInt32();
+
+ if (fileCount == 0)
+ {
+ Console.WriteLine("This file doesn't contain any image data.");
+ return;
+ }
+
+ var entries = ReadNameSection(reader, nameOffset);
+ ReadDataEntrySection(reader, dataEntryOffset, fileCount, entries);
+
+ var texInfo = new TexInfo();
+ if (rectOffset != 0)
+ {
+ var rectInfo = ReadRectEntrySection(reader, rectOffset);
+ foreach (var rect in rectInfo)
+ {
+ var e = new RectInfo
+ {
+ ExternalFilename = entries[rect.ImageId].Filename,
+ Filename = rect.Entry.Filename,
+ X = rect.X,
+ Y = rect.Y,
+ W = rect.W,
+ H = rect.H
+ };
+ texInfo.RectInfo.Add(e);
+ }
+
+ // Add code to optionally not split texture files and save a metadata file instead
+ if (!splitImages)
+ {
+ Serialize(texInfo, Path.Combine(outputPath, "_metadata.xml"));
+ }
+ }
+
+ foreach (var entry in entries)
+ {
+ reader.BaseStream.Seek(entry.DataOffset, SeekOrigin.Begin);
+
+ var data = Decompress(reader);
+
+ if (Encoding.ASCII.GetString(data, 0, 4) == "TXDT"
+ || Encoding.ASCII.GetString(data, 0, 4) == "TDXT")
+ {
+ var rectInfoList = texInfo.RectInfo.Where(x =>
+ String.CompareOrdinal(Path.GetFileNameWithoutExtension(entry.Filename),
+ x.ExternalFilename) == 0).ToList();
+
+ if (!splitImages)
+ {
+ rectInfoList.Clear();
+ }
+
+ if (rectInfoList.Count == 0)
+ {
+ var rectInfo = new RectInfo
+ {
+ ExternalFilename = entry.Filename,
+ Filename = entry.Filename,
+ X = 0,
+ Y = 0,
+ W = 0,
+ H = 0
+ };
+ rectInfoList.Add(rectInfo);
+ }
+
+ foreach (var rectInfo in rectInfoList)
+ {
+ try
+ {
+ using (var stream = new MemoryStream(data))
+ {
+ using (var dataReader = new BinaryReader(stream))
+ {
+ byte[] extractedData;
+
+
+ if (!splitImages || rectInfoList.Count == 0 || rectInfo.W == 0 || rectInfo.H == 0)
+ {
+ extractedData = gitadora_textool.Program.ExtractImageCore(dataReader, null);
+ }
+ else
+ {
+ extractedData = gitadora_textool.Program.ExtractImageCore(dataReader, rectInfo);
+ }
+
+ var ext = ".png";
+ if (extractedData[0] == 'D' && extractedData[1] == 'D' && extractedData[2] == 'S' && extractedData[3] == ' ')
+ {
+ ext = ".dds";
+ }
+
+ var outputFilename = Path.Combine(outputPath, rectInfo.Filename);
+ outputFilename += ext;
+
+ Console.WriteLine("Saving {0}...", outputFilename);
+
+ File.WriteAllBytes(outputFilename, extractedData);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Couldn't convert image: {0}", e.Message);
+ File.WriteAllBytes(Path.Combine(outputPath, entry.Filename), data);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ static List CreateNameSection(List filelist_unique)
+ {
+ var nameSection = new List();
+ var filenameSectionSize = 0x1c + (filelist_unique.Count * 0x0c) + filelist_unique.Select(x => x.Length + 1).Sum();
+ if ((filenameSectionSize % 4) != 0)
+ filenameSectionSize += 4 - (filenameSectionSize % 4);
+
+ nameSection.AddRange(new byte[] { 0x50, 0x4D, 0x41, 0x4E });
+ nameSection.AddRange(BitConverter.GetBytes(filenameSectionSize));
+ nameSection.AddRange(new byte[] { 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00 });
+ nameSection.AddRange(BitConverter.GetBytes(filelist_unique.Count));
+
+ ushort a = (ushort)(1 << Convert.ToString(filelist_unique.Count >> 1, 2).Length - 1);
+ ushort b = (ushort)((1 << Convert.ToString(filelist_unique.Count >> 1, 2).Length) - 1);
+
+ nameSection.AddRange(BitConverter.GetBytes(a));
+ nameSection.AddRange(BitConverter.GetBytes(b));
+ nameSection.AddRange(BitConverter.GetBytes(nameSection.Count + 4));
+
+ List hashes = filelist_unique.Select(x => (uint)CalculateHash(x)).OrderBy(x => x).ToList();
+ string[] filelist_sorted = new string[filelist_unique.Count];
+
+ foreach (var filename in filelist_unique)
+ {
+ filelist_sorted[hashes.IndexOf((uint)CalculateHash(filename))] = filename;
+ }
+
+ int nameBaseOffsetBase = nameSection.Count + (filelist_unique.Count * 0x0c);
+ for (int i = 0; i < filelist_sorted.Length; i++)
+ {
+ var filelist_idx = filelist_unique.IndexOf(filelist_sorted[i]);
+
+ nameSection.AddRange(BitConverter.GetBytes(CalculateHash(filelist_sorted[i])));
+ nameSection.AddRange(BitConverter.GetBytes(filelist_idx));
+
+ var nameBaseOffset = nameBaseOffsetBase;
+ for (int j = 0; j < filelist_idx; j++)
+ {
+ nameBaseOffset += filelist_unique[j].Length + 1;
+ }
+
+ nameSection.AddRange(BitConverter.GetBytes(nameBaseOffset));
+ }
+
+ for (int i = 0; i < filelist_unique.Count; i++)
+ {
+ nameSection.AddRange(Encoding.ASCII.GetBytes(filelist_unique[i]));
+ nameSection.Add(0);
+ }
+
+ while (nameSection.Count < filenameSectionSize)
+ {
+ nameSection.Add(0);
+ }
+
+ return nameSection;
+ }
+
+ static void CreateTexbinFile(string pathname, bool generateRectSection = true)
+ {
+ var filelist = Directory.GetFiles(pathname).Where(x => !x.ToLower().EndsWith("_metadata.xml")).ToArray();
+ var filelist_unique = filelist.Select(Path.GetFileNameWithoutExtension).Distinct().Where(x => !x.ToLower().EndsWith("_metadata.xml")).ToList();
+ filelist_unique = filelist_unique.Select(x => x.ToUpper()).ToList();
+
+ if (filelist_unique.Count != filelist.Length)
+ {
+ Console.WriteLine("Folder has more files than expected. Are there multiple files with the same name (not including extension)?");
+ Environment.Exit(1);
+ }
+
+ var nameSection = CreateNameSection(filelist_unique);
+
+ var dataSection = new List();
+ var fileinfoSection = new List();
+ var imageRectInfo = new Dictionary>();
+ for (int i = 0; i < filelist_unique.Count; i++)
+ {
+ var data = File.ReadAllBytes(filelist[i]);
+
+ Console.WriteLine("Adding {0}...", filelist[i]);
+
+ if (!data.Take(4).SequenceEqual(new byte[] { 0x54, 0x58, 0x44, 0x54 })
+ && !data.Take(4).SequenceEqual(new byte[] { 0x54, 0x44, 0x58, 0x54 }))
+ {
+ data = gitadora_textool.Program.CreateImageCore(data, true);
+ }
+
+ fileinfoSection.AddRange(BitConverter.GetBytes(0));
+ fileinfoSection.AddRange(BitConverter.GetBytes(data.Length + 0x08));
+ fileinfoSection.AddRange(BitConverter.GetBytes(0x40 + nameSection.Count + (filelist_unique.Count * 0x0c) + dataSection.Count));
+
+ dataSection.AddRange(BitConverter.GetBytes(data.Length).Reverse());
+ dataSection.AddRange(BitConverter.GetBytes(0));
+ dataSection.AddRange(data);
+
+ imageRectInfo[filelist_unique[i]] = new Tuple((ushort)((data[0x11] << 8) | data[0x10]), (ushort)((data[0x13] << 8) | data[0x12]));
+ }
+
+ if ((dataSection.Count % 4) != 0)
+ {
+ var padding = 4 - (dataSection.Count % 4);
+ while (padding > 0)
+ {
+ dataSection.Add(0);
+ padding--;
+ }
+ }
+
+ var rectSection = new List();
+ if (generateRectSection)
+ {
+ var rectInfo = new TexInfo();
+
+ if (File.Exists(Path.Combine(pathname, "_metadata.xml")))
+ {
+ rectInfo = Deserialize(Path.Combine(pathname, "_metadata.xml"));
+ }
+ else
+ {
+ foreach (var filename in filelist_unique)
+ {
+ var data = new RectInfo
+ {
+ ExternalFilename = filename,
+ Filename = filename,
+ X = 0,
+ Y = 0,
+ W = imageRectInfo[filename].Item1,
+ H = imageRectInfo[filename].Item2
+ };
+ rectInfo.RectInfo.Add(data);
+ }
+ }
+
+ var rectNameFilelist = rectInfo.RectInfo.Select(x => x.Filename)
+ .Select(Path.GetFileNameWithoutExtension).Distinct()
+ .Where(x => !x.ToLower().EndsWith("_metadata.xml")).ToList();
+
+ var rectinfoSection = new List();
+ var rectNameSection = CreateNameSection(rectNameFilelist);
+ foreach (var data in rectInfo.RectInfo)
+ {
+ rectinfoSection.AddRange(BitConverter.GetBytes(filelist_unique.IndexOf(Path.GetFileNameWithoutExtension(data.ExternalFilename))));
+ rectinfoSection.AddRange(BitConverter.GetBytes(data.X));
+ rectinfoSection.AddRange(BitConverter.GetBytes(data.W));
+ rectinfoSection.AddRange(BitConverter.GetBytes(data.Y));
+ rectinfoSection.AddRange(BitConverter.GetBytes(data.H));
+ }
+
+ rectSection.AddRange(
+ new byte[] { 0x54, 0x43, 0x45, 0x52, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00 });
+ rectSection.AddRange(BitConverter.GetBytes(0x1c + rectNameSection.Count + rectinfoSection.Count));
+ rectSection.AddRange(BitConverter.GetBytes(rectNameFilelist.Count));
+ rectSection.AddRange(BitConverter.GetBytes(0x1c));
+ rectSection.AddRange(BitConverter.GetBytes(0x1c + rectNameSection.Count));
+ rectSection.AddRange(rectNameSection);
+ rectSection.AddRange(rectinfoSection);
+ }
+
+ var outputData = new List();
+ outputData.AddRange(new byte[] { 0x50, 0x58, 0x45, 0x54, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00 });
+ outputData.AddRange(BitConverter.GetBytes(0x40 + nameSection.Count + fileinfoSection.Count + dataSection.Count + rectSection.Count)); // Archive size
+ outputData.AddRange(BitConverter.GetBytes(1));
+ outputData.AddRange(BitConverter.GetBytes(filelist_unique.Count));
+ outputData.AddRange(BitConverter.GetBytes(0));
+ outputData.AddRange(BitConverter.GetBytes(0x40 + nameSection.Count + fileinfoSection.Count));
+ outputData.AddRange(BitConverter.GetBytes(rectSection.Count > 0 ? 0x40 + nameSection.Count + fileinfoSection.Count + dataSection.Count : 0));
+ outputData.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
+ outputData.AddRange(BitConverter.GetBytes(0x40)); // PMAN section offset
+ outputData.AddRange(BitConverter.GetBytes(0));
+ outputData.AddRange(BitConverter.GetBytes(0x40 + nameSection.Count));
+ outputData.AddRange(nameSection);
+ outputData.AddRange(fileinfoSection);
+ outputData.AddRange(dataSection);
+ outputData.AddRange(rectSection);
+
+ var basePath = Path.GetFileName(pathname);
+ if (String.IsNullOrWhiteSpace(basePath))
+ basePath = pathname.Replace(".\\", "").Replace("\\", "");
+
+ var outputFilename = Path.Combine(Path.GetDirectoryName(pathname), String.Format("{0}.bin", basePath));
+ File.WriteAllBytes(outputFilename, outputData.ToArray());
+ }
+
+ static void Main(string[] args)
+ {
+ if (args.Length <= 0)
+ {
+ Console.WriteLine("usage: {0} [--no-rect/--nr] [--no-split/--ns] input_filename", AppDomain.CurrentDomain.FriendlyName);
+ Console.WriteLine("--no-rect/--nr: Don't create a rect table (Some games like Jubeat don't use the rect table)");
+ Console.WriteLine("--no-split/--ns: Don't split images into separate images if they use the rect table");
+ Environment.Exit(1);
+ }
+
+ var splitImage = true;
+ var generateRectSection = true;
+
+ var filenames = new List();
+ for (var index = 0; index < args.Length; index++)
+ {
+ if (String.CompareOrdinal(args[index].ToLower(), "--no-rect") == 0
+ || String.CompareOrdinal(args[index].ToLower(), "--nr") == 0)
+ {
+ generateRectSection = false;
+ }
+ else if (String.CompareOrdinal(args[index].ToLower(), "--no-split") == 0
+ || String.CompareOrdinal(args[index].ToLower(), "--ns") == 0)
+ {
+ splitImage = false;
+ }
+ else
+ {
+ filenames.Add(args[index]);
+ }
+ }
+
+ foreach (var filename in filenames)
+ {
+ if (Directory.Exists(filename))
+ {
+ CreateTexbinFile(filename, generateRectSection);
+ }
+ else if (File.Exists(filename))
+ {
+ ParseTexbinFile(filename, splitImage);
+ }
+ }
+ }
+ }
+}
diff --git a/gitadora-texbintool/Properties/AssemblyInfo.cs b/gitadora-texbintool/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..59bb22c
--- /dev/null
+++ b/gitadora-texbintool/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("gitadora-texbintool")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("gitadora-texbintool")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("14f21197-8ff1-4a4f-b976-aaa55378fade")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/gitadora-texbintool/gitadora-texbintool.csproj b/gitadora-texbintool/gitadora-texbintool.csproj
new file mode 100644
index 0000000..b37e7ac
--- /dev/null
+++ b/gitadora-texbintool/gitadora-texbintool.csproj
@@ -0,0 +1,58 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {14F21197-8FF1-4A4F-B976-AAA55378FADE}
+ Exe
+ gitadora_texbintool
+ gitadora-texbintool
+ v4.6.1
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ none
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {8c5cc09a-436c-4caa-9213-31a4f351dbdc}
+ gitadora-textool
+
+
+
+
\ No newline at end of file
diff --git a/gitadora-textool.sln b/gitadora-textool.sln
new file mode 100644
index 0000000..b91decf
--- /dev/null
+++ b/gitadora-textool.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27130.2010
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gitadora-textool", "gitadora-textool\gitadora-textool.csproj", "{8C5CC09A-436C-4CAA-9213-31A4F351DBDC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gitadora-texbintool", "gitadora-texbintool\gitadora-texbintool.csproj", "{14F21197-8FF1-4A4F-B976-AAA55378FADE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8C5CC09A-436C-4CAA-9213-31A4F351DBDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C5CC09A-436C-4CAA-9213-31A4F351DBDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C5CC09A-436C-4CAA-9213-31A4F351DBDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C5CC09A-436C-4CAA-9213-31A4F351DBDC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {14F21197-8FF1-4A4F-B976-AAA55378FADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {14F21197-8FF1-4A4F-B976-AAA55378FADE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {14F21197-8FF1-4A4F-B976-AAA55378FADE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {14F21197-8FF1-4A4F-B976-AAA55378FADE}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5EDE6B8F-78F2-46C0-A47E-D3A53277210A}
+ EndGlobalSection
+EndGlobal
diff --git a/gitadora-textool/App.config b/gitadora-textool/App.config
new file mode 100644
index 0000000..731f6de
--- /dev/null
+++ b/gitadora-textool/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gitadora-textool/DxtUtil.cs b/gitadora-textool/DxtUtil.cs
new file mode 100644
index 0000000..a1bc61b
--- /dev/null
+++ b/gitadora-textool/DxtUtil.cs
@@ -0,0 +1,460 @@
+// #region License
+// /*
+// Microsoft Public License (Ms-PL)
+// MonoGame - Copyright © 2009 The MonoGame Team
+//
+// All rights reserved.
+//
+// This license governs use of the accompanying software. If you use the software, you accept this license. If you do not
+// accept the license, do not use the software.
+//
+// 1. Definitions
+// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under
+// U.S. copyright law.
+//
+// A "contribution" is the original software, or any additions or changes to the software.
+// A "contributor" is any person that distributes its contribution under this license.
+// "Licensed patents" are a contributor's patent claims that read directly on its contribution.
+//
+// 2. Grant of Rights
+// (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
+// each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
+// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
+// each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
+//
+// 3. Conditions and Limitations
+// (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
+// (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
+// your patent license from such contributor to the software ends automatically.
+// (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution
+// notices that are present in the software.
+// (D) If you distribute any portion of the software in source code form, you may do so only under this license by including
+// a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object
+// code form, you may only do so under a license that complies with this license.
+// (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees
+// or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent
+// permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular
+// purpose and non-infringement.
+// */
+// #endregion License
+//
+using System;
+using System.IO;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+ internal static class DxtUtil
+ {
+ internal static byte[] DecompressDxt1(byte[] imageData, int width, int height)
+ {
+ using (MemoryStream imageStream = new MemoryStream(imageData))
+ return DecompressDxt1(imageStream, width, height);
+ }
+
+ internal static byte[] DecompressDxt1(Stream imageStream, int width, int height)
+ {
+ byte[] imageData = new byte[width * height * 4];
+
+ using (BinaryReader imageReader = new BinaryReader(imageStream))
+ {
+ int blockCountX = (width + 3) / 4;
+ int blockCountY = (height + 3) / 4;
+
+ for (int y = 0; y < blockCountY; y++)
+ {
+ for (int x = 0; x < blockCountX; x++)
+ {
+ DecompressDxt1Block(imageReader, x, y, blockCountX, width, height, imageData);
+ }
+ }
+ }
+
+ return imageData;
+ }
+
+ private static void DecompressDxt1Block(BinaryReader imageReader, int x, int y, int blockCountX, int width, int height, byte[] imageData)
+ {
+ ushort c0 = imageReader.ReadUInt16();
+ ushort c1 = imageReader.ReadUInt16();
+
+ byte r0, g0, b0;
+ byte r1, g1, b1;
+ ConvertRgb565ToRgb888(c0, out r0, out g0, out b0);
+ ConvertRgb565ToRgb888(c1, out r1, out g1, out b1);
+
+ uint lookupTable = imageReader.ReadUInt32();
+
+ for (int blockY = 0; blockY < 4; blockY++)
+ {
+ for (int blockX = 0; blockX < 4; blockX++)
+ {
+ byte r = 0, g = 0, b = 0, a = 255;
+ uint index = (lookupTable >> 2 * (4 * blockY + blockX)) & 0x03;
+
+ if (c0 > c1)
+ {
+ switch (index)
+ {
+ case 0:
+ r = r0;
+ g = g0;
+ b = b0;
+ break;
+ case 1:
+ r = r1;
+ g = g1;
+ b = b1;
+ break;
+ case 2:
+ r = (byte)((2 * r0 + r1) / 3);
+ g = (byte)((2 * g0 + g1) / 3);
+ b = (byte)((2 * b0 + b1) / 3);
+ break;
+ case 3:
+ r = (byte)((r0 + 2 * r1) / 3);
+ g = (byte)((g0 + 2 * g1) / 3);
+ b = (byte)((b0 + 2 * b1) / 3);
+ break;
+ }
+ }
+ else
+ {
+ switch (index)
+ {
+ case 0:
+ r = r0;
+ g = g0;
+ b = b0;
+ break;
+ case 1:
+ r = r1;
+ g = g1;
+ b = b1;
+ break;
+ case 2:
+ r = (byte)((r0 + r1) / 2);
+ g = (byte)((g0 + g1) / 2);
+ b = (byte)((b0 + b1) / 2);
+ break;
+ case 3:
+ r = 0;
+ g = 0;
+ b = 0;
+ a = 0;
+ break;
+ }
+ }
+
+ int px = (x << 2) + blockX;
+ int py = (y << 2) + blockY;
+ if ((px < width) && (py < height))
+ {
+ int offset = ((py * width) + px) << 2;
+ imageData[offset] = r;
+ imageData[offset + 1] = g;
+ imageData[offset + 2] = b;
+ imageData[offset + 3] = a;
+ }
+ }
+ }
+ }
+
+ internal static byte[] DecompressDxt3(byte[] imageData, int width, int height)
+ {
+ using (MemoryStream imageStream = new MemoryStream(imageData))
+ return DecompressDxt3(imageStream, width, height);
+ }
+
+ internal static byte[] DecompressDxt3(Stream imageStream, int width, int height)
+ {
+ byte[] imageData = new byte[width * height * 4];
+
+ using (BinaryReader imageReader = new BinaryReader(imageStream))
+ {
+ int blockCountX = (width + 3) / 4;
+ int blockCountY = (height + 3) / 4;
+
+ for (int y = 0; y < blockCountY; y++)
+ {
+ for (int x = 0; x < blockCountX; x++)
+ {
+ DecompressDxt3Block(imageReader, x, y, blockCountX, width, height, imageData);
+ }
+ }
+ }
+
+ return imageData;
+ }
+
+ private static void DecompressDxt3Block(BinaryReader imageReader, int x, int y, int blockCountX, int width, int height, byte[] imageData)
+ {
+ byte a0 = imageReader.ReadByte();
+ byte a1 = imageReader.ReadByte();
+ byte a2 = imageReader.ReadByte();
+ byte a3 = imageReader.ReadByte();
+ byte a4 = imageReader.ReadByte();
+ byte a5 = imageReader.ReadByte();
+ byte a6 = imageReader.ReadByte();
+ byte a7 = imageReader.ReadByte();
+
+ ushort c0 = imageReader.ReadUInt16();
+ ushort c1 = imageReader.ReadUInt16();
+
+ byte r0, g0, b0;
+ byte r1, g1, b1;
+ ConvertRgb565ToRgb888(c0, out r0, out g0, out b0);
+ ConvertRgb565ToRgb888(c1, out r1, out g1, out b1);
+
+ uint lookupTable = imageReader.ReadUInt32();
+
+ int alphaIndex = 0;
+ for (int blockY = 0; blockY < 4; blockY++)
+ {
+ for (int blockX = 0; blockX < 4; blockX++)
+ {
+ byte r = 0, g = 0, b = 0, a = 0;
+
+ uint index = (lookupTable >> 2 * (4 * blockY + blockX)) & 0x03;
+
+ switch (alphaIndex)
+ {
+ case 0:
+ a = (byte)((a0 & 0x0F) | ((a0 & 0x0F) << 4));
+ break;
+ case 1:
+ a = (byte)((a0 & 0xF0) | ((a0 & 0xF0) >> 4));
+ break;
+ case 2:
+ a = (byte)((a1 & 0x0F) | ((a1 & 0x0F) << 4));
+ break;
+ case 3:
+ a = (byte)((a1 & 0xF0) | ((a1 & 0xF0) >> 4));
+ break;
+ case 4:
+ a = (byte)((a2 & 0x0F) | ((a2 & 0x0F) << 4));
+ break;
+ case 5:
+ a = (byte)((a2 & 0xF0) | ((a2 & 0xF0) >> 4));
+ break;
+ case 6:
+ a = (byte)((a3 & 0x0F) | ((a3 & 0x0F) << 4));
+ break;
+ case 7:
+ a = (byte)((a3 & 0xF0) | ((a3 & 0xF0) >> 4));
+ break;
+ case 8:
+ a = (byte)((a4 & 0x0F) | ((a4 & 0x0F) << 4));
+ break;
+ case 9:
+ a = (byte)((a4 & 0xF0) | ((a4 & 0xF0) >> 4));
+ break;
+ case 10:
+ a = (byte)((a5 & 0x0F) | ((a5 & 0x0F) << 4));
+ break;
+ case 11:
+ a = (byte)((a5 & 0xF0) | ((a5 & 0xF0) >> 4));
+ break;
+ case 12:
+ a = (byte)((a6 & 0x0F) | ((a6 & 0x0F) << 4));
+ break;
+ case 13:
+ a = (byte)((a6 & 0xF0) | ((a6 & 0xF0) >> 4));
+ break;
+ case 14:
+ a = (byte)((a7 & 0x0F) | ((a7 & 0x0F) << 4));
+ break;
+ case 15:
+ a = (byte)((a7 & 0xF0) | ((a7 & 0xF0) >> 4));
+ break;
+ }
+ ++alphaIndex;
+
+ switch (index)
+ {
+ case 0:
+ r = r0;
+ g = g0;
+ b = b0;
+ break;
+ case 1:
+ r = r1;
+ g = g1;
+ b = b1;
+ break;
+ case 2:
+ r = (byte)((2 * r0 + r1) / 3);
+ g = (byte)((2 * g0 + g1) / 3);
+ b = (byte)((2 * b0 + b1) / 3);
+ break;
+ case 3:
+ r = (byte)((r0 + 2 * r1) / 3);
+ g = (byte)((g0 + 2 * g1) / 3);
+ b = (byte)((b0 + 2 * b1) / 3);
+ break;
+ }
+
+ int px = (x << 2) + blockX;
+ int py = (y << 2) + blockY;
+ if ((px < width) && (py < height))
+ {
+ int offset = ((py * width) + px) << 2;
+ imageData[offset] = r;
+ imageData[offset + 1] = g;
+ imageData[offset + 2] = b;
+ imageData[offset + 3] = a;
+ }
+ }
+ }
+ }
+
+ internal static byte[] DecompressDxt5(byte[] imageData, int width, int height)
+ {
+ using (MemoryStream imageStream = new MemoryStream(imageData))
+ return DecompressDxt5(imageStream, width, height);
+ }
+
+ internal static byte[] DecompressDxt5(Stream imageStream, int width, int height)
+ {
+ byte[] imageData = new byte[width * height * 4];
+
+ using (BinaryReader imageReader = new BinaryReader(imageStream))
+ {
+ int blockCountX = (width + 3) / 4;
+ int blockCountY = (height + 3) / 4;
+
+ for (int y = 0; y < blockCountY; y++)
+ {
+ for (int x = 0; x < blockCountX; x++)
+ {
+ DecompressDxt5Block(imageReader, x, y, blockCountX, width, height, imageData);
+ }
+ }
+ }
+
+ return imageData;
+ }
+
+ private static void DecompressDxt5Block(BinaryReader imageReader, int x, int y, int blockCountX, int width, int height, byte[] imageData)
+ {
+ byte alpha0 = imageReader.ReadByte();
+ byte alpha1 = imageReader.ReadByte();
+
+ ulong alphaMask = (ulong)imageReader.ReadByte();
+ alphaMask += (ulong)imageReader.ReadByte() << 8;
+ alphaMask += (ulong)imageReader.ReadByte() << 16;
+ alphaMask += (ulong)imageReader.ReadByte() << 24;
+ alphaMask += (ulong)imageReader.ReadByte() << 32;
+ alphaMask += (ulong)imageReader.ReadByte() << 40;
+
+ ushort c0 = imageReader.ReadUInt16();
+ ushort c1 = imageReader.ReadUInt16();
+
+ byte r0, g0, b0;
+ byte r1, g1, b1;
+ ConvertRgb565ToRgb888(c0, out r0, out g0, out b0);
+ ConvertRgb565ToRgb888(c1, out r1, out g1, out b1);
+
+ uint lookupTable = imageReader.ReadUInt32();
+
+ for (int blockY = 0; blockY < 4; blockY++)
+ {
+ for (int blockX = 0; blockX < 4; blockX++)
+ {
+ byte r = 0, g = 0, b = 0, a = 255;
+ uint index = (lookupTable >> 2 * (4 * blockY + blockX)) & 0x03;
+
+ uint alphaIndex = (uint)((alphaMask >> 3 * (4 * blockY + blockX)) & 0x07);
+ if (alphaIndex == 0)
+ {
+ a = alpha0;
+ }
+ else if (alphaIndex == 1)
+ {
+ a = alpha1;
+ }
+ else if (alpha0 > alpha1)
+ {
+ a = (byte)(((8 - alphaIndex) * alpha0 + (alphaIndex - 1) * alpha1) / 7);
+ }
+ else if (alphaIndex == 6)
+ {
+ a = 0;
+ }
+ else if (alphaIndex == 7)
+ {
+ a = 0xff;
+ }
+ else
+ {
+ a = (byte)(((6 - alphaIndex) * alpha0 + (alphaIndex - 1) * alpha1) / 5);
+ }
+
+ switch (index)
+ {
+ case 0:
+ r = r0;
+ g = g0;
+ b = b0;
+ break;
+ case 1:
+ r = r1;
+ g = g1;
+ b = b1;
+ break;
+ case 2:
+ r = (byte)((2 * r0 + r1) / 3);
+ g = (byte)((2 * g0 + g1) / 3);
+ b = (byte)((2 * b0 + b1) / 3);
+ break;
+ case 3:
+ r = (byte)((r0 + 2 * r1) / 3);
+ g = (byte)((g0 + 2 * g1) / 3);
+ b = (byte)((b0 + 2 * b1) / 3);
+ break;
+ }
+
+ int px = (x << 2) + blockX;
+ int py = (y << 2) + blockY;
+ if ((px < width) && (py < height))
+ {
+ int offset = ((py * width) + px) << 2;
+ imageData[offset] = r;
+ imageData[offset + 1] = g;
+ imageData[offset + 2] = b;
+ imageData[offset + 3] = a;
+ }
+ }
+ }
+ }
+
+ public static void ConvertRgb565ToRgb888(ushort color, out byte r, out byte g, out byte b)
+ {
+ int temp;
+
+ temp = (color >> 11) * 255 + 16;
+ r = (byte)((temp / 32 + temp) / 32);
+ temp = ((color & 0x07E0) >> 5) * 255 + 32;
+ g = (byte)((temp / 64 + temp) / 64);
+ temp = (color & 0x001F) * 255 + 16;
+ b = (byte)((temp / 32 + temp) / 32);
+ }
+
+ public static void ConvertArgb4444ToArgb8888(ushort color, out byte a, out byte r, out byte g, out byte b)
+ {
+ int temp;
+
+ temp = (color & 0xf000) >> 12;
+ a = (byte)(temp * 16 + temp);
+
+ temp = (color & 0x0f00) >> 8;
+ r = (byte)(temp * 16 + temp);
+
+ temp = (color & 0x00f0) >> 4;
+ g = (byte)(temp * 16 + temp);
+
+ temp = color & 0x000f;
+ b = (byte)(temp * 16 + temp);
+ }
+ }
+}
+
diff --git a/gitadora-textool/Program.cs b/gitadora-textool/Program.cs
new file mode 100644
index 0000000..d8bb021
--- /dev/null
+++ b/gitadora-textool/Program.cs
@@ -0,0 +1,530 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+using System.Linq.Expressions;
+using System.Runtime.InteropServices;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace gitadora_textool
+{
+ public struct RectInfo
+ {
+ public string ExternalFilename;
+ public string Filename;
+ public ushort X;
+ public ushort Y;
+ public ushort W;
+ public ushort H;
+ }
+
+ public class Program
+ {
+ static int ReadInt32(BinaryReader reader, bool endianness = false)
+ {
+ var data = reader.ReadBytes(4);
+ if (!endianness)
+ Array.Reverse(data);
+ return BitConverter.ToInt32(data, 0);
+ }
+
+ static int ReadInt16(BinaryReader reader, bool endianness = false)
+ {
+ var data = reader.ReadBytes(2);
+ if (!endianness)
+ Array.Reverse(data);
+ return BitConverter.ToInt16(data, 0);
+ }
+
+ static void WriteInt32(BinaryWriter writer, uint input, bool endianness = false)
+ {
+ var data = BitConverter.GetBytes(input);
+ if (!endianness)
+ Array.Reverse(data);
+ writer.Write(data);
+ }
+
+ static void WriteInt16(BinaryWriter writer, ushort input, bool endianness = false)
+ {
+ var data = BitConverter.GetBytes(input);
+ if (!endianness)
+ Array.Reverse(data);
+ writer.Write(data);
+ }
+
+
+ public static byte[] ExtractImageCore(BinaryReader reader, RectInfo? rectInfoList = null)
+ {
+
+ var magic = Encoding.ASCII.GetString(reader.ReadBytes(4));
+ var endianCheck1 = reader.ReadInt32();
+ var endianCheck2 = reader.ReadInt32();
+ var requiresEndianFix = endianCheck2 == 0x00010100;
+
+ reader.BaseStream.Seek(0x0c, SeekOrigin.Begin);
+
+ var dataSize = ReadInt32(reader, requiresEndianFix) - 0x40;
+ var width = ReadInt16(reader, requiresEndianFix);
+ var height = ReadInt16(reader, requiresEndianFix);
+
+ if (!requiresEndianFix)
+ reader.BaseStream.Seek(0x03, SeekOrigin.Current);
+
+
+ /*
+ GRAYSCALE_FORMAT 0x01
+ GRAYSCALE_FORMAT_2 0x06
+ BGR_16BIT_FORMAT 0x0C
+ BGRA_16BIT_FORMAT 0x0D
+ BGR_FORMAT 0x0E
+ BGRA_FORMAT 0x10
+ BGR_8BIT_FORMAT 0x12
+ DXT1_FORMAT 0x16
+ DXT3_FORMAT 0x18
+ DXT5_FORMAT 0x1A
+ */
+
+ var dataFormat = reader.ReadByte();
+
+ reader.BaseStream.Seek(0x40, SeekOrigin.Begin);
+ var bitmapData = reader.ReadBytes(dataSize);
+
+ var pixelFormat = PixelFormat.Undefined;
+ if (dataFormat == 0x01)
+ {
+ // GRAYSCALE_FORMAT8
+ pixelFormat = PixelFormat.Format8bppIndexed;
+ }
+ else if (dataFormat == 0x06)
+ {
+ // GRAYSCALE_FORMAT_2
+ pixelFormat = PixelFormat.Format8bppIndexed;
+ }
+ else if (dataFormat == 0x0c)
+ {
+ // BGR_16BIT_FORMAT
+ pixelFormat = PixelFormat.Format16bppRgb565;
+ }
+ else if (dataFormat == 0x0d)
+ {
+ // BGRA_16BIT_FORMAT
+ byte[] newBitmapData = new byte[width * height * 4];
+ for (int didx = 0, i = 0; i < height; i++)
+ {
+ for (var j = 0; j < width; j++, didx += 2)
+ {
+ ushort c = (ushort)((bitmapData[didx + 1] << 8) | bitmapData[didx]);
+
+ DxtUtil.ConvertArgb4444ToArgb8888(c, out var a, out var r, out var g, out var b);
+
+ newBitmapData[(j * 4) + (i * width * 4)] = a;
+ newBitmapData[(j * 4) + 1 + (i * width * 4)] = r;
+ newBitmapData[(j * 4) + 2 + (i * width * 4)] = g;
+ newBitmapData[(j * 4) + 3 + (i * width * 4)] = b;
+ }
+ }
+
+ bitmapData = newBitmapData;
+
+ pixelFormat = PixelFormat.Format32bppArgb;
+ }
+ else if (dataFormat == 0x0e)
+ {
+ // BGR_FORMAT
+ pixelFormat = PixelFormat.Format24bppRgb;
+ }
+ else if (dataFormat == 0x10)
+ {
+ // BGRA_FORMAT
+ pixelFormat = PixelFormat.Format32bppArgb;
+ }
+ else if (dataFormat == 0x12)
+ {
+ // BGR_8BIT_FORMAT
+ throw new Exception("Found BGR_8BIT_FORMAT");
+ }
+ else if (dataFormat == 0x16)
+ {
+ // DXT1_FORMAT
+ pixelFormat = PixelFormat.Format32bppArgb;
+ bitmapData = DxtUtil.DecompressDxt1(bitmapData, width, height);
+ }
+ else if (dataFormat == 0x18)
+ {
+ // DXT3_FORMAT
+ pixelFormat = PixelFormat.Format32bppArgb;
+ bitmapData = DxtUtil.DecompressDxt3(bitmapData, width, height);
+ }
+ else if (dataFormat == 0x1a)
+ {
+ // DXT5_FORMAT
+ pixelFormat = PixelFormat.Format32bppArgb;
+ bitmapData = DxtUtil.DecompressDxt5(bitmapData, width, height);
+ }
+ else
+ {
+ throw new Exception("Found unknown pixel format");
+ }
+
+ for (int i = 0; i < bitmapData.Length;)
+ {
+ if (pixelFormat == PixelFormat.Format16bppArgb1555)
+ {
+ var a = bitmapData[i] & 0x0f;
+ var r = (bitmapData[i] >> 4) & 0x0f;
+ var g = bitmapData[i + 1] & 0x0f;
+ var b = (bitmapData[i + 1] >> 4) & 0x0f;
+
+ bitmapData[i + 1] = (byte)((a << 4) | b);
+ bitmapData[i + 0] = (byte)((g << 4) | r);
+
+ i += 2;
+ }
+ else if (pixelFormat == PixelFormat.Format16bppRgb565)
+ {
+ bitmapData[i] = (byte)((bitmapData[i] & 0xc0) | (bitmapData[i] & 0x3f) >> 1);
+ i += 2;
+ }
+ else if (pixelFormat == PixelFormat.Format24bppRgb)
+ {
+ var t = bitmapData[i + 2];
+ bitmapData[i + 2] = bitmapData[i];
+ bitmapData[i] = t;
+ i += 3;
+ }
+ else if (pixelFormat == PixelFormat.Format32bppArgb)
+ {
+ var t = bitmapData[i + 2];
+ bitmapData[i + 2] = bitmapData[i];
+ bitmapData[i] = t;
+ i += 4;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (pixelFormat == PixelFormat.Undefined
+ || pixelFormat == PixelFormat.Format16bppArgb1555)
+ {
+ // Create DDS file
+ var output = new List();
+
+ output.AddRange(new byte[]
+ {
+ 0x44, 0x44, 0x53, 0x20, 0x7C, 0x00, 0x00, 0x00, 0x07, 0x10, 0x08, 0x00
+ });
+
+ output.AddRange(BitConverter.GetBytes(height));
+ output.AddRange(BitConverter.GetBytes(width));
+ output.AddRange(BitConverter.GetBytes(dataSize));
+
+ output.AddRange(new byte[]
+ {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00
+ });
+
+ if (pixelFormat == PixelFormat.Format16bppArgb1555)
+ {
+ output.AddRange(new byte[]
+ {
+ 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ });
+ }
+ else if (pixelFormat == PixelFormat.Format16bppRgb555)
+ {
+ output.AddRange(new byte[]
+ {
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00,
+ 0xE0, 0x07, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ });
+ }
+ else
+ {
+ output.AddRange(new byte[]
+ {
+ 0x04, 0x00, 0x00, 0x00
+ });
+
+ if (dataFormat == 0x16)
+ {
+ output.AddRange(Encoding.ASCII.GetBytes("DXT1"));
+ }
+ else if (dataFormat == 0x18)
+ {
+ output.AddRange(Encoding.ASCII.GetBytes("DXT3"));
+ }
+ else if (dataFormat == 0x1a)
+ {
+ output.AddRange(Encoding.ASCII.GetBytes("DXT5"));
+ }
+
+ output.AddRange(new byte[]
+ {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ });
+ }
+
+ output.AddRange(bitmapData);
+
+ return output.ToArray();
+ }
+ else
+ {
+ var b = new Bitmap(width, height, pixelFormat);
+
+ if (pixelFormat == PixelFormat.Format8bppIndexed)
+ {
+ ColorPalette palette = b.Palette;
+ Color[] entries = palette.Entries;
+ for (int i = 0; i < 256; i++)
+ {
+ Color c = Color.FromArgb((byte)i, (byte)i, (byte)i);
+ entries[i] = c;
+ }
+ b.Palette = palette;
+ }
+
+ var boundsRect = new Rectangle(0, 0, width, height);
+ BitmapData bmpData = b.LockBits(boundsRect,
+ ImageLockMode.WriteOnly,
+ b.PixelFormat);
+
+ IntPtr ptr = bmpData.Scan0;
+
+ if (pixelFormat != PixelFormat.Format24bppRgb)
+ {
+ int bytes = bmpData.Stride * b.Height;
+ Marshal.Copy(bitmapData, 0, ptr, bytes);
+ }
+ else
+ {
+ // Because things are stupid, we have to pad the lines for 24bit images ourself...
+ for (int i = 0; i < height; i++)
+ {
+ Marshal.Copy(bitmapData, i * width * 3, ptr + (bmpData.Stride * i), width * 3);
+ }
+ }
+
+ b.UnlockBits(bmpData);
+
+ // Split into separate smaller bitmap
+ if (rectInfoList != null)
+ {
+ var rect = new Rectangle(rectInfoList.Value.X, rectInfoList.Value.Y, rectInfoList.Value.W, rectInfoList.Value.H);
+ Bitmap subimage = new Bitmap(rect.Width, rect.Height);
+
+ Console.WriteLine(rect);
+
+ using (Graphics g = Graphics.FromImage(subimage))
+ {
+ g.DrawImage(b, new Rectangle(0, 0, subimage.Width, subimage.Height), rect, GraphicsUnit.Pixel);
+ }
+
+ b = subimage;
+ }
+
+ ImageConverter converter = new ImageConverter();
+ return (byte[])converter.ConvertTo(b, typeof(byte[]));
+ }
+ }
+
+ static void ExtractImage(string filename)
+ {
+ using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open)))
+ {
+ var data = ExtractImageCore(reader);
+
+ var ext = "png";
+ if (data[0] == 'D' && data[1] == 'D' && data[2] == 'S' && data[3] == ' ')
+ {
+ ext = "dds";
+ }
+
+ string outputFilename = String.Format("{0}.{1}", Path.Combine(Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(filename)), ext);
+ Console.WriteLine(outputFilename);
+ File.WriteAllBytes(outputFilename, data);
+ }
+ }
+
+ public static byte[] CreateImageCore(byte[] data, bool requiresEndianFix = false)
+ {
+ var image = Bitmap.FromStream(new MemoryStream(data));
+ var bmpOrig = new Bitmap(image);
+
+ var pixelFormat = image.PixelFormat;
+
+ if (pixelFormat == PixelFormat.Format8bppIndexed)
+ {
+ pixelFormat = PixelFormat.Format32bppArgb;
+ }
+
+ var bmp = new Bitmap(bmpOrig.Width, bmpOrig.Height, pixelFormat);
+ using (Graphics gr = Graphics.FromImage(bmp))
+ {
+ gr.DrawImage(bmpOrig, new Rectangle(0, 0, bmp.Width, bmp.Height));
+ }
+
+ var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly,
+ pixelFormat);
+
+ IntPtr ptr = bmpData.Scan0;
+ int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
+ byte[] rawData = new byte[bytes];
+
+ Marshal.Copy(ptr, rawData, 0, bytes);
+
+ bmp.UnlockBits(bmpData);
+
+ var stream = new MemoryStream();
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ Func writeBytes = delegate (byte[] inputData)
+ {
+ writer.Write(requiresEndianFix ? inputData.Reverse().ToArray() : inputData);
+ return true;
+ };
+
+ writeBytes(new byte[] { 0x54, 0x58, 0x44, 0x54 });
+ if (requiresEndianFix)
+ {
+ writeBytes(new byte[] { 0x00, 0x01, 0x00, 0x00 });
+ writeBytes(new byte[] { 0x00, 0x01, 0x01, 0x00 });
+ }
+ else
+ {
+ writeBytes(new byte[] { 0x00, 0x01, 0x02, 0x00 });
+ writeBytes(new byte[] { 0x00, 0x01, 0x02, 0x00 });
+ }
+
+ WriteInt32(writer, (uint)(rawData.Length + 0x40), requiresEndianFix);
+ WriteInt16(writer, (ushort)(bmp.Width), requiresEndianFix);
+ WriteInt16(writer, (ushort)(bmp.Height), requiresEndianFix);
+
+ if (bmp.PixelFormat == PixelFormat.Format24bppRgb)
+ {
+ if (requiresEndianFix)
+ {
+ writeBytes(new byte[] { 0x11, 0x22, 0x10, 0x0E });
+ }
+ else
+ {
+ writeBytes(new byte[] { 0x11, 0x11, 0x10, 0x0E });
+ }
+ }
+ else if (bmp.PixelFormat == PixelFormat.Format32bppArgb)
+ {
+ if (requiresEndianFix)
+ {
+ writeBytes(new byte[] { 0x11, 0x22, 0x10, 0x10 });
+ }
+ else
+ {
+ writeBytes(new byte[] { 0x11, 0x11, 0x10, 0x10 });
+ }
+ }
+ else
+ {
+ Console.WriteLine("Expected 24bit or 32bit image. Don't know how to handle pixel format {0}", bmp.PixelFormat);
+ Environment.Exit(1);
+ }
+
+ for (int i = 0; i < 0x14; i++)
+ {
+ writer.Write((byte)0x00);
+ }
+
+ if (bmp.PixelFormat == PixelFormat.Format24bppRgb)
+ {
+ WriteInt32(writer, 0x01, requiresEndianFix);
+ }
+ else if (bmp.PixelFormat == PixelFormat.Format32bppArgb)
+ {
+ WriteInt32(writer, 0x03, requiresEndianFix);
+ }
+
+ for (int i = 0; i < 0x10; i++)
+ {
+ writer.Write((byte)0x00);
+ }
+
+ var bpp = bmp.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 3;
+ for (int i = 0; i < rawData.Length; i += bpp)
+ {
+ var t = rawData[i];
+ rawData[i] = rawData[i + 2];
+ rawData[i + 2] = t;
+ }
+
+ writer.Write(rawData);
+ }
+
+ var outputData = stream.GetBuffer();
+ Array.Resize(ref outputData, bytes + 0x40);
+ return outputData;
+ }
+
+ static void CreateImage(string filename)
+ {
+ string outputFilename = String.Format("{0}.tex", Path.Combine(Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(filename)));
+ Console.WriteLine(outputFilename);
+
+ var rawData = File.ReadAllBytes(filename);
+ var data = CreateImageCore(rawData, false);
+ File.WriteAllBytes(outputFilename, data);
+ }
+
+ static void Main(string[] args)
+ {
+ if (args.Length < 1)
+ {
+ Console.WriteLine("usage: {0} input_filename", AppDomain.CurrentDomain.FriendlyName);
+ return;
+ }
+
+ foreach (var filename in args)
+ {
+ var isExtract = false;
+ using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open)))
+ {
+ var magic = reader.ReadBytes(4);
+ if (magic.SequenceEqual(new byte[] { 0x54, 0x58, 0x44, 0x54 })
+ || magic.SequenceEqual(new byte[] { 0x54, 0x44, 0x58, 0x54 }))
+ {
+ isExtract = true;
+ }
+ }
+
+ try
+ {
+ if (isExtract)
+ {
+ ExtractImage(filename);
+ }
+ else
+ {
+ CreateImage(filename);
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Error occurred: {0}", e.Message);
+ Environment.Exit(1);
+ }
+ }
+ }
+ }
+}
diff --git a/gitadora-textool/Properties/AssemblyInfo.cs b/gitadora-textool/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..dee5efb
--- /dev/null
+++ b/gitadora-textool/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("gitadora-textool")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("gitadora-textool")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("8c5cc09a-436c-4caa-9213-31a4f351dbdc")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/gitadora-textool/gitadora-textool.csproj b/gitadora-textool/gitadora-textool.csproj
new file mode 100644
index 0000000..4063a58
--- /dev/null
+++ b/gitadora-textool/gitadora-textool.csproj
@@ -0,0 +1,55 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {8C5CC09A-436C-4CAA-9213-31A4F351DBDC}
+ Exe
+ gitadora_textool
+ gitadora-textool
+ v4.6.1
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ none
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file