2003-12-11 22:38:14 +00:00
|
|
|
/*
|
|
|
|
* $Id: inf.c,v 1.3 2003/11/30 21:58:16 winter Exp $
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/cdefs.h>
|
|
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
#include <sys/queue.h>
|
|
|
|
|
|
|
|
#include "inf.h"
|
|
|
|
|
|
|
|
extern FILE *yyin;
|
|
|
|
int yyparse (void);
|
|
|
|
|
|
|
|
const char *words[W_MAX]; /* More than we'll need. */
|
|
|
|
int idx;
|
|
|
|
|
|
|
|
static struct section_head sh;
|
|
|
|
static struct reg_head rh;
|
|
|
|
static struct assign_head ah;
|
|
|
|
|
|
|
|
static char *sstrdup (const char *);
|
|
|
|
static struct assign
|
|
|
|
*find_assign (const char *, const char *);
|
|
|
|
static struct section
|
|
|
|
*find_section (const char *);
|
|
|
|
static void dump_deviceids (void);
|
|
|
|
static void dump_pci_id (const char *);
|
|
|
|
static void dump_regvals (void);
|
2003-12-18 03:51:21 +00:00
|
|
|
static void dump_paramreg (const struct section *,
|
|
|
|
const struct reg *, int);
|
2003-12-11 22:38:14 +00:00
|
|
|
|
|
|
|
static FILE *ofp;
|
|
|
|
|
|
|
|
int
|
|
|
|
inf_parse (FILE *fp, FILE *outfp)
|
|
|
|
{
|
|
|
|
TAILQ_INIT(&sh);
|
|
|
|
TAILQ_INIT(&rh);
|
|
|
|
TAILQ_INIT(&ah);
|
|
|
|
|
|
|
|
ofp = outfp;
|
|
|
|
yyin = fp;
|
|
|
|
yyparse();
|
|
|
|
|
|
|
|
dump_deviceids();
|
|
|
|
dump_regvals();
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
section_add (const char *s)
|
|
|
|
{
|
|
|
|
struct section *sec;
|
|
|
|
|
|
|
|
sec = malloc(sizeof(struct section));
|
|
|
|
bzero(sec, sizeof(struct section));
|
|
|
|
sec->name = s;
|
|
|
|
TAILQ_INSERT_TAIL(&sh, sec, link);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct assign *
|
|
|
|
find_assign (const char *s, const char *k)
|
|
|
|
{
|
|
|
|
struct assign *assign;
|
|
|
|
char newkey[256];
|
|
|
|
|
|
|
|
/* Deal with string section lookups. */
|
|
|
|
|
|
|
|
if (k != NULL && k[0] == '%') {
|
|
|
|
bzero(newkey, sizeof(newkey));
|
|
|
|
strncpy(newkey, k + 1, strlen(k) - 2);
|
|
|
|
k = newkey;
|
|
|
|
}
|
|
|
|
|
|
|
|
TAILQ_FOREACH(assign, &ah, link) {
|
|
|
|
if (strcasecmp(assign->section->name, s) == 0) {
|
|
|
|
if (k == NULL)
|
|
|
|
return(assign);
|
|
|
|
else
|
|
|
|
if (strcasecmp(assign->key, k) == 0)
|
|
|
|
return(assign);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
stringcvt(const char *s)
|
|
|
|
{
|
|
|
|
struct assign *manf;
|
|
|
|
|
|
|
|
manf = find_assign("strings", s);
|
|
|
|
if (manf == NULL)
|
|
|
|
return(s);
|
|
|
|
return(manf->vals[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct section *
|
|
|
|
find_section (const char *s)
|
|
|
|
{
|
|
|
|
struct section *section;
|
|
|
|
|
|
|
|
TAILQ_FOREACH(section, &sh, link) {
|
|
|
|
if (strcasecmp(section->name, s) == 0)
|
|
|
|
return(section);
|
|
|
|
}
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_pci_id(const char *s)
|
|
|
|
{
|
|
|
|
char *p;
|
2003-12-18 03:51:21 +00:00
|
|
|
char vidstr[7], didstr[7], subsysstr[14];
|
2003-12-11 22:38:14 +00:00
|
|
|
|
|
|
|
p = strcasestr(s, "VEN_");
|
|
|
|
if (p == NULL)
|
|
|
|
return;
|
|
|
|
p += 4;
|
|
|
|
strcpy(vidstr, "0x");
|
|
|
|
strncat(vidstr, p, 4);
|
|
|
|
p = strcasestr(s, "DEV_");
|
|
|
|
if (p == NULL)
|
|
|
|
return;
|
|
|
|
p += 4;
|
|
|
|
strcpy(didstr, "0x");
|
|
|
|
strncat(didstr, p, 4);
|
|
|
|
if (p == NULL)
|
|
|
|
return;
|
2003-12-18 03:51:21 +00:00
|
|
|
p = strcasestr(s, "SUBSYS_");
|
|
|
|
if (p == NULL)
|
|
|
|
strcpy(subsysstr, "0x00000000");
|
|
|
|
else {
|
|
|
|
p += 7;
|
|
|
|
strcpy(subsysstr, "0x");
|
|
|
|
strncat(subsysstr, p, 8);
|
|
|
|
}
|
2003-12-11 22:38:14 +00:00
|
|
|
|
2003-12-18 03:51:21 +00:00
|
|
|
fprintf(ofp, "\t\\\n\t{ %s, %s, %s, ", vidstr, didstr, subsysstr);
|
2003-12-11 22:38:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_deviceids()
|
|
|
|
{
|
|
|
|
struct assign *manf, *dev;
|
|
|
|
struct section *sec;
|
|
|
|
struct assign *assign;
|
2003-12-12 03:40:05 +00:00
|
|
|
char xpsec[256];
|
2003-12-11 22:38:14 +00:00
|
|
|
|
|
|
|
/* Find manufacturer name */
|
|
|
|
manf = find_assign("Manufacturer", NULL);
|
|
|
|
|
|
|
|
/* Find manufacturer section */
|
2003-12-12 03:40:05 +00:00
|
|
|
if (manf->vals[1] != NULL &&
|
2003-12-13 08:48:35 +00:00
|
|
|
(strcasecmp(manf->vals[1], "NT.5.1") == 0 ||
|
|
|
|
strcasecmp(manf->vals[1], "NTx86.5.1") == 0)) {
|
2003-12-12 03:40:05 +00:00
|
|
|
/* Handle Windows XP INF files. */
|
|
|
|
snprintf(xpsec, sizeof(xpsec), "%s.%s",
|
|
|
|
manf->vals[0], manf->vals[1]);
|
|
|
|
sec = find_section(xpsec);
|
|
|
|
} else
|
|
|
|
sec = find_section(manf->vals[0]);
|
2003-12-11 22:38:14 +00:00
|
|
|
|
|
|
|
/* Emit start of device table */
|
|
|
|
fprintf (ofp, "#define NDIS_DEV_TABLE");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now run through all the device names listed
|
|
|
|
* in the manufacturer section and dump out the
|
|
|
|
* device descriptions and vendor/device IDs.
|
|
|
|
*/
|
|
|
|
|
|
|
|
TAILQ_FOREACH(assign, &ah, link) {
|
|
|
|
if (assign->section == sec) {
|
|
|
|
dev = find_assign("strings", assign->key);
|
|
|
|
/* Emit device IDs. */
|
|
|
|
if (strcasestr(assign->vals[1], "PCI") != NULL)
|
|
|
|
dump_pci_id(assign->vals[1]);
|
|
|
|
#ifdef notdef
|
|
|
|
else if (strcasestr(assign->vals[1], "PCMCIA") != NULL)
|
|
|
|
dump_pcmcia_id(assign->vals[1]);
|
|
|
|
#endif
|
|
|
|
/* Emit device description */
|
2003-12-11 23:37:56 +00:00
|
|
|
fprintf (ofp, "\t\\\n\t\"%s\" },", dev->vals[0]);
|
2003-12-11 22:38:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Emit end of table */
|
|
|
|
|
2003-12-11 23:37:56 +00:00
|
|
|
fprintf(ofp, "\n\n");
|
|
|
|
|
2003-12-11 22:38:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2003-12-18 03:51:21 +00:00
|
|
|
dump_addreg(const char *s, int devidx)
|
2003-12-11 22:38:14 +00:00
|
|
|
{
|
|
|
|
struct section *sec;
|
|
|
|
struct reg *reg;
|
|
|
|
|
|
|
|
/* Find the addreg section */
|
|
|
|
sec = find_section(s);
|
|
|
|
|
|
|
|
/* Dump all the keys defined in it. */
|
|
|
|
TAILQ_FOREACH(reg, &rh, link) {
|
|
|
|
/*
|
|
|
|
* Keys with an empty subkey are very easy to parse,
|
|
|
|
* so just deal with them here. If a parameter key
|
|
|
|
* of the same name also exists, prefer that one and
|
|
|
|
* skip this one.
|
|
|
|
*/
|
|
|
|
if (reg->section == sec) {
|
|
|
|
if (reg->subkey == NULL) {
|
|
|
|
fprintf(ofp, "\n\t{ \"%s\",", reg->key);
|
|
|
|
fprintf(ofp,"\n\t\"%s \",", reg->key);
|
2003-12-18 03:51:21 +00:00
|
|
|
fprintf(ofp, "\n\t{ \"%s\" }, %d },",
|
2003-12-11 22:38:14 +00:00
|
|
|
reg->value == NULL ? "" :
|
2003-12-18 03:51:21 +00:00
|
|
|
stringcvt(reg->value), devidx);
|
2003-12-11 22:38:14 +00:00
|
|
|
} else if (strcasestr(reg->subkey,
|
|
|
|
"Ndi\\params") != NULL &&
|
|
|
|
strcasecmp(reg->key, "ParamDesc") == 0)
|
2003-12-18 03:51:21 +00:00
|
|
|
dump_paramreg(sec, reg, devidx);
|
2003-12-11 22:38:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_enumreg(const struct section *s, const struct reg *r)
|
|
|
|
{
|
|
|
|
struct reg *reg;
|
|
|
|
char enumkey[256];
|
|
|
|
|
|
|
|
sprintf(enumkey, "%s\\enum", r->subkey);
|
|
|
|
TAILQ_FOREACH(reg, &rh, link) {
|
|
|
|
if (reg->section != s)
|
|
|
|
continue;
|
|
|
|
if (reg->subkey == NULL || strcasecmp(reg->subkey, enumkey))
|
|
|
|
continue;
|
2003-12-12 03:40:05 +00:00
|
|
|
fprintf(ofp, " [%s=%s]", reg->key,
|
|
|
|
stringcvt(reg->value));
|
2003-12-11 22:38:14 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_editreg(const struct section *s, const struct reg *r)
|
|
|
|
{
|
|
|
|
struct reg *reg;
|
|
|
|
|
|
|
|
TAILQ_FOREACH(reg, &rh, link) {
|
|
|
|
if (reg->section != s)
|
|
|
|
continue;
|
|
|
|
if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
|
|
|
|
continue;
|
|
|
|
if (strcasecmp(reg->key, "LimitText") == 0)
|
|
|
|
fprintf(ofp, " [maxchars=%s]", reg->value);
|
|
|
|
if (strcasecmp(reg->key, "Optional") == 0 &&
|
|
|
|
strcmp(reg->value, "1") == 0)
|
|
|
|
fprintf(ofp, " [optional]");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Use this for int too */
|
|
|
|
static void
|
|
|
|
dump_dwordreg(const struct section *s, const struct reg *r)
|
|
|
|
{
|
|
|
|
struct reg *reg;
|
|
|
|
|
|
|
|
TAILQ_FOREACH(reg, &rh, link) {
|
|
|
|
if (reg->section != s)
|
|
|
|
continue;
|
|
|
|
if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
|
|
|
|
continue;
|
|
|
|
if (strcasecmp(reg->key, "min") == 0)
|
|
|
|
fprintf(ofp, " [min=%s]", reg->value);
|
|
|
|
if (strcasecmp(reg->key, "max") == 0)
|
|
|
|
fprintf(ofp, " [max=%s]", reg->value);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2003-12-18 03:51:21 +00:00
|
|
|
dump_defaultinfo(const struct section *s, const struct reg *r, int devidx)
|
2003-12-11 22:38:14 +00:00
|
|
|
{
|
|
|
|
struct reg *reg;
|
|
|
|
TAILQ_FOREACH(reg, &rh, link) {
|
|
|
|
if (reg->section != s)
|
|
|
|
continue;
|
|
|
|
if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
|
|
|
|
continue;
|
|
|
|
if (strcasecmp(reg->key, "Default"))
|
|
|
|
continue;
|
2003-12-18 03:51:21 +00:00
|
|
|
fprintf(ofp, "\n\t{ \"%s\" }, %d },", reg->value == NULL ? "" :
|
2003-12-30 00:30:45 +00:00
|
|
|
stringcvt(reg->value), devidx);
|
2003-12-11 22:38:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_paramdesc(const struct section *s, const struct reg *r)
|
|
|
|
{
|
|
|
|
struct reg *reg;
|
|
|
|
TAILQ_FOREACH(reg, &rh, link) {
|
|
|
|
if (reg->section != s)
|
|
|
|
continue;
|
|
|
|
if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
|
|
|
|
continue;
|
|
|
|
if (strcasecmp(reg->key, "ParamDesc"))
|
|
|
|
continue;
|
|
|
|
fprintf(ofp, "\n\t\"%s", stringcvt(r->value));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_typeinfo(const struct section *s, const struct reg *r)
|
|
|
|
{
|
|
|
|
struct reg *reg;
|
|
|
|
TAILQ_FOREACH(reg, &rh, link) {
|
|
|
|
if (reg->section != s)
|
|
|
|
continue;
|
|
|
|
if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
|
|
|
|
continue;
|
|
|
|
if (strcasecmp(reg->key, "type"))
|
|
|
|
continue;
|
|
|
|
if (strcasecmp(reg->value, "dword") == 0 ||
|
|
|
|
strcasecmp(reg->value, "int") == 0)
|
|
|
|
dump_dwordreg(s, r);
|
|
|
|
if (strcasecmp(reg->value, "enum") == 0)
|
|
|
|
dump_enumreg(s, r);
|
|
|
|
if (strcasecmp(reg->value, "edit") == 0)
|
|
|
|
dump_editreg(s, r);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2003-12-18 03:51:21 +00:00
|
|
|
dump_paramreg(const struct section *s, const struct reg *r, int devidx)
|
2003-12-11 22:38:14 +00:00
|
|
|
{
|
|
|
|
const char *keyname;
|
|
|
|
|
|
|
|
keyname = r->subkey + strlen("Ndi\\params\\");
|
|
|
|
fprintf(ofp, "\n\t{ \"%s\",", keyname);
|
|
|
|
dump_paramdesc(s, r);
|
|
|
|
dump_typeinfo(s, r);
|
|
|
|
fprintf(ofp, "\",");
|
2003-12-18 03:51:21 +00:00
|
|
|
dump_defaultinfo(s, r, devidx);
|
2003-12-11 22:38:14 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_regvals(void)
|
|
|
|
{
|
2003-12-18 03:51:21 +00:00
|
|
|
struct assign *manf, *dev;
|
2003-12-11 22:38:14 +00:00
|
|
|
struct section *sec;
|
|
|
|
struct assign *assign;
|
|
|
|
char sname[256];
|
2003-12-18 03:51:21 +00:00
|
|
|
int i, is_winxp = 0, devidx = 0;
|
2003-12-11 22:38:14 +00:00
|
|
|
|
|
|
|
/* Find manufacturer name */
|
|
|
|
manf = find_assign("Manufacturer", NULL);
|
|
|
|
|
|
|
|
/* Find manufacturer section */
|
2003-12-12 03:40:05 +00:00
|
|
|
if (manf->vals[1] != NULL &&
|
2003-12-13 08:48:35 +00:00
|
|
|
(strcasecmp(manf->vals[1], "NT.5.1") == 0 ||
|
|
|
|
strcasecmp(manf->vals[1], "NTx86.5.1") == 0)) {
|
2003-12-12 03:40:05 +00:00
|
|
|
is_winxp++;
|
|
|
|
/* Handle Windows XP INF files. */
|
|
|
|
snprintf(sname, sizeof(sname), "%s.%s",
|
|
|
|
manf->vals[0], manf->vals[1]);
|
|
|
|
sec = find_section(sname);
|
|
|
|
} else
|
|
|
|
sec = find_section(manf->vals[0]);
|
2003-12-11 22:38:14 +00:00
|
|
|
|
|
|
|
/* Emit start of block */
|
|
|
|
fprintf (ofp, "ndis_cfg ndis_regvals[] = {");
|
|
|
|
|
|
|
|
TAILQ_FOREACH(assign, &ah, link) {
|
|
|
|
if (assign->section == sec) {
|
2003-12-12 03:40:05 +00:00
|
|
|
/*
|
|
|
|
* Find all the AddReg sections.
|
|
|
|
* Look for section names with .NT, unless
|
|
|
|
* this is a WinXP .INF file.
|
|
|
|
*/
|
2003-12-13 08:48:35 +00:00
|
|
|
if (is_winxp) {
|
|
|
|
sprintf(sname, "%s.NTx86", assign->vals[0]);
|
|
|
|
dev = find_assign(sname, "AddReg");
|
|
|
|
if (dev == NULL)
|
|
|
|
dev = find_assign(assign->vals[0],
|
|
|
|
"AddReg");
|
|
|
|
} else {
|
2003-12-12 03:40:05 +00:00
|
|
|
sprintf(sname, "%s.NT", assign->vals[0]);
|
|
|
|
dev = find_assign(sname, "AddReg");
|
|
|
|
}
|
|
|
|
/* Section not found. */
|
|
|
|
if (dev == NULL)
|
|
|
|
continue;
|
2003-12-11 22:38:14 +00:00
|
|
|
for (i = 0; i < W_MAX; i++) {
|
|
|
|
if (dev->vals[i] != NULL)
|
2003-12-18 03:51:21 +00:00
|
|
|
dump_addreg(dev->vals[i], devidx);
|
2003-12-11 22:38:14 +00:00
|
|
|
}
|
2003-12-18 03:51:21 +00:00
|
|
|
devidx++;
|
2003-12-11 22:38:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-12-18 03:51:21 +00:00
|
|
|
fprintf(ofp, "\n\t{ NULL, NULL, { 0 }, 0 }\n};\n\n");
|
2003-12-11 22:38:14 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
assign_add (const char *a)
|
|
|
|
{
|
|
|
|
struct assign *assign;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
assign = malloc(sizeof(struct assign));
|
|
|
|
bzero(assign, sizeof(struct assign));
|
|
|
|
assign->section = TAILQ_LAST(&sh, section_head);
|
|
|
|
assign->key = sstrdup(a);
|
|
|
|
for (i = 0; i < idx; i++)
|
|
|
|
assign->vals[(idx - 1) - i] = sstrdup(words[i]);
|
|
|
|
TAILQ_INSERT_TAIL(&ah, assign, link);
|
|
|
|
|
|
|
|
clear_words();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
define_add (const char *d __unused)
|
|
|
|
{
|
|
|
|
#ifdef notdef
|
|
|
|
fprintf(stderr, "define \"%s\"\n", d);
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
sstrdup(const char *str)
|
|
|
|
{
|
|
|
|
if (str != NULL && strlen(str))
|
|
|
|
return (strdup(str));
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
satoi (const char *nptr)
|
|
|
|
{
|
|
|
|
if (nptr != NULL && strlen(nptr))
|
|
|
|
return (atoi(nptr));
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
regkey_add (const char *r)
|
|
|
|
{
|
|
|
|
struct reg *reg;
|
|
|
|
|
|
|
|
reg = malloc(sizeof(struct reg));
|
|
|
|
bzero(reg, sizeof(struct reg));
|
|
|
|
reg->section = TAILQ_LAST(&sh, section_head);
|
|
|
|
reg->root = sstrdup(r);
|
|
|
|
reg->subkey = sstrdup(words[3]);
|
|
|
|
reg->key = sstrdup(words[2]);
|
|
|
|
reg->flags = satoi(words[1]);
|
|
|
|
reg->value = sstrdup(words[0]);
|
|
|
|
TAILQ_INSERT_TAIL(&rh, reg, link);
|
|
|
|
|
|
|
|
free(__DECONST(char *, r));
|
|
|
|
clear_words();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
push_word (const char *w)
|
|
|
|
{
|
|
|
|
if (w && strlen(w))
|
|
|
|
words[idx++] = w;
|
|
|
|
else
|
|
|
|
words[idx++] = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
clear_words (void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < idx; i++) {
|
|
|
|
if (words[i]) {
|
|
|
|
free(__DECONST(char *, words[i]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
idx = 0;
|
|
|
|
bzero(words, sizeof(words));
|
|
|
|
return;
|
|
|
|
}
|