Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
f38fc2c4cc | |||
be30d3751a | |||
ca7722bb7e | |||
4ad356f145 | |||
206999c456 | |||
5d23d7d09f | |||
6516dbde44 | |||
d558f9e2b4 | |||
96b93a722c | |||
25c3ff879e | |||
8a1b604f6a | |||
58a4d5a13d | |||
ade27ea87c | |||
d3abca73ad | |||
6505ac4862 | |||
007873c955 | |||
a9b8081fb8 | |||
34fa4dc9e9 | |||
79db32b780 | |||
abb3d6c2ef | |||
cd926e361e | |||
767fce3dd9 | |||
4e9f6ad2a9 | |||
b3a6c529f8 | |||
72f9b2190f | |||
71e99f86d4 | |||
ec42590b0e | |||
8965c9aacb | |||
b91884b08d | |||
1ef8568ba2 | |||
b853aaeffc | |||
f42192bdc1 | |||
7ee930b9d1 | |||
46a7fc20a1 | |||
709d4f2b56 | |||
22e4ce06dd |
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"profiles": {
|
||||
"EscudeLSF": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
443
EscudeTools/DatabaseManager.cs
Normal file
443
EscudeTools/DatabaseManager.cs
Normal 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
841
EscudeTools/Define.cs
Normal 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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
EscudeTools/EscudeTools.csproj
Normal file
27
EscudeTools/EscudeTools.csproj
Normal 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
243
EscudeTools/ImageManager.cs
Normal 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
271
EscudeTools/PackManager.cs
Normal 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
226
EscudeTools/Program.cs
Normal 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");
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
EscudeTools/Properties/launchSettings.json
Normal file
8
EscudeTools/Properties/launchSettings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"EscudeTools": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "G:\\x221.local\\lab2"
|
||||
}
|
||||
}
|
||||
}
|
802
EscudeTools/ScriptManager.cs
Normal file
802
EscudeTools/ScriptManager.cs
Normal 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
138
EscudeTools/Utils.cs
Normal 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
BIN
EscudeTools/empty.db
Normal file
Binary file not shown.
BIN
example/masterdb/db_graphics.bin
Normal file
BIN
example/masterdb/db_graphics.bin
Normal file
Binary file not shown.
BIN
example/masterdb/db_localize.bin
Normal file
BIN
example/masterdb/db_localize.bin
Normal file
Binary file not shown.
BIN
example/masterdb/db_scripts.bin
Normal file
BIN
example/masterdb/db_scripts.bin
Normal file
Binary file not shown.
BIN
example/masterdb/db_sounds.bin
Normal file
BIN
example/masterdb/db_sounds.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologueA_01_H.001
Normal file
BIN
example/script/a_prologueA_01_H.001
Normal file
Binary file not shown.
BIN
example/script/a_prologueA_01_H.bin
Normal file
BIN
example/script/a_prologueA_01_H.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologueA_02_end.001
Normal file
BIN
example/script/a_prologueA_02_end.001
Normal file
Binary file not shown.
BIN
example/script/a_prologueA_02_end.bin
Normal file
BIN
example/script/a_prologueA_02_end.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_01.001
Normal file
BIN
example/script/a_prologueB_01.001
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_01.bin
Normal file
BIN
example/script/a_prologueB_01.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_02.001
Normal file
BIN
example/script/a_prologueB_02.001
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_02.bin
Normal file
BIN
example/script/a_prologueB_02.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_03.001
Normal file
BIN
example/script/a_prologueB_03.001
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_03.bin
Normal file
BIN
example/script/a_prologueB_03.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_04.001
Normal file
BIN
example/script/a_prologueB_04.001
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_04.bin
Normal file
BIN
example/script/a_prologueB_04.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_05.001
Normal file
BIN
example/script/a_prologueB_05.001
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_05.bin
Normal file
BIN
example/script/a_prologueB_05.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_06.001
Normal file
BIN
example/script/a_prologueB_06.001
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_06.bin
Normal file
BIN
example/script/a_prologueB_06.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_07_end.001
Normal file
BIN
example/script/a_prologueB_07_end.001
Normal file
Binary file not shown.
BIN
example/script/a_prologueB_07_end.bin
Normal file
BIN
example/script/a_prologueB_07_end.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_01.001
Normal file
BIN
example/script/a_prologue_01.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_01.bin
Normal file
BIN
example/script/a_prologue_01.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_02.001
Normal file
BIN
example/script/a_prologue_02.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_02.bin
Normal file
BIN
example/script/a_prologue_02.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_03.001
Normal file
BIN
example/script/a_prologue_03.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_03.bin
Normal file
BIN
example/script/a_prologue_03.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_04.001
Normal file
BIN
example/script/a_prologue_04.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_04.bin
Normal file
BIN
example/script/a_prologue_04.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_05.001
Normal file
BIN
example/script/a_prologue_05.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_05.bin
Normal file
BIN
example/script/a_prologue_05.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_06.001
Normal file
BIN
example/script/a_prologue_06.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_06.bin
Normal file
BIN
example/script/a_prologue_06.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_07.001
Normal file
BIN
example/script/a_prologue_07.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_07.bin
Normal file
BIN
example/script/a_prologue_07.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_08.001
Normal file
BIN
example/script/a_prologue_08.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_08.bin
Normal file
BIN
example/script/a_prologue_08.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_09.001
Normal file
BIN
example/script/a_prologue_09.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_09.bin
Normal file
BIN
example/script/a_prologue_09.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_10.001
Normal file
BIN
example/script/a_prologue_10.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_10.bin
Normal file
BIN
example/script/a_prologue_10.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_11.001
Normal file
BIN
example/script/a_prologue_11.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_11.bin
Normal file
BIN
example/script/a_prologue_11.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_12.001
Normal file
BIN
example/script/a_prologue_12.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_12.bin
Normal file
BIN
example/script/a_prologue_12.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_13.001
Normal file
BIN
example/script/a_prologue_13.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_13.bin
Normal file
BIN
example/script/a_prologue_13.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_14.001
Normal file
BIN
example/script/a_prologue_14.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_14.bin
Normal file
BIN
example/script/a_prologue_14.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_15.001
Normal file
BIN
example/script/a_prologue_15.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_15.bin
Normal file
BIN
example/script/a_prologue_15.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_16.001
Normal file
BIN
example/script/a_prologue_16.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_16.bin
Normal file
BIN
example/script/a_prologue_16.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_17.001
Normal file
BIN
example/script/a_prologue_17.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_17.bin
Normal file
BIN
example/script/a_prologue_17.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_18.001
Normal file
BIN
example/script/a_prologue_18.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_18.bin
Normal file
BIN
example/script/a_prologue_18.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_19.001
Normal file
BIN
example/script/a_prologue_19.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_19.bin
Normal file
BIN
example/script/a_prologue_19.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_20.001
Normal file
BIN
example/script/a_prologue_20.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_20.bin
Normal file
BIN
example/script/a_prologue_20.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_21.001
Normal file
BIN
example/script/a_prologue_21.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_21.bin
Normal file
BIN
example/script/a_prologue_21.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_22.001
Normal file
BIN
example/script/a_prologue_22.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_22.bin
Normal file
BIN
example/script/a_prologue_22.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_23.001
Normal file
BIN
example/script/a_prologue_23.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_23.bin
Normal file
BIN
example/script/a_prologue_23.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_24.001
Normal file
BIN
example/script/a_prologue_24.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_24.bin
Normal file
BIN
example/script/a_prologue_24.bin
Normal file
Binary file not shown.
BIN
example/script/a_prologue_25.001
Normal file
BIN
example/script/a_prologue_25.001
Normal file
Binary file not shown.
BIN
example/script/a_prologue_25.bin
Normal file
BIN
example/script/a_prologue_25.bin
Normal file
Binary file not shown.
BIN
example/script/b_mainA_01_H.001
Normal file
BIN
example/script/b_mainA_01_H.001
Normal file
Binary file not shown.
BIN
example/script/b_mainA_01_H.bin
Normal file
BIN
example/script/b_mainA_01_H.bin
Normal file
Binary file not shown.
BIN
example/script/b_mainA_02_end.001
Normal file
BIN
example/script/b_mainA_02_end.001
Normal file
Binary file not shown.
BIN
example/script/b_mainA_02_end.bin
Normal file
BIN
example/script/b_mainA_02_end.bin
Normal file
Binary file not shown.
BIN
example/script/b_mainB_01.001
Normal file
BIN
example/script/b_mainB_01.001
Normal file
Binary file not shown.
BIN
example/script/b_mainB_01.bin
Normal file
BIN
example/script/b_mainB_01.bin
Normal file
Binary file not shown.
BIN
example/script/b_mainB_02.001
Normal file
BIN
example/script/b_mainB_02.001
Normal file
Binary file not shown.
BIN
example/script/b_mainB_02.bin
Normal file
BIN
example/script/b_mainB_02.bin
Normal file
Binary file not shown.
BIN
example/script/b_mainB_03.001
Normal file
BIN
example/script/b_mainB_03.001
Normal file
Binary file not shown.
BIN
example/script/b_mainB_03.bin
Normal file
BIN
example/script/b_mainB_03.bin
Normal file
Binary file not shown.
BIN
example/script/b_mainB_04.001
Normal file
BIN
example/script/b_mainB_04.001
Normal file
Binary file not shown.
BIN
example/script/b_mainB_04.bin
Normal file
BIN
example/script/b_mainB_04.bin
Normal file
Binary file not shown.
BIN
example/script/b_mainB_05.001
Normal file
BIN
example/script/b_mainB_05.001
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user