Compare commits

...

36 Commits
master ... v2

Author SHA1 Message Date
f38fc2c4cc
支持读取lsf文件
mot看了下,写到一半感觉没意义就删了
2024-10-22 14:49:25 +08:00
be30d3751a
预先开坑 2024-10-19 22:28:59 +08:00
ca7722bb7e
修复完整script导出时的错误 2024-10-19 21:49:57 +08:00
4ad356f145 修复明显的错误,仍然存在bug
除导出type1外其他测试通过
2024-10-19 00:15:39 +08:00
206999c456 完成script封包的编码 !未测试! 2024-10-18 23:33:54 +08:00
5d23d7d09f ScriptFile(.bin)支持仅导出text
剩余部分以.bat形式存储
2024-10-18 20:32:22 +08:00
6516dbde44 添加ESC-ARC2封包测试用功能 2024-10-18 18:36:20 +08:00
d558f9e2b4 支持mdb打包
仅通过简单测试
2024-10-18 02:14:24 +08:00
96b93a722c 修复mdb提取遗漏问题 2024-10-17 23:59:48 +08:00
25c3ff879e 封包功能修正(未完成 2024-10-17 23:19:36 +08:00
8a1b604f6a 修复mdb重打包错误(未完成 2024-10-17 22:28:46 +08:00
58a4d5a13d 初步支持mdb打包
未广泛测试
2024-10-17 18:31:42 +08:00
ade27ea87c 整理代码 2024-10-17 12:25:14 +08:00
d3abca73ad 理论上支持ESC-ARC1重新封包
未测试,手头上没有老版本
2024-10-17 01:24:13 +08:00
6505ac4862 支持ESC-ARC2重新封包 2024-10-17 01:04:14 +08:00
007873c955 clean 2024-10-16 23:04:01 +08:00
a9b8081fb8 支持解包ESC-ARC(参照GARBRO)
放弃repack
2024-10-16 23:02:42 +08:00
34fa4dc9e9 更正Masterdb导出数据类型 2024-10-16 16:35:54 +08:00
79db32b780 优化MasterDB导出sqlite性能 2024-10-16 14:31:52 +08:00
abb3d6c2ef bug修复+sqlite导出性能优化 2024-10-16 13:31:13 +08:00
cd926e361e 解析&导出功能优化 2024-10-16 01:42:42 +08:00
767fce3dd9 修正command help显示,初步支持sqlite导出command表
但别指望完全准确
2024-10-15 22:49:43 +08:00
4e9f6ad2a9 进一步支持解析script code
关于code就做到这了,不再深入了
2024-10-15 22:02:45 +08:00
b3a6c529f8 现在自动加载.001在打开.bin script时 2024-10-15 13:25:15 +08:00
72f9b2190f 初步支持解析script code
稍后我再看看能不能改善一些
2024-10-15 13:15:23 +08:00
71e99f86d4 初步支持读取Script
不支持导出,sf中Data暂不支持解析
2024-10-14 23:10:48 +08:00
ec42590b0e update 2024-10-14 22:47:24 +08:00
8965c9aacb 准备开工script解析 2024-10-13 22:45:24 +08:00
b91884b08d 更新几个示例 2024-10-13 21:54:23 +08:00
1ef8568ba2 初步支持导出db_graphics、db_localize、db_scripts、db_sounds内容为sqlite 2024-10-13 21:53:10 +08:00
b853aaeffc 修复错误的偏移量增加值 2024-10-13 19:08:25 +08:00
f42192bdc1 update 2024-10-13 19:07:37 +08:00
7ee930b9d1 masterdb读取尚未完全实现(1/3) 2024-10-12 23:16:37 +08:00
46a7fc20a1 项目重命名 2024-10-12 17:05:40 +08:00
709d4f2b56 清理代码2 2024-10-12 00:09:54 +08:00
22e4ce06dd 清理代码,有空再重新糊 2024-10-11 23:56:36 +08:00
441 changed files with 3001 additions and 300 deletions

View File

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.0.0" />
</ItemGroup>
</Project>

View File

@ -1,277 +0,0 @@
using ImageMagick;
using System.Runtime.InteropServices;
namespace EscudeLSF
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct LSFHDR
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] signature; // 4 bytes
public uint unknown1; // 4 bytes
public uint unknown2; // 4 bytes
public uint width; // 4 bytes
public uint height; // 4 bytes
public uint unknown_width; // 4 bytes
public uint unknown_height; // 4 bytes
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct LSFENTRY
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public char[] name; // 128 bytes
public uint x; // 4 bytes
public uint y; // 4 bytes
public uint baseWidth; // 4 bytes
public uint baseHeight; // 4 bytes
public uint unknown3; // 4 bytes
public uint unknown4; // 4 bytes
public byte type; // 1 byte
public byte id; // 1 byte
public byte unknown5; // 1 byte
public byte unknown6; // 1 byte
public uint unknown7; // 4 bytes
public uint unknown8; // 4 bytes
}
internal class Program
{
private static readonly byte[] Signature = [0x4C, 0x53, 0x46, 0x00];
static void Main(string[] args)
{
if (args.Length == 0 || args.Length > 2)
{
Console.WriteLine("Invalid arguments. Use -h for help.");
return;
}
string? workDirectory;
string? outputDirectory;
switch (args[0])
{
case "-h":
DisplayHelp();
return;
case "-r":
case "-d":
case "-s":
workDirectory = GetWorkDirectory(args);
outputDirectory = GetOutputDirectory(workDirectory);
if (args[0] == "-d")
{
ProcessBatch(args[1], workDirectory, outputDirectory);
}
else
{
CombineIMG(ReadLSF(args[1]), workDirectory, outputDirectory);
}
break;
default:
if (!IsValidFileArgument(args))
{
Console.WriteLine("Invalid arguments. Use -h for help.");
return;
}
workDirectory = Path.GetDirectoryName(args[0]);
outputDirectory = GetOutputDirectory(workDirectory);
CombineIMG(ReadLSF(args[0]), workDirectory, outputDirectory);
break;
}
}
static void DisplayHelp()
{
Console.WriteLine("Usage: EscudeLSF.exe [-r <filepath>] [-d <directory>] [-s <filepath>] [-h]");
Console.WriteLine("Options:");
Console.WriteLine(" <filepath> Single lsf process");
Console.WriteLine(" -r <filepath> Read single lsf file");
Console.WriteLine(" -d <directory> Process all lsf files in directory");
Console.WriteLine(" -s <filepath> Same as <filepath>");
Console.WriteLine(" -h Display help info");
}
static string GetOutputDirectory(string workDirectory)
{
string outputDirectory = Path.Combine(Path.GetDirectoryName(workDirectory), "output");
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
return outputDirectory;
}
static void ProcessBatch(string directory, string workDirectory, string outputDirectory)
{
foreach (string file in Directory.GetFiles(directory, "*.lsf"))
{
CombineIMG(ReadLSF(file), workDirectory, outputDirectory);
}
}
static string GetWorkDirectory(string[] args)
{
return args[0] == "-d" ? args[1] : Path.GetDirectoryName(args[1]);
}
static bool IsValidFileArgument(string[] args)
{
return args.Length == 2 || (args.Length == 1 && !Path.Exists(args[0]));
}
static List<LSFENTRY> ReadLSF(string filePath)
{
LSFHDR header;
List<LSFENTRY> entries = [];
using (FileStream fs = new(filePath, FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new(fs))
{
header = ReadLSFHDR(reader);
if (!header.signature.SequenceEqual(Signature))
{
Console.WriteLine("Invalid LSF file: Signature does not match.");
return [];
}
while (fs.Position < fs.Length)
{
LSFENTRY entry = ReadLSFENTRY(reader);
// 过滤某些类型(主要是表情,还有审查
// 没找到合适的处理方法有主意的老哥欢迎Pull Request
if (entry.type == 0x0B || entry.type == 0x15 || entry.type == 0xFF || (entry.type == 0x00 && entry.unknown5 == 0x03))
{
continue;
}
// 过滤重复项(没看出重复的意义,也许是前面过滤掉的某个类型需要
if (entries.Any(e => e.name.SequenceEqual(entry.name)))
{
continue;
}
entries.Add(entry);
}
}
return entries;
}
static LSFHDR ReadLSFHDR(BinaryReader reader)
{
byte[] headerData = reader.ReadBytes(Marshal.SizeOf(typeof(LSFHDR)));
GCHandle handle = GCHandle.Alloc(headerData, GCHandleType.Pinned);
try
{
return (LSFHDR)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(LSFHDR));
}
finally
{
handle.Free();
}
}
static LSFENTRY ReadLSFENTRY(BinaryReader reader)
{
byte[] entryData = reader.ReadBytes(Marshal.SizeOf(typeof(LSFENTRY)));
GCHandle handle = GCHandle.Alloc(entryData, GCHandleType.Pinned);
try
{
return (LSFENTRY)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(LSFENTRY));
}
finally
{
handle.Free();
}
}
static void CombineIMG(List<LSFENTRY> entrys, string workDirectory, string storeDir)
{
// 检查是否有多个角色
bool isMultiCharacter = entrys.Any(e => e.type == 0x14);
foreach (var entry in entrys.Where(e => e.type == 0x00))
{
string baseName = new string(entry.name).Trim('\0');
string targetPath = Path.Combine(storeDir, baseName);
string baseImagePath = Path.Combine(workDirectory, baseName + ".png");
if (!File.Exists(baseImagePath))
{
Console.WriteLine($"Error, File not found: {baseName}");
continue;
}
foreach (var eyeEntry in entrys.Where(e => e.type == 0x0A))
{
using var baseImage = new MagickImage(baseImagePath);
ProcessEyeImage(entrys, workDirectory, baseImage, eyeEntry, targetPath, isMultiCharacter);
}
}
}
private static void ProcessEyeImage(List<LSFENTRY> entrys, string workDirectory, MagickImage baseImage, LSFENTRY eyeEntry, string targetPath, bool isMultiCharacter)
{
string eyeName = new string(eyeEntry.name).Trim('\0');
string eyeImagePath = Path.Combine(workDirectory, eyeName + ".png");
if (!File.Exists(eyeImagePath))
{
Console.WriteLine($"Error, File not found: {eyeName}");
return;
}
using var eyeImage = new MagickImage(eyeImagePath);
var tmpImage = RealProcessIMG(baseImage, eyeImage, (int)eyeEntry.x, (int)eyeEntry.y);
string target2Path = targetPath + "_" + eyeName;
if (isMultiCharacter)
{
foreach (var eye2Entry in entrys.Where(e => e.type == 0x14))
{
using var tmpImageClone = (MagickImage)tmpImage.Clone();
ProcessOverlayImage(eye2Entry, target2Path, workDirectory, tmpImageClone);
}
}
else
{
tmpImage.Write(target2Path + ".png");
}
}
private static void ProcessOverlayImage(LSFENTRY eye2Entry, string target2Path, string workDirectory, MagickImage tmpImage)
{
string eye2Name = new string(eye2Entry.name).Trim('\0');
string eye2ImagePath = Path.Combine(workDirectory, eye2Name + ".png");
if (!File.Exists(eye2ImagePath))
{
Console.WriteLine($"Error, File not found: {eye2Name}");
return;
}
using var overlayImage = new MagickImage(eye2ImagePath);
RealProcessIMG(tmpImage, overlayImage, (int)eye2Entry.x, (int)eye2Entry.y).Write($"{target2Path}_{eye2Name}.png");
}
static MagickImage RealProcessIMG(MagickImage b, MagickImage o, int x, int y)
{
o.Alpha(AlphaOption.Set);
// Not working
// 原先是设计给处理那些不透明的图片,但好像有问题
//if (args[4] == "y")
//{
// //overlayImage.ColorFuzz = new Percentage(6); // For Face
// //overlayImage.Transparent(MagickColors.White); // For Face
// //overlayImage.Blur(0, 4); // For Face
//}
b.Composite(o, x, y, CompositeOperator.Over);
return b;
}
}
}

View File

@ -1,7 +0,0 @@
{
"profiles": {
"EscudeLSF": {
"commandName": "Project"
}
}
}

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35312.102
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscudeLSF", "EscudeLSF\EscudeLSF.csproj", "{3DD9B6A4-5DD0-43FA-92EF-33DED4622A2B}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscudeTools", "EscudeTools\EscudeTools.csproj", "{3DD9B6A4-5DD0-43FA-92EF-33DED4622A2B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -0,0 +1,443 @@
using Microsoft.Data.Sqlite;
using System.Reflection;
using System.Text;
namespace EscudeTools
{
public class Sheet
{
public string name;
public uint cols;
public Column[] col;
public Record records;
}
public class Column
{
public string name;
public ushort type;
public ushort size;
}
public class Record(int columnCount)
{
public object[] values = new object[columnCount]; // 每列的数据值
//提示
//颜色值转换可以看看https://argb-int-calculator.netlify.app/
}
public class DatabaseManager
{
static readonly byte[] fileSignature = [0x6D, 0x64, 0x62, 0x00];
static readonly byte[] stopBytes = [0x00, 0x00, 0x00, 0x00];
private Sheet[] db = [];
private string dbName = string.Empty;
public Sheet[] GetDB() { return db; }
public bool LoadDatabase(string path)
{
if (db.Length > 0)
db = [];
if (!File.Exists(path))
return false;
dbName = Path.GetFileNameWithoutExtension(path);
List<Sheet> sheets = [];
using (FileStream fs = new(path, FileMode.Open))
using (BinaryReader br = new(fs))
{
byte[] head = br.ReadBytes(4);
if (!head.SequenceEqual(fileSignature))
return false;
byte[] nextBytes = br.ReadBytes(4);
if (nextBytes.Length < 4)
return false;
int order = 0;
while (!nextBytes.SequenceEqual(stopBytes))
{
uint sheet_struct_size = BitConverter.ToUInt32(nextBytes, 0);
byte[] sheet_struct = br.ReadBytes((int)sheet_struct_size);
nextBytes = br.ReadBytes(4);
uint sheet_data_size = BitConverter.ToUInt32(nextBytes, 0);
byte[] sheet_data = br.ReadBytes((int)sheet_data_size);
nextBytes = br.ReadBytes(4);
uint sheet_text_size = BitConverter.ToUInt32(nextBytes, 0);
byte[] sheet_text = br.ReadBytes((int)sheet_text_size);
Sheet sheet = ProcessSheet(sheet_struct, sheet_data, sheet_text, order++, sheets.Count);
sheets.Add(sheet);
nextBytes = br.ReadBytes(4);
if (nextBytes.Length < 4)
return false;
}
}
db = [.. sheets];
return true;
}
private static Sheet ProcessSheet(byte[] sheet_struct, byte[] sheet_data, byte[] sheet_text, int order, int debugInfo1 = 0)
{
Sheet sheet = new();
//process struct
uint nameOffset = BitConverter.ToUInt32(sheet_struct, 0);
sheet.name = Utils.ReadStringFromTextData(sheet_text, (int)nameOffset) + $"_{order:D2}";//注意末尾会添加_xx标记顺序
sheet.cols = BitConverter.ToUInt32(sheet_struct, 4);
sheet.col = new Column[sheet.cols];
int offset = 8;
for (int i = 0; i < sheet.cols; i++)
{
Column column = new()
{
type = BitConverter.ToUInt16(sheet_struct, offset)
};
if (column.type == 0x3 || column.type == 0x2)
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 = Utils.ReadStringFromTextData(sheet_text, (int)columnNameOffset) + $"_{column.type}{column.size}"; //给repack留条活路
sheet.col[i] = column;
offset += 8;
}
uint columnSizes = 0;
for (int i = 0; i < sheet.cols; i++)
{
columnSizes += sheet.col[i].size;
}
//process data
offset = 0;
int recordNum = (int)(sheet_data.Length / (columnSizes));//fix bug
Record recordFather = new(recordNum);
for (int i = 0; i < recordNum; i++)
{
Record record = new((int)sheet.cols);
for (int j = 0; j < sheet.cols; j++) //对应cols //色值处理好像有点问题?
{
if (sheet.col[j].type == 4)
{
uint textOffset = BitConverter.ToUInt32(sheet_data, offset);
if (sheet_text.Length < textOffset)
{
Console.WriteLine($"Invalid text offset: {textOffset:X}, sheet: {debugInfo1}, recordNum: {i}, type: {j}");
throw new Exception("Invalid text offset"); //应该不会再发生这种情况了
}
else
{
record.values[j] = Utils.ReadStringFromTextData(sheet_text, (int)textOffset);
}
}
else
{
string n = sheet.col[j].name;
if (sheet.col[j].size == 1)
record.values[j] = sheet_data[offset];
else if (sheet.col[j].size == 2)
record.values[j] = BitConverter.ToInt16(sheet_data, offset);
else if (sheet.col[j].size == 4 && sheet.col[j].type == 1 && n[..^3] == "色") //无奈
record.values[j] = BitConverter.ToUInt32(sheet_data, offset);
else
record.values[j] = BitConverter.ToInt32(sheet_data, offset);
}
offset += sheet.col[j].size; //较小概率还有问题
}
recordFather.values[i] = record;
}
sheet.records = recordFather;
return sheet;
}
public bool ExportDatabase(string? storePath)
{
if (db.Length == 0)
return false;
storePath ??= Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("Unable to determine the directory."); //导出位置
string targetFile = Path.Combine(storePath, dbName + ".db");
Utils.ExtractEmbeddedDatabase(targetFile);
return SqliteProcess(db, targetFile);
}
private static bool SqliteProcess(Sheet[] db, string path)
{
using SqliteConnection connection = new($"Data Source={path};");
connection.Open();
using var transaction = connection.BeginTransaction();
foreach (var sheet in db)
{
using (SqliteCommand createTableCommand = connection.CreateCommand())
{
StringBuilder createTableQuery = new();
createTableQuery.Append($"CREATE TABLE IF NOT EXISTS {sheet.name} (");
// Add columns to the create table query
foreach (var column in sheet.col)
{
createTableQuery.Append($"{column.name} {Utils.GetSQLiteColumnType(column.type)}, ");
}
createTableQuery.Remove(createTableQuery.Length - 2, 2); // Remove the last comma and space
createTableQuery.Append(");");
createTableCommand.CommandText = createTableQuery.ToString();
createTableCommand.ExecuteNonQuery();
}
using SqliteCommand insertDataCommand = connection.CreateCommand();
StringBuilder insertDataQuery = new();
insertDataQuery.Append($"INSERT INTO {sheet.name} (");
// Add column names to the insert data query
foreach (var column in sheet.col)
{
insertDataQuery.Append($"{column.name}, ");
}
insertDataQuery.Remove(insertDataQuery.Length - 2, 2); // Remove the last comma and space
insertDataQuery.Append(") VALUES (");
// Add parameter placeholders to the insert data query
for (int i = 0; i < sheet.cols; i++)
{
insertDataQuery.Append($"@param{i}, ");
}
insertDataQuery.Remove(insertDataQuery.Length - 2, 2); // Remove the last comma and space
insertDataQuery.Append(");");
insertDataCommand.CommandText = insertDataQuery.ToString();
// Add data parameters to the insert data command
for (int i = 0; i < sheet.records.values.Length; i++)
{
var record = (Record)sheet.records.values[i];
for (int j = 0; j < sheet.cols; j++)
{
var parameter = new SqliteParameter($"@param{j}", record.values[j]);
insertDataCommand.Parameters.Add(parameter);
}
insertDataCommand.ExecuteNonQuery();
insertDataCommand.Parameters.Clear();
}
}
transaction.Commit();
return true;
}
public static bool ExportMDB(string sqlitePath)
{
if (!File.Exists(sqlitePath))
return false;
using SqliteConnection connection = new($"Data Source={sqlitePath};");
connection.Open();
var tableNames = new List<string>();
var orders = new List<int>();
using (var command = new SqliteCommand("SELECT name FROM sqlite_master WHERE type='table';", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
string s = reader.GetString(0);
orders.Add(int.Parse(s[^2..]));
tableNames.Add(s);
}
}
var combined = tableNames
.Select((name, index) => new { Name = name, Order = orders[index] })
.OrderBy(x => x.Order)
.ToList();
tableNames = combined.Select(x => x.Name).ToList();
string outputPath = Path.Combine(Path.GetDirectoryName(sqlitePath), Path.GetFileNameWithoutExtension(sqlitePath) + ".bin");
using FileStream fs = new(outputPath, FileMode.Create);
using BinaryWriter bw = new(fs);
bw.Write(fileSignature);//文件头
EncodingProvider provider = CodePagesEncodingProvider.Instance;
Encoding? shiftJis = provider.GetEncoding("shift-jis");
foreach (var tableName in tableNames)
{
uint colsNum = 0;
using (var command = new SqliteCommand($"PRAGMA table_info({tableName});", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
colsNum++;
}
}
uint structSize = 8 + 8 * colsNum;
bw.Write(structSize);//结构体大小
uint textOffset = 0;
bool flag = true; //只有sheetname和columnname需要text表
bool flag2 = true; //第一行是空的吗
List<string> text = []; //只能放不重复
List<string> textMulti = []; //允许重复字符串
ushort[] types = new ushort[colsNum];
ushort[] sizes = new ushort[colsNum];
string[] cnames = new string[colsNum];
using (var command = new SqliteCommand($"PRAGMA table_info({tableName});", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
types[reader.GetInt32(0)] = Utils.GetColumnTypeFromSQLite(reader.GetString(1));
sizes[reader.GetInt32(0)] = Utils.GetColumnSize(reader.GetString(1));
cnames[reader.GetInt32(0)] = reader.GetString(1);
}
}
int recordCount = 0;
using (var command = new SqliteCommand($"SELECT COUNT(*) FROM {tableName};", connection))
{
recordCount = Convert.ToInt32(command.ExecuteScalar());
}
uint dataSize = (uint)(sizes.Sum(x => (uint)x) * recordCount);
using (var command = new SqliteCommand($"SELECT * FROM {tableName};", connection))
using (var reader = command.ExecuteReader())
{
reader.Read();
if (reader.GetFieldType(1) == typeof(int))
{
flag2 = reader.GetInt32(1)==0;
}
else if (reader.GetFieldType(1) == typeof(string))
{
flag2 = string.IsNullOrEmpty(reader.GetString(1));
}
}
List<uint> textOffset1 = [];
for (int i = 0; i < cnames.Length; i++)
{
if (types[i] != 4)
continue;
if (textOffset == 0&&flag2)
{
textOffset++;
flag = false;
}
using var colCommand = new SqliteCommand($"SELECT {cnames[i]} FROM {tableName};", connection);
using var colReader = colCommand.ExecuteReader();
bool first = flag2;
while (colReader.Read())
{
if (first)
{
first = false;
continue;
}
string s = colReader.GetString(0);
int index = textMulti.IndexOf(s);//fix bug
textMulti.Add(s);
if (string.IsNullOrEmpty(s))// empty
{
textOffset1.Add(0);
}
else if (index == -1) // 如果字符串不存在
{
text.Add(s);
textOffset1.Add(textOffset); // 记录偏移量
textOffset += (uint)(shiftJis.GetBytes(s).Length + 1);
}
else
{
textOffset1.Add(textOffset1[index]); // 重复字符串处理
}
}
}
int index1 = textMulti.IndexOf(tableName[..^3]);
textMulti.Add(tableName[..^3]);
if(index1 == -1)
{
text.Add(tableName[..^3]);//表名
textOffset1.Add(textOffset);
textOffset += (uint)shiftJis.GetBytes(tableName[..^3]).Length + 1;
}
else
textOffset1.Add(textOffset1[index1]);
foreach (string c in cnames)
{
index1 = textMulti.IndexOf(c[..^3]);
textMulti.Add(c[..^3]);
if (index1 == -1)
{
text.Add(c[..^3]);//列名
textOffset1.Add(textOffset);
textOffset += (uint)shiftJis.GetBytes(c[..^3]).Length + 1;
}
else
textOffset1.Add(textOffset1[index1]);
}
//
bw.Write(textOffset1[textOffset1.Count - cnames.Length - 1]); //表名在text中的偏移
bw.Write(colsNum);//列数
for (int i = 0; i < colsNum; i++)
{
bw.Write(types[i]);//类型
bw.Write(sizes[i]);//类型大小
bw.Write(textOffset1[textOffset1.Count - cnames.Length + i]); //列名在text中的偏移
}
bw.Write(dataSize);//数据大小
//填充垃圾
if (flag2)
{
byte[] zeroBytes = new byte[sizes.Sum(x => (uint)x)];
bw.Write(zeroBytes);
}
//填充数据
using (var command = new SqliteCommand($"SELECT * FROM {tableName};", connection))
using (var reader = command.ExecuteReader())
{
int index = 0;
bool first = flag2;
while (reader.Read())
{
if (first)
{
first = false;
continue;
}
int j = 0;
for (int i = 0; i < colsNum; i++)
{
int type = types[i];
int size = sizes[i];
string cname = cnames[i][..^3];
if (type == 4)
{
if (flag2)
bw.Write(textOffset1[index + (recordCount - 1) * j++]);//fix bug
else
bw.Write(textOffset1[index + (recordCount) * j++]);
}
else if (cname == "色" && size == 4)
bw.Write((uint)reader.GetInt64(i));
else if (size == 1)
bw.Write(reader.GetByte(i));
else if (size == 2)
bw.Write((ushort)(reader.GetInt16(i)));
else
bw.Write(reader.GetInt32(i));
}
index++;
}
}
bw.Write(textOffset);//文本大小
//bool flag = true; //只有sheetname和columnname需要text表
//bool flag2 = true; //第一行是空的吗
if (!flag)
{
bw.Write((byte)0);//垃圾
}
foreach (var str in text)//文本
{
bw.Write(shiftJis.GetBytes(str));
bw.Write((byte)0);
}
}
bw.Write(stopBytes);
return true;
}
}
}

841
EscudeTools/Define.cs Normal file
View File

@ -0,0 +1,841 @@
//以下是垃圾代码,闲人勿入
namespace EscudeTools
{
public static class Define
{
public static readonly string[] ProcNames =
[
"proc_end", "proc_call", "proc_argv", "proc_argc", "proc_typeof", "proc_int", "proc_float", "proc_abs",
"proc_rand", "proc_min", "proc_max", "proc_rgb", "proc_refer", "proc_credit", "proc_log_new", "proc_log_out",
"proc_title", "proc_auto_save", "proc_is_pass", "proc_event", "proc_scene", "proc_open_name", "proc_notice",
"proc_log_img", "proc_msg_opt", "proc_cf", "proc_cv", "proc_vt", "proc_frame", "proc_text", "proc_vals",
"proc_clear", "proc_gap", "proc_menu_opt", "proc_menu", "proc_wait", "proc_lsf_init", "proc_lsf_set",
"proc_lsf_get", "proc_lsf_break", "proc_dt", "proc_ps", "proc_cg", "proc_cg_org", "proc_cg_set", "proc_cg_get",
"proc_cg_em", "proc_cg_clr", "proc_cg_disp", "proc_path", "proc_tween", "proc_trans", "proc_mot_set",
"proc_mot_get", "proc_quake", "proc_flash", "proc_flt", "proc_ptcl", "proc_sync", "proc_auto_kill", "proc_movie",
"proc_bgm_play", "proc_bgm_stop", "proc_bgm_vol", "proc_bgm_fx", "proc_amb_play", "proc_amb_stop", "proc_amb_vol",
"proc_amb_fx", "proc_se_play", "proc_se_stop", "proc_se_wait", "proc_se_vol", "proc_se_fx", "proc_voc_play",
"proc_voc_stop", "proc_voc_wait", "proc_voc_vol", "proc_voc_fx", "proc_bgv_play", "proc_bgv_stop", "proc_bgv_vol",
"proc_bgv_fx", "proc_set_param", "proc_get_param", "proc_jump", "proc_date", "proc_flow", "proc_diary",
"proc_unlock", "proc_section", "proc_omake"
];
// 说句实话,我觉得这些定义可能会发生变化
public const byte INST_POP = 1;
public const byte INST_POP_N = 2;
public const byte INST_POP_RET = 3;
public const byte INST_PUSH_INT = 4;
public const byte INST_PUSH_FLOAT = 5;
public const byte INST_PUSH_RET = 6;
public const byte INST_PUSH_TEXT = 7;
public const byte INST_PUSH_MESS = 8;
public const byte INST_PUSH_GVAR = 9;
public const byte INST_PUSH_LVAR = 10;
public const byte INST_STORE_GVAR = 11;
public const byte INST_STORE_LVAR = 12;
public const byte INST_ENTER = 13;
public const byte INST_LEAVE = 14;
public const byte INST_JMP = 15;
public const byte INST_JMPZ = 16;
public const byte INST_CALL = 17;
public const byte INST_RET = 18;
public const byte INST_LOG_OR = 19;
public const byte INST_LOG_AND = 20;
public const byte INST_LOG_NOT = 21;
public const byte INST_OR = 22;
public const byte INST_XOR = 23;
public const byte INST_AND = 24;
public const byte INST_NOT = 25;
public const byte INST_CMP_EQ = 26;
public const byte INST_CMP_NE = 27;
public const byte INST_CMP_LT = 28;
public const byte INST_CMP_LE = 29;
public const byte INST_CMP_GT = 30;
public const byte INST_CMP_GE = 31;
public const byte INST_SHL = 32;
public const byte INST_SHR = 33;
public const byte INST_ADD = 34;
public const byte INST_SUB = 35;
public const byte INST_MUL = 36;
public const byte INST_DIV = 37;
public const byte INST_MOD = 38;
public const byte INST_NEG = 39;
public const byte INST_NAME = 40;
public const byte INST_TEXT = 41;
public const byte INST_PAGE = 42;
public const byte INST_OPTION = 43;
public const byte INST_PROC = 44;
public const byte INST_LINE = 45;
public static string GetInstructionString(byte instruction, out int paramNum)
{
paramNum = instruction switch
{
INST_POP_N or INST_PUSH_INT or INST_PUSH_FLOAT or INST_PUSH_TEXT or INST_PUSH_MESS or INST_PUSH_GVAR or
INST_PUSH_LVAR or INST_STORE_GVAR or INST_STORE_LVAR or INST_ENTER or INST_JMP or INST_JMPZ or INST_CALL or
INST_NAME or INST_TEXT or INST_PROC or INST_LINE => 1,
INST_OPTION => 2,
_ => 0
};
return instruction switch
{
INST_POP => "INST_POP",
INST_POP_N => "INST_POP_N",
INST_POP_RET => "INST_POP_RET",
INST_PUSH_INT => "INST_PUSH_INT",
INST_PUSH_FLOAT => "INST_PUSH_FLOAT",
INST_PUSH_RET => "INST_PUSH_RET",
INST_PUSH_TEXT => "INST_PUSH_TEXT",
INST_PUSH_MESS => "INST_PUSH_MESS",
INST_PUSH_GVAR => "INST_PUSH_GVAR",
INST_PUSH_LVAR => "INST_PUSH_LVAR",
INST_STORE_GVAR => "INST_STORE_GVAR",
INST_STORE_LVAR => "INST_STORE_LVAR",
INST_ENTER => "INST_ENTER",
INST_LEAVE => "INST_LEAVE",
INST_JMP => "INST_JMP",
INST_JMPZ => "INST_JMPZ",
INST_CALL => "INST_CALL",
INST_RET => "INST_RET",
INST_LOG_OR => "INST_LOG_OR",
INST_LOG_AND => "INST_LOG_AND",
INST_LOG_NOT => "INST_LOG_NOT",
INST_OR => "INST_OR",
INST_XOR => "INST_XOR",
INST_AND => "INST_AND",
INST_NOT => "INST_NOT",
INST_CMP_EQ => "INST_CMP_EQ",
INST_CMP_NE => "INST_CMP_NE",
INST_CMP_LT => "INST_CMP_LT",
INST_CMP_LE => "INST_CMP_LE",
INST_CMP_GT => "INST_CMP_GT",
INST_CMP_GE => "INST_CMP_GE",
INST_SHL => "INST_SHL",
INST_SHR => "INST_SHR",
INST_ADD => "INST_ADD",
INST_SUB => "INST_SUB",
INST_MUL => "INST_MUL",
INST_DIV => "INST_DIV",
INST_MOD => "INST_MOD",
INST_NEG => "INST_NEG",
INST_NAME => "INST_NAME",
INST_TEXT => "INST_TEXT",
INST_PAGE => "INST_PAGE",
INST_OPTION => "INST_OPTION",
INST_PROC => "INST_PROC",
INST_LINE => "INST_LINE",
_ => "UNKNOWN INSTRUCTION"
};
}
public static object TyperHelper(byte instruction, byte[] code, int i)
{
return instruction switch
{
INST_POP_N or INST_PUSH_TEXT or INST_PUSH_MESS or INST_PUSH_LVAR or INST_STORE_GVAR or INST_STORE_LVAR or INST_ENTER or INST_JMP or INST_JMPZ or INST_CALL or INST_TEXT or INST_OPTION or INST_PROC => BitConverter.ToUInt32(code, i),
INST_PUSH_FLOAT => BitConverter.ToSingle(code, i),
_ => (object)BitConverter.ToInt32(code, i),
};
}
public static string SetCommandStr(Command c, ScriptFile sf, ScriptMessage? sm, ref int messIndex)
{
//__cdecl
switch (c.Instruction)
{
case INST_POP:
{
Mark(sf, 1);
return "Pop a value";
}
case INST_POP_N:
{
Mark(sf, BitConverter.ToUInt32(c.Parameter));
return $"Pop multiple values";
}
case INST_POP_RET:
return $"Pop a return value";
case INST_PUSH_INT:
return $"Push an integer value";
case INST_PUSH_FLOAT:
return $"Push a floating-point value";
case INST_PUSH_RET:
return $"Push the return value";
case INST_PUSH_TEXT://并非所有的TEXT都会使用
return $"Push a string: {sf.TextString[BitConverter.ToUInt32(c.Parameter)]}";
case INST_PUSH_MESS://并非所有的MESS都会使用
{
messIndex++;
return $"{((sm == null) ? ",Mess" : sm.DataString[messIndex - 1])}";
}
case INST_PUSH_GVAR:
return $"Push a global variable";
case INST_PUSH_LVAR:
return $"Push a local variable";
case INST_STORE_GVAR:
return $"Assign to a global variable";
case INST_STORE_LVAR:
return $"Assign to a local variable";
case INST_ENTER:
return $"Function start";
case INST_LEAVE:
return $"Function end";
case INST_JMP:
return $"Jump";
case INST_JMPZ:
return $"Conditional jump";
case INST_CALL:
return $"Call function offset: {BitConverter.ToUInt32(c.Parameter) + 1}";
case INST_RET:
return $"Return";
case INST_LOG_OR:
return $"Logical OR";
case INST_LOG_AND:
return $"Logical AND";
case INST_LOG_NOT:
return $"Logical NOT";
case INST_OR:
return $"Bitwise OR";
case INST_XOR:
return $"Bitwise XOR";
case INST_AND:
return $"Bitwise AND";
case INST_NOT:
return $"Bitwise NOT";
case INST_CMP_EQ:
return $"Comparison(equal)";
case INST_CMP_NE:
return $"Comparison(not equal)";
case INST_CMP_LT:
return $"Comparison(less than)";
case INST_CMP_LE:
return $"Comparison(less than or equal)";
case INST_CMP_GT:
return $"Comparison(greater than)";
case INST_CMP_GE:
return $"Comparison(greater than or equal)";
case INST_SHL:
return $"Left bitwise shift";
case INST_SHR:
return $"Right bitwise shift";
case INST_ADD:
return $"Add";
case INST_SUB:
return $"Sub";
case INST_MUL:
return $"Multiplication";
case INST_DIV:
return $"Division";
case INST_MOD:
return $"Mod(remainder)";
case INST_NEG:
return $"Negation(sign reversal)";
case INST_NAME:
return $"Character name";
case INST_PAGE:
return $"Message page break";
case INST_OPTION:
return $"Set menu option";
case INST_LINE:
return $"File line number";
case INST_PROC:
uint index = BitConverter.ToUInt32(c.Parameter);
return $"Execute built-in function: {ProcNames[index]} {SetExtStr(c, sf)}";
case INST_TEXT:
messIndex++;
return (sm == null) ? "意外的指令此表无Mess" : sm.DataString[messIndex - 1];
default:
return "UNKNOWN";
}
}
private static void Mark(ScriptFile sf, uint j)
{
int k = sf.Commands.Count - 1;
for (int i = 0; i < j; i++)
{
if (sf.Commands[k].Instruction <= 10 && sf.Commands[k].Instruction >= 4 && !sf.Commands[k].IsProcSet)
{
sf.Commands[k--].IsProcSet = true;
}
}
}
private static string SetExtStr(Command c, ScriptFile sf)
{
switch (BitConverter.ToUInt32(c.Parameter))
{
case 0:
{
//(0 = 标题 / -1 = 游戏结束,保留数据 / 1以上 = 通关,保留数据)
string[] ps = ["结束代码"];
SetExtStr1(ps, sf);
return "脚本结束";
}
case 1:
{
string[] ps = ["脚本编号"];
SetExtStr1(ps, sf);
return "调用文件";
}
case 2:
{
string[] ps = ["参数索引"];
SetExtStr1(ps, sf);
return "获取函数帧的参数";
}
case 3:
{
string[] ps = ["参数数量"];
SetExtStr1(ps, sf);
return "获取函数帧的参数数量";
}
case 4:
{
string[] ps = ["值"];
SetExtStr1(ps, sf);
return "获取变量的类型";
}
case 5:
{
string[] ps = ["值"];
SetExtStr1(ps, sf);
return "转换为整数";
}
case 6:
{
string[] ps = ["值"];
SetExtStr1(ps, sf);
return "转换为浮点数";
}
case 7:
{
string[] ps = ["值"];
SetExtStr1(ps, sf);
return "绝对值";
}
case 8:
{
string[] ps = ["可变长度参数"];
SetExtStr1(ps, sf);
return "获取随机数";
}
case 9:
{
string[] ps = ["可变长度参数"];
SetExtStr1(ps, sf);
return "获取最小值";
}
case 10:
{
string[] ps = ["可变长度参数"];
SetExtStr1(ps, sf);
return "获取最大值";
}
case 11:
{
string[] ps = ["R", "G", "B", "A"];
SetExtStr1(ps, sf);
return "将RGB值转换为颜色代码";
}
case 12:
{
string[] ps = ["索引", "可变长度参数"];
SetExtStr1(ps, sf);
return "引用转换";
}
case 13:
{
string[] ps = ["编号"];
SetExtStr1(ps, sf);
return "员工名单滚动";
}
case 14:
{
string[] ps = ["文件名"];
SetExtStr1(ps, sf);
return "新建日志";
}
case 15:
{
string[] ps = ["文件名", "输出值..."];
SetExtStr1(ps, sf);
return "输出日志";
}
case 16:
{
string[] ps = ["标题字符串"];
SetExtStr1(ps, sf);
return "设置场景标题";
}
case 17:
{
return "自动保存";
}
case 18:
{
string[] ps = ["脚本编号"];
SetExtStr1(ps, sf);
return "获取脚本通过标志";
}
case 19:
{
string[] ps = ["CG编号"];
SetExtStr1(ps, sf);
return "设置CG欣赏标志";
}
case 20:
{
string[] ps = ["脚本编号"];
SetExtStr1(ps, sf);
return "设置场景回想标志";
}
case 21:
{
string[] ps = ["名称编号"];
SetExtStr1(ps, sf);
return "打开名称";
}
case 22:
{
string[] ps = ["文本", "显示时间", "强制标志"];
SetExtStr1(ps, sf);
return "显示通知文本";
}
case 23:
{
string[] ps = ["角色ID"];
SetExtStr1(ps, sf);
return "设置日志中的角色图像";
}
case 24:
{
string[] ps = ["文本控制ID", "设置标志"];
SetExtStr1(ps, sf);
return "文本控制标志的设置";
}
case 25:
{
string[] ps = ["图像ID", "表情ID"];
SetExtStr1(ps, sf);
return "表情指定";
}
case 26:
{
string[] ps = ["声音编号"];
SetExtStr1(ps, sf);
return "声音指定";
}
case 27:
{
string[] ps = ["时间", "音轨"];
SetExtStr1(ps, sf);
return "声音的播放时间等待";
}
case 28:
{
string[] ps = ["框架编号"];
SetExtStr1(ps, sf);
return "文本框架的更改";
}
case 29:
{
//显示标志 (0 = 隐藏 / 1 = 显示)
//显示时间 (1 / 1000毫秒)
string[] ps = ["显示标志", "显示时间"];
SetExtStr1(ps, sf);
return "文本框架的显示/隐藏";
}
case 30:
{
string[] ps = ["可变长参数"];
SetExtStr1(ps, sf);
return "文本引用值的设置";
}
case 31:
{
string[] ps = ["时间(1/1000 ms)"];
SetExtStr1(ps, sf);
return "文本消息的消去";
}
case 32:
{
string[] ps = ["x间隔", "y间隔"];
SetExtStr1(ps, sf);
return "文本间距的设置";
}
case 33:
{
string[] ps = ["选择允许标志"];
SetExtStr1(ps, sf);
return "菜单选项的设置";
}
case 34:
{
string[] ps = ["随机显示标志", "流程分支标志"];
SetExtStr1(ps, sf);
return "菜单选择";
}
case 35:
{
string[] ps = ["延迟时间 (以毫秒为单位)", "点击取消标志"];
SetExtStr1(ps, sf);
return "延迟处理";
}
case 36:
{
string[] ps = ["角色ID"];
SetExtStr1(ps, sf);
return "初始化LSF部件";
}
case 37:
{
string[] ps = ["角色ID", "部件状态..."];
SetExtStr1(ps, sf);
return "设置LSF部件状态";
}
case 38:
{
string[] ps = ["角色ID", "部件ID"];
SetExtStr1(ps, sf);
return "获取LSF部件状态";
}
case 39:
{
string[] ps = ["角色ID", "部件ID..."];
SetExtStr1(ps, sf);
return "LSF部件破坏效果";
}
case 40:
{
string[] ps = ["相对值"];
SetExtStr1(ps, sf);
return "相对指定显示对象属性";
}
case 41:
{
string[] ps = ["显示对象..."];
SetExtStr1(ps, sf);
return "指定显示对象列表";
}
case 42:
{
string[] ps = ["显示对象", "图像ID", "表情ID", "分辨率等级", "滤镜ID", "Z", "X", "Y", "水平缩放率", "垂直缩放率", "旋转角度", "不透明度"];
SetExtStr1(ps, sf);
return "设置显示对象";
}
case 43:
{
string[] ps = ["显示对象", "X", "Y"];
SetExtStr1(ps, sf);
return "设置显示对象的原点坐标";
}
case 44:
{
string[] ps = ["显示对象", "属性ID", "属性值"];
SetExtStr1(ps, sf);
return "设置显示对象的属性值";
}
case 45:
{
string[] ps = ["显示对象", "属性ID"];
SetExtStr1(ps, sf);
return "获取显示对象的属性值";
}
case 46:
{
string[] ps = ["显示对象", "效果编号", "部位ID", "相对于部位的X坐标", "相对于部位的Y坐标", "显示顺序"];
SetExtStr1(ps, sf);
return "设置情感效果";
}
case 47:
{
string[] ps = ["显示对象列表"];
SetExtStr1(ps, sf);
return "删除显示对象";
}
case 48:
{
string[] ps = ["效果编号", "显示时间 (ms)", "时间曲线", "翻转规则图像标志", "逆向播放标志", "颜色"];
SetExtStr1(ps, sf);
return "更新屏幕";
}
case 49:
{
string[] ps = ["显示对象", "时间", "X", "Y", "水平缩放率", "垂直缩放率", "旋转角度", "不透明度"];
SetExtStr1(ps, sf);
return "设置插值";
}
case 50:
{
string[] ps = ["显示对象", "插值动画ID"];
SetExtStr1(ps, sf);
return "插值动画";
}
case 51:
{
return "插值";
}
case 52:
{
string[] ps = ["显示对象", "变量编号", "值"];
SetExtStr1(ps, sf);
return "设置MOT的变量";
}
case 53:
{
string[] ps = ["显示对象", "变量编号"];
SetExtStr1(ps, sf);
return "获取MOT的变量";
}
case 54:
{
string[] ps = ["显示对象", "X轴震动像素数", "Y轴震动像素数", "效果时间"];
SetExtStr1(ps, sf);
return "屏幕震动";
}
case 55:
{
string[] ps = ["闪光颜色", "闪光次数"];
SetExtStr1(ps, sf);
return "闪光效果";
}
case 56:
{
string[] ps = ["滤镜ID", "模式", "颜色", "水平模糊", "垂直模糊"];
SetExtStr1(ps, sf);
return "设置颜色滤镜";
}
case 57:
{
string[] ps = ["效果编号"];
SetExtStr1(ps, sf);
return "设置特殊效果";
}
case 58:
{
string[] ps = ["效果标志", "取消标志"];
SetExtStr1(ps, sf);
return "屏幕效果等待";
}
case 59:
{
string[] ps = ["显示对象", "自动消失标志"];
SetExtStr1(ps, sf);
return "设置自动消失标志";
}
case 60:
{
string[] ps = ["视频编号"];
SetExtStr1(ps, sf);
return "播放视频";
}
case 61:
{
string[] ps = ["BGM编号", "淡入时间", "音量", "播放开始时间"];
SetExtStr1(ps, sf);
return "播放BGM";
}
case 62:
{
string[] ps = ["淡出时间"];
SetExtStr1(ps, sf);
return "停止BGM";
}
case 63:
{
string[] ps = ["音量值", "淡入淡出时间"];
SetExtStr1(ps, sf);
return "设置BGM音量";
}
case 64:
{
string[] ps = ["效果编号"];
SetExtStr1(ps, sf);
return "设置BGM效果";
}
case 65:
{
string[] ps = ["环境音编号", "淡入时间", "音量", "播放开始时间"];
SetExtStr1(ps, sf);
return "播放环境音";
}
case 66:
{
string[] ps = ["淡出时间"];
SetExtStr1(ps, sf);
return "停止环境音";
}
case 67:
{
string[] ps = ["音量值", "淡入淡出时间"];
SetExtStr1(ps, sf);
return "设置环境音音量";
}
case 68:
{
string[] ps = ["效果编号"];
SetExtStr1(ps, sf);
return "设置环境音效果";
}
case 69:
{
string[] ps = ["轨道", "音效编号", "循环标志", "淡入时间", "音量", "播放开始时间"];
SetExtStr1(ps, sf);
return "播放音效";
}
case 70:
{
string[] ps = ["轨道", "淡出时间"];
SetExtStr1(ps, sf);
return "停止音效";
}
case 71:
{
string[] ps = ["轨道"];
SetExtStr1(ps, sf);
return "音效等待";
}
case 72:
{
string[] ps = ["轨道", "音量值", "淡入淡出时间"];
SetExtStr1(ps, sf);
return "设置音效音量";
}
case 73:
{
string[] ps = ["效果编号"];
SetExtStr1(ps, sf);
return "设置音效效果";
}
case 74:
{
string[] ps = ["轨道", "语音编号", "淡入时间", "音量", "播放开始时间"];
SetExtStr1(ps, sf);
return "播放角色语音";
}
case 75:
{
string[] ps = ["轨道", "淡出时间"];
SetExtStr1(ps, sf);
return "停止角色语音";
}
case 76:
{
string[] ps = ["轨道"];
SetExtStr1(ps, sf);
return "角色语音等待";
}
case 77:
{
string[] ps = ["轨道", "音量值", "淡入淡出时间"];
SetExtStr1(ps, sf);
return "设置角色语音音量";
}
case 78:
{
string[] ps = ["效果编号"];
SetExtStr1(ps, sf);
return "设置角色语音效果";
}
case 79:
{
string[] ps = ["轨道", "语音编号", "淡入时间", "音量", "播放开始时间"];
SetExtStr1(ps, sf);
return "播放BGV";
}
case 80:
{
string[] ps = ["轨道", "淡出时间"];
SetExtStr1(ps, sf);
return "停止BGV";
}
case 81:
{
string[] ps = ["轨道", "音量值", "淡入淡出时间"];
SetExtStr1(ps, sf);
return "设置BGV音量";
}
case 82:
{
string[] ps = ["效果编号"];
SetExtStr1(ps, sf);
return "设置BGV效果";
}
case 83:
{
string[] ps = ["类别ID", "参数ID", "参数值"];
SetExtStr1(ps, sf);
return "设置参数";
}
case 84:
{
string[] ps = ["类别ID", "参数ID"];
SetExtStr1(ps, sf);
return "获取参数";
}
case 85:
{
string[] ps = ["脚本ID"];
SetExtStr1(ps, sf);
return "文件跳转";
}
case 86:
{
string[] ps = ["经过天数"];
SetExtStr1(ps, sf);
return "更新日期";
}
case 87:
{
string[] ps = ["脚本ID"];
SetExtStr1(ps, sf);
return "更新并显示流程图";
}
case 88:
{
string[] ps = ["日记ID", "附加标志", "等待标志"];
SetExtStr1(ps, sf);
return "更新并显示日记";
}
case 89:
{
string[] ps = ["脚本ID", "父脚本ID"];
SetExtStr1(ps, sf);
return "解锁区块";
}
case 90:
{
string[] ps = ["章节ID"];
SetExtStr1(ps, sf);
return "设置章节";
}
case 91:
{
return "附加内容";
}
}
return "";
}
private static void SetExtStr1(string[] ps, ScriptFile sf)
{
int i = sf.Commands.Count - 2;
sf.Commands[i + 1].IsProcSet = true;
for (int k = 0; k < ps.Length; k++)
{
if (sf.Commands[i].IsProcSet || sf.Commands[i].Instruction < 4 || sf.Commands[i].Instruction > 10)
{
k--;
i--;
continue;
}
sf.Commands[i].Helper += $": {ps[k]}";
sf.Commands[i].IsProcSet = true;
i--;
}
}
}
}

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="empty.db" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.0.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.10" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageReference Include="System.Threading.Tasks" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="empty.db">
<CopyToOutputDirectory></CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>

243
EscudeTools/ImageManager.cs Normal file
View File

@ -0,0 +1,243 @@
using ImageMagick;
using System.Text;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace EscudeTools
{
public class Image
{
//public byte[] file = new byte[64]; // Image file name
//public int page; // Image memory
//public int back_page; // Back image memory
public uint width; // Width
public uint height; // Height
public uint depth; // Color depth
//public int id; // ID
public int reff; // Reference counter
//public bool cache; // Cache flag
public bool isFile; // Is it an image file
//public uint[] extra = new uint[8]; // Reserved
public string fileStr; // 自己加的,用于保存文件名
}
public class GInfo
{
public int width; // Width
public int height; // Height
public int depth; // Color depth
public string pixel; // address of the pixel data
public uint pitch;
public string palette;
}
public class LsfImage
{
//public bool cache; // Cache flag
public Image img; // Layer image
}
public class LsfData
{
//public byte[] path = new byte[64]; // LSF folder
public LsfFileHeader lfh; // LSF file header
public LsfLayerInfo[] lli; // LSF layer information
public LsfImage[] layer; // LSF layer image
public string pathStr;
public string lsfName;
}
public class LsfFileHeader
{
//public uint signature; // Header signature (LSF) 0x46534C
public ushort revision; // Revision number
public ushort bg; // Background flag
public ushort id; // ID
public ushort layer_count; // Number of layers
public int width; // Width in pixels
public int height; // Height in pixels
public int bx; // Base coordinates
public int by; // Base coordinates
}
public class LsfLayerInfo
{
public byte[] name = new byte[64]; // File name
public byte[] text = new byte[64]; // Generic string
public Rect rect; // Layer position
public int cx; // Center coordinates
public int cy; // Center coordinates
public byte index; // Position
public byte state; // State
public byte mode; // Drawing mode
public byte opacity; // Opacity
public uint fill; // Fill color
public uint value; // Generic value
public string nameStr; // 自己加的,用于保存文件名
public string textStr; // 自己加的,用于保存通用名
public string indexStr; // Position str
public string stateStr; // State str
public string modeStr; // Drawing mode str
public string opacityStr; // Opacity str
public bool skip = false; // 是否跳过
}
public class Rect
{
public int left; // Top-left corner X coordinate of the rectangle
public int top; // Top-left corner Y coordinate of the rectangle
public int right; // Bottom-right corner X coordinate of the rectangle
public int bottom; // Bottom-right corner Y coordinate of the rectangle
}
public class CgInfo
{
public int kind; // Image category
public int index; // Image index
public int x; // Coordinates
public int y; // Coordinates
public int scale; // Scale factor
public bool loop; // Loop flag
public byte[] name = new byte[64]; // Registered name
public byte[] file = new byte[64]; // File name
public byte[] option = new byte[128]; // Options
public uint coverd; // White-out ID
public uint filter; // Filter
public uint color; // Color
public uint id; // Image identification ID
public uint loc; // Coordinate list
public uint spot; // Coordinate index
public int order; // CG viewing display order
public uint link; // Related CG
public string nameStr;
public string fileStr;
public string optionStr;
}
public class ImageManager
{
static readonly byte[] lsfFileSignature = [0x4C, 0x53, 0x46, 0x00];
static readonly byte[] lsfLayerSkipSignature = [0x00, 0x75, 0x6C, 0x00]; //flowchat部分的lsf块
static readonly byte[] motV1Signature = [0x6D, 0x6F, 0x74, 0x00]; // mot v1 file signature
static readonly byte[] motV2Signature = [0x4D, 0x4F, 0x54, 0x00]; // MOT v2 file signature
private static string WorkPath = string.Empty;
private LsfData lsfData = new();
private List<LsfData> lsfDatas = [];
private bool preFetchInfo;
public bool LoadLsf(string path, bool preFI = false)
{
if (!File.Exists(path))
return false;
preFetchInfo = preFI;
lsfData.pathStr = Path.GetDirectoryName(path);
lsfData.lsfName = Path.GetFileNameWithoutExtension(path);
lsfData.lfh = LoadLsfHeader(path);
lsfData.lli = LoadLsfLayerInfo(path);
lsfData.layer = new LsfImage[lsfData.lfh.layer_count];
for (int i = 0; i < lsfData.lfh.layer_count; i++)
{
string imgPath = Path.Combine(lsfData.pathStr, lsfData.lli[i].nameStr + ".png");
LsfImage li = new();
if (!lsfData.lli[i].skip)
{
li.img = LoadLsfImage(imgPath);
lsfData.layer[i] = li;
}
}
lsfDatas.Add(lsfData);
lsfData = new();
return true;
}
private LsfFileHeader LoadLsfHeader(string path)
{
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
if (fs.Length < 0x1C)
throw new Exception("Invalid LSF Header");
using var br = new BinaryReader(fs);
byte[] head = br.ReadBytes(4);
if (!head.SequenceEqual(lsfFileSignature))
throw new Exception("Invalid LSF file");
LsfFileHeader lfh = new()
{
//lfh.signature = br.ReadUInt32(); //无用
revision = br.ReadUInt16(),
bg = br.ReadUInt16(),
id = br.ReadUInt16(),
layer_count = br.ReadUInt16(),
width = br.ReadInt32(),
height = br.ReadInt32(),
bx = br.ReadInt32(),
by = br.ReadInt32()
};
return lfh;
}
private LsfLayerInfo[] LoadLsfLayerInfo(string path)
{
EncodingProvider provider = CodePagesEncodingProvider.Instance;
Encoding? shiftJis = provider.GetEncoding("shift-jis");
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
using var br = new BinaryReader(fs);
br.ReadBytes(0x1C); // Skip the header
long remainingBytes = br.BaseStream.Length - br.BaseStream.Position;
if (remainingBytes != lsfData.lfh.layer_count * 0xA4)
throw new Exception("Invalid LSF Layer Info");
LsfLayerInfo[] llis = new LsfLayerInfo[lsfData.lfh.layer_count];
for (int i = 0; i < lsfData.lfh.layer_count; i++)
{
LsfLayerInfo l = new()
{
name = br.ReadBytes(64),
text = br.ReadBytes(64),
rect = new Rect
{
left = br.ReadInt32(),
top = br.ReadInt32(),
right = br.ReadInt32(),
bottom = br.ReadInt32()
},
cx = br.ReadInt32(),
cy = br.ReadInt32(),
index = br.ReadByte(),
state = br.ReadByte(),
mode = br.ReadByte(),
opacity = br.ReadByte(),
fill = br.ReadUInt32(),
value = br.ReadUInt32()
};
if (l.name.Take(4).SequenceEqual(lsfLayerSkipSignature))//临时处理
l.skip = true;
l.nameStr = shiftJis.GetString(l.name).TrimEnd('\0');
l.textStr = shiftJis.GetString(l.text).TrimEnd('\0');
l.indexStr = l.index.ToString().TrimEnd('\0');
l.stateStr = l.state.ToString().TrimEnd('\0');
l.modeStr = l.mode.ToString().TrimEnd('\0');
l.opacityStr = l.opacity.ToString().TrimEnd('\0');
llis[i] = l;
}
return llis;
}
private Image LoadLsfImage(string imgPath)
{
if (!File.Exists(imgPath))
throw new Exception("Image file not found");//一般文件都是存在的不存在是因为这是特殊lsf
Image i = new()
{
fileStr = imgPath,
isFile = true,
reff = 1
};
if (preFetchInfo)
{
using var image = new MagickImage(imgPath);
i.width = image.Width;
i.height = image.Height;
i.depth = image.Depth;
}
return i;
}
}
}

271
EscudeTools/PackManager.cs Normal file
View File

@ -0,0 +1,271 @@
//这里的提取代码参考(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 uint LoadedKey;
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 bool LoadKey(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;
br.ReadByte();
m_seed = br.ReadUInt32();
}
LoadedKey = m_seed;
isLoaded = true;
return true;
}
private List<Entry>? ProcessV1(BinaryReader br)
{
m_seed = br.ReadUInt32();
LoadedKey = m_seed;
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();
LoadedKey = m_seed;
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, int version, bool useCustomKey = false, string customKeyProviderPath = "") //目前支持v2v1
{
if (useCustomKey)
LoadKey(customKeyProviderPath);
GeneratePItem(path);
m_seed = isLoaded ? LoadedKey : 2210579460;
string outputPath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileName(path) + ".bin");
using (FileStream fs = new(outputPath, FileMode.Create))
using (BinaryWriter bw = new(fs))
{
bw.Write(fileSignature);
bw.Write(supportPackVersion[version - 1]);
bw.Write(m_seed);
m_count = (uint)pItem.Count;
bw.Write(m_count ^ NextKey());
EncodingProvider provider = CodePagesEncodingProvider.Instance;
Encoding? shiftJis = provider.GetEncoding("shift-jis");
if (version == 1) //未经测试
{
long storeOffset = 0x10 + m_count * 0x88;
for (int i = 0; i < m_count; i++)
{
byte[] strbytes = shiftJis.GetBytes(pItem[i].Name);
byte[] result = new byte[80];
int lengthToCopy = Math.Min(strbytes.Length, 78);
Array.Copy(strbytes, result, lengthToCopy);
bw.Write(result);
bw.Write(storeOffset);
bw.Write(pItem[i].Size);
storeOffset += pItem[i].Size;
}
}
else
{
uint namesSize = (uint)pItem.Sum(e => e.Name.Length + 1);
bw.Write(namesSize ^ NextKey());
uint filenameOffset = 0;
long storeOffset = 0x14 + m_count * 12 + namesSize;
byte[] index = new byte[m_count * 12];
int indexOffset = 0;
for (int i = 0; i < m_count; i++)
{
BitConverter.GetBytes(filenameOffset).CopyTo(index, indexOffset);
indexOffset += 4;
BitConverter.GetBytes(storeOffset).CopyTo(index, indexOffset);
indexOffset += 4;
BitConverter.GetBytes(pItem[i].Size).CopyTo(index, indexOffset);
indexOffset += 4;
filenameOffset += (uint)pItem[i].Name.Length + 1;
storeOffset += pItem[i].Size;
}
Decrypt(ref index);
bw.Write(index);
foreach (Entry entry in pItem)
{
byte[] nameBytes = shiftJis.GetBytes(entry.Name);
bw.Write(nameBytes);
bw.Write((byte)0);
}
}
foreach (Entry entry in pItem)
{
byte[] data = File.ReadAllBytes(Path.Combine(path, entry.Name));
bw.Write(data);
}
}
return true;
}
private void GeneratePItem(string path)
{
pItem.Clear();
var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
var relativePath = Path.GetRelativePath(path, file);
var fileInfo = new FileInfo(file);
pItem.Add(new Entry
{
Name = relativePath,
Size = (uint)fileInfo.Length
});
}
m_count = (uint)pItem.Count;
}
}
}

