支持解包ESC-ARC(参照GARBRO)
放弃repack
This commit is contained in:
parent
34fa4dc9e9
commit
a9b8081fb8
@ -78,7 +78,7 @@ namespace EscudeTools
|
|||||||
Sheet sheet = new();
|
Sheet sheet = new();
|
||||||
//process struct
|
//process struct
|
||||||
uint nameOffset = BitConverter.ToUInt32(sheet_struct, 0);
|
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.cols = BitConverter.ToUInt32(sheet_struct, 4);
|
||||||
sheet.col = new Column[sheet.cols];
|
sheet.col = new Column[sheet.cols];
|
||||||
int offset = 8;
|
int offset = 8;
|
||||||
@ -92,7 +92,7 @@ namespace EscudeTools
|
|||||||
throw new NotSupportedException("Unsupported Format"); //暂时不受支持的0x2 0x3
|
throw new NotSupportedException("Unsupported Format"); //暂时不受支持的0x2 0x3
|
||||||
column.size = BitConverter.ToUInt16(sheet_struct, offset + 2);
|
column.size = BitConverter.ToUInt16(sheet_struct, offset + 2);
|
||||||
uint columnNameOffset = BitConverter.ToUInt32(sheet_struct, offset + 4);
|
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;
|
sheet.col[i] = column;
|
||||||
offset += 8;
|
offset += 8;
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@ namespace EscudeTools
|
|||||||
}
|
}
|
||||||
else
|
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;
|
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)
|
public bool ExportDatabase(int outputType, string? storePath)
|
||||||
{
|
{
|
||||||
if (db.Length == 0)
|
if (db.Length == 0)
|
||||||
|
168
EscudeTools/PackManager.cs
Normal file
168
EscudeTools/PackManager.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,52 @@
|
|||||||
{
|
{
|
||||||
static void Main(string[] args)
|
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]))
|
//if (Directory.Exists(args[0]))
|
||||||
//{
|
//{
|
||||||
// string[] files = Directory.GetFiles(args[0], "*.bin");
|
// string[] files = Directory.GetFiles(args[0], "*.bin");
|
||||||
@ -50,33 +96,33 @@
|
|||||||
//return;
|
//return;
|
||||||
|
|
||||||
|
|
||||||
if (Directory.Exists(args[0]))
|
//if (Directory.Exists(args[0]))
|
||||||
{
|
//{
|
||||||
string[] files = Directory.GetFiles(args[0], "db_*.bin");
|
// string[] files = Directory.GetFiles(args[0], "db_*.bin");
|
||||||
DatabaseManager dm = new();
|
// DatabaseManager dm = new();
|
||||||
foreach (string file in files)
|
// foreach (string file in files)
|
||||||
{
|
// {
|
||||||
if (dm.LoadDatabase(file))
|
// if (dm.LoadDatabase(file))
|
||||||
{
|
// {
|
||||||
Console.WriteLine($"Load {file} Success");
|
// Console.WriteLine($"Load {file} Success");
|
||||||
}
|
// }
|
||||||
else
|
// else
|
||||||
{
|
// {
|
||||||
Console.WriteLine($"Load {file} Failed");
|
// Console.WriteLine($"Load {file} Failed");
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (dm.ExportDatabase(0, Path.GetDirectoryName(args[0])))
|
// if (dm.ExportDatabase(0, Path.GetDirectoryName(args[0])))
|
||||||
Console.WriteLine("Export Database Success");
|
// Console.WriteLine("Export Database Success");
|
||||||
else
|
// else
|
||||||
{
|
// {
|
||||||
Console.WriteLine("Export Database Failed");
|
// Console.WriteLine("Export Database Failed");
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// if (args.Length == 0 || args.Length > 2)
|
// if (args.Length == 0 || args.Length > 2)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"profiles": {
|
"profiles": {
|
||||||
"EscudeTools": {
|
"EscudeTools": {
|
||||||
"commandName": "Project",
|
"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
51
EscudeTools/Utils.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user