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:
parent
b85eab4cc8
commit
9120080eb5
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -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",
|
||||
|
|
500
src/main.rs
500
src/main.rs
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue