Replace the partial mechanics transcriptions with a separate, readable C11 reconstruction of the complete Win16 image while preserving the original raw Ghidra export as immutable evidence. Cover all ordinary and overlapping entry points, Borland runtime behavior, Win16 imports, segmented data, callbacks, resources, indirect control flow, physics, rendering, persistence, and startup/shutdown lifecycles. Add deterministic extraction and audit tooling plus address-linked ledgers for functions, imports, DGROUP ranges and objects, relocations, resources, and callbacks. The final gate records zero raw, partial, restored, unknown, blocked, or unclassified required units. Keep the semantic-fidelity boundary explicit: the portable C is not claimed to reproduce a byte-identical Borland NE build. Add strict focused harnesses for every reconstructed C unit, exact resource round-trip checks, and a 16-bit Borland Real48 reference probe. No Rust source or Cargo metadata is changed in this phase. Test Plan: - `bash original/tools/test_reconstructed_c.sh` -- passed - `bash original/tools/probe_real48_reference.sh` -- passed bit-for-bit - `python3 original/tools/audit_reconstruction.py --require-complete` -- passed - `git diff --cached --check` -- passed - `git diff HEAD -- '*.rs' Cargo.toml Cargo.lock` -- empty
226 lines
7.8 KiB
C
226 lines
7.8 KiB
C
/* TDKPIN Win16 waveform resource lifecycle. */
|
|
|
|
#include "tdkpin_sound.h"
|
|
|
|
#include "tdkpin_borland_runtime.h"
|
|
#include "tdkpin_command_line.h"
|
|
#include "tdkpin_global_memory.h"
|
|
#include "tdkpin_shortstrings.h"
|
|
#include "tdkpin_strings.h"
|
|
|
|
#include <stddef.h>
|
|
|
|
enum {
|
|
DGROUP_SOUND_SIZE_INDEX_BASE = 0x03e6,
|
|
DGROUP_INI_SUFFIX = 0x0442,
|
|
DGROUP_INI_SECTION_SETTINGS = 0x0446,
|
|
DGROUP_INI_KEY_SOUND = 0x044f,
|
|
DGROUP_WAVE_RESOURCE_TYPE = 0x0455,
|
|
DGROUP_GAME_TILTED = 0x07c6,
|
|
DGROUP_WAVE_CAPABILITIES = 0x43be,
|
|
DGROUP_WAVE_DEVICE_COUNT = 0x43ee,
|
|
DGROUP_WAVE_DEVICE_ID = 0x43f0,
|
|
DGROUP_SOUND_INDEX_BASE = 0x43f2,
|
|
DGROUP_SOUND_ENABLED = 0x43f2,
|
|
DGROUP_SOUND_PLAYING = 0x43f3,
|
|
DGROUP_SOUND_VOLUME_STEP = 0x43f4,
|
|
CODE2_EXTENSION_DOT = 0x0f2c,
|
|
WAVE_RESOURCE_ID_BASE = 2000,
|
|
SOUND_FIRST = 1,
|
|
SOUND_LAST = 22,
|
|
GMEM_MOVEABLE_ZEROINIT = 0x0042,
|
|
WAVECAPS_VOLUME = 0x0004,
|
|
SND_ASYNC_NODEFAULT = 0x0005,
|
|
};
|
|
|
|
/*
|
|
* 1008:0002 — central gameplay sound dispatcher. Tilt suppresses every sound;
|
|
* the duplicated Enabled test is retained even though 1008:1131 tests it too.
|
|
*/
|
|
void tdkpin_play_sound_if_not_tilted(uint16_t sound_number)
|
|
{
|
|
if (win16_read_u8(win16_dgroup_pointer(DGROUP_GAME_TILTED)) == 0 &&
|
|
win16_read_u8(win16_dgroup_pointer(DGROUP_SOUND_ENABLED)) != 0) {
|
|
(void)tdkpin_play_sound(sound_number);
|
|
}
|
|
}
|
|
|
|
/* Preserve the original 16-bit shift and DGROUP-offset wrapping. */
|
|
static uint16_t sound_index_byte_offset(uint16_t sound_number)
|
|
{
|
|
return (uint16_t)(sound_number << 2);
|
|
}
|
|
|
|
static uint32_t sound_resource_byte_count(uint16_t sound_number)
|
|
{
|
|
uint16_t offset = (uint16_t)(
|
|
DGROUP_SOUND_SIZE_INDEX_BASE + sound_index_byte_offset(sound_number));
|
|
return win16_read_u32(win16_dgroup_pointer(offset));
|
|
}
|
|
|
|
static Win16FarPtr sound_buffer(uint16_t sound_number)
|
|
{
|
|
return win16_read_far_pointer(
|
|
win16_dgroup_pointer(DGROUP_SOUND_INDEX_BASE),
|
|
sound_index_byte_offset(sound_number));
|
|
}
|
|
|
|
static void set_sound_buffer(uint16_t sound_number, Win16FarPtr buffer)
|
|
{
|
|
Win16FarPtr base = win16_dgroup_pointer(DGROUP_SOUND_INDEX_BASE);
|
|
win16_write_u32(base, sound_index_byte_offset(sound_number), buffer);
|
|
}
|
|
|
|
/*
|
|
* 1008:0f2e — initialize optional waveform playback.
|
|
*
|
|
* The two ParamStr(0) calls and Borland string operations deliberately mirror
|
|
* the binary: they replace the first dot in the module path with "INI". The
|
|
* routine then reads [Settings] Sound (default 1), probes wave device zero,
|
|
* records volume support, and copies each present WAV resource into its own
|
|
* GMEM_MOVEABLE|GMEM_ZEROINIT block. The original has no resource/allocation
|
|
* failure checks and this reconstruction intentionally does not add any.
|
|
*/
|
|
void tdkpin_initialize_sounds(void)
|
|
{
|
|
uint8_t parameter_string[512];
|
|
uint8_t ini_file_name[102];
|
|
uint8_t module_file_name[102];
|
|
|
|
win16_write_u8(win16_dgroup_pointer(DGROUP_SOUND_ENABLED), 0);
|
|
win16_write_u8(win16_dgroup_pointer(DGROUP_SOUND_PLAYING), 0);
|
|
win16_write_u16(
|
|
win16_dgroup_pointer(DGROUP_WAVE_DEVICE_COUNT), 0, 0);
|
|
|
|
Win16FarPtr parameter = win16_stack_pointer(parameter_string);
|
|
Win16FarPtr module_file = win16_stack_pointer(module_file_name);
|
|
Win16FarPtr ini_file = win16_stack_pointer(ini_file_name);
|
|
borland_parameter_string(0, parameter);
|
|
(void)borland_short_to_c_string(module_file, parameter);
|
|
|
|
borland_parameter_string(0, parameter);
|
|
uint16_t extension_position = borland_short_position(
|
|
win16_relocated_code_pointer(2, CODE2_EXTENSION_DOT), parameter);
|
|
(void)borland_far_strncpy(
|
|
ini_file, module_file, extension_position);
|
|
(void)borland_far_strcat(
|
|
ini_file, win16_dgroup_pointer(DGROUP_INI_SUFFIX));
|
|
|
|
if (GetPrivateProfileInt16(
|
|
win16_dgroup_pointer(DGROUP_INI_SECTION_SETTINGS),
|
|
win16_dgroup_pointer(DGROUP_INI_KEY_SOUND),
|
|
1,
|
|
ini_file) == 0) {
|
|
return;
|
|
}
|
|
|
|
UINT16 device_count = WaveOutGetNumDevs16();
|
|
win16_write_u16(
|
|
win16_dgroup_pointer(DGROUP_WAVE_DEVICE_COUNT), 0, device_count);
|
|
if (device_count == 0) {
|
|
return;
|
|
}
|
|
|
|
UINT16 device_id =
|
|
win16_read_u16(win16_dgroup_pointer(DGROUP_WAVE_DEVICE_ID));
|
|
if (WaveOutGetDevCaps16(
|
|
device_id,
|
|
win16_dgroup_pointer(DGROUP_WAVE_CAPABILITIES),
|
|
sizeof(WAVEOUTCAPS16)) != 0) {
|
|
win16_write_u16(
|
|
win16_dgroup_pointer(DGROUP_WAVE_DEVICE_COUNT), 0, 0);
|
|
return;
|
|
}
|
|
|
|
uint32_t support = win16_read_u32(
|
|
win16_dgroup_pointer(
|
|
(uint16_t)(DGROUP_WAVE_CAPABILITIES +
|
|
offsetof(WAVEOUTCAPS16, support))));
|
|
if ((support & WAVECAPS_VOLUME) == 0) {
|
|
win16_write_u8(
|
|
win16_dgroup_pointer(DGROUP_SOUND_VOLUME_STEP), 0xff);
|
|
} else {
|
|
uint32_t volume = 0;
|
|
(void)WaveOutGetVolume16(device_id, win16_stack_pointer(&volume));
|
|
uint8_t high_byte = (uint8_t)(volume >> 8);
|
|
win16_write_u8(
|
|
win16_dgroup_pointer(DGROUP_SOUND_VOLUME_STEP),
|
|
(uint8_t)((high_byte / 10) * 10));
|
|
}
|
|
|
|
for (uint16_t sound_number = SOUND_FIRST;
|
|
sound_number <= SOUND_LAST;
|
|
sound_number++) {
|
|
uint32_t byte_count = sound_resource_byte_count(sound_number);
|
|
if (byte_count == 0) {
|
|
continue;
|
|
}
|
|
HRSRC16 found = FindResource16(
|
|
g_win16_instance,
|
|
win16_make_far_pointer(
|
|
0, (uint16_t)(sound_number + WAVE_RESOURCE_ID_BASE)),
|
|
win16_dgroup_pointer(DGROUP_WAVE_RESOURCE_TYPE));
|
|
HGLOBAL16 resource = LoadResource16(g_win16_instance, found);
|
|
Win16FarPtr allocation =
|
|
tdkpin_global_allocate_locked(GMEM_MOVEABLE_ZEROINIT, byte_count);
|
|
set_sound_buffer(sound_number, allocation);
|
|
Win16FarPtr resource_bytes = LockResource16(resource);
|
|
borland_move_bytes(
|
|
allocation, resource_bytes, (uint16_t)byte_count);
|
|
(void)GlobalUnlock16(resource);
|
|
(void)FreeResource16(resource);
|
|
}
|
|
win16_write_u8(win16_dgroup_pointer(DGROUP_SOUND_ENABLED), 1);
|
|
}
|
|
|
|
/*
|
|
* 1008:10e3 — stop playback, release sound slots 1..22, clear Playing.
|
|
* Device count, enabled state, and the freed far pointers remain unchanged.
|
|
*/
|
|
void tdkpin_unload_sounds(void)
|
|
{
|
|
if (win16_read_u16(
|
|
win16_dgroup_pointer(DGROUP_WAVE_DEVICE_COUNT)) == 0) {
|
|
return;
|
|
}
|
|
if (win16_read_u8(win16_dgroup_pointer(DGROUP_SOUND_PLAYING)) != 0) {
|
|
(void)SndPlaySound16(0, 0);
|
|
}
|
|
for (uint16_t sound_number = SOUND_FIRST;
|
|
sound_number <= SOUND_LAST;
|
|
sound_number++) {
|
|
tdkpin_global_unlock_and_free(sound_buffer(sound_number));
|
|
}
|
|
win16_write_u8(win16_dgroup_pointer(DGROUP_SOUND_PLAYING), 0);
|
|
}
|
|
|
|
/* 1008:1131 — play one loaded sound with SND_ASYNC|SND_NODEFAULT. */
|
|
uint16_t tdkpin_play_sound(uint16_t sound_number)
|
|
{
|
|
if (win16_read_u16(win16_dgroup_pointer(DGROUP_WAVE_DEVICE_COUNT)) != 0 &&
|
|
win16_read_u8(win16_dgroup_pointer(DGROUP_SOUND_ENABLED)) != 0 &&
|
|
sound_resource_byte_count(sound_number) != 0) {
|
|
if (win16_read_u8(win16_dgroup_pointer(DGROUP_SOUND_PLAYING)) != 0) {
|
|
(void)SndPlaySound16(0, 0);
|
|
}
|
|
BOOL16 played = SndPlaySound16(
|
|
sound_buffer(sound_number), SND_ASYNC_NODEFAULT);
|
|
win16_write_u8(
|
|
win16_dgroup_pointer(DGROUP_SOUND_PLAYING), played != 0);
|
|
}
|
|
return win16_indeterminate_u16();
|
|
}
|
|
|
|
/*
|
|
* 1008:118f — stop only while a wave device exists and a sound is marked
|
|
* playing. The target does not clear Playing and returns an uninitialized word.
|
|
*/
|
|
uint16_t tdkpin_stop_sound_if_active(void)
|
|
{
|
|
if (win16_read_u16(win16_dgroup_pointer(DGROUP_WAVE_DEVICE_COUNT)) != 0 &&
|
|
win16_read_u8(win16_dgroup_pointer(DGROUP_SOUND_PLAYING)) != 0) {
|
|
(void)SndPlaySound16(0, 0);
|
|
}
|
|
return win16_indeterminate_u16();
|
|
}
|