]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - VtoyTool/vtoykmod.c
1.0.98 release
[Ventoy.git] / VtoyTool / vtoykmod.c
1 /******************************************************************************
2 * vtoykmod.c ---- ventoy kmod
3 *
4 * Copyright (c) 2021, longpanda <admin@ventoy.net>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdint.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #ifdef VTOY_X86_64
28 #include <cpuid.h>
29 #endif
30
31 #define _ull unsigned long long
32
33 #define magic_sig 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF
34
35 #define EI_NIDENT (16)
36
37 #define EI_MAG0 0 /* e_ident[] indexes */
38 #define EI_MAG1 1
39 #define EI_MAG2 2
40 #define EI_MAG3 3
41 #define EI_CLASS 4
42 #define EI_DATA 5
43 #define EI_VERSION 6
44 #define EI_OSABI 7
45 #define EI_PAD 8
46
47 #define ELFMAG0 0x7f /* EI_MAG */
48 #define ELFMAG1 'E'
49 #define ELFMAG2 'L'
50 #define ELFMAG3 'F'
51 #define ELFMAG "\177ELF"
52 #define SELFMAG 4
53
54 #define ELFCLASSNONE 0 /* EI_CLASS */
55 #define ELFCLASS32 1
56 #define ELFCLASS64 2
57 #define ELFCLASSNUM 3
58
59 #define ELFDATANONE 0 /* e_ident[EI_DATA] */
60 #define ELFDATA2LSB 1
61 #define ELFDATA2MSB 2
62
63 #define EV_NONE 0 /* e_version, EI_VERSION */
64 #define EV_CURRENT 1
65 #define EV_NUM 2
66
67 #define ELFOSABI_NONE 0
68 #define ELFOSABI_LINUX 3
69
70 #define SHT_STRTAB 3
71
72 #pragma pack(1)
73
74
75 typedef struct
76 {
77 unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
78 uint16_t e_type; /* Object file type */
79 uint16_t e_machine; /* Architecture */
80 uint32_t e_version; /* Object file version */
81 uint32_t e_entry; /* Entry point virtual address */
82 uint32_t e_phoff; /* Program header table file offset */
83 uint32_t e_shoff; /* Section header table file offset */
84 uint32_t e_flags; /* Processor-specific flags */
85 uint16_t e_ehsize; /* ELF header size in bytes */
86 uint16_t e_phentsize; /* Program header table entry size */
87 uint16_t e_phnum; /* Program header table entry count */
88 uint16_t e_shentsize; /* Section header table entry size */
89 uint16_t e_shnum; /* Section header table entry count */
90 uint16_t e_shstrndx; /* Section header string table index */
91 } Elf32_Ehdr;
92
93 typedef struct
94 {
95 unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
96 uint16_t e_type; /* Object file type */
97 uint16_t e_machine; /* Architecture */
98 uint32_t e_version; /* Object file version */
99 uint64_t e_entry; /* Entry point virtual address */
100 uint64_t e_phoff; /* Program header table file offset */
101 uint64_t e_shoff; /* Section header table file offset */
102 uint32_t e_flags; /* Processor-specific flags */
103 uint16_t e_ehsize; /* ELF header size in bytes */
104 uint16_t e_phentsize; /* Program header table entry size */
105 uint16_t e_phnum; /* Program header table entry count */
106 uint16_t e_shentsize; /* Section header table entry size */
107 uint16_t e_shnum; /* Section header table entry count */
108 uint16_t e_shstrndx; /* Section header string table index */
109 } Elf64_Ehdr;
110
111 typedef struct
112 {
113 uint32_t sh_name; /* Section name (string tbl index) */
114 uint32_t sh_type; /* Section type */
115 uint32_t sh_flags; /* Section flags */
116 uint32_t sh_addr; /* Section virtual addr at execution */
117 uint32_t sh_offset; /* Section file offset */
118 uint32_t sh_size; /* Section size in bytes */
119 uint32_t sh_link; /* Link to another section */
120 uint32_t sh_info; /* Additional section information */
121 uint32_t sh_addralign; /* Section alignment */
122 uint32_t sh_entsize; /* Entry size if section holds table */
123 } Elf32_Shdr;
124
125 typedef struct
126 {
127 uint32_t sh_name; /* Section name (string tbl index) */
128 uint32_t sh_type; /* Section type */
129 uint64_t sh_flags; /* Section flags */
130 uint64_t sh_addr; /* Section virtual addr at execution */
131 uint64_t sh_offset; /* Section file offset */
132 uint64_t sh_size; /* Section size in bytes */
133 uint32_t sh_link; /* Link to another section */
134 uint32_t sh_info; /* Additional section information */
135 uint64_t sh_addralign; /* Section alignment */
136 uint64_t sh_entsize; /* Entry size if section holds table */
137 } Elf64_Shdr;
138
139 typedef struct elf32_rel {
140 uint32_t r_offset;
141 uint32_t r_info;
142 } Elf32_Rel;
143
144 typedef struct elf64_rel {
145 uint64_t r_offset; /* Location at which to apply the action */
146 uint64_t r_info; /* index and type of relocation */
147 } Elf64_Rel;
148
149 typedef struct elf32_rela{
150 uint32_t r_offset;
151 uint32_t r_info;
152 int32_t r_addend;
153 } Elf32_Rela;
154
155 typedef struct elf64_rela {
156 uint64_t r_offset; /* Location at which to apply the action */
157 uint64_t r_info; /* index and type of relocation */
158 int64_t r_addend; /* Constant addend used to compute value */
159 } Elf64_Rela;
160
161
162 struct modversion_info {
163 unsigned long crc;
164 char name[64 - sizeof(unsigned long)];
165 };
166
167 struct modversion_info2 {
168 /* Offset of the next modversion entry in relation to this one. */
169 uint32_t next;
170 uint32_t crc;
171 char name[0];
172 };
173
174
175 typedef struct ko_param
176 {
177 unsigned char magic[16];
178 unsigned long struct_size;
179 unsigned long pgsize;
180 unsigned long printk_addr;
181 unsigned long ro_addr;
182 unsigned long rw_addr;
183 unsigned long reg_kprobe_addr;
184 unsigned long unreg_kprobe_addr;
185 unsigned long sym_get_addr;
186 unsigned long sym_get_size;
187 unsigned long sym_put_addr;
188 unsigned long sym_put_size;
189 unsigned long kv_major;
190 unsigned long ibt;
191 unsigned long kv_minor;
192 unsigned long blkdev_get_addr;
193 unsigned long blkdev_put_addr;
194 unsigned long bdev_open_addr;
195 unsigned long kv_subminor;
196 unsigned long bdev_file_open_addr;
197 unsigned long padding[1];
198 }ko_param;
199
200 #pragma pack()
201
202 static int verbose = 0;
203 #define debug(fmt, ...) if(verbose) printf(fmt, ##__VA_ARGS__)
204
205 static int vtoykmod_write_file(char *name, void *buf, int size)
206 {
207 FILE *fp;
208
209 fp = fopen(name, "wb+");
210 if (!fp)
211 {
212 return -1;
213 }
214
215 fwrite(buf, 1, size, fp);
216 fclose(fp);
217
218 return 0;
219 }
220
221 static int vtoykmod_read_file(char *name, char **buf)
222 {
223 int size;
224 FILE *fp;
225 char *databuf;
226
227 fp = fopen(name, "rb");
228 if (!fp)
229 {
230 debug("failed to open %s %d\n", name, errno);
231 return -1;
232 }
233
234 fseek(fp, 0, SEEK_END);
235 size = (int)ftell(fp);
236 fseek(fp, 0, SEEK_SET);
237
238 databuf = malloc(size);
239 if (!databuf)
240 {
241 debug("failed to open malloc %d\n", size);
242 return -1;
243 }
244
245 fread(databuf, 1, size, fp);
246 fclose(fp);
247
248 *buf = databuf;
249 return size;
250 }
251
252 static int vtoykmod_find_section64(char *buf, char *section, int *offset, int *len, Elf64_Shdr **shdr)
253 {
254 uint16_t i;
255 int cmplen;
256 char *name = NULL;
257 char *strtbl = NULL;
258 Elf64_Ehdr *elf = NULL;
259 Elf64_Shdr *sec = NULL;
260
261 cmplen = (int)strlen(section);
262
263 elf = (Elf64_Ehdr *)buf;
264 sec = (Elf64_Shdr *)(buf + elf->e_shoff);
265 strtbl = buf + sec[elf->e_shstrndx].sh_offset;
266
267 for (i = 0; i < elf->e_shnum; i++)
268 {
269 name = strtbl + sec[i].sh_name;
270 if (name && strncmp(name, section, cmplen) == 0)
271 {
272 *offset = (int)(sec[i].sh_offset);
273 *len = (int)(sec[i].sh_size);
274 if (shdr)
275 {
276 *shdr = sec + i;
277 }
278 return 0;
279 }
280 }
281
282 return 1;
283 }
284
285 static int vtoykmod_find_section32(char *buf, char *section, int *offset, int *len, Elf32_Shdr **shdr)
286 {
287 uint16_t i;
288 int cmplen;
289 char *name = NULL;
290 char *strtbl = NULL;
291 Elf32_Ehdr *elf = NULL;
292 Elf32_Shdr *sec = NULL;
293
294 cmplen = (int)strlen(section);
295
296 elf = (Elf32_Ehdr *)buf;
297 sec = (Elf32_Shdr *)(buf + elf->e_shoff);
298 strtbl = buf + sec[elf->e_shstrndx].sh_offset;
299
300 for (i = 0; i < elf->e_shnum; i++)
301 {
302 name = strtbl + sec[i].sh_name;
303 if (name && strncmp(name, section, cmplen) == 0)
304 {
305 *offset = (int)(sec[i].sh_offset);
306 *len = (int)(sec[i].sh_size);
307 if (shdr)
308 {
309 *shdr = sec + i;
310 }
311 return 0;
312 }
313 }
314
315 return 1;
316 }
317
318 static int vtoykmod_update_modcrc1(char *oldmodver, int oldcnt, char *newmodver, int newcnt)
319 {
320 int i, j;
321 struct modversion_info *pold, *pnew;
322
323 pold = (struct modversion_info *)oldmodver;
324 pnew = (struct modversion_info *)newmodver;
325
326 debug("module update modver format 1\n");
327 for (i = 0; i < oldcnt; i++)
328 {
329 for (j = 0; j < newcnt; j++)
330 {
331 if (strcmp(pold[i].name, pnew[j].name) == 0)
332 {
333 debug("CRC 0x%08lx --> 0x%08lx %s\n", pold[i].crc, pnew[i].crc, pold[i].name);
334 pold[i].crc = pnew[j].crc;
335 break;
336 }
337 }
338 }
339
340 return 0;
341 }
342
343
344 static int vtoykmod_update_modcrc2(char *oldmodver, int oldlen, char *newmodver, int newlen)
345 {
346 struct modversion_info2 *pold, *pnew, *pnewend;
347
348 pold = (struct modversion_info2 *)oldmodver;
349 pnew = (struct modversion_info2 *)newmodver;
350 pnewend = (struct modversion_info2 *)(newmodver + newlen);
351
352 debug("module update modver format 2\n");
353 /* here we think that there is only module_layout in oldmodver */
354
355 for (; pnew < pnewend && pnew->next; pnew = (struct modversion_info2 *)((char *)pnew + pnew->next))
356 {
357 if (strcmp(pnew->name, "module_layout") == 0)
358 {
359 debug("CRC 0x%08x --> 0x%08x %s\n", pold->crc, pnew->crc, pnew->name);
360 memset(pold, 0, oldlen);
361 pold->next = 0x18; /* 8 + module_layout align 8 */
362 pold->crc = pnew->crc;
363 strcpy(pold->name, pnew->name);
364 break;
365 }
366 }
367
368 return 0;
369 }
370
371
372 static int vtoykmod_update_modcrc(char *oldmodver, int oldlen, char *newmodver, int newlen)
373 {
374 uint32_t uiCrc = 0;
375
376 memcpy(&uiCrc, newmodver + 4, 4);
377
378 if (uiCrc > 0)
379 {
380 return vtoykmod_update_modcrc2(oldmodver, oldlen, newmodver, newlen);
381 }
382 else
383 {
384 return vtoykmod_update_modcrc1(oldmodver, oldlen / 64, newmodver, newlen / 64);
385 }
386 }
387
388 static int vtoykmod_update_vermagic(char *oldbuf, int oldsize, char *newbuf, int newsize, int *modver)
389 {
390 int i = 0;
391 char *oldver = NULL;
392 char *newver = NULL;
393
394 *modver = 0;
395
396 for (i = 0; i < oldsize - 9; i++)
397 {
398 if (strncmp(oldbuf + i, "vermagic=", 9) == 0)
399 {
400 oldver = oldbuf + i + 9;
401 debug("Find old vermagic at %d <%s>\n", i, oldver);
402 break;
403 }
404 }
405
406 for (i = 0; i < newsize - 9; i++)
407 {
408 if (strncmp(newbuf + i, "vermagic=", 9) == 0)
409 {
410 newver = newbuf + i + 9;
411 debug("Find new vermagic at %d <%s>\n", i, newver);
412 break;
413 }
414 }
415
416 if (oldver && newver)
417 {
418 memcpy(oldver, newver, strlen(newver) + 1);
419 //if (strstr(newver, "modversions"))
420 {
421 *modver = 1;
422 }
423 }
424
425 return 0;
426 }
427
428 int vtoykmod_update(int kvMajor, int kvMinor, char *oldko, char *newko)
429 {
430 int rc = 0;
431 int modver = 0;
432 int oldoff, oldlen;
433 int newoff, newlen;
434 int oldsize, newsize;
435 char *newbuf, *oldbuf;
436 Elf64_Shdr *sec = NULL;
437
438 oldsize = vtoykmod_read_file(oldko, &oldbuf);
439 newsize = vtoykmod_read_file(newko, &newbuf);
440 if (oldsize < 0 || newsize < 0)
441 {
442 return 1;
443 }
444
445 /* 1: update vermagic */
446 vtoykmod_update_vermagic(oldbuf, oldsize, newbuf, newsize, &modver);
447
448 /* 2: update modversion crc */
449 if (modver)
450 {
451 if (oldbuf[EI_CLASS] == ELFCLASS64)
452 {
453 rc = vtoykmod_find_section64(oldbuf, "__versions", &oldoff, &oldlen, NULL);
454 rc += vtoykmod_find_section64(newbuf, "__versions", &newoff, &newlen, NULL);
455 }
456 else
457 {
458 rc = vtoykmod_find_section32(oldbuf, "__versions", &oldoff, &oldlen, NULL);
459 rc += vtoykmod_find_section32(newbuf, "__versions", &newoff, &newlen, NULL);
460 }
461
462 if (rc == 0)
463 {
464 vtoykmod_update_modcrc(oldbuf + oldoff, oldlen, newbuf + newoff, newlen);
465 }
466 }
467 else
468 {
469 debug("no need to proc modversions\n");
470 }
471
472 /* 3: update relocate address */
473 if (oldbuf[EI_CLASS] == ELFCLASS64)
474 {
475 Elf64_Rela *oldRela, *newRela;
476
477 rc = vtoykmod_find_section64(oldbuf, ".rela.gnu.linkonce.this_module", &oldoff, &oldlen, NULL);
478 rc += vtoykmod_find_section64(newbuf, ".rela.gnu.linkonce.this_module", &newoff, &newlen, NULL);
479 if (rc == 0)
480 {
481 oldRela = (Elf64_Rela *)(oldbuf + oldoff);
482 newRela = (Elf64_Rela *)(newbuf + newoff);
483
484 debug("init_module rela: 0x%llx --> 0x%llx\n", (_ull)(oldRela[0].r_offset), (_ull)(newRela[0].r_offset));
485 oldRela[0].r_offset = newRela[0].r_offset;
486 oldRela[0].r_addend = newRela[0].r_addend;
487
488 debug("cleanup_module rela: 0x%llx --> 0x%llx\n", (_ull)(oldRela[1].r_offset), (_ull)(newRela[1].r_offset));
489 oldRela[1].r_offset = newRela[1].r_offset;
490 oldRela[1].r_addend = newRela[1].r_addend;
491 }
492 else
493 {
494 debug("section .rela.gnu.linkonce.this_module not found\n");
495 }
496
497 if (kvMajor > 6 || (kvMajor == 6 && kvMinor >= 3))
498 {
499 rc = vtoykmod_find_section64(oldbuf, ".gnu.linkonce.this_module", &oldoff, &oldlen, &sec);
500 rc += vtoykmod_find_section64(newbuf, ".gnu.linkonce.this_module", &newoff, &newlen, NULL);
501 if (rc == 0)
502 {
503 debug("section .gnu.linkonce.this_module change oldlen:0x%x to newlen:0x%x\n", oldlen, newlen);
504 if (sec)
505 {
506 sec->sh_size = newlen;
507 }
508 }
509 else
510 {
511 debug("section .gnu.linkonce.this_module not found\n");
512 }
513 }
514 }
515 else
516 {
517 Elf32_Rel *oldRel, *newRel;
518
519 rc = vtoykmod_find_section32(oldbuf, ".rel.gnu.linkonce.this_module", &oldoff, &oldlen, NULL);
520 rc += vtoykmod_find_section32(newbuf, ".rel.gnu.linkonce.this_module", &newoff, &newlen, NULL);
521 if (rc == 0)
522 {
523 oldRel = (Elf32_Rel *)(oldbuf + oldoff);
524 newRel = (Elf32_Rel *)(newbuf + newoff);
525
526 debug("init_module rel: 0x%x --> 0x%x\n", oldRel[0].r_offset, newRel[0].r_offset);
527 oldRel[0].r_offset = newRel[0].r_offset;
528
529 debug("cleanup_module rel: 0x%x --> 0x%x\n", oldRel[0].r_offset, newRel[0].r_offset);
530 oldRel[1].r_offset = newRel[1].r_offset;
531 }
532 else
533 {
534 debug("section .rel.gnu.linkonce.this_module not found\n");
535 }
536 }
537
538 vtoykmod_write_file(oldko, oldbuf, oldsize);
539
540 free(oldbuf);
541 free(newbuf);
542
543 return 0;
544 }
545
546 int vtoykmod_fill_param(char **argv)
547 {
548 int i;
549 int size;
550 char *buf = NULL;
551 ko_param *param;
552 unsigned char magic[16] = { magic_sig };
553
554 size = vtoykmod_read_file(argv[0], &buf);
555 if (size < 0)
556 {
557 return 1;
558 }
559
560 for (i = 0; i < size; i++)
561 {
562 if (memcmp(buf + i, magic, 16) == 0)
563 {
564 debug("Find param magic at %d\n", i);
565 param = (ko_param *)(buf + i);
566
567 param->struct_size = (unsigned long)sizeof(ko_param);
568 param->pgsize = strtoul(argv[1], NULL, 10);
569 param->printk_addr = strtoul(argv[2], NULL, 16);
570 param->ro_addr = strtoul(argv[3], NULL, 16);
571 param->rw_addr = strtoul(argv[4], NULL, 16);
572 param->sym_get_addr = strtoul(argv[5], NULL, 16);
573 param->sym_get_size = strtoul(argv[6], NULL, 10);
574 param->sym_put_addr = strtoul(argv[7], NULL, 16);
575 param->sym_put_size = strtoul(argv[8], NULL, 10);
576 param->reg_kprobe_addr = strtoul(argv[9], NULL, 16);
577 param->unreg_kprobe_addr = strtoul(argv[10], NULL, 16);
578 param->kv_major = strtoul(argv[11], NULL, 10);
579 param->ibt = strtoul(argv[12], NULL, 16);;
580 param->kv_minor = strtoul(argv[13], NULL, 10);
581 param->blkdev_get_addr = strtoul(argv[14], NULL, 16);
582 param->blkdev_put_addr = strtoul(argv[15], NULL, 16);
583 param->kv_subminor = strtoul(argv[16], NULL, 10);
584 param->bdev_open_addr = strtoul(argv[17], NULL, 16);
585 param->bdev_file_open_addr = strtoul(argv[18], NULL, 16);
586
587 debug("pgsize=%lu (%s)\n", param->pgsize, argv[1]);
588 debug("printk_addr=0x%lx (%s)\n", param->printk_addr, argv[2]);
589 debug("ro_addr=0x%lx (%s)\n", param->ro_addr, argv[3]);
590 debug("rw_addr=0x%lx (%s)\n", param->rw_addr, argv[4]);
591 debug("sym_get_addr=0x%lx (%s)\n", param->sym_get_addr, argv[5]);
592 debug("sym_get_size=%lu (%s)\n", param->sym_get_size, argv[6]);
593 debug("sym_put_addr=0x%lx (%s)\n", param->sym_put_addr, argv[7]);
594 debug("sym_put_size=%lu (%s)\n", param->sym_put_size, argv[8]);
595 debug("reg_kprobe_addr=0x%lx (%s)\n", param->reg_kprobe_addr, argv[9]);
596 debug("unreg_kprobe_addr=0x%lx (%s)\n", param->unreg_kprobe_addr, argv[10]);
597 debug("kv_major=%lu (%s)\n", param->kv_major, argv[11]);
598 debug("ibt=0x%lx (%s)\n", param->ibt, argv[12]);
599 debug("kv_minor=%lu (%s)\n", param->kv_minor, argv[13]);
600 debug("blkdev_get_addr=0x%lx (%s)\n", param->blkdev_get_addr, argv[14]);
601 debug("blkdev_put_addr=0x%lx (%s)\n", param->blkdev_put_addr, argv[15]);
602 debug("kv_subminor=%lu (%s)\n", param->kv_subminor, argv[16]);
603 debug("bdev_open_addr=0x%lx (%s)\n", param->bdev_open_addr, argv[17]);
604 debug("bdev_file_open_addr=0x%lx (%s)\n", param->bdev_file_open_addr, argv[18]);
605
606 break;
607 }
608 }
609
610 if (i >= size)
611 {
612 debug("### param magic not found \n");
613 }
614
615 vtoykmod_write_file(argv[0], buf, size);
616
617 free(buf);
618 return 0;
619 }
620
621 #ifdef VTOY_X86_64
622 static int vtoykmod_check_ibt(void)
623 {
624 uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
625
626 __cpuid_count(7, 0, eax, ebx, ecx, edx);
627
628 if (edx & (1 << 20))
629 {
630 return 0;
631 }
632 return 1;
633 }
634 #else
635 static int vtoykmod_check_ibt(void)
636 {
637 return 1;
638 }
639 #endif
640
641 int vtoykmod_main(int argc, char **argv)
642 {
643 int i;
644 int kvMajor = 0;
645 int kvMinor = 0;
646
647 for (i = 0; i < argc; i++)
648 {
649 if (argv[i][0] == '-' && argv[i][1] == 'v')
650 {
651 verbose = 1;
652 break;
653 }
654 }
655
656 if (verbose)
657 {
658 printf("==== Dump Argv ====\n");
659 for (i = 0; i < argc; i++)
660 {
661 printf("<%s> ", argv[i]);
662 }
663 printf("\n");
664 }
665
666 if (argv[1][0] == '-' && argv[1][1] == 'f')
667 {
668 return vtoykmod_fill_param(argv + 2);
669 }
670 else if (argv[1][0] == '-' && argv[1][1] == 'u')
671 {
672 kvMajor = (int)strtol(argv[2], NULL, 10);
673 kvMinor = (int)strtol(argv[3], NULL, 10);
674 return vtoykmod_update(kvMajor, kvMinor, argv[4], argv[5]);
675 }
676 else if (argv[1][0] == '-' && argv[1][1] == 'I')
677 {
678 return vtoykmod_check_ibt();
679 }
680
681 return 0;
682 }
683
684 // wrapper main
685 #ifndef BUILD_VTOY_TOOL
686 int main(int argc, char **argv)
687 {
688 return vtoykmod_main(argc, argv);
689 }
690 #endif
691