Try to fix actor redrawing stalling again, some fixes for collections not correctly reloading metadata or changing wrong collection. Added hidden debug tab that can be activated by /penumbra debug (and is active when compiled in debug mode by default).

This commit is contained in:
Ottermandias 2021-07-10 22:20:18 +02:00
parent cfdff30189
commit cf223c927c
15 changed files with 309 additions and 60 deletions

View file

@ -10,7 +10,7 @@ namespace Penumbra.Game
public class CharEquipment
{
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
private readonly struct Weapon
internal readonly struct Weapon
{
public readonly ushort _1;
public readonly ushort _2;
@ -22,7 +22,7 @@ namespace Penumbra.Game
}
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
private readonly struct Equip
internal readonly struct Equip
{
public readonly ushort _1;
public readonly byte _2;
@ -39,18 +39,18 @@ namespace Penumbra.Game
private const int WeaponSlots = 2;
private readonly ushort IsSet; // Also fills struct size to 56, a multiple of 8.
private readonly Weapon Mainhand;
private readonly Weapon Offhand;
private readonly Equip Head;
private readonly Equip Body;
private readonly Equip Hands;
private readonly Equip Legs;
private readonly Equip Feet;
private readonly Equip Ear;
private readonly Equip Neck;
private readonly Equip Wrist;
private readonly Equip LFinger;
private readonly Equip RFinger;
internal readonly Weapon Mainhand;
internal readonly Weapon Offhand;
internal readonly Equip Head;
internal readonly Equip Body;
internal readonly Equip Hands;
internal readonly Equip Legs;
internal readonly Equip Feet;
internal readonly Equip Ear;
internal readonly Equip Neck;
internal readonly Equip Wrist;
internal readonly Equip LFinger;
internal readonly Equip RFinger;
public CharEquipment()
=> Clear();

View file

@ -286,5 +286,23 @@ namespace Penumbra.Game
IdentifyParsed( set, info );
}
}
public Item? Identify( ushort a, ushort b, ushort c, EquipSlot slot )
{
switch( slot )
{
case EquipSlot.MainHand:
case EquipSlot.Offhand:
{
var (begin, _) = FindIndexRange( _weapons, ( ( ulong )a << 32 ) | ( ( ulong )b << 16 ) | c, 0xFFFFFFFFFFFF );
return begin >= 0 ? _weapons[ begin ].Item2.FirstOrDefault() : null;
}
default:
{
var (begin, _) = FindIndexRange( _equipment, ( ( ulong )a << 32 ) | ( ( ulong )slot.ToSlot() << 16 ) | b, 0xFFFFFFFFFFFF );
return begin >= 0 ? _equipment[ begin ].Item2.FirstOrDefault() : null;
}
}
}
}
}

View file

