2019-05-02 00:08:09 +02:00
|
|
|
|
using Syroot.BinaryData;
|
|
|
|
|
using System.IO;
|
2019-06-26 03:23:00 +02:00
|
|
|
|
using System;
|
2019-05-02 00:08:09 +02:00
|
|
|
|
using System.IO.Compression;
|
|
|
|
|
using OpenTK;
|
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
|
2019-07-16 23:35:21 +02:00
|
|
|
|
namespace Toolbox.Library.IO
|
2019-05-02 00:08:09 +02:00
|
|
|
|
{
|
|
|
|
|
public class STFileLoader
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the <see cref="TreeNodeFile"/> from a file or byte array.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="FileName">The name of the file</param>
|
|
|
|
|
/// <param name="data">The byte array of the data</param>
|
|
|
|
|
/// <param name="InArchive">If the file is in an archive so it can be saved back</param>
|
|
|
|
|
/// <param name="archiveNode">The node being replaced from an archive</param>
|
|
|
|
|
/// <param name="ArchiveHash">The unique hash from an archive for saving</param>
|
|
|
|
|
/// <param name="Compressed">If the file is being compressed or not</param>
|
|
|
|
|
/// <param name="CompType">The type of <see cref="CompressionType"/> being used</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static TreeNode GetNodeFileFormat(string FileName, byte[] data = null, bool InArchive = false,
|
|
|
|
|
TreeNode archiveNode = null, bool LeaveStreamOpen = false, bool Compressed = false, CompressionType CompType = 0)
|
|
|
|
|
{
|
|
|
|
|
IFileFormat format = OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode);
|
|
|
|
|
|
|
|
|
|
if (format is TreeNode)
|
|
|
|
|
return (TreeNode)format;
|
|
|
|
|
else
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2019-06-26 03:23:00 +02:00
|
|
|
|
|
2019-06-26 18:48:54 +02:00
|
|
|
|
public static IFileFormat OpenFileFormat(string FileName, Type[] FileTypes, byte[] data = null)
|
2019-06-26 03:23:00 +02:00
|
|
|
|
{
|
2019-06-29 02:42:49 +02:00
|
|
|
|
//Todo. Create a compression list like IFileFormat to decompress via an Identiy method
|
|
|
|
|
data = CheckCompression(FileName, data);
|
2019-06-27 20:38:13 +02:00
|
|
|
|
|
2019-06-26 03:23:00 +02:00
|
|
|
|
Stream stream;
|
|
|
|
|
if (data != null)
|
|
|
|
|
stream = new MemoryStream(data);
|
|
|
|
|
else
|
|
|
|
|
stream = File.OpenRead(FileName);
|
|
|
|
|
|
|
|
|
|
foreach (IFileFormat fileFormat in FileManager.GetFileFormats())
|
|
|
|
|
{
|
2019-06-26 18:48:54 +02:00
|
|
|
|
fileFormat.FileName = Path.GetFileName(FileName);
|
|
|
|
|
fileFormat.IFileInfo = new IFileInfo();
|
|
|
|
|
|
|
|
|
|
foreach (Type type in FileTypes)
|
2019-06-26 03:23:00 +02:00
|
|
|
|
{
|
2019-06-26 18:48:54 +02:00
|
|
|
|
if (fileFormat.Identify(stream) && fileFormat.GetType() == type)
|
2019-06-26 03:23:00 +02:00
|
|
|
|
return OpenFileFormat(FileName, data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static IFileFormat OpenFileFormat(string FileName, Type FileType, byte[] data = null)
|
|
|
|
|
{
|
2019-06-27 20:38:13 +02:00
|
|
|
|
CheckCompression(FileName, data);
|
|
|
|
|
|
2019-06-26 03:23:00 +02:00
|
|
|
|
Stream stream;
|
|
|
|
|
if (data != null)
|
|
|
|
|
stream = new MemoryStream(data);
|
|
|
|
|
else
|
|
|
|
|
stream = File.OpenRead(FileName);
|
|
|
|
|
|
|
|
|
|
foreach (IFileFormat fileFormat in FileManager.GetFileFormats())
|
|
|
|
|
{
|
|
|
|
|
if (fileFormat.GetType() == FileType)
|
|
|
|
|
return OpenFileFormat(FileName, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-29 02:42:49 +02:00
|
|
|
|
private static byte[] CheckCompression(string FileName, byte[] data)
|
2019-06-27 20:38:13 +02:00
|
|
|
|
{
|
|
|
|
|
if (data != null)
|
|
|
|
|
{
|
|
|
|
|
using (var reader = new FileReader(data))
|
|
|
|
|
{
|
2019-06-29 02:42:49 +02:00
|
|
|
|
return DecompressData(reader, FileName, data);
|
2019-06-27 20:38:13 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
using (var reader = new FileReader(FileName))
|
|
|
|
|
{
|
2019-06-29 02:42:49 +02:00
|
|
|
|
return DecompressData(reader, FileName, data);
|
2019-06-27 20:38:13 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-29 02:42:49 +02:00
|
|
|
|
private static byte[] DecompressData(FileReader reader, string FileName, byte[] data)
|
2019-06-27 20:38:13 +02:00
|
|
|
|
{
|
|
|
|
|
reader.ByteOrder = ByteOrder.BigEndian;
|
2019-06-29 02:42:49 +02:00
|
|
|
|
reader.Position = 0;
|
2019-06-27 20:38:13 +02:00
|
|
|
|
uint MagicHex = reader.ReadUInt32();
|
|
|
|
|
string Magic = reader.ReadMagic(0, 4);
|
|
|
|
|
reader.Position = 0;
|
|
|
|
|
|
|
|
|
|
if (Magic == "Yaz0")
|
|
|
|
|
{
|
|
|
|
|
if (data != null)
|
2019-06-29 02:42:49 +02:00
|
|
|
|
return EveryFileExplorer.YAZ0.Decompress(data);
|
2019-06-27 20:38:13 +02:00
|
|
|
|
else
|
2019-06-29 02:42:49 +02:00
|
|
|
|
return EveryFileExplorer.YAZ0.Decompress(FileName);
|
2019-06-27 20:38:13 +02:00
|
|
|
|
}
|
|
|
|
|
if (MagicHex == 0x28B52FFD || MagicHex == 0xFD2FB528)
|
|
|
|
|
{
|
|
|
|
|
if (data != null)
|
2019-06-29 02:42:49 +02:00
|
|
|
|
return STLibraryCompression.ZSTD.Decompress(data);
|
2019-06-27 20:38:13 +02:00
|
|
|
|
else
|
2019-06-29 02:42:49 +02:00
|
|
|
|
return STLibraryCompression.ZSTD.Decompress(File.ReadAllBytes(FileName));
|
2019-06-27 20:38:13 +02:00
|
|
|
|
}
|
2019-06-29 02:42:49 +02:00
|
|
|
|
|
|
|
|
|
return data;
|
2019-06-27 20:38:13 +02:00
|
|
|
|
}
|
2019-06-26 03:23:00 +02:00
|
|
|
|
|
2019-05-02 00:08:09 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the <see cref="IFileFormat"/> from a file or byte array.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="FileName">The name of the file</param>
|
|
|
|
|
/// <param name="data">The byte array of the data</param>
|
|
|
|
|
/// <param name="InArchive">If the file is in an archive so it can be saved back</param>
|
|
|
|
|
/// <param name="archiveNode">The node being replaced from an archive</param>
|
|
|
|
|
/// <param name="Compressed">If the file is being compressed or not</param>
|
|
|
|
|
/// <param name="CompType">The type of <see cref="CompressionType"/> being used</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IFileFormat OpenFileFormat(string FileName, byte[] data = null, bool LeaveStreamOpen = false, bool InArchive = false,
|
|
|
|
|
TreeNode archiveNode = null, bool Compressed = false, CompressionType CompType = 0, uint DecompressedSize = 0, uint CompressedSize = 0)
|
|
|
|
|
{
|
|
|
|
|
uint DecompressedFileSize = 0;
|
|
|
|
|
uint CompressedFileSize = 0;
|
|
|
|
|
|
|
|
|
|
FileReader fileReader;
|
|
|
|
|
if (data != null)
|
|
|
|
|
fileReader = new FileReader(data);
|
|
|
|
|
else
|
|
|
|
|
fileReader = new FileReader(FileName);
|
|
|
|
|
|
|
|
|
|
if (CompType == CompressionType.None)
|
|
|
|
|
DecompressedFileSize = (uint)fileReader.BaseStream.Length;
|
|
|
|
|
if (CompType != CompressionType.None)
|
|
|
|
|
CompressedFileSize = (uint)fileReader.BaseStream.Length;
|
|
|
|
|
|
|
|
|
|
if (fileReader.BaseStream.Length <= 4)
|
|
|
|
|
{
|
|
|
|
|
fileReader.Close();
|
|
|
|
|
fileReader.Dispose();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
|
|
|
fileReader.ByteOrder = ByteOrder.BigEndian;
|
|
|
|
|
uint MagicHex = fileReader.ReadUInt32();
|
|
|
|
|
|
|
|
|
|
string Magic = fileReader.ReadMagic(0, 4);
|
|
|
|
|
|
|
|
|
|
fileReader.Position = 0;
|
2019-05-08 22:28:04 +02:00
|
|
|
|
ushort MagicHex2 = fileReader.ReadUInt16();
|
2019-05-02 00:08:09 +02:00
|
|
|
|
|
2019-07-03 03:39:19 +02:00
|
|
|
|
//Another hacky magic check if decomp size is first
|
|
|
|
|
fileReader.Position = 4;
|
|
|
|
|
ushort MagicHex3 = fileReader.ReadUInt16();
|
|
|
|
|
|
2019-07-01 21:44:19 +02:00
|
|
|
|
Stream stream;
|
|
|
|
|
if (data != null)
|
|
|
|
|
stream = new MemoryStream(data);
|
|
|
|
|
else
|
|
|
|
|
stream = File.OpenRead(FileName);
|
|
|
|
|
|
2019-07-03 03:39:19 +02:00
|
|
|
|
//Note this method will soon be how all compression formats are handled rather than being checked here
|
2019-07-01 21:44:19 +02:00
|
|
|
|
foreach (ICompressionFormat compressionFormat in FileManager.GetCompressionFormats())
|
|
|
|
|
{
|
|
|
|
|
if (compressionFormat.Identify(stream))
|
|
|
|
|
{
|
|
|
|
|
compressionFormat.Decompress(stream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-02 00:08:09 +02:00
|
|
|
|
if (Magic == "Yaz0")
|
|
|
|
|
{
|
|
|
|
|
if (data != null)
|
|
|
|
|
data = EveryFileExplorer.YAZ0.Decompress(data);
|
|
|
|
|
else
|
|
|
|
|
data = EveryFileExplorer.YAZ0.Decompress(FileName);
|
|
|
|
|
|
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Yaz0, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
2019-07-05 20:45:16 +02:00
|
|
|
|
if (Path.GetExtension(FileName) == ".cbtex")
|
|
|
|
|
{
|
|
|
|
|
fileReader.Position = 0;
|
|
|
|
|
byte compType = fileReader.ReadByte();
|
|
|
|
|
if (compType == 0x50)
|
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
|
|
|
|
fileReader.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian;
|
|
|
|
|
|
|
|
|
|
fileReader.Seek(4, System.IO.SeekOrigin.Begin);
|
|
|
|
|
uint decompSize = fileReader.ReadUInt32();
|
|
|
|
|
uint compSize = (uint)fileReader.BaseStream.Length - 8;
|
|
|
|
|
|
|
|
|
|
var comp = new STLibraryCompression.MTA_CUSTOM();
|
|
|
|
|
data = comp.Decompress(data, decompSize);
|
|
|
|
|
|
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.MarioTennisCustom, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
|
|
|
|
fileReader.Position = 0;
|
|
|
|
|
}
|
2019-07-02 22:12:23 +02:00
|
|
|
|
if (Path.GetExtension(FileName) == ".lz")
|
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
|
|
|
|
data = LZ77_WII.Decompress(fileReader.getSection(16, data.Length - 16));
|
|
|
|
|
|
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Yaz0, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
2019-05-02 00:08:09 +02:00
|
|
|
|
if (MagicHex == 0x28B52FFD || MagicHex == 0xFD2FB528)
|
|
|
|
|
{
|
2019-06-26 18:48:54 +02:00
|
|
|
|
if (data != null)
|
|
|
|
|
data = STLibraryCompression.ZSTD.Decompress(fileReader.getSection(0, data.Length));
|
|
|
|
|
else
|
|
|
|
|
data = STLibraryCompression.ZSTD.Decompress(File.ReadAllBytes(FileName));
|
|
|
|
|
|
2019-05-02 00:08:09 +02:00
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Zstb, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
2019-07-01 02:59:11 +02:00
|
|
|
|
if (Magic == "ZCMP" || MagicHex2 == 0x789C || MagicHex2 == 0x78DA || Path.GetExtension(FileName) == ".z" && CompType == CompressionType.None)
|
2019-05-02 00:08:09 +02:00
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
2019-05-29 20:24:24 +02:00
|
|
|
|
data = STLibraryCompression.ZLIB.Decompress(data);
|
2019-05-02 00:08:09 +02:00
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Zlib, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
2019-07-03 03:39:19 +02:00
|
|
|
|
if (MagicHex3 == 0x789C || MagicHex3 == 0x78DA && CompType == CompressionType.None)
|
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
|
|
|
|
fileReader.Position = 0;
|
|
|
|
|
int OuSize = fileReader.ReadInt32();
|
|
|
|
|
int InSize = data.Length - 4;
|
|
|
|
|
data = STLibraryCompression.ZLIB.Decompress(fileReader.getSection(4, InSize));
|
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Zlib, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
2019-06-16 02:50:26 +02:00
|
|
|
|
if (Path.GetExtension(FileName) == ".carc" && CompType == CompressionType.None)
|
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
|
|
|
|
data = STLibraryCompression.ZLIB.Decompress(fileReader.getSection(0x10, data.Length - 0x10));
|
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Zlib, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
2019-05-02 00:08:09 +02:00
|
|
|
|
if (Magic == "ZLIB")
|
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
|
|
|
|
data = STLibraryCompression.GZIP.Decompress(fileReader.getSection(64, data.Length - 64));
|
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Zlib, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
2019-05-08 22:28:04 +02:00
|
|
|
|
if (MagicHex == 0x1f8b0808 || MagicHex2 == 0x1f8b && CompType == CompressionType.None)
|
2019-05-02 00:08:09 +02:00
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
|
|
|
|
data = STLibraryCompression.GZIP.Decompress(data);
|
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Gzip, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
2019-06-22 15:26:45 +02:00
|
|
|
|
if (MagicHex == 0x184D2204)
|
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
|
|
|
|
data = STLibraryCompression.Type_LZ4.Decompress(data);
|
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Lz4, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
2019-05-02 00:08:09 +02:00
|
|
|
|
if (Path.GetExtension(FileName) == ".lz" && CompType == CompressionType.None)
|
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
|
|
|
|
data = STLibraryCompression.LZ77.Decompress(fileReader.getSection(16, data.Length - 16));
|
|
|
|
|
return OpenFileFormat(FileName, data, LeaveStreamOpen, InArchive, archiveNode, true,
|
|
|
|
|
CompressionType.Zlib, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
|
|
|
|
if (Path.GetExtension(FileName) == ".cmp" && CompType == CompressionType.None)
|
|
|
|
|
{
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = File.ReadAllBytes(FileName);
|
|
|
|
|
|
|
|
|
|
fileReader.Position = 0;
|
|
|
|
|
int OuSize = fileReader.ReadInt32();
|
|
|
|
|
int InSize = data.Length - 4;
|
|
|
|
|
data = STLibraryCompression.Type_LZ4F.Decompress(fileReader.getSection(4, InSize));
|
|
|
|
|
|
|
|
|
|
return OpenFileFormat(FileName, data, InArchive, LeaveStreamOpen, archiveNode, true,
|
|
|
|
|
CompressionType.Lz4f, DecompressedFileSize, CompressedFileSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fileReader.Close();
|
2019-07-14 23:36:52 +02:00
|
|
|
|
fileReader.Dispose();
|
2019-05-02 00:08:09 +02:00
|
|
|
|
|
|
|
|
|
foreach (IFileFormat fileFormat in FileManager.GetFileFormats())
|
|
|
|
|
{
|
|
|
|
|
//Set the file name so we can check it's extension in the identifier.
|
|
|
|
|
//Most is by magic but some can be extension or name.
|
|
|
|
|
|
|
|
|
|
fileFormat.FileName = Path.GetFileName(FileName);
|
|
|
|
|
fileFormat.IFileInfo = new IFileInfo();
|
|
|
|
|
|
|
|
|
|
if (fileFormat.Identify(stream))
|
|
|
|
|
{
|
|
|
|
|
fileFormat.IFileInfo.DecompressedSize = DecompressedFileSize;
|
|
|
|
|
fileFormat.IFileInfo.CompressedSize = CompressedFileSize;
|
|
|
|
|
return SetFileFormat(fileFormat, FileName, stream, LeaveStreamOpen, InArchive, archiveNode, Compressed, CompType);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IFileFormat SetFileFormat(IFileFormat fileFormat, string FileName, Stream stream, bool LeaveStreamOpen = false, bool InArchive = false,
|
|
|
|
|
TreeNode archiveNode = null, bool Compressed = false, CompressionType CompType = 0)
|
|
|
|
|
{
|
|
|
|
|
fileFormat.IFileInfo.CompressionType = CompType;
|
|
|
|
|
fileFormat.IFileInfo.FileIsCompressed = Compressed;
|
|
|
|
|
fileFormat.FileName = Path.GetFileName(FileName);
|
|
|
|
|
fileFormat.FilePath = FileName;
|
|
|
|
|
fileFormat.IFileInfo.InArchive = InArchive;
|
|
|
|
|
fileFormat.IFileInfo.FileIsCompressed = Compressed;
|
|
|
|
|
if (Compressed)
|
|
|
|
|
fileFormat.IFileInfo.CompressionType = CompType;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fileFormat.Load(stream);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (fileFormat is TreeNode)
|
|
|
|
|
{
|
|
|
|
|
if (archiveNode != null)
|
|
|
|
|
{
|
|
|
|
|
((TreeNode)fileFormat).Text = archiveNode.Text;
|
|
|
|
|
((TreeNode)fileFormat).ImageKey = archiveNode.ImageKey;
|
|
|
|
|
((TreeNode)fileFormat).SelectedImageKey = archiveNode.SelectedImageKey;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//After file has been loaded and read, we'll dispose unless left open
|
|
|
|
|
|
|
|
|
|
if (!LeaveStreamOpen)
|
|
|
|
|
{
|
2019-06-14 23:23:02 +02:00
|
|
|
|
// stream.Close();
|
|
|
|
|
// stream.Dispose();
|
2019-05-02 00:08:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fileFormat;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|