move desc to own tab, invert load order setting, normalise line endings

This commit is contained in:
Adam 2021-01-16 00:23:53 +11:00
parent fa9e4d7dcc
commit 25ac5d1999
14 changed files with 476 additions and 414 deletions

View file

@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Linq; using System.Linq;
using EmbedIO; using EmbedIO;
using EmbedIO.Routing; using EmbedIO.Routing;
@ -25,7 +24,7 @@ namespace Penumbra.API
x.FolderName, x.FolderName,
x.Mod.Meta, x.Mod.Meta,
BasePath = x.Mod.ModBasePath.FullName, BasePath = x.Mod.ModBasePath.FullName,
Files = x.Mod.ModFiles.Select( x => x.FullName ) Files = x.Mod.ModFiles.Select( fi => fi.FullName )
} ); } );
} }

View file

@ -12,16 +12,18 @@ namespace Penumbra
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;
public bool ShowAdvanced { get; set; } = false; public bool ShowAdvanced { get; set; }
public bool DisableFileSystemNotifications { get; set; } = false; public bool DisableFileSystemNotifications { get; set; }
public bool EnableHttpApi { get; set; } = false; public bool EnableHttpApi { get; set; }
public string CurrentCollection { get; set; } = @"D:/ffxiv/fs_mods/"; public string CurrentCollection { get; set; } = @"D:/ffxiv/fs_mods/";
public List< string > ModCollections { get; set; } = new(); public List< string > ModCollections { get; set; } = new();
public bool InvertModListOrder { get; set; }
// the below exist just to make saving less cumbersome // the below exist just to make saving less cumbersome
[NonSerialized] [NonSerialized]

View file

