param(
    [ValidateSet("Install", "Uninstall", "Verify", "Launch")]
    [string]$Action = "Install",
    [string]$GamePath = "",
    [switch]$Elevated,
    [switch]$NoElevation,
    [switch]$TestMode
)

$ErrorActionPreference = "Stop"
$PatchVersion = "1.0.0"
$SupportedGameVersion = "Patch 2.22 v7.0.3"
$OriginalPatchHash = "9384650A070297EFA7D706889F4090652858A94964D50DB548974F181DC17492"
$OriginalEnglishHash = "0715109A54E4202C3D97DCD4FCEDF880C6D5453FD8D6F4522D3A20C06F4475D4"
$OverlayPayloadHash = "7331629AE4AC48547E83F17FD4F74DF32D586733E8C72074111B6B28FD02D9C6"
$CsfPayloadHash = "9C1D0C6445A05BA16C7327AEFD92C39015310DA02B928A270661FE0C057B746C"
$LanguagePayloadHash = "E578672120B1AA5462505F8A5D26EA3E6EE21BC916AA8414F3A8B5A5744FE206"
$FontSubstitutionPayloadHash = "9BAE1F8B763E4ACD58A0AD0B777319652B36B3ED7D6E0781F5B2CD8526B3600A"
$MarkerName = ".bfme1-korean-222-v1"
$OverlayName = "_zz_korean_222.big"

function Test-Administrator {
    $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($identity)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Invoke-ElevatedSelf {
    $arguments = @(
        "-NoProfile",
        "-ExecutionPolicy", "Bypass",
        "-File", "`"$PSCommandPath`"",
        "-Action", $Action,
        "-Elevated"
    )
    if ($GamePath) {
        $arguments += @("-GamePath", "`"$GamePath`"")
    }
    $process = Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Verb RunAs -Wait -PassThru
    exit $process.ExitCode
}

if (-not $NoElevation -and $Action -in @("Install", "Uninstall") -and -not (Test-Administrator)) {
    Invoke-ElevatedSelf
}

function Get-Sha256([string]$Path) {
    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        return $null
    }
    return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash
}

function Assert-FileHash([string]$Path, [string]$ExpectedHash, [string]$Description) {
    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        throw "$Description 파일이 없습니다: $Path"
    }
    $actual = Get-Sha256 $Path
    if ($actual -ne $ExpectedHash) {
        throw "$Description 검증 실패.`n예상: $ExpectedHash`n실제: $actual`n파일: $Path"
    }
}

function Test-GameRoot([string]$Path) {
    if (-not $Path) {
        return $false
    }
    return (
        (Test-Path -LiteralPath (Join-Path $Path "lotrbfme.exe") -PathType Leaf) -and
        (Test-Path -LiteralPath (Join-Path $Path "_patch222.big") -PathType Leaf) -and
        (Test-Path -LiteralPath (Join-Path $Path "lang\english.big") -PathType Leaf)
    )
}

function Get-RegistryInstallPaths {
    $paths = New-Object System.Collections.Generic.List[string]
    $base = $null
    try {
        $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey(
            [Microsoft.Win32.RegistryHive]::LocalMachine,
            [Microsoft.Win32.RegistryView]::Registry32
        )
        foreach ($subPath in @(
            "SOFTWARE\Electronic Arts\EA Games\The Battle for Middle-earth",
            "SOFTWARE\EA GAMES\The Battle for Middle-earth"
        )) {
            $key = $base.OpenSubKey($subPath, $false)
            if ($key) {
                try {
                    $value = [string]$key.GetValue("InstallPath", "")
                    if ($value) {
                        $paths.Add($value)
                    }
                }
                finally {
                    $key.Dispose()
                }
            }
        }
    }
    finally {
        if ($base) {
            $base.Dispose()
        }
    }
    return $paths
}

