Browse Source

added sources

[email protected] 5 years ago
commit
d881a34930

+ 7 - 0
Source/CheckRide.conf

@@ -0,0 +1,7 @@
+;Note: intended to use [_General] section for program specific settings
+
+[default]
+; Default helper entry
+HelperHost=changethisline.example.com
+HelperPort=33334
+HelperName=Wonko the Sane

BIN
Source/CheckRide.ico


+ 172 - 0
Source/CheckRide.lpi

@@ -0,0 +1,172 @@
+<?xml version="1.0"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="9"/>
+    <PathDelim Value="\"/>
+    <General>
+      <Flags>
+        <SaveClosedFiles Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="CheckRide"/>
+      <ResourceType Value="res"/>
+      <Icon Value="0"/>
+    </General>
+    <i18n>
+      <EnableI18N LFM="False"/>
+    </i18n>
+    <VersionInfo>
+      <UseVersionInfo Value="True"/>
+      <AutoIncrementBuild Value="True"/>
+      <MajorVersionNr Value="1"/>
+      <BuildNr Value="7"/>
+      <StringTable CompanyName="Reinier Olislagers" ProductName="CheckRide" InternalName="CheckRide" ProductVersion="" FileDescription="Remote support software using VNC and SSL/TLS tunnels." OriginalFilename="CheckRide.exe"/>
+    </VersionInfo>
+    <BuildModes Count="2">
+      <Item1 Name="Default" Default="True"/>
+      <Item2 Name="Debug">
+        <CompilerOptions>
+          <Version Value="11"/>
+          <PathDelim Value="\"/>
+          <Target>
+            <Filename Value="..\Output\CheckRide"/>
+          </Target>
+          <SearchPaths>
+            <IncludeFiles Value="$(ProjOutDir)"/>
+            <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
+          </SearchPaths>
+          <CodeGeneration>
+            <Checks>
+              <IOChecks Value="True"/>
+              <RangeChecks Value="True"/>
+              <OverflowChecks Value="True"/>
+              <StackChecks Value="True"/>
+            </Checks>
+          </CodeGeneration>
+          <Linking>
+            <Debugging>
+              <DebugInfoType Value="dsStabs"/>
+              <UseHeaptrc Value="True"/>
+            </Debugging>
+            <LinkSmart Value="True"/>
+            <Options>
+              <Win32>
+                <GraphicApplication Value="True"/>
+              </Win32>
+            </Options>
+          </Linking>
+          <Other>
+            <CompilerMessages>
+              <UseMsgFile Value="True"/>
+            </CompilerMessages>
+            <CustomOptions Value="-dDEBUG"/>
+            <CompilerPath Value="$(CompPath)"/>
+            <ExecuteAfter>
+              <Command Value="checkrideresourcezipper.exe -o ..\Output\ --writeresourcecheck"/>
+            </ExecuteAfter>
+          </Other>
+        </CompilerOptions>
+      </Item2>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="LCL"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="5">
+      <Unit0>
+        <Filename Value="CheckRide.lpr"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="CheckRide"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="unit1.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="CheckRideMain"/>
+        <ResourceBaseClass Value="Form"/>
+        <UnitName Value="Unit1"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="checkrideutil.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="CheckRideUtil"/>
+      </Unit2>
+      <Unit3>
+        <Filename Value="aboutform.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="InfoAboutForm"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+        <UnitName Value="aboutform"/>
+      </Unit3>
+      <Unit4>
+        <Filename Value="poormansresource.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="poormansresource"/>
+      </Unit4>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <PathDelim Value="\"/>
+    <Target>
+      <Filename Value="..\Output\CheckRide"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <CodeGeneration>
+      <SmartLinkUnit Value="True"/>
+      <SmallerCode Value="True"/>
+      <Optimizations>
+        <OptimizationLevel Value="3"/>
+      </Optimizations>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <DebugInfoType Value="dsStabs"/>
+        <UseExternalDbgSyms Value="True"/>
+      </Debugging>
+      <LinkSmart Value="True"/>
+      <Options>
+        <Win32>
+          <GraphicApplication Value="True"/>
+        </Win32>
+      </Options>
+    </Linking>
+    <Other>
+      <CompilerMessages>
+        <UseMsgFile Value="True"/>
+      </CompilerMessages>
+      <CompilerPath Value="$(CompPath)"/>
+      <ExecuteAfter>
+        <Command Value="checkrideresourcezipper.exe -o ..\Output\ --writeresourcecheck"/>
+      </ExecuteAfter>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 52 - 0
Source/CheckRide.lpr

@@ -0,0 +1,52 @@
+program CheckRide;
+
+{*
+This source code is provided under the MIT license:
+Copyright (C) 2011 by Reinier Olislagers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in 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:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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
+AUTHORS 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 IN
+THE SOFTWARE.
+*}
+{$mode objfpc}{$H+}
+
+
+uses {$IFDEF UNIX} {$IFDEF UseCThreads}
+  cthreads, {$ENDIF} {$ENDIF}
+  Interfaces, // this includes the LCL widgetset
+  Forms,
+  Unit1,
+  CheckRideUtil,
+  aboutform,
+  poormansresource;
+
+//Regular form resource stuff
+{$R CheckRide.res}
+
+
+// For our embedded config/template files, and executables:
+// not used as we use our own imitation resource manipulation ;)
+//{$R checkrideconfig.rc}
+
+
+begin
+  RequireDerivedFormResource := True;
+  Application.Initialize;
+  Application.CreateForm(TCheckRideMain, CheckRideMain);
+  Application.Run;
+end.
+

BIN
Source/CheckRideHelper.ico


+ 233 - 0
Source/CheckRideHelper.lpi

@@ -0,0 +1,233 @@
+<?xml version="1.0"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="9"/>
+    <PathDelim Value="\"/>
+    <General>
+      <Flags>
+        <SaveClosedFiles Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="CheckRideHelper"/>
+      <ResourceType Value="res"/>
+      <Icon Value="0"/>
+    </General>
+    <i18n>
+      <EnableI18N LFM="False"/>
+    </i18n>
+    <VersionInfo>
+      <UseVersionInfo Value="True"/>
+      <AutoIncrementBuild Value="True"/>
+      <MajorVersionNr Value="1"/>
+      <BuildNr Value="3"/>
+      <StringTable CompanyName="Reinier Olislagers" ProductName="CheckRideHelper" InternalName="CheckRideHelper" ProductVersion="" FileDescription="Remote support software using VNC and SSL/TLS tunnels." OriginalFilename="CheckRideHelper.exe"/>
+    </VersionInfo>
+    <BuildModes Count="3">
+      <Item1 Name="Default" Default="True"/>
+      <Item2 Name="Debug">
+        <CompilerOptions>
+          <Version Value="11"/>
+          <PathDelim Value="\"/>
+          <Target>
+            <Filename Value="..\Output\CheckRideHelper"/>
+          </Target>
+          <SearchPaths>
+            <IncludeFiles Value="$(ProjOutDir)"/>
+            <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
+          </SearchPaths>
+          <CodeGeneration>
+            <Checks>
+              <IOChecks Value="True"/>
+              <RangeChecks Value="True"/>
+              <OverflowChecks Value="True"/>
+              <StackChecks Value="True"/>
+            </Checks>
+          </CodeGeneration>
+          <Linking>
+            <Debugging>
+              <DebugInfoType Value="dsDwarf2Set"/>
+              <UseHeaptrc Value="True"/>
+            </Debugging>
+            <LinkSmart Value="True"/>
+            <Options>
+              <Win32>
+                <GraphicApplication Value="True"/>
+              </Win32>
+            </Options>
+          </Linking>
+          <Other>
+            <CompilerMessages>
+              <UseMsgFile Value="True"/>
+            </CompilerMessages>
+            <CustomOptions Value="-dDEBUG"/>
+            <CompilerPath Value="$(CompPath)"/>
+            <ExecuteAfter>
+              <Command Value="checkrideresourcezipper.exe -o &quot;..\Output\&quot; --writeresourcehelp"/>
+            </ExecuteAfter>
+          </Other>
+        </CompilerOptions>
+      </Item2>
+      <Item3 Name="Win64">
+        <CompilerOptions>
+          <Version Value="11"/>
+          <PathDelim Value="\"/>
+          <Target>
+            <Filename Value="..\Output\CheckRideHelper"/>
+          </Target>
+          <SearchPaths>
+            <IncludeFiles Value="$(ProjOutDir)"/>
+            <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
+          </SearchPaths>
+          <CodeGeneration>
+            <SmartLinkUnit Value="True"/>
+            <SmallerCode Value="True"/>
+            <TargetCPU Value="x86_64"/>
+            <TargetOS Value="win64"/>
+            <Optimizations>
+              <OptimizationLevel Value="3"/>
+            </Optimizations>
+          </CodeGeneration>
+          <Linking>
+            <Debugging>
+              <DebugInfoType Value="dsStabs"/>
+              <UseExternalDbgSyms Value="True"/>
+            </Debugging>
+            <LinkSmart Value="True"/>
+            <Options>
+              <Win32>
+                <GraphicApplication Value="True"/>
+              </Win32>
+            </Options>
+          </Linking>
+          <Other>
+            <CompilerMessages>
+              <UseMsgFile Value="True"/>
+            </CompilerMessages>
+            <CompilerPath Value="$(CompPath)"/>
+            <ExecuteAfter>
+              <Command Value="checkrideresourcezipper.exe -o &quot;..\Output\&quot; --writeresourcehelp"/>
+            </ExecuteAfter>
+          </Other>
+        </CompilerOptions>
+      </Item3>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <CommandLineParams Value="--debug-log=d:\cop\lazdebug.txt"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="2">
+      <Item1>
+        <PackageName Value="LCLBase"/>
+        <MinVersion Major="1" Release="1" Valid="True"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="LCL"/>
+      </Item2>
+    </RequiredPackages>
+    <Units Count="7">
+      <Unit0>
+        <Filename Value="CheckRideHelper.lpr"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="CheckRideHelper"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="checkridehelperunit.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="CheckRideHelperMain"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+        <UnitName Value="CheckRideHelperUnit"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="checkrideutil.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="CheckRideUtil"/>
+      </Unit2>
+      <Unit3>
+        <Filename Value="aboutform.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="InfoAboutForm"/>
+        <ResourceBaseClass Value="Form"/>
+        <UnitName Value="aboutform"/>
+      </Unit3>
+      <Unit4>
+        <Filename Value="clientcustomizer.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="frmClientCustomizer"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+        <UnitName Value="clientcustomizer"/>
+      </Unit4>
+      <Unit5>
+        <Filename Value="poormansresource.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="poormansresource"/>
+      </Unit5>
+      <Unit6>
+        <Filename Value="resourcezipper.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="resourcezipper"/>
+      </Unit6>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <PathDelim Value="\"/>
+    <Target>
+      <Filename Value="..\Output\CheckRideHelper"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <CodeGeneration>
+      <SmartLinkUnit Value="True"/>
+      <SmallerCode Value="True"/>
+      <Optimizations>
+        <OptimizationLevel Value="3"/>
+      </Optimizations>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <DebugInfoType Value="dsStabs"/>
+        <UseExternalDbgSyms Value="True"/>
+      </Debugging>
+      <LinkSmart Value="True"/>
+      <Options>
+        <Win32>
+          <GraphicApplication Value="True"/>
+        </Win32>
+      </Options>
+    </Linking>
+    <Other>
+      <CompilerMessages>
+        <UseMsgFile Value="True"/>
+      </CompilerMessages>
+      <CompilerPath Value="$(CompPath)"/>
+      <ExecuteAfter>
+        <Command Value="checkrideresourcezipper.exe -o &quot;..\Output\&quot; --writeresourcehelp"/>
+      </ExecuteAfter>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 21 - 0
Source/CheckRideHelper.lpr

@@ -0,0 +1,21 @@
+program CheckRideHelper;
+
+{$mode objfpc}{$H+}
+
+uses {$IFDEF UNIX} {$IFDEF UseCThreads}
+  cthreads, {$ENDIF} {$ENDIF}
+  Interfaces, // this includes the LCL widgetset
+  Forms,
+  CheckRideHelperUnit,
+  checkrideutil,
+  clientcustomizer, poormansresource, resourcezipper, aboutform;
+
+{$R CheckRideHelper.res}
+
+begin
+  RequireDerivedFormResource := True;
+  Application.Initialize;
+  Application.CreateForm(TCheckRideHelperMain, CheckRideHelperMain);
+  Application.Createform(Tfrmclientcustomizer, Frmclientcustomizer);
+  Application.Run;
+end.

+ 144 - 0
Source/CheckRideResourceZipper.lpi

@@ -0,0 +1,144 @@
+<?xml version="1.0"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="9"/>
+    <PathDelim Value="\"/>
+    <General>
+      <Flags>
+        <SaveClosedFiles Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="CheckRideResourceZipper"/>
+      <ResourceType Value="res"/>
+    </General>
+    <i18n>
+      <EnableI18N LFM="False"/>
+    </i18n>
+    <VersionInfo>
+      <AutoIncrementBuild Value="True"/>
+      <MajorVersionNr Value="1"/>
+      <BuildNr Value="1"/>
+      <StringTable Comments="X11 license: all use permitted but no liability accepted" CompanyName="Reinier Olislagers" ProductName="CheckRideResourceZipper" InternalName="CheckRideResourceZipper" LegalCopyright="Reinier Olislagers" ProductVersion="" FileDescription="Utility program that zips resources for CheckRide compilation" OriginalFilename="CheckRideResourceZipper.exe"/>
+    </VersionInfo>
+    <BuildModes Count="2">
+      <Item1 Name="Default" Default="True"/>
+      <Item2 Name="Debug">
+        <CompilerOptions>
+          <Version Value="11"/>
+          <PathDelim Value="\"/>
+          <Target>
+            <Filename Value="checkrideresourcezipper"/>
+          </Target>
+          <SearchPaths>
+            <IncludeFiles Value="$(ProjOutDir)"/>
+            <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
+          </SearchPaths>
+          <CodeGeneration>
+            <Checks>
+              <IOChecks Value="True"/>
+              <RangeChecks Value="True"/>
+              <OverflowChecks Value="True"/>
+              <StackChecks Value="True"/>
+            </Checks>
+          </CodeGeneration>
+          <Linking>
+            <Debugging>
+              <DebugInfoType Value="dsDwarf2Set"/>
+              <UseHeaptrc Value="True"/>
+            </Debugging>
+          </Linking>
+          <Other>
+            <CompilerMessages>
+              <UseMsgFile Value="True"/>
+            </CompilerMessages>
+            <CustomOptions Value="-dDEBUG"/>
+            <CompilerPath Value="$(CompPath)"/>
+          </Other>
+        </CompilerOptions>
+      </Item2>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <CommandLineParams Value="-o &quot;..\Output\&quot; --writeresourcehelp"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="2">
+      <Item1>
+        <PackageName Value="LCLBase"/>
+        <MinVersion Major="1" Valid="True" Release="1"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="LCL"/>
+      </Item2>
+    </RequiredPackages>
+    <Units Count="3">
+      <Unit0>
+        <Filename Value="CheckRideResourceZipper.lpr"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="CheckRideResourceZipper"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="checkrideutil.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="CheckRideUtil"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="resourcezipper.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="resourcezipper"/>
+      </Unit2>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <PathDelim Value="\"/>
+    <Target>
+      <Filename Value="checkrideresourcezipper"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <CodeGeneration>
+      <SmartLinkUnit Value="True"/>
+      <SmallerCode Value="True"/>
+      <Optimizations>
+        <OptimizationLevel Value="3"/>
+      </Optimizations>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <GenerateDebugInfo Value="False"/>
+        <DebugInfoType Value="dsStabs"/>
+        <UseExternalDbgSyms Value="True"/>
+      </Debugging>
+      <LinkSmart Value="True"/>
+    </Linking>
+    <Other>
+      <CompilerMessages>
+        <UseMsgFile Value="True"/>
+      </CompilerMessages>
+      <CompilerPath Value="$(CompPath)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 121 - 0
Source/CheckRideResourceZipper.lpr

