diff --git a/.gitmodules b/.gitmodules index 1300966..c6be134 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 899e135..d226a9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,16 +14,22 @@ 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/variables.c ${PROJECT_SOURCE_DIR}/src/memory/heap.c + ${PROJECT_SOURCE_DIR}/src/apis/rbtree/rbtree.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 +39,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") @@ -44,10 +48,10 @@ else() ${PROJECT_SOURCE_DIR}/src/libswf/tag.c ${PROJECT_SOURCE_DIR}/src/flashbang/flashbang.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) @@ -85,13 +89,16 @@ 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/c-hashmap - ${PROJECT_SOURCE_DIR}/lib/SDL3/include + ${PROJECT_SOURCE_DIR}/lib/rbtree ${PROJECT_SOURCE_DIR}/lib/o1heap/o1heap + ${PROJECT_SOURCE_DIR}/lib/SDL3/include zlib lzma/liblzma/api ) diff --git a/include/actionmodern/action.h b/include/actionmodern/action.h index eb7fff5..0e9427e 100644 --- a/include/actionmodern/action.h +++ b/include/actionmodern/action.h @@ -1,21 +1,25 @@ #pragma once #include +#include #include #include +#define STACK_VAR_SIZE (4 + 4 + 8 + 8) +#define STACK_FUNC_SIZE (4 + 4 + 8 + 8 + 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; \ // 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; \ VAL(u32, &STACK[SP + 4]) = OLDSP; \ @@ -24,7 +28,7 @@ VAL(char*, &STACK[SP + 16]) = v; \ // 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]); \ @@ -34,7 +38,22 @@ VAL(u32, &STACK[SP + 4]) = OLDSP; \ VAL(u32, &STACK[SP + 8]) = n; \ -#define PUSH_VAR(p) pushVar(app_context, p); +#define PUSH_FUNC(v, id, f, args) \ + OLDSP = SP; \ + SP -= STACK_FUNC_SIZE; \ + SP &= ~7; \ + STACK[SP] = ACTION_STACK_VALUE_FUNCTION; \ + VAL(u32, &STACK[SP + 4]) = OLDSP; \ + VAL(u32, &STACK[SP + 12]) = id; \ + VAL(u64, &STACK[SP + 16]) = (u64) v; \ + VAL(u64, &STACK[SP + 24]) = (u64) f; \ + VAL(u64, &STACK[SP + 32]) = (u64) args; \ + +#define PUSH_UNDEFINED() PUSH(ACTION_STACK_VALUE_UNDEFINED, 0) + +#define PUSH_OBJ(o) PUSH(ACTION_STACK_VALUE_OBJECT, (u64) o) + +#define PUSH_VAR(p) pushVar(app_context, p) #define POP() \ SP = VAL(u32, &STACK[SP + 4]); \ @@ -47,12 +66,18 @@ #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 STACK_TOP_FUNC VAL(u64, &STACK[SP + 24]) +#define STACK_TOP_FUNC_ARGS VAL(u64, &STACK[SP + 32]) #define SP_SECOND_TOP VAL(u32, &STACK[SP + 4]) #define STACK_SECOND_TOP_TYPE STACK[SP_SECOND_TOP] #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 STACK_SECOND_TOP_FUNC VAL(u64, &STACK[SP_SECOND_TOP + 24]) +#define STACK_SECOND_TOP_FUNC_ARGS VAL(u64, &STACK[SP_SECOND_TOP + 32]) + +#define RETURN_VOID() PUSH_UNDEFINED() #define VAL(type, x) *((type*) x) @@ -61,26 +86,66 @@ extern ActionVar* temp_val; -void initTime(); +// Global object +// Initialized on first use via initActions() +extern ASObject* _global; + +void initActions(SWFAppContext* app_context); void pushVar(SWFAppContext* app_context, ActionVar* p); +ASProperty* getPropertyInThisScope(u32 string_id, const char* name, u32 name_len); + +// Arithmetic Operations void actionAdd(SWFAppContext* app_context); void actionSubtract(SWFAppContext* app_context); void actionMultiply(SWFAppContext* app_context); void actionDivide(SWFAppContext* app_context); + +// Comparison Operations void actionEquals(SWFAppContext* app_context); void actionLess(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); +// 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 actionTypeof(SWFAppContext* app_context, char* str_buffer); +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 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); + +typedef ActionVar (*Function2Ptr)(SWFAppContext* app_context, ActionVar* args, u32 arg_count, ActionVar* registers, void* this_obj); \ 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..ed4a70e --- /dev/null +++ b/include/actionmodern/initial_strings_decls.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +typedef enum +{ + STR_ID_EMPTY = 1, + STR_ID_GLOBAL, + STR_ID_RECOMP, + STR_ID_ARG1, + STR_ID_ARG2, + STR_ID_ARG3, + STR_ID_ARG4, + STR_ID_ARG5, + STR_ID_ARG6, + STR_ID_OBJECT, + STR_ID_THIS, + STR_ID_LENGTH, + STR_ID_MATH, + STR_ID_ABS, + STR_ID_X, +} StringIds; + +typedef struct +{ + u32 object_string_id; + u32 func_string_id; + action_func func; + u32* args; +} 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..e2d5ff0 --- /dev/null +++ b/include/actionmodern/initial_strings_defs.h @@ -0,0 +1,7 @@ +#include + +RuntimeFunc runtime_funcs[] = +{ + {0, STR_ID_OBJECT, new_Object, NULL}, + {STR_ID_MATH, STR_ID_ABS, Math_abs, (u32*) &(u32[]){ STR_ID_X }}, +}; \ No newline at end of file diff --git a/include/actionmodern/objects.h b/include/actionmodern/objects.h new file mode 100644 index 0000000..6fdf7d5 --- /dev/null +++ b/include/actionmodern/objects.h @@ -0,0 +1,151 @@ +#pragma once + +#include +#include + +#include + +// Forward declaration +typedef struct SWFAppContext SWFAppContext; + +/** + * 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. + */ + +// Forward declaration for property structure +typedef struct ASProperty ASProperty; + +/** + * 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 +{ + rbtree t; + u32 refcount; // Reference count (starts at 1 on allocation) +} ASObject; + +struct ASProperty +{ + rbnode n; + char* name; // Property name (heap-allocated) + u32 name_length; // Length of property name + u8 flags; // Property attribute flags (PROPERTY_FLAG_*) + ActionVar value; // Property value (can be any type) +}; + +/** + * Object Lifecycle Primitives + * + * These functions are called by generated code to manage object lifetimes. + */ + +// Allocate new object +// Returns object with refcount = 1 +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); + +/** + * Property Management + * + * Functions for manipulating object properties. + */ + +// Get property by name (returns NULL if not found) +ASProperty* getProperty(ASObject* obj, u32 string_id, const char* name, u32 name_length); + +// Get property by name with prototype chain traversal (returns NULL if not found) +// Walks up the __proto__ chain to find inherited properties +ActionVar* getPropertyWithPrototype(ASObject* obj, const char* name, u32 name_length); + +// Set property by name (creates if not exists) +// Handles refcount management if value is an object +void setProperty(SWFAppContext* app_context, ASObject* obj, 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 if it exists, NULL otherwise +ASObject* getConstructor(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 diff --git a/include/actionmodern/runtime_api/Math.h b/include/actionmodern/runtime_api/Math.h new file mode 100644 index 0000000..2d64598 --- /dev/null +++ b/include/actionmodern/runtime_api/Math.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void Math_abs(SWFAppContext* app_context); \ 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..f96de08 --- /dev/null +++ b/include/actionmodern/runtime_api/Object.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void new_Object(SWFAppContext* app_context); \ No newline at end of file diff --git a/include/actionmodern/stackvalue.h b/include/actionmodern/stackvalue.h index 4e39ab5..a8ce006 100644 --- a/include/actionmodern/stackvalue.h +++ b/include/actionmodern/stackvalue.h @@ -6,6 +6,14 @@ 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, + ACTION_STACK_VALUE_ARRAY = 0x11, + ACTION_STACK_VALUE_FUNCTION = 0x12 } ActionStackValueType; \ No newline at end of file diff --git a/include/actionmodern/variables.h b/include/actionmodern/variables.h index a72160b..1999108 100644 --- a/include/actionmodern/variables.h +++ b/include/actionmodern/variables.h @@ -9,14 +9,13 @@ typedef struct ActionStackValueType type; u32 str_size; u32 string_id; + bool owns_memory; + action_func func; + u32* args; union { u64 value; - struct - { - char* heap_ptr; - bool owns_memory; - }; + char* heap_ptr; }; } ActionVar; diff --git a/include/apis/rbtree.h b/include/apis/rbtree.h new file mode 100644 index 0000000..dbf299e --- /dev/null +++ b/include/apis/rbtree.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include + +#define RBT_GET_OR_INS(t, s) rbtree_get_or_insert(app_context, t, s) + +/** + * Red-Black Tree interface. + * + * Wrapper around red-black tree. + */ + +typedef struct +{ + struct rb_tree t; + size_t struct_size; +} rbtree; + +typedef struct +{ + struct rb_node n; + u32 string_id; +} rbnode; + +/** + * Initialize the tree. + * + * @param app_context Main app context + * @param size Heap size in bytes + */ +void rbtree_init(rbtree* t, size_t struct_size); + +/** + * Get a node and return it if it exists, but 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, but create it 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 existing/newly-created node + */ +rbnode* rbtree_get_or_insert(SWFAppContext* app_context, rbtree* t, u32 string_id); \ No newline at end of file diff --git a/include/flashbang/flashbang.h b/include/flashbang/flashbang.h index 15e8de3..9bfcfde 100644 --- a/include/flashbang/flashbang.h +++ b/include/flashbang/flashbang.h @@ -80,6 +80,6 @@ void flashbang_upload_extra_transform_id(FlashbangContext* context, u32 transfor 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_draw_shape(FlashbangContext* context, u32 offset, u32 num_verts, u32 transform_id); void flashbang_close_pass(FlashbangContext* context); void flashbang_release(FlashbangContext* context, SWFAppContext* app_context); \ No newline at end of file diff --git a/include/libswf/swf.h b/include/libswf/swf.h index 5ce8392..974958e 100644 --- a/include/libswf/swf.h +++ b/include/libswf/swf.h @@ -25,14 +25,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; }; @@ -48,6 +48,7 @@ typedef struct DisplayObject typedef struct SWFAppContext SWFAppContext; typedef void (*frame_func)(SWFAppContext* app_context); +typedef void (*action_func)(SWFAppContext*); extern frame_func frame_funcs[]; @@ -60,6 +61,7 @@ typedef struct SWFAppContext u32 oldSP; frame_func* frame_funcs; + char** str_table; int width; int height; diff --git a/include/libswf/tag.h b/include/libswf/tag.h index bbfa291..3c281a3 100644 --- a/include/libswf/tag.h +++ b/include/libswf/tag.h @@ -4,13 +4,15 @@ #include // Core tag functions - always available -void tagInit(SWFAppContext* app_context); +void tagInit(); void tagSetBackgroundColor(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(u32 offset, u32 size, u32 width, u32 height); +void finalizeBitmaps(); +#endif \ No newline at end of file diff --git a/include/memory/heap.h b/include/memory/heap.h index dcb1660..19135c4 100644 --- a/include/memory/heap.h +++ b/include/memory/heap.h @@ -8,7 +8,7 @@ /** * Memory Heap Manager * - * Wrapper around o1heap allocator providing multi-heap support with automatic expansion. + * Wrapper around o1heap allocator providing multi-heap support. */ /** @@ -42,7 +42,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/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/src/actionmodern/action.c b/src/actionmodern/action.c index 878173a..62dd1e5 100644 --- a/src/actionmodern/action.c +++ b/src/actionmodern/action.c @@ -6,22 +6,128 @@ #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 u32 scope_top_obj = 0; + +// ================================================================== +// Function Storage and Management +// ================================================================== + +// Function object structure +typedef struct { + char* name; // Function name (can be NULL for anonymous) + u8 function_type; // 1 = simple (DefineFunction), 2 = advanced (DefineFunction2) + u32 param_count; // Number of parameters + + // For DefineFunction (type 1) + action_func simple_func; + + // For DefineFunction2 (type 2) + Function2Ptr advanced_func; + u8 register_count; + u16 flags; +} ASFunction; + +// ================================================================== +// Global object for ActionScript +// This is initialized from initActions and persists for the lifetime of the runtime +ASObject* _global; + +void initActions(SWFAppContext* app_context) { start_time = get_elapsed_ms(); + + for (u32 i = 0; i < MAX_SCOPE_DEPTH; ++i) + { + scope_chain[i] = allocObject(app_context); + } + + _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(_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_FUNCTION; + v.func = runtime_funcs[i].func; + v.args = runtime_funcs[i].args; + v.value = (u64) allocObject(app_context); + setProperty(app_context, obj, runtime_funcs[i].func_string_id, NULL, 0, &v); + } +} + +ASProperty* searchScopesForProperty(u32 string_id, const char* name, u32 name_len) +{ + ASProperty* p = NULL; + + for (u32 i = scope_top_obj; i < MAX_SCOPE_DEPTH; --i) + { + p = getProperty(scope_chain[i], string_id, name, name_len); + + if (p != NULL) + { + break; + } + } + + return p; +} + +ASProperty* getPropertyInThisScope(u32 string_id, const char* name, u32 name_len) +{ + return getProperty(scope_chain[scope_top_obj], string_id, name, name_len); +} + +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); } ActionStackValueType convertString(SWFAppContext* app_context, char* var_str) { if (STACK_TOP_TYPE == ACTION_STACK_VALUE_F32) { + float temp_val = VAL(float, &STACK_TOP_VALUE); 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)); + snprintf(var_str, 17, "%.15g", temp_val); } return ACTION_STACK_VALUE_STRING; @@ -57,20 +163,28 @@ void pushVar(SWFAppContext* app_context, ActionVar* var) { 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->heap_ptr : + (char*) var->value; + + PUSH_STR_ID(str_ptr, var->string_id, var->str_size); break; } - case ACTION_STACK_VALUE_STRING: + case ACTION_STACK_VALUE_FUNCTION: { - // 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_FUNC(var->value, var->string_id, var->func, var->args); - PUSH_STR_ID(str_ptr, var->str_size, var->string_id); + break; + } + + default: + { + PUSH(var->type, var->value); break; } @@ -82,15 +196,46 @@ void peekVar(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; + break; + } + + case ACTION_STACK_VALUE_STRING: + { + // For strings, mark as not owning memory (it's on the stack) + var->value = STACK_TOP_VALUE; + var->owns_memory = false; + var->string_id = STACK_TOP_ID; + + break; + } + + case ACTION_STACK_VALUE_FUNCTION: + { + var->value = STACK_TOP_VALUE; + var->func = (action_func) STACK_TOP_FUNC; + var->args = (u32*) STACK_TOP_FUNC_ARGS; + + break; + } + + default: + { + var->value = STACK_TOP_VALUE; + break; + } } +} + +void popVar(SWFAppContext* app_context, ActionVar* var) +{ + peekVar(app_context, var); - else - { - var->value = VAL(u64, &STACK_TOP_VALUE); - } + POP(); } void peekSecondVar(SWFAppContext* app_context, ActionVar* var) @@ -103,17 +248,17 @@ void peekSecondVar(SWFAppContext* app_context, ActionVar* var) var->value = (u64) &STACK_SECOND_TOP_VALUE; } - else + else if (STACK_SECOND_TOP_TYPE == ACTION_STACK_VALUE_STRING) { - var->value = VAL(u64, &STACK_SECOND_TOP_VALUE); + var->value = STACK_SECOND_TOP_VALUE; + var->owns_memory = false; + var->string_id = STACK_SECOND_TOP_ID; } -} - -void popVar(SWFAppContext* app_context, ActionVar* var) -{ - peekVar(app_context, var); - POP(); + else + { + var->value = STACK_SECOND_TOP_VALUE; + } } void actionAdd(SWFAppContext* app_context) @@ -281,6 +426,10 @@ void actionDivide(SWFAppContext* app_context) } } +// ================================================================== +// Comparison Operations +// ================================================================== + void actionEquals(SWFAppContext* app_context) { convertFloat(app_context); @@ -431,14 +580,9 @@ void actionNot(SWFAppContext* app_context) PUSH(ACTION_STACK_VALUE_F32, VAL(u64, &result)); } -int evaluateCondition(SWFAppContext* app_context) -{ - ActionVar v; - convertFloat(app_context); - popVar(app_context, &v); - - return v.value != 0.0f; -} +// ================================================================== +// String Operations +// ================================================================== int strcmp_list_a_list_b(u64 a_value, u64 b_value) { @@ -723,6 +867,135 @@ void actionStringAdd(SWFAppContext* app_context, char* a_str, char* b_str) } } +// ================================================================== +// Variable Operations +// ================================================================== + +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) + 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; + } + + default: + { + // Constant string - use scope object (O(lg(n))) + for (u32 i = scope_top_obj; i < MAX_SCOPE_DEPTH; --i) + { + p = getProperty(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); + + // 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; + + 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: + { + setProperty(app_context, _global, string_id, var_name, var_name_len, &value); + + return; + } + + default: + { + // Constant string - use scope object (O(lg(n))) + for (u32 i = scope_top_obj; i > 0; --i) + { + p = getProperty(scope_chain[i], string_id, var_name, var_name_len); + + if (p != NULL) + { + break; + } + } + + break; + } + } + + if (p != NULL) + { + p->value = value; + } + + else + { + setProperty(app_context, _global, string_id, var_name, var_name_len, &value); + } +} + +// ================================================================== +// Utility Operations +// ================================================================== + void actionTrace(SWFAppContext* app_context) { ActionStackValueType type = STACK_TOP_TYPE; @@ -735,6 +1008,12 @@ void actionTrace(SWFAppContext* app_context) break; } + case ACTION_STACK_VALUE_UNDEFINED: + { + printf("undefined\n"); + break; + } + case ACTION_STACK_VALUE_STR_LIST: { u64* str_list = (u64*) &STACK_TOP_VALUE; @@ -760,6 +1039,18 @@ void actionTrace(SWFAppContext* app_context) printf("%.15g\n", VAL(double, &STACK_TOP_VALUE)); break; } + + case ACTION_STACK_VALUE_INT: + { + printf("%d\n", (s32) STACK_TOP_VALUE); + break; + } + + default: + { + fprintf(stderr, "Bad print type: %d\n", type); + break; + } } fflush(stdout); @@ -767,75 +1058,1423 @@ void actionTrace(SWFAppContext* app_context) POP(); } -void actionGetVariable(SWFAppContext* app_context) +void actionGetTime(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; + u32 delta_ms = get_elapsed_ms() - start_time; + float delta_ms_f32 = (float) delta_ms; - if (string_id != 0) + PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &delta_ms_f32)); +} + +// ================================================================== +// EnumeratedName helper structures for property enumeration +// ================================================================== + +typedef struct EnumeratedName { + const char* name; + u32 name_length; + struct EnumeratedName* next; +} EnumeratedName; + +/** + * 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) { - // Constant string - use array (O(1)) - var = getVariableById(app_context, string_id); + if (current->name_length == name_length && + strncmp(current->name, name, name_length) == 0) + { + return 1; // Found - property was already enumerated + } + current = current->next; } - - else + 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) { - // Dynamic string - use hashmap (O(n)) - var = getVariable(app_context, var_name, var_name_len); + 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(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; + //~ } + //~ } - assert(var != NULL); + //~ // Free the enumerated names list + //~ freeEnumeratedNames(enumerated_head); - // Push variable value to stack - PUSH_VAR(var); +//~ #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; + //~ } } -void actionSetVariable(SWFAppContext* app_context) +int evaluateCondition(SWFAppContext* app_context) { - // Stack layout: [value] [name] <- sp - // We need value at top, name at second + ActionVar v; + convertFloat(app_context); + popVar(app_context, &v); + + return v.value != 0.0f; +} + +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) // Read variable name info + // Stack layout for strings: +0=type, +4=oldSP, +8=length, +12=string_id, +16=pointer 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; + // 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; + peekVar(app_context, &value_var); + + // 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); + + // Pop both value and name + POP_2(); +} + +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 + + // Read variable name info + u32 string_id = STACK_TOP_ID; + char* var_name = (char*) STACK_TOP_VALUE; + u32 var_name_len = STACK_TOP_N; + + // 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); + + // Pop the name + POP(); +} + +void actionTypeof(SWFAppContext* app_context, char* str_buffer) +{ + //~ // Peek at the type without modifying value + //~ u8 type = STACK_TOP_TYPE; + + //~ // Pop the value + //~ POP(); + + //~ // Determine type string based on stack type + //~ const char* type_str; + //~ switch (type) + //~ { + //~ case ACTION_STACK_VALUE_F32: + //~ case ACTION_STACK_VALUE_F64: + //~ type_str = "number"; + //~ 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: + //~ case ACTION_STACK_VALUE_ARRAY: + //~ // 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 + //~ int len = strlen(type_str); + //~ strncpy(str_buffer, type_str, 16); + //~ str_buffer[len] = '\0'; + //~ PUSH_STR(str_buffer, 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) + //~ { + //~ // Check if property exists in this scope object + //~ ActionVar* prop = getProperty(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(ACTION_STACK_VALUE_F32, VAL(u32, &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(ACTION_STACK_VALUE_F32, VAL(u32, &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(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(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++; + + //~ // Check if this prototype matches the constructor's prototype + //~ if (current_proto_var->type == ACTION_STACK_VALUE_OBJECT) + //~ { + //~ 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(current_proto, 0, "__proto__", 9); + //~ } + //~ else + //~ { + //~ // Non-object in prototype chain, stop + //~ break; + //~ } + //~ } + + //~ // Check interface implementation (ActionScript 2.0 implements keyword) + //~ if (implementsInterface(obj, ctor)) + //~ { + //~ return 1; + //~ } + + // Not found in prototype chain or interfaces + return 0; +} + +// ================================================================== +// Register Storage (up to 256 registers for SWF 5+) +// ================================================================== + +#define MAX_REGISTERS 256 +static ActionVar g_registers[MAX_REGISTERS]; + +void actionStoreRegister(SWFAppContext* app_context, u8 register_num) +{ + // Validate register number + if (register_num >= MAX_REGISTERS) { + return; + } + + // Peek the top of stack (don't pop!) + ActionVar value; + peekVar(app_context, &value); - if (string_id != 0) + // Store value in register + g_registers[register_num] = value; +} + +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(ACTION_STACK_VALUE_F32, VAL(u32, &(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) +{ + // 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) + + // Pop the value to assign + ActionVar value_var; + popVar(app_context, &value_var); + + // Pop the property name + // The property name should be a string on the stack + ActionVar prop_name_var; + popVar(app_context, &prop_name_var); + + // Get the property name as string + u32 string_id = prop_name_var.string_id; + const char* prop_name = (const char*) prop_name_var.value; + u32 prop_name_len = prop_name_var.str_size; + + if (prop_name_var.type == ACTION_STACK_VALUE_STRING) { - // Constant string - use array (O(1)) - var = getVariableById(app_context, string_id); + // If it's a string, use it directly + string_id = prop_name_var.string_id; + prop_name = (const char*) prop_name_var.value; + prop_name_len = prop_name_var.str_size; } + //~ else if (prop_name_var.type == ACTION_STACK_VALUE_F32 || prop_name_var.type == ACTION_STACK_VALUE_F64) + //~ { + //~ // If it's a number, convert it to string (for array indices) + //~ // Use a static buffer for conversion + //~ static char index_buffer[32]; + //~ if (prop_name_var.type == ACTION_STACK_VALUE_F32) + //~ { + //~ float f = VAL(float, &prop_name_var.value); + //~ snprintf(index_buffer, sizeof(index_buffer), "%.15g", f); + //~ } + //~ else + //~ { + //~ double d = VAL(double, &prop_name_var.value); + //~ snprintf(index_buffer, sizeof(index_buffer), "%.15g", d); + //~ } + //~ prop_name = index_buffer; + //~ prop_name_len = strlen(index_buffer); + //~ } + else { - // Dynamic string - use hashmap (O(n)) - var = getVariable(app_context, var_name, var_name_len); + EXC("Bad SetMember property\n"); + + // Unknown type for property name - error case + // Just pop the object and return + POP(); + return; } - assert(var != NULL); + // Pop the object + ActionVar obj_var; + popVar(app_context, &obj_var); - // Set variable value (uses existing string materialization!) - setVariableWithValue(app_context, var); + assert(obj_var.type == ACTION_STACK_VALUE_OBJECT); - // Pop both value and name - POP_2(); + // Check if the object is actually an object type + if (obj_var.type == ACTION_STACK_VALUE_OBJECT) + { + ASObject* obj = (ASObject*) obj_var.value; + + if (obj != NULL) + { + // Set the property on the object + setProperty(app_context, obj, string_id, prop_name, prop_name_len, &value_var); + } + } + + // If it's not an object type, we silently ignore the operation + // (Flash behavior for setting properties on non-objects) } -void actionGetTime(SWFAppContext* app_context) +void actionInitObject(SWFAppContext* app_context) { - u32 delta_ms = get_elapsed_ms() - start_time; - float delta_ms_f32 = (float) delta_ms; + // Step 1: Pop property count from stack + convertFloat(app_context); + 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++) + { + // Pop property name first (it's on top) + ActionVar name_var; + char f[17]; + convertString(app_context, f); + popVar(app_context, &name_var); + + // 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; + + // Handle string name + string_id = name_var.string_id; + name = name_var.owns_memory ? + name_var.heap_ptr : + (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); + } - PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &delta_ms_f32)); + // Step 4: Push object reference to stack + // The object has refcount = 1 from allocation + PUSH(ACTION_STACK_VALUE_OBJECT, (u64) obj); +} + +void actionDelete(SWFAppContext* app_context) +{ + // 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.heap_ptr : + (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(ACTION_STACK_VALUE_F32, VAL(u32, &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.heap_ptr : + (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(ACTION_STACK_VALUE_F32, VAL(u32, &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(ACTION_STACK_VALUE_F32, VAL(u32, &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(ACTION_STACK_VALUE_F32, VAL(u32, &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(ACTION_STACK_VALUE_F32, VAL(u32, &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(ACTION_STACK_VALUE_F32, VAL(u32, &result)); +} + +void actionGetMember(SWFAppContext* app_context) +{ + // 1. Convert and pop property name (top of stack) + const char* prop_name = (const char*) STACK_TOP_VALUE; + u32 prop_name_len = STACK_TOP_N; + u32 string_id = STACK_TOP_ID; + POP(); + + // 2. Pop object (second on stack) + ActionVar obj_var; + popVar(app_context, &obj_var); + + // 3. Handle different object types + if (obj_var.type == ACTION_STACK_VALUE_OBJECT) + { + // Handle AS object + ASObject* obj = (ASObject*) obj_var.value; + + // Look up property + ASProperty* prop = getProperty(obj, string_id, prop_name, prop_name_len); + + if (prop != NULL) + { + // Property found - push its value + pushVar(app_context, &prop->value); + } + + else + { + // Property not found - push undefined + PUSH_UNDEFINED(); + } + } + + else if (obj_var.type == ACTION_STACK_VALUE_STRING) + { + // Handle string properties + if (string_id == STR_ID_LENGTH) + { + PUSH(ACTION_STACK_VALUE_F32, obj_var.str_size); + } + + else + { + EXC_ARG("Tried to get property %s of String type\n", prop_name); + } + } + + else if (obj_var.type == ACTION_STACK_VALUE_ARRAY) + { + EXC("ARRAY GETMEMBER UNIMPLEMENTED\n"); + + // Handle array properties + ASArray* arr = (ASArray*) obj_var.value; + + if (arr == NULL) + { + PUSH_UNDEFINED(); + return; + } + + // Check if accessing the "length" property + if (string_id == STR_ID_LENGTH) + { + // Push array length as float + float len = (float) arr->length; + PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &len)); + } + + else + { + // Try to parse property name as an array index + char* endptr; + long index = strtol(prop_name, &endptr, 10); + + // Check if conversion was successful and entire string was consumed + if (*endptr == '\0' && index >= 0) + { + // Valid numeric index + ActionVar* elem = getArrayElement(arr, (u32)index); + if (elem != NULL) + { + // Element exists - push its value + pushVar(app_context, elem); + } + else + { + // Index out of bounds - push undefined + PUSH_UNDEFINED(); + } + } + else + { + // Non-numeric property name - arrays don't have other properties + PUSH_UNDEFINED(); + } + } + } + + else + { + // Other primitive types (number, undefined, etc.) - push undefined + PUSH_UNDEFINED(); + } +} + +void actionNewObject(SWFAppContext* app_context) +{ + // 1. Pop constructor name (string) + ActionVar ctor_name_var; + popVar(app_context, &ctor_name_var); + + // 2. Pop number of arguments + ActionVar num_args_var; + popVar(app_context, &num_args_var); + u32 num_args = (u32) num_args_var.value; + + // Try to find user-defined constructor function + ASProperty* func_p = searchScopesForProperty(ctor_name_var.string_id, NULL, 0); + + if (func_p != NULL) + { + // User-defined constructor found + // Create new object to serve as 'this' + ASObject* this = allocObject(app_context); + ActionVar this_v; + this_v.type = ACTION_STACK_VALUE_OBJECT; + this_v.value = (u64) this; + + scope_top_obj += 1; + setPropertyInThisScope(app_context, STR_ID_THIS, NULL, 0, &this_v); + + u32* args = func_p->value.args; + + // 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); + setPropertyInThisScope(app_context, args[i], NULL, 0, &v); + } + } + + // Call the constructor with 'this' binding + // Put 'this' and arguments in scope, call function + // Note: Constructor return value is discarded per spec + + func_p->value.func(app_context); + + POP(); + + scope_top_obj -= 1; + + PUSH_OBJ(this); + } + + else + { + EXC_ARG("Constructor function %s not found.\n", (char*) ctor_name_var.value); + } +} + +/** + * 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 + * + * Current implementation: + * - Built-in constructors supported: Array, Object, Date, String, Number, Boolean + * - String/Number/Boolean wrapper objects store primitive values in 'valueOf' property + * - Function objects as constructors: SUPPORTED (blank method name with function object) + * - User-defined constructors: SUPPORTED (method property containing function object) + * - 'this' binding: SUPPORTED for DefineFunction2, limited for DefineFunction + * - Constructor return value: Discarded per spec (always returns new object) + * + * Remaining limitations: + * - Prototype chains not implemented (requires __proto__ property support) + * - DefineFunction (type 1) has limited 'this' context support + */ +void actionNewMethod(SWFAppContext* app_context) +{ + // Pop constructor method name (string) + ActionVar ctor_name_var; + popVar(app_context, &ctor_name_var); + + // Pop object which holds the method + ActionVar object_var; + popVar(app_context, &object_var); + + // Pop number of arguments + ActionVar num_args_var; + popVar(app_context, &num_args_var); + u32 num_args = (u32) num_args_var.value; + + // Try to find constructor method + ASObject* obj = (ASObject*) object_var.value; + ASProperty* func_p = getProperty(obj, ctor_name_var.string_id, NULL, 0); + + if (func_p != NULL) + { + // Constructor found + // Create new object to serve as 'this' + ASObject* this = allocObject(app_context); + ActionVar this_v; + this_v.type = ACTION_STACK_VALUE_OBJECT; + this_v.value = (u64) this; + + scope_top_obj += 1; + setPropertyInThisScope(app_context, STR_ID_THIS, NULL, 0, &this_v); + + u32* args = func_p->value.args; + + // 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); + setPropertyInThisScope(app_context, args[i], NULL, 0, &v); + } + } + + // Call the constructor with 'this' binding + // Put 'this' and arguments in scope, call function + // Note: Constructor return value is discarded per spec + + func_p->value.func(app_context); + + POP(); + + scope_top_obj -= 1; + + PUSH_OBJ(this); + } + + else + { + EXC_ARG("Constructor method %s not found.\n", (char*) ctor_name_var.value); + } +} + +void actionDefineFunction(SWFAppContext* app_context, u32 string_id, action_func func, u32* args, bool anonymous) +{ + assert(string_id != 0); + + // Create function object + ASObject* this = HALLOC(sizeof(ASObject)); + + // If named, store in variable + if (!anonymous) + { + ActionVar func_var; + func_var.type = ACTION_STACK_VALUE_FUNCTION; + func_var.func = func; + func_var.args = args; + + setPropertyInThisScope(app_context, string_id, NULL, 0, &func_var); + } + + else + { + // Anonymous function: push to stack + PUSH_FUNC(this, string_id, func, args); + } +} + +void actionCallFunction(SWFAppContext* app_context) +{ + // 1. Pop function name (string) from stack + char* func_name = (char*) STACK_TOP_VALUE; + u32 string_id = STACK_TOP_ID; + POP(); + + // 2. Pop number of arguments + ActionVar num_args_var; + popVar(app_context, &num_args_var); + u32 num_args = (u32) num_args_var.value; + + ASProperty* func_p = searchScopesForProperty(string_id, NULL, 0); + + if (func_p != NULL) + { + // Simple DefineFunction (type 1) + // Simple functions expect arguments on the stack, not in an array + // We need to push arguments back onto stack in correct order + + ActionVar* func_v = &func_p->value; + u32* args = func_p->value.args; + + scope_top_obj += 1; + + // 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); + setPropertyInThisScope(app_context, args[i], NULL, 0, &v); + } + } + + // Call the simple function + // It will execute body and push a return value + func_v->func(app_context); + + // TODO: DESTROY OBJECT'S PROPERTIES + scope_top_obj -= 1; + } + + else + { + // Function not found - throw + EXC_ARG("Function not found: %s\n", func_name); + } +} + +// Helper function to call built-in string methods +// Returns 1 if method was handled, 0 if not found +static int callStringPrimitiveMethod(SWFAppContext* app_context, char* str_buffer, + const char* str_value, u32 str_len, + const char* method_name, u32 method_name_len, + ActionVar* args, u32 num_args) +{ + //~ // toUpperCase() - no arguments + //~ if (method_name_len == 11 && strncmp(method_name, "toUpperCase", 11) == 0) + //~ { + //~ // Convert string to uppercase + //~ int i; + //~ for (i = 0; i < str_len && i < 16; i++) + //~ { + //~ char c = str_value[i]; + //~ if (c >= 'a' && c <= 'z') + //~ { + //~ str_buffer[i] = c - ('a' - 'A'); + //~ } + //~ else + //~ { + //~ str_buffer[i] = c; + //~ } + //~ } + //~ str_buffer[i] = '\0'; + //~ PUSH_STR(str_buffer, i); + //~ return 1; + //~ } + + //~ // toLowerCase() - no arguments + //~ if (method_name_len == 11 && strncmp(method_name, "toLowerCase", 11) == 0) + //~ { + //~ // Convert string to lowercase + //~ int i; + //~ for (i = 0; i < str_len && i < 16; i++) + //~ { + //~ char c = str_value[i]; + //~ if (c >= 'A' && c <= 'Z') + //~ { + //~ str_buffer[i] = c + ('a' - 'A'); + //~ } + //~ else + //~ { + //~ str_buffer[i] = c; + //~ } + //~ } + //~ str_buffer[i] = '\0'; + //~ PUSH_STR(str_buffer, i); + //~ return 1; + //~ } + + //~ // charAt(index) - 1 argument + //~ if (method_name_len == 6 && strncmp(method_name, "charAt", 6) == 0) + //~ { + //~ int index = 0; + //~ if (num_args > 0 && args[0].type == ACTION_STACK_VALUE_F32) + //~ { + //~ index = (int)VAL(float, &args[0].value); + //~ } + + //~ // Bounds check + //~ if (index < 0 || index >= str_len) + //~ { + //~ str_buffer[0] = '\0'; + //~ PUSH_STR(str_buffer, 0); + //~ } + //~ else + //~ { + //~ str_buffer[0] = str_value[index]; + //~ str_buffer[1] = '\0'; + //~ PUSH_STR(str_buffer, 1); + //~ } + //~ return 1; + //~ } + + //~ // substr(start, length) - 2 arguments + //~ if (method_name_len == 6 && strncmp(method_name, "substr", 6) == 0) + //~ { + //~ int start = 0; + //~ int length = str_len; + + //~ if (num_args > 0 && args[0].type == ACTION_STACK_VALUE_F32) + //~ { + //~ start = (int)VAL(float, &args[0].value); + //~ } + //~ if (num_args > 1 && args[1].type == ACTION_STACK_VALUE_F32) + //~ { + //~ length = (int)VAL(float, &args[1].value); + //~ } + + //~ // Handle negative start (count from end) + //~ if (start < 0) + //~ { + //~ start = str_len + start; + //~ if (start < 0) start = 0; + //~ } + + //~ // Bounds check + //~ if (start >= str_len || length <= 0) + //~ { + //~ str_buffer[0] = '\0'; + //~ PUSH_STR(str_buffer, 0); + //~ } + //~ else + //~ { + //~ if (start + length > str_len) + //~ { + //~ length = str_len - start; + //~ } + + //~ int i; + //~ for (i = 0; i < length && i < 16; i++) + //~ { + //~ str_buffer[i] = str_value[start + i]; + //~ } + //~ str_buffer[i] = '\0'; + //~ PUSH_STR(str_buffer, i); + //~ } + //~ return 1; + //~ } + + //~ // substring(start, end) - 2 arguments (different from substr!) + //~ if (method_name_len == 9 && strncmp(method_name, "substring", 9) == 0) + //~ { + //~ int start = 0; + //~ int end = str_len; + + //~ if (num_args > 0 && args[0].type == ACTION_STACK_VALUE_F32) + //~ { + //~ start = (int)VAL(float, &args[0].value); + //~ } + //~ if (num_args > 1 && args[1].type == ACTION_STACK_VALUE_F32) + //~ { + //~ end = (int)VAL(float, &args[1].value); + //~ } + + //~ // Clamp to valid range + //~ if (start < 0) start = 0; + //~ if (end < 0) end = 0; + //~ if (start > str_len) start = str_len; + //~ if (end > str_len) end = str_len; + + //~ // Swap if start > end + //~ if (start > end) + //~ { + //~ int temp = start; + //~ start = end; + //~ end = temp; + //~ } + + //~ int length = end - start; + //~ if (length <= 0) + //~ { + //~ str_buffer[0] = '\0'; + //~ PUSH_STR(str_buffer, 0); + //~ } + //~ else + //~ { + //~ int i; + //~ for (i = 0; i < length && i < 16; i++) + //~ { + //~ str_buffer[i] = str_value[start + i]; + //~ } + //~ str_buffer[i] = '\0'; + //~ PUSH_STR(str_buffer, i); + //~ } + //~ return 1; + //~ } + + //~ // indexOf(searchString, startIndex) - 1-2 arguments + //~ if (method_name_len == 7 && strncmp(method_name, "indexOf", 7) == 0) + //~ { + //~ const char* search_str = ""; + //~ int search_len = 0; + //~ int start_index = 0; + + //~ if (num_args > 0) + //~ { + //~ if (args[0].type == ACTION_STACK_VALUE_STRING) + //~ { + //~ search_str = (const char*)args[0].value; + //~ search_len = args[0].str_size; + //~ } + //~ } + //~ if (num_args > 1 && args[1].type == ACTION_STACK_VALUE_F32) + //~ { + //~ start_index = (int)VAL(float, &args[1].value); + //~ if (start_index < 0) start_index = 0; + //~ } + + //~ // Search for substring + //~ int found_index = -1; + //~ if (search_len == 0) + //~ { + //~ found_index = start_index <= str_len ? start_index : -1; + //~ } + //~ else + //~ { + //~ for (int i = start_index; i <= str_len - search_len; i++) + //~ { + //~ int match = 1; + //~ for (int j = 0; j < search_len; j++) + //~ { + //~ if (str_value[i + j] != search_str[j]) + //~ { + //~ match = 0; + //~ break; + //~ } + //~ } + //~ if (match) + //~ { + //~ found_index = i; + //~ break; + //~ } + //~ } + //~ } + + //~ float result = (float)found_index; + //~ PUSH(ACTION_STACK_VALUE_F32, VAL(u32, &result)); + //~ return 1; + //~ } + + // Method not found + return 0; +} + +void actionCallMethod(SWFAppContext* app_context) +{ + // Pop method name (string) from stack + char* func_name = (char*) STACK_TOP_VALUE; + u32 string_id = STACK_TOP_ID; + POP(); + + // Pop object from stack + ActionVar this_v; + popVar(app_context, &this_v); + + // Pop number of arguments + ActionVar num_args_var; + popVar(app_context, &num_args_var); + u32 num_args = (u32) num_args_var.value; + + ActionVar* func_v = NULL; + + if (string_id != STR_ID_EMPTY) + { + ASObject* this = (ASObject*) this_v.value; + + ASProperty* meth_p = getProperty(this, string_id, NULL, 0); + func_v = &meth_p->value; + } + + else + { + EXC("Callable objects not implemented (ActionCallMethod)."); + } + + if (func_v != NULL) + { + // Simple DefineFunction (type 1) + // Simple functions expect arguments on the stack, not in an array + // We need to push arguments back onto stack in correct order + + u32* args = func_v->args; + + scope_top_obj += 1; + + // 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); + setPropertyInThisScope(app_context, args[i], NULL, 0, &v); + } + } + + // Call the simple function + // It will execute body and push a return value + func_v->func(app_context); + + // TODO: DESTROY OBJECT'S PROPERTIES + scope_top_obj -= 1; + } + + else + { + // Function not found - throw + EXC_ARG("Function not found: %s\n", func_name); + } } \ No newline at end of file diff --git a/src/actionmodern/objects.c b/src/actionmodern/objects.c new file mode 100644 index 0000000..b199942 --- /dev/null +++ b/src/actionmodern/objects.c @@ -0,0 +1,558 @@ +#include +#include +#include +#include + +#include + +#include + +/** + * Object Allocation + * + * Allocates a new ASObject with the specified initial capacity. + * Returns object with refcount = 1 (caller owns the initial reference). + */ +ASObject* allocObject(SWFAppContext* app_context) +{ + ASObject* obj = (ASObject*) HALLOC(sizeof(ASObject)); + + rbtree_init(&obj->t, sizeof(ASProperty)); + obj->refcount = 1; // Initial reference owned by caller + + 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) +{ + if (obj == NULL) + { + return; + } + + obj->refcount++; +} + +/** + * 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->refcount == 0) + { + // Free object + //~ FREE(obj); + } +} + +/** + * Get Property + * + * Retrieves a property value by name. + * Returns pointer to the ASProperty if found, or NULL if not found. + */ +ASProperty* getProperty(ASObject* this, u32 string_id, const char* name, u32 name_length) +{ + if (this == NULL || (string_id == 0 && name == NULL)) + { + return NULL; + } + + return (ASProperty*) rbtree_get(&this->t, string_id); +} + +/** + * Get Property With Prototype Chain + * + * Retrieves a property value by name, searching up the prototype chain via __proto__. + * Returns pointer to ActionVar, or NULL if property not found in entire chain. + * + * This implements proper prototype-based inheritance for ActionScript. + */ +ActionVar* getPropertyWithPrototype(ASObject* obj, const char* name, u32 name_length) +{ + //~ if (obj == NULL || name == NULL) + //~ { + //~ return NULL; + //~ } + + //~ ASObject* current = obj; + //~ int max_depth = 100; // Prevent infinite loops in circular prototype chains + //~ int depth = 0; + + //~ while (current != NULL && depth < max_depth) + //~ { + //~ depth++; + + //~ // Search own properties first + //~ ActionVar* prop = getProperty(current, name, name_length); + //~ if (prop != NULL) + //~ { + //~ return prop; + //~ } + + //~ // Property not found on this object - walk up to __proto__ + //~ ActionVar* proto_var = getProperty(current, "__proto__", 9); + //~ if (proto_var == NULL || proto_var->type != ACTION_STACK_VALUE_OBJECT) + //~ { + //~ // No __proto__ property or not an object - end of chain + //~ break; + //~ } + + //~ // Move to next object in prototype chain + //~ current = (ASObject*) proto_var->value; + //~ } + + return NULL; // Property not found in entire prototype chain +} + +/** + * 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 (this == NULL || (string_id == 0 && name == NULL) || value == NULL) + { + return; + } + + ASProperty* p = getProperty(this, string_id, name, name_length); + + if (p != NULL) + { + // Property exists - update value + + // Release old value if it was an object + if (p->value.type == ACTION_STACK_VALUE_OBJECT) + { + ASObject* old_obj = (ASObject*) p->value.value; + releaseObject(app_context, old_obj); + } + + // Free old string if it owned memory + else if (p->value.type == ACTION_STACK_VALUE_STRING && + p->value.owns_memory) + { + FREE(p->value.heap_ptr); + } + + // Set new value + p->value = *value; + + // Retain new value if it's an object + if (value->type == ACTION_STACK_VALUE_OBJECT) + { + ASObject* new_obj = (ASObject*) value->value; + retainObject(new_obj); + } + + return; + } + + // Property doesn't exist - create new one + p = (ASProperty*) RBT_GET_OR_INS(&this->t, string_id); + 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.heap_ptr); + //~ } + + //~ // 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(ASObject* obj) +{ + if (obj == NULL) + { + return NULL; + } + + // Look for "constructor" property + static const char* constructor_name = "constructor"; + ActionVar* ctor = &getProperty(obj, 0, constructor_name, 11)->value; + + if (ctor != NULL && ctor->type == ACTION_STACK_VALUE_OBJECT) + { + return (ASObject*) ctor->value; + } + + 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.heap_ptr : + (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].heap_ptr : + (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].heap_ptr); + } + } + + // 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].heap_ptr); + } + } + + // 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 +} diff --git a/src/actionmodern/runtime_api/Math.c b/src/actionmodern/runtime_api/Math.c new file mode 100644 index 0000000..efb2377 --- /dev/null +++ b/src/actionmodern/runtime_api/Math.c @@ -0,0 +1,12 @@ +#include + +#include + +void Math_abs(SWFAppContext* app_context) +{ + ASProperty* arg1 = getPropertyInThisScope(STR_ID_X, NULL, 0); + + s32 val = (s32) arg1->value.value; + + PUSH(ACTION_STACK_VALUE_INT, val < 0 ? -val : val); +} \ 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..4f02c9a --- /dev/null +++ b/src/actionmodern/runtime_api/Object.c @@ -0,0 +1,8 @@ +#include + +#include + +void new_Object(SWFAppContext* app_context) +{ + RETURN_VOID(); +} \ 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..db2c629 --- /dev/null +++ b/src/apis/rbtree/rbtree.c @@ -0,0 +1,67 @@ +#include + +#include + +static int node_cmp_u32(const struct rb_node* n, const void* v) +{ + return *((u32*) v) - ((rbnode*) n)->string_id; +} + +/** 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 node The node to insert + * + * \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, + int (*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; + int 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 = *((u32*) string_id); + rb_tree_insert_at(&T->t, y, (struct rb_node*) node, c < 0); + return node; +} + +#include + +void rbtree_init(rbtree* t, size_t struct_size) +{ + assert(struct_size != 0); + rb_tree_init((struct rb_tree*) t); + 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_u32); +} + +rbnode* rbtree_get_or_insert(SWFAppContext* app_context, rbtree* t, u32 string_id) +{ + return rb_tree_get_or_insert(app_context, t, &string_id, node_cmp_u32); +} \ No newline at end of file diff --git a/src/flashbang/flashbang.c b/src/flashbang/flashbang.c index 3c2eaef..46a1024 100644 --- a/src/flashbang/flashbang.c +++ b/src/flashbang/flashbang.c @@ -909,7 +909,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]; diff --git a/src/libswf/swf.c b/src/libswf/swf.c index fcfb7a4..bb119ac 100644 --- a/src/libswf/swf.c +++ b/src/libswf/swf.c @@ -19,7 +19,7 @@ size_t max_depth = 0; FlashbangContext* context; -void tagInit(); +void tagInit(app_context); void tagMain(SWFAppContext* app_context) { @@ -94,7 +94,7 @@ void swfStart(SWFAppContext* app_context) initVarArray(app_context, app_context->max_string_id); - initTime(); + initActions(app_context); initMap(); tagInit(app_context); 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..4f5b3b9 100644 --- a/src/libswf/tag.c +++ b/src/libswf/tag.c @@ -1,3 +1,5 @@ +#ifndef NO_GRAPHICS + #include #include #include @@ -16,18 +18,18 @@ void tagSetBackgroundColor(u8 red, u8 green, u8 blue) void tagShowFrame(SWFAppContext* app_context) { flashbang_open_pass(context); - + for (size_t i = 1; i <= max_depth; ++i) { DisplayObject* obj = &display_list[i]; - + if (obj->char_id == 0) { continue; } - + Character* ch = &dictionary[obj->char_id]; - + switch (ch->type) { case CHAR_TYPE_SHAPE: @@ -36,31 +38,31 @@ void tagShowFrame(SWFAppContext* app_context) 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) + for (u32 j = 0; j < ch->text_size; ++j) { - 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); + u32 glyph_index = 2*app_context->text_data[ch->text_start + j]; + flashbang_draw_shape(context, app_context->glyph_data[glyph_index], app_context->glyph_data[glyph_index + 1], ch->transform_start + j); } break; } } - + flashbang_close_pass(context); } -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)); - + 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)); - + dictionary[char_id].type = CHAR_TYPE_TEXT; dictionary[char_id].text_start = text_start; dictionary[char_id].text_size = text_size; @@ -68,20 +70,20 @@ 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; } } -void defineBitmap(size_t offset, size_t size, u32 width, u32 height) +void defineBitmap(u32 offset, u32 size, u32 width, u32 height) { flashbang_upload_bitmap(context, offset, size, width, height); } @@ -89,4 +91,6 @@ void defineBitmap(size_t offset, size_t size, u32 width, u32 height) void finalizeBitmaps() { flashbang_finalize_bitmaps(context); -} \ No newline at end of file +} + +#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..4c5d1ad 100644 --- a/src/memory/heap.c +++ b/src/memory/heap.c @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -13,7 +15,9 @@ void heap_init(SWFAppContext* app_context, size_t size) void* heap_alloc(SWFAppContext* app_context, size_t size) { - return o1heapAllocate(app_context->heap_instance, size); + void* ret = o1heapAllocate(app_context->heap_instance, size); + assert(ret != NULL); + return ret; } void heap_free(SWFAppContext* app_context, void* ptr)