Skip to content

Commit

Permalink
Add project files.
Browse files Browse the repository at this point in the history
  • Loading branch information
MWR committed Jun 20, 2021
1 parent 205a997 commit e77b221
Show file tree
Hide file tree
Showing 19 changed files with 1,315 additions and 0 deletions.
49 changes: 49 additions & 0 deletions Commands/ScriptReinstaller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

using DTInstaller.Utils;
using static DTInstaller.Utils.Logger;
using static DTInstaller.Utils.Constants;

namespace DTInstaller.Commands
{
class ScriptReinstaller : ICommand
{
public static (string name, string purpose, string alias) CommandDetails { get; } =
("Reinstall", "reinstall the script", "r");
public (string name, string purpose, string alias) CommandDetailsInstance { get; } = CommandDetails;

public async Task<bool> Execute()
{
Log(LogVariant.Information, Texts.reinstallingMessage);

string archiveDirectoryPath = ArchiveDirectoryPathAssembler.AssembleArchiveDirectoryPath();
if (archiveDirectoryPath == null) return false;

string mainScreenFilePath = Path.Combine(archiveDirectoryPath, Paths.mainScreenFileWithinArchivePath);
string sourceCodeCoreArchivePath = Path.Combine(archiveDirectoryPath, FileNames.archiveFileName);
string archiveUnpackedDirectoryPath = Path.Combine(archiveDirectoryPath, FileNames.unpackedDirectoryName);

Log(LogVariant.Information, Texts.scriptFetchingMessage);
JsonScriptData scriptCode = await UpdatesManager.GetScript();
if (scriptCode == null) return false;

Log(LogVariant.Information, Texts.injectingProcessMessage);
Log(LogVariant.Warning, Texts.dontReopenClientMessage);
ScriptInjector scriptInjector = new(
targetFilePath: mainScreenFilePath,
archiveFilePath: sourceCodeCoreArchivePath,
archiveOutputDirectoryPath: archiveUnpackedDirectoryPath,
scriptCode: Encoding.UTF8.GetString(Convert.FromBase64String(scriptCode.content))
);

bool hasInjected = scriptInjector.Inject();
if (!hasInjected) return false;

Log(LogVariant.Success, Texts.reinstallationSuccessMessage);
return true;
}
}
}
64 changes: 64 additions & 0 deletions Commands/UpdateInstaller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

using DTInstaller.Utils;
using static DTInstaller.Utils.Logger;
using static DTInstaller.Utils.Methods;
using static DTInstaller.Utils.Constants;

namespace DTInstaller.Commands
{
class UpdateInstaller : ICommand
{
public static (string name, string purpose, string alias) CommandDetails { get; } =
("Install", "install the update, if there's one", "i");
public (string name, string purpose, string alias) CommandDetailsInstance { get; } = CommandDetails;

public async Task<bool> Execute()
{
Log(LogVariant.Information, Texts.installingMessage);

// We check again for an update because otherwise, the user could proceed to install an update
// when there isn't one to begin with.
var (isNewUpdateAvailable, didError) = await UpdatesManager.CheckForUpdates();
if (didError) return false;

if (!isNewUpdateAvailable)
{
Log(LogVariant.Information, Texts.noUpdatesMessage);
return true;
}

bool didUpdateLocalScriptData = LocalScriptDataManager
.UpdateLocalScriptData(UpdatesManager.FetchedScriptData);
// While this may not stop the installation of the update itself, when the user wants to reinstall
// the script, they'll actually install an old update, so rather than doing that, just fail.
if (!didUpdateLocalScriptData) return false;

string archiveDirectoryPath = ArchiveDirectoryPathAssembler.AssembleArchiveDirectoryPath();
if (archiveDirectoryPath == null) return false;

string mainScreenFilePath = Path.Combine(archiveDirectoryPath, Paths.mainScreenFileWithinArchivePath);
string sourceCodeCoreArchivePath = Path.Combine(archiveDirectoryPath, FileNames.archiveFileName);
string archiveUnpackedDirectory = Path.Combine(archiveDirectoryPath, FileNames.unpackedDirectoryName);

Log(LogVariant.Information, Texts.injectingProcessMessage);
Log(LogVariant.Warning, Texts.dontReopenClientMessage);
ScriptInjector scriptInjector = new(
targetFilePath: mainScreenFilePath,
archiveFilePath: sourceCodeCoreArchivePath,
archiveOutputDirectoryPath: archiveUnpackedDirectory,
scriptCode: Encoding.UTF8.GetString(Convert.FromBase64String(UpdatesManager.FetchedScriptData.content))
);

bool hasInjected = scriptInjector.Inject();
if (!hasInjected) return false;

Log(LogVariant.Success, Texts.installationSuccessMessage);
return true;
}

}
}
20 changes: 20 additions & 0 deletions DTInstaller.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ApplicationIcon>dt-icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="AsarSharp">
<HintPath>..\AsarSharp\bin\Release\net5.0\AsarSharp.dll</HintPath>
</Reference>
</ItemGroup>

