fallout2-ce/src/lips.cc

475 lines
13 KiB
C++

#include "lips.h"
#include <stdio.h>
#include <string.h>
#include "audio.h"
#include "db.h"
#include "debug.h"
#include "game_sound.h"
#include "input.h"
#include "memory.h"
#include "platform_compat.h"
#include "sound.h"
#include "svga.h"
namespace fallout {
static char* _lips_fix_string(const char* fileName, size_t length);
static int lipsReadV1(LipsData* a1, File* stream);
static int _lips_make_speech();
// 0x519240
unsigned char gLipsCurrentPhoneme = 0;
// 0x519241
static unsigned char gLipsPreviousPhoneme = 0;
// 0x519244
static int _head_marker_current = 0;
// 0x519248
bool gLipsPhonemeChanged = true;
// 0x51924C
LipsData gLipsData = {
2,
22528,
0,
nullptr,
-1,
nullptr,
nullptr,
0,
0,
0,
0,
0,
nullptr,
0,
0,
50,
100,
0,
0,
0,
"TEST",
"VOC",
"TXT",
"LIP",
};
// 0x5193B4
static int _speechStartTime = 0;
// 0x613CA0
static char _lips_subdir_name[14];
// 0x613CAE
static char _tmp_str[50];
// 0x47AAC0
static char* _lips_fix_string(const char* fileName, size_t length)
{
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 = getTicks();
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
static int lipsReadV1(LipsData* lipsData, File* stream)
{
int sound;
int field_14;
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;
if (fileReadInt32(stream, &(field_14)) == -1) return -1;
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 = nullptr;
lipsData->field_14 = nullptr;
lipsData->phonemes = nullptr;
lipsData->markers = nullptr;
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 != nullptr) {
*sep = '\0';
}
strcpy(v60, audioFileName);
sep = strchr(v60, '.');
if (sep != nullptr) {
*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 != nullptr) {
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);
}
}
gLipsData.phonemes = (unsigned char*)internal_malloc(gLipsData.field_24);
if (gLipsData.phonemes == nullptr) {
debugPrint("Out of memory in lips_load_file.'\n");
return -1;
}
if (stream != nullptr) {
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);
}
}
}
gLipsData.markers = (SpeechMarker*)internal_malloc(sizeof(*speech_marker) * gLipsData.field_2C);
if (gLipsData.markers == nullptr) {
debugPrint("Out of memory in lips_load_file.'\n");
return -1;
}
if (stream != nullptr) {
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 != nullptr) {
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
static int _lips_make_speech()
{
if (gLipsData.field_14 != nullptr) {
internal_free(gLipsData.field_14);
gLipsData.field_14 = nullptr;
}
char path[COMPAT_MAX_PATH];
char* v1 = _lips_fix_string(gLipsData.field_50, sizeof(gLipsData.field_50));
snprintf(path, sizeof(path), "%s%s\\%s.%s", "SOUND\\SPEECH\\", _lips_subdir_name, v1, "ACM");
if (gLipsData.sound != nullptr) {
soundDelete(gLipsData.sound);
gLipsData.sound = nullptr;
}
gLipsData.sound = soundAllocate(SOUND_TYPE_MEMORY, SOUND_16BIT);
if (gLipsData.sound == nullptr) {
debugPrint("\nsoundAllocate falied in lips_make_speech!");
return -1;
}
if (soundSetFileIO(gLipsData.sound, audioOpen, audioClose, audioRead, nullptr, audioSeek, nullptr, audioGetSize)) {
debugPrint("Ack!");
debugPrint("Error!");
}
if (soundLoad(gLipsData.sound, path)) {
soundDelete(gLipsData.sound);
gLipsData.sound = nullptr;
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 != nullptr) {
internal_free(gLipsData.field_14);
gLipsData.field_14 = nullptr;
}
if (gLipsData.sound != nullptr) {
_head_marker_current = 0;
soundStop(gLipsData.sound);
gLipsData.flags &= ~0x03;
soundDelete(gLipsData.sound);
gLipsData.sound = nullptr;
}
if (gLipsData.phonemes != nullptr) {
internal_free(gLipsData.phonemes);
gLipsData.phonemes = nullptr;
}
if (gLipsData.markers != nullptr) {
internal_free(gLipsData.markers);
gLipsData.markers = nullptr;
}
return 0;
}
} // namespace fallout