#include "../tdkpin_sound.h" #include #include #include #include enum { DGROUP_SOUND_LENGTHS = 0x03ea, DGROUP_INI_SUFFIX = 0x0442, DGROUP_INI_SECTION = 0x0446, DGROUP_INI_KEY = 0x044f, DGROUP_WAVE_TYPE = 0x0455, DGROUP_GAME_TILTED = 0x07c6, DGROUP_WAVE_CAPS = 0x43be, DGROUP_DEVICE_COUNT = 0x43ee, DGROUP_DEVICE_ID = 0x43f0, DGROUP_SOUND_BASE = 0x43f2, DGROUP_SOUND_ENABLED = 0x43f2, DGROUP_SOUND_PLAYING = 0x43f3, DGROUP_VOLUME_STEP = 0x43f4, CODE2_DOT = 0x0f2c, SOUND_COUNT = 22, }; static uint8_t g_dgroup[0x5000]; static uint8_t g_code2[0x1000]; HINSTANCE16 g_win16_instance; Win16FarPtr g_borland_command_line; static UINT16 g_profile_value; static unsigned g_profile_calls; static UINT16 g_wave_device_count; static UINT16 g_wave_caps_result; static uint32_t g_wave_support; static uint32_t g_wave_volume; static unsigned g_wave_count_calls; static unsigned g_wave_caps_calls; static unsigned g_wave_volume_calls; static uint16_t g_current_sound; static unsigned g_resource_stage; static uint16_t g_loaded_sounds[SOUND_COUNT]; static unsigned g_loaded_count; static unsigned g_stop_calls; static Win16FarPtr g_played_sound; static UINT16 g_play_flags; static BOOL16 g_play_result; static Win16FarPtr g_freed_sounds[SOUND_COUNT]; static unsigned g_free_count; static Win16FarPtr dgroup_at(uint16_t offset) { return win16_make_far_pointer(0x5000, offset); } static bool far_string_equals(Win16FarPtr string, const char *expected) { size_t index = 0; do { if (win16_read_u8(win16_far_add_offset(string, (uint16_t)index)) != (uint8_t)expected[index]) { return false; } } while (expected[index++] != '\0'); return true; } static uint16_t sound_index_offset(uint16_t sound_number) { return (uint16_t)(sound_number << 2); } static Win16FarPtr sound_buffer(uint16_t sound_number) { return win16_read_far_pointer( dgroup_at(DGROUP_SOUND_BASE), sound_index_offset(sound_number)); } static void set_sound_buffer(uint16_t sound_number, Win16FarPtr value) { win16_write_u32( dgroup_at(DGROUP_SOUND_BASE), sound_index_offset(sound_number), value); } static void initialize_image_data(void) { memset(g_dgroup, 0, sizeof(g_dgroup)); memset(g_code2, 0, sizeof(g_code2)); memcpy(&g_dgroup[DGROUP_INI_SUFFIX], "INI", 4); memcpy(&g_dgroup[DGROUP_INI_SECTION], "Settings", 9); memcpy(&g_dgroup[DGROUP_INI_KEY], "Sound", 6); memcpy(&g_dgroup[DGROUP_WAVE_TYPE], "WAV", 4); g_code2[CODE2_DOT] = 1; g_code2[CODE2_DOT + 1] = '.'; /* 03e6 belongs to the preceding object but is read for sound number zero. */ win16_write_u32(dgroup_at(0x03e6), 0, 0x00007070); for (uint16_t index = 0; index < SOUND_COUNT; index++) { win16_write_u32( dgroup_at(DGROUP_SOUND_LENGTHS), (uint16_t)(index * 4), g_sound_resource_byte_counts[index]); } } static void reset_calls(void) { g_profile_calls = 0; g_wave_count_calls = 0; g_wave_caps_calls = 0; g_wave_volume_calls = 0; g_current_sound = 0; g_resource_stage = 0; g_loaded_count = 0; g_stop_calls = 0; g_played_sound = 0; g_play_flags = 0; g_free_count = 0; } static void reset_sound_state(void) { memset(&g_dgroup[DGROUP_WAVE_CAPS], 0, 0x90); reset_calls(); } UINT16 GetModuleFileNameFar16( HINSTANCE16 module, Win16FarPtr buffer, UINT16 size) { static const char module_name[] = "C:\\GAMES\\TDKPIN.EXE"; assert(module == g_win16_instance && size == 0xff); size_t length = strlen(module_name); for (size_t index = 0; index < length; index++) { win16_write_u8( win16_far_add_offset(buffer, (uint16_t)index), (uint8_t)module_name[index]); } win16_write_u8(win16_far_add_offset(buffer, (uint16_t)length), 0); return (UINT16)length; } UINT16 GetPrivateProfileInt16( Win16FarPtr section, Win16FarPtr key, INT16 default_value, Win16FarPtr file_name) { assert(section == dgroup_at(DGROUP_INI_SECTION)); assert(key == dgroup_at(DGROUP_INI_KEY)); assert(default_value == 1); assert(far_string_equals(file_name, "C:\\GAMES\\TDKPIN.INI")); g_profile_calls++; return g_profile_value; } UINT16 WaveOutGetNumDevs16(void) { g_wave_count_calls++; return g_wave_device_count; } UINT16 WaveOutGetDevCaps16( UINT16 device_id, Win16FarPtr capabilities, UINT16 byte_count) { assert(device_id == win16_read_u16(dgroup_at(DGROUP_DEVICE_ID))); assert(capabilities == dgroup_at(DGROUP_WAVE_CAPS)); assert(byte_count == 48); win16_write_u32(capabilities, 44, g_wave_support); g_wave_caps_calls++; return g_wave_caps_result; } UINT16 WaveOutGetVolume16(UINT16 device_id, Win16FarPtr volume) { assert(device_id == win16_read_u16(dgroup_at(DGROUP_DEVICE_ID))); win16_write_u32(volume, 0, g_wave_volume); g_wave_volume_calls++; return 0; } HRSRC16 FindResource16( HINSTANCE16 instance, Win16FarPtr name, Win16FarPtr type) { assert(g_resource_stage == 0 && instance == g_win16_instance); assert(win16_far_selector(name) == 0); assert(type == dgroup_at(DGROUP_WAVE_TYPE)); g_current_sound = (uint16_t)(win16_far_offset(name) - 2000); assert(g_current_sound >= 1 && g_current_sound <= SOUND_COUNT); assert(g_sound_resource_byte_counts[g_current_sound - 1] != 0); g_resource_stage = 1; return (HRSRC16)(0x1000 + g_current_sound); } HGLOBAL16 LoadResource16(HINSTANCE16 instance, HRSRC16 resource) { assert(g_resource_stage == 1 && instance == g_win16_instance); assert(resource == (HRSRC16)(0x1000 + g_current_sound)); g_resource_stage = 2; return (HGLOBAL16)(0x2000 + g_current_sound); } Win16FarPtr tdkpin_global_allocate_locked(UINT16 flags, uint32_t bytes) { assert(g_resource_stage == 2 && flags == 0x42); assert(bytes == g_sound_resource_byte_counts[g_current_sound - 1]); g_resource_stage = 3; return win16_make_far_pointer( (uint16_t)(0x7000 + g_current_sound), 0x0100); } Win16FarPtr LockResource16(HGLOBAL16 resource) { assert(g_resource_stage == 3); assert(resource == (HGLOBAL16)(0x2000 + g_current_sound)); g_resource_stage = 4; return win16_make_far_pointer( (uint16_t)(0x6000 + g_current_sound), 0x0200); } void borland_move_bytes( Win16FarPtr destination, Win16FarPtr source, uint16_t count) { assert(g_resource_stage == 4); assert(destination == sound_buffer(g_current_sound)); assert(destination == win16_make_far_pointer( (uint16_t)(0x7000 + g_current_sound), 0x0100)); assert(source == win16_make_far_pointer( (uint16_t)(0x6000 + g_current_sound), 0x0200)); assert(count == g_sound_resource_byte_counts[g_current_sound - 1]); g_resource_stage = 5; } BOOL16 GlobalUnlock16(HGLOBAL16 resource) { assert(g_resource_stage == 5); assert(resource == (HGLOBAL16)(0x2000 + g_current_sound)); g_resource_stage = 6; return 1; } BOOL16 FreeResource16(HGLOBAL16 resource) { assert(g_resource_stage == 6); assert(resource == (HGLOBAL16)(0x2000 + g_current_sound)); g_loaded_sounds[g_loaded_count++] = g_current_sound; g_resource_stage = 0; return 1; } BOOL16 SndPlaySound16(Win16FarPtr sound, UINT16 flags) { if (sound == 0) { assert(flags == 0); g_stop_calls++; return 1; } g_played_sound = sound; g_play_flags = flags; return g_play_result; } void tdkpin_global_unlock_and_free(Win16FarPtr block) { assert(g_free_count < SOUND_COUNT); g_freed_sounds[g_free_count++] = block; } static void test_initializer_disabled_and_probe_failures(void) { reset_sound_state(); win16_write_u8(dgroup_at(DGROUP_SOUND_ENABLED), 0xaa); win16_write_u8(dgroup_at(DGROUP_SOUND_PLAYING), 0xbb); win16_write_u8(dgroup_at(DGROUP_VOLUME_STEP), 0x55); win16_write_u16(dgroup_at(DGROUP_DEVICE_COUNT), 0, 0xcccc); g_profile_value = 0; tdkpin_initialize_sounds(); assert(g_profile_calls == 1 && g_wave_count_calls == 0); assert(win16_read_u8(dgroup_at(DGROUP_SOUND_ENABLED)) == 0); assert(win16_read_u8(dgroup_at(DGROUP_SOUND_PLAYING)) == 0); assert(win16_read_u16(dgroup_at(DGROUP_DEVICE_COUNT)) == 0); assert(win16_read_u8(dgroup_at(DGROUP_VOLUME_STEP)) == 0x55); reset_sound_state(); g_profile_value = 1; g_wave_device_count = 0; tdkpin_initialize_sounds(); assert(g_wave_count_calls == 1 && g_wave_caps_calls == 0); assert(win16_read_u16(dgroup_at(DGROUP_DEVICE_COUNT)) == 0); reset_sound_state(); g_wave_device_count = 2; g_wave_caps_result = 1; tdkpin_initialize_sounds(); assert(g_wave_caps_calls == 1 && g_loaded_count == 0); assert(win16_read_u16(dgroup_at(DGROUP_DEVICE_COUNT)) == 0); } static void prime_sound_buffers(void) { for (uint16_t sound = 1; sound <= SOUND_COUNT; sound++) { set_sound_buffer( sound, win16_make_far_pointer(0x5555, sound)); } } static void assert_loaded_resource_set(void) { unsigned loaded = 0; for (uint16_t sound = 1; sound <= SOUND_COUNT; sound++) { uint32_t bytes = g_sound_resource_byte_counts[sound - 1]; if (bytes == 0) { assert(sound_buffer(sound) == win16_make_far_pointer(0x5555, sound)); continue; } assert(g_loaded_sounds[loaded++] == sound); assert(sound_buffer(sound) == win16_make_far_pointer( (uint16_t)(0x7000 + sound), 0x0100)); } assert(loaded == 16 && g_loaded_count == loaded && g_resource_stage == 0); } static void test_initializer_success(void) { reset_sound_state(); prime_sound_buffers(); g_profile_value = 1; g_wave_device_count = 2; g_wave_caps_result = 0; g_wave_support = 0; tdkpin_initialize_sounds(); assert(win16_read_u16(dgroup_at(DGROUP_DEVICE_COUNT)) == 2); assert(win16_read_u8(dgroup_at(DGROUP_SOUND_ENABLED)) == 1); assert(win16_read_u8(dgroup_at(DGROUP_SOUND_PLAYING)) == 0); assert(win16_read_u8(dgroup_at(DGROUP_VOLUME_STEP)) == 0xff); assert(g_wave_volume_calls == 0); assert_loaded_resource_set(); reset_sound_state(); prime_sound_buffers(); g_wave_support = 4; g_wave_volume = 0x1234abcd; tdkpin_initialize_sounds(); assert(g_wave_volume_calls == 1); assert(win16_read_u8(dgroup_at(DGROUP_VOLUME_STEP)) == 170); assert_loaded_resource_set(); } static void test_stop_unload_and_play(void) { win16_set_indeterminate_u16(0xbeef); reset_calls(); win16_write_u16(dgroup_at(DGROUP_DEVICE_COUNT), 0, 0); win16_write_u8(dgroup_at(DGROUP_SOUND_PLAYING), 1); assert(tdkpin_stop_sound_if_active() == 0xbeef); assert(g_stop_calls == 0); win16_write_u16(dgroup_at(DGROUP_DEVICE_COUNT), 0, 1); win16_write_u8(dgroup_at(DGROUP_SOUND_PLAYING), 0); assert(tdkpin_stop_sound_if_active() == 0xbeef); assert(g_stop_calls == 0); win16_write_u8(dgroup_at(DGROUP_SOUND_PLAYING), 1); assert(tdkpin_stop_sound_if_active() == 0xbeef); assert(g_stop_calls == 1); assert(win16_read_u8(dgroup_at(DGROUP_SOUND_PLAYING)) == 1); reset_calls(); for (uint16_t sound = 1; sound <= SOUND_COUNT; sound++) { set_sound_buffer(sound, win16_make_far_pointer(0x7100, sound)); } tdkpin_unload_sounds(); assert(g_stop_calls == 1 && g_free_count == SOUND_COUNT); for (uint16_t index = 0; index < SOUND_COUNT; index++) { assert(g_freed_sounds[index] == win16_make_far_pointer(0x7100, (uint16_t)(index + 1))); } assert(win16_read_u8(dgroup_at(DGROUP_SOUND_PLAYING)) == 0); reset_calls(); win16_write_u16(dgroup_at(DGROUP_DEVICE_COUNT), 0, 0); tdkpin_unload_sounds(); assert(g_free_count == 0); reset_calls(); win16_write_u16(dgroup_at(DGROUP_DEVICE_COUNT), 0, 1); win16_write_u8(dgroup_at(DGROUP_SOUND_ENABLED), 1); win16_write_u8(dgroup_at(DGROUP_SOUND_PLAYING), 1); set_sound_buffer(4, win16_make_far_pointer(0x7200, 0x4444)); g_play_result = 1; assert(tdkpin_play_sound(4) == 0xbeef); assert(g_stop_calls == 1); assert(g_played_sound == win16_make_far_pointer(0x7200, 0x4444)); assert(g_play_flags == 5); assert(win16_read_u8(dgroup_at(DGROUP_SOUND_PLAYING)) == 1); g_play_result = 0; assert(tdkpin_play_sound(4) == 0xbeef); assert(win16_read_u8(dgroup_at(DGROUP_SOUND_PLAYING)) == 0); g_played_sound = 0; (void)tdkpin_play_sound(3); /* Absent resource: exact size-table gate. */ assert(g_played_sound == 0); /* The binary does not range-check: number zero reads the preceding dword. */ win16_write_u8(dgroup_at(DGROUP_SOUND_ENABLED), 1); win16_write_u8(dgroup_at(DGROUP_SOUND_PLAYING), 0); win16_write_u8(dgroup_at(DGROUP_VOLUME_STEP), 0xaa); g_play_result = 1; (void)tdkpin_play_sound(0); assert(g_played_sound == win16_make_far_pointer(0x00aa, 0x0001)); } static void test_gameplay_sound_dispatcher(void) { reset_calls(); win16_write_u16(dgroup_at(DGROUP_DEVICE_COUNT), 0, 1); win16_write_u8(dgroup_at(DGROUP_SOUND_ENABLED), 1); win16_write_u8(dgroup_at(DGROUP_SOUND_PLAYING), 0); win16_write_u8(dgroup_at(DGROUP_GAME_TILTED), 1); set_sound_buffer(4, win16_make_far_pointer(0x7300, 0x4444)); g_play_result = 1; tdkpin_play_sound_if_not_tilted(4); assert(g_played_sound == 0); win16_write_u8(dgroup_at(DGROUP_GAME_TILTED), 0); win16_write_u8(dgroup_at(DGROUP_SOUND_ENABLED), 0); tdkpin_play_sound_if_not_tilted(4); assert(g_played_sound == 0); win16_write_u8(dgroup_at(DGROUP_SOUND_ENABLED), 1); tdkpin_play_sound_if_not_tilted(4); assert(g_played_sound == win16_make_far_pointer(0x7300, 0x4444)); assert(g_play_flags == 5); } int main(void) { uint8_t stack_marker; uintptr_t stack_base = (uintptr_t)&stack_marker - 0x8000; win16_reset_segment_bindings(); win16_bind_segment(0x5000, g_dgroup, sizeof(g_dgroup), true); win16_bind_ne_segment(2, 0x5100, g_code2, sizeof(g_code2), false); win16_bind_segment(0x9000, (void *)stack_base, 0x10000, true); win16_set_dgroup_selector(0x5000); g_win16_instance = 0x4444; initialize_image_data(); test_initializer_disabled_and_probe_failures(); test_initializer_success(); test_stop_unload_and_play(); test_gameplay_sound_dispatcher(); return 0; }