/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2025 tildearrow and contributors * * 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 #include #include "pch.h" #ifdef HAVE_SDL2 #include "SDL_events.h" #endif #include "ta-log.h" #include "fileutils.h" #include "engine/engine.h" #ifdef _WIN32 #include #include #include #if defined(HAVE_SDL2) && !defined(SUPPORT_XP) #include #endif #include "utfutils.h" #include "gui/shellScalingStub.h" typedef HRESULT (WINAPI *SPDA)(PROCESS_DPI_AWARENESS); #else #include #include struct sigaction termsa; #endif #ifdef SUPPORT_XP #define TUT_INTRO_PLAYED true #else #define TUT_INTRO_PLAYED false #endif #ifdef HAVE_LOCALE #ifdef HAVE_MOMO #define TA_BINDTEXTDOMAIN momo_bindtextdomain #define TA_TEXTDOMAIN momo_textdomain #else #define TA_BINDTEXTDOMAIN bindtextdomain #define TA_TEXTDOMAIN textdomain #endif #ifdef HAVE_SETLOCALE #include #endif #ifndef LC_CTYPE #define LC_CTYPE 0 #endif #ifndef LC_MESSAGES #define LC_MESSAGES 1 #endif #endif #include "cli/cli.h" #ifdef HAVE_GUI #include "gui/gui.h" #endif DivEngine e; #ifdef HAVE_GUI FurnaceGUI g; #endif FurnaceCLI cli; String outName; String vgmOutName; String cmdOutName; String romOutName; String txtOutName; int benchMode=0; int subsong=-1; DivCSOptions csExportOptions; DivAudioExportOptions exportOptions; DivConfig romExportConfig; #ifdef HAVE_GUI bool consoleMode=false; #else bool consoleMode=true; #endif bool consoleNoStatus=false; bool consoleNoControls=false; bool displayEngineFailError=false; bool displayLocaleFailError=false; bool vgmOutDirect=false; bool safeMode=false; bool safeModeWithAudio=false; bool infoMode=false; bool noReportError=false; std::vector params; #ifdef HAVE_LOCALE char reqLocaleCopy[64]; char localeDir[4096]; const char* localeDirs[]={ #ifdef __APPLE__ "../Resources/locale", #endif "locale", ".." DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "locale", ".." DIR_SEPARATOR_STR "po" DIR_SEPARATOR_STR "locale", #ifdef LOCALE_DIR LOCALE_DIR, #endif NULL }; #endif bool getExePath(char* argv0, char* exePath, size_t maxSize) { if (argv0==NULL) return false; #ifdef _WIN32 wchar_t exePathW[4096]; WString argv0W=utf8To16(argv0); if (GetFullPathNameW(argv0W.c_str(),4095,exePathW,NULL)==0) return false; String exePathS=utf16To8(exePathW); strncpy(exePath,exePathS.c_str(),maxSize); #else if (realpath(argv0,exePath)==NULL) return false; #endif char* lastChar=strrchr(exePath,DIR_SEPARATOR); if (lastChar==NULL) return false; *lastChar=0; return true; } TAParamResult pHelp(String) { printf("usage: furnace [params] [filename]\n" "you may specify the following parameters:\n"); for (auto& i: params) { if (i.value) { printf(" -%s %s: %s\n",i.name.c_str(),i.valName.c_str(),i.desc.c_str()); } else { printf(" -%s: %s\n",i.name.c_str(),i.desc.c_str()); } } return TA_PARAM_QUIT; } TAParamResult pAudio(String val) { if (outName!="") { logE("can't use -audio and -output at the same time."); return TA_PARAM_ERROR; } if (val=="jack") { e.setAudio(DIV_AUDIO_JACK); } else if (val=="sdl") { e.setAudio(DIV_AUDIO_SDL); } else if (val=="portaudio") { e.setAudio(DIV_AUDIO_PORTAUDIO); } else if (val=="pipe") { e.setAudio(DIV_AUDIO_PIPE); changeLogOutput(stderr); } else { logE("invalid value for audio engine! valid values are: jack, sdl, portaudio, pipe."); return TA_PARAM_ERROR; } return TA_PARAM_SUCCESS; } TAParamResult pView(String val) { if (val=="pattern") { e.setView(DIV_STATUS_PATTERN); } else if (val=="commands") { e.setView(DIV_STATUS_COMMANDS); } else if (val=="nothing") { e.setView(DIV_STATUS_NOTHING); } else { logE("invalid value for view type! valid values are: pattern, commands, nothing."); return TA_PARAM_ERROR; } return TA_PARAM_SUCCESS; } TAParamResult pConsole(String val) { consoleMode=true; return TA_PARAM_SUCCESS; } TAParamResult pQuiet(String val) { noReportError=true; return TA_PARAM_SUCCESS; } TAParamResult pNoStatus(String val) { consoleNoStatus=true; return TA_PARAM_SUCCESS; } TAParamResult pNoControls(String val) { consoleNoControls=true; return TA_PARAM_SUCCESS; } TAParamResult pSafeMode(String val) { #ifdef HAVE_GUI safeMode=true; return TA_PARAM_SUCCESS; #else logE("Furnace was compiled without the GUI. safe mode is pointless."); return TA_PARAM_ERROR; #endif } TAParamResult pSafeModeAudio(String val) { #ifdef HAVE_GUI safeMode=true; safeModeWithAudio=true; return TA_PARAM_SUCCESS; #else logE("Furnace was compiled without the GUI. safe mode is pointless."); return TA_PARAM_ERROR; #endif } TAParamResult pDirect(String val) { vgmOutDirect=true; return TA_PARAM_SUCCESS; } TAParamResult pInfo(String val) { infoMode=true; return TA_PARAM_SUCCESS; } TAParamResult pLogLevel(String val) { if (val=="trace") { logLevel=LOGLEVEL_TRACE; } else if (val=="debug") { logLevel=LOGLEVEL_DEBUG; } else if (val=="info") { logLevel=LOGLEVEL_INFO; } else if (val=="warning") { logLevel=LOGLEVEL_WARN; } else if (val=="error") { logLevel=LOGLEVEL_ERROR; } else { logE("invalid value for loglevel! valid values are: trace, debug, info, warning, error."); return TA_PARAM_ERROR; } return TA_PARAM_SUCCESS; } TAParamResult pVersion(String) { printf("Furnace version " DIV_VERSION ".\n\n"); printf("copyright (C) 2021-2025 tildearrow and contributors.\n"); printf("licensed under the GNU General Public License version 2 or later\n"); printf(".\n\n"); printf("this is free software with ABSOLUTELY NO WARRANTY.\n"); printf("pass the -warranty parameter for more information.\n\n"); printf("DISCLAIMER: this program is not affiliated with Delek in any form.\n"); printf("\n"); printf("furnace is powered by:\n"); printf("- libsndfile by Erik de Castro Lopo and rest of libsndfile team (LGPLv2.1)\n"); printf("- SDL2 by Sam Lantinga (zlib license)\n"); printf("- zlib by Jean-loup Gailly and Mark Adler (zlib license)\n"); printf("- PortAudio (PortAudio license)\n"); printf("- Weak-JACK by x42 (GPLv2)\n"); printf("- RtMidi by Gary P. Scavone (RtMidi license)\n"); printf("- backward-cpp by Google (MIT)\n"); printf("- Dear ImGui by Omar Cornut (MIT)\n"); #ifdef HAVE_FREETYPE printf("- FreeType (GPLv2)\n"); #endif printf("- Portable File Dialogs by Sam Hocevar (WTFPL)\n"); printf("- Native File Dialog (modified version) by Frogtoss Games (zlib license)\n"); printf("- FFTW by Matteo Frigo and Steven G. Johnson (GPLv2)\n"); printf("- Nuked-OPM by nukeykt (LGPLv2.1)\n"); printf("- Nuked-OPN2 by nukeykt (LGPLv2.1)\n"); printf("- Nuked-OPL3 by nukeykt (LGPLv2.1)\n"); printf("- Nuked-OPLL by nukeykt (GPLv2)\n"); printf("- Nuked-PSG (modified version) by nukeykt (GPLv2)\n"); printf("- YM3812-LLE by nukeykt (GPLv2)\n"); printf("- YMF262-LLE by nukeykt (GPLv2)\n"); printf("- YMF276-LLE by nukeykt (GPLv2)\n"); printf("- YM2608-LLE by nukeykt (GPLv2)\n"); printf("- ESFMu (modified version) by Kagamiin~ (LGPLv2.1)\n"); printf("- ymfm by Aaron Giles (BSD 3-clause)\n"); printf("- emu2413 by Digital Sound Antiques (MIT)\n"); printf("- adpcm by superctr (public domain)\n"); printf("- adpcm-xq by David Bryant (BSD 3-clause)\n"); printf("- MAME SN76496 emulation core by Nicola Salmoria (BSD 3-clause)\n"); printf("- MAME AY-3-8910 emulation core by Couriersud (BSD 3-clause)\n"); printf("- MAME SAA1099 emulation core by Juergen Buchmueller and Manuel Abadia (BSD 3-clause)\n"); printf("- MAME Namco WSG by Nicola Salmoria and Aaron Giles (BSD 3-clause)\n"); printf("- MAME RF5C68 core by Olivier Galibert and Aaron Giles (BSD 3-clause)\n"); printf("- MAME MSM5232 core by Jarek Burczynski and Hiromitsu Shioya (GPLv2)\n"); printf("- MAME MSM6258 core by Barry Rodewald (BSD 3-clause)\n"); printf("- MAME YMZ280B core by Aaron Giles (BSD 3-clause)\n"); printf("- MAME GA20 core (modified version) by Acho A. Tang, R. Belmont and Valley Bell (BSD 3-clause)\n"); printf("- MAME SegaPCM core by Hiromitsu Shioya and Olivier Galibert (BSD 3-clause)\n"); printf("- MAME µPD1771C-017 HLE core by David Viens (BSD 3-clause)\n"); printf("- QSound core by superctr (BSD 3-clause)\n"); printf("- VICE VIC-20 by Rami Rasanen and viznut (GPLv2)\n"); printf("- VICE TED by Andreas Boose, Tibor Biczo and Marco van den Heuvel (GPLv2)\n"); printf("- VERA core by Frank van den Hoef (BSD 2-clause)\n"); printf("- SAASound by Dave Hooper and Simon Owen (BSD 3-clause)\n"); printf("- SameBoy by Lior Halphon (MIT)\n"); printf("- Mednafen PCE, WonderSwan and Virtual Boy by Mednafen Team (GPLv2)\n"); printf("- Mednafen T6W28 (modified version) by Blargg (GPLv2)\n"); printf("- WonderSwan new core by asiekierka (zlib license)\n"); printf("- SNES DSP core by Blargg (LGPLv2.1)\n"); printf("- puNES (modified version) by FHorse (GPLv2)\n"); printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\n"); printf("- reSID by Dag Lem (GPLv2)\n"); printf("- reSIDfp by Dag Lem, Antti Lankila and Leandro Nini (GPLv2)\n"); printf("- dSID by DefleMask Team (based on jsSID by Hermit) (MIT)\n"); printf("- Stella by Stella Team (GPLv2)\n"); printf("- vgsound_emu (second version, modified version) by cam900 (zlib license)\n"); printf("- Impulse Tracker GUS volume table by Jeffrey Lim (BSD 3-clause)\n"); printf("- Schism Tracker IT sample decompression (GPLv2)\n"); printf("- Atari800 mzpokeysnd POKEY emulator by Michael Borisov (GPLv2)\n"); printf("- ASAP POKEY emulator by Piotr Fusik ported to C++ by laoo (GPLv2)\n"); printf("- SM8521 emulator (modified version) by cam900 (zlib license)\n"); printf("- D65010G031 emulator (modified version) by cam900 (zlib license)\n"); printf("- C140/C219 emulator (modified version) by cam900 (zlib license)\n"); printf("- PowerNoise emulator by scratchminer (MIT)\n"); printf("- ep128emu by Istvan Varga (GPLv2)\n"); printf("- NDS sound emulator by cam900 (zlib license)\n"); printf("- SID2 emulator by LTVA (GPLv2, modification of reSID emulator)\n"); printf("- SID3 emulator by LTVA (MIT)\n"); printf("- openMSX YMF278 emulator (modified version) by the openMSX developers (GPLv2)\n"); return TA_PARAM_QUIT; } TAParamResult pWarranty(String) { printf("This program is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU General Public License\n" "as published by the Free Software Foundation; either version 2\n" "of the License, or (at your option) any later version.\n\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n\n" "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"); return TA_PARAM_QUIT; } TAParamResult pLoops(String val) { try { int count=std::stoi(val); if (count<0) { exportOptions.loops=0; } else { exportOptions.loops=count; } } catch (std::exception& e) { logE("loop count shall be a number."); return TA_PARAM_ERROR; } return TA_PARAM_SUCCESS; } TAParamResult pSubSong(String val) { try { int v=std::stoi(val); if (v<0) { logE("sub-song shall be 0 or higher."); return TA_PARAM_ERROR; } subsong=v; } catch (std::exception& e) { logE("sub-song shall be a number."); return TA_PARAM_ERROR; } return TA_PARAM_SUCCESS; } TAParamResult pOutMode(String val) { if (val=="one") { exportOptions.mode=DIV_EXPORT_MODE_ONE; } else if (val=="persys") { exportOptions.mode=DIV_EXPORT_MODE_MANY_SYS; } else if (val=="perchan") { exportOptions.mode=DIV_EXPORT_MODE_MANY_CHAN; } else { logE("invalid value for outmode! valid values are: one, persys and perchan."); return TA_PARAM_ERROR; } return TA_PARAM_SUCCESS; } TAParamResult pBenchmark(String val) { if (val=="render") { benchMode=1; } else if (val=="seek") { benchMode=2; } else { logE("invalid value for benchmark! valid values are: render and seek."); return TA_PARAM_ERROR; } e.setAudio(DIV_AUDIO_DUMMY); return TA_PARAM_SUCCESS; } TAParamResult pOutput(String val) { outName=val; e.setAudio(DIV_AUDIO_DUMMY); return TA_PARAM_SUCCESS; } TAParamResult pVGMOut(String val) { vgmOutName=val; e.setAudio(DIV_AUDIO_DUMMY); return TA_PARAM_SUCCESS; } TAParamResult pCmdOut(String val) { cmdOutName=val; e.setAudio(DIV_AUDIO_DUMMY); return TA_PARAM_SUCCESS; } TAParamResult pROMOut(String val) { romOutName=val; e.setAudio(DIV_AUDIO_DUMMY); return TA_PARAM_SUCCESS; } TAParamResult pROMConf(String val) { size_t eqSplit=val.find_first_of('='); if (eqSplit==String::npos) { logE("invalid romconf parameter, must contain '=' as in: ="); return TA_PARAM_ERROR; } String key=val.substr(0,eqSplit); String param=val.substr(eqSplit+1); romExportConfig.set(key,param); return TA_PARAM_SUCCESS; } TAParamResult pTxtOut(String val) { txtOutName=val; e.setAudio(DIV_AUDIO_DUMMY); return TA_PARAM_SUCCESS; } bool needsValue(String param) { for (size_t i=0; i","output audio to file")); params.push_back(TAParam("O","vgmout",true,pVGMOut,"","output .vgm data")); params.push_back(TAParam("D","direct",false,pDirect,"","set VGM export direct stream mode")); params.push_back(TAParam("C","cmdout",true,pCmdOut,"","output command stream")); params.push_back(TAParam("r","romout",true,pROMOut,"","export ROM file, or path for multi-file export")); params.push_back(TAParam("R","romconf",true,pROMConf,"=","set configuration parameter for ROM export")); params.push_back(TAParam("t","txtout",true,pTxtOut,"","export as text file")); params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)")); params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (nothing by default)")); params.push_back(TAParam("i","info",false,pInfo,"","get info about a song")); params.push_back(TAParam("c","console",false,pConsole,"","enable console mode")); params.push_back(TAParam("q","noreport",false,pQuiet,"","do not display message box on error")); params.push_back(TAParam("n","nostatus",false,pNoStatus,"","disable playback status in console mode")); params.push_back(TAParam("N","nocontrols",false,pNoControls,"","disable standard input controls in console mode")); params.push_back(TAParam("l","loops",true,pLoops,"","set number of loops")); params.push_back(TAParam("s","subsong",true,pSubSong,"","set sub-song")); params.push_back(TAParam("o","outmode",true,pOutMode,"one|persys|perchan","set file output mode")); params.push_back(TAParam("S","safemode",false,pSafeMode,"","enable safe mode (software rendering and no audio)")); params.push_back(TAParam("A","safeaudio",false,pSafeModeAudio,"","enable safe mode (with audio")); params.push_back(TAParam("B","benchmark",true,pBenchmark,"render|seek","run performance test")); params.push_back(TAParam("V","version",false,pVersion,"","view information about Furnace.")); params.push_back(TAParam("W","warranty",false,pWarranty,"","view warranty disclaimer.")); } #ifdef _WIN32 void reportError(String what) { logE("%s",what); if (!noReportError) { MessageBox(NULL,what.c_str(),"Furnace",MB_OK|MB_ICONERROR); } } #elif defined(ANDROID) || defined(__APPLE__) void reportError(String what) { logE("%s",what); #ifdef HAVE_SDL2 if (!noReportError) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,"Error",what.c_str(),NULL); } #endif } #else void reportError(String what) { logE("%s",what); if (!noReportError) { // dummy } } #endif #ifndef _WIN32 #ifdef HAVE_GUI static void handleTermGUI(int) { g.requestQuit(); } #endif #endif // TODO: CoInitializeEx on Windows? // TODO: add crash log int main(int argc, char** argv) { // Windows console thing - thanks dj.tuBIG/MaliceX #ifdef _WIN32 #ifndef TA_SUBSYSTEM_CONSOLE #ifndef SUPPORT_XP if (AttachConsole(ATTACH_PARENT_PROCESS)) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); freopen("CONIN$", "r", stdin); } #endif #endif #endif srand(time(NULL)); initLog(stdout); #ifdef _WIN32 // set DPI awareness HMODULE shcore=LoadLibraryW(L"shcore.dll"); if (shcore!=NULL) { SPDA ta_SetProcessDpiAwareness=(SPDA)GetProcAddress(shcore,"SetProcessDpiAwareness"); if (ta_SetProcessDpiAwareness!=NULL) { HRESULT result=ta_SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); if (result!=S_OK) { // ??? } } if (!FreeLibrary(shcore)) { // ??? } } // co initialize ex HRESULT coResult=CoInitializeEx(NULL,COINIT_MULTITHREADED); if (coResult!=S_OK) { logE("CoInitializeEx failed!"); } #endif outName=""; vgmOutName=""; cmdOutName=""; romOutName=""; txtOutName=""; // load config for locale e.prePreInit(); #ifdef HAVE_LOCALE String reqLocale=e.getConfString("locale",""); if (!reqLocale.empty()) { if (reqLocale.find(".")==String::npos) { reqLocale+=".UTF-8"; } } strncpy(reqLocaleCopy,reqLocale.c_str(),63); if (reqLocale!="en_US.UTF-8") { const char* localeRet=NULL; #ifdef HAVE_SETLOCALE if ((localeRet=setlocale(LC_CTYPE,reqLocaleCopy))==NULL) { logE("could not set locale (CTYPE)!"); displayLocaleFailError=true; } else { logV("locale: %s",localeRet); } if ((localeRet=setlocale(LC_MESSAGES,reqLocaleCopy))==NULL) { logE("could not set locale (MESSAGES)!"); displayLocaleFailError=true; #ifdef HAVE_MOMO if (momo_setlocale(LC_MESSAGES,reqLocaleCopy)==NULL) { logV("Momo: could not set locale!"); } #endif } else { logV("locale: %s",localeRet); #ifdef HAVE_MOMO if (momo_setlocale(LC_MESSAGES,localeRet)==NULL) { logV("Momo: could not set locale!"); } #endif } #else if ((localeRet=momo_setlocale(LC_MESSAGES,reqLocaleCopy))==NULL) { logV("Momo: could not set locale!"); } else { logV("locale: %s",localeRet); } #endif char exePath[4096]; memset(exePath,0,4096); #ifndef ANDROID if (!getExePath(argv[0],exePath,4095)) memset(exePath,0,4096); #endif memset(localeDir,0,4096); bool textDomainBound=false; for (int i=0; localeDirs[i]; i++) { #ifdef ANDROID strncpy(localeDir,localeDirs[i],4095); #else if (exePath[0]!=0 && localeDirs[i][0]!=DIR_SEPARATOR) { // do you NOT understand what memset IS char* i_s=exePath; for (int i_i=0; i_i<4095; i_i++) { localeDir[i_i]=*i_s; if ((*i_s)==0) break; i_s++; } strncat(localeDir,DIR_SEPARATOR_STR,4095); strncat(localeDir,localeDirs[i],4095); } else { strncpy(localeDir,localeDirs[i],4095); } #endif logV("bind text domain: %s",localeDir); #ifndef ANDROID if (!dirExists(localeDir)) continue; #endif if ((localeRet=TA_BINDTEXTDOMAIN("furnace",localeDir))==NULL) { continue; } else { textDomainBound=true; logV("text domain 1: %s",localeRet); break; } } if (!textDomainBound) { logE("could not bind text domain!"); } else { if ((localeRet=TA_TEXTDOMAIN("furnace"))==NULL) { logE("could not text domain!"); } else { logV("text domain 2: %s",localeRet); } } } #endif initParams(); // parse arguments String arg, val, fileName; size_t eqSplit, argStart; for (int i=1; i>1)) { reportError(fmt::sprintf(_("couldn't open file! (couldn't get file length: %s)"),strerror(errno))); e.everythingOK(); fclose(f); finishLogFile(); return 1; } if (len<1) { if (len==0) { reportError(_("that file is empty!")); } else { reportError(fmt::sprintf(_("couldn't open file! (tell error: %s)"),strerror(errno))); } e.everythingOK(); fclose(f); finishLogFile(); return 1; } unsigned char* file=new unsigned char[len]; if (fseek(f,0,SEEK_SET)<0) { reportError(fmt::sprintf(_("couldn't open file! (size error: %s)"),strerror(errno))); e.everythingOK(); fclose(f); delete[] file; finishLogFile(); return 1; } if (fread(file,1,(size_t)len,f)!=(size_t)len) { reportError(fmt::sprintf(_("couldn't open file! (read error: %s)"),strerror(errno))); e.everythingOK(); fclose(f); delete[] file; finishLogFile(); return 1; } fclose(f); if (!e.load(file,(size_t)len,fileName.c_str())) { reportError(fmt::sprintf(_("could not open file! (%s)"),e.getLastError())); e.everythingOK(); finishLogFile(); return 1; } } if (infoMode) { e.dumpSongInfo(); finishLogFile(); return 0; } if (!e.init()) { if (consoleMode) { reportError(_("could not initialize engine!")); finishLogFile(); return 1; } else { logE("could not initialize engine!"); displayEngineFailError=true; } } if (subsong!=-1) { e.changeSongP(subsong); } if (benchMode) { logI("starting benchmark!"); if (benchMode==2) { e.benchmarkSeek(); } else { e.benchmarkPlayback(); } finishLogFile(); return 0; } if (outputMode) { if (cmdOutName!="") { SafeWriter* w=e.saveCommand(NULL); if (w!=NULL) { FILE* f=ps_fopen(cmdOutName.c_str(),"wb"); if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); } else { reportError(fmt::sprintf(_("could not open file! (%s)"),strerror(errno))); } w->finish(); delete w; } else { reportError(_("could not write command stream!")); } } if (vgmOutName!="") { SafeWriter* w=e.saveVGM(NULL,true,0x171,false,vgmOutDirect); if (w!=NULL) { FILE* f=ps_fopen(vgmOutName.c_str(),"wb"); if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); } else { reportError(fmt::sprintf(_("could not open file! (%s)"),strerror(errno))); } w->finish(); delete w; } else { reportError(_("could not write VGM!")); } } if (outName!="") { e.setConsoleMode(true); e.saveAudio(outName.c_str(),exportOptions); e.waitAudioFile(); } if (romOutName!="") { e.setConsoleMode(true); // select ROM target type DivROMExportOptions romTarget = DIV_ROM_ABSTRACT; String lowerCase=romOutName; for (char& i: lowerCase) { if (i>='A' && i<='Z') i+='a'-'A'; } for (int i=0; ifileExt && lowerCase.length()>=strlen(newDef->fileExt) && lowerCase.substr(lowerCase.length()-strlen(newDef->fileExt))==newDef->fileExt) { romTarget = opt; break; // extension matched, stop searching } if (romTarget == DIV_ROM_ABSTRACT) { romTarget = opt; // use first viable, but keep searching for extension match } } } if (romTarget > DIV_ROM_ABSTRACT && romTarget < DIV_ROM_MAX) { DivROMExport* pendingExport = e.buildROM(romTarget); if (pendingExport==NULL) { reportError(_("could not create exporter! you may want to report this issue...")); } else { pendingExport->setConf(romExportConfig); if (pendingExport->go(&e)) { pendingExport->wait(); if (!pendingExport->hasFailed()) { for (DivROMExportOutput& i: pendingExport->getResult()) { String path=romOutName; if (e.getROMExportDef(romTarget)->multiOutput) { path+=DIR_SEPARATOR_STR; path+=i.name; } FILE* f=ps_fopen(path.c_str(),"wb"); if (f!=NULL) { fwrite(i.data->getFinalBuf(),1,i.data->size(),f); fclose(f); } else { reportError(fmt::sprintf(_("could not open file! (%s)"),strerror(errno))); } } } else { reportError(fmt::sprintf(_("ROM export failed! (%s)"),e.getLastError())); } } else { reportError(_("could not begin exporting process! TODO: elaborate")); } } } else { reportError(_("no matching ROM export target is available.")); } } if (txtOutName!="") { e.setConsoleMode(true); SafeWriter* w=e.saveText(false); if (w!=NULL) { FILE* f=ps_fopen(txtOutName.c_str(),"wb"); if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); } else { reportError(fmt::sprintf(_("could not open file! (%s)"),strerror(errno))); } w->finish(); delete w; } else { reportError(_("could not write text!")); } } finishLogFile(); return 0; } if (consoleMode) { bool cliSuccess=false; if (consoleNoStatus) { cli.noStatus(); } if (consoleNoControls) { cli.noControls(); } cli.bindEngine(&e); if (!cli.init()) { reportError(_("error while starting CLI!")); } else { cliSuccess=true; } logI(_("playing...")); e.play(); if (cliSuccess) { cli.loop(); cli.finish(); e.quit(); finishLogFile(); return 0; } else { #ifdef HAVE_SDL2 SDL_Event ev; while (true) { SDL_WaitEvent(&ev); if (ev.type==SDL_QUIT) break; } e.quit(); finishLogFile(); return 0; #else while (true) { #ifdef _WIN32 Sleep(500); #else usleep(500000); #endif } #endif } } #ifdef HAVE_GUI if (safeMode) g.enableSafeMode(); g.bindEngine(&e); if (!g.init()) { reportError(g.getLastError()); finishLogFile(); e.everythingOK(); return 1; } if (displayEngineFailError) { logE(_("displaying engine fail error.")); g.showError(_("error while initializing audio!")); } if (displayLocaleFailError) { #ifndef HAVE_MOMO #ifdef __unix__ g.showError("could not load language!\napparently your system does not support this language correctly.\nmake sure you've generated language data by editing /etc/locale.gen\nand then running locale-gen as root."); #else g.showError("could not load language!\nthis is a bug!"); #endif #endif } if (!fileName.empty()) { g.setFileName(fileName); } #ifndef _WIN32 sigemptyset(&termsa.sa_mask); termsa.sa_flags=0; termsa.sa_handler=handleTermGUI; sigaction(SIGTERM,&termsa,NULL); #endif g.loop(); logI("closing GUI."); g.finish(true); #else logE("GUI requested but GUI not compiled!"); #endif logI("stopping engine."); e.quit(false); finishLogFile(); #ifdef _WIN32 if (coResult==S_OK || coResult==S_FALSE) { CoUninitialize(); } #endif e.everythingOK(); #ifdef HAVE_SDL2 SDL_Quit(); #endif return 0; }