( /* MAXPKG Installer Bridge for Autodesk 3ds Max ------------------------------------------------------------ Product: MAXPKG File: install.ms Purpose: Starts the installed MaxPkg Package Manager, installs it when the user explicitly allows it, and asks MaxPkg to install the embedded package GUID. Developer: MAXPKG / 3DGROUND Website: https://maxpkg.dev Author: Vasyl Lukianenko Contact: support.maxpkg@gmail.com Copyright: Copyright (c) MAXPKG / 3DGROUND. All rights reserved. License: This installer bridge is provided only for use with the official MAXPKG service. Unauthorized copying, modification, redistribution, reverse engineering, or use with third-party installer endpoints is prohibited. Security: Package metadata is embedded by the official maxpkg website before the script is downloaded. This bridge installs only the official MaxPkg Runtime from the maxpkg-dev/runtime repository after explicit user confirmation. Disclaimer: This software is provided "as is", without warranty of any kind. Use at your own risk. */ global MaxPkg global MaxPkgRuntimeManualDownloadUrl global MaxPkgRuntimeManualInstallError local EMBEDDED_PACKAGE = "model-packer" local EMBEDDED_GUID = "ef032600-6d09-41fd-a02d-1e666853a8be" local EMBEDDED_API_ORIGIN = "https://maxpkg.dev" local RUNTIME_ZIP_URL = "https://github.com/maxpkg-dev/runtime/archive/refs/heads/main.zip" fn trimString s = ( if (s == undefined) do return "" local result = s as string while (result.count > 0 and ( substring result 1 1 == " " or substring result 1 1 == "\t" or substring result 1 1 == "\n" or substring result 1 1 == "\r" )) do ( result = substring result 2 (result.count - 1) ) while (result.count > 0 and ( substring result result.count 1 == " " or substring result result.count 1 == "\t" or substring result result.count 1 == "\n" or substring result result.count 1 == "\r" )) do ( result = substring result 1 (result.count - 1) ) return result ) fn isTemplateValue s = ( if (s == undefined) do return true return (findString (s as string) "{{{") != undefined ) fn getEmbeddedValue s = ( if (s == undefined) do return undefined local value = trimString (s as string) if (value == "" or isTemplateValue value) do return undefined return value ) fn ensureSlash folderPath = ( if (folderPath == undefined or folderPath == "") do return "" local normalizedPath = substituteString (folderPath as string) "/" @"\" if ((substring normalizedPath normalizedPath.count 1) == @"\") do return normalizedPath return normalizedPath + @"\" ) fn isSafeGuid s = ( if (s == undefined or s.count != 36) do return false local allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-" for i in 1 to s.count do ( local ch = substring s i 1 if (findString allowed ch) == undefined do ( return false ) ) return true ) fn isSafePackageId s = ( if (s == undefined or s.count == 0) do return false local allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_/." for i in 1 to s.count do ( local ch = substring s i 1 if (findString allowed ch) == undefined do ( return false ) ) return true ) fn isSafeApiOrigin s = ( if (s == undefined or s.count == 0) do return false local lowered = toLower s return ( substring lowered 1 8 == "https://" or substring lowered 1 7 == "http://" ) ) fn trimTrailingSlash s = ( if (s == undefined or s.count == 0) do return "" if (substring s s.count 1 == "/") then ( return substring s 1 (s.count - 1) ) return s ) fn configureTls12 = ( try ( local servicePointManagerClass = dotNetClass "System.Net.ServicePointManager" local securityProtocolClass = dotNetClass "System.Net.SecurityProtocolType" servicePointManagerClass.SecurityProtocol = securityProtocolClass.Tls12 return true ) catch ( -- Ignore TLS setup errors on older systems. return false ) ) fn configureRuntime apiOrigin = ( if (MaxPkg == undefined) do return false if (not (isProperty MaxPkg #settings)) do return true if (MaxPkg.settings == undefined) do return true try ( if (isProperty MaxPkg.settings #apiEndpoint) do ( MaxPkg.settings.apiEndpoint = apiOrigin + "/api/runtime/v1/" ) if (isProperty MaxPkg #saveSettings) do MaxPkg.saveSettings() return true ) catch ( format "MaxPkg Runtime settings could not be updated: %\n" (getCurrentException()) return false ) ) fn maxPkgCanInstall = ( return MaxPkg != undefined and isProperty MaxPkg #install ) fn tryStartRuntimeFromDisk = ( if (maxPkgCanInstall()) do return true local runtimeRoot = ensureSlash ((getDir #userScripts) + @"\MaxPkg Runtime") local runtimeStartupPath = runtimeRoot + "startup.ms" local startupShimPath = ensureSlash (getDir #userStartupScripts) + "maxpkg-runtime-startup.ms" try ( if (doesFileExist runtimeStartupPath) do fileIn runtimeStartupPath if (maxPkgCanInstall()) do return true ) catch ( format "MaxPkg installed runtime startup failed: %\n" (getCurrentException()) ) try ( if (doesFileExist startupShimPath) do fileIn startupShimPath if (maxPkgCanInstall()) do return true ) catch ( format "MaxPkg startup shim failed: %\n" (getCurrentException()) ) return maxPkgCanInstall() ) fn prepareWebClient = ( configureTls12() local wc = dotNetObject "System.Net.WebClient" wc.Headers.Add "User-Agent" "MAXPKG-3dsMax-Installer" return wc ) fn openExternalUrl url = ( try ( shellLaunch url "" return true ) catch ( try ( local processClass = dotNetClass "System.Diagnostics.Process" processClass.Start url return true ) catch ( messageBox ("Could not open the download URL automatically.\n\n" + (url as string)) title:"MaxPkg" return false ) ) ) fn showRuntimeManualDownload errorMessage runtimeZipUrl = ( MaxPkgRuntimeManualDownloadUrl = runtimeZipUrl MaxPkgRuntimeManualInstallError = errorMessage try ( destroyDialog MaxPkgRuntimeManualDownloadRollout ) catch ( ) rollout MaxPkgRuntimeManualDownloadRollout "MaxPkg Runtime installation" width: 520 height: 260 ( label statusLabel "MaxPkg Package Manager could not be installed automatically." pos: [20, 18] width: 480 height: 20 label detailLabel "You can download the official Runtime ZIP archive manually:" pos: [20, 48] width: 480 height: 20 hyperlink downloadLink "Download MaxPkg Runtime ZIP" address: "https://github.com/maxpkg-dev/runtime/archive/refs/heads/main.zip" pos: [20, 78] width: 460 height: 22 color: (color 120 155 255) hoverColor: (color 165 195 255) visitedColor: (color 120 155 255) editText urlText "" pos: [20, 108] width: 480 height: 22 readOnly: true groupBox errorGroup "What happened" pos: [20, 142] width: 480 height: 58 label errorText "" pos: [32, 164] width: 456 height: 30 button openButton "Open ZIP archive" pos: [250, 218] width: 120 height: 28 button closeButton "Close" pos: [380, 218] width: 120 height: 28 on MaxPkgRuntimeManualDownloadRollout open do ( if (MaxPkgRuntimeManualDownloadUrl != undefined and MaxPkgRuntimeManualDownloadUrl != "") do ( downloadLink.address = MaxPkgRuntimeManualDownloadUrl urlText.text = MaxPkgRuntimeManualDownloadUrl ) if (MaxPkgRuntimeManualInstallError != undefined and MaxPkgRuntimeManualInstallError != "") do ( errorText.text = MaxPkgRuntimeManualInstallError ) ) on openButton pressed do ( if (MaxPkgRuntimeManualDownloadUrl != undefined and MaxPkgRuntimeManualDownloadUrl != "") do ( openExternalUrl MaxPkgRuntimeManualDownloadUrl ) ) on closeButton pressed do ( destroyDialog MaxPkgRuntimeManualDownloadRollout ) ) createDialog MaxPkgRuntimeManualDownloadRollout style: #(#style_titlebar, #style_sysmenu, #style_toolwindow) ) fn deleteDirectorySafe folderPath = ( if (folderPath == undefined or folderPath == "") do return true try ( if (doesDirectoryExist folderPath) do ( local directoryClass = dotNetClass "System.IO.Directory" directoryClass.Delete folderPath true ) return true ) catch ( return false ) ) fn extractZip zipPath targetPath = ( try ( try ( dotNet.loadAssembly "System.IO.Compression.FileSystem" ) catch ( -- Some 3ds Max versions load the compression assembly by default. ) local zipFileClass = dotNetClass "System.IO.Compression.ZipFile" zipFileClass.ExtractToDirectory zipPath targetPath return true ) catch ( return false ) ) fn findRuntimeInstaller extractedRoot = ( try ( local directoryClass = dotNetClass "System.IO.Directory" local searchOption = dotNetClass "System.IO.SearchOption" local files = directoryClass.GetFiles extractedRoot "install.ms" searchOption.AllDirectories for filePath in files do ( local folderPath = ensureSlash (getFilenamePath filePath) if ( doesDirectoryExist (folderPath + "core") and doesFileExist (folderPath + "startup.ms") ) do return filePath ) ) catch ( return undefined ) return undefined ) fn installRuntimeFromGitHub = ( local tempRoot = ensureSlash (getDir #temp) + "maxpkg-runtime-bootstrap" local zipPath = tempRoot + @"\runtime.zip" local extractRoot = tempRoot + @"\extract" local runtimeZipUrl = getEmbeddedValue RUNTIME_ZIP_URL progressStart "Installing MaxPkg Package Manager..." try ( if (runtimeZipUrl == undefined or runtimeZipUrl == "") do throw "Runtime download URL is missing." progressUpdate 10 deleteDirectorySafe tempRoot makeDir tempRoot all: true makeDir extractRoot all: true progressUpdate 25 local wc = prepareWebClient() wc.DownloadFile runtimeZipUrl zipPath if (not (doesFileExist zipPath)) do throw "Runtime download failed." progressUpdate 50 if (not (extractZip zipPath extractRoot)) do throw "Could not extract MaxPkg Runtime archive." progressUpdate 70 local installerPath = findRuntimeInstaller extractRoot if (installerPath == undefined or installerPath == "") do throw "Runtime installer was not found in the downloaded archive." progressUpdate 85 fileIn installerPath progressUpdate 95 if (not (tryStartRuntimeFromDisk())) do throw "MaxPkg Runtime was installed, but could not be started." progressUpdate 100 progressEnd() deleteDirectorySafe tempRoot return true ) catch ( progressEnd() deleteDirectorySafe tempRoot showRuntimeManualDownload (getCurrentException()) runtimeZipUrl return false ) ) fn ensureRuntimeInstalled = ( if (tryStartRuntimeFromDisk()) do return true local shouldInstall = queryBox ( "MaxPkg Package Manager is not installed.\n\n" + "It is required to install this package. MaxPkg will download the official runtime from GitHub and install it now.\n\n" + "Install MaxPkg Package Manager?" ) title:"MaxPkg" if (not shouldInstall) do ( messageBox "Nothing was installed.\n\nYou declined to install MaxPkg Package Manager." title:"MaxPkg" return false ) return installRuntimeFromGitHub() ) fn installStatusIsSuccess status = ( if (status == true) do return true if (status == false or status == undefined) do return false try ( if (isProperty status #success) do return status.success == true if (isProperty status #isSuccess) do return status.isSuccess == true if (isProperty status #ok) do return status.ok == true ) catch ( ) return false ) fn installStatusMessage status fallbackMessage = ( if (fallbackMessage == undefined) do fallbackMessage = "" try ( if (status != undefined and isProperty status #message and status.message != undefined and status.message != "") do ( return status.message as string ) if (status != undefined and isProperty status #error and status.error != undefined and status.error != "") do ( return status.error as string ) ) catch ( ) return fallbackMessage ) fn showPackageInstallResult packageId status = ( if (installStatusIsSuccess status) then ( local successMessage = installStatusMessage status ("Package installed successfully.\n\n" + packageId) if(::MaxPkg == undefined) do messageBox successMessage title:"MaxPkg" return true ) local failureMessage = installStatusMessage status ( "Package installation failed.\n\n" + "Open MaxPkg Package Manager for details, or try again from the package page.\n\n" + packageId ) if(::MaxPkg == undefined) do messageBox failureMessage title:"MaxPkg" return false ) try ( configureTls12() local packageId = getEmbeddedValue EMBEDDED_PACKAGE local packageGuid = getEmbeddedValue EMBEDDED_GUID local apiOrigin = getEmbeddedValue EMBEDDED_API_ORIGIN if (apiOrigin != undefined) do apiOrigin = trimTrailingSlash apiOrigin if (packageId == undefined or packageId == "") then ( messageBox "MAXPKG package data is missing.\n\nOpen the package page and drag the installer from the Install dialog." title:"MaxPkg" ) else if (not isSafePackageId packageId) then ( messageBox ("Invalid MAXPKG package id:\n\n" + (packageId as string)) title:"MaxPkg" ) else if (packageGuid == undefined or not (isSafeGuid packageGuid)) then ( messageBox "MAXPKG package GUID is missing or invalid.\n\nOpen the package page and drag the installer from the Install dialog." title:"MaxPkg" ) else if (not isSafeApiOrigin apiOrigin) then ( messageBox "MAXPKG API origin is missing or invalid.\n\nOpen the package page and drag the installer from the Install dialog." title:"MaxPkg" ) else if (ensureRuntimeInstalled()) then ( configureRuntime apiOrigin if (maxPkgCanInstall()) then ( local installStatus = MaxPkg.install packageGuid showPackageInstallResult packageId installStatus ) else ( messageBox "MaxPkg Package Manager is installed, but its install command is unavailable." title:"MaxPkg" ) ) ) catch ( messageBox ("MAXPKG install failed:\n\n" + getCurrentException()) title:"MaxPkg" ) )