1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
use crate::error::{CobbleError, CobbleResult};
use crate::instance::Instance;
use crate::minecraft::{
    build_fabric_launch_command, build_launch_command, GameProcess, GameProcessHandle,
    LaunchOptions,
};
use std::process::{Command, Stdio};
use tokio::process::Child;

impl Instance {
    /// Builds the launch command for this instance.
    /// Uses fabric when enabled.
    /// The instance needs to be properly installed.
    pub async fn launch_command(&self, options: &LaunchOptions) -> CobbleResult<Command> {
        trace!("Check if instance is installed");
        if !self.installed {
            return Err(CobbleError::NotInstalled);
        }

        trace!("Read version data from disk");
        let version_data = self.read_version_data().await?;

        let command = match &self.fabric_version {
            Some(_) => {
                trace!("Read fabric version data from disk");
                let fabric_version_data = self.read_fabric_version_data().await?;

                build_fabric_launch_command(
                    &version_data,
                    &fabric_version_data,
                    options,
                    self.dot_minecraft_path(),
                    self.libraries_path(),
                    self.assets_path(),
                    self.natives_path(),
                    self.log_configs_path(),
                )
            }
            None => build_launch_command(
                &version_data,
                options,
                self.dot_minecraft_path(),
                self.libraries_path(),
                self.assets_path(),
                self.natives_path(),
                self.log_configs_path(),
            ),
        };

        Ok(command)
    }

    /// Launches the instance as a child process.
    /// Uses fabric when enabled.
    /// Game process exits if parent exits.
    pub async fn launch<I, O, E>(
        &self,
        options: &LaunchOptions,
        stdin: I,
        stdout: O,
        stderr: E,
    ) -> CobbleResult<Child>
    where
        I: Into<Stdio>,
        O: Into<Stdio>,
        E: Into<Stdio>,
    {
        let mut command = self.launch_command(options).await?;

        command.stdin(stdin).stdout(stdout).stderr(stderr);

        let mut command = tokio::process::Command::from(command);

        Ok(command.spawn()?)
    }

    /// Launches the instance as a detached process.
    /// Uses fabric when enabled.
    /// Game process does not exit if parent exits.
    ///
    /// On unix platforms it is done by forking the process.
    ///
    /// On windows it is done using the
    /// [DETACHED_PROCESS and CREATE_NEW_PROCESS_GROUP flags](https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags).
    pub async fn detached_launch<I, O, E>(
        &self,
        options: &LaunchOptions,
        stdin: I,
        stdout: O,
        stderr: E,
    ) -> CobbleResult<GameProcessHandle>
    where
        I: Into<Stdio>,
        O: Into<Stdio>,
        E: Into<Stdio>,
    {
        let mut command = self.launch_command(options).await?;

        command.stdin(stdin).stdout(stdout).stderr(stderr);

        Ok(GameProcessHandle::launch(command)?)
    }
}