# MangaMeeya「♥入りのファイルが開けない」修正指示書（AI実行用）

## 人間へ：このファイルの使い方

MangaMeeya で、ファイル名に `♥` `💛` `绝` などが含まれると「ファイルが見つからない」となる問題を直します。

1. Claude Code（または同等のファイル操作＋コマンド実行ができるAI）を開く
2. このファイルをそのまま渡して「この指示書に従って修正して」と伝える
3. AIが途中で質問してきたら答える（1回だけ聞かれます）

**所要時間の目安：10分。** 元に戻す手順も入っているので、気に入らなければ戻せます。

必要なもの：Windows 10 バージョン1903以降 または Windows 11 / PowerShell / Resource Hacker（AIが自動で入れます）

---

# ここから下はAIへの指示

あなたはこれから、ユーザーのPC上にある MangaMeeya を修正します。以下を**順番通りに**実行してください。
各手順には検証があります。**検証に失敗したら次に進まず、ユーザーに報告して指示を仰いでください。**

## 0. 前提知識（読み飛ばさないこと）

この問題の原因は次の通りです。すでに調査済みなので、再調査しないでください。

- MangaMeeya は2006年ビルドの32bitアプリで、ファイル関連のWin32 APIを**全てANSI版（`CreateFileA` 等）**で呼んでいる。Unicode版（`〜W`）は1つも使っていない。
- ANSI版APIはファイル名をシステムのANSIコードページに変換する。日本語環境では **CP932（Shift-JIS）**。
- CP932 に `♥`(U+2665) は**存在しない**。変換すると `0x3F`（`?`）になる。だからアプリは `だいすき?.jpg` を探して失敗する。
- `★` `☆` `♪` `～` は CP932 に存在するので問題なく開ける。**記号全般がダメなのではない。**
- 同じ理由で、絵文字・簡体字（`绝` `铁` `图` 等）・macOS由来の分解濁点（`ト`+`゙`）も開けない。

**修正方針：** exe に `activeCodePage = UTF-8` のマニフェストを埋め込み、そのプロセスだけANSI APIをUTF-8で動作させる。ファイル名は一切変更しない。

---

## 1. 環境の確認

### 1-1. MangaMeeya の場所を特定する

ユーザーに MangaMeeya のフォルダを尋ねるか、次で探してください。

```powershell
Get-ChildItem -Path C:\, D:\ -Filter "MangaMeeya*.exe" -Recurse -Force -ErrorAction SilentlyContinue |
  Select-Object FullName, Length, LastWriteTime
```

見つかったフォルダを以降 `$dir` とします。同じフォルダに複数の exe がある構成が一般的です
（例：`MangaMeeya7.exe` = 英語版、`MangaMeeya-JP.exe` = 日本語版）。

### 1-2. 本当にANSI専用か確認する

```powershell
$dir = "<MangaMeeyaのフォルダ>"
Get-ChildItem -LiteralPath $dir -Filter "MangaMeeya*.exe" | ForEach-Object {
    $s = [System.Text.Encoding]::ASCII.GetString([System.IO.File]::ReadAllBytes($_.FullName))
    $a = ("CreateFileA","FindFirstFileA","GetFileAttributesA" | ForEach-Object { ([regex]::Matches($s,$_)).Count } | Measure-Object -Sum).Sum
    $w = ("CreateFileW","FindFirstFileW","GetFileAttributesW" | ForEach-Object { ([regex]::Matches($s,$_)).Count } | Measure-Object -Sum).Sum
    "{0,-28} ANSI={1} Unicode={2}" -f $_.Name, $a, $w
}
```

**検証：** ANSI が3前後、Unicode が **0** であること。
Unicode が1以上なら、この指示書の対象外です。ユーザーに報告して停止してください。

### 1-3. システムロケールを確認する

```powershell
"ACP = " + (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage').ACP
```

**検証：** `932` であること。
`65001` の場合は既にUTF-8ロケールなので、この問題は起きていないはずです。ユーザーに確認してください。

> **注意：** `[System.Text.Encoding]::Default` は PowerShell 7 では常に UTF-8 を返します。ロケール判定に使わないでください。

### 1-4. Resource Hacker を用意する

