2019-10-07 17:33:34 +00:00
using Alphaleonis.Win32.Filesystem ;
2019-09-23 21:37:10 +00:00
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2019-10-31 02:24:42 +00:00
using Newtonsoft.Json ;
2019-09-23 21:37:10 +00:00
using Wabbajack.Common ;
2019-10-30 12:29:06 +00:00
using Wabbajack.Lib.CompilationSteps ;
2019-09-23 21:37:10 +00:00
using Directory = Alphaleonis . Win32 . Filesystem . Directory ;
using File = Alphaleonis . Win32 . Filesystem . File ;
using Path = Alphaleonis . Win32 . Filesystem . Path ;
2019-12-04 01:26:26 +00:00
using System.Threading.Tasks ;
2020-04-09 17:17:24 +00:00
#nullable disable
2019-09-23 21:37:10 +00:00
2019-10-16 03:10:34 +00:00
namespace Wabbajack.Lib
2019-09-23 21:37:10 +00:00
{
public class zEditIntegration
{
2019-11-18 00:17:06 +00:00
private static MO2Compiler _mo2Compiler ;
2019-11-11 20:05:23 +00:00
2020-03-28 21:09:45 +00:00
public static AbsolutePath FindzEditPath ( ACompiler compiler )
2019-09-23 21:37:10 +00:00
{
2019-11-18 00:17:06 +00:00
_mo2Compiler = ( MO2Compiler ) compiler ;
2019-11-11 20:05:23 +00:00
var executables = _mo2Compiler . MO2Ini . customExecutables ;
2020-03-28 21:09:45 +00:00
if ( executables . size = = null ) return default ;
2019-09-23 21:37:10 +00:00
foreach ( var idx in Enumerable . Range ( 1 , int . Parse ( executables . size ) ) )
{
var path = ( string ) executables [ $"{idx}\\binary" ] ;
if ( path = = null ) continue ;
if ( path . EndsWith ( "zEdit.exe" ) )
2020-03-28 21:09:45 +00:00
return ( AbsolutePath ) path ;
2019-09-23 21:37:10 +00:00
}
2020-03-28 21:09:45 +00:00
return default ;
2019-09-23 21:37:10 +00:00
}
2019-10-30 12:29:06 +00:00
public class IncludeZEditPatches : ACompilationStep
2019-09-23 21:37:10 +00:00
{
2020-04-04 22:06:14 +00:00
private readonly Dictionary < AbsolutePath , zEditMerge > _mergesIndexed ;
2020-04-04 16:04:42 +00:00
private bool _disabled = true ;
2019-09-23 21:37:10 +00:00
2019-11-03 16:46:26 +00:00
public IncludeZEditPatches ( ACompiler compiler ) : base ( compiler )
2019-10-30 12:29:06 +00:00
{
var zEditPath = FindzEditPath ( compiler ) ;
2020-03-29 03:29:27 +00:00
var havezEdit = zEditPath ! = default ;
2019-09-23 21:37:10 +00:00
2020-04-04 16:04:42 +00:00
Utils . Log ( havezEdit ? $"Found zEdit at {zEditPath}" : "zEdit not detected, disabling zEdit routines" ) ;
2019-09-23 21:37:10 +00:00
2019-10-30 12:29:06 +00:00
if ( ! havezEdit )
2019-10-07 17:33:34 +00:00
{
2020-04-03 22:56:14 +00:00
_mergesIndexed = new Dictionary < AbsolutePath , zEditMerge > ( ) ;
2019-10-30 12:29:06 +00:00
return ;
}
2020-04-04 16:04:42 +00:00
_mo2Compiler = ( MO2Compiler ) compiler ;
2020-04-04 22:06:14 +00:00
var settingsFiles = zEditPath . Combine ( "profiles" ) . EnumerateFiles ( false )
. Where ( f = > f . IsFile )
. Where ( f = > f . FileName = = Consts . SettingsJson )
2020-04-04 16:04:42 +00:00
. Where ( f = >
{
2020-04-06 20:48:54 +00:00
var settings = f . FromJson < zEditSettings > ( ) ;
2020-04-04 16:04:42 +00:00
if ( settings . modManager ! = "Mod Organizer 2" )
{
Utils . Log ( $"zEdit settings file {f}: modManager is not Mod Organizer 2 but {settings.modManager}!" ) ;
return false ;
}
if ( settings . managerPath ! = _mo2Compiler . MO2Folder )
{
Utils . Log ( $"zEdit settings file {f}: managerPath is not {_mo2Compiler.MO2Folder} but {settings.managerPath}!" ) ;
return false ;
}
2020-04-04 22:06:14 +00:00
if ( settings . modsPath ! = _mo2Compiler . MO2Folder . Combine ( Consts . MO2ModFolderName ) )
2020-04-04 16:04:42 +00:00
{
2020-04-04 17:10:13 +00:00
Utils . Log ( $"zEdit settings file {f}: modsPath is not {_mo2Compiler.MO2Folder}\\{Consts.MO2ModFolderName} but {settings.modsPath}!" ) ;
2020-04-04 16:04:42 +00:00
return false ;
}
2020-04-04 22:06:14 +00:00
if ( settings . mergePath ! = _mo2Compiler . MO2Folder . Combine ( Consts . MO2ModFolderName ) )
2020-04-04 16:04:42 +00:00
{
2020-04-04 17:10:13 +00:00
Utils . Log ( $"zEdit settings file {f}: modsPath is not {_mo2Compiler.MO2Folder}\\{Consts.MO2ModFolderName} but {settings.modsPath}!" ) ;
2020-04-04 16:04:42 +00:00
return false ;
}
return true ;
2020-04-04 22:06:14 +00:00
} ) . ToList ( ) ;
2020-04-04 16:04:42 +00:00
if ( ! settingsFiles . Any ( ) )
{
Utils . Log ( $"Found not acceptable settings.json file for zEdit!" ) ;
return ;
}
var profileFolder =
2020-04-04 22:06:14 +00:00
settingsFiles . Where ( x = > x . Parent . Combine ( "merges.json" ) . IsFile )
. Select ( x = > x = = default ? x : x . Parent )
. FirstOrDefault ( ) ;
2019-09-23 21:37:10 +00:00
2020-04-04 22:06:14 +00:00
if ( profileFolder = = default )
2020-04-04 16:04:42 +00:00
{
Utils . Log ( "Found no acceptable profiles folder for zEdit!" ) ;
return ;
}
2020-04-04 22:06:14 +00:00
var mergeFile = profileFolder . Combine ( "merges.json" ) ;
2020-04-04 16:04:42 +00:00
Utils . Log ( $"Using merge file {mergeFile}" ) ;
2020-04-06 20:48:54 +00:00
var merges = mergeFile . FromJson < List < zEditMerge > > ( ) . GroupBy ( f = > ( f . name , f . filename ) ) . ToArray ( ) ;
2019-09-23 21:37:10 +00:00
2019-10-30 12:29:06 +00:00
merges . Where ( m = > m . Count ( ) > 1 )
. Do ( m = >
{
2019-12-04 04:12:08 +00:00
Utils . Log (
2019-10-30 12:29:06 +00:00
$"WARNING, you have two patches named {m.Key.name}\\{m.Key.filename} in your zEdit profiles. We'll pick one at random, this probably isn't what you want." ) ;
} ) ;
_mergesIndexed =
merges . ToDictionary (
2020-04-03 22:56:14 +00:00
m = > _mo2Compiler . MO2Folder . Combine ( ( string ) Consts . MO2ModFolderName , m . Key . name , m . Key . filename ) ,
2019-10-30 12:29:06 +00:00
m = > m . First ( ) ) ;
2020-04-04 16:04:42 +00:00
_disabled = false ;
2019-10-30 12:29:06 +00:00
}
2019-09-23 21:37:10 +00:00
2019-12-04 01:26:26 +00:00
public override async ValueTask < Directive > Run ( RawSourceFile source )
2019-09-23 21:37:10 +00:00
{
2020-04-04 16:04:42 +00:00
if ( _disabled ) return null ;
2020-04-04 16:30:42 +00:00
if ( ! _mergesIndexed . TryGetValue ( source . AbsolutePath , out var merge ) )
{
2020-04-04 22:06:14 +00:00
if ( source . AbsolutePath . Extension ! = Consts . SeqExtension )
2020-04-04 16:30:42 +00:00
return null ;
2020-04-04 22:06:14 +00:00
var seqFolder = source . AbsolutePath . Parent ;
2020-04-04 16:30:42 +00:00
2020-04-04 22:06:14 +00:00
if ( seqFolder . FileName ! = ( RelativePath ) "seq" )
2020-04-04 16:30:42 +00:00
return null ;
2020-04-04 22:06:14 +00:00
var mergeFolder = seqFolder . Parent ;
var mergeName = mergeFolder . FileName ;
2020-04-04 16:30:42 +00:00
2020-04-04 22:06:14 +00:00
if ( ! mergeFolder . Combine ( mergeName + ".esp" ) . Exists )
2020-04-04 16:30:42 +00:00
return null ;
var inline = source . EvolveTo < InlineFile > ( ) ;
2020-04-04 22:06:14 +00:00
inline . SourceDataID = await _compiler . IncludeFile ( await source . AbsolutePath . ReadAllBytesAsync ( ) ) ;
2020-04-04 16:30:42 +00:00
return inline ;
}
2019-10-30 12:29:06 +00:00
var result = source . EvolveTo < MergedPatch > ( ) ;
result . Sources = merge . plugins . Select ( f = >
2019-09-23 21:37:10 +00:00
{
2020-04-03 22:56:14 +00:00
var origPath = ( AbsolutePath ) Path . Combine ( f . dataFolder , f . filename ) ;
2019-11-02 20:32:40 +00:00
var paths = new [ ]
{
2020-04-03 12:53:22 +00:00
origPath ,
2020-04-03 22:56:14 +00:00
origPath . WithExtension ( new Extension ( ".mohidden" ) ) ,
origPath . Parent . Combine ( ( RelativePath ) "optional" , origPath . FileName )
2019-11-02 20:32:40 +00:00
} ;
2020-04-03 22:56:14 +00:00
var absPath = paths . FirstOrDefault ( file = > file . IsFile ) ;
2019-11-02 20:32:40 +00:00
2020-04-03 22:56:14 +00:00
if ( absPath = = default )
2019-10-30 12:29:06 +00:00
throw new InvalidDataException (
2020-04-03 12:53:22 +00:00
$"File {origPath} is required to build {merge.filename} but it doesn't exist searched in: \n" + string . Join ( "\n" , paths ) ) ;
2020-04-03 22:56:14 +00:00
Hash hash ;
2020-04-03 12:53:22 +00:00
try
{
2020-04-03 22:56:14 +00:00
hash = _compiler . VFS . Index . ByRootPath [ absPath ] . Hash ;
2020-04-03 12:53:22 +00:00
} catch ( KeyNotFoundException e )
{
Utils . ErrorThrow ( e , $"Could not find the key {absPath} in the VFS Index dictionary!" ) ;
2020-04-03 22:56:14 +00:00
return null ;
2020-04-03 12:53:22 +00:00
}
2019-09-23 21:37:10 +00:00
2019-10-30 12:29:06 +00:00
return new SourcePatch
2019-09-23 21:37:10 +00:00
{
2020-04-03 12:53:22 +00:00
RelativePath = absPath . RelativeTo ( _mo2Compiler . MO2Folder ) ,
Hash = hash
2019-10-30 12:29:06 +00:00
} ;
} ) . ToList ( ) ;
2020-04-03 22:56:14 +00:00
var srcData = result . Sources . Select ( f = > _mo2Compiler . MO2Folder . Combine ( f . RelativePath ) . ReadAllBytes ( ) )
2019-10-30 12:29:06 +00:00
. ConcatArrays ( ) ;
2019-09-23 21:37:10 +00:00
2020-04-03 22:56:14 +00:00
var dstData = await source . AbsolutePath . ReadAllBytesAsync ( ) ;
2019-09-23 21:37:10 +00:00
2020-02-05 05:17:12 +00:00
await using ( var ms = new MemoryStream ( ) )
2019-10-30 12:29:06 +00:00
{
2020-04-03 22:56:14 +00:00
await Utils . CreatePatch ( srcData , dstData , ms ) ;
result . PatchID = await _compiler . IncludeFile ( ms . ToArray ( ) ) ;
2019-09-23 21:37:10 +00:00
}
2019-10-30 12:29:06 +00:00
return result ;
2019-09-23 21:37:10 +00:00
2019-10-30 12:29:06 +00:00
}
2019-10-31 02:24:42 +00:00
public override IState GetState ( )
{
return new State ( ) ;
}
[JsonObject("IncludeZEditPatches")]
public class State : IState
{
2019-11-03 16:46:26 +00:00
public ICompilationStep CreateStep ( ACompiler compiler )
2019-10-31 02:24:42 +00:00
{
return new IncludeZEditPatches ( compiler ) ;
}
}
2019-09-23 21:37:10 +00:00
}
2020-04-04 16:04:42 +00:00
public class zEditSettings
{
public string modManager ;
2020-04-04 22:06:14 +00:00
public AbsolutePath managerPath ;
public AbsolutePath modsPath ;
public AbsolutePath mergePath ;
2020-04-04 16:04:42 +00:00
}
2019-11-02 15:38:03 +00:00
public class zEditMerge
2019-09-23 21:37:10 +00:00
{
public string name ;
public string filename ;
public List < zEditMergePlugin > plugins ;
}
2019-11-02 15:38:03 +00:00
public class zEditMergePlugin
2019-09-23 21:37:10 +00:00
{
public string filename ;
public string dataFolder ;
}
2019-11-18 00:17:06 +00:00
public static void VerifyMerges ( MO2Compiler compiler )
2019-09-23 21:37:10 +00:00
{
2020-04-04 16:30:42 +00:00
var byName = compiler . InstallDirectives . ToDictionary ( f = > f . To ) ;
2019-09-23 21:37:10 +00:00
foreach ( var directive in compiler . InstallDirectives . OfType < MergedPatch > ( ) )
{
foreach ( var source in directive . Sources )
{
2020-04-04 16:30:42 +00:00
if ( ! byName . TryGetValue ( source . RelativePath , out var result ) )
throw new InvalidDataException (
$"{source.RelativePath} is needed for merged patch {directive.To} but is not included in the install." ) ;
if ( result . Hash ! = source . Hash )
throw new InvalidDataException ( $"Hashes for {result.To} needed for zEdit merge sources don't match, this shouldn't happen" ) ;
2019-09-23 21:37:10 +00:00
}
}
}
2019-11-02 15:38:03 +00:00
2019-12-04 01:26:26 +00:00
public static async Task GenerateMerges ( MO2Installer installer )
2019-11-02 15:38:03 +00:00
{
2019-12-04 01:26:26 +00:00
await installer . ModList
2019-11-02 15:38:03 +00:00
. Directives
. OfType < MergedPatch > ( )
2020-04-03 22:56:14 +00:00
. PMap ( installer . Queue , async m = >
2019-11-02 15:38:03 +00:00
{
Utils . LogStatus ( $"Generating zEdit merge: {m.To}" ) ;
2020-04-03 22:56:14 +00:00
var srcData = m . Sources . Select ( s = > installer . OutputFolder . Combine ( s . RelativePath ) . ReadAllBytes ( ) )
2019-11-02 15:38:03 +00:00
. ConcatArrays ( ) ;
2020-04-03 22:56:14 +00:00
var patchData = await installer . LoadBytesFromPath ( m . PatchID ) ;
2019-11-02 15:38:03 +00:00
2020-04-03 22:56:14 +00:00
await using var fs = installer . OutputFolder . Combine ( m . To ) . Create ( ) ;
Utils . ApplyPatch ( new MemoryStream ( srcData ) , ( ) = > new MemoryStream ( patchData ) , fs ) ;
2019-11-02 15:38:03 +00:00
} ) ;
}
2019-09-23 21:37:10 +00:00
}
}