@@ -0,0 +1,121 @@
+program CheckRideResourceZipper;
+
+{$mode objfpc}{$H+}
+{$APPTYPE CONSOLE}
+//Handy for error messages in resourcezipper
+{$DEFINE CONSOLE}
+
+uses {$IFDEF UNIX} {$IFDEF UseCThreads}
+  cthreads, {$ENDIF} {$ENDIF}
+  Classes,
+  Interfaces, // this includes the LCL widgetset
+  SysUtils,
+  Forms {for application support},
+  checkrideutil {for future work with unzipping from resources},
+  resourcezipper;
+
+var
+  OurResourceZipper: TResourceZipper;
+  FMustWriteCheckRideResource: boolean;
+  FMustWriteCheckRideHelpResource: boolean;
+  FMustWriteAllResources: boolean;
+  FExeDirectory: string;
+
+  procedure ShowCommandLineHelp;
+  begin
+    writeln(ExtractFileName(Application.ExeName) + ' [options]');
+    writeln('-h or --help                         : help');
+    writeln('-o <dir> or --outputdirectory=<dir>  : output directory');
+    writeln('--writeresourcecheck                 : add zip as "poor man''s resource"');
+    writeln('                                       to the ' +
+      CheckRideExe + ' executable.');
+    writeln('--writeresourcehelp                  : add zip as "poor man''s resource"');
+    writeln('                                       to the ' +
+      CheckRideHelperExe + ' executable.');
+    writeln('                                       to the both executable.');
+  end;
+
+  procedure OptionsAndInit;
+  var
+    ErrorMessage: string;
+  begin
+    Application.CaseSensitiveOptions := False; //accept upper and lowercase
+    ErrorMessage := Application.CheckOptions('ho:',
+      'help outputdirectory: writeallresources writeresourcecheck writeresourcehelp');
+    if Length(ErrorMessage) > 0 then
+    begin
+      writeln(ErrorMessage);
+      ShowCommandLineHelp;
+      halt(1); //invalid options
+    end;
+
+
+    if (Application.HasOption('h', 'help')) or (Application.HasOption('?')) then
+    begin
+      ShowCommandLineHelp;
+      halt(0);
+    end;
+
+    FExeDirectory := '';
+    if Application.HasOption('o', 'outputdirectory') then
+    begin
+      //Get absolute path
+      //Trailed separator apparently not attached by ExpandFileName
+      // We write zips to output, this helps in troubleshooting.
+      FExeDirectory := ExpandFileName(
+        Trim(Application.GetOptionValue('o', 'outputdirectory')));
+      OurResourceZipper.ZipDirectory := FExeDirectory;
+    end;
+
+    if Application.HasOption('writeresourcecheck') then
+    begin
+      FMustWriteCheckRideResource := True;
+    end
+    else
+    begin
+      FMustWriteCheckRideResource := False;
+    end;
+
+    if Application.HasOption('writeallresources') then
+    begin
+      FMustWriteAllResources := True;
+    end
+    else
+    begin
+      FMustWriteAllResources := False;
+    end;
+
+    if Application.HasOption('writeresourcehelp') then
+    begin
+      FMustWriteCheckRideHelpResource := True;
+    end
+    else
+    begin
+      FMustWriteCheckRideHelpResource := False;
+    end;
+  end;
+
+begin
+  OurResourceZipper := TResourceZipper.Create;
+  try
+    writeln(ExtractFileName(Application.ExeName) +
+      ': zip CheckRide files for use into resource; optionally write resource.');
+    writeln('***NOTE: please update and recompile CheckRideResourceZipper.lpr');
+    writeln('         whenever you need files added to the resource.');
+    //Setup
+    OptionsAndInit; //Check what users asked us to do.
+    //Create zip(s), append to executable(s):
+    if (FMustWriteCheckRideResource or FMustWriteAllResources) then
+    begin
+      OurResourceZipper.Executable := FExeDirectory + CheckRideExe;
+      OurResourceZipper.WriteCheckRideResource;
+    end;
+    if (FMustWriteCheckRideHelpResource or FMustWriteAllResources) then
+    begin
+      OurResourceZipper.Executable := FExeDirectory + CheckRideHelperExe;
+      OurResourceZipper.WriteCheckRideHelperResource;
+    end;
+  finally
+    OurResourceZipper.Free;
+  end;
+end.

+ 33 - 0
Source/aboutform.lfm

@@ -0,0 +1,33 @@
+object InfoAboutForm: TInfoAboutForm
+  Left = 380
+  Height = 594
+  Top = 157
+  Width = 647
+  Caption = 'Info'
+  ClientHeight = 594
+  ClientWidth = 647
+  OnActivate = FormActivate
+  OnCreate = FormCreate
+  LCLVersion = '0.9.31'
+  object InfoMemo: TMemo
+    Left = 5
+    Height = 543
+    Top = 4
+    Width = 635
+    Lines.Strings = (
+      'Could not load file'
+    )
+    ReadOnly = True
+    ScrollBars = ssAutoBoth
+    TabOrder = 0
+  end
+  object CloseButton: TButton
+    Left = 565
+    Height = 25
+    Top = 559
+    Width = 75
+    Caption = 'Close'
+    OnClick = CloseButtonClick
+    TabOrder = 1
+  end
+end

+ 93 - 0
Source/aboutform.pas

@@ -0,0 +1,93 @@
+unit aboutform;
+
+{*
+This source code is provided under the MIT license:
+Copyright (C) 2011 by Reinier Olislagers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in 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:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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
+AUTHORS 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 IN
+THE SOFTWARE.
+*}
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;
+
+type
+
+  { TInfoAboutForm }
+
+  TInfoAboutForm = class(TForm)
+    CloseButton: TButton;
+    InfoMemo: TMemo;
+    procedure CloseButtonClick(Sender: TObject);
+    procedure FormActivate(Sender: TObject);
+    procedure FormCreate(Sender: TObject);
+  private
+    { private declarations }
+    FFileName: string;
+    FDoOnce: boolean;
+  public
+    { public declarations }
+    property Filename: string read FFileName write FFileName;
+  end;
+
+var
+  InfoAboutForm: TInfoAboutForm;
+
+implementation
+
+uses
+  CheckRideUtil;
+
+{$R *.lfm}
+
+{ TInfoAboutForm }
+
+procedure TInfoAboutForm.CloseButtonClick(Sender: TObject);
+begin
+  Close;
+end;
+
+procedure TInfoAboutForm.FormActivate(Sender: TObject);
+begin
+  if FDoOnce then
+  begin
+    try
+      InfoMemo.Lines.LoadFromFile(FFileName); //assumes file in same dir
+    except
+      try
+        InfoMemo.Lines.LoadFromFile(FResourceDir + DirectorySeparator + FFileName);
+        //Load from resource extract dir.
+      except
+        InfoMemo.Lines.Text := 'Sorry, could not load file ' + FFileName;
+      end;
+    end;
+    FDoOnce := False;
+  end;
+end;
+
+procedure TInfoAboutForm.FormCreate(Sender: TObject);
+begin
+  if FFileName = '' then
+    FFileName := 'readme.txt'; //Default
+  FDoOnce := True;
+end;
+
+end.

+ 143 - 0
Source/checkridehelperunit.lfm

@@ -0,0 +1,143 @@
+object CheckRideHelperMain: TCheckRideHelperMain
+  Left = 341
+  Height = 593
+  Top = 155
+  Width = 758
+  Caption = 'CheckRideHelper'
+  ClientHeight = 573
+  ClientWidth = 758
+  Menu = MainMenu1
+  OnActivate = FormActivate
+  OnClose = FormClose
+  OnCreate = FormCreate
+  LCLVersion = '1.0.1.2'
+  object ServerPort: TLabeledEdit
+    Left = 107
+    Height = 23
+    Top = 8
+    Width = 104
+    EditLabel.AnchorSideTop.Control = ServerPort
+    EditLabel.AnchorSideTop.Side = asrCenter
+    EditLabel.AnchorSideRight.Control = ServerPort
+    EditLabel.AnchorSideBottom.Control = ServerPort
+    EditLabel.AnchorSideBottom.Side = asrBottom
+    EditLabel.Left = 9
+    EditLabel.Height = 16
+    EditLabel.Top = 11
+    EditLabel.Width = 95
+    EditLabel.Caption = 'Helper server port'
+    EditLabel.ParentColor = False
+    EditLabel.Layout = tlBottom
+    LabelPosition = lpLeft
+    TabOrder = 0
+    OnEditingDone = ServerPortEditingDone
+  end
+  object ConnectButton: TButton
+    Left = 304
+    Height = 25
+    Hint = 'Start listening to remote support connections from helped party'
+    Top = 51
+    Width = 75
+    Caption = 'Listen'
+    OnClick = ConnectButtonClick
+    TabOrder = 1
+  end
+  object DisconnectButton: TButton
+    Left = 392
+    Height = 25
+    Hint = 'Disconnect/stop listening'
+    Top = 51
+    Width = 75
+    Caption = 'Disconnect'
+    OnClick = DisconnectButtonClick
+    TabOrder = 2
+  end
+  object Memo1: TMemo
+    Left = 8
+    Height = 498
+    Top = 88
+    Width = 750
+    TabOrder = 3
+  end
+  object WhatIsMyIPButton: TButton
+    Left = 536
+    Height = 25
+    Hint = 'Opens website that shows your external IP address'
+    Top = 51
+    Width = 93
+    Caption = 'What is my IP?'
+    OnClick = WhatIsMyIPButtonClick
+    TabOrder = 4
+  end
+  object PortScanButton: TButton
+    Left = 656
+    Height = 25
+    Hint = 'Opens website that lets you portscan your external IP to check if your port is open.'
+    Top = 51
+    Width = 75
+    Caption = 'Port open?'
+    OnClick = PortScanButtonClick
+    TabOrder = 5
+  end
+  object TunnelProcess: TAsyncProcess
+    Active = False
+    Options = []
+    Priority = ppNormal
+    StartupOptions = []
+    ShowWindow = swoNone
+    WindowColumns = 0
+    WindowHeight = 0
+    WindowLeft = 0
+    WindowRows = 0
+    WindowTop = 0
+    WindowWidth = 0
+    FillAttribute = 0
+    left = 272
+    top = 5
+  end
+  object VNCViewerProcess: TAsyncProcess
+    Active = False
+    Options = []
+    Priority = ppNormal
+    StartupOptions = []
+    ShowWindow = swoNone
+    WindowColumns = 0
+    WindowHeight = 0
+    WindowLeft = 0
+    WindowRows = 0
+    WindowTop = 0
+    WindowWidth = 0
+    FillAttribute = 0
+    left = 384
+    top = 5
+  end
+  object MainMenu1: TMainMenu
+    left = 472
+    top = 8
+    object FileMenu: TMenuItem
+      Caption = '&File'
+      object QuitMenu: TMenuItem
+        Caption = '&Quit'
+        OnClick = QuitMenuClick
+      end
+    end
+    object ToolsMenu: TMenuItem
+      Caption = '&Tools'
+      object CustomizeMenu: TMenuItem
+        Caption = '&Customize Checkride executable'
+        OnClick = CustomizeMenuClick
+      end
+    end
+    object HelpMenu: TMenuItem
+      Caption = '&Help'
+      object MenuLicenses: TMenuItem
+        Caption = '&Licenses'
+        OnClick = MenuLicensesClick
+      end
+      object About: TMenuItem
+        Caption = '&About'
+        OnClick = AboutClick
+      end
+    end
+  end
+end

+ 503 - 0
Source/checkridehelperunit.pas

