diff options
Diffstat (limited to '')
| -rw-r--r-- | project/src/lib-file.c | 3435 |
1 files changed, 3435 insertions, 0 deletions
diff --git a/project/src/lib-file.c b/project/src/lib-file.c new file mode 100644 index 0000000..5a1cc44 --- /dev/null +++ b/project/src/lib-file.c @@ -0,0 +1,3435 @@ + +/*************************************************************************** + * _______ _______ _______ * + * | ___ |____ | ___ | * + * | | |_| / /| | |_| * + * | |_____ / / | |_____ * + * |_____ | / / |_____ | * + * _ | | / / _ | | * + * | |___| |/ /____| |___| | * + * |_______|_______|_______| * + * * + * Wiimms SZS Tools * + * https://szs.wiimm.de/ * + * * + *************************************************************************** + * * + * This file is part of the SZS project. * + * Visit https://szs.wiimm.de/ for project details and sources. * + * * + * Copyright (c) 2011-2020 by Dirk Clemens <wiimm@wiimm.de> * + * * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt * + * * + ***************************************************************************/ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <time.h> +#include <utime.h> +#include <dirent.h> + +#include "lib-std.h" +#include "lib-szs.h" +#include "lib-rarc.h" +#include "lib-pack.h" +#include "lib-brres.h" +#include "lib-breff.h" +#include "lib-xbmg.h" +#include "lib-kcl.h" +#include "lib-kmp.h" +#include "lib-rkc.h" +#include "lib-rkg.h" +#include "lib-image.h" +#include "lib-staticr.h" +#include "lib-ctcode.h" +#include "lib-lecode.h" +#include "lib-common.h" +#include "lib-bzip2.h" +#include "lib-checksum.h" + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// cygwin support /////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef __CYGWIN__ + + void NormalizeFilenameParam ( ParamList_t *param ) + { + if ( param->arg && *param->arg ) + { + char *res = AllocNormalizedFilenameCygwin(param->arg); + FREE(param->arg); + param->arg = res; + } + } + +#endif // __CYGWIN__ + +/////////////////////////////////////////////////////////////////////////////// + +void SetSource ( ccp source ) +{ + #ifdef __CYGWIN__ + opt_source = IsWindowsDriveSpec(source) + ? AllocNormalizedFilenameCygwin(source) + : source; + #else + opt_source = source; + #endif + + //if ( source && *source ) + AppendStringField(&source_list,source,false); + noPRINT("SetSource(%s) N=%u/%u\n",source,source_list.used,source_list.size); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SetDest ( ccp dest, bool mkdir ) +{ + #ifdef __CYGWIN__ + opt_dest = IsWindowsDriveSpec(dest) + ? AllocNormalizedFilenameCygwin(dest) + : dest; + #else + opt_dest = dest; + #endif + opt_mkdir = mkdir; +} + +/////////////////////////////////////////////////////////////////////////////// + +void CheckOptDest ( ccp default_dest, bool default_mkdir ) +{ + if (!opt_dest) + { + opt_dest = default_dest; + if (!opt_mkdir) + opt_mkdir = default_mkdir; + } + + if ( opt_dest && opt_dest[0] == '-' && !opt_dest[1] ) + stdlog = stderr; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// struct File_t /////////////// +/////////////////////////////////////////////////////////////////////////////// + +static bool is_seekable +( + File_t * F // file structure +) +{ + mode_t mode = F->st.st_mode; + F->is_seekable = ( S_ISREG(mode) || S_ISBLK(mode) || S_ISCHR(mode) ) + && F->st.st_size + && lseek(fileno(F->f),0,SEEK_SET) != (off_t)-1; + return F->is_seekable; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError OpenFILE +( + File_t * f, // file structure + bool initialize, // true: initialize 'f' + ccp fname, // file to open + bool ignore_no_file, // ignore if file does not exists + // and return warning ERR_NOT_EXISTS + bool ignore_limit // ignore opt_max_file_size +) +{ + FileMode_t fmode = FM_STDIO; + if (ignore_no_file) + fmode |= FM_IGNORE; + return OpenFile( f,initialize, fname,fmode, + ignore_limit ? 0 : opt_max_file_size, + "You can change this security limit with --max-file-size=MiB.\n" ); +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError CreateFILE +( + File_t * f, // file structure + bool initialize, // true: initialize 'f' + ccp fname, // file to open + bool testmode, // true: don't open + bool create_path, // create path if necessary + bool overwrite, // overwrite existing files without warning + bool remove_dest, // remove existing file before creating + bool update // don't create new files +) +{ + FileMode_t fmode = FM_STDIO; + if (testmode) + fmode |= FM_TEST; + if (create_path) + fmode |= FM_MKDIR; + if (overwrite) + fmode |= FM_OVERWRITE; + if (opt_number) + fmode |= FM_NUMBER; + if (remove_dest) + fmode |= FM_REMOVE; + if (update) + fmode |= FM_UPDATE; + return CreateFile(f,initialize,fname,fmode); +} + +/////////////////////////////////////////////////////////////////////////////// + +FileMode_t GetFileModeByOpt +( + int testmode, // test mode + ccp fname1, // NULL or dest filename + ccp fname2 // NULL or source filename +) +{ + FileMode_t fmode = FM_STDIO; + + if ( testmode ) fmode |= FM_TEST; + if ( opt_mkdir ) fmode |= FM_MKDIR; + if ( opt_number ) fmode |= FM_NUMBER; + if ( opt_remove_dest ) fmode |= FM_REMOVE; + if ( opt_update ) fmode |= FM_UPDATE; + + if ( opt_overwrite || fname1 && *fname1 && fname2 && !strcmp(fname1,fname2) ) + fmode |= FM_OVERWRITE; + + return fmode; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError CreateFileOpt +( + File_t * f, // file structure + bool initialize, // true: initialize 'f' + ccp fname, // file to open + bool testmode, // true: don't open + ccp srcfile // NULL or src file name + // if fname == srcfile: overwrite enabled +) +{ + DASSERT(f); + DASSERT(fname); + + FileMode_t fmode = GetFileModeByOpt(testmode,fname,srcfile) | FM_TOUCH; + return CreateFile(f,initialize,fname,fmode); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +enumError AppendFILE +( + File_t * f, // file structure + bool initialize, // true: initialize 'f' + ccp fname, // file to open + bool testmode, // true: don't open + bool create_path // create path if necessary +) +{ + FileMode_t fmode = FM_STDIO | FM_APPEND; + if (testmode) + fmode |= FM_TEST; + if (create_path) + fmode |= FM_MKDIR; + return CreateFile(f,initialize,fname,fmode); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +FILE * CreateAnalyzeFile() +{ + static bool done = false; + if (done) + return analyze_file; + done = true; + + if ( !analyze_fname || !*analyze_fname ) + return 0; + + if (!strcmp(analyze_fname,"-")) + analyze_file = stdout; + else + analyze_file = fopen(analyze_fname,"w"); + if (!analyze_file) + ERROR1(ERR_CANT_CREATE,"Can't create analyse log file: %s\n",analyze_fname); + + return analyze_file; +} + +/////////////////////////////////////////////////////////////////////////////// + +void CloseAnalyzeFile() +{ + if ( analyze_file && analyze_file != stdout ) + fclose(analyze_file); + analyze_file = 0; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +char * SplitSubPath +( + char * buf, // destination buffer + size_t buf_size, // size of 'buf' + ccp path // source path, if NULL: use 'buf' +) +{ + char * end = path && path != buf + ? StringCopyS(buf,buf_size,path) + : buf + strlen(buf); + + if ( end >= buf + buf_size - 2 ) + { + end = buf + buf_size - 2; + *end = 0; + } + + struct stat st; + if (!stat(buf,&st)) + return end; + + char * ptr = end; + while ( ptr > buf ) + { + ptr--; + while ( ptr > buf && *ptr != '/' && *ptr != '\\' ) + ptr--; + if ( ptr == buf ) + break; + + *ptr = 0; + noPRINT("TEST: |%s|\n",buf); + if (!stat(buf,&st)) + { + noPRINT("FOUND: |%s|\n",buf); + if (S_ISREG(st.st_mode)) + { + memmove(ptr+1,ptr,end-ptr+1); + *++ptr = '/'; + return ptr; + } + *ptr = '/'; + return 0; + } + *ptr = '/'; + } + return 0; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// more file support /////////////// +/////////////////////////////////////////////////////////////////////////////// + +int ScanOptChdir ( ccp arg ) +{ + PRINT("CHDIR: %s\n",arg); + if ( !arg || !*arg || !chdir(arg) ) + return 0; + ERROR1(ERR_CANT_OPEN,"Can't change dir: %s\n",arg); + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError OpenReadFILE +( + ccp path1, // NULL or part #1 of path + ccp path2, // NULL or part #2 of path + bool silent, // suppress all error messages + u8 ** res_data, // store alloced data here + uint * res_size, // not NULL: store data size + ccp * res_fname, // not NULL: store alloced filename + FileAttrib_t * res_fatt // not NULL: store file attributes +) +{ + DASSERT( path1 || path2 ); + DASSERT(res_data); + + *res_data = 0; + if (res_size) + *res_size = 0; + if (res_fname) + *res_fname = 0; + if (res_fatt) + memset(res_fatt,0,sizeof(*res_fatt)); + + char pathbuf[PATH_MAX]; + ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2); + PRINT("OpenReadFILE(%s,%d)\n",path,silent); + + File_t F; + enumError err = OpenFILE(&F,true,path,silent,false); + if (err) + return err; + + uint data_size = F.st.st_size; + if (!F.is_seekable) + data_size = opt_max_file_size; + u8 *data = MALLOC(data_size+1); + + size_t read_size = fread(data,1,data_size,F.f); + if ( read_size && !F.is_seekable ) + { + PRINT("non seekable read: %zu\n",read_size); + data_size = read_size; + data = REALLOC(data,data_size+1); + } + data[data_size] = 0; // termination for text files + + if ( read_size != data_size ) + { + if (!silent) + FILEERROR1(&F,ERR_READ_FAILED,"Read failed: %s\n",path); + ResetFile(&F,false); + FREE(data); + return ERR_READ_FAILED; + } + + + *res_data = data; + if (res_size) + *res_size = data_size; + if (res_fname) + { + *res_fname = F.fname; + F.fname = EmptyString; + } + if (res_fatt) + SetFileAttrib(res_fatt,&F.fatt,0); + + ResetFile(&F,false); + return ERR_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError LoadFILE +( + ccp path1, // NULL or part #1 of path + ccp path2, // NULL or part #2 of path + size_t skip, // skip num of bytes before reading + void * data, // destination buffer, size = 'size' + size_t size, // size to read + int silent, // 0: print all error messages + // 1: suppress file size warning + // 2: suppress all error messages + FileAttrib_t * fatt, // not NULL: store file attributes + bool fatt_max // true: store *max* values to 'fatt' +) +{ + ASSERT(data); + if (!size) + return ERR_OK; + + char pathbuf[PATH_MAX]; + ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2); + TRACE("LoadFILE(%s,%zu,%zu,%d)\n",path,skip,size,silent); + + if (fatt) + { + struct stat st; + if (!stat(path,&st)) + UseFileAttrib(fatt,0,&st,fatt_max); + } + + FILE * f = fopen(path,"rb"); + if (!f) + { + if (silent<2) + ERROR1(ERR_CANT_OPEN,"Can't open file: %s\n",path); + return ERR_CANT_OPEN; + } + + if ( skip > 0 ) + fseek(f,skip,SEEK_SET); + + size_t read_stat = fread(data,1,size,f); + fclose(f); + + if ( read_stat == size ) + return ERR_OK; + + enumError err = ERR_READ_FAILED; + if ( silent == 1 && size > 0 ) + err = ERR_WARNING; + else if ( silent < 2 ) + ERROR1(ERR_READ_FAILED,"Can't read file: %s\n",path); + + noPRINT("D=%p, s=%zu/%zu: %s\n",data,read_stat,size,path); + if ( read_stat >= 0 && read_stat < size ) + memset((char*)data+read_stat,0,size-read_stat); + + return err; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError SaveFILE +( + ccp path1, // NULL or part #1 of path + ccp path2, // NULL or part #2 of path + bool overwrite, // true: force overwriting + const void * data, // data to write + uint data_size, // size of 'data' + FileAttrib_t * fatt // not NULL: set timestamps using this attribs +) +{ + DASSERT(data); + + char pathbuf[PATH_MAX]; + ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2); + TRACE("SaveFILE(%d) %s\n",overwrite,path); + + File_t F; + enumError err = CreateFileOpt(&F,true,path,testmode, overwrite ? path : 0 ); + if ( err || !F.f ) + { + ResetFile(&F,0); + return err; + } + + const uint written = fwrite(data,1,data_size,F.f); + if ( written != data_size ) + { + ERROR1(ERR_WRITE_FAILED,"Write to file failed: %s\n",path); + ResetFile(&F,0); + return ERR_WRITE_FAILED; + } + + if (fatt) + memcpy(&F.fatt,fatt,sizeof(F.fatt)); + return ResetFile(&F,true); +} + +/////////////////////////////////////////////////////////////////////////////// + +uint IsolateFilename +( + // return length of found and copied string + + char * buf, // destination buffer + size_t bufsize, // size of 'buf' + ccp path, // path to analyze + file_format_t fform // not NULL: remove related file extension +) +{ + DASSERT(buf); + DASSERT(path); + + uint len; + path = FindFilename(path,&len); + if ( len >= bufsize ) + len = bufsize-1; + memcpy(buf,path,len); + buf[len] = 0; + if (fform) + { + ccp ext = GetExtFF(0,fform); + uint elen = strlen(ext); + char *bufext = buf+len-elen; + if ( elen < len && !strcasecmp(ext,bufext) ) + { + *bufext = 0; + len = bufext - buf; + } + else + { + ext = GetMagicExtFF(0,fform); + elen = strlen(ext); + bufext = buf+len-elen; + if ( elen < len && !strcasecmp(ext,bufext) ) + { + *bufext = 0; + len = bufext - buf; + } + } + } + return len; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +enum { N_NAME_BUF = 16, NAME_BUF_SIZE = 24 }; + +//----------------------------------------------------------------------------- + +static char *GetNameBuf() +{ + static char buffers[N_NAME_BUF][NAME_BUF_SIZE]; + static int buf_idx = 0; + + if ( --buf_idx < 0 ) + buf_idx = N_NAME_BUF - 1; + return buffers[buf_idx]; +} + +//----------------------------------------------------------------------------- + +ccp GetNameFF ( file_format_t ff1, file_format_t ff2 ) +{ + if ( ff1 == FF_DIRECTORY || IsCompressedFF(ff1) ) + { + ccp prefix = FileTypeTab[ff1].name; + if ( !ff2 || (uint)ff2 >= FF_N || ff2 == ff1 ) + { + noPRINT("GetNameFF(%u,%u) -> '%s' [ff1]\n",ff1,ff2,prefix); + return prefix; + } + + char *buf = GetNameBuf(); + snprintf(buf,NAME_BUF_SIZE,"%s.%s",prefix,FileTypeTab[ff2].name); + noPRINT("GetNameFF(%u,%u) -> '%s' [1+2]\n",ff1,ff2,prefix); + return buf; + } + + noPRINT("GetNameFF(%u,%u) -> '%s' [tab]\n", ff1, ff2, + FileTypeTab[ ff2 && (uint)ff2 < FF_N ? ff2 : (uint)ff1 < FF_N ? ff1 : 0].name ); + return FileTypeTab[ ff2 && (uint)ff2 < FF_N ? ff2 : (uint)ff1 < FF_N ? ff1 : 0].name; +} + +//----------------------------------------------------------------------------- + +ccp GetNameFFv ( file_format_t ff1, file_format_t ff2, int version ) +{ + ccp name = GetNameFF(ff1,ff2); + if ( version > 0 ) + { + char *buf = GetNameBuf(); + snprintf(buf,NAME_BUF_SIZE,"%s.v%u",name,version); + name = buf; + } + return name; +} + +/////////////////////////////////////////////////////////////////////////////// + +ccp GetExtFF ( file_format_t ff1, file_format_t ff2 ) +{ + if ( IsCompressedFF(ff1) && ff1 != FF_BZ2 ) + { + return ff1 == FF_BZ + ? ( ff2 == FF_WU8 ? ".wbz" : ".bz" ) + : (uint)ff2 < FF_N ? FileTypeTab[ff2].ext_compr : ".szs"; + } + + if ( ff1 <= FF_UNKNOWN || ff1 >= FF_N ) + ff1 = ff2; + + return (uint)ff1 < FF_N + ? FileTypeTab[ff1].ext + : ".bin"; +} + +//----------------------------------------------------------------------------- + +ccp GetBinExtFF ( file_format_t ff ) +{ + if ( ff > FF_UNKNOWN && ff < FF_N && FileTypeTab[ff].fform_bin ) + return GetExtFF(0,FileTypeTab[ff].fform_bin); + + return ".bin"; +} + +//----------------------------------------------------------------------------- + +ccp GetTextExtFF ( file_format_t ff ) +{ + if ( ff > FF_UNKNOWN && ff < FF_N && FileTypeTab[ff].fform_txt ) + return GetExtFF(0,FileTypeTab[ff].fform_txt); + + return ".txt"; +} + +//----------------------------------------------------------------------------- + +ccp GetMagicStringFF ( file_format_t ff ) +{ + return ff > FF_UNKNOWN && ff < FF_N && FileTypeTab[ff].magic_len + ? (ccp)(FileTypeTab[ff].magic) + : 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +file_format_t GetBinFF ( file_format_t ff ) +{ + return ff > FF_UNKNOWN && ff < FF_N && FileTypeTab[ff].fform_bin + ? FileTypeTab[ff].fform_bin + : FF_UNKNOWN; +} + +//----------------------------------------------------------------------------- + +file_format_t GetTextFF ( file_format_t ff ) +{ + return ff > FF_UNKNOWN && ff < FF_N && FileTypeTab[ff].fform_txt + ? FileTypeTab[ff].fform_txt + : FF_UNKNOWN; +} + +/////////////////////////////////////////////////////////////////////////////// + +ccp GetMagicExtFF ( file_format_t ff1, file_format_t ff2 ) +{ + if (IsCompressedFF(ff1)) + return (uint)ff2 < FF_N ? FileTypeTab[ff2].ext_compr : ".szs"; + + if ( ff1 <= FF_UNKNOWN || ff1 >= FF_N ) + ff1 = ff2; + + return (uint)ff1 < FF_N + ? FileTypeTab[ff1].ext_magic + : ".bin"; +} + +/////////////////////////////////////////////////////////////////////////////// +// [[GetByMagicFF]] + +file_format_t GetByMagicFF +( + const void *data, // pointer to data + uint data_size, // size of data + uint file_size // NULL or total size of file +) +{ + if ( !data || !data_size ) + return FF_UNKNOWN; + + if ( file_size && data_size > file_size ) + data_size = file_size; + if (!file_size) + file_size = data_size; + + const u8 *data8 = (u8*)data; + if ( data_size >= 8 ) + { + if ( IsBZIP2(data,data_size) ) + return FF_BZ2; + + const u64 magic64 = be64(data); + switch(magic64) + { + case BMG_MAGIC8_NUM: return FF_BMG; + case PNG_MAGIC8_NUM: return FF_PNG; + case GCT_MAGIC8_NUM: return FF_GCT; + case LE_LPAR_MAGIC_NUM: return FF_LPAR; + + //--- see below for BOM support + case CT_TEXT_MAGIC8_NUM: return FF_CT_TEXT; + case DISTRIB_MAGIC8_NUM: return FF_DISTRIB; + case ITEMSLT_TEXT_MAGIC_NUM: return FF_ITEMSLT_TXT; + case OBJFLOW_TEXT_MAGIC8_NUM: return FF_OBJFLOW_TXT; + case GH_ITEM_TEXT_MAGIC8_NUM: return FF_GH_ITEM_TXT; + case GH_IOBJ_TEXT_MAGIC8_NUM: return FF_GH_IOBJ_TXT; + case GH_KART_TEXT_MAGIC8_NUM: return FF_GH_KART_TXT; + case GH_KOBJ_TEXT_MAGIC8_NUM: return FF_GH_KOBJ_TXT; + } + } + + if ( data_size >= 4 ) + { + const u32 magic32 = be32(data); + switch(magic32) + { + //--- see below for BOM support + case BMG_TEXT_MAGIC_NUM: return FF_BMG_TXT; + case KCL_TEXT_MAGIC_NUM: return FF_KCL_TXT; + case KMP_TEXT_MAGIC_NUM: return FF_KMP_TXT; + case MDL_TEXT_MAGIC_NUM: return FF_MDL_TXT; + case PAT_TEXT_MAGIC_NUM: return FF_PAT_TXT; + case KMG_TEXT_MAGIC_NUM: return FF_KMG_TXT; + case KRT_TEXT_MAGIC_NUM: return FF_KRT_TXT; + case KRM_TEXT_MAGIC_NUM: return FF_KRM_TXT; + + case YAZ0_MAGIC_NUM: return FF_YAZ0; + case YAZ1_MAGIC_NUM: return FF_YAZ1; + case XYZ0_MAGIC_NUM: return FF_XYZ; + case BZ_MAGIC_NUM: return FF_BZ; + case GCH_MAGIC_NUM: return FF_GCH; + case WCH_MAGIC_NUM: return FF_WCH; + case WPF_MAGIC_NUM: return FF_WPF; + case XPF_MAGIC_NUM: return FF_XPF; + case RKG_MAGIC_NUM: return FF_RKG; + + case KMG_MAGIC_NUM: return FF_KMG; + case KRT_MAGIC_NUM: return FF_KRT; + case KRM_MAGIC_NUM: return FF_KRM; +// [[obj-type+]] 3x +// case OBJFLOW_MAGIC_NUM: return FF_OBJFLOW; +// case GH_ITEM_MAGIC_NUM: return FF_GH_ITEM; +// case GH_KART_MAGIC_NUM: return FF_GH_KART; + + case U8_MAGIC_NUM: return FF_U8; + case WU8_MAGIC_NUM: return FF_WU8; + case RARC_MAGIC_NUM: return FF_RARC; + case RKCT_MAGIC_NUM: return FF_RKC; + case RKCO_MAGIC_NUM: return FF_RKCO; + case PACK_MAGIC_NUM: return FF_PACK; + + case BRRES_MAGIC_NUM: return FF_BRRES; + + case TPL_MAGIC_NUM: return FF_TPL; + case KMP_MAGIC_NUM: return FF_KMP; + case BRASD_MAGIC_NUM: return FF_BRASD; + + case LE_BINARY_MAGIC_NUM: return FF_LE_BIN; + case LEX_BIN_MAGIC_NUM: return FF_LEX; + case LEX_TEXT_MAGIC_NUM: return FF_LEX_TXT; + + case CT0_CODE_MAGIC_NUM: return FF_CT0_CODE; + case CT0_DATA_MAGIC_NUM: return FF_CT0_DATA; + case CT1_CODE_MAGIC_NUM: return FF_CT1_CODE; + case CT1_DATA_MAGIC_NUM: return FF_CT1_DATA; + + case CT_CUP1_MAGIC_NUM: return FF_CUP1; + case CT_CRS1_MAGIC_NUM: return FF_CRS1; + case CT_MOD1_MAGIC_NUM: return FF_MOD1; + case CT_MOD2_MAGIC_NUM: return FF_MOD2; + case CT_OVR1_MAGIC_NUM: return FF_OVR1; + + case BREFF_MAGIC_NUM: + if ( file_size >= 0x20 ) + return data_size < 0x14 || !memcmp(data+0x10,BREFF_MAGIC,4) + ? FF_BREFF : FF_UNKNOWN; + break; + + case BREFT_MAGIC_NUM: + if ( file_size >= 0x20 ) + return data_size < 0x14 || !memcmp(data+0x10,BREFT_MAGIC,4) + ? FF_BREFT : FF_UNKNOWN; + break; + } + + if ( data_size >= 0x20 ) + { + // size => avoid BRSUB.header detection + switch(magic32) + { + case CHR_MAGIC_NUM: return FF_CHR; + case CLR_MAGIC_NUM: return FF_CLR; + case MDL_MAGIC_NUM: return FF_MDL; + case PAT_MAGIC_NUM: return FF_PAT; + case SCN_MAGIC_NUM: return FF_SCN; + case SHP_MAGIC_NUM: return FF_SHP; + case SRT_MAGIC_NUM: return FF_SRT; + case TEX_MAGIC_NUM: + return IsValidTEXCT(data,data_size,0,0,0) < VALID_ERROR + ? FF_TEX_CT : FF_TEX; + } + } + } + + const uint bom_len = GetTextBOMLen(data,data_size); + if ( bom_len > 0 ) + { + if ( data_size >= 8 + bom_len ) + { + const u64 magic64 = be64(data+bom_len); + switch(magic64) + { + case CT_TEXT_MAGIC8_NUM: return FF_CT_TEXT; + case DISTRIB_MAGIC8_NUM: return FF_DISTRIB; + case OBJFLOW_TEXT_MAGIC8_NUM: return FF_OBJFLOW_TXT; +// [[obj-type+]] + case GH_ITEM_TEXT_MAGIC8_NUM: return FF_GH_ITEM_TXT; + case GH_IOBJ_TEXT_MAGIC8_NUM: return FF_GH_IOBJ_TXT; + case GH_KART_TEXT_MAGIC8_NUM: return FF_GH_KART_TXT; + case GH_KOBJ_TEXT_MAGIC8_NUM: return FF_GH_KOBJ_TXT; + } + } + + if ( data_size >= 4 + bom_len ) + { + const u32 magic32 = be32(data+bom_len); + switch(magic32) + { + case BMG_TEXT_MAGIC_NUM: return FF_BMG_TXT; + case KCL_TEXT_MAGIC_NUM: return FF_KCL_TXT; + case KMP_TEXT_MAGIC_NUM: return FF_KMP_TXT; + case MDL_TEXT_MAGIC_NUM: return FF_MDL_TXT; + case PAT_TEXT_MAGIC_NUM: return FF_PAT_TXT; + case KMG_TEXT_MAGIC_NUM: return FF_KMG_TXT; + case KRT_TEXT_MAGIC_NUM: return FF_KRT_TXT; + case KRM_TEXT_MAGIC_NUM: return FF_KRM_TXT; + } + } + } + + + //--- test for FF_OBJFLOW and FF_GH_* + + object_type_t ot; + if (ScanObjType(&ot,data,data_size,file_size)) + { + PRINT("GetByMagicFF(0x%x,0x%x) = %s\n",data_size,file_size,LogObjType(&ot)); + return ot.fform; + } + + //--- test for FF_BREFT_IMG + + if ( data_size >= sizeof(breft_image_t) && !be32(data) ) + { + const breft_image_t * bi = (breft_image_t*)data; + const ImageGeometry_t * geo = GetImageGeometry(bi->iform); + if ( geo && !geo->is_x ) + { + const uint width = be16(&bi->width); + const uint xwidth = ( width + geo->block_width - 1 ) + / geo->block_width * geo->block_width; + const uint height = be16(&bi->height); + const uint xheight = ( height + geo->block_height - 1 ) + / geo->block_height * geo->block_height; + const uint size = be32(&bi->img_size); + noPRINT("RAW IMG FOUND: %02x, %u*%u -> %u*%u, %u/%u\n", + bi->iform, width, height, xwidth, xheight, + xwidth * xheight * geo->bits_per_pixel / 8, size ); + if ( xwidth > 0 && xheight > 0 + && xwidth * xheight * geo->bits_per_pixel <= 8 * size ) + { + return FF_BREFT_IMG; + } + } + } + + if ( data_size > 3 && data8[1] == 12 && data8[2] == 19 ) + { + // mabye ItemSlot + if ( data8[0] == 6 && file_size == ITEMSLT_SIZE6 + || data8[0] == 12 + && ( file_size == ITEMSLT_SIZE12 || file_size == ITEMSLT_SIZE6 )) + { + return FF_ITEMSLT; + } + } + + if ( data_size > 4 && file_size == 4+27*396 && be32(data8) == 27 ) + return FF_DRIVER; + + if ( data_size > 4 && file_size == 4+36*396 && be32(data8) == 36 ) + return FF_VEHICLE; + + if (IsDolHeader(data,data_size,0)) + return FF_DOL; + + if ( IsValidKCL(0,data,data_size,file_size,0) < VALID_ERROR ) + return FF_KCL; + + if ( IsValidCTCODE(data,data_size,0,0) < VALID_ERROR ) + return FF_CT1_DATA; + + // test FF_WAV_OBJ & FF_SKP_OBJ + { + ccp ptr = data, end = ptr + data_size; + for(;;) + { + while ( ptr < end && ( *ptr == ' ' || *ptr == '\t' + || *ptr == '\r' || *ptr == '\n' )) + ptr++; + + if ( *ptr == 'g' ) + { + // ignore group commands + while ( ptr < end && *ptr != '\n' ) + ptr++; + continue; + } + + if ( *ptr != '#' ) + break; + + bool have_obj = false; + bool have_wavefront = false; + bool have_model = false; + while ( ptr < end && *ptr != '\n' ) + { + have_obj = have_obj || !strncasecmp(ptr,"obj",3); + have_wavefront = have_wavefront || !strncasecmp(ptr,"wavefront",9); + have_model = have_model || !strncasecmp(ptr,"model",5); + + if ( have_obj && have_wavefront ) + return FF_WAV_OBJ; + if ( have_obj && have_model ) + return FF_SKP_OBJ; + ptr++; + } + } + + if ( ( !memcmp(ptr,"mtllib",6) || !memcmp(ptr,"usemtl",6) ) + && ( ptr[6] == ' ' || ptr[6] == '\t' )) + { + return FF_WAV_OBJ; + } + + if ( *ptr == 'v' && ptr[1] == ' ' ) + { + ptr += 2; + int i; + bool ok = true; + for ( i = 0; i < 3 && ok; i++ ) + { + char *endscan; + strtod_comma(ptr,&endscan); + ok = endscan > ptr; + ptr = endscan; + } + while ( ptr < end && ( *ptr == ' ' || *ptr == '\r' )) + ptr++; + if ( ptr < end && *ptr == '\n' ) + return FF_WAV_OBJ; + } + } + + if ( IsValidSTATICR(data,data_size,0,0) < VALID_ERROR ) + return FF_STATICR; + + if ( IsValidBTI(data,data_size,0,0) < VALID_ERROR ) + return FF_BTI; + + + //--- test for scripts + + ccp beg = data + bom_len; + ccp end = data + data_size; + if ( beg < end ) + { + bool is_text = false; + ccp ptr = beg; + while ( ptr < end ) + { + const u8 ch = *ptr++; + if ( ch == '\r' || ch == '\n' ) + { + is_text = true; + ptr--; + break; + } + + if ( ch < ' ' && ch != '\f' && ch != '\v' && ch != '\t' ) + break; + } + + if (is_text) + { + while ( ptr > beg && ptr[-1] <= ' ' ) + ptr--; + ccp name = ptr; + while ( name > beg && isalnum((int)(name[-1])) ) + name--; + noPRINT("|%.*s|%.*s|\n",(int)(name-beg),beg,(int)(ptr-name),name); + if (!memcmp(beg,"#!",2)) + { + switch( ptr - name ) + { + case 2: + if (!strncasecmp(name,"sh",2)) return FF_SH; + break; + + case 3: + if (!strncasecmp(name,"php",3)) return FF_PHP; + break; + + case 4: + if (!strncasecmp(name,"bash",4)) return FF_BASH; + break; + + case 7: + if (!strncasecmp(name,"makedoc",7)) return FF_MAKEDOC; + break; + } + return FF_SCRIPT; + } + if ( !memcmp(beg,"<?",2) && !strncasecmp(name,"php",3) ) + return FF_PHP; + } + } + + return FF_UNKNOWN; +} + +/////////////////////////////////////////////////////////////////////////////// +// [[GetFileTypeByExt]] + +const file_type_t * GetFileTypeByExt +( + ccp ext, // NULL or file extension with optional preceeding point + bool need_magic // filter only file types with magic +) +{ + if (!ext) + return 0; + + while ( *ext == '.' ) + ext++; + + if (!*ext) + return 0; + + const file_type_t *p; + + // first: try member 'ext' + for ( p = FileTypeTab; p->name; p++ ) + if ( ( !need_magic || p->magic_len ) + && p->ext && !strcasecmp(ext,p->ext+1) ) + return p; + + // second: try member 'ext_compr' + for ( p = FileTypeTab; p->name; p++ ) + if ( ( !need_magic || p->magic_len ) + && p->ext_compr && !strcasecmp(ext,p->ext_compr+1) ) + return p; + + // third: try member 'ext_magic' + for ( p = FileTypeTab; p->name; p++ ) + if ( ( !need_magic || p->magic_len ) + && p->ext_magic && !strcasecmp(ext,p->ext_magic+1) ) + return p; + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// [[GetFileTypeByExt]] + +const file_type_t * GetFileTypeBySubdir +( + ccp path, // NULL or file extension with optional preceeding point + bool need_magic // filter only file types with magic +) +{ + if (!path) + return 0; + + ccp p1 = 0, p2 = 0; + for(;;) + { + while ( *path == '/' ) + path++; + + ccp end = strchr(path,'/'); + if (!end) + break; + p1 = path; + p2 = end; + path = p2; + } + + if (p1) + { + DASSERT(p2); + + char name[20]; + const uint len = p2 - p1; + if ( len < sizeof(name) ) + { + memcpy(name,p1,len); + name[len] = 0; + noPRINT("### %s : %s\n",name,p1); + + const file_type_t *p; + for ( p = FileTypeTab; p->name; p++ ) + if ( ( !need_magic || p->magic_len ) + && p->subdir && !strcasecmp(name,p->subdir) ) + return p; + } + } + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// [[GetByNameFF]] + +file_format_t GetByNameFF ( ccp name ) +{ + const KeywordTab_t * cmd = ScanKeyword(0,name,cmdtab_FileType); + return cmd ? cmd->id : FF_UNKNOWN; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +file_format_t IsImageFF +( + // returns the normalized FF for a valid image or NULL (=FF_UNKNOWN) + + file_format_t fform, // file format to check + bool allow_png // true: allow PNG +) +{ + switch (fform) + { + case FF_TPL: + case FF_BTI: + case FF_TEX: + case FF_TEX_CT: + case FF_BREFT_IMG: + return fform; + + case FF_BREFT: + return FF_BREFT_IMG; + + case FF_PNG: + return allow_png ? FF_PNG : FF_UNKNOWN; + + default: + return FF_UNKNOWN; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +file_format_t GetImageFF +( + file_format_t fform1, // if a valid image ff, use this one + file_format_t fform2, // if a valid image ff, use this one + ccp path, // not NULL and exists: use file format + file_format_t default_fform, // use this as default, if valid ff + bool allow_png, // true: allow PNG + file_format_t not_found // return this, if all other fails +) +{ + PRINT("GetImageFF(%s,%s,%s,%d,%s) %s\n", + GetNameFF(0,fform1), GetNameFF(0,fform2), GetNameFF(0,default_fform), + allow_png, GetNameFF(0,not_found), path ? path : "-" ); + + //--- analyze 'fform*' + + fform1 = IsImageFF(fform1,allow_png); + if ( fform1 != FF_UNKNOWN ) + return fform1; + + fform2 = IsImageFF(fform2,allow_png); + if ( fform2 != FF_UNKNOWN ) + return fform2; + + + //--- analyse path + + if ( path && *path ) + { + //--- analyse file content + + char buf[CHECK_FILE_SIZE]; + memset(buf,0,sizeof(buf)); + enumError err = LoadFILE(path,0,0,buf,sizeof(buf),2,0,false); + if (!err) + { +// [[analyse-magic]] + const file_format_t fform + = IsImageFF( GetByMagicFF(buf,sizeof(buf),0), allow_png ); + if ( fform != FF_UNKNOWN ) + return fform; + } + + //--- analyse file extension + + ccp point = strrchr(path,'.'); + if (point) + { + const file_format_t fform = IsImageFF(GetByNameFF(point+1),true); + if ( fform != FF_UNKNOWN ) + return fform; + } + + //--- analyse last directory + + ccp p1 = 0, p2 = path, ptr = path; + while (*ptr) + { + if ( *ptr++ == '/' || *ptr++ == '\\' ) + { + p1 = p2; + p2 = ptr; // position behind '/' + } + } + if (p1) + { + if (!strncasecmp(p1,"files/",6)) + return FF_BREFT_IMG; + if (!strncasecmp(p1,"Textures(NW4R)/",15)) + return FF_TEX; + } + } + + + //--- use default + + default_fform = IsImageFF(default_fform,allow_png); + return default_fform != FF_UNKNOWN ? default_fform : not_found; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// object name/type /////////////// +/////////////////////////////////////////////////////////////////////////////// + +ccp GetNewObjectName ( new_object_t no ) +{ + switch (no) + { + case NEWOBJ_OFF: return "OFF"; + case NEWOBJ_REPLACE: return "REPLACE"; + case NEWOBJ_SHRINK: return "SHRING"; + case NEWOBJ_GROW: return "GROW"; + default: return "?"; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void InitializeObjType ( object_type_t *ot, file_format_t fform ) +{ + DASSERT(ot); + memset(ot,0,sizeof(*ot)); + + ot->new_object = NEWOBJ_OFF; + ot->object_slots = OBJMGR_USUAL_SLOTS; + ot->max_object_id = OBJMGR_USUAL_MAX_OBJID; + + SetObjTypeByFF(ot,fform); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SetObjTypeByFF ( object_type_t *ot, file_format_t fform ) +{ + DASSERT(ot); + + switch(fform) + { + case FF_OBJFLOW: + case FF_OBJFLOW_TXT: + ot->is_objflow = true; + ot->is_geohit = false; + ot->is_kart = false; + ot->is_obj = false; + ot->row_size = 0x74; + ot->slot_offset = 2; + break; + + case FF_GH_ITEM: + case FF_GH_ITEM_TXT: + ot->is_objflow = false; + ot->is_geohit = true; + ot->is_kart = false; + ot->is_obj = false; + ot->row_size = 0x20; + ot->slot_offset = 4; + break; + + case FF_GH_IOBJ: + case FF_GH_IOBJ_TXT: + ot->is_objflow = false; + ot->is_geohit = true; + ot->is_kart = false; + ot->is_obj = true; + ot->row_size = 0x20; + ot->slot_offset = 4; + break; + + case FF_GH_KART: + case FF_GH_KART_TXT: + ot->is_objflow = false; + ot->is_geohit = true; + ot->is_kart = true; + ot->is_obj = false; + ot->row_size = 0x0a; + ot->slot_offset = 4; + break; + + case FF_GH_KOBJ: + case FF_GH_KOBJ_TXT: + ot->is_objflow = false; + ot->is_geohit = true; + ot->is_kart = true; + ot->is_obj = true; + ot->row_size = 0x0a; + ot->slot_offset = 4; + break; + + default: + ot->is_objflow = false; + ot->is_geohit = false; + ot->is_kart = false; + ot->is_obj = false; + ot->row_size = 0; + ot->slot_offset = 0; + break; + } + + ot->ref_offset = ot->slot_offset + ot->object_slots * ot->row_size; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool ScanObjType + ( object_type_t *ot, cvp data, uint data_size, uint file_size ) +{ + //--- check for valid data + + DASSERT(ot); + memset(ot,0,sizeof(*ot)); + if ( !data || data_size < 4 || file_size&1 ) + return false; + + const uint n_slot = be16(data); + if ( !n_slot || n_slot > OBJMGR_MAX_SLOTS ) + return false; + + + //--- determine possible file types + + file_format_t ff_std, ff_obj; + + const uint n_param = be16((u8*)data+2); + switch (n_param) + { + case 4: + ff_std = FF_GH_KART; + ff_obj = FF_GH_KOBJ; + ot->is_geohit = true; + ot->is_kart = true; + ot->row_size = 0x0a; + ot->slot_offset = 4; + break; + + case 15: + ff_std = FF_GH_ITEM; + ff_obj = FF_GH_IOBJ; + ot->is_geohit = true; + ot->row_size = 0x20; + ot->slot_offset = 4; + break; + + default: + ff_std = FF_OBJFLOW; + ff_obj = FF_UNKNOWN; + ot->is_objflow = true; + ot->row_size = 0x74; + ot->slot_offset = 2; + break; + } + + int max_ref_obj_id = OBJMGR_MAX_OBJID; + ot->ref_offset = ot->slot_offset + n_slot * ot->row_size; + if (file_size) + { + if ( file_size < ot->ref_offset + 2 ) + return false; + + max_ref_obj_id = ( file_size - ot->ref_offset ) / 2 - 1; + if ( max_ref_obj_id > OBJMGR_MAX_OBJID ) + max_ref_obj_id = OBJMGR_MAX_OBJID; + } + + + //--- determine max_object_id + + const u8 *bin, *bin0 = (u8*)data + ot->slot_offset; + uint slots_avail = ( (u8*)data + data_size - bin0 ) / ot->row_size; + if ( slots_avail > n_slot ) + slots_avail = n_slot; + + uint slot, max_obj_id = 0; + for ( slot = 0, bin = bin0; slot < slots_avail; slot++, bin += ot->row_size ) + { + uint obj_id = be16(bin); + if ( !obj_id || obj_id > max_ref_obj_id ) + return false; + + if ( max_obj_id < obj_id ) + max_obj_id = obj_id; + } + + ot->object_slots = n_slot; + ot->max_object_id = max_ref_obj_id; + + + //--- determine is_object + + if (ot->is_geohit) + { + if (ot->is_kart) + { + uint n7x = 0, n36 = 0; + for ( slot = 0, bin = bin0; slot < slots_avail; slot++, bin += ot->row_size ) + { + const geohit_bin_t *gb = (geohit_bin_t*)bin; + uint ip; + for ( ip = 0; ip < n_param; ip++ ) + { + const u16 p = ntohs(gb->param[ip]); + if ( p >= 7 ) + n7x++; + else if ( p >= 3 && p <= 6 ) + n36++; + } + } + ot->is_obj = n7x < n36; + PRINT("KART: N7x=%u, N36=%u -> obj=%u\n",n7x,n36,ot->is_obj); + } + else + { + uint n12 = 0, n36 = 0; + for ( slot = 0, bin = bin0; slot < slots_avail; slot++, bin += ot->row_size ) + { + const geohit_bin_t *gb = (geohit_bin_t*)bin; + uint ip; + for ( ip = 0; ip < n_param; ip++ ) + { + const u16 p = ntohs(gb->param[ip]); + if ( p >= 1 && p <= 2 ) + n12++; + else if ( p >= 3 && p <= 6 ) + n36++; + } + } + ot->is_obj = n12 < n36; + PRINT("ITEM: N12=%u, N36=%u -> obj=%u\n",n12,n36,ot->is_obj); + } + } + + + //--- terminate + + ot->fform = ot->is_obj ? ff_obj : ff_std; + + PRINT("ScanObjType(0x%x,0x%x) = %s\n", + data_size, file_size, LogObjType(ot) ); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +ccp LogObjType ( const object_type_t *ot ) +{ + DASSERT(ot); + if (!ot) + return "?"; + + char buf[100]; + const uint len + = snprintf(buf,sizeof(buf), + "%s[%s%s%s%s] rs=%u off:%x:%x %s,ns:%x,mo:%x", + GetNameFF(0,ot->fform), + ot->is_objflow ? "O" : "", + ot->is_geohit ? "G" : "", + ot->is_kart ? "K" : ot->is_objflow ? "" : "I", + ot->is_obj ? "o" : "", + ot->row_size, + ot->slot_offset, + ot->ref_offset, + GetNewObjectName(ot->new_object), + ot->object_slots, + ot->max_object_id ); + + return CopyCircBuf(buf,len+1); +} + +/////////////////////////////////////////////////////////////////////////////// + +file_format_t GetObjTypeFF ( const object_type_t *ot, bool want_text ) +{ + DASSERT(ot); + if (ot->is_objflow) + return want_text ? FF_OBJFLOW_TXT : FF_OBJFLOW; + + if (ot->is_geohit) + { + if (ot->is_kart) + { + return ot->is_obj + ? ( want_text ? FF_GH_KOBJ_TXT : FF_GH_KOBJ ) + : ( want_text ? FF_GH_KART_TXT : FF_GH_KART ); + } + else + { + return ot->is_obj + ? ( want_text ? FF_GH_IOBJ_TXT : FF_GH_IOBJ ) + : ( want_text ? FF_GH_ITEM_TXT : FF_GH_ITEM ); + } + } + + return FF_UNKNOWN; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// file class /////////////// +/////////////////////////////////////////////////////////////////////////////// + +patch_file_t PATCH_FILE_MODE = PFILE_M_DEFAULT | PFILE_F_DEFAULT; +int disable_patch_on_load = 0; // if >0: disable patching after loading + +/////////////////////////////////////////////////////////////////////////////// + +static const KeywordTab_t opt_patch_tab[] = +{ + { 0, "CLEAR", "RESET", PFILE_M_ALL | PFILE_F_HIDE }, + { PFILE_M_DEFAULT, "DEFAULT", 0, PFILE_M_ALL | PFILE_F_HIDE }, + { PFILE_M_ALL, "ALL", 0, PFILE_M_ALL | PFILE_F_HIDE }, + { PFILE_F_LOG, "LOG", 0, 0 }, + { PFILE_F_LOG_ALL, "LOG-ALL", "LOGALL", 0 }, + + { PFILE_COURSE_KMP, "KMP", 0, 0 }, + + { PFILE_M_KCL, "ALL-KCL", "ALLKCL", PFILE_M_KCL }, + { PFILE_COURSE_KCL, "KCL", 0, 0 }, + { PFILE_OTHER_KCL, "OTHER-KCL", "OTHERKCL", 0 }, + + { PFILE_M_BRRES, "ALL-BRRES", "ALLBRRES", PFILE_M_BRRES }, + { PFILE_MODEL, "MODEL", 0, 0 }, + { PFILE_MINIMAP, "MINIMAP", "MAP", 0 }, + { PFILE_VRCORN, "VRCORN", 0, 0 }, + { PFILE_OTHER_BRRES, "OTHER-BRRES", "OTHERBRRES", 0 }, + + { PFILE_M_TRACK, "TRACK", 0, PFILE_M_TRACK | PFILE_F_HIDE }, + + { 0,0,0,0 } +}; + +/////////////////////////////////////////////////////////////////////////////// + +int ScanOptPatchFile ( ccp arg ) +{ + if (!arg) + return 0; + + static bool done = false; + if (!done) + { + done = true; + if ( *arg != '+' && *arg != '-' ) + PATCH_FILE_MODE = 0; + } + + s64 stat = ScanKeywordList(arg,opt_patch_tab,0,true,0,PATCH_FILE_MODE, + "Option --patch-file",ERR_SYNTAX); + if ( stat != -1 ) + { + PATCH_FILE_MODE = stat & PFILE_M_ALL; + return 0; + } + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SetPatchFileModeReadonly() +{ + // switch to readonly default flags (silent), if not set by user + + if ( PATCH_FILE_MODE & PFILE_F_DEFAULT ) + PATCH_FILE_MODE = PFILE_M_SILENT; +} + +/////////////////////////////////////////////////////////////////////////////// + +uint PrintFileClassInfo ( char *buf, uint bufsize, patch_file_t mode ) +{ + DASSERT(buf); + DASSERT(bufsize>10); + char *dest = buf; + char *end = buf + bufsize - 1; + + mode = mode & PFILE_M_ALL | PFILE_F_HIDE; + patch_file_t mode1 = mode; + + const KeywordTab_t *ct; + for ( ct = opt_patch_tab; ct->name1 && dest < end; ct++ ) + { + if ( ct->opt & PFILE_F_HIDE ) + continue; + + if ( ct->opt ? (mode & ct->opt) == ct->id : mode & ct->id ) + { + if ( dest > buf ) + *dest++ = ','; + dest = StringCopyE(dest,end,ct->name1); + mode &= ~(ct->id|ct->opt); + } + } + + if ( mode1 == (PFILE_M_DEFAULT|PFILE_F_HIDE) ) + dest = StringCopyE(dest,end," (default)"); + else if (!mode1) + dest = StringCopyE(dest,end,"(none)"); + + *dest = 0; + return dest-buf; +} + +/////////////////////////////////////////////////////////////////////////////// + +ccp GetFileClassInfo() +{ + static char buf[100] = {0}; + if (!*buf) + PrintFileClassInfo(buf,sizeof(buf),PATCH_FILE_MODE); + return buf; +} + +/////////////////////////////////////////////////////////////////////////////// + +patch_file_t GetFileClass +( + file_format_t fform, // not FF_UNKNOWN: check file type + ccp fname // NULL or file name to check +) +{ + if ( !fname || !*fname ) + return PFILE_NO_FILENAME; + + ccp slash = strrchr(fname,'/'); + if (slash) + fname = slash+1; + + if ( fform == FF_UNKNOWN && !strcasecmp(fname,"course.txt") ) + return PFILE_COURSE; + + if ( fform == FF_UNKNOWN || fform == FF_KMP || fform == FF_KMP_TXT ) + { + if ( !strcasecmp(fname,"course.kmp") + || !strcasecmp(fname,"course.txt") + || !strcasecmp(fname,"course.kmp.txt") ) + { + return PFILE_COURSE_KMP; + } + } + + if ( fform == FF_UNKNOWN || fform == FF_KCL || fform == FF_KCL_TXT ) + { + if ( !strcasecmp(fname,"course.kcl") + || !strcasecmp(fname,"course.txt") + || !strcasecmp(fname,"course.kcl.txt") ) + { + return PFILE_COURSE_KCL; + } + + if ( fform == FF_KCL || fform == FF_KCL_TXT ) + return PFILE_OTHER_KCL; + } + + if ( fform == FF_UNKNOWN || fform == FF_BRRES ) + { + if ( !strcasecmp(fname,"course_model.brres") + || !strcasecmp(fname,"course_d_model.brres") + || !strcasecmp(fname,"course_model.d") + || !strcasecmp(fname,"course_d_model.d") ) + { + return PFILE_MODEL; + } + + if ( !strcasecmp(fname,"map_model.brres") + || !strcasecmp(fname,"map_model.d") ) + { + return PFILE_MINIMAP; + } + + if ( !strcasecmp(fname,"vrcorn_model.brres") + || !strcasecmp(fname,"vrcorn_model.d") ) + { + return PFILE_VRCORN; + } + + if ( fform == FF_BRRES ) + return PFILE_OTHER_BRRES; + } + + return PFILE_UNKNOWN; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool PatchFileClass +( + file_format_t fform, // not FF_UNKNOWN: check file type + ccp fname // NULL or file name to check +) +{ + const patch_file_t fc = GetFileClass(fform,fname); + return 0 != ( fc & PATCH_FILE_MODE ); +} + +/////////////////////////////////////////////////////////////////////////////// + +int patch_action_log_disabled = 0; + +bool PATCH_ACTION_LOG ( ccp action, ccp object, ccp format, ... ) +{ + #if HAVE_PRINT + { + char buf[200]; + snprintf(buf,sizeof(buf),">>>[PATCH]<<< %s",format); + va_list arg; + va_start(arg,format); + PRINT_ARG_FUNC(buf,arg); + va_end(arg); + } + #endif + + if ( patch_action_log_disabled <= 0 + && ( verbose > 2 || PATCH_FILE_MODE & (PFILE_F_LOG|PFILE_F_LOG_ALL) )) + { + fflush(stdout); + fprintf(stdlog," %s>[PATCH] %-6s %-7s%s ", + colset->heading, action, object, colset->info ); + if (format) + { + va_list arg; + va_start(arg,format); + vfprintf(stdlog,format,arg); + va_end(arg); + fputs(colset->reset,stdlog); + } + else + fprintf(stdlog,"%s\n",colset->reset); + fflush(stdlog); + return true; + } + + return false; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// auto add support /////////////// +/////////////////////////////////////////////////////////////////////////////// + +bool SetupAutoAdd() +{ + static bool done = false; + if (!done) + { + done = true; + + ccp *spath = search_path; + ccp *apath = auto_add_path; + while (*spath) + { + char path_buf[PATH_MAX]; + ccp path = PathCatPP(path_buf,sizeof(path_buf),*spath++,"auto-add"); + if (IsDirectory(path,0)) + { + PRINT("AUTO-PATH[%u] = %s\n",(int)(apath-auto_add_path),path); + *apath++ = STRDUP(path); + DASSERT( apath - auto_add_path + < sizeof(auto_add_path)/sizeof(*auto_add_path) ); + } + } + *apath = 0; + } + + return auto_add_path[0] != 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool DefineAutoAddPath ( ccp path ) +{ + SetupAutoAdd(); + if (path) + { + uint i; + for ( i = 0; i < sizeof(auto_add_path)/sizeof(*auto_add_path); i++ ) + { + FreeString(auto_add_path[i]); + auto_add_path[i] = 0; + } + auto_add_path[0] = STRDUP(path); + } + + return auto_add_path[0] != 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +s64 FindAutoAdd ( ccp fname, ccp ext, char *buf, uint buf_size ) +{ + DASSERT(fname); + DASSERT(buf); + DASSERT( buf_size > 100 ); + + if (!SetupAutoAdd()) + return -1; + + ccp *apath = auto_add_path; + while (*apath) + { + PathCatBufPPE(buf,buf_size,*apath++,fname,ext); + struct stat st; + if ( !stat(buf,&st) && S_ISREG(st.st_mode) ) + { + noPRINT("FindAutoAdd(%s) -> %llu %s\n",fname,(u64)st.st_size,buf); + return st.st_size; + } + } + + noPRINT("FindAutoAdd(%s) FAILED\n",fname); + *buf = 0; + return -1; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// file format atribs /////////////// +/////////////////////////////////////////////////////////////////////////////// + +const file_type_t * GetFileTypeFF ( file_format_t ff ) +{ + return (uint)ff < FF_N ? FileTypeTab + ff : 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +ff_attrib_t GetAttribFF ( file_format_t ff ) +{ + return (uint)ff < FF_N + ? FileTypeTab[ff].attrib + : (ff_attrib_t)0; +} + +/////////////////////////////////////////////////////////////////////////////// + +ff_attrib_t GetAttribByMagicFF +( + const void * data, // valid pointer to data + uint data_size, // size of 'data' + file_format_t *ff // not NULL: store file format here +) +{ +// [[analyse-magic]] + const file_format_t fform = GetByMagicFF(data,data_size,0); + if (ff) + *ff = fform; + return GetAttribFF(fform); +} + +/////////////////////////////////////////////////////////////////////////////// + +int GetVersionFF +( + // returns -1 for unknown, or the version number + + file_format_t fform, // valid firl format + const void *data, // data pointer + uint data_size, // size of available data + char *res_suffix // not NULL: return 1 char as suffix, or 0 +) +{ + if (res_suffix) + *res_suffix = 0; + + switch(fform) + { + #if 0 + case FF_YAZ0: + case FF_XYZ: + return 0; + + case FF_YAZ1: + return 1; + + case FF_BZ: + return ((u8*)data)[3] - 'a' + 1; + #endif + + case FF_BREFF: + case FF_BREFT: + if ( data_size >= 0x08 ) + return be16((u8*)data+0x06); + break; + + case FF_CHR: + case FF_CLR: + case FF_MDL: + case FF_PAT: + case FF_SCN: + case FF_SHP: + case FF_SRT: + case FF_TEX: + case FF_TEX_CT: // ??? [[CTCODE]] + if ( data_size >= 0x0c ) + return be32((u8*)data+0x08); + break; + + #if 0 + case FF_KMP: + if ( data_size >= 0x10 ) + return be32((u8*)data+0x0c); + break; + #endif + + case FF_BMG: // use encoding + if ( data_size >= sizeof(bmg_header_t) ) + { + const bmg_header_t *bh = (bmg_header_t*)data; + return ntohl(bh->n_sections)*10 + bh->encoding; + } + break; + + case FF_LE_BIN: + { + const int vers = be32((u8*)data+4); + if (res_suffix) + { + if ( vers == 4 && data_size >= sizeof(le_binary_head_v4_t) ) + { + const le_binary_head_v4_t *leh = (le_binary_head_v4_t*)data; + *res_suffix = leh->debug == 'R' + ? toupper(leh->region) + : tolower(leh->region); + } + } + return vers; + } + break; + + default: + //printf("x[%u]\n",fform); + { + const ff_attrib_t att = GetAttribFF(fform); + if ( att & FFT_BRSUB && data_size >= 0x0c ) + return be32((u8*)data+0x08); + } + break; + } + return -1; +} + +/////////////////////////////////////////////////////////////////////////////// + +int GetVersionByMagic +( + // returns -1 for unknown, or the version number + + const void *data, // data pointer + uint data_size, // size of available data + char *res_suffix // not NULL: return 1 char as suffix, or 0 +) +{ +// [[analyse-magic]] + return GetVersionFF( GetByMagicFF(data,data_size,0), + data, data_size, res_suffix ); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +bool IsBRSUB ( file_format_t ff ) +{ + return (uint)ff < FF_N && FileTypeTab[ff].attrib & FFT_BRSUB; +} + +//----------------------------------------------------------------------------- + +bool IsByMagicBRSUB ( const void * data, uint data_size ) +{ + return GetAttribByMagicFF(data,data_size,0) & FFT_BRSUB; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool IsArchiveFF ( file_format_t ff ) +{ + return (uint)ff < FF_N && FileTypeTab[ff].attrib & FFT_ARCHIVE; +} + +//----------------------------------------------------------------------------- + +bool IsTextFF ( file_format_t ff ) +{ + return (uint)ff < FF_N && FileTypeTab[ff].attrib & FFT_TEXT; +} + +//----------------------------------------------------------------------------- + +bool IsGeoHitKartFF ( file_format_t ff ) +{ + return ff == FF_GH_KART + || ff == FF_GH_KART_TXT + || ff == FF_GH_KOBJ + || ff == FF_GH_KOBJ_TXT; +} + +//----------------------------------------------------------------------------- + +bool IsGeoHitObjFF ( file_format_t ff ) +{ + return ff == FF_GH_IOBJ + || ff == FF_GH_IOBJ_TXT + || ff == FF_GH_KOBJ + || ff == FF_GH_KOBJ_TXT; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool IsYazFF ( file_format_t ff ) +{ + return ff == FF_YAZ0 || ff == FF_YAZ1 || ff == FF_XYZ; +} + +//----------------------------------------------------------------------------- + +int GetYazVersionFF ( file_format_t ff ) +{ + switch (ff) + { + case FF_YAZ0: + case FF_XYZ: + return 0; + + case FF_YAZ1: + return 1; + + default: + return -1; + } +} + +//----------------------------------------------------------------------------- + +file_format_t GetYazFF ( file_format_t ff ) +{ + return fform_compr_force && ( ff == FF_YAZ0 || ff == FF_YAZ1 || ff == FF_XYZ ) + ? fform_compr_force + : IsYazFF(ff) + ? ff + : FF_YAZ0; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool IsCompressedFF ( file_format_t ff ) +{ + return (uint)ff < FF_N && FileTypeTab[ff].attrib & FFT_COMPRESS; +} + +//----------------------------------------------------------------------------- + +file_format_t GetCompressedFF ( file_format_t ff ) +{ + return fform_compr_force + && (uint)ff < FF_N + && FileTypeTab[ff].attrib & FFT_COMPRESS + ? fform_compr_force + : ff; +} + +//----------------------------------------------------------------------------- + +void SetCompressionFF +( + file_format_t ff_arch, // if >0: set 'opt_fform' + file_format_t ff_compr // if >0: set 'fform_compr' + // and 'fform_compr_force' +) +{ + if ( ff_arch > 0 ) + opt_fform = ff_arch; + + if ( ff_compr > 0 ) + fform_compr = fform_compr_force = ff_compr; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// scan setup files /////////////// +/////////////////////////////////////////////////////////////////////////////// + +size_t ResetSetup +( + SetupDef_t * list // object list terminated with an element 'name=NULL' +) +{ + DASSERT(list); + size_t count; + for ( count = 0; list->name; list++, count++ ) + { + FreeString(list->param); + list->param = 0; + list->value = 0; + } + return count; +} + +/////////////////////////////////////////////////////////////////////////////// + +static char * trim_line ( char * ptr, char ** begin ) +{ + DASSERT(ptr); + + while ( *ptr > 0 && *ptr <= ' ' ) + ptr++; + + char *start = ptr; + if (begin) + *begin = ptr; + + while (*ptr) + ptr++; + + ptr--; + while ( *ptr > 0 && *ptr <= ' ' && ptr > start ) + ptr--; + *++ptr = 0; + return ptr; +} + +//----------------------------------------------------------------------------- + +enumError ScanSetupFile +( + SetupDef_t * sdef, // object list terminated with an element 'name=NULL' + ListDef_t * ldef, // NULL or object list terminated + // with an element 'name=NULL' + ccp path1, // filename of text file, part 1 + ccp path2, // filename of text file, part 2 + SubDir_t * sdir, // not NULL: use it and ignore 'path1' + bool silent // true: suppress error message if file not found +) +{ + DASSERT(sdef); + DASSERT(path1||path2); + + ResetSetup(sdef); + + char pathbuf[PATH_MAX]; + ccp path; + FILE * f; + + if (sdir) + { + path = path2; + f = OpenSubFile(sdir,MemByString(path2)); + } + else + { + path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2); + f = fopen(path,"rb"); + } + + if (!f) + { + if (!silent) + ERROR1(ERR_CANT_OPEN,"Can't open file: %s\n",path); + return ERR_CANT_OPEN; + } + + bool section_found = false; + uint linenum = 0; + while (fgets(iobuf,sizeof(iobuf)-1,f)) + { + char * ptr = iobuf; + + //----- check BOM + + if (!linenum++) + ptr += GetTextBOMLen(ptr,3); + + //----- skip spaces + + while ( *ptr > 0 && *ptr <= ' ' ) + ptr++; + + if ( *ptr == '!' || *ptr == '#' ) + continue; + + if ( *ptr == '[' ) + { + ptr = trim_line(ptr,0); + if ( ptr[-1] == ']' ) + { + section_found = true; + break; + } + continue; + } + + //----- find end of name + + char * name = ptr; + while ( isalnum((int)*ptr) || *ptr == '-' ) + ptr++; + if (!*ptr) + continue; + + char * name_end = ptr; + + //----- skip spaces and check for '=' + + while ( *ptr > 0 && *ptr <= ' ' ) + ptr++; + + if ( *ptr != '=' ) + continue; + + *name_end = 0; + + //----- check if name is a known parameter + + SetupDef_t * item; + for ( item = sdef; item->name; item++ ) + if (!strcmp(item->name,name)) + break; + if (!item->name) + continue; + + //----- trim parameter + + ptr++; // skip '=' + char * param; + ptr = trim_line(ptr,¶m); + + item->param = STRDUP(param); + if (item->factor) + { + ScanSizeU64(&item->value,param,1,1,0); + if ( item->factor > 1 ) + item->value = item->value / item->factor * item->factor; + } + } + + while ( section_found && ldef ) + { + char * beg; + char * ptr = trim_line(iobuf,&beg) - 1; + if ( *beg != '[' || *ptr != ']' ) + break; + + *ptr = 0; + beg++; + TRACE("SECTION |%s|\n",beg); + StringField_t *sf = 0; + bool append = false; + ListDef_t * ld; + for ( ld = ldef; ld->name; ld++ ) + if (!strcmp(beg,ld->name)) + { + sf = ld->sf; + append = ld->append; + break; + } + + while (fgets(iobuf,sizeof(iobuf)-1,f)) + { + char * ptr = trim_line(iobuf,&beg); + if ( *beg == '[' ) + { + if ( ptr[-1] == ']' ) + break; + continue; + } + + if ( *beg == '.' && beg[1] == '/' ) + beg += 2; + if ( !sf || !*beg || *beg == '#' ) + continue; + TRACE("INSERT |%s|\n",beg); + if (append) + AppendStringField(sf,beg,false); + else + InsertStringField(sf,beg,false); + } + } + + fclose(f); + return ERR_OK; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// SetupParam_t /////////////// +/////////////////////////////////////////////////////////////////////////////// + +void InitializeSetupParam ( SetupParam_t * sp ) +{ + DASSERT(sp); + memset(sp,0,sizeof(*sp)); + + InitializeStringField(&sp->include_pattern); + InitializeStringField(&sp->include_name); + InitializeStringField(&sp->exclude_pattern); + InitializeStringField(&sp->exclude_name); + InitializeFormatField(&sp->encode_list); + InitializeStringField(&sp->create_list); + InitializeFormatField(&sp->order_list); +} + +/////////////////////////////////////////////////////////////////////////////// + +void ResetSetupParam ( SetupParam_t * sp ) +{ + if (sp) + { + ResetStringField(&sp->include_pattern); + ResetStringField(&sp->include_name); + ResetStringField(&sp->exclude_pattern); + ResetStringField(&sp->exclude_name); + ResetFormatField(&sp->encode_list); + ResetStringField(&sp->create_list); + ResetFormatField(&sp->order_list); + FreeString(sp->object_name); + + InitializeSetupParam(sp); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +static void SetupParamOptions +( + SetupParam_t * sp // setup param to scan +) +{ + //--- use options? + + if ( opt_fform != FF_UNKNOWN ) + sp->fform_arch = opt_fform; + if ( opt_compr_mode ) + { + sp->compr_mode = opt_compr_mode; + if ( opt_compr_mode < 0 ) + sp->fform_file = FF_UNKNOWN; + } + + if ( sp->fform_arch == FF_UNKNOWN ) + sp->fform_arch = FF_U8; + if ( sp->fform_file == FF_UNKNOWN ) + sp->fform_file = opt_compr_mode < 0 ? sp->fform_arch : fform_compr; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError ScanSetupParam +( + SetupParam_t * sp, // setup param to scan + bool use_options, // true: use options to override values + ccp path1, // NULL or part #1 of path + ccp path2, // NULL or part #2 of path + SubDir_t * sdir, // not NULL: use it and ignore 'path1' + bool silent // true: suppress printing of error messages +) +{ + DASSERT(sp); + memset(sp,0,sizeof(*sp)); + + SetupDef_t setup_def[] = + { + { "object-name", 0 }, + { "object-id", 1 }, + { "archive-format", 0 }, + { "file-format", 0 }, + { "version", 1 }, + { "have-pt-dir", 1 }, + { "min-data-offset", 1 }, + { "max-data-offset", 1 }, + { "data-align", 1 }, + {0,0} + }; + + if (!opt_ignore_setup) + { + StringField_t encode_helper, order_helper; + InitializeStringField(&encode_helper); + InitializeStringField(&order_helper); + + ListDef_t list_def[] = + { + { "include-pattern", 0, &sp->include_pattern }, + { "include", 0, &sp->include_name }, + { "exclude-pattern", 0, &sp->exclude_pattern }, + { "exclude", 0, &sp->exclude_name }, + { "encode", 0, &encode_helper }, + { "create", 0, &sp->create_list }, + { "order", 1, &order_helper }, + {0,0} + }; + + enumError err = ScanSetupFile(setup_def,list_def,path1,path2,sdir,silent); + if (err) + { + if (use_options) + SetupParamOptions(sp); + return err; + } + + ccp *ptr = encode_helper.field, *end; + for ( end = ptr + encode_helper.used; ptr < end; ptr++ ) + { + InsertFormatField(&sp->encode_list,*ptr,true,true,0); + *ptr = 0; + } + ResetStringField(&encode_helper); + + ptr = order_helper.field; + for ( end = ptr + order_helper.used; ptr < end; ptr++ ) + { + AppendFormatField(&sp->order_list,*ptr,true,true); + *ptr = 0; + } + ResetStringField(&order_helper); + } + + + //--- object-name + + SetupDef_t * sd = setup_def; + noPRINT("object-name=%s\n",sd->param); + if (sd->param) + { + sp->object_name = sd->param; + sd->param = 0; + } + + //--- object-id + + sd++; + noPRINT("object-id=%s\n",sd->param); + if (sd->param) + { + sp->object_id_used = true; + sp->object_id = sd->value; + } + + //--- archive-format + + sd++; + noPRINT("ff-arch=%s\n",sd->param); + if (sd->param) + sp->fform_arch = GetByNameFF(sd->param); + + + //--- file-format + + sd++; + noPRINT("ff-file=%s\n",sd->param); + if ( sd->param && *sd->param ) + { + sp->fform_file = GetByNameFF(sd->param); + if (IsCompressedFF(sp->fform_file)) + { + sp->compr_mode = 1; + sp->fform_file = GetCompressedFF(sp->fform_file); + } + else + { + sp->fform_file = sp->fform_arch; + sp->compr_mode = -1; + } + } + + //--- version + + sd++; + noPRINT("version=%s\n",sd->param); + if (sd->param) + sp->version = sd->value; + + //--- have-pt-dir + + sd++; + noPRINT("pt-dir=%s\n",sd->param); + if (sd->param) + sp->have_pt_dir = sd->value > 0 ? 1 : -1; + + //--- min-data-size + + sd++; + noPRINT("pt-dir=%s\n",sd->param); + if (sd->param) + sp->min_data_off = sd->value; + + //--- max-data-size + + sd++; + noPRINT("pt-dir=%s\n",sd->param); + if (sd->param) + sp->max_data_off = sd->value; + + //--- data-align + + sd++; + noPRINT("pt-dir=%s\n",sd->param); + if (sd->param) + sp->data_align = sd->value; + + //--- terminate + + if (use_options) + SetupParamOptions(sp); + ResetSetup(setup_def); + return ERR_OK; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// sub file system /////////////// +/////////////////////////////////////////////////////////////////////////////// + +void ResetSubDirList ( SubDirList_t *sdl ) +{ + if (sdl) + { + uint i; + for ( i = 0; i < sdl->used; i++ ) + ResetSubDir(sdl->list[i]); + FREE(sdl->list); + InitializeSubDirList(sdl); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void ResetSubDir ( SubDir_t *sd ) +{ + if (sd) + { + if (sd->dname_alloced) + FreeString(sd->dname); + ResetSubFileList(&sd->file); + ResetSubDirList(&sd->dir); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void ResetSubFileList ( SubFileList_t *sfl ) +{ + if (sfl) + { + uint i; + for ( i = 0; i < sfl->used; i++ ) + ResetSubFile(sfl->list[i]); + FREE(sfl->list); + InitializeSubFileList(sfl); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void ResetSubFile ( SubFile_t *sf ) +{ + if (sf) + { + if (sf->fname_alloced) + FreeString(sf->fname); + if (sf->data_alloced) + FREE(sf->data); + InitializeSubFile(sf); + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +void DumpSubDir ( FILE *f, int indent, const SubDir_t *sd ) +{ + DASSERT(f); + DASSERT(sd); + + indent = NormalizeIndent(indent); + fprintf(f,"%*sDIR %s/\n",indent,"",sd->dname); + + indent += 2; + DumpSubFileList(f,indent,&sd->file); + DumpSubDirList(f,indent,&sd->dir); +} + +/////////////////////////////////////////////////////////////////////////////// + +void DumpSubDirList ( FILE *f, int indent, const SubDirList_t *sdl ) +{ + DASSERT(f); + DASSERT(sdl); + + uint i; + for ( i = 0; i < sdl->used; i++ ) + DumpSubDir(f,indent,sdl->list[i]); +} + +/////////////////////////////////////////////////////////////////////////////// + +void DumpSubFileList ( FILE *f, int indent, const SubFileList_t *sfl ) +{ + DASSERT(f); + DASSERT(sfl); + indent = NormalizeIndent(indent); + + uint i; + for ( i = 0; i < sfl->used; i++ ) + { + const SubFile_t *sf = sfl->list[i]; + fprintf(f,"%*s> %s [%u,t=%d]\n",indent,"",sf->fname,sf->size,sf->type); + } +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static uint FindSubDirHelper + ( const SubDirList_t *sdl, mem_t path, bool * p_found ) +{ + int beg = 0; + if ( sdl && path.len ) + { + int end = sdl->used - 1; + while ( beg <= end ) + { + const uint idx = (beg+end)/2; + int stat = StrCmpMem(path,sdl->list[idx]->dname); + if ( stat < 0 ) + end = idx - 1 ; + else if ( stat > 0 ) + beg = idx + 1; + else + { + noPRINT("InsertSubDirHelper(%.*s) FOUND=%d/%d/%d\n", + path.len, path.ptr, idx, sdl->used, sdl->size ); + if (p_found) + *p_found = true; + return idx; + } + } + } + + noPRINT("InsertSubDirHelper(%.*s) failed=%d/%d/%d\n", + path.len, path.ptr, beg, sdl->used, sdl->size ); + + if (p_found) + *p_found = false; + return beg; +} + +/////////////////////////////////////////////////////////////////////////////// + +static SubDir_t * InsertSubDirHelper ( SubDirList_t * sdl, int idx ) +{ + DASSERT(sdl); + DASSERT( sdl->used <= sdl->size ); + noPRINT("+SF: %u/%u/%u\n",idx,sdl->used,sdl->size); + if ( sdl->used == sdl->size ) + { + sdl->size = 3*sdl->size/2 + 0x20; + sdl->list = REALLOC(sdl->list,sizeof(*sdl->list)*sdl->size); + } + DASSERT( idx <= sdl->used ); + SubDir_t ** dest = sdl->list + idx; + memmove(dest+1,dest,(sdl->used-idx)*sizeof(*dest)); + sdl->used++; + + *dest = MALLOC(sizeof(**dest)); + InitializeSubDir(*dest); + return *dest; +} + +/////////////////////////////////////////////////////////////////////////////// + +SubDir_t * InsertSubDir ( SubDir_t *sd, mem_t path, bool *new_dir ) +{ + DASSERT(sd); + ccp ptr = path.ptr; + ccp end = ptr + path.len; + + while ( ptr < end ) + { + while ( ptr < end && *ptr == '/' ) + ptr++; + ccp start = ptr; + while ( ptr < end && *ptr && *ptr != '/' ) + ptr++; + noPRINT("\t\tDIR: |%.*s|\n",(int)(ptr-start),start); + + const uint len = ptr - start; + if (!len) + continue; + + bool found; + const uint idx = FindSubDirHelper(&sd->dir,MemByStringS(start,len),&found); + if (found) + { + noPRINT("\t-> DIR FOUND\n"); + sd = sd->dir.list[idx]; + } + else + { + noPRINT("\t\tDIR NOT FOUND\n"); + sd = InsertSubDirHelper(&sd->dir,idx); + sd->dname = MEMDUP(start,len); + sd->dname_alloced = true; + } + } + + return sd; +} + +/////////////////////////////////////////////////////////////////////////////// + +SubDir_t * FindSubDir ( const SubDir_t *sd, mem_t path ) +{ + if ( !sd || !path.len ) + return 0; + + ccp ptr = path.ptr; + ccp end = ptr + path.len; + + while ( ptr < end ) + { + while ( ptr < end && *ptr == '/' ) + ptr++; + ccp start = ptr; + while ( ptr < end && *ptr && *ptr != '/' ) + ptr++; + + const uint len = ptr - start; + if (!len) + continue; + + bool found; + const uint idx = FindSubDirHelper(&sd->dir,MemByStringS(start,len),&found); + noPRINT("\t\tDIR: |%.*s| %s\n",len,start,found?"FOUND":""); + if (!found) + return 0; + + sd = sd->dir.list[idx]; + } + return (SubDir_t*)sd; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool RemoveSubDir ( SubDir_t *sd, mem_t path ) +{ + // [[2do]] ??? + return false; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static uint FindSubFileHelper + ( const SubFileList_t *sfl, mem_t path, bool * p_found ) +{ + int beg = 0; + if ( sfl && path.len ) + { + int end = sfl->used - 1; + while ( beg <= end ) + { + const uint idx = (beg+end)/2; + int stat = StrCmpMem(path,sfl->list[idx]->fname); + if ( stat < 0 ) + end = idx - 1 ; + else if ( stat > 0 ) + beg = idx + 1; + else + { + noPRINT("InsertSubFileHelper(%.*s) FOUND=%d/%d/%d\n", + path.len, path.ptr, idx, sfl->used, sfl->size ); + if (p_found) + *p_found = true; + return idx; + } + } + } + + noPRINT("InsertSubFileHelper(%.*s) failed=%d/%d/%d\n", + path.len, path.ptr, beg, sfl->used, sfl->size ); + + if (p_found) + *p_found = false; + return beg; +} + +/////////////////////////////////////////////////////////////////////////////// + +static SubFile_t * InsertSubFileHelper ( SubFileList_t * sfl, int idx ) +{ + DASSERT(sfl); + DASSERT( sfl->used <= sfl->size ); + noPRINT("+SF: %u/%u/%u\n",idx,sfl->used,sfl->size); + if ( sfl->used == sfl->size ) + { + sfl->size = 3*sfl->size/2 + 0x20; + sfl->list = REALLOC(sfl->list,sizeof(*sfl->list)*sfl->size); + } + DASSERT( idx <= sfl->used ); + SubFile_t ** dest = sfl->list + idx; + memmove(dest+1,dest,(sfl->used-idx)*sizeof(*dest)); + sfl->used++; + + *dest = MALLOC(sizeof(**dest)); + InitializeSubFile(*dest); + return *dest; +} + +/////////////////////////////////////////////////////////////////////////////// + +SubFile_t * InsertSubFile ( SubDir_t *sd, mem_t path, bool *new_file ) +{ + DASSERT(sd); + ccp beg = path.ptr; + ccp end = beg + path.len; + ccp ptr = end; + + while ( ptr > beg && *ptr != '/' ) + ptr--; + if ( ptr > beg ) + { + sd = InsertSubDir(sd,MemByStringE(beg,ptr),0); + if ( ptr < end ) + ptr++; + } + DASSERT(sd); + + noPRINT("\t-> FILE: |%.*s|\n",(int)(end-ptr),ptr); + + const uint len = end-ptr; + bool found; + const uint idx = FindSubFileHelper(&sd->file,MemByStringS(ptr,len),&found); + if (found) + { + noPRINT("\t-> FILE FOUND\n"); + if (new_file) + *new_file = false; + return sd->file.list[idx]; + } + + noPRINT("\t\tFILE NOT FOUND\n"); + if (new_file) + *new_file = true; + + SubFile_t *sf = InsertSubFileHelper(&sd->file,idx); + sf->fname = MEMDUP(ptr,len); + sf->fname_alloced = true; + return sf; +} + +/////////////////////////////////////////////////////////////////////////////// + +SubFile_t * FindSubFile ( const SubDir_t *sd, mem_t path ) +{ + if ( !sd || !path.len ) + return 0; + + ccp beg = path.ptr; + ccp end = beg + path.len; + ccp ptr = end; + + while ( ptr > beg && *ptr != '/' ) + ptr--; + if ( ptr > beg ) + { + sd = FindSubDir(sd,MemByStringE(beg,ptr)); + if (!sd) + return 0; + if ( ptr < end ) + ptr++; + } + DASSERT(sd); + + const uint len = end - ptr; + bool found; + const uint idx = FindSubFileHelper(&sd->file,MemByStringS(ptr,len),&found); + noPRINT("\t\tFILE: |%.*s| %s\n",len,ptr,found?"FOUND":""); + return found ? sd->file.list[idx] : 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool RemoveSubFile ( SubDir_t *sd, mem_t path ) +{ + // [[2do]] ??? + return false; +} + +/////////////////////////////////////////////////////////////////////////////// + +FILE * OpenSubFile ( const SubDir_t *sd, mem_t path ) +{ + #ifdef __APPLE__ + ERROR0(ERR_FATAL,"OpenSubFile() not supported by MAC version."); + return 0; + #else + SubFile_t *sf = FindSubFile(sd,path); + return sf && sf->data ? fmemopen(sf->data,sf->size,"rb") : 0; + #endif +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static int IterateSubDirHelper ( SubDir_t *sd, SubFileIterator_t *sfi ) +{ + DASSERT(sd); + DASSERT(sfi); + + char *path_end = sfi->path + sizeof(sfi->path) - 1; + char *path_old = sfi->path_ptr; + char *path_ptr = StringCopyE(path_old,path_end,sd->dname); + + int stat = 0; + if (sfi->func) + { + sfi->path_ptr = path_ptr; + const int xstat = sfi->func(sfi,1,sd,0); + if ( xstat < 0 ) + return xstat; + if ( stat < xstat ) + stat = xstat; + } + + sfi->depth++; + char *path_file = path_ptr; + if ( sd->dname && *sd->dname && path_file < path_end ) + *path_file++ = '/'; + + uint i; + for ( i = 0; i < sd->file.used; i++ ) + { + SubFile_t *sf = sd->file.list[i]; + sfi->path_ptr = StringCopyE(path_file,path_end,sf->fname); + if ( sfi->func) + { + const int xstat = sfi->func(sfi,0,sd,sf); + if ( xstat < 0 ) + return xstat; + if ( stat < xstat ) + stat = xstat; + } + } + + sfi->path_ptr = path_file; + for ( i = 0; i < sd->dir.used; i++ ) + { + const int xstat = IterateSubDirHelper(sd->dir.list[i],sfi); + if ( xstat < 0 ) + return xstat; + if ( stat < xstat ) + stat = xstat; + } + + sfi->depth--; + *path_ptr = 0; + + if ( sfi->func) + { + sfi->path_ptr = path_ptr; + const int xstat = sfi->func(sfi,2,sd,0); + if ( xstat < 0 ) + return xstat; + if ( stat < xstat ) + stat = xstat; + } + + sfi->path_ptr = path_old; + return stat; +} + +/////////////////////////////////////////////////////////////////////////////// + +int IterateSubDir ( SubDir_t *sd, SubFileIterator_t *sfi ) +{ + DASSERT(sd); + DASSERT(sfi); + + sfi->depth = 0; + sfi->path_ptr = sfi->path; + return IterateSubDirHelper(sd,sfi); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static int SaveSubFile +( + SubFileIterator_t *sfi, // iterator data + uint mode, // 0:file, 1:open dir, 2:close dir + SubDir_t *dir, // current directory + SubFile_t *file // NULL or current file +) +{ + if ( !mode && file && file->data ) + { + DASSERT(file); + char pathbuf[PATH_MAX]; + ccp path = PathCatPP(pathbuf,sizeof(pathbuf),(ccp)sfi->user_ptr,sfi->path); + printf(" >SAVE %s\n",path); + + return SaveFile( (ccp)sfi->user_ptr, sfi->path, + FM_REMOVE|FM_OVERWRITE|FM_MKDIR, + file->data, file->size, 0 ); + } + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError SaveSubFiles ( SubDir_t *sd, ccp basepath ) +{ + SubFileIterator_t it; + memset(&it,0,sizeof(it)); + it.func = SaveSubFile; + it.user_ptr = (void*)basepath; + return IterateSubDir(sd,&it); +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// PrintScript /////////////// +/////////////////////////////////////////////////////////////////////////////// + +void PrintScriptHeader ( PrintScript_t *ps ) +{ + DASSERT(ps); + + ps->index = 0; + ps->add_index = false; + ps->sep[0] = ps->sep[1] = 0; + + if ( ps->fo && ps->fo->f ) + { + switch (script_fform) + { + case FF_JSON: + if ( script_array > 0 ) + fputs("[",ps->fo->f); + break; + + case FF_SH: + ps->add_index = script_array > 0; + break; + + case FF_PHP: + if ( script_array > 0 ) + fprintf(ps->fo->f,"$%s = array();\n\n",script_varname); + break; + + case FF_MAKEDOC: + if ( script_array > 0 ) + fprintf(ps->fo->f,"%s = @LIST\n\n",script_varname); + break; + + default: + //fputc('\n',ps->fo->f); + break; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void PrintScriptFooter ( PrintScript_t *ps ) +{ + DASSERT(ps); + + if ( ps && ps->fo && ps->fo->f ) + { + switch (script_fform) + { + case FF_JSON: + if ( script_array > 0 ) + fputs("]\n",ps->fo->f); + break; + + case FF_SH: + if (script_array) + fprintf(ps->fo->f,"%s_N=%u\n\n",script_varname,ps->index); + break; + + case FF_PHP: + fputs("?>\n",ps->fo->f); + break; + + default: + break; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +int PutScriptVars +( + PrintScript_t *ps, // valid control struct + uint mode, // bit field: 1=open var, 2:close var + ccp text // text with line of format NAME=VALUE +) +{ + DASSERT(ps); + + FILE * f = ps->fo ? ps->fo->f : 0; + if (!f) + return 0; + + + //--- begin of output + + if ( mode & 1 ) + { + if ( ps->add_index ) + snprintf( ps->prefix, sizeof(ps->prefix), + "%s_%u", script_varname, ps->index ); + else + snprintf( ps->prefix, sizeof(ps->prefix), + "%s", script_varname ); + + switch (script_fform) + { + case FF_JSON: + fprintf(f,"%s{",ps->sep); + ps->sep[0] = ','; + break; + + case FF_PHP: + fputs("$d = new stdClass;\n",f); + break; + + case FF_MAKEDOC: + fprintf(f,"d = @MAP\n"); + break; + + default: + break; + } + } + + + //--- print var lines + + if (!text) + text = ""; + + uint count; + for( count = 0; ; count++ ) + { + while ( *text && (uchar)*text <= ' ' ) + text++; + + ccp ptr = text; + while ( *ptr && *ptr != '=' ) + ptr++; + if ( *ptr != '=' || ptr == text ) + break; + + ccp param = ptr++; + while ( (uchar)*ptr >= ' ' ) + ptr++; + + switch (script_fform) + { + case FF_JSON: + fprintf(f, "%s\"%.*s\":%.*s", + count ? "," : "", + (int)( param - text ), text, + (int)( ptr - param - 1 ), param+1 ); + break; + + case FF_BASH: + if (script_array) + { + fprintf(f, "%s_%.*s%s=(%.*s)\n", ps->prefix, + (int)( param - text ), text, + ps->index ? "+" : "", + (int)( ptr - param - 1 ), param+1 ); + break; + } + // fall through + + case FF_SH: + fprintf(f, "%s_%.*s=%.*s\n", ps->prefix, + (int)( param - text ), text, + (int)( ptr - param - 1 ), param+1 ); + break; + + case FF_PHP: + fprintf(f, "$d->%.*s = %.*s;\n", + (int)( param - text ), text, + (int)( ptr - param - 1 ), param+1 ); + break; + + case FF_MAKEDOC: + fprintf(f, "d[\"%.*s\"] = %.*s\n", + (int)( param - text ), text, + (int)( ptr - param - 1 ), param+1 ); + break; + + default: + fprintf(f, "%20.*s = %.*s\n", + (int)( param - text ), text, + (int)( ptr - param - 1 ), param+1 ); + break; + } + + text = ptr; + } + + + //--- end of output + + if ( mode & 2 ) + { + switch (script_fform) + { + case FF_JSON: + fputs("}\n",f); + break; + + case FF_PHP: + fprintf(f, "$%s%s = $d;\n\n", + ps->prefix, script_array ? "[]" : "" ); + break; + + case FF_MAKEDOC: + fprintf(f, "%s %s= move(d);\n\n", + ps->prefix, script_array ? "#" : "" ); + break; + + default: + fputc('\n',f); + break; + } + } + + ps->index++; + return count; +} + +/////////////////////////////////////////////////////////////////////////////// + +int PrintScriptVars +( + PrintScript_t *ps, // valid control struct + uint mode, // bit field: 1=open var, 2:close var + ccp format, // format of message + ... // arguments + +) +{ + DASSERT(ps); + + char buf[5000]; + if (format) + { + va_list arg; + va_start(arg,format); + vsnprintf(buf,sizeof(buf),format,arg); + va_end(arg); + } + else + *buf = 0; + + return PutScriptVars(ps,mode,buf); +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// Base64 coding /////////////// +/////////////////////////////////////////////////////////////////////////////// + +char *TableDecode64BySetup = TableDecode64; +const char *TableEncode64BySetup = TableEncode64; + +//----------------------------------------------------------------------------- + +int ScanOptCoding64 ( ccp arg ) +{ + if ( !arg || !*arg ) + return 0; + + static const KeywordTab_t ktab[] = + { + { ENCODE_OFF, "DEFAULT", 0, 0 }, + { ENCODE_BASE64, "STANDARD", "STD", 0 }, + { ENCODE_BASE64URL, "URL", 0, 0 }, + { ENCODE_BASE64STAR, "STAR", 0, 0 }, + { ENCODE_BASE64XML, "XML", 0, 0 }, + { 0,0,0,0 } + }; + + const KeywordTab_t *key = ScanKeyword(0,arg,ktab); + if (!key) + { + ERROR0(ERR_SYNTAX,"Invalid --coding mode: '%s'\n",arg); + return 1; + } + + opt_coding64 = key->id; + PRINT("opt_coding64=%u\n",opt_coding64); + return 0; +} + +//----------------------------------------------------------------------------- + +EncodeMode_t SetupCoding64 ( EncodeMode_t mode, EncodeMode_t fallback ) +{ + PRINT("SetupCoding64(%d,%d) std=%d, url=%d, star=%d, xml=%d\n", + mode, fallback, + ENCODE_BASE64, ENCODE_BASE64URL, ENCODE_BASE64STAR, ENCODE_BASE64XML ); + + switch(mode) + { + case ENCODE_BASE64: + PRINT("USE ENCODE_BASE64\n"); + TableDecode64BySetup = TableDecode64; + TableEncode64BySetup = TableEncode64; + break; + + case ENCODE_BASE64URL: + PRINT("USE ENCODE_BASE64URL\n"); + TableDecode64BySetup = TableDecode64url; + TableEncode64BySetup = TableEncode64url; + break; + + case ENCODE_BASE64STAR: + PRINT("USE ENCODE_BASE64STAR\n"); + TableDecode64BySetup = TableDecode64url; + TableEncode64BySetup = TableEncode64star; + break; + + case ENCODE_BASE64XML: + PRINT("USE ENCODE_BASE64XML\n"); + TableDecode64BySetup = TableDecode64xml; + TableEncode64BySetup = TableEncode64xml; + break; + + default: + PRINT("BASE64 -> FALLBACK\n"); + return SetupCoding64( fallback == ENCODE_OFF + ? ENCODE_BASE64 : fallback, ENCODE_OFF ); + } + return mode; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// END /////////////// +/////////////////////////////////////////////////////////////////////////////// |
