diff --git a/.vs/Switch_Toolbox/v15/.suo b/.vs/Switch_Toolbox/v15/.suo index 9e83177e..4c72b9a4 100644 Binary files a/.vs/Switch_Toolbox/v15/.suo and b/.vs/Switch_Toolbox/v15/.suo differ diff --git a/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide b/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide index cd729a9f..8ed9556b 100644 Binary files a/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide and b/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide differ diff --git a/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide-shm b/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide-shm index e70d45cc..41e4cb16 100644 Binary files a/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide-shm and b/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide-shm differ diff --git a/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide-wal b/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide-wal index 29bb64a2..59373255 100644 Binary files a/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide-wal and b/.vs/Switch_Toolbox/v15/Server/sqlite3/storage.ide-wal differ diff --git a/Switch_FileFormatsMain/FileFormats/AAMP/AAMP.cs b/Switch_FileFormatsMain/FileFormats/AAMP/AAMP.cs index 011f08d0..b7a6c21f 100644 --- a/Switch_FileFormatsMain/FileFormats/AAMP/AAMP.cs +++ b/Switch_FileFormatsMain/FileFormats/AAMP/AAMP.cs @@ -207,6 +207,10 @@ namespace FirstPlugin } } + public void FillEditor(UserControl control) + { + } + public aampv1.AampFile aampFileV1; public aampv2.AampFile aampFileV2; diff --git a/Switch_FileFormatsMain/FileFormats/Archives/RARC.cs b/Switch_FileFormatsMain/FileFormats/Archives/RARC.cs index 12e5750d..95c3a91d 100644 --- a/Switch_FileFormatsMain/FileFormats/Archives/RARC.cs +++ b/Switch_FileFormatsMain/FileFormats/Archives/RARC.cs @@ -3,10 +3,201 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Switch_Toolbox; +using System.Windows.Forms; +using Switch_Toolbox.Library; +using Switch_Toolbox.Library.IO; namespace FirstPlugin { - public class RARC + public class RARC : IArchiveFile, IFileFormat { + public FileType FileType { get; set; } = FileType.Archive; + + public bool CanSave { get; set; } + public string[] Description { get; set; } = new string[] { "RARC" }; + public string[] Extension { get; set; } = new string[] { "*.rarc", "*.arc", "*.yaz0" }; + public string FileName { get; set; } + public string FilePath { get; set; } + public IFileInfo IFileInfo { get; set; } + + public bool CanAddFiles { get; set; } + public bool CanRenameFiles { get; set; } + public bool CanReplaceFiles { get; set; } + public bool CanDeleteFiles { get; set; } + + public bool Identify(System.IO.Stream stream) + { + using (var reader = new Switch_Toolbox.Library.IO.FileReader(stream, true)) + { + return reader.CheckSignature(4, "RARC"); + } + } + + public Type[] Types + { + get + { + List types = new List(); + return types.ToArray(); + } + } + + public List files = new List(); + + public IEnumerable Files => files; + + private DirectoryEntry[] Directories; + + public void Load(System.IO.Stream stream) + { + using (var reader = new FileReader(stream)) + { + reader.ByteOrder = Syroot.BinaryData.ByteOrder.LittleEndian; + reader.ReadSignature(4, "RARC"); + uint FileSize = reader.ReadUInt32(); + uint HeaderSize = reader.ReadUInt32(); + uint DataOffset = reader.ReadUInt32(); + uint FileDataSize = reader.ReadUInt32(); + uint EndOfFileOffset = reader.ReadUInt32(); + byte[] Padding = reader.ReadBytes(8); + + + //Info Block + long pos = reader.Position; + + uint DirectoryCount = reader.ReadUInt32(); + uint DirectoryOffset = reader.ReadUInt32(); + uint TotalNodeCount = reader.ReadUInt32(); + uint NodeOffset = reader.ReadUInt32(); + uint StringTableSize = reader.ReadUInt32(); + uint StringTablOffset = reader.ReadUInt32(); + ushort NodeCount = reader.ReadUInt16(); + ushort Unknown = reader.ReadUInt16(); + byte[] Padding2 = reader.ReadBytes(4); + + Directories = new DirectoryEntry[DirectoryCount]; + + reader.SeekBegin(DirectoryOffset + pos); + for (int dir = 0; dir < DirectoryCount; dir++) + { + Directories[dir] = new DirectoryEntry(this); + Directories[dir].Read(reader); + uint NamePointer = StringTablOffset + (uint)pos + Directories[dir].NameOffset; + Directories[dir].Name = ReadStringAtTable(reader, NamePointer); + } + + + + reader.SeekBegin(NodeOffset + pos); + for (int n = 0; n < TotalNodeCount; n++) + { + files.Add(new FileEntry()); + { + + } + } + } + } + + private string ReadStringAtTable(FileReader reader, uint NameOffset) + { + using (reader.TemporarySeek(NameOffset, System.IO.SeekOrigin.Begin)) + { + return reader.ReadZeroTerminatedString(); + } + } + + private static ushort CalculateHash(string Name) + { + ushort Hash = 0; + for (int i = 0; i < Name.Length; i++) + { + Hash *= 3; + Hash += Name[i]; + } + return Hash; + } + + private void CreateDirectoryEntry() + { + + } + + public class DirectoryEntry + { + public RARC ParentArchive { get; } + + public string Name { get; set; } + + private uint Identifier; + + internal uint NameOffset; //Relative to string table + + public ushort Hash { get; set; } + + private ushort NodeCount; + + public uint FirstNodeIndex { get; set; } + + public DirectoryEntry(RARC rarc) { ParentArchive = rarc; } + + public void Read(FileReader reader) + { + Identifier = reader.ReadUInt32(); + NameOffset = reader.ReadUInt32(); + Hash = reader.ReadUInt16(); + NodeCount = reader.ReadUInt16(); + FirstNodeIndex = reader.ReadUInt32(); + } + + public void Write(FileWriter writer) + { + Hash = CalculateHash(Name); + + writer.Write(Identifier); + writer.Write(NameOffset); + writer.Write(Hash); + writer.Write(NodeCount); + writer.Write(FirstNodeIndex); + } + } + + public void Unload() + { + + } + + public byte[] Save() + { + return null; + } + + public bool AddFile(ArchiveFileInfo archiveFileInfo) + { + return false; + } + + public bool DeleteFile(ArchiveFileInfo archiveFileInfo) + { + return false; + } + + public class FileEntry : ArchiveFileInfo + { + public uint Unknown { get; set; } + + internal uint Size; + internal uint Offset; + internal uint NameOffset; + + public void Read(FileReader reader) + { + uint Unknown = reader.ReadUInt32(); + NameOffset = reader.ReadUInt32(); + Offset = reader.ReadUInt32(); + Size = reader.ReadUInt32(); + } + } } } diff --git a/Switch_FileFormatsMain/FileFormats/Archives/SDF.cs b/Switch_FileFormatsMain/FileFormats/Archives/SDF.cs index db13c226..9784eaf0 100644 --- a/Switch_FileFormatsMain/FileFormats/Archives/SDF.cs +++ b/Switch_FileFormatsMain/FileFormats/Archives/SDF.cs @@ -369,9 +369,9 @@ namespace FirstPlugin } } - if (Header.Version <= 0x16) + if (Header.Version <= 0x15) { - // fileId = (long)readVariadicInteger(4, reader); + fileId = (long)readVariadicInteger(4, reader); } if (compSizeArray.Count == 0 && hasCompression) diff --git a/Switch_FileFormatsMain/FileFormats/Audio/BCSTM.cs b/Switch_FileFormatsMain/FileFormats/Audio/BCSTM.cs index fa99cc29..c6324f99 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/BCSTM.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/BCSTM.cs @@ -49,6 +49,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(audioData, this); + } + public void Load(System.IO.Stream stream) { CanSave = true; diff --git a/Switch_FileFormatsMain/FileFormats/Audio/BCWAV.cs b/Switch_FileFormatsMain/FileFormats/Audio/BCWAV.cs index 3943736c..dff5ec54 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/BCWAV.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/BCWAV.cs @@ -49,6 +49,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(audioData, this); + } + public void Load(System.IO.Stream stream) { CanSave = true; diff --git a/Switch_FileFormatsMain/FileFormats/Audio/BFSTM.cs b/Switch_FileFormatsMain/FileFormats/Audio/BFSTM.cs index d6883b3a..617ac160 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/BFSTM.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/BFSTM.cs @@ -62,6 +62,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(audioData, this); + } + public void Load(System.IO.Stream stream) { CanSave = true; diff --git a/Switch_FileFormatsMain/FileFormats/Audio/BFWAV.cs b/Switch_FileFormatsMain/FileFormats/Audio/BFWAV.cs index 39e7bf55..0e7ceec6 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/BFWAV.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/BFWAV.cs @@ -49,6 +49,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(audioData, this); + } + public void Load(System.IO.Stream stream) { LoadAudio(stream, this); diff --git a/Switch_FileFormatsMain/FileFormats/Audio/BRSTM.cs b/Switch_FileFormatsMain/FileFormats/Audio/BRSTM.cs index 03cfb090..e72bb43b 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/BRSTM.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/BRSTM.cs @@ -49,6 +49,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(audioData, this); + } + public void Load(System.IO.Stream stream) { CanSave = true; diff --git a/Switch_FileFormatsMain/FileFormats/Audio/BRWAV.cs b/Switch_FileFormatsMain/FileFormats/Audio/BRWAV.cs index b93879bb..1c4a72a9 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/BRWAV.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/BRWAV.cs @@ -49,6 +49,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(audioData, this); + } + public void Load(System.IO.Stream stream) { LoadAudio(stream, this); diff --git a/Switch_FileFormatsMain/FileFormats/Audio/HPS.cs b/Switch_FileFormatsMain/FileFormats/Audio/HPS.cs index 3980bfd3..d5c00873 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/HPS.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/HPS.cs @@ -49,6 +49,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(audioData, this); + } + public void Load(System.IO.Stream stream) { CanSave = true; diff --git a/Switch_FileFormatsMain/FileFormats/Audio/IDSP.cs b/Switch_FileFormatsMain/FileFormats/Audio/IDSP.cs index ce0bd103..8da82b8c 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/IDSP.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/IDSP.cs @@ -49,6 +49,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(audioData, this); + } + public void Load(System.IO.Stream stream) { CanSave = true; diff --git a/Switch_FileFormatsMain/FileFormats/Audio/MP3.cs b/Switch_FileFormatsMain/FileFormats/Audio/MP3.cs index b02056ab..a6b2f9aa 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/MP3.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/MP3.cs @@ -58,6 +58,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(waveSource, this, false, mp3Struct); + } + IWaveSource waveSource; object mp3Struct; diff --git a/Switch_FileFormatsMain/FileFormats/Audio/Ogg.cs b/Switch_FileFormatsMain/FileFormats/Audio/Ogg.cs index 41820eb8..33f00bc5 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/Ogg.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/Ogg.cs @@ -58,6 +58,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(waveSource, this); + } + IWaveSource waveSource; public void Load(System.IO.Stream stream) diff --git a/Switch_FileFormatsMain/FileFormats/Audio/WAV.cs b/Switch_FileFormatsMain/FileFormats/Audio/WAV.cs index 5ec24563..9dc86b9e 100644 --- a/Switch_FileFormatsMain/FileFormats/Audio/WAV.cs +++ b/Switch_FileFormatsMain/FileFormats/Audio/WAV.cs @@ -60,6 +60,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((AudioPlayerPanel)control).LoadFile(audioData, this); + } + AudioData audioData; public void Load(System.IO.Stream stream) { diff --git a/Switch_FileFormatsMain/FileFormats/Byaml/BYAML.cs b/Switch_FileFormatsMain/FileFormats/Byaml/BYAML.cs index a79b3ab4..70987dce 100644 --- a/Switch_FileFormatsMain/FileFormats/Byaml/BYAML.cs +++ b/Switch_FileFormatsMain/FileFormats/Byaml/BYAML.cs @@ -150,6 +150,10 @@ namespace FirstPlugin return editor; } + public void FillEditor(UserControl control) + { + } + public void Load(Stream stream) { CanSave = true; diff --git a/Switch_FileFormatsMain/FileFormats/Effects/EFCF.cs b/Switch_FileFormatsMain/FileFormats/Effects/EFCF.cs index f25fdf37..c2140358 100644 --- a/Switch_FileFormatsMain/FileFormats/Effects/EFCF.cs +++ b/Switch_FileFormatsMain/FileFormats/Effects/EFCF.cs @@ -57,6 +57,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + ((EffectTableEditor)control).LoadEffectFile(this); + } + public void Load(System.IO.Stream stream) { CanSave = true; diff --git a/Switch_FileFormatsMain/FileFormats/Message/MSBT.cs b/Switch_FileFormatsMain/FileFormats/Message/MSBT.cs index 4e2b917c..9cebfb53 100644 --- a/Switch_FileFormatsMain/FileFormats/Message/MSBT.cs +++ b/Switch_FileFormatsMain/FileFormats/Message/MSBT.cs @@ -48,6 +48,11 @@ namespace FirstPlugin return editor; } + public void FillEditor(UserControl control) + { + ((MSBTEditor)control).LoadMSBT(this); + } + public Header header; public void Load(System.IO.Stream stream) diff --git a/Switch_FileFormatsMain/FileFormats/Texture/BCLIM.cs b/Switch_FileFormatsMain/FileFormats/Texture/BCLIM.cs index 53331bf2..87ca26da 100644 --- a/Switch_FileFormatsMain/FileFormats/Texture/BCLIM.cs +++ b/Switch_FileFormatsMain/FileFormats/Texture/BCLIM.cs @@ -117,6 +117,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + UpdateForm(); + } + private void UpdateForm() { if (form != null && image != null) diff --git a/Switch_FileFormatsMain/FileFormats/Texture/BFLIM.cs b/Switch_FileFormatsMain/FileFormats/Texture/BFLIM.cs index 579eb74d..407b23bf 100644 --- a/Switch_FileFormatsMain/FileFormats/Texture/BFLIM.cs +++ b/Switch_FileFormatsMain/FileFormats/Texture/BFLIM.cs @@ -126,6 +126,11 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + UpdateForm(); + } + private void UpdateForm() { if (form != null && image != null) diff --git a/Switch_FileFormatsMain/FileFormats/Texture/TEX3DS.cs b/Switch_FileFormatsMain/FileFormats/Texture/TEX3DS.cs index 0976bc0c..2ffff7e3 100644 --- a/Switch_FileFormatsMain/FileFormats/Texture/TEX3DS.cs +++ b/Switch_FileFormatsMain/FileFormats/Texture/TEX3DS.cs @@ -86,6 +86,21 @@ namespace FirstPlugin return form; } + public void FillEditor(UserControl control) + { + Properties prop = new Properties(); + prop.Width = Width; + prop.Height = Height; + prop.Depth = Depth; + prop.MipCount = MipCount; + prop.ArrayCount = ArrayCount; + prop.ImageSize = (uint)ImageData.Length; + prop.Format = Format; + + ((ImageEditorBase)control).LoadImage(this); + ((ImageEditorBase)control).LoadProperties(prop); + } + public void Load(System.IO.Stream stream) { PlatformSwizzle = PlatformSwizzle.Platform_3DS; diff --git a/Switch_FileFormatsMain/FileFormats/Turbo/Course_MapCamera_bin.cs b/Switch_FileFormatsMain/FileFormats/Turbo/Course_MapCamera_bin.cs index 19c8c17d..dbffffc8 100644 --- a/Switch_FileFormatsMain/FileFormats/Turbo/Course_MapCamera_bin.cs +++ b/Switch_FileFormatsMain/FileFormats/Turbo/Course_MapCamera_bin.cs @@ -7,6 +7,7 @@ using Switch_Toolbox.Library.IO; using Switch_Toolbox.Library; using FirstPlugin.Forms; using OpenTK; +using System.Windows.Forms; namespace FirstPlugin.Turbo { @@ -43,6 +44,11 @@ namespace FirstPlugin.Turbo return form; } + public void FillEditor(UserControl control) + { + ((MK8MapCameraEditor)control).LoadFile(this); + } + public void Load(System.IO.Stream stream) { CanSave = true; diff --git a/Switch_FileFormatsMain/obj/Release/DesignTimeResolveAssemblyReferences.cache b/Switch_FileFormatsMain/obj/Release/DesignTimeResolveAssemblyReferences.cache index 9764cab2..0f165faf 100644 Binary files a/Switch_FileFormatsMain/obj/Release/DesignTimeResolveAssemblyReferences.cache and b/Switch_FileFormatsMain/obj/Release/DesignTimeResolveAssemblyReferences.cache differ diff --git a/Switch_FileFormatsMain/obj/Release/Switch_FileFormatsMain.csprojAssemblyReference.cache b/Switch_FileFormatsMain/obj/Release/Switch_FileFormatsMain.csprojAssemblyReference.cache index 1642d9c7..ba9930c2 100644 Binary files a/Switch_FileFormatsMain/obj/Release/Switch_FileFormatsMain.csprojAssemblyReference.cache and b/Switch_FileFormatsMain/obj/Release/Switch_FileFormatsMain.csprojAssemblyReference.cache differ diff --git a/Switch_Toolbox_Library/FileFormats/ASTC.cs b/Switch_Toolbox_Library/FileFormats/ASTC.cs index 7f17beb0..5fd40b01 100644 --- a/Switch_Toolbox_Library/FileFormats/ASTC.cs +++ b/Switch_Toolbox_Library/FileFormats/ASTC.cs @@ -90,6 +90,12 @@ namespace Switch_Toolbox.Library return form; } + public void FillEditor(UserControl control) + { + ((ImageEditorBase)control).LoadImage(this); + ((ImageEditorBase)control).LoadProperties(GenericProperties); + } + //https://github.com/ARM-software/astc-encoder/blob/a47b80f081f10c43d96bd10bcb713c71708041b9/Source/astc_toplevel.cpp public byte[] magic; public byte BlockDimX; @@ -133,20 +139,30 @@ namespace Switch_Toolbox.Library if (BlockDimX == 4 && BlockDimY == 4) Format = TEX_FORMAT.ASTC_4x4_UNORM; - if (BlockDimX == 5 && BlockDimY == 4) + else if (BlockDimX == 5 && BlockDimY == 4) Format = TEX_FORMAT.ASTC_5x4_UNORM; - if (BlockDimX == 5 && BlockDimY == 5) + else if (BlockDimX == 5 && BlockDimY == 5) Format = TEX_FORMAT.ASTC_5x5_UNORM; - if (BlockDimX == 6 && BlockDimY == 5) + else if (BlockDimX == 6 && BlockDimY == 5) Format = TEX_FORMAT.ASTC_6x5_UNORM; - if (BlockDimX == 6 && BlockDimY == 6) + else if (BlockDimX == 6 && BlockDimY == 6) Format = TEX_FORMAT.ASTC_6x6_UNORM; - if (BlockDimX == 8 && BlockDimY == 5) + else if (BlockDimX == 8 && BlockDimY == 5) Format = TEX_FORMAT.ASTC_8x5_UNORM; - if (BlockDimX == 8 && BlockDimY == 6) + else if (BlockDimX == 8 && BlockDimY == 6) Format = TEX_FORMAT.ASTC_8x6_UNORM; - if (BlockDimX == 8 && BlockDimY == 8) + else if (BlockDimX == 8 && BlockDimY == 8) Format = TEX_FORMAT.ASTC_8x8_UNORM; + else if (BlockDimX == 10 && BlockDimY == 10) + Format = TEX_FORMAT.ASTC_10x10_UNORM; + else if (BlockDimX == 10 && BlockDimY == 5) + Format = TEX_FORMAT.ASTC_10x5_UNORM; + else if (BlockDimX == 10 && BlockDimY == 6) + Format = TEX_FORMAT.ASTC_10x6_UNORM; + else if (BlockDimX == 10 && BlockDimY == 8) + Format = TEX_FORMAT.ASTC_10x8_UNORM; + else + throw new Exception($"Unsupported block dims! ({BlockDimX} x {BlockDimY})"); } stream.Dispose(); @@ -167,6 +183,11 @@ namespace Switch_Toolbox.Library writer.ByteOrder = Syroot.BinaryData.ByteOrder.LittleEndian; + magic = new byte[4]; + xsize = new byte[3]; + ysize = new byte[3]; + zsize = new byte[3]; + magic[0] = MagicFileConstant & 0xFF; magic[1] = (MagicFileConstant >> 8) & 0xFF; magic[2] = (MagicFileConstant >> 16) & 0xFF; diff --git a/Switch_Toolbox_Library/FileFormats/DDS.cs b/Switch_Toolbox_Library/FileFormats/DDS.cs index 8d21ab64..44510922 100644 --- a/Switch_Toolbox_Library/FileFormats/DDS.cs +++ b/Switch_Toolbox_Library/FileFormats/DDS.cs @@ -432,6 +432,12 @@ namespace Switch_Toolbox.Library return form; } + public void FillEditor(UserControl control) + { + ((ImageEditorBase)control).LoadImage(this); + ((ImageEditorBase)control).LoadProperties(GenericProperties); + } + public enum DXGI_ASTC_FORMAT { @@ -769,7 +775,8 @@ namespace Switch_Toolbox.Library public bool SwitchSwizzle = false; public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0) { - if (IsAtscFormat(Format)) SwitchSwizzle = true; + if (IsAtscFormat(Format)) + SwitchSwizzle = true; if (SwitchSwizzle) return TegraX1Swizzle.GetImageData(this, bdata, ArrayLevel, MipLevel); diff --git a/Switch_Toolbox_Library/FileFormats/TGA.cs b/Switch_Toolbox_Library/FileFormats/TGA.cs new file mode 100644 index 00000000..d901cc95 --- /dev/null +++ b/Switch_Toolbox_Library/FileFormats/TGA.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using Switch_Toolbox.Library.IO; +using Switch_Toolbox.Library.Forms; +using System.Drawing; +using Paloma; + +namespace Switch_Toolbox.Library +{ + public class TGA : STGenericTexture, IEditor, IFileFormat + { + public FileType FileType { get; set; } = FileType.Image; + + public override TEX_FORMAT[] SupportedFormats + { + get + { + return new TEX_FORMAT[] { + TEX_FORMAT.R8G8B8A8_UNORM, + }; + } + } + + const string MagicFileConstant = "TRUEVISION"; + + public override bool CanEdit { get; set; } = false; + + public bool CanSave { get; set; } + public string[] Description { get; set; } = new string[] { "TGA" }; + public string[] Extension { get; set; } = new string[] { "*.tga" }; + public string FileName { get; set; } + public string FilePath { get; set; } + public IFileInfo IFileInfo { get; set; } + + public bool Identify(System.IO.Stream stream) + { + using (var reader = new Switch_Toolbox.Library.IO.FileReader(stream, true)) + { + reader.Position = reader.BaseStream.Length - 18; + bool IsValidMagic = reader.ReadString(10) == MagicFileConstant; + return IsValidMagic || Utils.GetExtension(FileName) == ".tga"; + } + } + + public Type[] Types + { + get + { + List types = new List(); + return types.ToArray(); + } + } + + public ImageEditorBase OpenForm() + { + bool IsDialog = IFileInfo != null && IFileInfo.InArchive; + + ImageEditorBase form = new ImageEditorBase(); + form.Text = Text; + form.Dock = DockStyle.Fill; + form.LoadImage(this); + form.LoadProperties(GenericProperties); + return form; + } + + public void FillEditor(UserControl control) + { + ((ImageEditorBase)control).LoadImage(this); + ((ImageEditorBase)control).LoadProperties(GenericProperties); + } + + private TargaImage TargaImage; + + public void Load(System.IO.Stream stream) { + TargaImage = new TargaImage(stream); + Width = (uint)TargaImage.Header.Width; + Height = (uint)TargaImage.Header.Height; + } + + public void Unload() + { + TargaImage.Dispose(); + } + + public byte[] Save() + { + return null; + } + + public override void SetImageData(Bitmap bitmap, int ArrayLevel) + { + throw new NotImplementedException("Cannot set image data! Operation not implemented!"); + } + + public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0) + { + if (TargaImage == null || TargaImage.Image == null) return new byte[0]; + + return BitmapExtension.ImageToByte(BitmapExtension.SwapBlueRedChannels(TargaImage.Image)); + } + } +} diff --git a/Switch_Toolbox_Library/Forms/Archive/ArchiveFilePanel.cs b/Switch_Toolbox_Library/Forms/Archive/ArchiveFilePanel.cs index 0b0ebb0f..7dd69a08 100644 --- a/Switch_Toolbox_Library/Forms/Archive/ArchiveFilePanel.cs +++ b/Switch_Toolbox_Library/Forms/Archive/ArchiveFilePanel.cs @@ -110,12 +110,16 @@ namespace Switch_Toolbox.Library.Forms if (inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IEditor<>)) { System.Reflection.MethodInfo method = objectType.GetMethod("OpenForm"); + System.Reflection.MethodInfo methodFill = objectType.GetMethod("FillEditor"); + var Editor = (UserControl)method.Invoke(fileFormat, new object[0]); var ActiveEditor = GetActiveEditor(Editor.GetType()); if (ActiveEditor == null) AddControl(Editor); else Editor = ActiveEditor; + + methodFill.Invoke(fileFormat, new object[1] { Editor }); } } } diff --git a/Switch_Toolbox_Library/Forms/Editors/Object Editor/ObjectEditor.cs b/Switch_Toolbox_Library/Forms/Editors/Object Editor/ObjectEditor.cs index 0f40ddd3..6b71f2dd 100644 --- a/Switch_Toolbox_Library/Forms/Editors/Object Editor/ObjectEditor.cs +++ b/Switch_Toolbox_Library/Forms/Editors/Object Editor/ObjectEditor.cs @@ -103,82 +103,10 @@ namespace Switch_Toolbox.Library.Forms } } - public void AddIArchiveFile(IFileFormat FileFormat) - { - TreeNode FileRoot = new ArchiveRootNodeWrapper(FileFormat.FileName, (IArchiveFile)FileFormat); - FillTreeNodes(FileRoot, (IArchiveFile)FileFormat); - AddNode(FileRoot); + public void AddIArchiveFile(IFileFormat FileFormat){ + ObjectTree.AddIArchiveFile(FileFormat); } - //The process takes awhile atm so limit splitting if there's a high amount - private readonly int MAX_FILE_PATH_SPLIT = 5000; - - void FillTreeNodes(TreeNode root, IArchiveFile archiveFile) - { - var rootText = root.Text; - var rootTextLength = rootText.Length; - var nodeFiles = archiveFile.Files; - if (nodeFiles.Count() > MAX_FILE_PATH_SPLIT) - { - foreach (var node in nodeFiles) - { - ArchiveFileWrapper wrapperFile = new ArchiveFileWrapper(node.FileName, node, archiveFile); - root.Nodes.Add(wrapperFile); - } - } - else - { - foreach (var node in nodeFiles) - { - string nodeString = node.FileName; - - var roots = nodeString.Split(new char[] { '/' }, - StringSplitOptions.RemoveEmptyEntries); - - // The initial parent is the root node - var parentNode = root; - var sb = new StringBuilder(rootText, nodeString.Length + rootTextLength); - for (int rootIndex = 0; rootIndex < roots.Length; rootIndex++) - { - // Build the node name - var parentName = roots[rootIndex]; - sb.Append("/"); - sb.Append(parentName); - var nodeName = sb.ToString(); - - // Search for the node - var index = parentNode.Nodes.IndexOfKey(nodeName); - if (index == -1) - { - // Node was not found, add it - - var folder = new ArchiveFolderNodeWrapper(parentName, archiveFile); - - if (rootIndex == roots.Length - 1) - { - ArchiveFileWrapper wrapperFile = new ArchiveFileWrapper(parentName, node, archiveFile); - wrapperFile.Name = nodeName; - parentNode.Nodes.Add(wrapperFile); - parentNode = wrapperFile; - } - else - { - folder.Name = nodeName; - parentNode.Nodes.Add(folder); - parentNode = folder; - } - } - else - { - // Node was found, set that as parent and continue - parentNode = parentNode.Nodes[index]; - } - } - } - } - } - - public Viewport GetViewport() => viewport; //Attatch a viewport instance here if created. diff --git a/Switch_Toolbox_Library/Forms/Editors/Object Editor/ObjectEditorTree.cs b/Switch_Toolbox_Library/Forms/Editors/Object Editor/ObjectEditorTree.cs index 46ede54c..06ec4d63 100644 --- a/Switch_Toolbox_Library/Forms/Editors/Object Editor/ObjectEditorTree.cs +++ b/Switch_Toolbox_Library/Forms/Editors/Object Editor/ObjectEditorTree.cs @@ -24,6 +24,81 @@ namespace Switch_Toolbox.Library.Forms public void BeginUpdate() { treeViewCustom1.BeginUpdate(); } public void EndUpdate() { treeViewCustom1.EndUpdate(); } + public void AddIArchiveFile(IFileFormat FileFormat) + { + TreeNode FileRoot = new ArchiveRootNodeWrapper(FileFormat.FileName, (IArchiveFile)FileFormat); + FillTreeNodes(FileRoot, (IArchiveFile)FileFormat); + AddNode(FileRoot); + } + + //The process takes awhile atm so limit splitting if there's a high amount + private readonly int MAX_FILE_PATH_SPLIT = 5000; + + void FillTreeNodes(TreeNode root, IArchiveFile archiveFile) + { + var rootText = root.Text; + var rootTextLength = rootText.Length; + var nodeFiles = archiveFile.Files; + if (nodeFiles.Count() > MAX_FILE_PATH_SPLIT) + { + foreach (var node in nodeFiles) + { + ArchiveFileWrapper wrapperFile = new ArchiveFileWrapper(node.FileName, node, archiveFile); + root.Nodes.Add(wrapperFile); + } + } + else + { + foreach (var node in nodeFiles) + { + string nodeString = node.FileName; + + var roots = nodeString.Split(new char[] { '/' }, + StringSplitOptions.RemoveEmptyEntries); + + // The initial parent is the root node + var parentNode = root; + var sb = new StringBuilder(rootText, nodeString.Length + rootTextLength); + for (int rootIndex = 0; rootIndex < roots.Length; rootIndex++) + { + // Build the node name + var parentName = roots[rootIndex]; + sb.Append("/"); + sb.Append(parentName); + var nodeName = sb.ToString(); + + // Search for the node + var index = parentNode.Nodes.IndexOfKey(nodeName); + if (index == -1) + { + // Node was not found, add it + + var folder = new ArchiveFolderNodeWrapper(parentName, archiveFile); + + if (rootIndex == roots.Length - 1) + { + ArchiveFileWrapper wrapperFile = new ArchiveFileWrapper(parentName, node, archiveFile); + wrapperFile.Name = nodeName; + parentNode.Nodes.Add(wrapperFile); + parentNode = wrapperFile; + } + else + { + folder.Name = nodeName; + parentNode.Nodes.Add(folder); + parentNode = folder; + } + } + else + { + // Node was found, set that as parent and continue + parentNode = parentNode.Nodes[index]; + } + } + } + } + } + public void AddNodeCollection(TreeNodeCollection nodes, bool ClearNodes) { // Invoke the treeview to add the nodes diff --git a/Switch_Toolbox_Library/Forms/Editors/UV/UVEditor.cs b/Switch_Toolbox_Library/Forms/Editors/UV/UVEditor.cs index 83935403..5254d449 100644 --- a/Switch_Toolbox_Library/Forms/Editors/UV/UVEditor.cs +++ b/Switch_Toolbox_Library/Forms/Editors/UV/UVEditor.cs @@ -101,30 +101,33 @@ namespace Switch_Toolbox.Library.Forms Vector2 v2 = new Vector2(0); Vector2 v3 = new Vector2(0); - if (UvChannelIndex == 0) + if (f.Count < (v + 2) && genericObject.vertices.Count > f[v + 2]) { - v1 = genericObject.vertices[f[v]].uv0; - v2 = genericObject.vertices[f[v + 1]].uv0; - v3 = genericObject.vertices[f[v + 2]].uv0; - } - if (UvChannelIndex == 1) - { - v1 = genericObject.vertices[f[v]].uv1; - v2 = genericObject.vertices[f[v + 1]].uv1; - v3 = genericObject.vertices[f[v + 2]].uv1; - } - if (UvChannelIndex == 2) - { - v1 = genericObject.vertices[f[v]].uv2; - v2 = genericObject.vertices[f[v + 1]].uv2; - v3 = genericObject.vertices[f[v + 2]].uv2; - } + if (UvChannelIndex == 0) + { + v1 = genericObject.vertices[f[v]].uv0; + v2 = genericObject.vertices[f[v + 1]].uv0; + v3 = genericObject.vertices[f[v + 2]].uv0; + } + if (UvChannelIndex == 1) + { + v1 = genericObject.vertices[f[v]].uv1; + v2 = genericObject.vertices[f[v + 1]].uv1; + v3 = genericObject.vertices[f[v + 2]].uv1; + } + if (UvChannelIndex == 2) + { + v1 = genericObject.vertices[f[v]].uv2; + v2 = genericObject.vertices[f[v + 1]].uv2; + v3 = genericObject.vertices[f[v + 2]].uv2; + } - v1 = new Vector2(v1.X, 1 - v1.Y); - v2 = new Vector2(v2.X, 1 - v2.Y); - v3 = new Vector2(v3.X, 1 - v3.Y); + v1 = new Vector2(v1.X, 1 - v1.Y); + v2 = new Vector2(v2.X, 1 - v2.Y); + v3 = new Vector2(v3.X, 1 - v3.Y); - DrawUVTriangleAndGrid(v1, v2, v3, divisions, uvColor, lineWidth, gridColor); + DrawUVTriangleAndGrid(v1, v2, v3, divisions, uvColor, lineWidth, gridColor); + } } } } diff --git a/Switch_Toolbox_Library/Generics/GenericTexture.cs b/Switch_Toolbox_Library/Generics/GenericTexture.cs index 82a25be9..662864b1 100644 --- a/Switch_Toolbox_Library/Generics/GenericTexture.cs +++ b/Switch_Toolbox_Library/Generics/GenericTexture.cs @@ -151,7 +151,7 @@ namespace Switch_Toolbox.Library /// /// The Format of the image. /// - public TEX_FORMAT Format { get; set; } + public TEX_FORMAT Format { get; set; } = TEX_FORMAT.R8G8B8A8_UNORM; public RenderableTex RenderableTex { get; set; } @@ -248,30 +248,31 @@ namespace Switch_Toolbox.Library { TEX_FORMAT.ASTC_4x4_UNORM, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_4x4_SRGB, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_5x5_UNORM, new FormatInfo(16, 5, 5, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_6x6_SRGB, new FormatInfo(16, 6, 6, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_8x8_UNORM, new FormatInfo(16, 8, 8, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_8x8_SRGB, new FormatInfo(16, 8, 8, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_10x10_UNORM, new FormatInfo(16, 10, 10, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_10x10_SRGB, new FormatInfo(16, 10, 10, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_12x12_UNORM, new FormatInfo(16, 12, 12, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_12x12_SRGB, new FormatInfo(16, 12, 12, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_5x4_UNORM, new FormatInfo(16, 5, 4, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_5x4_SRGB, new FormatInfo(16, 5, 4, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_5x5_UNORM, new FormatInfo(16, 5, 5, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_5x5_SRGB, new FormatInfo(16, 5, 5, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_6x5_UNORM, new FormatInfo(16, 6, 5, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_6x5_SRGB, new FormatInfo(16, 6, 5, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_8x6_UNORM, new FormatInfo(16, 8, 6, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_8x6_SRGB, new FormatInfo(16, 8, 6, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_10x8_UNORM, new FormatInfo(16, 10, 8, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_10x8_SRGB, new FormatInfo(16, 10, 8, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_12x10_UNORM, new FormatInfo(16, 12, 10, 1, TargetBuffer.Color) }, - { TEX_FORMAT.ASTC_12x10_SRGB, new FormatInfo(16, 12, 10, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_6x6_SRGB, new FormatInfo(16, 6, 6, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_8x5_UNORM, new FormatInfo(16, 8, 5, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_8x5_SRGB, new FormatInfo(16, 8, 5, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_8x6_UNORM, new FormatInfo(16, 8, 6, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_8x6_SRGB, new FormatInfo(16, 8, 6, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_8x8_UNORM, new FormatInfo(16, 8, 8, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_8x8_SRGB, new FormatInfo(16, 8, 8, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_10x5_UNORM, new FormatInfo(16, 10, 5, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_10x5_SRGB, new FormatInfo(16, 10, 5, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_10x6_UNORM, new FormatInfo(16, 10, 6, 1, TargetBuffer.Color) }, { TEX_FORMAT.ASTC_10x6_SRGB, new FormatInfo(16, 10, 6, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_10x8_UNORM, new FormatInfo(16, 10, 8, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_10x8_SRGB, new FormatInfo(16, 10, 8, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_10x10_UNORM, new FormatInfo(16, 10, 10, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_10x10_SRGB, new FormatInfo(16, 10, 10, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_12x10_UNORM, new FormatInfo(16, 12, 10, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_12x10_SRGB, new FormatInfo(16, 12, 10, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_12x12_UNORM, new FormatInfo(16, 12, 12, 1, TargetBuffer.Color) }, + { TEX_FORMAT.ASTC_12x12_SRGB, new FormatInfo(16, 12, 12, 1, TargetBuffer.Color) }, { TEX_FORMAT.ETC1, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) }, { TEX_FORMAT.ETC1_A4, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) }, { TEX_FORMAT.HIL08, new FormatInfo(16, 1, 1, 1, TargetBuffer.Color) }, @@ -579,13 +580,30 @@ namespace Switch_Toolbox.Library SaveFileDialog sfd = new SaveFileDialog(); sfd.FileName = Text; sfd.DefaultExt = "dds"; - sfd.Filter = "Supported Formats|*.dds; *.png;*.tga;*.jpg;*.tiff|" + - "Microsoft DDS |*.dds|" + - "Portable Network Graphics |*.png|" + - "Joint Photographic Experts Group |*.jpg|" + - "Bitmap Image |*.bmp|" + - "Tagged Image File Format |*.tiff|" + - "All files(*.*)|*.*"; + + if (IsAtscFormat(Format)) + { + sfd.DefaultExt = "astc"; + sfd.Filter = "Supported Formats|*.dds; *.png;*.tga;*.jpg;*.tiff;*.astc|" + + "Microsoft DDS |*.dds|" + + "Portable Network Graphics |*.png|" + + "Joint Photographic Experts Group |*.jpg|" + + "Bitmap Image |*.bmp|" + + "Tagged Image File Format |*.tiff|" + + "ASTC |*.astc|" + + "All files(*.*)|*.*"; + } + else + { + sfd.Filter = "Supported Formats|*.dds; *.png;*.tga;*.jpg;*.tiff|" + + "Microsoft DDS |*.dds|" + + "Portable Network Graphics |*.png|" + + "Joint Photographic Experts Group |*.jpg|" + + "Bitmap Image |*.bmp|" + + "Tagged Image File Format |*.tiff|" + + "All files(*.*)|*.*"; + } + if (sfd.ShowDialog() == DialogResult.OK) { @@ -614,9 +632,15 @@ namespace Switch_Toolbox.Library public void SaveASTC(string FileName, int SurfaceLevel = 0, int MipLevel = 0) { ASTC atsc = new ASTC(); - atsc.BlockDimX = (byte)GetBlockHeight(Format); - atsc.BlockDimY = (byte)GetBlockWidth(Format); - atsc.BlockDimZ = (byte)1; + atsc.Width = Width; + atsc.Height = Height; + atsc.Depth = Depth; + atsc.BlockDimX = (byte)GetBlockWidth(Format); + atsc.BlockDimY = (byte)GetBlockHeight(Format); + atsc.BlockDimZ = (byte)GetBlockDepth(Format); + var surfaces = GetSurfaces(); + atsc.DataBlock = Utils.CombineByteArray(surfaces[0].mipmaps.ToArray()); + File.WriteAllBytes(FileName, atsc.Save()); } public void SaveTGA(string FileName, int SurfaceLevel = 0, int MipLevel = 0) { diff --git a/Switch_Toolbox_Library/IO/FileReader.cs b/Switch_Toolbox_Library/IO/FileReader.cs index 68399cd6..664b0bb5 100644 --- a/Switch_Toolbox_Library/IO/FileReader.cs +++ b/Switch_Toolbox_Library/IO/FileReader.cs @@ -118,6 +118,9 @@ namespace Switch_Toolbox.Library.IO return RealSignature; } + public void SeekBegin(uint Offset) { Seek(Offset, SeekOrigin.Begin); } + public void SeekBegin(int Offset) { Seek(Offset, SeekOrigin.Begin); } + public void SeekBegin(long Offset) { Seek(Offset, SeekOrigin.Begin); } public long ReadOffset(bool IsRelative, Type OffsetType) { diff --git a/Switch_Toolbox_Library/Interfaces/IEditor.cs b/Switch_Toolbox_Library/Interfaces/IEditor.cs index 1b158523..53db522e 100644 --- a/Switch_Toolbox_Library/Interfaces/IEditor.cs +++ b/Switch_Toolbox_Library/Interfaces/IEditor.cs @@ -11,5 +11,6 @@ namespace Switch_Toolbox.Library public interface IEditor where T : UserControl { T OpenForm(); + void FillEditor(UserControl Editor); } } diff --git a/Switch_Toolbox_Library/Plugin/FileManager.cs b/Switch_Toolbox_Library/Plugin/FileManager.cs index 1ffc300a..231aaf92 100644 --- a/Switch_Toolbox_Library/Plugin/FileManager.cs +++ b/Switch_Toolbox_Library/Plugin/FileManager.cs @@ -89,6 +89,7 @@ namespace Switch_Toolbox.Library List types = new List(); types.Add((IFileFormat)Activator.CreateInstance(typeof(DDS))); types.Add((IFileFormat)Activator.CreateInstance(typeof(ASTC))); + types.Add((IFileFormat)Activator.CreateInstance(typeof(TGA))); if (GenericPluginLoader._Plugins == null) GenericPluginLoader.LoadPlugin(); diff --git a/Switch_Toolbox_Library/Switch_Toolbox_Library.csproj b/Switch_Toolbox_Library/Switch_Toolbox_Library.csproj index f9782592..9fe1b485 100644 --- a/Switch_Toolbox_Library/Switch_Toolbox_Library.csproj +++ b/Switch_Toolbox_Library/Switch_Toolbox_Library.csproj @@ -227,6 +227,7 @@ + UserControl