@@ -0,0 +1,503 @@
+unit CheckRideHelperUnit;
+
+{*
+This source code is provided under the MIT license:
+Copyright (C) 2011 by Reinier Olislagers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in 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:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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
+AUTHORS 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 IN
+THE SOFTWARE.
+*}
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
+  StdCtrls, Grids, AsyncProcess, Menus;
+
+type
+
+  { TCheckRideHelperMain }
+
+  TCheckRideHelperMain = class(TForm)
+    MainMenu1: TMainMenu;
+    FileMenu: TMenuItem;
+    HelpMenu: TMenuItem;
+    About: TMenuItem;
+    ToolsMenu: Tmenuitem;
+    CustomizeMenu: Tmenuitem;
+    MenuLicenses: TMenuItem;
+    QuitMenu: TMenuItem;
+    PortScanButton: TButton;
+    WhatIsMyIPButton: TButton;
+    Memo1: TMemo;
+    TunnelProcess: TAsyncProcess;
+    ConnectButton: TButton;
+    DisconnectButton: TButton;
+    ServerPort: TLabeledEdit;
+    VNCViewerProcess: TAsyncProcess;
+    procedure AboutClick(Sender: TObject);
+    procedure ConnectButtonClick(Sender: TObject);
+    procedure DisconnectButtonClick(Sender: TObject);
+    procedure FormActivate(Sender: TObject);
+    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
+    procedure FormCreate(Sender: TObject);
+    procedure CustomizeMenuClick(Sender: TObject);
+    procedure MenuLicensesClick(Sender: TObject);
+    procedure PortScanButtonClick(Sender: TObject);
+    procedure QuitMenuClick(Sender: TObject);
+    procedure SetupConfigAndExes;
+    procedure ListenForHelp;
+    procedure DisconnectHelp;
+    procedure ShowCommandLineHelp;
+    procedure ServerPortEditingDone(Sender: TObject);
+    procedure WhatIsMyIPButtonClick(Sender: TObject);
+  private
+    FExePath: string;
+    FPortInCommandLine: boolean; //Does user want to override port using command line?
+    FListenOneTime: boolean; //Should we start listening in the OnActivate event?
+    FSetupComplete: boolean;
+    FVNCViewerFullPath: string;
+    FStunnelFullPath: string;
+    FListening: boolean; //Is the tunnel/vnc combo running and listening?
+    procedure Updatebuttons;
+    { private declarations }
+  public
+    { public declarations }
+  end;
+
+var
+  CheckRideHelperMain: TCheckRideHelperMain;
+
+implementation
+
+uses
+  Windows, CheckRideUtil, LCLIntf, aboutform, clientcustomizer;
+
+{$R *.lfm}
+
+{ TCheckRideHelperMain }
+const
+  VNCViewerExe = 'vncviewer.exe';
+  StunnelExe = 'stunnel.exe';
+  VNCViewerListenPort = 65001;
+
+
+procedure TCheckRideHelperMain.FormCreate(Sender: TObject);
+var
+  ErrorMessage: string;
+begin
+  FSetupComplete := False;
+  FListening := False;//We're not listening yet
+  FExePath := ExtractFilePath(Application.ExeName); //or ExtractFilePath(ParamStr(0))
+  FVNCViewerFullPath := FExePath + VNCViewerExe;  //FExePath already has trailing \
+  FStunnelFullPath := FExePath + StunnelExe;
+  //Find out if we need to start listening in the OnActivate event:
+  FListenOneTime := True;
+  // Check parameters.
+  // Host, port and name require parameters.
+  ErrorMessage := Application.CheckOptions('hxp:', 'noautoconnect help helperport:');
+  if Length(ErrorMessage) > 0 then
+  begin
+    Memo1.Append('Error: wrong command line options given:');
+    Memo1.Append(ErrorMessage);
+    ShowCommandLineHelp;
+  end;
+
+  if Application.HasOption('x', 'noautoconnect') then
+  begin
+    FListenOneTime := False;
+  end;
+
+  if Application.HasOption('h', 'help') then
+  begin
+    ShowCommandLineHelp;
+  end;
+
+  if Application.HasOption('p', 'helperport') then
+  begin
+    FPortInCommandLine := True;
+  end
+  else
+  begin
+    FPortInCommandLine := False;
+  end;
+end;
+
+procedure Tcheckridehelpermain.CustomizeMenuClick(Sender: TObject);
+var
+  TheForm: TForm;
+begin
+  TheForm := clientcustomizer.TfrmClientCustomizer.Create(Application);
+  try
+    TheForm.ShowModal;
+  finally
+    TheForm.Release; //free will be done by LCL code
+  end;
+end;
+
+procedure TCheckRideHelperMain.MenuLicensesClick(Sender: TObject);
+var
+  TheForm: TInfoAboutForm; //We need more precision than just TForm
+begin
+  TheForm := aboutform.TInfoAboutForm.Create(Application);
+  try
+    TheForm.Filename := 'License.txt';
+    TheForm.ShowModal;
+  finally
+    TheForm.Release;
+  end;
+end;
+
+procedure TCheckRideHelperMain.PortScanButtonClick(Sender: TObject);
+{description Open web browser to let user portscan own machine}
+const
+  URL = 'http://nmap-online.com/';
+begin
+  OpenURL(URL);
+end;
+
+procedure TCheckRideHelperMain.QuitMenuClick(Sender: TObject);
+begin
+  Close;
+end;
+
+procedure TCheckRideHelperMain.Updatebuttons;
+begin
+  ConnectButton.Enabled := not (FListening);
+  DisconnectButton.Enabled := FListening;
+end;
+
+procedure TCheckRideHelperMain.ConnectButtonClick(Sender: TObject);
+begin
+  if FListening = True then
+  begin
+    ShowMessage('Already connected. Please disconnect first.');
+  end
+  else
+  begin
+    ListenForHelp;
+  end;
+end;
+
+procedure TCheckRideHelperMain.AboutClick(Sender: TObject);
+var
+  TheForm: TInfoAboutForm; //We need more precision than just TForm
+begin
+  TheForm := aboutform.TInfoAboutForm.Create(Application);
+  try
+    TheForm.Filename := 'readme.txt';
+    TheForm.ShowModal;
+  finally
+    TheForm.Release;
+  end;
+end;
+
+procedure TCheckRideHelperMain.DisconnectButtonClick(Sender: TObject);
+begin
+  if FListening = False then
+  begin
+    ShowMessage('Connection not started, so I can''t disconnect.');
+  end
+  else
+  begin
+    DisconnectHelp;
+  end;
+end;
+
+procedure TCheckRideHelperMain.SetupConfigAndExes;
+{description Unpacks resources and sets up configuration. Needs to be called once in application.}
+begin
+  // Get resources from this executable.
+  // Note: won't be guaranteed portable to Linux/OSX
+  ResourceExtract(ParamStr(0));
+
+  if Trim(ServerPort.Text) = '' then
+  begin
+    if FPortInCommandLine = True then
+    begin
+      // Command line options override config file.
+      FConnectPort := StrToInt(Trim(Application.GetOptionValue('p', 'helperport')));
+      //Then we also override the rest
+      FConnectHelper := 'command line options';
+      FConnectHost := 'without name';
+    end;
+    ServerPort.Text := IntToStr(checkrideutil.FConnectPort);
+  end;
+  FSetupComplete := True;
+end;
+
+
+procedure TCheckRideHelperMain.FormActivate(Sender: TObject);
+begin
+  // Set up, one time only, hopefully
+  if FSetupComplete = False then
+  begin
+    SetupConfigAndExes; //Updates FSetupComplete.
+  end;
+  if FListenOneTime = True then
+  begin
+    FListenOneTime := False; //only do it once
+    ConnectButton.Enabled := False; //Don't let user click this
+    DisconnectButton.Enabled := False; //This just doesn't make sense now, either
+    Application.ProcessMessages; //Give Lazarus a chance to draw the screen.
+    ListenForHelp; //Actual connect/listen action
+  end;
+end;
+
+procedure TCheckRideHelperMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
+begin
+  // Clean up existing connections
+  DisconnectHelp;
+  // Clean up temp dir
+  CleanTempDir;
+end;
+
+procedure TCheckRideHelperMain.DisconnectHelp;
+var
+  ExitCode: integer = 0;
+begin
+  Screen.Cursor := crHourglass;
+  DisconnectButton.Enabled := False; //Don't let user click any more
+  try
+    // Stop/kill viewer
+    if VNCViewerProcess.Running = True then
+    begin
+      VNCViewerProcess.Terminate(ExitCode);
+    end;
+
+    //Stop/kill tunnel
+    if TunnelProcess.Running = True then
+    begin
+      TunnelProcess.Terminate(ExitCode);
+    end;
+
+    //Check again after spending some time closing stunnel
+    if VNCViewerProcess.Running = True then
+    begin
+      //Wait a bit for viewer to close
+      Application.ProcessMessages;
+      Sleep(200);
+      if VNCViewerProcess.Running = True then
+      begin
+        //If it won't go nicely, then force it...
+        PostMessage(VNCViewerProcess.Handle, WM_QUIT, 0, 0);
+        Application.ProcessMessages;
+        Sleep(500);
+        if VNCViewerProcess.Running = True then
+        begin
+          if TerminateProcess(VNCViewerProcess.Handle, 255) then
+          begin
+            Memo1.Append('Finished killing ' + VNCViewerExe + ' process.');
+          end
+          else
+          begin
+            Memo1.Append('Error killing ' + VNCViewerExe +
+              ' process. Please stop the program yourself.');
+          end;
+        end;
+      end
+      else
+      begin
+        Memo1.Append('Finished stopping ' + VNCViewerExe);
+      end;
+    end
+    else
+    begin
+      Memo1.Append('Finished stopping ' + VNCViewerExe);
+    end;
+
+    //Check after possibly spending some time closing
+    //VNCViewer
+    if TunnelProcess.Running = True then
+    begin
+      //Wait a bit for tunnel to close
+      Application.ProcessMessages;
+      Sleep(200);
+      if TunnelProcess.Running = True then
+      begin
+        // Stop stunnel
+        if SysUtils.ExecuteProcess(FindDefaultExecutablePath('taskkill.exe'),
+          ' /f /t /im ' + StunnelExe + '') <> 0 then
+        begin
+          Memo1.Append('Error running taskkill.exe /f /t /im ' + StunnelExe);
+        end
+        else
+        begin
+          Memo1.Append('Finished killing stunnel');
+        end;
+      end
+      else
+      begin
+        Memo1.Append('Finished stopping ' + StunnelExe);
+      end;
+    end
+    else
+    begin
+      //Tunnel had already stopped
+      Memo1.Append('Finished stopping ' + StunnelExe);
+    end;
+
+    try
+      CleanSystemTray; //Get rid of zombie icons
+    except
+      on E: Exception do
+      begin
+        Memo1.Append('Small problem cleaning up icons. Details:' +
+          E.ClassName + '/' + E.Message);
+      end;
+    end;
+
+    FListening := False;
+    Memo1.Append('Remote support session ended.');
+    UpdateButtons;
+    Screen.Cursor := crDefault;
+  except
+    on E: Exception do
+    begin
+      FListening := False; //Let's assume things were closed
+      UpdateButtons;
+      Memo1.Append('Error running commands; error was ' + E.ClassName + '/' + E.Message);
+      Screen.Cursor := crDefault;
+    end;
+  end;
+end;
+
+procedure TCheckRideHelperMain.ShowCommandLineHelp;
+begin
+  Memo1.Append('Command line options:');
+  Memo1.Append('-h or --help: show this information.');
+  Memo1.Append('-x or --noautoconnect: don''t connect/listen automatically.');
+  Memo1.Append(
+    '-p or --helperport: helper port number that we listen on. Overrides CheckRide.conf');
+end;
+
+procedure TCheckRideHelperMain.ServerPortEditingDone(Sender: TObject);
+begin
+  CheckRideUtil.FConnectPort := StrToIntDef(ServerPort.Text, 3334);
+end;
+
+procedure TCheckRideHelperMain.WhatIsMyIPButtonClick(Sender: TObject);
+{description Open web browser to show external IP}
+const
+  URL = 'http://automation.whatismyip.com/n09230945.asp';
+begin
+  OpenURL(URL);
+end;
+
+procedure TCheckRideHelperMain.ListenForHelp;
+{description This does the actual work }
+var
+  VNCViewerParameters: string;
+  LogFile: string;
+begin
+  // note: include space in front of option
+  // interesting options gleaned from vncviewer -help
+  // /8bit or /64colors: for improved connection speed.
+  // Don't know what enablecache does, but it sounds good!
+  // /listen 33334 listens on specified port, so you shouldn't have anything listening on that port
+  // /proxy proxyhost [portnum]
+  // /encoding zrle|zywrle|tight|zlib|zlibhex|ultra => also found raw rre corre hextile ultra2 in source (VNCOptions.cpp, near line 672)
+  // ultra2 might be useful; doesn't seem to work well though, see below
+  // /autoacceptincoming automatically accept incoming connections
+  // /socketkeepalivetimeout n
+  // /enablecache
+  // /autoacceptnodsm: useful for ignoring encryption if you have specified a DSM. Not useful to us ;)
+  // Finally, we have to specify /quickoption 8 to force manual parameters instead of auto.
+  // /quickoption
+  // 1: auto mode (zrle, full colors, cache)
+  // 2: LAN (hextile, full colors, no cache)
+  // 3: medium (zrle, 256 colors, no cache)
+  // 4: modem (zrle, 64 colors, cache)
+  // 5: slow (zrle, 8 colors, cache)
+  // 7: ULTRA_LAN (ultra enc, full color)
+  // 8: apparently manual
+  // apparently you can also disable auto mode by specifying noauto
+  // NOTE: at least in 1.0.9.6.1 and earlier:
+  // vncviewer -listen 65001 -quickoption 8 -8bit -encoding ultra2 -enablecache -disablesponsor -autoacceptincoming -autoacceptnodsm -loglevel 10 -logfile C:\<somewhere>TMP00015.tmp
+  // seems to give a crash in WinVNC: an unhandled Win32 exception occurred in WinVNC [724]
+  //
+  // Previous versions of this code used quickoption 3, which caused text entries/fonts in command windows to not appear on the screen
+  // and other update anomalies.
+  LogFile := SysUtils.GetTempFileName();
+  VNCViewerParameters :=
+    ' ' + '-listen ' + IntToStr(VNCViewerListenPort) + ' -quickoption 8 ' +
+    ' -encoding zywrle ' +
+    ' -disablesponsor -autoacceptincoming -autoacceptnodsm -loglevel 10 -logfile '
+    + LogFile;
+  if TunnelProcess.Running = True then
+  begin
+    ShowMessage('Tunnel has already been started. Stop tunnel before connecting.');
+    Exit;
+  end;
+
+  ConnectButton.Enabled := False; //Don't let user click twice
+  Memo1.Append('Reading profile ' + FConnectHelper + ' for server ' +
+    FConnectHost + ' listening on port ' + IntToStr(FConnectPort));
+  // Start up stunnel asynchronously: let it run in parallel with the rest of the program
+  try
+    TunnelProcess.CommandLine :=
+      FStunnelFullPath + ' ' + CustomStunnelconfig(Helper) + '';
+    TunnelProcess.Execute;
+    Memo1.Append('Finished setting up SSL/TLS tunnel listening on port ' +
+      IntToStr(CheckRideUtil.FConnectPort));
+  except
+    Screen.Cursor := crDefault;
+    Memo1.Append('Error running ' + FStunnelFullPath + ' stunnelhelper.conf');
+    exit; //exit procedure, useless to continue
+  end;
+
+  try
+    if TunnelProcess.Running = False then;
+    begin
+      Memo1.Append('Waiting 5 seconds for stunnel to come up.');
+      sleep(5000);
+    end;
+
+    // Start listening viewer. Don't use service parameter for this
+    try
+      VNCViewerProcess.CommandLine := FVNCViewerFullPath + VNCViewerParameters;
+      VNCViewerProcess.Execute;
+      FListening := True;
+      Memo1.Append('Finished setting up VNCViewer listening to tunnel.');
+      Memo1.Append('Information: parameters used: ' + VNCViewerParameters);
+      Memo1.Append('VNCViewer log file: ' + logfile);
+    except
+      FListening := False;
+      Screen.Cursor := crDefault;
+      Memo1.Append('Error running ' + FVNCViewerFullPath + ' ' + VNCViewerParameters);
+      exit; //exit procedure, useless to continue
+    end;
+    UpdateButtons;
+    Memo1.Append('Listening VNCViewer started...');
+    Screen.Cursor := crDefault;
+  except
+    on E: Exception do
+    begin
+      //Assume listening so we can clean up
+      FListening := True;
+      Screen.Cursor := crDefault;
+      Memo1.Append('Error running commands; error was ' + E.ClassName + '/' + E.Message);
+      Memo1.Append('Cleaning up.');
+      DisconnectHelp;
+    end;
+  end;
+end;
+
+end.
+