function Resolve-GameRoot {
    $candidates = New-Object System.Collections.Generic.List[string]
    if ($GamePath) {
        $candidates.Add($GamePath.Trim('"'))
    }
    foreach ($path in (Get-RegistryInstallPaths)) {
        $candidates.Add($path)
    }
    $candidates.Add("C:\BFME1")

    foreach ($candidate in ($candidates | Select-Object -Unique)) {
        if (Test-GameRoot $candidate) {
            return (Resolve-Path -LiteralPath $candidate).Path.TrimEnd("\")
        }
    }
    throw @"
BFME1 Patch 2.22 설치 폴더를 찾지 못했습니다.
All in One BFME Launcher에서 BFME1과 Patch 2.22 v7.0.3을 먼저 설치한 뒤 다시 실행하세요.
자동 검색이 안 되면 CMD 파일에 게임 폴더를 끌어다 놓으세요.
"@
}

function Assert-ProcessesClosed {
    if ($TestMode) {
        return
    }
    if (Get-Process -Name "lotrbfme" -ErrorAction SilentlyContinue) {
        throw "BFME1이 실행 중입니다. 게임을 완전히 종료한 뒤 다시 실행하세요."
    }
    if (Get-Process -Name "AllInOneLauncher" -ErrorAction SilentlyContinue) {
        throw "All in One BFME Launcher가 실행 중입니다. 런처를 완전히 종료한 뒤 다시 실행하세요."
    }
}

function Read-U32BE([IO.BinaryReader]$Reader) {
    [byte[]]$bytes = $Reader.ReadBytes(4)
    if ($bytes.Length -ne 4) {
        throw "잘린 BIG 정수 필드입니다."
    }
    if ([BitConverter]::IsLittleEndian) {
        [Array]::Reverse($bytes)
    }
    return [BitConverter]::ToUInt32($bytes, 0)
}

function Write-U32BE([IO.BinaryWriter]$Writer, [uint32]$Value) {
    [byte[]]$bytes = [BitConverter]::GetBytes($Value)
    if ([BitConverter]::IsLittleEndian) {
        [Array]::Reverse($bytes)
    }
    $Writer.Write($bytes)
}

function Get-BigArchiveInfo([string]$Archive) {
    $stream = $null
    $reader = $null
    try {
        $stream = [IO.File]::Open($Archive, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::Read)
        $reader = New-Object IO.BinaryReader($stream, [Text.Encoding]::ASCII)
        [byte[]]$magicBytes = $reader.ReadBytes(4)
        $magic = [Text.Encoding]::ASCII.GetString($magicBytes)
        if ($magic -notin @("BIG4", "BIGF")) {
            throw "지원하지 않는 BIG 형식입니다: $Archive"
        }
        [uint32]$archiveSize = $reader.ReadUInt32()
        [uint32]$entryCount = Read-U32BE $reader
        [uint32]$directorySize = Read-U32BE $reader
        if ([int64]$archiveSize -ne $stream.Length) {
            throw "BIG 크기 필드가 실제 파일 크기와 다릅니다: $Archive"
        }
        if ($directorySize -gt $stream.Length) {
            throw "BIG 디렉터리가 파일 범위를 벗어납니다: $Archive"
        }

        $entries = New-Object System.Collections.Generic.List[object]
        for ($index = 0; $index -lt $entryCount; $index++) {
            [uint32]$offset = Read-U32BE $reader
            [uint32]$size = Read-U32BE $reader
            $nameBytes = New-Object System.Collections.Generic.List[byte]
            while ($true) {
                if ($stream.Position -ge $stream.Length) {
                    throw "BIG 파일 이름이 끝나지 않았습니다: $Archive"
                }
                [byte]$value = $reader.ReadByte()
                if ($value -eq 0) {
                    break
                }
                $nameBytes.Add($value)
            }
            $name = [Text.Encoding]::ASCII.GetString($nameBytes.ToArray())
            if ([int64]$offset + [int64]$size -gt $stream.Length) {
                throw "BIG 항목이 파일 범위를 벗어납니다: $name"
            }
            $entries.Add([PSCustomObject]@{
                Name = $name
                Offset = [int64]$offset
                Size = [int64]$size
            })
        }
        return [PSCustomObject]@{
            Magic = $magic
            Size = [int64]$archiveSize
            DirectorySize = [int64]$directorySize
            Entries = $entries
        }
    }
    finally {
        if ($reader) {
            $reader.Dispose()
        }
        elseif ($stream) {
            $stream.Dispose()
        }
    }
}

function Copy-StreamRange(
    [IO.Stream]$Source,
    [IO.Stream]$Destination,
    [int64]$Offset,
    [int64]$Length
) {
    $Source.Position = $Offset
    [byte[]]$buffer = New-Object byte[] (1024 * 1024)
    [int64]$remaining = $Length
    while ($remaining -gt 0) {
        $wanted = [int][Math]::Min($buffer.Length, $remaining)
        $read = $Source.Read($buffer, 0, $wanted)
        if ($read -le 0) {
            throw "BIG 항목 데이터를 읽는 중 파일 끝에 도달했습니다."
        }
        $Destination.Write($buffer, 0, $read)
        $remaining -= $read
    }
}

function Update-BigArchive(
    [string]$Archive,
    [string]$Output,
    [string[]]$RemoveEntries,
    [hashtable]$ReplacementFiles
) {
    $info = Get-BigArchiveInfo $Archive
    $remove = New-Object System.Collections.Generic.HashSet[string]([StringComparer]::OrdinalIgnoreCase)
    foreach ($name in $RemoveEntries) {
        [void]$remove.Add($name)
    }

    $replacementFound = @{}
    foreach ($name in $ReplacementFiles.Keys) {
        $replacementFound[$name] = $false
        if (-not (Test-Path -LiteralPath $ReplacementFiles[$name] -PathType Leaf)) {
            throw "BIG 교체용 파일이 없습니다: $($ReplacementFiles[$name])"
        }
    }

    $outputEntries = New-Object System.Collections.Generic.List[object]
    foreach ($entry in $info.Entries) {
        if ($remove.Contains($entry.Name)) {
            continue
        }
        if ($ReplacementFiles.ContainsKey($entry.Name)) {
            $sourcePath = [string]$ReplacementFiles[$entry.Name]
            $replacementFound[$entry.Name] = $true
            $outputEntries.Add([PSCustomObject]@{
                Name = $entry.Name
                Size = [int64](Get-Item -LiteralPath $sourcePath).Length
                SourceKind = "File"
                SourcePath = $sourcePath
                SourceOffset = [int64]0
            })
        }
        else {
            $outputEntries.Add([PSCustomObject]@{
                Name = $entry.Name
                Size = [int64]$entry.Size
                SourceKind = "Archive"
                SourcePath = $Archive
                SourceOffset = [int64]$entry.Offset
            })
        }
    }
    foreach ($name in $replacementFound.Keys) {
        if (-not $replacementFound[$name]) {
            $sourcePath = [string]$ReplacementFiles[$name]
            $outputEntries.Add([PSCustomObject]@{
                Name = $name
                Size = [int64](Get-Item -LiteralPath $sourcePath).Length
                SourceKind = "File"
                SourcePath = $sourcePath
                SourceOffset = [int64]0
            })
        }
    }

    [int64]$directorySize = 16
    foreach ($entry in $outputEntries) {
        [byte[]]$nameBytes = [Text.Encoding]::ASCII.GetBytes($entry.Name)
        $directorySize += 8 + $nameBytes.Length + 1
    }
    [int64]$cursor = $directorySize + 1
    foreach ($entry in $outputEntries) {
        $entry | Add-Member -NotePropertyName OutputOffset -NotePropertyValue $cursor
        $cursor += $entry.Size
    }
    if ($cursor -gt [uint32]::MaxValue) {
        throw "BIG 결과 파일이 4GB 한계를 넘습니다."
    }

    $outputStream = $null
    $writer = $null
    $archiveStream = $null
    try {
        $outputStream = [IO.File]::Open($Output, [IO.FileMode]::Create, [IO.FileAccess]::Write, [IO.FileShare]::None)
        $writer = New-Object IO.BinaryWriter($outputStream, [Text.Encoding]::ASCII, $true)
        $writer.Write([Text.Encoding]::ASCII.GetBytes($info.Magic))
        $writer.Write([uint32]$cursor)
        Write-U32BE $writer ([uint32]$outputEntries.Count)
        Write-U32BE $writer ([uint32]$directorySize)
        foreach ($entry in $outputEntries) {
            Write-U32BE $writer ([uint32]$entry.OutputOffset)
            Write-U32BE $writer ([uint32]$entry.Size)
            $writer.Write([Text.Encoding]::ASCII.GetBytes($entry.Name))
            $writer.Write([byte]0)
        }
        $writer.Write([byte]0)
        $writer.Flush()

        $archiveStream = [IO.File]::Open($Archive, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::Read)
        foreach ($entry in $outputEntries) {
            if ($entry.SourceKind -eq "Archive") {
                Copy-StreamRange $archiveStream $outputStream $entry.SourceOffset $entry.Size
            }
            else {
                $replacementStream = $null
                try {
                    $replacementStream = [IO.File]::OpenRead($entry.SourcePath)
                    Copy-StreamRange $replacementStream $outputStream 0 $entry.Size
                }
                finally {
                    if ($replacementStream) {
                        $replacementStream.Dispose()
                    }
                }
            }
        }
        $outputStream.Flush()
        if ($outputStream.Length -ne $cursor) {
            throw "BIG 결과 파일 크기 검증에 실패했습니다."
        }
    }
    finally {
        if ($archiveStream) {
            $archiveStream.Dispose()
        }
        if ($writer) {
            $writer.Dispose()
        }
        if ($outputStream) {
            $outputStream.Dispose()
        }
    }
}

function Get-BigEntry([string]$Archive, [string]$Name) {
    $info = Get-BigArchiveInfo $Archive
    return $info.Entries | Where-Object { $_.Name -ieq $Name } | Select-Object -First 1
}

function Get-BigEntrySha256([string]$Archive, [string]$Name) {
    $entry = Get-BigEntry $Archive $Name
    if (-not $entry) {
        return $null
    }
    if ($entry.Size -gt 32MB) {
        throw "해시 확인 대상 BIG 항목이 예상보다 큽니다: $Name"
    }
    $stream = $null
    try {
        $stream = [IO.File]::OpenRead($Archive)
        $stream.Position = $entry.Offset
        [byte[]]$bytes = New-Object byte[] ([int]$entry.Size)
        $read = 0
        while ($read -lt $bytes.Length) {
            $count = $stream.Read($bytes, $read, $bytes.Length - $read)
            if ($count -le 0) {
                throw "BIG 항목을 읽지 못했습니다: $Name"
            }
            $read += $count
        }
        $sha = [Security.Cryptography.SHA256]::Create()
        try {
            return ([BitConverter]::ToString($sha.ComputeHash($bytes))).Replace("-", "")
        }
        finally {
            $sha.Dispose()
        }
    }
    finally {
        if ($stream) {
            $stream.Dispose()
        }
    }
}

function Get-LanguageState {
    if ($TestMode) {
        return [PSCustomObject]@{ Present = $false; Value = $null }
    }
    $base = $null
    $key = $null
    try {
        $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey(
            [Microsoft.Win32.RegistryHive]::LocalMachine,
            [Microsoft.Win32.RegistryView]::Registry32
        )
        $key = $base.OpenSubKey("SOFTWARE\Electronic Arts\EA Games\The Battle for Middle-earth", $false)
        if (-not $key) {
            throw "BFME1 설치 레지스트리를 찾지 못했습니다."
        }
        $names = $key.GetValueNames()
        $present = $names -contains "Language"
        return [PSCustomObject]@{
            Present = $present
            Value = $key.GetValue("Language", $null)
        }
    }
    finally {
        if ($key) {
            $key.Dispose()
        }
        if ($base) {
            $base.Dispose()
        }
    }
}

function Set-LanguageValue([bool]$Present, $Value) {
    if ($TestMode) {
        return
    }
    $base = $null
    $key = $null
    try {
        $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey(
            [Microsoft.Win32.RegistryHive]::LocalMachine,
            [Microsoft.Win32.RegistryView]::Registry32
        )
        $key = $base.OpenSubKey("SOFTWARE\Electronic Arts\EA Games\The Battle for Middle-earth", $true)
        if (-not $key) {
            throw "BFME1 설치 레지스트리를 쓰기 모드로 열지 못했습니다."
        }
        if ($Present) {
            $key.SetValue("Language", [string]$Value, [Microsoft.Win32.RegistryValueKind]::String)
        }
        else {
            $key.DeleteValue("Language", $false)
        }
    }
    finally {
        if ($key) {
            $key.Dispose()
        }
        if ($base) {
            $base.Dispose()
        }
    }
}

function Write-JsonFile([string]$Path, $Value) {
    $json = $Value | ConvertTo-Json -Depth 6
    $utf8 = New-Object Text.UTF8Encoding($false)
    [IO.File]::WriteAllText($Path, $json, $utf8)
}

function Read-State([string]$Marker) {
    $statePath = Join-Path $Marker "state.json"
    if (-not (Test-Path -LiteralPath $statePath -PathType Leaf)) {
        return $null
    }
    return (Get-Content -LiteralPath $statePath -Raw | ConvertFrom-Json)
}

function Invoke-Install([string]$Root) {
    Assert-ProcessesClosed
    $packageRoot = Split-Path -Parent $PSScriptRoot
    $overlayPayload = Join-Path $packageRoot "payload\_zz_korean_222.big.payload"
    $csfPayload = Join-Path $packageRoot "payload\lotr.full_final.csf.payload"
    $languagePayload = Join-Path $packageRoot "payload\language.openfont.ini.payload"
    $fontSubstitutionPayload = Join-Path $packageRoot "payload\fontsubstitution.openfont.ini.payload"
    Assert-FileHash $overlayPayload $OverlayPayloadHash "한글 오버레이"
    Assert-FileHash $csfPayload $CsfPayloadHash "한글 문자열"
    Assert-FileHash $languagePayload $LanguagePayloadHash "나눔고딕 언어 설정"
    Assert-FileHash $fontSubstitutionPayload $FontSubstitutionPayloadHash "나눔고딕 대체 설정"

    $patchPath = Join-Path $Root "_patch222.big"
    $englishPath = Join-Path $Root "lang\english.big"
    $overlayPath = Join-Path $Root $OverlayName
    $marker = Join-Path $Root $MarkerName
    $state = Read-State $marker
    if ($state) {
        Write-Host "이미 이 설치기로 한글패치가 설치되어 있습니다." -ForegroundColor Yellow
        Invoke-Verify $Root
        return
    }

    Assert-FileHash $patchPath $OriginalPatchHash "$SupportedGameVersion _patch222.big"
    Assert-FileHash $englishPath $OriginalEnglishHash "영문 언어 BIG"
    $languageState = Get-LanguageState

    New-Item -ItemType Directory -Path $marker -Force | Out-Null
    $patchBackup = Join-Path $marker "_patch222.big.original"
    $englishBackup = Join-Path $marker "english.big.original"
    $overlayBackup = Join-Path $marker "_zz_korean_222.big.previous"
    Copy-Item -LiteralPath $patchPath -Destination $patchBackup
    Copy-Item -LiteralPath $englishPath -Destination $englishBackup
    $hadOverlay = Test-Path -LiteralPath $overlayPath -PathType Leaf
    if ($hadOverlay) {
        Copy-Item -LiteralPath $overlayPath -Destination $overlayBackup
    }

    $pending = [ordered]@{
        PatchVersion = $PatchVersion
        SupportedGameVersion = $SupportedGameVersion
        GameRoot = $Root
        InstalledAt = (Get-Date).ToString("o")
        Status = "installing"
        OriginalPatchHash = $OriginalPatchHash
        OriginalEnglishHash = $OriginalEnglishHash
        OriginalOverlayPresent = [bool]$hadOverlay
        OriginalOverlayHash = (Get-Sha256 $overlayPath)
        OriginalLanguagePresent = [bool]$languageState.Present
        OriginalLanguage = $languageState.Value
    }
    Write-JsonFile (Join-Path $marker "state.pending.json") $pending

    $patchTemp = Join-Path $marker "_patch222.big.installing"
    $englishTemp = Join-Path $marker "english.big.installing"
    try {
        Update-BigArchive $patchPath $patchTemp @("data\lotr.str") @{}
        Update-BigArchive $englishPath $englishTemp @() @{
            "lang\english\lotr.csf" = $csfPayload
            "lang\english\language.ini" = $languagePayload
            "lang\english\data\ini\fontsubstitution.ini" = $fontSubstitutionPayload
        }
        if (Get-BigEntry $patchTemp "data\lotr.str") {
            throw "2.22 STR 제거 검증에 실패했습니다."
        }
        $embeddedCsfHash = Get-BigEntrySha256 $englishTemp "lang\english\lotr.csf"
        if ($embeddedCsfHash -ne $CsfPayloadHash) {
            throw "영문 BIG 안의 한글 CSF 검증에 실패했습니다."
        }
        if ((Get-BigEntrySha256 $englishTemp "lang\english\language.ini") -ne $LanguagePayloadHash) {
            throw "영문 BIG 안의 나눔고딕 언어 설정 검증에 실패했습니다."
        }
        if ((Get-BigEntrySha256 $englishTemp "lang\english\data\ini\fontsubstitution.ini") -ne $FontSubstitutionPayloadHash) {
            throw "영문 BIG 안의 나눔고딕 대체 설정 검증에 실패했습니다."
        }

        Move-Item -LiteralPath $patchTemp -Destination $patchPath -Force
        Move-Item -LiteralPath $englishTemp -Destination $englishPath -Force
        Copy-Item -LiteralPath $overlayPayload -Destination $overlayPath -Force
        Assert-FileHash $overlayPath $OverlayPayloadHash "설치된 한글 오버레이"
        Set-LanguageValue $true "English"

        $pending.Status = "installed"
        $pending.InstalledPatchHash = Get-Sha256 $patchPath
        $pending.InstalledEnglishHash = Get-Sha256 $englishPath
        $pending.InstalledOverlayHash = Get-Sha256 $overlayPath
        Write-JsonFile (Join-Path $marker "state.json") $pending
        Remove-Item -LiteralPath (Join-Path $marker "state.pending.json") -Force -ErrorAction SilentlyContinue

        Write-Host ""
        Write-Host "BFME1 Patch 2.22 한국어 패치 설치가 완료되었습니다." -ForegroundColor Green
        Write-Host "런처가 아니라 '03_한글로_실행.cmd'로 게임을 시작하세요."
    }
    catch {
        if (Test-Path -LiteralPath $patchBackup) {
            Copy-Item -LiteralPath $patchBackup -Destination $patchPath -Force
        }
        if (Test-Path -LiteralPath $englishBackup) {
            Copy-Item -LiteralPath $englishBackup -Destination $englishPath -Force
        }
        if ($hadOverlay -and (Test-Path -LiteralPath $overlayBackup)) {
            Copy-Item -LiteralPath $overlayBackup -Destination $overlayPath -Force
        }
        elseif ((Get-Sha256 $overlayPath) -eq $OverlayPayloadHash) {
            Remove-Item -LiteralPath $overlayPath -Force
        }
        Set-LanguageValue ([bool]$languageState.Present) $languageState.Value
        throw
    }
}

function Invoke-Uninstall([string]$Root) {
    Assert-ProcessesClosed
    $marker = Join-Path $Root $MarkerName
    $state = Read-State $marker
    if (-not $state) {
        throw "이 설치기로 만든 복원 정보가 없습니다: $marker"
    }
    $patchPath = Join-Path $Root "_patch222.big"
    $englishPath = Join-Path $Root "lang\english.big"
    $overlayPath = Join-Path $Root $OverlayName
    $patchBackup = Join-Path $marker "_patch222.big.original"
    $englishBackup = Join-Path $marker "english.big.original"
    $overlayBackup = Join-Path $marker "_zz_korean_222.big.previous"
    Assert-FileHash $patchBackup $state.OriginalPatchHash "원본 2.22 백업"
    Assert-FileHash $englishBackup $state.OriginalEnglishHash "원본 영문 BIG 백업"

    $currentPatchHash = Get-Sha256 $patchPath
    $currentEnglishHash = Get-Sha256 $englishPath
    if ($currentPatchHash -notin @($state.InstalledPatchHash, $state.OriginalPatchHash)) {
        throw "_patch222.big이 설치 후 다른 프로그램에 의해 변경되었습니다. 자동 복원을 중단합니다."
    }
    if ($currentEnglishHash -notin @($state.InstalledEnglishHash, $state.OriginalEnglishHash)) {
        throw "lang\english.big이 설치 후 다른 프로그램에 의해 변경되었습니다. 자동 복원을 중단합니다."
    }
    if ($currentPatchHash -eq $state.InstalledPatchHash) {
        Copy-Item -LiteralPath $patchBackup -Destination $patchPath -Force
    }
    if ($currentEnglishHash -eq $state.InstalledEnglishHash) {
        Copy-Item -LiteralPath $englishBackup -Destination $englishPath -Force
    }

    $currentOverlayHash = Get-Sha256 $overlayPath
    if ($currentOverlayHash -and $currentOverlayHash -ne $state.InstalledOverlayHash) {
        throw "$OverlayName 파일이 설치 후 변경되었습니다. 자동 복원을 중단합니다."
    }
    if ([bool]$state.OriginalOverlayPresent) {
        if (-not (Test-Path -LiteralPath $overlayBackup -PathType Leaf)) {
            throw "기존 오버레이 백업이 없습니다."
        }
        Copy-Item -LiteralPath $overlayBackup -Destination $overlayPath -Force
    }
    elseif ($currentOverlayHash -eq $state.InstalledOverlayHash) {
        Remove-Item -LiteralPath $overlayPath -Force
    }

    Set-LanguageValue ([bool]$state.OriginalLanguagePresent) $state.OriginalLanguage
    $state.Status = "uninstalled"
    $state | Add-Member -NotePropertyName "UninstalledAt" -NotePropertyValue ((Get-Date).ToString("o")) -Force
    Write-JsonFile (Join-Path $marker "state.uninstalled.json") $state
    Remove-Item -LiteralPath (Join-Path $marker "state.json") -Force

    Assert-FileHash $patchPath $state.OriginalPatchHash "복원된 2.22 BIG"
    Assert-FileHash $englishPath $state.OriginalEnglishHash "복원된 영문 BIG"
    Write-Host ""
    Write-Host "한국어 패치를 제거하고 원본 파일을 복원했습니다." -ForegroundColor Green
    Write-Host "복구 기록은 안전을 위해 남겨 두었습니다: $marker"
}

function Invoke-Verify([string]$Root) {
    $marker = Join-Path $Root $MarkerName
    $state = Read-State $marker
    $patchPath = Join-Path $Root "_patch222.big"
    $englishPath = Join-Path $Root "lang\english.big"
    $overlayPath = Join-Path $Root $OverlayName
    if (-not $state) {
        Write-Host "설치 상태: 이 설치기로 설치되지 않음" -ForegroundColor Yellow
        Write-Host "_patch222.big SHA-256: $(Get-Sha256 $patchPath)"
        Write-Host "english.big SHA-256: $(Get-Sha256 $englishPath)"
        return
    }

    $checks = @(
        [PSCustomObject]@{ Name = "_patch222.big"; Actual = (Get-Sha256 $patchPath); Expected = $state.InstalledPatchHash },
        [PSCustomObject]@{ Name = "lang\english.big"; Actual = (Get-Sha256 $englishPath); Expected = $state.InstalledEnglishHash },
        [PSCustomObject]@{ Name = $OverlayName; Actual = (Get-Sha256 $overlayPath); Expected = $state.InstalledOverlayHash }
    )
    $failed = $false
    foreach ($check in $checks) {
        $ok = $check.Actual -eq $check.Expected
        if (-not $ok) {
            $failed = $true
        }
        Write-Host ("{0}: {1}" -f $check.Name, $(if ($ok) { "정상" } else { "불일치" }))
    }
    if (Get-BigEntry $patchPath "data\lotr.str") {
        $failed = $true
        Write-Host "data\lotr.str: 남아 있음" -ForegroundColor Red
    }
    else {
        Write-Host "data\lotr.str: 제거 확인"
    }
    $csfHash = Get-BigEntrySha256 $englishPath "lang\english\lotr.csf"
    if ($csfHash -ne $CsfPayloadHash) {
        $failed = $true
        Write-Host "한글 CSF: 불일치" -ForegroundColor Red
    }
    else {
        Write-Host "한글 CSF: 정상"
    }
    $languageHash = Get-BigEntrySha256 $englishPath "lang\english\language.ini"
    $fontSubstitutionHash = Get-BigEntrySha256 $englishPath "lang\english\data\ini\fontsubstitution.ini"
    if ($languageHash -ne $LanguagePayloadHash -or $fontSubstitutionHash -ne $FontSubstitutionPayloadHash) {
        $failed = $true
        Write-Host "나눔고딕 설정: 불일치" -ForegroundColor Red
    }
    else {
        Write-Host "나눔고딕 설정: 정상"
    }
    if ($failed) {
        throw "설치 상태 검증에서 불일치가 발견되었습니다."
    }
    Write-Host "설치 상태: 정상 ($SupportedGameVersion / 한국어 패치 $PatchVersion)" -ForegroundColor Green
}

function Invoke-Launch([string]$Root) {
    if (Get-Process -Name "lotrbfme" -ErrorAction SilentlyContinue) {
        throw "BFME1이 이미 실행 중입니다."
    }
    Invoke-Verify $Root
    Start-Process -FilePath (Join-Path $Root "lotrbfme.exe") -WorkingDirectory $Root
}

try {
    $root = Resolve-GameRoot
    Write-Host "BFME1 폴더: $root"
    switch ($Action) {
        "Install" { Invoke-Install $root }
        "Uninstall" { Invoke-Uninstall $root }
        "Verify" { Invoke-Verify $root }
        "Launch" { Invoke-Launch $root }
    }
    exit 0
}
catch {
    Write-Host ""
    Write-Host "작업 실패:" -ForegroundColor Red
    Write-Host $_.Exception.Message -ForegroundColor Red
    exit 1
}
