add a realtime scheduler class and priority-inheritance mutexes with timeouts and abandonment, add an HDA audio driver and a SYS_AUDIO_* syscall family, flesh out kernel32 with Win32 threads, events, mutexes, critical sections, and condition variables, add SYS_RESUME_THREAD for suspended thread creation, add KeInitializeMemoryTypes to reprogram MTRRs and install a canonical PAT for WB/WC/UC control, rework the display pipeline with a pitched present path and console output flush, bring back disk.vhd with mtools since vvfat fucking sucks, harden AHCI with serialized cache flush, USB mass storage with SCSI sense-data decoding, and xHCI with cleaner DMA teardown, fix the last stray for(;;) in HalStopProcessor to while(true), pile on smoke tests for audio, win32 sync primitives, and thread suspend, implement AHCI timeout, kill readahead since it was desperetaly trying to murder me, fix doom sound x2.
This commit is contained in:
1 parent
8b254f84f4
commit
4add6789ba
177 files changed
+22565
-3070
No files matched your search
+159
-115
@@ -7549,15 +7549,25 @@ void doom_init(int argc, const char** argv, int flags)
|
||||
if (!doom_exit) doom_exit = doom_exit_impl;
|
||||
if (!doom_getenv) doom_getenv = doom_getenv_impl;
|
||||
|
||||
doom_print("doom_init: allocating screen buffer\n");
|
||||
screen_buffer = (unsigned char*)doom_malloc(SCREENWIDTH * SCREENHEIGHT);
|
||||
doom_print("doom_init: screen buffer ready\n");
|
||||
|
||||
doom_print("doom_init: allocating final screen buffer\n");
|
||||
final_screen_buffer = (unsigned char*)doom_malloc(SCREENWIDTH * SCREENHEIGHT * 4);
|
||||
doom_print("doom_init: final screen buffer ready\n");
|
||||
|
||||
doom_print("doom_init: reading initial time\n");
|
||||
last_update_time = I_GetTime();
|
||||
doom_print("doom_init: initial time ready\n");
|
||||
|
||||
myargc = argc;
|
||||
myargv = (char**)argv; // unsafe my ass
|
||||
doom_flags = flags;
|
||||
|
||||
doom_print("doom_init: entering D_DoomMain\n");
|
||||
D_DoomMain();
|
||||
doom_print("doom_init: D_DoomMain returned\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -9790,7 +9800,27 @@ void FindResponseFile(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
doom_print("FindResponseFile: entered\n");
|
||||
if (myargc <= 0)
|
||||
{
|
||||
doom_print("FindResponseFile: argc <= 0\n");
|
||||
return;
|
||||
}
|
||||
if (myargc == 1)
|
||||
{
|
||||
doom_print("FindResponseFile: argc == 1, returning\n");
|
||||
return;
|
||||
}
|
||||
doom_print("FindResponseFile: argc > 1\n");
|
||||
|
||||
for (i = 1; i < myargc; i++)
|
||||
{
|
||||
doom_print("FindResponseFile: checking argument\n");
|
||||
if (!myargv || !myargv[i])
|
||||
{
|
||||
doom_print("FindResponseFile: invalid argv entry\n");
|
||||
return;
|
||||
}
|
||||
if (myargv[i][0] == '@')
|
||||
{
|
||||
void* handle = 0;
|
||||
@@ -9860,6 +9890,9 @@ void FindResponseFile(void)
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
doom_print("FindResponseFile: scan complete\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -9871,9 +9904,13 @@ void D_DoomMain(void)
|
||||
int p;
|
||||
char file[256];
|
||||
|
||||
doom_print("D_DoomMain: FindResponseFile\n");
|
||||
FindResponseFile();
|
||||
doom_print("D_DoomMain: FindResponseFile done\n");
|
||||
|
||||
doom_print("D_DoomMain: IdentifyVersion\n");
|
||||
IdentifyVersion();
|
||||
doom_print("D_DoomMain: IdentifyVersion done\n");
|
||||
|
||||
modifiedgame = doom_false;
|
||||
|
||||
@@ -23741,19 +23778,67 @@ void M_SaveDefaults(void)
|
||||
//
|
||||
// M_LoadDefaults
|
||||
//
|
||||
// Reads one complete defaults line without allowing malformed files to escape the caller's buffer.
|
||||
static int M_ReadDefaultsLine(void* handle, char* line, int capacity)
|
||||
{
|
||||
int length = 0;
|
||||
doom_boolean overflow = doom_false;
|
||||
|
||||
if (!handle || !line || capacity < 2)
|
||||
return 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
char c = 0;
|
||||
int read = doom_read(handle, &c, 1);
|
||||
if (read != 1)
|
||||
{
|
||||
if (!length && !overflow)
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
break;
|
||||
if (c == '\r')
|
||||
continue;
|
||||
|
||||
if (length + 1 < capacity)
|
||||
line[length++] = c;
|
||||
else
|
||||
overflow = doom_true;
|
||||
}
|
||||
|
||||
line[length] = 0;
|
||||
return overflow ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
// Returns the first non-whitespace byte in a mutable defaults token.
|
||||
static char* M_SkipDefaultWhitespace(char* text)
|
||||
{
|
||||
while (*text == ' ' || *text == '\t')
|
||||
text++;
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
// Removes horizontal whitespace from the end of a mutable defaults token.
|
||||
static void M_TrimDefaultWhitespace(char* text)
|
||||
{
|
||||
int length = (int)doom_strlen(text);
|
||||
while (length > 0 && (text[length - 1] == ' ' || text[length - 1] == '\t'))
|
||||
text[--length] = 0;
|
||||
}
|
||||
|
||||
|
||||
// Loads bounded name/value records from the optional defaults file.
|
||||
void M_LoadDefaults(void)
|
||||
{
|
||||
char line[384];
|
||||
int i;
|
||||
int len;
|
||||
void* f;
|
||||
char def[80];
|
||||
char strparm[100];
|
||||
char* newstring;
|
||||
int parm;
|
||||
doom_boolean isstring;
|
||||
|
||||
// set everything to base values
|
||||
// numdefaults = sizeof(defaults)/sizeof(defaults[0]);
|
||||
// Set everything to base values.
|
||||
for (i = 0; i < numdefaults; i++)
|
||||
{
|
||||
if (defaults[i].defaultvalue == 0xFFFF)
|
||||
@@ -23762,104 +23847,80 @@ void M_LoadDefaults(void)
|
||||
*defaults[i].location = (int)defaults[i].defaultvalue;
|
||||
}
|
||||
|
||||
// check for a custom default file
|
||||
// Check for a custom default file.
|
||||
i = M_CheckParm((char*)"-config");
|
||||
if (i && i < myargc - 1)
|
||||
{
|
||||
defaultfile = myargv[i + 1];
|
||||
//doom_print(" default file: %s\n", defaultfile);
|
||||
doom_print(" default file: ");
|
||||
doom_print(defaultfile);
|
||||
doom_print("\n");
|
||||
}
|
||||
else
|
||||
defaultfile = basedefault;
|
||||
|
||||
// read the file in, overriding any set defaults
|
||||
f = doom_open(defaultfile, "r");
|
||||
if (f)
|
||||
{
|
||||
while (!doom_eof(f))
|
||||
defaultfile = basedefault;
|
||||
}
|
||||
|
||||
void* f = doom_open(defaultfile, "r");
|
||||
if (!f)
|
||||
return;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int lineStatus = M_ReadDefaultsLine(f, line, (int)sizeof(line));
|
||||
if (!lineStatus)
|
||||
break;
|
||||
if (lineStatus < 0)
|
||||
{
|
||||
// def
|
||||
int arg_read = 0;
|
||||
char c;
|
||||
for (i = 0; i < 79; ++i)
|
||||
{
|
||||
doom_read(f, &c, 1);
|
||||
if (c == ' ' || c == '\n' || c == '\t')
|
||||
{
|
||||
if (i > 0) arg_read++;
|
||||
break;
|
||||
}
|
||||
def[i] = c;
|
||||
}
|
||||
def[i] = '\0';
|
||||
|
||||
// Ignore spaces
|
||||
if (c != '\n')
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
doom_read(f, &c, 1);
|
||||
if (c != ' ' && c != '\t') break;
|
||||
}
|
||||
|
||||
// strparam
|
||||
i = 0;
|
||||
if (c != '\n')
|
||||
{
|
||||
for (; i < 260;)
|
||||
{
|
||||
strparm[i++] = c;
|
||||
doom_read(f, &c, 1);
|
||||
if (c == '\n')
|
||||
{
|
||||
if (i > 0) arg_read++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
strparm[i] = '\0';
|
||||
}
|
||||
|
||||
isstring = doom_false;
|
||||
//if (fscanf(f, "%79s %[^\n]\n", def, strparm) == 2)
|
||||
if (arg_read == 2)
|
||||
{
|
||||
if (strparm[0] == '"')
|
||||
{
|
||||
// get a string default
|
||||
isstring = doom_true;
|
||||
len = (int)doom_strlen(strparm);
|
||||
newstring = (char*)doom_malloc(len);
|
||||
strparm[len - 1] = 0;
|
||||
doom_strcpy(newstring, strparm + 1);
|
||||
}
|
||||
else if (strparm[0] == '0' && strparm[1] == 'x')
|
||||
{
|
||||
//sscanf(strparm + 2, "%x", &parm);
|
||||
parm = doom_atox(strparm + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
//sscanf(strparm, "%i", &parm);
|
||||
parm = doom_atoi(strparm);
|
||||
}
|
||||
for (i = 0; i < numdefaults; i++)
|
||||
if (!doom_strcmp(def, defaults[i].name))
|
||||
{
|
||||
if (!isstring)
|
||||
*defaults[i].location = parm;
|
||||
else
|
||||
*defaults[i].text_location = newstring;
|
||||
break;
|
||||
}
|
||||
}
|
||||
doom_print("M_LoadDefaults: ignored an overlong defaults line.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
doom_close(f);
|
||||
char* name = M_SkipDefaultWhitespace(line);
|
||||
if (!*name || *name == '#')
|
||||
continue;
|
||||
|
||||
char* value = name;
|
||||
while (*value && *value != ' ' && *value != '\t')
|
||||
value++;
|
||||
if (!*value)
|
||||
continue;
|
||||
*value++ = 0;
|
||||
value = M_SkipDefaultWhitespace(value);
|
||||
M_TrimDefaultWhitespace(value);
|
||||
if (!*value)
|
||||
continue;
|
||||
|
||||
default_t* setting = get_default(name);
|
||||
if (!setting)
|
||||
continue;
|
||||
|
||||
if (value[0] == '"')
|
||||
{
|
||||
int length = (int)doom_strlen(value);
|
||||
if (length < 2 || value[length - 1] != '"' || !setting->text_location)
|
||||
continue;
|
||||
|
||||
int textLength = length - 2;
|
||||
char* newstring = (char*)doom_malloc(textLength + 1);
|
||||
if (!newstring)
|
||||
continue;
|
||||
doom_memcpy(newstring, value + 1, textLength);
|
||||
newstring[textLength] = 0;
|
||||
*setting->text_location = newstring;
|
||||
}
|
||||
else if (setting->location)
|
||||
{
|
||||
int parsed;
|
||||
if (value[0] == '0' && value[1] == 'x')
|
||||
parsed = doom_atox(value + 2);
|
||||
else
|
||||
parsed = doom_atoi(value);
|
||||
*setting->location = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
doom_close(f);
|
||||
}
|
||||
|
||||
|
||||
@@ -37068,10 +37129,6 @@ void R_InitTextures(void)
|
||||
|
||||
int* directory;
|
||||
|
||||
int temp1;
|
||||
int temp2;
|
||||
int temp3;
|
||||
|
||||
// Load the patch names from pnames.lmp.
|
||||
name[8] = 0;
|
||||
names = (char*)W_CacheLumpName((char*)"PNAMES", PU_STATIC);
|
||||
@@ -37118,23 +37175,10 @@ void R_InitTextures(void)
|
||||
|
||||
totalwidth = 0;
|
||||
|
||||
// Really complex printing shit...
|
||||
temp1 = W_GetNumForName((char*)"S_START"); // P_???????
|
||||
temp2 = W_GetNumForName((char*)"S_END") - 1;
|
||||
temp3 = ((temp2 - temp1 + 63) / 64) + ((numtextures + 63) / 64);
|
||||
doom_print("[");
|
||||
for (i = 0; i < temp3; i++)
|
||||
doom_print(" ");
|
||||
doom_print(" ]");
|
||||
for (i = 0; i < temp3; i++)
|
||||
doom_print("\x8");
|
||||
doom_print("\x8\x8\x8\x8\x8\x8\x8\x8\x8\x8");
|
||||
// Cursor-control progress output can block simple platform consoles.
|
||||
|
||||
for (i = 0; i < numtextures; i++, directory++)
|
||||
{
|
||||
if (!(i & 63))
|
||||
doom_print(".");
|
||||
|
||||
if (i == numtextures1)
|
||||
{
|
||||
// Start looking in second texture file.
|
||||
@@ -37285,14 +37329,14 @@ void R_InitColormaps(void)
|
||||
//
|
||||
void R_InitData(void)
|
||||
{
|
||||
R_InitTextures();
|
||||
doom_print("\nInitTextures");
|
||||
R_InitFlats();
|
||||
R_InitTextures();
|
||||
doom_print("\nInitFlats");
|
||||
R_InitSpriteLumps();
|
||||
R_InitFlats();
|
||||
doom_print("\nInitSprites");
|
||||
R_InitColormaps();
|
||||
R_InitSpriteLumps();
|
||||
doom_print("\nInitColormaps");
|
||||
R_InitColormaps();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// doom.c — PureDOOM port
|
||||
// Native personality carrying all the weight.
|
||||
// Drop doom1.wad (or doom.wad) next to this binary on your FAT32 volume.
|
||||
// Place the Doom WAD and optional doom.sf2 in the process working directory.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <intrin.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#include "doom_audio.h"
|
||||
|
||||
#define DOOM_IMPLEMENTATION
|
||||
#include "PureDOOM.h"
|
||||
|
||||
@@ -189,6 +191,7 @@ static void doom_gettime_wrapper(int* sec, int* usec)
|
||||
// ---------------------------------------------------------------------------
|
||||
static void doom_exit_wrapper(int code)
|
||||
{
|
||||
DoomAudioAbort();
|
||||
SysExitProcess(code);
|
||||
}
|
||||
|
||||
@@ -445,7 +448,7 @@ static int doom_now_tic(void)
|
||||
// Waits until Doom has a new simulation tic to render.
|
||||
static int wait_for_next_doom_tic(int lastTic)
|
||||
{
|
||||
for (;;) {
|
||||
while (true) {
|
||||
int now = doom_now_tic();
|
||||
if (now != lastTic)
|
||||
return now;
|
||||
@@ -495,9 +498,18 @@ int main(void)
|
||||
char argvStorage[512];
|
||||
const char* argv[32];
|
||||
int argc = build_argv_from_command_line(cmdline, argvStorage, sizeof(argvStorage), argv, 32);
|
||||
if (argc == 1)
|
||||
doom_print_wrapper("doom: parsed argc == 1\n");
|
||||
else if (argc > 1)
|
||||
doom_print_wrapper("doom: parsed argc > 1\n");
|
||||
else
|
||||
doom_print_wrapper("doom: parsed argc <= 0\n");
|
||||
init_surface();
|
||||
init_display_clock();
|
||||
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting doom\n") }; SysWriteConsole(&req); } while (0);
|
||||
doom_init(argc, argv, 0);
|
||||
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting audio\n") }; SysWriteConsole(&req); } while (0);
|
||||
DoomAudioStart();
|
||||
|
||||
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: entering game loop\n") }; SysWriteConsole(&req); } while (0);
|
||||
|
||||
@@ -508,9 +520,12 @@ int main(void)
|
||||
int frameTic = wait_for_next_doom_tic(lastFrameTic);
|
||||
lastFrameTic = frameTic;
|
||||
|
||||
DoomAudioLockCore();
|
||||
poll_input();
|
||||
doom_update();
|
||||
render_frame((const uint8_t*)doom_get_framebuffer(4 /* RGBA */));
|
||||
const uint8_t* framebuffer = (const uint8_t*)doom_get_framebuffer(4 /* RGBA */);
|
||||
DoomAudioUnlockCore();
|
||||
render_frame(framebuffer);
|
||||
}
|
||||
|
||||
SysExitProcess(0);
|
||||
|
||||
@@ -0,0 +1,802 @@
|
||||
// Doom audio mixes PureDOOM SFX with optional SoundFont MIDI into one native render stream.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#include "doom_audio.h"
|
||||
#include "PureDOOM.h"
|
||||
|
||||
#define DOOM_AUDIO_SAMPLE_RATE DOOM_SAMPLERATE
|
||||
#define DOOM_AUDIO_CHANNELS 2u
|
||||
#define DOOM_AUDIO_BLOCK_FRAMES 512u
|
||||
#define DOOM_AUDIO_WRITE_QUANTUM_FRAMES 256u
|
||||
#define DOOM_AUDIO_PREFILL_FRAMES DOOM_AUDIO_BLOCK_FRAMES
|
||||
#define DOOM_AUDIO_REFILL_WATERMARK_FRAMES DOOM_AUDIO_WRITE_QUANTUM_FRAMES
|
||||
#define DOOM_AUDIO_MIDI_BATCH_MESSAGES 256u
|
||||
#define DOOM_AUDIO_SOUNDFONT_PATH "doom.sf2"
|
||||
#define DOOM_AUDIO_MAX_SOUNDFONT_BYTES (64u * 1024u * 1024u)
|
||||
#define DOOM_AUDIO_WAIT_MILLISECONDS 100u
|
||||
#define DOOM_AUDIO_WORKER_PRIORITY 2u
|
||||
#define DOOM_AUDIO_SYNTH_LOADER_PRIORITY 0u
|
||||
#define DOOM_AUDIO_MAX_VOICES 128
|
||||
#define DOOM_AUDIO_MUSIC_GAIN 1.0f
|
||||
|
||||
// Computes a natural logarithm for the positive finite values used by TinySoundFont.
|
||||
static double DoomAudioLog(double value) {
|
||||
union {
|
||||
double Value;
|
||||
uint64_t Bits;
|
||||
} bits;
|
||||
const double ln2 = 0.69314718055994530942;
|
||||
int exponent;
|
||||
double mantissa;
|
||||
double ratio;
|
||||
double ratio2;
|
||||
double term;
|
||||
double sum;
|
||||
|
||||
if (value <= 0.0)
|
||||
return -1.0e300;
|
||||
|
||||
bits.Value = value;
|
||||
exponent = (int)((bits.Bits >> 52) & 0x7FFu) - 1023;
|
||||
bits.Bits = (bits.Bits & UINT64_C(0x000FFFFFFFFFFFFF)) |
|
||||
UINT64_C(0x3FF0000000000000);
|
||||
mantissa = bits.Value;
|
||||
ratio = (mantissa - 1.0) / (mantissa + 1.0);
|
||||
ratio2 = ratio * ratio;
|
||||
term = ratio;
|
||||
sum = term;
|
||||
term *= ratio2;
|
||||
sum += term / 3.0;
|
||||
term *= ratio2;
|
||||
sum += term / 5.0;
|
||||
term *= ratio2;
|
||||
sum += term / 7.0;
|
||||
term *= ratio2;
|
||||
sum += term / 9.0;
|
||||
term *= ratio2;
|
||||
sum += term / 11.0;
|
||||
term *= ratio2;
|
||||
sum += term / 13.0;
|
||||
return (double)exponent * ln2 + 2.0 * sum;
|
||||
}
|
||||
|
||||
// Scales a normal double by an integral power of two without a runtime math library.
|
||||
static double DoomAudioScalePowerOfTwo(double value, int exponent) {
|
||||
union {
|
||||
double Value;
|
||||
uint64_t Bits;
|
||||
} scale;
|
||||
|
||||
if (exponent > 1023)
|
||||
return 1.0e300;
|
||||
if (exponent < -1022)
|
||||
return 0.0;
|
||||
|
||||
scale.Bits = (uint64_t)(exponent + 1023) << 52;
|
||||
return value * scale.Value;
|
||||
}
|
||||
|
||||
// Computes an exponential over the bounded ranges used by TinySoundFont envelopes.
|
||||
static double DoomAudioExp(double value) {
|
||||
const double ln2 = 0.69314718055994530942;
|
||||
const double inverseLn2 = 1.4426950408889634074;
|
||||
int exponent;
|
||||
double reduced;
|
||||
double result;
|
||||
|
||||
if (value >= 700.0)
|
||||
return 1.0e300;
|
||||
if (value <= -700.0)
|
||||
return 0.0;
|
||||
|
||||
exponent = (int)(value * inverseLn2 + (value >= 0.0 ? 0.5 : -0.5));
|
||||
reduced = value - (double)exponent * ln2;
|
||||
result = 1.0 + reduced *
|
||||
(1.0 + reduced *
|
||||
(0.5 + reduced *
|
||||
(0.16666666666666666667 + reduced *
|
||||
(0.04166666666666666667 + reduced *
|
||||
(0.00833333333333333333 + reduced *
|
||||
(0.00138888888888888889 + reduced *
|
||||
0.00019841269841269841))))));
|
||||
return DoomAudioScalePowerOfTwo(result, exponent);
|
||||
}
|
||||
|
||||
// Computes a positive real power for TinySoundFont frequency and gain conversion.
|
||||
static double DoomAudioPow(double base, double exponent) {
|
||||
if (base <= 0.0)
|
||||
return 0.0;
|
||||
return DoomAudioExp(exponent * DoomAudioLog(base));
|
||||
}
|
||||
|
||||
// Computes the float exponential required by TinySoundFont envelopes.
|
||||
static float DoomAudioExpf(float value) {
|
||||
return (float)DoomAudioExp((double)value);
|
||||
}
|
||||
|
||||
// Computes the float power required by TinySoundFont frequency and gain conversion.
|
||||
static float DoomAudioPowf(float base, float exponent) {
|
||||
return (float)DoomAudioPow((double)base, (double)exponent);
|
||||
}
|
||||
|
||||
// Computes base-ten logarithms from the shared natural logarithm approximation.
|
||||
static double DoomAudioLog10(double value) {
|
||||
return DoomAudioLog(value) * 0.43429448190325182765;
|
||||
}
|
||||
|
||||
// Computes square roots with a bit-derived seed and stable Newton refinement.
|
||||
static double DoomAudioSqrt(double value) {
|
||||
union {
|
||||
double Value;
|
||||
uint64_t Bits;
|
||||
} estimate;
|
||||
|
||||
if (value <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
estimate.Value = value;
|
||||
estimate.Bits = (estimate.Bits >> 1) + UINT64_C(0x1FF8000000000000);
|
||||
for (int iteration = 0; iteration < 6; iteration++)
|
||||
estimate.Value = 0.5 * (estimate.Value + value / estimate.Value);
|
||||
return estimate.Value;
|
||||
}
|
||||
|
||||
// Computes the float square root required by TinySoundFont panning.
|
||||
static float DoomAudioSqrtf(float value) {
|
||||
return (float)DoomAudioSqrt((double)value);
|
||||
}
|
||||
|
||||
// Computes tangent accurately over TinySoundFont's normalized low-pass range.
|
||||
static double DoomAudioTan(double value) {
|
||||
double squared = value * value;
|
||||
double sine = value *
|
||||
(1.0 + squared *
|
||||
(-0.16666666666666666667 + squared *
|
||||
(0.00833333333333333333 + squared *
|
||||
(-0.00019841269841269841 + squared *
|
||||
(0.00000275573192239859 + squared *
|
||||
(-0.00000002505210838544 + squared *
|
||||
0.00000000016059043837))))));
|
||||
double cosine = 1.0 + squared *
|
||||
(-0.5 + squared *
|
||||
(0.04166666666666666667 + squared *
|
||||
(-0.00138888888888888889 + squared *
|
||||
(0.00002480158730158730 + squared *
|
||||
(-0.00000027557319223986 + squared *
|
||||
(0.00000000208767569879 + squared *
|
||||
-0.00000000001147074560))))));
|
||||
|
||||
if (cosine > -1.0e-12 && cosine < 1.0e-12)
|
||||
cosine = cosine < 0.0 ? -1.0e-12 : 1.0e-12;
|
||||
return sine / cosine;
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244 4267 4706)
|
||||
#define TSF_NO_STDIO
|
||||
#define TSF_MALLOC malloc
|
||||
#define TSF_REALLOC realloc
|
||||
#define TSF_FREE free
|
||||
#define TSF_MEMCPY memcpy
|
||||
#define TSF_MEMSET memset
|
||||
#define TSF_POW DoomAudioPow
|
||||
#define TSF_POWF DoomAudioPowf
|
||||
#define TSF_EXPF DoomAudioExpf
|
||||
#define TSF_LOG DoomAudioLog
|
||||
#define TSF_TAN DoomAudioTan
|
||||
#define TSF_LOG10 DoomAudioLog10
|
||||
#define TSF_SQRT DoomAudioSqrt
|
||||
#define TSF_SQRTF DoomAudioSqrtf
|
||||
#define TSF_IMPLEMENTATION
|
||||
#include "third_party/tsf.h"
|
||||
#pragma warning(pop)
|
||||
|
||||
typedef struct DOOM_AUDIO_STATE {
|
||||
uint32_t CoreMutex;
|
||||
uint32_t StreamHandle;
|
||||
uint32_t WorkerHandle;
|
||||
uint32_t WorkerId;
|
||||
uint32_t SynthLoaderHandle;
|
||||
uint32_t SynthLoaderId;
|
||||
volatile long StopRequested;
|
||||
tsf* volatile Synth;
|
||||
} DOOM_AUDIO_STATE;
|
||||
|
||||
static DOOM_AUDIO_STATE g_doomAudio;
|
||||
|
||||
// Writes one complete diagnostic line to the process console.
|
||||
static void DoomAudioPrint(const char* text) {
|
||||
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
|
||||
(void)SysWriteConsole(&request);
|
||||
}
|
||||
|
||||
// Opens the default available native render endpoint.
|
||||
static KSTATUS DoomAudioFindEndpoint(uint64_t* endpointIdOut) {
|
||||
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
|
||||
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
|
||||
uint32_t count = 0;
|
||||
const SYS_AUDIO_ENDPOINT_INFO* fallback = NULL;
|
||||
KSTATUS status;
|
||||
|
||||
if (!endpointIdOut)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
*endpointIdOut = 0;
|
||||
|
||||
memset(endpoints, 0, sizeof(endpoints));
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.Endpoints = endpoints;
|
||||
request.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
|
||||
request.CountOut = &count;
|
||||
status = SysAudioEnumerateEndpoints(&request);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
return status;
|
||||
if (count > SYS_AUDIO_MAX_ENDPOINTS)
|
||||
count = SYS_AUDIO_MAX_ENDPOINTS;
|
||||
|
||||
for (uint32_t index = 0; index < count; index++) {
|
||||
const SYS_AUDIO_ENDPOINT_INFO* endpoint = &endpoints[index];
|
||||
if (endpoint->State != SysAudioEndpointReady ||
|
||||
(endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE) == 0)
|
||||
continue;
|
||||
if (!fallback)
|
||||
fallback = endpoint;
|
||||
if (endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_DEFAULT) {
|
||||
*endpointIdOut = endpoint->EndpointId;
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fallback)
|
||||
return NTLSTATUS_NOT_FOUND;
|
||||
*endpointIdOut = fallback->EndpointId;
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Returns the currently published synthesizer without taking the PureDOOM lock.
|
||||
static tsf* DoomAudioGetSynth(void) {
|
||||
return (tsf*)_InterlockedCompareExchangePointer(
|
||||
(void* volatile*)&g_doomAudio.Synth, NULL, NULL);
|
||||
}
|
||||
|
||||
// Maps the optional SoundFont so TinySoundFont can parse it without a separate input copy.
|
||||
static KSTATUS DoomAudioMapSoundFont(void** dataOut, uint32_t* sizeOut) {
|
||||
uint32_t fileHandle = 0;
|
||||
uint32_t mappingHandle = 0;
|
||||
uint64_t fileSize = 0;
|
||||
void* base = NULL;
|
||||
KSTATUS status;
|
||||
|
||||
if (!dataOut || !sizeOut)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
*dataOut = NULL;
|
||||
*sizeOut = 0;
|
||||
|
||||
SYS_OPEN_FILE_REQUEST open = SysMakeOpenFileRequest(
|
||||
SysSvFromCstr(DOOM_AUDIO_SOUNDFONT_PATH),
|
||||
SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_READ,
|
||||
SYS_FILE_OPEN_EXISTING,
|
||||
SYS_FILE_FLAG_NONE,
|
||||
&fileHandle);
|
||||
status = SysOpenFile(&open);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = SysGetFileSize(fileHandle, &fileSize);
|
||||
if (status != NTLSTATUS_SUCCESS || fileSize == 0 ||
|
||||
fileSize > DOOM_AUDIO_MAX_SOUNDFONT_BYTES ||
|
||||
fileSize > INT32_MAX) {
|
||||
(void)SysCloseHandle(fileHandle);
|
||||
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_INVALID_PARAMETER : status;
|
||||
}
|
||||
|
||||
status = SysCreateFileMapping(fileHandle, SYS_VM_READ, SYS_MAP_FLAG_NONE,
|
||||
fileSize, &mappingHandle);
|
||||
if (status != NTLSTATUS_SUCCESS || !mappingHandle) {
|
||||
(void)SysCloseHandle(fileHandle);
|
||||
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_FAILURE : status;
|
||||
}
|
||||
|
||||
status = SysMapFile(mappingHandle, 0, (size_t)fileSize,
|
||||
SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base);
|
||||
(void)SysCloseHandle(mappingHandle);
|
||||
(void)SysCloseHandle(fileHandle);
|
||||
if (status != NTLSTATUS_SUCCESS || !base)
|
||||
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_FAILURE : status;
|
||||
|
||||
*dataOut = base;
|
||||
*sizeOut = (uint32_t)fileSize;
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Configures one parsed General MIDI synthesizer for PureDOOM's native sample rate.
|
||||
static KSTATUS DoomAudioConfigureSynth(tsf* synth) {
|
||||
if (!synth)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
|
||||
tsf_set_output(synth, TSF_STEREO_INTERLEAVED,
|
||||
(int)DOOM_AUDIO_SAMPLE_RATE, 0.0f);
|
||||
if (!tsf_set_max_voices(synth, DOOM_AUDIO_MAX_VOICES))
|
||||
return NTLSTATUS_NO_MEMORY;
|
||||
|
||||
for (int channel = 0; channel < 16; channel++) {
|
||||
if (!tsf_channel_set_presetnumber(synth, channel, 0, channel == 9))
|
||||
return NTLSTATUS_FAILURE;
|
||||
}
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Loads optional MIDI state at low priority without blocking Doom or SFX startup.
|
||||
static void DoomAudioSynthLoader(uintptr_t argument) {
|
||||
uint32_t soundFontSize = 0;
|
||||
void* soundFontBase = NULL;
|
||||
tsf* synth = NULL;
|
||||
KSTATUS status;
|
||||
(void)argument;
|
||||
|
||||
status = DoomAudioMapSoundFont(&soundFontBase, &soundFontSize);
|
||||
if (status == NTLSTATUS_NOT_FOUND) {
|
||||
DoomAudioPrint("doom: doom.sf2 unavailable; continuing with sound effects only\n");
|
||||
SysExitThread(0);
|
||||
}
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
DoomAudioPrint("doom: doom.sf2 could not be mapped; continuing with sound effects only\n");
|
||||
SysExitThread(0);
|
||||
}
|
||||
|
||||
if (_InterlockedCompareExchange(&g_doomAudio.StopRequested, 0, 0)) {
|
||||
(void)SysFreeVm(soundFontBase);
|
||||
SysExitThread(0);
|
||||
}
|
||||
|
||||
synth = tsf_load_memory(soundFontBase, (int)soundFontSize);
|
||||
(void)SysFreeVm(soundFontBase);
|
||||
if (!synth) {
|
||||
DoomAudioPrint("doom: doom.sf2 is invalid or synth allocation failed; continuing with sound effects only\n");
|
||||
SysExitThread(0);
|
||||
}
|
||||
|
||||
status = DoomAudioConfigureSynth(synth);
|
||||
if (status != NTLSTATUS_SUCCESS ||
|
||||
_InterlockedCompareExchange(&g_doomAudio.StopRequested, 0, 0)) {
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
DoomAudioPrint("doom: MIDI synthesizer initialization failed; continuing with sound effects only\n");
|
||||
tsf_close(synth);
|
||||
SysExitThread(0);
|
||||
}
|
||||
|
||||
(void)_InterlockedExchangePointer(
|
||||
(void* volatile*)&g_doomAudio.Synth, synth);
|
||||
DoomAudioPrint("doom: MIDI synthesizer ready\n");
|
||||
SysExitThread(0);
|
||||
}
|
||||
|
||||
// Applies one packed WinMM-style short MIDI message to TinySoundFont.
|
||||
static void DoomAudioDispatchMidi(tsf* synth, uint32_t message) {
|
||||
uint8_t status = (uint8_t)(message & 0xFFu);
|
||||
uint8_t data1 = (uint8_t)((message >> 8) & 0x7Fu);
|
||||
uint8_t data2 = (uint8_t)((message >> 16) & 0x7Fu);
|
||||
int channel = status & 0x0F;
|
||||
|
||||
if (!synth)
|
||||
return;
|
||||
|
||||
switch (status & 0xF0) {
|
||||
case 0x80:
|
||||
tsf_channel_note_off(synth, channel, data1);
|
||||
break;
|
||||
case 0x90:
|
||||
if (data2)
|
||||
(void)tsf_channel_note_on(synth, channel, data1,
|
||||
(float)data2 / 127.0f);
|
||||
else
|
||||
tsf_channel_note_off(synth, channel, data1);
|
||||
break;
|
||||
case 0xB0:
|
||||
(void)tsf_channel_midi_control(synth, channel, data1, data2);
|
||||
break;
|
||||
case 0xC0:
|
||||
(void)tsf_channel_set_presetnumber(synth, channel, data1,
|
||||
channel == 9);
|
||||
break;
|
||||
case 0xE0:
|
||||
(void)tsf_channel_set_pitchwheel(synth, channel,
|
||||
(int)data1 | ((int)data2 << 7));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Advances one PureDOOM MIDI tick and dispatches every event outside the core lock.
|
||||
static void DoomAudioTickMidi(tsf* synth) {
|
||||
uint32_t messages[DOOM_AUDIO_MIDI_BATCH_MESSAGES];
|
||||
int tickComplete = 0;
|
||||
|
||||
if (!synth)
|
||||
return;
|
||||
|
||||
while (!tickComplete) {
|
||||
uint32_t count = 0;
|
||||
|
||||
DoomAudioLockCore();
|
||||
while (count < DOOM_AUDIO_MIDI_BATCH_MESSAGES) {
|
||||
uint32_t message = (uint32_t)doom_tick_midi();
|
||||
if (!message) {
|
||||
tickComplete = 1;
|
||||
break;
|
||||
}
|
||||
messages[count++] = message;
|
||||
}
|
||||
DoomAudioUnlockCore();
|
||||
|
||||
for (uint32_t index = 0; index < count; index++)
|
||||
DoomAudioDispatchMidi(synth, messages[index]);
|
||||
}
|
||||
}
|
||||
|
||||
// Copies one complete PureDOOM SFX block while holding the shared core lock.
|
||||
static void DoomAudioReadSfxBlock(int16_t* frames) {
|
||||
short* source;
|
||||
|
||||
DoomAudioLockCore();
|
||||
source = doom_get_sound_buffer();
|
||||
memcpy(frames, source,
|
||||
DOOM_AUDIO_BLOCK_FRAMES * DOOM_AUDIO_CHANNELS * sizeof(int16_t));
|
||||
DoomAudioUnlockCore();
|
||||
}
|
||||
|
||||
// Mixes rendered floating-point music into signed 16-bit PureDOOM SFX.
|
||||
static void DoomAudioMixMusic(int16_t* destination,
|
||||
const float* music,
|
||||
uint32_t frameOffset,
|
||||
uint32_t frameCount) {
|
||||
uint32_t firstSample = frameOffset * DOOM_AUDIO_CHANNELS;
|
||||
uint32_t sampleCount = frameCount * DOOM_AUDIO_CHANNELS;
|
||||
|
||||
for (uint32_t index = 0; index < sampleCount; index++) {
|
||||
float scaled = music[index] * (32767.0f * DOOM_AUDIO_MUSIC_GAIN);
|
||||
int32_t synthSample;
|
||||
int32_t mixed;
|
||||
|
||||
if (scaled > 32767.0f)
|
||||
synthSample = 32767;
|
||||
else if (scaled < -32768.0f)
|
||||
synthSample = -32768;
|
||||
else
|
||||
synthSample = (int32_t)scaled;
|
||||
|
||||
mixed = (int32_t)destination[firstSample + index] + synthSample;
|
||||
if (mixed > 32767)
|
||||
mixed = 32767;
|
||||
else if (mixed < -32768)
|
||||
mixed = -32768;
|
||||
destination[firstSample + index] = (int16_t)mixed;
|
||||
}
|
||||
}
|
||||
|
||||
// Renders MIDI at exactly 140 ticks per second into one native PureDOOM block.
|
||||
static void DoomAudioRenderMusic(tsf* synth,
|
||||
int16_t* frames,
|
||||
uint32_t* phaseInOut) {
|
||||
float music[DOOM_AUDIO_BLOCK_FRAMES * DOOM_AUDIO_CHANNELS];
|
||||
uint32_t frameOffset = 0;
|
||||
|
||||
memset(music, 0, sizeof(music));
|
||||
while (frameOffset < DOOM_AUDIO_BLOCK_FRAMES) {
|
||||
uint32_t remaining;
|
||||
uint32_t framesUntilTick;
|
||||
|
||||
while (*phaseInOut >= DOOM_AUDIO_SAMPLE_RATE) {
|
||||
*phaseInOut -= DOOM_AUDIO_SAMPLE_RATE;
|
||||
DoomAudioTickMidi(synth);
|
||||
}
|
||||
|
||||
remaining = DOOM_AUDIO_BLOCK_FRAMES - frameOffset;
|
||||
framesUntilTick =
|
||||
(DOOM_AUDIO_SAMPLE_RATE - *phaseInOut + DOOM_MIDI_RATE - 1u) /
|
||||
DOOM_MIDI_RATE;
|
||||
if (framesUntilTick > remaining)
|
||||
framesUntilTick = remaining;
|
||||
|
||||
tsf_render_float(synth,
|
||||
music + frameOffset * DOOM_AUDIO_CHANNELS,
|
||||
(int)framesUntilTick,
|
||||
0);
|
||||
DoomAudioMixMusic(frames, music + frameOffset * DOOM_AUDIO_CHANNELS,
|
||||
frameOffset, framesUntilTick);
|
||||
frameOffset += framesUntilTick;
|
||||
*phaseInOut += framesUntilTick * DOOM_MIDI_RATE;
|
||||
}
|
||||
}
|
||||
|
||||
// Renders one complete native-rate block containing synchronized music and SFX.
|
||||
static void DoomAudioRenderBlock(int16_t* frames,
|
||||
tsf** activeSynthInOut,
|
||||
uint32_t* midiPhaseInOut) {
|
||||
tsf* synth;
|
||||
|
||||
DoomAudioReadSfxBlock(frames);
|
||||
synth = DoomAudioGetSynth();
|
||||
if (synth != *activeSynthInOut) {
|
||||
*activeSynthInOut = synth;
|
||||
*midiPhaseInOut = DOOM_AUDIO_SAMPLE_RATE;
|
||||
}
|
||||
if (synth)
|
||||
DoomAudioRenderMusic(synth, frames, midiPhaseInOut);
|
||||
}
|
||||
|
||||
// Queries one coherent native stream snapshot.
|
||||
static KSTATUS DoomAudioQueryStream(SYS_AUDIO_STREAM_INFO* infoOut) {
|
||||
if (!infoOut)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
memset(infoOut, 0, sizeof(*infoOut));
|
||||
infoOut->Size = sizeof(*infoOut);
|
||||
return SysAudioQueryStream(g_doomAudio.StreamHandle, infoOut);
|
||||
}
|
||||
|
||||
// Arms a wait for writable space or a terminal stream condition.
|
||||
static KSTATUS DoomAudioWaitWritable(const SYS_AUDIO_STREAM_INFO* info,
|
||||
uint32_t writableFrames) {
|
||||
SYS_AUDIO_ARM_WAIT_REQUEST request;
|
||||
KSTATUS status;
|
||||
|
||||
if (!info || !writableFrames || writableFrames > info->CapacityFrames)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.StreamHandle = g_doomAudio.StreamHandle;
|
||||
request.Mask = SYS_AUDIO_WAIT_WRITABLE |
|
||||
SYS_AUDIO_WAIT_FAILED |
|
||||
SYS_AUDIO_WAIT_DISCONTINUITY;
|
||||
request.WritableFrames = writableFrames;
|
||||
request.DiscontinuityGeneration = info->DiscontinuityGeneration;
|
||||
status = SysAudioArmWait(&request);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
return status;
|
||||
return SysWaitSingle(g_doomAudio.StreamHandle,
|
||||
SYS_WAIT_FLAG_NONE,
|
||||
DOOM_AUDIO_WAIT_MILLISECONDS);
|
||||
}
|
||||
|
||||
// Waits until the buffered source has fallen to the low-latency refill watermark.
|
||||
static KSTATUS DoomAudioWaitForRefill(const SYS_AUDIO_STREAM_INFO* info) {
|
||||
if (!info || info->CapacityFrames <= DOOM_AUDIO_REFILL_WATERMARK_FRAMES)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
return DoomAudioWaitWritable(
|
||||
info, info->CapacityFrames - DOOM_AUDIO_REFILL_WATERMARK_FRAMES);
|
||||
}
|
||||
|
||||
// Writes as much of one pending block as the nonblocking stream accepts.
|
||||
static KSTATUS DoomAudioWritePending(const int16_t* frames,
|
||||
uint32_t* frameOffsetInOut) {
|
||||
SYS_AUDIO_WRITE_REQUEST request;
|
||||
uint32_t written = 0;
|
||||
KSTATUS status;
|
||||
|
||||
if (!frames || !frameOffsetInOut ||
|
||||
*frameOffsetInOut >= DOOM_AUDIO_BLOCK_FRAMES)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.StreamHandle = g_doomAudio.StreamHandle;
|
||||
request.FrameCount = DOOM_AUDIO_BLOCK_FRAMES - *frameOffsetInOut;
|
||||
if (request.FrameCount > DOOM_AUDIO_WRITE_QUANTUM_FRAMES)
|
||||
request.FrameCount = DOOM_AUDIO_WRITE_QUANTUM_FRAMES;
|
||||
request.Frames = frames + *frameOffsetInOut * DOOM_AUDIO_CHANNELS;
|
||||
request.WrittenFramesOut = &written;
|
||||
status = SysAudioWrite(&request);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
return status;
|
||||
if (!written)
|
||||
return NTLSTATUS_WOULD_BLOCK;
|
||||
|
||||
*frameOffsetInOut += written;
|
||||
return NTLSTATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Keeps a bounded low-latency stream queue at PureDOOM's native cadence.
|
||||
static void DoomAudioWorker(uintptr_t argument) {
|
||||
int16_t block[DOOM_AUDIO_BLOCK_FRAMES * DOOM_AUDIO_CHANNELS];
|
||||
uint32_t pendingOffset = DOOM_AUDIO_BLOCK_FRAMES;
|
||||
uint32_t midiPhase = 0;
|
||||
tsf* activeSynth = NULL;
|
||||
uint8_t started = 0;
|
||||
(void)argument;
|
||||
|
||||
while (true) {
|
||||
SYS_AUDIO_STREAM_INFO info;
|
||||
uint32_t requiredFrames;
|
||||
KSTATUS status;
|
||||
|
||||
if (_InterlockedCompareExchange(&g_doomAudio.StopRequested, 0, 0))
|
||||
break;
|
||||
|
||||
status = DoomAudioQueryStream(&info);
|
||||
if (status != NTLSTATUS_SUCCESS || info.State == SysAudioStreamFailed) {
|
||||
DoomAudioPrint("doom: native audio stream failed\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!started && info.BufferedFrames >= DOOM_AUDIO_PREFILL_FRAMES) {
|
||||
status = SysAudioStart(g_doomAudio.StreamHandle);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
DoomAudioPrint("doom: native audio start failed\n");
|
||||
break;
|
||||
}
|
||||
started = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pendingOffset == DOOM_AUDIO_BLOCK_FRAMES) {
|
||||
if (started &&
|
||||
info.BufferedFrames > DOOM_AUDIO_REFILL_WATERMARK_FRAMES) {
|
||||
status = DoomAudioWaitForRefill(&info);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_TIMEOUT) {
|
||||
DoomAudioPrint("doom: native audio wait failed\n");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
DoomAudioRenderBlock(block, &activeSynth, &midiPhase);
|
||||
pendingOffset = 0;
|
||||
}
|
||||
|
||||
requiredFrames = DOOM_AUDIO_BLOCK_FRAMES - pendingOffset;
|
||||
if (requiredFrames > DOOM_AUDIO_WRITE_QUANTUM_FRAMES)
|
||||
requiredFrames = DOOM_AUDIO_WRITE_QUANTUM_FRAMES;
|
||||
if (info.WritableFrames < requiredFrames) {
|
||||
status = DoomAudioWaitWritable(&info, requiredFrames);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_TIMEOUT) {
|
||||
DoomAudioPrint("doom: native audio wait failed\n");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
status = DoomAudioWritePending(block, &pendingOffset);
|
||||
if (status == NTLSTATUS_WOULD_BLOCK)
|
||||
continue;
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
DoomAudioPrint("doom: native audio write failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (started)
|
||||
(void)SysAudioStop(g_doomAudio.StreamHandle);
|
||||
SysExitThread(0);
|
||||
}
|
||||
|
||||
// Releases resources owned before or after the producer thread lifetime.
|
||||
static void DoomAudioReleaseResources(void) {
|
||||
if (g_doomAudio.StreamHandle) {
|
||||
(void)SysCloseHandle(g_doomAudio.StreamHandle);
|
||||
g_doomAudio.StreamHandle = 0;
|
||||
}
|
||||
tsf* synth = (tsf*)_InterlockedExchangePointer(
|
||||
(void* volatile*)&g_doomAudio.Synth, NULL);
|
||||
if (synth)
|
||||
tsf_close(synth);
|
||||
if (g_doomAudio.CoreMutex) {
|
||||
(void)SysCloseHandle(g_doomAudio.CoreMutex);
|
||||
g_doomAudio.CoreMutex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Starts one process-owned audio stream and its exclusive producer thread.
|
||||
void DoomAudioStart(void) {
|
||||
uint64_t endpointId = 0;
|
||||
uint32_t streamHandle = 0;
|
||||
KSTATUS status;
|
||||
|
||||
if (DOOM_AUDIO_PREFILL_FRAMES >= SYS_AUDIO_STREAM_CAPACITY_FRAMES ||
|
||||
DOOM_AUDIO_REFILL_WATERMARK_FRAMES >= DOOM_AUDIO_PREFILL_FRAMES ||
|
||||
DOOM_AUDIO_WRITE_QUANTUM_FRAMES > DOOM_AUDIO_BLOCK_FRAMES) {
|
||||
DoomAudioPrint("doom: invalid native audio buffering configuration\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&g_doomAudio, 0, sizeof(g_doomAudio));
|
||||
status = SysCreateMutex(&g_doomAudio.CoreMutex);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
DoomAudioPrint("doom: audio synchronization unavailable; continuing without sound\n");
|
||||
return;
|
||||
}
|
||||
|
||||
status = DoomAudioFindEndpoint(&endpointId);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
DoomAudioPrint("doom: no audio endpoint; continuing without sound\n");
|
||||
DoomAudioReleaseResources();
|
||||
return;
|
||||
}
|
||||
|
||||
SYS_AUDIO_OPEN_STREAM_REQUEST open;
|
||||
memset(&open, 0, sizeof(open));
|
||||
open.Size = sizeof(open);
|
||||
open.EndpointId = endpointId;
|
||||
open.Format.SampleRate = DOOM_AUDIO_SAMPLE_RATE;
|
||||
open.Format.ChannelCount = DOOM_AUDIO_CHANNELS;
|
||||
open.Format.SampleFormat = SysAudioSampleS16;
|
||||
open.StreamHandleOut = &streamHandle;
|
||||
status = SysAudioOpenStream(&open);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
DoomAudioPrint("doom: native audio open failed; continuing without sound\n");
|
||||
DoomAudioReleaseResources();
|
||||
return;
|
||||
}
|
||||
|
||||
g_doomAudio.StreamHandle = streamHandle;
|
||||
|
||||
SYS_CREATE_THREAD_REQUEST thread;
|
||||
memset(&thread, 0, sizeof(thread));
|
||||
thread.Entry = (uintptr_t)DoomAudioWorker;
|
||||
thread.Priority = DOOM_AUDIO_WORKER_PRIORITY;
|
||||
thread.ThreadHandleOut = &g_doomAudio.WorkerHandle;
|
||||
thread.ThreadIdOut = &g_doomAudio.WorkerId;
|
||||
status = SysCreateThread(&thread);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
DoomAudioPrint("doom: audio worker creation failed; continuing without sound\n");
|
||||
DoomAudioReleaseResources();
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&thread, 0, sizeof(thread));
|
||||
thread.Entry = (uintptr_t)DoomAudioSynthLoader;
|
||||
thread.Priority = DOOM_AUDIO_SYNTH_LOADER_PRIORITY;
|
||||
thread.ThreadHandleOut = &g_doomAudio.SynthLoaderHandle;
|
||||
thread.ThreadIdOut = &g_doomAudio.SynthLoaderId;
|
||||
status = SysCreateThread(&thread);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
DoomAudioPrint("doom: MIDI loader creation failed; continuing with sound effects only\n");
|
||||
}
|
||||
|
||||
// Stops playback immediately without waiting for the producer thread.
|
||||
void DoomAudioAbort(void) {
|
||||
_InterlockedExchange(&g_doomAudio.StopRequested, 1);
|
||||
if (g_doomAudio.StreamHandle)
|
||||
(void)SysAudioStop(g_doomAudio.StreamHandle);
|
||||
}
|
||||
|
||||
// Stops the producer before releasing its stream, synthesizer, and core lock.
|
||||
void DoomAudioShutdown(void) {
|
||||
DoomAudioAbort();
|
||||
if (g_doomAudio.WorkerHandle) {
|
||||
(void)SysWaitSingle(g_doomAudio.WorkerHandle,
|
||||
SYS_WAIT_FLAG_NONE,
|
||||
SYS_WAIT_TIMEOUT_INFINITE);
|
||||
(void)SysCloseHandle(g_doomAudio.WorkerHandle);
|
||||
g_doomAudio.WorkerHandle = 0;
|
||||
}
|
||||
if (g_doomAudio.SynthLoaderHandle) {
|
||||
(void)SysWaitSingle(g_doomAudio.SynthLoaderHandle,
|
||||
SYS_WAIT_FLAG_NONE,
|
||||
SYS_WAIT_TIMEOUT_INFINITE);
|
||||
(void)SysCloseHandle(g_doomAudio.SynthLoaderHandle);
|
||||
g_doomAudio.SynthLoaderHandle = 0;
|
||||
}
|
||||
DoomAudioReleaseResources();
|
||||
}
|
||||
|
||||
// Enters the lock shared by the game loop and PureDOOM audio callbacks.
|
||||
void DoomAudioLockCore(void) {
|
||||
if (g_doomAudio.CoreMutex)
|
||||
(void)SysWaitSingle(g_doomAudio.CoreMutex,
|
||||
SYS_WAIT_FLAG_NONE,
|
||||
SYS_WAIT_TIMEOUT_INFINITE);
|
||||
}
|
||||
|
||||
// Leaves the lock shared by the game loop and PureDOOM audio callbacks.
|
||||
void DoomAudioUnlockCore(void) {
|
||||
if (g_doomAudio.CoreMutex)
|
||||
(void)SysReleaseMutex(g_doomAudio.CoreMutex);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Doom audio owns PureDOOM sound extraction, MIDI synthesis, and native stream submission.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Starts the optional audio worker after PureDOOM has initialized its sound state.
|
||||
void DoomAudioStart(void);
|
||||
|
||||
// Stops playback immediately without waiting for the worker.
|
||||
void DoomAudioAbort(void);
|
||||
|
||||
// Stops the audio worker and releases its process-owned resources.
|
||||
void DoomAudioShutdown(void);
|
||||
|
||||
// Enters the lock shared by the game loop and PureDOOM audio callbacks.
|
||||
void DoomAudioLockCore(void);
|
||||
|
||||
// Leaves the lock shared by the game loop and PureDOOM audio callbacks.
|
||||
void DoomAudioUnlockCore(void);
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2017-2023 Bernhard Schelling (Based on SFZero, Copyright (C) 2012 Steve Folta, https://github.com/stevefolta/SFZero)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
TinySoundFont v0.9
|
||||
|
||||
Upstream: https://github.com/schellingb/TinySoundFont
|
||||
File: tsf.h
|
||||
License: MIT; see LICENSE.tsf.
|
||||
|
||||
The vendored header is unchanged apart from normalized line endings. nt-lite
|
||||
supplies allocator, memory, and math macros from doom_audio.c so the synthesizer
|
||||
stays within the native user runtime. Doom loads the optional small `doom.sf2`
|
||||
asset asynchronously, then releases the input mapping after TinySoundFont has
|
||||
copied the sample bank into its owned representation.
|
||||
+2045
File diff suppressed because it is too large.
Load diff
@@ -426,7 +426,7 @@ static void load_file(void) {
|
||||
char buf[512];
|
||||
size_t got = 0;
|
||||
bool truncated = false;
|
||||
for (;;) {
|
||||
while (true) {
|
||||
SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(h, buf, sizeof(buf), &got);
|
||||
if (SysReadFile(&readReq) != 0 || got == 0) break;
|
||||
for (size_t i = 0; i < got; i++) {
|
||||
|
||||
@@ -279,7 +279,7 @@ int main(void) {
|
||||
|
||||
char buf[513];
|
||||
size_t total = 0;
|
||||
for (;;) {
|
||||
while (true) {
|
||||
size_t got = 0;
|
||||
st = SysRecvSocket(sock, buf, sizeof(buf) - 1, 0, &got);
|
||||
if (st != NTLSTATUS_SUCCESS) {
|
||||
|
||||
@@ -332,7 +332,7 @@ int main(void) {
|
||||
|
||||
bool matched = false;
|
||||
uint8_t lastType = 0, lastCode = 0;
|
||||
for (;;) {
|
||||
while (true) {
|
||||
uint8_t recvBuf[512];
|
||||
SYS_SOCKET_ADDRESS from;
|
||||
size_t fromSize = sizeof(from);
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
#define MAX_PROCS 64 // Maximum number of processes we will fetch at once
|
||||
#define MAX_THREADS 128
|
||||
#define SHELL_PROMPT "> "
|
||||
#define SHELL_GLYPH_WIDTH 8
|
||||
#define SHELL_GLYPH_HEIGHT 16
|
||||
#define SHELL_TEXT_FOREGROUND 0x00CCCCCCu
|
||||
#define SHELL_TEXT_BACKGROUND 0x00012456u
|
||||
#define SHELL_CURSOR_FOREGROUND 0x00000000u
|
||||
#define SHELL_CURSOR_BACKGROUND 0x00E0E0E0u
|
||||
|
||||
// Output helpers.
|
||||
static void print(const char* s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); }
|
||||
@@ -192,6 +198,20 @@ static void printStringWidth(const char* s, size_t width) {
|
||||
static char gHistory[SHELL_HISTORY_DEPTH][MAX_CMD];
|
||||
static size_t gHistoryCount;
|
||||
|
||||
typedef struct SHELL_EDITOR {
|
||||
char* Buffer;
|
||||
int Capacity;
|
||||
int Length;
|
||||
int Cursor;
|
||||
} SHELL_EDITOR;
|
||||
|
||||
typedef struct SHELL_VISIBLE_CURSOR {
|
||||
int Column;
|
||||
int Row;
|
||||
char Character;
|
||||
bool Visible;
|
||||
} SHELL_VISIBLE_CURSOR;
|
||||
|
||||
static const char* const gShellBuiltinCommands[] = {
|
||||
"cd",
|
||||
"clear",
|
||||
@@ -201,6 +221,7 @@ static const char* const gShellBuiltinCommands[] = {
|
||||
"dir",
|
||||
"display",
|
||||
"exit",
|
||||
"fbbench",
|
||||
"gpu",
|
||||
"help",
|
||||
"kill",
|
||||
@@ -211,6 +232,7 @@ static const char* const gShellBuiltinCommands[] = {
|
||||
"threads",
|
||||
"time",
|
||||
"type",
|
||||
"volume",
|
||||
"write",
|
||||
};
|
||||
|
||||
@@ -280,23 +302,112 @@ static void shellHistoryAdd(const char* cmd) {
|
||||
gHistoryCount++;
|
||||
}
|
||||
|
||||
// Replaces the visible line while keeping the cursor at the end.
|
||||
static void shellEditorReplaceLine(char* buf, int len, int* pos, const char* text) {
|
||||
int oldPos = *pos;
|
||||
for (int i = 0; i < oldPos; i++) print("\b");
|
||||
for (int i = 0; i < oldPos; i++) print(" ");
|
||||
for (int i = 0; i < oldPos; i++) print("\b");
|
||||
// Restores the character underneath the visible edit cursor.
|
||||
static void shellEditorHideVisibleCursor(SHELL_VISIBLE_CURSOR* cursor) {
|
||||
if (!cursor || !cursor->Visible) return;
|
||||
|
||||
int newPos = 0;
|
||||
if (text) {
|
||||
while (text[newPos] && newPos < len - 1) {
|
||||
buf[newPos] = text[newPos];
|
||||
newPos++;
|
||||
(void)SysPutScreenChar(cursor->Character,
|
||||
cursor->Column * SHELL_GLYPH_WIDTH,
|
||||
cursor->Row * SHELL_GLYPH_HEIGHT,
|
||||
SHELL_TEXT_FOREGROUND,
|
||||
SHELL_TEXT_BACKGROUND);
|
||||
cursor->Visible = false;
|
||||
}
|
||||
|
||||
// Draws the same steady block cursor used by edit without changing console state.
|
||||
static void shellEditorShowVisibleCursor(SHELL_VISIBLE_CURSOR* cursor, const SHELL_EDITOR* editor) {
|
||||
if (!cursor || !editor) return;
|
||||
shellEditorHideVisibleCursor(cursor);
|
||||
|
||||
SYS_CONSOLE_INFO info;
|
||||
if (SysGetConsoleInfo(&info) != NTLSTATUS_SUCCESS) return;
|
||||
|
||||
char character = editor->Cursor < editor->Length
|
||||
? editor->Buffer[editor->Cursor]
|
||||
: ' ';
|
||||
if (SysPutScreenChar(character,
|
||||
(int)info.CursorX * SHELL_GLYPH_WIDTH,
|
||||
(int)info.CursorY * SHELL_GLYPH_HEIGHT,
|
||||
SHELL_CURSOR_FOREGROUND,
|
||||
SHELL_CURSOR_BACKGROUND) != NTLSTATUS_SUCCESS)
|
||||
return;
|
||||
|
||||
cursor->Column = (int)info.CursorX;
|
||||
cursor->Row = (int)info.CursorY;
|
||||
cursor->Character = character;
|
||||
cursor->Visible = true;
|
||||
}
|
||||
|
||||
// Moves the visible cursor left by the requested number of cells.
|
||||
static void shellEditorMoveLeftCells(int count) {
|
||||
while (count-- > 0) print("\b");
|
||||
}
|
||||
|
||||
// Moves the editor cursor without changing the buffer.
|
||||
static void shellEditorMoveCursor(SHELL_EDITOR* editor, int target) {
|
||||
if (!editor) return;
|
||||
if (target < 0) target = 0;
|
||||
if (target > editor->Length) target = editor->Length;
|
||||
|
||||
if (target < editor->Cursor) {
|
||||
shellEditorMoveLeftCells(editor->Cursor - target);
|
||||
} else {
|
||||
while (editor->Cursor < target) {
|
||||
char echo[2] = { editor->Buffer[editor->Cursor], '\0' };
|
||||
print(echo);
|
||||
editor->Cursor++;
|
||||
}
|
||||
}
|
||||
buf[newPos] = '\0';
|
||||
*pos = newPos;
|
||||
print(buf);
|
||||
editor->Cursor = target;
|
||||
}
|
||||
|
||||
// Redraws the changed tail and restores the requested cursor position.
|
||||
static void shellEditorRedrawTail(SHELL_EDITOR* editor, int start, int oldLength, int targetCursor) {
|
||||
if (!editor) return;
|
||||
|
||||
print(editor->Buffer + start);
|
||||
if (oldLength > editor->Length) printSpaces((size_t)(oldLength - editor->Length));
|
||||
|
||||
int displayedEnd = oldLength > editor->Length ? oldLength : editor->Length;
|
||||
shellEditorMoveLeftCells(displayedEnd - targetCursor);
|
||||
editor->Cursor = targetCursor;
|
||||
}
|
||||
|
||||
// Replaces one buffer range and keeps the visible line synchronized.
|
||||
static bool shellEditorReplaceRange(SHELL_EDITOR* editor, int start, int end, const char* text) {
|
||||
if (!editor || !editor->Buffer || editor->Capacity <= 0 || !text) return false;
|
||||
if (start < 0 || end < start || end > editor->Length) return false;
|
||||
|
||||
shellEditorMoveCursor(editor, start);
|
||||
|
||||
int oldLength = editor->Length;
|
||||
int removed = end - start;
|
||||
int available = editor->Capacity - 1 - (oldLength - removed);
|
||||
int replacementLength = 0;
|
||||
while (text[replacementLength] && replacementLength < available) replacementLength++;
|
||||
|
||||
memmove(editor->Buffer + start + replacementLength,
|
||||
editor->Buffer + end,
|
||||
(size_t)(oldLength - end + 1));
|
||||
if (replacementLength > 0)
|
||||
memcpy(editor->Buffer + start, text, (size_t)replacementLength);
|
||||
|
||||
editor->Length = oldLength - removed + replacementLength;
|
||||
int targetCursor = start + replacementLength;
|
||||
shellEditorRedrawTail(editor, start, oldLength, targetCursor);
|
||||
return removed != replacementLength || replacementLength != 0;
|
||||
}
|
||||
|
||||
// Inserts text at the current cursor position.
|
||||
static bool shellEditorInsert(SHELL_EDITOR* editor, const char* text) {
|
||||
if (!editor) return false;
|
||||
return shellEditorReplaceRange(editor, editor->Cursor, editor->Cursor, text);
|
||||
}
|
||||
|
||||
// Replaces the complete visible line and leaves the cursor at its end.
|
||||
static void shellEditorReplaceLine(SHELL_EDITOR* editor, const char* text) {
|
||||
if (!editor) return;
|
||||
(void)shellEditorReplaceRange(editor, 0, editor->Length, text ? text : "");
|
||||
}
|
||||
|
||||
// Returns true when a character separates shell tokens.
|
||||
@@ -340,23 +451,6 @@ static void shellUpdateCommonPrefix(char* common, size_t commonSize, const char*
|
||||
common[i] = '\0';
|
||||
}
|
||||
|
||||
// Appends text to the edited line and echoes it.
|
||||
static bool shellEditorAppend(char* buf, int len, int* pos, const char* text) {
|
||||
if (!buf || len <= 0 || !pos || !text) return false;
|
||||
|
||||
bool changed = false;
|
||||
while (*text && *pos < len - 1) {
|
||||
buf[*pos] = *text;
|
||||
(*pos)++;
|
||||
buf[*pos] = '\0';
|
||||
char echo[2] = { *text, '\0' };
|
||||
print(echo);
|
||||
text++;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
// Prints one completion candidate.
|
||||
static void shellPrintCompletionCandidate(const char* name, bool directory) {
|
||||
print(" ");
|
||||
@@ -454,18 +548,23 @@ static void shellCollectPathCompletions(const char* token, size_t tokenLength,
|
||||
}
|
||||
|
||||
// Redraws the prompt and current edit buffer after a completion list.
|
||||
static void shellRedrawPromptAndLine(const char* buf) {
|
||||
static void shellRedrawPromptAndLine(SHELL_EDITOR* editor) {
|
||||
print(SHELL_PROMPT);
|
||||
print(buf ? buf : "");
|
||||
if (!editor) return;
|
||||
print(editor->Buffer);
|
||||
shellEditorMoveLeftCells(editor->Length - editor->Cursor);
|
||||
}
|
||||
|
||||
// Completes the token at the end of the current line.
|
||||
static void shellCompleteLine(char* buf, int len, int* pos) {
|
||||
if (!buf || len <= 0 || !pos || *pos < 0) return;
|
||||
// Completes the token around the current cursor position.
|
||||
static void shellCompleteLine(SHELL_EDITOR* editor) {
|
||||
if (!editor || !editor->Buffer || editor->Capacity <= 0 || editor->Cursor < 0) return;
|
||||
|
||||
size_t cursor = (size_t)*pos;
|
||||
char* buf = editor->Buffer;
|
||||
size_t cursor = (size_t)editor->Cursor;
|
||||
size_t tokenStart = cursor;
|
||||
while (tokenStart > 0 && !shellIsTokenSeparator(buf[tokenStart - 1])) tokenStart--;
|
||||
size_t tokenEnd = cursor;
|
||||
while (tokenEnd < (size_t)editor->Length && !shellIsTokenSeparator(buf[tokenEnd])) tokenEnd++;
|
||||
|
||||
const char* token = buf + tokenStart;
|
||||
size_t tokenLength = cursor - tokenStart;
|
||||
@@ -490,14 +589,20 @@ static void shellCompleteLine(char* buf, int len, int* pos) {
|
||||
size_t commonLength = strlen(state.Common);
|
||||
bool expanded = commonLength > namePrefixLength;
|
||||
if (expanded) {
|
||||
shellEditorAppend(buf, len, pos, state.Common + namePrefixLength);
|
||||
(void)shellEditorReplaceRange(editor, editor->Cursor, (int)tokenEnd,
|
||||
state.Common + namePrefixLength);
|
||||
}
|
||||
|
||||
if (state.MatchCount == 1) {
|
||||
if (state.LastIsDirectory) {
|
||||
if (*pos < len - 1 && *pos > 0 && !shellIsPathSeparator(buf[*pos - 1])) shellEditorAppend(buf, len, pos, "/");
|
||||
if (editor->Cursor < editor->Capacity - 1 && editor->Cursor > 0 &&
|
||||
!shellIsPathSeparator(buf[editor->Cursor - 1]))
|
||||
(void)shellEditorInsert(editor, "/");
|
||||
} else if (state.LastAddsSpace) {
|
||||
if (*pos < len - 1 && (*pos == 0 || buf[*pos - 1] != ' ')) shellEditorAppend(buf, len, pos, " ");
|
||||
if (editor->Cursor < editor->Capacity - 1 &&
|
||||
(editor->Cursor == editor->Length ||
|
||||
!shellIsTokenSeparator(buf[editor->Cursor])))
|
||||
(void)shellEditorInsert(editor, " ");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -509,17 +614,27 @@ static void shellCompleteLine(char* buf, int len, int* pos) {
|
||||
shellCollectBuiltinCompletions(token, tokenLength, NULL, true);
|
||||
}
|
||||
shellCollectPathCompletions(token, tokenLength, NULL, true);
|
||||
shellRedrawPromptAndLine(buf);
|
||||
shellRedrawPromptAndLine(editor);
|
||||
}
|
||||
|
||||
// Reads one editable command line from the keyboard.
|
||||
static bool readLine(char* buf, int len) {
|
||||
int pos = 0;
|
||||
SHELL_EDITOR editor;
|
||||
SHELL_VISIBLE_CURSOR visibleCursor;
|
||||
char historyDraft[MAX_CMD];
|
||||
size_t historyCursor = gHistoryCount;
|
||||
if (len <= 0) return false;
|
||||
|
||||
editor.Buffer = buf;
|
||||
editor.Capacity = len;
|
||||
editor.Length = 0;
|
||||
editor.Cursor = 0;
|
||||
memset(&visibleCursor, 0, sizeof(visibleCursor));
|
||||
buf[0] = '\0';
|
||||
historyDraft[0] = '\0';
|
||||
|
||||
while (true) {
|
||||
shellEditorShowVisibleCursor(&visibleCursor, &editor);
|
||||
INPUT_KEY_RECORD input;
|
||||
SYS_INPUT_EVENT_REQUEST inputReq;
|
||||
inputReq.Size = sizeof(inputReq);
|
||||
@@ -527,7 +642,9 @@ static bool readLine(char* buf, int len) {
|
||||
inputReq.Flags = SYS_INPUT_EVENT_FLAG_NONE;
|
||||
inputReq.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE;
|
||||
inputReq.RecordOut = &input;
|
||||
if (SysReadInputEvent(&inputReq) != NTLSTATUS_SUCCESS) { SysYield(); continue; }
|
||||
KSTATUS inputStatus = SysReadInputEvent(&inputReq);
|
||||
shellEditorHideVisibleCursor(&visibleCursor);
|
||||
if (inputStatus != NTLSTATUS_SUCCESS) { SysYield(); continue; }
|
||||
|
||||
INPUT_KEY key = input.Key;
|
||||
if (key == INPUT_KEY_NONE) { SysYield(); continue; }
|
||||
@@ -545,18 +662,45 @@ static bool readLine(char* buf, int len) {
|
||||
}
|
||||
|
||||
if (key == INPUT_KEY_BACKSPACE) {
|
||||
if (pos > 0) {
|
||||
pos--;
|
||||
buf[pos] = '\0';
|
||||
print("\b \b");
|
||||
}
|
||||
if (editor.Cursor > 0)
|
||||
(void)shellEditorReplaceRange(&editor, editor.Cursor - 1, editor.Cursor, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == INPUT_KEY_DELETE) {
|
||||
if (editor.Cursor < editor.Length)
|
||||
(void)shellEditorReplaceRange(&editor, editor.Cursor, editor.Cursor + 1, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == INPUT_KEY_LEFT) {
|
||||
shellEditorMoveCursor(&editor, editor.Cursor - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == INPUT_KEY_RIGHT) {
|
||||
shellEditorMoveCursor(&editor, editor.Cursor + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == INPUT_KEY_HOME) {
|
||||
shellEditorMoveCursor(&editor, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == INPUT_KEY_END) {
|
||||
shellEditorMoveCursor(&editor, editor.Length);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == INPUT_KEY_UP) {
|
||||
if (historyCursor > 0) {
|
||||
if (historyCursor == gHistoryCount) {
|
||||
strncpy(historyDraft, editor.Buffer, sizeof(historyDraft) - 1);
|
||||
historyDraft[sizeof(historyDraft) - 1] = '\0';
|
||||
}
|
||||
historyCursor--;
|
||||
shellEditorReplaceLine(buf, len, &pos, gHistory[historyCursor]);
|
||||
shellEditorReplaceLine(&editor, gHistory[historyCursor]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -564,25 +708,21 @@ static bool readLine(char* buf, int len) {
|
||||
if (key == INPUT_KEY_DOWN) {
|
||||
if (historyCursor < gHistoryCount) {
|
||||
historyCursor++;
|
||||
shellEditorReplaceLine(buf, len, &pos,
|
||||
historyCursor < gHistoryCount ? gHistory[historyCursor] : "");
|
||||
shellEditorReplaceLine(&editor,
|
||||
historyCursor < gHistoryCount ? gHistory[historyCursor] : historyDraft);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == INPUT_KEY_TAB) {
|
||||
shellCompleteLine(buf, len, &pos);
|
||||
shellCompleteLine(&editor);
|
||||
continue;
|
||||
}
|
||||
|
||||
char c = input.Char ? input.Char : shellKeyToChar(key, modifiers);
|
||||
if (c < 0x20 || c == 127) continue;
|
||||
if (pos < len - 1) {
|
||||
buf[pos++] = c;
|
||||
buf[pos] = '\0';
|
||||
char echo[2] = { c, '\0' };
|
||||
print(echo);
|
||||
}
|
||||
char text[2] = { c, '\0' };
|
||||
(void)shellEditorInsert(&editor, text);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -604,6 +744,7 @@ static void cmd_help() {
|
||||
" mem - Show memory usage\n"
|
||||
" time - Show system wall-clock time\n"
|
||||
" display - Show display provider and counters\n"
|
||||
" fbbench - Benchmark Doom-size and full-screen presents\n"
|
||||
" stats - Show scheduler/CPU/MM telemetry\n"
|
||||
" ps - List processes\n"
|
||||
" threads <pid> - Show one process's thread statuses\n"
|
||||
@@ -611,6 +752,9 @@ static void cmd_help() {
|
||||
" dir [path] - List directory contents\n"
|
||||
" cd <path> - Change directory\n"
|
||||
" type <file> - Display file contents\n"
|
||||
" volume [0-100] - Query or set master audio volume\n"
|
||||
" volume mute - Mute the default audio endpoint\n"
|
||||
" volume unmute - Unmute the default audio endpoint\n"
|
||||
" del <file> - Delete a file\n"
|
||||
" write <file> <tx> - Create a file with text\n"
|
||||
" clear - Clear the screen\n"
|
||||
@@ -639,6 +783,117 @@ static void printCpuFlags(uint32_t flags) {
|
||||
if (flags & SYS_CPU_FLAG_PRESENT) print("present ");
|
||||
}
|
||||
|
||||
// Selects the available default audio endpoint, or the first available endpoint.
|
||||
static bool shellQueryDefaultAudioEndpoint(SYS_AUDIO_ENDPOINT_INFO* endpointOut) {
|
||||
if (!endpointOut) return false;
|
||||
|
||||
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
|
||||
uint32_t count = 0;
|
||||
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.Endpoints = endpoints;
|
||||
request.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
|
||||
request.CountOut = &count;
|
||||
|
||||
KSTATUS status = SysAudioEnumerateEndpoints(&request);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_BUFFER_TOO_SMALL) {
|
||||
print("volume: endpoint query failed status=");
|
||||
printUInt(status);
|
||||
print("\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const SYS_AUDIO_ENDPOINT_INFO* fallback = NULL;
|
||||
uint32_t availableCount = count < SYS_AUDIO_MAX_ENDPOINTS
|
||||
? count
|
||||
: SYS_AUDIO_MAX_ENDPOINTS;
|
||||
for (uint32_t index = 0; index < availableCount; index++) {
|
||||
const SYS_AUDIO_ENDPOINT_INFO* endpoint = &endpoints[index];
|
||||
if (endpoint->Size != sizeof(*endpoint) ||
|
||||
endpoint->Version != SYS_AUDIO_ABI_VERSION ||
|
||||
endpoint->State != SysAudioEndpointReady ||
|
||||
(endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE) == 0)
|
||||
continue;
|
||||
if (!fallback) fallback = endpoint;
|
||||
if (endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_DEFAULT) {
|
||||
*endpointOut = *endpoint;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fallback) {
|
||||
*endpointOut = *fallback;
|
||||
return true;
|
||||
}
|
||||
|
||||
print("volume: no available audio endpoint\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts one public Q16 gain into a rounded percentage.
|
||||
static uint32_t shellAudioVolumePercent(uint32_t volumeQ16) {
|
||||
uint64_t scaled = (uint64_t)volumeQ16 * 100u + SYS_AUDIO_GAIN_Q16_UNITY / 2u;
|
||||
return (uint32_t)(scaled / SYS_AUDIO_GAIN_Q16_UNITY);
|
||||
}
|
||||
|
||||
// Converts one percentage into public Q16 gain.
|
||||
static uint32_t shellAudioVolumeQ16(uint32_t percent) {
|
||||
return (uint32_t)(((uint64_t)percent * SYS_AUDIO_GAIN_Q16_UNITY + 50u) / 100u);
|
||||
}
|
||||
|
||||
// Queries or updates the default endpoint master volume.
|
||||
static void cmd_volume(const char* args) {
|
||||
SYS_AUDIO_ENDPOINT_INFO endpoint;
|
||||
if (!shellQueryDefaultAudioEndpoint(&endpoint)) return;
|
||||
|
||||
while (args && (*args == ' ' || *args == '\t')) args++;
|
||||
if (!args || *args == '\0') {
|
||||
print("Volume: ");
|
||||
printUInt(shellAudioVolumePercent(endpoint.MasterVolumeQ16));
|
||||
print("%");
|
||||
if (endpoint.MasterMuted) print(" (muted)");
|
||||
print("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t volumeQ16 = endpoint.MasterVolumeQ16;
|
||||
uint32_t muted = endpoint.MasterMuted ? 1u : 0u;
|
||||
if (strcmp(args, "mute") == 0) {
|
||||
muted = 1u;
|
||||
} else if (strcmp(args, "unmute") == 0) {
|
||||
muted = 0u;
|
||||
} else {
|
||||
uint32_t percent = 0;
|
||||
if (!parseUInt(args, &percent) || percent > 100u) {
|
||||
print("Usage: volume [0-100|mute|unmute]\n");
|
||||
return;
|
||||
}
|
||||
volumeQ16 = shellAudioVolumeQ16(percent);
|
||||
muted = 0u;
|
||||
}
|
||||
|
||||
SYS_AUDIO_SET_MASTER_VOLUME_REQUEST request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.EndpointId = endpoint.EndpointId;
|
||||
request.Muted = muted;
|
||||
request.VolumeQ16 = volumeQ16;
|
||||
KSTATUS status = SysAudioSetMasterVolume(&request);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
print("volume: update failed status=");
|
||||
printUInt(status);
|
||||
print("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
print("Volume: ");
|
||||
printUInt(shellAudioVolumePercent(volumeQ16));
|
||||
print("%");
|
||||
if (muted) print(" (muted)");
|
||||
print("\n");
|
||||
}
|
||||
|
||||
// Implements the cmd stats operation.
|
||||
static void cmd_stats(void) {
|
||||
SYS_SCHEDULER_STATS sched;
|
||||
@@ -801,6 +1056,12 @@ static void cmd_stats(void) {
|
||||
print(" voluntary blocks: "); printUInt64(sched.VoluntaryBlocks); print("\n");
|
||||
print(" SMT deferrals: "); printUInt64(sched.SmtSiblingDeferrals); print("\n");
|
||||
print(" SMT primary pokes: "); printUInt64(sched.SmtPrimaryPokes); print("\n");
|
||||
print(" RT dispatches: "); printUInt64(sched.RealtimeDispatches); print("\n");
|
||||
print(" RT preemptions: "); printUInt64(sched.RealtimePreemptions); print("\n");
|
||||
print(" PI boosts: "); printUInt64(sched.PriorityInheritanceBoosts); print("\n");
|
||||
print(" PI drops: "); printUInt64(sched.PriorityInheritanceDrops); print("\n");
|
||||
print(" PI propagations: "); printUInt64(sched.PriorityInheritancePropagations); print("\n");
|
||||
print(" PI max depth: "); printUInt64(sched.PriorityInheritanceMaxDepth); print("\n");
|
||||
|
||||
print("Exceptions:\n");
|
||||
print(" delivered: "); printUInt64(exceptionStats.Delivered); print("\n");
|
||||
@@ -975,20 +1236,6 @@ static void cmd_stats(void) {
|
||||
print(" standby="); printUInt64(fileCache.StandbyTransitions);
|
||||
print(" standby-hits="); printUInt64(fileCache.StandbyHits);
|
||||
print(" drop-fail="); printUInt64(fileCache.DropFailures); print("\n");
|
||||
print(" read-ahead: requests="); printUInt64(fileCache.ReadAheadRequests);
|
||||
print(" requested="); printUInt64(fileCache.ReadAheadPagesRequested);
|
||||
print(" queued="); printUInt64(fileCache.ReadAheadPagesQueued);
|
||||
print(" loaded="); printUInt64(fileCache.ReadAheadPagesLoaded);
|
||||
print(" skipped="); printUInt64(fileCache.ReadAheadPagesSkipped);
|
||||
print(" fail="); printUInt64(fileCache.ReadAheadFailures);
|
||||
print(" throttled="); printUInt64(fileCache.ReadAheadPagesThrottled); print("\n");
|
||||
print(" read-ahead max: pages/request="); printUInt64(fileCache.MaxReadAheadPagesPerRequest); print("\n");
|
||||
print(" batch fills: reads="); printUInt64(fileCache.BatchReads);
|
||||
print(" requested="); printUInt64(fileCache.BatchPagesRequested);
|
||||
print(" loaded="); printUInt64(fileCache.BatchPagesLoaded);
|
||||
print(" max-request="); printUInt64(fileCache.MaxBatchPagesRequested);
|
||||
print(" max-loaded="); printUInt64(fileCache.MaxBatchPagesLoaded); print("\n");
|
||||
|
||||
print("Filesystem:\n");
|
||||
print(" path cache: lookups="); printUInt64(filesystem.PathLookups);
|
||||
print(" hits="); printUInt64(filesystem.PathCacheHits);
|
||||
@@ -1012,18 +1259,6 @@ static void cmd_stats(void) {
|
||||
print(" writes: requests="); printUInt64(disk.WriteRequests);
|
||||
print(" sectors="); printUInt64(disk.WriteSectors);
|
||||
print(" max-sectors="); printUInt64(disk.MaxWriteSectors); print("\n");
|
||||
print(" read-ahead: hits="); printUInt64(disk.ReadaheadHits);
|
||||
print(" misses="); printUInt64(disk.ReadaheadMisses);
|
||||
print(" stores="); printUInt64(disk.ReadaheadStores);
|
||||
print(" sectors="); printUInt64(disk.ReadaheadSectorsLoaded);
|
||||
print(" max-sectors="); printUInt64(disk.MaxReadaheadSectors); print("\n");
|
||||
print(" read-ahead misc: served-bytes="); printUInt64(disk.ReadaheadBytesServed);
|
||||
print(" seq="); printUInt64(disk.ReadaheadSequentialDetections);
|
||||
print(" invalidations="); printUInt64(disk.ReadaheadInvalidations);
|
||||
print(" suppressed="); printUInt64(disk.ReadaheadSuppressed);
|
||||
print(" alloc-fail="); printUInt64(disk.ReadaheadAllocFailures);
|
||||
print(" read-fail="); printUInt64(disk.ReadaheadReadFailures); print("\n");
|
||||
|
||||
print("Surfaces:\n");
|
||||
print(" lifetime: created="); printUInt64(surface.CreatedSurfaces);
|
||||
print(" destroyed="); printUInt64(surface.DestroyedSurfaces);
|
||||
@@ -1123,9 +1358,11 @@ static void printDisplayStats(const char* title, const DISPLAY_STATS* st) {
|
||||
print(" fill: "); printUInt64(st->FillRectCount); print("\n");
|
||||
print(" copy: "); printUInt64(st->CopyRectCount); print("\n");
|
||||
print(" read: "); printUInt64(st->ReadRectCount); print("\n");
|
||||
print(" present: "); printUInt64(st->PresentCount); print(" rects="); printUInt64(st->PresentRectCount); print("\n");
|
||||
print(" present: "); printUInt64(st->PresentCount); print(" rects="); printUInt64(st->PresentRectCount);
|
||||
print(" direct="); printUInt64(st->DirectPresentCount); print("\n");
|
||||
print(" dirty: "); printUInt64(st->DirtyRectCount); print(" all="); printUInt64(st->DirtyAllCount); print("\n");
|
||||
print(" bytes: "); printUInt64(st->BytesCopied); print("\n");
|
||||
print(" bytes: logical="); printUInt64(st->BytesCopied);
|
||||
print(" scanout="); printUInt64(st->ScanoutBytes); print("\n");
|
||||
print(" sync: "); printUInt64(st->SyncCount); print("\n");
|
||||
print(" cursor: move="); printUInt64(st->CursorMoveCount);
|
||||
print(" show="); printUInt64(st->CursorShowCount);
|
||||
@@ -1149,10 +1386,205 @@ static void cmd_display(void) {
|
||||
print(" mode: "); printUInt(info.Mode.Width); print("x"); printUInt(info.Mode.Height);
|
||||
print(" stride="); printUInt(info.Mode.Stride); print(" format="); printUInt((uint32_t)info.Mode.Format); print("\n");
|
||||
print(" caps: 0x"); printHex32(info.Caps); print(" ["); printDisplayCaps(info.Caps); print("]\n");
|
||||
print(" scanout: ");
|
||||
print((info.Flags & DISPLAY_INFO_FLAG_PAT_WC_AVAILABLE) ? "PAT WC available" : "PAT WC unavailable");
|
||||
print("\n");
|
||||
printDisplayStats("Manager counters", &info.ManagerStats);
|
||||
printDisplayStats("Provider counters", &info.DeviceStats);
|
||||
}
|
||||
|
||||
typedef struct SHELL_FB_BENCH_RESULT {
|
||||
uint32_t Width;
|
||||
uint32_t Height;
|
||||
uint32_t Frames;
|
||||
uint32_t Status;
|
||||
uint64_t ElapsedTicks;
|
||||
uint64_t Frequency;
|
||||
uint64_t ExpectedBytes;
|
||||
uint64_t ScanoutBytes;
|
||||
uint64_t DirectPresents;
|
||||
} SHELL_FB_BENCH_RESULT;
|
||||
|
||||
// Computes a decimal fixed-point ratio with one digit after the decimal point.
|
||||
static uint64_t shellRatioTenths(uint64_t numerator, uint64_t denominator) {
|
||||
if (!denominator)
|
||||
return 0;
|
||||
if (numerator > UINT64_MAX / 10u)
|
||||
return (numerator / denominator) * 10u;
|
||||
return (numerator * 10u) / denominator;
|
||||
}
|
||||
|
||||
// Fills a benchmark surface with a deterministic high-contrast pattern.
|
||||
static void shellFillBenchmarkSurface(uint32_t* pixels,
|
||||
uint32_t pitchPixels,
|
||||
uint32_t width,
|
||||
uint32_t height) {
|
||||
for (uint32_t y = 0; y < height; y++) {
|
||||
uint32_t* row = pixels + (size_t)y * pitchPixels;
|
||||
for (uint32_t x = 0; x < width; x++) {
|
||||
uint32_t red = (x * 255u) / (width ? width : 1u);
|
||||
uint32_t green = (y * 255u) / (height ? height : 1u);
|
||||
uint32_t blue = ((x ^ y) & 0xFFu);
|
||||
row[x] = (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Runs one synchronous surface-present benchmark case.
|
||||
static void shellRunFramebufferBenchmark(uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t frames,
|
||||
uint64_t frequency,
|
||||
SHELL_FB_BENCH_RESULT* result) {
|
||||
memset(result, 0, sizeof(*result));
|
||||
result->Width = width;
|
||||
result->Height = height;
|
||||
result->Frames = frames;
|
||||
result->Frequency = frequency;
|
||||
|
||||
uint32_t surface = 0;
|
||||
SYS_CREATE_SURFACE_REQUEST create;
|
||||
memset(&create, 0, sizeof(create));
|
||||
create.Size = sizeof(create);
|
||||
create.Width = width;
|
||||
create.Height = height;
|
||||
create.Format = SYS_SURFACE_FORMAT_XRGB8888;
|
||||
create.Flags = SYS_SURFACE_FLAG_NONE;
|
||||
create.HandleOut = &surface;
|
||||
|
||||
uint32_t status = SysCreateSurface(&create);
|
||||
if (status != NTLSTATUS_SUCCESS || !surface) {
|
||||
result->Status = status ? status : NTLSTATUS_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
void* base = 0;
|
||||
uint32_t pitchBytes = 0;
|
||||
status = SysMapSurface(surface, &base, &pitchBytes);
|
||||
if (status != NTLSTATUS_SUCCESS || !base || pitchBytes < width * sizeof(uint32_t)) {
|
||||
result->Status = status ? status : NTLSTATUS_FAILURE;
|
||||
SysCloseHandle(surface);
|
||||
return;
|
||||
}
|
||||
|
||||
shellFillBenchmarkSurface((uint32_t*)base, pitchBytes / sizeof(uint32_t), width, height);
|
||||
|
||||
DISPLAY_RECT rect = { 0, 0, (int32_t)width, (int32_t)height };
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
status = SysPresentSurface(surface, &rect, 1);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
break;
|
||||
}
|
||||
|
||||
SYS_DISPLAY_INFO before;
|
||||
SYS_DISPLAY_INFO after;
|
||||
memset(&before, 0, sizeof(before));
|
||||
memset(&after, 0, sizeof(after));
|
||||
if (status == NTLSTATUS_SUCCESS)
|
||||
status = SysQueryDisplayInfo(&before);
|
||||
|
||||
uint64_t begin = 0;
|
||||
uint64_t end = 0;
|
||||
uint32_t completed = 0;
|
||||
if (status == NTLSTATUS_SUCCESS) {
|
||||
begin = SysGetTick();
|
||||
for (; completed < frames; completed++) {
|
||||
status = SysPresentSurface(surface, &rect, 1);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
break;
|
||||
}
|
||||
end = SysGetTick();
|
||||
uint32_t queryStatus = SysQueryDisplayInfo(&after);
|
||||
if (status == NTLSTATUS_SUCCESS && queryStatus != NTLSTATUS_SUCCESS)
|
||||
status = queryStatus;
|
||||
}
|
||||
|
||||
result->Frames = completed;
|
||||
result->Status = status;
|
||||
result->ElapsedTicks = end - begin;
|
||||
result->ExpectedBytes = (uint64_t)width * (uint64_t)height * sizeof(uint32_t) * completed;
|
||||
if (after.DeviceStats.ScanoutBytes >= before.DeviceStats.ScanoutBytes)
|
||||
result->ScanoutBytes = after.DeviceStats.ScanoutBytes - before.DeviceStats.ScanoutBytes;
|
||||
if (after.DeviceStats.DirectPresentCount >= before.DeviceStats.DirectPresentCount)
|
||||
result->DirectPresents = after.DeviceStats.DirectPresentCount - before.DeviceStats.DirectPresentCount;
|
||||
|
||||
SysCloseHandle(surface);
|
||||
}
|
||||
|
||||
// Prints one framebuffer benchmark result.
|
||||
static void shellPrintFramebufferBenchmark(const char* name,
|
||||
const SHELL_FB_BENCH_RESULT* result) {
|
||||
print(" "); print(name); print(" ");
|
||||
printUInt(result->Width); print("x"); printUInt(result->Height); print(": ");
|
||||
if (result->Status != NTLSTATUS_SUCCESS || !result->ElapsedTicks || !result->Frequency) {
|
||||
print("failed status=0x"); printHex32(result->Status); print("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t elapsedUs = SysTicksToMicroseconds(result->ElapsedTicks, result->Frequency);
|
||||
uint64_t fpsTenths = shellRatioTenths((uint64_t)result->Frames * result->Frequency,
|
||||
result->ElapsedTicks);
|
||||
uint64_t bytes = result->ScanoutBytes ? result->ScanoutBytes : result->ExpectedBytes;
|
||||
uint64_t mibTenths = shellRatioTenths(bytes * result->Frequency,
|
||||
result->ElapsedTicks * 1024u * 1024u);
|
||||
|
||||
print("frames="); printUInt(result->Frames);
|
||||
print(" time="); printUInt64(elapsedUs / 1000u); print(" ms");
|
||||
print(" fps="); printUInt64(fpsTenths / 10u); print("."); printUInt64(fpsTenths % 10u);
|
||||
print(" scanout="); printUInt64(mibTenths / 10u); print("."); printUInt64(mibTenths % 10u); print(" MiB/s");
|
||||
print(" direct="); printUInt64(result->DirectPresents);
|
||||
print(" bytes="); printUInt64(bytes);
|
||||
print("\n");
|
||||
}
|
||||
|
||||
// Benchmarks the synchronous surface-to-scanout path without console traffic.
|
||||
static void cmd_fbbench(void) {
|
||||
SYS_DISPLAY_INFO info;
|
||||
uint32_t status = SysQueryDisplayInfo(&info);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
print("fbbench: display query failed status=0x"); printHex32(status); print("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t frequency = 0;
|
||||
status = SysGetTickFrequency(&frequency);
|
||||
if (status != NTLSTATUS_SUCCESS || !frequency) {
|
||||
print("fbbench: timer query failed status=0x"); printHex32(status); print("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t doomWidth = info.Mode.Width < 960u ? info.Mode.Width : 960u;
|
||||
uint32_t doomHeight = info.Mode.Height < 600u ? info.Mode.Height : 600u;
|
||||
uint32_t fullWidth = info.Mode.Width;
|
||||
uint32_t fullHeight = info.Mode.Height;
|
||||
|
||||
uint64_t targetBytes = 128ull * 1024ull * 1024ull;
|
||||
uint64_t doomFrameBytes = (uint64_t)doomWidth * doomHeight * sizeof(uint32_t);
|
||||
uint64_t fullFrameBytes = (uint64_t)fullWidth * fullHeight * sizeof(uint32_t);
|
||||
uint32_t doomFrames = doomFrameBytes ? (uint32_t)((targetBytes + doomFrameBytes - 1u) / doomFrameBytes) : 1u;
|
||||
uint32_t fullFrames = fullFrameBytes ? (uint32_t)((targetBytes + fullFrameBytes - 1u) / fullFrameBytes) : 1u;
|
||||
if (doomFrames < 8u) doomFrames = 8u;
|
||||
if (doomFrames > 128u) doomFrames = 128u;
|
||||
if (fullFrames < 8u) fullFrames = 8u;
|
||||
if (fullFrames > 64u) fullFrames = 64u;
|
||||
|
||||
print("fbbench: running; the framebuffer will be overwritten temporarily\n");
|
||||
SHELL_FB_BENCH_RESULT doom;
|
||||
SHELL_FB_BENCH_RESULT full;
|
||||
shellRunFramebufferBenchmark(doomWidth, doomHeight, doomFrames, frequency, &doom);
|
||||
shellRunFramebufferBenchmark(fullWidth, fullHeight, fullFrames, frequency, &full);
|
||||
|
||||
SysClearScreen(0x00012456u);
|
||||
print("Framebuffer benchmark:\n");
|
||||
print(" provider: "); print(info.PrimaryName[0] ? info.PrimaryName : "?");
|
||||
print(" mode="); printUInt(info.Mode.Width); print("x"); printUInt(info.Mode.Height);
|
||||
print(" stride="); printUInt(info.Mode.Stride);
|
||||
print(" cache="); print((info.Flags & DISPLAY_INFO_FLAG_PAT_WC_AVAILABLE) ? "PAT-WC" : "fallback");
|
||||
print("\n");
|
||||
shellPrintFramebufferBenchmark("doom", &doom);
|
||||
shellPrintFramebufferBenchmark("full", &full);
|
||||
}
|
||||
|
||||
// Implements the cmd mem operation.
|
||||
static void cmd_mem(void) {
|
||||
SYS_MEMORY_STATUS st;
|
||||
@@ -1200,6 +1632,7 @@ static const char* threadStateName(uint32_t state) {
|
||||
case SYS_THREAD_STATE_RUNNING: return "run";
|
||||
case SYS_THREAD_STATE_WAITING: return "wait";
|
||||
case SYS_THREAD_STATE_DEAD: return "dead";
|
||||
case SYS_THREAD_STATE_SUSPENDED: return "suspend";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
@@ -1331,7 +1764,7 @@ static void cmd_threads(const char* args) {
|
||||
print(" (Count: ");
|
||||
printUInt((uint32_t)count);
|
||||
print("):\n");
|
||||
print(" TID State Pri CPU LastCPU QRem QTick Burst Wait WStat Flags\n");
|
||||
print(" TID State Base Eff CPU LastCPU QRem QTick Burst Wait WStat Flags\n");
|
||||
print(" Flags: *=current I=idle K=kernel Q=queued A=alertable k=kernel-apc u=user-apc.\n");
|
||||
|
||||
size_t displayCount = count < MAX_THREADS ? count : MAX_THREADS;
|
||||
@@ -1342,7 +1775,11 @@ static void cmd_threads(const char* args) {
|
||||
print(" ");
|
||||
printStringWidth(threadStateName(t->State), 5);
|
||||
print(" ");
|
||||
printUIntWidth(t->Priority, 3);
|
||||
if (t->BaseSchedulingClass == SYS_SCHEDULING_REALTIME) print("R"); else print("N");
|
||||
printUIntWidth(t->BasePriority, 2);
|
||||
print(" ");
|
||||
if (t->EffectiveSchedulingClass == SYS_SCHEDULING_REALTIME) print("R"); else print("N");
|
||||
printUIntWidth(t->EffectivePriority, 2);
|
||||
print(" ");
|
||||
if (t->RunningCpu == 0xffffffffu) printStringWidth("-", 3);
|
||||
else printUIntWidth(t->RunningCpu, 3);
|
||||
@@ -1431,7 +1868,7 @@ static int shellCompareNames(const char* a, const char* b) {
|
||||
if (!a) a = "";
|
||||
if (!b) b = "";
|
||||
|
||||
for (;;) {
|
||||
while (true) {
|
||||
char ca = shellAsciiLower(a[i]);
|
||||
char cb = shellAsciiLower(b[i]);
|
||||
if (ca != cb)
|
||||
@@ -1540,7 +1977,7 @@ static void cmd_dir(const char* path) {
|
||||
uint32_t count = 0;
|
||||
uint32_t capacity = 0;
|
||||
|
||||
for (;;) {
|
||||
while (true) {
|
||||
SYS_DIR_ENTRY ke;
|
||||
st = SysReadDir(hDir, &ke);
|
||||
if (st != 0)
|
||||
@@ -1575,7 +2012,7 @@ static void cmd_type(const char* filename) {
|
||||
uint32_t hFile;
|
||||
if (open_file(filename, &hFile) != 0) { print("type: cannot open file\n"); return; }
|
||||
char buffer[512]; size_t bytesRead;
|
||||
for (;;) {
|
||||
while (true) {
|
||||
SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(hFile, buffer, sizeof(buffer) - 1, &bytesRead);
|
||||
if (SysReadFile(&readReq) != 0 || bytesRead == 0) break;
|
||||
buffer[bytesRead] = '\0'; print(buffer);
|
||||
@@ -1604,78 +2041,39 @@ static bool extractProgramPath(const char* cmdline, char* out, size_t outSize) {
|
||||
return pos != 0;
|
||||
}
|
||||
|
||||
// Consumes queued Ctrl-C input without taking ordinary foreground stdin.
|
||||
static bool shellConsumeQueuedCtrlC(void) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
INPUT_KEY_RECORD input;
|
||||
SYS_INPUT_EVENT_REQUEST inputReq;
|
||||
inputReq.Size = sizeof(inputReq);
|
||||
inputReq.QueueHandle = 0;
|
||||
inputReq.Flags = SYS_INPUT_EVENT_FLAG_NONE;
|
||||
inputReq.TimeoutMilliseconds = 0;
|
||||
inputReq.RecordOut = &input;
|
||||
if (SysPeekInputEvent(&inputReq) != NTLSTATUS_SUCCESS) return false;
|
||||
|
||||
if ((input.Modifiers & INPUT_MOD_CTRL) && input.Key == INPUT_KEY_C) {
|
||||
(void)SysReadInputEvent(&inputReq);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (input.Key == INPUT_KEY_LEFT_CTRL || input.Key == INPUT_KEY_RIGHT_CTRL) {
|
||||
(void)SysReadInputEvent(&inputReq);
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns whether one input key is currently held.
|
||||
static bool shellIsKeyDown(INPUT_KEY key) {
|
||||
bool down = false;
|
||||
return SysIsKeyDown(key, &down) == NTLSTATUS_SUCCESS && down;
|
||||
}
|
||||
|
||||
// Waits for a foreground process while watching Ctrl-C without stealing ordinary stdin.
|
||||
// Waits for a foreground process while polling Ctrl-C without consuming child input.
|
||||
static uint32_t waitForegroundProcess(uint32_t hProc) {
|
||||
bool sentBreak = false;
|
||||
|
||||
uint32_t inputQueue = 0;
|
||||
if (SysOpenInputQueue(0, &inputQueue) != 0)
|
||||
return SysWait(hProc, SYS_WAIT_FLAG_NONE);
|
||||
|
||||
uint32_t handles[2] = { hProc, inputQueue };
|
||||
uint32_t index = 0;
|
||||
SYS_WAIT_MANY_REQUEST waitReq;
|
||||
waitReq.Size = sizeof(waitReq);
|
||||
waitReq.Flags = SYS_WAIT_FLAG_NONE;
|
||||
waitReq.Handles = handles;
|
||||
waitReq.Count = 2;
|
||||
waitReq.SignaledIndexOut = &index;
|
||||
waitReq.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE;
|
||||
|
||||
for (;;) {
|
||||
index = 0;
|
||||
uint32_t status = SysWaitMany(&waitReq);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
SysCloseHandle(inputQueue);
|
||||
while (true) {
|
||||
uint32_t status = SysWaitSingle(hProc,
|
||||
SYS_WAIT_FLAG_NONE,
|
||||
SHELL_FOREGROUND_POLL_MS);
|
||||
if (status == NTLSTATUS_SUCCESS)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
SysCloseHandle(inputQueue);
|
||||
if (status != NTLSTATUS_TIMEOUT)
|
||||
return status;
|
||||
}
|
||||
|
||||
uint32_t modifiers = SysGetKeyModifiers();
|
||||
if (((modifiers & INPUT_MOD_CTRL) && shellIsKeyDown(INPUT_KEY_C)) || shellConsumeQueuedCtrlC()) {
|
||||
if (!sentBreak) {
|
||||
print("^C\n");
|
||||
(void)SysKillProcess(hProc, (int32_t)NTLSTATUS_PROCESS_KILLED);
|
||||
sentBreak = true;
|
||||
if (!sentBreak && (modifiers & INPUT_MOD_CTRL) &&
|
||||
shellIsKeyDown(INPUT_KEY_C)) {
|
||||
print("^C\n");
|
||||
uint32_t killStatus = SysKillProcess(
|
||||
hProc,
|
||||
(int32_t)NTLSTATUS_PROCESS_KILLED);
|
||||
if (killStatus != NTLSTATUS_SUCCESS) {
|
||||
print("shell: foreground termination failed status=");
|
||||
printUInt(killStatus);
|
||||
print("\n");
|
||||
return killStatus;
|
||||
}
|
||||
sentBreak = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1727,6 +2125,9 @@ static int runCommand(const char* cmd) {
|
||||
if (strcmp(cmd, "mem") == 0) { cmd_mem(); return 0; }
|
||||
if (strcmp(cmd, "time") == 0 || strcmp(cmd, "date") == 0) { cmd_time(); return 0; }
|
||||
if (strcmp(cmd, "display") == 0 || strcmp(cmd, "gpu") == 0) { cmd_display(); return 0; }
|
||||
if (strcmp(cmd, "fbbench") == 0) { cmd_fbbench(); return 0; }
|
||||
if (strcmp(cmd, "volume") == 0) { cmd_volume(""); return 0; }
|
||||
if (strncmp(cmd, "volume ", 7) == 0) { cmd_volume(cmd + 7); return 0; }
|
||||
if (strcmp(cmd, "stats") == 0 || strcmp(cmd, "telemetry") == 0) { cmd_stats(); return 0; }
|
||||
if (strcmp(cmd, "ps") == 0) { cmd_ps(); return 0; }
|
||||
if (strncmp(cmd, "threads ", 8) == 0) { cmd_threads(cmd + 8); return 0; }
|
||||
|
||||
@@ -142,7 +142,7 @@ static int shellCompareNames(const char* a, const char* b) {
|
||||
if (!a) a = "";
|
||||
if (!b) b = "";
|
||||
|
||||
for (;;) {
|
||||
while (true) {
|
||||
char ca = shellAsciiLower(a[i]);
|
||||
char cb = shellAsciiLower(b[i]);
|
||||
if (ca != cb)
|
||||
|
||||
@@ -112,7 +112,7 @@ static int shellCompareNames(const char* a, const char* b) {
|
||||
if (!a) a = "";
|
||||
if (!b) b = "";
|
||||
|
||||
for (;;) {
|
||||
while (true) {
|
||||
char ca = shellAsciiLower(a[i]);
|
||||
char cb = shellAsciiLower(b[i]);
|
||||
if (ca != cb)
|
||||
|
||||
@@ -50,11 +50,11 @@ static void fail_status(const char* what, uint32_t status) {
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
for (;;) SysExitProcess(1);
|
||||
while (true) SysExitProcess(1);
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static SYS_STRING_VIEW command_line_view(void) {
|
||||
@@ -227,6 +227,16 @@ int main(void) {
|
||||
|
||||
print("video: input file mapped\n");
|
||||
|
||||
if (!BinkSoundUseWaveOut()) {
|
||||
print("video: BinkSoundUseWaveOut failed: ");
|
||||
const char* err = BinkGetError();
|
||||
print(err ? err : "unknown");
|
||||
print("\n");
|
||||
SysFreeVm(buf);
|
||||
exit_now(1);
|
||||
}
|
||||
print("video: WaveOut sound system ready\n");
|
||||
|
||||
HBINK b = BinkOpen((const char*)buf, BINKFROMMEMORY);
|
||||
if (!b) {
|
||||
print("video: BinkOpen failed: ");
|
||||
@@ -237,6 +247,25 @@ int main(void) {
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
print("video: Bink open tracks=");
|
||||
print_u64((uint64_t)(b->NumTracks < 0 ? 0 : b->NumTracks));
|
||||
print(" playing=");
|
||||
print_u64(b->playingtracks);
|
||||
print(" soundon=");
|
||||
print_u64(b->soundon);
|
||||
print("\n");
|
||||
for (int32_t track = 0; track < b->NumTracks; track++) {
|
||||
print("video: track ");
|
||||
print_u64((uint64_t)track);
|
||||
print(" id=");
|
||||
print_u64(BinkGetTrackID(b, (uint32_t)track));
|
||||
print(" type=");
|
||||
print_u64(BinkGetTrackType(b, (uint32_t)track));
|
||||
print(" maxBytes=");
|
||||
print_u64(BinkGetTrackMaxSize(b, (uint32_t)track));
|
||||
print("\n");
|
||||
}
|
||||
|
||||
uint32_t width = b->Width;
|
||||
uint32_t height = b->Height;
|
||||
if (!width || !height) {
|
||||
@@ -273,12 +302,16 @@ int main(void) {
|
||||
}
|
||||
|
||||
print("video: native surface ready\n");
|
||||
print("video: decode loop entered\n");
|
||||
|
||||
DISPLAY_RECT fullFrame = { 0, 0, (int32_t)width, (int32_t)height };
|
||||
int firstFramePresented = 0;
|
||||
|
||||
while (1) {
|
||||
if (BinkWait(b)) {
|
||||
_mm_pause();
|
||||
// Bink can be waiting on its decode, I/O, or WaveOut worker. Yield
|
||||
// a millisecond instead of monopolizing the CPU with a hot poll.
|
||||
SysSleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -299,6 +332,16 @@ int main(void) {
|
||||
SysFreeVm(buf);
|
||||
fail_status("SysPresentSurface", status);
|
||||
}
|
||||
if (!firstFramePresented) {
|
||||
firstFramePresented = 1;
|
||||
print("video: first frame presented sndBytes=");
|
||||
print_u64(b->SndSize);
|
||||
print(" soundon=");
|
||||
print_u64(b->soundon);
|
||||
print(" playing=");
|
||||
print_u64(b->playingtracks);
|
||||
print("\n");
|
||||
}
|
||||
|
||||
if (b->FrameNum == b->Frames)
|
||||
break;
|
||||
@@ -306,8 +349,17 @@ int main(void) {
|
||||
BinkNextFrame(b);
|
||||
}
|
||||
|
||||
print("video: playback complete frame=");
|
||||
print_u64(b->FrameNum);
|
||||
print(" sndBytes=");
|
||||
print_u64(b->SndSize);
|
||||
print("\n");
|
||||
print("video: closing surface\n");
|
||||
SysCloseHandle(surface);
|
||||
print("video: closing Bink\n");
|
||||
BinkClose(b);
|
||||
print("video: Bink closed\n");
|
||||
print("video: freeing input mapping\n");
|
||||
SysFreeVm(buf);
|
||||
|
||||
print("video: goodbye\n");
|
||||
|
||||
@@ -39,6 +39,54 @@ typedef long LONG;
|
||||
typedef struct { LONG x; LONG y; } POINT;
|
||||
typedef struct { SHORT X; SHORT Y; } COORD;
|
||||
typedef struct { SHORT Left; SHORT Top; SHORT Right; SHORT Bottom; } SMALL_RECT;
|
||||
typedef struct { uintptr_t _opaque[5]; } CRITICAL_SECTION;
|
||||
typedef struct { void* _ptr; } CONDITION_VARIABLE;
|
||||
|
||||
// Forward declarations used by the process-local GetProcAddress surface.
|
||||
__declspec(dllexport) HANDLE CreateEventA(void*, BOOL, BOOL, const char*);
|
||||
__declspec(dllexport) HANDLE CreateEventW(void*, BOOL, BOOL, const unsigned short*);
|
||||
__declspec(dllexport) BOOL SetEvent(HANDLE);
|
||||
__declspec(dllexport) BOOL ResetEvent(HANDLE);
|
||||
__declspec(dllexport) _Noreturn void ExitThread(DWORD);
|
||||
__declspec(dllexport) HANDLE CreateThread(void*, size_t, void*, void*, DWORD, DWORD*);
|
||||
__declspec(dllexport) DWORD WaitForSingleObject(HANDLE, DWORD);
|
||||
__declspec(dllexport) HANDLE CreateMutexA(void*, BOOL, const char*);
|
||||
__declspec(dllexport) HANDLE CreateMutexW(void*, BOOL, const unsigned short*);
|
||||
__declspec(dllexport) BOOL ReleaseMutex(HANDLE);
|
||||
__declspec(dllexport) BOOL CloseHandle(HANDLE);
|
||||
__declspec(dllexport) void InitializeCriticalSection(CRITICAL_SECTION*);
|
||||
__declspec(dllexport) BOOL InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION*, DWORD);
|
||||
__declspec(dllexport) void DeleteCriticalSection(CRITICAL_SECTION*);
|
||||
__declspec(dllexport) void EnterCriticalSection(CRITICAL_SECTION*);
|
||||
__declspec(dllexport) void LeaveCriticalSection(CRITICAL_SECTION*);
|
||||
__declspec(dllexport) BOOL TryEnterCriticalSection(CRITICAL_SECTION*);
|
||||
__declspec(dllexport) void InitializeConditionVariable(CONDITION_VARIABLE*);
|
||||
__declspec(dllexport) BOOL SleepConditionVariableCS(CONDITION_VARIABLE*, CRITICAL_SECTION*, DWORD);
|
||||
__declspec(dllexport) void WakeConditionVariable(CONDITION_VARIABLE*);
|
||||
__declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE*);
|
||||
__declspec(dllexport) BOOL IsDebuggerPresent(void);
|
||||
extern void waveOutGetNumDevs(void);
|
||||
extern void waveOutOpen(void);
|
||||
extern void waveOutClose(void);
|
||||
extern void waveOutPrepareHeader(void);
|
||||
extern void waveOutUnprepareHeader(void);
|
||||
extern void waveOutWrite(void);
|
||||
extern void waveOutPause(void);
|
||||
extern void waveOutRestart(void);
|
||||
extern void waveOutReset(void);
|
||||
extern void waveOutSetVolume(void);
|
||||
extern void waveOutGetVolume(void);
|
||||
extern void waveOutGetPosition(void);
|
||||
extern void waveOutGetID(void);
|
||||
extern void waveOutGetDevCapsA(void);
|
||||
extern void waveOutGetDevCapsW(void);
|
||||
extern void waveOutGetErrorTextA(void);
|
||||
extern void waveOutGetErrorTextW(void);
|
||||
extern void waveOutBreakLoop(void);
|
||||
extern void waveOutMessage(void);
|
||||
extern void timeGetTime(void);
|
||||
extern void timeBeginPeriod(void);
|
||||
extern void timeEndPeriod(void);
|
||||
|
||||
typedef struct _CONSOLE_SCREEN_BUFFER_INFO {
|
||||
COORD dwSize;
|
||||
@@ -318,6 +366,9 @@ static DWORD* K32LastErrorSlot(void) {
|
||||
|
||||
#define gLastError (*K32LastErrorSlot())
|
||||
|
||||
// Returns a stable nonzero identifier for the calling thread.
|
||||
static DWORD K32CurrentTidOrOne(void);
|
||||
|
||||
// Implements the file Exists A operation.
|
||||
static BOOL KiFileExistsA(const char* path) {
|
||||
if (!path || !*path) return FALSE;
|
||||
@@ -976,11 +1027,189 @@ __declspec(dllexport) BOOL DeleteFileA(const char* lpFileName) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// CloseHandle - unified close for files, threads, mutexes, semaphores, events, queues, and processes.
|
||||
__declspec(dllexport) BOOL CloseHandle(HANDLE hObject) {
|
||||
if (!hObject || hObject == INVALID_HANDLE_VALUE) { gLastError = 6; return FALSE; }
|
||||
#define K32_MUTEX_MAGIC 0x58544D4Bu
|
||||
|
||||
SysCloseHandle(HANDLE_TO_KH(hObject));
|
||||
typedef struct K32_MUTEX_HANDLE {
|
||||
struct K32_MUTEX_HANDLE* Next;
|
||||
uint32_t Magic;
|
||||
volatile long StateLock;
|
||||
volatile long ApiUsers;
|
||||
uint32_t NativeHandle;
|
||||
DWORD OwnerTid;
|
||||
uint32_t Recursion;
|
||||
uint32_t Waiters;
|
||||
volatile long Closing;
|
||||
} K32_MUTEX_HANDLE;
|
||||
|
||||
static K32_MUTEX_HANDLE* gK32MutexRegistry;
|
||||
static volatile long gK32MutexRegistryLock;
|
||||
|
||||
// Acquires one short process-local mutex registry lock.
|
||||
static void K32MutexRegistryLock(void) {
|
||||
while (_InterlockedCompareExchange(&gK32MutexRegistryLock, 1, 0) != 0)
|
||||
(void)SysYield();
|
||||
}
|
||||
|
||||
// Releases the process-local mutex registry lock.
|
||||
static void K32MutexRegistryUnlock(void) {
|
||||
_InterlockedExchange(&gK32MutexRegistryLock, 0);
|
||||
}
|
||||
|
||||
// Acquires one short mutex-wrapper state lock.
|
||||
static void K32MutexStateLock(K32_MUTEX_HANDLE* mutex) {
|
||||
while (_InterlockedCompareExchange(&mutex->StateLock, 1, 0) != 0)
|
||||
(void)SysYield();
|
||||
}
|
||||
|
||||
// Releases one mutex-wrapper state lock.
|
||||
static void K32MutexStateUnlock(K32_MUTEX_HANDLE* mutex) {
|
||||
_InterlockedExchange(&mutex->StateLock, 0);
|
||||
}
|
||||
|
||||
// Registers one process-local Win32 mutex wrapper.
|
||||
static void K32RegisterMutex(K32_MUTEX_HANDLE* mutex) {
|
||||
K32MutexRegistryLock();
|
||||
mutex->Next = gK32MutexRegistry;
|
||||
gK32MutexRegistry = mutex;
|
||||
K32MutexRegistryUnlock();
|
||||
}
|
||||
|
||||
// References one registered Win32 mutex without dereferencing unknown handles.
|
||||
static K32_MUTEX_HANDLE* K32ReferenceMutex(HANDLE handle) {
|
||||
K32_MUTEX_HANDLE* found = NULL;
|
||||
K32MutexRegistryLock();
|
||||
for (K32_MUTEX_HANDLE* current = gK32MutexRegistry;
|
||||
current;
|
||||
current = current->Next) {
|
||||
if ((HANDLE)current == handle &&
|
||||
current->Magic == K32_MUTEX_MAGIC &&
|
||||
_InterlockedCompareExchange(¤t->Closing, 0, 0) == 0) {
|
||||
_InterlockedIncrement(¤t->ApiUsers);
|
||||
found = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
K32MutexRegistryUnlock();
|
||||
return found;
|
||||
}
|
||||
|
||||
// Releases one mutex-wrapper API reference.
|
||||
static void K32DereferenceMutex(K32_MUTEX_HANDLE* mutex) {
|
||||
if (mutex)
|
||||
_InterlockedDecrement(&mutex->ApiUsers);
|
||||
}
|
||||
|
||||
// Removes one mutex wrapper so no new API references can be acquired.
|
||||
static BOOL K32RemoveMutex(K32_MUTEX_HANDLE* mutex) {
|
||||
BOOL removed = FALSE;
|
||||
K32MutexRegistryLock();
|
||||
K32_MUTEX_HANDLE** link = &gK32MutexRegistry;
|
||||
while (*link) {
|
||||
if (*link == mutex) {
|
||||
*link = mutex->Next;
|
||||
mutex->Next = NULL;
|
||||
removed = TRUE;
|
||||
break;
|
||||
}
|
||||
link = &(*link)->Next;
|
||||
}
|
||||
K32MutexRegistryUnlock();
|
||||
return removed;
|
||||
}
|
||||
|
||||
// Waits on one recursive Win32 mutex wrapper.
|
||||
static DWORD K32WaitMutex(K32_MUTEX_HANDLE* mutex, DWORD milliseconds) {
|
||||
DWORD tid = K32CurrentTidOrOne();
|
||||
K32MutexStateLock(mutex);
|
||||
if (_InterlockedCompareExchange(&mutex->Closing, 0, 0) != 0) {
|
||||
K32MutexStateUnlock(mutex);
|
||||
gLastError = 6;
|
||||
return 0xFFFFFFFFUL;
|
||||
}
|
||||
if (mutex->OwnerTid == tid) {
|
||||
if (mutex->Recursion == UINT32_MAX) {
|
||||
K32MutexStateUnlock(mutex);
|
||||
gLastError = 8;
|
||||
return 0xFFFFFFFFUL;
|
||||
}
|
||||
mutex->Recursion++;
|
||||
K32MutexStateUnlock(mutex);
|
||||
return 0;
|
||||
}
|
||||
mutex->Waiters++;
|
||||
K32MutexStateUnlock(mutex);
|
||||
|
||||
uint64_t timeout = milliseconds == 0xFFFFFFFFUL
|
||||
? SYS_WAIT_TIMEOUT_INFINITE
|
||||
: (uint64_t)milliseconds;
|
||||
uint32_t status = SysWaitSingle(mutex->NativeHandle, SYS_WAIT_FLAG_NONE, timeout);
|
||||
|
||||
K32MutexStateLock(mutex);
|
||||
if (mutex->Waiters)
|
||||
mutex->Waiters--;
|
||||
if (status == NTLSTATUS_SUCCESS || status == NTLSTATUS_ABANDONED) {
|
||||
mutex->OwnerTid = tid;
|
||||
mutex->Recursion = 1;
|
||||
}
|
||||
K32MutexStateUnlock(mutex);
|
||||
|
||||
if (status == NTLSTATUS_SUCCESS)
|
||||
return 0;
|
||||
if (status == NTLSTATUS_ABANDONED)
|
||||
return 0x00000080UL;
|
||||
if (status == NTLSTATUS_TIMEOUT)
|
||||
return 0x00000102UL;
|
||||
gLastError = 6;
|
||||
return 0xFFFFFFFFUL;
|
||||
}
|
||||
|
||||
// Closes one idle Win32 mutex wrapper.
|
||||
static BOOL K32CloseMutex(K32_MUTEX_HANDLE* mutex) {
|
||||
K32MutexStateLock(mutex);
|
||||
BOOL busy = mutex->OwnerTid != 0 || mutex->Waiters != 0;
|
||||
if (!busy && _InterlockedCompareExchange(&mutex->Closing, 1, 0) != 0)
|
||||
busy = TRUE;
|
||||
K32MutexStateUnlock(mutex);
|
||||
if (busy) {
|
||||
gLastError = 170;
|
||||
K32DereferenceMutex(mutex);
|
||||
return FALSE;
|
||||
}
|
||||
if (!K32RemoveMutex(mutex)) {
|
||||
gLastError = 6;
|
||||
K32DereferenceMutex(mutex);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
while (mutex->ApiUsers > 1)
|
||||
(void)SysYield();
|
||||
uint32_t status = SysCloseHandle(mutex->NativeHandle);
|
||||
mutex->Magic = 0;
|
||||
K32DereferenceMutex(mutex);
|
||||
free(mutex);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
gLastError = 6;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Closes one Win32 or native kernel handle.
|
||||
__declspec(dllexport) BOOL CloseHandle(HANDLE hObject) {
|
||||
if (!hObject || hObject == INVALID_HANDLE_VALUE) {
|
||||
gLastError = 6;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
K32_MUTEX_HANDLE* mutex = K32ReferenceMutex(hObject);
|
||||
if (mutex)
|
||||
return K32CloseMutex(mutex);
|
||||
|
||||
uint32_t status = SysCloseHandle(HANDLE_TO_KH(hObject));
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
gLastError = 6;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -1087,12 +1316,32 @@ static int KiStricmpA(const char* a, const char* b) {
|
||||
return ((unsigned char)*a | 0x20) - ((unsigned char)*b | 0x20);
|
||||
}
|
||||
|
||||
// Checks whether win32 Dll Name A.
|
||||
// Matches one Win32 module basename with its optional DLL suffix.
|
||||
static BOOL KiMatchesWin32DllNameA(const char* name, const char* baseName) {
|
||||
if (!name || !baseName)
|
||||
return FALSE;
|
||||
|
||||
while (*name && *baseName &&
|
||||
(((unsigned char)*name | 0x20u) == ((unsigned char)*baseName | 0x20u))) {
|
||||
name++;
|
||||
baseName++;
|
||||
}
|
||||
|
||||
if (*baseName)
|
||||
return FALSE;
|
||||
if (!*name)
|
||||
return TRUE;
|
||||
return KiStricmpA(name, ".dll") == 0;
|
||||
}
|
||||
|
||||
// Checks whether one name identifies a process-local Win32 personality module.
|
||||
static BOOL KiIsWin32DllNameA(const char* name) {
|
||||
return name && (
|
||||
!KiStricmpA(name, "kernel32.dll") || !KiStricmpA(name, "user32.dll") ||
|
||||
!KiStricmpA(name, "gdi32.dll") || !KiStricmpA(name, "ole32.dll") ||
|
||||
!KiStricmpA(name, "winmm.dll") || !KiStricmpA(name, "win32.dll"));
|
||||
return KiMatchesWin32DllNameA(name, "kernel32") ||
|
||||
KiMatchesWin32DllNameA(name, "user32") ||
|
||||
KiMatchesWin32DllNameA(name, "gdi32") ||
|
||||
KiMatchesWin32DllNameA(name, "ole32") ||
|
||||
KiMatchesWin32DllNameA(name, "winmm") ||
|
||||
KiMatchesWin32DllNameA(name, "win32");
|
||||
}
|
||||
|
||||
// Implements the dbg Write A operation.
|
||||
@@ -1100,11 +1349,13 @@ static void KiDbgWriteA(const char* s) {
|
||||
if (s) K32WriteConsoleSpan(s, strlen(s));
|
||||
}
|
||||
|
||||
// Returns module Handle A.
|
||||
// Returns the process-local module handle for one ANSI module name.
|
||||
__declspec(dllexport) HANDLE GetModuleHandleA(const char* lpModuleName) {
|
||||
if (!lpModuleName) return WIN32_HMODULE_EXE;
|
||||
if (KiIsWin32DllNameA(lpModuleName)) return WIN32_HMODULE_WIN32;
|
||||
gLastError = 126; // ERROR_MOD_NOT_FOUND
|
||||
if (!lpModuleName)
|
||||
return WIN32_HMODULE_EXE;
|
||||
if (KiIsWin32DllNameA(lpModuleName))
|
||||
return WIN32_HMODULE_WIN32;
|
||||
gLastError = 126; // ERROR_MOD_NOT_FOUND.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1175,13 +1426,31 @@ __declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcNam
|
||||
return (void*)address;
|
||||
}
|
||||
|
||||
#define GP(name) if (strcmp(lpProcName, #name) == 0) return (void*)&name
|
||||
#define GP(name) do { \
|
||||
if (strcmp(lpProcName, #name) == 0) return (void*)&name; \
|
||||
} while (0)
|
||||
GP(GetLastError); GP(SetLastError); GP(LoadLibraryA); GP(GetProcAddress);
|
||||
GP(IsDebuggerPresent);
|
||||
GP(FreeLibrary); GP(VirtualAlloc); GP(VirtualFree); GP(VirtualProtect);
|
||||
GP(VirtualQuery); GP(GetTickCount); GP(GetTickCount64); GP(Sleep);
|
||||
GP(CreateEventA); GP(CreateEventW); GP(SetEvent); GP(ResetEvent);
|
||||
GP(CreateThread); GP(ExitThread); GP(WaitForSingleObject);
|
||||
GP(CreateMutexA); GP(CreateMutexW); GP(ReleaseMutex); GP(CloseHandle);
|
||||
GP(InitializeCriticalSection); GP(InitializeCriticalSectionAndSpinCount);
|
||||
GP(DeleteCriticalSection); GP(EnterCriticalSection); GP(LeaveCriticalSection);
|
||||
GP(TryEnterCriticalSection); GP(InitializeConditionVariable);
|
||||
GP(SleepConditionVariableCS); GP(WakeConditionVariable); GP(WakeAllConditionVariable);
|
||||
GP(TlsAlloc); GP(TlsFree); GP(TlsGetValue); GP(TlsSetValue);
|
||||
GP(GetConsoleScreenBufferInfo); GP(FillConsoleOutputCharacterA);
|
||||
GP(FillConsoleOutputAttribute); GP(SetConsoleCursorPosition);
|
||||
GP(waveOutGetNumDevs); GP(waveOutOpen); GP(waveOutClose);
|
||||
GP(waveOutPrepareHeader); GP(waveOutUnprepareHeader); GP(waveOutWrite);
|
||||
GP(waveOutPause); GP(waveOutRestart); GP(waveOutReset);
|
||||
GP(waveOutSetVolume); GP(waveOutGetVolume); GP(waveOutGetPosition); GP(waveOutGetID);
|
||||
GP(waveOutGetDevCapsA); GP(waveOutGetDevCapsW);
|
||||
GP(waveOutGetErrorTextA); GP(waveOutGetErrorTextW);
|
||||
GP(waveOutBreakLoop); GP(waveOutMessage);
|
||||
GP(timeGetTime); GP(timeBeginPeriod); GP(timeEndPeriod);
|
||||
#undef GP
|
||||
KiDbgWriteA("GetProcAddress miss: ");
|
||||
KiDbgWriteA(lpProcName);
|
||||
@@ -1275,53 +1544,163 @@ __declspec(dllexport) char* GetCommandLineA(void) {
|
||||
#define WAIT_OBJECT_0 0x00000000UL
|
||||
#define WAIT_TIMEOUT 0x00000102UL
|
||||
#define INFINITE 0xFFFFFFFFUL
|
||||
#define CREATE_SUSPENDED 0x00000004UL
|
||||
|
||||
typedef DWORD (__stdcall *K32_THREAD_START_ROUTINE)(void* parameter);
|
||||
|
||||
typedef struct K32_THREAD_START_CONTEXT {
|
||||
K32_THREAD_START_ROUTINE StartRoutine;
|
||||
void* Parameter;
|
||||
} K32_THREAD_START_CONTEXT;
|
||||
|
||||
// Invokes one Win32 thread procedure and terminates the native thread on return.
|
||||
static void K32ThreadStart(uintptr_t argument) {
|
||||
K32_THREAD_START_CONTEXT* context = (K32_THREAD_START_CONTEXT*)argument;
|
||||
K32_THREAD_START_ROUTINE startRoutine = context->StartRoutine;
|
||||
void* parameter = context->Parameter;
|
||||
free(context);
|
||||
|
||||
DWORD exitCode = startRoutine(parameter);
|
||||
SysExitThread((int32_t)exitCode);
|
||||
while (true) {
|
||||
(void)SysYield();
|
||||
}
|
||||
}
|
||||
|
||||
// Creates one Win32 thread with return-to-ExitThread semantics.
|
||||
__declspec(dllexport) HANDLE CreateThread(void* lpThreadAttributes,
|
||||
size_t dwStackSize,
|
||||
void* lpStartAddress,
|
||||
void* lpParameter,
|
||||
DWORD dwCreationFlags,
|
||||
DWORD* lpThreadId) {
|
||||
(void)lpThreadAttributes; (void)dwStackSize; (void)dwCreationFlags;
|
||||
(void)lpThreadAttributes;
|
||||
(void)dwStackSize;
|
||||
if (!lpStartAddress || (dwCreationFlags & ~CREATE_SUSPENDED)) {
|
||||
gLastError = 87;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
K32_THREAD_START_CONTEXT* context =
|
||||
(K32_THREAD_START_CONTEXT*)malloc(sizeof(*context));
|
||||
if (!context) {
|
||||
gLastError = 8;
|
||||
return NULL;
|
||||
}
|
||||
context->StartRoutine = (K32_THREAD_START_ROUTINE)lpStartAddress;
|
||||
context->Parameter = lpParameter;
|
||||
|
||||
uint32_t tid = 0;
|
||||
uint32_t kh = 0;
|
||||
SYS_CREATE_THREAD_REQUEST req = {
|
||||
(uintptr_t)lpStartAddress,
|
||||
(uintptr_t)lpParameter,
|
||||
0,
|
||||
(uintptr_t)K32ThreadStart,
|
||||
(uintptr_t)context,
|
||||
0,
|
||||
(dwCreationFlags & CREATE_SUSPENDED)
|
||||
? SYS_CREATE_THREAD_FLAG_SUSPENDED
|
||||
: SYS_CREATE_THREAD_FLAG_NONE,
|
||||
&kh,
|
||||
&tid
|
||||
};
|
||||
uint32_t s = SysCreateThread(&req);
|
||||
if (s != 0) { gLastError = 11; return INVALID_HANDLE_VALUE; }
|
||||
if (lpThreadId) *lpThreadId = (DWORD)tid;
|
||||
uint32_t status = SysCreateThread(&req);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
free(context);
|
||||
gLastError = status == NTLSTATUS_NO_MEMORY ? 8 : 11;
|
||||
return NULL;
|
||||
}
|
||||
if (lpThreadId)
|
||||
*lpThreadId = (DWORD)tid;
|
||||
return KH_TO_HANDLE(kh);
|
||||
}
|
||||
|
||||
// WaitForSingleObject - blocks on native waitable handles. A zero timeout is
|
||||
// only a probe for mutexes/semaphores until native timeout waits exist.
|
||||
// Terminates the calling Win32 thread with the supplied exit code.
|
||||
__declspec(dllexport) _Noreturn void ExitThread(DWORD exitCode) {
|
||||
SysExitThread((int32_t)exitCode);
|
||||
while (true) {
|
||||
(void)SysYield();
|
||||
}
|
||||
}
|
||||
|
||||
// Waits for one Win32 mutex wrapper or native waitable handle.
|
||||
__declspec(dllexport) DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) {
|
||||
if (!IS_VALID_KERNEL_HANDLE(hHandle)) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||||
|
||||
uint32_t kh = HANDLE_TO_KH(hHandle);
|
||||
if (dwMilliseconds == 0) {
|
||||
uint32_t s = SysTryAcquireMutex(kh);
|
||||
if (s == 0) return WAIT_OBJECT_0;
|
||||
|
||||
s = SysTryWaitSem(kh);
|
||||
if (s == 0) return WAIT_OBJECT_0;
|
||||
|
||||
return WAIT_TIMEOUT;
|
||||
if (!hHandle || hHandle == INVALID_HANDLE_VALUE) {
|
||||
gLastError = 6;
|
||||
return 0xFFFFFFFFUL;
|
||||
}
|
||||
|
||||
uint32_t s = SysWait(kh, SYS_WAIT_FLAG_NONE);
|
||||
if (s == 0) return WAIT_OBJECT_0;
|
||||
K32_MUTEX_HANDLE* mutex = K32ReferenceMutex(hHandle);
|
||||
if (mutex) {
|
||||
DWORD result = K32WaitMutex(mutex, dwMilliseconds);
|
||||
K32DereferenceMutex(mutex);
|
||||
return result;
|
||||
}
|
||||
if (!IS_VALID_KERNEL_HANDLE(hHandle)) {
|
||||
gLastError = 6;
|
||||
return 0xFFFFFFFFUL;
|
||||
}
|
||||
|
||||
uint64_t timeout = dwMilliseconds == INFINITE
|
||||
? SYS_WAIT_TIMEOUT_INFINITE
|
||||
: (uint64_t)dwMilliseconds;
|
||||
uint32_t s = SysWaitSingle(HANDLE_TO_KH(hHandle), SYS_WAIT_FLAG_NONE, timeout);
|
||||
if (s == NTLSTATUS_SUCCESS) return WAIT_OBJECT_0;
|
||||
if (s == NTLSTATUS_ABANDONED) return 0x00000080UL;
|
||||
if (s == NTLSTATUS_TIMEOUT) return WAIT_TIMEOUT;
|
||||
|
||||
gLastError = 6;
|
||||
return 0xFFFFFFFFUL;
|
||||
}
|
||||
|
||||
// Creates one unnamed native event with Win32 reset semantics.
|
||||
__declspec(dllexport) HANDLE CreateEventA(void* lpEventAttributes,
|
||||
BOOL bManualReset,
|
||||
BOOL bInitialState,
|
||||
const char* lpName) {
|
||||
(void)lpEventAttributes;
|
||||
if (lpName && lpName[0]) {
|
||||
gLastError = 120;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t handle = 0;
|
||||
uint32_t status = SysCreateEvent(bManualReset ? 1u : 0u,
|
||||
bInitialState ? 1u : 0u,
|
||||
&handle);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
gLastError = status == NTLSTATUS_NO_MEMORY ? 8 : 87;
|
||||
return NULL;
|
||||
}
|
||||
return KH_TO_HANDLE(handle);
|
||||
}
|
||||
|
||||
// Creates one unnamed native event from the wide Win32 entry point.
|
||||
__declspec(dllexport) HANDLE CreateEventW(void* lpEventAttributes,
|
||||
BOOL bManualReset,
|
||||
BOOL bInitialState,
|
||||
const unsigned short* lpName) {
|
||||
if (lpName && lpName[0]) {
|
||||
gLastError = 120;
|
||||
return NULL;
|
||||
}
|
||||
return CreateEventA(lpEventAttributes, bManualReset, bInitialState, NULL);
|
||||
}
|
||||
|
||||
// Signals one native event.
|
||||
__declspec(dllexport) BOOL SetEvent(HANDLE hEvent) {
|
||||
if (!IS_VALID_KERNEL_HANDLE(hEvent)) { gLastError = 6; return FALSE; }
|
||||
uint32_t status = SysSignalEvent(HANDLE_TO_KH(hEvent));
|
||||
if (status != NTLSTATUS_SUCCESS) { gLastError = 6; return FALSE; }
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Clears one native event.
|
||||
__declspec(dllexport) BOOL ResetEvent(HANDLE hEvent) {
|
||||
if (!IS_VALID_KERNEL_HANDLE(hEvent)) { gLastError = 6; return FALSE; }
|
||||
uint32_t status = SysClearEvent(HANDLE_TO_KH(hEvent));
|
||||
if (status != NTLSTATUS_SUCCESS) { gLastError = 6; return FALSE; }
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements the extract Application Path A operation.
|
||||
static BOOL ExtractApplicationPathA(const char* commandLine, char* out, size_t outSize) {
|
||||
if (!commandLine || !out || outSize == 0) return FALSE;
|
||||
@@ -1449,10 +1828,22 @@ __declspec(dllexport) BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements the resume Thread operation.
|
||||
// Decrements a thread's suspend count and makes it runnable at zero.
|
||||
__declspec(dllexport) DWORD ResumeThread(HANDLE hThread) {
|
||||
(void)hThread;
|
||||
return 0; // previous suspend count
|
||||
if (!hThread || hThread == INVALID_HANDLE_VALUE) {
|
||||
gLastError = 6;
|
||||
return 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
uint32_t previousSuspendCount = 0;
|
||||
uint32_t status = SysResumeThread(HANDLE_TO_KH(hThread),
|
||||
&previousSuspendCount);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
gLastError = status == NTLSTATUS_ACCESS_DENIED ? 5 : 6;
|
||||
return 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
return previousSuspendCount;
|
||||
}
|
||||
|
||||
#define THREAD_PRIORITY_NORMAL 0
|
||||
@@ -1876,7 +2267,7 @@ __declspec(dllexport) HANDLE FindFirstFileA(const char* lpFileName,
|
||||
|
||||
// Read the first entry that matches the pattern.
|
||||
KDIR_ENTRY ke;
|
||||
for (;;) {
|
||||
while (true) {
|
||||
uint32_t rs = SysReadDir(kh, &ke);
|
||||
if (rs != 0) {
|
||||
// Directory exhausted or error.
|
||||
@@ -1902,7 +2293,7 @@ __declspec(dllexport) BOOL FindNextFileA(HANDLE hFindFile,
|
||||
FIND_HANDLE* fh = (FIND_HANDLE*)hFindFile;
|
||||
KDIR_ENTRY ke;
|
||||
|
||||
for (;;) {
|
||||
while (true) {
|
||||
uint32_t rs = SysReadDir(fh->kh, &ke);
|
||||
if (rs != 0) {
|
||||
gLastError = 18; // ERROR_NO_MORE_FILES
|
||||
@@ -1979,28 +2370,88 @@ __declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBu
|
||||
return (DWORD)(needed - 1); // length without NUL, matching Win32
|
||||
}
|
||||
|
||||
// Creates one unnamed recursive Win32 mutex over a native kernel mutex.
|
||||
__declspec(dllexport) HANDLE CreateMutexA(void* lpMutexAttributes, BOOL bInitialOwner,
|
||||
const char* lpName) {
|
||||
(void)lpMutexAttributes; (void)lpName;
|
||||
uint32_t kh = 0;
|
||||
uint32_t s = SysCreateMutex(&kh);
|
||||
if (s != 0 || !kh) { gLastError = 8; return NULL; }
|
||||
(void)lpMutexAttributes;
|
||||
if (lpName && lpName[0]) {
|
||||
gLastError = 120;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
K32_MUTEX_HANDLE* mutex = (K32_MUTEX_HANDLE*)calloc(1, sizeof(*mutex));
|
||||
if (!mutex) {
|
||||
gLastError = 8;
|
||||
return NULL;
|
||||
}
|
||||
uint32_t status = SysCreateMutex(&mutex->NativeHandle);
|
||||
if (status != NTLSTATUS_SUCCESS || !mutex->NativeHandle) {
|
||||
free(mutex);
|
||||
gLastError = status == NTLSTATUS_NO_MEMORY ? 8 : 1;
|
||||
return NULL;
|
||||
}
|
||||
mutex->Magic = K32_MUTEX_MAGIC;
|
||||
if (bInitialOwner) {
|
||||
s = SysWait(kh, SYS_WAIT_FLAG_NONE);
|
||||
if (s != 0) {
|
||||
SysCloseHandle(kh);
|
||||
status = SysWait(mutex->NativeHandle, SYS_WAIT_FLAG_NONE);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_ABANDONED) {
|
||||
(void)SysCloseHandle(mutex->NativeHandle);
|
||||
free(mutex);
|
||||
gLastError = 1;
|
||||
return NULL;
|
||||
}
|
||||
mutex->OwnerTid = K32CurrentTidOrOne();
|
||||
mutex->Recursion = 1;
|
||||
}
|
||||
return KH_TO_HANDLE(kh);
|
||||
K32RegisterMutex(mutex);
|
||||
return (HANDLE)mutex;
|
||||
}
|
||||
|
||||
// Releases mutex.
|
||||
// Creates one unnamed recursive Win32 mutex from a wide-name request.
|
||||
__declspec(dllexport) HANDLE CreateMutexW(void* lpMutexAttributes, BOOL bInitialOwner,
|
||||
const unsigned short* lpName) {
|
||||
if (lpName && lpName[0]) {
|
||||
gLastError = 120;
|
||||
return NULL;
|
||||
}
|
||||
return CreateMutexA(lpMutexAttributes, bInitialOwner, NULL);
|
||||
}
|
||||
|
||||
// Releases one recursion level from a Win32 mutex wrapper.
|
||||
__declspec(dllexport) BOOL ReleaseMutex(HANDLE hMutex) {
|
||||
if (!IS_VALID_KERNEL_HANDLE(hMutex)) { gLastError = 6; return FALSE; }
|
||||
uint32_t s = SysReleaseMutex(HANDLE_TO_KH(hMutex));
|
||||
if (s != 0) { gLastError = 6; return FALSE; }
|
||||
K32_MUTEX_HANDLE* mutex = K32ReferenceMutex(hMutex);
|
||||
if (!mutex) {
|
||||
gLastError = 6;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD tid = K32CurrentTidOrOne();
|
||||
K32MutexStateLock(mutex);
|
||||
if (mutex->OwnerTid != tid || mutex->Recursion == 0) {
|
||||
K32MutexStateUnlock(mutex);
|
||||
K32DereferenceMutex(mutex);
|
||||
gLastError = 288;
|
||||
return FALSE;
|
||||
}
|
||||
mutex->Recursion--;
|
||||
if (mutex->Recursion != 0) {
|
||||
K32MutexStateUnlock(mutex);
|
||||
K32DereferenceMutex(mutex);
|
||||
return TRUE;
|
||||
}
|
||||
mutex->OwnerTid = 0;
|
||||
K32MutexStateUnlock(mutex);
|
||||
|
||||
uint32_t status = SysReleaseMutex(mutex->NativeHandle);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
K32MutexStateLock(mutex);
|
||||
mutex->OwnerTid = tid;
|
||||
mutex->Recursion = 1;
|
||||
K32MutexStateUnlock(mutex);
|
||||
K32DereferenceMutex(mutex);
|
||||
gLastError = 288;
|
||||
return FALSE;
|
||||
}
|
||||
K32DereferenceMutex(mutex);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -2030,8 +2481,19 @@ __declspec(dllexport) BOOL ReleaseSemaphore(HANDLE hSemaphore, long lReleaseCoun
|
||||
// using the storage as a small recursive spin lock. Bink uses these from its
|
||||
// worker/copy paths, so the old no-op implementation allowed internal state
|
||||
// races as soon as CreateThread started working.
|
||||
typedef struct { uintptr_t _opaque[5]; } CRITICAL_SECTION;
|
||||
typedef struct { void* _ptr; } CONDITION_VARIABLE;
|
||||
typedef struct K32_CONDITION_WAITER K32_CONDITION_WAITER;
|
||||
typedef struct K32_CONDITION_STATE {
|
||||
volatile long Lock;
|
||||
K32_CONDITION_WAITER* Head;
|
||||
K32_CONDITION_WAITER* Tail;
|
||||
} K32_CONDITION_STATE;
|
||||
|
||||
struct K32_CONDITION_WAITER {
|
||||
K32_CONDITION_WAITER* Previous;
|
||||
K32_CONDITION_WAITER* Next;
|
||||
uint32_t Event;
|
||||
BOOL Queued;
|
||||
};
|
||||
|
||||
#define K32_CS_LOCK 0
|
||||
#define K32_CS_OWNER 1
|
||||
@@ -2067,8 +2529,12 @@ __declspec(dllexport) void EnterCriticalSection(CRITICAL_SECTION* lpCS) {
|
||||
lpCS->_opaque[K32_CS_RECURSION]++;
|
||||
return;
|
||||
}
|
||||
while (_InterlockedCompareExchange(K32CsLockWord(lpCS), 1, 0) != 0)
|
||||
uint32_t spins = 0;
|
||||
while (_InterlockedCompareExchange(K32CsLockWord(lpCS), 1, 0) != 0) {
|
||||
_mm_pause();
|
||||
if ((++spins & 0xFFFu) == 0)
|
||||
(void)SysYield();
|
||||
}
|
||||
lpCS->_opaque[K32_CS_OWNER] = tid;
|
||||
lpCS->_opaque[K32_CS_RECURSION] = 1;
|
||||
}
|
||||
@@ -2100,19 +2566,183 @@ __declspec(dllexport) BOOL TryEnterCriticalSection(CRITICAL_SECTION* lpCS) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Initializes condition Variable.
|
||||
__declspec(dllexport) void InitializeConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; }
|
||||
// Acquires one condition-variable queue lock.
|
||||
static void K32ConditionLock(K32_CONDITION_STATE* state) {
|
||||
uint32_t spins = 0;
|
||||
while (_InterlockedCompareExchange(&state->Lock, 1, 0) != 0) {
|
||||
_mm_pause();
|
||||
if ((++spins & 0xFFFu) == 0)
|
||||
(void)SysYield();
|
||||
}
|
||||
}
|
||||
|
||||
// Releases one condition-variable queue lock.
|
||||
static void K32ConditionUnlock(K32_CONDITION_STATE* state) {
|
||||
_ReadWriteBarrier();
|
||||
_InterlockedExchange(&state->Lock, 0);
|
||||
}
|
||||
|
||||
// Returns the lazily allocated process-local condition-variable state.
|
||||
static K32_CONDITION_STATE* K32ConditionState(CONDITION_VARIABLE* cv, BOOL create) {
|
||||
if (!cv)
|
||||
return NULL;
|
||||
|
||||
K32_CONDITION_STATE* state = (K32_CONDITION_STATE*)cv->_ptr;
|
||||
if (state || !create)
|
||||
return state;
|
||||
|
||||
K32_CONDITION_STATE* allocated = (K32_CONDITION_STATE*)calloc(1, sizeof(*allocated));
|
||||
if (!allocated)
|
||||
return NULL;
|
||||
|
||||
void* previous = _InterlockedCompareExchangePointer((void* volatile*)&cv->_ptr,
|
||||
allocated,
|
||||
NULL);
|
||||
if (previous) {
|
||||
free(allocated);
|
||||
state = (K32_CONDITION_STATE*)previous;
|
||||
} else {
|
||||
state = allocated;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
// Removes one waiter while the condition-variable queue lock is held.
|
||||
static void K32ConditionRemoveWaiter(K32_CONDITION_STATE* state,
|
||||
K32_CONDITION_WAITER* waiter) {
|
||||
if (!waiter->Queued)
|
||||
return;
|
||||
|
||||
if (waiter->Previous)
|
||||
waiter->Previous->Next = waiter->Next;
|
||||
else
|
||||
state->Head = waiter->Next;
|
||||
|
||||
if (waiter->Next)
|
||||
waiter->Next->Previous = waiter->Previous;
|
||||
else
|
||||
state->Tail = waiter->Previous;
|
||||
|
||||
waiter->Previous = NULL;
|
||||
waiter->Next = NULL;
|
||||
waiter->Queued = FALSE;
|
||||
}
|
||||
|
||||
// Initializes one process-local condition variable.
|
||||
__declspec(dllexport) void InitializeConditionVariable(CONDITION_VARIABLE* cv) {
|
||||
if (cv)
|
||||
cv->_ptr = NULL;
|
||||
}
|
||||
|
||||
// Atomically releases a critical section and waits for one condition wake.
|
||||
__declspec(dllexport) BOOL SleepConditionVariableCS(CONDITION_VARIABLE* cv,
|
||||
CRITICAL_SECTION* cs,
|
||||
DWORD dwMilliseconds) {
|
||||
(void)cv; (void)cs; (void)dwMilliseconds;
|
||||
return TRUE;
|
||||
if (!cv || !cs) {
|
||||
gLastError = 87;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD tid = K32CurrentTidOrOne();
|
||||
uintptr_t recursion = cs->_opaque[K32_CS_RECURSION];
|
||||
if ((DWORD)cs->_opaque[K32_CS_OWNER] != tid || recursion == 0) {
|
||||
gLastError = 87;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
K32_CONDITION_STATE* state = K32ConditionState(cv, TRUE);
|
||||
if (!state) {
|
||||
gLastError = 8;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
uint32_t event = 0;
|
||||
KSTATUS status = SysCreateEvent(0, 0, &event);
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
gLastError = status == NTLSTATUS_NO_MEMORY ? 8 : 87;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// The waiter is heap-backed because process termination can stop a user
|
||||
// thread at syscall return before it can remove a stack-backed queue node.
|
||||
K32_CONDITION_WAITER* waiter = (K32_CONDITION_WAITER*)calloc(1, sizeof(*waiter));
|
||||
if (!waiter) {
|
||||
(void)SysCloseHandle(event);
|
||||
gLastError = 8;
|
||||
return FALSE;
|
||||
}
|
||||
waiter->Event = event;
|
||||
waiter->Queued = TRUE;
|
||||
|
||||
K32ConditionLock(state);
|
||||
waiter->Previous = state->Tail;
|
||||
if (state->Tail)
|
||||
state->Tail->Next = waiter;
|
||||
else
|
||||
state->Head = waiter;
|
||||
state->Tail = waiter;
|
||||
K32ConditionUnlock(state);
|
||||
|
||||
// The waiter is visible before the lock is released, so a wake that races
|
||||
// this transition is retained by the private auto-reset event.
|
||||
cs->_opaque[K32_CS_RECURSION] = 0;
|
||||
cs->_opaque[K32_CS_OWNER] = 0;
|
||||
_ReadWriteBarrier();
|
||||
_InterlockedExchange(K32CsLockWord(cs), 0);
|
||||
|
||||
uint64_t timeout = dwMilliseconds == INFINITE
|
||||
? SYS_WAIT_TIMEOUT_INFINITE
|
||||
: (uint64_t)dwMilliseconds;
|
||||
status = SysWaitSingle(event, SYS_WAIT_FLAG_NONE, timeout);
|
||||
|
||||
K32ConditionLock(state);
|
||||
BOOL wakeWon = waiter->Queued ? FALSE : TRUE;
|
||||
K32ConditionRemoveWaiter(state, waiter);
|
||||
K32ConditionUnlock(state);
|
||||
|
||||
EnterCriticalSection(cs);
|
||||
cs->_opaque[K32_CS_RECURSION] = recursion;
|
||||
(void)SysCloseHandle(event);
|
||||
free(waiter);
|
||||
|
||||
if (status == NTLSTATUS_SUCCESS || wakeWon)
|
||||
return TRUE;
|
||||
if (status == NTLSTATUS_TIMEOUT)
|
||||
gLastError = 1460;
|
||||
else
|
||||
gLastError = 6;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Wakes the oldest waiter on one condition variable.
|
||||
__declspec(dllexport) void WakeConditionVariable(CONDITION_VARIABLE* cv) {
|
||||
K32_CONDITION_STATE* state = K32ConditionState(cv, FALSE);
|
||||
if (!state)
|
||||
return;
|
||||
|
||||
K32ConditionLock(state);
|
||||
K32_CONDITION_WAITER* waiter = state->Head;
|
||||
if (waiter) {
|
||||
K32ConditionRemoveWaiter(state, waiter);
|
||||
(void)SysSignalEvent(waiter->Event);
|
||||
}
|
||||
K32ConditionUnlock(state);
|
||||
}
|
||||
|
||||
// Wakes every waiter currently queued on one condition variable.
|
||||
__declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE* cv) {
|
||||
K32_CONDITION_STATE* state = K32ConditionState(cv, FALSE);
|
||||
if (!state)
|
||||
return;
|
||||
|
||||
K32ConditionLock(state);
|
||||
while (state->Head) {
|
||||
K32_CONDITION_WAITER* waiter = state->Head;
|
||||
K32ConditionRemoveWaiter(state, waiter);
|
||||
(void)SysSignalEvent(waiter->Event);
|
||||
}
|
||||
K32ConditionUnlock(state);
|
||||
}
|
||||
// Implements the wake Condition Variable operation.
|
||||
__declspec(dllexport) void WakeConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; }
|
||||
// Implements the wake All Condition Variable operation.
|
||||
__declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; }
|
||||
|
||||
// Timing & Performance Counters.
|
||||
|
||||
@@ -2961,7 +3591,7 @@ SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpFilter) {
|
||||
return NULL; // previous filter: none
|
||||
}
|
||||
|
||||
// Checks whether debugger Present.
|
||||
// Reports that no user-mode debugger is attached.
|
||||
__declspec(dllexport) BOOL IsDebuggerPresent(void) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -1,126 +1,28 @@
|
||||
// Win32 multimedia compatibility stubs. This file provides minimal timing and sound API entry points.
|
||||
// Win32 multimedia timing entry points.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
// Types.
|
||||
typedef unsigned int MMRESULT;
|
||||
typedef void* HWAVEOUT;
|
||||
typedef unsigned int MMRESULT;
|
||||
|
||||
#define MMSYSERR_NOERROR 0
|
||||
#define MMSYSERR_NOTSUPPORTED 8
|
||||
#define WAVERR_STILLPLAYING 33
|
||||
|
||||
// WAVEFORMATEX - standard PCM audio format descriptor.
|
||||
typedef struct {
|
||||
unsigned short wFormatTag;
|
||||
unsigned short nChannels;
|
||||
unsigned int nSamplesPerSec;
|
||||
unsigned int nAvgBytesPerSec;
|
||||
unsigned short nBlockAlign;
|
||||
unsigned short wBitsPerSample;
|
||||
unsigned short cbSize;
|
||||
} WAVEFORMATEX;
|
||||
|
||||
// WAVEHDR - buffer header submitted to waveOutWrite.
|
||||
typedef struct _WAVEHDR {
|
||||
char* lpData;
|
||||
unsigned int dwBufferLength;
|
||||
unsigned int dwBytesRecorded;
|
||||
uintptr_t dwUser;
|
||||
unsigned int dwFlags;
|
||||
unsigned int dwLoops;
|
||||
struct _WAVEHDR* lpNext;
|
||||
uintptr_t reserved;
|
||||
} WAVEHDR;
|
||||
|
||||
// MMTIME - used by waveOutGetPosition.
|
||||
typedef struct {
|
||||
unsigned int wType;
|
||||
union {
|
||||
unsigned int ms;
|
||||
unsigned int sample;
|
||||
unsigned int cb;
|
||||
unsigned int ticks;
|
||||
} u;
|
||||
} MMTIME;
|
||||
|
||||
// Timer.
|
||||
#define MMSYSERR_NOERROR 0u
|
||||
|
||||
// Returns monotonic milliseconds since boot.
|
||||
__declspec(dllexport) unsigned long timeGetTime(void) {
|
||||
uint64_t frequency = 0;
|
||||
if (SysGetTickFrequency(&frequency) != NTLSTATUS_SUCCESS || !frequency) return 0;
|
||||
if (SysGetTickFrequency(&frequency) != NTLSTATUS_SUCCESS || !frequency)
|
||||
return 0;
|
||||
return (unsigned long)SysTicksToMilliseconds(SysGetTick(), frequency);
|
||||
}
|
||||
|
||||
// Waveform audio (stubs - no audio hardware at this stage).
|
||||
|
||||
__declspec(dllexport) MMRESULT waveOutOpen(HWAVEOUT* phwo, unsigned int deviceId,
|
||||
const WAVEFORMATEX* fmt, uintptr_t callback,
|
||||
uintptr_t callbackInstance, unsigned long flags) {
|
||||
(void)deviceId; (void)fmt; (void)callback; (void)callbackInstance; (void)flags;
|
||||
if (phwo) *phwo = (void*)0;
|
||||
return MMSYSERR_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
// Implements the wave Out Close operation.
|
||||
__declspec(dllexport) MMRESULT waveOutClose(HWAVEOUT hwo) {
|
||||
(void)hwo; return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
__declspec(dllexport) MMRESULT waveOutPrepareHeader(HWAVEOUT hwo, WAVEHDR* hdr,
|
||||
unsigned int size) {
|
||||
(void)hwo; (void)hdr; (void)size; return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
__declspec(dllexport) MMRESULT waveOutUnprepareHeader(HWAVEOUT hwo, WAVEHDR* hdr,
|
||||
unsigned int size) {
|
||||
(void)hwo; (void)hdr; (void)size; return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
// Implements the wave Out Write operation.
|
||||
__declspec(dllexport) MMRESULT waveOutWrite(HWAVEOUT hwo, WAVEHDR* hdr, unsigned int size) {
|
||||
(void)hwo; (void)hdr; (void)size; return MMSYSERR_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
// Implements the wave Out Pause operation.
|
||||
__declspec(dllexport) MMRESULT waveOutPause(HWAVEOUT hwo) {
|
||||
(void)hwo; return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
// Implements the wave Out Restart operation.
|
||||
__declspec(dllexport) MMRESULT waveOutRestart(HWAVEOUT hwo) {
|
||||
(void)hwo; return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
// Implements the wave Out Reset operation.
|
||||
__declspec(dllexport) MMRESULT waveOutReset(HWAVEOUT hwo) {
|
||||
(void)hwo; return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
// Implements the wave Out Set Volume operation.
|
||||
__declspec(dllexport) MMRESULT waveOutSetVolume(HWAVEOUT hwo, unsigned long volume) {
|
||||
(void)hwo; (void)volume; return MMSYSERR_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
__declspec(dllexport) MMRESULT waveOutGetPosition(HWAVEOUT hwo, MMTIME* mmt,
|
||||
unsigned int size) {
|
||||
(void)hwo; (void)size;
|
||||
if (mmt) { mmt->wType = 0; mmt->u.ms = 0; }
|
||||
// Accepts timer-resolution requests without reprogramming the native timer.
|
||||
__declspec(dllexport) MMRESULT timeBeginPeriod(unsigned int periodMilliseconds) {
|
||||
(void)periodMilliseconds;
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
// Timer resolution control
|
||||
// No hardware timer reprogramming at this stage; pretend success so callers
|
||||
// that wrap their audio/game loop in timeBeginPeriod(1) don't bail out.
|
||||
__declspec(dllexport) MMRESULT timeBeginPeriod(unsigned int uPeriod) {
|
||||
(void)uPeriod;
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
// Implements the time End Period operation.
|
||||
__declspec(dllexport) MMRESULT timeEndPeriod(unsigned int uPeriod) {
|
||||
(void)uPeriod;
|
||||
// Releases one timer-resolution request without changing native timer policy.
|
||||
__declspec(dllexport) MMRESULT timeEndPeriod(unsigned int periodMilliseconds) {
|
||||
(void)periodMilliseconds;
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -164,7 +164,7 @@ void* _malloc(size_t size) {
|
||||
size = AlignSize(size);
|
||||
|
||||
HeapLock();
|
||||
for (;;) {
|
||||
while (true) {
|
||||
for (HEAP_SEGMENT* seg = gSegments; seg; seg = seg->Next) {
|
||||
for (HEAP_BLOCK* block = seg->First; block; block = block->Next) {
|
||||
if (block->Magic == USER_HEAP_MAGIC && block->Free && block->Size >= size) {
|
||||
|
||||
@@ -53,7 +53,7 @@ static void NtlpTerminateExceptionDispatch(void) {
|
||||
request.Disposition = SYS_EXCEPTION_DISPOSITION_TERMINATE_PROCESS;
|
||||
|
||||
uint32_t status = SysContinueException(&request);
|
||||
for (;;) SysExitProcess((int32_t)NtlpStatusOrFailure(status));
|
||||
while (true) SysExitProcess((int32_t)NtlpStatusOrFailure(status));
|
||||
}
|
||||
|
||||
// Asks the kernel to continue searching from the top-level native dispatcher.
|
||||
@@ -64,7 +64,7 @@ static void NtlpContinueExceptionSearch(void) {
|
||||
request.Disposition = SYS_EXCEPTION_DISPOSITION_CONTINUE_SEARCH;
|
||||
|
||||
uint32_t status = SysContinueException(&request);
|
||||
for (;;) SysExitProcess((int32_t)NtlpStatusOrFailure(status));
|
||||
while (true) SysExitProcess((int32_t)NtlpStatusOrFailure(status));
|
||||
}
|
||||
|
||||
// Asks the kernel to resume execution with a handler-edited user context.
|
||||
@@ -76,7 +76,7 @@ static void NtlpContinueExecution(SYS_USER_CONTEXT* context) {
|
||||
request.Context = context;
|
||||
|
||||
uint32_t status = SysContinueException(&request);
|
||||
for (;;) SysExitProcess((int32_t)NtlpStatusOrFailure(status));
|
||||
while (true) SysExitProcess((int32_t)NtlpStatusOrFailure(status));
|
||||
}
|
||||
|
||||
// Returns whether one kernel-provided exception frame has the expected ABI shape.
|
||||
|
||||
@@ -10,9 +10,9 @@ extern int main(void);
|
||||
void NtlNativeStart(void) {
|
||||
uint32_t status = NtlInitializeExceptionRuntime();
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
for (;;) SysExitProcess((int32_t)status);
|
||||
while (true) SysExitProcess((int32_t)status);
|
||||
}
|
||||
|
||||
int exitCode = main();
|
||||
for (;;) SysExitProcess(exitCode);
|
||||
while (true) SysExitProcess(exitCode);
|
||||
}
|
||||
@@ -12,11 +12,11 @@ extern void PosixFdInit(void);
|
||||
void PosixStart(void) {
|
||||
uint32_t status = NtlInitializeExceptionRuntime();
|
||||
if (status != NTLSTATUS_SUCCESS) {
|
||||
for (;;) SysExitProcess((int32_t)status);
|
||||
while (true) SysExitProcess((int32_t)status);
|
||||
}
|
||||
|
||||
PosixFdInit();
|
||||
int exitCode = main();
|
||||
exit(exitCode);
|
||||
for (;;) {}
|
||||
while (true) {}
|
||||
}
|
||||
+134
-118
@@ -10,138 +10,154 @@ SYS_KILL_PROCESS EQU 5
|
||||
SYS_GET_PROCESS_LIST EQU 6
|
||||
SYS_GET_PROCESS_PID EQU 7
|
||||
SYS_CREATE_THREAD EQU 8
|
||||
SYS_GET_THREAD_LIST EQU 9
|
||||
SYS_LOAD_MODULE EQU 10
|
||||
SYS_UNLOAD_MODULE EQU 11
|
||||
SYS_GET_MODULE_INFO EQU 12
|
||||
SYS_GET_MODULE_EXPORT EQU 13
|
||||
SYS_GET_MODULE_LIST EQU 14
|
||||
SYS_GET_CURRENT_TID EQU 15
|
||||
SYS_GET_CURRENT_PID EQU 16
|
||||
SYS_SET_THREAD_PRIORITY EQU 17
|
||||
SYS_YIELD EQU 18
|
||||
SYS_SLEEP EQU 19
|
||||
SYS_WAIT_SINGLE EQU 20
|
||||
SYS_WAIT_MANY EQU 21
|
||||
SYS_SET_USER_APC_DISPATCHER EQU 22
|
||||
SYS_APC_RETURN EQU 23
|
||||
SYS_SET_EXCEPTION_DISPATCHER EQU 24
|
||||
SYS_GET_EXCEPTION_DISPATCHER EQU 25
|
||||
SYS_CONTINUE_EXCEPTION EQU 26
|
||||
SYS_RAISE_EXCEPTION EQU 27
|
||||
SYS_RESUME_THREAD EQU 9
|
||||
SYS_GET_THREAD_LIST EQU 10
|
||||
SYS_LOAD_MODULE EQU 11
|
||||
SYS_UNLOAD_MODULE EQU 12
|
||||
SYS_GET_MODULE_INFO EQU 13
|
||||
SYS_GET_MODULE_EXPORT EQU 14
|
||||
SYS_GET_MODULE_LIST EQU 15
|
||||
SYS_GET_CURRENT_TID EQU 16
|
||||
SYS_GET_CURRENT_PID EQU 17
|
||||
SYS_SET_THREAD_PRIORITY EQU 18
|
||||
SYS_YIELD EQU 19
|
||||
SYS_SLEEP EQU 20
|
||||
SYS_WAIT_SINGLE EQU 21
|
||||
SYS_WAIT_MANY EQU 22
|
||||
SYS_SET_USER_APC_DISPATCHER EQU 23
|
||||
SYS_APC_RETURN EQU 24
|
||||
SYS_SET_EXCEPTION_DISPATCHER EQU 25
|
||||
SYS_GET_EXCEPTION_DISPATCHER EQU 26
|
||||
SYS_CONTINUE_EXCEPTION EQU 27
|
||||
SYS_RAISE_EXCEPTION EQU 28
|
||||
|
||||
; Virtual memory, mappings, and handles.
|
||||
SYS_ALLOC_VM EQU 28
|
||||
SYS_FREE_VM EQU 29
|
||||
SYS_PROTECT_VM EQU 30
|
||||
SYS_QUERY_VM EQU 31
|
||||
SYS_CLOSE_HANDLE EQU 32
|
||||
SYS_DUPLICATE_HANDLE EQU 33
|
||||
SYS_CREATE_FILE_MAPPING EQU 34
|
||||
SYS_MAP_FILE EQU 35
|
||||
SYS_ALLOC_VM EQU 29
|
||||
SYS_FREE_VM EQU 30
|
||||
SYS_PROTECT_VM EQU 31
|
||||
SYS_QUERY_VM EQU 32
|
||||
SYS_CLOSE_HANDLE EQU 33
|
||||
SYS_DUPLICATE_HANDLE EQU 34
|
||||
SYS_CREATE_FILE_MAPPING EQU 35
|
||||
SYS_MAP_FILE EQU 36
|
||||
|
||||
; Synchronization objects.
|
||||
SYS_CREATE_MUTEX EQU 36
|
||||
SYS_TRY_ACQUIRE_MUTEX EQU 37
|
||||
SYS_RELEASE_MUTEX EQU 38
|
||||
SYS_CREATE_SEM EQU 39
|
||||
SYS_TRY_WAIT_SEM EQU 40
|
||||
SYS_SIGNAL_SEM EQU 41
|
||||
SYS_CREATE_EVENT EQU 42
|
||||
SYS_SIGNAL_EVENT EQU 43
|
||||
SYS_CLEAR_EVENT EQU 44
|
||||
SYS_CREATE_MUTEX EQU 37
|
||||
SYS_TRY_ACQUIRE_MUTEX EQU 38
|
||||
SYS_RELEASE_MUTEX EQU 39
|
||||
SYS_CREATE_SEM EQU 40
|
||||
SYS_TRY_WAIT_SEM EQU 41
|
||||
SYS_SIGNAL_SEM EQU 42
|
||||
SYS_CREATE_EVENT EQU 43
|
||||
SYS_SIGNAL_EVENT EQU 44
|
||||
SYS_CLEAR_EVENT EQU 45
|
||||
|
||||
; Queues and named queues.
|
||||
SYS_CREATE_QUEUE EQU 45
|
||||
SYS_SEND_QUEUE EQU 46
|
||||
SYS_RECV_QUEUE EQU 47
|
||||
SYS_PUBLISH_QUEUE EQU 48
|
||||
SYS_OPEN_NAMED_QUEUE EQU 49
|
||||
SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS EQU 50
|
||||
SYS_CREATE_QUEUE EQU 46
|
||||
SYS_SEND_QUEUE EQU 47
|
||||
SYS_RECV_QUEUE EQU 48
|
||||
SYS_PUBLISH_QUEUE EQU 49
|
||||
SYS_OPEN_NAMED_QUEUE EQU 50
|
||||
SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS EQU 51
|
||||
|
||||
; Files, directories, and current directory.
|
||||
SYS_OPEN_FILE EQU 51
|
||||
SYS_READ_FILE EQU 52
|
||||
SYS_WRITE_FILE EQU 53
|
||||
SYS_READ_FILE_ASYNC EQU 54
|
||||
SYS_WRITE_FILE_ASYNC EQU 55
|
||||
SYS_CANCEL_IO EQU 56
|
||||
SYS_SEEK_FILE EQU 57
|
||||
SYS_GET_FILE_POS EQU 58
|
||||
SYS_GET_FILE_SIZE EQU 59
|
||||
SYS_GET_FILE_STAT EQU 60
|
||||
SYS_STAT_FILE EQU 61
|
||||
SYS_TRUNCATE_FILE EQU 62
|
||||
SYS_FLUSH_FILE EQU 63
|
||||
SYS_DELETE_FILE EQU 64
|
||||
SYS_RENAME_FILE EQU 65
|
||||
SYS_LOCK_FILE EQU 66
|
||||
SYS_UNLOCK_FILE EQU 67
|
||||
SYS_OPEN_DIR EQU 68
|
||||
SYS_READ_DIR EQU 69
|
||||
SYS_CREATE_DIR EQU 70
|
||||
SYS_REMOVE_DIR EQU 71
|
||||
SYS_SET_CWD EQU 72
|
||||
SYS_REAL_PATH EQU 73
|
||||
SYS_OPEN_FILE EQU 52
|
||||
SYS_READ_FILE EQU 53
|
||||
SYS_WRITE_FILE EQU 54
|
||||
SYS_READ_FILE_ASYNC EQU 55
|
||||
SYS_WRITE_FILE_ASYNC EQU 56
|
||||
SYS_CANCEL_IO EQU 57
|
||||
SYS_SEEK_FILE EQU 58
|
||||
SYS_GET_FILE_POS EQU 59
|
||||
SYS_GET_FILE_SIZE EQU 60
|
||||
SYS_GET_FILE_STAT EQU 61
|
||||
SYS_STAT_FILE EQU 62
|
||||
SYS_TRUNCATE_FILE EQU 63
|
||||
SYS_FLUSH_FILE EQU 64
|
||||
SYS_DELETE_FILE EQU 65
|
||||
SYS_RENAME_FILE EQU 66
|
||||
SYS_LOCK_FILE EQU 67
|
||||
SYS_UNLOCK_FILE EQU 68
|
||||
SYS_OPEN_DIR EQU 69
|
||||
SYS_READ_DIR EQU 70
|
||||
SYS_CREATE_DIR EQU 71
|
||||
SYS_REMOVE_DIR EQU 72
|
||||
SYS_SET_CWD EQU 73
|
||||
SYS_REAL_PATH EQU 74
|
||||
|
||||
; Console, display, and surfaces.
|
||||
SYS_WRITE_CONSOLE EQU 74
|
||||
SYS_GET_CONSOLE_INFO EQU 75
|
||||
SYS_SET_CONSOLE_CURSOR EQU 76
|
||||
SYS_FILL_CONSOLE_CHARACTER EQU 77
|
||||
SYS_FILL_CONSOLE_ATTRIBUTE EQU 78
|
||||
SYS_PUT_SCREEN_CHAR EQU 79
|
||||
SYS_CLEAR_SCREEN EQU 80
|
||||
SYS_GET_SCREEN_SIZE EQU 81
|
||||
SYS_QUERY_DISPLAY_INFO EQU 82
|
||||
SYS_OPEN_DISPLAY_CLOCK EQU 83
|
||||
SYS_CREATE_SURFACE EQU 84
|
||||
SYS_MAP_SURFACE EQU 85
|
||||
SYS_QUERY_SURFACE EQU 86
|
||||
SYS_PRESENT_SURFACE EQU 87
|
||||
SYS_WRITE_CONSOLE EQU 75
|
||||
SYS_GET_CONSOLE_INFO EQU 76
|
||||
SYS_SET_CONSOLE_CURSOR EQU 77
|
||||
SYS_FILL_CONSOLE_CHARACTER EQU 78
|
||||
SYS_FILL_CONSOLE_ATTRIBUTE EQU 79
|
||||
SYS_PUT_SCREEN_CHAR EQU 80
|
||||
SYS_CLEAR_SCREEN EQU 81
|
||||
SYS_GET_SCREEN_SIZE EQU 82
|
||||
SYS_QUERY_DISPLAY_INFO EQU 83
|
||||
SYS_OPEN_DISPLAY_CLOCK EQU 84
|
||||
SYS_CREATE_SURFACE EQU 85
|
||||
SYS_MAP_SURFACE EQU 86
|
||||
SYS_QUERY_SURFACE EQU 87
|
||||
SYS_PRESENT_SURFACE EQU 88
|
||||
|
||||
; Native render audio.
|
||||
SYS_AUDIO_ENUMERATE_ENDPOINTS EQU 89
|
||||
SYS_AUDIO_OPEN_STREAM EQU 90
|
||||
SYS_AUDIO_WRITE EQU 91
|
||||
SYS_AUDIO_ARM_WAIT EQU 92
|
||||
SYS_AUDIO_QUERY_STREAM EQU 93
|
||||
SYS_AUDIO_START EQU 94
|
||||
SYS_AUDIO_PAUSE EQU 95
|
||||
SYS_AUDIO_RESUME EQU 96
|
||||
SYS_AUDIO_DRAIN EQU 97
|
||||
SYS_AUDIO_STOP EQU 98
|
||||
SYS_AUDIO_RESET EQU 99
|
||||
SYS_AUDIO_SET_VOLUME EQU 100
|
||||
SYS_AUDIO_SET_MASTER_VOLUME EQU 101
|
||||
|
||||
; Input.
|
||||
SYS_POLL_KEYBOARD EQU 88
|
||||
SYS_IS_KEY_DOWN EQU 89
|
||||
SYS_READ_KEYBOARD_CHAR EQU 90
|
||||
SYS_READ_KEY EQU 91
|
||||
SYS_GET_KEY_MODIFIERS EQU 92
|
||||
SYS_QUERY_GLOBAL_INPUT_STATE EQU 93
|
||||
SYS_OPEN_INPUT_QUEUE EQU 94
|
||||
SYS_READ_INPUT_EVENT EQU 95
|
||||
SYS_PEEK_INPUT_EVENT EQU 96
|
||||
SYS_PEEK_MOUSE EQU 97
|
||||
SYS_READ_MOUSE EQU 98
|
||||
SYS_GET_MOUSE_POS EQU 99
|
||||
SYS_POLL_KEYBOARD EQU 102
|
||||
SYS_IS_KEY_DOWN EQU 103
|
||||
SYS_READ_KEYBOARD_CHAR EQU 104
|
||||
SYS_READ_KEY EQU 105
|
||||
SYS_GET_KEY_MODIFIERS EQU 106
|
||||
SYS_QUERY_GLOBAL_INPUT_STATE EQU 107
|
||||
SYS_OPEN_INPUT_QUEUE EQU 108
|
||||
SYS_READ_INPUT_EVENT EQU 109
|
||||
SYS_PEEK_INPUT_EVENT EQU 110
|
||||
SYS_PEEK_MOUSE EQU 111
|
||||
SYS_READ_MOUSE EQU 112
|
||||
SYS_GET_MOUSE_POS EQU 113
|
||||
|
||||
; Time, debug, and system information.
|
||||
SYS_CREATE_TIMER EQU 100
|
||||
SYS_SET_TIMER EQU 101
|
||||
SYS_CANCEL_TIMER EQU 102
|
||||
SYS_GET_TICK EQU 103
|
||||
SYS_GET_TICK_FREQUENCY EQU 104
|
||||
SYS_GET_SYSTEM_TIME EQU 105
|
||||
SYS_PRINT_DEBUG EQU 106
|
||||
SYS_QUERY_TRAP_FRAME EQU 107
|
||||
SYS_GET_MEMORY_STATUS EQU 108
|
||||
SYS_QUERY_KERNEL_STATS EQU 109
|
||||
SYS_CREATE_TIMER EQU 114
|
||||
SYS_SET_TIMER EQU 115
|
||||
SYS_CANCEL_TIMER EQU 116
|
||||
SYS_GET_TICK EQU 117
|
||||
SYS_GET_TICK_FREQUENCY EQU 118
|
||||
SYS_GET_SYSTEM_TIME EQU 119
|
||||
SYS_PRINT_DEBUG EQU 120
|
||||
SYS_QUERY_TRAP_FRAME EQU 121
|
||||
SYS_GET_MEMORY_STATUS EQU 122
|
||||
SYS_QUERY_KERNEL_STATS EQU 123
|
||||
|
||||
; Native sockets and polling.
|
||||
SYS_CREATE_SOCKET EQU 110
|
||||
SYS_SOCKET_IO_ASYNC EQU 111
|
||||
SYS_BIND_SOCKET EQU 112
|
||||
SYS_LISTEN_SOCKET EQU 113
|
||||
SYS_ACCEPT_SOCKET EQU 114
|
||||
SYS_CONNECT_SOCKET EQU 115
|
||||
SYS_SEND_SOCKET EQU 116
|
||||
SYS_RECV_SOCKET EQU 117
|
||||
SYS_SHUTDOWN_SOCKET EQU 118
|
||||
SYS_SEND_TO_SOCKET EQU 119
|
||||
SYS_RECV_FROM_SOCKET EQU 120
|
||||
SYS_RESOLVE_HOST EQU 121
|
||||
SYS_SET_SOCKET_OPTION EQU 122
|
||||
SYS_GET_SOCKET_OPTION EQU 123
|
||||
SYS_POLL EQU 124
|
||||
SYS_UNPUBLISH_QUEUE EQU 125
|
||||
SYS_CREATE_SOCKET EQU 124
|
||||
SYS_SOCKET_IO_ASYNC EQU 125
|
||||
SYS_BIND_SOCKET EQU 126
|
||||
SYS_LISTEN_SOCKET EQU 127
|
||||
SYS_ACCEPT_SOCKET EQU 128
|
||||
SYS_CONNECT_SOCKET EQU 129
|
||||
SYS_SEND_SOCKET EQU 130
|
||||
SYS_RECV_SOCKET EQU 131
|
||||
SYS_SHUTDOWN_SOCKET EQU 132
|
||||
SYS_SEND_TO_SOCKET EQU 133
|
||||
SYS_RECV_FROM_SOCKET EQU 134
|
||||
SYS_RESOLVE_HOST EQU 135
|
||||
SYS_SET_SOCKET_OPTION EQU 136
|
||||
SYS_GET_SOCKET_OPTION EQU 137
|
||||
SYS_POLL EQU 138
|
||||
SYS_UNPUBLISH_QUEUE EQU 139
|
||||
|
||||
SYS_MAX EQU 126
|
||||
SYS_MAX EQU 140
|
||||
@@ -15,11 +15,6 @@
|
||||
; SYSCALL_STUB2/3 - 4 or fewer args: pure register passing, no stack read.
|
||||
; SYSCALL_STUB5 - exactly 5 args: loads [rsp+28h] -> R10 only.
|
||||
; SYSCALL_STUB6 - 5 or 6 args: loads [rsp+28h] -> R10 and [rsp+30h] -> R11.
|
||||
;
|
||||
; Handle lifecycle:
|
||||
; SysCloseHandle is the single close stub for every object type.
|
||||
; There is no SysCloseFile - files are closed through the unified path,
|
||||
; exactly like mutexes, semaphores, events, and queues.
|
||||
|
||||
INCLUDE syscall_nr.inc
|
||||
|
||||
@@ -97,6 +92,7 @@ SYSCALL_STUB2 GetModuleExport, SYS_GET_MODULE_EXPORT
|
||||
SYSCALL_STUB2 GetModuleList, SYS_GET_MODULE_LIST
|
||||
|
||||
SYSCALL_STUB2 CreateThread, SYS_CREATE_THREAD
|
||||
SYSCALL_STUB2 ResumeThread, SYS_RESUME_THREAD
|
||||
SYSCALL_STUB2 GetCurrentTid, SYS_GET_CURRENT_TID
|
||||
SYSCALL_STUB2 GetCurrentPid, SYS_GET_CURRENT_PID
|
||||
SYSCALL_STUB2 SetThreadPriority, SYS_SET_THREAD_PRIORITY
|
||||
@@ -218,7 +214,24 @@ SYSCALL_STUB2 PresentSurface, SYS_PRESENT_SURFACE
|
||||
SYSCALL_STUB2 ClearScreen, SYS_CLEAR_SCREEN
|
||||
SYSCALL_STUB2 GetScreenSize, SYS_GET_SCREEN_SIZE
|
||||
SYSCALL_STUB2 QueryDisplayInfo, SYS_QUERY_DISPLAY_INFO
|
||||
SYSCALL_STUB2 OpenDisplayClock, SYS_OPEN_DISPLAY_CLOCK
|
||||
SYSCALL_STUB2 OpenDisplayClock, SYS_OPEN_DISPLAY_CLOCK
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Native render audio
|
||||
; ---------------------------------------------------------------------------
|
||||
SYSCALL_STUB2 AudioEnumerateEndpoints, SYS_AUDIO_ENUMERATE_ENDPOINTS
|
||||
SYSCALL_STUB2 AudioOpenStream, SYS_AUDIO_OPEN_STREAM
|
||||
SYSCALL_STUB2 AudioWrite, SYS_AUDIO_WRITE
|
||||
SYSCALL_STUB2 AudioArmWait, SYS_AUDIO_ARM_WAIT
|
||||
SYSCALL_STUB2 AudioQueryStream, SYS_AUDIO_QUERY_STREAM
|
||||
SYSCALL_STUB2 AudioStart, SYS_AUDIO_START
|
||||
SYSCALL_STUB2 AudioPause, SYS_AUDIO_PAUSE
|
||||
SYSCALL_STUB2 AudioResume, SYS_AUDIO_RESUME
|
||||
SYSCALL_STUB2 AudioDrain, SYS_AUDIO_DRAIN
|
||||
SYSCALL_STUB2 AudioStop, SYS_AUDIO_STOP
|
||||
SYSCALL_STUB2 AudioReset, SYS_AUDIO_RESET
|
||||
SYSCALL_STUB2 AudioSetVolume, SYS_AUDIO_SET_VOLUME
|
||||
SYSCALL_STUB2 AudioSetMasterVolume, SYS_AUDIO_SET_MASTER_VOLUME
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Input
|
||||
|
||||
@@ -57,7 +57,7 @@ static void print_hex32(uint32_t v) {
|
||||
|
||||
// Exits the process without returning to the runtime.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Converts ASCII to lowercase for stable case-insensitive comparisons.
|
||||
@@ -69,7 +69,7 @@ static char ascii_lower(char c) {
|
||||
// Compares two names case-insensitively, with a case-sensitive tie-breaker.
|
||||
static int compare_names(const char* a, const char* b) {
|
||||
size_t i = 0;
|
||||
for (;;) {
|
||||
while (true) {
|
||||
char ca = ascii_lower(a[i]);
|
||||
char cb = ascii_lower(b[i]);
|
||||
if (ca != cb) return (unsigned char)ca < (unsigned char)cb ? -1 : 1;
|
||||
@@ -219,7 +219,7 @@ static uint32_t collect_tests(const char* dir, const char* selfName, TEST_ENTRY*
|
||||
uint32_t count = 0;
|
||||
uint32_t capacity = 0;
|
||||
|
||||
for (;;) {
|
||||
while (true) {
|
||||
SYS_DIR_ENTRY entry;
|
||||
status = SysReadDir(hDir, &entry);
|
||||
if (status != NTLSTATUS_SUCCESS) break;
|
||||
|
||||
@@ -0,0 +1,729 @@
|
||||
// Native render-stream syscall smoke test.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define TEST_WAIT_MILLISECONDS 3000ull
|
||||
#define TEST_EXTRA_FRAMES 512u
|
||||
#define TEST_REFILL_FRAMES 1024u
|
||||
#define TEST_TONE_FRAMES 48000u
|
||||
#define TEST_AUDIBLE_FRAMES SYS_AUDIO_STREAM_CAPACITY_FRAMES
|
||||
#define TEST_AUDIBLE_RAMP_FRAMES 240u
|
||||
#define TEST_CLOSE_RUNDOWN_ITERATIONS 72u
|
||||
#define TEST_CLOSE_RUNDOWN_SLEEP_MILLISECONDS 5ull
|
||||
#define TEST_IDLE_QUIESCE_MILLISECONDS 300ull
|
||||
#define TEST_TONE_PHASE_MODULUS 1200u
|
||||
#define TEST_TONE_PHASE_STEP 11u
|
||||
#define TEST_TONE_QUARTER_PHASE 300u
|
||||
#define TEST_TONE_PEAK 8192
|
||||
|
||||
static int16_t gFrames[TEST_TONE_FRAMES * 2u];
|
||||
static int16_t gAudibleFrames[TEST_AUDIBLE_FRAMES * 2u];
|
||||
static uint32_t gOpenStream;
|
||||
|
||||
typedef char TEST_AUDIO_FORMAT_SIZE[(sizeof(SYS_AUDIO_FORMAT) == 8) ? 1 : -1];
|
||||
typedef char TEST_AUDIO_ENDPOINT_INFO_SIZE[(sizeof(SYS_AUDIO_ENDPOINT_INFO) == 64) ? 1 : -1];
|
||||
typedef char TEST_AUDIO_ENUMERATE_REQUEST_SIZE[(sizeof(SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST) == 32) ? 1 : -1];
|
||||
typedef char TEST_AUDIO_OPEN_REQUEST_SIZE[(sizeof(SYS_AUDIO_OPEN_STREAM_REQUEST) == 40) ? 1 : -1];
|
||||
typedef char TEST_AUDIO_WRITE_REQUEST_SIZE[(sizeof(SYS_AUDIO_WRITE_REQUEST) == 32) ? 1 : -1];
|
||||
typedef char TEST_AUDIO_ARM_WAIT_REQUEST_SIZE[(sizeof(SYS_AUDIO_ARM_WAIT_REQUEST) == 56) ? 1 : -1];
|
||||
typedef char TEST_AUDIO_STREAM_INFO_SIZE[(sizeof(SYS_AUDIO_STREAM_INFO) == 128) ? 1 : -1];
|
||||
typedef char TEST_AUDIO_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_VOLUME_REQUEST) == 24) ? 1 : -1];
|
||||
typedef char TEST_AUDIO_MASTER_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_MASTER_VOLUME_REQUEST) == 24) ? 1 : -1];
|
||||
|
||||
// Writes one NUL-terminated string to the console.
|
||||
static void print(const char* text) {
|
||||
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
|
||||
(void)SysWriteConsole(&request);
|
||||
}
|
||||
|
||||
// Writes one unsigned integer to the console.
|
||||
static void print_u64(uint64_t value) {
|
||||
char digits[32];
|
||||
uint32_t count = 0;
|
||||
if (!value) {
|
||||
print("0");
|
||||
return;
|
||||
}
|
||||
while (value && count < sizeof(digits)) {
|
||||
digits[count++] = (char)('0' + value % 10u);
|
||||
value /= 10u;
|
||||
}
|
||||
while (count) {
|
||||
char character = digits[--count];
|
||||
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromParts(&character, 1) };
|
||||
(void)SysWriteConsole(&request);
|
||||
}
|
||||
}
|
||||
|
||||
// Stops and closes the canonical stream after success, failure, or process interruption.
|
||||
static void cleanup_stream(void) {
|
||||
if (!gOpenStream)
|
||||
return;
|
||||
(void)SysAudioStop(gOpenStream);
|
||||
(void)SysCloseHandle(gOpenStream);
|
||||
gOpenStream = 0;
|
||||
}
|
||||
|
||||
// Terminates the test process.
|
||||
static void exit_now(int32_t code) {
|
||||
cleanup_stream();
|
||||
while (true) (void)SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one status value.
|
||||
static void fail_status(const char* operation, KSTATUS status) {
|
||||
print("audio_stream_smoke: ");
|
||||
print(operation);
|
||||
print(" status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Fails the test with one plain diagnostic.
|
||||
static void fail(const char* message) {
|
||||
print("audio_stream_smoke: ");
|
||||
print(message);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Returns one exact 440 Hz integer triangle-wave sample for the supplied phase.
|
||||
static int16_t tone_sample(uint32_t phase) {
|
||||
int32_t sample;
|
||||
if (phase < TEST_TONE_QUARTER_PHASE) {
|
||||
sample = (int32_t)(phase * TEST_TONE_PEAK / TEST_TONE_QUARTER_PHASE);
|
||||
} else if (phase < TEST_TONE_QUARTER_PHASE * 2u) {
|
||||
sample = (int32_t)((TEST_TONE_QUARTER_PHASE * 2u - phase) *
|
||||
TEST_TONE_PEAK / TEST_TONE_QUARTER_PHASE);
|
||||
} else if (phase < TEST_TONE_QUARTER_PHASE * 3u) {
|
||||
sample = -(int32_t)((phase - TEST_TONE_QUARTER_PHASE * 2u) *
|
||||
TEST_TONE_PEAK / TEST_TONE_QUARTER_PHASE);
|
||||
} else {
|
||||
sample = -(int32_t)((TEST_TONE_PHASE_MODULUS - phase) *
|
||||
TEST_TONE_PEAK / TEST_TONE_QUARTER_PHASE);
|
||||
}
|
||||
return (int16_t)sample;
|
||||
}
|
||||
|
||||
// Fills contract and audible buffers without requiring floating-point support.
|
||||
static void fill_test_frames(void) {
|
||||
uint32_t phase = 0;
|
||||
for (uint32_t frame = 0; frame < TEST_TONE_FRAMES; frame++) {
|
||||
int16_t sample = tone_sample(phase);
|
||||
gFrames[frame * 2u] = sample;
|
||||
gFrames[frame * 2u + 1u] = sample;
|
||||
phase += TEST_TONE_PHASE_STEP;
|
||||
if (phase >= TEST_TONE_PHASE_MODULUS)
|
||||
phase -= TEST_TONE_PHASE_MODULUS;
|
||||
}
|
||||
|
||||
phase = 0;
|
||||
for (uint32_t frame = 0; frame < TEST_AUDIBLE_FRAMES; frame++) {
|
||||
int32_t sample = tone_sample(phase);
|
||||
uint32_t gain = TEST_AUDIBLE_RAMP_FRAMES;
|
||||
if (frame < gain)
|
||||
gain = frame;
|
||||
uint32_t remaining = TEST_AUDIBLE_FRAMES - 1u - frame;
|
||||
if (remaining < gain)
|
||||
gain = remaining;
|
||||
sample = sample * (int32_t)gain / (int32_t)TEST_AUDIBLE_RAMP_FRAMES;
|
||||
gAudibleFrames[frame * 2u] = (int16_t)sample;
|
||||
gAudibleFrames[frame * 2u + 1u] = (int16_t)sample;
|
||||
phase += TEST_TONE_PHASE_STEP;
|
||||
if (phase >= TEST_TONE_PHASE_MODULUS)
|
||||
phase -= TEST_TONE_PHASE_MODULUS;
|
||||
}
|
||||
}
|
||||
|
||||
// Queries one stream with the required public structure size.
|
||||
static SYS_AUDIO_STREAM_INFO query_stream(uint32_t stream) {
|
||||
SYS_AUDIO_STREAM_INFO info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.Size = sizeof(info);
|
||||
KSTATUS status = SysAudioQueryStream(stream, &info);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("query stream", status);
|
||||
if (info.Version != SYS_AUDIO_ABI_VERSION)
|
||||
fail("unexpected audio ABI version");
|
||||
return info;
|
||||
}
|
||||
|
||||
// Applies one valid stream volume, pan, and mute setting.
|
||||
static void set_stream_mix(uint32_t stream,
|
||||
uint32_t volumeQ16,
|
||||
int32_t panQ16,
|
||||
uint32_t muted) {
|
||||
SYS_AUDIO_SET_VOLUME_REQUEST request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.StreamHandle = stream;
|
||||
request.Muted = muted;
|
||||
request.VolumeQ16 = volumeQ16;
|
||||
request.PanQ16 = panQ16;
|
||||
KSTATUS status = SysAudioSetVolume(&request);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("set stream mix", status);
|
||||
}
|
||||
|
||||
// Selects the available default endpoint, or the first available endpoint.
|
||||
static const SYS_AUDIO_ENDPOINT_INFO* select_endpoint(const SYS_AUDIO_ENDPOINT_INFO* endpoints,
|
||||
uint32_t count) {
|
||||
const SYS_AUDIO_ENDPOINT_INFO* fallback = 0;
|
||||
for (uint32_t index = 0; index < count; index++) {
|
||||
const SYS_AUDIO_ENDPOINT_INFO* endpoint = &endpoints[index];
|
||||
if (endpoint->Size != sizeof(*endpoint) ||
|
||||
endpoint->Version != SYS_AUDIO_ABI_VERSION ||
|
||||
endpoint->State != SysAudioEndpointReady ||
|
||||
(endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE) == 0)
|
||||
continue;
|
||||
if (!fallback) fallback = endpoint;
|
||||
if (endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_DEFAULT)
|
||||
return endpoint;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// Queries one endpoint by its stable identifier.
|
||||
static SYS_AUDIO_ENDPOINT_INFO query_endpoint(uint64_t endpointId) {
|
||||
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
|
||||
uint32_t count = 0;
|
||||
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.Endpoints = endpoints;
|
||||
request.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
|
||||
request.CountOut = &count;
|
||||
KSTATUS status = SysAudioEnumerateEndpoints(&request);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("query endpoint", status);
|
||||
|
||||
for (uint32_t index = 0; index < count; index++) {
|
||||
if (endpoints[index].EndpointId == endpointId)
|
||||
return endpoints[index];
|
||||
}
|
||||
fail("endpoint disappeared during control test");
|
||||
return endpoints[0];
|
||||
}
|
||||
|
||||
// Verifies endpoint master volume, mute, validation, and restoration.
|
||||
static void test_master_volume(const SYS_AUDIO_ENDPOINT_INFO* endpoint) {
|
||||
SYS_AUDIO_SET_MASTER_VOLUME_REQUEST request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.EndpointId = endpoint->EndpointId;
|
||||
request.Muted = 1u;
|
||||
request.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY / 2u;
|
||||
|
||||
KSTATUS status = SysAudioSetMasterVolume(&request);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("set master volume", status);
|
||||
SYS_AUDIO_ENDPOINT_INFO updated = query_endpoint(endpoint->EndpointId);
|
||||
if (updated.MasterVolumeQ16 != SYS_AUDIO_GAIN_Q16_UNITY / 2u ||
|
||||
updated.MasterMuted != 1u)
|
||||
fail("master volume snapshot mismatch");
|
||||
|
||||
request.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY + 1u;
|
||||
status = SysAudioSetMasterVolume(&request);
|
||||
if (status != NTLSTATUS_INVALID_PARAMETER)
|
||||
fail_status("reject master volume above unity", status);
|
||||
|
||||
request.Muted = endpoint->MasterMuted;
|
||||
request.VolumeQ16 = endpoint->MasterVolumeQ16;
|
||||
status = SysAudioSetMasterVolume(&request);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("restore master volume", status);
|
||||
}
|
||||
|
||||
// Opens one stream format and checks the expected status.
|
||||
static uint32_t open_stream_format(uint64_t endpointId,
|
||||
uint32_t sampleRate,
|
||||
uint16_t channelCount,
|
||||
uint16_t sampleFormat,
|
||||
KSTATUS expectedStatus) {
|
||||
uint32_t stream = 0;
|
||||
SYS_AUDIO_OPEN_STREAM_REQUEST request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.EndpointId = endpointId;
|
||||
request.Format.SampleRate = sampleRate;
|
||||
request.Format.ChannelCount = channelCount;
|
||||
request.Format.SampleFormat = sampleFormat;
|
||||
request.StreamHandleOut = &stream;
|
||||
KSTATUS status = SysAudioOpenStream(&request);
|
||||
if (status != expectedStatus)
|
||||
fail_status("open stream format", status);
|
||||
if (status == NTLSTATUS_SUCCESS && !stream)
|
||||
fail("open returned a null handle");
|
||||
if (status != NTLSTATUS_SUCCESS && stream)
|
||||
fail("rejected format returned a handle");
|
||||
return stream;
|
||||
}
|
||||
|
||||
// Proves the complete accepted and rejected source-format surface.
|
||||
static void test_format_contract(uint64_t endpointId) {
|
||||
static const uint32_t rates[] = { 11025u, 22050u, 44100u, 48000u };
|
||||
for (uint32_t rateIndex = 0; rateIndex < sizeof(rates) / sizeof(rates[0]); rateIndex++) {
|
||||
for (uint16_t channels = 1; channels <= 2; channels++) {
|
||||
uint32_t stream = open_stream_format(endpointId,
|
||||
rates[rateIndex],
|
||||
channels,
|
||||
SysAudioSampleS16,
|
||||
NTLSTATUS_SUCCESS);
|
||||
KSTATUS status = SysCloseHandle(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("close format stream", status);
|
||||
}
|
||||
}
|
||||
|
||||
(void)open_stream_format(endpointId, 48000u, 2, 2, NTLSTATUS_NOT_SUPPORTED);
|
||||
(void)open_stream_format(endpointId, 32000u, 2, SysAudioSampleS16, NTLSTATUS_NOT_SUPPORTED);
|
||||
(void)open_stream_format(endpointId, 48000u, 3, SysAudioSampleS16, NTLSTATUS_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
// Opens the canonical test stream.
|
||||
static uint32_t open_stream(uint64_t endpointId) {
|
||||
return open_stream_format(endpointId,
|
||||
48000u,
|
||||
2,
|
||||
SysAudioSampleS16,
|
||||
NTLSTATUS_SUCCESS);
|
||||
}
|
||||
|
||||
// Writes one sample run and returns the accepted frame count.
|
||||
static uint32_t write_samples(uint32_t stream,
|
||||
const int16_t* frames,
|
||||
uint32_t frameCount,
|
||||
KSTATUS expectedStatus) {
|
||||
uint32_t written = 0xFFFFFFFFu;
|
||||
SYS_AUDIO_WRITE_REQUEST request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.StreamHandle = stream;
|
||||
request.FrameCount = frameCount;
|
||||
request.Frames = frameCount ? frames : 0;
|
||||
request.WrittenFramesOut = &written;
|
||||
KSTATUS status = SysAudioWrite(&request);
|
||||
if (status != expectedStatus) fail_status("write stream", status);
|
||||
if (status != NTLSTATUS_SUCCESS && written != 0)
|
||||
fail("failed write reported accepted frames");
|
||||
return written;
|
||||
}
|
||||
|
||||
// Writes one contract-tone range and returns the accepted frame count.
|
||||
static uint32_t write_frames(uint32_t stream,
|
||||
uint32_t frameOffset,
|
||||
uint32_t frameCount,
|
||||
KSTATUS expectedStatus) {
|
||||
if (frameOffset > TEST_TONE_FRAMES ||
|
||||
frameCount > TEST_TONE_FRAMES - frameOffset)
|
||||
fail("test write range exceeded the tone buffer");
|
||||
return write_samples(stream,
|
||||
frameCount ? gFrames + (size_t)frameOffset * 2u : 0,
|
||||
frameCount,
|
||||
expectedStatus);
|
||||
}
|
||||
|
||||
// Arms one exact set of stream progress targets.
|
||||
static void arm_wait(uint32_t stream,
|
||||
uint32_t mask,
|
||||
uint32_t writableFrames,
|
||||
uint64_t consumedFrames,
|
||||
uint64_t presentedFrames,
|
||||
uint64_t drainedGeneration,
|
||||
uint64_t discontinuityGeneration) {
|
||||
SYS_AUDIO_ARM_WAIT_REQUEST request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.Size = sizeof(request);
|
||||
request.StreamHandle = stream;
|
||||
request.Mask = mask;
|
||||
request.WritableFrames = writableFrames;
|
||||
request.ConsumedFrames = consumedFrames;
|
||||
request.PresentedFrames = presentedFrames;
|
||||
request.DrainedGeneration = drainedGeneration;
|
||||
request.DiscontinuityGeneration = discontinuityGeneration;
|
||||
KSTATUS status = SysAudioArmWait(&request);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("arm wait", status);
|
||||
}
|
||||
|
||||
// Waits once and converts timeout or alert completion into a focused failure.
|
||||
static void wait_stream(uint32_t stream, const char* operation) {
|
||||
KSTATUS status = SysWaitSingle(stream, SYS_WAIT_FLAG_NONE, TEST_WAIT_MILLISECONDS);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status(operation, status);
|
||||
}
|
||||
|
||||
// Proves that closing the final handle retires an active stream with queued periods.
|
||||
static void test_active_close_rundown(uint64_t endpointId) {
|
||||
for (uint32_t iteration = 0;
|
||||
iteration < TEST_CLOSE_RUNDOWN_ITERATIONS;
|
||||
iteration++) {
|
||||
uint32_t stream = open_stream(endpointId);
|
||||
gOpenStream = stream;
|
||||
set_stream_mix(stream,
|
||||
SYS_AUDIO_GAIN_Q16_UNITY,
|
||||
SYS_AUDIO_PAN_Q16_CENTER,
|
||||
1u);
|
||||
uint32_t written = write_frames(stream,
|
||||
0,
|
||||
SYS_AUDIO_STREAM_CAPACITY_FRAMES,
|
||||
NTLSTATUS_SUCCESS);
|
||||
if (written != SYS_AUDIO_STREAM_CAPACITY_FRAMES)
|
||||
fail("active-close prefill was partial");
|
||||
|
||||
SYS_AUDIO_STREAM_INFO before = query_stream(stream);
|
||||
arm_wait(stream,
|
||||
SYS_AUDIO_WAIT_CONSUMED |
|
||||
SYS_AUDIO_WAIT_FAILED |
|
||||
SYS_AUDIO_WAIT_DISCONTINUITY,
|
||||
0,
|
||||
before.FramesConsumed + TEST_EXTRA_FRAMES,
|
||||
before.FramesPresented,
|
||||
before.DrainedGeneration,
|
||||
before.DiscontinuityGeneration);
|
||||
KSTATUS status = SysAudioStart(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("active-close start", status);
|
||||
wait_stream(stream, "active-close consumed wait");
|
||||
|
||||
SYS_AUDIO_STREAM_INFO active = query_stream(stream);
|
||||
if (active.State == SysAudioStreamFailed)
|
||||
fail_status("active-close stream failure", active.FailureStatus);
|
||||
if (active.DiscontinuityGeneration != before.DiscontinuityGeneration ||
|
||||
active.FramesConsumed < before.FramesConsumed + TEST_EXTRA_FRAMES)
|
||||
fail("active-close progress mismatch");
|
||||
|
||||
status = SysCloseHandle(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("active-close final handle", status);
|
||||
gOpenStream = 0;
|
||||
status = SysSleep(TEST_CLOSE_RUNDOWN_SLEEP_MILLISECONDS);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("active-close rundown sleep", status);
|
||||
}
|
||||
}
|
||||
|
||||
// Plays one bounded audible tone without depending on real-time user refills.
|
||||
static void play_continuous_tone(uint32_t stream) {
|
||||
KSTATUS status = SysAudioReset(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("audible reset", status);
|
||||
set_stream_mix(stream,
|
||||
SYS_AUDIO_GAIN_Q16_UNITY,
|
||||
SYS_AUDIO_PAN_Q16_CENTER,
|
||||
0u);
|
||||
|
||||
SYS_AUDIO_STREAM_INFO baseline = query_stream(stream);
|
||||
uint32_t submitted = write_samples(stream,
|
||||
gAudibleFrames,
|
||||
TEST_AUDIBLE_FRAMES,
|
||||
NTLSTATUS_SUCCESS);
|
||||
if (submitted != TEST_AUDIBLE_FRAMES)
|
||||
fail("audible prefill was partial");
|
||||
|
||||
status = SysAudioStart(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("audible start", status);
|
||||
status = SysAudioDrain(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("audible drain", status);
|
||||
print("audio_stream_smoke: audible 440 Hz tone begin\n");
|
||||
|
||||
arm_wait(stream,
|
||||
SYS_AUDIO_WAIT_DRAINED |
|
||||
SYS_AUDIO_WAIT_FAILED |
|
||||
SYS_AUDIO_WAIT_DISCONTINUITY,
|
||||
0,
|
||||
baseline.FramesConsumed,
|
||||
baseline.FramesPresented,
|
||||
baseline.DrainedGeneration,
|
||||
baseline.DiscontinuityGeneration);
|
||||
wait_stream(stream, "audible drained wait");
|
||||
|
||||
SYS_AUDIO_STREAM_INFO drained = query_stream(stream);
|
||||
if (drained.State == SysAudioStreamFailed)
|
||||
fail_status("audible stream failure", drained.FailureStatus);
|
||||
if (drained.State != SysAudioStreamStopped ||
|
||||
drained.DrainedGeneration != baseline.DrainedGeneration + 1u ||
|
||||
drained.DiscontinuityGeneration != baseline.DiscontinuityGeneration ||
|
||||
drained.FramesSubmitted != TEST_AUDIBLE_FRAMES ||
|
||||
drained.FramesConsumed != TEST_AUDIBLE_FRAMES ||
|
||||
drained.FramesPresented != TEST_AUDIBLE_FRAMES ||
|
||||
drained.BufferedFrames != 0 ||
|
||||
drained.Underruns != 0 ||
|
||||
drained.SilenceFramesInserted != 0)
|
||||
fail("audible tone completion mismatch");
|
||||
print("audio_stream_smoke: audible 440 Hz tone complete\n");
|
||||
}
|
||||
|
||||
// Runs the complete native stream contract against one endpoint.
|
||||
static void test_stream(const SYS_AUDIO_ENDPOINT_INFO* endpoint) {
|
||||
test_format_contract(endpoint->EndpointId);
|
||||
test_active_close_rundown(endpoint->EndpointId);
|
||||
|
||||
uint32_t stream = open_stream(endpoint->EndpointId);
|
||||
gOpenStream = stream;
|
||||
KSTATUS status;
|
||||
SYS_AUDIO_STREAM_INFO info = query_stream(stream);
|
||||
if (info.State != SysAudioStreamCreated ||
|
||||
info.CapacityFrames != SYS_AUDIO_STREAM_CAPACITY_FRAMES ||
|
||||
info.Format.SampleFormat != SysAudioSampleS16)
|
||||
fail("initial stream snapshot mismatch");
|
||||
|
||||
SYS_AUDIO_SET_VOLUME_REQUEST volume;
|
||||
memset(&volume, 0, sizeof(volume));
|
||||
volume.Size = sizeof(volume);
|
||||
volume.StreamHandle = stream;
|
||||
volume.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY / 2u;
|
||||
volume.PanQ16 = SYS_AUDIO_PAN_Q16_CENTER;
|
||||
status = SysAudioSetVolume(&volume);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("set volume", status);
|
||||
info = query_stream(stream);
|
||||
if (info.VolumeQ16 != SYS_AUDIO_GAIN_Q16_UNITY / 2u ||
|
||||
info.PanQ16 != SYS_AUDIO_PAN_Q16_CENTER || info.Muted)
|
||||
fail("volume snapshot mismatch");
|
||||
|
||||
volume.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY + 1u;
|
||||
status = SysAudioSetVolume(&volume);
|
||||
if (status != NTLSTATUS_INVALID_PARAMETER)
|
||||
fail_status("reject volume above unity", status);
|
||||
volume.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY / 2u;
|
||||
volume.PanQ16 = SYS_AUDIO_PAN_Q16_RIGHT + 1;
|
||||
status = SysAudioSetVolume(&volume);
|
||||
if (status != NTLSTATUS_INVALID_PARAMETER)
|
||||
fail_status("reject pan outside range", status);
|
||||
|
||||
volume.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY;
|
||||
volume.PanQ16 = SYS_AUDIO_PAN_Q16_CENTER;
|
||||
volume.Muted = 1u;
|
||||
status = SysAudioSetVolume(&volume);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("mute control stream", status);
|
||||
|
||||
uint32_t written = write_frames(stream, 0, 0, NTLSTATUS_SUCCESS);
|
||||
if (written != 0) fail("zero-length write accepted frames");
|
||||
written = write_frames(stream,
|
||||
0,
|
||||
SYS_AUDIO_STREAM_CAPACITY_FRAMES + TEST_EXTRA_FRAMES,
|
||||
NTLSTATUS_SUCCESS);
|
||||
if (written != SYS_AUDIO_STREAM_CAPACITY_FRAMES)
|
||||
fail("partial write did not fill exactly one stream ring");
|
||||
written = write_frames(stream, 0, 1, NTLSTATUS_WOULD_BLOCK);
|
||||
if (written != 0) fail("full-ring write accepted a frame");
|
||||
|
||||
info = query_stream(stream);
|
||||
arm_wait(stream,
|
||||
SYS_AUDIO_WAIT_WRITABLE |
|
||||
SYS_AUDIO_WAIT_CONSUMED |
|
||||
SYS_AUDIO_WAIT_FAILED |
|
||||
SYS_AUDIO_WAIT_DISCONTINUITY,
|
||||
TEST_EXTRA_FRAMES,
|
||||
info.FramesConsumed + TEST_EXTRA_FRAMES,
|
||||
0,
|
||||
info.DrainedGeneration,
|
||||
info.DiscontinuityGeneration);
|
||||
status = SysAudioStart(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("start", status);
|
||||
wait_stream(stream, "writable wait");
|
||||
|
||||
SYS_AUDIO_STREAM_INFO progressed = query_stream(stream);
|
||||
if (progressed.State == SysAudioStreamFailed)
|
||||
fail_status("stream failure", progressed.FailureStatus);
|
||||
if (progressed.DiscontinuityGeneration != info.DiscontinuityGeneration)
|
||||
fail("unexpected discontinuity before writable target");
|
||||
if (progressed.WritableFrames < TEST_EXTRA_FRAMES ||
|
||||
progressed.FramesConsumed < info.FramesConsumed + TEST_EXTRA_FRAMES)
|
||||
fail("writable or consumed wait did not observe its target");
|
||||
|
||||
status = SysAudioPause(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("pause", status);
|
||||
if (query_stream(stream).State != SysAudioStreamPaused)
|
||||
fail("pause state mismatch");
|
||||
status = SysAudioResume(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("resume", status);
|
||||
|
||||
uint32_t submitted = SYS_AUDIO_STREAM_CAPACITY_FRAMES;
|
||||
written = write_frames(stream, submitted, TEST_EXTRA_FRAMES, NTLSTATUS_SUCCESS);
|
||||
if (written != TEST_EXTRA_FRAMES)
|
||||
fail("resume refill was partial");
|
||||
submitted += written;
|
||||
|
||||
while (submitted < TEST_TONE_FRAMES) {
|
||||
SYS_AUDIO_STREAM_INFO refill = query_stream(stream);
|
||||
if (refill.State == SysAudioStreamFailed)
|
||||
fail_status("stream failure", refill.FailureStatus);
|
||||
if (refill.DiscontinuityGeneration != info.DiscontinuityGeneration)
|
||||
fail("unexpected discontinuity during muted control refill");
|
||||
|
||||
uint32_t remaining = TEST_TONE_FRAMES - submitted;
|
||||
uint32_t refillFrames = remaining < TEST_REFILL_FRAMES
|
||||
? remaining
|
||||
: TEST_REFILL_FRAMES;
|
||||
if (refill.WritableFrames < refillFrames) {
|
||||
arm_wait(stream,
|
||||
SYS_AUDIO_WAIT_WRITABLE |
|
||||
SYS_AUDIO_WAIT_FAILED |
|
||||
SYS_AUDIO_WAIT_DISCONTINUITY,
|
||||
refillFrames,
|
||||
refill.FramesConsumed,
|
||||
refill.FramesPresented,
|
||||
refill.DrainedGeneration,
|
||||
info.DiscontinuityGeneration);
|
||||
wait_stream(stream, "muted control refill wait");
|
||||
continue;
|
||||
}
|
||||
|
||||
written = write_frames(stream, submitted, refillFrames, NTLSTATUS_SUCCESS);
|
||||
if (written != refillFrames)
|
||||
fail("muted control refill was partial despite reserved capacity");
|
||||
submitted += written;
|
||||
}
|
||||
|
||||
SYS_AUDIO_STREAM_INFO beforeDrain = query_stream(stream);
|
||||
if (beforeDrain.FramesSubmitted != TEST_TONE_FRAMES)
|
||||
fail("muted control frame submission mismatch");
|
||||
uint64_t presentedTarget = beforeDrain.FramesSubmitted;
|
||||
status = SysAudioDrain(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("drain", status);
|
||||
arm_wait(stream,
|
||||
SYS_AUDIO_WAIT_PRESENTED |
|
||||
SYS_AUDIO_WAIT_FAILED |
|
||||
SYS_AUDIO_WAIT_DISCONTINUITY,
|
||||
0,
|
||||
beforeDrain.FramesConsumed,
|
||||
presentedTarget,
|
||||
beforeDrain.DrainedGeneration,
|
||||
beforeDrain.DiscontinuityGeneration);
|
||||
wait_stream(stream, "presentation wait");
|
||||
|
||||
SYS_AUDIO_STREAM_INFO presented = query_stream(stream);
|
||||
if (presented.State == SysAudioStreamFailed)
|
||||
fail_status("stream failure", presented.FailureStatus);
|
||||
if (presented.DiscontinuityGeneration != beforeDrain.DiscontinuityGeneration)
|
||||
fail("unexpected discontinuity before presentation target");
|
||||
if (presented.FramesPresented < presentedTarget)
|
||||
fail("presented wait did not observe its target");
|
||||
|
||||
if (presented.State != SysAudioStreamStopped) {
|
||||
arm_wait(stream,
|
||||
SYS_AUDIO_WAIT_DRAINED |
|
||||
SYS_AUDIO_WAIT_FAILED |
|
||||
SYS_AUDIO_WAIT_DISCONTINUITY,
|
||||
0,
|
||||
presented.FramesConsumed,
|
||||
presented.FramesPresented,
|
||||
beforeDrain.DrainedGeneration,
|
||||
beforeDrain.DiscontinuityGeneration);
|
||||
wait_stream(stream, "drained wait");
|
||||
}
|
||||
|
||||
SYS_AUDIO_STREAM_INFO drained = query_stream(stream);
|
||||
if (drained.State == SysAudioStreamFailed)
|
||||
fail_status("stream failure", drained.FailureStatus);
|
||||
if (drained.DiscontinuityGeneration != beforeDrain.DiscontinuityGeneration)
|
||||
fail("unexpected discontinuity during drain");
|
||||
if (drained.State != SysAudioStreamStopped ||
|
||||
drained.DrainedGeneration != beforeDrain.DrainedGeneration + 1u ||
|
||||
drained.BufferedFrames != 0 ||
|
||||
drained.FramesPresented < presentedTarget ||
|
||||
drained.FramesPresented + drained.FramesDiscarded < drained.FramesSubmitted)
|
||||
fail("drained stream snapshot mismatch");
|
||||
|
||||
status = SysAudioReset(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("reset", status);
|
||||
SYS_AUDIO_STREAM_INFO reset = query_stream(stream);
|
||||
if (reset.State != SysAudioStreamStopped || reset.FramesSubmitted != 0 ||
|
||||
reset.FramesConsumed != 0 || reset.FramesPresented != 0 ||
|
||||
reset.DrainedGeneration != drained.DrainedGeneration)
|
||||
fail("reset stream snapshot mismatch");
|
||||
|
||||
written = write_frames(stream, 0, 128, NTLSTATUS_SUCCESS);
|
||||
if (written != 128) fail("stop test write was partial");
|
||||
status = SysAudioStart(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("restart", status);
|
||||
SYS_AUDIO_STREAM_INFO beforeStop = query_stream(stream);
|
||||
arm_wait(stream,
|
||||
SYS_AUDIO_WAIT_STOPPED |
|
||||
SYS_AUDIO_WAIT_FAILED |
|
||||
SYS_AUDIO_WAIT_DISCONTINUITY,
|
||||
0,
|
||||
beforeStop.FramesConsumed,
|
||||
beforeStop.FramesPresented,
|
||||
beforeStop.DrainedGeneration,
|
||||
beforeStop.DiscontinuityGeneration);
|
||||
status = SysAudioStop(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("stop", status);
|
||||
wait_stream(stream, "stopped wait");
|
||||
if (query_stream(stream).State != SysAudioStreamStopped)
|
||||
fail("stop state mismatch");
|
||||
|
||||
play_continuous_tone(stream);
|
||||
|
||||
status = SysCloseHandle(stream);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("close stream", status);
|
||||
gOpenStream = 0;
|
||||
status = SysSleep(TEST_IDLE_QUIESCE_MILLISECONDS);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("endpoint quiesce sleep", status);
|
||||
}
|
||||
|
||||
// Executes the native audio syscall smoke test.
|
||||
int main(void) {
|
||||
fill_test_frames();
|
||||
print("audio_stream_smoke: begin native render contract test\n");
|
||||
|
||||
uint32_t count = 0;
|
||||
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST countRequest;
|
||||
memset(&countRequest, 0, sizeof(countRequest));
|
||||
countRequest.Size = sizeof(countRequest);
|
||||
countRequest.CountOut = &count;
|
||||
KSTATUS status = SysAudioEnumerateEndpoints(&countRequest);
|
||||
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_BUFFER_TOO_SMALL)
|
||||
fail_status("count endpoints", status);
|
||||
if (!count) {
|
||||
print("audio_stream_smoke: SKIP no render endpoint\n");
|
||||
exit_now(0);
|
||||
}
|
||||
if (count > SYS_AUDIO_MAX_ENDPOINTS)
|
||||
fail("endpoint count exceeded ABI maximum");
|
||||
|
||||
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
|
||||
memset(endpoints, 0, sizeof(endpoints));
|
||||
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST enumerate;
|
||||
memset(&enumerate, 0, sizeof(enumerate));
|
||||
enumerate.Size = sizeof(enumerate);
|
||||
enumerate.Endpoints = endpoints;
|
||||
enumerate.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
|
||||
enumerate.CountOut = &count;
|
||||
status = SysAudioEnumerateEndpoints(&enumerate);
|
||||
if (status != NTLSTATUS_SUCCESS)
|
||||
fail_status("enumerate endpoints", status);
|
||||
|
||||
const SYS_AUDIO_ENDPOINT_INFO* endpoint = select_endpoint(endpoints, count);
|
||||
if (!endpoint) {
|
||||
print("audio_stream_smoke: SKIP no available render endpoint\n");
|
||||
exit_now(0);
|
||||
}
|
||||
|
||||
print("audio_stream_smoke: endpoint=");
|
||||
print_u64(endpoint->EndpointId);
|
||||
print("\n");
|
||||
test_master_volume(endpoint);
|
||||
test_stream(endpoint);
|
||||
print("audio_stream_smoke: PASS endpoint=");
|
||||
print_u64(endpoint->EndpointId);
|
||||
print("\n");
|
||||
exit_now(0);
|
||||
}
|
||||
@@ -33,7 +33,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one status value.
|
||||
|
||||
@@ -38,7 +38,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
@@ -62,9 +62,10 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
|
||||
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
|
||||
}
|
||||
|
||||
static void delete_path(const char* path) {
|
||||
// Deletes one test path.
|
||||
static uint32_t delete_path(const char* path) {
|
||||
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
|
||||
(void)SysDeleteFile(&req);
|
||||
return SysDeleteFile(&req);
|
||||
}
|
||||
|
||||
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
|
||||
@@ -80,7 +81,7 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
|
||||
|
||||
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
delete_path(paths[i]);
|
||||
(void)delete_path(paths[i]);
|
||||
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
|
||||
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
|
||||
SYS_FILE_CREATE_NEW,
|
||||
@@ -222,7 +223,16 @@ int main(void) {
|
||||
s = SysCloseHandle(file);
|
||||
file = 0;
|
||||
if (s != NTLSTATUS_SUCCESS) fail_status("close reopened", s);
|
||||
delete_path(path);
|
||||
s = delete_path(path);
|
||||
if (s != NTLSTATUS_SUCCESS) fail_status("delete temp", s);
|
||||
|
||||
uint32_t deletedHandle = 0;
|
||||
s = open_path(path, &deletedHandle);
|
||||
if (s == NTLSTATUS_SUCCESS) {
|
||||
(void)SysCloseHandle(deletedHandle);
|
||||
fail("deleted file reopened");
|
||||
}
|
||||
if (s != NTLSTATUS_NOT_FOUND) fail_status("verify delete", s);
|
||||
|
||||
print("dirty_writeback: dirty-marks+=");
|
||||
print_u64(afterClose.DirtyMarks - before.DirtyMarks);
|
||||
|
||||
@@ -33,7 +33,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one message.
|
||||
|
||||
@@ -40,7 +40,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one message.
|
||||
|
||||
@@ -39,7 +39,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one status value.
|
||||
|
||||
@@ -36,7 +36,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one plain message.
|
||||
|
||||
@@ -42,7 +42,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one plain message.
|
||||
|
||||
@@ -32,7 +32,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one status value.
|
||||
|
||||
@@ -31,7 +31,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one status value.
|
||||
|
||||
@@ -35,7 +35,7 @@ static void print_u64(uint64_t v) {
|
||||
static void exit_now(int code) {
|
||||
// SysExitProcess should not return, but keep tests from falling through to
|
||||
// the synthetic entry frame if a broken kernel build returns anyway.
|
||||
for (;;)
|
||||
while (true)
|
||||
SysExitProcess(code);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one message.
|
||||
|
||||
@@ -45,7 +45,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one plain message.
|
||||
|
||||
@@ -35,7 +35,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one message.
|
||||
|
||||
@@ -36,7 +36,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one message.
|
||||
|
||||
@@ -36,7 +36,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one message.
|
||||
|
||||
@@ -33,7 +33,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
|
||||
@@ -30,7 +30,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
|
||||
@@ -41,7 +41,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
|
||||
@@ -33,7 +33,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
@@ -147,11 +147,6 @@ int main(void) {
|
||||
uint64_t loads = after.Loads - before.Loads;
|
||||
uint64_t hits = after.Hits - before.Hits;
|
||||
uint64_t drops = after.Drops - before.Drops;
|
||||
uint64_t raReq = after.ReadAheadRequests - before.ReadAheadRequests;
|
||||
uint64_t raLoad = after.ReadAheadPagesLoaded - before.ReadAheadPagesLoaded;
|
||||
uint64_t raThrottle = after.ReadAheadPagesThrottled - before.ReadAheadPagesThrottled;
|
||||
uint64_t batchReads = after.BatchReads - before.BatchReads;
|
||||
uint64_t batchPages = after.BatchPagesLoaded - before.BatchPagesLoaded;
|
||||
|
||||
print("pcache_pressure: read bytes="); print_u64(total);
|
||||
print(" loads+="); print_u64(loads);
|
||||
@@ -161,11 +156,6 @@ int main(void) {
|
||||
print(" active="); print_u64(after.ActivePages);
|
||||
print(" standby="); print_u64(after.StandbyPages);
|
||||
print(" max="); print_u64(after.MaxResidentPages);
|
||||
print(" readahead-req+="); print_u64(raReq);
|
||||
print(" readahead-load+="); print_u64(raLoad);
|
||||
print(" readahead-throttle+="); print_u64(raThrottle);
|
||||
print(" batch-reads+="); print_u64(batchReads);
|
||||
print(" batch-pages+="); print_u64(batchPages);
|
||||
print("\n");
|
||||
|
||||
if (size >= MIN_PRESSURE_BYTES) {
|
||||
|
||||
@@ -32,7 +32,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define RTLD_NOW 0x0002
|
||||
#define RTLD_LOCAL 0x0004
|
||||
@@ -23,7 +24,7 @@ static void print(const char* s) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) exit(code);
|
||||
while (true) exit(code);
|
||||
}
|
||||
|
||||
// Fails the smoke test with one message.
|
||||
|
||||
@@ -37,7 +37,7 @@ static void print_u32(uint32_t value) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) (void)SysExitProcess(code);
|
||||
while (true) (void)SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one status value.
|
||||
|
||||
@@ -33,7 +33,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one status value.
|
||||
|
||||
@@ -33,7 +33,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
|
||||
@@ -33,7 +33,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one message.
|
||||
|
||||
@@ -33,7 +33,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one plain message.
|
||||
|
||||
@@ -30,7 +30,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
|
||||
@@ -43,7 +43,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
|
||||
@@ -30,7 +30,7 @@ static void print_u64(uint64_t v) {
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
|
||||
@@ -34,7 +34,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Checks whether a string view equals a NUL-terminated string.
|
||||
|
||||
@@ -31,7 +31,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one status value.
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// Win32 condition-variable smoke test for Bink worker synchronization.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
typedef void* HANDLE;
|
||||
typedef unsigned long DWORD;
|
||||
typedef int BOOL;
|
||||
typedef struct { uintptr_t _opaque[5]; } CRITICAL_SECTION;
|
||||
typedef struct { void* _ptr; } CONDITION_VARIABLE;
|
||||
typedef DWORD (__stdcall *THREAD_START_ROUTINE)(void* parameter);
|
||||
|
||||
#define FALSE 0
|
||||
#define TRUE 1
|
||||
#define INFINITE 0xFFFFFFFFu
|
||||
#define WAIT_OBJECT_0 0u
|
||||
#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1)
|
||||
#define ERROR_TIMEOUT 1460u
|
||||
|
||||
__declspec(dllimport) void InitializeCriticalSection(CRITICAL_SECTION* criticalSection);
|
||||
__declspec(dllimport) void DeleteCriticalSection(CRITICAL_SECTION* criticalSection);
|
||||
__declspec(dllimport) void EnterCriticalSection(CRITICAL_SECTION* criticalSection);
|
||||
__declspec(dllimport) void LeaveCriticalSection(CRITICAL_SECTION* criticalSection);
|
||||
__declspec(dllimport) void InitializeConditionVariable(CONDITION_VARIABLE* conditionVariable);
|
||||
__declspec(dllimport) BOOL SleepConditionVariableCS(CONDITION_VARIABLE* conditionVariable,
|
||||
CRITICAL_SECTION* criticalSection,
|
||||
DWORD milliseconds);
|
||||
__declspec(dllimport) void WakeConditionVariable(CONDITION_VARIABLE* conditionVariable);
|
||||
__declspec(dllimport) void WakeAllConditionVariable(CONDITION_VARIABLE* conditionVariable);
|
||||
__declspec(dllimport) HANDLE CreateThread(void* attributes,
|
||||
size_t stackSize,
|
||||
THREAD_START_ROUTINE startRoutine,
|
||||
void* parameter,
|
||||
DWORD creationFlags,
|
||||
DWORD* threadId);
|
||||
__declspec(dllimport) DWORD WaitForSingleObject(HANDLE handle, DWORD milliseconds);
|
||||
__declspec(dllimport) BOOL CloseHandle(HANDLE handle);
|
||||
__declspec(dllimport) DWORD GetLastError(void);
|
||||
|
||||
static CRITICAL_SECTION gLock;
|
||||
static CONDITION_VARIABLE gCondition;
|
||||
static uint32_t gReady;
|
||||
static uint32_t gRelease;
|
||||
static uint32_t gWoke;
|
||||
static uint32_t gWorkerFailure;
|
||||
|
||||
// Writes one test message to the native console.
|
||||
static void print(const char* text) {
|
||||
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
|
||||
(void)SysWriteConsole(&request);
|
||||
}
|
||||
|
||||
// Terminates the smoke test after one failure.
|
||||
static void fail(const char* message) {
|
||||
print("win32_condition_variable_smoke: ");
|
||||
print(message);
|
||||
print("\n");
|
||||
while (true) (void)SysExitProcess(1);
|
||||
}
|
||||
|
||||
// Waits until the main thread releases all condition-variable workers.
|
||||
static DWORD __stdcall worker_main(void* parameter) {
|
||||
(void)parameter;
|
||||
EnterCriticalSection(&gLock);
|
||||
gReady++;
|
||||
while (!gRelease) {
|
||||
if (!SleepConditionVariableCS(&gCondition, &gLock, INFINITE)) {
|
||||
gWorkerFailure = 1;
|
||||
LeaveCriticalSection(&gLock);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
gWoke++;
|
||||
LeaveCriticalSection(&gLock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Waits for both workers to enter their condition wait.
|
||||
static void wait_for_workers(void) {
|
||||
for (uint32_t attempt = 0; attempt < 5000u; attempt++) {
|
||||
EnterCriticalSection(&gLock);
|
||||
uint32_t ready = gReady;
|
||||
LeaveCriticalSection(&gLock);
|
||||
if (ready == 2u)
|
||||
return;
|
||||
(void)SysSleep(1);
|
||||
}
|
||||
fail("workers did not reach condition wait");
|
||||
}
|
||||
|
||||
// Runs wake-all and finite-timeout condition-variable checks.
|
||||
int main(void) {
|
||||
print("win32_condition_variable_smoke: begin\n");
|
||||
InitializeCriticalSection(&gLock);
|
||||
InitializeConditionVariable(&gCondition);
|
||||
|
||||
HANDLE workers[2];
|
||||
workers[0] = CreateThread(NULL, 0, worker_main, NULL, 0, NULL);
|
||||
workers[1] = CreateThread(NULL, 0, worker_main, NULL, 0, NULL);
|
||||
if (!workers[0] || workers[0] == INVALID_HANDLE_VALUE ||
|
||||
!workers[1] || workers[1] == INVALID_HANDLE_VALUE)
|
||||
fail("CreateThread failed");
|
||||
|
||||
wait_for_workers();
|
||||
print("win32_condition_variable_smoke: workers waiting\n");
|
||||
EnterCriticalSection(&gLock);
|
||||
gRelease = 1;
|
||||
WakeAllConditionVariable(&gCondition);
|
||||
LeaveCriticalSection(&gLock);
|
||||
print("win32_condition_variable_smoke: wake-all sent\n");
|
||||
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
if (WaitForSingleObject(workers[index], 5000u) != WAIT_OBJECT_0)
|
||||
fail("worker did not exit after WakeAllConditionVariable");
|
||||
if (!CloseHandle(workers[index]))
|
||||
fail("CloseHandle failed");
|
||||
}
|
||||
if (gWorkerFailure || gWoke != 2u)
|
||||
fail("wake-all result mismatch");
|
||||
print("win32_condition_variable_smoke: workers joined\n");
|
||||
|
||||
EnterCriticalSection(&gLock);
|
||||
BOOL signaled = SleepConditionVariableCS(&gCondition, &gLock, 25u);
|
||||
DWORD error = GetLastError();
|
||||
gRelease = 2;
|
||||
LeaveCriticalSection(&gLock);
|
||||
if (signaled || error != ERROR_TIMEOUT || gRelease != 2u)
|
||||
fail("finite timeout or critical-section reacquire failed");
|
||||
print("win32_condition_variable_smoke: timeout complete\n");
|
||||
|
||||
// A wake with no waiter is intentionally not retained.
|
||||
WakeConditionVariable(&gCondition);
|
||||
DeleteCriticalSection(&gLock);
|
||||
print("win32_condition_variable_smoke: PASS\n");
|
||||
while (true) (void)SysExitProcess(0);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one message.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Win32 process-local module lookup smoke test.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
typedef void* HANDLE;
|
||||
typedef int BOOL;
|
||||
|
||||
typedef BOOL (__stdcall *IS_DEBUGGER_PRESENT_ROUTINE)(void);
|
||||
|
||||
__declspec(dllimport) HANDLE GetModuleHandleA(const char* moduleName);
|
||||
__declspec(dllimport) void* GetProcAddress(HANDLE module, const char* procedureName);
|
||||
|
||||
// Writes one test message to the native console.
|
||||
static void print(const char* text) {
|
||||
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
|
||||
(void)SysWriteConsole(&request);
|
||||
}
|
||||
|
||||
// Terminates the test after one contract failure.
|
||||
static void fail(const char* message) {
|
||||
print("win32_module_lookup_smoke: ");
|
||||
print(message);
|
||||
print("\n");
|
||||
while (true)
|
||||
(void)SysExitProcess(1);
|
||||
}
|
||||
|
||||
// Verifies extensionless system-module lookup and dynamic debugger probing.
|
||||
int main(void) {
|
||||
print("win32_module_lookup_smoke: begin\n");
|
||||
|
||||
HANDLE fullName = GetModuleHandleA("kernel32.dll");
|
||||
HANDLE baseName = GetModuleHandleA("KERNEL32");
|
||||
if (!fullName || !baseName || fullName != baseName)
|
||||
fail("extensionless KERNEL32 lookup failed");
|
||||
|
||||
if (GetModuleHandleA("kernel32.invalid"))
|
||||
fail("invalid module suffix was accepted");
|
||||
|
||||
IS_DEBUGGER_PRESENT_ROUTINE isDebuggerPresent =
|
||||
(IS_DEBUGGER_PRESENT_ROUTINE)GetProcAddress(baseName, "IsDebuggerPresent");
|
||||
if (!isDebuggerPresent)
|
||||
fail("IsDebuggerPresent was absent from GetProcAddress");
|
||||
if (isDebuggerPresent())
|
||||
fail("IsDebuggerPresent reported a debugger");
|
||||
|
||||
print("win32_module_lookup_smoke: PASS\n");
|
||||
while (true)
|
||||
(void)SysExitProcess(0);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Win32 recursive mutex smoke test for Bink worker teardown.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
typedef void* HANDLE;
|
||||
typedef unsigned long DWORD;
|
||||
typedef int BOOL;
|
||||
typedef DWORD (__stdcall *THREAD_START_ROUTINE)(void* parameter);
|
||||
|
||||
#define FALSE 0
|
||||
#define TRUE 1
|
||||
#define INFINITE 0xFFFFFFFFu
|
||||
#define WAIT_OBJECT_0 0u
|
||||
#define WAIT_TIMEOUT 0x00000102u
|
||||
#define WAIT_FAILED 0xFFFFFFFFu
|
||||
#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1)
|
||||
|
||||
__declspec(dllimport) HANDLE CreateMutexA(void* attributes, BOOL initialOwner, const char* name);
|
||||
__declspec(dllimport) BOOL ReleaseMutex(HANDLE mutex);
|
||||
__declspec(dllimport) HANDLE CreateThread(void* attributes,
|
||||
size_t stackSize,
|
||||
THREAD_START_ROUTINE startRoutine,
|
||||
void* parameter,
|
||||
DWORD creationFlags,
|
||||
DWORD* threadId);
|
||||
__declspec(dllimport) DWORD WaitForSingleObject(HANDLE handle, DWORD milliseconds);
|
||||
__declspec(dllimport) BOOL CloseHandle(HANDLE handle);
|
||||
__declspec(dllimport) DWORD GetLastError(void);
|
||||
|
||||
static HANDLE gMutex;
|
||||
static DWORD gWorkerWait;
|
||||
static DWORD gWorkerError;
|
||||
static BOOL gWorkerRelease;
|
||||
|
||||
// Writes one test message to the native console.
|
||||
static void print(const char* text) {
|
||||
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
|
||||
(void)SysWriteConsole(&request);
|
||||
}
|
||||
|
||||
// Terminates the test after one contract failure.
|
||||
static void fail(const char* message) {
|
||||
print("win32_mutex_smoke: ");
|
||||
print(message);
|
||||
print("\n");
|
||||
while (true) (void)SysExitProcess(1);
|
||||
}
|
||||
|
||||
// Writes one fixed-width hexadecimal value to the native console.
|
||||
static void print_hex32(DWORD value) {
|
||||
static const char digits[] = "0123456789ABCDEF";
|
||||
char text[] = "0x00000000";
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
text[9 - i] = digits[value & 0xFu];
|
||||
value >>= 4;
|
||||
}
|
||||
print(text);
|
||||
}
|
||||
|
||||
// Reports a failed worker wait with both Win32 result channels.
|
||||
static void fail_worker_wait(const char* message) {
|
||||
print("win32_mutex_smoke: ");
|
||||
print(message);
|
||||
print(" result=");
|
||||
print_hex32(gWorkerWait);
|
||||
print(" error=");
|
||||
print_hex32(gWorkerError);
|
||||
print("\n");
|
||||
while (true) (void)SysExitProcess(1);
|
||||
}
|
||||
|
||||
// Attempts one wait and releases the mutex only after successful acquisition.
|
||||
static DWORD __stdcall mutex_worker(void* parameter) {
|
||||
DWORD timeout = (DWORD)(uintptr_t)parameter;
|
||||
gWorkerWait = WaitForSingleObject(gMutex, timeout);
|
||||
gWorkerError = gWorkerWait == WAIT_FAILED ? GetLastError() : 0;
|
||||
if (gWorkerWait == WAIT_OBJECT_0)
|
||||
gWorkerRelease = ReleaseMutex(gMutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Runs one worker and waits for its normal return.
|
||||
static void run_worker(DWORD timeout) {
|
||||
gWorkerWait = 0xFFFFFFFFu;
|
||||
gWorkerError = 0;
|
||||
gWorkerRelease = FALSE;
|
||||
HANDLE worker = CreateThread(NULL, 0, mutex_worker,
|
||||
(void*)(uintptr_t)timeout, 0, NULL);
|
||||
if (!worker || worker == INVALID_HANDLE_VALUE)
|
||||
fail("CreateThread failed");
|
||||
if (WaitForSingleObject(worker, 5000u) != WAIT_OBJECT_0)
|
||||
fail("worker did not return");
|
||||
if (!CloseHandle(worker))
|
||||
fail("worker CloseHandle failed");
|
||||
}
|
||||
|
||||
// Verifies recursive ownership, non-owner safety, and final release transfer.
|
||||
int main(void) {
|
||||
print("win32_mutex_smoke: begin\n");
|
||||
gMutex = CreateMutexA(NULL, FALSE, NULL);
|
||||
if (!gMutex || gMutex == INVALID_HANDLE_VALUE)
|
||||
fail("CreateMutexA failed");
|
||||
|
||||
if (WaitForSingleObject(gMutex, INFINITE) != WAIT_OBJECT_0 ||
|
||||
WaitForSingleObject(gMutex, INFINITE) != WAIT_OBJECT_0)
|
||||
fail("recursive acquisition failed");
|
||||
|
||||
run_worker(25u);
|
||||
if (gWorkerWait == WAIT_FAILED)
|
||||
fail_worker_wait("doubly-owned finite wait failed");
|
||||
if (gWorkerWait != WAIT_TIMEOUT || gWorkerRelease)
|
||||
fail("worker acquired doubly-owned mutex");
|
||||
|
||||
if (!ReleaseMutex(gMutex))
|
||||
fail("first recursive release failed");
|
||||
run_worker(25u);
|
||||
if (gWorkerWait == WAIT_FAILED)
|
||||
fail_worker_wait("singly-owned finite wait failed");
|
||||
if (gWorkerWait != WAIT_TIMEOUT || gWorkerRelease)
|
||||
fail("worker acquired singly-owned mutex");
|
||||
|
||||
if (!ReleaseMutex(gMutex))
|
||||
fail("final recursive release failed");
|
||||
run_worker(1000u);
|
||||
if (gWorkerWait == WAIT_FAILED)
|
||||
fail_worker_wait("released finite wait failed");
|
||||
if (gWorkerWait != WAIT_OBJECT_0 || !gWorkerRelease)
|
||||
fail("worker did not acquire released mutex");
|
||||
|
||||
if (!CloseHandle(gMutex))
|
||||
fail("mutex CloseHandle failed");
|
||||
|
||||
gMutex = CreateMutexA(NULL, TRUE, NULL);
|
||||
if (!gMutex || gMutex == INVALID_HANDLE_VALUE)
|
||||
fail("initial-owner mutex failed");
|
||||
if (WaitForSingleObject(gMutex, INFINITE) != WAIT_OBJECT_0)
|
||||
fail("initial-owner recursion failed");
|
||||
if (!ReleaseMutex(gMutex) || !ReleaseMutex(gMutex))
|
||||
fail("initial-owner release failed");
|
||||
if (!CloseHandle(gMutex))
|
||||
fail("initial-owner close failed");
|
||||
|
||||
print("win32_mutex_smoke: PASS\n");
|
||||
while (true) (void)SysExitProcess(0);
|
||||
}
|
||||
@@ -54,7 +54,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current process.
|
||||
static void exit_now(int32_t code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one message.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// Win32 suspended-thread creation and resume smoke test.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
typedef void* HANDLE;
|
||||
typedef unsigned long DWORD;
|
||||
typedef int BOOL;
|
||||
typedef DWORD (__stdcall *THREAD_START_ROUTINE)(void* parameter);
|
||||
|
||||
#define CREATE_SUSPENDED 0x00000004u
|
||||
#define INFINITE 0xFFFFFFFFu
|
||||
#define WAIT_OBJECT_0 0u
|
||||
#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1)
|
||||
|
||||
__declspec(dllimport) HANDLE CreateThread(void* attributes,
|
||||
size_t stackSize,
|
||||
THREAD_START_ROUTINE startRoutine,
|
||||
void* parameter,
|
||||
DWORD creationFlags,
|
||||
DWORD* threadId);
|
||||
__declspec(dllimport) DWORD ResumeThread(HANDLE thread);
|
||||
__declspec(dllimport) DWORD WaitForSingleObject(HANDLE handle, DWORD milliseconds);
|
||||
__declspec(dllimport) void Sleep(DWORD milliseconds);
|
||||
__declspec(dllimport) BOOL CloseHandle(HANDLE handle);
|
||||
|
||||
static volatile long gStarted;
|
||||
static volatile long gRelease;
|
||||
|
||||
// Writes one test message to the native console.
|
||||
static void print(const char* text) {
|
||||
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
|
||||
(void)SysWriteConsole(&request);
|
||||
}
|
||||
|
||||
// Terminates the test after one contract failure.
|
||||
static void fail(const char* message) {
|
||||
print("win32_thread_suspend_smoke: ");
|
||||
print(message);
|
||||
print("\n");
|
||||
while (true) (void)SysExitProcess(1);
|
||||
}
|
||||
|
||||
// Records that the suspended worker reached its user entry point.
|
||||
static DWORD __stdcall suspended_worker(void* parameter) {
|
||||
(void)parameter;
|
||||
gStarted = 1;
|
||||
while (!gRelease)
|
||||
Sleep(1u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Exits immediately so process teardown can retain its thread handle.
|
||||
static DWORD __stdcall exited_worker(void* parameter) {
|
||||
(void)parameter;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Verifies that CREATE_SUSPENDED prevents execution until ResumeThread.
|
||||
int main(void) {
|
||||
print("win32_thread_suspend_smoke: begin\n");
|
||||
|
||||
DWORD threadId = 0;
|
||||
HANDLE thread = CreateThread(NULL, 0, suspended_worker, NULL,
|
||||
CREATE_SUSPENDED, &threadId);
|
||||
if (!thread || thread == INVALID_HANDLE_VALUE || threadId == 0)
|
||||
fail("CreateThread(CREATE_SUSPENDED) failed");
|
||||
|
||||
Sleep(25u);
|
||||
if (gStarted)
|
||||
fail("worker ran before ResumeThread");
|
||||
|
||||
if (ResumeThread(thread) != 1u)
|
||||
fail("first ResumeThread did not return one");
|
||||
if (ResumeThread(thread) != 0u)
|
||||
fail("second ResumeThread did not return zero");
|
||||
|
||||
gRelease = 1;
|
||||
if (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0)
|
||||
fail("worker wait failed");
|
||||
if (!gStarted)
|
||||
fail("worker did not run after ResumeThread");
|
||||
if (!CloseHandle(thread))
|
||||
fail("CloseHandle failed");
|
||||
|
||||
// Keep one completed thread handle open until process exit. Dead THREAD
|
||||
// objects may remain waitable, but they must not keep the process alive.
|
||||
HANDLE retained = CreateThread(NULL, 0, exited_worker, NULL, 0, NULL);
|
||||
if (!retained || retained == INVALID_HANDLE_VALUE)
|
||||
fail("retained CreateThread failed");
|
||||
if (WaitForSingleObject(retained, INFINITE) != WAIT_OBJECT_0)
|
||||
fail("retained worker wait failed");
|
||||
|
||||
print("win32_thread_suspend_smoke: PASS\n");
|
||||
while (true) (void)SysExitProcess(0);
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
// WinMM waveform-output smoke test over the native render-stream personality path.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
typedef void* HANDLE;
|
||||
typedef void* HWAVEOUT;
|
||||
typedef unsigned int MMRESULT;
|
||||
typedef unsigned int UINT;
|
||||
typedef uint32_t DWORD;
|
||||
typedef uintptr_t DWORD_PTR;
|
||||
typedef unsigned short WORD;
|
||||
typedef int BOOL;
|
||||
|
||||
#define FALSE 0
|
||||
#define TRUE 1
|
||||
#define INFINITE 0xFFFFFFFFu
|
||||
#define WAIT_OBJECT_0 0u
|
||||
|
||||
#define MMSYSERR_NOERROR 0u
|
||||
#define MMSYSERR_NOTSUPPORTED 8u
|
||||
#define WAVERR_BADFORMAT 32u
|
||||
#define WAVERR_STILLPLAYING 33u
|
||||
|
||||
#define WAVE_FORMAT_PCM 1u
|
||||
#define WAVE_FORMAT_QUERY 0x00000001u
|
||||
#define WAVE_ALLOWSYNC 0x00000002u
|
||||
#define WAVE_FORMAT_DIRECT 0x00000008u
|
||||
#define CALLBACK_WINDOW 0x00010000u
|
||||
#define CALLBACK_THREAD 0x00020000u
|
||||
#define CALLBACK_FUNCTION 0x00030000u
|
||||
#define CALLBACK_EVENT 0x00050000u
|
||||
#define WAVE_MAPPER 0xFFFFFFFFu
|
||||
|
||||
#define WHDR_DONE 0x00000001u
|
||||
#define WHDR_PREPARED 0x00000002u
|
||||
#define WHDR_BEGINLOOP 0x00000004u
|
||||
#define WHDR_ENDLOOP 0x00000008u
|
||||
#define WHDR_INQUEUE 0x00000010u
|
||||
|
||||
#define WOM_OPEN 0x03BBu
|
||||
#define WOM_CLOSE 0x03BCu
|
||||
#define WOM_DONE 0x03BDu
|
||||
|
||||
#define TIME_SAMPLES 0x0002u
|
||||
|
||||
#define TEST_FRAMES 4096u
|
||||
#define TEST_CONTROL_FRAMES 16384u
|
||||
#define TEST_PHASE_MODULUS 1200u
|
||||
#define TEST_PHASE_STEP 11u
|
||||
#define TEST_QUARTER_PHASE 300u
|
||||
#define TEST_PEAK 8192
|
||||
|
||||
typedef struct WAVEFORMATEX {
|
||||
WORD wFormatTag;
|
||||
WORD nChannels;
|
||||
DWORD nSamplesPerSec;
|
||||
DWORD nAvgBytesPerSec;
|
||||
WORD nBlockAlign;
|
||||
WORD wBitsPerSample;
|
||||
WORD cbSize;
|
||||
} WAVEFORMATEX;
|
||||
|
||||
typedef struct WAVEHDR {
|
||||
char* lpData;
|
||||
DWORD dwBufferLength;
|
||||
DWORD dwBytesRecorded;
|
||||
DWORD_PTR dwUser;
|
||||
DWORD dwFlags;
|
||||
DWORD dwLoops;
|
||||
struct WAVEHDR* lpNext;
|
||||
DWORD_PTR reserved;
|
||||
} WAVEHDR;
|
||||
|
||||
typedef struct MMTIME {
|
||||
UINT wType;
|
||||
union {
|
||||
DWORD ms;
|
||||
DWORD sample;
|
||||
DWORD cb;
|
||||
DWORD ticks;
|
||||
} u;
|
||||
} MMTIME;
|
||||
|
||||
typedef struct WAVEOUTCAPSA {
|
||||
WORD wMid;
|
||||
WORD wPid;
|
||||
DWORD vDriverVersion;
|
||||
char szPname[32];
|
||||
DWORD dwFormats;
|
||||
WORD wChannels;
|
||||
WORD wReserved1;
|
||||
DWORD dwSupport;
|
||||
} WAVEOUTCAPSA;
|
||||
|
||||
typedef char TEST_WAVEFORMATEX_SIZE[(sizeof(WAVEFORMATEX) == 20u) ? 1 : -1];
|
||||
typedef char TEST_WAVEHDR_SIZE[(sizeof(WAVEHDR) == 48u) ? 1 : -1];
|
||||
typedef char TEST_MMTIME_SIZE[(sizeof(MMTIME) == 8u) ? 1 : -1];
|
||||
typedef char TEST_WAVEOUTCAPSA_SIZE[(sizeof(WAVEOUTCAPSA) == 52u) ? 1 : -1];
|
||||
|
||||
typedef void (__stdcall *WAVEOUTPROC)(HWAVEOUT, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
|
||||
|
||||
__declspec(dllimport) UINT waveOutGetNumDevs(void);
|
||||
__declspec(dllimport) MMRESULT waveOutOpen(HWAVEOUT*, UINT, const WAVEFORMATEX*, DWORD_PTR, DWORD_PTR, DWORD);
|
||||
__declspec(dllimport) MMRESULT waveOutClose(HWAVEOUT);
|
||||
__declspec(dllimport) MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT);
|
||||
__declspec(dllimport) MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT);
|
||||
__declspec(dllimport) MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT);
|
||||
__declspec(dllimport) MMRESULT waveOutPause(HWAVEOUT);
|
||||
__declspec(dllimport) MMRESULT waveOutRestart(HWAVEOUT);
|
||||
__declspec(dllimport) MMRESULT waveOutReset(HWAVEOUT);
|
||||
__declspec(dllimport) MMRESULT waveOutSetVolume(HWAVEOUT, DWORD);
|
||||
__declspec(dllimport) MMRESULT waveOutGetVolume(HWAVEOUT, DWORD*);
|
||||
__declspec(dllimport) MMRESULT waveOutGetPosition(HWAVEOUT, MMTIME*, UINT);
|
||||
__declspec(dllimport) MMRESULT waveOutGetID(HWAVEOUT, UINT*);
|
||||
__declspec(dllimport) MMRESULT waveOutGetDevCapsA(UINT, WAVEOUTCAPSA*, UINT);
|
||||
__declspec(dllimport) MMRESULT waveOutGetErrorTextA(MMRESULT, char*, UINT);
|
||||
__declspec(dllimport) HANDLE CreateEventA(void*, BOOL, BOOL, const char*);
|
||||
__declspec(dllimport) BOOL SetEvent(HANDLE);
|
||||
__declspec(dllimport) DWORD WaitForSingleObject(HANDLE, DWORD);
|
||||
__declspec(dllimport) BOOL CloseHandle(HANDLE);
|
||||
|
||||
static int16_t gS16Frames[TEST_FRAMES * 2u];
|
||||
static uint8_t gS24Frames[TEST_FRAMES * 2u * 3u];
|
||||
static int16_t gControlFrames[TEST_CONTROL_FRAMES * 2u];
|
||||
static HANDLE gFunctionEvent;
|
||||
static volatile long gOpenCallbacks;
|
||||
static volatile long gDoneCallbacks;
|
||||
static volatile long gCloseCallbacks;
|
||||
|
||||
// Writes one NUL-terminated string to the kernel console.
|
||||
static void print(const char* text) {
|
||||
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
|
||||
(void)SysWriteConsole(&request);
|
||||
}
|
||||
|
||||
// Terminates the test process after one failure.
|
||||
static void fail(const char* message) {
|
||||
print("winmm_waveout_smoke: ");
|
||||
print(message);
|
||||
print("\n");
|
||||
while (true) (void)SysExitProcess(1);
|
||||
}
|
||||
|
||||
// Verifies one WinMM result exactly.
|
||||
static void expect_result(const char* operation, MMRESULT actual, MMRESULT expected) {
|
||||
if (actual != expected) {
|
||||
print("winmm_waveout_smoke: unexpected result from ");
|
||||
print(operation);
|
||||
print("\n");
|
||||
while (true) (void)SysExitProcess(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns one exact integer 440 Hz triangle-wave sample at 48 kHz.
|
||||
static int16_t tone_sample(uint32_t phase) {
|
||||
int32_t sample;
|
||||
if (phase < TEST_QUARTER_PHASE) {
|
||||
sample = (int32_t)(phase * TEST_PEAK / TEST_QUARTER_PHASE);
|
||||
} else if (phase < TEST_QUARTER_PHASE * 2u) {
|
||||
sample = (int32_t)((TEST_QUARTER_PHASE * 2u - phase) *
|
||||
TEST_PEAK / TEST_QUARTER_PHASE);
|
||||
} else if (phase < TEST_QUARTER_PHASE * 3u) {
|
||||
sample = -(int32_t)((phase - TEST_QUARTER_PHASE * 2u) *
|
||||
TEST_PEAK / TEST_QUARTER_PHASE);
|
||||
} else {
|
||||
sample = -(int32_t)((TEST_PHASE_MODULUS - phase) *
|
||||
TEST_PEAK / TEST_QUARTER_PHASE);
|
||||
}
|
||||
return (int16_t)sample;
|
||||
}
|
||||
|
||||
// Fills S16, packed-S24, and muted control buffers without floating point.
|
||||
static void fill_buffers(void) {
|
||||
uint32_t phase = 0;
|
||||
for (uint32_t frame = 0; frame < TEST_FRAMES; frame++) {
|
||||
int16_t sample = tone_sample(phase);
|
||||
gS16Frames[frame * 2u] = sample;
|
||||
gS16Frames[frame * 2u + 1u] = sample;
|
||||
int32_t sample24 = (int32_t)sample * 256;
|
||||
for (uint32_t channel = 0; channel < 2u; channel++) {
|
||||
size_t offset = ((size_t)frame * 2u + channel) * 3u;
|
||||
gS24Frames[offset] = (uint8_t)sample24;
|
||||
gS24Frames[offset + 1u] = (uint8_t)(sample24 >> 8u);
|
||||
gS24Frames[offset + 2u] = (uint8_t)(sample24 >> 16u);
|
||||
}
|
||||
phase += TEST_PHASE_STEP;
|
||||
if (phase >= TEST_PHASE_MODULUS)
|
||||
phase -= TEST_PHASE_MODULUS;
|
||||
}
|
||||
memset(gControlFrames, 0, sizeof(gControlFrames));
|
||||
}
|
||||
|
||||
// Receives function callbacks on the per-HWAVEOUT worker thread.
|
||||
static void __stdcall wave_callback(HWAVEOUT waveOut,
|
||||
UINT message,
|
||||
DWORD_PTR instance,
|
||||
DWORD_PTR parameter1,
|
||||
DWORD_PTR parameter2) {
|
||||
(void)waveOut;
|
||||
(void)instance;
|
||||
(void)parameter1;
|
||||
(void)parameter2;
|
||||
if (message == WOM_OPEN)
|
||||
_InterlockedIncrement(&gOpenCallbacks);
|
||||
else if (message == WOM_DONE) {
|
||||
_InterlockedIncrement(&gDoneCallbacks);
|
||||
(void)SetEvent(gFunctionEvent);
|
||||
} else if (message == WOM_CLOSE)
|
||||
_InterlockedIncrement(&gCloseCallbacks);
|
||||
}
|
||||
|
||||
// Waits for one callback event with a finite deadline.
|
||||
static void wait_event(HANDLE event, const char* operation) {
|
||||
if (WaitForSingleObject(event, 5000u) != WAIT_OBJECT_0)
|
||||
fail(operation);
|
||||
}
|
||||
|
||||
// Returns one canonical PCM format descriptor.
|
||||
static WAVEFORMATEX make_format_at(uint32_t sampleRate,
|
||||
uint16_t channels,
|
||||
uint16_t bitsPerSample) {
|
||||
WAVEFORMATEX format;
|
||||
memset(&format, 0, sizeof(format));
|
||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
format.nChannels = channels;
|
||||
format.nSamplesPerSec = sampleRate;
|
||||
format.wBitsPerSample = bitsPerSample;
|
||||
format.nBlockAlign = (uint16_t)(channels * (bitsPerSample / 8u));
|
||||
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
|
||||
return format;
|
||||
}
|
||||
|
||||
// Returns the default stereo 48 kHz PCM descriptor used for playback tests.
|
||||
static WAVEFORMATEX make_format(uint16_t bitsPerSample) {
|
||||
return make_format_at(48000u, 2u, bitsPerSample);
|
||||
}
|
||||
|
||||
|
||||
// Verifies device enumeration, capabilities, and fixed error descriptions.
|
||||
static void test_device_surface(void) {
|
||||
WAVEOUTCAPSA caps;
|
||||
memset(&caps, 0, sizeof(caps));
|
||||
expect_result("device capabilities",
|
||||
waveOutGetDevCapsA(WAVE_MAPPER, &caps, sizeof(caps)),
|
||||
MMSYSERR_NOERROR);
|
||||
if (caps.wChannels != 2u || !caps.szPname[0])
|
||||
fail("device capabilities mismatch");
|
||||
|
||||
char errorText[64];
|
||||
memset(errorText, 0, sizeof(errorText));
|
||||
expect_result("error text",
|
||||
waveOutGetErrorTextA(WAVERR_BADFORMAT, errorText, sizeof(errorText)),
|
||||
MMSYSERR_NOERROR);
|
||||
if (!errorText[0])
|
||||
fail("error text was empty");
|
||||
}
|
||||
|
||||
// Verifies function callbacks, queued completion, immediate copy, and position.
|
||||
static void test_function_callback(void) {
|
||||
WAVEFORMATEX format = make_format(16u);
|
||||
gFunctionEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
|
||||
if (!gFunctionEvent)
|
||||
fail("CreateEventA for function callback failed");
|
||||
|
||||
HWAVEOUT waveOut = NULL;
|
||||
expect_result("function waveOutOpen",
|
||||
waveOutOpen(&waveOut,
|
||||
WAVE_MAPPER,
|
||||
&format,
|
||||
(DWORD_PTR)(WAVEOUTPROC)wave_callback,
|
||||
0,
|
||||
CALLBACK_FUNCTION | WAVE_ALLOWSYNC | WAVE_FORMAT_DIRECT),
|
||||
MMSYSERR_NOERROR);
|
||||
if (!waveOut || gOpenCallbacks != 1)
|
||||
fail("function WOM_OPEN callback missing");
|
||||
UINT deviceId = UINT32_MAX;
|
||||
expect_result("get device ID", waveOutGetID(waveOut, &deviceId), MMSYSERR_NOERROR);
|
||||
if (deviceId >= waveOutGetNumDevs())
|
||||
fail("resolved device ID is out of range");
|
||||
|
||||
WAVEHDR first;
|
||||
WAVEHDR second;
|
||||
memset(&first, 0, sizeof(first));
|
||||
memset(&second, 0, sizeof(second));
|
||||
first.lpData = (char*)gS16Frames;
|
||||
first.dwBufferLength = sizeof(gS16Frames) / 2u;
|
||||
second.lpData = (char*)gS16Frames + first.dwBufferLength;
|
||||
second.dwBufferLength = sizeof(gS16Frames) - first.dwBufferLength;
|
||||
expect_result("prepare first", waveOutPrepareHeader(waveOut, &first, sizeof(first)), MMSYSERR_NOERROR);
|
||||
expect_result("prepare second", waveOutPrepareHeader(waveOut, &second, sizeof(second)), MMSYSERR_NOERROR);
|
||||
expect_result("write first", waveOutWrite(waveOut, &first, sizeof(first)), MMSYSERR_NOERROR);
|
||||
expect_result("write second", waveOutWrite(waveOut, &second, sizeof(second)), MMSYSERR_NOERROR);
|
||||
|
||||
memset(gS16Frames, 0, sizeof(gS16Frames));
|
||||
while (gDoneCallbacks < 2)
|
||||
wait_event(gFunctionEvent, "function WOM_DONE timed out");
|
||||
if (gDoneCallbacks != 2 ||
|
||||
(first.dwFlags & (WHDR_DONE | WHDR_PREPARED)) != (WHDR_DONE | WHDR_PREPARED) ||
|
||||
(second.dwFlags & (WHDR_DONE | WHDR_PREPARED)) != (WHDR_DONE | WHDR_PREPARED) ||
|
||||
(first.dwFlags & WHDR_INQUEUE) || (second.dwFlags & WHDR_INQUEUE))
|
||||
fail("function header completion mismatch");
|
||||
|
||||
MMTIME position;
|
||||
memset(&position, 0, sizeof(position));
|
||||
position.wType = TIME_SAMPLES;
|
||||
expect_result("get position", waveOutGetPosition(waveOut, &position, sizeof(position)), MMSYSERR_NOERROR);
|
||||
if (position.u.sample < TEST_FRAMES)
|
||||
fail("position did not reach submitted frames");
|
||||
|
||||
expect_result("set volume", waveOutSetVolume(waveOut, 0xC0008000u), MMSYSERR_NOERROR);
|
||||
DWORD volume = 0;
|
||||
expect_result("get volume", waveOutGetVolume(waveOut, &volume), MMSYSERR_NOERROR);
|
||||
if (volume != 0xC0008000u)
|
||||
fail("volume did not round trip");
|
||||
|
||||
expect_result("unprepare first", waveOutUnprepareHeader(waveOut, &first, sizeof(first)), MMSYSERR_NOERROR);
|
||||
expect_result("unprepare second", waveOutUnprepareHeader(waveOut, &second, sizeof(second)), MMSYSERR_NOERROR);
|
||||
expect_result("function close", waveOutClose(waveOut), MMSYSERR_NOERROR);
|
||||
if (gCloseCallbacks != 1)
|
||||
fail("function WOM_CLOSE callback missing");
|
||||
if (!CloseHandle(gFunctionEvent))
|
||||
fail("function callback event close failed");
|
||||
gFunctionEvent = NULL;
|
||||
}
|
||||
|
||||
// Verifies CALLBACK_EVENT and packed-S24 conversion through the native S16 stream.
|
||||
static void test_event_s24(void) {
|
||||
WAVEFORMATEX format = make_format(24u);
|
||||
HANDLE event = CreateEventA(NULL, FALSE, FALSE, NULL);
|
||||
if (!event)
|
||||
fail("CreateEventA for event callback failed");
|
||||
|
||||
HWAVEOUT waveOut = NULL;
|
||||
expect_result("event waveOutOpen",
|
||||
waveOutOpen(&waveOut,
|
||||
WAVE_MAPPER,
|
||||
&format,
|
||||
(DWORD_PTR)event,
|
||||
0,
|
||||
CALLBACK_EVENT),
|
||||
MMSYSERR_NOERROR);
|
||||
wait_event(event, "event WOM_OPEN timed out");
|
||||
|
||||
WAVEHDR header;
|
||||
memset(&header, 0, sizeof(header));
|
||||
header.lpData = (char*)gS24Frames;
|
||||
header.dwBufferLength = sizeof(gS24Frames);
|
||||
expect_result("event prepare", waveOutPrepareHeader(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
||||
expect_result("event write", waveOutWrite(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
||||
memset(gS24Frames, 0, sizeof(gS24Frames));
|
||||
wait_event(event, "event WOM_DONE timed out");
|
||||
if ((header.dwFlags & WHDR_DONE) == 0 || (header.dwFlags & WHDR_INQUEUE))
|
||||
fail("event header completion mismatch");
|
||||
expect_result("event unprepare", waveOutUnprepareHeader(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
||||
expect_result("event close", waveOutClose(waveOut), MMSYSERR_NOERROR);
|
||||
wait_event(event, "event WOM_CLOSE timed out");
|
||||
if (!CloseHandle(event))
|
||||
fail("event callback handle close failed");
|
||||
}
|
||||
|
||||
// Verifies queued-close refusal and synchronous pause, restart, and reset.
|
||||
static void test_control_operations(void) {
|
||||
WAVEFORMATEX format = make_format(16u);
|
||||
HWAVEOUT waveOut = NULL;
|
||||
expect_result("control waveOutOpen",
|
||||
waveOutOpen(&waveOut, WAVE_MAPPER, &format, 0, 0, 0),
|
||||
MMSYSERR_NOERROR);
|
||||
expect_result("control mute", waveOutSetVolume(waveOut, 0), MMSYSERR_NOERROR);
|
||||
|
||||
WAVEHDR header;
|
||||
memset(&header, 0, sizeof(header));
|
||||
header.lpData = (char*)gControlFrames;
|
||||
header.dwBufferLength = sizeof(gControlFrames);
|
||||
expect_result("control prepare", waveOutPrepareHeader(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
||||
header.dwFlags |= WHDR_BEGINLOOP | WHDR_ENDLOOP;
|
||||
header.dwLoops = 2u;
|
||||
expect_result("loop rejection", waveOutWrite(waveOut, &header, sizeof(header)), MMSYSERR_NOTSUPPORTED);
|
||||
header.dwFlags &= ~(WHDR_BEGINLOOP | WHDR_ENDLOOP);
|
||||
header.dwLoops = 0;
|
||||
expect_result("control write", waveOutWrite(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
||||
expect_result("close while queued", waveOutClose(waveOut), WAVERR_STILLPLAYING);
|
||||
expect_result("unprepare while queued",
|
||||
waveOutUnprepareHeader(waveOut, &header, sizeof(header)),
|
||||
WAVERR_STILLPLAYING);
|
||||
expect_result("pause", waveOutPause(waveOut), MMSYSERR_NOERROR);
|
||||
expect_result("second pause", waveOutPause(waveOut), MMSYSERR_NOERROR);
|
||||
expect_result("restart", waveOutRestart(waveOut), MMSYSERR_NOERROR);
|
||||
expect_result("second restart", waveOutRestart(waveOut), MMSYSERR_NOERROR);
|
||||
expect_result("reset", waveOutReset(waveOut), MMSYSERR_NOERROR);
|
||||
if ((header.dwFlags & WHDR_DONE) == 0 || (header.dwFlags & WHDR_INQUEUE))
|
||||
fail("reset did not complete queued header");
|
||||
expect_result("control unprepare", waveOutUnprepareHeader(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
||||
expect_result("control close", waveOutClose(waveOut), MMSYSERR_NOERROR);
|
||||
}
|
||||
|
||||
// Verifies the complete accepted PCM matrix and explicit rejection paths.
|
||||
static void test_formats_and_rejections(void) {
|
||||
static const uint32_t rates[] = { 11025u, 22050u, 44100u, 48000u };
|
||||
static const uint16_t bits[] = { 16u, 24u };
|
||||
for (uint32_t rateIndex = 0; rateIndex < sizeof(rates) / sizeof(rates[0]); rateIndex++) {
|
||||
for (uint16_t channels = 1u; channels <= 2u; channels++) {
|
||||
for (uint32_t bitsIndex = 0; bitsIndex < sizeof(bits) / sizeof(bits[0]); bitsIndex++) {
|
||||
WAVEFORMATEX format = make_format_at(rates[rateIndex], channels, bits[bitsIndex]);
|
||||
expect_result("accepted PCM query",
|
||||
waveOutOpen(NULL, WAVE_MAPPER, &format, 0, 0, WAVE_FORMAT_QUERY),
|
||||
MMSYSERR_NOERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WAVEFORMATEX s16 = make_format(16u);
|
||||
HWAVEOUT waveOut = NULL;
|
||||
expect_result("window callback rejection",
|
||||
waveOutOpen(&waveOut, WAVE_MAPPER, &s16, 1, 0, CALLBACK_WINDOW),
|
||||
MMSYSERR_NOTSUPPORTED);
|
||||
expect_result("thread callback rejection",
|
||||
waveOutOpen(&waveOut, WAVE_MAPPER, &s16, 1, 0, CALLBACK_THREAD),
|
||||
MMSYSERR_NOTSUPPORTED);
|
||||
|
||||
WAVEFORMATEX floating = make_format_at(48000u, 2u, 32u);
|
||||
floating.wFormatTag = 3u;
|
||||
expect_result("Float32 rejection",
|
||||
waveOutOpen(NULL, WAVE_MAPPER, &floating, 0, 0, WAVE_FORMAT_QUERY),
|
||||
WAVERR_BADFORMAT);
|
||||
|
||||
WAVEFORMATEX badRate = make_format_at(32000u, 2u, 16u);
|
||||
expect_result("32 kHz rejection",
|
||||
waveOutOpen(NULL, WAVE_MAPPER, &badRate, 0, 0, WAVE_FORMAT_QUERY),
|
||||
WAVERR_BADFORMAT);
|
||||
|
||||
WAVEFORMATEX badChannels = make_format_at(48000u, 3u, 16u);
|
||||
expect_result("three-channel rejection",
|
||||
waveOutOpen(NULL, WAVE_MAPPER, &badChannels, 0, 0, WAVE_FORMAT_QUERY),
|
||||
WAVERR_BADFORMAT);
|
||||
}
|
||||
|
||||
// Runs the focused WinMM waveform-output contract test.
|
||||
int main(void) {
|
||||
print("winmm_waveout_smoke: begin\n");
|
||||
if (!waveOutGetNumDevs())
|
||||
fail("no waveform output devices");
|
||||
fill_buffers();
|
||||
test_device_surface();
|
||||
test_formats_and_rejections();
|
||||
test_control_operations();
|
||||
test_function_callback();
|
||||
test_event_s24();
|
||||
print("winmm_waveout_smoke: PASS\n");
|
||||
while (true) (void)SysExitProcess(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ static void print_u64(uint64_t v) {
|
||||
|
||||
// Terminates the current test process.
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
while (true) SysExitProcess(code);
|
||||
}
|
||||
|
||||
// Fails the test with one plain message.
|
||||
|
||||
Reference in new issue
Block a user