Compare commits

..

4 Commits

Author SHA1 Message Date
robert-yates
2b76880cf1 partial process_context abstraction 2025-04-26 18:00:02 +02:00
robert-yates
60931da92a reduce process_context scope 2025-04-26 00:21:26 +02:00
robert-yates
4b5d82079c support mapping 32bit files 2025-04-25 22:49:34 +02:00
robert-yates
9c2a0d946e add 32bit unicorn backend 2025-04-25 21:30:17 +02:00
264 changed files with 10393 additions and 34618 deletions

View File

@@ -1,2 +0,0 @@
**/*.hxx
deps/**/*

View File

@@ -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

View File

@@ -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
View File

@@ -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
View File

@@ -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

View File

@@ -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)
##########################################

View File

@@ -23,7 +23,7 @@
"build"
],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
"CMAKE_BUILD_TYPE": "Release"
}
},
{

View File

@@ -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
![Preview](./docs/images/preview.jpg)

View File

@@ -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()

View File

@@ -1,3 +1,3 @@
Module["preRun"] = () => {
ENV = process.env;
};
Module['preRun'] = () => {
ENV = process.env;
};

View File

@@ -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
View File

@@ -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

Submodule deps/base64 deleted from 8d96a2a737

1
deps/capstone vendored

Submodule deps/capstone deleted from 198cd49dd9

1
deps/flatbuffers vendored

Submodule deps/flatbuffers deleted from 8914d06ab7

1
deps/minidump_cpp vendored

Submodule deps/minidump_cpp deleted from df9ec209ce

2
deps/reflect vendored

2
deps/unicorn vendored

2
deps/zlib vendored

Submodule deps/zlib updated: 570720b0c2...5a82f71ed1

View File

@@ -1,3 +1,2 @@
# Ignore artifacts:
dist
src/fb/

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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));
}

View File

@@ -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>
);
}

View File

@@ -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
View 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>
</>
);
}

View File

@@ -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
View 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>
</>
);
}

View File

@@ -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;
}
}

View File

@@ -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>
);
}
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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,
};

View File

@@ -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,
};

View File

@@ -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,
};

View File

@@ -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,
};

View File

@@ -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 };

View File

@@ -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>
);

View File

@@ -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>&nbsp;▶</span>
</div>
</a>`}
></iframe>
);
}

View File

@@ -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));
}

View File

@@ -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;
}
}
}

View File

@@ -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';

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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
);
}
}

View File

@@ -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);
}
}

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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
}

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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';

View File

@@ -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>
</>
);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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>
</>
);
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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>
</>
);
}
}

View File

@@ -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;
}

View File

@@ -1,2 +1 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pwa/client" />

View File

@@ -17,8 +17,8 @@
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",

View File

@@ -12,7 +12,6 @@
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": ["vite-plugin-pwa/client"]
}
}
}

View File

@@ -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()),
},
});

View File

@@ -14,8 +14,6 @@ SortIncludes: false
AlignEscapedNewlines: Left
PackConstructorInitializers: Never
IndentPPDirectives: None
InsertNewlineAtEOF: true
ColumnLimit: 140
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: true

View File

@@ -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)

View File

@@ -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)

View File

@@ -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), &reg_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), &reg_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;
}

View File

@@ -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);

View File

@@ -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_ = {};
}

View File

@@ -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();
};

View File

@@ -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

View File

@@ -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

View File

@@ -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_{};
};

View File

@@ -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)
{

View File

@@ -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>

View File

@@ -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();
}

View File

@@ -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_;
};

View File

@@ -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()

View File

@@ -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();
}

View File

@@ -1,6 +0,0 @@
#pragma once
#include <memory>
#include <arch_emulator.hpp>
std::unique_ptr<x86_64_emulator> create_x86_64_emulator();

View File

@@ -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