1 /*
2  * sys_amiga.c -- Amiga system interface code
3  * $Id: sys_amiga.c 6015 2018-02-21 17:30:51Z sezero $
4  *
5  * Copyright (C) 1996-1997  Id Software, Inc.
6  * Copyright (C) 2012  Szilard Biro <col.lawrence@gmail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or (at
11  * your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16  *
17  * See the GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22  */
23 
24 #include "quakedef.h"
25 #include "debuglog.h"
26 
27 #include <proto/exec.h>
28 #include <proto/dos.h>
29 
30 #include <proto/timer.h>
31 #include <time.h>
32 
33 #define MIN_STACK_SIZE 0x100000 /* 1 MB stack */
34 #ifdef __CLIB2__
35 int __stack_size = MIN_STACK_SIZE;
36 #else
37 int __stack = MIN_STACK_SIZE;
38 #endif
39 #ifdef __AROS__
40 #include "incstack.h"
41 /* The problem here is that our real main never returns: the exit
42  * point of program is either Sys_Quit() or Sys_Error(). One way
43  * of making the real main return to the incstack.h main wrapper
44  * is setjmp()'ing in real main and longjmp()'ing from the actual
45  * exit points, avoiding exit(). */
46 #include <setjmp.h>
47 static jmp_buf exit_buf;
48 static int my_rc = 0;
49 #define HAVE_AROS_MAIN_WRAPPER
50 #endif
51 
52 // heapsize: minimum 8 mb, standart 16 mb, max is 32 mb.
53 // -heapsize argument will abide by these min/max settings
54 // unless the -forcemem argument is used
55 #define MIN_MEM_ALLOC	0x0800000
56 #define STD_MEM_ALLOC	0x1000000
57 #define MAX_MEM_ALLOC	0x2000000
58 
59 cvar_t		sys_nostdout = {"sys_nostdout", "0", CVAR_NONE};
60 
61 qboolean		isDedicated = true;	/* compatibility */
62 
63 static double		starttime;
64 static qboolean		first = true;
65 
66 static BPTR		amiga_stdin, amiga_stdout;
67 #define	MODE_RAW	1
68 #define	MODE_NORMAL	0
69 
70 struct timerequest	*timerio;
71 struct MsgPort		*timerport;
72 #if defined(__MORPHOS__) || defined(__VBCC__)
73 struct Library		*TimerBase;
74 #else
75 struct Device		*TimerBase;
76 #endif
77 
78 
79 /*
80 ===============================================================================
81 
82 FILE IO
83 
84 ===============================================================================
85 */
86 
Sys_mkdir(const char * path,qboolean crash)87 int Sys_mkdir(const char *path, qboolean crash)
88 {
89 	BPTR lock = CreateDir((const STRPTR) path);
90 
91 	if (lock)
92 	{
93 		UnLock(lock);
94 		return 0;
95 	}
96 
97 	if (IoErr() == ERROR_OBJECT_EXISTS)
98 		return 0;
99 
100 	if (crash)
101 		Sys_Error("Unable to create directory %s", path);
102 	return -1;
103 }
104 
Sys_rmdir(const char * path)105 int Sys_rmdir (const char *path)
106 {
107 	if (DeleteFile((const STRPTR) path) != 0)
108 		return 0;
109 	return -1;
110 }
111 
Sys_unlink(const char * path)112 int Sys_unlink (const char *path)
113 {
114 	if (DeleteFile((const STRPTR) path) != 0)
115 		return 0;
116 	return -1;
117 }
118 
Sys_rename(const char * oldp,const char * newp)119 int Sys_rename (const char *oldp, const char *newp)
120 {
121 	if (Rename((const STRPTR) oldp, (const STRPTR) newp) != 0)
122 		return 0;
123 	return -1;
124 }
125 
Sys_filesize(const char * path)126 long Sys_filesize (const char *path)
127 {
128 	long size = -1;
129 	BPTR fh = Open((const STRPTR) path, MODE_OLDFILE);
130 	if (fh)
131 	{
132 		struct FileInfoBlock *fib = (struct FileInfoBlock*)
133 					AllocDosObject(DOS_FIB, NULL);
134 		if (fib != NULL)
135 		{
136 			if (ExamineFH(fh, fib))
137 				size = fib->fib_Size;
138 			FreeDosObject(DOS_FIB, fib);
139 		}
140 		Close(fh);
141 	}
142 	return size;
143 }
144 
Sys_FileType(const char * path)145 int Sys_FileType (const char *path)
146 {
147 	int type = FS_ENT_NONE;
148 	BPTR fh = Open((const STRPTR) path, MODE_OLDFILE);
149 	if (fh)
150 	{
151 		struct FileInfoBlock *fib = (struct FileInfoBlock*)
152 					AllocDosObject(DOS_FIB, NULL);
153 		if (fib != NULL)
154 		{
155 			if (ExamineFH(fh, fib))
156 			{
157 				if (fib->fib_DirEntryType >= 0)
158 					type = FS_ENT_DIRECTORY;
159 				else	type = FS_ENT_FILE;
160 			}
161 			FreeDosObject(DOS_FIB, fib);
162 		}
163 		Close(fh);
164 	}
165 	return type;
166 }
167 
168 #define	COPY_READ_BUFSIZE		8192	/* BUFSIZ */
Sys_CopyFile(const char * frompath,const char * topath)169 int Sys_CopyFile (const char *frompath, const char *topath)
170 {
171 	char buf[COPY_READ_BUFSIZE];
172 	BPTR in, out;
173 	struct FileInfoBlock *fib;
174 	struct DateStamp stamp;
175 	LONG remaining, count;
176 
177 	in = Open((const STRPTR) frompath, MODE_OLDFILE);
178 	if (!in)
179 	{
180 		Con_Printf ("%s: unable to open %s\n", __thisfunc__, frompath);
181 		return 1;
182 	}
183 	fib = (struct FileInfoBlock*) AllocDosObject(DOS_FIB, NULL);
184 	if (fib != NULL)
185 	{
186 		if (ExamineFH(in, fib) == 0)
187 			remaining = -1;
188 		else
189 		{
190 			remaining = fib->fib_Size;
191 			stamp = fib->fib_Date;
192 		}
193 		FreeDosObject(DOS_FIB, fib);
194 		if (remaining < 0)
195 		{
196 			Con_Printf ("%s: can't determine size for %s\n", __thisfunc__, frompath);
197 			Close(in);
198 			return 1;
199 		}
200 	}
201 	else
202 	{
203 		Con_Printf ("%s: can't allocate FileInfoBlock for %s\n", __thisfunc__, frompath);
204 		Close(in);
205 		return 1;
206 	}
207 	out = Open((const STRPTR) topath, MODE_NEWFILE);
208 	if (!out)
209 	{
210 		Con_Printf ("%s: unable to open %s\n", __thisfunc__, topath);
211 		Close(in);
212 		return 1;
213 	}
214 
215 	while (remaining)
216 	{
217 		if (remaining < sizeof(buf))
218 			count = remaining;
219 		else	count = sizeof(buf);
220 
221 		if (Read(in, buf, count) == -1)
222 			break;
223 		if (Write(out, buf, count) == -1)
224 			break;
225 
226 		remaining -= count;
227 	}
228 
229 	Close(in);
230 	Close(out);
231 
232 	if (remaining != 0)
233 		return 1;
234 
235 	SetFileDate(topath, &stamp);
236 
237 	return 0;
238 }
239 
240 /*
241 =================================================
242 simplified findfirst/findnext implementation:
243 Sys_FindFirstFile and Sys_FindNextFile return
244 filenames only, not a dirent struct. this is
245 what we presently need in this engine.
246 =================================================
247 */
248 #define PATH_SIZE 1024
249 static struct AnchorPath *apath;
250 static BPTR oldcurrentdir;
251 static STRPTR pattern_str;
252 
pattern_helper(const char * pat)253 static STRPTR pattern_helper (const char *pat)
254 {
255 	char	*pdup;
256 	const char	*p;
257 	int	n;
258 
259 	for (n = 0, p = pat; *p != '\0'; ++p, ++n)
260 	{
261 		if ((p = strchr (p, '*')) == NULL)
262 			break;
263 	}
264 
265 	if (n == 0)
266 		pdup = Z_Strdup(pat);
267 	else
268 	{
269 	/* replace each "*" by "#?" */
270 		n += (int) strlen(pat) + 1;
271 		pdup = (char *) Z_Malloc(n, Z_MAINZONE);
272 
273 		for (n = 0, p = pat; *p != '\0'; ++p, ++n)
274 		{
275 			if (*p != '*')
276 				pdup[n] = *p;
277 			else
278 			{
279 				pdup[n] = '#'; ++n;
280 				pdup[n] = '?';
281 			}
282 		}
283 		pdup[n] = '\0';
284 	}
285 
286 	return (STRPTR) pdup;
287 }
288 
Sys_FindFirstFile(const char * path,const char * pattern)289 const char *Sys_FindFirstFile (const char *path, const char *pattern)
290 {
291 	BPTR newdir;
292 
293 	if (apath)
294 		Sys_Error ("Sys_FindFirst without FindClose");
295 
296 	apath = (struct AnchorPath *) AllocVec (sizeof(struct AnchorPath) + PATH_SIZE, MEMF_CLEAR);
297 	if (!apath)
298 		return NULL;
299 
300 	apath->ap_Strlen = PATH_SIZE;
301 	apath->ap_BreakBits = 0;
302 	apath->ap_Flags = 0;  /* APF_DOWILD */
303 
304 	newdir = Lock((const STRPTR) path, SHARED_LOCK);
305 	if (newdir)
306 		oldcurrentdir = CurrentDir(newdir);
307 	else
308 	{
309 		FreeVec(apath);
310 		apath = NULL;
311 		return NULL;
312 	}
313 
314 	pattern_str = pattern_helper (pattern);
315 
316 	if (MatchFirst((const STRPTR) pattern_str, apath) == 0)
317 	{
318 	    if (apath->ap_Info.fib_DirEntryType < 0)
319 		return (const char *) (apath->ap_Info.fib_FileName);
320 	}
321 
322 	return Sys_FindNextFile();
323 }
324 
Sys_FindNextFile(void)325 const char *Sys_FindNextFile (void)
326 {
327 	if (!apath)
328 		return NULL;
329 
330 	while (MatchNext(apath) == 0)
331 	{
332 	    if (apath->ap_Info.fib_DirEntryType < 0)
333 		return (const char *) (apath->ap_Info.fib_FileName);
334 	}
335 
336 	return NULL;
337 }
338 
Sys_FindClose(void)339 void Sys_FindClose (void)
340 {
341 	if (!apath)
342 		return;
343 	MatchEnd(apath);
344 	FreeVec(apath);
345 	UnLock(CurrentDir(oldcurrentdir));
346 	oldcurrentdir = 0;
347 	apath = NULL;
348 	Z_Free(pattern_str);
349 	pattern_str = NULL;
350 }
351 
352 /*
353 ===============================================================================
354 
355 SYSTEM IO
356 
357 ===============================================================================
358 */
359 
360 /*
361 ================
362 Sys_Init
363 ================
364 */
Sys_Init(void)365 static void Sys_Init (void)
366 {
367 	if ((timerport = CreateMsgPort()))
368 	{
369 		if ((timerio = (struct timerequest *)CreateIORequest(timerport, sizeof(struct timerequest))))
370 		{
371 			if (OpenDevice((STRPTR) TIMERNAME, UNIT_MICROHZ,
372 					(struct IORequest *) timerio, 0) == 0)
373 			{
374 #if defined(__MORPHOS__) || defined(__VBCC__)
375 				TimerBase = (struct Library *)timerio->tr_node.io_Device;
376 #else
377 				TimerBase = timerio->tr_node.io_Device;
378 #endif
379 			}
380 			else
381 			{
382 				DeleteIORequest((struct IORequest *)timerio);
383 				DeleteMsgPort(timerport);
384 			}
385 		}
386 		else
387 		{
388 			DeleteMsgPort(timerport);
389 		}
390 	}
391 
392 	if (!TimerBase)
393 		Sys_Error("Can't open timer.device");
394 
395 	/* 1us wait, for timer cleanup success */
396 	timerio->tr_node.io_Command = TR_ADDREQUEST;
397 	timerio->tr_time.tv_secs = 0;
398 	timerio->tr_time.tv_micro = 1;
399 	SendIO((struct IORequest *) timerio);
400 	WaitIO((struct IORequest *) timerio);
401 
402 	amiga_stdout = Output();
403 	amiga_stdin = Input();
404 	SetMode(amiga_stdin, MODE_RAW);
405 }
406 
Sys_AtExit(void)407 static void Sys_AtExit (void)
408 {
409 	if (amiga_stdin)
410 		SetMode(amiga_stdin, MODE_NORMAL);
411 	if (TimerBase)
412 	{
413 		/*
414 		if (!CheckIO((struct IORequest *) timerio)
415 		{
416 			AbortIO((struct IORequest *) timerio);
417 			WaitIO((struct IORequest *) timerio);
418 		}
419 		*/
420 		WaitIO((struct IORequest *) timerio);
421 		CloseDevice((struct IORequest *) timerio);
422 		DeleteIORequest((struct IORequest *) timerio);
423 		DeleteMsgPort(timerport);
424 		TimerBase = NULL;
425 	}
426 }
427 
428 #define ERROR_PREFIX	"\nFATAL ERROR: "
Sys_Error(const char * error,...)429 void Sys_Error (const char *error, ...)
430 {
431 	va_list		argptr;
432 	char		text[MAX_PRINTMSG];
433 	const char	text2[] = ERROR_PREFIX;
434 	const unsigned char	*p;
435 
436 	host_parms->errstate++;
437 
438 	va_start (argptr, error);
439 	q_vsnprintf (text, sizeof(text), error, argptr);
440 	va_end (argptr);
441 
442 	if (con_debuglog)
443 	{
444 		LOG_Print (ERROR_PREFIX);
445 		LOG_Print (text);
446 		LOG_Print ("\n\n");
447 	}
448 
449 	Host_Shutdown ();
450 
451 	for (p = (const unsigned char *) text2; *p; p++)
452 		putc (*p, stderr);
453 	for (p = (const unsigned char *) text ; *p; p++)
454 		putc (*p, stderr);
455 	putc ('\n', stderr);
456 	putc ('\n', stderr);
457 
458 #ifdef HAVE_AROS_MAIN_WRAPPER
459 	Sys_AtExit();
460 	my_rc = 1;
461 	longjmp(exit_buf, 1);
462 #else
463 	exit (1);
464 #endif
465 }
466 
Sys_PrintTerm(const char * msgtxt)467 void Sys_PrintTerm (const char *msgtxt)
468 {
469 	const unsigned char	*p;
470 
471 	if (sys_nostdout.integer)
472 		return;
473 
474 	for (p = (const unsigned char *) msgtxt; *p; p++)
475 		putc (*p, stdout);
476 }
477 
Sys_Quit(void)478 void Sys_Quit (void)
479 {
480 	Host_Shutdown();
481 
482 #ifdef HAVE_AROS_MAIN_WRAPPER
483 	Sys_AtExit();
484 	longjmp(exit_buf, 1);
485 #else
486 	exit (0);
487 #endif
488 }
489 
490 
491 /*
492 ================
493 Sys_DoubleTime
494 ================
495 */
Sys_DoubleTime(void)496 double Sys_DoubleTime (void)
497 {
498 	struct timeval	tp;
499 	double		now;
500 
501 	GetSysTime(&tp);
502 
503 	now = tp.tv_secs + tp.tv_micro / 1e6;
504 
505 	if (first)
506 	{
507 		first = false;
508 		starttime = now;
509 		return 0.0;
510 	}
511 
512 	return now - starttime;
513 }
514 
Sys_DateTimeString(char * buf)515 char *Sys_DateTimeString (char *buf)
516 {
517 	static char strbuf[24];
518 	time_t t;
519 	struct tm *l;
520 	int val;
521 
522 	if (!buf) buf = strbuf;
523 
524 	t = time(NULL);
525 	l = localtime(&t);
526 
527 	val = l->tm_mon + 1;	/* tm_mon: months since January [0,11] */
528 	buf[0] = val / 10 + '0';
529 	buf[1] = val % 10 + '0';
530 	buf[2] = '/';
531 	val = l->tm_mday;
532 	buf[3] = val / 10 + '0';
533 	buf[4] = val % 10 + '0';
534 	buf[5] = '/';
535 	val = l->tm_year / 100 + 19;	/* tm_year: #years since 1900. */
536 	buf[6] = val / 10 + '0';
537 	buf[7] = val % 10 + '0';
538 	val = l->tm_year % 100;
539 	buf[8] = val / 10 + '0';
540 	buf[9] = val % 10 + '0';
541 
542 	buf[10] = ' ';
543 
544 	val = l->tm_hour;
545 	buf[11] = val / 10 + '0';
546 	buf[12] = val % 10 + '0';
547 	buf[13] = ':';
548 	val = l->tm_min;
549 	buf[14] = val / 10 + '0';
550 	buf[15] = val % 10 + '0';
551 	buf[16] = ':';
552 	val = l->tm_sec;
553 	buf[17] = val / 10 + '0';
554 	buf[18] = val % 10 + '0';
555 
556 	buf[19] = '\0';
557 
558 	return buf;
559 }
560 
561 
562 /*
563 ================
564 Sys_ConsoleInput
565 ================
566 */
Sys_ConsoleInput(void)567 const char *Sys_ConsoleInput (void)
568 {
569 	static char	con_text[256];
570 	static int	textlen;
571 	char		c;
572 
573 	while (WaitForChar(amiga_stdin,10))
574 	{
575 		Read (amiga_stdin, &c, 1);
576 		if (c == '\n' || c == '\r')
577 		{
578 			Write(amiga_stdout, "\n", 1);
579 			con_text[textlen] = '\0';
580 			textlen = 0;
581 			return con_text;
582 		}
583 		else if (c == 8)
584 		{
585 			if (textlen)
586 			{
587 				Write(amiga_stdout, "\b \b", 3);
588 				textlen--;
589 				con_text[textlen] = '\0';
590 			}
591 			continue;
592 		}
593 		con_text[textlen] = c;
594 		textlen++;
595 		if (textlen < (int) sizeof(con_text))
596 		{
597 			Write(amiga_stdout, &c, 1);
598 			con_text[textlen] = '\0';
599 		}
600 		else
601 		{
602 		// buffer is full
603 			textlen = 0;
604 			con_text[0] = '\0';
605 			Sys_PrintTerm("\nConsole input too long!\n");
606 			break;
607 		}
608 	}
609 
610 	return NULL;
611 }
612 
Sys_Sleep(unsigned long msecs)613 void Sys_Sleep (unsigned long msecs)
614 {
615 	timerio->tr_node.io_Command = TR_ADDREQUEST;
616 	timerio->tr_time.tv_secs = msecs / 1000;
617 	timerio->tr_time.tv_micro = (msecs * 1000) % 1000000;
618 	SendIO((struct IORequest *) timerio);
619 	WaitIO((struct IORequest *) timerio);
620 }
621 
Sys_GetBasedir(char * argv0,char * dst,size_t dstsize)622 static int Sys_GetBasedir (char *argv0, char *dst, size_t dstsize)
623 {
624 #if 1
625 	int len = q_strlcpy(dst, "PROGDIR:", dstsize);
626 	if (len < (int)dstsize)
627 		return 0;
628 	return -1;
629 #else
630 	if (NameFromLock(GetProgramDir(), (STRPTR) dst, dstsize) != 0)
631 		return 0;
632 	return -1;
633 #endif
634 }
635 
PrintVersion(void)636 static void PrintVersion (void)
637 {
638 	Sys_Printf ("Hammer of Thyrion, release %s (%s)\n", HOT_VERSION_STR, HOT_VERSION_REL_DATE);
639 	Sys_Printf ("Hexen II dedicated server %4.2f (%s)\n", ENGINE_VERSION, PLATFORM_STRING);
640 	Sys_Printf ("More info / sending bug reports:  http://uhexen2.sourceforge.net\n");
641 }
642 
643 static const char *help_strings[] = {
644 	"     [-v | --version]        Display version information",
645 #ifndef DEMOBUILD
646 #   if defined(H2MP)
647 	"     [-noportals]            Disable the mission pack support",
648 #   else
649 	"     [-portals | -h2mp ]     Run the Portal of Praevus mission pack",
650 #   endif
651 #endif
652 	"     [-heapsize Bytes]       Heapsize (memory to allocate)",
653 	NULL
654 };
655 
PrintHelp(const char * name)656 static void PrintHelp (const char *name)
657 {
658 	int i = 0;
659 
660 	Sys_Printf ("Usage: %s [options]\n", name);
661 	while (help_strings[i])
662 	{
663 		Sys_PrintTerm (help_strings[i]);
664 		Sys_PrintTerm ("\n");
665 		i++;
666 	}
667 	Sys_PrintTerm ("\n");
668 }
669 
670 /*
671 ===============================================================================
672 
673 MAIN
674 
675 ===============================================================================
676 */
677 static quakeparms_t	parms;
678 static char	cwd[MAX_OSPATH];
679 
main(int argc,char ** argv)680 int main (int argc, char **argv)
681 {
682 	int			i;
683 	double		time, oldtime;
684 	ULONG		availMem;
685 
686 #ifdef HAVE_AROS_MAIN_WRAPPER
687 	if (setjmp(exit_buf))
688 		return my_rc;
689 #endif
690 
691 	PrintVersion();
692 
693 	if (argc > 1)
694 	{
695 		for (i = 1; i < argc; i++)
696 		{
697 			if ( !(strcmp(argv[i], "-v")) || !(strcmp(argv[i], "-version" )) ||
698 				!(strcmp(argv[i], "--version")) )
699 			{
700 				return 0;
701 			}
702 			else if ( !(strcmp(argv[i], "-h")) || !(strcmp(argv[i], "-help" )) ||
703 				  !(strcmp(argv[i], "-?")) || !(strcmp(argv[i], "--help")) )
704 			{
705 				PrintHelp(argv[0]);
706 				return 0;
707 			}
708 		}
709 	}
710 
711 	/* initialize the host params */
712 	memset (&parms, 0, sizeof(parms));
713 	parms.basedir = cwd;
714 	parms.userdir = cwd;
715 	parms.argc = argc;
716 	parms.argv = argv;
717 	parms.errstate = 0;
718 	host_parms = &parms;
719 
720 	memset (cwd, 0, sizeof(cwd));
721 	if (Sys_GetBasedir(argv[0], cwd, sizeof(cwd)) != 0)
722 		Sys_Error ("Couldn't determine current directory");
723 
724 	LOG_Init (&parms);
725 
726 	Sys_Printf("basedir is: %s\n", parms.basedir);
727 	Sys_Printf("userdir is: %s\n", parms.userdir);
728 
729 	COM_ValidateByteorder ();
730 
731 	availMem = AvailMem(MEMF_ANY|MEMF_LARGEST);
732 	if (availMem < STD_MEM_ALLOC)
733 		parms.memsize = MIN_MEM_ALLOC;
734 	else
735 		parms.memsize = STD_MEM_ALLOC;
736 
737 	i = COM_CheckParm ("-heapsize");
738 	if (i && i < com_argc-1)
739 	{
740 		parms.memsize = atoi (com_argv[i+1]) * 1024;
741 
742 		if ((parms.memsize > MAX_MEM_ALLOC) && !(COM_CheckParm ("-forcemem")))
743 		{
744 			Sys_Printf ("Requested memory (%d Mb) too large, using the default maximum.\n", parms.memsize/(1024*1024));
745 			Sys_Printf ("If you are sure, use the -forcemem switch.\n");
746 			parms.memsize = MAX_MEM_ALLOC;
747 		}
748 		else if ((parms.memsize < MIN_MEM_ALLOC) && !(COM_CheckParm ("-forcemem")))
749 		{
750 			Sys_Printf ("Requested memory (%d Mb) too little, using the default minimum.\n", parms.memsize/(1024*1024));
751 			Sys_Printf ("If you are sure, use the -forcemem switch.\n");
752 			parms.memsize = MIN_MEM_ALLOC;
753 		}
754 	}
755 
756 	parms.membase = malloc (parms.memsize);
757 
758 	if (!parms.membase)
759 		Sys_Error ("Insufficient memory.\n");
760 
761 #ifndef HAVE_AROS_MAIN_WRAPPER
762 	atexit (Sys_AtExit);
763 #endif
764 	Sys_Init ();
765 
766 	Host_Init();
767 
768 	oldtime = Sys_DoubleTime ();
769 
770 	/* main window message loop */
771 	while (1)
772 	{
773 		time = Sys_DoubleTime ();
774 
775 		if (time - oldtime < sys_ticrate.value )
776 		{
777 			Sys_Sleep(1);
778 			continue;
779 		}
780 
781 		Host_Frame (time - oldtime);
782 		oldtime = time;
783 	}
784 
785 	return 0;
786 }
787 
788