Skip to content

Commit 262d022

Browse files
authored
Merge pull request #146 from patchkit-net/dev/v3.17.x.x
Release v3.17.2.0
2 parents ee5195f + 62c7be0 commit 262d022

25 files changed

+414
-97
lines changed

Assets/PatchKit Patcher/Patcher.unity

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@ MonoBehaviour:
848848
m_Name:
849849
m_EditorClassIdentifier:
850850
ErrorDialog: {fileID: 272161832}
851-
EditorAppSecret: ac20fc855b75a7ea5f3e936dfd38ccd8
851+
EditorAppSecret: 94987833c3b51565ce7bf85e9a747571
852852
EditorOverrideLatestVersionId: 0
853853
DefaultConfiguration:
854854
AppUpdaterConfiguration:

Assets/PatchKit Patcher/Scripts/AppData/Local/IUnarchiver.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,11 @@ public interface IUnarchiver
77
event UnarchiveProgressChangedHandler UnarchiveProgressChanged;
88

99
void Unarchive(CancellationToken cancellationToken);
10+
11+
// set to true to continue unpacking on error. Check HasErrors later to see if there are any
12+
bool ContinueOnError { set; }
13+
14+
// After Unarchive() if set to true, there were unpacking errors.
15+
bool HasErrors { get; }
1016
}
1117
}

Assets/PatchKit Patcher/Scripts/AppData/Local/Pack1Unarchiver.cs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class Pack1Unarchiver : IUnarchiver
3434
private readonly string _suffix;
3535
private readonly byte[] _key;
3636
private readonly byte[] _iv;
37+
private int _processedFiles = 0; // debugging
3738

3839
/// <summary>
3940
/// The range (in bytes) of the partial pack1 source file
@@ -42,6 +43,12 @@ public class Pack1Unarchiver : IUnarchiver
4243

4344
public event UnarchiveProgressChangedHandler UnarchiveProgressChanged;
4445

