支持解包ESC-ARC(参照GARBRO)
放弃repack
This commit is contained in:
parent
34fa4dc9e9
commit
a9b8081fb8
@ -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
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)
|
||||
{
|
||||
|
||||
|
||||
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)
|
||||
|
@ -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
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