+ 512 - 0
Source/checkrideutil.pas

@@ -0,0 +1,512 @@
+unit CheckRideUtil;
+
+{*
+This source code is provided under the MIT license:
+Copyright (C) 2011 by Reinier Olislagers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in 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:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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
+AUTHORS 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 IN
+THE SOFTWARE.
+*}
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils;
+
+const
+  CheckRideConfigFileName = 'CheckRide.conf'; //Change this when filename changes
+  CheckRideExe = 'checkride.exe'; //Change this if you change the executable file name
+  CheckRideHelperExe = 'checkridehelper.exe';
+  //Change this if you change the executable file name
+  HelperHostExample = 'changethisline.example.com';
+  //Edit this when changing the config file
+  HelperPortExample = '33334';//Edit this when changing the config file
+  HelperNameExample = 'Wonko the Sane';//Edit this when changing the config file
+
+type
+  TStunnelMode = (Helped, Helper);
+
+var
+  FConnectHost: string; //Host to connect to (CheckRide)
+  FConnectPort: integer; //Port to connect to (CheckRide) or listen on (CheckRideHelper)
+  FConnectHelper: string; //Show this to CheckRide as a connection identifier
+  FResourceDir: string = '';
+//Directory where configs etc are temporarily placed, no trailing directory separator.
+
+procedure CheckDebug(const Message: string);
+//Our own version of debugln that doesn't run if DEBUG not defined
+procedure CreateUniqueTempDir;
+procedure CleanTempDir; //Cleans up the temp dir, if created.
+procedure CleanSystemTray;
+function ServiceExists(ServiceName: string): boolean;
+function IsServiceRunning(ServiceName: string): boolean;
+procedure StartService(ServiceName: string);
+procedure StopService(ServiceName: string);
+function ProcessExists(ExeFileName: string): boolean;
+function WaitForProcessToDie(ProcessName: string): boolean;
+function CustomStunnelconfig(TypeOfTunnel: TStunnelMode): string;
+{ Extract required executables, config files out of resource so we can use them. Returns directory, with trailing \ or /, where extracted.}
+function ResourceExtract(ExeFile: string): string;
+
+
+implementation
+
+
+uses
+  FileUtil,
+  ServiceManager,
+  JwaTlHelp32 {for running processes},
+  JwaWinType {for processes declarations},
+  JwaWinBase {just a guess: for closing process handles},
+  JwaWinSvc {for services declarations, always required},
+  jwawinuser {for clearing tray icon/notification area},
+  Zipper {extraction of resources},
+  IniFiles, poormansresource,
+  lclproc {for debugging};
+
+function WaitForProcessToDie(ProcessName: string): boolean;
+  {description Waits until process is dead, but respects timeout}
+const
+  SleepTimeOut = 4;
+var
+  i: integer;
+begin
+  i := 0;
+  while ProcessExists(ProcessName) = False do
+  begin
+    sleep(500);
+    //Application.ProcessMessages; //Handle GUI events
+    i := i + 1;
+    if i >= SleepTimeOut then
+    begin
+      Result := False;
+      break;
+    end;
+  end; //while
+  Result := True;
+end;
+
+procedure CleanSystemTray;
+{description Clean dead icons from system tray/notification area}
+var
+  hNotificationArea: HWND;
+  r: RECT;
+  x: integer;
+  y: integer;
+begin
+  hNotificationArea := FindWindowEx(
+    FindWindowEx(FindWindowEx(FindWindowEx(0, 0, 'Shell_TrayWnd', ''),
+    0, 'TrayNotifyWnd', ''), 0, 'SysPager', ''), 0, 'ToolbarWindow32',
+    'Notification Area');
+  GetClientRect(hNotificationArea, r);
+
+  //Now we've got the area, force it to update
+  //by sending mouse messages to it.
+  x := 0;
+  y := 0;
+  while x < r.Right do
+  begin
+    while y < r.Bottom do
+    begin
+      SendMessage(hNotificationArea, WM_MOUSEMOVE, 0, (y shl 16) + x);
+      y := y + 5;
+    end;
+    x := x + 5;
+  end;
+end;
+
+function ServiceExists(ServiceName: string): boolean;
+  {description Checks if a Windows service exists}
+var
+  Services: TServiceManager;
+  ServiceStatus: TServiceStatus;
+begin
+  //Check for existing services
+  Services := TServiceManager.Create(nil);
+  try
+    try
+      Services.Acces := SC_MANAGER_CONNECT; //Note typo in property.
+      //We don't need more access permissions than this; by default
+      //the servicemanager is trying to get all access
+      Services.Connect;
+      try
+        Services.GetServiceStatus(ServiceName, ServiceStatus);
+        Result := True;
+      except
+        Result := False; //assume service does not exist
+      end;
+      Services.Disconnect;
+    except
+      on E: Exception do
+      begin
+        Result := False;
+      end;
+    end;
+  finally
+    Services.Free;
+  end;
+end;
+
+function IsServiceRunning(ServiceName: string): boolean;
+  {description Checks if a Windows service is running}
+var
+  Services: TServiceManager;
+  ServiceStatus: TServiceStatus;
+begin
+  //Check for existing services
+  //equivalent to sc query <servicename>
+  Services := TServiceManager.Create(nil);
+  try
+    try
+      Services.Acces := SC_MANAGER_CONNECT; //Note typo in .Access property.
+      Services.Connect; //Connect with requested access permissions.
+      Services.GetServiceStatus(ServiceName, ServiceStatus);
+      if ServiceStatus.dwCurrentState = SERVICE_RUNNING then
+      begin
+        Result := True;
+      end
+      else
+      begin
+        Result := False;
+      end;
+      Services.Disconnect;
+    except
+      on E: EServiceManager do
+      begin
+        // A missing service might throw a missing handle exception? No?
+        {LogOutput('Error getting service information for ' + ServiceName +
+          '. Technical details: ' + E.ClassName + '/' + E.Message);}
+        Result := False;
+        raise; //rethrow original exception
+      end;
+      on E: Exception do
+      begin
+        {LogOutput('Error getting service information for ' + ServiceName +
+          '. Technical details: ' + E.ClassName + '/' + E.Message);
+          }
+        Result := False;
+        raise; //rethrow original exception
+      end;
+    end;
+  finally
+    Services.Free;
+  end;
+end;
+
+procedure StartService(ServiceName: string);
+{description Gives a service the start command}
+var
+  Services: TServiceManager;
+  ServiceStatus: TServiceStatus;
+begin
+  Services := TServiceManager.Create(nil);
+  try
+    try
+      Services.Acces := SC_MANAGER_CONNECT; //Note typo in .Access property.
+      Services.Connect; //Connect with requested access permissions.
+      Services.GetServiceStatus(ServiceName, ServiceStatus);
+      if ServiceStatus.dwCurrentState <> SERVICE_RUNNING then
+      begin
+        Services.StartService(ServiceName, nil);
+      end;
+      Services.Disconnect;
+    except
+      on E: EServiceManager do
+      begin
+        // A missing service might throw a missing handle exception? No?
+        {LogOutput('Error getting service information for ' + ServiceName +
+          '. Technical details: ' + E.ClassName + '/' + E.Message);}
+        raise; //rethrow original exception
+      end;
+      on E: Exception do
+      begin
+        {LogOutput('Error getting service information for ' + ServiceName +
+          '. Technical details: ' + E.ClassName + '/' + E.Message);
+          }
+        raise; //rethrow original exception
+      end;
+    end;
+  finally
+    Services.Free;
+  end;
+end;
+
+procedure StopService(ServiceName: string);
+{description Gives a service the stop command}
+var
+  Services: TServiceManager;
+  ServiceStatus: TServiceStatus;
+begin
+  Services := TServiceManager.Create(nil);
+  try
+    try
+      Services.Acces := SC_MANAGER_CONNECT; //Note typo in .Access property.
+      Services.Connect; //Connect with requested access permissions.
+      Services.GetServiceStatus(ServiceName, ServiceStatus);
+      if ServiceStatus.dwCurrentState = SERVICE_RUNNING then
+      begin
+        Services.StopService(ServiceName, True);
+      end;
+      Services.Disconnect;
+    except
+      on E: EServiceManager do
+      begin
+        // A missing service might throw a missing handle exception? No?
+        {LogOutput('Error getting service information for ' + ServiceName +
+          '. Technical details: ' + E.ClassName + '/' + E.Message);}
+        raise; //rethrow original exception
+      end;
+      on E: Exception do
+      begin
+        {LogOutput('Error getting service information for ' + ServiceName +
+          '. Technical details: ' + E.ClassName + '/' + E.Message);
+          }
+        raise; //rethrow original exception
+      end;
+    end;
+  finally
+    Services.Free;
+  end;
+end;
+
+function ProcessExists(ExeFileName: string): boolean;
+{description checks if the process is running. Adapted for freepascal from:
+URL: http://www.swissdelphicenter.ch/torry/showcode.php?id=2554}
+var
+  ContinueLoop: BOOL;
+  FSnapshotHandle: THandle;
+  FProcessEntry32: TProcessEntry32;
+begin
+  FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+  FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
+  ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
+  Result := False;
+
+  while integer(ContinueLoop) <> 0 do
+  begin
+    if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) =
+      UpperCase(ExeFileName)) or (UpperCase(FProcessEntry32.szExeFile) =
+      UpperCase(ExeFileName))) then
+    begin
+      Result := True;
+    end;
+    ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
+  end;
+  CloseHandle(FSnapshotHandle);
+end;
+
+procedure CheckDebug(const Message: string);
+begin
+  {$IFDEF DEBUG}
+  DebugLn(DateTimeTostr(Now) + ': ' + Message);
+  sleep(500);//hopefully give time to write
+  {$ENDIF DEBUG}
+end;
+
+procedure CreateUniqueTempDir;
+{description Create a uniquely named directory under the user's temporary directory
+for this application - one temp dir for all files. Resulting dir in FTempDir
+Adapted from http://www.delphifaq.net/how-to-get-a-unique-file-name/}
+const
+  InvalidDir = '++&INVALID_TEMP_DIR!!';
+var
+  chTemp: char;
+  DirectoryName: string = '';
+  TempDir: string; //Our user's temporary directory
+begin
+  // Only create dir if it doesn't already exist:
+  if (FResourceDir = '') or (FResourceDir = InvalidDir) then
+  begin
+    TempDir := SysUtils.GetTempDir; //Apparently contains trailing path separator
+
+    // Get subdirectory name that doesn't already exist:
+    repeat
+      Randomize;
+      repeat
+        chTemp := Chr(Random(43) + 47);
+        if Length(DirectoryName) = 8 then
+          DirectoryName := DirectoryName + '.'
+        else if chTemp in ['0'..'9', 'A'..'Z'] then
+          DirectoryName := DirectoryName + chTemp;
+      until Length(DirectoryName) = 12;
+    until not DirectoryExists(TempDir + DirectoryName);
+
+    //Once we're sure we have a new directory:
+    try
+      mkdir(TempDir + DirectoryName);
+      FResourceDir := TempDir + DirectoryName; //Store the result
+    except
+      //This is bad.
+      FResourceDir := InvalidDir;
+      raise Exception.Create('Cannot create temporary directory.');
+    end;
+  end;
+end;
+
+procedure CleanTempDir;
+begin
+  if FResourceDir <> '' then
+  begin
+    DeleteDirectory(FResourceDir, False); //get rid of directory and children
+  end;
+end;
+
+
+function ResourceExtract(ExeFile: string): string;
+  {description Extracts required executables and other files from our poor man's resource to
+temporary directory - stored in FResourceDir}
+{NOTE: if you change files in the resources, please adjust
+CheckRideResourceZipper.lpr accordingly and recompile.
+CheckRideResourceZipper.exe is called before build of CheckRide and
+CheckRideHelper, so it needs to know about changes.}
+const
+  ResourceName = 'ALL';
+var
+  CheckRideConfigFile: string = ''; //Source for config adjustments
+  CheckRideConfigIni: TMemIniFile;
+  // Contains CheckRideConfig data for modifying templates
+  CheckRideConfigStrings: TStringList;// Contains data from ConfigResource
+  PoorMansResource: TPayload;
+  UnzipFile: TUnZipper;
+  ZipToExtract: string;
+begin
+  // Init, just to make sure
+  Result := '';
+  if FResourceDir = '' then
+  begin
+    CreateUniqueTempDir;
+  end;
+
+  // Point resource stream to the resource
+  PoorMansResource := TPayload.Create(ExeFile); //point to this executable
+  {Note: Paramstr(0) not guaranteed portable to Unix etc...}
+  UnzipFile := TUnZipper.Create();
+  try
+    //todo: we might streamline this, reading into memory and unzipping
+    //relevant files, directly reading others. Not worth the effort for
+    //minimum speed gains.
+    ZipToExtract := FResourceDir + DirectorySeparator + ResourceName + '.zip';
+    CheckDebug('ZipToExtract: ' + ZipToExtract); //is this actually the right file?
+    PoorMansResource.PayloadIntoFile(ZipToExtract);
+    UnzipFile.FileName := ZipToExtract;
+    UnzipFile.OutputPath := FResourceDir;
+    UnzipFile.UnZipAllFiles; //into temp dir, we hope
+  finally
+    UnzipFile.Free;
+    PoorMansResource.Free;
+  end;
+  try
+    DeleteFile(PChar(ZipToExtract));
+  except
+    //ignore errors deleting temp file; let's hope the file system will do this in time.
+  end;
+
+  //Now read in config
+  CheckRideConfigIni := TMemIniFile.Create(CheckRideConfigFile, False);
+  CheckRideConfigStrings := TStringList.Create;
+  try
+    try
+      // If ini file exists in exe directory, use that.
+      CheckRideConfigFile := ExtractFilePath(ParamStr(0)) + CheckRideConfigFileName;
+      CheckRideConfigStrings.LoadFromFile(CheckRideConfigFile);
+    except
+      // Fallback: use resource embedded in program
+      CheckRideConfigStrings.LoadFromFile(FResourceDir + DirectorySeparator +
+        CheckRideConfigFileName);
+    end;
+    // Load strings into ini object for further processing:
+    CheckRideConfigIni.SetStrings(CheckRideConfigStrings);
+    FConnectHost := Trim(CheckRideConfigIni.ReadString('Default',
+      'HelperHost', 'thiswillnotwork.example.com'));
+    FConnectPort := CheckRideConfigIni.ReadInteger('Default', 'HelperPort', 33334);
+    FConnectHelper := CheckRideConfigIni.ReadString('Default',
+      'HelperName', 'FallbackHelperInProgram');
+    Result := FResourceDir + DirectorySeparator; //Show valid result
+  finally
+    CheckRideConfigIni.Free;
+    CheckRideConfigStrings.Free;
+  end;
+end;
+
+function CustomStunnelconfig(TypeOfTunnel: TStunnelMode): string;
+  {description Applies CheckRide.conf to relevant template file, writes
+result to temporary file and returns the config file name.
+If CheckRide.conf doesn't exist in the executable directory,
+if necessary extract from resource and use.}
+const
+  HostReplace = '$(HELPERSERVERNAME)';
+  PortReplace = '$(HELPERSERVERPORT)';
+  HelpedTemplateName = 'stunnelhelped.conf.template';
+  HelpedConfigName = 'stunnelhelped.conf';
+  HelperTemplateName = 'stunnelhelper.conf.template';
+  HelperConfigName = 'stunnelhelper.conf';
+var
+  HelpedResultFile: string; //Final config file
+  HelperResultFile: string; //Final config file
+  ResultingConfig: TStringList; // Template with checkrideconfig replacements run
+begin
+  // Init, just to make sure
+  if FResourceDir = '' then
+  begin
+    CreateUniqueTempDir;
+  end;
+
+  HelpedResultFile := FResourceDir + DirectorySeparator + HelpedConfigName;
+  HelperResultFile := FResourceDir + DirectorySeparator + HelperConfigName;
+  ResultingConfig := TStringList.Create;
+  try
+    // Replace strings in helped template and save
+    case TypeOfTunnel of
+      Helped:
+      begin
+        ResultingConfig.LoadFromFile(FResourceDir + DirectorySeparator +
+          HelpedTemplateName);
+      end;
+      Helper:
+      begin
+        ResultingConfig.LoadFromFile(FResourceDir + DirectorySeparator +
+          HelperTemplateName);
+      end;
+      else
+        raise Exception.Create('Invalid TypeOfTunnel selected.');
+    end;
+    ResultingConfig.Text := StringReplace(ResultingConfig.Text,
+      HostReplace, FConnectHost, [rfReplaceAll]);
+    ResultingConfig.Text := StringReplace(ResultingConfig.Text,
+      PortReplace, IntToStr(FConnectPort), [rfReplaceAll]);
+    case TypeOfTunnel of
+      Helped:
+      begin
+        ResultingConfig.SaveToFile(HelpedResultFile);
+        Result := HelpedResultFile;
+      end;
+      Helper:
+      begin
+        ResultingConfig.SaveToFile(HelperResultFile);
+        Result := HelperResultFile;
+      end;
+      else
+        raise Exception.Create('Invalid TypeOfTunnel selected.');
+    end;
+  finally
+    ResultingConfig.Free;
+  end;
+end;
+
+end.

+ 123 - 0
Source/clientcustomizer.lfm

@@ -0,0 +1,123 @@
+object frmClientCustomizer: TfrmClientCustomizer
+  Left = 367
+  Height = 230
+  Top = 216
+  Width = 582
+  Caption = 'Client Customizer'
+  ClientHeight = 230
+  ClientWidth = 582
+  LCLVersion = '0.9.31'
+  object ServerPort: TLabeledEdit
+    Left = 128
+    Height = 23
+    Hint = 'Port on helper server'
+    Top = 152
+    Width = 104
+    EditLabel.AnchorSideTop.Control = ServerPort
+    EditLabel.AnchorSideTop.Side = asrCenter
+    EditLabel.AnchorSideRight.Control = ServerPort
+    EditLabel.AnchorSideBottom.Control = ServerPort
+    EditLabel.AnchorSideBottom.Side = asrBottom
+    EditLabel.Left = 30
+    EditLabel.Height = 16
+    EditLabel.Top = 155
+    EditLabel.Width = 95
+    EditLabel.Caption = 'Helper server port'
+    EditLabel.ParentColor = False
+    EditLabel.Layout = tlBottom
+    Enabled = False
+    LabelPosition = lpLeft
+    TabOrder = 3
+  end
+  object ServerName: TLabeledEdit
+    Left = 128
+    Height = 23
+    Hint = 'Hostname or IP address of helper server'
+    Top = 112
+    Width = 216
+    EditLabel.AnchorSideTop.Control = ServerName
+    EditLabel.AnchorSideTop.Side = asrCenter
+    EditLabel.AnchorSideRight.Control = ServerName
+    EditLabel.AnchorSideBottom.Control = ServerName
+    EditLabel.AnchorSideBottom.Side = asrBottom
+    EditLabel.Left = 22
+    EditLabel.Height = 16
+    EditLabel.Hint = 'Server name or IP address for the helper'
+    EditLabel.Top = 115
+    EditLabel.Width = 103
+    EditLabel.Caption = 'Helper server name'
+    EditLabel.ParentColor = False
+    Enabled = False
+    LabelPosition = lpLeft
+    TabOrder = 2
+  end
+  object HelperName: TLabeledEdit
+    Left = 128
+    Height = 23
+    Hint = 'For display purposes only'
+    Top = 72
+    Width = 216
+    EditLabel.AnchorSideTop.Control = HelperName
+    EditLabel.AnchorSideTop.Side = asrCenter
+    EditLabel.AnchorSideRight.Control = HelperName
+    EditLabel.AnchorSideBottom.Control = HelperName
+    EditLabel.AnchorSideBottom.Side = asrBottom
+    EditLabel.Left = 56
+    EditLabel.Height = 16
+    EditLabel.Hint = 'Server name or IP address for the helper'
+    EditLabel.Top = 75
+    EditLabel.Width = 69
+    EditLabel.Caption = 'Helper name'
+    EditLabel.ParentColor = False
+    Enabled = False
+    LabelPosition = lpLeft
+    TabOrder = 1
+  end
+  object CheckRideFile: TFileNameEdit
+    Left = 128
+    Height = 23
+    Top = 16
+    Width = 214
+    OnAcceptFileName = CheckRideFileAcceptFileName
+    DialogOptions = []
+    FilterIndex = 0
+    HideDirectories = False
+    ButtonWidth = 23
+    NumGlyphs = 0
+    MaxLength = 0
+    TabOrder = 0
+    OnEditingDone = CheckRideFileEditingDone
+  end
+  object lblCheckRideExe: TLabel
+    Left = 8
+    Height = 16
+    Top = 16
+    Width = 116
+    Alignment = taRightJustify
+    Caption = 'CheckRide executable'
+    ParentColor = False
+  end
+  object btnSave: TButton
+    Left = 440
+    Height = 25
+    Top = 150
+    Width = 75
+    Caption = 'Save as...'
+    Enabled = False
+    OnClick = btnSaveClick
+    TabOrder = 5
+  end
+  object btnCancel: TButton
+    Left = 352
+    Height = 25
+    Top = 150
+    Width = 75
+    Caption = 'Cancel'
+    OnClick = btnCancelClick
+    TabOrder = 4
+  end
+  object SaveCheckRide: TSaveDialog
+    left = 504
+    top = 68
+  end
+end

+ 131 - 0
Source/clientcustomizer.pas

@@ -0,0 +1,131 @@
+unit clientcustomizer;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, Fileutil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
+  EditBtn, StdCtrls;
+
+type
+
+  { TfrmClientCustomizer }
+
+  TfrmClientCustomizer = class(TForm)
+    Btncancel: TButton;
+    Btnsave: TButton;
+    Checkridefile: Tfilenameedit;
+    Lblcheckrideexe: Tlabel;
+    SaveCheckride: Tsavedialog;
+    Servername: Tlabelededit;
+    HelperName: TLabeledEdit;
+    Serverport: Tlabelededit;
+    procedure Btncancelclick(Sender: TObject);
+    procedure Btnsaveclick(Sender: TObject);
+    procedure CheckRideFileAcceptFileName(Sender: TObject; var Value: string);
+    procedure Checkridefileeditingdone(Sender: TObject);
+  private
+    procedure FindAndReplace(TextFile, LookFor, ReplaceWith: string);
+    procedure UpdateEditControls(SwitchOn: boolean);
+  public
+    { public declarations }
+  end;
+
+var
+  frmClientCustomizer: TfrmClientCustomizer;
+
+implementation
+
+uses resourcezipper, checkrideutil;
+
+
+{$R *.lfm}
+
+{ TfrmClientCustomizer }
+
+procedure Tfrmclientcustomizer.Checkridefileeditingdone(Sender: TObject);
+begin
+  UpdateEditControls(FileExists(CheckRideFile.Text));
+end;
+
+procedure TfrmClientCustomizer.FindAndReplace(TextFile, LookFor, ReplaceWith: string);
+var
+  slFile: TStringList;
+begin
+  slFile := TStringList.Create;
+  try
+    slFile.LoadFromFile(TextFile);
+    slFile.Text := StringReplace(slFile.Text, LookFor, ReplaceWith, [rfReplaceAll]);
+    slFile.SaveToFile(TextFile);
+  finally
+    slFile.Free;
+  end;
+end;
+
+procedure Tfrmclientcustomizer.Btncancelclick(Sender: TObject);
+begin
+  UpdateEditControls(False);
+  CheckRideFile.Text := '';
+end;
+
+procedure Tfrmclientcustomizer.Btnsaveclick(Sender: TObject);
+var
+  TargetFileName: string;
+  Resource: TResourceZipper;
+  ResourceDir: string;
+begin
+  //Suggestion:
+  SaveCheckride.FileName := CheckRideFile.FileName + '_' + Trim(HelperName.Text) + '.exe';
+  if SaveCheckride.Execute then
+  begin
+    Resource := TResourceZipper.Create;
+    try
+      TargetFileName := SaveCheckride.FileName;
+      CopyFile(Checkridefile.FileName, TargetFileName);
+
+      // Extract CheckRide.exe resource into temp dir...
+      ResourceDir := ResourceExtract(TargetFileName);
+      if ResourceDir = EmptyStr then
+        raise Exception.Create('Resource extraction failed.');
+
+      // ...Change contents...
+      FindAndReplace(ResourceDir + CheckRideConfigFileName, HelperHostExample,
+        ServerName.Text);
+      FindAndReplace(ResourceDir + CheckRideConfigFileName, HelperPortExample,
+        ServerPort.Text);
+      FindAndReplace(ResourceDir + CheckRideConfigFileName, HelperNameExample,
+        HelperName.Text);
+
+      //...and overwreite resource in target file
+      Resource.Executable := TargetFileName;
+      Resource.ZipSourceDir := ResourceDir; //Point to changed resource files
+      Resource.WriteCheckRideResource; //Replace existing resource
+      //todo: clear out temp files
+      ShowMessage('Custom checkride executable has been saved as: ' + TargetFileName);
+    finally
+      Resource.Free;
+      try
+        UpdateEditControls(false);
+      finally
+        //ignore errors
+      end;
+    end;
+  end;
+end;
+
+procedure TfrmClientCustomizer.CheckRideFileAcceptFileName(Sender: TObject;
+  var Value: string);
+begin
+  UpdateEditControls(True);
+end;
+
+procedure Tfrmclientcustomizer.UpdateEditControls(SwitchOn: boolean);
+begin
+  ServerPort.Enabled := SwitchOn;
+  ServerName.Enabled := SwitchOn;
+  HelperName.Enabled := SwitchOn;
+  btnSave.Enabled := SwitchOn;
+end;
+
+end.

+ 1 - 0
Source/manifest.rc

@@ -0,0 +1 @@
+1 24 "manifest.xml"

+ 17 - 0
Source/manifest.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="CompanyName.ProductName.YourApp" type="win32"/>
+ <description>Remote support help tool.</description>
+ <dependency>
+  <dependentAssembly>
+   <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
+  </dependentAssembly>
+ </dependency>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+  <security>
+   <requestedPrivileges>
+    <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
+   </requestedPrivileges>
+  </security>
+ </trustInfo>
+</assembly>

+ 252 - 0
Source/poormansresource.pas

@@ -0,0 +1,252 @@
+unit poormansresource;
+
+{$mode objfpc}{$H+}
+// Alternative way to store data in an .exe file
+// doesn't use resources, but just adds stuff behind exe proper
+// Adapted from UPayload at http://www.delphidabbler.com/articles?article=7
+// This is the base class; there's apparently also a class that implements
+// stream-based access to the payload data
+interface
+
+type
+
+  { TPayload }
+
+  TPayload = class(TObject)
+  private
+    {the name of the executable file we are manipulating.}
+    fFileName: string;
+    {Preserves the current Pascal file mode}
+    fOldFileMode: integer;
+    {Pascal file descriptor that records the details of an open file.}
+    fFile: file;
+    {Open payload for read or write}
+    procedure Open(Mode: integer);
+    procedure Close;
+  public
+    {Creates payload object; if the Filename executable already has a payload, it reads it in.}
+    constructor Create(const ExecutableName: string);
+    {Whether exe has payload}
+    function HasPayload: boolean;
+    {Payload size in bytes}
+    function PayloadSize: integer;
+    {Writes payload to exe, overwrites any existing payload}
+    procedure SetPayload(const Data; const DataSize: integer);
+    {Saves file contents into payload, overwrites any existing payload}
+    procedure FileIntoPayload(const FileName: string);
+    {Retrieves payload from exe into buffer Data.
+     Buffer must be big enough, see PayloadSize}
+    procedure GetPayload(var Data);
+    {Retrieves payload from exe, saves it to file.}
+    procedure PayloadIntoFile(const FileName: string);
+    {Removes payload from exe}
+    procedure RemovePayload;
+  end;
+
+implementation
+
+uses
+  Classes, SysUtils;
+
+type
+  TPayloadFooter = packed record
+    WaterMark: TGUID; //magic number that identifies there is a payload attached
+    ExeSize: longint; //size of original executable before payload added
+    DataSize: longint; //size of payload data (excluding footer)
+  end;
+
+const
+  cWaterMarkGUID: TGUID =
+    '{9FABA105-EDA8-45C3-89F4-369315A947EB}';
+  cReadOnlyMode = 0;
+  cReadWriteMode = 2;
+
+procedure InitFooter(out Footer: TPayloadFooter);
+begin
+  FillChar(Footer, SizeOf(Footer), 0);
+  Footer.WaterMark := cWaterMarkGUID;
+end;
+
+function ReadFooter(var F: file; out Footer: TPayloadFooter): boolean;
+var
+  FileLen: integer;
+begin
+  // Check that file is large enough for a footer!
+  FileLen := FileSize(F);
+  if FileLen > SizeOf(Footer) then
+  begin
+    // Big enough: move to start of footer and read it
+    Seek(F, FileLen - SizeOf(Footer));
+    BlockRead(F, Footer, SizeOf(Footer));
+  end
+  else
+    // File not large enough for footer: zero it
+    // .. this ensures watermark is invalid
+    FillChar(Footer, SizeOf(Footer), 0);
+  // Return if watermark is valid
+  Result := IsEqualGUID(Footer.WaterMark, cWaterMarkGUID);
+end;
+
+procedure TPayload.Close;
+begin
+  // close file and restores previous file mode
+  CloseFile(fFile);
+  FileMode := fOldFileMode;
+end;
+
+constructor TPayload.Create(const ExecutableName: string);
+begin
+  inherited Create;
+  fFileName := ExecutableName;
+end;
+
+procedure TPayload.GetPayload(var Data);
+var
+  Footer: TPayloadFooter;
+begin
+  // open file as read only
+  Open(cReadOnlyMode);
+  try
+    // read footer
+    if ReadFooter(fFile, Footer) and (Footer.DataSize > 0) then
+    begin
+      // move to end of exe code and read data
+      Seek(fFile, Footer.ExeSize);
+      BlockRead(fFile, Data, Footer.DataSize);
+    end;
+  finally
+    // close file
+    Close;
+  end;
+end;
+
+procedure Tpayload.PayloadIntoFile(const Filename: string);
+var
+  Buffer: string;
+begin
+  // Fail silently if no payload
+  if HasPayload then
+  begin
+    Setlength(Buffer, PayloadSize);
+    GetPayload(Buffer[1]);
+    //Get payload into buffer. Pass memory location, not pointer on stack
+    if FileExists(FileName) then
+      raise Exception.Create('Resource output file already exists.');
+    with TFileStream.Create(FileName, fmCreate or fmOpenWrite or fmShareDenyWrite) do
+    begin
+      try
+        Write(Pointer(Buffer)^, Length(Buffer));
+      except
+        Free;
+        raise;
+      end;
+      Free;
+    end;
+  end;
+end;
+
+function TPayload.HasPayload: boolean;
+begin
+  // we have a payload if size is greater than 0
+  Result := PayloadSize > 0;
+end;
+
+procedure TPayload.Open(Mode: integer);
+begin
+  // open file with given mode, recording current one
+  fOldFileMode := FileMode;
+  AssignFile(fFile, fFileName);
+  FileMode := Mode;
+  Reset(fFile, 1); //Open with record size 1
+end;
+
+function TPayload.PayloadSize: integer;
+var
+  Footer: TPayloadFooter;
+begin
+  // open file and assume no data
+  Result := 0;
+  Open(cReadOnlyMode);
+  try
+    // read footer and if valid return data size
+    if ReadFooter(fFile, Footer) then
+      Result := Footer.DataSize;
+  finally
+    Close;
+  end;
+end;
+
+procedure TPayload.RemovePayload;
+var
+  PLSize: integer;
+  FileLen: integer;
+begin
+  // get size of payload
+  PLSize := PayloadSize;
+  if PLSize > 0 then
+  begin
+    // we have payload: open file and get size
+    Open(cReadWriteMode);
+    FileLen := FileSize(fFile);
+    try
+      // seek to end of exec code and truncate file there
+      Seek(fFile, FileLen - PLSize - SizeOf(TPayloadFooter));
+      Truncate(fFile);
+    finally
+      Close;
+    end;
+  end;
+end;
+
+procedure TPayload.SetPayload(const Data; const DataSize: integer);
+var
+  Footer: TPayloadFooter;
+begin
+  // remove any existing payload
+  RemovePayload;
+  if DataSize > 0 then
+  begin
+    // we have some data: open file for writing
+    Open(cReadWriteMode);
+    try
+      // create a new footer with required data
+      InitFooter(Footer);
+      Footer.ExeSize := FileSize(fFile);
+      Footer.DataSize := DataSize;
+      // write data and footer at end of exe code
+      Seek(fFile, Footer.ExeSize);
+      BlockWrite(fFile, Data, DataSize);
+      BlockWrite(fFile, Footer, SizeOf(Footer));
+    finally
+      Close;
+    end;
+  end;
+end;
+
+procedure TPayload.FileIntoPayload(const FileName: string);
+var
+  Filesize: integer;
+  Buffer: string;
+begin
+  if FileExists(FileName) = False then
+  begin
+    raise Exception.Create('File not found trying to write file to resource.');
+  end;
+  with TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite) do
+  begin
+    try
+      FileSize := Size;
+      SetLength(Buffer, FileSize);
+      Read(Pointer(Buffer)^, Size);
+      // Write/overwrite resource:
+      SetPayload(Buffer[1], Length(Buffer));
+    except
+      Free;
+      Buffer := '';  // Deallocates memory
+      raise;
+    end;
+    Free;
+  end;
+end;
+
+end.

