2019-11-17 15:00:33 +00:00
using Compression.BSA ;
2019-10-07 17:33:34 +00:00
using System ;
2019-07-26 20:59:14 +00:00
using System.Collections.Concurrent ;
2019-07-21 04:40:54 +00:00
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2019-12-03 21:56:18 +00:00
using System.Threading ;
2019-11-12 04:35:07 +00:00
using System.Threading.Tasks ;
2019-12-10 12:26:49 +00:00
using Alphaleonis.Win32.Filesystem ;
2019-07-21 04:40:54 +00:00
using Wabbajack.Common ;
2019-10-30 12:29:06 +00:00
using Wabbajack.Lib.CompilationSteps ;
2019-10-16 03:10:34 +00:00
using Wabbajack.Lib.NexusApi ;
using Wabbajack.Lib.Validation ;
2019-09-18 21:33:23 +00:00
using Directory = Alphaleonis . Win32 . Filesystem . Directory ;
using File = Alphaleonis . Win32 . Filesystem . File ;
2019-12-10 12:26:49 +00:00
using FileInfo = Alphaleonis . Win32 . Filesystem . FileInfo ;
using Game = Wabbajack . Common . Game ;
2019-09-18 21:33:23 +00:00
using Path = Alphaleonis . Win32 . Filesystem . Path ;
2019-07-21 04:40:54 +00:00
2019-10-16 03:10:34 +00:00
namespace Wabbajack.Lib
2019-07-21 04:40:54 +00:00
{
2019-11-18 00:17:06 +00:00
public class MO2Compiler : ACompiler
2019-07-21 04:40:54 +00:00
{
2019-11-15 23:13:27 +00:00
2019-09-12 23:44:35 +00:00
private string _mo2DownloadsFolder ;
2019-11-15 13:06:34 +00:00
2019-07-21 04:40:54 +00:00
public string MO2Folder ;
2019-11-24 00:30:51 +00:00
public string MO2Profile { get ; }
2019-11-21 21:32:58 +00:00
public Dictionary < string , dynamic > ModMetas { get ; set ; }
2019-09-12 23:44:35 +00:00
2019-11-24 00:30:51 +00:00
public override ModManager ModManager = > ModManager . MO2 ;
public override string GamePath { get ; }
2019-12-10 12:26:49 +00:00
public GameMetaData CompilingGame { get ; set ; }
2019-11-24 00:30:51 +00:00
public override string ModListOutputFolder = > "output_folder" ;
2019-11-03 16:43:43 +00:00
2019-11-24 00:30:51 +00:00
public override string ModListOutputFile { get ; }
public MO2Compiler ( string mo2Folder , string mo2Profile , string outputFile )
{
2019-11-21 15:51:57 +00:00
MO2Folder = mo2Folder ;
2019-11-24 00:30:51 +00:00
MO2Profile = mo2Profile ;
2019-09-12 23:44:35 +00:00
MO2Ini = Path . Combine ( MO2Folder , "ModOrganizer.ini" ) . LoadIniFile ( ) ;
2019-12-10 12:26:49 +00:00
var mo2game = ( string ) MO2Ini . General . gameName ;
CompilingGame = GameRegistry . Games . First ( g = > g . Value . MO2Name = = mo2game ) . Value ;
2019-10-07 17:33:34 +00:00
GamePath = ( ( string ) MO2Ini . General . gamePath ) . Replace ( "\\\\" , "\\" ) ;
2019-11-24 00:30:51 +00:00
ModListOutputFile = outputFile ;
2019-09-12 23:44:35 +00:00
}
2019-07-21 04:40:54 +00:00
public dynamic MO2Ini { get ; }
2019-08-07 23:06:38 +00:00
public bool IgnoreMissingFiles { get ; set ; }
2019-07-22 22:17:46 +00:00
public string MO2DownloadsFolder
{
2019-07-21 04:40:54 +00:00
get
{
2019-09-03 22:12:39 +00:00
if ( _mo2DownloadsFolder ! = null ) return _mo2DownloadsFolder ;
if ( MO2Ini ! = null )
if ( MO2Ini . Settings ! = null )
if ( MO2Ini . Settings . download_directory ! = null )
return MO2Ini . Settings . download_directory . Replace ( "/" , "\\" ) ;
2019-11-24 00:36:57 +00:00
return GetTypicalDownloadsFolder ( MO2Folder ) ;
2019-07-21 04:40:54 +00:00
}
2019-09-03 22:12:39 +00:00
set = > _mo2DownloadsFolder = value ;
2019-07-21 04:40:54 +00:00
}
2019-11-24 00:36:57 +00:00
public static string GetTypicalDownloadsFolder ( string mo2Folder ) = > Path . Combine ( mo2Folder , "downloads" ) ;
2019-09-12 23:44:35 +00:00
public string MO2ProfileDir = > Path . Combine ( MO2Folder , "profiles" , MO2Profile ) ;
2019-07-21 12:42:29 +00:00
2019-08-05 23:58:42 +00:00
internal UserStatus User { get ; private set ; }
2019-07-26 20:59:14 +00:00
public ConcurrentBag < Directive > ExtraFiles { get ; private set ; }
2019-08-11 22:57:32 +00:00
public Dictionary < string , dynamic > ModInis { get ; private set ; }
2019-07-21 04:40:54 +00:00
2019-09-12 23:44:35 +00:00
public HashSet < string > SelectedProfiles { get ; set ; } = new HashSet < string > ( ) ;
2019-08-15 04:30:37 +00:00
2019-12-04 01:26:26 +00:00
protected override async Task < bool > _Begin ( CancellationToken cancel )
2019-07-21 04:40:54 +00:00
{
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-12 23:24:27 +00:00
ConfigureProcessor ( 19 ) ;
2019-11-17 04:16:42 +00:00
UpdateTracker . Reset ( ) ;
UpdateTracker . NextStep ( "Gathering information" ) ;
2019-09-12 23:44:35 +00:00
Info ( "Looking for other profiles" ) ;
2019-11-21 15:51:57 +00:00
var otherProfilesPath = Path . Combine ( MO2ProfileDir , "otherprofiles.txt" ) ;
2019-09-07 04:57:49 +00:00
SelectedProfiles = new HashSet < string > ( ) ;
2019-11-21 15:51:57 +00:00
if ( File . Exists ( otherProfilesPath ) ) SelectedProfiles = File . ReadAllLines ( otherProfilesPath ) . ToHashSet ( ) ;
2019-09-07 04:57:49 +00:00
SelectedProfiles . Add ( MO2Profile ) ;
2019-09-03 21:17:00 +00:00
2019-09-12 23:44:35 +00:00
Info ( "Using Profiles: " + string . Join ( ", " , SelectedProfiles . OrderBy ( p = > p ) ) ) ;
2019-09-03 21:17:00 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-07 02:54:27 +00:00
await VFS . IntegrateFromFile ( _vfsCacheName ) ;
2019-11-15 23:13:27 +00:00
2019-11-22 05:19:42 +00:00
var roots = new List < string > ( )
{
MO2Folder , GamePath , MO2DownloadsFolder
} ;
// TODO: make this generic so we can add more paths
2019-11-15 23:13:27 +00:00
2019-11-22 05:19:42 +00:00
var lootPath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
"LOOT" ) ;
IEnumerable < RawSourceFile > lootFiles = new List < RawSourceFile > ( ) ;
if ( Directory . Exists ( lootPath ) )
{
roots . Add ( lootPath ) ;
}
UpdateTracker . NextStep ( "Indexing folders" ) ;
2019-09-03 22:12:39 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-04 01:26:26 +00:00
await VFS . AddRoots ( roots ) ;
await VFS . WriteToFile ( _vfsCacheName ) ;
2019-11-22 05:19:42 +00:00
if ( Directory . Exists ( lootPath ) )
{
2019-12-20 20:32:04 +00:00
lootFiles = Directory . EnumerateFiles ( lootPath , "userlist.yaml" , SearchOption . AllDirectories )
2019-11-22 05:19:42 +00:00
. Where ( p = > p . FileExists ( ) )
2019-12-22 18:54:49 +00:00
. Select ( p = > new RawSourceFile ( VFS . Index . ByRootPath [ p ] , Path . Combine ( Consts . LOOTFolderFilesDir , p . RelativeTo ( lootPath ) ) ) ) ;
2019-11-22 05:19:42 +00:00
}
2019-11-15 23:13:27 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-11-17 04:16:42 +00:00
UpdateTracker . NextStep ( "Cleaning output folder" ) ;
2019-10-01 22:39:25 +00:00
if ( Directory . Exists ( ModListOutputFolder ) )
2019-11-23 17:37:24 +00:00
Utils . DeleteDirectory ( ModListOutputFolder ) ;
2019-10-01 22:39:25 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-10 12:26:49 +00:00
UpdateTracker . NextStep ( "Inferring metas for game file downloads" ) ;
2019-12-13 00:40:21 +00:00
await InferMetas ( ) ;
2019-11-15 23:13:27 +00:00
2019-12-13 00:40:21 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-10 12:26:49 +00:00
UpdateTracker . NextStep ( "Reindexing downloads after meta inferring" ) ;
2019-12-13 00:40:21 +00:00
await VFS . AddRoot ( MO2DownloadsFolder ) ;
await VFS . WriteToFile ( _vfsCacheName ) ;
2019-09-24 04:34:21 +00:00
2019-12-13 00:40:21 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-12 23:24:27 +00:00
UpdateTracker . NextStep ( "Pre-validating Archives" ) ;
2019-09-24 04:34:21 +00:00
2019-08-20 04:57:08 +00:00
IndexedArchives = Directory . EnumerateFiles ( MO2DownloadsFolder )
2019-09-12 23:44:35 +00:00
. Where ( f = > File . Exists ( f + ".meta" ) )
. Select ( f = > new IndexedArchive
{
2019-11-15 13:06:34 +00:00
File = VFS . Index . ByRootPath [ f ] ,
2019-09-12 23:44:35 +00:00
Name = Path . GetFileName ( f ) ,
IniData = ( f + ".meta" ) . LoadIniFile ( ) ,
Meta = File . ReadAllText ( f + ".meta" )
} )
. ToList ( ) ;
2019-12-13 13:02:58 +00:00
await CleanInvalidArchives ( ) ;
2019-12-12 23:24:27 +00:00
UpdateTracker . NextStep ( "Finding Install Files" ) ;
Directory . CreateDirectory ( ModListOutputFolder ) ;
var mo2Files = Directory . EnumerateFiles ( MO2Folder , "*" , SearchOption . AllDirectories )
. Where ( p = > p . FileExists ( ) )
2019-12-22 18:54:49 +00:00
. Select ( p = > new RawSourceFile ( VFS . Index . ByRootPath [ p ] , p . RelativeTo ( MO2Folder ) ) ) ;
2019-12-12 23:24:27 +00:00
var gameFiles = Directory . EnumerateFiles ( GamePath , "*" , SearchOption . AllDirectories )
. Where ( p = > p . FileExists ( ) )
2019-12-22 18:54:49 +00:00
. Select ( p = > new RawSourceFile ( VFS . Index . ByRootPath [ p ] , Path . Combine ( Consts . GameFolderFilesDir , p . RelativeTo ( GamePath ) ) ) ) ;
2019-12-12 23:24:27 +00:00
2019-11-21 21:32:58 +00:00
ModMetas = Directory . EnumerateDirectories ( Path . Combine ( MO2Folder , "mods" ) )
. Keep ( f = >
{
var path = Path . Combine ( f , "meta.ini" ) ;
return File . Exists ( path ) ? ( f , path . LoadIniFile ( ) ) : default ;
2019-11-21 22:35:59 +00:00
} ) . ToDictionary ( f = > f . f . RelativeTo ( MO2Folder ) + "\\" , v = > v . Item2 ) ;
2019-11-21 21:32:58 +00:00
2019-11-15 13:06:34 +00:00
IndexedFiles = IndexedArchives . SelectMany ( f = > f . File . ThisAndAllChildren )
. OrderBy ( f = > f . NestingFactor )
2019-09-12 23:44:35 +00:00
. GroupBy ( f = > f . Hash )
. ToDictionary ( f = > f . Key , f = > f . AsEnumerable ( ) ) ;
2019-08-15 04:30:37 +00:00
2019-11-21 15:51:57 +00:00
AllFiles = mo2Files . Concat ( gameFiles )
. Concat ( lootFiles )
2019-09-23 21:37:10 +00:00
. DistinctBy ( f = > f . Path )
2019-09-12 23:44:35 +00:00
. ToList ( ) ;
2019-08-15 04:30:37 +00:00
2019-09-26 22:32:15 +00:00
Info ( $"Found {AllFiles.Count} files to build into mod list" ) ;
2019-07-21 04:40:54 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Verifying destinations" ) ;
2019-09-30 23:39:41 +00:00
var dups = AllFiles . GroupBy ( f = > f . Path )
. Where ( fs = > fs . Count ( ) > 1 )
. Select ( fs = >
{
Utils . Log ( $"Duplicate files installed to {fs.Key} from : {String.Join(" , ", fs.Select(f => f.AbsolutePath))}" ) ;
return fs ;
} ) . ToList ( ) ;
if ( dups . Count > 0 )
{
Error ( $"Found {dups.Count} duplicates, exiting" ) ;
}
2019-07-26 20:59:14 +00:00
ExtraFiles = new ConcurrentBag < Directive > ( ) ;
2019-11-24 23:03:36 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Loading INIs" ) ;
2019-08-11 22:57:32 +00:00
ModInis = Directory . EnumerateDirectories ( Path . Combine ( MO2Folder , "mods" ) )
2019-09-12 23:44:35 +00:00
. Select ( f = >
{
2019-11-21 15:51:57 +00:00
var modName = Path . GetFileName ( f ) ;
var metaPath = Path . Combine ( f , "meta.ini" ) ;
if ( File . Exists ( metaPath ) )
return ( mod_name : modName , metaPath . LoadIniFile ( ) ) ;
2019-09-12 23:44:35 +00:00
return ( null , null ) ;
} )
. Where ( f = > f . Item2 ! = null )
. ToDictionary ( f = > f . Item1 , f = > f . Item2 ) ;
2019-08-11 22:57:32 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-08-20 04:57:08 +00:00
var stack = MakeStack ( ) ;
2019-11-17 04:16:42 +00:00
UpdateTracker . NextStep ( "Running Compilation Stack" ) ;
2019-12-04 01:26:26 +00:00
var results = await AllFiles . PMap ( Queue , UpdateTracker , f = > RunStack ( stack , f ) ) ;
2019-07-21 04:40:54 +00:00
2019-07-26 20:59:14 +00:00
// Add the extra files that were generated by the stack
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( $"Adding {ExtraFiles.Count} that were generated by the stack" ) ;
2019-12-04 01:26:26 +00:00
results = results . Concat ( ExtraFiles ) . ToArray ( ) ;
2019-07-26 20:59:14 +00:00
2019-07-21 04:40:54 +00:00
var nomatch = results . OfType < NoMatch > ( ) ;
2019-09-26 22:32:15 +00:00
Info ( $"No match for {nomatch.Count()} files" ) ;
2019-07-21 04:40:54 +00:00
foreach ( var file in nomatch )
2019-09-26 22:32:15 +00:00
Info ( $" {file.To}" ) ;
2019-12-13 13:02:58 +00:00
if ( nomatch . Any ( ) )
2019-08-02 22:31:13 +00:00
{
2019-08-07 23:06:38 +00:00
if ( IgnoreMissingFiles )
{
Info ( "Continuing even though files were missing at the request of the user." ) ;
}
2019-09-12 23:44:35 +00:00
else
{
2019-08-07 23:06:38 +00:00
Info ( "Exiting due to no way to compile these files" ) ;
2019-09-24 04:20:24 +00:00
return false ;
2019-08-07 23:06:38 +00:00
}
2019-08-02 22:31:13 +00:00
}
2019-07-21 04:40:54 +00:00
2019-07-21 22:47:17 +00:00
InstallDirectives = results . Where ( i = > ! ( i is IgnoredDirectly ) ) . ToList ( ) ;
2019-09-24 15:26:44 +00:00
Info ( "Getting Nexus api_key, please click authorize if a browser window appears" ) ;
2019-08-09 21:13:02 +00:00
2019-09-26 03:18:36 +00:00
if ( IndexedArchives . Any ( a = > a . IniData ? . General ? . gameName ! = null ) )
2019-09-24 04:20:24 +00:00
{
2019-12-07 03:50:50 +00:00
var nexusClient = await NexusApiClient . Get ( ) ;
if ( ! ( await nexusClient . IsPremium ( ) ) ) Error ( $"User {(await nexusClient.Username())} is not a premium Nexus user, so we cannot access the necessary API calls, cannot continue" ) ;
2019-09-12 23:44:35 +00:00
2019-09-24 04:20:24 +00:00
}
2019-09-12 23:44:35 +00:00
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Verifying Files" ) ;
2019-09-23 21:37:10 +00:00
zEditIntegration . VerifyMerges ( this ) ;
2019-08-05 23:58:42 +00:00
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Gathering Archives" ) ;
2019-12-04 01:26:26 +00:00
await GatherArchives ( ) ;
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Including Archive Metadata" ) ;
2019-12-07 07:22:54 +00:00
await IncludeArchiveMetadata ( ) ;
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Building Patches" ) ;
2019-12-04 01:26:26 +00:00
await BuildPatches ( ) ;
2019-07-23 04:27:26 +00:00
2019-09-12 23:44:35 +00:00
ModList = new ModList
2019-07-23 04:27:26 +00:00
{
2019-12-10 12:26:49 +00:00
GameType = CompilingGame . Game ,
2019-10-23 17:00:45 +00:00
WabbajackVersion = WabbajackVersion ,
2019-12-03 21:13:29 +00:00
Archives = SelectedArchives . ToList ( ) ,
2019-11-03 16:43:43 +00:00
ModManager = ModManager . MO2 ,
2019-08-02 23:04:04 +00:00
Directives = InstallDirectives ,
2019-10-07 14:44:28 +00:00
Name = ModListName ? ? MO2Profile ,
Author = ModListAuthor ? ? "" ,
Description = ModListDescription ? ? "" ,
2019-10-11 12:56:55 +00:00
Readme = ModListReadme ? ? "" ,
2019-10-11 11:09:34 +00:00
Image = ModListImage ? ? "" ,
2019-10-07 14:44:28 +00:00
Website = ModListWebsite ? ? ""
2019-07-23 04:27:26 +00:00
} ;
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Running Validation" ) ;
2019-12-04 01:26:26 +00:00
await ValidateModlist . RunValidation ( Queue , ModList ) ;
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Generating Report" ) ;
2019-09-30 23:39:41 +00:00
2019-08-30 23:57:56 +00:00
GenerateReport ( ) ;
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Exporting Modlist" ) ;
2019-11-17 15:00:33 +00:00
ExportModList ( ) ;
2019-09-12 23:44:35 +00:00
2019-07-23 04:27:26 +00:00
ResetMembers ( ) ;
2019-08-30 23:57:56 +00:00
ShowReport ( ) ;
2019-11-24 23:03:36 +00:00
UpdateTracker . NextStep ( "Done Building Modlist" ) ;
2019-09-24 04:20:24 +00:00
return true ;
2019-07-21 04:40:54 +00:00
}
2019-09-12 23:44:35 +00:00
2019-12-13 00:40:21 +00:00
private async Task CleanInvalidArchives ( )
2019-12-12 23:24:27 +00:00
{
2019-12-13 00:40:21 +00:00
var remove = ( await IndexedArchives . PMap ( Queue , async a = >
2019-12-12 23:24:27 +00:00
{
try
{
2019-12-13 00:40:21 +00:00
await ResolveArchive ( a ) ;
2019-12-12 23:24:27 +00:00
return null ;
}
catch
{
return a ;
}
2019-12-13 00:40:21 +00:00
} ) ) . Where ( a = > a ! = null ) . ToHashSet ( ) ;
2019-12-12 23:24:27 +00:00
if ( remove . Count = = 0 )
return ;
Utils . Log (
$"Removing {remove.Count} archives from the compilation state, this is probably not an issue but reference this if you have compilation failures" ) ;
remove . Do ( r = > Utils . Log ( $"Resolution failed for: {r.File}" ) ) ;
IndexedArchives . RemoveAll ( a = > remove . Contains ( a ) ) ;
}
2019-12-13 00:40:21 +00:00
private async Task InferMetas ( )
2019-12-10 12:26:49 +00:00
{
var to_find = Directory . EnumerateFiles ( MO2DownloadsFolder )
. Where ( f = > ! f . EndsWith ( ".meta" ) & & ! f . EndsWith ( Consts . HashFileExtension ) )
. Where ( f = > ! File . Exists ( f + ".meta" ) )
. ToList ( ) ;
if ( to_find . Count = = 0 ) return ;
var games = new [ ] { CompilingGame } . Concat ( GameRegistry . Games . Values . Where ( g = > g ! = CompilingGame ) ) ;
var game_files = games
. Where ( g = > g . GameLocation ( ) ! = null )
. SelectMany ( game = > Directory . EnumerateFiles ( game . GameLocation ( ) , "*" , DirectoryEnumerationOptions . Recursive ) . Select ( name = > ( game , name ) ) )
. GroupBy ( f = > ( Path . GetFileName ( f . name ) , new FileInfo ( f . name ) . Length ) )
. ToDictionary ( f = > f . Key ) ;
2019-12-13 00:40:21 +00:00
await to_find . PMap ( Queue , f = >
2019-12-10 12:26:49 +00:00
{
var vf = VFS . Index . ByFullPath [ f ] ;
if ( ! game_files . TryGetValue ( ( Path . GetFileName ( f ) , vf . Size ) , out var found ) )
return ;
var ( game , name ) = found . FirstOrDefault ( ff = > ff . name . FileHash ( ) = = vf . Hash ) ;
if ( name = = null )
return ;
File . WriteAllLines ( f + ".meta" , new [ ]
{
"[General]" ,
$"gameName={game.MO2ArchiveName}" ,
$"gameFile={name.RelativeTo(game.GameLocation()).Replace(" \ \ ", " / ")}"
} ) ;
} ) ;
}
2019-09-12 23:44:35 +00:00
2019-11-21 21:32:58 +00:00
2019-12-07 07:22:54 +00:00
private async Task IncludeArchiveMetadata ( )
2019-11-04 04:36:25 +00:00
{
Utils . Log ( $"Including {SelectedArchives.Count} .meta files for downloads" ) ;
2019-12-07 07:22:54 +00:00
await SelectedArchives . PMap ( Queue , a = >
2019-11-04 04:36:25 +00:00
{
var source = Path . Combine ( MO2DownloadsFolder , a . Name + ".meta" ) ;
InstallDirectives . Add ( new ArchiveMeta ( )
{
SourceDataID = IncludeFile ( File . ReadAllText ( source ) ) ,
Size = File . GetSize ( source ) ,
Hash = source . FileHash ( ) ,
To = Path . GetFileName ( source )
} ) ;
} ) ;
}
2019-07-23 04:27:26 +00:00
/// <summary>
2019-09-12 23:44:35 +00:00
/// Clear references to lists that hold a lot of data.
2019-07-23 04:27:26 +00:00
/// </summary>
private void ResetMembers ( )
{
AllFiles = null ;
InstallDirectives = null ;
SelectedArchives = null ;
2019-07-26 20:59:14 +00:00
ExtraFiles = null ;
2019-07-23 04:27:26 +00:00
}
2019-07-22 03:36:25 +00:00
/// <summary>
2019-09-12 23:44:35 +00:00
/// Fills in the Patch fields in files that require them
2019-07-22 03:36:25 +00:00
/// </summary>
2019-12-04 01:26:26 +00:00
private async Task BuildPatches ( )
2019-07-22 03:36:25 +00:00
{
2019-09-12 23:44:35 +00:00
Info ( "Gathering patch files" ) ;
2019-11-22 05:19:42 +00:00
InstallDirectives . OfType < PatchedFromArchive > ( )
. Where ( p = > p . PatchID = = null )
. Do ( p = >
{
if ( Utils . TryGetPatch ( p . FromHash , p . Hash , out var bytes ) )
p . PatchID = IncludeFile ( bytes ) ;
} ) ;
2019-07-22 03:36:25 +00:00
var groups = InstallDirectives . OfType < PatchedFromArchive > ( )
2019-10-01 22:39:25 +00:00
. Where ( p = > p . PatchID = = null )
2019-09-12 23:44:35 +00:00
. GroupBy ( p = > p . ArchiveHashPath [ 0 ] )
. ToList ( ) ;
2019-07-22 03:36:25 +00:00
2019-09-26 22:32:15 +00:00
Info ( $"Patching building patches from {groups.Count} archives" ) ;
2019-11-21 15:51:57 +00:00
var absolutePaths = AllFiles . ToDictionary ( e = > e . Path , e = > e . AbsolutePath ) ;
2019-12-04 01:26:26 +00:00
await groups . PMap ( Queue , group = > BuildArchivePatches ( group . Key , group , absolutePaths ) ) ;
2019-07-22 03:36:25 +00:00
2019-12-20 20:32:04 +00:00
var firstFailedPatch = InstallDirectives . OfType < PatchedFromArchive > ( ) . FirstOrDefault ( f = > f . PatchID = = null ) ;
if ( firstFailedPatch ! = null )
Error ( $"Missing patches after generation, this should not happen. First failure: {firstFailedPatch.FullPath}" ) ;
2019-07-22 03:36:25 +00:00
}
2019-12-04 01:26:26 +00:00
private async Task BuildArchivePatches ( string archiveSha , IEnumerable < PatchedFromArchive > group ,
2019-11-21 15:51:57 +00:00
Dictionary < string , string > absolutePaths )
2019-07-22 03:36:25 +00:00
{
2019-12-04 01:26:26 +00:00
using ( var files = await VFS . StageWith ( group . Select ( g = > VFS . Index . FileForArchiveHashPath ( g . ArchiveHashPath ) ) ) )
2019-07-22 03:36:25 +00:00
{
2019-11-21 15:51:57 +00:00
var byPath = files . GroupBy ( f = > string . Join ( "|" , f . FilesInFullPath . Skip ( 1 ) . Select ( i = > i . Name ) ) )
2019-09-12 23:44:35 +00:00
. ToDictionary ( f = > f . Key , f = > f . First ( ) ) ;
2019-08-20 04:57:08 +00:00
// Now Create the patches
2019-12-04 01:26:26 +00:00
await group . PMap ( Queue , entry = >
2019-07-22 03:36:25 +00:00
{
2019-09-26 22:32:15 +00:00
Info ( $"Patching {entry.To}" ) ;
2019-10-11 23:31:36 +00:00
Status ( $"Patching {entry.To}" ) ;
2019-11-21 15:51:57 +00:00
using ( var origin = byPath [ string . Join ( "|" , entry . ArchiveHashPath . Skip ( 1 ) ) ] . OpenRead ( ) )
2019-08-20 04:57:08 +00:00
using ( var output = new MemoryStream ( ) )
{
var a = origin . ReadAll ( ) ;
2019-11-22 05:19:42 +00:00
var b = LoadDataForTo ( entry . To , absolutePaths ) ;
2019-09-12 23:44:35 +00:00
Utils . CreatePatch ( a , b , output ) ;
2019-10-01 22:39:25 +00:00
entry . PatchID = IncludeFile ( output . ToArray ( ) ) ;
2019-11-21 15:51:57 +00:00
var fileSize = File . GetSize ( Path . Combine ( ModListOutputFolder , entry . PatchID ) ) ;
Info ( $"Patch size {fileSize} for {entry.To}" ) ;
2019-08-20 04:57:08 +00:00
}
} ) ;
}
2019-07-22 03:36:25 +00:00
}
2019-11-22 05:19:42 +00:00
private byte [ ] LoadDataForTo ( string to , Dictionary < string , string > absolutePaths )
2019-07-26 20:59:14 +00:00
{
2019-11-21 15:51:57 +00:00
if ( absolutePaths . TryGetValue ( to , out var absolute ) )
2019-07-26 20:59:14 +00:00
return File . ReadAllBytes ( absolute ) ;
if ( to . StartsWith ( Consts . BSACreationDir ) )
{
2019-11-21 15:51:57 +00:00
var bsaID = to . Split ( '\\' ) [ 1 ] ;
var bsa = InstallDirectives . OfType < CreateBSA > ( ) . First ( b = > b . TempID = = bsaID ) ;
2019-07-26 20:59:14 +00:00
2019-11-16 00:01:37 +00:00
using ( var a = BSADispatch . OpenRead ( Path . Combine ( MO2Folder , bsa . To ) ) )
2019-07-26 20:59:14 +00:00
{
2019-10-11 23:31:36 +00:00
var find = Path . Combine ( to . Split ( '\\' ) . Skip ( 2 ) . ToArray ( ) ) ;
var file = a . Files . First ( e = > e . Path . Replace ( '/' , '\\' ) = = find ) ;
using ( var ms = new MemoryStream ( ) )
{
2019-11-16 00:01:37 +00:00
file . CopyDataTo ( ms ) ;
2019-10-11 23:31:36 +00:00
return ms . ToArray ( ) ;
}
2019-07-26 20:59:14 +00:00
}
}
2019-09-12 23:44:35 +00:00
2019-07-26 20:59:14 +00:00
Error ( $"Couldn't load data for {to}" ) ;
return null ;
}
2019-11-03 16:43:43 +00:00
public override IEnumerable < ICompilationStep > GetStack ( )
2019-10-31 02:24:42 +00:00
{
2019-11-21 15:51:57 +00:00
var userConfig = Path . Combine ( MO2ProfileDir , "compilation_stack.yml" ) ;
if ( File . Exists ( userConfig ) )
return Serialization . Deserialize ( File . ReadAllText ( userConfig ) , this ) ;
2019-10-31 02:24:42 +00:00
var stack = MakeStack ( ) ;
File . WriteAllText ( Path . Combine ( MO2ProfileDir , "_current_compilation_stack.yml" ) ,
Serialization . Serialize ( stack ) ) ;
return stack ;
}
2019-07-21 04:40:54 +00:00
/// <summary>
2019-09-12 23:44:35 +00:00
/// Creates a execution stack. The stack should be passed into Run stack. Each function
/// in this stack will be run in-order and the first to return a non-null result will have its
/// result included into the pack
2019-07-21 04:40:54 +00:00
/// </summary>
/// <returns></returns>
2019-11-03 16:43:43 +00:00
public override IEnumerable < ICompilationStep > MakeStack ( )
2019-07-21 04:40:54 +00:00
{
2019-10-31 02:24:42 +00:00
Utils . Log ( "Generating compilation stack" ) ;
2019-10-30 12:29:06 +00:00
return new List < ICompilationStep >
{
new IncludePropertyFiles ( this ) ,
new IgnoreStartsWith ( this , "logs\\" ) ,
new IgnoreStartsWith ( this , "downloads\\" ) ,
new IgnoreStartsWith ( this , "webcache\\" ) ,
new IgnoreStartsWith ( this , "overwrite\\" ) ,
new IgnorePathContains ( this , "temporary_logs" ) ,
new IgnorePathContains ( this , "GPUCache" ) ,
new IgnorePathContains ( this , "SSEEdit Cache" ) ,
new IgnoreEndsWith ( this , ".pyc" ) ,
new IgnoreEndsWith ( this , ".log" ) ,
new IgnoreOtherProfiles ( this ) ,
new IgnoreDisabledMods ( this ) ,
new IncludeThisProfile ( this ) ,
2019-07-21 04:40:54 +00:00
// Ignore the ModOrganizer.ini file it contains info created by MO2 on startup
2019-10-30 12:29:06 +00:00
new IncludeStubbedConfigFiles ( this ) ,
new IncludeLootFiles ( this ) ,
new IgnoreStartsWith ( this , Path . Combine ( Consts . GameFolderFilesDir , "Data" ) ) ,
new IgnoreStartsWith ( this , Path . Combine ( Consts . GameFolderFilesDir , "Papyrus Compiler" ) ) ,
new IgnoreStartsWith ( this , Path . Combine ( Consts . GameFolderFilesDir , "Skyrim" ) ) ,
new IgnoreRegex ( this , Consts . GameFolderFilesDir + "\\\\.*\\.bsa" ) ,
new IncludeModIniData ( this ) ,
new DirectMatch ( this ) ,
new IncludeTaggedMods ( this , Consts . WABBAJACK_INCLUDE ) ,
new DeconstructBSAs ( this ) , // Deconstruct BSAs before building patches so we don't generate massive patch files
new IncludePatches ( this ) ,
new IncludeDummyESPs ( this ) ,
2019-07-21 12:42:29 +00:00
2019-07-26 20:59:14 +00:00
2019-07-21 12:42:29 +00:00
// If we have no match at this point for a game folder file, skip them, we can't do anything about them
2019-10-30 12:29:06 +00:00
new IgnoreGameFiles ( this ) ,
2019-07-26 20:59:14 +00:00
2019-09-26 23:08:10 +00:00
// There are some types of files that will error the compilation, because they're created on-the-fly via tools
2019-07-26 20:59:14 +00:00
// so if we don't have a match by this point, just drop them.
2019-10-30 12:29:06 +00:00
new IgnoreEndsWith ( this , ".ini" ) ,
new IgnoreEndsWith ( this , ".html" ) ,
new IgnoreEndsWith ( this , ".txt" ) ,
2019-07-26 20:59:14 +00:00
// Don't know why, but this seems to get copied around a bit
2019-10-30 12:29:06 +00:00
new IgnoreEndsWith ( this , "HavokBehaviorPostProcess.exe" ) ,
2019-08-03 17:37:32 +00:00
// Theme file MO2 downloads somehow
2019-10-30 12:29:06 +00:00
new IgnoreEndsWith ( this , "splash.png" ) ,
2019-07-23 04:27:26 +00:00
2019-10-30 12:29:06 +00:00
new IgnoreEndsWith ( this , ".bin" ) ,
new IgnoreEndsWith ( this , ".refcache" ) ,
2019-09-02 22:36:57 +00:00
2019-10-30 12:29:06 +00:00
new IgnoreWabbajackInstallCruft ( this ) ,
2019-07-21 22:47:17 +00:00
2019-10-30 12:29:06 +00:00
new PatchStockESMs ( this ) ,
2019-09-12 23:44:35 +00:00
2019-10-30 12:29:06 +00:00
new IncludeAllConfigs ( this ) ,
new zEditIntegration . IncludeZEditPatches ( this ) ,
2019-11-02 18:36:38 +00:00
new IncludeTaggedMods ( this , Consts . WABBAJACK_NOMATCH_INCLUDE ) ,
2019-09-12 23:44:35 +00:00
2019-10-30 12:29:06 +00:00
new DropAll ( this )
2019-07-21 04:40:54 +00:00
} ;
}
2019-07-22 22:17:46 +00:00
2019-09-12 23:44:35 +00:00
public class IndexedFileMatch
{
public IndexedArchive Archive ;
public IndexedArchiveEntry Entry ;
public DateTime LastModified ;
}
2019-07-21 04:40:54 +00:00
}
2019-11-20 23:39:03 +00:00
}