Skip to content

Commit

Permalink
Tile set downgrader, fixed most issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Nenkai committed Feb 18, 2024
1 parent 23f9cd8 commit 80d025e
Show file tree
Hide file tree
Showing 10 changed files with 400 additions and 30 deletions.
79 changes: 74 additions & 5 deletions GraniteTextureReader/GDEX/GDEXItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ public void Read(BinaryStream bs)
_value = bs.ReadString(StringCoding.ZeroTerminated, encoding: Encoding.Unicode);
break;
case GDEXItemType.Int32:
_value = bs.ReadInt16();
_value = bs.ReadInt32();
break;
case GDEXItemType.IntArray:
_value = bs.ReadInt16s((int)(itemSize / 4));
_value = bs.ReadInt32s((int)(itemSize / 4));
break;
case GDEXItemType.GUIDArray:
{
Expand All @@ -107,12 +107,81 @@ public void Read(BinaryStream bs)
bs.Align(0x04, grow: true);
}

public short GetShort()
public void Write(BinaryStream bs)
{
bs.WriteUInt32(Tag);
bs.WriteByte((byte)Type);
bs.WriteByte((byte)Flags);

long offsetToDataSize = bs.Position;
if (Flags.HasFlag(GDEXItemFlags.ExtendedHeader))
bs.Position += 0x06;
else
bs.Position += 0x02;

long baseDataPos = bs.Position;
switch (Type)
{
case GDEXItemType.Struct:
{
var items = _value as List<GDEXItem>;
foreach (var item in items)
item.Write(bs);
}
break;
case GDEXItemType.String:
bs.WriteString(_value as string, StringCoding.ZeroTerminated, Encoding.Unicode);
break;
case GDEXItemType.Int32:
bs.WriteInt32((int)_value);
break;
case GDEXItemType.IntArray:
{
var items = _value as int[];
foreach (var item in items)
bs.WriteInt32(item);
}
break;
case GDEXItemType.GUIDArray:
{
var items = _value as List<Guid>;
foreach (var item in items)
bs.WriteBytes(item.ToByteArray());
}
break;
default:
throw new NotSupportedException();
}
long endDataPos = bs.Position;

bs.Position = offsetToDataSize;
long itemSize = endDataPos - baseDataPos;
if (Flags.HasFlag(GDEXItemFlags.ExtendedHeader))
{
bs.WriteUInt32((uint)(itemSize & 0xFFFFFFFF));
bs.WriteUInt16((ushort)(itemSize >> 32));
}
else
bs.WriteUInt16((ushort)itemSize);

bs.Position = endDataPos;
bs.Align(0x04, grow: true);
}

public int GetInt()
{
if (Type != GDEXItemType.Int32)
throw new Exception("Item is not int type.");

return (int)_value;
}

public void SetInt(int value)
{
if (Type != GDEXItemType.Int32)
throw new Exception("Item is not short type.");
throw new Exception("Item is not int type.");

return (short)_value;
_value = value;
}

