cmd line parsing fixes, non-.smc roms allowed
[drnoksnes] / platform / sdl.cpp
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/time.h>
4 #include <time.h>
5 #include <SDL.h>
6
7 #include "platform.h"
8 #include "snes9x.h"
9 #include "gfx.h"
10 #include "display.h"
11 #include "memmap.h"
12 #include "soundux.h"
13 #include "hacks.h"
14 #include "snapshot.h"
15 #include "hgw.h"
16
17 #define kPollEveryNFrames               5               //Poll input only every this many frames
18 #define kPollHgwEveryNFrames    10              //Poll dbus only every this many frames
19
20 #define TRACE printf("trace: %s:%s\n", __FILE__, __func__);
21 #define DIE(format, ...) do { \
22                 fprintf(stderr, "Died at %s:%d: ", __FILE__, __LINE__ ); \
23                 fprintf(stderr, format "\n", ## __VA_ARGS__); \
24                 abort(); \
25         } while (0);
26
27 void S9xMessage(int type, int number, const char * message)
28 {
29         printf("%s\n", message);
30 }
31
32 void S9xLoadSDD1Data()
33 {TRACE
34         Settings.SDD1Pack=FALSE;
35 }
36
37 void S9xAutoSaveSRAM()
38 {
39         Memory.SaveSRAM(S9xGetFilename(FILE_SRAM));
40 }
41
42 static void S9xInit() 
43 {
44         if (!Memory.Init () || !S9xInitAPU())
45          DIE("Memory or APU failed");
46
47         if (!S9xInitSound ())
48                 DIE("Sound failed");
49         S9xSetSoundMute (TRUE);
50         
51         // TODO: PAL/NTSC something better than this
52         Settings.PAL = Settings.ForcePAL;
53         
54         Settings.FrameTime = Settings.PAL?Settings.FrameTimePAL:Settings.FrameTimeNTSC;
55         Memory.ROMFramesPerSecond = Settings.PAL?50:60;
56         
57         IPPU.RenderThisFrame = TRUE;
58 }
59
60 static void loadRom()
61 {
62         const char * file = S9xGetFilename(FILE_ROM);
63
64         printf("ROM: %s\n", file);
65
66         if (!Memory.LoadROM(file))
67                 DIE("Loading ROM failed");
68         
69         file = S9xGetFilename(FILE_SRAM);
70         printf("SRAM: %s\n", file);
71         Memory.LoadSRAM(file); 
72 }
73
74 static void resumeGame()
75 {
76         if (!Config.snapshotLoad) return;
77
78         const char * file = S9xGetFilename(FILE_FREEZE);
79         int result = S9xUnfreezeGame(file);
80
81         printf("Unfreeze: %s", file);
82
83         if (!result) {
84                 printf(" failed");
85                 FILE* fp = fopen(file, "rb");
86                 if (fp) {
87                         if (Config.snapshotSave) {
88                                 puts(", but the file exists, so I'm not going to overwrite it");
89                                 Config.snapshotSave = false;
90                         } else {
91                                 puts(" (bad file?)");
92                         }
93                         fclose(fp);
94                 } else {
95                         puts(" (file does not exist)");
96                 }
97         } else {
98                 puts(" ok");
99         }
100 }
101
102 static void pauseGame()
103 {
104         if (!Config.snapshotSave) return;
105
106         const char * file = S9xGetFilename(FILE_FREEZE);
107         int result = S9xFreezeGame(file);
108
109         printf("Freeze: %s", file);
110
111         if (!result) {
112                 Config.snapshotSave = false; // Serves as a flag to Hgw
113                 puts(" failed");
114         } else {
115                 puts(" ok");
116         }
117 }
118
119 /* This comes nearly straight from snes9x */
120 static void frameSync() {
121         static struct timeval next1 = {0, 0};
122         struct timeval now;
123         
124         if (Settings.TurboMode)
125         {
126                 if(Settings.SkipFrames == AUTO_FRAMERATE || 
127                         ++IPPU.FrameSkip >= Settings.SkipFrames)
128                 {
129                         IPPU.FrameSkip = 0;
130                         IPPU.SkippedFrames = 0;
131                         IPPU.RenderThisFrame = TRUE;
132                 }
133                 else
134                 {
135                         ++IPPU.SkippedFrames;
136                         IPPU.RenderThisFrame = FALSE;
137                 }
138                 return;
139         }
140         
141         /* Normal mode */
142         
143         while (gettimeofday(&now, 0) < 0);
144         
145         /* If there is no known "next" frame, initialize it now */
146         if (next1.tv_sec == 0) { next1 = now; ++next1.tv_usec; }
147         
148     /* If we're on AUTO_FRAMERATE, we'll display frames always
149      * only if there's excess time.
150      * Otherwise we'll display the defined amount of frames.
151      */
152     unsigned limit = Settings.SkipFrames == AUTO_FRAMERATE
153                      ? (timercmp(&next1, &now, <) ? 10 : 1)
154                      : Settings.SkipFrames;
155                      
156     IPPU.RenderThisFrame = ++IPPU.SkippedFrames >= limit;
157     if(IPPU.RenderThisFrame)
158     {
159         IPPU.SkippedFrames = 0;
160     }
161     else
162     {
163         /* If we were behind the schedule, check how much it is */
164         if(timercmp(&next1, &now, <))
165         {
166             unsigned lag =
167                 (now.tv_sec - next1.tv_sec) * 1000000
168                + now.tv_usec - next1.tv_usec;
169             if(lag >= 500000)
170             {
171                 /* More than a half-second behind means probably
172                  * pause. The next line prevents the magic
173                  * fast-forward effect.
174                  */
175                 next1 = now;
176             }
177         }
178     }
179     
180     /* Delay until we're completed this frame */
181
182     /* Can't use setitimer because the sound code already could
183      * be using it. We don't actually need it either.
184      */
185
186     while(timercmp(&next1, &now, >))
187     {
188         /* If we're ahead of time, sleep a while */
189         unsigned timeleft =
190             (next1.tv_sec - now.tv_sec) * 1000000
191            + next1.tv_usec - now.tv_usec;
192
193         usleep(timeleft);
194
195         // XXX : CHECK_SOUND(); S9xProcessEvents(FALSE);
196
197         while (gettimeofday(&now, 0) < 0);
198         /* Continue with a while-loop because usleep()
199          * could be interrupted by a signal
200          */
201     }
202
203     /* Calculate the timestamp of the next frame. */
204     next1.tv_usec += Settings.FrameTime;
205     if (next1.tv_usec >= 1000000)
206     {
207         next1.tv_sec += next1.tv_usec / 1000000;
208         next1.tv_usec %= 1000000;
209     }
210 }
211
212 /** Wraps s9xProcessEvents, taking care of kPollEveryNFrames */
213 static inline void pollEvents() {
214         static int frames = 0;
215         
216         if (++frames > kPollEveryNFrames) {
217                 S9xProcessEvents(FALSE);
218                 frames = 0;
219         }
220 }
221
222 /** Wraps HgwPollEvents, taking care of kPollHgwEveryNFrames */
223 static inline void pollHgwEvents() {
224         static int frames = 0;
225         
226         if (!hgwLaunched) return;
227         
228         if (++frames > kPollHgwEveryNFrames) {
229                 HgwPollEvents();
230                 frames = 0;
231         }
232 }
233
234 int main(int argc, const char ** argv) {        
235         // Initialise SDL
236         if (SDL_Init(0) < 0) 
237                 DIE("SDL_Init: %s", SDL_GetError());
238
239         // Configure snes9x
240         HgwInit();                                              // Hildon-games-wrapper initialization.
241         S9xLoadConfig(argc, argv);              // Load config files and parse cmd line.
242         HgwConfig();                                    // Apply specific hildon-games config.
243
244         // S9x initialization
245         S9xInitDisplay(argc, argv);
246         S9xInitAudioOutput();
247         S9xInitInputDevices();
248         S9xInit();
249         S9xReset();
250
251         // Load rom and related files: state, unfreeze if needed
252         loadRom();
253         resumeGame();
254
255         // Late initialization
256         sprintf(String, "DrNokSnes - %s", Memory.ROMName);
257         S9xSetTitle(String);
258         S9xHacksLoadFile(Config.hacksFile[0] ? Config.hacksFile : 0);
259         if (!S9xGraphicsInit())
260          DIE("S9xGraphicsInit failed");
261         S9xAudioOutputEnable(true);
262
263         do {
264                 frameSync();                    // May block, or set frameskip to true.
265                 S9xMainLoop();                  // Does CPU things, renders if needed.
266                 pollEvents();
267                 pollHgwEvents();
268         } while (!Config.quitting);
269         
270         // Deinitialization
271         S9xAudioOutputEnable(false);
272         S9xDeinitAudioOutput();
273         S9xDeinitDisplay();
274
275         // Save state
276         Memory.SaveSRAM(S9xGetFilename(FILE_SRAM));
277         pauseGame();
278
279         // Late deinitialization
280         S9xGraphicsDeinit();
281         Memory.Deinit();
282         S9xUnloadConfig();
283         HgwDeinit();
284
285         SDL_Quit();
286
287         return 0;
288 }
289
290 void S9xDoAction(unsigned char action)
291 {
292         if (action & kActionQuit) 
293                 Config.quitting = true;
294
295         if (action & kActionToggleFullscreen)
296                 S9xVideoToggleFullscreen();
297 }
298