diff --git a/config/G2ME01/splits.txt b/config/G2ME01/splits.txt index e7a4f33..6b2b8f0 100644 --- a/config/G2ME01/splits.txt +++ b/config/G2ME01/splits.txt @@ -78,6 +78,12 @@ MetroidPrime/ScriptObjects/CScriptAreaProperties.cpp: MetroidPrime/CHealthInfo.cpp: .text start:0x8014206C end:0x80142188 +MetroidPrime/ScriptObjects/CScriptStreamedMusic.cpp: + .text start:0x8015CD4C end:0x8015DDE8 + .rodata start:0x803A95F8 end:0x803A9620 + .data start:0x803B5430 end:0x803B5450 + .sdata2 start:0x8041C460 end:0x8041C468 + MetroidPrime/Player/CGameOptions.cpp: .text start:0x80160C20 end:0x80161D04 diff --git a/config/G2ME01/symbols.txt b/config/G2ME01/symbols.txt index 3f2b062..e2d6366 100644 --- a/config/G2ME01/symbols.txt +++ b/config/G2ME01/symbols.txt @@ -5798,29 +5798,29 @@ fn_8015C990 = .text:0x8015C990; // type:function size:0x19C align:4 fn_8015CB2C = .text:0x8015CB2C; // type:function size:0x80 align:4 fn_8015CBAC = .text:0x8015CBAC; // type:function size:0xCC align:4 fn_8015CC78 = .text:0x8015CC78; // type:function size:0xD4 -fn_8015CD4C = .text:0x8015CD4C; // type:function size:0x94 align:4 -fn_8015CDE0 = .text:0x8015CDE0; // type:function size:0x2D8 align:4 -fn_8015D0B8 = .text:0x8015D0B8; // type:function size:0x190 -fn_8015D248 = .text:0x8015D248; // type:function size:0x60 -fn_8015D2A8 = .text:0x8015D2A8; // type:function size:0xD8 -fn_8015D380 = .text:0x8015D380; // type:function size:0xAC align:4 -fn_8015D42C = .text:0x8015D42C; // type:function size:0x104 -fn_8015D530 = .text:0x8015D530; // type:function size:0x150 align:4 -fn_8015D680 = .text:0x8015D680; // type:function size:0x120 align:4 -fn_8015D7A0 = .text:0x8015D7A0; // type:function size:0x74 -fn_8015D814 = .text:0x8015D814; // type:function size:0x84 align:4 -fn_8015D898 = .text:0x8015D898; // type:function size:0x3C align:4 -fn_8015D8D4 = .text:0x8015D8D4; // type:function size:0x70 align:4 -fn_8015D944 = .text:0x8015D944; // type:function size:0x24 align:4 -fn_8015D968 = .text:0x8015D968; // type:function size:0x60 -fn_8015D9C8 = .text:0x8015D9C8; // type:function size:0x34 align:4 -fn_8015D9FC = .text:0x8015D9FC; // type:function size:0x34 -fn_8015DA30 = .text:0x8015DA30; // type:function size:0x7C align:4 -fn_8015DAAC = .text:0x8015DAAC; // type:function size:0x190 align:4 -fn_8015DC3C = .text:0x8015DC3C; // type:function size:0xB4 +__dt__20CScriptStreamedMusicFv = .text:0x8015CD4C; // type:function size:0x94 align:4 +LoadStreamedAudio__FR13CStateManagerR12CInputStreamRC11CEntityInfo = .text:0x8015CDE0; // type:function size:0x2D8 align:4 +SetStereoPair__20CScriptStreamedMusicFv = .text:0x8015D0B8; // type:function size:0x190 +__pl__4rstlFRCQ24rstl66basic_string,Q24rstl17rmemory_allocator>c = .text:0x8015D248; // type:function size:0x60 +__ct,Q24rstl17rmemory_allocator>,Q24rstl17rmemory_allocator>>__Q24rstl66basic_string,Q24rstl17rmemory_allocator>FQ24rstl127const_linear_iterator,Q24rstl17rmemory_allocator>,Q24rstl17rmemory_allocator>Q24rstl127const_linear_iterator,Q24rstl17rmemory_allocator>,Q24rstl17rmemory_allocator>RCQ24rstl17rmemory_allocator = .text:0x8015D2A8; // type:function size:0xD8 +find__Q24rstl66basic_string,Q24rstl17rmemory_allocator>CFci = .text:0x8015D380; // type:function size:0xAC align:4 +internal_search,Q24rstl17rmemory_allocator>,Q24rstl17rmemory_allocator>,PCc>__Q24rstl66basic_string,Q24rstl17rmemory_allocator>FQ24rstl127const_linear_iterator,Q24rstl17rmemory_allocator>,Q24rstl17rmemory_allocator>Q24rstl127const_linear_iterator,Q24rstl17rmemory_allocator>,Q24rstl17rmemory_allocator>PCcPCc = .text:0x8015D42C; // type:function size:0x104 +TweakOverride__20CScriptStreamedMusicFR13CStateManager = .text:0x8015D530; // type:function size:0x150 align:4 +PreloadMemoryAudio__20CScriptStreamedMusicFv = .text:0x8015D680; // type:function size:0x120 align:4 +substr__Q24rstl66basic_string,Q24rstl17rmemory_allocator>CFii = .text:0x8015D7A0; // type:function size:0x74 +Think__20CScriptStreamedMusicFfR13CStateManager = .text:0x8015D814; // type:function size:0x84 align:4 +StopStream__20CScriptStreamedMusicFv = .text:0x8015D898; // type:function size:0x3C align:4 +StartStream__20CScriptStreamedMusicFv = .text:0x8015D8D4; // type:function size:0x70 align:4 +StopNonDsp__20CScriptStreamedMusicFv = .text:0x8015D944; // type:function size:0x24 align:4 +PlayNonDsp__20CScriptStreamedMusicFv = .text:0x8015D968; // type:function size:0x60 +Stop__20CScriptStreamedMusicFv = .text:0x8015D9C8; // type:function size:0x34 align:4 +PlayAudio__20CScriptStreamedMusicFv = .text:0x8015D9FC; // type:function size:0x34 +Play__20CScriptStreamedMusicFR13CStateManager = .text:0x8015DA30; // type:function size:0x7C align:4 +AcceptScriptMsg__20CScriptStreamedMusicFR13CStateManagerRC10CScriptMsg = .text:0x8015DAAC; // type:function size:0x190 align:4 +IsDSPFile__20CScriptStreamedMusicFRCQ24rstl66basic_string,Q24rstl17rmemory_allocator> = .text:0x8015DC3C; // type:function size:0xB4 fn_8015DCF0 = .text:0x8015DCF0; // type:function size:0x4 -fn_8015DCF4 = .text:0x8015DCF4; // type:function size:0xE4 align:4 -fn_8015DDD8 = .text:0x8015DDD8; // type:function size:0x10 +__ct__20CScriptStreamedMusicF9TUniqueIdRC11CEntityInfoRCQ24rstl66basic_string,Q24rstl17rmemory_allocator>RCQ24rstl66basic_string,Q24rstl17rmemory_allocator>bffUibb = .text:0x8015DCF4; // type:function size:0xE4 align:4 +IsOneShot__20CScriptStreamedMusicFb = .text:0x8015DDD8; // type:function size:0x10 fn_8015DDE8 = .text:0x8015DDE8; // type:function size:0x120 align:4 fn_8015DF08 = .text:0x8015DF08; // type:function size:0x4 align:4 fn_8015DF0C = .text:0x8015DF0C; // type:function size:0x32C @@ -18148,7 +18148,7 @@ lbl_803B53A0 = .data:0x803B53A0; // type:object size:0x40 lbl_803B53E0 = .data:0x803B53E0; // type:object size:0x20 lbl_803B5400 = .data:0x803B5400; // type:object size:0x10 lbl_803B5410 = .data:0x803B5410; // type:object size:0x20 -lbl_803B5430 = .data:0x803B5430; // type:object size:0x20 +__vt__20CScriptStreamedMusic = .data:0x803B5430; // type:object size:0x20 lbl_803B5450 = .data:0x803B5450; // type:object size:0x18 lbl_803B5468 = .data:0x803B5468; // type:object size:0x98 lbl_803B5500 = .data:0x803B5500; // type:object size:0x80 diff --git a/configure.py b/configure.py index a59e736..962e22d 100644 --- a/configure.py +++ b/configure.py @@ -342,6 +342,7 @@ def MatchingFor(*versions): Object(NonMatching, "MetroidPrime/CHealthInfo.cpp"), Object(NonMatching, "MetroidPrime/Player/CGameOptions.cpp"), Object(NonMatching, "MetroidPrime/ScriptObjects/CScriptSequenceTimer.cpp"), + Object(NonMatching, "MetroidPrime/ScriptObjects/CScriptStreamedMusic.cpp"), Object(NonMatching, "MetroidPrime/ScriptLoaderRel.cpp"), Object(NonMatching, "MetroidPrime/ScriptLoader.cpp"), Object(NonMatching, "MetroidPrime/CAxisAngle.cpp"), diff --git a/include/Kyoto/Basics/CCast.hpp b/include/Kyoto/Basics/CCast.hpp index 41f4b41..1dd0e2c 100644 --- a/include/Kyoto/Basics/CCast.hpp +++ b/include/Kyoto/Basics/CCast.hpp @@ -17,6 +17,16 @@ inline uchar ToUint8(register float in) { return *ptr; } +inline char ToInt8(register float in) { + char a; + register char* ptr = &a; + + asm { + psq_st in, 0(ptr), 1, OS_FASTCAST_S8 + } + return *ptr; +} + inline uchar ToUint8(int c) { return static_cast(c); } inline float ToReal32(register const uchar& in) { @@ -52,6 +62,7 @@ inline float LtoF(int in) { return static_cast< float >(in); } #else inline uchar ToUint8(float in) { return static_cast< uchar >(in); } +inline char ToInt8(float in) { return static_cast< char >(in); } inline uchar ToUint8(int c) { return static_cast(c); } inline float ToReal32(uchar in) { return static_cast< float >(in); } inline short FtoS(float in) { return static_cast< short >(in); } diff --git a/include/MetroidPrime/CActor.hpp b/include/MetroidPrime/CActor.hpp index 5d88c8f..f0ef7e9 100644 --- a/include/MetroidPrime/CActor.hpp +++ b/include/MetroidPrime/CActor.hpp @@ -61,6 +61,7 @@ class CActor : public CEntity { void AcceptScriptMsg(CStateManager& mgr, const CScriptMsg&) override; void SetActive(const bool active) override; + virtual void UnkVtable20(); // G2ME01 slot +0x20; original name unknown virtual void PreRender(CStateManager&, const CFrustumPlanes&); virtual void AddToRenderer(const CStateManager&) const; virtual void Render(const CStateManager&) const; @@ -88,13 +89,14 @@ class CActor : public CEntity { virtual CAABox GetSortingBounds(const CStateManager&) const; virtual void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt); + virtual CScannableObjectInfo* GetScannableObjectInfo() const; + virtual void ProcessSoundEvent(int sfxId, float weight, int flags, float fallOff, float maxDist, + uchar minVol, uchar maxVol, const CVector3f& toListener, + const CVector3f& position, int aid, CStateManager& mgr, + bool translateId); CAdvancementDeltas UpdateAnimation(float dt, CStateManager& mgr, bool advTree); - void ProcessSoundEvent(int sfxId, float weight, int flags, float fallOff, float maxDist, - uchar minVol, uchar maxVol, const CVector3f& toListener, - const CVector3f& position, int aid, CStateManager& mgr, bool translateId); - void UpdateSfxEmitters(); void RemoveEmitter(); void SetModelData(const CModelData& modelData, CStateManager& mgr); @@ -125,7 +127,6 @@ class CActor : public CEntity { void SetActorLights(rstl::auto_ptr< CActorLights > lights); void SetInFluid(bool b, TUniqueId uid); - CScannableObjectInfo* GetScannableObjectInfo() const; void MoveScannableObjectInfoToActor(CActor* actor, CStateManager& mgr); /// ???? diff --git a/include/MetroidPrime/CEntity.hpp b/include/MetroidPrime/CEntity.hpp index c50fc4f..664273f 100644 --- a/include/MetroidPrime/CEntity.hpp +++ b/include/MetroidPrime/CEntity.hpp @@ -17,7 +17,7 @@ class CEntity { virtual void PreThink(float dt, CStateManager& mgr); virtual void Think(float dt, CStateManager& mgr); virtual void AcceptScriptMsg(CStateManager& mgr, const CScriptMsg&); - virtual void SendActive(CStateManager& mgr, bool active); + void SendActive(CStateManager& mgr, bool active); virtual void SetActive(const bool active); CEntity(TUniqueId id, const CEntityInfo& info, const rstl::string& name, uint inGrave); @@ -34,6 +34,7 @@ class CEntity { TAreaId GetCurrentAreaId() const { return m_areaId; } const bool GetActive() const { return m_active; } bool IsScriptingBlocked() const { return m_scriptingBlocked; } + bool GetEditorFlag2() const { return m_entityUnknown; } // might be fake? rstl::vector< SConnection >& ConnectionList() { return m_conns; } diff --git a/include/MetroidPrime/CEntityInfo.hpp b/include/MetroidPrime/CEntityInfo.hpp index 362408a..2665a58 100644 --- a/include/MetroidPrime/CEntityInfo.hpp +++ b/include/MetroidPrime/CEntityInfo.hpp @@ -60,6 +60,7 @@ enum EScriptObjectMessage { kSM_Start = 0x53545254, kSM_Stop = 0x53544f50, kSM_Play = 0x504c4159, + kSM_Load = 0x4c4f4144, kSM_Activate = 0x41435456, kSM_Deactivate = 0x44435456, kSM_ToggleActive = 0x54435456, diff --git a/include/MetroidPrime/CGameArea.hpp b/include/MetroidPrime/CGameArea.hpp index 31ac738..1e03b75 100644 --- a/include/MetroidPrime/CGameArea.hpp +++ b/include/MetroidPrime/CGameArea.hpp @@ -1,6 +1,8 @@ #ifndef _CGAMEAREA #define _CGAMEAREA +#include "Kyoto/SObjectTag.hpp" + class CScriptAreaProperties; class CGameArea { public: @@ -15,6 +17,7 @@ class CGameArea { }; int GetPhase() const { return m_phase; } + CAssetId GetAreaAssetId() const { return m_areaAssetId; } void SetAreaAttributes(CScriptAreaProperties*); bool IsLoaded() const { return m_phase == 0x10; } @@ -23,7 +26,9 @@ class CGameArea { EOcclusionState GetOcclusionState() const { return m_postConstructed->m_occlusionState; } private: - char pad1[0xf4]; + char pad1[0x54]; + CAssetId m_areaAssetId; // 0x54 + char pad2[0x9c]; int m_phase; // 0xf4; CGameArea* m_next; // 0xf8 CGameArea* m_prev; // 0xfc diff --git a/include/MetroidPrime/CInGameTweakManager.hpp b/include/MetroidPrime/CInGameTweakManager.hpp new file mode 100644 index 0000000..1a8aba5 --- /dev/null +++ b/include/MetroidPrime/CInGameTweakManager.hpp @@ -0,0 +1,45 @@ +#ifndef _CINGAMETWEAKMANAGER +#define _CINGAMETWEAKMANAGER + +#include "Kyoto/SObjectTag.hpp" +#include "rstl/string.hpp" + +class CTweakValue { +public: + struct Audio { + float GetFadeIn() const { return x0_fadeIn; } + float GetFadeOut() const { return x4_fadeOut; } + float GetVolume() const { return x8_volume; } + const rstl::string& GetFileName() const { return xc_fileName; } + + private: + float x0_fadeIn; + float x4_fadeOut; + float x8_volume; + rstl::string xc_fileName; + CAssetId x1c_resourceId; + }; + + const Audio& GetAudio() const { return x24_audio; } + +private: + uint x0_type; + rstl::string x4_key; + rstl::string x14_text; + Audio x24_audio; + uint x44_value; +}; + +NESTED_CHECK_SIZEOF(CTweakValue, Audio, 0x20) +CHECK_SIZEOF(CTweakValue, 0x48) + +class CInGameTweakManager { +public: + bool HasTweakValue(const rstl::string& key) const; + const CTweakValue* GetTweakValue(const rstl::string& key) const; + static rstl::string GetIdentifierForMusicEvent(CAssetId areaId, const rstl::string& name); +}; + +extern CInGameTweakManager* gpTweakManager; + +#endif // _CINGAMETWEAKMANAGER diff --git a/include/MetroidPrime/ScriptObjects/CScriptStreamedMusic.hpp b/include/MetroidPrime/ScriptObjects/CScriptStreamedMusic.hpp new file mode 100644 index 0000000..08a4052 --- /dev/null +++ b/include/MetroidPrime/ScriptObjects/CScriptStreamedMusic.hpp @@ -0,0 +1,47 @@ +#ifndef _CSCRIPTSTREAMEDMUSIC +#define _CSCRIPTSTREAMEDMUSIC + +#include "Kyoto/Streams/CFilePreload.hpp" +#include "MetroidPrime/CEntity.hpp" + +#include "rstl/optional_object.hpp" + +class CScriptStreamedMusic : public CEntity { +public: + CScriptStreamedMusic(TUniqueId id, const CEntityInfo& info, const rstl::string& name, + const rstl::string& fileName, bool noStopOnDeactivate, float fadeIn, + float fadeOut, uint volume, bool loop, bool music); + ~CScriptStreamedMusic() override; + CEntity* TypesMatch(int typeId) const override; + void Think(float dt, CStateManager& mgr) override; + void AcceptScriptMsg(CStateManager& mgr, const CScriptMsg& msg) override; + void PlayAudio(); + + static bool IsDSPFile(const rstl::string& fileName); + +private: + static int IsOneShot(bool loop); + void StartStream(); + void StopStream(); + void StopNonDsp(); + void PlayNonDsp(); + void Play(CStateManager& mgr); + void Stop(); + void TweakOverride(CStateManager& mgr); + void PreloadMemoryAudio(); + void SetStereoPair(); + + rstl::string x24_fileName; + bool x34_noStopOnDeactivate : 1; + bool x34_fileIsDsp : 1; + bool x34_loop : 1; + bool x34_music : 1; + bool x34_preloadPending : 1; + float x38_fadeIn; + float x3c_fadeOut; + uint x40_volume; + rstl::optional_object< CFilePreload > x44_preload; +}; +CHECK_SIZEOF(CScriptStreamedMusic, 0x4C) + +#endif // _CSCRIPTSTREAMEDMUSIC diff --git a/include/dolphin/os/OSFastCast.h b/include/dolphin/os/OSFastCast.h index b6ca50d..c64b5f9 100644 --- a/include/dolphin/os/OSFastCast.h +++ b/include/dolphin/os/OSFastCast.h @@ -13,6 +13,7 @@ extern "C" { #define OS_FASTCAST_U8 2 #define OS_FASTCAST_U16 3 +#define OS_FASTCAST_S8 4 #define OS_FASTCAST_S16 5 // clang-format off static inline void OSInitFastCast(void) { diff --git a/src/MetroidPrime/ScriptObjects/CScriptStreamedMusic.cpp b/src/MetroidPrime/ScriptObjects/CScriptStreamedMusic.cpp new file mode 100644 index 0000000..e09de24 --- /dev/null +++ b/src/MetroidPrime/ScriptObjects/CScriptStreamedMusic.cpp @@ -0,0 +1,330 @@ +#include "MetroidPrime/ScriptObjects/CScriptStreamedMusic.hpp" + +#include "Kyoto/Audio/CStreamAudioManager.hpp" +#include "Kyoto/Basics/CCast.hpp" +#include "Kyoto/CDvdFile.hpp" +#include "MetroidPrime/CInGameTweakManager.hpp" +#include "MetroidPrime/CMain.hpp" +#include "MetroidPrime/CStateManager.hpp" +#include "MetroidPrime/CWorld.hpp" +#include "MetroidPrime/ScriptLoader.hpp" +#include "MetroidPrime/ScriptLoader/SLdrEditorProperties.hpp" +#include "rstl/StringExtras.hpp" + +#include + +extern "C" void fn_8015DCF0(CScriptStreamedMusic*); + +CScriptStreamedMusic::CScriptStreamedMusic(TUniqueId id, const CEntityInfo& info, + const rstl::string& name, const rstl::string& fileName, + bool noStopOnDeactivate, float fadeIn, float fadeOut, + uint volume, bool loop, bool music) +: CEntity(id, info, name, 0) +, x24_fileName(fileName) +, x34_noStopOnDeactivate(noStopOnDeactivate) +, x34_fileIsDsp(IsDSPFile(fileName)) +, x34_loop(loop) +, x34_music(music) +, x34_preloadPending(false) +, x38_fadeIn(fadeIn) +, x3c_fadeOut(fadeOut) +, x40_volume(volume) +, x44_preload() { + fn_8015DCF0(this); +} + +extern "C" void fn_8015DCF0(CScriptStreamedMusic*) {} + +CScriptStreamedMusic::~CScriptStreamedMusic() {} + +CEntity* CScriptStreamedMusic::TypesMatch(int typeId) const { + return typeId == kET_ScriptStreamedMusic ? const_cast< CScriptStreamedMusic* >(this) + : CEntity::TypesMatch(typeId); +} + +bool CScriptStreamedMusic::IsDSPFile(const rstl::string& fileName) { + return CStringExtras::CompareCaseInsensitive(fileName, rstl::string_l("sw")) == 0 || + CStringExtras::IndexOfSubstring(fileName, rstl::string_l(".dsp")) != -1; +} + +int CScriptStreamedMusic::IsOneShot(bool loop) { return loop ? 0 : 1; } + +void CScriptStreamedMusic::Think(float dt, CStateManager& mgr) { + if (x34_preloadPending && x44_preload->IsReady()) { + SendScriptMsgs(static_cast< EScriptObjectState >(0x41525256), mgr, kInvalidUniqueId, kSM_None); + x34_preloadPending = false; + } +} + +void CScriptStreamedMusic::StartStream() { + if (!x44_preload || x44_preload->IsReady()) { + CStreamAudioManager::Start(IsOneShot(x34_loop), x24_fileName, static_cast< uchar >(x40_volume), + x34_music, x38_fadeIn, x3c_fadeOut); + } +} + +void CScriptStreamedMusic::StopStream() { + CStreamAudioManager::Stop(IsOneShot(x34_loop), x24_fileName); +} + +namespace rstl { +// Keep the iterator-range constructor's distance calculation inline in this TU. +template <> +inline long distance< string::const_iterator >(string::const_iterator first, + string::const_iterator last) { + return last - first; +} +} // namespace rstl + +template <> +template <> +rstl::basic_string< char >::basic_string(rstl::basic_string< char >::const_iterator first, + rstl::basic_string< char >::const_iterator last, + const rstl::rmemory_allocator&) { + const int len = rstl::distance(first, last); + internal_allocate(len + 1); + int i = 0; + for (const_iterator it = first; it != last; it = it + 1, ++i) { + const_cast< char& >(x0_ptr[i]) = *it; + } + const_cast< char& >(x0_ptr[i]) = char_traits< char >::eos(); + x8_size = len; +} + +template <> +rstl::string rstl::basic_string< char >::substr(int pos, int count) const { + const pair< const_iterator, const_iterator > range = range_iterator(pos, count); + return basic_string(range.first, range.second); +} + +void CScriptStreamedMusic::PreloadMemoryAudio() { + if (x44_preload) { + x34_preloadPending = true; + return; + } + const char* fileName = x24_fileName.data(); + if (strncmp(fileName, "mem:", strlen("mem:")) == 0) { + const rstl::string path = x24_fileName.substr(4); + if (CDvdFile::FileExists(path.data())) { + x44_preload = CFilePreload(path); + x34_preloadPending = true; + } + } +} + +void CScriptStreamedMusic::TweakOverride(CStateManager& mgr) { + const rstl::string key = CInGameTweakManager::GetIdentifierForMusicEvent( + mgr.GetWorld()->GetAreaAlways(GetCurrentAreaId()).GetAreaAssetId(), rstl::string_l("")); + if (gpTweakManager->HasTweakValue(key)) { + const CTweakValue::Audio& audio = gpTweakManager->GetTweakValue(key)->GetAudio(); + const rstl::string fileName(audio.GetFileName()); + const float fadeIn = audio.GetFadeIn(); + const char volume = CCast::ToInt8(audio.GetVolume() * 127.f); + const float fadeOut = audio.GetFadeOut(); + + x24_fileName = fileName; + x34_fileIsDsp = IsDSPFile(x24_fileName); + x38_fadeIn = fadeIn; + x40_volume = volume; + x3c_fadeOut = fadeOut; + fn_8015DCF0(this); + SetStereoPair(); + } +} + +template <> +template <> +int rstl::basic_string< char >::internal_search< rstl::string::const_iterator, const char* >( + rstl::string::const_iterator first, rstl::string::const_iterator last, + const char* searchFirst, const char* searchLast) { + if (searchFirst == searchLast) { + return 0; + } + int index = 0; + for (const_iterator it = first; it != last; ++it, ++index) { + if (char_traits< char >::eq(*it, *searchFirst)) { + const_iterator next = it; + ++next; + const char* search = searchFirst + 1; + const char* nextData = &*next; + while (search != searchLast && next != last) { + if (!char_traits< char >::eq(*nextData, *search)) { + break; + } + ++next; + ++nextData; + ++search; + } + if (search == searchLast) { + return index; + } + } + } + return -1; +} + +void CScriptStreamedMusic::SetStereoPair() { + if (x34_fileIsDsp && x24_fileName.find('|', 0) == -1 && x24_fileName.size() >= 5) { + if (CStringExtras::CompareCaseInsensitive( + rstl::string_l(x24_fileName.data() + x24_fileName.size() - 5), + rstl::string_l("L.dsp")) == 0) { + rstl::string right = rstl::string(x24_fileName.begin(), x24_fileName.end() - 5) + "R.dsp"; + if (CDvdFile::FileExists(right.data())) { + x24_fileName = x24_fileName + '|' + right; + } + } + } +} + +void CScriptStreamedMusic::StopNonDsp() { + CStreamAudioManager::sub_8036590c(x3c_fadeOut); +} + +void CScriptStreamedMusic::PlayNonDsp() { + const char volume = x40_volume; + if (x34_noStopOnDeactivate) { + CStreamAudioManager::SetDefaultAudio(x24_fileName, x3c_fadeOut, x38_fadeIn, volume); + } else { + CStreamAudioManager::SetCurrentAudio(x24_fileName, x3c_fadeOut, x38_fadeIn, volume); + } +} + +void CScriptStreamedMusic::Stop() { + if (x34_fileIsDsp) { + StopStream(); + } else { + StopNonDsp(); + } +} + +void CScriptStreamedMusic::PlayAudio() { + if (x34_fileIsDsp) { + StartStream(); + } else { + PlayNonDsp(); + } +} + +void CScriptStreamedMusic::Play(CStateManager& mgr) { + if (!GetEditorFlag2() && gpMain->fn_80008A1C()) { + return; + } + TweakOverride(mgr); + if (x34_fileIsDsp) { + StartStream(); + } else { + PlayNonDsp(); + } +} + +void CScriptStreamedMusic::AcceptScriptMsg(CStateManager& mgr, const CScriptMsg& msg) { + const EScriptObjectMessage message = msg.GetMessage(); + CEntity::AcceptScriptMsg(mgr, msg); + switch (message) { + case kSM_Play: + if (GetActive()) { + Play(mgr); + } + break; + case kSM_Load: + if (GetActive() && x34_fileIsDsp) { + PreloadMemoryAudio(); + } + break; + case kSM_Stop: + if (GetActive()) { + Stop(); + } + break; + case kSM_Deactivate: + if ((!x34_fileIsDsp && !x34_noStopOnDeactivate) || x34_fileIsDsp) { + Stop(); + } + break; + case kSM_Increment: + if (x34_fileIsDsp) { + CStreamAudioManager::FadeBackIn(IsOneShot(x34_loop), x38_fadeIn); + } else { + CStreamAudioManager::sub_803653f8(x38_fadeIn); + } + break; + case kSM_Decrement: + if (x34_fileIsDsp) { + CStreamAudioManager::TemporaryFadeOut(IsOneShot(x34_loop), x3c_fadeOut); + } else { + CStreamAudioManager::sub_80365424(x3c_fadeOut); + } + break; + default: + break; + } +} + +struct SLdrStreamedAudio { + SLdrStreamedAudio() + : editorProperties() + , fileName() + , noStopOnDeactivate(false) + , fadeIn(0.25f) + , fadeOut(0.25f) + , volume(127) + , loopMode(0) + , music(true) { + editorProperties.unknown = 3; + } + + SLdrEditorProperties editorProperties; + rstl::string fileName; + bool noStopOnDeactivate; + float fadeIn; + float fadeOut; + uint volume; + uint loopMode; + bool music; +}; + +CEntity* LoadStreamedAudio(CStateManager& mgr, CInputStream& input, const CEntityInfo& info) { + SLdrStreamedAudio data; + + const int propertyCount = input.ReadUint16(); + for (int i = 0; i < propertyCount; ++i) { + const uint propertyId = input.ReadInt32(); + const uint propertySize = input.ReadUint16(); + switch (propertyId) { + case 0x255a4580: + LoadTypedefSLdrEditorProperties(data.editorProperties, input); + break; + case 0xf6f3de1c: { + const rstl::string value(input); + data.fileName = value; + break; + } + case 0x34b152c4: + data.noStopOnDeactivate = input.ReadBool(); + break; + case 0x90aa341f: + data.fadeIn = input.ReadFloat(); + break; + case 0x7c269ebc: + data.fadeOut = input.ReadFloat(); + break; + case 0x80c66c37: + data.volume = input.ReadInt32(); + break; + case 0x28f82261: + data.loopMode = input.ReadInt32(); + break; + case 0xd3356fe7: + data.music = input.ReadBool(); + break; + default: + input.ReadBytes(nullptr, propertySize); + break; + } + } + + return new CScriptStreamedMusic( + mgr.AllocateUniqueId(), LdrToEntityInfo(info, data.editorProperties), + data.editorProperties.name, data.fileName, data.noStopOnDeactivate, data.fadeIn, data.fadeOut, + data.volume, data.loopMode == 0, data.music); +}