forked from aly/qstbak
muffin v7.5.11
This commit is contained in:
parent
addf2d530f
commit
3102923ce2
522 changed files with 41082 additions and 211592 deletions
21
Questionable.LgbWorker/Questionable.LgbWorker.csproj
Normal file
21
Questionable.LgbWorker/Questionable.LgbWorker.csproj
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<AssemblyName>Questionable.LgbWorker</AssemblyName>
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp1.0</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup />
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\15.0.3.2\Lumina.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace Questionable.LgbWorker;
|
||||
|
||||
internal sealed record ExitRangeEntry(ushort SourceTerritoryId, uint InstanceId, Vector3 SourcePosition, ushort DestTerritoryId, uint DestInstanceId, uint ReturnInstanceId, Vector3 Rotation, Vector3 HalfExtents);
|
||||
21
Questionable.LgbWorker/Questionable.LgbWorker/LevelEntry.cs
Normal file
21
Questionable.LgbWorker/Questionable.LgbWorker/LevelEntry.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Questionable.LgbWorker;
|
||||
|
||||
internal sealed class LevelEntry
|
||||
{
|
||||
[JsonPropertyName("objectId")]
|
||||
public uint ObjectId { get; set; }
|
||||
|
||||
[JsonPropertyName("territoryId")]
|
||||
public ushort TerritoryId { get; set; }
|
||||
|
||||
[JsonPropertyName("x")]
|
||||
public float X { get; set; }
|
||||
|
||||
[JsonPropertyName("y")]
|
||||
public float Y { get; set; }
|
||||
|
||||
[JsonPropertyName("z")]
|
||||
public float Z { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Questionable.LgbWorker;
|
||||
|
||||
internal enum LgbMessageType : byte
|
||||
{
|
||||
Progress = 1,
|
||||
Final
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Questionable.LgbWorker;
|
||||
|
||||
internal static class LgbWorkerFraming
|
||||
{
|
||||
public static void Write(Stream stream, in LgbWorkerMessage message)
|
||||
{
|
||||
using MemoryStream memoryStream = new MemoryStream();
|
||||
using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true);
|
||||
binaryWriter.Write((byte)message.Type);
|
||||
switch (message.Type)
|
||||
{
|
||||
case LgbMessageType.Progress:
|
||||
binaryWriter.Write(message.Done);
|
||||
binaryWriter.Write(message.Total);
|
||||
break;
|
||||
case LgbMessageType.Final:
|
||||
binaryWriter.Write(message.Success);
|
||||
binaryWriter.Write(message.DurationMs);
|
||||
binaryWriter.Write(message.Error ?? string.Empty);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("message");
|
||||
}
|
||||
binaryWriter.Flush();
|
||||
byte[] array = memoryStream.ToArray();
|
||||
Span<byte> span = stackalloc byte[4];
|
||||
BitConverter.TryWriteBytes(span, array.Length);
|
||||
stream.Write(span);
|
||||
stream.Write(array);
|
||||
}
|
||||
|
||||
public static LgbWorkerMessage Read(Stream stream)
|
||||
{
|
||||
Span<byte> span = stackalloc byte[4];
|
||||
ReadExact(stream, span);
|
||||
int num = BitConverter.ToInt32(span);
|
||||
if ((num <= 0 || num > 65536) ? true : false)
|
||||
{
|
||||
throw new InvalidDataException($"Invalid frame length: {num}");
|
||||
}
|
||||
byte[] array = new byte[num];
|
||||
ReadExact(stream, array);
|
||||
using BinaryReader binaryReader = new BinaryReader(new MemoryStream(array), Encoding.UTF8);
|
||||
LgbMessageType lgbMessageType = (LgbMessageType)binaryReader.ReadByte();
|
||||
return lgbMessageType switch
|
||||
{
|
||||
LgbMessageType.Progress => new LgbWorkerMessage
|
||||
{
|
||||
Type = lgbMessageType,
|
||||
Done = binaryReader.ReadInt32(),
|
||||
Total = binaryReader.ReadInt32()
|
||||
},
|
||||
LgbMessageType.Final => new LgbWorkerMessage
|
||||
{
|
||||
Type = lgbMessageType,
|
||||
Success = binaryReader.ReadBoolean(),
|
||||
DurationMs = binaryReader.ReadInt64(),
|
||||
Error = binaryReader.ReadString()
|
||||
},
|
||||
_ => throw new InvalidDataException($"Unknown message type: {lgbMessageType}"),
|
||||
};
|
||||
}
|
||||
|
||||
private static void ReadExact(Stream stream, Span<byte> buffer)
|
||||
{
|
||||
int num;
|
||||
for (int i = 0; i < buffer.Length; i += num)
|
||||
{
|
||||
num = stream.Read(buffer.Slice(i));
|
||||
if (num == 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Questionable.LgbWorker;
|
||||
|
||||
internal sealed class LgbWorkerManifest
|
||||
{
|
||||
[JsonPropertyName("sqpackPath")]
|
||||
public string SqpackPath { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("gameVersion")]
|
||||
public string GameVersion { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("npcCachePath")]
|
||||
public string NpcCachePath { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("boundaryCachePath")]
|
||||
public string BoundaryCachePath { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("territories")]
|
||||
public List<TerritoryEntry> Territories { get; set; } = new List<TerritoryEntry>();
|
||||
|
||||
[JsonPropertyName("levelEntries")]
|
||||
public List<LevelEntry> LevelEntries { get; set; } = new List<LevelEntry>();
|
||||
|
||||
public static LgbWorkerManifest Load(string path)
|
||||
{
|
||||
using FileStream utf8Json = File.OpenRead(path);
|
||||
LgbWorkerManifest? obj = JsonSerializer.Deserialize<LgbWorkerManifest>(utf8Json) ?? throw new InvalidDataException("Manifest deserialized to null");
|
||||
if (string.IsNullOrWhiteSpace(obj.SqpackPath))
|
||||
{
|
||||
throw new InvalidDataException("SqpackPath is missing");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(obj.GameVersion))
|
||||
{
|
||||
throw new InvalidDataException("GameVersion is missing");
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
namespace Questionable.LgbWorker;
|
||||
|
||||
internal readonly struct LgbWorkerMessage
|
||||
{
|
||||
public LgbMessageType Type { get; init; }
|
||||
|
||||
public int Done { get; init; }
|
||||
|
||||
public int Total { get; init; }
|
||||
|
||||
public bool Success { get; init; }
|
||||
|
||||
public long DurationMs { get; init; }
|
||||
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace Questionable.LgbWorker;
|
||||
|
||||
internal sealed record PopRangeEntry(ushort TerritoryId, uint InstanceId, Vector3 Position);
|
||||
573
Questionable.LgbWorker/Questionable.LgbWorker/Program.cs
Normal file
573
Questionable.LgbWorker/Questionable.LgbWorker/Program.cs
Normal file
|
|
@ -0,0 +1,573 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Lumina;
|
||||
using Lumina.Data.Files;
|
||||
using Lumina.Data.Parsing.Layer;
|
||||
|
||||
namespace Questionable.LgbWorker;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private const int ExitSuccess = 0;
|
||||
|
||||
private const int ExitUsage = 2;
|
||||
|
||||
private const int ExitProtocol = 3;
|
||||
|
||||
private const int ExitGameData = 4;
|
||||
|
||||
private const int ExitScanFailed = 5;
|
||||
|
||||
private const int ExitPipeConnect = 6;
|
||||
|
||||
private const int ExitOrphaned = 7;
|
||||
|
||||
private const int ConnectTimeoutMs = 30000;
|
||||
|
||||
private static readonly TimeSpan ParentPollInterval = TimeSpan.FromSeconds(1L);
|
||||
|
||||
private const uint NpcFileMagic = 1129336401u;
|
||||
|
||||
private const int NpcFileFormatVersion = 1;
|
||||
|
||||
private const uint BoundaryFileMagic = 1112425562u;
|
||||
|
||||
private const int BoundaryFileFormatVersion = 1;
|
||||
|
||||
private static readonly string[] LgbFileNames = new string[4] { "planevent", "planmap", "planner", "planlive" };
|
||||
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Run(args);
|
||||
}
|
||||
catch (Exception value)
|
||||
{
|
||||
Console.Error.WriteLine($"LGB worker failed unexpectedly: {value}");
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
private static int Run(string[] args)
|
||||
{
|
||||
if (!TryParseArgs(args, out string pipeName, out string manifestPath, out int? parentPid))
|
||||
{
|
||||
return Usage();
|
||||
}
|
||||
NamedPipeClientStream namedPipeClientStream = null;
|
||||
try
|
||||
{
|
||||
namedPipeClientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.Asynchronous);
|
||||
namedPipeClientStream.Connect(30000);
|
||||
}
|
||||
catch (Exception ex) when (((ex is TimeoutException || ex is IOException || ex is UnauthorizedAccessException || ex is ArgumentException) ? 1 : 0) != 0)
|
||||
{
|
||||
namedPipeClientStream?.Dispose();
|
||||
Console.Error.WriteLine("LGB worker could not connect to pipe '" + pipeName + "': " + ex.Message);
|
||||
return 6;
|
||||
}
|
||||
using (namedPipeClientStream)
|
||||
{
|
||||
return RunScan(namedPipeClientStream, manifestPath, parentPid);
|
||||
}
|
||||
}
|
||||
|
||||
private static int RunScan(Stream pipe, string manifestPath, int? parentPid)
|
||||
{
|
||||
LgbWorkerManifest lgbWorkerManifest;
|
||||
try
|
||||
{
|
||||
lgbWorkerManifest = LgbWorkerManifest.Load(manifestPath);
|
||||
}
|
||||
catch (Exception ex) when (((ex is InvalidDataException || ex is IOException || ex is UnauthorizedAccessException || ex is ArgumentException || ex is JsonException) ? 1 : 0) != 0)
|
||||
{
|
||||
Console.Error.WriteLine("LGB worker cannot read manifest '" + manifestPath + "': " + ex.Message);
|
||||
return 3;
|
||||
}
|
||||
GameData gameData;
|
||||
try
|
||||
{
|
||||
gameData = new GameData(lgbWorkerManifest.SqpackPath, new LuminaOptions
|
||||
{
|
||||
CacheFileResources = false
|
||||
});
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Console.Error.WriteLine("LGB worker cannot open game data at '" + lgbWorkerManifest.SqpackPath + "': " + ex2.Message);
|
||||
return 4;
|
||||
}
|
||||
CancellationTokenSource abort = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
CancellationTokenSource stopWatching = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
if (parentPid.HasValue)
|
||||
{
|
||||
int pid = parentPid.GetValueOrDefault();
|
||||
Task.Run(() => WatchParentAsync(pid, abort, stopWatching.Token));
|
||||
}
|
||||
try
|
||||
{
|
||||
return Scan(pipe, lgbWorkerManifest, gameData, abort.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopWatching.Cancel();
|
||||
gameData.Dispose();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (stopWatching != null)
|
||||
{
|
||||
((IDisposable)stopWatching).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (abort != null)
|
||||
{
|
||||
((IDisposable)abort).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int Scan(Stream pipe, LgbWorkerManifest manifest, GameData gameData, CancellationToken ct)
|
||||
{
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
Console.Error.WriteLine($"Scanning {manifest.Territories.Count} territories, {manifest.LevelEntries.Count} level entries");
|
||||
try
|
||||
{
|
||||
int total = manifest.Territories.Count * 2;
|
||||
int num = 0;
|
||||
Dictionary<uint, (ushort, Vector3, bool)> dictionary = new Dictionary<uint, (ushort, Vector3, bool)>();
|
||||
HashSet<string> visitedLgbPaths = new HashSet<string>();
|
||||
List<ExitRangeEntry> list = new List<ExitRangeEntry>();
|
||||
Dictionary<(ushort, uint), PopRangeEntry> dictionary2 = new Dictionary<(ushort, uint), PopRangeEntry>();
|
||||
HashSet<string> visitedLgbPaths2 = new HashSet<string>();
|
||||
foreach (TerritoryEntry territory in manifest.Territories)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (territory.HasAetheryte)
|
||||
{
|
||||
ScanNpcLgbFiles(gameData, dictionary, visitedLgbPaths, territory);
|
||||
ScanBoundaryLgb(gameData, list, dictionary2, visitedLgbPaths2, territory);
|
||||
num++;
|
||||
ReportProgress(pipe, num, total);
|
||||
}
|
||||
}
|
||||
foreach (TerritoryEntry territory2 in manifest.Territories)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
ScanNpcLgbFiles(gameData, dictionary, visitedLgbPaths, territory2);
|
||||
ScanBoundaryLgb(gameData, list, dictionary2, visitedLgbPaths2, territory2);
|
||||
num++;
|
||||
ReportProgress(pipe, num, total);
|
||||
}
|
||||
foreach (LevelEntry levelEntry in manifest.LevelEntries)
|
||||
{
|
||||
dictionary.TryAdd(levelEntry.ObjectId, (levelEntry.TerritoryId, new Vector3(levelEntry.X, levelEntry.Y, levelEntry.Z), false));
|
||||
}
|
||||
Console.Error.WriteLine($"Scan complete: {dictionary.Count} NPCs, {list.Count} exits, {dictionary2.Count} pops");
|
||||
if (!string.IsNullOrEmpty(manifest.NpcCachePath))
|
||||
{
|
||||
WriteNpcCache(manifest.NpcCachePath, manifest.GameVersion, dictionary);
|
||||
Console.Error.WriteLine("Wrote NPC cache: " + manifest.NpcCachePath);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(manifest.BoundaryCachePath))
|
||||
{
|
||||
WriteBoundaryCache(manifest.BoundaryCachePath, manifest.GameVersion, list, dictionary2);
|
||||
Console.Error.WriteLine("Wrote boundary cache: " + manifest.BoundaryCachePath);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.Error.WriteLine("LGB worker scan cancelled: the parent process is gone.");
|
||||
return 7;
|
||||
}
|
||||
catch (IOException ex2)
|
||||
{
|
||||
Console.Error.WriteLine("LGB worker pipe broke during the scan: " + ex2.Message);
|
||||
return 7;
|
||||
}
|
||||
catch (Exception ex3)
|
||||
{
|
||||
return ReportFailure(pipe, ex3.ToString());
|
||||
}
|
||||
stopwatch.Stop();
|
||||
try
|
||||
{
|
||||
LgbWorkerMessage message = new LgbWorkerMessage
|
||||
{
|
||||
Type = LgbMessageType.Final,
|
||||
Success = true,
|
||||
DurationMs = stopwatch.ElapsedMilliseconds
|
||||
};
|
||||
LgbWorkerFraming.Write(pipe, in message);
|
||||
pipe.Flush();
|
||||
}
|
||||
catch (IOException ex4)
|
||||
{
|
||||
Console.Error.WriteLine("LGB worker pipe broke before the final message: " + ex4.Message);
|
||||
return 7;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void ScanNpcLgbFiles(GameData gameData, Dictionary<uint, (ushort TerritoryId, Vector3 Position, bool FestivalOnly)> cache, HashSet<string> visitedLgbPaths, TerritoryEntry territory)
|
||||
{
|
||||
string bgPath = territory.BgPath;
|
||||
if (string.IsNullOrEmpty(bgPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int num = bgPath.IndexOf("/level/", StringComparison.Ordinal);
|
||||
if (num < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string[] lgbFileNames = LgbFileNames;
|
||||
foreach (string text in lgbFileNames)
|
||||
{
|
||||
string text2 = "bg/" + bgPath.Substring(0, num + 1) + "level/" + text + ".lgb";
|
||||
if (!visitedLgbPaths.Add(text2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
LgbFile file;
|
||||
try
|
||||
{
|
||||
file = gameData.GetFile<LgbFile>(text2);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (file == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
LayerCommon.Layer[] layers = file.Layers;
|
||||
for (int j = 0; j < layers.Length; j++)
|
||||
{
|
||||
LayerCommon.Layer layer = layers[j];
|
||||
bool flag = layer.FestivalID != 0 || layer.IsTemporary != 0;
|
||||
LayerCommon.InstanceObject[] instanceObjects = layer.InstanceObjects;
|
||||
for (int k = 0; k < instanceObjects.Length; k++)
|
||||
{
|
||||
LayerCommon.InstanceObject instanceObject = instanceObjects[k];
|
||||
uint baseId;
|
||||
if (instanceObject.AssetType == LayerEntryType.EventNPC)
|
||||
{
|
||||
baseId = ((LayerCommon.ENPCInstanceObject)instanceObject.Object).ParentData.ParentData.BaseId;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (instanceObject.AssetType != LayerEntryType.EventObject)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
baseId = ((LayerCommon.EventInstanceObject)instanceObject.Object).ParentData.BaseId;
|
||||
}
|
||||
if (baseId != 0 && (!cache.TryGetValue(baseId, out (ushort, Vector3, bool) value) || !(!value.Item3 || flag)))
|
||||
{
|
||||
cache[baseId] = ((ushort)territory.RowId, new Vector3(instanceObject.Transform.Translation.X, instanceObject.Transform.Translation.Y, instanceObject.Transform.Translation.Z), flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ScanBoundaryLgb(GameData gameData, List<ExitRangeEntry> exitRanges, Dictionary<(ushort TerritoryId, uint InstanceId), PopRangeEntry> popRangeIndex, HashSet<string> visitedLgbPaths, TerritoryEntry territory)
|
||||
{
|
||||
if (!IsNavigableOverworld(territory.IntendedUse))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string bgPath = territory.BgPath;
|
||||
if (string.IsNullOrEmpty(bgPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int num = bgPath.IndexOf("/level/", StringComparison.Ordinal);
|
||||
if (num < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string text = "bg/" + bgPath.Substring(0, num + 1) + "level/planmap.lgb";
|
||||
if (!visitedLgbPaths.Add(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ushort num2 = (ushort)territory.RowId;
|
||||
LgbFile file;
|
||||
try
|
||||
{
|
||||
file = gameData.GetFile<LgbFile>(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (file == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
LayerCommon.Layer[] layers = file.Layers;
|
||||
for (int i = 0; i < layers.Length; i++)
|
||||
{
|
||||
LayerCommon.InstanceObject[] instanceObjects = layers[i].InstanceObjects;
|
||||
for (int j = 0; j < instanceObjects.Length; j++)
|
||||
{
|
||||
LayerCommon.InstanceObject instanceObject = instanceObjects[j];
|
||||
if (instanceObject.AssetType == LayerEntryType.ExitRange)
|
||||
{
|
||||
LayerCommon.ExitRangeInstanceObject exitRangeInstanceObject = (LayerCommon.ExitRangeInstanceObject)(object)instanceObject.Object;
|
||||
exitRanges.Add(new ExitRangeEntry(num2, instanceObject.InstanceId, new Vector3(instanceObject.Transform.Translation.X, instanceObject.Transform.Translation.Y, instanceObject.Transform.Translation.Z), exitRangeInstanceObject.TerritoryType, exitRangeInstanceObject.DestInstanceId, exitRangeInstanceObject.ReturnInstanceId, new Vector3(instanceObject.Transform.Rotation.X, instanceObject.Transform.Rotation.Y, instanceObject.Transform.Rotation.Z), new Vector3(instanceObject.Transform.Scale.X, instanceObject.Transform.Scale.Y, instanceObject.Transform.Scale.Z)));
|
||||
}
|
||||
else if (instanceObject.AssetType == LayerEntryType.PopRange)
|
||||
{
|
||||
popRangeIndex[(num2, instanceObject.InstanceId)] = new PopRangeEntry(num2, instanceObject.InstanceId, new Vector3(instanceObject.Transform.Translation.X, instanceObject.Transform.Translation.Y, instanceObject.Transform.Translation.Z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNavigableOverworld(byte use)
|
||||
{
|
||||
switch (use)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
case 6:
|
||||
case 13:
|
||||
case 21:
|
||||
case 22:
|
||||
case 23:
|
||||
case 41:
|
||||
case 48:
|
||||
case 49:
|
||||
case 60:
|
||||
case 61:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteNpcCache(string path, string gameVersion, Dictionary<uint, (ushort TerritoryId, Vector3 Position, bool FestivalOnly)> cache)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
string text = path + ".tmp";
|
||||
using (BinaryWriter binaryWriter = new BinaryWriter(File.Create(text)))
|
||||
{
|
||||
binaryWriter.Write(1129336401u);
|
||||
binaryWriter.Write(1);
|
||||
binaryWriter.Write(gameVersion);
|
||||
binaryWriter.Write(cache.Count);
|
||||
foreach (var (value, tuple2) in cache)
|
||||
{
|
||||
binaryWriter.Write(value);
|
||||
binaryWriter.Write(tuple2.Item1);
|
||||
binaryWriter.Write(tuple2.Item2.X);
|
||||
binaryWriter.Write(tuple2.Item2.Y);
|
||||
binaryWriter.Write(tuple2.Item2.Z);
|
||||
binaryWriter.Write(tuple2.Item3);
|
||||
}
|
||||
}
|
||||
File.Move(text, path, overwrite: true);
|
||||
}
|
||||
|
||||
private static void WriteBoundaryCache(string path, string gameVersion, List<ExitRangeEntry> exitRanges, Dictionary<(ushort TerritoryId, uint InstanceId), PopRangeEntry> popRangeIndex)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
string text = path + ".tmp";
|
||||
using (BinaryWriter binaryWriter = new BinaryWriter(File.Create(text)))
|
||||
{
|
||||
binaryWriter.Write(1112425562u);
|
||||
binaryWriter.Write(1);
|
||||
binaryWriter.Write(gameVersion);
|
||||
binaryWriter.Write(exitRanges.Count);
|
||||
foreach (ExitRangeEntry exitRange in exitRanges)
|
||||
{
|
||||
binaryWriter.Write(exitRange.SourceTerritoryId);
|
||||
binaryWriter.Write(exitRange.InstanceId);
|
||||
binaryWriter.Write(exitRange.SourcePosition.X);
|
||||
binaryWriter.Write(exitRange.SourcePosition.Y);
|
||||
binaryWriter.Write(exitRange.SourcePosition.Z);
|
||||
binaryWriter.Write(exitRange.DestTerritoryId);
|
||||
binaryWriter.Write(exitRange.DestInstanceId);
|
||||
binaryWriter.Write(exitRange.ReturnInstanceId);
|
||||
binaryWriter.Write(exitRange.Rotation.X);
|
||||
binaryWriter.Write(exitRange.Rotation.Y);
|
||||
binaryWriter.Write(exitRange.Rotation.Z);
|
||||
binaryWriter.Write(exitRange.HalfExtents.X);
|
||||
binaryWriter.Write(exitRange.HalfExtents.Y);
|
||||
binaryWriter.Write(exitRange.HalfExtents.Z);
|
||||
}
|
||||
binaryWriter.Write(popRangeIndex.Count);
|
||||
foreach (var (_, popRangeEntry2) in popRangeIndex)
|
||||
{
|
||||
binaryWriter.Write(popRangeEntry2.TerritoryId);
|
||||
binaryWriter.Write(popRangeEntry2.InstanceId);
|
||||
binaryWriter.Write(popRangeEntry2.Position.X);
|
||||
binaryWriter.Write(popRangeEntry2.Position.Y);
|
||||
binaryWriter.Write(popRangeEntry2.Position.Z);
|
||||
}
|
||||
}
|
||||
File.Move(text, path, overwrite: true);
|
||||
}
|
||||
|
||||
private static void ReportProgress(Stream pipe, int done, int total)
|
||||
{
|
||||
try
|
||||
{
|
||||
LgbWorkerMessage message = new LgbWorkerMessage
|
||||
{
|
||||
Type = LgbMessageType.Progress,
|
||||
Done = done,
|
||||
Total = total
|
||||
};
|
||||
LgbWorkerFraming.Write(pipe, in message);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static int ReportFailure(Stream pipe, string error)
|
||||
{
|
||||
Console.Error.WriteLine(error);
|
||||
try
|
||||
{
|
||||
LgbWorkerMessage message = new LgbWorkerMessage
|
||||
{
|
||||
Type = LgbMessageType.Final,
|
||||
Success = false,
|
||||
Error = error
|
||||
};
|
||||
LgbWorkerFraming.Write(pipe, in message);
|
||||
pipe.Flush();
|
||||
}
|
||||
catch (Exception ex) when (((ex is IOException || ex is ObjectDisposedException) ? 1 : 0) != 0)
|
||||
{
|
||||
Console.Error.WriteLine("LGB worker could not report the failure: " + ex.Message);
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
private static async Task WatchParentAsync(int parentPid, CancellationTokenSource abort, CancellationToken stop)
|
||||
{
|
||||
_ = 1;
|
||||
try
|
||||
{
|
||||
while (!stop.IsCancellationRequested)
|
||||
{
|
||||
if (!ProcessExists(parentPid))
|
||||
{
|
||||
await abort.CancelAsync().ConfigureAwait(continueOnCapturedContext: false);
|
||||
break;
|
||||
}
|
||||
await Task.Delay(ParentPollInterval, stop).ConfigureAwait(continueOnCapturedContext: false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception value)
|
||||
{
|
||||
Console.Error.WriteLine($"LGB worker parent watch stopped: {value}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ProcessExists(int pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
using Process process = Process.GetProcessById(pid);
|
||||
return !process.HasExited;
|
||||
}
|
||||
catch (Exception ex) when (((ex is ArgumentException || ex is InvalidOperationException) ? 1 : 0) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Win32Exception)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseArgs(string[] args, out string pipeName, out string manifestPath, out int? parentPid)
|
||||
{
|
||||
pipeName = "";
|
||||
manifestPath = "";
|
||||
parentPid = null;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "--pipe":
|
||||
if (!TryTakeValue(args, ref i, out pipeName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "--manifest":
|
||||
if (!TryTakeValue(args, ref i, out manifestPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "--parent-pid":
|
||||
{
|
||||
if (!TryTakeValue(args, ref i, out string value) || !int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
parentPid = result;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (pipeName.Length > 0)
|
||||
{
|
||||
return manifestPath.Length > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryTakeValue(string[] args, ref int i, out string value)
|
||||
{
|
||||
value = "";
|
||||
if (i + 1 >= args.Length || string.IsNullOrWhiteSpace(args[i + 1]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
value = args[++i];
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int Usage()
|
||||
{
|
||||
Console.Error.WriteLine("Usage: Questionable.LgbWorker --pipe <name> --manifest <path> [--parent-pid <pid>]");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Questionable.LgbWorker;
|
||||
|
||||
internal sealed class TerritoryEntry
|
||||
{
|
||||
[JsonPropertyName("rowId")]
|
||||
public uint RowId { get; set; }
|
||||
|
||||
[JsonPropertyName("bgPath")]
|
||||
public string BgPath { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("intendedUse")]
|
||||
public byte IntendedUse { get; set; }
|
||||
|
||||
[JsonPropertyName("hasAetheryte")]
|
||||
public bool HasAetheryte { get; set; }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue