Support MWCC 4.1+ series compilers (#1)

This implements support for MWCC 4.1+ (aka GC 3.0a3 and above).

Notable changes:
- Hook `CreateFileMappingA` / `MapViewOfFile`: These compilers use these functions rather than `ReadFile` for headers.
- Hook `MultiByteToWideChar`: This function's behavior changed in Windows 2000 SP4 / Windows XP, and certain compiler versions rely on the old behavior. This fully reimplements it for CP-932 with the legacy behavior.
- Explicitly fail on invalid UTF-8 inputs. Add a warning log when the Shift JIS output is invalid.
This commit is contained in:
Luke Street 2024-10-27 16:18:43 -06:00 committed by GitHub
parent b85eab4cc8
commit 9120080eb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 717 additions and 114 deletions

View File

@ -8,8 +8,9 @@ on:
pull_request:
env:
CARGO_BIN_NAME: sjiswrap
BUILD_PROFILE: release
CARGO_TARGET_DIR: target
CARGO_INCREMENTAL: 0
jobs:
check:
@ -19,11 +20,14 @@ jobs:
RUSTFLAGS: -D warnings
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@master
with:
components: clippy
toolchain: nightly-2023-09-18
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check
run: cargo check --features debug --all-targets
- name: Cargo clippy
@ -36,53 +40,52 @@ jobs:
RUSTFLAGS: -D warnings
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust toolchain
# We use nightly options in rustfmt.toml
uses: dtolnay/rust-toolchain@nightly
uses: dtolnay/rust-toolchain@master
with:
components: rustfmt
toolchain: nightly-2023-09-18
- name: Cargo fmt
run: cargo fmt --all --check
build:
name: Build
env:
CARGO_BIN_NAME: sjiswrap
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
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@nightly
uses: dtolnay/rust-toolchain@master
with:
components: rust-src
targets: ${{ matrix.target }}
- name: Cargo build
run: cargo ${{ matrix.build }} --release --features nightly --target ${{ matrix.target }} --bin ${{ env.CARGO_BIN_NAME }} -Z build-std=std,panic_abort
- name: Upload artifacts
uses: actions/upload-artifact@v3
toolchain: nightly-2023-09-18
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
with:
name: ${{ matrix.name }}
key: ${{ matrix.target }}
- name: Cargo build
run: >
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} -Z build-std=std,panic_abort --features nightly
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ env.CARGO_BIN_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
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error
release:
@ -90,20 +93,52 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ build ]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check git tag against Cargo version
shell: bash
run: |
set -eou pipefail
tag='${{github.ref}}'
tag="${tag#refs/tags/}"
version=$(grep '^version' Cargo.toml | head -1 | awk -F' = ' '{print $2}' | tr -d '"')
version="v$version"
if [ "$tag" != "$version" ]; then
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
exit 1
fi
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Rename artifacts
working-directory: artifacts
run: |
set -euo pipefail
mkdir ../out
for i in */*/release/$CARGO_BIN_NAME*; do
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/release\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")"
for dir in */; do
for file in "$dir"*; do
base=$(basename "$file")
name="${base%.*}"
ext="${base##*.}"
if [ "$ext" = "$base" ]; then
ext=""
else
ext=".$ext"
fi
arch="${dir%/}" # remove trailing slash
arch="${arch##"$name-"}" # remove bin name
dst="../out/${name}-${arch}${ext}"
mv "$file" "$dst"
done
done
ls -R ../out
- name: Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
files: out/*
draft: true
generate_release_notes: true

224
Cargo.lock generated
View File

@ -4,15 +4,15 @@ version = 3
[[package]]
name = "anyhow"
version = "1.0.75"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "cfg-if"
@ -43,10 +43,74 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc62ccb14881da5d1862cda3a9648fb4a4897b2aff0b2557b89da44a5e550b7c"
[[package]]
name = "num-traits"
version = "0.2.16"
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
@ -63,40 +127,132 @@ dependencies = [
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-hash"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
[[package]]
name = "sjiswrap"
version = "1.1.1"
version = "1.2.0"
dependencies = [
"anyhow",
"encoding_rs",
"memexec",
"num",
"rustc-hash",
"windows",
]
[[package]]
name = "windows"
version = "0.48.0"
name = "syn"
version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "windows"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
dependencies = [
"windows-core",
"windows-targets",
]
[[package]]
name = "windows-core"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-strings",
"windows-targets",
]
[[package]]
name = "windows-implement"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result",
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@ -105,42 +261,48 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@ -3,12 +3,13 @@ name = "sjiswrap"
description = "UTF-8 to Shift JIS wrapper for old compilers."
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
version = "1.1.1"
version = "1.2.0"
edition = "2021"
publish = false
repository = "https://github.com/encounter/sjiswrap"
readme = "README.md"
categories = ["command-line-utilities"]
rust-version = "1.72.0"
# Size optimizations
[profile.release]
@ -25,13 +26,14 @@ nightly = [
]
[dependencies]
anyhow = "1.0.72"
encoding_rs = "0.8.32"
memexec = { version = "0.2.0", features = ["hook"] }
rustc-hash = "1.1.0"
anyhow = "1.0"
encoding_rs = "0.8"
memexec = { version = "0.2", features = ["hook"] }
num = "0.4"
rustc-hash = "2.0"
[dependencies.windows]
version = "0.48.0"
version = "0.58"
features = [
"Win32_Foundation",
"Win32_Globalization",

View File

@ -9,26 +9,38 @@ use std::{
iter::{Cloned, Peekable},
mem::MaybeUninit,
path::{Path, PathBuf},
pin::Pin,
process::exit,
};
use anyhow::{Context, Result};
use encoding_rs::SHIFT_JIS;
use encoding_rs::{SHIFT_JIS, UTF_8};
use num::Zero;
use rustc_hash::FxHashMap;
use windows::{
core::{PCSTR, PCWSTR},
Win32::{
Foundation::{
CloseHandle, GetLastError, SetLastError, BOOL, ERROR_INSUFFICIENT_BUFFER,
GENERIC_ACCESS_RIGHTS, GENERIC_READ, HANDLE, HMODULE, INVALID_HANDLE_VALUE,
ERROR_NO_UNICODE_TRANSLATION, ERROR_SUCCESS, GENERIC_ACCESS_RIGHTS, GENERIC_READ,
HANDLE, HMODULE, INVALID_HANDLE_VALUE,
},
Globalization::{MultiByteToWideChar, MULTI_BYTE_TO_WIDE_CHAR_FLAGS},
Security::SECURITY_ATTRIBUTES,
Storage::FileSystem::{
CreateFileA, GetFileSize, GetFullPathNameA, ReadFile, SetFilePointer, FILE_BEGIN,
FILE_CREATION_DISPOSITION, FILE_CURRENT, FILE_END, FILE_FLAGS_AND_ATTRIBUTES,
FILE_SHARE_MODE, SET_FILE_POINTER_MOVE_METHOD,
CreateFileA, GetFileSize, GetFullPathNameA, ReadFile, ReadFileEx, SetFilePointer,
FILE_BEGIN, FILE_CREATION_DISPOSITION, FILE_CURRENT, FILE_END,
FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_MODE, SET_FILE_POINTER_MOVE_METHOD,
},
System::{
Environment::GetCommandLineA,
LibraryLoader::SetDllDirectoryA,
Memory::{
CreateFileMappingA, MapViewOfFile, UnmapViewOfFile, FILE_MAP, FILE_MAP_ALL_ACCESS,
FILE_MAP_WRITE, MEMORY_MAPPED_VIEW_ADDRESS, PAGE_PROTECTION_FLAGS,
},
IO::{LPOVERLAPPED_COMPLETION_ROUTINE, OVERLAPPED},
},
System::{Environment::GetCommandLineA, LibraryLoader::SetDllDirectoryA, IO::OVERLAPPED},
},
};
@ -61,6 +73,15 @@ macro_rules! debug_println {
};
}
macro_rules! fail {
($($arg:tt)*) => {
{
eprintln!($($arg)*);
exit(1);
}
};
}
fn main() -> Result<()> {
let args: Vec<OsString> = std::env::args_os().collect();
if args.len() < 2 {
@ -99,10 +120,25 @@ fn main() -> Result<()> {
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!ReadFileEx".into(), hook_ReadFileEx as *const c_void);
hooks.insert("kernel32.dll!SetFilePointer".into(), hook_SetFilePointer as *const c_void);
hooks.insert("kernel32.dll!IsDBCSLeadByte".into(), hook_IsDBCSLeadByte as *const c_void);
hooks
.insert("kernel32.dll!GetModuleFileNameA".into(), hook_GetModuleFileNameA as *const c_void);
hooks
.insert("kernel32.dll!CreateFileMappingA".into(), hook_CreateFileMappingA as *const c_void);
hooks
.insert("kernel32.dll!CreateFileMappingW".into(), hook_CreateFileMappingW as *const c_void);
hooks.insert("kernel32.dll!OpenFileMappingA".into(), hook_OpenFileMappingA as *const c_void);
hooks.insert("kernel32.dll!OpenFileMappingW".into(), hook_OpenFileMappingW as *const c_void);
hooks.insert("kernel32.dll!MapViewOfFile".into(), hook_MapViewOfFile as *const c_void);
hooks.insert("kernel32.dll!MapViewOfFileEx".into(), hook_MapViewOfFileEx as *const c_void);
hooks.insert("kernel32.dll!UnmapViewOfFile".into(), hook_UnmapViewOfFile as *const c_void);
hooks.insert(
"kernel32.dll!MultiByteToWideChar".into(),
hook_MultiByteToWideChar as *const c_void,
);
hooks.insert("kernel32.dll!GetACP".into(), hook_GetACP as *const c_void);
unsafe { memexec::memexec_exe_with_hooks(&buf, &hooks) }.expect("Failed to execute");
Ok(())
}
@ -118,15 +154,21 @@ struct FileHandle {
struct GlobalState {
exe_path: CString,
cmdline: Option<CString>,
encoded_files: FxHashMap<PathBuf, Vec<u8>>,
file_handles: FxHashMap<isize, FileHandle>,
encoded_files: FxHashMap<PathBuf, Pin<Box<[u8]>>>,
file_handles: FxHashMap<*mut c_void, FileHandle>,
file_mapping_handles: FxHashMap<*mut c_void, HANDLE>,
view_to_mapping: FxHashMap<*mut c_void, HANDLE>,
}
impl GlobalState {
fn file_by_handle(&mut self, handle: HANDLE) -> Option<(&mut FileHandle, &[u8])> {
fn file_by_handle(&mut self, handle: HANDLE) -> Option<(&mut FileHandle, Pin<&[u8]>)> {
self.file_handles
.get_mut(&handle.0)
.and_then(|file| self.encoded_files.get(&file.path).map(|data| (file, data.as_slice())))
.and_then(|file| self.encoded_files.get(&file.path).map(|data| (file, data.as_ref())))
}
fn file_by_mapping_handle(&mut self, handle: HANDLE) -> Option<(&mut FileHandle, Pin<&[u8]>)> {
self.file_mapping_handles.get(&handle.0).cloned().and_then(|file| self.file_by_handle(file))
}
}
@ -154,7 +196,7 @@ extern "stdcall" fn hook_GetCommandLineA() -> PCSTR {
loop {
let Some(c) = iter.next() else {
if quoted {
panic!("GetCommandLineA(): Unterminated quoted string");
fail!("sjiswrap: GetCommandLineA(): Unterminated quoted string");
}
break;
};
@ -162,8 +204,8 @@ extern "stdcall" fn hook_GetCommandLineA() -> PCSTR {
if c == b'"' {
let next = iter.next();
if next != Some(b' ') && next.is_some() {
panic!(
"GetCommandLineA(): Expected space after quote, got '{}'",
fail!(
"sjiswrap: GetCommandLineA(): Expected space after quote, got '{}'",
char::from(next.unwrap())
);
}
@ -201,7 +243,7 @@ extern "stdcall" fn hook_GetCommandLineA() -> PCSTR {
/// `GetCommandLineW` hook. Currently unsupported.
extern "stdcall" fn hook_GetCommandLineW() -> PCSTR {
panic!("GetCommandLineW() is not supported");
fail!("sjiswrap: GetCommandLineW() is not supported");
}
/// Read a file into memory and encode it as Shift JIS.
@ -220,32 +262,35 @@ fn encode_file(handle: HANDLE, path: &Path) {
return;
}
let mut data = vec![0u8; filesize as usize];
// Include a null terminator for MapViewOfFile
let mut data = vec![0u8; filesize as usize + 1];
let mut bytes_read = 0u32;
if !unsafe {
ReadFile(
handle,
Some(data.as_mut_ptr() as *mut c_void),
data.len() as u32,
Some(&mut bytes_read),
None,
)
if unsafe {
let slice = &mut data[..filesize as usize];
ReadFile(handle, Some(slice), Some(&mut bytes_read), None)
}
.as_bool()
.is_err()
|| bytes_read != filesize as u32
{
eprintln!("sjiswrap: Failed to read file {}", path.display());
return;
}
let str = unsafe { std::str::from_utf8_unchecked(&data) };
let (encoded, _, _) = SHIFT_JIS.encode(str);
let str = match std::str::from_utf8(&data) {
Ok(str) => str,
Err(e) => fail!("sjiswrap: File {} is not valid UTF-8: {}", path.display(), e),
};
let (encoded, _, error) = SHIFT_JIS.encode(str);
if error {
eprintln!("sjiswrap: File {} contains Shift JIS encoding errors", path.display());
}
match encoded {
Cow::Borrowed(_) => {
// No modifications were made, use the original data
entry.insert(data);
entry.insert(Pin::new(data.into_boxed_slice()));
}
Cow::Owned(data) => {
entry.insert(data);
entry.insert(Pin::new(data.into_boxed_slice()));
}
}
}
@ -302,21 +347,23 @@ extern "stdcall" fn hook_CreateFileW(
_dwFlagsAndAttributes: FILE_FLAGS_AND_ATTRIBUTES,
_hTemplateFile: HANDLE,
) -> HANDLE {
panic!("CreateFileW() is not supported");
fail!("sjiswrap: 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 state = unsafe { GLOBAL_STATE.assume_init_mut() };
if let Some((_handle, data)) = state.file_by_handle(hFile) {
debug_println!("OVERRIDE: GetFileSize({:#X}) = {:#X}", hFile.0, data.len() as u32);
return data.len() as u32;
if let Some((handle, data)) = state.file_by_handle(hFile) {
let _ = handle;
let file_size = data.len() as u32 - 1 /* null terminator */;
debug_println!("OVERRIDE: GetFileSize({}) = {:#X}", handle.path.display(), file_size);
return file_size;
}
}
let ret = unsafe { GetFileSize(hFile, Some(lpFileSizeHigh)) };
debug_println!("GetFileSize({:#X}, {:?}) = {:#X}", hFile.0, lpFileSizeHigh, ret);
debug_println!("GetFileSize({:p}, {:?}) = {:#X}", hFile.0, lpFileSizeHigh, ret);
ret
}
@ -326,15 +373,18 @@ extern "stdcall" fn hook_CloseHandle(hObject: HANDLE) -> BOOL {
let state = unsafe { GLOBAL_STATE.assume_init_mut() };
if let Some(handle) = state.file_handles.remove(&hObject.0) {
let _ = handle;
debug_println!("File handle removed: {:#X} ({})", hObject.0, handle.path.display());
debug_println!("File handle removed: {:p} ({})", hObject.0, handle.path.display());
// Purposefully leave the file data itself in the cache.
// mwcceppc in particular will read the same file multiple times.
}
if let Some(_mapping) = state.file_mapping_handles.remove(&hObject.0) {
debug_println!("File mapping handle removed: {:p}", hObject.0);
}
}
let ret = unsafe { CloseHandle(hObject) };
debug_println!("CloseHandle({:#X}) = {:#X}", hObject.0, ret.0);
ret
let ret = unsafe { CloseHandle(hObject) }.is_ok();
debug_println!("CloseHandle({:p}) = {}", hObject.0, ret);
ret.into()
}
/// `ReadFile` hook. If the file was read into memory, read from that instead.
@ -348,9 +398,10 @@ extern "stdcall" fn hook_ReadFile(
if !hFile.is_invalid() {
let state = unsafe { GLOBAL_STATE.assume_init_mut() };
if let Some((handle, data)) = state.file_by_handle(hFile) {
let file_size = data.len() as u64 - 1 /* null terminator */;
let count = min(
nNumberOfBytesToRead,
u32::try_from(data.len() as u64 - handle.pos).unwrap_or(u32::MAX),
u32::try_from(file_size - handle.pos).unwrap_or(u32::MAX),
);
unsafe {
std::ptr::copy_nonoverlapping(
@ -364,7 +415,7 @@ extern "stdcall" fn hook_ReadFile(
unsafe { *lpNumberOfBytesRead = count };
}
debug_println!(
"OVERRIDE: ReadFile({:#X}, {:?}, {:#X}, {:?}) = {:#X}",
"OVERRIDE: ReadFile({:p}, {:?}, {:#X}, {:?}) = {:#X}",
hFile.0,
lpBuffer,
nNumberOfBytesToRead,
@ -378,23 +429,259 @@ extern "stdcall" fn hook_ReadFile(
let ret = unsafe {
ReadFile(
hFile,
Some(lpBuffer),
nNumberOfBytesToRead,
Some(std::slice::from_raw_parts_mut(
lpBuffer as *mut u8,
nNumberOfBytesToRead as usize,
)),
Some(lpNumberOfBytesRead),
Some(lpOverlapped),
)
};
}
.is_ok();
debug_println!(
"ReadFile({:#X}, {:?}, {:#X}, {:?}) = {:#X}",
"ReadFile({:p}, {:?}, {:#X}, {:?}) = {}",
hFile.0,
lpBuffer,
nNumberOfBytesToRead,
lpNumberOfBytesRead,
ret
);
ret.into()
}
/// `ReadFileEx` hook. Currently unsupported.
extern "stdcall" fn hook_ReadFileEx(
hFile: HANDLE,
lpBuffer: *mut c_void,
nNumberOfBytesToRead: u32,
lpOverlapped: *mut OVERLAPPED,
lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE,
) -> BOOL {
if !hFile.is_invalid() {
let state = unsafe { GLOBAL_STATE.assume_init_mut() };
if let Some((_handle, _data)) = state.file_by_handle(hFile) {
fail!("sjiswrap: ReadFileEx() is not supported");
}
}
// Pass through un-encoded files
let ret = unsafe {
ReadFileEx(
hFile,
if lpBuffer.is_null() {
None
} else {
Some(std::slice::from_raw_parts_mut(
lpBuffer as *mut u8,
nNumberOfBytesToRead as usize,
))
},
lpOverlapped,
lpCompletionRoutine,
)
}
.is_ok();
debug_println!(
"ReadFileEx({:p}, {:?}, {:#X}, {:?}, {:?}) = {}",
hFile.0,
lpBuffer,
nNumberOfBytesToRead,
lpOverlapped,
lpCompletionRoutine,
ret
);
ret.into()
}
/// `CreateFileMappingA` hook. Currently unsupported.
extern "stdcall" fn hook_CreateFileMappingA(
hFile: HANDLE,
lpAttributes: *const SECURITY_ATTRIBUTES,
flProtect: PAGE_PROTECTION_FLAGS,
dwMaximumSizeHigh: u32,
dwMaximumSizeLow: u32,
lpName: PCSTR,
) -> HANDLE {
let ret = unsafe {
CreateFileMappingA(
hFile,
if lpAttributes.is_null() { None } else { Some(lpAttributes) },
flProtect,
dwMaximumSizeHigh,
dwMaximumSizeLow,
lpName,
)
}
.unwrap_or(INVALID_HANDLE_VALUE);
if !hFile.is_invalid() && !ret.is_invalid() {
let state = unsafe { GLOBAL_STATE.assume_init_mut() };
if let Some((_handle, _data)) = state.file_by_handle(hFile) {
if let Some(existing) = state.file_mapping_handles.insert(ret.0, hFile) {
fail!(
"sjiswrap: CreateFileMappingA({:p}, {:?}, {:#X}, {:#X}, {:#X}, {:?}): Mapping already exists for {:p}",
hFile.0,
lpAttributes,
flProtect.0,
dwMaximumSizeHigh,
dwMaximumSizeLow,
lpName,
existing.0
);
}
debug_println!(
"OVERRIDE CreateFileMappingA({:p}, {:?}, {:#X}, {:#X}, {:#X}, {:?}) = {:p}",
hFile.0,
lpAttributes,
flProtect.0,
dwMaximumSizeHigh,
dwMaximumSizeLow,
lpName,
ret.0
);
return ret;
}
}
debug_println!(
"CreateFileMappingA({:p}, {:?}, {:#X}, {:#X}, {:#X}, {:?}) = {:p}",
hFile.0,
lpAttributes,
flProtect.0,
dwMaximumSizeHigh,
dwMaximumSizeLow,
lpName,
ret.0
);
ret
}
/// `CreateFileMappingW` hook. Currently unsupported.
extern "stdcall" fn hook_CreateFileMappingW(
_hFile: HANDLE,
_lpAttributes: *const SECURITY_ATTRIBUTES,
_flProtect: u32,
_dwMaximumSizeHigh: u32,
_dwMaximumSizeLow: u32,
_lpName: PCWSTR,
) -> HANDLE {
fail!("sjiswrap: CreateFileMappingW() is not supported");
}
/// `OpenFileMappingA` hook. Currently unsupported.
extern "stdcall" fn hook_OpenFileMappingA(
_dwDesiredAccess: u32,
_bInheritHandle: BOOL,
_lpName: PCSTR,
) -> HANDLE {
fail!("sjiswrap: OpenFileMappingA() is not supported");
}
/// `OpenFileMappingW` hook. Currently unsupported.
extern "stdcall" fn hook_OpenFileMappingW(
_dwDesiredAccess: u32,
_bInheritHandle: BOOL,
_lpName: PCWSTR,
) -> HANDLE {
fail!("sjiswrap: OpenFileMappingW() is not supported");
}
/// `MapViewOfFile` hook. If the file was read into memory, return that instead.
extern "stdcall" fn hook_MapViewOfFile(
hFileMappingObject: HANDLE,
dwDesiredAccess: FILE_MAP,
dwFileOffsetHigh: u32,
dwFileOffsetLow: u32,
dwNumberOfBytesToMap: usize,
) -> *mut c_void {
if !hFileMappingObject.is_invalid() {
let state = unsafe { GLOBAL_STATE.assume_init_mut() };
if let Some((handle, data)) = state.file_by_mapping_handle(hFileMappingObject) {
if dwDesiredAccess.contains(FILE_MAP_WRITE)
|| dwDesiredAccess.contains(FILE_MAP_ALL_ACCESS)
{
fail!("sjiswrap: MapViewOfFile(): Write access to encoded file is not supported");
}
let offset = (dwFileOffsetHigh as u64) << 32 | dwFileOffsetLow as u64;
if offset > 0 {
fail!(
"sjiswrap: MapViewOfFile({}): Offset is not supported ({})",
handle.path.display(),
offset
);
}
if dwNumberOfBytesToMap != data.len() - 1
/* null terminator */
{
fail!(
"sjiswrap: MapViewOfFile({}): Mapping size mismatch ({} != {})",
handle.path.display(),
dwNumberOfBytesToMap,
data.len() - 1
);
}
let ptr = data.as_ptr() as *mut c_void;
debug_println!(
"OVERRIDE MapViewOfFile({:p}, {:#X}, {:#X}, {:#X}, {:#X}) = {:p}",
hFileMappingObject.0,
dwDesiredAccess.0,
dwFileOffsetHigh,
dwFileOffsetLow,
dwNumberOfBytesToMap,
ptr
);
state.view_to_mapping.insert(ptr, hFileMappingObject);
return ptr;
}
}
let ret = unsafe {
MapViewOfFile(
hFileMappingObject,
dwDesiredAccess,
dwFileOffsetHigh,
dwFileOffsetLow,
dwNumberOfBytesToMap,
)
};
debug_println!(
"MapViewOfFile({:p}, {:#X}, {:#X}, {:#X}, {:#X}) = {:p}",
hFileMappingObject.0,
dwDesiredAccess.0,
dwFileOffsetHigh,
dwFileOffsetLow,
dwNumberOfBytesToMap,
ret.Value
);
ret.Value
}
/// `MapViewOfFileEx` hook. Currently unsupported.
extern "stdcall" fn hook_MapViewOfFileEx(
_hFileMappingObject: HANDLE,
_dwDesiredAccess: FILE_MAP,
_dwFileOffsetHigh: u32,
_dwFileOffsetLow: u32,
_dwNumberOfBytesToMap: usize,
_lpBaseAddress: *mut c_void,
) -> *mut c_void {
fail!("sjiswrap: MapViewOfFileEx() is not supported");
}
/// `UnmapViewOfFile` hook. If the file was read into memory, remove the mapping.
extern "stdcall" fn hook_UnmapViewOfFile(lpBaseAddress: *mut c_void) -> BOOL {
let state = unsafe { GLOBAL_STATE.assume_init_mut() };
if let Some(_handle) = state.view_to_mapping.remove(&lpBaseAddress) {
debug_println!("OVERRIDE UnmapViewOfFile({:p})", lpBaseAddress);
return true.into();
}
let ret =
unsafe { UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { Value: lpBaseAddress }) }.is_ok();
debug_println!("UnmapViewOfFile({:p}) = {}", lpBaseAddress, ret);
ret.into()
}
/// `SetFilePointer` hook. If the file was read into memory, set the position in that instead.
extern "stdcall" fn hook_SetFilePointer(
hFile: HANDLE,
@ -408,19 +695,22 @@ extern "stdcall" fn hook_SetFilePointer(
let distance_to_move_high =
if lpDistanceToMoveHigh.is_null() { 0 } else { unsafe { *lpDistanceToMoveHigh } };
let distance_to_move = lDistanceToMove as i64 | (distance_to_move_high as i64) << 32;
let file_size = data.len() as u64;
let file_size = data.len() as u64 - 1 /* null terminator */;
let pos = min(
match dwMoveMethod {
FILE_BEGIN => distance_to_move as u64,
FILE_CURRENT => handle.pos.saturating_add_signed(distance_to_move),
FILE_END => file_size.saturating_add_signed(distance_to_move),
_ => panic!("SetFilePointer(): Unsupported move method {:#X}", dwMoveMethod.0),
_ => fail!(
"sjiswrap: SetFilePointer(): Unsupported move method {:#X}",
dwMoveMethod.0
),
},
file_size,
);
handle.pos = pos;
debug_println!(
"OVERRIDE SetFilePointer({:#X}, {:#X}, {:?}, {}) = {:#X}",
"OVERRIDE SetFilePointer({:p}, {:#X}, {:?}, {}) = {:#X}",
hFile.0,
distance_to_move,
lpDistanceToMoveHigh,
@ -437,7 +727,7 @@ extern "stdcall" fn hook_SetFilePointer(
let ret =
unsafe { SetFilePointer(hFile, lDistanceToMove, Some(lpDistanceToMoveHigh), dwMoveMethod) };
debug_println!(
"SetFilePointer({:#X}, {:#X}, {:?}, {}) = {:#X}",
"SetFilePointer({:p}, {:#X}, {:?}, {}) = {:#X}",
hFile.0,
lDistanceToMove,
lpDistanceToMoveHigh,
@ -450,6 +740,120 @@ extern "stdcall" fn hook_SetFilePointer(
/// `IsDBCSLeadByte` hook. This normally uses the system codepage, override with Shift JIS behavior.
extern "stdcall" fn hook_IsDBCSLeadByte(TestChar: u8) -> BOOL { (TestChar & 0x80 != 0).into() }
fn slice_of<T>(ptr: *const T, len: i32) -> &'static [T]
where T: Copy + Zero {
if ptr.is_null() {
return &[];
}
if len < 0 {
// Null terminated
let mut len = 0;
while !unsafe { *ptr.offset(len) }.is_zero() {
len += 1;
}
unsafe { std::slice::from_raw_parts(ptr, len as usize) }
} else {
unsafe { std::slice::from_raw_parts(ptr, len as usize) }
}
}
fn slice_of_mut<T>(ptr: *mut T, len: i32) -> Option<&'static mut [T]>
where T: Copy + Zero {
if ptr.is_null() || len < 0 {
return None;
}
Some(unsafe { std::slice::from_raw_parts_mut(ptr, len as usize) })
}
/// `MultiByteToWideChar` hook. This reimplements the conversion for Shift JIS, using the pre-XP
/// behavior of failing on illegal code points. MWCC 3.0 relies on this behavior.
/// See https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
extern "stdcall" fn hook_MultiByteToWideChar(
CodePage: u32,
dwFlags: MULTI_BYTE_TO_WIDE_CHAR_FLAGS,
lpMultiByteStr: PCSTR,
cbMultiByte: i32,
lpWideCharStr: *mut u16,
cchWideChar: i32,
) -> i32 {
let mb_str = slice_of(lpMultiByteStr.as_ptr(), cbMultiByte);
let mut wide_str = slice_of_mut(lpWideCharStr, cchWideChar);
let decoder = match CodePage {
0 => UTF_8,
932 => SHIFT_JIS,
_ => {
// Try to pass through
let ret =
unsafe { MultiByteToWideChar(CodePage, dwFlags, mb_str, wide_str.as_deref_mut()) };
debug_println!(
"MultiByteToWideChar({}, {:#X}, {:?} ({:X?}), {:?}) = {} ({:?})",
CodePage,
dwFlags.0,
mb_str,
lpMultiByteStr,
wide_str,
ret,
unsafe { GetLastError() }
);
return ret;
}
};
let (decoded, _, err) = decoder.decode(mb_str);
let ret = if err {
unsafe { SetLastError(ERROR_NO_UNICODE_TRANSLATION) };
0
} else {
match wide_str.as_deref_mut() {
None => {
unsafe { SetLastError(ERROR_SUCCESS) };
decoded.encode_utf16().count() as i32
}
Some(out) => {
let mut out = out.iter_mut();
let mut written = 0;
for mut c in decoded.encode_utf16() {
if c == 0xFFFD && CodePage == 932 {
// CP-932 replacement character
c = 0x30FB;
}
if let Some(out) = out.next() {
*out = c;
written += 1;
} else {
// Insufficient buffer
written = -1;
break;
}
}
if written < 0 {
unsafe { SetLastError(ERROR_INSUFFICIENT_BUFFER) };
decoded.encode_utf16().count() as i32
} else {
unsafe { SetLastError(ERROR_SUCCESS) };
written
}
}
}
};
debug_println!(
"OVERRIDE MultiByteToWideChar({}, {:#X}, {:?} ({:X?}), {:?}) = {} ({:?})",
CodePage,
dwFlags.0,
decoded,
mb_str,
wide_str,
ret,
unsafe { GetLastError() }
);
ret
}
/// `GetACP` hook. Return the Shift JIS codepage.
extern "stdcall" fn hook_GetACP() -> u32 {
debug_println!("OVERRIDE GetACP() = 932");
932
}
/// `GetModuleFileNameA` hook. Return the absolute path of the executable.
extern "stdcall" fn hook_GetModuleFileNameA(
hModule: HMODULE,
@ -472,7 +876,7 @@ extern "stdcall" fn hook_GetModuleFileNameA(
let slice = unsafe { std::slice::from_raw_parts(lpFilename as *const u8, ret as usize) };
let str = unsafe { std::str::from_utf8_unchecked(slice) };
eprintln!(
"OVERRIDE GetModuleFileNameA({:#X}, {:?}, {:#X}) = {:#X} ({})",
"OVERRIDE GetModuleFileNameA({:p}, {:?}, {:#X}) = {:#X} ({})",
hModule.0, lpFilename, nSize, ret, str
);
}