| 1 | /* |
|---|
| 2 | ================================================================================= |
|---|
| 3 | This file is part of Cafu, the open-source game engine and graphics engine |
|---|
| 4 | for multiplayer, cross-platform, real-time 3D action. |
|---|
| 5 | Copyright (C) 2002-2012 Carsten Fuchs Software. |
|---|
| 6 | |
|---|
| 7 | Cafu is free software: you can redistribute it and/or modify it under the terms |
|---|
| 8 | of the GNU General Public License as published by the Free Software Foundation, |
|---|
| 9 | either version 3 of the License, or (at your option) any later version. |
|---|
| 10 | |
|---|
| 11 | Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
|---|
| 12 | without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
|---|
| 13 | PURPOSE. See the GNU General Public License for more details. |
|---|
| 14 | |
|---|
| 15 | You should have received a copy of the GNU General Public License |
|---|
| 16 | along with Cafu. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 17 | |
|---|
| 18 | For support and more information about Cafu, visit us at <http://www.cafu.de>. |
|---|
| 19 | ================================================================================= |
|---|
| 20 | */ |
|---|
| 21 | |
|---|
| 22 | #include "PlatformAux.hpp" |
|---|
| 23 | #include "Templates/Array.hpp" |
|---|
| 24 | #include "ConsoleCommands/Console.hpp" |
|---|
| 25 | #include "FileSys/FileMan.hpp" |
|---|
| 26 | #include "MaterialSystem/Renderer.hpp" |
|---|
| 27 | #include "SoundSystem/SoundSys.hpp" |
|---|
| 28 | |
|---|
| 29 | #include <cassert> |
|---|
| 30 | |
|---|
| 31 | #ifdef _WIN32 |
|---|
| 32 | #elif __linux__ |
|---|
| 33 | #include <cstdio> |
|---|
| 34 | #include <cstring> |
|---|
| 35 | #include <dirent.h> |
|---|
| 36 | #include <dlfcn.h> |
|---|
| 37 | #define __stdcall |
|---|
| 38 | #define GetProcAddress dlsym |
|---|
| 39 | #define FreeLibrary dlclose |
|---|
| 40 | #endif |
|---|
| 41 | |
|---|
| 42 | |
|---|
| 43 | #ifdef SCONS_BUILD_DIR |
|---|
| 44 | std::string PlatformAux::GetEnvFileSuffix() |
|---|
| 45 | { |
|---|
| 46 | // This is just a lousy way to say that this method is obsolete with the new SCons build system! |
|---|
| 47 | return std::string("_scons"); |
|---|
| 48 | } |
|---|
| 49 | #else |
|---|
| 50 | std::string PlatformAux::GetEnvFileSuffix() |
|---|
| 51 | { |
|---|
| 52 | std::string Suffix="_"; |
|---|
| 53 | |
|---|
| 54 | #if defined(_WIN32) |
|---|
| 55 | // We are on the Win32 platform. |
|---|
| 56 | Suffix+="win32"; |
|---|
| 57 | |
|---|
| 58 | #if defined(__WATCOMC__) |
|---|
| 59 | // Using the OpenWatcom C/C++ compiler. |
|---|
| 60 | Suffix+="_ow"; |
|---|
| 61 | #elif defined(_MSC_VER) |
|---|
| 62 | // Using the Microsoft Visual C++ compiler. |
|---|
| 63 | Suffix+="_vc60"; |
|---|
| 64 | #else |
|---|
| 65 | Suffix+="_unknown"; |
|---|
| 66 | #endif |
|---|
| 67 | #elif __linux__ && __i386__ |
|---|
| 68 | // We are on the Linux i386 platform. |
|---|
| 69 | Suffix+="li686"; |
|---|
| 70 | |
|---|
| 71 | #if __GNUG__ // This is equivalent to testing (__GNUC__ && __cplusplus). |
|---|
| 72 | // Using the g++ compiler. |
|---|
| 73 | // See http://www.delorie.com/gnu/docs/gcc/cpp_toc.html for documentation about the C preprocessor. |
|---|
| 74 | Suffix+="_g++"; |
|---|
| 75 | #else |
|---|
| 76 | Suffix+="_unknown"; |
|---|
| 77 | #endif |
|---|
| 78 | #else |
|---|
| 79 | Suffix+="unknown_unknown"; |
|---|
| 80 | #endif |
|---|
| 81 | |
|---|
| 82 | #if defined(DEBUG) |
|---|
| 83 | Suffix+="_d"; |
|---|
| 84 | #else |
|---|
| 85 | Suffix+="_r"; |
|---|
| 86 | #endif |
|---|
| 87 | |
|---|
| 88 | return Suffix; |
|---|
| 89 | } |
|---|
| 90 | #endif |
|---|
| 91 | |
|---|
| 92 | |
|---|
| 93 | static void GetDLLs(const std::string& Path, const std::string& Prefix, ArrayT<std::string>& FoundDLLs) |
|---|
| 94 | { |
|---|
| 95 | if (Path=="") return; |
|---|
| 96 | |
|---|
| 97 | #if defined(_WIN32) && defined(_MSC_VER) |
|---|
| 98 | { |
|---|
| 99 | WIN32_FIND_DATA FindFileData; |
|---|
| 100 | |
|---|
| 101 | HANDLE hFind=FindFirstFile((Path+"\\*").c_str(), &FindFileData); |
|---|
| 102 | if (hFind==INVALID_HANDLE_VALUE) return; |
|---|
| 103 | |
|---|
| 104 | do |
|---|
| 105 | { |
|---|
| 106 | if (!_stricmp(FindFileData.cFileName, "." )) continue; |
|---|
| 107 | if (!_stricmp(FindFileData.cFileName, ".." )) continue; |
|---|
| 108 | if (!_stricmp(FindFileData.cFileName, "cvs")) continue; |
|---|
| 109 | |
|---|
| 110 | // If FindFileData.cFileName doesn't begin with Prefix, continue. |
|---|
| 111 | if (std::string(FindFileData.cFileName, Prefix.length())!=Prefix) continue; |
|---|
| 112 | |
|---|
| 113 | std::string DLLName=Path+"/"+FindFileData.cFileName; |
|---|
| 114 | #ifdef SCONS_BUILD_DIR |
|---|
| 115 | const std::string Suffix=".dll"; |
|---|
| 116 | #else |
|---|
| 117 | const std::string Suffix=GetEnvFileSuffix()+".dll"; // Console->Print("Suffix "+Suffix+", DLLName "+DLLName+"\n"); |
|---|
| 118 | #endif |
|---|
| 119 | |
|---|
| 120 | // If FindFileData.cFileName doesn't end with Suffix, continue. |
|---|
| 121 | if (DLLName.length()<Suffix.length()) continue; |
|---|
| 122 | if (std::string(DLLName.c_str()+DLLName.length()-Suffix.length())!=Suffix) continue; |
|---|
| 123 | |
|---|
| 124 | FoundDLLs.PushBack(DLLName); |
|---|
| 125 | } while (FindNextFile(hFind, &FindFileData)!=0); |
|---|
| 126 | |
|---|
| 127 | if (GetLastError()==ERROR_NO_MORE_FILES) FindClose(hFind); |
|---|
| 128 | } |
|---|
| 129 | #else |
|---|
| 130 | { |
|---|
| 131 | DIR* Dir=opendir(Path.c_str()); |
|---|
| 132 | if (!Dir) return; |
|---|
| 133 | |
|---|
| 134 | for (dirent* DirEnt=readdir(Dir); DirEnt!=NULL; DirEnt=readdir(Dir)) |
|---|
| 135 | { |
|---|
| 136 | if (!strcasecmp(DirEnt->d_name, "." )) continue; |
|---|
| 137 | if (!strcasecmp(DirEnt->d_name, ".." )) continue; |
|---|
| 138 | if (!strcasecmp(DirEnt->d_name, "cvs")) continue; |
|---|
| 139 | |
|---|
| 140 | // If FindFileData.cFileName doesn't begin with LibPrefix, continue. |
|---|
| 141 | const std::string LibPrefix="lib"+Prefix; |
|---|
| 142 | if (std::string(DirEnt->d_name, LibPrefix.length())!=LibPrefix) continue; |
|---|
| 143 | |
|---|
| 144 | // For portability, only the 'd_name' member of a 'dirent' may be accessed. |
|---|
| 145 | std::string DLLName=Path+"/"+DirEnt->d_name; |
|---|
| 146 | #ifdef SCONS_BUILD_DIR |
|---|
| 147 | const std::string Suffix =".so"; |
|---|
| 148 | #else |
|---|
| 149 | const std::string Suffix =GetEnvFileSuffix()+".dll"; |
|---|
| 150 | #endif |
|---|
| 151 | |
|---|
| 152 | // If FindFileData.cFileName doesn't end with Suffix, continue. |
|---|
| 153 | if (DLLName.length()<Suffix.length()) continue; |
|---|
| 154 | if (std::string(DLLName.c_str()+DLLName.length()-Suffix.length())!=Suffix) continue; |
|---|
| 155 | |
|---|
| 156 | FoundDLLs.PushBack(DLLName); |
|---|
| 157 | } |
|---|
| 158 | |
|---|
| 159 | closedir(Dir); |
|---|
| 160 | } |
|---|
| 161 | #endif |
|---|
| 162 | } |
|---|
| 163 | |
|---|
| 164 | |
|---|
| 165 | MatSys::RendererI* PlatformAux::GetRenderer(const std::string& DLLName, HMODULE& OutRendererDLL) |
|---|
| 166 | { |
|---|
| 167 | #ifdef _WIN32 |
|---|
| 168 | OutRendererDLL=LoadLibrary(DLLName.c_str()); |
|---|
| 169 | #else |
|---|
| 170 | // Note that RTLD_GLOBAL must *not* be passed-in here, or else we get in trouble with subsequently loaded libraries. |
|---|
| 171 | // (E.g. it causes dlsym(OutRendererDLL, "GetRenderer") to return identical results for different OutRendererDLLs.) |
|---|
| 172 | // Please refer to the man page of dlopen for more details. |
|---|
| 173 | OutRendererDLL=dlopen(DLLName.c_str(), RTLD_NOW); |
|---|
| 174 | if (!OutRendererDLL) Console->Print(std::string(dlerror()) + ", "); |
|---|
| 175 | #endif |
|---|
| 176 | |
|---|
| 177 | if (!OutRendererDLL) { Console->Print("FAILED - could not load the library at "+DLLName+".\n"); return NULL; } |
|---|
| 178 | |
|---|
| 179 | |
|---|
| 180 | typedef MatSys::RendererI* (__stdcall *GetRendererT)(cf::ConsoleI* Console_, cf::FileSys::FileManI* FileMan_); |
|---|
| 181 | |
|---|
| 182 | #if defined(_WIN32) && !defined(_WIN64) |
|---|
| 183 | GetRendererT GetRendererFunc=(GetRendererT)GetProcAddress(OutRendererDLL, "_GetRenderer@8"); |
|---|
| 184 | #else |
|---|
| 185 | GetRendererT GetRendererFunc=(GetRendererT)GetProcAddress(OutRendererDLL, "GetRenderer"); |
|---|
| 186 | #endif |
|---|
| 187 | |
|---|
| 188 | if (!GetRendererFunc) { Console->Print("FAILED - could not get the address of the GetRenderer() function.\n"); FreeLibrary(OutRendererDLL); return NULL; } |
|---|
| 189 | |
|---|
| 190 | |
|---|
| 191 | // When we get here, the console and the file man must already have been initialized. |
|---|
| 192 | assert(Console!=NULL); |
|---|
| 193 | assert(cf::FileSys::FileMan!=NULL); |
|---|
| 194 | |
|---|
| 195 | MatSys::RendererI* Renderer=GetRendererFunc(Console, cf::FileSys::FileMan); |
|---|
| 196 | |
|---|
| 197 | if (!Renderer) { Console->Print("FAILED - could not get the renderer.\n"); FreeLibrary(OutRendererDLL); return NULL; } |
|---|
| 198 | if (!Renderer->IsSupported()) { Console->Print("FAILED - renderer says it's not supported.\n"); FreeLibrary(OutRendererDLL); return NULL; } |
|---|
| 199 | |
|---|
| 200 | return Renderer; |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | |
|---|
| 204 | MatSys::RendererI* PlatformAux::GetBestRenderer(HMODULE& OutRendererDLL) |
|---|
| 205 | { |
|---|
| 206 | #ifdef SCONS_BUILD_DIR |
|---|
| 207 | #define QUOTE(str) QUOTE_HELPER(str) |
|---|
| 208 | #define QUOTE_HELPER(str) #str |
|---|
| 209 | std::string Path=std::string("Libs/")+QUOTE(SCONS_BUILD_DIR)+"/MaterialSystem"; |
|---|
| 210 | #undef QUOTE |
|---|
| 211 | #undef QUOTE_HELPER |
|---|
| 212 | #else |
|---|
| 213 | std::string Path="Renderers"; |
|---|
| 214 | #endif |
|---|
| 215 | ArrayT<std::string> DLLNames; |
|---|
| 216 | |
|---|
| 217 | Console->Print("\n"); |
|---|
| 218 | Console->Print("Scanning cwd for all available renderers...\n"); |
|---|
| 219 | GetDLLs(".", "Renderer", DLLNames); |
|---|
| 220 | |
|---|
| 221 | if (DLLNames.Size()==0) |
|---|
| 222 | { |
|---|
| 223 | Console->Print("Scanning "+Path+" for all available renderers...\n"); |
|---|
| 224 | GetDLLs(Path, "Renderer", DLLNames); |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | |
|---|
| 228 | unsigned long BestDLLIndex= 0; // Index into DLLNames. |
|---|
| 229 | int BestPrefNr =-1; // The preference number of the renderer related to BestDLLIndex. |
|---|
| 230 | |
|---|
| 231 | for (unsigned long DLLNr=0; DLLNr<DLLNames.Size(); DLLNr++) |
|---|
| 232 | { |
|---|
| 233 | Console->Print(DLLNames[DLLNr]+" ... "); |
|---|
| 234 | |
|---|
| 235 | HMODULE RendererDLL; |
|---|
| 236 | MatSys::RendererI* Renderer=GetRenderer(DLLNames[DLLNr], RendererDLL); |
|---|
| 237 | |
|---|
| 238 | if (!Renderer) continue; |
|---|
| 239 | |
|---|
| 240 | int PrefNr=Renderer->GetPreferenceNr(); |
|---|
| 241 | |
|---|
| 242 | Renderer=NULL; |
|---|
| 243 | FreeLibrary(RendererDLL); |
|---|
| 244 | |
|---|
| 245 | if (PrefNr<10) |
|---|
| 246 | { |
|---|
| 247 | // We don't want the Null renderer to be possibly selected for client rendering |
|---|
| 248 | // (which can happen in the presence of other errors). |
|---|
| 249 | // It would only confuse and worry users to sit in front of a black, apparently frozen screen. |
|---|
| 250 | Console->Print(cf::va("SUCCESS - but excluded from auto-selection (Pref# %i).\n", PrefNr)); |
|---|
| 251 | continue; |
|---|
| 252 | } |
|---|
| 253 | |
|---|
| 254 | if (PrefNr>BestPrefNr) |
|---|
| 255 | { |
|---|
| 256 | Console->Print(cf::va("SUCCESS - %s renderer (Pref# %i).\n", BestPrefNr<0 ? "first supported" : "higher preference", PrefNr)); |
|---|
| 257 | |
|---|
| 258 | BestDLLIndex=DLLNr; |
|---|
| 259 | BestPrefNr =PrefNr; |
|---|
| 260 | } |
|---|
| 261 | else Console->Print(cf::va("SUCCESS - but no higher preference (Pref# %i).\n", PrefNr)); |
|---|
| 262 | } |
|---|
| 263 | |
|---|
| 264 | if (BestPrefNr==-1) |
|---|
| 265 | { |
|---|
| 266 | Console->Print("No renderer qualified.\n"); |
|---|
| 267 | return NULL; |
|---|
| 268 | } |
|---|
| 269 | |
|---|
| 270 | Console->Print("Reloading previously auto-selected renderer "+DLLNames[BestDLLIndex]+" ...\n"); |
|---|
| 271 | return GetRenderer(DLLNames[BestDLLIndex], OutRendererDLL); |
|---|
| 272 | } |
|---|
| 273 | |
|---|
| 274 | |
|---|
| 275 | MatSys::TextureMapManagerI* PlatformAux::GetTextureMapManager(HMODULE RendererDLL) |
|---|
| 276 | { |
|---|
| 277 | typedef MatSys::TextureMapManagerI* (__stdcall *GetTMMT)(); |
|---|
| 278 | |
|---|
| 279 | #if defined(_WIN32) && !defined(_WIN64) |
|---|
| 280 | GetTMMT GetTMM=(GetTMMT)GetProcAddress(RendererDLL, "_GetTextureMapManager@0"); |
|---|
| 281 | #else |
|---|
| 282 | GetTMMT GetTMM=(GetTMMT)GetProcAddress(RendererDLL, "GetTextureMapManager"); |
|---|
| 283 | #endif |
|---|
| 284 | |
|---|
| 285 | if (!GetTMM) { Console->Print("FAILED - could not get the address of the GetTextureMapManager() function.\n"); return NULL; } |
|---|
| 286 | |
|---|
| 287 | return GetTMM(); |
|---|
| 288 | } |
|---|
| 289 | |
|---|
| 290 | |
|---|
| 291 | SoundSysI* PlatformAux::GetSoundSys(const std::string& DLLName, HMODULE& OutSoundSysDLL) |
|---|
| 292 | { |
|---|
| 293 | #ifdef _WIN32 |
|---|
| 294 | OutSoundSysDLL=LoadLibrary(DLLName.c_str()); |
|---|
| 295 | #else |
|---|
| 296 | // Note that RTLD_GLOBAL must *not* be passed-in here, or else we get in trouble with subsequently loaded libraries. |
|---|
| 297 | // (E.g. it causes dlsym(OutSoundSysDLL, "GetSoundSys") to return identical results for different OutSoundSysDLLs.) |
|---|
| 298 | // Please refer to the man page of dlopen for more details. |
|---|
| 299 | OutSoundSysDLL=dlopen(DLLName.c_str(), RTLD_NOW); |
|---|
| 300 | if (!OutSoundSysDLL) Console->Print(std::string(dlerror()) + ", "); |
|---|
| 301 | #endif |
|---|
| 302 | |
|---|
| 303 | if (!OutSoundSysDLL) { Console->Print("FAILED - could not load the library at "+DLLName+".\n"); return NULL; } |
|---|
| 304 | |
|---|
| 305 | |
|---|
| 306 | typedef SoundSysI* (__stdcall *GetSoundSys)(cf::ConsoleI* Console_, cf::FileSys::FileManI* FileMan_); |
|---|
| 307 | |
|---|
| 308 | #if defined(_WIN32) && !defined(_WIN64) |
|---|
| 309 | GetSoundSys GetSoundSysFunc=(GetSoundSys)GetProcAddress(OutSoundSysDLL, "_GetSoundSys@8"); |
|---|
| 310 | #else |
|---|
| 311 | GetSoundSys GetSoundSysFunc=(GetSoundSys)GetProcAddress(OutSoundSysDLL, "GetSoundSys"); |
|---|
| 312 | #endif |
|---|
| 313 | |
|---|
| 314 | if (!GetSoundSysFunc) { Console->Print("FAILED - could not get the address of the GetSoundSys() function.\n"); FreeLibrary(OutSoundSysDLL); return NULL; } |
|---|
| 315 | |
|---|
| 316 | |
|---|
| 317 | // When we get here, the console and the file man must already have been initialized. |
|---|
| 318 | assert(Console!=NULL); |
|---|
| 319 | assert(cf::FileSys::FileMan!=NULL); |
|---|
| 320 | |
|---|
| 321 | SoundSysI* SoundSys=GetSoundSysFunc(Console, cf::FileSys::FileMan); |
|---|
| 322 | |
|---|
| 323 | if (!SoundSys) { Console->Print("FAILED - could not get the SoundSys.\n"); FreeLibrary(OutSoundSysDLL); return NULL; } |
|---|
| 324 | if (!SoundSys->IsSupported()) { Console->Print("FAILED - SoundSys says it's not supported.\n"); FreeLibrary(OutSoundSysDLL); return NULL; } |
|---|
| 325 | |
|---|
| 326 | return SoundSys; |
|---|
| 327 | } |
|---|
| 328 | |
|---|
| 329 | |
|---|
| 330 | SoundSysI* PlatformAux::GetBestSoundSys(HMODULE& OutSoundSysDLL) |
|---|
| 331 | { |
|---|
| 332 | #ifdef SCONS_BUILD_DIR |
|---|
| 333 | #define QUOTE(str) QUOTE_HELPER(str) |
|---|
| 334 | #define QUOTE_HELPER(str) #str |
|---|
| 335 | std::string Path=std::string("Libs/")+QUOTE(SCONS_BUILD_DIR)+"/SoundSystem"; |
|---|
| 336 | #undef QUOTE |
|---|
| 337 | #undef QUOTE_HELPER |
|---|
| 338 | #else |
|---|
| 339 | std::string Path="SoundSystem"; |
|---|
| 340 | #endif |
|---|
| 341 | ArrayT<std::string> DLLNames; |
|---|
| 342 | |
|---|
| 343 | Console->Print("\n"); |
|---|
| 344 | Console->Print("Scanning cwd for all available sound systems...\n"); |
|---|
| 345 | GetDLLs(".", "SoundSys", DLLNames); |
|---|
| 346 | |
|---|
| 347 | if (DLLNames.Size()==0) |
|---|
| 348 | { |
|---|
| 349 | Console->Print("Scanning "+Path+" for all available sound systems...\n"); |
|---|
| 350 | GetDLLs(Path, "SoundSys", DLLNames); |
|---|
| 351 | } |
|---|
| 352 | |
|---|
| 353 | |
|---|
| 354 | unsigned long BestDLLIndex= 0; // Index into DLLNames. |
|---|
| 355 | int BestPrefNr =-1; // The preference number of the sound system related to BestDLLIndex. |
|---|
| 356 | |
|---|
| 357 | for (unsigned long DLLNr=0; DLLNr<DLLNames.Size(); DLLNr++) |
|---|
| 358 | { |
|---|
| 359 | Console->Print(DLLNames[DLLNr]+" ... "); |
|---|
| 360 | |
|---|
| 361 | HMODULE SoundSysDLL; |
|---|
| 362 | SoundSysI* SoundSys=GetSoundSys(DLLNames[DLLNr], SoundSysDLL); |
|---|
| 363 | |
|---|
| 364 | if (!SoundSys) continue; |
|---|
| 365 | |
|---|
| 366 | int PrefNr=SoundSys->GetPreferenceNr(); |
|---|
| 367 | |
|---|
| 368 | SoundSys=NULL; |
|---|
| 369 | FreeLibrary(SoundSysDLL); |
|---|
| 370 | |
|---|
| 371 | if (PrefNr<10) |
|---|
| 372 | { |
|---|
| 373 | // We don't want the Null sound system to be possibly selected for client |
|---|
| 374 | // (which can happen in the presence of other errors). |
|---|
| 375 | // It would only confuse and worry users to sit in front of a black, apparently frozen screen. |
|---|
| 376 | Console->Print(cf::va("SUCCESS - but excluded from auto-selection (Pref# %i).\n", PrefNr)); |
|---|
| 377 | continue; |
|---|
| 378 | } |
|---|
| 379 | |
|---|
| 380 | if (PrefNr>BestPrefNr) |
|---|
| 381 | { |
|---|
| 382 | Console->Print(cf::va("SUCCESS - %s sound system (Pref# %i).\n", BestPrefNr<0 ? "first supported" : "higher preference", PrefNr)); |
|---|
| 383 | |
|---|
| 384 | BestDLLIndex=DLLNr; |
|---|
| 385 | BestPrefNr =PrefNr; |
|---|
| 386 | } |
|---|
| 387 | else Console->Print(cf::va("SUCCESS - but no higher preference (Pref# %i).\n", PrefNr)); |
|---|
| 388 | } |
|---|
| 389 | |
|---|
| 390 | if (BestPrefNr==-1) |
|---|
| 391 | { |
|---|
| 392 | Console->Print("No sound system qualified.\n"); |
|---|
| 393 | return NULL; |
|---|
| 394 | } |
|---|
| 395 | |
|---|
| 396 | Console->Print("Reloading previously auto-selected sound system "+DLLNames[BestDLLIndex]+" ...\n"); |
|---|
| 397 | return GetSoundSys(DLLNames[BestDLLIndex], OutSoundSysDLL); |
|---|
| 398 | } |
|---|