Spaces:
Sleeping
Sleeping
| <title>Basic Emulator</title><!-- not BASIC! --> | |
| <style> | |
| div { | |
| font-size: 12px; | |
| line-height: 16px; | |
| } | |
| BODY { | |
| background-color: #111; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| height: 100vh; | |
| overflow: hidden; | |
| } | |
| </style> | |
| <script src="../build/libv86.js"></script> | |
| <script> | |
| ; | |
| // Libs | |
| // SO: 40031688 | |
| function buf2hex(buffer) { // buffer is an ArrayBuffer | |
| return [...new Uint8Array(buffer)] | |
| .map(x => x.toString(16).padStart(2, '0')) | |
| .join(''); | |
| } | |
| class ATStream { | |
| constructor ({ delegate, acc, transform, observe }) { | |
| this.delegate = delegate; | |
| if ( acc ) this.acc = acc; | |
| if ( transform ) this.transform = transform; | |
| if ( observe ) this.observe = observe; | |
| this.state = {}; | |
| this.carry = []; | |
| } | |
| [Symbol.asyncIterator]() { return this; } | |
| async next_value_ () { | |
| if ( this.carry.length > 0 ) { | |
| console.log('got from carry!', this.carry); | |
| return { | |
| value: this.carry.shift(), | |
| done: false, | |
| }; | |
| } | |
| return await this.delegate.next(); | |
| } | |
| async acc ({ value }) { | |
| return value; | |
| } | |
| async next_ () { | |
| for (;;) { | |
| const ret = await this.next_value_(); | |
| if ( ret.done ) return ret; | |
| const v = await this.acc({ | |
| state: this.state, | |
| value: ret.value, | |
| carry: v => this.carry.push(v), | |
| }); | |
| if ( this.carry.length >= 0 && v === undefined ) { | |
| throw new Error(`no value, but carry value exists`); | |
| } | |
| if ( v === undefined ) continue; | |
| // We have a value, clear the state! | |
| this.state = {}; | |
| if ( this.transform ) { | |
| const new_value = await this.transform( | |
| { value: ret.value }); | |
| return { ...ret, value: new_value }; | |
| } | |
| return { ...ret, value: v }; | |
| } | |
| } | |
| async next () { | |
| const ret = await this.next_(); | |
| if ( this.observe && !ret.done ) { | |
| this.observe(ret); | |
| } | |
| return ret; | |
| } | |
| async enqueue_ (v) { | |
| this.queue.push(v); | |
| } | |
| } | |
| const NewCallbackByteStream = () => { | |
| let listener; | |
| let queue = []; | |
| const NOOP = () => {}; | |
| let signal = NOOP; | |
| (async () => { | |
| for (;;) { | |
| const v = await new Promise((rslv, rjct) => { | |
| listener = rslv; | |
| }); | |
| queue.push(v); | |
| signal(); | |
| } | |
| })(); | |
| const stream = { | |
| [Symbol.asyncIterator](){ | |
| return this; | |
| }, | |
| async next () { | |
| if ( queue.length > 0 ) { | |
| return { | |
| value: queue.shift(), | |
| done: false, | |
| }; | |
| } | |
| await new Promise(rslv => { | |
| signal = rslv; | |
| }); | |
| signal = NOOP; | |
| const v = queue.shift(); | |
| return { value: v, done: false }; | |
| } | |
| }; | |
| stream.listener = data => { | |
| listener(data); | |
| }; | |
| return stream; | |
| } | |
| // Tiny inline little-endian integer library | |
| const get_int = (n_bytes, array8, signed=false) => { | |
| return (v => signed ? v : v >>> 0)( | |
| array8.slice(0,n_bytes).reduce((v,e,i)=>v|=e<<8*i,0)); | |
| } | |
| const to_int = (n_bytes, num) => { | |
| return (new Uint8Array()).map((_,i)=>(num>>8*i)&0xFF); | |
| } | |
| const NewVirtioFrameStream = byteStream => { | |
| return new ATStream({ | |
| delegate: byteStream, | |
| async acc ({ value, carry }) { | |
| if ( ! this.state.buffer ) { | |
| const size = get_int(4, value); | |
| // 512MiB limit in case of attempted abuse or a bug | |
| // (assuming this won't happen under normal conditions) | |
| if ( size > 512*(1024**2) ) { | |
| throw new Error(`Way too much data! (${size} bytes)`); | |
| } | |
| value = value.slice(4); | |
| this.state.buffer = new Uint8Array(size); | |
| this.state.index = 0; | |
| } | |
| const needed = this.state.buffer.length - this.state.index; | |
| if ( value.length > needed ) { | |
| const remaining = value.slice(needed); | |
| console.log('we got more bytes than we needed', | |
| needed, | |
| remaining, | |
| value.length, | |
| this.state.buffer.length, | |
| this.state.index, | |
| ); | |
| carry(remaining); | |
| } | |
| const amount = Math.min(value.length, needed); | |
| const added = value.slice(0, amount); | |
| this.state.buffer.set(added, this.state.index); | |
| this.state.index += amount; | |
| if ( this.state.index > this.state.buffer.length ) { | |
| throw new Error('WUT'); | |
| } | |
| if ( this.state.index == this.state.buffer.length ) { | |
| return this.state.buffer; | |
| } | |
| } | |
| }); | |
| }; | |
| const wisp_types = [ | |
| { | |
| id: 3, | |
| label: 'CONTINUE', | |
| describe: ({ payload }) => { | |
| return `buffer: ${get_int(4, payload)}B`; | |
| }, | |
| getAttributes ({ payload }) { | |
| return { | |
| buffer_size: get_int(4, payload), | |
| }; | |
| } | |
| }, | |
| { | |
| id: 5, | |
| label: 'INFO', | |
| describe: ({ payload }) => { | |
| return `v${payload[0]}.${payload[1]} ` + | |
| buf2hex(payload.slice(2)); | |
| }, | |
| getAttributes ({ payload }) { | |
| return { | |
| version_major: payload[0], | |
| version_minor: payload[1], | |
| extensions: payload.slice(2), | |
| } | |
| } | |
| }, | |
| ]; | |
| class WispPacket { | |
| static SEND = Symbol('SEND'); | |
| static RECV = Symbol('RECV'); | |
| constructor ({ data, direction, extra }) { | |
| this.direction = direction; | |
| this.data_ = data; | |
| this.extra = extra ?? {}; | |
| this.types_ = { | |
| 1: { label: 'CONNECT' }, | |
| 2: { label: 'DATA' }, | |
| 4: { label: 'CLOSE' }, | |
| }; | |
| for ( const item of wisp_types ) { | |
| this.types_[item.id] = item; | |
| } | |
| } | |
| get type () { | |
| const i_ = this.data_[0]; | |
| return this.types_[i_]; | |
| } | |
| get attributes () { | |
| if ( ! this.type.getAttributes ) return {}; | |
| const attrs = {}; | |
| Object.assign(attrs, this.type.getAttributes({ | |
| payload: this.data_.slice(5), | |
| })); | |
| Object.assign(attrs, this.extra); | |
| return attrs; | |
| } | |
| toVirtioFrame () { | |
| const arry = new Uint8Array(this.data_.length + 4); | |
| arry.set(to_int(4, this.data_.length), 0); | |
| arry.set(this.data_, 4); | |
| return arry; | |
| } | |
| describe () { | |
| return this.type.label + '(' + | |
| (this.type.describe?.({ | |
| payload: this.data_.slice(5), | |
| }) ?? '?') + ')'; | |
| } | |
| log () { | |
| const arrow = | |
| this.direction === this.constructor.SEND ? '->' : | |
| this.direction === this.constructor.RECV ? '<-' : | |
| '<>' ; | |
| console.groupCollapsed(`WISP ${arrow} ${this.describe()}`); | |
| const attrs = this.attributes; | |
| for ( const k in attrs ) { | |
| console.log(k, attrs[k]); | |
| } | |
| console.groupEnd(); | |
| } | |
| reflect () { | |
| const reflected = new WispPacket({ | |
| data: this.data_, | |
| direction: | |
| this.direction === this.constructor.SEND ? | |
| this.constructor.RECV : | |
| this.direction === this.constructor.RECV ? | |
| this.constructor.SEND : | |
| undefined, | |
| extra: { | |
| reflectedFrom: this, | |
| } | |
| }); | |
| return reflected; | |
| } | |
| } | |
| for ( const item of wisp_types ) { | |
| WispPacket[item.label] = item; | |
| } | |
| const NewWispPacketStream = frameStream => { | |
| return new ATStream({ | |
| delegate: frameStream, | |
| transform ({ value }) { | |
| return new WispPacket({ | |
| data: value, | |
| direction: WispPacket.RECV, | |
| }); | |
| }, | |
| observe ({ value }) { | |
| value.log(); | |
| } | |
| }); | |
| } | |
| class WispClient { | |
| constructor ({ | |
| packetStream, | |
| sendFn, | |
| }) { | |
| this.packetStream = packetStream; | |
| this.sendFn = sendFn; | |
| } | |
| send (packet) { | |
| packet.log(); | |
| this.sendFn(packet); | |
| } | |
| } | |
| window.onload = async function() | |
| { | |
| const resp = await fetch( | |
| './image/build/x86images/rootfs.bin' | |
| ); | |
| const arrayBuffer = await resp.arrayBuffer(); | |
| var emulator = window.emulator = new V86({ | |
| wasm_path: "../build/v86.wasm", | |
| memory_size: 512 * 1024 * 1024, | |
| vga_memory_size: 2 * 1024 * 1024, | |
| screen_container: document.getElementById("screen_container"), | |
| bios: { | |
| url: "../bios/seabios.bin", | |
| }, | |
| vga_bios: { | |
| url: "../bios/vgabios.bin", | |
| }, | |
| initrd: { | |
| url: './image/build/x86images/boot/initramfs-lts', | |
| }, | |
| bzimage: { | |
| url: './image/build/x86images/boot/vmlinuz-lts', | |
| async: false | |
| }, | |
| cmdline: 'rw root=/dev/sda init=/sbin/init rootfstype=ext4', | |
| // cmdline: 'rw root=/dev/sda init=/bin/bash rootfstype=ext4', | |
| // cmdline: "rw init=/sbin/init root=/dev/sda rootfstype=ext4", | |
| // cmdline: "rw init=/sbin/init root=/dev/sda rootfstype=ext4 random.trust_cpu=on 8250.nr_uarts=10 spectre_v2=off pti=off mitigations=off", | |
| // cdrom: { | |
| // // url: "../images/al32-2024.07.10.iso", | |
| // url: "./image/build/x86images/rootfs.bin", | |
| // }, | |
| hda: { | |
| buffer: arrayBuffer, | |
| // url: './image/build/x86images/rootfs.bin', | |
| async: true, | |
| // size: 1073741824, | |
| // size: 805306368, | |
| }, | |
| // bzimage_initrd_from_filesystem: true, | |
| autostart: true, | |
| network_relay_url: "wisp://127.0.0.1:3000", | |
| virtio_console: true, | |
| }); | |
| const decoder = new TextDecoder(); | |
| const byteStream = NewCallbackByteStream(); | |
| emulator.add_listener('virtio-console0-output-bytes', | |
| byteStream.listener); | |
| const virtioStream = NewVirtioFrameStream(byteStream); | |
| const wispStream = NewWispPacketStream(virtioStream); | |
| class PTYManager { | |
| constructor ({ client }) { | |
| this.client = client; | |
| } | |
| init () { | |
| this.run_(); | |
| } | |
| async run_ () { | |
| const handlers_ = { | |
| [WispPacket.INFO.id]: ({ packet }) => { | |
| // console.log('guess we doing info packets now', packet); | |
| this.client.send(packet.reflect()); | |
| } | |
| }; | |
| for await ( const packet of this.client.packetStream ) { | |
| // console.log('what we got here?', | |
| // packet.type, | |
| // packet, | |
| // ); | |
| handlers_[packet.type.id]?.({ packet }); | |
| } | |
| } | |
| } | |
| const ptyMgr = new PTYManager({ | |
| client: new WispClient({ | |
| packetStream: wispStream, | |
| sendFn: packet => { | |
| emulator.bus.send( | |
| "virtio-console0-input-bytes", | |
| packet.toVirtioFrame(), | |
| ); | |
| } | |
| }) | |
| }); | |
| ptyMgr.init(); | |
| } | |
| </script> | |
| <!-- A minimal structure for the ScreenAdapter defined in browser/screen.js --> | |
| <div id="screen_container"> | |
| <div style="white-space: pre; font: 14px monospace; line-height: 14px"></div> | |
| <canvas style="display: none"></canvas> | |
| </div> | |