@ -22,11 +22,14 @@ namespace Penumbra.Interop
public class ActorRefresher : IDisposable
{
[Flags]
private enum LoadingFlags : int
public enum LoadingFlags : int
{
Invisibility = 0x00_02,
IsLoading = 0x08_00,
SomeNpcFlag = 0x01_00,
Invisibility = 0x00_00_00_02,
IsLoading = 0x00_00_08_00,
SomeNpcFlag = 0x00_00_01_00,
MaybeCulled = 0x00_00_04_00,
MaybeHiddenMinion = 0x00_00_80_00,
MaybeHiddenSummon = 0x00_80_00_00,
}
private const int RenderModeOffset = 0x0104;
@ -43,6 +46,9 @@ namespace Penumbra.Interop
private string? _currentActorName = null;
private Redraw _currentActorRedraw = Redraw.Unload;
public static IntPtr RenderPtr( Actor actor )
=> actor.Address + RenderModeOffset;
public ActorRefresher( DalamudPluginInterface pi, ModManager mods )
{
_pi = pi;
@ -74,10 +80,16 @@ namespace Penumbra.Interop
private static unsafe bool StillLoading( IntPtr renderPtr )
{
const LoadingFlags stillLoadingFlags = LoadingFlags.SomeNpcFlag
| LoadingFlags.MaybeCulled
| LoadingFlags.MaybeHiddenMinion
| LoadingFlags.MaybeHiddenSummon;
if( renderPtr != IntPtr.Zero )
{
var loadingFlags = *( LoadingFlags* )renderPtr;
return loadingFlags != 0 && !loadingFlags.HasFlag( LoadingFlags.SomeNpcFlag );
return !( loadingFlags == 0 || ( loadingFlags & stillLoadingFlags ) != 0 );
}
return false;
@ -140,7 +152,7 @@ namespace Penumbra.Interop
return;
}
if( StillLoading( actor.Address + RenderModeOffset ) )
if( StillLoading( RenderPtr( actor ) ) )
{
return;
}
@ -148,7 +160,7 @@ namespace Penumbra.Interop
switch( _currentActorRedraw )
{
case Redraw.Unload:
WriteInvisible( actor.Address + RenderModeOffset );
WriteInvisible( RenderPtr( actor ) );
_currentFrame = 0;
break;
case Redraw.RedrawWithSettings:
@ -156,16 +168,16 @@ namespace Penumbra.Interop
++_currentFrame;
break;
case Redraw.RedrawWithoutSettings:
WriteVisible( actor.Address + RenderModeOffset );
WriteVisible( RenderPtr( actor ) );
_currentFrame = 0;
break;
case Redraw.WithoutSettings:
WriteInvisible( actor.Address + RenderModeOffset );
WriteInvisible( RenderPtr( actor ) );
++_currentFrame;
break;
case Redraw.WithSettings:
ChangeSettings();
WriteInvisible( actor.Address + RenderModeOffset );
WriteInvisible( RenderPtr( actor ) );
++_currentFrame;
break;
case Redraw.OnlyWithSettings:
@ -175,7 +187,7 @@ namespace Penumbra.Interop
return;
}
WriteInvisible( actor.Address + RenderModeOffset );
WriteInvisible( RenderPtr( actor ) );
++_currentFrame;
break;
default: throw new InvalidEnumArgumentException();
@ -191,7 +203,7 @@ namespace Penumbra.Interop
return;
}
WriteVisible( actor.Address + RenderModeOffset );
WriteVisible( RenderPtr( actor ) );
_currentFrame = _changedSettings ? _currentFrame + 1 : 0;
}
@ -200,7 +212,7 @@ namespace Penumbra.Interop
var actor = FindCurrentActor();
if( actor != null )
{
if( !StillLoading( actor.Address + RenderModeOffset ) )
if( !StillLoading( RenderPtr( actor ) ) )
{
RestoreSettings();
_currentFrame = 0;
@ -292,7 +304,7 @@ namespace Penumbra.Interop
Clear();
foreach( var a in _pi.ClientState.Actors )
{
WriteInvisible( a.Address + RenderModeOffset );
WriteInvisible( RenderPtr( a ) );
}
}
@ -301,7 +313,7 @@ namespace Penumbra.Interop
Clear();
foreach( var a in _pi.ClientState.Actors )
{
WriteVisible( a.Address + RenderModeOffset );
WriteVisible( RenderPtr( a ) );
}
}

View file

