diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b4575a4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,27 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{cs,axaml,csproj,props,targets}] +indent_style = space +indent_size = 2 + +[*.cs] +indent_size = 4 +dotnet_sort_system_directives_first = true +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion + +[*.md] +trim_trailing_whitespace = false + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b03fe62 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,59 @@ +name: build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-test-publish: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore + run: dotnet restore Ehwrj.sln + + - name: Format check + run: dotnet format Ehwrj.sln --no-restore --verify-no-changes --verbosity minimal + + - name: Build + run: dotnet build Ehwrj.sln -c Release --no-restore + + - name: Test + run: dotnet run --project tests/Ehwrj.Tests/Ehwrj.Tests.csproj -c Release --no-build + + - name: Safety scan + run: scripts/verify-safety.sh + + - name: Check local API stub + run: dotnet run --project tools/Ehwrj.Tools.LocalApiStub/Ehwrj.Tools.LocalApiStub.csproj -c Release --no-build -- --help + + - name: Check capture tool + run: dotnet run --project tools/Ehwrj.Tools.Capture/Ehwrj.Tools.Capture.csproj -c Release --no-build -- --help + + - name: Publish Windows x64 + run: > + dotnet publish src/Ehwrj.App/Ehwrj.App.csproj + -c Release + -r win-x64 + --self-contained true + -p:PublishSingleFile=true + -p:PublishDir=artifacts/win-x64/ + + - name: Package Windows x64 + run: scripts/package-win-x64.sh --publish-dir artifacts/win-x64 --zip artifacts/ehwrj-win-x64.zip + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ehwrj-win-x64 + path: artifacts/ehwrj-win-x64.zip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc6eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +bin/ +obj/ +.vs/ +.vscode/ +*.user +*.suo +*.nupkg +publish/ +captures/ +artifacts/* +!artifacts/ehwrj-win-x64.zip +!artifacts/ehwrj-win-x64/ +!artifacts/ehwrj-win-x64/** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4d6bdc9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# Contributing + +Thanks for improving Ehwrj. + +## Ground Rules + +- Keep the project limited to the benign War Thunder local map companion scope. +- Do not add clipboard listeners, startup persistence, PE/ZIP mutation, credential collection, or external network reporting. +- Keep local API traffic restricted to loopback addresses. +- Put reusable parsing, projection, and tracking logic in `Ehwrj.Core`. +- Keep Avalonia UI, overlay windows, and platform interop in `Ehwrj.App`. +- Add or update tests when parser, coordinate, or tracking behavior changes. + +## Local Workflow + +On Ubuntu, bootstrap dependencies and run the full verification/publish pipeline: + +```bash +scripts/bootstrap-ubuntu.sh +``` + +For an already prepared environment: + +```bash +dotnet restore Ehwrj.sln +dotnet format Ehwrj.sln --no-restore --verify-no-changes --verbosity minimal +dotnet build Ehwrj.sln -c Release +dotnet run --project tests/Ehwrj.Tests/Ehwrj.Tests.csproj -c Release --no-build +scripts/verify-safety.sh +``` + +To develop the UI without War Thunder: + +```bash +scripts/run-local-api-stub.sh +``` + +To capture and replay a real local API session: + +```bash +scripts/capture-local-api.sh captures/my-session +scripts/validate-capture.sh captures/my-session +scripts/run-local-api-stub.sh 8111 captures/my-session +``` + +To publish a Windows x64 build from Linux: + +```bash +scripts/publish-win-x64.sh +``` + +## Review Checklist + +- Build succeeds with zero warnings. +- Tests pass. +- New network code is justified and loopback-only unless explicitly documented. +- UI controls remain dense, readable, and focused on operating the map/overlay. +- Security boundaries in `SECURITY.md` are preserved. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..8ca5292 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,13 @@ + + + latest + enable + enable + latest + true + true + true + true + true + + diff --git a/Ehwrj.sln b/Ehwrj.sln new file mode 100644 index 0000000..fcd2d57 --- /dev/null +++ b/Ehwrj.sln @@ -0,0 +1,59 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AFC3199F-8F50-446D-A8A1-A501A07FD238}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ehwrj.App", "src\Ehwrj.App\Ehwrj.App.csproj", "{E48273EA-304E-47AE-8206-9EFFB2BC00AE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{46338909-A80A-49F7-A274-6B62A2E37FCC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ehwrj.Tests", "tests\Ehwrj.Tests\Ehwrj.Tests.csproj", "{B253C8DF-A67D-494E-8805-BD1F02231672}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ehwrj.Core", "src\Ehwrj.Core\Ehwrj.Core.csproj", "{DCD75174-7AB0-4FC6-BD08-AB9F5E40F2C2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{669545BC-3FD6-4044-92E8-7B4142525C62}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ehwrj.Tools.LocalApiStub", "tools\Ehwrj.Tools.LocalApiStub\Ehwrj.Tools.LocalApiStub.csproj", "{4CD90023-EA88-4E9F-AF88-17DC3202E961}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ehwrj.Tools.Capture", "tools\Ehwrj.Tools.Capture\Ehwrj.Tools.Capture.csproj", "{A59D73D0-BEC0-4DDB-875E-70C3EA9C8808}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E48273EA-304E-47AE-8206-9EFFB2BC00AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E48273EA-304E-47AE-8206-9EFFB2BC00AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E48273EA-304E-47AE-8206-9EFFB2BC00AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E48273EA-304E-47AE-8206-9EFFB2BC00AE}.Release|Any CPU.Build.0 = Release|Any CPU + {B253C8DF-A67D-494E-8805-BD1F02231672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B253C8DF-A67D-494E-8805-BD1F02231672}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B253C8DF-A67D-494E-8805-BD1F02231672}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B253C8DF-A67D-494E-8805-BD1F02231672}.Release|Any CPU.Build.0 = Release|Any CPU + {DCD75174-7AB0-4FC6-BD08-AB9F5E40F2C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DCD75174-7AB0-4FC6-BD08-AB9F5E40F2C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DCD75174-7AB0-4FC6-BD08-AB9F5E40F2C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DCD75174-7AB0-4FC6-BD08-AB9F5E40F2C2}.Release|Any CPU.Build.0 = Release|Any CPU + {4CD90023-EA88-4E9F-AF88-17DC3202E961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4CD90023-EA88-4E9F-AF88-17DC3202E961}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4CD90023-EA88-4E9F-AF88-17DC3202E961}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4CD90023-EA88-4E9F-AF88-17DC3202E961}.Release|Any CPU.Build.0 = Release|Any CPU + {A59D73D0-BEC0-4DDB-875E-70C3EA9C8808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A59D73D0-BEC0-4DDB-875E-70C3EA9C8808}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A59D73D0-BEC0-4DDB-875E-70C3EA9C8808}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A59D73D0-BEC0-4DDB-875E-70C3EA9C8808}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E48273EA-304E-47AE-8206-9EFFB2BC00AE} = {AFC3199F-8F50-446D-A8A1-A501A07FD238} + {B253C8DF-A67D-494E-8805-BD1F02231672} = {46338909-A80A-49F7-A274-6B62A2E37FCC} + {DCD75174-7AB0-4FC6-BD08-AB9F5E40F2C2} = {AFC3199F-8F50-446D-A8A1-A501A07FD238} + {4CD90023-EA88-4E9F-AF88-17DC3202E961} = {669545BC-3FD6-4044-92E8-7B4142525C62} + {A59D73D0-BEC0-4DDB-875E-70C3EA9C8808} = {669545BC-3FD6-4044-92E8-7B4142525C62} + EndGlobalSection +EndGlobal diff --git a/LICENSE b/LICENSE index 7a3094a..08f2ed7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,11 +1,22 @@ -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -Version 2, December 2004 +MIT License -Copyright (C) 2004 Sam Hocevar +Copyright (c) 2026 Ehwrj contributors -Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. - 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md index aa18493..e75a110 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,206 @@ -# ehwrj +# Ehwrj -썬평ㅋㅋ \ No newline at end of file +Ehwrj is a clean-room War Thunder live map companion for Windows. + +It reads War Thunder's local map service at `http://127.0.0.1:8111`, renders a desktop map view, and can show a click-through always-on-top overlay. The code is newly written and intentionally excludes the malicious behavior found in the analyzed sample. + +## Analyzed Malware File + +This repository does not contain the original malicious sample. The notes below summarize the static analysis used to separate the benign WT Live Map behavior from the malicious loader/clipper behavior. + +Analysis date: 2026-06-02 +Analysis method: static analysis only; the sample was not executed. + +| File | SHA-256 | Assessment | +| --- | --- | --- | +| `WTLiveMap.exe` | `4dbbc21d9c2ed70dde046a2f737ee4173da807d43d5b3a6acfe447864ed6d643` | Native x64 Win32/C++ executable containing RCDATA 101 and 102. | +| `WTLiveMap.zip` | `429e07a27e7896f75a901a1df3cd6560b43de33986920f1f22013ef155541b4c` | ZIP package containing the same suspicious executable. | +| `resource_101.bin` | `00732b0bbc1740ebe88745424c88ab840b381475837e5e02e937a29e66df3909` | Benign WT Live Map UI/WebView-style resource. | +| `resource_102.bin` | `03547c81562a065bcb08defa638866c8c1c79343d78b8df7a8a7e0cb827242d7` | Resource-less loader/clipper variant with the same malicious capability class as the outer EXE. | + +Key findings: + +- RCDATA 101 matches the non-virus WT Live Map behavior: it references `Warthunder LiveMap`, `map_info.json`, `map_obj.json`, `map.img`, `aces.exe`, WebView messaging, and loopback access to `127.0.0.1:8111`. +- RCDATA 102 is not byte-identical to the outer `WTLiveMap.exe`, but it shares the suspicious import profile, hidden string structure, Windows Update disguise strings, clipboard API strings, and cryptocurrency address replacement logic. +- The malicious path monitors clipboard text and replaces BTC, ETH, SOL, TRX, XRP, DOGE, LTC, and BCH wallet addresses with attacker-controlled addresses. +- Persistence is disguised as Windows Update. The analyzed code builds paths similar to `%TEMP%\WindowsUpdateModule\SystemUpdateService.exe` and creates a startup shortcut named `WindowsSystemUpdate.lnk`. +- `SHGetSpecialFolderPathW(CSIDL_STARTUP)`, Shell Link COM use, `Global\WinSysUpdateMutexV11`, and resource update APIs were observed in the malicious loader path. +- A ZIP repackaging path uses hidden PowerShell command construction, extracts a ZIP, locates an inner EXE, reinvokes itself with `--merge-env`, injects RCDATA 101/102, and recreates the ZIP. +- No external C2 server, IP, domain, or upload path was confirmed in static analysis. The confirmed monetization path is clipboard-based cryptocurrency address replacement. + +## What It Implements + +- Local War Thunder API polling: + - `/map_info.json` + - `/map_obj.json` + - `/map.img` +- Optional local telemetry/message polling when available: + - `/state` + - `/hudmsg` + - `/gamechat` +- Main map preview with aircraft/object markers +- Transparent click-through overlay +- Configurable overlay size, position, zoom, minimap, Mach labels, and spot radar +- Overlay controls for aircraft scale, minimum Mach filters, radar spread, vertical scale/offset, arrow size, opacity, label colors, font sizes, distance, Mach, and closure speed labels +- English/Korean UI language selection +- Settings persisted under `%LOCALAPPDATA%\Ehwrj\settings.json` +- Windows build from Linux using .NET 8 and Avalonia targeting `win-x64` + +## Explicitly Not Implemented + +The analyzed binary contained malicious behavior. Ehwrj does not implement: + +- Clipboard monitoring or cryptocurrency address replacement +- Windows Update disguise or startup persistence +- ZIP/EXE infection or resource injection +- Hidden mutex-based malware lifecycle control +- Any external C2, exfiltration, or remote upload path + +## Build + +From Ubuntu: + +```bash +cd ehwrj +scripts/bootstrap-ubuntu.sh +``` + +The bootstrap script installs missing Ubuntu packages for .NET 8 and the safety scanner, then runs restore, build, tests, the safety scan, tool smoke checks, and Windows x64 publish. + +If the dependencies are already installed: + +```bash +dotnet restore ehwrj/Ehwrj.sln +dotnet build ehwrj/Ehwrj.sln -c Release +dotnet run --project ehwrj/tests/Ehwrj.Tests/Ehwrj.Tests.csproj -c Release +dotnet publish ehwrj/src/Ehwrj.App/Ehwrj.App.csproj -c Release -r win-x64 --self-contained true \ + -p:PublishSingleFile=true +``` + +Or from inside the `ehwrj` folder: + +```bash +scripts/publish-win-x64.sh +``` + +The output EXE will be under: + +```text +ehwrj/src/Ehwrj.App/bin/Release/net8.0/win-x64/publish/ +``` + +The portable release ZIP is written to: + +```text +ehwrj/artifacts/ehwrj-win-x64.zip +``` + +## Runtime + +Run War Thunder first, then start Ehwrj on Windows. The app expects the game to expose the local map API on `127.0.0.1:8111`. + +The current coordinate and speed estimates are intentionally conservative because War Thunder's local API shape can vary by mode and vehicle. Use real `map_info.json` and `map_obj.json` captures to tune projection and spot radar math for a specific game mode. + +## Local API Stub + +For UI development without War Thunder, run the deterministic local API stub: + +```bash +cd ehwrj +scripts/run-local-api-stub.sh +``` + +It serves: + +- `http://127.0.0.1:8111/map_info.json` +- `http://127.0.0.1:8111/map_obj.json` +- `http://127.0.0.1:8111/map.img` +- `http://127.0.0.1:8111/state` +- `http://127.0.0.1:8111/hudmsg` +- `http://127.0.0.1:8111/gamechat` + +Use another port for endpoint testing: + +```bash +scripts/run-local-api-stub.sh 18111 +``` + +## Capturing Real Local API Data + +When War Thunder is running on Windows, capture the local API into a fixture directory: + +```bash +cd ehwrj +scripts/capture-local-api.sh captures/my-session +``` + +The capture tool saves: + +- `map_info.json` +- `map_obj.json` +- `map.img` +- `state.json` when `/state` is available +- `hudmsg.json` when `/hudmsg` is available +- `gamechat.json` when `/gamechat` is available +- `capture-report.txt` with parser coverage, raw object field frequency, unknown object samples, replay readiness, and tuning warnings + +Validate an existing capture: + +```bash +scripts/validate-capture.sh captures/my-session +``` + +Use the report to tune real game-mode support. The most useful sections are `Object field coverage`, `Unknown object samples`, and `State field names`; they show which raw War Thunder fields are present and where object classification or telemetry parsing needs adjustment. + +Replay a capture through the local API stub: + +```bash +scripts/run-local-api-stub.sh 8111 captures/my-session +``` + +## New Code Structure + +```text +Directory.Build.props + shared compiler, analyzer, and Windows-targeting settings +docs/ + architecture and feature parity notes +scripts/ + Ubuntu bootstrap, safety scan, capture, publish, and packaging helpers +src/Ehwrj.App/ + Avalonia desktop UI and overlay host +src/Ehwrj.App/Models/ + UI settings, localization text, endpoint health, and render snapshots +src/Ehwrj.App/Services/ + polling adapter and settings store +src/Ehwrj.App/Rendering/ + main map and overlay drawing surfaces +src/Ehwrj.App/ViewModels/ + app state and UI commands +src/Ehwrj.App/Infrastructure/ + minimal Win32 interop for click-through overlay styles +src/Ehwrj.Core/ + loopback API parsing, map models, telemetry models, tracking, and projection logic +src/Ehwrj.Core/Models/ + War Thunder map object, map info, flight state, battle message, and motion tracker types +src/Ehwrj.Core/Services/ + local WT API client, process probe, loopback guard, and capture fixture analyzer +src/Ehwrj.Core/Geometry/ + viewport, projected point, and coordinate projection math +tools/Ehwrj.Tools.LocalApiStub/ + deterministic local map API server and capture replay helper +tools/Ehwrj.Tools.Capture/ + local API fixture capture and validation tool +tests/Ehwrj.Tests/ + lightweight parser, projection, localization, settings, and safety checks +artifacts/ehwrj-win-x64/ + committed portable Windows x64 build output, including Ehwrj.exe and checksums +``` + +## CI + +The repository includes a GitHub Actions workflow that restores, checks formatting, builds with warnings as errors, runs the lightweight tests, publishes a Windows x64 artifact, and uploads it as `ehwrj-win-x64`. + +## Feature Parity + +See [docs/feature-matrix.md](docs/feature-matrix.md) for the benign WT Live Map feature parity status and remaining data gaps. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..3af5748 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Security Scope + +Ehwrj is a clean-room replacement for the benign War Thunder live map behavior observed in the analyzed sample. + +Allowed behavior: + +- Connect to `127.0.0.1:8111` only +- Read local War Thunder map endpoints +- Store user settings in `%LOCALAPPDATA%\Ehwrj` +- Create an optional visible overlay window controlled by the user + +Disallowed behavior: + +- Clipboard listeners +- Cryptocurrency wallet matching or replacement +- Startup persistence +- Windows Update impersonation +- ZIP, PE, or resource modification +- Hidden external network communication +- Credential, cookie, wallet, browser, or messenger file collection + +Issues or pull requests that add disallowed behavior should be rejected. + diff --git a/artifacts/ehwrj-win-x64.zip b/artifacts/ehwrj-win-x64.zip new file mode 100644 index 0000000..63408d4 Binary files /dev/null and b/artifacts/ehwrj-win-x64.zip differ diff --git a/artifacts/ehwrj-win-x64/Ehwrj.exe b/artifacts/ehwrj-win-x64/Ehwrj.exe new file mode 100755 index 0000000..a27efac Binary files /dev/null and b/artifacts/ehwrj-win-x64/Ehwrj.exe differ diff --git a/artifacts/ehwrj-win-x64/LICENSE b/artifacts/ehwrj-win-x64/LICENSE new file mode 100644 index 0000000..08f2ed7 --- /dev/null +++ b/artifacts/ehwrj-win-x64/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2026 Ehwrj contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/artifacts/ehwrj-win-x64/README.md b/artifacts/ehwrj-win-x64/README.md new file mode 100644 index 0000000..e75a110 --- /dev/null +++ b/artifacts/ehwrj-win-x64/README.md @@ -0,0 +1,206 @@ +# Ehwrj + +Ehwrj is a clean-room War Thunder live map companion for Windows. + +It reads War Thunder's local map service at `http://127.0.0.1:8111`, renders a desktop map view, and can show a click-through always-on-top overlay. The code is newly written and intentionally excludes the malicious behavior found in the analyzed sample. + +## Analyzed Malware File + +This repository does not contain the original malicious sample. The notes below summarize the static analysis used to separate the benign WT Live Map behavior from the malicious loader/clipper behavior. + +Analysis date: 2026-06-02 +Analysis method: static analysis only; the sample was not executed. + +| File | SHA-256 | Assessment | +| --- | --- | --- | +| `WTLiveMap.exe` | `4dbbc21d9c2ed70dde046a2f737ee4173da807d43d5b3a6acfe447864ed6d643` | Native x64 Win32/C++ executable containing RCDATA 101 and 102. | +| `WTLiveMap.zip` | `429e07a27e7896f75a901a1df3cd6560b43de33986920f1f22013ef155541b4c` | ZIP package containing the same suspicious executable. | +| `resource_101.bin` | `00732b0bbc1740ebe88745424c88ab840b381475837e5e02e937a29e66df3909` | Benign WT Live Map UI/WebView-style resource. | +| `resource_102.bin` | `03547c81562a065bcb08defa638866c8c1c79343d78b8df7a8a7e0cb827242d7` | Resource-less loader/clipper variant with the same malicious capability class as the outer EXE. | + +Key findings: + +- RCDATA 101 matches the non-virus WT Live Map behavior: it references `Warthunder LiveMap`, `map_info.json`, `map_obj.json`, `map.img`, `aces.exe`, WebView messaging, and loopback access to `127.0.0.1:8111`. +- RCDATA 102 is not byte-identical to the outer `WTLiveMap.exe`, but it shares the suspicious import profile, hidden string structure, Windows Update disguise strings, clipboard API strings, and cryptocurrency address replacement logic. +- The malicious path monitors clipboard text and replaces BTC, ETH, SOL, TRX, XRP, DOGE, LTC, and BCH wallet addresses with attacker-controlled addresses. +- Persistence is disguised as Windows Update. The analyzed code builds paths similar to `%TEMP%\WindowsUpdateModule\SystemUpdateService.exe` and creates a startup shortcut named `WindowsSystemUpdate.lnk`. +- `SHGetSpecialFolderPathW(CSIDL_STARTUP)`, Shell Link COM use, `Global\WinSysUpdateMutexV11`, and resource update APIs were observed in the malicious loader path. +- A ZIP repackaging path uses hidden PowerShell command construction, extracts a ZIP, locates an inner EXE, reinvokes itself with `--merge-env`, injects RCDATA 101/102, and recreates the ZIP. +- No external C2 server, IP, domain, or upload path was confirmed in static analysis. The confirmed monetization path is clipboard-based cryptocurrency address replacement. + +## What It Implements + +- Local War Thunder API polling: + - `/map_info.json` + - `/map_obj.json` + - `/map.img` +- Optional local telemetry/message polling when available: + - `/state` + - `/hudmsg` + - `/gamechat` +- Main map preview with aircraft/object markers +- Transparent click-through overlay +- Configurable overlay size, position, zoom, minimap, Mach labels, and spot radar +- Overlay controls for aircraft scale, minimum Mach filters, radar spread, vertical scale/offset, arrow size, opacity, label colors, font sizes, distance, Mach, and closure speed labels +- English/Korean UI language selection +- Settings persisted under `%LOCALAPPDATA%\Ehwrj\settings.json` +- Windows build from Linux using .NET 8 and Avalonia targeting `win-x64` + +## Explicitly Not Implemented + +The analyzed binary contained malicious behavior. Ehwrj does not implement: + +- Clipboard monitoring or cryptocurrency address replacement +- Windows Update disguise or startup persistence +- ZIP/EXE infection or resource injection +- Hidden mutex-based malware lifecycle control +- Any external C2, exfiltration, or remote upload path + +## Build + +From Ubuntu: + +```bash +cd ehwrj +scripts/bootstrap-ubuntu.sh +``` + +The bootstrap script installs missing Ubuntu packages for .NET 8 and the safety scanner, then runs restore, build, tests, the safety scan, tool smoke checks, and Windows x64 publish. + +If the dependencies are already installed: + +```bash +dotnet restore ehwrj/Ehwrj.sln +dotnet build ehwrj/Ehwrj.sln -c Release +dotnet run --project ehwrj/tests/Ehwrj.Tests/Ehwrj.Tests.csproj -c Release +dotnet publish ehwrj/src/Ehwrj.App/Ehwrj.App.csproj -c Release -r win-x64 --self-contained true \ + -p:PublishSingleFile=true +``` + +Or from inside the `ehwrj` folder: + +```bash +scripts/publish-win-x64.sh +``` + +The output EXE will be under: + +```text +ehwrj/src/Ehwrj.App/bin/Release/net8.0/win-x64/publish/ +``` + +The portable release ZIP is written to: + +```text +ehwrj/artifacts/ehwrj-win-x64.zip +``` + +## Runtime + +Run War Thunder first, then start Ehwrj on Windows. The app expects the game to expose the local map API on `127.0.0.1:8111`. + +The current coordinate and speed estimates are intentionally conservative because War Thunder's local API shape can vary by mode and vehicle. Use real `map_info.json` and `map_obj.json` captures to tune projection and spot radar math for a specific game mode. + +## Local API Stub + +For UI development without War Thunder, run the deterministic local API stub: + +```bash +cd ehwrj +scripts/run-local-api-stub.sh +``` + +It serves: + +- `http://127.0.0.1:8111/map_info.json` +- `http://127.0.0.1:8111/map_obj.json` +- `http://127.0.0.1:8111/map.img` +- `http://127.0.0.1:8111/state` +- `http://127.0.0.1:8111/hudmsg` +- `http://127.0.0.1:8111/gamechat` + +Use another port for endpoint testing: + +```bash +scripts/run-local-api-stub.sh 18111 +``` + +## Capturing Real Local API Data + +When War Thunder is running on Windows, capture the local API into a fixture directory: + +```bash +cd ehwrj +scripts/capture-local-api.sh captures/my-session +``` + +The capture tool saves: + +- `map_info.json` +- `map_obj.json` +- `map.img` +- `state.json` when `/state` is available +- `hudmsg.json` when `/hudmsg` is available +- `gamechat.json` when `/gamechat` is available +- `capture-report.txt` with parser coverage, raw object field frequency, unknown object samples, replay readiness, and tuning warnings + +Validate an existing capture: + +```bash +scripts/validate-capture.sh captures/my-session +``` + +Use the report to tune real game-mode support. The most useful sections are `Object field coverage`, `Unknown object samples`, and `State field names`; they show which raw War Thunder fields are present and where object classification or telemetry parsing needs adjustment. + +Replay a capture through the local API stub: + +```bash +scripts/run-local-api-stub.sh 8111 captures/my-session +``` + +## New Code Structure + +```text +Directory.Build.props + shared compiler, analyzer, and Windows-targeting settings +docs/ + architecture and feature parity notes +scripts/ + Ubuntu bootstrap, safety scan, capture, publish, and packaging helpers +src/Ehwrj.App/ + Avalonia desktop UI and overlay host +src/Ehwrj.App/Models/ + UI settings, localization text, endpoint health, and render snapshots +src/Ehwrj.App/Services/ + polling adapter and settings store +src/Ehwrj.App/Rendering/ + main map and overlay drawing surfaces +src/Ehwrj.App/ViewModels/ + app state and UI commands +src/Ehwrj.App/Infrastructure/ + minimal Win32 interop for click-through overlay styles +src/Ehwrj.Core/ + loopback API parsing, map models, telemetry models, tracking, and projection logic +src/Ehwrj.Core/Models/ + War Thunder map object, map info, flight state, battle message, and motion tracker types +src/Ehwrj.Core/Services/ + local WT API client, process probe, loopback guard, and capture fixture analyzer +src/Ehwrj.Core/Geometry/ + viewport, projected point, and coordinate projection math +tools/Ehwrj.Tools.LocalApiStub/ + deterministic local map API server and capture replay helper +tools/Ehwrj.Tools.Capture/ + local API fixture capture and validation tool +tests/Ehwrj.Tests/ + lightweight parser, projection, localization, settings, and safety checks +artifacts/ehwrj-win-x64/ + committed portable Windows x64 build output, including Ehwrj.exe and checksums +``` + +## CI + +The repository includes a GitHub Actions workflow that restores, checks formatting, builds with warnings as errors, runs the lightweight tests, publishes a Windows x64 artifact, and uploads it as `ehwrj-win-x64`. + +## Feature Parity + +See [docs/feature-matrix.md](docs/feature-matrix.md) for the benign WT Live Map feature parity status and remaining data gaps. diff --git a/artifacts/ehwrj-win-x64/RUNNING.txt b/artifacts/ehwrj-win-x64/RUNNING.txt new file mode 100644 index 0000000..5a3a3f3 --- /dev/null +++ b/artifacts/ehwrj-win-x64/RUNNING.txt @@ -0,0 +1,13 @@ +Ehwrj portable Windows x64 build + +1. Start War Thunder. +2. Confirm the local map is available at http://127.0.0.1:8111/map_info.json. +3. Run Ehwrj.exe. +4. Use "Show overlay" in the left panel to enable the click-through overlay. + +Network scope: +- Ehwrj only reads the loopback War Thunder local API. +- It does not contact external hosts. + +Settings: +- Saved under %LOCALAPPDATA%\Ehwrj\settings.json. diff --git a/artifacts/ehwrj-win-x64/SECURITY.md b/artifacts/ehwrj-win-x64/SECURITY.md new file mode 100644 index 0000000..3af5748 --- /dev/null +++ b/artifacts/ehwrj-win-x64/SECURITY.md @@ -0,0 +1,23 @@ +# Security Scope + +Ehwrj is a clean-room replacement for the benign War Thunder live map behavior observed in the analyzed sample. + +Allowed behavior: + +- Connect to `127.0.0.1:8111` only +- Read local War Thunder map endpoints +- Store user settings in `%LOCALAPPDATA%\Ehwrj` +- Create an optional visible overlay window controlled by the user + +Disallowed behavior: + +- Clipboard listeners +- Cryptocurrency wallet matching or replacement +- Startup persistence +- Windows Update impersonation +- ZIP, PE, or resource modification +- Hidden external network communication +- Credential, cookie, wallet, browser, or messenger file collection + +Issues or pull requests that add disallowed behavior should be rejected. + diff --git a/artifacts/ehwrj-win-x64/SHA256SUMS.txt b/artifacts/ehwrj-win-x64/SHA256SUMS.txt new file mode 100644 index 0000000..113483e --- /dev/null +++ b/artifacts/ehwrj-win-x64/SHA256SUMS.txt @@ -0,0 +1,8 @@ +684aaf2276f934d7aae842da81adfe46b954764e9828d5bbe9242b00cd1f5168 Ehwrj.exe +4c1705d38ec895d4f3830165f1b061ec389da913f17d471ec97fcbe3e6cec012 LICENSE +d71dd3bed70b3c90aa04bed6c8f47caf47888566b79e48a50e7743a6ae35f031 README.md +866de6ec207750697e6a321ed1b8ba52ba04bdf9080c80ff929c885c9107ad27 RUNNING.txt +266a2a8f242f274530085cca86ebeb8c11706ca73ce03684ee3b6ba61ef5e274 SECURITY.md +9b203e40323b49dad29546a52b8b67d200bba8ff4cab9709a79cede23ba847d4 av_libglesv2.dll +eb76238c9e8e41d44b5a5b18167c4c5b39ca5db4277af5dbe92d730f0fc14a7d libHarfBuzzSharp.dll +9a0d95e8caaa852c70d085af6a40a744242172ad9ea3fd6bc7599875a8a1dbcd libSkiaSharp.dll diff --git a/artifacts/ehwrj-win-x64/av_libglesv2.dll b/artifacts/ehwrj-win-x64/av_libglesv2.dll new file mode 100755 index 0000000..487d711 Binary files /dev/null and b/artifacts/ehwrj-win-x64/av_libglesv2.dll differ diff --git a/artifacts/ehwrj-win-x64/libHarfBuzzSharp.dll b/artifacts/ehwrj-win-x64/libHarfBuzzSharp.dll new file mode 100755 index 0000000..2bb6849 Binary files /dev/null and b/artifacts/ehwrj-win-x64/libHarfBuzzSharp.dll differ diff --git a/artifacts/ehwrj-win-x64/libSkiaSharp.dll b/artifacts/ehwrj-win-x64/libSkiaSharp.dll new file mode 100755 index 0000000..3f8c6f2 Binary files /dev/null and b/artifacts/ehwrj-win-x64/libSkiaSharp.dll differ diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..c758169 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,84 @@ +# Architecture + +Ehwrj is split into small modules so that local API access, state derivation, rendering, and platform integration stay separate. + +## Data Flow + +```text +War Thunder local API + -> Ehwrj.Core.Services.WarThunderClient + -> Ehwrj.App.Services.LiveMapService + -> Ehwrj.App.Models.LiveSnapshot + -> MainWindow / OverlayWindow + -> MapCanvas / OverlayCanvas +``` + +## Modules + +### Ehwrj.Core + +- `WarThunderClient` is the only module allowed to perform network requests. +- Its base address is fixed to `http://127.0.0.1:8111/`. +- `ProcessProbe` checks whether `aces.exe` is present. +- `MapInfo` parses `/map_info.json`. +- `MapObject` parses `/map_obj.json`. +- `FlightState` parses optional `/state` telemetry such as `TAS, km/h`, `IAS, km/h`, `Vy, m/s`, and `H, m`. +- `BattleMessage` normalizes optional `/hudmsg` and `/gamechat` messages. +- `MapObjectKind` classifies player, ally, squad, enemy, objective, and unknown objects. +- `ObjectTracker` estimates motion from consecutive object positions. +- `CoordinateProjector` converts map coordinates into viewport coordinates. + +`Ehwrj.Core` targets plain `net8.0` and has no UI dependency. + +Parser tolerance is intentionally concentrated in `Ehwrj.Core`: + +- numeric JSON strings are parsed with invariant culture and comma decimal fallback +- map bounds can come from scalar min/max fields, `map_min`/`map_max` arrays, `bounds`, or `grid_size` +- object position can come from scalar `x`/`y` style fields or `pos`/`position` style arrays +- object direction can come from scalar `dx`/`dy` style fields or `dir`/`direction`/`velocity` style arrays + +### Ehwrj.App + +- `LiveMapService` owns the polling loop and converts endpoint responses into immutable UI snapshots. +- `SettingsStore` persists user settings under `%LOCALAPPDATA%\Ehwrj`. +- `OverlaySettings` holds user-adjustable overlay controls. +- `UiText` and `LanguageOption` provide the clean-room English/Korean UI language layer. +- `LiveSnapshot` is the single render input for both the main map and overlay. +- `MainViewModel` coordinates commands and live state for the Avalonia UI. + +### Rendering + +- `MapCanvas` draws the main map preview, including player-relative rotation of the map plane and projected tactical objects. +- `OverlayCanvas` draws the transparent overlay surface. +- Rendering code receives a `LiveSnapshot` and `OverlaySettings`; it does not perform I/O. +- Overlay setting changes invalidate the drawing surface directly, so UI sliders and text settings are reflected without restarting the polling loop. + +### Overlay Controls + +The overlay settings mirror the benign controls identified in the analyzed live map resource: + +- minimap visibility, aircraft scale, and Mach threshold +- spot radar visibility, detection range, spread, vertical scale, and vertical offset +- marker opacity, arrow scale, arrow outline, and colors +- distance, Mach, and closure speed label toggles +- per-label font size, outline width, color, and opacity + +### Platform Integration + +- `Win32.MakeOverlayClickThrough` is the only Windows interop hook. +- It applies click-through and no-activate extended styles to the optional overlay window. +- It is guarded with `OperatingSystem.IsWindows()`. + +### Developer Tooling + +- `Ehwrj.Tools.LocalApiStub` serves deterministic `/map_info.json`, `/map_obj.json`, and `/map.img` responses on loopback. +- It also serves optional `/state`, `/hudmsg`, and `/gamechat` responses for player info and battle log development. +- The stub lets contributors exercise the UI without running War Thunder. +- The generated map image is produced in memory and the moving aircraft data is deterministic. +- `Ehwrj.Tools.Capture` captures real local API responses into fixture directories. +- It writes `capture-report.txt` so captured fixtures can be checked for parser coverage, raw object field frequency, unknown object samples, replay readiness, and tuning gaps. +- `LocalApiStub --fixture-dir` replays captured fixtures before falling back to generated responses. + +## Safety Rules + +The project intentionally has no module for clipboard monitoring, startup persistence, ZIP mutation, PE resource updates, or external network communication. New features should preserve these boundaries. diff --git a/docs/feature-matrix.md b/docs/feature-matrix.md new file mode 100644 index 0000000..33c6a08 --- /dev/null +++ b/docs/feature-matrix.md @@ -0,0 +1,31 @@ +# Feature Matrix + +This matrix tracks the benign functionality identified from the analyzed WT Live Map resource and the corresponding clean-room Ehwrj implementation. + +| Feature | Ehwrj status | Notes | +| --- | --- | --- | +| Local War Thunder map polling | Implemented | Reads `map_info.json`, `map_obj.json`, and `map.img` from loopback. | +| Map image preview | Implemented | Drawn by `MapCanvas`. | +| Aircraft/object markers | Implemented | Uses `MapObjectKind` classification for player, ally, squad, enemy, and objective colors. | +| Ally/enemy aircraft counters | Implemented | Derived from parsed object kind and aircraft flags. | +| Player info panel | Implemented | Shows detected player name, map position, Mach, and heading when available. | +| Flight telemetry | Implemented | Optional `/state` support for TAS/IAS, altitude, vertical speed, Mach, and climb angle. | +| Battle timer | Implemented | Session-local timer starts on first live snapshot. | +| Battle log panel | Implemented | Reads optional `/hudmsg` and `/gamechat` when available, and also logs local snapshot events. | +| Labels | Implemented | Toggle-controlled map labels. | +| Aircraft Mach labels | Implemented | Uses tracked motion estimates or state-derived values when present in parsed data. | +| Follow player | Implemented | Main map zoom/pan centers the detected player. | +| Rotate with player | Implemented | The map bitmap, grid, markers, range rings, and delivery tracker rotate around the view center using detected player heading. | +| Range rings | Implemented | Drawn around detected player. | +| Delivery tracker | Implemented | Draws projected approach lines from friendly/player aircraft to enemy objective targets when heading alignment passes the configured threshold. | +| Transparent overlay | Implemented | Avalonia transparent window with Windows click-through interop. | +| Overlay minimap | Implemented | Includes aircraft scale and minimum Mach filtering. | +| Overlay spot radar | Implemented | Includes range, spread, vertical scale/offset, opacity, arrow size/outline/color, and distance/Mach/closure labels. | +| Settings persistence | Implemented | Stored in `%LOCALAPPDATA%\Ehwrj\settings.json`. | +| Local API capture/replay | Implemented | `Ehwrj.Tools.Capture` records fixtures and writes a validation report; `LocalApiStub --fixture-dir` replays them. | +| Language selection | Implemented | English and Korean UI text can be selected from the main control panel and persists in settings. | +| WebView2 host | Intentionally not used | Avalonia was chosen so Ubuntu ARM64 can build and publish Windows x64 artifacts without WindowsDesktop SDK support. | + +## Known Data Gaps + +Real War Thunder `map_obj.json`, `map_info.json`, `/state`, `/hudmsg`, and `/gamechat` captures are still needed to tune distance scale, object classification edge cases, and mode-specific fields. The local API stub covers development and UI testing but is not a replacement for game-mode validation. diff --git a/scripts/bootstrap-ubuntu.sh b/scripts/bootstrap-ubuntu.sh new file mode 100755 index 0000000..e037866 --- /dev/null +++ b/scripts/bootstrap-ubuntu.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +APT_UPDATED=0 + +usage() { + cat <<'EOF' +Usage: + scripts/bootstrap-ubuntu.sh [--no-install] + +Installs the Ubuntu packages needed to build Ehwrj, then runs the full +Release verification and Windows x64 publish pipeline. + +Options: + --no-install Skip apt installs and only run the verification/publish step. +EOF +} + +NO_INSTALL=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --no-install) + NO_INSTALL=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "error: unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ -r /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release +else + echo "error: /etc/os-release not found; this script expects Ubuntu." >&2 + exit 1 +fi + +if [[ "${ID:-}" != "ubuntu" ]]; then + echo "error: this bootstrap script expects Ubuntu, got '${ID:-unknown}'." >&2 + exit 1 +fi + +run_as_root() { + if [[ "$(id -u)" -eq 0 ]]; then + "$@" + return + fi + + if ! command -v sudo >/dev/null 2>&1; then + echo "error: sudo is required when not running as root." >&2 + exit 1 + fi + + sudo "$@" +} + +apt_install() { + if [[ "$NO_INSTALL" -eq 1 ]]; then + return + fi + + if [[ "$APT_UPDATED" -eq 0 ]]; then + run_as_root apt-get update + APT_UPDATED=1 + fi + + run_as_root apt-get install -y "$@" +} + +has_dotnet_8_sdk() { + command -v dotnet >/dev/null 2>&1 && dotnet --list-sdks | awk '{print $1}' | grep -q '^8\.' +} + +missing=() + +if ! has_dotnet_8_sdk; then + missing+=(dotnet-sdk-8.0) +fi + +if ! command -v rg >/dev/null 2>&1; then + missing+=(ripgrep) +fi + +if [[ "${#missing[@]}" -gt 0 ]]; then + if [[ "$NO_INSTALL" -eq 1 ]]; then + echo "error: missing required tools: ${missing[*]}" >&2 + exit 1 + fi + + apt_install "${missing[@]}" +fi + +if ! has_dotnet_8_sdk; then + echo "error: dotnet 8 SDK is still unavailable after package installation." >&2 + exit 1 +fi + +if ! command -v rg >/dev/null 2>&1; then + echo "error: ripgrep is still unavailable after package installation." >&2 + exit 1 +fi + +"$ROOT_DIR/scripts/publish-win-x64.sh" + +echo +echo "Published Windows build:" +echo " $ROOT_DIR/src/Ehwrj.App/bin/Release/net8.0/win-x64/publish/Ehwrj.exe" diff --git a/scripts/capture-local-api.sh b/scripts/capture-local-api.sh new file mode 100755 index 0000000..a5b2c5a --- /dev/null +++ b/scripts/capture-local-api.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT_DIR="${1:-}" + +if [[ -n "$OUT_DIR" ]]; then + dotnet run --project "$ROOT_DIR/tools/Ehwrj.Tools.Capture/Ehwrj.Tools.Capture.csproj" -c Release -- --out "$OUT_DIR" +else + dotnet run --project "$ROOT_DIR/tools/Ehwrj.Tools.Capture/Ehwrj.Tools.Capture.csproj" -c Release +fi + diff --git a/scripts/package-win-x64.sh b/scripts/package-win-x64.sh new file mode 100755 index 0000000..c79e807 --- /dev/null +++ b/scripts/package-win-x64.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PUBLISH_DIR="$ROOT_DIR/src/Ehwrj.App/bin/Release/net8.0/win-x64/publish" +ARTIFACT_DIR="$ROOT_DIR/artifacts" +PACKAGE_DIR="$ARTIFACT_DIR/ehwrj-win-x64" +ZIP_PATH="$ARTIFACT_DIR/ehwrj-win-x64.zip" + +while [[ $# -gt 0 ]]; do + case "$1" in + --publish-dir) + PUBLISH_DIR="$2" + shift 2 + ;; + --zip) + ZIP_PATH="$2" + ARTIFACT_DIR="$(dirname "$ZIP_PATH")" + PACKAGE_DIR="$ARTIFACT_DIR/ehwrj-win-x64" + shift 2 + ;; + -h|--help) + cat <<'EOF' +Usage: + scripts/package-win-x64.sh [--publish-dir path] [--zip path] + +Creates a portable Windows x64 ZIP containing Ehwrj.exe, native DLLs, +README, SECURITY, LICENSE, RUNNING.txt, and SHA256SUMS.txt. +EOF + exit 0 + ;; + *) + echo "error: unknown argument: $1" >&2 + exit 2 + ;; + esac +done + +if [[ ! -x "$PUBLISH_DIR/Ehwrj.exe" ]]; then + echo "error: missing published Ehwrj.exe; run scripts/publish-win-x64.sh first." >&2 + exit 1 +fi + +rm -rf "$PACKAGE_DIR" +mkdir -p "$PACKAGE_DIR" + +cp "$PUBLISH_DIR/Ehwrj.exe" "$PACKAGE_DIR/" +cp "$PUBLISH_DIR"/*.dll "$PACKAGE_DIR/" +cp "$ROOT_DIR/README.md" "$PACKAGE_DIR/README.md" +cp "$ROOT_DIR/SECURITY.md" "$PACKAGE_DIR/SECURITY.md" +cp "$ROOT_DIR/LICENSE" "$PACKAGE_DIR/LICENSE" + +cat > "$PACKAGE_DIR/RUNNING.txt" <<'EOF' +Ehwrj portable Windows x64 build + +1. Start War Thunder. +2. Confirm the local map is available at http://127.0.0.1:8111/map_info.json. +3. Run Ehwrj.exe. +4. Use "Show overlay" in the left panel to enable the click-through overlay. + +Network scope: +- Ehwrj only reads the loopback War Thunder local API. +- It does not contact external hosts. + +Settings: +- Saved under %LOCALAPPDATA%\Ehwrj\settings.json. +EOF + +( + cd "$PACKAGE_DIR" + sha256sum * > SHA256SUMS.txt +) + +rm -f "$ZIP_PATH" +( + cd "$ARTIFACT_DIR" + zip -qr "$(basename "$ZIP_PATH")" "$(basename "$PACKAGE_DIR")" +) + +echo "$ZIP_PATH" diff --git a/scripts/publish-win-x64.sh b/scripts/publish-win-x64.sh new file mode 100755 index 0000000..e9ab49d --- /dev/null +++ b/scripts/publish-win-x64.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +dotnet restore "$ROOT_DIR/Ehwrj.sln" +dotnet format "$ROOT_DIR/Ehwrj.sln" --no-restore --verify-no-changes --verbosity minimal +dotnet build "$ROOT_DIR/Ehwrj.sln" -c Release --no-restore +dotnet run --project "$ROOT_DIR/tests/Ehwrj.Tests/Ehwrj.Tests.csproj" -c Release --no-build +"$ROOT_DIR/scripts/verify-safety.sh" +dotnet run --project "$ROOT_DIR/tools/Ehwrj.Tools.LocalApiStub/Ehwrj.Tools.LocalApiStub.csproj" -c Release --no-build -- --help >/dev/null +dotnet run --project "$ROOT_DIR/tools/Ehwrj.Tools.Capture/Ehwrj.Tools.Capture.csproj" -c Release --no-build -- --help >/dev/null +dotnet publish "$ROOT_DIR/src/Ehwrj.App/Ehwrj.App.csproj" \ + -c Release \ + -r win-x64 \ + --self-contained true \ + -p:PublishSingleFile=true +"$ROOT_DIR/scripts/package-win-x64.sh" >/dev/null diff --git a/scripts/run-local-api-stub.sh b/scripts/run-local-api-stub.sh new file mode 100755 index 0000000..5b5cf74 --- /dev/null +++ b/scripts/run-local-api-stub.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PORT="${1:-8111}" +FIXTURE_DIR="${2:-}" + +if [[ -n "$FIXTURE_DIR" ]]; then + dotnet run --project "$ROOT_DIR/tools/Ehwrj.Tools.LocalApiStub/Ehwrj.Tools.LocalApiStub.csproj" -c Release -- --port "$PORT" --fixture-dir "$FIXTURE_DIR" +else + dotnet run --project "$ROOT_DIR/tools/Ehwrj.Tools.LocalApiStub/Ehwrj.Tools.LocalApiStub.csproj" -c Release -- --port "$PORT" +fi diff --git a/scripts/validate-capture.sh b/scripts/validate-capture.sh new file mode 100755 index 0000000..a07ca63 --- /dev/null +++ b/scripts/validate-capture.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CAPTURE_DIR="${1:-captures/smoke}" + +dotnet run --project "$ROOT_DIR/tools/Ehwrj.Tools.Capture/Ehwrj.Tools.Capture.csproj" -c Release -- --validate "$CAPTURE_DIR" diff --git a/scripts/verify-safety.sh b/scripts/verify-safety.sh new file mode 100755 index 0000000..04c7f29 --- /dev/null +++ b/scripts/verify-safety.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SEARCH_ROOTS=( + "$ROOT_DIR/src" + "$ROOT_DIR/tests" + "$ROOT_DIR/tools" + "$ROOT_DIR/scripts" +) +URL_SEARCH_ROOTS=( + "$ROOT_DIR/src" + "$ROOT_DIR/tools" + "$ROOT_DIR/scripts" +) +RG_COMMON=(--glob '!verify-safety.sh' --glob '!**/bin/**' --glob '!**/obj/**') + +DISALLOWED_PATTERN='SetClipboard|OpenClipboard|GetClipboardData|AddClipboardFormatListener|UpdateResource|BeginUpdateResource|EndUpdateResource|SHGetSpecialFolderPath|CreateMutex|WindowsUpdate|zip_work|TARGET_PATH|--merge-env|CryptUnprotectData|Login Data|wallet\.dat' + +if rg "${RG_COMMON[@]}" -n "$DISALLOWED_PATTERN" "${SEARCH_ROOTS[@]}"; then + echo "error: disallowed malware-adjacent capability found" >&2 + exit 1 +fi + +URL_PATTERN='https?://[^"[:space:]]+' +if rg "${RG_COMMON[@]}" --glob '!*.axaml' -n "$URL_PATTERN" "${URL_SEARCH_ROOTS[@]}" \ + | rg -v 'http://(127\.0\.0\.1|localhost)(:[0-9]+)?(/[^"[:space:]]*)?' \ + | rg -v 'http://\{'; then + echo "error: non-loopback URL literal found" >&2 + exit 1 +fi + +echo "Safety scan passed." diff --git a/src/Ehwrj.App/App.axaml b/src/Ehwrj.App/App.axaml new file mode 100644 index 0000000..5af5e04 --- /dev/null +++ b/src/Ehwrj.App/App.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/src/Ehwrj.App/App.axaml.cs b/src/Ehwrj.App/App.axaml.cs new file mode 100644 index 0000000..2e9ca54 --- /dev/null +++ b/src/Ehwrj.App/App.axaml.cs @@ -0,0 +1,24 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +namespace Ehwrj.App; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } +} + diff --git a/src/Ehwrj.App/Ehwrj.App.csproj b/src/Ehwrj.App/Ehwrj.App.csproj new file mode 100644 index 0000000..107e704 --- /dev/null +++ b/src/Ehwrj.App/Ehwrj.App.csproj @@ -0,0 +1,20 @@ + + + WinExe + net8.0 + Ehwrj.App + Ehwrj + false + + + + + + + + + + + + + diff --git a/src/Ehwrj.App/Infrastructure/Win32.cs b/src/Ehwrj.App/Infrastructure/Win32.cs new file mode 100644 index 0000000..d714a91 --- /dev/null +++ b/src/Ehwrj.App/Infrastructure/Win32.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace Ehwrj.App.Infrastructure; + +internal static class Win32 +{ + private const int GwlExStyle = -20; + private const int WsExTransparent = 0x00000020; + private const int WsExToolWindow = 0x00000080; + private const int WsExLayered = 0x00080000; + private const int WsExNoActivate = 0x08000000; + + public static void MakeOverlayClickThrough(IntPtr hwnd) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + var style = GetWindowLongPtr(hwnd, GwlExStyle); + style |= WsExTransparent | WsExToolWindow | WsExLayered | WsExNoActivate; + _ = SetWindowLongPtr(hwnd, GwlExStyle, style); + } + + [DllImport("user32.dll", EntryPoint = "GetWindowLongPtrW", SetLastError = true)] + private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = true)] + private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); +} diff --git a/src/Ehwrj.App/MainWindow.axaml b/src/Ehwrj.App/MainWindow.axaml new file mode 100644 index 0000000..e99e0b0 --- /dev/null +++ b/src/Ehwrj.App/MainWindow.axaml @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + +