UTK

## MicroTalk

**EA MicroTalk** (also **UTalk** or **UTK**) is a linear-predictive speech codec used in various games by Electronic Arts. The earliest known game to use it is Beasts & Bumpkins (1997). The codec has a bandwidth of 11.025kHz (sampling rate 22.05kHz) and frame size of 20ms (432 samples) and only supports mono. It is typically encoded at 32 kbit/s.

The codec was reverse-engineered by trass3r on August 05, 2010 and was later analyzed by Andrew D'Addesio on January 12, 2012.

MicroTalk has been seen in several containers, depending on the game:

* PT/.m10 (Beasts and Bumpkins)
* Maxis UTK (The Sims Online, SimCity 4)
* SCxl (FIFA 2001 PS2, FIFA 2002 PS2)

All code and text content on this page is public domain and licensed under the UNLICENSE.

For decoders for all of the above games, see https://github.com/daddesio/utkencode.

## Variants

There are a few variants of MicroTalk:

* MicroTalk 10:1 (codec\_id=4 in EA SCxl) and MicroTalk 5:1 (codec\_id=22) are really the same codec; the encoder simply chooses different compression parameters (reduced\_bw is enabled in 10:1 and disabled in 5:1). MicroTalk 10:1 is typically encoded at 32kit/s; 5:1 is encoded at 64kbit/s.
* MicroTalk Revision 3 (revision=3 in SCxl) is a variant which supports raw PCM samples. (See Revision 3.)

## UTK file header

The simplest container is Maxis UTK. The UTK file header is little-endian and is very similar to the Maxis XA file header, with a few modifications.

```
struct UTKHeader
{
 char sID[4];
 DWORD dwOutSize;
 DWORD dwWfxSize;
 /\* WAVEFORMATEX \*/
 WORD wFormatTag;
 WORD nChannels;
 DWORD nSamplesPerSec;
 DWORD nAvgBytesPerSec;
 WORD nBlockAlign;
 WORD wBitsPerSample;
 WORD cbSize;
 WORD padding;
};

```

* **sID** - A 4-byte string identifier equal to "UTM0".
* **dwOutSize** - The size in bytes of the decompressed audio, not including the WAV header; equal to num\_samples\*2.
* **dwWfxSize** - The size in bytes of the WAVEFORMATEX structure to follow; must be 20 (0x14).
* **wFormatTag** - The decoded audio format; set to WAVE\_FORMAT\_PCM (0x0001).
* **nChannels** - Number of channels in the decoded audio; must be 1.
* **nSamplesPerSec** - Sampling rate of the decoded audio; this is always 22050.
* **nAvgBytesPerSec** - Byte rate of the decoded audio; equal to nSamplesPerSec\*nChannels\*wBitsPerSample/8 or nSamplesPerSec\*nBlockAlign.
* **nBlockAlign** - Number of bytes per sampling interval in the decoded audio; equal to nChannels\*wBitsPerSample/8.
* **wBitsPerSample** - Bits per sample in the decoded audio (8, 16, 24, 32, etc.); this is always 16.
* **cbSize** - The size in bytes of extra format information following the WAVEFORMATEX structure; must be 0.
* **padding** - Two bytes of padding; this is always 0.

## Decoding

