first working app

This commit is contained in:
anthony@rabine.fr 2024-05-17 09:28:59 +02:00
parent bb0c18e433
commit c954563472
10 changed files with 509 additions and 264 deletions

View file

@ -20,7 +20,7 @@ You'll need:
Here is a list of packages for Ubuntu-like systems:
```
sudo apt install cmake mesa-utils mesa-common-dev ninja-build libxext-dev
sudo apt install cmake mesa-utils mesa-common-dev ninja-build libxext-dev libpipewire-0.3-dev libasound2-dev libpulse-dev
```
## How to build

21
docs/player-dev.md Normal file
View file

@ -0,0 +1,21 @@
# Story player project
The Story Player is a Flutter application.
# Packages
sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
# Environment variables
# Build for Linux
flutter build linux
# Build for Android
flutter build apk

View file

@ -31,6 +31,7 @@ migrate_working_dir/
.pub-cache/
.pub/
/build/
/linux/flutter/generated_plugin*
# Symbolication related
app.*.symbols

View file

@ -61,6 +61,12 @@ android {
signingConfig signingConfigs.debug
}
}
externalNativeBuild {
cmake {
path "../../storyvm/CMakeLists.txt"
}
}
}
flutter {

View file

@ -0,0 +1,175 @@
library libstory;
import 'dart:io';
import 'dart:typed_data';
import 'package:logger/logger.dart';
var logger = Logger(printer: PrettyPrinter(methodCount: 0));
class IndexFile {
static const int tlvArrayType = 0xAB;
static const int tlvObjectType = 0xE7;
static const int tlvIntegerType = 0x77;
static const int tlvStringType = 0x3D;
static const int tlvRealType = 0xB8;
// Index File stuff
Uint8List indexFileBuffer = Uint8List(0);
ByteData indexFileStream = ByteData(0);
int readPtr = 0;
int indexFileVersion = 0;
bool indexFileIsValid = false;
// Variables de gestion de l'index des histoires
int storiesCount = 0;
String libraryPath = '';
int currentStoryIndex = 0;
List<
({
String uuid,
String titleImage,
String titleSound,
String title,
String description,
int version
})> stories = [];
String getCurrentTitleImage() {
String fileName = '';
if (currentStoryIndex < storiesCount) {
fileName =
'$libraryPath/${stories[currentStoryIndex].uuid}/assets/${stories[currentStoryIndex].titleImage}';
}
return fileName;
}
String getCurrentSoundImage() {
String fileName = '';
if (currentStoryIndex < storiesCount) {
fileName =
'$libraryPath/${stories[currentStoryIndex].uuid}/assets/${stories[currentStoryIndex].titleSound}';
}
return fileName;
}
String getCurrentStoryPath() {
String path = '';
if (currentStoryIndex < storiesCount) {
path =
'$libraryPath/${stories[currentStoryIndex].uuid}';
}
return path;
}
void next() {
currentStoryIndex++;
if (currentStoryIndex >= storiesCount) {
currentStoryIndex = 0;
}
}
void previous() {
currentStoryIndex--;
if (currentStoryIndex < 0) {
currentStoryIndex = storiesCount - 1;
}
}
// Returns the size, 0 if error
int getTl(int expectedType) {
int size = 0;
if (indexFileStream.getUint8(readPtr) == expectedType) {
readPtr++;
size = indexFileStream.getUint16(readPtr, Endian.little);
readPtr += 2;
} else {
throw Exception("Expected type: $expectedType");
}
return size;
}
int getIntegerValue() {
int size = getTl(tlvIntegerType);
if (size == 4) {
int value = indexFileStream.getUint32(readPtr, Endian.little);
readPtr += 4;
return value;
} else {
throw Exception("Expected an integer of size 4 bytes");
}
}
String getStringValue() {
int size = getTl(tlvStringType);
if (size > 0) {
String value = String.fromCharCodes(
indexFileStream.buffer.asUint8List(), readPtr, readPtr + size);
readPtr += size;
return value;
} else {
return "";
}
}
Future<bool> loadIndexFile(String libraryRoot) async {
libraryPath = libraryRoot;
indexFileIsValid = false;
final file = File('$libraryRoot/index.ost');
if (!await file.exists()) {
logger.d('Le fichier n\'existe pas.');
return false;
}
// Ouvrir le fichier en mode lecture binaire
indexFileBuffer = file.readAsBytesSync();
readPtr = 0;
indexFileStream =
ByteData.sublistView(indexFileBuffer, readPtr); // start at zero
stories.clear();
if (indexFileBuffer.lengthInBytes > 3) {
// Root must be an object containing 2 elements
try {
if (getTl(tlvObjectType) == 2) {
indexFileVersion = getIntegerValue();
storiesCount = getTl(tlvArrayType);
for (int i = 0; i < storiesCount; i++) {
if (getTl(tlvObjectType) == 6) {
var record = (
uuid: getStringValue(),
titleImage: getStringValue(),
titleSound: getStringValue(),
title: getStringValue(),
description: getStringValue(),
version: getIntegerValue()
);
logger.d('Found story: ${record.title.toString()}');
stories.add(record);
} else {
throw Exception("Expected object of 6 elements at root");
}
}
// If get through here, no exception raised so the file shoould be ok
indexFileIsValid = true;
currentStoryIndex = 0;
return true;
} else {
throw Exception("Expected object of 2 elements at root");
}
} catch (e) {
logger.e('Exception reading index file: $e');
} finally {}
}
return false;
}
}

View file

@ -0,0 +1,164 @@
library libstory;
import 'dart:ffi';
import 'dart:io';
import 'dart:async';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:logger/logger.dart';
import 'package:event_bus/event_bus.dart';
var logger = Logger(printer: PrettyPrinter(methodCount: 0));
/// The global [EventBus] object.
EventBus eventBus = EventBus();
class MediaEvent {
String image;
String sound;
MediaEvent(this.image, this.sound);
}
Completer<bool> periodic(Duration interval, Function(int cycle) callback) {
final done = Completer<bool>();
() async {
var cycle = 0;
while (!done.isCompleted) {
try {
await callback(cycle);
} catch (e, s) {
logger.e("$e", stackTrace: s);
}
cycle++;
await done.future.timeout(interval).onError((error, stackTrace) => false);
}
}();
return done;
}
typedef MediaCallbackType = Void Function(Int32 i, Pointer<Utf8> str);
typedef MediaCallback = void Function(Int32 i, Pointer<Utf8> str);
typedef VmInitializeType = Void Function(
Pointer<NativeFunction<MediaCallbackType>>);
typedef VmInitialize = void Function(
Pointer<NativeFunction<MediaCallbackType>>);
typedef VmStartType = Void Function(Pointer<Uint8> data, Uint32 size);
typedef VmStart = void Function(Pointer<Uint8> data, int size);
typedef VmRunType = Void Function();
typedef VmRun = void Function();
typedef VmSendEventType = Void Function(Int);
typedef VmSendEvent = void Function(int event);
enum VmEvent { evNoEvent, evStep, evOkButton, evPreviousButton, evNextButton, evAudioFinished, evStop }
class StoryVm {
static late DynamicLibrary nativeApiLib;
static late VmInitialize vmInitialize;
static late VmStart vmStart;
static late VmRun vmRun;
static late VmSendEvent vmSendEvent;
static String currentStoryPath = '';
static bool running = false;
static bool loadLibrary() {
String dllName = 'libstoryvm.so';
if (Platform.isMacOS) {
dllName = 'libstoryvm.dylib';
} else if (Platform.isWindows) {
dllName = 'storyvm.dll';
}
final dylib = DynamicLibrary.open(dllName);
vmInitialize = dylib
.lookup<NativeFunction<VmInitializeType>>('storyvm_initialize')
.asFunction();
vmStart = dylib.lookup<NativeFunction<VmStartType>>('storyvm_start').asFunction();
vmRun = dylib.lookup<NativeFunction<VmRunType>>('storyvm_run').asFunction();
vmSendEvent = dylib.lookup<NativeFunction<VmSendEventType>>('storyvm_send_event').asFunction();
return true;
}
static void mediaCallback(int i, Pointer<Utf8> str) {
String file = str.toDartString();
logger.d('Mediatype: $i, Media file: $file');
if (i == 0) {
eventBus.fire(MediaEvent(file, ""));
} else {
eventBus.fire(MediaEvent("", file));
}
}
static void initialize() {
vmInitialize(Pointer.fromFunction<MediaCallbackType>(mediaCallback));
}
static Completer<bool> task = Completer<bool>();
static Future<bool> start(String storyBasePath) async{
currentStoryPath = storyBasePath;
final file = File('$storyBasePath/story.c32');
if (!file.existsSync()) {
logger.d('Le fichier n\'existe pas.');
return false;
}
// Ouvrir le fichier en mode lecture binaire
Uint8List fileBuffer = file.readAsBytesSync();
Pointer<Uint8> dataPointer = malloc.allocate<Uint8>(fileBuffer.length);
for (int i = 0; i < fileBuffer.length; i++) {
dataPointer[i] = fileBuffer[i];
}
vmStart(dataPointer, fileBuffer.length);
running = true;
task = periodic(const Duration(milliseconds: 10), (cycle) async {
if (running) {
vmRun();
}
});
return true;
}
static void endOfSound() {
vmSendEvent(VmEvent.evAudioFinished.index);
}
static void okButton() {
vmSendEvent(VmEvent.evOkButton.index);
}
static void previousButton() {
vmSendEvent(VmEvent.evPreviousButton.index);
}
static void nextButton() {
vmSendEvent(VmEvent.evNextButton.index);
}
static void stop() {
logger.d('VM stop');
running = false;
task.complete(true);
}
}

View file

@ -9,13 +9,14 @@ import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';
import 'package:mime/mime.dart';
import 'package:path/path.dart' as p;
import 'package:http_parser/http_parser.dart';
import 'package:http_parser/http_parser.dart'; // y'a un warning mais il faut laisser cet import
import 'package:saf/saf.dart';
import 'package:path_provider/path_provider.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter_launcher/storyvm.dart';
import 'libstory/storyvm.dart';
import 'libstory/indexfile.dart';
import 'package:logger/logger.dart';
@ -112,7 +113,7 @@ Future printIps() async {
void main() {
StoryVm.loadLibrary();
StoryVm.initialize();
StoryVm.start();
udpServer();
httpServer();
printIps();
@ -170,9 +171,16 @@ class MyHomePage extends StatefulWidget {
State<MyHomePage> createState() => _MyHomePageState();
}
enum PlayerState { disabled, indexFile, inStory }
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String myPath = 'fffff';
IndexFile indexFile = IndexFile();
String currentImage = 'assets/320x240.png';
final player = AudioPlayer();
StreamSubscription? mediaPub;
PlayerState state = PlayerState.disabled;
StreamSubscription? audioPlayerSub;
void initPaths() async {
Directory? dir;
@ -191,23 +199,42 @@ class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState() {
initPaths();
mediaPub = eventBus.on<MediaEvent>().listen((event) {
setState(() {
if (event.image.isNotEmpty) {
currentImage = '${indexFile.getCurrentStoryPath()}/assets/${event.image}';
}
});
if (event.sound.isNotEmpty) {
player.play(DeviceFileSource('${indexFile.getCurrentStoryPath()}/assets/${event.sound}'));
}
});
audioPlayerSub = player.onPlayerComplete.listen((event) {
if (state == PlayerState.inStory) {
// Send end of music event
StoryVm.endOfSound();
}
});
state = PlayerState.indexFile;
}
void _incrementCounter() {
void showCurrentStory() async {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
currentImage = indexFile.getCurrentTitleImage();
logger.d('Current image: $currentImage');
});
var asset = DeviceFileSource(indexFile.getCurrentSoundImage());
logger.d('Asset: ${asset.toString()}');
await player.play(asset);
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
// This method is rerun every time setState is called, for instance
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
@ -233,18 +260,11 @@ class _MyHomePageState extends State<MyHomePage> {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
Text(
myPath,
style: Theme.of(context).textTheme.headlineMedium,
),
const Image(image: AssetImage('assets/320x240.png')),
Image(image: AssetImage(currentImage)),
],
),
),
@ -253,7 +273,7 @@ class _MyHomePageState extends State<MyHomePage> {
child: Row(
children: <Widget>[
IconButton(
tooltip: 'Open navigation menu',
tooltip: 'Select library directory',
icon: const Icon(
Icons.folder,
size: 40,
@ -283,29 +303,63 @@ class _MyHomePageState extends State<MyHomePage> {
}
if (isGranted == true) {
StoryVm.loadIndexFile('$selectedDirectory/index.ost');
setState(() {
myPath = selectedDirectory;
});
bool success = await indexFile.loadIndexFile(selectedDirectory);
if (success) {
showCurrentStory();
}
}
}
},
color: const Color(0xFFb05728),
),
IconButton(
tooltip: 'Search',
tooltip: 'Previous',
icon: const Icon(Icons.arrow_circle_left, size: 40),
onPressed: () {},
onPressed: () {
if (state == PlayerState.inStory) {
StoryVm.previousButton();
} else if (state == PlayerState.indexFile) {
indexFile.previous();
showCurrentStory();
}
},
color: const Color(0xFFb05728),
),
IconButton(
tooltip: 'Favorite',
tooltip: 'Next',
icon: const Icon(Icons.arrow_circle_right, size: 40),
onPressed: () {},
onPressed: () {
if (state == PlayerState.inStory) {
StoryVm.nextButton();
} else if (state == PlayerState.indexFile) {
indexFile.next();
showCurrentStory();
}
},
color: const Color(0xFFb05728),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
onPressed: () {
if (state == PlayerState.inStory) {
StoryVm.okButton();
} else if (state == PlayerState.indexFile) {
String path = indexFile.getCurrentStoryPath();
if (path.isNotEmpty) {
StoryVm.start(path);
state = PlayerState.inStory;
}
}
},
tooltip: 'Ok',
backgroundColor: const Color(0xFF0092c8),
foregroundColor: Colors.white,

View file

@ -1,204 +0,0 @@
import 'dart:ffi';
import 'dart:io';
import 'dart:async';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:logger/logger.dart';
var logger = Logger(printer: PrettyPrinter(methodCount: 0));
Completer<bool> periodic(Duration interval, Function(int cycle) callback) {
final done = Completer<bool>();
() async {
var cycle = 0;
while (!done.isCompleted) {
try {
await callback(cycle);
} catch (e, s) {
logger.e("$e", stackTrace: s);
}
cycle++;
await done.future.timeout(interval).onError((error, stackTrace) => false);
}
}();
return done;
}
typedef MediaCallbackType = Void Function(Int32 i, Pointer<Utf8> str);
typedef MediaCallback = void Function(Int32 i, Pointer<Utf8> str);
typedef VmInitializeType = Void Function(
Pointer<NativeFunction<MediaCallbackType>>);
typedef VmInitialize = void Function(
Pointer<NativeFunction<MediaCallbackType>>);
typedef VmStartType = Void Function(Pointer<Uint8> data, Uint32 size);
typedef VmStart = void Function(Pointer<Uint8> data, int size);
typedef VmRunType = Void Function();
typedef VmRun = void Function();
class StoryVm {
static late DynamicLibrary nativeApiLib;
static late VmInitialize vmInitialize;
static late VmStart vmStart;
static late VmRun vmRun;
static bool running = false;
static bool loadLibrary() {
String dllName = 'libstoryvm.so';
if (Platform.isMacOS) {
dllName = 'libstoryvm.dylib';
} else if (Platform.isWindows) {
dllName = 'storyvm.dll';
}
final dylib = DynamicLibrary.open(dllName);
vmInitialize = dylib
.lookup<NativeFunction<VmInitializeType>>('storyvm_initialize')
.asFunction();
vmStart =
dylib.lookup<NativeFunction<VmStartType>>('storyvm_start').asFunction();
vmRun =
dylib.lookup<NativeFunction<VmRunType>>('storyvm_start').asFunction();
return true;
}
static void mediaCallback(int i, Pointer<Utf8> str) {
logger.d('Mediatype: $i, Media file: $str');
}
static void initialize() {
vmInitialize(Pointer.fromFunction<MediaCallbackType>(mediaCallback));
}
static Completer<bool> task = Completer<bool>();
static void start() {
task = periodic(const Duration(milliseconds: 10), (cycle) async {
if (running) {
vmRun();
}
});
}
static void stop() {
logger.d('VM stop');
task.complete(true);
}
static Uint8List indexFileBuffer = Uint8List(0);
static ByteData streamIndex = ByteData(0);
static int indexFileVersion = 0;
static int storiesCount = 0;
static int readPtr = 0;
static bool indexValid = false;
static const int tlvArrayType = 0xAB;
static const int tlvObjectType = 0xE7;
static const int tlvIntegerType = 0x77;
static const int tlvStringType = 0x3D;
static const int tlvRealType = 0xB8;
static List<
({
String uuid,
String titleImage,
String titleSound,
String title,
String description,
int version
})> stories = [];
// Returns the size, 0 if error
static int getTl(int expectedType) {
int size = 0;
if (streamIndex.getUint8(readPtr) == expectedType) {
readPtr++;
size = streamIndex.getUint16(readPtr, Endian.little);
readPtr += 2;
} else {
throw Exception("Expected type: $expectedType");
}
return size;
}
static int getIntegerValue() {
int size = getTl(tlvIntegerType);
if (size == 4) {
int value = streamIndex.getUint32(readPtr, Endian.little);
readPtr += 4;
return value;
} else {
throw Exception("Expected an integer of size 4 bytes");
}
}
static String getStringValue() {
int size = getTl(tlvStringType);
if (size > 0) {
String value =
ByteData.sublistView(streamIndex, readPtr, readPtr + size).toString();
readPtr += size;
return value;
} else {
return "";
}
}
static void loadIndexFile(String filePath) async {
final file = File(filePath);
if (!await file.exists()) {
logger.d('Le fichier n\'existe pas.');
return;
}
// Ouvrir le fichier en mode lecture binaire
indexFileBuffer = file.readAsBytesSync();
readPtr = 0;
streamIndex =
ByteData.sublistView(indexFileBuffer, readPtr); // start at zero
if (indexFileBuffer.lengthInBytes > 3) {
// Root must be an object containing 2 elements
try {
if (getTl(tlvObjectType) == 2) {
indexFileVersion = getIntegerValue();
storiesCount = getTl(tlvArrayType);
for (int i = 0; i < storiesCount; i++) {
if (getTl(tlvObjectType) == 6) {
var record = (
uuid: getStringValue(),
titleImage: getStringValue(),
titleSound: getStringValue(),
title: getStringValue(),
description: getStringValue(),
version: getIntegerValue()
);
logger.d('Found story: $record.title');
stories.add(record);
} else {
logger.e("Expected object of 6 elements at root");
}
}
} else {
logger.e("Expected object of 2 elements at root");
}
} catch (e) {
logger.e('Exception reading index file: $e');
} finally {}
}
}
}

View file

@ -44,6 +44,9 @@ dependencies:
ffi: ^2.1.0
logger: ^2.2.0
file_picker: ^7.0.2
dqoi: ^1.3.0
audioplayers: ^5.2.1
event_bus: ^2.0.0
dev_dependencies:
flutter_test:

View file

@ -25,12 +25,12 @@ static media_callback gMediaCallback = nullptr;
//---------------------------------------------------------------------------------------
// VM Stuff
//---------------------------------------------------------------------------------------
uint8_t rom_data[16*1024];
uint8_t ram_data[16*1024];
chip32_ctx_t chip32_ctx;
static uint8_t rom_data[16*1024];
static uint8_t ram_data[16*1024];
static chip32_ctx_t chip32_ctx;
chip32_result_t run_result;
static chip32_result_t run_result;
static uint8_t IndexBuf[260];
@ -67,10 +67,10 @@ uint8_t story_player_syscall(chip32_ctx_t *ctx, uint8_t code)
if (code == 1) // // Execute media
{
printf("SYSCALL 1\n");
fflush(stdout);
// UnloadTexture(*tex);
// *tex =
std::cout << "[STORYVM] Syscall 1" << std::endl;
// for (int i = 0; i< REGISTER_COUNT; i++) {
// std::cout << "[STORYVM] Reg: " << i << ", value: " << (int)ctx->registers[i] << std::endl;
// }
if (ctx->registers[R0] != 0)
{
@ -80,18 +80,15 @@ uint8_t story_player_syscall(chip32_ctx_t *ctx, uint8_t code)
if (gMediaCallback)
{
std::cout << "[STORYVM] Execute callback (image)" << std::endl;
gMediaCallback(0, image);
}
// texture = LoadTexture(image_path); // FIXME
}
else
{
// UnloadTexture(texture); // FIXME
std::cout << "[STORYVM] No image" << std::endl;
}
if (ctx->registers[R1] != 0)
{
// sound file name address is in R1
@ -100,26 +97,20 @@ uint8_t story_player_syscall(chip32_ctx_t *ctx, uint8_t code)
if (gMediaCallback)
{
std::cout << "[STORYVM] Execute callback (sound)" << std::endl;
gMediaCallback(1, sound);
}
// gMusic = LoadMusicStream(sound_path);
// gMusic.looping = false;
// gMusicLoaded = true;
// FIXME
// if (IsMusicReady(gMusic))
// {
// PlayMusicStream(gMusic);
// }
}
else
{
std::cout << "[STORYVM] No sound" << std::endl;
}
retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause
}
else if (code == 2) // Wait Event
{
printf("SYSCALL 2\n");
fflush(stdout);
std::cout << "[STORYVM] Syscall 2 (wait for event)" << std::endl;
retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause
}
return retCode;
@ -132,18 +123,24 @@ extern "C" void storyvm_run()
if (run_result == VM_OK)
{
run_result = chip32_step(&chip32_ctx);
// for (int i = 0; i< REGISTER_COUNT; i++) {
// std::cout << "[STORYVM] Reg: " << i << ", value: " << (int)chip32_ctx.registers[i] << std::endl;
// }
}
}
extern "C" void storyvm_stop()
{
std::cout << "[STORYVM] Stop: " << std::endl;
run_result = VM_FINISHED;
}
extern "C" void storyvm_initialize(media_callback cb)
{
std::cout << "[STORYVM] Initialize: " << (void *)cb << std::endl;
gMediaCallback = cb;
chip32_ctx.stack_size = 512;
@ -161,13 +158,41 @@ extern "C" void storyvm_initialize(media_callback cb)
run_result = VM_FINISHED;
storyvm_stop();
std::cout << "[STORYVM] Initialized" << std::endl;
}
enum VmEventType {EvNoEvent, EvStep, EvOkButton, EvPreviousButton, EvNextButton, EvAudioFinished, EvStop};
extern "C" void storyvm_send_event(int event)
{
if (event == VmEventType::EvStep)
{
run_result = VM_OK;
}
else if (event == VmEventType::EvOkButton)
{
chip32_ctx.registers[R0] = 0x01;
run_result = VM_OK;
}
else if (event == VmEventType::EvPreviousButton)
{
chip32_ctx.registers[R0] = 0x02;
run_result = VM_OK;
}
else if (event == VmEventType::EvNextButton)
{
chip32_ctx.registers[R0] = 0x04;
run_result = VM_OK;
}
else if (event == VmEventType::EvAudioFinished)
{
chip32_ctx.registers[R0] = 0x08;
run_result = VM_OK;
}
else if (event == VmEventType::EvStop)
{
run_result = VM_FINISHED;
}
}
extern "C" void storyvm_start(const uint8_t *data, uint32_t size)
@ -177,12 +202,12 @@ extern "C" void storyvm_start(const uint8_t *data, uint32_t size)
memcpy(chip32_ctx.rom.mem, data, size);
run_result = VM_OK;
chip32_initialize(&chip32_ctx);
std::cout << "[STORYVM] Start" << std::endl;
}
else
{
run_result = VM_FINISHED;
}
std::cout << "[STORYVM] Start" << std::endl;
std::cout << "[STORYVM] Not started (not enough memory)" << std::endl;
}
}