</Project>
25 changes: 25 additions & 0 deletions DTInstaller.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31129.286
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTInstaller", "DTInstaller.csproj", "{F13AB7E2-B656-4E47-9E71-DA1FC288874F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F13AB7E2-B656-4E47-9E71-DA1FC288874F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F13AB7E2-B656-4E47-9E71-DA1FC288874F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F13AB7E2-B656-4E47-9E71-DA1FC288874F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F13AB7E2-B656-4E47-9E71-DA1FC288874F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F586180-2936-444E-A987-E8CA606A6878}
EndGlobalSection
EndGlobal
78 changes: 78 additions & 0 deletions Managers/LocalScriptDataManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.IO;
using System.Text.Json;
using DTInstaller.Utils;
using static DTInstaller.Utils.Logger;
using static DTInstaller.Utils.Constants;

namespace DTInstaller
{
static class LocalScriptDataManager
{
/**
* <summary>Gets the script data from the local script data file, instead of the repository.</summary>
* <returns>The script data object.</returns>
*/
public static JsonScriptData GetLocalScriptData()
{
FileLoader fileLoader = new(path: Paths.localScriptDataFilePath);
string localScriptData = fileLoader.ReadText();

if (localScriptData == null) return null;

try
{
return JsonSerializer.Deserialize<JsonScriptData>(localScriptData);
}
catch
{
Log(LogVariant.Warning, Texts.localScriptDataInvalidMessage);
return null;
}
}

/**
* <summary>Updates the script data file in case a script change has occured.</summary>
* <param name="scriptData">The script data to update the file with.</param>
* <returns>True if the update has been successful, and false otherwise.</returns>
*/
public static bool UpdateLocalScriptData(JsonScriptData scriptData)
{
return new FileLoader(path: Paths.localScriptDataFilePath)
.WriteText(JsonSerializer.Serialize(scriptData));
}

/**
* <summary>
* Creates the local script data directory, in which we also create the local script data file.
* </summary>
* <param name="fileContents">The script data object to be written to the local script data file.</param>
* <returns>True if the creation of the directory and file was successful, and false otherwise.</returns>
*/
public static bool CreateLocalScriptData(JsonScriptData fileContents)
{
if (!Directory.Exists(Paths.localScriptDataDirPath))
{
try
{
Directory.CreateDirectory(Paths.localScriptDataDirPath);
}
catch (Exception error)
{
Log(
LogVariant.Error,
$"There's been an error trying to create a directory for " +
$"holding data for this installer: {error.Message}"
);
DebugLog(LogVariant.Error, error.ToString());
return false;
}
}

FileLoader fileLoader = new(path: Paths.localScriptDataFilePath);
bool hasWritten = fileLoader.WriteText(JsonSerializer.Serialize(fileContents));

return hasWritten;
}
}
}
110 changes: 110 additions & 0 deletions Managers/UpdatesManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

using static DTInstaller.Utils.Logger;
using static DTInstaller.Utils.Constants;

