2014-03-30 00:39:37 +00:00
# include <jni.h>
# include <errno.h>
# include <android/log.h>
# include "quakedef.h"
# include <unistd.h>
# include <fcntl.h>
# include <dlfcn.h>
# include <sys/stat.h>
# include <dirent.h>
2016-07-12 00:40:13 +00:00
# ifndef ANDROID
# error ANDROID wasnt defined
# endif
2014-03-30 00:39:37 +00:00
# ifndef isDedicated
# ifdef SERVERONLY
qboolean isDedicated = true ;
# else
qboolean isDedicated = false ;
# endif
# endif
void * sys_window ; /*public so the renderer can attach to the correct place*/
static int sys_running = false ;
int sys_glesversion ;
2016-07-12 00:40:13 +00:00
extern int r_blockvidrestart ;
2016-02-10 23:23:43 +00:00
float sys_dpi_x , sys_dpi_y ;
2014-03-30 00:39:37 +00:00
int sys_soundflags ; /*1 means active. 2 means reset (so claim that its not active for one frame to force a reset)*/
static void * sys_memheap ;
static unsigned int sys_lastframe ;
static unsigned int vibrateduration ;
static char errormessage [ 256 ] ;
static char sys_basedir [ MAX_OSPATH ] ;
static char sys_basepak [ MAX_OSPATH ] ;
extern jmp_buf host_abort ;
cvar_t sys_vibrate = CVARD ( " sys_vibrate " , " 1 " , " Enables the system vibrator for damage events and such things. The value provided is a duration scaler. " ) ;
cvar_t sys_osk = CVAR ( " sys_osk " , " 0 " ) ; //to be toggled
cvar_t sys_keepscreenon = CVARD ( " sys_keepscreenon " , " 1 " , " If set, the screen will never darken. This might cost some extra battery power, but then so will running a 3d engine. " ) ; //to be toggled
cvar_t sys_orientation = CVARD ( " sys_orientation " , " landscape " , " Specifies what angle to render quake at. \n Valid values are: sensor (autodetect), landscape, portrait, reverselandscape, reverseportrait " ) ;
cvar_t sys_glesversion_cvar = CVARD ( " sys_glesversion " , " 1 " , " Specifies which version of gles to use. 1 or 2 are valid values. " ) ;
extern cvar_t vid_conautoscale ;
# define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, DISTRIBUTION"Droid", __VA_ARGS__))
# define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, DISTRIBUTION"Droid", __VA_ARGS__))
# define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, DISTRIBUTION"Droid", __VA_ARGS__))
void Sys_Vibrate ( float count )
{
if ( count < 0 )
count = 0 ;
vibrateduration + = count * 10 * sys_vibrate . value ;
}
2015-03-03 00:14:43 +00:00
void Sys_Vibrate_f ( void )
{
//input is in seconds, because this is quake. output is in ms.
vibrateduration + = atof ( Cmd_Argv ( 1 ) ) * 1000 ;
}
2014-03-30 00:39:37 +00:00
JNIEXPORT jint JNICALL Java_com_fteqw_FTEDroidEngine_getvibrateduration ( JNIEnv * env , jobject obj )
{
unsigned int dur = vibrateduration ;
vibrateduration = 0 ;
return dur ;
}
JNIEXPORT jstring JNICALL Java_com_fteqw_FTEDroidEngine_geterrormessage ( JNIEnv * env , jobject obj )
{
return ( * env ) - > NewStringUTF ( env , errormessage ) ;
}
JNIEXPORT jstring JNICALL Java_com_fteqw_FTEDroidEngine_getpreferedorientation ( JNIEnv * env , jobject obj )
{
sys_orientation . modified = false ;
sys_glesversion_cvar . modified = false ;
return ( * env ) - > NewStringUTF ( env , sys_orientation . string ) ;
}
JNIEXPORT jint JNICALL Java_com_fteqw_FTEDroidEngine_getpreferedglesversion ( JNIEnv * env , jobject obj )
{
return sys_glesversion_cvar . ival ;
}
/*the java passes in all input directly via a 'UI' thread. we don't need to poll it at all*/
2012-10-14 09:00:49 +00:00
void INS_Move ( float * movements , int pnum )
{
2014-03-30 00:39:37 +00:00
}
2012-10-14 09:00:49 +00:00
void INS_Commands ( void )
{
2014-03-30 00:39:37 +00:00
}
2016-07-21 19:27:59 +00:00
void INS_EnumerateDevices ( void * ctx , void ( * callback ) ( void * ctx , const char * type , const char * devicename , unsigned int * qdevid ) )
2015-04-14 23:12:17 +00:00
{
}
2012-10-14 09:00:49 +00:00
void INS_Init ( void )
{
2014-03-30 00:39:37 +00:00
}
2012-10-14 09:00:49 +00:00
void INS_ReInit ( void )
{
2014-03-30 00:39:37 +00:00
}
2012-10-14 09:00:49 +00:00
void INS_Shutdown ( void )
{
2014-03-30 00:39:37 +00:00
}
2015-06-15 20:11:27 +00:00
JNIEXPORT int JNICALL Java_com_fteqw_FTEDroidEngine_keypress ( JNIEnv * env , jobject obj ,
2014-03-30 00:39:37 +00:00
jint down , jint keycode , jint unicode )
{
IN_KeyEvent ( 0 , down , keycode , unicode ) ;
2015-06-15 20:11:27 +00:00
return true ;
2014-03-30 00:39:37 +00:00
}
JNIEXPORT void JNICALL Java_com_fteqw_FTEDroidEngine_motion ( JNIEnv * env , jobject obj ,
jint act , jint ptrid , jfloat x , jfloat y , jfloat size )
{
if ( act )
IN_KeyEvent ( ptrid , act = = 1 , K_MOUSE1 , 0 ) ;
else
IN_MouseMove ( ptrid , true , x , y , 0 , size ) ;
}
JNIEXPORT jint JNICALL Java_com_fteqw_FTEDroidEngine_frame ( JNIEnv * env , jobject obj ,
2016-02-10 23:23:43 +00:00
jfloat ax , jfloat ay , jfloat az ,
jfloat gx , jfloat gy , jfloat gz )
2014-03-30 00:39:37 +00:00
{
int ret ;
2016-02-10 23:23:43 +00:00
static vec3_t oacc ;
static vec3_t ogyro ;
2014-03-30 00:39:37 +00:00
//if we had an error, don't even run a frame any more.
if ( * errormessage | | ! sys_running )
{
Sys_Printf ( " Crashed or quit \n " ) ;
return 8 ;
}
2016-02-10 23:23:43 +00:00
// Sys_Printf("starting frame\n");
2014-03-30 00:39:37 +00:00
# ifdef SERVERONLY
SV_Frame ( ) ;
# else
unsigned int now = Sys_Milliseconds ( ) ;
double tdelta = ( now - sys_lastframe ) * 0.001 ;
2016-02-10 23:23:43 +00:00
if ( oacc [ 0 ] ! = ax | | oacc [ 1 ] ! = ay | | oacc [ 2 ] ! = az )
2014-03-30 00:39:37 +00:00
{
//down: x= +9.8
//left: y= -9.8
//up: z= +9.8
CSQC_Accelerometer ( ax , ay , az ) ;
2016-02-10 23:23:43 +00:00
oacc [ 0 ] = ax ;
oacc [ 1 ] = ay ;
oacc [ 2 ] = az ;
}
if ( ogyro [ 0 ] ! = gx | | ogyro [ 1 ] ! = gy | | ogyro [ 2 ] ! = gz )
{
CSQC_Gyroscope ( gx * 180.0 / M_PI , gy * 180.0 / M_PI , gz * 180.0 / M_PI ) ;
ogyro [ 0 ] = gx ;
ogyro [ 1 ] = gy ;
ogyro [ 2 ] = gz ;
2014-03-30 00:39:37 +00:00
}
Host_Frame ( tdelta ) ;
sys_lastframe = now ;
# endif
ret = 0 ;
if ( Key_Dest_Has ( kdm_console | kdm_message ) | | ( ! Key_Dest_Has ( ~ kdm_game ) & & cls . state = = ca_disconnected ) | | sys_osk . ival )
ret | = 1 ;
if ( vibrateduration )
ret | = 2 ;
if ( sys_keepscreenon . ival )
ret | = 4 ;
2016-02-10 23:23:43 +00:00
if ( * errormessage | | ! sys_running )
2014-03-30 00:39:37 +00:00
ret | = 8 ;
if ( sys_orientation . modified | | sys_glesversion_cvar . modified )
ret | = 16 ;
if ( sys_soundflags )
{
if ( sys_soundflags & 2 )
sys_soundflags & = ~ 2 ;
else
ret | = 32 ;
}
2016-02-10 23:23:43 +00:00
// Sys_Printf("frame ended\n");
2014-03-30 00:39:37 +00:00
return ret ;
}
2016-02-10 23:23:43 +00:00
//tells us that our old gl context is about to be nuked.
JNIEXPORT void JNICALL Java_com_fteqw_FTEDroidEngine_killglcontext ( JNIEnv * env , jobject obj )
{
if ( ! sys_running )
return ;
if ( qrenderer = = QR_NONE )
return ; //not initialised yet...
Sys_Printf ( " Killing resources \n " ) ;
R_ShutdownRenderer ( true ) ;
qrenderer = QR_NONE ;
sys_glesversion = 0 ;
if ( ! r_blockvidrestart )
r_blockvidrestart = 3 ; //so video is restarted properly for the next frame
}
2014-03-30 00:39:37 +00:00
//tells us that our old gl context got completely obliterated
JNIEXPORT void JNICALL Java_com_fteqw_FTEDroidEngine_newglcontext ( JNIEnv * env , jobject obj )
{
if ( sys_running )
sys_running = 2 ;
2014-06-27 16:10:10 +00:00
//fixme: wipe image handles, and vbos
2014-03-30 00:39:37 +00:00
}
//called when the user tries to use us to open one of our file types
JNIEXPORT void JNICALL Java_com_fteqw_FTEDroidEngine_openfile ( JNIEnv * env , jobject obj ,
jstring openfile )
{
const char * fname = ( * env ) - > GetStringUTFChars ( env , openfile , NULL ) ;
2016-02-10 23:23:43 +00:00
if ( sys_running )
Host_RunFile ( fname , strlen ( fname ) , NULL ) ;
2014-03-30 00:39:37 +00:00
( * env ) - > ReleaseStringUTFChars ( env , openfile , fname ) ;
}
//called for init or resizes
JNIEXPORT void JNICALL Java_com_fteqw_FTEDroidEngine_init ( JNIEnv * env , jobject obj ,
2016-02-10 23:23:43 +00:00
jint width , jint height , jfloat dpix , jfloat dpiy , jint glesversion , jstring japkpath , jstring jusrpath )
2014-03-30 00:39:37 +00:00
{
const char * tmp ;
if ( * errormessage )
return ;
vid . pixelwidth = width ;
vid . pixelheight = height ;
sys_glesversion = glesversion ;
2016-02-10 23:23:43 +00:00
sys_dpi_x = dpix ;
sys_dpi_y = dpiy ;
2014-03-30 00:39:37 +00:00
if ( sys_running )
{
2016-02-10 23:23:43 +00:00
if ( ! glesversion )
return ; //gah!
Sys_Printf ( " vid size changed (%i %i gles%i) \n " , width , height , glesversion ) ;
if ( ! r_blockvidrestart )
2014-03-30 00:39:37 +00:00
{
//if our textures got destroyed, we need to reload them all
2016-02-10 23:23:43 +00:00
Cmd_ExecuteString ( " vid_reload \n " , RESTRICT_LOCAL ) ;
2014-03-30 00:39:37 +00:00
}
else
{
//otherwise we just need to set the size properly again.
Cvar_ForceCallback ( & vid_conautoscale ) ;
}
}
else
{
const char * args [ ] =
{
" ftedroid " ,
" -basepack " ,
sys_basepak , /*filled in later*/
" " ,
" "
} ;
quakeparms_t parms ;
Sys_Printf ( " reinit \n " ) ;
if ( sys_memheap )
free ( sys_memheap ) ;
memset ( & parms , 0 , sizeof ( parms ) ) ;
parms . basedir = sys_basedir ; /*filled in later*/
parms . argc = 3 ;
parms . argv = args ;
tmp = ( * env ) - > GetStringUTFChars ( env , japkpath , NULL ) ;
Q_strncpyz ( sys_basepak , tmp , sizeof ( sys_basedir ) ) ;
( * env ) - > ReleaseStringUTFChars ( env , japkpath , tmp ) ;
tmp = ( * env ) - > GetStringUTFChars ( env , jusrpath , NULL ) ;
Q_strncpyz ( sys_basedir , tmp , sizeof ( sys_basedir ) ) ;
( * env ) - > ReleaseStringUTFChars ( env , jusrpath , tmp ) ;
Sys_Printf ( " Starting up (apk=%s, usr=%s) \n " , args [ 2 ] , parms . basedir ) ;
COM_InitArgv ( parms . argc , parms . argv ) ;
2015-05-03 19:57:46 +00:00
TL_InitLanguages ( sys_basedir ) ;
2014-03-30 00:39:37 +00:00
# ifdef SERVERONLY
SV_Init ( & parms ) ;
# else
Host_Init ( & parms ) ;
# endif
sys_running = true ;
sys_lastframe = Sys_Milliseconds ( ) ;
sys_orientation . modified = true ;
2016-02-10 23:23:43 +00:00
while ( r_blockvidrestart = = 1 )
Java_com_fteqw_FTEDroidEngine_frame ( env , obj , 0 , 0 , 0 , 0 , 0 , 0 ) ;
Sys_Printf ( " Engine started \n " ) ;
2014-03-30 00:39:37 +00:00
}
}
static int secbase ;
# ifdef _POSIX_TIMERS
double Sys_DoubleTime ( void )
{
struct timespec ts ;
clock_gettime ( CLOCK_MONOTONIC , & ts ) ;
if ( ! secbase )
{
secbase = ts . tv_sec ;
return ts . tv_nsec / 1000000000.0 ;
}
return ( ts . tv_sec - secbase ) + ts . tv_nsec / 1000000000.0 ;
}
unsigned int Sys_Milliseconds ( void )
{
struct timespec ts ;
clock_gettime ( CLOCK_MONOTONIC , & ts ) ;
if ( ! secbase )
{
secbase = ts . tv_sec ;
return ts . tv_nsec / 1000000 ;
}
return ( ts . tv_sec - secbase ) * 1000 + ts . tv_nsec / 1000000 ;
}
# else
double Sys_DoubleTime ( void )
{
struct timeval tp ;
struct timezone tzp ;
gettimeofday ( & tp , & tzp ) ;
if ( ! secbase )
{
secbase = tp . tv_sec ;
return tp . tv_usec / 1000000.0 ;
}
return ( tp . tv_sec - secbase ) + tp . tv_usec / 1000000.0 ;
}
unsigned int Sys_Milliseconds ( void )
{
struct timeval tp ;
struct timezone tzp ;
gettimeofday ( & tp , & tzp ) ;
if ( ! secbase )
{
secbase = tp . tv_sec ;
return tp . tv_usec / 1000 ;
}
return ( tp . tv_sec - secbase ) * 1000 + tp . tv_usec / 1000 ;
}
# endif
void Sys_Shutdown ( void )
{
free ( sys_memheap ) ;
}
void Sys_Quit ( void )
{
# ifndef SERVERONLY
Host_Shutdown ( ) ;
# else
SV_Shutdown ( ) ;
# endif
2016-02-10 23:23:43 +00:00
sys_running = false ;
2014-03-30 00:39:37 +00:00
LOGI ( " %s " , " quitting " ) ;
longjmp ( host_abort , 1 ) ;
exit ( 0 ) ;
}
void Sys_Error ( const char * error , . . . )
{
va_list argptr ;
char string [ 1024 ] ;
va_start ( argptr , error ) ;
vsnprintf ( string , sizeof ( string ) - 1 , error , argptr ) ;
va_end ( argptr ) ;
if ( ! * string )
strcpy ( string , " no error " ) ;
Q_strncpyz ( errormessage , string , sizeof ( errormessage ) ) ;
LOGE ( " %s " , string ) ;
longjmp ( host_abort , 1 ) ;
exit ( 1 ) ;
}
void Sys_Printf ( char * fmt , . . . )
{
va_list argptr ;
char string [ 1024 ] ;
va_start ( argptr , fmt ) ;
vsnprintf ( string , sizeof ( string ) - 1 , fmt , argptr ) ;
va_end ( argptr ) ;
LOGI ( " %s " , string ) ;
}
void Sys_Warn ( char * fmt , . . . )
{
va_list argptr ;
char string [ 1024 ] ;
va_start ( argptr , fmt ) ;
vsnprintf ( string , sizeof ( string ) - 1 , fmt , argptr ) ;
va_end ( argptr ) ;
LOGW ( " %s " , string ) ;
}
void Sys_CloseLibrary ( dllhandle_t * lib )
{
dlclose ( lib ) ;
}
dllhandle_t * Sys_LoadLibrary ( const char * name , dllfunction_t * funcs )
{
dllhandle_t * h ;
h = dlopen ( name , RTLD_LAZY ) ;
return h ;
}
void * Sys_GetAddressForName ( dllhandle_t * module , const char * exportname )
{
return dlsym ( module , exportname ) ;
}
char * Sys_ConsoleInput ( void )
{
return NULL ;
}
void Sys_mkdir ( char * path ) //not all pre-unix systems have directories (including dos 1)
{
mkdir ( path , 0777 ) ;
}
qboolean Sys_remove ( char * path )
{
return ! unlink ( path ) ;
}
qboolean Sys_Rename ( char * oldfname , char * newfname )
{
return ! rename ( oldfname , newfname ) ;
}
void Sys_SendKeyEvents ( void )
{
}
void Sys_Init ( void )
{
2015-03-03 00:14:43 +00:00
Cmd_AddCommandD ( " sys_vibratetime " , Sys_Vibrate_f , " Provides gamecode with a way to explicitly invoke the hardware vibrator in a simple way. " ) ;
2014-03-30 00:39:37 +00:00
Cvar_Register ( & sys_vibrate , " android stuff " ) ;
Cvar_Register ( & sys_osk , " android stuff " ) ;
Cvar_Register ( & sys_keepscreenon , " android stuff " ) ;
Cvar_Register ( & sys_orientation , " android stuff " ) ;
Cvar_Register ( & sys_glesversion_cvar , " android stuff " ) ;
}
qboolean Sys_GetDesktopParameters ( int * width , int * height , int * bpp , int * refreshrate )
{
* width = 320 ;
* height = 240 ;
* bpp = 16 ;
* refreshrate = 60 ;
return false ;
}
qboolean Sys_RandomBytes ( qbyte * string , int len )
{
qboolean res = false ;
int fd = open ( " /dev/urandom " , 0 ) ;
if ( fd > = 0 )
{
res = ( read ( fd , string , len ) = = len ) ;
close ( fd ) ;
}
return res ;
}
void Sys_ServerActivity ( void )
{
/*FIXME: flash window*/
}
void Sys_Sleep ( double seconds )
{
struct timespec ts ;
ts . tv_sec = ( time_t ) seconds ;
seconds - = ts . tv_sec ;
ts . tv_nsec = seconds * 1000000000.0 ;
nanosleep ( & ts , NULL ) ;
}
qboolean Sys_InitTerminal ( void )
{
/*switching to dedicated mode, show text window*/
return false ;
}
void Sys_CloseTerminal ( void )
{
}
# define SYS_CLIPBOARD_SIZE 256
static char clipboard_buffer [ SYS_CLIPBOARD_SIZE ] = { 0 } ;
char * Sys_GetClipboard ( void )
{
return clipboard_buffer ;
}
void Sys_CloseClipboard ( char * bf )
{
}
void Sys_SaveClipboard ( char * text )
{
Q_strncpyz ( clipboard_buffer , text , SYS_CLIPBOARD_SIZE ) ;
}
2015-02-02 08:01:53 +00:00
int Sys_EnumerateFiles ( const char * gpath , const char * match , int ( * func ) ( const char * , qofs_t , time_t mtime , void * , searchpathfuncs_t * ) , void * parm , searchpathfuncs_t * spath )
2014-03-30 00:39:37 +00:00
{
DIR * dir ;
char apath [ MAX_OSPATH ] ;
char file [ MAX_OSPATH ] ;
char truepath [ MAX_OSPATH ] ;
char * s ;
struct dirent * ent ;
struct stat st ;
//printf("path = %s\n", gpath);
//printf("match = %s\n", match);
if ( ! gpath )
gpath = " " ;
* apath = ' \0 ' ;
Q_strncpyz ( apath , match , sizeof ( apath ) ) ;
for ( s = apath + strlen ( apath ) - 1 ; s > = apath ; s - - )
{
if ( * s = = ' / ' )
{
s [ 1 ] = ' \0 ' ;
match + = s - apath + 1 ;
break ;
}
}
if ( s < apath ) //didn't find a '/'
* apath = ' \0 ' ;
Q_snprintfz ( truepath , sizeof ( truepath ) , " %s/%s " , gpath , apath ) ;
//printf("truepath = %s\n", truepath);
//printf("gamepath = %s\n", gpath);
//printf("apppath = %s\n", apath);
//printf("match = %s\n", match);
dir = opendir ( truepath ) ;
if ( ! dir )
{
Con_DPrintf ( " Failed to open dir %s \n " , truepath ) ;
return true ;
}
do
{
ent = readdir ( dir ) ;
if ( ! ent )
break ;
if ( * ent - > d_name ! = ' . ' )
{
if ( wildcmp ( match , ent - > d_name ) )
{
Q_snprintfz ( file , sizeof ( file ) , " %s/%s " , truepath , ent - > d_name ) ;
if ( stat ( file , & st ) = = 0 )
{
Q_snprintfz ( file , sizeof ( file ) , " %s%s%s " , apath , ent - > d_name , S_ISDIR ( st . st_mode ) ? " / " : " " ) ;
2015-02-02 08:01:53 +00:00
if ( ! func ( file , st . st_size , st . st_mtime , parm , spath ) )
2014-03-30 00:39:37 +00:00
{
closedir ( dir ) ;
return false ;
}
}
else
printf ( " Stat failed for \" %s \" \n " , file ) ;
}
}
} while ( 1 ) ;
closedir ( dir ) ;
return true ;
}
#if 0
# include <android/asset_manager.h>
int Sys_EnumerateFiles ( const char * gpath , const char * match , int ( * func ) ( const char * , qofs_t , void * ) , void * parm )
{
qboolean go = true ;
const char * f ;
struct AAssetDir * ad ;
ad = AAssetManager_openDir ( assetmgr , gpath ) ;
while ( go & & ( f = AAssetDir_getNextFileName ( ad ) ) )
{
if ( wildcmp ( match , f ) )
{
Sys_Printf ( " Found %s \n " , f ) ;
go = func ( f , 0 , parm ) ;
}
}
AAssetDir_close ( ad ) ;
return 0 ;
}
typedef struct
{
vfsfile_t funcs ;
AAsset * handle ;
} assetfile_t ;
static int AF_ReadBytes ( vfsfile_t * h , void * buf , int len )
{
assetfile_t * f = ( assetfile_t * ) h ;
return AAsset_read ( f - > handle , buf , len ) ;
}
static qboolean AF_Seek ( vfsfile_t * h , unsigned long offs )
{
assetfile_t * f = ( assetfile_t * ) h ;
AAsset_seek ( f - > handle , offs , SEEK_SET ) ;
return true ;
}
static unsigned long AF_Tell ( vfsfile_t * h )
{
assetfile_t * f = ( assetfile_t * ) h ;
return AAsset_seek ( f - > handle , 0 , SEEK_CUR ) ;
}
static unsigned long AF_GetSize ( vfsfile_t * h )
{
assetfile_t * f = ( assetfile_t * ) h ;
return AAsset_getLength ( f - > handle ) ;
}
static void AF_Close ( vfsfile_t * h )
{
assetfile_t * f = ( assetfile_t * ) h ;
AAsset_close ( f - > handle ) ;
Z_Free ( f ) ;
}
static void AF_Flush ( vfsfile_t * h )
{
}
vfsfile_t * Sys_OpenAsset ( char * fname )
{
assetfile_t * file ;
AAsset * a ;
a = AAssetManager_open ( assetmgr , fname , AASSET_MODE_UNKNOWN ) ;
if ( ! a )
{
Sys_Printf ( " Unable to open asset %s \n " , fname ) ;
return NULL ;
}
Sys_Printf ( " opened asset %s \n " , fname ) ;
file = Z_Malloc ( sizeof ( assetfile_t ) ) ;
file - > funcs . ReadBytes = AF_ReadBytes ;
file - > funcs . WriteBytes = NULL ;
file - > funcs . Seek = AF_Seek ;
file - > funcs . Tell = AF_Tell ;
file - > funcs . GetLen = AF_GetSize ;
file - > funcs . Close = AF_Close ;
file - > funcs . Flush = AF_Flush ;
file - > handle = a ;
return ( vfsfile_t * ) file ;
}
# endif