2023-05-21 19:17:25 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
using Toolbox ;
using System.Windows.Forms ;
using Toolbox.Library ;
using Toolbox.Library.IO ;
using Toolbox.Library.Forms ;
using System.Runtime.InteropServices ;
using System.Drawing ;
using System.IO ;
using static Toolbox . Library . STGenericTexture ;
using LibHac ;
using System.ComponentModel ;
using VGAudio.Utilities ;
namespace FirstPlugin
{
2023-05-28 22:59:31 +02:00
public class TXTG : STGenericTexture , IFileFormat , ILeaveOpenOnLoad , IDisposable
2023-05-21 19:17:25 +02:00
{
public FileType FileType { get ; set ; } = FileType . Image ;
public bool CanSave { get ; set ; } = true ;
public string [ ] Description { get ; set ; } = new string [ ] { "Texture To Go" } ;
public string [ ] Extension { get ; set ; } = new string [ ] { "*.txtg" } ;
public string FileName { get ; set ; }
public string FilePath { get ; set ; }
public IFileInfo IFileInfo { get ; set ; }
public TXTG ( )
{
}
public bool Identify ( System . IO . Stream stream )
{
using ( var reader = new Toolbox . Library . IO . FileReader ( stream , true ) ) {
return reader . CheckSignature ( 4 , "6PK0" , 4 ) ;
}
}
public Type [ ] Types
{
get
{
List < Type > types = new List < Type > ( ) ;
return types . ToArray ( ) ;
}
}
public override bool CanEdit { get ; set ; } = true ;
public override TEX_FORMAT [ ] SupportedFormats
{
get
{
return new TEX_FORMAT [ ]
{
TEX_FORMAT . BC1_UNORM ,
TEX_FORMAT . BC2_UNORM ,
TEX_FORMAT . BC3_UNORM ,
TEX_FORMAT . BC4_UNORM ,
TEX_FORMAT . BC5_UNORM ,
TEX_FORMAT . R8_UNORM ,
TEX_FORMAT . R8G8_UNORM ,
TEX_FORMAT . R8G8_UNORM ,
TEX_FORMAT . R10G10B10A2_UNORM ,
TEX_FORMAT . B5G6R5_UNORM ,
TEX_FORMAT . B5G5R5A1_UNORM ,
TEX_FORMAT . B4G4R4A4_UNORM ,
TEX_FORMAT . R8G8B8A8_UNORM ,
TEX_FORMAT . R8G8B8A8_UNORM_SRGB ,
} ;
}
}
public override void OnClick ( TreeView treeview )
{
UpdateEditor ( ) ;
}
private void UpdateEditor ( )
{
ImageEditorBase editor = ( ImageEditorBase ) LibraryGUI . GetActiveContent ( typeof ( ImageEditorBase ) ) ;
if ( editor = = null )
{
editor = new ImageEditorBase ( ) ;
editor . Dock = DockStyle . Fill ;
LibraryGUI . LoadEditor ( editor ) ;
}
DisplayProperties prop = new DisplayProperties ( ) ;
prop . Width = Width ;
prop . Height = Height ;
prop . MipCount = MipCount ;
prop . ArrayCount = ArrayCount ;
prop . Format = this . Format ;
prop . Hash = String . Join ( String . Empty , Array . ConvertAll ( this . HeaderInfo . Hash , x = > x . ToString ( "X2" ) ) ) ;
editor . Text = Text ;
editor . LoadProperties ( prop ) ;
editor . LoadImage ( this ) ;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class Header
{
public ushort HeaderSize = 0x50 ;
public ushort Version = 0x11 ;
public Magic Magic = "6PK0" ;
public ushort Width ;
public ushort Height ;
public ushort Depth = 1 ;
public byte MipCount ;
public byte Unknown1 = 2 ;
public byte Unknown2 = 1 ;
public ushort Padding = 0 ;
public byte FormatFlag = 0 ; //Unsure how this value works
public uint FormatSetting = 0 ; //Varies by format flag. Sometimes a second channel layout
public byte CompSelectR = 0 ;
public byte CompSelectG = 1 ;
public byte CompSelectB = 2 ;
public byte CompSelectA = 3 ;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public byte [ ] Hash ; //32 byte hash, likely made off file path. Files with identical data still have unique hashes
public ushort Format ;
public ushort Unknown3 = 0x300 ; //Always 0x300?
public uint TextureSetting1 = 1116471296 ; //00 00 8C 42
public uint TextureSetting2 = 32563 ; //Likey some setting for astc. Terrain and tree files use different values for their astc formats.
public uint TextureSetting3 = 33554944 ; //00 02 00 02
public uint TextureSetting4 = 67330 ; //02 05 01 00 Varies sometimes with second byte
}
private Header HeaderInfo ;
//Image data is properly loaded afterwards
private List < List < byte [ ] > > ImageList = new List < List < byte [ ] > > ( ) ;
2023-05-31 02:37:47 +02:00
public override ToolStripItem [ ] GetContextMenuItems ( )
{
List < ToolStripItem > items = new List < ToolStripItem > ( ) ;
items . Add ( new ToolStripMenuItem ( "Save File" , null , ( o , e ) = >
{
STFileSaver . SaveFileFormat ( this , FilePath ) ;
} ) ) ;
items . AddRange ( base . GetContextMenuItems ( ) ) ;
return items . ToArray ( ) ;
}
2023-05-21 19:17:25 +02:00
public void Load ( Stream stream )
{
Tag = this ;
CanReplace = true ;
ImageKey = "Texture" ;
SelectedImageKey = "Texture" ;
2023-05-28 22:59:31 +02:00
string name = Path . GetFileNameWithoutExtension ( FileName ) ;
Text = name ;
//cache for loading file
if ( PluginRuntime . TextureCache . ContainsKey ( name ) )
PluginRuntime . TextureCache . Remove ( name ) ;
PluginRuntime . TextureCache . Add ( name , this ) ;
2023-05-21 19:17:25 +02:00
using ( var reader = new FileReader ( stream , true ) )
{
reader . SetByteOrder ( false ) ;
HeaderInfo = reader . ReadStruct < Header > ( ) ;
Width = HeaderInfo . Width ;
Height = HeaderInfo . Height ;
ArrayCount = HeaderInfo . Depth ;
MipCount = HeaderInfo . MipCount ;
RedChannel = ChannelList [ HeaderInfo . CompSelectR ] ;
GreenChannel = ChannelList [ HeaderInfo . CompSelectG ] ;
BlueChannel = ChannelList [ HeaderInfo . CompSelectB ] ;
AlphaChannel = ChannelList [ HeaderInfo . CompSelectA ] ;
SurfaceInfo [ ] surfaces = new SurfaceInfo [ MipCount * ArrayCount ] ;
reader . SeekBegin ( HeaderInfo . HeaderSize ) ;
for ( int i = 0 ; i < MipCount * ArrayCount ; i + + )
{
surfaces [ i ] = new SurfaceInfo ( ) ;
surfaces [ i ] . ArrayLevel = reader . ReadUInt16 ( ) ;
surfaces [ i ] . MipLevel = reader . ReadByte ( ) ;
reader . ReadByte ( ) ; //Always 1
}
for ( int i = 0 ; i < MipCount * ArrayCount ; i + + )
{
surfaces [ i ] . Size = reader . ReadUInt32 ( ) ;
reader . ReadUInt32 ( ) ; //Always 6
}
long pos = reader . Position ;
if ( FormatList . ContainsKey ( HeaderInfo . Format ) )
this . Format = FormatList [ HeaderInfo . Format ] ;
else
throw new Exception ( $"Unsupported format! {HeaderInfo.Format.ToString(" X ")}" ) ;
//Dumb hack. Terrain is oddly 8x8 astc, but the format seems to be 0x101
//Use some of the different texture settings, as they likely configure the astc blocks in some way
if ( this . HeaderInfo . TextureSetting2 = = 32631 )
{
this . Format = TEX_FORMAT . ASTC_8x8_UNORM ;
}
//Image data is properly loaded afterwards
List < List < byte [ ] > > data = new List < List < byte [ ] > > ( ) ;
//Combine each mip and array
for ( int i = 0 ; i < MipCount * ArrayCount ; i + + )
{
var imageData = reader . ReadBytes ( ( int ) ( surfaces [ i ] . Size ) ) ;
//Array level
if ( data . Count < = surfaces [ i ] . ArrayLevel )
data . Add ( new List < byte [ ] > ( ) ) ;
data [ surfaces [ i ] . ArrayLevel ] . Add ( Zstb . SDecompress ( imageData ) ) ;
}
ImageList = data ;
}
}
public void Save ( Stream stream )
{
//Apply generic properties
HeaderInfo . Format = FormatList . FirstOrDefault ( x = > x . Value = = Format ) . Key ;
HeaderInfo . Width = ( ushort ) this . Width ;
HeaderInfo . Height = ( ushort ) this . Height ;
HeaderInfo . Depth = ( ushort ) this . ArrayCount ;
HeaderInfo . MipCount = ( byte ) this . MipCount ;
using ( var writer = new FileWriter ( stream ) )
{
writer . WriteStruct ( this . HeaderInfo ) ;
writer . SeekBegin ( this . HeaderInfo . HeaderSize ) ;
List < uint > surfaceSizes = new List < uint > ( ) ;
List < byte [ ] > surfaceData = new List < byte [ ] > ( ) ;
//Surface index list
for ( int mip = 0 ; mip < this . MipCount ; mip + + )
{
for ( int array = 0 ; array < this . ArrayCount ; array + + )
{
writer . Write ( ( ushort ) array ) ;
writer . Write ( ( byte ) mip ) ;
writer . Write ( ( byte ) 1 ) ;
2023-05-28 01:33:14 +02:00
var surface = Zstb . SCompress ( ImageList [ array ] [ mip ] , 20 ) ;
2023-05-21 19:17:25 +02:00
surfaceSizes . Add ( ( uint ) surface . Length ) ;
surfaceData . Add ( surface ) ;
}
}
//Surface sizes
foreach ( var surface in surfaceSizes )
{
writer . Write ( surface ) ;
writer . Write ( 6 ) ;
}
//Surface data
foreach ( var data in surfaceData )
{
writer . Write ( data ) ;
}
}
}
2023-05-28 22:59:31 +02:00
public void Dispose ( )
{
if ( PluginRuntime . TextureCache . ContainsKey ( FileName ) )
PluginRuntime . TextureCache . Remove ( FileName ) ;
}
2023-05-21 19:17:25 +02:00
public override byte [ ] GetImageData ( int ArrayLevel = 0 , int MipLevel = 0 , int DepthLevel = 0 )
{
var data = ImageList [ ArrayLevel ] [ MipLevel ] ;
return TegraX1Swizzle . GetDirectImageData ( this , data , MipLevel ) ;
}
public override void SetImageData ( Bitmap bitmap , int ArrayLevel )
{
//Set the data using an instance of a switch texture
var tex = new TextureData ( ) ;
tex . Texture = new Syroot . NintenTools . NSW . Bntx . Texture ( ) ;
tex . Format = this . Format ;
tex . Width = this . Width ;
tex . Height = this . Height ;
tex . MipCount = this . MipCount ;
tex . ArrayCount = this . ArrayCount ;
tex . Texture . TextureData = new List < List < byte [ ] > > ( ) ;
tex . Texture . TextureData . Add ( new List < byte [ ] > ( ) ) ;
tex . SetImageData ( bitmap , ArrayLevel ) ;
SetImage ( tex , ArrayLevel ) ;
}
public override void Replace ( string FileName )
{
//Replace the data using an instance of a switch texture
var tex = new TextureData ( ) ;
2023-05-28 01:33:14 +02:00
tex . Replace ( FileName , MipCount , 0 , Format , Syroot . NintenTools . NSW . Bntx . GFX . SurfaceDim . Dim2D , 1 ) ;
2023-05-21 19:17:25 +02:00
//Get swappable array level
ImageEditorBase editor = ( ImageEditorBase ) LibraryGUI . GetActiveContent ( typeof ( ImageEditorBase ) ) ;
int targetArray = 0 ;
if ( editor ! = null )
targetArray = editor . GetArrayDisplayLevel ( ) ;
SetImage ( tex , targetArray ) ;
}
private void SetImage ( TextureData tex , int targetArray )
{
//If it's null, the operation is cancelled
if ( tex . Texture = = null )
return ;
2023-05-28 01:33:14 +02:00
for ( int i = 0 ; i < ImageList [ 0 ] . Count ; i + + )
Console . WriteLine ( $"SIZE 1 mip{i} {ImageList[0][i].Length}" ) ;
2023-05-21 19:17:25 +02:00
//Ensure the format matches if image requires multiple surface levels
if ( ImageList . Count > 1 & & this . Format ! = tex . Format )
throw new Exception ( $"Imported texture must use the original format for surface injecting! Expected {this.Format} but got {tex.Format}! If you need ASTC, use an astc encoder with .astc file format." ) ;
//Swap individual image
if ( tex . Texture . TextureData . Count = = 1 )
{
ImageList [ targetArray ] = tex . Texture . TextureData [ 0 ] ;
}
else //Swap all surfaces if multiple are imported
{
ImageList . Clear ( ) ;
foreach ( var surface in tex . Texture . TextureData )
ImageList . Add ( surface ) ;
}
2023-05-28 01:33:14 +02:00
for ( int i = 0 ; i < ImageList [ 0 ] . Count ; i + + )
Console . WriteLine ( $"SIZE 2 mip{i} {ImageList[0][i].Length}" ) ;
2023-05-21 19:17:25 +02:00
Width = tex . Texture . Width ;
Height = tex . Texture . Height ;
MipCount = tex . Texture . MipCount ;
ArrayCount = ( uint ) ImageList . Count ;
Format = tex . Format ;
UpdateEditor ( ) ;
}
class SurfaceInfo
{
public byte MipLevel ;
public ushort ArrayLevel ;
public byte SurfaceCount ; //Always 1
public uint Size ;
}
static Dictionary < uint , STChannelType > ChannelList = new Dictionary < uint , STChannelType > ( )
{
{ 0 , STChannelType . Red } ,
{ 1 , STChannelType . Green } ,
{ 2 , STChannelType . Blue } ,
{ 3 , STChannelType . Alpha } ,
{ 4 , STChannelType . Zero } ,
{ 5 , STChannelType . One } ,
} ;
static Dictionary < ushort , TEX_FORMAT > FormatList = new Dictionary < ushort , TEX_FORMAT > ( )
{
{ 0x101 , TEX_FORMAT . ASTC_4x4_UNORM } ,
{ 0x102 , TEX_FORMAT . ASTC_8x8_UNORM } ,
{ 0x105 , TEX_FORMAT . ASTC_8x8_SRGB } ,
2023-05-28 02:24:15 +02:00
{ 0x109 , TEX_FORMAT . ASTC_4x4_SRGB } ,
2023-05-21 19:17:25 +02:00
{ 0x202 , TEX_FORMAT . BC1_UNORM } ,
{ 0x203 , TEX_FORMAT . BC1_UNORM_SRGB } ,
{ 0x302 , TEX_FORMAT . BC1_UNORM } ,
2023-05-28 02:23:23 +02:00
{ 0x505 , TEX_FORMAT . BC3_UNORM_SRGB } ,
2023-05-28 22:59:31 +02:00
{ 0x602 , TEX_FORMAT . BC4_UNORM } ,
2023-05-21 19:17:25 +02:00
{ 0x606 , TEX_FORMAT . BC4_UNORM } ,
2023-05-28 01:33:14 +02:00
{ 0x607 , TEX_FORMAT . BC4_UNORM } ,
2023-05-21 19:17:25 +02:00
{ 0x702 , TEX_FORMAT . BC5_UNORM } ,
{ 0x703 , TEX_FORMAT . BC5_UNORM } ,
{ 0x707 , TEX_FORMAT . BC5_UNORM } ,
{ 0x901 , TEX_FORMAT . BC7_UNORM } ,
} ;
public class DisplayProperties
{
[Browsable(true)]
[ReadOnly(true)]
[Description("Height of the image.")]
[Category("Image Info")]
public uint Height { get ; set ; }
[Browsable(true)]
[ReadOnly(true)]
[Description("Width of the image.")]
[Category("Image Info")]
public uint Width { get ; set ; }
[Browsable(true)]
[ReadOnly(true)]
[Description("Format of the image.")]
[Category("Image Info")]
public TEX_FORMAT Format { get ; set ; }
[Browsable(true)]
[ReadOnly(true)]
[Description("Mip map count of the image.")]
[Category("Image Info")]
public uint MipCount { get ; set ; }
[Browsable(true)]
[ReadOnly(true)]
[Description("Array count of the image for multiple surfaces.")]
[Category("Image Info")]
public uint ArrayCount { get ; set ; }
[Browsable(true)]
[ReadOnly(true)]
[Description("The hash value.")]
[Category("Image Info")]
public string Hash { get ; set ; }
}
}
}