1
0
mirror of https://github.com/CPunch/Laika.git synced 2026-02-08 01:00:05 +00:00

Refactoring: reorganized files

This commit is contained in:
2022-09-01 20:00:37 -05:00
parent 169313ee39
commit b23057b219
52 changed files with 639 additions and 557 deletions

View File

@@ -0,0 +1,43 @@
// https://github.com/tidwall/hashmap.c
// Copyright 2020 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
#ifndef HASHMAP_H
#define HASHMAP_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct hashmap;
struct hashmap *hashmap_new(size_t elsize, size_t cap, uint64_t seed0, uint64_t seed1,
uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1),
int (*compare)(const void *a, const void *b, void *udata),
void (*elfree)(void *item), void *udata);
struct hashmap *
hashmap_new_with_allocator(void *(*malloc)(size_t), void *(*realloc)(void *, size_t),
void (*free)(void *), size_t elsize, size_t cap, uint64_t seed0,
uint64_t seed1,
uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1),
int (*compare)(const void *a, const void *b, void *udata),
void (*elfree)(void *item), void *udata);
void hashmap_free(struct hashmap *map);
void hashmap_clear(struct hashmap *map, bool update_cap);
size_t hashmap_count(struct hashmap *map);
bool hashmap_oom(struct hashmap *map);
void *hashmap_get(struct hashmap *map, const void *item);
void *hashmap_set(struct hashmap *map, const void *item);
void *hashmap_delete(struct hashmap *map, void *item);
void *hashmap_probe(struct hashmap *map, uint64_t position);
bool hashmap_scan(struct hashmap *map, bool (*iter)(const void *item, void *udata), void *udata);
bool hashmap_iter(struct hashmap *map, size_t *i, void **item);
uint64_t hashmap_sip(const void *data, size_t len, uint64_t seed0, uint64_t seed1);
uint64_t hashmap_murmur(const void *data, size_t len, uint64_t seed0, uint64_t seed1);
// DEPRECATED: use `hashmap_new_with_allocator`
void hashmap_set_allocator(void *(*malloc)(size_t), void (*free)(void *));
#endif

154
lib/include/core/ini.h Normal file
View File