+ 349 - 0
Source/resourcezipper.pas

@@ -0,0 +1,349 @@
+unit resourcezipper;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, zipper;
+
+type
+
+  { TResourceZipper }
+
+  TResourceZipper = class(TObject)
+  private
+    FZipDir: string; {Includes trailing dir separator}
+    FTargetExecutable: string;
+    FZipSourceDir: string;
+    { Indicates whether the resource zip has been written yet }
+    FCheckRideZipWritten: boolean;
+    FCheckRideHelperZipWritten: boolean;
+    { Add file to zip; strip out path part }
+    procedure AddToZip(var Zip: TZipper; const FileName: string);
+    procedure SetOutputDirectory(Value: string);
+    procedure SetOutputFiles;
+    procedure SetZipSourceDir(AValue: string);
+  public
+    { Directory where zip will be written }
+    property ZipDirectory: string read FZipDir write Setoutputdirectory;
+    { Executable where resource will be written/read }
+    property Executable: string read FTargetExecutable write FTargetExecutable;
+    { Write out helped resource to Executable, overwriting any resources present }
+    procedure WriteCheckRideResource;
+    { Write out helper resource to Executable, overwriting any resources present }
+    procedure WriteCheckRideHelperResource;
+    { Write out CheckRide zip for inclusion in resources etc. }
+    procedure WriteCheckRideZip;
+    { Write out CheckRide helper zip for inclusion in resources etc. }
+    procedure WriteCheckRideHelperZip;
+    { Directory where source files of resource zip are located. Can be empty for current dir.}
+    property ZipSourceDir: string read FZipSourceDir write SetZipSourceDir;
+    constructor Create;
+    destructor Destroy; override;
+  end;
+
+implementation
+
+uses poormansresource, checkrideutil;
+
+const
+  HelpedResourceName = 'helped.zip';
+  HelperResourceName = 'helper.zip';
+
+var
+  FHelpedResourceZip: string; //Zip file with resource for helped program (CheckRide)
+  FHelperResourceZip: string;
+//Zip file with resource for helper program (CheckRideHelper)
+
+procedure Tresourcezipper.Setoutputdirectory(Value: string);
+begin
+  FZipDir := Value;
+  if (RightStr(FZipDir, 1) <> DirectorySeparator) and (Value <> EmptyStr) then
+  begin
+    FZipDir := FZipDir + DirectorySeparator;
+  end;
+  Setoutputfiles;
+end;
+
+procedure TResourceZipper.AddToZip(var Zip: TZipper;
+  const FileName: string);
+begin
+  if FileExists(FileName)=false then
+  begin
+    {$IFDEF CONSOLE}
+    writeln('Error adding '+FileName+' to zip file. Could not find file.');
+    {$ENDIF CONSOLE}
+    raise Exception.Create('Cannot find file');
+  end;
+  Zip.Entries.AddFileEntry(FileName, ExtractFileName(FileName));
+end;
+
+procedure Tresourcezipper.Setoutputfiles;
+begin
+  FHelpedResourceZip := FZipDir + HelpedResourceName;
+  FHelperResourceZip := FZipDir + HelperResourceName;
+end;
+
+procedure TResourceZipper.SetZipSourceDir(AValue: string);
+begin
+  // Make sure trailing / or \
+  FZipSourceDir := AValue;
+  if (RightStr(AValue, 1) <> DirectorySeparator) and (AValue <> EmptyStr) then
+  begin
+    FZipSourceDir := AValue + DirectorySeparator;
+  end;
+end;
+
+procedure TResourceZipper.WriteCheckRideHelperResource;
+var
+  Resource: TPayload;
+begin
+  if Executable = EmptyStr then
+    raise Exception.Create('Executable property is required.');
+  ;
+  if FCheckRideHelperZipWritten = False then
+    WriteCheckRideHelperZip;
+  Resource := TPayload.Create(Executable);
+  try
+    try
+      Resource.FileIntoPayload(FZipDir + HelperResourceName);
+      writeln('Wrote resource ' + FZipDir + HelperResourceName + ' to ' + Executable);
+    except
+      on E: Exception do
+      begin
+        {$IFDEF CONSOLE}
+        writeln('An error occurred writing CheckRideHelper resource. Technical details: ',
+          E.ClassName, '/', E.message);
+        writeln('Resource ' + FZipDir + HelpedResourceName);
+        writeln('Output exe: ' + Executable);
+        {$ENDIF CONSOLE}
+        raise Exception.Create(
+          'Error writing CheckRideHelper resource. Technical details: ' +
+          E.ClassName + '/' + E.message);
+        halt(1); //stop with error
+      end;
+    end;
+  finally
+    Resource.Free;
+  end;
+end;
+
+procedure TResourceZipper.WriteCheckRideResource;
+var
+  Resource: TPayload;
+begin
+  if Executable = EmptyStr then
+    raise Exception.Create('Executable property is required.');
+  ;
+  if FCheckRideZipWritten = False then
+    WriteCheckRideZip;
+  Resource := TPayload.Create(Executable);
+  try
+    try
+      Resource.FileIntoPayload(FZipDir + HelpedResourceName);
+      {$IFDEF CONSOLE}
+      writeln('Wrote resource ' + FZipDir + HelpedResourceName + ' to ' + Executable);
+      {$ENDIF CONSOLE}
+    except
+      on E: Exception do
+      begin
+        {$IFDEF CONSOLE}
+        writeln('An error occurred writing CheckRide resource. Technical details: ',
+          E.ClassName, '/', E.message);
+        writeln('Resource ' + FZipDir + HelpedResourceName);
+        writeln('Output exe: ' + Executable);
+        {$ENDIF CONSOLE}
+        raise Exception.Create('Error writing CheckRide resource. Technical details: ' +
+          E.ClassName + '/' + E.message);
+        halt(1); //stop with error
+      end;
+    end;
+  finally
+    Resource.Free;
+  end;
+end;
+
+{ TResourceZipper }
+
+procedure Tresourcezipper.WriteCheckRideZip;
+var
+  ExternalDirPrefix: string;
+  RootDirTrail: string; {Includes trailing directoryseparator}
+  Zip: TZipper;
+begin
+  RootDirTrail := ZipSourceDir; //May be empty for current path
+
+  if DirectoryExists(RootDirTrail + 'external') then
+  begin
+    ExternalDirPrefix := RootDirTrail + 'external' + DirectorySeparator;
+  end
+  else
+  begin
+    ExternalDirPrefix := RootDirTrail;
+  end;
+
+  // Preparation: create output directory, delete existing file.
+  if trim(FZipDir) <> '' then
+    try
+      ForceDirectories(FZipDir);
+    except
+      on E: Exception do
+      begin
+        {
+        writeln('Warning: could not create output directory ' + FZipDir);
+        writeln('Technical details: ',
+          E.ClassName, '/', E.message);
+        }
+        raise; //just pass it on
+      end;//E: Exception
+    end;
+  try
+    DeleteFile(FHelpedResourceZip);
+  except
+    on E: Exception do
+    begin
+      writeln('Warning: Could not delete file ' + FHelpedResourceZip);
+      Writeln('Technical details: ',
+        E.ClassName, '/', E.message);
+    end;//E: Exception
+  end;
+
+  // Actual work
+  Zip := TZipper.Create;
+  try
+    try
+      Zip.FileName := FHelpedResourceZip;
+      // My build environment has an external directory with dlls/exes. Directory layout in
+      // CheckRideHelper install dir is flat though
+      AddToZip(Zip,ExternalDirPrefix+'libeay32.dll');
+      AddToZip(Zip,ExternalDirPrefix+'sas.dll');
+      AddToZip(Zip,ExternalDirPrefix+'schook.dll');
+      AddToZip(Zip,ExternalDirPrefix+'ssleay32.dll');
+      AddToZip(Zip,ExternalDirPrefix+'stunnel.exe');
+      AddToZip(Zip,ExternalDirPrefix+'stunnel.pem');
+      AddToZip(Zip,ExternalDirPrefix+'ultravnc.ini');
+      //See https://forum.ultravnc.net/viewtopic.php?f=9&t=15864
+      //we already have schook.dll so presumably that is used, no need for vnchooks.
+      AddToZip(Zip,ExternalDirPrefix+'winvnc.exe');
+      AddToZip(Zip,ExternalDirPrefix+'zlib1.dll');
+      AddToZip(Zip,RootDirTrail+'stunnelhelped.conf.template');
+      AddToZip(Zip,RootDirTrail+'CheckRide.conf');
+      if FileExists(RootDirTrail+'..'+DirectorySeparator+'Readme.txt') then
+      begin
+        AddToZip(Zip,RootDirTrail+'..'+DirectorySeparator+'Readme.txt');
+      end
+      else
+      begin
+        // Otherwise assume in current directory.
+        AddToZip(Zip,RootDirTrail+'Readme.txt');
+      end;
+      if FileExists(RootDirTrail+'..'+DirectorySeparator+'License.txt') then
+      begin
+        AddToZip(Zip,RootDirTrail+'..'+DirectorySeparator+'License.txt');
+      end
+      else
+      begin
+        // Otherwise assume in current directory.
+        AddToZip(Zip,RootDirTrail+'License.txt');
+      end;
+      Zip.ZipAllFiles;
+      {$IFDEF CONSOLE}
+      writeln('Done writing ' + HelpedResourceName);
+      {$ENDIF CONSOLE}
+    except
+      on E: Exception do
+      begin
+        {$IFDEF CONSOLE}
+        Writeln('Error creating ' + Zip.Filename + '. Details: ',
+          E.ClassName, '/', E.message);
+        {$ENDIF CONSOLE}
+        //Writeln('Fatal error: aborting.');
+        Halt(1); //Exit status: error.
+      end;//E: Exception
+    end;
+  finally
+    Zip.Free;
+  end;
+  FCheckRideZipWritten := True;
+end;
+
+procedure Tresourcezipper.WriteCheckRideHelperZip;
+var
+  RootDirTrail: string; {Includes trailing directoryseparator}
+  Zip: TZipper;
+begin
+  RootDirTrail := ZipSourceDir; //May be empty for current path
+
+  // Preparation: create output directory, delete existing file.
+  if trim(FZipDir) <> '' then
+    try
+      ForceDirectories(FZipDir);
+    except
+      on E: Exception do
+      begin
+        {$IFDEF CONSOLE}
+        writeln('Warning: could not create output directory ' + FZipDir);
+        writeln('Technical details: ',
+          E.ClassName, '/', E.message);
+        {$ENDIF CONSOLE}
+        raise; //just pass it on
+      end;//E: Exception
+    end;
+
+  try
+    DeleteFile(FHelperResourceZip);
+  except
+    on E: Exception do
+    begin
+      {$IFDEF CONSOLE}
+      writeln('Warning: Could not delete file ' + FHelperResourceZip);
+      Writeln('Technical details: ',
+        E.ClassName, '/', E.message);
+      {$ENDIF CONSOLE}
+    end;//E: Exception
+  end;
+
+  // Actual work
+  Zip := TZipper.Create;
+  try
+    try
+      Zip.FileName := FHelperResourceZip;
+      AddToZip(Zip,RootDirTrail+'stunnelhelper.conf.template');
+      AddToZip(Zip,RootDirTrail+'CheckRide.conf');
+      Zip.ZipAllFiles;
+      writeln('Done writing ' + HelperResourceName);
+    except
+      on E: Exception do
+      begin
+        Writeln('Error creating ' + Zip.Filename + '. Details: ',
+          E.ClassName, '/', E.message);
+        {$IFDEF CONSOLE}
+        Writeln('Fatal error: aborting.');
+        {$ENDIF CONSOLE}
+        Halt(1); //Exit status: error.
+      end;//E: Exception
+    end;
+  finally
+    Zip.Free;
+  end;
+  FCheckRideHelperZipWritten := True;
+end;
+
+
+constructor Tresourcezipper.Create;
+begin
+  FCheckRideHelperZipWritten := False;
+  FCheckRideZipWritten := False;
+  FZipDir := EmptyStr;
+  FTargetExecutable := CheckRideExe; //no path
+  FZipSourceDir := EmptyStr;
+  SetOutputFiles;
+end;
+
+destructor Tresourcezipper.Destroy;
+begin
+  inherited Destroy;
+end;
+
+end.

