diff --git a/.gitmodules b/.gitmodules
index c2813ab..7c32b53 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -3,5 +3,4 @@
url = https://github.com/Heroes-Hacking-Central/Heroes.SDK.git
[submodule "Submodules/Atlus-Script-Tools"]
path = Submodules/Atlus-Script-Tools
- url = https://github.com/AnimatedSwine37/Atlus-Script-Tools.git
- branch = bf-emulator
+ url = https://github.com/tge-was-taken/Atlus-Script-Tools.git
diff --git a/Emulator/BF.File.Emulator/Bf/BfBuilder.cs b/Emulator/BF.File.Emulator/Bf/BfBuilder.cs
index c169cbc..c12ba16 100644
--- a/Emulator/BF.File.Emulator/Bf/BfBuilder.cs
+++ b/Emulator/BF.File.Emulator/Bf/BfBuilder.cs
@@ -105,10 +105,38 @@ public void AddEnumFile(string filePath)
}
}
+ ///
+ /// Tries to get all files that the base flow imports
+ ///
+ /// The format of the flowscript
+ /// The library to use for the flowscript
+ /// The encoding of the flowscript
+ /// An array of absolute paths to all files that the base flows import,
+ /// both directly and transitively. This includes the base files.
+ ///
+ public bool TryGetImports(FlowFormatVersion flowFormat, Library library, Encoding encoding,
+ out string[] foundImports)
+ {
+ // Use compiler arg overrides (if they're there)
+ if (_library != null) library = _library;
+ if (_encoding != null) encoding = _encoding;
+ if (_flowFormat != null) flowFormat = (FlowFormatVersion)_flowFormat;
+
+ var compiler = new FlowScriptCompiler(flowFormat);
+ compiler.Library = OverrideLibraries(library);
+ compiler.Encoding = encoding;
+
+ var imports = new List();
+ imports.AddRange(_flowFiles);
+ imports.AddRange(_msgFiles);
+
+ return compiler.TryGetImports(imports, out foundImports);
+ }
+
///
/// Builds a BF file.
///
- public unsafe Stream? Build(IntPtr originalHandle, string originalPath, FlowFormatVersion flowFormat, Library library, Encoding encoding, AtlusLogListener? listener = null, bool noBaseBf = false)
+ public EmulatedBf? Build(IntPtr originalHandle, string originalPath, FlowFormatVersion flowFormat, Library library, Encoding encoding, AtlusLogListener? listener = null, bool noBaseBf = false)
{
_log?.Info("[BfEmulator] Building BF File | {0}", originalPath);
@@ -135,21 +163,43 @@ public void AddEnumFile(string filePath)
imports.AddRange(_flowFiles.GetRange(1, _flowFiles.Count - 1));
imports.AddRange(_msgFiles);
- if (!compiler.TryCompileWithImports(bfStream, imports, baseFlow, out FlowScript flowScript))
+ try
{
- _log?.Error("[BfEmulator] Failed to compile BF File | {0}", originalPath);
- return null;
- }
+ if (!compiler.TryCompileWithImports(bfStream, imports, baseFlow, out FlowScript flowScript,
+ out var sources))
+ {
+ _log?.Error("[BfEmulator] Failed to compile BF File | {0}", originalPath);
+ return null;
+ }
- // Return the compiled bf
- var bfBinary = flowScript.ToBinary();
- var stream = StreamUtils.CreateMemoryStream(bfBinary.Header.FileSize);
- bfBinary.ToStream(stream, true);
- stream.Position = 0;
+ // Return the compiled bf
+ var bfBinary = flowScript.ToBinary();
+ var stream = StreamUtils.CreateMemoryStream(bfBinary.Header.FileSize);
+ bfBinary.ToStream(stream, true);
+ stream.Position = 0;
- return stream;
+ DateTime lastWrite = sources.Where(x => x != null).Select(Fiel.GetLastWriteTimeUtc).Max();
+ return new EmulatedBf(stream, sources, lastWrite);
+ }
+ catch (Exception exception)
+ {
+ var flows = string.Join(", ", _flowFiles.Concat(_msgFiles));
+ _log.Error(
+ "[BF Builder] Failed to compile bf {0} with source files: {1}. This may be due to your mods not being translated. Error: {2}",
+ originalPath, flows, exception.Message);
+ if (exception.StackTrace != null)
+ _log.Error(exception.StackTrace);
+ return null;
+ }
}
+ ///
+ /// Applies library file overrides to the base library.
+ /// This adds aliases to functions with different names, replaces functions with different
+ /// return values or parameters, and adds any completely new functions.
+ ///
+ /// The base library to override
+ /// A deep copy of the base library with overrides applied
private Library OverrideLibraries(Library library)
{
if (_libraryEnums.Count == 0 && _libraryFuncs.Count == 0) return library;
@@ -176,7 +226,15 @@ private Library OverrideLibraries(Library library)
if (module != -1 && index != -1)
{
- library.FlowScriptModules[module].Functions[index] = func;
+ var existingFunc = library.FlowScriptModules[module].Functions[index];
+ if (FlowFunctionsSame(existingFunc, func))
+ {
+ existingFunc.Aliases.Add(func.Name);
+ }
+ else
+ {
+ library.FlowScriptModules[module].Functions[index] = func;
+ }
}
else
{
@@ -188,4 +246,17 @@ private Library OverrideLibraries(Library library)
library.FlowScriptModules[0].Enums.AddRange(_libraryEnums);
return library;
}
-}
+
+ ///
+ /// Checks if two flowscript functions are effectively the same.
+ /// They are the same if the return type and parameter types are the same.
+ ///
+ /// The first function to compare
+ /// The other function to compare
+ /// Truee if the two functions are effectively the same, false otherwise
+ private bool FlowFunctionsSame(FlowScriptModuleFunction func1, FlowScriptModuleFunction func2)
+ {
+ return func1.ReturnType == func2.ReturnType && func1.Parameters.Select(param => param.Type)
+ .SequenceEqual(func2.Parameters.Select(param => param.Type));
+ }
+}
\ No newline at end of file
diff --git a/Emulator/BF.File.Emulator/Bf/BfBuilderFactory.cs b/Emulator/BF.File.Emulator/Bf/BfBuilderFactory.cs
index cc6faa1..46a621d 100644
--- a/Emulator/BF.File.Emulator/Bf/BfBuilderFactory.cs
+++ b/Emulator/BF.File.Emulator/Bf/BfBuilderFactory.cs
@@ -148,7 +148,7 @@ private void OverrideCompilerArgs(string file)
_flowFormat = GetFlowScriptFormatVersion(outFormat);
_library = LibraryLookup.GetLibrary(args.Library);
- _encoding = AtlusEncoding.GetByName(args.Encoding);
+ _encoding = AtlusEncoding.Create(args.Encoding);
_log.Info($"[BfBuilderFactory] Changed script compiler args to OutFormat: {args.OutFormat}, Library: {args.Library}, Encoding: {args.Encoding}");
}
diff --git a/Emulator/BF.File.Emulator/Bf/EmulatedBf.cs b/Emulator/BF.File.Emulator/Bf/EmulatedBf.cs
new file mode 100644
index 0000000..17935c3
--- /dev/null
+++ b/Emulator/BF.File.Emulator/Bf/EmulatedBf.cs
@@ -0,0 +1,31 @@
+namespace BF.File.Emulator.Bf;
+
+///
+/// Contains information about a BF file that has been emulated
+///
+public class EmulatedBf
+{
+ ///
+ /// A list of full paths to all source files used to compile this bf.
+ /// This includes all msg, bf, and flow files that are imported excluding the base bf.
+ ///
+ public List Sources { get; }
+
+ ///
+ /// The stream for the emulated file
+ ///
+ public Stream Stream { get; }
+
+ ///
+ /// The last write time of the file
+ /// This is set as the maximum last write time of all sources when the file was first emulated
+ ///
+ public DateTime LastWriteTime { get; }
+
+ public EmulatedBf(Stream stream, List sources, DateTime lastWriteTime)
+ {
+ Sources = sources;
+ Stream = stream;
+ LastWriteTime = lastWriteTime;
+ }
+}
\ No newline at end of file
diff --git a/Emulator/BF.File.Emulator/BfEmulator.cs b/Emulator/BF.File.Emulator/BfEmulator.cs
index 46f3d15..63ed7c6 100644
--- a/Emulator/BF.File.Emulator/BfEmulator.cs
+++ b/Emulator/BF.File.Emulator/BfEmulator.cs
@@ -25,7 +25,7 @@ public class BfEmulator : IEmulator
// Note: Handle->Stream exists because hashing IntPtr is easier; thus can resolve reads faster.
private readonly BfBuilderFactory _builderFactory;
- private readonly ConcurrentDictionary _pathToStream = new(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary _pathToEmulated = new(StringComparer.OrdinalIgnoreCase);
private Logger _log;
private FlowFormatVersion _flowFormat;
@@ -46,17 +46,17 @@ public BfEmulator(Logger log, bool dumpFiles, Game game)
case Game.P3P:
_flowFormat = FlowFormatVersion.Version1;
_library = LibraryLookup.GetLibrary("P3P");
- _encoding = AtlusEncoding.GetByName("P4");
+ _encoding = AtlusEncoding.Create("P3P_EFIGS");
break;
case Game.P4G:
_flowFormat = FlowFormatVersion.Version1;
_library = LibraryLookup.GetLibrary("P4G");
- _encoding = AtlusEncoding.GetByName("P4");
+ _encoding = AtlusEncoding.Create("P4G_EFIGS");
break;
case Game.P5R:
_flowFormat = FlowFormatVersion.Version3BigEndian;
_library = LibraryLookup.GetLibrary("P5R");
- _encoding = AtlusEncoding.GetByName("P5R");
+ _encoding = AtlusEncoding.Create("P5R_EFIGS");
break;
}
}
@@ -65,13 +65,13 @@ public bool TryCreateFile(IntPtr handle, string filepath, string route, out IEmu
{
// Check if we already made a custom BF for this file.
emulated = null!;
- if (_pathToStream.TryGetValue(filepath, out var stream))
+ if (_pathToEmulated.TryGetValue(filepath, out var emulatedBf))
{
// Avoid recursion into same file.
- if (stream == null)
+ if (emulatedBf == null)
return false;
- emulated = new EmulatedFile(stream);
+ emulated = new EmulatedFile(emulatedBf.Stream, emulatedBf.LastWriteTime);
return true;
}
@@ -109,15 +109,16 @@ public bool TryCreateEmulatedFile(IntPtr handle, string srcDataPath, string outp
return false;
// Make the BF file.
- _pathToStream[outputPath] = null; // Avoid recursion into same file.
+ _pathToEmulated[outputPath] = null; // Avoid recursion into same file.
- stream = builder!.Build(handle, srcDataPath, _flowFormat, _library, _encoding, _listener, isEmpty);
- if (stream == null)
+ var emulatedBf = builder!.Build(handle, srcDataPath, _flowFormat, _library, _encoding, _listener, isEmpty);
+ if (emulatedBf == null)
return false;
- _pathToStream.TryAdd(outputPath, stream);
- emulated = new EmulatedFile(stream);
- _log.Info("[BfEmulator] Created Emulated file with Path {0}", outputPath);
+ stream = emulatedBf.Stream;
+ _pathToEmulated.TryAdd(outputPath, emulatedBf);
+ emulated = new EmulatedFile(stream, emulatedBf.LastWriteTime);
+ _log.Info("[BfEmulator] Created Emulated file with Path {0} and Last Write {1}", outputPath, emulatedBf.LastWriteTime);
if (DumpFiles)
DumpFile(route, stream);
@@ -143,13 +144,13 @@ public void OnModLoading(string modFolder)
/// Full path to the file.
public void UnregisterFile(string bfPath)
{
- _pathToStream!.Remove(bfPath, out var stream);
- stream?.Dispose();
+ _pathToEmulated!.Remove(bfPath, out var emulated);
+ emulated?.Stream.Dispose();
}
- public void RegisterFile(string destinationPath, Stream stream)
+ public void RegisterFile(string destinationPath, Stream stream, DateTime lastWriteTime)
{
- _pathToStream.TryAdd(destinationPath, stream);
+ _pathToEmulated.TryAdd(destinationPath, new EmulatedBf(stream, new List(), lastWriteTime));
}
private void DumpFile(string route, Stream stream)
@@ -162,11 +163,25 @@ private void DumpFile(string route, Stream stream)
_log.Info($"[BfEmulator] Written To {dumpPath}");
}
+ internal bool TryGetImports(string route, out string[] imports)
+ {
+ if (!_builderFactory.TryCreateFromPath(route, out var builder))
+ {
+ imports = Array.Empty();
+ return false;
+ }
+
+ return builder!.TryGetImports(_flowFormat, _library, _encoding, out imports);
+ }
+
internal List GetInput() => _builderFactory.RouteFileTuples;
internal void AddFromFolders(string dir) => _builderFactory.AddFromFolders(dir);
internal void AddFile(string file, string route) => _builderFactory.AddFile(Path.GetFileName(file), file, Path.GetDirectoryName(file)!, route);
+
+ internal void SetEncoding(string encoding) => _encoding = AtlusEncoding.Create(encoding);
+
}
public class AtlusLogListener : LogListener
diff --git a/Emulator/BF.File.Emulator/BfEmulatorApi.cs b/Emulator/BF.File.Emulator/BfEmulatorApi.cs
index 95993b7..59cd4e2 100644
--- a/Emulator/BF.File.Emulator/BfEmulatorApi.cs
+++ b/Emulator/BF.File.Emulator/BfEmulatorApi.cs
@@ -1,4 +1,5 @@
-using BF.File.Emulator.Interfaces;
+using System.Diagnostics;
+using BF.File.Emulator.Interfaces;
using BF.File.Emulator.Interfaces.Structures.IO;
using BF.File.Emulator.Utilities;
using FileEmulationFramework.Interfaces;
@@ -7,6 +8,9 @@
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
+// Aliasing for readability, since our assembly name has priority over 'File'
+using Fiel = System.IO.File;
+
namespace BF.File.Emulator;
public class BfEmulatorApi : IBfEmulator
@@ -57,8 +61,9 @@ public void RegisterBf(string sourcePath, string destinationPath)
Native.SetFilePointerEx(handle, 0, IntPtr.Zero, 0);
var fileStream = new FileStream(new SafeFileHandle(handle, true), FileAccess.Read);
- var emulated = new EmulatedFile(fileStream);
- _bfEmulator.RegisterFile(destinationPath, fileStream);
+ var lastWrite = Fiel.GetLastWriteTimeUtc(sourcePath);
+ var emulated = new EmulatedFile(fileStream, lastWrite);
+ _bfEmulator.RegisterFile(destinationPath, fileStream, lastWrite);
_framework.RegisterVirtualFile(destinationPath, emulated, false);
_logger.Info("[BfEmulatorApi] Registered bf {0} at {1}", sourcePath, destinationPath);
@@ -97,4 +102,14 @@ public void AddDirectory(string dir)
_bfEmulator.AddFromFolders(dir);
}
+ public void SetEncoding(string encoding)
+ {
+ _logger.Info("Setting encoding to {0}", encoding);
+ _bfEmulator.SetEncoding(encoding);
+ }
+
+ public bool TryGetImports(string route, out string[] imports)
+ {
+ return _bfEmulator.TryGetImports(route, out imports);
+ }
}
diff --git a/Emulator/BMD.File.Emulator/Bmd/BmdBuilder.cs b/Emulator/BMD.File.Emulator/Bmd/BmdBuilder.cs
index ef4c204..8ea9719 100644
--- a/Emulator/BMD.File.Emulator/Bmd/BmdBuilder.cs
+++ b/Emulator/BMD.File.Emulator/Bmd/BmdBuilder.cs
@@ -1,13 +1,10 @@
-using FileEmulationFramework.Lib.IO;
-using FileEmulationFramework.Lib.Utilities;
-using FileEmulationFramework.Lib.Memory;
+using FileEmulationFramework.Lib.Utilities;
using AtlusScriptLibrary.MessageScriptLanguage.Compiler;
using MessageFormatVersion = AtlusScriptLibrary.MessageScriptLanguage.FormatVersion;
using AtlusScriptLibrary.Common.Libraries;
using System.Text;
using Microsoft.Win32.SafeHandles;
using AtlusScriptLibrary.MessageScriptLanguage;
-using System.Text.Json;
using BMD.File.Emulator.Utilities;
// Aliasing for readability, since our assembly name has priority over 'File'
@@ -48,7 +45,7 @@ public void AddMsgFile(string filePath)
///
/// Builds a BMD file.
///
- public unsafe Stream? Build(IntPtr originalHandle, string originalPath, MessageFormatVersion msgFormat, Library library, Encoding encoding, AtlusLogListener? listener = null, bool noBaseBmd = false)
+ public EmulatedBmd? Build(IntPtr originalHandle, string originalPath, MessageFormatVersion msgFormat, Library library, Encoding encoding, AtlusLogListener? listener = null, bool noBaseBmd = false)
{
_log?.Info("[BmdEmulator] Building BMD File | {0}", originalPath);
@@ -67,18 +64,30 @@ public void AddMsgFile(string filePath)
if (!noBaseBmd)
bmdStream = new FileStream(new SafeFileHandle(originalHandle, false), FileAccess.Read);
- if (!compiler.TryCompileWithImports(bmdStream, _msgFiles, out MessageScript messageScript))
+ try
{
- _log?.Error("[BmdEmulator] Failed to compile BMD File | {0}", originalPath);
- return null;
- }
+ if (!compiler.TryCompileWithImports(bmdStream, _msgFiles, out MessageScript messageScript))
+ {
+ _log?.Error("[BmdEmulator] Failed to compile BMD File | {0}", originalPath);
+ return null;
+ }
- // Return the compiled bmd
- var bmdBinary = messageScript.ToBinary();
- var stream = StreamUtils.CreateMemoryStream(bmdBinary.Header.FileSize);
- bmdBinary.ToStream(stream, true);
- stream.Position = 0;
+ // Return the compiled bmd
+ var bmdBinary = messageScript.ToBinary();
+ var stream = StreamUtils.CreateMemoryStream(bmdBinary.FileSize);
+ bmdBinary.ToStream(stream, true);
+ stream.Position = 0;
- return stream;
+ DateTime lastWrite = _msgFiles.Select(Fiel.GetLastWriteTimeUtc).Max();
+ return new EmulatedBmd(stream, _msgFiles, lastWrite);
+ }
+ catch (Exception exception)
+ {
+ var msgs = string.Join(", ", _msgFiles);
+ _log.Error(
+ "[BMD Builder] Failed to compile bf {0} with msgs {1}. This may be due to your mods not being translated. Error: {2}",
+ originalPath, msgs, exception.Message);
+ return null;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Emulator/BMD.File.Emulator/Bmd/BmdBuilderFactory.cs b/Emulator/BMD.File.Emulator/Bmd/BmdBuilderFactory.cs
index 6ed7a4a..3f81b4e 100644
--- a/Emulator/BMD.File.Emulator/Bmd/BmdBuilderFactory.cs
+++ b/Emulator/BMD.File.Emulator/Bmd/BmdBuilderFactory.cs
@@ -122,7 +122,7 @@ private void OverrideCompilerArgs(string file)
_messageFormat = GetMessageScriptFormatVersion(outFormat);
_library = LibraryLookup.GetLibrary(args.Library);
- _encoding = AtlusEncoding.GetByName(args.Encoding);
+ _encoding = AtlusEncoding.Create(args.Encoding);
_log.Info($"[BmdBuilderFactory] Changed script compiler args to OutFormat: {args.OutFormat}, Library: {args.Library}, Encoding: {args.Encoding}");
}
diff --git a/Emulator/BMD.File.Emulator/Bmd/EmulatedBmd.cs b/Emulator/BMD.File.Emulator/Bmd/EmulatedBmd.cs
new file mode 100644
index 0000000..80abcb8
--- /dev/null
+++ b/Emulator/BMD.File.Emulator/Bmd/EmulatedBmd.cs
@@ -0,0 +1,31 @@
+namespace BMD.File.Emulator.Bmd;
+
+///
+/// Contains information about a BMD file that has been emulated
+///
+public class EmulatedBmd
+{
+ ///
+ /// A list of full paths to all source files used to compile this bmd.
+ /// This includes all msg files but excludes the base bmd.
+ ///
+ public List Sources { get; }
+
+ ///
+ /// The stream for the emulated file
+ ///
+ public Stream Stream { get; }
+
+ ///
+ /// The last write time of the file
+ /// This is set as the maximum last write time of all sources when the file was first emulated
+ ///
+ public DateTime LastWriteTime { get; }
+
+ public EmulatedBmd(Stream stream, List sources, DateTime lastWriteTime)
+ {
+ Sources = sources;
+ Stream = stream;
+ LastWriteTime = lastWriteTime;
+ }
+}
\ No newline at end of file
diff --git a/Emulator/BMD.File.Emulator/BmdEmulator.cs b/Emulator/BMD.File.Emulator/BmdEmulator.cs
index a199459..b3bd782 100644
--- a/Emulator/BMD.File.Emulator/BmdEmulator.cs
+++ b/Emulator/BMD.File.Emulator/BmdEmulator.cs
@@ -3,7 +3,6 @@
using System.Collections.Concurrent;
using System.Text;
using BMD.File.Emulator.Bmd;
-using FileEmulationFramework.Lib.Memory;
using BMD.File.Emulator.Utilities;
using MessageFormatVersion = AtlusScriptLibrary.MessageScriptLanguage.FormatVersion;
using static BMD.File.Emulator.Mod;
@@ -11,7 +10,6 @@
using AtlusScriptLibrary.Common.Text.Encodings;
using AtlusScriptLibrary.Common.Logging;
using Logger = FileEmulationFramework.Lib.Utilities.Logger;
-using System.Diagnostics;
namespace BMD.File.Emulator;
@@ -27,7 +25,7 @@ public class BmdEmulator : IEmulator
// Note: Handle->Stream exists because hashing IntPtr is easier; thus can resolve reads faster.
private readonly BmdBuilderFactory _builderFactory;
- private readonly ConcurrentDictionary _pathToStream = new(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary _pathToEmulated = new(StringComparer.OrdinalIgnoreCase);
private Logger _log;
private MessageFormatVersion _messageFormat;
@@ -47,17 +45,17 @@ public BmdEmulator(Logger log, bool dumpFiles, Game game)
case Game.P3P:
_messageFormat = MessageFormatVersion.Version1;
_library = LibraryLookup.GetLibrary("P3P");
- _encoding = AtlusEncoding.GetByName("P4");
+ _encoding = AtlusEncoding.Create("P3P_EFIGS");
break;
case Game.P4G:
_messageFormat = MessageFormatVersion.Version1;
_library = LibraryLookup.GetLibrary("P4G");
- _encoding = AtlusEncoding.GetByName("P4");
+ _encoding = AtlusEncoding.Create("P4G_EFIGS");
break;
case Game.P5R:
_messageFormat = MessageFormatVersion.Version1BigEndian;
_library = LibraryLookup.GetLibrary("P5R");
- _encoding = AtlusEncoding.GetByName("P5R");
+ _encoding = AtlusEncoding.Create("P5R_EFIGS");
break;
}
}
@@ -66,13 +64,13 @@ public bool TryCreateFile(IntPtr handle, string filepath, string route, out IEmu
{
// Check if we already made a custom BMD for this file.
emulated = null!;
- if (_pathToStream.TryGetValue(filepath, out var stream))
+ if (_pathToEmulated.TryGetValue(filepath, out var emulatedBmd))
{
// Avoid recursion into same file.
- if (stream == null)
+ if (emulatedBmd == null)
return false;
- emulated = new EmulatedFile(stream);
+ emulated = new EmulatedFile(emulatedBmd.Stream, emulatedBmd.LastWriteTime);
return true;
}
@@ -110,15 +108,16 @@ public bool TryCreateEmulatedFile(IntPtr handle, string srcDataPath, string outp
return false;
// Make the BMD file.
- _pathToStream[outputPath] = null; // Avoid recursion into same file.
+ _pathToEmulated[outputPath] = null; // Avoid recursion into same file.
- stream = builder!.Build(handle, srcDataPath, _messageFormat, _library, _encoding, _listener, isEmpty);
- if (stream == null)
+ var emulatedBmd = builder!.Build(handle, srcDataPath, _messageFormat, _library, _encoding, _listener, isEmpty);
+ if (emulatedBmd == null)
return false;
- _pathToStream.TryAdd(outputPath, stream);
- emulated = new EmulatedFile(stream);
- _log.Info("[BmdEmulator] Created Emulated file with Path {0}", outputPath);
+ stream = emulatedBmd.Stream;
+ _pathToEmulated.TryAdd(outputPath, emulatedBmd);
+ emulated = new EmulatedFile(stream, emulatedBmd.LastWriteTime);
+ _log.Info("[BmdEmulator] Created Emulated file with Path {0} and Last Write {1}", outputPath, emulatedBmd.LastWriteTime);
if (DumpFiles)
DumpFile(route, stream);
@@ -144,13 +143,13 @@ public void OnModLoading(string modFolder)
/// Full path to the file.
public void UnregisterFile(string bmdPath)
{
- _pathToStream!.Remove(bmdPath, out var stream);
- stream?.Dispose();
+ _pathToEmulated!.Remove(bmdPath, out var emulated);
+ emulated?.Stream.Dispose();
}
- public void RegisterFile(string destinationPath, Stream stream)
+ public void RegisterFile(string destinationPath, Stream stream, DateTime lastWriteTime)
{
- _pathToStream.TryAdd(destinationPath, stream);
+ _pathToEmulated.TryAdd(destinationPath, new EmulatedBmd(stream, new List(), lastWriteTime));
}
private void DumpFile(string route, Stream stream)
@@ -168,6 +167,8 @@ private void DumpFile(string route, Stream stream)
internal void AddFromFolders(string dir) => _builderFactory.AddFromFolders(dir);
internal void AddFile(string file, string route) => _builderFactory.AddFile(Path.GetFileName(file), file, Path.GetDirectoryName(file)!, route);
+
+ public void SetEncoding(string encoding) => _encoding = AtlusEncoding.Create(encoding);
}
public class AtlusLogListener : LogListener
diff --git a/Emulator/BMD.File.Emulator/BmdEmulatorApi.cs b/Emulator/BMD.File.Emulator/BmdEmulatorApi.cs
index 69bc204..536ea06 100644
--- a/Emulator/BMD.File.Emulator/BmdEmulatorApi.cs
+++ b/Emulator/BMD.File.Emulator/BmdEmulatorApi.cs
@@ -1,14 +1,14 @@
using BMD.File.Emulator.Interfaces;
using BMD.File.Emulator.Interfaces.Structures.IO;
-using BMD.File.Emulator.Utilities;
using FileEmulationFramework.Interfaces;
using FileEmulationFramework.Interfaces.Reference;
-using FileEmulationFramework.Lib.Memory;
using FileEmulationFramework.Lib.Utilities;
using Microsoft.Win32.SafeHandles;
-using System.Diagnostics;
using System.Runtime.InteropServices;
+// Aliasing for readability, since our assembly name has priority over 'File'
+using Fiel = System.IO.File;
+
namespace BMD.File.Emulator;
public class BmdEmulatorApi : IBmdEmulator
@@ -59,9 +59,10 @@ public void RegisterBmd(string sourcePath, string destinationPath)
Native.SetFilePointerEx(handle, 0, IntPtr.Zero, 0);
+ var lastWrite = Fiel.GetLastWriteTimeUtc(sourcePath);
var fileStream = new FileStream(new SafeFileHandle(handle, true), FileAccess.Read);
- var emulated = new EmulatedFile(fileStream);
- _bmdEmulator.RegisterFile(destinationPath, fileStream);
+ var emulated = new EmulatedFile(fileStream, lastWrite);
+ _bmdEmulator.RegisterFile(destinationPath, fileStream, lastWrite);
_framework.RegisterVirtualFile(destinationPath, emulated, false);
_logger.Info("[BmdEmulatorApi] Registered bmd {0} at {1}", sourcePath, destinationPath);
@@ -99,4 +100,10 @@ public void AddDirectory(string dir)
{
_bmdEmulator.AddFromFolders(dir);
}
+
+ public void SetEncoding(string encoding)
+ {
+ _logger.Info("Setting encoding to {0}", encoding);
+ _bmdEmulator.SetEncoding(encoding);
+ }
}
diff --git a/Emulator/Interfaces/BF.File.Emulator.Interfaces/IBfEmulator.cs b/Emulator/Interfaces/BF.File.Emulator.Interfaces/IBfEmulator.cs
index 6b372de..af7ee2d 100644
--- a/Emulator/Interfaces/BF.File.Emulator.Interfaces/IBfEmulator.cs
+++ b/Emulator/Interfaces/BF.File.Emulator.Interfaces/IBfEmulator.cs
@@ -42,4 +42,18 @@ public interface IBfEmulator
///
/// The directory to add the files from
public void AddDirectory(string dir);
+
+ ///
+ /// Sets the encoding that bf emulator will use
+ ///
+ /// The name of the encoding to use
+ public void SetEncoding(string encoding);
+
+ ///
+ /// Gets a list of files that a flow file imports
+ ///
+ /// The route to the file to check
+ /// A list of full paths to all files that are imported (directly or transitively) by the flow file
+ /// True if the imports could be determined, false otherwise
+ public bool TryGetImports(string route, out string[] imports);
}
diff --git a/Emulator/Interfaces/BMD.File.Emulator.Interfaces/IBmdEmulator.cs b/Emulator/Interfaces/BMD.File.Emulator.Interfaces/IBmdEmulator.cs
index eaff03c..2f56f66 100644
--- a/Emulator/Interfaces/BMD.File.Emulator.Interfaces/IBmdEmulator.cs
+++ b/Emulator/Interfaces/BMD.File.Emulator.Interfaces/IBmdEmulator.cs
@@ -42,4 +42,10 @@ public interface IBmdEmulator
///
/// The directory to add the files from
public void AddDirectory(string dir);
+
+ ///
+ /// Sets the encoding that bf emulator will use
+ ///
+ /// The name of the encoding to use
+ public void SetEncoding(string encoding);
}
diff --git a/Emulator/PAK.Stream.Emulator/Pak/PakBuilder.cs b/Emulator/PAK.Stream.Emulator/Pak/PakBuilder.cs
index b3fd939..50b95ec 100644
--- a/Emulator/PAK.Stream.Emulator/Pak/PakBuilder.cs
+++ b/Emulator/PAK.Stream.Emulator/Pak/PakBuilder.cs
@@ -5,9 +5,7 @@
using FileEmulationFramework.Lib.IO.Struct;
using FileEmulationFramework.Lib.Utilities;
using Microsoft.Win32.SafeHandles;
-using Reloaded.Memory;
using Reloaded.Memory.Extensions;
-using Reloaded.Memory.Streams;
using Reloaded.Memory.Utilities;
// Aliasing for readability, since our assembly name has priority over 'stream'
@@ -19,6 +17,12 @@ namespace PAK.Stream.Emulator.Pak;
public class PakBuilder
{
private readonly Dictionary _customFiles = new Dictionary();
+ private readonly Logger? _log;
+
+ public PakBuilder(Logger? log = null)
+ {
+ _log = log;
+ }
///
/// Adds a file to the Virtual PAK builder.
@@ -29,7 +33,15 @@ public void AddOrReplaceFile(string filePath, string pakName)
{
var nameIndex = filePath.IndexOf(pakName.Replace('\\', '/'), StringComparison.Ordinal);
var file = filePath.Substring(nameIndex + pakName.Length + 1).Replace('\\', '/');
- _customFiles[file] = new(filePath);
+ var fileSlice = new FileSlice(filePath);
+ if (fileSlice.Length == 0)
+ {
+ _log?.Error("[Pak Builder] File {0} is empty, not adding it to pak {1}", filePath, pakName);
+ }
+ else
+ {
+ _customFiles[file] = fileSlice;
+ }
}
///
@@ -40,16 +52,24 @@ public void AddOrReplaceFile(string filePath, string pakName)
/// Path to where the file will be in the pak
public void AddOrReplaceFileWithPath(string filePath, string inPakPath)
{
- _customFiles[inPakPath.Replace('\\', '/')] = new(filePath);
+ var fileSlice = new FileSlice(filePath);
+ if (fileSlice.Length == 0)
+ {
+ _log?.Error("[Pak Builder] File {0} is empty, not adding it into pak at {1}", filePath, inPakPath);
+ }
+ else
+ {
+ _customFiles[inPakPath.Replace('\\', '/')] = fileSlice;
+ }
}
///
/// Builds an PAK file.
///
- public unsafe MultiStream Build(IntPtr handle, string filepath, Logger? logger = null, string folder = "", long baseOffset = 0)
+ public unsafe MultiStream Build(IntPtr handle, string filepath, string folder = "", long baseOffset = 0)
{
- logger?.Info($"[{nameof(PakBuilder)}] Building PAK File | {{0}}", filepath);
+ _log?.Info($"[{nameof(PakBuilder)}] Building PAK File | {{0}}", filepath);
// Get original file's entries.
IEntry[] entries = GetEntriesFromFile(handle, baseOffset, out var format);
@@ -57,19 +77,19 @@ public unsafe MultiStream Build(IntPtr handle, string filepath, Logger? logger =
switch (format)
{
case FormatVersion.Version1:
- return BuildV1(entries, handle, filepath, logger, folder, baseOffset);
+ return BuildV1(entries, handle, filepath, folder, baseOffset);
case FormatVersion.Version2:
case FormatVersion.Version2BE:
- return BuildV2(entries, handle, filepath, format == FormatVersion.Version2BE, logger, folder, baseOffset);
+ return BuildV2(entries, handle, filepath, format == FormatVersion.Version2BE, folder, baseOffset);
case FormatVersion.Version3:
case FormatVersion.Version3BE:
- return BuildV3(entries, handle, filepath, format == FormatVersion.Version3BE, logger, folder, baseOffset);
+ return BuildV3(entries, handle, filepath, format == FormatVersion.Version3BE, folder, baseOffset);
default:
return null!; // Will nver even get here, an error is thrown in GetEntriesFromFile for invalid formats
}
}
- private unsafe MultiStream BuildV1(IEntry[] entries, IntPtr handle, string filepath, Logger? logger = null, string folder = "", long baseOffset = 0)
+ private unsafe MultiStream BuildV1(IEntry[] entries, IntPtr handle, string filepath, string folder = "", long baseOffset = 0)
{
int sizeofentry = sizeof(V1FileEntry);
@@ -122,7 +142,7 @@ private unsafe MultiStream BuildV1(IEntry[] entries, IntPtr handle, string filep
int length = 0;
if (intFiles.TryGetValue(x, out var overwrittenFile))
{
- logger?.Info($"{nameof(PakBuilder)} | Injecting {{0}}, in slot {{1}}", overwrittenFile.FilePath, x);
+ _log?.Info($"{nameof(PakBuilder)} | Injecting {{0}}, in slot {{1}}", overwrittenFile.FilePath, x);
// For custom files, add to pairs directly.
length = overwrittenFile.Length;
@@ -137,7 +157,7 @@ private unsafe MultiStream BuildV1(IEntry[] entries, IntPtr handle, string filep
length = (int)Align(length, 64);
- pairs.Add(new(new FileSliceStreamW32(overwrittenFile, logger), OffsetRange.FromStartAndLength(currentOffset + sizeofentry, overwrittenFile.Length)));
+ pairs.Add(new(new FileSliceStreamW32(overwrittenFile, _log), OffsetRange.FromStartAndLength(currentOffset + sizeofentry, overwrittenFile.Length)));
if (length > overwrittenFile.Length)
{
byte[] buffer = new byte[length - overwrittenFile.Length];
@@ -149,14 +169,14 @@ private unsafe MultiStream BuildV1(IEntry[] entries, IntPtr handle, string filep
{
length = (int)Align(entries[x].Length, 64);
var entryContents = new FileSlice(baseOffset + fileOffset + sizeofentry, length, filepath);
- Strim entryStream = new FileSliceStreamW32(entryContents, logger);
+ Strim entryStream = new FileSliceStreamW32(entryContents, _log);
var container = entries[x].FileName.Trim().Replace("../", "");
if (innerPaksToEdit.Contains(container) && DetectVersion(entryStream) != FormatVersion.Unknown)
{
var entryHeader = new FileSlice(baseOffset + fileOffset, sizeofentry - 4, filepath);
- Strim headerStream = new FileSliceStreamW32(entryHeader, logger);
+ Strim headerStream = new FileSliceStreamW32(entryHeader, _log);
- entryStream = this.Build(handle, filepath, logger, container, baseOffset + fileOffset + sizeofentry);
+ entryStream = this.Build(handle, filepath, container, baseOffset + fileOffset + sizeofentry);
length = (int)entryStream.Length;
var headerStream2 = new MemoryStream(4);
@@ -181,7 +201,7 @@ private unsafe MultiStream BuildV1(IEntry[] entries, IntPtr handle, string filep
else
{
var entryHeader = new FileSlice(baseOffset + fileOffset, sizeofentry, filepath);
- Strim headerStream = new FileSliceStreamW32(entryHeader, logger);
+ Strim headerStream = new FileSliceStreamW32(entryHeader, _log);
pairs.Add(new(headerStream, OffsetRange.FromStartAndLength(currentOffset, sizeofentry)));
pairs.Add(new(entryStream, OffsetRange.FromStartAndLength(currentOffset + sizeofentry, length)));
@@ -210,10 +230,10 @@ private unsafe MultiStream BuildV1(IEntry[] entries, IntPtr handle, string filep
pairs.Add(new(dummyHeader, OffsetRange.FromStartAndLength(currentOffset, sizeof(V1FileEntry))));
// Return MultiStream
- return new MultiStream(pairs, logger);
+ return new MultiStream(pairs, _log);
}
- private unsafe MultiStream BuildV2(IEntry[] entries, IntPtr handle, string filepath, bool bigEndian, Logger? logger = null, string folder = "", long baseOffset = 0)
+ private unsafe MultiStream BuildV2(IEntry[] entries, IntPtr handle, string filepath, bool bigEndian, string folder = "", long baseOffset = 0)
{
int sizeofentry = sizeof(V2FileEntry);
@@ -275,7 +295,7 @@ private unsafe MultiStream BuildV2(IEntry[] entries, IntPtr handle, string filep
int length = 0;
if (intFiles.TryGetValue(x, out var overwrittenFile))
{
- logger?.Info($"{nameof(PakBuilder)} | Injecting {{0}}, in slot {{1}}", overwrittenFile.FilePath, x);
+ _log?.Info($"{nameof(PakBuilder)} | Injecting {{0}}, in slot {{1}}", overwrittenFile.FilePath, x);
// For custom files, add to pairs directly.
length = overwrittenFile.Length;
@@ -288,7 +308,7 @@ private unsafe MultiStream BuildV2(IEntry[] entries, IntPtr handle, string filep
entrystream.Write(writelength);
pairs.Add(new(entrystream, OffsetRange.FromStartAndLength(currentOffset, sizeof(V2FileEntry))));
- pairs.Add(new(new FileSliceStreamW32(overwrittenFile, logger), OffsetRange.FromStartAndLength(currentOffset + sizeofentry, overwrittenFile.Length)));
+ pairs.Add(new(new FileSliceStreamW32(overwrittenFile, _log), OffsetRange.FromStartAndLength(currentOffset + sizeofentry, overwrittenFile.Length)));
if (length > overwrittenFile.Length)
{
byte[] buffer = new byte[length - overwrittenFile.Length];
@@ -301,14 +321,14 @@ private unsafe MultiStream BuildV2(IEntry[] entries, IntPtr handle, string filep
length = entries[x].Length;
length = bigEndian ? Endian.Reverse(length) : length;
var entryContents = new FileSlice(baseOffset + fileOffset + sizeofentry, length, filepath);
- Strim entryStream = new FileSliceStreamW32(entryContents, logger);
+ Strim entryStream = new FileSliceStreamW32(entryContents, _log);
var container = entries[x].FileName.Trim().Replace("../", "");
if (innerPaksToEdit.Contains(container) && DetectVersion(entryStream) != FormatVersion.Unknown)
{
var entryHeader = new FileSlice(baseOffset + fileOffset, sizeofentry - 4, filepath);
- headerStream = new FileSliceStreamW32(entryHeader, logger);
+ headerStream = new FileSliceStreamW32(entryHeader, _log);
- entryStream = this.Build(handle, filepath, logger, container, baseOffset + fileOffset + sizeofentry);
+ entryStream = this.Build(handle, filepath, container, baseOffset + fileOffset + sizeofentry);
length = (int)entryStream.Length;
var headerStream2 = new MemoryStream(4);
@@ -333,14 +353,11 @@ private unsafe MultiStream BuildV2(IEntry[] entries, IntPtr handle, string filep
else
{
var entryHeader = new FileSlice(baseOffset + fileOffset, sizeofentry, filepath);
- headerStream = new FileSliceStreamW32(entryHeader, logger);
+ headerStream = new FileSliceStreamW32(entryHeader, _log);
pairs.Add(new(headerStream, OffsetRange.FromStartAndLength(currentOffset, sizeofentry)));
pairs.Add(new(entryStream, OffsetRange.FromStartAndLength(currentOffset + sizeofentry, length)));
-
}
-
-
}
else
{
@@ -364,10 +381,10 @@ private unsafe MultiStream BuildV2(IEntry[] entries, IntPtr handle, string filep
pairs.Add(new(dummyHeader, OffsetRange.FromStartAndLength(currentOffset, sizeof(V2FileEntry))));
// Return MultiStream
- return new MultiStream(pairs, logger);
+ return new MultiStream(pairs, _log);
}
- private unsafe MultiStream BuildV3(IEntry[] entries, IntPtr handle, string filepath, bool bigEndian, Logger? logger = null, string folder = "", long baseOffset = 0)
+ private unsafe MultiStream BuildV3(IEntry[] entries, IntPtr handle, string filepath, bool bigEndian, string folder = "", long baseOffset = 0)
{
int sizeofentry = sizeof(V3FileEntry);
@@ -429,7 +446,7 @@ private unsafe MultiStream BuildV3(IEntry[] entries, IntPtr handle, string filep
int length = 0;
if (intFiles.TryGetValue(x, out var overwrittenFile))
{
- logger?.Info($"{nameof(PakBuilder)} | Injecting {{0}}, in slot {{1}}", overwrittenFile.FilePath, x);
+ _log?.Info($"{nameof(PakBuilder)} | Injecting {{0}}, in slot {{1}}", overwrittenFile.FilePath, x);
// For custom files, add to pairs directly.
length = overwrittenFile.Length;
@@ -442,7 +459,7 @@ private unsafe MultiStream BuildV3(IEntry[] entries, IntPtr handle, string filep
entrystream.Write(writelength);
pairs.Add(new(entrystream, OffsetRange.FromStartAndLength(currentOffset, sizeof(V3FileEntry))));
- pairs.Add(new(new FileSliceStreamW32(overwrittenFile, logger), OffsetRange.FromStartAndLength(currentOffset + sizeofentry, overwrittenFile.Length)));
+ pairs.Add(new(new FileSliceStreamW32(overwrittenFile, _log), OffsetRange.FromStartAndLength(currentOffset + sizeofentry, overwrittenFile.Length)));
if (length > overwrittenFile.Length)
{
byte[] buffer = new byte[length - overwrittenFile.Length];
@@ -455,14 +472,14 @@ private unsafe MultiStream BuildV3(IEntry[] entries, IntPtr handle, string filep
length = entries[x].Length;
length = bigEndian ? Endian.Reverse(length) : length;
var entryContents = new FileSlice(baseOffset + fileOffset + sizeofentry, length, filepath);
- Strim entryStream = new FileSliceStreamW32(entryContents, logger);
+ Strim entryStream = new FileSliceStreamW32(entryContents, _log);
var container = entries[x].FileName.Trim().Replace("../", "");
if (innerPaksToEdit.Contains(container) && DetectVersion(entryStream) != FormatVersion.Unknown)
{
var entryHeader = new FileSlice(baseOffset + fileOffset, sizeofentry - 4, filepath);
- headerStream = new FileSliceStreamW32(entryHeader, logger);
+ headerStream = new FileSliceStreamW32(entryHeader, _log);
- entryStream = this.Build(handle, filepath, logger, container, baseOffset + fileOffset + sizeofentry);
+ entryStream = this.Build(handle, filepath, container, baseOffset + fileOffset + sizeofentry);
length = (int)entryStream.Length;
var headerStream2 = new MemoryStream(4);
@@ -486,7 +503,7 @@ private unsafe MultiStream BuildV3(IEntry[] entries, IntPtr handle, string filep
else
{
var entryHeader = new FileSlice(baseOffset + fileOffset, sizeofentry, filepath);
- headerStream = new FileSliceStreamW32(entryHeader, logger);
+ headerStream = new FileSliceStreamW32(entryHeader, _log);
pairs.Add(new(headerStream, OffsetRange.FromStartAndLength(currentOffset, sizeofentry)));
pairs.Add(new(entryStream, OffsetRange.FromStartAndLength(currentOffset + sizeofentry, length)));
@@ -514,7 +531,7 @@ private unsafe MultiStream BuildV3(IEntry[] entries, IntPtr handle, string filep
pairs.Add(new(dummyHeader, OffsetRange.FromStartAndLength(currentOffset, sizeof(V3FileEntry))));
// Return MultiStream
- return new MultiStream(pairs, logger);
+ return new MultiStream(pairs, _log);
}
private static bool IsValidFormatVersion1(Strim stream)
diff --git a/Emulator/PAK.Stream.Emulator/Pak/PakBuilderFactory.cs b/Emulator/PAK.Stream.Emulator/Pak/PakBuilderFactory.cs
index 74674a5..6c26d07 100644
--- a/Emulator/PAK.Stream.Emulator/Pak/PakBuilderFactory.cs
+++ b/Emulator/PAK.Stream.Emulator/Pak/PakBuilderFactory.cs
@@ -63,7 +63,7 @@ public bool TryCreateFromPath(string path, out PakBuilder? builder)
continue;
// Make builder if not made.
- builder ??= new PakBuilder();
+ builder ??= new PakBuilder(_log);
// Add files to builder.
var dir = group.Files.Directory.FullPath;
@@ -77,7 +77,7 @@ public bool TryCreateFromPath(string path, out PakBuilder? builder)
continue;
// Make builder if not made.
- builder ??= new PakBuilder();
+ builder ??= new PakBuilder(_log);
builder.AddOrReplaceFileWithPath(group.FilePath, group.VirtualPath);
diff --git a/Emulator/PAK.Stream.Emulator/PakEmulator.cs b/Emulator/PAK.Stream.Emulator/PakEmulator.cs
index 2772ccb..2d6fe00 100644
--- a/Emulator/PAK.Stream.Emulator/PakEmulator.cs
+++ b/Emulator/PAK.Stream.Emulator/PakEmulator.cs
@@ -81,7 +81,7 @@ public bool TryCreateEmulatedFile(IntPtr handle, string srcDataPath, string outp
// Make the PAK file.
_pathToStream[outputPath] = null; // Avoid recursion into same file.
- stream = builder!.Build(handle, srcDataPath, _log);
+ stream = builder!.Build(handle, srcDataPath);
_pathToStream.TryAdd(outputPath, stream);
emulated = new EmulatedFile(stream);
diff --git a/FileEmulationFramework.Interfaces/IEmulatedFile.cs b/FileEmulationFramework.Interfaces/IEmulatedFile.cs
index f86b665..358bd75 100644
--- a/FileEmulationFramework.Interfaces/IEmulatedFile.cs
+++ b/FileEmulationFramework.Interfaces/IEmulatedFile.cs
@@ -26,9 +26,18 @@ public interface IEmulatedFile
public unsafe bool ReadData(IntPtr handle, byte* buffer, uint length, long offset, IFileInformation info, out int numReadBytes);
///
- /// Called when a handle to the given file is closed..
+ /// Called when a handle to the given file is closed.
///
/// The handle of the file.
/// Additional information about the current file being processed.
public void CloseHandle(IntPtr handle, IFileInformation info);
+
+ ///
+ /// Tries to get the last write time of a file with a given handle.
+ ///
+ /// The handle of the file.
+ /// Additional information about the current file being processed.
+ /// The time the emulated file was last written to.
+ /// True if the last write to was determined, false if the operation was not supported.
+ public bool TryGetLastWriteTime(IntPtr handle, IFileInformation info, out DateTime? lastWriteTime);
}
\ No newline at end of file
diff --git a/FileEmulationFramework.Interfaces/Reference/EmulatedFile.cs b/FileEmulationFramework.Interfaces/Reference/EmulatedFile.cs
index a45a03c..e413640 100644
--- a/FileEmulationFramework.Interfaces/Reference/EmulatedFile.cs
+++ b/FileEmulationFramework.Interfaces/Reference/EmulatedFile.cs
@@ -10,12 +10,31 @@ public class EmulatedFile : IEmulatedFile where TStream : Stream
///
public TStream BaseStream { get; set; }
+ ///
+ /// The last time the file was written to.
+ /// If null, the last write time of the original file will be reported.
+ ///
+ public DateTime? LastWrite { get; set; }
+
///
/// Creates an emulated file from a stream.
+ /// This file will report the last write time of the original.
///
/// Stream from which this emulated file is based from.
public EmulatedFile(TStream baseStream) => BaseStream = baseStream;
+
+ ///
+ /// Creates an emulated file from a stream with a last write time.
+ ///
+ /// Stream from which this emulated file is based from.
+ /// The last write time that the emulated file will report.
+ public EmulatedFile(TStream baseStream, DateTime lastWrite)
+ {
+ BaseStream = baseStream;
+ LastWrite = lastWrite;
+ }
+
///
public long GetFileSize(IntPtr handle, IFileInformation info) => BaseStream.Length;
@@ -30,4 +49,11 @@ public unsafe bool ReadData(IntPtr handle, byte* buffer, uint length, long offse
///
public void CloseHandle(IntPtr handle, IFileInformation info) { }
+
+ ///
+ public bool TryGetLastWriteTime(IntPtr handle, IFileInformation info, out DateTime? lastWriteTime)
+ {
+ lastWriteTime = LastWrite;
+ return lastWriteTime != null;
+ }
}
\ No newline at end of file
diff --git a/FileEmulationFramework.Lib/Utilities/Native.cs b/FileEmulationFramework.Lib/Utilities/Native.cs
index 1ead5ea..1ac9a94 100644
--- a/FileEmulationFramework.Lib/Utilities/Native.cs
+++ b/FileEmulationFramework.Lib/Utilities/Native.cs
@@ -453,6 +453,18 @@ public DateTime ToDateTime()
long fileTime = (long)((high << 32) + low);
return DateTime.FromFileTimeUtc(fileTime);
}
+
+ ///
+ /// Creates a LARGE_INTEGER from a long
+ ///
+ /// The long value to create a LARGE_INTEGER from
+ public LARGE_INTEGER(long value)
+ {
+ // Setting QuadPart sets all three, just adding the = 0s to make the compiler happy
+ LowPart = 0;
+ HighPart = 0;
+ QuadPart = value;
+ }
}
/// The type of access to a file mapping object, which determines the page protection of the pages.
diff --git a/FileEmulationFramework.Tests/Emulators/BF/BfEmulatorTests.cs b/FileEmulationFramework.Tests/Emulators/BF/BfEmulatorTests.cs
index b751e79..4a8e292 100644
--- a/FileEmulationFramework.Tests/Emulators/BF/BfEmulatorTests.cs
+++ b/FileEmulationFramework.Tests/Emulators/BF/BfEmulatorTests.cs
@@ -1,14 +1,10 @@
using AtlusScriptLibrary.Common.Libraries;
using AtlusScriptLibrary.Common.Text.Encodings;
using BF.File.Emulator.Bf;
-using FileEmulationFramework.Lib.Memory;
using FileEmulationFramework.Lib.Utilities;
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Text;
-using System.Threading.Tasks;
using Xunit;
using FlowFormatVersion = AtlusScriptLibrary.FlowScriptLanguage.FormatVersion;
@@ -27,16 +23,16 @@ public void SingleFlow()
{
var flowFormat = FlowFormatVersion.Version1;
var library = LibraryLookup.GetLibrary("P4G");
- var encoding = AtlusEncoding.GetByName("P4");
+ var encoding = AtlusEncoding.Create("P4G_EFIGS");
var builder = new BfBuilder();
builder.AddFlowFile(Assets.SimpleFlow);
var handle = Native.CreateFileW(Assets.BaseBf, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
- var stream = builder.Build(handle, Assets.BaseBf, flowFormat, library, encoding);
+ var emulatedBf = builder.Build(handle, Assets.BaseBf, flowFormat, library, encoding);
// Write to file for checking.
using var fileStream = new FileStream("field.bf", FileMode.Create);
- stream.CopyTo(fileStream);
+ emulatedBf!.Stream.CopyTo(fileStream);
fileStream.Close();
// Parse file and check.
@@ -49,17 +45,17 @@ public void MultipleFlows()
{
var flowFormat = FlowFormatVersion.Version1;
var library = LibraryLookup.GetLibrary("P4G");
- var encoding = AtlusEncoding.GetByName("P4");
+ var encoding = AtlusEncoding.Create("P4G_EFIGS");
var builder = new BfBuilder();
builder.AddFlowFile(Assets.SimpleFlow);
builder.AddFlowFile(Assets.ComplexFlow);
var handle = Native.CreateFileW(Assets.BaseBf, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
- var stream = builder.Build(handle, Assets.BaseBf, flowFormat, library, encoding);
+ var emulatedBf = builder.Build(handle, Assets.BaseBf, flowFormat, library, encoding);
// Write to file for checking.
using var fileStream = new FileStream("field.bf", FileMode.Create);
- stream.CopyTo(fileStream);
+ emulatedBf!.Stream.CopyTo(fileStream);
fileStream.Close();
// Parse file and check.
diff --git a/FileEmulationFramework.Tests/Emulators/BMD/BmdEmulatorTests.cs b/FileEmulationFramework.Tests/Emulators/BMD/BmdEmulatorTests.cs
index 6204a60..152004d 100644
--- a/FileEmulationFramework.Tests/Emulators/BMD/BmdEmulatorTests.cs
+++ b/FileEmulationFramework.Tests/Emulators/BMD/BmdEmulatorTests.cs
@@ -23,16 +23,16 @@ public void SingleMsg()
{
var msgFormat = MessageFormatVersion.Version1BigEndian;
var library = LibraryLookup.GetLibrary("P5R");
- var encoding = AtlusEncoding.GetByName("P5R");
+ var encoding = AtlusEncoding.Persona5RoyalEFIGS;
var builder = new BmdBuilder();
builder.AddMsgFile(Assets.FirstMsg);
var handle = Native.CreateFileW(Assets.BaseBmd, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
- var stream = builder.Build(handle, Assets.BaseBmd, msgFormat, library, encoding);
+ var emulatedBmd = builder.Build(handle, Assets.BaseBmd, msgFormat, library, encoding);
// Write to file for checking.
using var fileStream = new FileStream("e722_103.bmd", FileMode.Create);
- stream.CopyTo(fileStream);
+ emulatedBmd!.Stream.CopyTo(fileStream);
fileStream.Close();
// Parse file and check.
@@ -48,17 +48,17 @@ public void MultipleMsgs()
{
var msgFormat = MessageFormatVersion.Version1BigEndian;
var library = LibraryLookup.GetLibrary("P5R");
- var encoding = AtlusEncoding.GetByName("P5R");
+ var encoding = AtlusEncoding.Persona5RoyalEFIGS;
var builder = new BmdBuilder();
builder.AddMsgFile(Assets.FirstMsg);
builder.AddMsgFile(Assets.SecondMsg);
var handle = Native.CreateFileW(Assets.BaseBmd, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
- var stream = builder.Build(handle, Assets.BaseBmd, msgFormat, library, encoding);
+ var emulatedBmd = builder.Build(handle, Assets.BaseBmd, msgFormat, library, encoding);
// Write to file for checking.
using var fileStream = new FileStream("e722_103.bmd", FileMode.Create);
- stream.CopyTo(fileStream);
+ emulatedBmd!.Stream.CopyTo(fileStream);
fileStream.Close();
// Parse file and check.
diff --git a/FileEmulationFramework.sln b/FileEmulationFramework.sln
index 0971363..b855053 100644
--- a/FileEmulationFramework.sln
+++ b/FileEmulationFramework.sln
@@ -37,8 +37,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PAK.Stream.Emulator.Interfa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BF.File.Emulator", "Emulator\BF.File.Emulator\BF.File.Emulator.csproj", "{4132B2C3-4380-4193-A1E5-17395311D784}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AtlusScriptLibrary", "Submodules\Atlus-Script-Tools\Source\AtlusScriptLibrary\AtlusScriptLibrary.csproj", "{62EE486F-AE33-4DBB-AC4C-409A22CB04AD}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BF.File.Emulator.Interfaces", "Emulator\Interfaces\BF.File.Emulator.Interfaces\BF.File.Emulator.Interfaces.csproj", "{EE178CAD-7CD0-41EB-96FA-B1DCC4DA4E73}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BMD.File.Emulator", "Emulator\BMD.File.Emulator\BMD.File.Emulator.csproj", "{E053BEE9-B765-462E-A80D-B25949348835}"
@@ -52,6 +50,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SPD.File.Emulator", "Emulat
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SPD.File.Emulator.Interfaces", "Emulator\Interfaces\SPD.File.Emulator.Interfaces\SPD.File.Emulator.Interfaces.csproj", "{549B1691-ED3F-4AEF-8F60-B6410A361C3A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AtlusScriptLibrary", "Submodules\Atlus-Script-Tools\Source\AtlusScriptLibrary\AtlusScriptLibrary.csproj", "{BBBCC290-346C-4AAC-AE76-CAB05243A10C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -81,12 +81,15 @@ Global
{3365837F-814B-4478-AA6F-E932986E4652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3365837F-814B-4478-AA6F-E932986E4652}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3365837F-814B-4478-AA6F-E932986E4652}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3365837F-814B-4478-AA6F-E932986E4652}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A0C238F-28E4-49BC-95FA-2F3A538A3EED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A0C238F-28E4-49BC-95FA-2F3A538A3EED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A0C238F-28E4-49BC-95FA-2F3A538A3EED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3A0C238F-28E4-49BC-95FA-2F3A538A3EED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F0CB5B64-CC0C-4511-A454-EC6515C9217A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F0CB5B64-CC0C-4511-A454-EC6515C9217A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F0CB5B64-CC0C-4511-A454-EC6515C9217A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F0CB5B64-CC0C-4511-A454-EC6515C9217A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D8656F8-3C54-469B-909C-8C9200D6C200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D8656F8-3C54-469B-909C-8C9200D6C200}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D8656F8-3C54-469B-909C-8C9200D6C200}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -98,6 +101,7 @@ Global
{DE067AF0-EA87-4A2C-9C04-25438BA23C29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE067AF0-EA87-4A2C-9C04-25438BA23C29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE067AF0-EA87-4A2C-9C04-25438BA23C29}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE067AF0-EA87-4A2C-9C04-25438BA23C29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CAE1FB74-7A16-47A2-8FBC-78A167C4DD9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CAE1FB74-7A16-47A2-8FBC-78A167C4DD9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CAE1FB74-7A16-47A2-8FBC-78A167C4DD9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -110,10 +114,6 @@ Global
{4132B2C3-4380-4193-A1E5-17395311D784}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4132B2C3-4380-4193-A1E5-17395311D784}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4132B2C3-4380-4193-A1E5-17395311D784}.Release|Any CPU.Build.0 = Release|Any CPU
- {62EE486F-AE33-4DBB-AC4C-409A22CB04AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {62EE486F-AE33-4DBB-AC4C-409A22CB04AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {62EE486F-AE33-4DBB-AC4C-409A22CB04AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {62EE486F-AE33-4DBB-AC4C-409A22CB04AD}.Release|Any CPU.Build.0 = Release|Any CPU
{EE178CAD-7CD0-41EB-96FA-B1DCC4DA4E73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE178CAD-7CD0-41EB-96FA-B1DCC4DA4E73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE178CAD-7CD0-41EB-96FA-B1DCC4DA4E73}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -134,6 +134,10 @@ Global
{549B1691-ED3F-4AEF-8F60-B6410A361C3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{549B1691-ED3F-4AEF-8F60-B6410A361C3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{549B1691-ED3F-4AEF-8F60-B6410A361C3A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BBBCC290-346C-4AAC-AE76-CAB05243A10C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BBBCC290-346C-4AAC-AE76-CAB05243A10C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BBBCC290-346C-4AAC-AE76-CAB05243A10C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BBBCC290-346C-4AAC-AE76-CAB05243A10C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -150,12 +154,12 @@ Global
{CAE1FB74-7A16-47A2-8FBC-78A167C4DD9C} = {E530561A-D74E-48FD-B16B-8ECA7EE35ED6}
{17A7D1A1-DCEB-4C1B-A606-452AA5D968C9} = {604407C2-9BB1-4923-A744-DF5E1551A412}
{4132B2C3-4380-4193-A1E5-17395311D784} = {E530561A-D74E-48FD-B16B-8ECA7EE35ED6}
- {62EE486F-AE33-4DBB-AC4C-409A22CB04AD} = {732CB582-B44E-4EF6-9FDC-B1126309A9E0}
{EE178CAD-7CD0-41EB-96FA-B1DCC4DA4E73} = {604407C2-9BB1-4923-A744-DF5E1551A412}
{E053BEE9-B765-462E-A80D-B25949348835} = {E530561A-D74E-48FD-B16B-8ECA7EE35ED6}
{C527D538-F3A4-4FC4-A641-B7359691815F} = {604407C2-9BB1-4923-A744-DF5E1551A412}
{8B9B13C0-DA50-4E0F-9454-6E95BE9BC9E2} = {E530561A-D74E-48FD-B16B-8ECA7EE35ED6}
{549B1691-ED3F-4AEF-8F60-B6410A361C3A} = {604407C2-9BB1-4923-A744-DF5E1551A412}
+ {BBBCC290-346C-4AAC-AE76-CAB05243A10C} = {732CB582-B44E-4EF6-9FDC-B1126309A9E0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {33D4FC90-A513-4AB4-BF0A-704F0E193DCC}
diff --git a/FileEmulationFramework/FileAccessServer.cs b/FileEmulationFramework/FileAccessServer.cs
index d8c559e..9297053 100644
--- a/FileEmulationFramework/FileAccessServer.cs
+++ b/FileEmulationFramework/FileAccessServer.cs
@@ -13,6 +13,7 @@
using Native = FileEmulationFramework.Lib.Utilities.Native;
using System.Collections.Concurrent;
using Reloaded.Memory.Utilities;
+using Microsoft.Win32.SafeHandles;
// ReSharper disable RedundantArgumentDefaultValue
@@ -31,7 +32,7 @@ public static unsafe class FileAccessServer
private static List Emulators { get; } = new();
private static readonly ConcurrentDictionary HandleToInfoMap = new();
- private static readonly ConcurrentDictionary PathToVirtualFileMap = new(StringComparer.OrdinalIgnoreCase);
+ private static readonly ConcurrentDictionary PathToVirtualFileMap = new(StringComparer.OrdinalIgnoreCase);
private static readonly ConcurrentDictionary PathToHandleMap = new(StringComparer.OrdinalIgnoreCase);
private static readonly object ThreadLock = new();
@@ -39,7 +40,8 @@ public static unsafe class FileAccessServer
private static IHook _readFileHook = null!;
private static IHook _setFilePointerHook = null!;
private static IHook _getFileSizeHook = null!;
- private static IHook _getFileAttributesHook = null!;
+ private static IHook _getFileFullAttributesHook = null!;
+ private static IHook _getFileAttributesHook = null!;
private static IAsmHook _closeHandleHook = null!;
private static NtQueryInformationFileFn _ntQueryInformationFile;
@@ -60,7 +62,8 @@ public static void Init(Logger logger, NativeFunctions functions, IReloadedHooks
_setFilePointerHook = functions.SetFilePointer.Hook(typeof(FileAccessServer), nameof(SetInformationFileHook)).Activate();
_getFileSizeHook = functions.GetFileSize.Hook(typeof(FileAccessServer), nameof(QueryInformationFileImpl)).Activate();
_ntQueryInformationFile = _getFileSizeHook.OriginalFunction;
- _getFileAttributesHook = functions.NtQueryFullAttributes.Hook(typeof(FileAccessServer), nameof(QueryAttributesFileImpl)).Activate();
+ _getFileFullAttributesHook = functions.NtQueryFullAttributes.Hook(typeof(FileAccessServer), nameof(QueryFullAttributesFileImpl)).Activate();
+ _getFileAttributesHook = functions.NtQueryAttributes.Hook(typeof(FileAccessServer), nameof(QueryAttributesFileImpl)).Activate();
// We need to cook some assembly for NtClose, because Native->Managed
// transition can invoke thread setup code which will call CloseHandle again
@@ -106,27 +109,80 @@ private static void DequeueHandles()
private static int QueryInformationFileImpl(IntPtr hfile, IO_STATUS_BLOCK* ioStatusBlock, byte* fileInformation, uint length, FileInformationClass fileInformationClass)
{
var result = _getFileSizeHook.OriginalFunction.Value.Invoke(hfile, ioStatusBlock, fileInformation, length, fileInformationClass);
- if (fileInformationClass != FileInformationClass.FileStandardInformation || !HandleToInfoMap.TryGetValue(hfile, out var info))
+ if (!HandleToInfoMap.TryGetValue(hfile, out var info))
return result;
+
+ if (fileInformationClass == FileInformationClass.FileStandardInformation)
+ {
+ var information = (FILE_STANDARD_INFORMATION*)fileInformation;
+ var oldSize = information->EndOfFile;
+ var newSize = info.File.GetFileSize(hfile, info);
+ if (newSize != -1)
+ information->EndOfFile = newSize;
- var information = (FILE_STANDARD_INFORMATION*)fileInformation;
- var oldSize = information->EndOfFile;
- var newSize = info.File.GetFileSize(hfile, info);
- if (newSize != -1)
- information->EndOfFile = newSize;
+ _logger.Info("[FileAccessServer] File Size Override | Old: {0}, New: {1} | {2}", oldSize, newSize, info.FilePath);
+ }
+ else if (fileInformationClass == FileInformationClass.FileBasicInformation)
+ {
+ var information = (FILE_BASIC_INFORMATION*)fileInformation;
+ if (info.File.TryGetLastWriteTime(hfile, info, out var newWriteTime))
+ {
+ var oldWriteTime = information->LastWriteTime.ToDateTime();
+ information->LastWriteTime = new LARGE_INTEGER(newWriteTime!.Value.ToFileTimeUtc());
+ _logger.Info("[FileAccessServer] File Last Write Override | Old: {0}, New: {1} | {2}", oldWriteTime,
+ newWriteTime, info.FilePath);
+ }
+ }
- _logger.Info("[FileAccessServer] File Size Override | Old: {0}, New: {1} | {2}", oldSize, newSize, info.FilePath);
return result;
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
- private static int QueryAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FILE_NETWORK_OPEN_INFORMATION* information)
+ private static int QueryFullAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FILE_NETWORK_OPEN_INFORMATION* information)
{
- var result = _getFileAttributesHook.OriginalFunction.Value.Invoke(attributes, information);
+ var result = _getFileFullAttributesHook.OriginalFunction.Value.Invoke(attributes, information);
+
+ // We don't support directories currently
+ if ((information->FileAttributes & (uint)FileAttributes.Directory) != 0)
+ return result;
+
var path = Strings.TrimWindowsPrefixes(attributes->ObjectName);
+ SafeFileHandle? safeHandle = null;
+
if (!PathToHandleMap.TryGetValue(path, out var hfile) || !HandleToInfoMap.TryGetValue(hfile, out var info))
- return result;
+ {
+ // We haven't tried to create an emulated file for this yet, try it
+ if (!PathToVirtualFileMap.TryGetValue(path, out info))
+ {
+ // Prevent recursion
+ PathToVirtualFileMap[path] = null;
+
+ // There is a virtual file but no handle exists for it, we need to make one
+ try
+ {
+ safeHandle = File.OpenHandle(path);
+ }
+ catch (Exception e)
+ {
+ // If we couldn't make the handle this probably isn't a file we can emulate
+ return result;
+ }
+
+ hfile = safeHandle.DangerousGetHandle();
+
+ // We tried to make one but failed, this file isn't emulated
+ if (!HandleToInfoMap.TryGetValue(hfile, out info))
+ {
+ safeHandle.Close();
+ return result;
+ }
+ }
+
+ // We've tried to create an emulated file for this before but failed, this file isn't emulated
+ if (info == null)
+ return result;
+ }
var oldSize = information->EndOfFile;
var newSize = info.File.GetFileSize(hfile, info);
@@ -135,6 +191,38 @@ private static int QueryAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FILE_N
_logger.Info("[FileAccessServer] File Size Override | Old: {0}, New: {1} | {2}", oldSize, newSize, path);
+ if (info.File.TryGetLastWriteTime(hfile, info, out var newWriteTime))
+ {
+ var oldWriteTime = information->LastWriteTime.ToDateTime();
+ information->LastWriteTime = new LARGE_INTEGER(newWriteTime!.Value.ToFileTimeUtc());
+ _logger.Info("[FileAccessServer] File Last Write Override | Old: {0}, New: {1} | {2}", oldWriteTime,
+ newWriteTime, path);
+ }
+
+ // Clean up if we needed to make a new handle
+ if (safeHandle != null)
+ safeHandle.Close();
+
+ return result;
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
+ private static int QueryAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FILE_BASIC_INFORMATION* information)
+ {
+ var result = _getFileAttributesHook.OriginalFunction.Value.Invoke(attributes, information);
+ var path = Strings.TrimWindowsPrefixes(attributes->ObjectName);
+
+ if (!PathToHandleMap.TryGetValue(path, out var hfile) || !HandleToInfoMap.TryGetValue(hfile, out var info))
+ return result;
+
+ if (info.File.TryGetLastWriteTime(hfile, info, out var newWriteTime))
+ {
+ var oldWriteTime = information->LastWriteTime.ToDateTime();
+ information->LastWriteTime = new LARGE_INTEGER(newWriteTime!.Value.ToFileTimeUtc());
+ _logger.Info("[FileAccessServer] File Last Write Override | Old: {0}, New: {1} | {2}", oldWriteTime,
+ newWriteTime, path);
+ }
+
return result;
}
@@ -235,7 +323,7 @@ private static int NtCreateFileImpl(IntPtr* handle, FileAccess access, OBJECT_AT
_logger.Debug("[FileAccessServer] Accessing: {0}, {1}, Route: {2}", hndl, newFilePath, _currentRoute.FullPath);
// Try Accept New File (virtual override)
- if (PathToVirtualFileMap.TryGetValue(newFilePath, out var fileInfo))
+ if (PathToVirtualFileMap.TryGetValue(newFilePath, out var fileInfo) && fileInfo != null)
{
// Reuse of emulated file (backed by stream) is safe because file access is single threaded.
lock (ThreadLock)
diff --git a/FileEmulationFramework/Utilities/Native.cs b/FileEmulationFramework/Utilities/Native.cs
index d660835..aa2166e 100644
--- a/FileEmulationFramework/Utilities/Native.cs
+++ b/FileEmulationFramework/Utilities/Native.cs
@@ -86,6 +86,17 @@ public FuncPtr<
> Value;
}
+ [Reloaded.Hooks.Definitions.X64.Function(Reloaded.Hooks.Definitions.X64.CallingConventions.Microsoft)]
+ [Reloaded.Hooks.Definitions.X86.Function(Reloaded.Hooks.Definitions.X86.CallingConventions.Stdcall)]
+ public struct NtQueryAttributesFileFn
+ {
+ public FuncPtr<
+ Ptr, // attributes
+ Ptr, // information
+ int // status
+ > Value;
+ }
+
[Reloaded.Hooks.Definitions.X64.Function(Reloaded.Hooks.Definitions.X64.CallingConventions.Microsoft)]
[Reloaded.Hooks.Definitions.X86.Function(Reloaded.Hooks.Definitions.X86.CallingConventions.Stdcall)]
public struct NtQueryFullAttributesFileFn
@@ -118,6 +129,17 @@ public struct FILE_STANDARD_INFORMATION
public bool Directory;
}
+ [StructLayout(LayoutKind.Sequential)]
+ public struct FILE_BASIC_INFORMATION
+ {
+ public LARGE_INTEGER CreationTime;
+ public LARGE_INTEGER LastAccessTime;
+ public LARGE_INTEGER LastWriteTime;
+ public LARGE_INTEGER ChangeTime;
+ public ulong FileAttributes;
+ }
+
+
[StructLayout(LayoutKind.Sequential)]
public struct FILE_NETWORK_OPEN_INFORMATION
{
diff --git a/FileEmulationFramework/Utilities/NativeFunctions.cs b/FileEmulationFramework/Utilities/NativeFunctions.cs
index c2db931..24f1f52 100644
--- a/FileEmulationFramework/Utilities/NativeFunctions.cs
+++ b/FileEmulationFramework/Utilities/NativeFunctions.cs
@@ -16,15 +16,17 @@ public struct NativeFunctions
public IFunction SetFilePointer;
public IFunction GetFileSize;
public IFunction NtQueryFullAttributes;
+ public IFunction NtQueryAttributes;
public IFunction CloseHandle;
- public NativeFunctions(IntPtr ntCreateFile, IntPtr ntReadFile, IntPtr ntSetInformationFile, IntPtr ntQueryInformationFile, IntPtr ntQueryFullAttributesFile, IntPtr closeHandle, IReloadedHooks hooks)
+ public NativeFunctions(IntPtr ntCreateFile, IntPtr ntReadFile, IntPtr ntSetInformationFile, IntPtr ntQueryInformationFile, IntPtr ntQueryFullAttributesFile, IntPtr ntQueryAttributesFile, IntPtr closeHandle, IReloadedHooks hooks)
{
NtCreateFile = hooks.CreateFunction(ntCreateFile);
NtReadFile = hooks.CreateFunction(ntReadFile);
SetFilePointer = hooks.CreateFunction(ntSetInformationFile);
GetFileSize = hooks.CreateFunction(ntQueryInformationFile);
NtQueryFullAttributes = hooks.CreateFunction(ntQueryFullAttributesFile);
+ NtQueryAttributes = hooks.CreateFunction(ntQueryAttributesFile);
CloseHandle = hooks.CreateFunction(closeHandle);
}
@@ -41,12 +43,13 @@ public static NativeFunctions GetInstance(IReloadedHooks hooks)
var ntReadFilePointer = NativeFn.GetProcAddress(ntdllHandle, "NtReadFile");
var setFilePointer = NativeFn.GetProcAddress(ntdllHandle, "NtSetInformationFile");
var getFileSize = NativeFn.GetProcAddress(ntdllHandle, "NtQueryInformationFile");
- var ntQueryAttributesPointer = NativeFn.GetProcAddress(ntdllHandle, "NtQueryFullAttributesFile");
+ var ntQueryFullAttributesPointer = NativeFn.GetProcAddress(ntdllHandle, "NtQueryFullAttributesFile");
+ var ntQueryAttributesPointer = NativeFn.GetProcAddress(ntdllHandle, "NtQueryAttributesFile");
var k32Handle = NativeFn.LoadLibrary("kernel32");
var closeHandle = NativeFn.GetProcAddress(k32Handle, "CloseHandle");
- _instance = new(ntCreateFilePointer, ntReadFilePointer, setFilePointer, getFileSize, ntQueryAttributesPointer, closeHandle, hooks);
+ _instance = new(ntCreateFilePointer, ntReadFilePointer, setFilePointer, getFileSize, ntQueryFullAttributesPointer, ntQueryAttributesPointer, closeHandle, hooks);
_instanceMade = true;
return _instance;
diff --git a/Submodules/Atlus-Script-Tools b/Submodules/Atlus-Script-Tools
index 59a6b9b..fce608b 160000
--- a/Submodules/Atlus-Script-Tools
+++ b/Submodules/Atlus-Script-Tools
@@ -1 +1 @@
-Subproject commit 59a6b9b54846b95675241a81ec3d6517243d3309
+Subproject commit fce608b6238f645b4f7bf6c7a5d70c0305d2499b
diff --git a/global.json b/global.json
index 17d3d8d..d4e0b89 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,7 @@
{
- "sdk": {
- "version": "7.0.100",
- "rollForward": "latestFeature"
- }
- }
\ No newline at end of file
+ "sdk": {
+ "version": "8.0.0",
+ "rollForward": "latestFeature",
+ "allowPrerelease": true
+ }
+}
\ No newline at end of file