1 // Crimson Fields -- a game of tactical warfare
2 // Copyright (C) 2000-2007 Jens Granseuer
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 //
18 
19 ////////////////////////////////////////////////////////////////////////
20 // fileio.cpp
21 //
22 //  * additional code for Win32 by Adam Gates <radad@xoasis.com>
23 //  * additional code for WinCE/Unicode by Maik Stohn <maik@stohn.de>
24 ////////////////////////////////////////////////////////////////////////
25 
26 #ifndef _WIN32_WCE
27 # include <sys/stat.h>
28 #endif
29 
30 #ifdef WIN32
31 # include <windows.h>
32 # include <shlobj.h>
33 # ifndef _WIN32_WCE
34 #  include <shellapi.h>
35 # endif
36 #endif
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 
42 #include "SDL_zlib.h"
43 #include "fileio.h"
44 #include "globals.h"      // for CF_SHORTNAME
45 
46 ////////////////////////////////////////////////////////////////////////
47 // NAME       : Directory::Directory
48 // DESCRIPTION: Open a directory and initialize the first directory
49 //              entry for reading. Whether the directory was
50 //              successfully opened can be checked using
51 //              Directory::IsValid().
52 // PARAMETERS : dir - name of the directory to open
53 // RETURNS    : -
54 ////////////////////////////////////////////////////////////////////////
55 
Directory(const char * dir)56 Directory::Directory( const char *dir ) {
57 #ifdef WIN32
58   string search( dir );
59   append_path_delim( search );
60   search += "*.*";
61 
62 # ifdef UNICODE                     // unicode handling for WindowsCE
63   WCHAR *wsearch = new WCHAR[search.length()+1];
64 
65   size_t len = mbstowcs( wsearch, search.c_str(), search.length() );
66   if ( len < 0 ) len = 0;
67   wsearch[len] = 0;
68 
69   m_Dir = FindFirstFile( wsearch, &m_Entry );
70 
71   delete [] wsearch;
72 # else
73   m_Dir = FindFirstFile( search.c_str(), &m_Entry );
74 # endif
75 #else
76   m_Entry = NULL;
77   m_Dir = opendir( dir );
78 
79   if ( m_Dir ) m_Entry = readdir( m_Dir );
80 #endif
81 }
82 
83 ////////////////////////////////////////////////////////////////////////
84 // NAME       : Directory::~Directory
85 // DESCRIPTION: Close a directory.
86 // PARAMETERS : -
87 // RETURNS    : -
88 ////////////////////////////////////////////////////////////////////////
89 
~Directory(void)90 Directory::~Directory( void ) {
91 #ifdef WIN32
92   if ( m_Dir ) FindClose( m_Dir );
93 #else
94   if ( m_Dir ) closedir( m_Dir );
95 #endif
96 }
97 
98 ////////////////////////////////////////////////////////////////////////
99 // NAME       : Directory::GetFileName
100 // DESCRIPTION: Get the name of the currently selected file in the
101 //              directory.
102 // PARAMETERS : -
103 // RETURNS    : pointer to file name or NULL if end of directory reached
104 //              (or not opened).
105 ////////////////////////////////////////////////////////////////////////
106 
GetFileName(void) const107 const char *Directory::GetFileName( void ) const {
108 #ifdef WIN32
109 # ifdef UNICODE                            // unicode handling for WindowsCE
110   size_t len = wcstombs( (char *)m_AsciiDir, m_Entry.cFileName, wcslen(m_Entry.cFileName) );
111   if ( len < 0 ) len = 0;
112   ((char *)m_AsciiDir)[len] = 0;
113   return m_AsciiDir;
114 # else
115   return m_Entry.cFileName;
116 # endif
117 #else
118   if ( m_Entry ) return m_Entry->d_name;
119   else return NULL;
120 #endif
121 }
122 
123 ////////////////////////////////////////////////////////////////////////
124 // NAME       : Directory::GetFileNameLen
125 // DESCRIPTION: Get the length of the currently selected file name.
126 // PARAMETERS : -
127 // RETURNS    : length of file name in characters
128 ////////////////////////////////////////////////////////////////////////
129 
GetFileNameLen(void) const130 size_t Directory::GetFileNameLen( void ) const {
131 #ifdef WIN32
132 # ifdef UNICODE
133   return wcslen( m_Entry.cFileName );
134 # else
135   return strlen( m_Entry.cFileName );
136 # endif
137 #else
138   if ( m_Entry ) {
139 # ifdef HAVE_DIRENT_H
140     return strlen( m_Entry->d_name );
141 # else
142     return m_Entry->d_namlen;
143 # endif
144   }
145   return 0;
146 #endif
147 }
148 
149 ////////////////////////////////////////////////////////////////////////
150 // NAME       : Directory::IsFileHidden
151 // DESCRIPTION: Determine whether the currently selected file is a
152 //              hidden file.
153 // PARAMETERS : -
154 // RETURNS    : TRUE if file is a hidden file, FALSE otherwise
155 ////////////////////////////////////////////////////////////////////////
156 
IsFileHidden(void) const157 bool Directory::IsFileHidden( void ) const {
158 #ifdef WIN32
159   return (m_Entry.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0;
160 #else
161   if ( m_Entry ) return m_Entry->d_name[0] == '.';
162   else return false;
163 #endif
164 }
165 
166 ////////////////////////////////////////////////////////////////////////
167 // NAME       : Directory::NextFile
168 // DESCRIPTION: Initialize the next file in the directory for
169 //              examination.
170 // PARAMETERS : -
171 // RETURNS    : TRUE if another file was found, FALSE if the end of the
172 //              directory was reached.
173 ////////////////////////////////////////////////////////////////////////
174 
NextFile(void)175 bool Directory::NextFile( void ) {
176 #ifdef WIN32
177   return FindNextFile( m_Dir, &m_Entry ) != FALSE;
178 #else
179   if ( m_Dir ) m_Entry = readdir( m_Dir );
180   return m_Entry != NULL;
181 #endif
182 }
183 
184 
185 ////////////////////////////////////////////////////////////////////////
186 // NAME       : File::Close
187 // DESCRIPTION: Close the file handle.
188 // PARAMETERS : -
189 // RETURNS    : -
190 ////////////////////////////////////////////////////////////////////////
191 
Close(void)192 void File::Close( void ) {
193   if ( fh != 0 ) {
194     SDL_RWclose( fh );
195     fh = 0;
196   }
197 }
198 
199 ////////////////////////////////////////////////////////////////////////
200 // NAME       : File::Open
201 // DESCRIPTION: Open the file.
202 // PARAMETERS : mode       - file access descriptors (r, w, b)
203 //              compressed - whether to open for compression. This
204 //                           parameter should only be used when opening
205 //                           a file for writing. Default is true.
206 // RETURNS    : TRUE on success, FALSE on error
207 ////////////////////////////////////////////////////////////////////////
208 
Open(const char * mode,bool compressed)209 bool File::Open( const char *mode, bool compressed /* = true */ ) {
210   bool rc = false;
211 
212   if ( !fh ) {
213     if ( compressed ) fh = SDL_RWFromGzip( name.c_str(), mode );
214     else fh = SDL_RWFromFile( name.c_str(), mode );
215     rc = (fh != 0);
216   }
217 
218   return rc;
219 }
220 
221 ////////////////////////////////////////////////////////////////////////
222 // NAME       : File::OpenData
223 // DESCRIPTION: Open the file. The "path" parameter passed to the
224 //              constructor is treated as a path relative to the data
225 //              (sub)directory used by the game. This method first tries
226 //              to open the file from the user's home directory (if
227 //              available), and only if this attempt fails the file is
228 //              loaded from the system data directory. After a
229 //              successful call, File::Name() returns the full path to
230 //              the file.
231 // PARAMETERS : subdir     - data subdirectory to look in
232 //              mode       - file access descriptors (r, w, b)
233 //              compressed - whether to open for compression. This
234 //                           parameter should only be used when opening
235 //                           a file for writing. Default is true.
236 // RETURNS    : TRUE on success, FALSE on error
237 ////////////////////////////////////////////////////////////////////////
238 
OpenData(const char * mode,const string & subdir,bool compressed)239 bool File::OpenData( const char *mode, const string &subdir /* = "" */,
240                      bool compressed /* = true */ ) {
241   bool rc = false;
242 
243   if ( !fh ) {
244     string local( name );
245     name = get_home_dir();
246     if ( !name.empty() ) {
247       append_path( name, subdir );
248       append_path( name, local );
249       rc = Open( mode, compressed );
250     }
251 
252     if ( !rc ) {
253       name = get_data_subdir( subdir );
254       append_path( name, local );
255       rc = Open( mode, compressed );
256     }
257   }
258 
259   return rc;
260 }
261 
262 ////////////////////////////////////////////////////////////////////////
263 // NAME       : File::Exists
264 // DESCRIPTION: Check whether the filename is already in use.
265 // PARAMETERS : name - file name to check for
266 // RETURNS    : TRUE if file exists, FALSE otherwise
267 ////////////////////////////////////////////////////////////////////////
268 
Exists(const string & name)269 bool File::Exists( const string &name ) {
270   // try to open the file
271   FILE *fd = fopen( name.c_str(), "r" );
272   if ( fd ) fclose( fd );
273   return fd != NULL;
274 }
275 
276 ////////////////////////////////////////////////////////////////////////
277 // NAME       : File::Read8
278 // DESCRIPTION: Read a byte from the buffer.
279 // PARAMETERS : -
280 // RETURNS    : byte read
281 ////////////////////////////////////////////////////////////////////////
282 
Read8(void)283 unsigned char File::Read8( void ) {
284   unsigned char val;
285   Read( &val, 1 );
286   return val;
287 }
288 
289 ////////////////////////////////////////////////////////////////////////
290 // NAME       : MemBuffer::ReadS
291 // DESCRIPTION: Read a string from the buffer.
292 // PARAMETERS : size - number of bytes to read
293 // RETURNS    : string read
294 ////////////////////////////////////////////////////////////////////////
295 
ReadS(int size)296 string MemBuffer::ReadS( int size ) {
297   string str;
298 
299   for ( int i = 0; i < size; ++i )
300     str += Read8();
301 
302   return str;
303 }
304 
305 ////////////////////////////////////////////////////////////////////////
306 // NAME       : MemBuffer::WriteS
307 // DESCRIPTION: Write a string to the buffer.
308 // PARAMETERS : value - string to write
309 //              len   - minimum number of bytes to write. If value is
310 //                      shorter than len, a number of NUL-bytes are
311 //                      written to the buffer.
312 // RETURNS    : -1 on error
313 ////////////////////////////////////////////////////////////////////////
314 
WriteS(string value,int len)315 int MemBuffer::WriteS( string value, int len /* = 0 */ ) {
316   int size = value.size();
317   int rc = Write( value.c_str(), size );
318 
319   while ( (size < len) && (rc == 0) ) {
320     rc = Write8( 0 );
321     ++size;
322   }
323   return rc;
324 }
325 
326 
327 ////////////////////////////////////////////////////////////////////////
328 // NAME       : append_path_delim
329 // DESCRIPTION: Append a path delimiter to the end of the string if
330 //              it isn't already there.
331 // PARAMETERS : path - string containing a path
332 // RETURNS    : -
333 ////////////////////////////////////////////////////////////////////////
334 
append_path_delim(string & path)335 void append_path_delim( string &path ) {
336   if ( path.at( path.length() - 1 ) != PATHDELIM ) path += PATHDELIM;
337 }
338 
339 ////////////////////////////////////////////////////////////////////////
340 // NAME       : make_dir
341 // DESCRIPTION: Create a directory.
342 // PARAMETERS : dir - name of the directory to create
343 // RETURNS    : -
344 ////////////////////////////////////////////////////////////////////////
345 
make_dir(const char * dir)346 void make_dir( const char *dir ) {
347 #ifdef WIN32
348 # ifdef UNICODE
349   WCHAR *wdir = new WCHAR[strlen(dir)+1];
350   size_t len = mbstowcs( wdir, dir, strlen(dir) );
351   if ( len < 0 ) len = 0;
352   wdir[len] = 0;
353   CreateDirectory( wdir, NULL );
354   delete [] wdir;
355 # else
356   CreateDirectory( dir, NULL );
357 # endif
358 #else
359   mkdir( dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH );
360 #endif
361 }
362 
363 ////////////////////////////////////////////////////////////////////////
364 // NAME       : create_config_dir
365 // DESCRIPTION: Create a configuration and save directory in the user's
366 //              home dir (UNIX & Co.) or his My Documents folder
367 //              (Win32).
368 // PARAMETERS : -
369 // RETURNS    : -
370 ////////////////////////////////////////////////////////////////////////
371 
create_config_dir(void)372 void create_config_dir( void ) {
373   string confd = get_config_dir();
374 
375   // does the directory exist?
376   Directory cfdir( confd.c_str() );
377   if ( !cfdir.IsValid() ) make_dir( confd.c_str() );
378 
379   // now check for the saved games directory
380   append_path( confd, "games" );
381 
382   Directory svdir( confd.c_str() );
383   if ( !svdir.IsValid() ) make_dir( confd.c_str() );
384 }
385 
386 ////////////////////////////////////////////////////////////////////////
387 // NAME       : get_home_dir
388 // DESCRIPTION: Get the name of the user's home directory.
389 // PARAMETERS : -
390 // RETURNS    : the user's home directory if any
391 ////////////////////////////////////////////////////////////////////////
392 
get_home_dir(void)393 string get_home_dir( void ) {
394 #ifdef WIN32
395   TCHAR dir[MAX_PATH];
396   SHGetSpecialFolderPath( NULL, dir, CSIDL_PERSONAL, TRUE );
397 # ifdef UNICODE
398   char mbdir[MAX_PATH];
399   wcstombs( mbdir, dir, wcslen(dir)+1 );
400   return mbdir;
401 # else
402   return dir;
403 # endif
404 #elif defined __BEOS__
405   return "./";
406 #else
407   return getenv( "HOME" );
408 #endif
409 }
410 
411 ////////////////////////////////////////////////////////////////////////
412 // NAME       : get_config_dir
413 // DESCRIPTION: Get the name of the configuration directory.
414 // PARAMETERS : -
415 // RETURNS    : config directory
416 ////////////////////////////////////////////////////////////////////////
417 
get_config_dir(void)418 string get_config_dir( void ) {
419   string confd;
420   string homed( get_home_dir() );
421 
422 #ifdef WIN32
423   if ( homed.empty() ) confd = get_data_dir();
424 #else
425   if ( homed.empty() ) confd.append( CURRENTDIR );
426 #endif
427   else {
428     confd.append( homed );
429     append_path_delim( confd );
430 #ifndef WIN32
431     confd += '.';
432 #endif
433     confd.append( CF_SHORTNAME );
434   }
435   append_path_delim( confd );
436 
437   return confd;
438 }
439 
440 ////////////////////////////////////////////////////////////////////////
441 // NAME       : get_data_dir
442 // DESCRIPTION: Get the name of the directory containing the data files.
443 // PARAMETERS : -
444 // RETURNS    : data directory name
445 ////////////////////////////////////////////////////////////////////////
446 
get_data_dir(void)447 string get_data_dir( void ) {
448 
449 #ifdef WIN32
450   char dir[MAX_PATH];
451 
452 # ifdef UNICODE
453   WCHAR wdir[MAX_PATH];
454   GetModuleFileName( NULL, wdir, MAX_PATH );
455   size_t len = wcstombs( dir, wdir, wcslen(wdir) );
456   if( len < 0 ) len = 0;
457   dir[len] = 0;
458 # else
459   GetModuleFileName( NULL, dir, MAX_PATH );
460 # endif
461   {
462     // Remove the file name
463     char *l = dir;
464     char *c = dir;
465     while( *c != '\0' ) {
466       if ( *c == PATHDELIM ) l = c;
467       ++c;
468     }
469     ++l;
470     *l = '\0';
471   }
472   return dir;
473 #elif defined CF_DATADIR
474   return CF_DATADIR;
475 #else
476   return CURRENTDIR;
477 #endif
478 }
479 
480 ////////////////////////////////////////////////////////////////////////
481 // NAME       : get_save_dir
482 // DESCRIPTION: Get the name of the directory to save games in.
483 // PARAMETERS : -
484 // RETURNS    : saved games directory
485 ////////////////////////////////////////////////////////////////////////
486 
get_save_dir(void)487 string get_save_dir( void ) {
488   string saved( get_config_dir() );
489 
490   saved.append( "games" );
491   append_path_delim( saved );
492 
493   return saved;
494 }
495 
496 ////////////////////////////////////////////////////////////////////////
497 // NAME       : get_data_subdir
498 // DESCRIPTION: Get the full path of a data subdirectory.
499 // PARAMETERS : sub - name of the subdirectory
500 // RETURNS    : full path to directory (including trailing slash)
501 ////////////////////////////////////////////////////////////////////////
502 
get_data_subdir(const string & sub)503 string get_data_subdir( const string &sub ) {
504   string d( get_data_dir() );
505   append_path( d, sub );
506   append_path_delim( d );
507   return d;
508 }
509 
510 ////////////////////////////////////////////////////////////////////////
511 // NAME       : get_sfx_dir
512 // DESCRIPTION: Get the name of the directory containing the sound
513 //              effects.
514 // PARAMETERS : -
515 // RETURNS    : sound effects directory
516 ////////////////////////////////////////////////////////////////////////
517 
get_sfx_dir(void)518 string get_sfx_dir( void ) {
519   return get_data_subdir( "sound" );
520 }
521 
522 ////////////////////////////////////////////////////////////////////////
523 // NAME       : get_music_dir
524 // DESCRIPTION: Get the name of the directory containing the music
525 //              tracks.
526 // PARAMETERS : -
527 // RETURNS    : soundtracks directory
528 ////////////////////////////////////////////////////////////////////////
529 
get_music_dir(void)530 string get_music_dir( void ) {
531   return get_data_subdir( "music" );
532 }
533 
534 ////////////////////////////////////////////////////////////////////////
535 // NAME       : get_levels_dir
536 // DESCRIPTION: Get the name of the directory containing the map files.
537 // PARAMETERS : -
538 // RETURNS    : levels directory name
539 ////////////////////////////////////////////////////////////////////////
540 
get_levels_dir(void)541 string get_levels_dir( void ) {
542   return get_data_subdir( "levels" );
543 }
544 
545 ////////////////////////////////////////////////////////////////////////
546 // NAME       : get_locale_dir
547 // DESCRIPTION: Get the name of the directory containing the language
548 //              data files.
549 // PARAMETERS : -
550 // RETURNS    : locale directory name
551 ////////////////////////////////////////////////////////////////////////
552 
get_locale_dir(void)553 string get_locale_dir( void ) {
554   return get_data_subdir( "locale" );
555 }
556 
557 ////////////////////////////////////////////////////////////////////////
558 // NAME       : get_home_levels_dir
559 // DESCRIPTION: Get the name of an optional directory in the user's home
560 //              directory containing additional map files.
561 // PARAMETERS : -
562 // RETURNS    : levels directory name or an empty string if it does not
563 //              exist
564 ////////////////////////////////////////////////////////////////////////
565 
get_home_levels_dir(void)566 string get_home_levels_dir( void ) {
567   string hld("");
568 
569 #ifndef WIN32
570   string homed( get_home_dir() );
571   if ( !homed.empty() ) {
572     hld = get_config_dir();
573     hld.append( "levels" );
574     append_path_delim( hld );
575   }
576 #endif
577 
578   return hld;
579 }
580 
581 ////////////////////////////////////////////////////////////////////////
582 // DESCRIPTION: Extract the name of a file from a given path.
583 // PARAMETERS : path - filename (including full or partial path)
584 // RETURNS    : file part of the path
585 ////////////////////////////////////////////////////////////////////////
586 
file_part(const string & path)587 string file_part( const string &path ) {
588   int pathend = path.rfind( PATHDELIM );
589   return path.substr( pathend + 1 );
590 }
591 
592 ////////////////////////////////////////////////////////////////////////
593 // NAME       : append_path
594 // DESCRIPTION: Add another subpath to an existing path. Also add path
595 //              delimiter if required.
596 // PARAMETERS : path - existing path to be extended
597 //              sub  - subpath to be added
598 // RETURNS    : -
599 ////////////////////////////////////////////////////////////////////////
600 
append_path(string & path,const string & sub)601 void append_path( string &path, const string &sub ) {
602   append_path_delim( path );
603   path.append( sub );
604 }
605 
606