Skip to content

Instantly share code, notes, and snippets.

@XCVG

XCVG/Form1.cs Secret

Last active July 4, 2020 00:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save XCVG/ad9ef9a1bf03fb744ee05b0e1af6d053 to your computer and use it in GitHub Desktop.
Save XCVG/ad9ef9a1bf03fb744ee05b0e1af6d053 to your computer and use it in GitHub Desktop.
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