/* startuptest.c : Previews Hexen STARTUP screens for GZDoom mods. Requires SDL2 and SDL2_mixer. (C)2021 Marisa the Magician, UnSX Team. Released under the MIT License. */ #define _POSIX_C_SOURCE 199309L #include #include #include #include #include #include #include SDL_Window *w = 0; SDL_Surface *ws = 0, *st = 0; Mix_Music *startmus = 0; Mix_Chunk *ticksnd = 0, *dripsnd = 0; #define ST_MAX_NOTCHES 32 #define ST_NOTCH_WIDTH 16 #define ST_NOTCH_HEIGHT 23 #define ST_PROGRESS_X 64 #define ST_PROGRESS_Y 441 #define ST_NETPROGRESS_X 288 #define ST_NETPROGRESS_Y 32 #define ST_NETNOTCH_WIDTH 4 // 8 in vanilla hexen #define ST_NETNOTCH_HEIGHT 16 #define ST_MAX_NETNOTCHES 8 SDL_Color pal[16], pals[16] = {{0,0,0,0xFF}}; Uint8 startup[640*480], notch[ST_NOTCH_WIDTH*ST_NOTCH_HEIGHT], netnotch[ST_NETNOTCH_WIDTH*ST_NETNOTCH_HEIGHT]; int donet = 0; int ReadFiles( const char *s, const char *n, const char *nn ) { FILE *f = fopen(s,"rb"); if ( !f ) { fprintf(stderr,"Could not open startup file: %s\n",strerror(errno)); return 1; } // read palette for ( int i=0; i<16; i++ ) { fread(&pal[i],1,3,f); pal[i].r <<= 2; pal[i].g <<= 2; pal[i].b <<= 2; pal[i].a = 0xFF; } // read image memset(&startup[0],0,640*480); for ( int i=0; i<4; i++ ) for ( int j=0; j<(640*480); j+=8 ) { Uint8 bits; fread(&bits,1,1,f); startup[j] |= ((bits>>7)&1)<>6)&1)<>5)&1)<>4)&1)<>3)&1)<>2)&1)<>1)&1)<>4)&0xF; notch[i+1] = bits&0xF; } fclose(f); // read netnotch memset(&netnotch[0],0,ST_NETNOTCH_WIDTH*ST_NETNOTCH_HEIGHT); if ( !donet ) return 0; f = fopen(nn,"rb"); if ( !f ) { fprintf(stderr,"Could not open netnotch file: %s\n",strerror(errno)); return 1; } for ( int i=0; i<(ST_NETNOTCH_WIDTH*ST_NETNOTCH_HEIGHT); i+=2 ) { Uint8 bits; fread(&bits,1,1,f); netnotch[i] = (bits>>4)&0xF; netnotch[i+1] = bits&0xF; } fclose(f); return 0; } enum EStartupState { STATE_FADEIN, STATE_NOTCH, STATE_NETNOTCH, STATE_WAIT }; double gametime; #define NANOS_SEC 1000000000L long ticker( void ) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW,&ts); return ts.tv_nsec+ts.tv_sec*NANOS_SEC; } int main( int argc, char **argv ) { if ( (argc > 1) && !strcmp(argv[1],"-n") ) donet = 1; if ( argc < 3+donet*2 ) { fprintf(stderr, "usage: startuptest [music path] [tick path]\n" " startuptest -n [music path] [tick path] [drip path]\n"); return 1; } if ( ReadFiles(argv[1+donet],argv[2+donet],donet?argv[4]:"") ) return 1; SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER|SDL_INIT_EVENTS); w = SDL_CreateWindow("STARTUP TEST",SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,640,480,SDL_WINDOW_SHOWN); ws = SDL_GetWindowSurface(w); st = SDL_CreateRGBSurface(0,640,480,8,0,0,0,0); SDL_SetSurfaceBlendMode(st,SDL_BLENDMODE_NONE); SDL_SetPaletteColors(st->format->palette,&pals[0],0,16); Mix_Init(MIX_INIT_FLAC|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG); Mix_OpenAudio(48000,MIX_DEFAULT_FORMAT,2,4096); if ( argc > 3+donet*2 ) startmus = Mix_LoadMUS(argv[3+donet*2]); if ( argc > 4+donet*2 ) ticksnd = Mix_LoadWAV(argv[4+donet*2]); if ( donet && (argc > 7) ) dripsnd = Mix_LoadWAV(argv[7]); SDL_Event e; int active = 1; long tick, tock; if ( startmus ) Mix_PlayMusic(startmus,-1); // first blit SDL_LockSurface(st); memcpy(st->pixels,&startup[0],640*480); SDL_UnlockSurface(st); int state = STATE_FADEIN; int fadestep = 0; int ncnt = 0; while ( active ) { tick = ticker(); while ( SDL_PollEvent(&e) ) if ( (e.type == SDL_QUIT) || ((e.type == SDL_KEYDOWN) && (e.key.keysym.sym == SDLK_ESCAPE)) ) active = 0; switch ( state ) { case STATE_FADEIN: switch ( fadestep ) { case 0: if ( gametime > .125 ) { fadestep = 1; gametime = 0.; } break; case 1: ncnt++; for ( int i=0; i<16; i++ ) { pals[i].r = (pal[i].r*ncnt)/8; pals[i].g = (pal[i].g*ncnt)/8; pals[i].b = (pal[i].b*ncnt)/8; } SDL_SetPaletteColors(st->format->palette,&pals[0],0,16); fadestep = 0; gametime = 0.; if ( ncnt >= 8 ) { ncnt = 0; state = STATE_NOTCH; gametime = -1.; } break; } break; case STATE_NOTCH: switch ( fadestep ) { case 0: if ( gametime > 1. ) { fadestep++; gametime = (float)ncnt/(ST_MAX_NOTCHES); gametime = powf(gametime,.25)*.9; } break; case 1: if ( ticksnd ) Mix_PlayChannel(0,ticksnd,0); SDL_LockSurface(st); // draw notch for ( int i=0; ipixels+(ST_PROGRESS_Y+i)*640+ST_PROGRESS_X+ST_NOTCH_WIDTH*ncnt,¬ch[0]+ST_NOTCH_WIDTH*i,ST_NOTCH_WIDTH); SDL_UnlockSurface(st); ncnt++; if ( ncnt >= ST_MAX_NOTCHES ) fadestep = 2; else fadestep = 0; break; case 2: if ( donet ) state = STATE_NETNOTCH; else state = STATE_WAIT; fadestep = 0; gametime = 0.; ncnt = 0; break; } break; case STATE_NETNOTCH: switch ( fadestep ) { case 0: if ( gametime > 1. ) { fadestep++; gametime = 0.; } break; case 1: if ( dripsnd ) Mix_PlayChannel(0,dripsnd,0); SDL_LockSurface(st); // draw netnotch for ( int i=0; ipixels+(ST_NETPROGRESS_Y+i)*640+ST_NETPROGRESS_X+ST_NETNOTCH_WIDTH*ncnt,&netnotch[0]+ST_NETNOTCH_WIDTH*i,ST_NETNOTCH_WIDTH); SDL_UnlockSurface(st); ncnt++; if ( ncnt >= ST_MAX_NETNOTCHES ) fadestep = 2; else fadestep = 0; break; case 2: state = STATE_WAIT; fadestep = 0; gametime = 0.; break; } break; case STATE_WAIT: // wow it's literally nothing break; } SDL_BlitSurface(st,0,ws,0); SDL_UpdateWindowSurface(w); tock = ticker(); gametime += (float)(tock-tick)/NANOS_SEC; } if ( startmus ) Mix_HaltMusic(); if ( dripsnd ) Mix_FreeChunk(dripsnd); if ( ticksnd ) Mix_FreeChunk(ticksnd); if ( startmus ) Mix_FreeMusic(startmus); Mix_Quit(); SDL_FreeSurface(st); SDL_DestroyWindow(w); SDL_Quit(); return 0; }