```powershell
if (-not (Test-Path "C:\Program Files (x86)\Resource Hacker\ResourceHacker.exe")) {
    winget install --id AngusJohnson.ResourceHacker --accept-source-agreements --accept-package-agreements
}
Test-Path "C:\Program Files (x86)\Resource Hacker\ResourceHacker.exe"
```

**検証：** `True` が返ること。

### 1-5. どの exe を修正するか決める

各 exe について、コードに埋め込まれた日本語文字列の数を数えます。これが多い exe を UTF-8 化すると **UIが化けます**。

```powershell
$dir = "<MangaMeeyaのフォルダ>"
$sj = [System.Text.Encoding]::GetEncoding(932)
$words = "ファイル","設定","画像","表示","エラー","ページ","保存"
Get-ChildItem -LiteralPath $dir -Filter "MangaMeeya*.exe" | ForEach-Object {
    $b = [System.IO.File]::ReadAllBytes($_.FullName)
    $pe = [BitConverter]::ToInt32($b,0x3C)
    $n = [BitConverter]::ToUInt16($b,$pe+6); $o = [BitConverter]::ToUInt16($b,$pe+20)
    $t = $pe+24+$o
    $secs = for ($i=0; $i -lt $n; $i++) { $s=$t+$i*40
        [pscustomobject]@{ Name=[System.Text.Encoding]::ASCII.GetString($b,$s,8).Trim([char]0)
                           Start=[BitConverter]::ToInt32($b,$s+20); Size=[BitConverter]::ToInt32($b,$s+16) } }
    $lat = [System.Text.Encoding]::GetEncoding(28591).GetString($b)
    $out = 0
    foreach ($w in $words) {
        $pat = [System.Text.Encoding]::GetEncoding(28591).GetString($sj.GetBytes($w))
        foreach ($m in [regex]::Matches($lat,[regex]::Escape($pat))) {
            $sec = $secs | Where-Object { $m.Index -ge $_.Start -and $m.Index -lt ($_.Start+$_.Size) } | Select-Object -First 1
            if ($sec -and $sec.Name -ne '.rsrc') { $out++ }
        }
    }
    "{0,-28} コード埋め込み日本語文字列 = {1}" -f $_.Name, $out
}
```

**判定基準：**

| 結果 | 意味 | 対応 |
|---|---|---|
| 50未満 | 英語版ビルド | **この exe を修正対象にする** |
| 100以上 | 日本語版ビルド | 単独では修正しない。手順6（応用）を見る |

---

## 2. ユーザーへの確認【必須・ここで必ず止まること】

修正には**代償**があります。実行前に必ずユーザーに以下を伝え、承諾を得てください。

> この修正を当てると、**zip書庫の中の日本語ファイル名が文字化けします。**
>
> 日本語圏で作られた zip は、内部のエントリ名を Shift-JIS で格納していることが多く（UTF-8フラグが立っていない）、
> UTF-8モードのアプリはそれを誤って解釈するためです。MangaMeeya同梱の `arc.dll` は2006年製で、
> zip の Unicode拡張フィールド（0x7075）を読まないため、回避できません。
>
> - **画像ファイルやフォルダを直接開く**使い方が中心 → 修正する価値が高い
> - **日本語名の入った zip を大量に読む**使い方が中心 → 化けが目立つので、やめた方がいいかもしれない
>
> どちらにしますか？

**ユーザーが「やめる」と答えたら、何も変更せずに終了してください。**
代わりに「ファイル名の方を CP932 で表現できる文字に変える」という選択肢がある、とだけ伝えてください。

---

## 3. バックアップ

**MangaMeeya が起動していたら、必ず先に終了してもらってください。**
起動中に設定ファイルを触ると、終了時に上書きされて作業が無駄になります。

```powershell
$dir = "<MangaMeeyaのフォルダ>"
$target = "MangaMeeya7.exe"   # 手順1-5 で決めた exe 名

if (Get-Process | Where-Object { $_.ProcessName -like "MangaMeeya*" }) {
    Write-Host "!! MangaMeeya が起動中です。終了してもらってください。" -ForegroundColor Red
} else {
    Copy-Item -LiteralPath "$dir\$target" -Destination "$dir\$target.orig-backup" -Force
    Get-ChildItem -LiteralPath $dir -Filter "*.ini","*.lst" | ForEach-Object {
        Copy-Item -LiteralPath $_.FullName -Destination "$($_.FullName).orig-backup" -Force
    }
    "バックアップ完了"
    Get-ChildItem -LiteralPath $dir -Filter "*.orig-backup" | Select-Object Name, Length
}
```