226
EscudeTools/Program.cs Normal file
View File

@ -0,0 +1,226 @@
namespace EscudeTools
{
internal class Program
{
static void Main(string[] args)
{
if (Directory.Exists(args[0]))
{
string[] files = Directory.GetFiles(args[0], "*.lsf", SearchOption.AllDirectories);
ImageManager im = new();
foreach (string file in files)
{
if (im.LoadLsf(file))
Console.WriteLine($"Load {file} Success");
else
{
Console.WriteLine($"Load {file} Failed");
}
}
Console.WriteLine("OK");
// NOTE
// 推荐使用DB Browser for SQLite (https://sqlitebrowser.org/) 查看、编辑导出的数据库文件
// 这不是广告,这只是我在开发期间使用的工具
////Batch Unpack ESC-ARC Package
//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");
// }
// if (pm.Extract())
// Console.WriteLine("Extract Package Success");
// else
// {
// Console.WriteLine("Extract Package Failed");
// }
// }
//}
////Batch Repack ESC-ARC Package
//if (Directory.Exists(args[0]) && Directory.Exists(args[1]))
//{
// string[] directories = Directory.GetDirectories(args[0]);
// foreach (string directory in directories)
// {
// PackManager pm = new();
// string providerFilePath = Path.Combine(args[1], Path.GetFileName(directory) + ".bin");
// if (pm.Repack(directory, 2,true, providerFilePath))
// Console.WriteLine("Repack Package Success");
// else
// {
// Console.WriteLine("Repack Package Failed");
// return;
// }
// }
//}
////Batch Unpack Script(Full, Text, Mess)
//if (Directory.Exists(args[0]))
//{
// string[] files = Directory.GetFiles(args[0], "*.bin");
// foreach (string file in files)
// {
// ScriptManager smr = new();
// if (smr.LoadScriptFile(file))
// {
// Console.WriteLine($"Load {file} Success");
// }
// else
// {
// Console.WriteLine($"Load {file} Failed");
// return;
// }
// if (smr.ExportDatabase(Path.GetDirectoryName(args[0])))
// Console.WriteLine("Export Script Success");
// else
// {
// Console.WriteLine("Export Script Failed");
// return;
// }
// if (smr.ExportTextDatabase(Path.GetDirectoryName(args[0])))
// Console.WriteLine("Export Text Success");
// else
// {
// Console.WriteLine("Export Text Failed");
// return;
// }
// if (smr.ExportMessDatabase(Path.GetDirectoryName(args[0])))
// Console.WriteLine("Export Mess Success");
// else
// {
// Console.WriteLine("Export Mess Failed");
// return;
// }
// }
//}
////Export Full Script
//if (File.Exists(args[0])) //fail //lost 1 //something diff
//{
// ScriptManager.Repackv1(args[0], true);
//}
////Export ScriptMessage
//if (File.Exists(args[1])) //pass
//{
// ScriptManager.Repackv2(args[1], true);
//}
//////Export ScriptFile
//if (File.Exists(args[2])) //pass
//{
// ScriptManager.Repackv3(args[2]);
//}
//ScriptManager smr = new();
//smr.LoadScriptFile(args[0]); //加载.bin文件
//smr.ExportDatabase(Path.GetDirectoryName(args[0]));
//smr.ExportMessDatabase(Path.GetDirectoryName(args[0]));
//return;
//if (Directory.Exists(args[0]))
//{
// string[] files = Directory.GetFiles(args[0], "*.db");
// foreach (string file in files)
// {
// DatabaseManager.ExportMDB(file);
// }
//}
//if (Directory.Exists(args[0]))
//{
// string[] files = Directory.GetFiles(args[0], "*.bin");
// DatabaseManager dm = new();
// foreach (string file in files)
// {
// dm.LoadDatabase(file);
// if (dm.ExportDatabase(Path.GetDirectoryName(args[0])))
// Console.WriteLine("Export Database Success");
// }
//}
//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(Path.GetDirectoryName(args[0])))
// Console.WriteLine("Export Database Success");
// else
// {
// Console.WriteLine("Export Database Failed");
// return;
// }
// }
//}
// if (args.Length == 0 || args.Length > 2)
// {
// Console.WriteLine("Invalid arguments. Use -h for help.");
// return;
// }
// switch (args[0])
// {
// case "-h":
// case "-r":
// case "-d":
// case "-s":
// default:
// break;
// }
//}
//static void DisplayHelp()
//{
// Console.WriteLine("Usage: EscudeTools.exe [-r <filepath>] [-d <directory>] [-s <filepath>] [-h]");
// Console.WriteLine("Options:");
// Console.WriteLine(" <filepath> Single lsf process");
// Console.WriteLine(" -r <filepath> Read single lsf file");
// Console.WriteLine(" -d <directory> Process all lsf files in directory");
// Console.WriteLine(" -s <filepath> Same as <filepath>");
// Console.WriteLine(" -h Display help info");
//}
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"profiles": {
"EscudeTools": {
"commandName": "Project",
"commandLineArgs": "G:\\x221.local\\lab2"
}
}
}

View File

@ -0,0 +1,802 @@
using Microsoft.Data.Sqlite;
using System.Reflection;
using System.Text;
namespace EscudeTools
{
public class ScriptMessage
{
public byte[] Data { get; set; } // MESS領域 (消息区域) //这个还不知道怎么解析为人能看懂的
public uint Size { get; set; } // MESS領域サイズ (消息区域大小)
public List<uint> Offset { get; set; } // MESSオフセット (消息偏移)
public uint Count { get; set; } // MESS数 (消息数量)
public string[] DataString { get; set; } // 给人看的Data内容
}
public class ScriptFile
{
public byte[] Code { get; set; } // CODE領域 (代码区域)
public uint CodeSize { get; set; } // CODE領域サイズ (代码区域大小)
public byte[] Text { get; set; } // TEXT領域 (文本区域)
public uint TextSize { get; set; } // TEXT領域サイズ (文本区域大小)
public uint[] TextOffset { get; set; } // TEXTオフセット (文本偏移)
public uint TextCount { get; set; } // TEXT数 (文本数量)
public uint MessCount { get; set; } // MESS数 (消息数量)
public string[] TextString { get; set; } // 给人看的Text内容
public List<Command> Commands { get; set; } // 给人看的Code内容
}
public class Command
{
public uint Offset { get; set; }
public byte Instruction { get; set; }
public string InstructionString { get; set; }
public byte[] Parameter { get; set; }
public string Helper { get; set; }
public bool IsProcSet { get; set; }
}
public class ScriptManager
{
static readonly byte[] MessHeader = [0x40, 0x6D, 0x65, 0x73, 0x73, 0x3A, 0x5F, 0x5F]; //@mess:__
static readonly byte[] FileHeader = [0x40, 0x63, 0x6F, 0x64, 0x65, 0x3A, 0x5F, 0x5F]; //@code:__
private ScriptMessage sm;
private bool smEncrypted;
private string name = string.Empty;
private ScriptFile sf;
private int messIndex = 0;
public ScriptMessage GetSM()
{
return sm;
}
public ScriptFile GetSF()
{
return sf;
}
public bool LoadScriptFile(string path)
{
if (!File.Exists(path))
return false;
sf = new ScriptFile();
name = Path.GetFileNameWithoutExtension(path);
using FileStream fs = new(path, FileMode.Open);
using BinaryReader br = new(fs);
byte[] head = br.ReadBytes(8);
if (!head.SequenceEqual(FileHeader))
return false;
sf.CodeSize = Utils.ReadUInt32(br);
sf.TextCount = Utils.ReadUInt32(br);
sf.TextSize = Utils.ReadUInt32(br);
sf.MessCount = Utils.ReadUInt32(br);
if (sf.MessCount > 0)
if (sm == null)
if (!LoadScriptMess(Path.ChangeExtension(path, ".001")))
return false;
if (fs.Length < sf.CodeSize + sf.TextSize + 16 + sf.TextCount * 4)
return false;
sf.Code = br.ReadBytes((int)sf.CodeSize);
sf.TextOffset = new uint[sf.TextCount];
for (int i = 0; i < sf.TextCount; i++)
{
sf.TextOffset[i] = br.ReadUInt32();
}
sf.Text = br.ReadBytes((int)sf.TextSize);
sf.TextString = new string[sf.TextCount];
for (int i = 0; i < sf.TextCount; i++)
{
sf.TextString[i] = Utils.ReadStringFromTextData(sf.Text, (int)sf.TextOffset[i]);
}
sf.Commands = [];
for (int i = 0; i < sf.CodeSize;)
{
Command c = new()
{
Instruction = sf.Code[i++],
Offset = (uint)i,
IsProcSet = false,
};
c.InstructionString = Define.GetInstructionString(c.Instruction, out int paramNum);
if (paramNum > 0)
{
c.Parameter = new byte[paramNum * 4];
Buffer.BlockCopy(sf.Code, i, c.Parameter, 0, 4 * paramNum);
i += 4 * paramNum;
}
if (sm != null)
c.Helper = Define.SetCommandStr(c, sf, sm, ref messIndex);
else
c.Helper = Define.SetCommandStr(c, sf, null, ref messIndex);
sf.Commands.Add(c);
}
return true;
}
private bool LoadScriptMess(string path)
{
if (!File.Exists(path))
return false;
sm = new ScriptMessage();
using FileStream fs = new(path, FileMode.Open);
using BinaryReader br = new(fs);
byte[] head = br.ReadBytes(8);
smEncrypted = head.SequenceEqual(MessHeader);
if (smEncrypted)
{
sm.Count = Utils.ReadUInt32(br);
sm.Size = Utils.ReadUInt32(br);
sm.Offset = [];
for (int i = 0; i < sm.Count; i++)
{
sm.Offset.Add(br.ReadUInt32());
}
byte[] encryptData = br.ReadBytes((int)sm.Size);
for (int i = 0; i < encryptData.Length; i++)
{
encryptData[i] ^= 0x55;
}
sm.Data = encryptData;
}
else
{
fs.Position = 0;
sm.Count = 0;
sm.Size = (uint)fs.Length;
sm.Data = br.ReadBytes((int)sm.Size);
sm.Offset = [];
uint offset = 0;
for (uint i = 0; i < sm.Size; i++)
{
if (Utils.ISKANJI(sm.Data[i]))
i++;
else
{
if (sm.Data[i] == '\r')
{
if (sm.Data[i + 1] != '\n')
{
sm.Data[i] = (byte)'\n';
}
else
{
sm.Data[i++] = (byte)'\0';
}
}
if (sm.Data[i] == '\n')
{
sm.Data[i] = (byte)'\0';
if (sm.Count < 4096)
{
sm.Offset.Add(offset);
}
sm.Count++;
offset = i + 1;
}
}
}
}
sm.DataString = new string[sm.Count];
for (int i = 0; i < sm.Count; i++)
{
sm.DataString[i] = Utils.ReadStringFromTextData(sm.Data, (int)sm.Offset[i]);
}
return true;
}
//此导出功能导出的sqlite数据库
public bool ExportDatabase(string storePath)
{
if (sf.Code == null)
return false;
storePath ??= Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("Unable to determine the directory.");
if (string.IsNullOrEmpty(name))
return false;
string targetPath = Path.Combine(storePath, "script.db");
if (!File.Exists(targetPath))
Utils.ExtractEmbeddedDatabase(targetPath);
return SqliteProcess(sf, targetPath);
}
//从ScriptMessage中导出游戏文本
public bool ExportMessDatabase(string storePath)
{
if (sf == null)
return false;
if (sm == null)
return true;
storePath ??= Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("Unable to determine the directory.");
if (string.IsNullOrEmpty(name))
return false;
string targetPath = Path.Combine(storePath, "script_sm.db");
if (!File.Exists(targetPath))
Utils.ExtractEmbeddedDatabase(targetPath);
return SqliteProcess(sm, targetPath);
}
//从ScriptFile中导出Text部分剩余指令部分导出至.dat文件以便重新封包
public bool ExportTextDatabase(string storePath)
{
if (sf == null)
return false;
//分成两个文件一个是放text的sqlite数据库一个是放code的dat文件
//dat
string datPath = Path.Combine(storePath, name + ".dat");
if (File.Exists(datPath))
return false;
using FileStream fs = new(datPath, FileMode.Create);
using BinaryWriter bw = new(fs);
bw.Write(FileHeader);//文件头
bw.Write(sf.CodeSize);//代码区大小
bw.Write(sf.TextCount);//文本数量
byte[] empty4B = new byte[4];
bw.Write(empty4B);//文本大小(占位)
bw.Write(sf.MessCount);//消息数量
bw.Write(sf.Code);//代码区
//sqlite
storePath ??= Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("Unable to determine the directory.");
if (string.IsNullOrEmpty(name))
return false;
string targetPath = Path.Combine(storePath, "script_text.db");
if (!File.Exists(targetPath))
Utils.ExtractEmbeddedDatabase(targetPath);
return SqliteProcess(sf.TextString, targetPath);
}
private bool SqliteProcess(ScriptFile sf, string path)
{
using SqliteConnection connection = new($"Data Source={path};");
connection.Open();
string checkTableExistsQuery = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{name}';";
using (var checkTableCmd = new SqliteCommand(checkTableExistsQuery, connection))
{
var result = checkTableCmd.ExecuteScalar();
if (result != null) return false;
}
string createTableQuery = $@"
CREATE TABLE {name} (
Offset INTEGER,
Instruction INTEGER,
InstructionString TEXT,
Parameter BLOB,
Helper TEXT
);";
using (var createTableCmd = new SqliteCommand(createTableQuery, connection))
{
createTableCmd.ExecuteNonQuery();
}
string checkTextTableExistsQuery = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{name}__text';";
using (var checkTextTableCmd = new SqliteCommand(checkTextTableExistsQuery, connection))
{
var result = checkTextTableCmd.ExecuteScalar();
if (result == null)
{
string createTextTableQuery = $@"
CREATE TABLE {name}__text (
Text TEXT
);";
using var createTextTableCmd = new SqliteCommand(createTextTableQuery, connection);
createTextTableCmd.ExecuteNonQuery();
}
else
return false;
}
if (sm != null)
{
string checkMessTableExistsQuery = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{name}__mess';";
using var checkMessTableCmd = new SqliteCommand(checkMessTableExistsQuery, connection);
var result = checkMessTableCmd.ExecuteScalar();
if (result == null)
{
string createMessTableQuery = $@"
CREATE TABLE {name}__mess (
Mess TEXT
);";
using var createMessTableCmd = new SqliteCommand(createMessTableQuery, connection);
createMessTableCmd.ExecuteNonQuery();
}
else
return false;
}
string insertQuery = $"INSERT INTO {name} (Offset, Instruction, InstructionString, Parameter, Helper) VALUES (@Offset, @Instruction, @InstructionString, @Parameter, @Helper);";
using var transaction = connection.BeginTransaction();
using var insertCmd = new SqliteCommand(insertQuery, connection, transaction);
foreach (var command in sf.Commands)
{
insertCmd.Parameters.Clear();
insertCmd.Parameters.AddWithValue("@Offset", command.Offset);
insertCmd.Parameters.AddWithValue("@Instruction", command.Instruction);
insertCmd.Parameters.AddWithValue("@InstructionString", command.InstructionString);
insertCmd.Parameters.AddWithValue("@Parameter", (command.Parameter == null)
? DBNull.Value
: command.Parameter);
insertCmd.Parameters.AddWithValue("@Helper", command.Helper ?? "");
insertCmd.ExecuteNonQuery();
}
string insertQuerySub = $"INSERT INTO {name}__text (Text) VALUES (@Text);";
using var insertCmdSub = new SqliteCommand(insertQuerySub, connection, transaction);
foreach (string ts in sf.TextString)
{
insertCmdSub.Parameters.Clear();
insertCmdSub.Parameters.AddWithValue("@Text", ts ?? "");
insertCmdSub.ExecuteNonQuery();
}
if (sm != null)
{
string insertQuerySub2 = $"INSERT INTO {name}__mess (Mess) VALUES (@Mess);";
using var insertCmdSub2 = new SqliteCommand(insertQuerySub2, connection, transaction);
foreach (string ds in sm.DataString)
{
insertCmdSub2.Parameters.Clear();
insertCmdSub2.Parameters.AddWithValue("@Mess", ds ?? "");
insertCmdSub2.ExecuteNonQuery();
}
}
transaction.Commit();
return true;
}
private bool SqliteProcess(ScriptMessage sm, string path)
{
using SqliteConnection connection = new($"Data Source={path};");
connection.Open();
string checkTableExistsQuery = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{name}';";
using (var checkTableCmd = new SqliteCommand(checkTableExistsQuery, connection))
{
var result = checkTableCmd.ExecuteScalar();
if (result != null) return true;
}
string createTableQuery = $@"
CREATE TABLE {name} (
DataString TEXT
);";
using (var createTableCmd = new SqliteCommand(createTableQuery, connection))
{
createTableCmd.ExecuteNonQuery();
}
string insertQuery = $"INSERT INTO {name} (DataString) VALUES (@DataString);";
using var transaction = connection.BeginTransaction();
using var insertCmd = new SqliteCommand(insertQuery, connection, transaction);
foreach (var ds in sm.DataString)
{
insertCmd.Parameters.Clear();
insertCmd.Parameters.AddWithValue("@DataString", ds ?? "");
insertCmd.ExecuteNonQuery();
}
transaction.Commit();
return true;
}
private bool SqliteProcess(string[] ts, string path)
{
using SqliteConnection connection = new($"Data Source={path};");
connection.Open();
string checkTableExistsQuery = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{name}';";
using (var checkTableCmd = new SqliteCommand(checkTableExistsQuery, connection))
{
var result = checkTableCmd.ExecuteScalar();
if (result != null) return true;
}
string createTableQuery = $@"
CREATE TABLE {name} (
Text TEXT
);";
using (var createTableCmd = new SqliteCommand(createTableQuery, connection))
{
createTableCmd.ExecuteNonQuery();
}
string insertQuery = $"INSERT INTO {name} (Text) VALUES (@Text);";
using var transaction = connection.BeginTransaction();
using var insertCmd = new SqliteCommand(insertQuery, connection, transaction);
foreach (var t in ts)
{
insertCmd.Parameters.Clear();
insertCmd.Parameters.AddWithValue("@Text", t ?? "");
insertCmd.ExecuteNonQuery();
}
transaction.Commit();
return true;
}
public static bool Repackv1(string sqlitePath, bool scramble = true)
{
if (!File.Exists(sqlitePath))
return false;
using SqliteConnection connection = new($"Data Source={sqlitePath};");
connection.Open();
var tableNames = new List<string>();
using (var command = new SqliteCommand("SELECT name FROM sqlite_master WHERE type='table';", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
if (!reader.GetString(0).EndsWith("__text") && !reader.GetString(0).EndsWith("__mess"))
{
tableNames.Add(reader.GetString(0));
}
}
}
EncodingProvider provider = CodePagesEncodingProvider.Instance;
Encoding? shiftJis = provider.GetEncoding("shift-jis");
foreach (var tableName in tableNames)
{
string folder = Path.Combine(Path.GetDirectoryName(sqlitePath), "repack");
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
string outputPath = Path.Combine(Path.GetDirectoryName(sqlitePath), "repack", tableName + ".bin");
using FileStream fs = new(outputPath, FileMode.Create);
using BinaryWriter bw = new(fs);
ScriptFile sf = new()
{
CodeSize = 0,
TextCount = 0,
TextSize = 0,
MessCount = 0,
Commands = []
};
using (var command = new SqliteCommand($"SELECT * FROM {tableName};", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
Command c = new()
{
Instruction = reader.GetByte(1),
Parameter = reader.IsDBNull(3) ? [] : (byte[])reader[3]
};
sf.Commands.Add(c);
sf.CodeSize += 1 + (reader.IsDBNull(3) ? 0 : (uint)((byte[])reader[3]).Length);
}
}
List<string> textString = [];
List<uint> textOffset = [];
using (var command = new SqliteCommand($"SELECT * FROM {tableName}__text;", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
string s = reader.GetString(0);
sf.TextCount++;
textString.Add(s);
textOffset.Add(sf.TextSize);
sf.TextSize += (uint)(shiftJis.GetBytes(s).Length + 1);
}
}
uint Offset = 0;
List<uint> messOffset = [];
List<string> messString = [];
bool flag = false; // need .001?
using (var command = new SqliteCommand($"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}__mess';", connection))
{
using var reader = command.ExecuteReader();
flag = reader.Read();
}
if (flag)
{
using var command = new SqliteCommand($"SELECT * FROM {tableName}__mess;", connection);
using var reader = command.ExecuteReader();
while (reader.Read())
{
string s = reader.GetString(0);
messString.Add(s);
messOffset.Add(Offset);
Offset += (uint)(shiftJis.GetBytes(s).Length + 1);
sf.MessCount++;
}
}
//准备写入
bw.Write(FileHeader);//文件头
bw.Write(sf.CodeSize);//代码区大小
bw.Write(sf.TextCount);//文本数量
bw.Write(sf.TextSize);//文本大小
bw.Write(sf.MessCount);//消息数量
foreach (Command c in sf.Commands)//写入代码区
{
bw.Write(c.Instruction);
if (c.Parameter.Length > 0)
bw.Write(c.Parameter);
}
foreach (uint to in textOffset)//写入文本区偏移
{
bw.Write(to);
}
foreach (string ts in textString)//写入文本区
{
bw.Write(shiftJis.GetBytes(ts));
bw.Write((byte)0);
}
//再看看.001
if (flag)
{
string outputPath2 = Path.Combine(Path.GetDirectoryName(sqlitePath), "repack", tableName + ".001");
using FileStream fs2 = new(outputPath2, FileMode.Create);
using BinaryWriter bw2 = new(fs2);
if (scramble)
{
bw2.Write(MessHeader);//文件头
bw2.Write(sf.MessCount);//消息数量
bw2.Write(Offset);//消息大小
foreach (uint mo in messOffset)//写入消息区偏移
{
bw2.Write(mo);
}
byte[] rawData = new byte[Offset];
int index = 0;
foreach (string ms in messString)
{
byte[] data = shiftJis.GetBytes(ms);
Buffer.BlockCopy(data, 0, rawData, index, data.Length);
index += data.Length;
rawData[index++] = 0;
}
for (int i = 0; i < rawData.Length; i++)
{
rawData[i] ^= 0x55;
}
bw2.Write(rawData);//写入加密的消息区
}
else //ps.这是无加密情况下的处理代码下面这块是gpt照着读取写的出bug我不背锅
{
ScriptMessage sm = new()
{
Data = new byte[Offset],
Size = Offset,
Offset = messOffset,
Count = sf.MessCount
};
int index = 0;
foreach (string ms in messString)
{
byte[] data = shiftJis.GetBytes(ms);
Buffer.BlockCopy(data, 0, sm.Data, index, data.Length);
index += data.Length;
sm.Data[index++] = 0;
}
List<byte> reconstructedData = [];
uint currentPosition = 0;
for (int i = 0; i < sm.Count; i++)
{
uint offset = sm.Offset[i];
while (currentPosition < sm.Size && sm.Data[currentPosition] != 0)
{
if (Utils.ISKANJI(sm.Data[currentPosition]))
{
reconstructedData.Add(sm.Data[currentPosition]);
currentPosition++;
continue; // Skip the next byte since it is part of the Kanji character
}
reconstructedData.Add(sm.Data[currentPosition]);
currentPosition++;
}
reconstructedData.Add((byte)'\n');
currentPosition++; // Skip the null terminator
}
while (currentPosition < sm.Size)
{
if (sm.Data[currentPosition] != 0)
{
reconstructedData.Add(sm.Data[currentPosition]);
}
currentPosition++;
}
byte[] finalData = [.. reconstructedData];
bw2.Write(finalData);//写入整个数据
}
}
}
return true;
}
public static bool Repackv2(string sqlitePath, bool scramble = true)
{
if (!File.Exists(sqlitePath))
return false;
using SqliteConnection connection = new($"Data Source={sqlitePath};");
connection.Open();
var tableNames = new List<string>();
using (var command = new SqliteCommand("SELECT name FROM sqlite_master WHERE type='table';", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
tableNames.Add(reader.GetString(0));
}
}
EncodingProvider provider = CodePagesEncodingProvider.Instance;
Encoding? shiftJis = provider.GetEncoding("shift-jis");
foreach (var tableName in tableNames)
{
string folder = Path.Combine(Path.GetDirectoryName(sqlitePath), "repack");
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
string outputPath = Path.Combine(Path.GetDirectoryName(sqlitePath), "repack", tableName + ".001");
using FileStream fs = new(outputPath, FileMode.Create);
using BinaryWriter bw = new(fs);
ScriptMessage sm = new()
{
Count = 0,
Size = 0
};
List<uint> messOffset = [];
List<string> messString = [];
using (var command = new SqliteCommand($"SELECT * FROM {tableName};", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
string s = reader.GetString(0);
messString.Add(s);
messOffset.Add(sm.Size);
sm.Size += (uint)(shiftJis.GetBytes(s).Length + 1);
sm.Count++;
}
}
if (scramble) //mess部分好像有不少重复代码以后有空再优化
{
bw.Write(MessHeader);//文件头
bw.Write(sm.Count);//消息数量
bw.Write(sm.Size);//消息大小
foreach (uint mo in messOffset)//写入消息区偏移
{
bw.Write(mo);
}
byte[] rawData = new byte[sm.Size];
int index = 0;
foreach (string ms in messString)
{
byte[] data = shiftJis.GetBytes(ms);
Buffer.BlockCopy(data, 0, rawData, index, data.Length);
index += data.Length;
rawData[index++] = 0;
}
for (int i = 0; i < rawData.Length; i++)
{
rawData[i] ^= 0x55;
}
bw.Write(rawData);//写入加密的消息区
}
else //ps.这是无加密情况下的处理代码下面这块是gpt照着读取写的出bug我不背锅
{
int index = 0;
foreach (string ms in messString)
{
byte[] data = shiftJis.GetBytes(ms);
Buffer.BlockCopy(data, 0, sm.Data, index, data.Length);
index += data.Length;
sm.Data[index++] = 0;
}
List<byte> reconstructedData = [];
uint currentPosition = 0;
for (int i = 0; i < sm.Count; i++)
{
uint offset = sm.Offset[i];
while (currentPosition < sm.Size && sm.Data[currentPosition] != 0)
{
if (Utils.ISKANJI(sm.Data[currentPosition]))
{
reconstructedData.Add(sm.Data[currentPosition]);
currentPosition++;
continue; // Skip the next byte since it is part of the Kanji character
}
reconstructedData.Add(sm.Data[currentPosition]);
currentPosition++;
}
reconstructedData.Add((byte)'\n');
currentPosition++; // Skip the null terminator
}
while (currentPosition < sm.Size)
{
if (sm.Data[currentPosition] != 0)
{
reconstructedData.Add(sm.Data[currentPosition]);
}
currentPosition++;
}
byte[] finalData = [.. reconstructedData];
bw.Write(finalData);//写入整个数据
}
}
return true;
}
public static bool Repackv3(string sqlitePath)
{
if (!File.Exists(sqlitePath))
return false;
using SqliteConnection connection = new($"Data Source={sqlitePath};");
connection.Open();
var tableNames = new List<string>();
using (var command = new SqliteCommand("SELECT name FROM sqlite_master WHERE type='table';", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
tableNames.Add(reader.GetString(0));
}
}
EncodingProvider provider = CodePagesEncodingProvider.Instance;
Encoding? shiftJis = provider.GetEncoding("shift-jis");
foreach (var tableName in tableNames)
{
string folder = Path.Combine(Path.GetDirectoryName(sqlitePath), "repack");
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
string outputPath = Path.Combine(Path.GetDirectoryName(sqlitePath), "repack", tableName + ".bin");
using FileStream fs = new(outputPath, FileMode.Create);
using BinaryWriter bw = new(fs);
string trunkPath = Path.Combine(Path.GetDirectoryName(sqlitePath), tableName + ".dat");
byte[] bytes = File.ReadAllBytes(trunkPath);
uint textSizeOffset = 0x10;
List<uint> textOffset = [];
List<string> textString = [];
uint Offset = 0;
using (var command = new SqliteCommand($"SELECT * FROM {tableName};", connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
string s = reader.GetString(0);
textString.Add(s);
textOffset.Add(Offset);
Offset += (uint)(shiftJis.GetBytes(s).Length + 1);
}
}
Buffer.BlockCopy(BitConverter.GetBytes(Offset), 0, bytes, (int)textSizeOffset, 4);
//准备写入
bw.Write(bytes);//写入整个数据
foreach (uint to in textOffset)//写入文本区偏移
{
bw.Write(to);
}
foreach (string ts in textString)
{
bw.Write(shiftJis.GetBytes(ts));
bw.Write((byte)0);
}
}
return true;
}
}
}

138
EscudeTools/Utils.cs Normal file
View File

@ -0,0 +1,138 @@
using System.Reflection;
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);
}
public static uint ReadUInt32(BinaryReader reader)
{
byte[] bytes = reader.ReadBytes(4);
if (bytes.Length < 4)
throw new EndOfStreamException("Unexpected end of stream while reading UInt32.");
return BitConverter.ToUInt32(bytes, 0);
}
public static void ExtractEmbeddedDatabase(string outputPath)
{
if (File.Exists(outputPath))
{
Console.WriteLine($"File {outputPath} already exists. Do you want to overwrite it? (y/n)");
string? input = Console.ReadLine();
if (input?.ToLower() != "y")
{
Console.WriteLine("Task cancelled, Exporting database aborted.");
return;
}
}
var assembly = Assembly.GetExecutingAssembly();
string resourceName = "EscudeTools.empty.db";
using Stream stream = assembly.GetManifestResourceStream(resourceName) ?? throw new Exception($"Error, No resource with name {resourceName} found.");
using FileStream fileStream = new(outputPath, FileMode.Create, FileAccess.Write);
stream.CopyTo(fileStream);
}
public static string GetSQLiteColumnType(ushort type)
{
return type switch
{
// int
0x1 => "INTEGER",
// float
0x2 => "REAL",
// string
0x3 => "TEXT",
// bool
0x4 => "INTEGER",
_ => throw new NotSupportedException($"Unsupported column type: {type}"),
};
throw new NotImplementedException();
}
public static bool ISKANJI(byte x)
{
return (((x) ^ 0x20) - 0xa1) <= 0x3b;
}
public static ushort GetColumnTypeFromSQLite(string v)
{
return v[^2] switch
{
'1' => 0x1,
'2' => 0x2,
'3' => 0x3,
'4' => 0x4,
_ => throw new NotSupportedException($"Unsupported column type: {v}"),
};
}
public static ushort GetColumnSize(string v)
{
return v[^1] switch
{
'1' => 0x1,
'2' => 0x2,
'3' => 0x3,
'4' => 0x4,
_ => throw new NotSupportedException($"Unsupported column Size: {v}"),
};
}
public static byte RotByteR (byte v, int count)
{
count &= 7;
return (byte)(v >> count | v << (8-count));
}
public static byte RotByteL (byte v, int count)
{
count &= 7;
return (byte)(v << count | v >> (8-count));
}
}
}

BIN
EscudeTools/empty.db Normal file

Binary file not shown.

View File

@ -1 +1 @@
# EscudeLSF
# EscudeTools

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More