mirror of
https://github.com/sp-tarkov/launcher.git
synced 2025-02-12 14:30:44 -05:00
Add repo
This commit is contained in:
commit
dda5edfbc6
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
357
.gitignore
vendored
Normal file
357
.gitignore
vendored
Normal file
@ -0,0 +1,357 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# EmuTarkov
|
||||
[Bb]uild/
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Jetbrains IDEs database
|
||||
.idea/
|
||||
|
31
LICENSE.md
Normal file
31
LICENSE.md
Normal file
@ -0,0 +1,31 @@
|
||||
# NCSA Open Source License
|
||||
|
||||
Copyright (c) 2022 SPT-AKI. All rights reserved.
|
||||
|
||||
Developed by: SPT-AKI
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
with the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimers.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimers in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the names of SPT-AKI, nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this Software without specific
|
||||
prior written permission.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS WITH THE SOFTWARE.
|
27
README.md
Normal file
27
README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# Launcher
|
||||
|
||||
Custom launcher for Escape From Tarkov to start the game in offline mode
|
||||
|
||||
**Project** | **Function**
|
||||
------------------ | --------------------------------------------
|
||||
Aki.Build | Build script
|
||||
Aki.ByteBanger | Assembly-CSharp.dll patcher
|
||||
Aki.Launcher | Launcher frontend
|
||||
Aki.Launcher.Base | Launcher backend
|
||||
|
||||
## Requirements
|
||||
|
||||
- Escape From Tarkov 19765
|
||||
- .NET 6 SDK
|
||||
- Visual Studio Code
|
||||
|
||||
### For UI Development
|
||||
|
||||
- Visual Studio Community 2022 (.NET desktop workload)
|
||||
- Avalonia Visual Studio Extension
|
||||
|
||||
## Build
|
||||
|
||||
1. Open Launcher.code-workspace in Visual Studio Code.
|
||||
2. Run the build task: (top toolbar) Terminal -> Run Build Task...
|
||||
3. Copy-paste all files inside `Build` into `game root directory`, overwrite when prompted.
|
12
project/.config/dotnet-tools.json
Normal file
12
project/.config/dotnet-tools.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"cake.tool": {
|
||||
"version": "2.0.0",
|
||||
"commands": [
|
||||
"dotnet-cake"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
18
project/Aki.Build/Aki.Build.csproj
Normal file
18
project/Aki.Build/Aki.Build.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.ByteBanger\Aki.ByteBanger.csproj" />
|
||||
<ProjectReference Include="..\Aki.Launcher.Base\Aki.Launcher.Base.csproj" />
|
||||
<ProjectReference Include="..\Aki.Launcher\Aki.Launcher.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(BuildingInsideVisualStudio)' == 'true'">
|
||||
<Exec Command="dotnet cake "../build.cake" --config="$(ConfigurationName)" --vsbuilt=true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
7
project/Aki.ByteBanger/Aki.ByteBanger.csproj
Normal file
7
project/Aki.ByteBanger/Aki.ByteBanger.csproj
Normal file
@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
28
project/Aki.ByteBanger/DiffResult.cs
Normal file
28
project/Aki.ByteBanger/DiffResult.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace Aki.ByteBanger
|
||||
{
|
||||
public class DiffResult
|
||||
{
|
||||
public DiffResultType Result { get; }
|
||||
public PatchInfo PatchInfo { get; }
|
||||
|
||||
public DiffResult(DiffResultType result, PatchInfo patchInfo)
|
||||
{
|
||||
Result = result;
|
||||
PatchInfo = patchInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DiffResultType
|
||||
{
|
||||
Success,
|
||||
OriginalFilePathInvalid,
|
||||
OriginalFileNotFound,
|
||||
OriginalFileReadFailed,
|
||||
|
||||
PatchedFilePathInvalid,
|
||||
PatchedFileNotFound,
|
||||
PatchedFileReadFailed,
|
||||
|
||||
FilesMatch
|
||||
}
|
||||
}
|
36
project/Aki.ByteBanger/Docs/bpf-layout.md
Normal file
36
project/Aki.ByteBanger/Docs/bpf-layout.md
Normal file
@ -0,0 +1,36 @@
|
||||
# ByteBanger Binary Layout V 1.0
|
||||
|
||||
```unformatted
|
||||
42 59 42 41 # Identifier "BYBA" for ByteBanger
|
||||
01 00 # File version 1.0
|
||||
|
||||
00 89 54 98 # Original file length, Int32
|
||||
00 01 02 03 04 05 06 07 # -\
|
||||
08 09 0A 0B 0C 0D 0E 0F # | Original checksum, 32 Bytes
|
||||
10 11 12 13 14 15 16 17 # | SHA-256
|
||||
18 19 1A 1B 1C 1D 1E 1F # -/
|
||||
00 87 B8 00 # Patched file length, Int32
|
||||
20 21 22 23 24 25 26 27 # -\
|
||||
28 29 2A 2B 2C 2D 2E 2F # | Original checksum, 32 Bytes
|
||||
30 31 32 33 34 35 36 37 # | SHA-256
|
||||
38 39 3A 3B 3C 3D 3E 3F # -/
|
||||
00 00 00 04 # Count of patch items, Int32
|
||||
|
||||
00 00 00 9D # Offset from file start, Int32
|
||||
00 00 00 04 # Patch content length
|
||||
70 71 72 73 # Patch content
|
||||
|
||||
00 00 00 A8 # Offset, Int32
|
||||
00 00 00 04 # Patch content length
|
||||
80 81 82 83 # Content
|
||||
|
||||
00 00 00 B1 # Offset
|
||||
00 00 00 04 # Patch content length
|
||||
90 91 92 93 # Content
|
||||
|
||||
00 00 00 D1 # Offset
|
||||
00 00 00 04 # Patch content length
|
||||
A0 A1 A2 A3 # Content
|
||||
|
||||
Binary Length: 204 Bytes
|
||||
```
|
103
project/Aki.ByteBanger/PatchInfo.cs
Normal file
103
project/Aki.ByteBanger/PatchInfo.cs
Normal file
@ -0,0 +1,103 @@
|
||||
/* PatchInfo.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Basuro
|
||||
*/
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Aki.ByteBanger
|
||||
{
|
||||
public class PatchInfo
|
||||
{
|
||||
public const string BYBA = "BYBA";
|
||||
public byte[] OriginalChecksum { get; set; }
|
||||
public int OriginalLength { get; set; }
|
||||
public byte[] PatchedChecksum { get; set; }
|
||||
public int PatchedLength { get; set; }
|
||||
public PatchItem[] Items { get; set; }
|
||||
|
||||
public static PatchInfo FromBytes(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length < 82) throw new Exception("Input data too short, cannot be a valid patch");
|
||||
|
||||
PatchInfo pi = new PatchInfo();
|
||||
|
||||
using (MemoryStream ms = new MemoryStream(bytes))
|
||||
using (BinaryReader br = new BinaryReader(ms))
|
||||
{
|
||||
byte[] buf = null;
|
||||
|
||||
buf = br.ReadBytes(4);
|
||||
if (Encoding.ASCII.GetString(buf) != BYBA) throw new Exception("Invalid identifier");
|
||||
|
||||
if (br.ReadByte() != 1) throw new Exception("Invalid major file version (1 expected)");
|
||||
if (br.ReadByte() != 0) throw new Exception("Invalid minor file version (0 expected)");
|
||||
|
||||
pi.OriginalLength = br.ReadInt32();
|
||||
pi.OriginalChecksum = br.ReadBytes(32);
|
||||
pi.PatchedLength = br.ReadInt32();
|
||||
pi.PatchedChecksum = br.ReadBytes(32);
|
||||
|
||||
int itemCount = br.ReadInt32();
|
||||
|
||||
List<PatchItem> items = new List<PatchItem>();
|
||||
for (int i = 0; i < itemCount; i++)
|
||||
items.Add(PatchItem.FromReader(br));
|
||||
pi.Items = items.ToArray();
|
||||
}
|
||||
|
||||
return pi;
|
||||
}
|
||||
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
byte[] data;
|
||||
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
using (BinaryWriter bw = new BinaryWriter(ms, Encoding.ASCII, true))
|
||||
{
|
||||
// identifier "BYBA" // 4B
|
||||
byte[] byba = Encoding.ASCII.GetBytes(BYBA);
|
||||
bw.Write(byba, 0, byba.Length);
|
||||
|
||||
// version "1.0" // 2B
|
||||
bw.Write((byte)1);
|
||||
bw.Write((byte)0);
|
||||
|
||||
// original len // 4B
|
||||
bw.Write(OriginalLength);
|
||||
|
||||
// original chk // 32B
|
||||
bw.Write(OriginalChecksum, 0, OriginalChecksum.Length);
|
||||
|
||||
// patched len // 4B
|
||||
bw.Write(PatchedLength);
|
||||
|
||||
// patched chk // 32B
|
||||
bw.Write(PatchedChecksum, 0, PatchedChecksum.Length);
|
||||
|
||||
// item count // 4B
|
||||
bw.Write(Items.Length);
|
||||
|
||||
// data
|
||||
foreach (PatchItem pi in Items)
|
||||
pi.ToWriter(bw);
|
||||
}
|
||||
|
||||
data = new byte[ms.Length];
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
ms.Read(data, 0, (int)ms.Length);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
44
project/Aki.ByteBanger/PatchItem.cs
Normal file
44
project/Aki.ByteBanger/PatchItem.cs
Normal file
@ -0,0 +1,44 @@
|
||||
/* PatchItem.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Basuro
|
||||
*/
|
||||
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Aki.ByteBanger
|
||||
{
|
||||
public class PatchItem
|
||||
{
|
||||
public int Offset { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public static PatchItem FromReader(BinaryReader br)
|
||||
{
|
||||
int offset = br.ReadInt32();
|
||||
int dataLength = br.ReadInt32();
|
||||
byte[] data = br.ReadBytes(dataLength);
|
||||
|
||||
return new PatchItem
|
||||
{
|
||||
Offset = offset,
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
|
||||
internal void ToWriter(BinaryWriter bw)
|
||||
{
|
||||
// offset // 4B
|
||||
bw.Write(Offset);
|
||||
|
||||
// length // 4B
|
||||
bw.Write(Data.Length);
|
||||
|
||||
// data // xB
|
||||
bw.Write(Data, 0, Data.Length);
|
||||
}
|
||||
}
|
||||
}
|
26
project/Aki.ByteBanger/PatchResult.cs
Normal file
26
project/Aki.ByteBanger/PatchResult.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace Aki.ByteBanger
|
||||
{
|
||||
public class PatchResult
|
||||
{
|
||||
public PatchResultType Result { get; }
|
||||
public byte[] PatchedData { get; }
|
||||
|
||||
public PatchResult(PatchResultType result, byte[] patchedData)
|
||||
{
|
||||
Result = result;
|
||||
PatchedData = patchedData;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PatchResultType
|
||||
{
|
||||
Success,
|
||||
|
||||
InputLengthMismatch,
|
||||
InputChecksumMismatch,
|
||||
|
||||
AlreadyPatched,
|
||||
|
||||
OutputChecksumMismatch
|
||||
}
|
||||
}
|
137
project/Aki.ByteBanger/PatchUtil.cs
Normal file
137
project/Aki.ByteBanger/PatchUtil.cs
Normal file
@ -0,0 +1,137 @@
|
||||
/* BB.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Basuro
|
||||
*/
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Aki.ByteBanger
|
||||
{
|
||||
public static class PatchUtil
|
||||
{
|
||||
public static DiffResult Diff(byte[] original, byte[] patched)
|
||||
{
|
||||
PatchInfo pi = new PatchInfo
|
||||
{
|
||||
OriginalLength = original.Length,
|
||||
PatchedLength = patched.Length
|
||||
};
|
||||
|
||||
using (SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider())
|
||||
{
|
||||
pi.OriginalChecksum = sha256.ComputeHash(original);
|
||||
pi.PatchedChecksum = sha256.ComputeHash(patched);
|
||||
}
|
||||
|
||||
if ((pi.OriginalLength == pi.PatchedLength) && ArraysMatch(pi.OriginalChecksum, pi.PatchedChecksum))
|
||||
return new DiffResult(DiffResultType.FilesMatch, null);
|
||||
|
||||
int minLength = Math.Min(pi.OriginalLength, pi.PatchedLength);
|
||||
|
||||
List<PatchItem> items = new List<PatchItem>();
|
||||
List<byte> currentData = null;
|
||||
int diffOffsetStart = 0;
|
||||
|
||||
for (int i = 0; i < minLength; i++)
|
||||
{
|
||||
if (original[i] != patched[i])
|
||||
{
|
||||
if (currentData == null)
|
||||
{
|
||||
diffOffsetStart = i;
|
||||
currentData = new List<byte>();
|
||||
}
|
||||
|
||||
currentData.Add(patched[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentData != null)
|
||||
items.Add(new PatchItem { Offset = diffOffsetStart, Data = currentData.ToArray() });
|
||||
|
||||
currentData = null;
|
||||
diffOffsetStart = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentData != null)
|
||||
items.Add(new PatchItem { Offset = diffOffsetStart, Data = currentData.ToArray() });
|
||||
|
||||
if (pi.PatchedLength > pi.OriginalLength)
|
||||
{
|
||||
byte[] buf = new byte[pi.PatchedLength - pi.OriginalLength];
|
||||
Array.Copy(patched, pi.OriginalLength, buf, 0, buf.Length);
|
||||
items.Add(new PatchItem { Offset = pi.OriginalLength, Data = buf });
|
||||
}
|
||||
|
||||
pi.Items = items.ToArray();
|
||||
|
||||
return new DiffResult(DiffResultType.Success, pi);
|
||||
}
|
||||
|
||||
public static DiffResult Diff(string originalFile, string patchedFile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(originalFile)) return new DiffResult(DiffResultType.OriginalFilePathInvalid, null);
|
||||
if (string.IsNullOrWhiteSpace(patchedFile)) return new DiffResult(DiffResultType.PatchedFilePathInvalid, null);
|
||||
if (!File.Exists(originalFile)) return new DiffResult(DiffResultType.OriginalFileNotFound, null);
|
||||
if (!File.Exists(patchedFile)) return new DiffResult(DiffResultType.PatchedFileNotFound, null);
|
||||
|
||||
byte[] originalData, patchedData;
|
||||
|
||||
try { originalData = File.ReadAllBytes(originalFile); }
|
||||
catch { return new DiffResult(DiffResultType.OriginalFileReadFailed, null); }
|
||||
|
||||
try { patchedData = File.ReadAllBytes(patchedFile); }
|
||||
catch { return new DiffResult(DiffResultType.PatchedFileReadFailed, null); }
|
||||
|
||||
return Diff(originalData, patchedData);
|
||||
}
|
||||
|
||||
public static PatchResult Patch(byte[] input, PatchInfo pi)
|
||||
{
|
||||
byte[] inputHash;
|
||||
using (SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider())
|
||||
{
|
||||
inputHash = sha256.ComputeHash(input);
|
||||
}
|
||||
|
||||
if (ArraysMatch(inputHash, pi.PatchedChecksum)) return new PatchResult(PatchResultType.AlreadyPatched, null);
|
||||
if (!ArraysMatch(inputHash, pi.OriginalChecksum)) return new PatchResult(PatchResultType.InputChecksumMismatch, null);
|
||||
if (input.Length != pi.OriginalLength) return new PatchResult(PatchResultType.InputLengthMismatch, null);
|
||||
|
||||
byte[] patchedData = new byte[pi.PatchedLength];
|
||||
long minLen = Math.Min(pi.OriginalLength, pi.PatchedLength);
|
||||
Array.Copy(input, patchedData, minLen);
|
||||
|
||||
foreach (PatchItem itm in pi.Items)
|
||||
Array.Copy(itm.Data, 0, patchedData, itm.Offset, itm.Data.Length);
|
||||
|
||||
byte[] patchedHash;
|
||||
using (SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider())
|
||||
{
|
||||
patchedHash = sha256.ComputeHash(patchedData);
|
||||
}
|
||||
|
||||
if (!ArraysMatch(patchedHash, pi.PatchedChecksum)) return new PatchResult(PatchResultType.OutputChecksumMismatch, null);
|
||||
|
||||
return new PatchResult(PatchResultType.Success, patchedData);
|
||||
}
|
||||
|
||||
private static bool ArraysMatch(byte[] a, byte[] b)
|
||||
{
|
||||
if (a.Length != b.Length) return false;
|
||||
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
if (a[i] != b[i]) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
23
project/Aki.Launcher.Base/Aki.Launcher.Base.csproj
Normal file
23
project/Aki.Launcher.Base/Aki.Launcher.Base.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Aki.Launch</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="zlib.net, Version=1.0.3.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>..\Aki.Launcher\References\zlib.net.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.ByteBanger\Aki.ByteBanger.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
286
project/Aki.Launcher.Base/Controllers/AccountManager.cs
Normal file
286
project/Aki.Launcher.Base/Controllers/AccountManager.cs
Normal file
@ -0,0 +1,286 @@
|
||||
/* AccountManager.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using Aki.Launcher.Models.Aki;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public enum AccountStatus
|
||||
{
|
||||
OK = 0,
|
||||
NoConnection = 1,
|
||||
LoginFailed = 2,
|
||||
RegisterFailed = 3,
|
||||
UpdateFailed = 4
|
||||
}
|
||||
|
||||
public static class AccountManager
|
||||
{
|
||||
private const string STATUS_FAILED = "FAILED";
|
||||
private const string STATUS_OK = "OK";
|
||||
public static AccountInfo SelectedAccount { get; private set; } = null;
|
||||
public static ProfileInfo SelectedProfileInfo { get; private set; } = null;
|
||||
|
||||
public static void Logout() => SelectedAccount = null;
|
||||
|
||||
public static async Task<AccountStatus> LoginAsync(LoginModel Creds)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
return Login(Creds.Username, Creds.Password);
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<AccountStatus> LoginAsync(string username, string password)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
return Login(username, password);
|
||||
});
|
||||
}
|
||||
|
||||
public static AccountStatus Login(string username, string password)
|
||||
{
|
||||
LoginRequestData data = new LoginRequestData(username, password);
|
||||
string id = STATUS_FAILED;
|
||||
string json = "";
|
||||
|
||||
try
|
||||
{
|
||||
id = RequestHandler.RequestLogin(data);
|
||||
|
||||
if (id == STATUS_FAILED)
|
||||
{
|
||||
return AccountStatus.LoginFailed;
|
||||
}
|
||||
|
||||
json = RequestHandler.RequestAccount(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return AccountStatus.NoConnection;
|
||||
}
|
||||
|
||||
SelectedAccount = Json.Deserialize<AccountInfo>(json);
|
||||
RequestHandler.ChangeSession(SelectedAccount.id);
|
||||
|
||||
UpdateProfileInfo();
|
||||
|
||||
return AccountStatus.OK;
|
||||
}
|
||||
|
||||
public static void UpdateProfileInfo()
|
||||
{
|
||||
LoginRequestData data = new LoginRequestData(SelectedAccount.username, SelectedAccount.password);
|
||||
string profileInfoJson = RequestHandler.RequestProfileInfo(data);
|
||||
|
||||
if (profileInfoJson != null)
|
||||
{
|
||||
ServerProfileInfo serverProfileInfo = Json.Deserialize<ServerProfileInfo>(profileInfoJson);
|
||||
SelectedProfileInfo = new ProfileInfo(serverProfileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public static ServerProfileInfo[] GetExistingProfiles()
|
||||
{
|
||||
string profileJsonArray = RequestHandler.RequestExistingProfiles();
|
||||
|
||||
if(profileJsonArray != null)
|
||||
{
|
||||
ServerProfileInfo[] miniProfiles = Json.Deserialize<ServerProfileInfo[]>(profileJsonArray);
|
||||
|
||||
if (miniProfiles != null && miniProfiles.Length > 0)
|
||||
{
|
||||
return miniProfiles;
|
||||
}
|
||||
}
|
||||
|
||||
return new ServerProfileInfo[0];
|
||||
}
|
||||
|
||||
public static async Task<AccountStatus> RegisterAsync(string username, string password, string edition)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
return Register(username, password, edition);
|
||||
});
|
||||
}
|
||||
|
||||
public static AccountStatus Register(string username, string password, string edition)
|
||||
{
|
||||
RegisterRequestData data = new RegisterRequestData(username, password, edition);
|
||||
string registerStatus = STATUS_FAILED;
|
||||
|
||||
try
|
||||
{
|
||||
registerStatus = RequestHandler.RequestRegister(data);
|
||||
|
||||
if (registerStatus != STATUS_OK)
|
||||
{
|
||||
return AccountStatus.RegisterFailed;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return AccountStatus.NoConnection;
|
||||
}
|
||||
|
||||
return Login(username, password);
|
||||
}
|
||||
|
||||
//only added incase wanted for future use.
|
||||
public static async Task<AccountStatus> RemoveAsync()
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
return Remove();
|
||||
});
|
||||
}
|
||||
|
||||
public static AccountStatus Remove()
|
||||
{
|
||||
LoginRequestData data = new LoginRequestData(SelectedAccount.username, SelectedAccount.password);
|
||||
|
||||
try
|
||||
{
|
||||
string json = RequestHandler.RequestRemove(data);
|
||||
|
||||
if(Json.Deserialize<bool>(json))
|
||||
{
|
||||
SelectedAccount = null;
|
||||
|
||||
return AccountStatus.OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AccountStatus.UpdateFailed;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return AccountStatus.NoConnection;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<AccountStatus> ChangeUsernameAsync(string username)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
return ChangeUsername(username);
|
||||
});
|
||||
}
|
||||
|
||||
public static AccountStatus ChangeUsername(string username)
|
||||
{
|
||||
ChangeRequestData data = new ChangeRequestData(SelectedAccount.username, SelectedAccount.password, username);
|
||||
string json = STATUS_FAILED;
|
||||
|
||||
try
|
||||
{
|
||||
json = RequestHandler.RequestChangeUsername(data);
|
||||
|
||||
if (json != STATUS_OK)
|
||||
{
|
||||
return AccountStatus.UpdateFailed;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return AccountStatus.NoConnection;
|
||||
}
|
||||
|
||||
ServerSetting DefaultServer = LauncherSettingsProvider.Instance.Server;
|
||||
|
||||
if (DefaultServer.AutoLoginCreds != null)
|
||||
{
|
||||
DefaultServer.AutoLoginCreds.Username = username;
|
||||
}
|
||||
|
||||
SelectedAccount.username = username;
|
||||
LauncherSettingsProvider.Instance.SaveSettings();
|
||||
|
||||
return AccountStatus.OK;
|
||||
}
|
||||
|
||||
public static async Task<AccountStatus> ChangePasswordAsync(string password)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
return ChangePassword(password);
|
||||
});
|
||||
}
|
||||
public static AccountStatus ChangePassword(string password)
|
||||
{
|
||||
ChangeRequestData data = new ChangeRequestData(SelectedAccount.username, SelectedAccount.password, password);
|
||||
string json = STATUS_FAILED;
|
||||
|
||||
try
|
||||
{
|
||||
json = RequestHandler.RequestChangePassword(data);
|
||||
|
||||
if (json != STATUS_OK)
|
||||
{
|
||||
return AccountStatus.UpdateFailed;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return AccountStatus.NoConnection;
|
||||
}
|
||||
|
||||
ServerSetting DefaultServer = LauncherSettingsProvider.Instance.Server;
|
||||
|
||||
if (DefaultServer.AutoLoginCreds != null)
|
||||
{
|
||||
DefaultServer.AutoLoginCreds.Password = password;
|
||||
}
|
||||
|
||||
SelectedAccount.password = password;
|
||||
LauncherSettingsProvider.Instance.SaveSettings();
|
||||
|
||||
return AccountStatus.OK;
|
||||
}
|
||||
|
||||
public static async Task<AccountStatus> WipeAsync(string edition)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
return Wipe(edition);
|
||||
});
|
||||
}
|
||||
|
||||
public static AccountStatus Wipe(string edition)
|
||||
{
|
||||
RegisterRequestData data = new RegisterRequestData(SelectedAccount.username, SelectedAccount.password, edition);
|
||||
string json = STATUS_FAILED;
|
||||
|
||||
try
|
||||
{
|
||||
json = RequestHandler.RequestWipe(data);
|
||||
|
||||
if (json != STATUS_OK)
|
||||
{
|
||||
return AccountStatus.UpdateFailed;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return AccountStatus.NoConnection;
|
||||
}
|
||||
|
||||
SelectedAccount.edition = edition;
|
||||
return AccountStatus.OK;
|
||||
}
|
||||
}
|
||||
}
|
305
project/Aki.Launcher.Base/Controllers/GameStarter.cs
Normal file
305
project/Aki.Launcher.Base/Controllers/GameStarter.cs
Normal file
@ -0,0 +1,305 @@
|
||||
/* GameStarter.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
* reider123
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Aki.Launcher.Controllers;
|
||||
using Aki.Launcher.Interfaces;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public class GameStarter
|
||||
{
|
||||
private readonly IGameStarterFrontend _frontend;
|
||||
private readonly bool _showOnly;
|
||||
private readonly string _originalGamePath;
|
||||
private readonly string _gamePath;
|
||||
private readonly string[] _excludeFromCleanup;
|
||||
private const string registryInstall = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov";
|
||||
|
||||
private const string registrySettings = @"Software\Battlestate Games\EscapeFromTarkov";
|
||||
|
||||
public GameStarter(IGameStarterFrontend frontend, string gamePath = null, string originalGamePath = null,
|
||||
bool showOnly = false, string[] excludeFromCleanup = null)
|
||||
{
|
||||
_frontend = frontend;
|
||||
_showOnly = showOnly;
|
||||
_gamePath = gamePath ?? LauncherSettingsProvider.Instance.GamePath ?? Environment.CurrentDirectory;
|
||||
_originalGamePath = originalGamePath ??= DetectOriginalGamePath();
|
||||
_excludeFromCleanup = excludeFromCleanup ?? LauncherSettingsProvider.Instance.ExcludeFromCleanup;
|
||||
}
|
||||
|
||||
private static string DetectOriginalGamePath()
|
||||
{
|
||||
// We can't detect the installed path on non-Windows
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return null;
|
||||
|
||||
var uninstallStringValue = Registry.LocalMachine.OpenSubKey(registryInstall, false)
|
||||
?.GetValue("UninstallString");
|
||||
var info = (uninstallStringValue is string key) ? new FileInfo(key) : null;
|
||||
return info?.DirectoryName;
|
||||
}
|
||||
|
||||
public async Task<GameStarterResult> LaunchGame(ServerInfo server, AccountInfo account)
|
||||
{
|
||||
// setup directories
|
||||
if (IsInstalledInLive())
|
||||
{
|
||||
LogManager.Instance.Warning("Failed installed in live check");
|
||||
return GameStarterResult.FromError(-1);
|
||||
}
|
||||
|
||||
SetupGameFiles();
|
||||
|
||||
if (!ValidationUtil.Validate())
|
||||
{
|
||||
LogManager.Instance.Warning("Failed validation check");
|
||||
return GameStarterResult.FromError(-2);
|
||||
}
|
||||
|
||||
if (account.wipe)
|
||||
{
|
||||
RemoveRegistryKeys();
|
||||
CleanTempFiles();
|
||||
}
|
||||
|
||||
// check game path
|
||||
var clientExecutable = Path.Join(_gamePath, "EscapeFromTarkov.exe");
|
||||
|
||||
if (!File.Exists(clientExecutable))
|
||||
{
|
||||
LogManager.Instance.Warning($"Could not find {clientExecutable}");
|
||||
return GameStarterResult.FromError(-6);
|
||||
}
|
||||
|
||||
// apply patches
|
||||
ProgressReportingPatchRunner patchRunner = new ProgressReportingPatchRunner(_gamePath);
|
||||
|
||||
try
|
||||
{
|
||||
await _frontend.CompletePatchTask(patchRunner.PatchFiles());
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
LogManager.Instance.Warning("Failed to apply assembly patch");
|
||||
return GameStarterResult.FromError(-4);
|
||||
}
|
||||
|
||||
//start game
|
||||
var args =
|
||||
$"-force-gfx-jobs native -token={account.id} -config={Json.Serialize(new ClientConfig(server.backendUrl))}";
|
||||
|
||||
if (_showOnly)
|
||||
{
|
||||
Console.WriteLine($"{clientExecutable} {args}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var clientProcess = new ProcessStartInfo(clientExecutable)
|
||||
{
|
||||
Arguments = args,
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = _gamePath,
|
||||
};
|
||||
|
||||
Process.Start(clientProcess);
|
||||
}
|
||||
|
||||
return GameStarterResult.FromSuccess();
|
||||
}
|
||||
|
||||
bool IsInstalledInLive()
|
||||
{
|
||||
var isInstalledInLive = false;
|
||||
|
||||
try
|
||||
{
|
||||
var files = new FileInfo[]
|
||||
{
|
||||
// aki files
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"Aki.Launcher.exe")),
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"Aki.Server.exe")),
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"EscapeFromTarkov_Data\Managed\Aki.Build.dll")),
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"EscapeFromTarkov_Data\Managed\Aki.Common.dll")),
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"EscapeFromTarkov_Data\Managed\Aki.Reflection.dll")),
|
||||
|
||||
// bepinex files
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"doorstep_config.ini")),
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"winhttp.dll")),
|
||||
|
||||
// licenses
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"LICENSE-BEPINEX.txt")),
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"LICENSE-ConfigurationManager.txt")),
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"LICENSE-Launcher.txt")),
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"LICENSE-Modules.txt")),
|
||||
new FileInfo(Path.Combine(_originalGamePath, @"LICENSE-Server.txt"))
|
||||
};
|
||||
var directories = new DirectoryInfo[]
|
||||
{
|
||||
new DirectoryInfo(Path.Combine(_originalGamePath, @"Aki_Data")),
|
||||
new DirectoryInfo(Path.Combine(_originalGamePath, @"BepInEx"))
|
||||
};
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (File.Exists(file.FullName))
|
||||
{
|
||||
File.Delete(file.FullName);
|
||||
isInstalledInLive = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
if (Directory.Exists(directory.FullName))
|
||||
{
|
||||
RemoveFilesRecurse(directory);
|
||||
isInstalledInLive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Instance.Exception(ex);
|
||||
}
|
||||
|
||||
return isInstalledInLive;
|
||||
}
|
||||
|
||||
void SetupGameFiles()
|
||||
{
|
||||
var files = new []
|
||||
{
|
||||
GetFileForCleanup("BattlEye"),
|
||||
GetFileForCleanup("Logs"),
|
||||
GetFileForCleanup("ConsistencyInfo"),
|
||||
GetFileForCleanup("EscapeFromTarkov_BE.exe"),
|
||||
GetFileForCleanup("Uninstall.exe"),
|
||||
GetFileForCleanup("UnityCrashHandler64.exe"),
|
||||
GetFileForCleanup("WinPixEventRuntime.dll")
|
||||
};
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Directory.Exists(file))
|
||||
{
|
||||
RemoveFilesRecurse(new DirectoryInfo(file));
|
||||
}
|
||||
|
||||
if (File.Exists(file))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFileForCleanup(string fileName)
|
||||
{
|
||||
if (_excludeFromCleanup.Contains(fileName))
|
||||
{
|
||||
LogManager.Instance.Info($"Excluded {fileName} from file cleanup");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Path.Combine(_gamePath, fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the registry keys
|
||||
/// </summary>
|
||||
/// <returns>returns true if the keys were removed. returns false if an exception occured</returns>
|
||||
public bool RemoveRegistryKeys()
|
||||
{
|
||||
try
|
||||
{
|
||||
var key = Registry.CurrentUser.OpenSubKey(registrySettings, true);
|
||||
|
||||
foreach (var value in key.GetValueNames())
|
||||
{
|
||||
key.DeleteValue(value);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Instance.Exception(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean the temp folder
|
||||
/// </summary>
|
||||
/// <returns>returns true if the temp folder was cleaned succefully or doesn't exist. returns false if something went wrong.</returns>
|
||||
public bool CleanTempFiles()
|
||||
{
|
||||
var rootdir = new DirectoryInfo(Path.Combine(Path.GetTempPath(), @"Battlestate Games\EscapeFromTarkov"));
|
||||
|
||||
if (!rootdir.Exists)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return RemoveFilesRecurse(rootdir);
|
||||
}
|
||||
|
||||
bool RemoveFilesRecurse(DirectoryInfo basedir)
|
||||
{
|
||||
if (!basedir.Exists)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// remove subdirectories
|
||||
foreach (var dir in basedir.EnumerateDirectories())
|
||||
{
|
||||
RemoveFilesRecurse(dir);
|
||||
}
|
||||
|
||||
// remove files
|
||||
var files = basedir.GetFiles();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
file.IsReadOnly = false;
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
// remove directory
|
||||
basedir.Delete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Instance.Exception(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
51
project/Aki.Launcher.Base/Controllers/LogManager.cs
Normal file
51
project/Aki.Launcher.Base/Controllers/LogManager.cs
Normal file
@ -0,0 +1,51 @@
|
||||
/* LogManager.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Aki.Launcher.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// LogManager
|
||||
/// </summary>
|
||||
public class LogManager
|
||||
{
|
||||
//TODO - update this to use reflection to get the calling method, class, etc
|
||||
private static LogManager _instance;
|
||||
public static LogManager Instance => _instance ?? (_instance = new LogManager());
|
||||
private string filepath;
|
||||
|
||||
public LogManager()
|
||||
{
|
||||
filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "user", "logs");
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
{
|
||||
if (!Directory.Exists(filepath))
|
||||
{
|
||||
Directory.CreateDirectory(filepath);
|
||||
}
|
||||
|
||||
string filename = Path.Combine(filepath, "launcher.log");
|
||||
File.AppendAllLines(filename, new[] { $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}]{text}" });
|
||||
}
|
||||
|
||||
public void Debug(string text) => Write($"[Debug]{text}");
|
||||
|
||||
public void Info(string text) => Write($"[Info]{text}");
|
||||
|
||||
public void Warning(string text) => Write($"[Warning]{text}");
|
||||
|
||||
public void Error(string text) => Write($"[Error]{text}");
|
||||
|
||||
public void Exception(Exception ex) => Write($"[Exception]{ex.Message}\nStacktrace:\n{ex.StackTrace}");
|
||||
}
|
||||
}
|
98
project/Aki.Launcher.Base/Controllers/RequestHandler.cs
Normal file
98
project/Aki.Launcher.Base/Controllers/RequestHandler.cs
Normal file
@ -0,0 +1,98 @@
|
||||
/* RequestHandler.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using Aki.Launcher.MiniCommon;
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public static class RequestHandler
|
||||
{
|
||||
private static Request request = new Request(null, "");
|
||||
|
||||
public static string GetBackendUrl()
|
||||
{
|
||||
return request.RemoteEndPoint;
|
||||
}
|
||||
|
||||
public static void ChangeBackendUrl(string remoteEndPoint)
|
||||
{
|
||||
request.RemoteEndPoint = remoteEndPoint;
|
||||
}
|
||||
|
||||
public static void ChangeSession(string session)
|
||||
{
|
||||
request.Session = session;
|
||||
}
|
||||
|
||||
public static string RequestConnect()
|
||||
{
|
||||
return request.GetJson("/launcher/server/connect");
|
||||
}
|
||||
|
||||
public static string RequestLogin(LoginRequestData data)
|
||||
{
|
||||
return request.PostJson("/launcher/profile/login", Json.Serialize(data));
|
||||
}
|
||||
|
||||
public static string RequestRegister(RegisterRequestData data)
|
||||
{
|
||||
return request.PostJson("/launcher/profile/register", Json.Serialize(data));
|
||||
}
|
||||
|
||||
public static string RequestRemove(LoginRequestData data)
|
||||
{
|
||||
return request.PostJson("/launcher/profile/remove", Json.Serialize(data));
|
||||
}
|
||||
|
||||
public static string RequestAccount(LoginRequestData data)
|
||||
{
|
||||
return request.PostJson("/launcher/profile/get", Json.Serialize(data));
|
||||
}
|
||||
|
||||
public static string RequestProfileInfo(LoginRequestData data)
|
||||
{
|
||||
return request.PostJson("/launcher/profile/info", Json.Serialize(data));
|
||||
}
|
||||
|
||||
public static string RequestExistingProfiles()
|
||||
{
|
||||
return request.GetJson("/launcher/profiles");
|
||||
}
|
||||
|
||||
public static string RequestChangeUsername(ChangeRequestData data)
|
||||
{
|
||||
return request.PostJson("/launcher/profile/change/username", Json.Serialize(data));
|
||||
}
|
||||
|
||||
public static string RequestChangePassword(ChangeRequestData data)
|
||||
{
|
||||
return request.PostJson("/launcher/profile/change/password", Json.Serialize(data));
|
||||
}
|
||||
|
||||
public static string RequestWipe(RegisterRequestData data)
|
||||
{
|
||||
return request.PostJson("/launcher/profile/change/wipe", Json.Serialize(data));
|
||||
}
|
||||
|
||||
public static string SendPing()
|
||||
{
|
||||
return request.GetJson("/launcher/ping");
|
||||
}
|
||||
|
||||
public static string RequestServerVersion()
|
||||
{
|
||||
return request.GetJson("/launcher/server/version");
|
||||
}
|
||||
|
||||
public static string RequestCompatibleGameVersion()
|
||||
{
|
||||
return request.GetJson("/launcher/profile/compatibleTarkovVersion");
|
||||
}
|
||||
}
|
||||
}
|
91
project/Aki.Launcher.Base/Controllers/ServerManager.cs
Normal file
91
project/Aki.Launcher.Base/Controllers/ServerManager.cs
Normal file
@ -0,0 +1,91 @@
|
||||
/* ServerManager.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public static class ServerManager
|
||||
{
|
||||
public static ServerInfo SelectedServer { get; private set; } = null;
|
||||
|
||||
public static bool PingServer()
|
||||
{
|
||||
string json = "";
|
||||
|
||||
try
|
||||
{
|
||||
json = RequestHandler.SendPing();
|
||||
|
||||
if(json != null) return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string GetVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = RequestHandler.RequestServerVersion();
|
||||
|
||||
return Json.Deserialize<string>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetCompatibleGameVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = RequestHandler.RequestCompatibleGameVersion();
|
||||
|
||||
return Json.Deserialize<string>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadServer(string backendUrl)
|
||||
{
|
||||
string json = "";
|
||||
|
||||
try
|
||||
{
|
||||
RequestHandler.ChangeBackendUrl(backendUrl);
|
||||
json = RequestHandler.RequestConnect();
|
||||
}
|
||||
catch
|
||||
{
|
||||
SelectedServer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedServer = Json.Deserialize<ServerInfo>(json);
|
||||
}
|
||||
|
||||
public static async Task LoadDefaultServerAsync(string server)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
LoadServer(server);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
37
project/Aki.Launcher.Base/Extensions/DictionaryExtensions.cs
Normal file
37
project/Aki.Launcher.Base/Extensions/DictionaryExtensions.cs
Normal file
@ -0,0 +1,37 @@
|
||||
/* DictionaryExtension.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Aki.Launcher.Extensions
|
||||
{
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
public static TKey GetKeyByValue<TKey, TValue>(this Dictionary<TKey, TValue> Dic, TValue value)
|
||||
{
|
||||
List<TKey> Keys = Dic.Keys.ToList();
|
||||
|
||||
for (int x = 0; x < Keys.Count(); x++)
|
||||
{
|
||||
TValue tempValue;
|
||||
|
||||
if (Dic.TryGetValue(Keys[x], out tempValue))
|
||||
{
|
||||
if (tempValue != null && tempValue.Equals(value))
|
||||
{
|
||||
return Keys[x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
130
project/Aki.Launcher.Base/Helpers/FilePatcher.cs
Normal file
130
project/Aki.Launcher.Base/Helpers/FilePatcher.cs
Normal file
@ -0,0 +1,130 @@
|
||||
/* FilePatcher.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using Aki.ByteBanger;
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
|
||||
namespace Aki.Launcher.Helpers
|
||||
{
|
||||
public static class FilePatcher
|
||||
{
|
||||
public static event EventHandler<ProgressInfo> PatchProgress;
|
||||
private static void RaisePatchProgress(int Percentage, string Message)
|
||||
{
|
||||
PatchProgress?.Invoke(null, new ProgressInfo(Percentage, Message));
|
||||
}
|
||||
|
||||
public static PatchResultInfo Patch(string targetfile, string patchfile, bool IgnoreInputHashMismatch = false)
|
||||
{
|
||||
byte[] target = VFS.ReadFile(targetfile);
|
||||
byte[] patch = VFS.ReadFile(patchfile);
|
||||
|
||||
PatchResult result = PatchUtil.Patch(target, PatchInfo.FromBytes(patch));
|
||||
|
||||
switch (result.Result)
|
||||
{
|
||||
case PatchResultType.Success:
|
||||
File.Copy(targetfile, $"{targetfile}.bak");
|
||||
VFS.WriteFile(targetfile, result.PatchedData);
|
||||
break;
|
||||
|
||||
case PatchResultType.InputChecksumMismatch:
|
||||
if (IgnoreInputHashMismatch)
|
||||
return new PatchResultInfo(PatchResultType.Success, 1, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
return new PatchResultInfo(result.Result, 1, 1);
|
||||
}
|
||||
|
||||
private static PatchResultInfo PatchAll(string targetpath, string patchpath, bool IgnoreInputHashMismatch = false)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(patchpath);
|
||||
|
||||
// get all patch files within patchpath and it's sub directories.
|
||||
var patchfiles = di.GetFiles("*.bpf", SearchOption.AllDirectories);
|
||||
|
||||
int countfiles = patchfiles.Length;
|
||||
|
||||
int processed = 0;
|
||||
|
||||
foreach (FileInfo file in patchfiles)
|
||||
{
|
||||
FileInfo target;
|
||||
|
||||
int progress = (int)Math.Floor((double)processed / countfiles * 100);
|
||||
RaisePatchProgress(progress, $"{LocalizationProvider.Instance.patching} {file.Name} ...");
|
||||
|
||||
// get the relative portion of the patch file that will be appended to targetpath in order to create an official target file.
|
||||
var relativefile = file.FullName.Substring(patchpath.Length).TrimStart('\\', '/');
|
||||
|
||||
// create a target file from the relative patch file while utilizing targetpath as the root directory.
|
||||
target = new FileInfo(VFS.Combine(targetpath, relativefile.Replace(".bpf", "")));
|
||||
|
||||
PatchResultInfo result = Patch(target.FullName, file.FullName, IgnoreInputHashMismatch);
|
||||
|
||||
if (!result.OK)
|
||||
{
|
||||
// patch failed
|
||||
return result;
|
||||
}
|
||||
|
||||
processed++;
|
||||
}
|
||||
|
||||
RaisePatchProgress(100, LocalizationProvider.Instance.ok);
|
||||
|
||||
return new PatchResultInfo(PatchResultType.Success, processed, countfiles);
|
||||
}
|
||||
|
||||
public static PatchResultInfo Run(string targetPath, string patchPath, bool IgnoreInputHashMismatch = false)
|
||||
{
|
||||
return PatchAll(targetPath, patchPath, IgnoreInputHashMismatch);
|
||||
}
|
||||
|
||||
public static void Restore(string filepath)
|
||||
{
|
||||
RestoreRecurse(new DirectoryInfo(filepath));
|
||||
}
|
||||
|
||||
static void RestoreRecurse(DirectoryInfo basedir)
|
||||
{
|
||||
// scan subdirectories
|
||||
foreach (var dir in basedir.EnumerateDirectories())
|
||||
{
|
||||
RestoreRecurse(dir);
|
||||
}
|
||||
|
||||
// scan files
|
||||
var files = basedir.GetFiles();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (file.Extension == ".bak")
|
||||
{
|
||||
var target = Path.ChangeExtension(file.FullName, null);
|
||||
|
||||
// remove patched file
|
||||
var patched = new FileInfo(target);
|
||||
patched.IsReadOnly = false;
|
||||
patched.Delete();
|
||||
|
||||
// restore from backup
|
||||
File.Copy(file.FullName, target);
|
||||
file.IsReadOnly = false;
|
||||
file.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
163
project/Aki.Launcher.Base/Helpers/LauncherSettingsProvider.cs
Normal file
163
project/Aki.Launcher.Base/Helpers/LauncherSettingsProvider.cs
Normal file
@ -0,0 +1,163 @@
|
||||
/* LauncherSettingsProvider.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Aki.Launcher.Helpers
|
||||
{
|
||||
public static class LauncherSettingsProvider
|
||||
{
|
||||
public static string DefaultSettingsFileLocation = Path.Join(Environment.CurrentDirectory, "user", "launcher", "config.json");
|
||||
public static Settings Instance { get; } = Json.Load<Settings>(DefaultSettingsFileLocation) ?? new Settings();
|
||||
}
|
||||
|
||||
public class Settings : INotifyPropertyChanged
|
||||
{
|
||||
public bool FirstRun { get; set; } = true;
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
Json.SaveWithFormatting(LauncherSettingsProvider.DefaultSettingsFileLocation, this, Formatting.Indented);
|
||||
}
|
||||
|
||||
public string DefaultLocale { get; set; } = "English";
|
||||
|
||||
private bool _IsAddingServer;
|
||||
[JsonIgnore]
|
||||
public bool IsAddingServer
|
||||
{
|
||||
get => _IsAddingServer;
|
||||
set
|
||||
{
|
||||
if (_IsAddingServer != value)
|
||||
{
|
||||
_IsAddingServer = value;
|
||||
RaisePropertyChanged(nameof(IsAddingServer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _AllowSettings;
|
||||
[JsonIgnore]
|
||||
public bool AllowSettings
|
||||
{
|
||||
get => _AllowSettings;
|
||||
set
|
||||
{
|
||||
if (_AllowSettings != value)
|
||||
{
|
||||
_AllowSettings = value;
|
||||
RaisePropertyChanged(nameof(AllowSettings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _GameRunning;
|
||||
[JsonIgnore]
|
||||
public bool GameRunning
|
||||
{
|
||||
get => _GameRunning;
|
||||
set
|
||||
{
|
||||
if (_GameRunning != value)
|
||||
{
|
||||
_GameRunning = value;
|
||||
RaisePropertyChanged(nameof(GameRunning));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LauncherAction _LauncherStartGameAction;
|
||||
public LauncherAction LauncherStartGameAction
|
||||
{
|
||||
get => _LauncherStartGameAction;
|
||||
set
|
||||
{
|
||||
if (_LauncherStartGameAction != value)
|
||||
{
|
||||
_LauncherStartGameAction = value;
|
||||
RaisePropertyChanged(nameof(LauncherStartGameAction));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _UseAutoLogin;
|
||||
public bool UseAutoLogin
|
||||
{
|
||||
get => _UseAutoLogin;
|
||||
set
|
||||
{
|
||||
if (_UseAutoLogin != value)
|
||||
{
|
||||
_UseAutoLogin = value;
|
||||
RaisePropertyChanged(nameof(UseAutoLogin));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _GamePath;
|
||||
public string GamePath
|
||||
{
|
||||
get => _GamePath;
|
||||
set
|
||||
{
|
||||
if (_GamePath != value)
|
||||
{
|
||||
_GamePath = value;
|
||||
RaisePropertyChanged(nameof(GamePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string[] _ExcludeFromCleanup;
|
||||
|
||||
public string[] ExcludeFromCleanup
|
||||
{
|
||||
get => _ExcludeFromCleanup ??= Array.Empty<string>();
|
||||
set
|
||||
{
|
||||
if (_ExcludeFromCleanup != value)
|
||||
{
|
||||
_ExcludeFromCleanup = value;
|
||||
RaisePropertyChanged(nameof(ExcludeFromCleanup));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ServerSetting Server { get; set; } = new ServerSetting();
|
||||
|
||||
public Settings()
|
||||
{
|
||||
if (!File.Exists(LauncherSettingsProvider.DefaultSettingsFileLocation))
|
||||
{
|
||||
LauncherStartGameAction = LauncherAction.MinimizeAction;
|
||||
UseAutoLogin = true;
|
||||
GamePath = Environment.CurrentDirectory;
|
||||
|
||||
Server = new ServerSetting { Name = "SPT-AKI", Url = "http://127.0.0.1:6969" };
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
1514
project/Aki.Launcher.Base/Helpers/LocalizationProvider.cs
Normal file
1514
project/Aki.Launcher.Base/Helpers/LocalizationProvider.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,76 @@
|
||||
/* ProgressReportingPatchRunner.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Aki.ByteBanger;
|
||||
|
||||
namespace Aki.Launcher.Helpers
|
||||
{
|
||||
public class ProgressReportingPatchRunner
|
||||
{
|
||||
private string GamePath;
|
||||
private string[] Patches;
|
||||
|
||||
private async IAsyncEnumerable<PatchResultInfo> TryPatchFiles(bool IgnoreInputHashMismatch)
|
||||
{
|
||||
FilePatcher.Restore(GamePath);
|
||||
|
||||
int processed = 0;
|
||||
int countpatches = Patches.Length;
|
||||
|
||||
var _patches = Patches;
|
||||
foreach (var patch in _patches)
|
||||
{
|
||||
var result =
|
||||
await Task.Factory.StartNew(() => FilePatcher.Run(GamePath, patch, IgnoreInputHashMismatch));
|
||||
if (!result.OK)
|
||||
{
|
||||
yield return new PatchResultInfo(result.Status, processed, countpatches);
|
||||
yield break;
|
||||
}
|
||||
|
||||
processed++;
|
||||
var ourResult = new PatchResultInfo(PatchResultType.Success, processed, countpatches);
|
||||
yield return ourResult;
|
||||
}
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<PatchResultInfo> PatchFiles()
|
||||
{
|
||||
await foreach (var info in TryPatchFiles(false))
|
||||
{
|
||||
yield return info;
|
||||
|
||||
if (info.OK)
|
||||
continue;
|
||||
|
||||
// This will run _after_ the caller decides to continue iterating.
|
||||
await foreach (var secondInfo in TryPatchFiles(true))
|
||||
{
|
||||
yield return secondInfo;
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetCorePatches()
|
||||
{
|
||||
return VFS.GetDirectories(VFS.Combine(GamePath, "Aki_Data/Launcher/Patches/"));
|
||||
}
|
||||
|
||||
public ProgressReportingPatchRunner(string GamePath, string[] Patches = null)
|
||||
{
|
||||
this.GamePath = GamePath;
|
||||
this.Patches = Patches ?? GetCorePatches();
|
||||
}
|
||||
}
|
||||
}
|
46
project/Aki.Launcher.Base/Helpers/ValidationUtil.cs
Normal file
46
project/Aki.Launcher.Base/Helpers/ValidationUtil.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Microsoft.Win32;
|
||||
using System.IO;
|
||||
|
||||
namespace Aki.Launcher.Helpers
|
||||
{
|
||||
public static class ValidationUtil
|
||||
{
|
||||
public static bool Validate()
|
||||
{
|
||||
var c0 = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov";
|
||||
var v0 = 0;
|
||||
|
||||
try
|
||||
{
|
||||
var v1 = Registry.LocalMachine.OpenSubKey(c0, false).GetValue("UninstallString");
|
||||
var v2 = (v1 != null) ? v1.ToString() : string.Empty;
|
||||
var v3 = new FileInfo(v2);
|
||||
var v4 = new FileInfo[]
|
||||
{
|
||||
v3,
|
||||
new FileInfo(v2.Replace(v3.Name, @"BattlEye\BEClient_x64.dll")),
|
||||
new FileInfo(v2.Replace(v3.Name, @"BattlEye\BEService_x64.dll")),
|
||||
new FileInfo(v2.Replace(v3.Name, @"ConsistencyInfo")),
|
||||
new FileInfo(v2.Replace(v3.Name, @"Uninstall.exe")),
|
||||
new FileInfo(v2.Replace(v3.Name, @"UnityCrashHandler64.exe"))
|
||||
};
|
||||
|
||||
v0 = v4.Length - 1;
|
||||
|
||||
foreach (var value in v4)
|
||||
{
|
||||
if (File.Exists(value.FullName))
|
||||
{
|
||||
--v0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
v0 = -1;
|
||||
}
|
||||
|
||||
return v0 == 0;
|
||||
}
|
||||
}
|
||||
}
|
11
project/Aki.Launcher.Base/Interfaces/IGameStarterFrontend.cs
Normal file
11
project/Aki.Launcher.Base/Interfaces/IGameStarterFrontend.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
|
||||
namespace Aki.Launcher.Interfaces
|
||||
{
|
||||
public interface IGameStarterFrontend
|
||||
{
|
||||
Task CompletePatchTask(IAsyncEnumerable<PatchResultInfo> task);
|
||||
}
|
||||
}
|
31
project/Aki.Launcher.Base/Interfaces/IUpdateProgress.cs
Normal file
31
project/Aki.Launcher.Base/Interfaces/IUpdateProgress.cs
Normal file
@ -0,0 +1,31 @@
|
||||
/* IUpdateProgress
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using System;
|
||||
|
||||
namespace Aki.Launcher.Interfaces
|
||||
{
|
||||
public interface IUpdateProgress
|
||||
{
|
||||
/// <summary>
|
||||
/// The task that will report progress to the <see cref="Custom_Controls.Dialogs.ProgressDialog"/>
|
||||
/// </summary>
|
||||
public Action ProgressableTask { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cancel the ProgressableTask with a reason.
|
||||
/// </summary>
|
||||
public event EventHandler<object> TaskCancelled;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Custom_Controls.Dialogs.ProgressDialog"/> will subscribe to this event to update its main progress bar (top bar)
|
||||
/// </summary>
|
||||
public event EventHandler<ProgressInfo> ProgressChanged;
|
||||
}
|
||||
}
|
66
project/Aki.Launcher.Base/MiniCommon/ImageRequest.cs
Normal file
66
project/Aki.Launcher.Base/MiniCommon/ImageRequest.cs
Normal file
@ -0,0 +1,66 @@
|
||||
/* ImageRequest.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
using Aki.Launcher.Controllers;
|
||||
using Aki.Launcher.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Aki.Launcher.MiniCommon
|
||||
{
|
||||
|
||||
public static class ImageRequest
|
||||
{
|
||||
public static string ImageCacheFolder = Path.Join(LauncherSettingsProvider.Instance.GamePath, "Aki_Data", "Launcher", "Image_Cache");
|
||||
|
||||
private static List<string> CachedRoutes = new List<string>();
|
||||
|
||||
private static string LauncherRoute = "/files/launcher/";
|
||||
public static void CacheBackgroundImage() => CacheImage($"{LauncherRoute}bg.png", Path.Combine(ImageCacheFolder, "bg.png"));
|
||||
public static void CacheSideImage(string Side)
|
||||
{
|
||||
if (Side == null || string.IsNullOrWhiteSpace(Side) || Side.ToLower() == "unknown") return;
|
||||
|
||||
string SideImagePath = Path.Combine(ImageCacheFolder, $"side_{Side.ToLower()}.png");
|
||||
|
||||
CacheImage($"{LauncherRoute}side_{Side.ToLower()}.png", SideImagePath);
|
||||
}
|
||||
|
||||
private static void CacheImage(string route, string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(ImageCacheFolder);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(route) || CachedRoutes.Contains(route)) //Don't want to request the image if it was already cached this session.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using Stream s = new Request(null, LauncherSettingsProvider.Instance.Server.Url).Send(route, "GET", null, false);
|
||||
|
||||
using MemoryStream ms = new MemoryStream();
|
||||
|
||||
s.CopyTo(ms);
|
||||
|
||||
if (ms.Length == 0) return;
|
||||
|
||||
using FileStream fs = File.Create(filePath);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
ms.CopyTo(fs);
|
||||
|
||||
CachedRoutes.Add(route);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Instance.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
119
project/Aki.Launcher.Base/MiniCommon/Json.cs
Normal file
119
project/Aki.Launcher.Base/MiniCommon/Json.cs
Normal file
@ -0,0 +1,119 @@
|
||||
/* Json.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Aki.Launcher.MiniCommon
|
||||
{
|
||||
public static class Json
|
||||
{
|
||||
public static string Serialize<T>(T data)
|
||||
{
|
||||
return JsonConvert.SerializeObject(data);
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(string json)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
}
|
||||
|
||||
public static void Save<T>(string filepath, T data)
|
||||
{
|
||||
string json = Serialize<T>(data);
|
||||
File.WriteAllText(filepath, json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save an object as json with formatting
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="filepath">Full path to file</param>
|
||||
/// <param name="data">Object to save to json file</param>
|
||||
/// <param name="format">NewtonSoft.Json Formatting</param>
|
||||
public static void SaveWithFormatting<T>(string filepath, T data, Formatting format)
|
||||
{
|
||||
if (!File.Exists(filepath))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filepath));
|
||||
}
|
||||
|
||||
File.WriteAllText(filepath, JsonConvert.SerializeObject(data, format));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a class from file and don't save it if it doesn't exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="filepath">Full path to the file to load</param>
|
||||
/// <param name="AllowNullValues">Allow null class property values to be returned. Default is false</param>
|
||||
/// <returns>Returns a class object or null</returns>
|
||||
public static T LoadClassWithoutSaving<T>(string filepath, bool AllowNullValues = false) where T : class
|
||||
{
|
||||
if (File.Exists(filepath))
|
||||
{
|
||||
string json = File.ReadAllText(filepath);
|
||||
|
||||
T classObject = JsonConvert.DeserializeObject<T>(json);
|
||||
|
||||
if (!AllowNullValues)
|
||||
{
|
||||
if (classObject.GetType().GetProperties().Any(x => x.GetValue(classObject) == null))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return classObject;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static T Load<T>(string filepath) where T : new()
|
||||
{
|
||||
if (!File.Exists(filepath))
|
||||
{
|
||||
Save(filepath, new T());
|
||||
return Load<T>(filepath);
|
||||
}
|
||||
|
||||
string json = File.ReadAllText(filepath);
|
||||
return Deserialize<T>(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a single property back from a json file.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="FilePath">Full Path to json file</param>
|
||||
/// <param name="PropertyName">Name of property to return</param>
|
||||
/// <returns></returns>
|
||||
public static T GetPropertyByName<T>(string FilePath, string PropertyName)
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(FilePath))
|
||||
{
|
||||
var tempData = JObject.Parse(sr.ReadToEnd());
|
||||
|
||||
if (tempData != null)
|
||||
{
|
||||
if (tempData[PropertyName].Value<T>() is T requestedData)
|
||||
{
|
||||
return requestedData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
59
project/Aki.Launcher.Base/MiniCommon/ProcessMonitor.cs
Normal file
59
project/Aki.Launcher.Base/MiniCommon/ProcessMonitor.cs
Normal file
@ -0,0 +1,59 @@
|
||||
/* ProcessMonitor.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Timers;
|
||||
|
||||
namespace Aki.Launcher.MiniCommon
|
||||
{
|
||||
public class ProcessMonitor
|
||||
{
|
||||
private Timer monitor;
|
||||
private readonly string processName;
|
||||
private readonly Action<ProcessMonitor> aliveCallback;
|
||||
private readonly Action<ProcessMonitor> exitCallback;
|
||||
|
||||
public ProcessMonitor(string processName, double interval, Action<ProcessMonitor> aliveCallback, Action<ProcessMonitor> exitCallback)
|
||||
{
|
||||
monitor = new Timer(interval);
|
||||
monitor.Elapsed += OnPollEvent;
|
||||
monitor.AutoReset = true;
|
||||
|
||||
this.processName = processName;
|
||||
this.aliveCallback = aliveCallback;
|
||||
this.exitCallback = exitCallback;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
monitor.Enabled = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
monitor.Enabled = false;
|
||||
}
|
||||
|
||||
private void OnPollEvent(object source, ElapsedEventArgs e)
|
||||
{
|
||||
Process[] clientProcess = Process.GetProcessesByName(processName);
|
||||
|
||||
// client instances still running
|
||||
if (clientProcess.Length > 0)
|
||||
{
|
||||
aliveCallback(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// all client instances stopped running
|
||||
exitCallback(this);
|
||||
}
|
||||
}
|
||||
}
|
105
project/Aki.Launcher.Base/MiniCommon/Request.cs
Normal file
105
project/Aki.Launcher.Base/MiniCommon/Request.cs
Normal file
@ -0,0 +1,105 @@
|
||||
/* Request.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using ComponentAce.Compression.Libs.zlib;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace Aki.Launcher.MiniCommon
|
||||
{
|
||||
public class Request
|
||||
{
|
||||
public string Session;
|
||||
public string RemoteEndPoint;
|
||||
|
||||
public Request(string session, string remoteEndPoint)
|
||||
{
|
||||
Session = session;
|
||||
RemoteEndPoint = remoteEndPoint;
|
||||
}
|
||||
|
||||
public Stream Send(string url, string method = "GET", string data = null, bool compress = true)
|
||||
{
|
||||
// disable SSL encryption
|
||||
ServicePointManager.Expect100Continue = true;
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
|
||||
|
||||
// set session headers
|
||||
var request = WebRequest.Create(new Uri(RemoteEndPoint + url));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Session))
|
||||
{
|
||||
request.Headers.Add("Cookie", $"PHPSESSID={Session}");
|
||||
request.Headers.Add("SessionId", Session);
|
||||
}
|
||||
|
||||
request.Headers.Add("Accept-Encoding", "deflate");
|
||||
request.Method = method;
|
||||
|
||||
if (method != "GET" && !string.IsNullOrWhiteSpace(data))
|
||||
{
|
||||
// set request body
|
||||
var bytes = (compress) ? SimpleZlib.CompressToBytes(data, zlibConst.Z_BEST_COMPRESSION) : Encoding.UTF8.GetBytes(data);
|
||||
|
||||
request.ContentType = "application/json";
|
||||
request.ContentLength = bytes.Length;
|
||||
|
||||
if (compress)
|
||||
{
|
||||
request.Headers.Add("Content-Encoding", "deflate");
|
||||
}
|
||||
|
||||
using (var stream = request.GetRequestStream())
|
||||
{
|
||||
stream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
// get response stream
|
||||
try
|
||||
{
|
||||
var response = request.GetResponse();
|
||||
return response.GetResponseStream();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Not sure why this was a unityengine debug logger. Possilby used by another module?
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetJson(string url, bool compress = true)
|
||||
{
|
||||
using (var stream = Send(url, "GET", null, compress))
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(ms);
|
||||
return SimpleZlib.Decompress(ms.ToArray(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string PostJson(string url, string data, bool compress = true)
|
||||
{
|
||||
using (var stream = Send(url, "POST", data, compress))
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(ms);
|
||||
return SimpleZlib.Decompress(ms.ToArray(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
247
project/Aki.Launcher.Base/MiniCommon/VFS.cs
Normal file
247
project/Aki.Launcher.Base/MiniCommon/VFS.cs
Normal file
@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Aki.Launcher.MiniCommon
|
||||
{
|
||||
public static class VFS
|
||||
{
|
||||
public static string Cwd { get; private set; }
|
||||
private static object mutex;
|
||||
|
||||
static VFS()
|
||||
{
|
||||
Cwd = Environment.CurrentDirectory;
|
||||
mutex = new object();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine two filepaths.
|
||||
/// </summary>
|
||||
public static string Combine(string path1, string path2)
|
||||
{
|
||||
return Path.Combine(path1, path2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines the filepath with the current working directory.
|
||||
/// </summary>
|
||||
public static string FromCwd(this string filepath)
|
||||
{
|
||||
return Combine(Cwd, filepath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get directory path of a filepath.
|
||||
/// </summary>
|
||||
public static string GetDirectory(this string filepath)
|
||||
{
|
||||
string value = Path.GetDirectoryName(filepath);
|
||||
return (!string.IsNullOrWhiteSpace(value)) ? value : "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file of a filepath
|
||||
/// </summary>
|
||||
public static string GetFile(this string filepath)
|
||||
{
|
||||
string value = Path.GetFileName(filepath);
|
||||
return (!string.IsNullOrWhiteSpace(value)) ? value : "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file name of a filepath
|
||||
/// </summary>
|
||||
public static string GetFileName(this string filepath)
|
||||
{
|
||||
string value = Path.GetFileNameWithoutExtension(filepath);
|
||||
return (!string.IsNullOrWhiteSpace(value)) ? value : "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file extension of a filepath.
|
||||
/// </summary>
|
||||
public static string GetFileExtension(this string filepath)
|
||||
{
|
||||
string value = Path.GetExtension(filepath);
|
||||
return (!string.IsNullOrWhiteSpace(value)) ? value : "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move file from one place to another
|
||||
/// </summary>
|
||||
public static void MoveFile(string a, string b)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
new FileInfo(a).MoveTo(b);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the filepath exist?
|
||||
/// </summary>
|
||||
public static bool Exists(string filepath)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
return Directory.Exists(filepath) || File.Exists(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create directory (recursive).
|
||||
/// </summary>
|
||||
public static void CreateDirectory(string filepath)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
Directory.CreateDirectory(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file content as bytes.
|
||||
/// </summary>
|
||||
public static byte[] ReadFile(string filepath)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
return File.ReadAllBytes(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file content as string.
|
||||
/// </summary>
|
||||
public static string ReadFile(string filepath, Encoding encoding = null)
|
||||
{
|
||||
return (encoding ?? Encoding.UTF8).GetString(ReadFile(filepath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write data to file.
|
||||
/// </summary>
|
||||
public static void WriteFile(string filepath, byte[] data, bool append = false)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (!Exists(filepath))
|
||||
{
|
||||
CreateDirectory(filepath.GetDirectory());
|
||||
}
|
||||
|
||||
File.WriteAllBytes(filepath, data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write string to file.
|
||||
/// </summary>
|
||||
public static void WriteFile(string filepath, string data, bool append = false, Encoding encoding = null)
|
||||
{
|
||||
WriteFile(filepath, (encoding ?? Encoding.UTF8).GetBytes(data), append);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get directories in directory by full path.
|
||||
/// </summary>
|
||||
public static string[] GetDirectories(string filepath)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||
List<string> paths = new List<string>();
|
||||
|
||||
foreach (DirectoryInfo directory in di.GetDirectories())
|
||||
{
|
||||
paths.Add(directory.FullName);
|
||||
}
|
||||
|
||||
return paths.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get files in directory by full path.
|
||||
/// </summary>
|
||||
public static string[] GetFiles(string filepath)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||
List<string> paths = new List<string>();
|
||||
|
||||
foreach (FileInfo file in di.GetFiles())
|
||||
{
|
||||
paths.Add(file.FullName);
|
||||
}
|
||||
|
||||
return paths.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete directory.
|
||||
/// </summary>
|
||||
public static void DeleteDirectory(string filepath)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||
|
||||
foreach (FileInfo file in di.GetFiles())
|
||||
{
|
||||
file.IsReadOnly = false;
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo directory in di.GetDirectories())
|
||||
{
|
||||
DeleteDirectory(directory.FullName);
|
||||
}
|
||||
|
||||
di.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete file.
|
||||
/// </summary>
|
||||
public static void DeleteFile(string filepath)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
FileInfo file = new FileInfo(filepath);
|
||||
|
||||
file.IsReadOnly = false;
|
||||
file.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get files count inside directory recusively
|
||||
/// </summary>
|
||||
public static int GetFilesCount(string filepath)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||
int count = 0;
|
||||
|
||||
foreach (FileInfo file in di.GetFiles())
|
||||
{
|
||||
++count;
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo directory in di.GetDirectories())
|
||||
{
|
||||
count += GetFilesCount(directory.FullName);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
project/Aki.Launcher.Base/Models/Aki/AccountInfo.cs
Normal file
31
project/Aki.Launcher.Base/Models/Aki/AccountInfo.cs
Normal file
@ -0,0 +1,31 @@
|
||||
/* AccountInfo.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public class AccountInfo
|
||||
{
|
||||
public string id;
|
||||
public string nickname;
|
||||
public string username;
|
||||
public string password;
|
||||
public bool wipe;
|
||||
public string edition;
|
||||
|
||||
public AccountInfo()
|
||||
{
|
||||
id = "";
|
||||
nickname = "";
|
||||
username = "";
|
||||
password = "";
|
||||
wipe = false;
|
||||
edition = "";
|
||||
}
|
||||
}
|
||||
}
|
7
project/Aki.Launcher.Base/Models/Aki/AkiData.cs
Normal file
7
project/Aki.Launcher.Base/Models/Aki/AkiData.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Aki.Launcher.Models.Aki
|
||||
{
|
||||
public class AkiData
|
||||
{
|
||||
public string version { get; set; }
|
||||
}
|
||||
}
|
64
project/Aki.Launcher.Base/Models/Aki/AkiVersion.cs
Normal file
64
project/Aki.Launcher.Base/Models/Aki/AkiVersion.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Aki.Launch.Models.Aki
|
||||
{
|
||||
public class AkiVersion : INotifyPropertyChanged
|
||||
{
|
||||
public int Major;
|
||||
public int Minor;
|
||||
public int Build;
|
||||
|
||||
public bool HasTag => Tag != null;
|
||||
|
||||
private string _Tag = null;
|
||||
public string Tag
|
||||
{
|
||||
get => _Tag;
|
||||
set
|
||||
{
|
||||
if(_Tag != value)
|
||||
{
|
||||
_Tag = value;
|
||||
RaisePropertyChanged(nameof(Tag));
|
||||
RaisePropertyChanged(nameof(HasTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ParseVersionInfo(string AkiVersion)
|
||||
{
|
||||
if (AkiVersion.Contains('-'))
|
||||
{
|
||||
string[] versionInfo = AkiVersion.Split('-');
|
||||
|
||||
AkiVersion = versionInfo[0];
|
||||
|
||||
Tag = versionInfo[1];
|
||||
return;
|
||||
}
|
||||
|
||||
string[] splitVersion = AkiVersion.Split('.');
|
||||
|
||||
if (splitVersion.Length == 3)
|
||||
{
|
||||
int.TryParse(splitVersion[0], out Major);
|
||||
int.TryParse(splitVersion[1], out Minor);
|
||||
int.TryParse(splitVersion[2], out Build);
|
||||
}
|
||||
}
|
||||
|
||||
public AkiVersion() { }
|
||||
|
||||
public AkiVersion(string AkiVersion)
|
||||
{
|
||||
ParseVersionInfo(AkiVersion);
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
25
project/Aki.Launcher.Base/Models/Aki/ChangeRequestData.cs
Normal file
25
project/Aki.Launcher.Base/Models/Aki/ChangeRequestData.cs
Normal file
@ -0,0 +1,25 @@
|
||||
/* ChangeRequestData.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public struct ChangeRequestData
|
||||
{
|
||||
public string username;
|
||||
public string password;
|
||||
public string change;
|
||||
|
||||
public ChangeRequestData(string username, string password, string change)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.change = change;
|
||||
}
|
||||
}
|
||||
}
|
23
project/Aki.Launcher.Base/Models/Aki/LoginRequestData.cs
Normal file
23
project/Aki.Launcher.Base/Models/Aki/LoginRequestData.cs
Normal file
@ -0,0 +1,23 @@
|
||||
/* LoginRequestData.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public struct LoginRequestData
|
||||
{
|
||||
public string username;
|
||||
public string password;
|
||||
|
||||
public LoginRequestData(string username, string password)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
}
|
25
project/Aki.Launcher.Base/Models/Aki/RegisterRequestData.cs
Normal file
25
project/Aki.Launcher.Base/Models/Aki/RegisterRequestData.cs
Normal file
@ -0,0 +1,25 @@
|
||||
/* RegisterRequestData.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public struct RegisterRequestData
|
||||
{
|
||||
public string username;
|
||||
public string password;
|
||||
public string edition;
|
||||
|
||||
public RegisterRequestData(string username, string password, string edition)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.edition = edition;
|
||||
}
|
||||
}
|
||||
}
|
25
project/Aki.Launcher.Base/Models/Aki/ServerInfo.cs
Normal file
25
project/Aki.Launcher.Base/Models/Aki/ServerInfo.cs
Normal file
@ -0,0 +1,25 @@
|
||||
/* ServerInfo.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public class ServerInfo
|
||||
{
|
||||
public string backendUrl;
|
||||
public string name;
|
||||
public string[] editions;
|
||||
|
||||
public ServerInfo()
|
||||
{
|
||||
backendUrl = "http://127.0.0.1:6969";
|
||||
name = "Local SPT-AKI Server";
|
||||
editions = new string[0];
|
||||
}
|
||||
}
|
||||
}
|
23
project/Aki.Launcher.Base/Models/Aki/ServerProfileInfo.cs
Normal file
23
project/Aki.Launcher.Base/Models/Aki/ServerProfileInfo.cs
Normal file
@ -0,0 +1,23 @@
|
||||
/* ServerProfileInfo.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
namespace Aki.Launcher.Models.Aki
|
||||
{
|
||||
public class ServerProfileInfo
|
||||
{
|
||||
public string username { get; set; }
|
||||
public string nickname { get; set; }
|
||||
public string side { get; set; }
|
||||
public int currlvl { get; set; }
|
||||
public long currexp { get; set; }
|
||||
public long prevexp { get; set; }
|
||||
public long nextlvl { get; set; }
|
||||
public int maxlvl { get; set; }
|
||||
public AkiData akiData { get; set; }
|
||||
}
|
||||
}
|
29
project/Aki.Launcher.Base/Models/EFT/ClientConfig.cs
Normal file
29
project/Aki.Launcher.Base/Models/EFT/ClientConfig.cs
Normal file
@ -0,0 +1,29 @@
|
||||
/* ClientConfig.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public class ClientConfig
|
||||
{
|
||||
public string BackendUrl;
|
||||
public string Version;
|
||||
|
||||
public ClientConfig()
|
||||
{
|
||||
BackendUrl = "http://127.0.0.1:6969";
|
||||
Version = "live";
|
||||
}
|
||||
|
||||
public ClientConfig(string backendUrl)
|
||||
{
|
||||
BackendUrl = backendUrl;
|
||||
Version = "live";
|
||||
}
|
||||
}
|
||||
}
|
27
project/Aki.Launcher.Base/Models/EFT/LoginToken.cs
Normal file
27
project/Aki.Launcher.Base/Models/EFT/LoginToken.cs
Normal file
@ -0,0 +1,27 @@
|
||||
/* LoginToken.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public struct LoginToken
|
||||
{
|
||||
public string username;
|
||||
public string password;
|
||||
public bool toggle;
|
||||
public long timestamp;
|
||||
|
||||
public LoginToken(string username, string password)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
toggle = true;
|
||||
timestamp = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* ConnectServerModel.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class ConnectServerModel : INotifyPropertyChanged
|
||||
{
|
||||
private string _InfoText;
|
||||
public string InfoText
|
||||
{
|
||||
get => _InfoText;
|
||||
set
|
||||
{
|
||||
if (_InfoText != value)
|
||||
{
|
||||
_InfoText = value;
|
||||
RaisePropertyChanged(nameof(InfoText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _ConnectionFailed;
|
||||
public bool ConnectionFailed
|
||||
{
|
||||
get => _ConnectionFailed;
|
||||
set
|
||||
{
|
||||
if(_ConnectionFailed != value)
|
||||
{
|
||||
_ConnectionFailed = value;
|
||||
RaisePropertyChanged(nameof(ConnectionFailed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/* EditionCollection.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class EditionCollection : INotifyPropertyChanged
|
||||
{
|
||||
private bool _HasSelection;
|
||||
public bool HasSelection
|
||||
{
|
||||
get => _HasSelection;
|
||||
set
|
||||
{
|
||||
if(_HasSelection != value)
|
||||
{
|
||||
_HasSelection = value;
|
||||
RaisePropertyChanged(nameof(HasSelection));
|
||||
}
|
||||
}
|
||||
}
|
||||
private int _SelectedEditionIndex;
|
||||
public int SelectedEditionIndex
|
||||
{
|
||||
get => _SelectedEditionIndex;
|
||||
set
|
||||
{
|
||||
if (_SelectedEditionIndex != value)
|
||||
{
|
||||
_SelectedEditionIndex = value;
|
||||
RaisePropertyChanged(nameof(SelectedEditionIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _SelectedEdition;
|
||||
public string SelectedEdition
|
||||
{
|
||||
get => _SelectedEdition;
|
||||
set
|
||||
{
|
||||
if (_SelectedEdition != value)
|
||||
{
|
||||
_SelectedEdition = value;
|
||||
HasSelection = _SelectedEdition != null;
|
||||
RaisePropertyChanged(nameof(SelectedEdition));
|
||||
}
|
||||
}
|
||||
}
|
||||
public ObservableCollection<string> AvailableEditions { get; private set; } = new ObservableCollection<string>(ServerManager.SelectedServer.editions);
|
||||
|
||||
public EditionCollection()
|
||||
{
|
||||
SelectedEditionIndex = 0;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/* GameStarterResult.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
using Aki.Launcher.Helpers;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class GameStarterResult
|
||||
{
|
||||
public bool Succeeded => Message == null;
|
||||
public string Message { get; } = null;
|
||||
protected GameStarterResult(int ServerStatus)
|
||||
{
|
||||
switch (ServerStatus)
|
||||
{
|
||||
case 1:
|
||||
break;
|
||||
case -1:
|
||||
Message = LocalizationProvider.Instance.installed_in_live_game_warning;
|
||||
break;
|
||||
|
||||
case -2:
|
||||
Message = LocalizationProvider.Instance.no_official_game_warning;
|
||||
break;
|
||||
|
||||
case -3:
|
||||
Message = LocalizationProvider.Instance.failed_to_receive_patches;
|
||||
break;
|
||||
|
||||
case -4:
|
||||
Message = LocalizationProvider.Instance.failed_core_patch;
|
||||
break;
|
||||
|
||||
case -5:
|
||||
Message = LocalizationProvider.Instance.failed_mod_patch;
|
||||
break;
|
||||
|
||||
case -6:
|
||||
Message = LocalizationProvider.Instance.eft_exe_not_found_warning;
|
||||
break;
|
||||
|
||||
default:
|
||||
Message = LocalizationProvider.Instance.login_failed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static GameStarterResult FromSuccess() =>
|
||||
new GameStarterResult(1);
|
||||
public static GameStarterResult FromError(int ServerStatus) =>
|
||||
new GameStarterResult(ServerStatus);
|
||||
}
|
||||
}
|
18
project/Aki.Launcher.Base/Models/Launcher/LauncherAction.cs
Normal file
18
project/Aki.Launcher.Base/Models/Launcher/LauncherAction.cs
Normal file
@ -0,0 +1,18 @@
|
||||
/* LauncherAction.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public enum LauncherAction
|
||||
{
|
||||
MinimizeAction,
|
||||
DoNothingAction,
|
||||
ExitAction
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/* LocaleCollection.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
|
||||
using Aki.Launcher.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class LocaleCollection : INotifyPropertyChanged
|
||||
{
|
||||
private string _SelectedLocale;
|
||||
public string SelectedLocale
|
||||
{
|
||||
get => _SelectedLocale;
|
||||
set
|
||||
{
|
||||
if (_SelectedLocale != value)
|
||||
{
|
||||
_SelectedLocale = value;
|
||||
RaisePropertyChanged(nameof(SelectedLocale));
|
||||
LocalizationProvider.LoadLocaleFromFile(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<string> AvailableLocales { get; set; } = LocalizationProvider.GetAvailableLocales();
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public LocaleCollection()
|
||||
{
|
||||
SelectedLocale = LocalizationProvider.LocaleNameDictionary.GetValueOrDefault(LauncherSettingsProvider.Instance.DefaultLocale, "English");
|
||||
}
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using System.ComponentModel;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Aki.Launcher.Models
|
||||
{
|
||||
public class LocalizedLauncherAction : INotifyPropertyChanged
|
||||
{
|
||||
public LauncherAction Action { get; set; }
|
||||
|
||||
private string _Name;
|
||||
public string Name
|
||||
{
|
||||
get => _Name;
|
||||
set
|
||||
{
|
||||
if(_Name != value)
|
||||
{
|
||||
_Name = value;
|
||||
RaisePropertyChanged(nameof(Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateLocaleName()
|
||||
{
|
||||
string value = Action.ToString();
|
||||
|
||||
//this adds an underscore before capitalized letters, except if it is the first letter in the string. Then it is lower cased.
|
||||
//The result should be the name of the localization providers property you want to use.
|
||||
//Example: MinimizeAction -> minimize_action
|
||||
string localePropertyName = Regex.Replace(value, "(?<!^)[A-Z]", "_$0").ToLower();
|
||||
|
||||
var locale = LocalizationProvider.Instance.GetType().GetProperty(localePropertyName).GetValue(LocalizationProvider.Instance, null) ?? value;
|
||||
|
||||
if (locale is string localizedName)
|
||||
{
|
||||
Name = localizedName;
|
||||
}
|
||||
}
|
||||
|
||||
public LocalizedLauncherAction(LauncherAction action)
|
||||
{
|
||||
string value = action.ToString();
|
||||
|
||||
Action = action;
|
||||
Name = value;
|
||||
|
||||
UpdateLocaleName();
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
50
project/Aki.Launcher.Base/Models/Launcher/LoginModel.cs
Normal file
50
project/Aki.Launcher.Base/Models/Launcher/LoginModel.cs
Normal file
@ -0,0 +1,50 @@
|
||||
/* LoginModel.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class LoginModel : INotifyPropertyChanged
|
||||
{
|
||||
private string _Username = "";
|
||||
public string Username
|
||||
{
|
||||
get => _Username;
|
||||
set
|
||||
{
|
||||
if (_Username != value)
|
||||
{
|
||||
_Username = value;
|
||||
RaisePropertyChanged(nameof(Username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _Password = "";
|
||||
public string Password
|
||||
{
|
||||
get => _Password;
|
||||
set
|
||||
{
|
||||
if (_Password != value)
|
||||
{
|
||||
_Password = value;
|
||||
RaisePropertyChanged(nameof(Password));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
70
project/Aki.Launcher.Base/Models/Launcher/MenuBarItem.cs
Normal file
70
project/Aki.Launcher.Base/Models/Launcher/MenuBarItem.cs
Normal file
@ -0,0 +1,70 @@
|
||||
/* MenuBarItem.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class MenuBarItem : INotifyPropertyChanged
|
||||
{
|
||||
private string _Name;
|
||||
public string Name
|
||||
{
|
||||
get => _Name;
|
||||
set
|
||||
{
|
||||
if (_Name != value)
|
||||
{
|
||||
_Name = value;
|
||||
RaisePropertyChanged(nameof(Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _IsSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _IsSelected;
|
||||
set
|
||||
{
|
||||
if (_IsSelected != value)
|
||||
{
|
||||
_IsSelected = value;
|
||||
RaisePropertyChanged(nameof(IsSelected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Action _ItemAction;
|
||||
public Action ItemAction
|
||||
{
|
||||
get => _ItemAction;
|
||||
set
|
||||
{
|
||||
if (_ItemAction != value)
|
||||
{
|
||||
_ItemAction = value;
|
||||
RaisePropertyChanged(nameof(ItemAction));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Func<Task<bool>> CanUseAction = async () => await Task.FromResult(true);
|
||||
|
||||
public Action OnFailedToUseAction = null;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/* NotificationItem.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher.Notifications
|
||||
{
|
||||
public class NotificationItem : INotifyPropertyChanged
|
||||
{
|
||||
private string _Message;
|
||||
public string Message
|
||||
{
|
||||
get => _Message;
|
||||
set
|
||||
{
|
||||
if (_Message != value)
|
||||
{
|
||||
_Message = value;
|
||||
RaisePropertyChanged(nameof(Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _ButtonText;
|
||||
public string ButtonText
|
||||
{
|
||||
get => _ButtonText;
|
||||
set
|
||||
{
|
||||
if (_ButtonText != value)
|
||||
{
|
||||
_ButtonText = value;
|
||||
RaisePropertyChanged(nameof(ButtonText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _HasButton;
|
||||
public bool HasButton
|
||||
{
|
||||
get => _HasButton;
|
||||
set
|
||||
{
|
||||
if (_HasButton != value)
|
||||
{
|
||||
_HasButton = value;
|
||||
RaisePropertyChanged(nameof(HasButton));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action ItemAction = null;
|
||||
|
||||
public NotificationItem(string Message)
|
||||
{
|
||||
this.Message = Message;
|
||||
ButtonText = string.Empty;
|
||||
HasButton = false;
|
||||
}
|
||||
|
||||
public NotificationItem(string Message, string ButtonText, Action ItemAction)
|
||||
{
|
||||
this.Message = Message;
|
||||
this.ButtonText = ButtonText;
|
||||
HasButton = true;
|
||||
this.ItemAction = ItemAction;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
/* NotificationQueue.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
|
||||
using Aki.Launcher.Helpers;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Timers;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher.Notifications
|
||||
{
|
||||
public class NotificationQueue : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
public Timer queueTimer = new Timer();
|
||||
private Timer animateChangeTimer = new Timer(230);
|
||||
private Timer animateCloseTimer = new Timer(230);
|
||||
|
||||
public ObservableCollection<NotificationItem> queue { get; set; } = new ObservableCollection<NotificationItem>();
|
||||
|
||||
private bool _ShowBanner;
|
||||
public bool ShowBanner
|
||||
{
|
||||
get => _ShowBanner;
|
||||
set
|
||||
{
|
||||
if (_ShowBanner != value)
|
||||
{
|
||||
_ShowBanner = value;
|
||||
RaisePropertyChanged(nameof(ShowBanner));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NotificationQueue(int ShowTimeInMiliseconds)
|
||||
{
|
||||
ShowBanner = false;
|
||||
queueTimer.Interval = ShowTimeInMiliseconds;
|
||||
queueTimer.Elapsed += QueueTimer_Elapsed;
|
||||
|
||||
animateChangeTimer.Elapsed += AnimateChange_Elapsed;
|
||||
animateCloseTimer.Elapsed += AnimateCloseTimer_Elapsed;
|
||||
}
|
||||
|
||||
private void AnimateCloseTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
animateCloseTimer.Stop();
|
||||
|
||||
queue.Clear();
|
||||
queueTimer.Stop();
|
||||
}
|
||||
|
||||
public void CloseQueue()
|
||||
{
|
||||
ShowBanner = false;
|
||||
animateCloseTimer.Start();
|
||||
}
|
||||
|
||||
private void CheckAndShowNotifications()
|
||||
{
|
||||
if (!queueTimer.Enabled)
|
||||
{
|
||||
ShowBanner = true;
|
||||
queueTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(string Message, bool AutowNext = false, bool NoDefaultButton = false)
|
||||
{
|
||||
if (queue.Where(x => x.Message == Message).Count() == 0)
|
||||
{
|
||||
if (NoDefaultButton)
|
||||
{
|
||||
queue.Add(new NotificationItem(Message));
|
||||
}
|
||||
else
|
||||
{
|
||||
queue.Add(new NotificationItem(Message, LocalizationProvider.Instance.ok, () => { }));
|
||||
}
|
||||
|
||||
CheckAndShowNotifications();
|
||||
|
||||
if (AutowNext && queue.Count == 2)
|
||||
{
|
||||
Next(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(string Message, string ButtonText, Action ButtonAction, bool AllowNext = false)
|
||||
{
|
||||
if (queue.Where(x => x.Message == Message && x.ButtonText == ButtonText).Count() == 0)
|
||||
{
|
||||
queue.Add(new NotificationItem(Message, ButtonText, ButtonAction));
|
||||
CheckAndShowNotifications();
|
||||
|
||||
if (AllowNext && queue.Count == 2)
|
||||
{
|
||||
Next(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Next(bool ResetTimer = false)
|
||||
{
|
||||
if (queue.Count - 1 <= 0)
|
||||
{
|
||||
CloseQueue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ResetTimer)
|
||||
{
|
||||
queueTimer.Stop();
|
||||
queueTimer.Start();
|
||||
}
|
||||
|
||||
ShowBanner = false;
|
||||
animateChangeTimer.Start();
|
||||
}
|
||||
|
||||
private void QueueTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
Next();
|
||||
}
|
||||
|
||||
private void AnimateChange_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
animateChangeTimer.Stop();
|
||||
|
||||
if (queue.Count > 0)
|
||||
{
|
||||
queue.RemoveAt(0);
|
||||
}
|
||||
|
||||
ShowBanner = true;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
queueTimer.Dispose();
|
||||
animateChangeTimer.Dispose();
|
||||
animateCloseTimer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
32
project/Aki.Launcher.Base/Models/Launcher/PatchResultInfo.cs
Normal file
32
project/Aki.Launcher.Base/Models/Launcher/PatchResultInfo.cs
Normal file
@ -0,0 +1,32 @@
|
||||
/* PatchResultInfo.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
using Aki.ByteBanger;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class PatchResultInfo
|
||||
{
|
||||
public PatchResultType Status { get; }
|
||||
|
||||
public int NumCompleted { get; }
|
||||
|
||||
public int NumTotal { get; }
|
||||
|
||||
public bool OK => (Status == PatchResultType.Success) || (Status == PatchResultType.AlreadyPatched);
|
||||
|
||||
public int PercentComplete => (NumCompleted * 100) / NumTotal;
|
||||
|
||||
public PatchResultInfo(PatchResultType Status, int NumCompleted, int NumTotal)
|
||||
{
|
||||
this.Status = Status;
|
||||
this.NumCompleted = NumCompleted;
|
||||
this.NumTotal = NumTotal;
|
||||
}
|
||||
}
|
||||
}
|
281
project/Aki.Launcher.Base/Models/Launcher/ProfileInfo.cs
Normal file
281
project/Aki.Launcher.Base/Models/Launcher/ProfileInfo.cs
Normal file
@ -0,0 +1,281 @@
|
||||
/* ProfileInfo.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
using Aki.Launch.Models.Aki;
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using Aki.Launcher.Models.Aki;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class ProfileInfo : INotifyPropertyChanged
|
||||
{
|
||||
private string _Username;
|
||||
public string Username
|
||||
{
|
||||
get => _Username;
|
||||
set
|
||||
{
|
||||
if(_Username != value)
|
||||
{
|
||||
_Username = value;
|
||||
RaisePropertyChanged(nameof(Username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _Nickname;
|
||||
public string Nickname
|
||||
{
|
||||
get => _Nickname;
|
||||
set
|
||||
{
|
||||
if (_Nickname != value)
|
||||
{
|
||||
_Nickname = value;
|
||||
RaisePropertyChanged(nameof(Nickname));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _SideImage;
|
||||
public string SideImage
|
||||
{
|
||||
get => _SideImage;
|
||||
set
|
||||
{
|
||||
if (_SideImage != value)
|
||||
{
|
||||
_SideImage = value;
|
||||
RaisePropertyChanged(nameof(SideImage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _Side;
|
||||
public string Side
|
||||
{
|
||||
get => _Side;
|
||||
set
|
||||
{
|
||||
if (_Side != value)
|
||||
{
|
||||
_Side = value;
|
||||
RaisePropertyChanged(nameof(Side));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _Level;
|
||||
public string Level
|
||||
{
|
||||
get => _Level;
|
||||
set
|
||||
{
|
||||
if (_Level != value)
|
||||
{
|
||||
_Level = value;
|
||||
RaisePropertyChanged(nameof(Level));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _XPLevelProgress;
|
||||
public int XPLevelProgress
|
||||
{
|
||||
get => _XPLevelProgress;
|
||||
set
|
||||
{
|
||||
if (_XPLevelProgress != value)
|
||||
{
|
||||
_XPLevelProgress = value;
|
||||
RaisePropertyChanged(nameof(XPLevelProgress));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long _CurrentXP;
|
||||
public long CurrentExp
|
||||
{
|
||||
get => _CurrentXP;
|
||||
set
|
||||
{
|
||||
if (_CurrentXP != value)
|
||||
{
|
||||
_CurrentXP = value;
|
||||
RaisePropertyChanged(nameof(CurrentExp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long _RemainingExp;
|
||||
public long RemainingExp
|
||||
{
|
||||
get => _RemainingExp;
|
||||
set
|
||||
{
|
||||
if (_RemainingExp != value)
|
||||
{
|
||||
_RemainingExp = value;
|
||||
RaisePropertyChanged(nameof(RemainingExp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long _NextLvlExp;
|
||||
public long NextLvlExp
|
||||
{
|
||||
get => _NextLvlExp;
|
||||
set
|
||||
{
|
||||
if (_NextLvlExp != value)
|
||||
{
|
||||
_NextLvlExp = value;
|
||||
RaisePropertyChanged(nameof(NextLvlExp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _HasData;
|
||||
public bool HasData
|
||||
{
|
||||
get => _HasData;
|
||||
set
|
||||
{
|
||||
if (_HasData != value)
|
||||
{
|
||||
_HasData = value;
|
||||
RaisePropertyChanged(nameof(HasData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string MismatchMessage => VersionMismatch ? LocalizationProvider.Instance.profile_version_mismath : null;
|
||||
|
||||
private bool _VersionMismatch;
|
||||
public bool VersionMismatch
|
||||
{
|
||||
get => _VersionMismatch;
|
||||
set
|
||||
{
|
||||
if(_VersionMismatch != value)
|
||||
{
|
||||
_VersionMismatch = value;
|
||||
RaisePropertyChanged(nameof(VersionMismatch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AkiData _Aki;
|
||||
public AkiData Aki
|
||||
{
|
||||
get => _Aki;
|
||||
set
|
||||
{
|
||||
if(_Aki != value)
|
||||
{
|
||||
_Aki = value;
|
||||
RaisePropertyChanged(nameof(Aki));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDisplayedProfile(ProfileInfo PInfo)
|
||||
{
|
||||
if (PInfo.Side == null || string.IsNullOrWhiteSpace(PInfo.Side) || PInfo.Side == "unknown") return;
|
||||
|
||||
HasData = true;
|
||||
Nickname = PInfo.Nickname;
|
||||
Side = PInfo.Side;
|
||||
SideImage = PInfo.SideImage;
|
||||
Level = PInfo.Level;
|
||||
CurrentExp = PInfo.CurrentExp;
|
||||
NextLvlExp = PInfo.NextLvlExp;
|
||||
RemainingExp = PInfo.RemainingExp;
|
||||
XPLevelProgress = PInfo.XPLevelProgress;
|
||||
|
||||
Aki = PInfo.Aki;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the aki versions are compatible (non-major changes)
|
||||
/// </summary>
|
||||
/// <param name="CurrentVersion"></param>
|
||||
/// <param name="ExpectedVersion"></param>
|
||||
/// <returns></returns>
|
||||
private bool CompareVersions(string CurrentVersion, string ExpectedVersion)
|
||||
{
|
||||
if (ExpectedVersion == "") return false;
|
||||
|
||||
AkiVersion v1 = new AkiVersion(CurrentVersion);
|
||||
AkiVersion v2 = new AkiVersion(ExpectedVersion);
|
||||
|
||||
// check 'X'.x.x
|
||||
if (v1.Major != v2.Major) return false;
|
||||
|
||||
// check x.'X'.x
|
||||
if(v1.Minor != v2.Minor) return false;
|
||||
|
||||
//otherwise probably good
|
||||
return true;
|
||||
}
|
||||
|
||||
public ProfileInfo(ServerProfileInfo serverProfileInfo)
|
||||
{
|
||||
Username = serverProfileInfo.username;
|
||||
Nickname = serverProfileInfo.nickname;
|
||||
Side = serverProfileInfo.side;
|
||||
|
||||
Aki = serverProfileInfo.akiData;
|
||||
|
||||
if (Aki != null)
|
||||
{
|
||||
VersionMismatch = !CompareVersions(Aki.version, ServerManager.GetVersion());
|
||||
}
|
||||
|
||||
SideImage = Path.Combine(ImageRequest.ImageCacheFolder, $"side_{Side.ToLower()}.png");
|
||||
|
||||
if (Side != null && !string.IsNullOrWhiteSpace(Side) && Side != "unknown")
|
||||
{
|
||||
HasData = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
HasData = false;
|
||||
}
|
||||
|
||||
Level = serverProfileInfo.currlvl.ToString();
|
||||
CurrentExp = serverProfileInfo.currexp;
|
||||
|
||||
//check if player is max level
|
||||
if (Level == serverProfileInfo.maxlvl.ToString())
|
||||
{
|
||||
NextLvlExp = 0;
|
||||
XPLevelProgress = 100;
|
||||
return;
|
||||
}
|
||||
|
||||
NextLvlExp = serverProfileInfo.nextlvl;
|
||||
RemainingExp = NextLvlExp - CurrentExp;
|
||||
|
||||
long currentLvlTotal = NextLvlExp - serverProfileInfo.prevexp;
|
||||
|
||||
XPLevelProgress = (int)Math.Floor((((double)currentLvlTotal) - RemainingExp) / currentLvlTotal * 100);
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
22
project/Aki.Launcher.Base/Models/Launcher/ProgressInfo.cs
Normal file
22
project/Aki.Launcher.Base/Models/Launcher/ProgressInfo.cs
Normal file
@ -0,0 +1,22 @@
|
||||
/* ProgressInfo.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class ProgressInfo
|
||||
{
|
||||
public int Percentage { get; private set; }
|
||||
public string Message { get; private set; }
|
||||
|
||||
public ProgressInfo(int Percentage, string Message)
|
||||
{
|
||||
this.Percentage = Percentage;
|
||||
this.Message = Message;
|
||||
}
|
||||
}
|
||||
}
|
53
project/Aki.Launcher.Base/Models/Launcher/RegisterModel.cs
Normal file
53
project/Aki.Launcher.Base/Models/Launcher/RegisterModel.cs
Normal file
@ -0,0 +1,53 @@
|
||||
/* RegisterModel.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class RegisterModel : INotifyPropertyChanged
|
||||
{
|
||||
private string _Username;
|
||||
public string Username
|
||||
{
|
||||
get => _Username;
|
||||
set
|
||||
{
|
||||
if (_Username != value)
|
||||
{
|
||||
_Username = value;
|
||||
RaisePropertyChanged(nameof(Username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _Password;
|
||||
public string Password
|
||||
{
|
||||
get => _Password;
|
||||
set
|
||||
{
|
||||
if (_Password != value)
|
||||
{
|
||||
_Password = value;
|
||||
RaisePropertyChanged(nameof(Password));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EditionCollection EditionsCollection { get; set; } = new EditionCollection();
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
54
project/Aki.Launcher.Base/Models/Launcher/ServerSetting.cs
Normal file
54
project/Aki.Launcher.Base/Models/Launcher/ServerSetting.cs
Normal file
@ -0,0 +1,54 @@
|
||||
/* ServerSetting.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class ServerSetting : INotifyPropertyChanged
|
||||
{
|
||||
public LoginModel AutoLoginCreds { get; set; } = null;
|
||||
|
||||
private string _Name;
|
||||
public string Name
|
||||
{
|
||||
get => _Name;
|
||||
set
|
||||
{
|
||||
if (_Name != value)
|
||||
{
|
||||
_Name = value;
|
||||
RaisePropertyChanged(nameof(Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _Url;
|
||||
public string Url
|
||||
{
|
||||
get => _Url;
|
||||
set
|
||||
{
|
||||
if (_Url != value)
|
||||
{
|
||||
_Url = value;
|
||||
RaisePropertyChanged(nameof(Url));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged(string property)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/* WipeProfileModel.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* Merijn Hendriks
|
||||
*/
|
||||
|
||||
namespace Aki.Launcher.Models.Launcher
|
||||
{
|
||||
public class WipeProfileModel
|
||||
{
|
||||
public EditionCollection EditionsCollection { get; set; } = new EditionCollection();
|
||||
}
|
||||
}
|
454
project/Aki.Launcher/.gitignore
vendored
Normal file
454
project/Aki.Launcher/.gitignore
vendored
Normal file
@ -0,0 +1,454 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
##
|
||||
## Visual Studio Code
|
||||
##
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
49
project/Aki.Launcher/Aki.Launcher.csproj
Normal file
49
project/Aki.Launcher/Aki.Launcher.csproj
Normal file
@ -0,0 +1,49 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
<AvaloniaXaml Remove="Properties\**" />
|
||||
<Compile Remove="Properties\**" />
|
||||
<EmbeddedResource Remove="Properties\**" />
|
||||
<None Remove="Properties\**" />
|
||||
<AvaloniaResource Remove="Assets\Styles.axaml" />
|
||||
<None Remove=".gitignore" />
|
||||
<None Remove="Assets\aki-logo.png" />
|
||||
<None Remove="Assets\icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.15" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.12" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
|
||||
<PackageReference Include="DialogHost.Avalonia" Version="0.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.ByteBanger\Aki.ByteBanger.csproj" />
|
||||
<ProjectReference Include="..\Aki.Launcher.Base\Aki.Launcher.Base.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>References\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="zlib.net">
|
||||
<HintPath>References\zlib.net.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaXaml Update="Assets\Styles.axaml">
|
||||
<SubType>Designer</SubType>
|
||||
</AvaloniaXaml>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,83 @@
|
||||
{
|
||||
"native_name": "简体中文",
|
||||
"retry": "重试",
|
||||
"server_connecting": "连接中",
|
||||
"server_unavailable_format_1": "默认服务器'{0}'不可用。",
|
||||
"no_servers_available": "找不到服务器。请检查设置中的服务器列表。",
|
||||
"settings_menu": "设置",
|
||||
"back": "返回",
|
||||
"wipe_profile": "清除该存档",
|
||||
"username": "电子邮箱",
|
||||
"password": "密码",
|
||||
"update": "更新",
|
||||
"edit_account_update_error": "更新存档时出现了一些问题。",
|
||||
"register": "注册",
|
||||
"go_to_register": "去注册",
|
||||
"login_or_register": "登录 / 注册",
|
||||
"go_to_login": "去登录",
|
||||
"login_automatically": "自动登录",
|
||||
"incorrect_login": "邮箱或密码不正确",
|
||||
"login_failed": "登录失败",
|
||||
"edition": "游戏版本",
|
||||
"id": "ID",
|
||||
"logout": "登出",
|
||||
"account": "Account",
|
||||
"edit_account": "编辑个人存档",
|
||||
"start_game": "开始游戏",
|
||||
"installed_in_live_game_warning": "Aki不应该安装在在线版塔科夫的目录里,请复制游戏文件并在其他地方安装。",
|
||||
"no_official_game_warning": "您的电脑上未安装《逃离塔科夫》。请购买游戏,支持游戏开发商!",
|
||||
"eft_exe_not_found_warning": "在当前路径中找不到EscapeFromTarkov.exe。",
|
||||
"account_exist": "该账户已存在",
|
||||
"url": "URL",
|
||||
"default_language": "默认语言",
|
||||
"game_path": "游戏路径",
|
||||
"clear_game_settings": "恢复默认游戏设置",
|
||||
"clear_game_settings_warning": "您将要删除旧的游戏设置文件。它们将备份到:\n{0}\n\n您确定吗?",
|
||||
"clear_game_settings_succeeded": "游戏设置已恢复默认",
|
||||
"clear_game_settings_failed": "游戏设置恢复失败",
|
||||
"remove_registry_keys": "移除注册表",
|
||||
"remove_registry_keys_succeeded": "注册表已移除",
|
||||
"remove_registry_keys_failed": "注册表移除失败",
|
||||
"clean_temp_files": "清理临时文件",
|
||||
"clean_temp_files_succeeded": "临时文件已清理",
|
||||
"clean_temp_files_failed": "临时文件清理失败",
|
||||
"select_folder": "选择文件夹",
|
||||
"registration_failed": "注册失败",
|
||||
"minimize_action": "最小化",
|
||||
"do_nothing_action": "无动作",
|
||||
"exit_action": "关闭启动器",
|
||||
"on_game_start": "登录器在启动游戏后",
|
||||
"game": "游戏",
|
||||
"new_password": "新密码",
|
||||
"cancel": "取消",
|
||||
"need_an_account": "还没有账号吗?",
|
||||
"have_an_account": "已经有账号了?",
|
||||
"reapply_patch": "重新打包",
|
||||
"failed_to_receive_patches": "接收补丁失败",
|
||||
"failed_core_patch": "核心补丁安装失败",
|
||||
"failed_mod_patch": "Mod补丁安装失败",
|
||||
"ok": "好",
|
||||
"account_page_denied": "帐户页被拒绝。您未登录或游戏正在运行。",
|
||||
"account_updated": "您的帐户已更新",
|
||||
"nickname": "昵称",
|
||||
"side": "阵营",
|
||||
"level": "等级",
|
||||
"patching": "Patching",
|
||||
"file_mismatch_dialog_message": "输入文件哈希与预期哈希不匹配。您的客户端文件可能使用了错误的AKI版本。\n\n是否继续?",
|
||||
"yes": "确定",
|
||||
"no": "取消",
|
||||
"open_folder": "Open Folder",
|
||||
"select_edition": "Select Edition",
|
||||
"profile_created": "Profile Created",
|
||||
"registration_question_format_1": "Profile '{0}' does not exist.\n\nWould you like to create it?",
|
||||
"next_level_in": "Next level in",
|
||||
"wipe_warning": "Changing your account edition requires a profile wipe. This will reset your game progress.",
|
||||
"copied": "Copied",
|
||||
"no_profile_data": "No profile data",
|
||||
"profile_version_mismath": "Your profile was made using a different version of aki and may have issues",
|
||||
"profile_removed": "Profile removed",
|
||||
"profile_removal_failed": "Failed to remove profile",
|
||||
"profile_remove_question_format_1": "Permanently remove profile '{0}'?",
|
||||
"i_understand": "I Understand",
|
||||
"game_version_mismatch_format_2": "SPT is unable to run, this is because SPT expected to find EFT version '{1}',\nbut instead found version '{0}'\n\nEnsure you've downgraded your EFT as described in the install guide\non the page you downloaded SPT from"
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
{
|
||||
"native_name": "繁體中文",
|
||||
"retry": "重試",
|
||||
"server_connecting": "正在連接伺服器",
|
||||
"server_unavailable_format_1": "伺服器'{0}'無法使用。",
|
||||
"no_servers_available": "無法找出伺服器,請於設置瀏覽伺服器列表",
|
||||
"settings_menu": "設定",
|
||||
"back": "返回",
|
||||
"wipe_profile": "刪除存檔",
|
||||
"username": "用戶名稱",
|
||||
"password": "密碼",
|
||||
"update": "更新",
|
||||
"edit_account_update_error": "更新存檔過程中發生錯誤",
|
||||
"register": "登錄",
|
||||
"go_to_register": "前往登錄",
|
||||
"login_or_register": "登入 / 登錄",
|
||||
"go_to_login": "前往登入",
|
||||
"login_automatically": "自動登入",
|
||||
"incorrect_login": "用戶名稱或密碼不正確",
|
||||
"login_failed": "登入失敗",
|
||||
"edition": "版本",
|
||||
"id": "認證序碼",
|
||||
"logout": "登出",
|
||||
"account": "Account",
|
||||
"edit_account": "編輯個人存檔",
|
||||
"start_game": "開始遊戲",
|
||||
"installed_in_live_game_warning": "Aki 並不建議於在線版塔哥夫檔案位置上安裝,請複制遊戲客戶端檔案並於其他位置安裝",
|
||||
"no_official_game_warning": "您的電腦並未安裝正版逃離塔哥夫,請購買遊戲並支持遊戲開發者!",
|
||||
"eft_exe_not_found_warning": "在當前路徑並未能找到 EscapeFromTarkov.exe, 請檢查檔案路徑是否正確",
|
||||
"account_exist": "用戶名稱已存在",
|
||||
"url": "URL",
|
||||
"default_language": "預設語言",
|
||||
"game_path": "遊戲路徑",
|
||||
"clear_game_settings": "重置遊戲設定",
|
||||
"clear_game_settings_warning": "You are about to remove your old game settings files. They will be backed up to:\n{0}\n\nAre you sure?",
|
||||
"clear_game_settings_succeeded": "遊戲設定已重置",
|
||||
"clear_game_settings_failed": "遊戲設定重置過程中發生錯誤",
|
||||
"remove_registry_keys": "移除登錄檔機碼",
|
||||
"remove_registry_keys_succeeded": "登錄檔機碼已移除",
|
||||
"remove_registry_keys_failed": "移除登錄檔機碼過程中發生錯誤",
|
||||
"clean_temp_files": "清理快取",
|
||||
"clean_temp_files_succeeded": "已清理快取檔案",
|
||||
"clean_temp_files_failed": "清理快取檔案過程中發生錯誤",
|
||||
"select_folder": "請選擇文件夾",
|
||||
"registration_failed": "登錄失敗",
|
||||
"minimize_action": "視窗最小化",
|
||||
"do_nothing_action": "不作任何變更",
|
||||
"exit_action": "關閉啟動器",
|
||||
"on_game_start": "於遊戲啟動後",
|
||||
"game": "遊戲",
|
||||
"new_password": "新密碼",
|
||||
"cancel": "取消",
|
||||
"need_an_account": "沒有帳號?",
|
||||
"have_an_account": "已有帳號?",
|
||||
"reapply_patch": "重新應用補缺包",
|
||||
"failed_to_receive_patches": "接收補缺包失敗",
|
||||
"failed_core_patch": "核心補缺失敗",
|
||||
"failed_mod_patch": "改裝程式補缺失敗",
|
||||
"ok": "確定",
|
||||
"account_page_denied": "Account page denied. Either you are not logged in or the game is running.",
|
||||
"account_updated": "Your account has been updated",
|
||||
"nickname": "Nickname",
|
||||
"side": "Side",
|
||||
"level": "Level",
|
||||
"patching": "Patching",
|
||||
"file_mismatch_dialog_message": "The input file hash doesn't match the expected hash. You may be using the wrong version\nof AKI for your client files.\n\nDo you want to continue?",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"open_folder": "Open Folder",
|
||||
"select_edition": "Select Edition",
|
||||
"profile_created": "Profile Created",
|
||||
"registration_question_format_1": "Profile '{0}' does not exist.\n\nWould you like to create it?",
|
||||
"next_level_in": "Next level in",
|
||||
"wipe_warning": "Changing your account edition requires a profile wipe. This will reset your game progress.",
|
||||
"copied": "Copied",
|
||||
"no_profile_data": "No profile data",
|
||||
"profile_version_mismath": "Your profile was made using a different version of aki and may have issues",
|
||||
"profile_removed": "Profile removed",
|
||||
"profile_removal_failed": "Failed to remove profile",
|
||||
"profile_remove_question_format_1": "Permanently remove profile '{0}'?",
|
||||
"i_understand": "I Understand",
|
||||
"game_version_mismatch_format_2": "SPT is unable to run, this is because SPT expected to find EFT version '{1}',\nbut instead found version '{0}'\n\nEnsure you've downgraded your EFT as described in the install guide\non the page you downloaded SPT from"
|
||||
}
|
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/English.json
Normal file
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/English.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"native_name": "English",
|
||||
"retry": "Retry",
|
||||
"server_connecting": "Connecting",
|
||||
"server_unavailable_format_1": "Default server '{0}' is not available.",
|
||||
"no_servers_available": "No Servers found. Check server list in settings.",
|
||||
"settings_menu": "Settings",
|
||||
"back": "Back",
|
||||
"wipe_profile": "Wipe Profile",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"update": "Update",
|
||||
"edit_account_update_error": "An issue occurred while updating your profile.",
|
||||
"register": "Register",
|
||||
"go_to_register": "Go to Register",
|
||||
"login_or_register": "Login / Register",
|
||||
"go_to_login": "Go to Login",
|
||||
"login_automatically": "Login Automatically",
|
||||
"incorrect_login": "Username or password is incorrect.",
|
||||
"login_failed": "Login Failed",
|
||||
"edition": "Edition",
|
||||
"id": "ID",
|
||||
"logout": "Logout",
|
||||
"account": "Profile",
|
||||
"edit_account": "Edit Profile",
|
||||
"start_game": "Start Game",
|
||||
"installed_in_live_game_warning": "Aki shouldn't be installed into the live game directory. Please install Aki into a copy of the game directory elsewhere on your computer.",
|
||||
"no_official_game_warning": "Escape From Tarkov isn't installed on your computer. Please buy a copy of the game and support the developers!",
|
||||
"eft_exe_not_found_warning": "EscapeFromTarkov.exe not found at game path. Please check that the directory is correct.",
|
||||
"account_exist": "Profile already exists",
|
||||
"url": "URL",
|
||||
"default_language": "Default Language",
|
||||
"game_path": "SPT Game Path",
|
||||
"clear_game_settings": "Clear Game Settings",
|
||||
"clear_game_settings_warning": "You are about to remove your old game settings files. They will be backed up to:\n{0}\n\nAre you sure?",
|
||||
"clear_game_settings_succeeded": "Game settings cleared.",
|
||||
"clear_game_settings_failed": "An issue occurred while clearing game settings.",
|
||||
"remove_registry_keys": "Remove Registry Keys",
|
||||
"remove_registry_keys_succeeded": "Registry keys removed.",
|
||||
"remove_registry_keys_failed": "An issue occurred while removing registry keys.",
|
||||
"clean_temp_files": "Clean Temp Files",
|
||||
"clean_temp_files_succeeded": "Temp files cleaned.",
|
||||
"clean_temp_files_failed": "An issue occurred while cleaning temp files.",
|
||||
"select_folder": "Select Folder",
|
||||
"registration_failed": "Registration Failed.",
|
||||
"minimize_action": "Minimize",
|
||||
"do_nothing_action": "Do nothing",
|
||||
"exit_action": "Close Launcher",
|
||||
"on_game_start": "On Game Start",
|
||||
"game": "Game",
|
||||
"new_password": "New Password",
|
||||
"cancel": "Cancel",
|
||||
"need_an_account": "Don't have a profile yet?",
|
||||
"have_an_account": "Already have a profile?",
|
||||
"reapply_patch": "Reapply Patch",
|
||||
"failed_to_receive_patches": "Failed to receive patches",
|
||||
"failed_core_patch": "Core patch failed",
|
||||
"failed_mod_patch": "Mod patch failed",
|
||||
"ok": "OK",
|
||||
"account_page_denied": "Profile page denied. Either you are not logged in or the game is running.",
|
||||
"account_updated": "Your profile has been updated",
|
||||
"nickname": "Nickname",
|
||||
"side": "Side",
|
||||
"level": "Level",
|
||||
"patching": "Patching",
|
||||
"file_mismatch_dialog_message": "The input file hash doesn't match the expected hash. You may be using the wrong version\nof AKI for your client files.\n\nDo you want to continue?",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"open_folder": "Open Folder",
|
||||
"select_edition": "Select Edition",
|
||||
"profile_created": "Profile Created",
|
||||
"registration_question_format_1": "Profile '{0}' does not exist.\n\nWould you like to create it?",
|
||||
"next_level_in": "Next level in",
|
||||
"wipe_warning": "Changing your account edition requires a profile wipe. This will reset your game progress.",
|
||||
"copied": "Copied",
|
||||
"no_profile_data": "No profile data",
|
||||
"profile_version_mismath": "Your profile was made using a different version of aki and may have issues",
|
||||
"profile_removed": "Profile removed",
|
||||
"profile_removal_failed": "Failed to remove profile",
|
||||
"profile_remove_question_format_1": "Permanently remove profile '{0}'?",
|
||||
"i_understand": "I Understand",
|
||||
"game_version_mismatch_format_2": "SPT is unable to run, this is because SPT expected to find EFT version '{1}',\nbut instead found version '{0}'\n\nEnsure you've downgraded your EFT as described in the install guide\non the page you downloaded SPT from"
|
||||
}
|
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/French.json
Normal file
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/French.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"native_name": "Français",
|
||||
"retry": "Réessayer",
|
||||
"server_connecting": "Connexion",
|
||||
"server_unavailable_format_1": "Le serveur '{0}' n'est pas disponible.",
|
||||
"no_servers_available": "Aucun serveur détecté. Veuillez vérifier la liste des serveurs dans les paramètres.",
|
||||
"settings_menu": "Paramètres",
|
||||
"back": "Retour",
|
||||
"wipe_profile": "Réinitialiser le profil",
|
||||
"username": "Nom d'utilisateur",
|
||||
"password": "Mot de passe",
|
||||
"update": "Mettre à jour",
|
||||
"edit_account_update_error": "Une erreur est survenue pendant la mise à jour de votre compte.",
|
||||
"register": "Inscription",
|
||||
"go_to_register": "S'inscrire",
|
||||
"login_or_register": "Connexion / Inscription",
|
||||
"go_to_login": "Se connecter",
|
||||
"login_automatically": "Se connecter automatiquement",
|
||||
"incorrect_login": "Nom d'utilisateur ou mot de passe invalide.",
|
||||
"login_failed": "Échec de la connexion",
|
||||
"edition": "Édition",
|
||||
"id": "ID",
|
||||
"logout": "Se déconnecter",
|
||||
"account": "Account",
|
||||
"edit_account": "Modifier le compte",
|
||||
"start_game": "Lancer le jeu",
|
||||
"installed_in_live_game_warning": "Le Launcher Aki ne devrait pas être installé dans le répertoire du jeu d'origine. Veuillez créer une copie des fichiers du jeu et installer le Launcher Aki dedans.",
|
||||
"no_official_game_warning": "Escape From Tarkov n'est pas installé sur votre ordinateur. S’il vous plaît, achetez une copie officielle et soutenez les développeurs !",
|
||||
"eft_exe_not_found_warning": "EscapeFromTarkov.exe est introuvable dans le répertoire du jeu.",
|
||||
"account_exist": "Ce compte existe déjà.",
|
||||
"url": "URL",
|
||||
"default_language": "Langue par défaut",
|
||||
"game_path": "Répertoire du jeu",
|
||||
"clear_game_settings": "Réinitialiser les paramètres du jeu",
|
||||
"clear_game_settings_warning": "You are about to remove your old game settings files. They will be backed up to:\n{0}\n\nAre you sure?",
|
||||
"clear_game_settings_succeeded": "Paramètres du jeu réinitialisés.",
|
||||
"clear_game_settings_failed": "Un problème est survenu pendant la réinitialisation des paramètres de jeu.",
|
||||
"remove_registry_keys": "Réinitialiser les clés du registre",
|
||||
"remove_registry_keys_succeeded": "Clés de registre réinitialisés.",
|
||||
"remove_registry_keys_failed": "Un problème est survenu pendant la réinitialisation des clés de registre.",
|
||||
"clean_temp_files": "Nettoyer les fichiers temporaires",
|
||||
"clean_temp_files_succeeded": "Fichiers temporaires nettoyés.",
|
||||
"clean_temp_files_failed": "Un problème est survenu pendant le nettoyage des fichiers temporaires.",
|
||||
"select_folder": "Sélectionner un dossier",
|
||||
"registration_failed": "L'inscription a échoué",
|
||||
"minimize_action": "Réduire le Launcher",
|
||||
"do_nothing_action": "Ne rien faire",
|
||||
"exit_action": "Fermer le Launcher",
|
||||
"on_game_start": "Au démarrage du jeu",
|
||||
"game": "Jeu",
|
||||
"new_password": "Nouveau mot de passe",
|
||||
"cancel": "Annuler",
|
||||
"need_an_account": "Pas encore de compte ?",
|
||||
"have_an_account": "Déjà un compte ?",
|
||||
"reapply_patch": "Repatcher",
|
||||
"failed_to_receive_patches": "Erreur lors du téléchargement des patchs",
|
||||
"failed_core_patch": "Erreur lors de la mise à jour de l’application",
|
||||
"failed_mod_patch": "Erreur lors de la mise à jour des mods",
|
||||
"ok": "OK",
|
||||
"account_page_denied": "Account page denied. Either you are not logged in or the game is running.",
|
||||
"account_updated": "Your account has been updated",
|
||||
"nickname": "Nickname",
|
||||
"side": "Side",
|
||||
"level": "Level",
|
||||
"patching": "Patching",
|
||||
"file_mismatch_dialog_message": "The input file hash doesn't match the expected hash. You may be using the wrong version\nof AKI for your client files.\n\nDo you want to continue?",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"open_folder": "Open Folder",
|
||||
"select_edition": "Select Edition",
|
||||
"profile_created": "Profile Created",
|
||||
"registration_question_format_1": "Profile '{0}' does not exist.\n\nWould you like to create it?",
|
||||
"next_level_in": "Next level in",
|
||||
"wipe_warning": "Changing your account edition requires a profile wipe. This will reset your game progress.",
|
||||
"copied": "Copied",
|
||||
"no_profile_data": "No profile data",
|
||||
"profile_version_mismath": "Your profile was made using a different version of aki and may have issues",
|
||||
"profile_removed": "Profile removed",
|
||||
"profile_removal_failed": "Failed to remove profile",
|
||||
"profile_remove_question_format_1": "Permanently remove profile '{0}'?",
|
||||
"i_understand": "I Understand",
|
||||
"game_version_mismatch_format_2": "SPT is unable to run, this is because SPT expected to find EFT version '{1}',\nbut instead found version '{0}'\n\nEnsure you've downgraded your EFT as described in the install guide\non the page you downloaded SPT from"
|
||||
}
|
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/German.json
Normal file
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/German.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"native_name": "Deutsch",
|
||||
"retry": "Erneut versuchen",
|
||||
"server_connecting": "Verbinden",
|
||||
"server_unavailable_format_1": "Der Server '{0}' ist nicht verfügbar.",
|
||||
"no_servers_available": "Kein Server gefunden. Die Serverliste kann in den Einstellungen bearbeitet werden.",
|
||||
"settings_menu": "Einstellungen",
|
||||
"back": "Zurück",
|
||||
"wipe_profile": "Profil löschen",
|
||||
"username": "Benutzername",
|
||||
"password": "Passwort",
|
||||
"update": "Aktualisieren",
|
||||
"edit_account_update_error": "Das Profil konnte aufgrund eines Fehlers nicht aktualisiert werden.",
|
||||
"register": "Registrieren",
|
||||
"go_to_register": "Zur Registrierung gehen",
|
||||
"login_or_register": "Anmelden / Registrieren",
|
||||
"go_to_login": "Zum Login gehen",
|
||||
"login_automatically": "Automatisch anmelden",
|
||||
"incorrect_login": "Die E-Mail-Adresse und das Passwort stimmen nicht überein.",
|
||||
"login_failed": "Anmeldung fehlgeschlagen",
|
||||
"edition": "Edition",
|
||||
"id": "ID",
|
||||
"logout": "Abmelden",
|
||||
"account": "Account",
|
||||
"edit_account": "Profil bearbeiten",
|
||||
"start_game": "Spiel starten",
|
||||
"installed_in_live_game_warning": "Aki sollte nicht im \"Online\"-Spielordner installiert werden. Bitte kopieren Sie die Spieldateien in einen neuen Ordner und installieren Sie Aki dort.",
|
||||
"no_official_game_warning": "Escape From Tarkov ist auf dem System nicht installiert. Bitte erwerben Sie eine originale Kopie des Spiels, um die Entwickler zu unterstützen.",
|
||||
"eft_exe_not_found_warning": "EscapeFromTarkov.exe konnte nicht gefunden werden.",
|
||||
"account_exist": "Dieser Account existiert bereits.",
|
||||
"url": "URL",
|
||||
"default_language": "Standardsprache",
|
||||
"game_path": "Spielpfad",
|
||||
"clear_game_settings": "Spieleinstellungen zurücksetzen",
|
||||
"clear_game_settings_warning": "You are about to remove your old game settings files. They will be backed up to:\n{0}\n\nAre you sure?",
|
||||
"clear_game_settings_succeeded": "Spieleinstellungen zurückgesetzt",
|
||||
"clear_game_settings_failed": "Die Spieleinstellungen konnten aufgrund eines Fehlers nicht zurückgesetzt werden.",
|
||||
"remove_registry_keys": "Registry-Schlüssel entfernen?",
|
||||
"remove_registry_keys_succeeded": "Registry-Schlüssel entfernt",
|
||||
"remove_registry_keys_failed": "Die Registry-Schlüssel konnten aufgrund eines Fehlers nicht entfernt werden.",
|
||||
"clean_temp_files": "Temporäre Dateien löschen.",
|
||||
"clean_temp_files_succeeded": "Temporäre Dateien gelöscht.",
|
||||
"clean_temp_files_failed": "Die temporären Dateien konnten aufgrund eines Fehlers nicht gelöscht werden.",
|
||||
"select_folder": "Ordner auswählen",
|
||||
"registration_failed": "Registrierung fehlgeschlagen",
|
||||
"minimize_action": "Minimieren",
|
||||
"do_nothing_action": "Nichts tun",
|
||||
"exit_action": "Launcher schließen",
|
||||
"on_game_start": "Bei Spielstart",
|
||||
"game": "Spiel",
|
||||
"new_password": "Neues Passwort",
|
||||
"cancel": "Abbrechen",
|
||||
"need_an_account": "Noch kein eigenes Konto?",
|
||||
"have_an_account": "Schon ein eigenes Konto?",
|
||||
"reapply_patch": "Nochmal patchen",
|
||||
"failed_to_receive_patches": "Fehler beim Erhalt von Patches",
|
||||
"failed_core_patch": "Kernpatch fehlgeschlagen",
|
||||
"failed_mod_patch": "Modpatch fehlgeschlagen",
|
||||
"ok": "OK",
|
||||
"account_page_denied": "Account page denied. Either you are not logged in or the game is running.",
|
||||
"account_updated": "Your account has been updated",
|
||||
"nickname": "Nickname",
|
||||
"side": "Side",
|
||||
"level": "Level",
|
||||
"patching": "Patching",
|
||||
"file_mismatch_dialog_message": "The input file hash doesn't match the expected hash. You may be using the wrong version\nof AKI for your client files.\n\nDo you want to continue?",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"open_folder": "Open Folder",
|
||||
"select_edition": "Select Edition",
|
||||
"profile_created": "Profile Created",
|
||||
"registration_question_format_1": "Profile '{0}' does not exist.\n\nWould you like to create it?",
|
||||
"next_level_in": "Next level in",
|
||||
"wipe_warning": "Changing your account edition requires a profile wipe. This will reset your game progress.",
|
||||
"copied": "Copied",
|
||||
"no_profile_data": "No profile data",
|
||||
"profile_version_mismath": "Your profile was made using a different version of aki and may have issues",
|
||||
"profile_removed": "Profile removed",
|
||||
"profile_removal_failed": "Failed to remove profile",
|
||||
"profile_remove_question_format_1": "Permanently remove profile '{0}'?",
|
||||
"i_understand": "I Understand",
|
||||
"game_version_mismatch_format_2": "SPT is unable to run, this is because SPT expected to find EFT version '{1}',\nbut instead found version '{0}'\n\nEnsure you've downgraded your EFT as described in the install guide\non the page you downloaded SPT from"
|
||||
}
|
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/Japanese.json
Normal file
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/Japanese.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"native_name": "日本語",
|
||||
"retry": "リトライ",
|
||||
"server_connecting": "接続中",
|
||||
"server_unavailable_format_1": "デフォルト サーバー '{0}' は利用できません。",
|
||||
"no_servers_available": "サーバーが見つかりません。 設定でサーバーリストを確認してください。",
|
||||
"settings_menu": "設定",
|
||||
"back": "戻る",
|
||||
"wipe_profile": "プロファイルをワイプ",
|
||||
"username": "ユーザー名",
|
||||
"password": "パスワード",
|
||||
"update": "アップデート",
|
||||
"edit_account_update_error": "プロファイルの更新中に問題が発生しました。",
|
||||
"register": "登録",
|
||||
"go_to_register": "登録する",
|
||||
"login_or_register": "ログイン / 新規登録",
|
||||
"go_to_login": "ログインする",
|
||||
"login_automatically": "自動ログイン",
|
||||
"incorrect_login": "ユーザー名かパスワードが間違っています。",
|
||||
"login_failed": "ログイン失敗",
|
||||
"edition": "エディション",
|
||||
"id": "ID",
|
||||
"logout": "ログアウト",
|
||||
"account": "プロファイル",
|
||||
"edit_account": "プロファイル編集",
|
||||
"start_game": "ゲーム開始",
|
||||
"installed_in_live_game_warning": "AKI はライブ ゲーム ディレクトリにインストールしないでください。 コンピューターの別の場所にあるゲーム ディレクトリのコピーに AKI をインストールしてください。",
|
||||
"no_official_game_warning": "Escape From Tarkov がコンピューターにインストールされていません。 ゲームのコピーを購入して、開発者をサポートしてください!",
|
||||
"eft_exe_not_found_warning": "EscapeFromTarkov.exe がゲーム パスに見つかりません。 ディレクトリが正しいことを確認してください。",
|
||||
"account_exist": "プロファイルは既に存在します",
|
||||
"url": "URL",
|
||||
"default_language": "デフォルト言語",
|
||||
"game_path": "SPTゲームパス",
|
||||
"clear_game_settings": "ゲーム設定をクリア",
|
||||
"clear_game_settings_warning": "古いゲーム設定ファイルを削除しようとしています。 次の場所にバックアップされます:\n{0}\n\nよろしいですか?",
|
||||
"clear_game_settings_succeeded": "ゲーム設定をクリアしました。",
|
||||
"clear_game_settings_failed": "ゲーム設定のクリア中に問題が発生しました。",
|
||||
"remove_registry_keys": "レジストリキーを削除する",
|
||||
"remove_registry_keys_succeeded": "レジストリキーが削除されました。",
|
||||
"remove_registry_keys_failed": "レジストリキーの削除中に問題が発生しました。",
|
||||
"clean_temp_files": "一時ファイルの消去",
|
||||
"clean_temp_files_succeeded": "一時ファイルが消去されました。",
|
||||
"clean_temp_files_failed": "一時ファイルのクリーニング中に問題が発生しました",
|
||||
"select_folder": "フォルダ選択",
|
||||
"registration_failed": "登録に失敗しました。",
|
||||
"minimize_action": "最小化",
|
||||
"do_nothing_action": "何もしない",
|
||||
"exit_action": "ランチャーを閉じる",
|
||||
"on_game_start": "ゲーム開始時",
|
||||
"game": "ゲーム",
|
||||
"new_password": "新規パスワード",
|
||||
"cancel": "キャンセル",
|
||||
"need_an_account": "まだプロファイルを持っていませんか?",
|
||||
"have_an_account": "すでにプロフィールをお持ちですか?",
|
||||
"reapply_patch": "パッチの再適用",
|
||||
"failed_to_receive_patches": "パッチの受信に失敗しました",
|
||||
"failed_core_patch": "コアパッチに失敗しました",
|
||||
"failed_mod_patch": "MODパッチ失敗",
|
||||
"ok": "OK",
|
||||
"account_page_denied": "プロファイルページが拒否されました。ログインしていないか、ゲームが実行中です。",
|
||||
"account_updated": "プロファイルが更新されました",
|
||||
"nickname": "ニックネーム",
|
||||
"side": "派閥",
|
||||
"level": "レベル",
|
||||
"patching": "パッチ適用",
|
||||
"file_mismatch_dialog_message": "入力ファイルのハッシュが予想されるハッシュと一致しません。 クライアントファイルに\n間違ったバージョンの AKI を使用している可能性があります。\n\n続行しますか?",
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"open_folder": "フォルダ展開",
|
||||
"select_edition": "エディションを選択",
|
||||
"profile_created": "プロファイルを作成しました",
|
||||
"registration_question_format_1": "プロファイル '{0}' は存在しません。\n\n作成しますか?",
|
||||
"next_level_in": "次のレベル",
|
||||
"wipe_warning": "アカウントのエディションを変更するには、プロファイルのワイプが必要です。 これにより、ゲームの進行状況がリセットされます。",
|
||||
"copied": "コピーしました",
|
||||
"no_profile_data": "プロファイル データがありません",
|
||||
"profile_version_mismath": "あなたのプロファイルは別のバージョンの AKI を使用して作成されており、問題がある可能性があります",
|
||||
"profile_removed": "プロファイルを削除しました",
|
||||
"profile_removal_failed": "プロファイルを削除できませんでした",
|
||||
"profile_remove_question_format_1": "プロファイル '{0}' を完全に削除しますか?",
|
||||
"i_understand": "了解",
|
||||
"game_version_mismatch_format_2": "SPT を実行できません。これは、SPT が EFT バージョン '{1}' を検出することを期待していたためです\n代わりにバージョン '{0}' を検出しました\n\nインストール ガイドの説明に従って EFT をダウングレードしたことを確認してください\n SPT をダウンロードしたページ"
|
||||
}
|
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/Korean.json
Normal file
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/Korean.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"native_name": "한국어",
|
||||
"retry": "재시도",
|
||||
"server_connecting": "연결 중",
|
||||
"server_unavailable_format_1": "기본 서버 '{0}'를 이용할 수 없습니다.",
|
||||
"no_servers_available": "서버를 찾을 수 없습니다. 설정에서 서버 목록을 확인하세요.",
|
||||
"settings_menu": "설정",
|
||||
"back": "뒤로가기",
|
||||
"wipe_profile": "프로필 초기화",
|
||||
"username": "유저명",
|
||||
"password": "비밀번호",
|
||||
"update": "업데이트",
|
||||
"edit_account_update_error": "프로필을 업데이트하는 도중에 문제가 발생하였습니다.",
|
||||
"register": "등록",
|
||||
"go_to_register": "등록하기",
|
||||
"login_or_register": "로그인 / 등록",
|
||||
"go_to_login": "로그인하기",
|
||||
"login_automatically": "자동 로그인",
|
||||
"incorrect_login": "유저명 혹은 비밀번호가 틀립니다.",
|
||||
"login_failed": "로그인 실패",
|
||||
"edition": "에디션",
|
||||
"id": "아이디",
|
||||
"logout": "로그아웃",
|
||||
"account": "프로필",
|
||||
"edit_account": "프로필 수정",
|
||||
"start_game": "게임 시작",
|
||||
"installed_in_live_game_warning": "Aki는 원본 게임 폴더에 직접 설치할 수 없습니다. 게임 폴더를 다른 위치에 복사하신 후 설치하세요.",
|
||||
"no_official_game_warning": "Escape From Tarkov가 설치되어 있지 않습니다. 게임을 구매하여 개발자들을 지원해주세요!",
|
||||
"eft_exe_not_found_warning": "EscapeFromTarkov.exe 파일이 경로에 없습니다. 경로를 확인하여 주세요.",
|
||||
"account_exist": "프로필이 이미 존재합니다",
|
||||
"url": "URL",
|
||||
"default_language": "기본 언어",
|
||||
"game_path": "SPT 게임 경로",
|
||||
"clear_game_settings": "게임 설정 초기화",
|
||||
"clear_game_settings_warning": "기존 게임의 설정 파일을 초기화 합니다. 기존 파일들은\n{0}\n\n위치에 백업됩니다. 초기화하시겠습니까?",
|
||||
"clear_game_settings_succeeded": "게임 설정이 초기화되었습니다.",
|
||||
"clear_game_settings_failed": "게임 설정을 초기화하는 도중에 문제가 발생하였습니다.",
|
||||
"remove_registry_keys": "레지스트리 키 삭제",
|
||||
"remove_registry_keys_succeeded": "레지스트리 키가 삭제되었습니다.",
|
||||
"remove_registry_keys_failed": "레지스트리 키 삭제 도중에 문제가 발생하였습니다.",
|
||||
"clean_temp_files": "임시 파일 삭제",
|
||||
"clean_temp_files_succeeded": "임시 파일들이 삭제되었습니다.",
|
||||
"clean_temp_files_failed": "임시 파일들을 삭제하는 도중에 문제가 발생하였습니다.",
|
||||
"select_folder": "폴더 선택",
|
||||
"registration_failed": "등록 실패.",
|
||||
"minimize_action": "최소화",
|
||||
"do_nothing_action": "아무것도 하지 않음",
|
||||
"exit_action": "런쳐 종료",
|
||||
"on_game_start": "게임 시작 시",
|
||||
"game": "게임",
|
||||
"new_password": "새 비밀번호",
|
||||
"cancel": "취소",
|
||||
"need_an_account": "프로필이 아직 없습니까?",
|
||||
"have_an_account": "프로필이 있습니까?",
|
||||
"reapply_patch": "패치 재적용",
|
||||
"failed_to_receive_patches": "패치를 받아오는데 실패하였습니다",
|
||||
"failed_core_patch": "코어 패치에 실패하였습니다",
|
||||
"failed_mod_patch": "모드 패치에 실패하였습니다",
|
||||
"ok": "확인",
|
||||
"account_page_denied": "프로필 페이지에 접근할 수 없습니다. 로그인하지 않았거나 이미 게임이 진행 중입니다.",
|
||||
"account_updated": "프로필이 업데이트 되었습니다",
|
||||
"nickname": "닉네임",
|
||||
"side": "진영",
|
||||
"level": "레벨",
|
||||
"patching": "패치 중",
|
||||
"file_mismatch_dialog_message": "입력된 파일의 해시 값이 지정된 해시 값과 틀립니다.\nAKI와 현재 게임의 버전이 맞지 않을 수 있습니다.\n\n계속 진행하시겠습니까?",
|
||||
"yes": "예",
|
||||
"no": "아니오",
|
||||
"open_folder": "폴더 열기",
|
||||
"select_edition": "에디션 선택",
|
||||
"profile_created": "프로필이 생성되었습니다",
|
||||
"registration_question_format_1": "프로필 '{0}'가 존재하지 않습니다.\n\n새로 생성하시겠습니까?",
|
||||
"next_level_in": "다음 레벨까지 필요한 경험치",
|
||||
"wipe_warning": "프로필의 에디션을 변경할 경우 프로필 초기화가 필요합니다. 현재까지 저장된 프로필의 게임 진행이 초기화 됩니다.",
|
||||
"copied": "복사됨",
|
||||
"no_profile_data": "프로필 데이터가 없음",
|
||||
"profile_version_mismath": "다른 버전의 AKI에서 생성된 프로필이므로 문제가 발생할 수 있습니다",
|
||||
"profile_removed": "프로필 삭제됨",
|
||||
"profile_removal_failed": "프로필 삭제에 실패하였습니다",
|
||||
"profile_remove_question_format_1": "프로필 '{0}'을 영구적으로 삭제하시겠습니까?",
|
||||
"i_understand": "이해하였습니다",
|
||||
"game_version_mismatch_format_2": "당신의 게임 버전은 '{0}'이며 호환되는 버전은 '{1}' 입니다.\n\n게임 실행에 문제가 발생하거나 되지 않을 수 있습니다."
|
||||
}
|
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/Russian.json
Normal file
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/Russian.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"native_name": "Русский",
|
||||
"retry": "Повторить",
|
||||
"server_connecting": "Соединение",
|
||||
"server_unavailable_format_1": "Сервер по-умолчанию '{0}' не доступен.",
|
||||
"no_servers_available": "Сервер недоступен. Проверьте список серверов в настройках.",
|
||||
"settings_menu": "Настройки",
|
||||
"back": "Назад",
|
||||
"wipe_profile": "Очистить профиль",
|
||||
"username": "Имя пользователя",
|
||||
"password": "Пароль",
|
||||
"update": "Обновление",
|
||||
"edit_account_update_error": "Произошла ошибка при обновлении профиля.",
|
||||
"register": "Регистрация",
|
||||
"go_to_register": "Перейти к регистриции",
|
||||
"login_or_register": "Вход / Регистрация",
|
||||
"go_to_login": "Перейти ко входу",
|
||||
"login_automatically": "Войти автоматически",
|
||||
"incorrect_login": "Неверное имя пользователя или пароль",
|
||||
"login_failed": "Неудачный вход",
|
||||
"edition": "Издание",
|
||||
"id": "ИД",
|
||||
"logout": "Выход",
|
||||
"account": "Аккаунт",
|
||||
"edit_account": "Редактировать профиль",
|
||||
"start_game": "Запустить игру",
|
||||
"installed_in_live_game_warning": "Aki не должен быть установлен в установленную игру. Пожалуйста, сделайте копию игры и скопируйте файлы игры и установите Aki туда.",
|
||||
"no_official_game_warning": "Escape From Tarkov не установлен на компьютере. Пожалуйста, поддержите разработчиков и купите копию игры!",
|
||||
"eft_exe_not_found_warning": "EscapeFromTarkov.exe не найден.",
|
||||
"account_exist": "Профиль уже существует",
|
||||
"url": "URL",
|
||||
"default_language": "Язык по-уолчанию",
|
||||
"game_path": "Путь к игре",
|
||||
"clear_game_settings": "Очистить настройки игры",
|
||||
"clear_game_settings_warning": "Вы собираетесь удалить старые файлы настроек игры. Их копия будет расположена здесь:\n{0}\n\n Продолжить?",
|
||||
"clear_game_settings_succeeded": "Настройки игры очищены",
|
||||
"clear_game_settings_failed": "Неизвестная проблема при очистке настроек игры",
|
||||
"remove_registry_keys": "Очистить реестр",
|
||||
"remove_registry_keys_succeeded": "Реестр очищен",
|
||||
"remove_registry_keys_failed": "Неизвестная проблема при очистке реестра",
|
||||
"clean_temp_files": "Очистка временных фалов",
|
||||
"clean_temp_files_succeeded": "Временные файлы очищены",
|
||||
"clean_temp_files_failed": "Неизвестная проблема при очистке временных фалов",
|
||||
"select_folder": "Выбрать директорию",
|
||||
"registration_failed": "Неудачная попытка регистрации",
|
||||
"minimize_action": "Минимизировать",
|
||||
"do_nothing_action": "Ничего не делать",
|
||||
"exit_action": "Закрыть лаунчер",
|
||||
"on_game_start": "Запуск игры через лаунчер",
|
||||
"game": "Игра",
|
||||
"new_password": "Новый пароль",
|
||||
"cancel": "Отмена",
|
||||
"need_an_account": "Еще нет профиля?",
|
||||
"have_an_account": "Уже есть профиль?",
|
||||
"reapply_patch": "Применить патч снова",
|
||||
"failed_to_receive_patches": "Не удалось получить патчи",
|
||||
"failed_core_patch": "Не удалось пропатчить ядро",
|
||||
"failed_mod_patch": "Не удалось пропатчит мод",
|
||||
"ok": "Ок",
|
||||
"account_page_denied": "Страница профиля недоступна. Возможно вы не авторизованы, либо игра запущена.",
|
||||
"account_updated": "Ваш аккаут обновлен",
|
||||
"nickname": "Никнейм",
|
||||
"side": "Сторона",
|
||||
"level": "Уровень",
|
||||
"patching": "Патчинг",
|
||||
"file_mismatch_dialog_message": "Хеш-сумма исходного файла не соотвествует требуемому.\nВозможно используется несовместимая версия клиента.\n\nВы хотите продолжить?",
|
||||
"yes": "Да",
|
||||
"no": "Нет",
|
||||
"open_folder": "Открыть папку",
|
||||
"select_edition": "Выбор издания",
|
||||
"profile_created": "Профиль создан",
|
||||
"registration_question_format_1": "Профиль '{0}' не сущестует.\n\nВы желаете его создать?",
|
||||
"next_level_in": "Следующий уровень через",
|
||||
"wipe_warning": "Смена игрового издания требует вайп профиля. Весь текущий прогресс будет сброшен.",
|
||||
"copied": "Скопировано",
|
||||
"no_profile_data": "Данные профиля отсутствуют",
|
||||
"profile_version_mismath": "Ваш профиль был создан с использованием другой версии aki и может содержать ошибки",
|
||||
"profile_removed": "Профиль удален",
|
||||
"profile_removal_failed": "Ошибка удаления профиля",
|
||||
"profile_remove_question_format_1": "Удалить профиль '{0}' безвозвратно?",
|
||||
"i_understand": "Я понимаю",
|
||||
"game_version_mismatch_format_2": "Ваша версия игры: '{0}' и совместимая версия: '{1}'.\n\nИгра может работать некорректно или не работать вообще."
|
||||
}
|
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/Spanish.json
Normal file
83
project/Aki.Launcher/Aki_Data/Launcher/Locales/Spanish.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"native_name": "Español",
|
||||
"retry": "Reintentar",
|
||||
"server_connecting": "Conectando",
|
||||
"server_unavailable_format_1": "El servidor '{0}' no está disponible.",
|
||||
"no_servers_available": "No se han encontrado servidores. Revisa la lista de servidores en ajustes.",
|
||||
"settings_menu": "Ajustes",
|
||||
"back": "Volver",
|
||||
"wipe_profile": "Wipe",
|
||||
"username": "Nombre de Usuario",
|
||||
"password": "Contraseña",
|
||||
"update": "Cambiar",
|
||||
"edit_account_update_error": "Ha ocurrido un problema actualizando el perfil.",
|
||||
"register": "Registrar",
|
||||
"go_to_register": "Ir al registro",
|
||||
"login_or_register": "Login / Registro",
|
||||
"go_to_login": "Ir a iniciar sesión",
|
||||
"login_automatically": "Entrar automáticamente",
|
||||
"incorrect_login": "Usuario y/o contraseña erróneos",
|
||||
"login_failed": "Error la iniciar sesión",
|
||||
"edition": "Edición",
|
||||
"id": "ID",
|
||||
"logout": "Cerrar Sesión",
|
||||
"account": "Perfil",
|
||||
"edit_account": "Editar Perfil",
|
||||
"start_game": "Entrar",
|
||||
"installed_in_live_game_warning": "Aki no debería ser instalado dentro del directorio del juego Live. Favor instala Aki en una copia del juego Live en algún otro sitio de tu equipo.",
|
||||
"no_official_game_warning": "Escape From Tarkov no está instalado en tu equipo. ¡Debes omprar una copia del juego y ayuda a los desarrolladores!isn't installed on your computer. Please buy a copy of the game and support the developers!",
|
||||
"eft_exe_not_found_warning": "EscapeFromTarkov.exe No encontrado. Verifica que tal archivo esté en el directorio del juego.",
|
||||
"account_exist": "El Perfil ya existe",
|
||||
"url": "IP del servidor",
|
||||
"default_language": "Idioma",
|
||||
"game_path": "Ruta para SPT",
|
||||
"clear_game_settings": "Limpiar ajustes del juego",
|
||||
"clear_game_settings_warning": "Estás a punto de borrar tus antiguos ajustes del juego. Ellos estarán respaldados en:\n{0}\n\n¿Estás seguro?",
|
||||
"clear_game_settings_succeeded": "Ajustes limpiados.",
|
||||
"clear_game_settings_failed": "Ha habido un problema mientras se limpiaban los ajustes del juego.",
|
||||
"remove_registry_keys": "Eliminar claves de registro",
|
||||
"remove_registry_keys_succeeded": "Claves de registro borradas.",
|
||||
"remove_registry_keys_failed": "Ha habido un problema mientras se borraban las claves de registro.",
|
||||
"clean_temp_files": "Limpiar archivos temporales",
|
||||
"clean_temp_files_succeeded": "Archivos temporales borrados.",
|
||||
"clean_temp_files_failed": "Ha habido un problema mientras se limpiaban los archivos temporales.",
|
||||
"select_folder": "Elegir carpeta",
|
||||
"registration_failed": "Error al registrarse.",
|
||||
"minimize_action": "Minimizar",
|
||||
"do_nothing_action": "No hacer nada",
|
||||
"exit_action": "Cerrar el launcher",
|
||||
"on_game_start": "Al inicio del juego",
|
||||
"game": "Juego",
|
||||
"new_password": "Nueva contraseña",
|
||||
"cancel": "Cancelar",
|
||||
"need_an_account": "¿Aún no tienes un perfil?",
|
||||
"have_an_account": "¿Ya tienes un perfil?",
|
||||
"reapply_patch": "Reaplicar parche",
|
||||
"failed_to_receive_patches": "Error al recibir los parches",
|
||||
"failed_core_patch": "Error al parchear el núcleo",
|
||||
"failed_mod_patch": "Error al parchear el mod",
|
||||
"ok": "OK",
|
||||
"account_page_denied": "Acceso denegado al perfil. Quizás no estás logueado o el juego está en funcionamiento.",
|
||||
"account_updated": "Tu perfil se ha actualizado",
|
||||
"nickname": "Apodo",
|
||||
"side": "Facción",
|
||||
"level": "Nivel",
|
||||
"patching": "Parcheando",
|
||||
"file_mismatch_dialog_message": "El hash del archivo ingresado no concuerda con el hash esperado. Puede que estés utilizando la versión equivocada\nde AKI en tus archvos del cliente.\n\n¿Quieres continuar?",
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
"open_folder": "Abrir carpeta",
|
||||
"select_edition": "Elegir edición",
|
||||
"profile_created": "Perfil creado",
|
||||
"registration_question_format_1": "El perfil '{0}' no existe.\n\n¿Quieres crear uno?",
|
||||
"next_level_in": "Siguiente nivel en",
|
||||
"wipe_warning": "Para cambiar la edición de tu juego, es necesario reiniciar a tu perfil.\nEsto hará que se reinicie el progreso actual.",
|
||||
"copied": "Copiado",
|
||||
"no_profile_data": "Perfil sin datos",
|
||||
"profile_version_mismath": "Debido a que tu perfil fue creado con una versión diferente de AKI, puede que te encuentres algunos problemas.",
|
||||
"profile_removed": "Perfil borrado",
|
||||
"profile_removal_failed": "Error al borrar el perfil",
|
||||
"profile_remove_question_format_1": "Confirma si quieres borrar el perfil: '{0}'",
|
||||
"i_understand": "Lo entiendo",
|
||||
"game_version_mismatch_format_2": "SPT no puede iniciar, esto es debido a que la versión de EFT esperada es '{1}',\npero la versión encontrada es '{0}'\n\nAsegurate de haber realizado el downgrade de tu versión de EFT, tal como se indica en la guía de instalación\nen la página en la que has descargado SPT"
|
||||
}
|
Binary file not shown.
52
project/Aki.Launcher/App.axaml
Normal file
52
project/Aki.Launcher/App.axaml
Normal file
@ -0,0 +1,52 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Aki.Launcher"
|
||||
x:Class="Aki.Launcher.App">
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<StyleInclude Source="avares://DialogHost.Avalonia/Styles.xaml"/>
|
||||
<FluentTheme Mode="Light"/>
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
<!-- Colors -->
|
||||
<Color x:Key="AKI_DarkGray">#121212</Color>
|
||||
<Color x:Key="AKI_Yellow">#FFC107</Color>
|
||||
<Color x:Key="AKI_White">#FFFFFF</Color>
|
||||
<Color x:Key="AKI_Gray">#282828</Color>
|
||||
<Color x:Key="AKI_DarkGrayBlue">#323947</Color>
|
||||
|
||||
<!-- Brushes -->
|
||||
<SolidColorBrush x:Key="AKI_Foreground_Light" Color="{StaticResource AKI_White}"/>
|
||||
<SolidColorBrush x:Key="AKI_Background_Light" Color="{StaticResource AKI_Gray}"/>
|
||||
<SolidColorBrush x:Key="AKI_Background_Dark" Color="{StaticResource AKI_DarkGray}"/>
|
||||
<SolidColorBrush x:Key="AKI_Brush_Yellow" Color="{StaticResource AKI_Yellow}"/>
|
||||
<SolidColorBrush x:Key="AKI_Brush_DarkGrayBlue" Color="{StaticResource AKI_DarkGrayBlue}"/>
|
||||
<SolidColorBrush x:Key="AKI_Brush_Lighter" Color="Gainsboro"/>
|
||||
|
||||
<!-- Path Geometry for re-usable icons -->
|
||||
<PathGeometry x:Key="FolderWithPlus" Figures="M20 6h-8l-2-2H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-1 8h-3v3h-2v-3h-3v-2h3V9h2v3h3v2z" FillRule="NonZero"
|
||||
/>
|
||||
|
||||
<PathGeometry x:Key="OpenFolder" Figures="M 2.2731724 14.474999 C 2.5381753 14.186249 3.2824783 12.195001 3.9271792 10.05 5.6676413 4.2592679 4.7621113 4.8000009 12.719033 4.8000009 c 5.6684 0 6.78597
|
||||
0.072438 7.12511 0.4618343 0.332844 0.3821726 0.17704 1.1971998 -0.903259 4.7250006 -0.763041 2.4917722 -1.52781 4.4189802 -1.840552 4.6381652 C 16.708149 14.899859 14.592619 15 9.1783054
|
||||
15 2.1694393 15 1.8160107 14.973129 2.2731724 14.474999 Z M 0.36305228 14.025959 C 0.11166709 13.786409 0 11.721164 0 7.3114288 0 1.9218189 0.0760474 0.8703905 0.49472143 0.47142828 0.8806724
|
||||
0.10364926 1.7051307 0 4.2446088 0 7.4749739 0 7.5058294 0.00685701 8.2944922 0.89999983 L 9.0892098 1.8 h 3.6407872 c 3.221023 0 3.71338 0.069177 4.270431 0.5999996 0.346306 0.3300009 0.629646
|
||||
0.802501 0.629646 1.0500009 0 0.3838238 -0.858607 0.4500002 -5.83853 0.4500002 -5.6986082 0 -5.856156 0.016794 -6.5739181 0.7007613 C 4.8131633 4.9861817 4.2426547 6.0999322 3.9498292 7.0757619
|
||||
2.3566037 12.385128 1.8127023 13.81777 1.2887903 14.084957 c -0.37832867 0.192941 -0.68163535 0.173611 -0.92573802 -0.059 z" FillRule="NonZero"
|
||||
/>
|
||||
|
||||
<PathGeometry x:Key="Alert" Figures="M 1.1531774 13.277025 C 1.7874263 12.329396 3.9770847 8.9543883 6.0190869 5.7770253 8.0610888 2.599662 9.8524992 0 10 0 c 0.147501 0 1.938911 2.599662 3.980913 5.7770253 2.042002 3.177363 4.23166 6.5523707 4.86591 7.4999997 L 20 15 H 10 0 Z M 10.904431 11.75675 c 0 -0.540527 -0.301477 -0.810801 -0.904431 -0.810801 -0.6029544 0 -0.9044312 0.270274 -0.9044312 0.810801 0 0.540545 0.3014768 0.810818 0.9044312 0.810818 0.602954 0 0.904431 -0.270273 0.904431 -0.810818 z m 0 -4.4594502 C 10.904431 5.540539 10.783833 5.270267 10 5.270267 c -0.78384 0 -0.9044312 0.270272 -0.9044312 2.0270328 0 1.7567452 0.1205987 2.0270177 0.9044312 2.0270177 0.78384 0 0.904431 -0.2702725 0.904431 -2.0270177 z" FillRule="NonZero"
|
||||
/>
|
||||
|
||||
<PathGeometry x:Key="Delete" Figures="M 1.371429 14.489266 C 1.0250013 14.152485 0.85714332 12.248674 0.85714332 8.6563122 V 3.3233314 H 6 11.142857 v 5.3329808 c 0 3.5923618 -0.167858 5.4961728 -0.514286 5.8329538 -0.7004895 0.680979 -8.5566525 0.680979 -9.257142 0 z M 0 1.6567735 C 0 0.96445539 0.28571444 0.82349613 1.6889935 0.82349613 c 0.9289457 0 1.8081847 -0.18748843 1.9538644 -0.41664027 0.3448694 -0.54247448 4.3694148 -0.54247448 4.7142842 0 0.1456846 0.22915184 1.0249187 0.41664027 1.9538639 0.41664027 1.40328 0 1.688994 0.14096714 1.688994 0.83327737 0 0.7935997 -0.285714 0.8332789 -6 0.8332789 -5.71428556 0 -6 -0.039683 -6 -0.8332789 z" FillRule="NonZero"
|
||||
/>
|
||||
|
||||
<PathGeometry x:Key="Gear" Figures="m 4.4615102 11.271429 c 0 -0.992273 -0.845292 -1.7685001 -1.6612536 -1.5255145 C 1.5450127 10.119702 1.3309542 10.039187 0.67223375 8.9455133 L 0.00700434 7.8410343 0.69579591 7.1261827 C 1.5704786 6.2184053 1.5693913 5.4953105 0.69230711 4.7875001 L 0 4.228826 0.66874495 3.0894129 C 1.3304739 1.9619619 1.5415251 1.879247 2.8002845 2.2540889 3.6162462 2.4970745 4.4615381 1.7208472 4.4615381 0.7285713 4.4615381 0.05428908 4.5761682 0 6 0 c 1.4238306 0 1.5384619 0.05431176 1.5384619 0.7285713 0 0.9922759 0.8452919 1.7685032 1.6612536 1.5255176 1.2587595 -0.3748419 1.4698105 -0.292127 2.1315395 0.835324 L 12 4.228826 11.307692 4.7875001 c -0.877111 0.7078104 -0.878199 1.4309052 -0.0035 2.3386826 l 0.688796 0.7148516 -0.665233 1.104479 C 10.669034 10.039187 10.454975 10.119702 9.1997324 9.7459145 8.3837708 9.5029289 7.5384788 10.279156 7.5384788 11.271429 7.5384788 11.945711 7.4238487 12 6.0000169 12 4.5761863 12 4.461555 11.945688 4.461555 11.271429 Z M 7.4416793 7.4477106 C 8.3356062 6.628907 8.3527385 5.4951178 7.4848013 4.5943362 6.6450019 3.7227628 5.4821482 3.7060611 4.5582677 4.5522701 3.6643409 5.3710738 3.6472085 6.504863 4.5151457 7.4056445 5.3549451 8.2772179 6.5177988 8.2939196 7.4416793 7.4477106 Z" FillRule="NonZero"
|
||||
/>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
38
project/Aki.Launcher/App.axaml.cs
Normal file
38
project/Aki.Launcher/App.axaml.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Aki.Launcher.Controllers;
|
||||
using Aki.Launcher.ViewModels;
|
||||
using Aki.Launcher.Views;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Reactive;
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
RxApp.DefaultExceptionHandler = Observer.Create<Exception>((exception) =>
|
||||
{
|
||||
LogManager.Instance.Exception(exception);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = new MainWindowViewModel(),
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
476
project/Aki.Launcher/Assets/Styles.axaml
Normal file
476
project/Aki.Launcher/Assets/Styles.axaml
Normal file
@ -0,0 +1,476 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cc="using:Aki.Launcher.CustomControls"
|
||||
xmlns:rxui="using:Avalonia.ReactiveUI"
|
||||
>
|
||||
<Design.PreviewWith>
|
||||
<StackPanel Spacing="5" Background="{StaticResource AKI_Background_Dark}">
|
||||
<Button Content="Blah"/>
|
||||
<TextBox Text="Some cool text here" Margin="5"/>
|
||||
<TextBox Watermark="This is a watermark" Margin="5"/>
|
||||
</StackPanel>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<!-- Add Styles Here -->
|
||||
|
||||
<!-- Notification Manager Styles -->
|
||||
<Style Selector="WindowNotificationManager">
|
||||
<Setter Property="Margin" Value="0 35 0 0"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="MaxItems" Value="2"/>
|
||||
</Style>
|
||||
|
||||
<!-- NotificationCard Styles -->
|
||||
<Style Selector="NotificationCard">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<LayoutTransformControl Name="PART_LayoutTransformControl" UseRenderTransform="True">
|
||||
<Border CornerRadius="{DynamicResource ControlCornerRadius}" BoxShadow="0 6 8 0 #4F000000" Margin="5 5 5 10">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{DynamicResource ControlCornerRadius}" ClipToBounds="True">
|
||||
<DockPanel>
|
||||
<ContentControl Name="PART_Content" Content="{TemplateBinding Content}" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Border>
|
||||
</LayoutTransformControl>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.45" Easing="SineEaseIn" FillMode="Forward">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0"/>
|
||||
<Setter Property="ZIndex" Value="0"/>
|
||||
<Setter Property="TranslateTransform.Y" Value="-100"/>
|
||||
<Setter Property="ScaleTransform.ScaleX" Value="1"/>
|
||||
<Setter Property="ScaleTransform.ScaleY" Value="1"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="30%">
|
||||
<Setter Property="Opacity" Value="0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="99%">
|
||||
<Setter Property="ZIndex" Value="0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
<Setter Property="ZIndex" Value="1"/>
|
||||
<Setter Property="TranslateTransform.Y" Value="0"/>
|
||||
<Setter Property="ScaleTransform.ScaleX" Value="1"/>
|
||||
<Setter Property="ScaleTransform.ScaleY" Value="1"/>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
|
||||
<Style Selector="NotificationCard[IsClosing=true] /template/ LayoutTransformControl#PART_LayoutTransformControl">
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.3" Easing="SineEaseOut" FillMode="Backward">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
<Setter Property="TranslateTransform.X" Value="0"/>
|
||||
<Setter Property="TranslateTransform.Y" Value="0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="0"/>
|
||||
<Setter Property="TranslateTransform.X" Value="0"/>
|
||||
<Setter Property="TranslateTransform.Y" Value="-100"/>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
|
||||
<!-- TitleBar Styles -->
|
||||
<Style Selector="cc|TitleBar">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||
<Setter Property="ButtonForeground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="cc|TitleBar.versiontag">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="BorderThickness" Value="0 0 0 2"/>
|
||||
</Style>
|
||||
|
||||
<!-- TextBox Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml -->
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Light}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:focus">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:pointerover">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:pointerover /template/ Border#PART_BorderElement">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="DimGray"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:pointerover /template/ TextBlock#PART_Watermark, TextBox:focus /template/ TextBlock#PART_FloatingWatermark">
|
||||
<Setter Property="Foreground" Value="DimGray"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:focus /template/ TextBlock#PART_Watermark, TextBox:focus /template/ TextBlock#PART_FloatingWatermark">
|
||||
<Setter Property="Foreground" Value="DimGray"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox /template/ TextBlock#PART_Watermark, TextBox:focus /template/ TextBlock#PART_FloatingWatermark">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<!-- Label Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Label.xaml -->
|
||||
<Style Selector="Label">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Label.yellow">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Label.dark">
|
||||
<Setter Property="Foreground" Value="DimGray"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Label.versionMismatch">
|
||||
<Setter Property="Foreground" Value="OrangeRed"/>
|
||||
</Style>
|
||||
|
||||
<!-- ProgressBar Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml -->
|
||||
<Style Selector="ProgressBar">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ProgressBar.error">
|
||||
<Setter Property="Foreground" Value="Red"/>
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.5" FillMode="Forward">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="Value" Value="0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Foreground" Value="Red"/>
|
||||
<Setter Property="Value" Value="100"/>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
|
||||
<!-- Seperator Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Separator.xaml -->
|
||||
<Style Selector="Separator">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Button.xaml -->
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Light}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:disabled /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button yellow -->
|
||||
<Style Selector="Button.yellow">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.yellow:pointerover">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.yellow:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Gold"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.yellow:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.yellow:disabled /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button Link Style -->
|
||||
<Style Selector="Button.link">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button Bordered Link Style -->
|
||||
<Style Selector="Button.borderedlink">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.borderedlink:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.borderedlink:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button Profile Info Style -->
|
||||
<Style Selector="Button.profileinfo">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.icon">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.icon:pointerover">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
|
||||
<!-- Checkbox styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml -->
|
||||
<Style Selector="CheckBox">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="CheckBox:pointerover /template/ ContentPresenter#ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Foreground_Light}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="CheckBox:checked">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="CheckBox:checked /template/ Border#NormalRectangle">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="CheckBox:checked /template/ Path#CheckGlyph">
|
||||
<Setter Property="Fill" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
</Style>
|
||||
|
||||
<!-- ComboBox Styles -->
|
||||
<!-- Source Ref: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml -->
|
||||
<Style Selector="ComboBox">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||
<Setter Property="PlaceholderForeground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<DataValidationErrors>
|
||||
<Grid RowDefinitions="Auto, *, Auto"
|
||||
ColumnDefinitions="*,32">
|
||||
<ContentPresenter x:Name="HeaderContentPresenter"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
IsVisible="False"
|
||||
TextBlock.FontWeight="{DynamicResource ComboBoxHeaderThemeFontWeight}"
|
||||
Margin="{DynamicResource ComboBoxTopHeaderMargin}"
|
||||
VerticalAlignment="Top" />
|
||||
<Border x:Name="Background"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
MinWidth="{DynamicResource ComboBoxThemeMinWidth}" />
|
||||
|
||||
<Border x:Name="HighlightBackground"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="{DynamicResource ComboBoxBackgroundUnfocused}"
|
||||
BorderBrush="{DynamicResource ComboBoxBackgroundBorderBrushUnfocused}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}" />
|
||||
<TextBlock x:Name="PlaceholderTextBlock"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Text="{TemplateBinding PlaceholderText}"
|
||||
Foreground="{TemplateBinding PlaceholderForeground}"
|
||||
IsVisible="{TemplateBinding SelectionBoxItem, Converter={x:Static ObjectConverters.IsNull}}" />
|
||||
<ContentControl x:Name="ContentPresenter"
|
||||
Content="{TemplateBinding SelectionBoxItem}"
|
||||
ContentTemplate="{TemplateBinding ItemTemplate}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
|
||||
<Border x:Name="DropDownOverlay"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Background="Transparent"
|
||||
Margin="0,1,1,1"
|
||||
Width="30"
|
||||
IsVisible="False"
|
||||
HorizontalAlignment="Right" />
|
||||
|
||||
<Viewbox UseLayoutRounding="False"
|
||||
MinHeight="{DynamicResource ComboBoxMinHeight}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
IsHitTestVisible="False"
|
||||
Margin="0,0,10,0"
|
||||
Height="12"
|
||||
Width="12"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
<Panel>
|
||||
<Panel Height="12"
|
||||
Width="12" />
|
||||
<Path x:Name="DropDownGlyph"
|
||||
VerticalAlignment="Center" />
|
||||
</Panel>
|
||||
</Viewbox>
|
||||
<Popup Name="PART_Popup"
|
||||
WindowManagerAddShadowHint="False"
|
||||
IsOpen="{TemplateBinding IsDropDownOpen, Mode=TwoWay}"
|
||||
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
MaxHeight="{TemplateBinding MaxDropDownHeight}"
|
||||
PlacementTarget="Background"
|
||||
IsLightDismissEnabled="True">
|
||||
<Border x:Name="PopupBorder"
|
||||
Background="{StaticResource AKI_Background_Dark}"
|
||||
BorderBrush="{StaticResource AKI_Background_Dark}"
|
||||
BorderThickness="{DynamicResource ComboBoxDropdownBorderThickness}"
|
||||
Margin="0,-1,0,-1"
|
||||
Padding="{DynamicResource ComboBoxDropdownBorderPadding}"
|
||||
HorizontalAlignment="Stretch"
|
||||
CornerRadius="{DynamicResource OverlayCornerRadius}">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
|
||||
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
|
||||
<ItemsPresenter Name="PART_ItemsPresenter"
|
||||
Items="{TemplateBinding Items}"
|
||||
Margin="{DynamicResource ComboBoxDropdownContentMargin}"
|
||||
ItemsPanel="{TemplateBinding ItemsPanel}"
|
||||
ItemTemplate="{TemplateBinding ItemTemplate}"
|
||||
VirtualizationMode="{TemplateBinding VirtualizationMode}" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</DataValidationErrors>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox /template/ Path#DropDownGlyph">
|
||||
<Setter Property="Fill" Value="{StaticResource AKI_Foreground_Light}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox:pointerover /template/ Path#DropDownGlyph">
|
||||
<Setter Property="Fill" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox:pointerover">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox:pointerover /template/ Border#Background">
|
||||
<Setter Property="Background" Value="Black"/>
|
||||
<Setter Property="BorderBrush" Value="Black" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox /template/ Border#PopupBorder">
|
||||
<Setter Property="Background" Value="Black"/>
|
||||
<Setter Property="BorderBrush" Value="Black"/>
|
||||
</Style>
|
||||
|
||||
<!-- ComboBoxItem Styles -->
|
||||
<!-- Source Ref: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml-->
|
||||
<Style Selector="ComboBoxItem /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Black"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBoxItem">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBoxItem:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Light}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Background_Light}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Foreground_Light}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Background_Dark}" />
|
||||
</Style>
|
||||
|
||||
</Styles>
|
BIN
project/Aki.Launcher/Assets/aki-logo.png
Normal file
BIN
project/Aki.Launcher/Assets/aki-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
project/Aki.Launcher/Assets/icon.ico
Normal file
BIN
project/Aki.Launcher/Assets/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 420 KiB |
13
project/Aki.Launcher/Attributes/NavigationPreCondition.cs
Normal file
13
project/Aki.Launcher/Attributes/NavigationPreCondition.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Aki.Launcher.Models;
|
||||
using Aki.Launcher.ViewModels;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
|
||||
namespace Aki.Launcher.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public abstract class NavigationPreCondition : Attribute
|
||||
{
|
||||
public abstract NavigationPreConditionResult TestPreCondition(IScreen Host);
|
||||
}
|
||||
}
|
19
project/Aki.Launcher/Attributes/RequireLoggedIn.cs
Normal file
19
project/Aki.Launcher/Attributes/RequireLoggedIn.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.Models;
|
||||
using Aki.Launcher.ViewModels;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Aki.Launcher.Attributes
|
||||
{
|
||||
public class RequireLoggedIn : NavigationPreCondition
|
||||
{
|
||||
public override NavigationPreConditionResult TestPreCondition(IScreen Host)
|
||||
{
|
||||
AccountStatus status = AccountManager.Login(AccountManager.SelectedAccount.username, AccountManager.SelectedAccount.password);
|
||||
|
||||
if (status == AccountStatus.OK) return NavigationPreConditionResult.FromSuccess();
|
||||
|
||||
return NavigationPreConditionResult.FromError(LocalizationProvider.Instance.login_failed, new ConnectServerViewModel(Host));
|
||||
}
|
||||
}
|
||||
}
|
19
project/Aki.Launcher/Attributes/RequireServerConnected.cs
Normal file
19
project/Aki.Launcher/Attributes/RequireServerConnected.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.Models;
|
||||
using Aki.Launcher.ViewModels;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Aki.Launcher.Attributes
|
||||
{
|
||||
public class RequireServerConnected : NavigationPreCondition
|
||||
{
|
||||
public override NavigationPreConditionResult TestPreCondition(IScreen Host)
|
||||
{
|
||||
if (ServerManager.PingServer()) return NavigationPreConditionResult.FromSuccess();
|
||||
|
||||
string error = string.Format(LocalizationProvider.Instance.server_unavailable_format_1, LauncherSettingsProvider.Instance.Server.Name);
|
||||
|
||||
return NavigationPreConditionResult.FromError(error, new ConnectServerViewModel(Host));
|
||||
}
|
||||
}
|
||||
}
|
45
project/Aki.Launcher/Converters/ImageSourceConverter.cs
Normal file
45
project/Aki.Launcher/Converters/ImageSourceConverter.cs
Normal file
@ -0,0 +1,45 @@
|
||||
/* ImageSourceConverter.cs
|
||||
* License: NCSA Open Source License
|
||||
*
|
||||
* Copyright: Merijn Hendriks
|
||||
* AUTHORS:
|
||||
* waffle.lord
|
||||
*/
|
||||
|
||||
using Aki.Launcher.Controllers;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media.Imaging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Aki.Launcher.Converters
|
||||
{
|
||||
public class ImageSourceConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (!(value is string valueString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (value is string rawUri && targetType.IsAssignableFrom(typeof(Bitmap)))
|
||||
{
|
||||
return new Bitmap(rawUri);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:Aki.Launcher.Models"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Aki.Launcher.CustomControls.LocalizedLauncherActionSelector">
|
||||
<ComboBox x:Name="combobox"
|
||||
SelectionChanged="combobox_SelectionChanged"
|
||||
>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type model:LocalizedLauncherAction}">
|
||||
<Label Content="{Binding Name}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</UserControl>
|
@ -0,0 +1,83 @@
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.Models;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Aki.Launcher.CustomControls
|
||||
{
|
||||
public partial class LocalizedLauncherActionSelector : UserControl
|
||||
{
|
||||
public LocalizedLauncherActionSelector()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.DetachedFromVisualTree += LocalizedLauncherActionSelector_DetachedFromVisualTree;
|
||||
LocalizationProvider.LocaleChanged += LocalizationProvider_LocaleChanged;
|
||||
}
|
||||
|
||||
private void LocalizationProvider_LocaleChanged(object? sender, EventArgs e)
|
||||
{
|
||||
UpdateLocales();
|
||||
}
|
||||
|
||||
private void LocalizedLauncherActionSelector_DetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
//make sure static event handler is released
|
||||
LocalizationProvider.LocaleChanged -= LocalizationProvider_LocaleChanged;
|
||||
}
|
||||
|
||||
public void UpdateLocales()
|
||||
{
|
||||
var comboBox = this.FindControl<ComboBox>("combobox");
|
||||
|
||||
foreach (var item in comboBox.Items)
|
||||
{
|
||||
if(item is LocalizedLauncherAction localizedAction)
|
||||
{
|
||||
localizedAction.UpdateLocaleName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
var comboBox = this.FindControl<ComboBox>("combobox");
|
||||
|
||||
Array actions = Enum.GetValues(typeof(LauncherAction));
|
||||
|
||||
List<LocalizedLauncherAction> actionsList = new List<LocalizedLauncherAction>();
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (action is LauncherAction launcherAction)
|
||||
{
|
||||
actionsList.Add(new LocalizedLauncherAction(launcherAction));
|
||||
}
|
||||
}
|
||||
|
||||
comboBox.Items = actionsList;
|
||||
|
||||
foreach(var item in comboBox.Items)
|
||||
{
|
||||
if(item is LocalizedLauncherAction actionItem && actionItem.Action == LauncherSettingsProvider.Instance.LauncherStartGameAction)
|
||||
{
|
||||
comboBox.SelectedItem = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void combobox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if(e.AddedItems.Count == 1 && e.AddedItems[0] is LocalizedLauncherAction localizedAction)
|
||||
{
|
||||
LauncherSettingsProvider.Instance.LauncherStartGameAction = localizedAction.Action;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
94
project/Aki.Launcher/CustomControls/TitleBar.axaml
Normal file
94
project/Aki.Launcher/CustomControls/TitleBar.axaml
Normal file
@ -0,0 +1,94 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="using:Aki.Launcher.Helpers"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Aki.Launcher.CustomControls.TitleBar">
|
||||
|
||||
<Grid ColumnDefinitions="AUTO,*,AUTO,10,AUTO,AUTO">
|
||||
|
||||
<Rectangle Grid.ColumnSpan="6" IsHitTestVisible="False"
|
||||
Fill="{Binding Background, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
|
||||
<Label Content="{Binding Title, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
IsHitTestVisible="False"
|
||||
Foreground="{Binding Foreground, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
Background="Transparent"
|
||||
VerticalContentAlignment="Center"
|
||||
/>
|
||||
|
||||
<!-- Setting button -->
|
||||
<Button x:Name="stb"
|
||||
Grid.Column="2"
|
||||
FontSize="12"
|
||||
Command="{Binding SettingsButtonCommand, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
Classes="link"
|
||||
IsEnabled="{Binding Source={x:Static helpers:LauncherSettingsProvider.Instance}, Path=AllowSettings}"
|
||||
IsVisible="{Binding Source={x:Static helpers:LauncherSettingsProvider.Instance}, Path=AllowSettings}"
|
||||
>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Data="{StaticResource Gear}" Fill="{Binding ElementName=stb, Path=Foreground}"
|
||||
Margin="2"/>
|
||||
<TextBlock Text="{Binding Source={x:Static helpers:LocalizationProvider.Instance}, Path=settings_menu}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Minimize (-) Button -->
|
||||
<Button Content="" Grid.Column="4"
|
||||
Foreground="{Binding ButtonForeground, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
Command="{Binding MinButtonCommand, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
Background="Transparent"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
CornerRadius="0"
|
||||
Width="35"
|
||||
>
|
||||
<Button.Styles>
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Light}"/>
|
||||
</Style>
|
||||
</Button.Styles>
|
||||
</Button>
|
||||
|
||||
<!-- Close (X) Button -->
|
||||
<Button Content="" Grid.Column="5"
|
||||
Foreground="{Binding ButtonForeground, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
Command="{Binding XButtonCommand, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
Background="Transparent"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
CornerRadius="0"
|
||||
Width="35"
|
||||
>
|
||||
<Button.Styles>
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="IndianRed"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Crimson"/>
|
||||
</Style>
|
||||
</Button.Styles>
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
87
project/Aki.Launcher/CustomControls/TitleBar.axaml.cs
Normal file
87
project/Aki.Launcher/CustomControls/TitleBar.axaml.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Aki.Launcher.CustomControls
|
||||
{
|
||||
public partial class TitleBar : UserControl
|
||||
{
|
||||
public TitleBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string> TitleProperty =
|
||||
AvaloniaProperty.Register<TitleBar, string>(nameof(Title));
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => GetValue(TitleProperty);
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> ButtonForegroundProperty =
|
||||
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(ButtonForeground));
|
||||
|
||||
public IBrush ButtonForeground
|
||||
{
|
||||
get => GetValue(ButtonForegroundProperty);
|
||||
set => SetValue(ButtonForegroundProperty, value);
|
||||
}
|
||||
|
||||
public static new readonly StyledProperty<IBrush> ForegroundProperty =
|
||||
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Foreground));
|
||||
|
||||
public new IBrush Foreground
|
||||
{
|
||||
get => GetValue(ForegroundProperty);
|
||||
set => SetValue(ForegroundProperty, value);
|
||||
}
|
||||
|
||||
public static new readonly StyledProperty<IBrush> BackgroundProperty =
|
||||
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Background));
|
||||
|
||||
public new IBrush Background
|
||||
{
|
||||
get => GetValue(BackgroundProperty);
|
||||
set => SetValue(BackgroundProperty, value);
|
||||
}
|
||||
|
||||
//Close Button Command (X Button) Property
|
||||
public static readonly StyledProperty<ICommand> XButtonCommandProperty =
|
||||
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(XButtonCommand));
|
||||
|
||||
public ICommand XButtonCommand
|
||||
{
|
||||
get => GetValue(XButtonCommandProperty);
|
||||
set => SetValue(XButtonCommandProperty, value);
|
||||
}
|
||||
|
||||
//Minimize Button Command (- Button) Property
|
||||
public static readonly StyledProperty<ICommand> MinButtonCommandProperty =
|
||||
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(MinButtonCommand));
|
||||
|
||||
public ICommand MinButtonCommand
|
||||
{
|
||||
get => GetValue(MinButtonCommandProperty);
|
||||
set => SetValue(MinButtonCommandProperty, value);
|
||||
}
|
||||
|
||||
//Setting Button Command Property
|
||||
public static readonly StyledProperty<ICommand> SettingsButtonCommandProperty =
|
||||
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(SettingsButtonCommand));
|
||||
|
||||
public ICommand SettingsButtonCommand
|
||||
{
|
||||
get => GetValue(SettingsButtonCommandProperty);
|
||||
set => SetValue(SettingsButtonCommandProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
46
project/Aki.Launcher/Models/GameStarterFrontend.cs
Normal file
46
project/Aki.Launcher/Models/GameStarterFrontend.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.Interfaces;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using Aki.Launcher.ViewModels.Dialogs;
|
||||
using Aki.Launcher.ViewModels.Notifications;
|
||||
using Avalonia.Controls.Notifications;
|
||||
using Splat;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.Launcher.Models
|
||||
{
|
||||
public class GameStarterFrontend : IGameStarterFrontend
|
||||
{
|
||||
private WindowNotificationManager notificationManager => Locator.Current.GetService<WindowNotificationManager>();
|
||||
|
||||
public async Task CompletePatchTask(IAsyncEnumerable<PatchResultInfo> task)
|
||||
{
|
||||
notificationManager.Show(new AkiNotificationViewModel(null, "", $"{LocalizationProvider.Instance.patching} ..."));
|
||||
|
||||
var iter = task.GetAsyncEnumerator();
|
||||
while (await iter.MoveNextAsync())
|
||||
{
|
||||
var info = iter.Current;
|
||||
if (!info.OK)
|
||||
{
|
||||
if(info.Status == ByteBanger.PatchResultType.InputChecksumMismatch)
|
||||
{
|
||||
var result = await DialogHost.DialogHost.Show(new ConfirmationDialogViewModel(null, LocalizationProvider.Instance.file_mismatch_dialog_message));
|
||||
|
||||
if(result != null && result is bool confirmation && !confirmation)
|
||||
{
|
||||
notificationManager.Show(new AkiNotificationViewModel(null, "", LocalizationProvider.Instance.failed_core_patch, NotificationType.Error));
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
notificationManager.Show(new AkiNotificationViewModel(null, "", LocalizationProvider.Instance.failed_core_patch, NotificationType.Error));
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
project/Aki.Launcher/Models/ImageHelper.cs
Normal file
27
project/Aki.Launcher/Models/ImageHelper.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Aki.Launcher.Models
|
||||
{
|
||||
public class ImageHelper : ReactiveObject
|
||||
{
|
||||
private string _Path;
|
||||
public string Path
|
||||
{
|
||||
get => _Path;
|
||||
set => this.RaiseAndSetIfChanged(ref _Path, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force property changed by touching the image path.
|
||||
/// </summary>
|
||||
/// <remarks>Can be used to force image re-loading</remarks>
|
||||
public void Touch()
|
||||
{
|
||||
string tmp = Path;
|
||||
|
||||
Path = "";
|
||||
|
||||
Path = tmp;
|
||||
}
|
||||
}
|
||||
}
|
22
project/Aki.Launcher/Models/NavigationPreConditionResult.cs
Normal file
22
project/Aki.Launcher/Models/NavigationPreConditionResult.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Aki.Launcher.ViewModels;
|
||||
|
||||
namespace Aki.Launcher.Models
|
||||
{
|
||||
public class NavigationPreConditionResult
|
||||
{
|
||||
public bool Succeeded => ErrorMessage == null;
|
||||
public string? ErrorMessage { get; private set; } = null;
|
||||
|
||||
public ViewModelBase? ViewModel { get; private set; } = null;
|
||||
|
||||
protected NavigationPreConditionResult(string? ErrorMessage = null, ViewModelBase? OnFailedViewModel = null)
|
||||
{
|
||||
this.ErrorMessage = ErrorMessage;
|
||||
ViewModel = OnFailedViewModel;
|
||||
}
|
||||
|
||||
public static NavigationPreConditionResult FromSuccess() => new NavigationPreConditionResult();
|
||||
|
||||
public static NavigationPreConditionResult FromError(string ErrorMessage, ViewModelBase ViewModel) => new NavigationPreConditionResult(ErrorMessage, ViewModel);
|
||||
}
|
||||
}
|
42
project/Aki.Launcher/Program.cs
Normal file
42
project/Aki.Launcher/Program.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Aki.Launcher.Controllers;
|
||||
using Avalonia;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Instance.Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
{
|
||||
Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly());
|
||||
|
||||
return AppBuilder.Configure<App>()
|
||||
.UseReactiveUI()
|
||||
.UsePlatformDetect()
|
||||
.LogToTrace()
|
||||
.UseReactiveUI();
|
||||
}
|
||||
}
|
||||
}
|
BIN
project/Aki.Launcher/References/Newtonsoft.Json.dll
Normal file
BIN
project/Aki.Launcher/References/Newtonsoft.Json.dll
Normal file
Binary file not shown.
BIN
project/Aki.Launcher/References/zlib.net.dll
Normal file
BIN
project/Aki.Launcher/References/zlib.net.dll
Normal file
Binary file not shown.
30
project/Aki.Launcher/ViewLocator.cs
Normal file
30
project/Aki.Launcher/ViewLocator.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using Aki.Launcher.ViewModels;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using System;
|
||||
|
||||
namespace Aki.Launcher
|
||||
{
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
public IControl Build(object data)
|
||||
{
|
||||
var name = data.GetType().FullName!.Replace("ViewModel", "View");
|
||||
var type = Type.GetType(name);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
return (Control)Activator.CreateInstance(type)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new TextBlock { Text = "Not Found: " + name };
|
||||
}
|
||||
}
|
||||
|
||||
public bool Match(object data)
|
||||
{
|
||||
return data is ViewModelBase;
|
||||
}
|
||||
}
|
||||
}
|
65
project/Aki.Launcher/ViewModels/ConnectServerViewModel.cs
Normal file
65
project/Aki.Launcher/ViewModels/ConnectServerViewModel.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using Aki.Launch.Models.Aki;
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.Launcher.ViewModels
|
||||
{
|
||||
public class ConnectServerViewModel : ViewModelBase
|
||||
{
|
||||
private bool noAutoLogin = false;
|
||||
|
||||
public ConnectServerModel connectModel { get; set; } = new ConnectServerModel()
|
||||
{
|
||||
InfoText = LocalizationProvider.Instance.server_connecting
|
||||
};
|
||||
|
||||
public ConnectServerViewModel(IScreen Host, bool NoAutoLogin = false) : base(Host)
|
||||
{
|
||||
noAutoLogin = NoAutoLogin;
|
||||
|
||||
this.WhenActivated((CompositeDisposable disposables) =>
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await ConnectServer();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async Task ConnectServer()
|
||||
{
|
||||
await ServerManager.LoadDefaultServerAsync(LauncherSettingsProvider.Instance.Server.Url);
|
||||
|
||||
bool connected = ServerManager.PingServer();
|
||||
|
||||
connectModel.ConnectionFailed = !connected;
|
||||
|
||||
connectModel.InfoText = connected ? LocalizationProvider.Instance.ok : string.Format(LocalizationProvider.Instance.server_unavailable_format_1, LauncherSettingsProvider.Instance.Server.Name);
|
||||
|
||||
if (connected)
|
||||
{
|
||||
AkiVersion version = Locator.Current.GetService<AkiVersion>("akiversion");
|
||||
|
||||
version.ParseVersionInfo(ServerManager.GetVersion());
|
||||
|
||||
NavigateTo(new LoginViewModel(HostScreen, noAutoLogin));
|
||||
}
|
||||
}
|
||||
|
||||
public void RetryCommand()
|
||||
{
|
||||
connectModel.InfoText = LocalizationProvider.Instance.server_connecting;
|
||||
|
||||
connectModel.ConnectionFailed = false;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await ConnectServer();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Aki.Launcher.ViewModels.Dialogs
|
||||
{
|
||||
public class ChangeEditionDialogViewModel : ViewModelBase
|
||||
{
|
||||
public EditionCollection editions { get; set; } = new EditionCollection();
|
||||
|
||||
public ChangeEditionDialogViewModel(IScreen Host) : base(Host)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using Aki.Launcher.Helpers;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Aki.Launcher.ViewModels.Dialogs
|
||||
{
|
||||
public class ConfirmationDialogViewModel : ViewModelBase
|
||||
{
|
||||
public string Question { get; set; }
|
||||
public string ConfirmButtonText { get; set; }
|
||||
public string DenyButtonText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A confirmation dialog
|
||||
/// </summary>
|
||||
/// <param name="Host">Set to null when <see cref="ViewModelBase.ShowDialog(object)"/> is used, since the dialog host is handling routing</param>
|
||||
/// <param name="Question"></param>
|
||||
/// <param name="ConfirmButtonText"></param>
|
||||
/// <param name="DenyButtonText"></param>
|
||||
public ConfirmationDialogViewModel(IScreen Host, string Question, string? ConfirmButtonText = null, string? DenyButtonText = null) : base(Host)
|
||||
{
|
||||
this.Question = Question;
|
||||
this.ConfirmButtonText = ConfirmButtonText ?? LocalizationProvider.Instance.yes;
|
||||
this.DenyButtonText = DenyButtonText ?? LocalizationProvider.Instance.no;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Aki.Launcher.ViewModels.Dialogs
|
||||
{
|
||||
public class RegisterDialogViewModel : ViewModelBase
|
||||
{
|
||||
public string Question { get; set; }
|
||||
public string RegisterButtonText { get; set; }
|
||||
public string CancelButtonText { get; set; }
|
||||
public string ComboBoxPlaceholderText { get; set; }
|
||||
public EditionCollection Editions { get; set; } = new EditionCollection();
|
||||
|
||||
/// <summary>
|
||||
/// A registration dialog
|
||||
/// </summary>
|
||||
/// <param name="Host">Set to null when <see cref="ViewModelBase.ShowDialog(object)"/> is used, since the dialog host is handling routing</param>
|
||||
/// <param name="Username"></param>
|
||||
public RegisterDialogViewModel(IScreen Host, string Username) : base(Host)
|
||||
{
|
||||
Question = string.Format(LocalizationProvider.Instance.registration_question_format_1, Username);
|
||||
|
||||
RegisterButtonText = LocalizationProvider.Instance.register;
|
||||
|
||||
CancelButtonText = LocalizationProvider.Instance.cancel;
|
||||
|
||||
ComboBoxPlaceholderText = LocalizationProvider.Instance.select_edition;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
using Aki.Launcher.Helpers;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Aki.Launcher.ViewModels.Dialogs
|
||||
{
|
||||
public class WarningDialogViewModel : ViewModelBase
|
||||
{
|
||||
public string ButtonText { get; set; }
|
||||
public string WarningMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A warning dialog
|
||||
/// </summary>
|
||||
/// <param name="Host">Set to null when <see cref="ViewModelBase.ShowDialog(object)"/> is used, since the dialog host is handling routing</param>
|
||||
/// <param name="ButtonText"></param>
|
||||
/// <param name="WarningMessage"></param>
|
||||
public WarningDialogViewModel(IScreen Host, string WarningMessage, string? ButtonText = null) : base(Host)
|
||||
{
|
||||
this.WarningMessage = WarningMessage;
|
||||
this.ButtonText = ButtonText ?? LocalizationProvider.Instance.ok;
|
||||
}
|
||||
}
|
||||
}
|
165
project/Aki.Launcher/ViewModels/LoginViewModel.cs
Normal file
165
project/Aki.Launcher/ViewModels/LoginViewModel.cs
Normal file
@ -0,0 +1,165 @@
|
||||
using Aki.Launcher.Attributes;
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using Aki.Launcher.Models;
|
||||
using Aki.Launcher.Models.Aki;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using Aki.Launcher.ViewModels.Dialogs;
|
||||
using Avalonia.Controls.Notifications;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.Launcher.ViewModels
|
||||
{
|
||||
[RequireServerConnected]
|
||||
public class LoginViewModel : ViewModelBase
|
||||
{
|
||||
public ObservableCollection<ProfileInfo> ExistingProfiles { get; set; } = new ObservableCollection<ProfileInfo>();
|
||||
|
||||
public LoginModel Login { get; set; } = new LoginModel();
|
||||
|
||||
public ReactiveCommand<Unit, Unit> LoginCommand { get; set; }
|
||||
|
||||
public LoginViewModel(IScreen Host, bool NoAutoLogin = false) : base(Host)
|
||||
{
|
||||
//setup reactive commands
|
||||
LoginCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
AccountStatus status = await AccountManager.LoginAsync(Login);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case AccountStatus.OK:
|
||||
{
|
||||
if (LauncherSettingsProvider.Instance.UseAutoLogin && LauncherSettingsProvider.Instance.Server.AutoLoginCreds != Login)
|
||||
{
|
||||
LauncherSettingsProvider.Instance.Server.AutoLoginCreds = Login;
|
||||
}
|
||||
|
||||
LauncherSettingsProvider.Instance.SaveSettings();
|
||||
NavigateTo(new ProfileViewModel(HostScreen));
|
||||
break;
|
||||
}
|
||||
case AccountStatus.LoginFailed:
|
||||
{
|
||||
// Create account if it doesn't exist
|
||||
if (!string.IsNullOrWhiteSpace(Login.Username))
|
||||
{
|
||||
var result = await ShowDialog(new RegisterDialogViewModel(null, Login.Username));
|
||||
|
||||
if (result != null && result is string edition)
|
||||
{
|
||||
AccountStatus registerResult = await AccountManager.RegisterAsync(Login.Username, Login.Password, edition);
|
||||
|
||||
switch (registerResult)
|
||||
{
|
||||
case AccountStatus.OK:
|
||||
{
|
||||
if (LauncherSettingsProvider.Instance.UseAutoLogin && LauncherSettingsProvider.Instance.Server.AutoLoginCreds != Login)
|
||||
{
|
||||
LauncherSettingsProvider.Instance.Server.AutoLoginCreds = Login;
|
||||
}
|
||||
|
||||
LauncherSettingsProvider.Instance.SaveSettings();
|
||||
SendNotification(LocalizationProvider.Instance.profile_created, Login.Username, NotificationType.Success);
|
||||
NavigateTo(new ProfileViewModel(HostScreen));
|
||||
break;
|
||||
}
|
||||
case AccountStatus.RegisterFailed:
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.registration_failed, NotificationType.Error);
|
||||
break;
|
||||
}
|
||||
case AccountStatus.NoConnection:
|
||||
{
|
||||
NavigateTo(new ConnectServerViewModel(HostScreen));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
SendNotification("", registerResult.ToString(), NotificationType.Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SendNotification("", LocalizationProvider.Instance.login_failed, NotificationType.Error);
|
||||
|
||||
break;
|
||||
}
|
||||
case AccountStatus.NoConnection:
|
||||
{
|
||||
NavigateTo(new ConnectServerViewModel(HostScreen));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//cache and touch background image
|
||||
var backgroundImage = Locator.Current.GetService<ImageHelper>("bgimage");
|
||||
|
||||
ImageRequest.CacheBackgroundImage();
|
||||
|
||||
backgroundImage.Touch();
|
||||
|
||||
//handle auto-login
|
||||
if (LauncherSettingsProvider.Instance.UseAutoLogin && LauncherSettingsProvider.Instance.Server.AutoLoginCreds != null && !NoAutoLogin)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Login = LauncherSettingsProvider.Instance.Server.AutoLoginCreds;
|
||||
LoginCommand.Execute();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
GetExistingProfiles();
|
||||
});
|
||||
}
|
||||
|
||||
public void LoginProfileCommand(object parameter)
|
||||
{
|
||||
if (parameter == null) return;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (parameter is string username)
|
||||
{
|
||||
Login.Username = username;
|
||||
LoginCommand.Execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void GetExistingProfiles()
|
||||
{
|
||||
ServerProfileInfo[] existingProfiles = AccountManager.GetExistingProfiles();
|
||||
|
||||
if(existingProfiles != null)
|
||||
{
|
||||
ExistingProfiles.Clear();
|
||||
|
||||
foreach(ServerProfileInfo profile in existingProfiles)
|
||||
{
|
||||
ProfileInfo profileInfo = new ProfileInfo(profile);
|
||||
|
||||
ExistingProfiles.Add(profileInfo);
|
||||
|
||||
ImageRequest.CacheSideImage(profileInfo.Side);
|
||||
|
||||
ImageHelper sideImage = new ImageHelper() { Path = profileInfo.SideImage };
|
||||
sideImage.Touch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
project/Aki.Launcher/ViewModels/MainWindowViewModel.cs
Normal file
61
project/Aki.Launcher/ViewModels/MainWindowViewModel.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using Avalonia;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
using Aki.Launcher.Models;
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using System.IO;
|
||||
using Splat;
|
||||
using Aki.Launch.Models.Aki;
|
||||
using Aki.Launcher.Helpers;
|
||||
|
||||
namespace Aki.Launcher.ViewModels
|
||||
{
|
||||
public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScreen
|
||||
{
|
||||
public AkiVersion VersionInfo { get; set; } = new AkiVersion();
|
||||
public RoutingState Router { get; } = new RoutingState();
|
||||
public ViewModelActivator Activator { get; } = new ViewModelActivator();
|
||||
|
||||
public ImageHelper Background { get; } = new ImageHelper()
|
||||
{
|
||||
Path = Path.Join(ImageRequest.ImageCacheFolder, "bg.png")
|
||||
};
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.RegisterConstant<ImageHelper>(Background, "bgimage");
|
||||
|
||||
Locator.CurrentMutable.RegisterConstant<AkiVersion>(VersionInfo, "akiversion");
|
||||
|
||||
LauncherSettingsProvider.Instance.AllowSettings = true;
|
||||
|
||||
this.WhenActivated((CompositeDisposable disposables) =>
|
||||
{
|
||||
Router.Navigate.Execute(new ConnectServerViewModel(this));
|
||||
});
|
||||
}
|
||||
|
||||
public void CloseCommand()
|
||||
{
|
||||
if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
{
|
||||
desktopApp.MainWindow.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void MinimizeCommand()
|
||||
{
|
||||
if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
{
|
||||
desktopApp.MainWindow.WindowState = Avalonia.Controls.WindowState.Minimized;
|
||||
}
|
||||
}
|
||||
|
||||
public void GoToSettingsCommand()
|
||||
{
|
||||
LauncherSettingsProvider.Instance.AllowSettings = false;
|
||||
|
||||
Router.Navigate.Execute(new SettingsViewModel(this));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
using Avalonia.Controls.Notifications;
|
||||
using Avalonia.Media;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Aki.Launcher.ViewModels.Notifications
|
||||
{
|
||||
public class AkiNotificationViewModel : ViewModelBase
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Message { get; set; }
|
||||
public IBrush BarColor { get; set; }
|
||||
|
||||
public AkiNotificationViewModel(IScreen Host, string Title, string Message, NotificationType Type = NotificationType.Information) : base(Host)
|
||||
{
|
||||
this.Title = Title;
|
||||
this.Message = Message;
|
||||
|
||||
switch(Type)
|
||||
{
|
||||
case NotificationType.Information:
|
||||
{
|
||||
BarColor = new SolidColorBrush(Colors.DodgerBlue);
|
||||
break;
|
||||
}
|
||||
case NotificationType.Warning:
|
||||
{
|
||||
BarColor = new SolidColorBrush(Colors.Gold);
|
||||
break;
|
||||
}
|
||||
case NotificationType.Success:
|
||||
{
|
||||
BarColor = new SolidColorBrush(Colors.ForestGreen);
|
||||
break;
|
||||
}
|
||||
case NotificationType.Error:
|
||||
{
|
||||
BarColor = new SolidColorBrush(Colors.IndianRed);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
BarColor = new SolidColorBrush(Colors.Gray);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
282
project/Aki.Launcher/ViewModels/ProfileViewModel.cs
Normal file
282
project/Aki.Launcher/ViewModels/ProfileViewModel.cs
Normal file
@ -0,0 +1,282 @@
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.MiniCommon;
|
||||
using Aki.Launcher.Models;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using Avalonia;
|
||||
using ReactiveUI;
|
||||
using System.Threading.Tasks;
|
||||
using Aki.Launcher.Attributes;
|
||||
using Aki.Launcher.ViewModels.Dialogs;
|
||||
using Avalonia.Threading;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Aki.Launcher.ViewModels
|
||||
{
|
||||
[RequireLoggedIn]
|
||||
public class ProfileViewModel : ViewModelBase
|
||||
{
|
||||
public string CurrentUsername { get; set; }
|
||||
|
||||
private string _CurrentEdition;
|
||||
public string CurrentEdition
|
||||
{
|
||||
get => _CurrentEdition;
|
||||
set => this.RaiseAndSetIfChanged(ref _CurrentEdition, value);
|
||||
}
|
||||
|
||||
public string CurrentID { get; set; }
|
||||
|
||||
public ProfileInfo ProfileInfo { get; set; } = AccountManager.SelectedProfileInfo;
|
||||
|
||||
public ImageHelper SideImage { get; } = new ImageHelper();
|
||||
|
||||
private GameStarter gameStarter = new GameStarter(new GameStarterFrontend());
|
||||
|
||||
private ProcessMonitor monitor { get; set; }
|
||||
|
||||
public ProfileViewModel(IScreen Host) : base(Host)
|
||||
{
|
||||
this.WhenActivated((CompositeDisposable disposables) =>
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
GameVersionCheck();
|
||||
});
|
||||
});
|
||||
|
||||
// cache and load side image if profile has a side
|
||||
if(AccountManager.SelectedProfileInfo != null && AccountManager.SelectedProfileInfo.Side != null)
|
||||
{
|
||||
ImageRequest.CacheSideImage(AccountManager.SelectedProfileInfo.Side);
|
||||
SideImage.Path = AccountManager.SelectedProfileInfo.SideImage;
|
||||
SideImage.Touch();
|
||||
}
|
||||
|
||||
monitor = new ProcessMonitor("EscapeFromTarkov", 1000, aliveCallback: GameAliveCallBack, exitCallback: GameExitCallback);
|
||||
|
||||
CurrentUsername = AccountManager.SelectedAccount.username;
|
||||
|
||||
CurrentEdition = AccountManager.SelectedAccount.edition;
|
||||
|
||||
CurrentID = AccountManager.SelectedAccount.id;
|
||||
}
|
||||
|
||||
private async Task GameVersionCheck()
|
||||
{
|
||||
string compatibleGameVersion = ServerManager.GetCompatibleGameVersion();
|
||||
|
||||
if (compatibleGameVersion == "") return;
|
||||
|
||||
// get the product version of the exe
|
||||
string gameVersion = FileVersionInfo.GetVersionInfo(Path.Join(LauncherSettingsProvider.Instance.GamePath, "EscapeFromTarkov.exe")).FileVersion;
|
||||
|
||||
if (gameVersion == null) return;
|
||||
|
||||
// if the compatible version isn't the same as the game version show a warning dialog
|
||||
if(compatibleGameVersion != gameVersion)
|
||||
{
|
||||
WarningDialogViewModel warning = new WarningDialogViewModel(null,
|
||||
string.Format(LocalizationProvider.Instance.game_version_mismatch_format_2, gameVersion, compatibleGameVersion),
|
||||
LocalizationProvider.Instance.i_understand);
|
||||
Dispatcher.UIThread.InvokeAsync(async() =>
|
||||
{
|
||||
await ShowDialog(warning);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void LogoutCommand()
|
||||
{
|
||||
AccountManager.Logout();
|
||||
|
||||
NavigateTo(new ConnectServerViewModel(HostScreen, true));
|
||||
}
|
||||
|
||||
public void ChangeWindowState(Avalonia.Controls.WindowState? State, bool Close = false)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (Close)
|
||||
{
|
||||
desktop.ShutdownMode = Avalonia.Controls.ShutdownMode.OnMainWindowClose;
|
||||
desktop.Shutdown();
|
||||
}
|
||||
else
|
||||
{
|
||||
desktop.MainWindow.WindowState = State ?? Avalonia.Controls.WindowState.Normal;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task StartGameCommand()
|
||||
{
|
||||
LauncherSettingsProvider.Instance.AllowSettings = false;
|
||||
|
||||
AccountStatus status = await AccountManager.LoginAsync(AccountManager.SelectedAccount.username, AccountManager.SelectedAccount.password);
|
||||
|
||||
LauncherSettingsProvider.Instance.AllowSettings = true;
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case AccountStatus.NoConnection:
|
||||
NavigateTo(new ConnectServerViewModel(HostScreen));
|
||||
return;
|
||||
}
|
||||
|
||||
LauncherSettingsProvider.Instance.GameRunning = true;
|
||||
|
||||
GameStarterResult gameStartResult = await gameStarter.LaunchGame(ServerManager.SelectedServer, AccountManager.SelectedAccount);
|
||||
|
||||
if (gameStartResult.Succeeded)
|
||||
{
|
||||
monitor.Start();
|
||||
|
||||
switch (LauncherSettingsProvider.Instance.LauncherStartGameAction)
|
||||
{
|
||||
case LauncherAction.MinimizeAction:
|
||||
{
|
||||
ChangeWindowState(Avalonia.Controls.WindowState.Minimized);
|
||||
break;
|
||||
}
|
||||
case LauncherAction.ExitAction:
|
||||
{
|
||||
ChangeWindowState(null, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SendNotification("", gameStartResult.Message, Avalonia.Controls.Notifications.NotificationType.Error);
|
||||
LauncherSettingsProvider.Instance.GameRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ChangeEditionCommand()
|
||||
{
|
||||
var result = await ShowDialog(new ChangeEditionDialogViewModel(null));
|
||||
|
||||
if(result != null && result is string edition)
|
||||
{
|
||||
AccountStatus status = await AccountManager.WipeAsync(edition);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case AccountStatus.OK:
|
||||
{
|
||||
CurrentEdition = AccountManager.SelectedAccount.edition;
|
||||
SendNotification("", LocalizationProvider.Instance.account_updated);
|
||||
break;
|
||||
}
|
||||
case AccountStatus.NoConnection:
|
||||
{
|
||||
NavigateTo(new ConnectServerViewModel(HostScreen));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.edit_account_update_error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CopyCommand(object parameter)
|
||||
{
|
||||
if (Application.Current.Clipboard != null && parameter != null && parameter is string text)
|
||||
{
|
||||
await Application.Current.Clipboard.SetTextAsync(text);
|
||||
SendNotification("", $"{text} {LocalizationProvider.Instance.copied}", Avalonia.Controls.Notifications.NotificationType.Success);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveProfileCommand()
|
||||
{
|
||||
ConfirmationDialogViewModel confirmation = new ConfirmationDialogViewModel(null, string.Format(LocalizationProvider.Instance.profile_remove_question_format_1, AccountManager.SelectedAccount.username));
|
||||
|
||||
var result = await ShowDialog(confirmation);
|
||||
|
||||
if (result is bool b && !b) return;
|
||||
|
||||
AccountStatus status = await AccountManager.RemoveAsync();
|
||||
|
||||
switch(status)
|
||||
{
|
||||
case AccountStatus.OK:
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.profile_removed);
|
||||
|
||||
LauncherSettingsProvider.Instance.Server.AutoLoginCreds = null;
|
||||
|
||||
LauncherSettingsProvider.Instance.SaveSettings();
|
||||
|
||||
NavigateTo(new ConnectServerViewModel(HostScreen));
|
||||
break;
|
||||
}
|
||||
case AccountStatus.UpdateFailed:
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.profile_removal_failed);
|
||||
break;
|
||||
}
|
||||
case AccountStatus.NoConnection:
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.no_servers_available);
|
||||
NavigateTo(new ConnectServerViewModel(HostScreen));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProfileInfo()
|
||||
{
|
||||
AccountManager.UpdateProfileInfo();
|
||||
ImageRequest.CacheSideImage(AccountManager.SelectedProfileInfo.Side);
|
||||
ProfileInfo.UpdateDisplayedProfile(AccountManager.SelectedProfileInfo);
|
||||
if (ProfileInfo.SideImage != SideImage.Path)
|
||||
{
|
||||
SideImage.Path = ProfileInfo.SideImage;
|
||||
SideImage.Touch();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//pull profile every x seconds
|
||||
private int aliveCallBackCountdown = 60;
|
||||
private void GameAliveCallBack(ProcessMonitor monitor)
|
||||
{
|
||||
aliveCallBackCountdown--;
|
||||
|
||||
if (aliveCallBackCountdown <= 0)
|
||||
{
|
||||
aliveCallBackCountdown = 60;
|
||||
UpdateProfileInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private void GameExitCallback(ProcessMonitor monitor)
|
||||
{
|
||||
monitor.Stop();
|
||||
|
||||
LauncherSettingsProvider.Instance.GameRunning = false;
|
||||
|
||||
//Make sure the call to MainWindow happens on the UI thread.
|
||||
switch (LauncherSettingsProvider.Instance.LauncherStartGameAction)
|
||||
{
|
||||
case LauncherAction.MinimizeAction:
|
||||
{
|
||||
ChangeWindowState(Avalonia.Controls.WindowState.Normal);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateProfileInfo();
|
||||
}
|
||||
}
|
||||
}
|
181
project/Aki.Launcher/ViewModels/SettingsViewModel.cs
Normal file
181
project/Aki.Launcher/ViewModels/SettingsViewModel.cs
Normal file
@ -0,0 +1,181 @@
|
||||
using Aki.Launcher.Controllers;
|
||||
using Aki.Launcher.Helpers;
|
||||
using Aki.Launcher.Models;
|
||||
using Aki.Launcher.Models.Launcher;
|
||||
using Aki.Launcher.ViewModels.Dialogs;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.Launcher.ViewModels
|
||||
{
|
||||
public class SettingsViewModel : ViewModelBase
|
||||
{
|
||||
public LocaleCollection Locales { get; set; } = new LocaleCollection();
|
||||
|
||||
private GameStarter gameStarter = new GameStarter(new GameStarterFrontend());
|
||||
|
||||
public SettingsViewModel(IScreen Host) : base(Host)
|
||||
{
|
||||
if(Application.Current?.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow.Closing += MainWindow_Closing;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
LauncherSettingsProvider.Instance.SaveSettings();
|
||||
}
|
||||
|
||||
public void GoBackCommand()
|
||||
{
|
||||
if (Application.Current?.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow.Closing -= MainWindow_Closing;
|
||||
}
|
||||
|
||||
LauncherSettingsProvider.Instance.AllowSettings = true;
|
||||
LauncherSettingsProvider.Instance.SaveSettings();
|
||||
|
||||
NavigateBack();
|
||||
}
|
||||
|
||||
public void CleanTempFilesCommand()
|
||||
{
|
||||
bool filesCleared = gameStarter.CleanTempFiles();
|
||||
|
||||
if (filesCleared)
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.clean_temp_files_succeeded, Avalonia.Controls.Notifications.NotificationType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.clean_temp_files_failed, Avalonia.Controls.Notifications.NotificationType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRegistryKeysCommand()
|
||||
{
|
||||
bool regKeysRemoved = gameStarter.RemoveRegistryKeys();
|
||||
|
||||
if (regKeysRemoved)
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.remove_registry_keys_succeeded, Avalonia.Controls.Notifications.NotificationType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.remove_registry_keys_failed, Avalonia.Controls.Notifications.NotificationType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearGameSettingsCommand()
|
||||
{
|
||||
|
||||
bool BackupAndRemove(string backupFolderPath, FileInfo file)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Refresh();
|
||||
|
||||
//if for some reason the file no longer exists /shrug
|
||||
if (!file.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//create backup dir and copy file
|
||||
Directory.CreateDirectory(backupFolderPath);
|
||||
|
||||
string newFilePath = Path.Combine(backupFolderPath, $"{file.Name}_{DateTime.Now.ToString("MM-dd-yyyy_hh-mm-ss-tt")}.bak");
|
||||
|
||||
File.Copy(file.FullName, newFilePath);
|
||||
|
||||
//copy check
|
||||
if (!File.Exists(newFilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//delete old file
|
||||
file.Delete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Instance.Exception(ex);
|
||||
}
|
||||
|
||||
//delete check
|
||||
if (file.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string EFTSettingsFolder = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Escape from Tarkov");
|
||||
string backupFolderPath = Path.Combine(EFTSettingsFolder, "Backups");
|
||||
|
||||
if (Directory.Exists(EFTSettingsFolder))
|
||||
{
|
||||
FileInfo local_ini = new FileInfo(Path.Combine(EFTSettingsFolder, "local.ini"));
|
||||
FileInfo shared_ini = new FileInfo(Path.Combine(EFTSettingsFolder, "shared.ini"));
|
||||
|
||||
string Message = string.Format(LocalizationProvider.Instance.clear_game_settings_warning, backupFolderPath);
|
||||
ConfirmationDialogViewModel confirmDelete = new ConfirmationDialogViewModel(null, Message, LocalizationProvider.Instance.clear_game_settings, LocalizationProvider.Instance.cancel);
|
||||
|
||||
var confirmation = await ShowDialog(confirmDelete);
|
||||
|
||||
if (confirmation is bool proceed && !proceed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool localSucceeded = BackupAndRemove(backupFolderPath, local_ini);
|
||||
bool sharedSucceeded = BackupAndRemove(backupFolderPath, shared_ini);
|
||||
|
||||
//if one fails, I'm considering it bad. Send failed notification.
|
||||
if (!localSucceeded || !sharedSucceeded)
|
||||
{
|
||||
SendNotification("", LocalizationProvider.Instance.clear_game_settings_failed, Avalonia.Controls.Notifications.NotificationType.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SendNotification("", LocalizationProvider.Instance.clear_game_settings_succeeded, Avalonia.Controls.Notifications.NotificationType.Success);
|
||||
}
|
||||
|
||||
public void OpenGameFolderCommand()
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = Path.EndsInDirectorySeparator(LauncherSettingsProvider.Instance.GamePath) ? LauncherSettingsProvider.Instance.GamePath : LauncherSettingsProvider.Instance.GamePath + Path.DirectorySeparatorChar,
|
||||
UseShellExecute = true,
|
||||
Verb = "open"
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SelectGameFolderCommand()
|
||||
{
|
||||
OpenFolderDialog dialog = new OpenFolderDialog();
|
||||
|
||||
dialog.Directory = Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
if (Application.Current?.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
string? result = await dialog.ShowAsync(desktop.MainWindow);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
LauncherSettingsProvider.Instance.GamePath = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user