├── Updater-windows7-x64-packed-runtime
└── run.bat
├── Tutorial Images
├── rel_v2.1.2+
│ ├── img01.jpg
│ └── img02.jpg
└── rel_v3.2.4+
│ ├── cn_img01.png
│ └── cn_img02.png
├── HDiffPatchCreator
├── bin
│ ├── Debug
│ │ └── net6.0-windows7.0
│ │ │ ├── 7z.exe
│ │ │ ├── hdiffz.exe
│ │ │ └── hpatchz.exe
│ └── Release
│ │ └── net6.0-windows7.0
│ │ ├── 7z.exe
│ │ ├── hdiffz.exe
│ │ └── hpatchz.exe
├── HDiffPatchCreator.csproj
└── Program.cs
├── HappyGenyuanImsactUpdate
├── bin
│ ├── Debug
│ │ └── net6.0-windows7.0
│ │ │ ├── 7z.exe
│ │ │ └── hpatchz.exe
│ └── Release
│ │ └── net6.0-windows7.0
│ │ ├── 7z.exe
│ │ └── hpatchz.exe
├── HappyGenyuanImsactUpdate.csproj
├── MyMD5.cs
├── Unzipped.cs
├── ConfigIni.cs
├── Patch.cs
├── UpCheck.cs
├── Helper.cs
├── OuterInvoke.cs
└── Program.cs
├── .gitignore
├── LICENSE
├── create-release.bat
├── HappyGenyuanImsactUpdate.sln
├── .github
└── workflows
│ └── dotnet-desktop.yml
├── README_CN.md
└── README.md
/Updater-windows7-x64-packed-runtime/run.bat:
--------------------------------------------------------------------------------
1 | .\bin\HappyGenyuanImsactUpdate.exe
--------------------------------------------------------------------------------
/Tutorial Images/rel_v2.1.2+/img01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/Tutorial Images/rel_v2.1.2+/img01.jpg
--------------------------------------------------------------------------------
/Tutorial Images/rel_v2.1.2+/img02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/Tutorial Images/rel_v2.1.2+/img02.jpg
--------------------------------------------------------------------------------
/Tutorial Images/rel_v3.2.4+/cn_img01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/Tutorial Images/rel_v3.2.4+/cn_img01.png
--------------------------------------------------------------------------------
/Tutorial Images/rel_v3.2.4+/cn_img02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/Tutorial Images/rel_v3.2.4+/cn_img02.png
--------------------------------------------------------------------------------
/HDiffPatchCreator/bin/Debug/net6.0-windows7.0/7z.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HDiffPatchCreator/bin/Debug/net6.0-windows7.0/7z.exe
--------------------------------------------------------------------------------
/HDiffPatchCreator/bin/Release/net6.0-windows7.0/7z.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HDiffPatchCreator/bin/Release/net6.0-windows7.0/7z.exe
--------------------------------------------------------------------------------
/HDiffPatchCreator/bin/Debug/net6.0-windows7.0/hdiffz.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HDiffPatchCreator/bin/Debug/net6.0-windows7.0/hdiffz.exe
--------------------------------------------------------------------------------
/HDiffPatchCreator/bin/Debug/net6.0-windows7.0/hpatchz.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HDiffPatchCreator/bin/Debug/net6.0-windows7.0/hpatchz.exe
--------------------------------------------------------------------------------
/HDiffPatchCreator/bin/Release/net6.0-windows7.0/hdiffz.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HDiffPatchCreator/bin/Release/net6.0-windows7.0/hdiffz.exe
--------------------------------------------------------------------------------
/HDiffPatchCreator/bin/Release/net6.0-windows7.0/hpatchz.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HDiffPatchCreator/bin/Release/net6.0-windows7.0/hpatchz.exe
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/bin/Debug/net6.0-windows7.0/7z.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HappyGenyuanImsactUpdate/bin/Debug/net6.0-windows7.0/7z.exe
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/bin/Release/net6.0-windows7.0/7z.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HappyGenyuanImsactUpdate/bin/Release/net6.0-windows7.0/7z.exe
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/bin/Debug/net6.0-windows7.0/hpatchz.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HappyGenyuanImsactUpdate/bin/Debug/net6.0-windows7.0/hpatchz.exe
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/bin/Release/net6.0-windows7.0/hpatchz.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYHEggEgg/HappyGenyuanImsactUpdate/HEAD/HappyGenyuanImsactUpdate/bin/Release/net6.0-windows7.0/hpatchz.exe
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vs
2 | /Release-windows10-x64
3 | /HappyGenyuanImsactUpdate/bin
4 | /HappyGenyuanImsactUpdate/obj
5 | /HappyGenyuanImsactUpdate/Properties
6 | /HDiffPatchCreator/bin
7 | /HDiffPatchCreator/obj
8 | /HDiffPatchCreator/Properties
9 | /.vscode
10 | /Release-windows7-x64
11 | /Updater-windows7-x64-packed-runtime
12 | /Release-windows7-x64.zip
13 | /Updater-windows7-x64-packed-runtime.zip
14 | /Release-windows7-x86
15 | /Updater-windows7-x86-packed-runtime
16 | /Release-windows7-x86.zip
17 | /Updater-windows7-x86-packed-runtime.zip
18 |
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/HappyGenyuanImsactUpdate.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0-windows7.0
6 | enable
7 | enable
8 | 7.0
9 | 3.2.4
10 | YYHEggEgg
11 | Copyright (c) 2023 YYHEggEgg
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/MyMD5.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Cryptography;
2 | using System.Text;
3 |
4 | namespace HappyGenyuanImsactUpdate
5 | {
6 | public class MyMD5
7 | {
8 | public static string GetMD5HashFromFile(string fileName)
9 | {
10 | FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read);
11 | //MD5 md5 = new MD5CryptoServiceProvider();
12 | MD5 md5 = MD5.Create();
13 | byte[] retVal = md5.ComputeHash(file);
14 | file.Close();
15 | StringBuilder sb = new StringBuilder();
16 | for (int i = 0; i < retVal.Length; i++)
17 | {
18 | sb.Append(retVal[i].ToString("x2"));
19 | }
20 | return sb.ToString();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/HDiffPatchCreator/HDiffPatchCreator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0-windows7.0
6 | enable
7 | enable
8 | 7.0
9 | 3.2.4
10 | YYHEggEgg
11 | Copyright (c) 2024 YYHEggEgg
12 | false
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 YYHEggEgg
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/create-release.bat:
--------------------------------------------------------------------------------
1 | mkdir .\Release-windows7-x64
2 | dotnet publish --configuration Release .\HappyGenyuanImsactUpdate --output .\Release-windows7-x64\Updater
3 | copy ".\HappyGenyuanImsactUpdate\bin\Release\net6.0-windows7.0\7z.exe" ".\Release-windows7-x64\Updater\7z.exe" /y
4 | copy ".\HappyGenyuanImsactUpdate\bin\Release\net6.0-windows7.0\hpatchz.exe" ".\Release-windows7-x64\Updater\hpatchz.exe" /y
5 |
6 | dotnet publish --configuration Release .\HDiffPatchCreator --output ".\Release-windows7-x64\Patch Creator"
7 | copy ".\HDiffPatchCreator\bin\Release\net6.0-windows7.0\7z.exe" ".\Release-windows7-x64\Patch Creator\7z.exe" /y
8 | copy ".\HDiffPatchCreator\bin\Release\net6.0-windows7.0\hdiffz.exe" ".\Release-windows7-x64\Patch Creator\hdiffz.exe" /y
9 | copy ".\HDiffPatchCreator\bin\Release\net6.0-windows7.0\hpatchz.exe" ".\Release-windows7-x64\Patch Creator\hpatchz.exe" /y
10 |
11 | mkdir .\Updater-windows7-x64-packed-runtime
12 | dotnet publish --configuration Release --runtime win7-x64 --self-contained .\HappyGenyuanImsactUpdate --output .\Updater-windows7-x64-packed-runtime\bin
13 | copy ".\HappyGenyuanImsactUpdate\bin\Release\net6.0-windows7.0\7z.exe" ".\Updater-windows7-x64-packed-runtime\bin\7z.exe" /y
14 | copy ".\HappyGenyuanImsactUpdate\bin\Release\net6.0-windows7.0\hpatchz.exe" ".\Updater-windows7-x64-packed-runtime\bin\hpatchz.exe" /y
15 |
16 | pause
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/Unzipped.cs:
--------------------------------------------------------------------------------
1 | namespace HappyGenyuanImsactUpdate
2 | {
3 | // NOTE: Because some dawn packages from gdrive has a sub folder, we should move it back.
4 | internal class Unzipped
5 | {
6 | #region Move Back Sub Folder from zip file
7 | public static void MoveBackSubFolder(DirectoryInfo datadir, string[]? predirs)
8 | {
9 | var nowdirs = Directory.GetDirectories(datadir.FullName);
10 | var newappeared_dirs = GetNewlyAppearedFolders(predirs, nowdirs);
11 | foreach (var dir in newappeared_dirs)
12 | {
13 | MoveDir(dir, datadir.FullName);
14 | }
15 | }
16 |
17 | private static List GetNewlyAppearedFolders(string[]? predirs, string[]? nowdirs)
18 | {
19 | if (nowdirs == null) return new();
20 | List newdirs = new();
21 | foreach (var dir in nowdirs)
22 | {
23 | if (predirs == null || !predirs.Contains(dir)) newdirs.Add(dir);
24 | }
25 | return newdirs;
26 | }
27 |
28 | private static void MoveDir(string source, string target)
29 | {
30 | foreach (var file in Directory.GetFiles(source))
31 | {
32 | File.Move(file, $"{target}\\{new FileInfo(file).Name}", true);
33 | }
34 | foreach (var dir in Directory.GetDirectories(source))
35 | {
36 | string newdir = $"{target}\\{new DirectoryInfo(dir).Name}";
37 | Directory.CreateDirectory(newdir);
38 | MoveDir(dir, newdir);
39 | }
40 | Directory.Delete(source);
41 | }
42 | #endregion
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.2.32526.322
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HappyGenyuanImsactUpdate", "HappyGenyuanImsactUpdate\HappyGenyuanImsactUpdate.csproj", "{CA429FBB-149E-4B0E-ACAC-B31845A675D4}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HDiffPatchCreator", "HDiffPatchCreator\HDiffPatchCreator.csproj", "{7BC7537D-76F1-4AA7-8617-7C069FB82561}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {CA429FBB-149E-4B0E-ACAC-B31845A675D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {CA429FBB-149E-4B0E-ACAC-B31845A675D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {CA429FBB-149E-4B0E-ACAC-B31845A675D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {CA429FBB-149E-4B0E-ACAC-B31845A675D4}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {7BC7537D-76F1-4AA7-8617-7C069FB82561}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {7BC7537D-76F1-4AA7-8617-7C069FB82561}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {7BC7537D-76F1-4AA7-8617-7C069FB82561}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {7BC7537D-76F1-4AA7-8617-7C069FB82561}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {2FCDC790-7724-49DA-9D4B-13BF716BD188}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/ConfigIni.cs:
--------------------------------------------------------------------------------
1 | namespace HappyGenyuanImsactUpdate
2 | {
3 | //config.ini contains version info
4 | internal static class ConfigIni
5 | {
6 | #region Find Version from zip name
7 | //index 012345678901234567890
8 | // zip: game_2.9.0_3.10.0_hdiff_abCdEFgHIjKLMnOP.zip
9 | public static string FindStartVersion(string zipName)
10 | {
11 | int index = zipName.IndexOf("_hdiff");
12 | if (index == -1) return string.Empty;
13 | string substr = zipName.Substring(0, index);
14 | int veridx = substr.IndexOf('_');
15 | int endidx = substr.IndexOf('_', veridx + 1);
16 | if (veridx == -1 || endidx == -1) return string.Empty;
17 | string rtn = substr.Substring(veridx + 1, endidx - veridx - 1);
18 | // Custom packages may not use standard version string.
19 | // return VerifyVersionString(rtn) ? rtn : string.Empty;
20 | return rtn;
21 | }
22 |
23 | public static string FindToVersion(string zipName)
24 | {
25 | int index = zipName.IndexOf("_hdiff");
26 | string substr = zipName.Substring(0, index);
27 | if (index == -1) return string.Empty;
28 | int veridx = substr.LastIndexOf('_');
29 | if (veridx == -1) return string.Empty;
30 | string rtn = substr.Substring(veridx + 1, substr.Length - veridx - 1);
31 | // return VerifyVersionString(rtn) ? rtn : string.Empty;
32 | return rtn;
33 | }
34 |
35 | public static bool VerifyVersionString(string verstr)
36 | {
37 | var strs = verstr.Split('.');
38 | foreach (string str in strs)
39 | {
40 | if (!int.TryParse(str, out _)) return false;
41 | }
42 | return true;
43 | }
44 | #endregion
45 |
46 | // i don't want to write a real ini writer lol
47 | public static void ApplyConfigChange(FileInfo configfile, string version)
48 | {
49 | string fullfile = string.Empty;
50 | using (StreamReader reader = new(configfile.FullName))
51 | {
52 | while (true)
53 | {
54 | string? line = reader.ReadLine();
55 | if (line == null || line == string.Empty) break;
56 | if (line.StartsWith("game_version="))
57 | line = $"game_version={version}";
58 | fullfile += line + "\r\n";
59 | }
60 | }
61 |
62 | File.WriteAllText(configfile.FullName, fullfile);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-desktop.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 |
6 | # This workflow will build, test, sign and package a WPF or Windows Forms desktop application
7 | # built on .NET Core.
8 | # To learn how to migrate your existing application to .NET Core,
9 | # refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework
10 | #
11 | #
12 | # To configure this workflow:
13 | #
14 | # 1. Configure environment variables
15 | # GitHub sets default environment variables for every workflow run.
16 | # Replace the variables relative to your project in the "env" section below.
17 | #
18 | # 2. Signing
19 | # Generate a signing certificate in the Windows Application
20 | # Packaging Project or add an existing signing certificate to the project.
21 | # Next, use PowerShell to encode the .pfx file using Base64 encoding
22 | # by running the following Powershell script to generate the output string:
23 | #
24 | # $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte
25 | # [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt'
26 | #
27 | # Open the output file, SigningCertificate_Encoded.txt, and copy the
28 | # string inside. Then, add the string to the repo as a GitHub secret
29 | # and name it "Base64_Encoded_Pfx."
30 | # For more information on how to configure your signing certificate for
31 | # this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing
32 | #
33 | # Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key".
34 | # See "Build the Windows Application Packaging project" below to see how the secret is used.
35 | #
36 | # For more information on GitHub Actions, refer to https://github.com/features/actions
37 | # For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications,
38 | # refer to https://github.com/microsoft/github-actions-for-desktop-apps
39 |
40 | name: Normal dotnet publish
41 |
42 | on:
43 | push:
44 | branches: [ "development-(have-not-been-tested)" ]
45 | pull_request:
46 | branches: [ "main", "development-(have-not-been-tested)" ]
47 |
48 | jobs:
49 |
50 | build:
51 | runs-on: windows-latest # For a list of available runner types, refer to
52 | # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
53 |
54 | steps:
55 | - name: Checkout
56 | uses: actions/checkout@v3
57 | with:
58 | fetch-depth: 0
59 |
60 | # Install the .NET Core workload
61 | - name: Install .NET Core
62 | uses: actions/setup-dotnet@v3
63 | with:
64 | dotnet-version: 6.0.x
65 |
66 | # Build
67 | - name: Create release
68 | working-directory: ${{env.GITHUB_WORKSPACE}}
69 | run: ./create-release.bat
70 |
71 | # Upload artifact
72 | - name: Upload release
73 | uses: actions/upload-artifact@v2
74 | with:
75 | name: Release-windows7-x64
76 | path: Release-windows7-x64
77 | retention-days: 7
78 |
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/Patch.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using YYHEggEgg.Utils;
3 |
4 | namespace HappyGenyuanImsactUpdate
5 | {
6 | internal class Patch
7 | {
8 | public Patch(DirectoryInfo datadir, string path7z, string pathHdiff)
9 | {
10 | Path7z = path7z;
11 | PathHdiff = pathHdiff;
12 | this.datadir = datadir;
13 | }
14 |
15 | public string Path7z { get; set; }
16 | public string PathHdiff { get; set; }
17 | public DirectoryInfo datadir { get; set; }
18 |
19 | #region Patch hdiff
20 | ///
21 | /// Patch hdiff
22 | ///
23 | /// hdiff files used for deleteing
24 | public async Task Hdiff()
25 | {
26 | var hdiffs = new List();
27 | var invokes = new List();
28 |
29 | var hdifftxtPath = $"{datadir}\\hdifffiles.txt";
30 | if (File.Exists(hdifftxtPath))
31 | {
32 | using (StreamReader hdiffreader = new(hdifftxtPath))
33 | {
34 | while (true)
35 | {
36 | string? output = hdiffreader.ReadLine();
37 | if (output == null) break;
38 | else
39 | {
40 | var doc = JsonDocument.Parse(output);
41 | //{"remoteName": "name.pck"}
42 | string hdiffName = datadir.FullName + '/'
43 | + doc.RootElement.GetProperty("remoteName").GetString();
44 | //command: -f (original file) (patch file) (output file)
45 | // hpatchz -f name.pck name.pck.hdiff name.pck
46 | string hdiffPathstd = new FileInfo(hdiffName).FullName;
47 | // If package is created by an individual, he may include
48 | // unnecessary files like cache and live updates,
49 | // So it's essential to skip some files that doesn't exist.
50 | if (!File.Exists(hdiffPathstd)) continue;
51 |
52 | invokes.Add(new OuterInvokeInfo
53 | {
54 | ProcessPath = PathHdiff,
55 | CmdLine = $"-f \"{hdiffName}\" \"{hdiffName}.hdiff\" \"{hdiffName}\"",
56 | AutoTerminateReason = $"hdiff patch for \"{hdiffName}\" failed."
57 | });
58 | hdiffs.Add(hdiffPathstd);
59 | }
60 | }
61 | }
62 |
63 | File.Delete(hdifftxtPath);
64 | }
65 |
66 | await OuterInvoke.RunMultiple(invokes, 3851, 2);
67 |
68 | // Delete .hdiff afterwards
69 | foreach (var hdiffFile in hdiffs)
70 | {
71 | File.Delete($"{hdiffFile}.hdiff");
72 | }
73 | }
74 | #endregion
75 |
76 | #region Delete Files
77 | ///
78 | /// Process deletedFiles.txt. Notice that files that failed to be deleted will be returned.
79 | ///
80 | /// files failed to be deleted
81 | public List DeleteFiles()
82 | {
83 | var delete_delays = new List();
84 |
85 | var deletetxtPath = $"{datadir}\\deletefiles.txt";
86 | if (File.Exists(deletetxtPath))
87 | {
88 | using (StreamReader hdiffreader = new(deletetxtPath))
89 | {
90 | while (true)
91 | {
92 | string? output = hdiffreader.ReadLine();
93 | if (output == null) break;
94 | else
95 | {
96 | string deletedName = datadir.FullName + '\\' + output;
97 | if (File.Exists(deletedName))
98 | File.Delete(deletedName);
99 | else delete_delays.Add(deletedName);
100 | }
101 | }
102 | }
103 |
104 | File.Delete(deletetxtPath);
105 | }
106 |
107 | return delete_delays;
108 | }
109 | #endregion
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | 中文 | [EN](https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/blob/main/README.md)
2 |
3 | # HappyGenyuanImsactUpdate
4 | A hdiff-using update program of a certain anime game.
5 |
6 | ## 可用包体下载
7 |
8 | 您可以前往这里下载来自官方的 hdiff 更新包与完整包:
9 |
10 | - [Anime Game Downloads Archive](https://git.xeondev.com/YYHEggEgg/GI-Download-Library)
11 | - [Honkai: March 7th Downloads Archive](https://github.com/keitarogg/HSR-Download-Library)
12 | - [3Z Downloads Archive](https://github.com/360NENZ/ZZZ-Download-Library)
13 |
14 | ## 公告
15 | ### 许可证更改通知
16 | 自 2023 年 8 月 30 日 起,本项目已经更改为 MIT 许可证。所有以前和未来的贡献都受此新许可证约束。
17 |
18 | ### [请勿使用该程序来更新至3.6版本](https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/issues/15)
19 |
20 | 从3.6开始,miHoYo 将 `StreamingAssets/Audio/GeneratedSoundBanks/Windows` 更改为 `StreamingAssets/AudioAssets`,但由启动器负责修改,不包含在更新包中。
21 |
22 | 这不会被修复,因为代码可能面临被污染的风险。
23 |
24 | 这很可能是一个临时的特例,**此更新程序在 3.7 及以后的版本中仍可用**。有关详细信息,请转至 [该 issue](https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/issues/15)。
25 |
26 | ### 关于有限支持的说明
27 |
28 | 自 4.6 版本起,HoYoPlay 逐渐将 `sophon chunk` 作为其主要的更新分发与安装模式。
29 |
30 | 本程序仅对 hdiff 更新包适配,故不会添加对相应功能的支持;但本软件仍然会进行基本 Bug 修复与优化的更新(如果必要)。
31 |
32 | ## 新版本特性
33 | ### v3.2.4
34 | - 在使用 `Updater` 时,目录下如果不存在(可被程序识别的)游戏可执行文件,允许用户手动二次确认继续。
35 |
36 | ### v3.2.3
37 | - 由于提供的 `7z.exe`、`hpatchz.exe` 与 `hdiffz.exe` 均为 64 位程序,取消了对于 32 位 Windows 的支持计划。
38 |
39 | #### Patch Creator
40 | - 更新了 `--only-include-pkg-defined-files`、`--include-audios` 选项。有关详细信息,请参阅 [如何使用 - 创建更新包 / Patch Creater](#创建更新包--patch-creater).
41 |
42 | ### v3.2.2
43 | #### Updater
44 | - 修复了 Updater 无法正确支持 崩坏:整活铁道 的问题。
45 | - 软件现在打包 .NET 6.0 运行时发布。
46 | - 在发行版中将会支持 32 位 Windows。
47 | - 在 Windows 7 上添加了右下角气泡提示。Windows 10 上此功能仍然表现为“通知”。
48 |
49 | ### v3.2.1
50 | #### Updater
51 | - 修复了 Updater 在 <=1.5 版本下异常提示无法检查音频包完整性的问题。
52 | - 修复了 Updater 在 >=3.6 版本下无法检测到已安装语音包的问题。
53 | - 添加了对 崩坏:整活铁道 更新包的支持。
54 |
55 | #### Patch Creator
56 | 现在,当程序检测到提供的两个版本文件夹包含的文件名称相同时,会请求用户检查提供的参数是否正确。
57 |
58 | ### v3.2.0
59 | - 将整个项目迁移到了 `EggEgg.CSharp-Logger v3.0.0`。
60 | 现在无论是更新还是创建更新包都可以有日志记录保留在程序目录下,但调试日志不会显示在控制台而会输出在 `latest.debug.log` 中。
61 | - 在更新时,hdiff patch 失败支持进行自动重试(3 次)。
62 | - 如果不解压程序直接运行现在会触发警告。
63 |
64 | ### v3.0.0
65 | 现在您可以像官方一样自己创建更新包了!
66 | 使用命令行调用 `Patch Creater\HDiffPatchCreator.exe` 即可。
67 |
68 | 注意:强烈建议**仅使用原本的官方包为源文件**创建更新包。
69 |
70 | 供您自己电脑上使用的文件可能会包含小更新和缓存内容,使用包的人可能并不具备这些文件。**将缓存放入包内甚至可能导致您的个人信息泄露。** 有关信息参见 [可用包体下载](#可用包体下载).
71 |
72 | ## 如何找到游戏目录文件夹
73 | 1. 打开米哈游启动器
74 | 2. 点击“开始游戏”或“更新游戏”旁的菜单
75 | 
76 |
77 | 3. 点击“游戏设置”选项
78 | 4. 找到启动器显示的文件夹 (**注意:图片仅供参考,目录在您自己的电脑上与图片中不同!**)
79 |
80 | 5. 您也可以点击下方的“打开所在目录”按钮,直接点击地址栏并复制目录。如果您已从官方启动器预下载了更新文件,可以在内看到 `chunk` 文件夹。**本更新程序不适用于此种更新模式,请另行下载 hdiff 更新包。** 有关信息参见 [可用包体下载](#可用包体下载).
81 |
82 | 
83 | ## 如何使用
84 | ### 补丁工具使用 / Updater
85 | 你需要以下文件:
86 |
87 | - 游戏文件
88 | - 一个或多个zip更新包
89 | - 在 [release](https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/releases) 下载的本程序最新发行版
90 |
91 | 您可以参考这里的指示使用程序。
92 | 首先,您需要将游戏目录(目录下有 Yuansact.exe 或 GenyuanImsact.exe)输入程序,
93 | 其次,程序会询问您是否要在更新后检查文件正确性。
94 | - 输入0 - 请勿进行任何检查
95 | - 输入1 - _(推荐使用)_ 仅检查文件大小是否符合预期(过程一般在 10s 以内,大多数情况下足够)
96 | - 输入2 - 获取 MD5 进行完全检查(速度取决于硬盘性能,如果游戏数据没有存放在 SSD 之类的高速驱动器上将会需要很长时间)
97 |
98 | 然后输入您的更新包(通常是 zip 文件)**数量**。
99 | 在这之后,您只需要依次拖入所有更新包(每拖入一个包需要回车确认)即可开始更新。
100 |
101 | 通常,在更新完成后,如果您使用从官方启动器下载的游戏版本,程序将会指导您使官方启动器显示正确的游戏版本。
102 | 程序会告知您检测到的版本更新状态,如果版本正确只需按 `y` 确认即可,不想进行更改也可按 `n` 取消。
103 | 如果程序显示的版本不正确,您也可以输入正确的版本并继续。
104 |
105 | Enjoy it!
106 |
107 | ### 创建更新包 / Patch Creater
108 | 你需要以下文件:
109 |
110 | - **从官方下载的**两个版本的**已解压游戏文件**
111 | - 在 [release](https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/releases) 下载的本程序最新发行版
112 |
113 | 您可以参考这里的命令行使用指南。
114 | ```
115 | Usage: hdiffpatchcreator
116 | -from <更新前版本> <所在文件夹>
117 | -to <更新后版本> <所在文件夹>
118 | -output_to <输出文件夹,存放更新包和临时文件>
119 | [-p <前缀>] [-reverse] [--skip-check]
120 | [--only-include-pkg-defined-files [--include-audios]]
121 | ```
122 |
123 | 使用程序可以得到这样一个更新包:
124 | ```
125 | [前缀]_<更新前版本>_<更新后版本>_hdiff_<16位随机字符串>.zip
126 | ```
127 | 如 `game_3.4_8.0_hdiff_nj89iGjh4d.zip`
128 | 前缀默认为 `game`.
129 |
130 | `-reverse` 选项在创建更新包后,更换“更新前版本”与“更新后版本”并再创建一个包。
131 |
132 | `--skip-check` 选项跳过基于文件大小比较的基础检查。但注意:对于 Patch Creator,由于需要进行严格的文件比较,程序一定会对文件进行 MD5 计算。建议您在读写速度较快的存储设备上(如 SSD)进行创建更新包的操作。
133 |
134 | `--only-include-pkg-defined-files` 可以在创建更新包时忽略 `pkg_version` 定义以外的所有文件,以避免本地的热更新、缓存、错误日志等无关内容被包括在更新包中。
135 | `--only-include-pkg-defined-files` 并不包括 `Audio_*_pkg_version` 定义的特定语言配音音频包文件。如果需要包含它们,请指定 `--include-audios` 选项。
136 |
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/UpCheck.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using YYHEggEgg.Logger;
3 |
4 | namespace HappyGenyuanImsactUpdate
5 | {
6 | public enum CheckMode
7 | {
8 | None = 0,
9 | Basic = 1, //file size
10 | Full = 2, //size + md5
11 | Null = -1
12 | }
13 |
14 | public static class UpCheck
15 | {
16 | #region pkg_versions
17 | ///
18 | /// It has two formats:
19 | /// pkg_version
20 | /// Audio_[Language]_pkg_version
21 | ///
22 | public static List GetPkgVersion(DirectoryInfo datadir)
23 | {
24 | List rtns = new();
25 |
26 | string originVersionPath = $"{datadir.FullName}\\pkg_version";
27 | if (File.Exists(originVersionPath)) rtns.Add(originVersionPath);
28 | foreach (var file in datadir.GetFiles())
29 | {
30 | if (file.Name.StartsWith("Audio_") && file.Name.EndsWith("_pkg_version"))
31 | {
32 | #if DEBUG
33 | Log.Info(
34 | $"The lauguage of this audio package is {file.Name.Substring(6, file.Name.Length - 18)}.", nameof(GetPkgVersion));
35 | #endif
36 | rtns.Add(file.FullName);
37 | }
38 | }
39 | return rtns;
40 | }
41 | #endregion
42 |
43 | #region Package Check
44 | public static bool CheckByPkgVersion(DirectoryInfo datadir, List pkgversionPaths,
45 | CheckMode checkAfter)
46 | {
47 | if (checkAfter == CheckMode.None) return true;
48 |
49 | bool checkPassed = true;
50 |
51 | foreach (var pkgversionPath in pkgversionPaths)
52 | {
53 | checkPassed = checkPassed && CheckByPkgVersion(datadir, pkgversionPath, checkAfter);
54 | }
55 |
56 | return checkPassed;
57 | }
58 |
59 | public static bool CheckByPkgVersion(DirectoryInfo datadir, string pkgversionPath,
60 | CheckMode checkAfter)
61 | {
62 | if (checkAfter == CheckMode.None) return true;
63 |
64 | bool checkPassed = true;
65 | using (StreamReader versionreader = new(pkgversionPath))
66 | {
67 | while (true)
68 | {
69 | string? output = versionreader.ReadLine();
70 | if (output == null) break;
71 | else
72 | {
73 | var doce = JsonDocument.Parse(output).RootElement;
74 | /* {
75 | * "remoteName": "name.pck",
76 | * "md5": "123456QWERTYUIOPASDFGHJKLZXCVBNM",
77 | * "fileSize": 1919810
78 | * }
79 | */
80 | string checkName = datadir.FullName + '\\'
81 | + doce.GetProperty("remoteName").GetString();
82 | //command: -f (original file) (patch file) (output file)
83 | // hpatchz -f name.pck name.pck.hdiff name.pck
84 | var checkFile = new FileInfo(checkName);
85 | string checkPathstd = checkFile.FullName;
86 |
87 | Log.PushLog($"Checking: {checkPathstd}",
88 | checkAfter == CheckMode.Full ? LogLevel.Information : LogLevel.Verbose,
89 | nameof(CheckByPkgVersion));
90 |
91 |
92 | if (!File.Exists(checkPathstd))
93 | {
94 | Log.Warn(ReportFileError(checkPathstd, "The file does not exist"), nameof(CheckByPkgVersion));
95 | checkPassed = false;
96 | continue;
97 | }
98 |
99 | RemoveReadOnly(checkFile);
100 |
101 | #region File Size Check
102 | long sizeExpected = doce.GetProperty("fileSize").GetInt64();
103 | if (checkFile.Length != sizeExpected)
104 | {
105 | Log.Warn(ReportFileError(checkPathstd, "The file is not correct"), nameof(CheckByPkgVersion));
106 | checkPassed = false;
107 | continue;
108 | }
109 | #endregion
110 |
111 | if (checkAfter == CheckMode.Full)
112 | {
113 | #region MD5 Check
114 | string? md5Expected = doce.GetProperty("md5").GetString();
115 | if (MyMD5.GetMD5HashFromFile(checkPathstd) != md5Expected)
116 | {
117 | Log.Warn(ReportFileError(checkPathstd, "The file is not correct"), nameof(CheckByPkgVersion));
118 | checkPassed = false;
119 | continue;
120 | }
121 | #endregion
122 | }
123 | }
124 | }
125 | }
126 |
127 | return checkPassed;
128 | }
129 |
130 | private static void RemoveReadOnly(FileInfo checkFile)
131 | {
132 | if (checkFile.Attributes.HasFlag(FileAttributes.ReadOnly))
133 | checkFile.Attributes = FileAttributes.Normal;
134 | }
135 |
136 | private static string ReportFileError(string checkPathstd, string reason)
137 | {
138 | return $"{reason} : {checkPathstd}";
139 | }
140 | #endregion
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [中文](https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/blob/main/README_CN.md) | EN
2 |
3 | # HappyGenyuanImsactUpdate
4 | A hdiff-using update program of a certain anime game.
5 |
6 | ## Available Package Download
7 | You can turn to this repository to download files from `the anime game company`:
8 |
9 | - [Anime Game Downloads Archive](https://git.xeondev.com/YYHEggEgg/GI-Download-Library)
10 | - [Honkai: March 7th Downloads Archive](https://github.com/keitarogg/HSR-Download-Library)
11 | - [3Z Downloads Archive](https://github.com/360NENZ/ZZZ-Download-Library)
12 |
13 | ## Annoucements
14 | ### License Change Notice
15 | As of Aug 30, 2023, this project has been re-licensed under the MIT License. All previous and future contributions are subject to this new license.
16 |
17 | ### [Don't use this for ->3.6 Update](https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/issues/15)
18 | From 3.6, miHoYo changed `StreamingAssets/Audio/GeneratedSoundBanks/Windows` to `StreamingAssets/AudioAssets`, but the launcher is responsible for the modification, not the update package.
19 |
20 | This won't be fixed as I don't want to pollute the code any more.
21 |
22 | This is most probably a temporaily a corner case and **this Updater program is still avaliable in >=3.7 versions**. For more information, go to [this issue](https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/issues/15).
23 |
24 | ### Explanation of Limited Support
25 |
26 | Since version 4.6, HoYoPlay has gradually adopted `sophon chunk` as its primary update distribution and installation mode.
27 |
28 | This program only adapts to hdiff update packages, so it will not add support for the corresponding feature. However, this software will still receive basic bug fixes and optimization updates (if necessary).
29 |
30 | ## New feature
31 |
32 | ### v3.2.4
33 | - When using the `Updater`, if the executable file of the game (that can be recognized by program) does not exist in the directory, users are allowed to manually confirm to continue.
34 |
35 | ### v3.2.3
36 |
37 | - Due to the fact that the provided `7z.exe`, `hpatchz.exe` and `hdiffz.exe` are all 64-bit programs, the support plan for 32-bit Windows has been cancelled.
38 |
39 | #### Patch Creator
40 |
41 | - The `--only-include-pkg-defined-files` and `--include-audios` option have been supported. For more information, please refer to [Usage - How to create a patch / Patch Creator](#how-to-create-a-patch--patch-creater).
42 |
43 | ### v3.2.2
44 | #### Updater
45 | - Fixed the issue where the Updater won't work with `Honkai: March 7th`.
46 | - The software is now packed with dotnet runtime (6.0), allowing user not to install runtime.
47 | - Release version supported Windows x86.
48 | - Supported balloon tip notice on Windows 7 and newer (notifications instead on >= Windows 10).
49 |
50 | ### v3.2.1
51 | #### Updater
52 | - Fixed the issue where the Updater won't work with anime game version <= `1.5`.
53 | - Fixed an issue with Audio packages where the Updater won't detect them in anime game version >= `3.6`.
54 | - Supported packages from `Honkai: March 7th`.
55 |
56 | #### Patch Creator
57 | Now, when the Patch Creator detected that the given two directories have files with the same names, it will request the user for review and confirmation.
58 |
59 | ### v3.2.0
60 | - Migrated the entire project to `EggEgg.CSharp-Logger v3.0.0`.
61 | Now, whether updating or creating update packages, the logs will be kept in the program directory, but the debug logs will not be displayed on the console and will be output to `latest.debug.log`.
62 | - During updates, hdiff patch failure is supported for automatic retries (3 times).
63 | - A warning will be triggered if the program is run without extracting it.
64 |
65 | ### v3.0.0
66 | Now you can create hdiff patch packages on your own, like `the anime game company`!
67 | Just invoke `Patch Creater\HDiffPatchCreator.exe` in command line.
68 |
69 | Notice: It's highly recommended to **use original packages from the anime game company only** to create patches.
70 |
71 | Files from your own computer will probably contains live updates and caches, which some users don't have. **Putting caches into the package will be likely to make your personal information got leaked.** For more information, see [Available Package Download](#available-package-download).
72 |
73 | ## Usage
74 | ### How to use the patcher / Updater
75 | You should have the following things:
76 |
77 | - A game (for sure)
78 | - One or more upgrade packages (zip file)
79 | - A release of this program
80 |
81 | You can use it by the instruction here or in the program.
82 | First of all, it will ask for the full path of game directory.
83 | Next, it will ask you to choose how to check the files after update:
84 | - 0 - Don't have any check
85 | - 1 - _(Recommended)_ Only check file size (usually < 10s, very fast, in most cases enough)
86 | - 2 - Full check on MD5 (the speed depends on your disk, it will take a long time if the data isn't on a fast-speed drive like SSD)
87 |
88 | Then, you need to type how many zip files you have.
89 | After that, you just need to drag zip files one by one (press enter after dragging in), then the update program will finish the update process automatically.
90 |
91 | Enjoy it!
92 |
93 | ### How to create a patch / Patch Creater
94 | You can refer to the following command line usage.
95 | ```
96 | Usage: hdiffpatchcreator
97 | -from
98 | -to
99 | -output_to
100 | [-p ] [-reverse] [--skip-check]
101 | ```
102 |
103 | By using this program, you can get a package named:
104 | ```
105 | [prefix]___hdiff_.zip
106 | ```
107 | e.g. `game_3.4_8.0_hdiff_nj89iGjh4d.zip`
108 | If not given, prefix will be `game`.
109 |
110 | `-reverse` option: After package is created, reverse 'versionFrom' and 'versionTo' and create another package.
111 |
112 | `--skip-check` option: skip the Basic Mode check (only compare file size). Notice: For the patch creator, MD5 computing when comparing files is essential. You are recommended to create Update packages on a disk with high I/O speed (like SSD).
113 |
114 | `--only-include-pkg-defined-files` option: ignore all files not defined in 'pkg_version' file to avoid unrelated content, like local live updates, cache and error logs, to be included in the update package.
115 | `--only-include-pkg-defined-files` excludes specified language audio files defined in `Audio_*_pkg_version`. If want to include them, use `--include-audios` option.
116 |
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/Helper.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Windows.Forms;
3 | using YYHEggEgg.Logger;
4 |
5 | namespace HappyGenyuanImsactUpdate
6 | {
7 | ///
8 | /// Methods in console interface.
9 | ///
10 | public class Helper
11 | {
12 | ///
13 | /// 7z.exe and hdiff.exe check.
14 | ///
15 | public static void CheckForTools(bool requirediff = false)
16 | {
17 | bool ok = true;
18 | if (!File.Exists($"{exePath}\\7z.exe"))
19 | {
20 | Log.Erro("7z.exe was missing. " +
21 | "Please copy it to the path of this program " +
22 | "or download the newest release in " +
23 | "https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/releases");
24 | ok = false;
25 | }
26 | if (!File.Exists($"{exePath}\\hpatchz.exe"))
27 | {
28 | Log.Erro("hpatchz.exe was missing. " +
29 | "Please copy it to the path of this program " +
30 | "or download the newest release in " +
31 | "https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/releases");
32 | ok = false;
33 | }
34 | if (requirediff)
35 | {
36 | if (!File.Exists($"{exePath}\\hdiffz.exe"))
37 | {
38 | Log.Erro("hpatchz.exe was missing. " +
39 | "Please copy it to the path of this program " +
40 | "or download the newest release in " +
41 | "https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/releases");
42 | ok = false;
43 | }
44 | }
45 | if (!ok)
46 | {
47 | Log.Erro("The program will exit after an enter. " +
48 | "Please get missing file(s) the right location and restart.");
49 | Console.ReadLine();
50 | Environment.Exit(1);
51 | }
52 | }
53 |
54 | ///
55 | /// Program executable path.
56 | ///
57 | public static string exePath { get => AppDomain.CurrentDomain.BaseDirectory; }
58 |
59 | //"ei hei"
60 | public static readonly string[] certaingames = new string[]
61 | {
62 | // anime game
63 | "\u0067\u0065\u006e\u0073\u0068\u0069\u006e\u0069\u006d\u0070\u0061\u0063\u0074",
64 | "\u0079\u0075\u0061\u006e\u0073\u0068\u0065\u006e",
65 | // Houtai: March 7th
66 | "\u0053\u0074\u0061\u0072\u0052\u0061\u0069\u006C",
67 | // "Reserved Executable Name:/\\<>" // OS exename == CN
68 | // Sleep
69 | "\u004a\u0075\u0065\u0051\u0075\u004c\u0069\u006e\u0067", // Former used
70 | "\u005a\u0065\u006e\u006c\u0065\u0073\u0073\u005a\u006f\u006e\u0065\u005a\u0065\u0072\u006f",
71 | };
72 |
73 | public static bool AnyCertainGameExists(DirectoryInfo checkdir)
74 | {
75 | foreach (var certaingame in Helper.certaingames)
76 | {
77 | if (File.Exists($"{checkdir.FullName}\\{certaingame}.exe")) return true;
78 | if (File.Exists($"{checkdir.FullName}\\{certaingame}Beta.exe")) return true;
79 | }
80 | return false;
81 | }
82 |
83 | private static string? _tmpdir = null;
84 | private static FileStream? _tmpdirhandle = null;
85 | public static string tempPath
86 | {
87 | get
88 | {
89 | if (string.IsNullOrEmpty(_tmpdir))
90 | {
91 | _tmpdir = $"{exePath}\\Temp-HappyGenyuanImsactUpdate-{DateTime.Now:yyyyMMdd-HHmmss}-{new Random().NextInt64()}";
92 | Directory.CreateDirectory(_tmpdir);
93 | _tmpdirhandle = File.Create($"{_tmpdir}\\_Created By HappyGenyuanImsactUpdate for temp files.txt");
94 | }
95 | return _tmpdir;
96 | }
97 | }
98 |
99 | public static void TryDisposeTempFiles()
100 | {
101 | try
102 | {
103 | if (_tmpdir == null) return;
104 | _tmpdirhandle?.Dispose();
105 | Directory.Delete(_tmpdir, true);
106 | _tmpdir = null;
107 | }
108 | catch (Exception ex)
109 | {
110 | Log.Warn($"----------------------------\n{ex}\n----------------------------", nameof(TryDisposeTempFiles));
111 | Log.Warn($"Failed to delete program temp files. You may delete directory {_tmpdir} by yourself.", nameof(TryDisposeTempFiles));
112 | }
113 | }
114 |
115 | public static void CheckForRunningInZipFile()
116 | {
117 | if (Environment.CurrentDirectory.StartsWith(
118 | $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/Local/Temp"))
119 | {
120 | Log.Warn("You may be running the program without extracting, and that will make" +
121 | "logs lost since closing the zip file. ");
122 | Log.Warn("Consider extracting the program in case you meet some problem updating.");
123 | }
124 | }
125 |
126 | public static void ShowWarningBalloonTip(int persisting_ms, string title, string text)
127 | => ShowBalloonTipCore(title, text, persisting_ms, SystemIcons.Warning);
128 |
129 | public static void ShowInformationBalloonTip(int persisting_ms, string title, string text)
130 | => ShowBalloonTipCore(title, text, persisting_ms, SystemIcons.Information);
131 |
132 | public static void ShowErrorBalloonTip(int persisting_ms, string title, string text)
133 | => ShowBalloonTipCore(title, text, persisting_ms, SystemIcons.Error);
134 |
135 | private static void ShowBalloonTipCore(string title, string text, int persisting_ms,
136 | Icon notifyShowingIcon)
137 | {
138 | // Generated by ChatGPT
139 | NotifyIcon notifyIcon = new NotifyIcon();
140 | notifyIcon.Visible = true;
141 | notifyIcon.Icon = notifyShowingIcon;
142 | notifyIcon.BalloonTipTitle = title;
143 | notifyIcon.BalloonTipText = text;
144 | notifyIcon.ShowBalloonTip(persisting_ms);
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/OuterInvoke.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using YYHEggEgg.Logger;
3 |
4 | namespace YYHEggEgg.Utils
5 | {
6 | public struct OuterInvokeInfo
7 | {
8 | ///
9 | /// The path of the executable.
10 | ///
11 | public string ProcessPath;
12 | ///
13 | /// The command line args that will be provided to the process.
14 | ///
15 | public string? CmdLine;
16 | ///
17 | /// The notice that will be send to the user when the invoke start.
18 | ///
19 | public string? StartingNotice;
20 | ///
21 | /// The reason for auto terminate. May be a complete sentence.
22 | /// If it's given not null, the program will auto terminate before the method return.
23 | ///
24 | public string? AutoTerminateReason;
25 | ///
26 | /// The working directory of the program. The default value is .
27 | ///
28 | public string? WorkingDir;
29 | }
30 |
31 | public static class OuterInvoke
32 | {
33 | private static async Task MinorRun(ProcessStartInfo startInfo, int max_rerun = 0)
34 | {
35 | Process? p = Process.Start(startInfo);
36 | await (p?.WaitForExitAsync() ?? Task.CompletedTask);
37 | if (p?.ExitCode != 0 && max_rerun > 0)
38 | {
39 | return await MinorRun(startInfo, max_rerun - 1);
40 | }
41 | return p?.ExitCode ?? int.MinValue;
42 | }
43 |
44 | private static async Task InnerRun(OuterInvokeInfo invokeInfo, int max_rerun = 0)
45 | {
46 | ProcessStartInfo startInfo = new ProcessStartInfo(
47 | invokeInfo.ProcessPath, invokeInfo.CmdLine ?? "")
48 | {
49 | WorkingDirectory = invokeInfo.WorkingDir ?? Environment.CurrentDirectory
50 | };
51 | if (invokeInfo.StartingNotice != null) Log.Info(invokeInfo.StartingNotice, nameof(OuterInvoke));
52 | return await MinorRun(startInfo, max_rerun);
53 | }
54 |
55 | ///
56 | /// Run a process with certain args and wait for exit.
57 | ///
58 | /// The details of the invoke.
59 | /// The exut code for the program's auto terminate.
60 | /// The maximum retry times if process exited with not 0.
61 | /// The exit code of the program.
62 | public static async Task Run(OuterInvokeInfo invokeInfo,
63 | int autoTerminateCode = -1, int max_rerun = 0)
64 | {
65 | var exitcode = await InnerRun(invokeInfo, max_rerun);
66 | if (exitcode != 0 && invokeInfo.AutoTerminateReason != null)
67 | {
68 | Log.Erro($"{invokeInfo.AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
69 | Log.Info("Press any key to exit...");
70 | Console.ReadLine();
71 | Environment.Exit(autoTerminateCode);
72 | }
73 | return exitcode;
74 | }
75 |
76 | ///
77 | /// Run multiple processes with certain args and wait for exit.
78 | ///
79 | /// The details of the first invoke.
80 | /// The details of the second invoke.
81 | /// The exut code for the program's auto terminate.
82 | /// The maximum retry times if process exited with not 0.
83 | /// The exit code of the program.
84 | public static async Task RunMultiple(
85 | OuterInvokeInfo invokeInfo1, OuterInvokeInfo invokeInfo2,
86 | int autoTerminateCode = -1, int max_rerun = 0)
87 | {
88 | int[] rtn = new int[2];
89 | rtn[0] = await InnerRun(invokeInfo1, max_rerun);
90 | rtn[1] = await InnerRun(invokeInfo2, max_rerun);
91 |
92 | bool exiting = false;
93 | if (rtn[0] != 0 && invokeInfo1.AutoTerminateReason != null)
94 | {
95 | exiting = true;
96 | Log.Erro($"{invokeInfo1.AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
97 | }
98 | if (rtn[1] != 0 && invokeInfo2.AutoTerminateReason != null)
99 | {
100 | exiting = true;
101 | Log.Erro($"{invokeInfo2.AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
102 | }
103 | if (exiting)
104 | {
105 | Log.Info("Press any key to exit...");
106 | Console.ReadLine();
107 | Environment.Exit(autoTerminateCode);
108 | }
109 | return rtn;
110 | }
111 |
112 | ///
113 | /// Run multiple processes with certain args and wait for exit.
114 | ///
115 | /// The details of the first invoke.
116 | /// The details of the second invoke.
117 | /// The details of the third invoke.
118 | /// The exut code for the program's auto terminate.
119 | /// The maximum retry times if process exited with not 0.
120 | /// The exit code of the program.
121 | public static async Task RunMultiple(
122 | OuterInvokeInfo invokeInfo1, OuterInvokeInfo invokeInfo2, OuterInvokeInfo invokeInfo3,
123 | int autoTerminateCode = -1, int max_rerun = 0)
124 | {
125 | int[] rtn = new int[3];
126 | rtn[0] = await InnerRun(invokeInfo1, max_rerun);
127 | rtn[1] = await InnerRun(invokeInfo2, max_rerun);
128 | rtn[2] = await InnerRun(invokeInfo3, max_rerun);
129 |
130 | bool exiting = false;
131 | if (rtn[0] != 0 && invokeInfo1.AutoTerminateReason != null)
132 | {
133 | exiting = true;
134 | Log.Erro($"{invokeInfo1.AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
135 | }
136 | if (rtn[1] != 0 && invokeInfo2.AutoTerminateReason != null)
137 | {
138 | exiting = true;
139 | Log.Erro($"{invokeInfo2.AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
140 | }
141 | if (rtn[2] != 0 && invokeInfo3.AutoTerminateReason != null)
142 | {
143 | exiting = true;
144 | Log.Erro($"{invokeInfo3.AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
145 | }
146 | if (exiting)
147 | {
148 | Log.Info("Press any key to exit...");
149 | Console.ReadLine();
150 | Environment.Exit(autoTerminateCode);
151 | }
152 | return rtn;
153 | }
154 |
155 | ///
156 | /// Syncronously run multiple processes with certain args and wait for exit.
157 | ///
158 | /// The details of the invokes.
159 | /// The exut code for the program's auto terminate.
160 | /// The maximum retry times if process exited with not 0.
161 | /// The exit code of the program.
162 | public static async Task RunMultiple(OuterInvokeInfo[] invokeInfos,
163 | int autoTerminateCode = -1, int max_rerun = 0)
164 | {
165 | int[] rtn = new int[invokeInfos.Length];
166 | for (int i = 0; i < invokeInfos.Length; i++)
167 | {
168 | rtn[i] = await InnerRun(invokeInfos[i], max_rerun);
169 | }
170 |
171 | bool exiting = false;
172 | for (int i = 0; i < invokeInfos.Length; i++)
173 | {
174 | if (rtn[i] != 0 && invokeInfos[i].AutoTerminateReason != null)
175 | {
176 | exiting = true;
177 | Log.Erro($"{invokeInfos[i].AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
178 | }
179 | }
180 | if (exiting)
181 | {
182 | Log.Info("Press any key to exit...");
183 | Console.ReadLine();
184 | Environment.Exit(autoTerminateCode);
185 | }
186 | return rtn;
187 | }
188 |
189 | ///
190 | /// Syncronously run multiple processes with certain args and wait for exit.
191 | ///
192 | /// The details of the invokes.
193 | /// The exut code for the program's auto terminate.
194 | /// The maximum retry times if process exited with not 0.
195 | /// The exit code of the program.
196 | public static async Task RunMultiple(List invokeInfos,
197 | int autoTerminateCode = -1, int max_rerun = 0)
198 | {
199 | int[] rtn = new int[invokeInfos.Count];
200 | for (int i = 0; i < invokeInfos.Count; i++)
201 | {
202 | rtn[i] = await InnerRun(invokeInfos[i], max_rerun);
203 | }
204 |
205 | bool exiting = false;
206 | for (int i = 0; i < invokeInfos.Count; i++)
207 | {
208 | if (rtn[i] != 0 && invokeInfos[i].AutoTerminateReason != null)
209 | {
210 | exiting = true;
211 | Log.Erro($"{invokeInfos[i].AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
212 | }
213 | }
214 | if (exiting)
215 | {
216 | Log.Info("Press any key to exit...");
217 | Console.ReadLine();
218 | Environment.Exit(autoTerminateCode);
219 | }
220 | return rtn;
221 | }
222 |
223 | ///
224 | /// Run multiple processes parallel with certain args and wait for exit. Notice: may cause unexpected behaviour.
225 | ///
226 | /// The details of the invokes.
227 | /// The exut code for the program's auto terminate.
228 | /// The maximum retry times if process exited with not 0.
229 | /// The exit code of the program.
230 | public static int[] RunParallel(OuterInvokeInfo[] invokeInfos,
231 | int autoTerminateCode = -1, int max_rerun = 0)
232 | {
233 | int[] rtn = new int[invokeInfos.Length];
234 | Parallel.For(0, invokeInfos.Length, async (i) =>
235 | {
236 | rtn[i] = await InnerRun(invokeInfos[i], max_rerun);
237 | });
238 |
239 | bool exiting = false;
240 | for (int i = 0; i < invokeInfos.Length; i++)
241 | {
242 | if (rtn[i] != 0 && invokeInfos[i].AutoTerminateReason != null)
243 | {
244 | exiting = true;
245 | Log.Erro($"{invokeInfos[i].AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
246 | }
247 | }
248 | if (exiting)
249 | {
250 | Log.Info("Press any key to exit...");
251 | Console.ReadLine();
252 | Environment.Exit(autoTerminateCode);
253 | }
254 | return rtn;
255 | }
256 |
257 | ///
258 | /// Run multiple processes parallel with certain args and wait for exit. Notice: may cause unexpected behaviour.
259 | ///
260 | /// The details of the invokes.
261 | /// The exut code for the program's auto terminate.
262 | /// The maximum retry times if process exited with not 0.
263 | /// The exit code of the program.
264 | public static int[] RunParallel(List invokeInfos,
265 | int autoTerminateCode = -1, int max_rerun = 0)
266 | {
267 | int[] rtn = new int[invokeInfos.Count];
268 | Parallel.For(0, invokeInfos.Count, async (i) =>
269 | {
270 | rtn[i] = await InnerRun(invokeInfos[i], max_rerun);
271 | });
272 |
273 | bool exiting = false;
274 | for (int i = 0; i < invokeInfos.Count; i++)
275 | {
276 | if (rtn[i] != 0 && invokeInfos[i].AutoTerminateReason != null)
277 | {
278 | exiting = true;
279 | Log.Erro($"{invokeInfos[i].AutoTerminateReason} Exit code is {autoTerminateCode}. ", "OuterInvoke");
280 | }
281 | }
282 | if (exiting)
283 | {
284 | Log.Info("Press any key to exit...");
285 | Console.ReadLine();
286 | Environment.Exit(autoTerminateCode);
287 | }
288 | return rtn;
289 | }
290 | }
291 | }
--------------------------------------------------------------------------------
/HDiffPatchCreator/Program.cs:
--------------------------------------------------------------------------------
1 | using HappyGenyuanImsactUpdate;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Text;
6 | using System.Text.Json;
7 | using System.Web;
8 | using YYHEggEgg.Logger;
9 | using YYHEggEgg.Utils;
10 |
11 | namespace HDiffPatchCreator
12 | {
13 | internal class Program
14 | {
15 | static string path7z = $"{Helper.exePath}\\7z.exe";
16 | static string hdiffzPath = $"{Helper.exePath}\\hdiffz.exe";
17 |
18 | static async Task Main(string[] args)
19 | {
20 | Log.Initialize(new LoggerConfig(
21 | max_Output_Char_Count: -1,
22 | use_Console_Wrapper: false,
23 | use_Working_Directory: false,
24 | global_Minimum_LogLevel: LogLevel.Verbose,
25 | console_Minimum_LogLevel: LogLevel.Information,
26 | debug_LogWriter_AutoFlush: true));
27 |
28 | string? version = Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
29 | Log.Info($"----------Happy hdiff creator (v{version ?? ""})----------");
30 | Helper.CheckForRunningInZipFile();
31 |
32 | Helper.CheckForTools();
33 |
34 | Log.Info("This program is used to create Patch from two versions of a certain anime game.");
35 |
36 | string verFrom = "Unknown", verTo = "Unknown", prefix = "game";
37 | DirectoryInfo? dirFrom = null, dirTo = null, outputAt = null;
38 | bool createReverse = false, performCheck = true,
39 | onlyIncludeDefinedFiles = false, includeAudioVersions = false;
40 | #region Command Line
41 | bool[] arghaveread = new bool[8];
42 |
43 | for (int i = 0; i < args.Length; i++)
44 | {
45 | if (args[i].StartsWith('"') && args[i].EndsWith('"'))
46 | args[i] = args[i].Substring(1, args[i].Length - 2);
47 | }
48 |
49 | if (args.Length > 0)
50 | {
51 | for (int i = 0; i < args.Length; i++)
52 | {
53 | switch (args[i])
54 | {
55 | case "-from":
56 | ReadAssert(0);
57 | verFrom = HttpUtility.UrlEncode(args[i + 1]);
58 | dirFrom = new DirectoryInfo(args[i + 2]);
59 | i += 2;
60 | break;
61 | case "-to":
62 | ReadAssert(1);
63 | verTo = HttpUtility.UrlEncode(args[i + 1]);
64 | dirTo = new DirectoryInfo(args[i + 2]);
65 | i += 2;
66 | break;
67 | case "-output_to":
68 | ReadAssert(2);
69 | outputAt = new DirectoryInfo(args[i + 1]);
70 | i += 1;
71 | break;
72 | case "-p":
73 | ReadAssert(3);
74 | prefix = HttpUtility.UrlEncode(args[i + 1]);
75 | i += 1;
76 | break;
77 | case "-reverse":
78 | ReadAssert(4);
79 | createReverse = true;
80 | break;
81 | case "--skip-check":
82 | ReadAssert(5);
83 | performCheck = false;
84 | break;
85 | case "--only-include-pkg-defined-files":
86 | ReadAssert(6);
87 | onlyIncludeDefinedFiles = true;
88 | break;
89 | case "--include-audios":
90 | ReadAssert(7);
91 | includeAudioVersions = true;
92 | break;
93 | default:
94 | Usage();
95 | return;
96 | }
97 | }
98 | }
99 | else
100 | {
101 | Usage();
102 | return;
103 | }
104 | #endregion
105 |
106 | #region Input Assert
107 | if (dirFrom == null || dirTo == null || outputAt == null)
108 | {
109 | Log.Erro("Input param lack!");
110 | Usage();
111 | Environment.Exit(1);
112 | }
113 | if (!Helper.AnyCertainGameExists(dirFrom))
114 | {
115 | Log.Warn("WARNING: No known game executable under game path. (verFrom)");
116 | }
117 | if (!Helper.AnyCertainGameExists(dirTo))
118 | {
119 | Log.Warn("WARNING: No known game executable under game path. (verTo)");
120 | }
121 | if (!onlyIncludeDefinedFiles && includeAudioVersions)
122 | {
123 | Log.Erro("--include-audios option is only valid when --only-include-pkg-defined-files option exists!", "InputAssert");
124 | Environment.Exit(1);
125 | }
126 | #endregion
127 |
128 | if (performCheck)
129 | {
130 | if (!HappyGenyuanImsactUpdate.Program.UpdateCheck(dirFrom, CheckMode.Basic)
131 | || !HappyGenyuanImsactUpdate.Program.UpdateCheck(dirTo, CheckMode.Basic))
132 | {
133 | Log.Erro("Original files not correct. Not supported in current version.");
134 | Environment.Exit(1);
135 | }
136 | }
137 |
138 | // Take a snapshot of the file system
139 | Log.Info($"Start Emunerating directories, it'll probably take a long time...");
140 | IEnumerable list1 = dirFrom.GetFiles("*.*", SearchOption.AllDirectories);
141 | IEnumerable list2 = dirTo.GetFiles("*.*", SearchOption.AllDirectories);
142 |
143 | #region Select files (--only-include-pkg-defined-files)
144 | if (onlyIncludeDefinedFiles)
145 | {
146 | List pkgVersions = new()
147 | { Path.GetFullPath("pkg_version", dirFrom.FullName) };
148 | if (includeAudioVersions)
149 | pkgVersions.AddRange(UpCheck.GetPkgVersion(dirFrom));
150 |
151 | var definedFiles1 = new SortedSet((
152 | from pkgVersion in pkgVersions
153 | from json in File.ReadLines(pkgVersion)
154 | let doc = JsonDocument.Parse(json)
155 | let fullName = Path.GetFullPath(dirFrom.FullName + '/'
156 | + doc.RootElement.GetProperty("remoteName").GetString())
157 | select fullName).Concat(
158 | from pkgVersion in pkgVersions
159 | select Path.Combine(dirFrom.FullName, pkgVersion)));
160 | list1 = from fileInfo in list1
161 | where definedFiles1.Contains(fileInfo.FullName)
162 | select fileInfo;
163 |
164 | pkgVersions = new()
165 | { Path.GetFullPath("pkg_version", dirTo.FullName) };
166 | if (includeAudioVersions)
167 | pkgVersions.AddRange(UpCheck.GetPkgVersion(dirTo));
168 |
169 | var definedFiles2 = new SortedSet((
170 | from pkgVersion in pkgVersions
171 | from json in File.ReadLines(pkgVersion)
172 | let doc = JsonDocument.Parse(json)
173 | let fullName = Path.GetFullPath(dirTo.FullName + '/'
174 | + doc.RootElement.GetProperty("remoteName").GetString())
175 | select fullName).Concat(
176 | from pkgVersion in pkgVersions
177 | select Path.Combine(dirTo.FullName, pkgVersion)));
178 | list2 = from fileInfo in list2
179 | where definedFiles2.Contains(fileInfo.FullName)
180 | select fileInfo;
181 | }
182 | #endregion
183 |
184 | //A custom file comparer defined below
185 | FileCompare cmp = new FileCompare(dirFrom, dirTo);
186 |
187 | #region Fail Tips
188 | // This query determines whether the two folders contain
189 | // identical file lists, based on the custom file comparer
190 | // that is defined in the FileCompare class.
191 | // The query executes immediately because it returns a bool.
192 | bool areIdentical = list1.SequenceEqual(list2, cmp);
193 |
194 | if (areIdentical == true)
195 | {
196 | Log.Warn("The two folders are the same! Seem not need patch.");
197 | Log.Info($"from {verFrom}: {dirFrom}");
198 | Log.Info($"to {verTo}: {dirTo}");
199 | Log.Warn($"Please confirm the paths are true. Press Enter to continue, or Press Ctrl+C to cancel.");
200 | Console.ReadLine();
201 | }
202 | #endregion
203 |
204 | await CreatePatch(list1, list2, dirFrom, dirTo,
205 | $"{outputAt}\\{prefix}_{verFrom}_{verTo}_hdiff_{Randomstr(16)}.zip", cmp);
206 |
207 | if (createReverse)
208 | {
209 | await CreatePatch(list2, list1, dirTo, dirFrom,
210 | $"{outputAt}\\{prefix}_{verTo}_{verFrom}_hdiff_{Randomstr(16)}.zip", cmp);
211 | }
212 |
213 | #region Multiple Read Assert
214 | void ReadAssert(int expected)
215 | {
216 | if (arghaveread[expected])
217 | {
218 | Log.Erro("Duplicated param!");
219 | Usage();
220 | Environment.Exit(1);
221 | }
222 | arghaveread[expected] = true;
223 | }
224 | #endregion
225 | }
226 |
227 | static void Usage()
228 | {
229 | Log.Info("Usage: hdiffpatchcreator", "CommandLine");
230 | Log.Info(" -from ", "CommandLine");
231 | Log.Info(" -to ", "CommandLine");
232 | Log.Info(" -output_to ", "CommandLine");
233 | Log.Info(" [-p ] [-reverse] [--skip-check]", "CommandLine");
234 | Log.Info(" [--only-include-pkg-defined-files [--include-audios]]", "CommandLine");
235 | Log.Info("", "CommandLine");
236 | Log.Info("By using this program, you can get a package named: ", "CommandLine");
237 | Log.Info("[prefix]___hdiff_.zip", "CommandLine");
238 | Log.Info("e.g. game_3.4_8.0_hdiff_nj89iGjh4d.zip", "CommandLine");
239 | Log.Info("If not given, prefix will be 'game'.", "CommandLine");
240 | Log.Info("", "CommandLine");
241 | Log.Info("-reverse: After package is created, reverse 'versionFrom' and 'versionTo' and create another package.", "CommandLine");
242 | Log.Info("", "CommandLine");
243 | Log.Info("--skip-check: skip the check (Basic Mode, only compare file size). ", "CommandLine");
244 | Log.Info("Notice: For the patch creator, MD5 computing when comparing files is essential.", "CommandLine");
245 | Log.Info("You can't choose not to use it.", "CommandLine");
246 | Log.Info("", "CommandLine");
247 | Log.Info("--only-include-pkg-defined-files: ignore all files not defined in 'pkg_version' file.", "CommandLine");
248 | Log.Info(" --include-audios: Apply files defined in 'Audio_*_version' with an exception for ignore.", "CommandLine");
249 | }
250 |
251 | static async Task CreatePatch(IEnumerable filesFrom, IEnumerable filesTo,
252 | DirectoryInfo dirFrom, DirectoryInfo dirTo, string createpakPath, FileCompare cmp)
253 | {
254 | var tmpFilePath = $"{new FileInfo(createpakPath).DirectoryName}\\Temp-{Randomstr(32)}";
255 | Directory.CreateDirectory(tmpFilePath);
256 |
257 | // Files in From but not in To should be deleted
258 | var fromOnly = filesFrom.Except(filesTo, cmp);
259 | #region deletefiles.txt
260 | StringBuilder strb = new();
261 | foreach (var file in fromOnly)
262 | {
263 | strb.AppendLine(FileCompare.GetRelativePath(file, dirFrom));
264 | }
265 | File.WriteAllText($"{tmpFilePath}\\deletefiles.txt", strb.ToString());
266 | #endregion
267 |
268 | // Files in To but not in From could be directly reserved
269 | var toOnly = filesTo.Except(filesFrom, cmp);
270 | #region Copy
271 | foreach (var file in toOnly)
272 | {
273 | var newfile = new FileInfo($"{tmpFilePath}\\{FileCompare.GetRelativePath(file, dirTo)}");
274 | CreateDirectoryFor(newfile);
275 | Log.Info($"Copying: {file.FullName} -> {newfile.FullName}", $"{nameof(CreatePatch)}_FileCopy");
276 | File.Copy(file.FullName, newfile.FullName);
277 | }
278 | #endregion
279 |
280 | // Files in both should create hdiff patch
281 | var queryCommonFiles = filesFrom.Intersect(filesTo, cmp);
282 | #region Hdiff Create
283 | strb = new();
284 | foreach (var file in queryCommonFiles)
285 | {
286 | var relativePath = FileCompare.GetRelativePath(file, dirFrom);
287 |
288 | var fromPath = new FileInfo($"{dirFrom}\\{relativePath}");
289 | var toPath = new FileInfo($"{dirTo}\\{relativePath}");
290 | var diffPath = new FileInfo($"{tmpFilePath}\\{relativePath}.hdiff");
291 |
292 | if (cmp.RealEqual(fromPath, toPath))
293 | {
294 | Log.Verb($"Skip: {fromPath.FullName} == {toPath.FullName}", $"{nameof(CreatePatch)}_Hdiff");
295 | continue;
296 | }
297 |
298 | if (file.Name.EndsWith("pkg_version"))
299 | {
300 | // pkg_versions shouldn't use hdiff
301 | File.Copy(toPath.FullName, $"{tmpFilePath}\\{relativePath}");
302 | continue;
303 | }
304 |
305 | if (await InvokeHDiffz(fromPath.FullName, toPath.FullName, diffPath.FullName))
306 | {
307 | if (diffPath.Length >= toPath.Length)
308 | {
309 | // Fallback to diff
310 | Log.Info($"HDiff = {diffPath.Length}, To = {toPath.Length}, fallback to diff", $"{nameof(CreatePatch)}_Hdiff");
311 | diffPath.Delete();
312 | File.Copy(toPath.FullName, $"{tmpFilePath}\\{relativePath}");
313 | }
314 | else
315 | {
316 | // Don't mistake it to diffPath!
317 | strb.AppendLine($"{{\"remoteName\": \"{relativePath.Replace('\\', '/')}\"}}");
318 | }
319 | }
320 | else
321 | {
322 | Log.Warn($"HDiff failed, fallback to diff, file fromVer: {fromPath.FullName}, toVer: {toPath.FullName}", $"{nameof(CreatePatch)}_Hdiff");
323 | CreateDirectoryFor(diffPath);
324 | Debug.Assert(false);
325 | File.Copy(toPath.FullName, $"{tmpFilePath}\\{relativePath}");
326 | }
327 | }
328 |
329 | File.WriteAllText($"{tmpFilePath}\\hdifffiles.txt", strb.ToString());
330 | #endregion
331 |
332 | #region Write README
333 | string readme =
334 | "This is a hdiff update package created by HappuGenyuanImsactUpdate. \n" +
335 | "For using, you may download our patcher release here: \n" +
336 | "https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/releases\n" +
337 | "Then run Updater\\HappyGenyuanImsactUpdate.exe to perform a update.\n" +
338 | "\n" +
339 | "Have a good day! Thanks for using!";
340 | File.WriteAllText($"{tmpFilePath}\\README.txt", readme);
341 | #endregion
342 |
343 | #region Create Compressed File
344 | await OuterInvoke.Run(new OuterInvokeInfo
345 | {
346 | ProcessPath = path7z,
347 | CmdLine = $"a -tzip \"{createpakPath}\" \"{tmpFilePath}\\*\" -mmt",
348 | StartingNotice = "Compressing output zip archive...",
349 | AutoTerminateReason = "Output compressing failed. You may retry by yourself."
350 | }, 6);
351 | #endregion
352 |
353 | // Clear temp files
354 | Directory.Delete(tmpFilePath, true);
355 | }
356 |
357 | static async Task InvokeHDiffz(string source, string target, string outdiffpath, int retry = 5)
358 | {
359 | CreateDirectoryFor(new FileInfo(outdiffpath));
360 |
361 | return await OuterInvoke.Run(new OuterInvokeInfo
362 | {
363 | ProcessPath = hdiffzPath,
364 | CmdLine = $"-f \"{source}\" \"{target}\" \"{outdiffpath}\""
365 | }, max_rerun: retry) == 0;
366 | }
367 |
368 | #region Random
369 | static Random ran = new Random();
370 |
371 | static string Randomstr(int len)
372 | {
373 | string charset = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
374 | Debug.Assert(charset.Length == 62);
375 | string res = "";
376 | while (len-- > 0)
377 | {
378 | res += charset[ran.Next(0, 61)];
379 | }
380 | return res;
381 | }
382 | #endregion
383 |
384 | private static void CreateDirectoryFor(FileInfo file)
385 | {
386 | if (file.DirectoryName == null) return;
387 | Directory.CreateDirectory(file.DirectoryName);
388 | }
389 | }
390 |
391 | internal class FileCompare : IEqualityComparer
392 | {
393 | public FileCompare(DirectoryInfo dir1, DirectoryInfo dir2)
394 | {
395 | rel1 = dir1;
396 | rel2 = dir2;
397 | }
398 |
399 | #region Relative Path
400 | DirectoryInfo rel1, rel2;
401 |
402 | public static string GetRelativePath(FileInfo info, DirectoryInfo relative_path_start_from)
403 | {
404 | string filePath = info.FullName;
405 | string dirPath = relative_path_start_from.FullName;
406 |
407 | if (!filePath.StartsWith(dirPath))
408 | throw new ArgumentException("File not in directory so can't create relative path.");
409 |
410 | return filePath.Remove(0, dirPath.Length + 1);
411 | }
412 |
413 | public static bool TryGetRelativePath(FileInfo info,
414 | DirectoryInfo relative_path_start_from, out string rtn)
415 | {
416 | string filePath = info.FullName;
417 | string dirPath = relative_path_start_from.FullName;
418 |
419 | if (!filePath.StartsWith(dirPath))
420 | {
421 | rtn = "File not in directory so can't create relative path.";
422 | return false;
423 | }
424 | rtn = filePath.Remove(0, dirPath.Length + 1);
425 | return true;
426 | }
427 | #endregion
428 |
429 | public bool Equals(FileInfo? f1, FileInfo? f2)
430 | {
431 | if (f1 == null && f2 == null) return true;
432 | if (f1 == null || f2 == null) return false;
433 | bool tryf1 = TryGetRelativePath(f1, rel1, out string relf1);
434 | if (!tryf1)
435 | {
436 | relf1 = GetRelativePath(f1, rel2);
437 | }
438 | bool tryf2 = TryGetRelativePath(f2, rel2, out string relf2);
439 | if (!tryf2)
440 | {
441 | relf2 = GetRelativePath(f2, rel1);
442 | }
443 | return relf1 == relf2;
444 | }
445 |
446 | private Dictionary _MD5memory = new();
447 |
448 | ///
449 | /// MD5 memory. Invoke to get a file's MD5, so you can get it without read file again next time.
450 | ///
451 | /// File's Full path
452 | /// MD5 value of this file
453 | public string this[string filePath]
454 | {
455 | get
456 | {
457 | lock (this)
458 | {
459 | if (!_MD5memory.ContainsKey(filePath))
460 | {
461 | Log.Info($"Start Computing MD5 of: {filePath}");
462 | _MD5memory.Add(filePath, MyMD5.GetMD5HashFromFile(filePath));
463 | }
464 | return _MD5memory[filePath];
465 | }
466 | }
467 | }
468 |
469 | public bool RealEqual(FileInfo? a, FileInfo? b)
470 | {
471 | if (a == null && b == null) return true;
472 | if (a == null || b == null) return false;
473 | Debug.Assert(a.Name == b.Name);
474 | if (a.Length != b.Length) return false;
475 | return this[a.FullName] == this[b.FullName];
476 | }
477 |
478 | // Return a hash that reflects the comparison criteria. According to the
479 | // rules for IEqualityComparer, if Equals is true, then the hash codes must
480 | // also be equal. Because equality as defined here is a simple value equality, not
481 | // reference identity, it is possible that two or more objects will produce the same
482 | // hash code.
483 | public int GetHashCode(FileInfo fi)
484 | {
485 | bool tryf1 = TryGetRelativePath(fi, rel1, out string relf1);
486 | if (!tryf1)
487 | {
488 | relf1 = GetRelativePath(fi, rel2);
489 | }
490 | return relf1.GetHashCode();
491 | }
492 | }
493 | }
--------------------------------------------------------------------------------
/HappyGenyuanImsactUpdate/Program.cs:
--------------------------------------------------------------------------------
1 | /*******HappyGenyuanImsactUpdate*******/
2 | // A hdiff-using update program of a certain anime game.
3 |
4 | using System.Reflection;
5 | using YYHEggEgg.Logger;
6 | using YYHEggEgg.Utils;
7 |
8 | namespace HappyGenyuanImsactUpdate
9 | {
10 | public class Program
11 | {
12 | static async Task Main(string[] args)
13 | {
14 | Log.Initialize(new LoggerConfig(
15 | max_Output_Char_Count: -1,
16 | use_Console_Wrapper: false,
17 | use_Working_Directory: false,
18 | global_Minimum_LogLevel: LogLevel.Verbose,
19 | console_Minimum_LogLevel: LogLevel.Information,
20 | debug_LogWriter_AutoFlush: true));
21 |
22 | string? version = Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
23 | Log.Info($"Welcome to the update program! (v{version ?? ""})");
24 | Helper.CheckForRunningInZipFile();
25 |
26 | //Not working path, but the path where the program located
27 | Helper.CheckForTools();
28 |
29 | var path7z = $"{Helper.exePath}\\7z.exe";
30 | var hpatchzPath = $"{Helper.exePath}\\hpatchz.exe";
31 |
32 | #region Variables
33 | DirectoryInfo? datadir = null;
34 | Patch patch;
35 | CheckMode checkAfter = CheckMode.Null;
36 | int t = 0;
37 | List zips = new();
38 | bool ifconfigchange = true;
39 | bool? ifdeletepackage = null;
40 | bool[] arghaveread = new bool[6];
41 |
42 | bool usingcommandline = false;
43 | #endregion
44 |
45 | #region Console Usage
46 | if (args.Length == 0)
47 | {
48 | Log.Info("You can also use command line args to execute this program.", "CommandLine");
49 |
50 | datadir = GetDataPath();
51 |
52 | Log.Info("");
53 | // 0 -> none, 1 -> basic check (file size), 2 -> full check (size + md5)
54 | checkAfter = (CheckMode)AskForCheck();
55 |
56 | Log.Info("");
57 |
58 | if (!PkgVersionCheck(datadir, checkAfter))
59 | {
60 | Log.Erro("Sorry, the update process was exited because the original files aren't correct.", nameof(PkgVersionCheck));
61 | Log.Erro("Press any key to continue. ", nameof(PkgVersionCheck));
62 | Console.Read();
63 | Environment.Exit(1);
64 | }
65 | else Log.Info("Congratulations! Check passed!", nameof(PkgVersionCheck));
66 |
67 | t = GetZipCount();
68 |
69 | Log.Info("");
70 |
71 | for (int i = 0; i < t; i++)
72 | {
73 | Log.Info("");
74 | if (i > 0) Log.Info("Now you should paste the path of another zip file.", nameof(GetUpdatePakPath));
75 | zips.Add(GetUpdatePakPath(datadir.FullName));
76 | }
77 | }
78 | #endregion
79 | #region Command LIne Usage
80 | else
81 | {
82 | usingcommandline = true;
83 |
84 | #region Remove '.\'
85 | for (int i = 0; i < args.Length; i++)
86 | {
87 | if (args[i].StartsWith('.'))
88 | args[i] = args[i].Substring(1);
89 | }
90 | #endregion
91 |
92 | for (int i = 0; i < args.Length; i++)
93 | {
94 | args[i] = RemoveDoubleQuotes(args[i]) ?? string.Empty;
95 | }
96 |
97 | if (args.Length > 0)
98 | {
99 | for (int i = 0; i < args.Length; i++)
100 | {
101 | switch (args[i])
102 | {
103 | case "-patchAt":
104 | ReadAssert(0);
105 | datadir = new DirectoryInfo(args[i + 1]);
106 | i += 1;
107 | break;
108 | case "-checkmode":
109 | ReadAssert(1);
110 | checkAfter = (CheckMode)int.Parse(args[i + 1]);
111 | i += 1;
112 | break;
113 | case "-zip_count":
114 | ReadAssert(2);
115 | t = int.Parse(args[i + 1]);
116 | for (int j = 0; j < t; j++)
117 | {
118 | zips.Add(new FileInfo(args[i + 2 + j]));
119 | }
120 | i += t + 1;
121 | break;
122 | case "--config_change_guidance":
123 | ReadAssert(3);
124 | ifconfigchange = bool.Parse(args[i + 1]);
125 | i += 1;
126 | break;
127 | case "--delete_update_packages":
128 | ReadAssert(4);
129 | ifdeletepackage = true;
130 | break;
131 | default:
132 | Usage();
133 | return;
134 | }
135 | }
136 | }
137 | else
138 | {
139 | Usage();
140 | return;
141 | }
142 |
143 | ifdeletepackage ??= false;
144 | }
145 | #endregion
146 |
147 | #region Input lost Assert
148 | if (datadir == null || checkAfter == CheckMode.Null || t == 0)
149 | {
150 | Usage();
151 | throw new ArgumentException("Input param lack!");
152 | }
153 | #endregion
154 |
155 | patch = new Patch(datadir, path7z, hpatchzPath);
156 |
157 | // Backup the original pkg_version file
158 | var pkgversionpaths = UpCheck.GetPkgVersion(datadir);
159 | foreach (var pkgversionpath in pkgversionpaths)
160 | {
161 | FileInfo pkgver = new(pkgversionpath);
162 | if (pkgver.Name == "pkg_version") continue;
163 | File.Move(pkgversionpath, $"{Helper.tempPath}\\{pkgver.Name}");
164 | }
165 |
166 | // Due to some reasons, if the deleted files are not there,
167 | // we'll try to delete them afterwards.
168 | List delete_delays = new();
169 |
170 | foreach (var zipfile in zips)
171 | {
172 | #region Unzip the package
173 | // NOTE: Because some dawn packages from gdrive has a sub folder, we should move it back.
174 | // Record the directories now
175 | var predirs = Directory.GetDirectories(datadir.FullName);
176 |
177 | await OuterInvoke.Run(new OuterInvokeInfo
178 | {
179 | ProcessPath = path7z,
180 | CmdLine = $"x \"{zipfile.FullName}\" -o\"{datadir.FullName}\" -aoa -bsp1",
181 | StartingNotice = "Unzip the package...",
182 | AutoTerminateReason = $"7z decompress package: {zipfile.FullName} to {datadir.FullName} failed."
183 | }, 3750);
184 |
185 | Unzipped.MoveBackSubFolder(datadir, predirs);
186 | #endregion
187 |
188 | await patch.Hdiff();
189 | delete_delays.AddRange(patch.DeleteFiles());
190 |
191 | Log.Info("\n\n");
192 | }
193 |
194 | // For some reasons, the package check is delayed to the end.
195 | // It is a proper change because only the newest pkg_version is valid.
196 | if (!UpdateCheck(datadir, checkAfter))
197 | {
198 | Helper.ShowErrorBalloonTip(5000, "Update failed.",
199 | "Sorry, the update process was exited because files aren't correct.");
200 |
201 | Log.Erro("Sorry, the update process was exited because the original files aren't correct.", nameof(PkgVersionCheck));
202 | Log.Erro("Press any key to continue. ", nameof(PkgVersionCheck));
203 | Console.Read();
204 | Environment.Exit(1);
205 | }
206 | else Log.Info("Congratulations! Check passed!", nameof(PkgVersionCheck));
207 |
208 | foreach (var pkgversionpath in pkgversionpaths)
209 | {
210 | FileInfo pkgver = new(pkgversionpath);
211 | if (pkgver.Name == "pkg_version") continue;
212 | if (pkgver.Exists) continue; // pkg_version Overrided
213 |
214 | var backuppath = $"{Helper.tempPath}\\{pkgver.Name}";
215 | File.Move(backuppath, pkgversionpath);
216 | if (checkAfter == CheckMode.None)
217 | {
218 | Log.Warn($"{pkgver.Name} hasn't checked and may not fit the current version.");
219 | continue;
220 | }
221 |
222 | var checkres = UpCheck.CheckByPkgVersion(datadir, pkgversionpath, checkAfter);
223 |
224 | if (!checkres)
225 | {
226 | Log.Warn($"{pkgver.Name} isn't fit with current version any more. You may fix the error or remove the file under the game data directory.");
227 | }
228 | }
229 |
230 | Log.Info("\n\n\n\n\n---------------------\n\n\n\n\n");
231 |
232 | // Change the config.ini of official launcher
233 | if ((usingcommandline && ifconfigchange) || !usingcommandline)
234 | ConfigChange(datadir, zips[0], zips[zips.Count - 1]);
235 |
236 | // Handling with delayed deletions
237 | foreach (var deletedfile in delete_delays)
238 | if (File.Exists(deletedfile))
239 | File.Delete(deletedfile);
240 |
241 | Helper.ShowInformationBalloonTip(5000, "Update process is done!", "Enjoy the new version!");
242 |
243 | DeleteZipFilesReq(zips, ifdeletepackage);
244 | Log.Info("-------------------------");
245 |
246 | Helper.TryDisposeTempFiles();
247 |
248 | Log.Info("Update process is done!");
249 |
250 | if (args.Length == 0)
251 | {
252 | Log.Info("Press Enter to continue.");
253 |
254 | Console.ReadLine();
255 | }
256 | else
257 | {
258 | Log.Info("The program will exit in 3 seconds.");
259 | await Task.Delay(3000);
260 | }
261 |
262 | #region Multiple Read Assert
263 | void ReadAssert(int expected)
264 | {
265 | if (arghaveread[expected])
266 | {
267 | Log.Info("Duplicated param!");
268 | Usage();
269 | Environment.Exit(1);
270 | }
271 | arghaveread[expected] = true;
272 | }
273 | #endregion
274 | }
275 |
276 | private static void Usage()
277 | {
278 | Log.Info("CommandLine usage: \n" +
279 | "happygenyuanimsactupdate \n" +
280 | "-patchAt \n" +
281 | "-checkmode <0/1/2> (0 -> none, 1 -> basic check (file size), 2 -> full check (size + md5))\n" +
282 | "-zip_count \n" +
283 | "[--config_change_guidance ] (change the showing version of official launcher, default is true)\n" +
284 | "[--delete_update_packages] (delete update packages, won't delete if the param isn't given)" +
285 | "\n\n" +
286 | "e.g. happygenyuanimsactupdate -patchAt \"D:\\Game\" -checkmode 1 -zip_count 2 \"game_1_hdiff.zip\" \"zh-cn_hdiff.zip\" " +
287 | "--config_change_guidance false\n", "CommandLine");
288 | }
289 |
290 | #region Change config for official launcher
291 | ///
292 | /// Change config for official launcher
293 | ///
294 | /// Game Data dir
295 | /// Used for infering the update version
296 | /// Used for infering the update version
297 | public static void ConfigChange(DirectoryInfo datadir, FileInfo zipstart, FileInfo zipend)
298 | {
299 | if (!File.Exists($"{datadir}\\config.ini")) return;
300 |
301 | Helper.ShowInformationBalloonTip(5000, "The update program needs you decision.",
302 | "You need to apply change to the Launcher config in the console.");
303 |
304 | Log.Info("We have noticed that you're probably using an official launcher.", nameof(ConfigChange));
305 | Log.Info("To make it display the correct version, we would make some change on related file.", nameof(ConfigChange));
306 |
307 | string verstart = ConfigIni.FindStartVersion(zipstart.Name);
308 | string verto = ConfigIni.FindToVersion(zipend.Name);
309 |
310 | FileInfo configfile = new($"{datadir}\\config.ini");
311 |
312 | if (verstart == string.Empty || verto == string.Empty)
313 | {
314 | Log.Warn("We can't infer the version you're updating to.", nameof(ConfigChange));
315 | CustomChangeVersion(configfile);
316 | }
317 | else
318 | {
319 | GetConfigUpdateOptions(configfile, verstart, verto);
320 | }
321 | }
322 |
323 | ///
324 | /// Ask user for applying the inferred update options
325 | ///
326 | /// config.ini
327 | /// the update version
328 | /// the update version
329 | public static void GetConfigUpdateOptions(FileInfo configfile, string verstart, string verto)
330 | {
331 | Log.Info($"We infer that you're updating from {verstart} to {verto} .", nameof(ConfigChange));
332 | Log.Info("Is it true? Type 'y' to apply the change " +
333 | "or type the correct version you're updating to.", nameof(ConfigChange));
334 | Log.Info("If you don't use a launcher or don't want to change the display version, type 'n' to refuse it.", nameof(ConfigChange));
335 | string? s = Console.ReadLine();
336 | if (s == null || s == string.Empty)
337 | {
338 | Log.Warn("Invaild version!", nameof(ConfigChange));
339 | CustomChangeVersion(configfile);
340 | }
341 | else if (s.ToLower() == "y")
342 | ConfigIni.ApplyConfigChange(configfile, verto);
343 | else if (s.ToLower() == "n") return;
344 | else if (ConfigIni.VerifyVersionString(s))
345 | ConfigIni.ApplyConfigChange(configfile, s);
346 | else
347 | {
348 | Log.Warn("Invaild version!", nameof(ConfigChange));
349 | CustomChangeVersion(configfile);
350 | }
351 | }
352 |
353 | ///
354 | /// Type a custom version for update
355 | ///
356 | /// config.ini
357 | public static void CustomChangeVersion(FileInfo configfile)
358 | {
359 | Log.Info("Please type the version you're updating to, and we'll apply the change:", nameof(CustomChangeVersion));
360 | Log.Info("If you don't use a launcher or don't want to change the display version, type 'n' to refuse it.", nameof(CustomChangeVersion));
361 |
362 | string? s = Console.ReadLine();
363 | if (s == null || s == string.Empty)
364 | {
365 | Log.Warn("Invaild version!", nameof(CustomChangeVersion));
366 | CustomChangeVersion(configfile);
367 | }
368 | else if (s.ToLower() == "n") return;
369 | else if (ConfigIni.VerifyVersionString(s))
370 | ConfigIni.ApplyConfigChange(configfile, s);
371 | else
372 | {
373 | Log.Warn("Invaild version!", nameof(CustomChangeVersion));
374 | CustomChangeVersion(configfile);
375 | }
376 | }
377 | #endregion
378 |
379 | #region Package Verify
380 | public static bool UpdateCheck(DirectoryInfo datadir, CheckMode checkAfter)
381 | {
382 | Log.Info("Start verifying...\n", nameof(UpdateCheck));
383 |
384 | if (checkAfter == CheckMode.None)
385 | {
386 | Log.Info("Due to user's demanding, no checks are performed.", nameof(UpdateCheck));
387 | return true;
388 | }
389 |
390 | var pkgversionPaths = UpCheck.GetPkgVersion(datadir);
391 | if (pkgversionPaths == null || pkgversionPaths.Count == 0)
392 | {
393 | Log.Info("Can't find version file. No checks are performed.", nameof(UpdateCheck));
394 | Log.Info("If you can find it, please tell to us: " +
395 | "https://github.com/YYHEggEgg/HappyGenyuanImsactUpdate/issues", nameof(UpdateCheck));
396 | return true;
397 | }
398 |
399 | return UpCheck.CheckByPkgVersion(datadir, pkgversionPaths, checkAfter);
400 | }
401 |
402 | // Check if pkg_version and Audio_pkg_version can match the real condition
403 | static bool PkgVersionCheck(DirectoryInfo datadir, CheckMode checkAfter)
404 | {
405 | if (checkAfter == CheckMode.None)
406 | {
407 | Log.Info("No checks are performed.", nameof(PkgVersionCheck));
408 | return true;
409 | }
410 |
411 | var pkgversionPaths = UpCheck.GetPkgVersion(datadir);
412 | if (!pkgversionPaths.Contains($"{datadir}\\pkg_version"))
413 | {
414 | Log.Warn($"Can't find pkg_version file. No checks are performed.", nameof(PkgVersionCheck));
415 | Log.Info($"It's normal if you're attempting to update Honkai: March 7th.", nameof(PkgVersionCheck));
416 | return true;
417 | }
418 |
419 | // ...\??? game\???_Data\StreamingAssets\Audio\GeneratedSoundBanks\Windows
420 | string old_audio1 = $@"{datadir.FullName}\{Helper.certaingames[0]}_Data\StreamingAssets\Audio\GeneratedSoundBanks\Windows";
421 | string old_audio2 = $@"{datadir.FullName}\{Helper.certaingames[1]}_Data\StreamingAssets\Audio\GeneratedSoundBanks\Windows";
422 | string[]? audio_pkgversions = null;
423 | if (Directory.Exists(old_audio1)) audio_pkgversions = Directory.GetDirectories(old_audio1);
424 | else if (Directory.Exists(old_audio2)) audio_pkgversions = Directory.GetDirectories(old_audio2);
425 | else // ver >= 3.6
426 | {
427 | // ...\??? game\???_Data\StreamingAssets\AudioAssets
428 | string new_audio1 = $@"{datadir.FullName}\{Helper.certaingames[0]}_Data\StreamingAssets\AudioAssets";
429 | string new_audio2 = $@"{datadir.FullName}\{Helper.certaingames[1]}_Data\StreamingAssets\Audio\AudioAssets";
430 |
431 | if (Directory.Exists(new_audio1)) audio_pkgversions = Directory.GetDirectories(new_audio1);
432 | else if (Directory.Exists(new_audio2)) audio_pkgversions = Directory.GetDirectories(new_audio2);
433 | else return UpdateCheck(datadir, checkAfter);
434 | }
435 |
436 | foreach (string audiopath in audio_pkgversions)
437 | {
438 | string audioname = new DirectoryInfo(audiopath).Name;
439 | if (!pkgversionPaths.Contains($"{datadir}\\Audio_{audioname}_pkg_version"))
440 | {
441 | // ver <= 1.4
442 | Log.Warn($"Not checking Audio: {audioname} for Audio_{audioname}_pkg_version does not exist.", nameof(PkgVersionCheck));
443 | }
444 | }
445 |
446 | return UpdateCheck(datadir, checkAfter);
447 | }
448 | #endregion
449 |
450 | #region Param Getting
451 | //For standarlizing, we use a DirectoryInfo object.
452 | //The same goes for the following methods.
453 | static DirectoryInfo GetDataPath()
454 | {
455 | Log.Info("Paste the full path of game directory here. " +
456 | "It's usually ended with \"Genyuan Imsact game\".", nameof(GetDataPath));
457 | string? dataPath = RemoveDoubleQuotes(Console.ReadLine());
458 | if (dataPath == null || dataPath == string.Empty)
459 | {
460 | Log.Warn("Invaild game path!", nameof(GetDataPath));
461 | return GetDataPath();
462 | }
463 |
464 | DirectoryInfo datadir = new(dataPath);
465 | if (!Helper.AnyCertainGameExists(datadir))
466 | {
467 | Log.Warn("No known game executable found under directory. Do you believe the path is correct? Type 'y' to confirm.", nameof(GetDataPath));
468 | if (Console.ReadLine()?.ToLower() == "y") return datadir;
469 | return GetDataPath();
470 | }
471 | else return datadir;
472 | }
473 |
474 | static FileInfo GetUpdatePakPath(string gamePath)
475 | {
476 | Log.Info("Drag the update package here. " +
477 | "It should be a zip file.", nameof(GetUpdatePakPath));
478 | Log.Info("If it's under the game directory, you can just paste the name of zip file here.", nameof(GetUpdatePakPath));
479 | string? pakPath = RemoveDoubleQuotes(Console.ReadLine());
480 | if (pakPath == null || pakPath == string.Empty)
481 | {
482 | Log.Warn("Invaild update package!", nameof(GetUpdatePakPath));
483 | return GetUpdatePakPath(gamePath);
484 | }
485 |
486 | FileInfo zipfile = new(pakPath);
487 |
488 | // Fuck why I have tested this
489 | if (pakPath.Length >= 3)
490 | if (pakPath.Substring(1, 2) != ":\\")
491 | {
492 | //Support relative path
493 | pakPath = $"{gamePath}\\{pakPath}";
494 | zipfile = new(pakPath);
495 | }
496 |
497 | //To protect fools who really just paste its name
498 | if (zipfile.Extension != ".zip"
499 | || zipfile.Extension != ".rar"
500 | || zipfile.Extension != ".7z"
501 | || zipfile.Extension != ".001")
502 | {
503 | if (File.Exists($"{pakPath}.zip")) pakPath += ".zip";
504 | else if (File.Exists($"{pakPath}.rar")) pakPath += ".rar";
505 | else if (File.Exists($"{pakPath}.7z")) pakPath += ".7z";
506 | else if (File.Exists($"{pakPath}.001")) pakPath += ".001";
507 | zipfile = new(pakPath);
508 | }
509 |
510 | if (!zipfile.Exists)
511 | {
512 | Log.Warn("Invaild update package!", nameof(GetUpdatePakPath));
513 | return GetUpdatePakPath(gamePath);
514 | }
515 |
516 | return zipfile;
517 | }
518 |
519 | static int GetZipCount()
520 | {
521 | int rtn = 0;
522 | Log.Info("Please type the count of zip file you have.", nameof(GetUpdatePakPath));
523 | if (!int.TryParse(Console.ReadLine(), out rtn))
524 | {
525 | Log.Warn("Invaild input!", nameof(GetUpdatePakPath));
526 | return GetZipCount();
527 | }
528 | else return rtn;
529 | }
530 |
531 | // 0 -> none, 1 -> basic check (file size), 2 -> full check (size + md5)
532 | static int AskForCheck()
533 | {
534 | Log.Info("Do you want to have a check after updating?", nameof(AskForCheck));
535 | Log.Info("If you don't want any check, type 0;", nameof(AskForCheck));
536 | Log.Info("For a fast check (recommended, only compares file size, usually < 10s), type 1;", nameof(AskForCheck));
537 | Log.Info("For a full check (scans files, takes a long time, usually > 5 minutes), type 2.", nameof(AskForCheck));
538 | int rtn = 0;
539 | if (!int.TryParse(Console.ReadLine(), out rtn))
540 | {
541 | Log.Warn("Invaild input!", nameof(AskForCheck));
542 | return AskForCheck();
543 | }
544 | else if (rtn < 0 || rtn > 2)
545 | {
546 | Log.Warn("Invaild input!", nameof(AskForCheck));
547 | return AskForCheck();
548 | }
549 | else return rtn;
550 | }
551 | #endregion
552 |
553 | #region Delete Update Zip File
554 | /// true=delete; false=reserve; null=not given, ask the user
555 | static void DeleteZipFilesReq(List zips, bool? delete = null)
556 | {
557 | if (delete == null)
558 | {
559 | Log.Info("The pre-download packages aren't needed any more.", nameof(DeleteZipFilesReq));
560 | Log.Info("Do you want to delete them? Type 'y' to accept or 'n' to refuse.", nameof(DeleteZipFilesReq));
561 | string? s = Console.ReadLine();
562 | if (s == null)
563 | {
564 | Log.Warn("Invaild input!", nameof(DeleteZipFilesReq));
565 | DeleteZipFilesReq(zips);
566 | return;
567 | }
568 | else if (s.ToLower() == "y")
569 | {
570 | foreach (var zip in zips)
571 | {
572 | try
573 | {
574 | zip.Delete();
575 | }
576 | catch (Exception ex)
577 | {
578 | Log.Warn(ex.ToString(), nameof(DeleteZipFilesReq));
579 | Log.Warn($"Deleting package: {zip} failed. " +
580 | $"You may close and delete it yourself.",
581 | nameof(DeleteZipFilesReq));
582 | }
583 | }
584 | }
585 | else if (s.ToLower() == "n")
586 | {
587 | return;
588 | }
589 | else
590 | {
591 | Log.Warn("Invaild input!", nameof(DeleteZipFilesReq));
592 | DeleteZipFilesReq(zips);
593 | return;
594 | }
595 | }
596 | else if ((bool)delete)
597 | {
598 | foreach (var zip in zips)
599 | {
600 | try
601 | {
602 | zip.Delete();
603 | }
604 | catch (Exception ex)
605 | {
606 | Log.Warn(ex.ToString(), nameof(DeleteZipFilesReq));
607 | Log.Warn($"Deleting package: {zip} failed. " +
608 | $"You may close and delete it yourself.",
609 | nameof(DeleteZipFilesReq));
610 | }
611 | }
612 | }
613 | }
614 | #endregion
615 |
616 | public static string? RemoveDoubleQuotes(string? str)
617 | {
618 | if (str == null) return null;
619 | if (str.StartsWith('"') && str.EndsWith('"'))
620 | return str.Substring(1, str.Length - 2);
621 | else return str;
622 | }
623 | }
624 | }
--------------------------------------------------------------------------------