diff --git a/.gitmodules b/.gitmodules index 1300966..c1f0582 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,3 @@ -[submodule "lib/zlib"] - path = lib/zlib - url = https://github.com/madler/zlib.git -[submodule "lib/lzma"] - path = lib/lzma - url = https://github.com/SWFRecomp/lzma.git [submodule "lib/c-hashmap"] path = lib/c-hashmap url = https://github.com/Mashpoe/c-hashmap.git @@ -13,3 +7,12 @@ [submodule "lib/o1heap"] path = lib/o1heap url = https://github.com/pavel-kirienko/o1heap.git +[submodule "lib/rbtree"] + path = lib/rbtree + url = https://github.com/SWFRecomp/rb-tree.git +[submodule "lib/libtess2"] + path = lib/libtess2 + url = https://github.com/SWFRecomp/libtess2.git +[submodule "lib/minimp3"] + path = lib/minimp3 + url = https://github.com/lieff/minimp3.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 899e135..2906b73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,16 +14,24 @@ option(NO_GRAPHICS "Build without graphics support (console-only)" OFF) # Core sources (always included) set(CORE_SOURCES ${PROJECT_SOURCE_DIR}/src/actionmodern/action.c + ${PROJECT_SOURCE_DIR}/src/actionmodern/objects.c + ${PROJECT_SOURCE_DIR}/src/actionmodern/free_thread.c ${PROJECT_SOURCE_DIR}/src/actionmodern/variables.c ${PROJECT_SOURCE_DIR}/src/memory/heap.c + ${PROJECT_SOURCE_DIR}/src/apis/rbtree/rbtree.c + ${PROJECT_SOURCE_DIR}/src/apis/swap-vector/swap_vector.c ${PROJECT_SOURCE_DIR}/src/utils.c - ${PROJECT_SOURCE_DIR}/lib/o1heap/o1heap/o1heap.c - - # Compile hashmap file directly to avoid undefined symbols on Linux + # Compile libs directly to avoid undefined symbols on Linux ${PROJECT_SOURCE_DIR}/lib/c-hashmap/map.c + ${PROJECT_SOURCE_DIR}/lib/rbtree/rb_tree.c + ${PROJECT_SOURCE_DIR}/lib/o1heap/o1heap/o1heap.c ) +file(GLOB_RECURSE RUNTIME_API_SOURCES ${PROJECT_SOURCE_DIR}/src/actionmodern/runtime_api/*.c) + + + if(NO_GRAPHICS) # Console-only mode message(STATUS "Building in NO_GRAPHICS mode (console-only)") @@ -33,8 +41,6 @@ if(NO_GRAPHICS) ${PROJECT_SOURCE_DIR}/src/libswf/swf_core.c ${PROJECT_SOURCE_DIR}/src/libswf/tag_stubs.c ) - - set(SOURCES ${CORE_SOURCES} ${SWF_SOURCES}) else() # Full graphics mode message(STATUS "Building in full graphics mode") @@ -43,36 +49,36 @@ else() ${PROJECT_SOURCE_DIR}/src/libswf/swf.c ${PROJECT_SOURCE_DIR}/src/libswf/tag.c ${PROJECT_SOURCE_DIR}/src/flashbang/flashbang.c + ${PROJECT_SOURCE_DIR}/src/flashbang/triangulation.c ) - - set(SOURCES ${CORE_SOURCES} ${SWF_SOURCES}) endif() +set(SOURCES ${CORE_SOURCES} ${SWF_SOURCES} ${RUNTIME_API_SOURCES}) + add_library(${PROJECT_NAME} STATIC ${SOURCES}) if (WIN32) -target_compile_options(${PROJECT_NAME} PRIVATE) + target_compile_options(${PROJECT_NAME} PRIVATE) else() -target_compile_options(${PROJECT_NAME} PRIVATE -Wno-format-truncation) + target_compile_options(${PROJECT_NAME} PRIVATE -Wno-format-truncation) endif() set(RENAME_ZCONF OFF) -add_subdirectory(${PROJECT_SOURCE_DIR}/lib/zlib) -add_subdirectory(${PROJECT_SOURCE_DIR}/lib/lzma) +add_subdirectory(${PROJECT_SOURCE_DIR}/lib/libtess2) if(NOT NO_GRAPHICS) add_subdirectory(${PROJECT_SOURCE_DIR}/lib/SDL3) target_link_libraries(${PROJECT_NAME} PUBLIC - zlibstatic - lzma + libtess2 SDL3::SDL3 + $<$:m> ) else() target_link_libraries(${PROJECT_NAME} PUBLIC - zlibstatic - lzma + libtess2 + $<$:m> ) endif() @@ -85,15 +91,18 @@ endif() target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/include/apis ${PROJECT_SOURCE_DIR}/include/actionmodern + ${PROJECT_SOURCE_DIR}/include/actionmodern/runtime_api ${PROJECT_SOURCE_DIR}/include/libswf ${PROJECT_SOURCE_DIR}/include/flashbang ${PROJECT_SOURCE_DIR}/include/memory + ${PROJECT_SOURCE_DIR}/lib/libtess2/Include + ${PROJECT_SOURCE_DIR}/lib/minimp3 ${PROJECT_SOURCE_DIR}/lib/c-hashmap - ${PROJECT_SOURCE_DIR}/lib/SDL3/include + ${PROJECT_SOURCE_DIR}/lib/rbtree ${PROJECT_SOURCE_DIR}/lib/o1heap/o1heap - zlib - lzma/liblzma/api + ${PROJECT_SOURCE_DIR}/lib/SDL3/include ) if(NOT NO_GRAPHICS) diff --git a/include/actionmodern/action.h b/include/actionmodern/action.h index eb7fff5..6df6b23 100644 --- a/include/actionmodern/action.h +++ b/include/actionmodern/action.h @@ -1,30 +1,45 @@ #pragma once #include +#include #include #include +#define STACK_VAR_SIZE (4 + 4 + 8 + 8) + #define PUSH(t, v) \ OLDSP = SP; \ - SP -= 4 + 4 + 8 + 8; \ + SP -= STACK_VAR_SIZE; \ SP &= ~7; \ STACK[SP] = t; \ VAL(u32, &STACK[SP + 4]) = OLDSP; \ - VAL(u64, &STACK[SP + 16]) = v; \ + VAL(u64, &STACK[SP + 16]) = v; // Push string with ID (for constant strings from compiler) -#define PUSH_STR_ID(v, n, id) \ +#define PUSH_STR_ID(v, id, n) \ OLDSP = SP; \ - SP -= 4 + 4 + 8 + 8; \ + SP -= STACK_VAR_SIZE; \ SP &= ~7; \ STACK[SP] = ACTION_STACK_VALUE_STRING; \ + STACK[SP + 1] = false; \ VAL(u32, &STACK[SP + 4]) = OLDSP; \ VAL(u32, &STACK[SP + 8]) = n; \ VAL(u32, &STACK[SP + 12]) = id; \ - VAL(char*, &STACK[SP + 16]) = v; \ + VAL(char*, &STACK[SP + 16]) = v; + +// Push dynamic string onto the stack +#define PUSH_STR_STACK(n) \ + OLDSP = SP; \ + SP -= (u32) (4 + 4 + 8 + (n + 1)); \ + SP &= ~7; \ + STACK[SP] = ACTION_STACK_VALUE_STRING; \ + STACK[SP + 1] = true; \ + VAL(u32, &STACK[SP + 4]) = OLDSP; \ + VAL(u32, &STACK[SP + 8]) = n; \ + VAL(u32, &STACK[SP + 12]) = 0; // Push string without ID (for dynamic strings, ID = 0) -#define PUSH_STR(v, n) PUSH_STR_ID(v, n, 0) +#define PUSH_STR(v, n) PUSH_STR_ID(v, 0, n) #define PUSH_STR_LIST(n, size) \ OLDSP = VAL(u32, &STACK[SP_SECOND_TOP + 4]); \ @@ -33,27 +48,74 @@ STACK[SP] = ACTION_STACK_VALUE_STR_LIST; \ VAL(u32, &STACK[SP + 4]) = OLDSP; \ VAL(u32, &STACK[SP + 8]) = n; \ + VAL(u32, &STACK[SP + 12]) = 0; -#define PUSH_VAR(p) pushVar(app_context, p); +#define PUSH_NULL() PUSH(ACTION_STACK_VALUE_NULL, 0) +#define PUSH_UNDEFINED() PUSH(ACTION_STACK_VALUE_UNDEFINED, 0) + +#define PUSH_F32(f) PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &(f))); +#define PUSH_F64(f) PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &(f))); +#define PUSH_INT(i) PUSH(ACTION_STACK_VALUE_INT, (i)); +#define PUSH_BOOL(b) PUSH(ACTION_STACK_VALUE_BOOLEAN, (b)); + +#define PUSH_OBJ(o) \ + PUSH(ACTION_STACK_VALUE_OBJECT, (u64) o) \ + OBJ_LOCK_WRITE(o, \ + { \ + retainObject(o); \ + }); + +#define PUSH_VAR(p) pushVar(app_context, p) #define POP() \ - SP = VAL(u32, &STACK[SP + 4]); \ + if (STACK_TOP_TYPE == ACTION_STACK_VALUE_OBJECT) \ + { \ + ASObject* o = (ASObject*) STACK_TOP_VALUE; \ + OBJ_LOCK_WRITE(o, \ + { \ + releaseObject(app_context, o); \ + }); \ + } \ + SP = VAL(u32, &STACK[SP + 4]); #define POP_2() \ POP(); \ - POP(); \ + POP(); + +#define DISCARD_ARGS(n) discardArgs(app_context, n) #define STACK_TOP_TYPE STACK[SP] +#define STACK_TOP_OWNS_MEM STACK[SP + 1] #define STACK_TOP_N VAL(u32, &STACK[SP + 8]) #define STACK_TOP_ID VAL(u32, &STACK[SP + 12]) #define STACK_TOP_VALUE VAL(u64, &STACK[SP + 16]) #define SP_SECOND_TOP VAL(u32, &STACK[SP + 4]) #define STACK_SECOND_TOP_TYPE STACK[SP_SECOND_TOP] +#define STACK_SECOND_TOP_OWNS_MEM STACK[SP_SECOND_TOP + 1] #define STACK_SECOND_TOP_N VAL(u32, &STACK[SP_SECOND_TOP + 8]) #define STACK_SECOND_TOP_ID VAL(u32, &STACK[SP_SECOND_TOP + 12]) #define STACK_SECOND_TOP_VALUE VAL(u64, &STACK[SP_SECOND_TOP + 16]) +#define FUNC_FLAG_PRELOAD_PARENT 0b0000000010000000 +#define FUNC_FLAG_PRELOAD_ROOT 0b0000000001000000 +#define FUNC_FLAG_SUPPRESS_SUPER 0b0000000000100000 +#define FUNC_FLAG_PRELOAD_SUPER 0b0000000000010000 +#define FUNC_FLAG_SUPPRESS_ARGUMENTS 0b0000000000001000 +#define FUNC_FLAG_PRELOAD_ARGUMENTS 0b0000000000000100 +#define FUNC_FLAG_SUPPRESS_THIS 0b0000000000000010 +#define FUNC_FLAG_PRELOAD_THIS 0b0000000000000001 +#define FUNC_FLAG_PRELOAD_GLOBAL 0b0000000100000000 + +#define IS_NULL(v) (v.type == ACTION_STACK_VALUE_NULL) +#define IS_UNDEFINED(v) (v.type == ACTION_STACK_VALUE_UNDEFINED) +#define IS_NULL_UNDEFINED(v) (IS_NULL(v) || IS_UNDEFINED(v)) + +#define IS_STR_T(t) (t == ACTION_STACK_VALUE_STRING || t == ACTION_STACK_VALUE_STR_LIST) +#define IS_NUM_T(t) (t == ACTION_STACK_VALUE_F32 || t == ACTION_STACK_VALUE_F64 || t == ACTION_STACK_VALUE_INT) + +#define RETURN_VOID() PUSH_UNDEFINED() + #define VAL(type, x) *((type*) x) #define INITIAL_STACK_SIZE 8388608 // 8 MB @@ -61,26 +123,115 @@ extern ActionVar* temp_val; -void initTime(); +extern ASObject* _global; + +typedef struct +{ + u8 reg; + u32 string_id; +} Function2Param; + +void initActions(SWFAppContext* app_context); +void freeActions(SWFAppContext* app_context); + +void discardArgs(SWFAppContext* app_context, u32 num_args); void pushVar(SWFAppContext* app_context, ActionVar* p); +void pushReg(SWFAppContext* app_context, u8 reg); + +void releaseObjectVar(SWFAppContext* app_context, ActionVar* var); + +void peekVar(SWFAppContext* app_context, ActionVar* var); +void popVar(SWFAppContext* app_context, ActionVar* var); + +void convertNumericToNumber(SWFAppContext* app_context, ActionVar* v); +void convertNumericToInteger(SWFAppContext* app_context, ActionVar* v); + +void toNumber(SWFAppContext* app_context, ActionVar* v); +void toPrimitive(SWFAppContext* app_context, ASObject* this); +void toString(SWFAppContext* app_context, ActionVar* v); + +ActionStackValueType convertString(SWFAppContext* app_context); +ActionStackValueType convertDouble(SWFAppContext* app_context); +ActionStackValueType convertIntECMA(SWFAppContext* app_context); + +ASProperty* getPropertyInThisScope(SWFAppContext* app_context, u32 string_id, const char* name, u32 name_len); +void setPropertyInThisScope(SWFAppContext* app_context, u32 string_id, const char* name, u32 name_len, ActionVar* value); + +void callFunction(SWFAppContext* app_context, ASObject* this, ActionVar* func_v, u32 num_args); +void getAndCallMethod(SWFAppContext* app_context, ASObject* this, u32 method_name, u32 num_args); +bool getAndCallMethodIfExists(SWFAppContext* app_context, ASObject* this, u32 method_name, u32 num_args); + +bool evaluateCondition(SWFAppContext* app_context); +// Arithmetic Operations void actionAdd(SWFAppContext* app_context); +void actionAdd2(SWFAppContext* app_context); void actionSubtract(SWFAppContext* app_context); void actionMultiply(SWFAppContext* app_context); void actionDivide(SWFAppContext* app_context); +void actionModulo(SWFAppContext* app_context); +void actionIncrement(SWFAppContext* app_context); +void actionDecrement(SWFAppContext* app_context); + +// Bitwise Operations +void actionBitAnd(SWFAppContext* app_context); +void actionBitOr(SWFAppContext* app_context); +void actionBitLShift(SWFAppContext* app_context); +void actionBitRShift(SWFAppContext* app_context); +void actionBitURShift(SWFAppContext* app_context); +void actionBitXor(SWFAppContext* app_context); + +// Comparison Operations void actionEquals(SWFAppContext* app_context); +void actionEquals2(SWFAppContext* app_context); void actionLess(SWFAppContext* app_context); +void actionLess2(SWFAppContext* app_context); +void actionGreater(SWFAppContext* app_context); void actionAnd(SWFAppContext* app_context); void actionOr(SWFAppContext* app_context); void actionNot(SWFAppContext* app_context); +// String Operations void actionStringEquals(SWFAppContext* app_context, char* a_str, char* b_str); void actionStringLength(SWFAppContext* app_context, char* v_str); void actionStringAdd(SWFAppContext* app_context, char* a_str, char* b_str); +// Variable Operations void actionGetVariable(SWFAppContext* app_context); void actionSetVariable(SWFAppContext* app_context); +void actionToInteger(SWFAppContext* app_context); +void actionToNumber(SWFAppContext* app_context); +void actionToString(SWFAppContext* app_context); +void actionTypeOf(SWFAppContext* app_context); +// Utility Operations void actionTrace(SWFAppContext* app_context); -void actionGetTime(SWFAppContext* app_context); \ No newline at end of file +void actionGetTime(SWFAppContext* app_context); + +// Object Operations +void actionGetMember(SWFAppContext* app_context); +void actionSetMember(SWFAppContext* app_context); +void actionEnumerate(SWFAppContext* app_context, char* str_buffer); +void actionDelete(SWFAppContext* app_context); +void actionDelete2(SWFAppContext* app_context, char* str_buffer); +void actionNewObject(SWFAppContext* app_context); +void actionNewMethod(SWFAppContext* app_context); +void actionExtends(SWFAppContext* app_context); +void actionInitObject(SWFAppContext* app_context); + +// Array Operations +void actionInitArray(SWFAppContext* app_context); + +// Function Operations +void actionDefineLocal(SWFAppContext* app_context); +void actionDefineLocal2(SWFAppContext* app_context); +void actionCallFunction(SWFAppContext* app_context); +void actionCallMethod(SWFAppContext* app_context); + +// Stack/Register Operations +void actionStoreRegister(SWFAppContext* app_context, u8 register_num); + +// Function Definitions +void actionDefineFunction(SWFAppContext* app_context, u32 string_id, action_func func, u32* args, bool anonymous); +void actionDefineFunction2(SWFAppContext* app_context, u32 string_id, action_func func, Function2Param* args, u8 reg_count, u16 flags, bool anonymous); \ No newline at end of file diff --git a/include/actionmodern/free_thread.h b/include/actionmodern/free_thread.h new file mode 100644 index 0000000..df07adb --- /dev/null +++ b/include/actionmodern/free_thread.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +extern recomp_rwlock_t object_queue_lock; +extern rbtree object_free_queue; + +extern recomp_thread_t free_thread_handle; + +DECLARE_RUNTIME_THREAD_FUNC(freeThread); \ No newline at end of file diff --git a/include/actionmodern/initial_strings_decls.h b/include/actionmodern/initial_strings_decls.h new file mode 100644 index 0000000..815bc2c --- /dev/null +++ b/include/actionmodern/initial_strings_decls.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include + +typedef void (*action_runtime_func)(SWFAppContext* app_context, ASObject* this, u32 num_args); + +typedef enum +{ + STR_ID_EMPTY = 1, + STR_ID_GLOBAL, + STR_ID_ROOT, + STR_ID_PARENT, + STR_ID_FLASH, + STR_ID_DISPLAY, + STR_ID_RECOMP, + STR_ID_RECOMP_GET_LAST_KEY, + STR_ID_RECOMP_SET_DISPLAY_SCALE, + STR_ID_RECOMP_SIN, + STR_ID_RECOMP_COS, + STR_ID_OBJECT, + STR_ID_DESTROY, + STR_ID_TO_STRING, + STR_ID_VALUE_OF, + STR_ID_RECOMP_ID, + STR_ID_FUNCTION, + STR_ID_NUMBER, + STR_ID_STRING, + STR_ID_ARRAY, + STR_ID_PUSH, + STR_ID_POP, + STR_ID_THIS, + STR_ID_ARGUMENTS, + STR_ID_SUPER, + STR_ID_CONSTRUCTOR, + STR_ID_PROTOTYPE, + STR_ID_PROTO, + STR_ID_LENGTH, + STR_ID_MOVIECLIP, + STR_ID__PARENT, + STR_ID__ROTATION, + STR_ID__X, + STR_ID__Y, + STR_ID__XSCALE, + STR_ID__YSCALE, + STR_ID_ATTACH_BITMAP, + STR_ID_CREATE_EMPTY_MOVIECLIP, + STR_ID_REMOVE_MOVIECLIP, + STR_ID_ON_ENTER_FRAME, + STR_ID_ON_KEY_DOWN, + STR_ID_BITMAP_DATA, + STR_ID_LOAD_BITMAP, + STR_ID_WIDTH, + STR_ID_HEIGHT, + STR_ID_SOUND, + STR_ID_LOAD_SOUND, + STR_ID_ON_LOAD, + STR_ID_START, + STR_ID_KEY, + STR_ID_FIRE_LISTENERS_DOWN, + STR_ID_FIRE_LISTENERS_UP, + STR_ID_ASSETPROPFLAGS, + STR_ID_MATH, + STR_ID_ABS, + STR_ID_X, + STR_ID_Y, + STR_ID_Z, + STR_ID_NAN, + STR_ID_POSITIVE_INFINITY, + STR_ID_NEGATIVE_INFINITY, + STR_ID_MAX_VALUE, + STR_ID_MIN_VALUE, +} StringIds; + +typedef struct +{ + u32 object_string_id; + u32 func_string_id; + action_runtime_func func; + bool constructor; +} RuntimeFunc; \ No newline at end of file diff --git a/include/actionmodern/initial_strings_defs.h b/include/actionmodern/initial_strings_defs.h new file mode 100644 index 0000000..be1da8c --- /dev/null +++ b/include/actionmodern/initial_strings_defs.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +RuntimeFunc runtime_funcs[] = +{ + {0, STR_ID_OBJECT, Object_new, true}, + {0, STR_ID_FUNCTION, Function_new, true}, + {0, STR_ID_NUMBER, Number_new, true}, + {0, STR_ID_STRING, String_new, true}, + {0, STR_ID_ARRAY, Array_new, true}, + {0, STR_ID_MOVIECLIP, MovieClip_new, true}, + {0, STR_ID_SOUND, Sound_new, true}, + + {0, STR_ID_ASSETPROPFLAGS, ASSetPropFlags, false}, + {0, STR_ID_RECOMP_GET_LAST_KEY, recompGetLastKey, false}, + {0, STR_ID_RECOMP_SET_DISPLAY_SCALE, recompSetDisplayScale, false}, + {0, STR_ID_RECOMP_SIN, recompSin, false}, + {0, STR_ID_RECOMP_COS, recompCos, false}, +}; + +RuntimeFunc runtime_meths[] = +{ + {STR_ID_OBJECT, STR_ID_TO_STRING, Object_toString, false}, + {STR_ID_OBJECT, STR_ID_VALUE_OF, Object_valueOf, false}, + {STR_ID_OBJECT, STR_ID_RECOMP_ID, Object_recompId, false}, + {STR_ID_NUMBER, STR_ID_TO_STRING, Number_toString, false}, + {STR_ID_NUMBER, STR_ID_VALUE_OF, Number_valueOf, false}, + {STR_ID_STRING, STR_ID_TO_STRING, String_toString, false}, + {STR_ID_STRING, STR_ID_VALUE_OF, String_valueOf, false}, + {STR_ID_ARRAY, STR_ID_PUSH, Array_push, false}, + {STR_ID_ARRAY, STR_ID_POP, Array_pop, false}, + {STR_ID_MOVIECLIP, STR_ID_ATTACH_BITMAP, MovieClip_attachBitmap, false}, + {STR_ID_MOVIECLIP, STR_ID_CREATE_EMPTY_MOVIECLIP, MovieClip_createEmptyMovieClip, false}, + {STR_ID_MOVIECLIP, STR_ID_REMOVE_MOVIECLIP, MovieClip_removeMovieClip, false}, + {STR_ID_SOUND, STR_ID_LOAD_SOUND, Sound_loadSound, false}, + {STR_ID_SOUND, STR_ID_START, Sound_start, false}, +}; + +action_runtime_func static_initializers[] = +{ + Number_init, +}; \ No newline at end of file diff --git a/include/actionmodern/object_struct.h b/include/actionmodern/object_struct.h new file mode 100644 index 0000000..e1c0ae8 --- /dev/null +++ b/include/actionmodern/object_struct.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include + +typedef struct +{ + u32 id; + rbtree t; + u32 refcount; + recomp_rwlock_t lock; + void* extra_data; + bool reached; + bool used; + bool blocked; + bool freed; + SwapVector neighbors; + SwapVector blocked_list; + u32 temp_rc; +} ASObject; \ No newline at end of file diff --git a/include/actionmodern/objects.h b/include/actionmodern/objects.h new file mode 100644 index 0000000..29dfe26 --- /dev/null +++ b/include/actionmodern/objects.h @@ -0,0 +1,170 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +#define IS_OBJ(v) ((v.type & 0xF0) == 0x10) +#define IS_OBJ_P(v) ((v->type & 0xF0) == 0x10) +#define IS_OBJ_T(t) ((t & 0xF0) == 0x10) + +#define OBJ_LOCK_READ(obj, code) \ + rwlock_lock_read(&((ASObject*) (obj))->lock); \ + code \ + rwlock_unlock_read(&((ASObject*) (obj))->lock); + +#define OBJ_LOCK_WRITE(obj, code) \ + rwlock_lock_write(&((ASObject*) (obj))->lock); \ + code \ + rwlock_unlock_write(&((ASObject*) (obj))->lock); + +/** + * ASObject - ActionScript Object with Reference Counting + * + * This structure implements compile-time reference counting for object/array opcodes. + * The recompiler (SWFRecomp) emits inline refcount increment/decrement operations, + * providing deterministic memory management without runtime GC. + */ + +/** + * Property Attribute Flags (ECMA-262 compliant) + * + * These flags control property behavior during enumeration, deletion, and assignment. + */ +#define PROPERTY_FLAG_ENUMERABLE 0x01 // Property appears in for..in loops (default for user properties) +#define PROPERTY_FLAG_WRITABLE 0x02 // Property can be modified (default for user properties) +#define PROPERTY_FLAG_CONFIGURABLE 0x04 // Property can be deleted (default for user properties) + +// Default flags for user-created properties (fully mutable and enumerable) +#define PROPERTY_FLAGS_DEFAULT (PROPERTY_FLAG_ENUMERABLE | PROPERTY_FLAG_WRITABLE | PROPERTY_FLAG_CONFIGURABLE) + +// Flags for DontEnum properties (internal/built-in properties) +#define PROPERTY_FLAGS_DONTENUM (PROPERTY_FLAG_WRITABLE | PROPERTY_FLAG_CONFIGURABLE) + +typedef struct +{ + rbnode n; + ActionVar value; // Property value (can be any type) +} ASProperty; + +typedef struct +{ + struct rb_node n; + u64 key; +} objnode; + +/** + * Object Lifecycle Primitives + * + * These functions are called by generated code to manage object lifetimes. + */ + +// Allocate new object +ASObject* allocObjectCommon(SWFAppContext* app_context); +ASObject* allocObject(SWFAppContext* app_context); + +// Increment reference count +// Should be called when: +// - Storing object in a variable +// - Adding object to an array/container +// - Assigning object to a property +// - Returning object from a function +void retainObject(ASObject* obj); + +// Decrement reference count, free if zero +// Should be called when: +// - Popping object from stack (if not stored) +// - Overwriting a variable that held an object +// - Removing object from array +// - Function/scope cleanup +void releaseObject(SWFAppContext* app_context, ASObject* obj); + +void destroyObject(SWFAppContext* app_context, ASObject* obj); + +/** + * Property Management + * + * Functions for manipulating object properties. + */ + +// Get property by name (returns NULL if not found) +ASProperty* getProperty(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length); + +// Get property value, or give undefined +void getPropertyVar(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length, ActionVar* out_var); + +// Get or create property by name +// IMPORTANT: IF YOU CREATE A PROPERTY THAT HOLDS AN OBJECT +// RETAIN IT RIGHT AFTER +ASProperty* getOrCreateProperty(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length, bool* created); + +// Get property by name with prototype chain traversal +// Walks up the __proto__ chain to find inherited properties +void getPropertyVarWithPrototype(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length, ActionVar* out_v); + +// Set property by name (creates if not exists) +// Handles refcount management if value is an object +void setProperty(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length, ActionVar* value); + +// Delete property by name (returns true if deleted or not found, false if protected) +// Handles refcount management if value is an object +bool deleteProperty(SWFAppContext* app_context, ASObject* obj, const char* name, u32 name_length); + +// Get the constructor function for an object +// Returns the constructor property +ASObject* getConstructor(SWFAppContext* app_context, ASObject* obj); + +/** + * ASArray - ActionScript Array with Reference Counting + * + * Arrays store elements in a dynamic array with automatic growth. + * Like objects, arrays use reference counting for memory management. + */ + +typedef struct ASArray +{ + u32 refcount; // Reference count (starts at 1 on allocation) + u32 length; // Number of elements in the array + u32 capacity; // Allocated capacity + ActionVar* elements; // Dynamic array of elements +} ASArray; + +/** + * Array Lifecycle Primitives + */ + +// Allocate new array with initial capacity +// Returns array with refcount = 1 +ASArray* allocArray(SWFAppContext* app_context, u32 initial_capacity); + +// Increment reference count for array +void retainArray(ASArray* arr); + +// Decrement reference count for array, free if zero +void releaseArray(SWFAppContext* app_context, ASArray* arr); + +// Get element at index (returns NULL if out of bounds) +ActionVar* getArrayElement(ASArray* arr, u32 index); + +// Set element at index (grows array if needed) +void setArrayElement(SWFAppContext* app_context, ASArray* arr, u32 index, ActionVar* value); + +/** + * Debug/Testing Functions + */ + +#ifdef DEBUG +// Verify object refcount matches expected value (assertion) +void assertRefcount(ASObject* obj, u32 expected); + +// Print object state for debugging +void printObject(ASObject* obj); + +// Print array state for debugging +void printArray(ASArray* arr); +#endif \ No newline at end of file diff --git a/include/actionmodern/runtime_api/Array.h b/include/actionmodern/runtime_api/Array.h new file mode 100644 index 0000000..eaacbc2 --- /dev/null +++ b/include/actionmodern/runtime_api/Array.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#define AR_EXTDATA_OF(o, member) (((ArrayData*) o->extra_data)->member) + +typedef struct +{ + ActionVar* data; + size_t length; + size_t capacity; + + ActionVar undef; +} ArrayData; + +void Array_init(SWFAppContext* app_context, ASObject* this, u32 num_args); + +void Array_new(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Array_push(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Array_pop(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Array_toString(SWFAppContext* app_context, ASObject* this, u32 num_args); + +ActionVar* Array_getElement(SWFAppContext* app_context, ASObject* this, s32 i); +void Array_setElement(SWFAppContext* app_context, ASObject* this, s32 i, ActionVar* v); +void Array_setLength(SWFAppContext* app_context, ASObject* this, size_t new_length); +void Array_destroy(SWFAppContext* app_context, ASObject* this); \ No newline at end of file diff --git a/include/actionmodern/runtime_api/BitmapData.h b/include/actionmodern/runtime_api/BitmapData.h new file mode 100644 index 0000000..8381dd3 --- /dev/null +++ b/include/actionmodern/runtime_api/BitmapData.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#define BM_EXTDATA_OF(o, member) (((BitmapData*) o->extra_data)->member) + +typedef struct +{ + u16 char_id; + ASObject* _parent; + size_t parent_depth; + + u16 bitmap_id; + + u32 width; + u32 height; +} BitmapData; + +void BitmapData_new(SWFAppContext* app_context, ASObject* this, u32 num_args); +void BitmapData_loadBitmap(SWFAppContext* app_context, ASObject* this, u32 num_args); + +bool BitmapData_getMember(SWFAppContext* app_context, ASObject* this, u32 string_id, ActionVar* out_v); \ No newline at end of file diff --git a/include/actionmodern/runtime_api/Function.h b/include/actionmodern/runtime_api/Function.h new file mode 100644 index 0000000..1cf450b --- /dev/null +++ b/include/actionmodern/runtime_api/Function.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +typedef struct +{ + action_func func; + void* args; + FunctionType func_type; + u8 reg_count; + u16 flags; + u32 func_name_string_id; +} FunctionData; + +void Function_new(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Function_init_object(SWFAppContext* app_context, ASObject* this); + +void Function_set_members(SWFAppContext* app_context, ASObject* this, FunctionType func_type, action_func func, void* args, u8 reg_count, u16 flags, u32 func_name_string_id); + +action_func Function_get_func(SWFAppContext* app_context, ASObject* this); +void* Function_get_args(SWFAppContext* app_context, ASObject* this); +FunctionType Function_get_func_type(SWFAppContext* app_context, ASObject* this); +u8 Function_get_reg_count(SWFAppContext* app_context, ASObject* this); +u16 Function_get_flags(SWFAppContext* app_context, ASObject* this); +u32 Function_get_func_name_string_id(SWFAppContext* app_context, ASObject* this); \ No newline at end of file diff --git a/include/actionmodern/runtime_api/MovieClip.h b/include/actionmodern/runtime_api/MovieClip.h new file mode 100644 index 0000000..9b01c9f --- /dev/null +++ b/include/actionmodern/runtime_api/MovieClip.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#define MC_EXTDATA_OF(o, member) (((MovieClipData*) o->extra_data)->member) + +typedef struct +{ + u16 char_id; + ASObject* _parent; + size_t parent_depth; + + size_t max_depth; + + u32 transform_id; + + ASObject** children; + size_t display_list_capacity; + + bool has_tris; + u32 tri_count; + u32* tris; + + u32 bitmap_at; + + f64 _rotation; + f64 _x; + f64 _y; + f64 _xscale; + f64 _yscale; + + bool _multiline; + bool _visible; +} MovieClipData; + +void MovieClip_new(SWFAppContext* app_context, ASObject* this, u32 num_args); + +ASObject* MovieClip_create(SWFAppContext* app_context); +void MovieClip_applyTransformsParents(SWFAppContext* app_context, ASObject* this, f32* mat); +void MovieClip_placeObject2_internal(SWFAppContext* app_context, ASObject* this, u32 depth, u32 char_id, u32 transform_id); + +void MovieClip_attachBitmap(SWFAppContext* app_context, ASObject* this, u32 num_args); +void MovieClip_createTextField(SWFAppContext* app_context, ASObject* this, u32 num_args); +void MovieClip_createEmptyMovieClip(SWFAppContext* app_context, ASObject* this, u32 num_args); +void MovieClip_removeMovieClip(SWFAppContext* app_context, ASObject* this, u32 num_args); +void MovieClip_destroy(SWFAppContext* app_context, ASObject* this); + +bool MovieClip_getMember(SWFAppContext* app_context, ASObject* this, u32 string_id, ActionVar* out_v); +bool MovieClip_setMember(SWFAppContext* app_context, ASObject* this, u32 string_id, ActionVar* v); \ No newline at end of file diff --git a/include/actionmodern/runtime_api/Number.h b/include/actionmodern/runtime_api/Number.h new file mode 100644 index 0000000..f0efce7 --- /dev/null +++ b/include/actionmodern/runtime_api/Number.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct +{ + ActionVar num; +} NumberData; + +void Number_init(SWFAppContext* app_context, ASObject* this, u32 num_args); + +void Number_new(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Number_toString(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Number_valueOf(SWFAppContext* app_context, ASObject* this, u32 num_args); \ No newline at end of file diff --git a/include/actionmodern/runtime_api/Object.h b/include/actionmodern/runtime_api/Object.h new file mode 100644 index 0000000..fc10651 --- /dev/null +++ b/include/actionmodern/runtime_api/Object.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +void Object_new(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Object_toString(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Object_valueOf(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Object_recompId(SWFAppContext* app_context, ASObject* this, u32 num_args); \ No newline at end of file diff --git a/include/actionmodern/runtime_api/Sound.h b/include/actionmodern/runtime_api/Sound.h new file mode 100644 index 0000000..a2a2be0 --- /dev/null +++ b/include/actionmodern/runtime_api/Sound.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +typedef struct +{ + char* samples; + size_t byte_count; + bool loaded; + + size_t stream_id; +} SoundData; + +void Sound_new(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Sound_loadSound(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Sound_start(SWFAppContext* app_context, ASObject* this, u32 num_args); +void Sound_destroy(SWFAppContext* app_context, ASObject* this); \ No newline at end of file diff --git a/include/actionmodern/runtime_api/String_recomp.h b/include/actionmodern/runtime_api/String_recomp.h new file mode 100644 index 0000000..29b4fbb --- /dev/null +++ b/include/actionmodern/runtime_api/String_recomp.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct +{ + ActionVar str; +} StringData; + +void String_init(SWFAppContext* app_context, ASObject* this, u32 num_args); + +void String_new(SWFAppContext* app_context, ASObject* this, u32 num_args); +void String_toString(SWFAppContext* app_context, ASObject* this, u32 num_args); +void String_valueOf(SWFAppContext* app_context, ASObject* this, u32 num_args); \ No newline at end of file diff --git a/include/actionmodern/runtime_api/toplevel.h b/include/actionmodern/runtime_api/toplevel.h new file mode 100644 index 0000000..847837a --- /dev/null +++ b/include/actionmodern/runtime_api/toplevel.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +void ASSetPropFlags(SWFAppContext* app_context, ASObject* this, u32 num_args); +void recompGetLastKey(SWFAppContext* app_context, ASObject* this, u32 num_args); +void recompSetDisplayScale(SWFAppContext* app_context, ASObject* this, u32 num_args); +void recompSin(SWFAppContext* app_context, ASObject* this, u32 num_args); +void recompCos(SWFAppContext* app_context, ASObject* this, u32 num_args); \ No newline at end of file diff --git a/include/actionmodern/stackvalue.h b/include/actionmodern/stackvalue.h index 4e39ab5..164c20e 100644 --- a/include/actionmodern/stackvalue.h +++ b/include/actionmodern/stackvalue.h @@ -6,6 +6,12 @@ typedef enum { ACTION_STACK_VALUE_STRING = 0, ACTION_STACK_VALUE_F32 = 1, + ACTION_STACK_VALUE_NULL = 2, + ACTION_STACK_VALUE_UNDEFINED = 3, + ACTION_STACK_VALUE_REGISTER = 4, + ACTION_STACK_VALUE_BOOLEAN = 5, ACTION_STACK_VALUE_F64 = 6, - ACTION_STACK_VALUE_STR_LIST = 10 + ACTION_STACK_VALUE_INT = 7, + ACTION_STACK_VALUE_STR_LIST = 10, + ACTION_STACK_VALUE_OBJECT = 0x10, } ActionStackValueType; \ No newline at end of file diff --git a/include/actionmodern/variables.h b/include/actionmodern/variables.h index a72160b..6bebb3f 100644 --- a/include/actionmodern/variables.h +++ b/include/actionmodern/variables.h @@ -4,32 +4,52 @@ #include #include +typedef enum +{ + FUNC_TYPE_1 = 1, + FUNC_TYPE_2, + FUNC_TYPE_3, +} FunctionType; + +typedef struct +{ + void* var_map; + size_t next_str_id; +} VarCtx; + typedef struct { ActionStackValueType type; - u32 str_size; - u32 string_id; + union { - u64 value; + // string struct { - char* heap_ptr; + u32 str_size; + u32 string_id; bool owns_memory; }; }; + + // value + union + { + u64 value; + u64 u64; + s64 s64; + u32 u32; + s32 s32; + char* str; + f32 f32; + f64 f64; + bool b; + ASObject* object; + }; } ActionVar; -void initMap(); +void initMap(SWFAppContext* app_context); void freeMap(SWFAppContext* app_context); -// Array-based variable storage for constant string IDs -extern ActionVar** var_array; -extern size_t var_array_size; - -void initVarArray(SWFAppContext* app_context, size_t max_string_id); -ActionVar* getVariableById(SWFAppContext* app_context, u32 string_id); - -ActionVar* getVariable(SWFAppContext* app_context, char* var_name, size_t key_size); -char* materializeStringList(SWFAppContext* app_context); -void setVariableWithValue(SWFAppContext* app_context, ActionVar* var); \ No newline at end of file +u32 getStringId(SWFAppContext* app_context, char* str, size_t str_size); +char* materializeStringList(SWFAppContext* app_context); \ No newline at end of file diff --git a/include/apis/rbtree.h b/include/apis/rbtree.h new file mode 100644 index 0000000..b2ec1d2 --- /dev/null +++ b/include/apis/rbtree.h @@ -0,0 +1,98 @@ +#pragma once + +#include + +#include + +#include +#include +#include + +#define RBT_GET_OR_INS(t, s, c) rbtree_get_or_insert(app_context, t, s, c) +#define RBT_GET_OR_INS_U64(t, s) rbtree_get_or_insert_u64(app_context, t, s) +#define RBT_INS_U64(t, s) rbtree_insert_u64(app_context, t, s) + +/** + * Red-Black Tree interface. + * + * Wrapper around red-black tree. + */ + +typedef struct +{ + struct rb_node n; + u32 string_id; +} rbnode; + +/** + * Initialize the tree. + * + * @param t Pointer to tree to be inited + * @param struct_size Size of struct rbnode is in + */ +void rbtree_init(rbtree* t, size_t struct_size); + +/** + * Get a node and return it if it exists, or return NULL if it doesn't. + * + * @param t Pointer to "this" rbtree + * @param string_id Unique integer generated by recompiler id'ing a string + * @return Pointer to node + */ +rbnode* rbtree_get(rbtree* t, u32 string_id); + +/** + * Get a node that might not exist, or create it if it doesn't. + * + * @param app_context Pointer to the app context + * @param t Pointer to "this" rbtree + * @param string_id Unique integer generated by recompiler id'ing a string + * @return Pointer to existing/newly-created node + */ +rbnode* rbtree_get_or_insert(SWFAppContext* app_context, rbtree* t, u32 string_id, bool* created); + +/** + * Get a node and return it if it exists, or return NULL if it doesn't. + * This function accepts a u64 key + * + * @param t Pointer to "this" rbtree + * @param key Unique integer identifying the node to return + * @return Pointer to node + */ +void* rbtree_get_u64(rbtree* t, u64 key); + +/** + * Get a node that might not exist, or create it if it doesn't. + * + * @param app_context Pointer to the app context + * @param t Pointer to "this" rbtree + * @param key Unique integer identifying the node to return + * @return Pointer to existing/newly-created node + */ +void* rbtree_get_or_insert_u64(SWFAppContext* app_context, rbtree* t, u64 key); + +/** + * Insert a node that may not exist, or do nothing if it does already. + * + * @param app_context Pointer to the app context + * @param t Pointer to "this" rbtree + * @param key Unique integer identifying the node to insert + */ +void rbtree_insert_u64(SWFAppContext* app_context, rbtree* t, u64 key); + +/** + * Remove a node, or do nothing if it does not exist. + * + * @param app_context Pointer to the app context + * @param t Pointer to "this" rbtree + * @param key Unique integer identifying the node to remove + */ +void rbtree_remove_u64(SWFAppContext* app_context, rbtree* t, u64 key); + +/** + * Removes the root of the tree and returns it. + * + * @param t Pointer to "this" rbtree + * @return Pointer to root node + */ +void* rbtree_pop_root(rbtree* t); \ No newline at end of file diff --git a/include/apis/rbtree_struct.h b/include/apis/rbtree_struct.h new file mode 100644 index 0000000..7668d2c --- /dev/null +++ b/include/apis/rbtree_struct.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include + +typedef struct +{ + struct rb_tree t; + size_t length; + size_t struct_size; +} rbtree; \ No newline at end of file diff --git a/include/apis/swap_vector.h b/include/apis/swap_vector.h new file mode 100644 index 0000000..ac0a304 --- /dev/null +++ b/include/apis/swap_vector.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include + +#define SVEC_GET(v, t, i) ((t*) (((t*) ((v)->data)) + i)) +#define SVEC_GET_TOP(v, t) ((t*) (((t*) ((v)->data)) + ((v)->length - 1))) + +#define SVEC_INIT(v) svec_init(app_context, v) +#define SVEC_SIZED_INIT(v, s) svec_sized_init(app_context, v, s) +#define SVEC_PUSH(v, x) svec_push(app_context, v, (uintptr_t) x) +#define SVEC_BUMP(v) svec_bump(app_context, v) +#define SVEC_REMOVE(v, i) svec_remove(v, i) +#define SVEC_POP(v) svec_pop(v) +#define SVEC_CLEAR(v) svec_clear(v) +#define SVEC_RELEASE(v) svec_release(app_context, v) +#define SVEC_TOP(v) (v)->data[(v)->length - 1] + +typedef struct +{ + union + { + char* restrict arena; + uintptr_t* restrict data; + }; + size_t struct_size; + size_t length; + size_t length_bytes; + size_t arena_capacity; +} SwapVector; + +typedef struct SWFAppContext SWFAppContext; + +void svec_init(SWFAppContext* app_context, SwapVector* v); +void svec_sized_init(SWFAppContext* app_context, SwapVector* v, size_t struct_size); +void svec_push(SWFAppContext* app_context, SwapVector* v, uintptr_t value); +void svec_bump(SWFAppContext* app_context, SwapVector* v); +void svec_remove(SwapVector* v, size_t index); +void svec_pop(SwapVector* v); +void svec_clear(SwapVector* v); +void svec_release(SWFAppContext* app_context, SwapVector* v); \ No newline at end of file diff --git a/include/common.h b/include/common.h index 87d61f3..9a68a65 100644 --- a/include/common.h +++ b/include/common.h @@ -5,9 +5,13 @@ #include #include +// TODO: use __builtin_unreachable() in EXC and EXC_ARG + #define THROW *((u32*) 0) = 0; #define EXC(str) fprintf(stderr, str); THROW; #define EXC_ARG(str, arg) fprintf(stderr, str, arg); THROW; +#define UNIMPLEMENTED(str) EXC_ARG("unimplemented: %s\n", str); +#define UNREACHABLE(str) EXC_ARG("unreachable: %s\n", str); typedef int8_t s8; typedef int16_t s16; @@ -17,4 +21,7 @@ typedef int64_t s64; typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; -typedef uint64_t u64; \ No newline at end of file +typedef uint64_t u64; + +typedef float f32; +typedef double f64; \ No newline at end of file diff --git a/include/flashbang/flashbang.h b/include/flashbang/flashbang.h index 15e8de3..6ec6976 100644 --- a/include/flashbang/flashbang.h +++ b/include/flashbang/flashbang.h @@ -1,85 +1,31 @@ #pragma once -#include +#include +#include -#include -#include - -typedef struct -{ - int width; - int height; - - const float* stage_to_ndc; - - size_t bitmap_count; - size_t bitmap_highest_w; - size_t bitmap_highest_h; - - size_t current_bitmap; - u32* bitmap_sizes; - - char* shape_data; - size_t shape_data_size; - char* transform_data; - size_t transform_data_size; - char* color_data; - size_t color_data_size; - char* uninv_mat_data; - size_t uninv_mat_data_size; - char* gradient_data; - size_t gradient_data_size; - char* bitmap_data; - size_t bitmap_data_size; - char* cxform_data; - size_t cxform_data_size; - - SDL_Window* window; - SDL_GPUDevice* device; - - SDL_GPUTexture* dummy_tex; - SDL_GPUSampler* dummy_sampler; - - SDL_GPUBuffer* vertex_buffer; - SDL_GPUBuffer* xform_buffer; - SDL_GPUBuffer* color_buffer; - SDL_GPUBuffer* uninv_mat_buffer; - SDL_GPUBuffer* inv_mat_buffer; - SDL_GPUBuffer* bitmap_sizes_buffer; - SDL_GPUBuffer* cxform_buffer; - - SDL_GPUTexture* gradient_tex_array; - SDL_GPUSampler* gradient_sampler; - - SDL_GPUTransferBuffer* bitmap_transfer; - SDL_GPUTransferBuffer* bitmap_sizes_transfer; - SDL_GPUTexture* bitmap_tex_array; - SDL_GPUSampler* bitmap_sampler; - - SDL_GPUTexture* msaa_texture; - SDL_GPUTexture* resolve_texture; - - SDL_GPUGraphicsPipeline* graphics_pipeline; - - SDL_GPUCommandBuffer* command_buffer; - SDL_GPURenderPass* render_pass; - - // Window background color - u8 red; - u8 green; - u8 blue; -} FlashbangContext; +extern const float identity[16]; +extern const float identity_cxform[20]; void flashbang_init(FlashbangContext* context, SWFAppContext* app_context); -int flashbang_poll(); +int flashbang_poll(FlashbangContext* context, SWFAppContext* app_context); +size_t flashbang_create_audio_stream(FlashbangContext* context, SWFAppContext* app_context); +void flashbang_put_audio(FlashbangContext* context, size_t stream_id, char* buffer, size_t size); +void flashbang_stop_stream(FlashbangContext* context, size_t stream_id); void flashbang_set_window_background(FlashbangContext* context, u8 r, u8 g, u8 b); void flashbang_upload_bitmap(FlashbangContext* context, size_t offset, size_t size, u32 width, u32 height); void flashbang_finalize_bitmaps(FlashbangContext* context); -void flashbang_open_pass(FlashbangContext* context); +void flashbang_set_display_scale(FlashbangContext* context, u8 scale); +bool flashbang_open_pass(FlashbangContext* context, SWFAppContext* app_context); +u32 flashbang_allocate_vertices(FlashbangContext* context, u32 num_verts); +u32 flashbang_allocate_uninv(FlashbangContext* context); +void flashbang_open_vertex_transfer(FlashbangContext* context, size_t total_vertex_count, size_t total_uninv_count); +void flashbang_upload_vertices(FlashbangContext* context, u32* data, u32 upload_offset, u32 vertex_count); +void flashbang_upload_uninv(FlashbangContext* context, float* uninv, u32 offset); +void flashbang_close_vertex_transfer(FlashbangContext* context); void flashbang_upload_extra_transform_id(FlashbangContext* context, u32 transform_id); void flashbang_upload_extra_transform(FlashbangContext* context, float* transform); void flashbang_upload_cxform_id(FlashbangContext* context, u32 cxform_id); void flashbang_upload_cxform(FlashbangContext* context, float* cxform); -void flashbang_draw_shape(FlashbangContext* context, size_t offset, size_t num_verts, u32 transform_id); -void flashbang_close_pass(FlashbangContext* context); +void flashbang_draw_shape(FlashbangContext* context, u32 offset, u32 num_verts, u32 transform_id); +void flashbang_close_pass(FlashbangContext* context, SWFAppContext* app_context); void flashbang_release(FlashbangContext* context, SWFAppContext* app_context); \ No newline at end of file diff --git a/include/flashbang/flashbang_context.h b/include/flashbang/flashbang_context.h new file mode 100644 index 0000000..fc63074 --- /dev/null +++ b/include/flashbang/flashbang_context.h @@ -0,0 +1,112 @@ +#pragma once + +#include + +#define FBC ((FlashbangContext*) app_context->fbc) + +typedef struct +{ + void* stream; + bool stopping; + bool playing; +} FlashbangAudioStream; + +typedef struct +{ + int width; + int height; + u8 scale; + + const float* stage_to_ndc; + + size_t bitmap_count; + size_t bitmap_highest_w; + size_t bitmap_highest_h; + + size_t current_bitmap; + u32* bitmap_sizes; + + bool shape_data_exists; + + char* shape_data; + size_t shape_data_size; + char* transform_data; + size_t transform_data_size; + char* color_data; + size_t color_data_size; + char* uninv_mat_data; + size_t uninv_mat_data_size; + char* gradient_data; + size_t gradient_data_size; + char* bitmap_data; + size_t bitmap_data_size; + char* cxform_data; + size_t cxform_data_size; + + void* copy_pass; + + size_t allocated_vertex_size; + size_t allocated_vertex_transfer_size; + size_t current_vertex_offset; + size_t vertices_uploading_count; + void* vertex_transfer_buffer; + char* vertex_buffer_mapped; + + void* vertex_buffer_to_free; + void* transfer_buffer_to_free; + + size_t allocated_uninv_size; + size_t allocated_uninv_transfer_size; + size_t current_uninv_offset; + size_t uninvs_uploading_count; + void* uninv_transfer_buffer; + char* uninv_buffer_mapped; + + void* uninv_buffer_to_free; + void* inv_buffer_to_free; + + void* window; + void* device; + u32 audio_device; + + FlashbangAudioStream* audio_streams; + size_t audio_stream_capacity; + + u8 last_key_pressed; + + void* swapchain; + void* target_texture; + + u32 swapchain_width; + u32 swapchain_height; + + void* dummy_tex; + void* dummy_sampler; + + void* vertex_buffer; + void* xform_buffer; + void* color_buffer; + void* uninv_mat_buffer; + void* inv_mat_buffer; + void* bitmap_sizes_buffer; + void* cxform_buffer; + + void* gradient_tex_array; + void* gradient_sampler; + + void* bitmap_transfer; + void* bitmap_sizes_transfer; + void* bitmap_tex_array; + void* bitmap_sampler; + + void* graphics_pipeline; + void* inv_pipeline; + + void* command_buffer; + void* render_pass; + + // Window background color + u8 red; + u8 green; + u8 blue; +} FlashbangContext; \ No newline at end of file diff --git a/include/flashbang/triangulation.h b/include/flashbang/triangulation.h new file mode 100644 index 0000000..10842c3 --- /dev/null +++ b/include/flashbang/triangulation.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include + +typedef TESStesselator recomp_triangulate_t; + +void triInit(SWFAppContext* app_context); + +recomp_triangulate_t* triAlloc(SWFAppContext* app_context); + +void triTessellate(recomp_triangulate_t* tess, f32* data, u32 num_vertices); + +const f32* triGetVertices(recomp_triangulate_t* tess); +const int* triGetElements(recomp_triangulate_t* tess); +u32 triGetVertexCount(recomp_triangulate_t* tess); +u32 triGetElementCount(recomp_triangulate_t* tess); + +void triDestroy(SWFAppContext* app_context, recomp_triangulate_t* tess); \ No newline at end of file diff --git a/include/libswf/context.h b/include/libswf/context.h new file mode 100644 index 0000000..fbe84e8 --- /dev/null +++ b/include/libswf/context.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include +#include + +typedef struct SWFAppContext SWFAppContext; + +typedef void (*frame_func)(SWFAppContext* app_context); +typedef void (*action_func)(SWFAppContext* app_context); + +extern frame_func frame_funcs[]; + +typedef struct O1HeapInstance O1HeapInstance; + +typedef struct SWFAppContext +{ + char* stack; + u32 sp; + u32 oldSP; + + u8 version; + + frame_func* frame_funcs; + + size_t dictionary_capacity; + + char** str_table; + u32* str_len_table; + + VarCtx var_ctx; + + int width; + int height; + + const float* stage_to_ndc; + + O1HeapInstance* heap_instance; + recomp_rwlock_t heap_lock; + char* heap; + size_t heap_size; + + bool stop_free; + bool global_free_override; + + SwapVector active_objects; + SwapVector reachable; + SwapVector objects_to_test; + SwapVector cycles; + SwapVector objects_subbed; + + size_t max_string_id; + + ASObject* _root; + + u32 last_frame; + + u32 frame_vertices; + + SwapVector vertex_tasks; + SwapVector uninv_tasks; + SwapVector draw_tasks; + + SwapVector movieclip_stack; + + size_t frame_vertex_count; + + ASObject* Object_prototype; + ASObject* Object_constructor; + + ASObject* Function_constructor; + + ASObject* MovieClip_prototype; + ASObject* MovieClip_constructor; + + ASObject* BitmapData_prototype; + ASObject* BitmapData_constructor; + + size_t exported_chars_count; + u16* exported_char_ids; + u32* exported_string_ids; + + void* fbc; + + size_t bitmap_count; + size_t bitmap_highest_w; + size_t bitmap_highest_h; + + u16* bitmap_char_ids; + u16* bitmap_ids; + + bool shape_data_exists; + + char* shape_data; + size_t shape_data_size; + char* transform_data; + size_t transform_data_size; + char* color_data; + size_t color_data_size; + char* uninv_mat_data; + size_t uninv_mat_data_size; + char* gradient_data; + size_t gradient_data_size; + char* bitmap_data; + size_t bitmap_data_size; + u32* glyph_data; + size_t glyph_data_size; + u32* text_data; + size_t text_data_size; + char* cxform_data; + size_t cxform_data_size; +} SWFAppContext; \ No newline at end of file diff --git a/include/libswf/recomp.h b/include/libswf/recomp.h index cc57a6f..f8f7c85 100644 --- a/include/libswf/recomp.h +++ b/include/libswf/recomp.h @@ -3,7 +3,7 @@ #include // libswf -#include +#include #include // actionmodern diff --git a/include/libswf/swf.h b/include/libswf/swf.h index 5ce8392..934dd6a 100644 --- a/include/libswf/swf.h +++ b/include/libswf/swf.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #define HEAP_SIZE 1024*1024*1024 // 1 GB @@ -25,14 +27,14 @@ typedef struct Character // DefineShape struct { - size_t shape_offset; - size_t size; + u32 shape_offset; + u32 size; }; // DefineText struct { - size_t text_start; - size_t text_size; + u32 text_start; + u32 text_size; u32 transform_start; u32 cxform_id; }; @@ -41,62 +43,12 @@ typedef struct Character typedef struct DisplayObject { - size_t char_id; + u32 char_id; u32 transform_id; } DisplayObject; -typedef struct SWFAppContext SWFAppContext; - -typedef void (*frame_func)(SWFAppContext* app_context); - -extern frame_func frame_funcs[]; - -typedef struct O1HeapInstance O1HeapInstance; - -typedef struct SWFAppContext -{ - char* stack; - u32 sp; - u32 oldSP; - - frame_func* frame_funcs; - - int width; - int height; - - const float* stage_to_ndc; - - O1HeapInstance* heap_instance; - char* heap; - size_t heap_size; - - size_t max_string_id; - - size_t bitmap_count; - size_t bitmap_highest_w; - size_t bitmap_highest_h; - - char* shape_data; - size_t shape_data_size; - char* transform_data; - size_t transform_data_size; - char* color_data; - size_t color_data_size; - char* uninv_mat_data; - size_t uninv_mat_data_size; - char* gradient_data; - size_t gradient_data_size; - char* bitmap_data; - size_t bitmap_data_size; - u32* glyph_data; - size_t glyph_data_size; - u32* text_data; - size_t text_data_size; - char* cxform_data; - size_t cxform_data_size; -} SWFAppContext; - extern int quit_swf; +extern int bad_poll; extern size_t next_frame; extern int manual_next_frame; @@ -105,4 +57,7 @@ extern Character* dictionary; extern DisplayObject* display_list; extern size_t max_depth; +u16 swfGetExportedChar(SWFAppContext* app_context, u32 string_id); +u16 swfGetBitmapId(SWFAppContext* app_context, u32 char_id); + void swfStart(SWFAppContext* app_context); \ No newline at end of file diff --git a/include/libswf/tag.h b/include/libswf/tag.h index bbfa291..590c5c4 100644 --- a/include/libswf/tag.h +++ b/include/libswf/tag.h @@ -3,14 +3,46 @@ #include #include +typedef struct +{ + u32 offset; + u32 count; + u32* tris; + + bool free_after; +} VertexTask; + +typedef struct +{ + u32 offset; +} UninvTask; + +typedef struct +{ + bool has_extra_transform_id; + u32 extra_transform_id; + + bool has_extra_cxform_id; + u32 extra_cxform_id; + + bool has_extra_transform; + ASObject* obj; + + u32 offset; + u32 count; + u32 transform_id; +} DrawTask; + // Core tag functions - always available void tagInit(SWFAppContext* app_context); -void tagSetBackgroundColor(u8 red, u8 green, u8 blue); +void tagSetBackgroundColor(SWFAppContext* app_context, u8 red, u8 green, u8 blue); void tagShowFrame(SWFAppContext* app_context); +#ifndef NO_GRAPHICS // Graphics-only tag functions -void tagDefineShape(SWFAppContext* app_context, CharacterType type, size_t char_id, size_t shape_offset, size_t shape_size); -void tagDefineText(SWFAppContext* app_context, size_t char_id, size_t text_start, size_t text_size, u32 transform_start, u32 cxform_id); -void tagPlaceObject2(SWFAppContext* app_context, size_t depth, size_t char_id, u32 transform_id); -void defineBitmap(size_t offset, size_t size, u32 width, u32 height); -void finalizeBitmaps(); \ No newline at end of file +void tagDefineShape(SWFAppContext* app_context, CharacterType type, u32 char_id, u32 shape_offset, u32 shape_size); +void tagDefineText(SWFAppContext* app_context, u32 char_id, u32 text_start, u32 text_size, u32 transform_start, u32 cxform_id); +void tagPlaceObject2(SWFAppContext* app_context, u32 depth, u32 char_id, u32 transform_id); +void defineBitmap(SWFAppContext* app_context, u32 offset, u32 size, u32 width, u32 height); +void finalizeBitmaps(SWFAppContext* app_context); +#endif \ No newline at end of file diff --git a/include/memory/heap.h b/include/memory/heap.h index dcb1660..3433d09 100644 --- a/include/memory/heap.h +++ b/include/memory/heap.h @@ -1,14 +1,15 @@ #pragma once -#include +#include #define HALLOC(s) heap_alloc(app_context, s); +#define HREALLOC(p, s) heap_realloc(app_context, p, s); #define FREE(p) heap_free(app_context, p); /** * Memory Heap Manager * - * Wrapper around o1heap allocator providing multi-heap support with automatic expansion. + * Wrapper around o1heap allocator providing multi-heap support. */ /** @@ -28,6 +29,16 @@ void heap_init(SWFAppContext* app_context, size_t size); */ void* heap_alloc(SWFAppContext* app_context, size_t size); +/** + * Allocate memory from the heap, copying memory from an old ptr and freeing it + * + * @param app_context Main app context + * @param ptr Old ptr to copy and free + * @param size Number of bytes to allocate + * @return Pointer to allocated memory, or NULL on failure + */ +void* heap_realloc(SWFAppContext* app_context, void* ptr, size_t size); + /** * Free memory allocated by heap_alloc() or heap_calloc() * @@ -42,7 +53,7 @@ void heap_free(SWFAppContext* app_context, void* ptr); * Shutdown the heap system * * Frees all heap arenas. Should be called at program exit. - * + * * @param app_context Main app context */ void heap_shutdown(SWFAppContext* app_context); \ No newline at end of file diff --git a/include/utils.h b/include/utils.h index 1e781a6..783bb68 100644 --- a/include/utils.h +++ b/include/utils.h @@ -1,19 +1,73 @@ #pragma once #include +#include #include +#include + +#if defined(_MSC_VER) +// Microsoft + +#include + +#define LIKELY(exp) exp +#define UNLIKELY(exp) exp + +#define DECLARE_RUNTIME_THREAD_FUNC(f) unsigned int f(SWFAppContext* app_context) + +typedef uintptr_t recomp_thread_t; +typedef unsigned int (*runtime_thread_func)(SWFAppContext* arg); + +#elif defined(__GNUC__) +// GCC + +#define LIKELY(exp) __builtin_expect(exp, true) +#define UNLIKELY(exp) __builtin_expect(exp, false) + +#define DECLARE_RUNTIME_THREAD_FUNC(f) void* f(SWFAppContext* app_context) + +typedef pthread_t recomp_thread_t; +typedef void* (*runtime_thread_func)(SWFAppContext* arg); + +#endif + #define ENSURE_SIZE(ptr, new_size, capac, elem_size) \ - if (new_size >= capac) \ + if (UNLIKELY(new_size >= capac)) \ { \ grow_ptr(app_context, (char**) &ptr, &capac, elem_size); \ } +#define ENSURE_SIZE_FAR(ptr, new_size, capac, elem_size) \ + if (UNLIKELY(new_size >= capac)) \ + { \ + grow_ptr_far(app_context, (char**) &ptr, &capac, elem_size, new_size); \ + } + +void recomp_init_utils(); +void recomp_sync_window(); +void recomp_deinit_utils(); + +size_t get_power_two_size(size_t old_size, size_t size); + void grow_ptr(SWFAppContext* app_context, char** ptr, size_t* capacity_ptr, size_t elem_size); +void grow_ptr_far(SWFAppContext* app_context, char** ptr, size_t* capacity_ptr, size_t elem_size, size_t new_size); u32 get_elapsed_ms(); +void recomp_sleep(u32 ms); int getpagesize(); char* vmem_reserve(size_t size); -void vmem_release(char* addr, size_t size); \ No newline at end of file +void vmem_release(char* addr, size_t size); + +void thread_start(SWFAppContext* app_context, runtime_thread_func f, recomp_thread_t* handle); +void thread_exit(); +void thread_join(recomp_thread_t* handle); + +void rwlock_init(recomp_rwlock_t* rwlock); +void rwlock_lock_read(recomp_rwlock_t* rwlock); +void rwlock_unlock_read(recomp_rwlock_t* rwlock); +void rwlock_lock_write(recomp_rwlock_t* rwlock); +void rwlock_unlock_write(recomp_rwlock_t* rwlock); +void rwlock_destroy(recomp_rwlock_t* rwlock); \ No newline at end of file diff --git a/include/utils_lock.h b/include/utils_lock.h new file mode 100644 index 0000000..df11a6b --- /dev/null +++ b/include/utils_lock.h @@ -0,0 +1,27 @@ +#pragma once + +#define LOCK_READ(lock, code) \ + rwlock_lock_read(&lock); \ + code \ + rwlock_unlock_read(&lock); + +#define LOCK_WRITE(lock, code) \ + rwlock_lock_write(&lock); \ + code \ + rwlock_unlock_write(&lock); + +#if defined(_MSC_VER) +// Microsoft + +#include + +typedef SRWLOCK recomp_rwlock_t; + +#elif defined(__GNUC__) +// GCC + +#include + +typedef pthread_rwlock_t recomp_rwlock_t; + +#endif \ No newline at end of file diff --git a/lib/libtess2 b/lib/libtess2 new file mode 160000 index 0000000..5981a90 --- /dev/null +++ b/lib/libtess2 @@ -0,0 +1 @@ +Subproject commit 5981a9017b260310e8f16418214d64f5b02e0de7 diff --git a/lib/lzma b/lib/lzma deleted file mode 160000 index df10aff..0000000 --- a/lib/lzma +++ /dev/null @@ -1 +0,0 @@ -Subproject commit df10aff448847167d3d170e3b7f0f4e23a32e592 diff --git a/lib/minimp3 b/lib/minimp3 new file mode 160000 index 0000000..7b590fd --- /dev/null +++ b/lib/minimp3 @@ -0,0 +1 @@ +Subproject commit 7b590fdcfa5a79c033e76eacc05d0c3e4c79f536 diff --git a/lib/o1heap b/lib/o1heap index b3a1aa4..388a73f 160000 --- a/lib/o1heap +++ b/lib/o1heap @@ -1 +1 @@ -Subproject commit b3a1aa43e1ff0a4ce8b2c5180e878c2011b6bc25 +Subproject commit 388a73fd9007300e5130c5fe352d9ce3288b6dde diff --git a/lib/rbtree b/lib/rbtree new file mode 160000 index 0000000..13dec33 --- /dev/null +++ b/lib/rbtree @@ -0,0 +1 @@ +Subproject commit 13dec33fe52cefd9cb2376d02504700ae56079df diff --git a/lib/zlib b/lib/zlib deleted file mode 160000 index ef24c4c..0000000 --- a/lib/zlib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ef24c4c7502169f016dcd2a26923dbaf3216748c diff --git a/src/actionmodern/action.c b/src/actionmodern/action.c index 878173a..8119773 100644 --- a/src/actionmodern/action.c +++ b/src/actionmodern/action.c @@ -5,837 +5,3968 @@ #include #include +#include + #include +#include +#include +#include +#include #include u32 start_time; -void initTime() +// ================================================================== +// Scope Chain for WITH statement +// ================================================================== + +#define MAX_SCOPE_DEPTH 16 +static ASObject* scope_chain[MAX_SCOPE_DEPTH]; +static ActionVar* scope_registers[MAX_SCOPE_DEPTH]; +static u32 scope_top_obj = 1; + +// ================================================================== +// Global object for ActionScript +// This is initialized from initActions and persists for the lifetime of the runtime +ASObject* _global; + +void initActions(SWFAppContext* app_context) { + SVEC_INIT(&app_context->active_objects); + + rwlock_init(&object_queue_lock); + rbtree_init(&object_free_queue, sizeof(objnode)); + + app_context->Object_prototype = allocObjectCommon(app_context); + app_context->Object_constructor = allocObjectCommon(app_context); + retainObject(app_context->Object_prototype); + retainObject(app_context->Object_constructor); + + app_context->Function_constructor = allocObject(app_context); + + app_context->MovieClip_prototype = allocObject(app_context); + app_context->MovieClip_constructor = allocObject(app_context); + + app_context->BitmapData_prototype = allocObject(app_context); + start_time = get_elapsed_ms(); + + for (u32 i = 0; i < 2; ++i) + { + scope_chain[i] = allocObject(app_context); + retainObject(scope_chain[i]); + + scope_registers[i] = HALLOC(4*sizeof(ActionVar)); + + for (u32 j = 0; j < 4; ++j) + { + scope_registers[i][j].type = ACTION_STACK_VALUE_UNDEFINED; + } + } + + _global = scope_chain[0]; + + for (int i = 0; i < sizeof(runtime_funcs)/sizeof(RuntimeFunc); ++i) + { + ASObject* obj; + + if (runtime_funcs[i].object_string_id == 0) + { + obj = _global; + } + + else + { + ASProperty* p = getProperty(app_context, _global, runtime_funcs[i].object_string_id, NULL, 0); + + if (p != NULL) + { + obj = (ASObject*) p->value.value; + } + + else + { + obj = allocObject(app_context); + ActionVar obj_v; + obj_v.type = ACTION_STACK_VALUE_OBJECT; + obj_v.value = (u64) obj; + setProperty(app_context, _global, runtime_funcs[i].object_string_id, NULL, 0, &obj_v); + } + } + + ActionVar v; + v.type = ACTION_STACK_VALUE_OBJECT; + + switch (runtime_funcs[i].func_string_id) + { + case STR_ID_OBJECT: + { + v.object = app_context->Object_constructor; + + v.object->extra_data = HALLOC(sizeof(FunctionData)); + + break; + } + + case STR_ID_FUNCTION: + { + v.object = app_context->Function_constructor; + + v.object->extra_data = HALLOC(sizeof(FunctionData)); + + break; + } + + case STR_ID_MOVIECLIP: + { + v.object = app_context->MovieClip_constructor; + + Function_init_object(app_context, v.object); + + break; + } + + default: + { + v.object = allocObject(app_context); + + Function_init_object(app_context, v.object); + + break; + } + } + + Function_set_members(app_context, v.object, + FUNC_TYPE_3, + (action_func) runtime_funcs[i].func, + NULL, + 0, + 0, + runtime_funcs[i].func_string_id); + + if (runtime_funcs[i].constructor) + { + ActionVar proto_var; + proto_var.type = ACTION_STACK_VALUE_OBJECT; + + switch (runtime_funcs[i].func_string_id) + { + case STR_ID_OBJECT: + { + proto_var.object = app_context->Object_prototype; + + break; + } + + case STR_ID_MOVIECLIP: + { + proto_var.object = app_context->MovieClip_prototype; + + break; + } + + default: + { + proto_var.object = allocObject(app_context); + + break; + } + } + + setProperty(app_context, v.object, STR_ID_PROTOTYPE, NULL, 0, &proto_var); + } + + setProperty(app_context, obj, runtime_funcs[i].func_string_id, NULL, 0, &v); + } + + for (int i = 0; i < sizeof(runtime_meths)/sizeof(RuntimeFunc); ++i) + { + ASObject* obj; + + ASProperty* p = getProperty(app_context, _global, runtime_meths[i].object_string_id, NULL, 0); + + if (p != NULL) + { + obj = (ASObject*) p->value.value; + } + + else + { + UNIMPLEMENTED("Method on non-existent object"); + } + + ActionVar v; + v.type = ACTION_STACK_VALUE_OBJECT; + v.object = allocObject(app_context); + + Function_init_object(app_context, v.object); + + Function_set_members(app_context, v.object, + FUNC_TYPE_3, + (action_func) runtime_meths[i].func, + NULL, + 0, + 0, + runtime_meths[i].func_string_id); + + if (runtime_meths[i].constructor) + { + UNIMPLEMENTED("Constructor method"); + } + + ASObject* prototype = getProperty(app_context, obj, STR_ID_PROTOTYPE, NULL, 0)->value.object; + setProperty(app_context, prototype, runtime_meths[i].func_string_id, NULL, 0, &v); + } + + // instantiate initial objects (for packages) + + ASObject* flash = allocObject(app_context); + ActionVar flash_v; + flash_v.type = ACTION_STACK_VALUE_OBJECT; + flash_v.object = flash; + setProperty(app_context, _global, STR_ID_FLASH, NULL, 0, &flash_v); + + ASObject* display = allocObject(app_context); + ActionVar display_v; + display_v.type = ACTION_STACK_VALUE_OBJECT; + display_v.object = display; + setProperty(app_context, flash, STR_ID_DISPLAY, NULL, 0, &display_v); + + ASObject* bitmapdata = allocObject(app_context); + + Function_init_object(app_context, bitmapdata); + + Function_set_members(app_context, bitmapdata, + FUNC_TYPE_3, + (action_func) BitmapData_new, + NULL, + 0, + 0, + STR_ID_BITMAP_DATA); + + app_context->BitmapData_constructor = bitmapdata; + + ActionVar proto_var; + proto_var.type = ACTION_STACK_VALUE_OBJECT; + proto_var.object = app_context->BitmapData_prototype; + setProperty(app_context, bitmapdata, STR_ID_PROTOTYPE, NULL, 0, &proto_var); + + ActionVar bitmapdata_v; + bitmapdata_v.type = ACTION_STACK_VALUE_OBJECT; + bitmapdata_v.object = bitmapdata; + setProperty(app_context, display, STR_ID_BITMAP_DATA, NULL, 0, &bitmapdata_v); + + ASObject* loadbitmap = allocObject(app_context); + + Function_init_object(app_context, loadbitmap); + + Function_set_members(app_context, loadbitmap, + FUNC_TYPE_3, + (action_func) BitmapData_loadBitmap, + NULL, + 0, + 0, + STR_ID_LOAD_BITMAP); + + ActionVar loadbitmap_v; + loadbitmap_v.type = ACTION_STACK_VALUE_OBJECT; + loadbitmap_v.object = loadbitmap; + setProperty(app_context, bitmapdata, STR_ID_LOAD_BITMAP, NULL, 0, &loadbitmap_v); + + for (int i = 0; i < sizeof(static_initializers)/sizeof(action_runtime_func); ++i) + { + scope_top_obj += 1; + scope_chain[scope_top_obj] = allocObject(app_context); + retainObject(scope_chain[scope_top_obj]); + + static_initializers[i](app_context, NULL, 0); + POP(); + + OBJ_LOCK_WRITE(scope_chain[scope_top_obj], + { + releaseObject(app_context, scope_chain[scope_top_obj]); + }); + + scope_top_obj -= 1; + } + + app_context->stop_free = false; + app_context->global_free_override = false; + + app_context->_root = MovieClip_create(app_context); + + ActionVar root_v; + root_v.type = ACTION_STACK_VALUE_OBJECT; + root_v.object = app_context->_root; + + setProperty(app_context, scope_chain[1], STR_ID_THIS, NULL, 0, &root_v); + + thread_start(app_context, freeThread, &free_thread_handle); } -ActionStackValueType convertString(SWFAppContext* app_context, char* var_str) +void freeActions(SWFAppContext* app_context) { - if (STACK_TOP_TYPE == ACTION_STACK_VALUE_F32) + LOCK_WRITE(object_queue_lock, + { + app_context->stop_free = true; + }); + + thread_join(&free_thread_handle); + + //~ while (app_context->active_objects.length > 0) + //~ { + //~ ASObject* o = (ASObject*) app_context->active_objects.data[0]; + //~ fprintf(stderr, "unfreed object %d (%p) with rc %d\n", o->id, o, o->refcount); + + //~ destroyObject(app_context, o); + //~ FREE(o); + //~ } + + for (u32 i = 0; i < 2; ++i) { - STACK_TOP_TYPE = ACTION_STACK_VALUE_STRING; - VAL(u64, &STACK_TOP_VALUE) = (u64) var_str; - snprintf(var_str, 17, "%.15g", VAL(float, &STACK_TOP_VALUE)); + FREE(scope_registers[i]); } - return ACTION_STACK_VALUE_STRING; + SVEC_RELEASE(&app_context->active_objects); + + rwlock_destroy(&object_queue_lock); } -ActionStackValueType convertFloat(SWFAppContext* app_context) +void discardArgs(SWFAppContext* app_context, u32 num_args) { - if (STACK_TOP_TYPE == ACTION_STACK_VALUE_STRING) + for (u32 i = 0; i < num_args; ++i) { - double temp = atof((char*) VAL(u64, &STACK_TOP_VALUE)); - STACK_TOP_TYPE = ACTION_STACK_VALUE_F64; - VAL(u64, &STACK_TOP_VALUE) = VAL(u64, &temp); + POP(); + } +} + +void searchScopesForPropertyVar(SWFAppContext* app_context, u32 string_id, const char* name, u32 name_len, ActionVar* out_var) +{ + ASProperty* p = NULL; + + for (u32 j = 0; j <= scope_top_obj; ++j) + { + u32 i = scope_top_obj - j; - return ACTION_STACK_VALUE_F64; + OBJ_LOCK_READ(scope_chain[i], + { + p = getProperty(app_context, scope_chain[i], string_id, name, name_len); + }); + + if (p != NULL) + { + *out_var = p->value; + return; + } } - return ACTION_STACK_VALUE_F32; + out_var->type = ACTION_STACK_VALUE_UNDEFINED; } -ActionStackValueType convertDouble(SWFAppContext* app_context) +ASProperty* getPropertyInThisScope(SWFAppContext* app_context, u32 string_id, const char* name, u32 name_len) { - if (STACK_TOP_TYPE == ACTION_STACK_VALUE_F32) + ASProperty* p; + + OBJ_LOCK_READ(scope_chain[scope_top_obj], { - double temp = VAL(double, &STACK_TOP_VALUE); - STACK_TOP_TYPE = ACTION_STACK_VALUE_F64; - VAL(u64, &STACK_TOP_VALUE) = VAL(u64, &temp); - } + p = getProperty(app_context, scope_chain[scope_top_obj], string_id, name, name_len); + }); - return ACTION_STACK_VALUE_F64; + return p; +} + +void setPropertyInThisScope(SWFAppContext* app_context, u32 string_id, const char* name, u32 name_len, ActionVar* value) +{ + setProperty(app_context, scope_chain[scope_top_obj], string_id, name, name_len, value); } void pushVar(SWFAppContext* app_context, ActionVar* var) { + if (IS_OBJ_T(var->type)) + { + ASObject* po = var->object; + + OBJ_LOCK_WRITE(po, + { + // the stack now has a reference to this object + retainObject(po); + }); + } + switch (var->type) { - case ACTION_STACK_VALUE_F32: - case ACTION_STACK_VALUE_F64: + case ACTION_STACK_VALUE_STRING: { - PUSH(var->type, var->value); + // Use heap pointer if variable owns memory, otherwise use numeric_value as pointer + char* str_ptr = var->owns_memory ? + var->str : + (char*) var->value; + + PUSH_STR_ID(str_ptr, var->string_id, var->str_size); break; } - case ACTION_STACK_VALUE_STRING: + default: { - // Use heap pointer if variable owns memory, otherwise use value as pointer - char* str_ptr = var->owns_memory ? var->heap_ptr : (char*) var->value; - - PUSH_STR_ID(str_ptr, var->str_size, var->string_id); + PUSH(var->type, var->value); break; } } } -void peekVar(SWFAppContext* app_context, ActionVar* var) +void pushReg(SWFAppContext* app_context, u8 reg) +{ + pushVar(app_context, &scope_registers[scope_top_obj][reg]); +} + +void peekConvert(SWFAppContext* app_context, ActionVar* var) { var->type = STACK_TOP_TYPE; - var->str_size = STACK_TOP_N; - if (STACK_TOP_TYPE == ACTION_STACK_VALUE_STR_LIST) + switch (var->type) { - var->value = (u64) &STACK_TOP_VALUE; + case ACTION_STACK_VALUE_STR_LIST: + { + var->value = (u64) &STACK_TOP_VALUE; + var->str_size = STACK_TOP_N; + break; + } + + case ACTION_STACK_VALUE_STRING: + { + var->string_id = STACK_TOP_ID; + var->str_size = STACK_TOP_N; + + if (STACK_TOP_OWNS_MEM != 0) + { + var->str = HALLOC(STACK_TOP_N + 1); + var->owns_memory = true; + memcpy(var->str, &STACK_TOP_VALUE, STACK_TOP_N + 1); + } + + else + { + var->value = STACK_TOP_VALUE; + var->owns_memory = false; + } + + break; + } + + default: + { + var->value = STACK_TOP_VALUE; + break; + } } - else + if (IS_OBJ_T(var->type)) { - var->value = VAL(u64, &STACK_TOP_VALUE); + OBJ_LOCK_WRITE(var->object, + { + retainObject(var->object); + }); } } -void peekSecondVar(SWFAppContext* app_context, ActionVar* var) +void releaseObjectVar(SWFAppContext* app_context, ActionVar* var) { - var->type = STACK_SECOND_TOP_TYPE; - var->str_size = STACK_SECOND_TOP_N; - - if (STACK_SECOND_TOP_TYPE == ACTION_STACK_VALUE_STR_LIST) - { - var->value = (u64) &STACK_SECOND_TOP_VALUE; - } - - else + if (IS_OBJ_T(var->type)) { - var->value = VAL(u64, &STACK_SECOND_TOP_VALUE); + OBJ_LOCK_WRITE(var->object, + { + releaseObject(app_context, var->object); + }); } } -void popVar(SWFAppContext* app_context, ActionVar* var) +void copyReg(SWFAppContext* app_context) { - peekVar(app_context, var); + if (STACK_TOP_TYPE != ACTION_STACK_VALUE_REGISTER) + { + return; + } + + u8 reg = (u8) STACK_TOP_VALUE; POP(); + pushReg(app_context, reg); } -void actionAdd(SWFAppContext* app_context) +void copy2Regs(SWFAppContext* app_context) { - convertFloat(app_context); - ActionVar a; - popVar(app_context, &a); + ActionVar reg1_v; + ActionVar reg2_v; - convertFloat(app_context); - ActionVar b; - popVar(app_context, &b); + peekConvert(app_context, ®1_v); + POP(); + + peekConvert(app_context, ®2_v); + POP(); - if (a.type == ACTION_STACK_VALUE_F64) + if (reg2_v.type == ACTION_STACK_VALUE_REGISTER) { - double a_val = VAL(double, &a.value); - double b_val = b.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &b.value) : VAL(double, &b.value); - - double c = b_val + a_val; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); + pushReg(app_context, (u8) reg2_v.u32); } - else if (b.type == ACTION_STACK_VALUE_F64) + else { - double a_val = a.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &a.value) : VAL(double, &a.value); - double b_val = VAL(double, &b.value); - - double c = b_val + a_val; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); + pushVar(app_context, ®2_v); + } + + if (reg1_v.type == ACTION_STACK_VALUE_REGISTER) + { + pushReg(app_context, (u8) reg1_v.u32); } else { - float c = VAL(float, &b.value) + VAL(float, &a.value); - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); + pushVar(app_context, ®1_v); } + + releaseObjectVar(app_context, ®1_v); + releaseObjectVar(app_context, ®2_v); } -void actionSubtract(SWFAppContext* app_context) +void peekVar(SWFAppContext* app_context, ActionVar* var) { - convertFloat(app_context); - ActionVar a; - popVar(app_context, &a); + copyReg(app_context); + peekConvert(app_context, var); +} + +void popVar(SWFAppContext* app_context, ActionVar* var) +{ + peekVar(app_context, var); - convertFloat(app_context); - ActionVar b; - popVar(app_context, &b); + POP(); +} + +void peekSecondVar(SWFAppContext* app_context, ActionVar* var) +{ + var->type = STACK_SECOND_TOP_TYPE; + var->str_size = STACK_SECOND_TOP_N; - if (a.type == ACTION_STACK_VALUE_F64) + if (STACK_SECOND_TOP_TYPE == ACTION_STACK_VALUE_STR_LIST) { - double a_val = VAL(double, &a.value); - double b_val = b.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &b.value) : VAL(double, &b.value); - - double c = b_val - a_val; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); + var->value = (u64) &STACK_SECOND_TOP_VALUE; } - else if (b.type == ACTION_STACK_VALUE_F64) + else if (STACK_SECOND_TOP_TYPE == ACTION_STACK_VALUE_STRING) { - double a_val = a.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &a.value) : VAL(double, &a.value); - double b_val = VAL(double, &b.value); - - double c = b_val - a_val; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); + var->value = STACK_SECOND_TOP_VALUE; + var->owns_memory = false; + var->string_id = STACK_SECOND_TOP_ID; } else { - float c = VAL(float, &b.value) - VAL(float, &a.value); - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); + var->value = STACK_SECOND_TOP_VALUE; } } -void actionMultiply(SWFAppContext* app_context) +void convertNumericToNumber(SWFAppContext* app_context, ActionVar* v) { - convertFloat(app_context); - ActionVar a; - popVar(app_context, &a); - - convertFloat(app_context); - ActionVar b; - popVar(app_context, &b); - - if (a.type == ACTION_STACK_VALUE_F64) - { - double a_val = VAL(double, &a.value); - double b_val = b.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &b.value) : VAL(double, &b.value); - - double c = b_val*a_val; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); - } + f64 d; - else if (b.type == ACTION_STACK_VALUE_F64) + switch (v->type) { - double a_val = a.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &a.value) : VAL(double, &a.value); - double b_val = VAL(double, &b.value); - - double c = b_val*a_val; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); + case ACTION_STACK_VALUE_F32: + f32 f = v->f32; + v->type = ACTION_STACK_VALUE_F64; + d = (f64) f; + v->f64 = d; + break; + case ACTION_STACK_VALUE_INT: + s32 i = v->s32; + v->type = ACTION_STACK_VALUE_F64; + d = (f64) i; + v->f64 = d; + break; } +} + +void convertNumericToInteger(SWFAppContext* app_context, ActionVar* v) +{ + s32 i; - else + switch (v->type) { - float c = VAL(float, &b.value)*VAL(float, &a.value); - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); + case ACTION_STACK_VALUE_F32: + f32 f = v->f32; + v->type = ACTION_STACK_VALUE_INT; + i = (s32) f; + v->s32 = i; + break; + case ACTION_STACK_VALUE_F64: + f64 d = v->f64; + v->type = ACTION_STACK_VALUE_INT; + i = (s32) d; + v->s32 = i; + break; } } -void actionDivide(SWFAppContext* app_context) +f64 toNumberCommon(SWFAppContext* app_context, ActionVar* v) { - convertFloat(app_context); + switch (v->type) + { + case ACTION_STACK_VALUE_UNDEFINED: + return NAN; + + case ACTION_STACK_VALUE_NULL: + return +0.0; + + case ACTION_STACK_VALUE_BOOLEAN: + return v->b ? 1.0 : +0.0; + + case ACTION_STACK_VALUE_STRING: + case ACTION_STACK_VALUE_STR_LIST: + // TODO: implement real ToNumber ECMA-262 3rd Edition algorithm + char* end; + return strtod(v->str, &end); + + case ACTION_STACK_VALUE_F32: + case ACTION_STACK_VALUE_INT: + convertNumericToNumber(app_context, v); + // fallthrough + case ACTION_STACK_VALUE_F64: + return v->f64; + + default: + UNREACHABLE("ToNumber"); + } + + return NAN; +} + +void toNumber(SWFAppContext* app_context, ActionVar* v) +{ + if (IS_OBJ_P(v)) + { + //~ UNIMPLEMENTED("ToNumber on an Object"); + + // TODO: implement real ToNumber ECMA-262 3rd Edition algorithm + toPrimitive(app_context, v->object); + popVar(app_context, v); + } + + f64 num = toNumberCommon(app_context, v); + + PUSH_F64(num); + + if (UNLIKELY(IS_OBJ_P(v))) + { + releaseObjectVar(app_context, v); + } +} + +void toInteger(SWFAppContext* app_context, ActionVar* v) +{ + if (IS_OBJ_P(v)) + { + UNIMPLEMENTED("ToInteger on an Object"); + } + + f64 num = toNumberCommon(app_context, v); + + if (num == NAN) + { + num = +0.0; + PUSH_F64(num); + return; + } + + if (num == +0.0 || num == -0.0 || + num == INFINITY || num == -INFINITY) + { + PUSH_F64(num); + return; + } + + num = (num < 0) ? ceil(num) : floor(num); + PUSH_F64(num); +} + +void toPrimitive(SWFAppContext* app_context, ASObject* this) +{ + getAndCallMethod(app_context, this, STR_ID_VALUE_OF, 0); + + if (IS_OBJ_T(STACK_TOP_TYPE)) + { + POP(); + getAndCallMethod(app_context, this, STR_ID_TO_STRING, 0); + } +} + +void toString(SWFAppContext* app_context, ActionVar* v) +{ + char str[64]; + + // TODO: implement real ToString ECMA-262 3rd Edition algorithm + snprintf(str, 64, "%.15g", v->f64); + u32 len = (u32) strnlen(str, 64); + + PUSH_STR_STACK(len); + char* stack_str = (char*) &STACK_TOP_VALUE; + + memcpy(stack_str, str, len + 1); +} + +ActionStackValueType convertString(SWFAppContext* app_context) +{ + copyReg(app_context); + + ActionVar v; + + char str[64]; + + switch (STACK_TOP_TYPE) + { + case ACTION_STACK_VALUE_NULL: + { + POP(); + snprintf(str, 64, "null"); + + u32 len = 4; + + PUSH_STR_STACK(len); + + STACK_TOP_TYPE = ACTION_STACK_VALUE_STRING; + memcpy((char*) &STACK_TOP_VALUE, str, len + 1); + STACK_TOP_OWNS_MEM = true; + STACK_TOP_N = len; + STACK_TOP_ID = 0; + + break; + } + + case ACTION_STACK_VALUE_UNDEFINED: + { + POP(); + snprintf(str, 64, "undefined"); + + u32 len = 9; + + PUSH_STR_STACK(len); + + STACK_TOP_TYPE = ACTION_STACK_VALUE_STRING; + memcpy((char*) &STACK_TOP_VALUE, str, len + 1); + STACK_TOP_OWNS_MEM = true; + STACK_TOP_N = len; + STACK_TOP_ID = 0; + + break; + } + + case ACTION_STACK_VALUE_BOOLEAN: + { + popVar(app_context, &v); + snprintf(str, 64, (v.b) ? "true" : "false"); + + u32 len = (v.b) ? 4 : 5; + + PUSH_STR_STACK(len); + + STACK_TOP_TYPE = ACTION_STACK_VALUE_STRING; + memcpy((char*) &STACK_TOP_VALUE, str, len + 1); + STACK_TOP_OWNS_MEM = true; + STACK_TOP_N = len; + STACK_TOP_ID = 0; + + releaseObjectVar(app_context, &v); + break; + } + + case ACTION_STACK_VALUE_F32: + { + popVar(app_context, &v); + f32 temp_val = v.f32; + + snprintf(str, 64, "%.15g", temp_val); + + u32 len = (u32) strnlen(str, 64); + + PUSH_STR_STACK(len); + + STACK_TOP_TYPE = ACTION_STACK_VALUE_STRING; + memcpy((char*) &STACK_TOP_VALUE, str, len + 1); + STACK_TOP_OWNS_MEM = true; + STACK_TOP_N = len; + STACK_TOP_ID = 0; + + releaseObjectVar(app_context, &v); + break; + } + + case ACTION_STACK_VALUE_F64: + { + popVar(app_context, &v); + f64 temp_val = v.f64; + + snprintf(str, 64, "%.15g", temp_val); + u32 len = (u32) strnlen(str, 64); + + PUSH_STR_STACK(len); + + STACK_TOP_TYPE = ACTION_STACK_VALUE_STRING; + memcpy((char*) &STACK_TOP_VALUE, str, len + 1); + STACK_TOP_OWNS_MEM = true; + STACK_TOP_N = len; + STACK_TOP_ID = 0; + + releaseObjectVar(app_context, &v); + break; + } + + case ACTION_STACK_VALUE_INT: + { + popVar(app_context, &v); + s32 temp_val = v.s32; + + snprintf(str, 64, "%d", temp_val); + + u32 len = (u32) strnlen(str, 64); + + PUSH_STR_STACK(len); + + STACK_TOP_TYPE = ACTION_STACK_VALUE_STRING; + memcpy((char*) &STACK_TOP_VALUE, str, len + 1); + STACK_TOP_OWNS_MEM = true; + STACK_TOP_N = len; + STACK_TOP_ID = 0; + + releaseObjectVar(app_context, &v); + break; + } + } + + return ACTION_STACK_VALUE_STRING; +} + +ActionStackValueType convertFloat(SWFAppContext* app_context) +{ + copyReg(app_context); + + if (STACK_TOP_TYPE == ACTION_STACK_VALUE_STRING) + { + f64 temp = atof((char*) VAL(u64, &STACK_TOP_VALUE)); + STACK_TOP_TYPE = ACTION_STACK_VALUE_F64; + VAL(u64, &STACK_TOP_VALUE) = VAL(u64, &temp); + + return ACTION_STACK_VALUE_F64; + } + + return ACTION_STACK_VALUE_F32; +} + +ActionStackValueType convertDouble(SWFAppContext* app_context) +{ + // TODO: refactor to just use ActionVars on the stack + + copyReg(app_context); + + ActionVar v; + popVar(app_context, &v); + + toNumber(app_context, &v); + + releaseObjectVar(app_context, &v); + + return ACTION_STACK_VALUE_F64; +} + +ActionStackValueType convertInt(SWFAppContext* app_context) +{ + copyReg(app_context); + + s32 i; + + switch (STACK_TOP_TYPE) + { + case ACTION_STACK_VALUE_STRING: + i = atoi((char*) VAL(u64, &STACK_TOP_VALUE)); + VAL(u32, &STACK_TOP_VALUE) = i; + break; + case ACTION_STACK_VALUE_F32: + f32 f = VAL(f32, &STACK_TOP_VALUE); + i = (s32) f; + VAL(u32, &STACK_TOP_VALUE) = i; + break; + case ACTION_STACK_VALUE_F64: + f64 d = VAL(f64, &STACK_TOP_VALUE); + i = (s32) d; + VAL(u32, &STACK_TOP_VALUE) = i; + break; + } + + STACK_TOP_TYPE = ACTION_STACK_VALUE_INT; + + return ACTION_STACK_VALUE_INT; +} + +ActionStackValueType convertIntECMA(SWFAppContext* app_context) +{ + copyReg(app_context); + + ActionVar v; + popVar(app_context, &v); + + toInteger(app_context, &v); + + releaseObjectVar(app_context, &v); + + return ACTION_STACK_VALUE_F64; +} + +// fully ECMA 262-3 compliant +ActionStackValueType convertBool(SWFAppContext* app_context) +{ + copyReg(app_context); + + // TODO: make the macros for checking objects better + if ((STACK_TOP_TYPE & 0x10) != 0x00) + { + ASObject* o = (ASObject*) STACK_TOP_VALUE; + OBJ_LOCK_WRITE(o, + { + releaseObject(app_context, o); + }); + + VAL(bool, &STACK_TOP_VALUE) = true; + STACK_TOP_TYPE = ACTION_STACK_VALUE_BOOLEAN; + return ACTION_STACK_VALUE_BOOLEAN; + } + + bool b; + + switch (STACK_TOP_TYPE) + { + case ACTION_STACK_VALUE_STRING: + b = STACK_TOP_N != 0; + VAL(bool, &STACK_TOP_VALUE) = b; + break; + case ACTION_STACK_VALUE_F32: + f32 f = VAL(f32, &STACK_TOP_VALUE); + b = f != +0.0f && f != -0.0f && f != NAN; + VAL(bool, &STACK_TOP_VALUE) = b; + break; + case ACTION_STACK_VALUE_F64: + f64 d = VAL(f64, &STACK_TOP_VALUE); + b = d != +0.0 && d != -0.0 && d != NAN; + VAL(bool, &STACK_TOP_VALUE) = b; + break; + case ACTION_STACK_VALUE_INT: + int i = VAL(s32, &STACK_TOP_VALUE); + b = i != 0; + VAL(bool, &STACK_TOP_VALUE) = b; + break; + } + + STACK_TOP_TYPE = ACTION_STACK_VALUE_BOOLEAN; + + return ACTION_STACK_VALUE_BOOLEAN; +} + +void actionAdd(SWFAppContext* app_context) +{ + convertDouble(app_context); ActionVar a; popVar(app_context, &a); - convertFloat(app_context); + convertDouble(app_context); + ActionVar b; + popVar(app_context, &b); + + double c = b.f64 + a.f64; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_F64(c); +} + +void actionAdd2(SWFAppContext* app_context) +{ + copy2Regs(app_context); + + if (IS_OBJ_T(STACK_TOP_TYPE) || IS_OBJ_T(STACK_SECOND_TOP_TYPE)) + { + ActionVar a; + popVar(app_context, &a); + + ActionVar b; + popVar(app_context, &b); + + if (IS_OBJ_T(b.type)) + { + ASObject* this = b.object; + + toPrimitive(app_context, this); + } + + else + { + pushVar(app_context, &b); + } + + if (IS_OBJ_T(a.type)) + { + ASObject* this = a.object; + + toPrimitive(app_context, this); + } + + else + { + pushVar(app_context, &a); + } + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + } + + if (IS_STR_T(STACK_TOP_TYPE) || IS_STR_T(STACK_SECOND_TOP_TYPE)) + { + convertString(app_context); + ActionVar a_str; + popVar(app_context, &a_str); + + convertString(app_context); + ActionVar b_str; + popVar(app_context, &b_str); + + u32 a_n = a_str.str_size; + u32 b_n = b_str.str_size; + + PUSH_STR_STACK(b_n + a_n); + + memcpy((char*) &STACK_TOP_VALUE, b_str.str, b_n); + memcpy(((char*) &STACK_TOP_VALUE) + b_n, a_str.str, a_n); + *((u8*) (((char*) &STACK_TOP_VALUE) + b_n + a_n)) = '\0'; + + releaseObjectVar(app_context, &a_str); + releaseObjectVar(app_context, &b_str); + + return; + } + + convertDouble(app_context); + ActionVar a; + popVar(app_context, &a); + + convertDouble(app_context); ActionVar b; popVar(app_context, &b); - if (VAL(float, &a.value) == 0.0f) - { - // SWF 4: - PUSH_STR("#ERROR#", 8); - - // SWF 5: - //~ if (a->value == 0.0f) + // wait it's just 754 do i need to do any of this LOL + + if ((b.f64 == NAN || a.f64 == NAN) || + b.f64 == INFINITY && a.f64 == -INFINITY || + b.f64 == -INFINITY && a.f64 == INFINITY) + { + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + f64 nan = NAN; + PUSH_F64(nan); + return; + } + + if (b.f64 == INFINITY || a.f64 == INFINITY) + { + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + f64 inf = INFINITY; + PUSH_F64(inf); + return; + } + + if (b.f64 == -INFINITY || a.f64 == -INFINITY) + { + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + f64 ninf = -INFINITY; + PUSH_F64(ninf); + return; + } + + if (b.f64 == -0.0 && a.f64 == -0.0) + { + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + f64 n0 = -0.0; + PUSH_F64(n0); + return; + } + + if ((b.f64 == +0.0 || b.f64 == -0.0) && + (a.f64 == +0.0 || a.f64 == -0.0)) + { + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + f64 p0 = +0.0; + PUSH_F64(p0); + return; + } + + // eh too late now + + double c = b.f64 + a.f64; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_F64(c); +} + +void actionSubtract(SWFAppContext* app_context) +{ + convertDouble(app_context); + ActionVar a; + popVar(app_context, &a); + + convertDouble(app_context); + ActionVar b; + popVar(app_context, &b); + + double c = b.f64 - a.f64; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_F64(c); +} + +void actionMultiply(SWFAppContext* app_context) +{ + convertDouble(app_context); + ActionVar a; + popVar(app_context, &a); + + convertDouble(app_context); + ActionVar b; + popVar(app_context, &b); + + double c = b.f64*a.f64; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_F64(c); +} + +void actionDivide(SWFAppContext* app_context) +{ + convertDouble(app_context); + ActionVar a; + popVar(app_context, &a); + + convertDouble(app_context); + ActionVar b; + popVar(app_context, &b); + + f64 c; + + if (a.f64 == 0.0) + { + // TODO: handle versioning from the recompiler + switch (app_context->version) + { + case 4: + { + PUSH_STR("#ERROR#", 8); + break; + } + + case 5: + { + if (a.f64 == 0.0) + { + c = NAN; + PUSH_F64(c); + } + + else if (a.f64 > 0.0) + { + c = INFINITY; + PUSH_F64(c); + } + + else + { + c = -INFINITY; + PUSH_F64(c); + } + } + } + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + return; + } + + c = b.f64/a.f64; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_F64(c); +} + +void actionModulo(SWFAppContext* app_context) +{ + convertDouble(app_context); + ActionVar a; + popVar(app_context, &a); + + convertDouble(app_context); + ActionVar b; + popVar(app_context, &b); + + if (UNLIKELY(a.f64 == 0.0)) + { + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + f64 nan = NAN; + PUSH_F64(nan); + return; + } + + f64 mod = fmod(b.f64, a.f64); + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_F64(mod); +} + +void actionIncrement(SWFAppContext* app_context) +{ + convertDouble(app_context); + ActionVar v; + popVar(app_context, &v); + + f64 inc = v.f64 + 1.0; + + releaseObjectVar(app_context, &v); + + PUSH_F64(inc); +} + +void actionDecrement(SWFAppContext* app_context) +{ + convertDouble(app_context); + ActionVar v; + popVar(app_context, &v); + + f64 dec = v.f64 - 1.0; + + releaseObjectVar(app_context, &v); + + PUSH_F64(dec); +} + +// ================================================================== +// Bitwise Operations +// ================================================================== + +void actionBitAnd(SWFAppContext* app_context) +{ + convertInt(app_context); + ActionVar a; + popVar(app_context, &a); + + convertInt(app_context); + ActionVar b; + popVar(app_context, &b); + + s32 and = b.u32 & a.u32; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_INT(and); +} + +void actionBitOr(SWFAppContext* app_context) +{ + convertInt(app_context); + ActionVar a; + popVar(app_context, &a); + + convertInt(app_context); + ActionVar b; + popVar(app_context, &b); + + s32 or = b.u32 | a.u32; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_INT(or); +} + +void actionBitLShift(SWFAppContext* app_context) +{ + convertInt(app_context); + ActionVar a; + popVar(app_context, &a); + + convertInt(app_context); + ActionVar b; + popVar(app_context, &b); + + s32 lsh = b.u32 << (a.u32 & 0b11111); + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_INT(lsh); +} + +void actionBitRShift(SWFAppContext* app_context) +{ + convertInt(app_context); + ActionVar a; + popVar(app_context, &a); + + convertInt(app_context); + ActionVar b; + popVar(app_context, &b); + + s32 rsh = b.s32 >> (a.u32 & 0b11111); + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_INT(rsh); +} + +void actionBitURShift(SWFAppContext* app_context) +{ + convertInt(app_context); + ActionVar a; + popVar(app_context, &a); + + convertInt(app_context); + ActionVar b; + popVar(app_context, &b); + + u32 rsh = b.u32 >> (a.u32 & 0b11111); + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_INT(rsh); +} + +void actionBitXor(SWFAppContext* app_context) +{ + convertInt(app_context); + ActionVar a; + popVar(app_context, &a); + + convertInt(app_context); + ActionVar b; + popVar(app_context, &b); + + u32 xor = b.u32 ^ a.u32; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_INT(xor); +} + +// ================================================================== +// Comparison Operations +// ================================================================== + +void actionEquals(SWFAppContext* app_context) +{ + convertDouble(app_context); + ActionVar a; + popVar(app_context, &a); + + convertDouble(app_context); + ActionVar b; + popVar(app_context, &b); + + bool equals = b.f64 == a.f64; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_BOOL(equals); +} + +void actionEquals2(SWFAppContext* app_context) +{ + ActionVar a; + popVar(app_context, &a); + + ActionVar b; + popVar(app_context, &b); + + if (b.type != a.type) + { + if (IS_NUM_T(a.type) && IS_NUM_T(b.type)) + { + convertNumericToNumber(app_context, &a); + convertNumericToNumber(app_context, &b); + } + + // TEMPORARY: + else if (IS_UNDEFINED(a) != IS_UNDEFINED(b)) + { + PUSH_BOOL(false); + goto release; + } + + else if (IS_NULL(a) != IS_NULL(b)) + { + PUSH_BOOL(false); + goto release; + } + + else + { + UNIMPLEMENTED("Equals2 of differing types"); + } + } + + if (b.type == ACTION_STACK_VALUE_UNDEFINED || + b.type == ACTION_STACK_VALUE_NULL) + { + PUSH_BOOL(true); + goto release; + } + + if (!IS_NUM_T(b.type)) + { + if (IS_STR_T(b.type)) + { + if (b.str_size != a.str_size) + { + PUSH_BOOL(false); + goto release; + } + + PUSH_BOOL(strncmp(b.str, a.str, b.str_size) == 0); + goto release; + } + + if (b.type == ACTION_STACK_VALUE_BOOLEAN) + { + PUSH_BOOL(b.b && a.b || !b.b && !a.b); + goto release; + } + + // why does this need double parens, sadge + PUSH_BOOL((b.object == a.object)); + goto release; + } + + if (IS_OBJ_T(b.type)) + { + toPrimitive(app_context, b.object); + } + + if (IS_OBJ_T(a.type)) + { + toPrimitive(app_context, a.object); + } + + convertNumericToNumber(app_context, &b); + convertNumericToNumber(app_context, &a); + + if (b.f64 == NAN || a.f64 == NAN) + { + PUSH_BOOL(false); + goto release; + } + + if (b.f64 == a.f64 || + b.f64 == +0.0 && a.f64 == -0.0 || + b.f64 == -0.0 && a.f64 == +0.0) + { + PUSH_BOOL(true); + goto release; + } + + PUSH_BOOL(false); + + release: + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); +} + +void actionLess(SWFAppContext* app_context) +{ + ActionVar a; + convertDouble(app_context); + popVar(app_context, &a); + + ActionVar b; + convertDouble(app_context); + popVar(app_context, &b); + + bool less = b.f64 < a.f64; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_BOOL(less); +} + +void actionLess2(SWFAppContext* app_context) +{ + copy2Regs(app_context); + + if (IS_OBJ_T(STACK_TOP_TYPE) || IS_OBJ_T(STACK_SECOND_TOP_TYPE)) + { + ActionVar a; + popVar(app_context, &a); + + ActionVar b; + popVar(app_context, &b); + + if (IS_OBJ_T(b.type)) + { + ASObject* this = b.object; + + toPrimitive(app_context, this); + } + + if (IS_OBJ_T(a.type)) + { + ASObject* this = a.object; + + toPrimitive(app_context, this); + } + + pushVar(app_context, &b); + + pushVar(app_context, &a); + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + } + + if (IS_STR_T(STACK_TOP_TYPE) && IS_STR_T(STACK_SECOND_TOP_TYPE)) + { + UNIMPLEMENTED("Less2 String comparison"); + } + + ActionVar a; + convertDouble(app_context); + popVar(app_context, &a); + + ActionVar b; + convertDouble(app_context); + popVar(app_context, &b); + + if (b.f64 == NAN || a.f64 == NAN) + { + PUSH_UNDEFINED(); + goto release; + } + + if (b.f64 == a.f64 || + b.f64 == +0.0 && a.f64 == -0.0 || + b.f64 == -0.0 && a.f64 == +0.0) + { + PUSH_BOOL(false); + goto release; + } + + if (b.f64 == INFINITY) + { + PUSH_BOOL(false); + goto release; + } + + if (a.f64 == INFINITY) + { + PUSH_BOOL(true); + goto release; + } + + if (a.f64 == -INFINITY) + { + PUSH_BOOL(false); + goto release; + } + + if (b.f64 == -INFINITY) + { + PUSH_BOOL(true); + goto release; + } + + PUSH_BOOL(b.f64 < a.f64); + + release: + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); +} + +void actionGreater(SWFAppContext* app_context) +{ + ActionVar a; + convertDouble(app_context); + popVar(app_context, &a); + + ActionVar b; + convertDouble(app_context); + popVar(app_context, &b); + + bool greater = b.f64 > a.f64; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_BOOL(greater); +} + +void actionAnd(SWFAppContext* app_context) +{ + ActionVar a; + convertBool(app_context); + popVar(app_context, &a); + + ActionVar b; + convertBool(app_context); + popVar(app_context, &b); + + bool and = b.b && a.b; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_BOOL(and); +} + +void actionOr(SWFAppContext* app_context) +{ + ActionVar a; + convertBool(app_context); + popVar(app_context, &a); + + ActionVar b; + convertBool(app_context); + popVar(app_context, &b); + + bool or = b.b || a.b; + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + PUSH_BOOL(or); +} + +void actionNot(SWFAppContext* app_context) +{ + ActionVar v; + convertBool(app_context); + popVar(app_context, &v); + + bool b = !v.b; + + releaseObjectVar(app_context, &v); + + PUSH_BOOL(b); +} + +// ================================================================== +// String Operations +// ================================================================== + +int strcmp_list_a_list_b(u64 a_value, u64 b_value) +{ + char** a_list = (char**) a_value; + char** b_list = (char**) b_value; + + u64 num_a_strings = (u64) a_list[0]; + u64 num_b_strings = (u64) b_list[0]; + + u64 a_str_i = 0; + u64 b_str_i = 0; + + u64 a_i = 0; + u64 b_i = 0; + + u64 min_count = (num_a_strings < num_b_strings) ? num_a_strings : num_b_strings; + + while (1) + { + char c_a = a_list[a_str_i + 1][a_i]; + char c_b = b_list[b_str_i + 1][b_i]; + + if (c_a == 0) + { + if (a_str_i + 1 != min_count) + { + a_str_i += 1; + a_i = 0; + continue; + } + + else + { + return c_a - c_b; + } + } + + if (c_b == 0) + { + if (b_str_i + 1 != min_count) + { + b_str_i += 1; + b_i = 0; + continue; + } + + else + { + return c_a - c_b; + } + } + + if (c_a != c_b) + { + return c_a - c_b; + } + + a_i += 1; + b_i += 1; + } + + EXC("um how lol\n"); + return 0; +} + +int strcmp_list_a_not_b(u64 a_value, u64 b_value) +{ + char** a_list = (char**) a_value; + char* b_str = (char*) b_value; + + u64 num_a_strings = (u64) a_list[0]; + + u64 a_str_i = 0; + + u64 a_i = 0; + u64 b_i = 0; + + while (1) + { + char c_a = a_list[a_str_i + 1][a_i]; + char c_b = b_str[b_i]; + + if (c_a == 0) + { + if (a_str_i + 1 != num_a_strings) + { + a_str_i += 1; + a_i = 0; + continue; + } + + else + { + return c_a - c_b; + } + } + + if (c_a != c_b) + { + return c_a - c_b; + } + + a_i += 1; + b_i += 1; + } + + EXC("um how lol\n"); + return 0; +} + +int strcmp_not_a_list_b(u64 a_value, u64 b_value) +{ + char* a_str = (char*) a_value; + char** b_list = (char**) b_value; + + u64 num_b_strings = (u64) b_list[0]; + + u64 b_str_i = 0; + + u64 a_i = 0; + u64 b_i = 0; + + while (1) + { + char c_a = a_str[a_i]; + char c_b = b_list[b_str_i + 1][b_i]; + + if (c_b == 0) + { + if (b_str_i + 1 != num_b_strings) + { + b_str_i += 1; + b_i = 0; + continue; + } + + else + { + return c_a - c_b; + } + } + + if (c_a != c_b) + { + return c_a - c_b; + } + + a_i += 1; + b_i += 1; + } + + EXC("um how lol\n"); + return 0; +} + +void actionStringEquals(SWFAppContext* app_context, char* a_str, char* b_str) +{ + ActionVar a; + convertString(app_context); + popVar(app_context, &a); + + ActionVar b; + convertString(app_context); + popVar(app_context, &b); + + int cmp_result; + + int a_is_list = a.type == ACTION_STACK_VALUE_STR_LIST; + int b_is_list = b.type == ACTION_STACK_VALUE_STR_LIST; + + if (a_is_list && b_is_list) + { + cmp_result = strcmp_list_a_list_b(a.value, b.value); + } + + else if (a_is_list && !b_is_list) + { + cmp_result = strcmp_list_a_not_b(a.value, b.value); + } + + else if (!a_is_list && b_is_list) + { + cmp_result = strcmp_not_a_list_b(a.value, b.value); + } + + else + { + cmp_result = strcmp((char*) a.value, (char*) b.value); + } + + releaseObjectVar(app_context, &a); + releaseObjectVar(app_context, &b); + + float result = cmp_result == 0 ? 1.0f : 0.0f; + PUSH_F32(result); +} + +void actionStringLength(SWFAppContext* app_context, char* v_str) +{ + ActionVar v; + convertString(app_context); + popVar(app_context, &v); + + PUSH_INT(v.str_size); +} + +void actionStringAdd(SWFAppContext* app_context, char* a_str, char* b_str) +{ + // TODO: check if this handles the stack properly + + ActionVar a; + convertString(app_context); + peekVar(app_context, &a); + + ActionVar b; + convertString(app_context); + peekSecondVar(app_context, &b); + + u64 num_a_strings; + u64 num_b_strings; + u64 num_strings = 0; + + if (b.type == ACTION_STACK_VALUE_STR_LIST) + { + num_b_strings = *((u64*) b.value); + } + + else + { + num_b_strings = 1; + } + + num_strings += num_b_strings; + + if (a.type == ACTION_STACK_VALUE_STR_LIST) + { + num_a_strings = *((u64*) a.value); + } + + else + { + num_a_strings = 1; + } + + num_strings += num_a_strings; + + PUSH_STR_LIST(b.str_size + a.str_size, (u32) sizeof(u64)*(2*num_strings + 1)); + + u64* str_list = (u64*) &STACK_TOP_VALUE; + str_list[0] = num_strings; + + if (b.type == ACTION_STACK_VALUE_STR_LIST) + { + u64* b_list = (u64*) b.value; + + for (u64 i = 0; i < num_b_strings; ++i) + { + u64 str_i = 2*i; + str_list[str_i + 1] = b_list[str_i + 1]; + str_list[str_i + 2] = b_list[str_i + 2]; + } + } + + else + { + str_list[1] = b.value; + str_list[2] = b.str_size; + } + + if (a.type == ACTION_STACK_VALUE_STR_LIST) + { + u64* a_list = (u64*) a.value; + + for (u64 i = 0; i < num_a_strings; ++i) + { + u64 str_i = 2*i; + str_list[str_i + 1 + 2*num_b_strings] = a_list[str_i + 1]; + str_list[str_i + 2 + 2*num_b_strings] = a_list[str_i + 2]; + } + } + + else + { + str_list[1 + 2*num_b_strings] = a.value; + str_list[1 + 2*num_b_strings + 1] = a.str_size; + } +} + +// ================================================================== +// Variable Operations +// ================================================================== + +void actionGetVariable(SWFAppContext* app_context) +{ + copyReg(app_context); + + // TODO: see if this can be an object + + // Read variable name info from stack + u32 string_id = STACK_TOP_ID; + char* var_name = (char*) STACK_TOP_VALUE; + u32 var_name_len = STACK_TOP_N; + + // Pop variable name + POP(); + + // Get variable (fast path for constant strings) + ASProperty* p = NULL; + + switch (string_id) + { + case 0: + { + // TODO: Dynamic string - use hashmap (O(n)) + //~ var = getVariable(app_context, var_name, var_name_len); + EXC_ARG("Unimplemented: string %s has no id\n", var_name); + break; + } + + case STR_ID_GLOBAL: + { + PUSH_OBJ(_global); + + return; + } + + case STR_ID_ROOT: + { + PUSH_OBJ(app_context->_root); + + return; + } + + default: + { + // Constant string - use scope object (O(lg(n))) + for (u32 i = scope_top_obj; i < MAX_SCOPE_DEPTH; --i) + { + OBJ_LOCK_READ(scope_chain[i], + { + p = getProperty(app_context, scope_chain[i], string_id, var_name, var_name_len); + }); + + if (p != NULL) + { + break; + } + } + + break; + } + } + + if (p != NULL) + { + // Push variable value to stack + PUSH_VAR(&p->value); + } + + else + { + PUSH_UNDEFINED(); + } +} + +void actionSetVariable(SWFAppContext* app_context) +{ + // Stack layout: [value] [name] <- sp + // We need value at top, name at second + + ActionVar value; + popVar(app_context, &value); + + copyReg(app_context); + + // Read variable name info + u32 string_id = STACK_TOP_ID; + char* var_name = (char*) STACK_TOP_VALUE; + u32 var_name_len = STACK_TOP_N; + + POP(); + + ASProperty* p = NULL; + + ASObject* scope_obj; + + switch (string_id) + { + case 0: + { + // TODO: Dynamic string - use hashmap (O(n)) + //~ var = getVariable(app_context, var_name, var_name_len); + EXC_ARG("Unimplemented: string %s has no id\n", var_name); + break; + } + + case STR_ID_GLOBAL: + { + EXC("can't set _global\n"); + + // sue me + goto release_value; + } + + case STR_ID_ROOT: + { + EXC("can't set _root\n"); + + // sue me + goto release_value; + } + + default: + { + // Constant string - use scope object (O(lg(n))) + for (u32 i = scope_top_obj; i < MAX_SCOPE_DEPTH; --i) + { + scope_obj = scope_chain[i]; + + OBJ_LOCK_READ(scope_obj, + { + p = getProperty(app_context, scope_obj, string_id, var_name, var_name_len); + }); + + if (p != NULL) + { + break; + } + } + + break; + } + } + + if (p != NULL) + { + bool should_release = false; + ASObject* old_obj; + + if (IS_OBJ(p->value)) + { + should_release = true; + old_obj = (ASObject*) p->value.value; + } + + if (IS_OBJ(value)) + { + OBJ_LOCK_WRITE(value.object, + { + retainObject(value.object); + }); + } + + OBJ_LOCK_WRITE(scope_obj, + { + p->value = value; + }); + + if (should_release) + { + OBJ_LOCK_WRITE(old_obj, + { + releaseObject(app_context, old_obj); + }); + } + } + + else + { + setProperty(app_context, scope_chain[1], string_id, var_name, var_name_len, &value); + } + + release_value: + + releaseObjectVar(app_context, &value); +} + +void actionToInteger(SWFAppContext* app_context) +{ + convertIntECMA(app_context); +} + +void actionToNumber(SWFAppContext* app_context) +{ + convertDouble(app_context); +} + +void actionToString(SWFAppContext* app_context) +{ + convertString(app_context); +} + +// ================================================================== +// Utility Operations +// ================================================================== + +void actionTrace(SWFAppContext* app_context) +{ + ActionVar v; + popVar(app_context, &v); + + if (IS_OBJ_T(v.type)) + { + getAndCallMethod(app_context, v.object, STR_ID_TO_STRING, 0); + releaseObjectVar(app_context, &v); + popVar(app_context, &v); + + printf("%s\n", v.str); + + releaseObjectVar(app_context, &v); + return; + } + + switch (v.type) + { + case ACTION_STACK_VALUE_STRING: + { + printf("%s\n", v.str); + + if (v.owns_memory) + { + FREE(v.str); + } + + break; + } + + case ACTION_STACK_VALUE_NULL: + { + printf("null\n"); + break; + } + + case ACTION_STACK_VALUE_UNDEFINED: + { + printf("undefined\n"); + break; + } + + case ACTION_STACK_VALUE_BOOLEAN: + { + printf("%s\n", v.b ? "true" : "false"); + break; + } + + case ACTION_STACK_VALUE_STR_LIST: + { + u64* str_list = (u64*) &v.value; + + for (u64 i = 0; i < 2*str_list[0]; i += 2) + { + printf("%s", (char*) str_list[i + 1]); + } + + printf("\n"); + + break; + } + + case ACTION_STACK_VALUE_F32: + { + printf("%.15g\n", v.f32); + break; + } + + case ACTION_STACK_VALUE_F64: + { + printf("%.15g\n", v.f64); + break; + } + + case ACTION_STACK_VALUE_INT: + { + printf("%d\n", v.s32); + break; + } + + default: + { + fprintf(stderr, "Bad print type: %d\n", v.type); + break; + } + } + + fflush(stdout); + + releaseObjectVar(app_context, &v); +} + +void actionGetTime(SWFAppContext* app_context) +{ + u32 delta_ms = get_elapsed_ms() - start_time; + float delta_ms_f32 = (float) delta_ms; + + PUSH_F32(delta_ms_f32); +} + +// ================================================================== +// EnumeratedName helper structures for property enumeration +// ================================================================== + +typedef struct EnumeratedName EnumeratedName; + +struct EnumeratedName { + const char* name; + u32 name_length; + EnumeratedName* next; +}; + +/** + * Check if a property name has already been enumerated + */ +static int isPropertyEnumerated(EnumeratedName* head, const char* name, u32 name_length) +{ + EnumeratedName* current = head; + while (current != NULL) + { + if (current->name_length == name_length && + strncmp(current->name, name, name_length) == 0) + { + return 1; // Found - property was already enumerated + } + current = current->next; + } + return 0; // Not found +} + +/** + * Add a property name to the enumerated list + */ +static void addEnumeratedName(EnumeratedName** head, const char* name, u32 name_length) +{ + EnumeratedName* node = (EnumeratedName*) malloc(sizeof(EnumeratedName)); + if (node == NULL) + { + return; // Out of memory, skip this property + } + node->name = name; + node->name_length = name_length; + node->next = *head; + *head = node; +} + +/** + * Free the enumerated names list + */ +static void freeEnumeratedNames(EnumeratedName* head) +{ + while (head != NULL) + { + EnumeratedName* next = head->next; + free(head); + head = next; + } +} + +void actionEnumerate(SWFAppContext* app_context, char* str_buffer) +{ + //~ // Step 1: Pop variable name from stack + //~ // Stack layout for strings: +0=type, +4=oldSP, +8=length, +12=string_id, +16=pointer + //~ u32 string_id = VAL(u32, &STACK[SP + 12]); + //~ char* var_name = (char*) VAL(u64, &STACK[SP + 16]); + //~ u32 var_name_len = VAL(u32, &STACK[SP + 8]); + //~ POP(); + +//~ #ifdef DEBUG + //~ printf("[DEBUG] actionEnumerate: looking up variable '%.*s' (len=%u, id=%u)\n", + //~ var_name_len, var_name, var_name_len, string_id); +//~ #endif + + //~ // Step 2: Look up the variable + //~ ActionVar* var = NULL; + //~ if (string_id > 0) + //~ { + //~ // Constant string - use array lookup (O(1)) + //~ var = getVariableById(app_context, string_id); + //~ } + //~ else + //~ { + //~ // Dynamic string - use hashmap (O(n)) + //~ var = getVariable(app_context, var_name, var_name_len); + //~ } + + //~ // Step 3: Check if variable exists and is an object + //~ if (!var || var->type != ACTION_STACK_VALUE_OBJECT) + //~ { +//~ #ifdef DEBUG + //~ if (!var) + //~ printf("[DEBUG] actionEnumerate: variable not found\n"); + //~ else + //~ printf("[DEBUG] actionEnumerate: variable is not an object (type=%d)\n", var->type); +//~ #endif + //~ // Variable not found or not an object - push null terminator only + //~ PUSH(ACTION_STACK_VALUE_UNDEFINED, 0); + //~ return; + //~ } + + //~ // Step 4: Get the object from the variable + //~ ASObject* obj = (ASObject*) VAL(u64, &var->value); + //~ if (obj == NULL) + //~ { +//~ #ifdef DEBUG + //~ printf("[DEBUG] actionEnumerate: object pointer is NULL\n"); +//~ #endif + //~ // Null object - push null terminator only + //~ PUSH(ACTION_STACK_VALUE_UNDEFINED, 0); + //~ return; + //~ } + + //~ // Step 5: Collect all enumerable properties from the entire prototype chain + //~ // We need to collect them first to push in reverse order + + //~ // Temporary storage for property names (we'll push them to stack after collecting) + //~ typedef struct PropList { + //~ const char* name; + //~ u32 name_length; + //~ struct PropList* next; + //~ } PropList; + + //~ PropList* prop_head = NULL; + //~ u32 total_props = 0; + + //~ // Track which properties we've already seen (to handle shadowing) + //~ EnumeratedName* enumerated_head = NULL; + + //~ // Walk the prototype chain + //~ ASObject* current_obj = obj; + //~ int chain_depth = 0; + //~ const int MAX_CHAIN_DEPTH = 100; // Prevent infinite loops + + //~ while (current_obj != NULL && chain_depth < MAX_CHAIN_DEPTH) + //~ { + //~ chain_depth++; + +//~ #ifdef DEBUG + //~ printf("[DEBUG] actionEnumerate: walking prototype chain depth=%d, num_used=%u\n", + //~ chain_depth, current_obj->num_used); +//~ #endif + + //~ // Enumerate properties from this level + //~ for (u32 i = 0; i < current_obj->num_used; i++) + //~ { + //~ const char* prop_name = current_obj->properties[i].name; + //~ u32 prop_name_len = current_obj->properties[i].name_length; + //~ u8 prop_flags = current_obj->properties[i].flags; + + //~ // Skip if property is not enumerable (DontEnum) + //~ if (!(prop_flags & PROPERTY_FLAG_ENUMERABLE)) + //~ { +//~ #ifdef DEBUG + //~ printf("[DEBUG] actionEnumerate: skipping non-enumerable property '%.*s'\n", + //~ prop_name_len, prop_name); +//~ #endif + //~ continue; + //~ } + + //~ // Skip if we've already enumerated this property name (shadowing) + //~ if (isPropertyEnumerated(enumerated_head, prop_name, prop_name_len)) + //~ { +//~ #ifdef DEBUG + //~ printf("[DEBUG] actionEnumerate: skipping shadowed property '%.*s'\n", + //~ prop_name_len, prop_name); +//~ #endif + //~ continue; + //~ } + + //~ // Add to enumerated list + //~ addEnumeratedName(&enumerated_head, prop_name, prop_name_len); + + //~ // Add to property list (for later pushing to stack) + //~ PropList* node = (PropList*) malloc(sizeof(PropList)); + //~ if (node != NULL) + //~ { + //~ node->name = prop_name; + //~ node->name_length = prop_name_len; + //~ node->next = prop_head; + //~ prop_head = node; + //~ total_props++; + +//~ #ifdef DEBUG + //~ printf("[DEBUG] actionEnumerate: added enumerable property '%.*s'\n", + //~ prop_name_len, prop_name); +//~ #endif + //~ } + //~ } + + //~ // Move to prototype via __proto__ property + //~ ActionVar* proto_var = getProperty(app_context, current_obj, 0, "__proto__", 9); + //~ if (proto_var != NULL && proto_var->type == ACTION_STACK_VALUE_OBJECT) + //~ { + //~ current_obj = (ASObject*) proto_var->value; +//~ #ifdef DEBUG + //~ printf("[DEBUG] actionEnumerate: following __proto__ to next level\n"); +//~ #endif + //~ } + //~ else + //~ { + //~ // End of prototype chain + //~ current_obj = NULL; + //~ } + //~ } + + //~ // Free the enumerated names list + //~ freeEnumeratedNames(enumerated_head); + +//~ #ifdef DEBUG + //~ printf("[DEBUG] actionEnumerate: collected %u enumerable properties total\n", total_props); +//~ #endif + + //~ // Step 6: Push null terminator first + //~ // This marks the end of the enumeration for for..in loops + //~ PUSH(ACTION_STACK_VALUE_UNDEFINED, 0); + + //~ // Step 7: Push property names from the list (they're already in reverse order) + //~ while (prop_head != NULL) + //~ { + //~ PUSH_STR((char*)prop_head->name, prop_head->name_length); + + //~ PropList* next = prop_head->next; + //~ free(prop_head); + //~ prop_head = next; + //~ } +} + +bool evaluateCondition(SWFAppContext* app_context) +{ + ActionVar v; + convertBool(app_context); + popVar(app_context, &v); + + bool b = v.b; + + releaseObjectVar(app_context, &v); + + return b; +} + +void actionDefineLocal(SWFAppContext* app_context) +{ + // Stack layout: [name, value] <- sp + // According to AS2 spec for DefineLocal: + // Pop value first, then name + // So VALUE is at top (*sp), NAME is at second (SP_SECOND_TOP) + + // DefineLocal ALWAYS creates/updates in the local scope + // We have a local scope object - define variable as a property + ASObject* local_scope = scope_chain[scope_top_obj]; + ActionVar value_var; + popVar(app_context, &value_var); + + copyReg(app_context); + + // Read variable name info + // Stack layout for strings: +0=type, +4=oldSP, +8=length, +12=string_id, +16=pointer + u32 string_id = STACK_TOP_ID; + char* var_name = (char*) STACK_TOP_VALUE; + u32 var_name_len = STACK_TOP_N; + + POP(); + + // Set property on the local scope object + // This will create the property if it doesn't exist, or update if it does + setProperty(app_context, local_scope, string_id, var_name, var_name_len, &value_var); + + releaseObjectVar(app_context, &value_var); +} + +void actionDefineLocal2(SWFAppContext* app_context) +{ + // DefineLocal2 pops only the variable name (no value) + // It declares a local variable initialized to undefined + + // Stack layout: [name] <- sp + + copyReg(app_context); + + // Read variable name info + u32 string_id = STACK_TOP_ID; + char* var_name = (char*) STACK_TOP_VALUE; + u32 var_name_len = STACK_TOP_N; + + // Pop the name + POP(); + + // Declare variable as undefined property at top of scope + ASObject* local_scope = scope_chain[scope_top_obj]; + + // Create an undefined value + ActionVar undefined_var; + undefined_var.type = ACTION_STACK_VALUE_UNDEFINED; + + // Set property on the local scope object + // This will create the property if it doesn't exist + setProperty(app_context, local_scope, string_id, var_name, var_name_len, &undefined_var); +} + +void actionTypeOf(SWFAppContext* app_context) +{ + copyReg(app_context); + + // Peek at the type without modifying value + u8 type = STACK_TOP_TYPE; + + // Pop the value + POP(); + + // Determine type string based on stack type + char* type_str; + switch (type) + { + case ACTION_STACK_VALUE_F32: + case ACTION_STACK_VALUE_F64: + case ACTION_STACK_VALUE_INT: + type_str = "number"; + break; + + case ACTION_STACK_VALUE_BOOLEAN: + type_str = "boolean"; + break; + + case ACTION_STACK_VALUE_STRING: + case ACTION_STACK_VALUE_STR_LIST: + type_str = "string"; + break; + + //~ case ACTION_STACK_VALUE_FUNCTION: + //~ type_str = "function"; + //~ break; + + case ACTION_STACK_VALUE_OBJECT: + // Arrays are objects in ActionScript (typeof [] returns "object") + type_str = "object"; + break; + + case ACTION_STACK_VALUE_UNDEFINED: + type_str = "undefined"; + break; + + default: + type_str = "undefined"; + break; + } + + // Copy to str_buffer and push + u32 len = (u32) strnlen(type_str, 10); + PUSH_STR(type_str, len); +} + +void actionDelete2(SWFAppContext* app_context, char* str_buffer) +{ + //~ // Delete2 deletes a named property/variable + //~ // Pops the name from the stack, deletes it, pushes success boolean + + //~ // Read variable name from stack + //~ u8 name_type = STACK_TOP_TYPE; + //~ u32 string_id = 0; + //~ char* var_name = NULL; + //~ u32 var_name_len = 0; + + //~ // Get the variable name string + //~ if (name_type == ACTION_STACK_VALUE_STRING) + //~ { + //~ string_id = STACK_TOP_ID; + //~ var_name = (char*) STACK_TOP_VALUE; + //~ var_name_len = STACK_TOP_N; + //~ } + + //~ else if (name_type == ACTION_STACK_VALUE_STR_LIST) + //~ { + //~ // Materialize string list + //~ var_name = materializeStringList(app_context); + //~ var_name_len = strlen(var_name); + //~ } + + //~ // Pop the variable name + //~ POP(); + + //~ // Default: assume deletion succeeds (Flash behavior) + //~ bool success = true; + + //~ // Try to delete from scope chain (innermost to outermost) + //~ for (int i = scope_depth - 1; i >= 0; i--) + //~ { + //~ if (scope_chain[i] != NULL) //~ { - //~ float c = NAN; + //~ // Check if property exists in this scope object + //~ ActionVar* prop = getProperty(app_context, scope_chain[i], string_id, var_name, var_name_len); + //~ if (prop != NULL) + //~ { + //~ // Found in scope chain - delete it + //~ success = deleteProperty(app_context, scope_chain[i], var_name, var_name_len); + + //~ // Push result and return + //~ float result = success ? 1.0f : 0.0f; + //~ PUSH_F32(result); + //~ return; + //~ } //~ } + //~ } + + //~ // Not found in scope chain - check global variables + //~ // Note: In Flash, you cannot delete variables declared with 'var', so we return false + //~ // However, if the variable doesn't exist at all, we return true (Flash behavior) + //~ if (getVariable(app_context, var_name, var_name_len) != NULL) + //~ { + //~ // Variable exists but is a 'var' declaration - cannot delete + //~ success = false; + //~ } + //~ else + //~ { + //~ // Variable doesn't exist - Flash returns true + //~ success = true; + //~ } + + //~ // Push result + //~ float result = success ? 1.0f : 0.0f; + //~ PUSH_F32(result); +} + +/** + * Helper function to check if an object is an instance of a constructor + * + * Implements the same logic as ActionScript's instanceof operator: + * 1. Checks prototype chain - walks __proto__ looking for constructor's prototype + * 2. Checks interface implementation - for AS2 interfaces + * + * @param obj_var Pointer to the object to check + * @param ctor_var Pointer to the constructor function + * @return 1 if object is instance of constructor, 0 otherwise + */ +static int checkInstanceOf(ActionVar* obj_var, ActionVar* ctor_var) +{ + //~ // Primitives (number, string, undefined) are never instances + //~ if (obj_var->type == ACTION_STACK_VALUE_F32 || + //~ obj_var->type == ACTION_STACK_VALUE_F64 || + //~ obj_var->type == ACTION_STACK_VALUE_STRING || + //~ obj_var->type == ACTION_STACK_VALUE_UNDEFINED) + //~ { + //~ return 0; + //~ } + + //~ // Object and constructor must be object types + //~ if (obj_var->type != ACTION_STACK_VALUE_OBJECT && + //~ obj_var->type != ACTION_STACK_VALUE_ARRAY && + //~ obj_var->type != ACTION_STACK_VALUE_FUNCTION) + //~ { + //~ return 0; + //~ } + + //~ if (ctor_var->type != ACTION_STACK_VALUE_OBJECT && + //~ ctor_var->type != ACTION_STACK_VALUE_FUNCTION) + //~ { + //~ return 0; + //~ } + + //~ ASObject* obj = (ASObject*) obj_var->value; + //~ ASObject* ctor = (ASObject*) ctor_var->value; + + //~ if (obj == NULL || ctor == NULL) + //~ { + //~ return 0; + //~ } + + //~ // Get the constructor's "prototype" property + //~ ActionVar* ctor_proto_var = getProperty(app_context, ctor, 0, "prototype", 9); + //~ if (ctor_proto_var == NULL) + //~ { + //~ return 0; + //~ } + + //~ // Get the prototype object + //~ if (ctor_proto_var->type != ACTION_STACK_VALUE_OBJECT) + //~ { + //~ return 0; + //~ } + + //~ ASObject* ctor_proto = (ASObject*) ctor_proto_var->value; + //~ if (ctor_proto == NULL) + //~ { + //~ return 0; + //~ } + + //~ // Walk up the object's prototype chain via __proto__ property + //~ // Start with the object's __proto__ + //~ ActionVar* current_proto_var = getProperty(app_context, obj, 0, "__proto__", 9); + + //~ // Maximum chain depth to prevent infinite loops + //~ int max_depth = 100; + //~ int depth = 0; + + //~ while (current_proto_var != NULL && depth < max_depth) + //~ { + //~ depth++; - //~ else if (a->value > 0.0f) + //~ // Check if this prototype matches the constructor's prototype + //~ if (current_proto_var->type == ACTION_STACK_VALUE_OBJECT) //~ { - //~ float c = INFINITY; + //~ ASObject* current_proto = (ASObject*) current_proto_var->value; + + //~ if (current_proto == ctor_proto) + //~ { + //~ // Found a match! + //~ return 1; + //~ } + + //~ // Continue up the chain + //~ current_proto_var = getProperty(app_context, current_proto, 0, "__proto__", 9); //~ } - //~ else //~ { - //~ float c = -INFINITY; + //~ // Non-object in prototype chain, stop + //~ break; //~ } - } + //~ } - else - { - if (a.type == ACTION_STACK_VALUE_F64) - { - double a_val = VAL(double, &a.value); - double b_val = b.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &b.value) : VAL(double, &b.value); - - double c = b_val/a_val; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); - } - - else if (b.type == ACTION_STACK_VALUE_F64) - { - double a_val = a.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &a.value) : VAL(double, &a.value); - double b_val = VAL(double, &b.value); - - double c = b_val/a_val; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); - } - - else - { - float c = VAL(float, &b.value)/VAL(float, &a.value); - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); - } - } + //~ // Check interface implementation (ActionScript 2.0 implements keyword) + //~ if (implementsInterface(obj, ctor)) + //~ { + //~ return 1; + //~ } + + // Not found in prototype chain or interfaces + return 0; } -void actionEquals(SWFAppContext* app_context) +void actionStoreRegister(SWFAppContext* app_context, u8 reg_i) { - convertFloat(app_context); - ActionVar a; - popVar(app_context, &a); + // Peek the top of stack (don't pop!) + ActionVar value; + peekVar(app_context, &value); - convertFloat(app_context); - ActionVar b; - popVar(app_context, &b); + ActionVar* reg = &scope_registers[scope_top_obj][reg_i]; - if (a.type == ACTION_STACK_VALUE_F64) + if (IS_OBJ_T(value.type)) { - double a_val = VAL(double, &a.value); - double b_val = b.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &b.value) : VAL(double, &b.value); - - float c = b_val == a_val ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); + OBJ_LOCK_WRITE(value.object, + { + retainObject(value.object); + }); } - else if (b.type == ACTION_STACK_VALUE_F64) + if (IS_OBJ_T(reg->type)) { - double a_val = a.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &a.value) : VAL(double, &a.value); - double b_val = VAL(double, &b.value); - - float c = b_val == a_val ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); + OBJ_LOCK_WRITE(reg->object, + { + releaseObject(app_context, reg->object); + }); } - else - { - float c = VAL(float, &b.value) == VAL(float, &a.value) ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); - } + // Store value in register + *reg = value; + + releaseObjectVar(app_context, &value); } -void actionLess(SWFAppContext* app_context) +//~ void actionInitArray(SWFAppContext* app_context) +//~ { + //~ // 1. Pop array element count + //~ convertFloat(app_context); + //~ ActionVar count_var; + //~ popVar(app_context, &count_var); + //~ u32 num_elements = (u32) VAL(float, &count_var.value); + + //~ // 2. Allocate array + //~ ASArray* arr = allocArray(app_context, num_elements); + //~ if (!arr) { + //~ // Handle allocation failure - push empty array or null + //~ PUSH_F32((float){0.0f}); + //~ return; + //~ } + //~ arr->length = num_elements; + + //~ // 3. Pop elements and populate array + //~ // Per SWF spec: elements were pushed in reverse order (rightmost first, leftmost last) + //~ // Stack has: [..., elem_N, elem_N-1, ..., elem_1] with elem_1 on top + //~ // We pop and store sequentially: pop elem_1 -> arr[0], pop elem_2 -> arr[1], etc. + //~ for (u32 i = 0; i < num_elements; i++) { + //~ ActionVar elem; + //~ popVar(app_context, &elem); + //~ arr->elements[i] = elem; + + //~ // If element is array, increment refcount + //~ if (elem.type == ACTION_STACK_VALUE_ARRAY) { + //~ retainArray((ASArray*) elem.value); + //~ } + //~ // Could also handle ACTION_STACK_VALUE_OBJECT here if needed + //~ } + + //~ // 4. Push array reference to stack + //~ PUSH(ACTION_STACK_VALUE_ARRAY, (u64) arr); +//~ } + +void actionSetMember(SWFAppContext* app_context) { - ActionVar a; - convertFloat(app_context); - popVar(app_context, &a); + // Stack layout (from top to bottom): + // 1. value (the value to assign) + // 2. property_name (the name of the property) + // 3. object (the object to set the property on) - ActionVar b; - convertFloat(app_context); - popVar(app_context, &b); + // Fetch the value to assign + ActionVar value_var; + popVar(app_context, &value_var); - if (a.type == ACTION_STACK_VALUE_F64) - { - double a_val = VAL(double, &a.value); - double b_val = b.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &b.value) : VAL(double, &b.value); - - float c = b_val < a_val ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); - } + // Pop the property name + ActionVar prop_name_var; + popVar(app_context, &prop_name_var); + + ASObject* prop_obj; - else if (b.type == ACTION_STACK_VALUE_F64) + if (IS_OBJ(prop_name_var)) { - double a_val = a.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &a.value) : VAL(double, &a.value); - double b_val = VAL(double, &b.value); + prop_obj = prop_name_var.object; + + toPrimitive(app_context, prop_obj); + + releaseObjectVar(app_context, &prop_name_var); - float c = b_val < a_val ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); + popVar(app_context, &prop_name_var); } - else + if (UNLIKELY(prop_name_var.type != ACTION_STACK_VALUE_STRING)) { - float c = VAL(float, &b.value) < VAL(float, &a.value) ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); + if (IS_OBJ_T(prop_name_var.type)) + { + EXC("Bad property key"); + } + + if (IS_NUM_T(prop_name_var.type)) + { + convertNumericToInteger(app_context, &prop_name_var); + } } -} - -void actionAnd(SWFAppContext* app_context) -{ - ActionVar a; - convertFloat(app_context); - popVar(app_context, &a); - ActionVar b; - convertFloat(app_context); - popVar(app_context, &b); + // Fetch the object + ActionVar obj_var; + popVar(app_context, &obj_var); - if (a.type == ACTION_STACK_VALUE_F64) + // Check if the object is actually an object type + if (IS_OBJ(obj_var)) { - double a_val = VAL(double, &a.value); - double b_val = b.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &b.value) : VAL(double, &b.value); + ASObject* obj = (ASObject*) obj_var.value; - float c = b_val != 0.0 && a_val != 0.0 ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); - } - - else if (b.type == ACTION_STACK_VALUE_F64) - { - double a_val = a.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &a.value) : VAL(double, &a.value); - double b_val = VAL(double, &b.value); + ASObject* constructor = getConstructor(app_context, obj); - float c = b_val != 0.0 && a_val != 0.0 ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); + switch (Function_get_func_name_string_id(app_context, constructor)) + { + case STR_ID_ARRAY: + { + if (IS_STR_T(prop_name_var.type) && prop_name_var.string_id == STR_ID_LENGTH) + { + if (UNLIKELY(!IS_NUM_T(value_var.type))) + { + UNIMPLEMENTED("handling setting Array length to non-numeric"); + } + + convertNumericToInteger(app_context, &value_var); + Array_setLength(app_context, obj, value_var.s32); + + break; + } + + s32 i = prop_name_var.s32; + Array_setElement(app_context, obj, i, &value_var); + + break; + } + + case STR_ID_MOVIECLIP: + { + if (MovieClip_setMember(app_context, obj, prop_name_var.string_id, &value_var)) + { + break; + } + + // fallthrough + } + + default: + { + if (IS_NUM_T(prop_name_var.type)) + { + toString(app_context, &prop_name_var); + releaseObjectVar(app_context, &prop_name_var); + popVar(app_context, &prop_name_var); + } + + // Set the property on the object + setProperty(app_context, obj, prop_name_var.string_id, prop_name_var.str, prop_name_var.str_size, &value_var); + + break; + } + } } - else - { - float c = VAL(float, &b.value) != 0.0f && VAL(float, &a.value) != 0.0f ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); - } + releaseObjectVar(app_context, &obj_var); + releaseObjectVar(app_context, &value_var); + releaseObjectVar(app_context, &prop_name_var); + + // If it's not an object type, we silently ignore the operation + // (Flash behavior for setting properties on non-objects) } -void actionOr(SWFAppContext* app_context) +void actionInitObject(SWFAppContext* app_context) { - ActionVar a; - convertFloat(app_context); - popVar(app_context, &a); - - ActionVar b; + // Step 1: Pop property count from stack convertFloat(app_context); - popVar(app_context, &b); - - if (a.type == ACTION_STACK_VALUE_F64) + ActionVar count_var; + popVar(app_context, &count_var); + u32 num_props = (u32) VAL(float, &count_var.value); + + // Step 2: Allocate object + ASObject* obj = allocObject(app_context); + + // Step 3: Pop property name/value pairs from stack + // Properties are in reverse order: rightmost property is on top of stack + // Stack order is: [..., value1, name1, ..., valueN, nameN, count] + // So after popping count, top of stack is nameN + for (u32 i = 0; i < num_props; i++) { - double a_val = VAL(double, &a.value); - double b_val = b.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &b.value) : VAL(double, &b.value); + // Pop property name first (it's on top) + ActionVar name_var; + convertString(app_context); + popVar(app_context, &name_var); - float c = b_val != 0.0 || a_val != 0.0 ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); - } - - else if (b.type == ACTION_STACK_VALUE_F64) - { - double a_val = a.type == ACTION_STACK_VALUE_F32 ? (double) VAL(float, &a.value) : VAL(double, &a.value); - double b_val = VAL(double, &b.value); + // Pop property value (it's below the name) + ActionVar value; + popVar(app_context, &value); + u32 string_id = 0; + const char* name = NULL; + u32 name_length = 0; - float c = b_val != 0.0 || a_val != 0.0 ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F64, VAL(u64, &c)); + // Handle string name + string_id = name_var.string_id; + name = name_var.owns_memory ? + name_var.str : + (const char*) name_var.value; + name_length = name_var.str_size; + + // Store property using the object API + // This handles refcount management if value is an object + setProperty(app_context, obj, string_id, name, name_length, &value); } - else - { - float c = VAL(float, &b.value) != 0.0f || VAL(float, &a.value) != 0.0f ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &c)); - } + // Step 4: Push object reference to stack + // The object has refcount = 1 from allocation + PUSH(ACTION_STACK_VALUE_OBJECT, (u64) obj); } -void actionNot(SWFAppContext* app_context) +void actionDelete(SWFAppContext* app_context) { - ActionVar v; - convertFloat(app_context); - popVar(app_context, &v); - - float result = v.value == 0.0f ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F32, VAL(u64, &result)); + //~ // Stack layout (from top to bottom): + //~ // 1. property_name (string) - name of property to delete + //~ // 2. object_name (string) - name of variable containing the object + + //~ // Pop property name + //~ ActionVar prop_name_var; + //~ popVar(app_context, &prop_name_var); + + //~ const char* prop_name = NULL; + //~ u32 prop_name_len = 0; + + //~ if (prop_name_var.type == ACTION_STACK_VALUE_STRING) + //~ { + //~ prop_name = prop_name_var.owns_memory ? + //~ prop_name_var.str : + //~ (const char*) prop_name_var.value; + //~ prop_name_len = prop_name_var.str_size; + //~ } + //~ else + //~ { + //~ // Property name must be a string + //~ // Return true (AS2 spec: returns true for invalid operations) + //~ float result = 1.0f; + //~ PUSH_F32(result); + //~ return; + //~ } + + //~ // Pop object name (variable name) + //~ ActionVar obj_name_var; + //~ popVar(app_context, &obj_name_var); + + //~ const char* obj_name = NULL; + //~ u32 obj_name_len = 0; + + //~ if (obj_name_var.type == ACTION_STACK_VALUE_STRING) + //~ { + //~ obj_name = obj_name_var.owns_memory ? + //~ obj_name_var.str : + //~ (const char*) obj_name_var.value; + //~ obj_name_len = obj_name_var.str_size; + //~ } + //~ else + //~ { + //~ // Object name must be a string + //~ // Return true (AS2 spec: returns true for invalid operations) + //~ float result = 1.0f; + //~ PUSH_F32(result); + //~ return; + //~ } + + //~ // Look up the variable to get the object + //~ ActionVar* obj_var = getVariable(app_context, (char*)obj_name, obj_name_len); + + //~ // If variable doesn't exist, return true (AS2 spec) + //~ if (obj_var == NULL) + //~ { + //~ float result = 1.0f; + //~ PUSH_F32(result); + //~ return; + //~ } + + //~ // If variable is not an object, return true (AS2 spec) + //~ if (obj_var->type != ACTION_STACK_VALUE_OBJECT) + //~ { + //~ float result = 1.0f; + //~ PUSH_F32(result); + //~ return; + //~ } + + //~ // Get the object + //~ ASObject* obj = (ASObject*) obj_var->value; + + //~ // If object is NULL, return true + //~ if (obj == NULL) + //~ { + //~ float result = 1.0f; + //~ PUSH_F32(result); + //~ return; + //~ } + + //~ // Delete the property + //~ bool success = deleteProperty(app_context, obj, prop_name, prop_name_len); + + //~ // Push result (1.0 for success, 0.0 for failure) + //~ float result = success ? 1.0f : 0.0f; + //~ PUSH_F32(result); } -int evaluateCondition(SWFAppContext* app_context) +void actionGetMember(SWFAppContext* app_context) { - ActionVar v; - convertFloat(app_context); - popVar(app_context, &v); + ActionVar prop_name_var; + popVar(app_context, &prop_name_var); - return v.value != 0.0f; -} - -int strcmp_list_a_list_b(u64 a_value, u64 b_value) -{ - char** a_list = (char**) a_value; - char** b_list = (char**) b_value; + ASObject* prop_obj; - u64 num_a_strings = (u64) a_list[0]; - u64 num_b_strings = (u64) b_list[0]; + if (IS_OBJ(prop_name_var)) + { + prop_obj = prop_name_var.object; + + toPrimitive(app_context, prop_obj); + + releaseObjectVar(app_context, &prop_name_var); + + popVar(app_context, &prop_name_var); + } - u64 a_str_i = 0; - u64 b_str_i = 0; + if (UNLIKELY(prop_name_var.type != ACTION_STACK_VALUE_STRING)) + { + if (IS_OBJ_T(prop_name_var.type)) + { + EXC("Bad property key"); + } + + if (IS_NUM_T(prop_name_var.type)) + { + convertNumericToInteger(app_context, &prop_name_var); + } + } - u64 a_i = 0; - u64 b_i = 0; + // 2. Pop object (second on stack) + ActionVar obj_var; + popVar(app_context, &obj_var); - u64 min_count = (num_a_strings < num_b_strings) ? num_a_strings : num_b_strings; + ASObject* obj = (ASObject*) obj_var.value; - while (1) + bool special_object = false; + + // 3. Handle different object types + switch (obj_var.type) { - char c_a = a_list[a_str_i + 1][a_i]; - char c_b = b_list[b_str_i + 1][b_i]; - - if (c_a == 0) + case ACTION_STACK_VALUE_OBJECT: { - if (a_str_i + 1 != min_count) + ASObject* constructor = getConstructor(app_context, obj); + + switch (Function_get_func_name_string_id(app_context, constructor)) { - a_str_i += 1; - a_i = 0; - continue; + case STR_ID_ARRAY: + { + if (IS_STR_T(prop_name_var.type) && prop_name_var.string_id == STR_ID_LENGTH) + { + PUSH_INT(AR_EXTDATA_OF(obj, length)); + special_object = true; + + break; + } + + s32 i = prop_name_var.s32; + PUSH_VAR(Array_getElement(app_context, obj, i)); + + special_object = true; + + break; + } + + case STR_ID_MOVIECLIP: + { + ActionVar v; + special_object = MovieClip_getMember(app_context, obj, prop_name_var.string_id, &v); + + if (special_object) + { + PUSH_VAR(&v); + } + + break; + } + + case STR_ID_BITMAP_DATA: + { + ActionVar v; + special_object = BitmapData_getMember(app_context, obj, prop_name_var.string_id, &v); + + if (special_object) + { + PUSH_VAR(&v); + } + + break; + } + } + + if (special_object) + { + break; + } + + if (IS_NUM_T(prop_name_var.type)) + { + toString(app_context, &prop_name_var); + releaseObjectVar(app_context, &prop_name_var); + popVar(app_context, &prop_name_var); + } + + ActionVar prop_v; + + // Look up property + getPropertyVarWithPrototype(app_context, obj, prop_name_var.string_id, prop_name_var.str, prop_name_var.str_size, &prop_v); + + if (prop_v.type != ACTION_STACK_VALUE_UNDEFINED) + { + // Property found - push its value + PUSH_VAR(&prop_v); } else { - return c_a - c_b; + // Property not found - push undefined + PUSH_UNDEFINED(); } + + break; } - if (c_b == 0) + case ACTION_STACK_VALUE_STRING: { - if (b_str_i + 1 != min_count) + // Handle string properties + if (prop_name_var.string_id == STR_ID_LENGTH) { - b_str_i += 1; - b_i = 0; - continue; + f64 str_size_f64 = (f64) obj_var.str_size; + PUSH_F64(str_size_f64); } else { - return c_a - c_b; + EXC_ARG("Tried to get property %s of String type\n", prop_name_var.str); } + + break; } - if (c_a != c_b) + default: { - return c_a - c_b; + // Other primitive types (number, undefined, etc.) - push undefined + PUSH_UNDEFINED(); + + break; } - - a_i += 1; - b_i += 1; } - EXC("um how lol\n"); - return 0; + releaseObjectVar(app_context, &obj_var); + releaseObjectVar(app_context, &prop_name_var); } -int strcmp_list_a_not_b(u64 a_value, u64 b_value) +void callFunction(SWFAppContext* app_context, ASObject* this, ActionVar* func_v, u32 num_args) { - char** a_list = (char**) a_value; - char* b_str = (char*) b_value; - - u64 num_a_strings = (u64) a_list[0]; - - u64 a_str_i = 0; - - u64 a_i = 0; - u64 b_i = 0; + ASObject* func_obj = func_v->object; - while (1) + switch (Function_get_func_type(app_context, func_obj)) { - char c_a = a_list[a_str_i + 1][a_i]; - char c_b = b_str[b_i]; + case FUNC_TYPE_1: + { + // can't change scope_top_obj yet, or popVar will break on registers + u32 this_scope = scope_top_obj + 1; + + scope_chain[this_scope] = allocObject(app_context); + retainObject(scope_chain[this_scope]); + + scope_registers[this_scope] = HALLOC(4*sizeof(ActionVar)); + + ActionVar* regs = scope_registers[this_scope]; + + for (u8 i = 0; i < 4; ++i) + { + regs[i].type = ACTION_STACK_VALUE_UNDEFINED; + } + + if (this != NULL) + { + ActionVar this_v; + this_v.type = ACTION_STACK_VALUE_OBJECT; + this_v.object = this; + + setProperty(app_context, scope_chain[this_scope], STR_ID_THIS, NULL, 0, &this_v); + } + + u32* args = Function_get_args(app_context, func_obj); + + // Pop arguments from stack (in reverse order) + if (num_args > 0) + { + for (u32 i = 0; i < num_args; ++i) + { + ActionVar v; + popVar(app_context, &v); + setProperty(app_context, scope_chain[this_scope], args[i], NULL, 0, &v); + releaseObjectVar(app_context, &v); + } + } + + scope_top_obj = this_scope; + + Function_get_func(app_context, func_obj)(app_context); + + copyReg(app_context); + + for (u8 i = 0; i < 4; ++i) + { + if (IS_OBJ_T(regs[i].type)) + { + OBJ_LOCK_WRITE(regs[i].object, + { + releaseObject(app_context, regs[i].object); + }); + } + } + + FREE(regs); + + OBJ_LOCK_WRITE(scope_chain[scope_top_obj], + { + releaseObject(app_context, scope_chain[scope_top_obj]); + }); + + scope_top_obj -= 1; + break; + } - if (c_a == 0) + case FUNC_TYPE_2: { - if (a_str_i + 1 != num_a_strings) + // can't change scope_top_obj yet, or popVar will break on registers + u32 this_scope = scope_top_obj + 1; + + scope_chain[this_scope] = allocObject(app_context); + retainObject(scope_chain[this_scope]); + + u8 reg_count = Function_get_reg_count(app_context, func_obj); + u16 flags = Function_get_flags(app_context, func_obj); + + scope_registers[this_scope] = HALLOC((reg_count + 1)*sizeof(ActionVar)); + + ActionVar* regs = scope_registers[this_scope]; + + for (u8 i = 0; i < reg_count + 1; ++i) + { + regs[i].type = ACTION_STACK_VALUE_UNDEFINED; + } + + Function2Param* args2 = Function_get_args(app_context, func_obj); + + // Pop arguments from stack (in reverse order) + if (args2 != NULL && num_args > 0) + { + for (u32 i = 0; i < num_args; ++i) + { + Function2Param* arg = &args2[i]; + + if (arg->reg == 0) + { + ActionVar v; + popVar(app_context, &v); + setProperty(app_context, scope_chain[this_scope], arg->string_id, NULL, 0, &v); + releaseObjectVar(app_context, &v); + } + + else + { + popVar(app_context, ®s[arg->reg]); + } + } + } + + u8 next_preload = 1; + + if ((flags & FUNC_FLAG_SUPPRESS_THIS) == 0) + { + assert(this != NULL); + + ActionVar this_v; + this_v.type = ACTION_STACK_VALUE_OBJECT; + this_v.object = this; + + setProperty(app_context, scope_chain[this_scope], STR_ID_THIS, NULL, 0, &this_v); + + if (flags & FUNC_FLAG_PRELOAD_THIS) + { + regs[next_preload].type = ACTION_STACK_VALUE_OBJECT; + regs[next_preload].object = this; + next_preload += 1; + + OBJ_LOCK_WRITE(this, + { + retainObject(this); + }); + } + } + + if ((flags & FUNC_FLAG_SUPPRESS_ARGUMENTS) == 0) + { + EXC("arguments not implemented\n"); + + // REMEMBER TO RETAIN + } + + if ((flags & FUNC_FLAG_SUPPRESS_SUPER) == 0) + { + ActionVar super_v; + getPropertyVar(app_context, this, STR_ID_PROTO, NULL, 0, &super_v); + + ASObject* super = super_v.object; + + setProperty(app_context, scope_chain[this_scope], STR_ID_SUPER, NULL, 0, &super_v); + + if (flags & FUNC_FLAG_PRELOAD_SUPER) + { + regs[next_preload].type = ACTION_STACK_VALUE_OBJECT; + regs[next_preload].object = super; + next_preload += 1; + + OBJ_LOCK_WRITE(super, + { + retainObject(super); + }); + } + } + + if (flags & FUNC_FLAG_PRELOAD_ROOT) + { + regs[next_preload].type = ACTION_STACK_VALUE_OBJECT; + regs[next_preload].object = app_context->_root; + next_preload += 1; + + OBJ_LOCK_WRITE(app_context->_root, + { + retainObject(app_context->_root); + }); + } + + if (flags & FUNC_FLAG_PRELOAD_PARENT) + { + EXC("_parent not implemented\n"); + + // REMEMBER TO RETAIN + } + + if (flags & FUNC_FLAG_PRELOAD_GLOBAL) { - a_str_i += 1; - a_i = 0; - continue; + regs[next_preload].type = ACTION_STACK_VALUE_OBJECT; + regs[next_preload].object = _global; + next_preload += 1; + + OBJ_LOCK_WRITE(_global, + { + retainObject(_global); + }); } - else + scope_top_obj = this_scope; + + Function_get_func(app_context, func_obj)(app_context); + + copyReg(app_context); + + for (u8 i = 0; i < reg_count + 1; ++i) { - return c_a - c_b; + if (IS_OBJ_T(regs[i].type)) + { + OBJ_LOCK_WRITE(regs[i].object, + { + releaseObject(app_context, regs[i].object); + }); + } } + + FREE(regs); + + OBJ_LOCK_WRITE(scope_chain[scope_top_obj], + { + releaseObject(app_context, scope_chain[scope_top_obj]); + }); + + scope_top_obj -= 1; + break; } - if (c_a != c_b) + case FUNC_TYPE_3: { - return c_a - c_b; + action_runtime_func f = (action_runtime_func) Function_get_func(app_context, func_obj); + f(app_context, this, num_args); + + break; } - - a_i += 1; - b_i += 1; + } +} + +void getAndCallMethod(SWFAppContext* app_context, ASObject* this, u32 method_name, u32 num_args) +{ + ActionVar meth_v; + getPropertyVarWithPrototype(app_context, this, method_name, NULL, 0, &meth_v); + callFunction(app_context, this, &meth_v, num_args); +} + +bool getAndCallMethodIfExists(SWFAppContext* app_context, ASObject* this, u32 method_name, u32 num_args) +{ + ActionVar meth_v; + getPropertyVarWithPrototype(app_context, this, method_name, NULL, 0, &meth_v); + + if (meth_v.type != ACTION_STACK_VALUE_UNDEFINED) + { + callFunction(app_context, this, &meth_v, num_args); + return true; } - EXC("um how lol\n"); - return 0; + return false; } -int strcmp_not_a_list_b(u64 a_value, u64 b_value) +void actionNewObject(SWFAppContext* app_context) { - char* a_str = (char*) a_value; - char** b_list = (char**) b_value; + // 1. Pop constructor name (string) + ActionVar ctor_name_var; + popVar(app_context, &ctor_name_var); - u64 num_b_strings = (u64) b_list[0]; + ASObject* ctor_name_obj = ctor_name_var.object; - u64 b_str_i = 0; + if (IS_OBJ_T(ctor_name_var.type)) + { + UNIMPLEMENTED("NewObject constructor name object\n"); + } - u64 a_i = 0; - u64 b_i = 0; + // 2. Pop number of arguments + ActionVar num_args_var; + popVar(app_context, &num_args_var); + u32 num_args = (u32) num_args_var.value; - while (1) + if (IS_OBJ_T(num_args_var.type)) { - char c_a = a_str[a_i]; - char c_b = b_list[b_str_i + 1][b_i]; + UNIMPLEMENTED("NewObject num args object\n"); + } + + // Try to find existing constructor function + ActionVar func_v; + searchScopesForPropertyVar(app_context, ctor_name_var.string_id, ctor_name_var.str, ctor_name_var.str_size, &func_v); + + if (func_v.type != ACTION_STACK_VALUE_UNDEFINED) + { + // Create new object to serve as 'this' + ASObject* this = allocObject(app_context); - if (c_b == 0) + OBJ_LOCK_WRITE(this, { - if (b_str_i + 1 != num_b_strings) - { - b_str_i += 1; - b_i = 0; - continue; - } - - else - { - return c_a - c_b; - } - } + retainObject(this); + }); - if (c_a != c_b) - { - return c_a - c_b; - } + ASProperty* prototype = getProperty(app_context, func_v.object, STR_ID_PROTOTYPE, NULL, 0); - a_i += 1; - b_i += 1; + ActionVar proto_ref_var; + proto_ref_var.type = ACTION_STACK_VALUE_OBJECT; + proto_ref_var.object = prototype->value.object; + + setProperty(app_context, this, STR_ID_PROTO, NULL, 0, &proto_ref_var); + + setProperty(app_context, this, STR_ID_CONSTRUCTOR, NULL, 0, &func_v); + + callFunction(app_context, this, &func_v, num_args); + POP(); + + PUSH_OBJ(this); + + OBJ_LOCK_WRITE(this, + { + releaseObject(app_context, this); + }); } - EXC("um how lol\n"); - return 0; + else + { + EXC_ARG("Constructor function %s not found.\n", (char*) ctor_name_var.value); + } + + releaseObjectVar(app_context, &num_args_var); + releaseObjectVar(app_context, &ctor_name_var); } -void actionStringEquals(SWFAppContext* app_context, char* a_str, char* b_str) +/** + * ActionNewMethod (0x53) - Create new object by calling method on object as constructor + * + * Stack layout: [method_name] [object] [num_args] [arg1] [arg2] ... <- sp + * + * SWF Specification behavior: + * 1. Pops the name of the method from the stack + * 2. Pops the ScriptObject from the stack + * - If method name is blank: object is treated as function object (constructor) + * - If method name not blank: named method of object is invoked as constructor + * 3. Pops the number of arguments from the stack + * 4. Executes the method call as constructor + * 5. Pushes the newly constructed object to the stack + */ +void actionNewMethod(SWFAppContext* app_context) { - ActionVar a; - convertString(app_context, a_str); - popVar(app_context, &a); - - ActionVar b; - convertString(app_context, b_str); - popVar(app_context, &b); - - int cmp_result; + // Pop constructor method name (string) + ActionVar ctor_name_var; + popVar(app_context, &ctor_name_var); - int a_is_list = a.type == ACTION_STACK_VALUE_STR_LIST; - int b_is_list = b.type == ACTION_STACK_VALUE_STR_LIST; + ASObject* ctor_name_obj = ctor_name_var.object; - if (a_is_list && b_is_list) + if (IS_OBJ_T(ctor_name_var.type)) { - cmp_result = strcmp_list_a_list_b(a.value, b.value); + UNIMPLEMENTED("NewMethod constructor name object"); } - else if (a_is_list && !b_is_list) + // Pop object which holds the method + ActionVar object_var; + popVar(app_context, &object_var); + + ASObject* obj = object_var.object; + + // Pop number of arguments + ActionVar num_args_var; + popVar(app_context, &num_args_var); + u32 num_args = (u32) num_args_var.value; + + if (IS_OBJ_T(ctor_name_var.type)) { - cmp_result = strcmp_list_a_not_b(a.value, b.value); + UNIMPLEMENTED("NewMethod num args object"); } - else if (!a_is_list && b_is_list) + // Try to find constructor method + ActionVar func_v; + getPropertyVar(app_context, obj, ctor_name_var.string_id, ctor_name_var.str, ctor_name_var.str_size, &func_v); + + if (func_v.type != ACTION_STACK_VALUE_UNDEFINED) { - cmp_result = strcmp_not_a_list_b(a.value, b.value); + // Constructor found + // Create new object to serve as 'this' + ASObject* this = allocObject(app_context); + + OBJ_LOCK_WRITE(this, + { + retainObject(this); + }); + + ASProperty* prototype = getProperty(app_context, func_v.object, STR_ID_PROTOTYPE, NULL, 0); + + ActionVar proto_ref_var; + proto_ref_var.type = ACTION_STACK_VALUE_OBJECT; + proto_ref_var.object = prototype->value.object; + + setProperty(app_context, this, STR_ID_PROTO, NULL, 0, &proto_ref_var); + + setProperty(app_context, this, STR_ID_CONSTRUCTOR, NULL, 0, &func_v); + + callFunction(app_context, this, &func_v, num_args); + POP(); + + PUSH_OBJ(this); + + OBJ_LOCK_WRITE(this, + { + releaseObject(app_context, this); + }); } else { - cmp_result = strcmp((char*) a.value, (char*) b.value); + EXC_ARG("Constructor method %s not found.\n", (char*) ctor_name_var.value); } - float result = cmp_result == 0 ? 1.0f : 0.0f; - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &result)); + releaseObjectVar(app_context, &num_args_var); + releaseObjectVar(app_context, &object_var); + releaseObjectVar(app_context, &ctor_name_var); } -void actionStringLength(SWFAppContext* app_context, char* v_str) +void actionExtends(SWFAppContext* app_context) { - ActionVar v; - convertString(app_context, v_str); - popVar(app_context, &v); + ActionVar super_v; + popVar(app_context, &super_v); + ActionVar sub_v; + popVar(app_context, &sub_v); + + ASObject* super = super_v.object; + ASObject* sub = sub_v.object; + + ASObject* prototype = allocObject(app_context); + + ActionVar prototype_v; + prototype_v.type = ACTION_STACK_VALUE_OBJECT; + prototype_v.object = prototype; + + ActionVar super_prototype_v; + getPropertyVar(app_context, super, STR_ID_PROTOTYPE, NULL, 0, &super_prototype_v); + + setProperty(app_context, prototype, STR_ID_PROTO, NULL, 0, &super_prototype_v); + setProperty(app_context, prototype, STR_ID_CONSTRUCTOR, NULL, 0, &super_v); + + setProperty(app_context, sub, STR_ID_PROTOTYPE, NULL, 0, &prototype_v); - float str_size = (float) v.str_size; - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &str_size)); + releaseObjectVar(app_context, &sub_v); + releaseObjectVar(app_context, &super_v); } -void actionStringAdd(SWFAppContext* app_context, char* a_str, char* b_str) +void actionDefineFunction(SWFAppContext* app_context, u32 string_id, action_func func, u32* args, bool anonymous) { - ActionVar a; - convertString(app_context, a_str); - peekVar(app_context, &a); + assert(string_id != 0); - ActionVar b; - convertString(app_context, b_str); - peekSecondVar(app_context, &b); + // Create function object + ASObject* func_obj = allocObject(app_context); - u64 num_a_strings; - u64 num_b_strings; - u64 num_strings = 0; + Function_init_object(app_context, func_obj); - if (b.type == ACTION_STACK_VALUE_STR_LIST) + Function_set_members(app_context, func_obj, + FUNC_TYPE_1, + func, + args, + 0, + 0, + string_id); + + ActionVar proto_var; + proto_var.type = ACTION_STACK_VALUE_OBJECT; + proto_var.object = allocObject(app_context); + setProperty(app_context, func_obj, STR_ID_PROTOTYPE, NULL, 0, &proto_var); + + if (!anonymous) { - num_b_strings = *((u64*) b.value); + // If named, store in variable + ActionVar func_var; + func_var.type = ACTION_STACK_VALUE_OBJECT; + func_var.object = func_obj; + + setPropertyInThisScope(app_context, string_id, NULL, 0, &func_var); } else { - num_b_strings = 1; + // Anonymous function: push to stack + PUSH_OBJ(func_obj); } +} + +void actionDefineFunction2(SWFAppContext* app_context, u32 string_id, action_func func, Function2Param* args, u8 reg_count, u16 flags, bool anonymous) +{ + assert(string_id != 0); - num_strings += num_b_strings; + // Create function object + ASObject* func_obj = allocObject(app_context); - if (a.type == ACTION_STACK_VALUE_STR_LIST) + Function_init_object(app_context, func_obj); + + Function_set_members(app_context, func_obj, + FUNC_TYPE_2, + func, + args, + reg_count, + flags, + string_id); + + ActionVar proto_var; + proto_var.type = ACTION_STACK_VALUE_OBJECT; + proto_var.object = allocObject(app_context); + setProperty(app_context, func_obj, STR_ID_PROTOTYPE, NULL, 0, &proto_var); + + if (!anonymous) { - num_a_strings = *((u64*) a.value); + // If named, store in variable + ActionVar func_var; + func_var.type = ACTION_STACK_VALUE_OBJECT; + func_var.object = func_obj; + + setPropertyInThisScope(app_context, string_id, NULL, 0, &func_var); } else { - num_a_strings = 1; + // Anonymous function: push to stack + PUSH_OBJ(func_obj); } +} + +void actionCallFunction(SWFAppContext* app_context) +{ + copyReg(app_context); - num_strings += num_a_strings; + // 1. Pop function name (string) from stack + char* func_name = (char*) STACK_TOP_VALUE; + u32 func_n = STACK_TOP_N; + u32 string_id = STACK_TOP_ID; + POP(); - PUSH_STR_LIST(b.str_size + a.str_size, (u32) sizeof(u64)*(2*num_strings + 1)); + // 2. Pop number of arguments + ActionVar num_args_var; + popVar(app_context, &num_args_var); + u32 num_args = (u32) num_args_var.value; - u64* str_list = (u64*) &STACK_TOP_VALUE; - str_list[0] = num_strings; + ActionVar func_v; + searchScopesForPropertyVar(app_context, string_id, func_name, func_n, &func_v); - if (b.type == ACTION_STACK_VALUE_STR_LIST) + if (func_v.type == ACTION_STACK_VALUE_UNDEFINED) { - u64* b_list = (u64*) b.value; - - for (u64 i = 0; i < num_b_strings; ++i) - { - u64 str_i = 2*i; - str_list[str_i + 1] = b_list[str_i + 1]; - str_list[str_i + 2] = b_list[str_i + 2]; - } + // Function not found - throw + EXC_ARG("Function not found: %s\n", func_name); + return; } - else + callFunction(app_context, NULL, &func_v, num_args); + + releaseObjectVar(app_context, &num_args_var); +} + +void actionCallMethod(SWFAppContext* app_context) +{ + copyReg(app_context); + + if (IS_OBJ_T(STACK_TOP_TYPE)) { - str_list[1] = b.value; - str_list[2] = b.str_size; + UNIMPLEMENTED("CallMethod method name object"); } - if (a.type == ACTION_STACK_VALUE_STR_LIST) + // Pop method name (string) from stack + ActionVar name_v; + popVar(app_context, &name_v); + + char* func_name = "UNDEFINED METHOD NAME"; + u32 func_n = 0; + u32 string_id = STR_ID_EMPTY; + + if (name_v.type != ACTION_STACK_VALUE_UNDEFINED) { - u64* a_list = (u64*) a.value; - - for (u64 i = 0; i < num_a_strings; ++i) - { - u64 str_i = 2*i; - str_list[str_i + 1 + 2*num_b_strings] = a_list[str_i + 1]; - str_list[str_i + 2 + 2*num_b_strings] = a_list[str_i + 2]; - } + func_name = name_v.str; + func_n = name_v.str_size; + string_id = name_v.string_id; } - else + // Pop object from stack + ActionVar this_v; + popVar(app_context, &this_v); + + ASObject* this = this_v.object; + + // Pop number of arguments + ActionVar num_args_var; + popVar(app_context, &num_args_var); + u32 num_args = (u32) num_args_var.value; + + if (IS_OBJ_T(num_args_var.type)) { - str_list[1 + 2*num_b_strings] = a.value; - str_list[1 + 2*num_b_strings + 1] = a.str_size; + UNIMPLEMENTED("CallMethod num args object"); } -} - -void actionTrace(SWFAppContext* app_context) -{ - ActionStackValueType type = STACK_TOP_TYPE; - switch (type) + switch (this_v.type) { - case ACTION_STACK_VALUE_STRING: - { - printf("%s\n", (char*) STACK_TOP_VALUE); - break; - } - - case ACTION_STACK_VALUE_STR_LIST: + case ACTION_STACK_VALUE_F32: + case ACTION_STACK_VALUE_F64: + case ACTION_STACK_VALUE_INT: { - u64* str_list = (u64*) &STACK_TOP_VALUE; - - for (u64 i = 0; i < 2*str_list[0]; i += 2) + switch (string_id) { - printf("%s", (char*) str_list[i + 1]); + case STR_ID_TO_STRING: + { + convertNumericToNumber(app_context, &this_v); + toString(app_context, &this_v); + + break; + } + + case STR_ID_VALUE_OF: + { + PUSH_VAR(&this_v); + + break; + } + + default: + { + // Function not found - throw + EXC_ARG("Function not found: %s\n", func_name); + } } - printf("\n"); - - break; - } - - case ACTION_STACK_VALUE_F32: - { - printf("%.15g\n", VAL(float, &STACK_TOP_VALUE)); - break; + goto release; } - case ACTION_STACK_VALUE_F64: + case ACTION_STACK_VALUE_STRING: { - printf("%.15g\n", VAL(double, &STACK_TOP_VALUE)); - break; + switch (string_id) + { + case STR_ID_TO_STRING: + { + PUSH_VAR(&this_v); + + break; + } + + case STR_ID_VALUE_OF: + { + toNumber(app_context, &this_v); + + break; + } + + default: + { + // Function not found - throw + EXC_ARG("Function not found: %s\n", func_name); + } + } + + goto release; } } - fflush(stdout); - - POP(); -} - -void actionGetVariable(SWFAppContext* app_context) -{ - // Read variable name info from stack - u32 string_id = STACK_TOP_ID; - char* var_name = (char*) STACK_TOP_VALUE; - u32 var_name_len = STACK_TOP_N; - - // Pop variable name - POP(); - - // Get variable (fast path for constant strings) - ActionVar* var = NULL; + ActionVar meth_v; - if (string_id != 0) + if (string_id != STR_ID_EMPTY) { - // Constant string - use array (O(1)) - var = getVariableById(app_context, string_id); + getPropertyVarWithPrototype(app_context, this, string_id, func_name, func_n, &meth_v); } else { - // Dynamic string - use hashmap (O(n)) - var = getVariable(app_context, var_name, var_name_len); + meth_v.type = ACTION_STACK_VALUE_OBJECT; + meth_v.object = this; } - assert(var != NULL); - - // Push variable value to stack - PUSH_VAR(var); -} - -void actionSetVariable(SWFAppContext* app_context) -{ - // Stack layout: [value] [name] <- sp - // We need value at top, name at second - - // Read variable name info - u32 string_id = STACK_SECOND_TOP_ID; - char* var_name = (char*) STACK_SECOND_TOP_VALUE; - u32 var_name_len = STACK_SECOND_TOP_N; - - // Get variable (fast path for constant strings) - ActionVar* var = NULL; - - if (string_id != 0) + if (meth_v.type != ACTION_STACK_VALUE_UNDEFINED) { - // Constant string - use array (O(1)) - var = getVariableById(app_context, string_id); + callFunction(app_context, this, &meth_v, num_args); } else { - // Dynamic string - use hashmap (O(n)) - var = getVariable(app_context, var_name, var_name_len); + ActionVar ctor_name_var; + getPropertyVar(app_context, this, STR_ID_CONSTRUCTOR, NULL, 0, &ctor_name_var); + + u32 ctor_name_id = Function_get_func_name_string_id(app_context, ctor_name_var.object); + + // Function not found - throw + fprintf(stderr, "Method of %s not found: %s\n", app_context->str_table[ctor_name_id], func_name); + THROW; } - assert(var != NULL); - - // Set variable value (uses existing string materialization!) - setVariableWithValue(app_context, var); - - // Pop both value and name - POP_2(); -} - -void actionGetTime(SWFAppContext* app_context) -{ - u32 delta_ms = get_elapsed_ms() - start_time; - float delta_ms_f32 = (float) delta_ms; + release: - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &delta_ms_f32)); + releaseObjectVar(app_context, &num_args_var); + releaseObjectVar(app_context, &this_v); + releaseObjectVar(app_context, &name_v); } \ No newline at end of file diff --git a/src/actionmodern/free_thread.c b/src/actionmodern/free_thread.c new file mode 100644 index 0000000..0cd6403 --- /dev/null +++ b/src/actionmodern/free_thread.c @@ -0,0 +1,467 @@ +#include + +#include +#include + +#include + +extern ASObject* _global; + +void block(SWFAppContext* app_context, ASObject* o) +{ + SVEC_CLEAR(&o->blocked_list); + + for (size_t i = 0; i < o->neighbors.length; ++i) + { + ASObject* neighbor = (ASObject*) o->neighbors.data[i]; + SVEC_PUSH(&o->blocked_list, neighbor); + } +} + +void unblock(ASObject* o) +{ + o->blocked = false; + + for (size_t i = 0; i < o->blocked_list.length; ++i) + { + ASObject* b = (ASObject*) o->blocked_list.data[i]; + + if (b->blocked) + { + unblock(b); + } + } + + SVEC_CLEAR(&o->blocked_list); +} + +bool traverseIteration(SWFAppContext* app_context, ASObject* o, SwapVector* path_stack); + +bool detectCycle(SWFAppContext* app_context, ASObject* o, SwapVector* path_stack) +{ + if (o == (ASObject*) path_stack->data[0]) + { + SwapVector* cycle = HALLOC(sizeof(SwapVector)); + SVEC_INIT(cycle); + + for (size_t i = 0; i < path_stack->length; ++i) + { + SVEC_PUSH(cycle, path_stack->data[i]); + } + + SVEC_PUSH(&app_context->cycles, cycle); + + return true; + } + + if (o->blocked) + { + return false; + } + + return traverseIteration(app_context, o, path_stack); +} + +bool traverseIteration(SWFAppContext* app_context, ASObject* o, SwapVector* path_stack) +{ + SVEC_PUSH(path_stack, o); + + o->blocked = true; + + bool cycle_found = false; + + for (size_t i = 0; i < o->neighbors.length; ++i) + { + ASObject* neighbor = (ASObject*) o->neighbors.data[i]; + + if (neighbor->used) + { + continue; + } + + cycle_found |= detectCycle(app_context, neighbor, path_stack); + } + + SVEC_POP(path_stack); + + if (cycle_found) + { + unblock(o); + return true; + } + + block(app_context, o); + + return false; +} + +void johnson(SWFAppContext* app_context) +{ + SwapVector path_stack; + SVEC_INIT(&path_stack); + + for (size_t i = 0; i < app_context->reachable.length; ++i) + { + for (size_t j = 0; j < app_context->reachable.length; ++j) + { + ASObject* o = (ASObject*) app_context->reachable.data[j]; + + o->blocked = false; + SVEC_CLEAR(&o->blocked_list); + } + + ASObject* o = (ASObject*) app_context->reachable.data[i]; + + (void) traverseIteration(app_context, o, &path_stack); + o->used = true; + } + + for (size_t i = 0; i < app_context->reachable.length; ++i) + { + ASObject* o = (ASObject*) app_context->reachable.data[i]; + o->used = false; + } + + SVEC_RELEASE(&path_stack); +} + +void pushObjReachable(SWFAppContext* app_context, ASObject* this, ASProperty* p) +{ + ASObject* neighbor = (ASObject*) p->value.value; + + if (IS_OBJ(p->value)) + { + bool reached = neighbor->reached; + neighbor->reached = true; + SVEC_PUSH(&this->neighbors, neighbor); + if (!reached) + { + SVEC_PUSH(&app_context->reachable, neighbor); + } + } +} + +void pushObjsReachable(SWFAppContext* app_context, ASObject* this) +{ + rbtree* t = &this->t; + + if (UNLIKELY(t->length == 0 || t->t.root == NULL)) + { + return; + } + + SwapVector node_stack; + SVEC_INIT(&node_stack); + SVEC_PUSH(&node_stack, t->t.root); + + while (node_stack.length > 0) + { + struct rb_node* node = (struct rb_node*) SVEC_TOP(&node_stack); + + SVEC_POP(&node_stack); + + pushObjReachable(app_context, this, (ASProperty*) node); + + if (node->right) + { + SVEC_PUSH(&node_stack, node->right); + } + + if (node->left) + { + SVEC_PUSH(&node_stack, node->left); + } + } + + SVEC_RELEASE(&node_stack); +} + +void getReachable(SWFAppContext* app_context, ASObject* o) +{ + SVEC_PUSH(&app_context->reachable, o); + o->reached = true; + + size_t obj_i = 0; + + while (obj_i < app_context->reachable.length) + { + ASObject* this = (ASObject*) app_context->reachable.data[obj_i]; + + SVEC_CLEAR(&this->neighbors); + + OBJ_LOCK_READ(this, + { + pushObjsReachable(app_context, this); + this->temp_rc = this->refcount; + }); + + obj_i += 1; + } + + for (size_t i = 0; i < app_context->reachable.length; ++i) + { + ASObject* this = (ASObject*) app_context->reachable.data[i]; + this->reached = false; + } +} + +bool containsObj(SwapVector* v, ASObject* o) +{ + for (size_t i = 0; i < v->length; ++i) + { + ASObject* this = (ASObject*) v->data[i]; + + if (o == this) + { + return true; + } + } + + return false; +} + +u32 countObjs(SwapVector* v, ASObject* o) +{ + u32 num_objs = 0; + + for (size_t i = 0; i < v->length; ++i) + { + ASObject* this = (ASObject*) v->data[i]; + + if (o == this) + { + num_objs += 1; + } + } + + return num_objs; +} + +recomp_rwlock_t object_queue_lock; +rbtree object_free_queue; + +bool attemptFree(SWFAppContext* app_context, ASObject* o); + +void freeObject(SWFAppContext* app_context, ASObject* o) +{ + o->freed = true; + + size_t length = o->neighbors.length; + + ASObject** neighbors = HALLOC(length*sizeof(ASObject*)); + memcpy(neighbors, o->neighbors.arena, length*sizeof(ASObject*)); + + for (size_t i = 0; i < length; ++i) + { + ASObject* r = neighbors[i]; + + if (r->freed || attemptFree(app_context, r)) + { + continue; + } + + LOCK_WRITE(object_queue_lock, + { + rbtree_insert_u64(app_context, &object_free_queue, (u64) r); + }); + + OBJ_LOCK_WRITE(r, + { + r->refcount -= 1; + }); + } + + FREE(neighbors); + + destroyObject(app_context, o); + FREE(o); + + LOCK_WRITE(object_queue_lock, + { + rbtree_remove_u64(app_context, &object_free_queue, (u64) o); + }); +} + +ASObject* findBackRef(SwapVector* v, ASObject* ref) +{ + for (size_t i = 0; i < v->length; ++i) + { + ASObject* this = (ASObject*) v->data[i]; + + if (this == ref) + { + if (UNLIKELY(i == 0)) + { + return (ASObject*) v->data[v->length - 1]; + } + + return (ASObject*) v->data[i - 1]; + } + } + + return NULL; +} + +bool subRCTest(SWFAppContext* app_context, ASObject* o) +{ + johnson(app_context); + + SVEC_PUSH(&app_context->objects_to_test, o); + + for (size_t i = 0; i < app_context->cycles.length; ++i) + { + SwapVector* cycle = (SwapVector*) app_context->cycles.data[i]; + + if (!containsObj(cycle, o)) + { + continue; + } + + for (size_t j = 0; j < cycle->length; ++j) + { + ASObject* this = (ASObject*) cycle->data[j]; + + if (!containsObj(&app_context->objects_to_test, this)) + { + SVEC_PUSH(&app_context->objects_to_test, this); + } + } + } + + bool pass_test = true; + + for (size_t i = 0; i < app_context->objects_to_test.length; ++i) + { + ASObject* test = (ASObject*) app_context->objects_to_test.data[i]; + + // TODO: should find backrefs from cycles here (see below TODO) + + SVEC_CLEAR(&app_context->objects_subbed); + + for (size_t j = 0; j < app_context->cycles.length; ++j) + { + SwapVector* cycle = (SwapVector*) app_context->cycles.data[j]; + + // TODO: preprocess to find all backrefs to remove objects_subbed + ASObject* backref = findBackRef(cycle, test); + + if (backref != NULL) + { + u32 num_refs = countObjs(&backref->neighbors, test); + + if (!containsObj(&app_context->objects_subbed, backref) && num_refs > 0) + { + SVEC_PUSH(&app_context->objects_subbed, backref); + test->temp_rc -= num_refs; + } + } + } + + if (test->temp_rc != 0) + { + pass_test = false; + break; + } + } + + SVEC_CLEAR(&app_context->objects_to_test); + + for (size_t i = 0; i < app_context->cycles.length; ++i) + { + SwapVector* cycle = (SwapVector*) app_context->cycles.data[i]; + + SVEC_RELEASE(cycle); + FREE(cycle); + } + + SVEC_CLEAR(&app_context->cycles); + + return pass_test; +} + +bool attemptFree(SWFAppContext* app_context, ASObject* o) +{ + u32 rc; + + OBJ_LOCK_READ(o, + { + rc = o->refcount; + }); + + SVEC_CLEAR(&app_context->reachable); + + getReachable(app_context, o); + + if (rc == 0) + { + freeObject(app_context, o); + return true; + } + + if (subRCTest(app_context, o)) + { + freeObject(app_context, o); + return true; + } + + return false; +} + +recomp_thread_t free_thread_handle; + +DECLARE_RUNTIME_THREAD_FUNC(freeThread) +{ + SVEC_INIT(&app_context->reachable); + SVEC_INIT(&app_context->objects_to_test); + SVEC_INIT(&app_context->cycles); + SVEC_INIT(&app_context->objects_subbed); + + while (true) + { + size_t length; + + LOCK_READ(object_queue_lock, + { + length = object_free_queue.length; + }); + + if (length == 0) + { + bool stop; + + LOCK_READ(object_queue_lock, + { + stop = app_context->stop_free; + }); + + if (stop) + { + break; + } + + continue; + } + + objnode* n; + + LOCK_WRITE(object_queue_lock, + { + n = (objnode*) rbtree_pop_root(&object_free_queue); + }); + + ASObject* o = (ASObject*) n->key; + + attemptFree(app_context, o); + + FREE(n); + } + + SVEC_RELEASE(&app_context->reachable); + SVEC_RELEASE(&app_context->objects_to_test); + SVEC_RELEASE(&app_context->cycles); + SVEC_RELEASE(&app_context->objects_subbed); + + thread_exit(); + + return 0; +} \ No newline at end of file diff --git a/src/actionmodern/objects.c b/src/actionmodern/objects.c new file mode 100644 index 0000000..e50a0e2 --- /dev/null +++ b/src/actionmodern/objects.c @@ -0,0 +1,844 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +extern recomp_rwlock_t object_queue_lock; + +u32 next_id = 0; + +ASObject* allocObjectCommon(SWFAppContext* app_context) +{ + ASObject* obj = HALLOC(sizeof(ASObject)); + + obj->id = next_id; + next_id += 1; + + rbtree_init(&obj->t, sizeof(ASProperty)); + obj->refcount = 0; + rwlock_init(&obj->lock); + obj->extra_data = NULL; + obj->reached = false; + obj->used = false; + obj->blocked = false; + obj->freed = false; + SVEC_INIT(&obj->neighbors); + SVEC_INIT(&obj->blocked_list); + obj->temp_rc = 0; + + //~ LOCK_WRITE(object_queue_lock, + //~ { + //~ SVEC_PUSH(&app_context->active_objects, obj); + //~ }); + + return obj; +} + +/** + * Object Allocation + * + * Allocates a new ASObject and returns it. + */ +ASObject* allocObject(SWFAppContext* app_context) +{ + ASObject* obj = allocObjectCommon(app_context); + + ActionVar proto_var; + proto_var.type = ACTION_STACK_VALUE_OBJECT; + proto_var.object = (ASObject*) app_context->Object_prototype; + setProperty(app_context, obj, STR_ID_PROTO, NULL, 0, &proto_var); + + ActionVar constructor_var; + constructor_var.type = ACTION_STACK_VALUE_OBJECT; + constructor_var.object = app_context->Object_constructor; + setProperty(app_context, obj, STR_ID_CONSTRUCTOR, NULL, 0, &constructor_var); + + return obj; +} + +/** + * Retain Object + * + * Increments the reference count of an object. + * Called when storing object in a variable, property, or array. + */ +void retainObject(ASObject* obj) +{ + obj->refcount++; +} + +extern rbtree object_free_queue; + +void queueObjectFreeCheck(SWFAppContext* app_context, ASObject* obj) +{ + LOCK_WRITE(object_queue_lock, + { + rbtree_insert_u64(app_context, &object_free_queue, (u64) obj); + }); +} + +extern ASObject* _global; + +/** + * Release Object + * + * Decrements the reference count of an object. + * When refcount reaches 0, frees the object and all its properties. + * Recursively releases any objects stored in properties. + */ +void releaseObject(SWFAppContext* app_context, ASObject* obj) +{ + obj->refcount--; + + if (obj != _global || app_context->global_free_override) + { + // queue object for free check + queueObjectFreeCheck(app_context, obj); + } +} + +bool getAndCallMethodIfExists(SWFAppContext* app_context, ASObject* this, u32 method_name, u32 num_args); + +void destroyObject(SWFAppContext* app_context, ASObject* obj) +{ + ASObject* ctor = getConstructor(app_context, obj); + + // TODO: implement a lock for the stack + // (or something else that doesn't suck lol) + switch (Function_get_func_name_string_id(app_context, ctor)) + { + case STR_ID_ARRAY: + { + Array_destroy(app_context, obj); + + break; + } + + case STR_ID_MOVIECLIP: + { + MovieClip_destroy(app_context, obj); + + break; + } + + case STR_ID_SOUND: + { + Sound_destroy(app_context, obj); + + break; + } + } + + while (obj->t.length > 0) + { + ASProperty* p = rbtree_pop_root(&obj->t); + FREE(p); + } + + if (obj->extra_data != NULL) + { + FREE(obj->extra_data); + } + + SVEC_RELEASE(&obj->neighbors); + SVEC_RELEASE(&obj->blocked_list); + + //~ LOCK_WRITE(object_queue_lock, + //~ { + //~ for (size_t i = 0; i < app_context->active_objects.length; ++i) + //~ { + //~ if ((ASObject*) app_context->active_objects.data[i] == obj) + //~ { + //~ SVEC_REMOVE(&app_context->active_objects, i); + //~ break; + //~ } + //~ } + //~ }); +} + +/** + * Get Property + * + * Retrieves a property value by name. + * Returns pointer to the ASProperty if found, or NULL if not found. + */ +ASProperty* getProperty(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length) +{ + if (UNLIKELY(this == NULL || (string_id == 0 && name == NULL))) + { + if (this == NULL) + { + UNREACHABLE("getProperty: 'this' is null"); + } + + if (string_id == 0 && name == NULL) + { + UNREACHABLE("getProperty: string id is 0 and name is null"); + } + + return NULL; + } + + if (UNLIKELY(string_id == 0)) + { + string_id = getStringId(app_context, (char*) name, name_length); + } + + ASProperty* p = (ASProperty*) rbtree_get(&this->t, string_id); + return p; +} + +/** + * Get Property Var + * + * Retrieves a property var by name. + * Copies var into output parameter. This operation is locked for you. + */ +void getPropertyVar(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length, ActionVar* out_var) +{ + if (UNLIKELY(this == NULL || (string_id == 0 && name == NULL) || out_var == NULL)) + { + if (this == NULL) + { + UNREACHABLE("getPropertyVar: 'this' is null"); + } + + if (string_id == 0 && name == NULL) + { + UNREACHABLE("getPropertyVar: string id is 0 and name is null"); + } + + if (out_var == NULL) + { + UNREACHABLE("getPropertyVar: out_var is null"); + } + + return; + } + + if (UNLIKELY(string_id == 0)) + { + string_id = getStringId(app_context, (char*) name, name_length); + } + + OBJ_LOCK_READ(this, + { + ASProperty* p = getProperty(app_context, this, string_id, name, name_length); + + if (p != NULL) + { + *out_var = p->value; + } + + else + { + out_var->type = ACTION_STACK_VALUE_UNDEFINED; + } + }); +} + +/** + * Get Property + * + * Retrieves a property value by name, or creates a new one. + * Returns pointer to the ASProperty. + */ +ASProperty* getOrCreateProperty(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length, bool* created) +{ + if (UNLIKELY(this == NULL || (string_id == 0 && name == NULL))) + { + if (this == NULL) + { + UNREACHABLE("getOrCreateProperty: 'this' is null"); + } + + if (string_id == 0 && name == NULL) + { + UNREACHABLE("getOrCreateProperty: string id is 0 and name is null"); + } + + return NULL; + } + + return (ASProperty*) RBT_GET_OR_INS(&this->t, string_id, created); +} + +/** + * Get Property With Prototype Chain + * + * Retrieves a property value by name, searching up the prototype chain via __proto__. + * + * This implements proper prototype-based inheritance for ActionScript. + */ +void getPropertyVarWithPrototype(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length, ActionVar* out_v) +{ + out_v->type = ACTION_STACK_VALUE_UNDEFINED; + + if (UNLIKELY(this == NULL || (string_id == 0 && name == NULL) || out_v == NULL)) + { + if (this == NULL) + { + UNREACHABLE("getPropertyVarWithPrototype: 'this' is null"); + } + + if (string_id == 0 && name == NULL) + { + UNREACHABLE("getPropertyVarWithPrototype: string id is 0 and name is null"); + } + + if (out_v == NULL) + { + UNREACHABLE("getPropertyVarWithPrototype: out_v is null"); + } + + return; + } + + ASObject* current = this; + ASProperty* prop; + + while (true) + { + // Search own properties first + + rwlock_lock_read(¤t->lock); + prop = getProperty(app_context, current, string_id, name, name_length); + + if (prop != NULL) + { + break; + } + + rwlock_unlock_read(¤t->lock); + + // Property not found on this object - walk up to __proto__ + + ASProperty* proto_prop; + + rwlock_lock_read(¤t->lock); + proto_prop = getProperty(app_context, current, STR_ID_PROTO, NULL, 0); + + if (proto_prop == NULL) + { + // No __proto__ property - end of chain + prop = NULL; + break; + } + + ASObject* next = proto_prop->value.object; + + rwlock_unlock_read(¤t->lock); + + // Move to next object in prototype chain + current = next; + } + + if (prop != NULL) + { + *out_v = prop->value; + } + + rwlock_unlock_read(¤t->lock); +} + +/** + * Set Property + * + * Sets a property value by name. Creates property if it doesn't exist. + * Handles reference counting if value is an object. + */ +void setProperty(SWFAppContext* app_context, ASObject* this, u32 string_id, const char* name, u32 name_length, ActionVar* value) +{ + if (UNLIKELY(this == NULL || (string_id == 0 && name == NULL) || value == NULL)) + { + if (this == NULL) + { + UNREACHABLE("setProperty: 'this' is null"); + } + + if (string_id == 0 && name == NULL) + { + UNREACHABLE("setProperty: string id is 0 and name is null"); + } + + if (value == NULL) + { + UNREACHABLE("setProperty: value is null"); + } + + return; + } + + if (UNLIKELY(string_id == 0)) + { + string_id = getStringId(app_context, (char*) name, name_length); + } + + ASProperty* p; + + OBJ_LOCK_READ(this, + { + p = getProperty(app_context, this, string_id, name, name_length); + }); + + // Retain new value if it's an object + if (IS_OBJ_P(value)) + { + ASObject* new_obj = (ASObject*) value->value; + + OBJ_LOCK_WRITE(new_obj, + { + retainObject(new_obj); + }); + } + + if (p != NULL) + { + // Property exists - update value + + bool release_old = false; + ASObject* old_obj; + + OBJ_LOCK_READ(this, + { + // Release old value if it was an object + if (IS_OBJ(p->value)) + { + release_old = true; + old_obj = (ASObject*) p->value.value; + } + + // Free old string if it owned memory + else if (p->value.type == ACTION_STACK_VALUE_STRING && + p->value.owns_memory) + { + FREE(p->value.str); + } + }); + + OBJ_LOCK_WRITE(this, + { + // Set new value + p->value = *value; + }); + + if (release_old) + { + OBJ_LOCK_WRITE(old_obj, + { + releaseObject(app_context, old_obj); + }); + } + + return; + } + + bool created; + + OBJ_LOCK_READ(this, + { + // Property doesn't exist - create new one + p = (ASProperty*) RBT_GET_OR_INS(&this->t, string_id, &created); + }); + + OBJ_LOCK_WRITE(this, + { + p->value = *value; + }); +} + +/** + * Delete Property + * + * Deletes a property by name. Returns true if deleted or not found (Flash behavior). + * Handles reference counting if value is an object/array. + */ +bool deleteProperty(SWFAppContext* app_context, ASObject* obj, const char* name, u32 name_length) +{ + //~ if (obj == NULL || name == NULL) + //~ { + //~ return true; // Flash behavior: delete on null returns true + //~ } + + //~ // Find property by name + //~ for (u32 i = 0; i < obj->num_used; i++) + //~ { + //~ if (obj->properties[i].name_length == name_length && + //~ strncmp(obj->properties[i].name, name, name_length) == 0) + //~ { + //~ // Property found - delete it + + //~ // 1. Release the property value if it's an object/array + //~ if (obj->properties[i].value.type == ACTION_STACK_VALUE_OBJECT) + //~ { + //~ ASObject* child_obj = (ASObject*) obj->properties[i].value.value; + //~ releaseObject(app_context, child_obj); + //~ } + + //~ else if (obj->properties[i].value.type == ACTION_STACK_VALUE_ARRAY) + //~ { + //~ ASArray* child_arr = (ASArray*) obj->properties[i].value.value; + //~ releaseArray(app_context, child_arr); + //~ } + + //~ // Free string if it owns memory + //~ else if (obj->properties[i].value.type == ACTION_STACK_VALUE_STRING && + //~ obj->properties[i].value.owns_memory) + //~ { + //~ free(obj->properties[i].value.str); + //~ } + + //~ // 2. Free the property name + //~ if (obj->properties[i].name != NULL) + //~ { + //~ FREE(obj->properties[i].name); + //~ } + + //~ // 3. Shift remaining properties down to fill the gap + //~ for (u32 j = i; j < obj->num_used - 1; j++) + //~ { + //~ obj->properties[j] = obj->properties[j + 1]; + //~ } + + //~ // 4. Decrement the number of used slots + //~ obj->num_used--; + + //~ // 5. Zero out the last slot + //~ memset(&obj->properties[obj->num_used], 0, sizeof(ASProperty)); + + //~ return true; + //~ } + //~ } + + return true; +} + +/** + * Get Constructor + * + * Get the constructor function for an object. + * Returns the "constructor" property if it exists, NULL otherwise. + */ +ASObject* getConstructor(SWFAppContext* app_context, ASObject* obj) +{ + // Look for "constructor" property + ASObject* ctor = getProperty(app_context, obj, STR_ID_CONSTRUCTOR, NULL, 0)->value.object; + + if (LIKELY(ctor != NULL)) + { + return ctor; + } + + UNREACHABLE("Object without constructor"); + return NULL; +} + +/** + * Debug Functions + */ + +#ifdef DEBUG +void assertRefcount(ASObject* obj, u32 expected) +{ + if (obj == NULL) + { + fprintf(stderr, "ERROR: assertRefcount called with NULL object\n"); + assert(0); + } + + if (obj->refcount != expected) + { + fprintf(stderr, "ERROR: refcount assertion failed: expected %u, got %u\n", + expected, obj->refcount); + assert(0); + } + + printf("[DEBUG] assertRefcount: obj=%p, refcount=%u (OK)\n", (void*)obj, expected); +} + +void printObject(ASObject* obj) +{ + if (obj == NULL) + { + printf("Object: NULL\n"); + return; + } + + printf("Object: %p\n", (void*)obj); + printf(" refcount: %u\n", obj->refcount); + printf(" num_properties: %u\n", obj->num_properties); + printf(" num_used: %u\n", obj->num_used); + printf(" properties:\n"); + + for (u32 i = 0; i < obj->num_used; i++) + { + printf(" [%u] '%.*s' = ", + i, obj->properties[i].name_length, obj->properties[i].name); + + switch (obj->properties[i].value.type) + { + case ACTION_STACK_VALUE_F32: + printf("%.15g (F32)\n", *((float*)&obj->properties[i].value.value)); + break; + + case ACTION_STACK_VALUE_F64: + printf("%.15g (F64)\n", *((double*)&obj->properties[i].value.value)); + break; + + case ACTION_STACK_VALUE_STRING: + { + const char* str = obj->properties[i].value.owns_memory ? + obj->properties[i].value.str : + (const char*)obj->properties[i].value.value; + printf("'%.*s' (STRING)\n", obj->properties[i].value.str_size, str); + break; + } + + case ACTION_STACK_VALUE_OBJECT: + printf("%p (OBJECT)\n", (void*)obj->properties[i].value.value); + break; + + default: + printf("(unknown type %d)\n", obj->properties[i].value.type); + break; + } + } +} + +void printArray(ASArray* arr) +{ + if (arr == NULL) + { + printf("Array: NULL\n"); + return; + } + + printf("Array: %p\n", (void*)arr); + printf(" refcount: %u\n", arr->refcount); + printf(" length: %u\n", arr->length); + printf(" capacity: %u\n", arr->capacity); + printf(" elements:\n"); + + for (u32 i = 0; i < arr->length; i++) + { + printf(" [%u] = ", i); + + switch (arr->elements[i].type) + { + case ACTION_STACK_VALUE_F32: + printf("%.15g (F32)\n", *((float*)&arr->elements[i].value)); + break; + + case ACTION_STACK_VALUE_F64: + printf("%.15g (F64)\n", *((double*)&arr->elements[i].value)); + break; + + case ACTION_STACK_VALUE_STRING: + { + const char* str = arr->elements[i].owns_memory ? + arr->elements[i].str : + (const char*)arr->elements[i].value; + printf("'%.*s' (STRING)\n", arr->elements[i].str_size, str); + break; + } + + case ACTION_STACK_VALUE_OBJECT: + printf("%p (OBJECT)\n", (void*)arr->elements[i].value); + break; + + case ACTION_STACK_VALUE_ARRAY: + printf("%p (ARRAY)\n", (void*)arr->elements[i].value); + break; + + default: + printf("(unknown type %d)\n", arr->elements[i].type); + break; + } + } +} +#endif + +/** + * Array Implementation + */ + +ASArray* allocArray(SWFAppContext* app_context, u32 initial_capacity) +{ + ASArray* arr = (ASArray*) malloc(sizeof(ASArray)); + if (arr == NULL) + { + fprintf(stderr, "ERROR: Failed to allocate ASArray\n"); + return NULL; + } + + arr->refcount = 1; // Initial reference owned by caller + arr->length = 0; + arr->capacity = initial_capacity > 0 ? initial_capacity : 4; + + // Allocate element array + arr->elements = (ActionVar*) malloc(sizeof(ActionVar) * arr->capacity); + if (arr->elements == NULL) + { + fprintf(stderr, "ERROR: Failed to allocate array elements\n"); + free(arr); + return NULL; + } + + // Initialize elements to zero + memset(arr->elements, 0, sizeof(ActionVar) * arr->capacity); + + return arr; +} + +void retainArray(ASArray* arr) +{ + if (arr == NULL) + { + return; + } + + arr->refcount++; +} + +void releaseArray(SWFAppContext* app_context, ASArray* arr) +{ + //~ if (arr == NULL) + //~ { + //~ return; + //~ } + + //~ arr->refcount--; + + //~ if (arr->refcount == 0) + //~ { + //~ // Release all element values + //~ for (u32 i = 0; i < arr->length; i++) + //~ { + //~ // If element is an object, release it recursively + //~ if (arr->elements[i].type == ACTION_STACK_VALUE_OBJECT) + //~ { + //~ ASObject* child_obj = (ASObject*) arr->elements[i].value; + //~ releaseObject(app_context, child_obj); + //~ } + //~ // If element is an array, release it recursively + //~ else if (arr->elements[i].type == ACTION_STACK_VALUE_ARRAY) + //~ { + //~ ASArray* child_arr = (ASArray*) arr->elements[i].value; + //~ releaseArray(app_context, child_arr); + //~ } + //~ // If element is a string that owns memory, free it + //~ else if (arr->elements[i].type == ACTION_STACK_VALUE_STRING && + //~ arr->elements[i].owns_memory) + //~ { + //~ free(arr->elements[i].str); + //~ } + //~ } + + //~ // Free element array + //~ if (arr->elements != NULL) + //~ { + //~ free(arr->elements); + //~ } + + //~ // Free array itself + //~ free(arr); + //~ } +} + +ActionVar* getArrayElement(ASArray* arr, u32 index) +{ + if (arr == NULL || index >= arr->length) + { + return NULL; + } + + return &arr->elements[index]; +} + +void setArrayElement(SWFAppContext* app_context, ASArray* arr, u32 index, ActionVar* value) +{ + //~ if (arr == NULL || value == NULL) + //~ { + //~ return; + //~ } + + //~ // Grow array if needed + //~ if (index >= arr->capacity) + //~ { + //~ u32 new_capacity = (index + 1) * 2; // Grow to accommodate index + //~ ActionVar* new_elements = (ActionVar*) realloc(arr->elements, + //~ sizeof(ActionVar) * new_capacity); + //~ if (new_elements == NULL) + //~ { + //~ fprintf(stderr, "ERROR: Failed to grow array\n"); + //~ return; + //~ } + + //~ arr->elements = new_elements; + + //~ // Zero out new slots + //~ memset(&arr->elements[arr->capacity], 0, + //~ sizeof(ActionVar) * (new_capacity - arr->capacity)); + + //~ arr->capacity = new_capacity; + //~ } + + //~ // Release old value if it exists and is an object/array + //~ if (index < arr->length) + //~ { + //~ if (arr->elements[index].type == ACTION_STACK_VALUE_OBJECT) + //~ { + //~ ASObject* old_obj = (ASObject*) arr->elements[index].value; + //~ releaseObject(app_context, old_obj); + //~ } + //~ else if (arr->elements[index].type == ACTION_STACK_VALUE_ARRAY) + //~ { + //~ ASArray* old_arr = (ASArray*) arr->elements[index].value; + //~ releaseArray(app_context, old_arr); + //~ } + //~ else if (arr->elements[index].type == ACTION_STACK_VALUE_STRING && + //~ arr->elements[index].owns_memory) + //~ { + //~ free(arr->elements[index].str); + //~ } + //~ } + + //~ // Set new value + //~ arr->elements[index] = *value; + + //~ // Update length if needed + //~ if (index >= arr->length) + //~ { + //~ arr->length = index + 1; + //~ } + + //~ // Retain new value if it's an object or array + //~ if (value->type == ACTION_STACK_VALUE_OBJECT) + //~ { + //~ ASObject* new_obj = (ASObject*) value->value; + //~ retainObject(new_obj); + //~ } + //~ else if (value->type == ACTION_STACK_VALUE_ARRAY) + //~ { + //~ ASArray* new_arr = (ASArray*) value->value; + //~ retainArray(new_arr); + //~ } + +//~ #ifdef DEBUG + //~ printf("[DEBUG] setArrayElement: arr=%p, index=%u, length=%u\n", + //~ (void*)arr, index, arr->length); +//~ #endif +} \ No newline at end of file diff --git a/src/actionmodern/runtime_api/Array.c b/src/actionmodern/runtime_api/Array.c new file mode 100644 index 0000000..1a888b1 --- /dev/null +++ b/src/actionmodern/runtime_api/Array.c @@ -0,0 +1,230 @@ +#include + +#include + +#include + +#include + +#define EXTDATA(member) (((ArrayData*) this->extra_data)->member) + +void Array_init(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + ASObject* Array = getProperty(app_context, _global, STR_ID_ARRAY, NULL, 0)->value.object; + + ActionVar v; + v.type = ACTION_STACK_VALUE_INT; + + //~ v.s32 = 0; + //~ setProperty(app_context, Array, STR_ID_CASE_INSENSITIVE, NULL, 0, &v); + + RETURN_VOID(); +} + +void Array_new(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + this->extra_data = HALLOC(sizeof(ArrayData)); + + EXTDATA(undef).type = ACTION_STACK_VALUE_UNDEFINED; + + if (num_args == 0) + { + size_t length = 0; + size_t capacity = 8; + + EXTDATA(data) = HALLOC(capacity*sizeof(ActionVar)); + EXTDATA(length) = length; + EXTDATA(capacity) = capacity; + + for (size_t i = 0; i < length; ++i) + { + EXTDATA(data)[i].type = ACTION_STACK_VALUE_UNDEFINED; + } + + RETURN_VOID(); + return; + } + + if (num_args == 1) + { + convertIntECMA(app_context); + + ActionVar length_v; + popVar(app_context, &length_v); + + if (UNLIKELY(length_v.f64 == INFINITY || length_v.f64 == -INFINITY)) + { + EXC("Array constructor got inf\n"); + } + + size_t length = (size_t) length_v.f64; + size_t capacity = get_power_two_size(8, length); + + EXTDATA(data) = HALLOC(capacity*sizeof(ActionVar)); + EXTDATA(length) = length; + EXTDATA(capacity) = capacity; + + for (size_t i = 0; i < length; ++i) + { + EXTDATA(data)[i].type = ACTION_STACK_VALUE_UNDEFINED; + } + + RETURN_VOID(); + return; + } + + if (num_args > 1) + { + UNIMPLEMENTED("Array constructor with initial arguments"); + } + + RETURN_VOID(); +} + +void Array_push(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + ActionVar v; + peekVar(app_context, &v); + + size_t length = ++EXTDATA(length); + + ENSURE_SIZE(EXTDATA(data), length, EXTDATA(capacity), sizeof(ActionVar)); + + if (IS_OBJ_T(v.type)) + { + OBJ_LOCK_WRITE(v.object, + { + retainObject(v.object); + }); + } + + EXTDATA(data)[length - 1] = v; + + DISCARD_ARGS(num_args); + + releaseObjectVar(app_context, &v); + + f64 length_f64 = (f64) length; + PUSH_F64(length_f64); +} + +void Array_pop(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + ActionVar* data = EXTDATA(data); + size_t length = --EXTDATA(length); + + if (IS_OBJ_T(data[length].type)) + { + OBJ_LOCK_WRITE(data[length].object, + { + releaseObject(app_context, data[length].object); + }); + } + + PUSH_VAR(&data[length]); +} + +void Array_toString(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + UNIMPLEMENTED("Array toString"); + + size_t length = EXTDATA(length); + + if (UNLIKELY(length == 0)) + { + PUSH_STR_ID("", STR_ID_EMPTY, 0); + return; + } + + // add first element separately without comma + + //~ for (size_t i = 1; i < length; ++i) + //~ { + + //~ } +} + +ActionVar* Array_getElement(SWFAppContext* app_context, ASObject* this, s32 i) +{ + if (UNLIKELY(i >= EXTDATA(length))) + { + return &EXTDATA(undef); + } + + return &(EXTDATA(data)[i]); +} + +void Array_setElement(SWFAppContext* app_context, ASObject* this, s32 i, ActionVar* v) +{ + ENSURE_SIZE_FAR(EXTDATA(data), i + 1, EXTDATA(capacity), sizeof(ActionVar)); + + if (i >= EXTDATA(length)) + { + EXTDATA(length) = i + 1; + } + + if (IS_OBJ_T(v->type)) + { + OBJ_LOCK_WRITE(v->object, + { + retainObject(v->object); + }); + } + + if (IS_OBJ_T(EXTDATA(data)[i].type)) + { + OBJ_LOCK_WRITE(EXTDATA(data)[i].object, + { + releaseObject(app_context, EXTDATA(data)[i].object); + }); + } + + EXTDATA(data)[i] = *v; +} + +void Array_setLength(SWFAppContext* app_context, ASObject* this, size_t new_length) +{ + if (new_length < EXTDATA(length)) + { + for (size_t i = new_length; i < EXTDATA(length); ++i) + { + ActionVar* v = &EXTDATA(data[i]); + + if (IS_OBJ_T(v->type)) + { + OBJ_LOCK_WRITE(v->object, + { + releaseObject(app_context, v->object); + }); + } + + EXTDATA(data)[i].type = ACTION_STACK_VALUE_UNDEFINED; + } + } + + EXTDATA(length) = new_length; +} + +void Array_destroy(SWFAppContext* app_context, ASObject* this) +{ + for (size_t i = 0; i < EXTDATA(length); ++i) + { + ActionVar* v = &EXTDATA(data[i]); + + if (IS_OBJ_T(v->type)) + { + OBJ_LOCK_WRITE(v->object, + { + releaseObject(app_context, v->object); + }); + } + } + + FREE(EXTDATA(data)); +} \ No newline at end of file diff --git a/src/actionmodern/runtime_api/BitmapData.c b/src/actionmodern/runtime_api/BitmapData.c new file mode 100644 index 0000000..c00d52f --- /dev/null +++ b/src/actionmodern/runtime_api/BitmapData.c @@ -0,0 +1,113 @@ +#include + +#include +#include +#include +#include + +#define EXTDATA(member) (((BitmapData*) this->extra_data)->member) +#define EXTDATA_OF(o, member) (((BitmapData*) o->extra_data)->member) + +void BitmapData_init(SWFAppContext* app_context, ASObject* this) +{ + ActionVar proto_v; + proto_v.type = ACTION_STACK_VALUE_OBJECT; + proto_v.object = app_context->BitmapData_prototype; + setProperty(app_context, this, STR_ID_PROTO, NULL, 0, &proto_v); + + ActionVar ctor_v; + ctor_v.type = ACTION_STACK_VALUE_OBJECT; + ctor_v.object = app_context->BitmapData_constructor; + setProperty(app_context, this, STR_ID_CONSTRUCTOR, NULL, 0, &ctor_v); + + this->extra_data = HALLOC(sizeof(BitmapData)); + + EXTDATA(char_id) = 0; + EXTDATA(_parent) = NULL; + + EXTDATA(width) = 0; + EXTDATA(height) = 0; +} + +ASObject* BitmapData_create(SWFAppContext* app_context) +{ + ASObject* this = allocObject(app_context); + + BitmapData_init(app_context, this); + + return this; +} + +void BitmapData_new(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + BitmapData_init(app_context, this); + + RETURN_VOID(); +} + +void BitmapData_loadBitmap(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + ActionVar bitmap_v; + popVar(app_context, &bitmap_v); + + u32 bitmap_string_id; + + if (bitmap_v.string_id) + { + bitmap_string_id = bitmap_v.string_id; + } + + else + { + bitmap_string_id = getStringId(app_context, bitmap_v.str, bitmap_v.str_size); + } + + releaseObjectVar(app_context, &bitmap_v); + + DISCARD_ARGS(num_args - 1); + + u16 char_id = swfGetExportedChar(app_context, bitmap_string_id); + + ASObject* bitmap = BitmapData_create(app_context); + bitmap->extra_data = HALLOC(sizeof(BitmapData)); + EXTDATA_OF(bitmap, char_id) = char_id; + + u16 bitmap_id = swfGetBitmapId(app_context, char_id); + EXTDATA_OF(bitmap, bitmap_id) = bitmap_id; + + EXTDATA_OF(bitmap, width) = FBC->bitmap_sizes[2*bitmap_id]; + EXTDATA_OF(bitmap, height) = FBC->bitmap_sizes[2*bitmap_id + 1]; + + PUSH_OBJ(bitmap); +} + +bool BitmapData_getMember(SWFAppContext* app_context, ASObject* this, u32 string_id, ActionVar* out_v) +{ + switch (string_id) + { + case STR_ID_WIDTH: + { + out_v->type = ACTION_STACK_VALUE_INT; + out_v->s32 = EXTDATA(width); + + break; + } + + case STR_ID_HEIGHT: + { + out_v->type = ACTION_STACK_VALUE_INT; + out_v->s32 = EXTDATA(height); + + break; + } + + default: + { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/src/actionmodern/runtime_api/Function.c b/src/actionmodern/runtime_api/Function.c new file mode 100644 index 0000000..cc745d0 --- /dev/null +++ b/src/actionmodern/runtime_api/Function.c @@ -0,0 +1,64 @@ +#include + +#include + +#include + +#include + +#define EXTDATA(member) (((FunctionData*) this->extra_data)->member) + +void Function_new(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + UNREACHABLE("Function constructor"); +} + +void Function_init_object(SWFAppContext* app_context, ASObject* this) +{ + this->extra_data = HALLOC(sizeof(FunctionData)); + + ActionVar ctor_v; + ctor_v.type = ACTION_STACK_VALUE_OBJECT; + ctor_v.object = app_context->Function_constructor; + setProperty(app_context, this, STR_ID_CONSTRUCTOR, NULL, 0, &ctor_v); +} + +void Function_set_members(SWFAppContext* app_context, ASObject* this, FunctionType func_type, action_func func, void* args, u8 reg_count, u16 flags, u32 func_name_string_id) +{ + EXTDATA(func_type) = func_type; + EXTDATA(func) = func; + EXTDATA(args) = args; + EXTDATA(reg_count) = reg_count; + EXTDATA(flags) = flags; + EXTDATA(func_name_string_id) = func_name_string_id; +} + +action_func Function_get_func(SWFAppContext* app_context, ASObject* this) +{ + return EXTDATA(func); +} + +void* Function_get_args(SWFAppContext* app_context, ASObject* this) +{ + return EXTDATA(args); +} + +FunctionType Function_get_func_type(SWFAppContext* app_context, ASObject* this) +{ + return EXTDATA(func_type); +} + +u8 Function_get_reg_count(SWFAppContext* app_context, ASObject* this) +{ + return EXTDATA(reg_count); +} + +u16 Function_get_flags(SWFAppContext* app_context, ASObject* this) +{ + return EXTDATA(flags); +} + +u32 Function_get_func_name_string_id(SWFAppContext* app_context, ASObject* this) +{ + return EXTDATA(func_name_string_id); +} \ No newline at end of file diff --git a/src/actionmodern/runtime_api/MovieClip.c b/src/actionmodern/runtime_api/MovieClip.c new file mode 100644 index 0000000..a7ed50f --- /dev/null +++ b/src/actionmodern/runtime_api/MovieClip.c @@ -0,0 +1,483 @@ +#define _USE_MATH_DEFINES +#include + +#include +#include + +#include + +#include + +#define EXTDATA(member) (((MovieClipData*) this->extra_data)->member) +#define EXTDATA_OF(o, member) (((MovieClipData*) o->extra_data)->member) + +#define RAD(r) (r*M_PI/180.0) + +void MovieClip_new(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + EXC("who just tried to call new MovieClip() LMFAO"); +} + +ASObject* MovieClip_create(SWFAppContext* app_context) +{ + ASObject* this = allocObject(app_context); + + ActionVar proto_v; + proto_v.type = ACTION_STACK_VALUE_OBJECT; + proto_v.object = app_context->MovieClip_prototype; + setProperty(app_context, this, STR_ID_PROTO, NULL, 0, &proto_v); + + ActionVar ctor_v; + ctor_v.type = ACTION_STACK_VALUE_OBJECT; + ctor_v.object = app_context->MovieClip_constructor; + setProperty(app_context, this, STR_ID_CONSTRUCTOR, NULL, 0, &ctor_v); + + this->extra_data = HALLOC(sizeof(MovieClipData)); + + EXTDATA(char_id) = 0; + + EXTDATA(has_tris) = false; + EXTDATA(bitmap_at) = 0; + + EXTDATA(_parent) = NULL; + EXTDATA(parent_depth) = 0; + EXTDATA(_rotation) = 0.0; + EXTDATA(_x) = 0.0; + EXTDATA(_y) = 0.0; + EXTDATA(_xscale) = 100.0; + EXTDATA(_yscale) = 100.0; + + size_t capacity = 8; + + EXTDATA(children) = HALLOC(capacity*sizeof(ASObject*)); + EXTDATA(max_depth) = 0; + EXTDATA(display_list_capacity) = capacity; + + for (size_t i = 0; i < capacity; ++i) + { + EXTDATA(children)[i] = NULL; + } + + return this; +} + +void MovieClip_applyTransformsParents(SWFAppContext* app_context, ASObject* this, f32 mat[16]) +{ + ASObject* current = EXTDATA(_parent); + + SwapVector parent_chain; + + SVEC_INIT(&parent_chain); + SVEC_PUSH(&parent_chain, this); + + while (current != app_context->_root) + { + SVEC_PUSH(&parent_chain, current); + + current = EXTDATA_OF(current, _parent); + } + + current = (ASObject*) SVEC_TOP(&parent_chain); + + f32 x = (f32) (20.0f*EXTDATA_OF(current, _x)); + f32 y = (f32) (20.0f*EXTDATA_OF(current, _y)); + f32 r = (f32) EXTDATA_OF(current, _rotation); + f32 sx = (f32) EXTDATA_OF(current, _xscale)/100.0f; + f32 sy = (f32) EXTDATA_OF(current, _yscale)/100.0f; + + f32 t[6]; + f32 m[6]; + + mat[0] = cosf((f32) RAD(r))*sx; + mat[1] = sinf((f32) RAD(r))*sx; + mat[4] = -sinf((f32) RAD(r))*sy; + mat[5] = cosf((f32) RAD(r))*sy; + + mat[12] = x; + mat[13] = y; + + SVEC_POP(&parent_chain); + + for (s64 i = parent_chain.length - 1; i >= 0; --i) + { + m[0] = mat[0]; + m[1] = mat[1]; + m[2] = mat[4]; + m[3] = mat[5]; + m[4] = mat[12]; + m[5] = mat[13]; + + current = (ASObject*) parent_chain.data[i]; + + x = (f32) (20.0f*EXTDATA_OF(current, _x)); + y = (f32) (20.0f*EXTDATA_OF(current, _y)); + r = (f32) EXTDATA_OF(current, _rotation); + sx = (f32) EXTDATA_OF(current, _xscale)/100.0f; + sy = (f32) EXTDATA_OF(current, _yscale)/100.0f; + + // row-major because i'm a disgusting pagan + t[0] = cosf((f32) RAD(r))*sx; + t[1] = -sinf((f32) RAD(r))*sy; + t[2] = sinf((f32) RAD(r))*sx; + t[3] = cosf((f32) RAD(r))*sy; + + // just kidding + t[4] = x; + t[5] = y; + + mat[0] = m[0]*t[0] + m[2]*t[2]; + mat[1] = m[1]*t[0] + m[3]*t[2]; + mat[4] = m[0]*t[1] + m[2]*t[3]; + mat[5] = m[1]*t[1] + m[3]*t[3]; + + mat[12] = m[0]*t[4] + m[2]*t[5] + m[4]; + mat[13] = m[1]*t[4] + m[3]*t[5] + m[5]; + } + + SVEC_RELEASE(&parent_chain); +} + +void MovieClip_growChildren_internal(SWFAppContext* app_context, ASObject* this, u32 depth) +{ + size_t old_capacity = EXTDATA(display_list_capacity); + + ENSURE_SIZE_FAR(EXTDATA(children), depth + 1, EXTDATA(display_list_capacity), sizeof(ASObject*)); + + for (size_t i = old_capacity; i < EXTDATA(display_list_capacity); ++i) + { + EXTDATA(children)[i] = NULL; + } +} + +void MovieClip_setChild_internal(SWFAppContext* app_context, ASObject* this, u32 depth, ASObject* new_child) +{ + MovieClip_growChildren_internal(app_context, this, depth); + ASObject* old_child = EXTDATA(children)[depth]; + + OBJ_LOCK_WRITE(new_child, + { + retainObject(new_child); + }); + + if (old_child != NULL) + { + OBJ_LOCK_WRITE(old_child, + { + releaseObject(app_context, old_child); + }); + } + + EXTDATA(children)[depth] = new_child; + + //~ ASObject* old_parent = EXTDATA_OF(new_child, _parent); + + //~ OBJ_LOCK_WRITE(this, + //~ { + //~ retainObject(this); + //~ }); + + //~ if (old_parent != NULL) + //~ { + //~ OBJ_LOCK_WRITE(old_parent, + //~ { + //~ releaseObject(app_context, old_parent); + //~ }); + //~ } + + EXTDATA_OF(new_child, _parent) = this; + EXTDATA_OF(new_child, parent_depth) = depth; +} + +void MovieClip_removeChild_internal(SWFAppContext* app_context, ASObject* this, u32 depth) +{ + ASObject* old_child = EXTDATA(children)[depth]; + + EXTDATA_OF(old_child, _parent) = NULL; + EXTDATA_OF(old_child, parent_depth) = 0; + + // TODO: also remove old child from the rbtree + + if (old_child != NULL) + { + OBJ_LOCK_WRITE(old_child, + { + releaseObject(app_context, old_child); + }); + } + + EXTDATA(children)[depth] = NULL; +} + +void MovieClip_placeObject2_internal(SWFAppContext* app_context, ASObject* this, u32 depth, u32 char_id, u32 transform_id) +{ + MovieClip_setChild_internal(app_context, this, depth, MovieClip_create(app_context)); + + EXTDATA_OF(EXTDATA(children)[depth], char_id) = char_id; + EXTDATA_OF(EXTDATA(children)[depth], transform_id) = transform_id; + + if (depth > EXTDATA(max_depth)) + { + EXTDATA(max_depth) = depth; + } +} + +void MovieClip_attachBitmap(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + ActionVar bitmap_v; + popVar(app_context, &bitmap_v); + ActionVar depth_v; + popVar(app_context, &depth_v); + + ASObject* bitmap = bitmap_v.object; + + releaseObjectVar(app_context, &depth_v); + toNumber(app_context, &depth_v); + popVar(app_context, &depth_v); + + u32 depth = (u32) depth_v.f64; + MovieClip_setChild_internal(app_context, this, depth, bitmap); + EXTDATA(bitmap_at) = depth; + + if (depth > EXTDATA(max_depth)) + { + EXTDATA(max_depth) = depth; + } + + releaseObjectVar(app_context, &depth_v); + releaseObjectVar(app_context, &bitmap_v); + + DISCARD_ARGS(num_args - 2); + + RETURN_VOID(); +} + +ASObject* MovieClip_createTextField_internal(SWFAppContext* app_context, ASObject* this, ActionVar* name_v) +{ + ASObject* tf = allocObject(app_context); + + ActionVar tf_v; + tf_v.type = ACTION_STACK_VALUE_OBJECT; + tf_v.object = tf; + + setProperty(app_context, this, name_v->string_id, NULL, 0, &tf_v); + + return tf; +} + +void MovieClip_createTextField(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + ActionVar name_v; + popVar(app_context, &name_v); + + ASObject* tf = MovieClip_createTextField_internal(app_context, this, &name_v); + + releaseObjectVar(app_context, &name_v); + + DISCARD_ARGS(num_args - 1); + + PUSH_OBJ(tf); +} + +ASObject* MovieClip_createEmptyMovieClip_internal(SWFAppContext* app_context, ASObject* this, ActionVar* name_v, ActionVar* depth_v) +{ + ASObject* mc = MovieClip_create(app_context); + + ActionVar mc_v; + mc_v.type = ACTION_STACK_VALUE_OBJECT; + mc_v.object = mc; + + setProperty(app_context, this, name_v->string_id, name_v->str, name_v->str_size, &mc_v); + + releaseObjectVar(app_context, depth_v); + toNumber(app_context, depth_v); + popVar(app_context, depth_v); + + u32 depth = (u32) depth_v->f64; + + if (depth > EXTDATA(max_depth)) + { + EXTDATA(max_depth) = depth; + } + + MovieClip_setChild_internal(app_context, this, depth, mc); + + return mc; +} + +void MovieClip_createEmptyMovieClip(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + ActionVar name_v; + popVar(app_context, &name_v); + ActionVar depth_v; + popVar(app_context, &depth_v); + + ASObject* mc = MovieClip_createEmptyMovieClip_internal(app_context, this, &name_v, &depth_v); + + releaseObjectVar(app_context, &depth_v); + releaseObjectVar(app_context, &name_v); + + DISCARD_ARGS(num_args - 2); + + PUSH_OBJ(mc); +} + +void MovieClip_removeMovieClip(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + ASObject* parent = EXTDATA(_parent); + size_t depth = EXTDATA(parent_depth); + + MovieClip_removeChild_internal(app_context, parent, (u32) depth); + + RETURN_VOID(); +} + +void MovieClip_destroy(SWFAppContext* app_context, ASObject* this) +{ + for (size_t i = 1; i <= EXTDATA(max_depth); ++i) + { + ASObject* child = EXTDATA(children)[i]; + + if (child != NULL) + { + OBJ_LOCK_WRITE(child, + { + releaseObject(app_context, child); + }); + } + } + + FREE(EXTDATA(children)); + + //~ ASObject* old_parent = EXTDATA(_parent); + + //~ if (old_parent != NULL) + //~ { + //~ OBJ_LOCK_WRITE(old_parent, + //~ { + //~ releaseObject(app_context, old_parent); + //~ }); + //~ } +} + +bool MovieClip_getMember(SWFAppContext* app_context, ASObject* this, u32 string_id, ActionVar* out_v) +{ + switch (string_id) + { + case STR_ID__PARENT: + { + out_v->type = ACTION_STACK_VALUE_OBJECT; + out_v->object = EXTDATA(_parent); + + break; + } + + case STR_ID__ROTATION: + { + out_v->type = ACTION_STACK_VALUE_F64; + out_v->f64 = EXTDATA(_rotation); + + break; + } + + case STR_ID__X: + { + out_v->type = ACTION_STACK_VALUE_F64; + out_v->f64 = EXTDATA(_x); + + break; + } + + case STR_ID__Y: + { + out_v->type = ACTION_STACK_VALUE_F64; + out_v->f64 = EXTDATA(_y); + + break; + } + + case STR_ID__XSCALE: + { + out_v->type = ACTION_STACK_VALUE_F64; + out_v->f64 = EXTDATA(_xscale); + + break; + } + + case STR_ID__YSCALE: + { + out_v->type = ACTION_STACK_VALUE_F64; + out_v->f64 = EXTDATA(_yscale); + + break; + } + + default: + { + return false; + } + } + + return true; +} + +bool MovieClip_setMember(SWFAppContext* app_context, ASObject* this, u32 string_id, ActionVar* v) +{ + switch (string_id) + { + case STR_ID__PARENT: + { + EXTDATA(_parent) = v->object; + + break; + } + + case STR_ID__ROTATION: + { + convertNumericToNumber(app_context, v); + EXTDATA(_rotation) = v->f64; + + break; + } + + case STR_ID__X: + { + convertNumericToNumber(app_context, v); + EXTDATA(_x) = v->f64; + + break; + } + + case STR_ID__Y: + { + convertNumericToNumber(app_context, v); + EXTDATA(_y) = v->f64; + + break; + } + + case STR_ID__XSCALE: + { + convertNumericToNumber(app_context, v); + EXTDATA(_xscale) = v->f64; + + break; + } + + case STR_ID__YSCALE: + { + convertNumericToNumber(app_context, v); + EXTDATA(_yscale) = v->f64; + + break; + } + + default: + { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/src/actionmodern/runtime_api/Number.c b/src/actionmodern/runtime_api/Number.c new file mode 100644 index 0000000..0ba39c9 --- /dev/null +++ b/src/actionmodern/runtime_api/Number.c @@ -0,0 +1,76 @@ +#include + +#include + +#include + +#include + +#define EXTDATA(member) (((NumberData*) this->extra_data)->member) + +void Number_init(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + ASObject* Number = getProperty(app_context, _global, STR_ID_NUMBER, NULL, 0)->value.object; + + ActionVar v; + v.type = ACTION_STACK_VALUE_F64; + + v.f64 = NAN; + setProperty(app_context, Number, STR_ID_NAN, NULL, 0, &v); + + v.f64 = INFINITY; + setProperty(app_context, Number, STR_ID_POSITIVE_INFINITY, NULL, 0, &v); + + v.f64 = -INFINITY; + setProperty(app_context, Number, STR_ID_NEGATIVE_INFINITY, NULL, 0, &v); + + v.u64 = 0x7FEFFFFFFFFFFFFF; + setProperty(app_context, Number, STR_ID_MAX_VALUE, NULL, 0, &v); + + v.u64 = 0x0000000000000001; + setProperty(app_context, Number, STR_ID_MIN_VALUE, NULL, 0, &v); + + RETURN_VOID(); +} + +void Number_new(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + this->extra_data = HALLOC(sizeof(NumberData)); + + EXTDATA(num).type = ACTION_STACK_VALUE_F64; + + ActionVar num; + + if (num_args > 0) + { + convertDouble(app_context); + popVar(app_context, &num); + + DISCARD_ARGS(num_args - 1); + + EXTDATA(num).f64 = num.f64; + } + + else + { + EXTDATA(num).f64 = 0.0; + } + + RETURN_VOID(); +} + +void Number_toString(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + toString(app_context, &EXTDATA(num)); +} + +void Number_valueOf(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + PUSH_VAR(&EXTDATA(num)); +} \ No newline at end of file diff --git a/src/actionmodern/runtime_api/Object.c b/src/actionmodern/runtime_api/Object.c new file mode 100644 index 0000000..c147de3 --- /dev/null +++ b/src/actionmodern/runtime_api/Object.c @@ -0,0 +1,44 @@ +#include +#include +#include + +#include + +#include + +void Object_new(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + RETURN_VOID(); +} + +void Object_toString(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + ActionVar constructor; + getPropertyVar(app_context, this, STR_ID_CONSTRUCTOR, NULL, 0, &constructor); + + u32 ctor_name_id = Function_get_func_name_string_id(app_context, constructor.object); + u32 len = 8 + app_context->str_len_table[ctor_name_id] + 1; + + PUSH_STR_STACK(len); + char* stack_str = (char*) &STACK_TOP_VALUE; + + snprintf(stack_str, len + 1, "[object %s]", app_context->str_table[ctor_name_id]); +} + +void Object_valueOf(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + PUSH_OBJ(this); +} + +void Object_recompId(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + PUSH_INT(this->id); +} \ No newline at end of file diff --git a/src/actionmodern/runtime_api/Sound.c b/src/actionmodern/runtime_api/Sound.c new file mode 100644 index 0000000..a7ecfe0 --- /dev/null +++ b/src/actionmodern/runtime_api/Sound.c @@ -0,0 +1,128 @@ +#include + +#define MINIMP3_IMPLEMENTATION +#include + +#include +#include +#include +#include + +#define EXTDATA(member) (((SoundData*) this->extra_data)->member) + +#define MPC ((SoundData*) app_context->minimp3_ctx) + +void Sound_new(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + this->extra_data = HALLOC(sizeof(SoundData)); + + EXTDATA(samples) = NULL; + EXTDATA(loaded) = false; + + RETURN_VOID(); +} + +void Sound_loadSound(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + ActionVar file_v; + popVar(app_context, &file_v); + ActionVar is_stream_v; + popVar(app_context, &is_stream_v); + + DISCARD_ARGS(num_args - 2); + + mp3dec_ex_t ctx; + + if (mp3dec_ex_open(&ctx, file_v.str, MP3D_SEEK_TO_SAMPLE)) + { + goto return_void; + } + + if (EXTDATA(samples) != NULL) + { + FREE(EXTDATA(samples)); + } + + size_t sample_count = ctx.samples; + EXTDATA(byte_count) = sizeof(mp3d_sample_t)*sample_count; + EXTDATA(samples) = HALLOC(EXTDATA(byte_count)); + size_t read = mp3dec_ex_read(&ctx, (mp3d_sample_t*) EXTDATA(samples), sample_count); + + if (read != sample_count) + { + FREE(EXTDATA(samples)); + goto return_void; + } + + EXTDATA(loaded) = true; + EXTDATA(stream_id) = flashbang_create_audio_stream(FBC, app_context); + + PUSH_BOOL(true); + getAndCallMethodIfExists(app_context, this, STR_ID_ON_LOAD, 1); + POP(); + + return_void: + + releaseObjectVar(app_context, &is_stream_v); + releaseObjectVar(app_context, &file_v); + + RETURN_VOID(); +} + +void Sound_start(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + f64 offset = 0.0; + s32 loops = 0; + + u32 discards = 0; + + if (num_args >= 1) + { + ActionVar offset_v; + popVar(app_context, &offset_v); + + if (IS_OBJ_T(offset_v.type)) + { + UNIMPLEMENTED("Sound.start object parameters\n"); + } + + convertNumericToNumber(app_context, &offset_v); + offset = offset_v.f64; + + discards += 1; + } + + if (num_args >= 2) + { + ActionVar loop_v; + popVar(app_context, &loop_v); + + if (IS_OBJ_T(loop_v.type)) + { + UNIMPLEMENTED("Sound.start object parameters\n"); + } + + convertNumericToInteger(app_context, &loop_v); + loops = loop_v.s32; + + discards += 1; + } + + DISCARD_ARGS(num_args - discards); + + for (s32 i = 0; i < loops + 1; ++i) + { + flashbang_put_audio(FBC, EXTDATA(stream_id), EXTDATA(samples), EXTDATA(byte_count)); + } + + RETURN_VOID(); +} + +void Sound_destroy(SWFAppContext* app_context, ASObject* this) +{ + FREE(EXTDATA(samples)); + + flashbang_stop_stream(FBC, EXTDATA(stream_id)); +} \ No newline at end of file diff --git a/src/actionmodern/runtime_api/String_recomp.c b/src/actionmodern/runtime_api/String_recomp.c new file mode 100644 index 0000000..87ed348 --- /dev/null +++ b/src/actionmodern/runtime_api/String_recomp.c @@ -0,0 +1,56 @@ +#include +#include + +#include + +#include + +#include + +#define EXTDATA(member) (((StringData*) this->extra_data)->member) + +void String_new(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + this->extra_data = HALLOC(sizeof(StringData)); + + ActionVar* str = &EXTDATA(str); + + if (num_args > 0) + { + convertString(app_context); + popVar(app_context, str); + + DISCARD_ARGS(num_args - 1); + + char* old_str = str->str; + u32 len = str->str_size + 1; + + str->str = HALLOC(len); + memcpy(str->str, old_str, len); + } + + else + { + str->type = ACTION_STACK_VALUE_STRING; + str->str = NULL; + str->str_size = 0; + str->string_id = 0; + str->owns_memory = false; + } + + RETURN_VOID(); +} + +void String_toString(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + PUSH_VAR(&EXTDATA(str)); +} + +void String_valueOf(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + PUSH_VAR(&EXTDATA(str)); +} \ No newline at end of file diff --git a/src/actionmodern/runtime_api/toplevel.c b/src/actionmodern/runtime_api/toplevel.c new file mode 100644 index 0000000..63807fe --- /dev/null +++ b/src/actionmodern/runtime_api/toplevel.c @@ -0,0 +1,69 @@ +#include + +#include +#include +#include + +#include + +void ASSetPropFlags(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + RETURN_VOID(); +} + +void recompGetLastKey(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + DISCARD_ARGS(num_args); + + u8 key = FBC->last_key_pressed; + + PUSH_INT(key); +} + +void recompSetDisplayScale(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + ActionVar scale_v; + popVar(app_context, &scale_v); + + DISCARD_ARGS(num_args - 1); + + convertNumericToInteger(app_context, &scale_v); + + flashbang_set_display_scale(FBC, scale_v.s32); + + releaseObjectVar(app_context, &scale_v); + + RETURN_VOID(); +} + +void recompSin(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + ActionVar value_v; + popVar(app_context, &value_v); + convertNumericToNumber(app_context, &value_v); + + DISCARD_ARGS(num_args - 1); + + f64 ret = sin(value_v.f64); + + releaseObjectVar(app_context, &value_v); + + PUSH_F64(ret); +} + +void recompCos(SWFAppContext* app_context, ASObject* this, u32 num_args) +{ + ActionVar value_v; + popVar(app_context, &value_v); + convertNumericToNumber(app_context, &value_v); + + DISCARD_ARGS(num_args - 1); + + f64 ret = cos(value_v.f64); + + releaseObjectVar(app_context, &value_v); + + PUSH_F64(ret); +} \ No newline at end of file diff --git a/src/actionmodern/variables.c b/src/actionmodern/variables.c index 5addfe4..c9075d4 100644 --- a/src/actionmodern/variables.c +++ b/src/actionmodern/variables.c @@ -8,60 +8,38 @@ #define VAL(type, x) *((type*) x) -hashmap* var_map = NULL; -ActionVar** var_array = NULL; -size_t var_array_size = 0; - -void initMap() +void initMap(SWFAppContext* app_context) { - var_map = hashmap_create(); -} - -void initVarArray(SWFAppContext* app_context, size_t max_string_id) -{ - var_array_size = max_string_id + 1; - var_array = (ActionVar**) HALLOC(var_array_size*sizeof(ActionVar*)); + app_context->var_ctx.var_map = hashmap_create(); + app_context->var_ctx.next_str_id = app_context->max_string_id; - for (size_t i = 1; i < var_array_size; ++i) + for (size_t i = 0; i < app_context->max_string_id; ++i) { - var_array[i] = (ActionVar*) HALLOC(sizeof(ActionVar)); + hashmap_set(app_context->var_ctx.var_map, app_context->str_table[i], app_context->str_len_table[i], i); } } -static int free_variable_callback(const void* key, size_t ksize, uintptr_t value, void* app_context_void) -{ - SWFAppContext* app_context = (SWFAppContext*) app_context_void; - ActionVar* var = (ActionVar*) value; - - // Free heap-allocated strings - if (var->type == ACTION_STACK_VALUE_STRING && var->owns_memory) - { - FREE(var->heap_ptr); - } - - FREE(var); - return 0; -} - -ActionVar* getVariableById(SWFAppContext* app_context, u32 string_id) +void freeMap(SWFAppContext* app_context) { - return var_array[string_id]; + // Free hashmap + hashmap_free(app_context->var_ctx.var_map); } -ActionVar* getVariable(SWFAppContext* app_context, char* var_name, size_t key_size) +u32 getStringId(SWFAppContext* app_context, char* str, size_t str_size) { - ActionVar* var; + uintptr_t id; - if (hashmap_get(var_map, var_name, key_size, (uintptr_t*) &var)) + if (hashmap_get(app_context->var_ctx.var_map, str, str_size, &id)) { - return var; + return (u32) id; } - var = (ActionVar*) HALLOC(sizeof(ActionVar)); + id = app_context->var_ctx.next_str_id; + app_context->var_ctx.next_str_id += 1; - hashmap_set(var_map, var_name, key_size, (uintptr_t) var); + hashmap_set(app_context->var_ctx.var_map, str, str_size, id); - return var; + return (u32) id; } char* materializeStringList(SWFAppContext* app_context) @@ -86,70 +64,4 @@ char* materializeStringList(SWFAppContext* app_context) *dest = '\0'; return result; -} - -void setVariableWithValue(SWFAppContext* app_context, ActionVar* var) -{ - // Free old string if variable owns memory - if (var->type == ACTION_STACK_VALUE_STRING && var->owns_memory) - { - FREE(var->heap_ptr); - var->owns_memory = false; - } - - ActionStackValueType type = STACK_TOP_TYPE; - - if (type == ACTION_STACK_VALUE_STR_LIST) - { - // Materialize string to heap - char* heap_str = materializeStringList(app_context); - u32 total_size = STACK_TOP_N; - - var->type = ACTION_STACK_VALUE_STRING; - var->str_size = total_size; - var->heap_ptr = heap_str; - var->owns_memory = true; - } - - else - { - // Numeric types and regular strings - store directly - var->type = type; - var->str_size = STACK_TOP_N; - var->value = STACK_TOP_VALUE; - } -} - -void freeMap(SWFAppContext* app_context) -{ - // Free hashmap-based variables - if (var_map) - { - hashmap_iterate(var_map, free_variable_callback, app_context); - hashmap_free(var_map); - var_map = NULL; - } - - // Free array-based variables - if (var_array) - { - for (size_t i = 1; i < var_array_size; i++) - { - if (var_array[i]) - { - // Free heap-allocated strings - if (var_array[i]->type == ACTION_STACK_VALUE_STRING && - var_array[i]->owns_memory) - { - FREE(var_array[i]->heap_ptr); - } - - FREE(var_array[i]); - } - } - - FREE(var_array); - var_array = NULL; - var_array_size = 0; - } } \ No newline at end of file diff --git a/src/apis/rbtree/rbtree.c b/src/apis/rbtree/rbtree.c new file mode 100644 index 0000000..75279a9 --- /dev/null +++ b/src/apis/rbtree/rbtree.c @@ -0,0 +1,221 @@ +#include + +#include + +static s32 node_cmp_string_id(const struct rb_node* n, const void* v) +{ + return *((u32*) v) - ((rbnode*) n)->string_id; +} + +static s64 node_cmp_u64(const struct rb_node* n, const void* v) +{ + return *((u64*) v) - *((u64*) (((char*) n) + sizeof(struct rb_node))); +} + +/** Insert a node into a tree if it doesn't exist, but return it if it does + * + * \param T The red-black tree into which to insert the new node + * + * \param key The key to search for + * + * \param cmp A comparison function to use to order the nodes. + */ +static inline rbnode* rb_tree_get_or_insert(SWFAppContext* app_context, + rbtree* T, const u32* string_id, bool* created, + s32 (*cmp)(const struct rb_node*, const void*)) +{ + /* This function is declared inline in the hopes that the compiler can + * optimize away the comparison function pointer call. + */ + *created = false; + struct rb_node* y = NULL; + struct rb_node* x = T->t.root; + s32 c = 0; + while (x != NULL) + { + y = x; + c = cmp(x, string_id); + if (c < 0) + x = x->left; + else if (c > 0) + x = x->right; + else + { + return (rbnode*) x; + } + } + + rbnode* node = HALLOC(T->struct_size); + node->string_id = *string_id; + rb_tree_insert_at(&T->t, y, (struct rb_node*) node, c < 0); + T->length += 1; + *created = true; + return node; +} + +/** Search the tree for a node + * + * If a node with a matching key exists, the first matching node found will + * be returned. If no matching node exists, NULL is returned. + * + * \param T The red-black tree to search + * + * \param key The key to search for + * + * \param cmp A comparison function to use to order the nodes + */ +static inline rbnode* rb_tree_search_u64(rbtree* T, const void* key, + s64 (*cmp)(const struct rb_node*, const void*)) +{ + /* This function is declared inline in the hopes that the compiler can + * optimize away the comparison function pointer call. + */ + struct rb_node* x = T->t.root; + while (x != NULL) + { + s64 c = cmp(x, key); + if (c < 0) + x = x->left; + else if (c > 0) + x = x->right; + else + return (rbnode*) x; + } + + return NULL; +} + +/** Insert a node into a tree if it doesn't exist, but return it if it does + * This function accepts a u64 key + * + * \param T The red-black tree into which to insert the new node + * + * \param key The (u64) key to search for + * + * \param cmp A comparison function to use to order the nodes. + */ +static inline void* rb_tree_get_or_insert_u64(SWFAppContext* app_context, + rbtree* T, const u64* key, + s64 (*cmp)(const struct rb_node*, const void*)) +{ + /* This function is declared inline in the hopes that the compiler can + * optimize away the comparison function pointer call. + */ + struct rb_node* y = NULL; + struct rb_node* x = T->t.root; + s64 c = 0; + while (x != NULL) + { + y = x; + c = cmp(x, key); + if (c < 0) + x = x->left; + else if (c > 0) + x = x->right; + else + { + return (rbnode*) x; + } + } + + void* node = HALLOC(T->struct_size); + *((u64*) (((char*) node) + sizeof(struct rb_node))) = *key; + rb_tree_insert_at(&T->t, y, (struct rb_node*) node, c < 0); + T->length += 1; + return node; +} + +/** Insert a node into a tree if it doesn't exist + * This function accepts a u64 key + * + * \param T The red-black tree into which to insert the new node + * + * \param key The (u64) key to search for + * + * \param node The node to insert + * + * \param cmp A comparison function to use to order the nodes. + */ +static inline void rb_tree_insert_u64(SWFAppContext* app_context, + rbtree* T, const u64* key, + s64 (*cmp)(const struct rb_node*, const void*)) +{ + /* This function is declared inline in the hopes that the compiler can + * optimize away the comparison function pointer call. + */ + struct rb_node* y = NULL; + struct rb_node* x = T->t.root; + s64 c = 0; + while (x != NULL) + { + y = x; + c = cmp(x, key); + if (c < 0) + x = x->left; + else if (c > 0) + x = x->right; + else + { + return; + } + } + + void* node = HALLOC(T->struct_size); + *((u64*) (((char*) node) + sizeof(struct rb_node))) = *key; + rb_tree_insert_at(&T->t, y, (struct rb_node*) node, c < 0); + T->length += 1; +} + +void rbtree_init(rbtree* t, size_t struct_size) +{ + rb_tree_init((struct rb_tree*) t); + t->length = 0; + t->struct_size = struct_size; +} + +rbnode* rbtree_get(rbtree* t, u32 string_id) +{ + return (rbnode*) rb_tree_search((struct rb_tree*) t, &string_id, node_cmp_string_id); +} + +rbnode* rbtree_get_or_insert(SWFAppContext* app_context, rbtree* t, u32 string_id, bool* created) +{ + return rb_tree_get_or_insert(app_context, t, &string_id, created, node_cmp_string_id); +} + +void* rbtree_get_u64(rbtree* t, u64 key) +{ + return (void*) rb_tree_search_u64(t, &key, node_cmp_u64); +} + +void* rbtree_get_or_insert_u64(SWFAppContext* app_context, rbtree* t, u64 key) +{ + return rb_tree_get_or_insert_u64(app_context, t, &key, node_cmp_u64); +} + +void rbtree_insert_u64(SWFAppContext* app_context, rbtree* t, u64 key) +{ + rb_tree_insert_u64(app_context, t, &key, node_cmp_u64); +} + +void rbtree_remove_u64(SWFAppContext* app_context, rbtree* t, u64 key) +{ + struct rb_node* n = (struct rb_node*) rb_tree_search_u64(t, &key, node_cmp_u64); + + if (n == NULL) + { + return; + } + + rb_tree_remove(&t->t, n); + t->length -= 1; + FREE(n); +} + +void* rbtree_pop_root(rbtree* t) +{ + void* root = (void*) t->t.root; + rb_tree_remove(&t->t, (struct rb_node*) root); + t->length -= 1; + return root; +} \ No newline at end of file diff --git a/src/apis/swap-vector/swap_vector.c b/src/apis/swap-vector/swap_vector.c new file mode 100644 index 0000000..4c6bb8f --- /dev/null +++ b/src/apis/swap-vector/swap_vector.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include + +#define UP(x) VAL(uintptr_t, x) + +void svec_init(SWFAppContext* app_context, SwapVector* v) +{ + svec_sized_init(app_context, v, sizeof(uintptr_t)); +} + +void svec_sized_init(SWFAppContext* app_context, SwapVector* v, size_t struct_size) +{ + v->length = 0; + v->length_bytes = 0; + v->struct_size = struct_size; + v->arena_capacity = 64*struct_size; + v->arena = HALLOC(v->arena_capacity); +} + +void svec_push(SWFAppContext* app_context, SwapVector* v, uintptr_t value) +{ + svec_bump(app_context, v); + + char* this = &v->arena[v->length_bytes - v->struct_size]; + UP(this) = value; +} + +void svec_bump(SWFAppContext* app_context, SwapVector* v) +{ + v->length += 1; + v->length_bytes += v->struct_size; + ENSURE_SIZE(v->arena, v->length_bytes, v->arena_capacity, 1); +} + +void svec_remove(SwapVector* v, size_t index) +{ + v->length -= 1; + v->length_bytes -= v->struct_size; + + if (UNLIKELY(v->length_bytes == 0)) + { + return; + } + + char* this = &v->arena[v->length_bytes]; + v->data[index] = UP(this); +} + +void svec_pop(SwapVector* v) +{ + v->length -= 1; + v->length_bytes -= v->struct_size; +} + +void svec_clear(SwapVector* v) +{ + v->length = 0; + v->length_bytes = 0; +} + +void svec_release(SWFAppContext* app_context, SwapVector* v) +{ + FREE(v->arena); +} \ No newline at end of file diff --git a/src/flashbang/flashbang.c b/src/flashbang/flashbang.c index 3c2eaef..c995c1b 100644 --- a/src/flashbang/flashbang.c +++ b/src/flashbang/flashbang.c @@ -2,11 +2,19 @@ #include #include +#include + #include +#include +#include #include +#include #include #include +#define SHAPE_DATA_SIZE (context->shape_data_exists ? context->shape_data_size : 0) +#define UNINV_SIZE (context->uninv_mat_data_size != 4 ? context->uninv_mat_data_size : 0) + int once = 0; const float identity[16] = @@ -53,9 +61,22 @@ const float identity_cxform[20] = 0.0f }; +const SDL_AudioSpec spec = +{ + .format = SDL_AUDIO_S16LE, + .channels = 2, + .freq = 44100, +}; + +void flashbang_reset_currents(FlashbangContext* context, SWFAppContext* app_context) +{ + context->current_vertex_offset = SHAPE_DATA_SIZE; + context->current_uninv_offset = UNINV_SIZE; +} + void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) { - if (!once && !SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) + if (!once && !SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) { SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); exit(EXIT_FAILURE); @@ -63,8 +84,23 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) once = 1; + context->scale = 1; + context->current_bitmap = 0; + context->audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL); + + context->audio_stream_capacity = 1; + context->audio_streams = HALLOC(context->audio_stream_capacity*sizeof(FlashbangAudioStream)); + + for (size_t i = 0; i < context->audio_stream_capacity; ++i) + { + context->audio_streams[i].stream = SDL_CreateAudioStream(&spec, NULL); + SDL_BindAudioStream(context->audio_device, context->audio_streams[i].stream); + context->audio_streams[i].playing = false; + context->audio_streams[i].stopping = false; + } + // create a window context->window = SDL_CreateWindow("TestSWFRecompiled", context->width, context->height, SDL_WINDOW_RESIZABLE); @@ -83,17 +119,17 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) exit(EXIT_FAILURE); } - SDL_GPUTransferBuffer* vertex_transfer_buffer; SDL_GPUTransferBuffer* xform_transfer_buffer; SDL_GPUTransferBuffer* color_transfer_buffer; - SDL_GPUTransferBuffer* uninv_mat_transfer_buffer; SDL_GPUTransferBuffer* gradient_transfer_buffer; SDL_GPUTransferBuffer* cxform_transfer_buffer; SDL_GPUTransferBuffer* dummy_transfer_buffer; + context->allocated_vertex_size = context->shape_data_size; + // create the vertex buffer SDL_GPUBufferCreateInfo bufferInfo = {0}; - bufferInfo.size = (Uint32) context->shape_data_size; + bufferInfo.size = (Uint32) context->allocated_vertex_size; bufferInfo.usage = SDL_GPU_BUFFERUSAGE_VERTEX; context->vertex_buffer = SDL_CreateGPUBuffer(context->device, &bufferInfo); @@ -107,13 +143,15 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) bufferInfo.usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ; context->color_buffer = SDL_CreateGPUBuffer(context->device, &bufferInfo); + context->allocated_uninv_size = context->uninv_mat_data_size; + // create a storage buffer for gradient matrices - bufferInfo.size = (Uint32) context->uninv_mat_data_size; + bufferInfo.size = (Uint32) context->allocated_uninv_size; bufferInfo.usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ; context->uninv_mat_buffer = SDL_CreateGPUBuffer(context->device, &bufferInfo); // create a storage buffer for inverse gradient matrices - bufferInfo.size = (Uint32) context->uninv_mat_data_size; + bufferInfo.size = (Uint32) context->allocated_uninv_size; bufferInfo.usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ | SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE; context->inv_mat_buffer = SDL_CreateGPUBuffer(context->device, &bufferInfo); @@ -127,11 +165,13 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) bufferInfo.usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ; context->cxform_buffer = SDL_CreateGPUBuffer(context->device, &bufferInfo); + context->allocated_vertex_transfer_size = context->allocated_vertex_size; + // create a transfer buffer to upload to the vertex buffer SDL_GPUTransferBufferCreateInfo transfer_info = {0}; - transfer_info.size = (Uint32) context->shape_data_size; + transfer_info.size = (Uint32) context->allocated_vertex_transfer_size; transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; - vertex_transfer_buffer = SDL_CreateGPUTransferBuffer(context->device, &transfer_info); + context->vertex_transfer_buffer = SDL_CreateGPUTransferBuffer(context->device, &transfer_info); // create a transfer buffer to upload to the transform buffer transfer_info.size = (Uint32) context->transform_data_size; @@ -146,7 +186,7 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) // create a transfer buffer to upload to the gradient matrix buffer transfer_info.size = (Uint32) context->uninv_mat_data_size; transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; - uninv_mat_transfer_buffer = SDL_CreateGPUTransferBuffer(context->device, &transfer_info); + context->uninv_transfer_buffer = SDL_CreateGPUTransferBuffer(context->device, &transfer_info); // create a transfer buffer to upload to the gradient texture transfer_info.size = (Uint32) context->gradient_data_size; @@ -189,6 +229,16 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) SDL_GPUTextureCreateInfo texture_info = {0}; + texture_info.type = SDL_GPU_TEXTURETYPE_2D; + texture_info.format = SDL_GetGPUSwapchainTextureFormat(context->device, context->window); + texture_info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; + texture_info.sample_count = SDL_GPU_SAMPLECOUNT_1; + texture_info.width = context->width; + texture_info.height = context->height; + texture_info.layer_count_or_depth = 1; + texture_info.num_levels = 1; + context->target_texture = SDL_CreateGPUTexture(context->device, &texture_info); + if (num_gradient_textures) { texture_info.type = SDL_GPU_TEXTURETYPE_2D_ARRAY; @@ -228,12 +278,12 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) compute_pipeline_info.num_readonly_storage_textures = 0; compute_pipeline_info.num_readwrite_storage_buffers = 1; compute_pipeline_info.num_readwrite_storage_textures = 0; - compute_pipeline_info.num_uniform_buffers = 0; + compute_pipeline_info.num_uniform_buffers = 1; compute_pipeline_info.threadcount_x = 64; compute_pipeline_info.threadcount_y = 1; compute_pipeline_info.threadcount_z = 1; - SDL_GPUComputePipeline* compute_pipeline = SDL_CreateGPUComputePipeline(context->device, &compute_pipeline_info); + context->inv_pipeline = SDL_CreateGPUComputePipeline(context->device, &compute_pipeline_info); // free the file SDL_free(compute_code); @@ -339,39 +389,19 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) // create the pipeline context->graphics_pipeline = SDL_CreateGPUGraphicsPipeline(context->device, &pipeline_info); - texture_info.type = SDL_GPU_TEXTURETYPE_2D; - texture_info.format = SDL_GetGPUSwapchainTextureFormat(context->device, context->window); - texture_info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; - texture_info.sample_count = sample_count; - texture_info.width = context->width; - texture_info.height = context->height; - texture_info.layer_count_or_depth = 1; - texture_info.num_levels = 1; - context->msaa_texture = SDL_CreateGPUTexture(context->device, &texture_info); - - texture_info.type = SDL_GPU_TEXTURETYPE_2D; - texture_info.format = SDL_GetGPUSwapchainTextureFormat(context->device, context->window); - texture_info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; - texture_info.sample_count = SDL_GPU_SAMPLECOUNT_1; - texture_info.width = context->width; - texture_info.height = context->height; - texture_info.layer_count_or_depth = 1; - texture_info.num_levels = 1; - context->resolve_texture = SDL_CreateGPUTexture(context->device, &texture_info); - // we don't need to store the shaders after creating the pipeline SDL_ReleaseGPUShader(context->device, vertex_shader); SDL_ReleaseGPUShader(context->device, fragment_shader); // upload all DefineShape vertex data once on init - char* buffer = (char*) SDL_MapGPUTransferBuffer(context->device, vertex_transfer_buffer, 0); + char* buffer = (char*) SDL_MapGPUTransferBuffer(context->device, context->vertex_transfer_buffer, 0); for (size_t i = 0; i < context->shape_data_size; ++i) { buffer[i] = context->shape_data[i]; } - SDL_UnmapGPUTransferBuffer(context->device, vertex_transfer_buffer); + SDL_UnmapGPUTransferBuffer(context->device, context->vertex_transfer_buffer); // upload all PlaceObject transform data once on init buffer = (char*) SDL_MapGPUTransferBuffer(context->device, xform_transfer_buffer, 0); @@ -409,14 +439,14 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) if (num_gradient_textures || context->bitmap_count) { // upload all DefineShape gradient/bitmap matrix data once on init - buffer = (char*) SDL_MapGPUTransferBuffer(context->device, uninv_mat_transfer_buffer, 0); + buffer = (char*) SDL_MapGPUTransferBuffer(context->device, context->uninv_transfer_buffer, 0); for (size_t i = 0; i < context->uninv_mat_data_size; ++i) { buffer[i] = context->uninv_mat_data[i]; } - SDL_UnmapGPUTransferBuffer(context->device, uninv_mat_transfer_buffer); + SDL_UnmapGPUTransferBuffer(context->device, context->uninv_transfer_buffer); } SDL_GPUSamplerCreateInfo sampler_create_info = {0}; @@ -482,7 +512,7 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) // where is the data SDL_GPUTransferBufferLocation location = {0}; - location.transfer_buffer = vertex_transfer_buffer; + location.transfer_buffer = context->vertex_transfer_buffer; location.offset = 0; // start from the beginning // where to upload the data @@ -573,7 +603,7 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) if (num_gradient_textures || context->bitmap_count) { // where is the data - location.transfer_buffer = uninv_mat_transfer_buffer; + location.transfer_buffer = context->uninv_transfer_buffer; location.offset = 0; // where to upload the data @@ -617,6 +647,8 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) if (num_gradient_textures || context->bitmap_count) { + size_t uninv_count = num_gradient_textures + context->bitmap_count; + context->command_buffer = SDL_AcquireGPUCommandBuffer(context->device); SDL_GPUStorageBufferReadWriteBinding compute_buffer_bindings[1] = {0}; @@ -624,32 +656,44 @@ void flashbang_init(FlashbangContext* context, SWFAppContext* app_context) compute_buffer_bindings[0].buffer = context->inv_mat_buffer; compute_buffer_bindings[0].cycle = false; + u32 start_offset = 0; + SDL_PushGPUComputeUniformData(context->command_buffer, 0, &start_offset, sizeof(u32)); + SDL_GPUComputePass* compute_pass = SDL_BeginGPUComputePass(context->command_buffer, NULL, 0, compute_buffer_bindings, 1); - SDL_BindGPUComputePipeline(compute_pass, compute_pipeline); - SDL_BindGPUComputeStorageBuffers(compute_pass, 0, &context->uninv_mat_buffer, 1); - SDL_DispatchGPUCompute(compute_pass, (Uint32) (num_gradient_textures/64 + 1), 1, 1); // "we have ceil at home" + SDL_BindGPUComputePipeline(compute_pass, context->inv_pipeline); + SDL_BindGPUComputeStorageBuffers(compute_pass, 0, (SDL_GPUBuffer**) &context->uninv_mat_buffer, 1); + SDL_DispatchGPUCompute(compute_pass, (Uint32) (uninv_count/64 + 1), 1, 1); // "we have ceil at home" SDL_EndGPUComputePass(compute_pass); // submit the command buffer SDL_SubmitGPUCommandBuffer(context->command_buffer); } - SDL_ReleaseGPUComputePipeline(context->device, compute_pipeline); - - SDL_ReleaseGPUTransferBuffer(context->device, vertex_transfer_buffer); SDL_ReleaseGPUTransferBuffer(context->device, xform_transfer_buffer); SDL_ReleaseGPUTransferBuffer(context->device, color_transfer_buffer); - SDL_ReleaseGPUTransferBuffer(context->device, uninv_mat_transfer_buffer); SDL_ReleaseGPUTransferBuffer(context->device, gradient_transfer_buffer); SDL_ReleaseGPUTransferBuffer(context->device, cxform_transfer_buffer); SDL_ReleaseGPUTransferBuffer(context->device, dummy_transfer_buffer); + + flashbang_reset_currents(context, app_context); + + context->vertex_buffer_to_free = NULL; + context->transfer_buffer_to_free = NULL; + + context->uninv_buffer_to_free = NULL; + context->inv_buffer_to_free = NULL; + + SDL_SetGPUAllowedFramesInFlight(context->device, 1); + SDL_SetGPUSwapchainParameters(context->device, context->window, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_IMMEDIATE); + + triInit(app_context); } -int flashbang_poll() +int flashbang_poll(FlashbangContext* context, SWFAppContext* app_context) { SDL_Event evt; - if (SDL_PollEvent(&evt)) + while (SDL_PollEvent(&evt)) { switch (evt.type) { @@ -658,12 +702,139 @@ int flashbang_poll() { return 1; } + + case SDL_EVENT_KEY_DOWN: + { + u8 key; + + switch (evt.key.key) + { + case SDLK_ESCAPE: key = 27; break; + case SDLK_LEFT: key = 37; break; + case SDLK_UP: key = 38; break; + case SDLK_RIGHT: key = 39; break; + case SDLK_DOWN: key = 40; break; + default: key = 0; break; + } + + if (key != 0) + { + context->last_key_pressed = key; + + ActionVar Key_v; + getPropertyVar(app_context, _global, STR_ID_KEY, NULL, 0, &Key_v); + + getAndCallMethod(app_context, Key_v.object, STR_ID_FIRE_LISTENERS_DOWN, 0); + POP(); + } + + break; + } + + case SDL_EVENT_KEY_UP: + { + u8 key; + + if (evt.key.repeat) + { + break; + } + + switch (evt.key.key) + { + case SDLK_ESCAPE: key = 27; break; + case SDLK_LEFT: key = 37; break; + case SDLK_UP: key = 38; break; + case SDLK_RIGHT: key = 39; break; + case SDLK_DOWN: key = 40; break; + default: key = 0; break; + } + + if (key != 0) + { + context->last_key_pressed = key; + + ActionVar Key_v; + getPropertyVar(app_context, _global, STR_ID_KEY, NULL, 0, &Key_v); + + getAndCallMethod(app_context, Key_v.object, STR_ID_FIRE_LISTENERS_UP, 0); + POP(); + } + + break; + } + } + } + + for (size_t i = 0; i < context->audio_stream_capacity; ++i) + { + if (!context->audio_streams[i].stopping) + { + continue; + } + + FlashbangAudioStream* stream = &context->audio_streams[i]; + + if (SDL_GetAudioStreamQueued(stream->stream) == 0) + { + SDL_ClearAudioStream(stream->stream); + stream->playing = false; + stream->stopping = false; } } return 0; } +size_t flashbang_create_audio_stream(FlashbangContext* context, SWFAppContext* app_context) +{ + size_t available_stream = 0; + + while (available_stream < context->audio_stream_capacity) + { + if (!context->audio_streams[available_stream].playing) + { + break; + } + + available_stream += 1; + } + + if (available_stream == context->audio_stream_capacity) + { + ENSURE_SIZE(context->audio_streams, + available_stream + 1, + context->audio_stream_capacity, + sizeof(FlashbangAudioStream)); + + for (size_t i = available_stream; i < context->audio_stream_capacity; ++i) + { + context->audio_streams[i].stream = SDL_CreateAudioStream(&spec, NULL); + SDL_BindAudioStream(context->audio_device, context->audio_streams[i].stream); + context->audio_streams[i].playing = false; + context->audio_streams[i].stopping = false; + } + } + + context->audio_streams[available_stream].playing = true; + + return available_stream; +} + +void flashbang_put_audio(FlashbangContext* context, size_t stream_id, char* buffer, size_t size) +{ + SDL_PutAudioStreamData(context->audio_streams[stream_id].stream, buffer, (int) size); + SDL_ResumeAudioDevice(context->audio_device); +} + +void flashbang_stop_stream(FlashbangContext* context, size_t stream_id) +{ + FlashbangAudioStream* stream = &context->audio_streams[stream_id]; + + SDL_FlushAudioStream(stream->stream); + stream->stopping = true; +} + void flashbang_set_window_background(FlashbangContext* context, u8 r, u8 g, u8 b) { context->red = r; @@ -675,15 +846,15 @@ void flashbang_upload_bitmap(FlashbangContext* context, size_t offset, size_t si { u32* buffer = (u32*) SDL_MapGPUTransferBuffer(context->device, context->bitmap_transfer, 0); - size_t bitmap_global_size = (context->bitmap_highest_w + 1)*(context->bitmap_highest_h + 1); + size_t bitmap_global_size = (context->bitmap_highest_w)*(context->bitmap_highest_h); size_t current_buffer_offset = bitmap_global_size*context->current_bitmap; - for (size_t y = 0; y < height + 1; ++y) + for (size_t y = 0; y < height; ++y) { - for (size_t x = 0; x < width + 1; ++x) + for (size_t x = 0; x < width; ++x) { - size_t buffer_pixel = current_buffer_offset + y*(context->bitmap_highest_w + 1) + x; - size_t bitmap_pixel = y*width + x; + size_t buffer_pixel = current_buffer_offset + y*(context->bitmap_highest_w) + x; + size_t bitmap_pixel = offset/4 + y*width + x; if (x == width) { @@ -692,13 +863,13 @@ void flashbang_upload_bitmap(FlashbangContext* context, size_t offset, size_t si break; } - buffer[buffer_pixel] = ((u32*) context->bitmap_data)[bitmap_pixel - 1]; + buffer[buffer_pixel] = ((u32*) context->bitmap_data)[bitmap_pixel]; break; } else if (y == height) { - buffer[buffer_pixel - context->bitmap_highest_w - 1] = ((u32*) context->bitmap_data)[bitmap_pixel - width]; + buffer[buffer_pixel - context->bitmap_highest_w] = ((u32*) context->bitmap_data)[bitmap_pixel - width]; continue; } @@ -731,8 +902,8 @@ void flashbang_finalize_bitmaps(FlashbangContext* context) texture_info.type = SDL_GPU_TEXTURETYPE_2D_ARRAY; texture_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; texture_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; - texture_info.width = (Uint32) (context->bitmap_highest_w + 1); - texture_info.height = (Uint32) (context->bitmap_highest_h + 1); + texture_info.width = (Uint32) (context->bitmap_highest_w); + texture_info.height = (Uint32) (context->bitmap_highest_h); texture_info.layer_count_or_depth = (Uint32) context->bitmap_count; texture_info.num_levels = 1; texture_info.sample_count = SDL_GPU_SAMPLECOUNT_1; @@ -741,9 +912,9 @@ void flashbang_finalize_bitmaps(FlashbangContext* context) SDL_GPUSamplerCreateInfo sampler_create_info = {0}; - sampler_create_info.min_filter = SDL_GPU_FILTER_LINEAR; - sampler_create_info.mag_filter = SDL_GPU_FILTER_LINEAR; - sampler_create_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; + sampler_create_info.min_filter = SDL_GPU_FILTER_NEAREST; + sampler_create_info.mag_filter = SDL_GPU_FILTER_NEAREST; + sampler_create_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST; sampler_create_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; sampler_create_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; sampler_create_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; @@ -767,7 +938,7 @@ void flashbang_finalize_bitmaps(FlashbangContext* context) // start a copy pass SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(context->command_buffer); - size_t bitmap_size = 4*(context->bitmap_highest_w + 1)*(context->bitmap_highest_h + 1); + size_t bitmap_size = 4*(context->bitmap_highest_w)*(context->bitmap_highest_h); for (size_t i = 0; i < context->bitmap_count; ++i) { @@ -786,8 +957,8 @@ void flashbang_finalize_bitmaps(FlashbangContext* context) texture_region.x = 0; texture_region.y = 0; texture_region.z = 0; - texture_region.w = (Uint32) (context->bitmap_highest_w + 1); - texture_region.h = (Uint32) (context->bitmap_highest_h + 1); + texture_region.w = (Uint32) (context->bitmap_highest_w); + texture_region.h = (Uint32) (context->bitmap_highest_h); texture_region.d = 1; // upload a bitmap @@ -816,13 +987,42 @@ void flashbang_finalize_bitmaps(FlashbangContext* context) SDL_ReleaseGPUFence(context->device, fence); } -void flashbang_open_pass(FlashbangContext* context) +void flashbang_set_display_scale(FlashbangContext* context, u8 scale) +{ + u8 old_scale = context->scale; + context->scale = scale; + + u32 w = scale*context->width; + u32 h = scale*context->height; + + int x; + int y; + SDL_GetWindowPosition(context->window, &x, &y); + SDL_SetWindowSize(context->window, w, h); + + int sign = 1 - 2*(old_scale > scale); + int old_w = sign*context->width; + int old_h = sign*context->height; + SDL_SetWindowPosition(context->window, x - old_w, y - old_h); + + SDL_SyncWindow(context->window); +} + +bool flashbang_open_pass(FlashbangContext* context, SWFAppContext* app_context) { // acquire the command buffer context->command_buffer = SDL_AcquireGPUCommandBuffer(context->device); assert(context->command_buffer != NULL); + // get the swapchain texture + SDL_WaitAndAcquireGPUSwapchainTexture(context->command_buffer, context->window, (SDL_GPUTexture**) &context->swapchain, &context->swapchain_width, &context->swapchain_height); + + if (context->swapchain == NULL) + { + return false; + } + // create the color target SDL_GPUColorTargetInfo colorTargetInfo = {0}; colorTargetInfo.clear_color.r = context->red/255.0f; @@ -830,9 +1030,8 @@ void flashbang_open_pass(FlashbangContext* context) colorTargetInfo.clear_color.b = context->blue/255.0f; colorTargetInfo.clear_color.a = 255/255.0f; colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR; - colorTargetInfo.store_op = SDL_GPU_STOREOP_RESOLVE; - colorTargetInfo.texture = context->msaa_texture; - colorTargetInfo.resolve_texture = context->resolve_texture; + colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE; + colorTargetInfo.texture = context->target_texture; // begin a render pass context->render_pass = SDL_BeginGPURenderPass(context->command_buffer, &colorTargetInfo, 1, NULL); @@ -851,10 +1050,10 @@ void flashbang_open_pass(FlashbangContext* context) SDL_PushGPUFragmentUniformData(context->command_buffer, 0, &identity_id, sizeof(u32)); SDL_PushGPUFragmentUniformData(context->command_buffer, 1, identity_cxform, 20*sizeof(float)); - SDL_BindGPUVertexStorageBuffers(context->render_pass, 0, &context->xform_buffer, 1); - SDL_BindGPUVertexStorageBuffers(context->render_pass, 1, &context->color_buffer, 1); - SDL_BindGPUVertexStorageBuffers(context->render_pass, 2, &context->inv_mat_buffer, 1); - SDL_BindGPUVertexStorageBuffers(context->render_pass, 3, &context->bitmap_sizes_buffer, 1); + SDL_BindGPUVertexStorageBuffers(context->render_pass, 0, (SDL_GPUBuffer**) &context->xform_buffer, 1); + SDL_BindGPUVertexStorageBuffers(context->render_pass, 1, (SDL_GPUBuffer**) &context->color_buffer, 1); + SDL_BindGPUVertexStorageBuffers(context->render_pass, 2, (SDL_GPUBuffer**) &context->inv_mat_buffer, 1); + SDL_BindGPUVertexStorageBuffers(context->render_pass, 3, (SDL_GPUBuffer**) &context->bitmap_sizes_buffer, 1); size_t sizeof_gradient = 256*4*sizeof(float); size_t num_gradient_textures = context->gradient_data_size/sizeof_gradient; @@ -886,7 +1085,223 @@ void flashbang_open_pass(FlashbangContext* context) } SDL_BindGPUFragmentSamplers(context->render_pass, 0, sampler_bindings, 2); - SDL_BindGPUFragmentStorageBuffers(context->render_pass, 0, &context->cxform_buffer, 1); + SDL_BindGPUFragmentStorageBuffers(context->render_pass, 0, (SDL_GPUBuffer**) &context->cxform_buffer, 1); + + return true; +} + +u32 flashbang_allocate_vertices(FlashbangContext* context, u32 num_verts) +{ + u32 this_offset = (u32) context->current_vertex_offset; + + context->current_vertex_offset += 4*sizeof(u32)*num_verts; + + return this_offset/(4*sizeof(u32)); +} + +u32 flashbang_allocate_uninv(FlashbangContext* context) +{ + u32 this_offset = (u32) context->current_uninv_offset; + + context->current_uninv_offset += 16*sizeof(float); + + return this_offset; +} + +void flashbang_ensure_size_far_vertex(FlashbangContext* context, u32 size) +{ + if (UNLIKELY(context->allocated_vertex_size == 0)) + { + context->allocated_vertex_size = 64*4*sizeof(u32); + } + + size_t old_allocated_size = context->allocated_vertex_size; + + if (LIKELY(old_allocated_size >= size)) + { + return; + } + + while (context->allocated_vertex_size < size) + { + context->allocated_vertex_size <<= 1; + } + + // create a new vertex buffer + SDL_GPUBufferCreateInfo bufferInfo = {0}; + bufferInfo.size = (Uint32) context->allocated_vertex_size; + bufferInfo.usage = SDL_GPU_BUFFERUSAGE_VERTEX; + SDL_GPUBuffer* new_vertex_buffer = SDL_CreateGPUBuffer(context->device, &bufferInfo); + + SDL_GPUBufferLocation old = {0}; + old.buffer = context->vertex_buffer; + old.offset = 0; + + SDL_GPUBufferLocation new = {0}; + new.buffer = new_vertex_buffer; + new.offset = 0; + + SDL_CopyGPUBufferToBuffer(context->copy_pass, &old, &new, (Uint32) context->shape_data_size, false); + + context->vertex_buffer_to_free = context->vertex_buffer; + context->vertex_buffer = new_vertex_buffer; + + context->transfer_buffer_to_free = context->vertex_transfer_buffer; + + // create a transfer buffer to upload to the vertex buffer + SDL_GPUTransferBufferCreateInfo transfer_info = {0}; + transfer_info.size = (Uint32) context->allocated_vertex_size; + transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + context->vertex_transfer_buffer = SDL_CreateGPUTransferBuffer(context->device, &transfer_info); +} + +void flashbang_ensure_size_far_uninv(FlashbangContext* context, u32 size) +{ + size_t old_allocated_size = context->allocated_uninv_size; + + if (LIKELY(old_allocated_size >= size)) + { + return; + } + + while (context->allocated_uninv_size < size) + { + context->allocated_uninv_size <<= 1; + } + + SDL_GPUBufferCreateInfo bufferInfo = {0}; + + // create a new uninv buffer + bufferInfo.size = (Uint32) context->allocated_uninv_size; + bufferInfo.usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ; + SDL_GPUBuffer* new_uninv_buffer = SDL_CreateGPUBuffer(context->device, &bufferInfo); + + SDL_GPUBufferLocation old = {0}; + old.buffer = context->uninv_mat_buffer; + old.offset = 0; + + SDL_GPUBufferLocation new = {0}; + new.buffer = new_uninv_buffer; + new.offset = 0; + + SDL_CopyGPUBufferToBuffer(context->copy_pass, &old, &new, (Uint32) context->uninv_mat_data_size, false); + + // create a new inv buffer + bufferInfo.size = (Uint32) context->allocated_uninv_size; + bufferInfo.usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ | SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE; + SDL_GPUBuffer* new_inv_buffer = SDL_CreateGPUBuffer(context->device, &bufferInfo); + + old.buffer = context->inv_mat_buffer; + old.offset = 0; + + new.buffer = new_inv_buffer; + new.offset = 0; + + SDL_CopyGPUBufferToBuffer(context->copy_pass, &old, &new, (Uint32) context->uninv_mat_data_size, false); + + // create a transfer buffer to upload to the uninv buffer + SDL_GPUTransferBufferCreateInfo transfer_info = {0}; + transfer_info.size = (Uint32) context->allocated_uninv_size; + transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + context->uninv_transfer_buffer = SDL_CreateGPUTransferBuffer(context->device, &transfer_info); + + context->uninv_buffer_to_free = context->uninv_mat_buffer; + context->inv_buffer_to_free = context->inv_mat_buffer; + + context->uninv_mat_buffer = new_uninv_buffer; + context->inv_mat_buffer = new_inv_buffer; +} + +void flashbang_open_vertex_transfer(FlashbangContext* context, size_t total_vertex_count, size_t total_uninv_count) +{ + // start a copy pass + context->copy_pass = SDL_BeginGPUCopyPass(context->command_buffer); + + size_t vertex_upload_size = 4*sizeof(u32)*total_vertex_count; + + flashbang_ensure_size_far_vertex(context, (u32) (context->current_vertex_offset + vertex_upload_size)); + + context->vertex_buffer_mapped = (char*) SDL_MapGPUTransferBuffer(context->device, context->vertex_transfer_buffer, 0); + + if (total_uninv_count != 0) + { + size_t uninv_upload_size = 16*sizeof(u32)*total_uninv_count; + flashbang_ensure_size_far_uninv(context, (u32) (context->current_uninv_offset + uninv_upload_size)); + context->uninv_buffer_mapped = (char*) SDL_MapGPUTransferBuffer(context->device, context->uninv_transfer_buffer, 0); + } + + context->uninvs_uploading_count = total_uninv_count; +} + +void flashbang_upload_vertices(FlashbangContext* context, u32* data, u32 vertex_offset, u32 vertex_count) +{ + size_t upload_offset = 4*sizeof(u32)*vertex_offset; + size_t upload_size = 4*sizeof(u32)*vertex_count; + + memcpy(context->vertex_buffer_mapped + upload_offset, data, upload_size); +} + +void flashbang_upload_uninv(FlashbangContext* context, float* uninv, u32 offset) +{ + size_t upload_size = 16*sizeof(float); + + memcpy(context->uninv_buffer_mapped + offset - UNINV_SIZE, uninv, upload_size); +} + +void flashbang_close_vertex_transfer(FlashbangContext* context) +{ + // where is the data + SDL_GPUTransferBufferLocation location = {0}; + location.transfer_buffer = context->vertex_transfer_buffer; + location.offset = 0; // start from the beginning + + // where to upload the data + SDL_GPUBufferRegion region = {0}; + region.buffer = context->vertex_buffer; + region.size = (Uint32) (context->current_vertex_offset - SHAPE_DATA_SIZE); // size of the data in bytes + region.offset = (Uint32) SHAPE_DATA_SIZE; // begin writing from after the existing shape data + + // upload vertices + SDL_UploadToGPUBuffer(context->copy_pass, &location, ®ion, false); + + SDL_UnmapGPUTransferBuffer(context->device, context->vertex_transfer_buffer); + + if (context->uninvs_uploading_count > 0) + { + // where is the data + location.transfer_buffer = context->uninv_transfer_buffer; + location.offset = 0; // start from the beginning + + // where to upload the data + region.buffer = context->uninv_mat_buffer; + region.size = (Uint32) (context->current_uninv_offset - UNINV_SIZE); // size of the data in bytes + region.offset = (Uint32) UNINV_SIZE; // begin writing from after the existing matrix data + + // upload matrices + SDL_UploadToGPUBuffer(context->copy_pass, &location, ®ion, false); + + SDL_UnmapGPUTransferBuffer(context->device, context->uninv_transfer_buffer); + } + + // end the copy pass + SDL_EndGPUCopyPass(context->copy_pass); + + if (context->uninvs_uploading_count > 0) + { + SDL_GPUStorageBufferReadWriteBinding compute_buffer_bindings[1] = {0}; + + compute_buffer_bindings[0].buffer = context->inv_mat_buffer; + compute_buffer_bindings[0].cycle = false; + + u32 start_offset = (u32) UNINV_SIZE/(16*sizeof(float)); + SDL_PushGPUComputeUniformData(context->command_buffer, 0, &start_offset, sizeof(u32)); + + SDL_GPUComputePass* compute_pass = SDL_BeginGPUComputePass(context->command_buffer, NULL, 0, compute_buffer_bindings, 1); + SDL_BindGPUComputePipeline(compute_pass, context->inv_pipeline); + SDL_BindGPUComputeStorageBuffers(compute_pass, 0, (SDL_GPUBuffer**) &context->uninv_mat_buffer, 1); + SDL_DispatchGPUCompute(compute_pass, (Uint32) (context->uninvs_uploading_count/64 + 1), 1, 1); // "we have ceil at home" + SDL_EndGPUComputePass(compute_pass); + } } void flashbang_upload_extra_transform_id(FlashbangContext* context, u32 transform_id) @@ -909,7 +1324,7 @@ void flashbang_upload_cxform(FlashbangContext* context, float* cxform) SDL_PushGPUFragmentUniformData(context->command_buffer, 1, cxform, 20*sizeof(float)); } -void flashbang_draw_shape(FlashbangContext* context, size_t offset, size_t num_verts, u32 transform_id) +void flashbang_draw_shape(FlashbangContext* context, u32 offset, u32 num_verts, u32 transform_id) { // bind the vertex buffer SDL_GPUBufferBinding buffer_bindings[1]; @@ -924,20 +1339,18 @@ void flashbang_draw_shape(FlashbangContext* context, size_t offset, size_t num_v SDL_DrawGPUPrimitives(context->render_pass, (Uint32) num_verts, 1, 0, 0); } -void flashbang_close_pass(FlashbangContext* context) +void flashbang_close_pass(FlashbangContext* context, SWFAppContext* app_context) { + if (context->swapchain == NULL) + { + return; + } + // end the render pass SDL_EndGPURenderPass(context->render_pass); - // get the swapchain texture - SDL_GPUTexture* swapchain_texture; - Uint32 width, height; - SDL_WaitAndAcquireGPUSwapchainTexture(context->command_buffer, context->window, &swapchain_texture, &width, &height); - - assert(swapchain_texture != NULL); - SDL_GPUBlitInfo blit_info = {0}; - blit_info.source.texture = context->resolve_texture; + blit_info.source.texture = context->target_texture; blit_info.source.mip_level = 0; blit_info.source.layer_or_depth_plane = 0; blit_info.source.x = 0; @@ -945,29 +1358,69 @@ void flashbang_close_pass(FlashbangContext* context) blit_info.source.w = context->width; blit_info.source.h = context->height; - blit_info.destination.texture = swapchain_texture; + blit_info.destination.texture = context->swapchain; blit_info.destination.mip_level = 0; blit_info.destination.layer_or_depth_plane = 0; blit_info.destination.x = 0; blit_info.destination.y = 0; - blit_info.destination.w = width; - blit_info.destination.h = height; + blit_info.destination.w = context->swapchain_width; + blit_info.destination.h = context->swapchain_height; blit_info.load_op = SDL_GPU_LOADOP_DONT_CARE; blit_info.flip_mode = SDL_FLIP_NONE; - blit_info.filter = SDL_GPU_FILTER_LINEAR; + blit_info.filter = SDL_GPU_FILTER_NEAREST; blit_info.cycle = false; SDL_BlitGPUTexture(context->command_buffer, &blit_info); // submit the command buffer SDL_SubmitGPUCommandBuffer(context->command_buffer); + + flashbang_reset_currents(context, app_context); + + // TODO: allow for freeing multiple buffers of each type + + if (context->vertex_buffer_to_free != NULL) + { + SDL_ReleaseGPUBuffer(context->device, context->vertex_buffer_to_free); + context->vertex_buffer_to_free = NULL; + } + + if (context->transfer_buffer_to_free != NULL) + { + SDL_ReleaseGPUTransferBuffer(context->device, context->transfer_buffer_to_free); + context->transfer_buffer_to_free = NULL; + } + + if (context->uninv_buffer_to_free != NULL) + { + SDL_ReleaseGPUBuffer(context->device, context->uninv_buffer_to_free); + context->uninv_buffer_to_free = NULL; + } + + if (context->inv_buffer_to_free != NULL) + { + SDL_ReleaseGPUTransferBuffer(context->device, context->inv_buffer_to_free); + context->inv_buffer_to_free = NULL; + } } void flashbang_release(FlashbangContext* context, SWFAppContext* app_context) { - // release the pipeline + for (size_t i = 0; i < context->audio_stream_capacity; ++i) + { + SDL_DestroyAudioStream(context->audio_streams[i].stream); + } + + FREE(context->audio_streams); + + SDL_CloseAudioDevice(context->audio_device); + + SDL_ReleaseGPUTransferBuffer(context->device, context->vertex_transfer_buffer); + + // release the pipelines SDL_ReleaseGPUGraphicsPipeline(context->device, context->graphics_pipeline); + SDL_ReleaseGPUComputePipeline(context->device, context->inv_pipeline); // destroy the buffers SDL_ReleaseGPUBuffer(context->device, context->vertex_buffer); @@ -998,8 +1451,6 @@ void flashbang_release(FlashbangContext* context, SWFAppContext* app_context) // destroy other textures SDL_ReleaseGPUTexture(context->device, context->dummy_tex); - SDL_ReleaseGPUTexture(context->device, context->msaa_texture); - SDL_ReleaseGPUTexture(context->device, context->resolve_texture); // destroy other samplers SDL_ReleaseGPUSampler(context->device, context->dummy_sampler); diff --git a/src/flashbang/shaders/compute.glsl b/src/flashbang/shaders/compute.glsl index 82b639e..22d19e2 100644 --- a/src/flashbang/shaders/compute.glsl +++ b/src/flashbang/shaders/compute.glsl @@ -14,9 +14,14 @@ layout(std430, set = 1, binding = 0) buffer InverseGradientMatrices mat4 inv_gradmats[]; }; +layout(set = 2, binding = 0) uniform StartOffset +{ + uint offset; +}; + void main() { uint mat_i = gl_GlobalInvocationID.x; - mat4 gradmat = gradmats[mat_i]; - inv_gradmats[mat_i] = inverse(gradmat); + mat4 gradmat = gradmats[mat_i + offset]; + inv_gradmats[mat_i + offset] = inverse(gradmat); } \ No newline at end of file diff --git a/src/flashbang/shaders/vertex.glsl b/src/flashbang/shaders/vertex.glsl index 6b3f07f..e29827d 100644 --- a/src/flashbang/shaders/vertex.glsl +++ b/src/flashbang/shaders/vertex.glsl @@ -65,6 +65,6 @@ void main() v_args = (v_style_type == 0x00) ? colors[v_style_id] : ((v_style_type & 0xF0) == 0x10) ? vec4(V_GRAD_UV(v_style_id), 0.0f, 0.0f) : - ((v_style_type & 0xF0) == 0x40) ? vec4(V_BITMAP_UV(style_upper, bitmap_sizes[v_style_id]), 0.0f, 0.0f) : + ((v_style_type & 0xF0) == 0x40) ? vec4(V_BITMAP_UV(style_upper, bitmap_sizes[0]), 0.0f, 0.0f) : vec4(0.0f); } \ No newline at end of file diff --git a/src/flashbang/triangulation.c b/src/flashbang/triangulation.c new file mode 100644 index 0000000..b4c8042 --- /dev/null +++ b/src/flashbang/triangulation.c @@ -0,0 +1,59 @@ +#include +#include +#include + +static TESSalloc libtess2AllocCtx; + +void triInit(SWFAppContext* app_context) +{ + libtess2AllocCtx.memalloc = (void* (*)(void*, unsigned int)) heap_alloc; + libtess2AllocCtx.memrealloc = (void* (*)(void*, void*, unsigned int)) heap_realloc; + libtess2AllocCtx.memfree = (void (*)(void*, void*)) heap_free; + libtess2AllocCtx.userData = app_context; + libtess2AllocCtx.meshEdgeBucketSize = 512; + libtess2AllocCtx.meshVertexBucketSize = 512; + libtess2AllocCtx.meshFaceBucketSize = 256; + libtess2AllocCtx.dictNodeBucketSize = 512; + libtess2AllocCtx.regionBucketSize = 256; + libtess2AllocCtx.extraVertices = 0; +} + +recomp_triangulate_t* triAlloc(SWFAppContext* app_context) +{ + return tessNewTess(&libtess2AllocCtx); +} + +void triTessellate(recomp_triangulate_t* tess, f32* data, u32 num_vertices) +{ + tessAddContour(tess, 2, data, 2*sizeof(f32), num_vertices); + + if (UNLIKELY(!tessTesselate(tess, TESS_WINDING_ODD, TESS_POLYGONS, 3, 2, NULL))) + { + EXC("tessellation failed"); + } +} + +const f32* triGetVertices(recomp_triangulate_t* tess) +{ + return tessGetVertices(tess); +} + +const int* triGetElements(recomp_triangulate_t* tess) +{ + return tessGetElements(tess); +} + +u32 triGetVertexCount(recomp_triangulate_t* tess) +{ + return tessGetVertexCount(tess); +} + +u32 triGetElementCount(recomp_triangulate_t* tess) +{ + return tessGetElementCount(tess); +} + +void triDestroy(SWFAppContext* app_context, recomp_triangulate_t* tess) +{ + tessDeleteTess(tess); +} \ No newline at end of file diff --git a/src/libswf/swf.c b/src/libswf/swf.c index fcfb7a4..b625dde 100644 --- a/src/libswf/swf.c +++ b/src/libswf/swf.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -14,12 +14,58 @@ ActionVar* temp_val; Character* dictionary = NULL; -DisplayObject* display_list = NULL; -size_t max_depth = 0; +u16 swfGetExportedChar(SWFAppContext* app_context, u32 string_id) +{ + u16 char_id = 0; + + for (size_t i = 0; i < app_context->exported_chars_count; ++i) + { + if (app_context->exported_string_ids[i] == string_id) + { + char_id = app_context->exported_char_ids[i]; + break; + } + } + + return char_id; +} -FlashbangContext* context; +u16 swfGetBitmapId(SWFAppContext* app_context, u32 char_id) +{ + u16 bitmap_id = 0xFFFF; + + for (size_t i = 0; i < app_context->bitmap_count; ++i) + { + if (app_context->bitmap_char_ids[i] == char_id) + { + bitmap_id = app_context->bitmap_ids[i]; + break; + } + } + + return bitmap_id; +} -void tagInit(); +void swfSleepToNextFrame(SWFAppContext* app_context) +{ + if (LIKELY(app_context->last_frame != 0)) + { + s32 diff = get_elapsed_ms() - app_context->last_frame; + s32 delay = 7 - diff; + + while (true) + { + if ((s32) (get_elapsed_ms() - app_context->last_frame) > delay) + { + break; + } + + recomp_sleep(0); + } + } + + app_context->last_frame = get_elapsed_ms(); +} void tagMain(SWFAppContext* app_context) { @@ -34,18 +80,15 @@ void tagMain(SWFAppContext* app_context) } manual_next_frame = 0; - bad_poll |= flashbang_poll(); + bad_poll |= flashbang_poll(FBC, app_context); quit_swf |= bad_poll; } - if (bad_poll) - { - return; - } - - while (!flashbang_poll()) + while (!(bad_poll = flashbang_poll(FBC, app_context))) { tagShowFrame(app_context); + + swfSleepToNextFrame(app_context); } } @@ -54,36 +97,41 @@ void swfStart(SWFAppContext* app_context) heap_init(app_context, HEAP_SIZE); FlashbangContext c; - context = &c; - - context->width = app_context->width; - context->height = app_context->height; - - context->stage_to_ndc = app_context->stage_to_ndc; - - context->bitmap_count = app_context->bitmap_count; - context->bitmap_highest_w = app_context->bitmap_highest_w; - context->bitmap_highest_h = app_context->bitmap_highest_h; - - context->shape_data = app_context->shape_data; - context->shape_data_size = app_context->shape_data_size; - context->transform_data = app_context->transform_data; - context->transform_data_size = app_context->transform_data_size; - context->color_data = app_context->color_data; - context->color_data_size = app_context->color_data_size; - context->uninv_mat_data = app_context->uninv_mat_data; - context->uninv_mat_data_size = app_context->uninv_mat_data_size; - context->gradient_data = app_context->gradient_data; - context->gradient_data_size = app_context->gradient_data_size; - context->bitmap_data = app_context->bitmap_data; - context->bitmap_data_size = app_context->bitmap_data_size; - context->cxform_data = app_context->cxform_data; - context->cxform_data_size = app_context->cxform_data_size; - - flashbang_init(context, app_context); + app_context->fbc = &c; + + c.width = app_context->width; + c.height = app_context->height; + + c.stage_to_ndc = app_context->stage_to_ndc; + + c.bitmap_count = app_context->bitmap_count; + c.bitmap_highest_w = app_context->bitmap_highest_w; + c.bitmap_highest_h = app_context->bitmap_highest_h; + + c.shape_data_exists = app_context->shape_data_exists; + + c.shape_data = app_context->shape_data; + c.shape_data_size = app_context->shape_data_size; + c.transform_data = app_context->transform_data; + c.transform_data_size = app_context->transform_data_size; + c.color_data = app_context->color_data; + c.color_data_size = app_context->color_data_size; + c.uninv_mat_data = app_context->uninv_mat_data; + c.uninv_mat_data_size = app_context->uninv_mat_data_size; + c.gradient_data = app_context->gradient_data; + c.gradient_data_size = app_context->gradient_data_size; + c.bitmap_data = app_context->bitmap_data; + c.bitmap_data_size = app_context->bitmap_data_size; + c.cxform_data = app_context->cxform_data; + c.cxform_data_size = app_context->cxform_data_size; + + flashbang_init(&c, app_context); + + recomp_init_utils(); dictionary = HALLOC(INITIAL_DICTIONARY_CAPACITY*sizeof(Character)); - display_list = HALLOC(INITIAL_DISPLAYLIST_CAPACITY*sizeof(DisplayObject)); + + app_context->dictionary_capacity = INITIAL_DICTIONARY_CAPACITY; STACK = (char*) HALLOC(INITIAL_STACK_SIZE); SP = INITIAL_SP; @@ -92,23 +140,37 @@ void swfStart(SWFAppContext* app_context) bad_poll = 0; next_frame = 0; - initVarArray(app_context, app_context->max_string_id); + initActions(app_context); + initMap(app_context); - initTime(); - initMap(); + SVEC_SIZED_INIT(&app_context->vertex_tasks, sizeof(VertexTask)); + SVEC_SIZED_INIT(&app_context->uninv_tasks, sizeof(UninvTask)); + SVEC_SIZED_INIT(&app_context->draw_tasks, sizeof(DrawTask)); + + SVEC_INIT(&app_context->movieclip_stack); + + app_context->last_frame = 0; tagInit(app_context); tagMain(app_context); + SVEC_RELEASE(&app_context->vertex_tasks); + SVEC_RELEASE(&app_context->uninv_tasks); + SVEC_RELEASE(&app_context->draw_tasks); + + SVEC_RELEASE(&app_context->movieclip_stack); + freeMap(app_context); + freeActions(app_context); FREE(STACK); FREE(dictionary); - FREE(display_list); - flashbang_release(context, app_context); + recomp_deinit_utils(); + + flashbang_release(&c, app_context); heap_shutdown(app_context); } \ No newline at end of file diff --git a/src/libswf/swf_core.c b/src/libswf/swf_core.c index 10d2ca1..247f1ac 100644 --- a/src/libswf/swf_core.c +++ b/src/libswf/swf_core.c @@ -33,7 +33,9 @@ void swfStart(SWFAppContext* app_context) next_frame = 0; manual_next_frame = 0; - initTime(); + initVarArray(app_context, app_context->max_string_id); + + initActions(app_context); initMap(); tagInit(); @@ -75,7 +77,7 @@ void swfStart(SWFAppContext* app_context) printf("\n=== SWF Execution Completed ===\n"); // Cleanup - freeMap(); + freeMap(app_context); FREE(stack); heap_shutdown(app_context); diff --git a/src/libswf/tag.c b/src/libswf/tag.c index f0fc30c..a0b8b3b 100644 --- a/src/libswf/tag.c +++ b/src/libswf/tag.c @@ -1,65 +1,330 @@ +#ifndef NO_GRAPHICS + #include #include +#include +#include +#include #include +#include #include -extern FlashbangContext* context; - -size_t dictionary_capacity = INITIAL_DICTIONARY_CAPACITY; -size_t display_list_capacity = INITIAL_DISPLAYLIST_CAPACITY; +float temp_mat_data[16] = +{ + 1.000000000000000f, + 0.000000000000000f, + 0.0f, + 0.0f, + 0.000000000000000f, + 1.000000000000000f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.000000000000000f, + 0.000000000000000f, + 0.0f, + 1.0f, +}; -void tagSetBackgroundColor(u8 red, u8 green, u8 blue) +void tagSetBackgroundColor(SWFAppContext* app_context, u8 red, u8 green, u8 blue) { - flashbang_set_window_background(context, red, green, blue); + flashbang_set_window_background(app_context->fbc, red, green, blue); } void tagShowFrame(SWFAppContext* app_context) { - flashbang_open_pass(context); + app_context->frame_vertex_count = 0; + + SwapVector* stack = &app_context->movieclip_stack; + ASObject* root = app_context->_root; - for (size_t i = 1; i <= max_depth; ++i) + SVEC_PUSH(stack, root); + + while (stack->length > 0) { - DisplayObject* obj = &display_list[i]; + ASObject* disp_obj = (ASObject*) SVEC_TOP(stack); + SVEC_POP(stack); - if (obj->char_id == 0) + if (disp_obj == NULL) { continue; } - Character* ch = &dictionary[obj->char_id]; + if (getAndCallMethodIfExists(app_context, disp_obj, STR_ID_ON_ENTER_FRAME, 0)) + { + POP(); + } + + size_t max_depth = MC_EXTDATA_OF(disp_obj, max_depth); + + for (size_t i = max_depth; i >= 1; --i) + { + if (MC_EXTDATA_OF(disp_obj, bitmap_at) == i) + { + continue; + } + + SVEC_PUSH(stack, MC_EXTDATA_OF(disp_obj, children)[i]); + } + + u32 char_id = MC_EXTDATA_OF(disp_obj, char_id); + + if (char_id != 0) + { + u32 transform_id = MC_EXTDATA_OF(disp_obj, transform_id); + + Character* ch = &dictionary[char_id]; + + switch (ch->type) + { + case CHAR_TYPE_SHAPE: + SVEC_BUMP(&app_context->draw_tasks); + + DrawTask* dt = SVEC_GET_TOP(&app_context->draw_tasks, DrawTask); + + dt->has_extra_transform_id = false; + dt->has_extra_cxform_id = false; + dt->has_extra_transform = false; + + dt->offset = ch->shape_offset; + dt->count = ch->size; + dt->transform_id = transform_id; + break; + case CHAR_TYPE_TEXT: + for (u32 j = 0; j < ch->text_size; ++j) + { + SVEC_BUMP(&app_context->draw_tasks); + + DrawTask* dt = SVEC_GET_TOP(&app_context->draw_tasks, DrawTask); + + dt->has_extra_transform_id = true; + dt->extra_transform_id = transform_id; + + dt->has_extra_cxform_id = true; + dt->extra_cxform_id = ch->cxform_id; + + dt->has_extra_transform = false; + + u32 glyph_index = 2*app_context->text_data[ch->text_start + j]; + + dt->offset = app_context->glyph_data[glyph_index]; + dt->count = app_context->glyph_data[glyph_index + 1]; + dt->transform_id = ch->transform_start + j; + } + break; + } + } + + else + { + if (MC_EXTDATA_OF(disp_obj, has_tris)) + { + SVEC_BUMP(&app_context->vertex_tasks); + + VertexTask* vt = SVEC_GET_TOP(&app_context->vertex_tasks, VertexTask); + + u32 vertex_count = 3*MC_EXTDATA_OF(disp_obj, tri_count); + u32 vertex_offset = flashbang_allocate_vertices(app_context->fbc, vertex_count); + + app_context->frame_vertex_count += vertex_count; + + vt->count = vertex_count; + vt->offset = vertex_offset; + vt->tris = MC_EXTDATA_OF(disp_obj, tris); + + vt->free_after = false; + + SVEC_BUMP(&app_context->draw_tasks); + + DrawTask* dt = SVEC_GET_TOP(&app_context->draw_tasks, DrawTask); + + dt->has_extra_transform_id = false; + dt->has_extra_cxform_id = false; + dt->has_extra_transform = false; + + dt->offset = vertex_offset; + dt->count = vertex_count; + dt->transform_id = 0; + } + + u32 bitmap_at = MC_EXTDATA_OF(disp_obj, bitmap_at); + + if (bitmap_at != 0) + { + ASObject* bitmap = MC_EXTDATA_OF(disp_obj, children)[bitmap_at]; + + u32* tris = HALLOC(6*4*sizeof(float)); + + u32 uninv_offset = flashbang_allocate_uninv(app_context->fbc); + u32 uninv_id = uninv_offset/(16*sizeof(float)); + + u32 bitmap_id = BM_EXTDATA_OF(bitmap, bitmap_id); + + // TODO: change 0x41 to 0x43 + + VAL(float, &tris[0]) = 0.0f; + VAL(float, &tris[1]) = (float) (20*BM_EXTDATA_OF(bitmap, height)); + tris[2] = 0x41; + tris[3] = (uninv_id << 16) | bitmap_id; + + VAL(float, &tris[4]) = (float) (20*BM_EXTDATA_OF(bitmap, width)); + VAL(float, &tris[5]) = 0.0f; + tris[6] = 0x41; + tris[7] = (uninv_id << 16) | bitmap_id; + + VAL(float, &tris[8]) = 0.0f; + VAL(float, &tris[9]) = 0.0f; + tris[10] = 0x41; + tris[11] = (uninv_id << 16) | bitmap_id; + + VAL(float, &tris[12]) = 0.0f; + VAL(float, &tris[13]) = (float) (20*BM_EXTDATA_OF(bitmap, height)); + tris[14] = 0x41; + tris[15] = (uninv_id << 16) | bitmap_id; + + VAL(float, &tris[16]) = (float) (20*BM_EXTDATA_OF(bitmap, width)); + VAL(float, &tris[17]) = 0.0f; + tris[18] = 0x41; + tris[19] = (uninv_id << 16) | bitmap_id; + + VAL(float, &tris[20]) = (float) (20*BM_EXTDATA_OF(bitmap, width)); + VAL(float, &tris[21]) = (float) (20*BM_EXTDATA_OF(bitmap, height)); + tris[22] = 0x41; + tris[23] = (uninv_id << 16) | bitmap_id; + + SVEC_BUMP(&app_context->draw_tasks); + + DrawTask* dt = SVEC_GET_TOP(&app_context->draw_tasks, DrawTask); + + dt->has_extra_transform_id = false; + dt->has_extra_cxform_id = false; + + dt->has_extra_transform = true; + + dt->obj = disp_obj; + + SVEC_BUMP(&app_context->vertex_tasks); + + VertexTask* vt = SVEC_GET_TOP(&app_context->vertex_tasks, VertexTask); + + u32 vertex_count = 6; + app_context->frame_vertex_count += vertex_count; + vt->count = vertex_count; + + u32 vertex_offset = flashbang_allocate_vertices(app_context->fbc, vertex_count); + vt->offset = vertex_offset; + + dt->count = vertex_count; + dt->offset = vertex_offset; + dt->transform_id = 0; + + vt->tris = tris; + + vt->free_after = true; + + SVEC_BUMP(&app_context->uninv_tasks); + + UninvTask* ut = SVEC_GET_TOP(&app_context->uninv_tasks, UninvTask); + ut->offset = uninv_offset; + } + } + } + + if (!flashbang_open_pass(app_context->fbc, app_context)) + { + goto clear; + } + + if (app_context->vertex_tasks.length > 0) + { + flashbang_open_vertex_transfer(app_context->fbc, app_context->frame_vertex_count, app_context->uninv_tasks.length); + + for (size_t i = 0; i < app_context->vertex_tasks.length; ++i) + { + VertexTask* vt = SVEC_GET(&app_context->vertex_tasks, VertexTask, i); + + flashbang_upload_vertices(app_context->fbc, vt->tris, vt->offset, vt->count); + + bool free_after = vt->free_after; + + if (free_after) + { + FREE(vt->tris); + } + } + + for (size_t i = 0; i < app_context->uninv_tasks.length; ++i) + { + UninvTask* ut = SVEC_GET(&app_context->uninv_tasks, UninvTask, i); + + u32 offset = ut->offset; + + temp_mat_data[0] = 20.0f; + temp_mat_data[1] = 0.0f; + temp_mat_data[4] = 0.0f; + temp_mat_data[5] = 20.0f; + + temp_mat_data[12] = 0.0f; + temp_mat_data[13] = 0.0f; + + flashbang_upload_uninv(app_context->fbc, temp_mat_data, offset); + } - switch (ch->type) + flashbang_close_vertex_transfer(app_context->fbc); + } + + for (size_t i = 0; i < app_context->draw_tasks.length; ++i) + { + DrawTask* t = SVEC_GET(&app_context->draw_tasks, DrawTask, i); + + u32 extra_transform_id = t->has_extra_transform_id ? t->extra_transform_id : 0; + flashbang_upload_extra_transform_id(app_context->fbc, extra_transform_id); + + u32 extra_cxform_id = t->has_extra_cxform_id ? t->extra_cxform_id : 0; + flashbang_upload_cxform_id(app_context->fbc, extra_cxform_id); + + if (t->has_extra_transform) { - case CHAR_TYPE_SHAPE: - flashbang_draw_shape(context, ch->shape_offset, ch->size, obj->transform_id); - break; - case CHAR_TYPE_TEXT: - flashbang_upload_extra_transform_id(context, obj->transform_id); - flashbang_upload_cxform_id(context, ch->cxform_id); - for (int i = 0; i < ch->text_size; ++i) - { - size_t glyph_index = 2*app_context->text_data[ch->text_start + i]; - flashbang_draw_shape(context, app_context->glyph_data[glyph_index], app_context->glyph_data[glyph_index + 1], ch->transform_start + i); - } - break; + MovieClip_applyTransformsParents(app_context, t->obj, temp_mat_data); + flashbang_upload_extra_transform(app_context->fbc, temp_mat_data); } + + else + { + flashbang_upload_extra_transform(app_context->fbc, (float*) identity); + } + + flashbang_draw_shape(app_context->fbc, t->offset, t->count, t->transform_id); } - flashbang_close_pass(context); + flashbang_close_pass(app_context->fbc, app_context); + + recomp_sync_window(); + + clear: + + SVEC_CLEAR(&app_context->vertex_tasks); + SVEC_CLEAR(&app_context->uninv_tasks); + SVEC_CLEAR(&app_context->draw_tasks); + + SVEC_CLEAR(&app_context->movieclip_stack); } -void tagDefineShape(SWFAppContext* app_context, CharacterType type, size_t char_id, size_t shape_offset, size_t shape_size) +void tagDefineShape(SWFAppContext* app_context, CharacterType type, u32 char_id, u32 shape_offset, u32 shape_size) { - ENSURE_SIZE(dictionary, char_id, dictionary_capacity, sizeof(Character)); + ENSURE_SIZE(dictionary, char_id, app_context->dictionary_capacity, sizeof(Character)); dictionary[char_id].type = type; dictionary[char_id].shape_offset = shape_offset; dictionary[char_id].size = shape_size; } -void tagDefineText(SWFAppContext* app_context, size_t char_id, size_t text_start, size_t text_size, u32 transform_start, u32 cxform_id) +void tagDefineText(SWFAppContext* app_context, u32 char_id, u32 text_start, u32 text_size, u32 transform_start, u32 cxform_id) { - ENSURE_SIZE(dictionary, char_id, dictionary_capacity, sizeof(Character)); + ENSURE_SIZE(dictionary, char_id, app_context->dictionary_capacity, sizeof(Character)); dictionary[char_id].type = CHAR_TYPE_TEXT; dictionary[char_id].text_start = text_start; @@ -68,25 +333,19 @@ void tagDefineText(SWFAppContext* app_context, size_t char_id, size_t text_start dictionary[char_id].cxform_id = cxform_id; } -void tagPlaceObject2(SWFAppContext* app_context, size_t depth, size_t char_id, u32 transform_id) +void tagPlaceObject2(SWFAppContext* app_context, u32 depth, u32 char_id, u32 transform_id) { - ENSURE_SIZE(display_list, depth, display_list_capacity, sizeof(DisplayObject)); - - display_list[depth].char_id = char_id; - display_list[depth].transform_id = transform_id; - - if (depth > max_depth) - { - max_depth = depth; - } + MovieClip_placeObject2_internal(app_context, app_context->_root, depth, char_id, transform_id); } -void defineBitmap(size_t offset, size_t size, u32 width, u32 height) +void defineBitmap(SWFAppContext* app_context, u32 offset, u32 size, u32 width, u32 height) { - flashbang_upload_bitmap(context, offset, size, width, height); + flashbang_upload_bitmap(app_context->fbc, offset, size, width, height); } -void finalizeBitmaps() +void finalizeBitmaps(SWFAppContext* app_context) { - flashbang_finalize_bitmaps(context); -} \ No newline at end of file + flashbang_finalize_bitmaps(app_context->fbc); +} + +#endif // NO_GRAPHICS \ No newline at end of file diff --git a/src/libswf/tag_stubs.c b/src/libswf/tag_stubs.c index f6a7463..ea6d796 100644 --- a/src/libswf/tag_stubs.c +++ b/src/libswf/tag_stubs.c @@ -1,5 +1,8 @@ -#include +#ifdef NO_GRAPHICS + #include +#include +#include // Stub implementations for console-only mode // Note: tagInit() is provided by the generated tagMain.c file @@ -9,8 +12,9 @@ void tagSetBackgroundColor(u8 red, u8 green, u8 blue) printf("[Tag] SetBackgroundColor(%d, %d, %d)\n", red, green, blue); } -void tagShowFrame() +void tagShowFrame(SWFAppContext* app_context) { + (void)app_context; // Unused in NO_GRAPHICS mode printf("[Tag] ShowFrame()\n"); } @@ -36,4 +40,6 @@ void finalizeBitmaps() { printf("[Tag] FinalizeBitmaps() [ignored in NO_GRAPHICS mode]\n"); } -#endif \ No newline at end of file +#endif + +#endif // NO_GRAPHICS \ No newline at end of file diff --git a/src/memory/heap.c b/src/memory/heap.c index 0010f7c..f0bd020 100644 --- a/src/memory/heap.c +++ b/src/memory/heap.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -9,16 +10,61 @@ void heap_init(SWFAppContext* app_context, size_t size) app_context->heap = h; app_context->heap_size = size; app_context->heap_instance = o1heapInit(h, size); + rwlock_init(&app_context->heap_lock); } void* heap_alloc(SWFAppContext* app_context, size_t size) { - return o1heapAllocate(app_context->heap_instance, size); + void* ret; + + LOCK_WRITE(app_context->heap_lock, + { + ret = o1heapAllocate(app_context->heap_instance, size); + }); + + if (UNLIKELY(ret == NULL)) + { + //~ for (size_t i = 0; i < app_context->active_objects.length; ++i) + //~ { + //~ u32* o = (u32*) app_context->active_objects.data[i]; + //~ fprintf(stderr, "unfreed object %d\n", *o); + //~ } + + UNREACHABLE("Error allocating memory"); + } + + return ret; +} + +void* heap_realloc(SWFAppContext* app_context, void* ptr, size_t size) +{ + void* ret; + + LOCK_WRITE(app_context->heap_lock, + { + ret = o1heapReallocate(app_context->heap_instance, ptr, size); + }); + + if (UNLIKELY(ret == NULL)) + { + //~ for (size_t i = 0; i < app_context->active_objects.length; ++i) + //~ { + //~ u32* o = (u32*) app_context->active_objects.data[i]; + //~ fprintf(stderr, "unfreed object %d\n", *o); + //~ } + + UNREACHABLE("Out of memory, quitting"); + } + + return ret; } void heap_free(SWFAppContext* app_context, void* ptr) { - o1heapFree(app_context->heap_instance, ptr); + LOCK_WRITE(app_context->heap_lock, + { + o1heapFree(app_context->heap_instance, ptr); + }); } void heap_shutdown(SWFAppContext* app_context) diff --git a/src/utils.c b/src/utils.c index 952014c..a5f1139 100644 --- a/src/utils.c +++ b/src/utils.c @@ -3,6 +3,16 @@ #include #include +size_t get_power_two_size(size_t old_size, size_t size) +{ + while (old_size < size) + { + old_size <<= 1; + } + + return old_size; +} + void grow_ptr(SWFAppContext* app_context, char** ptr, size_t* capacity_ptr, size_t elem_size) { char* data = *ptr; @@ -19,15 +29,70 @@ void grow_ptr(SWFAppContext* app_context, char** ptr, size_t* capacity_ptr, size *capacity_ptr = capacity << 1; } +void grow_ptr_far(SWFAppContext* app_context, char** ptr, size_t* capacity_ptr, size_t elem_size, size_t new_size) +{ + char* data = *ptr; + size_t capacity = *capacity_ptr; + size_t old_data_size = capacity*elem_size; + + size_t new_capacity = get_power_two_size(capacity, new_size); + size_t new_data_size = new_capacity*elem_size; + + char* new_data = HALLOC(new_data_size); + + memcpy(new_data, data, old_data_size); + + FREE(data); + + *ptr = new_data; + *capacity_ptr = new_capacity; +} + #if defined(_MSC_VER) // Microsoft #include +#include #include +#include + +#pragma comment(lib, "winmm.lib") +#pragma comment(lib, "dwmapi.lib") + +// windows-only machine-specific global: +LARGE_INTEGER counter_frequency; + +void recomp_init_utils() +{ + QueryPerformanceFrequency(&counter_frequency); + + timeBeginPeriod(1); +} + +void recomp_sync_window() +{ + DwmFlush(); +} + +void recomp_deinit_utils() +{ + timeEndPeriod(1); +} + u32 get_elapsed_ms() { - return (u32) GetTickCount(); + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + + u32 time = (u32) (1000*counter.QuadPart/counter_frequency.QuadPart); + + return time; +} + +void recomp_sleep(u32 ms) +{ + Sleep(ms); } int getpagesize() @@ -48,6 +113,52 @@ void vmem_release(char* addr, size_t size) VirtualFree(addr, 0, MEM_RELEASE); } +void thread_start(SWFAppContext* app_context, runtime_thread_func f, recomp_thread_t* handle) +{ + *handle = _beginthreadex(NULL, 0, f, app_context, 0, NULL); +} + +void thread_exit() +{ + _endthreadex(0); +} + +void thread_join(recomp_thread_t* handle) +{ + WaitForSingleObject((HANDLE) *handle, INFINITE); + CloseHandle((HANDLE) *handle); +} + +void rwlock_init(recomp_rwlock_t* rwlock) +{ + InitializeSRWLock((PSRWLOCK) rwlock); +} + +void rwlock_lock_read(recomp_rwlock_t* rwlock) +{ + AcquireSRWLockShared((PSRWLOCK) rwlock); +} + +void rwlock_unlock_read(recomp_rwlock_t* rwlock) +{ + ReleaseSRWLockShared((PSRWLOCK) rwlock); +} + +void rwlock_lock_write(recomp_rwlock_t* rwlock) +{ + AcquireSRWLockExclusive((PSRWLOCK) rwlock); +} + +void rwlock_unlock_write(recomp_rwlock_t* rwlock) +{ + ReleaseSRWLockExclusive((PSRWLOCK) rwlock); +} + +void rwlock_destroy(recomp_rwlock_t* rwlock) +{ + +} + #elif defined(__GNUC__) // GCC @@ -55,6 +166,21 @@ void vmem_release(char* addr, size_t size) #include #include +void recomp_init_utils() +{ + +} + +void recomp_sync_window() +{ + +} + +void recomp_deinit_utils() +{ + +} + u32 get_elapsed_ms() { struct timespec now; @@ -62,6 +188,14 @@ u32 get_elapsed_ms() return (now.tv_sec)*1000 + (now.tv_nsec)/1000000; } +void recomp_sleep(u32 ms) +{ + struct timespec ms_ts; + ms_ts.tv_sec = ms/1000; + ms_ts.tv_nsec = (ms % 1000)*1000000; + nanosleep(&ms_ts, NULL); +} + char* vmem_reserve(size_t size) { return mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); @@ -72,4 +206,49 @@ void vmem_release(char* addr, size_t size) munmap(addr, size); } +void thread_start(SWFAppContext* app_context, runtime_thread_func f, recomp_thread_t* handle) +{ + pthread_create(handle, NULL, (void* (*)(void*)) f, app_context); +} + +void thread_exit() +{ + +} + +void thread_join(recomp_thread_t* handle) +{ + pthread_join(*handle, NULL); +} + +void rwlock_init(recomp_rwlock_t* rwlock) +{ + pthread_rwlock_init(rwlock, NULL); +} + +void rwlock_lock_read(recomp_rwlock_t* rwlock) +{ + pthread_rwlock_rdlock(rwlock); +} + +void rwlock_unlock_read(recomp_rwlock_t* rwlock) +{ + pthread_rwlock_unlock(rwlock); +} + +void rwlock_lock_write(recomp_rwlock_t* rwlock) +{ + pthread_rwlock_wrlock(rwlock); +} + +void rwlock_unlock_write(recomp_rwlock_t* rwlock) +{ + pthread_rwlock_unlock(rwlock); +} + +void rwlock_destroy(recomp_rwlock_t* rwlock) +{ + pthread_rwlock_destroy(rwlock); +} + #endif \ No newline at end of file