mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-25 14:41:02 +00:00
Add prototypical landing page
This commit is contained in:
149
page/package-lock.json
generated
149
page/package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"@radix-ui/react-scroll-area": "^1.2.5",
|
||||
"@radix-ui/react-separator": "^1.1.4",
|
||||
"@radix-ui/react-slot": "^1.2.0",
|
||||
"@radix-ui/react-tabs": "^1.1.8",
|
||||
"@radix-ui/react-tooltip": "^1.2.3",
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"@types/react-window": "^1.8.8",
|
||||
@@ -25,6 +26,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap-icons": "^1.11.5",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.5.1",
|
||||
"react-window": "^1.8.11",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss": "^4.1.4",
|
||||
@@ -1275,6 +1277,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.4.tgz",
|
||||
"integrity": "sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.0",
|
||||
"@radix-ui/react-slot": "1.2.0"
|
||||
},
|
||||
"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-compose-refs": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||
@@ -1604,6 +1632,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.7.tgz",
|
||||
"integrity": "sha512-C6oAg451/fQT3EGbWHbCQjYTtbyjNO1uzQgMzwyivcHT3GKNEmu1q3UuREhN+HzHAVtv3ivMVK08QlC+PkYw9Q==",
|
||||
"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-id": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.1.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||
"@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-scroll-area": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.5.tgz",
|
||||
@@ -1676,6 +1735,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tabs": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.8.tgz",
|
||||
"integrity": "sha512-4iUaN9SYtG+/E+hJ7jRks/Nv90f+uAsRHbLYA6BcA9EsR6GNWgsvtS4iwU2SP0tOZfDGAyqIT0yz7ckgohEIFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-presence": "1.1.3",
|
||||
"@radix-ui/react-primitive": "2.1.0",
|
||||
"@radix-ui/react-roving-focus": "1.1.7",
|
||||
"@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-tooltip": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.3.tgz",
|
||||
@@ -3020,6 +3109,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
@@ -4458,6 +4556,45 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.1.tgz",
|
||||
"integrity": "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"turbo-stream": "2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.1.tgz",
|
||||
"integrity": "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-style-singleton": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||
@@ -4624,6 +4761,12 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
@@ -4796,6 +4939,12 @@
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/turbo-stream": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
||||
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/tw-animate-css": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.2.8.tgz",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"@radix-ui/react-scroll-area": "^1.2.5",
|
||||
"@radix-ui/react-separator": "^1.1.4",
|
||||
"@radix-ui/react-slot": "^1.2.0",
|
||||
"@radix-ui/react-tabs": "^1.1.8",
|
||||
"@radix-ui/react-tooltip": "^1.2.3",
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"@types/react-window": "^1.8.8",
|
||||
@@ -27,6 +28,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap-icons": "^1.11.5",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.5.1",
|
||||
"react-window": "^1.8.11",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss": "^4.1.4",
|
||||
|
||||
@@ -21,10 +21,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.terminal-output {
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
|
||||
133
page/src/App.tsx
133
page/src/App.tsx
@@ -1,126 +1,21 @@
|
||||
import { useState, useRef } from 'react'
|
||||
import { Output } from '@/components/output'
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
import { Playground } from "./Playground";
|
||||
import { LandingPage } from "./LandingPage";
|
||||
|
||||
import { AppSidebar } from "@/components/app-sidebar"
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
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'
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
import "./App.css";
|
||||
|
||||
function App() {
|
||||
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 (
|
||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||
<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>
|
||||
</ThemeProvider>)
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<LandingPage />} />
|
||||
<Route path="/playground" element={<Playground />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
||||
189
page/src/LandingPage.tsx
Normal file
189
page/src/LandingPage.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
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";
|
||||
|
||||
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 (
|
||||
<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">
|
||||
High-performance Windows user space emulator operating at
|
||||
syscall level
|
||||
</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=ag_zebGFpQPXBsTx"
|
||||
title="YouTube video player"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
></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>
|
||||
);
|
||||
}
|
||||
123
page/src/Playground.tsx
Normal file
123
page/src/Playground.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
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'
|
||||
|
||||
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 (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
64
page/src/components/ui/tabs.tsx
Normal file
64
page/src/components/ui/tabs.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Tabs({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||
return (
|
||||
<TabsPrimitive.Root
|
||||
data-slot="tabs"
|
||||
className={cn("flex flex-col gap-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TabsList({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
||||
return (
|
||||
<TabsPrimitive.List
|
||||
data-slot="tabs-list"
|
||||
className={cn(
|
||||
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TabsTrigger({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
||||
return (
|
||||
<TabsPrimitive.Trigger
|
||||
data-slot="tabs-trigger"
|
||||
className={cn(
|
||||
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TabsContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||
return (
|
||||
<TabsPrimitive.Content
|
||||
data-slot="tabs-content"
|
||||
className={cn("flex-1 outline-none", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
Reference in New Issue
Block a user