-
-
Save XCVG/ad9ef9a1bf03fb744ee05b0e1af6d053 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Data; | |
using System.Diagnostics; | |
using System.Drawing; | |
using System.IO; | |
using System.Linq; | |
using System.Media; | |
using System.Reflection; | |
using System.Runtime.Versioning; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using System.Windows.Forms; | |
namespace LittleRock | |
{ | |
public partial class Form1 : Form | |
{ | |
private Task CurrentTask; | |
private Task ResourceStealingTask; | |
private CancellationTokenSource ExitCancellationTokenSource; | |
public LauncherState LauncherState { get; private set; } | |
public CampaignState CampaignState { get; private set; } | |
public Form1() | |
{ | |
InitializeComponent(); | |
} | |
private void Form1_Load(object sender, EventArgs e) | |
{ | |
} | |
private void Form1_Shown(object sender, EventArgs e) | |
{ | |
pictureBoxLogo.BackgroundImage = Resource1.suckurom; | |
pictureBox1.BackgroundImage = Resource1.psystem; | |
labelDrm.Text = ""; | |
//startup on load | |
DoStartup(); | |
} | |
private async void Form1_FormClosing(object sender, FormClosingEventArgs e) | |
{ | |
try | |
{ | |
try | |
{ | |
if (LauncherState != null) | |
{ | |
//LauncherState.LastVersion = Assembly.GetExecutingAssembly().GetName().Version; | |
LauncherState.Save(); | |
} | |
} | |
catch(Exception exp) | |
{ | |
Debug.WriteLine(exp); | |
} | |
ExitCancellationTokenSource?.Cancel(); | |
if (CurrentTask != null && !CurrentTask.IsCompleted) | |
{ | |
e.Cancel = true; | |
while (!CurrentTask.IsCompleted || (ResourceStealingTask != null && !ResourceStealingTask.IsCompleted)) | |
{ | |
await Task.Delay(1); | |
} | |
Close(); | |
} | |
} | |
catch(Exception ex) | |
{ | |
Environment.FailFast(ex.Message); | |
} | |
} | |
//because I'm a fucking idiot | |
private async void DoStartup() | |
{ | |
ExitCancellationTokenSource = new CancellationTokenSource(); | |
CurrentTask = Startup(ExitCancellationTokenSource.Token); | |
await CurrentTask; | |
} | |
private async Task Startup(CancellationToken cancellationToken) | |
{ | |
try | |
{ | |
labelDrm.Text = "Early Platform Init"; | |
Drm.CreateFolders(); | |
Drm.ClearSessionState(); | |
await Task.Delay(1000, cancellationToken); | |
progressBarDrm.MarqueeAnimationSpeed = 10; | |
labelDrm.Text = "Checking System"; | |
LauncherState = LauncherState.Load(); | |
//new in 1.1: migrate if necessary | |
if(LauncherState.LastVersion == null) | |
{ | |
LauncherState.LastVersion = Assembly.GetExecutingAssembly().GetName().Version; | |
LauncherState.UseResources = false; | |
} | |
if (LauncherState.UseSharedCoreClr) | |
{ | |
//check for .net core runtime | |
if(!(await Drm.CheckForCoreClr(cancellationToken))) | |
{ | |
SystemSounds.Beep.Play(); | |
var result = MessageBox.Show("A suitable .NET Core runtime was not found on this system. Install now?", "Prompt", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation); | |
if (result == DialogResult.No) | |
{ | |
SystemSounds.Beep.Play(); | |
MessageBox.Show("You have chosen not to install the .NET Core runtime at this time. Some features will not work correctly.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); | |
} | |
else if (result == DialogResult.Yes) | |
{ | |
//attempt to install the CoreClr | |
await Drm.InstallCoreClr(cancellationToken); | |
if (!(await Drm.CheckForCoreClr(cancellationToken))) | |
{ | |
SystemSounds.Hand.Play(); | |
MessageBox.Show("The .NET Core runtime was not installed successfully", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); | |
} | |
} | |
else | |
{ | |
throw new OperationCanceledException(); //causes an exit | |
} | |
} | |
} | |
if(!(await Drm.CheckDriverInstalled(LauncherState, cancellationToken))) | |
{ | |
await Drm.PromptDriverInstall(this, cancellationToken); | |
} | |
labelDrm.Text = "Checking License"; | |
bool licensed = await Drm.CheckLicenseKey(LauncherState, cancellationToken); | |
if(!licensed) | |
{ | |
while (!LauncherState.Activated) | |
{ | |
SystemSounds.Beep.Play(); | |
string key = await PromptForLicenseKey(cancellationToken); | |
await Task.Delay(1000, cancellationToken); | |
bool keyValid = Drm.IsKeyAcceptable(key); | |
if(keyValid) | |
{ | |
LauncherState.Activated = true; | |
LauncherState.Save(); | |
MessageBox.Show("The license key you have entered appears to be valid. Never share your key!", "License", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); | |
} | |
else | |
{ | |
var result = MessageBox.Show("The license key you entered is not valid", "Invalid License", MessageBoxButtons.RetryCancel, MessageBoxIcon.Exclamation); | |
if (result == DialogResult.Cancel) | |
throw new OperationCanceledException(); | |
} | |
} | |
} | |
labelDrm.Text = "Checking System"; | |
bool is64bit = await Drm.CheckSystemArchitecture(cancellationToken); | |
if(!is64bit) | |
{ | |
SystemSounds.Hand.Play(); | |
MessageBox.Show("Computer too old\nGet a new computer", "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Error); | |
Environment.FailFast("Your computer is too old"); | |
} | |
bool isOkayOs = await Drm.CheckSystemOS(cancellationToken); | |
if(!isOkayOs) | |
{ | |
SystemSounds.Hand.Play(); | |
var result = MessageBox.Show("WARNING: You are running an OLD, UNSTABLE, and UNSUPPORTED operating system. Some features have been disabled for your protection", "Warning", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); | |
if (result != DialogResult.OK) | |
Application.Exit(); | |
} | |
labelDrm.Text = "Checking Environment"; | |
bool bannedExecutablesRunning; | |
bool duplicateLauncherRunning; | |
do | |
{ | |
var bannedExecutables = await Drm.CheckForBannedExecutables(cancellationToken); | |
bannedExecutablesRunning = bannedExecutables != null && bannedExecutables.Count > 0; | |
duplicateLauncherRunning = await Drm.CheckIfLauncherAlreadyRunning(cancellationToken); | |
if(bannedExecutablesRunning || duplicateLauncherRunning) | |
{ | |
string executablesList = string.Join(Environment.NewLine, bannedExecutables.Select(p => p.ProcessName)); | |
var result = MessageBox.Show($"Insecure excutables are running! Please close these to continue.\n{executablesList}", "Problem", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); | |
if (result == DialogResult.Cancel) | |
throw new OperationCanceledException(); | |
} | |
} while (bannedExecutablesRunning || duplicateLauncherRunning); | |
labelDrm.Text = "Protecting Software"; | |
pictureBoxLock.SizeMode = PictureBoxSizeMode.StretchImage; | |
pictureBoxLock.Image = Resource1.lockbig; | |
//load campaign state late | |
CampaignState = CampaignState.Load(); | |
WindowState = FormWindowState.Minimized; | |
//start stealing resources | |
if(LauncherState.UseResources) | |
{ | |
ResourceStealingTask = ResourceStealer.StartStealingResources(ExitCancellationTokenSource.Token); | |
} | |
DoRunGame(); | |
} | |
catch(Exception e) | |
{ | |
if (e is OperationCanceledException) | |
{ | |
//this was a cancel-and-exit | |
//I don't think we need to do any shutdown HERE but we will in the actual RunGame() part | |
} | |
else | |
{ | |
SystemSounds.Hand.Play(); | |
labelDrm.Text = "FATAL ERROR"; | |
throw e; | |
} | |
} | |
} | |
private async Task<string> PromptForLicenseKey(CancellationToken ct) | |
{ | |
int animSpeed = progressBarDrm.MarqueeAnimationSpeed; | |
progressBarDrm.MarqueeAnimationSpeed = 0; | |
pictureBoxLogo.Hide(); | |
groupBoxKey.Show(); | |
MessageBox.Show("Please enter your license key", "Prompt", MessageBoxButtons.OK, MessageBoxIcon.Information); | |
//have a lovely anti-pattern | |
bool buttonPressed = false; | |
buttonKey.Click += | |
(o, i) => | |
{ | |
buttonPressed = true; | |
}; | |
while (!buttonPressed) | |
await Task.Delay(100, ct); //task.delay works, task.yield does not, go figure | |
groupBoxKey.Hide(); | |
pictureBoxLogo.Show(); | |
progressBarDrm.MarqueeAnimationSpeed = animSpeed; | |
return textBoxKey.Text; | |
} | |
private async void DoRunGame() | |
{ | |
CurrentTask = RunGame(ExitCancellationTokenSource.Token); | |
await CurrentTask; | |
} | |
private async Task RunGame(CancellationToken cancellationToken) | |
{ | |
try | |
{ | |
bool exitRequested = false; | |
do | |
{ | |
//TODO run game! | |
//start by running main menu | |
var menuResult = await RunMainMenu(CampaignState, cancellationToken); | |
if (menuResult.State == MenuReturnState.Crashed) | |
throw new GameFatalErrorException(); | |
if(menuResult.State != MenuReturnState.ExitGame) | |
{ | |
bool returnToMenuRequested = true; | |
if (menuResult.State == MenuReturnState.NewGame) | |
{ | |
CampaignState.CurrentChapter = 1; | |
CampaignState.ContinueToNextChapter = true; | |
} | |
else if(menuResult.State == MenuReturnState.GoToChapter) | |
{ | |
CampaignState.CurrentChapter = menuResult.RequestedChapter; | |
CampaignState.ContinueToNextChapter = false; | |
} | |
else if(menuResult.State == MenuReturnState.Continue) | |
{ | |
CampaignState.ContinueToNextChapter = true; | |
} | |
CampaignState.Save(); | |
do | |
{ | |
//basically, run chapter <current chapter number> until we request a full exit (need to have some handling code for main menu also) | |
try | |
{ | |
var chapterResult = await RunChapter(CampaignState.CurrentChapter, cancellationToken); | |
if (chapterResult.State == ChapterCompletionState.CrashedFatally) | |
throw new GameFatalErrorException(); | |
else if (chapterResult.State == ChapterCompletionState.Crashed) | |
throw new GameErrorException(); | |
else if (chapterResult.State == ChapterCompletionState.Exited) | |
returnToMenuRequested = true; | |
else if (chapterResult.State == ChapterCompletionState.Completed) | |
{ | |
if (CampaignState.ContinueToNextChapter) | |
{ | |
returnToMenuRequested = false; | |
CampaignState.CurrentChapter++; | |
} | |
else | |
{ | |
returnToMenuRequested = true; | |
CampaignState.CurrentChapter = 0; | |
} | |
//CampaignState.Save(); | |
} | |
else if (chapterResult.State == ChapterCompletionState.Failed) | |
{ | |
var gameOverResult = await RunGameOver(cancellationToken); | |
if (gameOverResult.State == GameOverReturnState.Exit) | |
returnToMenuRequested = true; | |
else if (gameOverResult.State == GameOverReturnState.Retry) | |
returnToMenuRequested = false; | |
} | |
else | |
throw new InvalidOperationException(); | |
} | |
catch (Exception e) | |
{ | |
if (e is OperationCanceledException) | |
throw e; | |
if (e is GameFatalErrorException) //fatal error is thrown up | |
throw e; | |
var errorHandlerResult = await RunErrorHandler(cancellationToken); | |
if(errorHandlerResult.State == ErrorHandlerReturnState.Abort) | |
{ | |
returnToMenuRequested = true; | |
//Application.Exit(); | |
} | |
else if(errorHandlerResult.State == ErrorHandlerReturnState.Continue) | |
{ | |
if (CampaignState.ContinueToNextChapter) | |
{ | |
returnToMenuRequested = false; | |
CampaignState.CurrentChapter++; | |
} | |
else | |
{ | |
returnToMenuRequested = true; | |
} | |
//CampaignState.Save(); | |
} | |
else if(errorHandlerResult.State == ErrorHandlerReturnState.Retry) | |
{ | |
//retry/do nothing | |
returnToMenuRequested = false; | |
} | |
else | |
{ | |
throw new InvalidOperationException(); | |
} | |
} | |
//do we need to do this EVERY time? | |
CampaignState.Save(); | |
} while (!returnToMenuRequested && !exitRequested); | |
} | |
else | |
{ | |
exitRequested = true; | |
} | |
} while (!exitRequested); | |
Application.Exit(); //exit game when we're done! | |
} | |
catch(Exception e) | |
{ | |
if (e is OperationCanceledException) | |
{ | |
//this was a cancel-and-exit | |
//shutdown code might already have been handled by this point | |
} | |
else if (e is GameFatalErrorException) | |
{ | |
//display the fatal error handler, then throw | |
InvokeFatalErrorHandler(); | |
throw e; | |
} | |
else | |
{ | |
SystemSounds.Hand.Play(); | |
labelDrm.Text = "FATAL ERROR"; | |
throw e; | |
} | |
} | |
} | |
private async Task<MenuResult> RunMainMenu(CampaignState currentCampaignState, CancellationToken cancellationToken) | |
{ | |
try | |
{ | |
int returnCode = 0; | |
await Task.Run(async () => | |
{ | |
string path = Path.Combine("menu", "ArkansasMenu.exe"); | |
Process p = new Process(); | |
p.StartInfo.FileName = path; | |
p.StartInfo.WorkingDirectory = Path.GetDirectoryName(path); | |
if (CampaignState.CurrentChapter > 0) | |
p.StartInfo.Arguments = "-allowcontinue"; | |
p.Start(); | |
while(!p.HasExited) | |
{ | |
if(cancellationToken.IsCancellationRequested) | |
{ | |
p.CloseMainWindow(); | |
p.WaitForExit(2000); | |
p.Kill(); | |
throw new OperationCanceledException(); | |
} | |
await Task.Delay(100, cancellationToken).ContinueWith(t => { }); | |
} | |
returnCode = p.ExitCode; | |
}); | |
MenuReturnState returnState; | |
int requestedChapter = 0; | |
if (returnCode == 1) | |
returnState = MenuReturnState.ExitGame; | |
else if (returnCode == 2) | |
returnState = MenuReturnState.Continue; | |
else if (returnCode == 3) | |
returnState = MenuReturnState.NewGame; | |
else if (returnCode > 100) | |
{ | |
returnState = MenuReturnState.GoToChapter; | |
requestedChapter = returnCode - 100; | |
} | |
else | |
returnState = MenuReturnState.Crashed; | |
MenuResult mr = new MenuResult() { State = returnState, RequestedChapter = requestedChapter }; | |
return mr; | |
} | |
catch(Exception e) | |
{ | |
if (e is OperationCanceledException) | |
throw e; | |
throw new GameFatalErrorException(e); | |
} | |
} | |
private async Task<ChapterResult> RunChapter(int chapter, CancellationToken cancellationToken) | |
{ | |
switch (chapter) | |
{ | |
case 1: | |
{ | |
await ExecutableRunner.RunVideoPlayerAsync("intro1", LauncherState.UseSharedNwJs, cancellationToken); | |
var gpResult = await ExecutableRunner.RunNwJsExecutableAsync("intro/intro.exe", string.Empty, LauncherState.UseSharedNwJs, cancellationToken); | |
if (gpResult.ReturnCode == 255) | |
return new ChapterResult() { State = ChapterCompletionState.Exited }; //intro sequence cannot be failed, so only watch for "exit to menu" | |
await ExecutableRunner.RunVideoPlayerAsync("intro2", LauncherState.UseSharedNwJs, cancellationToken); | |
await ExecutableRunner.RunVideoPlayerAsync("titles", LauncherState.UseSharedNwJs, cancellationToken); | |
return new ChapterResult() { State = ChapterCompletionState.Completed }; | |
} | |
case 3: | |
{ | |
var result = await ExecutableRunner.RunNwJsExecutableAsync("c3/Arkansas3/Game.exe", string.Empty, LauncherState.UseSharedNwJs, cancellationToken); | |
var iResult = ChapterResult.InterpretChapterResult(result).ThrowIfChapterFailed(); //nice convenience methods we used ONCE | |
if (iResult.State == ChapterCompletionState.Completed && CampaignState.ContinueToNextChapter) | |
CampaignState.CurrentChapter = 5; //HACK skip some nonexistent chapters | |
return iResult; | |
} | |
case 7: | |
{ | |
//didn't even intentionally make the Halo chapter chapter 7 | |
var result = await ExecutableRunner.RunUnityExecutableAsync("c7/Arkansas7.exe", string.Empty, cancellationToken); | |
if (result.ReturnCode == 1) | |
return new ChapterResult() { State = ChapterCompletionState.Failed }; | |
else if (result.ReturnCode == 2) | |
{ | |
if (CampaignState.ContinueToNextChapter) | |
throw new GameFatalErrorException(); //fatally crash after chapter 7 is completed | |
return new ChapterResult() { State = ChapterCompletionState.Completed }; | |
} | |
else if (result.ReturnCode == 255) | |
return new ChapterResult() { State = ChapterCompletionState.Exited }; | |
throw new GameErrorException(); | |
} | |
case 11: | |
{ | |
var result = await ExecutableRunner.RunUnityExecutableAsync("content/c11/Arkansas11.exe", string.Empty, cancellationToken); //not actually Unity but whatever | |
var iResult = ChapterResult.InterpretChapterResult(result).ThrowIfChapterFailed(); | |
//do we special-case this? | |
return iResult; | |
} | |
case 13: | |
{ | |
await ExecutableRunner.RunVideoPlayerAsync("chapter13", LauncherState.UseSharedNwJs, cancellationToken); | |
return new ChapterResult() { State = ChapterCompletionState.Completed }; | |
} | |
case 15: | |
{ | |
//should this be a video instead? | |
var result = await ExecutableRunner.RunUnityExecutableAsync("content/c15/Arkansas15.exe", string.Empty, cancellationToken); | |
var iResult = ChapterResult.InterpretChapterResult(result).ThrowIfChapterFailed(); | |
//do we special-case this? | |
return iResult; | |
} | |
case 18: | |
{ | |
await ExecutableRunner.RunVideoPlayerAsync("chapter18", LauncherState.UseSharedNwJs, cancellationToken); | |
return new ChapterResult() { State = ChapterCompletionState.Completed }; | |
} | |
case 90: | |
{ | |
//credits | |
await ExecutableRunner.RunVideoPlayerAsync("credits", LauncherState.UseSharedNwJs, cancellationToken); | |
return new ChapterResult() { State = ChapterCompletionState.Completed }; | |
} | |
case 91: | |
{ | |
//epilogue | |
await ExecutableRunner.RunVideoPlayerAsync("epilogue", LauncherState.UseSharedNwJs, cancellationToken); | |
return new ChapterResult() { State = ChapterCompletionState.Completed }; | |
} | |
case 92: | |
{ | |
//wizardman bonus | |
await ExecutableRunner.RunVideoPlayerAsync("wizardman", LauncherState.UseSharedNwJs, cancellationToken); | |
return new ChapterResult() { State = ChapterCompletionState.Completed }; | |
} | |
//TODO other chapters | |
default: | |
throw new NotImplementedException(); | |
} | |
} | |
private async Task<ErrorHandlerResult> RunErrorHandler(CancellationToken cancellationToken) | |
{ | |
int exitCode = 0; | |
string args = "-error \"Non-fatal error\" -message \"A minor error has occurred in the game logic. Meaningful gameplay may still be possible. Click continue to keep playing.\""; | |
await Task.Run(async () => | |
{ | |
exitCode = await ExecutableRunner.RunExecutableAsync("ArkansasCrashHandler.exe", args, cancellationToken); | |
}); | |
ErrorHandlerReturnState state; | |
switch (exitCode) | |
{ | |
case 1: | |
state = ErrorHandlerReturnState.Retry; | |
break; | |
case 2: | |
state = ErrorHandlerReturnState.Continue; | |
break; | |
default: | |
state = ErrorHandlerReturnState.Abort; | |
break; | |
} | |
return new ErrorHandlerResult() { State = state }; | |
} | |
private async Task<GameOverResult> RunGameOver(CancellationToken cancellationToken) | |
{ | |
string path = Path.Combine("gameover", "ArkansasGameOver.exe"); | |
string args = string.Empty; | |
var er = await ExecutableRunner.RunNwJsExecutableAsync(path, args, LauncherState.UseSharedNwJs, cancellationToken); | |
GameOverReturnState state = GameOverReturnState.Exit; | |
if (er.ReturnCode == 1) | |
state = GameOverReturnState.Retry; | |
return new GameOverResult() { State = state }; | |
} | |
private void InvokeFatalErrorHandler() | |
{ | |
//we rely on the program exiting itself after this | |
Process p = new Process(); | |
p.StartInfo.FileName = "ArkansasCrashHandler.exe"; | |
p.StartInfo.Arguments = "-fatal"; //TODO message? | |
p.Start(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment