diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..9380186 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "i686-pc-windows-msvc" diff --git a/.gitignore b/.gitignore index 22174a2..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ -.vscode/ -build/ - -src/test.c -buildtest.bat +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2d52498 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,308 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "clap" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "mhf-iel" +version = "0.1.0" +dependencies = [ + "serde", + "smart-default", + "windows", +] + +[[package]] +name = "mhf-iel-cli" +version = "0.1.0" +dependencies = [ + "clap", + "mhf-iel", + "serde", + "serde_json", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smart-default" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f0b8aa2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[workspace] +members = ["mhf-iel-cli"] + +[package] +name = "mhf-iel" +version = "0.1.0" +edition = "2021" + +[workspace.dependencies] +serde = { version = "1.0", features = ["derive"] } + +[dependencies] +serde = { workspace = true } +smart-default = "0.7.1" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies.windows] +version = "0.51.1" +features = ["Win32_Foundation", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_Security", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_TextServices", "Win32_System_Memory", "Win32_System_WindowsProgramming"] diff --git a/README.md b/README.md index cc69933..fbb1ea0 100644 --- a/README.md +++ b/README.md @@ -17,21 +17,20 @@ If you're wondering 'Why use this instead of the original launcher?', here are s - Allows storing and displaying extra information. For example, it would be *possible* to get character portraits on the launcher window. - Removes the need to modify the launcher (since we're replacing it) and `mhfo-hd.dll` to remove GameGuard, since `mhfo-hd.dll` calls a function provided by the launcher to run GameGuard checks. -Obs.: It should be noted that the Python GUI on the root is for testing the APIs. A decent GUI will be developed in the future, but some decisions need to be made first. - ## Usage -If you want to test this, make sure your server is running under the [`newsign`](https://github.com/rockisch/Erupe-1/tree/newsign) branch. +If calling from another Rust project, make sure it itself is targeting `nightly-i686-pc-windows-msvc`, and just call `run` with the correct parameters. The idea at the moment is that the `Config` structure can be created from user provided settings, while `MhfConfig` contain data returned from [signv2server](https://github.com/ZeruLight/Erupe/tree/main/server/signv2server), but this might change in the future. -After that, copy `gui.py` and `mhf-iel.exe` (either from the releases page or by compiling it yourself) to the folder MHF is installed. Run `gui.py`, and be happy. +You use the [CLI interface](mhf-iel-cli/README.md) to run this project from any other program, and without the `i686` limitation. -## Compile +Feel free to create a ticket if you need another way to integrate this lib into your app (`.dll`, bindings for static linking, etc). -In order to compile the project, a x86/32bit version of `cl` should be used, since we need to interface with `mhfo-hd.dll`, which was built for x86. +## Compiling -The easiest way to get `cl` is by installing Visual Studio's 'Desktop C++ Dev' package. -If your Visual Studio is from 2019 or before, it should target x86 by default. -Otherwise, you can run the 'x86 Native Tools CMD' shortcut, which will set everything for you. +Before running `cargo build`, make sure you have the `nightly` toolchain and the `i686-pc-windows-msvc` target intalled: -After having a x86 version of `cl` available on your shell, just run `buildCMD.bat` or `buildDLL.bat`. +``` +rustup toolchain install nightly +rustup target add i686-pc-windows-msvc +``` diff --git a/buildCMD.bat b/buildCMD.bat deleted file mode 100644 index 29e83f4..0000000 --- a/buildCMD.bat +++ /dev/null @@ -1,3 +0,0 @@ -md "build" 2>NUL -:: cl.exe should be x86/32bit version -cl.exe src/cmd.c Shlwapi.lib User32.lib /Fo:build/ /Fe:build/mhf-iel.exe diff --git a/buildDLL.bat b/buildDLL.bat deleted file mode 100644 index a56f1d4..0000000 --- a/buildDLL.bat +++ /dev/null @@ -1,4 +0,0 @@ -md "build" 2>NUL -:: cl.exe should be x86/32bit version -cl.exe /D_USRDLL /D_WINDLL src/dll.c Shlwapi.lib User32.lib /Fo:build/ /link /DLL /OUT:build/mhf-iel.dll - diff --git a/gui.py b/gui.py index 75d5b65..3947045 100644 --- a/gui.py +++ b/gui.py @@ -7,11 +7,11 @@ import requests -ENDPOINT = 'http://127.0.0.1:8080' +ENDPOINT = "http://127.0.0.1:8080" -def boot_and_exit(char_id, is_new, token): - subprocess.Popen(['./mhf-iel.exe', str(char_id), str(int(is_new)), token]) +def boot_and_exit(char_id, char_new, token): + subprocess.Popen(["./mhf-iel.exe", str(char_id), str(int(char_new)), token]) exit() @@ -34,8 +34,14 @@ def __init__(self, root): button_frame = ttk.Frame(self.frm) button_frame.grid(pady=(10, 0)) - ttk.Button(button_frame, text="Login", command=partial(self.connect, 'login')).grid(row=0, column=0) - ttk.Button(button_frame, text="Create Account", command=partial(self.connect, 'register')).grid(row=0, column=1, padx=(20, 0), sticky=tk.E) + ttk.Button( + button_frame, text="Login", command=partial(self.connect, "login") + ).grid(row=0, column=0) + ttk.Button( + button_frame, + text="Create Account", + command=partial(self.connect, "register"), + ).grid(row=0, column=1, padx=(20, 0), sticky=tk.E) for child in self.frm.winfo_children(): child.grid_configure(sticky=(tk.W, tk.E)) @@ -45,25 +51,32 @@ def connect(self, action, *args): self.error_label.destroy() try: - resp = requests.post(f'{ENDPOINT}/{action}', json={'username': self.username.get(), 'password': self.password.get()}) + resp = requests.post( + f"{ENDPOINT}/{action}", + json={"username": self.username.get(), "password": self.password.get()}, + ) resp.raise_for_status() except requests.HTTPError as e: - self.error_label = ttk.Label(self.frm, text=f"Unable to {action}: {resp.status_code}") + self.error_label = ttk.Label( + self.frm, text=f"Unable to {action}: {resp.status_code}" + ) self.error_label.grid() return except Exception as e: - self.error_label = ttk.Label(self.frm, text=f"Unable to connect to server: {e}") + self.error_label = ttk.Label( + self.frm, text=f"Unable to connect to server: {e}" + ) self.error_label.grid() return data = resp.json() self.frm.destroy() - CharSelectionScreen(self.root, data.get('characters', []), data['token']) + CharSelectionScreen(self.root, data.get("characters", []), data["token"]) class CharSelectionScreen: - def __init__(self, root, characters=[], token=''): + def __init__(self, root, characters=[], token=""): self.root = root self.token = token self.character_id = tk.IntVar() @@ -75,10 +88,14 @@ def __init__(self, root, characters=[], token=''): char_frm = ttk.Frame(self.frm) char_frm.grid() for i, character in enumerate(characters, start=1): - ttk.Label(char_frm, text=character['name']).grid(row=i, column=1) - ttk.Button(char_frm, text='Select', command=partial(self.select, character['id'])).grid(row=i, column=2) + ttk.Label(char_frm, text=character["name"]).grid(row=i, column=1) + ttk.Button( + char_frm, text="Select", command=partial(self.select, character["id"]) + ).grid(row=i, column=2) - ttk.Button(self.frm, text='Create New Character', command=self.create_character).grid() + ttk.Button( + self.frm, text="Create New Character", command=self.create_character + ).grid() def select(self, char_id, *args): boot_and_exit(char_id, False, self.token) @@ -88,19 +105,23 @@ def create_character(self, *args): self.error_label.destroy() try: - resp = requests.post(f'{ENDPOINT}/character', json={'token': self.token}) + resp = requests.post(f"{ENDPOINT}/character", json={"token": self.token}) resp.raise_for_status() except requests.HTTPError as e: - self.error_label = ttk.Label(self.frm, text=f"Unable to create character: {resp.status_code}") + self.error_label = ttk.Label( + self.frm, text=f"Unable to create character: {resp.status_code}" + ) self.error_label.grid() return except Exception as e: - self.error_label = ttk.Label(self.frm, text=f"Unable to connect to server: {e}") + self.error_label = ttk.Label( + self.frm, text=f"Unable to connect to server: {e}" + ) self.error_label.grid() return data = resp.json() - boot_and_exit(data['id'], True, self.token) + boot_and_exit(data["id"], True, self.token) root = tk.Tk(className="mhf") diff --git a/mhf-iel-cli/Cargo.toml b/mhf-iel-cli/Cargo.toml new file mode 100644 index 0000000..a120631 --- /dev/null +++ b/mhf-iel-cli/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "mhf-iel-cli" +version = "0.1.0" +edition = "2021" + +[dependencies] +mhf-iel = { path = ".."} +serde = { workspace = true } +clap = { version = "4.4.7", features = ["derive"] } +serde_json = "1.0" diff --git a/mhf-iel-cli/README.md b/mhf-iel-cli/README.md new file mode 100644 index 0000000..89a1c4b --- /dev/null +++ b/mhf-iel-cli/README.md @@ -0,0 +1,22 @@ +# MHF IELess Launcher CLI + +CLI interface for `mhf-iel`. + +## Usage + +1. Get a `mhf-iel-cli.exe` file by either compiling the project or downloading the latest release. +2. Download [`config.json`](config.json). +1. Copy both files to the MHF folder. +2. Modify `config.json` to have valid values. Specifically, make sure the `char_*` keys and `user_token` have correct values. +3. Run `mhf-iel-cli.exe`. + +If you plan on using the CLI interface as the entrypoint of your external application, run `mhf-iel-cli.exe --help` to see some extra options available. + +## Compiling + +Before running `cargo build`, make sure you have the `nightly` toolchain and the `i686-pc-windows-msvc` target intalled: + +``` +rustup toolchain install nightly +rustup target add i686-pc-windows-msvc +``` diff --git a/mhf-iel-cli/config.json b/mhf-iel-cli/config.json new file mode 100644 index 0000000..fc8edf8 --- /dev/null +++ b/mhf-iel-cli/config.json @@ -0,0 +1,33 @@ +{ + "char_id": 1, + "char_name": "char_abc", + "char_new": false, + "char_hr": 999, + "char_gr": 50, + "char_ids": [ + 1, + 2 + ], + "user_rights": 12, + "user_token": "KySJuNnR2PJu00Uw", + "user_name": "user_abc", + "user_password": "123456", + "entrance_count": 1, + "current_ts": 0, + "expiry_ts": 4294967295, + "messages": [ + { + "message": "this is a test 1", + "flags": 0 + } + ], + "mez_event_id": 1, + "mez_start": 0, + "mez_end": 0, + "mez_solo_tickets": 10, + "mez_group_tickets": 4, + "mez_stalls": [ + "TokotokoPartnya", + "Pachinko" + ] +} diff --git a/mhf-iel-cli/src/main.rs b/mhf-iel-cli/src/main.rs new file mode 100644 index 0000000..c26c8f3 --- /dev/null +++ b/mhf-iel-cli/src/main.rs @@ -0,0 +1,43 @@ +#![windows_subsystem = "windows"] +use mhf_iel::{Config, MhfConfig}; + +use std::{fs::File, path::PathBuf}; + +use clap::Parser; + +#[derive(Parser, Debug, Default)] +#[command(about)] +pub struct CliConfig { + #[arg( + long, + help = "JSON config file (defaults to 'config.json' in current folder" + )] + pub config_file: Option, + #[arg( + long, + help = "JSON config file (defaults to 'config.json' in current folder" + )] + pub config_data: Option, + #[arg(help = "game folder (defaults to current folder)")] + pub game_folder: Option, +} + +fn main() { + let cli_config = CliConfig::parse(); + let config_data = cli_config + .config_data + .or_else(|| { + cli_config + .config_file + .or_else(|| std::env::current_dir().map(|d| d.join("config.json")).ok()) + .and_then(|v| File::open(v).ok()) + .and_then(|v| std::io::read_to_string(v).ok()) + }) + .expect("unable to locate 'config.json' file"); + let mhf_config: MhfConfig = serde_json::from_str(&config_data).unwrap(); + let config = Config { + game_folder: cli_config.game_folder, + mhf_flags: vec![], + }; + mhf_iel::run(config, mhf_config).unwrap(); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/cmd.c b/src/cmd.c deleted file mode 100644 index 03d2e38..0000000 --- a/src/cmd.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "main.c" - -int main(int argc, char **argv) { - int FIXED_ARGS = 4; - if (argc < FIXED_ARGS) { - printf("usage: main.exe [loginmessages...]"); - return 0; - } - uint32_t char_id = atoi(argv[1]); - uint8_t is_new = atoi(argv[2]) > 0; - char *logintoken = argv[3]; - if (strlen(logintoken) != 16) { - fprintf(stderr, "error: logintoken must be 16 characters long"); - return 0; - } - - char **marr = malloc(8 * sizeof(char**)); - int mlen = 0; - if (argc > FIXED_ARGS) { - mlen = argc - FIXED_ARGS; - char **mptr = argv + FIXED_ARGS; - for (int i = 0; i < mlen && i < 8; i++) { - marr[i] = mptr[i]; - } - } - - uint32_t result = start(char_id, is_new, logintoken, marr, mlen); - printf("game closed with status %d", result); - free(marr); -} diff --git a/src/dll.c b/src/dll.c deleted file mode 100644 index 66ece22..0000000 --- a/src/dll.c +++ /dev/null @@ -1,8 +0,0 @@ -#define EXPORTING_DLL -#include "dll.h" - -#include "main.c" - -int DllMain(struct IElessData data) { - return start(data.char_id, data.is_new, data.logintoken, data.messages, data.message_size); -} diff --git a/src/dll.h b/src/dll.h deleted file mode 100644 index 9672c78..0000000 --- a/src/dll.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef IELESS_DLL_H -#define IELESS_DLL_H - -#include - -struct IElessData { - uint32_t char_id; - uint8_t is_new; - char *logintoken; - char **messages; - size_t message_size; -}; - -#ifdef EXPORTING_DLL -extern __declspec(dllexport) int DllMain(struct IElessData); -#else -extern __declspec(dllimport) int DllMain(struct IElessData); -#endif - -#endif diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..083b977 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,26 @@ +use std::fmt::Display; + +#[derive(Debug)] +pub enum Error { + GamePath, + Mutex, + DllNotFound, + ProcNotFound, + TokenLength, +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::GamePath => write!(f, "unable to find path to game"), + Self::Mutex => write!(f, "unable to create game mutexes"), + Self::DllNotFound => write!(f, "unable to find mhfo-hd.dll in the specified game path"), + Self::ProcNotFound => write!(f, "unable to find mhDLL_Main proc in mhfo-hd.dll"), + Self::TokenLength => write!(f, "user token must have a length of 16"), + } + } +} + +impl std::error::Error for Error {} + +pub type Result = std::result::Result; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8347cc0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,83 @@ +#![feature(generic_arg_infer)] +#![feature(new_uninit)] +mod error; +mod mhf; +mod utils; + +pub use error::Error; +pub use error::Result; + +use std::path::PathBuf; + +use serde::Deserialize; + +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum MhfFlags { + Selfup = 1, + Restat = 2, + Autolc = 3, + Hanres = 4, + DmmBoot = 5, + DmmSelfup = 6, + DmmAutolc = 7, + DmmReboot = 8, + Npge = 9, + NpMhfoTest = 10, +} + +#[repr(u32)] +#[derive(Deserialize, Debug, Copy, Clone)] +pub enum MezFesStalls { + TokotokoPartnya = 2, + Pachinko = 3, + VolpakkunTogether = 4, + Nyanrendo = 6, + HoneyPanic = 7, + DokkanBattleCats = 8, + PointStall = 9, + StallMap = 10, +} + +#[derive(Deserialize, Debug)] +pub struct MhfConfigMessage { + pub flags: u16, + pub message: String, +} + +#[derive(Deserialize, Debug)] +pub struct MhfConfig { + pub char_id: u32, + pub char_name: String, + pub char_gr: u32, + pub char_hr: u32, + pub char_ids: Vec, + pub char_new: bool, + pub user_token: String, + pub user_name: String, + pub user_password: String, + pub user_rights: u32, + pub entrance_count: u32, + pub current_ts: u32, + pub expiry_ts: u32, + pub messages: Vec, + pub mez_event_id: u32, + pub mez_start: u32, + pub mez_end: u32, + pub mez_solo_tickets: u32, + pub mez_group_tickets: u32, + pub mez_stalls: Vec, +} + +#[derive(Debug)] +pub struct Config { + pub game_folder: Option, + pub mhf_flags: Vec, +} + +pub fn run(config: Config, mhf_config: MhfConfig) -> Result { + if mhf_config.user_token.len() != 16 { + return Err(Error::TokenLength); + } + mhf::run_mhf(config, mhf_config) +} diff --git a/src/main.c b/src/main.c deleted file mode 100644 index d54cb73..0000000 --- a/src/main.c +++ /dev/null @@ -1,172 +0,0 @@ -#include "main.h" - -#define BUFSIZE 1024 - -const char *MHF_LABEL = "Monster Hunter Frontier Z"; - -static struct initdata INITDATA = { - .DOS_header_ptr = 0x400000, - .fixed448994_0x7 = 0x7, - .fixed448a64_0x0 = 0x0, - .fixed448ed0_0x1 = 0x1, - .fixed4491ac_0x10 = 0x10, - .fixed4491b8_0x10 = 0x10, -}; - -uint32_t gg_proc() { - // GameGuard function, 1 means everything's ok. - return 1; -} - -uint32_t check_0x4466cc_proc(int a) { - if (a != 0) { - // Checks 0x004466cc, if it's different than 0, does something - // From my tests it seems like it's always 0, so might be an edge case or something legacy - } - return 0; -} - -void get_current_path(char *buf, size_t size) { - uint32_t len = GetModuleFileNameA(NULL, buf, size); - int i; - for (i = len-1; i > 0; i--) { - if (buf[i] == '\\') - break; - } - if (i > 0) { - while (i < len) { - buf[i+1] = '\0'; - i++; - } - } -} - -void __cdecl get_mhf_name(char *buf, size_t size, char *name) { - sprintf_s(buf, size, "%s %s", MHF_LABEL, name); -} - -HANDLE __cdecl get_mutex(char *name) { - HANDLE hMutex = CreateMutexA((LPSECURITY_ATTRIBUTES)0x0, 0, name); - DWORD DVar1 = GetLastError(); - if (DVar1 != 0x0) { - ReleaseMutex(hMutex); - CloseHandle(hMutex); - return 0; - } - return hMutex; -} - -void init_global_alloc(char **messages, size_t messages_size) { - INITDATA.global_alloc = GlobalAlloc(0x42, 0x8ae0); - uint32_t *global_ptr = GlobalLock(INITDATA.global_alloc); - uint32_t *len_arr_ptr = global_ptr + 643; - uint32_t *body_arr_ptr = len_arr_ptr + 8; - for (int i = 0; i < messages_size; i++) { - size_t len = strlen(messages[i]); - len_arr_ptr[0] = len + 1; - len_arr_ptr++; - strncpy_s((char*)body_arr_ptr, 1024 * 4, messages[i], 1024 * 4); - body_arr_ptr += 1024; - } - GlobalUnlock(INITDATA.global_alloc); -} - -void init_init_config() { - INITDATA.PRESET_LEVEL = GetPrivateProfileIntA("SET", "PRESET_LEVEL", 0, INITDATA.ini_name); - INITDATA.CUSTOM = GetPrivateProfileIntA("SET", "CUSTOM", 1, INITDATA.ini_name); - INITDATA.FULLSCREEN_MODE = GetPrivateProfileIntA("SCREEN", "FULLSCREEN_MODE", 1, INITDATA.ini_name); - INITDATA.WINDOW_RESOLUTION_W = GetPrivateProfileIntA("SCREEN", "WINDOW_RESOLUTION_W", 1920, INITDATA.ini_name); - INITDATA.WINDOW_RESOLUTION_H = GetPrivateProfileIntA("SCREEN", "WINDOW_RESOLUTION_H", 1080, INITDATA.ini_name); - INITDATA.FULLSCREEN_RESOLUTION_W = GetPrivateProfileIntA("SCREEN", "FULLSCREEN_RESOLUTION_W", 1920, INITDATA.ini_name); - INITDATA.FULLSCREEN_RESOLUTION_H = GetPrivateProfileIntA("SCREEN", "FULLSCREEN_RESOLUTION_H", 1080, INITDATA.ini_name); - INITDATA.DISP_MAX_CHAR = GetPrivateProfileIntA("VIDEO", "DISP_MAX_CHAR", 100, INITDATA.ini_name); - INITDATA.TEXTURE_DXT_USE = GetPrivateProfileIntA("VIDEO", "TEXTURE_DXT_USE", 0, INITDATA.ini_name); - INITDATA.NOW_MONITOR_WH = GetPrivateProfileIntA("VIDEO", "NOW_MONITOR_WH", 0, INITDATA.ini_name); - INITDATA.GRAPHICS_VER = GetPrivateProfileIntA("VIDEO", "GRAPHICS_VER", 1, INITDATA.ini_name); - INITDATA.SOUND_NOTUSE = GetPrivateProfileIntA("SOUND", "SOUND_NOTUSE", 0, INITDATA.ini_name); - INITDATA.SOUND_VOLUME = GetPrivateProfileIntA("SOUND", "SOUND_VOLUME", 0, INITDATA.ini_name); - INITDATA.SOUND_VOLUME_INACTIVITY = GetPrivateProfileIntA("SOUND", "SOUND_VOLUME_INACTIVITY", 0, INITDATA.ini_name); - INITDATA.SOUND_VOLUME_MINIMIZE = GetPrivateProfileIntA("SOUND", "SOUND_VOLUME_MINIMIZE", 0, INITDATA.ini_name); - INITDATA.SOUND_FREQUENCY = GetPrivateProfileIntA("SOUND", "SOUND_FREQUENCY", 48000, INITDATA.ini_name); - INITDATA.SOUND_BUFFERNUM = GetPrivateProfileIntA("SOUND", "SOUND_BUFFERNUM", 2048, INITDATA.ini_name); - INITDATA.LANGUAGE = GetPrivateProfileIntA("LOCALIZATION", "LANGUAGE", 0, INITDATA.ini_name); - INITDATA.FONT_QUALITY = GetPrivateProfileIntA("FONT", "QUALITY", 4, INITDATA.ini_name); - INITDATA.FONT_WEIGHT = GetPrivateProfileIntA("FONT", "WEIGHT", 0x2bc, INITDATA.ini_name); - GetPrivateProfileStringA("FONT", "NAME", (uint8_t[]){0x4d, 0x53, 0x20, 0x3f, 0x3f, 0x3f, 0x3f}, INITDATA.FONT_NAME, 32 * sizeof(uint32_t), INITDATA.ini_name); - INITDATA.DRAWSKIP = GetPrivateProfileIntA("OPTION", "DRAWSKIP", 1, INITDATA.ini_name); - INITDATA.CLOGDIS = GetPrivateProfileIntA("OPTION", "CLOGDIS", 0, INITDATA.ini_name); - INITDATA.PROXY_USE = GetPrivateProfileIntA("LAUNCH", "PROXY_USE", 0, INITDATA.ini_name); - INITDATA.PROXY_IE = GetPrivateProfileIntA("LAUNCH", "PROXY_IE", 0, INITDATA.ini_name); - INITDATA.PROXY_SET = GetPrivateProfileIntA("LAUNCH", "PROXY_SET", 1, INITDATA.ini_name); - GetPrivateProfileStringA("LAUNCH", "PROXY_ADDR", "127.0.0.1", INITDATA.PROXY_ADDR, 16 * sizeof(uint32_t), INITDATA.ini_name); - INITDATA.PROXY_PORT = GetPrivateProfileIntA("LAUNCH", "PROXY_PORT", 8888, INITDATA.ini_name); - INITDATA.SERVER_SEL = GetPrivateProfileIntA("LAUNCH", "SERVER_SEL", 1, INITDATA.ini_name); -} - -int start(uint32_t char_id, uint8_t is_new, char *logintoken, char **messages, size_t messages_size) { - char buf[BUFSIZE]; - - strncpy(INITDATA.server_sign_token, logintoken, 16); - strcpy(INITDATA.ini_name, "mhf.ini"); - strcpy(INITDATA.server_sign_addr, "127.0.0.1:53310"); - strcpy(INITDATA.server_sign_host, "mhf-n.capcom.com.tw"); - strcpy(INITDATA.alt_ip_address, "203.191.249.36:8080"); - - INITDATA.server_sign_character_id_selected_1 = char_id; - INITDATA.server_sign_character_id_selected_2 = char_id; - INITDATA.server_sign_0xffffffff = 0xffffffff; - INITDATA.server_sign_current_ts = time(NULL); - INITDATA.server_sign_patch_count = 0x0; - INITDATA.server_sign_entrance_count = 0x1; - INITDATA.server_sign_character_status = is_new ? 2 : 0; - INITDATA.server_sign_exp_hr = 0x0; - INITDATA.server_sign_character_id_list[0] = char_id; - INITDATA.server_sign_expiry_ts = time(NULL) + (3600 * 24 * 7); - - get_current_path(buf, BUFSIZE); - SetCurrentDirectoryA(buf); - strncpy(INITDATA.path, buf, 1024); - strncpy(INITDATA.path2, buf, 1024); - get_mhf_name(INITDATA.ms_name, 64, "MHF_MASTER"); - get_mhf_name(INITDATA.msr_name, 64, "MHF_MASTER_READY"); - INITDATA.keyboard_layout = GetKeyboardLayout(0); - init_init_config(); - - INITDATA.initdata_ptr = &INITDATA; - INITDATA.inner1_ptr = &INITDATA.inner1_addr_0x0; - INITDATA.inner2_ptr = &INITDATA.inner2_addr_0x0; - INITDATA.inner3_ptr = &INITDATA.inner3_addr_0x0; - INITDATA.gg_proc_ptr = &gg_proc; - INITDATA.unk1_proc_ptr = &check_0x4466cc_proc; - INITDATA.unk2_proc_ptr = &check_0x4466cc_proc; - - INITDATA.unk448e74_0xe = 0xe; - INITDATA.unk4491e8_0x10000000 = 0x10000000; - - INITDATA.ms_mutex = get_mutex(INITDATA.ms_name); - if (!INITDATA.ms_mutex) { - fprintf(stderr, "failed to get '%s' mutex\n", INITDATA.ms_name); - return EXIT_FAILURE; - } - - init_global_alloc(messages, messages_size); - if (GetLastError()) { - fprintf(stderr, "failed to initialize global allocation\n"); - return EXIT_FAILURE; - } - - sprintf_s(buf, BUFSIZE, "%s%s", &INITDATA.path, "mhfo-hd.dll"); - HINSTANCE mhDLL = LoadLibraryA(buf); - if (!mhDLL) { - fprintf(stderr, "failed to load 'mhfo-hd.dll' dll: %d\n", GetLastError()); - return EXIT_FAILURE; - } - INITDATA.mhDLL_Main = GetProcAddress(mhDLL, "mhDLL_Main"); - if (!INITDATA.mhDLL_Main) { - fprintf(stderr, "failed to load 'mhDLL_Main' func: %d\n", GetLastError()); - return EXIT_FAILURE; - } - uint32_t result = INITDATA.mhDLL_Main(&INITDATA); - FreeLibrary(mhDLL); - return result; -} diff --git a/src/main.h b/src/main.h deleted file mode 100644 index b86a2d8..0000000 --- a/src/main.h +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -struct initdata { - uint32_t DOS_header_ptr; // 447178 - uint32_t _pad1[4]; // 44717c - - char path[256 * sizeof(uint32_t)]; // 44718c - char path2[256 * sizeof(uint32_t)]; // 44758c - char username[512 * sizeof(uint32_t)]; // 44798c - char password[512 * sizeof(uint32_t)]; // 44818c - - uint32_t _pad2[2]; // 44898c - uint32_t fixed448994_0x7; // 448994 - uint32_t _pad3[1]; // 448998 - HANDLE ms_mutex; // 44899c - uint32_t _pad4[1]; // 4489a0 -- check what this is - char ms_name[16 * sizeof(uint32_t)]; // 4489a4 - char ini_name[16 * sizeof(uint32_t)]; // 4489e4 - uint32_t (*unk1_proc_ptr)(uint); // 448a24 - uint32_t (*gg_proc_ptr)(); // 448a28 - uint32_t (*unk2_proc_ptr)(uint); // 448a2c - uint32_t _pad5[3]; // 448a30 - - // Sign response data - uint32_t server_sign_character_id_selected_1; // 448a3c - uint32_t server_sign_character_id_selected_2; // 448a40 - uint32_t server_sign_0xffffffff; // 448a44 - char server_sign_token[4 * sizeof(uint32_t)]; // 448a48 - uint32_t _pad6[2]; // 448a58 - uint32_t server_sign_current_ts; // 448a60 - uint32_t fixed448a64_0x0; // 448a64 - uint32_t _pad8[128]; // 448a68 - char server_sign_addr[96 * sizeof(uint32_t)]; // 448c68 - char server_sign_host[32 * sizeof(uint32_t)]; // 448d68 - uint32_t server_sign_patch_count; // 448e68 - uint32_t server_sign_entrance_count; // 448e6c - // 0 if existing, 2 if new. I can see from the disassembly it can also be 1, which the game seems to treat as 2, - // but that's based on a byte set on the individual character data that I couldn't find where to set. - uint32_t server_sign_character_status; // 448e70 - uint32_t unk448e74_0xe; // 448e74 -- something based on 'patch_count', if it differs from 0 this value gets zeroed - uint32_t server_sign_exp_hr; // 448e78 - char server_sign_character_name_sel[4 * sizeof(uint32_t)]; // 448e7c - uint32_t server_sign_character_id_list[16]; // 448e8c - - HGLOBAL global_alloc; // 448ecc - uint32_t fixed448ed0_0x1; // 448ed0 - uint32_t unk448ed4; // 448ed4 - uint32_t unk448ed8; // 448ed8 - uint32_t _pad448edc[2]; //448edc - - // Init config - uint32_t PRESET_LEVEL; // 448ee4 - uint32_t CUSTOM; // 448ee8 - uint32_t FULLSCREEN_MODE; // 448eec - uint32_t WINDOW_RESOLUTION_W; // 448ef0 - uint32_t WINDOW_RESOLUTION_H; // 448ef4 - uint32_t FULLSCREEN_RESOLUTION_W; // 448ef8 - uint32_t FULLSCREEN_RESOLUTION_H; // 448efc - uint32_t DISP_MAX_CHAR; // 448f00 - uint32_t TEXTURE_DXT_USE; // 448f04 - uint32_t NOW_MONITOR_WH; // 448f08 - uint32_t GRAPHICS_VER; // 448f0c - uint32_t SOUND_NOTUSE; // 448f10 - uint32_t SOUND_VOLUME; // 448f14 - uint32_t SOUND_VOLUME_INACTIVITY; // 448f18 - uint32_t SOUND_VOLUME_MINIMIZE; // 448f1c - uint32_t SOUND_FREQUENCY; // 448f20 - uint32_t SOUND_BUFFERNUM; // 448f24 - uint32_t LANGUAGE; // 448f28 -- 0x0 - uint32_t FONT_QUALITY; // 448f2c -- 0x4 - uint32_t FONT_WEIGHT; // 448f30 -- 0x2bc - char FONT_NAME[26 * sizeof(uint32_t)]; // 448f34 -- [0x3f20534d, 0x3f3f3f, 0x3f3f3f] is the default - uint32_t DRAWSKIP; // 448f9c -- 0x1 - uint32_t CLOGDIS; // 448fa0 -- 0x0 - uint32_t PROXY_USE; // 448fa4 - uint32_t PROXY_IE; // 448fa8 - uint32_t PROXY_SET; // 448fac - char PROXY_ADDR[16 * sizeof(uint32_t)]; // 448fb0 - uint32_t PROXY_PORT; // 448ff0 - uint32_t SERVER_SEL; // 448ff4 - - void *inner1_ptr; // 448ff8 - uint32_t unk15[32]; // 448ffc - char alt_ip_address[64 * sizeof(uint32_t)]; // 44907c - uint32_t server_sign_expiry_ts; // 44917c - uint32_t unk16; // 449180 - uint32_t unk17_0x1; // 449184 - uint32_t unk18[2]; // 449188 - void *initdata_ptr; // 449190 -- 0x447178 - HKL keyboard_layout; // 449194 - uint32_t inner3_addr_0x0[4]; // 449198 - uint32_t inner1_addr_0x0; // 4491a8 - uint32_t fixed4491ac_0x10; // 4491ac - void *inner2_ptr; // 4491b0 -- 0x4491d4 - uint32_t unk24_0x0; // 4491b4 - uint32_t fixed4491b8_0x10; // 4491b8 - void *inner3_ptr; // 4491bc - uint32_t proc4_ptr; // 4491c0 - uint32_t unk28_0x0; // 4491c4 - uint32_t proc5_ptr; // 4491c8 - uint32_t unk30[2]; // 4491cc - uint32_t inner2_addr_0x0; // 4491d4 - uint32_t unk32[4]; // 4491d8 - uint32_t unk4491e8_0x10000000; // 4491e8 - uint32_t unk34_0x108c0000; // 4491ec -- can be diff - uint32_t unk4491f0[338]; // 4491f0 - char msr_name[64 * sizeof(uint32_t)]; - uint32_t unk449810[261]; // 449810 - FARPROC mhDLL_Main; // 449c24 - uint32_t unk449c28; // 449c28 - uint32_t unk449c2c[4]; // 449c2c - uint32_t unk449c3c; // 449c3c -}; diff --git a/src/mhf.rs b/src/mhf.rs new file mode 100644 index 0000000..312eeb4 --- /dev/null +++ b/src/mhf.rs @@ -0,0 +1,387 @@ +use crate::utils::bufcopy; +use crate::{utils, Error, MhfConfig, MhfFlags, Result}; + +use windows::core::s; +use windows::Win32::Foundation::{FARPROC, HANDLE, HGLOBAL, HMODULE}; +use windows::Win32::System::LibraryLoader::GetModuleHandleA; +use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; +use windows::Win32::System::Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GLOBAL_ALLOC_FLAGS}; +use windows::Win32::System::WindowsProgramming::{GetPrivateProfileIntA, GetPrivateProfileStringA}; +use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyboardLayout; +use windows::Win32::UI::TextServices::HKL; + +extern "C" fn mock_proc(_v: u32) -> u32 { + // TODO: investigate individual procs + 0 +} + +extern "C" fn gg_proc() -> u32 { + // TODO: I'm pretty sure this isn't called anymore in the fixed version, check + // let mut x: u32 = 0; + // unsafe { + // std::arch::asm!( + // "mov ebx, eax", + // out("ebx") x, + // ); + // } + // let data = unsafe { &mut *DATA.get() }.0; + 1 +} + +#[derive(Debug)] +#[repr(C)] +struct Data { + main_module: HMODULE, // 447178 + _pad_44717c: [u8; 0x8], // 44717c + cmd_flags_1: u32, // 447184 // set based on CLI flags + cmd_flags_2: u32, // 447188 // set based on CLI flags + + path1: [u8; 0x400], // 44718c + path2: [u8; 0x400], // 44758c + user_name: [u8; 0x800], // 44798c + user_password: [u8; 0x800], // 44818c + + cmd_number: u32, // 44898c // CLI argument stuff + cmd_netfcup: u32, // 448990 // CLI '-NETFCUP' + cmd_dmm: u32, // 448994 // set whenever a /DMM_ flag is specified + _pad_448998: [u8; 0x4], // 448998 // set when mutex_master is already set? + mutex_master: HANDLE, // 44899c + mutex_master_ready: HANDLE, // 4489a0 + mutex_master_name: [u8; 0x40], // 4489a4 + ini_file: [u8; 0x40], // 4489e4 + proc_1: usize, // 448a24 // copied from 40187a + proc_2: usize, // 448a28 // copied from 401868, probably gg_proc + proc_3: usize, // 448a2c // copied from 40188b + _pad_448a30: [u8; 0xc], // 448a30 + + // Server data + selected_char_id_1: u32, // 448a3c + selected_char_id_2: u32, // 448a40 + char_ids_count: u32, // 448a44 + user_token: [u8; 0x10], // 448a48 + _pad_448a58: [u8; 0x8], // 448a58 + server_current_ts: u32, // 448a60 + fixed_448a64_0x0: u32, // 448a64 + _pad_448a68: [u8; 0x200], // 448a68 + remote_addr: [u8; 0x100], // 448c68 + remote_host: [u8; 0x100], // 448d68 + remote_patch_count: u32, // 448e68 + server_entrance_count: u32, // 448e6c + // 0 if existing, 2 if new. I can see from the disassembly it can also be 1, which the game seems to treat as 2, + // but that's based on a byte set on the individual character data that I couldn't find where to set. + selected_char_status: u32, // 448e70 + user_rights: u32, // 448e74 + selected_char_hr: u32, // 448e78 + selected_char_name: [u8; 0x10], // 448e7c + char_ids: [u32; 0x10], // 448e8c + + global_alloc: HGLOBAL, // 448ecc + fixed_448ed0_0x1: u32, // 448ed0 + unk_448ed4: u32, // 448ed4 + selected_char_gr: u32, // 448ed8 // can be value before 'bool + name' if bool is false + _pad_448edc: [u8; 0x8], //448edc + + // Config + preset_level: u32, // 448ee4 + custom: u32, // 448ee8 + fullscreen_mode: u32, // 448eec + window_resolution_w: u32, // 448ef0 + window_resolution_h: u32, // 448ef4 + fullscreen_resolution_w: u32, // 448ef8 + fullscreen_resolution_h: u32, // 448efc + disp_max_char: u32, // 448f00 + texture_dxt_use: u32, // 448f04 + now_monitor_wh: u32, // 448f08 + graphics_ver: u32, // 448f0c + sound_notuse: u32, // 448f10 + sound_volume: u32, // 448f14 + sound_volume_inactivity: u32, // 448f18 + sound_volume_minimize: u32, // 448f1c + sound_frequency: u32, // 448f20 + sound_buffernum: u32, // 448f24 + language: u32, // 448f28 -- 0x0 + font_quality: u32, // 448f2c -- 0x4 + font_weight: u32, // 448f30 -- 0x2bc + font_name: [u8; 0x68], // 448f34 -- [0x3f20534d, 0x3f3f3f, 0x3f3f3f] is the default + drawskip: u32, // 448f9c -- 0x1 + clogdis: u32, // 448fa0 -- 0x0 + proxy_use: u32, // 448fa4 + proxy_ie: u32, // 448fa8 + proxy_set: u32, // 448fac + proxy_addr: [u8; 0x40], // 448fb0 + proxy_port: u32, // 448ff0 + server_sel: u32, // 448ff4 + + inner_ptr_1_4491a8: usize, // 448ff8 + _pad_448ffc: [u8; 0x40], // 448ffc + _pad_44903c: [u8; 0x40], // 44903c // the 'alt_ip_address' load happens here, with 0x100 width + alt_ip_address: [u8; 0xC0], // 44907c + _pad_44913c: [u8; 0x40], // 44913c + server_expiry_ts: u32, // 44917c + remote_16e: u32, // 449180 + fixed_449184_0x1: u32, // 449184 // 2 if 100812B0 == 9 + _pad_449188: [u8; 0x8], // 449188 + data_ptr: usize, // 449190 + keyboard_layout: HKL, // 449194 + inner_3: (), // 449198 + _pad_449198: [u8; 0x10], // 449198 + inner_1: (), // 4491a8 + _pad_4491a8: [u8; 0x4], // 4491a8 + fixed_4491ac_0x10: u32, // 4491ac + inner_ptr_2_4491d4: usize, // 4491b0 + _pad_4491b4: [u8; 4], // 4491b4 + fixed_4491b8_0x10: u32, // 4491b8 + inner_ptr_3_449198: usize, // 4491bc + proc_4: usize, // 4491c0 // fixed 40605e + _pad_4491c4: [u8; 0x4], // 4491c4 + proc_5: usize, // 4491c8 // fixed 40609c + _pad_4491cc: [u8; 0x8], // 4491cc + inner_2: (), // 4491d4 + _pad_4491d4: [u8; 0x14], // 4491d4 + mhfo_module: HMODULE, // 4491e8 + _pad_4491ec: [u8; 0x4], // 4491ec + _pad_4491f0: [u8; 0x520], // 4491f0 + mutex_master_ready_name: [u8; 0x100], // 449710 + _pad_449810: [u8; 0x414], // 449810 + mhddl_main: FARPROC, // 449c24 +} // 449188 + +#[repr(C)] +struct GlobalData { + _pad_0x0000: [u8; 0xa00], // 0000 + _pad_0x0a00: [u8; 0xc], // 0a00 + messages_count: [u32; 0x4], // 0a0c + _pad_0x0a10: [u8; 0x8], // 0a1c + messages_flags: [u16; 0x4], // 0a24 + messages: [[u8; 0x1000]; 0x4], // 0a2c + _filter: [u8; 0x3000], // 4a2c + _pad_0x4a2c: [u8; 0x1080], // 7a2c + mez_event_id: u32, // 8aac + mez_start: u32, // 8ab0 + mez_end: u32, // 8ab4 + mez_solo_tickets: u32, // 8ab8 + mez_group_tickets: u32, // 8abc + mez_stalls: [u32; 0x8], // 8ac0 +} + +// TODO: this might be needed in the future +// struct DataStatic(*const Data); +// unsafe impl Sync for DataStatic {} +// static DATA: SyncUnsafeCell = SyncUnsafeCell::new(DataStatic(0 as *const Data)); + +impl Data { + fn init_global_alloc(&mut self, mhf_config: &MhfConfig) { + self.global_alloc = unsafe { GlobalAlloc(GLOBAL_ALLOC_FLAGS(0x42), 0x8ae0) }.unwrap(); + let global_ptr = unsafe { GlobalLock(self.global_alloc) }; + unsafe { global_ptr.write_bytes(0, 0x8ae0) }; + { + let global_data = unsafe { &mut *(global_ptr as *mut GlobalData) }; + for (i, message) in mhf_config.messages.iter().enumerate() { + global_data.messages_count[i] = message.message.len() as u32; + global_data.messages_flags[i] = message.flags; + bufcopy(&mut global_data.messages[i], message.message.as_bytes()); + } + global_data.mez_event_id = mhf_config.mez_event_id; + global_data.mez_start = mhf_config.mez_start; + global_data.mez_end = mhf_config.mez_end; + global_data.mez_solo_tickets = mhf_config.mez_solo_tickets; + global_data.mez_group_tickets = mhf_config.mez_group_tickets; + for (i, stall) in mhf_config.mez_stalls.iter().enumerate() { + global_data.mez_stalls[i] = *stall as u32; + } + } + unsafe { GlobalUnlock(self.global_alloc) } + .or_else(|e| match e.code().0 { + 0 => Ok(()), + _ => Err(e), + }) + .unwrap(); + } + + fn init_cli(&mut self, mhf_flags: &[MhfFlags]) { + for flag in mhf_flags { + match flag { + MhfFlags::Selfup => self.cmd_flags_1 = 1, + MhfFlags::Restat => self.cmd_flags_1 = 2, + MhfFlags::Autolc => self.cmd_flags_1 = 3, + MhfFlags::Hanres => self.cmd_flags_1 = 4, + MhfFlags::DmmBoot => { + self.cmd_flags_1 = 5; + self.cmd_dmm = 1; + } + MhfFlags::DmmSelfup => { + self.cmd_flags_1 = 6; + self.cmd_dmm = 1; + } + MhfFlags::DmmAutolc => { + self.cmd_flags_1 = 7; + self.cmd_dmm = 1; + } + MhfFlags::DmmReboot => { + self.cmd_flags_1 = 8; + self.cmd_dmm = 1; + } + MhfFlags::Npge => { + self.cmd_flags_1 = 9; + self.cmd_flags_2 |= 6; + } + MhfFlags::NpMhfoTest => self.cmd_flags_2 |= 4, + } + } + } + + fn init_config(&mut self) { + let ini_file = s!("mhf.ini"); + unsafe { + self.preset_level = GetPrivateProfileIntA(s!("SET"), s!("PRESET_LEVEL"), 0, ini_file); + self.custom = GetPrivateProfileIntA(s!("SET"), s!("CUSTOM"), 1, ini_file); + self.fullscreen_mode = + GetPrivateProfileIntA(s!("SCREEN"), s!("FULLSCREEN_MODE"), 1, ini_file); + self.window_resolution_w = + GetPrivateProfileIntA(s!("SCREEN"), s!("WINDOW_RESOLUTION_W"), 1920, ini_file); + self.window_resolution_h = + GetPrivateProfileIntA(s!("SCREEN"), s!("WINDOW_RESOLUTION_H"), 1080, ini_file); + self.fullscreen_resolution_w = + GetPrivateProfileIntA(s!("SCREEN"), s!("FULLSCREEN_RESOLUTION_W"), 1920, ini_file); + self.fullscreen_resolution_h = + GetPrivateProfileIntA(s!("SCREEN"), s!("FULLSCREEN_RESOLUTION_H"), 1080, ini_file); + self.disp_max_char = + GetPrivateProfileIntA(s!("VIDEO"), s!("DISP_MAX_CHAR"), 100, ini_file); + self.texture_dxt_use = + GetPrivateProfileIntA(s!("VIDEO"), s!("TEXTURE_DXT_USE"), 0, ini_file); + self.now_monitor_wh = + GetPrivateProfileIntA(s!("VIDEO"), s!("NOW_MONITOR_WH"), 0, ini_file); + self.graphics_ver = GetPrivateProfileIntA(s!("VIDEO"), s!("GRAPHICS_VER"), 1, ini_file); + self.sound_notuse = GetPrivateProfileIntA(s!("SOUND"), s!("SOUND_NOTUSE"), 0, ini_file); + self.sound_volume = GetPrivateProfileIntA(s!("SOUND"), s!("SOUND_VOLUME"), 0, ini_file); + self.sound_volume_inactivity = + GetPrivateProfileIntA(s!("SOUND"), s!("SOUND_VOLUME_INACTIVITY"), 0, ini_file); + self.sound_volume_minimize = + GetPrivateProfileIntA(s!("SOUND"), s!("SOUND_VOLUME_MINIMIZE"), 0, ini_file); + self.sound_frequency = + GetPrivateProfileIntA(s!("SOUND"), s!("SOUND_FREQUENCY"), 48000, ini_file); + self.sound_buffernum = + GetPrivateProfileIntA(s!("SOUND"), s!("SOUND_BUFFERNUM"), 2048, ini_file); + self.language = GetPrivateProfileIntA(s!("LOCALIZATION"), s!("LANGUAGE"), 0, ini_file); + self.font_quality = GetPrivateProfileIntA(s!("FONT"), s!("QUALITY"), 4, ini_file); + self.font_weight = GetPrivateProfileIntA(s!("FONT"), s!("WEIGHT"), 0x2bc, ini_file); + GetPrivateProfileStringA( + s!("FONT"), + s!("NAME"), + s!("MS ????"), + Some(&mut self.font_name), + ini_file, + ); + self.drawskip = GetPrivateProfileIntA(s!("OPTION"), s!("DRAWSKIP"), 1, ini_file); + self.clogdis = GetPrivateProfileIntA(s!("OPTION"), s!("CLOGDIS"), 0, ini_file); + self.proxy_use = GetPrivateProfileIntA(s!("LAUNCH"), s!("PROXY_USE"), 0, ini_file); + self.proxy_ie = GetPrivateProfileIntA(s!("LAUNCH"), s!("PROXY_IE"), 0, ini_file); + self.proxy_set = GetPrivateProfileIntA(s!("LAUNCH"), s!("PROXY_SET"), 1, ini_file); + GetPrivateProfileStringA( + s!("LAUNCH"), + s!("PROXY_ADDR"), + s!("127.0.0.1"), + Some(&mut self.proxy_addr), + ini_file, + ); + self.proxy_port = GetPrivateProfileIntA(s!("LAUNCH"), s!("PROXY_PORT"), 8888, ini_file); + self.server_sel = GetPrivateProfileIntA(s!("LAUNCH"), s!("SERVER_SEL"), 1, ini_file); + } + } +} + +pub fn run_mhf(config: crate::Config, mhf_config: crate::MhfConfig) -> Result { + let game_folder = config + .game_folder + .or_else(|| std::env::current_dir().ok()) + .ok_or(Error::GamePath)?; + std::env::set_current_dir(&game_folder).or(Err(Error::GamePath))?; + let mut game_folder_name = game_folder.to_str().ok_or(Error::GamePath)?.to_owned(); + if !game_folder_name.ends_with(&['/', '\\']) { + game_folder_name.push('/'); + } + + // Init + let main_module = unsafe { GetModuleHandleA(None).unwrap() }; + let keyboard_layout = unsafe { GetKeyboardLayout(0) }; + let mutex_master_name = utils::get_mutex_name("MHF_MASTER"); + let mutex_master = utils::create_mutex(&mutex_master_name)?; + let mutex_master_ready_name = utils::get_mutex_name("MHF_MASTER_READY"); + let mutex_master_ready = utils::create_mutex(&mutex_master_ready_name)?; + + let data = Box::::new_zeroed(); + let mut data = unsafe { data.assume_init() }; + data.main_module = main_module; + data.mutex_master = mutex_master; + data.mutex_master_ready = mutex_master_ready; + data.keyboard_layout = keyboard_layout; + data.fixed_448a64_0x0 = 0x0; + data.fixed_448ed0_0x1 = 0x1; + data.fixed_449184_0x1 = 0x1; + data.fixed_4491ac_0x10 = 0x10; + data.fixed_4491b8_0x10 = 0x10; + data.proc_1 = mock_proc as usize; + data.proc_2 = gg_proc as usize; + data.proc_3 = mock_proc as usize; + data.proc_4 = mock_proc as usize; + data.proc_5 = mock_proc as usize; + + data.init_config(); + data.init_global_alloc(&mhf_config); + data.init_cli(&config.mhf_flags); + + // Char + data.selected_char_id_1 = mhf_config.char_id; + data.selected_char_id_2 = mhf_config.char_id; + bufcopy( + &mut data.selected_char_name, + mhf_config.char_name.as_bytes(), + ); + data.selected_char_hr = mhf_config.char_hr; + data.selected_char_gr = mhf_config.char_gr; + data.selected_char_status = if mhf_config.char_new { 2 } else { 0 }; + data.char_ids_count = mhf_config.char_ids.len() as u32; + bufcopy(&mut data.char_ids, &mhf_config.char_ids); + + // User + bufcopy(&mut data.user_name, mhf_config.user_name.as_bytes()); + bufcopy(&mut data.user_password, mhf_config.user_password.as_bytes()); + bufcopy(&mut data.user_token, mhf_config.user_token.as_bytes()); + data.user_rights = mhf_config.user_rights; + + // Server + data.server_entrance_count = mhf_config.entrance_count; + data.server_current_ts = mhf_config.current_ts; + data.server_expiry_ts = mhf_config.expiry_ts; + + // Meta + bufcopy(&mut data.mutex_master_name, mutex_master_name.as_bytes()); + bufcopy( + &mut data.mutex_master_ready_name, + mutex_master_ready_name.as_bytes(), + ); + bufcopy(&mut data.path1, game_folder_name.as_bytes()); + bufcopy(&mut data.path2, game_folder_name.as_bytes()); + bufcopy(&mut data.ini_file, b"mhf.ini"); + bufcopy(&mut data.remote_addr, b"127.0.0.1:53310"); + bufcopy(&mut data.remote_host, b"mhf-n.capcom.com.tw"); + bufcopy(&mut data.alt_ip_address, b"203.191.249.36:8080"); + + // Dll + data.mhfo_module = unsafe { LoadLibraryA(s!("mhfo-hd.dll")) }.or(Err(Error::DllNotFound))?; + data.mhddl_main = unsafe { GetProcAddress(data.mhfo_module, s!("mhDLL_Main")) }; + let proc = data.mhddl_main.ok_or(Error::ProcNotFound)?; + // I'm pretty sure this should be "stdcall", but that causes the caller to 'sub esp, 4' without pushing + // Investigate more + let proc: unsafe extern "C" fn(*const Data) -> isize = unsafe { std::mem::transmute(proc) }; + + // Pointers + data.data_ptr = Box::as_ref(&data) as *const Data as usize; + data.inner_ptr_1_4491a8 = &data.inner_1 as *const _ as usize; + data.inner_ptr_2_4491d4 = &data.inner_2 as *const _ as usize; + data.inner_ptr_3_449198 = &data.inner_3 as *const _ as usize; + + let result = unsafe { proc(Box::into_raw(data)) }; + + Ok(result) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..8d28bb1 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,23 @@ +use windows::{ + core::HSTRING, + Win32::{Foundation::HANDLE, System::Threading::CreateMutexW}, +}; + +use crate::{Error, Result}; + +pub fn bufcopy(s: &mut [T], v: &[T]) { + s[..v.len()].copy_from_slice(v) +} + +pub fn get_mutex_name(s: &str) -> String { + format!("Monster Hunter Frontier Z {s}") +} + +pub fn create_mutex(name: impl Into) -> Result { + unsafe { CreateMutexW(None, false, &name.into()) }.or(Err(Error::Mutex)) +} + +// fn get_mutex(name: impl Into) -> Result { +// unsafe { OpenMutexW(SYNCHRONIZATION_ACCESS_RIGHTS(0x1F0001), false, &name.into()) } +// .or(Err(Error::Mutex)) +// }