2019-11-03 16:43:43 +00:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2019-11-04 16:04:37 +00:00
using System.Security.Cryptography ;
2019-11-03 16:43:43 +00:00
using System.Text ;
2019-12-13 14:34:18 +00:00
using System.Threading.Tasks ;
2019-12-14 17:30:52 +00:00
using System.Threading ;
2019-11-17 16:26:04 +00:00
using Newtonsoft.Json ;
2019-11-03 16:43:43 +00:00
using Wabbajack.Common ;
2020-01-07 13:50:11 +00:00
using Wabbajack.Common.IO ;
2020-01-03 17:22:50 +00:00
using Wabbajack.Common.StoreHandlers ;
2019-11-03 16:43:43 +00:00
using Wabbajack.Lib.CompilationSteps ;
2019-11-04 16:04:37 +00:00
using Wabbajack.Lib.NexusApi ;
2019-12-17 16:25:15 +00:00
using Wabbajack.Lib.Validation ;
2019-12-14 17:30:52 +00:00
using File = Alphaleonis . Win32 . Filesystem . File ;
2019-12-17 16:25:15 +00:00
using Game = Wabbajack . Common . Game ;
2019-11-03 16:43:43 +00:00
namespace Wabbajack.Lib
{
public class VortexCompiler : ACompiler
{
2019-11-17 16:26:04 +00:00
/ * vortex creates a vortex . deployment . json file that contains information
about all deployed files , parsing that file , we can get a list of all ' active '
archives so we don ' t force the user to install all archives found in the downloads folder .
Similar to how IgnoreDisabledMods for MO2 works
* /
public VortexDeployment VortexDeployment ;
public List < string > ActiveArchives ;
2019-11-09 14:10:28 +00:00
public Game Game { get ; }
2019-11-03 16:43:43 +00:00
public string GameName { get ; }
2019-11-12 17:54:48 +00:00
public string VortexFolder { get ; set ; }
public string StagingFolder { get ; set ; }
public string DownloadsFolder { get ; set ; }
2019-11-03 16:43:43 +00:00
2019-12-14 17:30:52 +00:00
public bool IgnoreMissingFiles { get ; set ; }
2019-11-24 00:30:51 +00:00
public override ModManager ModManager = > ModManager . Vortex ;
public override string GamePath { get ; }
2019-12-17 16:25:15 +00:00
public override string ModListOutputFolder = > "output_folder" ;
2019-11-24 00:30:51 +00:00
public override string ModListOutputFile { get ; }
2019-11-17 03:09:46 +00:00
public const string StagingMarkerName = "__vortex_staging_folder" ;
public const string DownloadMarkerName = "__vortex_downloads_folder" ;
2019-12-13 15:01:26 +00:00
private bool _isSteamGame ;
private SteamGame _steamGame ;
private bool _hasSteamWorkshopItems ;
2019-12-14 17:30:52 +00:00
public VortexCompiler ( Game game , string gamePath , string vortexFolder , string downloadsFolder , string stagingFolder , string outputFile )
2019-11-03 16:43:43 +00:00
{
2019-11-17 13:51:28 +00:00
Game = game ;
2019-12-14 17:30:52 +00:00
2019-11-03 16:43:43 +00:00
GamePath = gamePath ;
2019-12-14 17:30:52 +00:00
GameName = game . MetaData ( ) . NexusName ;
2019-11-17 13:51:28 +00:00
VortexFolder = vortexFolder ;
DownloadsFolder = downloadsFolder ;
StagingFolder = stagingFolder ;
2019-11-24 00:30:51 +00:00
ModListOutputFile = outputFile ;
2019-11-17 16:26:04 +00:00
2019-12-17 16:25:15 +00:00
if ( string . IsNullOrEmpty ( ModListName ) )
{
ModListName = $"Vortex ModList for {Game.ToString()}" ;
ModListOutputFile = $"{ModListName}{ExtensionManager.Extension}" ;
}
GameName = Game . MetaData ( ) . NexusName ;
ActiveArchives = new List < string > ( ) ;
2019-12-14 17:30:52 +00:00
// there can be max one game after filtering
2020-01-03 17:22:50 +00:00
StoreHandler . Instance . StoreGames . Where ( g = > g . Game = = game & & g . Type = = StoreType . STEAM ) . Do ( g = >
2019-12-13 15:01:26 +00:00
{
_isSteamGame = true ;
2020-01-03 17:22:50 +00:00
_steamGame = ( SteamGame ) g ;
2019-12-13 15:01:26 +00:00
_hasSteamWorkshopItems = _steamGame . WorkshopItems . Count > 0 ;
} ) ;
2019-12-14 17:30:52 +00:00
}
2019-12-04 01:26:26 +00:00
protected override async Task < bool > _Begin ( CancellationToken cancel )
2019-11-03 16:43:43 +00:00
{
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-17 16:25:15 +00:00
Info ( $"Starting Vortex compilation for {GameName} at {GamePath} with staging folder at {StagingFolder} and downloads folder at {DownloadsFolder}." ) ;
2019-11-17 14:30:06 +00:00
2019-12-17 16:25:15 +00:00
ConfigureProcessor ( 12 ) ;
UpdateTracker . Reset ( ) ;
2019-11-03 16:43:43 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Parsing deployment file" ) ;
2019-11-17 16:26:04 +00:00
ParseDeploymentFile ( ) ;
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Creating metas for archives" ) ;
2019-12-07 02:45:13 +00:00
await CreateMetaFiles ( ) ;
2019-11-04 16:04:37 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-17 16:25:15 +00:00
await VFS . IntegrateFromFile ( _vfsCacheName ) ;
2019-11-03 16:43:43 +00:00
2019-12-17 16:25:15 +00:00
var roots = new List < string > { StagingFolder , GamePath , DownloadsFolder } ;
AddExternalFolder ( ref roots ) ;
2019-11-09 20:35:08 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Indexing folders" ) ;
await VFS . AddRoots ( roots ) ;
await VFS . WriteToFile ( _vfsCacheName ) ;
2019-12-13 14:34:18 +00:00
2019-12-14 17:30:52 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Cleaning output folder" ) ;
if ( Directory . Exists ( ModListOutputFolder ) )
Utils . DeleteDirectory ( ModListOutputFolder ) ;
2019-11-03 16:43:43 +00:00
Directory . CreateDirectory ( ModListOutputFolder ) ;
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Finding Install Files" ) ;
2019-12-01 14:59:08 +00:00
var vortexStagingFiles = Directory . EnumerateFiles ( StagingFolder , "*" , SearchOption . AllDirectories )
2019-12-18 14:25:43 +00:00
. Where ( p = > p . FileExists ( ) & & p ! = StagingMarkerName & & ! p . Contains ( Consts . ManualGameFilesDir ) )
2019-12-22 18:54:49 +00:00
. Select ( p = > new RawSourceFile ( VFS . Index . ByRootPath [ p ] , p . RelativeTo ( StagingFolder ) ) ) ;
2019-11-04 16:04:37 +00:00
2019-12-01 14:59:08 +00:00
var vortexDownloads = Directory . EnumerateFiles ( DownloadsFolder , "*" , SearchOption . AllDirectories )
2019-12-17 16:25:15 +00:00
. Where ( p = > p . FileExists ( ) & & p ! = DownloadMarkerName )
2019-12-22 18:54:49 +00:00
. Select ( p = > new RawSourceFile ( VFS . Index . ByRootPath [ p ] , p . RelativeTo ( DownloadsFolder ) ) ) ;
2019-11-03 16:43:43 +00:00
2019-12-01 14:59:08 +00:00
var gameFiles = Directory . EnumerateFiles ( GamePath , "*" , SearchOption . AllDirectories )
2019-11-03 16:43:43 +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 . GameFolderFilesDir , p . RelativeTo ( GamePath ) ) ) ) ;
2019-11-03 16:43:43 +00:00
Info ( "Indexing Archives" ) ;
IndexedArchives = Directory . EnumerateFiles ( DownloadsFolder )
2019-11-15 13:06:34 +00:00
. Where ( f = > File . Exists ( f + ".meta" ) )
2019-11-03 16:43:43 +00:00
. Select ( f = > new IndexedArchive
{
2019-11-15 13:06:34 +00:00
File = VFS . Index . ByRootPath [ f ] ,
2019-11-04 12:30:02 +00:00
Name = Path . GetFileName ( f ) ,
2019-11-15 13:06:34 +00:00
IniData = ( f + ".meta" ) . LoadIniFile ( ) ,
Meta = File . ReadAllText ( f + ".meta" )
2019-11-03 16:43:43 +00:00
} )
. ToList ( ) ;
Info ( "Indexing Files" ) ;
2019-11-15 13:06:34 +00:00
IndexedFiles = IndexedArchives . SelectMany ( f = > f . File . ThisAndAllChildren )
. OrderBy ( f = > f . NestingFactor )
2019-11-03 16:43:43 +00:00
. GroupBy ( f = > f . Hash )
. ToDictionary ( f = > f . Key , f = > f . AsEnumerable ( ) ) ;
2019-11-04 16:04:37 +00:00
AllFiles = vortexStagingFiles . Concat ( vortexDownloads )
. Concat ( gameFiles )
. DistinctBy ( f = > f . Path )
. ToList ( ) ;
2019-11-03 16:43:43 +00:00
Info ( $"Found {AllFiles.Count} files to build into mod list" ) ;
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Verifying destinations" ) ;
2019-12-01 14:59:08 +00:00
var duplicates = AllFiles . GroupBy ( f = > f . Path )
2019-11-03 16:43:43 +00:00
. Where ( fs = > fs . Count ( ) > 1 )
. Select ( fs = >
{
2019-11-04 12:30:02 +00:00
Utils . Log ( $"Duplicate files installed to {fs.Key} from : {string.Join(" , ", fs.Select(f => f.AbsolutePath))}" ) ;
2019-11-03 16:43:43 +00:00
return fs ;
} ) . ToList ( ) ;
2019-12-01 14:59:08 +00:00
if ( duplicates . Count > 0 )
2019-11-03 16:43:43 +00:00
{
2019-12-01 14:59:08 +00:00
Error ( $"Found {duplicates.Count} duplicates, exiting" ) ;
2019-11-03 16:43:43 +00:00
}
2019-12-14 11:10:22 +00:00
for ( var i = 0 ; i < AllFiles . Count ; i + + )
{
var f = AllFiles [ i ] ;
if ( ! f . Path . StartsWith ( Consts . GameFolderFilesDir ) | | ! IndexedFiles . ContainsKey ( f . Hash ) )
continue ;
if ( ! IndexedFiles . TryGetValue ( f . Hash , out var value ) )
continue ;
var element = value . ElementAt ( 0 ) ;
if ( ! f . Path . Contains ( element . Name ) )
continue ;
IndexedArchive targetArchive = null ;
2019-12-15 11:44:20 +00:00
IndexedArchives . Where ( a = > a . File . ThisAndAllChildren . Contains ( element ) ) . Do ( a = > targetArchive = a ) ;
2019-12-14 11:10:22 +00:00
if ( targetArchive = = null )
continue ;
if ( targetArchive . IniData ? . General ? . tag = = null | | targetArchive . IniData ? . General ? . tag ! = Consts . WABBAJACK_VORTEX_MANUAL )
continue ;
2019-12-14 17:30:52 +00:00
#if DEBUG
2019-12-14 11:10:22 +00:00
Utils . Log ( $"Double hash for: {f.AbsolutePath}" ) ;
2019-12-14 17:30:52 +00:00
#endif
2019-12-14 11:10:22 +00:00
var replace = f ;
var name = replace . File . Name ;
var archiveName = targetArchive . Name ;
2019-12-15 11:44:20 +00:00
var elementPath = element . FullPath . Substring ( element . FullPath . LastIndexOf ( '|' ) + 1 ) ;
2019-12-14 11:10:22 +00:00
var gameToFile = name . Substring ( GamePath . Length + 1 ) . Replace ( elementPath , "" ) ;
if ( gameToFile . EndsWith ( "\\" ) )
gameToFile = gameToFile . Substring ( 0 , gameToFile . Length - 1 ) ;
//replace.Path = replace.Path.Replace(Consts.GameFolderFilesDir, Consts.ManualGameFilesDir);
replace . Path = Path . Combine ( Consts . ManualGameFilesDir , archiveName , gameToFile , elementPath ) ;
//replace.Path = Path.Combine(Consts.ManualGameFilesDir, element.FullPath.Substring(DownloadsFolder.Length + 1).Replace('|', '\\'));
AllFiles . RemoveAt ( i ) ;
AllFiles . Insert ( i , replace ) ;
//AllFiles.Replace(f, replace);
}
2019-12-01 14:59:08 +00:00
var stack = MakeStack ( ) ;
2019-12-14 17:30:52 +00:00
Info ( "Running Compilation Stack" ) ;
2019-12-13 15:01:26 +00:00
var results = await AllFiles . PMap ( Queue , f = > RunStack ( stack . Where ( s = > s ! = null ) , f ) ) ;
2019-11-03 16:43:43 +00:00
IEnumerable < NoMatch > noMatch = results . OfType < NoMatch > ( ) . ToList ( ) ;
Info ( $"No match for {noMatch.Count()} files" ) ;
foreach ( var file in noMatch )
2020-01-04 18:18:26 +00:00
Info ( $" {file.To} - {file.Reason}" ) ;
2019-11-03 16:43:43 +00:00
if ( noMatch . Any ( ) )
{
if ( IgnoreMissingFiles )
{
Info ( "Continuing even though files were missing at the request of the user." ) ;
}
else
{
Info ( "Exiting due to no way to compile these files" ) ;
return false ;
}
}
InstallDirectives = results . Where ( i = > ! ( i is IgnoredDirectly ) ) . ToList ( ) ;
2019-12-17 16:25:15 +00:00
Info ( "Getting Nexus api_key, please click authorize if a browser window appears" ) ;
2019-11-03 16:43:43 +00:00
if ( IndexedArchives . Any ( a = > a . IniData ? . General ? . gameName ! = null ) )
{
2019-12-17 16:25:15 +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-11-03 16:43:43 +00:00
}
2019-12-17 16:25:15 +00:00
2019-11-03 16:43:43 +00:00
2019-12-03 21:56:18 +00:00
if ( cancel . IsCancellationRequested ) return false ;
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Gathering Archives" ) ;
2019-12-04 01:26:26 +00:00
await GatherArchives ( ) ;
2019-11-03 16:43:43 +00:00
ModList = new ModList
{
2019-11-17 14:54:04 +00:00
Name = ModListName ? ? "" ,
2019-11-17 14:30:06 +00:00
Author = ModListAuthor ? ? "" ,
Description = ModListDescription ? ? "" ,
Readme = ModListReadme ? ? "" ,
Image = ModListImage ? ? "" ,
Website = ModListWebsite ? ? "" ,
2019-12-03 21:13:29 +00:00
Archives = SelectedArchives . ToList ( ) ,
2019-11-03 16:43:43 +00:00
ModManager = ModManager . Vortex ,
2019-11-09 15:10:31 +00:00
Directives = InstallDirectives ,
GameType = Game
2019-11-03 16:43:43 +00:00
} ;
2019-12-14 17:30:52 +00:00
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Running Validation" ) ;
await ValidateModlist . RunValidation ( Queue , ModList ) ;
UpdateTracker . NextStep ( "Generating Report" ) ;
2019-11-17 14:54:04 +00:00
GenerateReport ( ) ;
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Exporting ModList" ) ;
2019-11-03 16:43:43 +00:00
ExportModList ( ) ;
2019-12-17 16:25:15 +00:00
ResetMembers ( ) ;
2019-11-17 14:54:04 +00:00
ShowReport ( ) ;
2019-12-17 16:25:15 +00:00
UpdateTracker . NextStep ( "Done Building ModList" ) ;
2019-11-03 16:43:43 +00:00
return true ;
}
2019-12-17 16:25:15 +00:00
/// <summary>
/// Clear references to lists that hold a lot of data.
/// </summary>
private void ResetMembers ( )
{
AllFiles = null ;
InstallDirectives = null ;
SelectedArchives = null ;
}
private void AddExternalFolder ( ref List < string > roots )
{
var currentGame = Game . MetaData ( ) ;
if ( currentGame . AdditionalFolders = = null | | currentGame . AdditionalFolders . Count = = 0 ) return ;
foreach ( var path in currentGame . AdditionalFolders . Select ( f = > f . Replace ( "%documents%" , KnownFolders . Documents . Path ) ) )
{
if ( ! Directory . Exists ( path ) ) return ;
roots . Add ( path ) ;
}
}
2019-11-17 16:26:04 +00:00
private void ParseDeploymentFile ( )
{
Info ( "Searching for vortex.deployment.json..." ) ;
var deploymentFile = "" ;
Directory . EnumerateFiles ( GamePath , "vortex.deployment.json" , SearchOption . AllDirectories )
. Where ( File . Exists )
. Do ( f = > deploymentFile = f ) ;
2019-12-09 22:46:03 +00:00
var currentGame = Game . MetaData ( ) ;
2019-11-17 16:26:04 +00:00
if ( currentGame . AdditionalFolders ! = null & & currentGame . AdditionalFolders . Count ! = 0 )
currentGame . AdditionalFolders . Do ( f = > Directory . EnumerateFiles ( f , "vortex.deployment.json" , SearchOption . AllDirectories )
. Where ( File . Exists )
. Do ( d = > deploymentFile = d ) ) ;
if ( string . IsNullOrEmpty ( deploymentFile ) )
{
2019-12-17 16:25:15 +00:00
Error ( "vortex.deployment.json not found!" ) ;
2019-11-17 16:26:04 +00:00
return ;
}
2019-12-17 16:25:15 +00:00
Info ( $"vortex.deployment.json found at {deploymentFile}" ) ;
2019-11-17 16:26:04 +00:00
Info ( "Parsing vortex.deployment.json..." ) ;
try
{
VortexDeployment = deploymentFile . FromJSON < VortexDeployment > ( ) ;
}
catch ( JsonSerializationException e )
{
2019-12-05 05:07:44 +00:00
Utils . Error ( e , "Failed to parse vortex.deployment.json!" ) ;
2019-11-17 16:26:04 +00:00
}
VortexDeployment . files . Do ( f = >
{
var archive = f . source ;
2019-12-04 12:36:56 +00:00
if ( ActiveArchives . Contains ( archive ) )
return ;
Utils . Log ( $"Adding Archive {archive} to ActiveArchives" ) ;
ActiveArchives . Add ( archive ) ;
2019-11-17 16:26:04 +00:00
} ) ;
}
2019-12-07 02:45:13 +00:00
private async Task CreateMetaFiles ( )
2019-11-04 16:04:37 +00:00
{
2019-11-11 13:03:48 +00:00
Utils . Log ( "Getting Nexus api_key, please click authorize if a browser window appears" ) ;
2019-12-07 03:50:50 +00:00
var nexusClient = await NexusApiClient . Get ( ) ;
2019-11-11 13:03:48 +00:00
2019-12-17 16:25:15 +00:00
var archives = Directory . EnumerateFiles ( DownloadsFolder , "*" , SearchOption . TopDirectoryOnly ) . Where ( f = >
File . Exists ( f ) & & Path . GetExtension ( f ) ! = ".meta" & & Path . GetExtension ( f ) ! = ".xxHash" & &
! File . Exists ( $"{f}.meta" ) & & ActiveArchives . Contains ( Path . GetFileNameWithoutExtension ( f ) ) ) ;
await archives . PMap ( Queue , async f = >
{
Info ( $"Creating meta file for {Path.GetFileName(f)}" ) ;
var metaString = "[General]\n" +
"repository=Nexus\n" +
$"gameName={GameName}\n" ;
string hash ;
using ( var md5 = MD5 . Create ( ) )
using ( var stream = File . OpenRead ( f ) )
2019-12-14 11:10:22 +00:00
{
2019-12-17 16:25:15 +00:00
Utils . Log ( $"Calculating hash for {Path.GetFileName(f)}" ) ;
var cH = md5 . ComputeHash ( stream ) ;
hash = BitConverter . ToString ( cH ) . Replace ( "-" , "" ) . ToLowerInvariant ( ) ;
Utils . Log ( $"Hash is {hash}" ) ;
}
2019-12-14 11:10:22 +00:00
2019-12-17 16:25:15 +00:00
var md5Response = await nexusClient . GetModInfoFromMD5 ( Game , hash ) ;
if ( md5Response . Count > = 1 )
{
var modInfo = md5Response [ 0 ] . mod ;
metaString + = $"modID={modInfo.mod_id}\n" +
$"modName={modInfo.name}\n" +
$"fileID={md5Response[0].file_details.file_id}\n" +
$"version={md5Response[0].file_details.version}\n" +
$"hash={hash}\n" ;
File . WriteAllText ( f + ".meta" , metaString , Encoding . UTF8 ) ;
}
else
{
Error ( "Error while getting information from NexusMods via MD5 hash!" ) ;
}
} ) ;
var otherFiles = Directory . EnumerateFiles ( DownloadsFolder , "*" , SearchOption . TopDirectoryOnly ) . Where ( f = >
Path . GetExtension ( f ) = = ".meta" & & ! ActiveArchives . Contains ( Path . GetFileNameWithoutExtension ( f ) ) ) ;
await otherFiles . PMap ( Queue , async f = >
{
Info ( $"File {f} is not in ActiveArchives" ) ;
var lines = File . ReadAllLines ( f ) ;
if ( lines . Length = = 0 | | ! lines . Any ( line = > lines . Contains ( "directURL=" ) ) )
{
if ( lines . Length = = 0 )
return ;
lines . Do ( line = >
{
var tag = "" ;
if ( line . Contains ( "tag=" ) )
tag = line . Substring ( "tag=" . Length ) ;
2019-12-15 11:27:00 +00:00
2019-12-17 16:25:15 +00:00
if ( tag ! = Consts . WABBAJACK_VORTEX_MANUAL )
2019-12-15 11:27:00 +00:00
return ;
2019-12-17 16:25:15 +00:00
Info ( $"File {f} contains the {Consts.WABBAJACK_VORTEX_MANUAL} tag, adding to ActiveArchives" ) ;
ActiveArchives . Add ( Path . GetFileNameWithoutExtension ( f ) ) ;
} ) ;
}
else
{
Info ( $"File {f} appears to not be from the Nexus, adding to ActiveArchives" ) ;
ActiveArchives . Add ( Path . GetFileNameWithoutExtension ( f ) ) ;
}
} ) ;
Info ( "Checking for Steam Workshop Items..." ) ;
if ( ! _isSteamGame | | _steamGame = = null | | ! _hasSteamWorkshopItems )
2019-12-13 15:01:26 +00:00
return ;
_steamGame . WorkshopItems . Do ( item = >
{
var filePath = Path . Combine ( DownloadsFolder , $"steamWorkshopItem_{item.ItemID}.meta" ) ;
if ( File . Exists ( filePath ) )
{
Utils . Log ( $"File {filePath} already exists, skipping..." ) ;
return ;
}
Utils . Log ( $"Creating meta file for {item.ItemID}" ) ;
var metaString = "[General]\n" +
"repository=Steam\n" +
$"gameName={GameName}\n" +
2020-01-03 17:22:50 +00:00
$"steamID={_steamGame.ID}\n" +
2019-12-13 15:01:26 +00:00
$"itemID={item.ItemID}\n" +
$"itemSize={item.Size}\n" ;
try
{
File . WriteAllText ( filePath , metaString ) ;
}
catch ( Exception e )
{
Utils . Error ( e , $"Exception while writing to disk at {filePath}" ) ;
}
} ) ;
2019-11-04 16:04:37 +00:00
}
2019-11-03 16:43:43 +00:00
public override IEnumerable < ICompilationStep > GetStack ( )
{
2019-11-14 19:20:08 +00:00
var s = Consts . TestMode ? DownloadsFolder : VortexFolder ;
var userConfig = Path . Combine ( s , "compilation_stack.yml" ) ;
2019-11-03 16:43:43 +00:00
if ( File . Exists ( userConfig ) )
return Serialization . Deserialize ( File . ReadAllText ( userConfig ) , this ) ;
2019-12-01 14:59:08 +00:00
var stack = MakeStack ( ) ;
2019-11-03 16:43:43 +00:00
2019-12-17 16:25:15 +00:00
var compilationSteps = stack . ToList ( ) ;
2019-12-14 17:30:52 +00:00
File . WriteAllText ( Path . Combine ( s , "_current_compilation_stack.yml" ) ,
2019-12-17 16:25:15 +00:00
Serialization . Serialize ( compilationSteps ) ) ;
2019-11-03 16:43:43 +00:00
2019-12-17 16:25:15 +00:00
return compilationSteps ;
2019-11-03 16:43:43 +00:00
}
public override IEnumerable < ICompilationStep > MakeStack ( )
{
2019-12-17 16:25:15 +00:00
Info ( "Generating compilation stack" ) ;
2019-11-03 16:43:43 +00:00
return new List < ICompilationStep >
{
2019-11-17 14:30:06 +00:00
new IncludePropertyFiles ( this ) ,
2019-12-18 14:25:43 +00:00
new IncludeVortexDeployment ( this ) ,
2019-12-17 16:25:15 +00:00
2019-12-13 15:01:26 +00:00
new IncludeSteamWorkshopItems ( this , _steamGame ) ,
_hasSteamWorkshopItems ? new IncludeRegex ( this , "^steamWorkshopItem_\\d*\\.meta$" ) : null ,
2019-12-17 16:25:15 +00:00
2019-11-17 16:26:04 +00:00
new IgnoreDisabledVortexMods ( this ) ,
2019-11-11 13:03:48 +00:00
new IgnoreVortex ( this ) ,
2019-12-03 02:38:33 +00:00
new IgnoreRegex ( this , $"^*{StagingMarkerName}$" ) ,
2019-11-09 18:57:29 +00:00
Game = = Game . DarkestDungeon ? new IncludeRegex ( this , "project\\.xml$" ) : null ,
2019-11-17 03:09:46 +00:00
new IgnoreStartsWith ( this , StagingFolder ) ,
new IgnoreEndsWith ( this , StagingFolder ) ,
2019-11-03 16:43:43 +00:00
2019-11-04 12:47:41 +00:00
new IgnoreGameFiles ( this ) ,
2019-11-03 16:43:43 +00:00
new DirectMatch ( this ) ,
new IgnoreGameFiles ( this ) ,
new IgnoreWabbajackInstallCruft ( this ) ,
new DropAll ( this )
} ;
}
2019-11-16 22:10:13 +00:00
public static string TypicalVortexFolder ( )
{
return Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , "Vortex" ) ;
}
2019-11-17 03:09:46 +00:00
public static string RetrieveDownloadLocation ( Game game , string vortexFolderPath = null )
2019-11-16 22:10:13 +00:00
{
vortexFolderPath = vortexFolderPath ? ? TypicalVortexFolder ( ) ;
2019-12-09 22:46:03 +00:00
return Path . Combine ( vortexFolderPath , "downloads" , game . MetaData ( ) . NexusName ) ;
2019-11-16 22:10:13 +00:00
}
2019-11-17 03:09:46 +00:00
public static string RetrieveStagingLocation ( Game game , string vortexFolderPath = null )
2019-11-16 22:10:13 +00:00
{
2019-11-17 03:09:46 +00:00
vortexFolderPath = vortexFolderPath ? ? TypicalVortexFolder ( ) ;
2019-12-09 22:46:03 +00:00
var gameName = game . MetaData ( ) . NexusName ;
2019-11-17 03:09:46 +00:00
return Path . Combine ( vortexFolderPath , gameName , "mods" ) ;
2019-11-16 22:10:13 +00:00
}
2019-11-17 03:09:46 +00:00
public static IErrorResponse IsValidBaseDownloadsFolder ( string path )
2019-11-16 22:10:13 +00:00
{
2019-11-17 03:09:46 +00:00
if ( ! Directory . Exists ( path ) ) return ErrorResponse . Fail ( $"Path does not exist: {path}" ) ;
if ( Directory . EnumerateFiles ( path , DownloadMarkerName , SearchOption . TopDirectoryOnly ) . Any ( ) ) return ErrorResponse . Success ;
return ErrorResponse . Fail ( $"Folder must contain {DownloadMarkerName} file" ) ;
2019-11-16 22:10:13 +00:00
}
2019-11-17 03:09:46 +00:00
public static IErrorResponse IsValidDownloadsFolder ( string path )
2019-11-16 22:10:13 +00:00
{
2019-11-17 03:09:46 +00:00
return IsValidBaseDownloadsFolder ( Path . GetDirectoryName ( path ) ) ;
}
public static IErrorResponse IsValidBaseStagingFolder ( string path )
{
if ( ! Directory . Exists ( path ) ) return ErrorResponse . Fail ( $"Path does not exist: {path}" ) ;
if ( Directory . EnumerateFiles ( path , StagingMarkerName , SearchOption . TopDirectoryOnly ) . Any ( ) ) return ErrorResponse . Success ;
return ErrorResponse . Fail ( $"Folder must contain {StagingMarkerName} file" ) ;
}
public static IErrorResponse IsValidStagingFolder ( string path )
{
return IsValidBaseStagingFolder ( Path . GetDirectoryName ( path ) ) ;
2019-11-16 22:10:13 +00:00
}
2019-12-03 02:38:33 +00:00
public static bool IsActiveVortexGame ( Game g )
{
2019-12-09 22:46:03 +00:00
return g . MetaData ( ) . SupportedModManager = = ModManager . Vortex & & ! GameRegistry . Games [ g ] . Disabled ;
2019-12-03 02:38:33 +00:00
}
2019-11-03 16:43:43 +00:00
}
2019-11-17 16:26:04 +00:00
public class VortexDeployment
{
public string instance ;
public int version ;
public string deploymentMethod ;
public List < VortexFile > files ;
}
public class VortexFile
{
public string relPath ;
public string source ;
public string target ;
}
2019-11-03 16:43:43 +00:00
}