Skip to content

Instantly share code, notes, and snippets.

@HBIDamian
Last active December 1, 2024 21:21
Show Gist options
  • Save HBIDamian/2f7ffd349651373ec4e877b7fe41859d to your computer and use it in GitHub Desktop.
Save HBIDamian/2f7ffd349651373ec4e877b7fe41859d to your computer and use it in GitHub Desktop.
PocketMine Installer
[Setup]
AppName=PocketMine-MP Win-x64 Installer
AppVersion=1.0.0.0
AppVerName=PocketMine-MP Win-x64 Installer v1.0.0
AppPublisher=PocketMine-MP
AppPublisherURL=pmmp.io
AppSupportURL=pmmp.io
AppID=PM-Win-x64-Installer
DefaultDirName={userdesktop}\PocketMine-MP
DefaultGroupName=PocketMine-MP
OutputBaseFilename=PocketMine-MP-Installer
SetupIconFile=installer_icon.ico
LicenseFile=license.txt
SolidCompression=yes
Compression=lzma2
PrivilegesRequired=admin
ArchitecturesInstallIn64BitMode=x64
DisableProgramGroupPage=yes
DisableDirPage=no
DisableReadyPage=yes
DisableFinishedPage=no
WizardImageFile=sideBar.bmp
WizardSmallImageFile=logo.bmp
Uninstallable=no
CreateUninstallRegKey=no
[Code]
const
S_OK = 0;
JSON_URL = 'https://update.pmmp.io/';
VCRUNTIME_URL = 'https://aka.ms/vs/17/release/vc_redist.x64.exe';
START_CMD_URL = 'https://raw.githubusercontent.com/pmmp/PocketMine-MP/stable/start.cmd';
START_PS1_URL = 'https://raw.githubusercontent.com/pmmp/PocketMine-MP/stable/start.ps1';
USER_AGENT = 'PocketMine-MP WIN-x64 Installer';
SHCONTCH_NOPROGRESSBOX = $00000001;
SHCONTCH_RESPONDYESTOALL = $00000004;
NO_PROGRESS_BOX = 4;
RESPOND_YES_TO_ALL = 16;
var
CurrentStep: Integer;
procedure InitializeWizard;
begin
WizardForm.DiskSpaceLabel.Visible := false; // False to hide
WizardForm.ProgressGauge.Visible := true; // True to show
CurrentStep := 0;
end;
procedure UnZip(ZipPath, FileName, TargetPath: string);
var
Shell: Variant;
ZipFile: Variant;
Item: Variant;
TargetFolder: Variant;
begin
try
Shell := CreateOleObject('Shell.Application');
if VarIsEmpty(Shell) then
RaiseException('Failed to create Shell.Application object.');
ZipFile := Shell.NameSpace(ZipPath);
if VarIsEmpty(ZipFile) then
RaiseException(Format('Cannot open ZIP file "%s" or it does not exist.', [ZipPath]));
if FileName <> '' then
begin
Item := ZipFile.ParseName(FileName);
if VarIsEmpty(Item) then
RaiseException(Format('Cannot find "%s" in ZIP file "%s".', [FileName, ZipPath]));
end
else
Item := ZipFile.Items;
TargetFolder := Shell.NameSpace(TargetPath);
if VarIsEmpty(TargetFolder) then
RaiseException(Format('Target path "%s" does not exist.', [TargetPath]));
TargetFolder.CopyHere(Item, NO_PROGRESS_BOX or RESPOND_YES_TO_ALL);
DeleteFile(ZipPath);
except
MsgBox('Error during unzipping: ' + GetExceptionMessage, mbError, MB_OK);
end;
end;
procedure UnzipWithProgress(const ZipPath, TargetPath: string);
var
Shell: Variant;
ZipFile: Variant;
TargetFolder: Variant;
TotalItems, ExtractedItems: Integer;
Item: Variant;
begin
try
Shell := CreateOleObject('Shell.Application');
if VarIsEmpty(Shell) then
begin
MsgBox('Failed to create Shell.Application object.', mbError, MB_OK);
Exit;
end;
ZipFile := Shell.NameSpace(ZipPath);
if VarIsEmpty(ZipFile) then
begin
MsgBox('Failed to open ZIP file: ' + ZipPath, mbError, MB_OK);
Exit;
end;
TargetFolder := Shell.NameSpace(TargetPath);
if VarIsEmpty(TargetFolder) then
begin
MsgBox('Target path "' + TargetPath + '" does not exist.', mbError, MB_OK);
Exit;
end;
TotalItems := ZipFile.Items.Count;
WizardForm.ProgressGauge.Min := 0;
WizardForm.ProgressGauge.Max := TotalItems;
WizardForm.ProgressGauge.Position := 0;
WizardForm.StatusLabel.Caption := 'Extracting Binary Files. Please wait...';
// Extract items one by one
for ExtractedItems := 0 to TotalItems - 1 do
begin
Item := ZipFile.Items.Item(ExtractedItems);
TargetFolder.CopyHere(Item, 16); // Flags: 16 = Respond Yes to All
// Update progress bar
WizardForm.ProgressGauge.Position := ExtractedItems + 1;
// Add a small delay to improve UI responsiveness
Sleep(50);
end;
// Optionally delete the ZIP file after extraction
DeleteFile(ZipPath);
except
MsgBox('Error occurred during unzipping!', mbError, MB_OK);
end;
end;
function PosEx(const SubStr, S: string; Offset: Integer): Integer;
begin
if Offset > Length(S) then
begin
Result := 0;
Exit;
end;
Result := Pos(SubStr, Copy(S, Offset, MaxInt));
if Result <> 0 then
Result := Result + Offset - 1;
end;
function StringReplace(const S, OldPattern, NewPattern: string): string;
var
FoundPos, LenOld, LenNew: Integer;
begin
LenOld := Length(OldPattern);
LenNew := Length(NewPattern);
Result := S;
FoundPos := 1;
while FoundPos <= Length(Result) do
begin
FoundPos := PosEx(OldPattern, Result, FoundPos);
if FoundPos = 0 then
Break;
Delete(Result, FoundPos, LenOld);
Insert(NewPattern, Result, FoundPos);
FoundPos := FoundPos + LenNew;
end;
end;
function URLDownloadToFile(Caller: Integer; URL, FileName: PAnsiChar; Reserved: DWORD; StatusCB: Integer): Integer;
external '[email protected] stdcall';
function URLExists(const URL: string): Boolean;
var
HTTPRequest: Variant;
StatusCode: Integer;
ValidURL: string;
begin
// Ensure the URL starts with "http://" or "https://"
ValidURL := Trim(URL);
if (Pos('http://', ValidURL) <> 1) and (Pos('https://', ValidURL) <> 1) then
ValidURL := 'https://' + ValidURL;
try
HTTPRequest := CreateOleObject('WinHttp.WinHttpRequest.5.1');
HTTPRequest.Open('HEAD', ValidURL, False);
HTTPRequest.SetRequestHeader('User-Agent', USER_AGENT);
HTTPRequest.Send;
StatusCode := HTTPRequest.Status;
Result := (StatusCode = 200);
except
// Log or show the exception to help with troubleshooting
MsgBox('Error checking URL: ' + GetExceptionMessage, mbError, MB_OK);
Result := False;
end;
end;
function DownloadFile(const URL, DestFile: string): Boolean;
begin
// Check if the URL is valid and the file exists before attempting to download
if not URLExists(URL) then
begin
MsgBox('The file at ' + URL + ' does not exist or is unreachable.', mbError, MB_OK);
Result := False;
Exit;
end;
// Proceed with downloading the file if it exists
Result := URLDownloadToFile(0, PAnsiChar(URL), PAnsiChar(DestFile), 0, 0) = 0;
if not Result then
begin
MsgBox('Failed to download: ' + URL + ' to ' + DestFile, mbError, MB_OK);
end
else
begin
// MsgBox('Downloaded ' + URL + ' to ' + DestFile, mbInformation, MB_OK);
end;
end;
function DownloadString(const URL: string): string;
var
TempFile: string;
Lines: TStringList;
begin
TempFile := ExpandConstant('{tmp}\temp.json');
if not DownloadFile(URL, TempFile) then
Abort;
Lines := TStringList.Create;
try
Lines.LoadFromFile(TempFile);
Result := Lines.Text;
finally
Lines.Free;
end;
end;
function GetJSONValue(const JSON, Key: string): string;
var
StartPos, EndPos: Integer;
begin
StartPos := Pos('"' + Key + '":', JSON) + Length(Key) + 3;
if StartPos > Length(JSON) then
begin
Result := '';
Exit;
end;
EndPos := PosEx(',', JSON, StartPos);
if EndPos = 0 then
EndPos := PosEx('}', JSON, StartPos);
Result := Copy(JSON, StartPos, EndPos - StartPos);
Result := StringReplace(Result, '"', '');
end;
function ExtractZip(const ZipFile, OutputFolder: string): Boolean;
begin
UnZip(ZipFile, '', OutputFolder);
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
OutputFolder, JSONData, PHPZipURL, PharURL: string;
PHPVersion, BASEVersion, BASEVersionMajor: string;
TotalSteps, CurrentStep: Integer;
ResultCode: Integer;
begin
if CurStep = ssInstall then
begin
OutputFolder := ExpandConstant('{app}');
TotalSteps := 8;
WizardForm.ProgressGauge.Min := 0;
WizardForm.ProgressGauge.Max := TotalSteps;
WizardForm.ProgressGauge.Position := 0;
CurrentStep := 0; // Reset global CurrentStep
if not DirExists(OutputFolder) then
if not CreateDir(OutputFolder) then
begin
MsgBox('Failed to create output directory: ' + OutputFolder, mbError, MB_OK);
Abort;
end;
// Step 1: Download JSON data
WizardForm.StatusLabel.Caption := 'Downloading update.pmmp.io data...';
JSONData := DownloadString(JSON_URL);
CurrentStep := CurrentStep + 1;
WizardForm.ProgressGauge.Position := CurrentStep;
if JSONData = '' then
Abort;
// Step 2: Parse JSON
WizardForm.StatusLabel.Caption := 'Parsing JSON data...';
PHPZipURL := GetJSONValue(JSONData, 'php_download_url');
PHPVersion := GetJSONValue(JSONData, 'php_version');
PHPVersion := StringReplace(PHPVersion, ' ', '');
BASEVersion := GetJSONValue(JSONData, 'base_version');
BASEVersionMajor := Copy(BASEVersion, 2, 1);
PHPZipURL := 'https://github.com/pmmp/PHP-Binaries/releases/download/pm' + BASEVersionMajor + '-php-' + PHPVersion + '-latest/PHP-' + PHPVersion + '-Windows-x64-PM' + BASEVersionMajor + '.zip';
PharURL := GetJSONValue(JSONData, 'download_url');
CurrentStep := CurrentStep + 1;
WizardForm.ProgressGauge.Position := CurrentStep;
if (PHPZipURL = '') or (PharURL = '') then
Abort;
// Step 3: Download PHP binary
WizardForm.StatusLabel.Caption := 'Downloading PHP binary...';
CurrentStep := CurrentStep + 1;
WizardForm.ProgressGauge.Position := CurrentStep;
if not DownloadFile(PHPZipURL, ExpandConstant(OutputFolder + '\php.zip')) then
Abort;
// Step 4: Extract PHP binary
UnzipWithProgress(ExpandConstant(OutputFolder + '\php.zip'), OutputFolder);
CurrentStep := CurrentStep + 1;
WizardForm.ProgressGauge.Position := CurrentStep;
// Step 5: Download PocketMine-MP phar
WizardForm.StatusLabel.Caption := 'Downloading PocketMine-MP phar...';
CurrentStep := CurrentStep + 1;
WizardForm.ProgressGauge.Position := CurrentStep;
if not DownloadFile(PharURL, OutputFolder + '\PocketMine-MP.phar') then
MsgBox('Failed to download PocketMine-MP.phar. You can download it manually from the PocketMine-MP GitHub repository Release page.', mbError, MB_OK);
// Step 6: Download start scripts
WizardForm.StatusLabel.Caption := 'Downloading start scripts...';
CurrentStep := CurrentStep + 1;
WizardForm.ProgressGauge.Position := CurrentStep;
if not DownloadFile(START_CMD_URL, OutputFolder + '\start.cmd') then
MsgBox('Failed to download start.cmd. You can download it manually from the PocketMine-MP GitHub repository.', mbError, MB_OK);
if not DownloadFile(START_PS1_URL, OutputFolder + '\start.ps1') then
MsgBox('Failed to download start.ps1. You can download it manually from the PocketMine-MP GitHub repository.', mbError, MB_OK);
// Step 7: Download and install Visual C++ Redistributable
if not DownloadFile(VCRUNTIME_URL, OutputFolder + '\vc_redist.x64.exe') then
Abort;
WizardForm.StatusLabel.Caption := 'Installing Visual C++ Redistributable...';
Exec(ExpandConstant(OutputFolder + '\vc_redist.x64.exe'), '/install /quiet /norestart', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if ResultCode <> S_OK then
MsgBox('Failed to install Visual C++ Redistributable.', mbError, MB_OK);
CurrentStep := CurrentStep + 1;
WizardForm.ProgressGauge.Position := CurrentStep;
WizardForm.StatusLabel.Caption := 'Verifying installation...';
// Step 8: Verify installation (If phar file, start scripts and PHP binary don't exist, then installation failed, else success)
if not FileExists(OutputFolder + '\PocketMine-MP.phar') or
not FileExists(OutputFolder + '\bin\php\php.exe') then
begin
MsgBox('Installation failed. Please try again.', mbError, MB_OK);
Abort;
end
else
begin
WizardForm.StatusLabel.Caption := 'Installation completed successfully.';
end;
end;
end;
[Run]
Filename: "{app}\start.cmd"; Description: "Start PocketMine-MP"; Flags: postinstall skipifsilent
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment