0637fc4f00c5904535c93398a94a04db699b3c9f
[blender.git] / source / blender / blenkernel / intern / packedFile.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/blenkernel/intern/packedFile.c
29  *  \ingroup bke
30  */
31
32
33 #include <stdio.h>
34 #include <fcntl.h>
35 #include <sys/stat.h>
36
37 #ifndef WIN32
38 #include <unistd.h>
39 #else
40 #include <io.h>
41 #endif
42 #include <string.h>
43 #include "MEM_guardedalloc.h"
44
45 #include "DNA_image_types.h"
46 #include "DNA_ID.h"
47 #include "DNA_packedFile_types.h"
48 #include "DNA_sound_types.h"
49 #include "DNA_vfont_types.h"
50
51 #include "BLI_blenlib.h"
52 #include "BLI_utildefines.h"
53
54 #include "BKE_font.h"
55 #include "BKE_global.h"
56 #include "BKE_image.h"
57 #include "BKE_main.h"
58 #include "BKE_packedFile.h"
59 #include "BKE_report.h"
60 #include "BKE_sound.h"
61
62 int seekPackedFile(PackedFile *pf, int offset, int whence)
63 {
64         int oldseek = -1, seek = 0;
65
66         if (pf) {
67                 oldseek = pf->seek;
68                 switch (whence) {
69                         case SEEK_CUR:
70                                 seek = oldseek + offset;
71                                 break;
72                         case SEEK_END:
73                                 seek = pf->size + offset;
74                                 break;
75                         case SEEK_SET:
76                                 seek = offset;
77                                 break;
78                         default:
79                                 oldseek = -1;
80                                 break;
81                 }
82                 if (seek < 0) {
83                         seek = 0;
84                 }
85                 else if (seek > pf->size) {
86                         seek = pf->size;
87                 }
88                 pf->seek = seek;
89         }
90
91         return(oldseek);
92 }
93
94 void rewindPackedFile(PackedFile *pf)
95 {
96         seekPackedFile(pf, 0, SEEK_SET);
97 }
98
99 int readPackedFile(PackedFile *pf, void *data, int size)
100 {
101         if ((pf != NULL) && (size >= 0) && (data != NULL)) {
102                 if (size + pf->seek > pf->size) {
103                         size = pf->size - pf->seek;
104                 }
105
106                 if (size > 0) {
107                         memcpy(data, ((char *) pf->data) + pf->seek, size);
108                 }
109                 else {
110                         size = 0;
111                 }
112
113                 pf->seek += size;
114         }
115         else {
116                 size = -1;
117         }
118
119         return(size);
120 }
121
122 int countPackedFiles(Main *bmain)
123 {
124         Image *ima;
125         VFont *vf;
126         bSound *sound;
127         int count = 0;
128
129         /* let's check if there are packed files... */
130         for (ima = bmain->image.first; ima; ima = ima->id.next)
131                 if (BKE_image_has_packedfile(ima))
132                         count ++;
133
134         for (vf = bmain->vfont.first; vf; vf = vf->id.next)
135                 if (vf->packedfile)
136                         count++;
137
138         for (sound = bmain->sound.first; sound; sound = sound->id.next)
139                 if (sound->packedfile)
140                         count++;
141
142         return count;
143 }
144
145 void freePackedFile(PackedFile *pf)
146 {
147         if (pf) {
148                 MEM_freeN(pf->data);
149                 MEM_freeN(pf);
150         }
151         else
152                 printf("freePackedFile: Trying to free a NULL pointer\n");
153 }
154
155 PackedFile *dupPackedFile(const PackedFile *pf_src)
156 {
157         PackedFile *pf_dst;
158
159         pf_dst       = MEM_dupallocN(pf_src);
160         pf_dst->data = MEM_dupallocN(pf_src->data);
161
162         return pf_dst;
163 }
164
165 PackedFile *newPackedFileMemory(void *mem, int memlen)
166 {
167         PackedFile *pf = MEM_callocN(sizeof(*pf), "PackedFile");
168         pf->data = mem;
169         pf->size = memlen;
170
171         return pf;
172 }
173
174 PackedFile *newPackedFile(ReportList *reports, const char *filename, const char *basepath)
175 {
176         PackedFile *pf = NULL;
177         int file, filelen;
178         char name[FILE_MAX];
179         void *data;
180
181         /* render result has no filename and can be ignored
182          * any other files with no name can be ignored too */
183         if (filename[0] == '\0')
184                 return NULL;
185
186         //XXX waitcursor(1);
187
188         /* convert relative filenames to absolute filenames */
189
190         BLI_strncpy(name, filename, sizeof(name));
191         BLI_path_abs(name, basepath);
192
193         /* open the file
194          * and create a PackedFile structure */
195
196         file = BLI_open(name, O_BINARY | O_RDONLY, 0);
197         if (file == -1) {
198                 BKE_reportf(reports, RPT_ERROR, "Unable to pack file, source path '%s' not found", name);
199         }
200         else {
201                 filelen = BLI_file_descriptor_size(file);
202
203                 if (filelen == 0) {
204                         /* MEM_mallocN complains about MEM_mallocN(0, "bla");
205                          * we don't care.... */
206                         data = MEM_mallocN(1, "packFile");
207                 }
208                 else {
209                         data = MEM_mallocN(filelen, "packFile");
210                 }
211                 if (read(file, data, filelen) == filelen) {
212                         pf = newPackedFileMemory(data, filelen);
213                 }
214                 else {
215                         MEM_freeN(data);
216                 }
217
218                 close(file);
219         }
220
221         //XXX waitcursor(0);
222
223         return (pf);
224 }
225
226 /* no libraries for now */
227 void packAll(Main *bmain, ReportList *reports, bool verbose)
228 {
229         Image *ima;
230         VFont *vfont;
231         bSound *sound;
232         int tot = 0;
233
234         for (ima = bmain->image.first; ima; ima = ima->id.next) {
235                 if (BKE_image_has_packedfile(ima) == false && !ID_IS_LINKED(ima)) {
236                         if (ima->source == IMA_SRC_FILE) {
237                                 BKE_image_packfiles(reports, ima, ID_BLEND_PATH(bmain, &ima->id));
238                                 tot ++;
239                         }
240                         else if (BKE_image_is_animated(ima) && verbose) {
241                                 BKE_reportf(reports, RPT_WARNING, "Image '%s' skipped, movies and image sequences not supported",
242                                             ima->id.name + 2);
243                         }
244                 }
245         }
246
247         for (vfont = bmain->vfont.first; vfont; vfont = vfont->id.next) {
248                 if (vfont->packedfile == NULL && !ID_IS_LINKED(vfont) && BKE_vfont_is_builtin(vfont) == false) {
249                         vfont->packedfile = newPackedFile(reports, vfont->name, BKE_main_blendfile_path(bmain));
250                         tot ++;
251                 }
252         }
253
254         for (sound = bmain->sound.first; sound; sound = sound->id.next) {
255                 if (sound->packedfile == NULL && !ID_IS_LINKED(sound)) {
256                         sound->packedfile = newPackedFile(reports, sound->name, BKE_main_blendfile_path(bmain));
257                         tot++;
258                 }
259         }
260
261         if (tot > 0)
262                 BKE_reportf(reports, RPT_INFO, "Packed %d files", tot);
263         else if (verbose)
264                 BKE_report(reports, RPT_INFO, "No new files have been packed");
265 }
266
267
268 #if 0
269
270 // attempt to create a function that generates an unique filename
271 // this will work when all functions in fileops.c understand relative filenames...
272
273 static char *find_new_name(char *name)
274 {
275         char tempname[FILE_MAX];
276         char *newname;
277         size_t len;
278
279         if (fop_exists(name)) {
280                 for (number = 1; number <= 999; number++) {
281                         BLI_snprintf(tempname, sizeof(tempname), "%s.%03d", name, number);
282                         if (!fop_exists(tempname)) {
283                                 break;
284                         }
285                 }
286         }
287         len = strlen(tempname) + 1;
288         newname = MEM_mallocN(len, "find_new_name");
289         memcpy(newname, tempname, len * sizeof(char));
290         return newname;
291 }
292 #endif
293
294 int writePackedFile(
295         ReportList *reports, const char *ref_file_name, const char *filename, PackedFile *pf, const bool guimode)
296 {
297         int file, number;
298         int ret_value = RET_OK;
299         bool remove_tmp = false;
300         char name[FILE_MAX];
301         char tempname[FILE_MAX];
302 /*      void *data; */
303
304         if (guimode) {} //XXX  waitcursor(1);
305
306         BLI_strncpy(name, filename, sizeof(name));
307         BLI_path_abs(name, ref_file_name);
308
309         if (BLI_exists(name)) {
310                 for (number = 1; number <= 999; number++) {
311                         BLI_snprintf(tempname, sizeof(tempname), "%s.%03d_", name, number);
312                         if (!BLI_exists(tempname)) {
313                                 if (BLI_copy(name, tempname) == RET_OK) {
314                                         remove_tmp = true;
315                                 }
316                                 break;
317                         }
318                 }
319         }
320
321         /* make sure the path to the file exists... */
322         BLI_make_existing_file(name);
323
324         file = BLI_open(name, O_BINARY + O_WRONLY + O_CREAT + O_TRUNC, 0666);
325         if (file == -1) {
326                 BKE_reportf(reports, RPT_ERROR, "Error creating file '%s'", name);
327                 ret_value = RET_ERROR;
328         }
329         else {
330                 if (write(file, pf->data, pf->size) != pf->size) {
331                         BKE_reportf(reports, RPT_ERROR, "Error writing file '%s'", name);
332                         ret_value = RET_ERROR;
333                 }
334                 else {
335                         BKE_reportf(reports, RPT_INFO, "Saved packed file to: %s", name);
336                 }
337
338                 close(file);
339         }
340
341         if (remove_tmp) {
342                 if (ret_value == RET_ERROR) {
343                         if (BLI_rename(tempname, name) != 0) {
344                                 BKE_reportf(reports, RPT_ERROR, "Error restoring temp file (check files '%s' '%s')", tempname, name);
345                         }
346                 }
347                 else {
348                         if (BLI_delete(tempname, false, false) != 0) {
349                                 BKE_reportf(reports, RPT_ERROR, "Error deleting '%s' (ignored)", tempname);
350                         }
351                 }
352         }
353
354         if (guimode) {} //XXX waitcursor(0);
355
356         return (ret_value);
357 }
358
359 /**
360  * This function compares a packed file to a 'real' file.
361  * It returns an integer indicating if:
362  *
363  * - PF_EQUAL:     the packed file and original file are identical
364  * - PF_DIFFERENT: the packed file and original file differ
365  * - PF_NOFILE:    the original file doesn't exist
366  */
367 int checkPackedFile(const char *ref_file_name, const char *filename, PackedFile *pf)
368 {
369         BLI_stat_t st;
370         int ret_val, i, len, file;
371         char buf[4096];
372         char name[FILE_MAX];
373
374         BLI_strncpy(name, filename, sizeof(name));
375         BLI_path_abs(name, ref_file_name);
376
377         if (BLI_stat(name, &st) == -1) {
378                 ret_val = PF_NOFILE;
379         }
380         else if (st.st_size != pf->size) {
381                 ret_val = PF_DIFFERS;
382         }
383         else {
384                 /* we'll have to compare the two... */
385
386                 file = BLI_open(name, O_BINARY | O_RDONLY, 0);
387                 if (file == -1) {
388                         ret_val = PF_NOFILE;
389                 }
390                 else {
391                         ret_val = PF_EQUAL;
392
393                         for (i = 0; i < pf->size; i += sizeof(buf)) {
394                                 len = pf->size - i;
395                                 if (len > sizeof(buf)) {
396                                         len = sizeof(buf);
397                                 }
398
399                                 if (read(file, buf, len) != len) {
400                                         /* read error ... */
401                                         ret_val = PF_DIFFERS;
402                                         break;
403                                 }
404                                 else {
405                                         if (memcmp(buf, ((char *)pf->data) + i, len)) {
406                                                 ret_val = PF_DIFFERS;
407                                                 break;
408                                         }
409                                 }
410                         }
411
412                         close(file);
413                 }
414         }
415
416         return(ret_val);
417 }
418
419 /**
420  * unpackFile() looks at the existing files (abs_name, local_name) and a packed file.
421  *
422  * It returns a char *to the existing file name / new file name or NULL when
423  * there was an error or when the user decides to cancel the operation.
424  *
425  * \warning 'abs_name' may be relative still! (use a "//" prefix) be sure to run #BLI_path_abs on it first.
426  */
427 char *unpackFile(
428         ReportList *reports, const char *ref_file_name,
429         const char *abs_name, const char *local_name, PackedFile *pf, int how)
430 {
431         char *newname = NULL;
432         const char *temp = NULL;
433
434         if (pf != NULL) {
435                 switch (how) {
436                         case -1:
437                         case PF_KEEP:
438                                 break;
439                         case PF_REMOVE:
440                                 temp = abs_name;
441                                 break;
442                         case PF_USE_LOCAL:
443                         {
444                                 char temp_abs[FILE_MAX];
445
446                                 BLI_strncpy(temp_abs, local_name, sizeof(temp_abs));
447                                 BLI_path_abs(temp_abs, ref_file_name);
448
449                                 /* if file exists use it */
450                                 if (BLI_exists(temp_abs)) {
451                                         temp = local_name;
452                                         break;
453                                 }
454                                 /* else create it */
455                                 ATTR_FALLTHROUGH;
456                         }
457                         case PF_WRITE_LOCAL:
458                                 if (writePackedFile(reports, ref_file_name, local_name, pf, 1) == RET_OK) {
459                                         temp = local_name;
460                                 }
461                                 break;
462                         case PF_USE_ORIGINAL:
463                         {
464                                 char temp_abs[FILE_MAX];
465
466                                 BLI_strncpy(temp_abs, abs_name, sizeof(temp_abs));
467                                 BLI_path_abs(temp_abs, ref_file_name);
468
469                                 /* if file exists use it */
470                                 if (BLI_exists(temp_abs)) {
471                                         BKE_reportf(reports, RPT_INFO, "Use existing file (instead of packed): %s", abs_name);
472                                         temp = abs_name;
473                                         break;
474                                 }
475                                 /* else create it */
476                                 ATTR_FALLTHROUGH;
477                         }
478                         case PF_WRITE_ORIGINAL:
479                                 if (writePackedFile(reports, ref_file_name, abs_name, pf, 1) == RET_OK) {
480                                         temp = abs_name;
481                                 }
482                                 break;
483                         default:
484                                 printf("unpackFile: unknown return_value %d\n", how);
485                                 break;
486                 }
487
488                 if (temp) {
489                         newname = BLI_strdup(temp);
490                 }
491         }
492
493         return newname;
494 }
495
496 static void unpack_generate_paths(
497         const char *name, ID *id, char *r_abspath, char *r_relpath, size_t abspathlen, size_t relpathlen)
498 {
499         char tempname[FILE_MAX];
500         char tempdir[FILE_MAXDIR];
501
502         BLI_split_dirfile(name, tempdir, tempname, sizeof(tempdir), sizeof(tempname));
503
504         if (tempname[0] == '\0') {
505                 /* Note: we do not have any real way to re-create extension out of data... */
506                 BLI_strncpy(tempname, id->name + 2, sizeof(tempname));
507                 printf("%s\n", tempname);
508                 BLI_filename_make_safe(tempname);
509                 printf("%s\n", tempname);
510         }
511
512         if (tempdir[0] == '\0') {
513                 /* Fallback to relative dir. */
514                 BLI_strncpy(tempdir, "//", sizeof(tempdir));
515         }
516
517         switch (GS(id->name)) {
518                 case ID_VF:
519                         BLI_snprintf(r_relpath, relpathlen, "//fonts/%s", tempname);
520                         break;
521                 case ID_SO:
522                         BLI_snprintf(r_relpath, relpathlen, "//sounds/%s", tempname);
523                         break;
524                 case ID_IM:
525                         BLI_snprintf(r_relpath, relpathlen, "//textures/%s", tempname);
526                         break;
527                 default:
528                         break;
529         }
530
531         {
532                 size_t len = BLI_strncpy_rlen(r_abspath, tempdir, abspathlen);
533                 BLI_strncpy(r_abspath + len, tempname, abspathlen - len);
534         }
535 }
536
537 int unpackVFont(Main *bmain, ReportList *reports, VFont *vfont, int how)
538 {
539         char localname[FILE_MAX], absname[FILE_MAX];
540         char *newname;
541         int ret_value = RET_ERROR;
542
543         if (vfont != NULL) {
544                 unpack_generate_paths(vfont->name, (ID *)vfont, absname, localname, sizeof(absname), sizeof(localname));
545                 newname = unpackFile(reports, BKE_main_blendfile_path(bmain), absname, localname, vfont->packedfile, how);
546                 if (newname != NULL) {
547                         ret_value = RET_OK;
548                         freePackedFile(vfont->packedfile);
549                         vfont->packedfile = NULL;
550                         BLI_strncpy(vfont->name, newname, sizeof(vfont->name));
551                         MEM_freeN(newname);
552                 }
553         }
554
555         return (ret_value);
556 }
557
558 int unpackSound(Main *bmain, ReportList *reports, bSound *sound, int how)
559 {
560         char localname[FILE_MAX], absname[FILE_MAX];
561         char *newname;
562         int ret_value = RET_ERROR;
563
564         if (sound != NULL) {
565                 unpack_generate_paths(sound->name, (ID *)sound, absname, localname, sizeof(absname), sizeof(localname));
566                 newname = unpackFile(reports, BKE_main_blendfile_path(bmain), absname, localname, sound->packedfile, how);
567                 if (newname != NULL) {
568                         BLI_strncpy(sound->name, newname, sizeof(sound->name));
569                         MEM_freeN(newname);
570
571                         freePackedFile(sound->packedfile);
572                         sound->packedfile = NULL;
573
574                         BKE_sound_load(bmain, sound);
575
576                         ret_value = RET_OK;
577                 }
578         }
579
580         return(ret_value);
581 }
582
583 int unpackImage(Main *bmain, ReportList *reports, Image *ima, int how)
584 {
585         int ret_value = RET_ERROR;
586
587         if (ima != NULL) {
588                 while (ima->packedfiles.last) {
589                         char localname[FILE_MAX], absname[FILE_MAX];
590                         char *newname;
591                         ImagePackedFile *imapf = ima->packedfiles.last;
592
593                         unpack_generate_paths(imapf->filepath, (ID *)ima, absname, localname, sizeof(absname), sizeof(localname));
594                         newname = unpackFile(reports, BKE_main_blendfile_path(bmain), absname, localname, imapf->packedfile, how);
595
596                         if (newname != NULL) {
597                                 ImageView *iv;
598
599                                 ret_value = ret_value == RET_ERROR ? RET_ERROR : RET_OK;
600                                 freePackedFile(imapf->packedfile);
601                                 imapf->packedfile = NULL;
602
603                                 /* update the new corresponding view filepath */
604                                 iv = BLI_findstring(&ima->views, imapf->filepath, offsetof(ImageView, filepath));
605                                 if (iv) {
606                                         BLI_strncpy(iv->filepath, newname, sizeof(imapf->filepath));
607                                 }
608
609                                 /* keep the new name in the image for non-pack specific reasons */
610                                 if (how != PF_REMOVE) {
611                                         BLI_strncpy(ima->name, newname, sizeof(imapf->filepath));
612                                 }
613                                 MEM_freeN(newname);
614                         }
615                         else {
616                                 ret_value = RET_ERROR;
617                         }
618
619                         BLI_remlink(&ima->packedfiles, imapf);
620                         MEM_freeN(imapf);
621                 }
622         }
623
624         if (ret_value == RET_OK) {
625                 BKE_image_signal(bmain, ima, NULL, IMA_SIGNAL_RELOAD);
626         }
627
628         return(ret_value);
629 }
630
631 int unpackLibraries(Main *bmain, ReportList *reports)
632 {
633         Library *lib;
634         char *newname;
635         int ret_value = RET_ERROR;
636
637         for (lib = bmain->library.first; lib; lib = lib->id.next) {
638                 if (lib->packedfile && lib->name[0]) {
639
640                         newname = unpackFile(reports, BKE_main_blendfile_path(bmain), lib->filepath, lib->filepath, lib->packedfile, PF_WRITE_ORIGINAL);
641                         if (newname != NULL) {
642                                 ret_value = RET_OK;
643
644                                 printf("Unpacked .blend library: %s\n", newname);
645
646                                 freePackedFile(lib->packedfile);
647                                 lib->packedfile = NULL;
648
649                                 MEM_freeN(newname);
650                         }
651                 }
652         }
653
654         return(ret_value);
655 }
656
657 void packLibraries(Main *bmain, ReportList *reports)
658 {
659         Library *lib;
660
661         /* test for relativenss */
662         for (lib = bmain->library.first; lib; lib = lib->id.next)
663                 if (!BLI_path_is_rel(lib->name))
664                         break;
665
666         if (lib) {
667                 BKE_reportf(reports, RPT_ERROR, "Cannot pack absolute file: '%s'", lib->name);
668                 return;
669         }
670
671         for (lib = bmain->library.first; lib; lib = lib->id.next)
672                 if (lib->packedfile == NULL)
673                         lib->packedfile = newPackedFile(reports, lib->name, BKE_main_blendfile_path(bmain));
674 }
675
676 void unpackAll(Main *bmain, ReportList *reports, int how)
677 {
678         Image *ima;
679         VFont *vf;
680         bSound *sound;
681
682         for (ima = bmain->image.first; ima; ima = ima->id.next)
683                 if (BKE_image_has_packedfile(ima))
684                         unpackImage(bmain, reports, ima, how);
685
686         for (vf = bmain->vfont.first; vf; vf = vf->id.next)
687                 if (vf->packedfile)
688                         unpackVFont(bmain, reports, vf, how);
689
690         for (sound = bmain->sound.first; sound; sound = sound->id.next)
691                 if (sound->packedfile)
692                         unpackSound(bmain, reports, sound, how);
693 }
694
695 /* ID should be not NULL, return 1 if there's a packed file */
696 bool BKE_pack_check(ID *id)
697 {
698         switch (GS(id->name)) {
699                 case ID_IM:
700                 {
701                         Image *ima = (Image *)id;
702                         return BKE_image_has_packedfile(ima);
703                 }
704                 case ID_VF:
705                 {
706                         VFont *vf = (VFont *)id;
707                         return vf->packedfile != NULL;
708                 }
709                 case ID_SO:
710                 {
711                         bSound *snd = (bSound *)id;
712                         return snd->packedfile != NULL;
713                 }
714                 case ID_LI:
715                 {
716                         Library *li = (Library *)id;
717                         return li->packedfile != NULL;
718                 }
719                 default:
720                         break;
721         }
722         return false;
723 }
724
725 /* ID should be not NULL */
726 void BKE_unpack_id(Main *bmain, ID *id, ReportList *reports, int how)
727 {
728         switch (GS(id->name)) {
729                 case ID_IM:
730                 {
731                         Image *ima = (Image *)id;
732                         if (BKE_image_has_packedfile(ima)) {
733                                 unpackImage(bmain, reports, ima, how);
734                         }
735                         break;
736                 }
737                 case ID_VF:
738                 {
739                         VFont *vf = (VFont *)id;
740                         if (vf->packedfile) {
741                                 unpackVFont(bmain, reports, vf, how);
742                         }
743                         break;
744                 }
745                 case ID_SO:
746                 {
747                         bSound *snd = (bSound *)id;
748                         if (snd->packedfile) {
749                                 unpackSound(bmain, reports, snd, how);
750                         }
751                         break;
752                 }
753                 case ID_LI:
754                 {
755                         Library *li = (Library *)id;
756                         BKE_reportf(reports, RPT_ERROR, "Cannot unpack individual Library file, '%s'", li->name);
757                         break;
758                 }
759                 default:
760                         break;
761         }
762 }