@ -21,9 +21,9 @@ namespace Penumbra.Meta
public FileInformation( object data )
=> Data = data;
public void Write( DirectoryInfo dir )
public void Write( DirectoryInfo dir, GamePath originalPath )
{
byte[] data = Data switch
var data = Data switch
{
EqdpFile eqdp => eqdp.WriteBytes(),
EqpFile eqp => eqp.WriteBytes(),
@ -34,7 +34,7 @@ namespace Penumbra.Meta
_ => throw new NotImplementedException(),
};
DisposeFile( CurrentFile );
CurrentFile = TempFile.WriteNew( dir, data );
CurrentFile = TempFile.WriteNew( dir, data, $"_{originalPath.Filename()}" );
Changed = false;
}
}
@ -93,22 +93,28 @@ namespace Penumbra.Meta
public void Dispose()
=> Reset();
private void ClearDirectory()
private static void ClearDirectory( DirectoryInfo modDir )
{
_dir.Refresh();
if( _dir.Exists )
modDir.Refresh();
if( modDir.Exists )
{
try
{
Directory.Delete( _dir.FullName, true );
Directory.Delete( modDir.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not clear temporary metafile directory \"{_dir.FullName}\":\n{e}" );
PluginLog.Error( $"Could not clear temporary metafile directory \"{modDir.FullName}\":\n{e}" );
}
}
}
private void ClearDirectory()
=> ClearDirectory( _dir );
public static void ClearBaseDirectory( DirectoryInfo modDir )
=> ClearDirectory( new DirectoryInfo( Path.Combine( modDir.FullName, TmpDirectory ) ) );
public MetaManager( string name, Dictionary< GamePath, FileInfo > resolvedFiles, DirectoryInfo modDir )
{
_resolvedFiles = resolvedFiles;
@ -123,7 +129,7 @@ namespace Penumbra.Meta
Directory.CreateDirectory( _dir.FullName );
foreach( var kvp in _currentFiles.Where( kvp => kvp.Value.Changed ) )
{
kvp.Value.Write( _dir );
kvp.Value.Write( _dir, kvp.Key );
_resolvedFiles[ kvp.Key ] = kvp.Value.CurrentFile!;
}
}

View file

@ -174,8 +174,8 @@ namespace Penumbra.Mods
_plugin.Configuration.Save();
}
public void SetCurrentCollection( ModCollection newCollection )
=> SetCollection( newCollection, CurrentCollection, c =>
public void SetDefaultCollection( ModCollection newCollection )
=> SetCollection( newCollection, DefaultCollection, c =>
{
if( ActiveCollection == DefaultCollection )
{
@ -184,19 +184,30 @@ namespace Penumbra.Mods
resourceManager.ReloadPlayerResources();
}
CurrentCollection = c;
}, s => _plugin.Configuration.CurrentCollection = s );
DefaultCollection = c;
}, s => _plugin.Configuration.DefaultCollection = s );
public void SetForcedCollection( ModCollection newCollection )
=> SetCollection( newCollection, ForcedCollection, c => ForcedCollection = c, s => _plugin.Configuration.ForcedCollection = s );
public void SetDefaultCollection( ModCollection newCollection )
=> SetCollection( newCollection, DefaultCollection, c => DefaultCollection = c, s => _plugin.Configuration.DefaultCollection = s );
public void SetCurrentCollection( ModCollection newCollection )
=> SetCollection( newCollection, CurrentCollection, c => CurrentCollection = c, s => _plugin.Configuration.CurrentCollection = s );
public void SetCharacterCollection( string characterName, ModCollection newCollection )
=> SetCollection( newCollection,
CharacterCollection.TryGetValue( characterName, out var oldCollection ) ? oldCollection : ModCollection.Empty,
c => CharacterCollection[ characterName ] = c, s => _plugin.Configuration.CharacterCollections[ characterName ] = s );
c =>
{
if( CharacterCollection.TryGetValue( characterName, out var collection )
&& ActiveCollection == collection )
{
ActiveCollection = c;
var resourceManager = Service< GameResourceManagement >.Get();
resourceManager.ReloadPlayerResources();
}
CharacterCollection[ characterName ] = c;
}, s => _plugin.Configuration.CharacterCollections[ characterName ] = s );
public bool CreateCharacterCollection( string characterName )
{

View file

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Penumbra.Interop;
using Penumbra.Mod;
using Penumbra.Util;
@ -87,7 +88,7 @@ namespace Penumbra.Mods
}
Cache.SortMods();
CalculateEffectiveFileList( modDirectory, true );
CalculateEffectiveFileList( modDirectory, true, false );
}
public void ClearCache()
@ -125,7 +126,7 @@ namespace Penumbra.Mods
}
}
public void CalculateEffectiveFileList( DirectoryInfo modDir, bool withMetaManipulations )
public void CalculateEffectiveFileList( DirectoryInfo modDir, bool withMetaManipulations, bool activeCollection )
{
Cache ??= new ModCollectionCache( Name, modDir );
UpdateSettings();
@ -133,6 +134,10 @@ namespace Penumbra.Mods
if( withMetaManipulations )
{
Cache.UpdateMetaManipulations();
if( activeCollection )
{
Service< GameResourceManagement >.Get().ReloadPlayerResources();
}
}
}
@ -240,6 +245,6 @@ namespace Penumbra.Mods
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
=> Cache?.ResolveSwappedOrReplacementPath( gameResourcePath );
public static readonly ModCollection Empty = new(){ Name = "" };
public static readonly ModCollection Empty = new() { Name = "" };
}
}

View file

@ -26,6 +26,8 @@ namespace Penumbra.Mods
{
_plugin = plugin;
BasePath = new DirectoryInfo( plugin.Configuration.ModDirectory );
MetaManager.ClearBaseDirectory( BasePath );
Collections = new CollectionManager( plugin, this );
}

View file

@ -216,7 +216,8 @@ namespace Penumbra.Mods
manager.Collections.SaveCollection( collection );
if( collection.Cache != null && settings.Enabled )
{
collection.CalculateEffectiveFileList( manager.BasePath, mod.Resources.MetaManipulations.Count > 0 );
collection.CalculateEffectiveFileList( manager.BasePath, mod.Resources.MetaManipulations.Count > 0,
collection == manager.Collections.ActiveCollection );
}
}
}

View file

@ -5,6 +5,7 @@ using EmbedIO.WebApi;
using Penumbra.API;
using Penumbra.Game;
using Penumbra.Interop;
using Penumbra.Meta;
using Penumbra.Meta.Files;
using Penumbra.Mods;
using Penumbra.UI;
@ -154,6 +155,11 @@ namespace Penumbra
break;
}
case "debug":
{
SettingsInterface.MakeDebugTabVisible();
break;
}
}
return;

View file

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
using ImGuiNET;
using Penumbra.Game;
using Penumbra.Game.Enums;
using Penumbra.Interop;
using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private void DrawDebugTabActors()
{
if( !ImGui.CollapsingHeader( "Actors##Debug" ) )
{
return;
}
var actors = ( Dictionary< string, CharEquipment >? )_plugin.PlayerWatcher.GetType()
.GetField( "_equip", BindingFlags.Instance | BindingFlags.NonPublic )
?.GetValue( _plugin.PlayerWatcher )
?? new Dictionary< string, CharEquipment >();
if( actors.Any() && ImGui.BeginTable( "##ActorTable", 13, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.ScrollX ) )
{
var identifier = Service< ObjectIdentification >.Get();
ImGui.TableNextRow();
foreach( var actor in actors )
{
ImGui.TableNextColumn();
ImGui.Text( actor.Key );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Mainhand} {identifier.Identify( actor.Value.Mainhand._1, actor.Value.Mainhand._2, actor.Value.Mainhand._3, EquipSlot.MainHand )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Offhand} {identifier.Identify( actor.Value.Offhand._1, actor.Value.Offhand._2, actor.Value.Offhand._3, EquipSlot.Offhand )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Head} {identifier.Identify( actor.Value.Head._1, actor.Value.Head._2, 0, EquipSlot.Head )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Body} {identifier.Identify( actor.Value.Body._1, actor.Value.Body._2, 0, EquipSlot.Body )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Hands} {identifier.Identify( actor.Value.Hands._1, actor.Value.Hands._2, 0, EquipSlot.Hands )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Legs} {identifier.Identify( actor.Value.Legs._1, actor.Value.Legs._2, 0, EquipSlot.Legs )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Feet} {identifier.Identify( actor.Value.Feet._1, actor.Value.Feet._2, 0, EquipSlot.Feet )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Ear} {identifier.Identify( actor.Value.Ear._1, actor.Value.Ear._2, 0, EquipSlot.Ears )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Neck} {identifier.Identify( actor.Value.Neck._1, actor.Value.Neck._2, 0, EquipSlot.Neck )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.Wrist} {identifier.Identify( actor.Value.Wrist._1, actor.Value.Wrist._2, 0, EquipSlot.Wrists )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.LFinger} {identifier.Identify( actor.Value.LFinger._1, actor.Value.LFinger._2, 0, EquipSlot.RingL )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextColumn();
ImGui.Text(
$"{actor.Value.RFinger} {identifier.Identify( actor.Value.RFinger._1, actor.Value.RFinger._2, 0, EquipSlot.RingL )?.Name.ToString() ?? "Unknown"}" );
ImGui.TableNextRow();
}
ImGui.EndTable();
}
}
private void DrawDebugTabGeneral()
{
if( !ImGui.CollapsingHeader( "General##Debug" ) )
{
return;
}
ImGui.Text( $"Active Collection: {Service< ModManager >.Get().Collections.ActiveCollection.Name}" );
}
private void DrawDebugTabRedraw()
{
if( !ImGui.CollapsingHeader( "Redrawing##Debug" ) )
{
return;
}
var queue = ( Queue< (int, string, Redraw) >? )_plugin.ActorRefresher.GetType()
.GetField( "_actorIds", BindingFlags.Instance | BindingFlags.NonPublic )
?.GetValue( _plugin.ActorRefresher )
?? new Queue< (int, string, Redraw) >();
var currentFrame = ( int? )_plugin.ActorRefresher.GetType()
.GetField( "_currentFrame", BindingFlags.Instance | BindingFlags.NonPublic )
?.GetValue( _plugin.ActorRefresher );
var changedSettings = ( bool? )_plugin.ActorRefresher.GetType()
.GetField( "_changedSettings", BindingFlags.Instance | BindingFlags.NonPublic )
?.GetValue( _plugin.ActorRefresher );
var currentActorId = ( int? )_plugin.ActorRefresher.GetType()
.GetField( "_currentActorId", BindingFlags.Instance | BindingFlags.NonPublic )
?.GetValue( _plugin.ActorRefresher );
var currentActorName = ( string? )_plugin.ActorRefresher.GetType()
.GetField( "_currentActorName", BindingFlags.Instance | BindingFlags.NonPublic )
?.GetValue( _plugin.ActorRefresher );
var currentActorRedraw = ( Redraw? )_plugin.ActorRefresher.GetType()
.GetField( "_currentActorRedraw", BindingFlags.Instance | BindingFlags.NonPublic )
?.GetValue( _plugin.ActorRefresher );
var currentActor = ( Actor? )_plugin.ActorRefresher.GetType()
.GetMethod( "FindCurrentActor", BindingFlags.NonPublic | BindingFlags.Instance )?
.Invoke( _plugin.ActorRefresher, Array.Empty< object >() );
var currentRender = currentActor != null
? ( ActorRefresher.LoadingFlags? )Marshal.ReadInt32( ActorRefresher.RenderPtr( currentActor ) )
: null;
ImGui.Text( $"Current Frame: {currentFrame?.ToString() ?? "null"}" );
ImGui.Text( $"Current Changed Settings: {changedSettings?.ToString() ?? "null"}" );
ImGui.Text( $"Current Actor Id: {currentActorId?.ToString( "X8" ) ?? "null"}" );
ImGui.Text( $"Current Actor Name: {currentActorName ?? "null"}" );
ImGui.Text( $"Current Actor Redraw: {currentActorRedraw.ToString() ?? "null"}" );
ImGui.Text( $"Current Actor Address: {currentActor?.Address.ToString( "X16" ) ?? "null"}" );
ImGui.Text( $"Current Actor Render Flags: {( ( int? )currentRender )?.ToString( "X8" ) ?? "null"}" );
if( queue.Any() && ImGui.BeginTable( "##RedrawTable", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.ScrollX ) )
{
ImGui.TableNextRow();
foreach( var (actorId, actorName, redraw) in queue )
{
ImGui.TableNextColumn();
ImGui.Text( actorName );
ImGui.TableNextColumn();
ImGui.Text( $"0x{actorId:X8}" );
ImGui.TableNextColumn();
ImGui.Text( redraw.ToString() );
ImGui.TableNextRow();
}
ImGui.EndTable();
}
}
private void DrawDebugTab()
{
if( !ImGui.BeginTabItem( "Debug Tab" ) )
{
return;
}
DrawDebugTabGeneral();
DrawDebugTabRedraw();
DrawDebugTabActors();
ImGui.EndTabItem();
}
}
}

