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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use std::{panic, panic::PanicInfo, path::PathBuf};
use tracing::error;

pub fn set_panic_hook(log_filename: String, logs_dir: PathBuf) {
    // Set up panic handler to relay swish panic messages to the user
    let default_hook = panic::take_hook();
    panic::set_hook(Box::new(move |panic_info| {
        let panic_info_payload = panic_info.payload();
        let payload_string = panic_info_payload.downcast_ref::<String>();
        let reason = match payload_string {
            Some(s) => s,
            None => {
                let payload_str = panic_info_payload.downcast_ref::<&str>();
                match payload_str {
                    Some(st) => st,
                    None => "Payload is not a string",
                }
            },
        };
        let potential_cause = potential_cause(panic_info);

        let mut dialog_message = format!(
            "A critical error has occurred and Voxygen has been forced to terminate in an unusual \
             manner. Details about the error can be found below.\n\nPanic reason: {}\n\n",
            reason
        );

        if let Some(potential_cause) = potential_cause {
            // The error is a known error, so don't show the full bug report instructions
            // and instead show a potential fix.
            dialog_message.push_str(format!("Potential causes: {}\n\n", potential_cause).as_str())
        } else {
            dialog_message.push_str(
                format!("> What should I do?\n\
            \n\
            We need your help to fix this! You can help by contacting us and \
            reporting this problem. To do this, open an issue on the Veloren \
            issue tracker:\n\
            \n\
            https://www.gitlab.com/veloren/veloren/issues/new\n\
            \n\
            If you're on the Veloren community Discord server, we'd be \
            grateful if you could also post a message in the #support channel.
            \n\
            > What should I include?\n\
            \n\
            The error information below will be useful in finding and fixing \
            the problem. Please include as much information about your setup \
            and the events that led up to the panic as possible.
            \n\
            Voxygen has logged information about the problem (including this \
            message) to the file {}. Please include the contents of this \
            file in your bug report.
            \n\n", logs_dir.join(&log_filename).display())
                .as_str(),
            );
        }

        dialog_message.push_str(
            format!(
                "> Error information\n\nThe information below is intended for developers and \
                 testers.\n\nPanicInfo: {} \nGame version: {} [{}]",
                panic_info,
                *common::util::GIT_HASH,
                *common::util::GIT_DATE
            )
            .as_str(),
        );

        error!(
            "VOXYGEN HAS PANICKED\n\n{}\n\nBacktrace:\n{:?}",
            dialog_message,
            backtrace::Backtrace::new(),
        );

        #[cfg(feature = "native-dialog")]
        {
            use native_dialog::{MessageDialog, MessageType};

            let mbox = move || {
                MessageDialog::new()
                    .set_title("Veloren has crashed!")
                    //somehow `<` and `>` are invalid characters and cause the msg to get replaced
                    // by some generic text thus i replace them
                    .set_text(&dialog_message.replace('<', "[").replace('>', "]"))
                    .set_type(MessageType::Error)
                    .show_alert()
                    .unwrap()
            };

            // On windows we need to spawn a thread as the msg doesn't work otherwise
            #[cfg(target_os = "windows")]
            {
                let builder = std::thread::Builder::new().name("shutdown".into());
                builder
                    .spawn(move || {
                        mbox();
                    })
                    .unwrap()
                    .join()
                    .unwrap();
            }

            #[cfg(not(target_os = "windows"))]
            mbox();
        }

        default_hook(panic_info);
    }));
}

enum PotentialPanicCause {
    GraphicsCardIncompatibleWithRenderingBackend,
}

fn potential_cause(panic_info: &PanicInfo) -> Option<String> {
    let location = panic_info
        .location()
        .map_or("".to_string(), |x| x.file().to_string())
        .to_lowercase();

    // Use some basic heuristics to determine the likely cause of the panic. This is
    // deliberately overly simplistic as the vast majority of graphics errors
    // for example are caused by an incompatible GPU rather than a bug.
    let potential_cause = if location.contains("gfx") || location.contains("wgpu") {
        Some(PotentialPanicCause::GraphicsCardIncompatibleWithRenderingBackend)
    } else {
        None
    };

    potential_cause.map(potential_cause_to_string)
}

fn potential_cause_to_string(potential_cause: PotentialPanicCause) -> String {
    match potential_cause {
        PotentialPanicCause::GraphicsCardIncompatibleWithRenderingBackend => {
            "This error occurs when your graphics card is not compatible with the selected \
             graphics mode. This can be changed in the Airshipper settings window, however it may \
             be the case that your graphics card is not supported by any graphics mode."
                .to_string()
        },
    }
}