Home | Info | Community | Development | myReactOS | Contact Us
ReactOS Development > Doxygeninfcore.c
Go to the documentation of this file.
00001 /* 00002 * PROJECT: .inf file parser 00003 * LICENSE: GPL - See COPYING in the top level directory 00004 * PROGRAMMER: Royce Mitchell III 00005 * Eric Kohl 00006 * Ge van Geldorp <gvg@reactos.org> 00007 */ 00008 00009 /* INCLUDES *****************************************************************/ 00010 00011 #include "inflib.h" 00012 00013 #define NDEBUG 00014 #include <debug.h> 00015 00016 #define CONTROL_Z '\x1a' 00017 #define MAX_SECTION_NAME_LEN 255 00018 #define MAX_FIELD_LEN 511 /* larger fields get silently truncated */ 00019 /* actual string limit is MAX_INF_STRING_LENGTH+1 (plus terminating null) under Windows */ 00020 #define MAX_STRING_LEN (MAX_INF_STRING_LENGTH+1) 00021 00022 00023 /* parser definitions */ 00024 00025 enum parser_state 00026 { 00027 LINE_START, /* at beginning of a line */ 00028 SECTION_NAME, /* parsing a section name */ 00029 KEY_NAME, /* parsing a key name */ 00030 VALUE_NAME, /* parsing a value name */ 00031 EOL_BACKSLASH, /* backslash at end of line */ 00032 QUOTES, /* inside quotes */ 00033 LEADING_SPACES, /* leading spaces */ 00034 TRAILING_SPACES, /* trailing spaces */ 00035 COMMENT, /* inside a comment */ 00036 NB_PARSER_STATES 00037 }; 00038 00039 struct parser 00040 { 00041 const CHAR *start; /* start position of item being parsed */ 00042 const CHAR *end; /* end of buffer */ 00043 PINFCACHE file; /* file being built */ 00044 enum parser_state state; /* current parser state */ 00045 enum parser_state stack[4]; /* state stack */ 00046 int stack_pos; /* current pos in stack */ 00047 00048 PINFCACHESECTION cur_section; /* pointer to the section being parsed*/ 00049 PINFCACHELINE line; /* current line */ 00050 unsigned int line_pos; /* current line position in file */ 00051 INFSTATUS error; /* error code */ 00052 unsigned int token_len; /* current token len */ 00053 TCHAR token[MAX_FIELD_LEN+1]; /* current token */ 00054 }; 00055 00056 typedef const CHAR * (*parser_state_func)( struct parser *parser, const CHAR *pos ); 00057 00058 /* parser state machine functions */ 00059 static const CHAR *line_start_state( struct parser *parser, const CHAR *pos ); 00060 static const CHAR *section_name_state( struct parser *parser, const CHAR *pos ); 00061 static const CHAR *key_name_state( struct parser *parser, const CHAR *pos ); 00062 static const CHAR *value_name_state( struct parser *parser, const CHAR *pos ); 00063 static const CHAR *eol_backslash_state( struct parser *parser, const CHAR *pos ); 00064 static const CHAR *quotes_state( struct parser *parser, const CHAR *pos ); 00065 static const CHAR *leading_spaces_state( struct parser *parser, const CHAR *pos ); 00066 static const CHAR *trailing_spaces_state( struct parser *parser, const CHAR *pos ); 00067 static const CHAR *comment_state( struct parser *parser, const CHAR *pos ); 00068 00069 static const parser_state_func parser_funcs[NB_PARSER_STATES] = 00070 { 00071 line_start_state, /* LINE_START */ 00072 section_name_state, /* SECTION_NAME */ 00073 key_name_state, /* KEY_NAME */ 00074 value_name_state, /* VALUE_NAME */ 00075 eol_backslash_state, /* EOL_BACKSLASH */ 00076 quotes_state, /* QUOTES */ 00077 leading_spaces_state, /* LEADING_SPACES */ 00078 trailing_spaces_state, /* TRAILING_SPACES */ 00079 comment_state /* COMMENT */ 00080 }; 00081 00082 00083 /* PRIVATE FUNCTIONS ********************************************************/ 00084 00085 static PINFCACHELINE 00086 InfpFreeLine (PINFCACHELINE Line) 00087 { 00088 PINFCACHELINE Next; 00089 PINFCACHEFIELD Field; 00090 00091 if (Line == NULL) 00092 { 00093 return NULL; 00094 } 00095 00096 Next = Line->Next; 00097 if (Line->Key != NULL) 00098 { 00099 FREE (Line->Key); 00100 Line->Key = NULL; 00101 } 00102 00103 /* Remove data fields */ 00104 while (Line->FirstField != NULL) 00105 { 00106 Field = Line->FirstField->Next; 00107 FREE (Line->FirstField); 00108 Line->FirstField = Field; 00109 } 00110 Line->LastField = NULL; 00111 00112 FREE (Line); 00113 00114 return Next; 00115 } 00116 00117 00118 PINFCACHESECTION 00119 InfpFreeSection (PINFCACHESECTION Section) 00120 { 00121 PINFCACHESECTION Next; 00122 00123 if (Section == NULL) 00124 { 00125 return NULL; 00126 } 00127 00128 /* Release all keys */ 00129 Next = Section->Next; 00130 while (Section->FirstLine != NULL) 00131 { 00132 Section->FirstLine = InfpFreeLine (Section->FirstLine); 00133 } 00134 Section->LastLine = NULL; 00135 00136 FREE (Section); 00137 00138 return Next; 00139 } 00140 00141 00142 PINFCACHESECTION 00143 InfpFindSection(PINFCACHE Cache, 00144 PCTSTR Name) 00145 { 00146 PINFCACHESECTION Section = NULL; 00147 00148 if (Cache == NULL || Name == NULL) 00149 { 00150 return NULL; 00151 } 00152 00153 /* iterate through list of sections */ 00154 Section = Cache->FirstSection; 00155 while (Section != NULL) 00156 { 00157 if (_tcsicmp (Section->Name, Name) == 0) 00158 { 00159 return Section; 00160 } 00161 00162 /* get the next section*/ 00163 Section = Section->Next; 00164 } 00165 00166 return NULL; 00167 } 00168 00169 00170 PINFCACHESECTION 00171 InfpAddSection(PINFCACHE Cache, 00172 PCTSTR Name) 00173 { 00174 PINFCACHESECTION Section = NULL; 00175 ULONG Size; 00176 00177 if (Cache == NULL || Name == NULL) 00178 { 00179 DPRINT("Invalid parameter\n"); 00180 return NULL; 00181 } 00182 00183 /* Allocate and initialize the new section */ 00184 Size = (ULONG)FIELD_OFFSET(INFCACHESECTION, 00185 Name[_tcslen (Name) + 1]); 00186 Section = (PINFCACHESECTION)MALLOC (Size); 00187 if (Section == NULL) 00188 { 00189 DPRINT("MALLOC() failed\n"); 00190 return NULL; 00191 } 00192 ZEROMEMORY (Section, 00193 Size); 00194 00195 /* Copy section name */ 00196 _tcscpy (Section->Name, Name); 00197 00198 /* Append section */ 00199 if (Cache->FirstSection == NULL) 00200 { 00201 Cache->FirstSection = Section; 00202 Cache->LastSection = Section; 00203 } 00204 else 00205 { 00206 Cache->LastSection->Next = Section; 00207 Section->Prev = Cache->LastSection; 00208 Cache->LastSection = Section; 00209 } 00210 00211 return Section; 00212 } 00213 00214 00215 PINFCACHELINE 00216 InfpAddLine(PINFCACHESECTION Section) 00217 { 00218 PINFCACHELINE Line; 00219 00220 if (Section == NULL) 00221 { 00222 DPRINT("Invalid parameter\n"); 00223 return NULL; 00224 } 00225 00226 Line = (PINFCACHELINE)MALLOC (sizeof(INFCACHELINE)); 00227 if (Line == NULL) 00228 { 00229 DPRINT("MALLOC() failed\n"); 00230 return NULL; 00231 } 00232 ZEROMEMORY(Line, 00233 sizeof(INFCACHELINE)); 00234 00235 /* Append line */ 00236 if (Section->FirstLine == NULL) 00237 { 00238 Section->FirstLine = Line; 00239 Section->LastLine = Line; 00240 } 00241 else 00242 { 00243 Section->LastLine->Next = Line; 00244 Line->Prev = Section->LastLine; 00245 Section->LastLine = Line; 00246 } 00247 Section->LineCount++; 00248 00249 return Line; 00250 } 00251 00252 00253 PVOID 00254 InfpAddKeyToLine(PINFCACHELINE Line, 00255 PCTSTR Key) 00256 { 00257 if (Line == NULL) 00258 { 00259 DPRINT1("Invalid Line\n"); 00260 return NULL; 00261 } 00262 00263 if (Line->Key != NULL) 00264 { 00265 DPRINT1("Line already has a key\n"); 00266 return NULL; 00267 } 00268 00269 Line->Key = (PTCHAR)MALLOC((_tcslen(Key) + 1) * sizeof(TCHAR)); 00270 if (Line->Key == NULL) 00271 { 00272 DPRINT1("MALLOC() failed\n"); 00273 return NULL; 00274 } 00275 00276 _tcscpy(Line->Key, Key); 00277 00278 return (PVOID)Line->Key; 00279 } 00280 00281 00282 PVOID 00283 InfpAddFieldToLine(PINFCACHELINE Line, 00284 PCTSTR Data) 00285 { 00286 PINFCACHEFIELD Field; 00287 ULONG Size; 00288 00289 Size = (ULONG)FIELD_OFFSET(INFCACHEFIELD, 00290 Data[_tcslen(Data) + 1]); 00291 Field = (PINFCACHEFIELD)MALLOC(Size); 00292 if (Field == NULL) 00293 { 00294 DPRINT1("MALLOC() failed\n"); 00295 return NULL; 00296 } 00297 ZEROMEMORY (Field, 00298 Size); 00299 _tcscpy (Field->Data, Data); 00300 00301 /* Append key */ 00302 if (Line->FirstField == NULL) 00303 { 00304 Line->FirstField = Field; 00305 Line->LastField = Field; 00306 } 00307 else 00308 { 00309 Line->LastField->Next = Field; 00310 Field->Prev = Line->LastField; 00311 Line->LastField = Field; 00312 } 00313 Line->FieldCount++; 00314 00315 return (PVOID)Field; 00316 } 00317 00318 00319 PINFCACHELINE 00320 InfpFindKeyLine(PINFCACHESECTION Section, 00321 PCTSTR Key) 00322 { 00323 PINFCACHELINE Line; 00324 00325 Line = Section->FirstLine; 00326 while (Line != NULL) 00327 { 00328 if (Line->Key != NULL && _tcsicmp (Line->Key, Key) == 0) 00329 { 00330 return Line; 00331 } 00332 00333 Line = Line->Next; 00334 } 00335 00336 return NULL; 00337 } 00338 00339 00340 /* push the current state on the parser stack */ 00341 __inline static void push_state( struct parser *parser, enum parser_state state ) 00342 { 00343 // assert( parser->stack_pos < sizeof(parser->stack)/sizeof(parser->stack[0]) ); 00344 parser->stack[parser->stack_pos++] = state; 00345 } 00346 00347 00348 /* pop the current state */ 00349 __inline static void pop_state( struct parser *parser ) 00350 { 00351 // assert( parser->stack_pos ); 00352 parser->state = parser->stack[--parser->stack_pos]; 00353 } 00354 00355 00356 /* set the parser state and return the previous one */ 00357 __inline static enum parser_state set_state( struct parser *parser, enum parser_state state ) 00358 { 00359 enum parser_state ret = parser->state; 00360 parser->state = state; 00361 return ret; 00362 } 00363 00364 00365 /* check if the pointer points to an end of file */ 00366 __inline static int is_eof( struct parser *parser, const CHAR *ptr ) 00367 { 00368 return (ptr >= parser->end || *ptr == CONTROL_Z); 00369 } 00370 00371 00372 /* check if the pointer points to an end of line */ 00373 __inline static int is_eol( struct parser *parser, const CHAR *ptr ) 00374 { 00375 return (ptr >= parser->end || 00376 *ptr == CONTROL_Z || 00377 *ptr == '\n' || 00378 (*ptr == '\r' && *(ptr + 1) == '\n')); 00379 } 00380 00381 00382 /* push data from current token start up to pos into the current token */ 00383 static int push_token( struct parser *parser, const CHAR *pos ) 00384 { 00385 UINT len = (UINT)(pos - parser->start); 00386 const CHAR *src = parser->start; 00387 TCHAR *dst = parser->token + parser->token_len; 00388 00389 if (len > MAX_FIELD_LEN - parser->token_len) 00390 len = MAX_FIELD_LEN - parser->token_len; 00391 00392 parser->token_len += len; 00393 for ( ; len > 0; len--, dst++, src++) 00394 { 00395 if (*src) 00396 { 00397 *dst = *src; 00398 } 00399 else 00400 { 00401 *dst = _T(' '); 00402 } 00403 } 00404 00405 *dst = 0; 00406 parser->start = pos; 00407 00408 return 0; 00409 } 00410 00411 00412 00413 /* add a section with the current token as name */ 00414 static PVOID add_section_from_token( struct parser *parser ) 00415 { 00416 PINFCACHESECTION Section; 00417 00418 if (parser->token_len > MAX_SECTION_NAME_LEN) 00419 { 00420 parser->error = INF_STATUS_SECTION_NAME_TOO_LONG; 00421 return NULL; 00422 } 00423 00424 Section = InfpFindSection(parser->file, 00425 parser->token); 00426 if (Section == NULL) 00427 { 00428 /* need to create a new one */ 00429 Section= InfpAddSection(parser->file, 00430 parser->token); 00431 if (Section == NULL) 00432 { 00433 parser->error = INF_STATUS_NOT_ENOUGH_MEMORY; 00434 return NULL; 00435 } 00436 } 00437 00438 parser->token_len = 0; 00439 parser->cur_section = Section; 00440 00441 return (PVOID)Section; 00442 } 00443 00444 00445 /* add a field containing the current token to the current line */ 00446 static struct field *add_field_from_token( struct parser *parser, int is_key ) 00447 { 00448 PVOID field; 00449 00450 if (!parser->line) /* need to start a new line */ 00451 { 00452 if (parser->cur_section == NULL) /* got a line before the first section */ 00453 { 00454 parser->error = INF_STATUS_WRONG_INF_STYLE; 00455 return NULL; 00456 } 00457 00458 parser->line = InfpAddLine(parser->cur_section); 00459 if (parser->line == NULL) 00460 goto error; 00461 } 00462 else 00463 { 00464 // assert(!is_key); 00465 } 00466 00467 if (is_key) 00468 { 00469 field = InfpAddKeyToLine(parser->line, parser->token); 00470 } 00471 else 00472 { 00473 field = InfpAddFieldToLine(parser->line, parser->token); 00474 } 00475 00476 if (field != NULL) 00477 { 00478 parser->token_len = 0; 00479 return field; 00480 } 00481 00482 error: 00483 parser->error = INF_STATUS_NOT_ENOUGH_MEMORY; 00484 return NULL; 00485 } 00486 00487 00488 /* close the current line and prepare for parsing a new one */ 00489 static void close_current_line( struct parser *parser ) 00490 { 00491 parser->line = NULL; 00492 } 00493 00494 00495 00496 /* handler for parser LINE_START state */ 00497 static const CHAR *line_start_state( struct parser *parser, const CHAR *pos ) 00498 { 00499 const CHAR *p; 00500 00501 for (p = pos; !is_eof( parser, p ); p++) 00502 { 00503 switch(*p) 00504 { 00505 case '\r': 00506 continue; 00507 00508 case '\n': 00509 parser->line_pos++; 00510 close_current_line( parser ); 00511 break; 00512 00513 case ';': 00514 push_state( parser, LINE_START ); 00515 set_state( parser, COMMENT ); 00516 return p + 1; 00517 00518 case '[': 00519 parser->start = p + 1; 00520 set_state( parser, SECTION_NAME ); 00521 return p + 1; 00522 00523 default: 00524 if (!isspace(*p)) 00525 { 00526 parser->start = p; 00527 set_state( parser, KEY_NAME ); 00528 return p; 00529 } 00530 break; 00531 } 00532 } 00533 close_current_line( parser ); 00534 return NULL; 00535 } 00536 00537 00538 /* handler for parser SECTION_NAME state */ 00539 static const CHAR *section_name_state( struct parser *parser, const CHAR *pos ) 00540 { 00541 const CHAR *p; 00542 00543 for (p = pos; !is_eol( parser, p ); p++) 00544 { 00545 if (*p == ']') 00546 { 00547 push_token( parser, p ); 00548 if (add_section_from_token( parser ) == NULL) 00549 return NULL; 00550 push_state( parser, LINE_START ); 00551 set_state( parser, COMMENT ); /* ignore everything else on the line */ 00552 return p + 1; 00553 } 00554 } 00555 parser->error = INF_STATUS_BAD_SECTION_NAME_LINE; /* unfinished section name */ 00556 return NULL; 00557 } 00558 00559 00560 /* handler for parser KEY_NAME state */ 00561 static const CHAR *key_name_state( struct parser *parser, const CHAR *pos ) 00562 { 00563 const CHAR *p, *token_end = parser->start; 00564 00565 for (p = pos; !is_eol( parser, p ); p++) 00566 { 00567 if (*p == ',') break; 00568 switch(*p) 00569 { 00570 00571 case '=': 00572 push_token( parser, token_end ); 00573 if (!add_field_from_token( parser, 1 )) return NULL; 00574 parser->start = p + 1; 00575 push_state( parser, VALUE_NAME ); 00576 set_state( parser, LEADING_SPACES ); 00577 return p + 1; 00578 case ';': 00579 push_token( parser, token_end ); 00580 if (!add_field_from_token( parser, 0 )) return NULL; 00581 push_state( parser, LINE_START ); 00582 set_state( parser, COMMENT ); 00583 return p + 1; 00584 case '"': 00585 push_token( parser, token_end ); 00586 parser->start = p + 1; 00587 push_state( parser, KEY_NAME ); 00588 set_state( parser, QUOTES ); 00589 return p + 1; 00590 case '\\': 00591 push_token( parser, token_end ); 00592 parser->start = p; 00593 push_state( parser, KEY_NAME ); 00594 set_state( parser, EOL_BACKSLASH ); 00595 return p; 00596 default: 00597 if (!isspace(*p)) token_end = p + 1; 00598 else 00599 { 00600 push_token( parser, p ); 00601 push_state( parser, KEY_NAME ); 00602 set_state( parser, TRAILING_SPACES ); 00603 return p; 00604 } 00605 break; 00606 } 00607 } 00608 push_token( parser, token_end ); 00609 set_state( parser, VALUE_NAME ); 00610 return p; 00611 } 00612 00613 00614 /* handler for parser VALUE_NAME state */ 00615 static const CHAR *value_name_state( struct parser *parser, const CHAR *pos ) 00616 { 00617 const CHAR *p, *token_end = parser->start; 00618 00619 for (p = pos; !is_eol( parser, p ); p++) 00620 { 00621 switch(*p) 00622 { 00623 case ';': 00624 push_token( parser, token_end ); 00625 if (!add_field_from_token( parser, 0 )) return NULL; 00626 push_state( parser, LINE_START ); 00627 set_state( parser, COMMENT ); 00628 return p + 1; 00629 case ',': 00630 push_token( parser, token_end ); 00631 if (!add_field_from_token( parser, 0 )) return NULL; 00632 parser->start = p + 1; 00633 push_state( parser, VALUE_NAME ); 00634 set_state( parser, LEADING_SPACES ); 00635 return p + 1; 00636 case '"': 00637 push_token( parser, token_end ); 00638 parser->start = p + 1; 00639 push_state( parser, VALUE_NAME ); 00640 set_state( parser, QUOTES ); 00641 return p + 1; 00642 case '\\': 00643 push_token( parser, token_end ); 00644 parser->start = p; 00645 push_state( parser, VALUE_NAME ); 00646 set_state( parser, EOL_BACKSLASH ); 00647 return p; 00648 default: 00649 if (!isspace(*p)) token_end = p + 1; 00650 else 00651 { 00652 push_token( parser, p ); 00653 push_state( parser, VALUE_NAME ); 00654 set_state( parser, TRAILING_SPACES ); 00655 return p; 00656 } 00657 break; 00658 } 00659 } 00660 push_token( parser, token_end ); 00661 if (!add_field_from_token( parser, 0 )) return NULL; 00662 set_state( parser, LINE_START ); 00663 return p; 00664 } 00665 00666 00667 /* handler for parser EOL_BACKSLASH state */ 00668 static const CHAR *eol_backslash_state( struct parser *parser, const CHAR *pos ) 00669 { 00670 const CHAR *p; 00671 00672 for (p = pos; !is_eof( parser, p ); p++) 00673 { 00674 switch(*p) 00675 { 00676 case '\r': 00677 continue; 00678 00679 case '\n': 00680 parser->line_pos++; 00681 parser->start = p + 1; 00682 set_state( parser, LEADING_SPACES ); 00683 return p + 1; 00684 00685 case '\\': 00686 continue; 00687 00688 case ';': 00689 push_state( parser, EOL_BACKSLASH ); 00690 set_state( parser, COMMENT ); 00691 return p + 1; 00692 00693 default: 00694 if (isspace(*p)) 00695 continue; 00696 push_token( parser, p ); 00697 pop_state( parser ); 00698 return p; 00699 } 00700 } 00701 parser->start = p; 00702 pop_state( parser ); 00703 00704 return p; 00705 } 00706 00707 00708 /* handler for parser QUOTES state */ 00709 static const CHAR *quotes_state( struct parser *parser, const CHAR *pos ) 00710 { 00711 const CHAR *p, *token_end = parser->start; 00712 00713 for (p = pos; !is_eol( parser, p ); p++) 00714 { 00715 if (*p == '"') 00716 { 00717 if (p+1 < parser->end && p[1] == '"') /* double quotes */ 00718 { 00719 push_token( parser, p + 1 ); 00720 parser->start = token_end = p + 2; 00721 p++; 00722 } 00723 else /* end of quotes */ 00724 { 00725 push_token( parser, p ); 00726 parser->start = p + 1; 00727 pop_state( parser ); 00728 return p + 1; 00729 } 00730 } 00731 } 00732 push_token( parser, p ); 00733 pop_state( parser ); 00734 return p; 00735 } 00736 00737 00738 /* handler for parser LEADING_SPACES state */ 00739 static const CHAR *leading_spaces_state( struct parser *parser, const CHAR *pos ) 00740 { 00741 const CHAR *p; 00742 00743 for (p = pos; !is_eol( parser, p ); p++) 00744 { 00745 if (*p == '\\') 00746 { 00747 parser->start = p; 00748 set_state( parser, EOL_BACKSLASH ); 00749 return p; 00750 } 00751 if (!isspace(*p)) 00752 break; 00753 } 00754 parser->start = p; 00755 pop_state( parser ); 00756 return p; 00757 } 00758 00759 00760 /* handler for parser TRAILING_SPACES state */ 00761 static const CHAR *trailing_spaces_state( struct parser *parser, const CHAR *pos ) 00762 { 00763 const CHAR *p; 00764 00765 for (p = pos; !is_eol( parser, p ); p++) 00766 { 00767 if (*p == '\\') 00768 { 00769 set_state( parser, EOL_BACKSLASH ); 00770 return p; 00771 } 00772 if (!isspace(*p)) 00773 break; 00774 } 00775 pop_state( parser ); 00776 return p; 00777 } 00778 00779 00780 /* handler for parser COMMENT state */ 00781 static const CHAR *comment_state( struct parser *parser, const CHAR *pos ) 00782 { 00783 const CHAR *p = pos; 00784 00785 while (!is_eol( parser, p )) 00786 p++; 00787 pop_state( parser ); 00788 return p; 00789 } 00790 00791 00792 /* parse a complete buffer */ 00793 INFSTATUS 00794 InfpParseBuffer (PINFCACHE file, 00795 const CHAR *buffer, 00796 const CHAR *end, 00797 PULONG error_line) 00798 { 00799 struct parser parser; 00800 const CHAR *pos = buffer; 00801 00802 parser.start = buffer; 00803 parser.end = end; 00804 parser.file = file; 00805 parser.line = NULL; 00806 parser.state = LINE_START; 00807 parser.stack_pos = 0; 00808 parser.cur_section = NULL; 00809 parser.line_pos = 1; 00810 parser.error = 0; 00811 parser.token_len = 0; 00812 00813 /* parser main loop */ 00814 while (pos) 00815 pos = (parser_funcs[parser.state])(&parser, pos); 00816 00817 if (parser.error) 00818 { 00819 if (error_line) 00820 *error_line = parser.line_pos; 00821 return parser.error; 00822 } 00823 00824 /* find the [strings] section */ 00825 file->StringsSection = InfpFindSection(file, 00826 _T("Strings")); 00827 00828 return INF_STATUS_SUCCESS; 00829 } 00830 00831 /* EOF */ Generated on Sun May 27 2012 04:36:14 for ReactOS by
1.7.6.1
|