View file

@ -565,7 +565,8 @@ namespace Penumbra.UI
if( Mod.Settings.Enabled && _modManager.Collections.CurrentCollection.Cache != null )
{
_modManager.Collections.CurrentCollection.CalculateEffectiveFileList( Mod.Data.BasePath,
Mod.Data.Resources.MetaManipulations.Count > 0 );
Mod.Data.Resources.MetaManipulations.Count > 0,
_modManager.Collections.CurrentCollection == _modManager.Collections.ActiveCollection );
}
Save();
@ -605,7 +606,8 @@ namespace Penumbra.UI
if( Mod.Settings.Enabled && _modManager.Collections.CurrentCollection.Cache != null )
{
_modManager.Collections.CurrentCollection.CalculateEffectiveFileList( Mod.Data.BasePath,
Mod.Data.Resources.MetaManipulations.Count > 0 );
Mod.Data.Resources.MetaManipulations.Count > 0,
_modManager.Collections.CurrentCollection == _modManager.Collections.ActiveCollection );
}
Save();

View file

@ -205,7 +205,8 @@ namespace Penumbra.UI
collection.Save( _base._plugin.PluginInterface! );
if( collection.Cache != null )
{
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0 );
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0,
collection == _modManager.Collections.ActiveCollection );
}
}
@ -226,7 +227,8 @@ namespace Penumbra.UI
collection.Save( _base._plugin.PluginInterface! );
if( collection.Cache != null )
{
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0 );
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0,
collection == _modManager.Collections.ActiveCollection );
}
}
}

View file

@ -29,6 +29,9 @@ namespace Penumbra.UI
public void FlipVisibility()
=> _menu.Visible = !_menu.Visible;
public void MakeDebugTabVisible()
=> _menu.DebugTabVisible = true;
public void Draw()
{
_menuBar.Draw();

View file

@ -37,6 +37,7 @@ namespace Penumbra.UI
private const bool DefaultVisibility = false;
#endif
public bool Visible = DefaultVisibility;
public bool DebugTabVisible = DefaultVisibility;
public void Draw()
{
@ -73,6 +74,11 @@ namespace Penumbra.UI
}
}
if( DebugTabVisible )
{
_base.DrawDebugTab();
}
ImGui.EndTabBar();
ImGui.End();
}

View file

@ -1,16 +1,17 @@
using System.IO;
using System.Linq;
namespace Penumbra.Util
{
public static class TempFile
{
public static FileInfo TempFileName( DirectoryInfo baseDir )
public static FileInfo TempFileName( DirectoryInfo baseDir, string suffix = "")
{
const uint maxTries = 15;
for( var i = 0; i < maxTries; ++i )
{
var name = Path.GetRandomFileName();
var path = new FileInfo( Path.Combine( baseDir.FullName, name ) );
var path = new FileInfo( Path.Combine( baseDir.FullName, suffix.Any() ? name.Substring( 0, name.LastIndexOf( '.' ) ) + suffix : name ) );
if( !path.Exists )
{
return path;
@ -20,9 +21,9 @@ namespace Penumbra.Util
throw new IOException();
}
public static FileInfo WriteNew( DirectoryInfo baseDir, byte[] data )
public static FileInfo WriteNew( DirectoryInfo baseDir, byte[] data, string suffix = "" )
{
var fileName = TempFileName( baseDir );
var fileName = TempFileName( baseDir, suffix );
File.WriteAllBytes( fileName.FullName, data );
fileName.Refresh();
return fileName;