```
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

/\* Note: This struct assumes a member alignment of 4 bytes.
\*\* This matters when pitch\_lag > 216 on the first subframe of any given frame,
\*\* causing the adaptive codebook pointer to underflow as far as fixed\_gains.
\*\* (Does that happen in any real-world samples?) \*/
typedef struct UTKContext {
 FILE \*fp;
 unsigned int bits\_value;
 int bits\_count;
 int reduced\_bw;
 int multipulse\_thresh;
 float fixed\_gains[64];
 float rc[12];
 float synth\_history[12];
 float adapt\_cb[324];
 float decompressed\_frame[432];
} UTKContext;

enum {
 MDL\_NORMAL = 0,
 MDL\_LARGEPULSE = 1
};

static const float utk\_rc\_table[64] = {
 0.0f,
 -.99677598476409912109375f, -.99032700061798095703125f, -.983879029750823974609375f, -.977430999279022216796875f,
 -.970982015132904052734375f, -.964533984661102294921875f, -.958085000514984130859375f, -.9516370296478271484375f,
 -.930754005908966064453125f, -.904959976673126220703125f, -.879167020320892333984375f, -.853372991085052490234375f,
 -.827579021453857421875f, -.801786005496978759765625f, -.775991976261138916015625f, -.75019800662994384765625f,
 -.724404990673065185546875f, -.6986110210418701171875f, -.6706349849700927734375f, -.61904799938201904296875f,
 -.567460000514984130859375f, -.515873014926910400390625f, -.4642859995365142822265625f, -.4126980006694793701171875f,
 -.361110985279083251953125f, -.309523999691009521484375f, -.257937014102935791015625f, -.20634900033473968505859375f,
 -.1547619998455047607421875f, -.10317499935626983642578125f, -.05158700048923492431640625f,
 0.0f,
 +.05158700048923492431640625f, +.10317499935626983642578125f, +.1547619998455047607421875f, +.20634900033473968505859375f,
 +.257937014102935791015625f, +.309523999691009521484375f, +.361110985279083251953125f, +.4126980006694793701171875f,
 +.4642859995365142822265625f, +.515873014926910400390625f, +.567460000514984130859375f, +.61904799938201904296875f,
 +.6706349849700927734375f, +.6986110210418701171875f, +.724404990673065185546875f, +.75019800662994384765625f,
 +.775991976261138916015625f, +.801786005496978759765625f, +.827579021453857421875f, +.853372991085052490234375f,
 +.879167020320892333984375f, +.904959976673126220703125f, +.930754005908966064453125f, +.9516370296478271484375f,
 +.958085000514984130859375f, +.964533984661102294921875f, +.970982015132904052734375f, +.977430999279022216796875f,
 +.983879029750823974609375f, +.99032700061798095703125f, +.99677598476409912109375
};

static const uint8\_t utk\_codebooks[2][256] = {
 { /\* normal model \*/
 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 25,
 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 0,
 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 26,
 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 2
 }, { /\* large-pulse model \*/
 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3,
 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3
 }
};

static const struct {
 int next\_model;
 int code\_size;
 float pulse\_value;
} utk\_commands[29] = {
 {MDL\_LARGEPULSE, 8, 0.0f},
 {MDL\_LARGEPULSE, 7, 0.0f},
 {MDL\_NORMAL, 8, 0.0f},
 {MDL\_NORMAL, 7, 0.0f},
 {MDL\_NORMAL, 2, 0.0f},
 {MDL\_NORMAL, 2, -1.0f},
 {MDL\_NORMAL, 2, +1.0f},
 {MDL\_NORMAL, 3, -1.0f},
 {MDL\_NORMAL, 3, +1.0f},
 {MDL\_LARGEPULSE, 4, -2.0f},
 {MDL\_LARGEPULSE, 4, +2.0f},
 {MDL\_LARGEPULSE, 3, -2.0f},
 {MDL\_LARGEPULSE, 3, +2.0f},
 {MDL\_LARGEPULSE, 5, -3.0f},
 {MDL\_LARGEPULSE, 5, +3.0f},
 {MDL\_LARGEPULSE, 4, -3.0f},
 {MDL\_LARGEPULSE, 4, +3.0f},
 {MDL\_LARGEPULSE, 6, -4.0f},
 {MDL\_LARGEPULSE, 6, +4.0f},
 {MDL\_LARGEPULSE, 5, -4.0f},
 {MDL\_LARGEPULSE, 5, +4.0f},
 {MDL\_LARGEPULSE, 7, -5.0f},
 {MDL\_LARGEPULSE, 7, +5.0f},
 {MDL\_LARGEPULSE, 6, -5.0f},
 {MDL\_LARGEPULSE, 6, +5.0f},
 {MDL\_LARGEPULSE, 8, -6.0f},
 {MDL\_LARGEPULSE, 8, +6.0f},
 {MDL\_LARGEPULSE, 7, -6.0f},
 {MDL\_LARGEPULSE, 7, +6.0f}
};

static int utk\_read\_bits(UTKContext \*ctx, int count)
{
 int ret = ctx->bits\_value & ((1 << count) - 1);
 ctx->bits\_value >>= count;
 ctx->bits\_count -= count;

 if (ctx->bits\_count < 8) {
 /\* read another byte \*/
 uint8\_t x;
 if (fread(&x, 1, 1, ctx->fp) != 1)
 x = 0;
 ctx->bits\_value |= x << ctx->bits\_count;
 ctx->bits\_count += 8;
 }

 return ret;
}

static void utk\_init(FILE \*fp, UTKContext \*ctx)
{
 int i;
 uint8\_t x;
 float multiplier;

 memset(ctx, 0, sizeof(\*ctx));

 ctx->fp = fp;

 if (fread(&x, 1, 1, ctx->fp) != 1)
 x = 0;
 ctx->bits\_value = x;
 ctx->bits\_count = 8;

 ctx->reduced\_bw = utk\_read\_bits(ctx, 1);
 ctx->multipulse\_thresh = 32 - utk\_read\_bits(ctx, 4);
 ctx->fixed\_gains[0] = 8.0f \* (1 + utk\_read\_bits(ctx, 4));
 multiplier = 1.04f + utk\_read\_bits(ctx, 6)\*0.001f;

 for (i = 1; i < 64; i++)
 ctx->fixed\_gains[i] = ctx->fixed\_gains[i-1] \* multiplier;
}

static void utk\_decode\_excitation(UTKContext \*ctx, int use\_multipulse, float \*out, int stride)
{
 int i;

 if (use\_multipulse) {
 /\* multi-pulse model: n pulses are coded explicitly; the rest are zero \*/
 int model, cmd;
 model = 0;
 i = 0;
 while (i < 108) {
 cmd = utk\_codebooks[model][ctx->bits\_value & 0xff];
 model = utk\_commands[cmd].next\_model;
 utk\_read\_bits(ctx, utk\_commands[cmd].code\_size);

 if (cmd > 3) {
 /\* insert a pulse with magnitude <= 6.0f \*/
 out[i] = utk\_commands[cmd].pulse\_value;
 i += stride;
 } else if (cmd > 1) {
 /\* insert between 7 and 70 zeros \*/
 int count = 7 + utk\_read\_bits(ctx, 6);
 if (i + count \* stride > 108)
 count = (108 - i)/stride;

 while (count > 0) {
 out[i] = 0.0f;
 i += stride;
 count--;
 }
 } else {
 /\* insert a pulse with magnitude >= 7.0f \*/
 int x = 7;

 while (utk\_read\_bits(ctx, 1))
 x++;

 if (!utk\_read\_bits(ctx, 1))
 x \*= -1;

 out[i] = (float)x;
 i += stride;
 }
 }
 } else {
 /\* RELP model: entire residual (excitation) signal is coded explicitly \*/
 i = 0;
 while (i < 108) {
 if (!utk\_read\_bits(ctx, 1))
 out[i] = 0.0f;
 else if (!utk\_read\_bits(ctx, 1))
 out[i] = -2.0f;
 else
 out[i] = 2.0f;

 i += stride;
 }
 }
}

static void rc\_to\_lpc(const float \*rc, float \*lpc)
{
 int i, j;
 float tmp1[12];
 float tmp2[12];

 for (i = 10; i >= 0; i--)
 tmp2[1+i] = rc[i];

 tmp2[0] = 1.0f;

 for (i = 0; i < 12; i++) {
 float x = -tmp2[11] \* rc[11];

 for (j = 10; j >= 0; j--) {
 x -= tmp2[j] \* rc[j];
 tmp2[j+1] = x \* rc[j] + tmp2[j];
 }

 tmp1[i] = tmp2[0] = x;

 for (j = 0; j < i; j++)
 x -= tmp1[i-1-j] \* lpc[j];

 lpc[i] = x;
 }
}

static void utk\_lp\_synthesis\_filter(UTKContext \*ctx, int offset, int num\_blocks)
{
 int i, j, k;
 float lpc[12];
 float \*ptr = &ctx->decompressed\_frame[offset];

 rc\_to\_lpc(ctx->rc, lpc);

 for (i = 0; i < num\_blocks; i++) {
 for (j = 0; j < 12; j++) {
 float x = \*ptr;

 for (k = 0; k < j; k++)
 x += lpc[k] \* ctx->synth\_history[k-j+12];
 for (; k < 12; k++)
 x += lpc[k] \* ctx->synth\_history[k-j];

 ctx->synth\_history[11-j] = x;
 \*ptr++ = x;
 }
 }
}

static void utk\_decode\_frame(UTKContext \*ctx)
{
 int i, j;
 int use\_multipulse = 0;
 float excitation[5+108+5];
 float rc\_delta[12];

 memset(&excitation[0], 0, 5\*sizeof(float));
 memset(&excitation[5+108], 0, 5\*sizeof(float));

 /\* read the reflection coefficients \*/
 for (i = 0; i < 12; i++) {
 int idx;
 if (i == 0) {
 idx = utk\_read\_bits(ctx, 6);
 if (idx < ctx->multipulse\_thresh)
 use\_multipulse = 1;
 } else if (i < 4) {
 idx = utk\_read\_bits(ctx, 6);
 } else {
 idx = 16 + utk\_read\_bits(ctx, 5);
 }

 rc\_delta[i] = (utk\_rc\_table[idx] - ctx->rc[i])\*0.25f;
 }

 /\* decode four subframes \*/
 for (i = 0; i < 4; i++) {
 int pitch\_lag = utk\_read\_bits(ctx, 8);
 float pitch\_gain = (float)utk\_read\_bits(ctx, 4)/15.0f;
 float fixed\_gain = ctx->fixed\_gains[utk\_read\_bits(ctx, 6)];

 if (!ctx->reduced\_bw) {
 utk\_decode\_excitation(ctx, use\_multipulse, &excitation[5], 1);
 } else {
 /\* residual (excitation) signal is encoded at reduced bandwidth \*/
 int align = utk\_read\_bits(ctx, 1);
 int zero = utk\_read\_bits(ctx, 1);

 utk\_decode\_excitation(ctx, use\_multipulse, &excitation[5+align], 2);

 if (zero) {
 /\* fill the remaining samples with zero
 \*\* (spectrum is duplicated into high frequencies) \*/
 for (j = 0; j < 54; j++)
 excitation[5+(1-align)+2\*j] = 0.0f;
 } else {
 /\* interpolate the remaining samples
 \*\* (spectrum is low-pass filtered) \*/
 float \*ptr = &excitation[5+(1-align)];
 for (j = 0; j < 108; j += 2)
 ptr[j] = ptr[j-5] \* 0.01803267933428287506103515625f
 - ptr[j-3] \* 0.114591561257839202880859375f
 + ptr[j-1] \* 0.597385942935943603515625f
 + ptr[j+1] \* 0.597385942935943603515625f
 - ptr[j+3] \* 0.114591561257839202880859375f
 + ptr[j+5] \* 0.01803267933428287506103515625f;

 /\* scale by 0.5f to give the sinc impulse response unit energy \*/
 fixed\_gain \*= 0.5f;
 }
 }

 for (j = 0; j < 108; j++)
 ctx->decompressed\_frame[108\*i+j] = fixed\_gain \* excitation[5+j]
 + pitch\_gain \* ctx->adapt\_cb[108\*i+216-pitch\_lag+j];
 }

 for (i = 0; i < 324; i++)
 ctx->adapt\_cb[i] = ctx->decompressed\_frame[108+i];

 for (i = 0; i < 4; i++) {
 for (j = 0; j < 12; j++)
 ctx->rc[j] += rc\_delta[j];

 utk\_lp\_synthesis\_filter(ctx, 12\*i, i < 3 ? 1 : 33);
 }
}

```

