Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] macOS support #9

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ flashplayer.*
.svelte-kit
.tauri
build_script.sh
vite.config.ts*
vite.config.ts*

# System files
desktop.ini
**/desktop.ini
.DS_Store
**/.DS_Store
23 changes: 21 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,33 @@
"isBackground": true,
// change this to your `beforeDevCommand`:
"command": "yarn",
"args": ["dev"]
"args": ["dev"],
},
{
"label": "ui:tauri-dev",
"type": "shell",
// `dev` keeps running in the background
// ideally you should also configure a `problemMatcher`
// see https://code.visualstudio.com/docs/editor/tasks#_can-a-background-task-be-used-as-a-prelaunchtask-in-launchjson
"isBackground": true,
// change this to your `beforeDevCommand`:
"command": "yarn",
"args": ["tauri", "dev"],
"group": {
"kind": "test",
"isDefault": true,
}
},
{
"label": "ui:build",
"type": "shell",
// change this to your `beforeBuildCommand`:
"command": "yarn",
"args": ["build"]
"args": ["build"],
"group": {
"kind": "build",
"isDefault": true,
}
}
]
}
5 changes: 5 additions & 0 deletions auto-install-dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/zsh

VOLUME=`hdiutil attach -nobrowse "$1" | grep Volumes | cut -f 3`
cp -rf "$VOLUME"/*.app "$2"
hdiutil detach "$VOLUME"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script is never used?

1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ tauri = { version = "1.6.4", features = [ "http-all",
tokio = { version = "1", features = ["io-util", "fs"] }
tauri-webview2 = "0.1.2"
window-shadows = "0.2.2" # optional, for window shadows on Windows (unmaintained) - tauri@v2 will have this feature built-in.
regex = "1"

[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
Expand Down
10 changes: 6 additions & 4 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod networking;
mod runtime_extraction;
mod version_manager;

use crate::version_manager::*;
Expand Down Expand Up @@ -50,19 +51,20 @@ async fn initialize_app(app: AppHandle) -> Result<(), String> {
}
};

let file_info = get_platform_flash_runtime(&env::consts::OS)?;
if !file_info.0.exists() {
let platform = &env::consts::OS;
let runtime_info = get_platform_flash_runtime(platform)?;
if !runtime_info.0.exists() {
let log = "Downloading flash player for your platform...";
emit_event(&app, "infoLog", log.to_string());
download_runtime(file_info, use_https).await?;
download_and_extract_runtime(runtime_info, platform, use_https).await?;
}

Ok(())
}

#[command]
fn launch_game(build_name: &str, language: &str, token: Option<&str>) -> Result<(), String> {
let (flash_runtime_path, _) = get_platform_flash_runtime(&env::consts::OS)?;
let (flash_runtime_path, _, _) = get_platform_flash_runtime(&env::consts::OS)?;

if !flash_runtime_path.exists() {
eprintln!("cannot find file: {}", flash_runtime_path.display());
Expand Down
6 changes: 4 additions & 2 deletions src-tauri/src/networking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,20 +133,22 @@ fn get_protocol(use_https: bool) -> &'static str {

pub async fn download_file(
file_path: &PathBuf,
url: &str,
web_path: &str,
use_https: bool,
) -> Result<(), FetchError> {
let full_url = format!(
"{}://{}{}",
get_protocol(use_https),
SWFS_URL,
url
web_path
);
println!("Full download url: {:?}", full_url);
let mut response = reqwest::Client::new().get(&full_url).send().await?;
// Exit early on bad code
if !response.status().is_success() {
return Err(FetchError::InvalidStatusCode(response.status().as_u16()));
}
println!("Downloading to path: {:?}", file_path);
let mut out = File::create(file_path).await?;
while let Some(chunk) = response.chunk().await? {
out.write_all(chunk.as_ref()).await?;
Expand Down
109 changes: 109 additions & 0 deletions src-tauri/src/runtime_extraction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std::{
fs,
path::PathBuf,
process::Command,
};
use regex::Regex;

use crate::RUNTIMES_DIR;

pub fn potentially_extract_runtime(
executable_path: PathBuf,
package_path: PathBuf,
platform: &str,
) -> Result<(), String> {
if executable_path == package_path {
// Nothing to do
return Ok(());
}
match platform {
"darwin" | "macos" => extract_runtime_macos(executable_path, package_path),
_ => Err("Runtime extraction not supported on this platform".to_string())
}
}

fn extract_runtime_macos(
executable_path: PathBuf,
package_path: PathBuf,
) -> Result<(), String> {
let hdiutil_process = Command::new("hdiutil")
.arg("attach")
.arg("-nobrowse")
.arg(package_path)
.output()
.map_err(|err| err.to_string())?;

if !hdiutil_process.status.success() {
return Err(format!(
"Mounting .dmg failed: {}",
String::from_utf8_lossy(&hdiutil_process.stderr)
));
}

// Extract path to mounted volume from process output
let regex = Regex::new(r"(/Volumes/[\w\s]+)").unwrap();
let hdiutil_output_string = String::from_utf8_lossy(&hdiutil_process.stdout);
let mounted_volume;
if let Some(matching_volume_capture) = regex.captures(&hdiutil_output_string) {
mounted_volume = matching_volume_capture.get(0).unwrap().as_str().trim();
}
else {
return Err("Mount command returned no path to the volume".to_string());
}

// Copy runtime executable from volume to runtime folder
let volume_read_result = fs::read_dir(mounted_volume)
.map_err(|err| err.to_string())?;

let volume_apps: Vec<_> = volume_read_result
.filter_map(|file_entry| {
let file_entry = file_entry.ok()?;
let file_path = file_entry.path();
if !file_path.is_file() && file_path.extension()?.eq("app") {
Some(file_path)
}
else {
None
}
})
.collect();

// Ensure only one .app is contained
let app_file_count = volume_apps.len();
if app_file_count != 1 {
return Err(format!(
"Unexpected number of app files within volume: {}, expected 1, contained files: {:?}",
app_file_count,
volume_apps,
));
}

// Copy the only .app folder to runtime folder using system copy command
let source_app_folder = volume_apps.first().unwrap();
let dest_folder = format!("{}/", RUNTIMES_DIR);
let copy_process = Command::new("cp")
.arg("-rf")
.arg(source_app_folder)
.arg(&dest_folder)
.output()
.map_err(|err| err.to_string())?;

if !copy_process.status.success() {
return Err(format!(
"Copying .app from: {:?} to {}, error: {}",
source_app_folder,
dest_folder,
String::from_utf8_lossy(&hdiutil_process.stderr),
));
}

// Finally check if the executable path is now available
if !executable_path.exists() {
return Err(format!(
"After extraction, the expected executable path is still not available: {:?}",
executable_path,
));
}

Ok(())
}
54 changes: 44 additions & 10 deletions src-tauri/src/version_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
};

use crate::networking::{self, download_file, fetch_json_with_http_retry};
use crate::runtime_extraction::potentially_extract_runtime;
use serde::{Deserialize, Serialize};

pub const VERSION_MANIFEST_URL: &str = "cdn.bymrefitted.com/versionManifest.json";
Expand Down Expand Up @@ -45,24 +46,57 @@ fn ensure_folder_exists(runtime_path: &Path) -> std::io::Result<()> {
Ok(())
}

pub async fn download_runtime(
(runtime_path, file_extension): (PathBuf, String),
pub async fn download_and_extract_runtime(
(executable_path, package_path, package_file_name): (PathBuf, Option<PathBuf>, String),
platform: &str,
use_https: bool,
) -> Result<(), String> {
ensure_folder_exists(Path::new(RUNTIMES_DIR)).expect("Could not create runtimes folder");

download_file(&runtime_path, &file_extension, use_https)
// If there is a package path, download to that path. If not, fallback to the executable path
let file_path = package_path.clone().unwrap_or(executable_path.clone());
println!("Package path: {:?}", package_path);
println!("Executable path: {:?}", executable_path);
println!("Download to path: {:?}", file_path);
println!("Package file name: {:?}", package_file_name);

download_file(&file_path, &package_file_name, use_https)
.await
.map_err(|err| err.to_string())
.map_err(|err| err.to_string())?;

if let Some(package_path) = package_path {
potentially_extract_runtime(executable_path, package_path, platform)
}
else {
// As there is no package path, definitely no extraction step is necessary
Ok(())
}
}

pub fn get_platform_flash_runtime(platform: &str) -> Result<(PathBuf, String), String> {
let flash_runtimes = match platform {
"windows" => Ok("flashplayer.exe".to_string()),
"darwin" => Ok("flashplayer.dmg".to_string()),
"linux" => Ok("flashplayer".to_string()),
/// Returns path to runtime executable, path to runtime package, file name of runtime package
pub fn get_platform_flash_runtime(platform: &str) -> Result<(PathBuf, Option<PathBuf>, String), String> {
// If the package (second value) is None, it is the same as the executable (first value)
let flash_runtime_executable_and_package = match platform {
"windows" => Ok(("flashplayer.exe".to_string(), None)),
"darwin" | "macos" => Ok((
"Flash Player.app/Contents/MacOS/Flash Player".to_string(),
Some("flashplayer.dmg".to_string())
)),
"linux" => Ok(("flashplayer".to_string(), None)),
_ => Err(format!("unsupported platform: {}", platform)),
};

flash_runtimes.map(|runtime| (PathBuf::from(RUNTIMES_DIR).join(runtime.clone()), runtime))
fn get_runtime_dir_path_buf(path_str: &str) -> PathBuf {
PathBuf::from(RUNTIMES_DIR).join(path_str)
}

flash_runtime_executable_and_package.map(|executable_and_package| {
// Convert all path strings to runtime directory PathBufs
let executable = executable_and_package.0.clone();
let executable_pathbuf = get_runtime_dir_path_buf(&executable);
// If the runtime package is None, leave it untouched
let package = executable_and_package.1;
let package_pathbuf = package.clone().map(|path_str| get_runtime_dir_path_buf(&path_str));
(executable_pathbuf, package_pathbuf, package.unwrap_or(executable))
})
}