+ 38 - 0
Source/stunnelhelped.conf.template

@@ -0,0 +1,38 @@
+; Certificate/key is needed in server mode and optional in client mode
+; The default certificate is provided only for testing and should not
+; be used in a production environment
+cert = stunnel.pem
+;key = stunnel.pem
+
+; Some performance tunings
+socket = l:TCP_NODELAY=1
+socket = r:TCP_NODELAY=1
+
+; Use it for client mode
+client = yes
+
+;only useful if we don't use vnc compression??
+;compression=zlib
+;output log
+output=stunnel.log
+; if we use a taskbar icon, end user may
+; fix problems himself once disconnected.
+taskbar=yes
+
+; Service-level configuration
+[vncserver]
+;client mode (remote service uses SSL):
+client = yes
+; port we're listening on:
+accept  = 127.0.0.1:65000
+; forward that to:
+connect = $(HELPERSERVERNAME):$(HELPERSERVERPORT)
+delay = yes
+sslVersion = TLSv1
+ciphers = AES256-SHA:AES128-SHA:IDEA-CBC-MD5
+;DES-CBC3-SHA is slow
+;don't use hosts.allow
+;won't work on windows
+;libwrap = no
+TIMEOUTconnect = 60
+TIMEOUTidle = 60

+ 36 - 0
Source/stunnelhelper.conf.template