## Sample program

```
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define MAKE\_U32(a,b,c,d) ((a)|((b)<<8)|((c)<<16)|((d)<<24))
#define ROUND(x) ((x) >= 0.0f ? ((x)+0.5f) : ((x)-0.5f))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define MAX(x,y) ((x)>(y)?(x):(y))
#define CLAMP(x,min,max) MIN(MAX(x,min),max)

static void read\_bytes(FILE \*fp, uint8\_t \*dest, size\_t size);
static void write\_bytes(FILE \*fp, const uint8\_t \*dest, size\_t size);
static uint32\_t read\_u32(FILE \*fp);
static uint16\_t read\_u16(FILE \*fp);
static void write\_u32(FILE \*fp, uint32\_t x);
static void write\_u16(FILE \*fp, uint16\_t x);

int main(int argc, char \*argv[])
{
 const char \*infile, \*outfile;
 UTKContext ctx;
 uint32\_t sID;
 uint32\_t dwOutSize;
 uint32\_t dwWfxSize;
 uint16\_t wFormatTag;
 uint16\_t nChannels;
 uint32\_t nSamplesPerSec;
 uint32\_t nAvgBytesPerSec;
 uint16\_t nBlockAlign;
 uint16\_t wBitsPerSample;
 uint16\_t cbSize;
 uint32\_t num\_samples;
 FILE \*infp, \*outfp;
 int force = 0;
 int error = 0;
 int i;

 if (argc == 4 && !strcmp(argv[1], "-f")) {
 force = 1;
 argv++, argc--;
 }

 if (argc != 3) {
 printf("Usage: utkdecode [-f] infile outfile\n");
 printf("Decode Maxis UTK to wav.\n");
 return EXIT\_FAILURE;
 }

 infile = argv[1];
 outfile = argv[2];

 infp = fopen(infile, "rb");
 if (!infp) {
 fprintf(stderr, "error: failed to open '%s' for reading: %s\n", argv[1], strerror(errno));
 return EXIT\_FAILURE;
 }

 if (!force && fopen(outfile, "rb")) {
 fprintf(stderr, "error: '%s' already exists\n", argv[2]);
 return EXIT\_FAILURE;
 }

 outfp = fopen(outfile, "wb");
 if (!outfp) {
 fprintf(stderr, "error: failed to create '%s': %s\n", argv[2], strerror(errno));
 return EXIT\_FAILURE;
 }

 sID = read\_u32(infp);
 dwOutSize = read\_u32(infp);
 dwWfxSize = read\_u32(infp);
 wFormatTag = read\_u16(infp);
 nChannels = read\_u16(infp);
 nSamplesPerSec = read\_u32(infp);
 nAvgBytesPerSec = read\_u32(infp);
 nBlockAlign = read\_u16(infp);
 wBitsPerSample = read\_u16(infp);
 cbSize = read\_u16(infp);
 read\_u16(infp); /\* padding \*/

 if (sID != MAKE\_U32('U','T','M','0')) {
 fprintf(stderr, "error: not a valid UTK file (expected UTM0 signature)\n");
 return EXIT\_FAILURE;
 } else if ((dwOutSize & 0x01) != 0 || dwOutSize >= 0x01000000) {
 fprintf(stderr, "error: invalid dwOutSize %u\n", (unsigned)dwOutSize);
 return EXIT\_FAILURE;
 } else if (dwWfxSize != 20) {
 fprintf(stderr, "error: invalid dwWfxSize %u (expected 20)\n", (unsigned)dwWfxSize);
 return EXIT\_FAILURE;
 } else if (wFormatTag != 1) {
 fprintf(stderr, "error: invalid wFormatTag %u (expected 1)\n", (unsigned)wFormatTag);
 return EXIT\_FAILURE;
 }

 if (nChannels != 1) {
 fprintf(stderr, "error: invalid nChannels %u (only mono is supported)\n", (unsigned)nChannels);
 error = 1;
 }
 if (nSamplesPerSec < 8000 || nSamplesPerSec > 192000) {
 fprintf(stderr, "error: invalid nSamplesPerSec %u\n", (unsigned)nSamplesPerSec);
 error = 1;
 }
 if (nAvgBytesPerSec != nSamplesPerSec \* nBlockAlign) {
 fprintf(stderr, "error: invalid nAvgBytesPerSec %u (expected nSamplesPerSec \* nBlockAlign)\n", (unsigned)nAvgBytesPerSec);
 error = 1;
 }
 if (nBlockAlign != 2) {
 fprintf(stderr, "error: invalid nBlockAlign %u (expected 2)\n", (unsigned)nBlockAlign);
 error = 1;
 }
 if (wBitsPerSample != 16) {
 fprintf(stderr, "error: invalid wBitsPerSample %u (expected 16)\n", (unsigned)wBitsPerSample);
 error = 1;
 }
 if (cbSize != 0) {
 fprintf(stderr, "error: invalid cbSize %u (expected 0)\n", (unsigned)cbSize);
 error = 1;
 }
 if (error)
 return EXIT\_FAILURE;

 num\_samples = dwOutSize/2;

 utk\_init(infp, &ctx);

 write\_u32(outfp, MAKE\_U32('R','I','F','F'));
 write\_u32(outfp, 36 + num\_samples\*2);
 write\_u32(outfp, MAKE\_U32('W','A','V','E'));
 write\_u32(outfp, MAKE\_U32('f','m','t',' '));
 write\_u32(outfp, 16);
 write\_u16(outfp, wFormatTag);
 write\_u16(outfp, nChannels);
 write\_u32(outfp, nSamplesPerSec);
 write\_u32(outfp, nAvgBytesPerSec);
 write\_u16(outfp, nBlockAlign);
 write\_u16(outfp, wBitsPerSample);
 write\_u32(outfp, MAKE\_U32('d','a','t','a'));
 write\_u32(outfp, num\_samples\*2);

 while (num\_samples > 0) {
 int count = MIN(num\_samples, 432);

 if (feof(infp)) {
 fprintf(stderr, "error: unexpected end of file in '%s'\n", infile);
 return EXIT\_FAILURE;
 }

 utk\_decode\_frame(&ctx);

 for (i = 0; i < count; i++) {
 int x = ROUND(ctx.decompressed\_frame[i]);
 write\_u16(outfp, (int16\_t)CLAMP(x, -32768, 32767));
 }

 num\_samples -= count;
 }

 if (fclose(outfp) != 0) {
 fprintf(stderr, "error: failed to close '%s': %s\n", outfile, strerror(errno));
 return EXIT\_FAILURE;
 }

 fclose(infp);

 return EXIT\_SUCCESS;
}

static void read\_bytes(FILE \*fp, uint8\_t \*dest, size\_t size)
{
 size\_t bytes\_copied;

 if (!size)
 return;

 bytes\_copied = fread(dest, 1, size, fp);
 if (bytes\_copied < size) {
 if (ferror(fp)) {
 fprintf(stderr, "error: fread failed: %s\n", strerror(errno));
 exit(EXIT\_FAILURE);
 }

 /\* We have reached EOF, so return zeros. \*/
 memset(dest+bytes\_copied, 0, size-bytes\_copied);
 }
}

static void write\_bytes(FILE \*fp, const uint8\_t \*dest, size\_t size)
{
 if (!size)
 return;

 if (fwrite(dest, 1, size, fp) != size) {
 fprintf(stderr, "error: fwrite failed: %s\n", strerror(errno));
 exit(EXIT\_FAILURE);
 }
}

static uint32\_t read\_u32(FILE \*fp)
{
 uint8\_t dest[4];
 read\_bytes(fp, dest, sizeof(dest));
 return dest[0] | (dest[1] << 8) | (dest[2] << 16) | (dest[3] << 24);
}

static uint16\_t read\_u16(FILE \*fp)
{
 uint8\_t dest[2];
 read\_bytes(fp, dest, sizeof(dest));
 return dest[0] | (dest[1] << 8);
}

static void write\_u32(FILE \*fp, uint32\_t x)
{
 uint8\_t dest[4];
 dest[0] = (uint8\_t)x;
 dest[1] = (uint8\_t)(x>>8);
 dest[2] = (uint8\_t)(x>>16);
 dest[3] = (uint8\_t)(x>>24);
 write\_bytes(fp, dest, sizeof(dest));
}

static void write\_u16(FILE \*fp, uint16\_t x)
{
 uint8\_t dest[2];
 dest[0] = (uint8\_t)x;
 dest[1] = (uint8\_t)(x>>8);
 write\_bytes(fp, dest, sizeof(dest));
}

```

## Revision 3

MicroTalk Revision 3 does not seem to be used in any games(?) but can be created using the Electronic Arts Sound eXchange tool sx.exe (sx -mt5\_blk input.dat -=output.dat).

The only difference is that each audio frame may optionally contain raw 16-bit PCM data, which overwrites n samples at a given offset in the frame.

The decoder logic changes from this:

```
while position < num_samples:
  utk_decode_frame();

```

to this:

```
while position < num_samples:
  raw_data_present = (read_byte() == 0xee);
  utk_decode_frame();
  reset_bit_reader();
  if (raw_data_present):
    offset = read_i16_be(); // 16-bit big-endian signed integer
    count = read_i16_be();
    if (offset < 0 || offset > 432)
        die("offset out of range");
    if (count < 0 || count > 432 - offset)
        die("count out of range");

    for (i = 0; i < count; i++):
      decompressed_frame[offset+i] = read_i16_be();

```

sx.exe reads the offset and count fields as 16-bit signed integers and does not do any bounds checking (see 004274D1 in sx.exe v3.01.01). This means a specially crafted MicroTalk Rev. 3 file can crash sx.exe. For completeness, we have added the bounds checking to the above pseudocode.