package runci.targets; import sys.FileSystem; import sys.io.File; import sys.io.Process; import haxe.Json; import haxe.Http; import haxe.io.Path; import runci.System.*; import runci.System.CommandFailure; import runci.Config.*; using StringTools; class Flash { static final miscFlashDir = getMiscSubDir('flash'); static var playerLocation:String; static final DEFAULT_APACHE_MIRROR = "https://downloads.apache.org/"; static function getPreferredApacheMirror() { return try { Json.parse(Http.requestUrl("https://www.apache.org/dyn/closer.lua?as_json=1")).preferred; } catch (e) { failMsg('Unable to determine preferred Apache mirror. Defaulting to $DEFAULT_APACHE_MIRROR'); DEFAULT_APACHE_MIRROR; }; } static function downloadAndExtractFlexSdk(version:String, sdkPath:String):Void { final apacheMirror = getPreferredApacheMirror(); final archiveExtension = if (systemName == "Windows") "zip" else "tar.gz"; final archiveName = 'apache-flex-sdk-${version}-bin.$archiveExtension'; runNetworkCommand("wget", ["-nv", '${apacheMirror}/flex/${version}/binaries/$archiveName', "-O", getDownloadPath() + '/$archiveName']); if (systemName == "Windows") { runCommand("7z", ["x", getDownloadPath() + '/$archiveName', "-o" + sdkPath, "-y"]); } else { runCommand("tar", ["-xf", getDownloadPath() + '/$archiveName', "-C", getInstallPath()]); } } static function setupFlexSdk():Void { if (commandSucceed("mxmlc", ["--version"])) { infoMsg('mxmlc has already been installed.'); return; } // download flex sdk final flexVersion = "4.16.1"; final flexSdkPath = Path.normalize(getInstallPath() + '/apache-flex-sdk-${flexVersion}-bin'); if (FileSystem.exists(flexSdkPath)) { infoMsg('Flex SDK found at $flexSdkPath'); } else { downloadAndExtractFlexSdk(flexVersion, flexSdkPath); } addToPATH(flexSdkPath + "/bin"); // download playerglobal.swc final playerGlobalSwcFolder = flexSdkPath + "/player"; final playerGlobalSwcSubFolder = playerGlobalSwcFolder + "/27.0"; FileSystem.createDirectory(playerGlobalSwcSubFolder); final playerGlobalSwcPath = '$playerGlobalSwcSubFolder/playerglobal.swc'; if (FileSystem.exists(playerGlobalSwcPath)) { infoMsg('playerglobal.swc found at $playerGlobalSwcPath'); } else { final flashVersion = getLatestFPVersion(); runNetworkCommand("wget", [ "-nv", 'https://fpdownload.macromedia.com/get/flashplayer/updaters/${flashVersion[0]}/playerglobal${flashVersion[0]}_${flashVersion[1]}.swc', "-O", playerGlobalSwcPath ]); } // set playerglobal.swc File.saveContent(flexSdkPath + "/env.properties", 'env.PLAYERGLOBAL_HOME=$playerGlobalSwcFolder'); // ensure flex sdk is working runCommand("mxmlc", ["--version"]); } static function getLatestFPVersion():Array { final appcast = Xml.parse(haxe.Http.requestUrl("http://fpdownload.macromedia.com/get/flashplayer/update/current/xml/version_en_mac_pep.xml")); final versionStr = new haxe.xml.Access(appcast).node.XML.node.update.att.version; return versionStr.split(",").map(Std.parseInt); } static function downloadPlayer(version:Int, fileName:String, outputPath:String) { runNetworkCommand("wget", [ "-nv", 'https://fpdownload.macromedia.com/pub/flashplayer/updaters/$version/$fileName', "-O", '$outputPath/$fileName' ]); } static function setupFlashPlayerMac():Void { playerLocation = getInstallPath() + "/Flash Player.app"; if (FileSystem.exists(playerLocation)) infoMsg('Flash player found at $playerLocation'); else { final majorVersion = getLatestFPVersion()[0]; final packageName = 'flashplayer_${majorVersion}_sa_debug.dmg'; downloadPlayer(majorVersion, packageName, getDownloadPath()); runCommand("hdiutil", ["attach", getDownloadPath() + '/$packageName']); runCommand("cp", ["-R", "/Volumes/Flash Player/Flash Player.app", getInstallPath()]); runCommand("hdiutil", ["detach", "/Volumes/Flash Player"]); // Disable the "application downloaded from Internet" warning runCommand("xattr", ["-d", "-r", "com.apple.quarantine", playerLocation]); } runCommand("open", ["-a", playerLocation, "-v"]); } static function setupFlashPlayerLinux():Void { final playerName = "flashplayerdebugger"; playerLocation = Path.join([getInstallPath(), playerName]); if (Sys.command("type", [playerName]) == 0) { playerLocation = playerName; infoMsg('Using $playerName from PATH'); } else if(FileSystem.exists(playerLocation)) { infoMsg('Flash player found at $playerLocation'); } else { Linux.requireAptPackages(["libglib2.0-0", "libfreetype6"]); final tarFileName = 'flash_player_sa_linux_debug.x86_64.tar.gz'; downloadPlayer(getLatestFPVersion()[0], tarFileName, getDownloadPath()); runCommand("tar", ["-xf", getDownloadPath() + '/$tarFileName', "-C", getInstallPath()]); } // ensure the debugger works switch (ci) { case GithubActions: runCommand("xvfb-run", ["-a", playerLocation, "-v"]); case _: runCommand(playerLocation, ["-v"]); } } static function setupFlashPlayerWindows():Void { final majorVersion = getLatestFPVersion()[0]; final exeName = 'flashplayer_${majorVersion}_sa_debug.exe'; playerLocation = Path.join([getInstallPath(), exeName]); if (FileSystem.exists(playerLocation)) infoMsg('Flash player found at $playerLocation'); else downloadPlayer(majorVersion, exeName, getInstallPath()); } static function setupFlashPlayer():Void { switch (systemName) { case "Linux": setupFlashPlayerLinux(); case "Mac": setupFlashPlayerMac(); case "Windows": setupFlashPlayerWindows(); case _: throw "unsupported system"; } } static function readUntil(stdout:haxe.io.Input, expected:String, ?unexpectedStrings:MapVoid>) { final possibleStrings = unexpectedStrings?.copy() ?? []; possibleStrings[expected] = function() {}; var output = ""; while (true) { final char = try { String.fromCharCode(stdout.readByte()); } catch (e:haxe.io.Eof) { failMsg('Expected to find "$expected" in stdout'); throw new CommandFailure(); }; Sys.print(char); output += char; for (string => onMatch in possibleStrings) { if (output.endsWith(string)) { onMatch(); return; } } } } /** Run a Flash swf file. Throws `CommandFailure` if unsuccessful. */ static function runFlash(swf:String):Void { swf = FileSystem.fullPath(swf); infoMsg('Running .swf file: $swf'); final debuggerProcess = new Process("fdb"); final FDB_PROMPT = "(fdb) "; // waits for the fdb prompt and runs a command function runDebuggerCommand(command:String) { readUntil(debuggerProcess.stdout, FDB_PROMPT); Sys.println(command); debuggerProcess.stdin.writeString('$command\n'); } runDebuggerCommand("run"); function onUnexpectedPrompt() { Sys.println("quit"); debuggerProcess.stdin.writeString("quit\n"); throw new CommandFailure(); } readUntil(debuggerProcess.stdout, "Waiting for Player to connect", [FDB_PROMPT => onUnexpectedPrompt]); final playerProcess = switch (systemName) { case "Linux" if (ci == GithubActions): new Process("xvfb-run", ["-a", playerLocation, swf]); case "Mac": new Process("open", ["-a", playerLocation, swf]); default: new Process(playerLocation, [swf]); }; readUntil(debuggerProcess.stdout, "Player connected; session starting.", [FDB_PROMPT => onUnexpectedPrompt]); runDebuggerCommand("continue"); var success = true; readUntil(debuggerProcess.stdout, "(success: true)", [ FDB_PROMPT => onUnexpectedPrompt, "(success: false)" => () -> { success = false; } ]); runDebuggerCommand("quit"); debuggerProcess.kill(); debuggerProcess.close(); playerProcess.kill(); playerProcess.close(); if (!success) throw new CommandFailure(); } static public function run(args:Array) { setupFlashPlayer(); setupFlexSdk(); for (flashVersion in ["11", "32"]) { runCommand("haxe", ["compile-flash.hxml", "-D", "fdb", "-D", "dump", "-D", "dump_ignore_var_ids", "--swf-version", flashVersion].concat(args)); runFlash("bin/unit.swf"); } Display.maybeRunDisplayTests(Flash); changeDirectory(miscFlashDir); runCommand("haxe", ["run.hxml"]); } }