@@ -0,0 +1,36 @@
+; Certificate/key is needed in server mode and optional in client mode
+; The default certificate is provided only for testing and should not
+; be used in a production environment
+cert = stunnel.pem
+;key = stunnel.pem
+
+; Some performance tunings
+socket = l:TCP_NODELAY=1
+socket = r:TCP_NODELAY=1
+
+; Use it for client mode?
+client = no
+
+;only useful if we don't use vnc compression??
+;compression=zlib
+;output log
+output=stunnel.log
+;taskbar icon for helper
+taskbar = yes
+
+; Service-level configuration
+[vncclient]
+client = no
+; port we're listening on:
+accept = $(HELPERSERVERPORT)
+; forward that to:
+connect = 127.0.0.1:65001
+client = no
+sslVersion = TLSv1
+ciphers = AES256-SHA:AES128-SHA:IDEA-CBC-MD5
+;DES-CBC3-SHA is slow
+;don't use hosts.allow
+;won't work on windows
+;libwrap = no
+TIMEOUTconnect = 60
+TIMEOUTidle = 60

+ 135 - 0
Source/unit1.lfm

@@ -0,0 +1,135 @@
+object CheckRideMain: TCheckRideMain
+  Left = 341
+  Height = 593
+  Top = 155
+  Width = 758
+  Caption = 'CheckRide'
+  ClientHeight = 573
+  ClientWidth = 758
+  Menu = MainMenu1
+  OnActivate = FormActivate
+  OnClose = FormClose
+  OnCreate = FormCreate
+  LCLVersion = '0.9.31'
+  object ServerName: TLabeledEdit
+    Left = 120
+    Height = 23
+    Top = 9
+    Width = 216
+    EditLabel.AnchorSideTop.Control = ServerName
+    EditLabel.AnchorSideTop.Side = asrCenter
+    EditLabel.AnchorSideRight.Control = ServerName
+    EditLabel.AnchorSideBottom.Control = ServerName
+    EditLabel.AnchorSideBottom.Side = asrBottom
+    EditLabel.Left = 14
+    EditLabel.Height = 16
+    EditLabel.Hint = 'Server name or IP address for the helper'
+    EditLabel.Top = 12
+    EditLabel.Width = 103
+    EditLabel.Caption = 'Helper server name'
+    EditLabel.ParentColor = False
+    LabelPosition = lpLeft
+    TabOrder = 0
+    OnEditingDone = ServerNameEditingDone
+  end
+  object ServerPort: TLabeledEdit
+    Left = 120
+    Height = 23
+    Top = 49
+    Width = 104
+    EditLabel.AnchorSideTop.Control = ServerPort
+    EditLabel.AnchorSideTop.Side = asrCenter
+    EditLabel.AnchorSideRight.Control = ServerPort
+    EditLabel.AnchorSideBottom.Control = ServerPort
+    EditLabel.AnchorSideBottom.Side = asrBottom
+    EditLabel.Left = 22
+    EditLabel.Height = 16
+    EditLabel.Top = 52
+    EditLabel.Width = 95
+    EditLabel.Caption = 'Helper server port'
+    EditLabel.ParentColor = False
+    EditLabel.Layout = tlBottom
+    LabelPosition = lpLeft
+    TabOrder = 1
+    OnEditingDone = ServerPortEditingDone
+  end
+  object ConnectButton: TButton
+    Left = 304
+    Height = 25
+    Top = 76
+    Width = 75
+    Caption = 'Connect'
+    OnClick = ConnectButtonClick
+    TabOrder = 2
+  end
+  object DisconnectButton: TButton
+    Left = 392
+    Height = 25
+    Top = 76
+    Width = 75
+    Caption = 'Disconnect'
+    OnClick = DisconnectButtonClick
+    TabOrder = 3
+  end
+  object Memo1: TMemo
+    Left = 8
+    Height = 474
+    Top = 112
+    Width = 750
+    TabOrder = 4
+  end
+  object TunnelProcess: TAsyncProcess
+    Active = False
+    Options = []
+    Priority = ppNormal
+    StartupOptions = []
+    ShowWindow = swoNone
+    WindowColumns = 0
+    WindowHeight = 0
+    WindowLeft = 0
+    WindowRows = 0
+    WindowTop = 0
+    WindowWidth = 0
+    FillAttribute = 0
+    left = 696
+    top = 16
+  end
+  object VNCGUIProcess: TAsyncProcess
+    Active = False
+    Options = []
+    Priority = ppNormal
+    StartupOptions = []
+    ShowWindow = swoNone
+    WindowColumns = 0
+    WindowHeight = 0
+    WindowLeft = 0
+    WindowRows = 0
+    WindowTop = 0
+    WindowWidth = 0
+    FillAttribute = 0
+    left = 600
+    top = 16
+  end
+  object MainMenu1: TMainMenu
+    left = 512
+    top = 16
+    object FileMenu: TMenuItem
+      Caption = '&File'
+      object QuitMenu: TMenuItem
+        Caption = 'Quit'
+        OnClick = QuitMenuClick
+      end
+    end
+    object HelpMenu: TMenuItem
+      Caption = '&Help'
+      object MenuLicense: TMenuItem
+        Caption = '&Licenses'
+        OnClick = MenuLicenseClick
+      end
+      object AboutMenu: TMenuItem
+        Caption = '&About'
+        OnClick = AboutMenuClick
+      end
+    end
+  end
+end

+ 622 - 0
Source/unit1.pas

