program downloader; uses fphttpclient, base64, opensslsockets, fpjson, jsonparser, SysUtils, process, Classes, libtar, CustApp, Math; var gameId: Integer; gameData: TJSONData; client: TFPHTTPClient; authString, tarFile, errorMsg: String; app: TCustomApplication; procedure createFileDir(filePath: AnsiString); var splitDir: TStringArray; s: String; newDir: String = ''; begin splitDir := filePath.Split(['\', '/']); for s in splitDir do begin if not (s = splitDir[Length(splitDir)-1]) then begin newDir := newDir + s + '/'; CreateDir(newDir); end; end; end; procedure copyFile(oldFile, newFile: AnsiString); var source, dest: TFileStream; begin createFileDir(newFile); source := TFileStream.Create(oldFile, fmOpenRead); dest := TFileStream.Create(newFile, fmCreate); dest.CopyFrom(source, source.Size); source.Free; dest.Free; end; function getGameData(var client: TFPHTTPClient; gameId: Integer; out gameData: TJSONData): Boolean; const url = 'https://rpi.narnian.us/games/get_game/'; var jData: TJSONData; begin writeln('start get game data'); client.AllowRedirect := true; try jData := GetJSON(client.Get(url+IntToStr(gameId))); gameData := jData; getGameData := true; Except getGameData := false; end; end; function downloadGameTar(var client: TFPHTTPClient; gameId: Integer): String; const url = 'https://rpi.narnian.us/games/download/windows/'; var tarFile: TFileStream; tarFileName: String; begin writeln('start download tar'); client.AllowRedirect := true; tarFileName := gameData.FindPath('title_sanitized').AsString+'.tar'; tarFile := TFileStream.Create(tarFileName, fmCreate); client.Get(url+IntToStr(gameId), tarFile); tarFile.Free; downloadGameTar := tarFileName; end; procedure extractTar(fileName: String); var tarFile: TTarArchive; tarContent: TTarDirRec; files: TJSONArray; firstFile: String; i: Integer; begin writeln('start extract tar'); tarFile := TTarArchive.Create(fileName); while tarFile.FindNext(tarContent) do begin files := TJSONArray(gameData.FindPath('windows.files.'+tarContent.Name)); firstFile := files.Strings[0]; createFileDir(firstFile); tarFile.ReadFile(firstFile); for i := 1 to files.Count-1 do begin createFileDir(files.Strings[i]); copyFile(firstFile, files.Strings[i]); end; end; tarFile.Destroy; end; function authenticate(client: TFPHTTPClient): Boolean; const url = 'https://rpi.narnian.us'; var username, password: String; begin if app.HasOption('u', 'user') then begin username := app.GetOptionValue('u', 'username'); end else begin Write('Enter username: '); ReadLn(username); end; if app.HasOption('p', 'password') then begin password := app.GetOptionValue('p', 'password'); end else begin Write('Enter password: '); ReadLn(password); end; authString := username + ':' + password; client.AddHeader('Authorization', 'Basic '+base64.EncodeStringBase64(authString)); client.AllowRedirect := true; try client.Get(url); authenticate := true; except authenticate := false; end; end; procedure displayHelp; const optionFlags: TStringArray = ( '-h, --help', '-g, --gameid ', '-u, --username ', '-p, --password '); optionDescriptions: TStringArray = ( 'Display help.', 'Sets the game id. This is required.', 'Sets the username for authentication.', 'Sets the password for authentication.'); var i, lenFlag, lenDesc, maxLenFlag, maxLenDesc, minLenFlag, minLenDesc: Integer; begin maxLenFlag := 0; maxLenDesc := 0; minLenFlag := 1000; minLenDesc := 1000; for i := 0 to Length(optionFlags)-1 do begin minLenFlag := Min(minLenFlag, Length(optionFlags[i])); maxLenFlag := Max(maxLenFlag, Length(optionFlags[i])); minLenDesc := Min(minLenDesc, Length(optionDescriptions[i])); maxLenDesc := Max(maxLenDesc, Length(optionDescriptions[i])); end; WriteLn('Usage: downloader.exe [options]'); WriteLn; WriteLn('Options:'); for i := 0 to Length(optionFLags)-1 do begin lenFlag := Length(optionFLags[i]); lenDesc := Length(optionDescriptions[i]); Write(optionFlags[i]:(lenFlag+2)); WriteLn(optionDescriptions[i]:(lenDesc+maxLenFlag-lenFlag+2)) end; end; begin app := TCustomApplication.Create(nil); errorMsg := app.checkOptions('hg:u:p:', 'help gameid: username: password:'); if (errorMsg <> '') or app.HasOption('h', 'help') then begin WriteLn(errorMsg); displayHelp; end else begin if app.HasOption('g', 'gameid') then begin gameId := StrToInt(app.GetOptionValue('g', 'gameid')); client := TFPHTTPClient.Create(nil); if authenticate(client) then begin if getGameData(client, gameId, gameData) then begin tarFile := downloadGameTar(client, gameId); extractTar(tarFile); DeleteFile(tarFile); WriteLn('Download finished'); end else begin WriteLn('Invalid game id given'); end; end else begin WriteLn('Invalid username or password'); end; end else begin WriteLn('No game id was given'); end; end; end.