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
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later

use std::fs;

fn from_u16(from: &mut [u16]) -> &[u8] {
    #[cfg(target_endian = "little")]
    from.iter_mut().for_each(|word| *word = word.to_be());

    let ptr: *const u8 = from.as_ptr().cast();
    let len = from.len().checked_mul(2).unwrap();

    unsafe { std::slice::from_raw_parts(ptr, len) }
}

fn find_needle(installer_file: &[u8], needle: &str) -> Option<String> {
    let mut needle: Vec<u16> = needle.encode_utf16().collect();
    let bytes = from_u16(&mut needle);

    let mut position = installer_file
        .windows(bytes.len())
        .position(|window| window == bytes)?;

    let parse_char_at_position = |position: usize| {
        let upper = installer_file[position];
        let lower = installer_file[position + 1];

        let result = char::decode_utf16([((upper as u16) << 8) | lower as u16])
            .map(|r| r.map_err(|e| e.unpaired_surrogate()))
            .collect::<Vec<_>>();

        result[0]
    };

    let mut string: String = String::new();

    let mut last_char = parse_char_at_position(position);
    while last_char.is_ok() && last_char.unwrap() != '\0' {
        string.push(last_char.unwrap());

        position += 2;
        last_char = parse_char_at_position(position);
    }

    Some(string)
}

/// Extract the frontier URL from ffxivlauncher.exe
pub fn extract_frontier_url(launcher_path: &str) -> Option<String> {
    let installer_file = fs::read(launcher_path).unwrap();
    
    // New Frontier URL format
    if let Some(url) = find_needle(&installer_file, "https://launcher.finalfantasyxiv.com") {
        return Some(url);
    }
    
    // Old Frontier URL format
    if let Some(url) = find_needle(&installer_file, "https://frontier.ffxiv.com") {
        return Some(url);
    }

    None
}