2022-05-19 01:51:26 -07:00
|
|
|
#include "lips.h"
|
|
|
|
|
|
|
|
#include "audio.h"
|
|
|
|
#include "core.h"
|
2022-06-18 07:49:48 -07:00
|
|
|
#include "db.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "debug.h"
|
|
|
|
#include "game_sound.h"
|
|
|
|
#include "memory.h"
|
2022-05-28 02:34:49 -07:00
|
|
|
#include "platform_compat.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "sound.h"
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2022-06-18 07:49:48 -07:00
|
|
|
static char* _lips_fix_string(const char* fileName, size_t length);
|
|
|
|
static int lipsReadV1(LipsData* a1, File* stream);
|
|
|
|
static int _lips_make_speech();
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
// 0x519240
|
|
|
|
unsigned char gLipsCurrentPhoneme = 0;
|
|
|
|
|
|
|
|
// 0x519241
|
2022-06-18 07:49:48 -07:00
|
|
|
static unsigned char gLipsPreviousPhoneme = 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x519244
|
2022-06-18 07:49:48 -07:00
|
|
|
static int _head_marker_current = 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x519248
|
|
|
|
bool gLipsPhonemeChanged = true;
|
|
|
|
|
|
|
|
// 0x51924C
|
|
|
|
LipsData gLipsData = {
|
|
|
|
2,
|
|
|
|
22528,
|
|
|
|
0,
|
|
|
|
NULL,
|
|
|
|
-1,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
50,
|
|
|
|
100,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
"TEST",
|
|
|
|
"VOC",
|
|
|
|
"TXT",
|
|
|
|
"LIP",
|
|
|
|
};
|
|
|
|
|
|
|
|
// 0x5193B4
|
2022-06-18 07:49:48 -07:00
|
|
|
static int _speechStartTime = 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x613CA0
|
2022-06-18 07:49:48 -07:00
|
|
|
static char _lips_subdir_name[14];
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x613CAE
|
2022-06-18 07:49:48 -07:00
|
|
|
static char _tmp_str[50];
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x47AAC0
|
2022-06-18 07:49:48 -07:00
|
|
|
static char* _lips_fix_string(const char* fileName, size_t length)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
|
|
|
strncpy(_tmp_str, fileName, length);
|
|
|
|
return _tmp_str;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x47AAD8
|
|
|
|
void lipsTicker()
|
|
|
|
{
|
|
|
|
int v0;
|
|
|
|
SpeechMarker* speech_marker;
|
|
|
|
int v5;
|
|
|
|
|
|
|
|
v0 = _head_marker_current;
|
|
|
|
|
|
|
|
if ((gLipsData.flags & LIPS_FLAG_0x02) != 0) {
|
|
|
|
int v1 = _soundGetPosition(gLipsData.sound);
|
|
|
|
|
|
|
|
speech_marker = &(gLipsData.markers[v0]);
|
|
|
|
while (v1 > speech_marker->position) {
|
|
|
|
gLipsCurrentPhoneme = gLipsData.phonemes[v0];
|
|
|
|
v0++;
|
|
|
|
|
|
|
|
if (v0 >= gLipsData.field_2C) {
|
|
|
|
v0 = 0;
|
|
|
|
gLipsCurrentPhoneme = gLipsData.phonemes[0];
|
|
|
|
|
|
|
|
if ((gLipsData.flags & LIPS_FLAG_0x01) == 0) {
|
|
|
|
_head_marker_current = 0;
|
|
|
|
soundStop(gLipsData.sound);
|
|
|
|
v0 = _head_marker_current;
|
|
|
|
gLipsData.flags &= ~(LIPS_FLAG_0x01 | LIPS_FLAG_0x02);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
speech_marker = &(gLipsData.markers[v0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (v0 >= gLipsData.field_2C - 1) {
|
|
|
|
_head_marker_current = v0;
|
|
|
|
|
|
|
|
v5 = 0;
|
|
|
|
if (gLipsData.field_2C <= 5) {
|
|
|
|
debugPrint("Error: Too few markers to stop speech!");
|
|
|
|
} else {
|
|
|
|
v5 = 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
speech_marker = &(gLipsData.markers[v5]);
|
|
|
|
if (v1 < speech_marker->position) {
|
|
|
|
v0 = 0;
|
|
|
|
gLipsCurrentPhoneme = gLipsData.phonemes[0];
|
|
|
|
|
|
|
|
if ((gLipsData.flags & LIPS_FLAG_0x01) == 0) {
|
|
|
|
_head_marker_current = 0;
|
|
|
|
soundStop(gLipsData.sound);
|
|
|
|
v0 = _head_marker_current;
|
|
|
|
gLipsData.flags &= ~(LIPS_FLAG_0x01 | LIPS_FLAG_0x02);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gLipsPreviousPhoneme != gLipsCurrentPhoneme) {
|
|
|
|
gLipsPreviousPhoneme = gLipsCurrentPhoneme;
|
|
|
|
gLipsPhonemeChanged = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
_head_marker_current = v0;
|
|
|
|
|
|
|
|
soundContinueAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x47AC2C
|
|
|
|
int lipsStart()
|
|
|
|
{
|
|
|
|
gLipsData.flags |= LIPS_FLAG_0x02;
|
|
|
|
_head_marker_current = 0;
|
|
|
|
|
|
|
|
if (_soundSetPosition(gLipsData.sound, gLipsData.field_20) != 0) {
|
|
|
|
debugPrint("Failed set of start_offset!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
int v2 = _head_marker_current;
|
|
|
|
while (1) {
|
|
|
|
_head_marker_current = v2;
|
|
|
|
|
|
|
|
SpeechMarker* speechEntry = &(gLipsData.markers[v2]);
|
|
|
|
if (gLipsData.field_20 <= speechEntry->position) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
v2++;
|
|
|
|
|
|
|
|
gLipsCurrentPhoneme = gLipsData.phonemes[v2];
|
|
|
|
}
|
|
|
|
|
|
|
|
int speechVolume = speechGetVolume();
|
|
|
|
soundSetVolume(gLipsData.sound, (int)(speechVolume * 0.69));
|
|
|
|
|
|
|
|
_speechStartTime = _get_time();
|
|
|
|
|
|
|
|
if (soundPlay(gLipsData.sound) != 0) {
|
|
|
|
debugPrint("Failed play!\n");
|
|
|
|
_head_marker_current = 0;
|
|
|
|
|
|
|
|
soundStop(gLipsData.sound);
|
|
|
|
gLipsData.flags |= ~(LIPS_FLAG_0x01 | LIPS_FLAG_0x02);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x47AD98
|
2022-06-18 07:49:48 -07:00
|
|
|
static int lipsReadV1(LipsData* lipsData, File* stream)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
2022-06-11 12:33:43 -07:00
|
|
|
int sound;
|
2022-05-19 01:51:26 -07:00
|
|
|
int field_14;
|
2022-06-11 12:33:43 -07:00
|
|
|
int phonemes;
|
|
|
|
int markers;
|
|
|
|
|
|
|
|
if (fileReadInt32(stream, &(lipsData->version)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_4)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->flags)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(sound)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_10)) == -1) return -1;
|
2022-05-19 01:51:26 -07:00
|
|
|
if (fileReadInt32(stream, &(field_14)) == -1) return -1;
|
2022-06-11 12:33:43 -07:00
|
|
|
if (fileReadInt32(stream, &(phonemes)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_1C)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_20)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_24)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_28)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_2C)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(markers)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_34)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_38)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_3C)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_40)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_44)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_48)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(lipsData->field_4C)) == -1) return -1;
|
|
|
|
if (fileReadFixedLengthString(stream, lipsData->field_50, 8) == -1) return -1;
|
|
|
|
if (fileReadFixedLengthString(stream, lipsData->field_58, 4) == -1) return -1;
|
|
|
|
if (fileReadFixedLengthString(stream, lipsData->field_5C, 4) == -1) return -1;
|
|
|
|
if (fileReadFixedLengthString(stream, lipsData->field_60, 4) == -1) return -1;
|
|
|
|
if (fileReadFixedLengthString(stream, lipsData->field_64, 260) == -1) return -1;
|
|
|
|
|
|
|
|
// NOTE: Original code is different. For unknown reason it assigns values
|
|
|
|
// from file (integers) and treat them as pointers, which is obviously wrong
|
|
|
|
// is in this case.
|
|
|
|
lipsData->sound = NULL;
|
|
|
|
lipsData->field_14 = NULL;
|
|
|
|
lipsData->phonemes = NULL;
|
|
|
|
lipsData->markers = NULL;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// lips_load_file
|
|
|
|
// 0x47AFAC
|
|
|
|
int lipsLoad(const char* audioFileName, const char* headFileName)
|
|
|
|
{
|
|
|
|
char* sep;
|
|
|
|
int i;
|
|
|
|
char v60[16];
|
|
|
|
|
|
|
|
SpeechMarker* speech_marker;
|
|
|
|
SpeechMarker* prev_speech_marker;
|
|
|
|
|
|
|
|
char path[260];
|
|
|
|
strcpy(path, "SOUND\\SPEECH\\");
|
|
|
|
|
|
|
|
strcpy(_lips_subdir_name, headFileName);
|
|
|
|
|
|
|
|
strcat(path, _lips_subdir_name);
|
|
|
|
|
|
|
|
strcat(path, "\\");
|
|
|
|
|
|
|
|
sep = strchr(path, '.');
|
|
|
|
if (sep != NULL) {
|
|
|
|
*sep = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy(v60, audioFileName);
|
|
|
|
|
|
|
|
sep = strchr(v60, '.');
|
|
|
|
if (sep != NULL) {
|
|
|
|
*sep = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy(gLipsData.field_50, v60);
|
|
|
|
|
|
|
|
strcat(path, _lips_fix_string(gLipsData.field_50, sizeof(gLipsData.field_50)));
|
|
|
|
strcat(path, ".");
|
|
|
|
strcat(path, gLipsData.field_60);
|
|
|
|
|
|
|
|
lipsFree();
|
|
|
|
|
|
|
|
// FIXME: stream is not closed if any error is encountered during reading.
|
|
|
|
File* stream = fileOpen(path, "rb");
|
|
|
|
if (stream != NULL) {
|
|
|
|
if (fileReadInt32(stream, &(gLipsData.version)) == -1) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gLipsData.version == 1) {
|
|
|
|
debugPrint("\nLoading old save-file version (1)");
|
|
|
|
|
|
|
|
if (fileSeek(stream, 0, SEEK_SET) != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lipsReadV1(&gLipsData, stream) != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (gLipsData.version == 2) {
|
|
|
|
debugPrint("\nLoading current save-file version (2)");
|
|
|
|
|
|
|
|
if (fileReadInt32(stream, &(gLipsData.field_4)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(gLipsData.flags)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(gLipsData.field_10)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(gLipsData.field_1C)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(gLipsData.field_24)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(gLipsData.field_28)) == -1) return -1;
|
|
|
|
if (fileReadInt32(stream, &(gLipsData.field_2C)) == -1) return -1;
|
|
|
|
if (fileReadFixedLengthString(stream, gLipsData.field_50, 8) == -1) return -1;
|
|
|
|
if (fileReadFixedLengthString(stream, gLipsData.field_58, 4) == -1) return -1;
|
|
|
|
} else {
|
|
|
|
debugPrint("\nError: Lips file WRONG version: %s!", path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-21 08:22:03 -07:00
|
|
|
gLipsData.phonemes = (unsigned char*)internal_malloc(gLipsData.field_24);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (gLipsData.phonemes == NULL) {
|
|
|
|
debugPrint("Out of memory in lips_load_file.'\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream != NULL) {
|
|
|
|
for (i = 0; i < gLipsData.field_24; i++) {
|
|
|
|
if (fileReadUInt8(stream, &(gLipsData.phonemes[i])) == -1) {
|
|
|
|
debugPrint("lips_load_file: Error reading phoneme type.\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < gLipsData.field_24; i++) {
|
|
|
|
unsigned char phoneme = gLipsData.phonemes[i];
|
|
|
|
if (phoneme >= PHONEME_COUNT) {
|
|
|
|
debugPrint("\nLoad error: Speech phoneme %d is invalid (%d)!", i, phoneme);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-21 08:22:03 -07:00
|
|
|
gLipsData.markers = (SpeechMarker*)internal_malloc(sizeof(*speech_marker) * gLipsData.field_2C);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (gLipsData.markers == NULL) {
|
|
|
|
debugPrint("Out of memory in lips_load_file.'\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream != NULL) {
|
|
|
|
for (i = 0; i < gLipsData.field_2C; i++) {
|
|
|
|
speech_marker = &(gLipsData.markers[i]);
|
|
|
|
|
|
|
|
if (fileReadInt32(stream, &(speech_marker->marker)) == -1) break;
|
|
|
|
if (fileReadInt32(stream, &(speech_marker->position)) == -1) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i != gLipsData.field_2C) {
|
|
|
|
debugPrint("lips_load_file: Error reading marker type.");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
speech_marker = &(gLipsData.markers[0]);
|
|
|
|
|
|
|
|
if (speech_marker->marker != 1 && speech_marker->marker != 0) {
|
|
|
|
debugPrint("\nLoad error: Speech marker 0 is invalid (%d)!", speech_marker->marker);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (speech_marker->position != 0) {
|
|
|
|
debugPrint("Load error: Speech marker 0 has invalid position(%d)!", speech_marker->position);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 1; i < gLipsData.field_2C; i++) {
|
|
|
|
speech_marker = &(gLipsData.markers[i]);
|
|
|
|
prev_speech_marker = &(gLipsData.markers[i - 1]);
|
|
|
|
|
|
|
|
if (speech_marker->marker != 1 && speech_marker->marker != 0) {
|
|
|
|
debugPrint("\nLoad error: Speech marker %d is invalid (%d)!", i, speech_marker->marker);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (speech_marker->position < prev_speech_marker->position) {
|
|
|
|
debugPrint("Load error: Speech marker %d has invalid position(%d)!", i, speech_marker->position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream != NULL) {
|
|
|
|
fileClose(stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
gLipsData.field_38 = 0;
|
|
|
|
gLipsData.field_34 = 0;
|
|
|
|
gLipsData.field_48 = 0;
|
|
|
|
gLipsData.field_20 = 0;
|
|
|
|
gLipsData.field_3C = 50;
|
|
|
|
gLipsData.field_40 = 100;
|
|
|
|
|
|
|
|
if (gLipsData.version == 1) {
|
|
|
|
gLipsData.field_4 = 22528;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy(gLipsData.field_58, "ACM");
|
|
|
|
strcpy(gLipsData.field_5C, "TXT");
|
|
|
|
strcpy(gLipsData.field_60, "LIP");
|
|
|
|
|
|
|
|
_lips_make_speech();
|
|
|
|
|
|
|
|
_head_marker_current = 0;
|
|
|
|
gLipsCurrentPhoneme = gLipsData.phonemes[0];
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// lips_make_speech
|
|
|
|
// 0x47B5D0
|
2022-06-18 07:49:48 -07:00
|
|
|
static int _lips_make_speech()
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
|
|
|
if (gLipsData.field_14 != NULL) {
|
|
|
|
internal_free(gLipsData.field_14);
|
|
|
|
gLipsData.field_14 = NULL;
|
|
|
|
}
|
|
|
|
|
2022-05-28 02:34:49 -07:00
|
|
|
char path[COMPAT_MAX_PATH];
|
2022-05-19 01:51:26 -07:00
|
|
|
char* v1 = _lips_fix_string(gLipsData.field_50, sizeof(gLipsData.field_50));
|
|
|
|
sprintf(path, "%s%s\\%s.%s", "SOUND\\SPEECH\\", _lips_subdir_name, v1, "ACM");
|
|
|
|
|
|
|
|
if (gLipsData.sound != NULL) {
|
|
|
|
soundDelete(gLipsData.sound);
|
|
|
|
gLipsData.sound = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
gLipsData.sound = soundAllocate(1, 8);
|
|
|
|
if (gLipsData.sound == NULL) {
|
|
|
|
debugPrint("\nsoundAllocate falied in lips_make_speech!");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (soundSetFileIO(gLipsData.sound, audioOpen, audioClose, audioRead, NULL, audioSeek, NULL, audioGetSize)) {
|
|
|
|
debugPrint("Ack!");
|
|
|
|
debugPrint("Error!");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (soundLoad(gLipsData.sound, path)) {
|
|
|
|
soundDelete(gLipsData.sound);
|
|
|
|
gLipsData.sound = NULL;
|
|
|
|
|
|
|
|
debugPrint("lips_make_speech: soundLoad failed with path ");
|
|
|
|
debugPrint("%s -- file probably doesn't exist.\n", path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
gLipsData.field_34 = 8 * (gLipsData.field_1C / gLipsData.field_2C);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x47B730
|
|
|
|
int lipsFree()
|
|
|
|
{
|
|
|
|
if (gLipsData.field_14 != NULL) {
|
|
|
|
internal_free(gLipsData.field_14);
|
|
|
|
gLipsData.field_14 = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gLipsData.sound != NULL) {
|
|
|
|
_head_marker_current = 0;
|
|
|
|
|
|
|
|
soundStop(gLipsData.sound);
|
|
|
|
|
|
|
|
gLipsData.flags &= ~0x03;
|
|
|
|
|
|
|
|
soundDelete(gLipsData.sound);
|
|
|
|
|
|
|
|
gLipsData.sound = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gLipsData.phonemes != NULL) {
|
|
|
|
internal_free(gLipsData.phonemes);
|
|
|
|
gLipsData.phonemes = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gLipsData.markers != NULL) {
|
|
|
|
internal_free(gLipsData.markers);
|
|
|
|
gLipsData.markers = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|