上传文件至 PUBG2017PSLauncher

This commit is contained in:
才羽青空 2025-09-17 22:27:27 +00:00
parent d246c1ff86
commit 5cf123a8f2
5 changed files with 1089 additions and 0 deletions

View file

@ -0,0 +1,43 @@
using System.Reflection;
using System.Windows;
using System.Drawing;
namespace PUBG2017PSLauncher
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : System.Windows.Application
{
static NotifyIcon trayIcon;
protected override void OnStartup(StartupEventArgs e)
{
trayIcon = new NotifyIcon()
{
Text = "OG:BG 启动器",
Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().ManifestModule.Name),
ContextMenuStrip = new ContextMenuStrip()
{
Items = {new ToolStripMenuItem("退出启动器", null, ExitFromTray)}
},
Visible = true
};
trayIcon.Click += TrayLeftClicked;
base.OnStartup(e);
}
void ExitFromTray(object sender, EventArgs e)
{
Current.Shutdown();
}
void TrayLeftClicked(object sender, EventArgs e)
{
MainWindow.Visibility = Visibility.Visible;
MainWindow.ShowInTaskbar = true;
}
}
}

View file

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View file

@ -0,0 +1,146 @@
<Window x:Class="PUBG2017PSLauncher.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PUBG2017PSLauncher"
mc:Ignorable="d"
Title="OG:BG 启动器" Height="720" Width="1168" WindowStartupLocation="CenterScreen" WindowStyle="None" AllowsTransparency="True"
Background="Transparent" Closing="windowIsClosing" Visibility="Collapsed" ShowInTaskbar="False">
<Border x:Name="mainWnd" CornerRadius="10" BorderBrush="#6e7783" BorderThickness="1">
<!--Main Grid-->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!--Title Bar-->
<StackPanel Grid.ColumnSpan="2" Orientation="Horizontal">
<Border x:Name="Border" Background="#212121" CornerRadius="10 17 0 0" Width="1168" MouseDown="Border_MouseDown"/>
</StackPanel>
<Button Grid.Column="2" Style="{StaticResource closeButton}" Click="closeBtnClick">
<Icon:PackIconMaterial Style="{StaticResource windowBtnIcon}" Kind="Close"/>
</Button>
<Button Grid.Column="2" Style="{StaticResource minimizeButton}" Margin="40 0" Click="minimizeBtnClick">
<Icon:PackIconMaterial Style="{StaticResource windowBtnIcon}" Kind="Minus"/>
</Button>
<StackPanel Orientation="Horizontal">
<Image Source="pack://application:,,,/Images/icon.png" Width="16" Height="16" Margin="10 0" HorizontalAlignment="Left"/>
<TextBlock Text="OG:BG 启动器" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="-2"/>
</StackPanel>
<!--Page Selection-->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="1" Background="#171717" CornerRadius="0 0 0 10">
<DockPanel>
<StackPanel DockPanel.Dock="Top">
<Button x:Name="mainBtn" Margin="15" Style="{StaticResource menuButton}" Background="#212121" Foreground="White" Click="playBtnClick">
<StackPanel Orientation="Horizontal">
<Icon:PackIconMaterial Kind="Controller" Style="{StaticResource menuButtonIcon}"/>
<TextBlock FontSize="14" VerticalAlignment="Center" Text="主页"/>
</StackPanel>
</Button>
<Button x:Name="settingsBtn" Margin="15 -5" Style="{StaticResource menuButton}" Background="Transparent" Foreground="#6e7783" Click="settingsBtnClick">
<StackPanel Orientation="Horizontal">
<Icon:PackIconMaterial Kind="Cog" Style="{StaticResource menuButtonIcon}"/>
<TextBlock FontSize="14" VerticalAlignment="Center" Text="设置"/>
</StackPanel>
</Button>
</StackPanel>
</DockPanel>
</Border>
</Grid>
<!--Main Page-->
<Grid x:Name="mainMenu" Grid.Column="2" Grid.Row="2" Visibility="Visible">
<Border Grid.Column="2" Grid.Row="2" VerticalAlignment="Top" Height="590">
<Border.Background>
<ImageBrush Stretch="Fill" ImageSource="pack://application:,,,/Images/back.png"/>
</Border.Background>
</Border>
<StackPanel Margin="35 25">
<TextBlock Text="OG:BG" FontFamily="./fonts/#PUBG SANS" Foreground="White" FontSize="72" FontWeight="Bold"/>
<TextBlock Text="最新内容:" Foreground="White" FontSize="24"/>
<TextBlock x:Name="whatsNewText" Text="Loading..." Foreground="#FF98A5B7" Margin="0 10" FontSize="16"/>
</StackPanel>
<Border Grid.Column="2" Grid.Row="2" CornerRadius="0 0 10 0" Background="#212121" Height="100" VerticalAlignment="Bottom">
<DockPanel>
<Button x:Name="installBtn" Style="{StaticResource installButton}" DockPanel.Dock="Left" Margin="30 0" Click="installBtnClick">
<TextBlock x:Name="installBtnText" FontFamily="Microsoft Yi Baiti" HorizontalAlignment="Center" Text="安装游戏" Margin="0 5" FontSize="32" FontWeight="Bold"/>
</Button>
<StackPanel x:Name="lastPlayed" Orientation="Vertical" Margin="50 0">
<TextBlock Text="上次游玩" Foreground="#996E7783" FontSize="22" HorizontalAlignment="Center" FontWeight="Bold" Margin="15 23"/>
<TextBlock x:Name="lastPlayedText" Text="12/24/2024 3:59 AM" Foreground="#996E7783" FontSize="14" Margin="15 -20" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel x:Name="miscButtons" Margin="37 0" Orientation="Horizontal" DockPanel.Dock="Right">
<Button x:Name="repairGameBtn" ToolTip="修复游戏" Style="{StaticResource miscButton}" Margin="25" IsEnabled="False" Click="repairGame">
<Icon:PackIconMaterial Kind="Wrench" Width="20" Height="20"/>
</Button>
<Button x:Name="uninstallGameBtn" ToolTip="卸载游戏" Style="{StaticResource miscButton}" Margin="-10" IsEnabled="False" Click="uninstallGame">
<Icon:PackIconMaterial Kind="Delete" Width="20" Height="20"/>
</Button>
</StackPanel>
<StackPanel x:Name="playtime" Orientation="Vertical" DockPanel.Dock="Right" Margin="25 0">
<TextBlock Text="游玩时间" Foreground="#996E7783" FontSize="22" HorizontalAlignment="Center" FontWeight="Bold" Margin="15 23"/>
<TextBlock x:Name="playtimeText" Text="0.0 hrs" Foreground="#996E7783" FontSize="14" Margin="15 -20" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel x:Name="downloadBar" Visibility="Collapsed" Orientation="Vertical" VerticalAlignment="Center" Margin="0 5 0 0">
<ProgressBar x:Name="downloadProgress" Style="{DynamicResource ProgressBarStyle1}" Height="5" Width="630" HorizontalAlignment="Left"/>
<TextBlock x:Name="downloadText" Text="下载中" Foreground="#6e7783" Margin="0 5 0 0"/>
</StackPanel>
</DockPanel>
</Border>
</Grid>
<!--Settings Page-->
<Grid x:Name="settingsMenu" Grid.Column="2" Grid.Row="2" Visibility="Collapsed">
<Border Background="#1A1A1A" CornerRadius="0 0 10 0"/>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Margin="25">
<TextBlock Text="安装目录" Foreground="White"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="" Foreground="#6e7783" x:Name="gamePathTextBox" Width="auto" MaxWidth="700" HorizontalAlignment="Left" Margin="0 10"/>
<Button x:Name="gameInstallPathBtn" Style="{StaticResource selectPathButton}" Margin="10 0" Click="gameInstallBtnClick">
<Icon:PackIconMaterial Kind="DotsHorizontal"/>
</Button>
</StackPanel>
<CheckBox x:Name="startOnBootCheck" Style="{StaticResource CheckBoxStyle}" Margin="0 10" Checked="startOnBootChecked" Unchecked="startOnBootUnchecked">
<TextBlock Text="电脑启动时自动运行启动器"/>
</CheckBox>
<CheckBox x:Name="startMinimizedCheck" Style="{StaticResource CheckBoxStyle}" Margin="0 5">
<TextBlock Text="启动游戏时最小化启动器"/>
</CheckBox>
</StackPanel>
</DockPanel>
</Grid>
</Grid>
</Border>
</Window>