**検証：** `$target.orig-backup` が作成され、元と同じサイズであること。

---

## 4. マニフェストの埋め込み

### 4-1. 現状のマニフェストを確認する

```powershell
Add-Type -Namespace MM -Name N -MemberDefinition @'
[DllImport("kernel32", CharSet=CharSet.Unicode)] public static extern IntPtr LoadLibraryExW(string f, IntPtr h, uint fl);
[DllImport("kernel32")] public static extern IntPtr FindResourceW(IntPtr h, IntPtr n, IntPtr t);
[DllImport("kernel32")] public static extern IntPtr LoadResource(IntPtr h, IntPtr r);
[DllImport("kernel32")] public static extern IntPtr LockResource(IntPtr d);
[DllImport("kernel32")] public static extern uint SizeofResource(IntPtr h, IntPtr r);
[DllImport("kernel32")] public static extern bool FreeLibrary(IntPtr h);
'@

function Get-Manifest([string]$path) {
    $h = [MM.N]::LoadLibraryExW($path, [IntPtr]::Zero, 0x2)
    $r = [MM.N]::FindResourceW($h, [IntPtr]1, [IntPtr]24)
    if ($r -eq [IntPtr]::Zero) { [MM.N]::FreeLibrary($h) | Out-Null; return $null }
    $sz = [MM.N]::SizeofResource($h, $r)
    $buf = New-Object 'byte[]' ([int]$sz)
    [System.Runtime.InteropServices.Marshal]::Copy([MM.N]::LockResource([MM.N]::LoadResource($h,$r)), $buf, 0, [int]$sz)
    [MM.N]::FreeLibrary($h) | Out-Null
    [pscustomobject]@{ Size = $sz; Xml = [System.Text.Encoding]::UTF8.GetString($buf) }
}

Get-Manifest "$dir\$target"
```

**想定される結果：** サイズ `86` 前後の、中身が空のスタブが入っています。

```xml
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
</assembly>
```

> **重要：** ここに何か入っている＝**外部の `.exe.manifest` ファイルを置く方法は効きません。**
> Windows は埋め込みマニフェストがある場合、外部ファイルを無視します。必ず埋め込んでください。

### 4-2. マニフェストを作る

```powershell
$sp = "$env:TEMP\mm-fix"
New-Item -ItemType Directory -Force -Path $sp | Out-Null
@'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32" name="MangaMeeya" version="7.4.2.1"/>
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
    </windowsSettings>
  </application>
</assembly>
'@ | Set-Content -LiteralPath "$sp\utf8.manifest" -Encoding UTF8
Get-Content -LiteralPath "$sp\utf8.manifest"
```

### 4-3. 作業コピーに適用する

**いきなり本体を書き換えないでください。** 必ずコピーで作業します。

```powershell
$rh = "C:\Program Files (x86)\Resource Hacker\ResourceHacker.exe"
Copy-Item -LiteralPath "$dir\$target" -Destination "$sp\work.exe" -Force
& $rh -open "$sp\work.exe" -save "$sp\out.exe" -action addoverwrite `
      -res "$sp\utf8.manifest" -mask MANIFEST,1,1033 -log "$sp\rh.log"
