aboutsummaryrefslogtreecommitdiff
path: root/project/src/lib-file.c
diff options
context:
space:
mode:
Diffstat (limited to 'project/src/lib-file.c')
-rw-r--r--project/src/lib-file.c677
1 files changed, 668 insertions, 9 deletions
diff --git a/project/src/lib-file.c b/project/src/lib-file.c
index 18a5bd1..16037f7 100644
--- a/project/src/lib-file.c
+++ b/project/src/lib-file.c
@@ -46,6 +46,7 @@
#include <dirent.h>
#include <stddef.h>
+#include "dclib-utf8.h"
#include "lib-std.h"
#include "lib-szs.h"
#include "lib-rarc.h"
@@ -59,8 +60,7 @@
#include "lib-rkg.h"
#include "lib-image.h"
#include "lib-staticr.h"
-#include "lib-ctcode.h"
-#include "lib-lecode.h"
+#include "lib-ledis.h"
#include "lib-common.h"
#include "lib-bzip2.h"
#include "lib-checksum.h"
@@ -758,7 +758,7 @@ file_format_t GetByMagicFF
case ADDR_PORT_MAGIC_NUM: return FF_PORTDB;
//--- see below for BOM support
- case CT_TEXT_MAGIC8_NUM: return FF_CT_TEXT;
+ case CT_DEF_MAGIC8_NUM: return FF_CTDEF;
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;
@@ -767,6 +767,12 @@ file_format_t GetByMagicFF
case GH_KART_TEXT_MAGIC8_NUM: return FF_GH_KART_TXT;
case GH_KOBJ_TEXT_MAGIC8_NUM: return FF_GH_KOBJ_TXT;
case LE_LPAR_MAGIC_NUM: return FF_LPAR;
+ case LE_DEFINE_MAGIC8_NUM: return FF_LEDEF;
+ case LE_DISTRIB_MAGIC8_NUM: return FF_LEDIS;
+ case LE_REFERENCE_MAGIC8_NUM: return FF_LEREF;
+ case LE_STRINGS_MAGIC8_NUM: return FF_LESTR;
+ case LE_SHA1REF_MAGIC8_NUM: return FF_SHA1REF;
+ case LE_PREFIX_MAGIC8_NUM: return FF_PREFIX;
}
}
@@ -867,7 +873,7 @@ file_format_t GetByMagicFF
const u64 magic64 = be64(data+bom_len);
switch(magic64)
{
- case CT_TEXT_MAGIC8_NUM: return FF_CT_TEXT;
+ case CT_DEF_MAGIC8_NUM: return FF_CTDEF;
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;
@@ -1955,8 +1961,8 @@ void SearchConfigHelper ( search_file_list_t *sfl, int stop_mode )
ccp etc[] = { "wiimm", "" };
SearchConfig(sfl,CONFIG_FILE, opt,2, &xdg_home,1, &home,1, etc,2, etc+1,1, 0,0, stop_mode );
- if (logging>3)
- DumpSearchFile(stdout,2,sfl,logging>4,"Config search list");
+ if ( logging >= 4 )
+ DumpSearchFile(stdout,2,sfl,logging>=5,"Config search list");
}
//-----------------------------------------------------------------------------
@@ -2543,7 +2549,7 @@ static char * trim_line ( char * ptr, bool allow_quote, char ** begin )
buf_ptr = MALLOC(need);
buf_size = need;
}
-
+
uint scanned_len;
int elen = ScanEscapedString(buf_ptr,buf_size,ptr,slen,true,-1,&scanned_len);
if ( elen > slen )
@@ -3528,6 +3534,7 @@ int ScanOptCoding64 ( ccp arg )
{ ENCODE_BASE64URL, "URL", 0, 0 },
{ ENCODE_BASE64STAR, "STAR", 0, 0 },
{ ENCODE_BASE64XML, "XML", 0, 0 },
+ { ENCODE_HEX, "HEX", 0, 0 },
{ 0,0,0,0 }
};
@@ -3547,9 +3554,9 @@ int ScanOptCoding64 ( ccp arg )
EncodeMode_t SetupCoding64 ( EncodeMode_t mode, EncodeMode_t fallback )
{
- PRINT("SetupCoding64(%d,%d) std=%d, url=%d, star=%d, xml=%d\n",
+ PRINT("SetupCoding64(%d,%d) std=%d, url=%d, star=%d, xml=%d, hex=%d\n",
mode, fallback,
- ENCODE_BASE64, ENCODE_BASE64URL, ENCODE_BASE64STAR, ENCODE_BASE64XML );
+ ENCODE_BASE64, ENCODE_BASE64URL, ENCODE_BASE64STAR, ENCODE_BASE64XML, ENCODE_HEX );
switch(mode)
{
@@ -3577,6 +3584,14 @@ EncodeMode_t SetupCoding64 ( EncodeMode_t mode, EncodeMode_t fallback )
TableEncode64BySetup = TableEncode64xml;
break;
+ #if 0
+ case ENCODE_HEX:
+ PRINT("USE ENCODE_HEX\n");
+ TableDecode64BySetup = TableDecode64;
+ TableEncode64BySetup = TableEncode64;
+ break;
+ #endif
+
default:
PRINT("BASE64 -> FALLBACK\n");
return SetupCoding64( fallback == ENCODE_OFF
@@ -3853,5 +3868,649 @@ slot_info_t GetSlotByName ( mem_t name, bool minus_for_empty )
//
///////////////////////////////////////////////////////////////////////////////
+/////////////// mkw_prefix_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#include "prefix.inc"
+
+static const mkw_prefix_t *current_prefix_tab = 0;
+static int current_prefix_tab_len = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanOptLoadPrefix ( ccp arg )
+{
+ if ( !arg || !*arg )
+ return 0;
+
+ raw_data_t raw;
+ enumError err = LoadRawData(&raw,true,arg,0,opt_ignore>0,0);
+ if ( !err && raw.fform == FF_PREFIX )
+ DefinePrefixTable(raw.data,raw.data_size);
+ ResetRawData(&raw);
+ return err > ERR_WARNING;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int compare_prekey ( mkw_prefix_t *a, mkw_prefix_t *b )
+{
+ return strcmp(a->prefix,b->prefix);
+}
+
+//-----------------------------------------------------------------------------
+
+const mkw_prefix_t * ScanPrefixTable ( ScanText_t *st )
+{
+ DASSERT(st);
+ if (!st)
+ return 0;
+
+ enum
+ {
+ IDX_PREFIX,
+ IDX_INFO,
+ IDX_FLAGS,
+ IDX_ORDER,
+ IDX_COLOR,
+ IDX__N
+ };
+
+ st->detect_sections++;
+
+ int n_elem = 0;
+ while (NextLineScanText(st))
+ {
+ ccp pipe = memchr(st->line,'|',st->eol-st->line);
+ if ( pipe && pipe > st->line )
+ n_elem++;
+ }
+ if (!n_elem)
+ return 0;
+
+ mkw_prefix_t *tab = CALLOC( n_elem+1, sizeof(*tab) );
+ mkw_prefix_t *ptr = tab;
+
+ RewindScanText(st);
+ while ( ptr < tab+n_elem < n_elem && NextLineScanText(st) )
+ {
+ mem_t srcline = { .ptr = st->line, .len = st->eol - st->line };
+ mem_t src[IDX__N+2];
+ const uint n = SplitByCharMem(src,IDX__N+1,srcline,'|');
+ if ( n <= IDX_INFO )
+ continue;
+
+ PRINT0("LINE %d/%d: %.*s | %.*s | %.*s | %.*s | %.*s |\n",
+ lineno, n_elem,
+ src[IDX_PREFIX].len, src[IDX_PREFIX].ptr,
+ src[IDX_INFO].len, src[IDX_INFO].ptr,
+ src[IDX_FLAGS].len, src[IDX_FLAGS].ptr,
+ src[IDX_ORDER].len, src[IDX_ORDER].ptr,
+ src[IDX_COLOR].len, src[IDX_COLOR].ptr );
+
+ StringCopySMem(ptr->prefix,sizeof(ptr->prefix),src[IDX_PREFIX]);
+ ptr->info = MEMDUP(src[IDX_INFO].ptr,src[IDX_INFO].len);
+ if ( n > IDX_ORDER )
+ ptr->order = str2l(src[IDX_ORDER].ptr,0,10);
+ if ( n > IDX_COLOR )
+ ptr->color = str2l(src[IDX_COLOR].ptr,0,10);
+
+ if ( n > IDX_FLAGS )
+ {
+ mkw_prefix_flags_t flags = 0;
+ mem_t f = src[IDX_FLAGS];
+ if (memchr(f.ptr,'N',f.len)) flags |= MPF_NINTENDO;
+ if (memchr(f.ptr,'M',f.len)) flags |= MPF_MARIOKART;
+ if (memchr(f.ptr,'1',f.len)) flags |= MPF_PREFIX1;
+ if (memchr(f.ptr,'2',f.len)) flags |= MPF_PREFIX2;
+ ptr->flags = flags;
+ }
+ ptr++;
+ }
+
+ qsort( tab, n_elem, sizeof(*tab), (qsort_func)compare_prekey );
+
+ st->detect_sections--;
+ return tab;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const mkw_prefix_t * DefinePrefixTable ( cvp source, uint len )
+{
+ ScanText_t st;
+ SetupScanText(&st,source,len);
+
+ if (current_prefix_tab)
+ {
+ for ( mkw_prefix_t *ptr = (mkw_prefix_t*)current_prefix_tab; ptr->info; ptr++ )
+ FreeString(ptr->info);
+ FREE((void*)current_prefix_tab);
+ }
+
+ current_prefix_tab = ScanPrefixTable(&st);
+ current_prefix_tab_len = 0;
+ if (current_prefix_tab)
+ for ( const mkw_prefix_t *ptr = current_prefix_tab; ptr->info; ptr++ )
+ current_prefix_tab_len++;
+
+ ResetScanText(&st);
+ return current_prefix_tab;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const mkw_prefix_t * GetPrefixTable(void)
+{
+ if (!current_prefix_tab)
+ {
+ DecodeBZIP2Manager(&prefix_inc_mgr);
+ DefinePrefixTable(prefix_inc_mgr.data,prefix_inc_mgr.size);
+ }
+
+ return current_prefix_tab;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const mkw_prefix_t * FindPrefix ( ccp pre, int pre_len, bool exact )
+{
+ if ( !pre || !*pre )
+ return 0;
+
+ char pbuf[20];
+ if ( pre_len < 0 )
+ pre_len = strlen(pre);
+ if ( !pre_len || pre_len >= sizeof(pbuf) )
+ return 0;
+ if (pre[pre_len])
+ {
+ memcpy(pbuf,pre,pre_len);
+ pbuf[pre_len] = 0;
+ pre = pbuf;
+ }
+
+ const mkw_prefix_t *tab = GetPrefixTable();
+ if (!tab)
+ return 0;
+
+ for ( int i = 0; i < 2; i++ )
+ {
+ int beg = 0;
+ int end = current_prefix_tab_len - 1;
+ while ( beg <= end )
+ {
+ const uint idx = (beg+end)/2;
+ const mkw_prefix_t *p = tab + idx;
+ const int stat = strcmp(pre,p->prefix);
+ if ( stat < 0 )
+ end = idx - 1 ;
+ else if ( stat > 0 )
+ beg = idx + 1;
+ else
+ return p;
+ }
+
+ if ( exact || i )
+ return 0;
+
+ StringUpperS(pbuf,sizeof(pbuf),pre);
+ if (!strcmp(pbuf,pre))
+ return 0;
+ pre = pbuf;
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SavePrefixTable ( FILE *f, const mkw_prefix_t * tab )
+{
+ if (!tab)
+ tab = GetPrefixTable();
+ if ( !tab || !f )
+ return ERR_MISSING_PARAM;
+
+ fprintf(f,
+ "#PREFIX1\r\n"
+ "#> prefix|info|flags|order|\r\n"
+ "# Info flags: N: by Nintento, M: Original Mario Kart\r\n"
+ "# Functional flags: 1|2: Can be used as first|second part of a combi\r\n"
+ "#%.68s\r\n"
+ ,Minus300 );
+
+ int count = 0, fw_prefix = 0, fw_info = 0;
+ for ( ; tab->info; tab++ )
+ {
+ count++;
+ exmem_t prefix = EscapeStringPipeCircS(tab->prefix);
+ exmem_t info = EscapeStringPipeCircS(tab->info);
+
+ if ( fw_prefix < prefix.data.len )
+ fw_prefix = prefix.data.len;
+ if ( fw_info < info.data.len )
+ fw_info = info.data.len;
+
+ fprintf(f,"%s|%s|%s%s%s%s|%u|\r\n",
+ prefix.data.ptr, info.data.ptr,
+ tab->flags & MPF_NINTENDO ? "N" : "",
+ tab->flags & MPF_MARIOKART ? "M" : "",
+ tab->flags & MPF_PREFIX1 ? "1" : "",
+ tab->flags & MPF_PREFIX2 ? "2" : "",
+ tab->order );
+
+ FreeExMem(&prefix);
+ FreeExMem(&info);
+ }
+
+ fprintf(f,
+ "#-------------------\r\n"
+ "# %u prefixes total\r\n"
+ "# Max prefix len:%2u\r\n"
+ "# Max info len:%4u\r\n"
+ ,count
+ ,fw_prefix
+ ,fw_info
+ );
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SavePrefixTableFN ( ccp fname, const mkw_prefix_t * tab )
+{
+ File_t F;
+ enumError err = CreateFileOpt(&F,true,fname,false,0);
+ if (!err)
+ err = SavePrefixTable(F.f,tab);
+ CloseFile(&F,0);
+ return err;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// split_filename_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp opt_plus = "+";
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetSPF ( split_filename_t *spf )
+{
+ if (spf)
+ {
+ if (spf->input_alloced)
+ FreeString(spf->input);
+ FreeString(spf->norm.ptr);
+ InitializeSPF(spf);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AnalyseSPF
+(
+ split_filename_t *spf, // valid pointet
+ bool init_spf, // true: InitializeSPF(), false: ResetSPF()
+ ccp source, // NULL or ponter to name
+ ccp src_end, // end of source. if 0 then use strlen()
+ CopyMode_t cpm, // copy mode for 'source'
+ ccp plus_mode // NULL or string of chars
+)
+{
+ if (!spf)
+ return;
+
+ //--- setup
+
+ if ( init_spf)
+ InitializeSPF(spf);
+ else
+ ResetSPF(spf);
+
+ if ( !source || !*source || src_end && src_end <= source )
+ return;
+
+ char buf[100];
+
+
+ //--- store input data
+
+ if ( cpm == CPM_COPY )
+ {
+ // optimization: we need only the base name
+ const int nlen = src_end ? src_end - source : strlen(source);
+ char *slash = memrchr(source,'/',nlen);
+ if (slash)
+ source = slash+1;
+ }
+
+ int nlen = src_end ? src_end - source : strlen(source);
+ spf->input = CopyData(source,nlen,cpm,&spf->input_alloced);
+
+ char *name = (char*)spf->input;
+ char *end = name + nlen;
+
+
+ //--- remove leading directory and store source
+
+ char *slash = memrchr(name,'/',nlen);
+ if (slash)
+ {
+ slash++;
+ nlen -= slash - name;
+ name = slash;
+ }
+
+ spf->source.ptr = name;
+ spf->source.len = nlen;
+
+
+ //--- split source into name + d + ext
+
+ // find last space or ']' or ')'
+ for ( char *p1 = end-1; p1 > name; p1-- )
+ {
+ if ( *p1 == '.' )
+ {
+ spf->f_ext.ptr = p1;
+ spf->f_ext.len = end - p1;
+ end = p1;
+ nlen = end - name;
+ break;
+ }
+
+ if ( (uchar)*p1 <= ' ' || *p1 == ']' || *p1 == ')' )
+ break;
+ }
+
+ if ( nlen >= 2 && end[-1] == 'd' && end[-2] == '_' )
+ {
+ end -= 2;
+ spf->f_d.ptr = end;
+ spf->f_d.len = 2;
+ nlen -= 2;
+ }
+
+ spf->f_name.ptr = name;
+ spf->f_name.len = nlen;;
+
+
+ //--- alloc name buffer and normalize blanks,controls and "ː⁄"
+
+ char *p1 = MALLOC(nlen+1), *p2;
+ spf->norm.ptr = p1;
+ int have_blank = 0;
+ for ( ccp src = name; src < end; src++ )
+ {
+ if ( (uchar)*src > ' ' )
+ {
+ if ( have_blank && p1 > spf->norm.ptr )
+ *p1++ = ' ';
+
+ if (!memcmp(src,"ː",sizeof("ː")-1))
+ {
+ *p1++ = ':';
+ src += sizeof("ː") - 2;
+ }
+ else if (!memcmp(src,"⁄",sizeof("⁄")-1))
+ {
+ *p1++ = '/';
+ src += sizeof("⁄") - 2;
+ }
+ else
+ *p1++ = *src;
+ have_blank = 0;
+ }
+ else
+ have_blank++;
+ }
+
+ *p1 = 0;
+ name = (char*)spf->norm.ptr;
+ end = p1;
+ nlen = end - name;
+ spf->norm.len = nlen;
+
+
+ //--- attributes and authors
+
+ p1 = memrchr(name,'[',nlen);
+ if (p1)
+ {
+ p2 = memrchr(name,']',nlen);
+ if ( !p2 || p2 <= p1 )
+ p2 = end;
+
+ spf->attribs.ptr = p1+1;
+ spf->attribs.len = p2 - spf->attribs.ptr;
+ end = p1;
+ while ( end > name && end[-1] == ' ' )
+ end--;
+ nlen = end - name;
+ }
+
+ p1 = memrchr(name,'(',nlen);
+ if (p1)
+ {
+ p2 = memrchr(name,')',nlen);
+ if ( !p2 || p2 <= p1 )
+ p2 = end;
+
+ spf->authors.ptr = p1+1;
+ spf->authors.len = p2 - spf->authors.ptr;
+ end = p1;
+ while ( end > name && end[-1] == ' ' )
+ end--;
+ nlen = end - name;
+ }
+
+
+ //--- check version number
+
+ p1 = memrchr(name,' ',nlen);
+ p2 = memrchr(name,'}',nlen);
+ if ( !p1 || p2 && p1 < p2 )
+ p1 = p2;
+ if (p1)
+ {
+ p1++;
+ p2 = buf;
+ for ( char *src = p1; src < end && p2 < buf+sizeof(buf)-1 && isalpha((int)*src); src++ )
+ *p2++ = tolower((int)*src);
+
+ int plen = p2 - buf, valid = 0;
+ switch (plen)
+ {
+ case 1:
+ valid = !memcmp(buf,"v",1)
+ || !memcmp(buf,"t",1);
+ break;
+
+ case 2:
+ valid = !memcmp(buf,"rc",2)
+ || !memcmp(buf,"vx",2);
+ break;
+
+ case 3:
+ valid = !memcmp(buf,"pre",3)
+ || !memcmp(buf,"poc",3)
+ || !memcmp(buf,"rcx",3);
+ break;
+
+ case 4:
+ valid = !memcmp(buf,"beta",4)
+ || !memcmp(buf,"test",4)
+ || !memcmp(buf,"vjam",4);
+ break;
+
+ case 5:
+ valid = !memcmp(buf,"alpha",5);
+ break;
+
+ case 7:
+ valid = !memcmp(buf,"preview",7);
+ break;
+ }
+
+ if (valid)
+ {
+ spf->version.ptr = p1;
+ spf->version.len = end - p1;
+ end = p1;
+ while ( end > name && end[-1] == ' ' )
+ end--;
+ nlen = end - name;
+ }
+ }
+
+
+ //--- check extra
+
+ p1 = memrchr(name,'{',nlen);
+ if (!p1)
+ p1 = memrchr(name,'(',nlen);
+ if (p1)
+ {
+ p2 = memrchr(p1,'}',end-p1);
+ if (!p2)
+ p2 = memrchr(p1,')',end-p1);
+ if (!p2)
+ p2 = end;
+ if ( p1 < p2 )
+ {
+ spf->extra.ptr = p1+1;
+ spf->extra.len = p2 - spf->extra.ptr;
+ end = p1;
+ while ( end > name && end[-1] == ' ' )
+ end--;
+ nlen = end - name;
+ }
+ }
+
+
+ //--- plus
+
+ spf->plus_order = 999999;
+ if ( *name == '+' )
+ {
+ p2 = strchr(name,' ');
+ if (p2)
+ {
+ p1 = name;
+ while ( *p1 == '+' )
+ p1++;
+ if ( p1 == p2 )
+ p1--;
+
+ spf->plus_order = 998000 - (p1-name) * 1000;
+ if ( spf->plus_order < 0 )
+ spf->plus_order = 0;
+
+ ccp found = plus_mode ? strchr(plus_mode,*p1) : 0;
+ spf->plus_order += found ? found - plus_mode : 500 + *p1;
+
+ spf->plus.ptr = name;
+ spf->plus.len = p2 - name;
+ name = p2+1;
+ nlen = end - name;
+ }
+ }
+
+
+ //--- special chars
+
+ #if SUPPORT_SPLIT_SIGN
+ p1 = name;
+ for (;;)
+ {
+ p2 = NextUTF8Char(p1);
+ const int len = p2 - p1;
+ if ( ( !len || memcmp(p1,"→",len) && memcmp(p1,"»",len) )
+ && ( len != 1 || !strchr("~>^",*p1) )
+ )
+ {
+ break;
+ }
+ p1 = p2;
+ }
+ if ( p1 > name )
+ {
+ if ( *p1 == ' ' )
+ p1++;
+ spf->sign.ptr = name;
+ spf->sign.len = p1 - name;
+ name = p1;
+ nlen = end - name;
+ }
+ #endif
+
+
+ //--- boost
+
+ if (!strncasecmp(name,"boost:",6))
+ {
+ spf->boost.ptr = name;
+ spf->boost.len = 6;
+ name += 6;
+ if ( *name == ' ' )
+ name++;
+ nlen = end - name;
+ }
+
+
+ //--- game prefix
+
+ spf->game_order = 999999;
+ p1 = memchr(name,' ',nlen);
+ if (p1)
+ {
+ const mkw_prefix_t *pre1 = FindPrefix(name,p1-name,true);
+ if (pre1)
+ {
+ spf->game1.ptr = name;
+ spf->game1.len = p1 - name;
+ spf->game = spf->game1;
+ spf->game_color = pre1->color;
+ spf->game_order = 1000 * pre1->order;
+ int add_game = 998;
+ name = p1+1;
+ nlen = end - name;
+
+ if ( pre1->flags & MPF_PREFIX1 )
+ {
+ p1 = memchr(name,' ',nlen);
+ if (p1)
+ {
+ const mkw_prefix_t *pre2 = FindPrefix(name,p1-name,true);
+ if ( pre2 && pre2->flags & MPF_PREFIX2 )
+ {
+ spf->game2.ptr = name;
+ spf->game2.len = p1 - name;
+ spf->game.len += spf->game2.len + 1;
+ name = p1+1;
+ nlen = end - name;
+ add_game = 2*pre2->order;
+ }
+ }
+ }
+ spf->game_order += add_game;
+ if (spf->boost.len)
+ spf->game_order++;
+ }
+ }
+
+
+ //--- finally we have the pure name
+
+ spf->name.ptr = name;
+ spf->name.len = nlen;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
/////////////// END ///////////////
///////////////////////////////////////////////////////////////////////////////