public string GetString()
Expand Down
35 changes: 25 additions & 10 deletions GraniteTextureReader/GraniteProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void Extract(int layer, string textureName = "")
if (layer > -1)
{
string realName = string.Empty;
ushort w = texture.Width, h = texture.Height;
ushort w = (ushort)texture.Width, h = (ushort)texture.Height;
uint tileStepX = 1, tileStepY = 1;
if (project is not null)
{
Expand All @@ -67,14 +67,15 @@ public void Extract(int layer, string textureName = "")
ProjectAssetLayerTexturesTexture textureLayerAsset = layerAsset.Textures.Texture;
realName = Path.GetFileName(textureLayerAsset.Src);


// Page file width and height can differ from texture. It's odd.
// Files that do this also have tiles with their id value having the upper bit set
w = textureLayerAsset.Width;
h = textureLayerAsset.Height;

// Required when texture is smaller than declared in the page file
tileStepX = (uint)texture.Width / textureLayerAsset.Width;
tileStepY = (uint)texture.Height / textureLayerAsset.Height;
tileStepX = texture.Width / textureLayerAsset.Width;
tileStepY = texture.Height / textureLayerAsset.Height;
}

Console.WriteLine($"[{i + 1}/{textures.Count}] Processing {realName} from {texture.Name} ({texture.Width}x{texture.Height}, layer {layer})");
Expand All @@ -87,7 +88,7 @@ public void Extract(int layer, string textureName = "")
for (int layerNum = 0; layerNum < 4; layerNum++)
{
string realName = string.Empty;
ushort w = texture.Width, h = texture.Height;
ushort w = (ushort)texture.Width, h = (ushort)texture.Height;
uint tileStepX = 1, tileStepY = 1;
if (project is not null)
{
Expand All @@ -101,8 +102,8 @@ public void Extract(int layer, string textureName = "")

w = textureLayerAsset.Width;
h = textureLayerAsset.Height;
tileStepX = (uint)texture.Width / textureLayerAsset.Width;
tileStepY = (uint)texture.Height / textureLayerAsset.Height;
tileStepX = texture.Width / textureLayerAsset.Width;
tileStepY = texture.Height / textureLayerAsset.Height;
}

string outputName = string.IsNullOrEmpty(realName) ? texture.Name + $"_{layerNum}" : realName;
Expand All @@ -114,7 +115,7 @@ public void Extract(int layer, string textureName = "")
}
}

