1 // Emacs style mode select   -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: i_system.cpp 4556 2014-02-10 02:48:10Z dr_sean $
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 // Copyright (C) 2006-2014 by The Odamex Team.
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // DESCRIPTION:
20 //	System specific interface stuff.
21 //
22 //-----------------------------------------------------------------------------
23 
24 #include <sstream>
25 #include <limits>
26 
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <cstring>
30 #include <stdarg.h>
31 #include <math.h>
32 
33 #ifdef OSX
34 #include <mach/clock.h>
35 #include <mach/mach.h>
36 #endif
37 
38 #include "win32inc.h"
39 #ifdef _WIN32
40     #include <conio.h>
41     #include <io.h>
42     #include <process.h>
43     #include <mmsystem.h>
44     #include <direct.h> // SoM: I don't know HOW this has been overlooked until now...
45 	#ifndef _XBOX
46 		#include <winsock2.h>
47 	#endif  // !_XBOX
48 #endif
49 
50 #ifdef UNIX
51 #define HAVE_PWD_H
52 
53 #include <fnmatch.h>
54 #include <sys/stat.h>
55 #include <sys/types.h>
56 #include <sys/time.h>
57 #include <pwd.h>
58 #include <unistd.h>
59 #include <limits.h>
60 
61 #endif
62 
63 #include "errors.h"
64 
65 #include "doomtype.h"
66 #include "version.h"
67 #include "doomdef.h"
68 #include "cmdlib.h"
69 #include "m_argv.h"
70 #include "m_misc.h"
71 
72 #include "d_main.h"
73 #include "d_net.h"
74 #include "g_game.h"
75 #include "i_system.h"
76 #include "i_net.h"
77 #include "c_dispatch.h"
78 #include "sv_main.h"
79 
80 #ifdef _WIN32
81 UINT TimerPeriod;
82 #endif
83 
84 ticcmd_t emptycmd;
I_BaseTiccmd(void)85 ticcmd_t *I_BaseTiccmd(void)
86 {
87 	return &emptycmd;
88 }
89 
90 /* [Russell] - Modified to accomodate a minimal allowable heap size */
91 // These values are in megabytes
92 size_t def_heapsize = 64;
93 const size_t min_heapsize = 8;
94 
95 // The size we got back from I_ZoneBase in megabytes
96 size_t got_heapsize = 0;
97 
98 DWORD LanguageIDs[4];
99 
100 //
101 // I_MegabytesToBytes
102 //
103 // Returns the megabyte value of size in bytes
I_MegabytesToBytes(size_t Megabytes)104 size_t I_MegabytesToBytes (size_t Megabytes)
105 {
106 	return (Megabytes*1024*1024);
107 }
108 
109 //
110 // I_BytesToMegabytes
111 //
112 // Returns the byte value of size in megabytes
I_BytesToMegabytes(size_t Bytes)113 size_t I_BytesToMegabytes (size_t Bytes)
114 {
115 	if (!Bytes)
116         return 0;
117 
118     return (Bytes/1024/1024);
119 }
120 
121 //
122 // I_ZoneBase
123 //
124 // Allocates a portion of system memory for the Zone Memory Allocator, returns
125 // the 'size' of what it could allocate in its parameter
I_ZoneBase(size_t * size)126 void *I_ZoneBase (size_t *size)
127 {
128 	void *zone = NULL;
129 
130     // User wanted a different default size
131 	const char *p = Args.CheckValue ("-heapsize");
132 
133 	if (p)
134 		def_heapsize = atoi(p);
135 
136     if (def_heapsize < min_heapsize)
137         def_heapsize = min_heapsize;
138 
139     // Set the size
140 	*size = I_MegabytesToBytes(def_heapsize);
141 
142     // Allocate the def_heapsize, otherwise try to allocate a smaller amount
143 	while ((zone == NULL) && (*size >= I_MegabytesToBytes(min_heapsize)))
144 	{
145 	    zone = malloc (*size);
146 
147 	    if (zone != NULL)
148             break;
149 
150         *size -= I_MegabytesToBytes(1);
151 	}
152 
153     // Our heap size we received
154     got_heapsize = I_BytesToMegabytes(*size);
155 
156     // Die if the system has insufficient memory
157     if (got_heapsize < min_heapsize)
158         I_FatalError("I_ZoneBase: Insufficient memory available! Minimum size "
159                      "is %lu MB but got %lu MB instead",
160                      min_heapsize,
161                      got_heapsize);
162 
163 	return zone;
164 }
165 
I_BeginRead(void)166 void I_BeginRead(void)
167 {
168 }
169 
I_EndRead(void)170 void I_EndRead(void)
171 {
172 }
173 
174 //
175 // I_GetTime
176 //
177 // [SL] Retrieve an arbitrarily-based time from a high-resolution timer with
178 // nanosecond accuracy.
179 //
I_GetTime()180 dtime_t I_GetTime()
181 {
182 #if defined OSX
183 	clock_serv_t cclock;
184 	mach_timespec_t mts;
185 
186 	host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
187 	clock_get_time(cclock, &mts);
188 	mach_port_deallocate(mach_task_self(), cclock);
189 	return mts.tv_sec * 1000LL * 1000LL * 1000LL + mts.tv_nsec;
190 
191 #elif defined UNIX
192 	timespec ts;
193 	clock_gettime(CLOCK_MONOTONIC, &ts);
194 	return ts.tv_sec * 1000LL * 1000LL * 1000LL + ts.tv_nsec;
195 
196 #elif defined WIN32 && !defined _XBOX
197 	static bool initialized = false;
198 	static uint64_t initial_count;
199 	static double nanoseconds_per_count;
200 
201 	if (!initialized)
202 	{
203 		QueryPerformanceCounter((LARGE_INTEGER*)&initial_count);
204 
205 		uint64_t temp;
206 		QueryPerformanceFrequency((LARGE_INTEGER*)&temp);
207 		nanoseconds_per_count = 1000.0 * 1000.0 * 1000.0 / double(temp);
208 
209 		initialized = true;
210 	}
211 
212 	uint64_t current_count;
213 	QueryPerformanceCounter((LARGE_INTEGER*)&current_count);
214 
215 	return nanoseconds_per_count * (current_count - initial_count);
216 
217 #endif
218 }
219 
I_MSTime()220 dtime_t I_MSTime()
221 {
222 	return I_ConvertTimeToMs(I_GetTime());
223 }
224 
I_ConvertTimeToMs(dtime_t value)225 dtime_t I_ConvertTimeToMs(dtime_t value)
226 {
227 	return value / 1000000LL;
228 }
229 
I_ConvertTimeFromMs(dtime_t value)230 dtime_t I_ConvertTimeFromMs(dtime_t value)
231 {
232 	return value * 1000000LL;
233 }
234 
235 //
236 // I_Sleep
237 //
238 // Sleeps for the specified number of nanoseconds, yielding control to the
239 // operating system. In actuality, the highest resolution availible with
240 // the select() function is 1 microsecond, but the nanosecond parameter
241 // is used for consistency with I_GetTime().
242 //
I_Sleep(dtime_t sleep_time)243 void I_Sleep(dtime_t sleep_time)
244 {
245 #if defined UNIX
246 	usleep(sleep_time / 1000LL);
247 
248 #elif defined(WIN32) && !defined(_XBOX)
249 	Sleep(sleep_time / 1000000LL);
250 
251 #else
252 	SDL_Delay(sleep_time / 1000000LL);
253 
254 #endif
255 }
256 
257 
258 //
259 // I_Yield
260 //
261 // Sleeps for 1 millisecond
262 //
I_Yield()263 void I_Yield()
264 {
265 	I_Sleep(1000LL * 1000LL);		// sleep for 1ms
266 }
267 
268 //
269 // I_WaitVBL
270 //
271 // I_WaitVBL is never used to actually synchronize to the
272 // vertical blank. Instead, it's used for delay purposes.
273 //
I_WaitVBL(int count)274 void I_WaitVBL(int count)
275 {
276 	I_Sleep(1000000LL * 1000LL * count / 70);
277 }
278 
279 //
280 // SubsetLanguageIDs
281 //
282 #ifdef _WIN32
SubsetLanguageIDs(LCID id,LCTYPE type,int idx)283 static void SubsetLanguageIDs (LCID id, LCTYPE type, int idx)
284 {
285 	char buf[8];
286 	LCID langid;
287 	char *idp;
288 
289 	if (!GetLocaleInfo (id, type, buf, 8))
290 		return;
291 	langid = MAKELCID (strtoul(buf, NULL, 16), SORT_DEFAULT);
292 	if (!GetLocaleInfo (langid, LOCALE_SABBREVLANGNAME, buf, 8))
293 		return;
294 	idp = (char *)(&LanguageIDs[idx]);
295 	memset (idp, 0, 4);
296 	idp[0] = tolower(buf[0]);
297 	idp[1] = tolower(buf[1]);
298 	idp[2] = tolower(buf[2]);
299 	idp[3] = 0;
300 }
301 #endif
302 
303 //
304 // SetLanguageIDs
305 //
306 static const char *langids[] = {
307 	"auto",
308 	"enu",
309 	"fr",
310 	"it"
311 };
312 
EXTERN_CVAR(language)313 EXTERN_CVAR (language)
314 
315 void SetLanguageIDs ()
316 {
317 	unsigned int langid = language.asInt();
318 
319 	if (langid == 0 || langid > 3)
320 	{
321     #ifdef _WIN32
322 		memset (LanguageIDs, 0, sizeof(LanguageIDs));
323 		SubsetLanguageIDs (LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, 0);
324 		SubsetLanguageIDs (LOCALE_USER_DEFAULT, LOCALE_IDEFAULTLANGUAGE, 1);
325 		SubsetLanguageIDs (LOCALE_SYSTEM_DEFAULT, LOCALE_ILANGUAGE, 2);
326 		SubsetLanguageIDs (LOCALE_SYSTEM_DEFAULT, LOCALE_IDEFAULTLANGUAGE, 3);
327     #else
328         langid = 1;     // Default to US English on non-windows systems
329     #endif
330 	}
331 	else
332 	{
333 		DWORD lang = 0;
334 		const char *langtag = langids[langid];
335 
336 		((BYTE *)&lang)[0] = (langtag)[0];
337 		((BYTE *)&lang)[1] = (langtag)[1];
338 		((BYTE *)&lang)[2] = (langtag)[2];
339 		LanguageIDs[0] = lang;
340 		LanguageIDs[1] = lang;
341 		LanguageIDs[2] = lang;
342 		LanguageIDs[3] = lang;
343 	}
344 }
345 
346 //
347 // I_Init
348 //
I_Init(void)349 void I_Init (void)
350 {
351 }
352 
I_GetCWD()353 std::string I_GetCWD()
354 {
355 	char tmp[4096] = {0};
356 	std::string ret = "./";
357 
358 	const char *cwd = getcwd(tmp, sizeof(tmp));
359 	if(cwd)
360 		ret = cwd;
361 
362 	FixPathSeparator(ret);
363 
364 	return ret;
365 }
366 
367 #ifdef UNIX
I_GetHomeDir(std::string user="")368 std::string I_GetHomeDir(std::string user = "")
369 {
370 	const char *envhome = getenv("HOME");
371 	std::string home = envhome ? envhome : "";
372 
373 	if (!home.length())
374 	{
375 #ifdef HAVE_PWD_H
376 		// try the uid way
377 		passwd *p = user.length() ? getpwnam(user.c_str()) : getpwuid(getuid());
378 		if(p && p->pw_dir)
379 			home = p->pw_dir;
380 #endif
381 
382 		if (!home.length())
383 			I_FatalError ("Please set your HOME variable");
384 	}
385 
386 	if(home[home.length() - 1] != PATHSEPCHAR)
387 		home += PATHSEP;
388 
389 	return home;
390 }
391 #endif
392 
I_GetUserFileName(const char * file)393 std::string I_GetUserFileName (const char *file)
394 {
395 #ifdef UNIX
396 	std::string path = I_GetHomeDir();
397 
398 	if(path[path.length() - 1] != PATHSEPCHAR)
399 		path += PATHSEP;
400 
401 	path += ".odamex";
402 
403 	struct stat info;
404 	if (stat (path.c_str(), &info) == -1)
405 	{
406 		if (mkdir (path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) == -1)
407 		{
408 			I_FatalError ("Failed to create %s directory:\n%s",
409 						  path.c_str(), strerror (errno));
410 		}
411 	}
412 	else
413 	{
414 		if (!S_ISDIR(info.st_mode))
415 		{
416 			I_FatalError ("%s must be a directory", path.c_str());
417 		}
418 	}
419 
420 	path += PATHSEP;
421 	path += file;
422 #endif
423 
424 #ifdef _WIN32
425 	std::string path = I_GetBinaryDir();
426 
427 	if(path[path.length() - 1] != PATHSEPCHAR)
428 		path += PATHSEP;
429 
430 	path += file;
431 #endif
432 
433 	return path;
434 }
435 
I_ExpandHomeDir(std::string & path)436 void I_ExpandHomeDir (std::string &path)
437 {
438 #ifdef UNIX
439 	if(!path.length())
440 		return;
441 
442 	if(path[0] != '~')
443 		return;
444 
445 	std::string user;
446 
447 	size_t slash_pos = path.find_first_of(PATHSEPCHAR);
448 	size_t end_pos = path.length();
449 
450 	if(slash_pos == std::string::npos)
451 		slash_pos = end_pos;
452 
453 	if(path.length() != 1 && slash_pos != 1)
454 		user = path.substr(1, slash_pos - 1);
455 
456 	if(slash_pos != end_pos)
457 		slash_pos++;
458 
459 	path = I_GetHomeDir(user) + path.substr(slash_pos, end_pos - slash_pos);
460 #endif
461 }
462 
I_GetBinaryDir()463 std::string I_GetBinaryDir()
464 {
465 	std::string ret;
466 
467 #ifdef _WIN32
468 	char tmp[MAX_PATH]; // denis - todo - make separate function
469 	GetModuleFileName (NULL, tmp, sizeof(tmp));
470 	ret = tmp;
471 #else
472 	if(!Args[0])
473 		return "./";
474 
475 	char realp[PATH_MAX];
476 	if(realpath(Args[0], realp))
477 		ret = realp;
478 	else
479 	{
480 		// search through $PATH
481 		const char *path = getenv("PATH");
482 		if(path)
483 		{
484 			std::stringstream ss(path);
485 			std::string segment;
486 
487 			while(ss)
488 			{
489 				std::getline(ss, segment, ':');
490 
491 				if(!segment.length())
492 					continue;
493 
494 				if(segment[segment.length() - 1] != PATHSEPCHAR)
495 					segment += PATHSEP;
496 				segment += Args[0];
497 
498 				if(realpath(segment.c_str(), realp))
499 				{
500 					ret = realp;
501 					break;
502 				}
503 			}
504 		}
505 	}
506 #endif
507 
508 	FixPathSeparator(ret);
509 
510 	size_t slash = ret.find_last_of(PATHSEPCHAR);
511 	if(slash == std::string::npos)
512 		return "";
513 	else
514 		return ret.substr(0, slash);
515 }
516 
I_FinishClockCalibration()517 void I_FinishClockCalibration ()
518 {
519     ///    Printf (PRINT_HIGH, "CPU Frequency: ~%f MHz\n", CyclesPerSecond / 1e6);
520 }
521 
522 //
523 // I_Quit
524 //
525 static int has_exited;
526 
I_Quit(void)527 void STACK_ARGS I_Quit (void)
528 {
529     has_exited = 1;             /* Prevent infinitely recursive exits -- killough */
530 
531     #ifdef _WIN32
532     timeEndPeriod (TimerPeriod);
533     #endif
534 
535     G_ClearSnapshots ();
536     SV_SendDisconnectSignal();
537 
538     CloseNetwork ();
539 
540 	DConsoleAlias::DestroyAll();
541 }
542 
543 
544 //
545 // I_Error
546 //
547 BOOL gameisdead;
548 
549 #define MAX_ERRORTEXT	1024
550 
551 void STACK_ARGS call_terms (void);
552 
I_FatalError(const char * error,...)553 void STACK_ARGS I_FatalError (const char *error, ...)
554 {
555     static BOOL alreadyThrown = false;
556     gameisdead = true;
557 
558     if (!alreadyThrown)         // ignore all but the first message -- killough
559     {
560                 alreadyThrown = true;
561                 char errortext[MAX_ERRORTEXT];
562                 va_list argptr;
563                 va_start (argptr, error);
564                 #ifdef _WIN32
565                 int index = vsprintf (errortext, error, argptr);
566                 sprintf (errortext + index, "\nGetLastError = %ld", GetLastError());
567 				#else
568                 vsprintf (errortext, error, argptr);
569 				#endif
570                 va_end (argptr);
571 
572                 throw CFatalError (errortext);
573     }
574 
575     if (!has_exited)    // If it hasn't exited yet, exit now -- killough
576     {
577         has_exited = 1; // Prevent infinitely recursive exits -- killough
578 
579         call_terms();
580 
581         exit(EXIT_FAILURE);
582     }
583 }
584 
I_Error(const char * error,...)585 void STACK_ARGS I_Error (const char *error, ...)
586 {
587     va_list argptr;
588     char errortext[MAX_ERRORTEXT];
589 
590     va_start (argptr, error);
591     vsprintf (errortext, error, argptr);
592     va_end (argptr);
593 
594     throw CRecoverableError (errortext);
595 }
596 
597 char DoomStartupTitle[256] = { 0 };
598 
I_SetTitleString(const char * title)599 void I_SetTitleString (const char *title)
600 {
601     int i;
602 
603     for (i = 0; title[i]; i++)
604                 DoomStartupTitle[i] = title[i] | 0x80;
605 }
606 
I_PrintStr(int xp,const char * cp,int count,BOOL scroll)607 void I_PrintStr (int xp, const char *cp, int count, BOOL scroll)
608 {
609         char string[4096];
610 
611         memcpy (string, cp, count);
612         if (scroll)
613                 string[count++] = '\n';
614         string[count] = 0;
615 
616         fputs (string, stdout);
617         fflush (stdout);
618 }
619 
620 //static const char *pattern; // [DL] todo - remove
621 //static findstate_t *findstates[8]; // [DL] todo - remove
622 
I_FindFirst(char * filespec,findstate_t * fileinfo)623 long I_FindFirst (char *filespec, findstate_t *fileinfo)
624 {
625     return 0;
626 }
627 
I_FindNext(long handle,findstate_t * fileinfo)628 int I_FindNext (long handle, findstate_t *fileinfo)
629 {
630     return 0;
631 }
632 
I_FindClose(long handle)633 int I_FindClose (long handle)
634 {
635     return 0;
636 }
637 
I_FindAttr(findstate_t * fileinfo)638 int I_FindAttr (findstate_t *fileinfo)
639 {
640     return 0;
641 }
642 
643 //
644 // I_ConsoleInput
645 //
646 #ifdef _WIN32
647 int ShutdownNow();
648 
I_ConsoleInput(void)649 std::string I_ConsoleInput (void)
650 {
651 	// denis - todo - implement this properly!!!
652     static char     text[1024] = {0};
653     static char     buffer[1024] = {0};
654     unsigned int    len = strlen(buffer);
655 
656     if (ShutdownNow())
657         return "quit";
658 
659 	while(kbhit() && len < sizeof(text))
660 	{
661 		char ch = (char)getch();
662 
663 		// input the character
664 		if(ch == '\b' && len)
665 		{
666 			buffer[--len] = 0;
667 			// john - backspace hack
668 			fwrite(&ch, 1, 1, stdout);
669 			ch = ' ';
670 			fwrite(&ch, 1, 1, stdout);
671 			ch = '\b';
672 		}
673 		else
674 			buffer[len++] = ch;
675 		buffer[len] = 0;
676 
677 		// recalculate length
678 		len = strlen(buffer);
679 
680 		// echo character back to user
681 		fwrite(&ch, 1, 1, stdout);
682 		fflush(stdout);
683 	}
684 
685 	if(len && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r'))
686 	{
687 		// echo newline back to user
688 		char ch = '\n';
689 		fwrite(&ch, 1, 1, stdout);
690 		fflush(stdout);
691 
692 		strcpy(text, buffer);
693 		text[len-1] = 0; // rip off the /n and terminate
694 		buffer[0] = 0;
695 		len = 0;
696 
697 		return text;
698 	}
699 
700 	return "";
701 }
702 
703 #else
704 
I_ConsoleInput(void)705 std::string I_ConsoleInput (void)
706 {
707 	std::string ret;
708     static char     text[1024] = {0};
709     int             len;
710 
711     fd_set fdr;
712     FD_ZERO(&fdr);
713     FD_SET(0, &fdr);
714     struct timeval tv;
715     tv.tv_sec = 0;
716     tv.tv_usec = 0;
717 
718     if (select(1, &fdr, NULL, NULL, &tv) <= 0)
719         return "";
720 
721     len = read (0, text + strlen(text), sizeof(text) - strlen(text)); // denis - fixme - make it read until the next linebreak instead
722 
723     if (len < 1)
724         return "";
725 
726 	len = strlen(text);
727 
728 	if (strlen(text) >= sizeof(text))
729 	{
730 		if(text[len-1] == '\n' || text[len-1] == '\r')
731 			text[len-1] = 0; // rip off the /n and terminate
732 
733 		ret = text;
734 		memset(text, 0, sizeof(text));
735 		return ret;
736 	}
737 
738 	if(text[len-1] == '\n' || text[len-1] == '\r')
739 	{
740 		text[len-1] = 0;
741 
742 		ret = text;
743 		memset(text, 0, sizeof(text));
744 		return ret;
745 	}
746 
747     return "";
748 }
749 #endif
750 
751 VERSION_CONTROL (i_system_cpp, "$Id: i_system.cpp 4556 2014-02-10 02:48:10Z dr_sean $")
752 
753