```

> **Resource Hacker の癖：** 終了コードが `1` になったり、直後にログや出力ファイルが見えないことがあります。
> **これは失敗ではありません。** 別コマンドとして少し後に確認すれば生成されています。
> エラーに見えても即座にリトライせず、まず下の検証をしてください。

### 4-4. 検証（重要）

```powershell
Get-Content -LiteralPath "$sp\rh.log" | Select-Object -Last 3   # "Success!" が出るはず
(Get-Manifest "$sp\out.exe").Size                               # 440 前後になるはず
(Get-Manifest "$sp\out.exe").Xml                                # activeCodePage が含まれるはず
```

**検証基準：**
- ログに `Success!` が出ている（UTF-16で書かれているので1文字ずつ空きがあるように見えます）
- マニフェストのサイズが `86` → `440` 前後に増えている
- XML に `activeCodePage` と `UTF-8` の文字列がある

さらに、**リソースが失われていないか**を照合してください。Resource Hacker は `.rsrc` を再構築するため、
アイコンやダイアログが消える事故がありえます。

```powershell
Add-Type -Namespace MR -Name N -MemberDefinition @'
[DllImport("kernel32", CharSet=CharSet.Unicode)] public static extern IntPtr LoadLibraryExW(string f, IntPtr h, uint fl);
[DllImport("kernel32")] public static extern bool EnumResourceTypesW(IntPtr h, ERT cb, IntPtr p);
[DllImport("kernel32")] public static extern bool EnumResourceNamesW(IntPtr h, IntPtr t, ERN cb, IntPtr p);
[DllImport("kernel32")] public static extern bool FreeLibrary(IntPtr h);
public delegate bool ERT(IntPtr h, IntPtr t, IntPtr p);
public delegate bool ERN(IntPtr h, IntPtr t, IntPtr n, IntPtr p);
'@
function Get-ResCount([string]$path) {
    $h = [MR.N]::LoadLibraryExW($path, [IntPtr]::Zero, 0x2)
    $types = New-Object System.Collections.ArrayList
    [void][MR.N]::EnumResourceTypesW($h, [MR.N+ERT]{ param($a,$t,$c) [void]$types.Add($t); $true }, [IntPtr]::Zero)
    $res = [ordered]@{}
    foreach ($t in $types) {
        $script:c = 0
        [void][MR.N]::EnumResourceNamesW($h, $t, [MR.N+ERN]{ param($a,$b,$d,$e) $script:c++; $true }, [IntPtr]::Zero)
        $res["type $([int64]$t)"] = $script:c
    }
    [void][MR.N]::FreeLibrary($h); $res
}
$before = Get-ResCount "$sp\work.exe"
$after  = Get-ResCount "$sp\out.exe"
foreach ($k in (($before.Keys + $after.Keys) | Select-Object -Unique | Sort-Object)) {
    "{0,-12} 元={1,4} 後={2,4} {3}" -f $k, $before[$k], $after[$k], $(if ($before[$k] -eq $after[$k]) { "OK" } else { "!! 不一致" })
}
```

**検証基準：** 全ての行が `OK`。1つでも不一致なら**先に進まず**ユーザーに報告してください。

### 4-5. 本体に差し替える

```powershell
if (Get-Process | Where-Object { $_.ProcessName -like "MangaMeeya*" }) {
    Write-Host "!! 起動中です。終了してもらってください。" -ForegroundColor Red
} else {
    [System.IO.File]::WriteAllBytes("$dir\$target", [System.IO.File]::ReadAllBytes("$sp\out.exe"))
    (Get-Item -LiteralPath "$dir\$target").LastWriteTime = Get-Date
    (Get-Manifest "$dir\$target").Size
}
```

**検証：** `440` 前後が返ること。

> タイムスタンプを更新するのは、Windows のサイドバイサイド機構が古いマニフェストをキャッシュしたままになるのを避けるためです。省略しないでください。

---

## 5. 履歴・設定ファイルの正規化

MangaMeeya は履歴（`Hist.lst`）などをテキストで保存します。UTF-8モードになると新しい行はUTF-8で書かれますが、
**過去の行はShift-JISのまま**なので、1つのファイルに2種類の文字コードが混在します。

これを放置すると「**今まで普通に開けていたファイルが、履歴から開くと文字化けする**」という現象が起き、
原因が分かりにくくなります。必ず揃えてください。

```powershell
$sj  = [System.Text.Encoding]::GetEncoding(932)
$u8o = New-Object System.Text.UTF8Encoding($false)
$u8s = [System.Text.Encoding]::GetEncoding('utf-8',
        [System.Text.EncoderFallback]::ExceptionFallback,
        [System.Text.DecoderFallback]::ExceptionFallback)