public void ExtractTexture(string outputPath, ushort xOffset, ushort yOffset, ushort textureWidth, ushort textureHeight, uint tileStepX, uint tileStepY, int level, int layer)
public void ExtractTexture(string outputPath, uint xOffset, uint yOffset, ushort textureWidth, ushort textureHeight, uint tileStepX, uint tileStepY, int level, int layer)
{
uint tileWidthNoBorder = TileSet.TileWidth - (2 * TileSet.TileBorder);
uint tileHeightNoBorder = TileSet.TileHeight - (2 * TileSet.TileBorder);
Expand All @@ -128,7 +129,8 @@ public void ExtractTexture(string outputPath, ushort xOffset, ushort yOffset, us
Rgba32[] texturePixels = ArrayPool<Rgba32>.Shared.Rent(textureWidth * textureHeight);

// Layers explicitly can set a default image color.
texturePixels.AsSpan().Fill(new Rgba32(TileSet.LayerInfos[layer].DefaultColor));
LayerInfo layerInfo = TileSet.LayerInfos[layer];
texturePixels.AsSpan().Fill(new Rgba32(layerInfo.DefaultColor));

for (uint currentTileY = 0; currentTileY < numYTiles; currentTileY++)
{
Expand All @@ -139,18 +141,31 @@ public void ExtractTexture(string outputPath, ushort xOffset, ushort yOffset, us
uint tX = (currentTileX * tileStepX) + texTileOfsX;
uint tY = (currentTileY * tileStepY) + texTileOfsY;
TileInfo tileInfo = levelInfo.TileInfos[layer + TileSet.LayerInfos.Count * (tY * levelInfo.NumTilesX + tX)];
if ((tileInfo.FlatTileIndex >> 31) != 0)
{
// This bit means the current level has no data to be used and a lower level should be used.
// TODO: Actually use lower level rather than use tile stepping.
}

int outputX = (int)(currentTileX * tileWidthNoBorder);
int outputY = (int)(currentTileY * tileHeightNoBorder);

ColorRgba32[] tilePixels = GetFlatTileData(tileInfo.FlatTileIndex);
Span<Rgba32> tilePixelsRgba = MemoryMarshal.Cast<ColorRgba32, Rgba32>(tilePixels);
if (layerInfo.DataType == DataType.X8Y8Z0_TANGENT) // Normal maps, b and a is ignored
{
for (int i = 0; i < tilePixels.Length; i++)
{
tilePixels[i].b = (byte)((layerInfo.DefaultColor >> 16) & 0xFF);
tilePixels[i].a = (byte)((layerInfo.DefaultColor >> 24) & 0xFF);
}
}

Span<Rgba32> tilePixelsRgba = MemoryMarshal.Cast<ColorRgba32, Rgba32>(tilePixels);
// Copy each row to the output, faster than doing it per-pixel
for (int yRow = 0; yRow < tileHeightNoBorder; yRow++)
{
Span<Rgba32> rowPixels = tilePixelsRgba.Slice((int)(((yRow + TileSet.TileBorder) * TileSet.TileWidth) + TileSet.TileBorder), (int)tileWidthNoBorder);
Span<Rgba32> outputRow = texturePixels.AsSpan((outputY * textureWidth) + (yRow * textureHeight) + outputX, (int)tileWidthNoBorder);
Span<Rgba32> outputRow = texturePixels.AsSpan((outputY * textureWidth) + (yRow * textureWidth) + outputX, (int)tileWidthNoBorder);
rowPixels.CopyTo(outputRow);
}
}
Expand Down
4 changes: 2 additions & 2 deletions GraniteTextureReader/GraniteTextureReader.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyVersion>1.0.1.0</AssemblyVersion>
<FileVersion>1.0.1.0</FileVersion>
<AssemblyVersion>1.1.0.0</AssemblyVersion>
<FileVersion>1.1.0.0</FileVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
88 changes: 86 additions & 2 deletions GraniteTextureReader/Program.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using GraniteTextureReader.TileSet;
using GraniteTextureReader.GDEX;

using CommandLine;

namespace GraniteTextureReader;

internal class Program
{
public const string Version = "1.0.1";
public const string Version = "1.1.0";

//======================
//Main Program
Expand All @@ -21,11 +22,12 @@ static void Main(string[] args)
Console.WriteLine("- https://github.com/AlphaSatanOmega");
Console.WriteLine("---------------------------------------------");

var p = Parser.Default.ParseArguments<ExtractVerbs, ExtractAllVerbs, ExtractProjectFileVerbs>(args);
var p = Parser.Default.ParseArguments<ExtractVerbs, ExtractAllVerbs, ExtractProjectFileVerbs, DowngradeVerbs>(args);

p.WithParsed<ExtractVerbs>(ExtractSpecific)
.WithParsed<ExtractAllVerbs>(ExtractAll)
.WithParsed<ExtractProjectFileVerbs>(ExtractProjectFile)
.WithParsed<DowngradeVerbs>(Downgrade)
.WithNotParsed(HandleNotParsedArgs);
}

Expand Down Expand Up @@ -108,6 +110,78 @@ public static void ExtractProjectFile(ExtractProjectFileVerbs verbs)
}
}

public static void Downgrade(DowngradeVerbs verbs)
{
if (!File.Exists(verbs.TileSetPath))
{
Console.WriteLine($"ERROR: Tile set file '{verbs.TileSetPath}' does not exist.");
return;
}

var tileSetFile = new TileSetFile();
try
{
// Downgrade compatibility & build version. These are checked.
tileSetFile.Initialize(verbs.TileSetPath);
if (tileSetFile.Version == 5)
{
Console.WriteLine("ERROR: Tile set file is already version 5.");
return;
}

if (tileSetFile.Version < 5)
{
Console.WriteLine("ERROR: Tile set under version 5 is not supported.");
return;
}

GDEXItem info = tileSetFile.Metadata[GDEXTags.Information];
GDEXItem comp = info[GDEXTags.Compatibility];

GDEXItem compWith = comp[GDEXTags.CompatibleWith];
compWith[GDEXTags.VersionMajor].SetInt(5);
compWith[GDEXTags.VersionMinor].SetInt(0);

GDEXItem buildVersion = comp[GDEXTags.BuildVersion];
buildVersion[GDEXTags.VersionMajor].SetInt(5);
buildVersion[GDEXTags.VersionMinor].SetInt(0);

string dir = Path.GetDirectoryName(Path.GetFullPath(verbs.TileSetPath));

Console.Write("GTP files needs to be overwritten and aligned to page size. Proceed? [y/n]");
if (Console.ReadKey().Key != ConsoleKey.Y)
{
Console.WriteLine();
Console.WriteLine("Aborted.");
return;
}

// Align all GTP files to page size. Version 6 seems to have removed this requirement
foreach (var file in Directory.GetFiles(dir, "*.gtp", SearchOption.TopDirectoryOnly))
{
Console.WriteLine($"Padding {Path.GetFileName(file)} to page size, required for V5 (0x{tileSetFile.CustomPageSize:X8})");
using var fs = File.OpenWrite(file);
fs.Position = fs.Length;
Syroot.BinaryData.StreamExtensions.Align(fs, tileSetFile.CustomPageSize, grow: true);
fs.Position -= 1;
fs.WriteByte(0); // Just incase it doesn't actually add the bytes at the end of the file
}

// Output new .gts file with the appropriate version.
string outputFile = Path.Combine(dir, $"{Path.GetFileNameWithoutExtension(verbs.TileSetPath)}_v5.gts");
using (var output = new FileStream(outputFile, FileMode.Create))
tileSetFile.Write(output, 5);

Console.WriteLine($"Done, saved as '{outputFile}'.");
Console.WriteLine($"NOTE: Thumbnail data omitted as it is not yet supported, but the official tile set viewer should read the file just fine.");
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: Failed to extract from {verbs.TileSetPath} - {ex.Message}");
return;
}
}

public static void HandleNotParsedArgs(IEnumerable<Error> errors)
{

Expand Down Expand Up @@ -172,4 +246,14 @@ public class ExtractProjectFileVerbs
HelpText = "Input .gts file.")]
public string TileSetPath { get; set; }
}