46+
// set to true to continue unpacking on error. Check HasErrors later to see if there are any
47+
public bool ContinueOnError { private get; set; }
48+
49+
// After Unarchive() finishes if this set to true, there were unpacking errors.
50+
public bool HasErrors { get; private set; }
51+
4552
public Pack1Unarchiver(string packagePath, Pack1Meta metaData, string destinationDirPath, string key, string suffix = "")
4653
: this(packagePath, metaData, destinationDirPath, Encoding.ASCII.GetBytes(key), suffix, new BytesRange(0, -1))
4754
{
@@ -87,6 +94,7 @@ private Pack1Unarchiver(string packagePath, Pack1Meta metaData, string destinati
8794
public void Unarchive(CancellationToken cancellationToken)
8895
{
8996
int entry = 1;
97+
HasErrors = false;
9098

9199
DebugLogger.Log("Unpacking " + _metaData.Files.Length + " files...");
92100
foreach (var file in _metaData.Files)
@@ -149,7 +157,28 @@ private void Unpack(Pack1Meta.FileEntry file, Action<double> progress, Cancellat
149157
switch (file.Type)
150158
{
151159
case Pack1Meta.RegularFileType:
152-
UnpackRegularFile(file, progress, cancellationToken, destinationDirPath);
160+
try
161+
{
162+
UnpackRegularFile(file, progress, cancellationToken, destinationDirPath);
163+
}
164+
catch (Ionic.Zlib.ZlibException e)
165+
{
166+
if (ContinueOnError)
167+
{
168+
DebugLogger.LogWarning("ZlibException caught. The process will continue, but I will try to repair it later.");
169+
DebugLogger.LogException(e);
170+
HasErrors = true;
171+
}
172+
else
173+
{
174+
throw;
175+
}
176+
}
177+
finally
178+
{
179+
_processedFiles += 1;
180+
}
181+
153182
break;
154183
case Pack1Meta.DirectoryFileType:
155184
progress(0.0);
@@ -246,6 +275,7 @@ private void UnpackRegularFile(Pack1Meta.FileEntry file, Action<double> onProgre
246275
using (var target = new FileStream(destPath, FileMode.Create))
247276
{
248277
ExtractFileFromStream(limitedStream, target, file.Size.Value, decryptor, decompressorCreator, onProgress, cancellationToken);
278+
DebugTestCorruption(target);
249279
}
250280
}
251281

@@ -259,6 +289,30 @@ private void UnpackRegularFile(Pack1Meta.FileEntry file, Action<double> onProgre
259289
DebugLogger.Log("File " + file.Name + " unpacked successfully!");
260290
}
261291

292+
// allows to test corruption if valid environment variable is set
293+
private void DebugTestCorruption(FileStream target)
294+
{
295+
if (_processedFiles == 0)
296+
{
297+
// do not corrupt the first file
298+
return;
299+
}
300+
301+
if (
302+
_processedFiles % 10 == 0 && EnvironmentInfo.GetEnvironmentVariable(EnvironmentVariables.CorruptFilesOnUnpack10, "") == "1"
303+
||
304+
_processedFiles % 50 == 0 && EnvironmentInfo.GetEnvironmentVariable(EnvironmentVariables.CorruptFilesOnUnpack50, "") == "1"
305+
||
306+
_processedFiles % 300 == 0 && EnvironmentInfo.GetEnvironmentVariable(EnvironmentVariables.CorruptFilesOnUnpack300, "") == "1"
307+
)
308+
{
309+
DebugLogger.LogWarning("DEBUG: Writing extra byte and triggering zlibexception");
310+
311+
target.Write(new byte[] { 1 }, 0, 1);
312+
throw new Ionic.Zlib.ZlibException();
313+
}
314+
}
315+
262316
private Stream CreateXzDecompressor(Stream source)
263317
{
264318
if (source.CanSeek)

Assets/PatchKit Patcher/Scripts/AppData/Local/ZipUnarchiver.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ public class ZipUnarchiver : IUnarchiver
1919

2020
public event UnarchiveProgressChangedHandler UnarchiveProgressChanged;
2121

22+
// not used
23+
public bool ContinueOnError { private get; set; }
24+
25+
// not used
26+
public bool HasErrors { get; private set; }
27+
2228
public ZipUnarchiver(string packagePath, string destinationDirPath, string password = null)
2329
{
2430
Checks.ArgumentFileExists(packagePath, "packagePath");
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using System.Threading;
6+
using System.Collections.Generic;
7+
using PatchKit.Unity.Patcher.Cancellation;
8+
using PatchKit.Unity.Patcher.Debug;
9+
using PatchKit.Unity.Patcher.AppUpdater.Commands;
10+
using PatchKit.Unity.Patcher.AppUpdater.Status;
11+
using PatchKit.Api.Models.Main;
12+
13+
namespace PatchKit.Unity.Patcher.AppUpdater
14+
{
15+
public class AppRepairer
16+
{
17+
private static readonly DebugLogger DebugLogger = new DebugLogger(typeof(AppRepairer));
18+
19+
public readonly AppUpdaterContext Context;
20+
21+
// set to true if you wish to check file hashes
22+
public bool CheckHashes = false;
23+
24+
// how many times process will repeat until it ultimately fails
25+
public int RepeatCount = 3;
26+
27+
private UpdaterStatus _status;
28+
29+
private AppUpdaterStrategyResolver _strategyResolver;
30+
31+
private AppUpdaterCommandFactory _commandFactory;
32+
33+
private int _lowestVersionWithContentId;
34+
35+
private const double IncreaseRepairCost = 1.5d;
36+
37+
38+
public AppRepairer(AppUpdaterContext context, UpdaterStatus status)
39+
{
40+
DebugLogger.LogConstructor();
41+
42+
Checks.ArgumentNotNull(context, "context");
43+
44+
Context = context;
45+
_status = status;
46+
47+
_strategyResolver = new AppUpdaterStrategyResolver(_status);
48+
_commandFactory = new AppUpdaterCommandFactory();
49+
}
50+
51+
// returns true if data is valid (was valid from the start or successfull repair was performed)
52+
public bool Perform(PatchKit.Unity.Patcher.Cancellation.CancellationToken cancellationToken)
53+
{
54+
_lowestVersionWithContentId = Context.App.GetLowestVersionWithContentId(cancellationToken);
55+
56+
for(int attempt = 1; attempt <= RepeatCount; ++attempt)
57+
{
58+
DebugLogger.Log("Running integrity check, attempt " + attempt + " of " + RepeatCount);
59+
60+
if (PerformInternal(cancellationToken))
61+
{
62+
return true;
63+
}
64+
}
65+
66+
// retry count reached, let's check for the last time if data is ok, but without repairing
67+
int installedVersionId = Context.App.GetInstalledVersionId();
68+
VersionIntegrity results = CheckIntegrity(cancellationToken, installedVersionId);
69+
var filesNeedFixing = FilesNeedFixing(results);
70+
71+
if (filesNeedFixing.Count() == 0)
72+
{
73+
DebugLogger.Log("No missing or invalid size files.");
74+
return true;
75+
}
76+
77+
78+
DebugLogger.LogError("Still have corrupted files after all fixing attempts");
79+
return false;
80+
}
81+
82+
// returns true if there was no integrity errors, false if there was and repair was performed
83+
private bool PerformInternal(PatchKit.Unity.Patcher.Cancellation.CancellationToken cancellationToken)
84+
{
85+
int installedVersionId = Context.App.GetInstalledVersionId();
86+
87+
VersionIntegrity results = CheckIntegrity(cancellationToken, installedVersionId);
88+
var filesNeedFixing = FilesNeedFixing(results);
89+
90+
if (filesNeedFixing.Count() == 0)
91+
{
92+
DebugLogger.Log("No missing or invalid size files.");
93+
return true;
94+
}
95+
96+
// need to collect some data about the application to calculate the repair cost and make decisions
97+
98+
int latestVersionId = Context.App.GetLatestVersionId(true, cancellationToken);
99+
100+
AppContentSummary installedVersionContentSummary
101+
= Context.App.RemoteMetaData.GetContentSummary(installedVersionId, cancellationToken);
102+
103+
AppContentSummary latestVersionContentSummary
104+
= Context.App.RemoteMetaData.GetContentSummary(latestVersionId, cancellationToken);
105+
106+
bool isNewVersionAvailable = installedVersionId < latestVersionId;
107+
108+
long contentSize = isNewVersionAvailable
109+
? latestVersionContentSummary.Files.Sum(f => f.Size)
110+
: installedVersionContentSummary.Files.Sum(f => f.Size);
111+
112+
double repairCost = CalculateRepairCost(installedVersionContentSummary, filesNeedFixing);
113+
114+
// increasing repair costs that reinstallation will be done for 1/3 of the content size
115+
repairCost *= IncreaseRepairCost;
116+
117+
118+
if (_lowestVersionWithContentId > installedVersionId)
119+
{
120+
DebugLogger.Log(
121+
"Repair is impossible because lowest version with content id is "
122+
+ _lowestVersionWithContentId +
123+
" and currently installed version id is "
124+
+ installedVersionId +
125+
". Reinstalling.");
126+
127+
ReinstallContent(cancellationToken);
128+
}
129+
else if (repairCost < contentSize)
130+
{
131+
DebugLogger.Log(string.Format("Repair cost {0} is smaller than content cost {1}, repairing...", repairCost, contentSize));
132+
IAppUpdaterStrategy repairStrategy = _strategyResolver.Create(StrategyType.Repair, Context);
133+
repairStrategy.Update(cancellationToken);
134+
}
135+
else
136+
{
137+
DebugLogger.Log(string.Format("Content cost {0} is smaller than repair {1}. Reinstalling.", contentSize, repairCost));
138+
ReinstallContent(cancellationToken);
139+
}
140+
141+
return false;
142+
}
143+
144+
private VersionIntegrity CheckIntegrity(
145+
PatchKit.Unity.Patcher.Cancellation.CancellationToken cancellationToken,
146+
int installedVersionId
147+
)
148+
{
149+
ICheckVersionIntegrityCommand checkIntegrity = _commandFactory
150+
.CreateCheckVersionIntegrityCommand(
151+
versionId: installedVersionId,
152+
context: Context,
153+
isCheckingHash: CheckHashes,
154+
isCheckingSize: true,
155+
cancellationToken: cancellationToken);
156+
157+
checkIntegrity.Prepare(_status, cancellationToken);
158+
checkIntegrity.Execute(cancellationToken);
159+
160+
return checkIntegrity.Results;
161+
}
162+
163+
private IEnumerable<FileIntegrity> FilesNeedFixing(VersionIntegrity results)
164+
{
165+
var missingFiles = results.Files.Where(f => f.Status == FileIntegrityStatus.MissingData);
166+
var invalidSizeFiles = results.Files.Where(f => f.Status == FileIntegrityStatus.InvalidSize);
167+
168+
return missingFiles.Concat(invalidSizeFiles);
169+
}
170+
171+
private long CalculateRepairCost(AppContentSummary contentSummary, IEnumerable<FileIntegrity> filesToRepair)
172+
{
173+
return filesToRepair
174+
.Select(f => contentSummary.Files.FirstOrDefault(e => e.Path == f.FileName))
175+
.Sum(f => f.Size);
176+
}
177+
178+
private void ReinstallContent(PatchKit.Unity.Patcher.Cancellation.CancellationToken cancellationToken)
179+
{
180+
IUninstallCommand uninstall = _commandFactory.CreateUninstallCommand(Context);
181+
uninstall.Prepare(_status, cancellationToken);
182+
uninstall.Execute(cancellationToken);
183+
184+
// not catching any exceptions here, because exception during content installation in this place should be fatal
185+
var contentStrategy = new AppUpdaterContentStrategy(Context, _status);
186+
contentStrategy.RepairOnError = false; // do not attempt to repair content to not cause a loop
187+
contentStrategy.Update(cancellationToken);
188+
}
189+
}
190+
}

Assets/PatchKit Patcher/Scripts/AppUpdater/AppRepairer.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)