v4.10.1
- Upgraded to .NET 8 - Minor refactoring & optimizations
This commit is contained in:
parent
b0279461db
commit
992b7a9374
15 changed files with 99 additions and 108 deletions
|
@ -1,10 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0-windows10.0.22621.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<ApplicationIcon>Resources\program.ico</ApplicationIcon>
|
||||
<Version>4.10.0</Version>
|
||||
<Version>4.10.1</Version>
|
||||
<Copyright>2021, pointfeev (https://github.com/pointfeev)</Copyright>
|
||||
<Company>CreamInstaller</Company>
|
||||
<Product>Automatic DLC Unlocker Installer & Configuration Generator</Product>
|
||||
|
|
|
@ -16,14 +16,14 @@ internal sealed partial class DialogForm : CustomForm
|
|||
{
|
||||
descriptionIcon ??= Icon;
|
||||
icon.Image = descriptionIcon?.ToBitmap();
|
||||
List<LinkLabel.Link> links = new();
|
||||
List<LinkLabel.Link> links = [];
|
||||
for (int i = 0; i < descriptionText.Length; i++)
|
||||
if (descriptionText[i] == '[')
|
||||
{
|
||||
int textLeft = descriptionText.IndexOf("[", i, StringComparison.Ordinal);
|
||||
int textRight = descriptionText.IndexOf("]", textLeft == -1 ? i : textLeft, StringComparison.Ordinal);
|
||||
int linkLeft = descriptionText.IndexOf("(", textRight == -1 ? i : textRight, StringComparison.Ordinal);
|
||||
int linkRight = descriptionText.IndexOf(")", linkLeft == -1 ? i : linkLeft, StringComparison.Ordinal);
|
||||
int textLeft = descriptionText.IndexOf('[', i);
|
||||
int textRight = descriptionText.IndexOf(']', textLeft == -1 ? i : textLeft);
|
||||
int linkLeft = descriptionText.IndexOf('(', textRight == -1 ? i : textRight);
|
||||
int linkRight = descriptionText.IndexOf(')', linkLeft == -1 ? i : linkLeft);
|
||||
if (textLeft == -1 || textRight != linkLeft - 1 || linkRight == -1)
|
||||
continue;
|
||||
string text = descriptionText[(textLeft + 1)..textRight];
|
||||
|
|
|
@ -138,7 +138,7 @@ internal sealed partial class UpdateForm : CustomForm
|
|||
cancellation = new();
|
||||
bool success = true;
|
||||
PackagePath.DeleteFile(true);
|
||||
await using Stream update = PackagePath.CreateFile(true);
|
||||
await using FileStream update = PackagePath.CreateFile(true);
|
||||
bool retry = true;
|
||||
try
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ internal static class EpicStore
|
|||
|
||||
internal static async Task<List<(string id, string name, string product, string icon, string developer)>> QueryCatalog(string categoryNamespace)
|
||||
{
|
||||
List<(string id, string name, string product, string icon, string developer)> dlcIds = new();
|
||||
List<(string id, string name, string product, string icon, string developer)> dlcIds = [];
|
||||
string cacheFile = ProgramData.AppInfoPath + @$"\{categoryNamespace}.json";
|
||||
bool cachedExists = cacheFile.FileExists();
|
||||
Response response = null;
|
||||
|
@ -43,7 +43,7 @@ internal static class EpicStore
|
|||
}
|
||||
if (response is null)
|
||||
return dlcIds;
|
||||
List<Element> searchStore = new(response.Data.Catalog.SearchStore.Elements);
|
||||
List<Element> searchStore = [..response.Data.Catalog.SearchStore.Elements];
|
||||
foreach (Element element in searchStore)
|
||||
{
|
||||
string title = element.Title;
|
||||
|
@ -60,7 +60,7 @@ internal static class EpicStore
|
|||
foreach (Item item in element.Items)
|
||||
dlcIds.Populate(item.Id, title, product, icon, null, element.Items.Length == 1);
|
||||
}
|
||||
List<Element> catalogOffers = new(response.Data.Catalog.CatalogOffers.Elements);
|
||||
List<Element> catalogOffers = [..response.Data.Catalog.CatalogOffers.Elements];
|
||||
foreach (Element element in catalogOffers)
|
||||
{
|
||||
string title = element.Title;
|
||||
|
|
|
@ -13,50 +13,52 @@ internal sealed class Request
|
|||
|
||||
[JsonProperty(PropertyName = "query")]
|
||||
private string Query
|
||||
=> @"query searchOffers($namespace: String!) {
|
||||
Catalog {
|
||||
searchStore(category: ""*"", namespace: $namespace){
|
||||
elements {
|
||||
id
|
||||
title
|
||||
developer
|
||||
items {
|
||||
id
|
||||
}
|
||||
catalogNs {
|
||||
mappings(pageType: ""productHome"") {
|
||||
pageSlug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catalogOffers(
|
||||
namespace: $namespace
|
||||
params: {
|
||||
count: 1000,
|
||||
}
|
||||
) {
|
||||
elements {
|
||||
id
|
||||
title
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
items {
|
||||
id
|
||||
title
|
||||
developer
|
||||
}
|
||||
catalogNs {
|
||||
mappings(pageType: ""productHome"") {
|
||||
pageSlug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}";
|
||||
=> """
|
||||
query searchOffers($namespace: String!) {
|
||||
Catalog {
|
||||
searchStore(category: "*", namespace: $namespace){
|
||||
elements {
|
||||
id
|
||||
title
|
||||
developer
|
||||
items {
|
||||
id
|
||||
}
|
||||
catalogNs {
|
||||
mappings(pageType: "productHome") {
|
||||
pageSlug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catalogOffers(
|
||||
namespace: $namespace
|
||||
params: {
|
||||
count: 1000,
|
||||
}
|
||||
) {
|
||||
elements {
|
||||
id
|
||||
title
|
||||
keyImages {
|
||||
type
|
||||
url
|
||||
}
|
||||
items {
|
||||
id
|
||||
title
|
||||
developer
|
||||
}
|
||||
catalogNs {
|
||||
mappings(pageType: "productHome") {
|
||||
pageSlug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
[JsonProperty(PropertyName = "variables")]
|
||||
private Variables Vars { get; set; }
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#pragma warning disable CA1819 // Properties should not return arrays
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CreamInstaller.Platforms.Epic.GraphQL;
|
||||
|
|
|
@ -205,8 +205,8 @@ internal static class SteamCMD
|
|||
if (output is null)
|
||||
{
|
||||
output = await Run(appId) ?? "";
|
||||
int openBracket = output.IndexOf("{", StringComparison.Ordinal);
|
||||
int closeBracket = output.LastIndexOf("}", StringComparison.Ordinal);
|
||||
int openBracket = output.IndexOf('{');
|
||||
int closeBracket = output.LastIndexOf('}');
|
||||
if (openBracket != -1 && closeBracket != -1 && closeBracket > openBracket)
|
||||
{
|
||||
output = $"\"{appId}\"\n" + output[openBracket..(1 + closeBracket)];
|
||||
|
@ -255,7 +255,7 @@ internal static class SteamCMD
|
|||
internal static async Task<HashSet<string>> ParseDlcAppIds(VProperty appInfo)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
HashSet<string> dlcIds = new();
|
||||
HashSet<string> dlcIds = [];
|
||||
if (Program.Canceled || appInfo is null)
|
||||
return dlcIds;
|
||||
VToken extended = appInfo.Value.GetChild("extended");
|
||||
|
|
|
@ -13,7 +13,7 @@ internal static class Program
|
|||
{
|
||||
internal static readonly string Name = Application.CompanyName;
|
||||
private static readonly string Description = Application.ProductName;
|
||||
internal static readonly string Version = Application.ProductVersion;
|
||||
internal static readonly string Version = Application.ProductVersion[..(Application.ProductVersion.IndexOf('+') is var index && index != -1 ? index : Application.ProductVersion.Length)];
|
||||
|
||||
internal const string RepositoryOwner = "pointfeev";
|
||||
internal static readonly string RepositoryName = Name;
|
||||
|
@ -32,9 +32,9 @@ internal static class Program
|
|||
internal static readonly int CurrentProcessId = CurrentProcess.Id;
|
||||
|
||||
internal static bool BlockProtectedGames = true;
|
||||
internal static readonly string[] ProtectedGames = { "PAYDAY 2" };
|
||||
internal static readonly string[] ProtectedGameDirectories = { @"\EasyAntiCheat", @"\BattlEye" };
|
||||
internal static readonly string[] ProtectedGameDirectoryExceptions = Array.Empty<string>();
|
||||
internal static readonly string[] ProtectedGames = ["PAYDAY 2"];
|
||||
internal static readonly string[] ProtectedGameDirectories = [@"\EasyAntiCheat", @"\BattlEye"];
|
||||
internal static readonly string[] ProtectedGameDirectoryExceptions = [];
|
||||
|
||||
internal static bool IsGameBlocked(string name, string directory = null)
|
||||
=> BlockProtectedGames && (ProtectedGames.Contains(name) || directory is not null && !ProtectedGameDirectoryExceptions.Contains(name)
|
||||
|
|
|
@ -13,14 +13,14 @@ namespace CreamInstaller.Resources;
|
|||
|
||||
internal static class Koaloader
|
||||
{
|
||||
internal static readonly List<(string unlocker, string dll)> AutoLoadDLLs = new()
|
||||
{
|
||||
internal static readonly List<(string unlocker, string dll)> AutoLoadDLLs =
|
||||
[
|
||||
("Koaloader", "Unlocker.dll"), ("Koaloader", "Unlocker32.dll"), ("Koaloader", "Unlocker64.dll"), ("Lyptus", "Lyptus.dll"),
|
||||
("Lyptus", "Lyptus32.dll"), ("Lyptus", "Lyptus64.dll"), ("SmokeAPI", "SmokeAPI.dll"), ("SmokeAPI", "SmokeAPI32.dll"),
|
||||
("SmokeAPI", "SmokeAPI64.dll"), ("ScreamAPI", "ScreamAPI.dll"), ("ScreamAPI", "ScreamAPI32.dll"), ("ScreamAPI", "ScreamAPI64.dll"),
|
||||
("Uplay R1 Unlocker", "UplayR1Unlocker.dll"), ("Uplay R1 Unlocker", "UplayR1Unlocker32.dll"), ("Uplay R1 Unlocker", "UplayR1Unlocker64.dll"),
|
||||
("Uplay R2 Unlocker", "UplayR2Unlocker.dll"), ("Uplay R2 Unlocker", "UplayR2Unlocker32.dll"), ("Uplay R2 Unlocker", "UplayR2Unlocker64.dll")
|
||||
};
|
||||
];
|
||||
|
||||
internal static IEnumerable<string> GetKoaloaderProxies(this string directory)
|
||||
=> from resource in EmbeddedResources
|
||||
|
@ -92,7 +92,7 @@ internal static class Koaloader
|
|||
}
|
||||
}
|
||||
|
||||
private static void WriteConfig(TextWriter writer, SortedList<string, string> targets, SortedList<string, string> modules, InstallForm installForm = null)
|
||||
private static void WriteConfig(StreamWriter writer, SortedList<string, string> targets, SortedList<string, string> modules, InstallForm installForm = null)
|
||||
{
|
||||
writer.WriteLine("{");
|
||||
writer.WriteLine(" \"logging\": false,");
|
||||
|
|
|
@ -17,8 +17,7 @@ internal static class Resources
|
|||
private static readonly Dictionary<ResourceIdentifier, HashSet<string>> ResourceMD5s = new()
|
||||
{
|
||||
{
|
||||
ResourceIdentifier.Koaloader, new()
|
||||
{
|
||||
ResourceIdentifier.Koaloader, [
|
||||
"8A0958687B5ED7C34DAD037856DD1530", // Koaloader v2.0.0
|
||||
"8FECDEB40980F4E687C10E056232D96B", // Koaloader v2.0.0
|
||||
"92AD5E145A7CF9BA6F841BEB714B919B", // Koaloader v2.0.0
|
||||
|
@ -351,27 +350,24 @@ internal static class Resources
|
|||
"4207947D0452C1E33428ED098DC23D26", // Koaloader v3.0.2
|
||||
"BDD0DCAE7A5FBBBA0D8B857AC34BD43C", // Koaloader v3.0.2
|
||||
"A0933D21552CC5C835416DFD7604548D" // Koaloader v3.0.2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ResourceIdentifier.EpicOnlineServices32, new()
|
||||
{
|
||||
ResourceIdentifier.EpicOnlineServices32, [
|
||||
"069A57B1834A960193D2AD6B96926D70", // ScreamAPI v3.0.0
|
||||
"E2FB3A4A9583FDC215832E5F935E4440", // ScreamAPI v3.0.1
|
||||
"8B4B30AFAE8D7B06413EE2F2266B20DB" // ScreamAPI v4.0.0-rc01
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ResourceIdentifier.EpicOnlineServices64, new()
|
||||
{
|
||||
ResourceIdentifier.EpicOnlineServices64, [
|
||||
"0D62E57139F1A64F807A9934946A9474", // ScreamAPI v3.0.0
|
||||
"3875C7B735EE80C23239CC4749FDCBE6", // ScreamAPI v3.0.1
|
||||
"CBC89E2221713B0D4482F91282030A88" // ScreamAPI v4.0.0-rc01
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ResourceIdentifier.Steamworks32, new()
|
||||
{
|
||||
ResourceIdentifier.Steamworks32, [
|
||||
"02594110FE56B2945955D46670B9A094", // CreamAPI v4.5.0.0 Hotfix
|
||||
"B2434578957CBE38BDCE0A671C1262FC", // SmokeAPI v1.0.0
|
||||
"973AB1632B747D4BF3B2666F32E34327", // SmokeAPI v1.0.1
|
||||
|
@ -385,11 +381,10 @@ internal static class Resources
|
|||
"8B075C6B272A172A014D5C9E60F13DF2", // SmokeAPI v2.0.3
|
||||
"A3873569DECAD08962C46E88352E6DB1", // SmokeAPI v2.0.4
|
||||
"4A1A823E5CF4FB861DD6BA94539D29C4" // SmokeAPI v2.0.5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ResourceIdentifier.Steamworks64, new()
|
||||
{
|
||||
ResourceIdentifier.Steamworks64, [
|
||||
"30091B91923D9583A54A93ED1145554B", // CreamAPI v4.5.0.0 Hotfix
|
||||
"08713035CAD6F52548FF324D0487B88D", // SmokeAPI v1.0.0
|
||||
"D077737B9979D32458AC938A2978FA3C", // SmokeAPI v1.0.1
|
||||
|
@ -403,33 +398,29 @@ internal static class Resources
|
|||
"E4DC2AF2B8B77A0C9BF9BFBBAEA11CF7", // SmokeAPI v2.0.3
|
||||
"C0DDB49C9BFD3E05CBC1C61D117E93F9", // SmokeAPI v2.0.4
|
||||
"F7C3064D5E3C892B168F504C21AC4923" // SmokeAPI v2.0.5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ResourceIdentifier.Uplay32, new()
|
||||
{
|
||||
ResourceIdentifier.Uplay32, [
|
||||
"1977967B2549A38EC2DB39D4C8ED499B" // Uplay R1 Unlocker v2.0.0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ResourceIdentifier.Uplay64, new()
|
||||
{
|
||||
ResourceIdentifier.Uplay64, [
|
||||
"333FEDD9DC2B299419B37ED1624FF8DB" // Uplay R1 Unlocker v2.0.0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ResourceIdentifier.Upc32, new()
|
||||
{
|
||||
ResourceIdentifier.Upc32, [
|
||||
"C14368BC4EE19FDE8DBAC07E31C67AE4", // Uplay R2 Unlocker v3.0.0
|
||||
"DED3A3EA1876E3110D7D87B9A22946B0" // Uplay R2 Unlocker v3.0.1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ResourceIdentifier.Upc64, new()
|
||||
{
|
||||
ResourceIdentifier.Upc64, [
|
||||
"7D9A4C12972BAABCB6C181920CC0F19B", // Uplay R2 Unlocker v3.0.0
|
||||
"D7FDBFE0FC8D7600FEB8EC0A97713184" // Uplay R2 Unlocker v3.0.1
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -440,7 +431,7 @@ internal static class Resources
|
|||
if (embeddedResources is not null)
|
||||
return embeddedResources;
|
||||
string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
|
||||
embeddedResources = new();
|
||||
embeddedResources = [];
|
||||
foreach (string resourceName in names.Where(n => n.StartsWith("CreamInstaller.Resources.", StringComparison.Ordinal)))
|
||||
_ = embeddedResources.Add(resourceName[25..]);
|
||||
return embeddedResources;
|
||||
|
@ -491,13 +482,13 @@ internal static class Resources
|
|||
{
|
||||
e.path = Path.GetDirectoryName(e.path);
|
||||
return e;
|
||||
}).DistinctBy(e => e.path).ToList() ?? new());
|
||||
}).DistinctBy(e => e.path).ToList() ?? []);
|
||||
|
||||
internal static async Task<List<(string path, BinaryType binaryType)>> GetExecutables(this string rootDirectory, bool filterCommon = false,
|
||||
Func<string, bool> validFunc = null)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
List<(string path, BinaryType binaryType)> executables = new();
|
||||
List<(string path, BinaryType binaryType)> executables = [];
|
||||
if (Program.Canceled || !rootDirectory.DirectoryExists())
|
||||
return null;
|
||||
foreach (string path in rootDirectory.EnumerateDirectory("*.exe", true))
|
||||
|
@ -531,7 +522,7 @@ internal static class Resources
|
|||
internal static async Task<HashSet<string>> GetDllDirectoriesFromGameDirectory(this string gameDirectory, Platform platform)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
HashSet<string> dllDirectories = new();
|
||||
HashSet<string> dllDirectories = [];
|
||||
if (Program.Canceled || !gameDirectory.DirectoryExists())
|
||||
return null;
|
||||
foreach (string directory in gameDirectory.EnumerateSubdirectories("*", true).Append(gameDirectory))
|
||||
|
|
|
@ -27,7 +27,7 @@ internal static class ScreamAPI
|
|||
directory.GetScreamApiComponents(out _, out _, out _, out _, out string config, out _);
|
||||
HashSet<SelectionDLC> overrideCatalogItems = selection.DLC.Where(dlc => dlc.Type is DLCType.Epic && !dlc.Enabled).ToHashSet();
|
||||
int entitlementCount = 0;
|
||||
HashSet<SelectionDLC> injectedEntitlements = new();
|
||||
HashSet<SelectionDLC> injectedEntitlements = [];
|
||||
foreach (SelectionDLC dlc in selection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement))
|
||||
{
|
||||
if (dlc.Enabled)
|
||||
|
@ -65,7 +65,7 @@ internal static class ScreamAPI
|
|||
}
|
||||
}
|
||||
|
||||
private static void WriteConfig(TextWriter writer, SortedList<string, SelectionDLC> overrideCatalogItems,
|
||||
private static void WriteConfig(StreamWriter writer, SortedList<string, SelectionDLC> overrideCatalogItems,
|
||||
SortedList<string, SelectionDLC> injectedEntitlements, InstallForm installForm = null)
|
||||
{
|
||||
writer.WriteLine("{");
|
||||
|
|
|
@ -31,11 +31,11 @@ internal static class SmokeAPI
|
|||
HashSet<SelectionDLC> overrideDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet();
|
||||
foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled)))
|
||||
_ = overrideDlc.Add(extraDlc);
|
||||
HashSet<SelectionDLC> injectDlc = new();
|
||||
HashSet<SelectionDLC> injectDlc = [];
|
||||
if (selection.DLC.Count() > 64)
|
||||
foreach (SelectionDLC hiddenDlc in selection.DLC.Where(dlc => dlc.Enabled && dlc.Type is DLCType.SteamHidden))
|
||||
_ = injectDlc.Add(hiddenDlc);
|
||||
List<KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)>> extraApps = new();
|
||||
List<KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)>> extraApps = [];
|
||||
foreach (Selection extraSelection in selection.ExtraSelections.Where(extraSelection => extraSelection.DLC.Count() > 64))
|
||||
{
|
||||
SortedList<string, SelectionDLC> extraInjectDlc = new(PlatformIdComparer.String);
|
||||
|
@ -68,7 +68,7 @@ internal static class SmokeAPI
|
|||
}
|
||||
}
|
||||
|
||||
private static void WriteConfig(TextWriter writer, string appId, SortedList<string, (string name, SortedList<string, SelectionDLC> injectDlc)> extraApps,
|
||||
private static void WriteConfig(StreamWriter writer, string appId, SortedList<string, (string name, SortedList<string, SelectionDLC> injectDlc)> extraApps,
|
||||
SortedList<string, SelectionDLC> overrideDlc, SortedList<string, SelectionDLC> injectDlc, InstallForm installForm = null)
|
||||
{
|
||||
writer.WriteLine("{");
|
||||
|
|
|
@ -45,7 +45,7 @@ internal static class UplayR1
|
|||
}
|
||||
}
|
||||
|
||||
private static void WriteConfig(TextWriter writer, SortedList<string, SelectionDLC> blacklistDlc, InstallForm installForm = null)
|
||||
private static void WriteConfig(StreamWriter writer, SortedList<string, SelectionDLC> blacklistDlc, InstallForm installForm = null)
|
||||
{
|
||||
writer.WriteLine("{");
|
||||
writer.WriteLine(" \"logging\": false,");
|
||||
|
|
|
@ -47,7 +47,7 @@ internal static class UplayR2
|
|||
}
|
||||
}
|
||||
|
||||
private static void WriteConfig(TextWriter writer, SortedList<string, SelectionDLC> blacklistDlc, InstallForm installForm = null)
|
||||
private static void WriteConfig(StreamWriter writer, SortedList<string, SelectionDLC> blacklistDlc, InstallForm installForm = null)
|
||||
{
|
||||
writer.WriteLine("{");
|
||||
writer.WriteLine(" \"logging\": false,");
|
||||
|
|
|
@ -139,7 +139,7 @@ internal static class ProgramData
|
|||
{
|
||||
try
|
||||
{
|
||||
if (choices is null || !choices.Any())
|
||||
if (choices is null || choices.Count == 0)
|
||||
DlcChoicesPath.DeleteFile();
|
||||
else
|
||||
DlcChoicesPath.WriteFile(JsonConvert.SerializeObject(choices));
|
||||
|
|
Loading…
Reference in a new issue