@@ -0,0 +1,622 @@
+unit Unit1;
+
+{*
+This source code is provided under the MIT license:
+Copyright (C) 2011 by Reinier Olislagers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in 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:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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
+AUTHORS 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 IN
+THE SOFTWARE.
+*}
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
+  StdCtrls, Grids, AsyncProcess, Menus;
+
+type
+  { TCheckRideMain }
+  TCheckRideMain = class(TForm)
+    MainMenu1: TMainMenu;
+    Memo1: TMemo;
+    FileMenu: TMenuItem;
+    HelpMenu: TMenuItem;
+    AboutMenu: TMenuItem;
+    MenuLicense: TMenuItem;
+    QuitMenu: TMenuItem;
+    TunnelProcess: TAsyncProcess;
+    VNCGUIProcess: TAsyncProcess;
+    ConnectButton: TButton;
+    DisconnectButton: TButton;
+    ServerPort: TLabeledEdit;
+    ServerName: TLabeledEdit;
+    procedure AboutMenuClick(Sender: TObject);
+    procedure ConnectButtonClick(Sender: TObject);
+    procedure DisconnectButtonClick(Sender: TObject);
+    procedure FormActivate(Sender: TObject);
+    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
+    procedure FormCreate(Sender: TObject);
+    procedure ConnectForHelp;
+    procedure DisconnectHelp;
+    procedure ShowCommandLineHelp;
+    procedure MenuLicenseClick(Sender: TObject);
+    procedure QuitMenuClick(Sender: TObject);
+    procedure ServerNameEditingDone(Sender: TObject);
+    procedure ServerPortEditingDone(Sender: TObject);
+  private
+    FSetupComplete: boolean;
+    FHostInCommandLine: boolean; //Does user want to override host using command line?
+    FPortInCommandLine: boolean; //Does user want to override port using command line?
+    FNameInCommandLine: boolean;
+    //Does user want to override helper name using command line?
+    FConnected: boolean; //whether or not the connection is started
+    FConnectOneTime: boolean; //Should we connect in the OnActivate event?
+    FVNCFullPath: string;
+    FStunnelFullPath: string;
+    FVNCServiceAlreadyExisted: boolean;
+    FWeInstalledVNCService: boolean; //Used for cleanup
+    FVNCServiceWasRunning: boolean; //Shows if VNC service was already running.
+    procedure SetupConfigAndExes;
+    procedure Updatebuttons;
+    { private declarations }
+  public
+    { public declarations }
+  end;
+
+var
+  CheckRideMain: TCheckRideMain;
+
+implementation
+
+uses
+  Windows, CheckRideUtil, aboutform;
+
+{$R *.lfm}
+// Use explicit manifest file created so we have elevated (admin) privileges on
+// Windows systems with UAC on.
+// Note: disable in Project Options, uncheck the "Use manifest file to enable themes (windows only)" checkbox
+{$R manifest.rc}
+
+
+{ TCheckRideMain }
+const
+  VNCExe = 'winvnc.exe';
+  UltraVNCServiceName = 'uvnc_service';
+  StunnelExe = 'stunnel.exe';
+
+procedure TCheckRideMain.ShowCommandLineHelp;
+begin
+  Memo1.Append('Command line options:');
+  Memo1.Append('-h or --help: show this information.');
+  Memo1.Append('-x or --noautoconnect: don''t connect automatically.');
+  Memo1.Append('-i or --helperhost: host name or IP address of helper. Overrides CheckRide.conf');
+  Memo1.Append('-p or --helperport: helper port number. Overrides CheckRide.conf');
+  Memo1.Append('-n or --helpername: helper name to be displayed. Overrides CheckRide.conf');
+end;
+
+procedure TCheckRideMain.FormCreate(Sender: TObject);
+var
+  ErrorMessage: string = '';
+begin
+  FSetupComplete := False; //Run setup later, in activate
+  FConnected := False;
+  FWeInstalledVNCService := False; //We haven't installed the VNC service... yet
+  FVNCServiceAlreadyExisted := False; //We haven't detected any existing VNC service...
+  FVNCServiceWasRunning := False; // .. so it isn't running either, as far as we know.
+  FVNCServiceAlreadyExisted := False;
+  FConnectOneTime := True;
+
+  // Check parameters.
+  // Host, port and name require parameters.
+  ErrorMessage := Application.CheckOptions(
+    'hxi:p:n:', 'noautoconnect help helperhost: helperport: helpername:');
+  if Length(ErrorMessage) > 0 then
+  begin
+    Memo1.Append('Error: wrong command line options given.');
+    Memo1.Append(ErrorMessage);
+    ShowCommandLineHelp;
+  end;
+
+  if Application.HasOption('x', 'noautoconnect') then
+  begin
+    FConnectOneTime := False;
+  end;
+
+  if Application.HasOption('h', 'help') then
+  begin
+    ShowCommandLineHelp;
+  end;
+
+  // We start with name as we might need to overrule
+  // FNameInCommandLine:=false later on.
+  if Application.HasOption('n', 'helpername') then
+  begin
+    FNameInCommandLine := True;
+  end
+  else
+  begin
+    FNameInCommandLine := False;
+  end;
+
+  if Application.HasOption('i', 'helperhost') then
+  begin
+    FHostInCommandLine := True;
+    FNameInCommandLine := True; //Seems logical, it's a different host now.
+  end
+  else
+  begin
+    FHostInCommandLine := False;
+  end;
+
+  if Application.HasOption('p', 'helperport') then
+  begin
+    FPortInCommandLine := True;
+    FNameInCommandLine := True; //Seems logical, it's a different host now.
+  end
+  else
+  begin
+    FPortInCommandLine := False;
+  end;
+end;
+
+procedure TCheckRideMain.SetupConfigAndExes;
+{description Unpacks resources and sets up configuration. Needs to be called once in application.}
+begin
+  ResourceExtract(ParamStr(0)); //Extract resources, read config etc.
+  FVNCFullPath := FResourceDir + DirectorySeparator + VNCExe;
+  FStunnelFullPath := FResourceDir + DirectorySeparator + StunnelExe;
+  if Trim(ServerName.Text) = '' then
+  begin
+    if FHostInCommandLine = True then
+    begin
+      // Command line options override config file.
+      FConnectHost := Trim(Application.GetOptionValue('i', 'helperhost'));
+      FConnectHelper := 'helper'; //default value
+    end;
+    ServerName.Text := FConnectHost;
+  end;
+  if Trim(ServerPort.Text) = '' then
+  begin
+    if FPortInCommandLine = True then
+    begin
+      // Command line options override config file.
+      FConnectPort := StrToInt(Trim(Application.GetOptionValue('p', 'helperport')));
+      FConnectHelper := 'helper'; //default value
+    end;
+    ServerPort.Text := IntToStr(FConnectPort);
+  end;
+  if FNameInCommandLine = True then
+  begin
+    FConnectHelper := Application.GetOptionValue('n', 'helpername');
+  end;
+  FSetupComplete := True;
+end;
+
+procedure TCheckRideMain.ConnectButtonClick(Sender: TObject);
+begin
+  if FConnected = True then
+  begin
+    ShowMessage('Already connected. Please disconnect first.');
+  end
+  else
+  begin
+    ConnectForHelp;
+  end;
+end;
+
+procedure TCheckRideMain.AboutMenuClick(Sender: TObject);
+var
+  TheForm: TInfoAboutForm;
+begin
+  TheForm := aboutform.TInfoAboutForm.Create(Application);
+  try
+    TheForm.FileName := 'Readme.txt';
+    TheForm.ShowModal;
+  finally
+    TheForm.Release;
+  end;
+end;
+
+procedure TCheckRideMain.DisconnectButtonClick(Sender: TObject);
+begin
+  if FConnected = False then
+  begin
+    ShowMessage('Connection not started, so I can''t disconnect.');
+  end
+  else
+  begin
+    DisconnectHelp;
+  end;
+end;
+
+procedure TCheckRideMain.FormActivate(Sender: TObject);
+begin
+  // Set up, one time only, hopefully
+  if FSetupComplete = False then
+  begin
+    SetupConfigAndExes; //Updates FSetupComplete.
+  end;
+  // Implement autoconnect after form is shown for the first time
+  if FConnectOneTime = True then
+  begin
+    FConnectOneTime := False; //Don't start this again
+    ConnectButton.Enabled := False; //Don't let user click this
+    DisconnectButton.Enabled := False; //This just doesn't make sense now, either
+    Application.ProcessMessages; //Give Lazarus a chance to draw the screen.
+    ConnectForHelp;
+  end;
+end;
+
+procedure TCheckRideMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
+begin
+  // Clean up existing connections
+  DisconnectHelp;
+  // Clean up temp dir
+  CleanTempDir;
+end;
+
+procedure TCheckRideMain.DisconnectHelp;
+{description Disconnect existing sessions, clean up}
+const
+  SleepTimeOut = 4; //Wait this number of times for service to stop
+var
+  i: integer;
+begin
+  Screen.Cursor := crHourglass;
+  try
+    Memo1.Append('Stopping connections to helper.');
+    // Stop connection
+    if SysUtils.ExecuteProcess(FVNCFullPath, ' -stopreconnect') <> 0 then
+    begin
+      Memo1.Append('Step 1 of 7: Error running winvnc -stopreconnect');
+    end
+    else
+    begin
+      Memo1.Append('Step 1 of 7: VNC connection attempts stopped.');
+    end;
+
+    if FWeInstalledVNCService = True then
+    begin
+      // Stop vnc service, if we have installed it
+      if SysUtils.ExecuteProcess(FVNCFullPath, ' -stopservice') <> 0 then
+      begin
+        Memo1.Append('Step 2 of 7: Error running winvnc -stopservice');
+      end
+      else
+      begin
+        Memo1.Append('Step 2 of 7: Temporary VNC service stopped.');
+      end;
+
+      // Uninstall service, if we have installed it.
+      if SysUtils.ExecuteProcess(FVNCFullPath, ' -uninstall') <> 0 then
+      begin
+        Memo1.Append('Step 3 of 7: Error running winvnc -uninstall');
+      end
+      else
+      begin
+        Memo1.Append('Step 3 of 7: Temporary VNC service uninstalled.');
+        FWeInstalledVNCService := False; //Now it's not installed anymore
+      end;
+    end
+    else
+    begin
+      if FVNCServiceAlreadyExisted = True then
+      begin
+        if FVNCServiceWasRunning = False then
+        begin
+          // Leave it as we found it:
+          StopService(UltraVNCServiceName);
+          Memo1.Append(
+            'Step 3 of 7: Stopped existing VNC service, to leave it as we found it.');
+        end;
+      end;
+    end;
+
+    if VNCGUIProcess.Running = True then
+      // Kill VNC trayicon GUI
+    begin
+      VNCGUIProcess.Terminate(ExitCode);
+      //We'll wait for it to quit below, so no need to wait here.
+      Memo1.Append('Step 4 of 7: VNC tray icon closed.');
+    end;
+
+    if TunnelProcess.Running = True then
+    begin
+      // Stop our stunnel process
+      TunnelProcess.Terminate(ExitCode);
+      // Wait some, apparently needed for terminate to filter through.
+      Sleep(100); //Should be long enough to get some work done
+      Application.ProcessMessages;//for good measure
+      //DON'T MESS WITH FWeStartedStunnel; we'll need it below
+      Memo1.Append('Step 5 of 7: Asked SSL/TLS tunnel to stop.');
+    end;
+
+    // Make sure that the VNC service is gone after uninstalling
+    if FWeInstalledVNCService = True then
+    begin
+      if IsServiceRunning(UltraVNCServiceName) = True then
+      begin
+        // Wait for service to stop
+        Memo1.Append('Step 6 of 7: Temporary VNC service is still running.');
+        i := 0;
+        repeat
+          sleep(500);
+          Application.ProcessMessages; //Handle GUI events
+          i := i + 1;
+          Memo1.Append('Step 6 of 7: Waiting for temporary VNC service to stop (' +
+            IntToStr(i) + ')');
+          if i >= SleepTimeOut then
+          begin
+            Memo1.Append(
+              'Step 6 of 7: Giving up waiting for temporary VNC service to stop.');
+            break;
+          end;
+        until IsServiceRunning(UltraVNCServiceName) = False;
+      end;
+    end;
+
+    // Now check that stunnel is down
+    // note we can check for process names, but what if there are 2 stunnels?
+    if TunnelProcess.Running = True then
+    begin
+      Memo1.Append(
+        'Step 7 of 7: Error stopping SSL/TLS tunnel; trying to kill all stunnel.exes with taskkill.');
+      // Stop stunnel process forcefully
+      PostMessage(TunnelProcess.Handle, WM_QUIT, 0, 0);
+      Application.ProcessMessages;
+      Sleep(500);
+      if TunnelProcess.Running = True then
+      begin
+        if TerminateProcess(TunnelProcess.Handle, 255) then
+        begin
+          Memo1.Append('Step 7 of 7: Finished stopping SSL/TLS tunnel.');
+        end
+        else
+        begin
+          Memo1.Append('Step 7 of 7: Error stopping SSL/TLS tunnel: ' + StunnelExe);
+        end;
+      end;
+    end
+    else
+    begin
+      Memo1.Append('Step 7 of 7: SSL/TLS tunnel is stopped.');
+    end;
+
+    try
+      CleanSystemTray; //Get rid of zombie icons
+    except
+      on E: Exception do
+      begin
+        Memo1.Append('Step 7 of 7: small problem cleaning up icons. Details:' +
+          E.ClassName + '/' + E.Message);
+      end;
+    end;
+
+    FConnected := False;
+    //might not be totally true if disconnect attempts above failed...
+
+    Memo1.Append('Disconnected.');
+    Updatebuttons;
+    Screen.Cursor := crDefault;
+  except
+    on E: Exception do
+    begin
+      Memo1.Append('Error running commands; error was ' + E.ClassName + '/' + E.Message);
+      Updatebuttons;
+      Screen.Cursor := crDefault;
+    end;
+  end;
+end;
+
+procedure TCheckRideMain.MenuLicenseClick(Sender: TObject);
+var
+  TheForm: TInfoAboutForm; //TForm is not specialised enough
+begin
+  TheForm := aboutform.TInfoAboutForm.Create(Application);
+  try
+    TheForm.FileName := 'License.txt';
+    TheForm.ShowModal;
+  finally
+    TheForm.Release;
+  end;
+end;
+
+procedure TCheckRideMain.QuitMenuClick(Sender: TObject);
+begin
+  Close;
+end;
+
+procedure TCheckRideMain.ServerNameEditingDone(Sender: TObject);
+begin
+  CheckRideUtil.FConnectHost := Trim(ServerName.Text);
+end;
+
+procedure TCheckRideMain.ServerPortEditingDone(Sender: TObject);
+begin
+  CheckRideUtil.FConnectPort := StrToIntDef(ServerPort.Text, 3334);
+end;
+
+procedure TCheckRideMain.Updatebuttons;
+begin
+  ConnectButton.Enabled := not (FConnected);
+  DisconnectButton.Enabled := FConnected;
+end;
+
+procedure TCheckRideMain.ConnectForHelp;
+{description Connect to helper}
+{ TODO 6 -oAnyone -cNice to have: refactor vnc into separate unit/class, also stunnel }
+const
+  // Note the leading space to separate command from options/parameters
+  // Note that this "autoreconnect" param MUST be BEFORE the "connect" on the command line
+  // (see UltraVNC winvnc.cpp)
+  // Officially, I think you should run -servicehelper, but that doesn't seem
+  // to do anything.
+  VNCParameters = ' -autoreconnect -connect 127.0.0.1::65000';
+  SleepTimeOut = 4;
+var
+  i: integer;
+
+  procedure PrepareToExit(Message: string);
+  {description Revert GUI level changes so we can exit the procedure cleanly}
+  begin
+    Screen.Cursor := crDefault;
+    UpdateButtons;
+    Memo1.Append(Message);
+  end;
+
+begin
+  ConnectButton.Enabled := False; //Don't let user click twice.
+  Screen.Cursor := crHourglass;
+  Memo1.Append('Setting up connection to helper ' + FConnectHelper +
+    ' at ' + FConnectHost + ':' + IntToStr(FConnectPort) + '.');
+  // Start up stunnel asynchronously: let it run in parallel with the rest of the program
+  try
+    TunnelProcess.CommandLine :=
+      FStunnelFullPath + ' ' + CustomSTunnelconfig(Helped) + '';
+    TunnelProcess.Execute;
+    Memo1.Append('Step 1 of 5: Started SSL/TLS tunnel.');
+  except
+    PrepareToExit('Step 1 of 5: Error running ' + FStunnelFullPath +
+      ' ' + CustomSTunnelConfig(Helped) + '');
+    exit; //exit procedure, useless to continue
+  end;
+
+  try
+    if ServiceExists(UltraVNCServiceName) then
+    begin
+      FVNCServiceAlreadyExisted := True;
+      if IsServiceRunning(UltraVNCServiceName) then
+      begin
+        FVNCServiceWasRunning := True;
+        Memo1.Append('Step 2 of 5: Existing running VNC service detected.');
+      end
+      else
+      begin
+        FVNCServiceWasRunning := False;
+        Memo1.Append('Step 2 of 5: Existing VNC service detected.');
+      end;
+    end
+    else
+    begin
+      FVNCServiceAlreadyExisted := False;
+    end;
+  except
+    on E: Exception do
+    begin
+      //do nothing, just report the error
+      Memo1.Append('Error checking existing VNC service. Details: ' +
+        E.ClassName + '/' + E.Message);
+    end;
+  end;
+
+  try
+    // Install vnc service, required for ctr-alt-del support on at least Vista+
+    FWeInstalledVNCService := False; //default
+    if FVNCServiceAlreadyExisted = False then
+    begin
+      // Only do this if there isn't a vnc service already present
+      Memo1.Append('Step 2 of 5: Installing temporary VNC service.');
+      if SysUtils.ExecuteProcess(FVNCFullPath, ' -install') <> 0 then
+      begin
+        Memo1.Append('Error running ' + FVNCFullPath + ' -install');
+      end
+      else
+      begin
+        FWeInstalledVNCService := True;
+        Memo1.Append('Step 2 of 5: Finished installing temporary VNC service.');
+      end;
+    end
+    else
+    begin
+      // Service already existed.
+      if FVNCServiceWasRunning = False then
+      begin
+        // Start up VNC service
+        CheckRideUtil.StartService(UltraVNCServiceName);
+        Memo1.Append(
+          'Step 2 of 5: Found and started existing VNC service. This might not work, though.');
+      end;
+    end;
+
+    if TunnelProcess.Running = True then
+    begin
+      //Let's not get the user confused.
+      //Memo1.Append('Tunnel program running.');
+    end
+    else
+    begin
+      Memo1.Append('Step 3 of 5: Tunnel not running yet. We might have problems later on.');
+    end;
+
+    //Wait for vnc service to come up
+    if IsServiceRunning(UltraVNCServiceName) = False then
+    begin
+      // Wait for service to come up
+      i := 0;
+      repeat
+        sleep(500);
+        Application.ProcessMessages; //Handle
+        i := i + 1;
+        Memo1.Append('Step 3 of 5: Waiting for temporary VNC service to start up (' +
+          IntToStr(i) + ')');
+        if i >= SleepTimeOut then
+          break;
+      until IsServiceRunning(UltraVNCServiceName) = True;
+    end
+    else
+    begin
+      Memo1.Append('Step 3 of 5: Temporary VNC service is running.');
+    end;
+
+    // Now, it seems we need some extra wait time otherwise
+    // vnc command won't work
+    Memo1.Append('Step 4 of 5: Waiting 10 seconds for temporary VNC service to get ready.');
+    Application.ProcessMessages;
+    sleep(10000);
+    Application.ProcessMessages;
+
+    // Actual connect. Don't use service parameter for this
+    try
+      VNCGUIProcess.CommandLine :=
+        FVNCFullPath + ' ' + VNCParameters + '';
+      VNCGUIProcess.Execute;
+      FConnected := True;
+      Memo1.Append('Step 5 of 5: Started VNC connection attempt.');
+      Memo1.Append('Done.');
+      Memo1.Append('Waiting for helper to take over...');
+      PrepareToExit('If helper takes over, the VNC eye symbol close to the clock will change color.');
+    except
+      PrepareToExit('Step 1 of 5: Error running ' + FVNCFullPath +
+        ' ' + VNCParameters + '');
+      exit; //exit procedure, useless to continue
+    end;
+
+  except
+    on E: Exception do
+    begin
+      PrepareToExit('Error running commands; error was ' + E.ClassName +
+        '/' + E.Message);
+      Memo1.Append('Cleaning up.');
+      DisconnectHelp;
+    end;
+  end;
+end;
+
+end.
+