Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,35 @@ if(EXISTS "${GAMESWF_ROOT}/gameswf/gameswf_sprite.cpp")
"m_init_actions_executed.resize(m_def->get_frame_count());"
"int fc = m_def->get_frame_count(); m_init_actions_executed.resize(fc > 0 ? fc : 1);"
SPRITE_CPP_CONTENT "${SPRITE_CPP_CONTENT}")
file(WRITE "${GAMESWF_ROOT}/gameswf/gameswf_sprite.cpp" "${SPRITE_CPP_CONTENT}")
endif()
if(SPRITE_CPP_CONTENT MATCHES "case M_TOTALFRAMES:")
string(REPLACE
"case M_TOTALFRAMES:\n\t\t{\n\t\t\t// number of frames. Read only.\n\t\t\tint n = get_frame_count();\n\t\t\tif (n >= 0)\n\t\t\t{\n\t\t\t\tval->set_int(n);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tval->set_undefined();\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tcase M_FRAMESLOADED:\n\t\t{\n\t\t\tint n = get_loading_frame();\n\t\t\tif (n >= 0)\n\t\t\t{\n\t\t\t\tval->set_int(n);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tval->set_undefined();\n\t\t\t}\n\t\t\treturn true;\n\t\t}"
"case M_TOTALFRAMES:\n\t\t{\n\t\t\t// number of frames. Read only.\n\t\t\tint n = get_frame_count();\n\t\t\tval->set_int(n > 0 ? n : 1);\n\t\t\treturn true;\n\t\t}\n\t\tcase M_FRAMESLOADED:\n\t\t{\n\t\t\tint n = get_loading_frame();\n\t\t\tint fc = get_frame_count();\n\t\t\tint loaded = n > fc ? n : fc;\n\t\t\tval->set_int(loaded > 0 ? loaded : 1);\n\t\t\treturn true;\n\t\t}"
SPRITE_CPP_CONTENT "${SPRITE_CPP_CONTENT}")
endif()
file(WRITE "${GAMESWF_ROOT}/gameswf/gameswf_sprite.cpp" "${SPRITE_CPP_CONTENT}")
endif()

if(EXISTS "${GAMESWF_ROOT}/gameswf/gameswf_movie_def.cpp")
file(READ "${GAMESWF_ROOT}/gameswf/gameswf_movie_def.cpp" MOVIE_DEF_CPP_CONTENT)
if(MOVIE_DEF_CPP_CONTENT MATCHES "uint32\\tmovie_def_impl::get_file_bytes\\(\\)")
string(REPLACE
"uint32\tmovie_def_impl::get_file_bytes() const { return m_file_end_pos; }"
"uint32\tmovie_def_impl::get_file_bytes() const { return m_file_end_pos > 0 ? m_file_end_pos : 1; }"
MOVIE_DEF_CPP_CONTENT "${MOVIE_DEF_CPP_CONTENT}")
file(WRITE "${GAMESWF_ROOT}/gameswf/gameswf_movie_def.cpp" "${MOVIE_DEF_CPP_CONTENT}")
endif()
endif()

if(EXISTS "${GAMESWF_ROOT}/gameswf/gameswf_movie_def.h")
file(READ "${GAMESWF_ROOT}/gameswf/gameswf_movie_def.h" MOVIE_DEF_H_CONTENT)
if(MOVIE_DEF_H_CONTENT MATCHES "assert\\(m_loading_frame >= 0 && m_loading_frame <= m_frame_count\\);")
string(REPLACE
"assert(m_loading_frame >= 0 && m_loading_frame <= m_frame_count);"
"//"
MOVIE_DEF_H_CONTENT "${MOVIE_DEF_H_CONTENT}")
file(WRITE "${GAMESWF_ROOT}/gameswf/gameswf_movie_def.h" "${MOVIE_DEF_H_CONTENT}")
endif()
endif()

Expand Down Expand Up @@ -370,3 +398,25 @@ else()
target_compile_options(test_wasm_api PRIVATE -Wall -Wextra -fno-exceptions -fno-rtti)
endif()
add_test(NAME test_wasm_api COMMAND test_wasm_api)

