comparison src/xs_length.c @ 909:84394ee26545

Cleanups.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 10 Nov 2012 12:29:16 +0200
parents 37ab2fba30c8
children 560cdf7f1fec
comparison
equal deleted inserted replaced
908:8b4c016802ea 909:84394ee26545
29 29
30 /* Free memory allocated for given SLDB node 30 /* Free memory allocated for given SLDB node
31 */ 31 */
32 static void xs_sldb_node_free(XSSLDBNode *node) 32 static void xs_sldb_node_free(XSSLDBNode *node)
33 { 33 {
34 if (node) { 34 if (node)
35 /* Nothing much to do here ... */ 35 {
36 g_free(node->lengths); 36 g_free(node->lengths);
37 g_free(node); 37 g_free(node);
38 } 38 }
39 } 39 }
40 40
43 */ 43 */
44 static void xs_sldb_node_insert(XSSLDB *db, XSSLDBNode *node) 44 static void xs_sldb_node_insert(XSSLDB *db, XSSLDBNode *node)
45 { 45 {
46 assert(db != NULL); 46 assert(db != NULL);
47 47
48 if (db->nodes) { 48 if (db->nodes)
49 {
49 node->prev = db->nodes->prev; 50 node->prev = db->nodes->prev;
50 db->nodes->prev->next = node; 51 db->nodes->prev->next = node;
51 db->nodes->prev = node; 52 db->nodes->prev = node;
52 } else { 53 }
54 else
55 {
53 db->nodes = node; 56 db->nodes = node;
54 node->prev = node; 57 node->prev = node;
55 } 58 }
56 node->next = NULL; 59 node->next = NULL;
57 } 60 }
62 static gint xs_sldb_gettime(gchar *str, size_t *pos) 65 static gint xs_sldb_gettime(gchar *str, size_t *pos)
63 { 66 {
64 gint result, tmp; 67 gint result, tmp;
65 68
66 /* Check if it starts with a digit */ 69 /* Check if it starts with a digit */
67 if (isdigit(str[*pos])) { 70 if (isdigit(str[*pos]))
71 {
68 /* Get minutes-field */ 72 /* Get minutes-field */
69 result = 0; 73 result = 0;
70 while (isdigit(str[*pos])) 74 while (isdigit(str[*pos]))
71 result = (result * 10) + (str[(*pos)++] - '0'); 75 result = (result * 10) + (str[(*pos)++] - '0');
72 76
73 result *= 60; 77 result *= 60;
74 78
75 /* Check the field separator char */ 79 /* Check the field separator char */
76 if (str[*pos] == ':') { 80 if (str[*pos] == ':')
81 {
77 /* Get seconds-field */ 82 /* Get seconds-field */
78 (*pos)++; 83 (*pos)++;
79 tmp = 0; 84 tmp = 0;
80 while (isdigit(str[*pos])) { 85 while (isdigit(str[*pos]))
81 tmp = (tmp * 10) + (str[(*pos)++] - '0'); 86 tmp = (tmp * 10) + (str[(*pos)++] - '0');
82 }
83 87
84 result += tmp; 88 result += tmp;
85 } else 89 }
90 else
86 result = -2; 91 result = -2;
87 } else 92 }
93 else
88 result = -1; 94 result = -1;
89 95
90 /* Ignore and skip the possible attributes */ 96 /* Ignore and skip the possible attributes */
91 while (str[*pos] && !isspace(str[*pos])) 97 while (str[*pos] && !isspace(str[*pos]))
92 (*pos)++; 98 (*pos)++;
104 gboolean isOK; 110 gboolean isOK;
105 XSSLDBNode *tmnode; 111 XSSLDBNode *tmnode;
106 112
107 /* Allocate new node */ 113 /* Allocate new node */
108 tmnode = (XSSLDBNode *) g_malloc0(sizeof(XSSLDBNode)); 114 tmnode = (XSSLDBNode *) g_malloc0(sizeof(XSSLDBNode));
109 if (!tmnode) { 115 if (!tmnode)
116 {
110 xs_error("Error allocating new node. Fatal error.\n"); 117 xs_error("Error allocating new node. Fatal error.\n");
111 return NULL; 118 return NULL;
112 } 119 }
113 120
114 /* Get hash value */ 121 /* Get hash value */
115 linePos = 0; 122 linePos = 0;
116 for (i = 0; i < XS_MD5HASH_LENGTH; i++, linePos += 2) { 123 for (i = 0; i < XS_MD5HASH_LENGTH; i++, linePos += 2)
124 {
117 gint tmpu; 125 gint tmpu;
118 sscanf(&inLine[linePos], "%2x", &tmpu); 126 sscanf(&inLine[linePos], "%2x", &tmpu);
119 tmnode->md5Hash[i] = tmpu; 127 tmnode->md5Hash[i] = tmpu;
120 } 128 }
121 129
122 /* Get playtimes */ 130 /* Get playtimes */
123 if (inLine[linePos] != 0) { 131 if (inLine[linePos] != 0)
124 if (inLine[linePos] != '=') { 132 {
133 if (inLine[linePos] != '=')
134 {
125 xs_error("'=' expected on column #%d.\n", linePos); 135 xs_error("'=' expected on column #%d.\n", linePos);
126 xs_sldb_node_free(tmnode); 136 xs_sldb_node_free(tmnode);
127 return NULL; 137 return NULL;
128 } else { 138 }
139 else
140 {
129 size_t tmpLen, savePos; 141 size_t tmpLen, savePos;
130 142
131 /* First playtime is after '=' */ 143 /* First playtime is after '=' */
132 savePos = ++linePos; 144 savePos = ++linePos;
133 tmpLen = strlen(inLine); 145 tmpLen = strlen(inLine);
134 146
135 /* Get number of sub-tune lengths */ 147 /* Get number of sub-tune lengths */
136 isOK = TRUE; 148 isOK = TRUE;
137 while ((linePos < tmpLen) && isOK) { 149 while (linePos < tmpLen && isOK)
150 {
138 xs_findnext(inLine, &linePos); 151 xs_findnext(inLine, &linePos);
139 152
140 if (xs_sldb_gettime(inLine, &linePos) >= 0) 153 if (xs_sldb_gettime(inLine, &linePos) >= 0)
141 tmnode->nlengths++; 154 tmnode->nlengths++;
142 else 155 else
143 isOK = FALSE; 156 isOK = FALSE;
144 } 157 }
145 158
146 /* Allocate memory for lengths */ 159 /* Allocate memory for lengths */
147 if (tmnode->nlengths > 0) { 160 if (tmnode->nlengths > 0)
161 {
148 tmnode->lengths = (gint *) g_malloc0(tmnode->nlengths * sizeof(gint)); 162 tmnode->lengths = (gint *) g_malloc0(tmnode->nlengths * sizeof(gint));
149 if (!tmnode->lengths) { 163 if (!tmnode->lengths)
164 {
150 xs_error("Could not allocate memory for node.\n"); 165 xs_error("Could not allocate memory for node.\n");
151 xs_sldb_node_free(tmnode); 166 xs_sldb_node_free(tmnode);
152 return NULL; 167 return NULL;
153 } 168 }
154 } else { 169 }
170 else
171 {
155 xs_sldb_node_free(tmnode); 172 xs_sldb_node_free(tmnode);
156 return NULL; 173 return NULL;
157 } 174 }
158 175
159 /* Read lengths in */ 176 /* Read lengths in */
160 i = 0; 177 for (i = 0, linePos = savePos, isOK = TRUE;
161 linePos = savePos; 178 linePos < tmpLen && i < tmnode->nlengths && isOK; i++)
162 isOK = TRUE; 179 {
163 while ((linePos < tmpLen) && (i < tmnode->nlengths) && isOK) {
164 gint l; 180 gint l;
165 181
166 xs_findnext(inLine, &linePos); 182 xs_findnext(inLine, &linePos);
167 183
168 l = xs_sldb_gettime(inLine, &linePos); 184 l = xs_sldb_gettime(inLine, &linePos);
172 isOK = FALSE; 188 isOK = FALSE;
173 189
174 i++; 190 i++;
175 } 191 }
176 192
177 if (!isOK) { 193 if (!isOK)
194 {
178 xs_sldb_node_free(tmnode); 195 xs_sldb_node_free(tmnode);
179 return NULL; 196 return NULL;
180 } else 197 }
198 else
181 return tmnode; 199 return tmnode;
182 } 200 }
183 } 201 }
184 202
185 return NULL; 203 return NULL;
195 size_t lineNum; 213 size_t lineNum;
196 XSSLDBNode *tmnode; 214 XSSLDBNode *tmnode;
197 assert(db); 215 assert(db);
198 216
199 /* Try to open the file */ 217 /* Try to open the file */
200 if ((inFile = fopen(dbFilename, "ra")) == NULL) { 218 if ((inFile = fopen(dbFilename, "ra")) == NULL)
219 {
201 xs_error("Could not open SongLengthDB '%s'\n", dbFilename); 220 xs_error("Could not open SongLengthDB '%s'\n", dbFilename);
202 return -1; 221 return -1;
203 } 222 }
204 223
205 /* Read and parse the data */ 224 /* Read and parse the data */
206 lineNum = 0; 225 lineNum = 0;
207 226
208 while (fgets(inLine, XS_BUF2_SIZE, inFile) != NULL) { 227 while (fgets(inLine, XS_BUF2_SIZE, inFile) != NULL)
228 {
209 size_t linePos = 0; 229 size_t linePos = 0;
210 lineNum++; 230 lineNum++;
211 231
212 xs_findnext(inLine, &linePos); 232 xs_findnext(inLine, &linePos);
213 233
214 /* Check if it is datafield */ 234 /* Check if it is datafield */
215 if (isxdigit(inLine[linePos])) { 235 if (isxdigit(inLine[linePos]))
236 {
216 /* Check the length of the hash */ 237 /* Check the length of the hash */
217 gint hashLen; 238 gint hashLen;
218 for (hashLen = 0; inLine[linePos] && isxdigit(inLine[linePos]); hashLen++, linePos++); 239 for (hashLen = 0; inLine[linePos] && isxdigit(inLine[linePos]); hashLen++, linePos++);
219 240
220 if (hashLen != XS_MD5HASH_LENGTH_CH) { 241 if (hashLen != XS_MD5HASH_LENGTH_CH)
242 {
221 xs_error("Invalid MD5-hash in SongLengthDB file '%s' line #%d:\n%s\n", 243 xs_error("Invalid MD5-hash in SongLengthDB file '%s' line #%d:\n%s\n",
222 dbFilename, lineNum, inLine); 244 dbFilename, lineNum, inLine);
223 } else { 245 }
246 else
247 {
224 /* Parse and add node to db */ 248 /* Parse and add node to db */
225 if ((tmnode = xs_sldb_read_entry(inLine)) != NULL) { 249 if ((tmnode = xs_sldb_read_entry(inLine)) != NULL)
250 {
226 xs_sldb_node_insert(db, tmnode); 251 xs_sldb_node_insert(db, tmnode);
227 } else { 252 }
253 else
254 {
228 xs_error("Invalid entry in SongLengthDB file '%s' line #%d:\n%s\n", 255 xs_error("Invalid entry in SongLengthDB file '%s' line #%d:\n%s\n",
229 dbFilename, lineNum, inLine); 256 dbFilename, lineNum, inLine);
230 } 257 }
231 } 258 }
232 } else if (inLine[linePos] != ';' && inLine[linePos] != '[' && inLine[linePos] != 0) { 259 }
260 else
261 if (inLine[linePos] != ';' && inLine[linePos] != '[' && inLine[linePos] != 0)
262 {
233 xs_error("Invalid line in SongLengthDB file '%s' line #%d:\n%s\n", 263 xs_error("Invalid line in SongLengthDB file '%s' line #%d:\n%s\n",
234 dbFilename, lineNum, inLine); 264 dbFilename, lineNum, inLine);
235 } 265 }
236 266
237 } 267 }
238 268
239 /* Close the file */
240 fclose(inFile); 269 fclose(inFile);
241
242 return 0; 270 return 0;
243 } 271 }
244 272
245 273
246 /* Compare two given MD5-hashes. 274 /* Compare two given MD5-hashes.
273 301
274 /* (Re)create index 302 /* (Re)create index
275 */ 303 */
276 gint xs_sldb_index(XSSLDB * db) 304 gint xs_sldb_index(XSSLDB * db)
277 { 305 {
278 XSSLDBNode *pCurr; 306 XSSLDBNode *node;
279 size_t i; 307 size_t i;
280 assert(db); 308 assert(db);
281 309
282 /* Free old index */ 310 /* Free old index */
283 if (db->pindex) { 311 g_free(db->pindex);
284 g_free(db->pindex); 312 db->pindex = NULL;
285 db->pindex = NULL;
286 }
287 313
288 /* Get size of db */ 314 /* Get size of db */
289 pCurr = db->nodes; 315 for (node = db->nodes, db->n = 0; node != NULL; node = node->next)
290 db->n = 0;
291 while (pCurr) {
292 db->n++; 316 db->n++;
293 pCurr = pCurr->next;
294 }
295 317
296 /* Check number of nodes */ 318 /* Check number of nodes */
297 if (db->n > 0) { 319 if (db->n > 0)
320 {
298 /* Allocate memory for index-table */ 321 /* Allocate memory for index-table */
299 db->pindex = (XSSLDBNode **) g_malloc(sizeof(XSSLDBNode *) * db->n); 322 db->pindex = (XSSLDBNode **) g_malloc(sizeof(XSSLDBNode *) * db->n);
300 if (!db->pindex) 323 if (!db->pindex)
301 return -1; 324 return -1;
302 325
303 /* Get node-pointers to table */ 326 /* Get node-pointers to table */
304 i = 0; 327 for (i = 0, node = db->nodes; node && i < db->n; node = node->next)
305 pCurr = db->nodes; 328 db->pindex[i++] = node;
306 while (pCurr && (i < db->n)) {
307 db->pindex[i++] = pCurr;
308 pCurr = pCurr->next;
309 }
310 329
311 /* Sort the indexes */ 330 /* Sort the indexes */
312 qsort(db->pindex, db->n, sizeof(XSSLDBNode *), xs_sldb_cmp); 331 qsort(db->pindex, db->n, sizeof(XSSLDBNode *), xs_sldb_cmp);
313 } 332 }
314 333
318 337
319 /* Free a given song-length database 338 /* Free a given song-length database
320 */ 339 */
321 void xs_sldb_free(XSSLDB * db) 340 void xs_sldb_free(XSSLDB * db)
322 { 341 {
323 XSSLDBNode *pCurr, *next; 342 XSSLDBNode *node, *next;
324 343
325 if (!db) 344 if (!db)
326 return; 345 return;
327 346
328 /* Free the memory allocated for nodes */ 347 /* Free the memory allocated for nodes */
329 pCurr = db->nodes; 348 node = db->nodes;
330 while (pCurr) { 349 while (node != NULL)
331 next = pCurr->next; 350 {
332 xs_sldb_node_free(pCurr); 351 next = node->next;
333 pCurr = next; 352 xs_sldb_node_free(node);
353 node = next;
334 } 354 }
335 355
336 db->nodes = NULL; 356 db->nodes = NULL;
337 357
338 /* Free memory allocated for index */ 358 /* Free memory allocated for index */
339 if (db->pindex) { 359 g_free(db->pindex);
340 g_free(db->pindex); 360 db->pindex = NULL;
341 db->pindex = NULL;
342 }
343 361
344 /* Free structure */ 362 /* Free structure */
345 db->n = 0; 363 db->n = 0;
346 g_free(db); 364 g_free(db);
347 } 365 }
348 366
349 367
350 /* Compute md5hash of given SID-file 368 /* Compute md5hash of given SID-file
351 */ 369 */
352 typedef struct { 370 typedef struct
371 {
353 gchar magicID[4]; /* "PSID" / "RSID" magic identifier */ 372 gchar magicID[4]; /* "PSID" / "RSID" magic identifier */
354 guint16 version, /* Version number */ 373 guint16 version, /* Version number */
355 dataOffset, /* Start of actual c64 data in file */ 374 dataOffset, /* Start of actual c64 data in file */
356 loadAddress, /* Loading address */ 375 loadAddress, /* Loading address */
357 initAddress, /* Initialization address */ 376 initAddress, /* Initialization address */
363 gchar sidAuthor[32]; 382 gchar sidAuthor[32];
364 gchar sidCopyright[32]; 383 gchar sidCopyright[32];
365 } psidv1_header_t; 384 } psidv1_header_t;
366 385
367 386
368 typedef struct { 387 typedef struct
388 {
369 guint16 flags; /* Flags */ 389 guint16 flags; /* Flags */
370 guint8 startPage, pageLength; 390 guint8 startPage, pageLength;
371 guint16 reserved; 391 guint16 reserved;
372 } psidv2_header_t; 392 } psidv2_header_t;
373 393
386 if ((inFile = xs_fopen(filename, "rb")) == NULL) 406 if ((inFile = xs_fopen(filename, "rb")) == NULL)
387 return -1; 407 return -1;
388 408
389 /* Read PSID header in */ 409 /* Read PSID header in */
390 xs_fread(psidH.magicID, sizeof(psidH.magicID), 1, inFile); 410 xs_fread(psidH.magicID, sizeof(psidH.magicID), 1, inFile);
391 if (strncmp(psidH.magicID, "PSID", 4) && strncmp(psidH.magicID, "RSID", 4)) { 411 if (strncmp(psidH.magicID, "PSID", 4) &&
412 strncmp(psidH.magicID, "RSID", 4))
413 {
392 xs_fclose(inFile); 414 xs_fclose(inFile);
393 xs_error("Not a PSID or RSID file '%s'\n", filename); 415 xs_error("Not a PSID or RSID file '%s'\n", filename);
394 return -2; 416 return -2;
395 } 417 }
396 418
405 427
406 xs_fread(psidH.sidName, sizeof(gchar), sizeof(psidH.sidName), inFile); 428 xs_fread(psidH.sidName, sizeof(gchar), sizeof(psidH.sidName), inFile);
407 xs_fread(psidH.sidAuthor, sizeof(gchar), sizeof(psidH.sidAuthor), inFile); 429 xs_fread(psidH.sidAuthor, sizeof(gchar), sizeof(psidH.sidAuthor), inFile);
408 xs_fread(psidH.sidCopyright, sizeof(gchar), sizeof(psidH.sidCopyright), inFile); 430 xs_fread(psidH.sidCopyright, sizeof(gchar), sizeof(psidH.sidCopyright), inFile);
409 431
410 if (xs_feof(inFile) || xs_ferror(inFile)) { 432 if (xs_feof(inFile) || xs_ferror(inFile))
433 {
411 xs_fclose(inFile); 434 xs_fclose(inFile);
412 xs_error("Error reading SID file header from '%s'\n", filename); 435 xs_error("Error reading SID file header from '%s'\n", filename);
413 return -4; 436 return -4;
414 } 437 }
415 438
416 /* Check if we need to load PSIDv2NG header ... */ 439 /* Check if we need to load PSIDv2NG header ... */
417 psidH2.flags = 0; /* Just silence a stupid gcc warning */ 440 psidH2.flags = 0; /* Just silence a stupid gcc warning */
418 441
419 if (psidH.version == 2) { 442 if (psidH.version == 2)
443 {
420 /* Yes, we need to */ 444 /* Yes, we need to */
421 psidH2.flags = xs_fread_be16(inFile); 445 psidH2.flags = xs_fread_be16(inFile);
422 psidH2.startPage = xs_fgetc(inFile); 446 psidH2.startPage = xs_fgetc(inFile);
423 psidH2.pageLength = xs_fgetc(inFile); 447 psidH2.pageLength = xs_fgetc(inFile);
424 psidH2.reserved = xs_fread_be16(inFile); 448 psidH2.reserved = xs_fread_be16(inFile);
425 } 449 }
426 450
427 /* Allocate buffer */ 451 /* Allocate buffer */
428 songData = (guint8 *) g_malloc(XS_SIDBUF_SIZE * sizeof(guint8)); 452 songData = (guint8 *) g_malloc(XS_SIDBUF_SIZE * sizeof(guint8));
429 if (!songData) { 453 if (!songData)
454 {
430 xs_fclose(inFile); 455 xs_fclose(inFile);
431 xs_error("Error allocating temp data buffer for file '%s'\n", filename); 456 xs_error("Error allocating temp data buffer for file '%s'\n", filename);
432 return -3; 457 return -3;
433 } 458 }
434 459
437 xs_fclose(inFile); 462 xs_fclose(inFile);
438 463
439 /* Initialize and start MD5-hash calculation */ 464 /* Initialize and start MD5-hash calculation */
440 xs_md5_init(&inState); 465 xs_md5_init(&inState);
441 466
442 if (psidH.loadAddress == 0) { 467 if (psidH.loadAddress == 0)
468 {
443 /* Strip load address (2 first bytes) */ 469 /* Strip load address (2 first bytes) */
444 xs_md5_append(&inState, &songData[2], result - 2); 470 xs_md5_append(&inState, &songData[2], result - 2);
445 } else { 471 }
472 else
473 {
446 /* Append "as is" */ 474 /* Append "as is" */
447 xs_md5_append(&inState, songData, result); 475 xs_md5_append(&inState, songData, result);
448 } 476 }
449 477
450 /* Free buffer */ 478 /* Free buffer */
451 g_free(songData); 479 g_free(songData);
452 480
453 /* Append header data to hash */ 481 /* Append header data to hash */
454 #define XSADDHASH(QDATAB) do { \ 482 #define XSADDHASH(QDATAB) do { \
455 ib8[0] = (QDATAB & 0xff); \ 483 ib8[0] = (QDATAB & 0xff); \
456 ib8[1] = (QDATAB >> 8); \ 484 ib8[1] = (QDATAB >> 8); \
457 xs_md5_append(&inState, (guint8 *) &ib8, sizeof(ib8)); \ 485 xs_md5_append(&inState, (guint8 *) &ib8, sizeof(ib8)); \
458 } while (0) 486 } while (0)
459 487
460 XSADDHASH(psidH.initAddress); 488 XSADDHASH(psidH.initAddress);
461 XSADDHASH(psidH.playAddress); 489 XSADDHASH(psidH.playAddress);
462 XSADDHASH(psidH.nSongs); 490 XSADDHASH(psidH.nSongs);
463 #undef XSADDHASH 491 #undef XSADDHASH
464 492
465 /* Append song speed data to hash */ 493 /* Append song speed data to hash */
466 i8 = 0; 494 i8 = 0;
467 for (index = 0; (index < psidH.nSongs) && (index < 32); index++) { 495 for (index = 0; index < psidH.nSongs && index < 32; index++)
496 {
468 i8 = (psidH.speed & (1 << index)) ? 60 : 0; 497 i8 = (psidH.speed & (1 << index)) ? 60 : 0;
469 xs_md5_append(&inState, &i8, sizeof(i8)); 498 xs_md5_append(&inState, &i8, sizeof(i8));
470 } 499 }
471 500
472 /* Rest of songs (more than 32) */ 501 /* Rest of songs (more than 32) */
473 for (index = 32; index < psidH.nSongs; index++) { 502 for (index = 32; index < psidH.nSongs; index++)
474 xs_md5_append(&inState, &i8, sizeof(i8)); 503 xs_md5_append(&inState, &i8, sizeof(i8));
475 }
476 504
477 /* PSIDv2NG specific */ 505 /* PSIDv2NG specific */
478 if (psidH.version == 2) { 506 if (psidH.version == 2)
507 {
479 /* SEE SIDPLAY HEADERS FOR INFO */ 508 /* SEE SIDPLAY HEADERS FOR INFO */
480 i8 = (psidH2.flags >> 2) & 3; 509 i8 = (psidH2.flags >> 2) & 3;
481 if (i8 == 2) 510 if (i8 == 2)
482 xs_md5_append(&inState, &i8, sizeof(i8)); 511 xs_md5_append(&inState, &i8, sizeof(i8));
483 } 512 }
498 /* Check the database pointers */ 527 /* Check the database pointers */
499 if (!db || !db->nodes || !db->pindex) 528 if (!db || !db->nodes || !db->pindex)
500 return NULL; 529 return NULL;
501 530
502 /* Get the hash and then look up from db */ 531 /* Get the hash and then look up from db */
503 if (xs_get_sid_hash(filename, keyItem.md5Hash) == 0) { 532 if (xs_get_sid_hash(filename, keyItem.md5Hash) == 0)
533 {
504 key = &keyItem; 534 key = &keyItem;
505 item = bsearch(&key, db->pindex, db->n, 535 item = bsearch(&key, db->pindex, db->n,
506 sizeof(db->pindex[0]), xs_sldb_cmp); 536 sizeof(db->pindex[0]), xs_sldb_cmp);
507 537
508 if (item) 538 return (item != NULL) ? *item : NULL;
509 return *item; 539 }
510 else 540 else
511 return NULL;
512 } else
513 return NULL; 541 return NULL;
514 } 542 }
515
516