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))
+// }