namespace DTInstaller.Utils
{
static class UpdatesManager
{
private static readonly HttpClient _client = new();
// We use a nullable bool so we can differentiate between unset and error (null/false).
private static bool? _isNewUpdateAvailable = null;
public static JsonScriptData FetchedScriptData { get; private set; } = null;

/**
* <summary>Checks for updates by pulling data from the repository.</summary>
* <param name="force">
* Forces to ping the GitHub servers for a new update, rather than getting the update availability
* from an internal field. In other words, it "invalidates the cache".
* </param>
* <returns>A tuple that holds the update availability, and error presences.</returns>
*/
public static async Task<(bool isNewUpdateAvailable, bool didError)> CheckForUpdates(bool force = false)
{
if (_isNewUpdateAvailable != null && !force)
return ((bool)_isNewUpdateAvailable, didError: false);

FetchedScriptData = await GetRepositoryScript();
if (FetchedScriptData == null) return (isNewUpdateAvailable: false, didError: true);

JsonScriptData localScriptData = LocalScriptDataManager.GetLocalScriptData();

// If true, it might be that it's the first time they get the installer,
// or they removed the local script directory, or the file inside.
if (localScriptData == null)
{
DebugLog(LogVariant.Information,
$"({nameof(CheckForUpdates)}) Local data directory is not defined. Creating...");

bool didCreateScriptDataPlace = LocalScriptDataManager.CreateLocalScriptData(fileContents: FetchedScriptData);
if (!didCreateScriptDataPlace)
return (isNewUpdateAvailable: true, didError: true);

// Assuming it's the first time they get the installer, say there's an update.
_isNewUpdateAvailable = true;

return (isNewUpdateAvailable: true, didError: false);
}

_isNewUpdateAvailable = localScriptData.sha != FetchedScriptData.sha;

return ((bool)_isNewUpdateAvailable, didError: false);
}

/**
* <summary>
* Gets the script from the local script data file. If said file doesn't exist,
* it gets the script data from the repository, and creates a new script data file.
* </summary>
* <returns>The script data object.</returns>
*/
public static async Task<JsonScriptData> GetScript()
{
JsonScriptData localScriptData = LocalScriptDataManager.GetLocalScriptData();
if (localScriptData == null)
{
DebugLog(LogVariant.Information, $"({nameof(GetScript)}) Local data directory is not defined. Pulling script from repo...");
FetchedScriptData = await GetRepositoryScript();
if (FetchedScriptData == null) return null;

DebugLog(LogVariant.Information, $"({nameof(GetScript)}) Creating local script data place...");
bool didCreateScriptDataPlace = LocalScriptDataManager.CreateLocalScriptData(fileContents: FetchedScriptData);
if (!didCreateScriptDataPlace) return null;

return FetchedScriptData;
}

return localScriptData;
}

/**
* <summary>Makes a request to the servers where the script is hosted, and gets it.</summary>
* <returns>The script data object.</returns>
*/
public static async Task<JsonScriptData> GetRepositoryScript()
{
try
{
HttpRequestMessage requestMessage = new(HttpMethod.Get, URLs.scriptFileURL);

// GitHub (the current host) requires an "User-Agent" header.
requestMessage.Headers.Add("User-Agent", Miscellaneous.installerHeaderRequest);

var response = await _client.SendAsync(requestMessage);
FetchedScriptData = JsonSerializer.Deserialize<JsonScriptData>(await response.Content.ReadAsStringAsync());

return FetchedScriptData;
}
catch (Exception error)
{
Log(LogVariant.Error, $"There's been an error fetching the script data: {error.Message}");
DebugLog(LogVariant.Error, error.ToString());
return null;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using DTInstaller.PathAssemblers.ArchiveDirectoryPath;

using static DTInstaller.Utils.Methods;
using static DTInstaller.Utils.Constants;

namespace DTInstaller
{
static class ArchiveDirectoryPathAssembler
{
/**
* <summary>
* Assembles the path to the directory where the archive file with the source code is located.
* </summary>
* <returns>The path to the archive's directory if found, and null otherwise.</returns>
*/
public static string AssembleArchiveDirectoryPath()
{
string clientType = GetClientType();
return OS switch
{
OperatingSystems.Windows => WindowsPath.AssembleArchiveDirectoryPath(clientType),
OperatingSystems.Linux => LinuxPath.AssembleArchiveDirectoryPath(clientType),
_ => null,
};
}
}
}
Loading

0 comments on commit e77b221

Please sign in to comment.