├── .github └── workflows │ ├── release.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── README.md └── src └── main.rs /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/release.yml 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | release: 9 | name: release ${{ matrix.target }} 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | include: 15 | - target: x86_64-pc-windows-gnu 16 | archive: zip 17 | - target: x86_64-unknown-linux-musl 18 | archive: tar.gz 19 | - target: x86_64-apple-darwin 20 | archive: zip 21 | steps: 22 | - uses: actions/checkout@master 23 | - name: Compile and release 24 | uses: rust-build/rust-build.action@v1.3.2 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | TOOLCHAIN_VERSION: 1.62.0 28 | with: 29 | RUSTTARGET: ${{ matrix.target }} 30 | ARCHIVE_TYPES: ${{ matrix.archive }} 31 | EXTRA_FILES: "README.md LICENSE.txt" 32 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "1.3.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 27 | 28 | [[package]] 29 | name = "clap" 30 | version = "3.2.8" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" 33 | dependencies = [ 34 | "atty", 35 | "bitflags", 36 | "clap_derive", 37 | "clap_lex", 38 | "indexmap", 39 | "once_cell", 40 | "strsim", 41 | "termcolor", 42 | "textwrap", 43 | ] 44 | 45 | [[package]] 46 | name = "clap_derive" 47 | version = "3.2.7" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" 50 | dependencies = [ 51 | "heck", 52 | "proc-macro-error", 53 | "proc-macro2", 54 | "quote", 55 | "syn", 56 | ] 57 | 58 | [[package]] 59 | name = "clap_lex" 60 | version = "0.2.4" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 63 | dependencies = [ 64 | "os_str_bytes", 65 | ] 66 | 67 | [[package]] 68 | name = "hashbrown" 69 | version = "0.12.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" 72 | 73 | [[package]] 74 | name = "heck" 75 | version = "0.4.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 78 | 79 | [[package]] 80 | name = "hermit-abi" 81 | version = "0.1.19" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 84 | dependencies = [ 85 | "libc", 86 | ] 87 | 88 | [[package]] 89 | name = "indexmap" 90 | version = "1.9.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 93 | dependencies = [ 94 | "autocfg", 95 | "hashbrown", 96 | ] 97 | 98 | [[package]] 99 | name = "libc" 100 | version = "0.2.126" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 103 | 104 | [[package]] 105 | name = "once_cell" 106 | version = "1.13.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 109 | 110 | [[package]] 111 | name = "os_str_bytes" 112 | version = "6.1.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" 115 | 116 | [[package]] 117 | name = "proc-macro-error" 118 | version = "1.0.4" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 121 | dependencies = [ 122 | "proc-macro-error-attr", 123 | "proc-macro2", 124 | "quote", 125 | "syn", 126 | "version_check", 127 | ] 128 | 129 | [[package]] 130 | name = "proc-macro-error-attr" 131 | version = "1.0.4" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 134 | dependencies = [ 135 | "proc-macro2", 136 | "quote", 137 | "version_check", 138 | ] 139 | 140 | [[package]] 141 | name = "proc-macro2" 142 | version = "1.0.40" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 145 | dependencies = [ 146 | "unicode-ident", 147 | ] 148 | 149 | [[package]] 150 | name = "quote" 151 | version = "1.0.20" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 154 | dependencies = [ 155 | "proc-macro2", 156 | ] 157 | 158 | [[package]] 159 | name = "strsim" 160 | version = "0.10.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 163 | 164 | [[package]] 165 | name = "syn" 166 | version = "1.0.98" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 169 | dependencies = [ 170 | "proc-macro2", 171 | "quote", 172 | "unicode-ident", 173 | ] 174 | 175 | [[package]] 176 | name = "termcolor" 177 | version = "1.1.3" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 180 | dependencies = [ 181 | "winapi-util", 182 | ] 183 | 184 | [[package]] 185 | name = "textwrap" 186 | version = "0.15.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 189 | 190 | [[package]] 191 | name = "unicode-ident" 192 | version = "1.0.1" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 195 | 196 | [[package]] 197 | name = "vanilla-tweaks" 198 | version = "1.6.0" 199 | dependencies = [ 200 | "clap", 201 | ] 202 | 203 | [[package]] 204 | name = "version_check" 205 | version = "0.9.4" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 208 | 209 | [[package]] 210 | name = "winapi" 211 | version = "0.3.9" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 214 | dependencies = [ 215 | "winapi-i686-pc-windows-gnu", 216 | "winapi-x86_64-pc-windows-gnu", 217 | ] 218 | 219 | [[package]] 220 | name = "winapi-i686-pc-windows-gnu" 221 | version = "0.4.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 224 | 225 | [[package]] 226 | name = "winapi-util" 227 | version = "0.1.5" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 230 | dependencies = [ 231 | "winapi", 232 | ] 233 | 234 | [[package]] 235 | name = "winapi-x86_64-pc-windows-gnu" 236 | version = "0.4.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 239 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vanilla-tweaks" 3 | version = "1.6.0" 4 | edition = "2021" 5 | authors = ["burneddi "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | clap = { version = "3.2.8", features = ["derive"] } 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 brndd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build and tests](https://github.com/brndd/vanilla-tweaks/actions/workflows/rust.yml/badge.svg) 2 | 3 | # vanilla-tweaks 4 | 5 | These are some custom patches for the old 1.12.1 World of Warcraft client, which lacks many of the conveniences of more modern clients. 6 | 7 | ## Current patches 8 | 9 | - **Widescreen FoV fix** 10 | - **Sound in background patch** 11 | - **Sound channel count default increase** 12 | - By default the game only uses 12 channels to play sound. This is essentially the number of sounds that can play at the same time, so the low default value means many sound effects in group content do not play. 13 | - Increased to 64 by default. The default in TBC is 32. The default in newer expansions is 64. 14 | - This can also be set with `/console SoundSoftwareChannels 64`, but this patcher changes the default value so that it survives Config.wtf deletions. 15 | - Values above 64 have been reported to cause crashes. If you run into performance issues, try decreasing this setting further. 16 | - **Farclip (max render distance) increase** 17 | - Farclip is changed with `/console farclip 1000` (777 is the default maximum) 18 | - This patch allows up to 10000, but this may cause crashes. Enabling the Large Address Aware patch (enabled by default) may help reduce crashing. 19 | - **Frilldistance (max grass render distance) increase** 20 | - You may want to customize the value used here if you use a very high frilldensity in order to maintain performance. The default (300) works fine on my machine with a relatively low frilldensity (64), but causes FPS to drop below 144 with high frilldensity. 21 | - Frill density (grass density) is changed with `/console frilldensity 100`. 256 is the max value (unchanged by patcher as it is already very dense). 22 | - **Quickloot reverse patch (hold shift to manual loot)** 23 | - This should work reliably for all types of looting. Please make an issue if it doesn't (e.g. if it occasionally fails to loot). 24 | - **Nameplate range change** 25 | - Increased to 41 yards to match the maximum value in Classic and TBC Classic. 20 yards is the default value. 26 | - **CameraDistanceMax limit increase** 27 | - Allows you to increase the CameraDistanceMax limit. This only changes the max value settable; the actual max camera distance can be changed with /console CameraDistanceMax . 28 | - Unchanged by default. Default maximum value is 50. 29 | - **Large Address Aware patch** 30 | - This allows the game to use more than 2GB of memory by setting a flag in the executable header. See https://codekabinett.com/rdumps.php?Lang=2&targetDoc=largeaddressaware-msaccess-exe for more information. 31 | - If you experience inexplicable crashes, try disabling this patch, and if you manage to reproduce them let me know via an issue. The client *should* have no issues being Large Address Aware, but you never know. 32 | - **Camera skip glitch fix** 33 | - Fixes the glitch where the camera sometimes skips to face a random direction when rotated. 34 | 35 | ## Usage 36 | 37 | Download the release matching your operating system from the [releases page](https://github.com/brndd/vanilla-tweaks/releases). Extract the executable from the archive into your WoW directory. 38 | 39 | ### Simple usage (Windows) 40 | 41 | Open your WoW directory and drag WoW.exe on top of vanilla_tweaks.exe. This will create a new file called WoW_tweaked.exe, which has all the patches applied with their default settings. You should then start the game from WoW_tweaked.exe instead. You may also rename your original WoW.exe to something else and then rename WoW_tweaked.exe to WoW.exe if you prefer. However, do note that this may cause issues if the server you are playing on uses the game's update system to update the game. 42 | 43 | ### Customizing the settings 44 | 45 | To customize the values changed by the patches, or to disable some patches, you must run vanilla-tweaks from the command line. 46 | 47 | First, open a command prompt and navigate to your WoW directory. The easiest way to do this on Windows is to click File -> Open Windows PowerShell. On Mac, you can control-click the folder in the path bar and select Open in Terminal. On Linux, you can probably right-click on an empty space in the directory and open a terminal from there, but as a Linux user you probably know how to use the `cd` command anyway. 48 | 49 | With your terminal open in your WoW directory, you may then run vanilla-tweaks with custom parameters like this: 50 | 51 | ```sh 52 | ./vanilla-tweaks --no-sound-in-background --nameplatedistance 60 WoW.exe 53 | ``` 54 | 55 | The example here disables the sound in background patch and sets nameplate distance to 60 feet rather than 40. 56 | 57 | To see a full list of the available options, you may use the `--help` parameter: 58 | 59 | ```sh 60 | ./vanilla-tweaks --help 61 | ``` 62 | 63 | ## Launch scripts 64 | 65 | (Pull requests to add scripts for other platforms here are welcome!) 66 | 67 | ### Linux/Lutris 68 | 69 | Here is an example Lutris launch script that clears the game's cache folder and regenerates the patched executable if WoW.exe has changed since the last time the patches were applied (e.g. the server shipped an update to the game files). 70 | 71 | Make sure to modify the game path to match your installation. You can then enable the script by setting it in Lutris via Configure > System Options > Pre-launch script (make sure "Wait for pre-launch script completion" is active). 72 | 73 | ```bash 74 | #!/bin/bash 75 | 76 | cd /media/ssd0/games/turtle-wow/drive_c/turtle_client_116/ 77 | #Clear cache 78 | rm -f /media/ssd0/games/turtle-wow/drive_c/turtle_client_116/WDB/* 79 | 80 | #Check hash of WoW.exe to see if it has changed 81 | if ! sha256sum --status --check WoW.exe.sha256; then 82 | echo "WoW.exe has changed, updating WoW.exe.sha256 and WoW_tweaked.exe" 83 | sha256sum WoW.exe > WoW.exe.sha256 84 | ./vanilla-tweaks WoW.exe 85 | fi 86 | ``` 87 | 88 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //Patches some QoL stuff into the old 1.12.1 WoW client. 2 | 3 | //Files can be verified after patching with: 4 | //cmp -l WoW_patched.exe WoW.exe | gawk '{printf "%08X %02X %02X\n", $1, strtonum(0$2), strtonum(0$3)}' 5 | 6 | use std::fs; 7 | use std::process::ExitCode; 8 | use std::ffi::OsString; 9 | use std::ffi::CString; 10 | use clap::Parser; 11 | 12 | // Command line arguments 13 | #[derive(Parser, Debug)] 14 | #[clap(author)] 15 | #[clap(version)] 16 | #[clap(about = "Applies patches to enhance the functionality of the 1.12.1 World of Warcraft client")] 17 | #[clap(long_about = "Applies patches to enhance the functionality of the 1.12.1 World of Warcraft client. 18 | 19 | The following patches are currently enabled by default: 20 | - Widescreen FoV fix 21 | - Sound in background patch 22 | - Sound channel count increase 23 | - Farclip (terrain render distance) maximum value change 24 | - Frilldistance (grass render distance) change 25 | - Quickloot by default patch (hold shift for manual loot) 26 | - Nameplate range change 27 | - Large address aware patch 28 | - Camera rotation skip glitch fix 29 | 30 | The following patches are disabled by default, and can be enabled with command line parameters: 31 | - Maximum camera distance limit increase")] 32 | struct Args { 33 | /// Path to WoW.exe. 34 | #[clap(value_parser)] 35 | infile: String, 36 | 37 | /// Filename of the output file. 38 | #[clap(short, default_value_t = String::from("WoW_tweaked.exe"), value_parser)] 39 | outfile: String, 40 | 41 | /// FoV value in radians. Default game value is 1.5708. 42 | #[clap(long, default_value_t = 1.925f32, value_parser)] 43 | fov: f32, 44 | 45 | /// Farclip (terrain render distance) maximum value. Default game value is 777. Set with `/console farclip 1000` in-game. 46 | #[clap(long, default_value_t = 10000f32, value_parser)] 47 | farclip: f32, 48 | 49 | /// Frilldistance (grass render distance) value. Default game value is 70. 50 | #[clap(long, default_value_t = 300f32, value_parser)] 51 | frilldistance: f32, 52 | 53 | /// Nameplate distance in yards. Default game value is 20. 54 | #[clap(long, default_value_t = 41f32, value_parser)] 55 | nameplatedistance: f32, 56 | 57 | /// Default sound channel count. This can also be set with /console SoundSoftwareChannels 64, but is included here so that the changes persist if Config.wtf is deleted. 58 | /// Default game value is 12. Default value in TBC is 32(?). Default value in modern client is 64. 999 is the maximum value settable here. 59 | /// If you experience problems with performance, try lowering this. Values above 64 may cause crashes. 60 | #[clap(long, default_value_t = 64i32, value_parser = clap::value_parser!(i32).range(1..999))] 61 | soundchannels: i32, 62 | 63 | /// Max camera distance LIMIT. Current max camera distance is a setting in the menu & a console command. Default game value is 50. Unchanged by default. Should be greater than 0, otherwise bad things may happen. 64 | /// After patching, change with /console CameraDistanceMax 100 65 | #[clap(long, value_parser)] 66 | maxcameradistance: Option, 67 | 68 | /// If set, do not patch FoV. 69 | #[clap(long, default_value_t = false, value_parser)] 70 | no_fov: bool, 71 | 72 | /// If set, do not patch farclip. 73 | #[clap(long, default_value_t = false, value_parser)] 74 | no_farclip: bool, 75 | 76 | /// If set, do not patch frilldistance. 77 | #[clap(long, default_value_t = false, value_parser)] 78 | no_frilldistance: bool, 79 | 80 | /// If set, do not patch sound in background. 81 | #[clap(long, default_value_t = false, value_parser)] 82 | no_sound_in_background: bool, 83 | 84 | /// If set, do not patch quickloot. 85 | #[clap(long, default_value_t = false, value_parser)] 86 | no_quickloot: bool, 87 | 88 | /// If set, do not patch nameplate distance. 89 | #[clap(long, default_value_t = false, value_parser)] 90 | no_nameplatedistance: bool, 91 | 92 | /// If set, do not patch the number of sound channels. 93 | #[clap(long, default_value_t = false, value_parser)] 94 | no_soundchannels: bool, 95 | 96 | /// If set, do not patch the executable to be Large Address Aware. 97 | /// You may want to enable this if playing on incredibly low-end hardware with less than 3 GiB RAM. 98 | #[clap(long, default_value_t = false, value_parser)] 99 | no_largeaddressaware: bool, 100 | 101 | /// If set, do not patch the fix for the camera sometimes skipping to a random direction when rotated. 102 | #[clap(long, default_value_t = false, value_parser)] 103 | no_cameraskipfix: bool 104 | } 105 | 106 | /** 107 | * Replaces the first occurrence of find with replace, mutating haystack. 108 | * Returns true if a replacement occurred, false if not. 109 | */ 110 | #[allow(dead_code)] //unused, but I want to keep this here in case it's necessary later, so shut up compiler 111 | fn replace(haystack: &mut Vec, find: &Vec, replace: &Vec) -> bool { 112 | if haystack.len() < find.len() { 113 | return false; 114 | } 115 | 116 | if haystack.len() < replace.len() { 117 | return false; 118 | } 119 | 120 | let mut match_index: Option = None; 121 | for i in 0..haystack.len() - find.len() + 1 { 122 | if haystack[i..i+find.len()] == find[..] { 123 | match_index = Some(i); 124 | } 125 | } 126 | 127 | let match_index = match match_index { 128 | None => return false, 129 | Some(idx) => idx 130 | }; 131 | 132 | haystack.splice(match_index..match_index+replace.len(), replace.iter().cloned()); 133 | return true; 134 | } 135 | 136 | fn main() -> ExitCode { 137 | let args = Args::parse(); 138 | 139 | //Open input file 140 | let file_path = &args.infile; 141 | let mut file: std::vec::Vec = match fs::read(file_path) { 142 | Ok(file) => file, 143 | Err(err) => { 144 | println!("Unable to read file: {err}"); 145 | return ExitCode::from(1); 146 | } 147 | }; 148 | 149 | let outfile_path = OsString::from(&args.outfile); 150 | 151 | /* 152 | * PATCHES PATCHES PATCHES PATCHES 153 | */ 154 | 155 | // Large address aware patch 156 | if !args.no_largeaddressaware { 157 | const CHARACTERISTICS_OFFSET: usize = 0x126; 158 | let mut characteristics = u16::from_le_bytes(file[CHARACTERISTICS_OFFSET..CHARACTERISTICS_OFFSET+2].try_into().expect("slice with incorrect length!")); 159 | characteristics = characteristics | 0x20; // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#characteristics 160 | let characteristics_bytes = characteristics.to_le_bytes(); 161 | print!("Applying patch: make executable large address aware..."); 162 | file[CHARACTERISTICS_OFFSET..CHARACTERISTICS_OFFSET+characteristics_bytes.len()].copy_from_slice(&characteristics_bytes); 163 | println!(" Success!"); 164 | } 165 | 166 | // Farclip patch 167 | if !args.no_farclip { 168 | const FARCLIP_OFFSET: usize = 0x40FED8; 169 | let farclip_bytes: [u8; 4] = args.farclip.to_le_bytes(); 170 | print!("Applying patch: increased farclip max value..."); 171 | file[FARCLIP_OFFSET..FARCLIP_OFFSET+farclip_bytes.len()].copy_from_slice(&farclip_bytes); 172 | println!(" Success!"); 173 | } 174 | 175 | // Widescreen FoV patch 176 | if !args.no_fov { 177 | const FOV_OFFSET: usize = 0x4089B4; 178 | let fov_bytes = args.fov.to_le_bytes(); 179 | print!("Applying patch: widescreen FoV fix..."); 180 | file[FOV_OFFSET..FOV_OFFSET+fov_bytes.len()].copy_from_slice(&fov_bytes); 181 | println!(" Success!"); 182 | } 183 | 184 | // Frilldistance patch 185 | if !args.no_frilldistance { 186 | const FRILLDISTANCE_OFFSET: usize = 0x467958; 187 | let frilldistance_bytes = args.frilldistance.to_le_bytes(); 188 | print!("Applying patch: frilldistance (grass distance) increase..."); 189 | file[FRILLDISTANCE_OFFSET..FRILLDISTANCE_OFFSET+frilldistance_bytes.len()].copy_from_slice(&frilldistance_bytes); 190 | println!(" Success!"); 191 | } 192 | 193 | // Sound in background patch 194 | if !args.no_sound_in_background { 195 | const SOUND_IN_BACKGROUND_OFFSET: usize = 0x3A4869; 196 | const SOUND_IN_BACKGROUND_BYTES: [u8; 1] = [0x27]; 197 | print!("Applying patch: sound in background..."); 198 | file[SOUND_IN_BACKGROUND_OFFSET..SOUND_IN_BACKGROUND_OFFSET+SOUND_IN_BACKGROUND_BYTES.len()].copy_from_slice(&SOUND_IN_BACKGROUND_BYTES); 199 | println!(" Success!"); 200 | } 201 | 202 | // Sound channels patch 203 | if !args.no_soundchannels { 204 | const SOUNDCHANNEL_OFFSET: usize = 0x435d38; 205 | let soundchannel_string = args.soundchannels.to_string(); 206 | print!("Applying patch: software sound channels default increase..."); 207 | let cstring = CString::new(soundchannel_string).expect("CString::new failed"); 208 | let soundchannel_bytes = cstring.to_bytes_with_nul(); 209 | if soundchannel_bytes.len() <= 4 { 210 | file[SOUNDCHANNEL_OFFSET..SOUNDCHANNEL_OFFSET+soundchannel_bytes.len()].copy_from_slice(&soundchannel_bytes); 211 | println!(" Success!"); 212 | } 213 | else { 214 | println!(" FAILED!"); 215 | println!("Sound channels value is too long."); 216 | return ExitCode::from(1); 217 | } 218 | } 219 | 220 | // Quickloot key reverse patch (hold shift to manual loot) 221 | if !args.no_quickloot { 222 | const QUICKLOOT_OFFSET: usize = 0x0C1ECF; 223 | const QUICKLOOT_BYTES: [u8; 1] = [0x75]; 224 | const QUICKLOOT_OFFSET2: usize = 0x0C2B25; 225 | const QUICKLOOT_BYTES2: [u8; 1] = [0x75]; 226 | print!("Applying patch: quickloot reverse..."); 227 | file[QUICKLOOT_OFFSET..QUICKLOOT_OFFSET+QUICKLOOT_BYTES.len()].copy_from_slice(&QUICKLOOT_BYTES); 228 | file[QUICKLOOT_OFFSET2..QUICKLOOT_OFFSET2+QUICKLOOT_BYTES2.len()].copy_from_slice(&QUICKLOOT_BYTES2); 229 | println!(" Success!"); 230 | } 231 | 232 | // Nameplate range change patch 233 | if !args.no_nameplatedistance { 234 | const NAMEPLATE_OFFSET: usize = 0x40c448; 235 | let nameplate_bytes: [u8; 4] = args.nameplatedistance.to_le_bytes(); 236 | print!("Applying patch: nameplate range..."); 237 | file[NAMEPLATE_OFFSET..NAMEPLATE_OFFSET+nameplate_bytes.len()].copy_from_slice(&nameplate_bytes); 238 | println!(" Success!"); 239 | } 240 | 241 | // Max camera distance patch 242 | if let Some(maxcameradistance) = args.maxcameradistance { 243 | const MAXCAMERADISTANCE_OFFSET: usize = 0x4089a4; 244 | let maxcamera_bytes: [u8; 4] = maxcameradistance.to_le_bytes(); 245 | print!("Applying patch: max camera distance..."); 246 | file[MAXCAMERADISTANCE_OFFSET..MAXCAMERADISTANCE_OFFSET+maxcamera_bytes.len()].copy_from_slice(&maxcamera_bytes); 247 | println!(" Success!"); 248 | } 249 | 250 | // Camera skip glitch fix. 251 | // Thanks to Bon on the Turtle WoW Discord for implementing this patch, and phamd for submitting the PR to include it. 252 | if !args.no_cameraskipfix { 253 | let patches: [(usize, Vec); 5] = [ 254 | (0x02ccd0, vec![0x55, 0x8b, 0x05, 0x48, 0x4e, 0x88, 0x00, 0x8b, 0x0d, 0x44, 0x4e, 0x88, 0x00, 0xe9, 0x33, 0x90, 255 | 0x32, 0x00, 0x83, 0xc0, 0x32, 0x83, 0xc1, 0x32, 0x3b, 0x0d, 0xa8, 0xeb, 0xc4, 0x00, 0x7e, 0x03, 256 | 0x83, 0xe9, 0x01, 0x3b, 0x05, 0xac, 0xeb, 0xc4, 0x00, 0x7e, 0x03, 0x83, 0xe8, 0x01, 0x83, 0xe9, 257 | 0x32, 0x83, 0xe8, 0x32, 0x89, 0x05, 0x48, 0x4e, 0x88, 0x00, 0x89, 0x0d, 0x44, 0x4e, 0x88, 0x00, 258 | 0x5d, 0xeb, 0x0d]), 259 | (0x02d326, vec![0xe9, 0xb1, 0x8a, 0x32, 0x00]), 260 | (0x02d334, vec![0x8b, 0x35, 0x48, 0x4e, 0x88, 0x00]), 261 | (0x355d15, vec![ 0x83, 0xf8, 0x32, 0x7d, 0x03, 0x83, 0xc0, 0x01, 0x83, 0xf9, 0x32, 262 | 0x7d, 0x03, 0x83, 0xc1, 0x01, 0xe9, 0xb8, 0x6f, 0xcd, 0xff]), 263 | (0x355ddc, vec![ 0x8d, 0x4d, 0xf0, 0x51, 264 | 0xff, 0x35, 0x00, 0x4e, 0x88, 0x00, 0xff, 0x15, 0x50, 0xf6, 0x7f, 0x00, 0x8b, 0x45, 0xf0, 0x8b, 265 | 0x15, 0x44, 0x4e, 0x88, 0x00, 0xe9, 0x35, 0x75, 0xcd, 0xff]) 266 | ]; 267 | 268 | print!("Applying patch: camera skip glitch fix..."); 269 | for (address, bytes) in patches.iter() { 270 | file[*address..*address+bytes.len()].copy_from_slice(&bytes); 271 | } 272 | println!(" Success!"); 273 | } 274 | 275 | //Write out patched file 276 | match fs::write(&outfile_path, file) { 277 | Err(err) => { 278 | println!("File writing failed: {err}"); 279 | return ExitCode::from(1); 280 | }, 281 | Ok(_) => println!("Wrote file {}", outfile_path.to_string_lossy()) 282 | }; 283 | 284 | return ExitCode::from(0); 285 | } 286 | 287 | #[cfg(test)] 288 | mod tests { 289 | use super::*; 290 | 291 | #[test] 292 | fn replace_should_succeed() { 293 | let mut data: Vec:: = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 294 | let find: Vec:: = vec![3, 4, 5, 6]; 295 | let repl: Vec:: = vec![6, 5, 4, 3]; 296 | let return_val = replace(&mut data, &find, &repl); 297 | assert_eq!(data, [1u8, 2, 6, 5, 4, 3, 7, 8, 9, 10]); 298 | assert!(return_val); 299 | } 300 | 301 | #[test] 302 | fn replace_should_fail() { 303 | let mut data: Vec:: = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 304 | let find: Vec:: = vec![6, 6, 6, 6]; 305 | let repl: Vec:: = vec![6, 5, 4, 3]; 306 | let return_val = replace(&mut data, &find, &repl); 307 | assert!(!return_val); 308 | } 309 | 310 | #[test] 311 | fn replace_shouldnt_panic() { 312 | let mut data: Vec:: = vec![1, 2]; 313 | let find: Vec:: = vec![3, 4, 5, 6]; 314 | let repl: Vec:: = vec![6, 5, 4, 3]; 315 | let return_val = replace(&mut data, &find, &repl); 316 | assert!(!return_val); 317 | } 318 | } 319 | --------------------------------------------------------------------------------