]> git.xonotic.org Git - xonotic/netradiant.git/blob - libs/picomodel/lwo/lwob.c
3ae40b43e66974cdaf5a81f39ae267359fb519d9
[xonotic/netradiant.git] / libs / picomodel / lwo / lwob.c
1 /*
2    ======================================================================
3    lwob.c
4
5    Functions for an LWOB reader.  LWOB is the LightWave object format
6    for versions of LW prior to 6.0.
7
8    Ernie Wright  17 Sep 00
9    ====================================================================== */
10
11 #include "../picointernal.h"
12 #include "lwo2.h"
13
14 /* disable warnings */
15 #ifdef _WIN32
16 #pragma warning( disable:4018 )         /* signed/unsigned mismatch */
17 #endif
18
19
20 /* IDs specific to LWOB */
21
22 #define ID_SRFS  LWID_( 'S','R','F','S' )
23 #define ID_FLAG  LWID_( 'F','L','A','G' )
24 #define ID_VLUM  LWID_( 'V','L','U','M' )
25 #define ID_VDIF  LWID_( 'V','D','I','F' )
26 #define ID_VSPC  LWID_( 'V','S','P','C' )
27 #define ID_RFLT  LWID_( 'R','F','L','T' )
28 #define ID_BTEX  LWID_( 'B','T','E','X' )
29 #define ID_CTEX  LWID_( 'C','T','E','X' )
30 #define ID_DTEX  LWID_( 'D','T','E','X' )
31 #define ID_LTEX  LWID_( 'L','T','E','X' )
32 #define ID_RTEX  LWID_( 'R','T','E','X' )
33 #define ID_STEX  LWID_( 'S','T','E','X' )
34 #define ID_TTEX  LWID_( 'T','T','E','X' )
35 #define ID_TFLG  LWID_( 'T','F','L','G' )
36 #define ID_TSIZ  LWID_( 'T','S','I','Z' )
37 #define ID_TCTR  LWID_( 'T','C','T','R' )
38 #define ID_TFAL  LWID_( 'T','F','A','L' )
39 #define ID_TVEL  LWID_( 'T','V','E','L' )
40 #define ID_TCLR  LWID_( 'T','C','L','R' )
41 #define ID_TVAL  LWID_( 'T','V','A','L' )
42 #define ID_TAMP  LWID_( 'T','A','M','P' )
43 #define ID_TIMG  LWID_( 'T','I','M','G' )
44 #define ID_TAAS  LWID_( 'T','A','A','S' )
45 #define ID_TREF  LWID_( 'T','R','E','F' )
46 #define ID_TOPC  LWID_( 'T','O','P','C' )
47 #define ID_SDAT  LWID_( 'S','D','A','T' )
48 #define ID_TFP0  LWID_( 'T','F','P','0' )
49 #define ID_TFP1  LWID_( 'T','F','P','1' )
50
51
52 /*
53    ======================================================================
54    add_clip()
55
56    Add a clip to the clip list.  Used to store the contents of an RIMG or
57    TIMG surface subchunk.
58    ====================================================================== */
59
60 static int add_clip( char *s, lwClip **clist, int *nclips ){
61         lwClip *clip;
62         char *p;
63
64         clip = _pico_calloc( 1, sizeof( lwClip ) );
65         if ( !clip ) {
66                 return 0;
67         }
68
69         clip->contrast.val = 1.0f;
70         clip->brightness.val = 1.0f;
71         clip->saturation.val = 1.0f;
72         clip->gamma.val = 1.0f;
73
74         if ( ( p = strstr( s, "(sequence)" ) ) != NULL ) {
75                 p[ -1 ] = 0;
76                 clip->type = ID_ISEQ;
77                 clip->source.seq.prefix = s;
78                 clip->source.seq.digits = 3;
79         }
80         else {
81                 clip->type = ID_STIL;
82                 clip->source.still.name = s;
83         }
84
85         ( *nclips )++;
86         clip->index = *nclips;
87
88         lwListAdd( clist, clip );
89
90         return clip->index;
91 }
92
93
94 /*
95    ======================================================================
96    add_tvel()
97
98    Add a triple of envelopes to simulate the old texture velocity
99    parameters.
100    ====================================================================== */
101
102 static int add_tvel( float pos[], float vel[], lwEnvelope **elist, int *nenvs ){
103         lwEnvelope *env;
104         lwKey *key0, *key1;
105         int i;
106
107         for ( i = 0; i < 3; i++ ) {
108                 env = _pico_calloc( 1, sizeof( lwEnvelope ) );
109                 key0 = _pico_calloc( 1, sizeof( lwKey ) );
110                 key1 = _pico_calloc( 1, sizeof( lwKey ) );
111                 if ( !env || !key0 || !key1 ) {
112                         return 0;
113                 }
114
115                 key0->next = key1;
116                 key0->value = pos[ i ];
117                 key0->time = 0.0f;
118                 key1->prev = key0;
119                 key1->value = pos[ i ] + vel[ i ] * 30.0f;
120                 key1->time = 1.0f;
121                 key0->shape = key1->shape = ID_LINE;
122
123                 env->index = *nenvs + i + 1;
124                 env->type = 0x0301 + i;
125                 env->name = _pico_alloc( 11 );
126                 if ( env->name ) {
127                         strcpy( env->name, "Position.X" );
128                         env->name[ 9 ] += i;
129                 }
130                 env->key = key0;
131                 env->nkeys = 2;
132                 env->behavior[ 0 ] = BEH_LINEAR;
133                 env->behavior[ 1 ] = BEH_LINEAR;
134
135                 lwListAdd( elist, env );
136         }
137
138         *nenvs += 3;
139         return env->index - 2;
140 }
141
142
143 /*
144    ======================================================================
145    get_texture()
146
147    Create a new texture for BTEX, CTEX, etc. subchunks.
148    ====================================================================== */
149
150 static lwTexture *get_texture( char *s ){
151         lwTexture *tex;
152
153         tex = _pico_calloc( 1, sizeof( lwTexture ) );
154         if ( !tex ) {
155                 return NULL;
156         }
157
158         tex->tmap.size.val[ 0 ] =
159                 tex->tmap.size.val[ 1 ] =
160                         tex->tmap.size.val[ 2 ] = 1.0f;
161         tex->opacity.val = 1.0f;
162         tex->enabled = 1;
163
164         if ( strstr( s, "Image Map" ) ) {
165                 tex->type = ID_IMAP;
166                 if ( strstr( s, "Planar" ) ) {
167                         tex->param.imap.projection = 0;
168                 }
169                 else if ( strstr( s, "Cylindrical" ) ) {
170                         tex->param.imap.projection = 1;
171                 }
172                 else if ( strstr( s, "Spherical" ) ) {
173                         tex->param.imap.projection = 2;
174                 }
175                 else if ( strstr( s, "Cubic" ) ) {
176                         tex->param.imap.projection = 3;
177                 }
178                 else if ( strstr( s, "Front" ) ) {
179                         tex->param.imap.projection = 4;
180                 }
181                 tex->param.imap.aa_strength = 1.0f;
182                 tex->param.imap.amplitude.val = 1.0f;
183                 _pico_free( s );
184         }
185         else {
186                 tex->type = ID_PROC;
187                 tex->param.proc.name = s;
188         }
189
190         return tex;
191 }
192
193
194 /*
195    ======================================================================
196    lwGetSurface5()
197
198    Read an lwSurface from an LWOB file.
199    ====================================================================== */
200
201 lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ){
202         lwSurface *surf;
203         lwTexture *tex;
204         lwPlugin *shdr;
205         char *s;
206         float v[ 3 ];
207         unsigned int id, flags;
208         unsigned short sz;
209         int pos, rlen, i;
210
211
212         /* allocate the Surface structure */
213
214         surf = _pico_calloc( 1, sizeof( lwSurface ) );
215         if ( !surf ) {
216                 goto Fail;
217         }
218
219         /* non-zero defaults */
220
221         surf->color.rgb[ 0 ] = 0.78431f;
222         surf->color.rgb[ 1 ] = 0.78431f;
223         surf->color.rgb[ 2 ] = 0.78431f;
224         surf->diffuse.val    = 1.0f;
225         surf->glossiness.val = 0.4f;
226         surf->bump.val       = 1.0f;
227         surf->eta.val        = 1.0f;
228         surf->sideflags      = 1;
229
230         /* remember where we started */
231
232         set_flen( 0 );
233         pos = _pico_memstream_tell( fp );
234
235         /* name */
236
237         surf->name = getS0( fp );
238
239         /* first subchunk header */
240
241         id = getU4( fp );
242         sz = getU2( fp );
243         if ( 0 > get_flen() ) {
244                 goto Fail;
245         }
246
247         /* process subchunks as they're encountered */
248
249         while ( 1 ) {
250                 sz += sz & 1;
251                 set_flen( 0 );
252
253                 switch ( id ) {
254                 case ID_COLR:
255                         surf->color.rgb[ 0 ] = getU1( fp ) / 255.0f;
256                         surf->color.rgb[ 1 ] = getU1( fp ) / 255.0f;
257                         surf->color.rgb[ 2 ] = getU1( fp ) / 255.0f;
258                         break;
259
260                 case ID_FLAG:
261                         flags = getU2( fp );
262                         if ( flags &   4 ) {
263                                 surf->smooth = 1.56207f;
264                         }
265                         if ( flags &   8 ) {
266                                 surf->color_hilite.val = 1.0f;
267                         }
268                         if ( flags &  16 ) {
269                                 surf->color_filter.val = 1.0f;
270                         }
271                         if ( flags & 128 ) {
272                                 surf->dif_sharp.val = 0.5f;
273                         }
274                         if ( flags & 256 ) {
275                                 surf->sideflags = 3;
276                         }
277                         if ( flags & 512 ) {
278                                 surf->add_trans.val = 1.0f;
279                         }
280                         break;
281
282                 case ID_LUMI:
283                         surf->luminosity.val = getI2( fp ) / 256.0f;
284                         break;
285
286                 case ID_VLUM:
287                         surf->luminosity.val = getF4( fp );
288                         break;
289
290                 case ID_DIFF:
291                         surf->diffuse.val = getI2( fp ) / 256.0f;
292                         break;
293
294                 case ID_VDIF:
295                         surf->diffuse.val = getF4( fp );
296                         break;
297
298                 case ID_SPEC:
299                         surf->specularity.val = getI2( fp ) / 256.0f;
300                         break;
301
302                 case ID_VSPC:
303                         surf->specularity.val = getF4( fp );
304                         break;
305
306                 case ID_GLOS:
307                         surf->glossiness.val = ( float ) log( getU2( fp ) ) / 20.7944f;
308                         break;
309
310                 case ID_SMAN:
311                         surf->smooth = getF4( fp );
312                         break;
313
314                 case ID_REFL:
315                         surf->reflection.val.val = getI2( fp ) / 256.0f;
316                         break;
317
318                 case ID_RFLT:
319                         surf->reflection.options = getU2( fp );
320                         break;
321
322                 case ID_RIMG:
323                         s = getS0( fp );
324                         surf->reflection.cindex = add_clip( s, &obj->clip, &obj->nclips );
325                         surf->reflection.options = 3;
326                         break;
327
328                 case ID_RSAN:
329                         surf->reflection.seam_angle = getF4( fp );
330                         break;
331
332                 case ID_TRAN:
333                         surf->transparency.val.val = getI2( fp ) / 256.0f;
334                         break;
335
336                 case ID_RIND:
337                         surf->eta.val = getF4( fp );
338                         break;
339
340                 case ID_BTEX:
341                         s = getbytes( fp, sz );
342                         tex = get_texture( s );
343                         lwListAdd( &surf->bump.tex, tex );
344                         break;
345
346                 case ID_CTEX:
347                         s = getbytes( fp, sz );
348                         tex = get_texture( s );
349                         lwListAdd( &surf->color.tex, tex );
350                         break;
351
352                 case ID_DTEX:
353                         s = getbytes( fp, sz );
354                         tex = get_texture( s );
355                         lwListAdd( &surf->diffuse.tex, tex );
356                         break;
357
358                 case ID_LTEX:
359                         s = getbytes( fp, sz );
360                         tex = get_texture( s );
361                         lwListAdd( &surf->luminosity.tex, tex );
362                         break;
363
364                 case ID_RTEX:
365                         s = getbytes( fp, sz );
366                         tex = get_texture( s );
367                         lwListAdd( &surf->reflection.val.tex, tex );
368                         break;
369
370                 case ID_STEX:
371                         s = getbytes( fp, sz );
372                         tex = get_texture( s );
373                         lwListAdd( &surf->specularity.tex, tex );
374                         break;
375
376                 case ID_TTEX:
377                         s = getbytes( fp, sz );
378                         tex = get_texture( s );
379                         lwListAdd( &surf->transparency.val.tex, tex );
380                         break;
381
382                 case ID_TFLG:
383                         flags = getU2( fp );
384
385                         if ( flags & 1 ) {
386                                 i = 0;
387                         }
388                         if ( flags & 2 ) {
389                                 i = 1;
390                         }
391                         if ( flags & 4 ) {
392                                 i = 2;
393                         }
394                         tex->axis = i;
395                         if ( tex->type == ID_IMAP ) {
396                                 tex->param.imap.axis = i;
397                         }
398                         else{
399                                 tex->param.proc.axis = i;
400                         }
401
402                         if ( flags &  8 ) {
403                                 tex->tmap.coord_sys = 1;
404                         }
405                         if ( flags & 16 ) {
406                                 tex->negative = 1;
407                         }
408                         if ( flags & 32 ) {
409                                 tex->param.imap.pblend = 1;
410                         }
411                         if ( flags & 64 ) {
412                                 tex->param.imap.aa_strength = 1.0f;
413                                 tex->param.imap.aas_flags = 1;
414                         }
415                         break;
416
417                 case ID_TSIZ:
418                         for ( i = 0; i < 3; i++ )
419                                 tex->tmap.size.val[ i ] = getF4( fp );
420                         break;
421
422                 case ID_TCTR:
423                         for ( i = 0; i < 3; i++ )
424                                 tex->tmap.center.val[ i ] = getF4( fp );
425                         break;
426
427                 case ID_TFAL:
428                         for ( i = 0; i < 3; i++ )
429                                 tex->tmap.falloff.val[ i ] = getF4( fp );
430                         break;
431
432                 case ID_TVEL:
433                         for ( i = 0; i < 3; i++ )
434                                 v[ i ] = getF4( fp );
435                         tex->tmap.center.eindex = add_tvel( tex->tmap.center.val, v,
436                                                                                                 &obj->env, &obj->nenvs );
437                         break;
438
439                 case ID_TCLR:
440                         if ( tex->type == ID_PROC ) {
441                                 for ( i = 0; i < 3; i++ )
442                                         tex->param.proc.value[ i ] = getU1( fp ) / 255.0f;
443                         }
444                         break;
445
446                 case ID_TVAL:
447                         tex->param.proc.value[ 0 ] = getI2( fp ) / 256.0f;
448                         break;
449
450                 case ID_TAMP:
451                         if ( tex->type == ID_IMAP ) {
452                                 tex->param.imap.amplitude.val = getF4( fp );
453                         }
454                         break;
455
456                 case ID_TIMG:
457                         s = getS0( fp );
458                         tex->param.imap.cindex = add_clip( s, &obj->clip, &obj->nclips );
459                         break;
460
461                 case ID_TAAS:
462                         tex->param.imap.aa_strength = getF4( fp );
463                         tex->param.imap.aas_flags = 1;
464                         break;
465
466                 case ID_TREF:
467                         tex->tmap.ref_object = getbytes( fp, sz );
468                         break;
469
470                 case ID_TOPC:
471                         tex->opacity.val = getF4( fp );
472                         break;
473
474                 case ID_TFP0:
475                         if ( tex->type == ID_IMAP ) {
476                                 tex->param.imap.wrapw.val = getF4( fp );
477                         }
478                         break;
479
480                 case ID_TFP1:
481                         if ( tex->type == ID_IMAP ) {
482                                 tex->param.imap.wraph.val = getF4( fp );
483                         }
484                         break;
485
486                 case ID_SHDR:
487                         shdr = _pico_calloc( 1, sizeof( lwPlugin ) );
488                         if ( !shdr ) {
489                                 goto Fail;
490                         }
491                         shdr->name = getbytes( fp, sz );
492                         lwListAdd( &surf->shader, shdr );
493                         surf->nshaders++;
494                         break;
495
496                 case ID_SDAT:
497                         shdr->data = getbytes( fp, sz );
498                         break;
499
500                 default:
501                         break;
502                 }
503
504                 /* error while reading current subchunk? */
505
506                 rlen = get_flen();
507                 if ( rlen < 0 || rlen > sz ) {
508                         goto Fail;
509                 }
510
511                 /* skip unread parts of the current subchunk */
512
513                 if ( rlen < sz ) {
514                         _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
515                 }
516
517                 /* end of the SURF chunk? */
518
519                 if ( cksize <= _pico_memstream_tell( fp ) - pos ) {
520                         break;
521                 }
522
523                 /* get the next subchunk header */
524
525                 set_flen( 0 );
526                 id = getU4( fp );
527                 sz = getU2( fp );
528                 if ( 6 != get_flen() ) {
529                         goto Fail;
530                 }
531         }
532
533         return surf;
534
535 Fail:
536         if ( surf ) {
537                 lwFreeSurface( surf );
538         }
539         return NULL;
540 }
541
542
543 /*
544    ======================================================================
545    lwGetPolygons5()
546
547    Read polygon records from a POLS chunk in an LWOB file.  The polygons
548    are added to the array in the lwPolygonList.
549    ====================================================================== */
550
551 int lwGetPolygons5( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ){
552         lwPolygon *pp;
553         lwPolVert *pv;
554         unsigned char *buf, *bp;
555         int i, j, nv, nverts, npols;
556
557
558         if ( cksize == 0 ) {
559                 return 1;
560         }
561
562         /* read the whole chunk */
563
564         set_flen( 0 );
565         buf = getbytes( fp, cksize );
566         if ( !buf ) {
567                 goto Fail;
568         }
569
570         /* count the polygons and vertices */
571
572         nverts = 0;
573         npols = 0;
574         bp = buf;
575
576         while ( bp < buf + cksize ) {
577                 nv = sgetU2( &bp );
578                 nverts += nv;
579                 npols++;
580                 bp += 2 * nv;
581                 i = sgetI2( &bp );
582                 if ( i < 0 ) {
583                         bp += 2;             /* detail polygons */
584                 }
585         }
586
587         if ( !lwAllocPolygons( plist, npols, nverts ) ) {
588                 goto Fail;
589         }
590
591         /* fill in the new polygons */
592
593         bp = buf;
594         pp = plist->pol + plist->offset;
595         pv = plist->pol[ 0 ].v + plist->voffset;
596
597         for ( i = 0; i < npols; i++ ) {
598                 nv = sgetU2( &bp );
599
600                 pp->nverts = nv;
601                 pp->type = ID_FACE;
602                 if ( !pp->v ) {
603                         pp->v = pv;
604                 }
605                 for ( j = 0; j < nv; j++ )
606                         pv[ j ].index = sgetU2( &bp ) + ptoffset;
607                 j = sgetI2( &bp );
608                 if ( j < 0 ) {
609                         j = -j;
610                         bp += 2;
611                 }
612                 j -= 1;
613                 pp->surf = ( lwSurface * ) ( (size_t)j );
614
615                 pp++;
616                 pv += nv;
617         }
618
619         _pico_free( buf );
620         return 1;
621
622 Fail:
623         if ( buf ) {
624                 _pico_free( buf );
625         }
626         lwFreePolygons( plist );
627         return 0;
628 }
629
630
631 /*
632    ======================================================================
633    getLWObject5()
634
635    Returns the contents of an LWOB, given its filename, or NULL if the
636    file couldn't be loaded.  On failure, failID and failpos can be used
637    to diagnose the cause.
638
639    1.  If the file isn't an LWOB, failpos will contain 12 and failID will
640     be unchanged.
641
642    2.  If an error occurs while reading an LWOB, failID will contain the
643     most recently read IFF chunk ID, and failpos will contain the
644     value returned by _pico_memstream_tell() at the time of the failure.
645
646    3.  If the file couldn't be opened, or an error occurs while reading
647     the first 12 bytes, both failID and failpos will be unchanged.
648
649    If you don't need this information, failID and failpos can be NULL.
650    ====================================================================== */
651
652 lwObject *lwGetObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ){
653         lwObject *object;
654         lwLayer *layer;
655         lwNode *node;
656         unsigned int id, formsize, type, cksize;
657
658
659         /* open the file */
660
661         if ( !fp ) {
662                 return NULL;
663         }
664
665         /* read the first 12 bytes */
666
667         set_flen( 0 );
668         id       = getU4( fp );
669         formsize = getU4( fp );
670         type     = getU4( fp );
671         if ( 12 != get_flen() ) {
672                 return NULL;
673         }
674
675         /* LWOB? */
676
677         if ( id != ID_FORM || type != ID_LWOB ) {
678                 if ( failpos ) {
679                         *failpos = 12;
680                 }
681                 return NULL;
682         }
683
684         /* allocate an object and a default layer */
685
686         object = _pico_calloc( 1, sizeof( lwObject ) );
687         if ( !object ) {
688                 goto Fail;
689         }
690
691         layer = _pico_calloc( 1, sizeof( lwLayer ) );
692         if ( !layer ) {
693                 goto Fail;
694         }
695         object->layer = layer;
696         object->nlayers = 1;
697
698         /* get the first chunk header */
699
700         id = getU4( fp );
701         cksize = getU4( fp );
702         if ( 0 > get_flen() ) {
703                 goto Fail;
704         }
705
706         /* process chunks as they're encountered */
707
708         while ( 1 ) {
709                 cksize += cksize & 1;
710
711                 switch ( id )
712                 {
713                 case ID_PNTS:
714                         if ( !lwGetPoints( fp, cksize, &layer->point ) ) {
715                                 goto Fail;
716                         }
717                         break;
718
719                 case ID_POLS:
720                         if ( !lwGetPolygons5( fp, cksize, &layer->polygon,
721                                                                   layer->point.offset ) ) {
722                                 goto Fail;
723                         }
724                         break;
725
726                 case ID_SRFS:
727                         if ( !lwGetTags( fp, cksize, &object->taglist ) ) {
728                                 goto Fail;
729                         }
730                         break;
731
732                 case ID_SURF:
733                         node = ( lwNode * ) lwGetSurface5( fp, cksize, object );
734                         if ( !node ) {
735                                 goto Fail;
736                         }
737                         lwListAdd( &object->surf, node );
738                         object->nsurfs++;
739                         break;
740
741                 default:
742                         _pico_memstream_seek( fp, cksize, PICO_SEEK_CUR );
743                         break;
744                 }
745
746                 /* end of the file? */
747
748                 if ( formsize <= _pico_memstream_tell( fp ) - 8 ) {
749                         break;
750                 }
751
752                 /* get the next chunk header */
753
754                 set_flen( 0 );
755                 id = getU4( fp );
756                 cksize = getU4( fp );
757                 if ( 8 != get_flen() ) {
758                         goto Fail;
759                 }
760         }
761
762         lwGetBoundingBox( &layer->point, layer->bbox );
763         lwGetPolyNormals( &layer->point, &layer->polygon );
764         if ( !lwGetPointPolygons( &layer->point, &layer->polygon ) ) {
765                 goto Fail;
766         }
767         if ( !lwResolvePolySurfaces( &layer->polygon, &object->taglist,
768                                                                  &object->surf, &object->nsurfs ) ) {
769                 goto Fail;
770         }
771         lwGetVertNormals( &layer->point, &layer->polygon );
772
773         return object;
774
775 Fail:
776         if ( failID ) {
777                 *failID = id;
778         }
779         if ( fp ) {
780                 if ( failpos ) {
781                         *failpos = _pico_memstream_tell( fp );
782                 }
783         }
784         lwFreeObject( object );
785         return NULL;
786 }
787
788 int lwValidateObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ){
789         unsigned int id, formsize, type;
790
791
792         /* open the file */
793
794         if ( !fp ) {
795                 return PICO_PMV_ERROR_MEMORY;
796         }
797
798         /* read the first 12 bytes */
799
800         set_flen( 0 );
801         id       = getU4( fp );
802         formsize = getU4( fp );
803         type     = getU4( fp );
804         if ( 12 != get_flen() ) {
805                 return PICO_PMV_ERROR_SIZE;
806         }
807
808         /* LWOB? */
809
810         if ( id != ID_FORM || type != ID_LWOB ) {
811                 if ( failpos ) {
812                         *failpos = 12;
813                 }
814                 return PICO_PMV_ERROR_IDENT;
815         }
816
817         return PICO_PMV_OK;
818 }