Get-ChildItem -LiteralPath $dir -Filter "*.lst" | Where-Object { $_.Length -gt 0 } | ForEach-Object {
    $bytes = [System.IO.File]::ReadAllBytes($_.FullName)
    $lines = New-Object System.Collections.ArrayList
    $cur   = New-Object System.Collections.ArrayList
    foreach ($x in $bytes) {
        if ($x -eq 0x0A) { [void]$lines.Add($cur.ToArray()); $cur.Clear() }
        elseif ($x -ne 0x0D) { [void]$cur.Add($x) }
    }
    if ($cur.Count) { [void]$lines.Add($cur.ToArray()) }

    $out  = New-Object System.Collections.ArrayList
    $seen = New-Object 'System.Collections.Generic.HashSet[string]'
    $conv = 0; $dedup = 0
    foreach ($lb in $lines) {
        if ($lb.Count -eq 0) { continue }
        if (-not ($lb | Where-Object { $_ -gt 0x7F })) {
            $text = [System.Text.Encoding]::ASCII.GetString([byte[]]$lb)
        } else {
            try   { $text = $u8s.GetString([byte[]]$lb) }          # 既にUTF-8
            catch { $text = $sj.GetString([byte[]]$lb); $conv++ }  # CP932だったので変換
        }
        $key = ([regex]::Match($text,'^"([^"]+)"')).Groups[1].Value
        if ($key -and -not $seen.Add($key)) { $dedup++; continue } # 重複エントリを削除
        [void]$out.Add($text)
    }
    [System.IO.File]::WriteAllLines($_.FullName, $out.ToArray(), $u8o)
    "{0,-20} UTF-8へ変換={1} 重複削除={2} -> {3}行" -f $_.Name, $conv, $dedup, $out.Count
}
```

---

## 6. 動作確認とユーザーへの報告

ユーザーに MangaMeeya を起動してもらい、次を確認してください。

1. **`♥` を含むファイル／フォルダが開けるか** ← これが本題
2. **メニューやダイアログが化けていないか**（英語版ビルドなら化けないはず）
3. **zip書庫内の日本語ファイル名**（ここは化けます。手順2で説明済みのはず）

完了報告には次を含めてください。

- 修正した exe の名前とパス
- バックアップの場所（`*.orig-backup`）
- 元に戻す手順（付録A）
- **`*.orig-backup` を消さないよう伝える**

---

## 付録A：元に戻す手順

```powershell
$dir = "<MangaMeeyaのフォルダ>"
$target = "MangaMeeya7.exe"
Copy-Item -LiteralPath "$dir\$target.orig-backup" -Destination "$dir\$target" -Force
Get-ChildItem -LiteralPath $dir -Filter "*.lst.orig-backup" | ForEach-Object {
    Copy-Item -LiteralPath $_.FullName -Destination ($_.FullName -replace '\.orig-backup$','') -Force
}
"元に戻しました"
```

---

## 付録B：試してはいけないこと【重要】

以下は**すべて実際に試して失敗が確認済み**です。時間の無駄なので実行しないでください。

### ❌ 外部マニフェストファイル（`MangaMeeya7.exe.manifest`）を置く

exe に既に埋め込みマニフェスト（中身が空のスタブ）があるため、**Windows は外部ファイルを無視します。**
「埋め込みマニフェストが無いように見える」場合も要注意です。空のスタブには `assemblyIdentity` の文字列が
含まれないため、バイト列検索では見つかりません。必ず `FindResource` API で確認してください。

### ❌ フォントの `lfCharSet` を `DEFAULT_CHARSET(1)` に変える

ini の `[Text]` セクションにある `LogFont`（Base64のLOGFONT構造体）の charset を変えても、
下部のファイル名バーの文字化けは**直りません。むしろ全ファイルで化けるようになり悪化しました。**

理由：GDI の `TextOutA` / `DrawTextA` は、プロセスのANSIコードページではなく
**選択中フォントのcharsetでコードページを決定します**。ここをUTF-8にする手段はありません。
バーの表示が邪魔なら、ini の `[Config]` の `DispTextInfo` を `0` にして非表示にしてください
（同じ情報はタイトルバーにも表示されます）。

### ❌ 日本語版（`MangaMeeya-JP.exe`）に単独でマニフェストを当てる

技術的には成功しますが、**UIが崩れて実用になりません。**
リソース（UTF-16格納）由来のメニューやラベルは正常ですが、コードに直接埋め込まれた
Shift-JIS文字列（ウィンドウタイトル、設定ダイアログのタブ名、エラーメッセージ）が化けます。
検証では英語版36個に対し日本語版は270個ありました。日本語UIが必要なら付録Cを見てください。

### ❌ 埋め込みShift-JIS文字列をUTF-8に書き換える

不可能です。UTF-8では日本語1文字が3バイト、Shift-JISは2バイトなので、
元の領域に収まりません。270箇所すべてで領域超過します。

### ❌ 動作が重くなったと感じても、マニフェストを疑う

UTF-8変換はファイルを開くたびに1回、マイクロ秒オーダーです。体感差は出ません。
書き換え直後は Windows Defender が署名なしexeを再スキャンし、プリフェッチ情報も破棄されるため、
数回起動するまで遅く感じることがあります。比較するなら、**元のexeも同じタイミングで書き直して**
Defenderの扱いを揃えた上で行ってください。

---

## 付録C：日本語UIを残したまま修正する（英語版と日本語版が両方ある場合のみ）

`MangaMeeya7.exe`（英語）と `MangaMeeya-JP.exe`（日本語）が同梱されている配布物なら、
**英語版のコードに日本語版のリソースを移植**することで、日本語メニューを保ったまま修正できます。

### C-1. 成立条件を確認する

2つの exe が同じビルドから作られている必要があります。

```powershell
$en = [System.IO.File]::ReadAllBytes("$dir\MangaMeeya7.exe.orig-backup")
$jp = [System.IO.File]::ReadAllBytes("$dir\MangaMeeya-JP.exe")
function Get-Secs($b) {
    $pe = [BitConverter]::ToInt32($b,0x3C)
    $n = [BitConverter]::ToUInt16($b,$pe+6); $o = [BitConverter]::ToUInt16($b,$pe+20); $t = $pe+24+$o
    for ($i=0; $i -lt $n; $i++) { $s=$t+$i*40
        [pscustomobject]@{ Name=[System.Text.Encoding]::ASCII.GetString($b,$s,8).Trim([char]0)
                           Start=[BitConverter]::ToInt32($b,$s+20); Size=[BitConverter]::ToInt32($b,$s+16) } }
}
$se = Get-Secs $en; $sj2 = Get-Secs $jp
foreach ($name in '.text','.rdata','.data') {
    $a = $se | Where-Object Name -eq $name; $b2 = $sj2 | Where-Object Name -eq $name
    if ($a.Start -ne $b2.Start -or $a.Size -ne $b2.Size) { "$name : 配置が異なる → 移植不可"; continue }
    $diff = 0
    for ($i=0; $i -lt $a.Size; $i++) { if ($en[$a.Start+$i] -ne $jp[$b2.Start+$i]) { $diff++ } }
    "{0,-8} 差分 {1} バイト" -f $name, $diff
}
```

**判定基準：**
- `.text` の差分が **0** → 移植可能。コードが完全に同一で、文字列だけが違うパッチ品です
- `.text` に差分がある → **移植しないでください。** 別ビルドなので壊れます

`.rdata` に差分があるのは正常です（文字列リテラルの置き場所）。

### C-2. 移植する

```powershell
$rh = "C:\Program Files (x86)\Resource Hacker\ResourceHacker.exe"

