]> icculus.org git repositories - btb/d2x.git/blob - main/movie.c
keep movies out of the hog namespace
[btb/d2x.git] / main / movie.c
1 /*
2 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
3 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
4 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
5 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
6 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
7 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
8 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
9 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
10 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
11 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
12 */
13
14 /*
15  *
16  * Movie Playing Stuff
17  *
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <conf.h>
22 #endif
23
24 #include <string.h>
25 #ifndef macintosh
26 # ifndef _WIN32_WCE
27 #  include <sys/types.h>
28 #  include <sys/stat.h>
29 #  include <fcntl.h>
30 # endif
31 # ifndef _MSC_VER
32 #  include <unistd.h>
33 # endif
34 #endif // ! macintosh
35 #include <ctype.h>
36
37 #include "args.h"
38 #include "key.h"
39 #include "inferno.h"
40 #include "strutil.h"
41 #include "dxxerror.h"
42 #include "u_mem.h"
43 #include "byteswap.h"
44 #include "gr.h"
45 #include "vid.h"
46 #include "cfile.h"
47 #include "libmve.h"
48 #include "physfsrwops.h"
49
50
51 // Subtitle data
52 typedef struct {
53         short first_frame,last_frame;
54         char *msg;
55 } subtitle;
56
57 #define MAX_SUBTITLES 500
58 #define MAX_ACTIVE_SUBTITLES 3
59 subtitle Subtitles[MAX_SUBTITLES];
60 int Num_subtitles;
61
62 // Movielib data
63
64 #ifdef D2_OEM
65 char movielib_files[5][FILENAME_LEN] = {"intro","other","robots","oem"};
66 #else
67 char movielib_files[4][FILENAME_LEN] = {"intro","other","robots"};
68 #endif
69
70 #define N_MOVIE_LIBS (sizeof(movielib_files) / sizeof(*movielib_files))
71 #define N_BUILTIN_MOVIE_LIBS (N_MOVIE_LIBS - 1)
72 #define EXTRA_ROBOT_LIB N_BUILTIN_MOVIE_LIBS
73
74 cvar_t MovieHires = { "MovieHires", "1", CVAR_ARCHIVE }; // default is highres
75
76 SDL_RWops *RoboFile = NULL;
77 MVE_videoSpec MVESpec;
78
79 // Function Prototypes
80 int RunMovie(char *filename, int highres_flag, int allow_abort,int dx,int dy);
81
82 void decode_text_line(char *p);
83 void draw_subtitles(int frame_num);
84
85 // Abstraction for movie files
86 static SDL_RWops *open_movie_file(const char *filename, int must_have);
87 #define read_movie_file(handle, buf, count) SDL_RWread((SDL_RWops *)handle, buf, 1, count);
88 #define reset_movie_file(handle) SDL_RWseek(handle, 0, SEEK_SET)
89 #define close_movie_file(handle) SDL_RWclose(handle)
90
91
92 // Callbacks for MVE lib
93
94 // ----------------------------------------------------------------------
95 void* MPlayAlloc(unsigned size)
96 {
97     return d_malloc(size);
98 }
99
100 void MPlayFree(void *p)
101 {
102     d_free(p);
103 }
104
105
106 //-----------------------------------------------------------------------
107
108 unsigned int FileRead(void *handle, void *buf, unsigned int count)
109 {
110     unsigned numread;
111     numread = read_movie_file(handle, buf, count);
112     return (numread == count);
113 }
114
115
116 //-----------------------------------------------------------------------
117
118
119 //filename will actually get modified to be either low-res or high-res
120 //returns status.  see values in movie.h
121 int PlayMovie(const char *filename, int must_have)
122 {
123         char name[FILENAME_LEN],*p;
124         int c, ret;
125
126         if (FindArg("-nomovies"))
127                 return MOVIE_NOT_PLAYED;
128
129         strcpy(name,filename);
130
131         if ((p=strchr(name,'.')) == NULL)               //add extension, if missing
132                 strcat(name,".mve");
133
134         //check for escape already pressed & abort if so
135         while ((c = newmenu_inkey()) != 0)
136                 if (c == KEY_ESC)
137                         return MOVIE_ABORTED;
138
139         // Stop all digital sounds currently playing.
140         digi_stop_all();
141
142         // Stop all songs
143         songs_stop_all();
144
145         digi_close();
146
147         // Start sound
148         if (!FindArg("-nosound"))
149                 MVE_sndInit(1);
150         else
151                 MVE_sndInit(-1);
152
153         ret = RunMovie(name, MovieHires.intval, must_have, -1, -1);
154
155         if (!FindArg("-nosound"))
156                 digi_init();
157
158         Screen_mode = -1;               //force screen reset
159
160         return ret;
161 }
162
163
164 void MovieShowFrame(ubyte *buf, uint bufw, uint bufh, uint sx, uint sy,
165                                         uint w, uint h, uint dstx, uint dsty)
166 {
167         grs_bitmap source_bm;
168         grs_canvas *dest_canv, *save_canv;
169
170         //mprintf((0,"MovieShowFrame %d,%d  %d,%d  %d,%d  %d,%d\n",bufw,bufh,sx,sy,w,h,dstx,dsty));
171
172         Assert(bufw == w && bufh == h);
173
174         source_bm.bm_x = source_bm.bm_y = 0;
175         source_bm.bm_w = source_bm.bm_rowsize = bufw;
176         source_bm.bm_h = bufh;
177         source_bm.bm_type = BM_LINEAR;
178         source_bm.bm_flags = 0;
179         source_bm.bm_data = buf;
180
181         if (menu_use_game_res.intval) {
182                 float aspect = (float)w / (float)h;
183
184                 if (RoboFile) {
185                         h = h * GHEIGHT / MVESpec.screenHeight;
186                         w = h * aspect;
187                         dstx = dstx * GWIDTH / MVESpec.screenWidth;
188                         dsty = dsty * GHEIGHT / MVESpec.screenHeight;
189                 } else {
190                         w = w * GWIDTH / MVESpec.screenWidth;
191                         h = w / aspect;
192                         dstx = dstx * GWIDTH / MVESpec.screenWidth;
193                         dsty = GHEIGHT / 2 - h / 2;
194                 }
195                 dest_canv = gr_create_sub_canvas(grd_curcanv, dstx, dsty, w, h);
196                 save_canv = grd_curcanv;
197                 gr_set_current_canvas(dest_canv);
198                 gr_bitmap_fullscr(&source_bm);
199                 gr_set_current_canvas(save_canv);
200                 gr_free_sub_canvas(dest_canv);
201         } else
202                 gr_bm_ubitblt(bufw,bufh,dstx,dsty,sx,sy,&source_bm,&grd_curcanv->cv_bitmap);
203 }
204
205 //our routine to set the pallete, called from the movie code
206 void MovieSetPalette(unsigned char *p, unsigned start, unsigned count)
207 {
208         if (count == 0)
209                 return;
210
211         //mprintf((0,"SetPalette p=%x, start=%d, count=%d\n",p,start,count));
212
213         //Color 0 should be black, and we get color 255
214         Assert(start>=1 && start+count-1<=254);
215
216         //Set color 0 to be black
217         gr_palette[0] = gr_palette[1] = gr_palette[2] = 0;
218
219         //Set color 255 to be our subtitle color
220         gr_palette[765] = gr_palette[766] = gr_palette[767] = 50;
221
222         //movie libs palette into our array
223         memcpy(gr_palette+start*3,p+start*3,count*3);
224
225         //finally set the palette in the hardware
226         gr_palette_load(gr_palette);
227
228         //MVE_SetPalette(p, start, count);
229 }
230
231
232 #if 0
233 typedef struct bkg {
234         short x, y, w, h;           // The location of the menu.
235         grs_bitmap * bmp;               // The background under the menu.
236 } bkg;
237
238 bkg movie_bg = {0,0,0,0,NULL};
239 #endif
240
241 #define BOX_BORDER (MenuHires?40:20)
242
243
244 void show_pause_message(char *msg)
245 {
246         int w,h,aw;
247         int x,y;
248
249         gr_set_current_canvas(NULL);
250         gr_set_curfont( SMALL_FONT );
251
252         gr_get_string_size(msg,&w,&h,&aw);
253
254         x = (grd_curscreen->sc_w-w)/2;
255         y = (grd_curscreen->sc_h-h)/2;
256
257 #if 0
258         if (movie_bg.bmp) {
259                 gr_free_bitmap(movie_bg.bmp);
260                 movie_bg.bmp = NULL;
261         }
262
263         // Save the background of the display
264         movie_bg.x=x; movie_bg.y=y; movie_bg.w=w; movie_bg.h=h;
265
266         movie_bg.bmp = gr_create_bitmap( w+BOX_BORDER, h+BOX_BORDER );
267
268         gr_bm_ubitblt(w+BOX_BORDER, h+BOX_BORDER, 0, 0, x-BOX_BORDER/2, y-BOX_BORDER/2, &(grd_curcanv->cv_bitmap), movie_bg.bmp );
269 #endif
270
271         gr_setcolor(0);
272         gr_rect(x-BOX_BORDER/2,y-BOX_BORDER/2,x+w+BOX_BORDER/2-1,y+h+BOX_BORDER/2-1);
273
274         gr_set_fontcolor( 255, -1 );
275
276         gr_ustring( 0x8000, y, msg );
277
278         vid_update();
279 }
280
281 void clear_pause_message()
282 {
283 #if 0
284         if (movie_bg.bmp) {
285
286                 gr_bitmap(movie_bg.x-BOX_BORDER/2, movie_bg.y-BOX_BORDER/2, movie_bg.bmp);
287
288                 gr_free_bitmap(movie_bg.bmp);
289                 movie_bg.bmp = NULL;
290         }
291 #endif
292 }
293
294
295 //returns status.  see movie.h
296 int RunMovie(char *filename, int hires_flag, int must_have,int dx,int dy)
297 {
298         SDL_RWops *filehndl;
299         int result=1,aborted=0;
300         int track = 0;
301         int frame_num;
302         int key;
303
304         result=1;
305
306         // Open Movie file.  If it doesn't exist, no movie, just return.
307
308         filehndl = open_movie_file(filename, must_have);
309
310         if (!filehndl)
311         {
312                 if (must_have)
313                         con_printf(CON_URGENT, "Can't open movie <%s>: %s\n", filename, PHYSFS_getLastError());
314                 return MOVIE_NOT_PLAYED;
315         }
316
317         MVE_memCallbacks(MPlayAlloc, MPlayFree);
318         MVE_ioCallbacks(FileRead);
319         MVE_sfCallbacks(MovieShowFrame);
320         MVE_palCallbacks(MovieSetPalette);
321
322         vid_set_mode(MOVIE_SCREEN_MODE);
323 #ifdef OGL
324         set_screen_mode(SCREEN_MENU);
325 #endif
326
327         if (MVE_rmPrepMovie((void *)filehndl, dx, dy, track)) {
328                 Int3();
329                 return MOVIE_NOT_PLAYED;
330         }
331
332         MVE_getVideoSpec(&MVESpec);
333
334         frame_num = 0;
335
336         FontHires = FontHiresAvailable && hires_flag;
337
338         while((result = MVE_rmStepMovie()) == 0) {
339
340                 draw_subtitles(frame_num);
341
342                 vid_update();
343
344                 key = newmenu_inkey();
345
346                 // If ESCAPE pressed, then quit movie.
347                 if (key == KEY_ESC) {
348                         result = aborted = 1;
349                         break;
350                 }
351
352                 // If PAUSE pressed, then pause movie
353                 if (key == KEY_PAUSE) {
354                         MVE_rmHoldMovie();
355                         show_pause_message(TXT_PAUSE);
356                         while (!newmenu_inkey()) ;
357                         clear_pause_message();
358                 }
359
360 #ifdef VID_SUPPORTS_FULLSCREEN_TOGGLE
361                 if ((key == KEY_COMMAND+KEY_SHIFTED+KEY_F) ||
362                         (key == KEY_ALTED+KEY_ENTER) ||
363                     (key == KEY_ALTED+KEY_PADENTER))
364                         vid_toggle_fullscreen();
365 #endif
366
367                 frame_num++;
368         }
369
370         Assert(aborted || result == MVE_ERR_EOF);        ///movie should be over
371
372     MVE_rmEndMovie();
373
374         close_movie_file(filehndl); // Close Movie File
375
376         // Restore old graphic state
377
378         Screen_mode=-1;  //force reset of screen mode
379
380         return (aborted?MOVIE_ABORTED:MOVIE_PLAYED_FULL);
381 }
382
383
384 int InitMovieBriefing()
385 {
386 #if 0
387         if (MenuHires)
388                 vid_set_mode(SM(640,480));
389         else
390                 vid_set_mode(SM(320,200));
391
392         gr_init_sub_canvas( &VR_screen_pages[0], &grd_curscreen->sc_canvas, 0, 0, grd_curscreen->sc_w, grd_curscreen->sc_h );
393         gr_init_sub_canvas( &VR_screen_pages[1], &grd_curscreen->sc_canvas, 0, 0, grd_curscreen->sc_w, grd_curscreen->sc_h );
394 #endif
395
396         return 1;
397 }
398
399
400 //returns 1 if frame updated ok
401 int RotateRobot()
402 {
403         int err;
404
405         err = MVE_rmStepMovie();
406
407         if (err == MVE_ERR_EOF)     //end of movie, so reset
408         {
409                 reset_movie_file(RoboFile);
410                 if (MVE_rmPrepMovie(RoboFile, MenuHires?280:140, MenuHires?200:80, 0))
411                 {
412                         Int3();
413                         return 0;
414                 }
415         }
416         else if (err) {
417                 Int3();
418                 return 0;
419         }
420
421         return 1;
422 }
423
424
425 void DeInitRobotMovie(void)
426 {
427         MVE_rmEndMovie();
428         close_movie_file(RoboFile); // Close Movie File
429         RoboFile = NULL;
430 }
431
432
433 int InitRobotMovie(char *filename)
434 {
435         if (FindArg("-nomovies"))
436                 return 0;
437
438         con_printf(CON_DEBUG, "RoboFile=%s\n", filename);
439
440         MVE_sndInit(-1);        //tell movies to play no sound for robots
441
442         MVE_memCallbacks(MPlayAlloc, MPlayFree);
443         MVE_ioCallbacks(FileRead);
444
445         RoboFile = open_movie_file(filename, 1);
446
447         if (!RoboFile)
448         {
449                 con_printf(CON_URGENT, "Can't open movie <%s>: %s\n", filename, PHYSFS_getLastError());
450                 return MOVIE_NOT_PLAYED;
451         }
452
453         MVE_palCallbacks(MovieSetPalette);
454         MVE_sfCallbacks(MovieShowFrame);
455
456         if (MVE_rmPrepMovie((void *)RoboFile, MenuHires?280:140, MenuHires?200:80, 0)) {
457                 Int3();
458                 return 0;
459         }
460
461         MVE_getVideoSpec(&MVESpec);
462
463         return 1;
464 }
465
466
467 /*
468  *              Subtitle system code
469  */
470
471 char *subtitle_raw_data;
472
473
474 //search for next field following whitespace 
475 char *next_field (char *p)
476 {
477         while (*p && !isspace(*p))
478                 p++;
479
480         if (!*p)
481                 return NULL;
482
483         while (*p && isspace(*p))
484                 p++;
485
486         if (!*p)
487                 return NULL;
488
489         return p;
490 }
491
492
493 int init_subtitles(char *filename)
494 {
495         CFILE *ifile;
496         int size,read_count;
497         char *p;
498         int have_binary = 0;
499
500         Num_subtitles = 0;
501
502         if (! FindArg("-subtitles"))
503                 return 0;
504
505         ifile = cfopen(filename,"rb");          //try text version
506
507         if (!ifile) {                                                           //no text version, try binary version
508                 char filename2[FILENAME_LEN];
509                 change_filename_extension(filename2, filename, ".TXB");
510                 ifile = cfopen(filename2,"rb");
511                 if (!ifile)
512                         return 0;
513                 have_binary = 1;
514         }
515
516         size = cfilelength(ifile);
517
518         MALLOC (subtitle_raw_data, char, size+1);
519
520         read_count = cfread(subtitle_raw_data, 1, size, ifile);
521
522         cfclose(ifile);
523
524         subtitle_raw_data[size] = 0;
525
526         if (read_count != size) {
527                 d_free(subtitle_raw_data);
528                 return 0;
529         }
530
531         p = subtitle_raw_data;
532
533         while (p && p < subtitle_raw_data+size) {
534                 char *endp;
535
536                 endp = strchr(p,'\n'); 
537                 if (endp) {
538                         if (endp[-1] == '\r')
539                                 endp[-1] = 0;           //handle 0d0a pair
540                         *endp = 0;                      //string termintor
541                 }
542
543                 if (have_binary)
544                         decode_text_line(p);
545
546                 if (*p != ';') {
547                         Subtitles[Num_subtitles].first_frame = atoi(p);
548                         p = next_field(p); if (!p) continue;
549                         Subtitles[Num_subtitles].last_frame = atoi(p);
550                         p = next_field(p); if (!p) continue;
551                         Subtitles[Num_subtitles].msg = p;
552
553                         Assert(Num_subtitles==0 || Subtitles[Num_subtitles].first_frame >= Subtitles[Num_subtitles-1].first_frame);
554                         Assert(Subtitles[Num_subtitles].last_frame >= Subtitles[Num_subtitles].first_frame);
555
556                         Num_subtitles++;
557                 }
558
559                 p = endp+1;
560
561         }
562
563         return 1;
564 }
565
566
567 void close_subtitles()
568 {
569         if (subtitle_raw_data)
570                 d_free(subtitle_raw_data);
571         subtitle_raw_data = NULL;
572         Num_subtitles = 0;
573 }
574
575
576 //draw the subtitles for this frame
577 void draw_subtitles(int frame_num)
578 {
579         static int active_subtitles[MAX_ACTIVE_SUBTITLES];
580         static int num_active_subtitles,next_subtitle,line_spacing;
581         int t,y;
582         int must_erase=0;
583
584         if (frame_num == 0) {
585                 num_active_subtitles = 0;
586                 next_subtitle = 0;
587                 gr_set_curfont( GAME_FONT );
588                 line_spacing = grd_curcanv->cv_font->ft_h + (grd_curcanv->cv_font->ft_h >> 2);
589                 gr_set_fontcolor(255,-1);
590         }
591
592         //get rid of any subtitles that have expired
593         for (t=0;t<num_active_subtitles;)
594                 if (frame_num > Subtitles[active_subtitles[t]].last_frame) {
595                         int t2;
596                         for (t2=t;t2<num_active_subtitles-1;t2++)
597                                 active_subtitles[t2] = active_subtitles[t2+1];
598                         num_active_subtitles--;
599                         must_erase = 1;
600                 }
601                 else
602                         t++;
603
604         //get any subtitles new for this frame 
605         while (next_subtitle < Num_subtitles && frame_num >= Subtitles[next_subtitle].first_frame) {
606                 if (num_active_subtitles >= MAX_ACTIVE_SUBTITLES)
607                         Error("Too many active subtitles!");
608                 active_subtitles[num_active_subtitles++] = next_subtitle;
609                 next_subtitle++;
610         }
611
612         //find y coordinate for first line of subtitles
613         y = grd_curcanv->cv_bitmap.bm_h-((line_spacing+1)*MAX_ACTIVE_SUBTITLES+2);
614
615         //erase old subtitles if necessary
616         if (must_erase) {
617                 gr_setcolor(0);
618                 gr_rect(0,y,grd_curcanv->cv_bitmap.bm_w-1,grd_curcanv->cv_bitmap.bm_h-1);
619         }
620
621         //now draw the current subtitles
622         for (t=0;t<num_active_subtitles;t++)
623                 if (active_subtitles[t] != -1) {
624                         gr_string(0x8000,y,Subtitles[active_subtitles[t]].msg);
625                         y += line_spacing+1;
626                 }
627 }
628
629
630 //find the specified movie library, and read in list of movies in it
631 static int init_movie_lib(char *filename)
632 {
633         //note: this based on cfile_init_hogfile()
634
635         char pathname[PATH_MAX];
636
637         if (!PHYSFSX_getRealPath(filename, pathname))
638                 return 0;
639
640         return PHYSFS_mount(pathname, "movies", 1);
641 }
642
643
644 void close_movie(char *movielib, int is_robots)
645 {
646         int high_res;
647         char filename[FILENAME_LEN];
648         char pathname[PATH_MAX];
649
650         if (is_robots)
651                 high_res = MenuHiresAvailable;
652         else
653                 high_res = MovieHires.intval;
654
655         sprintf(filename, "%s-%s.mvl", movielib, high_res?"h":"l");
656
657         if (!PHYSFSX_getRealPath(filename, pathname) ||
658                 !PHYSFS_removeFromSearchPath(pathname))
659         {
660                 con_printf(CON_URGENT, "Can't close movielib <%s>: %s\n", filename, PHYSFS_getLastError());
661                 sprintf(filename, "%s-%s.mvl", movielib, high_res?"l":"h");
662
663                 if (!PHYSFSX_getRealPath(filename, pathname) ||
664                         !PHYSFS_removeFromSearchPath(pathname))
665                         con_printf(CON_URGENT, "Can't close movielib <%s>: %s\n", filename, PHYSFS_getLastError());
666         }
667 }
668
669 void close_movies()
670 {
671         int i, is_robots;
672
673         for (i = 0 ; i < N_BUILTIN_MOVIE_LIBS ; i++)
674         {
675                 if (!strnicmp(movielib_files[i], "robot", 5))
676                         is_robots = 1;
677                 else
678                         is_robots = 0;
679
680                 close_movie(movielib_files[i], is_robots);
681         }
682 }
683
684
685 void init_movie(char *movielib, int is_robots, int required)
686 {
687         int high_res;
688         char filename[FILENAME_LEN];
689
690         //for robots, load highres versions if highres menus set
691         if (is_robots)
692                 high_res = MenuHiresAvailable;
693         else
694                 high_res = MovieHires.intval;
695
696         sprintf(filename, "%s-%s.mvl", movielib, high_res?"h":"l");
697
698         if (!init_movie_lib(filename))
699         {
700                 if (required)
701                         con_printf(CON_URGENT, "Can't open movielib <%s>: %s\n", filename, PHYSFS_getLastError());
702
703                 sprintf(filename, "%s-%s.mvl", movielib, high_res?"l":"h");
704
705                 if (!init_movie_lib(filename))
706                         if (required)
707                                 con_printf(CON_URGENT, "Can't open movielib <%s>: %s\n", filename, PHYSFS_getLastError());
708         }
709 }
710
711
712 //find and initialize the movie libraries
713 void init_movies()
714 {
715         int i;
716         int is_robots;
717
718         if (FindArg("-nomovies"))
719                 return;
720
721         for (i=0;i<N_BUILTIN_MOVIE_LIBS;i++) {
722
723                 if (!strnicmp(movielib_files[i],"robot",5))
724                         is_robots = 1;
725                 else
726                         is_robots = 0;
727
728                 init_movie(movielib_files[i], is_robots, 1);
729         }
730
731         atexit(close_movies);
732 }
733
734
735 void close_extra_robot_movie(void)
736 {
737         if (strlen(movielib_files[EXTRA_ROBOT_LIB]))
738                 close_movie(movielib_files[EXTRA_ROBOT_LIB], 1);
739 }
740
741 void init_extra_robot_movie(char *movielib)
742 {
743         if (FindArg("-nomovies"))
744                 return;
745
746         close_extra_robot_movie();
747         init_movie(movielib, 1, 0);
748         strcpy(movielib_files[EXTRA_ROBOT_LIB], movielib);
749         atexit(close_extra_robot_movie);
750 }
751
752
753 //returns file handle
754 static SDL_RWops *open_movie_file(const char *filename, int must_have)
755 {
756         char moviepath[PATH_MAX];
757
758         strcpy(moviepath, "movies/");
759         strcat(moviepath, filename);
760
761         return PHYSFSRWOPS_openRead(moviepath);
762 }