diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7176bd0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,108 @@ +name: Build + +on: + push: + paths-ignore: + - '*.md' + - 'LICENSE*' + pull_request: + +env: + CARGO_BIN_NAME: sjiswrap + CARGO_TARGET_DIR: target + +jobs: + check: + name: Check + runs-on: windows-latest + env: + RUSTFLAGS: -D warnings + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Cargo check + run: cargo check --all-features --all-targets + - name: Cargo clippy + run: cargo clippy --all-features --all-targets + + fmt: + name: Format + runs-on: ubuntu-latest + env: + RUSTFLAGS: -D warnings + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Rust toolchain + # We use nightly options in rustfmt.toml + uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt + - name: Cargo fmt + run: cargo fmt --all --check + + build: + name: Build + strategy: + matrix: + include: + - platform: windows-latest + target: i686-pc-windows-msvc + name: windows-x86 + build: build + fail-fast: false + runs-on: ${{ matrix.platform }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install dependencies + if: matrix.packages != '' + run: | + sudo apt-get -y update + sudo apt-get -y install ${{ matrix.packages }} + - name: Install cargo-zigbuild + if: matrix.build == 'zigbuild' + run: pip install ziglang==0.10.1.post1 cargo-zigbuild==0.17.0 + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - name: Cargo build + run: cargo ${{ matrix.build }} --release --all-features --target ${{ matrix.target }} --bin ${{ env.CARGO_BIN_NAME }} + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.name }} + path: | + ${{ env.CARGO_TARGET_DIR }}/release/${{ env.CARGO_BIN_NAME }} + ${{ env.CARGO_TARGET_DIR }}/release/${{ env.CARGO_BIN_NAME }}.exe + ${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/release/${{ env.CARGO_BIN_NAME }} + ${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/release/${{ env.CARGO_BIN_NAME }}.exe + if-no-files-found: error + + release: + name: Release + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + needs: [ build ] + steps: + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + path: artifacts + - name: Rename artifacts + working-directory: artifacts + run: | + mkdir ../out + for i in */*/release/$CARGO_BIN_NAME*; do + mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/release\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")" + done + ls -R ../out + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: out/* diff --git a/Cargo.lock b/Cargo.lock index cb50b66..0ce9214 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,7 +52,7 @@ checksum = "bc62ccb14881da5d1862cda3a9648fb4a4897b2aff0b2557b89da44a5e550b7c" [[package]] name = "sjiswrap" -version = "0.1.0" +version = "1.0.0" dependencies = [ "anyhow", "encoding_rs", diff --git a/Cargo.toml b/Cargo.toml index ceeabab..11a7d84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,24 @@ [package] name = "sjiswrap" -version = "0.1.0" +description = "UTF-8 to Shift JIS wrapper for old compilers." +authors = ["Luke Street "] +license = "MIT OR Apache-2.0" +version = "1.0.0" edition = "2021" +publish = false +repository = "https://github.com/encounter/sjiswrap" +readme = "README.md" +categories = ["command-line-utilities"] + +# Size optimizations +[profile.release] +codegen-units = 1 +lto = true +opt-level = "z" +panic = "abort" + +[features] +debug = [] [dependencies] anyhow = "1.0.72" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..0afbee3 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Luke Street. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..59022f7 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright 2023 Luke Street. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..61ece8c --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# sjiswrap [![Build Status]][actions] + +[Build Status]: https://github.com/encounter/sjiswrap/actions/workflows/build.yml/badge.svg +[actions]: https://github.com/encounter/sjiswrap/actions + +UTF-8 to Shift JIS wrapper for old 32-bit Windows compilers. + +When the wrapped executable reads a text file, it will be encoded from UTF-8 to Shift JIS on the fly. + +Encoded file extensions: +- `.c` +- `.cc` +- `.cp` +- `.cpp` +- `.cxx` +- `.h` +- `.hh` +- `.hp` +- `.hpp` +- `.hxx` + +## Usage + +Download the latest release from [here](https://github.com/encounter/sjiswrap/releases). + +```shell +$ sjiswrap.exe [args...] +``` + +## Building + +```shell +$ cargo build --target i686-pc-windows-msvc --release +``` + +For smaller binaries: + +```shell +$ cargo +nightly build -Z build-std=std,panic_abort --target i686-pc-windows-msvc --release +``` + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as +defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/src/main.rs b/src/main.rs index 129d22e..3593a72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ -#![allow(non_snake_case)] - +#![allow(non_snake_case, clippy::let_and_return)] use std::{ borrow::Cow, cmp::min, @@ -13,15 +12,15 @@ use std::{ sync::Mutex, }; -use anyhow::Result; +use anyhow::{Context, Result}; use encoding_rs::SHIFT_JIS; use lazy_static::lazy_static; use windows::{ - core::PCSTR, + core::{PCSTR, PCWSTR}, Win32::{ Foundation::{ CloseHandle, GetLastError, SetLastError, BOOL, GENERIC_ACCESS_RIGHTS, GENERIC_READ, - HANDLE, + HANDLE, INVALID_HANDLE_VALUE, }, Security::SECURITY_ATTRIBUTES, Storage::FileSystem::{ @@ -33,29 +32,91 @@ use windows::{ }, }; +/// Whether to hook and encode a file. +fn is_text_file(path: &str) -> bool { + path.ends_with(".c") + || path.ends_with(".cc") + || path.ends_with(".cp") + || path.ends_with(".cpp") + || path.ends_with(".cxx") + || path.ends_with(".h") + || path.ends_with(".hh") + || path.ends_with(".hp") + || path.ends_with(".hpp") + || path.ends_with(".hxx") +} + +macro_rules! debug_println { + ($($arg:tt)*) => { + #[cfg(feature = "debug")] + { + // Writing to console will overwrite LastError + let err = unsafe { GetLastError() }; + eprintln!($($arg)*); + unsafe { SetLastError(err) }; + } + }; +} + +fn main() -> Result<()> { + let args: Vec = std::env::args_os().collect(); + if args.len() < 2 { + println!("Usage: {} ", args[0].to_string_lossy()); + exit(1); + } + + let path = PathBuf::from(&args[1]); + let parent = CString::new( + path.parent() + .context("Failed to get executable parent directory")? + .to_string_lossy() + .as_ref(), + ) + .unwrap(); + let parent = + get_full_path(&parent).expect("Failed to get absolute executable parent directory"); + unsafe { SetDllDirectoryA(PCSTR(parent.as_ptr() as *const u8)) } + .ok() + .context("SetDllDirectoryA() failed")?; + debug_println!("SetDllDirectoryA({:?})", parent.to_string_lossy()); + + let mut buf = Vec::new(); + File::open(&path) + .with_context(|| format!("Failed to open executable: '{}'", path.display()))? + .read_to_end(&mut buf) + .with_context(|| format!("Failed to read executable: '{}'", path.display()))?; + + let mut hooks = HashMap::new(); + hooks.insert("kernel32.dll!GetCommandLineA".into(), hook_GetCommandLineA as *const c_void); + hooks.insert("kernel32.dll!GetCommandLineW".into(), hook_GetCommandLineW as *const c_void); + hooks.insert("kernel32.dll!CreateFileA".into(), hook_CreateFileA as *const c_void); + hooks.insert("kernel32.dll!CreateFileW".into(), hook_CreateFileW as *const c_void); + hooks.insert("kernel32.dll!GetFileSize".into(), hook_GetFileSize as *const c_void); + hooks.insert("kernel32.dll!CloseHandle".into(), hook_CloseHandle as *const c_void); + hooks.insert("kernel32.dll!ReadFile".into(), hook_ReadFile as *const c_void); + hooks.insert("kernel32.dll!SetFilePointer".into(), hook_SetFilePointer as *const c_void); + unsafe { memexec::memexec_exe_with_hooks(&buf, &hooks) }.expect("Failed to execute"); + Ok(()) +} + +/// File that has been read into memory and encoded. struct FileHandle { data: Vec, pos: u64, } + +/// Global state shared between hooks. #[derive(Default)] struct GlobalState { cmdline: Option, file_handles: HashMap, } + lazy_static! { static ref GLOBAL_STATE: Mutex = Default::default(); } -fn get_full_path(path: &CStr) -> Result { - let mut buf = [0u8; 4096]; - let len = - unsafe { GetFullPathNameA(PCSTR(path.as_ptr() as *const u8), Some(buf.as_mut()), None) }; - if len == 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(unsafe { CString::from_vec_with_nul_unchecked(buf[..len as usize + 1].to_vec()) }) -} - +/// `GetCommandLineA` hook. Skips our own executable name and replaces the subprocess path with an absolute path. extern "stdcall" fn hook_GetCommandLineA() -> PCSTR { let mut guard = GLOBAL_STATE.lock().expect("Failed to lock global state"); if let Some(str) = &guard.cmdline { @@ -122,15 +183,12 @@ extern "stdcall" fn hook_GetCommandLineA() -> PCSTR { PCSTR(guard.cmdline.as_ref().unwrap().as_ptr() as *const u8) } -fn is_text_file(path: &str) -> bool { - path.ends_with(".c") - || path.ends_with(".cc") - || path.ends_with(".cp") - || path.ends_with(".cpp") - || path.ends_with(".h") - || path.ends_with(".hpp") +/// `GetCommandLineW` hook. Currently unsupported. +extern "stdcall" fn hook_GetCommandLineW() -> PCSTR { + panic!("GetCommandLineW() is not supported"); } +/// `CreateFileA` hook. If it's a text file, read it into memory and encode it as Shift-JIS. extern "stdcall" fn hook_CreateFileA( lpFileName: PCSTR, dwDesiredAccess: GENERIC_ACCESS_RIGHTS, @@ -151,7 +209,7 @@ extern "stdcall" fn hook_CreateFileA( hTemplateFile, ) } - .unwrap_or(HANDLE(0)); + .unwrap_or(INVALID_HANDLE_VALUE); let err = unsafe { GetLastError() }; let path = unsafe { CStr::from_ptr(lpFileName.as_ptr() as *const c_char) }.to_string_lossy(); @@ -181,16 +239,9 @@ extern "stdcall" fn hook_CreateFileA( match encoded { Cow::Borrowed(_) => { // No modifications were made, use the original data - // println!("READ FILE {:#X}, size {:#X} (UNCHANGED)", ret.0, filesize); guard.file_handles.insert(ret.0, FileHandle { data, pos: 0 }); } Cow::Owned(data) => { - println!( - "READ FILE {:#X}, size {:#X} (CHANGED: {:#X})", - ret.0, - filesize, - data.len() - ); guard.file_handles.insert(ret.0, FileHandle { data, pos: 0 }); } } @@ -198,42 +249,54 @@ extern "stdcall" fn hook_CreateFileA( } } } - // println!("CreateFileA({}, {:#X}) = {:#X}", path, dwDesiredAccess.0, ret.0 as u32); + debug_println!("CreateFileA({}, {:#X}) = {:#X}", path, dwDesiredAccess.0, ret.0 as u32); unsafe { SetLastError(err) }; ret } +/// `CreateFileW` hook. Currently unsupported. +extern "stdcall" fn hook_CreateFileW( + _lpFileName: PCWSTR, + _dwDesiredAccess: GENERIC_ACCESS_RIGHTS, + _dwShareMode: FILE_SHARE_MODE, + _lpSecurityAttributes: *const SECURITY_ATTRIBUTES, + _dwCreationDisposition: FILE_CREATION_DISPOSITION, + _dwFlagsAndAttributes: FILE_FLAGS_AND_ATTRIBUTES, + _hTemplateFile: HANDLE, +) -> HANDLE { + panic!("CreateFileW() is not supported"); +} + +/// `GetFileSize` hook. If the file was read into memory, return that size instead. extern "stdcall" fn hook_GetFileSize(hFile: HANDLE, lpFileSizeHigh: *mut u32) -> u32 { if !hFile.is_invalid() { let guard = GLOBAL_STATE.lock().expect("Failed to lock global state"); if let Some(file) = guard.file_handles.get(&hFile.0) { - // println!("OVERRIDE GetFileSize({:#X}) = {:#X}", hFile.0, file.data.len() as u32); + debug_println!("OVERRIDE: GetFileSize({:#X}) = {:#X}", hFile.0, file.data.len() as u32); return file.data.len() as u32; } } let ret = unsafe { GetFileSize(hFile, Some(lpFileSizeHigh)) }; - // let err = unsafe { GetLastError() }; - // println!("GetFileSize({:#X}, {:?}) = {:#X}", hFile.0, lpFileSizeHigh, ret); - // unsafe { SetLastError(err) }; + debug_println!("GetFileSize({:#X}, {:?}) = {:#X}", hFile.0, lpFileSizeHigh, ret); ret } +/// `CloseHandle` hook. If the file was read into memory, free it. extern "stdcall" fn hook_CloseHandle(hObject: HANDLE) -> BOOL { if !hObject.is_invalid() { let mut guard = GLOBAL_STATE.lock().expect("Failed to lock global state"); if guard.file_handles.remove(&hObject.0).is_some() { - // println!("REMOVED HANDLE {:#X}", hObject.0); + debug_println!("File handle removed: {:#X}", hObject.0); } } let ret = unsafe { CloseHandle(hObject) }; - // let err = unsafe { GetLastError() }; - // println!("CloseHandle({:#X}) = {:#X}", hObject.0, ret.0); - // unsafe { SetLastError(err) }; + debug_println!("CloseHandle({:#X}) = {:#X}", hObject.0, ret.0); ret } +/// `ReadFile` hook. If the file was read into memory, read from that instead. extern "stdcall" fn hook_ReadFile( hFile: HANDLE, lpBuffer: *mut c_void, @@ -259,10 +322,14 @@ extern "stdcall" fn hook_ReadFile( if !lpNumberOfBytesRead.is_null() { unsafe { *lpNumberOfBytesRead = count }; } - // println!( - // "OVERRIDE ReadFile({:#X}, {:?}, {:#X}, {:?}) = {:#X}", - // hFile.0, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, count - // ); + debug_println!( + "OVERRIDE: ReadFile({:#X}, {:?}, {:#X}, {:?}) = {:#X}", + hFile.0, + lpBuffer, + nNumberOfBytesToRead, + lpNumberOfBytesRead, + count + ); return true.into(); } } @@ -276,15 +343,18 @@ extern "stdcall" fn hook_ReadFile( Some(lpOverlapped), ) }; - let err = unsafe { GetLastError() }; - // println!( - // "ReadFile({:#X}, {:?}, {:#X}, {:?}) = {:#X}", - // hFile.0, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, ret.0 - // ); - unsafe { SetLastError(err) }; + debug_println!( + "ReadFile({:#X}, {:?}, {:#X}, {:?}) = {:#X}", + hFile.0, + lpBuffer, + nNumberOfBytesToRead, + lpNumberOfBytesRead, + ret.0 + ); ret } +/// `SetFilePointer` hook. If the file was read into memory, set the position in that instead. extern "stdcall" fn hook_SetFilePointer( hFile: HANDLE, lDistanceToMove: i32, @@ -308,9 +378,13 @@ extern "stdcall" fn hook_SetFilePointer( file_size, ); file.pos = pos; - println!( + debug_println!( "OVERRIDE SetFilePointer({:#X}, {:#X}, {:?}, {}) = {:#X}", - hFile.0, distance_to_move, lpDistanceToMoveHigh, dwMoveMethod.0, pos + hFile.0, + distance_to_move, + lpDistanceToMoveHigh, + dwMoveMethod.0, + pos ); if !lpDistanceToMoveHigh.is_null() { unsafe { *lpDistanceToMoveHigh = (pos >> 32) as i32 }; @@ -321,46 +395,24 @@ extern "stdcall" fn hook_SetFilePointer( let ret = unsafe { SetFilePointer(hFile, lDistanceToMove, Some(lpDistanceToMoveHigh), dwMoveMethod) }; - let err = unsafe { GetLastError() }; - // println!( - // "SetFilePointer({:#X}, {:#X}, {:?}, {}) = {:#X}", - // hFile.0, lDistanceToMove, lpDistanceToMoveHigh, dwMoveMethod.0, ret - // ); - unsafe { SetLastError(err) }; + debug_println!( + "SetFilePointer({:#X}, {:#X}, {:?}, {}) = {:#X}", + hFile.0, + lDistanceToMove, + lpDistanceToMoveHigh, + dwMoveMethod.0, + ret + ); ret } -fn main() -> Result<()> { - let args: Vec = std::env::args_os().collect(); - if args.len() < 2 { - println!("Usage: {} ", args[0].to_string_lossy()); - exit(1); +/// Get the absolute path of a file. +fn get_full_path(path: &CStr) -> Result { + let mut buf = [0u8; 4096]; + let len = + unsafe { GetFullPathNameA(PCSTR(path.as_ptr() as *const u8), Some(buf.as_mut()), None) }; + if len == 0 { + return Err(std::io::Error::last_os_error().into()); } - - let path = PathBuf::from(&args[1]); - let parent = CString::new( - path.parent() - .expect("Failed to get executable parent directory") - .to_string_lossy() - .as_ref(), - ) - .unwrap(); - let parent = - get_full_path(&parent).expect("Failed to get absolute executable parent directory"); - unsafe { SetDllDirectoryA(PCSTR(parent.as_ptr() as *const u8)) } - .expect("SetDllDirectoryA() failed"); - // println!("SetDllDirectoryA({:?})", parent.to_string_lossy()); - - let mut buf = Vec::new(); - File::open(&path).unwrap().read_to_end(&mut buf).unwrap(); - - let mut hooks = HashMap::new(); - hooks.insert("kernel32.dll!GetCommandLineA".into(), hook_GetCommandLineA as *const c_void); - hooks.insert("kernel32.dll!CreateFileA".into(), hook_CreateFileA as *const c_void); - hooks.insert("kernel32.dll!GetFileSize".into(), hook_GetFileSize as *const c_void); - hooks.insert("kernel32.dll!CloseHandle".into(), hook_CloseHandle as *const c_void); - hooks.insert("kernel32.dll!ReadFile".into(), hook_ReadFile as *const c_void); - hooks.insert("kernel32.dll!SetFilePointer".into(), hook_SetFilePointer as *const c_void); - unsafe { memexec::memexec_exe_with_hooks(&buf, &hooks) }.unwrap(); - Ok(()) + Ok(unsafe { CString::from_vec_with_nul_unchecked(buf[..len as usize + 1].to_vec()) }) }