# 1. 日本語版から全リソースを抽出（-mask の "," は必ず引用符で囲むこと）
& $rh -open "$dir\MangaMeeya-JP.exe" -save "$sp\jp-all.res" -action extract -mask ",," -log "$sp\ex.log"

# 2. 英語版のコードに上書き
& $rh -open "$dir\MangaMeeya7.exe.orig-backup" -save "$sp\hyb1.exe" -action addoverwrite -res "$sp\jp-all.res" -log "$sp\h1.log"

# 3. UTF-8マニフェストを再適用（手順2で日本語版の空スタブに戻ってしまうため）
& $rh -open "$sp\hyb1.exe" -save "$sp\hybrid.exe" -action addoverwrite -res "$sp\utf8.manifest" -mask MANIFEST,1,1033 -log "$sp\h2.log"
```

> **順序が重要：** 手順2でリソースを丸ごと上書きすると、マニフェストも日本語版のもの（空スタブ）で
> 置き換わります。必ず**最後に**マニフェストを当て直してください。

### C-3. 検証

```powershell
(Get-Manifest "$sp\hybrid.exe").Size    # 440 前後
# コードが英語版と一致しているか
$hy = [System.IO.File]::ReadAllBytes("$sp\hybrid.exe")
$sh = Get-Secs $hy
foreach ($name in '.text','.rdata','.data') {
    $a = $se | Where-Object Name -eq $name; $b2 = $sh | Where-Object Name -eq $name
    $diff = 0
    for ($i=0; $i -lt $a.Size; $i++) { if ($en[$a.Start+$i] -ne $hy[$b2.Start+$i]) { $diff++ } }
    "{0,-8} 英語版との差分 {1} バイト" -f $name, $diff
}
# 日本語メニュー文字列が入ったか
$lat = [System.Text.Encoding]::GetEncoding(28591).GetString($hy)
foreach ($w in "ファイル","表示","ツール") {
    $pat = [System.Text.Encoding]::GetEncoding(28591).GetString([System.Text.Encoding]::Unicode.GetBytes($w))
    "{0} = {1} 箇所" -f $w, ([regex]::Matches($lat,[regex]::Escape($pat))).Count
}
```

**検証基準：**
- マニフェスト `440` 前後
- `.text` / `.rdata` / `.data` の英語版との差分が**すべて 0**
- 日本語メニュー文字列が**1箇所以上**検出される

### C-4. 設置

**元の exe を上書きせず、別名で置いてください。** ユーザーが比較できます。

```powershell
[System.IO.File]::WriteAllBytes("$dir\MangaMeeya-JPUI.exe", [System.IO.File]::ReadAllBytes("$sp\hybrid.exe"))
```

### C-5. 期待される表示

| 部分 | 由来 | 結果 |
|---|---|---|
| メニュー（ファイル/表示/ツール…） | リソース | 日本語 |
| ダイアログのラベル・ボタン | リソース | 日本語 |
| ウィンドウタイトル | コード埋め込み | 英語（化けない） |
| 設定ダイアログのタブ名 | コード埋め込み | 英語（化けない） |
| エラーメッセージ | コード埋め込み | 英語 |

---

## 付録D：どうしても zip の化けが許せない場合

UTF-8化を諦め、**ファイル名の方をCP932で表現できる形に変える**方法があります。
この場合、exe は一切改変しません（付録Aで元に戻してから実施）。

ただし**単純に削除するのは避けてください。** `崩坏星穹铁道` から簡体字を消すと `崩星穹道` になり意味が壊れます。
次の3段階で処理してください。

1. **NFC正規化** — `$name.Normalize([System.Text.NormalizationForm]::FormC)`。
   分解された濁点（`ト`+`゙` → `ド`）が直ります。**ここで直る分は完全に無損失**なので必ず最初に行うこと
2. **簡体字を日本語漢字に変換** — `绝→絶` `铁→鉄` `图→図` `发→発` `电→電` `军→軍` `鸣→鳴` `见→見` `爱→愛` など対応表を当てる
3. **残りを置換** — 絵文字や対応漢字のない文字だけを削除、または `_` に

実装時の注意：

- **フォルダ名も対象。深い階層から処理**すること（親を先に変えると子のパスが無効になる）
- 絵文字はサロゲートペアなので `ToCharArray()` ではなく `[System.Globalization.StringInfo]::GetTextElementEnumerator()` で分割する
- 壊れた孤立サロゲートがあると `Normalize()` が例外を投げる。先に除去すること
- リネーム後の名前衝突を回避する処理を入れること
- **必ず変更前に一覧を表示してユーザーに確認を取ること**

---

## 検証済み環境

- Windows 11 Pro 26200 / 日本語ロケール（ACP=932）
- MangaMeeya 7.4 Beta Rev2.01a NeoJam Version（2006年6月ビルド・32bit・ANSI専用）
- Resource Hacker 5.2.8

MangaMeeya のバージョンが違っても、ANSI専用（手順1-2で確認）であれば同じ手順が使えます。
