diff --git a/README.md b/README.md index 8c86c57..c42791d 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,41 @@ # Unity Automatic Licensor -Unity doesn't support automatically licensing installations with Personal licenses. The only way to activate a Personal license is to interactively login and click through the licensing wizard. +Licensing Unity Personal via commandline is currently not supported. The only way to activate a Personal license is to interactively login and click through the licensing wizard. -This causes a problem for Windows build agents that are automated with Packer, or are otherwise dynamically spun up on public cloud infrastructure. +Using build agents this is not always a feasible option, since you are not always able(or inclined) to do this manually. Especially for volatile systems like GitHub Actions this is a problem. This tool allows you to license Unity with a Personal license from the command line. ## Usage -Download a release from the Releases page, and extract it somewhere on your Windows system. +On a Windows System you can simply download, build and run the licensor natively. -Then run it like so: +``` +.\UnityAutomaticLicensor.exe --username --password --unity-path \Editor\Unity.exe" +``` + +Using the dotnet SDK, this application can be run on linux systems as well. Start the application via: +``` +dotnet run --username --password --unity-path "//Editor/Unity" +``` + +If you are on a headless system and using Unity 2017 or newer you should run the licensor via ``xvfb-run``. The license agreement will not be confirmed properly if otherwise. + +### Optional arguments + +By default the license will be saved in the "C:\ProgramData\Unity" directory. You can specify the folder manually(e.g. for linux systems) via: +``` +--unity-license-path +``` + +By default this application will try to license Unity 5.x versions. To activate newer versions which do not require to answer the activation survey, specify the correct version via +``` +--unity-version --unity-changeset +``` +Usually Unity will be started after obtaining a license to verify if it is valid. You can disable this behaviour via ``` -.\UnityAutomaticLicensor.exe --username --password --unity-path "C:\Program Files\Unity\Editor\Unity.exe" +--nocheck ``` ## Building from Source @@ -21,7 +43,7 @@ Then run it like so: You can build your own copy of the application with: ``` -dotnet publish -c Release -r win10-x64 +dotnet publish -c Release ``` ## License diff --git a/UnityAutomaticLicensor/Program.cs b/UnityAutomaticLicensor/Program.cs index 54eadff..86d5d8e 100644 --- a/UnityAutomaticLicensor/Program.cs +++ b/UnityAutomaticLicensor/Program.cs @@ -22,8 +22,18 @@ public static Task Main(string[] args) [Option("--unity-path ", Description = "Path to Unity executable")] public string UnityPath { get; set; } - [Option("--unity-version ", Description = "'v5.x' for 5.x series, 'lic' for 2017.0 and later")] - public string UnityVersion { get; set; } = "v5.x"; + [Option("--unity-license-path ", Description = + "Path to directory containing Unity license file")] + public string UnityLicensePath { get; set; } = "C:/ProgramData/Unity"; + + [Option("--unity-version ", Description = "Unity version number (e.g. '2018.3.4f1')")] + public string UnityVersion { get; set; } = "5.4.1f1"; + + [Option("--unity-changeset ", Description = "Unity version changeset")] + public string UnityChangeset { get; set; } = "649f48bbbf0f"; + + [Option("--nocheck", Description = "Indicates that unity should not be started again to verify the obtained license.")] + public bool CheckSuccess { get; set; } = true; private async Task OnExecute() { @@ -33,6 +43,9 @@ private async Task OnExecute() Password = this.Password, UnityExecutablePath = this.UnityPath, UnityVersion = this.UnityVersion, + UnityChangeset = this.UnityChangeset, + UnityLicensePath = this.UnityLicensePath, + CheckSuccess = this.CheckSuccess }); await licensor.Run(); } diff --git a/UnityAutomaticLicensor/UnityAutomaticLicensor.csproj b/UnityAutomaticLicensor/UnityAutomaticLicensor.csproj index 05005c2..1ce295d 100644 --- a/UnityAutomaticLicensor/UnityAutomaticLicensor.csproj +++ b/UnityAutomaticLicensor/UnityAutomaticLicensor.csproj @@ -2,11 +2,8 @@ netcoreapp2.1 - Exe - 7.1 - win10-x64 diff --git a/UnityAutomaticLicensor/UnityExecutor.cs b/UnityAutomaticLicensor/UnityExecutor.cs index ee2ecba..85a9af2 100644 --- a/UnityAutomaticLicensor/UnityExecutor.cs +++ b/UnityAutomaticLicensor/UnityExecutor.cs @@ -29,8 +29,8 @@ public async Task ExecuteAsync(UnityExecutorRequest reque } processStartInfo.ArgumentList.Add("-logFile"); processStartInfo.ArgumentList.Add(logPath); - processStartInfo.ArgumentList.Add("-createProject"); - processStartInfo.ArgumentList.Add(temporaryDirectory); + //processStartInfo.ArgumentList.Add("-createProject"); + //processStartInfo.ArgumentList.Add(temporaryDirectory); var process = Process.Start(processStartInfo); Console.WriteLine("Unity process has been launched..."); @@ -119,7 +119,8 @@ public async Task ExecuteAsync(UnityExecutorRequest reque Result = UnityExecutorResponseResult.Retry }; } - if (newContent.Contains("Canceling DisplayDialog: Updating license failed Failed to update license within 60 seconds")) + if (newContent.Contains("Canceling DisplayDialog: Updating license failed Failed to update license within 60 seconds") + || newContent.Contains("Cancelling DisplayDialog: Failed to activate/update license. Timeout occured while trying to update license")) { Console.WriteLine("Licensing timeout - Unity has stalled!"); await KillProcess(process.Id); @@ -201,18 +202,27 @@ private async Task HandleMonoIsStalled(DateTimeOffset? cl private async Task KillProcess(int processId) { - while (!(Process.GetProcessById(processId)?.HasExited ?? true)) + try { - try - { - Console.WriteLine("Sending kill signal to Unity and waiting for it to exit..."); - Process.GetProcessById(processId).Kill(); - } - catch + while (!(Process.GetProcessById(processId)?.HasExited ?? true)) { + try + { + Console.WriteLine("Sending kill signal to Unity and waiting for it to exit..."); + Process.GetProcessById(processId).Kill(); + } + catch + { + } + + await Task.Delay(1000); } - - await Task.Delay(1000); + } + catch (ArgumentException) + { + // on linux systems the process will be killed immediatly and removed from record. + // in this case simply return, as an argument exception indicates the process was properly killed. + return; } } } diff --git a/UnityAutomaticLicensor/UnityLicensor.cs b/UnityAutomaticLicensor/UnityLicensor.cs index 8b4072e..e9a7c64 100644 --- a/UnityAutomaticLicensor/UnityLicensor.cs +++ b/UnityAutomaticLicensor/UnityLicensor.cs @@ -28,7 +28,8 @@ public async Task Run() { while (true) { - var licensePath = $@"C:\ProgramData\Unity\Unity_{_request.UnityVersion}.ulf"; + var licenseFileName = _request.UnityVersion[1] == '.' ? _request.UnityVersion : "lic"; + var licensePath = $@"{_request.UnityLicensePath}/Unity_{licenseFileName}.ulf"; var licenseKeyCheck = await RunUnityAndCaptureMachineKeys(); if (licenseKeyCheck.IsActivated) @@ -40,8 +41,8 @@ public async Task Run() Console.WriteLine("Logging into Unity Cloud..."); var coreClient = new RestClient("https://core.cloud.unity3d.com"); var loginRequest = new RestRequest("api/login", Method.POST); - loginRequest.AddCookie("unity_version", "5.4.1f1"); - loginRequest.AddCookie("unity_version_full", "5.4.1f1 (649f48bbbf0f)"); + loginRequest.AddCookie("unity_version", $@"{_request.UnityVersion}"); + loginRequest.AddCookie("unity_version_full", $@"{_request.UnityVersion} ({_request.UnityChangeset})"); loginRequest.AddJsonBody(new { grant_type = "password", @@ -56,8 +57,8 @@ public async Task Run() Console.WriteLine("Discovering user info for licensing..."); var meRequest = new RestRequest("api/users/me", Method.GET); - meRequest.AddCookie("unity_version", "5.4.1f1"); - meRequest.AddCookie("unity_version_full", "5.4.1f1 (649f48bbbf0f)"); + loginRequest.AddCookie("unity_version", $@"{_request.UnityVersion}"); + loginRequest.AddCookie("unity_version_full", $@"{_request.UnityVersion} ({_request.UnityChangeset})"); meRequest.AddHeader("Authorization", "Bearer " + loginResponse.AccessToken); response = await coreClient.ExecuteTaskAsync(meRequest); var userResponse = JsonConvert.DeserializeObject(response.Content); @@ -86,16 +87,37 @@ public async Task Run() var licenseRequest = new RestRequest("api/transactions/{txId}", Method.PUT); licenseRequest.AddUrlSegment("txId", txId); licenseRequest.AddHeader("Authorization", "Bearer " + loginResponse.AccessToken); - licenseRequest.AddJsonBody(new + // newer versions can just skip the survey + if (licenseFileName == "lic") { - transaction = new + licenseRequest.AddJsonBody(new { - serial = new + transaction = new { - type = "personal" + serial = new + { + type = "personal" + }, + survey_answer = new + { + skipped = true + } } - } - }); + }); + } + else + { + licenseRequest.AddJsonBody(new + { + transaction = new + { + serial = new + { + type = "personal" + } + } + }); + } response = await licenseClient.ExecuteTaskAsync(licenseRequest); licenseResponse = JsonConvert.DeserializeObject(response.Content); @@ -239,6 +261,12 @@ public async Task Run() Console.WriteLine("Successfully obtained a Unity license!"); Console.WriteLine("Finalising license by running Unity..."); + if(!_request.CheckSuccess) + { + Console.WriteLine("Successfully finalised Unity license!"); + return; + } + var finaliseResponse = await RunUnityToFinaliseLicense(); if (finaliseResponse.IsActivated) { @@ -274,6 +302,7 @@ private async Task RunUnityAndCaptureMachineKeys() { "-quit", "-batchmode", + "-nographics", "-username", _request.Username, "-password", @@ -322,6 +351,7 @@ private async Task RunUnityAndCaptureMachineKeys() } else if (response.Result == UnityExecutorResponseResult.Error) { + Console.WriteLine("Error encountered!"); throw new InvalidOperationException(response.Output); } } @@ -341,6 +371,7 @@ private async Task RunUnityToFinaliseLicense() { "-quit", "-batchmode", + "-nographics", "-username", _request.Username, "-password", @@ -348,7 +379,7 @@ private async Task RunUnityToFinaliseLicense() "-force-free" } }); - + if (response.Result == UnityExecutorResponseResult.Success) { return new UnityLicenseStatusCheck diff --git a/UnityAutomaticLicensor/UnityLicensorRequest.cs b/UnityAutomaticLicensor/UnityLicensorRequest.cs index 24326f7..6ef92a8 100644 --- a/UnityAutomaticLicensor/UnityLicensorRequest.cs +++ b/UnityAutomaticLicensor/UnityLicensorRequest.cs @@ -6,8 +6,14 @@ public class UnityLicensorRequest public string Password { get; set; } + public string UnityVersion { get; set; } + + public string UnityChangeset { get; set; } + + public string UnityLicensePath { get; set; } + public string UnityExecutablePath { get; set; } - public string UnityVersion { get; set; } + public bool CheckSuccess { get; set; } } }