支持解包ESC-ARC(参照GARBRO)

放弃repack
This commit is contained in:
Chenx221 2024-10-16 23:02:42 +08:00
parent 34fa4dc9e9
commit a9b8081fb8
5 changed files with 293 additions and 42 deletions

View File

@ -78,7 +78,7 @@ namespace EscudeTools
Sheet sheet = new();
//process struct
uint nameOffset = BitConverter.ToUInt32(sheet_struct, 0);
sheet.name = ReadStringFromTextData(sheet_text, (int)nameOffset);
sheet.name = Utils.ReadStringFromTextData(sheet_text, (int)nameOffset);
sheet.cols = BitConverter.ToUInt32(sheet_struct, 4);
sheet.col = new Column[sheet.cols];
int offset = 8;
@ -92,7 +92,7 @@ namespace EscudeTools
throw new NotSupportedException("Unsupported Format"); //暂时不受支持的0x2 0x3
column.size = BitConverter.ToUInt16(sheet_struct, offset + 2);
uint columnNameOffset = BitConverter.ToUInt32(sheet_struct, offset + 4);
column.name = ReadStringFromTextData(sheet_text, (int)columnNameOffset);
column.name = Utils.ReadStringFromTextData(sheet_text, (int)columnNameOffset);
sheet.col[i] = column;
offset += 8;
}
@ -115,7 +115,7 @@ namespace EscudeTools
}
else
{
record.values[j] = ReadStringFromTextData(sheet_text, (int)textOffset);
record.values[j] = Utils.ReadStringFromTextData(sheet_text, (int)textOffset);
}
}
@ -139,20 +139,6 @@ namespace EscudeTools
return sheet;
}
private static string ReadStringFromTextData(byte[] sheet_text, int offset)
{
List<byte> stringBytes = [];
for (int i = offset; i < sheet_text.Length && sheet_text[i] != 0x00; i++)
{
stringBytes.Add(sheet_text[i]);
}
EncodingProvider provider = CodePagesEncodingProvider.Instance;
Encoding? shiftJis = provider.GetEncoding("shift-jis");
return shiftJis == null
? throw new InvalidOperationException("Shift-JIS encoding not supported.")
: shiftJis.GetString(stringBytes.ToArray());
}
public bool ExportDatabase(int outputType, string? storePath)
{
if (db.Length == 0)

168
EscudeTools/PackManager.cs Normal file
View File

@ -0,0 +1,168 @@
//这里的代码参考(Ctrl+C, Ctrl+V)了Garbro中关于ESCUDE BIN封包的实现
using System.Text;
namespace EscudeTools
{
public class Entry
{
public string Name { get; set; }
public long Offset { get; set; }
public uint Size { get; set; }
}
public class PackManager
{
static readonly byte[] fileSignature = [0x45, 0x53, 0x43, 0x2D, 0x41, 0x52, 0x43]; //"ESC-ARC"
static readonly byte[] supportPackVersion = [0x31, 0x32]; //1, 2
private bool isLoaded = false;
private string pFile = "";
private uint m_seed;
private uint m_count;
List<Entry> pItem = [];
public bool Load(string path)
{
if (!File.Exists(path))
return false;
using (FileStream fs = new(path, FileMode.Open))
using (BinaryReader br = new(fs))
{
byte[] head = br.ReadBytes(fileSignature.Length);
if (!head.SequenceEqual(fileSignature))
return false;
byte ver = br.ReadByte();
List<Entry>? item = [];
switch (Array.IndexOf(supportPackVersion, ver))
{
case 0:
item = ProcessV1(br);
break;
case 1:
item = ProcessV2(br);
break;
default:
return false;
}
if (item == null)
return false;
pItem = item;
}
isLoaded = true;
pFile = path;
return true;
}
private List<Entry>? ProcessV1(BinaryReader br)
{
m_seed = br.ReadUInt32();
m_count = br.ReadUInt32() ^ NextKey();
uint index_size = m_count * 0x88;
byte[] index = Utils.ReadBytes(br, index_size);
if (index.Length != index_size)
return null;
Decrypt(ref index);
int index_offset = 0;
List<Entry> dir = new((int)m_count);
for (uint i = 0; i < m_count; ++i)
{
string name = Utils.ReadStringFromTextData(index, index_offset, 0x80);
Entry entry = new()
{
Name = name,
Offset = Utils.ToUInt32(index, index_offset + 0x80),
Size = Utils.ToUInt32(index, index_offset + 0x84)
};
index_offset += 0x88;
dir.Add(entry);
}
return dir;
}
private List<Entry>? ProcessV2(BinaryReader br)
{
m_seed = br.ReadUInt32();
m_count = br.ReadUInt32() ^ NextKey();
uint names_size = br.ReadUInt32() ^ NextKey();
uint index_size = m_count * 12;
byte[] index = Utils.ReadBytes(br, index_size);
if (index.Length != index_size)
return null;
var names = Utils.ReadBytes(br, names_size);
if (names.Length != names_size)
return null;
Decrypt(ref index);
int index_offset = 0;
var dir = new List<Entry>((int)m_count);
for (uint i = 0; i < m_count; ++i)
{
int filename_offset = (int)Utils.ToUInt32(index, index_offset);
if (filename_offset < 0 || filename_offset >= names.Length)
return null;
var name = Utils.ReadStringFromTextData(names, filename_offset, names.Length - filename_offset);
if (0 == name.Length)
return null;
Entry entry = new()
{
Name = name,
Offset = Utils.ToUInt32(index, index_offset + 4),
Size = Utils.ToUInt32(index, index_offset + 8)
};
index_offset += 12;
dir.Add(entry);
}
return dir;
}
private void Decrypt(ref byte[] data)
{
int length = data.Length / 4;
uint[] buffer = new uint[length];
Buffer.BlockCopy(data, 0, buffer, 0, data.Length);
for (int i = 0; i < length; i++)
{
buffer[i] ^= NextKey();
}
Buffer.BlockCopy(buffer, 0, data, 0, data.Length);
}
private uint NextKey()
{
m_seed ^= 0x65AC9365;
m_seed ^= (((m_seed >> 1) ^ m_seed) >> 3) ^ (((m_seed << 1) ^ m_seed) << 3);
return m_seed;
}
public bool Extract()
{
if (!isLoaded)
throw new InvalidOperationException("Pack not loaded");
string output = Path.Combine(Path.GetDirectoryName(pFile), "output", Path.GetFileNameWithoutExtension(pFile));
if (!Directory.Exists(output))
Directory.CreateDirectory(output);
using FileStream inputStream = new(pFile, FileMode.Open);
foreach (Entry entry in pItem)
{
string entryPath = Path.Combine(output, entry.Name);
string entryDirectory = Path.GetDirectoryName(entryPath);
if (!Directory.Exists(entryDirectory))
Directory.CreateDirectory(entryDirectory);
using FileStream outputStream = new(entryPath, FileMode.Create);
inputStream.Seek(entry.Offset, SeekOrigin.Begin);
byte[] buffer = new byte[entry.Size];
inputStream.Read(buffer, 0, buffer.Length);
outputStream.Write(buffer, 0, buffer.Length);
}
return true;
}
public bool Repack(string path)
{
throw new NotSupportedException("Repack not supported");
}
}
}

View File

@ -4,6 +4,52 @@
{
static void Main(string[] args)
{
if (Directory.Exists(args[0]))
{
string[] files = Directory.GetFiles(args[0], "*.bin");
PackManager pm = new();
foreach (string file in files)
{
if (pm.Load(file))
{
Console.WriteLine($"Load {file} Success");
}
else
{
Console.WriteLine($"Load {file} Failed");
return;
}
if (pm.Extract())
Console.WriteLine("Export Database Success");
else
{
Console.WriteLine("Export Database Failed");
return;
}
if (pm.Repack(args[1]))
Console.WriteLine("Export Database Success");
else
{
Console.WriteLine("Export Database Failed");
return;
}
}
}
//PackManager pm = new();
//if (pm.Repack(args[1]))
// Console.WriteLine("Export Database Success");
//else
//{
// Console.WriteLine("Export Database Failed");
// return;
//}
//if (Directory.Exists(args[0]))
//{
// string[] files = Directory.GetFiles(args[0], "*.bin");
@ -50,33 +96,33 @@
//return;
if (Directory.Exists(args[0]))
{
string[] files = Directory.GetFiles(args[0], "db_*.bin");
DatabaseManager dm = new();
foreach (string file in files)
{
if (dm.LoadDatabase(file))
{
Console.WriteLine($"Load {file} Success");
}
else
{
Console.WriteLine($"Load {file} Failed");
return;
}
//if (Directory.Exists(args[0]))
//{
// string[] files = Directory.GetFiles(args[0], "db_*.bin");
// DatabaseManager dm = new();
// foreach (string file in files)
// {
// if (dm.LoadDatabase(file))
// {
// Console.WriteLine($"Load {file} Success");
// }
// else
// {
// Console.WriteLine($"Load {file} Failed");
// return;
// }
if (dm.ExportDatabase(0, Path.GetDirectoryName(args[0])))
Console.WriteLine("Export Database Success");
else
{
Console.WriteLine("Export Database Failed");
return;
}
// if (dm.ExportDatabase(0, Path.GetDirectoryName(args[0])))
// Console.WriteLine("Export Database Success");
// else
// {
// Console.WriteLine("Export Database Failed");
// return;
// }
}
// }
}
//}
// if (args.Length == 0 || args.Length > 2)

View File

@ -2,7 +2,7 @@
"profiles": {
"EscudeTools": {
"commandName": "Project",
"commandLineArgs": "G:\\x221.local\\lab\\db"
"commandLineArgs": "G:\\x221.local\\lab\\1\r\nG:\\x221.local\\lab\\1\\output\\data"
}
}
}

51
EscudeTools/Utils.cs Normal file
View File

@ -0,0 +1,51 @@
using System.Text;
namespace EscudeTools
{
public class Utils
{
public static string ReadStringFromTextData(byte[] sheet_text, int offset)
{
return ReadStringFromTextData(sheet_text, offset, -1);
}
public static string ReadStringFromTextData(byte[] sheet_text, int offset, int length_limit)
{
EncodingProvider provider = CodePagesEncodingProvider.Instance;
Encoding? shiftJis = provider.GetEncoding("shift-jis") ?? throw new InvalidOperationException("Shift-JIS encoding not supported.");
return ReadStringFromTextData(sheet_text, offset, length_limit, shiftJis);
}
public static string ReadStringFromTextData(byte[] sheet_text, int offset, int length_limit, Encoding enc)
{
List<byte> stringBytes = [];
int end = (length_limit != -1) ? Math.Min(offset + length_limit, sheet_text.Length) : sheet_text.Length;
for (int i = offset; i < end && sheet_text[i] != 0x00; i++)
{
stringBytes.Add(sheet_text[i]);
}
return enc.GetString(stringBytes.ToArray());
}
public static byte[] ReadBytes(BinaryReader reader, ulong length)
{
const int bufferSize = 8192;
byte[] data = new byte[length];
ulong bytesRead = 0;
while (bytesRead < length)
{
int toRead = (int)Math.Min(bufferSize, length - bytesRead);
int read = reader.Read(data, (int)bytesRead, toRead);
if (read == 0)
break;
bytesRead += (ulong)read;
}
return data;
}
public static uint ToUInt32<TArray>(TArray value, int index) where TArray : IList<byte>
{
return (uint)(value[index] | value[index + 1] << 8 | value[index + 2] << 16 | value[index + 3] << 24);
}
}
}