mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Merge branch 'develop' into MetaData
This commit is contained in:
commit
ba7cf17d68
8 changed files with 329 additions and 167 deletions
|
|
@ -65,7 +65,7 @@ namespace Penumbra.Importer
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can in no way rely on any file paths in TTMPs so we need to just do this, sorry
|
// You can in no way rely on any file paths in TTMPs so we need to just do this, sorry
|
||||||
private ZipEntry FindZipEntry( ZipFile file, string fileName )
|
private static ZipEntry? FindZipEntry( ZipFile file, string fileName )
|
||||||
{
|
{
|
||||||
for( var i = 0; i < file.Count; i++ )
|
for( var i = 0; i < file.Count; i++ )
|
||||||
{
|
{
|
||||||
|
|
@ -84,7 +84,10 @@ namespace Penumbra.Importer
|
||||||
|
|
||||||
// write shitty zip garbage to disk
|
// write shitty zip garbage to disk
|
||||||
var entry = FindZipEntry( file, entryName );
|
var entry = FindZipEntry( file, entryName );
|
||||||
Debug.Assert( entry != null, $"Could not find in mod zip: {entryName}" );
|
if( entry == null )
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException( $"ZIP does not contain a file named {entryName}." );
|
||||||
|
}
|
||||||
|
|
||||||
using var s = file.GetInputStream( entry );
|
using var s = file.GetInputStream( entry );
|
||||||
|
|
||||||
|
|
@ -100,7 +103,10 @@ namespace Penumbra.Importer
|
||||||
using var extractedModPack = new ZipFile( zfs );
|
using var extractedModPack = new ZipFile( zfs );
|
||||||
|
|
||||||
var mpl = FindZipEntry( extractedModPack, "TTMPL.mpl" );
|
var mpl = FindZipEntry( extractedModPack, "TTMPL.mpl" );
|
||||||
Debug.Assert( mpl != null, "Could not find mod meta in ZIP." );
|
if( mpl == null )
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException( "ZIP does not contain a TTMPL.mpl file." );
|
||||||
|
}
|
||||||
|
|
||||||
var modRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 );
|
var modRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ namespace Penumbra.Models
|
||||||
|
|
||||||
public List< string > ChangedItems { get; set; } = new();
|
public List< string > ChangedItems { get; set; } = new();
|
||||||
|
|
||||||
|
|
||||||
|
[JsonProperty( ItemConverterType = typeof( GamePathConverter ))]
|
||||||
public Dictionary< GamePath, GamePath > FileSwaps { get; } = new();
|
public Dictionary< GamePath, GamePath > FileSwaps { get; } = new();
|
||||||
|
|
||||||
public Dictionary< string, OptionGroup > Groups { get; set; } = new();
|
public Dictionary< string, OptionGroup > Groups { get; set; } = new();
|
||||||
|
|
|
||||||
|
|
@ -53,14 +53,14 @@ namespace Penumbra.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.ListBoxHeader( "##effective_files", AutoFillSize ) )
|
if( ImGui.BeginListBox( "##effective_files", AutoFillSize ) )
|
||||||
{
|
{
|
||||||
foreach( var file in _mods.ResolvedFiles )
|
foreach( var file in _mods.ResolvedFiles )
|
||||||
{
|
{
|
||||||
DrawFileLine( file.Value, file.Key );
|
DrawFileLine( file.Value, file.Key );
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
ImGui.EndListBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ namespace Penumbra.UI
|
||||||
private const string LabelConfigurationTab = "Configuration";
|
private const string LabelConfigurationTab = "Configuration";
|
||||||
|
|
||||||
private const string TooltipFilesTab =
|
private const string TooltipFilesTab =
|
||||||
"Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\n" +
|
"Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\n"
|
||||||
"Yellow files are restricted to some options.";
|
+ "Yellow files are restricted to some options.";
|
||||||
|
|
||||||
private const float TextSizePadding = 5f;
|
private const float TextSizePadding = 5f;
|
||||||
private const float OptionSelectionWidth = 140f;
|
private const float OptionSelectionWidth = 140f;
|
||||||
|
|
@ -130,8 +130,11 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is only drawn when we have a mod selected, so we can forgive nulls.
|
// This is only drawn when we have a mod selected, so we can forgive nulls.
|
||||||
private ModInfo Mod => _selector.Mod()!;
|
private ModInfo Mod
|
||||||
private ModMeta Meta => Mod.Mod.Meta;
|
=> _selector.Mod()!;
|
||||||
|
|
||||||
|
private ModMeta Meta
|
||||||
|
=> Mod.Mod.Meta;
|
||||||
|
|
||||||
private void Save()
|
private void Save()
|
||||||
{
|
{
|
||||||
|
|
@ -193,10 +196,10 @@ namespace Penumbra.UI
|
||||||
if( ImGui.BeginTabItem( LabelChangedItemsTab ) )
|
if( ImGui.BeginTabItem( LabelChangedItemsTab ) )
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
if( ImGui.ListBoxHeader( LabelChangedItemsHeader, AutoFillSize ) )
|
if( ImGui.BeginListBox( LabelChangedItemsHeader, AutoFillSize ) )
|
||||||
{
|
{
|
||||||
_changedItemsList ??= Meta.ChangedItems
|
_changedItemsList ??= Meta.ChangedItems
|
||||||
.Select( ( I, index ) => ( $"{LabelChangedItemIdx}{index}", I ) ).ToArray();
|
.Select( ( I, index ) => ( $"{LabelChangedItemIdx}{index}", I ) ).ToArray();
|
||||||
|
|
||||||
for( var i = 0; i < Meta.ChangedItems.Count; ++i )
|
for( var i = 0; i < Meta.ChangedItems.Count; ++i )
|
||||||
{
|
{
|
||||||
|
|
@ -212,15 +215,15 @@ namespace Penumbra.UI
|
||||||
if( _editMode )
|
if( _editMode )
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
if( ImGui.InputText( LabelChangedItemNew, ref newItem, 128, flags )
|
if( ImGui.InputTextWithHint( LabelChangedItemNew, "Enter new changed item...", ref newItem, 128, flags )
|
||||||
&& newItem.Length > 0 )
|
&& newItem.Length > 0 )
|
||||||
{
|
{
|
||||||
Meta.ChangedItems.Add( newItem );
|
Meta.ChangedItems.Add( newItem );
|
||||||
_selector.SaveCurrentMod();
|
_selector.SaveCurrentMod();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
ImGui.EndListBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
|
|
@ -239,7 +242,7 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
if( ImGui.ListBoxHeader( LabelConflictsHeader, AutoFillSize ) )
|
if( ImGui.BeginListBox( LabelConflictsHeader, AutoFillSize ) )
|
||||||
{
|
{
|
||||||
foreach( var kv in Mod.Mod.FileConflicts )
|
foreach( var kv in Mod.Mod.FileConflicts )
|
||||||
{
|
{
|
||||||
|
|
@ -258,7 +261,7 @@ namespace Penumbra.UI
|
||||||
ImGui.Unindent( 15 );
|
ImGui.Unindent( 15 );
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
ImGui.EndListBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
|
|
@ -266,6 +269,12 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private void DrawFileSwapTab()
|
private void DrawFileSwapTab()
|
||||||
{
|
{
|
||||||
|
if( _editMode )
|
||||||
|
{
|
||||||
|
DrawFileSwapTabEdit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if( !Meta.FileSwaps.Any() )
|
if( !Meta.FileSwaps.Any() )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -274,10 +283,11 @@ namespace Penumbra.UI
|
||||||
if( ImGui.BeginTabItem( LabelFileSwapTab ) )
|
if( ImGui.BeginTabItem( LabelFileSwapTab ) )
|
||||||
{
|
{
|
||||||
_fileSwapOffset ??= Meta.FileSwaps
|
_fileSwapOffset ??= Meta.FileSwaps
|
||||||
.Max( P => ImGui.CalcTextSize( P.Key ).X ) + TextSizePadding;
|
.Max( P => ImGui.CalcTextSize( P.Key ).X )
|
||||||
|
+ TextSizePadding;
|
||||||
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
if( ImGui.ListBoxHeader( LabelFileSwapHeader, AutoFillSize ) )
|
if( ImGui.BeginListBox( LabelFileSwapHeader, AutoFillSize ) )
|
||||||
{
|
{
|
||||||
foreach( var file in Meta.FileSwaps )
|
foreach( var file in Meta.FileSwaps )
|
||||||
{
|
{
|
||||||
|
|
@ -288,7 +298,7 @@ namespace Penumbra.UI
|
||||||
ImGui.Selectable( file.Value );
|
ImGui.Selectable( file.Value );
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
ImGui.EndListBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
|
|
@ -308,7 +318,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
var len = Mod.Mod.ModBasePath.FullName.Length;
|
var len = Mod.Mod.ModBasePath.FullName.Length;
|
||||||
_fullFilenameList = Mod.Mod.ModFiles
|
_fullFilenameList = Mod.Mod.ModFiles
|
||||||
.Select( F => ( F, false, ColorGreen, new RelPath( F, Mod.Mod.ModBasePath ) ) ).ToArray();
|
.Select( F => ( F, false, ColorGreen, new RelPath( F, Mod.Mod.ModBasePath ) ) ).ToArray();
|
||||||
|
|
||||||
if( Meta.Groups.Count == 0 )
|
if( Meta.Groups.Count == 0 )
|
||||||
{
|
{
|
||||||
|
|
@ -353,7 +363,7 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize ) )
|
if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize ) )
|
||||||
{
|
{
|
||||||
UpdateFilenameList();
|
UpdateFilenameList();
|
||||||
foreach( var file in _fullFilenameList! )
|
foreach( var file in _fullFilenameList! )
|
||||||
|
|
@ -363,7 +373,7 @@ namespace Penumbra.UI
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
ImGui.EndListBox();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -389,7 +399,7 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
if( path[ TextDefaultGamePath.Length ] != '-'
|
if( path[ TextDefaultGamePath.Length ] != '-'
|
||||||
|| !int.TryParse( path.Substring( TextDefaultGamePath.Length + 1 ), out removeFolders ) )
|
|| !int.TryParse( path.Substring( TextDefaultGamePath.Length + 1 ), out removeFolders ) )
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
@ -442,7 +452,7 @@ namespace Penumbra.UI
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
changed = gamePaths
|
changed = gamePaths
|
||||||
.Aggregate( changed, ( current, gamePath ) => current | option.AddFile( relName, gamePath ) );
|
.Aggregate( changed, ( current, gamePath ) => current | option.AddFile( relName, gamePath ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -473,7 +483,7 @@ namespace Penumbra.UI
|
||||||
ImGui.TextUnformatted( LabelGamePathsEdit );
|
ImGui.TextUnformatted( LabelGamePathsEdit );
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
ImGui.InputText( LabelGamePathsEditBox, ref _currentGamePaths, 128 );
|
ImGui.InputTextWithHint( LabelGamePathsEditBox, "Hover for help...", ref _currentGamePaths, 128 );
|
||||||
if( ImGui.IsItemHovered() )
|
if( ImGui.IsItemHovered() )
|
||||||
{
|
{
|
||||||
ImGui.SetTooltip( TooltipGamePathsEdit );
|
ImGui.SetTooltip( TooltipGamePathsEdit );
|
||||||
|
|
@ -545,7 +555,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
string tmp = gamePath;
|
string tmp = gamePath;
|
||||||
if( ImGui.InputText( $"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue )
|
if( ImGui.InputText( $"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue )
|
||||||
&& tmp != gamePath )
|
&& tmp != gamePath )
|
||||||
{
|
{
|
||||||
gamePaths.Remove( gamePath );
|
gamePaths.Remove( gamePath );
|
||||||
if( tmp.Length > 0 )
|
if( tmp.Length > 0 )
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,13 @@ namespace Penumbra.UI
|
||||||
private const char GamePathsSeparator = ';';
|
private const char GamePathsSeparator = ';';
|
||||||
|
|
||||||
private static readonly string TooltipFilesTabEdit =
|
private static readonly string TooltipFilesTabEdit =
|
||||||
$"{TooltipFilesTab}\n" +
|
$"{TooltipFilesTab}\n"
|
||||||
$"Red Files are replaced in another group or a different option in this group, but not contained in the current option.";
|
+ $"Red Files are replaced in another group or a different option in this group, but not contained in the current option.";
|
||||||
|
|
||||||
private static readonly string TooltipGamePathsEdit =
|
private static readonly string TooltipGamePathsEdit =
|
||||||
$"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\n" +
|
$"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\n"
|
||||||
$"Use '{TextDefaultGamePath}' to add the original file path." +
|
+ $"Use '{TextDefaultGamePath}' to add the original file path."
|
||||||
$"Use '{TextDefaultGamePath}-#' to skip the first # relative directories.";
|
+ $"Use '{TextDefaultGamePath}-#' to skip the first # relative directories.";
|
||||||
|
|
||||||
private const float MultiEditBoxWidth = 300f;
|
private const float MultiEditBoxWidth = 300f;
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize - new Vector2( 0, 1.5f * ImGui.GetTextLineHeight() ) ) )
|
if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize - new Vector2( 0, 1.5f * ImGui.GetTextLineHeight() ) ) )
|
||||||
{
|
{
|
||||||
for( var i = 0; i < Mod!.Mod.ModFiles.Count; ++i )
|
for( var i = 0; i < Mod!.Mod.ModFiles.Count; ++i )
|
||||||
{
|
{
|
||||||
|
|
@ -94,7 +94,7 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
ImGui.EndListBox();
|
||||||
|
|
||||||
DrawGroupRow();
|
DrawGroupRow();
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
|
|
@ -109,7 +109,8 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
var groupName = group.GroupName;
|
var groupName = group.GroupName;
|
||||||
if( ImGuiCustom.BeginFramedGroupEdit( ref groupName )
|
if( ImGuiCustom.BeginFramedGroupEdit( ref groupName )
|
||||||
&& groupName != group.GroupName && !Meta!.Groups.ContainsKey( groupName ) )
|
&& groupName != group.GroupName
|
||||||
|
&& !Meta!.Groups.ContainsKey( groupName ) )
|
||||||
{
|
{
|
||||||
var oldConf = Mod!.Settings[ group.GroupName ];
|
var oldConf = Mod!.Settings[ group.GroupName ];
|
||||||
Meta.Groups.Remove( group.GroupName );
|
Meta.Groups.Remove( group.GroupName );
|
||||||
|
|
@ -120,7 +121,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
GroupName = groupName,
|
GroupName = groupName,
|
||||||
SelectionType = SelectType.Multi,
|
SelectionType = SelectType.Multi,
|
||||||
Options = group.Options
|
Options = group.Options,
|
||||||
};
|
};
|
||||||
Mod.Settings[ groupName ] = oldConf;
|
Mod.Settings[ groupName ] = oldConf;
|
||||||
}
|
}
|
||||||
|
|
@ -136,8 +137,9 @@ namespace Penumbra.UI
|
||||||
var newOption = "";
|
var newOption = "";
|
||||||
ImGui.SetCursorPosX( nameBoxStart );
|
ImGui.SetCursorPosX( nameBoxStart );
|
||||||
ImGui.SetNextItemWidth( MultiEditBoxWidth );
|
ImGui.SetNextItemWidth( MultiEditBoxWidth );
|
||||||
if( ImGui.InputText( $"##new_{group.GroupName}_l", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue )
|
if( ImGui.InputTextWithHint( $"##new_{group.GroupName}_l", "Add new option...", ref newOption, 64,
|
||||||
&& newOption.Length != 0 )
|
ImGuiInputTextFlags.EnterReturnsTrue )
|
||||||
|
&& newOption.Length != 0 )
|
||||||
{
|
{
|
||||||
group.Options.Add( new Option()
|
group.Options.Add( new Option()
|
||||||
{ OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >() } );
|
{ OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >() } );
|
||||||
|
|
@ -200,7 +202,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
var groupName = group.GroupName;
|
var groupName = group.GroupName;
|
||||||
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue )
|
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue )
|
||||||
&& !Meta!.Groups.ContainsKey( groupName ) )
|
&& !Meta!.Groups.ContainsKey( groupName ) )
|
||||||
{
|
{
|
||||||
var oldConf = Mod!.Settings[ group.GroupName ];
|
var oldConf = Mod!.Settings[ group.GroupName ];
|
||||||
if( groupName != group.GroupName )
|
if( groupName != group.GroupName )
|
||||||
|
|
@ -215,7 +217,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
GroupName = groupName,
|
GroupName = groupName,
|
||||||
Options = group.Options,
|
Options = group.Options,
|
||||||
SelectionType = SelectType.Single
|
SelectionType = SelectType.Single,
|
||||||
} );
|
} );
|
||||||
Mod.Settings[ groupName ] = oldConf;
|
Mod.Settings[ groupName ] = oldConf;
|
||||||
}
|
}
|
||||||
|
|
@ -245,7 +247,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
OptionName = newName,
|
OptionName = newName,
|
||||||
OptionDesc = "",
|
OptionDesc = "",
|
||||||
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >()
|
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(),
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -264,7 +266,7 @@ namespace Penumbra.UI
|
||||||
group.Options[ code ] = new Option()
|
group.Options[ code ] = new Option()
|
||||||
{
|
{
|
||||||
OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc,
|
OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc,
|
||||||
OptionFiles = group.Options[ code ].OptionFiles
|
OptionFiles = group.Options[ code ].OptionFiles,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,7 +306,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
GroupName = newGroup,
|
GroupName = newGroup,
|
||||||
SelectionType = selectType,
|
SelectionType = selectType,
|
||||||
Options = new List< Option >()
|
Options = new List< Option >(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Mod.Settings[ newGroup ] = 0;
|
Mod.Settings[ newGroup ] = 0;
|
||||||
|
|
@ -314,12 +316,13 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private void DrawAddSingleGroupField( float labelEditPos )
|
private void DrawAddSingleGroupField( float labelEditPos )
|
||||||
{
|
{
|
||||||
var newGroup = "";
|
const string hint = "Add new Single Group...";
|
||||||
|
var newGroup = "";
|
||||||
if( labelEditPos == CheckMarkSize )
|
if( labelEditPos == CheckMarkSize )
|
||||||
{
|
{
|
||||||
ImGui.SetCursorPosX( CheckMarkSize );
|
ImGui.SetCursorPosX( CheckMarkSize );
|
||||||
ImGui.SetNextItemWidth( MultiEditBoxWidth );
|
ImGui.SetNextItemWidth( MultiEditBoxWidth );
|
||||||
if( ImGui.InputText( LabelNewSingleGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
if( ImGui.InputTextWithHint( LabelNewSingleGroup, hint, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||||
{
|
{
|
||||||
AddNewGroup( newGroup, SelectType.Single );
|
AddNewGroup( newGroup, SelectType.Single );
|
||||||
}
|
}
|
||||||
|
|
@ -327,7 +330,7 @@ namespace Penumbra.UI
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGuiCustom.RightJustifiedLabel( labelEditPos, LabelNewSingleGroup );
|
ImGuiCustom.RightJustifiedLabel( labelEditPos, LabelNewSingleGroup );
|
||||||
if( ImGui.InputText( LabelNewSingleGroupEdit, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
if( ImGui.InputTextWithHint( LabelNewSingleGroupEdit, hint, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||||
{
|
{
|
||||||
AddNewGroup( newGroup, SelectType.Single );
|
AddNewGroup( newGroup, SelectType.Single );
|
||||||
}
|
}
|
||||||
|
|
@ -339,7 +342,7 @@ namespace Penumbra.UI
|
||||||
var newGroup = "";
|
var newGroup = "";
|
||||||
ImGui.SetCursorPosX( CheckMarkSize );
|
ImGui.SetCursorPosX( CheckMarkSize );
|
||||||
ImGui.SetNextItemWidth( MultiEditBoxWidth );
|
ImGui.SetNextItemWidth( MultiEditBoxWidth );
|
||||||
if( ImGui.InputText( LabelNewMultiGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
if( ImGui.InputTextWithHint( LabelNewMultiGroup, "Add new Multi Group...", ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||||
{
|
{
|
||||||
AddNewGroup( newGroup, SelectType.Multi );
|
AddNewGroup( newGroup, SelectType.Multi );
|
||||||
}
|
}
|
||||||
|
|
@ -362,6 +365,87 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
DrawAddMultiGroupField();
|
DrawAddMultiGroupField();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawFileSwapTabEdit()
|
||||||
|
{
|
||||||
|
const string arrow = " -> ";
|
||||||
|
|
||||||
|
if( ImGui.BeginTabItem( LabelFileSwapTab ) )
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth( -1 );
|
||||||
|
if( ImGui.BeginListBox( LabelFileSwapHeader, AutoFillSize ) )
|
||||||
|
{
|
||||||
|
var swaps = Meta.FileSwaps.Keys.ToArray();
|
||||||
|
|
||||||
|
var arrowWidth = ImGui.CalcTextSize( arrow ).X;
|
||||||
|
var width = ( ImGui.GetWindowWidth() - arrowWidth - 4 * ImGui.GetStyle().ItemSpacing.X ) / 2;
|
||||||
|
for( var idx = 0; idx < swaps.Length + 1; ++idx )
|
||||||
|
{
|
||||||
|
var key = idx == swaps.Length ? GamePath.GenerateUnchecked( "" ) : swaps[ idx ];
|
||||||
|
var value = idx == swaps.Length ? GamePath.GenerateUnchecked( "" ) : Meta.FileSwaps[ key ];
|
||||||
|
string keyString = key;
|
||||||
|
string valueString = value;
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth( width );
|
||||||
|
if( ImGui.InputTextWithHint( $"##swapLhs_{idx}", "Enter new file to be replaced...", ref keyString,
|
||||||
|
GamePath.MaxGamePathLength, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||||
|
{
|
||||||
|
var newKey = new GamePath( keyString );
|
||||||
|
if( newKey.CompareTo( key ) != 0 )
|
||||||
|
{
|
||||||
|
if( idx < swaps.Length )
|
||||||
|
{
|
||||||
|
Meta.FileSwaps.Remove( key );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( newKey != string.Empty )
|
||||||
|
{
|
||||||
|
Meta.FileSwaps[ newKey ] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
if( Mod.Enabled )
|
||||||
|
{
|
||||||
|
_selector.ReloadCurrentMod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( idx < swaps.Length )
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted( arrow );
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth( width );
|
||||||
|
if( ImGui.InputTextWithHint( $"##swapRhs_{idx}", "Enter new replacement path...", ref valueString,
|
||||||
|
GamePath.MaxGamePathLength,
|
||||||
|
ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||||
|
{
|
||||||
|
var newValue = new GamePath( valueString );
|
||||||
|
if( newValue.CompareTo( value ) != 0 )
|
||||||
|
{
|
||||||
|
Meta.FileSwaps[ key ] = newValue;
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
if( Mod.Enabled )
|
||||||
|
{
|
||||||
|
_selector.ReloadCurrentMod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndListBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_fileSwapOffset = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +121,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 );
|
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 );
|
||||||
var tmp = _modFilter;
|
var tmp = _modFilter;
|
||||||
if( ImGui.InputText( LabelModFilter, ref tmp, 256 ) )
|
if( ImGui.InputTextWithHint( LabelModFilter, "Filter Mods...", ref tmp, 256 ) )
|
||||||
{
|
{
|
||||||
_modFilter = tmp.ToLowerInvariant();
|
_modFilter = tmp.ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Penumbra.Util
|
namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
|
|
@ -29,7 +31,7 @@ namespace Penumbra.Util
|
||||||
=> _path = CheckPre( file, baseDir ) ? Trim( Substring( file, baseDir ) ) : "";
|
=> _path = CheckPre( file, baseDir ) ? Trim( Substring( file, baseDir ) ) : "";
|
||||||
|
|
||||||
public RelPath( GamePath gamePath )
|
public RelPath( GamePath gamePath )
|
||||||
=> _path = gamePath ? ReplaceSlash( gamePath ) : "";
|
=> _path = ReplaceSlash( gamePath );
|
||||||
|
|
||||||
private static bool CheckPre( FileInfo file, DirectoryInfo baseDir )
|
private static bool CheckPre( FileInfo file, DirectoryInfo baseDir )
|
||||||
=> file.FullName.StartsWith( baseDir.FullName ) && file.FullName.Length < MaxRelPathLength;
|
=> file.FullName.StartsWith( baseDir.FullName ) && file.FullName.Length < MaxRelPathLength;
|
||||||
|
|
@ -43,22 +45,22 @@ namespace Penumbra.Util
|
||||||
private static string Trim( string path )
|
private static string Trim( string path )
|
||||||
=> path.TrimStart( '\\' );
|
=> path.TrimStart( '\\' );
|
||||||
|
|
||||||
public static implicit operator bool( RelPath relPath )
|
|
||||||
=> relPath._path.Length > 0;
|
|
||||||
|
|
||||||
public static implicit operator string( RelPath relPath )
|
public static implicit operator string( RelPath relPath )
|
||||||
=> relPath._path;
|
=> relPath._path;
|
||||||
|
|
||||||
public static explicit operator RelPath( string relPath )
|
public static explicit operator RelPath( string relPath )
|
||||||
=> new( relPath );
|
=> new( relPath );
|
||||||
|
|
||||||
|
public bool Empty
|
||||||
|
=> _path.Length == 0;
|
||||||
|
|
||||||
public int CompareTo( object rhs )
|
public int CompareTo( object rhs )
|
||||||
{
|
{
|
||||||
return rhs switch
|
return rhs switch
|
||||||
{
|
{
|
||||||
string => string.Compare( _path, _path, StringComparison.InvariantCulture ),
|
string path => string.Compare( _path, path, StringComparison.InvariantCulture ),
|
||||||
RelPath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ),
|
RelPath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ),
|
||||||
_ => -1
|
_ => -1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,10 +122,7 @@ namespace Penumbra.Util
|
||||||
=> new( path, true );
|
=> new( path, true );
|
||||||
|
|
||||||
public static GamePath GenerateUncheckedLower( string path )
|
public static GamePath GenerateUncheckedLower( string path )
|
||||||
=> new( Lower(path), true );
|
=> new( Lower( path ), true );
|
||||||
|
|
||||||
public static implicit operator bool( GamePath gamePath )
|
|
||||||
=> gamePath._path.Length > 0;
|
|
||||||
|
|
||||||
public static implicit operator string( GamePath gamePath )
|
public static implicit operator string( GamePath gamePath )
|
||||||
=> gamePath._path;
|
=> gamePath._path;
|
||||||
|
|
@ -131,6 +130,9 @@ namespace Penumbra.Util
|
||||||
public static explicit operator GamePath( string gamePath )
|
public static explicit operator GamePath( string gamePath )
|
||||||
=> new( gamePath );
|
=> new( gamePath );
|
||||||
|
|
||||||
|
public bool Empty
|
||||||
|
=> _path.Length == 0;
|
||||||
|
|
||||||
public string Filename()
|
public string Filename()
|
||||||
{
|
{
|
||||||
var idx = _path.LastIndexOf( "/", StringComparison.Ordinal );
|
var idx = _path.LastIndexOf( "/", StringComparison.Ordinal );
|
||||||
|
|
@ -143,11 +145,35 @@ namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
string path => string.Compare( _path, path, StringComparison.InvariantCulture ),
|
string path => string.Compare( _path, path, StringComparison.InvariantCulture ),
|
||||||
GamePath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ),
|
GamePath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ),
|
||||||
_ => -1
|
_ => -1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> _path;
|
=> _path;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public class GamePathConverter : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert( Type objectType )
|
||||||
|
=> objectType == typeof( GamePath );
|
||||||
|
|
||||||
|
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
||||||
|
{
|
||||||
|
var token = JToken.Load( reader );
|
||||||
|
return token.ToObject< GamePath >();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite
|
||||||
|
=> true;
|
||||||
|
|
||||||
|
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
||||||
|
{
|
||||||
|
if( value != null )
|
||||||
|
{
|
||||||
|
var v = ( GamePath) value;
|
||||||
|
serializer.Serialize( writer, v.ToString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Lumina;
|
using Lumina;
|
||||||
using Lumina.Data;
|
|
||||||
using Lumina.Data.Structs;
|
using Lumina.Data.Structs;
|
||||||
|
|
||||||
namespace Penumbra.Util
|
namespace Penumbra.Util
|
||||||
|
|
@ -19,12 +15,14 @@ namespace Penumbra.Util
|
||||||
|
|
||||||
protected BinaryReader Reader { get; set; }
|
protected BinaryReader Reader { get; set; }
|
||||||
|
|
||||||
public PenumbraSqPackStream( FileInfo file ) : this( file.OpenRead() ) {}
|
public PenumbraSqPackStream( FileInfo file )
|
||||||
|
: this( file.OpenRead() )
|
||||||
|
{ }
|
||||||
|
|
||||||
public PenumbraSqPackStream( Stream stream )
|
public PenumbraSqPackStream( Stream stream )
|
||||||
{
|
{
|
||||||
BaseStream = stream;
|
BaseStream = stream;
|
||||||
Reader = new BinaryReader( BaseStream );
|
Reader = new BinaryReader( BaseStream );
|
||||||
}
|
}
|
||||||
|
|
||||||
public SqPackHeader GetSqPackHeader()
|
public SqPackHeader GetSqPackHeader()
|
||||||
|
|
@ -48,7 +46,7 @@ namespace Penumbra.Util
|
||||||
BaseStream.Position = offset;
|
BaseStream.Position = offset;
|
||||||
|
|
||||||
var fileInfo = Reader.ReadStructure< SqPackFileInfo >();
|
var fileInfo = Reader.ReadStructure< SqPackFileInfo >();
|
||||||
var file = Activator.CreateInstance< T >();
|
var file = Activator.CreateInstance< T >();
|
||||||
|
|
||||||
// check if we need to read the extended model header or just default to the standard file header
|
// check if we need to read the extended model header or just default to the standard file header
|
||||||
if( fileInfo.Type == FileType.Model )
|
if( fileInfo.Type == FileType.Model )
|
||||||
|
|
@ -59,32 +57,31 @@ namespace Penumbra.Util
|
||||||
|
|
||||||
file.FileInfo = new PenumbraFileInfo
|
file.FileInfo = new PenumbraFileInfo
|
||||||
{
|
{
|
||||||
HeaderSize = modelFileInfo.Size,
|
HeaderSize = modelFileInfo.Size,
|
||||||
Type = modelFileInfo.Type,
|
Type = modelFileInfo.Type,
|
||||||
BlockCount = modelFileInfo.UsedNumberOfBlocks,
|
BlockCount = modelFileInfo.UsedNumberOfBlocks,
|
||||||
RawFileSize = modelFileInfo.RawFileSize,
|
RawFileSize = modelFileInfo.RawFileSize,
|
||||||
Offset = offset,
|
Offset = offset,
|
||||||
|
|
||||||
// todo: is this useful?
|
// todo: is this useful?
|
||||||
ModelBlock = modelFileInfo
|
ModelBlock = modelFileInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
file.FileInfo = new PenumbraFileInfo
|
file.FileInfo = new PenumbraFileInfo
|
||||||
{
|
{
|
||||||
HeaderSize = fileInfo.Size,
|
HeaderSize = fileInfo.Size,
|
||||||
Type = fileInfo.Type,
|
Type = fileInfo.Type,
|
||||||
BlockCount = fileInfo.NumberOfBlocks,
|
BlockCount = fileInfo.NumberOfBlocks,
|
||||||
RawFileSize = fileInfo.RawFileSize,
|
RawFileSize = fileInfo.RawFileSize,
|
||||||
Offset = offset
|
Offset = offset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
switch( fileInfo.Type )
|
switch( fileInfo.Type )
|
||||||
{
|
{
|
||||||
case FileType.Empty:
|
case FileType.Empty: throw new FileNotFoundException( $"The file located at 0x{offset:x} is empty." );
|
||||||
throw new FileNotFoundException( $"The file located at 0x{offset:x} is empty." );
|
|
||||||
|
|
||||||
case FileType.Standard:
|
case FileType.Standard:
|
||||||
ReadStandardFile( file, ms );
|
ReadStandardFile( file, ms );
|
||||||
|
|
@ -98,16 +95,17 @@ namespace Penumbra.Util
|
||||||
ReadTextureFile( file, ms );
|
ReadTextureFile( file, ms );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default: throw new NotImplementedException( $"File Type {( uint )fileInfo.Type} is not implemented." );
|
||||||
throw new NotImplementedException( $"File Type {(UInt32)fileInfo.Type} is not implemented." );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Data = ms.ToArray();
|
file.Data = ms.ToArray();
|
||||||
if( file.Data.Length != file.FileInfo.RawFileSize )
|
if( file.Data.Length != file.FileInfo.RawFileSize )
|
||||||
|
{
|
||||||
Debug.WriteLine( "Read data size does not match file size." );
|
Debug.WriteLine( "Read data size does not match file size." );
|
||||||
|
}
|
||||||
|
|
||||||
file.FileStream = new MemoryStream( file.Data, false );
|
file.FileStream = new MemoryStream( file.Data, false );
|
||||||
file.Reader = new BinaryReader( file.FileStream );
|
file.Reader = new BinaryReader( file.FileStream );
|
||||||
file.FileStream.Position = 0;
|
file.FileStream.Position = 0;
|
||||||
|
|
||||||
file.LoadFile();
|
file.LoadFile();
|
||||||
|
|
@ -117,7 +115,7 @@ namespace Penumbra.Util
|
||||||
|
|
||||||
private void ReadStandardFile( PenumbraFileResource resource, MemoryStream ms )
|
private void ReadStandardFile( PenumbraFileResource resource, MemoryStream ms )
|
||||||
{
|
{
|
||||||
var blocks = Reader.ReadStructures< DatStdFileBlockInfos >( (int)resource.FileInfo.BlockCount );
|
var blocks = Reader.ReadStructures< DatStdFileBlockInfos >( ( int )resource.FileInfo!.BlockCount );
|
||||||
|
|
||||||
foreach( var block in blocks )
|
foreach( var block in blocks )
|
||||||
{
|
{
|
||||||
|
|
@ -128,9 +126,10 @@ namespace Penumbra.Util
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void ReadModelFile( PenumbraFileResource resource, MemoryStream ms ) {
|
private unsafe void ReadModelFile( PenumbraFileResource resource, MemoryStream ms )
|
||||||
var mdlBlock = resource.FileInfo.ModelBlock;
|
{
|
||||||
long baseOffset = resource.FileInfo.Offset + resource.FileInfo.HeaderSize;
|
var mdlBlock = resource.FileInfo!.ModelBlock;
|
||||||
|
var baseOffset = resource.FileInfo.Offset + resource.FileInfo.HeaderSize;
|
||||||
|
|
||||||
// 1/1/3/3/3 stack/runtime/vertex/egeo/index
|
// 1/1/3/3/3 stack/runtime/vertex/egeo/index
|
||||||
// TODO: consider testing if this is more reliable than the Explorer method
|
// TODO: consider testing if this is more reliable than the Explorer method
|
||||||
|
|
@ -139,89 +138,111 @@ namespace Penumbra.Util
|
||||||
// but it seems to work fine in explorer...
|
// but it seems to work fine in explorer...
|
||||||
int totalBlocks = mdlBlock.StackBlockNum;
|
int totalBlocks = mdlBlock.StackBlockNum;
|
||||||
totalBlocks += mdlBlock.RuntimeBlockNum;
|
totalBlocks += mdlBlock.RuntimeBlockNum;
|
||||||
for( int i = 0; i < 3; i++ )
|
for( var i = 0; i < 3; i++ )
|
||||||
|
{
|
||||||
totalBlocks += mdlBlock.VertexBufferBlockNum[ i ];
|
totalBlocks += mdlBlock.VertexBufferBlockNum[ i ];
|
||||||
for( int i = 0; i < 3; i++ )
|
}
|
||||||
totalBlocks += mdlBlock.EdgeGeometryVertexBufferBlockNum[ i ];
|
|
||||||
for( int i = 0; i < 3; i++ )
|
|
||||||
totalBlocks += mdlBlock.IndexBufferBlockNum[ i ];
|
|
||||||
|
|
||||||
var compressedBlockSizes = Reader.ReadStructures< UInt16 >( totalBlocks );
|
for( var i = 0; i < 3; i++ )
|
||||||
int currentBlock = 0;
|
{
|
||||||
int stackSize;
|
totalBlocks += mdlBlock.EdgeGeometryVertexBufferBlockNum[ i ];
|
||||||
int runtimeSize;
|
}
|
||||||
int[] vertexDataOffsets = new int[3];
|
|
||||||
int[] indexDataOffsets = new int[3];
|
for( var i = 0; i < 3; i++ )
|
||||||
int[] vertexBufferSizes = new int[3];
|
{
|
||||||
int[] indexBufferSizes = new int[3];
|
totalBlocks += mdlBlock.IndexBufferBlockNum[ i ];
|
||||||
|
}
|
||||||
|
|
||||||
|
var compressedBlockSizes = Reader.ReadStructures< ushort >( totalBlocks );
|
||||||
|
var currentBlock = 0;
|
||||||
|
var vertexDataOffsets = new int[3];
|
||||||
|
var indexDataOffsets = new int[3];
|
||||||
|
var vertexBufferSizes = new int[3];
|
||||||
|
var indexBufferSizes = new int[3];
|
||||||
|
|
||||||
ms.Seek( 0x44, SeekOrigin.Begin );
|
ms.Seek( 0x44, SeekOrigin.Begin );
|
||||||
|
|
||||||
Reader.Seek( baseOffset + mdlBlock.StackOffset );
|
Reader.Seek( baseOffset + mdlBlock.StackOffset );
|
||||||
long stackStart = ms.Position;
|
var stackStart = ms.Position;
|
||||||
for( int i = 0; i < mdlBlock.StackBlockNum; i++ ) {
|
for( var i = 0; i < mdlBlock.StackBlockNum; i++ )
|
||||||
long lastPos = Reader.BaseStream.Position;
|
{
|
||||||
|
var lastPos = Reader.BaseStream.Position;
|
||||||
ReadFileBlock( ms );
|
ReadFileBlock( ms );
|
||||||
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
||||||
currentBlock++;
|
currentBlock++;
|
||||||
}
|
}
|
||||||
|
|
||||||
long stackEnd = ms.Position;
|
var stackEnd = ms.Position;
|
||||||
stackSize = (int) ( stackEnd - stackStart );
|
var stackSize = ( int )( stackEnd - stackStart );
|
||||||
|
|
||||||
Reader.Seek( baseOffset + mdlBlock.RuntimeOffset );
|
Reader.Seek( baseOffset + mdlBlock.RuntimeOffset );
|
||||||
long runtimeStart = ms.Position;
|
var runtimeStart = ms.Position;
|
||||||
for( int i = 0; i < mdlBlock.RuntimeBlockNum; i++ ) {
|
for( var i = 0; i < mdlBlock.RuntimeBlockNum; i++ )
|
||||||
long lastPos = Reader.BaseStream.Position;
|
{
|
||||||
|
var lastPos = Reader.BaseStream.Position;
|
||||||
ReadFileBlock( ms );
|
ReadFileBlock( ms );
|
||||||
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
||||||
currentBlock++;
|
currentBlock++;
|
||||||
}
|
}
|
||||||
|
|
||||||
long runtimeEnd = ms.Position;
|
var runtimeEnd = ms.Position;
|
||||||
runtimeSize = (int) ( runtimeEnd - runtimeStart );
|
var runtimeSize = ( int )( runtimeEnd - runtimeStart );
|
||||||
|
|
||||||
for( int i = 0; i < 3; i++ ) {
|
for( var i = 0; i < 3; i++ )
|
||||||
|
{
|
||||||
if( mdlBlock.VertexBufferBlockNum[ i ] != 0 ) {
|
if( mdlBlock.VertexBufferBlockNum[ i ] != 0 )
|
||||||
int currentVertexOffset = (int) ms.Position;
|
{
|
||||||
|
var currentVertexOffset = ( int )ms.Position;
|
||||||
if( i == 0 || currentVertexOffset != vertexDataOffsets[ i - 1 ] )
|
if( i == 0 || currentVertexOffset != vertexDataOffsets[ i - 1 ] )
|
||||||
|
{
|
||||||
vertexDataOffsets[ i ] = currentVertexOffset;
|
vertexDataOffsets[ i ] = currentVertexOffset;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
vertexDataOffsets[ i ] = 0;
|
vertexDataOffsets[ i ] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Reader.Seek( baseOffset + mdlBlock.VertexBufferOffset[ i ] );
|
Reader.Seek( baseOffset + mdlBlock.VertexBufferOffset[ i ] );
|
||||||
|
|
||||||
for( int j = 0; j < mdlBlock.VertexBufferBlockNum[ i ]; j++ ) {
|
for( var j = 0; j < mdlBlock.VertexBufferBlockNum[ i ]; j++ )
|
||||||
long lastPos = Reader.BaseStream.Position;
|
{
|
||||||
vertexBufferSizes[ i ] += (int) ReadFileBlock( ms );
|
var lastPos = Reader.BaseStream.Position;
|
||||||
|
vertexBufferSizes[ i ] += ( int )ReadFileBlock( ms );
|
||||||
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
||||||
currentBlock++;
|
currentBlock++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( mdlBlock.EdgeGeometryVertexBufferBlockNum[ i ] != 0 ) {
|
if( mdlBlock.EdgeGeometryVertexBufferBlockNum[ i ] != 0 )
|
||||||
for( int j = 0; j < mdlBlock.EdgeGeometryVertexBufferBlockNum[ i ]; j++ ) {
|
{
|
||||||
long lastPos = Reader.BaseStream.Position;
|
for( var j = 0; j < mdlBlock.EdgeGeometryVertexBufferBlockNum[ i ]; j++ )
|
||||||
|
{
|
||||||
|
var lastPos = Reader.BaseStream.Position;
|
||||||
ReadFileBlock( ms );
|
ReadFileBlock( ms );
|
||||||
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
||||||
currentBlock++;
|
currentBlock++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( mdlBlock.IndexBufferBlockNum[ i ] != 0 ) {
|
if( mdlBlock.IndexBufferBlockNum[ i ] != 0 )
|
||||||
int currentIndexOffset = (int) ms.Position;
|
{
|
||||||
|
var currentIndexOffset = ( int )ms.Position;
|
||||||
if( i == 0 || currentIndexOffset != indexDataOffsets[ i - 1 ] )
|
if( i == 0 || currentIndexOffset != indexDataOffsets[ i - 1 ] )
|
||||||
|
{
|
||||||
indexDataOffsets[ i ] = currentIndexOffset;
|
indexDataOffsets[ i ] = currentIndexOffset;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
indexDataOffsets[ i ] = 0;
|
indexDataOffsets[ i ] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// i guess this is only needed in the vertex area, for i = 0
|
// i guess this is only needed in the vertex area, for i = 0
|
||||||
// Reader.Seek( baseOffset + mdlBlock.IndexBufferOffset[ i ] );
|
// Reader.Seek( baseOffset + mdlBlock.IndexBufferOffset[ i ] );
|
||||||
|
|
||||||
for( int j = 0; j < mdlBlock.IndexBufferBlockNum[ i ]; j++ ) {
|
for( var j = 0; j < mdlBlock.IndexBufferBlockNum[ i ]; j++ )
|
||||||
long lastPos = Reader.BaseStream.Position;
|
{
|
||||||
indexBufferSizes[ i ] += (int) ReadFileBlock( ms );
|
var lastPos = Reader.BaseStream.Position;
|
||||||
|
indexBufferSizes[ i ] += ( int )ReadFileBlock( ms );
|
||||||
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
Reader.Seek( lastPos + compressedBlockSizes[ currentBlock ] );
|
||||||
currentBlock++;
|
currentBlock++;
|
||||||
}
|
}
|
||||||
|
|
@ -234,33 +255,45 @@ namespace Penumbra.Util
|
||||||
ms.Write( BitConverter.GetBytes( runtimeSize ) );
|
ms.Write( BitConverter.GetBytes( runtimeSize ) );
|
||||||
ms.Write( BitConverter.GetBytes( mdlBlock.VertexDeclarationNum ) );
|
ms.Write( BitConverter.GetBytes( mdlBlock.VertexDeclarationNum ) );
|
||||||
ms.Write( BitConverter.GetBytes( mdlBlock.MaterialNum ) );
|
ms.Write( BitConverter.GetBytes( mdlBlock.MaterialNum ) );
|
||||||
for( int i = 0; i < 3; i++ )
|
for( var i = 0; i < 3; i++ )
|
||||||
|
{
|
||||||
ms.Write( BitConverter.GetBytes( vertexDataOffsets[ i ] ) );
|
ms.Write( BitConverter.GetBytes( vertexDataOffsets[ i ] ) );
|
||||||
for( int i = 0; i < 3; i++ )
|
}
|
||||||
|
|
||||||
|
for( var i = 0; i < 3; i++ )
|
||||||
|
{
|
||||||
ms.Write( BitConverter.GetBytes( indexDataOffsets[ i ] ) );
|
ms.Write( BitConverter.GetBytes( indexDataOffsets[ i ] ) );
|
||||||
for( int i = 0; i < 3; i++ )
|
}
|
||||||
|
|
||||||
|
for( var i = 0; i < 3; i++ )
|
||||||
|
{
|
||||||
ms.Write( BitConverter.GetBytes( vertexBufferSizes[ i ] ) );
|
ms.Write( BitConverter.GetBytes( vertexBufferSizes[ i ] ) );
|
||||||
for( int i = 0; i < 3; i++ )
|
}
|
||||||
|
|
||||||
|
for( var i = 0; i < 3; i++ )
|
||||||
|
{
|
||||||
ms.Write( BitConverter.GetBytes( indexBufferSizes[ i ] ) );
|
ms.Write( BitConverter.GetBytes( indexBufferSizes[ i ] ) );
|
||||||
ms.Write( new [] {mdlBlock.NumLods} );
|
}
|
||||||
|
|
||||||
|
ms.Write( new[] { mdlBlock.NumLods } );
|
||||||
ms.Write( BitConverter.GetBytes( mdlBlock.IndexBufferStreamingEnabled ) );
|
ms.Write( BitConverter.GetBytes( mdlBlock.IndexBufferStreamingEnabled ) );
|
||||||
ms.Write( BitConverter.GetBytes( mdlBlock.EdgeGeometryEnabled ) );
|
ms.Write( BitConverter.GetBytes( mdlBlock.EdgeGeometryEnabled ) );
|
||||||
ms.Write( new byte[] {0} );
|
ms.Write( new byte[] { 0 } );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadTextureFile( PenumbraFileResource resource, MemoryStream ms )
|
private void ReadTextureFile( PenumbraFileResource resource, MemoryStream ms )
|
||||||
{
|
{
|
||||||
var blocks = Reader.ReadStructures< LodBlock >( (int)resource.FileInfo.BlockCount );
|
var blocks = Reader.ReadStructures< LodBlock >( ( int )resource.FileInfo!.BlockCount );
|
||||||
|
|
||||||
// if there is a mipmap header, the comp_offset
|
// if there is a mipmap header, the comp_offset
|
||||||
// will not be 0
|
// will not be 0
|
||||||
uint mipMapSize = blocks[ 0 ].CompressedOffset;
|
var mipMapSize = blocks[ 0 ].CompressedOffset;
|
||||||
if( mipMapSize != 0 )
|
if( mipMapSize != 0 )
|
||||||
{
|
{
|
||||||
long originalPos = BaseStream.Position;
|
var originalPos = BaseStream.Position;
|
||||||
|
|
||||||
BaseStream.Position = resource.FileInfo.Offset + resource.FileInfo.HeaderSize;
|
BaseStream.Position = resource.FileInfo.Offset + resource.FileInfo.HeaderSize;
|
||||||
ms.Write( Reader.ReadBytes( (int)mipMapSize ) );
|
ms.Write( Reader.ReadBytes( ( int )mipMapSize ) );
|
||||||
|
|
||||||
BaseStream.Position = originalPos;
|
BaseStream.Position = originalPos;
|
||||||
}
|
}
|
||||||
|
|
@ -269,12 +302,12 @@ namespace Penumbra.Util
|
||||||
for( byte i = 0; i < blocks.Count; i++ )
|
for( byte i = 0; i < blocks.Count; i++ )
|
||||||
{
|
{
|
||||||
// start from comp_offset
|
// start from comp_offset
|
||||||
long runningBlockTotal = blocks[ i ].CompressedOffset + resource.FileInfo.Offset + resource.FileInfo.HeaderSize;
|
var runningBlockTotal = blocks[ i ].CompressedOffset + resource.FileInfo.Offset + resource.FileInfo.HeaderSize;
|
||||||
ReadFileBlock( runningBlockTotal, ms, true );
|
ReadFileBlock( runningBlockTotal, ms, true );
|
||||||
|
|
||||||
for( int j = 1; j < blocks[ i ].BlockCount; j++ )
|
for( var j = 1; j < blocks[ i ].BlockCount; j++ )
|
||||||
{
|
{
|
||||||
runningBlockTotal += (UInt32)Reader.ReadInt16();
|
runningBlockTotal += ( uint )Reader.ReadInt16();
|
||||||
ReadFileBlock( runningBlockTotal, ms, true );
|
ReadFileBlock( runningBlockTotal, ms, true );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,25 +316,24 @@ namespace Penumbra.Util
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected uint ReadFileBlock( MemoryStream dest, bool resetPosition = false ) {
|
protected uint ReadFileBlock( MemoryStream dest, bool resetPosition = false )
|
||||||
return ReadFileBlock( Reader.BaseStream.Position, dest, resetPosition );
|
=> ReadFileBlock( Reader.BaseStream.Position, dest, resetPosition );
|
||||||
}
|
|
||||||
|
|
||||||
protected uint ReadFileBlock( long offset, MemoryStream dest, bool resetPosition = false )
|
protected uint ReadFileBlock( long offset, MemoryStream dest, bool resetPosition = false )
|
||||||
{
|
{
|
||||||
long originalPosition = BaseStream.Position;
|
var originalPosition = BaseStream.Position;
|
||||||
BaseStream.Position = offset;
|
BaseStream.Position = offset;
|
||||||
|
|
||||||
var blockHeader = Reader.ReadStructure< DatBlockHeader >();
|
var blockHeader = Reader.ReadStructure< DatBlockHeader >();
|
||||||
|
|
||||||
// uncompressed block
|
// uncompressed block
|
||||||
if( blockHeader.CompressedSize == 32000 )
|
if( blockHeader.CompressedSize == 32000 )
|
||||||
{
|
{
|
||||||
dest.Write( Reader.ReadBytes( (int)blockHeader.UncompressedSize ) );
|
dest.Write( Reader.ReadBytes( ( int )blockHeader.UncompressedSize ) );
|
||||||
return blockHeader.UncompressedSize;
|
return blockHeader.UncompressedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = Reader.ReadBytes( (int)blockHeader.UncompressedSize );
|
var data = Reader.ReadBytes( ( int )blockHeader.UncompressedSize );
|
||||||
|
|
||||||
using( var compressedStream = new MemoryStream( data ) )
|
using( var compressedStream = new MemoryStream( data ) )
|
||||||
{
|
{
|
||||||
|
|
@ -311,7 +343,9 @@ namespace Penumbra.Util
|
||||||
}
|
}
|
||||||
|
|
||||||
if( resetPosition )
|
if( resetPosition )
|
||||||
|
{
|
||||||
BaseStream.Position = originalPosition;
|
BaseStream.Position = originalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
return blockHeader.UncompressedSize;
|
return blockHeader.UncompressedSize;
|
||||||
}
|
}
|
||||||
|
|
@ -323,10 +357,10 @@ namespace Penumbra.Util
|
||||||
|
|
||||||
public class PenumbraFileInfo
|
public class PenumbraFileInfo
|
||||||
{
|
{
|
||||||
public UInt32 HeaderSize;
|
public uint HeaderSize;
|
||||||
public FileType Type;
|
public FileType Type;
|
||||||
public UInt32 RawFileSize;
|
public uint RawFileSize;
|
||||||
public UInt32 BlockCount;
|
public uint BlockCount;
|
||||||
|
|
||||||
public long Offset { get; internal set; }
|
public long Offset { get; internal set; }
|
||||||
|
|
||||||
|
|
@ -336,20 +370,20 @@ namespace Penumbra.Util
|
||||||
public class PenumbraFileResource
|
public class PenumbraFileResource
|
||||||
{
|
{
|
||||||
public PenumbraFileResource()
|
public PenumbraFileResource()
|
||||||
{
|
{ }
|
||||||
}
|
|
||||||
|
|
||||||
public PenumbraFileInfo FileInfo { get; internal set; }
|
public PenumbraFileInfo? FileInfo { get; internal set; }
|
||||||
|
|
||||||
public byte[] Data { get; internal set; }
|
public byte[] Data { get; internal set; } = new byte[0];
|
||||||
|
|
||||||
public Span< byte > DataSpan => Data.AsSpan();
|
public Span< byte > DataSpan
|
||||||
|
=> Data.AsSpan();
|
||||||
|
|
||||||
public MemoryStream FileStream { get; internal set; }
|
public MemoryStream? FileStream { get; internal set; }
|
||||||
|
|
||||||
public BinaryReader Reader { get; internal set; }
|
public BinaryReader? Reader { get; internal set; }
|
||||||
|
|
||||||
public ParsedFilePath FilePath { get; internal set; }
|
public ParsedFilePath? FilePath { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called once the files are read out from the dats. Used to further parse the file into usable data structures.
|
/// Called once the files are read out from the dats. Used to further parse the file into usable data structures.
|
||||||
|
|
@ -380,22 +414,22 @@ namespace Penumbra.Util
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Sequential )]
|
[StructLayout( LayoutKind.Sequential )]
|
||||||
struct DatBlockHeader
|
private struct DatBlockHeader
|
||||||
{
|
{
|
||||||
public UInt32 Size;
|
public uint Size;
|
||||||
public UInt32 unknown1;
|
public uint unknown1;
|
||||||
public UInt32 CompressedSize;
|
public uint CompressedSize;
|
||||||
public UInt32 UncompressedSize;
|
public uint UncompressedSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Sequential )]
|
[StructLayout( LayoutKind.Sequential )]
|
||||||
struct LodBlock
|
private struct LodBlock
|
||||||
{
|
{
|
||||||
public UInt32 CompressedOffset;
|
public uint CompressedOffset;
|
||||||
public UInt32 CompressedSize;
|
public uint CompressedSize;
|
||||||
public UInt32 DecompressedSize;
|
public uint DecompressedSize;
|
||||||
public UInt32 BlockOffset;
|
public uint BlockOffset;
|
||||||
public UInt32 BlockCount;
|
public uint BlockCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue