#include "lips.h" #include #include #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