forked from Parcosmic/Hammerwatch-Archipelago-Mod-Installer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCrystalProjectAPInstaller.cs
More file actions
231 lines (209 loc) · 8.63 KB
/
CrystalProjectAPInstaller.cs
File metadata and controls
231 lines (209 loc) · 8.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Windows.Documents;
using System.Windows.Forms;
using BsDiff.Core;
using Microsoft.Win32;
namespace CrystalProjectInstaller;
class CrystalProjectAPInstaller
{
[STAThread]
private static void Main(string[] args)
{
const string originalCrystalProjectExeName = "Crystal Project.exe.original";
const string crystalProjectExeName = "Crystal Project.exe";
const string patchName = "CrystalProjectAP.bsdiff";
Console.WriteLine("Crystal Project Archipelago Installer");
string installerPath = Directory.GetCurrentDirectory();
string crystalProjectPath = null;
//Check if the installer is in the install directory, if it is skip the file selection dialogue
if (!File.Exists(crystalProjectExeName))
{
string initialDirectory = "C:\\";
try
{
RegistryKey steamKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Wow6432Node\\Valve\\Steam");
string steamDir = steamKey.GetValue("InstallPath") as string;
StreamReader libraryFoldersReader = File.OpenText(Path.Combine(steamDir, "steamapps", "libraryfolders.vdf"));
List<string> paths = new List<string>();
//Parse through folders file and keep track of steam install paths
while (!libraryFoldersReader.EndOfStream)
{
string line = libraryFoldersReader.ReadLine();
if (line.Contains("\"path\""))
{
line = line.Replace("\"path\"", "");
line = line.Substring(line.IndexOf('"') + 1);
line = line.Remove(line.Length - 1);
line = line.Replace("\\\\", "\\");
paths.Add(line);
}
}
for (int p = 0; p < paths.Count; p++)
{
//For some reason games are installed in either "steam" or "steamapps"
//Not sure what the distinction is but seems like C:// is "steam" and D:// is "steamapps"?
string path1 = Path.Combine(paths[p], "steam\\common\\Crystal Project");
if (Directory.Exists(path1))
{
initialDirectory = path1;
break;
}
string path2 = Path.Combine(paths[p], "steamapps\\common\\Crystal Project");
if (Directory.Exists(path2))
{
initialDirectory = path2;
break;
}
}
}
catch (Exception e)
{
Console.Error.WriteLine("An error has occured when trying to find the Crystal Project install directory:");
Console.Error.WriteLine(e.ToString());
}
Console.WriteLine("Please select your Crystal Project.exe file in the dialogue window");
using System.Windows.Forms.OpenFileDialog openFileDialog = new();
openFileDialog.InitialDirectory = initialDirectory;
openFileDialog.Filter = "Crystal Project.exe|Crystal Project.exe";
openFileDialog.FilterIndex = 1;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
crystalProjectPath = Path.GetDirectoryName(openFileDialog.FileName);
}
}
else
{
Console.Error.WriteLine("\nInstaller located in Crystal Project install directory. Installer will not run correctly if you dropped it into the Crystal Project directory.\n");
Exit();
return;
}
if (crystalProjectPath != null)
{
string crystalProjectExePath = Path.Combine(crystalProjectPath, crystalProjectExeName);
string originalCrystalProjectExePath = Path.Combine(crystalProjectPath, originalCrystalProjectExeName);
// We used to keep backup files around in the directory, but this is pointless because we have Steam. It is the backup.
// Running verify files will restore a mistake in 2 seconds.
// This code exists now so that if anybody else runs the installer it'll clean up our old backup file. This is something we can remove by v1.0 release.
if (File.Exists(originalCrystalProjectExePath))
{
File.Delete(originalCrystalProjectExePath);
}
try
{
string archipelagoBranchHashString = "9a1e47b7fb5198f86b13279beb9f6f50"; //1.6.5 Archipelago Branch hash
string archipelagoModdedVersionString = "9131df17ceb6a47f4035a336e6a69c29"; //v0.15.1 Archipelago Modded hash
//Open crystal project exe path and compute the hash to make sure that it's the right version
FileStream crystalProjectBeforeStream = new(crystalProjectExePath, FileMode.Open, FileAccess.Read, FileShare.Read);
using MD5 md5 = MD5.Create();
string exeBeforeHashString = BitConverter.ToString(md5.ComputeHash(crystalProjectBeforeStream)).Replace("-", "").ToLower();
crystalProjectBeforeStream.Dispose();
if (exeBeforeHashString == archipelagoModdedVersionString)
{
Console.WriteLine("\nYour Crystal Project.exe is already the archipelago modded version. Proceeding to copy other files.");
}
else if (exeBeforeHashString != archipelagoBranchHashString)
{
Console.Error.WriteLine("\nCrystal Project.exe file was not the expected version. Are you in the Archipelago Beta branch on steam? If yes, try verifying file integrity in Steam and trying again.\n");
Exit();
return;
}
else
{
//Apply the bsdiff to the cry pro exe which should be vanilla still
ApplyDiff(crystalProjectExePath, Path.Combine(installerPath, patchName));
FileStream crystalProjectAfterStream = new(crystalProjectExePath, FileMode.Open, FileAccess.Read, FileShare.Read);
string exeAfterHashString = BitConverter.ToString(md5.ComputeHash(crystalProjectAfterStream)).Replace("-", "").ToLower();
crystalProjectAfterStream.Dispose();
if (exeAfterHashString != archipelagoModdedVersionString)
{
Console.Error.WriteLine("\nSomething went wrong and the final version does not have the correct file hash. Verify File Integrity in Steam and try again, or contact the developers.\n");
Exit();
return;
}
DeepApplyDiff(Path.Combine(installerPath, "Content"), Path.Combine(crystalProjectPath, "Content"));
DeepApplyDiff(Path.Combine(installerPath, "Crystal Edit"), Path.Combine(crystalProjectPath, "Crystal Edit"));
}
}
catch (FileNotFoundException ex)
{
Console.Error.WriteLine($"\nCould not open '{ex.FileName}'.");
Console.Error.WriteLine("Make sure all supplied mod files exist in the same directory as the installer.\n");
Exit();
return;
}
// Don't attempt the copies until after we've finished all other verifications (i.e. file hash)
DeepCopy(installerPath, crystalProjectPath, excludedFiles: new List<string>
{
"Bsdiff.Core.dll",
"CrystalProjectAPInstaller.deps.json",
"CrystalProjectAPInstaller.dll",
"CrystalProjectAPInstaller.exe",
"CrystalProjectAPInstaller.runtimeconfig.json",
"ICSharpCode.SharpZipLib.dll",
"LICENSE.txt",
"README.md",
patchName });
Console.WriteLine("\nPatching successful!\n");
}
else
{
Console.WriteLine("\n'Crystal Project.exe' file not selected, exiting installation process.\n");
}
Exit();
}
private static void Exit()
{
Console.WriteLine("Press any key to close this window...");
Console.ReadKey(true);
}
private static void DeepCopy(string fromFolder, string toFolder, List<string> excludedFiles = null)
{
string[] files = Directory.GetFiles(fromFolder);
Directory.CreateDirectory(toFolder);
foreach (string file in files)
{
if ((excludedFiles != null && excludedFiles.Contains(Path.GetFileName(file))) || file.Contains(".bsdiff"))
continue;
string dest = Path.Combine(toFolder, Path.GetFileName(file));
File.Copy(file, dest, true);
}
string[] folders = Directory.GetDirectories(fromFolder);
foreach (string folder in folders)
{
DeepCopy(folder, Path.Combine(toFolder, Path.GetFileNameWithoutExtension(folder)));
}
}
private static void DeepApplyDiff(string fromFolder, string toFolder)
{
string[] files = Directory.GetFiles(fromFolder);
Directory.CreateDirectory(toFolder);
foreach (string file in files)
{
if (file.EndsWith(".bsdiff"))
{
string originalFileName = Path.GetFileName(file.Substring(0, file.Length - ".bsdiff".Length));
string dest = Path.Combine(toFolder, originalFileName);
ApplyDiff(dest, file);
}
}
string[] folders = Directory.GetDirectories(fromFolder);
foreach (string folder in folders)
{
DeepApplyDiff(folder, Path.Combine(toFolder, Path.GetFileNameWithoutExtension(folder)));
}
}
private static void ApplyDiff(string fileToUpdatePath, string diffFilePath)
{
string tempFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Temp");
FileStream input = new(fileToUpdatePath, FileMode.Open, FileAccess.Read, FileShare.Read);
FileStream output = new(tempFilePath, FileMode.Create);
BinaryPatchUtility.Apply(input, () => new FileStream(diffFilePath, FileMode.Open, FileAccess.Read, FileShare.Read), output);
input.Dispose();
output.Dispose();
File.Move(tempFilePath, fileToUpdatePath, true);
}
}