[Verb("downgrade", HelpText = "Downgrades a tile set file from version 6 to 5 for use in the granite tile set viewer tool.")]
public class DowngradeVerbs
{
[Option(
't', "tileset",
Required = true,
HelpText = "Input .gts file.")]
public string TileSetPath { get; set; }
}
}
16 changes: 8 additions & 8 deletions GraniteTextureReader/Texture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ namespace GraniteTextureReader;
public class TextureDescriptor
{
public string Name { get; set; }
public ushort Width { get; set; }
public ushort Height { get; set; }
public ushort X { get; set; }
public ushort Y { get; set; }
public uint Width { get; set; }
public uint Height { get; set; }
public uint X { get; set; }
public uint Y { get; set; }

public static TextureDescriptor FromGDEXItem(GDEXItem item)
{
var texture = new TextureDescriptor();
texture.Name = item[GDEXTags.Name].GetString();
texture.Width = (ushort)item[GDEXTags.Width].GetShort();
texture.Height = (ushort)item[GDEXTags.Height].GetShort();
texture.X = (ushort)item[GDEXTags.X].GetShort();
texture.Y = (ushort)item[GDEXTags.Y].GetShort();
texture.Width = (uint)item[GDEXTags.Width].GetInt();
texture.Height = (uint)item[GDEXTags.Height].GetInt();
texture.X = (uint)item[GDEXTags.X].GetInt();
texture.Y = (uint)item[GDEXTags.Y].GetInt();
return texture;
}
}
9 changes: 9 additions & 0 deletions GraniteTextureReader/TileSet/FlatTileInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ public void Read(BinaryStream bs)
TileListOffset = bs.ReadUInt32();
}

public void Write(BinaryStream bs)
{
bs.WriteUInt16(PageFileIndex);
bs.WriteUInt16(pageIndex);
bs.WriteUInt16(TileIndex);
bs.WriteUInt16(NumTiles);
bs.WriteUInt32(TileListOffset);
}

public static uint GetSize()
{
return 0x0C;
Expand Down
6 changes: 6 additions & 0 deletions GraniteTextureReader/TileSet/LayerInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public void Read(BinaryStream bs)
DefaultColor = bs.ReadUInt32();
}

public void Write(BinaryStream bs)
{
bs.WriteUInt32((uint)DataType);
bs.WriteUInt32(DefaultColor);
}

public static uint GetSize(uint version)
{
if (version < 4 || version > 6)
Expand Down
2 changes: 1 addition & 1 deletion GraniteTextureReader/TileSet/LevelInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public void Read(BinaryStream bs, int numLayers)
bs.Position = tilesOffset;
for (int i = 0; i < TileInfos.Length; i++)
TileInfos[i] = new TileInfo(bs.ReadInt32());

}


public static uint GetSize(uint version)
{
if (version < 4 || version > 6)
Expand Down
Loading

0 comments on commit 80d025e

Please sign in to comment.