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
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.11.35312.102
|
VisualStudioVersion = 17.11.35312.102
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
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