mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 11:35:05 +08:00
Compare commits
29 Commits
continuous
...
continuous
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c9b62cb03 | ||
|
|
9ff6eed0bf | ||
|
|
b6196c3c95 | ||
|
|
1ee2186f72 | ||
|
|
6099d5eadd | ||
|
|
3d68e13585 | ||
|
|
83b5edea88 | ||
|
|
9d2a85f11f | ||
|
|
93d3df81ee | ||
|
|
2a7f13151a | ||
|
|
0c4f6ae61d | ||
|
|
b7150c3208 | ||
|
|
d6ebff81e2 | ||
|
|
ce4148f351 | ||
|
|
08c2632c14 | ||
|
|
03eac904c1 | ||
|
|
62f3354f4e | ||
|
|
d5b6eff0c0 | ||
|
|
c9ee844a24 | ||
|
|
687c9b004f | ||
|
|
14e79f04ac | ||
|
|
5d6cf48687 | ||
|
|
88949a9ee6 | ||
|
|
2d1e76199b | ||
|
|
a5d78ecfd7 | ||
|
|
3576e474e6 | ||
|
|
d50927f21f | ||
|
|
c6e3146290 | ||
|
|
9811988cce |
2
.github/workflows/c-cpp.yml
vendored
2
.github/workflows/c-cpp.yml
vendored
@@ -66,7 +66,7 @@ jobs:
|
||||
targetos: apple
|
||||
targetarch: amd64
|
||||
env:
|
||||
SDL_VERSION: 2.30.9
|
||||
SDL_VERSION: 2.30.10
|
||||
GH_CPU_ARCH: ${{ matrix.targetarch }}
|
||||
GH_CROSSCOMPILING: ${{ matrix.cross }}
|
||||
steps:
|
||||
|
||||
2
3rdparty/mainui
vendored
2
3rdparty/mainui
vendored
Submodule 3rdparty/mainui updated: c92a06e979...734a6d1ca2
@@ -85,6 +85,9 @@ These strings are specific to Xash3D FWGS.
|
||||
As Xash3D accidentally supports GoldSrc games, it also supports parsing liblist.gam.\
|
||||
Xash3D will use this file if gameinfo.txt is absent, or if its modification timestamp is older than liblist.gam.
|
||||
|
||||
> [!NOTE]
|
||||
> Starting from January 2025, Xash3D FWGS doesn't automatically generate gameinfo.txt from liblist.gam. The key conversion table still remains but if you wish to use gameinfo.txt instead of liblist.gam, you can execute `fs_make_gameinfo` in console.
|
||||
|
||||
For game creators who plan supporting only Xash3D, using this file is not recommended.
|
||||
|
||||
The table below defines conversion rules from liblist.gam to gameinfo.txt. Some keys' interpretation does differ from `gameinfo.txt`, in this case a note will be left. If `liblist.gam` key isn't present in this table, it's ignored.
|
||||
|
||||
@@ -38,6 +38,9 @@ Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Ct
|
||||
## Deathmatch Classic
|
||||
Available in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/dmc
|
||||
|
||||
## Earth Special Forces
|
||||
Alpha 2.0 - https://www.gamers-desire.de/details/2830
|
||||
|
||||
## ESHQ
|
||||
Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ
|
||||
|
||||
|
||||
@@ -1114,7 +1114,7 @@ static void GAME_EXPORT UI_ShellExecute( const char *path, const char *parms, in
|
||||
Platform_ShellExecute( path, parms );
|
||||
|
||||
if( shouldExit )
|
||||
Sys_Quit();
|
||||
Sys_Quit( __func__ );
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -965,7 +965,7 @@ CL_Quit_f
|
||||
void CL_Quit_f( void )
|
||||
{
|
||||
CL_Disconnect();
|
||||
Sys_Quit();
|
||||
Sys_Quit( "command" );
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -1325,11 +1325,11 @@ void CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto )
|
||||
{
|
||||
int style;
|
||||
const char *s;
|
||||
float f = 0.0f;
|
||||
float f = cl.mtime[0];
|
||||
|
||||
style = MSG_ReadByte( msg );
|
||||
s = MSG_ReadString( msg );
|
||||
if( proto != PROTO_GOLDSRC )
|
||||
if( proto != PROTO_GOLDSRC && proto != PROTO_QUAKE )
|
||||
f = MSG_ReadFloat( msg );
|
||||
|
||||
CL_SetLightstyle( style, s, f );
|
||||
|
||||
@@ -970,9 +970,7 @@ void CL_ParseQuakeMessage( sizebuf_t *msg )
|
||||
CL_ParseQuakeServerInfo( msg );
|
||||
break;
|
||||
case svc_lightstyle:
|
||||
param1 = MSG_ReadByte( msg );
|
||||
str = MSG_ReadString( msg );
|
||||
CL_SetLightstyle( param1, str, cl.mtime[0] );
|
||||
CL_ParseLightStyle( msg, PROTO_QUAKE );
|
||||
break;
|
||||
case svc_updatename:
|
||||
param1 = MSG_ReadByte( msg );
|
||||
|
||||
@@ -1101,7 +1101,7 @@ void Touch_Init( void )
|
||||
Touch_AddDefaultButton( "duck", "touch_default/crouch", "+duck", 0.880000, 0.757428, 1.000000, 0.984656, color, 2, 1, 0 );
|
||||
Touch_AddDefaultButton( "tduck", "touch_default/tduck", ";+duck", 0.560000, 0.833171, 0.620000, 0.946785, color, 2, 1, 0 );
|
||||
Touch_AddDefaultButton( "edit", "touch_default/settings", "touch_enableedit", 0.420000, 0.000000, 0.500000, 0.151486, color, 2, 1, 32 );
|
||||
Touch_AddDefaultButton( "menu", "touch_default/menu", "escape", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 );
|
||||
Touch_AddDefaultButton( "menu", "touch_default/menu", "cancelselect", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 );
|
||||
Touch_AddDefaultButton( "spray", "touch_default/spray", "impulse 201", 0.680000, 0.000000, 0.760000, 0.142222, color, 2, 1, 8 );
|
||||
Touch_AddDefaultButton( "voicechat", "touch_default/microphone", "+voicerecord", 0.780000, 0.817778, 0.860000, 0.960000, color, 2, 1, 8 );
|
||||
|
||||
|
||||
@@ -633,8 +633,6 @@ Called every frame, even if not generating commands
|
||||
*/
|
||||
void Host_InputFrame( void )
|
||||
{
|
||||
Platform_SendKeyEvents();
|
||||
|
||||
IN_Commands();
|
||||
|
||||
IN_MouseMove();
|
||||
|
||||
@@ -40,7 +40,7 @@ static const keyname_t keynames[] =
|
||||
{
|
||||
{"TAB", K_TAB, "" },
|
||||
{"ENTER", K_ENTER, "" },
|
||||
{"ESCAPE", K_ESCAPE, "escape" }, // hardcoded
|
||||
{"ESCAPE", K_ESCAPE, "cancelselect" }, // hardcoded
|
||||
{"SPACE", K_SPACE, "+jump" },
|
||||
{"BACKSPACE", K_BACKSPACE, "" },
|
||||
{"UPARROW", K_UPARROW, "+forward" },
|
||||
@@ -106,7 +106,7 @@ static const keyname_t keynames[] =
|
||||
{"Y_BUTTON", K_Y_BUTTON, "impulse 100"}, // Flashlight
|
||||
{"BACK", K_BACK_BUTTON, "pause"}, // Menu
|
||||
{"MODE", K_MODE_BUTTON, ""},
|
||||
{"START", K_START_BUTTON, "escape"},
|
||||
{"START", K_START_BUTTON, "cancelselect"},
|
||||
{"STICK1", K_LSTICK, "+speed"},
|
||||
{"STICK2", K_RSTICK, "+duck"},
|
||||
{"L1_BUTTON", K_L1_BUTTON, "+duck"},
|
||||
@@ -339,6 +339,12 @@ static void Key_Unbind_f( void )
|
||||
return;
|
||||
}
|
||||
|
||||
if( b == K_ESCAPE )
|
||||
{
|
||||
Con_Printf( "Can't unbind ESCAPE key\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
Key_SetBinding( b, "" );
|
||||
}
|
||||
|
||||
@@ -358,8 +364,8 @@ static void Key_Unbindall_f( void )
|
||||
}
|
||||
|
||||
// set some defaults
|
||||
Key_SetBinding( K_ESCAPE, "escape" );
|
||||
Key_SetBinding( K_START_BUTTON, "escape" );
|
||||
Key_SetBinding( K_ESCAPE, "cancelselect" );
|
||||
Key_SetBinding( K_START_BUTTON, "cancelselect" );
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -737,18 +743,24 @@ void GAME_EXPORT Key_Event( int key, int down )
|
||||
|
||||
if( cls.key_dest == key_menu )
|
||||
{
|
||||
// only non printable keys passed
|
||||
// classic Xash3D menus don't have an extension that tells engine
|
||||
// to enable text input
|
||||
if( !gameui.use_extended_api )
|
||||
Key_EnableTextInput( true, false );
|
||||
//pass printable chars for old menus
|
||||
if( !gameui.use_extended_api && !host.textmode && down && ( key >= 32 ) && ( key <= 'z' ) )
|
||||
{
|
||||
if( Key_IsDown( K_SHIFT ) )
|
||||
// we don't know if menu wants text input or not
|
||||
// enable it unconditionally
|
||||
Key_EnableTextInput( true, false );
|
||||
|
||||
// pass this key to the menu, if printable
|
||||
if( !host.textmode && down && ( key >= 32 ) && ( key <= 'z' ))
|
||||
{
|
||||
key += 'A' - 'a';
|
||||
if( Key_IsDown( K_SHIFT ))
|
||||
key += 'A' - 'a';
|
||||
|
||||
UI_CharEvent( key );
|
||||
}
|
||||
UI_CharEvent( key );
|
||||
}
|
||||
|
||||
UI_KeyEvent( key, down );
|
||||
return;
|
||||
}
|
||||
@@ -844,11 +856,12 @@ void GAME_EXPORT Key_ClearStates( void )
|
||||
int i;
|
||||
|
||||
// don't clear keys during changelevel
|
||||
if( cls.changelevel ) return;
|
||||
if( cls.changelevel )
|
||||
return;
|
||||
|
||||
for( i = 0; i < 256; i++ )
|
||||
{
|
||||
if( keys[i].down )
|
||||
if( keys[i].down && i < K_MOUSE1 && i > K_MOUSE5 )
|
||||
Key_Event( i, false );
|
||||
|
||||
keys[i].down = 0;
|
||||
@@ -856,6 +869,9 @@ void GAME_EXPORT Key_ClearStates( void )
|
||||
keys[i].gamedown = 0;
|
||||
}
|
||||
|
||||
for( i = K_MOUSE1; i < K_MOUSE5; i++ ) // from K_MOUSE1 to K_MOUSE5
|
||||
IN_MouseEvent( i - K_MOUSE1, false );
|
||||
|
||||
if( clgame.hInstance )
|
||||
clgame.dllFuncs.IN_ClearStates();
|
||||
}
|
||||
|
||||
@@ -25,20 +25,29 @@ GNU General Public License for more details.
|
||||
|
||||
#define VGUI_MAX_TEXTURES 1024
|
||||
|
||||
typedef struct vgui_reusable_texture_s
|
||||
{
|
||||
int gl_texturenum;
|
||||
byte hash[16];
|
||||
} vgui_reusable_texture_t;
|
||||
|
||||
typedef struct vgui_static_s
|
||||
{
|
||||
qboolean initialized;
|
||||
VGUI_DefaultCursor cursor;
|
||||
vguiapi_t dllFuncs;
|
||||
|
||||
int textures[VGUI_MAX_TEXTURES];
|
||||
vgui_reusable_texture_t *textures;
|
||||
int texture_id;
|
||||
int max_textures;
|
||||
int bound_texture;
|
||||
byte color[4];
|
||||
qboolean enable_texture;
|
||||
|
||||
HINSTANCE hInstance;
|
||||
|
||||
poolhandle_t mempool;
|
||||
|
||||
enum VGUI_KeyCode virtualKeyTrans[256];
|
||||
} vgui_static_t;
|
||||
|
||||
@@ -49,9 +58,16 @@ static CVAR_DEFINE_AUTO( vgui_utf8, "0", FCVAR_ARCHIVE, "enable utf-8 support fo
|
||||
|
||||
static void GAME_EXPORT VGUI_DrawInit( void )
|
||||
{
|
||||
memset( vgui.textures, 0, sizeof( vgui.textures ));
|
||||
if( vgui.mempool )
|
||||
Mem_EmptyPool( vgui.mempool );
|
||||
else vgui.mempool = Mem_AllocPool( "VGui Support Pool" );
|
||||
|
||||
vgui.textures = NULL;
|
||||
|
||||
memset( vgui.color, 0, sizeof( vgui.color ));
|
||||
vgui.texture_id = vgui.bound_texture = 0;
|
||||
vgui.texture_id = 0;
|
||||
vgui.bound_texture = 0;
|
||||
vgui.max_textures = 0;
|
||||
vgui.enable_texture = true;
|
||||
}
|
||||
|
||||
@@ -60,28 +76,79 @@ static void GAME_EXPORT VGUI_DrawShutdown( void )
|
||||
int i;
|
||||
|
||||
for( i = 1; i < vgui.texture_id; i++ )
|
||||
ref.dllFuncs.GL_FreeTexture( vgui.textures[i] );
|
||||
ref.dllFuncs.GL_FreeTexture( vgui.textures[i].gl_texturenum );
|
||||
|
||||
Mem_FreePool( &vgui.mempool );
|
||||
vgui.textures = NULL;
|
||||
|
||||
memset( vgui.color, 0, sizeof( vgui.color ));
|
||||
vgui.texture_id = 0;
|
||||
vgui.bound_texture = 0;
|
||||
vgui.max_textures = 0;
|
||||
}
|
||||
|
||||
static int GAME_EXPORT VGUI_GenerateTexture( void )
|
||||
{
|
||||
if( ++vgui.texture_id >= VGUI_MAX_TEXTURES )
|
||||
Host_Error( "%s: VGUI_MAX_TEXTURES limit exceeded\n", __func__ );
|
||||
// allocate new
|
||||
if( vgui.texture_id + 1 >= vgui.max_textures )
|
||||
{
|
||||
if( vgui.max_textures + VGUI_MAX_TEXTURES >= VGUI_MAX_TEXTURES * VGUI_MAX_TEXTURES )
|
||||
{
|
||||
// in theory it might look up texture that hasn't been bound for a while and
|
||||
// reuse that but it will eventually overwrite some important textures anyway
|
||||
Con_Printf( S_ERROR "%s: Refusing resizing VGUI textures array due to memory leak\n", __func__ );
|
||||
return vgui.texture_id;
|
||||
}
|
||||
|
||||
return vgui.texture_id;
|
||||
vgui.max_textures += VGUI_MAX_TEXTURES;
|
||||
|
||||
// this potentially might leak memory if VGUI is used incorrectly!
|
||||
// (like in Cry of Fear)
|
||||
vgui.textures = Mem_Realloc( vgui.mempool, vgui.textures, sizeof( *vgui.textures ) * vgui.max_textures );
|
||||
|
||||
// warn mod developer
|
||||
if( vgui.max_textures >= VGUI_MAX_TEXTURES * 4 )
|
||||
Con_Printf( S_ERROR "%s: Potential memory leak in VGUI code is detected!\n", __func__ );
|
||||
}
|
||||
|
||||
return ++vgui.texture_id;
|
||||
}
|
||||
|
||||
static void GAME_EXPORT VGUI_UploadTexture( int id, const char *buffer, int width, int height )
|
||||
{
|
||||
rgbdata_t r_image = { 0 };
|
||||
char texName[32];
|
||||
MD5Context_t ctx;
|
||||
byte hash[16];
|
||||
|
||||
if( id <= 0 || id >= VGUI_MAX_TEXTURES )
|
||||
if( id <= 0 || id >= vgui.max_textures || width <= 0 || height <= 0 )
|
||||
{
|
||||
Con_DPrintf( S_ERROR "%s: bad texture %i. Ignored\n", __func__, id );
|
||||
return;
|
||||
}
|
||||
|
||||
// need to do this as some mods tend to upload same texture over and over
|
||||
// exhausing engine-wide limit on textures and leaking vram
|
||||
MD5Init( &ctx );
|
||||
MD5Update( &ctx, buffer, width * height * 4 );
|
||||
MD5Final( hash, &ctx );
|
||||
|
||||
// it's a new texture, try to find a copy
|
||||
if( vgui.textures[id].gl_texturenum == 0 )
|
||||
{
|
||||
int i;
|
||||
|
||||
for( i = 1; i < vgui.texture_id; i++ )
|
||||
{
|
||||
if( vgui.textures[i].gl_texturenum != 0 && !memcmp( vgui.textures[i].hash, hash, sizeof( hash )))
|
||||
{
|
||||
// copy data to new texture id
|
||||
vgui.textures[id] = vgui.textures[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Q_snprintf( texName, sizeof( texName ), "*vgui%i", id );
|
||||
|
||||
r_image.width = width;
|
||||
@@ -91,51 +158,29 @@ static void GAME_EXPORT VGUI_UploadTexture( int id, const char *buffer, int widt
|
||||
r_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA;
|
||||
r_image.buffer = (byte*)buffer;
|
||||
|
||||
vgui.textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE );
|
||||
vgui.textures[id].gl_texturenum = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE );
|
||||
memcpy( vgui.textures[id].hash, hash, sizeof( hash ));
|
||||
}
|
||||
|
||||
static void GAME_EXPORT VGUI_CreateTexture( int id, int width, int height )
|
||||
{
|
||||
rgbdata_t r_image = { 0 };
|
||||
char texName[32];
|
||||
// nothing uses it, it can be removed
|
||||
Host_Error( "%s: deprecated\n", __func__ );
|
||||
|
||||
if( id <= 0 || id >= VGUI_MAX_TEXTURES )
|
||||
{
|
||||
Con_DPrintf( S_ERROR "%s: bad texture %i. Ignored\n", __func__, id );
|
||||
return;
|
||||
}
|
||||
|
||||
Q_snprintf( texName, sizeof( texName ), "*vgui%i", id );
|
||||
|
||||
r_image.width = width;
|
||||
r_image.height = height;
|
||||
r_image.type = PF_RGBA_32;
|
||||
r_image.size = width * height * 4;
|
||||
r_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA;
|
||||
r_image.buffer = NULL;
|
||||
|
||||
vgui.textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE );
|
||||
vgui.bound_texture = id;
|
||||
}
|
||||
|
||||
static void GAME_EXPORT VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight )
|
||||
{
|
||||
if( id <= 0 || id >= VGUI_MAX_TEXTURES || vgui.textures[id] == 0 )
|
||||
{
|
||||
Con_DPrintf( S_ERROR "%s: bad texture %i. Ignored\n", __func__, id );
|
||||
return;
|
||||
}
|
||||
|
||||
ref.dllFuncs.VGUI_UploadTextureBlock( drawX, drawY, rgba, blockWidth, blockHeight );
|
||||
vgui.bound_texture = id;
|
||||
// nothing uses it, it can be removed
|
||||
Host_Error( "%s: deprecated\n", __func__ );
|
||||
}
|
||||
|
||||
static void GAME_EXPORT VGUI_BindTexture( int id )
|
||||
{
|
||||
if( id <= 0 || id >= VGUI_MAX_TEXTURES || !vgui.textures[id] )
|
||||
if( id <= 0 || id >= vgui.max_textures || !vgui.textures[id].gl_texturenum )
|
||||
id = 1; // NOTE: same as bogus index 2700 in GoldSrc
|
||||
|
||||
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, vgui.textures[id] );
|
||||
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, vgui.textures[id].gl_texturenum );
|
||||
vgui.bound_texture = id;
|
||||
}
|
||||
|
||||
@@ -144,7 +189,7 @@ static void GAME_EXPORT VGUI_GetTextureSizes( int *w, int *h )
|
||||
int texnum;
|
||||
|
||||
if( vgui.bound_texture )
|
||||
texnum = vgui.textures[vgui.bound_texture];
|
||||
texnum = vgui.textures[vgui.bound_texture].gl_texturenum;
|
||||
else
|
||||
texnum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );
|
||||
|
||||
@@ -187,7 +232,7 @@ static void GAME_EXPORT VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr )
|
||||
t2 = lr->coord[1];
|
||||
|
||||
ref.dllFuncs.Color4ub( vgui.color[0], vgui.color[1], vgui.color[2], vgui.color[3] );
|
||||
ref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, vgui.textures[vgui.bound_texture] );
|
||||
ref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, vgui.textures[vgui.bound_texture].gl_texturenum );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -142,28 +142,33 @@ void Cbuf_AddFilteredText( const char *text )
|
||||
Cbuf_InsertText
|
||||
|
||||
Adds command text immediately after the current command
|
||||
Adds a \n to the text
|
||||
============
|
||||
*/
|
||||
static void Cbuf_InsertTextToBuffer( cmdbuf_t *buf, const char *text )
|
||||
static void Cbuf_InsertTextToBuffer( cmdbuf_t *buf, const char *text, size_t len, size_t requested_len )
|
||||
{
|
||||
int l = Q_strlen( text );
|
||||
|
||||
if(( buf->cursize + l ) >= buf->maxsize )
|
||||
if(( buf->cursize + requested_len ) >= buf->maxsize )
|
||||
{
|
||||
Con_Reportf( S_WARN "%s: overflow\n", __func__ );
|
||||
}
|
||||
else
|
||||
{
|
||||
memmove( buf->data + l, buf->data, buf->cursize );
|
||||
memcpy( buf->data, text, l );
|
||||
buf->cursize += l;
|
||||
memmove( buf->data + len, buf->data, buf->cursize );
|
||||
memcpy( buf->data, text, len );
|
||||
buf->cursize += len;
|
||||
}
|
||||
}
|
||||
|
||||
void Cbuf_InsertTextLen( const char *text, size_t len, size_t requested_len )
|
||||
{
|
||||
// sometimes we need to insert more data than we have
|
||||
// but also prevent overflow
|
||||
Cbuf_InsertTextToBuffer( &cmd_text, text, len, requested_len );
|
||||
}
|
||||
|
||||
void Cbuf_InsertText( const char *text )
|
||||
{
|
||||
Cbuf_InsertTextToBuffer( &cmd_text, text );
|
||||
size_t l = Q_strlen( text );
|
||||
Cbuf_InsertTextToBuffer( &cmd_text, text, l, l );
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -712,7 +717,7 @@ int Cmd_AddCommandEx( const char *cmd_name, xcommand_t function, const char *cmd
|
||||
}
|
||||
else
|
||||
{
|
||||
Con_DPrintf( S_ERROR "%s: %s already defined\n", funcname, cmd_name );
|
||||
Con_DPrintf( "%s%s: %s already defined\n", cmd->function == function ? S_WARN : S_ERROR, funcname, cmd_name );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1010,9 +1015,10 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr
|
||||
|
||||
if( a )
|
||||
{
|
||||
size_t len = Q_strlen( a->value );
|
||||
Cbuf_InsertTextToBuffer(
|
||||
isPrivileged ? &cmd_text : &filteredcmd_text,
|
||||
a->value );
|
||||
a->value, len, len );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,6 +425,7 @@ void Cbuf_AddText( const char *text );
|
||||
void Cbuf_AddTextf( const char *text, ... ) FORMAT_CHECK( 1 );
|
||||
void Cbuf_AddFilteredText( const char *text );
|
||||
void Cbuf_InsertText( const char *text );
|
||||
void Cbuf_InsertTextLen( const char *text, size_t len, size_t requested_len );
|
||||
void Cbuf_ExecStuffCmds( void );
|
||||
void Cbuf_Execute (void);
|
||||
qboolean Cmd_CurrentCommandIsPrivileged( void );
|
||||
@@ -547,7 +548,7 @@ qboolean Sound_SupportedFileFormat( const char *fileext );
|
||||
typedef void( *pfnChangeGame )( const char *progname );
|
||||
|
||||
qboolean Host_IsQuakeCompatible( void );
|
||||
void EXPORT Host_Shutdown( void );
|
||||
void Host_ShutdownWithReason( const char *reason );
|
||||
int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func );
|
||||
void Host_EndGame( qboolean abort, const char *message, ... ) FORMAT_CHECK( 2 );
|
||||
void Host_AbortCurrentFrame( void ) NORETURN;
|
||||
|
||||
@@ -84,6 +84,11 @@ static void FS_Path_f_( void )
|
||||
FS_Path_f();
|
||||
}
|
||||
|
||||
static void FS_MakeGameInfo_f( void )
|
||||
{
|
||||
g_fsapi.MakeGameInfo();
|
||||
}
|
||||
|
||||
static const fs_interface_t fs_memfuncs =
|
||||
{
|
||||
Con_Printf,
|
||||
@@ -272,6 +277,7 @@ void FS_Init( const char *basedir )
|
||||
Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" );
|
||||
Cmd_AddRestrictedCommand( "fs_path", FS_Path_f_, "show filesystem search pathes" );
|
||||
Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" );
|
||||
Cmd_AddRestrictedCommand( "fs_make_gameinfo", FS_MakeGameInfo_f, "create gameinfo.txt for current running game" );
|
||||
|
||||
if( !Sys_GetParmFromCmdLine( "-dll", host.gamedll ))
|
||||
host.gamedll[0] = 0;
|
||||
|
||||
@@ -209,7 +209,7 @@ static void Sys_PrintUsage( const char *exename )
|
||||
fprintf( stderr, usage_str, exename );
|
||||
#endif
|
||||
|
||||
Sys_Quit();
|
||||
Sys_Quit( NULL );
|
||||
}
|
||||
|
||||
static void Sys_PrintBugcompUsage( const char *exename )
|
||||
@@ -234,7 +234,7 @@ static void Sys_PrintBugcompUsage( const char *exename )
|
||||
fprintf( stderr, usage_str, exename );
|
||||
#endif
|
||||
|
||||
Sys_Quit();
|
||||
Sys_Quit( NULL );
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -418,8 +418,7 @@ static void Host_Exec_f( void )
|
||||
{
|
||||
string cfgpath;
|
||||
byte *f;
|
||||
char *txt;
|
||||
fs_offset_t len;
|
||||
fs_offset_t len;
|
||||
|
||||
if( Cmd_Argc() != 2 )
|
||||
{
|
||||
@@ -474,20 +473,29 @@ static void Host_Exec_f( void )
|
||||
return;
|
||||
}
|
||||
|
||||
// len is fs_offset_t, which can be larger than size_t
|
||||
if( len >= SIZE_MAX )
|
||||
{
|
||||
Con_Reportf( "%s: %s is too long\n", __func__, Cmd_Argv( 1 ));
|
||||
return;
|
||||
}
|
||||
|
||||
if( !Q_stricmp( "config.cfg", cfgpath ))
|
||||
host.config_executed = true;
|
||||
|
||||
// adds \n\0 at end of the file
|
||||
txt = Z_Calloc( len + 2 );
|
||||
memcpy( txt, f, len );
|
||||
txt[len] = '\n';
|
||||
txt[len + 1] = '\0';
|
||||
Mem_Free( f );
|
||||
|
||||
if( !host.apply_game_config )
|
||||
Con_Printf( "execing %s\n", Cmd_Argv( 1 ));
|
||||
Cbuf_InsertText( txt );
|
||||
Mem_Free( txt );
|
||||
|
||||
// adds \n at end of the file
|
||||
// FS_LoadFile always null terminates
|
||||
if( f[len - 1] != '\n' )
|
||||
{
|
||||
Cbuf_InsertTextLen( f, len, len + 1 );
|
||||
Cbuf_InsertTextLen( "\n", 1, 1 );
|
||||
}
|
||||
else Cbuf_InsertTextLen( f, len, len );
|
||||
|
||||
Mem_Free( f );
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -809,11 +817,11 @@ void GAME_EXPORT Host_Error( const char *error, ... )
|
||||
}
|
||||
else
|
||||
{
|
||||
Con_Printf( "%s: %s", __func__, hosterror1 );
|
||||
if( host.allow_console )
|
||||
{
|
||||
UI_SetActiveMenu( false );
|
||||
Key_SetKeyDest( key_console );
|
||||
Con_Printf( "%s: %s", __func__, hosterror1 );
|
||||
}
|
||||
else Platform_MessageBox( "Host Error", hosterror1, true );
|
||||
}
|
||||
@@ -915,7 +923,7 @@ static void Host_RunTests( int stage )
|
||||
#endif
|
||||
Msg( "Done! %d passed, %d failed\n", tests_stats.passed, tests_stats.failed );
|
||||
error_on_exit = tests_stats.failed > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
Sys_Quit();
|
||||
Sys_Quit( NULL );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1162,6 +1170,11 @@ static void Host_FreeCommon( void )
|
||||
FS_Shutdown();
|
||||
}
|
||||
|
||||
static void Sys_Quit_f( void )
|
||||
{
|
||||
Sys_Quit( "command" );
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Host_Main
|
||||
@@ -1236,8 +1249,8 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa
|
||||
// disable texture replacements for dedicated
|
||||
Cvar_FullSet( "host_allow_materials", "0", FCVAR_READ_ONLY );
|
||||
|
||||
Cmd_AddRestrictedCommand( "quit", Sys_Quit, "quit the game" );
|
||||
Cmd_AddRestrictedCommand( "exit", Sys_Quit, "quit the game" );
|
||||
Cmd_AddRestrictedCommand( "quit", Sys_Quit_f, "quit the game" );
|
||||
Cmd_AddRestrictedCommand( "exit", Sys_Quit_f, "quit the game" );
|
||||
}
|
||||
else Cmd_AddRestrictedCommand( "minimize", Host_Minimize_f, "minimize main window to tray" );
|
||||
|
||||
@@ -1320,20 +1333,34 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa
|
||||
return 0;
|
||||
}
|
||||
|
||||
void EXPORT Host_Shutdown( void );
|
||||
void EXPORT Host_Shutdown( void )
|
||||
{
|
||||
Host_ShutdownWithReason( "launcher shutdown" );
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Host_Shutdown
|
||||
=================
|
||||
*/
|
||||
void EXPORT Host_Shutdown( void )
|
||||
void Host_ShutdownWithReason( const char *reason )
|
||||
{
|
||||
qboolean error = host.status == HOST_ERR_FATAL;
|
||||
|
||||
if( host.shutdown_issued ) return;
|
||||
if( host.shutdown_issued )
|
||||
return;
|
||||
|
||||
host.shutdown_issued = true;
|
||||
|
||||
if( host.status != HOST_ERR_FATAL ) host.status = HOST_SHUTDOWN; // prepare host to normal shutdown
|
||||
if( !host.change_game ) Q_strncpy( host.finalmsg, "Server shutdown", sizeof( host.finalmsg ));
|
||||
if( reason != NULL )
|
||||
Con_Printf( S_NOTE "Issuing host shutdown due to reason \"%s\"\n", reason );
|
||||
|
||||
if( host.status != HOST_ERR_FATAL )
|
||||
host.status = HOST_SHUTDOWN; // prepare host to normal shutdown
|
||||
|
||||
if( !host.change_game )
|
||||
Q_strncpy( host.finalmsg, "Server shutdown", sizeof( host.finalmsg ));
|
||||
|
||||
#if !XASH_DEDICATED
|
||||
if( host.type == HOST_NORMAL && !error )
|
||||
|
||||
@@ -37,11 +37,7 @@ static char **szArgv;
|
||||
|
||||
static void Sys_ChangeGame( const char *progname )
|
||||
{
|
||||
// a1ba: may never be called within engine
|
||||
// if platform supports execv() function
|
||||
Q_strncpy( szGameDir, progname, sizeof( szGameDir ));
|
||||
Host_Shutdown( );
|
||||
exit( Host_Main( szArgc, szArgv, szGameDir, 1, &Sys_ChangeGame ) );
|
||||
// stub
|
||||
}
|
||||
|
||||
static int Sys_Start( void )
|
||||
|
||||
@@ -385,10 +385,7 @@ void NET_SaveMasters( void )
|
||||
master_t *m;
|
||||
|
||||
if( !ml.modified )
|
||||
{
|
||||
Con_Reportf( "Master server list not changed\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
f = FS_Open( "xashcomm.lst", "w", true );
|
||||
|
||||
|
||||
@@ -412,7 +412,7 @@ void Sys_Error( const char *error, ... )
|
||||
Sys_WaitForQuit();
|
||||
}
|
||||
|
||||
Sys_Quit();
|
||||
Sys_Quit( "caught an error" );
|
||||
}
|
||||
|
||||
#if XASH_EMSCRIPTEN
|
||||
@@ -435,9 +435,9 @@ void my_exit(int ret)
|
||||
Sys_Quit
|
||||
================
|
||||
*/
|
||||
void Sys_Quit( void )
|
||||
void Sys_Quit( const char *reason )
|
||||
{
|
||||
Host_Shutdown();
|
||||
Host_ShutdownWithReason( reason );
|
||||
exit( error_on_exit );
|
||||
}
|
||||
|
||||
@@ -540,7 +540,7 @@ qboolean Sys_NewInstance( const char *gamedir )
|
||||
// just restart the entire thing
|
||||
printf( "envSetNextLoad exe: `%s`\n", exe );
|
||||
printf( "envSetNextLoad argv:\n`%s`\n", newargs );
|
||||
Host_Shutdown( );
|
||||
Host_ShutdownWithReason( "changing game" );
|
||||
envSetNextLoad( exe, newargs );
|
||||
exit( 0 );
|
||||
#else
|
||||
@@ -578,7 +578,7 @@ qboolean Sys_NewInstance( const char *gamedir )
|
||||
#if XASH_PSVITA
|
||||
// under normal circumstances it's always going to be the same path
|
||||
exe = strdup( "app0:/eboot.bin" );
|
||||
Host_Shutdown( );
|
||||
Host_ShutdownWithReason( "changing game" );
|
||||
sceAppMgrLoadExec( exe, newargs, NULL );
|
||||
#else
|
||||
exelen = wai_getExecutablePath( NULL, 0, NULL );
|
||||
@@ -586,7 +586,7 @@ qboolean Sys_NewInstance( const char *gamedir )
|
||||
wai_getExecutablePath( exe, exelen, NULL );
|
||||
exe[exelen] = 0;
|
||||
|
||||
Host_Shutdown();
|
||||
Host_ShutdownWithReason( "changing game" );
|
||||
|
||||
execv( exe, newargs );
|
||||
#endif
|
||||
|
||||
@@ -55,12 +55,11 @@ void Sys_DebugBreak( void );
|
||||
#define Sys_GetParmFromCmdLine( parm, out ) _Sys_GetParmFromCmdLine( parm, out, sizeof( out ))
|
||||
qboolean _Sys_GetParmFromCmdLine( const char *parm, char *out, size_t size );
|
||||
qboolean Sys_GetIntFromCmdLine( const char *parm, int *out );
|
||||
void Sys_SendKeyEvents( void );
|
||||
void Sys_Print( const char *pMsg );
|
||||
void Sys_PrintLog( const char *pMsg );
|
||||
void Sys_InitLog( void );
|
||||
void Sys_CloseLog( void );
|
||||
void Sys_Quit( void ) NORETURN;
|
||||
void Sys_Quit( const char *reason ) NORETURN;
|
||||
qboolean Sys_NewInstance( const char *gamedir );
|
||||
void *Sys_GetNativeObject( const char *obj );
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ long pathconf( const char *path, int name ) { return -1; }
|
||||
|
||||
// part of libunwind; required by any dynamic lib that uses C++ exceptions
|
||||
extern void *_Unwind_Resume;
|
||||
extern void *_Unwind_GetIPInfo;
|
||||
|
||||
// these are macros in our libc, so we need to wrap them
|
||||
static int tolower_fn( int c ) { return tolower( c ); }
|
||||
@@ -55,6 +56,7 @@ static const solder_export_t aux_exports[] =
|
||||
SOLDER_EXPORT_SYMBOL( strchrnul ),
|
||||
SOLDER_EXPORT_SYMBOL( stpcpy ),
|
||||
SOLDER_EXPORT_SYMBOL( _Unwind_Resume ),
|
||||
SOLDER_EXPORT_SYMBOL( _Unwind_GetIPInfo ),
|
||||
};
|
||||
|
||||
const solder_export_t *__solder_aux_exports = aux_exports;
|
||||
|
||||
@@ -253,14 +253,6 @@ static inline char *Platform_Input( void )
|
||||
#endif
|
||||
}
|
||||
|
||||
#if XASH_WIN32
|
||||
void Platform_SendKeyEvents( void );
|
||||
#else
|
||||
static inline void Platform_SendKeyEvents( void )
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
|
||||
@@ -197,8 +197,7 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context)
|
||||
CL_Crashed();
|
||||
host.status = HOST_CRASHED;
|
||||
|
||||
|
||||
Sys_Quit();
|
||||
Sys_Quit( "crashed" );
|
||||
}
|
||||
|
||||
void Sys_SetupCrashHandler( void )
|
||||
|
||||
@@ -148,7 +148,9 @@ void Posix_Daemonize( void )
|
||||
|
||||
static void Posix_SigtermCallback( int signal )
|
||||
{
|
||||
Sys_Quit();
|
||||
string reason;
|
||||
Con_Printf( reason, sizeof( reason ), "caught signal %d", signal );
|
||||
Sys_Quit( reason );
|
||||
}
|
||||
|
||||
void Posix_SetupSigtermHandling( void )
|
||||
|
||||
@@ -396,8 +396,12 @@ static void SDLash_ActiveEvent( int gain )
|
||||
}
|
||||
#endif
|
||||
host.status = HOST_NOFOCUS;
|
||||
|
||||
if( cls.key_dest == key_game )
|
||||
{
|
||||
Key_ClearStates();
|
||||
IN_DeactivateMouse();
|
||||
}
|
||||
|
||||
host.force_draw_version_time = host.realtime + 2.0;
|
||||
VID_RestoreScreenResolution();
|
||||
@@ -498,7 +502,7 @@ static void SDLash_EventHandler( SDL_Event *event )
|
||||
break;
|
||||
|
||||
case SDL_QUIT:
|
||||
Sys_Quit();
|
||||
Sys_Quit( "caught SDL_QUIT" );
|
||||
break;
|
||||
#if SDL_VERSION_ATLEAST( 2, 0, 0 )
|
||||
case SDL_MOUSEWHEEL:
|
||||
|
||||
@@ -99,13 +99,43 @@ qboolean SNDDMA_Init( void )
|
||||
{
|
||||
SDL_AudioSpec desired, obtained;
|
||||
int samplecount;
|
||||
const char *driver = NULL;
|
||||
|
||||
// Modders often tend to use proprietary crappy solutions
|
||||
// like FMOD to play music, sometimes even with versions outdated by a few decades!
|
||||
//
|
||||
// As these bullshit sound engines prefer to use DirectSound, we ask SDL2 to do
|
||||
// the same. Why you might ask? If SDL2 uses another audio API, like WASAPI on
|
||||
// more modern versions of Windows, it breaks the logic inside Windows, and the whole
|
||||
// application could hang in WaitFor{Single,Multiple}Object function, either called by
|
||||
// SDL2 if FMOD was shut down first, or deep in dsound.dll->fmod.dll if SDL2 audio
|
||||
// was shut down first.
|
||||
//
|
||||
// I honestly don't know who is the real culprit here: FMOD, HL modders, Windows, SDL2
|
||||
// or us.
|
||||
//
|
||||
// Also, fun note, GoldSrc seems doesn't use SDL2 for sound stuff at all, as nothing
|
||||
// reference SDL audio functions there. It's probably has DirectSound backend, that's
|
||||
// why modders never stumble upon this bug.
|
||||
#if XASH_WIN32
|
||||
driver = "directsound";
|
||||
|
||||
if( SDL_getenv( "SDL_AUDIODRIVER" ))
|
||||
driver = NULL; // let SDL2 and user decide
|
||||
|
||||
SDL_SetHint( SDL_HINT_AUDIODRIVER, driver );
|
||||
#endif // XASH_WIN32
|
||||
|
||||
// even if we don't have PA
|
||||
// we still can safely set env variables
|
||||
SDL_setenv( "PULSE_PROP_application.name", GI->title, 1 );
|
||||
SDL_setenv( "PULSE_PROP_media.role", "game", 1 );
|
||||
|
||||
if( SDL_Init( SDL_INIT_AUDIO ))
|
||||
// reinitialize SDL with our driver just in case
|
||||
if( SDL_WasInit( SDL_INIT_AUDIO ))
|
||||
SDL_QuitSubSystem( SDL_INIT_AUDIO );
|
||||
|
||||
if( SDL_InitSubSystem( SDL_INIT_AUDIO ))
|
||||
{
|
||||
Con_Reportf( S_ERROR "Audio: SDL: %s \n", SDL_GetError( ) );
|
||||
return false;
|
||||
@@ -212,7 +242,7 @@ void SNDDMA_Shutdown( void )
|
||||
}
|
||||
|
||||
#if !XASH_EMSCRIPTEN
|
||||
if( SDL_WasInit( SDL_INIT_AUDIO ) )
|
||||
if( SDL_WasInit( SDL_INIT_AUDIO ))
|
||||
SDL_QuitSubSystem( SDL_INIT_AUDIO );
|
||||
#endif
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ static long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo )
|
||||
if( host_developer.value <= 0 )
|
||||
{
|
||||
// no reason to call debugger in release build - just exit
|
||||
Sys_Quit();
|
||||
Sys_Quit( "crashed" );
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,17 +56,3 @@ void Platform_MessageBox( const char *title, const char *message, qboolean paren
|
||||
}
|
||||
#endif // XASH_MESSAGEBOX == MSGBOX_WIN32
|
||||
|
||||
// I don't know why we need this or what this does
|
||||
void Platform_SendKeyEvents( void )
|
||||
{
|
||||
MSG msg;
|
||||
|
||||
while( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ))
|
||||
{
|
||||
if( !GetMessage( &msg, NULL, 0, 0 ))
|
||||
Sys_Quit ();
|
||||
|
||||
TranslateMessage( &msg );
|
||||
DispatchMessage( &msg );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1606,6 +1606,10 @@ static int LoadGameState( char const *level, qboolean changelevel )
|
||||
pSaveData = LoadSaveData( level );
|
||||
if( !pSaveData ) return 0; // couldn't load the file
|
||||
|
||||
// must set mapname before calling into DLL
|
||||
Q_strncpy( sv.name, level, sizeof( sv.name ));
|
||||
svgame.globals->mapname = MAKE_STRING( sv.name );
|
||||
|
||||
ParseSaveTables( pSaveData, &header, true );
|
||||
EntityPatchRead( pSaveData, level );
|
||||
|
||||
@@ -1613,8 +1617,6 @@ static int LoadGameState( char const *level, qboolean changelevel )
|
||||
sv.loadgame = sv.paused = true;
|
||||
|
||||
Cvar_SetValue( "skill", header.skillLevel );
|
||||
Q_strncpy( sv.name, header.mapName, sizeof( sv.name ));
|
||||
svgame.globals->mapname = MAKE_STRING( sv.name );
|
||||
Cvar_Set( "sv_skyname", header.skyName );
|
||||
|
||||
// restore sky parms
|
||||
|
||||
@@ -170,9 +170,9 @@ typedef struct vguiapi_s
|
||||
void (*SetupDrawingImage)( int *pColor );
|
||||
void (*BindTexture)( int id );
|
||||
void (*EnableTexture)( qboolean enable );
|
||||
void (*CreateTexture)( int id, int width, int height );
|
||||
void (*Reserved0)( int id, int width, int height );
|
||||
void (*UploadTexture)( int id, const char *buffer, int width, int height );
|
||||
void (*UploadTextureBlock)( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight );
|
||||
void (*Reserved1)( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight );
|
||||
void (*DrawQuad)( const vpoint_t *ul, const vpoint_t *lr );
|
||||
void (*GetTextureSizes)( int *width, int *height );
|
||||
int (*GenerateTexture)( void );
|
||||
|
||||
@@ -134,7 +134,7 @@ static void FS_PopulateDirEntries( dir_t *dir, const char *path )
|
||||
}
|
||||
|
||||
stringlistinit( &list );
|
||||
listdirectory( &list, path );
|
||||
listdirectory( &list, path, false );
|
||||
if( !list.numstrings )
|
||||
{
|
||||
dir->numentries = DIRENTRY_EMPTY_DIRECTORY;
|
||||
@@ -225,7 +225,7 @@ static int FS_MaybeUpdateDirEntries( dir_t *dir, const char *path, const char *e
|
||||
int ret;
|
||||
|
||||
stringlistinit( &list );
|
||||
listdirectory( &list, path );
|
||||
listdirectory( &list, path, false );
|
||||
|
||||
if( list.numstrings == 0 ) // empty directory
|
||||
{
|
||||
@@ -421,7 +421,7 @@ static void FS_Search_DIR( searchpath_t *search, stringlist_t *list, const char
|
||||
}
|
||||
|
||||
stringlistinit( &dirlist );
|
||||
listdirectory( &dirlist, netpath );
|
||||
listdirectory( &dirlist, netpath, false );
|
||||
|
||||
Q_strncpy( temp, basepath, sizeof( temp ));
|
||||
|
||||
|
||||
@@ -225,18 +225,13 @@ static void listlowercase( stringlist_t *list )
|
||||
}
|
||||
#endif
|
||||
|
||||
void listdirectory( stringlist_t *list, const char *path )
|
||||
void listdirectory( stringlist_t *list, const char *path, qboolean dirs_only )
|
||||
{
|
||||
#if XASH_WIN32
|
||||
char pattern[4096];
|
||||
struct _finddata_t n_file;
|
||||
intptr_t hFile;
|
||||
#else
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
#endif
|
||||
struct _finddata_t n_file;
|
||||
intptr_t hFile;
|
||||
|
||||
#if XASH_WIN32
|
||||
Q_snprintf( pattern, sizeof( pattern ), "%s/*", path );
|
||||
|
||||
// ask for the directory listing handle
|
||||
@@ -247,15 +242,33 @@ void listdirectory( stringlist_t *list, const char *path )
|
||||
stringlistappend( list, n_file.name );
|
||||
// iterate through the directory
|
||||
while( _findnext( hFile, &n_file ) == 0 )
|
||||
{
|
||||
if( dirs_only && !FBitSet( n_file.attrib, _A_SUBDIR ))
|
||||
continue;
|
||||
|
||||
stringlistappend( list, n_file.name );
|
||||
}
|
||||
_findclose( hFile );
|
||||
#else
|
||||
if( !( dir = opendir( path ) ) )
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
|
||||
dir = opendir( path );
|
||||
|
||||
if( !dir )
|
||||
return;
|
||||
|
||||
// iterate through the directory
|
||||
while( ( entry = readdir( dir ) ))
|
||||
while(( entry = readdir( dir )))
|
||||
{
|
||||
#if HAVE_DIRENT_D_TYPE
|
||||
if( dirs_only && entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN )
|
||||
continue;
|
||||
#endif
|
||||
|
||||
stringlistappend( list, entry->d_name );
|
||||
}
|
||||
|
||||
closedir( dir );
|
||||
#endif
|
||||
|
||||
@@ -396,7 +409,7 @@ void FS_AddGameDirectory( const char *dir, uint flags )
|
||||
int i;
|
||||
|
||||
stringlistinit( &list );
|
||||
listdirectory( &list, dir );
|
||||
listdirectory( &list, dir, false );
|
||||
stringlistsort( &list );
|
||||
|
||||
for( archive = g_archives; archive->ext; archive++ )
|
||||
@@ -508,12 +521,13 @@ FS_WriteGameInfo
|
||||
assume GameInfo is valid
|
||||
================
|
||||
*/
|
||||
static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )
|
||||
static qboolean FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )
|
||||
{
|
||||
file_t *f = FS_Open( filepath, "w", false ); // we in binary-mode
|
||||
int i, write_ambients = false;
|
||||
|
||||
if( !f ) Sys_Error( "%s: can't write %s\n", __func__, filepath ); // may be disk-space is out?
|
||||
if( !f )
|
||||
return false;
|
||||
|
||||
FS_Printf( f, "// generated by " XASH_ENGINE_NAME " " XASH_VERSION "-%s (%s-%s)\n\n\n", Q_buildcommit(), Q_buildos(), Q_buildarch() );
|
||||
|
||||
@@ -637,25 +651,50 @@ static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )
|
||||
FS_Printf( f, "demomap\t\t\"%s\"\n", GameInfo->demomap );
|
||||
|
||||
FS_Close( f ); // all done
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir )
|
||||
static void FS_MakeGameInfo( void )
|
||||
{
|
||||
if( FS_WriteGameInfo( "gameinfo.txt", FI.GameInfo ))
|
||||
Con_Printf( "Successfully generated %s/gameinfo.txt\n", FI.GameInfo->gamefolder );
|
||||
else
|
||||
Con_Printf( S_ERROR "Can't open %s/gameinfo.txt for write\n", FI.GameInfo->gamefolder );
|
||||
}
|
||||
|
||||
static void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir, qboolean quake, time_t mtime )
|
||||
{
|
||||
memset( GameInfo, 0, sizeof( *GameInfo ));
|
||||
|
||||
// filesystem info
|
||||
Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title ));
|
||||
GameInfo->mtime = mtime;
|
||||
Q_strncpy( GameInfo->gamefolder, gamedir, sizeof( GameInfo->gamefolder ));
|
||||
Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir ));
|
||||
Q_strncpy( GameInfo->sp_entity, "info_player_start", sizeof( GameInfo->sp_entity ));
|
||||
Q_strncpy( GameInfo->mp_entity, "info_player_deathmatch", sizeof( GameInfo->mp_entity ));
|
||||
Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap ));
|
||||
Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path ));
|
||||
Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll ));
|
||||
Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux ));
|
||||
Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx ));
|
||||
Q_strncpy( GameInfo->iconpath, "game.ico", sizeof( GameInfo->iconpath ));
|
||||
|
||||
if( quake )
|
||||
{
|
||||
Q_strncpy( GameInfo->basedir, "id1", sizeof( GameInfo->basedir ));
|
||||
Q_strncpy( GameInfo->title, gamedir, sizeof( GameInfo->title ));
|
||||
Q_strncpy( GameInfo->startmap, "start", sizeof( GameInfo->startmap ));
|
||||
Q_strncpy( GameInfo->dll_path, "bin", sizeof( GameInfo->dll_path ));
|
||||
Q_strncpy( GameInfo->game_dll, "bin/progs.dll", sizeof( GameInfo->game_dll ));
|
||||
Q_strncpy( GameInfo->game_dll_linux, "bin/progs.so", sizeof( GameInfo->game_dll_linux ));
|
||||
Q_strncpy( GameInfo->game_dll_osx, "bin/progs.dylib", sizeof( GameInfo->game_dll_osx ));
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir ));
|
||||
Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title ));
|
||||
Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap ));
|
||||
Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path ));
|
||||
Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll ));
|
||||
Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux ));
|
||||
Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx ));
|
||||
}
|
||||
|
||||
GameInfo->max_edicts = DEFAULT_MAX_EDICTS; // default value if not specified
|
||||
GameInfo->max_tents = 500;
|
||||
GameInfo->max_beams = 128;
|
||||
@@ -940,36 +979,19 @@ static void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, cons
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
FS_CreateDefaultGameInfo
|
||||
================
|
||||
*/
|
||||
static void FS_CreateDefaultGameInfo( const char *filename )
|
||||
{
|
||||
gameinfo_t defGI;
|
||||
|
||||
FS_InitGameInfo( &defGI, fs_basedir );
|
||||
|
||||
// make simple gameinfo.txt
|
||||
FS_WriteGameInfo( filename, &defGI );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
FS_ParseLiblistGam
|
||||
================
|
||||
*/
|
||||
static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo )
|
||||
static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime )
|
||||
{
|
||||
char *afile;
|
||||
char *afile = FS_LoadDirectFile( filename, NULL );
|
||||
|
||||
if( !GameInfo ) return false;
|
||||
afile = (char *)FS_LoadDirectFile( filename, NULL );
|
||||
if( !afile ) return false;
|
||||
|
||||
FS_InitGameInfo( GameInfo, gamedir );
|
||||
if( !afile )
|
||||
return false;
|
||||
|
||||
FS_InitGameInfo( GameInfo, gamedir, false, mtime );
|
||||
FS_ParseGenericGameInfo( GameInfo, afile, false );
|
||||
|
||||
Mem_Free( afile );
|
||||
@@ -982,18 +1004,15 @@ static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, g
|
||||
FS_ConvertGameInfo
|
||||
================
|
||||
*/
|
||||
static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path )
|
||||
static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path, gameinfo_t *gi, time_t liblist_mtime )
|
||||
{
|
||||
gameinfo_t GameInfo;
|
||||
memset( gi, 0, sizeof( *gi ));
|
||||
|
||||
memset( &GameInfo, 0, sizeof( GameInfo ));
|
||||
|
||||
if( FS_ParseLiblistGam( liblist_path, gamedir, &GameInfo ))
|
||||
// liblist.gam to gameinfo.txt conversion is deprecated, only support for RwDir!
|
||||
if( FS_ParseLiblistGam( liblist_path, gamedir, gi, liblist_mtime ))
|
||||
{
|
||||
Con_DPrintf( "Convert %s to %s\n", liblist_path, gameinfo_path );
|
||||
FS_WriteGameInfo( gameinfo_path, &GameInfo );
|
||||
|
||||
return true;
|
||||
return FS_WriteGameInfo( gameinfo_path, gi );
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1004,16 +1023,14 @@ static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_pa
|
||||
FS_ReadGameInfo
|
||||
================
|
||||
*/
|
||||
static qboolean FS_ReadGameInfo( const char *filepath, const char *gamedir, gameinfo_t *GameInfo )
|
||||
static qboolean FS_ReadGameInfo( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime )
|
||||
{
|
||||
char *afile;
|
||||
char *afile = FS_LoadDirectFile( filename, NULL );
|
||||
|
||||
afile = (char *)FS_LoadFile( filepath, NULL, false );
|
||||
if( !afile )
|
||||
return false;
|
||||
|
||||
FS_InitGameInfo( GameInfo, gamedir );
|
||||
|
||||
FS_InitGameInfo( GameInfo, gamedir, false, mtime );
|
||||
FS_ParseGenericGameInfo( GameInfo, afile, true );
|
||||
|
||||
Mem_Free( afile );
|
||||
@@ -1029,20 +1046,32 @@ Checks if game directory resembles Quake Engine game directory
|
||||
(some of checks may as well work with Xash gamedirs, it's not a bug)
|
||||
================
|
||||
*/
|
||||
static qboolean FS_CheckForQuakeGameDir( const char *gamedir, qboolean direct )
|
||||
static qboolean FS_CheckForQuakeGameDir( const char *gamedir )
|
||||
{
|
||||
// if directory contain config.cfg or progs.dat it's 100% gamedir
|
||||
// quake mods probably always archived but can missed config.cfg before first running
|
||||
const char *files[] = { "config.cfg", "progs.dat", "pak0.pak" };
|
||||
// if directory contain quake.rc or progs.dat it's 100% quake gamedir
|
||||
// quake mods probably always archived, so check pak0.pak too
|
||||
const char *files[] = { "progs.dat", "quake.rc" };
|
||||
char buf[MAX_SYSPATH];
|
||||
int i;
|
||||
|
||||
// try to read pak0.pak first, most quake mods are archived
|
||||
if( Q_snprintf( buf, sizeof( buf ), "%s/pak0.pak", gamedir ) > 0 )
|
||||
{
|
||||
if( FS_SysFileExists( buf ))
|
||||
{
|
||||
if( FS_CheckForQuakePak( buf, files, sizeof( files ) / sizeof( files[0] )))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// search it in the filesystem
|
||||
for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ )
|
||||
{
|
||||
char buf[MAX_VA_STRING];
|
||||
|
||||
Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] );
|
||||
if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false ))
|
||||
return true;
|
||||
if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 )
|
||||
{
|
||||
if( FS_SysFileExists( buf ))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1055,7 +1084,7 @@ FS_CheckForXashGameDir
|
||||
Checks if game directory resembles Xash3D game directory
|
||||
===============
|
||||
*/
|
||||
static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct )
|
||||
static qboolean FS_CheckForXashGameDir( const char *gamedir )
|
||||
{
|
||||
// if directory contain gameinfo.txt or liblist.gam it's 100% gamedir
|
||||
const char *files[] = { "gameinfo.txt", "liblist.gam" };
|
||||
@@ -1063,15 +1092,16 @@ static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct )
|
||||
|
||||
for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ )
|
||||
{
|
||||
char buf[MAX_SYSPATH];
|
||||
char buf[MAX_SYSPATH];
|
||||
|
||||
Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] );
|
||||
if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false ))
|
||||
return true;
|
||||
if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 )
|
||||
{
|
||||
if( FS_SysFileExists( buf ))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1079,86 +1109,74 @@ static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct )
|
||||
FS_ParseGameInfo
|
||||
================
|
||||
*/
|
||||
static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo )
|
||||
static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo, qboolean rodir )
|
||||
{
|
||||
string liblist_path, gameinfo_path;
|
||||
string default_gameinfo_path;
|
||||
qboolean haveUpdate = false;
|
||||
char liblist_path[MAX_SYSPATH];
|
||||
char gameinfo_path[MAX_SYSPATH];
|
||||
char gamedir_path[MAX_SYSPATH];
|
||||
time_t liblist_mtime = -1;
|
||||
time_t gameinfo_mtime = -1;
|
||||
|
||||
Q_snprintf( default_gameinfo_path, sizeof( default_gameinfo_path ), "%s/gameinfo.txt", fs_basedir );
|
||||
Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir );
|
||||
Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir );
|
||||
if( rodir )
|
||||
Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s/%s", fs_rodir, gamedir );
|
||||
else
|
||||
Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s/%s", fs_rootdir, gamedir );
|
||||
|
||||
// here goes some RoDir magic...
|
||||
if( COM_CheckStringEmpty( fs_rodir ))
|
||||
if( !FS_CheckForXashGameDir( gamedir_path ))
|
||||
{
|
||||
string gameinfo_ro, liblist_ro;
|
||||
fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime;
|
||||
|
||||
FS_AllowDirectPaths( true );
|
||||
|
||||
Q_snprintf( gameinfo_ro, sizeof( gameinfo_ro ), "%s/%s/gameinfo.txt", fs_rodir, gamedir );
|
||||
Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", fs_rodir, gamedir );
|
||||
|
||||
roLibListTime = FS_SysFileTime( liblist_ro );
|
||||
roGameInfoTime = FS_SysFileTime( gameinfo_ro );
|
||||
rwGameInfoTime = FS_SysFileTime( gameinfo_path );
|
||||
|
||||
// if rodir's liblist.gam newer than rwdir's gameinfo.txt, then convert it
|
||||
if( roLibListTime > rwGameInfoTime )
|
||||
// check if we need to generate gameinfo for Quake
|
||||
if( FS_CheckForQuakeGameDir( gamedir_path ))
|
||||
{
|
||||
haveUpdate = FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_ro );
|
||||
}
|
||||
// if rodir's gameinfo.txt newer than rwdir's gameinfo.txt, just copy the file
|
||||
else if( roGameInfoTime > rwGameInfoTime )
|
||||
{
|
||||
file_t *ro, *rw;
|
||||
fs_offset_t ro_size;
|
||||
|
||||
// read & write as binary to copy the exact file
|
||||
ro = FS_SysOpen( gameinfo_ro, "rb" );
|
||||
rw = FS_SysOpen( gameinfo_path, "wb" );
|
||||
|
||||
FS_Seek( ro, 0, SEEK_END );
|
||||
ro_size = FS_Tell( ro );
|
||||
FS_Seek( ro, 0, SEEK_SET );
|
||||
|
||||
FS_FileCopy( rw, ro, ro_size );
|
||||
|
||||
FS_Close( rw );
|
||||
FS_Close( ro );
|
||||
|
||||
haveUpdate = true;
|
||||
// just generate stub gameinfo in memory
|
||||
FS_InitGameInfo( GameInfo, gamedir, true, -1 );
|
||||
GameInfo->rodir = rodir;
|
||||
return true;
|
||||
}
|
||||
|
||||
FS_AllowDirectPaths( false );
|
||||
// don't add empty or addon directories
|
||||
return false;
|
||||
}
|
||||
|
||||
// do not update gameinfo.txt, if it was just copied from rodir's
|
||||
// if user change liblist.gam update the gameinfo.txt
|
||||
if( !haveUpdate && FS_FileTime( liblist_path, false ) > FS_FileTime( gameinfo_path, false ))
|
||||
FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path );
|
||||
Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir_path );
|
||||
Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir_path );
|
||||
|
||||
// force to create gameinfo for specified game if missing
|
||||
if(( FS_CheckForQuakeGameDir( gamedir, false ) || !Q_stricmp( fs_gamedir, gamedir )) && !FS_FileExists( gameinfo_path, false ))
|
||||
liblist_mtime = FS_SysFileTime( liblist_path );
|
||||
gameinfo_mtime = FS_SysFileTime( gameinfo_path );
|
||||
|
||||
// in this function we never write new files for RoDir compatibility
|
||||
// since RoDir is only FWGS feature, do gameinfo.txt conversion only
|
||||
// for RwDir for those who worked with original Xash3D
|
||||
if( !rodir )
|
||||
{
|
||||
gameinfo_t tmpGameInfo;
|
||||
memset( &tmpGameInfo, 0, sizeof( tmpGameInfo ));
|
||||
|
||||
if( FS_ReadGameInfo( default_gameinfo_path, gamedir, &tmpGameInfo ))
|
||||
// !!!only if we have both liblist.gam and gameinfo.txt try to convert liblist.gam to gameinfo.txt if it's newer!!!
|
||||
if( liblist_mtime >= 0 && gameinfo_mtime >= 0 && liblist_mtime > gameinfo_mtime )
|
||||
{
|
||||
// now we have copy of game info from basedir but needs to change gamedir
|
||||
Con_DPrintf( "Convert %s to %s\n", default_gameinfo_path, gameinfo_path );
|
||||
Q_strncpy( tmpGameInfo.gamefolder, gamedir, sizeof( tmpGameInfo.gamefolder ));
|
||||
FS_WriteGameInfo( gameinfo_path, &tmpGameInfo );
|
||||
if( FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path, GameInfo, liblist_mtime ))
|
||||
return true;
|
||||
}
|
||||
else FS_CreateDefaultGameInfo( gameinfo_path );
|
||||
}
|
||||
|
||||
if( !GameInfo || !FS_FileExists( gameinfo_path, false ))
|
||||
return false; // no dest
|
||||
// can we parse gameinfo.txt?
|
||||
if( gameinfo_mtime >= 0 )
|
||||
{
|
||||
if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo, gameinfo_mtime ))
|
||||
{
|
||||
GameInfo->rodir = rodir;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo );
|
||||
// can we parse liblist.gam?
|
||||
if( liblist_mtime >= 0 )
|
||||
{
|
||||
if( FS_ParseLiblistGam( liblist_path, gamedir, GameInfo, liblist_mtime ))
|
||||
{
|
||||
GameInfo->rodir = rodir;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1197,7 +1215,7 @@ void FS_AddGameHierarchy( const char *dir, uint flags )
|
||||
}
|
||||
}
|
||||
|
||||
if( COM_CheckStringEmpty( fs_rodir ) )
|
||||
if( COM_CheckStringEmpty( fs_rodir ))
|
||||
{
|
||||
// append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH
|
||||
uint new_flags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH));
|
||||
@@ -1266,10 +1284,12 @@ void FS_LoadGameInfo( const char *rootfolder )
|
||||
// lock uplevel of gamedir for read\write
|
||||
FS_AllowDirectPaths( false );
|
||||
|
||||
if( rootfolder ) Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir ));
|
||||
if( rootfolder )
|
||||
Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir ));
|
||||
|
||||
Con_Reportf( "%s( %s )\n", __func__, fs_gamedir );
|
||||
|
||||
// clear any old pathes
|
||||
// clear any old paths
|
||||
FS_ClearSearchPath();
|
||||
|
||||
// validate gamedir
|
||||
@@ -1284,6 +1304,14 @@ void FS_LoadGameInfo( const char *rootfolder )
|
||||
|
||||
FI.GameInfo = FI.games[i];
|
||||
|
||||
if( FI.GameInfo->rodir )
|
||||
{
|
||||
// ensure we have directory in rwdir
|
||||
char buf[MAX_SYSPATH + MAX_QPATH + 2]; // and have plenty of space
|
||||
Q_snprintf( buf, sizeof( buf ), "%s/%s/", fs_rootdir, FI.GameInfo->gamefolder );
|
||||
FS_CreatePath( buf );
|
||||
}
|
||||
|
||||
FS_Rescan(); // create new filesystem
|
||||
}
|
||||
|
||||
@@ -1344,7 +1372,7 @@ static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dll
|
||||
{
|
||||
string fixedname;
|
||||
searchpath_t *search;
|
||||
int index, start = 0, i, len;
|
||||
int index, start = 0, len;
|
||||
|
||||
// check for bad exports
|
||||
if( !COM_CheckString( dllname ))
|
||||
@@ -1479,6 +1507,33 @@ static void *Sys_GetNativeObjectStub( const char *object )
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void FS_ValidateDirectories( const char *path, qboolean *has_base_dir, qboolean *has_game_dir )
|
||||
{
|
||||
stringlist_t dirs;
|
||||
int i;
|
||||
|
||||
stringlistinit( &dirs );
|
||||
listdirectory( &dirs, path, true );
|
||||
stringlistsort( &dirs );
|
||||
|
||||
for( i = 0; i < dirs.numstrings; i++ )
|
||||
{
|
||||
if( !FS_SysFolderExists( dirs.strings[i] ))
|
||||
continue;
|
||||
|
||||
if( !Q_stricmp( fs_basedir, dirs.strings[i] ))
|
||||
*has_base_dir = true;
|
||||
|
||||
if( !Q_stricmp( fs_gamedir, dirs.strings[i] ))
|
||||
*has_game_dir = true;
|
||||
|
||||
if( *has_base_dir && *has_game_dir )
|
||||
break;
|
||||
}
|
||||
|
||||
stringlistfreecontents( &dirs );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
FS_Init
|
||||
@@ -1486,11 +1541,9 @@ FS_Init
|
||||
*/
|
||||
qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir )
|
||||
{
|
||||
stringlist_t dirs;
|
||||
qboolean hasBaseDir = false;
|
||||
qboolean hasGameDir = false;
|
||||
int i;
|
||||
char buf[MAX_VA_STRING];
|
||||
stringlist_t dirs;
|
||||
int i, rodir_num_games;
|
||||
char buf[MAX_SYSPATH];
|
||||
|
||||
FS_InitMemory();
|
||||
|
||||
@@ -1503,63 +1556,40 @@ qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const c
|
||||
Q_strncpy( fs_basedir, basedir, sizeof( fs_basedir ));
|
||||
Q_strncpy( fs_rodir, rodir, sizeof( fs_rodir ));
|
||||
|
||||
// add readonly directories first
|
||||
if( COM_CheckStringEmpty( fs_rodir ))
|
||||
// validate user input
|
||||
if( COM_CheckStringEmpty( fs_rodir ) && !Q_stricmp( fs_rodir, fs_rootdir ))
|
||||
{
|
||||
if( !Q_stricmp( fs_rodir, fs_rootdir ))
|
||||
Sys_Error( "RoDir and default rootdir can't point to same directory!" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// look for game directories in RwDir first
|
||||
// this whole check only required to fallback to base directory
|
||||
// (i.e. default game directory hardcoded in the executable file)
|
||||
{
|
||||
qboolean has_base_dir = false;
|
||||
qboolean has_game_dir = false;
|
||||
|
||||
FS_ValidateDirectories( "./", &has_base_dir, &has_game_dir );
|
||||
|
||||
if( !has_game_dir )
|
||||
{
|
||||
Sys_Error( "RoDir and default rootdir can't point to same directory!" );
|
||||
return false;
|
||||
}
|
||||
// look for game directories in RoDir now
|
||||
if( COM_CheckStringEmpty( fs_rodir ))
|
||||
FS_ValidateDirectories( fs_rodir, &has_base_dir, &has_game_dir );
|
||||
|
||||
stringlistinit( &dirs );
|
||||
listdirectory( &dirs, fs_rodir );
|
||||
stringlistsort( &dirs );
|
||||
|
||||
for( i = 0; i < dirs.numstrings; i++ )
|
||||
{
|
||||
char roPath[MAX_SYSPATH];
|
||||
|
||||
Q_snprintf( roPath, sizeof( roPath ), "%s/%s/", fs_rodir, dirs.strings[i] );
|
||||
|
||||
// check if it's a directory
|
||||
if( !FS_SysFolderExists( roPath ))
|
||||
continue;
|
||||
|
||||
// check if it's gamedir
|
||||
if( FS_CheckForXashGameDir( roPath, true ) || FS_CheckForQuakeGameDir( roPath, true ))
|
||||
if( !has_game_dir )
|
||||
{
|
||||
char rwPath[MAX_SYSPATH];
|
||||
Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir );
|
||||
|
||||
Q_snprintf( rwPath, sizeof( rwPath ), "%s/%s/", fs_rootdir, dirs.strings[i] );
|
||||
FS_CreatePath( rwPath );
|
||||
// revert to base game directory
|
||||
if( has_base_dir )
|
||||
Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir ));
|
||||
}
|
||||
}
|
||||
|
||||
stringlistfreecontents( &dirs );
|
||||
}
|
||||
|
||||
// validate directories
|
||||
stringlistinit( &dirs );
|
||||
listdirectory( &dirs, "./" );
|
||||
stringlistsort( &dirs );
|
||||
|
||||
for( i = 0; i < dirs.numstrings; i++ )
|
||||
{
|
||||
if( !Q_stricmp( fs_basedir, dirs.strings[i] ))
|
||||
hasBaseDir = true;
|
||||
|
||||
if( !Q_stricmp( fs_gamedir, dirs.strings[i] ))
|
||||
hasGameDir = true;
|
||||
}
|
||||
|
||||
if( !hasGameDir )
|
||||
{
|
||||
Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir );
|
||||
if( hasBaseDir ) Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir ));
|
||||
}
|
||||
|
||||
// build list of game directories here
|
||||
// now start building first level of directory hierarchy
|
||||
if( COM_CheckStringEmpty( fs_rodir ))
|
||||
{
|
||||
Q_snprintf( buf, sizeof( buf ), "%s/", fs_rodir );
|
||||
@@ -1567,15 +1597,62 @@ qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const c
|
||||
}
|
||||
FS_AddGameDirectory( "./", FS_STATIC_PATH );
|
||||
|
||||
// but scan rodir for games first
|
||||
if( COM_CheckStringEmpty( fs_rodir ))
|
||||
{
|
||||
stringlistinit( &dirs );
|
||||
listdirectory( &dirs, fs_rodir, true );
|
||||
stringlistsort( &dirs );
|
||||
|
||||
for( i = 0; i < dirs.numstrings; i++ )
|
||||
{
|
||||
if( !FS_SysFolderExists( dirs.strings[i] ))
|
||||
continue;
|
||||
|
||||
if( FI.games[FI.numgames] == NULL )
|
||||
FI.games[FI.numgames] = Mem_Malloc( fs_mempool, sizeof( *FI.games[FI.numgames] ));
|
||||
|
||||
if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], true ))
|
||||
FI.numgames++;
|
||||
}
|
||||
|
||||
stringlistfreecontents( &dirs );
|
||||
}
|
||||
|
||||
rodir_num_games = FI.numgames;
|
||||
|
||||
stringlistinit( &dirs );
|
||||
listdirectory( &dirs, "./", true );
|
||||
stringlistsort( &dirs );
|
||||
|
||||
for( i = 0; i < dirs.numstrings; i++ )
|
||||
{
|
||||
int j;
|
||||
|
||||
if( !FS_SysFolderExists( dirs.strings[i] ))
|
||||
continue;
|
||||
|
||||
if( FI.games[FI.numgames] == NULL )
|
||||
FI.games[FI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t ));
|
||||
// update gameinfo from rwdir, if it's newer
|
||||
// with rodir we should never create new gameinfos anymore,
|
||||
// so this only catches possible user changes
|
||||
for( j = 0; j < rodir_num_games; j++ )
|
||||
{
|
||||
if( !Q_stricmp( dirs.strings[i], FI.games[j]->gamefolder ))
|
||||
{
|
||||
gameinfo_t gi;
|
||||
if( FS_ParseGameInfo( dirs.strings[i], &gi, false ))
|
||||
{
|
||||
if( gi.mtime > FI.games[j]->mtime )
|
||||
*FI.games[j] = gi;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames] ))
|
||||
if( FI.games[FI.numgames] == NULL )
|
||||
FI.games[FI.numgames] = Mem_Calloc( fs_mempool, sizeof( *FI.games[FI.numgames] ));
|
||||
|
||||
if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], false ))
|
||||
FI.numgames++; // added
|
||||
}
|
||||
|
||||
@@ -3187,6 +3264,8 @@ const fs_api_t g_api =
|
||||
FS_LoadFileFromArchive,
|
||||
|
||||
FS_GetRootDirectory,
|
||||
|
||||
FS_MakeGameInfo,
|
||||
};
|
||||
|
||||
int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs );
|
||||
|
||||
@@ -123,6 +123,9 @@ typedef struct gameinfo_s
|
||||
qboolean animated_title;
|
||||
|
||||
char demomap[MAX_QPATH];
|
||||
|
||||
qboolean rodir; // if true, parsed from rodir
|
||||
int64_t mtime;
|
||||
} gameinfo_t;
|
||||
|
||||
typedef struct fs_dllinfo_t
|
||||
@@ -226,6 +229,8 @@ typedef struct fs_api_t
|
||||
|
||||
// gets current root directory, set by InitStdio
|
||||
qboolean (*GetRootDirectory)( char *path, size_t size );
|
||||
|
||||
void (*MakeGameInfo)( void );
|
||||
} fs_api_t;
|
||||
|
||||
typedef struct fs_interface_t
|
||||
|
||||
@@ -205,7 +205,7 @@ void stringlistinit( stringlist_t *list );
|
||||
void stringlistfreecontents( stringlist_t *list );
|
||||
void stringlistappend( stringlist_t *list, const char *text );
|
||||
void stringlistsort( stringlist_t *list );
|
||||
void listdirectory( stringlist_t *list, const char *path );
|
||||
void listdirectory( stringlist_t *list, const char *path, qboolean dirs_only );
|
||||
|
||||
// filesystem ops
|
||||
int FS_FileExists( const char *filename, int gamedironly );
|
||||
@@ -230,6 +230,7 @@ qboolean FS_FullPathToRelativePath( char *dst, const char *src, size_t size );
|
||||
//
|
||||
// pak.c
|
||||
//
|
||||
qboolean FS_CheckForQuakePak( const char *pakfile, const char *files[], size_t num_files );
|
||||
searchpath_t *FS_AddPak_Fullpath( const char *pakfile, int flags );
|
||||
|
||||
//
|
||||
|
||||
@@ -105,6 +105,8 @@ static pack_t *FS_LoadPackPAK( const char *packfile, int *error )
|
||||
|
||||
// TODO: use FS_Open to allow PK3 to be included into other archives
|
||||
// Currently, it doesn't work with rodir due to FS_FindFile logic
|
||||
// when it will use FS_Open, check that FS_CheckForQuakePak correctly
|
||||
// detects Quake gamedirs in RoDir
|
||||
packhandle = FS_SysOpen( packfile, "rb" );
|
||||
|
||||
if( packhandle == NULL )
|
||||
@@ -364,3 +366,45 @@ searchpath_t *FS_AddPak_Fullpath( const char *pakfile, int flags )
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
FS_CheckForQuakePak
|
||||
|
||||
To generate fake gameinfo for Quake directory, we need to parse pak0.pak
|
||||
and find progs.dat in it
|
||||
================
|
||||
*/
|
||||
qboolean FS_CheckForQuakePak( const char *pakfile, const char *files[], size_t num_files )
|
||||
{
|
||||
qboolean is_quake = false;
|
||||
pack_t *pak;
|
||||
int i;
|
||||
|
||||
pak = FS_LoadPackPAK( pakfile, NULL );
|
||||
if( !pak )
|
||||
return false;
|
||||
|
||||
for( i = 0; i < num_files; i++ )
|
||||
{
|
||||
int j;
|
||||
|
||||
for( j = 0; j < pak->numfiles; j++ )
|
||||
{
|
||||
if( Q_strchr( pak->files[j].name, '/' ))
|
||||
continue; // exclude subdirectories
|
||||
|
||||
if( !Q_stricmp( pak->files[j].name, files[i] ))
|
||||
{
|
||||
is_quake = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( is_quake )
|
||||
break;
|
||||
}
|
||||
|
||||
Mem_Free( pak );
|
||||
return is_quake;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ MEMFD_CREATE_TEST = '''#define _GNU_SOURCE
|
||||
#include <sys/mman.h>
|
||||
int main(int argc, char **argv) { return memfd_create(argv[0], 0); }'''
|
||||
|
||||
DIRENT_D_TYPE_TEST = '''#define _GNU_SOURCE
|
||||
#include <dirent.h>
|
||||
int main(int argc, char **argv) { struct dirent entry; entry.d_type = DT_DIR; return 0; }
|
||||
'''
|
||||
|
||||
def options(opt):
|
||||
pass
|
||||
|
||||
@@ -18,6 +23,9 @@ def configure(conf):
|
||||
if conf.check_cc(fragment=MEMFD_CREATE_TEST, msg='Checking for memfd_create', mandatory=False):
|
||||
conf.define('HAVE_MEMFD_CREATE', 1)
|
||||
|
||||
if conf.check_cc(fragment=DIRENT_D_TYPE_TEST, msg='Checking for d_type field in struct dirent', mandatory=False):
|
||||
conf.define('HAVE_DIRENT_D_TYPE', 1)
|
||||
|
||||
def build(bld):
|
||||
bld(name = 'filesystem_includes', export_includes = '.')
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ _inline int Sys_Start( void )
|
||||
if( Xash_Shutdown )
|
||||
changeGame = Sys_ChangeGame;
|
||||
|
||||
ret = Xash_Main( szArgc, szArgv, szGameDir, 0, changeGame );
|
||||
ret = Xash_Main( szArgc, szArgv, szGameDir, 0, XASH_DISABLE_MENU_CHANGEGAME ? NULL : changeGame );
|
||||
|
||||
Sys_UnloadEngine();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ def configure(conf):
|
||||
if conf.env.DEST_OS == 'win32':
|
||||
conf.load('winres')
|
||||
|
||||
conf.define_cond('XASH_DISABLE_MENU_CHANGEGAME', conf.options.DISABLE_MENU_CHANGEGAME)
|
||||
conf.define('XASH_DISABLE_MENU_CHANGEGAME', conf.options.DISABLE_MENU_CHANGEGAME)
|
||||
|
||||
def build(bld):
|
||||
source = ['game.cpp']
|
||||
|
||||
@@ -663,21 +663,6 @@ void COM_RemoveLineFeed( char *str, size_t bufsize )
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
COM_FixSlashes
|
||||
|
||||
Changes all '\' characters into '/' characters, in place.
|
||||
============
|
||||
*/
|
||||
void COM_FixSlashes( char *pname )
|
||||
{
|
||||
while(( pname = Q_strchr( pname, '\\' )))
|
||||
{
|
||||
*pname = '/';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
COM_PathSlashFix
|
||||
|
||||
@@ -96,7 +96,6 @@ void COM_ExtractFilePath( const char *path, char *dest );
|
||||
const char *COM_FileWithoutPath( const char *in );
|
||||
void COM_StripExtension( char *path );
|
||||
void COM_RemoveLineFeed( char *str, size_t bufsize );
|
||||
void COM_FixSlashes( char *pname );
|
||||
void COM_PathSlashFix( char *path );
|
||||
// return 0 on empty or null string, 1 otherwise
|
||||
#define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 )
|
||||
@@ -320,6 +319,19 @@ static inline int Q_splitstr( char *str, int delim, void *userdata,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
COM_FixSlashes
|
||||
|
||||
Changes all '\' characters into '/' characters, in place.
|
||||
============
|
||||
*/
|
||||
static inline void COM_FixSlashes( char *pname )
|
||||
{
|
||||
while(( pname = Q_strchr( pname, '\\' )))
|
||||
*pname = '/';
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -502,11 +502,11 @@ void R_ClearSkyBox( void );
|
||||
void R_DrawSkyBox( void );
|
||||
void R_DrawClouds( void );
|
||||
void R_UnloadSkybox( void );
|
||||
void EmitWaterPolys( msurface_t *warp, qboolean reverse );
|
||||
void EmitWaterPolys( msurface_t *warp, qboolean reverse, qboolean ripples );
|
||||
void R_InitRipples( void );
|
||||
void R_ResetRipples( void );
|
||||
void R_AnimateRipples( void );
|
||||
void R_UploadRipples( texture_t *image );
|
||||
qboolean R_UploadRipples( const texture_t *image );
|
||||
|
||||
//#include "vid_common.h"
|
||||
|
||||
|
||||
@@ -1317,10 +1317,8 @@ static void R_RenderBrushPoly( msurface_t *fa, int cull_type )
|
||||
|
||||
if( FBitSet( fa->flags, SURF_DRAWTURB ))
|
||||
{
|
||||
R_UploadRipples( t );
|
||||
|
||||
// warp texture, no lightmaps
|
||||
EmitWaterPolys( fa, (cull_type == CULL_BACKSIDE));
|
||||
EmitWaterPolys( fa, cull_type == CULL_BACKSIDE, R_UploadRipples( t ));
|
||||
return;
|
||||
}
|
||||
else GL_Bind( XASH_TEXTURE0, t->gl_texturenum );
|
||||
@@ -1500,11 +1498,8 @@ void R_DrawWaterSurfaces( void )
|
||||
if( !FBitSet( s->flags, SURF_DRAWTURB ))
|
||||
continue;
|
||||
|
||||
// set modulate mode explicitly
|
||||
R_UploadRipples( t );
|
||||
|
||||
for( ; s; s = s->texturechain )
|
||||
EmitWaterPolys( s, false );
|
||||
EmitWaterPolys( s, false, R_UploadRipples( t ));
|
||||
|
||||
t->texturechain = NULL;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@ static struct
|
||||
uint32_t texture[RIPPLES_TEXSIZE];
|
||||
int gl_texturenum;
|
||||
int rippletexturenum;
|
||||
float texturescale; // not all textures are 128x128, scale the texcoords down
|
||||
} g_ripple;
|
||||
|
||||
|
||||
@@ -565,7 +564,7 @@ EmitWaterPolys
|
||||
Does a water warp on the pre-fragmented glpoly_t chain
|
||||
=============
|
||||
*/
|
||||
void EmitWaterPolys( msurface_t *warp, qboolean reverse )
|
||||
void EmitWaterPolys( msurface_t *warp, qboolean reverse, qboolean ripples )
|
||||
{
|
||||
float *v, nv, waveHeight;
|
||||
float s, t, os, ot;
|
||||
@@ -609,15 +608,15 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse )
|
||||
os = v[3];
|
||||
ot = v[4];
|
||||
|
||||
if( !r_ripple.value )
|
||||
if( !ripples )
|
||||
{
|
||||
s = os + r_turbsin[(int)((ot * 0.125f + gp_cl->time) * TURBSCALE) & 255];
|
||||
t = ot + r_turbsin[(int)((os * 0.125f + gp_cl->time) * TURBSCALE) & 255];
|
||||
}
|
||||
else
|
||||
{
|
||||
s = os / g_ripple.texturescale;
|
||||
t = ot / g_ripple.texturescale;
|
||||
s = os;
|
||||
t = ot;
|
||||
}
|
||||
|
||||
s *= ( 1.0f / SUBDIVIDE_SIZE );
|
||||
@@ -710,17 +709,6 @@ static void R_RunRipplesAnimation( const short *oldbuf, short *pbuf )
|
||||
}
|
||||
}
|
||||
|
||||
static int MostSignificantBit( unsigned int v )
|
||||
{
|
||||
#if __GNUC__
|
||||
return 31 - __builtin_clz( v );
|
||||
#else
|
||||
int i;
|
||||
for( i = 0, v >>= 1; v; v >>= 1, i++ );
|
||||
return i;
|
||||
#endif
|
||||
}
|
||||
|
||||
void R_AnimateRipples( void )
|
||||
{
|
||||
double frametime = gp_cl->time - g_ripple.time;
|
||||
@@ -750,67 +738,72 @@ void R_AnimateRipples( void )
|
||||
R_RunRipplesAnimation( g_ripple.oldbuf, g_ripple.curbuf );
|
||||
}
|
||||
|
||||
void R_UploadRipples( texture_t *image )
|
||||
qboolean R_UploadRipples( const texture_t *image )
|
||||
{
|
||||
gl_texture_t *glt;
|
||||
uint32_t *pixels;
|
||||
int wbits, wmask, wshft;
|
||||
const gl_texture_t *glt;
|
||||
const uint32_t *pixels;
|
||||
int y;
|
||||
int width, height, size;
|
||||
|
||||
// discard unuseful textures
|
||||
if( !r_ripple.value || image->width > RIPPLES_CACHEWIDTH || image->width != image->height )
|
||||
if( !r_ripple.value )
|
||||
{
|
||||
GL_Bind( XASH_TEXTURE0, image->gl_texturenum );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// discard unuseful textures
|
||||
glt = R_GetTexture( image->gl_texturenum );
|
||||
if( !glt || !glt->original || !glt->original->buffer || !FBitSet( glt->flags, TF_EXPAND_SOURCE ))
|
||||
{
|
||||
GL_Bind( XASH_TEXTURE0, image->gl_texturenum );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
GL_Bind( XASH_TEXTURE0, g_ripple.rippletexturenum );
|
||||
|
||||
// no updates this frame
|
||||
if( !g_ripple.update && image->gl_texturenum == g_ripple.gl_texturenum )
|
||||
return;
|
||||
return true;
|
||||
|
||||
g_ripple.gl_texturenum = image->gl_texturenum;
|
||||
if( r_ripple.value == 1.0f )
|
||||
{
|
||||
g_ripple.texturescale = Q_max( 1.0f, image->width / 64.0f );
|
||||
}
|
||||
else
|
||||
{
|
||||
g_ripple.texturescale = 1.0f;
|
||||
}
|
||||
|
||||
size = r_ripple.value == 1.0f ? 64 : RIPPLES_CACHEWIDTH;
|
||||
|
||||
pixels = (uint32_t *)glt->original->buffer;
|
||||
wbits = MostSignificantBit( image->width );
|
||||
wshft = 7 - wbits;
|
||||
wmask = image->width - 1;
|
||||
// try to preserve aspect ratio
|
||||
width = height = RIPPLES_CACHEWIDTH; // always render at maximum size
|
||||
if( image->width > image->height )
|
||||
height = (float)image->height / image->width * width;
|
||||
else if( image->width < image->height )
|
||||
width = (float)image->width / image->height * height;
|
||||
|
||||
for( y = 0; y < image->height; y++ )
|
||||
pixels = (const uint32_t *)glt->original->buffer;
|
||||
|
||||
for( y = 0; y < height; y++ )
|
||||
{
|
||||
int ry = y << ( 7 + wshft );
|
||||
int ry = (float)y / height * size;
|
||||
int x;
|
||||
|
||||
for( x = 0; x < image->width; x++ )
|
||||
for( x = 0; x < width; x++ )
|
||||
{
|
||||
int rx = x << wshft;
|
||||
int val = g_ripple.curbuf[ry + rx] >> 4;
|
||||
int rx = (float)x / width * size;
|
||||
int val = g_ripple.curbuf[ry * RIPPLES_CACHEWIDTH + rx] / 16;
|
||||
|
||||
int py = (y - val) & wmask;
|
||||
int px = (x + val) & wmask;
|
||||
int p = ( py << wbits ) + px;
|
||||
// transform it to texture space and get nice tiling effect
|
||||
int rpy = ( y - val ) % height;
|
||||
int rpx = ( x + val ) % width;
|
||||
|
||||
g_ripple.texture[(y << wbits) + x] = pixels[p];
|
||||
int py = (float)rpy / height * image->height;
|
||||
int px = (float)rpx / width * image->width;
|
||||
|
||||
if( py < 0 ) py = image->height + py;
|
||||
if( px < 0 ) px = image->width + px;
|
||||
|
||||
g_ripple.texture[y * width + x] = pixels[py * image->width + px];
|
||||
}
|
||||
}
|
||||
|
||||
pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->width, 0,
|
||||
pglTexImage2D( GL_TEXTURE_2D, 0, glt->format, width, height, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, g_ripple.texture );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ fi
|
||||
|
||||
if [ "$ARCH" = "i386" ]; then
|
||||
cp SDL2_VC/lib/x86/SDL2.dll . # Install SDL2
|
||||
cp SDL2_VC/lib/x86/SDL2.pdb .
|
||||
elif [ "$ARCH" = "amd64" ]; then
|
||||
cp SDL2_VC/lib/x64/SDL2.dll .
|
||||
cp SDL2_VC/lib/x64/SDL2.pdb .
|
||||
else
|
||||
die
|
||||
fi
|
||||
|
||||
1
wscript
1
wscript
@@ -395,6 +395,7 @@ def configure(conf):
|
||||
conf.env.ENABLE_XAR = conf.options.ENABLE_XAR
|
||||
conf.env.ENABLE_FUZZER = conf.options.ENABLE_FUZZER
|
||||
conf.env.DEDICATED = conf.options.DEDICATED
|
||||
conf.env.SUPPORT_BSP2_FORMAT = conf.options.SUPPORT_BSP2_FORMAT
|
||||
|
||||
conf.define_cond('SUPPORT_BSP2_FORMAT', conf.options.SUPPORT_BSP2_FORMAT)
|
||||
conf.define_cond('SUPPORT_HL25_EXTENDED_STRUCTS', conf.options.SUPPORT_HL25_EXTENDED_STRUCTS)
|
||||
|
||||
Reference in New Issue
Block a user