5
0
mirror of synced 2024-11-27 16:10:50 +01:00

Initial release

This commit is contained in:
windyfairy 2018-05-12 17:00:25 +09:00
commit 8f7e17251f
12 changed files with 2270 additions and 0 deletions

330
.gitignore vendored Normal file
View File

@ -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/

29
README Normal file
View File

@ -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.

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
</configuration>

View File

@ -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> RectInfo;
public TexInfo()
{
RectInfo = new List<RectInfo>();
}
}
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<EntryInfo> 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<EntryInfo> 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<EntryInfo>();
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<byte>();
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<RectMetadata> 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<RectMetadata>();
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>(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>(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<byte> CreateNameSection(List<string> filelist_unique)
{
var nameSection = new List<byte>();
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<uint> 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<byte>();
var fileinfoSection = new List<byte>();
var imageRectInfo = new Dictionary<string, Tuple<ushort, ushort>>();
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, ushort>((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<byte>();
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<byte>();
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<byte>();
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<string>();
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);
}
}
}
}
}

View File

@ -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")]

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{14F21197-8FF1-4A4F-B976-AAA55378FADE}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>gitadora_texbintool</RootNamespace>
<AssemblyName>gitadora-texbintool</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\gitadora-textool\gitadora-textool.csproj">
<Project>{8c5cc09a-436c-4caa-9213-31a4f351dbdc}</Project>
<Name>gitadora-textool</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

31
gitadora-textool.sln Normal file
View File

@ -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

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
</configuration>

460
gitadora-textool/DxtUtil.cs Normal file
View File

@ -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);
}
}
}

530
gitadora-textool/Program.cs Normal file
View File

@ -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<byte>();
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<byte[], bool> 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);
}
}
}
}
}

View File

@ -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")]

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8C5CC09A-436C-4CAA-9213-31A4F351DBDC}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>gitadora_textool</RootNamespace>
<AssemblyName>gitadora-textool</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DxtUtil.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>