/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ags/shared/ac/audio_clip_type.h" #include "ags/shared/ac/game_setup_struct.h" #include "ags/shared/ac/old_game_setup_struct.h" #include "ags/shared/ac/words_dictionary.h" #include "ags/shared/ac/dynobj/script_audio_clip.h" #include "ags/shared/game/interactions.h" #include "ags/shared/util/aligned_stream.h" #include "ags/globals.h" namespace AGS3 { using namespace AGS::Shared; GameSetupStruct::GameSetupStruct() : filever(0) , roomCount(0) , roomNumbers(nullptr) , roomNames(nullptr) , scoreClipID(0) { memset(invinfo, 0, sizeof(invinfo)); memset(mcurs, 0, sizeof(mcurs)); memset(lipSyncFrameLetters, 0, sizeof(lipSyncFrameLetters)); memset(guid, 0, sizeof(guid)); memset(saveGameFileExtension, 0, sizeof(saveGameFileExtension)); memset(saveGameFolderName, 0, sizeof(saveGameFolderName)); } GameSetupStruct::~GameSetupStruct() { Free(); } void GameSetupStruct::Free() { GameSetupStructBase::Free(); intrChar.clear(); charScripts.clear(); numcharacters = 0; // TODO: find out if it really needs to begin with 1 here? for (size_t i = 1; i < (size_t)MAX_INV; i++) intrInv[i].reset(); invScripts.clear(); numinvitems = 0; for (int i = 0; i < roomCount; i++) delete roomNames[i]; delete[] roomNames; delete[] roomNumbers; roomCount = 0; audioClips.clear(); audioClipTypes.clear(); charProps.clear(); viewNames.clear(); } // Assigns font info parameters using legacy flags value read from the game data void SetFontInfoFromLegacyFlags(FontInfo &finfo, const uint8_t data) { finfo.Flags = (data >> 6) & 0xFF; finfo.SizePt = data & FFLG_LEGACY_SIZEMASK; } void AdjustFontInfoUsingFlags(FontInfo &finfo, const uint32_t flags) { finfo.Flags = flags; if ((flags & FFLG_SIZEMULTIPLIER) != 0) { finfo.SizeMultiplier = finfo.SizePt; finfo.SizePt = 0; } } ScriptAudioClip *GetAudioClipForOldStyleNumber(GameSetupStruct &game, bool is_music, int num) { String clip_name; if (is_music) clip_name.Format("aMusic%d", num); else clip_name.Format("aSound%d", num); for (size_t i = 0; i < _GP(game).audioClips.size(); ++i) { if (clip_name.CompareNoCase(_GP(game).audioClips[i].scriptName) == 0) return &_GP(game).audioClips[i]; } return nullptr; } //----------------------------------------------------------------------------- // Reading Part 1 void GameSetupStruct::read_savegame_info(Shared::Stream *in, GameDataVersion data_ver) { if (data_ver > kGameVersion_272) { // only 3.x in->Read(&guid[0], MAX_GUID_LENGTH); in->Read(&saveGameFileExtension[0], MAX_SG_EXT_LENGTH); in->Read(&saveGameFolderName[0], MAX_SG_FOLDER_LEN); } } void GameSetupStruct::read_font_infos(Shared::Stream *in, GameDataVersion data_ver) { fonts.resize(numfonts); if (data_ver < kGameVersion_350) { for (int i = 0; i < numfonts; ++i) SetFontInfoFromLegacyFlags(fonts[i], in->ReadInt8()); for (int i = 0; i < numfonts; ++i) fonts[i].Outline = in->ReadInt8(); // size of char if (data_ver < kGameVersion_341) return; for (int i = 0; i < numfonts; ++i) { fonts[i].YOffset = in->ReadInt32(); if (data_ver >= kGameVersion_341_2) fonts[i].LineSpacing = Math::Max(0, in->ReadInt32()); } } else { for (int i = 0; i < numfonts; ++i) { uint32_t flags = in->ReadInt32(); fonts[i].SizePt = in->ReadInt32(); fonts[i].Outline = in->ReadInt32(); fonts[i].YOffset = in->ReadInt32(); fonts[i].LineSpacing = Math::Max(0, in->ReadInt32()); AdjustFontInfoUsingFlags(fonts[i], flags); } } } void GameSetupStruct::ReadInvInfo_Aligned(Stream *in) { AlignedStream align_s(in, Shared::kAligned_Read); for (int iteratorCount = 0; iteratorCount < numinvitems; ++iteratorCount) { invinfo[iteratorCount].ReadFromFile(&align_s); align_s.Reset(); } } void GameSetupStruct::WriteInvInfo_Aligned(Stream *out) { AlignedStream align_s(out, Shared::kAligned_Write); for (int iteratorCount = 0; iteratorCount < numinvitems; ++iteratorCount) { invinfo[iteratorCount].WriteToFile(&align_s); align_s.Reset(); } } HGameFileError GameSetupStruct::read_cursors(Shared::Stream *in, GameDataVersion data_ver) { if (numcursors > MAX_CURSOR) return new MainGameFileError(kMGFErr_TooManyCursors, String::FromFormat("Count: %d, max: %d", numcursors, MAX_CURSOR)); ReadMouseCursors_Aligned(in); return HGameFileError::None(); } void GameSetupStruct::read_interaction_scripts(Shared::Stream *in, GameDataVersion data_ver) { _G(numGlobalVars) = 0; if (data_ver > kGameVersion_272) { // 3.x charScripts.resize(numcharacters); invScripts.resize(numinvitems); for (size_t i = 0; i < (size_t)numcharacters; ++i) charScripts[i].reset(InteractionScripts::CreateFromStream(in)); // NOTE: new inventory items' events are loaded starting from 1 for some reason for (size_t i = 1; i < (size_t)numinvitems; ++i) invScripts[i].reset(InteractionScripts::CreateFromStream(in)); } else { // 2.x intrChar.resize(numcharacters); for (size_t i = 0; i < (size_t)numcharacters; ++i) intrChar[i].reset(Interaction::CreateFromStream(in)); for (size_t i = 0; i < (size_t)numinvitems; ++i) intrInv[i].reset(Interaction::CreateFromStream(in)); _G(numGlobalVars) = in->ReadInt32(); for (size_t i = 0; i < (size_t)_G(numGlobalVars); ++i) _G(globalvars)[i].Read(in); } } void GameSetupStruct::read_words_dictionary(Shared::Stream *in) { if (load_dictionary) { dict = new WordsDictionary(); read_dictionary(dict, in); } } void GameSetupStruct::ReadMouseCursors_Aligned(Stream *in) { AlignedStream align_s(in, Shared::kAligned_Read); for (int iteratorCount = 0; iteratorCount < numcursors; ++iteratorCount) { mcurs[iteratorCount].ReadFromFile(&align_s); align_s.Reset(); } } void GameSetupStruct::WriteMouseCursors_Aligned(Stream *out) { AlignedStream align_s(out, Shared::kAligned_Write); for (int iteratorCount = 0; iteratorCount < numcursors; ++iteratorCount) { mcurs[iteratorCount].WriteToFile(&align_s); align_s.Reset(); } } //----------------------------------------------------------------------------- // Reading Part 2 void GameSetupStruct::read_characters(Shared::Stream *in, GameDataVersion data_ver) { chars = new CharacterInfo[numcharacters + 5]; // TODO: why +5, is this really needed? ReadCharacters_Aligned(in); } void GameSetupStruct::read_lipsync(Shared::Stream *in, GameDataVersion data_ver) { if (data_ver >= kGameVersion_254) // lip syncing was introduced in 2.54 in->ReadArray(&lipSyncFrameLetters[0][0], MAXLIPSYNCFRAMES, 50); } void GameSetupStruct::read_messages(Shared::Stream *in, GameDataVersion data_ver) { for (int ee = 0; ee < MAXGLOBALMES; ee++) { if (!load_messages[ee]) continue; messages[ee] = new char[GLOBALMESLENGTH]; if (data_ver < kGameVersion_261) { // Global messages are not encrypted on < 2.61 char *nextchar = messages[ee]; // TODO: probably this is same as fgetstring while (1) { *nextchar = in->ReadInt8(); if (*nextchar == 0) break; nextchar++; } } else read_string_decrypt(in, messages[ee], GLOBALMESLENGTH); } delete[] load_messages; load_messages = nullptr; } void GameSetupStruct::ReadCharacters_Aligned(Stream *in) { AlignedStream align_s(in, Shared::kAligned_Read); for (int iteratorCount = 0; iteratorCount < numcharacters; ++iteratorCount) { chars[iteratorCount].ReadFromFile(&align_s); align_s.Reset(); } } void GameSetupStruct::WriteCharacters_Aligned(Stream *out) { AlignedStream align_s(out, Shared::kAligned_Write); for (int iteratorCount = 0; iteratorCount < numcharacters; ++iteratorCount) { chars[iteratorCount].WriteToFile(&align_s); align_s.Reset(); } } //----------------------------------------------------------------------------- // Reading Part 3 HGameFileError GameSetupStruct::read_customprops(Shared::Stream *in, GameDataVersion data_ver) { dialogScriptNames.resize(numdialog); viewNames.resize(numviews); if (data_ver >= kGameVersion_260) { // >= 2.60 if (Properties::ReadSchema(propSchema, in) != kPropertyErr_NoError) return new MainGameFileError(kMGFErr_InvalidPropertySchema); int errors = 0; charProps.resize(numcharacters); for (int i = 0; i < numcharacters; ++i) { errors += Properties::ReadValues(charProps[i], in); } for (int i = 0; i < numinvitems; ++i) { errors += Properties::ReadValues(invProps[i], in); } if (errors > 0) return new MainGameFileError(kMGFErr_InvalidPropertyValues); for (int i = 0; i < numviews; ++i) viewNames[i] = String::FromStream(in); if (data_ver >= kGameVersion_270) { for (int i = 0; i < numinvitems; ++i) invScriptNames[i] = String::FromStream(in); if (data_ver >= kGameVersion_272) { for (int i = 0; i < numdialog; ++i) dialogScriptNames[i] = String::FromStream(in); } } } return HGameFileError::None(); } HGameFileError GameSetupStruct::read_audio(Shared::Stream *in, GameDataVersion data_ver) { if (data_ver >= kGameVersion_320) { size_t audiotype_count = in->ReadInt32(); audioClipTypes.resize(audiotype_count); for (size_t i = 0; i < audiotype_count; ++i) { audioClipTypes[i].ReadFromFile(in); } size_t audioclip_count = in->ReadInt32(); audioClips.resize(audioclip_count); ReadAudioClips_Aligned(in, audioclip_count); scoreClipID = in->ReadInt32(); } return HGameFileError::None(); } // Temporarily copied this from acruntim.h; // it is unknown if this should be defined for all solution, or only runtime #define STD_BUFFER_SIZE 3000 void GameSetupStruct::read_room_names(Stream *in, GameDataVersion data_ver) { if ((data_ver >= kGameVersion_301) && (options[OPT_DEBUGMODE] != 0)) { roomCount = in->ReadInt32(); roomNumbers = new int[roomCount]; roomNames = new char *[roomCount]; String pexbuf; for (int bb = 0; bb < roomCount; bb++) { roomNumbers[bb] = in->ReadInt32(); pexbuf.Read(in, STD_BUFFER_SIZE); roomNames[bb] = new char[pexbuf.GetLength() + 1]; strcpy(roomNames[bb], pexbuf.GetCStr()); } } else { roomCount = 0; } } void GameSetupStruct::ReadAudioClips_Aligned(Shared::Stream *in, size_t count) { AlignedStream align_s(in, Shared::kAligned_Read); for (size_t i = 0; i < count; ++i) { audioClips[i].ReadFromFile(&align_s); align_s.Reset(); } } void GameSetupStruct::ReadFromSaveGame_v321(Stream *in, char *gswas, ccScript *compsc, CharacterInfo *chwas, WordsDictionary *olddict, char **mesbk) { int bb; ReadInvInfo_Aligned(in); ReadMouseCursors_Aligned(in); if (_G(loaded_game_file_version) <= kGameVersion_272) { for (bb = 0; bb < numinvitems; bb++) intrInv[bb]->ReadTimesRunFromSave_v321(in); for (bb = 0; bb < numcharacters; bb++) intrChar[bb]->ReadTimesRunFromSave_v321(in); } // restore pointer members globalscript = gswas; compiled_script = compsc; chars = chwas; dict = olddict; for (int vv = 0; vv < MAXGLOBALMES; vv++) messages[vv] = mesbk[vv]; in->ReadArrayOfInt32(&options[0], OPT_HIGHESTOPTION_321 + 1); options[OPT_LIPSYNCTEXT] = in->ReadByte(); ReadCharacters_Aligned(in); } //============================================================================= void ConvertOldGameStruct(OldGameSetupStruct *ogss, GameSetupStruct *gss) { strcpy(gss->gamename, ogss->gamename); for (int i = 0; i < 20; i++) gss->options[i] = ogss->options[i]; memcpy(&gss->paluses[0], &ogss->paluses[0], 256); memcpy(&gss->defpal[0], &ogss->defpal[0], 256 * sizeof(RGB)); gss->numviews = ogss->numviews; gss->numcharacters = ogss->numcharacters; gss->playercharacter = ogss->playercharacter; gss->totalscore = ogss->totalscore; gss->numinvitems = ogss->numinvitems; gss->numdialog = ogss->numdialog; gss->numdlgmessage = ogss->numdlgmessage; gss->numfonts = ogss->numfonts; gss->color_depth = ogss->color_depth; gss->target_win = ogss->target_win; gss->dialog_bullet = ogss->dialog_bullet; gss->hotdot = ogss->hotdot; gss->hotdotouter = ogss->hotdotouter; gss->uniqueid = ogss->uniqueid; gss->numgui = ogss->numgui; for (int i = 0; i < 10; ++i) { SetFontInfoFromLegacyFlags(gss->fonts[i], ogss->fontflags[i]); gss->fonts[i].Outline = ogss->fontoutline[i]; } for (int i = 0; i < LEGACY_MAX_SPRITES_V25; ++i) { gss->SpriteInfos[i].Flags = ogss->spriteflags[i]; } memcpy(&gss->invinfo[0], &ogss->invinfo[0], 100 * sizeof(InventoryItemInfo)); memcpy(&gss->mcurs[0], &ogss->mcurs[0], 10 * sizeof(MouseCursor)); for (int i = 0; i < MAXGLOBALMES; i++) gss->messages[i] = ogss->messages[i]; gss->dict = ogss->dict; gss->globalscript = ogss->globalscript; gss->chars = nullptr; //ogss->chars; gss->compiled_script = ogss->compiled_script; gss->numcursors = 10; } void GameSetupStruct::ReadFromSavegame(Stream *in) { // of GameSetupStruct in->ReadArrayOfInt32(options, OPT_HIGHESTOPTION_321 + 1); options[OPT_LIPSYNCTEXT] = in->ReadInt32(); // of GameSetupStructBase playercharacter = in->ReadInt32(); dialog_bullet = in->ReadInt32(); hotdot = in->ReadInt16(); hotdotouter = in->ReadInt16(); invhotdotsprite = in->ReadInt32(); default_lipsync_frame = in->ReadInt32(); } void GameSetupStruct::WriteForSavegame(Stream *out) { // of GameSetupStruct out->WriteArrayOfInt32(options, OPT_HIGHESTOPTION_321 + 1); out->WriteInt32(options[OPT_LIPSYNCTEXT]); // of GameSetupStructBase out->WriteInt32(playercharacter); out->WriteInt32(dialog_bullet); out->WriteInt16(hotdot); out->WriteInt16(hotdotouter); out->WriteInt32(invhotdotsprite); out->WriteInt32(default_lipsync_frame); } } // namespace AGS3