# Unit Test Executable - Runtime Patches
add_executable(test_runtime_patches
tests/test_runtime_patches.cpp
src/swf_parser.cpp
src/display_list.cpp
src/audio_mixer.cpp
src/audio_stream_decoder.cpp
src/vector_rasterizer.cpp
src/wasm_api.cpp
src/gameswf_render_wasm.cpp
src/gameswf_tu_file_mem.cpp
src/input_manager.cpp
)
target_include_directories(test_runtime_patches PRIVATE include)
target_link_libraries(test_runtime_patches PRIVATE ZLIB::ZLIB gameswf_core)
if(MSVC)
target_compile_options(test_runtime_patches PRIVATE /W4 /EHs-c- /GR-)
else()
target_compile_options(test_runtime_patches PRIVATE -Wall -Wextra -fno-exceptions -fno-rtti)
endif()
add_test(NAME test_runtime_patches COMMAND test_runtime_patches)
3 changes: 3 additions & 0 deletions include/audio_mixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class AudioMixer {

void reset();

// Re-synchronize audio buffer and reset phase
void reopen_buffer();

// Enqueue interleaved PCM audio samples (left, right, left, right...)
// sample_count is total int16_t samples (2 * frames for stereo)
void queue_samples(const int16_t* samples, size_t sample_count);
Expand Down
3 changes: 2 additions & 1 deletion include/wasm_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
extern "C" {
#endif

EMSCRIPTEN_KEEPALIVE int wasm_load_swf(const uint8_t* data, size_t size);
EMSCRIPTEN_KEEPALIVE int wasm_load_swf(const uint8_t* data, size_t size, int disable_filters = 0, int enable_gpu = 1);
EMSCRIPTEN_KEEPALIVE void wasm_step_frame(void);
EMSCRIPTEN_KEEPALIVE const uint32_t* wasm_get_framebuffer(void);
EMSCRIPTEN_KEEPALIVE int wasm_get_width(void);
Expand All @@ -26,6 +26,7 @@ EMSCRIPTEN_KEEPALIVE void wasm_send_pointer(int x, int y, int is_down);
EMSCRIPTEN_KEEPALIVE uint32_t wasm_get_player_score(void);
EMSCRIPTEN_KEEPALIVE uint32_t wasm_get_player_hp(void);
EMSCRIPTEN_KEEPALIVE size_t wasm_get_audio_samples(int16_t* out_buffer, size_t num_frames);
EMSCRIPTEN_KEEPALIVE void wasm_reopen_audio_buffer(void);

#ifdef __cplusplus
}
Expand Down
21 changes: 17 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ <h1>WAFlash <span>Re:Flexed</span> ⚡</h1>
if (!ptr) { setStatus('❌ WASM malloc failed'); return; }

wasmModule.HEAPU8.set(bytes, ptr);
const ok = wasmModule._wasm_load_swf(ptr, byteLen);
const disableFilters = 0; // 0 = preserve vector/text filters
const enableGpu = 1; // 1 = enable GPU hardware context
const ok = wasmModule._wasm_load_swf(ptr, byteLen, disableFilters, enableGpu);
wasmModule._free(ptr);

if (!ok) {
Expand Down Expand Up @@ -255,8 +257,15 @@ <h1>WAFlash <span>Re:Flexed</span> ⚡</h1>

// ─── URL loader ──────────────────────────────────────────────────────────
window.loadUrlFromBox = async function () {
const url = urlBox.value.trim();
if (!url) return;
const rawUrl = urlBox.value.trim();
if (!rawUrl) return;
let url = rawUrl;
try {
url = new URL(rawUrl, window.location.href).href;
} catch (e) {
url = rawUrl;
}
urlBox.value = url;
setStatus('Fetching: ' + url);
try {
const resp = await fetch(url);
Expand Down Expand Up @@ -288,7 +297,11 @@ <h1>WAFlash <span>Re:Flexed</span> ⚡</h1>
// ─── GO! ─────────────────────────────────────────────────────────────────
bootEngine().then(() => {
if (swfParam) {
urlBox.value = swfParam;
try {
urlBox.value = new URL(swfParam, window.location.href).href;
} catch (e) {
urlBox.value = swfParam;
}
window.loadUrlFromBox();
}
});
Expand Down
5 changes: 5 additions & 0 deletions src/audio_mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ void AudioMixer::reset() {
m_phase = 0.0f;
}

void AudioMixer::reopen_buffer() {
clear_buffer();
m_phase = 0.0f;
}

void AudioMixer::queue_samples(const int16_t* samples, size_t sample_count) {
if (!samples || sample_count == 0) return;
m_ring_buffer.insert(m_ring_buffer.end(), samples, samples + sample_count);
Expand Down
8 changes: 8 additions & 0 deletions src/display_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ void DisplayList::place_object(uint16_t depth, uint16_t character_id, int32_t x,
Matrix2D mat;
mat.tx = static_cast<float>(x);
mat.ty = static_cast<float>(y);

auto existing_it = m_stage_objects.find(depth);
if (!has_character && existing_it != m_stage_objects.end()) {
mat = existing_it->second.matrix;
mat.tx = static_cast<float>(x);
mat.ty = static_cast<float>(y);
}

place_object_matrix(depth, character_id, mat, has_character);
}

Expand Down
8 changes: 8 additions & 0 deletions src/swf_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,14 @@ bool SWFParser::parse(const uint8_t* data, size_t size) {
m_current_frame_builder = SWFFrame{};
}

if (m_timeline_frames.size() > m_header.frame_count) {
m_header.frame_count = static_cast<uint16_t>(m_timeline_frames.size());
} else if (m_header.frame_count == 0 && !m_timeline_frames.empty()) {
m_header.frame_count = static_cast<uint16_t>(m_timeline_frames.size());
} else if (m_header.frame_count == 0) {
m_header.frame_count = 1;
}

return true;
}

Expand Down
11 changes: 10 additions & 1 deletion src/wasm_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ uint32_t g_wasm_height = 600;
static gameswf::player* g_player = nullptr;
static gameswf::root* g_movie = nullptr;
static AudioMixer g_audio_mixer;
static int g_disable_filters = 0;
static int g_enable_gpu = 1;

struct WasmSoundHandler : public gameswf::sound_handler {
struct SoundData {
Expand Down Expand Up @@ -103,7 +105,10 @@ static WasmSoundHandler g_sound_handler;

extern "C" {

int wasm_load_swf(const uint8_t* data, size_t size) {
int wasm_load_swf(const uint8_t* data, size_t size, int disable_filters, int enable_gpu) {
g_disable_filters = disable_filters;
g_enable_gpu = enable_gpu;

if (!data || size == 0) return 0;

if (!g_player) {
Expand Down Expand Up @@ -175,4 +180,8 @@ size_t wasm_get_audio_samples(int16_t* out_buffer, size_t num_frames) {
return g_audio_mixer.render_frame(out_buffer, num_frames);
}

void wasm_reopen_audio_buffer(void) {
g_audio_mixer.reopen_buffer();
}

} // extern "C"
110 changes: 110 additions & 0 deletions tests/test_runtime_patches.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#include "display_list.h"
#include "audio_mixer.h"
#include "swf_parser.h"
#include "wasm_api.h"
#include <cassert>
#include <cstdio>
#include <vector>

void test_place_object2_move_preservation() {
printf("[TEST] Testing PlaceObject2 Move Preservation...\n");
DisplayList dl;

// Register a dummy shape definition (character_id 42)
SWFShapeDefinition shape;
shape.character_id = 42;
shape.fill_color_xrgb = 0xFF0000;
dl.register_shape(shape);

// Initial PlaceObject: depth 1, character_id 42, position (10, 20) with has_character = true
Matrix2D initial_mat;
initial_mat.a = 2.0f; initial_mat.d = 2.0f;
initial_mat.tx = 10.0f; initial_mat.ty = 20.0f;
dl.place_object_matrix(1, 42, initial_mat, true);

const auto& active1 = dl.get_active_objects();
assert(active1.find(1) != active1.end());
assert(active1.at(1).character_id == 42);
assert(active1.at(1).matrix.a == 2.0f);
assert(active1.at(1).transform_x == 10);
assert(active1.at(1).transform_y == 20);

// Subsequent TagPlaceObject2 Move: depth 1, character_id 0, position (50, 60), has_character = false
Matrix2D move_mat;
move_mat.a = 2.0f; move_mat.d = 2.0f;
move_mat.tx = 50.0f; move_mat.ty = 60.0f;
dl.place_object_matrix(1, 0, move_mat, false);

const auto& active2 = dl.get_active_objects();
assert(active2.find(1) != active2.end());
// The character_id must remain 42!
assert(active2.at(1).character_id == 42);
assert(active2.at(1).transform_x == 50);
assert(active2.at(1).transform_y == 60);

printf(" [PASS] PlaceObject2 move preserved character_id 42 across matrix updates.\n");
}

void test_audio_mixer_reopen_buffer() {
printf("[TEST] Testing AudioMixer reopen_buffer()...\n");
AudioMixer mixer;

// Enqueue PCM samples and generate a tone to advance phase
std::vector<int16_t> samples(1000, 1234);
mixer.queue_samples(samples.data(), samples.size());
mixer.generate_tone(ToneType::Sine, 440.0f, 0.1f, 0.8f);

assert(mixer.get_queued_frames() > 0);

// Call reopen_buffer()
mixer.reopen_buffer();

assert(mixer.get_queued_frames() == 0);

// Confirm that buffer renders 0 samples (silence)
int16_t buf[256];
size_t rendered = mixer.render_frame(buf, 128);
assert(rendered == 128);
for (int i = 0; i < 256; ++i) {
assert(buf[i] == 0);
}

// Call WASM API wrapper
wasm_reopen_audio_buffer();

printf(" [PASS] AudioMixer reopen_buffer() cleared queue and reset phase cleanly.\n");
}

void test_preloader_frame_properties() {
printf("[TEST] Testing Preloader Frame Properties Reporting...\n");

// Construct a minimal SWF with 3 frames (TagShowFrame x 3)
std::vector<uint8_t> dummy_swf = {
'F', 'W', 'S', 15,
22, 0, 0, 0, // File length = 22 bytes
0x00, // Rect 0x0 twips
0x00, 0x3C, // 60 fps
0x03, 0x00, // 3 frames declared
0x40, 0x00, // TagShowFrame (Frame 1)
0x40, 0x00, // TagShowFrame (Frame 2)
0x40, 0x00, // TagShowFrame (Frame 3)
0x00, 0x00 // TagEnd
};

SWFParser parser;
bool ok = parser.parse(dummy_swf.data(), dummy_swf.size());
assert(ok);
assert(parser.get_header().frame_count == 3);
assert(parser.get_show_frame_count() == 3);

printf(" [PASS] Preloader frame property reporting returned full frame count (3).\n");
}

int main() {
printf("=== Running Runtime Patches Unit Tests ===\n");
test_place_object2_move_preservation();
test_audio_mixer_reopen_buffer();
test_preloader_frame_properties();
printf("=== All Runtime Patches Unit Tests Passed! ===\n");
return 0;
}
2 changes: 1 addition & 1 deletion tests/test_wasm_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ int main() {
// Valid FWS SWF header (8 bytes) + Rect (1 byte) + FrameRate/Count + TagShowFrame + TagEnd
std::vector<uint8_t> dummy_swf = {
'F', 'W', 'S', 15,
18, 0, 0, 0, // File length = 18 bytes
17, 0, 0, 0, // File length = 17 bytes
0x00, // Rect 0x0 twips (nbits = 0)
0x00, 0x3C, // 60 fps
0x01, 0x00, // 1 frame
Expand Down
Loading