2019-04-12 00:05:15 +02:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
2019-07-16 23:35:21 +02:00
|
|
|
|
using Toolbox;
|
2019-04-12 00:05:15 +02:00
|
|
|
|
using System.Windows.Forms;
|
2019-07-16 23:35:21 +02:00
|
|
|
|
using Toolbox.Library;
|
|
|
|
|
using Toolbox.Library.IO;
|
|
|
|
|
using Toolbox.Library.Forms;
|
2020-01-29 22:08:29 +01:00
|
|
|
|
using System.Linq;
|
2019-04-12 00:05:15 +02:00
|
|
|
|
|
|
|
|
|
namespace FirstPlugin
|
|
|
|
|
{
|
2020-01-15 22:02:09 +01:00
|
|
|
|
public class APAK : IFileFormat, IArchiveFile
|
2019-04-12 00:05:15 +02:00
|
|
|
|
{
|
2020-01-15 22:02:09 +01:00
|
|
|
|
public FileType FileType { get; set; } = FileType.Archive;
|
2019-04-12 00:05:15 +02:00
|
|
|
|
|
|
|
|
|
public bool CanSave { get; set; }
|
|
|
|
|
public string[] Description { get; set; } = new string[] { "APAK" };
|
|
|
|
|
public string[] Extension { get; set; } = new string[] { "*.apak" };
|
|
|
|
|
public string FileName { get; set; }
|
|
|
|
|
public string FilePath { get; set; }
|
|
|
|
|
public IFileInfo IFileInfo { get; set; }
|
|
|
|
|
|
|
|
|
|
public bool Identify(System.IO.Stream stream)
|
|
|
|
|
{
|
2019-07-16 23:35:21 +02:00
|
|
|
|
using (var reader = new Toolbox.Library.IO.FileReader(stream, true))
|
2019-04-12 00:05:15 +02:00
|
|
|
|
{
|
|
|
|
|
return reader.CheckSignature(4, "APAK");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Type[] Types
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
List<Type> types = new List<Type>();
|
|
|
|
|
return types.ToArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-15 22:02:09 +01:00
|
|
|
|
public List<APAKFileInfo> files = new List<APAKFileInfo>();
|
|
|
|
|
|
|
|
|
|
public IEnumerable<ArchiveFileInfo> Files => files;
|
|
|
|
|
|
|
|
|
|
public void ClearFiles() { files.Clear(); }
|
2019-04-12 00:05:15 +02:00
|
|
|
|
|
2020-01-31 01:04:18 +01:00
|
|
|
|
public bool CanAddFiles { get; set; } = false;
|
2019-04-12 00:05:15 +02:00
|
|
|
|
public bool CanRenameFiles { get; set; } = true;
|
|
|
|
|
public bool CanReplaceFiles { get; set; } = true;
|
|
|
|
|
public bool CanDeleteFiles { get; set; } = true;
|
|
|
|
|
|
2020-01-29 22:08:29 +01:00
|
|
|
|
public Header ApakHeader;
|
|
|
|
|
|
2019-04-12 00:05:15 +02:00
|
|
|
|
public void Load(System.IO.Stream stream)
|
|
|
|
|
{
|
2020-01-29 22:08:29 +01:00
|
|
|
|
CanSave = true;
|
|
|
|
|
using (var reader = new FileReader(stream)) {
|
|
|
|
|
ApakHeader = new APAK.Header(reader, files);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Save(System.IO.Stream stream) {
|
|
|
|
|
using (var writer = new FileWriter(stream))
|
|
|
|
|
{
|
|
|
|
|
ApakHeader.Write(writer, files);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Header
|
|
|
|
|
{
|
|
|
|
|
public ushort Version { get; set; } = 5;
|
|
|
|
|
public bool IsBigEndian { get; set; }
|
|
|
|
|
|
|
|
|
|
public uint Unknown1 { get; set; } = 5381; //Always 5381?
|
|
|
|
|
public uint Unknown2 { get; set; }
|
|
|
|
|
|
|
|
|
|
public Header(FileReader reader, List<APAKFileInfo> files)
|
2019-04-12 00:05:15 +02:00
|
|
|
|
{
|
2020-01-15 22:02:09 +01:00
|
|
|
|
reader.SetByteOrder(true);
|
2019-04-12 00:05:15 +02:00
|
|
|
|
|
|
|
|
|
reader.ReadSignature(4, "APAK");
|
2020-01-15 22:02:09 +01:00
|
|
|
|
reader.ReadUInt16();
|
2020-01-29 22:08:29 +01:00
|
|
|
|
Version = reader.ReadUInt16();
|
|
|
|
|
IsBigEndian = Version == 5;
|
|
|
|
|
|
|
|
|
|
if (!IsBigEndian) {
|
|
|
|
|
Version = 5;
|
2020-01-15 22:02:09 +01:00
|
|
|
|
reader.SetByteOrder(false);
|
2020-01-29 22:08:29 +01:00
|
|
|
|
}
|
2020-01-15 22:02:09 +01:00
|
|
|
|
|
2019-04-12 00:05:15 +02:00
|
|
|
|
uint FileCount = reader.ReadUInt32();
|
2020-01-29 22:08:29 +01:00
|
|
|
|
Unknown1 = reader.ReadUInt32();
|
|
|
|
|
uint FileInfoSize = reader.ReadUInt32();
|
2019-04-12 00:05:15 +02:00
|
|
|
|
uint DataTotalSize = reader.ReadUInt32();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < FileCount; i++)
|
2020-01-15 22:02:09 +01:00
|
|
|
|
files.Add(new APAKFileInfo(reader));
|
2019-04-12 00:05:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-29 22:08:29 +01:00
|
|
|
|
public void Write(FileWriter writer, List<APAKFileInfo> files)
|
|
|
|
|
{
|
|
|
|
|
writer.SetByteOrder(IsBigEndian);
|
|
|
|
|
writer.WriteSignature("APAK");
|
|
|
|
|
writer.Write((ushort)0);
|
|
|
|
|
writer.Write(Version);
|
|
|
|
|
writer.Write(files.Count);
|
|
|
|
|
writer.Write(Unknown1);
|
|
|
|
|
writer.Write(files.Count * 64);
|
|
|
|
|
writer.Write(uint.MaxValue);
|
|
|
|
|
|
|
|
|
|
long fileInfoPos = writer.Position;
|
|
|
|
|
for (int i = 0; i < files.Count; i++) {
|
2020-01-30 01:47:53 +01:00
|
|
|
|
files[i].SaveFileFormat();
|
2020-01-29 22:08:29 +01:00
|
|
|
|
writer.Write(files[i].Hash);
|
|
|
|
|
writer.Write(uint.MaxValue);
|
|
|
|
|
writer.Write(files[i].FileData.Length);
|
|
|
|
|
writer.Write(files[i].FileData.Length);
|
|
|
|
|
writer.Write(files[i].Alignment);
|
|
|
|
|
writer.Write(files[i].Unknown1);
|
|
|
|
|
writer.Write(files[i].Unknown2);
|
|
|
|
|
writer.Write(files[i].Unknown3);
|
|
|
|
|
writer.WriteString(files[i].FileName, 0x20);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//The data gets ordered by largest alignment size to lowest
|
|
|
|
|
//Then by the file name
|
|
|
|
|
var filesSorted = files.OrderByDescending(x => x.Alignment)
|
|
|
|
|
.ThenBy(x => x.FileName)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
writer.Align((int)files.Max(x => x.Alignment));
|
|
|
|
|
|
|
|
|
|
long pos = writer.Position;
|
|
|
|
|
for (int i = 0; i < files.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var file = filesSorted[i];
|
|
|
|
|
int index = files.IndexOf(file);
|
|
|
|
|
|
|
|
|
|
long dataPos = writer.Position;
|
|
|
|
|
writer.WriteUint32Offset((fileInfoPos + 4) + index * 64);
|
|
|
|
|
writer.Write(file.FileData);
|
|
|
|
|
writer.AlignBytes((int)file.Alignment);
|
|
|
|
|
long dataEndPos = writer.Position;
|
|
|
|
|
|
|
|
|
|
using (writer.TemporarySeek((fileInfoPos + 12) + index * 64, SeekOrigin.Begin)) {
|
|
|
|
|
writer.Write((uint)(dataEndPos - dataPos));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long endPos = writer.Position;
|
|
|
|
|
uint dataSize = (uint)(endPos - pos);
|
|
|
|
|
using (writer.TemporarySeek(0x14, SeekOrigin.Begin)) {
|
|
|
|
|
writer.Write(dataSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Idk what this format is doing but there's tons of padding at the end and idk why :(
|
|
|
|
|
//Even combining all the alignments it can still be too small
|
|
|
|
|
writer.AlignBytes((int)files.Sum(x => x.Alignment));
|
|
|
|
|
}
|
2020-01-15 22:02:09 +01:00
|
|
|
|
}
|
2019-04-12 00:05:15 +02:00
|
|
|
|
|
2020-01-15 22:02:09 +01:00
|
|
|
|
public class APAKFileInfo : ArchiveFileInfo
|
|
|
|
|
{
|
2020-01-29 22:08:29 +01:00
|
|
|
|
public uint Hash { get; set; } //Unsure about this. Can't find any matches?
|
|
|
|
|
|
|
|
|
|
public uint Alignment { get; set; } = 64;
|
|
|
|
|
|
|
|
|
|
//These are all 0 for files i've seen.
|
|
|
|
|
public uint Unknown1 { get; set; }
|
|
|
|
|
public uint Unknown2 { get; set; }
|
|
|
|
|
public uint Unknown3 { get; set; }
|
|
|
|
|
|
|
|
|
|
public APAKFileInfo(string fileName)
|
|
|
|
|
{
|
|
|
|
|
FileName = fileName;
|
|
|
|
|
string ext = Utils.GetExtension(FileName);
|
|
|
|
|
if (AlignmentTable.ContainsKey(ext))
|
|
|
|
|
Alignment = AlignmentTable[ext];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Dictionary<string, uint> AlignmentTable = new Dictionary<string, uint>()
|
|
|
|
|
{
|
|
|
|
|
{ ".bfres", 8192 },
|
|
|
|
|
{ ".pspk" , 256 },
|
|
|
|
|
{ ".stfl" , 64 },
|
|
|
|
|
{ ".strc" , 64 },
|
|
|
|
|
{ ".layout" , 64 },
|
|
|
|
|
{ ".atcol" , 64 },
|
|
|
|
|
{ ".stsp" , 64 },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
internal uint DataOffset;
|
|
|
|
|
|
2020-01-15 22:02:09 +01:00
|
|
|
|
public APAKFileInfo(FileReader reader)
|
2019-04-12 00:05:15 +02:00
|
|
|
|
{
|
|
|
|
|
long pos = reader.Position;
|
|
|
|
|
|
2020-01-29 22:08:29 +01:00
|
|
|
|
Hash = reader.ReadUInt32();
|
|
|
|
|
DataOffset = reader.ReadUInt32();
|
|
|
|
|
uint fileSize = reader.ReadUInt32();
|
|
|
|
|
uint totalFileSize = reader.ReadUInt32(); //File size + aligned data
|
|
|
|
|
Alignment = reader.ReadUInt32();
|
|
|
|
|
Unknown1 = reader.ReadUInt32();
|
|
|
|
|
Unknown2 = reader.ReadUInt32();
|
|
|
|
|
Unknown3 = reader.ReadUInt32();
|
2020-01-15 22:02:09 +01:00
|
|
|
|
FileName = reader.ReadString(0x20, true);
|
2020-01-29 22:08:29 +01:00
|
|
|
|
|
|
|
|
|
Console.WriteLine($"{Utils.GetExtension(FileName)} {Alignment}");
|
2019-04-12 00:05:15 +02:00
|
|
|
|
|
2020-01-15 22:02:09 +01:00
|
|
|
|
long endpos = reader.Position;
|
2019-04-12 00:05:15 +02:00
|
|
|
|
|
2020-01-29 22:08:29 +01:00
|
|
|
|
reader.Seek(DataOffset, System.IO.SeekOrigin.Begin);
|
|
|
|
|
FileData = reader.ReadBytes((int)fileSize);
|
2019-04-12 00:05:15 +02:00
|
|
|
|
|
2020-01-15 22:02:09 +01:00
|
|
|
|
reader.Seek(endpos, System.IO.SeekOrigin.Begin);
|
2019-04-12 00:05:15 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void Unload()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-29 22:08:29 +01:00
|
|
|
|
public bool AddFile(ArchiveFileInfo archiveFileInfo) {
|
|
|
|
|
files.Add(new APAKFileInfo(archiveFileInfo.FileName)
|
|
|
|
|
{
|
|
|
|
|
FileData = archiveFileInfo.FileData,
|
|
|
|
|
});
|
|
|
|
|
return true;
|
2019-04-12 00:05:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-29 22:08:29 +01:00
|
|
|
|
public bool DeleteFile(ArchiveFileInfo archiveFileInfo) {
|
|
|
|
|
files.Remove((APAKFileInfo)archiveFileInfo);
|
|
|
|
|
return true;
|
2019-04-12 00:05:15 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|