mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-19 03:33:56 +00:00
Compare commits
4 Commits
e0c386abbb
...
32bit-emul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b76880cf1 | ||
|
|
60931da92a | ||
|
|
4b5d82079c | ||
|
|
9c2a0d946e |
@@ -1,2 +0,0 @@
|
||||
**/*.hxx
|
||||
deps/**/*
|
||||
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@@ -3,23 +3,23 @@ updates:
|
||||
- package-ecosystem: gitsubmodule
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 100
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 100
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/page"
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 100
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/src/backends/icicle-emulator/icicle-bridge/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 100
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
355
.github/workflows/build.yml
vendored
355
.github/workflows/build.yml
vendored
@@ -1,9 +1,11 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "**"
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
@@ -19,36 +21,27 @@ on:
|
||||
- "true"
|
||||
- "false"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
clang-tidy:
|
||||
name: Run Clang Tidy
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
LLVM_VERSION: 21
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
name: Run Clang Tidy
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
LLVM_VERSION: 20
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Ninja
|
||||
uses: seanmiddleditch/gha-setup-ninja@v6
|
||||
- name: Install Ninja
|
||||
uses: seanmiddleditch/gha-setup-ninja@v6
|
||||
|
||||
- name: Install Clang
|
||||
uses: nick-fields/retry@v3.0.2
|
||||
with:
|
||||
max_attempts: 5
|
||||
timeout_minutes: 15
|
||||
retry_wait_seconds: 60
|
||||
command: |
|
||||
- name: Install Clang
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh ${{ env.LLVM_VERSION }}
|
||||
@@ -59,21 +52,22 @@ jobs:
|
||||
sudo update-alternatives --set cc /usr/bin/clang-${{ env.LLVM_VERSION }}
|
||||
sudo update-alternatives --set c++ /usr/bin/clang++-${{ env.LLVM_VERSION }}
|
||||
|
||||
- name: CMake Build
|
||||
run: cmake --preset=release -DMOMO_ENABLE_CLANG_TIDY=On && cmake --build --preset=release
|
||||
- name: CMake Build
|
||||
run: cmake --preset=release -DMOMO_ENABLE_CLANG_TIDY=On && cmake --build --preset=release
|
||||
|
||||
verify-formatting:
|
||||
name: Verify Formatting
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Verify Formatting
|
||||
uses: jidicula/clang-format-action@v4.16.0
|
||||
uses: jidicula/clang-format-action@v4.15.0
|
||||
with:
|
||||
clang-format-version: "21"
|
||||
|
||||
clang-format-version: '20'
|
||||
check-path: 'src'
|
||||
|
||||
- name: Verify Page Formatting
|
||||
run: cd page && npx --yes prettier . --check
|
||||
|
||||
@@ -82,7 +76,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -96,7 +90,7 @@ jobs:
|
||||
run: cmake --preset=release && cmake --build --preset=release -t dump-apiset
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: pyTooling/upload-artifact@v6
|
||||
uses: pyTooling/upload-artifact@v4
|
||||
with:
|
||||
name: Temp API Set Dumper
|
||||
working-directory: build/release/artifacts/
|
||||
@@ -113,29 +107,29 @@ jobs:
|
||||
platform:
|
||||
- Windows 2025
|
||||
- Windows 2022
|
||||
#- Windows 2019
|
||||
- Windows 2019
|
||||
include:
|
||||
- platform: Windows 2025
|
||||
runner: windows-2025
|
||||
- platform: Windows 2022
|
||||
runner: windows-2022
|
||||
#- platform: Windows 2019
|
||||
# runner: windows-2019
|
||||
- platform: Windows 2019
|
||||
runner: windows-2019
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download DirectX Runtime
|
||||
run: curl --connect-timeout 20 --max-time 200 --retry 5 --retry-delay 2 --retry-max-time 200 -L -o directx_Jun2010_redist.exe https://download.microsoft.com/download/8/4/A/84A35BF1-DAFE-4AE8-82AF-AD2AE20B6B14/directx_Jun2010_redist.exe
|
||||
|
||||
- name: Extract DirectX Runtime
|
||||
run: 'cmd /c "start /wait directx_Jun2010_redist.exe /Q /T:${{github.workspace}}/dxrt"'
|
||||
run: "cmd /c \"start /wait directx_Jun2010_redist.exe /Q /T:${{github.workspace}}/dxrt\""
|
||||
|
||||
- name: Install DirectX Runtime
|
||||
run: "cmd /c \"start /wait .\\dxrt\\dxsetup.exe /silent\""
|
||||
|
||||
- name: Download API Set Dumper
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Temp API Set Dumper
|
||||
path: build/release/artifacts
|
||||
@@ -147,7 +141,7 @@ jobs:
|
||||
run: cd root && ../build/release/artifacts/dump-apiset.exe
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: pyTooling/upload-artifact@v6
|
||||
uses: pyTooling/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform }} Emulation Root
|
||||
path: "*"
|
||||
@@ -163,7 +157,6 @@ jobs:
|
||||
platform:
|
||||
- Windows x86
|
||||
- Windows x86_64
|
||||
- MinGW x86_64
|
||||
- Linux x86_64 GCC
|
||||
- Linux x86_64 GCC Sanitizer
|
||||
- Linux x86_64 Clang
|
||||
@@ -173,14 +166,13 @@ jobs:
|
||||
- Android x86_64
|
||||
- Android arm64-v8a
|
||||
- Emscripten Web
|
||||
- Emscripten Web Memory 64
|
||||
- Emscripten Node.js
|
||||
configuration:
|
||||
#- Debug
|
||||
- Debug
|
||||
- Release
|
||||
include:
|
||||
#- configuration: Debug
|
||||
# preset: debug
|
||||
- configuration: Debug
|
||||
preset: debug
|
||||
- configuration: Release
|
||||
preset: release
|
||||
- platform: Windows x86
|
||||
@@ -189,10 +181,6 @@ jobs:
|
||||
- platform: Windows x86_64
|
||||
runner: windows-latest
|
||||
devcmd_arch: x64
|
||||
- platform: MinGW x86_64
|
||||
runner: ubuntu-24.04
|
||||
rust-target: x86_64-pc-windows-gnu
|
||||
cmake-options: "-DCMAKE_TOOLCHAIN_FILE=$GITHUB_WORKSPACE/cmake/toolchain/mingw-w64.cmake"
|
||||
- platform: Linux x86_64 GCC Sanitizer
|
||||
runner: ubuntu-24.04
|
||||
cmake-options: "-DMOMO_ENABLE_SANITIZER=On"
|
||||
@@ -200,7 +188,7 @@ jobs:
|
||||
runner: ubuntu-24.04
|
||||
- platform: Linux x86_64 Clang
|
||||
runner: ubuntu-24.04
|
||||
clang-version: 21
|
||||
clang-version: 20
|
||||
- platform: iOS arm64
|
||||
runner: macos-latest
|
||||
rust-target: aarch64-apple-ios
|
||||
@@ -208,7 +196,7 @@ jobs:
|
||||
- platform: macOS arm64
|
||||
runner: macos-latest
|
||||
- platform: macOS x86_64
|
||||
runner: macos-15-intel
|
||||
runner: macos-13
|
||||
- platform: Android x86_64
|
||||
runner: ubuntu-24.04
|
||||
abi: x86_64
|
||||
@@ -222,15 +210,12 @@ jobs:
|
||||
- platform: Emscripten Web
|
||||
runner: ubuntu-24.04
|
||||
cmake-options: "-DMOMO_ENABLE_RUST_CODE=Off -DCMAKE_TOOLCHAIN_FILE=$(dirname $(which emcc))/cmake/Modules/Platform/Emscripten.cmake"
|
||||
- platform: Emscripten Web Memory 64
|
||||
runner: ubuntu-24.04
|
||||
cmake-options: "-DMOMO_ENABLE_RUST_CODE=Off -DMOMO_EMSCRIPTEN_MEMORY64=On -DCMAKE_TOOLCHAIN_FILE=$(dirname $(which emcc))/cmake/Modules/Platform/Emscripten.cmake"
|
||||
- platform: Emscripten Node.js
|
||||
runner: ubuntu-24.04
|
||||
cmake-options: "-DMOMO_EMSCRIPTEN_SUPPORT_NODEJS=On -DMOMO_ENABLE_RUST_CODE=Off -DCMAKE_TOOLCHAIN_FILE=$(dirname $(which emcc))/cmake/Modules/Platform/Emscripten.cmake"
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -246,27 +231,16 @@ jobs:
|
||||
uses: mymindstorm/setup-emsdk@v14
|
||||
|
||||
- name: Install Clang
|
||||
uses: nick-fields/retry@v3.0.2
|
||||
if: "${{ matrix.platform == 'Linux x86_64 Clang' }}"
|
||||
with:
|
||||
max_attempts: 5
|
||||
timeout_minutes: 15
|
||||
retry_wait_seconds: 60
|
||||
command: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh ${{ matrix.clang-version }}
|
||||
sudo apt install -y clang-${{ matrix.clang-version }} lld-${{ matrix.clang-version }}
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${{ matrix.clang-version }} 100
|
||||
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-${{ matrix.clang-version }} 100
|
||||
sudo update-alternatives --set cc /usr/bin/clang-${{ matrix.clang-version }}
|
||||
sudo update-alternatives --set c++ /usr/bin/clang++-${{ matrix.clang-version }}
|
||||
|
||||
- name: Set up MinGW
|
||||
uses: egor-tensin/setup-mingw@v3
|
||||
if: "${{ startsWith(matrix.platform, 'MinGW') }}"
|
||||
with:
|
||||
platform: x64
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh ${{ matrix.clang-version }}
|
||||
sudo apt install -y clang-${{ matrix.clang-version }} lld-${{ matrix.clang-version }}
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${{ matrix.clang-version }} 100
|
||||
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-${{ matrix.clang-version }} 100
|
||||
sudo update-alternatives --set cc /usr/bin/clang-${{ matrix.clang-version }}
|
||||
sudo update-alternatives --set c++ /usr/bin/clang++-${{ matrix.clang-version }}
|
||||
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
@@ -281,7 +255,7 @@ jobs:
|
||||
ndk-version: r26d
|
||||
add-to-path: false
|
||||
|
||||
- name: Setup Android Environment Variables
|
||||
- name: Setup Environment Variables
|
||||
shell: bash
|
||||
if: ${{ startsWith(matrix.platform, 'Android') }}
|
||||
run: |
|
||||
@@ -292,7 +266,7 @@ jobs:
|
||||
run: cmake --preset=${{matrix.preset}} ${{matrix.cmake-options}} && cmake --build --preset=${{matrix.preset}}
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: pyTooling/upload-artifact@v6
|
||||
uses: pyTooling/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform }} ${{matrix.configuration}} Artifacts
|
||||
working-directory: build/${{matrix.preset}}/artifacts/
|
||||
@@ -300,67 +274,12 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Test Configuration
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: Temp ${{ matrix.platform }} ${{matrix.configuration}} Test Config
|
||||
path: "build/${{matrix.preset}}/**/CTestTestfile.cmake"
|
||||
retention-days: 1
|
||||
|
||||
# Release is the same as Release from build step
|
||||
# However, that way the win tests can start without
|
||||
# waiting for other platforms
|
||||
build-isolate:
|
||||
name: Build Isolate
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- Windows x86_64
|
||||
configuration:
|
||||
- Debug
|
||||
- Release
|
||||
include:
|
||||
- configuration: Debug
|
||||
preset: debug
|
||||
- configuration: Release
|
||||
preset: release
|
||||
- platform: Windows x86_64
|
||||
runner: windows-latest
|
||||
devcmd_arch: x64
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Ninja
|
||||
uses: seanmiddleditch/gha-setup-ninja@v6
|
||||
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
if: ${{ startsWith(matrix.platform, 'Windows') }}
|
||||
with:
|
||||
arch: ${{ matrix.devcmd_arch }}
|
||||
|
||||
- name: CMake Build
|
||||
run: cmake --preset=${{matrix.preset}} ${{matrix.cmake-options}} && cmake --build --preset=${{matrix.preset}}
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: pyTooling/upload-artifact@v6
|
||||
with:
|
||||
name: Temp Isolate ${{ matrix.platform }} ${{matrix.configuration}} Artifacts
|
||||
working-directory: build/${{matrix.preset}}/artifacts/
|
||||
path: "*"
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Test Configuration
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
with:
|
||||
name: Temp Isolate ${{ matrix.platform }} ${{matrix.configuration}} Test Config
|
||||
path: "build/${{matrix.preset}}/**/CTestTestfile.cmake"
|
||||
retention-days: 1
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ${{ matrix.runner }}
|
||||
@@ -384,11 +303,11 @@ jobs:
|
||||
- Windows 2022
|
||||
#- Windows 2019
|
||||
configuration:
|
||||
#- Debug
|
||||
- Debug
|
||||
- Release
|
||||
include:
|
||||
#- configuration: Debug
|
||||
# preset: debug
|
||||
- configuration: Debug
|
||||
preset: debug
|
||||
- configuration: Release
|
||||
preset: release
|
||||
- platform: Windows x86
|
||||
@@ -404,10 +323,10 @@ jobs:
|
||||
- platform: macOS arm64
|
||||
runner: macos-latest
|
||||
- platform: macOS x86_64
|
||||
runner: macos-15-intel
|
||||
runner: macos-13
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -419,26 +338,26 @@ jobs:
|
||||
echo "EMULATOR_ICICLE=${{ matrix.emulator == 'Icicle' }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Download Test Configuration
|
||||
uses: actions/download-artifact@v7.0.0
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: Temp ${{ matrix.platform }} ${{matrix.configuration}} Test Config
|
||||
path: build/${{matrix.preset}}
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform }} ${{matrix.configuration}} Artifacts
|
||||
path: build/${{matrix.preset}}/artifacts
|
||||
|
||||
- name: Download Windows Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
if: "${{ matrix.platform != 'Windows x86_64' }}"
|
||||
with:
|
||||
name: Windows x86_64 Release Artifacts
|
||||
path: build/${{matrix.preset}}/artifacts
|
||||
|
||||
|
||||
- name: Download Emulation Root
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.emulation-root }} Emulation Root
|
||||
path: build/${{matrix.preset}}/artifacts/root
|
||||
@@ -457,28 +376,20 @@ jobs:
|
||||
win-test:
|
||||
name: Windows Test
|
||||
runs-on: windows-latest
|
||||
needs: [create-emulation-root, build-isolate]
|
||||
needs: [create-emulation-root, build]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- Debug
|
||||
- Release
|
||||
emulator:
|
||||
- Unicorn
|
||||
- Icicle
|
||||
emulation-root:
|
||||
- Windows 2025
|
||||
- Windows 2022
|
||||
#- Windows 2019
|
||||
include:
|
||||
- configuration: Debug
|
||||
preset: debug
|
||||
- configuration: Release
|
||||
preset: release
|
||||
- Windows 2019
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -490,52 +401,58 @@ jobs:
|
||||
echo "EMULATOR_ICICLE=${{ matrix.emulator == 'Icicle' }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Download Test Configuration
|
||||
uses: actions/download-artifact@v7.0.0
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: Temp Isolate Windows x86_64 ${{ matrix.configuration}} Test Config
|
||||
path: build/${{ matrix.preset }}
|
||||
name: Temp Windows x86_64 Release Test Config
|
||||
path: build/release
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Temp Isolate Windows x86_64 ${{ matrix.configuration}} Artifacts
|
||||
path: build/${{ matrix.preset }}/artifacts
|
||||
name: Windows x86_64 Release Artifacts
|
||||
path: build/release/artifacts
|
||||
|
||||
- name: Download Emulation Root
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.emulation-root }} Emulation Root
|
||||
path: build/${{ matrix.preset }}/artifacts/root
|
||||
path: build/release/artifacts/root
|
||||
|
||||
- name: Copy Test Sample
|
||||
run: cp build/${{ matrix.preset }}/artifacts/test-sample.exe build/${{ matrix.preset }}/artifacts/root/filesys/c/
|
||||
run: cp build/release/artifacts/test-sample.exe build/release/artifacts/root/filesys/c/
|
||||
|
||||
- name: CMake Test
|
||||
run: cd build/${{ matrix.preset }} && ctest --verbose -j
|
||||
run: cd build/release && ctest --verbose -j
|
||||
env:
|
||||
EMULATOR_ROOT: ${{github.workspace}}/build/${{ matrix.preset }}/artifacts/root
|
||||
EMULATOR_ROOT: ${{github.workspace}}/build/release/artifacts/root
|
||||
EMULATOR_VERBOSE: ${{ github.event.inputs.verbose }}
|
||||
ANALYSIS_SAMPLE: ${{github.workspace}}/build/${{ matrix.preset }}/artifacts/test-sample.exe
|
||||
ANALYSIS_SAMPLE: ${{github.workspace}}/build/release/artifacts/test-sample.exe
|
||||
|
||||
|
||||
smoke-test-node:
|
||||
name: Smoke Test Node.js
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [create-emulation-root, build]
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Emscripten Node.js Release Artifacts
|
||||
path: build/release/artifacts
|
||||
|
||||
- name: Download Windows Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Windows x86_64 Release Artifacts
|
||||
path: build/release/artifacts
|
||||
|
||||
|
||||
- name: Download Emulation Root
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Windows 2022 Emulation Root
|
||||
path: build/release/artifacts/root
|
||||
@@ -549,31 +466,6 @@ jobs:
|
||||
EMULATOR_ROOT: ${{github.workspace}}/build/release/artifacts/root
|
||||
EMULATOR_VERBOSE: ${{ github.event.inputs.verbose }}
|
||||
|
||||
smoke-test-mingw:
|
||||
name: Smoke Test MinGW x86_64
|
||||
runs-on: windows-latest
|
||||
needs: [create-emulation-root, build]
|
||||
steps:
|
||||
- name: Download Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
with:
|
||||
name: MinGW x86_64 Release Artifacts
|
||||
path: build/release/artifacts
|
||||
|
||||
- name: Download Emulation Root
|
||||
uses: pyTooling/download-artifact@v7
|
||||
with:
|
||||
name: Windows 2022 Emulation Root
|
||||
path: build/release/artifacts/root
|
||||
|
||||
- name: Copy Test Sample
|
||||
run: cp build/release/artifacts/test-sample.exe build/release/artifacts/root/filesys/c/
|
||||
|
||||
- name: CMake Test
|
||||
run: cd build/release/artifacts && ./windows-emulator-test.exe && ./analyzer.exe -e root c:/test-sample.exe
|
||||
env:
|
||||
EMULATOR_ROOT: ${{github.workspace}}/build/release/artifacts/root
|
||||
EMULATOR_VERBOSE: ${{ github.event.inputs.verbose }}
|
||||
|
||||
smoke-test-android:
|
||||
name: Smoke Test Android
|
||||
@@ -593,11 +485,11 @@ jobs:
|
||||
- Windows 2022
|
||||
#- Windows 2019
|
||||
configuration:
|
||||
#- Debug
|
||||
- Debug
|
||||
- Release
|
||||
include:
|
||||
#- configuration: Debug
|
||||
# preset: debug
|
||||
- configuration: Debug
|
||||
preset: debug
|
||||
- configuration: Release
|
||||
preset: release
|
||||
- architecture: x86_64
|
||||
@@ -613,19 +505,19 @@ jobs:
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Android ${{matrix.architecture}} ${{matrix.configuration}} Artifacts
|
||||
path: build/${{matrix.preset}}/artifacts
|
||||
|
||||
- name: Download Windows Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Windows x86_64 Release Artifacts
|
||||
path: build/${{matrix.preset}}/artifacts
|
||||
|
||||
|
||||
- name: Download Emulation Root
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.emulation-root }} Emulation Root
|
||||
path: build/${{matrix.preset}}/artifacts/root
|
||||
@@ -634,11 +526,11 @@ jobs:
|
||||
run: cp build/${{matrix.preset}}/artifacts/test-sample.exe build/${{matrix.preset}}/artifacts/root/filesys/c/
|
||||
|
||||
- name: Run Test
|
||||
uses: reactivecircus/android-emulator-runner@v2.35.0
|
||||
uses: reactivecircus/android-emulator-runner@v2.34.0
|
||||
with:
|
||||
api-level: 29
|
||||
arch: ${{matrix.architecture}}
|
||||
script: 'adb push build/${{matrix.preset}}/artifacts/* /data/local/tmp && adb shell "cd /data/local/tmp && export LD_LIBRARY_PATH=. && chmod +x ./analyzer && EMULATOR_ICICLE=${{ matrix.emulator == ''Icicle'' }} ./analyzer -e ./root c:/test-sample.exe"'
|
||||
script: "adb push build/${{matrix.preset}}/artifacts/* /data/local/tmp && adb shell \"cd /data/local/tmp && export LD_LIBRARY_PATH=. && chmod +x ./analyzer && EMULATOR_ICICLE=${{ matrix.emulator == 'Icicle' }} ./analyzer -e ./root c:/test-sample.exe\""
|
||||
|
||||
build-page:
|
||||
name: Build Page
|
||||
@@ -646,34 +538,28 @@ jobs:
|
||||
needs: [create-emulation-root, build]
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download Emscripten Web Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Emscripten Web Release Artifacts
|
||||
path: build/release/artifacts/32
|
||||
|
||||
- name: Download Emscripten Web Memory 64 Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
with:
|
||||
name: Emscripten Web Memory 64 Release Artifacts
|
||||
path: build/release/artifacts/64
|
||||
|
||||
path: build/release/artifacts
|
||||
|
||||
- name: Download Windows Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Windows x86_64 Release Artifacts
|
||||
path: build/release/artifacts
|
||||
|
||||
|
||||
- name: Download Emulation Root
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Windows 2022 Emulation Root
|
||||
path: build/release/artifacts/root
|
||||
|
||||
|
||||
- name: Copy Sample
|
||||
run: cp ./build/release/artifacts/test-sample.exe build/release/artifacts/root/filesys/c/
|
||||
|
||||
@@ -682,18 +568,14 @@ jobs:
|
||||
|
||||
- name: Copy Files
|
||||
run: |
|
||||
mkdir -p ./page/public/32/
|
||||
mkdir -p ./page/public/64/
|
||||
cp ./build/release/artifacts/32/analyzer.js ./page/public/32/
|
||||
cp ./build/release/artifacts/32/analyzer.wasm ./page/public/32/
|
||||
cp ./build/release/artifacts/64/analyzer.js ./page/public/64/
|
||||
cp ./build/release/artifacts/64/analyzer.wasm ./page/public/64/
|
||||
cp ./build/release/artifacts/analyzer.js ./page/public/
|
||||
cp ./build/release/artifacts/analyzer.wasm ./page/public/
|
||||
|
||||
- name: Build Page
|
||||
run: cd ./page && npm i && npm run build
|
||||
|
||||
|
||||
- name: Upload Page Artifacts
|
||||
uses: pyTooling/upload-artifact@v6
|
||||
uses: pyTooling/upload-artifact@v4
|
||||
with:
|
||||
name: Page Artifacts
|
||||
working-directory: page/dist/
|
||||
@@ -711,16 +593,16 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Download Page Artifacts
|
||||
uses: pyTooling/download-artifact@v7
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: Page Artifacts
|
||||
path: ./page/dist/
|
||||
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./page/dist
|
||||
|
||||
@@ -731,27 +613,12 @@ jobs:
|
||||
summary:
|
||||
name: Pipeline Summary
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
[
|
||||
build-page,
|
||||
clang-tidy,
|
||||
build-apiset-dumper,
|
||||
smoke-test-node,
|
||||
smoke-test-mingw,
|
||||
smoke-test-android,
|
||||
create-emulation-root,
|
||||
build,
|
||||
build-isolate,
|
||||
test,
|
||||
win-test,
|
||||
verify-formatting,
|
||||
]
|
||||
needs: [build-page, clang-tidy, build-apiset-dumper, smoke-test-node, smoke-test-android, create-emulation-root, build, test, win-test, verify-formatting]
|
||||
if: always()
|
||||
steps:
|
||||
- uses: geekyeggo/delete-artifact@v5
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: "Temp *"
|
||||
name: 'Temp *'
|
||||
|
||||
- name: Pipeline failed
|
||||
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -147,15 +147,7 @@ UpgradeLog*.htm
|
||||
user*.bat
|
||||
|
||||
.vscode/
|
||||
.cache/
|
||||
compile_commands.json
|
||||
|
||||
page/public/analyzer.js
|
||||
page/public/analyzer.wasm
|
||||
page/public/debugger.js
|
||||
page/public/debugger.wasm
|
||||
page/public/root.zip
|
||||
page/public/32/analyzer.js
|
||||
page/public/32/analyzer.wasm
|
||||
page/public/64/analyzer.js
|
||||
page/public/64/analyzer.wasm
|
||||
|
||||
23
.gitmodules
vendored
23
.gitmodules
vendored
@@ -1,8 +1,8 @@
|
||||
[submodule "deps/unicorn"]
|
||||
path = deps/unicorn
|
||||
url = https://github.com/momo5502/unicorn.git
|
||||
url = ../unicorn.git
|
||||
shallow = true
|
||||
branch = dev
|
||||
branch = wasm
|
||||
[submodule "deps/reflect"]
|
||||
path = deps/reflect
|
||||
url = https://github.com/qlibs/reflect.git
|
||||
@@ -10,30 +10,11 @@
|
||||
[submodule "deps/googletest"]
|
||||
path = deps/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
shallow = true
|
||||
[submodule "deps/zlib"]
|
||||
path = deps/zlib
|
||||
url = https://github.com/madler/zlib.git
|
||||
branch = develop
|
||||
ignore = dirty
|
||||
shallow = true
|
||||
[submodule "deps/gtest-parallel"]
|
||||
path = deps/gtest-parallel
|
||||
url = https://github.com/google/gtest-parallel.git
|
||||
shallow = true
|
||||
[submodule "deps/flatbuffers"]
|
||||
path = deps/flatbuffers
|
||||
url = https://github.com/google/flatbuffers.git
|
||||
shallow = true
|
||||
[submodule "deps/base64"]
|
||||
path = deps/base64
|
||||
url = https://github.com/tobiaslocker/base64.git
|
||||
shallow = true
|
||||
[submodule "deps/minidump_cpp"]
|
||||
path = deps/minidump_cpp
|
||||
url = https://github.com/redthing1/minidump_cpp
|
||||
shallow = true
|
||||
[submodule "deps/capstone"]
|
||||
path = deps/capstone
|
||||
url = https://github.com/capstone-engine/capstone.git
|
||||
shallow = true
|
||||
@@ -6,8 +6,8 @@ option(MOMO_ENABLE_AVX2 "Enable AVX2 support" ON)
|
||||
option(MOMO_ENABLE_SANITIZER "Enable sanitizer" OFF)
|
||||
option(MOMO_ENABLE_CLANG_TIDY "Enable clang-tidy checks" OFF)
|
||||
option(MOMO_ENABLE_RUST_CODE "Enable code parts written in rust" ON)
|
||||
option(MOMO_EMSCRIPTEN_MEMORY64 "Enable memory 64 support for emscripten builds" OFF)
|
||||
option(MOMO_EMSCRIPTEN_SUPPORT_NODEJS "Enable Node.js filesystem for emscripten compilation" OFF)
|
||||
option(MOMO_BUILD_AS_LIBRARY "Configure and Build the emulator as a shared library (without the samples and tests)" OFF)
|
||||
|
||||
set(MOMO_REFLECTION_LEVEL "0" CACHE STRING "Reflection level for the build")
|
||||
message(STATUS "Reflection level is set to: ${MOMO_REFLECTION_LEVEL}")
|
||||
@@ -29,25 +29,14 @@ endif()
|
||||
# It doesn't support it, even if it thinks it does...
|
||||
set(ENV{ARCHFLAGS} "nope")
|
||||
|
||||
|
||||
##########################################
|
||||
|
||||
project(sogen LANGUAGES C CXX)
|
||||
project(emulator LANGUAGES C CXX)
|
||||
enable_testing()
|
||||
|
||||
##########################################
|
||||
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
set(MOMO_IS_SUBPROJECT OFF)
|
||||
else()
|
||||
set(MOMO_IS_SUBPROJECT ON)
|
||||
endif()
|
||||
|
||||
##########################################
|
||||
|
||||
option(SOGEN_BUILD_STATIC "Build sogen as static libraries for embedding (e.g., IDA plugins)" ${MOMO_IS_SUBPROJECT})
|
||||
|
||||
##########################################
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
##########################################
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"build"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
16
README.md
16
README.md
@@ -1,27 +1,21 @@
|
||||
<img src="./docs/images/cover.png" />
|
||||
<h1 align="center">
|
||||
<img src="https://momo5502.com/emulator/banner.png" height="200" />
|
||||
Sogen
|
||||
<br>
|
||||
<a href="https://github.com/momo5502/sogen?tab=GPL-2.0-1-ov-file"><img src="https://img.shields.io/github/license/momo5502/sogen?color=00B0F8"/></a>
|
||||
<a href="https://github.com/momo5502/sogen/actions"><img src="https://img.shields.io/github/actions/workflow/status/momo5502/sogen/build.yml?branch=main&label=build"/></a>
|
||||
<a href="https://github.com/momo5502/sogen/issues"><img src="https://img.shields.io/github/issues/momo5502/sogen?color=F8B000"/></a>
|
||||
<img src="https://img.shields.io/github/commit-activity/m/momo5502/sogen?color=FF3131"/>
|
||||
<a href="https://deepwiki.com/momo5502/sogen"><img src="https://deepwiki.com/badge.svg"/></a>
|
||||
</h1>
|
||||
|
||||
Sogen is a high-performance Windows user space emulator that operates at syscall level, providing full control over process execution through comprehensive hooking capabilities.
|
||||
|
||||
Perfect for security research, malware analysis, and DRM research where fine-grained control over process execution is required.
|
||||
|
||||
Built in C++ and powered by the [Unicorn Engine](https://github.com/unicorn-engine/unicorn) or the [icicle-emu](https://github.com/icicle-emu/icicle-emu).
|
||||
Built in C++ and powered by the [Unicorn Engine](https://github.com/unicorn-engine/unicorn) (or the [icicle-emu](https://github.com/icicle-emu/icicle-emu) 🆕).
|
||||
|
||||
Try it out: <a href="https://sogen.dev">sogen.dev</a>
|
||||
|
||||
<hr>
|
||||
|
||||
> [!WARNING]
|
||||
> Caution is advised when analyzing malware in Sogen, as host isolation might not be perfect.
|
||||
> To mitigate potential risk, use the <a href="https://sogen.dev/#/playground">web version</a> to benefit from the additional safety provided by your browser's sandbox.
|
||||
|
||||
## Key Features
|
||||
|
||||
* 🔄 __Syscall-Level Emulation__
|
||||
@@ -39,6 +33,10 @@ Try it out: <a href="https://sogen.dev">sogen.dev</a>
|
||||
* 💻 __Debugging Interface__
|
||||
* Implements GDB serial protocol for integration with common debugging tools (IDA Pro, GDB, LLDB, VS Code, ...)
|
||||
|
||||
##
|
||||
> [!NOTE]
|
||||
> The project is still in a very early, prototypical state. The code still needs a lot of cleanup and many features and syscalls need to be implemented. However, constant progress is being made :)
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||
|
||||
@@ -25,28 +25,21 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
##########################################
|
||||
|
||||
if(NOT MINGW AND NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||
if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
|
||||
endif()
|
||||
|
||||
##########################################
|
||||
|
||||
if(SOGEN_BUILD_STATIC)
|
||||
add_compile_definitions(SOGEN_BUILD_STATIC=1)
|
||||
if(MOMO_BUILD_AS_LIBRARY)
|
||||
add_compile_definitions(MOMO_BUILD_AS_LIBRARY=1)
|
||||
else()
|
||||
add_compile_definitions(SOGEN_BUILD_STATIC=0)
|
||||
add_compile_definitions(MOMO_BUILD_AS_LIBRARY=0)
|
||||
endif()
|
||||
|
||||
##########################################
|
||||
|
||||
set(MOMO_ENABLE_RUST OFF)
|
||||
if(MOMO_ENABLE_RUST_CODE AND NOT MINGW AND NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||
set(MOMO_ENABLE_RUST ON)
|
||||
endif()
|
||||
|
||||
##########################################
|
||||
|
||||
if(MOMO_ENABLE_RUST)
|
||||
if(MOMO_ENABLE_RUST_CODE)
|
||||
add_compile_definitions(MOMO_ENABLE_RUST_CODE=1)
|
||||
else()
|
||||
add_compile_definitions(MOMO_ENABLE_RUST_CODE=0)
|
||||
@@ -63,21 +56,6 @@ endif()
|
||||
|
||||
##########################################
|
||||
|
||||
if(MINGW)
|
||||
add_link_options(
|
||||
-static-libstdc++
|
||||
-static-libgcc
|
||||
-static
|
||||
-lwinpthread
|
||||
)
|
||||
|
||||
momo_add_c_and_cxx_compile_options(
|
||||
-Wno-array-bounds
|
||||
)
|
||||
endif()
|
||||
|
||||
##########################################
|
||||
|
||||
if(LINUX)
|
||||
add_link_options(
|
||||
-Wl,--no-undefined
|
||||
@@ -114,40 +92,21 @@ if(CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||
momo_add_c_and_cxx_compile_options(
|
||||
-fexceptions
|
||||
-ftrivial-auto-var-init=zero
|
||||
-Wno-dollar-in-identifier-extension
|
||||
)
|
||||
|
||||
add_link_options(
|
||||
-fexceptions
|
||||
-sALLOW_MEMORY_GROWTH=1
|
||||
$<$<CONFIG:Debug>:-sASSERTIONS>
|
||||
-sASSERTIONS
|
||||
-sWASM_BIGINT
|
||||
#-sUSE_OFFSET_CONVERTER
|
||||
-sUSE_OFFSET_CONVERTER
|
||||
-sMAXIMUM_MEMORY=4gb
|
||||
#-sEXCEPTION_CATCHING_ALLOWED=[..]
|
||||
-sEXIT_RUNTIME
|
||||
-sASYNCIFY
|
||||
)
|
||||
|
||||
if(MOMO_EMSCRIPTEN_MEMORY64)
|
||||
momo_add_c_and_cxx_compile_options(
|
||||
-sMEMORY64
|
||||
)
|
||||
|
||||
add_link_options(
|
||||
-sMAXIMUM_MEMORY=8gb
|
||||
-sMEMORY64
|
||||
)
|
||||
else()
|
||||
add_link_options(
|
||||
-sMAXIMUM_MEMORY=4gb
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MOMO_EMSCRIPTEN_SUPPORT_NODEJS)
|
||||
add_compile_definitions(
|
||||
MOMO_EMSCRIPTEN_SUPPORT_NODEJS=1
|
||||
)
|
||||
|
||||
add_link_options(
|
||||
-lnodefs.js -sNODERAWFS=1
|
||||
-sENVIRONMENT=node
|
||||
@@ -155,10 +114,7 @@ if(CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||
)
|
||||
else()
|
||||
add_link_options(
|
||||
-lidbfs.js
|
||||
-sENVIRONMENT=worker
|
||||
-sINVOKE_RUN=0
|
||||
-sEXPORTED_RUNTIME_METHODS=['callMain']
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
@@ -185,6 +141,10 @@ if(MSVC)
|
||||
/INCREMENTAL:NO
|
||||
)
|
||||
|
||||
momo_add_c_and_cxx_release_compile_options(
|
||||
/Ob2
|
||||
)
|
||||
|
||||
add_compile_definitions(
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_CRT_NONSTDC_NO_WARNINGS
|
||||
@@ -217,33 +177,10 @@ if(MOMO_ENABLE_SANITIZER)
|
||||
endif()
|
||||
|
||||
##########################################
|
||||
# MSVC Runtime Library Selection
|
||||
#
|
||||
# Default is dynamic runtime (/MD or /MDd) to enforce shared allocators
|
||||
# between emulator and implementation.
|
||||
#
|
||||
# Use SOGEN_STATIC_CRT=ON for static runtime (/MT or /MTd) when embedding
|
||||
# in projects that require it (e.g., IDA plugins).
|
||||
#
|
||||
# WARNING: Static CRT may cause heap corruption if memory is allocated
|
||||
# in one module and freed in another. Ensure allocation ownership is clear.
|
||||
# Must be a dynamic runtime (/MD or /MDd) to enforce
|
||||
# shared allocators between emulator and implementation
|
||||
|
||||
option(SOGEN_STATIC_CRT "Use static CRT (/MT) instead of dynamic (/MD)" OFF)
|
||||
|
||||
if(SOGEN_STATIC_CRT AND NOT SOGEN_BUILD_STATIC)
|
||||
message(FATAL_ERROR
|
||||
"SOGEN_STATIC_CRT=ON requires SOGEN_BUILD_STATIC=ON.\n"
|
||||
"Static CRT with shared libraries causes heap corruption - "
|
||||
"each DLL gets its own allocator, but sogen passes ownership across boundaries.")
|
||||
endif()
|
||||
|
||||
if(SOGEN_STATIC_CRT)
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
elseif(DEFINED CMAKE_MSVC_RUNTIME_LIBRARY)
|
||||
# Respect parent project's setting
|
||||
else()
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$<CONFIG:Debug>:Debug>DLL)
|
||||
|
||||
##########################################
|
||||
|
||||
@@ -258,6 +195,24 @@ endif()
|
||||
|
||||
##########################################
|
||||
|
||||
set(OPT_DEBUG "-O0 -g")
|
||||
set(OPT_RELEASE "-O3 -g")
|
||||
|
||||
if(MSVC)
|
||||
set(OPT_DEBUG "/Od /Ob0 /Zi")
|
||||
set(OPT_RELEASE "/O2 /Ob2 /Zi")
|
||||
|
||||
add_link_options(/DEBUG)
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${OPT_DEBUG}")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${OPT_DEBUG}")
|
||||
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${OPT_RELEASE}")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${OPT_RELEASE}")
|
||||
|
||||
##########################################
|
||||
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
momo_add_c_and_cxx_compile_options(/MP)
|
||||
endif()
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Module["preRun"] = () => {
|
||||
ENV = process.env;
|
||||
};
|
||||
Module['preRun'] = () => {
|
||||
ENV = process.env;
|
||||
};
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# cross compile
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
|
||||
set(MINGW_C_COMPILER_NAME "x86_64-w64-mingw32-gcc")
|
||||
set(MINGW_CXX_COMPILER_NAME "x86_64-w64-mingw32-g++")
|
||||
set(MINGW_WINDRES_COMPILER_NAME "x86_64-w64-mingw32-windres")
|
||||
|
||||
find_file(MINGW_C_COMPILER ${MINGW_C_COMPILER_NAME})
|
||||
find_file(MINGW_CXX_COMPILER ${MINGW_CXX_COMPILER_NAME})
|
||||
find_file(MINGW_WINDRES_COMPILER ${MINGW_WINDRES_COMPILER_NAME})
|
||||
|
||||
if (${MINGW_C_COMPILER} STREQUAL "MINGW_C_COMPILER-NOTFOUND")
|
||||
message(FATAL_ERROR "mingw-w64 compiler not found: ${MINGW_C_COMPILER_NAME}")
|
||||
endif()
|
||||
if (${MINGW_CXX_COMPILER} STREQUAL "MINGW_CXX_COMPILER-NOTFOUND")
|
||||
message(FATAL_ERROR "mingw-w64 compiler not found: ${MINGW_CXX_COMPILER_NAME}")
|
||||
endif()
|
||||
if (${MINGW_WINDRES_COMPILER} STREQUAL "MINGW_WINDRES_COMPILER-NOTFOUND")
|
||||
message(FATAL_ERROR "mingw-w64 compiler not found: ${MINGW_WINDRES_COMPILER_NAME}")
|
||||
endif()
|
||||
|
||||
# this macro is needed when compile `libwindows-emulator.a`
|
||||
add_compile_definitions(NTDDI_VERSION=NTDDI_WIN10_MN)
|
||||
|
||||
# set the compiler
|
||||
set(CMAKE_C_COMPILER ${MINGW_C_COMPILER})
|
||||
set(CMAKE_CXX_COMPILER ${MINGW_CXX_COMPILER})
|
||||
set(CMAKE_RC_COMPILER ${MINGW_WINDRES_COMPILER})
|
||||
|
||||
# set the compiler search path
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
|
||||
|
||||
# adjust the default behaviour of the FIND_XXX() commands:
|
||||
# search headers and libraries in the target environment, search
|
||||
# programs in the host environment
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
29
deps/CMakeLists.txt
vendored
29
deps/CMakeLists.txt
vendored
@@ -3,21 +3,6 @@ add_subdirectory(unicorn)
|
||||
|
||||
##########################################
|
||||
|
||||
option(BASE64_ENABLE_TESTING "" OFF)
|
||||
add_subdirectory(base64)
|
||||
|
||||
##########################################
|
||||
|
||||
option(FLATBUFFERS_BUILD_TESTS "" OFF)
|
||||
option(FLATBUFFERS_INSTALL "" OFF)
|
||||
add_subdirectory(flatbuffers)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(flatc PRIVATE /MD$<$<CONFIG:Debug>:d>)
|
||||
endif()
|
||||
|
||||
##########################################
|
||||
|
||||
add_library(reflect INTERFACE)
|
||||
target_include_directories(reflect INTERFACE
|
||||
"${CMAKE_CURRENT_LIST_DIR}/reflect"
|
||||
@@ -25,19 +10,5 @@ target_include_directories(reflect INTERFACE
|
||||
|
||||
##########################################
|
||||
|
||||
add_subdirectory(minidump_cpp)
|
||||
|
||||
##########################################
|
||||
|
||||
|
||||
option(CAPSTONE_BUILD_MACOS_THIN "" ON)
|
||||
option(CAPSTONE_X86_SUPPORT "" ON)
|
||||
option(CAPSTONE_X86_ATT_DISABLE "" ON)
|
||||
option(CAPSTONE_ARCHITECTURE_DEFAULT "" OFF)
|
||||
option(CAPSTONE_BUILD_STATIC_MSVC_RUNTIME "" OFF)
|
||||
add_subdirectory(capstone)
|
||||
|
||||
##########################################
|
||||
|
||||
include(googletest.cmake)
|
||||
include(zlib.cmake)
|
||||
|
||||
1
deps/base64
vendored
1
deps/base64
vendored
Submodule deps/base64 deleted from 8d96a2a737
1
deps/capstone
vendored
1
deps/capstone
vendored
Submodule deps/capstone deleted from 198cd49dd9
1
deps/flatbuffers
vendored
1
deps/flatbuffers
vendored
Submodule deps/flatbuffers deleted from 8914d06ab7
2
deps/googletest
vendored
2
deps/googletest
vendored
Submodule deps/googletest updated: 9156d4caac...cd430b47a5
2
deps/gtest-parallel
vendored
2
deps/gtest-parallel
vendored
Submodule deps/gtest-parallel updated: cd488bdedc...96f4f90492
1
deps/minidump_cpp
vendored
1
deps/minidump_cpp
vendored
Submodule deps/minidump_cpp deleted from df9ec209ce
2
deps/reflect
vendored
2
deps/reflect
vendored
Submodule deps/reflect updated: 96b9548a6e...68d8fd0913
2
deps/unicorn
vendored
2
deps/unicorn
vendored
Submodule deps/unicorn updated: a4da453114...a19ae94cbf
2
deps/zlib
vendored
2
deps/zlib
vendored
Submodule deps/zlib updated: 570720b0c2...5a82f71ed1
@@ -1,3 +1,2 @@
|
||||
# Ignore artifacts:
|
||||
dist
|
||||
src/fb/
|
||||
@@ -7,47 +7,52 @@
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
crossorigin="anonymous"
|
||||
href="./pinata.png"
|
||||
href="https://momo5502.com/emulator/pinata.png"
|
||||
/>
|
||||
|
||||
<title>Sogen - Windows User Space Emulator</title>
|
||||
|
||||
<meta name="color-scheme" content="dark" />
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content="Sogen is a high-performance Windows user space emulator that can emulate windows processes. It is ideal for security-, DRM- or malware research."
|
||||
data-react-helmet="true"
|
||||
/>
|
||||
<meta property="og:site_name" content="Sogen" data-react-helmet="true" />
|
||||
<meta property="og:title" content="Sogen" data-react-helmet="true" />
|
||||
<meta
|
||||
property="og:site_name"
|
||||
content="Sogen - Windows User Space Emulator"
|
||||
data-react-helmet="true"
|
||||
/>
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Sogen - Windows User Space Emulator"
|
||||
data-react-helmet="true"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="A high-performance Windows user space emulator."
|
||||
content="Sogen is a high-performance Windows user space emulator that can emulate windows processes. It is ideal for security-, DRM- or malware research."
|
||||
data-react-helmet="true"
|
||||
/>
|
||||
<meta property="og:locale" content="en-us" data-react-helmet="true" />
|
||||
<meta property="og:type" content="website" data-react-helmet="true" />
|
||||
<meta
|
||||
name="og:image"
|
||||
content="https://momo5502.com/emulator/preview.png"
|
||||
content="https://repository-images.githubusercontent.com/842883987/9e56f43b-4afd-464d-85b9-d7e555751a39"
|
||||
data-react-helmet="true"
|
||||
/>
|
||||
<meta name="twitter:card" content="summary" data-react-helmet="true" />
|
||||
<meta
|
||||
name="twitter:card"
|
||||
content="summary_large_image"
|
||||
name="twitter:title"
|
||||
content="Sogen - Windows User Space Emulator"
|
||||
data-react-helmet="true"
|
||||
/>
|
||||
<meta name="twitter:title" content="Sogen" data-react-helmet="true" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="A high-performance Windows user space emulator."
|
||||
content="Sogen is a high-performance Windows user space emulator that can emulate windows processes. It is ideal for security-, DRM- or malware research."
|
||||
data-react-helmet="true"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://momo5502.com/emulator/preview.png"
|
||||
content="https://repository-images.githubusercontent.com/842883987/9e56f43b-4afd-464d-85b9-d7e555751a39"
|
||||
data-react-helmet="true"
|
||||
/>
|
||||
</head>
|
||||
|
||||
7302
page/package-lock.json
generated
7302
page/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "sogen",
|
||||
"name": "emulator-ui",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -10,57 +10,46 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@irori/idbfs": "^0.5.1",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-context-menu": "^2.2.16",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@fontsource/inter": "^5.2.5",
|
||||
"@radix-ui/react-checkbox": "^1.2.3",
|
||||
"@radix-ui/react-dialog": "^1.1.11",
|
||||
"@radix-ui/react-label": "^2.1.4",
|
||||
"@radix-ui/react-popover": "^1.1.11",
|
||||
"@radix-ui/react-scroll-area": "^1.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.4",
|
||||
"@radix-ui/react-slot": "^1.2.0",
|
||||
"@radix-ui/react-tabs": "^1.1.9",
|
||||
"@radix-ui/react-tooltip": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"flatbuffers": "^25.9.23",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"pe-library": "^2.0.1",
|
||||
"react": "^19.2.3",
|
||||
"react-bootstrap-icons": "^1.11.6",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"lucide-react": "^0.503.0",
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap-icons": "^1.11.5",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-router-dom": "^7.11.0",
|
||||
"react-window": "^2.2.3",
|
||||
"shell-quote": "^1.8.3",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vaul": "^1.1.2",
|
||||
"wasm-feature-detect": "^1.8.0"
|
||||
"react-router-dom": "^7.5.2",
|
||||
"react-window": "^1.8.11",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"tw-animate-css": "^1.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@eslint/js": "^9.22.0",
|
||||
"@types/node": "^22.15.2",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/shell-quote": "^1.7.5",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.26",
|
||||
"globals": "^16.5.0",
|
||||
"prettier": "3.7.4",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.51.0",
|
||||
"vite": "^7.3.0",
|
||||
"vite-plugin-pwa": "^1.2.0"
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint-plugin-react-hooks": "^6.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.31.0",
|
||||
"vite": "^6.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,19 @@
|
||||
var logLines = [];
|
||||
var lastFlush = new Date().getTime();
|
||||
|
||||
var msgQueue = [];
|
||||
|
||||
onmessage = async (event) => {
|
||||
const data = event.data;
|
||||
const payload = data.data;
|
||||
|
||||
switch (data.message) {
|
||||
case "run":
|
||||
runEmulation(
|
||||
payload.file,
|
||||
payload.options,
|
||||
payload.arguments,
|
||||
payload.persist,
|
||||
payload.wasm64,
|
||||
payload.cacheBuster,
|
||||
);
|
||||
break;
|
||||
case "event":
|
||||
msgQueue.push(payload);
|
||||
break;
|
||||
if (data.message == "run") {
|
||||
const payload = data.data;
|
||||
runEmulation(payload.filesystem, payload.file, payload.options);
|
||||
}
|
||||
};
|
||||
|
||||
function sendMessage(message, data) {
|
||||
postMessage({ message, data });
|
||||
}
|
||||
|
||||
function flushLines() {
|
||||
const lines = logLines;
|
||||
logLines = [];
|
||||
lastFlush = new Date().getTime();
|
||||
sendMessage("log", lines);
|
||||
postMessage({ message: "log", data: lines });
|
||||
}
|
||||
|
||||
function logLine(text) {
|
||||
@@ -45,72 +26,36 @@ function logLine(text) {
|
||||
}
|
||||
}
|
||||
|
||||
function notifyExit(code, persist) {
|
||||
function notifyExit(code) {
|
||||
flushLines();
|
||||
|
||||
const finishExecution = () => {
|
||||
sendMessage("end", code);
|
||||
self.close();
|
||||
};
|
||||
|
||||
if (persist) {
|
||||
FS.syncfs(false, finishExecution);
|
||||
} else {
|
||||
finishExecution();
|
||||
}
|
||||
postMessage({ message: "end", data: code });
|
||||
self.close();
|
||||
}
|
||||
|
||||
function handleMessage(message) {
|
||||
sendMessage("event", message);
|
||||
}
|
||||
|
||||
function getMessageFromQueue() {
|
||||
if (msgQueue.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return msgQueue.shift();
|
||||
}
|
||||
|
||||
function runEmulation(
|
||||
file,
|
||||
options,
|
||||
appArguments,
|
||||
persist,
|
||||
wasm64,
|
||||
cacheBuster,
|
||||
) {
|
||||
const mainArguments = [...options, "-e", "./root", file, ...appArguments];
|
||||
|
||||
function runEmulation(filesystem, file, options) {
|
||||
globalThis.Module = {
|
||||
arguments: mainArguments,
|
||||
noInitialRun: true,
|
||||
locateFile: (path, scriptDirectory) => {
|
||||
const bitness = wasm64 ? "64" : "32";
|
||||
const busterParams = cacheBuster ? `?${cacheBuster}` : "";
|
||||
return `${scriptDirectory}${bitness}/${path}${busterParams}`;
|
||||
},
|
||||
arguments: [...options, "-e", "./root", file],
|
||||
onRuntimeInitialized: function () {
|
||||
FS.mkdir("/root");
|
||||
FS.mount(IDBFS, {}, "/root");
|
||||
FS.syncfs(true, function (_) {
|
||||
setTimeout(() => {
|
||||
Module.callMain(mainArguments);
|
||||
}, 0);
|
||||
filesystem.forEach((e) => {
|
||||
if (e.name.endsWith("/")) {
|
||||
FS.mkdir(e.name.slice(0, -1));
|
||||
} else {
|
||||
const dirs = e.name.split("/");
|
||||
const file = dirs.pop();
|
||||
const buffer = new Uint8Array(e.data);
|
||||
if (FS.analyzePath(e.name).exists) {
|
||||
FS.unlink(e.name);
|
||||
}
|
||||
FS.createDataFile("/" + dirs.join("/"), file, buffer, true, true);
|
||||
}
|
||||
});
|
||||
},
|
||||
print: logLine,
|
||||
printErr: logLine,
|
||||
onAbort: () => notifyExit(null, persist),
|
||||
onExit: (code) => notifyExit(code, persist),
|
||||
onAbort: () => notifyExit(null),
|
||||
onExit: notifyExit,
|
||||
postRun: flushLines,
|
||||
};
|
||||
|
||||
const busterParams = cacheBuster ? `?${cacheBuster}` : "";
|
||||
|
||||
if (wasm64) {
|
||||
importScripts("./64/analyzer.js" + busterParams);
|
||||
} else {
|
||||
importScripts("./32/analyzer.js" + busterParams);
|
||||
}
|
||||
importScripts("./analyzer.js");
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
@@ -5,95 +5,32 @@ html {
|
||||
@media (pointer: fine) {
|
||||
::-webkit-scrollbar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(97, 97, 97, 0.7);
|
||||
border-radius: 20px;
|
||||
min-height: 50px;
|
||||
border: 6px solid transparent;
|
||||
background-clip: content-box;
|
||||
transition: all 0.1s linear;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(97, 97, 97, 0.9);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.fancy.bg-primary,
|
||||
button.fancy.bg-secondary,
|
||||
button.fancy.bg-destructive {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button.fancy.bg-primary {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(38, 144, 255, 1) 0%,
|
||||
rgba(0, 123, 255, 1) 100%
|
||||
);
|
||||
border: 0px solid rgb(27, 149, 255);
|
||||
text-shadow: rgba(0, 0, 0, 0.2) 0px 1px;
|
||||
color: oklch(0.97 0.014 254.604);
|
||||
}
|
||||
|
||||
button.fancy.bg-primary:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(46, 151, 255, 1) 0%,
|
||||
rgba(8, 130, 255, 1) 100%
|
||||
);
|
||||
/*border: 1px solid rgb(33, 33, 34);*/
|
||||
}
|
||||
|
||||
button.fancy.bg-destructive {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgb(173, 35, 35) 0%,
|
||||
rgb(143, 46, 24) 100%
|
||||
);
|
||||
border: 0px solid rgb(147, 32, 7);
|
||||
text-shadow: rgba(0, 0, 0, 0.2) 0px 1px;
|
||||
}
|
||||
|
||||
button.fancy.bg-destructive:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgb(185, 47, 47) 0%,
|
||||
rgb(153, 53, 30) 100%
|
||||
);
|
||||
/*border: 1px solid rgb(33, 33, 34);*/
|
||||
}
|
||||
|
||||
button.fancy.bg-secondary {
|
||||
background: linear-gradient(180deg, rgb(38, 38, 39) 0%, rgb(34, 34, 35) 100%);
|
||||
border: 0px solid rgb(44, 44, 46);
|
||||
}
|
||||
|
||||
button.fancy.bg-secondary:hover {
|
||||
background: linear-gradient(180deg, rgb(42, 42, 43) 0%, rgb(38, 38, 39) 100%);
|
||||
/*border: 1px solid rgb(33, 33, 34);*/
|
||||
}
|
||||
|
||||
/*.terminal-output span {
|
||||
padding: 0px 16px;
|
||||
}*/
|
||||
|
||||
.terminal-output {
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
font-size: 1.05em;
|
||||
font-family: monospace;
|
||||
@@ -135,19 +72,3 @@ button.fancy.bg-secondary:hover {
|
||||
.terminal-dark-gray {
|
||||
color: rgb(81, 81, 81);
|
||||
}
|
||||
|
||||
.terminal-glass {
|
||||
box-shadow: 0px 0px 15px 4px rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(6px) brightness(0.8) saturate(1.3);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.folder-element .folder-icon,
|
||||
.folder-element svg {
|
||||
filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.4));
|
||||
}
|
||||
|
||||
.folder-element:hover .folder-icon,
|
||||
.folder-element:hover svg {
|
||||
filter: drop-shadow(0px 0px 4px rgba(80, 80, 80, 0.3));
|
||||
}
|
||||
|
||||
@@ -1,49 +1,30 @@
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { HashRouter, Route, Routes, Navigate } from "react-router-dom";
|
||||
import { Playground, storeEmulateData } from "./playground";
|
||||
import { LandingPage } from "./landing-page";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Loader from "./Loader";
|
||||
import { Playground } from "./Playground";
|
||||
import { LandingPage } from "./LandingPage";
|
||||
|
||||
import "@fontsource/inter/latin.css";
|
||||
import "@fontsource/inter/100.css";
|
||||
import "@fontsource/inter/200.css";
|
||||
import "@fontsource/inter/300.css";
|
||||
import "@fontsource/inter/400.css";
|
||||
import "@fontsource/inter/500.css";
|
||||
import "@fontsource/inter/600.css";
|
||||
import "@fontsource/inter/700.css";
|
||||
import "@fontsource/inter/800.css";
|
||||
import "@fontsource/inter/900.css";
|
||||
|
||||
import "./App.css";
|
||||
import "./animation.css";
|
||||
|
||||
function EmulateFile() {
|
||||
const { encodedData } = useParams();
|
||||
storeEmulateData(encodedData);
|
||||
return <Navigate to="/playground" replace />;
|
||||
}
|
||||
|
||||
function Spinner() {
|
||||
const loading = Loader.useLoader();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"fixed z-9999 top-0 left-0 right-0 h-[2px] pointer-events-none select-none transition-opacity duration-1000 animated-gradient " +
|
||||
(loading ? "opacity-100" : "opacity-0")
|
||||
}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||
<TooltipProvider>
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
<Route path="/" element={<LandingPage />} />
|
||||
<Route path="/playground" element={<Playground />} />
|
||||
<Route path="/emulate/:encodedData?" element={<EmulateFile />} />
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
<Spinner />
|
||||
</TooltipProvider>
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
<Route path="/" element={<LandingPage />} />
|
||||
<Route path="/playground" element={<Playground />} />
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ import { Helmet } from "react-helmet";
|
||||
export interface HeaderProps {
|
||||
title: string;
|
||||
description: string;
|
||||
preload?: string[];
|
||||
}
|
||||
|
||||
const image = "https://momo5502.com/emulator/preview.png";
|
||||
const image =
|
||||
"https://repository-images.githubusercontent.com/842883987/9e56f43b-4afd-464d-85b9-d7e555751a39";
|
||||
|
||||
export function Header(props: HeaderProps) {
|
||||
return (
|
||||
@@ -19,20 +19,10 @@ export function Header(props: HeaderProps) {
|
||||
<meta property="og:locale" content="en-us" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta name="og:image" content={image} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:title" content={props.title} />
|
||||
<meta name="twitter:description" content={props.description} />
|
||||
<meta name="twitter:image" content={image} />
|
||||
|
||||
{props.preload?.map((l) => (
|
||||
<link
|
||||
key={`link-${l}`}
|
||||
rel="preload"
|
||||
as={l.endsWith(".js") ? "script" : "fetch"}
|
||||
crossOrigin=""
|
||||
href={`${l}${l.indexOf("?") == -1 ? "?" : "&"}cb=${import.meta.env.VITE_BUILD_TIME}`}
|
||||
/>
|
||||
))}
|
||||
</Helmet>
|
||||
);
|
||||
}
|
||||
|
||||
200
page/src/LandingPage.tsx
Normal file
200
page/src/LandingPage.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Shield,
|
||||
FileCode,
|
||||
Layers,
|
||||
Cpu,
|
||||
Database,
|
||||
Terminal,
|
||||
ExternalLink,
|
||||
Github,
|
||||
Play,
|
||||
} from "lucide-react";
|
||||
import { Header } from "./Header";
|
||||
|
||||
export function LandingPage() {
|
||||
const features = [
|
||||
{
|
||||
icon: <Cpu className="h-6 w-6 text-[var(--primary)]" />,
|
||||
title: "Syscall-Level Emulation",
|
||||
description:
|
||||
"Operates at syscall level, leveraging existing system DLLs instead of reimplementing Windows APIs",
|
||||
},
|
||||
{
|
||||
icon: <Database className="h-6 w-6 text-[var(--primary)]" />,
|
||||
title: "Advanced Memory Management",
|
||||
description:
|
||||
"Supports Windows-specific memory types including reserved, committed, built on top of Unicorn's memory management",
|
||||
},
|
||||
{
|
||||
icon: <FileCode className="h-6 w-6 text-[var(--primary)]" />,
|
||||
title: "Complete PE Loading",
|
||||
description:
|
||||
"Handles executable and DLL loading with proper memory mapping, relocations, and TLS",
|
||||
},
|
||||
{
|
||||
icon: <Shield className="h-6 w-6 text-[var(--primary)]" />,
|
||||
title: "Exception Handling",
|
||||
description:
|
||||
"Implements Windows structured exception handling (SEH) with proper exception dispatcher and unwinding support",
|
||||
},
|
||||
{
|
||||
icon: <Layers className="h-6 w-6 text-[var(--primary)]" />,
|
||||
title: "Threading Support",
|
||||
description: "Provides a scheduled (round-robin) threading model",
|
||||
},
|
||||
{
|
||||
icon: <Terminal className="h-6 w-6 text-[var(--primary)]" />,
|
||||
title: "Debugging Interface",
|
||||
description:
|
||||
"Implements GDB serial protocol for integration with common debugging tools",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
title="Sogen - Windows User Space Emulator"
|
||||
description="Sogen is a high-performance Windows user space emulator that can emulate windows processes. It is ideal for security-, DRM- or malware research."
|
||||
/>
|
||||
<div className="flex flex-col min-h-screen">
|
||||
{/* Hero Section */}
|
||||
<header className="bg-gradient-to-r from-blue-600 to-cyan-500 py-16 md:py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between">
|
||||
<div className="w-full md:w-1/2 space-y-6 text-center md:text-left text-white">
|
||||
<h1 className="text-4xl md:text-6xl font-bold tracking-tight">
|
||||
Sogen
|
||||
</h1>
|
||||
<p className="text-xl md:text-2xl font-light">
|
||||
A high-performance Windows user space emulator
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4 justify-center md:justify-start">
|
||||
<a href="#/playground" target="_blank">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-white text-blue-700 hover:bg-blue-50"
|
||||
>
|
||||
<Play className="mr-2 h-5 w-5" />
|
||||
Try Online
|
||||
</Button>
|
||||
</a>
|
||||
<a href="https://github.com/momo5502/sogen" target="_blank">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-white text-white hover:bg-white/10"
|
||||
>
|
||||
<Github className="mr-2 h-5 w-5" />
|
||||
GitHub
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/*<div className="w-full md:w-1/2 mt-8 md:mt-0 flex justify-center md:justify-end">
|
||||
<div className="relative w-full max-w-md">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-500/20 to-indigo-500/20 rounded-lg blur-xl"></div>
|
||||
<img
|
||||
src="/api/placeholder/600/400"
|
||||
alt="Sogen Emulator"
|
||||
className="relative rounded-lg shadow-xl w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>*/}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Key Features */}
|
||||
<section className="py-16 md:py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
||||
Key Features
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{features.map((feature, index) => (
|
||||
<Card key={index} className="hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<div className="mb-2">{feature.icon}</div>
|
||||
<CardTitle>{feature.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{feature.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Video Section */}
|
||||
<section className="bg-zinc-900 py-16 md:py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
||||
See Sogen in Action
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400 max-w-3xl mx-auto">
|
||||
Watch an overview of the emulator's capabilities and see how it
|
||||
can help with your research.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="relative aspect-video rounded-2xl shadow-2xl">
|
||||
<iframe
|
||||
className="rounded-2xl"
|
||||
width="100%"
|
||||
height="100%"
|
||||
src="https://www.youtube.com/embed/wY9Q0DhodOQ?si=Lm_anpeBU6Txl5AW"
|
||||
title="YouTube video player"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerPolicy="strict-origin-when-cross-origin"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
<div className="mt-4 text-center"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-12">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center">
|
||||
<div className="mb-6 md:mb-0">
|
||||
<h2 className="text-2xl font-bold">Sogen</h2>
|
||||
<p className="mt-2 text-gray-400">
|
||||
Windows User Space Emulator
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-6">
|
||||
<a
|
||||
href="https://github.com/momo5502/sogen"
|
||||
target="_blank"
|
||||
className="hover:text-blue-400"
|
||||
>
|
||||
<Github className="h-6 w-6" />
|
||||
</a>
|
||||
<a
|
||||
href="#/playground"
|
||||
target="_blank"
|
||||
className="hover:text-blue-400"
|
||||
>
|
||||
<ExternalLink className="h-6 w-6" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type Callback = (loading: boolean) => void;
|
||||
|
||||
class Loader {
|
||||
private callbacks: Set<Callback> = new Set();
|
||||
private loading: boolean = false;
|
||||
|
||||
public isLoading(): boolean {
|
||||
return this.loading;
|
||||
}
|
||||
|
||||
public setLoading(value: boolean) {
|
||||
if (this.loading == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = value;
|
||||
this.callbacks.forEach((callback) => callback(this.loading));
|
||||
}
|
||||
|
||||
public register(callback: Callback): void {
|
||||
this.callbacks.add(callback);
|
||||
}
|
||||
|
||||
public unregister(callback: Callback): void {
|
||||
this.callbacks.delete(callback);
|
||||
}
|
||||
|
||||
public useLoader() {
|
||||
const [isLoading, setIsLoading] = useState(this.isLoading());
|
||||
|
||||
useEffect(() => {
|
||||
function callback(loading: boolean) {
|
||||
setIsLoading(loading);
|
||||
}
|
||||
|
||||
this.register(callback);
|
||||
|
||||
return () => {
|
||||
this.unregister(callback);
|
||||
};
|
||||
});
|
||||
|
||||
return isLoading;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Loader();
|
||||
141
page/src/Playground.tsx
Normal file
141
page/src/Playground.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { Output } from "@/components/output";
|
||||
|
||||
import { AppSidebar } from "@/components/app-sidebar";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { Button } from "./components/ui/button";
|
||||
|
||||
import { Emulator, UserFile } from "./emulator";
|
||||
import { getFilesystem } from "./filesystem";
|
||||
|
||||
import "./App.css";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "./components/ui/popover";
|
||||
|
||||
import { createDefaultSettings } from "./settings";
|
||||
import { SettingsMenu } from "./components/settings-menu";
|
||||
|
||||
import { PlayFill, StopFill, GearFill } from "react-bootstrap-icons";
|
||||
import { StatusIndicator } from "./components/status-indicator";
|
||||
import { Header } from "./Header";
|
||||
|
||||
function selectAndReadFile(): Promise<UserFile> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileInput = document.createElement("input");
|
||||
fileInput.type = "file";
|
||||
fileInput.accept = ".exe";
|
||||
|
||||
fileInput.addEventListener("change", function (event) {
|
||||
const file = (event as any).target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function (e: ProgressEvent<FileReader>) {
|
||||
const arrayBuffer = e.target?.result;
|
||||
resolve({
|
||||
name: file.name,
|
||||
data: arrayBuffer as ArrayBuffer,
|
||||
});
|
||||
};
|
||||
|
||||
reader.onerror = function (e: ProgressEvent<FileReader>) {
|
||||
reject(new Error("Error reading file: " + e.target?.error));
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
} else {
|
||||
reject(new Error("No file selected"));
|
||||
}
|
||||
});
|
||||
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
export function Playground() {
|
||||
const output = useRef<Output>(null);
|
||||
const [settings, setSettings] = useState(createDefaultSettings());
|
||||
const [emulator, setEmulator] = useState<Emulator | null>(null);
|
||||
|
||||
function logLine(line: string) {
|
||||
output.current?.logLine(line);
|
||||
}
|
||||
|
||||
function logLines(lines: string[]) {
|
||||
output.current?.logLines(lines);
|
||||
}
|
||||
|
||||
async function createEmulator(userFile: UserFile | null = null) {
|
||||
emulator?.stop();
|
||||
output.current?.clear();
|
||||
|
||||
logLine("Starting emulation...");
|
||||
|
||||
const fs = await getFilesystem((current, total, file) => {
|
||||
logLine(`Processing filesystem (${current}/${total}): ${file}`);
|
||||
});
|
||||
|
||||
const new_emulator = new Emulator(fs, logLines);
|
||||
new_emulator.onTerminate().then(() => setEmulator(null));
|
||||
setEmulator(new_emulator);
|
||||
|
||||
new_emulator.start(settings, userFile);
|
||||
}
|
||||
|
||||
async function loadAndRunUserFile() {
|
||||
const fileBuffer = await selectAndReadFile();
|
||||
await createEmulator(fileBuffer);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
title="Playground - Sogen"
|
||||
description="Playground to test and run Sogen, the Windows user space emulator, right in your browser."
|
||||
/>
|
||||
<SidebarProvider defaultOpen={false}>
|
||||
<AppSidebar />
|
||||
<SidebarInset className="h-[100dvh]">
|
||||
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4 overflow-y-auto">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<Button onClick={() => createEmulator()}>
|
||||
<PlayFill /> Run Sample
|
||||
</Button>
|
||||
<Button onClick={() => loadAndRunUserFile()}>
|
||||
<PlayFill /> Run your .exe
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => emulator?.stop()}>
|
||||
<StopFill /> Stop Emulation
|
||||
</Button>
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="secondary">
|
||||
<GearFill /> Settings
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<SettingsMenu settings={settings} onChange={setSettings} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="text-right flex-1">
|
||||
<StatusIndicator running={!!emulator} />
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex flex-1 flex-col gap-4 p-4 overflow-auto">
|
||||
<Output ref={output} />
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
:root {
|
||||
--gradient-loader-1: rgba(38, 38, 38, 0);
|
||||
--gradient-loader-2: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.animated-gradient {
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
var(--gradient-loader-1) 0%,
|
||||
var(--gradient-loader-1) 35%,
|
||||
var(--gradient-loader-2) 50%,
|
||||
var(--gradient-loader-1) 53%,
|
||||
var(--gradient-loader-1) 100%
|
||||
);
|
||||
width: 100%;
|
||||
background-size: 200% auto;
|
||||
background-position: 0 100%;
|
||||
animation: gradient 2s infinite;
|
||||
animation-fill-mode: forwards;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import { EmulationStatus } from "@/emulator";
|
||||
import { TextTooltip } from "./text-tooltip";
|
||||
import {
|
||||
BarChartSteps,
|
||||
CpuFill,
|
||||
FloppyFill,
|
||||
StopwatchFill,
|
||||
} from "react-bootstrap-icons";
|
||||
import React from "react";
|
||||
|
||||
export interface EmulationSummaryProps {
|
||||
status?: EmulationStatus;
|
||||
executionTimeFetcher: () => number;
|
||||
}
|
||||
|
||||
function formatMemory(value: BigInt): string {
|
||||
const abbr = ["B", "KB", "MB", "GB", "PB"];
|
||||
|
||||
let num = Number(value);
|
||||
let index = 0;
|
||||
|
||||
while (num >= 1024 && index < abbr.length - 1) {
|
||||
num /= 1024;
|
||||
index++;
|
||||
}
|
||||
|
||||
return num.toFixed(2) + " " + abbr[index];
|
||||
}
|
||||
|
||||
function formatTime(seconds: number): string {
|
||||
const hrs = Math.floor(seconds / 3600);
|
||||
const mins = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
|
||||
const secsString = secs < 10 ? "0" + secs : secs.toString();
|
||||
|
||||
if (hrs > 0) {
|
||||
const minsString = mins < 10 ? "0" + mins : mins.toString();
|
||||
return `${hrs.toString()}:${minsString}:${secsString}`;
|
||||
}
|
||||
|
||||
return `${mins.toString()}:${secsString}`;
|
||||
}
|
||||
|
||||
export class EmulationSummary extends React.Component<
|
||||
EmulationSummaryProps,
|
||||
{}
|
||||
> {
|
||||
private timer: NodeJS.Timeout | undefined = undefined;
|
||||
|
||||
constructor(props: EmulationSummaryProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
this.forceUpdate();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.status) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="emulation-summary terminal-glass items-center absolute z-49 right-0 m-6 rounded-xl min-w-[150px] p-3 text-white cursor-default font-medium text-right text-sm whitespace-nowrap leading-6 font-mono">
|
||||
<TextTooltip tooltip={"Active Threads"}>
|
||||
{this.props.status.activeThreads}
|
||||
<BarChartSteps className="inline ml-3" />
|
||||
</TextTooltip>
|
||||
<br />
|
||||
<TextTooltip tooltip={"Application Memory"}>
|
||||
{formatMemory(this.props.status.committedMemory)}
|
||||
<FloppyFill className="inline ml-3" />
|
||||
</TextTooltip>
|
||||
<br />
|
||||
<TextTooltip tooltip={"Executed Instructions"}>
|
||||
{this.props.status.executedInstructions.toLocaleString()}
|
||||
<CpuFill className="inline ml-3" />
|
||||
</TextTooltip>
|
||||
<br />
|
||||
<TextTooltip tooltip={"Execution Time"}>
|
||||
{formatTime(this.props.executionTimeFetcher() / 1000)}
|
||||
<StopwatchFill className="inline ml-3" />
|
||||
</TextTooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
import {
|
||||
FolderFill,
|
||||
FolderSymlinkFill,
|
||||
FileEarmark,
|
||||
FiletypeExe,
|
||||
FileEarmarkBinary,
|
||||
} from "react-bootstrap-icons";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuLabel,
|
||||
} from "@/components/ui/context-menu";
|
||||
import { TextTooltip } from "./text-tooltip";
|
||||
|
||||
export enum FolderElementType {
|
||||
Folder = 0,
|
||||
File,
|
||||
}
|
||||
|
||||
export interface FolderElement {
|
||||
name: string;
|
||||
type: FolderElementType;
|
||||
}
|
||||
|
||||
type ClickHandler = (element: FolderElement) => void;
|
||||
type CreateFolderHandler = () => void;
|
||||
type RemoveElementHandler = (element: FolderElement) => void;
|
||||
type RenameElementHandler = (element: FolderElement) => void;
|
||||
type DownloadElementHandler = (element: FolderElement) => void;
|
||||
type AddFilesHandler = () => void;
|
||||
type IconReader = (element: FolderElement) => string | null;
|
||||
|
||||
export interface FolderProps {
|
||||
elements: FolderElement[];
|
||||
iconReader: IconReader;
|
||||
clickHandler: ClickHandler;
|
||||
createFolderHandler: CreateFolderHandler;
|
||||
removeElementHandler: RemoveElementHandler;
|
||||
renameElementHandler: RenameElementHandler;
|
||||
downloadElementHandler: DownloadElementHandler;
|
||||
addFilesHandler: AddFilesHandler;
|
||||
}
|
||||
|
||||
function elementComparator(e1: FolderElement, e2: FolderElement) {
|
||||
if (e1.type != e2.type) {
|
||||
return e1.type - e2.type;
|
||||
}
|
||||
|
||||
return e1.name.localeCompare(e2.name);
|
||||
}
|
||||
|
||||
function getIcon(
|
||||
element: FolderElement,
|
||||
iconReader: IconReader,
|
||||
className: string = "",
|
||||
) {
|
||||
const icon = iconReader(element);
|
||||
if (icon) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="w-full h-full flex items-center">
|
||||
<img className="rounded-lg folder-icon" src={icon} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (element.type) {
|
||||
case FolderElementType.File:
|
||||
if (element.name.endsWith(".dll")) {
|
||||
return <FileEarmarkBinary className={className} />;
|
||||
}
|
||||
if (element.name.endsWith(".exe")) {
|
||||
return <FiletypeExe className={className} />;
|
||||
}
|
||||
return <FileEarmark className={className} />;
|
||||
case FolderElementType.Folder:
|
||||
return element.name == ".." ? (
|
||||
<FolderSymlinkFill className={className} />
|
||||
) : (
|
||||
<FolderFill className={className} />
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
function renderIcon(element: FolderElement, iconReader: IconReader) {
|
||||
let className = "w-11 h-11 flex-1";
|
||||
return getIcon(element, iconReader, className);
|
||||
}
|
||||
|
||||
function renderElement(element: FolderElement, props: FolderProps) {
|
||||
return (
|
||||
<div
|
||||
onClick={() => props.clickHandler(element)}
|
||||
className="folder-element cursor-default select-none flex flex-col gap-2 items-center text-center text-xs p-2 m-2 w-27 h-25 rounded-lg border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
|
||||
>
|
||||
{renderIcon(element, props.iconReader)}
|
||||
<span className="whitespace-nowrap text-ellipsis overflow-hidden w-20">
|
||||
{element.name}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function trimFilename(filename: string, limit = 25) {
|
||||
if (limit < 4) {
|
||||
limit = 4;
|
||||
}
|
||||
|
||||
if (filename.length < limit) {
|
||||
return filename;
|
||||
}
|
||||
|
||||
return filename.substring(0, limit - 3) + "...";
|
||||
}
|
||||
|
||||
function renderElementWithContext(element: FolderElement, props: FolderProps) {
|
||||
if (element.name == "..") {
|
||||
return renderElement(element, props);
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>
|
||||
<TextTooltip tooltip={element.name}>
|
||||
{renderElement(element, props)}
|
||||
</TextTooltip>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuLabel>{trimFilename(element.name)}</ContextMenuLabel>
|
||||
<ContextMenuSeparator />
|
||||
{element.type != FolderElementType.File ? (
|
||||
<></>
|
||||
) : (
|
||||
<ContextMenuItem
|
||||
onClick={() => props.downloadElementHandler(element)}
|
||||
>
|
||||
Download
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
<ContextMenuItem onClick={() => props.renameElementHandler(element)}>
|
||||
Rename
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={() => props.removeElementHandler(element)}>
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
function renderElementWrapper(element: FolderElement, props: FolderProps) {
|
||||
return (
|
||||
<div key={`folder-element-${element.name}`}>
|
||||
{renderElementWithContext(element, props)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Folder(props: FolderProps) {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>
|
||||
<ScrollArea className="h-[50dvh]">
|
||||
<div className="folder flex flex-wrap h-full text-neutral-300">
|
||||
{props.elements
|
||||
.sort(elementComparator)
|
||||
.map((e) => renderElementWrapper(e, props))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem onClick={props.createFolderHandler}>
|
||||
Create new Folder
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={props.addFilesHandler}>
|
||||
Add Files
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { Input } from "./ui/input";
|
||||
import { Button } from "./ui/button";
|
||||
import { Plus, Trash } from "react-bootstrap-icons";
|
||||
import { Label } from "./ui/label";
|
||||
|
||||
interface ItemListProps {
|
||||
title: string;
|
||||
items: string[];
|
||||
onChange: (items: string[]) => void;
|
||||
}
|
||||
|
||||
export function ItemList(props: ItemListProps) {
|
||||
const removeItem = (index: number) => {
|
||||
const newItems = [...props.items];
|
||||
newItems.splice(index, 1);
|
||||
props.onChange(newItems);
|
||||
};
|
||||
|
||||
const addItem = (item: string) => {
|
||||
if (item.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newItems = props.items.concat(item);
|
||||
props.onChange(newItems);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">{props.title}</h4>
|
||||
{/*<p className="text-sm text-muted-foreground">
|
||||
Set the settings for the emulation.
|
||||
</p>*/}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2 overflow-auto overflow-x-hidden max-h-45 mt-2 mb-2">
|
||||
{props.items.map((item, index) => {
|
||||
return (
|
||||
<div
|
||||
key={`item-list-item-${index}-${item}`}
|
||||
className="flex gap-3 items-center min-w-0"
|
||||
>
|
||||
<Label className="flex-1 text-left truncate min-w-0">
|
||||
{item}
|
||||
</Label>
|
||||
<Button
|
||||
onClick={() => removeItem(index)}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="fancy rounded-lg"
|
||||
>
|
||||
<Trash />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
const nameInput = (e.target as any).elements.name;
|
||||
const newItem = nameInput.value;
|
||||
nameInput.value = "";
|
||||
|
||||
addItem(newItem);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<Input id="name" />
|
||||
<Button
|
||||
type="submit"
|
||||
variant="secondary"
|
||||
className="fancy rounded-lg"
|
||||
>
|
||||
<Plus />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import React from "react";
|
||||
import { List, ListImperativeAPI, type RowComponentProps } from "react-window";
|
||||
import { ArrowDown } from "react-bootstrap-icons";
|
||||
import { Button } from "./ui/button";
|
||||
import { FixedSizeList as List } from "react-window";
|
||||
|
||||
interface OutputProps {}
|
||||
|
||||
@@ -13,17 +11,10 @@ interface OutputState extends ColorState {
|
||||
lines: LogLine[];
|
||||
}
|
||||
|
||||
enum SizeState {
|
||||
Final,
|
||||
Updating,
|
||||
}
|
||||
|
||||
interface FullOutputState extends OutputState {
|
||||
grouper: OutputGrouper;
|
||||
height: number;
|
||||
width: number;
|
||||
state: SizeState;
|
||||
autoScroll: boolean;
|
||||
}
|
||||
|
||||
interface LogLine {
|
||||
@@ -159,29 +150,10 @@ class OutputGrouper {
|
||||
}
|
||||
}
|
||||
|
||||
function LogLineRow({
|
||||
ariaAttributes,
|
||||
lines,
|
||||
index,
|
||||
style,
|
||||
}: RowComponentProps<{
|
||||
lines: LogLine[];
|
||||
}>) {
|
||||
{
|
||||
const line = lines[index];
|
||||
return (
|
||||
<span className={line.classNames} style={style} {...ariaAttributes}>
|
||||
{line.text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Output extends React.Component<OutputProps, FullOutputState> {
|
||||
private outputRef: React.RefObject<HTMLDivElement | null>;
|
||||
private listRef: React.RefObject<ListImperativeAPI | null>;
|
||||
private listRef: React.RefObject<List | null>;
|
||||
private resizeObserver: ResizeObserver;
|
||||
private scrollElement: HTMLDivElement | null | undefined;
|
||||
|
||||
constructor(props: OutputProps) {
|
||||
super(props);
|
||||
@@ -189,8 +161,6 @@ export class Output extends React.Component<OutputProps, FullOutputState> {
|
||||
this.clear = this.clear.bind(this);
|
||||
this.logLine = this.logLine.bind(this);
|
||||
this.logLines = this.logLines.bind(this);
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
this.scrollListToEnd = this.scrollListToEnd.bind(this);
|
||||
this.updateDimensions = this.updateDimensions.bind(this);
|
||||
|
||||
this.outputRef = React.createRef();
|
||||
@@ -203,8 +173,6 @@ export class Output extends React.Component<OutputProps, FullOutputState> {
|
||||
grouper: new OutputGrouper(),
|
||||
height: 10,
|
||||
width: 10,
|
||||
state: SizeState.Final,
|
||||
autoScroll: true,
|
||||
};
|
||||
|
||||
this.state.grouper.handler = (lines: string[]) => {
|
||||
@@ -212,68 +180,27 @@ export class Output extends React.Component<OutputProps, FullOutputState> {
|
||||
};
|
||||
}
|
||||
|
||||
handleScroll(e: Event) {
|
||||
const threshold = 40;
|
||||
const element = e.target as HTMLElement;
|
||||
const { scrollTop, scrollHeight, clientHeight } = element;
|
||||
const isAtEnd = scrollTop + clientHeight >= scrollHeight - threshold;
|
||||
|
||||
this.setState({ autoScroll: isAtEnd });
|
||||
}
|
||||
|
||||
unregisterScrollListener() {
|
||||
this.scrollElement?.removeEventListener("scroll", this.handleScroll);
|
||||
}
|
||||
|
||||
registerScrollListener(element: HTMLDivElement | null | undefined) {
|
||||
if (element == this.scrollElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.unregisterScrollListener();
|
||||
this.scrollElement = element;
|
||||
element?.addEventListener("scroll", this.handleScroll);
|
||||
}
|
||||
|
||||
registerScrollOnList() {
|
||||
this.registerScrollListener(this.listRef.current?.element);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateDimensions();
|
||||
|
||||
if (this.outputRef.current) {
|
||||
this.resizeObserver.observe(this.outputRef.current);
|
||||
}
|
||||
|
||||
this.registerScrollOnList();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.resizeObserver.disconnect();
|
||||
this.unregisterScrollListener();
|
||||
}
|
||||
|
||||
scrollListToEnd() {
|
||||
if (this.listRef.current && this.state.lines.length > 0) {
|
||||
this.listRef.current.scrollToRow({
|
||||
index: this.state.lines.length - 1,
|
||||
behavior: "instant",
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ autoScroll: true });
|
||||
}
|
||||
|
||||
componentDidUpdate(_: OutputProps, prevState: FullOutputState) {
|
||||
this.registerScrollOnList();
|
||||
|
||||
if (
|
||||
this.state.autoScroll &&
|
||||
prevState.lines.length != this.state.lines.length
|
||||
prevState.lines.length == this.state.lines.length ||
|
||||
!this.listRef.current
|
||||
) {
|
||||
this.scrollListToEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
this.listRef.current.scrollToItem(this.state.lines.length - 1);
|
||||
}
|
||||
|
||||
clear() {
|
||||
@@ -289,29 +216,9 @@ export class Output extends React.Component<OutputProps, FullOutputState> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.state == SizeState.Updating) {
|
||||
this.setState({
|
||||
width: this.outputRef.current.offsetWidth - 1,
|
||||
height: this.outputRef.current.offsetHeight - 1,
|
||||
state: SizeState.Final,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
width: 0,
|
||||
height: 0,
|
||||
state: SizeState.Updating,
|
||||
},
|
||||
this.triggerDimensionUpdate.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
triggerDimensionUpdate() {
|
||||
requestAnimationFrame(() => {
|
||||
this.updateDimensions();
|
||||
this.setState({
|
||||
width: this.outputRef.current.offsetWidth,
|
||||
height: this.outputRef.current.offsetHeight,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,25 +234,21 @@ export class Output extends React.Component<OutputProps, FullOutputState> {
|
||||
return (
|
||||
<div className="terminal-output" ref={this.outputRef}>
|
||||
<List
|
||||
listRef={this.listRef}
|
||||
overscanCount={30}
|
||||
rowComponent={LogLineRow}
|
||||
rowCount={this.state.lines.length}
|
||||
rowProps={{ lines: this.state.lines }}
|
||||
rowHeight={20}
|
||||
style={{ height: this.state.height, width: this.state.width }}
|
||||
/>
|
||||
<Button
|
||||
title="Scroll to end"
|
||||
className={
|
||||
"absolute bottom-6 right-6 z-50 terminal-glass transition-opacity duration-50 ease-linear " +
|
||||
(this.state.autoScroll ? "opacity-0" : "")
|
||||
}
|
||||
variant="secondary"
|
||||
onClick={this.scrollListToEnd}
|
||||
ref={this.listRef}
|
||||
width={this.state.width}
|
||||
height={this.state.height}
|
||||
itemCount={this.state.lines.length}
|
||||
itemSize={20}
|
||||
>
|
||||
<ArrowDown />
|
||||
</Button>
|
||||
{({ index, style }) => {
|
||||
const line = this.state.lines[index];
|
||||
return (
|
||||
<span className={line.classNames} style={style}>
|
||||
{line.text}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,38 +3,12 @@ import { Checkbox } from "./ui/checkbox";
|
||||
import { Label } from "./ui/label";
|
||||
|
||||
import { Settings } from "@/settings";
|
||||
import { TextTooltip } from "./text-tooltip";
|
||||
import { ItemList } from "./item-list";
|
||||
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ChevronDown } from "react-bootstrap-icons";
|
||||
import { Input } from "./ui/input";
|
||||
import { RadioGroup, RadioGroupItem } from "./ui/radio-group";
|
||||
|
||||
interface SettingsMenuProps {
|
||||
settings: Settings;
|
||||
allowWasm64: boolean;
|
||||
onChange: (settings: Settings) => void;
|
||||
}
|
||||
|
||||
interface SettingsLabelProps {
|
||||
htmlFor?: string | undefined;
|
||||
text: React.ReactNode;
|
||||
tooltip: React.ReactNode;
|
||||
}
|
||||
|
||||
function SettingsLabel(props: SettingsLabelProps) {
|
||||
return (
|
||||
<Label htmlFor={props.htmlFor}>
|
||||
<TextTooltip tooltip={props.tooltip}>{props.text}</TextTooltip>
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
|
||||
export class SettingsMenu extends React.Component<SettingsMenuProps, Settings> {
|
||||
constructor(props: SettingsMenuProps) {
|
||||
super(props);
|
||||
@@ -50,72 +24,54 @@ export class SettingsMenu extends React.Component<SettingsMenuProps, Settings> {
|
||||
this.setState(() => settings);
|
||||
}
|
||||
|
||||
componentDidUpdate(_: SettingsMenuProps, oldSettings: Settings) {
|
||||
if (JSON.stringify(oldSettings) !== JSON.stringify(this.state)) {
|
||||
this.props.onChange(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
updateArgv(commandLine: string) {
|
||||
this.setState({ commandLine });
|
||||
componentDidUpdate() {
|
||||
this.props.onChange(this.state);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
<div className="space-y-2 mb-1">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Settings</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Set the settings for the emulation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-6 mb-2">
|
||||
<RadioGroup
|
||||
defaultValue="regular"
|
||||
value={this.state.logging}
|
||||
onValueChange={(value) => this.setState({ logging: value })}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<RadioGroupItem value="regular" id="settings-regular" />
|
||||
<SettingsLabel
|
||||
htmlFor="settings-regular"
|
||||
text={"Regular Logging"}
|
||||
tooltip={
|
||||
"Default logging behaviour, not too verbose, but also not very concise"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<RadioGroupItem value="verbose" id="settings-verbose" />
|
||||
<SettingsLabel
|
||||
htmlFor="settings-verbose"
|
||||
text={"Verbose Logging"}
|
||||
tooltip={
|
||||
"Very detailed logging of all function calls and accesses"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<RadioGroupItem value="concise" id="settings-concise" />
|
||||
<SettingsLabel
|
||||
htmlFor="settings-concise"
|
||||
text={"Concise Logging"}
|
||||
tooltip={"Suppress logging until the application code runs"}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<RadioGroupItem value="silent" id="settings-silent" />
|
||||
<SettingsLabel
|
||||
htmlFor="settings-silent"
|
||||
text={"Silent Logging"}
|
||||
tooltip={"Suppress all logging except for stdout"}
|
||||
/>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div className="flex gap-6">
|
||||
<Checkbox
|
||||
id="settings-verbose"
|
||||
checked={this.state.verbose}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
this.setState({ verbose: checked });
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="settings-verbose">Verbose Logging</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<div className="flex gap-6">
|
||||
<Checkbox
|
||||
id="settings-concise"
|
||||
checked={this.state.concise}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
this.setState({ concise: checked });
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="settings-concise">Concise Logging</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-6">
|
||||
<Checkbox
|
||||
id="settings-silent"
|
||||
checked={this.state.silent}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
this.setState({ silent: checked });
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="settings-silent">Silent Logging</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-6">
|
||||
<Checkbox
|
||||
id="settings-buffer"
|
||||
checked={this.state.bufferStdout}
|
||||
@@ -123,145 +79,8 @@ export class SettingsMenu extends React.Component<SettingsMenuProps, Settings> {
|
||||
this.setState({ bufferStdout: checked });
|
||||
}}
|
||||
/>
|
||||
<SettingsLabel
|
||||
htmlFor="settings-buffer"
|
||||
text={"Buffer stdout"}
|
||||
tooltip={
|
||||
"Group stdout and print everything when the emulation ends"
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="settings-buffer">Buffer stdout</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Checkbox
|
||||
id="settings-exec"
|
||||
checked={this.state.execAccess}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
this.setState({ execAccess: checked });
|
||||
}}
|
||||
/>
|
||||
<SettingsLabel
|
||||
htmlFor="settings-exec"
|
||||
text={"Log exec Memory Access"}
|
||||
tooltip={"Log when the application reads/writes executable memory"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Checkbox
|
||||
id="settings-foreign"
|
||||
checked={this.state.foreignAccess}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
this.setState({ foreignAccess: checked });
|
||||
}}
|
||||
/>
|
||||
<SettingsLabel
|
||||
htmlFor="settings-foreign"
|
||||
text={"Log Foreign Access"}
|
||||
tooltip={
|
||||
"Log when the application reads/writes memory of other modules"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Checkbox
|
||||
id="settings-summary"
|
||||
checked={this.state.instructionSummary}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
this.setState({ instructionSummary: checked });
|
||||
}}
|
||||
/>
|
||||
<SettingsLabel
|
||||
htmlFor="settings-summary"
|
||||
text={"Print Instruction Summary"}
|
||||
tooltip={"Print summary of executed instructions"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Checkbox
|
||||
id="settings-persist"
|
||||
checked={this.state.persist}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
this.setState({ persist: checked });
|
||||
}}
|
||||
/>
|
||||
<SettingsLabel
|
||||
htmlFor="settings-persist"
|
||||
text={"Persist Filesystem"}
|
||||
tooltip={
|
||||
"Persist files and folders that were created, modified or deleted during the emulation"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Checkbox
|
||||
id="settings-mem64"
|
||||
disabled={!this.props.allowWasm64}
|
||||
checked={this.state.wasm64}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
this.setState({ wasm64: checked });
|
||||
}}
|
||||
/>
|
||||
<SettingsLabel
|
||||
htmlFor="settings-mem64"
|
||||
text={"64-Bit WebAssembly"}
|
||||
tooltip={
|
||||
"Use 64-bit WebAssembly which supports emulating applications that require more than 2gb of memory"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-6 my-2">
|
||||
<Input
|
||||
id="settings-argv"
|
||||
placeholder="Command-Line Arguments"
|
||||
value={this.state.commandLine}
|
||||
onChange={(e) => this.updateArgv(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<TextTooltip tooltip="Don't log executions of listed functions">
|
||||
<div className="flex items-center mb-2">
|
||||
<Label className="flex-1 text-left cursor-pointer">
|
||||
Ignored Functions
|
||||
</Label>
|
||||
<ChevronDown />
|
||||
</div>
|
||||
</TextTooltip>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="shadow-2xl">
|
||||
<ItemList
|
||||
title="Ignored Functions"
|
||||
items={this.state.ignoredFunctions}
|
||||
onChange={(items) => this.setState({ ignoredFunctions: items })}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<TextTooltip tooltip="Log interactions of additional modules">
|
||||
<div className="flex items-center mb-1">
|
||||
<Label className="flex-1 text-left cursor-pointer">
|
||||
Interesting Modules
|
||||
</Label>
|
||||
<ChevronDown />
|
||||
</div>
|
||||
</TextTooltip>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="shadow-2xl">
|
||||
<ItemList
|
||||
title="Interesting Modules"
|
||||
items={this.state.interestingModules}
|
||||
onChange={(items) => this.setState({ interestingModules: items })}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,87 +1,28 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { CircleFill } from "react-bootstrap-icons";
|
||||
import { EmulationStatus, EmulationState as State } from "@/emulator";
|
||||
|
||||
function getStateName(state: State) {
|
||||
switch (state) {
|
||||
case State.Stopped:
|
||||
return "Stopped";
|
||||
case State.Paused:
|
||||
return "Paused";
|
||||
case State.Running:
|
||||
return "Running";
|
||||
case State.Failed:
|
||||
return "Failed";
|
||||
case State.Success:
|
||||
return "Success";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getStateColor(state: State) {
|
||||
switch (state) {
|
||||
case State.Failed:
|
||||
return "bg-orange-600";
|
||||
case State.Paused:
|
||||
return "bg-amber-500";
|
||||
case State.Success:
|
||||
return "bg-lime-600";
|
||||
case State.Stopped:
|
||||
return "bg-yellow-800";
|
||||
case State.Running:
|
||||
return "bg-sky-500";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getStateEmoji(state: State) {
|
||||
switch (state) {
|
||||
case State.Stopped:
|
||||
return "🟤";
|
||||
case State.Paused:
|
||||
return "🟡";
|
||||
case State.Running:
|
||||
return "🔵";
|
||||
case State.Failed:
|
||||
return "🔴";
|
||||
case State.Success:
|
||||
return "🟢";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getFilename(path: string) {
|
||||
const lastSlash = path.lastIndexOf("/");
|
||||
if (lastSlash == -1) {
|
||||
return path;
|
||||
}
|
||||
|
||||
return path.substring(lastSlash + 1);
|
||||
}
|
||||
|
||||
export interface StatusIndicatorProps {
|
||||
state: State;
|
||||
application: string | undefined;
|
||||
running: boolean;
|
||||
}
|
||||
|
||||
export function StatusIndicator(props: StatusIndicatorProps) {
|
||||
if (props.application && props.application.length > 0) {
|
||||
document.title = `${getStateEmoji(props.state)} ${getFilename(props.application)} | Sogen`;
|
||||
}
|
||||
const getText = () => {
|
||||
return props.running ? " Running" : " Stopped";
|
||||
};
|
||||
|
||||
const getColor = () => {
|
||||
return props.running ? "bg-lime-600" : "bg-orange-600";
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge variant="outline">
|
||||
<CircleFill
|
||||
className={
|
||||
getStateColor(props.state) +
|
||||
" rounded-full mr-1 n duration-200 ease-in-out"
|
||||
getColor() + " rounded-full mr-1 n duration-200 ease-in-out"
|
||||
}
|
||||
color="transparent"
|
||||
/>
|
||||
{getStateName(props.state)}
|
||||
{getText()}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
export interface TextTooltipProps {
|
||||
children?: React.ReactNode;
|
||||
tooltip: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TextTooltip(props: TextTooltipProps) {
|
||||
return (
|
||||
<Tooltip delayDuration={700} disableHoverableContent>
|
||||
<TooltipTrigger asChild>
|
||||
<span>{props.children}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{props.tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function ContextMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
||||
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
|
||||
}
|
||||
|
||||
function ContextMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
||||
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
|
||||
}
|
||||
|
||||
function ContextMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.RadioGroup
|
||||
data-slot="context-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.SubTrigger
|
||||
data-slot="context-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto" />
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.SubContent
|
||||
data-slot="context-menu-sub-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Portal>
|
||||
<ContextMenuPrimitive.Content
|
||||
data-slot="context-menu-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</ContextMenuPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
||||
inset?: boolean;
|
||||
variant?: "default" | "destructive";
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Item
|
||||
data-slot="context-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.CheckboxItem
|
||||
data-slot="context-menu-checkbox-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.CheckboxItem>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.RadioItem
|
||||
data-slot="context-menu-radio-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.RadioItem>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
|
||||
inset?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Label
|
||||
data-slot="context-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Separator
|
||||
data-slot="context-menu-separator"
|
||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="context-menu-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
ContextMenu,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuGroup,
|
||||
ContextMenuPortal,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuRadioGroup,
|
||||
};
|
||||
@@ -1,133 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { XIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Dialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||
}
|
||||
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||
}
|
||||
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||
}
|
||||
|
||||
function DialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-header"
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-footer"
|
||||
className={cn(
|
||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
data-slot="dialog-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
};
|
||||
@@ -1,130 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { Drawer as DrawerPrimitive } from "vaul";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Drawer({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
||||
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
|
||||
}
|
||||
|
||||
function DrawerTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
||||
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function DrawerPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
||||
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
|
||||
}
|
||||
|
||||
function DrawerClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
||||
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
|
||||
}
|
||||
|
||||
function DrawerOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
||||
return (
|
||||
<DrawerPrimitive.Overlay
|
||||
data-slot="drawer-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DrawerContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
|
||||
return (
|
||||
<DrawerPortal data-slot="drawer-portal">
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
data-slot="drawer-content"
|
||||
className={cn(
|
||||
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
|
||||
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
|
||||
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
|
||||
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
||||
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
);
|
||||
}
|
||||
|
||||
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="drawer-header"
|
||||
className={cn("flex flex-col gap-1.5 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="drawer-footer"
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DrawerTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
||||
return (
|
||||
<DrawerPrimitive.Title
|
||||
data-slot="drawer-title"
|
||||
className={cn("text-foreground font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DrawerDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
||||
return (
|
||||
<DrawerPrimitive.Description
|
||||
data-slot="drawer-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Drawer,
|
||||
DrawerPortal,
|
||||
DrawerOverlay,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
};
|
||||
@@ -1,255 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function DropdownMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
||||
}
|
||||
|
||||
function DropdownMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Trigger
|
||||
data-slot="dropdown-menu-trigger"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean;
|
||||
variant?: "default" | "destructive";
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Label
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
data-slot="dropdown-menu-separator"
|
||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
||||
}
|
||||
|
||||
function DropdownMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
||||
import { CircleIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function RadioGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Root
|
||||
data-slot="radio-group"
|
||||
className={cn("grid gap-3", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function RadioGroupItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
data-slot="radio-group-item"
|
||||
className={cn(
|
||||
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator
|
||||
data-slot="radio-group-indicator"
|
||||
className="relative flex items-center justify-center"
|
||||
>
|
||||
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
}
|
||||
|
||||
export { RadioGroup, RadioGroupItem };
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
@@ -44,13 +46,13 @@ function TooltipContent({
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-neutral-700 text-secondary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="bg-neutral-700 fill-neutral-700 z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
);
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
export interface FolderProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export function YoutubeVideo(props: FolderProps) {
|
||||
const style = `
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
img,
|
||||
div {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
div {
|
||||
height: 1.5em;
|
||||
text-align: center;
|
||||
font: 30px/1.5 sans-serif;
|
||||
color: white;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
span {
|
||||
background: red;
|
||||
padding: 10px 20px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 3px 5px 10px #0000007a;
|
||||
}
|
||||
`;
|
||||
|
||||
return (
|
||||
<iframe
|
||||
className="w-full h-full"
|
||||
title="Sogen Emulator Overview"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerPolicy="strict-origin-when-cross-origin"
|
||||
allowFullScreen
|
||||
srcDoc={`
|
||||
<style>${style}</style>
|
||||
<a href="https://www.youtube.com/embed/${props.id}/?autoplay=1&rel=0&hl=en">
|
||||
<img src="https://img.youtube.com/vi/${props.id}/maxresdefault.jpg">
|
||||
<div>
|
||||
<span> ▶</span>
|
||||
</div>
|
||||
</a>`}
|
||||
></iframe>
|
||||
);
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
export type DownloadProgressHandler = (
|
||||
receivedBytes: number,
|
||||
totalBytes: number,
|
||||
) => void;
|
||||
|
||||
export type DownloadPercentHandler = (percent: number) => void;
|
||||
|
||||
export function makePercentHandler(
|
||||
handler: DownloadPercentHandler,
|
||||
): DownloadProgressHandler {
|
||||
const progress = {
|
||||
tracked: 0,
|
||||
};
|
||||
|
||||
return (current, total) => {
|
||||
if (total == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const percent = Math.floor((current * 100) / total);
|
||||
const sanePercent = Math.max(Math.min(percent, 100), 0);
|
||||
|
||||
if (sanePercent + 1 > progress.tracked) {
|
||||
progress.tracked = sanePercent + 1;
|
||||
handler(sanePercent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function downloadBinaryFile(
|
||||
file: string,
|
||||
progressCallback: DownloadProgressHandler,
|
||||
) {
|
||||
return fetch(file, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
},
|
||||
}).then((response) => {
|
||||
const maybeReader = response.body?.getReader();
|
||||
if (!maybeReader) {
|
||||
throw new Error("Bad reader");
|
||||
}
|
||||
|
||||
const reader = maybeReader;
|
||||
|
||||
const contentLength = parseInt(
|
||||
response.headers?.get("Content-Length") || "0",
|
||||
);
|
||||
|
||||
let receivedLength = 0;
|
||||
let chunks: Uint8Array<ArrayBufferLike>[] = [];
|
||||
|
||||
function processData(
|
||||
res: ReadableStreamReadResult<Uint8Array<ArrayBufferLike>>,
|
||||
): Promise<ArrayBuffer> {
|
||||
if (res.value) {
|
||||
chunks.push(res.value);
|
||||
receivedLength += res.value.length;
|
||||
}
|
||||
|
||||
progressCallback(receivedLength, contentLength);
|
||||
|
||||
if (!res.done) {
|
||||
return reader.read().then(processData);
|
||||
}
|
||||
const chunksAll = new Uint8Array(receivedLength);
|
||||
let position = 0;
|
||||
for (const chunk of chunks) {
|
||||
chunksAll.set(new Uint8Array(chunk), position);
|
||||
position += chunk.length;
|
||||
}
|
||||
|
||||
return Promise.resolve(chunksAll.buffer);
|
||||
}
|
||||
|
||||
return reader.read().then(processData);
|
||||
});
|
||||
}
|
||||
|
||||
export function downloadBinaryFilePercent(
|
||||
file: string,
|
||||
progressCallback: DownloadPercentHandler,
|
||||
) {
|
||||
return downloadBinaryFile(file, makePercentHandler(progressCallback));
|
||||
}
|
||||
@@ -1,102 +1,24 @@
|
||||
import { Settings, translateSettings } from "./settings";
|
||||
|
||||
import * as flatbuffers from "flatbuffers";
|
||||
import * as fbDebugger from "@/fb/debugger";
|
||||
import { createDefaultSettings, Settings, translateSettings } from "./settings";
|
||||
import { FileEntry } from "./zip-file";
|
||||
|
||||
type LogHandler = (lines: string[]) => void;
|
||||
|
||||
export enum EmulationState {
|
||||
Stopped,
|
||||
Paused,
|
||||
Running,
|
||||
Success,
|
||||
Failed,
|
||||
export interface UserFile {
|
||||
name: string;
|
||||
data: ArrayBuffer;
|
||||
}
|
||||
|
||||
export interface EmulationStatus {
|
||||
activeThreads: number;
|
||||
reservedMemory: BigInt;
|
||||
committedMemory: BigInt;
|
||||
executedInstructions: BigInt;
|
||||
}
|
||||
|
||||
function createDefaultEmulationStatus(): EmulationStatus {
|
||||
return {
|
||||
executedInstructions: BigInt(0),
|
||||
activeThreads: 0,
|
||||
reservedMemory: BigInt(0),
|
||||
committedMemory: BigInt(0),
|
||||
};
|
||||
}
|
||||
|
||||
export function isFinalState(state: EmulationState) {
|
||||
switch (state) {
|
||||
case EmulationState.Stopped:
|
||||
case EmulationState.Success:
|
||||
case EmulationState.Failed:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function base64Encode(uint8Array: Uint8Array): string {
|
||||
let binaryString = "";
|
||||
for (let i = 0; i < uint8Array.byteLength; i++) {
|
||||
binaryString += String.fromCharCode(uint8Array[i]);
|
||||
}
|
||||
|
||||
return btoa(binaryString);
|
||||
}
|
||||
|
||||
function base64Decode(data: string) {
|
||||
const binaryString = atob(data);
|
||||
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function decodeEvent(data: string) {
|
||||
const array = base64Decode(data);
|
||||
const buffer = new flatbuffers.ByteBuffer(array);
|
||||
const event = fbDebugger.DebugEvent.getRootAsDebugEvent(buffer);
|
||||
return event.unpack();
|
||||
}
|
||||
|
||||
type StateChangeHandler = (state: EmulationState) => void;
|
||||
type StatusUpdateHandler = (status: EmulationStatus) => void;
|
||||
|
||||
const cacheBuster = undefined; //import.meta.env.VITE_BUILD_TIME || Date.now();
|
||||
|
||||
export class Emulator {
|
||||
filesystem: FileEntry[];
|
||||
logHandler: LogHandler;
|
||||
stateChangeHandler: StateChangeHandler;
|
||||
stautsUpdateHandler: StatusUpdateHandler;
|
||||
terminatePromise: Promise<number | null>;
|
||||
terminateResolve: (value: number | null) => void;
|
||||
terminateReject: (reason?: any) => void;
|
||||
worker: Worker;
|
||||
state: EmulationState = EmulationState.Stopped;
|
||||
exit_status: number | null = null;
|
||||
start_time: Date = new Date();
|
||||
pause_time: Date | null = null;
|
||||
paused_time: number = 0;
|
||||
|
||||
constructor(
|
||||
logHandler: LogHandler,
|
||||
stateChangeHandler: StateChangeHandler,
|
||||
stautsUpdateHandler: StatusUpdateHandler,
|
||||
) {
|
||||
constructor(filesystem: FileEntry[], logHandler: LogHandler) {
|
||||
this.filesystem = filesystem;
|
||||
this.logHandler = logHandler;
|
||||
this.stateChangeHandler = stateChangeHandler;
|
||||
this.stautsUpdateHandler = stautsUpdateHandler;
|
||||
this.terminateResolve = () => {};
|
||||
this.terminateReject = () => {};
|
||||
this.terminatePromise = new Promise((resolve, reject) => {
|
||||
@@ -104,184 +26,50 @@ export class Emulator {
|
||||
this.terminateReject = reject;
|
||||
});
|
||||
|
||||
const busterParams = cacheBuster ? `?${cacheBuster}` : "";
|
||||
this.worker = new Worker(
|
||||
/*new URL('./emulator-worker.js', import.meta.url)*/ "./emulator-worker.js",
|
||||
);
|
||||
|
||||
this.worker = new Worker("./emulator-worker.js" + busterParams);
|
||||
this.worker.onerror = this._onError.bind(this);
|
||||
this.worker.onmessage = (e) => queueMicrotask(() => this._onMessage(e));
|
||||
this.worker.onmessage = (event: MessageEvent) => {
|
||||
if (event.data.message == "log") {
|
||||
this.logHandler(event.data.data);
|
||||
} else if (event.data.message == "end") {
|
||||
this.terminateResolve(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async start(settings: Settings, file: string) {
|
||||
this.start_time = new Date();
|
||||
this.pause_time = null;
|
||||
this.paused_time = 0;
|
||||
this._setState(EmulationState.Running);
|
||||
this.stautsUpdateHandler(createDefaultEmulationStatus());
|
||||
|
||||
const options = translateSettings(settings);
|
||||
start(
|
||||
settings: Settings = createDefaultSettings(),
|
||||
userFile: UserFile | null = null,
|
||||
) {
|
||||
var file = "c:/test-sample.exe";
|
||||
if (userFile) {
|
||||
const filename = userFile.name.split("/").pop()?.split("\\").pop();
|
||||
const canonicalName = filename?.toLowerCase();
|
||||
file = "c:/" + canonicalName;
|
||||
this.filesystem.push({
|
||||
name: "root/filesys/c/" + canonicalName,
|
||||
data: userFile.data,
|
||||
});
|
||||
}
|
||||
|
||||
this.worker.postMessage({
|
||||
message: "run",
|
||||
data: {
|
||||
filesystem: this.filesystem,
|
||||
file,
|
||||
options: options.emulatorOptions,
|
||||
arguments: options.applicationOptions,
|
||||
persist: settings.persist,
|
||||
wasm64: settings.wasm64,
|
||||
cacheBuster,
|
||||
options: translateSettings(settings),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updateState() {
|
||||
this.sendEvent(
|
||||
new fbDebugger.DebugEventT(
|
||||
fbDebugger.Event.GetStateRequest,
|
||||
new fbDebugger.GetStateRequestT(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.worker.terminate();
|
||||
this._setState(EmulationState.Stopped);
|
||||
this.terminateResolve(null);
|
||||
}
|
||||
|
||||
onTerminate() {
|
||||
return this.terminatePromise;
|
||||
}
|
||||
|
||||
sendEvent(event: fbDebugger.DebugEventT) {
|
||||
const builder = new flatbuffers.Builder(1024);
|
||||
fbDebugger.DebugEvent.finishDebugEventBuffer(builder, event.pack(builder));
|
||||
|
||||
const message = base64Encode(builder.asUint8Array());
|
||||
|
||||
this.worker.postMessage({
|
||||
message: "event",
|
||||
data: message,
|
||||
});
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.sendEvent(
|
||||
new fbDebugger.DebugEventT(
|
||||
fbDebugger.Event.PauseRequest,
|
||||
new fbDebugger.PauseRequestT(),
|
||||
),
|
||||
);
|
||||
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.sendEvent(
|
||||
new fbDebugger.DebugEventT(
|
||||
fbDebugger.Event.RunRequest,
|
||||
new fbDebugger.RunRequestT(),
|
||||
),
|
||||
);
|
||||
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
getExecutionTime() {
|
||||
const endTime = this.pause_time ? this.pause_time : new Date();
|
||||
const totalTime = endTime.getTime() - this.start_time.getTime();
|
||||
return totalTime - this.paused_time;
|
||||
}
|
||||
|
||||
logError(message: string) {
|
||||
this.logHandler([`<span class="terminal-red">${message}</span>`]);
|
||||
}
|
||||
|
||||
_onError(ev: ErrorEvent) {
|
||||
try {
|
||||
this.worker.terminate();
|
||||
} catch (e) {}
|
||||
|
||||
this.logError(`Emulator encountered fatal error: ${ev.message}`);
|
||||
this._setState(EmulationState.Failed);
|
||||
this.terminateResolve(-1);
|
||||
}
|
||||
|
||||
_onMessage(event: MessageEvent) {
|
||||
if (event.data.message == "log") {
|
||||
this.logHandler(event.data.data);
|
||||
} else if (event.data.message == "event") {
|
||||
this._onEvent(decodeEvent(event.data.data));
|
||||
} else if (event.data.message == "end") {
|
||||
this._setState(
|
||||
this.exit_status === 0 ? EmulationState.Success : EmulationState.Failed,
|
||||
);
|
||||
this.terminateResolve(this.exit_status);
|
||||
}
|
||||
}
|
||||
|
||||
_onEvent(event: fbDebugger.DebugEventT) {
|
||||
switch (event.eventType) {
|
||||
case fbDebugger.Event.GetStateResponse:
|
||||
this._handle_state_response(
|
||||
event.event as fbDebugger.GetStateResponseT,
|
||||
);
|
||||
break;
|
||||
case fbDebugger.Event.ApplicationExit:
|
||||
this._handle_application_exit(
|
||||
event.event as fbDebugger.ApplicationExitT,
|
||||
);
|
||||
break;
|
||||
case fbDebugger.Event.EmulationStatus:
|
||||
this._handle_emulation_status(
|
||||
event.event as fbDebugger.EmulationStatusT,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_setState(state: EmulationState) {
|
||||
this.state = state;
|
||||
|
||||
if (isFinalState(this.state) || this.state === EmulationState.Paused) {
|
||||
this.pause_time = new Date();
|
||||
} else if (this.state == EmulationState.Running && this.pause_time) {
|
||||
this.paused_time += new Date().getTime() - this.pause_time.getTime();
|
||||
this.pause_time = null;
|
||||
}
|
||||
|
||||
this.stateChangeHandler(this.state);
|
||||
}
|
||||
|
||||
_handle_application_exit(info: fbDebugger.ApplicationExitT) {
|
||||
this.exit_status = info.exitStatus;
|
||||
}
|
||||
|
||||
_handle_emulation_status(info: fbDebugger.EmulationStatusT) {
|
||||
this.stautsUpdateHandler({
|
||||
activeThreads: info.activeThreads,
|
||||
executedInstructions: info.executedInstructions,
|
||||
reservedMemory: info.reservedMemory,
|
||||
committedMemory: info.committedMemory,
|
||||
});
|
||||
}
|
||||
|
||||
_handle_state_response(response: fbDebugger.GetStateResponseT) {
|
||||
switch (response.state) {
|
||||
case fbDebugger.State.None:
|
||||
this._setState(EmulationState.Stopped);
|
||||
break;
|
||||
|
||||
case fbDebugger.State.Paused:
|
||||
this._setState(EmulationState.Paused);
|
||||
break;
|
||||
|
||||
case fbDebugger.State.Running:
|
||||
this._setState(EmulationState.Running);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
export { ApplicationExit, ApplicationExitT } from './debugger/application-exit.js';
|
||||
export { DebugEvent, DebugEventT } from './debugger/debug-event.js';
|
||||
export { EmulationStatus, EmulationStatusT } from './debugger/emulation-status.js';
|
||||
export { Event } from './debugger/event.js';
|
||||
export { GetStateRequest, GetStateRequestT } from './debugger/get-state-request.js';
|
||||
export { GetStateResponse, GetStateResponseT } from './debugger/get-state-response.js';
|
||||
export { PauseRequest, PauseRequestT } from './debugger/pause-request.js';
|
||||
export { ReadMemoryRequest, ReadMemoryRequestT } from './debugger/read-memory-request.js';
|
||||
export { ReadMemoryResponse, ReadMemoryResponseT } from './debugger/read-memory-response.js';
|
||||
export { ReadRegisterRequest, ReadRegisterRequestT } from './debugger/read-register-request.js';
|
||||
export { ReadRegisterResponse, ReadRegisterResponseT } from './debugger/read-register-response.js';
|
||||
export { RunRequest, RunRequestT } from './debugger/run-request.js';
|
||||
export { State } from './debugger/state.js';
|
||||
export { WriteMemoryRequest, WriteMemoryRequestT } from './debugger/write-memory-request.js';
|
||||
export { WriteMemoryResponse, WriteMemoryResponseT } from './debugger/write-memory-response.js';
|
||||
export { WriteRegisterRequest, WriteRegisterRequestT } from './debugger/write-register-request.js';
|
||||
export { WriteRegisterResponse, WriteRegisterResponseT } from './debugger/write-register-response.js';
|
||||
@@ -1,86 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class ApplicationExit implements flatbuffers.IUnpackableObject<ApplicationExitT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):ApplicationExit {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsApplicationExit(bb:flatbuffers.ByteBuffer, obj?:ApplicationExit):ApplicationExit {
|
||||
return (obj || new ApplicationExit()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsApplicationExit(bb:flatbuffers.ByteBuffer, obj?:ApplicationExit):ApplicationExit {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new ApplicationExit()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
exitStatus():number|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : null;
|
||||
}
|
||||
|
||||
mutate_exit_status(value:number):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static startApplicationExit(builder:flatbuffers.Builder) {
|
||||
builder.startObject(1);
|
||||
}
|
||||
|
||||
static addExitStatus(builder:flatbuffers.Builder, exitStatus:number) {
|
||||
builder.addFieldInt32(0, exitStatus, null);
|
||||
}
|
||||
|
||||
static endApplicationExit(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createApplicationExit(builder:flatbuffers.Builder, exitStatus:number|null):flatbuffers.Offset {
|
||||
ApplicationExit.startApplicationExit(builder);
|
||||
if (exitStatus !== null)
|
||||
ApplicationExit.addExitStatus(builder, exitStatus);
|
||||
return ApplicationExit.endApplicationExit(builder);
|
||||
}
|
||||
|
||||
unpack(): ApplicationExitT {
|
||||
return new ApplicationExitT(
|
||||
this.exitStatus()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: ApplicationExitT): void {
|
||||
_o.exitStatus = this.exitStatus();
|
||||
}
|
||||
}
|
||||
|
||||
export class ApplicationExitT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public exitStatus: number|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return ApplicationExit.createApplicationExit(builder,
|
||||
this.exitStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
import { ApplicationExit, ApplicationExitT } from '../debugger/application-exit.js';
|
||||
import { EmulationStatus, EmulationStatusT } from '../debugger/emulation-status.js';
|
||||
import { Event, unionToEvent, unionListToEvent } from '../debugger/event.js';
|
||||
import { GetStateRequest, GetStateRequestT } from '../debugger/get-state-request.js';
|
||||
import { GetStateResponse, GetStateResponseT } from '../debugger/get-state-response.js';
|
||||
import { PauseRequest, PauseRequestT } from '../debugger/pause-request.js';
|
||||
import { ReadMemoryRequest, ReadMemoryRequestT } from '../debugger/read-memory-request.js';
|
||||
import { ReadMemoryResponse, ReadMemoryResponseT } from '../debugger/read-memory-response.js';
|
||||
import { ReadRegisterRequest, ReadRegisterRequestT } from '../debugger/read-register-request.js';
|
||||
import { ReadRegisterResponse, ReadRegisterResponseT } from '../debugger/read-register-response.js';
|
||||
import { RunRequest, RunRequestT } from '../debugger/run-request.js';
|
||||
import { WriteMemoryRequest, WriteMemoryRequestT } from '../debugger/write-memory-request.js';
|
||||
import { WriteMemoryResponse, WriteMemoryResponseT } from '../debugger/write-memory-response.js';
|
||||
import { WriteRegisterRequest, WriteRegisterRequestT } from '../debugger/write-register-request.js';
|
||||
import { WriteRegisterResponse, WriteRegisterResponseT } from '../debugger/write-register-response.js';
|
||||
|
||||
|
||||
export class DebugEvent implements flatbuffers.IUnpackableObject<DebugEventT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):DebugEvent {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsDebugEvent(bb:flatbuffers.ByteBuffer, obj?:DebugEvent):DebugEvent {
|
||||
return (obj || new DebugEvent()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsDebugEvent(bb:flatbuffers.ByteBuffer, obj?:DebugEvent):DebugEvent {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new DebugEvent()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
eventType():Event {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint8(this.bb_pos + offset) : Event.NONE;
|
||||
}
|
||||
|
||||
event<T extends flatbuffers.Table>(obj:any):any|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.__union(obj, this.bb_pos + offset) : null;
|
||||
}
|
||||
|
||||
static startDebugEvent(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addEventType(builder:flatbuffers.Builder, eventType:Event) {
|
||||
builder.addFieldInt8(0, eventType, Event.NONE);
|
||||
}
|
||||
|
||||
static addEvent(builder:flatbuffers.Builder, eventOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, eventOffset, 0);
|
||||
}
|
||||
|
||||
static endDebugEvent(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static finishDebugEventBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) {
|
||||
builder.finish(offset);
|
||||
}
|
||||
|
||||
static finishSizePrefixedDebugEventBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) {
|
||||
builder.finish(offset, undefined, true);
|
||||
}
|
||||
|
||||
static createDebugEvent(builder:flatbuffers.Builder, eventType:Event, eventOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
DebugEvent.startDebugEvent(builder);
|
||||
DebugEvent.addEventType(builder, eventType);
|
||||
DebugEvent.addEvent(builder, eventOffset);
|
||||
return DebugEvent.endDebugEvent(builder);
|
||||
}
|
||||
|
||||
unpack(): DebugEventT {
|
||||
return new DebugEventT(
|
||||
this.eventType(),
|
||||
(() => {
|
||||
const temp = unionToEvent(this.eventType(), this.event.bind(this));
|
||||
if(temp === null) { return null; }
|
||||
return temp.unpack()
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: DebugEventT): void {
|
||||
_o.eventType = this.eventType();
|
||||
_o.event = (() => {
|
||||
const temp = unionToEvent(this.eventType(), this.event.bind(this));
|
||||
if(temp === null) { return null; }
|
||||
return temp.unpack()
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugEventT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public eventType: Event = Event.NONE,
|
||||
public event: ApplicationExitT|EmulationStatusT|GetStateRequestT|GetStateResponseT|PauseRequestT|ReadMemoryRequestT|ReadMemoryResponseT|ReadRegisterRequestT|ReadRegisterResponseT|RunRequestT|WriteMemoryRequestT|WriteMemoryResponseT|WriteRegisterRequestT|WriteRegisterResponseT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const event = builder.createObjectOffset(this.event);
|
||||
|
||||
return DebugEvent.createDebugEvent(builder,
|
||||
this.eventType,
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class EmulationStatus implements flatbuffers.IUnpackableObject<EmulationStatusT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):EmulationStatus {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsEmulationStatus(bb:flatbuffers.ByteBuffer, obj?:EmulationStatus):EmulationStatus {
|
||||
return (obj || new EmulationStatus()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsEmulationStatus(bb:flatbuffers.ByteBuffer, obj?:EmulationStatus):EmulationStatus {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new EmulationStatus()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
activeThreads():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
mutate_active_threads(value:number):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
reservedMemory():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
mutate_reserved_memory(value:bigint):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint64(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
committedMemory():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
mutate_committed_memory(value:bigint):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint64(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
executedInstructions():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 10);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
mutate_executed_instructions(value:bigint):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 10);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint64(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static startEmulationStatus(builder:flatbuffers.Builder) {
|
||||
builder.startObject(4);
|
||||
}
|
||||
|
||||
static addActiveThreads(builder:flatbuffers.Builder, activeThreads:number) {
|
||||
builder.addFieldInt32(0, activeThreads, 0);
|
||||
}
|
||||
|
||||
static addReservedMemory(builder:flatbuffers.Builder, reservedMemory:bigint) {
|
||||
builder.addFieldInt64(1, reservedMemory, BigInt('0'));
|
||||
}
|
||||
|
||||
static addCommittedMemory(builder:flatbuffers.Builder, committedMemory:bigint) {
|
||||
builder.addFieldInt64(2, committedMemory, BigInt('0'));
|
||||
}
|
||||
|
||||
static addExecutedInstructions(builder:flatbuffers.Builder, executedInstructions:bigint) {
|
||||
builder.addFieldInt64(3, executedInstructions, BigInt('0'));
|
||||
}
|
||||
|
||||
static endEmulationStatus(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createEmulationStatus(builder:flatbuffers.Builder, activeThreads:number, reservedMemory:bigint, committedMemory:bigint, executedInstructions:bigint):flatbuffers.Offset {
|
||||
EmulationStatus.startEmulationStatus(builder);
|
||||
EmulationStatus.addActiveThreads(builder, activeThreads);
|
||||
EmulationStatus.addReservedMemory(builder, reservedMemory);
|
||||
EmulationStatus.addCommittedMemory(builder, committedMemory);
|
||||
EmulationStatus.addExecutedInstructions(builder, executedInstructions);
|
||||
return EmulationStatus.endEmulationStatus(builder);
|
||||
}
|
||||
|
||||
unpack(): EmulationStatusT {
|
||||
return new EmulationStatusT(
|
||||
this.activeThreads(),
|
||||
this.reservedMemory(),
|
||||
this.committedMemory(),
|
||||
this.executedInstructions()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: EmulationStatusT): void {
|
||||
_o.activeThreads = this.activeThreads();
|
||||
_o.reservedMemory = this.reservedMemory();
|
||||
_o.committedMemory = this.committedMemory();
|
||||
_o.executedInstructions = this.executedInstructions();
|
||||
}
|
||||
}
|
||||
|
||||
export class EmulationStatusT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public activeThreads: number = 0,
|
||||
public reservedMemory: bigint = BigInt('0'),
|
||||
public committedMemory: bigint = BigInt('0'),
|
||||
public executedInstructions: bigint = BigInt('0')
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return EmulationStatus.createEmulationStatus(builder,
|
||||
this.activeThreads,
|
||||
this.reservedMemory,
|
||||
this.committedMemory,
|
||||
this.executedInstructions
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { ApplicationExit, ApplicationExitT } from '../debugger/application-exit.js';
|
||||
import { EmulationStatus, EmulationStatusT } from '../debugger/emulation-status.js';
|
||||
import { GetStateRequest, GetStateRequestT } from '../debugger/get-state-request.js';
|
||||
import { GetStateResponse, GetStateResponseT } from '../debugger/get-state-response.js';
|
||||
import { PauseRequest, PauseRequestT } from '../debugger/pause-request.js';
|
||||
import { ReadMemoryRequest, ReadMemoryRequestT } from '../debugger/read-memory-request.js';
|
||||
import { ReadMemoryResponse, ReadMemoryResponseT } from '../debugger/read-memory-response.js';
|
||||
import { ReadRegisterRequest, ReadRegisterRequestT } from '../debugger/read-register-request.js';
|
||||
import { ReadRegisterResponse, ReadRegisterResponseT } from '../debugger/read-register-response.js';
|
||||
import { RunRequest, RunRequestT } from '../debugger/run-request.js';
|
||||
import { WriteMemoryRequest, WriteMemoryRequestT } from '../debugger/write-memory-request.js';
|
||||
import { WriteMemoryResponse, WriteMemoryResponseT } from '../debugger/write-memory-response.js';
|
||||
import { WriteRegisterRequest, WriteRegisterRequestT } from '../debugger/write-register-request.js';
|
||||
import { WriteRegisterResponse, WriteRegisterResponseT } from '../debugger/write-register-response.js';
|
||||
|
||||
|
||||
export enum Event {
|
||||
NONE = 0,
|
||||
PauseRequest = 1,
|
||||
RunRequest = 2,
|
||||
GetStateRequest = 3,
|
||||
GetStateResponse = 4,
|
||||
WriteMemoryRequest = 5,
|
||||
WriteMemoryResponse = 6,
|
||||
ReadMemoryRequest = 7,
|
||||
ReadMemoryResponse = 8,
|
||||
WriteRegisterRequest = 9,
|
||||
WriteRegisterResponse = 10,
|
||||
ReadRegisterRequest = 11,
|
||||
ReadRegisterResponse = 12,
|
||||
ApplicationExit = 13,
|
||||
EmulationStatus = 14
|
||||
}
|
||||
|
||||
export function unionToEvent(
|
||||
type: Event,
|
||||
accessor: (obj:ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null
|
||||
): ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null {
|
||||
switch(Event[type]) {
|
||||
case 'NONE': return null;
|
||||
case 'PauseRequest': return accessor(new PauseRequest())! as PauseRequest;
|
||||
case 'RunRequest': return accessor(new RunRequest())! as RunRequest;
|
||||
case 'GetStateRequest': return accessor(new GetStateRequest())! as GetStateRequest;
|
||||
case 'GetStateResponse': return accessor(new GetStateResponse())! as GetStateResponse;
|
||||
case 'WriteMemoryRequest': return accessor(new WriteMemoryRequest())! as WriteMemoryRequest;
|
||||
case 'WriteMemoryResponse': return accessor(new WriteMemoryResponse())! as WriteMemoryResponse;
|
||||
case 'ReadMemoryRequest': return accessor(new ReadMemoryRequest())! as ReadMemoryRequest;
|
||||
case 'ReadMemoryResponse': return accessor(new ReadMemoryResponse())! as ReadMemoryResponse;
|
||||
case 'WriteRegisterRequest': return accessor(new WriteRegisterRequest())! as WriteRegisterRequest;
|
||||
case 'WriteRegisterResponse': return accessor(new WriteRegisterResponse())! as WriteRegisterResponse;
|
||||
case 'ReadRegisterRequest': return accessor(new ReadRegisterRequest())! as ReadRegisterRequest;
|
||||
case 'ReadRegisterResponse': return accessor(new ReadRegisterResponse())! as ReadRegisterResponse;
|
||||
case 'ApplicationExit': return accessor(new ApplicationExit())! as ApplicationExit;
|
||||
case 'EmulationStatus': return accessor(new EmulationStatus())! as EmulationStatus;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function unionListToEvent(
|
||||
type: Event,
|
||||
accessor: (index: number, obj:ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null,
|
||||
index: number
|
||||
): ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null {
|
||||
switch(Event[type]) {
|
||||
case 'NONE': return null;
|
||||
case 'PauseRequest': return accessor(index, new PauseRequest())! as PauseRequest;
|
||||
case 'RunRequest': return accessor(index, new RunRequest())! as RunRequest;
|
||||
case 'GetStateRequest': return accessor(index, new GetStateRequest())! as GetStateRequest;
|
||||
case 'GetStateResponse': return accessor(index, new GetStateResponse())! as GetStateResponse;
|
||||
case 'WriteMemoryRequest': return accessor(index, new WriteMemoryRequest())! as WriteMemoryRequest;
|
||||
case 'WriteMemoryResponse': return accessor(index, new WriteMemoryResponse())! as WriteMemoryResponse;
|
||||
case 'ReadMemoryRequest': return accessor(index, new ReadMemoryRequest())! as ReadMemoryRequest;
|
||||
case 'ReadMemoryResponse': return accessor(index, new ReadMemoryResponse())! as ReadMemoryResponse;
|
||||
case 'WriteRegisterRequest': return accessor(index, new WriteRegisterRequest())! as WriteRegisterRequest;
|
||||
case 'WriteRegisterResponse': return accessor(index, new WriteRegisterResponse())! as WriteRegisterResponse;
|
||||
case 'ReadRegisterRequest': return accessor(index, new ReadRegisterRequest())! as ReadRegisterRequest;
|
||||
case 'ReadRegisterResponse': return accessor(index, new ReadRegisterResponse())! as ReadRegisterResponse;
|
||||
case 'ApplicationExit': return accessor(index, new ApplicationExit())! as ApplicationExit;
|
||||
case 'EmulationStatus': return accessor(index, new EmulationStatus())! as EmulationStatus;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class GetStateRequest implements flatbuffers.IUnpackableObject<GetStateRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):GetStateRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsGetStateRequest(bb:flatbuffers.ByteBuffer, obj?:GetStateRequest):GetStateRequest {
|
||||
return (obj || new GetStateRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsGetStateRequest(bb:flatbuffers.ByteBuffer, obj?:GetStateRequest):GetStateRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new GetStateRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static startGetStateRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(0);
|
||||
}
|
||||
|
||||
static endGetStateRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createGetStateRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
GetStateRequest.startGetStateRequest(builder);
|
||||
return GetStateRequest.endGetStateRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): GetStateRequestT {
|
||||
return new GetStateRequestT();
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: GetStateRequestT): void {}
|
||||
}
|
||||
|
||||
export class GetStateRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return GetStateRequest.createGetStateRequest(builder);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
import { State } from '../debugger/state.js';
|
||||
|
||||
|
||||
export class GetStateResponse implements flatbuffers.IUnpackableObject<GetStateResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):GetStateResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsGetStateResponse(bb:flatbuffers.ByteBuffer, obj?:GetStateResponse):GetStateResponse {
|
||||
return (obj || new GetStateResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsGetStateResponse(bb:flatbuffers.ByteBuffer, obj?:GetStateResponse):GetStateResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new GetStateResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
state():State {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : State.None;
|
||||
}
|
||||
|
||||
mutate_state(value:State):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static startGetStateResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(1);
|
||||
}
|
||||
|
||||
static addState(builder:flatbuffers.Builder, state:State) {
|
||||
builder.addFieldInt32(0, state, State.None);
|
||||
}
|
||||
|
||||
static endGetStateResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createGetStateResponse(builder:flatbuffers.Builder, state:State):flatbuffers.Offset {
|
||||
GetStateResponse.startGetStateResponse(builder);
|
||||
GetStateResponse.addState(builder, state);
|
||||
return GetStateResponse.endGetStateResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): GetStateResponseT {
|
||||
return new GetStateResponseT(
|
||||
this.state()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: GetStateResponseT): void {
|
||||
_o.state = this.state();
|
||||
}
|
||||
}
|
||||
|
||||
export class GetStateResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public state: State = State.None
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return GetStateResponse.createGetStateResponse(builder,
|
||||
this.state
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class PauseRequest implements flatbuffers.IUnpackableObject<PauseRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):PauseRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsPauseRequest(bb:flatbuffers.ByteBuffer, obj?:PauseRequest):PauseRequest {
|
||||
return (obj || new PauseRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsPauseRequest(bb:flatbuffers.ByteBuffer, obj?:PauseRequest):PauseRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new PauseRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static startPauseRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(0);
|
||||
}
|
||||
|
||||
static endPauseRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createPauseRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
PauseRequest.startPauseRequest(builder);
|
||||
return PauseRequest.endPauseRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): PauseRequestT {
|
||||
return new PauseRequestT();
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: PauseRequestT): void {}
|
||||
}
|
||||
|
||||
export class PauseRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return PauseRequest.createPauseRequest(builder);
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class ReadMemoryRequest implements flatbuffers.IUnpackableObject<ReadMemoryRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):ReadMemoryRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsReadMemoryRequest(bb:flatbuffers.ByteBuffer, obj?:ReadMemoryRequest):ReadMemoryRequest {
|
||||
return (obj || new ReadMemoryRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsReadMemoryRequest(bb:flatbuffers.ByteBuffer, obj?:ReadMemoryRequest):ReadMemoryRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new ReadMemoryRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
address():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
mutate_address(value:bigint):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint64(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
size():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
mutate_size(value:number):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static startReadMemoryRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addAddress(builder:flatbuffers.Builder, address:bigint) {
|
||||
builder.addFieldInt64(0, address, BigInt('0'));
|
||||
}
|
||||
|
||||
static addSize(builder:flatbuffers.Builder, size:number) {
|
||||
builder.addFieldInt32(1, size, 0);
|
||||
}
|
||||
|
||||
static endReadMemoryRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createReadMemoryRequest(builder:flatbuffers.Builder, address:bigint, size:number):flatbuffers.Offset {
|
||||
ReadMemoryRequest.startReadMemoryRequest(builder);
|
||||
ReadMemoryRequest.addAddress(builder, address);
|
||||
ReadMemoryRequest.addSize(builder, size);
|
||||
return ReadMemoryRequest.endReadMemoryRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): ReadMemoryRequestT {
|
||||
return new ReadMemoryRequestT(
|
||||
this.address(),
|
||||
this.size()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: ReadMemoryRequestT): void {
|
||||
_o.address = this.address();
|
||||
_o.size = this.size();
|
||||
}
|
||||
}
|
||||
|
||||
export class ReadMemoryRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public address: bigint = BigInt('0'),
|
||||
public size: number = 0
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return ReadMemoryRequest.createReadMemoryRequest(builder,
|
||||
this.address,
|
||||
this.size
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class ReadMemoryResponse implements flatbuffers.IUnpackableObject<ReadMemoryResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):ReadMemoryResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsReadMemoryResponse(bb:flatbuffers.ByteBuffer, obj?:ReadMemoryResponse):ReadMemoryResponse {
|
||||
return (obj || new ReadMemoryResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsReadMemoryResponse(bb:flatbuffers.ByteBuffer, obj?:ReadMemoryResponse):ReadMemoryResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new ReadMemoryResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
address():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
mutate_address(value:bigint):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint64(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
data(index: number):number|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint8(this.bb!.__vector(this.bb_pos + offset) + index) : 0;
|
||||
}
|
||||
|
||||
dataLength():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
dataArray():Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? new Uint8Array(this.bb!.bytes().buffer, this.bb!.bytes().byteOffset + this.bb!.__vector(this.bb_pos + offset), this.bb!.__vector_len(this.bb_pos + offset)) : null;
|
||||
}
|
||||
|
||||
static startReadMemoryResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addAddress(builder:flatbuffers.Builder, address:bigint) {
|
||||
builder.addFieldInt64(0, address, BigInt('0'));
|
||||
}
|
||||
|
||||
static addData(builder:flatbuffers.Builder, dataOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, dataOffset, 0);
|
||||
}
|
||||
|
||||
static createDataVector(builder:flatbuffers.Builder, data:number[]|Uint8Array):flatbuffers.Offset {
|
||||
builder.startVector(1, data.length, 1);
|
||||
for (let i = data.length - 1; i >= 0; i--) {
|
||||
builder.addInt8(data[i]!);
|
||||
}
|
||||
return builder.endVector();
|
||||
}
|
||||
|
||||
static startDataVector(builder:flatbuffers.Builder, numElems:number) {
|
||||
builder.startVector(1, numElems, 1);
|
||||
}
|
||||
|
||||
static endReadMemoryResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createReadMemoryResponse(builder:flatbuffers.Builder, address:bigint, dataOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
ReadMemoryResponse.startReadMemoryResponse(builder);
|
||||
ReadMemoryResponse.addAddress(builder, address);
|
||||
ReadMemoryResponse.addData(builder, dataOffset);
|
||||
return ReadMemoryResponse.endReadMemoryResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): ReadMemoryResponseT {
|
||||
return new ReadMemoryResponseT(
|
||||
this.address(),
|
||||
this.bb!.createScalarList<number>(this.data.bind(this), this.dataLength())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: ReadMemoryResponseT): void {
|
||||
_o.address = this.address();
|
||||
_o.data = this.bb!.createScalarList<number>(this.data.bind(this), this.dataLength());
|
||||
}
|
||||
}
|
||||
|
||||
export class ReadMemoryResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public address: bigint = BigInt('0'),
|
||||
public data: (number)[] = []
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const data = ReadMemoryResponse.createDataVector(builder, this.data);
|
||||
|
||||
return ReadMemoryResponse.createReadMemoryResponse(builder,
|
||||
this.address,
|
||||
data
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class ReadRegisterRequest implements flatbuffers.IUnpackableObject<ReadRegisterRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):ReadRegisterRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsReadRegisterRequest(bb:flatbuffers.ByteBuffer, obj?:ReadRegisterRequest):ReadRegisterRequest {
|
||||
return (obj || new ReadRegisterRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsReadRegisterRequest(bb:flatbuffers.ByteBuffer, obj?:ReadRegisterRequest):ReadRegisterRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new ReadRegisterRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
register():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
mutate_register(value:number):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static startReadRegisterRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(1);
|
||||
}
|
||||
|
||||
static addRegister(builder:flatbuffers.Builder, register:number) {
|
||||
builder.addFieldInt32(0, register, 0);
|
||||
}
|
||||
|
||||
static endReadRegisterRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createReadRegisterRequest(builder:flatbuffers.Builder, register:number):flatbuffers.Offset {
|
||||
ReadRegisterRequest.startReadRegisterRequest(builder);
|
||||
ReadRegisterRequest.addRegister(builder, register);
|
||||
return ReadRegisterRequest.endReadRegisterRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): ReadRegisterRequestT {
|
||||
return new ReadRegisterRequestT(
|
||||
this.register()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: ReadRegisterRequestT): void {
|
||||
_o.register = this.register();
|
||||
}
|
||||
}
|
||||
|
||||
export class ReadRegisterRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public register: number = 0
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return ReadRegisterRequest.createReadRegisterRequest(builder,
|
||||
this.register
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class ReadRegisterResponse implements flatbuffers.IUnpackableObject<ReadRegisterResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):ReadRegisterResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsReadRegisterResponse(bb:flatbuffers.ByteBuffer, obj?:ReadRegisterResponse):ReadRegisterResponse {
|
||||
return (obj || new ReadRegisterResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsReadRegisterResponse(bb:flatbuffers.ByteBuffer, obj?:ReadRegisterResponse):ReadRegisterResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new ReadRegisterResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
register():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
mutate_register(value:number):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
data(index: number):number|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint8(this.bb!.__vector(this.bb_pos + offset) + index) : 0;
|
||||
}
|
||||
|
||||
dataLength():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
dataArray():Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? new Uint8Array(this.bb!.bytes().buffer, this.bb!.bytes().byteOffset + this.bb!.__vector(this.bb_pos + offset), this.bb!.__vector_len(this.bb_pos + offset)) : null;
|
||||
}
|
||||
|
||||
static startReadRegisterResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addRegister(builder:flatbuffers.Builder, register:number) {
|
||||
builder.addFieldInt32(0, register, 0);
|
||||
}
|
||||
|
||||
static addData(builder:flatbuffers.Builder, dataOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, dataOffset, 0);
|
||||
}
|
||||
|
||||
static createDataVector(builder:flatbuffers.Builder, data:number[]|Uint8Array):flatbuffers.Offset {
|
||||
builder.startVector(1, data.length, 1);
|
||||
for (let i = data.length - 1; i >= 0; i--) {
|
||||
builder.addInt8(data[i]!);
|
||||
}
|
||||
return builder.endVector();
|
||||
}
|
||||
|
||||
static startDataVector(builder:flatbuffers.Builder, numElems:number) {
|
||||
builder.startVector(1, numElems, 1);
|
||||
}
|
||||
|
||||
static endReadRegisterResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createReadRegisterResponse(builder:flatbuffers.Builder, register:number, dataOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
ReadRegisterResponse.startReadRegisterResponse(builder);
|
||||
ReadRegisterResponse.addRegister(builder, register);
|
||||
ReadRegisterResponse.addData(builder, dataOffset);
|
||||
return ReadRegisterResponse.endReadRegisterResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): ReadRegisterResponseT {
|
||||
return new ReadRegisterResponseT(
|
||||
this.register(),
|
||||
this.bb!.createScalarList<number>(this.data.bind(this), this.dataLength())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: ReadRegisterResponseT): void {
|
||||
_o.register = this.register();
|
||||
_o.data = this.bb!.createScalarList<number>(this.data.bind(this), this.dataLength());
|
||||
}
|
||||
}
|
||||
|
||||
export class ReadRegisterResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public register: number = 0,
|
||||
public data: (number)[] = []
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const data = ReadRegisterResponse.createDataVector(builder, this.data);
|
||||
|
||||
return ReadRegisterResponse.createReadRegisterResponse(builder,
|
||||
this.register,
|
||||
data
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class RunRequest implements flatbuffers.IUnpackableObject<RunRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):RunRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsRunRequest(bb:flatbuffers.ByteBuffer, obj?:RunRequest):RunRequest {
|
||||
return (obj || new RunRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsRunRequest(bb:flatbuffers.ByteBuffer, obj?:RunRequest):RunRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new RunRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
singleStep():boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false;
|
||||
}
|
||||
|
||||
mutate_single_step(value:boolean):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeInt8(this.bb_pos + offset, +value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static startRunRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(1);
|
||||
}
|
||||
|
||||
static addSingleStep(builder:flatbuffers.Builder, singleStep:boolean) {
|
||||
builder.addFieldInt8(0, +singleStep, +false);
|
||||
}
|
||||
|
||||
static endRunRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createRunRequest(builder:flatbuffers.Builder, singleStep:boolean):flatbuffers.Offset {
|
||||
RunRequest.startRunRequest(builder);
|
||||
RunRequest.addSingleStep(builder, singleStep);
|
||||
return RunRequest.endRunRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): RunRequestT {
|
||||
return new RunRequestT(
|
||||
this.singleStep()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: RunRequestT): void {
|
||||
_o.singleStep = this.singleStep();
|
||||
}
|
||||
}
|
||||
|
||||
export class RunRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public singleStep: boolean = false
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return RunRequest.createRunRequest(builder,
|
||||
this.singleStep
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
export enum State {
|
||||
None = 0,
|
||||
Running = 1,
|
||||
Paused = 2
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class WriteMemoryRequest implements flatbuffers.IUnpackableObject<WriteMemoryRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):WriteMemoryRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsWriteMemoryRequest(bb:flatbuffers.ByteBuffer, obj?:WriteMemoryRequest):WriteMemoryRequest {
|
||||
return (obj || new WriteMemoryRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsWriteMemoryRequest(bb:flatbuffers.ByteBuffer, obj?:WriteMemoryRequest):WriteMemoryRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new WriteMemoryRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
address():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
mutate_address(value:bigint):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint64(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
data(index: number):number|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint8(this.bb!.__vector(this.bb_pos + offset) + index) : 0;
|
||||
}
|
||||
|
||||
dataLength():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
dataArray():Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? new Uint8Array(this.bb!.bytes().buffer, this.bb!.bytes().byteOffset + this.bb!.__vector(this.bb_pos + offset), this.bb!.__vector_len(this.bb_pos + offset)) : null;
|
||||
}
|
||||
|
||||
static startWriteMemoryRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addAddress(builder:flatbuffers.Builder, address:bigint) {
|
||||
builder.addFieldInt64(0, address, BigInt('0'));
|
||||
}
|
||||
|
||||
static addData(builder:flatbuffers.Builder, dataOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, dataOffset, 0);
|
||||
}
|
||||
|
||||
static createDataVector(builder:flatbuffers.Builder, data:number[]|Uint8Array):flatbuffers.Offset {
|
||||
builder.startVector(1, data.length, 1);
|
||||
for (let i = data.length - 1; i >= 0; i--) {
|
||||
builder.addInt8(data[i]!);
|
||||
}
|
||||
return builder.endVector();
|
||||
}
|
||||
|
||||
static startDataVector(builder:flatbuffers.Builder, numElems:number) {
|
||||
builder.startVector(1, numElems, 1);
|
||||
}
|
||||
|
||||
static endWriteMemoryRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createWriteMemoryRequest(builder:flatbuffers.Builder, address:bigint, dataOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
WriteMemoryRequest.startWriteMemoryRequest(builder);
|
||||
WriteMemoryRequest.addAddress(builder, address);
|
||||
WriteMemoryRequest.addData(builder, dataOffset);
|
||||
return WriteMemoryRequest.endWriteMemoryRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): WriteMemoryRequestT {
|
||||
return new WriteMemoryRequestT(
|
||||
this.address(),
|
||||
this.bb!.createScalarList<number>(this.data.bind(this), this.dataLength())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: WriteMemoryRequestT): void {
|
||||
_o.address = this.address();
|
||||
_o.data = this.bb!.createScalarList<number>(this.data.bind(this), this.dataLength());
|
||||
}
|
||||
}
|
||||
|
||||
export class WriteMemoryRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public address: bigint = BigInt('0'),
|
||||
public data: (number)[] = []
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const data = WriteMemoryRequest.createDataVector(builder, this.data);
|
||||
|
||||
return WriteMemoryRequest.createWriteMemoryRequest(builder,
|
||||
this.address,
|
||||
data
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class WriteMemoryResponse implements flatbuffers.IUnpackableObject<WriteMemoryResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):WriteMemoryResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsWriteMemoryResponse(bb:flatbuffers.ByteBuffer, obj?:WriteMemoryResponse):WriteMemoryResponse {
|
||||
return (obj || new WriteMemoryResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsWriteMemoryResponse(bb:flatbuffers.ByteBuffer, obj?:WriteMemoryResponse):WriteMemoryResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new WriteMemoryResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
address():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
mutate_address(value:bigint):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint64(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
size():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
mutate_size(value:number):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
success():boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false;
|
||||
}
|
||||
|
||||
mutate_success(value:boolean):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeInt8(this.bb_pos + offset, +value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static startWriteMemoryResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(3);
|
||||
}
|
||||
|
||||
static addAddress(builder:flatbuffers.Builder, address:bigint) {
|
||||
builder.addFieldInt64(0, address, BigInt('0'));
|
||||
}
|
||||
|
||||
static addSize(builder:flatbuffers.Builder, size:number) {
|
||||
builder.addFieldInt32(1, size, 0);
|
||||
}
|
||||
|
||||
static addSuccess(builder:flatbuffers.Builder, success:boolean) {
|
||||
builder.addFieldInt8(2, +success, +false);
|
||||
}
|
||||
|
||||
static endWriteMemoryResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createWriteMemoryResponse(builder:flatbuffers.Builder, address:bigint, size:number, success:boolean):flatbuffers.Offset {
|
||||
WriteMemoryResponse.startWriteMemoryResponse(builder);
|
||||
WriteMemoryResponse.addAddress(builder, address);
|
||||
WriteMemoryResponse.addSize(builder, size);
|
||||
WriteMemoryResponse.addSuccess(builder, success);
|
||||
return WriteMemoryResponse.endWriteMemoryResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): WriteMemoryResponseT {
|
||||
return new WriteMemoryResponseT(
|
||||
this.address(),
|
||||
this.size(),
|
||||
this.success()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: WriteMemoryResponseT): void {
|
||||
_o.address = this.address();
|
||||
_o.size = this.size();
|
||||
_o.success = this.success();
|
||||
}
|
||||
}
|
||||
|
||||
export class WriteMemoryResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public address: bigint = BigInt('0'),
|
||||
public size: number = 0,
|
||||
public success: boolean = false
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return WriteMemoryResponse.createWriteMemoryResponse(builder,
|
||||
this.address,
|
||||
this.size,
|
||||
this.success
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class WriteRegisterRequest implements flatbuffers.IUnpackableObject<WriteRegisterRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):WriteRegisterRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsWriteRegisterRequest(bb:flatbuffers.ByteBuffer, obj?:WriteRegisterRequest):WriteRegisterRequest {
|
||||
return (obj || new WriteRegisterRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsWriteRegisterRequest(bb:flatbuffers.ByteBuffer, obj?:WriteRegisterRequest):WriteRegisterRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new WriteRegisterRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
register():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
mutate_register(value:number):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
data(index: number):number|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint8(this.bb!.__vector(this.bb_pos + offset) + index) : 0;
|
||||
}
|
||||
|
||||
dataLength():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
dataArray():Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? new Uint8Array(this.bb!.bytes().buffer, this.bb!.bytes().byteOffset + this.bb!.__vector(this.bb_pos + offset), this.bb!.__vector_len(this.bb_pos + offset)) : null;
|
||||
}
|
||||
|
||||
static startWriteRegisterRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addRegister(builder:flatbuffers.Builder, register:number) {
|
||||
builder.addFieldInt32(0, register, 0);
|
||||
}
|
||||
|
||||
static addData(builder:flatbuffers.Builder, dataOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, dataOffset, 0);
|
||||
}
|
||||
|
||||
static createDataVector(builder:flatbuffers.Builder, data:number[]|Uint8Array):flatbuffers.Offset {
|
||||
builder.startVector(1, data.length, 1);
|
||||
for (let i = data.length - 1; i >= 0; i--) {
|
||||
builder.addInt8(data[i]!);
|
||||
}
|
||||
return builder.endVector();
|
||||
}
|
||||
|
||||
static startDataVector(builder:flatbuffers.Builder, numElems:number) {
|
||||
builder.startVector(1, numElems, 1);
|
||||
}
|
||||
|
||||
static endWriteRegisterRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createWriteRegisterRequest(builder:flatbuffers.Builder, register:number, dataOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
WriteRegisterRequest.startWriteRegisterRequest(builder);
|
||||
WriteRegisterRequest.addRegister(builder, register);
|
||||
WriteRegisterRequest.addData(builder, dataOffset);
|
||||
return WriteRegisterRequest.endWriteRegisterRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): WriteRegisterRequestT {
|
||||
return new WriteRegisterRequestT(
|
||||
this.register(),
|
||||
this.bb!.createScalarList<number>(this.data.bind(this), this.dataLength())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: WriteRegisterRequestT): void {
|
||||
_o.register = this.register();
|
||||
_o.data = this.bb!.createScalarList<number>(this.data.bind(this), this.dataLength());
|
||||
}
|
||||
}
|
||||
|
||||
export class WriteRegisterRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public register: number = 0,
|
||||
public data: (number)[] = []
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const data = WriteRegisterRequest.createDataVector(builder, this.data);
|
||||
|
||||
return WriteRegisterRequest.createWriteRegisterRequest(builder,
|
||||
this.register,
|
||||
data
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import * as flatbuffers from 'flatbuffers';
|
||||
|
||||
|
||||
|
||||
export class WriteRegisterResponse implements flatbuffers.IUnpackableObject<WriteRegisterResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):WriteRegisterResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsWriteRegisterResponse(bb:flatbuffers.ByteBuffer, obj?:WriteRegisterResponse):WriteRegisterResponse {
|
||||
return (obj || new WriteRegisterResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsWriteRegisterResponse(bb:flatbuffers.ByteBuffer, obj?:WriteRegisterResponse):WriteRegisterResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new WriteRegisterResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
register():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
mutate_register(value:number):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
size():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
mutate_size(value:number):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeUint32(this.bb_pos + offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
success():boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false;
|
||||
}
|
||||
|
||||
mutate_success(value:boolean):boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
|
||||
if (offset === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bb!.writeInt8(this.bb_pos + offset, +value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static startWriteRegisterResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(3);
|
||||
}
|
||||
|
||||
static addRegister(builder:flatbuffers.Builder, register:number) {
|
||||
builder.addFieldInt32(0, register, 0);
|
||||
}
|
||||
|
||||
static addSize(builder:flatbuffers.Builder, size:number) {
|
||||
builder.addFieldInt32(1, size, 0);
|
||||
}
|
||||
|
||||
static addSuccess(builder:flatbuffers.Builder, success:boolean) {
|
||||
builder.addFieldInt8(2, +success, +false);
|
||||
}
|
||||
|
||||
static endWriteRegisterResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createWriteRegisterResponse(builder:flatbuffers.Builder, register:number, size:number, success:boolean):flatbuffers.Offset {
|
||||
WriteRegisterResponse.startWriteRegisterResponse(builder);
|
||||
WriteRegisterResponse.addRegister(builder, register);
|
||||
WriteRegisterResponse.addSize(builder, size);
|
||||
WriteRegisterResponse.addSuccess(builder, success);
|
||||
return WriteRegisterResponse.endWriteRegisterResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): WriteRegisterResponseT {
|
||||
return new WriteRegisterResponseT(
|
||||
this.register(),
|
||||
this.size(),
|
||||
this.success()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: WriteRegisterResponseT): void {
|
||||
_o.register = this.register();
|
||||
_o.size = this.size();
|
||||
_o.success = this.success();
|
||||
}
|
||||
}
|
||||
|
||||
export class WriteRegisterResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public register: number = 0,
|
||||
public size: number = 0,
|
||||
public success: boolean = false
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return WriteRegisterResponse.createWriteRegisterResponse(builder,
|
||||
this.register,
|
||||
this.size,
|
||||
this.success
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
export * as Debugger from './debugger.js';
|
||||
@@ -1,702 +0,0 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Folder,
|
||||
FolderElement,
|
||||
FolderElementType,
|
||||
trimFilename,
|
||||
} from "./components/folder";
|
||||
import { Filesystem } from "./filesystem";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "./components/ui/button";
|
||||
import { Input } from "./components/ui/input";
|
||||
import { DialogDescription } from "@radix-ui/react-dialog";
|
||||
|
||||
import Dropzone from "react-dropzone";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
|
||||
import { HouseFill } from "react-bootstrap-icons";
|
||||
import { parsePeIcon } from "./pe-icon-parser";
|
||||
|
||||
export interface FilesystemExplorerProps {
|
||||
filesystem: Filesystem;
|
||||
runFile: (file: string) => void;
|
||||
resetFilesys: () => void;
|
||||
path: string[];
|
||||
iconCache: Map<string, string | null>;
|
||||
}
|
||||
export interface FilesystemExplorerState {
|
||||
path: string[];
|
||||
createFolder: boolean;
|
||||
resetFilesys: boolean;
|
||||
errorText: string;
|
||||
removeFile: string;
|
||||
renameFile: string;
|
||||
}
|
||||
|
||||
function makeFullPath(path: string[]) {
|
||||
return "/root/filesys/" + path.join("/");
|
||||
}
|
||||
|
||||
function makeFullPathAndJoin(path: string[], element: string) {
|
||||
return makeFullPath([...path, element]);
|
||||
}
|
||||
|
||||
function makeFullPathWithState(
|
||||
state: FilesystemExplorerState,
|
||||
element: string,
|
||||
) {
|
||||
return makeFullPathAndJoin(state.path, element);
|
||||
}
|
||||
|
||||
function relativePathToWindowsPath(fullPath: string) {
|
||||
if (fullPath.length == 0) {
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
const drive = fullPath.substring(0, 1);
|
||||
const rest = fullPath.substring(1);
|
||||
|
||||
return `${drive}:${rest}`;
|
||||
}
|
||||
|
||||
function makeRelativePathWithState(
|
||||
state: FilesystemExplorerState,
|
||||
element: string,
|
||||
) {
|
||||
return [...state.path, element].join("/");
|
||||
}
|
||||
|
||||
function makeWindowsPathWithState(
|
||||
state: FilesystemExplorerState,
|
||||
element: string,
|
||||
) {
|
||||
const fullPath = makeRelativePathWithState(state, element);
|
||||
return relativePathToWindowsPath(fullPath);
|
||||
}
|
||||
|
||||
function getFolderElements(filesystem: Filesystem, path: string[]) {
|
||||
const fullPath = makeFullPath(path);
|
||||
const files = filesystem.readDir(fullPath);
|
||||
|
||||
return files
|
||||
.filter((f) => {
|
||||
if (f == ".") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path.length == 0 && f == "..") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((f) => {
|
||||
const element: FolderElement = {
|
||||
name: f,
|
||||
type: filesystem.isFolder(`${fullPath}/${f}`)
|
||||
? FolderElementType.Folder
|
||||
: FolderElementType.File,
|
||||
};
|
||||
|
||||
return element;
|
||||
});
|
||||
}
|
||||
|
||||
interface FileWithData {
|
||||
file: File;
|
||||
data: ArrayBuffer;
|
||||
}
|
||||
|
||||
function readFile(file: File): Promise<FileWithData> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
if (reader.readyState === FileReader.DONE) {
|
||||
resolve({
|
||||
file,
|
||||
data: reader.result as ArrayBuffer,
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
async function readFiles(files: FileList | File[]): Promise<FileWithData[]> {
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
promises.push(readFile(files[i]));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function selectFiles(): Promise<FileList> {
|
||||
return new Promise((resolve) => {
|
||||
const fileInput = document.createElement("input");
|
||||
fileInput.type = "file";
|
||||
fileInput.accept = ".exe";
|
||||
|
||||
fileInput.addEventListener("change", function (event) {
|
||||
const files = (event as any).target.files as FileList;
|
||||
resolve(files);
|
||||
});
|
||||
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
function getPeIcon(
|
||||
filesystem: Filesystem,
|
||||
file: string,
|
||||
cache: Map<string, string | null>,
|
||||
) {
|
||||
if (!file || !file.endsWith(".exe")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cachedValue = cache.get(file);
|
||||
if (cachedValue) {
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
const data = filesystem.readFile(file);
|
||||
const icon = parsePeIcon(data);
|
||||
cache.set(file, icon);
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
interface BreadcrumbElement {
|
||||
node: React.ReactNode;
|
||||
targetPath: string[];
|
||||
}
|
||||
|
||||
function isGoodPath(path: any) {
|
||||
return typeof path === "string" && path.length > 0;
|
||||
}
|
||||
|
||||
function trimLeadingSlash(path: string) {
|
||||
if (path.startsWith("/")) {
|
||||
return path.substring(1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function getFileName(file: File) {
|
||||
const fileObj = file as any;
|
||||
const properties = ["relativePath", "webkitRelativePath", "name"];
|
||||
|
||||
for (let i = 0; i < properties.length; ++i) {
|
||||
const prop = properties[i];
|
||||
|
||||
if (prop in fileObj) {
|
||||
const relativePath = fileObj[prop];
|
||||
if (isGoodPath(relativePath)) {
|
||||
return trimLeadingSlash(relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return file.name;
|
||||
}
|
||||
|
||||
function generateBreadcrumbElements(path: string[]): BreadcrumbElement[] {
|
||||
const elements = path.map((p, index) => {
|
||||
const e: BreadcrumbElement = {
|
||||
node: p,
|
||||
targetPath: path.slice(0, index + 1),
|
||||
};
|
||||
|
||||
return e;
|
||||
});
|
||||
elements.unshift({
|
||||
node: <HouseFill />,
|
||||
targetPath: [],
|
||||
});
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
function downloadData(
|
||||
data: Uint8Array,
|
||||
filename: string,
|
||||
mimeType: string = "application/octet-stream",
|
||||
) {
|
||||
const buffer = data.buffer.slice(
|
||||
data.byteOffset,
|
||||
data.byteOffset + data.byteLength,
|
||||
) as ArrayBuffer;
|
||||
const blob = new Blob([buffer], { type: mimeType });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export class FilesystemExplorer extends React.Component<
|
||||
FilesystemExplorerProps,
|
||||
FilesystemExplorerState
|
||||
> {
|
||||
constructor(props: FilesystemExplorerProps) {
|
||||
super(props);
|
||||
|
||||
this._onAddFiles = this._onAddFiles.bind(this);
|
||||
this._uploadFiles = this._uploadFiles.bind(this);
|
||||
this._onElementSelect = this._onElementSelect.bind(this);
|
||||
|
||||
this.state = {
|
||||
path: this.props.path,
|
||||
createFolder: false,
|
||||
resetFilesys: false,
|
||||
errorText: "",
|
||||
removeFile: "",
|
||||
renameFile: "",
|
||||
};
|
||||
}
|
||||
|
||||
_showError(errorText: string) {
|
||||
this.setState({ errorText });
|
||||
}
|
||||
|
||||
_onElementSelect(element: FolderElement) {
|
||||
if (element.type != FolderElementType.Folder) {
|
||||
if (element.name.endsWith(".exe")) {
|
||||
const file = makeWindowsPathWithState(this.state, element.name);
|
||||
this.props.runFile(file);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState((s) => {
|
||||
const path = [...s.path];
|
||||
|
||||
if (element.name == "..") {
|
||||
path.pop();
|
||||
} else {
|
||||
path.push(element.name);
|
||||
}
|
||||
|
||||
return {
|
||||
path,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async _onFileRename(file: string, newFile: string) {
|
||||
newFile = newFile.toLowerCase();
|
||||
|
||||
if (newFile == file) {
|
||||
this.setState({ renameFile: "" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._validateName(newFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldPath = makeFullPathWithState(this.state, file);
|
||||
const newPath = makeFullPathWithState(this.state, newFile);
|
||||
|
||||
this.setState({ renameFile: "" });
|
||||
|
||||
this._removeFromCache(file);
|
||||
this._removeFromCache(newFile);
|
||||
|
||||
await this.props.filesystem.rename(oldPath, newPath);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
async _onAddFiles() {
|
||||
const files = await selectFiles();
|
||||
await this._uploadFiles(files);
|
||||
}
|
||||
|
||||
_validateName(name: string) {
|
||||
if (name.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.includes("/") || name.includes("\\")) {
|
||||
this._showError("Folder must not contain special characters");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.state.path.length == 0 && name.length > 1) {
|
||||
this._showError("Drives must be a single letter");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async _onFolderCreate(name: string) {
|
||||
name = name.toLowerCase();
|
||||
|
||||
if (!this._validateName(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ createFolder: false });
|
||||
|
||||
const fullPath = makeFullPathWithState(this.state, name);
|
||||
await this.props.filesystem.createFolder(fullPath);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
async _uploadFiles(files: FileList | File[]) {
|
||||
if (files.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.path.length == 0) {
|
||||
this._showError("Files must be within a drive");
|
||||
return;
|
||||
}
|
||||
|
||||
const fileData = (await readFiles(files)).map((f) => {
|
||||
const name = getFileName(f.file);
|
||||
return {
|
||||
name: makeFullPathWithState(this.state, name.toLowerCase()),
|
||||
data: f.data,
|
||||
};
|
||||
});
|
||||
|
||||
fileData.forEach((d) => {
|
||||
this._removeFromCache(d.name);
|
||||
});
|
||||
|
||||
await this.props.filesystem.storeFiles(fileData);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
_renderCreateFolderDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.createFolder}
|
||||
onOpenChange={(open) => this.setState({ createFolder: open })}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
const folderName = (e.target as any).elements.name.value;
|
||||
this._onFolderCreate(folderName);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create new folder</DialogTitle>
|
||||
<DialogDescription className="hidden">
|
||||
Create new folder
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<Input id="name" defaultValue="New Folder" />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" className="fancy rounded-lg">
|
||||
Create
|
||||
</Button>
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary" className="fancy rounded-lg">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_renderRenameDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.renameFile.length > 0}
|
||||
onOpenChange={(open) => (open ? {} : this.setState({ renameFile: "" }))}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
const newName = (e.target as any).elements.name.value;
|
||||
this._onFileRename(this.state.renameFile, newName);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Rename {trimFilename(this.state.renameFile)}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="hidden">
|
||||
Rename {this.state.renameFile}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<Input id="name" defaultValue={this.state.renameFile} />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" className="fancy rounded-lg">
|
||||
Rename
|
||||
</Button>
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary" className="fancy rounded-lg">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_renderErrorDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.errorText.length > 0}
|
||||
onOpenChange={(open) => (open ? {} : this.setState({ errorText: "" }))}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Error</DialogTitle>
|
||||
<DialogDescription className="hidden">
|
||||
Error: {this.state.errorText}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">{this.state.errorText}</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="fancy rounded-lg"
|
||||
onClick={() => this.setState({ errorText: "" })}
|
||||
>
|
||||
Ok
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_renderRemoveDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.removeFile.length > 0}
|
||||
onOpenChange={(open) => (open ? {} : this.setState({ removeFile: "" }))}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Delete {trimFilename(this.state.removeFile)}?
|
||||
</DialogTitle>
|
||||
<DialogDescription className="hidden">
|
||||
Delete {this.state.removeFile}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
Are you sure you want to delete{" "}
|
||||
<b className="break-all">
|
||||
{makeWindowsPathWithState(this.state, this.state.removeFile)}
|
||||
</b>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="fancy rounded-lg"
|
||||
onClick={() => {
|
||||
const file = makeFullPathWithState(
|
||||
this.state,
|
||||
this.state.removeFile,
|
||||
);
|
||||
this.setState({ removeFile: "" });
|
||||
this._removeFromCache(file);
|
||||
this.props.filesystem
|
||||
.unlink(file)
|
||||
.then(() => this.forceUpdate());
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="fancy rounded-lg"
|
||||
onClick={() => {
|
||||
this.setState({ removeFile: "" });
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_renderResetDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.resetFilesys}
|
||||
onOpenChange={(open) => this.setState({ resetFilesys: open })}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Reset filesystem</DialogTitle>
|
||||
<DialogDescription className="hidden">
|
||||
Reset filesystem
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
Are you sure you want to reset the filesystem?
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="fancy rounded-lg"
|
||||
onClick={() => {
|
||||
this.setState({ resetFilesys: false });
|
||||
this.props.iconCache.clear();
|
||||
this.props.resetFilesys();
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="fancy rounded-lg"
|
||||
onClick={() => {
|
||||
this.setState({ resetFilesys: false });
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_renderBreadcrumbElements() {
|
||||
const elements = generateBreadcrumbElements(this.state.path);
|
||||
|
||||
return elements.map((e, index) => {
|
||||
if (index == this.state.path.length) {
|
||||
return (
|
||||
<BreadcrumbItem key={`breadcrumb-item-${index}`}>
|
||||
<BreadcrumbPage key={`breadcrumb-page-${index}`}>
|
||||
{e.node}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
|
||||
const navigate = () => this.setState({ path: e.targetPath });
|
||||
return [
|
||||
<BreadcrumbItem key={`breadcrumb-item-${index}`}>
|
||||
<BreadcrumbLink key={`breadcrumb-link-${index}`} onClick={navigate}>
|
||||
{e.node}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>,
|
||||
<BreadcrumbSeparator key={`breadcrumb-separator-${index}`} />,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
_renderBreadCrumb() {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>{this._renderBreadcrumbElements()}</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
);
|
||||
}
|
||||
|
||||
_removeFromCache(file: string) {
|
||||
this.props.iconCache.delete(file);
|
||||
}
|
||||
|
||||
_downloadFile(file: string) {
|
||||
const fullPath = makeFullPathWithState(this.state, file);
|
||||
const data = this.props.filesystem.readFile(fullPath);
|
||||
downloadData(data, file);
|
||||
}
|
||||
|
||||
render() {
|
||||
const elements = getFolderElements(this.props.filesystem, this.state.path);
|
||||
|
||||
return (
|
||||
<>
|
||||
{this._renderCreateFolderDialog()}
|
||||
{this._renderRenameDialog()}
|
||||
{this._renderErrorDialog()}
|
||||
{this._renderRemoveDialog()}
|
||||
{this._renderResetDialog()}
|
||||
|
||||
<div className="flex flex-row w-full items-center gap-3">
|
||||
<div className="whitespace-nowrap">{this._renderBreadCrumb()}</div>
|
||||
<div className="flex-1 text-right">
|
||||
<Button
|
||||
onClick={() => this.setState({ resetFilesys: true })}
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="fancy rounded-lg"
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dropzone onDrop={this._uploadFiles} noClick={true}>
|
||||
{({ getRootProps, getInputProps }) => (
|
||||
<div {...getRootProps()}>
|
||||
<input {...getInputProps()} />
|
||||
<Folder
|
||||
elements={elements}
|
||||
clickHandler={this._onElementSelect}
|
||||
createFolderHandler={() =>
|
||||
this.setState({ createFolder: true })
|
||||
}
|
||||
removeElementHandler={(e) =>
|
||||
this.setState({ removeFile: e.name })
|
||||
}
|
||||
renameElementHandler={(e) =>
|
||||
this.setState({ renameFile: e.name })
|
||||
}
|
||||
downloadElementHandler={(e) => this._downloadFile(e.name)}
|
||||
addFilesHandler={this._onAddFiles}
|
||||
iconReader={(e) =>
|
||||
getPeIcon(
|
||||
this.props.filesystem,
|
||||
makeFullPathWithState(this.state, e.name),
|
||||
this.props.iconCache,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,217 +1,97 @@
|
||||
import { downloadBinaryFilePercent, DownloadPercentHandler } from "./download";
|
||||
import { parseZipFile, ProgressHandler } from "./zip-file";
|
||||
import idbfsModule, { MainModule } from "@irori/idbfs";
|
||||
import { parseZipFile, FileEntry, ProgressHandler } from "./zip-file";
|
||||
|
||||
function fetchFilesystemZip(progressCallback: DownloadPercentHandler) {
|
||||
return downloadBinaryFilePercent("./root.zip", progressCallback);
|
||||
function openDatabase(): Promise<IDBDatabase> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open("cacheDB", 1);
|
||||
|
||||
request.onerror = (event: Event) => {
|
||||
reject(event);
|
||||
};
|
||||
|
||||
request.onsuccess = (event: Event) => {
|
||||
resolve((event as any).target.result as IDBDatabase);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event: Event) => {
|
||||
const db = (event as any).target.result as IDBDatabase;
|
||||
if (!db.objectStoreNames.contains("cacheStore")) {
|
||||
db.createObjectStore("cacheStore", { keyPath: "id" });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchFilesystem(
|
||||
progressHandler: ProgressHandler,
|
||||
downloadProgressHandler: DownloadPercentHandler,
|
||||
async function saveData(id: string, data: any) {
|
||||
const db = await openDatabase();
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction(["cacheStore"], "readwrite");
|
||||
const objectStore = transaction.objectStore("cacheStore");
|
||||
const request = objectStore.put({ id: id, data: data });
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve("Data saved successfully");
|
||||
};
|
||||
|
||||
request.onerror = (event) => {
|
||||
reject("Save error: " + (event as any).target.errorCode);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function getData(id: string) {
|
||||
const db = await openDatabase();
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction(["cacheStore"], "readonly");
|
||||
const objectStore = transaction.objectStore("cacheStore");
|
||||
const request = objectStore.get(id);
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
if ((event as any).target.result) {
|
||||
resolve((event as any).target.result.data);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = (event) => {
|
||||
reject("Retrieve error: " + (event as any).target.errorCode);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function cacheAndUseData(
|
||||
id: string,
|
||||
asyncFunction: () => Promise<FileEntry[]>,
|
||||
) {
|
||||
const filesys = await fetchFilesystemZip(downloadProgressHandler);
|
||||
try {
|
||||
let data = (await getData(id)) as FileEntry[];
|
||||
if (!data) {
|
||||
data = await asyncFunction();
|
||||
await saveData(id, data);
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function fetchFilesystemZip() {
|
||||
return fetch("./root.zip?1", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
},
|
||||
}).then((r) => r.arrayBuffer());
|
||||
}
|
||||
|
||||
async function fetchFilesystem(progressHandler: ProgressHandler) {
|
||||
const filesys = await fetchFilesystemZip();
|
||||
return await parseZipFile(filesys, progressHandler);
|
||||
}
|
||||
|
||||
function synchronizeIDBFS(idbfs: MainModule, populate: boolean) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
idbfs.FS.syncfs(populate, function (err: any) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const filesystemPrefix = "/root/filesys/";
|
||||
|
||||
export function internalToWindowsPath(internalPath: string): string {
|
||||
if (
|
||||
!internalPath.startsWith(filesystemPrefix) ||
|
||||
internalPath.length <= filesystemPrefix.length
|
||||
) {
|
||||
throw new Error("Invalid path");
|
||||
}
|
||||
|
||||
const winPath = internalPath.substring(filesystemPrefix.length);
|
||||
return `${winPath[0]}:${winPath.substring(1)}`;
|
||||
}
|
||||
|
||||
export function windowsToInternalPath(windowsPath: string): string {
|
||||
if (windowsPath.length < 2 || windowsPath[1] != ":") {
|
||||
throw new Error("Invalid path");
|
||||
}
|
||||
|
||||
return `${filesystemPrefix}${windowsPath[0]}${windowsPath.substring(2)}`;
|
||||
}
|
||||
|
||||
async function initializeIDBFS() {
|
||||
const idbfs = await idbfsModule();
|
||||
|
||||
idbfs.FS.mkdir("/root");
|
||||
idbfs.FS.mount(idbfs.IDBFS, {}, "/root");
|
||||
|
||||
await synchronizeIDBFS(idbfs, true);
|
||||
|
||||
return idbfs;
|
||||
}
|
||||
|
||||
export interface FileWithData {
|
||||
name: string;
|
||||
data: ArrayBuffer;
|
||||
}
|
||||
|
||||
function deleteDatabase(dbName: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const request = indexedDB.deleteDatabase(dbName);
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`Error deleting database ${dbName}.`));
|
||||
};
|
||||
|
||||
request.onblocked = () => {
|
||||
reject(new Error(`Deletion of database ${dbName} blocked.`));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function filterPseudoDir(e: string) {
|
||||
return e != "." && e != "..";
|
||||
}
|
||||
|
||||
export class Filesystem {
|
||||
private idbfs: MainModule;
|
||||
|
||||
constructor(idbfs: MainModule) {
|
||||
this.idbfs = idbfs;
|
||||
}
|
||||
|
||||
_storeFile(file: FileWithData) {
|
||||
if (file.name.includes("/")) {
|
||||
const folder = file.name.split("/").slice(0, -1).join("/");
|
||||
this._createFolder(folder);
|
||||
}
|
||||
|
||||
const buffer = new Uint8Array(file.data);
|
||||
this.idbfs.FS.writeFile(file.name, buffer);
|
||||
}
|
||||
|
||||
readFile(file: string): Uint8Array {
|
||||
return this.idbfs.FS.readFile(file);
|
||||
}
|
||||
|
||||
async storeFiles(files: FileWithData[]) {
|
||||
files.forEach((f) => {
|
||||
this._storeFile(f);
|
||||
});
|
||||
|
||||
await this.sync();
|
||||
}
|
||||
|
||||
_unlinkRecursive(element: string) {
|
||||
if (!this.isFolder(element)) {
|
||||
this.idbfs.FS.unlink(element);
|
||||
return;
|
||||
}
|
||||
|
||||
this.readDir(element) //
|
||||
.filter(filterPseudoDir)
|
||||
.forEach((e) => {
|
||||
this._unlinkRecursive(`${element}/${e}`);
|
||||
});
|
||||
|
||||
this.idbfs.FS.rmdir(element);
|
||||
}
|
||||
|
||||
async rename(oldFile: string, newFile: string) {
|
||||
this.idbfs.FS.rename(oldFile, newFile);
|
||||
await this.sync();
|
||||
}
|
||||
|
||||
async unlink(file: string) {
|
||||
this._unlinkRecursive(file);
|
||||
await this.sync();
|
||||
}
|
||||
|
||||
_createFolder(folder: string) {
|
||||
this.idbfs.FS.mkdirTree(folder, 0o777);
|
||||
}
|
||||
|
||||
async createFolder(folder: string) {
|
||||
this._createFolder(folder);
|
||||
await this.sync();
|
||||
}
|
||||
|
||||
async sync() {
|
||||
await synchronizeIDBFS(this.idbfs, false);
|
||||
}
|
||||
|
||||
readDir(dir: string): string[] {
|
||||
return this.idbfs.FS.readdir(dir);
|
||||
}
|
||||
|
||||
stat(file: string) {
|
||||
return this.idbfs.FS.stat(file, false);
|
||||
}
|
||||
|
||||
isFolder(file: string) {
|
||||
return (this.stat(file).mode & 0x4000) != 0;
|
||||
}
|
||||
|
||||
async delete() {
|
||||
this.readDir("/root") //
|
||||
.filter(filterPseudoDir) //
|
||||
.forEach((e) => {
|
||||
try {
|
||||
this._unlinkRecursive(e);
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
await this.sync();
|
||||
|
||||
try {
|
||||
await deleteDatabase("/root");
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupFilesystem(
|
||||
progressHandler: ProgressHandler,
|
||||
downloadProgressHandler: DownloadPercentHandler,
|
||||
) {
|
||||
const idbfs = await initializeIDBFS();
|
||||
const fs = new Filesystem(idbfs);
|
||||
|
||||
if (idbfs.FS.analyzePath("/root/api-set.bin", false).exists) {
|
||||
return fs;
|
||||
}
|
||||
|
||||
const filesystem = await fetchFilesystem(
|
||||
progressHandler,
|
||||
downloadProgressHandler,
|
||||
export function getFilesystem(progressHandler: ProgressHandler) {
|
||||
return cacheAndUseData("emulator-filesystem-2", () =>
|
||||
fetchFilesystem(progressHandler),
|
||||
);
|
||||
|
||||
filesystem.forEach((e) => {
|
||||
if (idbfs.FS.analyzePath("/" + e.name, false).exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.name.endsWith("/")) {
|
||||
idbfs.FS.mkdir("/" + e.name.slice(0, -1));
|
||||
} else {
|
||||
const buffer = new Uint8Array(e.data);
|
||||
idbfs.FS.writeFile("/" + e.name, buffer);
|
||||
}
|
||||
});
|
||||
|
||||
await fs.sync();
|
||||
|
||||
return fs;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
--card-foreground: oklch(0.141 0.005 285.823);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||
--primary: oklch(61.686% 0.20434 256.402);
|
||||
--primary: oklch(0.64 0.192 253.32);
|
||||
--primary-foreground: oklch(0.97 0.014 254.604);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
@@ -84,18 +84,18 @@
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.21 0.006 285.885);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(66.906% 0.18376 248.826);
|
||||
--primary-foreground: oklch(29.313% 0.00003 271.152);
|
||||
--primary: oklch(0.64 0.192 253.32);
|
||||
--primary-foreground: oklch(0.97 0.014 254.604);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.274 0.006 286.033);
|
||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
||||
--accent: oklch(0.274 0.006 286.033);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.69 0.1953 33.18);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(57.771% 0.18952 256.306);
|
||||
--ring: oklch(0.488 0.243 264.376);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
@@ -115,7 +115,6 @@
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Shield,
|
||||
Cpu,
|
||||
Terminal,
|
||||
ExternalLink,
|
||||
Github,
|
||||
Play,
|
||||
ArrowRight,
|
||||
BookOpen,
|
||||
Download,
|
||||
Lock,
|
||||
Bug,
|
||||
Split,
|
||||
Layers,
|
||||
} from "lucide-react";
|
||||
import { Header } from "./Header";
|
||||
import { YoutubeVideo } from "@/components/youtube-video";
|
||||
|
||||
function generateButtons(additionalClasses: string = "") {
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col sm:flex-row gap-4 justify-center items-stretch sm:items-center px-4 min-[340px]:px-16 ${additionalClasses}`}
|
||||
>
|
||||
<a href="#/playground">
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="rounded-lg bg-linear-to-br from-white to-neutral-300 text-neutral-900 border-0 px-8 py-6 text-lg font-semibold group transition-all duration-100 w-full flex"
|
||||
>
|
||||
<span>
|
||||
<Play className="mr-2 h-5 w-5 transition-transform" />
|
||||
<span className="flex-1">Try Online</span>
|
||||
<ArrowRight className="ml-2 h-5 w-5 group-hover:translate-x-1 transition-transform" />
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
<a href="https://github.com/momo5502/sogen" target="_blank">
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="rounded-lg border-neutral-600 text-neutral-300 hover:bg-neutral-800/50 px-8 py-6 text-lg font-semibold group transition-all duration-300 w-full flex"
|
||||
>
|
||||
<span>
|
||||
<Github className="mr-2 h-5 w-5 group-hover:scale-110 transition-transform" />
|
||||
<span className="flex-1">Get Source</span>
|
||||
<ExternalLink className="ml-2 h-4 w-4" />
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LandingPage() {
|
||||
const features = [
|
||||
{
|
||||
icon: <Cpu className="h-6 w-6" />,
|
||||
title: "Syscall Emulation",
|
||||
description:
|
||||
"Operates at syscall level, leveraging existing system DLLs instead of reimplementing Windows APIs",
|
||||
accent: "from-[#f76548] to-[#b00101]",
|
||||
},
|
||||
{
|
||||
icon: <Split className="h-6 w-6" />,
|
||||
title: "Hooking Capabilities",
|
||||
description:
|
||||
"Provides powerful hooking interfaces to intercept memory access, code execution and much more",
|
||||
accent: "from-[#ffcb00] to-[#da6000]",
|
||||
},
|
||||
{
|
||||
icon: <Terminal className="h-6 w-6" />,
|
||||
title: "Debugging Interface",
|
||||
description:
|
||||
"Implements GDB serial protocol for integration with common debugging tools",
|
||||
accent: "from-[#00c4e9] to-[#005ff6]",
|
||||
},
|
||||
{
|
||||
icon: <Layers className="h-6 w-6" />,
|
||||
title: "State Management",
|
||||
description:
|
||||
"Saves and restores the entire state of the emulator to quickly resume your work exactly where you left off.",
|
||||
accent: "from-[#aee703] to-[#647502]",
|
||||
},
|
||||
];
|
||||
|
||||
const useCases = [
|
||||
{
|
||||
icon: <Shield className="h-6 w-6" />,
|
||||
title: "Security Research",
|
||||
description:
|
||||
"Analyze malware and security vulnerabilities in a controlled environment",
|
||||
},
|
||||
{
|
||||
icon: <Lock className="h-6 w-6" />,
|
||||
title: "DRM Research",
|
||||
description:
|
||||
"Study digital rights management systems and protection mechanisms",
|
||||
},
|
||||
{
|
||||
icon: <Bug className="h-6 w-6" />,
|
||||
title: "Malware Analysis",
|
||||
description:
|
||||
"Reverse engineer malicious software with full process control",
|
||||
},
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ value: "100%", label: "Open Source" },
|
||||
{ value: "14", label: "Platforms" },
|
||||
{ value: "2", label: "Backends" },
|
||||
{ value: "100%", label: "Deterministic" },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
title="Sogen"
|
||||
description="A high-performance Windows user space emulator."
|
||||
/>
|
||||
<div className="flex flex-col min-h-screen bg-linear-to-br from-zinc-900 via-neutral-900 to-black overflow-x-hidden">
|
||||
{/* Hero Section with Animated Background */}
|
||||
<section className="relative overflow-visible">
|
||||
{/* Animated Background Elements */}
|
||||
<div className="absolute inset-0 container mx-auto">
|
||||
<div className="absolute top-20 left-10 w-72 h-72 bg-yellow-500/15 rounded-full blur-3xl"></div>
|
||||
<div className="absolute top-40 right-20 w-96 h-96 bg-lime-500/15 rounded-full blur-3xl"></div>
|
||||
<div className="absolute bottom-20 left-1/3 w-80 h-80 bg-cyan-500/15 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<div className="relative container mx-auto min-h-dvh p-1 min-[340px]:p-4 flex items-center xl:min-h-0 xl:px-6 xl:py-32">
|
||||
<div className="text-center space-y-8 max-w-4xl mx-auto">
|
||||
{/* Main Headline */}
|
||||
<h1 className="text-5xl md:text-7xl font-bold text-white leading-tight">
|
||||
Sogen
|
||||
</h1>
|
||||
|
||||
<p className="text-xl md:text-2xl text-neutral-300 font-light leading-relaxed">
|
||||
A high-performance Windows user space emulator.
|
||||
</p>
|
||||
|
||||
{
|
||||
/* CTA Buttons */
|
||||
generateButtons("pt-8")
|
||||
}
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex justify-center flex-col min-[400px]:flex-row gap-6 sm:gap-8 pt-12">
|
||||
{stats.map((stat, index) => (
|
||||
<div key={index} className="text-center">
|
||||
<div className="text-2xl font-bold text-white">
|
||||
{stat.value}
|
||||
</div>
|
||||
<div className="text-sm text-neutral-400">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Section with Hover Effects */}
|
||||
<section className="py-24 relative">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white mb-6">
|
||||
Powerful Features
|
||||
</h2>
|
||||
<p className="text-xl text-neutral-400 max-w-2xl mx-auto">
|
||||
Built from the ground up for performance and accuracy.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-8 lg:m-32">
|
||||
{features.map((feature, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className="bg-neutral-800/50 border-neutral-700 hover:border-neutral-600 hover:bg-neutral-800/80 cursor-default transition-all duration-150 group hover:shadow-2xl"
|
||||
>
|
||||
<CardHeader>
|
||||
<div
|
||||
className={`w-12 h-12 rounded-[0.625rem] bg-linear-to-br ${feature.accent} p-3 mb-4`}
|
||||
>
|
||||
<div className="text-neutral-900">{feature.icon}</div>
|
||||
</div>
|
||||
<CardTitle className="text-white text-xl font-semibold transition-colors">
|
||||
{feature.title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-neutral-300 leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases */}
|
||||
<section className="py-24 bg-neutral-800/40">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-white mb-6">
|
||||
Perfect For Your Research
|
||||
</h2>
|
||||
<p className="text-xl text-neutral-400">
|
||||
Designed for researchers who need precise control over Windows
|
||||
process execution.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-4xl mx-auto">
|
||||
{useCases.map((useCase, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="text-center p-8 rounded-2xl bg-neutral-800/50 border border-neutral-700 hover:border-neutral-600 hover:bg-neutral-800/80 cursor-default transition-all duration-150 group"
|
||||
>
|
||||
<div className="w-12 h-12 mx-auto mb-4 rounded-[0.625rem] bg-linear-to-br from-cyan-500 to-blue-500 p-3">
|
||||
<div className="text-neutral-800">{useCase.icon}</div>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-3">
|
||||
{useCase.title}
|
||||
</h3>
|
||||
<p className="text-neutral-400">{useCase.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Video Section with Modern Design */}
|
||||
<section className="py-24">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-white mb-6">
|
||||
See Sogen in Action
|
||||
</h2>
|
||||
<p className="text-xl text-neutral-400 max-w-3xl mx-auto">
|
||||
Watch a comprehensive overview of the emulator's capabilities
|
||||
and discover how it can accelerate your research workflow.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto w-full gap-12 flex items-center justify-center flex-col lg:flex-row">
|
||||
{["wY9Q0DhodOQ", "RkodCUEmiuA"].map((id) => {
|
||||
return (
|
||||
<div
|
||||
key={`video-${id}`}
|
||||
className="flex-1 w-full max-w-xl relative group"
|
||||
>
|
||||
<div className="absolute -inset-4 bg-linear-to-r from-neutral-500/15 to-neutral-500/15 rounded-3xl blur-md group-hover:blur-lg transition-all duration-300"></div>
|
||||
<div className="relative aspect-video rounded-2xl overflow-hidden ">
|
||||
<YoutubeVideo id={id} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-24 bg-linear-to-r from-neutral-800/40 to-neutral-900">
|
||||
<div className="container mx-auto px-6 text-center">
|
||||
<h2 className="text-4xl font-bold text-white mb-6">
|
||||
Ready to Start Emulating?
|
||||
</h2>
|
||||
<p className="text-xl text-neutral-300 mb-8 max-w-2xl mx-auto">
|
||||
Try Sogen directly in your browser or explore the source code.
|
||||
</p>
|
||||
{generateButtons()}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-16 border-t border-neutral-800">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center">
|
||||
<div className="mb-8 md:mb-0 text-center md:text-left">
|
||||
<h2 className="text-3xl font-bold">Sogen</h2>
|
||||
<p className="mt-1 text-neutral-500 text-sm">
|
||||
Built by{" "}
|
||||
<a
|
||||
href="https://momo5502.com"
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
momo5502
|
||||
</a>{" "}
|
||||
with lots of help from{" "}
|
||||
<a
|
||||
href="https://github.com/momo5502/sogen/graphs/contributors"
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
the community
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-6">
|
||||
<a
|
||||
href="https://github.com/momo5502/sogen"
|
||||
target="_blank"
|
||||
title="Soure Code"
|
||||
className="text-neutral-400 hover:text-blue-400 transition-colors p-2 rounded-lg hover:bg-neutral-800/50"
|
||||
>
|
||||
<Github className="h-6 w-6" />
|
||||
</a>
|
||||
<a
|
||||
href="#/playground"
|
||||
title="Playground"
|
||||
className="text-neutral-400 hover:text-blue-400 transition-colors p-2 rounded-lg hover:bg-neutral-800/50"
|
||||
>
|
||||
<Play className="h-6 w-6" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/momo5502/sogen/wiki"
|
||||
target="_blank"
|
||||
title="Wiki"
|
||||
className="text-neutral-400 hover:text-blue-400 transition-colors p-2 rounded-lg hover:bg-neutral-800/50"
|
||||
>
|
||||
<BookOpen className="h-6 w-6" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,23 +2,6 @@ import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App.tsx";
|
||||
import { registerSW } from "virtual:pwa-register";
|
||||
import Loader from "./Loader";
|
||||
|
||||
registerSW({
|
||||
onNeedRefresh() {
|
||||
Loader.setLoading(false);
|
||||
window.location.reload();
|
||||
},
|
||||
onOfflineReady() {
|
||||
Loader.setLoading(false);
|
||||
},
|
||||
onRegisteredSW(_, registration) {
|
||||
registration?.addEventListener("updatefound", () => {
|
||||
Loader.setLoading(true);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
import * as PE from "pe-library";
|
||||
|
||||
function patchExeFile(exe: PE.NtExecutable) {
|
||||
// The PE library doesn't support parsing resources if other sections follow
|
||||
// This might make sense, as the library will have issues rewriting the PE file.
|
||||
// As we only care about parsing though, just kill the other sections.
|
||||
const rsrc = exe.getSectionByEntry(PE.Format.ImageDirectoryEntry.Resource);
|
||||
const orig = exe.getAllSections.bind(exe);
|
||||
exe.getAllSections = function () {
|
||||
let x = { skip: false };
|
||||
return orig().filter((s) => {
|
||||
if (x.skip) {
|
||||
return false;
|
||||
}
|
||||
if (s == rsrc) {
|
||||
x.skip = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function arrayBufferToBase64(bytes: Uint8Array) {
|
||||
let binary = "";
|
||||
const len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
function isPng(buffer: Uint8Array) {
|
||||
if (buffer.length < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71;
|
||||
}
|
||||
|
||||
function generateDataURL(arrayBuffer: Uint8Array, contentType: string) {
|
||||
const base64 = arrayBufferToBase64(arrayBuffer);
|
||||
return `data:${contentType};base64,${base64}`;
|
||||
}
|
||||
|
||||
interface IconEntry {
|
||||
width: number;
|
||||
height: number;
|
||||
colorCount: number;
|
||||
reserved: number;
|
||||
planes: number;
|
||||
bitCount: number;
|
||||
bytesInRes: number;
|
||||
id: number;
|
||||
}
|
||||
|
||||
interface IconGroup {
|
||||
reserved: number;
|
||||
type: number;
|
||||
icons: IconEntry[];
|
||||
}
|
||||
|
||||
function writeUint8(buffer: Uint8Array, offset: number, value: number) {
|
||||
buffer[offset] = value;
|
||||
}
|
||||
|
||||
function writeUint16(buffer: Uint8Array, offset: number, value: number) {
|
||||
writeUint8(buffer, offset + 0, value & 0xff);
|
||||
writeUint8(buffer, offset + 1, (value >> 8) & 0xff);
|
||||
}
|
||||
|
||||
function writeUint32(buffer: Uint8Array, offset: number, value: number) {
|
||||
writeUint16(buffer, offset + 0, value & 0xffff);
|
||||
writeUint16(buffer, offset + 2, (value >> 16) & 0xffff);
|
||||
}
|
||||
|
||||
function readUInt8(buffer: Uint8Array, offset: number) {
|
||||
return buffer[offset];
|
||||
}
|
||||
|
||||
function readUInt16(buffer: Uint8Array, offset: number) {
|
||||
return readUInt8(buffer, offset) | (readUInt8(buffer, offset + 1) << 8);
|
||||
}
|
||||
|
||||
function readUInt32(buffer: Uint8Array, offset: number) {
|
||||
return readUInt16(buffer, offset) | (readUInt16(buffer, offset + 2) << 16);
|
||||
}
|
||||
|
||||
function parseIconGroup(buffer: Uint8Array): IconGroup {
|
||||
const reserved = readUInt16(buffer, 0);
|
||||
const type = readUInt16(buffer, 2);
|
||||
const count = readUInt16(buffer, 4);
|
||||
|
||||
const icons: IconEntry[] = [];
|
||||
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const start = 6 + i * 14;
|
||||
const width = readUInt8(buffer, start + 0);
|
||||
const height = readUInt8(buffer, start + 1);
|
||||
const colorCount = readUInt8(buffer, start + 2);
|
||||
const reserved2 = readUInt8(buffer, start + 3);
|
||||
const planes = readUInt16(buffer, start + 4);
|
||||
const bitCount = readUInt16(buffer, start + 6);
|
||||
const bytesInRes = readUInt32(buffer, start + 8);
|
||||
const id = readUInt16(buffer, start + 12);
|
||||
|
||||
icons.push({
|
||||
width,
|
||||
height,
|
||||
colorCount,
|
||||
reserved: reserved2,
|
||||
planes,
|
||||
bitCount,
|
||||
bytesInRes,
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
reserved,
|
||||
type,
|
||||
icons,
|
||||
};
|
||||
}
|
||||
|
||||
function mergeArrayBuffers(
|
||||
buffer1: ArrayBuffer,
|
||||
buffer2: ArrayBuffer,
|
||||
): ArrayBuffer {
|
||||
const mergedBuffer = new ArrayBuffer(buffer1.byteLength + buffer2.byteLength);
|
||||
|
||||
const view1 = new Uint8Array(buffer1);
|
||||
const view2 = new Uint8Array(buffer2);
|
||||
const mergedView = new Uint8Array(mergedBuffer);
|
||||
|
||||
mergedView.set(view1, 0);
|
||||
mergedView.set(view2, buffer1.byteLength);
|
||||
|
||||
return mergedBuffer;
|
||||
}
|
||||
|
||||
function generateIcoHeader(icon: IconEntry) {
|
||||
const headerSize = 0x16;
|
||||
const header = new Uint8Array(headerSize);
|
||||
writeUint8(header, 2, 1); // Image type -> ico
|
||||
writeUint8(header, 4, 1); // Image count
|
||||
|
||||
const start = 6;
|
||||
|
||||
writeUint8(header, start + 0, icon.width);
|
||||
writeUint8(header, start + 1, icon.height);
|
||||
writeUint8(header, start + 2, icon.colorCount);
|
||||
|
||||
writeUint16(header, start + 4, icon.planes);
|
||||
writeUint16(header, start + 6, icon.bitCount);
|
||||
writeUint32(header, start + 8, icon.bytesInRes);
|
||||
writeUint32(header, start + 12, headerSize);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
function isMaxResIcon(icon: IconEntry) {
|
||||
return icon.width == 0 && icon.height == 0;
|
||||
}
|
||||
|
||||
function getBiggestIcon(group: IconGroup) {
|
||||
if (group.icons.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var biggest = group.icons[0];
|
||||
if (isMaxResIcon(biggest)) {
|
||||
return biggest;
|
||||
}
|
||||
|
||||
for (let i = 1; i < group.icons.length; ++i) {
|
||||
let current = group.icons[i];
|
||||
if (isMaxResIcon(current)) {
|
||||
return current;
|
||||
}
|
||||
|
||||
if (current.width * current.height > biggest.width * biggest.height) {
|
||||
biggest = current;
|
||||
}
|
||||
}
|
||||
|
||||
return biggest;
|
||||
}
|
||||
|
||||
function getPeResources(data: Uint8Array) {
|
||||
const exe = PE.NtExecutable.from(data, { ignoreCert: true });
|
||||
patchExeFile(exe);
|
||||
return PE.NtExecutableResource.from(exe, true);
|
||||
}
|
||||
|
||||
function getIconDataUrl(iconEntry: IconEntry, iconData: ArrayBuffer) {
|
||||
let contentType = "image/png";
|
||||
|
||||
if (!isPng(new Uint8Array(iconData))) {
|
||||
contentType = "image/x-icon";
|
||||
|
||||
const header = generateIcoHeader(iconEntry);
|
||||
iconData = mergeArrayBuffers(header.slice().buffer, iconData);
|
||||
}
|
||||
|
||||
return generateDataURL(new Uint8Array(iconData), contentType);
|
||||
}
|
||||
|
||||
function tryParsePeIcon(data: Uint8Array) {
|
||||
const res = getPeResources(data);
|
||||
const icons = res.entries.filter((e) => e.type == 3);
|
||||
const iconGroups = res.entries.filter((e) => e.type == 14);
|
||||
|
||||
if (iconGroups.length == 0 || icons.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const groupData = new Uint8Array(iconGroups[0].bin);
|
||||
const group = parseIconGroup(groupData);
|
||||
const iconEntry = getBiggestIcon(group);
|
||||
|
||||
if (!iconEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const icon = icons.find((i) => i.id == iconEntry.id);
|
||||
if (!icon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getIconDataUrl(iconEntry, icon.bin);
|
||||
}
|
||||
|
||||
export function parsePeIcon(data: Uint8Array) {
|
||||
try {
|
||||
return tryParsePeIcon(data);
|
||||
} catch (e) {
|
||||
// console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,458 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { Output } from "@/components/output";
|
||||
|
||||
import { Emulator, EmulationState, isFinalState } from "./emulator";
|
||||
import {
|
||||
Filesystem,
|
||||
setupFilesystem,
|
||||
windowsToInternalPath,
|
||||
} from "./filesystem";
|
||||
|
||||
import { memory64 } from "wasm-feature-detect";
|
||||
|
||||
import "./App.css";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
|
||||
import { Settings, loadSettings, saveSettings } from "./settings";
|
||||
import { SettingsMenu } from "@/components/settings-menu";
|
||||
|
||||
import {
|
||||
PlayFill,
|
||||
StopFill,
|
||||
GearFill,
|
||||
PauseFill,
|
||||
HouseFill,
|
||||
} from "react-bootstrap-icons";
|
||||
import { StatusIndicator } from "@/components/status-indicator";
|
||||
import { Header } from "./Header";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
} from "@/components/ui/drawer";
|
||||
import { FilesystemExplorer } from "./filesystem-explorer";
|
||||
import { EmulationStatus } from "./emulator";
|
||||
import { EmulationSummary } from "./components/emulation-summary";
|
||||
import { downloadBinaryFilePercent } from "./download";
|
||||
|
||||
export interface PlaygroundFile {
|
||||
file: string;
|
||||
storage: string;
|
||||
}
|
||||
|
||||
export interface PlaygroundProps {}
|
||||
|
||||
export interface PlaygroundState {
|
||||
settings: Settings;
|
||||
filesystemPromise?: Promise<Filesystem>;
|
||||
filesystem?: Filesystem;
|
||||
emulator?: Emulator;
|
||||
emulationStatus?: EmulationStatus;
|
||||
application?: string;
|
||||
drawerOpen: boolean;
|
||||
allowWasm64: boolean;
|
||||
file?: PlaygroundFile;
|
||||
}
|
||||
|
||||
function decodeFileData(data: string | null): PlaygroundFile | undefined {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const jsonData = JSON.parse(atob(data));
|
||||
|
||||
return {
|
||||
file: jsonData.file,
|
||||
storage: jsonData.storage,
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
interface GlobalThisExt {
|
||||
emulateCache?: string | null;
|
||||
}
|
||||
|
||||
function getGlobalThis() {
|
||||
return globalThis as GlobalThisExt;
|
||||
}
|
||||
|
||||
export function storeEmulateData(data?: string) {
|
||||
getGlobalThis().emulateCache = undefined;
|
||||
|
||||
if (data) {
|
||||
localStorage.setItem("emulate", data);
|
||||
} else {
|
||||
localStorage.removeItem("emulate");
|
||||
}
|
||||
}
|
||||
|
||||
function getEmulateData() {
|
||||
const gt = getGlobalThis();
|
||||
if (gt.emulateCache) {
|
||||
return gt.emulateCache;
|
||||
}
|
||||
|
||||
const emulateData = localStorage.getItem("emulate");
|
||||
localStorage.removeItem("emulate");
|
||||
|
||||
gt.emulateCache = emulateData;
|
||||
return emulateData;
|
||||
}
|
||||
|
||||
export class Playground extends React.Component<
|
||||
PlaygroundProps,
|
||||
PlaygroundState
|
||||
> {
|
||||
private output: React.RefObject<Output | null>;
|
||||
private iconCache: Map<string, string | null> = new Map();
|
||||
|
||||
constructor(props: PlaygroundProps) {
|
||||
super(props);
|
||||
|
||||
this.output = React.createRef();
|
||||
|
||||
this.start = this.start.bind(this);
|
||||
this.resetFilesys = this.resetFilesys.bind(this);
|
||||
this.startEmulator = this.startEmulator.bind(this);
|
||||
this.fetchExecutionTime = this.fetchExecutionTime.bind(this);
|
||||
this.toggleEmulatorState = this.toggleEmulatorState.bind(this);
|
||||
|
||||
this.state = {
|
||||
settings: loadSettings(),
|
||||
drawerOpen: false,
|
||||
allowWasm64: false,
|
||||
file: decodeFileData(getEmulateData()),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
memory64().then((allowWasm64) => {
|
||||
this.setState({ allowWasm64 });
|
||||
});
|
||||
|
||||
if (this.state.file) {
|
||||
this.emulateRemoteFile(this.state.file);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.state.emulator?.stop();
|
||||
}
|
||||
|
||||
resetFilesystemState() {
|
||||
this.setState({
|
||||
filesystemPromise: undefined,
|
||||
filesystem: undefined,
|
||||
drawerOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
fetchExecutionTime() {
|
||||
return this.state.emulator ? this.state.emulator.getExecutionTime() : 0;
|
||||
}
|
||||
|
||||
async resetFilesys() {
|
||||
if (!this.state.filesystem) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.state.filesystem.delete();
|
||||
|
||||
this.resetFilesystemState();
|
||||
this.output.current?.clear();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
_onEmulatorStatusChanged(s: EmulationStatus) {
|
||||
this.setState({ emulationStatus: s });
|
||||
}
|
||||
|
||||
_onEmulatorStateChanged(s: EmulationState, persistFs: boolean) {
|
||||
if (isFinalState(s) && persistFs) {
|
||||
this.setState({ filesystemPromise: undefined, filesystem: undefined });
|
||||
this.initFilesys(true);
|
||||
} else {
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
initFilesys(force: boolean = false) {
|
||||
if (!force && this.state.filesystemPromise) {
|
||||
return this.state.filesystemPromise;
|
||||
}
|
||||
|
||||
const promise = new Promise<Filesystem>((resolve, reject) => {
|
||||
if (!force) {
|
||||
this.output.current?.clear();
|
||||
this.logLine("Loading filesystem...");
|
||||
}
|
||||
|
||||
setupFilesystem(
|
||||
(current, total, file) => {
|
||||
this.logLine(`Processing filesystem (${current}/${total}): ${file}`);
|
||||
},
|
||||
(percent) => {
|
||||
this.logLine(`Downloading filesystem: ${percent}%`);
|
||||
},
|
||||
)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
|
||||
promise.then((filesystem) => this.setState({ filesystem }));
|
||||
this.setState({ filesystemPromise: promise });
|
||||
|
||||
promise.catch((e) => {
|
||||
console.log(e);
|
||||
this.logLine("Failed to fetch filesystem:");
|
||||
this.logLine(e.toString());
|
||||
this.resetFilesystemState();
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
setDrawerOpen(drawerOpen: boolean) {
|
||||
this.setState({ drawerOpen });
|
||||
}
|
||||
|
||||
async downloadFileToFilesystem(file: PlaygroundFile) {
|
||||
const fs = await this.initFilesys();
|
||||
|
||||
const fileData = await downloadBinaryFilePercent(
|
||||
file.storage,
|
||||
(percent) => {
|
||||
this.logLine(`Downloading binary: ${percent}%`);
|
||||
},
|
||||
);
|
||||
|
||||
await fs.storeFiles([
|
||||
{
|
||||
name: windowsToInternalPath(file.file),
|
||||
data: fileData,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
async emulateRemoteFile(file: PlaygroundFile) {
|
||||
await this.downloadFileToFilesystem(file);
|
||||
await this.startEmulator(file.file);
|
||||
}
|
||||
|
||||
async start() {
|
||||
await this.initFilesys();
|
||||
this.setDrawerOpen(true);
|
||||
}
|
||||
|
||||
logLine(line: string) {
|
||||
this.output.current?.logLine(line);
|
||||
}
|
||||
|
||||
logLines(lines: string[]) {
|
||||
this.output.current?.logLines(lines);
|
||||
}
|
||||
|
||||
isEmulatorPaused() {
|
||||
return (
|
||||
this.state.emulator &&
|
||||
this.state.emulator.getState() == EmulationState.Paused
|
||||
);
|
||||
}
|
||||
|
||||
toggleEmulatorState() {
|
||||
if (this.isEmulatorPaused()) {
|
||||
this.state.emulator?.resume();
|
||||
} else {
|
||||
this.state.emulator?.pause();
|
||||
}
|
||||
}
|
||||
|
||||
async startEmulator(userFile: string) {
|
||||
this.state.emulator?.stop();
|
||||
this.output.current?.clear();
|
||||
|
||||
this.setDrawerOpen(false);
|
||||
|
||||
if (this.state.filesystemPromise) {
|
||||
await this.state.filesystemPromise;
|
||||
}
|
||||
|
||||
const persistFs = this.state.settings.persist;
|
||||
|
||||
const new_emulator = new Emulator(
|
||||
(l) => this.logLines(l),
|
||||
(s) => this._onEmulatorStateChanged(s, persistFs),
|
||||
(s) => this._onEmulatorStatusChanged(s),
|
||||
);
|
||||
//new_emulator.onTerminate().then(() => this.setState({ emulator: null }));
|
||||
|
||||
this.setState({ emulator: new_emulator, application: userFile });
|
||||
|
||||
new_emulator.start(this.state.settings, userFile);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
title="Sogen - Playground"
|
||||
description="Playground to test and run Sogen, a Windows user space emulator, right in your browser."
|
||||
preload={
|
||||
[
|
||||
/*"./emulator-worker.js", "./analyzer.js", "./analyzer.wasm"*/
|
||||
]
|
||||
}
|
||||
/>
|
||||
<div className="h-[100dvh] flex flex-col">
|
||||
<header className="flex shrink-0 items-center gap-2 border-b p-2 overflow-y-auto">
|
||||
<a title="Home" href="#/">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="fancy"
|
||||
title="Home Button"
|
||||
>
|
||||
<HouseFill />
|
||||
</Button>
|
||||
</a>
|
||||
<Button
|
||||
size="sm"
|
||||
className="fancy"
|
||||
onClick={this.start}
|
||||
title="Start"
|
||||
>
|
||||
<PlayFill /> <span>Start</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
disabled={
|
||||
!this.state.emulator ||
|
||||
isFinalState(this.state.emulator.getState())
|
||||
}
|
||||
size="sm"
|
||||
title="Stop"
|
||||
variant="secondary"
|
||||
className="fancy"
|
||||
onClick={() => this.state.emulator?.stop()}
|
||||
>
|
||||
<StopFill /> <span className="hidden sm:inline">Stop</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
title={this.isEmulatorPaused() ? "Resume" : "Pause"}
|
||||
disabled={
|
||||
!this.state.emulator ||
|
||||
isFinalState(this.state.emulator.getState())
|
||||
}
|
||||
variant="secondary"
|
||||
className="fancy"
|
||||
onClick={this.toggleEmulatorState}
|
||||
>
|
||||
{this.isEmulatorPaused() ? (
|
||||
<>
|
||||
<PlayFill /> <span className="hidden sm:inline">Resume</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PauseFill /> <span className="hidden sm:inline">Pause</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="fancy"
|
||||
title="Settings"
|
||||
>
|
||||
<GearFill />{" "}
|
||||
<span className="hidden sm:inline">Settings</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<SettingsMenu
|
||||
settings={this.state.settings}
|
||||
allowWasm64={this.state.allowWasm64}
|
||||
onChange={(s) => {
|
||||
saveSettings(s);
|
||||
this.setState({ settings: s });
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{!this.state.filesystem ? (
|
||||
<></>
|
||||
) : (
|
||||
<Drawer
|
||||
open={this.state.drawerOpen}
|
||||
onOpenChange={(o) => this.setState({ drawerOpen: o })}
|
||||
>
|
||||
<DrawerContent className="!will-change-auto">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle className="hidden">
|
||||
Filesystem Explorer
|
||||
</DrawerTitle>
|
||||
<DrawerDescription className="hidden">
|
||||
Filesystem Explorer
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<DrawerFooter>
|
||||
<FilesystemExplorer
|
||||
filesystem={this.state.filesystem}
|
||||
iconCache={this.iconCache}
|
||||
runFile={this.startEmulator}
|
||||
resetFilesys={this.resetFilesys}
|
||||
path={["c"]}
|
||||
/>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)}
|
||||
|
||||
{/* Separator */}
|
||||
<div className="flex-1"></div>
|
||||
|
||||
<div className="text-right items-center">
|
||||
<StatusIndicator
|
||||
application={this.state.application}
|
||||
state={
|
||||
this.state.emulator
|
||||
? this.state.emulator.getState()
|
||||
: EmulationState.Stopped
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex flex-1">
|
||||
<EmulationSummary
|
||||
status={this.state.emulationStatus}
|
||||
executionTimeFetcher={this.fetchExecutionTime}
|
||||
/>
|
||||
<div className="flex flex-1 flex-col pl-1 overflow-auto">
|
||||
<Output ref={this.output} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,118 +1,37 @@
|
||||
import { parse } from "shell-quote";
|
||||
|
||||
export interface Settings {
|
||||
logging: "verbose" | "silent" | "concise" | string;
|
||||
verbose: boolean;
|
||||
concise: boolean;
|
||||
silent: boolean;
|
||||
bufferStdout: boolean;
|
||||
persist: boolean;
|
||||
execAccess: boolean;
|
||||
foreignAccess: boolean;
|
||||
wasm64: boolean;
|
||||
instructionSummary: boolean;
|
||||
ignoredFunctions: string[];
|
||||
interestingModules: string[];
|
||||
commandLine: string;
|
||||
}
|
||||
|
||||
export interface TranslatedSettings {
|
||||
emulatorOptions: string[];
|
||||
applicationOptions: string[];
|
||||
}
|
||||
|
||||
export function createDefaultSettings(): Settings {
|
||||
return {
|
||||
logging: "regular",
|
||||
verbose: false,
|
||||
concise: false,
|
||||
silent: false,
|
||||
bufferStdout: true,
|
||||
persist: false,
|
||||
execAccess: false,
|
||||
foreignAccess: false,
|
||||
wasm64: false,
|
||||
instructionSummary: false,
|
||||
ignoredFunctions: [],
|
||||
interestingModules: [],
|
||||
commandLine: "",
|
||||
};
|
||||
}
|
||||
|
||||
export function loadSettings(): Settings {
|
||||
const defaultSettings = createDefaultSettings();
|
||||
export function translateSettings(settings: Settings): string[] {
|
||||
const switches: string[] = [];
|
||||
|
||||
const settingsStr = localStorage.getItem("settings");
|
||||
if (!settingsStr) {
|
||||
return defaultSettings;
|
||||
if (settings.verbose) {
|
||||
switches.push("-v");
|
||||
}
|
||||
|
||||
try {
|
||||
const userSettings = JSON.parse(settingsStr);
|
||||
const keys = Object.keys(defaultSettings);
|
||||
if (settings.concise) {
|
||||
switches.push("-c");
|
||||
}
|
||||
|
||||
keys.forEach((k) => {
|
||||
if (k in userSettings) {
|
||||
(defaultSettings as any)[k] = userSettings[k];
|
||||
}
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
return defaultSettings;
|
||||
}
|
||||
|
||||
export function saveSettings(settings: Settings) {
|
||||
localStorage.setItem("settings", JSON.stringify(settings));
|
||||
}
|
||||
|
||||
export function translateSettings(settings: Settings): TranslatedSettings {
|
||||
const switches: string[] = [];
|
||||
const options: string[] = [];
|
||||
|
||||
switch (settings.logging) {
|
||||
case "verbose":
|
||||
switches.push("-v");
|
||||
break;
|
||||
case "silent":
|
||||
switches.push("-s");
|
||||
break;
|
||||
case "concise":
|
||||
switches.push("-c");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (settings.silent) {
|
||||
switches.push("-s");
|
||||
}
|
||||
|
||||
if (settings.bufferStdout) {
|
||||
switches.push("-b");
|
||||
}
|
||||
|
||||
if (settings.execAccess) {
|
||||
switches.push("-x");
|
||||
}
|
||||
|
||||
if (settings.foreignAccess) {
|
||||
switches.push("-f");
|
||||
}
|
||||
|
||||
if (settings.instructionSummary) {
|
||||
switches.push("-is");
|
||||
}
|
||||
|
||||
settings.ignoredFunctions.forEach((f) => {
|
||||
switches.push("-i");
|
||||
switches.push(f);
|
||||
});
|
||||
|
||||
settings.interestingModules.forEach((m) => {
|
||||
switches.push("-m");
|
||||
switches.push(m);
|
||||
});
|
||||
|
||||
try {
|
||||
const argv = parse(settings.commandLine) as string[];
|
||||
options.push(...argv);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return {
|
||||
applicationOptions: options,
|
||||
emulatorOptions: switches,
|
||||
};
|
||||
return switches;
|
||||
}
|
||||
|
||||
1
page/src/vite-env.d.ts
vendored
1
page/src/vite-env.d.ts
vendored
@@ -1,2 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-pwa/client" />
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"baseUrl": ".",
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"types": ["vite-plugin-pwa/client"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,14 @@
|
||||
import path from "path";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { RuntimeCaching } from "workbox-build";
|
||||
|
||||
const mb = 1024 ** 2;
|
||||
|
||||
function generateExternalCache(
|
||||
pattern: string | RegExp,
|
||||
name: string,
|
||||
): RuntimeCaching {
|
||||
return {
|
||||
urlPattern: pattern,
|
||||
handler: "CacheFirst",
|
||||
options: {
|
||||
cacheName: name,
|
||||
expiration: {
|
||||
maxEntries: 10,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
manifest: {
|
||||
theme_color: "#0279E8",
|
||||
background_color: "#141416",
|
||||
},
|
||||
workbox: {
|
||||
maximumFileSizeToCacheInBytes: 100 * mb,
|
||||
cleanupOutdatedCaches: true,
|
||||
globPatterns: ["**/*.{js,css,html,woff,woff2,wasm}"],
|
||||
globIgnores: ["root.zip"],
|
||||
navigateFallbackDenylist: [/^\/root\.zip$/],
|
||||
runtimeCaching: [
|
||||
generateExternalCache(
|
||||
/^https:\/\/momo5502\.com\/.*/i,
|
||||
"momo5502-cache",
|
||||
),
|
||||
generateExternalCache(
|
||||
/^https:\/\/img\.youtube\.com\/.*/i,
|
||||
"youtube-img-cache",
|
||||
),
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
define: {
|
||||
"import.meta.env.VITE_BUILD_TIME": JSON.stringify(Date.now()),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,8 +14,6 @@ SortIncludes: false
|
||||
AlignEscapedNewlines: Left
|
||||
PackConstructorInitializers: Never
|
||||
IndentPPDirectives: None
|
||||
InsertNewlineAtEOF: true
|
||||
ColumnLimit: 140
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: true
|
||||
@@ -3,14 +3,12 @@ add_subdirectory(emulator)
|
||||
add_subdirectory(gdb-stub)
|
||||
add_subdirectory(windows-emulator)
|
||||
add_subdirectory(windows-gdb-stub)
|
||||
add_subdirectory(backend-selection)
|
||||
|
||||
momo_add_subdirectory_and_get_targets("backends" BACKEND_TARGETS)
|
||||
momo_targets_set_folder("backends" ${BACKEND_TARGETS})
|
||||
|
||||
if (NOT SOGEN_BUILD_STATIC)
|
||||
if (NOT MOMO_BUILD_AS_LIBRARY)
|
||||
add_subdirectory(analyzer)
|
||||
add_subdirectory(debugger)
|
||||
add_subdirectory(fuzzing-engine)
|
||||
add_subdirectory(fuzzer)
|
||||
add_subdirectory(windows-emulator-test)
|
||||
|
||||
@@ -16,11 +16,8 @@ endif()
|
||||
|
||||
target_link_libraries(analyzer PRIVATE
|
||||
reflect
|
||||
debugger
|
||||
capstone
|
||||
windows-emulator
|
||||
windows-gdb-stub
|
||||
backend-selection
|
||||
)
|
||||
|
||||
set_property(GLOBAL PROPERTY VS_STARTUP_PROJECT analyzer)
|
||||
|
||||
@@ -1,620 +0,0 @@
|
||||
#include "std_include.hpp"
|
||||
|
||||
#include "analysis.hpp"
|
||||
#include "disassembler.hpp"
|
||||
#include "windows_emulator.hpp"
|
||||
#include <utils/lazy_object.hpp>
|
||||
|
||||
#if defined(OS_EMSCRIPTEN) && !defined(MOMO_EMSCRIPTEN_SUPPORT_NODEJS)
|
||||
#include <event_handler.hpp>
|
||||
#endif
|
||||
|
||||
#define STR_VIEW_VA(str) static_cast<int>((str).size()), (str).data()
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr size_t MAX_INSTRUCTION_BYTES = 15;
|
||||
|
||||
template <typename Return, typename... Args>
|
||||
std::function<Return(Args...)> make_callback(analysis_context& c, Return (*callback)(analysis_context&, Args...))
|
||||
{
|
||||
return [&c, callback](Args... args) {
|
||||
return callback(c, std::forward<Args>(args)...); //
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Return, typename... Args>
|
||||
std::function<Return(Args...)> make_callback(analysis_context& c, Return (*callback)(const analysis_context&, Args...))
|
||||
{
|
||||
return [&c, callback](Args... args) {
|
||||
return callback(c, std::forward<Args>(args)...); //
|
||||
};
|
||||
}
|
||||
|
||||
std::string get_instruction_string(const disassembler& d, const emulator& emu, const uint64_t address)
|
||||
{
|
||||
std::array<uint8_t, MAX_INSTRUCTION_BYTES> instruction_bytes{};
|
||||
const auto result = emu.try_read_memory(address, instruction_bytes.data(), instruction_bytes.size());
|
||||
if (!result)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
uint16_t reg_cs = 0;
|
||||
auto& emu_ref = const_cast<emulator&>(emu);
|
||||
emu_ref.read_raw_register(static_cast<int>(x86_register::cs), ®_cs, sizeof(reg_cs));
|
||||
const auto instructions = d.disassemble(emu_ref, reg_cs, instruction_bytes, 1);
|
||||
if (instructions.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& inst = instructions[0];
|
||||
return std::string(inst.mnemonic) + (strlen(inst.op_str) ? " "s + inst.op_str : "");
|
||||
}
|
||||
|
||||
void handle_suspicious_activity(const analysis_context& c, const std::string_view details)
|
||||
{
|
||||
std::string addition{};
|
||||
const auto rip = c.win_emu->emu().read_instruction_pointer();
|
||||
|
||||
// TODO: Pass enum?
|
||||
if (details == "Illegal instruction")
|
||||
{
|
||||
const auto inst = get_instruction_string(c.d, c.win_emu->emu(), rip);
|
||||
if (!inst.empty())
|
||||
{
|
||||
addition = " (" + inst + ")";
|
||||
}
|
||||
}
|
||||
|
||||
c.win_emu->log.print(color::pink, "Suspicious: %.*s%.*s at 0x%" PRIx64 " (via 0x%" PRIx64 ")\n", STR_VIEW_VA(details),
|
||||
STR_VIEW_VA(addition), rip, c.win_emu->current_thread().previous_ip);
|
||||
}
|
||||
|
||||
void handle_debug_string(const analysis_context& c, const std::string_view details)
|
||||
{
|
||||
c.win_emu->log.info("--> Debug string: %.*s\n", STR_VIEW_VA(details));
|
||||
}
|
||||
|
||||
void handle_generic_activity(const analysis_context& c, const std::string_view details)
|
||||
{
|
||||
c.win_emu->log.print(color::dark_gray, "%.*s\n", STR_VIEW_VA(details));
|
||||
}
|
||||
|
||||
void handle_generic_access(const analysis_context& c, const std::string_view type, const std::u16string_view name)
|
||||
{
|
||||
c.win_emu->log.print(color::dark_gray, "--> %.*s: %s\n", STR_VIEW_VA(type), u16_to_u8(name).c_str()); //
|
||||
}
|
||||
|
||||
void handle_memory_allocate(const analysis_context& c, const uint64_t address, const uint64_t length,
|
||||
const memory_permission permission, const bool commit)
|
||||
{
|
||||
const auto* action = commit ? "Committed" : "Allocating";
|
||||
|
||||
c.win_emu->log.print(is_executable(permission) ? color::gray : color::dark_gray, "--> %s 0x%" PRIx64 " - 0x%" PRIx64 " (%s)\n",
|
||||
action, address, address + length, get_permission_string(permission).c_str());
|
||||
}
|
||||
|
||||
void handle_memory_protect(const analysis_context& c, const uint64_t address, const uint64_t length, const memory_permission permission)
|
||||
{
|
||||
c.win_emu->log.print(color::dark_gray, "--> Changing protection at 0x%" PRIx64 "-0x%" PRIx64 " to %s\n", address, address + length,
|
||||
get_permission_string(permission).c_str());
|
||||
}
|
||||
|
||||
void handle_memory_violate(const analysis_context& c, const uint64_t address, const uint64_t size, const memory_operation operation,
|
||||
const memory_violation_type type)
|
||||
{
|
||||
const auto permission = get_permission_string(operation);
|
||||
const auto ip = c.win_emu->emu().read_instruction_pointer();
|
||||
const char* name = c.win_emu->mod_manager.find_name(ip);
|
||||
|
||||
if (type == memory_violation_type::protection)
|
||||
{
|
||||
c.win_emu->log.print(color::gray, "Protection violation: 0x%" PRIx64 " (%" PRIx64 ") - %s at 0x%" PRIx64 " (%s)\n", address,
|
||||
size, permission.c_str(), ip, name);
|
||||
}
|
||||
else if (type == memory_violation_type::unmapped)
|
||||
{
|
||||
c.win_emu->log.print(color::gray, "Mapping violation: 0x%" PRIx64 " (%" PRIx64 ") - %s at 0x%" PRIx64 " (%s)\n", address, size,
|
||||
permission.c_str(), ip, name);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_ioctrl(const analysis_context& c, const io_device&, const std::u16string_view device_name, const ULONG code)
|
||||
{
|
||||
c.win_emu->log.print(color::dark_gray, "--> %s: 0x%X\n", u16_to_u8(device_name).c_str(), static_cast<uint32_t>(code));
|
||||
}
|
||||
|
||||
void handle_thread_set_name(const analysis_context& c, const emulator_thread& t)
|
||||
{
|
||||
c.win_emu->log.print(color::blue, "Setting thread (%u) name: %s\n", t.id, u16_to_u8(t.name).c_str());
|
||||
}
|
||||
|
||||
void handle_thread_switch(const analysis_context& c, const emulator_thread& current_thread, const emulator_thread& new_thread)
|
||||
{
|
||||
c.win_emu->log.print(color::dark_gray, "Performing thread switch: %X -> %X\n", current_thread.id, new_thread.id);
|
||||
}
|
||||
|
||||
void handle_module_load(const analysis_context& c, const mapped_module& mod)
|
||||
{
|
||||
c.win_emu->log.log("Mapped %s at 0x%" PRIx64 "\n", mod.path.generic_string().c_str(), mod.image_base);
|
||||
}
|
||||
|
||||
void handle_module_unload(const analysis_context& c, const mapped_module& mod)
|
||||
{
|
||||
c.win_emu->log.log("Unmapping %s (0x%" PRIx64 ")\n", mod.path.generic_string().c_str(), mod.image_base);
|
||||
}
|
||||
|
||||
void print_string(logger& log, const std::string_view str)
|
||||
{
|
||||
log.print(color::dark_gray, "--> %.*s\n", STR_VIEW_VA(str));
|
||||
}
|
||||
|
||||
void print_string(logger& log, const std::u16string_view str)
|
||||
{
|
||||
print_string(log, u16_to_u8(str));
|
||||
}
|
||||
|
||||
bool is_int_resource(const uint64_t address)
|
||||
{
|
||||
return (address >> 0x10) == 0;
|
||||
}
|
||||
|
||||
template <typename CharType = char>
|
||||
void print_arg_as_string(windows_emulator& win_emu, const size_t index)
|
||||
{
|
||||
const auto var_ptr = get_function_argument(win_emu.emu(), index);
|
||||
if (var_ptr && !is_int_resource(var_ptr))
|
||||
{
|
||||
const auto str = read_string<CharType>(win_emu.memory, var_ptr);
|
||||
print_string(win_emu.log, str);
|
||||
}
|
||||
}
|
||||
|
||||
void print_module_name(windows_emulator& win_emu, const size_t index)
|
||||
{
|
||||
const auto var_ptr = get_function_argument(win_emu.emu(), index);
|
||||
if (var_ptr)
|
||||
{
|
||||
const auto* module_name = win_emu.mod_manager.find_name(var_ptr);
|
||||
print_string(win_emu.log, module_name);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_function_details(const analysis_context& c, const std::string_view function)
|
||||
{
|
||||
if (function == "GetEnvironmentVariableA" //
|
||||
|| function == "ExpandEnvironmentStringsA" //
|
||||
|| function == "LoadLibraryA")
|
||||
{
|
||||
print_arg_as_string(*c.win_emu, 0);
|
||||
}
|
||||
else if (function == "LoadLibraryW")
|
||||
{
|
||||
print_arg_as_string<char16_t>(*c.win_emu, 0);
|
||||
}
|
||||
else if (function == "MessageBoxA")
|
||||
{
|
||||
print_arg_as_string(*c.win_emu, 2);
|
||||
print_arg_as_string(*c.win_emu, 1);
|
||||
}
|
||||
else if (function == "MessageBoxW")
|
||||
{
|
||||
print_arg_as_string<char16_t>(*c.win_emu, 2);
|
||||
print_arg_as_string<char16_t>(*c.win_emu, 1);
|
||||
}
|
||||
else if (function == "GetProcAddress")
|
||||
{
|
||||
print_module_name(*c.win_emu, 0);
|
||||
print_arg_as_string(*c.win_emu, 1);
|
||||
}
|
||||
else if (function == "WinVerifyTrust")
|
||||
{
|
||||
auto& emu = c.win_emu->emu();
|
||||
emu.reg(x86_register::rip, emu.read_stack(0));
|
||||
emu.reg(x86_register::rsp, emu.reg(x86_register::rsp) + 8);
|
||||
emu.reg(x86_register::rax, 0);
|
||||
}
|
||||
else if (function == "lstrcmp" || function == "lstrcmpi")
|
||||
{
|
||||
print_arg_as_string(*c.win_emu, 0);
|
||||
print_arg_as_string(*c.win_emu, 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_thread_alive(const analysis_context& c, const uint32_t thread_id)
|
||||
{
|
||||
for (const auto& t : c.win_emu->process.threads | std::views::values)
|
||||
{
|
||||
if (t.id == thread_id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void update_import_access(analysis_context& c, const uint64_t address)
|
||||
{
|
||||
if (c.accessed_imports.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& t = c.win_emu->current_thread();
|
||||
for (auto entry = c.accessed_imports.begin(); entry != c.accessed_imports.end();)
|
||||
{
|
||||
auto& a = *entry;
|
||||
const auto is_same_thread = t.id == a.thread_id;
|
||||
|
||||
if (is_same_thread && address == a.address)
|
||||
{
|
||||
entry = c.accessed_imports.erase(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
constexpr auto inst_delay = 100u;
|
||||
const auto execution_delay_reached = is_same_thread && a.access_inst_count + inst_delay <= t.executed_instructions;
|
||||
|
||||
if (!execution_delay_reached && is_thread_alive(c, a.thread_id))
|
||||
{
|
||||
++entry;
|
||||
continue;
|
||||
}
|
||||
|
||||
c.win_emu->log.print(color::green, "Import read access: %s (%s) at 0x%" PRIx64 " (%s)\n", a.import_name.c_str(),
|
||||
a.import_module.c_str(), a.access_rip, a.accessor_module.c_str());
|
||||
|
||||
entry = c.accessed_imports.erase(entry);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_return(const disassembler& d, const emulator& emu, const uint64_t address)
|
||||
{
|
||||
std::array<uint8_t, MAX_INSTRUCTION_BYTES> instruction_bytes{};
|
||||
const auto result = emu.try_read_memory(address, instruction_bytes.data(), instruction_bytes.size());
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t reg_cs = 0;
|
||||
auto& emu_ref = const_cast<emulator&>(emu);
|
||||
emu_ref.read_raw_register(static_cast<int>(x86_register::cs), ®_cs, sizeof(reg_cs));
|
||||
const auto instructions = d.disassemble(emu_ref, reg_cs, instruction_bytes, 1);
|
||||
if (instructions.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto handle = d.resolve_handle(emu_ref, reg_cs);
|
||||
return cs_insn_group(handle, instructions.data(), CS_GRP_RET);
|
||||
}
|
||||
|
||||
void record_instruction(analysis_context& c, const uint64_t address)
|
||||
{
|
||||
auto& emu = c.win_emu->emu();
|
||||
std::array<uint8_t, MAX_INSTRUCTION_BYTES> instruction_bytes{};
|
||||
const auto result = emu.try_read_memory(address, instruction_bytes.data(), instruction_bytes.size());
|
||||
if (!result)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto reg_cs = emu.reg<uint16_t>(x86_register::cs);
|
||||
disassembler disasm{};
|
||||
const auto instructions = disasm.disassemble(emu, reg_cs, instruction_bytes, 1);
|
||||
if (instructions.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
++c.instructions[instructions[0].id];
|
||||
}
|
||||
|
||||
void handle_instruction(analysis_context& c, const uint64_t address)
|
||||
{
|
||||
auto& win_emu = *c.win_emu;
|
||||
update_import_access(c, address);
|
||||
|
||||
#if defined(OS_EMSCRIPTEN) && !defined(MOMO_EMSCRIPTEN_SUPPORT_NODEJS)
|
||||
if ((win_emu.get_executed_instructions() % 0x20000) == 0)
|
||||
{
|
||||
debugger::event_context ec{.win_emu = win_emu};
|
||||
debugger::handle_events(ec);
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto& current_thread = c.win_emu->current_thread();
|
||||
const auto previous_ip = current_thread.previous_ip;
|
||||
[[maybe_unused]] const auto current_ip = current_thread.current_ip;
|
||||
const auto is_main_exe = win_emu.mod_manager.executable->contains(address);
|
||||
const auto is_previous_main_exe = win_emu.mod_manager.executable->contains(previous_ip);
|
||||
|
||||
const auto binary = utils::make_lazy([&] {
|
||||
if (is_main_exe)
|
||||
{
|
||||
return win_emu.mod_manager.executable;
|
||||
}
|
||||
|
||||
return win_emu.mod_manager.find_by_address(address); //
|
||||
});
|
||||
|
||||
const auto previous_binary = utils::make_lazy([&] {
|
||||
if (is_previous_main_exe)
|
||||
{
|
||||
return win_emu.mod_manager.executable;
|
||||
}
|
||||
|
||||
return win_emu.mod_manager.find_by_address(previous_ip); //
|
||||
});
|
||||
|
||||
const auto is_current_binary_interesting = utils::make_lazy([&] {
|
||||
return is_main_exe || (binary && c.settings->modules.contains(binary->name)); //
|
||||
});
|
||||
|
||||
const auto is_in_interesting_module = [&] {
|
||||
if (c.settings->modules.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_current_binary_interesting || (previous_binary && c.settings->modules.contains(previous_binary->name));
|
||||
};
|
||||
|
||||
if (c.settings->instruction_summary && (is_current_binary_interesting || !binary))
|
||||
{
|
||||
record_instruction(c, address);
|
||||
}
|
||||
|
||||
const auto is_interesting_call = is_previous_main_exe //
|
||||
|| (!previous_binary && current_thread.executed_instructions > 1) //
|
||||
|| is_in_interesting_module();
|
||||
|
||||
if (!c.has_reached_main && c.settings->concise_logging && !c.settings->silent && is_main_exe)
|
||||
{
|
||||
c.has_reached_main = true;
|
||||
win_emu.log.disable_output(false);
|
||||
}
|
||||
|
||||
if ((!c.settings->verbose_logging && !is_interesting_call) || !binary)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto export_entry = binary->address_names.find(address);
|
||||
if (export_entry != binary->address_names.end())
|
||||
{
|
||||
if (!c.settings->ignored_functions.contains(export_entry->second))
|
||||
{
|
||||
win_emu.log.print(is_interesting_call ? color::yellow : color::dark_gray,
|
||||
"Executing function: %s (%s) (0x%" PRIx64 ") via (0x%" PRIx64 ") %s\n", export_entry->second.c_str(),
|
||||
binary->name.c_str(), address, previous_ip, previous_binary ? previous_binary->name.c_str() : "<N/A>");
|
||||
|
||||
if (is_interesting_call)
|
||||
{
|
||||
handle_function_details(c, export_entry->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (address == binary->entry_point)
|
||||
{
|
||||
win_emu.log.print(is_interesting_call ? color::yellow : color::gray, "Executing entry point: %s (0x%" PRIx64 ")\n",
|
||||
binary->name.c_str(), address);
|
||||
}
|
||||
else if (is_previous_main_exe && binary != previous_binary && !is_return(c.d, c.win_emu->emu(), previous_ip))
|
||||
{
|
||||
auto nearest_entry = binary->address_names.upper_bound(address);
|
||||
if (nearest_entry == binary->address_names.begin())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
--nearest_entry;
|
||||
|
||||
win_emu.log.print(is_interesting_call ? color::yellow : color::dark_gray,
|
||||
"Transition to foreign code: %s+0x%" PRIx64 " (%s) (0x%" PRIx64 ") via (0x%" PRIx64 ") %s\n",
|
||||
nearest_entry->second.c_str(), address - nearest_entry->first, binary->name.c_str(), address, previous_ip,
|
||||
previous_binary ? previous_binary->name.c_str() : "<N/A>");
|
||||
}
|
||||
}
|
||||
|
||||
void handle_rdtsc(analysis_context& c)
|
||||
{
|
||||
auto& win_emu = *c.win_emu;
|
||||
auto& emu = win_emu.emu();
|
||||
|
||||
const auto rip = emu.read_instruction_pointer();
|
||||
const auto mod = get_module_if_interesting(win_emu.mod_manager, c.settings->modules, rip);
|
||||
|
||||
if (!mod.has_value() || (c.settings->concise_logging && !c.rdtsc_cache.insert(rip).second))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
win_emu.log.print(color::blue, "Executing RDTSC instruction at 0x%" PRIx64 " (%s)\n", rip, (*mod) ? (*mod)->name.c_str() : "<N/A>");
|
||||
}
|
||||
|
||||
void handle_rdtscp(analysis_context& c)
|
||||
{
|
||||
auto& win_emu = *c.win_emu;
|
||||
auto& emu = win_emu.emu();
|
||||
|
||||
const auto rip = emu.read_instruction_pointer();
|
||||
const auto mod = get_module_if_interesting(win_emu.mod_manager, c.settings->modules, rip);
|
||||
|
||||
if (!mod.has_value() || (c.settings->concise_logging && !c.rdtscp_cache.insert(rip).second))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
win_emu.log.print(color::blue, "Executing RDTSCP instruction at 0x%" PRIx64 " (%s)\n", rip,
|
||||
(*mod) ? (*mod)->name.c_str() : "<N/A>");
|
||||
}
|
||||
|
||||
emulator_callbacks::continuation handle_syscall(const analysis_context& c, const uint32_t syscall_id,
|
||||
const std::string_view syscall_name)
|
||||
{
|
||||
auto& win_emu = *c.win_emu;
|
||||
auto& emu = win_emu.emu();
|
||||
|
||||
const auto address = emu.read_instruction_pointer();
|
||||
const auto* mod = win_emu.mod_manager.find_by_address(address);
|
||||
const auto is_sus_module = mod != win_emu.mod_manager.ntdll && mod != win_emu.mod_manager.win32u;
|
||||
const auto previous_ip = win_emu.current_thread().previous_ip;
|
||||
|
||||
if (is_sus_module)
|
||||
{
|
||||
win_emu.log.print(color::blue, "Executing inline syscall: %.*s (0x%X) at 0x%" PRIx64 " (%s)\n", STR_VIEW_VA(syscall_name),
|
||||
syscall_id, address, mod ? mod->name.c_str() : "<N/A>");
|
||||
}
|
||||
else if (mod->contains(previous_ip))
|
||||
{
|
||||
if (!c.settings->skip_syscalls)
|
||||
{
|
||||
const auto rsp = emu.read_stack_pointer();
|
||||
|
||||
uint64_t return_address{};
|
||||
emu.try_read_memory(rsp, &return_address, sizeof(return_address));
|
||||
|
||||
const auto* caller_mod_name = win_emu.mod_manager.find_name(return_address);
|
||||
|
||||
win_emu.log.print(color::dark_gray, "Executing syscall: %.*s (0x%X) at 0x%" PRIx64 " via 0x%" PRIx64 " (%s)\n",
|
||||
STR_VIEW_VA(syscall_name), syscall_id, address, return_address, caller_mod_name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto* previous_mod = win_emu.mod_manager.find_by_address(previous_ip);
|
||||
|
||||
win_emu.log.print(color::blue, "Crafted out-of-line syscall: %.*s (0x%X) at 0x%" PRIx64 " (%s) via 0x%" PRIx64 " (%s)\n",
|
||||
STR_VIEW_VA(syscall_name), syscall_id, address, mod ? mod->name.c_str() : "<N/A>", previous_ip,
|
||||
previous_mod ? previous_mod->name.c_str() : "<N/A>");
|
||||
}
|
||||
|
||||
return instruction_hook_continuation::run_instruction;
|
||||
}
|
||||
|
||||
void handle_stdout(analysis_context& c, const std::string_view data)
|
||||
{
|
||||
if (c.settings->silent)
|
||||
{
|
||||
(void)fwrite(data.data(), 1, data.size(), stdout);
|
||||
}
|
||||
else if (c.settings->buffer_stdout)
|
||||
{
|
||||
c.output.append(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
c.win_emu->log.info("%.*s%s", static_cast<int>(data.size()), data.data(), data.ends_with("\n") ? "" : "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void watch_import_table(analysis_context& c)
|
||||
{
|
||||
c.win_emu->setup_process_if_necessary();
|
||||
|
||||
const auto& import_list = c.win_emu->mod_manager.executable->imports;
|
||||
if (import_list.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto min = std::numeric_limits<uint64_t>::max();
|
||||
auto max = std::numeric_limits<uint64_t>::min();
|
||||
|
||||
for (const auto& import_thunk : import_list | std::views::keys)
|
||||
{
|
||||
min = std::min(import_thunk, min);
|
||||
max = std::max(import_thunk, max);
|
||||
}
|
||||
|
||||
c.win_emu->emu().hook_memory_read(min, max - min, [&c](const uint64_t address, const void*, size_t) {
|
||||
const auto& watched_module = *c.win_emu->mod_manager.executable;
|
||||
const auto& accessor_module = *c.win_emu->mod_manager.executable;
|
||||
|
||||
const auto rip = c.win_emu->emu().read_instruction_pointer();
|
||||
|
||||
if (!accessor_module.contains(rip))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sym = watched_module.imports.find(address);
|
||||
if (sym == watched_module.imports.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
accessed_import access{};
|
||||
|
||||
access.address = c.win_emu->emu().read_memory<uint64_t>(address);
|
||||
|
||||
access.access_rip = rip;
|
||||
access.accessor_module = accessor_module.name;
|
||||
|
||||
access.import_name = sym->second.name;
|
||||
access.import_module = watched_module.imported_modules.at(sym->second.module_index);
|
||||
|
||||
const auto& t = c.win_emu->current_thread();
|
||||
access.thread_id = t.id;
|
||||
access.access_inst_count = t.executed_instructions;
|
||||
|
||||
c.accessed_imports.push_back(std::move(access));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void register_analysis_callbacks(analysis_context& c)
|
||||
{
|
||||
auto& cb = c.win_emu->callbacks;
|
||||
|
||||
cb.on_stdout = make_callback(c, handle_stdout);
|
||||
cb.on_syscall = make_callback(c, handle_syscall);
|
||||
cb.on_rdtsc = make_callback(c, handle_rdtsc);
|
||||
cb.on_rdtscp = make_callback(c, handle_rdtscp);
|
||||
cb.on_ioctrl = make_callback(c, handle_ioctrl);
|
||||
|
||||
cb.on_memory_protect = make_callback(c, handle_memory_protect);
|
||||
cb.on_memory_violate = make_callback(c, handle_memory_violate);
|
||||
cb.on_memory_allocate = make_callback(c, handle_memory_allocate);
|
||||
|
||||
cb.on_module_load = make_callback(c, handle_module_load);
|
||||
cb.on_module_unload = make_callback(c, handle_module_unload);
|
||||
|
||||
cb.on_thread_switch = make_callback(c, handle_thread_switch);
|
||||
cb.on_thread_set_name = make_callback(c, handle_thread_set_name);
|
||||
|
||||
cb.on_instruction = make_callback(c, handle_instruction);
|
||||
cb.on_debug_string = make_callback(c, handle_debug_string);
|
||||
cb.on_generic_access = make_callback(c, handle_generic_access);
|
||||
cb.on_generic_activity = make_callback(c, handle_generic_activity);
|
||||
cb.on_suspicious_activity = make_callback(c, handle_suspicious_activity);
|
||||
|
||||
watch_import_table(c);
|
||||
}
|
||||
|
||||
std::optional<mapped_module*> get_module_if_interesting(module_manager& manager, const string_set& modules, const uint64_t address)
|
||||
{
|
||||
if (manager.executable->contains(address))
|
||||
{
|
||||
return manager.executable;
|
||||
}
|
||||
|
||||
auto* mod = manager.find_by_address(address);
|
||||
if (!mod)
|
||||
{
|
||||
// Not being part of any module is interesting
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (modules.contains(mod->name))
|
||||
{
|
||||
return mod;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include "disassembler.hpp"
|
||||
|
||||
struct mapped_module;
|
||||
class module_manager;
|
||||
class windows_emulator;
|
||||
|
||||
using string_set = std::set<std::string, std::less<>>;
|
||||
|
||||
struct analysis_settings
|
||||
{
|
||||
bool concise_logging{false};
|
||||
bool verbose_logging{false};
|
||||
bool silent{false};
|
||||
bool buffer_stdout{false};
|
||||
bool instruction_summary{false};
|
||||
bool skip_syscalls{false};
|
||||
|
||||
string_set modules{};
|
||||
string_set ignored_functions{};
|
||||
};
|
||||
|
||||
struct accessed_import
|
||||
{
|
||||
uint64_t address{};
|
||||
uint32_t thread_id{};
|
||||
uint64_t access_rip{};
|
||||
uint64_t access_inst_count{};
|
||||
std::string accessor_module{};
|
||||
std::string import_name{};
|
||||
std::string import_module{};
|
||||
};
|
||||
|
||||
struct analysis_context
|
||||
{
|
||||
const analysis_settings* settings{};
|
||||
windows_emulator* win_emu{};
|
||||
|
||||
std::string output{};
|
||||
bool has_reached_main{false};
|
||||
|
||||
disassembler d{};
|
||||
std::unordered_map<uint32_t, uint64_t> instructions{};
|
||||
std::vector<accessed_import> accessed_imports{};
|
||||
std::set<uint64_t> rdtsc_cache{};
|
||||
std::set<uint64_t> rdtscp_cache{};
|
||||
std::set<std::pair<uint64_t, uint32_t>> cpuid_cache{};
|
||||
};
|
||||
|
||||
void register_analysis_callbacks(analysis_context& c);
|
||||
std::optional<mapped_module*> get_module_if_interesting(module_manager& manager, const string_set& modules, uint64_t address);
|
||||
@@ -1,125 +0,0 @@
|
||||
#include "std_include.hpp"
|
||||
#include "disassembler.hpp"
|
||||
#include "common/segment_utils.hpp"
|
||||
|
||||
#include <utils/finally.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
void cse(const cs_err error)
|
||||
{
|
||||
if (error != CS_ERR_OK)
|
||||
{
|
||||
throw std::runtime_error(cs_strerror(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disassembler::disassembler()
|
||||
{
|
||||
auto deleter = utils::finally([&] { this->release(); });
|
||||
|
||||
cse(cs_open(CS_ARCH_X86, CS_MODE_64, &this->handle_64_));
|
||||
cse(cs_option(this->handle_64_, CS_OPT_DETAIL, CS_OPT_ON));
|
||||
|
||||
cse(cs_open(CS_ARCH_X86, CS_MODE_32, &this->handle_32_));
|
||||
cse(cs_option(this->handle_32_, CS_OPT_DETAIL, CS_OPT_ON));
|
||||
|
||||
cse(cs_open(CS_ARCH_X86, CS_MODE_16, &this->handle_16_));
|
||||
cse(cs_option(this->handle_16_, CS_OPT_DETAIL, CS_OPT_ON));
|
||||
|
||||
deleter.cancel();
|
||||
}
|
||||
|
||||
disassembler::~disassembler()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
disassembler::disassembler(disassembler&& obj) noexcept
|
||||
{
|
||||
this->operator=(std::move(obj));
|
||||
}
|
||||
|
||||
disassembler& disassembler::operator=(disassembler&& obj) noexcept
|
||||
{
|
||||
if (this != &obj)
|
||||
{
|
||||
this->release();
|
||||
this->handle_64_ = obj.handle_64_;
|
||||
this->handle_32_ = obj.handle_32_;
|
||||
this->handle_16_ = obj.handle_16_;
|
||||
obj.handle_64_ = 0;
|
||||
obj.handle_32_ = 0;
|
||||
obj.handle_16_ = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void disassembler::release()
|
||||
{
|
||||
if (this->handle_64_)
|
||||
{
|
||||
cs_close(&this->handle_64_);
|
||||
this->handle_64_ = 0;
|
||||
}
|
||||
|
||||
if (this->handle_32_)
|
||||
{
|
||||
cs_close(&this->handle_32_);
|
||||
this->handle_32_ = 0;
|
||||
}
|
||||
|
||||
if (this->handle_16_)
|
||||
{
|
||||
cs_close(&this->handle_16_);
|
||||
this->handle_16_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
instructions disassembler::disassemble(emulator& cpu, const uint16_t cs_selector, const std::span<const uint8_t> data,
|
||||
const size_t count) const
|
||||
{
|
||||
// Select the handle by decoding the code segment descriptor as documented in Intel 64 and IA-32 Architectures SDM Vol. 3.
|
||||
const csh handle_to_use = this->resolve_handle(cpu, cs_selector);
|
||||
|
||||
cs_insn* insts{};
|
||||
const auto inst_count = cs_disasm(handle_to_use, data.data(), data.size(), count, 0, &insts);
|
||||
return instructions{std::span(insts, inst_count)};
|
||||
}
|
||||
|
||||
std::optional<disassembler::segment_bitness> disassembler::get_segment_bitness(emulator& cpu, const uint16_t cs_selector)
|
||||
{
|
||||
return segment_utils::get_segment_bitness(cpu, cs_selector);
|
||||
}
|
||||
|
||||
csh disassembler::resolve_handle(emulator& cpu, const uint16_t cs_selector) const
|
||||
{
|
||||
const auto mode = disassembler::get_segment_bitness(cpu, cs_selector);
|
||||
if (!mode)
|
||||
{
|
||||
return this->handle_64_;
|
||||
}
|
||||
|
||||
switch (*mode)
|
||||
{
|
||||
case segment_bitness::bit16:
|
||||
return this->handle_16_;
|
||||
case segment_bitness::bit32:
|
||||
return this->handle_32_;
|
||||
case segment_bitness::bit64:
|
||||
default:
|
||||
return this->handle_64_;
|
||||
}
|
||||
}
|
||||
|
||||
void instructions::release()
|
||||
{
|
||||
if (!this->instructions_.empty())
|
||||
{
|
||||
cs_free(this->instructions_.data(), this->instructions_.size());
|
||||
}
|
||||
|
||||
this->instructions_ = {};
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <capstone/capstone.h>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
#include "common/segment_utils.hpp"
|
||||
|
||||
class emulator;
|
||||
|
||||
class instructions
|
||||
{
|
||||
public:
|
||||
instructions() = default;
|
||||
~instructions()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
instructions(instructions&& obj) noexcept
|
||||
: instructions()
|
||||
{
|
||||
this->operator=(std::move(obj));
|
||||
}
|
||||
|
||||
instructions& operator=(instructions&& obj) noexcept
|
||||
{
|
||||
if (this != &obj)
|
||||
{
|
||||
this->release();
|
||||
this->instructions_ = obj.instructions_;
|
||||
obj.instructions_ = {};
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
instructions(const instructions&) = delete;
|
||||
instructions& operator=(const instructions&) = delete;
|
||||
|
||||
operator std::span<cs_insn>() const
|
||||
{
|
||||
return this->instructions_;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return this->instructions_.empty();
|
||||
}
|
||||
|
||||
size_t size() const noexcept
|
||||
{
|
||||
return this->instructions_.size();
|
||||
}
|
||||
|
||||
const cs_insn* data() const noexcept
|
||||
{
|
||||
return this->instructions_.data();
|
||||
}
|
||||
|
||||
const cs_insn& operator[](const size_t index) const
|
||||
{
|
||||
return this->instructions_[index];
|
||||
}
|
||||
|
||||
auto begin() const
|
||||
{
|
||||
return this->instructions_.begin();
|
||||
}
|
||||
auto end() const
|
||||
{
|
||||
return this->instructions_.end();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class disassembler;
|
||||
std::span<cs_insn> instructions_{};
|
||||
|
||||
explicit instructions(const std::span<cs_insn> insts)
|
||||
: instructions_(insts)
|
||||
{
|
||||
}
|
||||
|
||||
void release();
|
||||
};
|
||||
|
||||
class disassembler
|
||||
{
|
||||
public:
|
||||
disassembler();
|
||||
~disassembler();
|
||||
|
||||
disassembler(disassembler&& obj) noexcept;
|
||||
disassembler& operator=(disassembler&& obj) noexcept;
|
||||
|
||||
disassembler(const disassembler& obj) = delete;
|
||||
disassembler& operator=(const disassembler& obj) = delete;
|
||||
|
||||
using segment_bitness = segment_utils::segment_bitness;
|
||||
|
||||
instructions disassemble(emulator& cpu, uint16_t cs_selector, std::span<const uint8_t> data, size_t count) const;
|
||||
static std::optional<segment_bitness> get_segment_bitness(emulator& cpu, uint16_t cs_selector);
|
||||
csh resolve_handle(emulator& cpu, uint16_t cs_selector) const;
|
||||
|
||||
csh get_handle_64() const
|
||||
{
|
||||
return this->handle_64_;
|
||||
}
|
||||
|
||||
csh get_handle_32() const
|
||||
{
|
||||
return this->handle_32_;
|
||||
}
|
||||
|
||||
csh get_handle_16() const
|
||||
{
|
||||
return this->handle_16_;
|
||||
}
|
||||
|
||||
private:
|
||||
csh handle_64_{};
|
||||
csh handle_32_{};
|
||||
csh handle_16_{};
|
||||
|
||||
void release();
|
||||
};
|
||||
@@ -1,39 +1,34 @@
|
||||
#include "std_include.hpp"
|
||||
|
||||
#include <windows_emulator.hpp>
|
||||
#include <backend_selection.hpp>
|
||||
#include <win_x64_gdb_stub_handler.hpp>
|
||||
#include <minidump_loader.hpp>
|
||||
#include <scoped_hook.hpp>
|
||||
|
||||
#include "object_watching.hpp"
|
||||
#include "snapshot.hpp"
|
||||
#include "analysis.hpp"
|
||||
#include "tenet_tracer.hpp"
|
||||
|
||||
#include <utils/finally.hpp>
|
||||
#include <utils/interupt_handler.hpp>
|
||||
|
||||
#if defined(OS_EMSCRIPTEN) && !defined(MOMO_EMSCRIPTEN_SUPPORT_NODEJS)
|
||||
#include <event_handler.hpp>
|
||||
#endif
|
||||
#include <cstdio>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct analysis_options : analysis_settings
|
||||
struct analysis_options
|
||||
{
|
||||
mutable bool use_gdb{false};
|
||||
bool log_executable_access{false};
|
||||
bool log_foreign_module_access{false};
|
||||
bool tenet_trace{false};
|
||||
bool concise_logging{false};
|
||||
bool verbose_logging{false};
|
||||
bool silent{false};
|
||||
bool buffer_stdout{false};
|
||||
std::filesystem::path dump{};
|
||||
std::filesystem::path minidump_path{};
|
||||
std::string registry_path{"./registry"};
|
||||
std::string emulation_root{};
|
||||
std::set<std::string, std::less<>> modules{};
|
||||
std::set<std::string, std::less<>> ignored_functions{};
|
||||
std::unordered_map<windows_path, std::filesystem::path> path_mappings{};
|
||||
};
|
||||
|
||||
void split_and_insert(std::set<std::string, std::less<>>& container, const std::string_view str, const char splitter = ',')
|
||||
void split_and_insert(std::set<std::string, std::less<>>& container, const std::string_view str,
|
||||
const char splitter = ',')
|
||||
{
|
||||
size_t current_start = 0;
|
||||
for (size_t i = 0; i < str.size(); ++i)
|
||||
@@ -58,166 +53,37 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void watch_system_objects(windows_emulator& win_emu, const std::set<std::string, std::less<>>& modules,
|
||||
const bool cache_logging)
|
||||
{
|
||||
(void)win_emu;
|
||||
(void)modules;
|
||||
(void)cache_logging;
|
||||
|
||||
#if !defined(__GNUC__) || defined(__clang__)
|
||||
struct analysis_state
|
||||
{
|
||||
windows_emulator& win_emu_;
|
||||
scoped_hook env_data_hook_;
|
||||
scoped_hook env_ptr_hook_;
|
||||
scoped_hook params_hook_;
|
||||
scoped_hook ldr_hook_;
|
||||
std::map<std::string, uint64_t> env_module_cache_{};
|
||||
std::shared_ptr<object_watching_state> params_state_ = std::make_shared<object_watching_state>();
|
||||
std::shared_ptr<object_watching_state> ldr_state_ = std::make_shared<object_watching_state>();
|
||||
std::set<std::string, std::less<>> modules_;
|
||||
bool verbose_;
|
||||
bool concise_;
|
||||
watch_object(win_emu, modules, *win_emu.current_thread().teb, cache_logging);
|
||||
watch_object(win_emu, modules, win_emu.process.peb, cache_logging);
|
||||
watch_object(win_emu, modules, emulator_object<KUSER_SHARED_DATA64>{win_emu.emu(), kusd_mmio::address()},
|
||||
cache_logging);
|
||||
|
||||
analysis_state(windows_emulator& win_emu, std::set<std::string, std::less<>> modules, const bool verbose, const bool concise)
|
||||
: win_emu_(win_emu),
|
||||
env_data_hook_(win_emu.emu()),
|
||||
env_ptr_hook_(win_emu.emu()),
|
||||
params_hook_(win_emu.emu()),
|
||||
ldr_hook_(win_emu.emu()),
|
||||
modules_(std::move(modules)),
|
||||
verbose_(verbose),
|
||||
concise_(concise)
|
||||
{
|
||||
}
|
||||
};
|
||||
auto* params_hook = watch_object(win_emu, modules, win_emu.process.process_params, cache_logging);
|
||||
|
||||
emulator_object<RTL_USER_PROCESS_PARAMETERS64> get_process_params(windows_emulator& win_emu)
|
||||
{
|
||||
const auto peb = win_emu.process.peb64.read();
|
||||
return {win_emu.emu(), peb.ProcessParameters};
|
||||
}
|
||||
|
||||
uint64_t get_environment_ptr(windows_emulator& win_emu)
|
||||
{
|
||||
const auto process_params = get_process_params(win_emu);
|
||||
return process_params.read().Environment;
|
||||
}
|
||||
|
||||
size_t get_environment_size(const x86_64_emulator& emu, const uint64_t env)
|
||||
{
|
||||
std::array<uint8_t, 4> data{};
|
||||
std::array<uint8_t, 4> empty{};
|
||||
|
||||
for (size_t i = 0; i < 0x100000; ++i)
|
||||
{
|
||||
if (!emu.try_read_memory(env + i, data.data(), data.size()))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
if (data == empty)
|
||||
{
|
||||
return i + data.size();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
emulator_hook* install_env_hook(const std::shared_ptr<analysis_state>& state)
|
||||
{
|
||||
const auto process_params = get_process_params(state->win_emu_);
|
||||
|
||||
auto install_env_access_hook = [state] {
|
||||
const auto env_ptr = get_environment_ptr(state->win_emu_);
|
||||
const auto env_size = get_environment_size(state->win_emu_.emu(), env_ptr);
|
||||
if (!env_size)
|
||||
{
|
||||
state->env_data_hook_.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
auto hook_handler = [state, env_ptr](const uint64_t address, const void*, const size_t size) {
|
||||
const auto rip = state->win_emu_.emu().read_instruction_pointer();
|
||||
const auto* mod = state->win_emu_.mod_manager.find_by_address(rip);
|
||||
const auto is_main_access = !mod || (mod == state->win_emu_.mod_manager.executable || state->modules_.contains(mod->name));
|
||||
|
||||
if (!is_main_access && !state->verbose_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (state->concise_)
|
||||
{
|
||||
const auto count = ++state->env_module_cache_[mod->name];
|
||||
if (count > 100 && count % 1000 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto offset = address - env_ptr;
|
||||
const auto* mod_name = mod ? mod->name.c_str() : "<N/A>";
|
||||
state->win_emu_.log.print(is_main_access ? color::green : color::dark_gray,
|
||||
"Environment access: 0x%" PRIx64 " (0x%zX) at 0x%" PRIx64 " (%s)\n", offset, size, rip, mod_name);
|
||||
};
|
||||
|
||||
state->env_data_hook_ = state->win_emu_.emu().hook_memory_read(env_ptr, env_size, std::move(hook_handler));
|
||||
};
|
||||
|
||||
install_env_access_hook();
|
||||
|
||||
auto& win_emu = state->win_emu_;
|
||||
return state->win_emu_.emu().hook_memory_write(
|
||||
process_params.value() + offsetof(RTL_USER_PROCESS_PARAMETERS64, Environment), 0x8,
|
||||
[&win_emu, install = std::move(install_env_access_hook)](const uint64_t address, const void*, size_t) {
|
||||
const auto new_process_params = get_process_params(win_emu);
|
||||
|
||||
const auto target_address = new_process_params.value() + offsetof(RTL_USER_PROCESS_PARAMETERS64, Environment);
|
||||
win_emu.emu().hook_memory_write(
|
||||
win_emu.process.peb.value() + offsetof(PEB64, ProcessParameters), 0x8,
|
||||
[&win_emu, cache_logging, params_hook, modules](const uint64_t address, const void*, size_t) mutable {
|
||||
const auto target_address = win_emu.process.peb.value() + offsetof(PEB64, ProcessParameters);
|
||||
|
||||
if (address == target_address)
|
||||
{
|
||||
install();
|
||||
const emulator_object<RTL_USER_PROCESS_PARAMETERS64> obj{
|
||||
win_emu.emu(),
|
||||
win_emu.emu().read_memory<uint64_t>(address),
|
||||
};
|
||||
|
||||
win_emu.emu().delete_hook(params_hook);
|
||||
params_hook = watch_object(win_emu, modules, obj, cache_logging);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
void watch_system_objects(windows_emulator& win_emu, const std::set<std::string, std::less<>>& modules, const bool verbose,
|
||||
const bool concise)
|
||||
{
|
||||
win_emu.setup_process_if_necessary();
|
||||
|
||||
(void)win_emu;
|
||||
(void)modules;
|
||||
(void)verbose;
|
||||
(void)concise;
|
||||
|
||||
#if !defined(__GNUC__) || defined(__clang__)
|
||||
watch_object(win_emu, modules, *win_emu.current_thread().teb64, verbose);
|
||||
watch_object(win_emu, modules, win_emu.process.peb64, verbose);
|
||||
watch_object<KUSER_SHARED_DATA64>(win_emu, modules, kusd_mmio::address(), verbose);
|
||||
|
||||
auto state = std::make_shared<analysis_state>(win_emu, modules, verbose, concise);
|
||||
|
||||
state->params_hook_ = watch_object(win_emu, modules, win_emu.process.process_params64, verbose, state->params_state_);
|
||||
state->ldr_hook_ = watch_object<PEB_LDR_DATA64>(win_emu, modules, win_emu.process.peb64.read().Ldr, verbose, state->ldr_state_);
|
||||
|
||||
const auto update_env_hook = [state] {
|
||||
state->env_ptr_hook_ = install_env_hook(state); //
|
||||
};
|
||||
|
||||
update_env_hook();
|
||||
|
||||
win_emu.emu().hook_memory_write(win_emu.process.peb64.value() + offsetof(PEB64, ProcessParameters), 0x8,
|
||||
[state, update_env = std::move(update_env_hook)](const uint64_t, const void*, size_t) {
|
||||
const auto new_ptr = state->win_emu_.process.peb64.read().ProcessParameters;
|
||||
state->params_hook_ = watch_object<RTL_USER_PROCESS_PARAMETERS64>(
|
||||
state->win_emu_, state->modules_, new_ptr, state->verbose_, state->params_state_);
|
||||
update_env();
|
||||
});
|
||||
|
||||
win_emu.emu().hook_memory_write(
|
||||
win_emu.process.peb64.value() + offsetof(PEB64, Ldr), 0x8, [state](const uint64_t, const void*, size_t) {
|
||||
const auto new_ptr = state->win_emu_.process.peb64.read().Ldr;
|
||||
state->ldr_hook_ =
|
||||
watch_object<PEB_LDR_DATA64>(state->win_emu_, state->modules_, new_ptr, state->verbose_, state->ldr_state_);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -238,87 +104,34 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void print_instruction_summary(const analysis_context& c)
|
||||
bool run_emulation(windows_emulator& win_emu, const analysis_options& options)
|
||||
{
|
||||
std::map<uint64_t, std::vector<uint32_t>> instruction_counts{};
|
||||
|
||||
for (const auto& [instruction, count] : c.instructions)
|
||||
{
|
||||
instruction_counts[count].push_back(instruction);
|
||||
}
|
||||
|
||||
c.win_emu->log.print(color::white, "Instruction summary:\n");
|
||||
|
||||
for (const auto& [count, instructions] : instruction_counts)
|
||||
{
|
||||
for (const auto& instruction : instructions)
|
||||
{
|
||||
const auto& e = c.win_emu;
|
||||
auto& emu = e->emu();
|
||||
const auto reg_cs = emu.reg<uint16_t>(x86_register::cs);
|
||||
const auto handle = c.d.resolve_handle(emu, reg_cs);
|
||||
const auto* mnemonic = cs_insn_name(handle, instruction);
|
||||
c.win_emu->log.print(color::white, "%s: %" PRIu64 "\n", mnemonic, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void do_post_emulation_work(const analysis_context& c)
|
||||
{
|
||||
if (c.settings->instruction_summary)
|
||||
{
|
||||
print_instruction_summary(c);
|
||||
}
|
||||
|
||||
if (c.settings->buffer_stdout)
|
||||
{
|
||||
c.win_emu->log.info("%.*s%s", static_cast<int>(c.output.size()), c.output.data(), c.output.ends_with("\n") ? "" : "\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool run_emulation(const analysis_context& c, const analysis_options& options)
|
||||
{
|
||||
auto& win_emu = *c.win_emu;
|
||||
|
||||
std::atomic_uint32_t signals_received{0};
|
||||
utils::interupt_handler _{[&] {
|
||||
const auto value = signals_received++;
|
||||
if (value == 1)
|
||||
{
|
||||
win_emu.log.log("Exit already requested. Press CTRL+C again to force kill!\n");
|
||||
win_emu.log.log("Exit already requested. Press CTRL+C again to force kill!");
|
||||
}
|
||||
else if (value >= 2)
|
||||
{
|
||||
_Exit(1);
|
||||
}
|
||||
|
||||
win_emu.stop();
|
||||
win_emu.emu().stop();
|
||||
}};
|
||||
|
||||
std::optional<NTSTATUS> exit_status{};
|
||||
#if defined(OS_EMSCRIPTEN) && !defined(MOMO_EMSCRIPTEN_SUPPORT_NODEJS)
|
||||
const auto _1 = utils::finally([&] {
|
||||
debugger::handle_exit(win_emu, exit_status); //
|
||||
});
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
if (options.use_gdb)
|
||||
{
|
||||
const auto* address = "127.0.0.1:28960";
|
||||
win_emu.log.force_print(color::pink, "Waiting for GDB connection on %s...\n", address);
|
||||
win_emu.log.print(color::pink, "Waiting for GDB connection on %s...\n", address);
|
||||
|
||||
const auto should_stop = [&] { return signals_received > 0; };
|
||||
|
||||
win_x64_gdb_stub_handler handler{win_emu, should_stop};
|
||||
gdb_stub::run_gdb_stub(network::address{address, AF_INET}, handler);
|
||||
}
|
||||
else if (!options.minidump_path.empty())
|
||||
{
|
||||
// For minidumps, don't start execution automatically; just report ready state
|
||||
win_emu.log.print(color::green, "Minidump loaded successfully. Process state ready for analysis.\n");
|
||||
return true; // Return success without starting emulation
|
||||
gdb_stub::run_gdb_stub(network::address{"0.0.0.0:28960", AF_INET}, handler);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -340,21 +153,19 @@ namespace
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
do_post_emulation_work(c);
|
||||
win_emu.log.error("Emulation failed at: 0x%" PRIx64 " - %s\n", win_emu.emu().read_instruction_pointer(), e.what());
|
||||
win_emu.log.error("Emulation failed at: 0x%" PRIx64 " - %s\n", win_emu.emu().read_instruction_pointer(),
|
||||
e.what());
|
||||
throw;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
do_post_emulation_work(c);
|
||||
win_emu.log.error("Emulation failed at: 0x%" PRIx64 "\n", win_emu.emu().read_instruction_pointer());
|
||||
throw;
|
||||
}
|
||||
|
||||
exit_status = win_emu.process.exit_status;
|
||||
const auto exit_status = win_emu.process.exit_status;
|
||||
if (!exit_status.has_value())
|
||||
{
|
||||
do_post_emulation_work(c);
|
||||
win_emu.log.error("Emulation terminated without status!\n");
|
||||
return false;
|
||||
}
|
||||
@@ -363,9 +174,9 @@ namespace
|
||||
|
||||
if (!options.silent)
|
||||
{
|
||||
do_post_emulation_work(c);
|
||||
win_emu.log.disable_output(false);
|
||||
win_emu.log.print(success ? color::green : color::red, "Emulation terminated with status: %X\n", *exit_status);
|
||||
win_emu.log.print(success ? color::green : color::red, "Emulation terminated with status: %X\n",
|
||||
*exit_status);
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -390,14 +201,19 @@ namespace
|
||||
return {
|
||||
.emulation_root = options.emulation_root,
|
||||
.registry_directory = options.registry_path,
|
||||
.verbose_calls = options.verbose_logging,
|
||||
.disable_logging = options.silent,
|
||||
.silent_until_main = options.concise_logging,
|
||||
.path_mappings = options.path_mappings,
|
||||
.modules = options.modules,
|
||||
.ignored_functions = options.ignored_functions,
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<windows_emulator> create_empty_emulator(const analysis_options& options)
|
||||
{
|
||||
const auto settings = create_emulator_settings(options);
|
||||
return std::make_unique<windows_emulator>(create_x86_64_emulator(), settings);
|
||||
return std::make_unique<windows_emulator>(settings);
|
||||
}
|
||||
|
||||
std::unique_ptr<windows_emulator> create_application_emulator(const analysis_options& options,
|
||||
@@ -414,207 +230,98 @@ namespace
|
||||
};
|
||||
|
||||
const auto settings = create_emulator_settings(options);
|
||||
return std::make_unique<windows_emulator>(create_x86_64_emulator(), std::move(app_settings), settings);
|
||||
return std::make_unique<windows_emulator>(std::move(app_settings), settings);
|
||||
}
|
||||
|
||||
std::unique_ptr<windows_emulator> setup_emulator(const analysis_options& options, const std::span<const std::string_view> args)
|
||||
std::unique_ptr<windows_emulator> setup_emulator(const analysis_options& options,
|
||||
const std::span<const std::string_view> args)
|
||||
{
|
||||
if (!options.dump.empty())
|
||||
if (options.dump.empty())
|
||||
{
|
||||
// load snapshot
|
||||
auto win_emu = create_empty_emulator(options);
|
||||
snapshot::load_emulator_snapshot(*win_emu, options.dump);
|
||||
return win_emu;
|
||||
}
|
||||
if (!options.minidump_path.empty())
|
||||
{
|
||||
// load minidump
|
||||
auto win_emu = create_empty_emulator(options);
|
||||
minidump_loader::load_minidump_into_emulator(*win_emu, options.minidump_path);
|
||||
return win_emu;
|
||||
return create_application_emulator(options, args);
|
||||
}
|
||||
|
||||
// default: load application
|
||||
return create_application_emulator(options, args);
|
||||
}
|
||||
|
||||
const char* get_module_memory_region_name(const mapped_module& mod, const uint64_t address)
|
||||
{
|
||||
if (!mod.contains(address))
|
||||
{
|
||||
return "outside???";
|
||||
}
|
||||
|
||||
uint64_t first_section = mod.image_base + mod.size_of_image;
|
||||
|
||||
for (const auto& section : mod.sections)
|
||||
{
|
||||
first_section = std::min(first_section, section.region.start);
|
||||
|
||||
if (is_within_start_and_length(address, section.region.start, section.region.length))
|
||||
{
|
||||
return section.name.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
if (address < first_section)
|
||||
{
|
||||
return "header";
|
||||
}
|
||||
|
||||
return "?";
|
||||
auto win_emu = create_empty_emulator(options);
|
||||
snapshot::load_emulator_snapshot(*win_emu, options.dump);
|
||||
return win_emu;
|
||||
}
|
||||
|
||||
bool run(const analysis_options& options, const std::span<const std::string_view> args)
|
||||
{
|
||||
analysis_context context{
|
||||
.settings = &options,
|
||||
};
|
||||
|
||||
const auto concise_logging = options.concise_logging;
|
||||
|
||||
const auto win_emu = setup_emulator(options, args);
|
||||
context.win_emu = win_emu.get();
|
||||
win_emu->log.log("Using emulator: %s\n", win_emu->emu().get_name().c_str());
|
||||
|
||||
win_emu->log.disable_output(concise_logging || options.silent);
|
||||
(void)&watch_system_objects;
|
||||
watch_system_objects(*win_emu, options.modules, !options.verbose_logging);
|
||||
win_emu->buffer_stdout = options.buffer_stdout;
|
||||
|
||||
if (!options.silent)
|
||||
if (options.silent)
|
||||
{
|
||||
win_emu->log.force_print(color::gray, "Using emulator backend: %s\n", win_emu->emu().get_name().c_str());
|
||||
win_emu->buffer_stdout = false;
|
||||
win_emu->callbacks.on_stdout = [](const std::string_view data) {
|
||||
(void)fwrite(data.data(), 1, data.size(), stdout);
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<tenet_tracer> tenet_tracer{};
|
||||
if (options.tenet_trace)
|
||||
{
|
||||
win_emu->log.log("Tenet Tracer enabled. Output: tenet_trace.log\n");
|
||||
tenet_tracer.emplace(*win_emu, "tenet_trace.log");
|
||||
}
|
||||
|
||||
register_analysis_callbacks(context);
|
||||
watch_system_objects(*win_emu, options.modules, options.verbose_logging, options.concise_logging);
|
||||
|
||||
const auto& exe = *win_emu->mod_manager.executable;
|
||||
|
||||
win_emu->emu().hook_instruction(x86_hookable_instructions::cpuid, [&] {
|
||||
auto& emu = win_emu->emu();
|
||||
const auto concise_logging = !options.verbose_logging;
|
||||
|
||||
const auto rip = emu.read_instruction_pointer();
|
||||
const auto leaf = emu.reg<uint32_t>(x86_register::eax);
|
||||
const auto mod = get_module_if_interesting(win_emu->mod_manager, options.modules, rip);
|
||||
|
||||
if (mod.has_value() && (!concise_logging || context.cpuid_cache.insert({rip, leaf}).second))
|
||||
for (const auto& section : exe.sections)
|
||||
{
|
||||
if ((section.region.permissions & memory_permission::exec) != memory_permission::exec)
|
||||
{
|
||||
win_emu->log.print(color::blue, "Executing CPUID instruction with leaf 0x%X at 0x%" PRIx64 " (%s)\n", leaf, rip,
|
||||
(*mod) ? (*mod)->name.c_str() : "<N/A>");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (leaf == 1)
|
||||
{
|
||||
// NOTE: We hard-code these values to disable SSE4.x
|
||||
emu.reg<uint32_t>(x86_register::eax, 0x000906EA);
|
||||
emu.reg<uint32_t>(x86_register::ebx, 0x00100800);
|
||||
emu.reg<uint32_t>(x86_register::ecx, 0xFFE2F38F);
|
||||
emu.reg<uint32_t>(x86_register::edx, 0xBFEBFBFF);
|
||||
|
||||
return instruction_hook_continuation::skip_instruction;
|
||||
}
|
||||
|
||||
return instruction_hook_continuation::run_instruction;
|
||||
});
|
||||
|
||||
if (options.log_foreign_module_access)
|
||||
{
|
||||
auto module_cache = std::make_shared<std::map<std::string, uint64_t>>();
|
||||
win_emu->emu().hook_memory_read(
|
||||
0, std::numeric_limits<uint64_t>::max(), [&, module_cache](const uint64_t address, const void*, size_t) {
|
||||
const auto rip = win_emu->emu().read_instruction_pointer();
|
||||
const auto accessor = get_module_if_interesting(win_emu->mod_manager, options.modules, rip);
|
||||
|
||||
if (!accessor.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* mod = win_emu->mod_manager.find_by_address(address);
|
||||
if (!mod || mod == *accessor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (concise_logging)
|
||||
{
|
||||
const auto count = ++(*module_cache)[mod->name];
|
||||
if (count > 100 && count % 100000 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto* region_name = get_module_memory_region_name(*mod, address);
|
||||
|
||||
win_emu->log.print(color::pink, "Reading from module %s at 0x%" PRIx64 " (%s) via 0x%" PRIx64 " (%s)\n",
|
||||
mod->name.c_str(), address, region_name, rip, (*accessor) ? (*accessor)->name.c_str() : "<N/A>");
|
||||
});
|
||||
}
|
||||
|
||||
if (options.log_executable_access)
|
||||
{
|
||||
for (const auto& section : exe.sections)
|
||||
{
|
||||
if ((section.region.permissions & memory_permission::exec) != memory_permission::exec)
|
||||
auto read_handler = [&, section, concise_logging](const uint64_t address, const void*, size_t) {
|
||||
const auto rip = win_emu->emu().read_instruction_pointer();
|
||||
if (!win_emu->mod_manager.executable->is_within(rip))
|
||||
{
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto read_count = std::make_shared<uint64_t>(0);
|
||||
const auto write_count = std::make_shared<uint64_t>(0);
|
||||
|
||||
auto read_handler = [&, section, concise_logging, read_count](const uint64_t address, const void*, size_t) {
|
||||
const auto rip = win_emu->emu().read_instruction_pointer();
|
||||
if (!win_emu->mod_manager.executable->contains(rip))
|
||||
if (concise_logging)
|
||||
{
|
||||
static uint64_t count{0};
|
||||
++count;
|
||||
if (count > 100 && count % 100000 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (concise_logging)
|
||||
{
|
||||
const auto count = ++*read_count;
|
||||
if (count > 20 && count % 100000 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
win_emu->log.print(color::green,
|
||||
"Reading from executable section %s at 0x%" PRIx64 " via 0x%" PRIx64 "\n",
|
||||
section.name.c_str(), address, rip);
|
||||
};
|
||||
|
||||
win_emu->log.print(color::green, "Reading from executable section %s at 0x%" PRIx64 " via 0x%" PRIx64 "\n",
|
||||
section.name.c_str(), address, rip);
|
||||
};
|
||||
const auto write_handler = [&, section, concise_logging](const uint64_t address, const void*, size_t) {
|
||||
const auto rip = win_emu->emu().read_instruction_pointer();
|
||||
if (!win_emu->mod_manager.executable->is_within(rip))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto write_handler = [&, section, concise_logging, write_count](const uint64_t address, const void*, size_t) {
|
||||
const auto rip = win_emu->emu().read_instruction_pointer();
|
||||
if (!win_emu->mod_manager.executable->contains(rip))
|
||||
if (concise_logging)
|
||||
{
|
||||
static uint64_t count{0};
|
||||
++count;
|
||||
if (count > 100 && count % 100000 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (concise_logging)
|
||||
{
|
||||
const auto count = ++*write_count;
|
||||
if (count > 100 && count % 100000 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
win_emu->log.print(color::blue, "Writing to executable section %s at 0x%" PRIx64 " via 0x%" PRIx64 "\n",
|
||||
section.name.c_str(), address, rip);
|
||||
};
|
||||
|
||||
win_emu->log.print(color::blue, "Writing to executable section %s at 0x%" PRIx64 " via 0x%" PRIx64 "\n",
|
||||
section.name.c_str(), address, rip);
|
||||
};
|
||||
|
||||
win_emu->emu().hook_memory_read(section.region.start, section.region.length, std::move(read_handler));
|
||||
win_emu->emu().hook_memory_write(section.region.start, section.region.length, std::move(write_handler));
|
||||
}
|
||||
win_emu->emu().hook_memory_read(section.region.start, section.region.length, std::move(read_handler));
|
||||
win_emu->emu().hook_memory_write(section.region.start, section.region.length, std::move(write_handler));
|
||||
}
|
||||
|
||||
return run_emulation(context, options);
|
||||
return run_emulation(*win_emu, options);
|
||||
}
|
||||
|
||||
std::vector<std::string_view> bundle_arguments(const int argc, char** argv)
|
||||
@@ -629,33 +336,6 @@ namespace
|
||||
return args;
|
||||
}
|
||||
|
||||
void print_help()
|
||||
{
|
||||
printf("Usage: analyzer [options] [application] [args...]\n\n");
|
||||
printf("Options:\n");
|
||||
printf(" -h, --help Show this help message\n");
|
||||
printf(" -d, --debug Enable GDB debugging mode\n");
|
||||
printf(" -s, --silent Silent mode\n");
|
||||
printf(" -v, --verbose Verbose logging\n");
|
||||
printf(" -b, --buffer Buffer stdout\n");
|
||||
printf(" -f, --foreign Log read access to foreign modules\n");
|
||||
printf(" -c, --concise Concise logging\n");
|
||||
printf(" -x, --exec Log r/w access to executable memory\n");
|
||||
printf(" -m, --module <module> Specify module to track\n");
|
||||
printf(" -e, --emulation <path> Set emulation root path\n");
|
||||
printf(" -a, --snapshot <path> Load snapshot dump from path\n");
|
||||
printf(" --minidump <path> Load minidump from path\n");
|
||||
printf(" -t, --tenet-trace Enable Tenet tracer\n");
|
||||
printf(" -i, --ignore <funcs> Comma-separated list of functions to ignore\n");
|
||||
printf(" -p, --path <src> <dst> Map Windows path to host path\n");
|
||||
printf(" -r, --registry <path> Set registry path (default: ./registry)\n\n");
|
||||
printf(" -is, --inst-summary Print a summary of executed instructions of the analyzed modules\n");
|
||||
printf(" -ss, --skip-syscalls Skip the logging of regular syscalls\n");
|
||||
printf("Examples:\n");
|
||||
printf(" analyzer -v -e path/to/root myapp.exe\n");
|
||||
printf(" analyzer -e path/to/root -p c:/analysis-sample.exe /path/to/sample.exe c:/analysis-sample.exe\n");
|
||||
}
|
||||
|
||||
analysis_options parse_options(std::vector<std::string_view>& args)
|
||||
{
|
||||
analysis_options options{};
|
||||
@@ -665,103 +345,68 @@ namespace
|
||||
auto arg_it = args.begin();
|
||||
const auto& arg = *arg_it;
|
||||
|
||||
if (arg == "-h" || arg == "--help")
|
||||
{
|
||||
print_help();
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
if (arg == "-d" || arg == "--debug")
|
||||
if (arg == "-d")
|
||||
{
|
||||
options.use_gdb = true;
|
||||
}
|
||||
else if (arg == "-s" || arg == "--silent")
|
||||
else if (arg == "-s")
|
||||
{
|
||||
options.silent = true;
|
||||
}
|
||||
else if (arg == "-v" || arg == "--verbose")
|
||||
else if (arg == "-v")
|
||||
{
|
||||
options.verbose_logging = true;
|
||||
}
|
||||
else if (arg == "-b" || arg == "--buffer")
|
||||
else if (arg == "-b")
|
||||
{
|
||||
options.buffer_stdout = true;
|
||||
}
|
||||
else if (arg == "-x" || arg == "--exec")
|
||||
{
|
||||
options.log_executable_access = true;
|
||||
}
|
||||
else if (arg == "-f" || arg == "--foreign")
|
||||
{
|
||||
options.log_foreign_module_access = true;
|
||||
}
|
||||
else if (arg == "-c" || arg == "--concise")
|
||||
else if (arg == "-c")
|
||||
{
|
||||
options.concise_logging = true;
|
||||
}
|
||||
else if (arg == "-t" || arg == "--tenet-trace")
|
||||
{
|
||||
options.tenet_trace = true;
|
||||
}
|
||||
else if (arg == "-is" || arg == "--inst-summary")
|
||||
{
|
||||
options.instruction_summary = true;
|
||||
}
|
||||
else if (arg == "-ss" || arg == "--skip-syscalls")
|
||||
{
|
||||
options.skip_syscalls = true;
|
||||
}
|
||||
else if (arg == "-m" || arg == "--module")
|
||||
else if (arg == "-m")
|
||||
{
|
||||
if (args.size() < 2)
|
||||
{
|
||||
throw std::runtime_error("No module provided after -m/--module");
|
||||
throw std::runtime_error("No module provided after -m");
|
||||
}
|
||||
|
||||
arg_it = args.erase(arg_it);
|
||||
options.modules.insert(std::string(args[0]));
|
||||
}
|
||||
else if (arg == "-e" || arg == "--emulation")
|
||||
else if (arg == "-e")
|
||||
{
|
||||
if (args.size() < 2)
|
||||
{
|
||||
throw std::runtime_error("No emulation root path provided after -e/--emulation");
|
||||
throw std::runtime_error("No emulation root path provided after -e");
|
||||
}
|
||||
arg_it = args.erase(arg_it);
|
||||
options.emulation_root = args[0];
|
||||
}
|
||||
else if (arg == "-a" || arg == "--snapshot")
|
||||
else if (arg == "-a")
|
||||
{
|
||||
if (args.size() < 2)
|
||||
{
|
||||
throw std::runtime_error("No dump path provided after -a/--snapshot");
|
||||
throw std::runtime_error("No dump path provided after -a");
|
||||
}
|
||||
arg_it = args.erase(arg_it);
|
||||
options.dump = args[0];
|
||||
}
|
||||
else if (arg == "--minidump")
|
||||
else if (arg == "-i")
|
||||
{
|
||||
if (args.size() < 2)
|
||||
{
|
||||
throw std::runtime_error("No minidump path provided after --minidump");
|
||||
}
|
||||
arg_it = args.erase(arg_it);
|
||||
options.minidump_path = args[0];
|
||||
}
|
||||
else if (arg == "-i" || arg == "--ignore")
|
||||
{
|
||||
if (args.size() < 2)
|
||||
{
|
||||
throw std::runtime_error("No ignored function(s) provided after -i/--ignore");
|
||||
throw std::runtime_error("No ignored function(s) provided after -i");
|
||||
}
|
||||
arg_it = args.erase(arg_it);
|
||||
split_and_insert(options.ignored_functions, args[0]);
|
||||
}
|
||||
else if (arg == "-p" || arg == "--path")
|
||||
else if (arg == "-p")
|
||||
{
|
||||
if (args.size() < 3)
|
||||
{
|
||||
throw std::runtime_error("No path mapping provided after -p/--path");
|
||||
throw std::runtime_error("No path mapping provided after -p");
|
||||
}
|
||||
arg_it = args.erase(arg_it);
|
||||
windows_path source = args[0];
|
||||
@@ -770,11 +415,11 @@ namespace
|
||||
|
||||
options.path_mappings[std::move(source)] = std::move(target);
|
||||
}
|
||||
else if (arg == "-r" || arg == "--registry")
|
||||
else if (arg == "-r")
|
||||
{
|
||||
if (args.size() < 2)
|
||||
{
|
||||
throw std::runtime_error("No registry path provided after -r/--registry");
|
||||
throw std::runtime_error("No registry path provided after -r");
|
||||
}
|
||||
arg_it = args.erase(arg_it);
|
||||
options.registry_path = args[0];
|
||||
@@ -789,50 +434,44 @@ namespace
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
int run_main(const int argc, char** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto args = bundle_arguments(argc, argv);
|
||||
if (args.empty())
|
||||
{
|
||||
print_help();
|
||||
return 1;
|
||||
}
|
||||
|
||||
const auto options = parse_options(args);
|
||||
|
||||
bool result{};
|
||||
|
||||
do
|
||||
{
|
||||
result = run(options, args);
|
||||
} while (options.use_gdb);
|
||||
|
||||
return result ? 0 : 1;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
puts(e.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
puts("An unknown exception occured");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int main(const int argc, char** argv)
|
||||
{
|
||||
return run_main(argc, argv);
|
||||
try
|
||||
{
|
||||
auto args = bundle_arguments(argc, argv);
|
||||
const auto options = parse_options(args);
|
||||
|
||||
if (args.empty() && options.dump.empty())
|
||||
{
|
||||
throw std::runtime_error("Application not specified!");
|
||||
}
|
||||
|
||||
bool result{};
|
||||
|
||||
do
|
||||
{
|
||||
result = run(options, args);
|
||||
} while (options.use_gdb);
|
||||
|
||||
return result ? 0 : 1;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
puts(e.what());
|
||||
|
||||
#if defined(_WIN32) && 0
|
||||
MessageBoxA(nullptr, e.what(), "ERROR", MB_ICONERROR);
|
||||
#endif
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR, int)
|
||||
{
|
||||
return run_main(__argc, __argv);
|
||||
return main(__argc, __argv);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2,82 +2,42 @@
|
||||
|
||||
#include "reflect_type_info.hpp"
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <cinttypes>
|
||||
|
||||
struct object_watching_state
|
||||
{
|
||||
std::unordered_set<uint64_t> logged_addresses{};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
emulator_hook* watch_object(windows_emulator& emu, const std::set<std::string, std::less<>>& modules, emulator_object<T> object,
|
||||
const auto verbose,
|
||||
std::shared_ptr<object_watching_state> shared_state = std::make_unique<object_watching_state>())
|
||||
emulator_hook* watch_object(windows_emulator& emu, const std::set<std::string, std::less<>>& modules,
|
||||
emulator_object<T> object, const bool cache_logging = false)
|
||||
{
|
||||
const reflect_type_info<T> info{};
|
||||
|
||||
return emu.emu().hook_memory_read(
|
||||
object.value(), static_cast<size_t>(object.size()),
|
||||
[i = std::move(info), object, &emu, verbose, modules, state = std::move(shared_state)](const uint64_t address, const void*,
|
||||
const size_t size) {
|
||||
[i = std::move(info), object, &emu, cache_logging, modules](const uint64_t address, const void*, size_t) {
|
||||
const auto rip = emu.emu().read_instruction_pointer();
|
||||
const auto* mod = emu.mod_manager.find_by_address(rip);
|
||||
const auto is_main_access = !mod || (mod == emu.mod_manager.executable || modules.contains(mod->name));
|
||||
const auto is_main_access = mod == emu.mod_manager.executable || modules.contains(mod->name);
|
||||
|
||||
if (!verbose && !is_main_access)
|
||||
if (!emu.verbose_calls && !is_main_access)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!verbose)
|
||||
if (cache_logging)
|
||||
{
|
||||
bool is_new = false;
|
||||
for (size_t j = 0; j < size; ++j)
|
||||
{
|
||||
is_new |= state->logged_addresses.insert(address + j).second;
|
||||
}
|
||||
|
||||
if (!is_new)
|
||||
static std::unordered_set<uint64_t> logged_addresses{};
|
||||
if (is_main_access && !logged_addresses.insert(address).second)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto start_offset = address - object.value();
|
||||
const auto end_offset = start_offset + size;
|
||||
const auto offset = address - object.value();
|
||||
const auto* mod_name = mod ? mod->name.c_str() : "<N/A>";
|
||||
const auto& type_name = i.get_type_name();
|
||||
const auto member_name = i.get_member_name(static_cast<size_t>(offset));
|
||||
|
||||
for (auto offset = start_offset; offset < end_offset;)
|
||||
{
|
||||
const auto member_info = i.get_member_info(static_cast<size_t>(offset));
|
||||
if (!member_info.has_value())
|
||||
{
|
||||
const auto remaining_size = end_offset - offset;
|
||||
emu.log.print(is_main_access ? color::green : color::dark_gray,
|
||||
"Object access: %s - 0x%" PRIx64 " 0x%" PRIx64 " (<N/A>) at 0x%" PRIx64 " (%s)\n", type_name.c_str(),
|
||||
offset, remaining_size, rip, mod_name);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto remaining_size = end_offset - offset;
|
||||
const auto member_end = member_info->offset + member_info->size;
|
||||
const auto member_access_size = member_end - offset;
|
||||
const auto access_size = std::min(remaining_size, member_access_size);
|
||||
|
||||
emu.log.print(is_main_access ? color::green : color::dark_gray,
|
||||
"Object access: %s - 0x%" PRIx64 " 0x%" PRIx64 " (%s) at 0x%" PRIx64 " (%s)\n", type_name.c_str(), offset,
|
||||
access_size, member_info->get_diff_name(static_cast<size_t>(offset)).c_str(), rip, mod_name);
|
||||
|
||||
offset = member_end;
|
||||
}
|
||||
emu.log.print(is_main_access ? color::green : color::dark_gray,
|
||||
"Object access: %s - 0x%" PRIx64 " (%s) at 0x%" PRIx64 " (%s)\n", type_name.c_str(), offset,
|
||||
member_name.c_str(), rip, mod_name);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
emulator_hook* watch_object(windows_emulator& emu, const std::set<std::string, std::less<>>& modules, const uint64_t address,
|
||||
const auto verbose, std::shared_ptr<object_watching_state> state = std::make_unique<object_watching_state>())
|
||||
{
|
||||
return watch_object<T>(emu, modules, emulator_object<T>{emu.emu(), address}, verbose, std::move(state));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,7 @@
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4189)
|
||||
#pragma warning(disable : 4308)
|
||||
#pragma warning(disable : 4715)
|
||||
#endif
|
||||
|
||||
#include "reflect_extension.hpp"
|
||||
@@ -44,61 +42,34 @@ class reflect_type_info
|
||||
reflect::for_each<T>([this](auto I) {
|
||||
const auto member_name = reflect::member_name<I, T>();
|
||||
const auto member_offset = reflect::offset_of<I, T>();
|
||||
const auto member_size = reflect::size_of<I, T>();
|
||||
|
||||
this->members_[member_offset] = std::make_pair(std::string(member_name), member_size);
|
||||
this->members_[member_offset] = member_name;
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_member_name(const size_t offset) const
|
||||
{
|
||||
const auto info = this->get_member_info(offset);
|
||||
if (!info.has_value())
|
||||
size_t last_offset{};
|
||||
std::string_view last_member{};
|
||||
|
||||
for (const auto& member : this->members_)
|
||||
{
|
||||
return "<N/A>";
|
||||
}
|
||||
|
||||
return info->get_diff_name(offset);
|
||||
}
|
||||
|
||||
struct member_info
|
||||
{
|
||||
std::string name{};
|
||||
size_t offset{};
|
||||
size_t size{};
|
||||
|
||||
std::string get_diff_name(const size_t access) const
|
||||
{
|
||||
const auto diff = access - this->offset;
|
||||
if (diff == 0)
|
||||
if (offset == member.first)
|
||||
{
|
||||
return this->name;
|
||||
return member.second;
|
||||
}
|
||||
|
||||
return this->name + "+" + std::to_string(diff);
|
||||
}
|
||||
};
|
||||
if (offset < member.first)
|
||||
{
|
||||
const auto diff = offset - last_offset;
|
||||
return std::string(last_member) + "+" + std::to_string(diff);
|
||||
}
|
||||
|
||||
std::optional<member_info> get_member_info(const size_t offset) const
|
||||
{
|
||||
auto entry = this->members_.upper_bound(offset);
|
||||
if (entry == this->members_.begin())
|
||||
{
|
||||
return std::nullopt;
|
||||
last_offset = member.first;
|
||||
last_member = member.second;
|
||||
}
|
||||
|
||||
--entry;
|
||||
|
||||
if (entry->first + entry->second.second <= offset)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return member_info{
|
||||
.name = entry->second.first,
|
||||
.offset = entry->first,
|
||||
.size = entry->second.second,
|
||||
};
|
||||
return "<N/A>";
|
||||
}
|
||||
|
||||
const std::string& get_type_name() const
|
||||
@@ -108,5 +79,5 @@ class reflect_type_info
|
||||
|
||||
private:
|
||||
std::string type_name_{};
|
||||
std::map<size_t, std::pair<std::string, size_t>> members_{};
|
||||
std::map<size_t, std::string> members_{};
|
||||
};
|
||||
|
||||
@@ -85,7 +85,8 @@ namespace snapshot
|
||||
|
||||
std::filesystem::path write_emulator_snapshot(const windows_emulator& win_emu, const bool log)
|
||||
{
|
||||
std::filesystem::path snapshot_file = get_main_executable_name(win_emu) + "-" + std::to_string(time(nullptr)) + ".snap";
|
||||
std::filesystem::path snapshot_file =
|
||||
get_main_executable_name(win_emu) + "-" + std::to_string(time(nullptr)) + ".snap";
|
||||
|
||||
if (log)
|
||||
{
|
||||
|
||||
@@ -6,16 +6,15 @@
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <ranges>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
@@ -24,7 +23,6 @@
|
||||
#include <unordered_set>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
|
||||
#include <platform/platform.hpp>
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
#include "std_include.hpp"
|
||||
#include "tenet_tracer.hpp"
|
||||
#include <utils/finally.hpp>
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string format_hex(uint64_t value)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "0x" << std::hex << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string format_byte_array(const uint8_t* data, size_t size)
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(data[i]);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void parse_and_accumulate_changes(const std::string& line, std::map<std::string, std::string>& changes)
|
||||
{
|
||||
size_t start = 0;
|
||||
while (start < line.length())
|
||||
{
|
||||
size_t end = line.find(',', start);
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
end = line.length();
|
||||
}
|
||||
|
||||
std::string pair_str = line.substr(start, end - start);
|
||||
size_t equals_pos = pair_str.find('=');
|
||||
if (equals_pos != std::string::npos)
|
||||
{
|
||||
std::string key = pair_str.substr(0, equals_pos);
|
||||
std::string value = pair_str.substr(equals_pos + 1);
|
||||
changes[key] = value;
|
||||
}
|
||||
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tenet_tracer::tenet_tracer(windows_emulator& win_emu, const std::filesystem::path& log_filename)
|
||||
: win_emu_(win_emu),
|
||||
log_file_(log_filename)
|
||||
{
|
||||
if (!log_file_)
|
||||
{
|
||||
throw std::runtime_error("TenetTracer: Failed to open log file -> " + log_filename.string());
|
||||
}
|
||||
|
||||
auto& emu = win_emu_.emu();
|
||||
|
||||
auto* read_hook = emu.hook_memory_read(0, 0xFFFFFFFFFFFFFFFF, [this](uint64_t a, const void* d, size_t s) {
|
||||
this->log_memory_read(a, d, s); //
|
||||
});
|
||||
read_hook_ = scoped_hook(emu, read_hook);
|
||||
|
||||
auto* write_hook = emu.hook_memory_write(0, 0xFFFFFFFFFFFFFFFF, [this](uint64_t a, const void* d, size_t s) {
|
||||
this->log_memory_write(a, d, s); //
|
||||
});
|
||||
write_hook_ = scoped_hook(emu, write_hook);
|
||||
|
||||
auto* execute_hook = emu.hook_memory_execution([&](uint64_t address) {
|
||||
this->process_instruction(address); //
|
||||
});
|
||||
execute_hook_ = scoped_hook(emu, execute_hook);
|
||||
}
|
||||
|
||||
tenet_tracer::~tenet_tracer()
|
||||
{
|
||||
filter_and_write_buffer();
|
||||
|
||||
if (log_file_.is_open())
|
||||
{
|
||||
log_file_.close();
|
||||
}
|
||||
}
|
||||
|
||||
void tenet_tracer::filter_and_write_buffer()
|
||||
{
|
||||
if (raw_log_buffer_.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* exe_module = win_emu_.mod_manager.executable;
|
||||
if (!exe_module)
|
||||
{
|
||||
for (const auto& line : raw_log_buffer_)
|
||||
{
|
||||
log_file_ << line << '\n';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!raw_log_buffer_.empty())
|
||||
{
|
||||
log_file_ << raw_log_buffer_.front() << '\n';
|
||||
}
|
||||
|
||||
bool currently_outside = false;
|
||||
std::map<std::string, std::string> accumulated_changes;
|
||||
|
||||
for (size_t i = 1; i < raw_log_buffer_.size(); ++i)
|
||||
{
|
||||
const auto& line = raw_log_buffer_[i];
|
||||
|
||||
size_t rip_pos = line.find("rip=0x");
|
||||
if (rip_pos == std::string::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
char* end_ptr = nullptr;
|
||||
uint64_t address = std::strtoull(line.c_str() + rip_pos + 6, &end_ptr, 16);
|
||||
|
||||
bool is_line_inside = exe_module->contains(address);
|
||||
const auto _1 = utils::finally([&] {
|
||||
currently_outside = !is_line_inside; //
|
||||
});
|
||||
|
||||
if (!is_line_inside)
|
||||
{
|
||||
parse_and_accumulate_changes(line, accumulated_changes);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto _2 = utils::finally([&] {
|
||||
log_file_ << line << '\n'; //
|
||||
});
|
||||
|
||||
if (!currently_outside || accumulated_changes.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::stringstream summary_line;
|
||||
bool first = true;
|
||||
|
||||
auto rip_it = accumulated_changes.find("rip");
|
||||
std::string last_rip;
|
||||
if (rip_it != accumulated_changes.end())
|
||||
{
|
||||
last_rip = rip_it->second;
|
||||
accumulated_changes.erase(rip_it);
|
||||
}
|
||||
|
||||
for (const auto& pair : accumulated_changes)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
summary_line << ",";
|
||||
}
|
||||
summary_line << pair.first << "=" << pair.second;
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (!last_rip.empty())
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
summary_line << ",";
|
||||
}
|
||||
summary_line << "rip=" << last_rip;
|
||||
}
|
||||
|
||||
log_file_ << summary_line.str() << '\n';
|
||||
accumulated_changes.clear();
|
||||
}
|
||||
|
||||
raw_log_buffer_.clear();
|
||||
}
|
||||
|
||||
void tenet_tracer::log_memory_read(uint64_t address, const void* data, size_t size)
|
||||
{
|
||||
if (!mem_read_log_.str().empty())
|
||||
{
|
||||
mem_read_log_ << ";";
|
||||
}
|
||||
|
||||
mem_read_log_ << format_hex(address) << ":" << format_byte_array(static_cast<const uint8_t*>(data), size);
|
||||
}
|
||||
|
||||
void tenet_tracer::log_memory_write(uint64_t address, const void* data, size_t size)
|
||||
{
|
||||
if (!mem_write_log_.str().empty())
|
||||
{
|
||||
mem_write_log_ << ";";
|
||||
}
|
||||
|
||||
mem_write_log_ << format_hex(address) << ":" << format_byte_array(static_cast<const uint8_t*>(data), size);
|
||||
}
|
||||
|
||||
void tenet_tracer::process_instruction(const uint64_t address)
|
||||
{
|
||||
auto& emu = win_emu_.emu();
|
||||
std::stringstream trace_line;
|
||||
|
||||
std::array<uint64_t, GPRs_TO_TRACE.size()> current_regs{};
|
||||
for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i)
|
||||
{
|
||||
current_regs[i] = emu.reg<uint64_t>(GPRs_TO_TRACE[i].first);
|
||||
}
|
||||
|
||||
bool first_entry = true;
|
||||
auto append_separator = [&] {
|
||||
if (!first_entry)
|
||||
{
|
||||
trace_line << ",";
|
||||
}
|
||||
first_entry = false;
|
||||
};
|
||||
|
||||
if (is_first_instruction_)
|
||||
{
|
||||
for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i)
|
||||
{
|
||||
append_separator();
|
||||
trace_line << GPRs_TO_TRACE[i].second << "=" << format_hex(current_regs[i]);
|
||||
}
|
||||
is_first_instruction_ = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i)
|
||||
{
|
||||
if (previous_registers_[i] != current_regs[i])
|
||||
{
|
||||
append_separator();
|
||||
trace_line << GPRs_TO_TRACE[i].second << "=" << format_hex(current_regs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
append_separator();
|
||||
trace_line << "rip=" << format_hex(address);
|
||||
|
||||
const auto mem_reads = mem_read_log_.str();
|
||||
if (!mem_reads.empty())
|
||||
{
|
||||
append_separator();
|
||||
trace_line << "mr=" << mem_reads;
|
||||
}
|
||||
|
||||
const auto mem_writes = mem_write_log_.str();
|
||||
if (!mem_writes.empty())
|
||||
{
|
||||
append_separator();
|
||||
trace_line << "mw=" << mem_writes;
|
||||
}
|
||||
|
||||
raw_log_buffer_.push_back(trace_line.str());
|
||||
previous_registers_ = current_regs;
|
||||
|
||||
mem_read_log_.str("");
|
||||
mem_read_log_.clear();
|
||||
mem_write_log_.str("");
|
||||
mem_write_log_.clear();
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows_emulator.hpp>
|
||||
#include <emulator/x86_register.hpp>
|
||||
#include <emulator/scoped_hook.hpp>
|
||||
|
||||
constexpr std::array<std::pair<x86_register, std::string_view>, 16> GPRs_TO_TRACE = {
|
||||
{
|
||||
{x86_register::rax, "rax"},
|
||||
{x86_register::rbx, "rbx"},
|
||||
{x86_register::rcx, "rcx"},
|
||||
{x86_register::rdx, "rdx"},
|
||||
{x86_register::rsi, "rsi"},
|
||||
{x86_register::rdi, "rdi"},
|
||||
{x86_register::rbp, "rbp"},
|
||||
{x86_register::rsp, "rsp"},
|
||||
{x86_register::r8, "r8"},
|
||||
{x86_register::r9, "r9"},
|
||||
{x86_register::r10, "r10"},
|
||||
{x86_register::r11, "r11"},
|
||||
{x86_register::r12, "r12"},
|
||||
{x86_register::r13, "r13"},
|
||||
{x86_register::r14, "r14"},
|
||||
{x86_register::r15, "r15"},
|
||||
},
|
||||
};
|
||||
|
||||
class tenet_tracer
|
||||
{
|
||||
public:
|
||||
tenet_tracer(windows_emulator& win_emu, const std::filesystem::path& log_filename);
|
||||
~tenet_tracer();
|
||||
|
||||
tenet_tracer(tenet_tracer&) = delete;
|
||||
tenet_tracer(const tenet_tracer&) = delete;
|
||||
tenet_tracer& operator=(tenet_tracer&) = delete;
|
||||
tenet_tracer& operator=(const tenet_tracer&) = delete;
|
||||
|
||||
private:
|
||||
void filter_and_write_buffer();
|
||||
void log_memory_read(uint64_t address, const void* data, size_t size);
|
||||
void log_memory_write(uint64_t address, const void* data, size_t size);
|
||||
void process_instruction(uint64_t address);
|
||||
|
||||
windows_emulator& win_emu_;
|
||||
std::ofstream log_file_;
|
||||
|
||||
std::vector<std::string> raw_log_buffer_;
|
||||
std::array<uint64_t, GPRs_TO_TRACE.size()> previous_registers_{};
|
||||
bool is_first_instruction_ = true;
|
||||
|
||||
std::stringstream mem_read_log_;
|
||||
std::stringstream mem_write_log_;
|
||||
|
||||
scoped_hook read_hook_;
|
||||
scoped_hook write_hook_;
|
||||
scoped_hook execute_hook_;
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
|
||||
*.cpp
|
||||
*.hpp
|
||||
*.rc
|
||||
)
|
||||
|
||||
list(SORT SRC_FILES)
|
||||
|
||||
add_library(backend-selection ${SRC_FILES})
|
||||
|
||||
momo_assign_source_group(${SRC_FILES})
|
||||
|
||||
target_include_directories(backend-selection INTERFACE "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
target_link_libraries(backend-selection PRIVATE
|
||||
unicorn-emulator
|
||||
)
|
||||
|
||||
if (MOMO_ENABLE_RUST)
|
||||
target_link_libraries(backend-selection PRIVATE
|
||||
icicle-emulator
|
||||
)
|
||||
endif()
|
||||
@@ -1,26 +0,0 @@
|
||||
#include "backend_selection.hpp"
|
||||
|
||||
#include <string_view>
|
||||
#include <unicorn_x86_64_emulator.hpp>
|
||||
|
||||
#if MOMO_ENABLE_RUST_CODE
|
||||
#include <icicle_x86_64_emulator.hpp>
|
||||
#endif
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
std::unique_ptr<x86_64_emulator> create_x86_64_emulator()
|
||||
{
|
||||
#if MOMO_ENABLE_RUST_CODE
|
||||
const auto* env = getenv("EMULATOR_ICICLE");
|
||||
if (env && (env == "1"sv || env == "true"sv))
|
||||
{
|
||||
// TODO: Add proper handling for WOW64 case (x64 -> x86 emulation is not supported yet).
|
||||
// icicle does not support automatic cross-architecture conversion from x64 to x86.
|
||||
// therefore WOW64 programs are naturally not supported to run.
|
||||
return icicle::create_x86_64_emulator();
|
||||
}
|
||||
#endif
|
||||
|
||||
return unicorn::create_x86_64_emulator();
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <arch_emulator.hpp>
|
||||
|
||||
std::unique_ptr<x86_64_emulator> create_x86_64_emulator();
|
||||
@@ -1,5 +1,5 @@
|
||||
add_subdirectory(unicorn-emulator)
|
||||
|
||||
if (MOMO_ENABLE_RUST)
|
||||
if (MOMO_ENABLE_RUST_CODE)
|
||||
add_subdirectory(icicle-emulator)
|
||||
endif()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user