@@ -0,0 +1,154 @@
/* inih -- simple .INI file parser
SPDX-License-Identifier: BSD-3-Clause
Copyright (C) 2009-2020, Ben Hoyt
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef INI_H
#define INI_H
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdio.h>
/* Nonzero if ini_handler callback should accept lineno parameter. */
#ifndef INI_HANDLER_LINENO
# define INI_HANDLER_LINENO 0
#endif
/* Typedef for prototype of handler function. */
#if INI_HANDLER_LINENO
typedef int (*ini_handler)(void *user, const char *section, const char *name, const char *value,
int lineno);
#else
typedef int (*ini_handler)(void *user, const char *section, const char *name, const char *value);
#endif
/* Typedef for prototype of fgets-style reader function. */
typedef char *(*ini_reader)(char *str, int num, void *stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char *filename, ini_handler handler, void *user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE *file, ini_handler handler, void *user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O (see also
ini_parse_string). */
int ini_parse_stream(ini_reader reader, void *stream, ini_handler handler, void *user);
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
instead of a file. Useful for parsing INI data from a network socket or
already in memory. */
int ini_parse_string(const char *string, ini_handler handler, void *user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
# define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See https://github.com/benhoyt/inih/issues/21 */
#ifndef INI_ALLOW_BOM
# define INI_ALLOW_BOM 1
#endif
/* Chars that begin a start-of-line comment. Per Python configparser, allow
both ; and # comments at the start of a line by default. */
#ifndef INI_START_COMMENT_PREFIXES
# define INI_START_COMMENT_PREFIXES ";#"
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
# define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
# define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
# define INI_USE_STACK 1
#endif
/* Maximum line length for any line in INI file (stack or heap). Note that
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
#ifndef INI_MAX_LINE
# define INI_MAX_LINE 200
#endif
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
zero. */
#ifndef INI_ALLOW_REALLOC
# define INI_ALLOW_REALLOC 0
#endif
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
is zero. */
#ifndef INI_INITIAL_ALLOC
# define INI_INITIAL_ALLOC 200
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
# define INI_STOP_ON_FIRST_ERROR 0
#endif
/* Nonzero to call the handler at the start of each new section (with
name and value NULL). Default is to only call the handler on
each name=value pair. */
#ifndef INI_CALL_HANDLER_ON_NEW_SECTION
# define INI_CALL_HANDLER_ON_NEW_SECTION 0
#endif
/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
call the handler with value NULL in this case. Default is to treat
no-value lines as an error. */
#ifndef INI_ALLOW_NO_VALUE
# define INI_ALLOW_NO_VALUE 0
#endif
/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory
allocation functions (INI_USE_STACK must also be 0). These functions must
have the same signatures as malloc/free/realloc and behave in a similar
way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */
#ifndef INI_CUSTOM_ALLOCATOR
# define INI_CUSTOM_ALLOCATOR 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* INI_H */

123
lib/include/core/lbox.h Normal file
View File

@@ -0,0 +1,123 @@
#ifndef LAIKA_BOX_H
#define LAIKA_BOX_H
#include "core/lmem.h"
#include "core/lsodium.h"
#include "core/lvm.h"
#include "laika.h"
#include <inttypes.h>
#define LAIKA_BOX_SCRATCH_SIZE 128
#define LAIKA_BOX_HEAPSIZE 256
enum
{
LAIKA_BOX_UNLOCKED_INDX, /* for output */
LAIKA_BOX_SCRATCH_INDX, /* for misc. scratch work the vm needs to do (hold keys, etc.) */
LAIKA_BOX_DATA_INDX /* for input */
};
/* Laika Box:
Laika Boxes are obfuscated storage mediums where data is only in memory for a very short
amount of time. Of course, this can be bypassed with a simple debugger and setting a breakpoint
right after the data is 'unlocked', but the game of obfuscation isn't to prevent the data from
being seen, it's to slow the reverse engineer down.
2 main APIs are exposed here, laikaB_unlock() & laikaB_lock(). Both of which are inlined to
make it more painful for the reverse engineer to quickly dump boxes from memory, forcing them to
set breakpoints across the executable. Each box has its own VM, with it's own deobfuscation
routine. This makes static analysis a painful route for string dumping. These apis, while can be
used directly, are abstracted through macros with the pre-built boxes.
Use LAIKA_BOX_SKID_START & LAIKA_BOX_SKID_END for quick and dirty usage. The data macros in
`lboxconfig.h` are passed to these, which are generated by VMBoxGen (`tools/vmboxgen`). This will
be extended in the future with more boxes and such, however for the time being only
LAIKA_BOX_SKID_* is implemented.
*/
struct sLaikaB_box
{
uint8_t unlockedData[LAIKA_BOX_HEAPSIZE];
uint8_t scratch[LAIKA_BOX_SCRATCH_SIZE];
uint8_t code[LAIKA_VM_CODESIZE];
};
/* ======================================[[ Box Var API ]]====================================== */
#define LAIKA_BOX_STARTVAR(type, ident, box, data) \
uint8_t __data##ident[LAIKA_VM_CODESIZE] = data; \
type ident; \
struct sLaikaB_box __box##ident = box; \
laikaB_unlock(&__box##ident, __data##ident); \
ident = (type)__box##ident.unlockedData;
#define LAIKA_BOX_ENDVAR(ident) laikaB_lock(&__box##ident);
#ifdef LAIKA_OBFUSCATE
# define LAIKA_BOX_SKID_START(type, ident, strmacro) \
LAIKA_BOX_STARTVAR(type, ident, LAIKA_BOX_SKID(KEY_##strmacro), DATA_##strmacro)
# define LAIKA_BOX_SKID_END(ident) LAIKA_BOX_ENDVAR(ident)
#else /* disable obfuscations */
# define LAIKA_BOX_SKID_START(type, ident, strmacro) type ident = strmacro;
# define LAIKA_BOX_SKID_END(ident) ((void)0) /* no-op */
#endif
/* ======================================[[ Laika Boxes ]]====================================== */
/* BOX_SKID decodes null-terminated strings using a provided xor _key. aptly named lol */
#define LAIKA_BOX_SKID(_key) \
{ \
.unlockedData = {0}, /* reserved */ \
.code = { /* stack layout: \
[0] - unlockedData (ptr) \
[1] - data (ptr) \
[2] - key (uint8_t) \
[3] - working data (uint8_t) \
*/ \
LAIKA_MAKE_VM_IAB(OP_LOADCONST, 0, LAIKA_BOX_UNLOCKED_INDX), \
LAIKA_MAKE_VM_IAB(OP_LOADCONST, 1, LAIKA_BOX_DATA_INDX), \
LAIKA_MAKE_VM_IAB(OP_PUSHLIT, 2, _key), /* LOOP_START */ \
LAIKA_MAKE_VM_IAB(OP_READ, 3, 1), /* load data into working data */ \
LAIKA_MAKE_VM_IABC(OP_XOR, 3, 3, 2), /* xor data with key */ \
LAIKA_MAKE_VM_IAB(OP_WRITE, 0, 3), /* write data to unlockedData */ \
LAIKA_MAKE_VM_IA(OP_INCPTR, 0), \
LAIKA_MAKE_VM_IA(OP_INCPTR, 1), \
LAIKA_MAKE_VM_IAB(OP_TESTJMP, 3, -17), /* exit loop on null terminator */ \
OP_EXIT \
} \
}
/* ======================================[[ Raw Box API ]]====================================== */
LAIKA_FORCEINLINE void *laikaB_unlock(struct sLaikaB_box *box, void *data)
{
struct sLaikaV_vm vm = {
/* boxes have 2 reserved constants, [0] for the output, [1] for the input */
.constList =
{
[LAIKA_BOX_UNLOCKED_INDX] = LAIKA_MAKE_VM_PTR(box->unlockedData),
[LAIKA_BOX_SCRATCH_INDX] = LAIKA_MAKE_VM_PTR(box->scratch),
[LAIKA_BOX_DATA_INDX] = LAIKA_MAKE_VM_PTR(data),
},
.code = { 0 },
.stack = { 0 },
.pc = 0
};
memcpy(vm.code, box->code, LAIKA_VM_CODESIZE);
laikaV_execute(&vm);
return (void *)box->unlockedData;
}
/* safely zeros the unlockedData using libsodium's api for clearing sensitive data from memory */
LAIKA_FORCEINLINE void laikaB_lock(struct sLaikaB_box *box)
{
sodium_memzero(box->unlockedData, LAIKA_BOX_HEAPSIZE);
sodium_memzero(box->scratch, LAIKA_BOX_SCRATCH_SIZE);
}
/* include KEY_* & DATA_* macros for each obfuscated string */
#include "lboxconfig.h"
#endif

55
lib/include/core/lerror.h Normal file
View File

@@ -0,0 +1,55 @@
#ifndef LAIKA_ERROR_H
#define LAIKA_ERROR_H
#include "laika.h"
#include <setjmp.h>
#include <stdio.h>
/* defines errorstack size */
#define LAIKA_MAXERRORS 32
/* DO NOT RETURN/GOTO/BREAK or otherwise skip LAIKA_TRYEND */
#define LAIKA_TRY if (setjmp(eLaika_errStack[++eLaika_errIndx]) == 0) {
#define LAIKA_CATCH \
} \
else \
{
#define LAIKA_TRYEND \
} \
--eLaika_errIndx;
/* if eLaika_errIndx is >= 0, we have a safe spot to jump too if an error is thrown */
#define LAIKA_ISPROTECTED (eLaika_errIndx >= 0)
/* LAIKA_ERROR(printf args):
if called after a LAIKA_TRY block will jump to the previous LAIKA_CATCH/LAIKA_TRYEND block,
otherwise program is exit()'d. if DEBUG is defined printf is called with passed args, else
arguments are ignored.
*/
#ifndef DEBUG
# define LAIKA_ERROR(...) \
do { \
if (LAIKA_ISPROTECTED) \
longjmp(eLaika_errStack[eLaika_errIndx], 1); \
else \
exit(1); \
} while (0);
# define LAIKA_WARN(...) ((void)0) /* no op */
#else
# define LAIKA_ERROR(...) \
do { \
printf("[ERROR] : " __VA_ARGS__); \
if (LAIKA_ISPROTECTED) \
longjmp(eLaika_errStack[eLaika_errIndx], 1); \
else \
exit(1); \
} while (0);
# define LAIKA_WARN(...) printf("[WARN] : " __VA_ARGS__);
#endif
extern int eLaika_errIndx;
extern jmp_buf eLaika_errStack[LAIKA_MAXERRORS];
#endif

60
lib/include/core/lmem.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef LAIKA_MEM_H
#define LAIKA_MEM_H
#include "laika.h"
#define GROW_FACTOR 2
/* microsoft strikes again with their lack of support for VLAs */
#if _MSC_VER
# define VLA(type, var, sz) type *var = laikaM_malloc(sizeof(type) * sz);
# define ENDVLA(var) laikaM_free(var);
#else
# define VLA(type, var, sz) type var[sz];
# define ENDVLA(var) ((void)0) /* no op */
#endif
#define laikaM_malloc(sz) laikaM_realloc(NULL, sz)
#define laikaM_free(buf) laikaM_realloc(buf, 0)
/* ========================================[[ Vectors ]]======================================== */
#define laikaM_countVector(name) name##_COUNT
#define laikaM_capVector(name) name##_CAP
#define laikaM_newVector(type, name) \
type *name; \
int name##_COUNT; \
int name##_CAP;
#define laikaM_initVector(name, startCap) \
name = NULL; \
name##_COUNT = 0; \
name##_CAP = startCap;
#define laikaM_growVector(type, name, needed) \
if (name##_COUNT + needed >= name##_CAP || name == NULL) { \
name##_CAP = (name##_CAP + needed) * GROW_FACTOR; \
name = (type *)laikaM_realloc(name, sizeof(type) * name##_CAP); \
}
/* moves vector elements above indx down by numElem, removing numElem elements at indx */
#define laikaM_rmvVector(name, indx, numElem) \
do { \
int _i, _sz = ((name##_COUNT - indx) - numElem); \
for (_i = 0; _i < _sz; _i++) \
name[indx + _i] = name[indx + numElem + _i]; \
name##_COUNT -= numElem; \
} while (0);
/* moves vector elements above indx up by numElem, inserting numElem elements at indx */
#define laikaM_insertVector(name, indx, numElem) \
do { \
int _i; \
for (_i = name##_COUNT; _i > indx; _i--) \
name[_i] = name[_i - 1]; \
name##_COUNT += numElem; \
} while (0);
void *laikaM_realloc(void *buf, size_t sz);
#endif

View File

@@ -0,0 +1,15 @@
#ifndef LAIKA_RSA_H
#define LAIKA_RSA_H
#include "sodium.h"
#include <stdbool.h>
#define LAIKAENC_SIZE(sz) (sz + crypto_box_SEALBYTES)
bool laikaK_loadKeys(uint8_t *outPub, uint8_t *outPriv, const char *inPub, const char *inPriv);
bool laikaK_genKeys(uint8_t *outPub, uint8_t *outPriv);
bool laikaK_checkAuth(uint8_t *pubKey, uint8_t **authKeys, int keys);
#endif

39
lib/include/core/ltask.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef LAIKA_TASK_H
#define LAIKA_TASK_H
#include "laika.h"
#include <time.h>
typedef void (*taskCallback)(struct sLaika_taskService *service, struct sLaika_task *task,
clock_t currTick, void *uData);
struct sLaika_task
{
struct sLaika_task *next;
taskCallback callback;
void *uData;
long scheduled;
int delta;
};
struct sLaika_taskService
{
struct sLaika_task *headTask;
};
void laikaT_initTaskService(struct sLaika_taskService *service);
void laikaT_cleanTaskService(struct sLaika_taskService *service);
struct sLaika_task *laikaT_newTask(struct sLaika_taskService *service, int delta,
taskCallback callback, void *uData);
void laikaT_delTask(struct sLaika_taskService *service, struct sLaika_task *task);
void laikaT_pollTasks(struct sLaika_taskService *service);
/* will return the delta time in ms till the next event. -1 for no tasks scheduled */
int laikaT_timeTillTask(struct sLaika_taskService *service);
long laikaT_getTime(void);
#endif

178
lib/include/core/lvm.h Normal file
View File

@@ -0,0 +1,178 @@
#ifndef LAIKA_VM_H
#define LAIKA_VM_H
/* Laika VM:
This is an obfuscation technique where vital code can be executed in a
stack-based VM, inlined into the function. The VM instruction-set is fairly
simple, see the OP_* enum for avaliable opcodes and their expected arguments.
The VM is turing-complete, however the instruction-set has been curated to
fit this specific use case.
*/
#include "core/lerror.h"
#include "laika.h"
#include <inttypes.h>
#define LAIKA_VM_STACKSIZE 64
#define LAIKA_VM_CONSTSIZE 32
struct sLaikaV_vm_val
{
union
{
uint8_t i;
uint8_t *ptr;
};
};
struct sLaikaV_vm
{
struct sLaikaV_vm_val stack[LAIKA_VM_STACKSIZE];
struct sLaikaV_vm_val constList[LAIKA_VM_CONSTSIZE];
uint8_t code[LAIKA_VM_CODESIZE];
int pc;
};
#define LAIKA_MAKE_VM(_consts, _code) \
{ \
.constList = _consts, .code = _code, .pc = 0, .stack = { 0 } \
}
/* constants */
#define LAIKA_MAKE_VM_INT(_i) \
{ \
.i = _i \
}
#define LAIKA_MAKE_VM_PTR(_ptr) \
{ \
.ptr = _ptr \
}
/* instructions */
#define LAIKA_MAKE_VM_IA(opcode, a) opcode, a
#define LAIKA_MAKE_VM_IAB(opcode, a, b) opcode, a, b
#define LAIKA_MAKE_VM_IABC(opcode, a, b, c) opcode, a, b, c
enum
{
OP_EXIT,
OP_LOADCONST, /* stk_indx[uint8_t] = const_indx[uint8_t] */
OP_PUSHLIT, /* stk_indx[uint8_t].i = uint8_t */
OP_READ, /* stk_indx[uint8_t].i = *(int8_t*)stk_indx[uint8_t] */
OP_WRITE, /* *(uint8_t*)stk_indx[uint8_t].ptr = stk_indx[uint8_t].i */
OP_INCPTR, /* stk_indx[uint8_t].ptr++ */
OP_DECPTR, /* stk_indx[uint8_t].ptr-- */
/* arithmetic */
OP_ADD, /* stk_indx[uint8_t] = stk_indx[uint8_t] + stk_indx[uint8_t] */
OP_SUB, /* stk_indx[uint8_t] = stk_indx[uint8_t] - stk_indx[uint8_t] */
OP_MUL, /* stk_indx[uint8_t] = stk_indx[uint8_t] * stk_indx[uint8_t] */
OP_DIV, /* stk_indx[uint8_t] = stk_indx[uint8_t] / stk_indx[uint8_t] */
OP_AND, /* stk_indx[uint8_t] = stk_indx[uint8_t] & stk_indx[uint8_t] */
OP_OR, /* stk_indx[uint8_t] = stk_indx[uint8_t] | stk_indx[uint8_t] */
OP_XOR, /* stk_indx[uint8_t] = stk_indx[uint8_t] ^ stk_indx[uint8_t] */
/* control-flow */
OP_TESTJMP, /* if stk_indx[uint8_t] != 0, pc += [int8_t] */
/* misc. */
#ifdef DEBUG
OP_DEBUG
#endif
};
LAIKA_FORCEINLINE void laikaV_execute(struct sLaikaV_vm *vm)
{
#define READBYTE (vm->code[vm->pc++])
#define BINOP(x) \
{ \
uint8_t a = READBYTE; \
uint8_t b = READBYTE; \
uint8_t c = READBYTE; \
vm->stack[a].i = vm->stack[b].i x vm->stack[c].i; \
break; \
}
while (vm->code[vm->pc]) {
switch (vm->code[vm->pc++]) {
case OP_LOADCONST: {
uint8_t indx = READBYTE;
uint8_t constIndx = READBYTE;
vm->stack[indx] = vm->constList[constIndx];
break;
}
case OP_PUSHLIT: {
uint8_t indx = READBYTE;
uint8_t lit = READBYTE;
vm->stack[indx].i = lit;
break;
}
case OP_READ: {
uint8_t indx = READBYTE;
uint8_t ptr = READBYTE;
vm->stack[indx].i = *vm->stack[ptr].ptr;
break;
}
case OP_WRITE: {
uint8_t ptr = READBYTE;
uint8_t indx = READBYTE;
*vm->stack[ptr].ptr = vm->stack[indx].i;
break;
}
case OP_INCPTR: {
uint8_t ptr = READBYTE;
vm->stack[ptr].ptr++;
break;
}
case OP_DECPTR: {
uint8_t ptr = READBYTE;
vm->stack[ptr].ptr--;
break;
}
case OP_ADD:
BINOP(+);
case OP_SUB:
BINOP(-);
case OP_MUL:
BINOP(*);
case OP_DIV:
BINOP(/);
case OP_AND:
BINOP(&);
case OP_OR:
BINOP(|);
case OP_XOR:
BINOP(^);
case OP_TESTJMP: {
uint8_t indx = READBYTE;
int8_t jmp = READBYTE;
/* if stack indx is true, jump by jmp (signed 8-bit int) */
if (vm->stack[indx].i)
vm->pc += jmp;
break;
}
#ifdef DEBUG
case OP_DEBUG: {
int i;
/* print stack info */
for (i = 0; i < LAIKA_VM_STACKSIZE; i++)
printf("[%03d] - 0x%02x\n", i, vm->stack[i].i);
break;
}
#endif
default:
LAIKA_ERROR("laikaV_execute: unknown opcode [0x%02x]! pc: %d\n", vm->code[vm->pc],
vm->pc);
}
}
#undef READBYTE
#undef BINOP
}
#endif