mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
205 lines
5.3 KiB
Dart
205 lines
5.3 KiB
Dart
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 {}
|
|
}
|
|
}
|
|
}
|