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