View file

@ -0,0 +1,850 @@
using System.ComponentModel;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Windows;
using System.Diagnostics;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using System.Security.Cryptography;
using Microsoft.Win32;
//all of this code was written in 3 days, so expect it to be all over the fucking place lmao
namespace PUBG2017PSLauncher
{
public partial class MainWindow : Window
{
static readonly HttpClient client = new HttpClient();
static CancellationTokenSource cancellationToken = new CancellationTokenSource();
static List<FileHash> gameHashes = new List<FileHash>();
static List<FileHash> localGameHashes = new List<FileHash>();
static List<FileHash> filesToUpdate = new List<FileHash>();
static List<Folder> gameFolders = new List<Folder>();
static Config config = new Config() { gameInstall = AppContext.BaseDirectory+"GameInstall", startOnBoot = false, startMinimized = false, gameIsInstalled = false, lastPlayed = DateTime.Now, playtime = 0, webServerIp = "https://w3.dev.tlogin.zxwmr.top/resource/files/game/" };
static GameInfo gameInfo = new GameInfo();
static LauncherInfo launcherInfo = new LauncherInfo();
static Process? pubgProcess = null;
static DispatcherTimer playtimeTimer = new DispatcherTimer();
static DispatcherTimer updateCheckTimer = new DispatcherTimer();
static readonly string configPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\OGBG\\config.json";
bool inMainPage = true;
bool justLaunched = true;
bool downloadInProgress = false;
bool updateAvailable = false;
bool gameIsRunning = false;
bool repairInProgress = false;
static long totalDownloadSize;
static long downloadedAmount;
static double downloadSpeed;
const int launcherVersion = 3;
public MainWindow()
{
InitializeComponent();
if (!Directory.Exists(Path.GetDirectoryName(configPath))) Directory.CreateDirectory(Path.GetDirectoryName(configPath));
if (!Directory.Exists(AppContext.BaseDirectory + "\\Temp")) Directory.CreateDirectory(AppContext.BaseDirectory + "\\Temp");
if (!File.Exists(configPath))
{
File.WriteAllText(configPath, JsonSerializer.Serialize(config));
LoadConfig();
}
else LoadConfig();
if (string.IsNullOrEmpty(config.webServerIp))
{
config.webServerIp = "https://w3.dev.tlogin.zxwmr.top/resource/files/game/";
}
if (!config.startMinimized)
{
Visibility = Visibility.Visible;
ShowInTaskbar = true;
}
playtimeText.Text = string.Format("{0:F1}", config.playtime/3600f) + " 小时";
lastPlayedText.Text = config.lastPlayed.ToString();
playtimeTimer.Interval = TimeSpan.FromSeconds(1);
playtimeTimer.Tick += playtimeTimerTick;
updateCheckTimer.Interval = TimeSpan.FromMinutes(2);
updateCheckTimer.Tick += updateCheckTimerTick;
updateCheckTimer.Start();
if (config.gameIsInstalled && !Directory.EnumerateFileSystemEntries(config.gameInstall).Any())
{
config.gameIsInstalled = false;
}
if(config.gameIsInstalled)
{
repairGameBtn.IsEnabled = true;
uninstallGameBtn.IsEnabled = true;
}
UpdateInstallButton();
if (!Directory.Exists(config.gameInstall)) Directory.CreateDirectory(config.gameInstall);
if (System.Windows.Application.Current.MainWindow.IsInitialized && justLaunched)
{
try
{
gameInfo = JsonSerializer.Deserialize<GameInfo>(Get($"{config.webServerIp}json/gameinfo.json").Result);
gameHashes = JsonSerializer.Deserialize<List<FileHash>>(Get($"{config.webServerIp}json/hashes.json").Result);
gameFolders = JsonSerializer.Deserialize<List<Folder>>(Get($"{config.webServerIp}json/folders.json").Result);
launcherInfo = JsonSerializer.Deserialize<LauncherInfo>(Get($"{config.webServerIp}json/launcherinfo.json").Result);
}
catch(Exception ex)
{
ThrowExceptionMessage(ex);
}
CheckForLauncherUpdate();
whatsNewText.Text = gameInfo.whatsNew;
justLaunched = false;
}
if (config.gameIsInstalled && File.Exists(config.gameInstall + "\\hashes.json"))
{
CheckForGameUpdate();
}
}
private void updateCheckTimerTick(object? sender, EventArgs e)
{
CheckForGameUpdate();
}
void UpdatePage()
{
if(inMainPage)
{
mainMenu.Visibility = Visibility.Visible;
settingsMenu.Visibility = Visibility.Collapsed;
mainBtn.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(33, 33, 33));
mainBtn.Foreground = new SolidColorBrush(Colors.White);
settingsBtn.Background = new SolidColorBrush(Colors.Transparent);
settingsBtn.Foreground = new SolidColorBrush(System.Windows.Media.Color.FromRgb(110, 119, 131));
}
else
{
mainMenu.Visibility = Visibility.Collapsed;
settingsMenu.Visibility = Visibility.Visible;
mainBtn.Background = new SolidColorBrush(Colors.Transparent);
mainBtn.Foreground = new SolidColorBrush(System.Windows.Media.Color.FromRgb(110, 119, 131));
settingsBtn.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(33, 33, 33));
settingsBtn.Foreground = new SolidColorBrush(Colors.White);
}
}
void LoadConfig()
{
config = JsonSerializer.Deserialize<Config>(File.ReadAllText(configPath));
gamePathTextBox.Text = config?.gameInstall;
startOnBootCheck.IsChecked = config?.startOnBoot;
startMinimizedCheck.IsChecked = config?.startMinimized;
}
void SaveConfig()
{
config.gameInstall = gamePathTextBox.Text;
config.startOnBoot = (bool)startOnBootCheck.IsChecked;
config.startMinimized = (bool)startMinimizedCheck.IsChecked;
File.WriteAllText(configPath, JsonSerializer.Serialize(config));
}
private static async Task<string> Get(string url)
{
try
{
var resp = await client.GetAsync(url).ConfigureAwait(false);
var content = await resp.Content.ReadAsStringAsync();
// === 调试:打印返回内容 ===
Debug.WriteLine($"[DEBUG] {url}");
Debug.WriteLine($"[DEBUG] RESP: {content}");
// ============================
return resp.IsSuccessStatusCode ? content : string.Empty;
}
catch
{
System.Windows.MessageBox.Show("无法连接服务器,请检查网络。", "OG:BG 启动器", MessageBoxButton.OK, MessageBoxImage.Error);
return string.Empty;
}
}
private void Border_MouseDown(object sender, MouseButtonEventArgs e)
{
if(e.LeftButton == MouseButtonState.Pressed)
{
this.DragMove();
}
}
private void closeBtnClick(object sender, RoutedEventArgs e)
{
System.Windows.Application.Current.Shutdown();
}
private void minimizeBtnClick(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private async void installBtnClick(object sender, RoutedEventArgs e)
{
if(!config.gameIsInstalled && !downloadInProgress)
{
downloadInProgress = true;
updateCheckTimer.Stop();
gameInstallPathBtn.IsEnabled = false;
UpdateInstallButton();
if (!Directory.Exists(config.gameInstall)) Directory.CreateDirectory(config.gameInstall);
if (!Directory.Exists(AppContext.BaseDirectory + "\\Temp")) Directory.CreateDirectory(AppContext.BaseDirectory + "\\Temp");
File.WriteAllText(config.gameInstall + "\\hashes.json", JsonSerializer.Serialize(gameHashes));
for(int i = 0; i < gameFolders.Count; i++)
{
if(!Directory.Exists(config.gameInstall+"\\"+gameFolders[i].path)) Directory.CreateDirectory(config.gameInstall + "\\" + gameFolders[i].path);
}
totalDownloadSize = 0;
GetDownloadSize(gameHashes);
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timerTick;
timer.Start();
ToggleDownloadBarVisibility(true);
for (int i = 0; i < gameHashes.Count; i++)
{
if (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine(gameHashes[i].path);
await DownloadFileAsync($"{config.webServerIp}files/{gameHashes[i].path.Replace("\\", "/")}", config.gameInstall + "\\" + gameHashes[i].path, cancellationToken.Token);
}
else
{
timer.Stop();
updateCheckTimer.Start();
ToggleDownloadBarVisibility(false);
downloadedAmount = 0;
totalDownloadSize = 0;
downloadSpeed = 0;
downloadInProgress = false;
gameInstallPathBtn.IsEnabled = true;
UpdateInstallButton();
Directory.Delete(config.gameInstall, true);
cancellationToken = new CancellationTokenSource();
return;
}
}
ToggleDownloadBarVisibility(false);
updateCheckTimer.Start();
config.gameIsInstalled = true;
uninstallGameBtn.IsEnabled = true;
repairGameBtn.IsEnabled = true;
gameInstallPathBtn.IsEnabled = true;
downloadInProgress = false;
UpdateInstallButton();
}
if(downloadInProgress)
{
cancellationToken.Cancel();
}
if (config.gameIsInstalled && !updateAvailable && !gameIsRunning)
{
try
{
pubgProcess = new Process();
pubgProcess.StartInfo.FileName = config.gameInstall + "\\" + gameInfo.startupExecutable;
pubgProcess.StartInfo.Arguments = gameInfo.startupArgs;
pubgProcess.Start();
gameIsRunning = true;
pubgProcess.EnableRaisingEvents = true;
pubgProcess.Exited += PubgProcessEnded;
playtimeTimer.Start();
}
catch(Exception ex)
{
ThrowExceptionMessage(ex, "游戏已崩溃!这通常可能由于你修改了游戏的某些文件,建议尝试修复游戏文件重试!");
}
config.lastPlayed = DateTime.Now;
lastPlayedText.Text = DateTime.Now.ToString();
uninstallGameBtn.IsEnabled = false;
repairGameBtn.IsEnabled = false;
gameInstallPathBtn.IsEnabled = false;
UpdateInstallButton();
return;
}
if (gameIsRunning && !pubgProcess.HasExited)
{
DialogResult result = System.Windows.Forms.MessageBox.Show("你确定吗?", "OG:BG 启动器", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if(result == System.Windows.Forms.DialogResult.Yes)
{
try
{
pubgProcess.Kill();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
if(updateAvailable & !downloadInProgress)
{
downloadInProgress = true;
uninstallGameBtn.IsEnabled = false;
repairGameBtn.IsEnabled = false;
gameInstallPathBtn.IsEnabled = false;
UpdateInstallButton();
GetDownloadSize(filesToUpdate);
if (!Directory.Exists(AppContext.BaseDirectory + "\\Temp")) Directory.CreateDirectory(AppContext.BaseDirectory + "\\Temp");
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timerTick;
timer.Start();
ToggleDownloadBarVisibility(true);
for (int i = 0; i < filesToUpdate.Count; i++)
{
if(!cancellationToken.IsCancellationRequested)
{
Console.WriteLine($"正在更新: {filesToUpdate[i].path}");
await DownloadFileAsync($"{config.webServerIp}files/{filesToUpdate[i].path.Replace("\\", "/")}", config.gameInstall + "\\" + filesToUpdate[i].path, cancellationToken.Token);
filesToUpdate.Remove(filesToUpdate[i]);
}
else
{
timer.Stop();
ToggleDownloadBarVisibility(false);
downloadedAmount = 0;
totalDownloadSize = 0;
downloadSpeed = 0;
downloadInProgress = false;
uninstallGameBtn.IsEnabled = true;
repairGameBtn.IsEnabled = true;
gameInstallPathBtn.IsEnabled = true;
UpdateInstallButton();
cancellationToken = new CancellationTokenSource();
return;
}
}
timer.Stop();
ToggleDownloadBarVisibility(false);
downloadInProgress = false;
updateAvailable = false;
downloadedAmount = 0;
downloadSpeed = 0;
totalDownloadSize = 0;
uninstallGameBtn.IsEnabled = true;
repairGameBtn.IsEnabled = true;
gameInstallPathBtn.IsEnabled = true;
File.WriteAllText(config.gameInstall + "\\hashes.json", JsonSerializer.Serialize(gameHashes));
UpdateInstallButton();
return;
}
}
private void playtimeTimerTick(object? sender, EventArgs e)
{
config.playtime++;
playtimeText.Text = string.Format("{0:F1}", config.playtime/3600f) + " 小时";
}
//prepare to see a function with the most for loops :)
private async void repairGame(object sender, RoutedEventArgs e)
{
repairInProgress = true;
string[] files = Array.Empty<string>();
try
{
files = Directory.GetFiles(config.gameInstall, "*.*", SearchOption.AllDirectories);
}
catch(Exception ex)
{
ThrowExceptionMessage(ex);
}
List<FileHash> installedFiles = new List<FileHash>();
List<string> installedFilesPaths = new List<string>();
List<FileHash> filesToRepair = new List<FileHash>();
List<FileHash> filesToCheck = new List<FileHash>();
installBtn.IsEnabled = false;
repairGameBtn.IsEnabled = false;
uninstallGameBtn.IsEnabled = false;
UpdateInstallButton();
ToggleDownloadBarVisibility(true);
downloadText.Text = "校验文件中...";
for(int i = 0; i < files.Length; i++)
{
var relativePath = Path.GetRelativePath(config.gameInstall, files[i]);
Console.WriteLine($"获取 MD5 哈希值: {relativePath}");
installedFiles.Add(new FileHash() { path = relativePath, hash = await GetMD5(files[i])});
downloadProgress.Value = (i*1d) / (files.Length*1d) * 100;
}
for(int i = 0; i < installedFiles.Count; i++)
{
if(installedFiles[i].path == "hashes.json")
{
installedFiles.Remove(installedFiles[i]); //fuck you hashes, yall dont belong here >:(
break;
}
}
downloadText.Text = "扫描损坏/丢失文件...";
for (int i = 0; i < installedFiles.Count; i++)
{
installedFilesPaths.Add(installedFiles[i].path);
}
for(int i = 0; i < gameHashes.Count; i++)
{
if(!installedFilesPaths.Contains(gameHashes[i].path))
{
Console.WriteLine($"丢失文件: {gameHashes[i].path}");
filesToRepair.Add(gameHashes[i]);
}
}
filesToCheck = gameHashes;
for(int i = 0; i < filesToRepair.Count; i++)
{
filesToCheck.Remove(filesToRepair[i]);
}
for (int i = 0; i < filesToCheck.Count; i++)
{
if (filesToCheck[i].hash != installedFiles[i].hash)
{
Console.WriteLine($"{filesToCheck[i].path} is borked");
filesToRepair.Add(filesToCheck[i]);
}
}
if(filesToRepair.Count == 0)
{
System.Windows.MessageBox.Show("所有文件均已校验成功!", "OG:BG 启动器", MessageBoxButton.OK, MessageBoxImage.Information);
}
else
{
downloadInProgress = true;
if (!Directory.Exists(AppContext.BaseDirectory + "\\Temp")) Directory.CreateDirectory(AppContext.BaseDirectory + "\\Temp");
GetDownloadSize(filesToRepair);
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timerTick;
timer.Start();
for (int i = 0; i < filesToRepair.Count; i++)
{
await DownloadFileAsync($"{config.webServerIp}files/{filesToRepair[i].path.Replace("\\", "/")}", config.gameInstall + "\\" + filesToRepair[i].path, cancellationToken.Token);
}
timer.Stop();
}
ToggleDownloadBarVisibility(false);
downloadInProgress = false;
repairInProgress = false;
downloadedAmount = 0;
downloadSpeed = 0;
totalDownloadSize = 0;
installBtn.IsEnabled = true;
repairGameBtn.IsEnabled = true;
uninstallGameBtn.IsEnabled = true;
UpdateInstallButton();
}
private void uninstallGame(object sender, RoutedEventArgs e)
{
DialogResult result = System.Windows.Forms.MessageBox.Show("你确定吗?", "OG:BG 启动器", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if(result == System.Windows.Forms.DialogResult.Yes)
{
try
{
Directory.Delete(config.gameInstall, true);
}
catch(Exception ex)
{
ThrowExceptionMessage(ex);
}
config.gameIsInstalled = false;
uninstallGameBtn.IsEnabled = false;
repairGameBtn.IsEnabled = false;
UpdateInstallButton();
}
}
private async void CheckForGameUpdate()
{
gameHashes = JsonSerializer.Deserialize<List<FileHash>>(await Get($"{config.webServerIp}json/hashes.json"));
localGameHashes = JsonSerializer.Deserialize<List<FileHash>>(File.ReadAllText(config.gameInstall + "\\hashes.json"));
for (int i = 0; i < gameHashes.Count; i++)
{
if (File.Exists(config.gameInstall + "\\" + gameHashes[i].path))
{
if (gameHashes[i].hash != localGameHashes[i].hash)
{
filesToUpdate.Add(gameHashes[i]);
}
}
else
{
filesToUpdate.Add(gameHashes[i]);
}
}
if (filesToUpdate.Any())
{
updateAvailable = true;
UpdateInstallButton();
}
}
private async Task DownloadFileAsync(string url, string filename, CancellationToken token)
{
try
{
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
if (File.Exists(AppContext.BaseDirectory + "\\Temp\\" + Path.GetFileName(filename))) File.Delete(AppContext.BaseDirectory + "\\Temp\\" + Path.GetFileName(filename));
if (!response.IsSuccessStatusCode)
{
throw new Exception(string.Format("尝试请求,服务器 HTTP 返回 {0}", response.StatusCode));
}
var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
var canReportProgress = total != -1;
using (var stream = await response.Content.ReadAsStreamAsync())
{
var buffer = new byte[4096];
var isMoreToRead = true;
do
{
var read = await stream.ReadAsync(buffer, 0, buffer.Length, token);
if (read == 0)
{
isMoreToRead = false;
}
else
{
var data = new byte[read];
buffer.ToList().CopyTo(0, data, 0, read);
using (var fs = new FileStream(AppContext.BaseDirectory+"\\Temp\\"+Path.GetFileName(filename), FileMode.Append))
{
await fs.WriteAsync(data, 0, data.Length);
}
downloadedAmount += read;
downloadSpeed += read;
if (canReportProgress)
{
//progress = ((downloadedAmount * 1d) / (totalDownloadSize * 1d) * 100);
//downloadText.Text = string.Format("{0:F1}", downloadedAmount / 1073741824f) + "/" + string.Format("{0:F1}", totalDownloadSize / 1073741824f) + " GB (" + string.Format("{0:F1}", downloadSpeed / 1048576f) + " MB/s)";
}
}
} while (isMoreToRead);
}
File.Copy(AppContext.BaseDirectory + "\\Temp\\" + Path.GetFileName(filename), filename, true);
File.Delete(AppContext.BaseDirectory + "\\Temp\\" + Path.GetFileName(filename));
}
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("用户取消请求");
}
catch (TaskCanceledException ex)
{
ThrowExceptionMessage(ex);
}
catch(Exception ex)
{
ThrowExceptionMessage(ex);
}
}
private void UpdateInstallButton()
{
if (downloadInProgress)
{
installBtnText.Text = "取消安装";
installBtn.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(201, 34, 16));
}
else if (config.gameIsInstalled && !gameIsRunning && !updateAvailable && !repairInProgress)
{
installBtnText.Text = "开始游戏";
installBtn.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(246, 192, 0));
installBtn.Foreground = new SolidColorBrush(Colors.Black);
}
else if (!downloadInProgress && !config.gameIsInstalled)
{
installBtnText.Text = "安装游戏";
installBtn.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(246, 192, 0));
installBtn.Foreground = new SolidColorBrush(Colors.Black);
}
else if (pubgProcess != null)
{
installBtnText.Text = "停止游戏";
installBtn.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(201, 34, 16));
}
else if (updateAvailable && !downloadInProgress && !gameIsRunning)
{
installBtnText.Text = "更新游戏";
installBtn.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(246, 192, 0));
installBtn.Foreground = new SolidColorBrush(Colors.Black);
}
else if(repairInProgress)
{
installBtnText.Text = "...";
installBtn.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(48, 48, 48));
installBtn.Foreground = new SolidColorBrush(System.Windows.Media.Color.FromRgb(110, 119, 131));
}
}
private void ToggleDownloadBarVisibility(bool visible)
{
if(visible)
{
downloadBar.Visibility = Visibility.Visible;
lastPlayed.Visibility = Visibility.Collapsed;
playtime.Visibility = Visibility.Collapsed;
miscButtons.Visibility = Visibility.Collapsed;
}
else
{
downloadBar.Visibility = Visibility.Collapsed;
playtime.Visibility = Visibility.Visible;
lastPlayed.Visibility = Visibility.Visible;
miscButtons.Visibility = Visibility.Visible;
}
}
private void timerTick(object state, EventArgs args)
{
downloadProgress.Value = (downloadedAmount * 1d) / (totalDownloadSize * 1d) * 100;
if(totalDownloadSize >= 1073741824)
{
downloadText.Text = string.Format("{0:F1}", downloadedAmount / 1073741824f) + "/" + string.Format("{0:F1}", totalDownloadSize / 1073741824f) + " GB (" + string.Format("{0:F1}", downloadSpeed / 1048576d) + " MB/s)";
}
else if(totalDownloadSize < 1073741824)
{
downloadText.Text = string.Format("{0:F1}", downloadedAmount / 1048576f) + "/" + string.Format("{0:F1}", totalDownloadSize / 1048576f) + " MB (" + string.Format("{0:F1}", downloadSpeed / 1048576d) + " MB/s)";
}
else if(totalDownloadSize < 1048576)
{
downloadText.Text = string.Format("{0:F1}", downloadedAmount / 1024f) + "/" + string.Format("{0:F1}", totalDownloadSize / 1024f) + " KB (" + string.Format("{0:F1}", downloadSpeed / 1048576d) + " MB/s)";
}
downloadSpeed = 0;
}
private void PubgProcessEnded(object sender, EventArgs args)
{
pubgProcess = null;
gameIsRunning = false;
playtimeTimer.Stop();
config.lastPlayed = DateTime.Now;
if (!Dispatcher.CheckAccess())
{
Dispatcher.Invoke(()=>UpdateInstallButton(), DispatcherPriority.Normal);
Dispatcher.Invoke(() => uninstallGameBtn.IsEnabled = true, DispatcherPriority.Normal);
Dispatcher.Invoke(() => repairGameBtn.IsEnabled = true, DispatcherPriority.Normal);
Dispatcher.Invoke(() => gameInstallPathBtn.IsEnabled = true, DispatcherPriority.Normal);
Dispatcher.Invoke(() => lastPlayedText.Text = DateTime.Now.ToString(), DispatcherPriority.Normal);
}
else
{
UpdateInstallButton();
lastPlayedText.Text = DateTime.Now.ToString();
uninstallGameBtn.IsEnabled = true;
repairGameBtn.IsEnabled = true;
gameInstallPathBtn.IsEnabled = true;
}
}
private static void GetDownloadSize(List<FileHash> files)
{
for (int i = 0; i < files.Count; i++)
{
totalDownloadSize += files[i].size;
}
}
async void CheckForLauncherUpdate()
{
if (launcherInfo.version > launcherVersion)
{
DialogResult result = System.Windows.Forms.MessageBox.Show("已检测到新版本,是否更新?", "OG:BG 启动器", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == System.Windows.Forms.DialogResult.Yes)
{
totalDownloadSize = launcherInfo.size;
ToggleDownloadBarVisibility(true);
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timerTick;
timer.Start();
await DownloadFileAsync($"{config.webServerIp}launcher/update.zip", AppContext.BaseDirectory + "\\update.zip", cancellationToken.Token);
timer.Stop();
Process.Start(AppContext.BaseDirectory+"Updater\\Updater.exe");
System.Windows.Application.Current.Shutdown();
}
}
}
private void settingsBtnClick(object sender, RoutedEventArgs e)
{
inMainPage = false;
UpdatePage();
}
private void startOnBootChecked(object sender, RoutedEventArgs e)
{
RegistryKey rk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
rk.SetValue("OG:BG Launcher", '"' + Process.GetCurrentProcess().MainModule.FileName + '"');
}
private void startOnBootUnchecked(object sender, RoutedEventArgs e)
{
RegistryKey rk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
if(rk.GetValueNames().Contains("OG:BG Launcher"))
rk.DeleteValue("OG:BG Launcher");
}
private void playBtnClick(object sender, RoutedEventArgs e)
{
inMainPage = true;
UpdatePage();
}
private void gameInstallBtnClick(object sender, RoutedEventArgs e)
{
using (var path = new FolderBrowserDialog())
{
DialogResult result = path.ShowDialog();
if(result == System.Windows.Forms.DialogResult.OK && !Directory.EnumerateFileSystemEntries(path.SelectedPath).Any())
{
config.gameInstall = path.SelectedPath;
gamePathTextBox.Text = path.SelectedPath;
}
else if(result == System.Windows.Forms.DialogResult.OK && Directory.EnumerateFileSystemEntries(path.SelectedPath).Any())
{
System.Windows.MessageBox.Show("请选择空文件夹!", "OG:BG 启动器", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
}
private void windowIsClosing(object sender, CancelEventArgs e)
{
SaveConfig();
}
static async Task<string> GetMD5(string filename)
{
using (var md5 = MD5.Create())
{
using (var stream = File.OpenRead(filename))
{
var hash = await md5.ComputeHashAsync(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
}
static void ThrowExceptionMessage(Exception ex, string customMessage = "")
{
var st = new StackTrace(ex, true);
var frame = st.GetFrame(st.FrameCount-1);
System.Windows.MessageBox.Show($"第 {frame.GetFileLineNumber()} 行: {frame.GetMethod()}: {ex.Message}"+Environment.NewLine+customMessage, "OG:BG 启动器", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
class FileHash
{
public string? path { get; set; }
public string? hash { get; set; }
public long size { get; set; }
}
class GameInfo
{
public string? whatsNew { get; set; }
public string? startupExecutable { get; set; }
public string? startupArgs { get; set; }
}
class Folder
{
public string? path { get; set; }
}
class Config
{
public string? gameInstall { get; set; }
public bool startOnBoot { get; set; }
public bool startMinimized { get; set; }
public bool gameIsInstalled { get; set; }
public DateTime lastPlayed { get; set; }
public int playtime { get; set; }
public string? webServerIp { get; set; }
}
class LauncherInfo
{
public int version { get; set; }
public int size { get; set; }
}
}

View file

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>icon.ico</ApplicationIcon>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Shaders\**" />
<EmbeddedResource Remove="Shaders\**" />
<None Remove="Shaders\**" />
<Page Remove="Shaders\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Fonts\AlimamaFYT.ttf" />
<None Remove="Fonts\PUBG SANS.ttf" />
<None Remove="Images\icon.png" />
<None Remove="README" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MahApps.Metro.IconPacks.Material" Version="5.1.0" />
</ItemGroup>
<ItemGroup>
<Resource Include="Fonts\AlimamaFYT.ttf" />
<Resource Include="Fonts\PUBG SANS.ttf" />
<Resource Include="Images\icon.png" />
<Resource Include="Images\back.png" />
<Resource Include="README" />
</ItemGroup>
</Project>