Prepare web debugger (#247)

This commit is contained in:
Maurice Heumann
2025-04-29 09:51:15 +02:00
committed by GitHub
49 changed files with 4768 additions and 47 deletions

2
.gitignore vendored
View File

@@ -150,4 +150,6 @@ user*.bat
page/public/analyzer.js
page/public/analyzer.wasm
page/public/debugger.js
page/public/debugger.wasm
page/public/root.zip

6
.gitmodules vendored
View File

@@ -18,3 +18,9 @@
[submodule "deps/gtest-parallel"]
path = deps/gtest-parallel
url = https://github.com/google/gtest-parallel.git
[submodule "deps/flatbuffers"]
path = deps/flatbuffers
url = https://github.com/google/flatbuffers.git
[submodule "deps/base64"]
path = deps/base64
url = https://github.com/tobiaslocker/base64.git

View File

@@ -92,6 +92,7 @@ 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(

15
deps/CMakeLists.txt vendored
View File

@@ -3,6 +3,21 @@ 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)
endif()
##########################################
add_library(reflect INTERFACE)
target_include_directories(reflect INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/reflect"

1
deps/base64 vendored Submodule

Submodule deps/base64 added at 0d0f5a8849

1
deps/flatbuffers vendored Submodule

Submodule deps/flatbuffers added at 609c72ca1a

View File

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

View File

@@ -12,6 +12,8 @@
<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."

88
page/package-lock.json generated
View File

@@ -11,6 +11,7 @@
"@fontsource/inter": "^5.2.5",
"@radix-ui/react-checkbox": "^1.2.3",
"@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-label": "^2.1.4",
"@radix-ui/react-popover": "^1.1.11",
"@radix-ui/react-scroll-area": "^1.2.6",
@@ -22,12 +23,14 @@
"@types/react-window": "^1.8.8",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"flatbuffers": "^25.2.10",
"jszip": "^3.10.1",
"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-resizable-panels": "^2.1.9",
"react-router-dom": "^7.5.2",
"react-window": "^1.8.11",
"tailwind-merge": "^3.2.0",
@@ -1424,6 +1427,35 @@
}
}
},
"node_modules/@radix-ui/react-dropdown-menu": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.12.tgz",
"integrity": "sha512-VJoMs+BWWE7YhzEQyVwvF9n22Eiyr83HotCVrMQzla/OwRovXCgah7AcaEr4hMNj4gJxSdtIbcHGvmJXOoJVHA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-menu": "2.1.12",
"@radix-ui/react-primitive": "2.1.0",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-focus-guards": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz",
@@ -1505,6 +1537,46 @@
}
}
},
"node_modules/@radix-ui/react-menu": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.12.tgz",
"integrity": "sha512-+qYq6LfbiGo97Zz9fioX83HCiIYYFNs8zAsVCMQrIakoNYylIzWuoD/anAD3UzvvR6cnswmfRFJFq/zYYq/k7Q==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.2",
"@radix-ui/react-collection": "1.1.4",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.7",
"@radix-ui/react-focus-guards": "1.1.2",
"@radix-ui/react-focus-scope": "1.1.4",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-popper": "1.2.4",
"@radix-ui/react-portal": "1.1.6",
"@radix-ui/react-presence": "1.1.4",
"@radix-ui/react-primitive": "2.1.0",
"@radix-ui/react-roving-focus": "1.1.7",
"@radix-ui/react-slot": "1.2.0",
"@radix-ui/react-use-callback-ref": "1.1.1",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.6.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.11.tgz",
@@ -3595,6 +3667,12 @@
"node": ">=16"
}
},
"node_modules/flatbuffers": {
"version": "25.2.10",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.2.10.tgz",
"integrity": "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw==",
"license": "Apache-2.0"
},
"node_modules/flatted": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
@@ -4625,6 +4703,16 @@
}
}
},
"node_modules/react-resizable-panels": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz",
"integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==",
"license": "MIT",
"peerDependencies": {
"react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/react-router": {
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.2.tgz",

View File

@@ -13,6 +13,7 @@
"@fontsource/inter": "^5.2.5",
"@radix-ui/react-checkbox": "^1.2.3",
"@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-label": "^2.1.4",
"@radix-ui/react-popover": "^1.1.11",
"@radix-ui/react-scroll-area": "^1.2.6",
@@ -24,12 +25,14 @@
"@types/react-window": "^1.8.8",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"flatbuffers": "^25.2.10",
"jszip": "^3.10.1",
"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-resizable-panels": "^2.1.9",
"react-router-dom": "^7.5.2",
"react-window": "^1.8.11",
"tailwind-merge": "^3.2.0",

View File

@@ -1,11 +1,16 @@
var logLines = [];
var lastFlush = new Date().getTime();
var msgQueue = [];
onmessage = async (event) => {
const data = event.data;
if (data.message == "run") {
const payload = data.data;
runEmulation(payload.filesystem, payload.file, payload.options);
} else if (data.message == "event") {
const payload = data.data;
msgQueue.push(payload);
}
};
@@ -32,6 +37,18 @@ function notifyExit(code) {
self.close();
}
function handleMessage(message) {
postMessage({ message: "event", data: message });
}
function getMessageFromQueue() {
if (msgQueue.length == 0) {
return "";
}
return msgQueue.shift();
}
function runEmulation(filesystem, file, options) {
globalThis.Module = {
arguments: [...options, "-e", "./root", file],

View File

@@ -5,32 +5,74 @@ html {
@media (pointer: fine) {
::-webkit-scrollbar {
width: 20px;
height: 20px;
}
::-webkit-scrollbar-track {
background-color: transparent;
background: 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-primary,
button.fancy-secondary {
transition: all 0.2s ease;
}
button.fancy-primary {
background: linear-gradient(
180deg,
rgba(38, 144, 255, 1) 0%,
rgba(0, 123, 255, 1) 100%
);
border: 1px solid rgb(18, 101, 236);
text-shadow: rgba(0, 0, 0, 0.2) 0px 1px;
}
button.fancy-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-secondary {
background: linear-gradient(180deg, rgb(38, 38, 39) 0%, rgb(34, 34, 35) 100%);
border: 1px solid rgb(42, 42, 44);
}
button.fancy-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;

View File

@@ -1,4 +1,4 @@
import { useState, useRef } from "react";
import { useState, useRef, useReducer } from "react";
import { Output } from "@/components/output";
import { AppSidebar } from "@/components/app-sidebar";
@@ -8,9 +8,9 @@ import {
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { Button } from "./components/ui/button";
import { Button } from "@/components/ui/button";
import { Emulator, UserFile } from "./emulator";
import { Emulator, UserFile, EmulationState } from "./emulator";
import { getFilesystem } from "./filesystem";
import "./App.css";
@@ -18,14 +18,36 @@ import {
Popover,
PopoverContent,
PopoverTrigger,
} from "./components/ui/popover";
} from "@/components/ui/popover";
import { createDefaultSettings } from "./settings";
import { SettingsMenu } from "./components/settings-menu";
import { SettingsMenu } from "@/components/settings-menu";
import { PlayFill, StopFill, GearFill } from "react-bootstrap-icons";
import { StatusIndicator } from "./components/status-indicator";
import {
PlayFill,
StopFill,
GearFill,
PauseFill,
FileEarmarkCheckFill,
ImageFill,
} from "react-bootstrap-icons";
import { StatusIndicator } from "@/components/status-indicator";
import { Header } from "./Header";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
DropdownMenuGroup,
} from "@/components/ui/dropdown-menu";
function selectAndReadFile(): Promise<UserFile> {
return new Promise((resolve, reject) => {
@@ -64,6 +86,7 @@ export function Playground() {
const output = useRef<Output>(null);
const [settings, setSettings] = useState(createDefaultSettings());
const [emulator, setEmulator] = useState<Emulator | null>(null);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
function logLine(line: string) {
output.current?.logLine(line);
@@ -73,6 +96,18 @@ export function Playground() {
output.current?.logLines(lines);
}
function isEmulatorPaused() {
return emulator && emulator.getState() == EmulationState.Paused;
}
function toggleEmulatorState() {
if (isEmulatorPaused()) {
emulator?.resume();
} else {
emulator?.pause();
}
}
async function createEmulator(userFile: UserFile | null = null) {
emulator?.stop();
output.current?.clear();
@@ -83,7 +118,7 @@ export function Playground() {
logLine(`Processing filesystem (${current}/${total}): ${file}`);
});
const new_emulator = new Emulator(fs, logLines);
const new_emulator = new Emulator(fs, logLines, (_) => forceUpdate());
new_emulator.onTerminate().then(() => setEmulator(null));
setEmulator(new_emulator);
@@ -104,22 +139,62 @@ export function Playground() {
<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
<header className="flex shrink-0 items-center gap-2 border-b p-2 overflow-y-auto">
<SidebarTrigger />
<Separator orientation="vertical" className="h-4" />
{/* RUN */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm">
<PlayFill /> Run
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Run Application</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem onClick={() => createEmulator()}>
<ImageFill className="mr-2" />
<span>Select Sample</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => loadAndRunUserFile()}>
<FileEarmarkCheckFill className="mr-2" />
<span>Seelct your .exe</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<Button
disabled={!emulator}
size="sm"
variant="secondary"
onClick={() => emulator?.stop()}
>
<StopFill /> Stop
</Button>
<Button onClick={() => loadAndRunUserFile()}>
<PlayFill /> Run your .exe
</Button>
<Button variant="secondary" onClick={() => emulator?.stop()}>
<StopFill /> Stop Emulation
<Button
size="sm"
disabled={!emulator}
variant="secondary"
onClick={toggleEmulatorState}
>
{isEmulatorPaused() ? (
<>
<PlayFill /> Resume
</>
) : (
<>
<PauseFill /> Pause
</>
)}
</Button>
<Popover>
<PopoverTrigger asChild>
<Button variant="secondary">
<Button size="sm" variant="secondary">
<GearFill /> Settings
</Button>
</PopoverTrigger>
@@ -128,10 +203,12 @@ export function Playground() {
</PopoverContent>
</Popover>
<div className="text-right flex-1">
<StatusIndicator running={!!emulator} />
<StatusIndicator
state={emulator ? emulator.getState() : EmulationState.Stopped}
/>
</div>
</header>
<div className="flex flex-1 flex-col gap-4 p-4 overflow-auto">
<div className="flex flex-1 flex-col overflow-auto pt-4">
<Output ref={output} />
</div>
</SidebarInset>

View File

@@ -1,28 +1,48 @@
import { Badge } from "@/components/ui/badge";
import { CircleFill } from "react-bootstrap-icons";
import { 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";
default:
return "";
}
}
function getStateColor(state: State) {
switch (state) {
case State.Stopped:
return "bg-orange-600";
case State.Paused:
return "bg-amber-500";
case State.Running:
return "bg-lime-600";
default:
return "";
}
}
export interface StatusIndicatorProps {
running: boolean;
state: State;
}
export function StatusIndicator(props: StatusIndicatorProps) {
const getText = () => {
return props.running ? " Running" : " Stopped";
};
const getColor = () => {
return props.running ? "bg-lime-600" : "bg-orange-600";
};
return (
<Badge variant="outline">
<CircleFill
className={
getColor() + " rounded-full mr-1 n duration-200 ease-in-out"
getStateColor(props.state) +
" rounded-full mr-1 n duration-200 ease-in-out"
}
color="transparent"
/>
{getText()}
{getStateName(props.state)}
</Badge>
);
}

View File

@@ -10,13 +10,13 @@ const buttonVariants = cva(
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 fancy-primary",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80 fancy-secondary",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",

View File

@@ -0,0 +1,255 @@
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

@@ -0,0 +1,54 @@
import * as React from "react";
import { GripVerticalIcon } from "lucide-react";
import * as ResizablePrimitive from "react-resizable-panels";
import { cn } from "@/lib/utils";
function ResizablePanelGroup({
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
return (
<ResizablePrimitive.PanelGroup
data-slot="resizable-panel-group"
className={cn(
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
className,
)}
{...props}
/>
);
}
function ResizablePanel({
...props
}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
}
function ResizableHandle({
withHandle,
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean;
}) {
return (
<ResizablePrimitive.PanelResizeHandle
data-slot="resizable-handle"
className={cn(
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className,
)}
{...props}
>
{withHandle && (
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
<GripVerticalIcon className="size-2.5" />
</div>
)}
</ResizablePrimitive.PanelResizeHandle>
);
}
export { ResizablePanelGroup, ResizablePanel, ResizableHandle };

View File

@@ -1,6 +1,10 @@
import { createDefaultSettings, Settings, translateSettings } from "./settings";
import { FileEntry } from "./zip-file";
import * as flatbuffers from "flatbuffers";
import * as fbDebugger from "@/fb/debugger";
import * as fbDebuggerEvent from "@/fb/debugger/event";
type LogHandler = (lines: string[]) => void;
export interface UserFile {
@@ -8,17 +12,61 @@ export interface UserFile {
data: ArrayBuffer;
}
export enum EmulationState {
Stopped,
Paused,
Running,
}
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;
export class Emulator {
filesystem: FileEntry[];
logHandler: LogHandler;
stateChangeHandler: StateChangeHandler;
terminatePromise: Promise<number | null>;
terminateResolve: (value: number | null) => void;
terminateReject: (reason?: any) => void;
worker: Worker;
state: EmulationState = EmulationState.Stopped;
constructor(filesystem: FileEntry[], logHandler: LogHandler) {
constructor(
filesystem: FileEntry[],
logHandler: LogHandler,
stateChangeHandler: StateChangeHandler,
) {
this.filesystem = filesystem;
this.logHandler = logHandler;
this.stateChangeHandler = stateChangeHandler;
this.terminateResolve = () => {};
this.terminateReject = () => {};
this.terminatePromise = new Promise((resolve, reject) => {
@@ -30,13 +78,7 @@ export class Emulator {
/*new URL('./emulator-worker.js', import.meta.url)*/ "./emulator-worker.js",
);
this.worker.onmessage = (event: MessageEvent) => {
if (event.data.message == "log") {
this.logHandler(event.data.data);
} else if (event.data.message == "end") {
this.terminateResolve(0);
}
};
this.worker.onmessage = this._onMessage.bind(this);
}
start(
@@ -54,6 +96,7 @@ export class Emulator {
});
}
this._setState(EmulationState.Running);
this.worker.postMessage({
message: "run",
data: {
@@ -64,6 +107,19 @@ export class Emulator {
});
}
updateState() {
this.sendEvent(
new fbDebugger.DebugEventT(
fbDebugger.Event.GetStateRequest,
new fbDebugger.GetStateRequestT(),
),
);
}
getState() {
return this.state;
}
stop() {
this.worker.terminate();
this.terminateResolve(null);
@@ -72,4 +128,80 @@ export class Emulator {
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();
}
_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(EmulationState.Stopped);
this.terminateResolve(0);
}
}
_onEvent(event: fbDebugger.DebugEventT) {
switch (event.eventType) {
case fbDebugger.Event.GetStateResponse:
this._handle_state_response(
event.event as fbDebugger.GetStateResponseT,
);
break;
}
}
_setState(state: EmulationState) {
this.state = state;
this.stateChangeHandler(this.state);
}
_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;
}
}
}

19
page/src/fb/debugger.ts Normal file
View File

@@ -0,0 +1,19 @@
// 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 { DebugEvent, DebugEventT } from './debugger/debug-event.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

@@ -0,0 +1,119 @@
// 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 { 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: 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

@@ -0,0 +1,78 @@
// 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 { 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
}
export function unionToEvent(
type: Event,
accessor: (obj:GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null
): 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;
default: return null;
}
}
export function unionListToEvent(
type: Event,
accessor: (index: number, obj:GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null,
index: number
): 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;
default: return null;
}
}

View File

@@ -0,0 +1,56 @@
// 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

@@ -0,0 +1,86 @@
// 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

@@ -0,0 +1,56 @@
// 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

@@ -0,0 +1,110 @@
// 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

@@ -0,0 +1,123 @@
// 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

@@ -0,0 +1,85 @@
// 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

@@ -0,0 +1,123 @@
// 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

@@ -0,0 +1,85 @@
// 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

@@ -0,0 +1,9 @@
// 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

@@ -0,0 +1,123 @@
// 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

@@ -0,0 +1,135 @@
// 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

@@ -0,0 +1,123 @@
// 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

@@ -0,0 +1,135 @@
// 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
);
}
}

5
page/src/fb/events.ts Normal file
View File

@@ -0,0 +1,5 @@
// 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

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

1
src/.clang-format-ignore Normal file
View File

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

View File

@@ -9,6 +9,7 @@ momo_targets_set_folder("backends" ${BACKEND_TARGETS})
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,6 +16,7 @@ endif()
target_link_libraries(analyzer PRIVATE
reflect
debugger
windows-emulator
windows-gdb-stub
)

View File

@@ -6,6 +6,10 @@
#include "object_watching.hpp"
#include "snapshot.hpp"
#ifdef OS_EMSCRIPTEN
#include <event_handler.hpp>
#endif
#include <utils/interupt_handler.hpp>
#include <cstdio>
@@ -249,6 +253,14 @@ namespace
bool run(const analysis_options& options, const std::span<const std::string_view> args)
{
const auto win_emu = setup_emulator(options, args);
#ifdef OS_EMSCRIPTEN
win_emu->callbacks.on_thread_switch = [&] {
debugger::event_context c{.win_emu = *win_emu};
debugger::handle_events(c); //
};
#endif
win_emu->log.log("Using emulator: %s\n", win_emu->emu().get_name().c_str());
(void)&watch_system_objects;

View File

@@ -0,0 +1,25 @@
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
*.cpp
*.hpp
*.rc
)
list(SORT SRC_FILES)
add_library(debugger ${SRC_FILES})
momo_assign_source_group(${SRC_FILES})
target_link_libraries(debugger PRIVATE
windows-emulator
flatbuffers
base64
)
target_include_directories(debugger INTERFACE "${CMAKE_CURRENT_LIST_DIR}")
add_custom_target(generate-flatbuffer
DEPENDS flatc
COMMAND "$<TARGET_FILE:flatc>" --gen-mutable --gen-object-api --filename-ext hxx --cpp -o "${CMAKE_CURRENT_LIST_DIR}" "${CMAKE_CURRENT_LIST_DIR}/events.fbs"
COMMAND "$<TARGET_FILE:flatc>" --gen-mutable --gen-object-api --ts -o "${PROJECT_SOURCE_DIR}/page/src/fb" "${CMAKE_CURRENT_LIST_DIR}/events.fbs"
)

View File

@@ -0,0 +1,233 @@
#include "event_handler.hpp"
#include "message_transmitter.hpp"
#include <base64.hpp>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
#include "events_generated.hxx"
#ifdef _MSC_VER
#pragma warning(pop)
#endif
namespace debugger
{
namespace
{
std::optional<Debugger::DebugEventT> receive_event()
{
const auto message = receive_message();
if (message.empty())
{
return std::nullopt;
}
const auto data = base64::from_base64(message);
flatbuffers::Verifier verifier(reinterpret_cast<const uint8_t*>(data.data()), data.size());
if (!Debugger::VerifyDebugEventBuffer(verifier))
{
return std::nullopt;
}
Debugger::DebugEventT e{};
Debugger::GetDebugEvent(data.data())->UnPackTo(&e);
return {std::move(e)};
}
void send_event(const Debugger::DebugEventT& event)
{
flatbuffers::FlatBufferBuilder fbb{};
fbb.Finish(Debugger::DebugEvent::Pack(fbb, &event));
const std::string_view buffer(reinterpret_cast<const char*>(fbb.GetBufferPointer()), fbb.GetSize());
const auto message = base64::to_base64(buffer);
send_message(message);
}
template <typename T>
requires(!std::is_same_v<std::remove_cvref_t<T>, Debugger::DebugEventT>)
void send_event(T event)
{
Debugger::DebugEventT e{};
e.event.Set(std::move(event));
send_event(e);
}
Debugger::State translate_state(const emulation_state state)
{
switch (state)
{
case emulation_state::paused:
return Debugger::State_Paused;
case emulation_state::none:
case emulation_state::running:
return Debugger::State_Running;
default:
return Debugger::State_None;
}
}
void handle_get_state(const event_context& c)
{
Debugger::GetStateResponseT response{};
response.state = translate_state(c.state);
send_event(response);
}
void handle_read_memory(const event_context& c, const Debugger::ReadMemoryRequestT& request)
{
std::vector<uint8_t> buffer{};
buffer.resize(request.size);
const auto res = c.win_emu.memory.try_read_memory(request.address, buffer.data(), buffer.size());
Debugger::ReadMemoryResponseT response{};
response.address = request.address;
if (res)
{
response.data = std::move(buffer);
}
send_event(std::move(response));
}
void handle_write_memory(const event_context& c, const Debugger::WriteMemoryRequestT& request)
{
bool success{};
try
{
c.win_emu.memory.write_memory(request.address, request.data.data(), request.data.size());
success = true;
}
catch (...)
{
success = false;
}
Debugger::WriteMemoryResponseT response{};
response.address = request.address;
response.size = static_cast<uint32_t>(request.data.size());
response.success = success;
send_event(response);
}
void handle_read_register(const event_context& c, const Debugger::ReadRegisterRequestT& request)
{
std::array<uint8_t, 512> buffer{};
const auto res = c.win_emu.emu().read_register(static_cast<x86_register>(request.register_), buffer.data(),
buffer.size());
const auto size = std::min(buffer.size(), res);
Debugger::ReadRegisterResponseT response{};
response.register_ = request.register_;
response.data.assign(buffer.data(), buffer.data() + size);
send_event(std::move(response));
}
void handle_write_register(const event_context& c, const Debugger::WriteRegisterRequestT& request)
{
bool success{};
size_t size = request.data.size();
try
{
size = c.win_emu.emu().write_register(static_cast<x86_register>(request.register_), request.data.data(),
request.data.size());
success = true;
}
catch (...)
{
success = false;
}
Debugger::WriteRegisterResponseT response{};
response.register_ = request.register_;
response.size = static_cast<uint32_t>(size);
response.success = success;
send_event(response);
}
void handle_event(event_context& c, const Debugger::DebugEventT& e)
{
switch (e.event.type)
{
case Debugger::Event_PauseRequest:
c.state = emulation_state::paused;
break;
case Debugger::Event_RunRequest:
c.state = emulation_state::running;
break;
case Debugger::Event_GetStateRequest:
handle_get_state(c);
break;
case Debugger::Event_ReadMemoryRequest:
handle_read_memory(c, *e.event.AsReadMemoryRequest());
break;
case Debugger::Event_WriteMemoryRequest:
handle_write_memory(c, *e.event.AsWriteMemoryRequest());
break;
case Debugger::Event_ReadRegisterRequest:
handle_read_register(c, *e.event.AsReadRegisterRequest());
break;
case Debugger::Event_WriteRegisterRequest:
handle_write_register(c, *e.event.AsWriteRegisterRequest());
break;
default:
break;
}
}
}
void handle_events_once(event_context& c)
{
while (true)
{
suspend_execution(0ms);
const auto e = receive_event();
if (!e.has_value())
{
break;
}
handle_event(c, *e);
}
}
void handle_events(event_context& c)
{
while (true)
{
handle_events_once(c);
if (c.state != emulation_state::paused)
{
break;
}
suspend_execution(2ms);
}
}
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <windows_emulator.hpp>
namespace debugger
{
enum class emulation_state
{
none,
running,
paused,
};
struct event_context
{
windows_emulator& win_emu;
emulation_state state{emulation_state::none};
};
void handle_events(event_context& c);
}

81
src/debugger/events.fbs Normal file
View File

@@ -0,0 +1,81 @@
namespace Debugger;
table GetStateRequest {}
enum State : uint32 {
None = 0,
Running,
Paused,
}
table GetStateResponse {
state: State;
}
table PauseRequest {}
table RunRequest {
single_step: bool;
}
table WriteMemoryRequest {
address: uint64;
data: [ubyte];
}
table WriteMemoryResponse {
address: uint64;
size: uint32;
success: bool;
}
table ReadMemoryRequest {
address: uint64;
size: uint32;
}
table ReadMemoryResponse {
address: uint64;
data: [ubyte];
}
table WriteRegisterRequest {
register: uint32;
data: [ubyte];
}
table WriteRegisterResponse {
register: uint32;
size: uint32;
success: bool;
}
table ReadRegisterRequest {
register: uint32;
}
table ReadRegisterResponse {
register: uint32;
data: [ubyte];
}
union Event {
PauseRequest,
RunRequest,
GetStateRequest,
GetStateResponse,
WriteMemoryRequest,
WriteMemoryResponse,
ReadMemoryRequest,
ReadMemoryResponse,
WriteRegisterRequest,
WriteRegisterResponse,
ReadRegisterRequest,
ReadRegisterResponse,
}
table DebugEvent {
event: Event;
}
root_type DebugEvent;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
#include "message_transmitter.hpp"
#include <platform/compiler.hpp>
#include <thread>
#include <utils/finally.hpp>
#ifdef OS_EMSCRIPTEN
#include <emscripten.h>
#endif
using namespace std::literals;
namespace debugger
{
void send_message(const std::string& message)
{
#ifdef OS_EMSCRIPTEN
// clang-format off
EM_ASM_({
handleMessage(UTF8ToString($0));
}, message.c_str());
// clang-format on
#else
(void)message;
#endif
}
std::string receive_message()
{
#ifdef OS_EMSCRIPTEN
// clang-format off
auto* ptr = EM_ASM_PTR({
var message = getMessageFromQueue();
if (!message || message.length == 0)
{
return null;
}
const length = lengthBytesUTF8(message) + 1;
const buffer = _malloc(length);
stringToUTF8(message, buffer, length);
return buffer;
});
// clang-format on
if (!ptr)
{
return {};
}
const auto _ = utils::finally([&] {
free(ptr); //
});
return {reinterpret_cast<const char*>(ptr)};
#else
return {};
#endif
}
void suspend_execution(const std::chrono::milliseconds ms)
{
#ifdef OS_EMSCRIPTEN
emscripten_sleep(static_cast<uint32_t>(ms.count()));
#else
if (ms > 0ms)
{
std::this_thread::sleep_for(ms);
}
else
{
std::this_thread::yield();
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include <chrono>
#include <string>
namespace debugger
{
void suspend_execution(std::chrono::milliseconds ms = std::chrono::milliseconds(0));
void send_message(const std::string& message);
std::string receive_message();
}

View File

@@ -34,6 +34,7 @@ struct process_context
{
utils::optional_function<void(handle h, emulator_thread& thr)> on_create_thread{};
utils::optional_function<void(handle h, emulator_thread& thr)> on_thread_terminated{};
utils::optional_function<void()> on_thread_switch{};
};
struct atom_entry

View File

@@ -169,6 +169,7 @@ namespace
}
thread.apc_alertable = false;
win_emu.callbacks.on_thread_switch();
return true;
}