@ -1,9 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
@ -49,15 +46,15 @@ namespace Penumbra
public class HiddenForm : Form public class HiddenForm : Form
{ {
private readonly CommonDialog form; private readonly CommonDialog _form;
private readonly IWin32Window owner; private readonly IWin32Window _owner;
private readonly TaskCompletionSource< DialogResult > taskSource; private readonly TaskCompletionSource< DialogResult > _taskSource;
public HiddenForm( CommonDialog form, IWin32Window owner, TaskCompletionSource< DialogResult > taskSource ) public HiddenForm( CommonDialog form, IWin32Window owner, TaskCompletionSource< DialogResult > taskSource )
{ {
this.form = form; this._form = form;
this.owner = owner; this._owner = owner;
this.taskSource = taskSource; this._taskSource = taskSource;
Opacity = 0; Opacity = 0;
FormBorderStyle = FormBorderStyle.None; FormBorderStyle = FormBorderStyle.None;
@ -72,12 +69,12 @@ namespace Penumbra
Hide(); Hide();
try try
{ {
var result = form.ShowDialog( owner ); var result = _form.ShowDialog( _owner );
taskSource.SetResult( result ); _taskSource.SetResult( result );
} }
catch( Exception e ) catch( Exception e )
{ {
taskSource.SetException( e ); _taskSource.SetException( e );
} }
Close(); Close();

View file

@ -7,8 +7,6 @@ namespace Penumbra.Game
{ {
public class GameUtils public class GameUtils
{ {
private readonly DalamudPluginInterface _pluginInterface;
[Function( CallingConventions.Microsoft )] [Function( CallingConventions.Microsoft )]
public unsafe delegate void* LoadPlayerResourcesPrototype( IntPtr pResourceManager ); public unsafe delegate void* LoadPlayerResourcesPrototype( IntPtr pResourceManager );
@ -25,9 +23,7 @@ namespace Penumbra.Game
public GameUtils( DalamudPluginInterface pluginInterface ) public GameUtils( DalamudPluginInterface pluginInterface )
{ {
_pluginInterface = pluginInterface; var scanner = pluginInterface.TargetModuleScanner;
var scanner = _pluginInterface.TargetModuleScanner;
var loadPlayerResourcesAddress = var loadPlayerResourcesAddress =
scanner.ScanText( scanner.ScanText(

View file

@ -17,10 +17,10 @@ namespace Penumbra.Importer
private readonly DirectoryInfo _outDirectory; private readonly DirectoryInfo _outDirectory;
private const string TempFileName = "textools-import"; private const string TempFileName = "textools-import";
private readonly string _resolvedTempFilePath = null; private readonly string _resolvedTempFilePath;
public ImporterState State { get; private set; } public ImporterState State { get; private set; }
public long TotalProgress { get; private set; } public long TotalProgress { get; private set; }
public long CurrentProgress { get; private set; } public long CurrentProgress { get; private set; }
@ -49,7 +49,7 @@ namespace Penumbra.Importer
public void ImportModPack( FileInfo modPackFile ) public void ImportModPack( FileInfo modPackFile )
{ {
CurrentModPack = modPackFile.Name; CurrentModPack = modPackFile.Name;
switch( modPackFile.Extension ) switch( modPackFile.Extension )
{ {
case ".ttmp": case ".ttmp":
@ -74,7 +74,7 @@ namespace Penumbra.Importer
private SqPackStream GetMagicSqPackDeleterStream( ZipFile file, string entryName ) private SqPackStream GetMagicSqPackDeleterStream( ZipFile file, string entryName )
{ {
State = ImporterState.WritingPackToDisk; State = ImporterState.WritingPackToDisk;
// write shitty zip garbage to disk // write shitty zip garbage to disk
var entry = file.GetEntry( entryName ); var entry = file.GetEntry( entryName );
using var s = file.GetInputStream( entry ); using var s = file.GetInputStream( entry );
@ -215,32 +215,32 @@ namespace Penumbra.Importer
from modGroup in modPackPage.ModGroups from modGroup in modPackPage.ModGroups
from option in modGroup.OptionList from option in modGroup.OptionList
select option ) select option )
{ {
var OptionFolder = new DirectoryInfo(Path.Combine(newModFolder.FullName, option.Name)); var optionFolder = new DirectoryInfo( Path.Combine( newModFolder.FullName, option.Name ) );
ExtractSimpleModList(OptionFolder, option.ModsJsons, modData ); ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
AddMeta(OptionFolder, newModFolder, modMeta, option.Name); AddMeta( optionFolder, newModFolder, modMeta, option.Name );
} }
File.WriteAllText( File.WriteAllText(
Path.Combine( newModFolder.FullName, "meta.json" ), Path.Combine( newModFolder.FullName, "meta.json" ),
JsonConvert.SerializeObject( modMeta, Formatting.Indented ) JsonConvert.SerializeObject( modMeta, Formatting.Indented )
); );
} }
void AddMeta(DirectoryInfo optionFolder, DirectoryInfo baseFolder, ModMeta meta, string optionName) void AddMeta( DirectoryInfo optionFolder, DirectoryInfo baseFolder, ModMeta meta, string optionName )
{ {
var optionFolderLength = optionFolder.FullName.Length; var optionFolderLength = optionFolder.FullName.Length;
var baseFolderLength = baseFolder.FullName.Length; var baseFolderLength = baseFolder.FullName.Length;
foreach( var dir in optionFolder.EnumerateDirectories() ) foreach( var dir in optionFolder.EnumerateDirectories() )
{ {
foreach( var file in dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) foreach( var file in dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{ {
meta.Groups.AddFileToOtherGroups(optionName meta.Groups.AddFileToOtherGroups( optionName
, file.FullName.Substring(baseFolderLength).TrimStart('\\') , file.FullName.Substring( baseFolderLength ).TrimStart( '\\' )
, file.FullName.Substring(optionFolderLength).TrimStart('\\').Replace('\\', '/')); , file.FullName.Substring( optionFolderLength ).TrimStart( '\\' ).Replace( '\\', '/' ) );
} }
} }
} }
private void ImportMetaModPack( FileInfo file ) private void ImportMetaModPack( FileInfo file )
{ {
@ -250,12 +250,12 @@ namespace Penumbra.Importer
private void ExtractSimpleModList( DirectoryInfo outDirectory, IEnumerable< SimpleMod > mods, SqPackStream dataStream ) private void ExtractSimpleModList( DirectoryInfo outDirectory, IEnumerable< SimpleMod > mods, SqPackStream dataStream )
{ {
State = ImporterState.ExtractingModFiles; State = ImporterState.ExtractingModFiles;
// haha allocation go brr // haha allocation go brr
var wtf = mods.ToList(); var wtf = mods.ToList();
TotalProgress = wtf.LongCount(); TotalProgress = wtf.LongCount();
// Extract each SimpleMod into the new mod folder // Extract each SimpleMod into the new mod folder
foreach( var simpleMod in wtf ) foreach( var simpleMod in wtf )
{ {
@ -264,7 +264,7 @@ namespace Penumbra.Importer
// do we increment here too???? can this even happen????? // do we increment here too???? can this even happen?????
continue; continue;
} }
ExtractMod( outDirectory, simpleMod, dataStream ); ExtractMod( outDirectory, simpleMod, dataStream );
CurrentProgress++; CurrentProgress++;
} }
@ -296,12 +296,12 @@ namespace Penumbra.Importer
{ {
// Model file header LOD num // Model file header LOD num
mdl[ 64 ] = 1; mdl[ 64 ] = 1;
// Model header LOD num // Model header LOD num
var stackSize = BitConverter.ToUInt32( mdl, 4 ); var stackSize = BitConverter.ToUInt32( mdl, 4 );
var runtimeBegin = stackSize + 0x44; var runtimeBegin = stackSize + 0x44;
var stringsLengthOffset = runtimeBegin + 4; var stringsLengthOffset = runtimeBegin + 4;
var stringsLength = BitConverter.ToUInt32( mdl, (int) stringsLengthOffset ); var stringsLength = BitConverter.ToUInt32( mdl, ( int )stringsLengthOffset );
var modelHeaderStart = stringsLengthOffset + stringsLength + 4; var modelHeaderStart = stringsLengthOffset + stringsLength + 4;
var modelHeaderLodOffset = 22; var modelHeaderLodOffset = 22;
mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1; mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1;

View file

@ -1,157 +1,189 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Linq; using System.Linq;
namespace Penumbra.Models namespace Penumbra.Models
{ {
[Serializable] [Serializable]
public class GroupInformation : ISerializable public class GroupInformation : ISerializable
{ {
// This class is just used as a temp class while (de)-serializing.
// This class is just used as a temp class while (de)-serializing. // It converts the flags into lists and back.
// It converts the flags into lists and back. [Serializable]
[Serializable] private class GroupDescription : ISerializable
private class GroupDescription : ISerializable {
{ public GroupDescription( GroupInformation info, (string, uint, uint, ulong) vars )
public GroupDescription(GroupInformation info, (string, uint, uint, ulong) vars) {
{ GamePath = vars.Item1;
GamePath = vars.Item1;
static List< string > AddGroupTypes( ulong flags, ulong bound, List< string > groupType )
static List<string> AddGroupTypes(ulong flags, ulong bound, List<string> groupType) {
{ List< string > ret = null;
List<string> ret = null; if( flags != uint.MaxValue )
if (flags != uint.MaxValue) {
{ ret = new();
ret = new(); for( var i = 0; i < groupType.Count; ++i )
for (var i = 0; i < groupType.Count; ++i) {
{ var flag = 1u << i;
var flag = 1u << i; if( ( flags & flag ) == flag )
if ((flags & flag) == flag) ret.Add( groupType[ i ] );
ret.Add(groupType[i]); }
} }
}
return ret; return ret;
} }
// Tops and Bottoms are uint. // Tops and Bottoms are uint.
TopTypes = AddGroupTypes(vars.Item2, uint.MaxValue, info.TopTypes); TopTypes = AddGroupTypes( vars.Item2, uint.MaxValue, info.TopTypes );
BottomTypes = AddGroupTypes(vars.Item3, uint.MaxValue, info.BottomTypes); BottomTypes = AddGroupTypes( vars.Item3, uint.MaxValue, info.BottomTypes );
// Exclusions are the other way around and ulong. // Exclusions are the other way around and ulong.
GroupExclusions = AddGroupTypes(~vars.Item4, 0, info.OtherGroups); GroupExclusions = AddGroupTypes( ~vars.Item4, 0, info.OtherGroups );
} }
public (string, uint, uint, ulong) ToTuple(GroupInformation info) public (string, uint, uint, ulong) ToTuple( GroupInformation info )
{ {
static ulong TypesToFlags(List<string> ownTypes, List<string> globalTypes) static ulong TypesToFlags( List< string > ownTypes, List< string > globalTypes )
{ {
if (ownTypes == null) if( ownTypes == null )
return ulong.MaxValue; return ulong.MaxValue;
ulong flags = 0; ulong flags = 0;
foreach (var x in ownTypes) foreach( var x in ownTypes )
{ {
var index = globalTypes.IndexOf(x); var index = globalTypes.IndexOf( x );
if (index >= 0) if( index >= 0 )
flags |= (1u << index); flags |= ( 1u << index );
} }
return flags;
} return flags;
var tops = (uint) TypesToFlags(TopTypes, info.TopTypes); }
var bottoms = (uint) TypesToFlags(BottomTypes, info.BottomTypes);
// Exclusions are the other way around. var tops = ( uint )TypesToFlags( TopTypes, info.TopTypes );
var groupEx = (GroupExclusions == null) ? ulong.MaxValue : ~TypesToFlags(GroupExclusions, info.OtherGroups); var bottoms = ( uint )TypesToFlags( BottomTypes, info.BottomTypes );
return (GamePath, tops, bottoms, groupEx); // Exclusions are the other way around.
} var groupEx = ( GroupExclusions == null ) ? ulong.MaxValue : ~TypesToFlags( GroupExclusions, info.OtherGroups );
return ( GamePath, tops, bottoms, groupEx );
public string GamePath { get; set; } }
public List<string> TopTypes { get; set; } = null;
public List<string> BottomTypes { get; set; } = null; public string GamePath { get; set; }
public List<string> GroupExclusions { get; set; } = null; public List< string > TopTypes { get; set; }
public List< string > BottomTypes { get; set; }
// Customize (De)-Serialization to ignore nulls. public List< string > GroupExclusions { get; set; }
public GroupDescription(SerializationInfo info, StreamingContext context)
{ // Customize (De)-Serialization to ignore nulls.
List<string> readListOrNull(string name) public GroupDescription( SerializationInfo info, StreamingContext context )
{ {
try List< string > readListOrNull( string name )
{ {
var ret = (List<string>) info.GetValue(name, typeof(List<string>)); try
if (ret == null || ret.Count == 0) {
return null; var ret = ( List< string > )info.GetValue( name, typeof( List< string > ) );
return ret; if( ret == null || ret.Count == 0 )
} return null;
catch (Exception) { return null; } return ret;
} }
GamePath = info.GetString("GamePath"); catch( Exception )
TopTypes = readListOrNull("TopTypes"); {
BottomTypes = readListOrNull("BottomTypes"); return null;
GroupExclusions = readListOrNull("GroupExclusions"); }
} }
public virtual void GetObjectData(SerializationInfo info, StreamingContext context) GamePath = info.GetString( "GamePath" );
{ TopTypes = readListOrNull( "TopTypes" );
info.AddValue( "GamePath", GamePath ); BottomTypes = readListOrNull( "BottomTypes" );
if (TopTypes != null) info.AddValue("TopTypes", TopTypes); GroupExclusions = readListOrNull( "GroupExclusions" );
if (BottomTypes != null) info.AddValue("BottomTypes", BottomTypes); }
if (GroupExclusions != null) info.AddValue("GroupExclusions", GroupExclusions);
} public virtual void GetObjectData( SerializationInfo info, StreamingContext context )
} {
info.AddValue( "GamePath", GamePath );
public List<string> TopTypes { get; set; } = new(); if( TopTypes != null ) info.AddValue( "TopTypes", TopTypes );
public List<string> BottomTypes { get; set; } = new(); if( BottomTypes != null ) info.AddValue( "BottomTypes", BottomTypes );
public List<string> OtherGroups { get; set; } = new(); if( GroupExclusions != null ) info.AddValue( "GroupExclusions", GroupExclusions );
}
public void AddFileToOtherGroups(string optionName, string fileName, string gamePath) }
{
var idx = OtherGroups.IndexOf(optionName); public List< string > TopTypes { get; set; } = new();
if (idx < 0) public List< string > BottomTypes { get; set; } = new();
{ public List< string > OtherGroups { get; set; } = new();
idx = OtherGroups.Count;
OtherGroups.Add(optionName); public void AddFileToOtherGroups( string optionName, string fileName, string gamePath )
} {
var idx = OtherGroups.IndexOf( optionName );
(string, uint, uint, ulong) tuple = (gamePath, uint.MaxValue, uint.MaxValue, (1ul << idx)); if( idx < 0 )
{
if (!FileToGameAndGroup.TryGetValue(fileName, out var tuple2)) idx = OtherGroups.Count;
{ OtherGroups.Add( optionName );
FileToGameAndGroup.Add(fileName, tuple); }
}
else (string, uint, uint, ulong) tuple = ( gamePath, uint.MaxValue, uint.MaxValue, ( 1ul << idx ) );
{
tuple2.Item1 = tuple.Item1; if( !FileToGameAndGroup.TryGetValue( fileName, out var tuple2 ) )
tuple2.Item4 |= tuple.Item4; {
} FileToGameAndGroup.Add( fileName, tuple );
} }
else
public Dictionary<string, (string, uint, uint, ulong)> FileToGameAndGroup { get; set; } = new(); {
tuple2.Item1 = tuple.Item1;
public GroupInformation(){ } tuple2.Item4 |= tuple.Item4;
}
public GroupInformation(SerializationInfo info, StreamingContext context) }
{
try { TopTypes = (List<string>) info.GetValue( "TopTypes", TopTypes.GetType() ); } catch(Exception){ } public Dictionary< string, (string, uint, uint, ulong) > FileToGameAndGroup { get; set; } = new();
try { BottomTypes = (List<string>) info.GetValue( "BottomTypes", BottomTypes.GetType() ); } catch(Exception){ }
try { OtherGroups = (List<string>) info.GetValue( "Groups", OtherGroups.GetType() ); } catch(Exception){ } public GroupInformation()
try {
{ }
Dictionary<string, GroupDescription> dict = new();
dict = (Dictionary<string, GroupDescription>) info.GetValue( "FileToGameAndGroups", dict.GetType()); public GroupInformation( SerializationInfo info, StreamingContext context )
foreach (var pair in dict) {
FileToGameAndGroup.Add(pair.Key, pair.Value.ToTuple(this)); try
} catch (Exception){ } {
} TopTypes = ( List< string > )info.GetValue( "TopTypes", TopTypes.GetType() );
}
public virtual void GetObjectData(SerializationInfo info, StreamingContext context) catch( Exception )
{ {
if ((TopTypes?.Count ?? 0) > 0) info.AddValue("TopTypes", TopTypes); }
if ((BottomTypes?.Count ?? 0) > 0) info.AddValue("BottomTypes", BottomTypes);
if ((OtherGroups?.Count ?? 0) > 0) info.AddValue("Groups", OtherGroups); try
if ((FileToGameAndGroup?.Count ?? 0) > 0) {
{ BottomTypes = ( List< string > )info.GetValue( "BottomTypes", BottomTypes.GetType() );
var dict = FileToGameAndGroup.ToDictionary( pair => pair.Key, pair => new GroupDescription( this, pair.Value ) ); }
info.AddValue("FileToGameAndGroups", dict); catch( Exception )
} {
} }
}
try
{
OtherGroups = ( List< string > )info.GetValue( "Groups", OtherGroups.GetType() );
}
catch( Exception )
{
}
try
{
Dictionary< string, GroupDescription > dict = new();
dict = ( Dictionary< string, GroupDescription > )info.GetValue( "FileToGameAndGroups", dict.GetType() );
foreach( var pair in dict )
FileToGameAndGroup.Add( pair.Key, pair.Value.ToTuple( this ) );
}
catch( Exception )
{
}
}
public virtual void GetObjectData( SerializationInfo info, StreamingContext context )
{
if( ( TopTypes?.Count ?? 0 ) > 0 ) info.AddValue( "TopTypes", TopTypes );
if( ( BottomTypes?.Count ?? 0 ) > 0 ) info.AddValue( "BottomTypes", BottomTypes );
if( ( OtherGroups?.Count ?? 0 ) > 0 ) info.AddValue( "Groups", OtherGroups );
if( ( FileToGameAndGroup?.Count ?? 0 ) > 0 )
{
var dict = FileToGameAndGroup.ToDictionary( pair => pair.Key, pair => new GroupDescription( this, pair.Value ) );
info.AddValue( "FileToGameAndGroups", dict );
}
}
}
} }

View file

@ -4,18 +4,19 @@ namespace Penumbra.Models
{ {
public class ModMeta public class ModMeta
{ {
public uint FileVersion { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Author { get; set; } public string Author { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Version { get; set; } public string Version { get; set; }
public string Website { get; set; } public string Website { get; set; }
public List<string> ChangedItems { get; set; } = new(); public List< string > ChangedItems { get; set; } = new();
public Dictionary< string, string > FileSwaps { get; } = new(); public Dictionary< string, string > FileSwaps { get; } = new();
public GroupInformation Groups { get; set; } = new(); public GroupInformation Groups { get; set; } = new();
} }
} }

View file

@ -21,7 +21,7 @@ namespace Penumbra.Mods
_basePath = basePath; _basePath = basePath;
} }
public void Load() public void Load( bool invertOrder = false )
{ {
// find the collection json // find the collection json
var collectionPath = Path.Combine( _basePath.FullName, "collection.json" ); var collectionPath = Path.Combine( _basePath.FullName, "collection.json" );
@ -94,7 +94,7 @@ namespace Penumbra.Mods
} }
// reorder the resourcemods list so we can just directly iterate // reorder the resourcemods list so we can just directly iterate
EnabledMods = GetOrderedAndEnabledModList().ToArray(); EnabledMods = GetOrderedAndEnabledModList( invertOrder ).ToArray();
// write the collection metadata back to disk // write the collection metadata back to disk
Save(); Save();
@ -181,22 +181,28 @@ namespace Penumbra.Mods
return AddModSettings( mod ); return AddModSettings( mod );
} }
public IEnumerable<ModInfo> GetOrderedAndEnabledModSettings() public IEnumerable<ModInfo> GetOrderedAndEnabledModSettings( bool invertOrder = false )
{ {
return ModSettings var query = ModSettings
.Where( x => x.Enabled ) .Where( x => x.Enabled );
.OrderBy( x => x.Priority );
} if( !invertOrder )
{
public IEnumerable<ResourceMod> GetOrderedAndEnabledModList() return query.OrderBy( x => x.Priority );
}
return query.OrderByDescending( x => x.Priority );
}
public IEnumerable<ResourceMod> GetOrderedAndEnabledModList( bool invertOrder = false )
{ {
return GetOrderedAndEnabledModSettings() return GetOrderedAndEnabledModSettings( invertOrder )
.Select( x => x.Mod ); .Select( x => x.Mod );
} }
public IEnumerable<(ResourceMod, ModInfo)> GetOrderedAndEnabledModListWithSettings() public IEnumerable<(ResourceMod, ModInfo)> GetOrderedAndEnabledModListWithSettings( bool invertOrder = false )
{ {
return GetOrderedAndEnabledModSettings() return GetOrderedAndEnabledModSettings( invertOrder )
.Select( x => (x.Mod, x) ); .Select( x => (x.Mod, x) );
} }
} }

View file

@ -1,14 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Penumbra.Models; using Penumbra.Models;
namespace Penumbra.Mods namespace Penumbra.Mods
{ {
public class ModManager : IDisposable public class ModManager : IDisposable
{ {
private readonly Plugin _plugin; private readonly Plugin _plugin;
public readonly Dictionary< string, FileInfo > ResolvedFiles = new(); public readonly Dictionary< string, FileInfo > ResolvedFiles = new();
public readonly Dictionary< string, string > SwappedFiles = new(); public readonly Dictionary< string, string > SwappedFiles = new();
public ModCollection Mods { get; set; } public ModCollection Mods { get; set; }
@ -98,45 +98,45 @@ namespace Penumbra.Mods
Mods.Save(); Mods.Save();
CalculateEffectiveFileList(); CalculateEffectiveFileList();
// Needed to reload body textures with mods // Needed to reload body textures with mods
_plugin.GameUtils.ReloadPlayerResources(); _plugin.GameUtils.ReloadPlayerResources();
} }
public void CalculateEffectiveFileList() public void CalculateEffectiveFileList()
{ {
ResolvedFiles.Clear(); ResolvedFiles.Clear();
SwappedFiles.Clear(); SwappedFiles.Clear();
var registeredFiles = new Dictionary< string, string >(); var registeredFiles = new Dictionary< string, string >();
foreach( var (mod, settings) in Mods.GetOrderedAndEnabledModListWithSettings() ) foreach( var (mod, settings) in Mods.GetOrderedAndEnabledModListWithSettings( _plugin.Configuration.InvertModListOrder ) )
{ {
mod.FileConflicts?.Clear(); mod.FileConflicts?.Clear();
// fixup path // fixup path
var baseDir = mod.ModBasePath.FullName; var baseDir = mod.ModBasePath.FullName;
foreach( var file in mod.ModFiles ) foreach( var file in mod.ModFiles )
{ {
var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' ); var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' );
string gamePath; string gamePath;
bool addFile = true; bool addFile = true;
(string, uint, uint, ulong) tuple; (string, uint, uint, ulong) tuple;
if (mod.Meta.Groups.FileToGameAndGroup.TryGetValue(relativeFilePath, out tuple)) if( mod.Meta.Groups.FileToGameAndGroup.TryGetValue( relativeFilePath, out tuple ) )
{ {
gamePath = tuple.Item1; gamePath = tuple.Item1;
var (_, tops, bottoms, excludes) = tuple; var (_, tops, bottoms, excludes) = tuple;
var validTop = ((1u << settings.CurrentTop) & tops ) != 0; var validTop = ( ( 1u << settings.CurrentTop ) & tops ) != 0;
var validBottom = ((1u << settings.CurrentBottom) & bottoms ) != 0; var validBottom = ( ( 1u << settings.CurrentBottom ) & bottoms ) != 0;
var validGroup = ((1ul << settings.CurrentGroup) & excludes) != 0; var validGroup = ( ( 1ul << settings.CurrentGroup ) & excludes ) != 0;
addFile = validTop && validBottom && validGroup; addFile = validTop && validBottom && validGroup;
} }
else else
gamePath = relativeFilePath.Replace( '\\', '/' ); gamePath = relativeFilePath.Replace( '\\', '/' );
if ( addFile ) if( addFile )
{ {
if( !ResolvedFiles.ContainsKey( gamePath ) ) if( !ResolvedFiles.ContainsKey( gamePath ) )
{ {
@ -146,10 +146,10 @@ namespace Penumbra.Mods
else if( registeredFiles.TryGetValue( gamePath, out var modName ) ) else if( registeredFiles.TryGetValue( gamePath, out var modName ) )
{ {
mod.AddConflict( modName, gamePath ); mod.AddConflict( modName, gamePath );
} }
} }
} }
foreach( var swap in mod.Meta.FileSwaps ) foreach( var swap in mod.Meta.FileSwaps )
{ {
// just assume people put not fucked paths in here lol // just assume people put not fucked paths in here lol
@ -160,8 +160,8 @@ namespace Penumbra.Mods
} }
else if( registeredFiles.TryGetValue( swap.Key, out var modName ) ) else if( registeredFiles.TryGetValue( swap.Key, out var modName ) )
{ {
mod.AddConflict( modName, swap.Key ); mod.AddConflict( modName, swap.Key );
} }
} }
} }
} }
@ -202,10 +202,10 @@ namespace Penumbra.Mods
public string ResolveSwappedOrReplacementFilePath( string gameResourcePath ) public string ResolveSwappedOrReplacementFilePath( string gameResourcePath )
{ {
gameResourcePath = gameResourcePath.ToLowerInvariant(); gameResourcePath = gameResourcePath.ToLowerInvariant();
return GetCandidateForGameFile( gameResourcePath )?.FullName ?? GetSwappedFilePath( gameResourcePath ); return GetCandidateForGameFile( gameResourcePath )?.FullName ?? GetSwappedFilePath( gameResourcePath );
} }
public void Dispose() public void Dispose()
{ {

View file

@ -31,10 +31,10 @@ namespace Penumbra.Mods
ModFiles.Add( file ); ModFiles.Add( file );
} }
} }
// Only add if not in a sub-folder, otherwise it was already added. // Only add if not in a sub-folder, otherwise it was already added.
foreach( var pair in Meta.Groups.FileToGameAndGroup ) foreach( var pair in Meta.Groups.FileToGameAndGroup )
if (pair.Key.IndexOfAny(new char[]{'/', '\\'}) < 0) if (pair.Key.IndexOfAny(new[]{'/', '\\'}) < 0)
ModFiles.Add( new FileInfo(Path.Combine(ModBasePath.FullName, pair.Key)) ); ModFiles.Add( new FileInfo(Path.Combine(ModBasePath.FullName, pair.Key)) );
} }

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <TargetFramework>net472</TargetFramework>
<LangVersion>latest</LangVersion> <LangVersion>preview</LangVersion>
<AssemblyTitle>Penumbra</AssemblyTitle> <AssemblyTitle>Penumbra</AssemblyTitle>
<Company>absolute gangstas</Company> <Company>absolute gangstas</Company>
<Product>Penumbra</Product> <Product>Penumbra</Product>

View file

@ -1,7 +1,6 @@
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Plugin; using Dalamud.Plugin;
using EmbedIO; using EmbedIO;
using EmbedIO.Actions;
using EmbedIO.WebApi; using EmbedIO.WebApi;
using Penumbra.API; using Penumbra.API;
using Penumbra.Game; using Penumbra.Game;

View file

@ -6,8 +6,8 @@ using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Plugin; using Dalamud.Plugin;
using ImGuiNET; using ImGuiNET;
using Newtonsoft.Json; using Newtonsoft.Json;
using Penumbra.Importer; using Penumbra.Importer;
using Penumbra.Models; using Penumbra.Models;
@ -18,8 +18,8 @@ namespace Penumbra.UI
{ {
private readonly Plugin _plugin; private readonly Plugin _plugin;
public bool Visible = false; public bool Visible;
public bool ShowDebugBar = false; public bool ShowDebugBar;
private static readonly Vector2 AutoFillSize = new Vector2( -1, -1 ); private static readonly Vector2 AutoFillSize = new Vector2( -1, -1 );
private static readonly Vector2 ModListSize = new Vector2( 200, -1 ); private static readonly Vector2 ModListSize = new Vector2( 200, -1 );
@ -33,7 +33,7 @@ namespace Penumbra.UI
private int? _selectedModDeleteIndex; private int? _selectedModDeleteIndex;
private ModInfo _selectedMod; private ModInfo _selectedMod;
public bool IsImportRunning = false; private bool _isImportRunning;
private TexToolsImport _texToolsImport = null!; private TexToolsImport _texToolsImport = null!;
public SettingsInterface( Plugin plugin ) public SettingsInterface( Plugin plugin )
@ -129,7 +129,7 @@ namespace Penumbra.UI
DrawImportTab(); DrawImportTab();
if( !IsImportRunning ) if( !_isImportRunning )
{ {
DrawModBrowser(); DrawModBrowser();
@ -156,11 +156,11 @@ namespace Penumbra.UI
return; return;
} }
if( !IsImportRunning ) if( !_isImportRunning )
{ {
if( ImGui.Button( "Import TexTools Modpacks" ) ) if( ImGui.Button( "Import TexTools Modpacks" ) )
{ {
IsImportRunning = true; _isImportRunning = true;
Task.Run( async () => Task.Run( async () =>
{ {
@ -197,7 +197,7 @@ namespace Penumbra.UI
ReloadMods(); ReloadMods();
} }
IsImportRunning = false; _isImportRunning = false;
} ); } );
} }
} }
@ -267,9 +267,9 @@ namespace Penumbra.UI
if( ImGui.Button( "Rediscover Mods" ) ) if( ImGui.Button( "Rediscover Mods" ) )
{ {
ReloadMods(); ReloadMods();
_selectedModIndex = 0; _selectedModIndex = 0;
_selectedMod = null; _selectedMod = null;
} }
ImGui.SameLine(); ImGui.SameLine();
@ -279,7 +279,18 @@ namespace Penumbra.UI
Process.Start( _plugin.Configuration.CurrentCollection ); Process.Start( _plugin.Configuration.CurrentCollection );
} }
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 15 ); ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 20 );
var invertOrder = _plugin.Configuration.InvertModListOrder;
if( ImGui.Checkbox( "Invert mod load order (mods are loaded bottom up)", ref invertOrder ) )
{
_plugin.Configuration.InvertModListOrder = invertOrder;
dirty = true;
ReloadMods();
}
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 20 );
var showAdvanced = _plugin.Configuration.ShowAdvanced; var showAdvanced = _plugin.Configuration.ShowAdvanced;
if( ImGui.Checkbox( "Show Advanced Settings", ref showAdvanced ) ) if( ImGui.Checkbox( "Show Advanced Settings", ref showAdvanced ) )
@ -405,7 +416,13 @@ namespace Penumbra.UI
ImGui.PopFont(); ImGui.PopFont();
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
ImGui.SetTooltip( "Move the selected mod up in priority" ); {
ImGui.SetTooltip(
_plugin.Configuration.InvertModListOrder
? "Move the selected mod down in priority"
: "Move the selected mod up in priority"
);
}
ImGui.PushFont( UiBuilder.IconFont ); ImGui.PushFont( UiBuilder.IconFont );
@ -430,7 +447,13 @@ namespace Penumbra.UI
ImGui.PopFont(); ImGui.PopFont();
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
ImGui.SetTooltip( "Move the selected mod down in priority" ); {
ImGui.SetTooltip(
_plugin.Configuration.InvertModListOrder
? "Move the selected mod up in priority"
: "Move the selected mod down in priority"
);
}
ImGui.PushFont( UiBuilder.IconFont ); ImGui.PushFont( UiBuilder.IconFont );
@ -503,106 +526,109 @@ namespace Penumbra.UI
} }
ImGui.EndPopup(); ImGui.EndPopup();
}
// Website button with On-Hover address if valid http(s), otherwise text.
private void DrawWebsiteText()
{
if ((_selectedMod.Mod.Meta.Website?.Length ?? 0) > 0)
{
var validUrl = Uri.TryCreate(_selectedMod.Mod.Meta.Website, UriKind.Absolute, out Uri uriResult)
&& (uriResult.Scheme == Uri.UriSchemeHttps ||uriResult.Scheme == Uri.UriSchemeHttp);
ImGui.SameLine();
if (validUrl)
{
if (ImGui.SmallButton("Open Website"))
{
Process.Start( _selectedMod.Mod.Meta.Website );
}
if (ImGui.IsItemHovered())
{
ImGui.BeginTooltip();
ImGui.Text( _selectedMod.Mod.Meta.Website );
ImGui.EndTooltip();
}
}
else
{
ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "from" );
ImGui.SameLine();
ImGui.Text(_selectedMod.Mod.Meta.Website);
}
}
} }
// Create Mod-Handling buttons. // Website button with On-Hover address if valid http(s), otherwise text.
private void DrawEditButtons() private void DrawWebsiteText()
{ {
ImGui.SameLine(); if( ( _selectedMod.Mod.Meta.Website?.Length ?? 0 ) <= 0 )
{
return;
}
var validUrl = Uri.TryCreate( _selectedMod.Mod.Meta.Website, UriKind.Absolute, out Uri uriResult )
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
ImGui.SameLine();
if( validUrl )
{
if( ImGui.SmallButton( "Open Website" ) )
{
Process.Start( _selectedMod.Mod.Meta.Website );
}
if( ImGui.IsItemHovered() )
{
ImGui.BeginTooltip();
ImGui.Text( _selectedMod.Mod.Meta.Website );
ImGui.EndTooltip();
}
}
else
{
ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "from" );
ImGui.SameLine();
ImGui.Text( _selectedMod.Mod.Meta.Website );
}
}
// Create Mod-Handling buttons.
private void DrawEditButtons()
{
ImGui.SameLine();
if( ImGui.Button( "Open Mod Folder" ) ) if( ImGui.Button( "Open Mod Folder" ) )
{ {
Process.Start( _selectedMod.Mod.ModBasePath.FullName ); Process.Start( _selectedMod.Mod.ModBasePath.FullName );
} }
ImGui.SameLine(); ImGui.SameLine();
if( ImGui.Button( "Edit JSON" ) ) if( ImGui.Button( "Edit JSON" ) )
{ {
var metaPath = Path.Combine( _selectedMod.Mod.ModBasePath.FullName, "meta.json"); var metaPath = Path.Combine( _selectedMod.Mod.ModBasePath.FullName, "meta.json" );
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _selectedMod.Mod.Meta, Formatting.Indented ) ); File.WriteAllText( metaPath, JsonConvert.SerializeObject( _selectedMod.Mod.Meta, Formatting.Indented ) );
Process.Start( metaPath ); Process.Start( metaPath );
} }
ImGui.SameLine(); ImGui.SameLine();
if( ImGui.Button( "Reload JSON" ) ) if( ImGui.Button( "Reload JSON" ) )
{ {
ReloadMods(); ReloadMods();
// May select a different mod than before if mods were added or deleted, but will not crash. // May select a different mod than before if mods were added or deleted, but will not crash.
if (_selectedModIndex < _plugin.ModManager.Mods.ModSettings.Count) if( _selectedModIndex < _plugin.ModManager.Mods.ModSettings.Count )
{ {
_selectedMod = _plugin.ModManager.Mods.ModSettings[_selectedModIndex]; _selectedMod = _plugin.ModManager.Mods.ModSettings[ _selectedModIndex ];
} }
else else
{ {
_selectedModIndex = 0; _selectedModIndex = 0;
_selectedMod = null; _selectedMod = null;
} }
} }
} }
private void DrawGroupSelectors() private void DrawGroupSelectors()
{ {
var hasTopTypes = (_selectedMod.Mod.Meta.Groups.TopTypes?.Count ?? 0) > 1; var hasTopTypes = ( _selectedMod.Mod.Meta.Groups.TopTypes?.Count ?? 0 ) > 1;
var hasBottomTypes = (_selectedMod.Mod.Meta.Groups.BottomTypes?.Count ?? 0) > 1; var hasBottomTypes = ( _selectedMod.Mod.Meta.Groups.BottomTypes?.Count ?? 0 ) > 1;
var hasGroups = (_selectedMod.Mod.Meta.Groups.OtherGroups?.Count ?? 0) > 1; var hasGroups = ( _selectedMod.Mod.Meta.Groups.OtherGroups?.Count ?? 0 ) > 1;
var numSelectors = (hasTopTypes ? 1 : 0) + (hasBottomTypes ? 1 : 0) + (hasGroups ? 1 : 0); var numSelectors = ( hasTopTypes ? 1 : 0 ) + ( hasBottomTypes ? 1 : 0 ) + ( hasGroups ? 1 : 0 );
var selectorWidth = (ImGui.GetWindowWidth() var selectorWidth = ( ImGui.GetWindowWidth()
- (hasTopTypes ? ImGui.CalcTextSize("Top ").X : 0) - ( hasTopTypes ? ImGui.CalcTextSize( "Top " ).X : 0 )
- (hasBottomTypes ? ImGui.CalcTextSize("Bottom ").X : 0) - ( hasBottomTypes ? ImGui.CalcTextSize( "Bottom " ).X : 0 )
- (hasGroups ? ImGui.CalcTextSize("Group ").X : 0)) / numSelectors; - ( hasGroups ? ImGui.CalcTextSize( "Group " ).X : 0 ) ) / numSelectors;
void DrawSelector(string label, string propertyName, System.Collections.Generic.List<string> list, bool sameLine) void DrawSelector( string label, string propertyName, System.Collections.Generic.List< string > list, bool sameLine )
{ {
var current = (int) _selectedMod.GetType().GetProperty(propertyName).GetValue(_selectedMod); var current = ( int )_selectedMod.GetType().GetProperty( propertyName ).GetValue( _selectedMod );
ImGui.SetNextItemWidth( selectorWidth ); ImGui.SetNextItemWidth( selectorWidth );
if (sameLine) ImGui.SameLine(); if( sameLine ) ImGui.SameLine();
if ( ImGui.Combo(label, ref current, list.ToArray(), list.Count()) ) if( ImGui.Combo( label, ref current, list.ToArray(), list.Count() ) )
{ {
_selectedMod.GetType().GetProperty(propertyName).SetValue(_selectedMod, current); _selectedMod.GetType().GetProperty( propertyName ).SetValue( _selectedMod, current );
_plugin.ModManager.Mods.Save(); _plugin.ModManager.Mods.Save();
_plugin.ModManager.CalculateEffectiveFileList(); _plugin.ModManager.CalculateEffectiveFileList();
} }
} }
if ( hasTopTypes ) if( hasTopTypes )
DrawSelector("Top", "CurrentTop", _selectedMod.Mod.Meta.Groups.TopTypes, false); DrawSelector( "Top", "CurrentTop", _selectedMod.Mod.Meta.Groups.TopTypes, false );
if ( hasBottomTypes ) if( hasBottomTypes )
DrawSelector("Bottom", "CurrentBottom", _selectedMod.Mod.Meta.Groups.BottomTypes, hasTopTypes); DrawSelector( "Bottom", "CurrentBottom", _selectedMod.Mod.Meta.Groups.BottomTypes, hasTopTypes );
if ( hasGroups ) if( hasGroups )
DrawSelector("Group", "CurrentGroup", _selectedMod.Mod.Meta.Groups.OtherGroups, numSelectors > 1); DrawSelector( "Group", "CurrentGroup", _selectedMod.Mod.Meta.Groups.OtherGroups, numSelectors > 1 );
} }
void DrawInstalledMods() void DrawInstalledMods()
{ {
@ -634,24 +660,24 @@ namespace Penumbra.UI
{ {
ImGui.BeginChild( "selectedModInfo", AutoFillSize, true ); ImGui.BeginChild( "selectedModInfo", AutoFillSize, true );
ImGui.Text( _selectedMod.Mod.Meta.Name ); ImGui.Text( _selectedMod.Mod.Meta.Name );
// (Version ...) or nothing. // (Version ...) or nothing.
if ((_selectedMod.Mod.Meta.Version?.Length ?? 0) > 0) if( ( _selectedMod.Mod.Meta.Version?.Length ?? 0 ) > 0 )
{ {
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text($"(Version {_selectedMod.Mod.Meta.Version})" ); ImGui.Text( $"(Version {_selectedMod.Mod.Meta.Version})" );
} }
// by Author or Unknown. // by Author or Unknown.
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "by" ); ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "by" );
ImGui.SameLine(); ImGui.SameLine();
if ((_selectedMod.Mod.Meta.Author?.Length ?? 0) > 0 ) if( ( _selectedMod.Mod.Meta.Author?.Length ?? 0 ) > 0 )
ImGui.Text( _selectedMod.Mod.Meta.Author ); ImGui.Text( _selectedMod.Mod.Meta.Author );
else else
ImGui.Text( "Unknown" ); ImGui.Text( "Unknown" );
DrawWebsiteText(); DrawWebsiteText();
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 10 ); ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 10 );
@ -662,27 +688,32 @@ namespace Penumbra.UI
_selectedMod.Enabled = enabled; _selectedMod.Enabled = enabled;
_plugin.ModManager.Mods.Save(); _plugin.ModManager.Mods.Save();
_plugin.ModManager.CalculateEffectiveFileList(); _plugin.ModManager.CalculateEffectiveFileList();
} }
DrawEditButtons(); DrawEditButtons();
DrawGroupSelectors(); DrawGroupSelectors();
ImGui.TextWrapped( _selectedMod.Mod.Meta.Description ?? "" ); ImGui.BeginTabBar( "PenumbraPluginDetails" );
ImGui.BeginTabBar( "PenumbraPluginDetails" ); if( _selectedMod.Mod.Meta.Description?.Length > 0 && ImGui.BeginTabItem( "About" ) )
if ( (_selectedMod.Mod.Meta.ChangedItems?.Count ?? 0 ) > 0) {
{ ImGui.TextWrapped( _selectedMod.Mod.Meta.Description );
if( ImGui.BeginTabItem( "Changed Items" ) ) ImGui.EndTabItem();
{ }
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( "###", AutoFillSize ) ) if( ( _selectedMod.Mod.Meta.ChangedItems?.Count ?? 0 ) > 0 )
foreach(var item in _selectedMod.Mod.Meta.ChangedItems) {
if( ImGui.BeginTabItem( "Changed Items" ) )
{
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( "###", AutoFillSize ) )
foreach( var item in _selectedMod.Mod.Meta.ChangedItems )
ImGui.Selectable( item ); ImGui.Selectable( item );
ImGui.ListBoxFooter(); ImGui.ListBoxFooter();
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
} }
if( ImGui.BeginTabItem( "Files" ) ) if( ImGui.BeginTabItem( "Files" ) )
{ {
@ -713,6 +744,7 @@ namespace Penumbra.UI
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
} }
if( _selectedMod.Mod.FileConflicts.Any() ) if( _selectedMod.Mod.FileConflicts.Any() )
{ {
if( ImGui.BeginTabItem( "File Conflicts" ) ) if( ImGui.BeginTabItem( "File Conflicts" ) )

View file

@ -1,7 +1,5 @@
using System; using System.Linq;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Penumbra.Extensions;
namespace Penumbra.Util namespace Penumbra.Util
{ {
@ -10,14 +8,14 @@ namespace Penumbra.Util
/// </summary> /// </summary>
public class Crc32 public class Crc32
{ {
private const uint POLY = 0xedb88320; private const uint Poly = 0xedb88320;
private static readonly uint[] CrcArray = private static readonly uint[] CrcArray =
Enumerable.Range( 0, 256 ).Select( i => Enumerable.Range( 0, 256 ).Select( i =>
{ {
var k = ( uint )i; var k = ( uint )i;
for( var j = 0; j < 8; j++ ) for( var j = 0; j < 8; j++ )
k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ POLY : k >> 1; k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1;
return k; return k;
} ).ToArray(); } ).ToArray();