Source

atomicparsley / src / parsley.cpp

Full commit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
4836
4837
4838
4839
4840
4841
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902
4903
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
4924
4925
4926
4927
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998
4999
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
5039
5040
5041
5042
5043
5044
5045
5046
5047
5048
5049
5050
5051
5052
5053
5054
5055
5056
//==================================================================//
/*
    AtomicParsley - parsley.cpp

    AtomicParsley is GPL software; you can freely distribute,
    redistribute, modify & use under the terms of the GNU General
    Public License; either version 2 or its successor.

    AtomicParsley is distributed under the GPL "AS IS", without
    any warranty; without the implied warranty of merchantability
    or fitness for either an expressed or implied particular purpose.

    Please see the included GNU General Public License (GPL) for
    your rights and further details; see the file COPYING. If you
    cannot, write to the Free Software Foundation, 59 Temple Place
    Suite 330, Boston, MA 02111-1307, USA.  Or www.fsf.org

    Copyright Š2005-2007 puck_lock
    with contributions from others; see the CREDITS file

    ----------------------
    Code Contributions by:

    * Mike Brancato - Debian patches & build support
    * Lowell Stewart - null-termination bugfix for Apple compliance
		* Brian Story - native Win32 patches; memset/framing/leaks fixes
                                                                   */
//==================================================================//

#include "AtomicParsley.h"
#include "AtomDefs.h"

//#define DEBUG_V

/////////////////////////////////////////////////////////////////////////////
//                     Global Variables                                    //
/////////////////////////////////////////////////////////////////////////////

bool modified_atoms = false;
bool alter_original = false;
bool preserve_timestamps = false;


FILE* source_file = NULL;
uint64_t file_size;

struct AtomicInfo parsedAtoms[MAX_ATOMS];
short atom_number = 0;
uint8_t generalAtomicLevel = 1;

bool file_opened = false;
bool parsedfile = false;
bool move_moov_atom = true;
bool moov_atom_was_mooved = false;
AtomicInfo* hdlrAtom = NULL;
AtomicInfo* movie_header_atom = NULL;
bool complete_free_space_erasure = false;
bool psp_brand = false;
bool force_existing_hierarchy = false;
int metadata_style = UNDEFINED_STYLE;
bool deep_atom_scan = false;

uint32_t max_buffer =
#ifdef __linux__
	0.5 /* splice() allows us to use less buffer space */
#else
	10
#endif
	*1024*1024
;

uint64_t bytes_before_mdat=0;
uint64_t bytes_into_mdat = 0;
uint64_t mdat_supplemental_offset = 0;
uint64_t removed_bytes_tally = 0;
uint64_t new_file_size = 0; //used for the progressbar
uint32_t brand = 0;
uint64_t mdatData = 0; //now global, used in bitrate calcs

uint64_t gapless_void_padding = 0; //possibly used in the context of gapless playback support by Apple

struct DynamicUpdateStat dynUpd;
struct padding_preferences pad_prefs;

short max_display_width = 55;
char* file_progress_buffer=(char*)calloc(1, sizeof(char)* (max_display_width+50) ); //+50 for any overflow in "%100", or "|"

#if defined (DARWIN_PLATFORM)
struct PicPrefs myPicturePrefs;
#endif
bool parsed_prefs = false;
char* twenty_byte_buffer = (char *)malloc(sizeof(char)*20);

EmployedCodecs track_codecs = {false, false, false, false, false, false, false, false, false, false};

uint8_t UnicodeOutputStatus = UNIVERSAL_UTF8; //on windows, controls whether input/output strings are utf16 or raw utf8; reset in wmain()

uint8_t forced_suffix_type = NO_TYPE_FORCING;

/////////////////////////////////////////////////////////////////////////////
//                      Versioning                                         //
/////////////////////////////////////////////////////////////////////////////

void ShowVersionInfo() {

#if defined (_WIN32)
	char *unicode_enabled;
	if (UnicodeOutputStatus == WIN32_UTF16) {
#ifndef __CYGWIN__
		unicode_enabled = "(utf16)";
#else
		unicode_enabled = "(utf8 with utf16 CD access)";
#endif

		// its utf16 in the sense that any text entering on a modern Win32 system
		// enters as utf16le - but gets converted immediately after AP.exe starts
		// to utf8 all arguments, strings, filenames, options are sent around as
		// utf8. For modern Win32 systems, filenames get converted to utf16 for
		// output as needed.  Any strings to be set as utf16 in 3gp assets are
		// converted to utf16be as needed (true for all OS implementations).
		// Printing out to the console should be utf8.

	} else if (UnicodeOutputStatus == UNIVERSAL_UTF8) {
#ifndef __CYGWIN__
		unicode_enabled = "(raw utf8)";
#else
		unicode_enabled = "(utf8 with raw utf8 CD access)";
#endif

		// utf8 in the sense that any text entered had its utf16 upper byte
		// stripped and reduced to (unchecked) raw utf8 for utilities that work in
		// utf8. Any unicode (utf16) filenames were clobbered in that processes are
		// invalid now. Any intermediate folder with unicode in it will now likely
		// cause an error of some sort.
	}

#else
#define unicode_enabled	"(utf8)"
#endif

	fprintf(stdout, "AtomicParsley version: %s %s\n",
		PACKAGE_VERSION, unicode_enabled);

}

///////////////////////////////////////////////////////////////////////////////////////
//                               Generic Functions                                   //
///////////////////////////////////////////////////////////////////////////////////////

int APar_TestArtworkBinaryData(const char* artworkPath) {
	int artwork_dataType = 0;
	FILE *artfile = APar_OpenFile(artworkPath, "rb");
	if (artfile != NULL) {
		APar_read64(twenty_byte_buffer, artfile, 0);
		if ( strncmp(twenty_byte_buffer, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0 ) {
			artwork_dataType = AtomFlags_Data_PNGBinary;
		} else if ( strncmp(twenty_byte_buffer, "\xFF\xD8\xFF\xE0", 4) == 0 || memcmp(twenty_byte_buffer, "\xFF\xD8\xFF\xE1", 4) == 0 ) {
			artwork_dataType = AtomFlags_Data_JPEGBinary;
		} else {
			fprintf(stdout, "AtomicParsley error: %s\n\t image file is not jpg/png and cannot be embedded.\n", artworkPath);
			exit(1);
		}
		fclose(artfile);

	} else {
		fprintf(stdout, "AtomicParsley error: %s\n\t image file could not be opened.\n", artworkPath);
		exit(1);
	}
	return artwork_dataType;
}

void APar_FreeMemory() {
	for(int iter=0; iter < atom_number; iter++) {
		if (parsedAtoms[iter].AtomicName != NULL) {
				free(parsedAtoms[iter].AtomicName);
				parsedAtoms[iter].AtomicName=NULL;
			}
		if (parsedAtoms[iter].AtomicData != NULL) {
			free(parsedAtoms[iter].AtomicData);
			parsedAtoms[iter].AtomicData = NULL;
		}
		if (parsedAtoms[iter].ReverseDNSname != NULL) {
			free(parsedAtoms[iter].ReverseDNSname);
			parsedAtoms[iter].ReverseDNSname = NULL;
		}
		if (parsedAtoms[iter].ReverseDNSdomain != NULL) {
			free(parsedAtoms[iter].ReverseDNSdomain);
			parsedAtoms[iter].ReverseDNSdomain = NULL;
		}
		if (parsedAtoms[iter].uuid_ap_atomname != NULL) {
			free(parsedAtoms[iter].uuid_ap_atomname);
			parsedAtoms[iter].uuid_ap_atomname = NULL;
		}
		if (parsedAtoms[iter].ID32_TagInfo != NULL) {
			//a cascade of tests & free-ing of all the accrued ID3v2 tag/frame/field data
			APar_FreeID32Memory(parsedAtoms[iter].ID32_TagInfo);

			free(parsedAtoms[iter].ID32_TagInfo);
			parsedAtoms[iter].ID32_TagInfo = NULL;
		}
	}
	free(twenty_byte_buffer);
	twenty_byte_buffer=NULL;
	free(file_progress_buffer);
	file_progress_buffer=NULL;

	return;
}

///////////////////////////////////////////////////////////////////////////////////////
//                        Picture Preferences Functions                              //
///////////////////////////////////////////////////////////////////////////////////////

#if defined (DARWIN_PLATFORM)
PicPrefs APar_ExtractPicPrefs(char* env_PicOptions) {
	if (!parsed_prefs) {

		parsed_prefs = true; //only set default values & parse once

		myPicturePrefs.max_dimension=0; //dimensions won't be used to alter image
		myPicturePrefs.dpi = 72;
		myPicturePrefs.max_Kbytes = 0; //no target size to shoot for
		myPicturePrefs.allJPEG = false;
		myPicturePrefs.allPNG = false;
		myPicturePrefs.addBOTHpix = false;
		myPicturePrefs.force_dimensions = false;
		myPicturePrefs.force_height = 0;
		myPicturePrefs.force_width = 0;
		myPicturePrefs.removeTempPix = true; //we'll just make this the default

		char* unparsed_opts = env_PicOptions;
		if (env_PicOptions == NULL) return myPicturePrefs;

		while (unparsed_opts[0] != 0) {
			if (strncmp(unparsed_opts,"MaxDimensions=",14) == 0) {
				unparsed_opts+=14;
				myPicturePrefs.max_dimension = (int)strtol(unparsed_opts, NULL, 10);

			} else if (strncmp(unparsed_opts,"DPI=",4) == 0) {
				unparsed_opts+=4;
				myPicturePrefs.dpi = (int)strtol(unparsed_opts, NULL, 10);

			} else if (strncmp(unparsed_opts,"MaxKBytes=",10) == 0) {
				unparsed_opts+=10;
				myPicturePrefs.max_Kbytes = (int)strtol(unparsed_opts, NULL, 10)*1024;

			} else if (strncmp(unparsed_opts,"AllPixJPEG=",11) == 0) {
				unparsed_opts+=11;
				if (strcmp(unparsed_opts, "true") == 0) {
					myPicturePrefs.allJPEG = true;
				}

			} else if (strncmp(unparsed_opts,"AllPixPNG=",10) == 0) {
				unparsed_opts+=10;
				if (strcmp(unparsed_opts, "true") == 0) {
					myPicturePrefs.allPNG = true;
				}

			} else if (strncmp(unparsed_opts,"AddBothPix=",11) == 0) {
				unparsed_opts+=11;
				if (strcmp(unparsed_opts, "true") == 0) {
					myPicturePrefs.addBOTHpix = true;
				}

			} else if (strcmp(unparsed_opts,"SquareUp") == 0) {
				unparsed_opts+=7;
				myPicturePrefs.squareUp = true;

			} else if (strcmp(unparsed_opts,"removeTempPix") == 0) {
				unparsed_opts+=13;
				myPicturePrefs.removeTempPix = true;

			} else if (strcmp(unparsed_opts,"keepTempPix") == 0) { //NEW
				unparsed_opts+=11;
				myPicturePrefs.removeTempPix = false;

			} else if (strncmp(unparsed_opts,"ForceHeight=",12) == 0) {
				unparsed_opts+=12;
				myPicturePrefs.force_height = strtol(unparsed_opts, NULL, 10);

			} else if (strncmp(unparsed_opts,"ForceWidth=",11) == 0) {
				unparsed_opts+=11;
				myPicturePrefs.force_width = strtol(unparsed_opts, NULL, 10);

			} else {
				unparsed_opts++;
			}
		}
	}

	if (myPicturePrefs.force_height > 0 && myPicturePrefs.force_width > 0) myPicturePrefs.force_dimensions = true;
	return myPicturePrefs;
}
#endif

///////////////////////////////////////////////////////////////////////////////////////
//                            Locating/Finding Atoms                                 //
///////////////////////////////////////////////////////////////////////////////////////

AtomicInfo* APar_FindAtomInTrack(uint8_t &total_tracks, uint8_t &track_num, const char* search_atom_str) {
	uint8_t track_tally = 0;
	short iter = 0;

	while (parsedAtoms[iter].NextAtomNumber != 0) {
		if ( memcmp(parsedAtoms[iter].AtomicName, "trak", 4) == 0 && parsedAtoms[iter].AtomicLevel == 2) {
			track_tally += 1;
			if (track_num == 0) {
				total_tracks += 1;

			} else if (track_num == track_tally) {
				//drill down into stsd
				short next_atom = parsedAtoms[iter].NextAtomNumber;
				while (parsedAtoms[next_atom].AtomicLevel > parsedAtoms[iter].AtomicLevel) {

					if (strncmp(parsedAtoms[next_atom].AtomicName, search_atom_str, 4) == 0) {
						return &parsedAtoms[next_atom];
					} else {
						next_atom = parsedAtoms[next_atom].NextAtomNumber;
					}
				}
			}
		}
		iter=parsedAtoms[iter].NextAtomNumber;
	}
	return NULL;
}

short APar_FindPrecedingAtom(short an_atom_num) {
	short precedingAtom = 0;
	short iter = 0;
	while (parsedAtoms[iter].NextAtomNumber != 0) {
		if (parsedAtoms[iter].NextAtomNumber == parsedAtoms[an_atom_num].NextAtomNumber) {
			break;
		} else {
			precedingAtom = iter;
			iter=parsedAtoms[iter].NextAtomNumber;
		}
	}
	return precedingAtom;
}

short APar_FindParentAtom(int order_in_tree, uint8_t this_atom_level) {
	short thisAtom = 0;
	short iter = order_in_tree;
	while (parsedAtoms[iter].AtomicNumber != 0) {
		iter = APar_FindPrecedingAtom(iter);
		if (parsedAtoms[iter].AtomicLevel == this_atom_level-1) {
			thisAtom = iter;
			break;
		}
	}
	return thisAtom;
}

/*----------------------
APar_ProvideAtomPath
  this_atom - index into array of parsedAtoms for the wanted path of an atom
  atom_path - string into which the path will be placed (working backwards)
  fromFile - controls the manner of extracting parents (atom sizes from file, or a simpler atomic level if from memory)

    First, determine exactly how many atoms will constitute the full path and calculate where into the string to first start placing atom names. Start by
		working off the current atom. Using fromFile, either use a more stringent atom start/length from a file, or a more relaxed atom level if from memory.
		The array in memory won't have proper atom sizes except for the last child atom typically ('data' will have a proper size, but its parent and all
		other parents will not have sizing automatically updated - which happens only at writeout time).
----------------------*/
void APar_ProvideAtomPath(short this_atom, char* &atom_path, bool fromFile) {
	short preceding_atom = this_atom;
	uint8_t	current_atomic_level = parsedAtoms[this_atom].AtomicLevel;
	int str_offset = (parsedAtoms[this_atom].AtomicLevel-1) * 5; //5 = 'atom" + '.'
	if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM) {
		str_offset+=5; //include a "uuid=" string;
	}

	memcpy(atom_path + str_offset, parsedAtoms[preceding_atom].AtomicName, 4);
	str_offset -=5;
	if (parsedAtoms[preceding_atom].AtomicLevel != 1) {
		memcpy(atom_path + str_offset +4, ".", 1);
	}
	if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM) {
		memcpy(atom_path + str_offset, "uuid=", 5);
		str_offset-=5;
	}

	while (parsedAtoms[preceding_atom].AtomicNumber != 0) {

		if (fromFile) {
			if (parsedAtoms[preceding_atom].AtomicStart < parsedAtoms[this_atom].AtomicStart &&
					parsedAtoms[preceding_atom].AtomicLength > parsedAtoms[this_atom].AtomicLength &&
					parsedAtoms[preceding_atom].AtomicStart + parsedAtoms[preceding_atom].AtomicLength >= parsedAtoms[this_atom].AtomicStart + parsedAtoms[this_atom].AtomicLength &&
					parsedAtoms[preceding_atom].AtomicContainerState <= DUAL_STATE_ATOM ) {
				memcpy(atom_path + str_offset, parsedAtoms[preceding_atom].AtomicName, 4);
				str_offset -=5;
				if (str_offset >= 0) {
					memcpy(atom_path + str_offset +4, ".", 1);
				}

				preceding_atom = APar_FindPrecedingAtom(preceding_atom);

			} else {
				preceding_atom = APar_FindPrecedingAtom(preceding_atom);
			}
		} else {
			if (parsedAtoms[preceding_atom].AtomicLevel < current_atomic_level) {
				memcpy(atom_path + str_offset, parsedAtoms[preceding_atom].AtomicName, 4);
				str_offset -=5;
				if (str_offset >= 0) {
					memcpy(atom_path + str_offset +4, ".", 1);
				}

				current_atomic_level = parsedAtoms[preceding_atom].AtomicLevel;
				preceding_atom = APar_FindPrecedingAtom(preceding_atom);
			} else {
				preceding_atom = APar_FindPrecedingAtom(preceding_atom);
			}
		}
		if (preceding_atom == 0 || str_offset < 0) {
			break;
		}
	}

	return;
}

bool APar_Eval_ChunkOffsetImpact(short an_atom_num) {
	bool impact_calculations_directly = false;
	short iter = 0;
	uint8_t found_desired_atom = 0;

	while (true) {
		if ( strncmp(parsedAtoms[iter].AtomicName, "mdat", 4) == 0) {
			if (found_desired_atom) {
				impact_calculations_directly = true;
			}
			break;
		} else {
			iter=parsedAtoms[iter].NextAtomNumber;
		}
		if (iter == 0) {
			break;
		}
		if (iter == an_atom_num) {
			found_desired_atom = 1;
		}
	}
	return impact_calculations_directly;
}

short APar_FindLastAtom() {
	short this_atom_num = 0; //start our search with the first atom
	while (parsedAtoms[this_atom_num].NextAtomNumber != 0) {
		this_atom_num = parsedAtoms[this_atom_num].NextAtomNumber;
	}
	return this_atom_num;
}

short APar_FindLastChild_of_ParentAtom(short thisAtom) {
	short child_atom = parsedAtoms[thisAtom].NextAtomNumber;
	short last_atom = thisAtom; //if there are no children, this will be the first and last atom in the hiearchy
	while (true) {
		if (parsedAtoms[ child_atom ].AtomicLevel > parsedAtoms[thisAtom].AtomicLevel) {
			last_atom = child_atom;
		}
		child_atom = parsedAtoms[child_atom].NextAtomNumber;
		if (child_atom == 0 || parsedAtoms[ child_atom ].AtomicLevel <= parsedAtoms[thisAtom].AtomicLevel) {
			break;
		}
	}
	return last_atom;
}

/*----------------------
APar_ReturnChildrenAtoms
  this_atom - the parent atom that contains any number of children atoms (that are currenly unknown)
  atom_index - the index of the desired child. Passing 0 will return a count of the *total* number of children atoms under this_atom

    Working off of AtomicLevel, test the atoms that follow this_atom to see if they are immediately below this_atom. Increment total_children if is - if
		total_children should match our index, return that desired child at index atom.
----------------------*/
short APar_ReturnChildrenAtoms(short this_atom, uint8_t atom_index) {
	short child_atom = 0;
	uint8_t total_children = 0;
	short iter = parsedAtoms[this_atom].NextAtomNumber;

	while (true) {
		if ( (parsedAtoms[iter].AtomicLevel == parsedAtoms[this_atom].AtomicLevel + 1  && this_atom > 0) ||
				 (this_atom == 0 && parsedAtoms[iter].AtomicLevel == 1) ) {
			total_children++;

			if (atom_index == total_children) {
				child_atom = iter;
				break;
			}
		}
		if (parsedAtoms[iter].AtomicLevel <= parsedAtoms[this_atom].AtomicLevel && this_atom != 0) {
			break;
		} else {
			iter = parsedAtoms[iter].NextAtomNumber;
		}
		if (iter == 0) {
			break;
		}
	}
	if (atom_index == 0) {
		child_atom = (short)total_children;
	}
	return child_atom;
}

/*----------------------
APar_FindChildAtom
  parent_atom - the parent atom that contains any number of children atoms (that are currenly unknown)
	child_name - the name of the atom to search for under the parent_atom

    Given an atom, search its children for child_name
----------------------*/
AtomicInfo* APar_FindChildAtom(short parent_atom, const char* child_name, uint8_t child_name_len = 4, uint16_t desired_index = 1) {
	AtomicInfo* found_child = NULL;
	short test_child_idx = parsedAtoms[parent_atom].NextAtomNumber;
	uint16_t current_count = 0;

	while (parsedAtoms[test_child_idx].AtomicLevel > parsedAtoms[parent_atom].AtomicLevel) {
		if ( (memcmp(parsedAtoms[test_child_idx].AtomicName, child_name, child_name_len) == 0  || memcmp(child_name, "any", 4) == 0) &&
		    parsedAtoms[test_child_idx].AtomicLevel == parsedAtoms[parent_atom].AtomicLevel +1) {
			current_count++;
			if (desired_index == current_count) {
				found_child = &parsedAtoms[test_child_idx];
				break;
			}
		}
		test_child_idx = parsedAtoms[test_child_idx].NextAtomNumber;
	}

	return found_child;
}

/*----------------------
APar_AtomicComparison
	proto_atom - the temporary atom structure to run the tests on
	test_atom - the exising atom to compare the proto_atom against
	match_full_uuids - selects whether to match by atom names (4 bytes) or uuids(16 bytes) which are stored on AtomicName
	reverseDNSdomain - the reverse DNS like com.foo.thing (only used with reverseDNS atoms: ----, mean, name)

    Test if proto_atom matches a single atom (test_atom) by name, level & classification (packed_lang_atom, extended atom...); for certain types of data
		(like packed_lang & reverseDNS 'moov.udta.meta.ilst.----.name:[iTunNORM] atoms currently) add finer grained tests. The return result will be NULL
		if not matched, or returns the atom it matches.
----------------------*/
AtomicInfo* APar_AtomicComparison(AtomicInfo* proto_atom, short test_atom, bool match_full_uuids, const char* reverseDNSdomain) {
	AtomicInfo* return_atom = NULL;
	size_t ATOM_TEST_LEN = (match_full_uuids ? 16 : 4);

	if (parsedAtoms[test_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[test_atom].uuid_style == UUID_DEPRECATED_FORM) {   //accommodate deprecated form
		if (memcmp(parsedAtoms[test_atom].uuid_ap_atomname, proto_atom->AtomicName, 4) == 0) {
			return &parsedAtoms[test_atom];
		}
	}

	//can't do AtomicVerFlags because lots of utilities don't write the proper iTunes flags for iTunes metadata
	if ( memcmp(proto_atom->AtomicName, parsedAtoms[test_atom].AtomicName, ATOM_TEST_LEN) == 0 &&
				proto_atom->AtomicLevel == parsedAtoms[test_atom].AtomicLevel &&
				(proto_atom->AtomicClassification == parsedAtoms[test_atom].AtomicClassification || proto_atom->AtomicClassification == UNKNOWN_ATOM) ) {

		if (proto_atom->AtomicClassification == PACKED_LANG_ATOM) {
			//0x05D9 = 'any' and will be used (internally) to match on name,class,container state alone, disregarding AtomicLanguage
			if (proto_atom->AtomicLanguage == parsedAtoms[test_atom].AtomicLanguage || proto_atom->AtomicLanguage == 0x05D9) {
				return_atom = &parsedAtoms[test_atom];
			}

		} else if (proto_atom->ReverseDNSname != NULL && parsedAtoms[test_atom].ReverseDNSname != NULL) {
			//match on moov.udta.meta.ilst.----.name:[something] (reverse DNS atom)
			if (strcmp(proto_atom->ReverseDNSname, parsedAtoms[test_atom].ReverseDNSname) == 0) {
				if (reverseDNSdomain == NULL) { //lock onto the first reverseDNS form irrespective of domain (TODO: manualAtomRemove will cause this to be NULL)
					return_atom = &parsedAtoms[test_atom];
				} else {
#if defined(DEBUG_V)
					fprintf(stdout, "AP_AtomicComparison   testing wanted rDNS %s domain against atom '%s' %s rDNS domain\n", reverseDNSdomain, parsedAtoms[test_atom].AtomicName,
					                                                              parsedAtoms[APar_FindPrecedingAtom(test_atom)].ReverseDNSdomain);
#endif
					if ( strcmp(reverseDNSdomain, parsedAtoms[APar_FindPrecedingAtom(test_atom)].ReverseDNSdomain) == 0 ) {
						return_atom = &parsedAtoms[test_atom];
					}
				}
			}
		} else {
			return_atom = &parsedAtoms[test_atom];
		}
	}
	return return_atom;
}

/*----------------------
APar_FindLastLikeNamedAtom
	atom_name - the name of the atom to search for; the string itself may have more than 4 bytes
	containing_hierarchy - the parent hierarchy that is expected to carry multiply named atoms differing (in language for example)

    Follow through the atom tree; if a test atom is matched by name, and is a child to the container atom, remember that atom. If nothing matches, the index
		of the container atom is returned; otherwise the last like named atom is returned.
----------------------*/
short APar_FindLastLikeNamedAtom(char* atom_name, short containing_hierarchy) {
	short last_identically_named_atom = APar_FindLastChild_of_ParentAtom(containing_hierarchy); //default returns the last atom in the parent, not the parent
	short eval_atom = parsedAtoms[containing_hierarchy].NextAtomNumber;

	while (true) {
		if (parsedAtoms[eval_atom].AtomicLevel < parsedAtoms[containing_hierarchy].AtomicLevel + 1 || eval_atom == 0) {
			break;
		} else {
			if (memcmp(parsedAtoms[eval_atom].AtomicName, atom_name, 4) == 0 &&
			     parsedAtoms[eval_atom].AtomicLevel == parsedAtoms[containing_hierarchy].AtomicLevel + 1) {
				last_identically_named_atom = eval_atom;
			}
			eval_atom = parsedAtoms[eval_atom].NextAtomNumber;
		}
	}
	return last_identically_named_atom;
}

void APar_FreeSurrogateAtom(AtomicInfo* surrogate_atom) {
	if (surrogate_atom->ReverseDNSname != NULL) {
		free(surrogate_atom->ReverseDNSname);
		surrogate_atom->ReverseDNSname = NULL;
	}
	return;
}

/*----------------------
APar_CreateSurrogateAtom

    Make a temporary AtomicInfo structure to run comparisons against; currently comparisons are done on name, level, classification (versioned...), langauge
		(3gp assets), and iTunes-style reverse dns 'name' carrying a string describing the purpose of the data (iTunNORM). This atom exists outside of a file's
		atom hieararchy that resides in the parsedAtoms[] array.
----------------------*/
void APar_CreateSurrogateAtom(AtomicInfo* surrogate_atom, const char* atom_name, uint8_t atom_level, uint8_t atom_class, uint16_t atom_lang,
                               char* revdns_name, uint8_t revdns_name_len) {
	surrogate_atom->AtomicName = (char*)atom_name;
	surrogate_atom->AtomicLevel = atom_level;

	if (revdns_name != NULL && revdns_name_len) {
		surrogate_atom->ReverseDNSname = (char *)malloc(sizeof(char)*revdns_name_len > 8 ? revdns_name_len+1 : 9);
		memset(surrogate_atom->ReverseDNSname, 0, sizeof(char)*revdns_name_len > 8 ? revdns_name_len+1 : 9);
		memcpy(surrogate_atom->ReverseDNSname, revdns_name, revdns_name_len);

	} else {
		APar_FreeSurrogateAtom(surrogate_atom);
	}
	surrogate_atom->AtomicClassification = atom_class;
	surrogate_atom->AtomicLanguage = atom_lang;
	return;
}

/*----------------------
APar_FindAtom
	atom_name - the full path describing the hiearchy the desired atom can be found in
	createMissing - either create the missing interim atoms as required, or return a NULL if not found
	atom_type - the classification of the last atom (packed language, uuid extended atom...)
	atom_lang - the language of the 3gp asset used when atom_type is packed language type
	match_full_uuids - match 16byte full uuids (typically removing ( possibly non-AP) uuids via --manualAtomRemoval; AP uuids (the new ones) still work on 4bytes**
	reverseDNSdomain - the reverse DNS like com.foo.thing (only used with reverseDNS atoms: ----, mean, name)

    Follow through the atom tree starting with the atom following 'ftyp'. Testing occurs on an atom level basis; a stand-in temporary skeletal atom
		is created to evaluate. If they atoms are deemed matching, atom_name is advanced forward (it still contains the full path, but only 4bytes are
		typically used at a time) and testing occurs until either the desired atom is found, or the last containing hiearchy with an exising atom is
		exhausted without making new atoms.

		NOTE: atom_name can come in these forms:
			classic/vanilla/ordinary atoms:		moov.udta.meta.ilst.cprt.data
			iTunes reverseDNS atoms:          moov.udta.meta.ilst.----.name:[iTunNORM]
			uuid user-extension atoms:        moov.udta.meta.uuid=tdtg (the deprecated form)
			uuid user-extension atoms:        moov.udta.meta.uuid=ba45fcaa-7ef5-5201-8a63-78886495ab1f
			index-based atoms:                moov.trak[2].mdia.minf

		NOTE: On my computer it takes about .04 second to scan the file, .1 second to add about 2 dozen tags, and 1.0 second to copy a file. Updating a file
		from start to finish takes 0.21 seconds. As many loops as this new APar_FindAtom eliminates, it is only marginally faster than the old code.

		** the reason why the old deprecated uuid form & the new uuid full 16byte form work off of a 4byte value (the atom name) is that because we are using a version 5
		sha1 hashed uuid of a name in a given namespace, the identical name in the identical namespace will yield identical an identical uuid (if corrected for endianness).
		This means that that matching by 4 bytes of atom name is the functional equvalent of matching by 16byte uuids.
----------------------*/
AtomicInfo* APar_FindAtom(const char* atom_name, bool createMissing, uint8_t atom_type, uint16_t atom_lang, bool match_full_uuids, const char* reverseDNSdomain) {
	AtomicInfo* thisAtom = NULL;
	char* search_atom_name = (char*)atom_name;
	char* reverse_dns_name = NULL;
	short iter=0;
	uint8_t revdns_name_len = 0;
	uint8_t atom_index = 0; // if there are atoms mutliple identically named at the same level, this is where to store the count as it occurs
	uint16_t desired_index = 1;
	uint8_t search_atom_type = UNKNOWN_ATOM;
	int known_atom = -1;
	short search_atom_start_num = parsedAtoms[0].NextAtomNumber; //don't test 'ftyp'; its atom_number[0] & will be used to know when we have hit the end of the tree; can't hardcode it to '1' because ftyp's following atom can change; only ftyp as parsedAtoms[0] is guaranteed.
	uint8_t present_atomic_level = 1;
	AtomicInfo* last_known_present_parent = NULL;
	AtomicInfo atom_surrogate = { 0 };


#if defined(DEBUG_V)
	fprintf(stdout, "debug: AP_FindAtom entry trying to find '%s'; create missing: %u\n", atom_name, createMissing);
#endif

	while (search_atom_name != NULL) {
		desired_index = 1; //reset the index

		if (atom_type == EXTENDED_ATOM && strncmp(search_atom_name, "uuid=", 5) == 0 ) {
			search_atom_name+=5;
			search_atom_type = atom_type;
 		}

#if defined(DEBUG_V)
		fprintf(stdout, "debug: AP_FindAtom   loop evaluate test  %s (index=%u)\n", search_atom_name, atom_index);
#endif

		size_t portion_len = strlen(search_atom_name);
		if (strncmp(search_atom_name+4, ":[", 2) == 0 && search_atom_name[portion_len-1] == ']') {
			reverse_dns_name = search_atom_name + 4+2; //4bytes atom name 2bytes ":["
			revdns_name_len = portion_len-7; //4bytes atom name, 2 bytes ":[", 1 byte "]"
			search_atom_type = atom_type;

		} else if (search_atom_name[4] == '[') {
			long sa_length=0;
			char sa_at_5=NULL;
			sa_length=strlen(search_atom_name);
			sa_at_5=*(search_atom_name+5);

			sscanf(search_atom_name+5, "%" PRIu16 "", &desired_index);


#if defined(DEBUG_V)
			fprintf(stdout, "debug: AP_FindAtom     >#<indexed atom>#< '%s' at index=%u\n", search_atom_name, desired_index);
#endif
		}

		if (strlen(search_atom_name) == 4) {
			if (atom_type == UNKNOWN_ATOM) {
				known_atom = APar_MatchToKnownAtom(search_atom_name, last_known_present_parent->AtomicName, false, atom_name);
				search_atom_type = KnownAtoms[known_atom].box_type;
			} else {
				search_atom_type = atom_type;
			}
		}

		APar_CreateSurrogateAtom(&atom_surrogate, search_atom_name, present_atomic_level, search_atom_type, atom_lang, reverse_dns_name, revdns_name_len);
		atom_index = 0;

		iter = search_atom_start_num;
		while (true) {
			AtomicInfo* result = NULL;

			//if iter == 0, that means test against 'ftyp' - and since its always 0, don't test it; its to know that the end of the tree is reached
			if (iter != 0 && (parsedAtoms[iter].AtomicLevel == present_atomic_level || reverse_dns_name != NULL) ) {
				result = APar_AtomicComparison(&atom_surrogate, iter, (search_atom_type == EXTENDED_ATOM ? match_full_uuids : false), reverseDNSdomain );
#if defined(DEBUG_V)
				fprintf(stdout, "debug: AP_FindAtom     compare  %s(%u)  against %s (wanted index=%u)\n", search_atom_name, atom_index, parsedAtoms[iter].AtomicName, desired_index);
			} else {
				fprintf(stdout, "debug: AP_FindAtom       %s  rejected against %s\n", search_atom_name, parsedAtoms[iter].AtomicName);
#endif
			}
			if (result != NULL) { //something matched
				atom_index++;
#if defined(DEBUG_V)
		fprintf(stdout, "debug: AP_FindAtom       ***matched*** current index=%u (want %u)\n", atom_index, desired_index);
#endif
				if (search_atom_type != UNKNOWN_ATOM || (search_atom_type == UNKNOWN_ATOM && known_atom != -1) ) {
					thisAtom = result;
#if defined(DEBUG_V)
					fprintf(stdout, "debug: AP_FindAtom         perfect match: %s(%u) == existing %s(%u)\n", search_atom_name, desired_index, parsedAtoms[iter].AtomicName, atom_index);
#endif
				} else {
					last_known_present_parent = result;   //if not, then it isn't the last atom, and must be some form of parent
				}
				if (desired_index == atom_index) {
					search_atom_start_num = parsedAtoms[iter].NextAtomNumber;
					break;
				}
			}

			if (parsedAtoms[iter].AtomicLevel < present_atomic_level && reverse_dns_name == NULL) {
				iter = 0; //force the ending determination of whether to make new atoms or not;
			}

			if (iter == 0 && createMissing) {
				//create that atom
				if (last_known_present_parent != NULL) {
					short last_hierarchical_atom = 0;
#if defined(DEBUG_V)
					fprintf(stdout, "debug: AP_FindAtom-------missing atom, need to create '%s'\n", search_atom_name);
#endif
					if (search_atom_type == PACKED_LANG_ATOM) {
						last_hierarchical_atom = APar_FindLastLikeNamedAtom(atom_surrogate.AtomicName, last_known_present_parent->AtomicNumber);
					} else {
						last_hierarchical_atom = APar_FindLastChild_of_ParentAtom(last_known_present_parent->AtomicNumber);
					}
					thisAtom = APar_CreateSparseAtom(&atom_surrogate, last_known_present_parent, last_hierarchical_atom);
					search_atom_start_num = thisAtom->AtomicNumber;
					if (strlen(search_atom_name) >= 4) {
						last_known_present_parent = thisAtom;
					}
				} else {
					//its a file-level atom that needs to be created, so it won't have a last_known_present_parent
					if (strlen(atom_name) == 4) {
						short total_root_level_atoms = APar_ReturnChildrenAtoms (0, 0);
						short test_root_atom = 0;


						//scan through all top level atoms
						for(uint8_t root_atom_i = 1; root_atom_i <= total_root_level_atoms; root_atom_i++) {
							test_root_atom = APar_ReturnChildrenAtoms (0, root_atom_i);
							if (memcmp(parsedAtoms[test_root_atom].AtomicName, "moov", 4) == 0) {
								break;
							}
						}
						if (test_root_atom != 0) {
							thisAtom = APar_CreateSparseAtom(&atom_surrogate, NULL, APar_FindLastChild_of_ParentAtom(test_root_atom));
						}
					}
				}
				break;

			} else if (iter == 0 && !createMissing) {
					search_atom_name = NULL; //force the break;
					break;
			}
			//fprintf(stdout, "while loop %s %u %u\n", parsedAtoms[iter].AtomicName, atom_index, desired_index);
			iter = parsedAtoms[iter].NextAtomNumber;
		}

		if (iter == 0 && (search_atom_name == NULL || search_atom_type == EXTENDED_ATOM) ) {
			break;
		} else {
			uint8_t periodicity = 0; //allow atoms with periods in their names
			while (true) { // search_atom_name = strsep(&atom_name,".") equivalent
				if (search_atom_name[0] == 0) {
					search_atom_name = NULL;
					break;
				} else if (search_atom_name[0] == '.' && periodicity > 3) {
					search_atom_name++;
					periodicity++;
					break;
				} else {
					search_atom_name++;
					periodicity++;
				}
			}
			present_atomic_level++;
		}
	}
	//APar_PrintAtomicTree(); //because PrintAtomicTree calls DetermineDynamicUpdate (which calls this FindAtom function) to print out padding space, an infinite loop occurs
	APar_FreeSurrogateAtom(&atom_surrogate);
	return thisAtom;
}

///////////////////////////////////////////////////////////////////////////////////////
//                      File scanning & atom parsing                                 //
///////////////////////////////////////////////////////////////////////////////////////

void APar_AtomizeFileInfo(uint64_t Astart, uint64_t Alength,
	uint64_t Aextendedlength, char* Astring, uint8_t Alevel,
	uint8_t Acon_state, uint8_t Aclass, uint32_t Averflags,
	uint16_t Alang, uuid_vitals* uuid_info)
{
	static bool passed_mdat = false;
	AtomicInfo* thisAtom;

	if (atom_number < 0 || atom_number >= MAX_ATOMS) {
		fprintf(stderr, "too many atoms\n");
		abort();
	}

	thisAtom = &parsedAtoms[atom_number];

	thisAtom->AtomicStart = Astart;
	thisAtom->AtomicLength = Alength;
	thisAtom->AtomicLengthExtended = Aextendedlength;
	thisAtom->AtomicNumber = atom_number;
	thisAtom->AtomicLevel = Alevel;
	thisAtom->AtomicContainerState = Acon_state;
	thisAtom->AtomicClassification = Aclass;

	thisAtom->AtomicName = (char*)malloc(sizeof(char)*20);
	memset(thisAtom->AtomicName, 0, sizeof(char)*20);

	if (Aclass == EXTENDED_ATOM) {
		thisAtom->uuid_style = uuid_info->uuid_form;
		if (uuid_info->uuid_form == UUID_DEPRECATED_FORM) {
			memcpy(thisAtom->AtomicName, Astring, 4);
			thisAtom->uuid_ap_atomname = (char*)calloc(1, sizeof(char)*16);
			memcpy(thisAtom->uuid_ap_atomname, Astring, 4);
		} else {
			memcpy(thisAtom->AtomicName, uuid_info->binary_uuid, 16);
			if (uuid_info->uuid_form == UUID_AP_SHA1_NAMESPACE) {
				thisAtom->uuid_ap_atomname = (char*)calloc(1, sizeof(char)*16);
				memcpy(thisAtom->uuid_ap_atomname, uuid_info->uuid_AP_atom_name, 4);
			}
		}
	} else {
		memcpy(thisAtom->AtomicName, Astring, 4);
	}

	thisAtom->AtomicVerFlags = Averflags;
	thisAtom->AtomicLanguage = Alang;

	thisAtom->ancillary_data = 0;

	//set the next atom number of the PREVIOUS atom (we didn't know there would be one until now); this is our default normal mode
	if (atom_number > 0) {
		parsedAtoms[atom_number-1].NextAtomNumber = atom_number;
	}
	thisAtom->NextAtomNumber=0; //this could be the end... (we just can't quite say until we find another atom)

	if (strncmp(Astring, "mdat", 4) == 0) {
		passed_mdat = true;
	}

	if (!passed_mdat && Alevel == 1) {
		bytes_before_mdat += Alength; //this value gets used during FreeFree (for removed_bytes_tally) & chunk offset calculations
	}
	thisAtom->ID32_TagInfo = NULL;

	atom_number++; //increment to the next AtomicInfo array

	return;
}

uint8_t APar_GetCurrentAtomDepth(uint64_t atom_start, uint64_t atom_length) {
	uint8_t level = 1;
	for (int i = 0; i < atom_number; i++) {
		AtomicInfo* thisAtom = &parsedAtoms[i];
		if (atom_start == (thisAtom->AtomicStart + thisAtom->AtomicLength) ) {
			return thisAtom->AtomicLevel;
		} else {
			if ( (atom_start < thisAtom->AtomicStart + thisAtom->AtomicLength) && (atom_start > thisAtom->AtomicStart) ) {
				level++;
			}
		}
	}
	return level;
}

void APar_IdentifyBrand(char* file_brand ) {
	brand = UInt32FromBigEndian(file_brand);
	switch (brand) {
		//what ISN'T supported
		case 0x71742020 : //'qt  '  --this is listed at mp4ra, but there are features of the file that aren't supported (like the 4 NULL bytes after the last udta child atom
			fprintf(stdout, "AtomicParsley error: Quicktime movie files are not supported.\n");
			exit(2);
			break;

		//
		//3GPP2 specification documents brands
		//

		case 0x33673262 : //'3g2b' 3GPP2 release A
			metadata_style = THIRD_GEN_PARTNER_VER2_REL_A;    //3GPP2 C.S0050-A_v1.0_060403, Annex A.2 lists differences between 3GPP & 3GPP2 - assets are not listed
			break;

		case 0x33673261 : //'3g2a'                          //3GPP2 release 0
			metadata_style = THIRD_GEN_PARTNER_VER2;
			break;

		//
		//3GPP specification documents brands, not all are listed at mp4ra
		//

		case 0x33677037 : //'3gp7'                          //Release 7 introduces ID32; though it doesn't list a iso bmffv2 compatible brand. Technically, ID32
		                                                    //could be used on older 3gp brands, but iso2 would have to be added to the compatible brand list.
		case 0x33677337 : //'3gs7'                          //I don't feel the need to do that, since other things might have to be done. And I'm not looking into it.
		case 0x33677237 : //'3gr7'
		case 0x33676537 : //'3ge7'
		case 0x33676737 : //'3gg7'
			metadata_style = THIRD_GEN_PARTNER_VER1_REL7;
			break;

		case 0x33677036 : //'3gp6'                          //3gp assets which were introducted by NTT DoCoMo to the Rel6 workgroup on January 16, 2003
																												//with S4-030005.zip from http://www.3gpp.org/ftp/tsg_sa/WG4_CODEC/TSGS4_25/Docs/ (! albm, loci)
		case 0x33677236 : //'3gr6' progressive
		case 0x33677336 : //'3gs6' streaming
		case 0x33676536 : //'3ge6' extended presentations (jpeg images)
		case 0x33676736 : //'3gg6' general (not yet suitable; superset)
			metadata_style = THIRD_GEN_PARTNER_VER1_REL6;
			break;

		case 0x33677034 : //'3gp4'                          //3gp assets (the full complement) are available: source clause is S5.5 of TS26.244 (Rel6.4 & later):
		case 0x33677035 : //'3gp5'                          //"that the file conforms to the specification; it includes everything required by,
			metadata_style = THIRD_GEN_PARTNER;               //and nothing contrary to the specification (though there may be other material)"
			break;                                            //it stands to reason that 3gp assets aren't contrary since 'udta' is defined by iso bmffv1

		//
		//other brands that are have compatible brands relating to 3GPP/3GPP2
		//

		case 0x6B646469 : //'kddi'                          //3GPP2 EZmovie (optionally restricted) media; these have a 3GPP2 compatible brand
			metadata_style = THIRD_GEN_PARTNER_VER2;
			break;
		case 0x6D6D7034 : //'mmp4'
			metadata_style = THIRD_GEN_PARTNER;
			break;

		//
		//what IS supported for iTunes-style metadata
		//

		case 0x4D534E56 : //'MSNV'  (PSP) - this isn't actually listed at mp4ra, but since they are popular...
			metadata_style = ITUNES_STYLE;
			psp_brand = true;
			break;
		case 0x4D344120 : //'M4A '  -- these are all listed at http://www.mp4ra.org/filetype.html as registered brands
		case 0x4D344220 : //'M4B '
		case 0x4D345020 : //'M4P '
		case 0x4D345620 : //'M4V '
		case 0x4D345648 : //'MV4H'
		case 0x4D345650 : //'M4VP'
		case 0x6D703432 : //'mp42'
		case 0x6D703431 : //'mp41'
		case 0x69736F6D : //'isom'
		case 0x69736F32 : //'iso2'
		case 0x61766331 : //'avc1'


			metadata_style = ITUNES_STYLE;
			break;

		//
		//other brands that are derivatives of the ISO Base Media File Format
		//
		case 0x6D6A7032 : //'mjp2'
		case 0x6D6A3273 : //'mj2s'
			metadata_style = MOTIONJPEG2000;
			break;

		//other lesser unsupported brands; http://www.mp4ra.org/filetype.html like dv, mp21 & ... whatever mpeg7 brand is
		default :
			fprintf(stdout, "AtomicParsley error: unsupported MPEG-4 file brand found '%s'\n", file_brand);
			exit(2);
			break;
	}
	return;
}

void APar_TestCompatibleBrand(FILE* file, uint64_t atom_start, uint64_t atom_length) {
	if (atom_length <= 16) return;
	uint32_t compatible_brand = 0;

	for (uint32_t brand = 16; brand < atom_length; brand+=4) {
		compatible_brand = APar_read32(twenty_byte_buffer, file, atom_start+brand);
		if (compatible_brand == 0x6D703432 || compatible_brand == 0x69736F32) {
			parsedAtoms[atom_number-1].ancillary_data = compatible_brand;
		}
	}
	return;
}

void APar_Extract_stsd_codec(FILE* file, uint64_t midJump) {
	memset(twenty_byte_buffer, 0, 12);
	APar_readX(twenty_byte_buffer, file, midJump, 12);
	parsedAtoms[atom_number-1].ancillary_data = UInt32FromBigEndian( twenty_byte_buffer +4 );
  return;
}

/*----------------------
APar_LocateDataReference
  fill
----------------------*/
void APar_LocateDataReference(short chunk_offset_idx, FILE* file) {
	uint32_t data_ref_idx = 0;
	short sampletable_atom_idx = 0;
	short minf_atom_idx = 0;
	AtomicInfo *stsd_atom, *dinf_atom, *dref_atom, *target_reference_atom = NULL;

	sampletable_atom_idx = APar_FindParentAtom(chunk_offset_idx, parsedAtoms[chunk_offset_idx].AtomicLevel);
	stsd_atom = APar_FindChildAtom(sampletable_atom_idx, "stsd");
	if (stsd_atom == NULL) {
		return;
	}
	data_ref_idx = APar_read32(twenty_byte_buffer, file, stsd_atom->AtomicStart+28);

	minf_atom_idx = APar_FindParentAtom(sampletable_atom_idx, parsedAtoms[sampletable_atom_idx].AtomicLevel);
	dinf_atom = APar_FindChildAtom(minf_atom_idx, "dinf");
	dref_atom = APar_FindChildAtom(dinf_atom->AtomicNumber, "dref");

	target_reference_atom = APar_FindChildAtom(dref_atom->AtomicNumber, "any", 4, data_ref_idx);

	if (target_reference_atom != NULL) {
		parsedAtoms[chunk_offset_idx].ancillary_data = target_reference_atom->AtomicVerFlags;
	}
	return;
}

void APar_SampleTableIterator(FILE *file) {
	uint8_t total_tracks = 0;
	uint8_t a_track = 0;
	char track_path[36];
	memset(track_path, 0, 36);
	AtomicInfo* samples_parent = NULL;
	AtomicInfo* chunk_offset_atom = NULL;

	APar_FindAtomInTrack(total_tracks, a_track, NULL); //gets the number of tracks
	for (uint8_t trk_idx=1; trk_idx <= total_tracks; trk_idx++) {
		sprintf(track_path, "moov.trak[%hhu].mdia.minf.stbl", trk_idx);
		samples_parent = APar_FindAtom(track_path, false, SIMPLE_ATOM, 0, false);
		if (samples_parent != NULL) {
			chunk_offset_atom = APar_FindChildAtom(samples_parent->AtomicNumber, "stco");
			if (chunk_offset_atom == NULL) chunk_offset_atom = APar_FindChildAtom(samples_parent->AtomicNumber, "co64");
			if (chunk_offset_atom != NULL) {
				APar_LocateDataReference(chunk_offset_atom->AtomicNumber, file);
			}
		}
	}
	return;
}

/*----------------------
APar_MatchToKnownAtom
  atom_name - the name of our newly found atom
  atom_container - the name of the parent container atom
  fromFile - controls the manner of extracting parents (passed thu to another function)

    Using the atom_name of this new atom, search through KnownAtoms, testing that the names match. If they do, move onto a finer grained sieve.
		If the parent can be at any level (like "free"), just let it through; if the parent is "ilst" (iTunes-style metadata), or a uuid, return a generic match
		The final test is the one most atoms will go through. Some atoms can have different parents - up to 5 different parents are allowed by this version of AP
		Iterate through the known parents, and test it against atom_container. If they match, return the properties of the known atom
----------------------*/
int APar_MatchToKnownAtom(const char* atom_name, const char* atom_container, bool fromFile, const char* find_atom_path) {
	uint32_t total_known_atoms = (sizeof(KnownAtoms)/sizeof(*KnownAtoms));
	uint32_t return_known_atom = 0;

	//if this atom is contained by 'ilst', then it is *highly* likely an iTunes-style metadata parent atom
	if ( memcmp(atom_container, "ilst", 4) == 0 && memcmp(atom_name, "uuid", 4) != 0) {
		return_known_atom = total_known_atoms-2; //2nd to last KnowAtoms is a generic placeholder iTunes-parent atom
		//fprintf(stdout, "found iTunes parent %s = atom %s\n", KnownAtoms[return_known_atom].known_atom_name, atom_name);

	//if this atom is "data" get the full path to it; we will take any atom under 'ilst' and consider it an iTunes metadata parent atom
	} else if (memcmp(atom_name, "data", 4) == 0 && find_atom_path != NULL) {
		if (strncmp(find_atom_path, "moov.udta.meta.ilst.", 20) == 0) {
			return_known_atom = total_known_atoms-1; //last KnowAtoms is a generic placeholder iTunes-data atom
			//fprintf(stdout, "found iTunes data child\n");
		}

	} else if (memcmp(atom_name, "data", 4) == 0) {
		char* fullpath = (char *)malloc(sizeof(char) * 200);
		memset(fullpath, 0, sizeof(char) * 200);

		if (fromFile) {
			APar_ProvideAtomPath(parsedAtoms[atom_number-1].AtomicNumber, fullpath, fromFile);
		} else { //find_atom_path only is NULL in APar_ScanAtoms (where fromFile is true) and in APar_CreateSparseAtom, where atom_number was just filled
			APar_ProvideAtomPath(parsedAtoms[atom_number].AtomicNumber, fullpath, fromFile);
		}

		//fprintf(stdout, "APar_ProvideAtomPath gives %s (%s-%s)\n", fullpath, atom_name, atom_container);
		if (strncmp(fullpath, "moov.udta.meta.ilst.", 20) == 0) {
			return_known_atom = total_known_atoms-1; //last KnowAtoms is a generic placeholder iTunes-data atom
			//fprintf(stdout, "found iTunes data child\n");
		}
		free(fullpath);
		fullpath = NULL;

	//if this atom is "esds" get the full path to it; take any atom under 'stsd' as a parent to esds (that parent would be a 4CC codec; not all do have 'esds'...)
	} else if (memcmp(atom_name, "esds", 4) == 0 ) {
		char* fullpath = (char *)malloc(sizeof(char) * 300);
		memset(fullpath, 0, sizeof(char) * 200);

		APar_ProvideAtomPath(parsedAtoms[atom_number-1].AtomicNumber, fullpath, fromFile);

		if (strncmp(fullpath, "moov.trak.mdia.minf.stbl.stsd.", 30) == 0) {
			return_known_atom = total_known_atoms-3; //manually return the esds atom
		}
		free(fullpath);
		fullpath = NULL;


	} else {
	//try matching the name of the atom
		for(uint32_t i = 1; i < total_known_atoms; i++) {
			if (memcmp(atom_name, KnownAtoms[i].known_atom_name, 4) == 0) {
				//name matches, now see if the container atom matches any known container for that atom
				if ( strncmp(KnownAtoms[i].known_parent_atoms[0], "_ANY_LEVEL", 10) == 0 ) {
					return_known_atom = i; //the list starts at 0; the unknown atom is at 0; first known atom (ftyp) is at 1
					break;

				} else {
					uint8_t total_known_containers = (uint8_t)(sizeof(KnownAtoms[i].known_parent_atoms)/sizeof(*KnownAtoms[i].known_parent_atoms)); //always 5
					for (uint8_t iii = 0; iii < total_known_containers; iii++) {
						if (KnownAtoms[i].known_parent_atoms[iii] != NULL) {
							if ( strncmp(atom_container, KnownAtoms[i].known_parent_atoms[iii], strlen(atom_container) ) == 0) { //strlen(atom_container)
								return_known_atom = i; //the list starts at 0; the unknown atom is at 0; first known atom (ftyp) is at 1
								break;
							}
						}
					}
				}
				if (return_known_atom) {
					break;
				}
			}
		}
	}
	if ( return_known_atom > total_known_atoms ) {
		return_known_atom = 0;
	}
	//accommodate any future child to dref; force to being versioned
	if (return_known_atom == 0 && memcmp(atom_container, "dref", 4) == 0) {
		return_known_atom = total_known_atoms-4; //return a generic *VERSIONED* child atom; otherwise an atom without flags will be present & chunk offsets will not update
	}
	return return_known_atom;
}

/*----------------------
APar_Manually_Determine_Parent
  atom_start - the place in the file where the atom begins
  atom_length - length of the eval atom
  container - a string of the last known container atom (or FILE_LEVEL)

    given the next atom (unknown string at this point), run some tests using its starting point and length. Iterate backwards through the already parsed
		atoms, and for each test if it could contain this atom. Tests include if the container starts before ours (which it would to contain the new atom),
		that its length is longer than our length (any parent would need to be longer than us if even by 8 bytes), the start + length sum of the parent atom
		(where it ends), needs to be greater than or equal to where this new atom ends, and finally, that the eval containing atom be some form of parent as
		defined in KnownAtoms
----------------------*/
void APar_Manually_Determine_Parent(uint64_t atom_start, uint64_t atom_length, char* container) {
	short preceding_atom = atom_number-1;
	while (parsedAtoms[preceding_atom].AtomicNumber != 0) {

		if (parsedAtoms[preceding_atom].AtomicStart < atom_start &&
				parsedAtoms[preceding_atom].AtomicLength > atom_length &&
				parsedAtoms[preceding_atom].AtomicStart + parsedAtoms[preceding_atom].AtomicLength >= atom_start + atom_length &&
				parsedAtoms[preceding_atom].AtomicContainerState <= DUAL_STATE_ATOM ) {
			memcpy(container, parsedAtoms[preceding_atom].AtomicName, 5);
			break;

		} else {
			preceding_atom--;
		}
		if (preceding_atom == 0) {
			strcpy(container, "FILE_LEVEL");
		}
	}
}

/*----------------------
APar_ScanAtoms
  path - the complete path to the originating file to be tested
  deepscan_REQ - controls whether we go into 'stsd' or just a superficial scan

if the file has not yet been scanned (this gets called by nearly every cli
option), then open the file and start scanning. Read in the first 12 bytes and
see if bytes 4-8 are 'ftyp' as any modern MPEG-4 file will have 'ftyp' first.
Accommodations are also in place for the jpeg2000 signature, but the sig.  must
be followed by 'ftyp' and have an 'mjp2' or 'mj2s' brand. If it does, start
scanning the rest of the file. An MPEG-4 file is logically organized into
discrete hierarchies called "atoms" or "boxes". Each atom is at minimum 8 bytes
long. Bytes 1-4 make an unsigned 32-bit integer that denotes how long this atom
is (ie: 8 would mean this atom is 8 bytes long).  The next 4 bytes (bytes 5-8)
make the atom name. If the atom presents longer than 8 bytes, then that
supplemental data would be what the atom carries. Atoms are broadly separated
into 2 categories: parents & children (or container & leaf).  Typically, a
parent can hold other atoms, but not data; a child can hold data but not other
atoms. This 'rule' is broken sometimes (the atoms listed as DUAL_STATE_ATOM),
but largely holds.

Each atom is read in as 12 bytes (to accommodate flags & versioning). The atom
name is extracted, and using the last known container (either FILE_LEVEL or an
actual atom name), the new atom's hierarchy is found based on its length &
position. Using its containing atom, the KnownAtoms table is searched to locate
the properties of that atom (parent/child, versioned/simple), and jumping
around in the file is based off that known atom's type. Atoms that fall into a
hybrid category (DUAL_STATE_ATOMs) are explicitly handled. If an atom is listed
has having a language attribute, it is read to support multiple langauges (as
most 3GP assets do).

----------------------*/
void APar_ScanAtoms(const char *path, bool deepscan_REQ) {
	if (!parsedfile) {
		file_size = findFileSize(path);

		FILE *file = APar_OpenFile(path, "rb");
		if (file != NULL) {
			char *data = (char *)calloc(1, 13);
			char *container = (char *)calloc(1, 20);
			memcpy(container, "FILE_LEVEL", 10);
			bool corrupted_data_atom = false;
			bool jpeg2000signature = false;

			uuid_vitals uuid_info = {0};
			uuid_info.binary_uuid=(char*)malloc(sizeof(char)*16 + 1);  //this will hold any potential 16byte uuids
			uuid_info.uuid_AP_atom_name=(char*)malloc(sizeof(char)*5); //this will hold any atom name that is written after the uuid written by AP

			if (data == NULL) return;
			uint64_t dataSize = 0;
			uint64_t jump = 0;

			APar_readX(data, file, 0, 12);
			char *atom = data+4;

			if ( memcmp(data, "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A ", 12) == 0 ) {
				jpeg2000signature = true;
			}

			if ( memcmp(atom, "ftyp", 4) == 0 || jpeg2000signature) {

				dataSize = UInt32FromBigEndian(data);
				jump = dataSize;

				APar_AtomizeFileInfo(0, jump, 0, atom, generalAtomicLevel, CHILD_ATOM, SIMPLE_ATOM, 0, 0 , &uuid_info);

				if (!jpeg2000signature) {
					APar_IdentifyBrand( data + 8 );
					APar_TestCompatibleBrand(file, 0, dataSize);
				}

				fseek(file, jump, SEEK_SET);

				while (jump < file_size) {

					uuid_info.uuid_form = UUID_DEPRECATED_FORM; //start with the assumption that any found atom is in the depracted uuid form

					APar_readX_noseek(data, file, 8);
					char *atom = data+4;
					dataSize = UInt32FromBigEndian(data);

					if (jpeg2000signature) {
						if (memcmp(atom, "ftyp", 4) == 0) {
							APar_readX_noseek(twenty_byte_buffer, file, 4);
							APar_IdentifyBrand(twenty_byte_buffer);
						} else {
							exit(0); //the atom right after the jpeg2000/mjpeg2000 signature is *supposed* to be 'ftyp'
						}
						jpeg2000signature = false;
					}

					if (dataSize > file_size - jump) {
						dataSize = file_size - jump;
					}

					if (dataSize == 0 && (atom[0] == 0 && atom[1] == 0 && atom[2] == 0 && atom[3] == 0) ) {
						gapless_void_padding = file_size-jump; //Apple has decided to add around 2k of NULL space outside of any atom structure starting with iTunes 7.0.0
						break; //its possible this is part of gapless playback - but then why would it come after the 'free' at the end of a file like gpac writes?
					} //after actual tested its elimination, it doesn't seem to be required for gapless playback

					//diagnose damage to 'cprt' by libmp4v2 in 1.4.1 & 1.5.0.1
					//typically, the length of this atom (dataSize) will exceeed it parent (which is reported as 17)
					//true length ot this data will be 9 - impossible for iTunes-style 'data' atom.
					if (memcmp(atom, "data", 4) == 0 && parsedAtoms[ atom_number-1].AtomicContainerState == PARENT_ATOM) {
						if (dataSize > parsedAtoms[ atom_number-1].AtomicLength) {
							dataSize = parsedAtoms[ atom_number-1].AtomicLength -8; //force its length to its true length
							fprintf(stdout, "AtomicParsley warning: the 'data' child of the '%s' atom seems to be corrupted.\n", parsedAtoms[ atom_number-1].AtomicName);
							corrupted_data_atom = true;
						}
					}
					//end diagnosis; APar_Manually_Determine_Parent will still determine it to be a versioned atom (it tests by names), but at file write out,
					//it will write with a length of 9 bytes

					APar_Manually_Determine_Parent(jump, dataSize, container);
					int filtered_known_atom = APar_MatchToKnownAtom(atom, container, true, NULL);

					uint32_t atom_verflags = 0;
					uint16_t atom_language = 0;

					if (memcmp(atom, "uuid", 4) == 0) {
						memset(uuid_info.binary_uuid, 0, 17);

						APar_readX(uuid_info.binary_uuid, file, jump+8, 16);

						if (UInt32FromBigEndian(uuid_info.binary_uuid+8) == 0) { //the deperacted uuid form
							memcpy(atom, uuid_info.binary_uuid, 4);
							atom_verflags = APar_read32(uuid_info.binary_uuid, file, jump+12);
							if (atom_verflags > AtomFlags_Data_UInt) {
								atom_verflags = 0;
							}
						} else {
							uint8_t uuid_version = APar_extract_uuid_version(NULL, uuid_info.binary_uuid);
							APar_endian_uuid_bin_str_conversion(uuid_info.binary_uuid);
							if (uuid_version == 5) {
								uuid_info.uuid_form = UUID_SHA1_NAMESPACE;
								//read in what AP would set the atom name to. The new uuid form is:
								// 4bytes atom length, 4 bytes 'uuid', 16bytes uuidv5, 4bytes name of uuid in AP namespace, 4bytes versioning, 4bytes NULL, Xbytes data
								APar_readX(uuid_info.uuid_AP_atom_name, file, jump+24, 4);

								char uuid_of_foundname_in_AP_namesapce[20];
								APar_generate_uuid_from_atomname(uuid_info.uuid_AP_atom_name, uuid_of_foundname_in_AP_namesapce);
								if (memcmp(uuid_info.binary_uuid, uuid_of_foundname_in_AP_namesapce, 16) == 0) {
									uuid_info.uuid_form = UUID_AP_SHA1_NAMESPACE; //our own uuid ver5 atoms in the AtomicParsley.sf.net namespace
									atom_verflags = APar_read32(twenty_byte_buffer, file, jump+28);
								}
							} else {
								uuid_info.uuid_form = UUID_OTHER;
							}
						}
					}

					if (KnownAtoms[filtered_known_atom].box_type == VERSIONED_ATOM && !corrupted_data_atom) {
						atom_verflags = APar_read32(twenty_byte_buffer, file, jump+8);
					}

					if (KnownAtoms[filtered_known_atom].box_type == PACKED_LANG_ATOM) {
						//the problem with storing the language is that the uint16_t 2 bytes that carry the actual language are in different places on different atoms
						//some atoms have it right after the atom version/flags; some like rating/classification have it 8 bytes later; yrrc doesn't have it at all
						char bitpacked_lang[4];
						memset(bitpacked_lang, 0, 4);

						uint32_t userdata_box = UInt32FromBigEndian(atom);

						switch (userdata_box) {
							case 0x7469746C : //'titl'
							case 0x64736370 : //'dscp'
							case 0x63707274 : //'cprt'
							case 0x70657266 : //'perf'
							case 0x61757468 : //'auth'
							case 0x676E7265 : //'gnre'
							case 0x616C626D : //'albm'
							case 0x6B797764 : //'kywd'
							case 0x6C6F6369 : //'loci'
							case 0x49443332 : //'ID32' ; technically not a 'user data box', but this only extracts the packed language (which ID32 does have)
							{
								atom_language = APar_read16(bitpacked_lang, file, jump + 12);
								break;
							}
							case 0x636C7366 : //'clsf'
							{
								atom_language = APar_read16(bitpacked_lang, file, jump + 18);
								break;
							}
							case 0x72746E67 : //'rtng'
							{
								atom_language = APar_read16(bitpacked_lang, file, jump + 20);
								break;
							}
							//case 0x79727263 : //'yrrc' is the only 3gp tag that doesn't support multiple languages; won't even get here because != PACKED_LANG_ATOM
							default :
							{
								break; //which means that any new/unknown packed language atoms will have their language of 0; AP will only support 1 of this atom name then
							}
						}
					}

					//mdat.length=1; and ONLY supported for mdat atoms - no idea if the spec says "only mdat", but that's what I'm doing for now
					if ( (strncmp(atom, "mdat", 4) == 0) && (generalAtomicLevel == 1) && (dataSize == 1) ) {

						uint64_t extended_dataSize = APar_read64(
											twenty_byte_buffer, file, jump+8);
						APar_AtomizeFileInfo(jump, 1, extended_dataSize, atom, generalAtomicLevel, KnownAtoms[filtered_known_atom].container_state,
                                  KnownAtoms[filtered_known_atom].box_type, atom_verflags, atom_language, &uuid_info );

					} else {
						APar_AtomizeFileInfo(jump, dataSize, 0, atom, generalAtomicLevel, KnownAtoms[filtered_known_atom].container_state,
                                  corrupted_data_atom ? SIMPLE_ATOM : KnownAtoms[filtered_known_atom].box_type, atom_verflags, atom_language, &uuid_info );
					}
					corrupted_data_atom = false;

					//read in the name of an iTunes-style internal reverseDNS directly into parsedAtoms
					if (memcmp(atom, "mean", 4) == 0 && memcmp(parsedAtoms[atom_number-2].AtomicName, "----", 4) == 0) {
							parsedAtoms[atom_number-1].ReverseDNSdomain = (char *)calloc(1, sizeof(char) * dataSize);

							//jump + 12 because 'name' atom is the 2nd child
							APar_readX(parsedAtoms[atom_number-1].ReverseDNSdomain, file, jump + 12, dataSize - 12);
					}
					if (memcmp(atom, "name", 4) == 0 &&
					    memcmp(parsedAtoms[atom_number-2].AtomicName, "mean", 4) == 0 &&
							memcmp(parsedAtoms[atom_number-3].AtomicName, "----", 4) == 0) {

							parsedAtoms[atom_number-1].ReverseDNSname = (char *)calloc(1, sizeof(char) * dataSize);

							//jump + 12 because 'name' atom is the 2nd child
							APar_readX(parsedAtoms[atom_number-1].ReverseDNSname, file, jump + 12, dataSize - 12);
					}

					if (dataSize == 0) { // length = 0 means it reaches to EOF
						break;
					}

					switch (KnownAtoms[filtered_known_atom].container_state) {
						case PARENT_ATOM : {
							jump += 8;
							break;
						}
						case CHILD_ATOM : {
							if (memcmp(atom, "hdlr", 4) == 0) {
								APar_readX(twenty_byte_buffer, file, jump+16, 4);
								parsedAtoms[atom_number-1].ancillary_data = UInt32FromBigEndian(twenty_byte_buffer);
							}

							if ((generalAtomicLevel == 1) && (dataSize == 1)) { //mdat.length =1 64-bit length that is more of a cludge.
								jump += parsedAtoms[atom_number-1].AtomicLengthExtended;
							} else {
								jump += dataSize;
							}
							break;
						}
						case DUAL_STATE_ATOM : {
							if (memcmp(atom, "meta", 4) == 0) {
								jump += 12;

							} else if (memcmp(atom, "dref", 4) == 0) {
								jump += 16;

							} else if (memcmp(atom, "iinf", 4) == 0) {
								jump += 14;

							} else if (memcmp(atom, "stsd", 4) == 0) {
								if (deepscan_REQ) {
									//for a tree ONLY, we go all the way, parsing everything; for any other option, we leave this atom as a monolithic entity
									jump += 16;
								} else {
									APar_Extract_stsd_codec(file, jump+16); //just get the codec used for this track
									jump += dataSize;
								}

							} else if (memcmp(atom, "schi", 4) == 0) {
								if (memcmp(container, "sinf", 4) == 0) { //seems for iTMS drm files, schi is a simple parent atom, and 'user' comes right after it
									jump += 8;
								} else {
									 jump += dataSize; //no idea what it would be under srpp, so just skip over it
								}

							} else if (memcmp(container, "stsd", 4) == 0) {
								//each one is different, so list its size manually
								//the beauty of this is that even if there is an error here or a new codec shows up, it only affects SHOWING the tree.
								//Getting a tree for display ONLY purposes is different from when setting a tag - display ONLY goes into stsd; tagging makes 'stsd' monolithic.
								//so setting metadata on unknown or improperly enumerated codecs (which might have different lengths) don't affect tagging.
								uint32_t named_atom = UInt32FromBigEndian(atom);
								switch(named_atom) {
									case 0x6D703473 : { //mp4s
										jump+= 16;
										break;
									}
									case 0x73727470 :		//srtp
									case 0x72747020 : { //'rtp '
										jump+= 24;
										break;
									}
									case 0x616C6163 :		//alac
									case 0x6D703461 :		//mp4a
									case 0x73616D72 :		//samr
									case 0x73617762 :		//sawb
									case 0x73617770 :		//sawp
									case 0x73657663 :		//sevc
									case 0x73716370 :		//sqcp
									case 0x73736D76 :		//ssmv
									case 0x64726D73 : { //drms
										jump+= 36;
										break;
									}
									case 0x74783367  : { //tx3g
										jump+= 46;
										break;
									}
									case 0x6D6A7032 :		//mjp2
									case 0x6D703476 :		//mp4v
									case 0x61766331 :		//avc1
									case 0x6A706567 :		//jpeg
									case 0x73323633 :		//s263
									case 0x64726D69 : { //drmi
										jump+= 86;
										break;
									}
									default :	{					//anything else that isn't covered here will just jump past any child atoms (avcp, text, enc*)
										jump += dataSize;
									}
								}
							}
							break;
						}
						case UNKNOWN_ATOM_TYPE : {
							short parent_atom = APar_FindParentAtom(atom_number-1, generalAtomicLevel);
							//to accommodate the retarted utility that keeps putting in 'prjp' atoms in mpeg-4 files written QTstyle
							if (parsedAtoms[parent_atom].AtomicContainerState == DUAL_STATE_ATOM) {
								jump = parsedAtoms[parent_atom].AtomicStart + parsedAtoms[parent_atom].AtomicLength;
							} else {
								jump += dataSize;
							}
							break;
						}
					} //end swtich

					generalAtomicLevel = APar_GetCurrentAtomDepth(jump, dataSize);

					if ( (jump > 8 ? jump : 8) >= file_size) { //prevents jumping past EOF for the smallest of atoms
						break;
					}

					fseeko(file, jump, SEEK_SET);
				}

			} else {
				fprintf(stderr, "\nAtomicParsley error: bad mpeg4 file (ftyp atom missing or alignment error).\n\n");
				data = NULL;
				exit(1); //return;
			}
			APar_SampleTableIterator(file);

			free(data);
			data=NULL;
			free(container);
			container=NULL;
			free(uuid_info.binary_uuid);
			free(uuid_info.uuid_AP_atom_name);
			fclose(file);
		}
		if (brand == 0x69736F6D) { //'isom' test for amc files & its (?always present?) uuid 0x63706764A88C11D48197009027087703
			char EZ_movie_uuid[100];
			memset(EZ_movie_uuid, 0, sizeof(EZ_movie_uuid));
			memcpy(EZ_movie_uuid, "uuid=\x63\x70\x67\x64\xA8\x8C\x11\xD4\x81\x97\x00\x90\x27\x08\x77\x03", 21); //this is in an endian form, so it needs to be converted
			APar_endian_uuid_bin_str_conversion(EZ_movie_uuid+5);
			if ( APar_FindAtom(EZ_movie_uuid, false, EXTENDED_ATOM, 0, true) != NULL) {
				metadata_style = UNDEFINED_STYLE;
			}
		}
		parsedfile = true;
	}
	if (!deep_atom_scan && !parsedfile && APar_FindAtom("moov", false, SIMPLE_ATOM, 0) == NULL) {
		fprintf(stderr, "\nAtomicParsley error: bad mpeg4 file (no 'moov' atom).\n\n");
		exit(1);
	}
	return;
}

///////////////////////////////////////////////////////////////////////////////////////
//                            mod time functions                                     //
///////////////////////////////////////////////////////////////////////////////////////

void APar_FlagMovieHeader() {
	if (movie_header_atom == NULL) movie_header_atom = APar_FindAtom("moov.mvhd", false, VERSIONED_ATOM, 0);
	if (movie_header_atom == NULL) return;
	if (movie_header_atom != NULL) movie_header_atom->ancillary_data = 0x666C6167;
	return;
}

void APar_FlagTrackHeader(AtomicInfo* thisAtom) {
	AtomicInfo* trak_atom = NULL;
	AtomicInfo* track_header_atom = NULL;
	short current_atom_idx = thisAtom->AtomicNumber;
	short current_level = thisAtom->AtomicLevel;

	if (thisAtom->AtomicLevel >= 3) {
		while (true) {
			short parent_atom = APar_FindParentAtom(current_atom_idx, current_level);
			current_atom_idx = parent_atom;
			current_level = parsedAtoms[parent_atom].AtomicLevel;

			if (current_level == 2) {
				if (memcmp(parsedAtoms[parent_atom].AtomicName, "trak", 4) == 0) {
					trak_atom = &parsedAtoms[parent_atom];
				}
				break;
			} else if (current_level == 1) {
				break;
			}
		}
		if (trak_atom != NULL) {
			track_header_atom = APar_FindChildAtom(trak_atom->AtomicNumber, "tkhd");
			if (track_header_atom != NULL) {
				track_header_atom->ancillary_data = 0x666C6167;
			}
		}
	}
	APar_FlagMovieHeader();
	return;
}

///////////////////////////////////////////////////////////////////////////////////////
//                          Atom Removal Functions                                   //
///////////////////////////////////////////////////////////////////////////////////////

/*----------------------
APar_EliminateAtom
  this_atom_number - the index into parsedAtoms[] of the atom to be erased
	resume_atom_number - the point in parsedAtoms[] where the tree will be picked up

    This manually removes the atoms from being used. The atom is still in parsedAtoms[] at the same location it was previously, but because parsedAtoms is used
		as a linked list & followed by NextAtomNumber, effectively, this atom (and atoms leading to resume_atom_number) are no longer considered part of the tree.
----------------------*/
void APar_EliminateAtom(short this_atom_number, int resume_atom_number) {
	if ( this_atom_number > 0 && this_atom_number < atom_number && resume_atom_number >= 0 && resume_atom_number < atom_number) {
		APar_FlagTrackHeader(&parsedAtoms[this_atom_number]);

		short preceding_atom_pos = APar_FindPrecedingAtom(this_atom_number);
		if ( APar_Eval_ChunkOffsetImpact(this_atom_number) ) {
			removed_bytes_tally+=parsedAtoms[this_atom_number].AtomicLength; //used in validation routine
		}
		parsedAtoms[preceding_atom_pos].NextAtomNumber = resume_atom_number;

		memset(parsedAtoms[this_atom_number].AtomicName, 0, 4); //blank out the name of the parent atom name
		parsedAtoms[this_atom_number].AtomicNumber = -1;
		parsedAtoms[this_atom_number].NextAtomNumber = -1;
	}
	return;
}

/*----------------------
APar_RemoveAtom
  atom_path - the "peri.od_d.elim.inat.ed__.atom.path" string that represents the target atom
	atom_type - the type of atom to be eliminated (packed language, extended...) of the target atom
	UD_lang - the language code for a packed language atom (ignored for non-packed language atoms)
	rDNS_domain - the reverse DNS domain (com.foo.thing) of the atom (ignored for non reverse DNS '----' atoms)

    APar_RemoveAtom tries to find the atom in the string. If it exists, then depending on its atom_type, it or its last child will get passed along for elimination.
		TODO: the last child part could use some more intelligence at some point; its relatively hardcoded.
----------------------*/
void APar_RemoveAtom(const char* atom_path, uint8_t atom_type, uint16_t UD_lang, const char* rDNS_domain) {

	AtomicInfo* desiredAtom = APar_FindAtom(atom_path, false, atom_type, UD_lang, (atom_type == EXTENDED_ATOM ? true : false), rDNS_domain );

	if (desiredAtom == NULL) return; //the atom didn't exist or wasn't found
	if (desiredAtom->AtomicNumber == 0) return; //we got the default atom, ftyp - and since that can't be removed, it must not exist (or it was missed)

	modified_atoms = true;
	if (atom_type != EXTENDED_ATOM) {
		if (atom_type == PACKED_LANG_ATOM || desiredAtom->AtomicClassification == UNKNOWN_ATOM) {
			APar_EliminateAtom(desiredAtom->AtomicNumber, desiredAtom->NextAtomNumber);

		//reverseDNS atom
		} else if (desiredAtom->ReverseDNSname != NULL) {
			short parent_atom = APar_FindParentAtom(desiredAtom->AtomicNumber, desiredAtom->AtomicLevel);
			short last_elim_atom = APar_FindLastChild_of_ParentAtom(parent_atom);
			APar_EliminateAtom( parent_atom, parsedAtoms[last_elim_atom].NextAtomNumber );

		} else if (memcmp(desiredAtom->AtomicName, "data", 4) == 0 && desiredAtom->AtomicLevel == 6){
			short parent_atom = APar_FindParentAtom(desiredAtom->AtomicNumber, desiredAtom->AtomicLevel);
			short last_elim_atom = APar_FindLastChild_of_ParentAtom(parent_atom);
			APar_EliminateAtom( parent_atom, parsedAtoms[last_elim_atom].NextAtomNumber );

		} else if (desiredAtom->AtomicContainerState <= DUAL_STATE_ATOM) {
			short last_elim_atom = APar_FindLastChild_of_ParentAtom(desiredAtom->AtomicNumber);
			APar_EliminateAtom( desiredAtom->AtomicNumber, parsedAtoms[last_elim_atom].NextAtomNumber );

		} else if (UD_lang == 1) { //yrrc
			APar_EliminateAtom(desiredAtom->AtomicNumber, desiredAtom->NextAtomNumber);

		} else {
			short last_elim_atom = APar_FindLastChild_of_ParentAtom(desiredAtom->AtomicNumber);
			APar_EliminateAtom(desiredAtom->AtomicNumber, last_elim_atom );
		}

	//this will only work for AtomicParsley created uuid atoms that don't have children, but since all uuid atoms are treaded as non-parent atoms... no problems
	} else if (atom_type == EXTENDED_ATOM) {
		APar_EliminateAtom(desiredAtom->AtomicNumber, desiredAtom->NextAtomNumber);
	}
	return;
}

/*----------------------
APar_freefree
  purge_level - an integer ranging from -1 to some positive number of the level a 'free' atom must be on for it to be erased.

    Some tagging utilities (things based on libmp4v2 & faac irrespective of which tagging system used) have a dirty little secret. They way the work is to copy the
		'moov' atom - in its entirety - to the end of the file, and make the changes there. The original 'moov' file is nulled out, but the file only increases in size.
		Even if you eliminate the tag, the file grows. Only when the 'moov' atom is last do these taggers work efficiently - and they are blazingly fast, no doubt about
		that - but they are incredibly wasteful. It is possible to switch between using AP and mp4tags and build a file with dozens of megabytes wasted just be changing
		a single letter.
		This function can be used to iterate through the atoms in the file, and selectively eliminate 'free' atoms. Pass a -1 and every last 'free' atom that exists will
		be eliminated (but another will appear later as padding - to completely eliminate any resulting 'free' atoms, the environmental variable "AP_PADDING" needs to be
		set with MID_PAD to 0). Pass a 0, and all 'free' atoms preceding 'moov' or after 'mdat' (including gpac's pesky "Place Your Ad Here" free-at-the-end) will be
		removed. A value of >= 1 will eliminate 'free' atoms between those levels and level 1 (or file level).
----------------------*/
void APar_freefree(int purge_level) {
	modified_atoms = true;
	short eval_atom = 0;
	short prev_atom = 0;
	short moov_atom = 0; //a moov atom has yet to be seen
	short mdat_atom = 0; //any ol' mdat

	if (purge_level == -1) {
		complete_free_space_erasure = true; //prevent any in situ dynamic updating when trying to remove all free atoms. Also triggers a more efficient means of forcing padding
	}

	while (true) {
		prev_atom = eval_atom;
		eval_atom = parsedAtoms[eval_atom].NextAtomNumber;
		if (eval_atom == 0) { //we've hit the last atom
			break;
		}

		if ( memcmp(parsedAtoms[eval_atom].AtomicName, "free", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "skip", 4) == 0 ) {
			//fprintf(stdout, "i am of size %u purge level %i (%u) -> %i\n", parsedAtoms[eval_atom].AtomicLength, purge_level, parsedAtoms[eval_atom].AtomicLevel, eval_atom);
			if ( purge_level == -1 || purge_level >= parsedAtoms[eval_atom].AtomicLevel ||
			     (purge_level == 0 && parsedAtoms[eval_atom].AtomicLevel == 1 && (moov_atom == 0 || mdat_atom != 0)) ) {
				short prev_atom = APar_FindPrecedingAtom(eval_atom);
				if (parsedAtoms[eval_atom].NextAtomNumber == 0) { //we've hit the last atom
					APar_EliminateAtom(eval_atom, parsedAtoms[eval_atom].NextAtomNumber);
					parsedAtoms[prev_atom].NextAtomNumber = 0;
				} else {
					APar_EliminateAtom(eval_atom, parsedAtoms[eval_atom].NextAtomNumber);
				}
				eval_atom = prev_atom; //go back to the previous atom and continue the search
			}
		}
		if ( memcmp(parsedAtoms[eval_atom].AtomicName, "moov", 4) == 0 ) {
			moov_atom = eval_atom;
		}
		if ( memcmp(parsedAtoms[eval_atom].AtomicName, "mdat", 4) == 0 ) {
			mdat_atom = eval_atom;
		}
	}
	return;
}

///////////////////////////////////////////////////////////////////////////////////////
//                          Atom Moving Functions                                    //
///////////////////////////////////////////////////////////////////////////////////////

/*----------------------
APar_MoveAtom
  this_atom_number - the atom that will follow the new_position atom
  new_position - the atom that the atoms at that level will precede, followed by this_atom_number (including its hierarchy of child atoms)

    first find which atoms lead to both atoms (precedingAtom flows to this_atom_number, lastStationaryAtom flows to new_position). Depending on whether there
		are children, find the last atom in the atoms that will be moving. Shunt as required; reordering occurs by following NextAtomNumber (linked list)
----------------------*/
void APar_MoveAtom(short this_atom_number, short new_position) {
	short precedingAtom = 0;
	short lastStationaryAtom = 0;
	short iter = 0;

	//look for the preceding atom (either directly before of the same level, or moov's last nth level child
	while (parsedAtoms[iter].NextAtomNumber != 0) {
		if (parsedAtoms[iter].NextAtomNumber == this_atom_number) {
			precedingAtom = iter;
			break;
		} else {
			if (parsedAtoms[iter].NextAtomNumber == 0) { //we found the last atom (which we end our search on)
				break;
			}
		}
		iter=parsedAtoms[iter].NextAtomNumber;
	}

	iter = 0;

	//search where to insert our new atom
	while (parsedAtoms[iter].NextAtomNumber != 0) {
		if (parsedAtoms[iter].NextAtomNumber == new_position) {
			lastStationaryAtom = iter;
			break;
		}
		iter=parsedAtoms[iter].NextAtomNumber;
		if (parsedAtoms[iter].NextAtomNumber == 0) { //we found the last atom
				lastStationaryAtom = iter;
				break;
		}

	}
	//fprintf(stdout, "%s preceded by %s, last would be %s\n", parsedAtoms[this_atom_number].AtomicName, parsedAtoms[precedingAtom].AtomicName, parsedAtoms[lastStationaryAtom].AtomicName);

	if (parsedAtoms[this_atom_number].AtomicContainerState <= DUAL_STATE_ATOM) {
		if (parsedAtoms[new_position].AtomicContainerState <= DUAL_STATE_ATOM) {
			short last_SwapChild = APar_FindLastChild_of_ParentAtom(this_atom_number);
			short last_WiredChild = APar_FindLastChild_of_ParentAtom(new_position);
			//fprintf(stdout, "moving %s, last child atom %s\n", parsedAtoms[this_atom_number].AtomicName, parsedAtoms[last_SwapChild].AtomicName);
			//fprintf(stdout, "wired %s, last child atom %s\n", parsedAtoms[new_position].AtomicName, parsedAtoms[last_WiredChild].AtomicName);
			//fprintf(stdout, "stationary atom %s , preceding atom %s\n", parsedAtoms[lastStationaryAtom].AtomicName, parsedAtoms[precedingAtom].AtomicName);

			short swap_resume = parsedAtoms[last_SwapChild].NextAtomNumber;
			short wired_resume = parsedAtoms[last_WiredChild].NextAtomNumber;

			parsedAtoms[precedingAtom].NextAtomNumber = swap_resume;         //shunt the main tree (over the [this_atom_number] atom to be move) to other tween atoms,
			parsedAtoms[lastStationaryAtom].NextAtomNumber = new_position;   //pick up with the 2nd to last hierarchy
			parsedAtoms[last_WiredChild].NextAtomNumber = this_atom_number;  //and route the 2nd to last hierarchy to wrap around to the this_atom_number atom
			parsedAtoms[last_SwapChild].NextAtomNumber = wired_resume;       //and continue with whatever was after the [new_position] atom


		} else {
			short last_child = APar_FindLastChild_of_ParentAtom(this_atom_number);
			parsedAtoms[lastStationaryAtom].NextAtomNumber = this_atom_number;
			parsedAtoms[precedingAtom].NextAtomNumber = parsedAtoms[last_child].NextAtomNumber;
			parsedAtoms[last_child].NextAtomNumber = new_position;
		}

	} else {
		parsedAtoms[lastStationaryAtom].NextAtomNumber = this_atom_number;
		parsedAtoms[precedingAtom].NextAtomNumber = parsedAtoms[this_atom_number].NextAtomNumber;
		parsedAtoms[this_atom_number].NextAtomNumber = new_position;
	}

	return;
}

///////////////////////////////////////////////////////////////////////////////////////
//                          Atom Creation Functions                                  //
///////////////////////////////////////////////////////////////////////////////////////

/*----------------------
APar_InterjectNewAtom
  atom_name - the 4 character name of the atom
	cntr_state - the type of container it will be (child, parent, dual state)
	atom_class - the atom type it will be (simple, versioned, uuid, versioned with packed language)
	atom_length - the forced length of this atom (undefined beyond intrinsic length of the container type except for 'free' atoms)
	atom_verflags - the 1 byte atom version & 3 bytes atom flags for the atom if versioned
	packed_lang - the 2 byte packed language for the atom if versioned with packed language type
	atom_level - the level of this atom (1 denotes file level, anything else denotes a child of the last preceding parent or dual state atom)
	preceding_atom - the atom that precedes this newly created interjected atom

    Creates a single new atom (carrying NULLed data) inserted after preceding_atom
----------------------*/
short APar_InterjectNewAtom(const char* atom_name, uint8_t cntr_state,
	uint8_t atom_class, uint64_t atom_length,
	uint32_t atom_verflags, uint16_t packed_lang, uint8_t atom_level,
	short preceding_atom)
{

	if (deep_atom_scan && !modified_atoms) {
		return 0;
	}

	if (atom_number >= MAX_ATOMS) {
		fprintf(stderr, "too many atoms\n");
		abort();
	}

	AtomicInfo* new_atom = &parsedAtoms[atom_number];
	new_atom->AtomicNumber = atom_number;
	new_atom->AtomicName = (char*)malloc(sizeof(char)*6);
	memset(new_atom->AtomicName, 0, sizeof(char)*6);
	memcpy(new_atom->AtomicName, atom_name, 4);

	new_atom->AtomicContainerState = cntr_state;
	new_atom->AtomicClassification = atom_class;
	new_atom->AtomicVerFlags = atom_verflags;
	new_atom->AtomicLevel = atom_level;
	new_atom->AtomicLength = atom_length;
	new_atom->AtomicLanguage = packed_lang;

	new_atom->AtomicData = (char*)calloc(1, sizeof(char)* (atom_length > 16 ? atom_length : 16) ); //puts a hard limit on the length of strings (the spec doesn't)

	new_atom->ID32_TagInfo = NULL;

	new_atom->NextAtomNumber = parsedAtoms[ preceding_atom ].NextAtomNumber;
	parsedAtoms[ preceding_atom ].NextAtomNumber = atom_number;

	atom_number++;
	return new_atom->AtomicNumber;
}

/*----------------------
APar_CreateSparseAtom

	surrogate_atom - an skeletal template of the atom to be created; currently name, level, lang, (if uuid/extended: container & class) are copied; other
	                 stats should be filled in routines that called for their creation and know things like flags & if is to carry an data (this doesn't malloc)
	parent_atom - the stats for the parent atom (used to match through the KnownAtoms array and get things like container & class (for most atoms)
	preceding_atom - the new atom will follow this atom

    Create a single new atom (not carrying any data) copied from a template to follow preceding_atom
----------------------*/
AtomicInfo* APar_CreateSparseAtom(AtomicInfo* surrogate_atom, AtomicInfo* parent_atom, short preceding_atom) {

	if (atom_number >= MAX_ATOMS) {
		fprintf(stderr, "too many atoms\n");
		abort();
	}
	AtomicInfo* new_atom = &parsedAtoms[atom_number];
	new_atom->AtomicNumber = atom_number;
	new_atom->AtomicStart = 0;
	int known_atom = 0;

	new_atom->AtomicName = (char*)malloc(sizeof(char)*20);
	memset(new_atom->AtomicName, 0, sizeof(char)*20);
	size_t copy_bytes_len = (surrogate_atom->AtomicClassification == EXTENDED_ATOM ? 16 : 4);
	memcpy(new_atom->AtomicName, surrogate_atom->AtomicName, copy_bytes_len);
	new_atom->AtomicLevel = surrogate_atom->AtomicLevel;
	new_atom->AtomicLanguage = surrogate_atom->AtomicLanguage;

	//this is almost assuredly wrong for everything except a simple parent atom & needs to be handled properly afterwards; Note the use of 'Sparse'
	new_atom->AtomicVerFlags = 0;
	new_atom->AtomicLength = 8;

	new_atom->NextAtomNumber = parsedAtoms[ preceding_atom ].NextAtomNumber;
	parsedAtoms[ preceding_atom ].NextAtomNumber = atom_number;

	//if 'uuid' atom, copy the info directly, otherwise use KnownAtoms to get the info
	if (surrogate_atom->AtomicClassification == EXTENDED_ATOM) {
		new_atom->AtomicContainerState = CHILD_ATOM;
		new_atom->AtomicClassification = surrogate_atom->AtomicClassification;

		new_atom->uuid_style = UUID_AP_SHA1_NAMESPACE;

	} else {
		//determine the type of atom from our array of KnownAtoms; this is a worst case scenario; it should be handled properly afterwards
		//make sure the level & the atom gets integrated into NextAtomNumber before APar_MatchToKnownAtom because getting the fullpath will rely on that
		known_atom = APar_MatchToKnownAtom(surrogate_atom->AtomicName, (parent_atom == NULL ? "FILE_LEVEL" : parent_atom->AtomicName), false, NULL);

		new_atom->AtomicContainerState = KnownAtoms[known_atom].container_state;
		new_atom->AtomicClassification = KnownAtoms[known_atom].box_type;
	}
	new_atom->ID32_TagInfo = NULL;

	atom_number++;

	return new_atom;
}

/*----------------------
APar_Unified_atom_Put
  target_atom - pointer to the structure describing the atom we are setting
  unicode_data - a pointer to a string (possibly utf8 already); may go onto conversion to utf16 prior to the put
  text_tag_style - flag to denote that unicode_data is to become utf-16, or stay the flavors of utf8 (iTunes style, 3gp style...)
  ancillary_data - a (possibly cast) 32-bit number of any type of supplemental data to be set
	anc_bit_width - controls the number of bytes to set for ancillary data [0 to skip, 8 (1byte) - 32 (4 bytes)]

    take any variety of data & tack it onto the malloced AtomicData at the next available spot (determined by its length)
    priority is given to the numerical ancillary_data so that language can be set prior to setting whatever unicode data. Finally, advance
	  the length of the atom so that we can tack onto the end repeated times (up to the max malloced amount - which isn't checked [blush])
		if unicode_data is NULL itself, then only ancillary_data will be set - which is endian safe cuz o' bitshifting (or set 1 byte at a time)

		works on iTunes-style & 3GP asset style but NOT binary safe (use APar_atom_Binary_Put)
		TODO: work past the max malloced amount onto a new larger array
----------------------*/
void APar_Unified_atom_Put(AtomicInfo* target_atom, const char* unicode_data,
	uint8_t text_tag_style, uint64_t ancillary_data, uint8_t anc_bit_width)
{
	uint64_t atom_data_pos = 0;
	if (target_atom == NULL) {
		return;
	}
	if (target_atom->AtomicClassification == EXTENDED_ATOM) {
		if (target_atom->uuid_style == UUID_SHA1_NAMESPACE) atom_data_pos = target_atom->AtomicLength - 32;
		else if (target_atom->uuid_style == UUID_OTHER) atom_data_pos = target_atom->AtomicLength - 24;
	} else {
		atom_data_pos = target_atom->AtomicLength - 12;
	}

	switch (anc_bit_width) {
		case 0 : { //aye, 'twas a false alarm; arg (I'm a pirate), we just wanted to set a text string
			break;
		}

		case 8 : { //compilation, podcast flag, advisory
			target_atom->AtomicData[atom_data_pos] = (uint8_t)ancillary_data;
			target_atom->AtomicLength++;
			atom_data_pos++;
			break;
		}

		case 16 : { //lang & its ilk
			target_atom->AtomicData[atom_data_pos] = (ancillary_data & 0xff00) >> 8;
			target_atom->AtomicData[atom_data_pos + 1] = (ancillary_data & 0xff) << 0;
			target_atom->AtomicLength+= 2;
			atom_data_pos+=2;
			break;
		}

		case 32 : { //things like coordinates and.... stuff (ah, the prose)
			target_atom->AtomicData[atom_data_pos] = (ancillary_data & 0xff000000) >> 24;
			target_atom->AtomicData[atom_data_pos + 1] = (ancillary_data & 0xff0000) >> 16;
			target_atom->AtomicData[atom_data_pos + 2] = (ancillary_data & 0xff00) >> 8;
			target_atom->AtomicData[atom_data_pos + 3] = (ancillary_data & 0xff) << 0;
			target_atom->AtomicLength+= 4;
			atom_data_pos+=4;
			break;
		}

		default : {
			break;
		}
	}

	if (unicode_data != NULL) {
		if (text_tag_style == UTF16_3GP_Style) {
			uint32_t string_length = strlen(unicode_data) + 1;
			uint32_t glyphs_req_bytes = mbstowcs(NULL, unicode_data, string_length) * 2; //passing NULL pre-calculates the size of wchar_t needed;

			unsigned char* utf16_conversion = (unsigned char*)calloc(1, sizeof(unsigned char)* string_length * 2 );

			UTF8ToUTF16BE(utf16_conversion, glyphs_req_bytes, (unsigned char*)unicode_data, string_length);

			target_atom->AtomicData[atom_data_pos] = (char)(0xFE); //BOM
			target_atom->AtomicData[atom_data_pos+1] = (char)(0xFF); //BOM
			atom_data_pos +=2; //BOM

			/* copy the string directly onto AtomicData at the address of the start of AtomicData + the current length in atom_data_pos */
			/* in marked contrast to iTunes-style metadata where a string is a single string, 3gp tags like keyword & classification are more complex */
			/* directly putting the text into memory and being able to tack on more becomes a necessary accommodation */
			memcpy(target_atom->AtomicData + atom_data_pos, utf16_conversion, glyphs_req_bytes );
			target_atom->AtomicLength += glyphs_req_bytes;

			//double check terminating NULL (don't want to double add them - blush.... or have them missing - blushing on the.... other side)
			if (target_atom->AtomicData[atom_data_pos + (glyphs_req_bytes -1)] + target_atom->AtomicData[atom_data_pos + glyphs_req_bytes] != 0) {
				target_atom->AtomicLength += 4; //+4 because add 2 bytes for the character we just found + 2bytes for the req. NULL
			}
			free(utf16_conversion); utf16_conversion = NULL;

		} else if (text_tag_style == UTF8_iTunesStyle_Binary) { //because this will be 'binary' data (a misnomer for purl & egid), memcpy 4 bytes into AtomicData, not at the start of it
			uint32_t binary_bytes = strlen(unicode_data);
			memcpy(target_atom->AtomicData + atom_data_pos, unicode_data, binary_bytes + 1 );
			target_atom->AtomicLength += binary_bytes;

		} else {
			uint32_t total_bytes = 0;

			if (text_tag_style == UTF8_3GP_Style) {
				total_bytes = strlen(unicode_data);
				total_bytes++;  //include the terminating NULL

			} else if (text_tag_style == UTF8_iTunesStyle_256glyphLimited) {

				uint32_t raw_bytes = strlen(unicode_data);
				total_bytes = utf8_length(unicode_data, 256); //counts the number of characters, not bytes

				if (raw_bytes > total_bytes && total_bytes > 255) {

					fprintf(stdout, "AtomicParsley warning: %s was trimmed to 255 characters (%u characters over)\n",
					        parsedAtoms[ APar_FindParentAtom(target_atom->AtomicNumber, target_atom->AtomicLevel) ].AtomicName,
									                                 utf8_length(unicode_data+total_bytes, 0) );
				} else {
					total_bytes = raw_bytes;
				}

			} else if (text_tag_style == UTF8_iTunesStyle_Unlimited) {
				total_bytes = strlen(unicode_data);

				if (atom_data_pos + total_bytes > MAXDATA_PAYLOAD) {
					target_atom->AtomicData = (char*)realloc(target_atom->AtomicData, sizeof(char)* (atom_data_pos +total_bytes +1) );
					memset(target_atom->AtomicData + atom_data_pos, 0, total_bytes +1);

				}
			}

			//if we are setting iTunes-style metadata, add 0 to the pointer; for 3gp user data atoms - add in the (length-default bare atom lenth): account for language uint16_t (plus any other crap we will set); unicodeWin32 with wchar_t was converted right after program started, so do a direct copy

			memcpy(target_atom->AtomicData + atom_data_pos, unicode_data, total_bytes + 1 );
			target_atom->AtomicLength += total_bytes;
		}
	}
	return;
}

/*----------------------
APar_atom_Binary_Put
  target_atom - pointer to the structure describing the atom we are setting
  binary_data - a pointer to a string of binary data
  bytecount - number of bytes to copy
  atomic_data_offset - place binary data some bytes offset from the start of AtomicData

    Simple placement of binary data (perhaps containing NULLs) onto AtomicData.
		TODO: if over MAXDATA_PAYLOAD malloc a new char string
----------------------*/
void APar_atom_Binary_Put(AtomicInfo* target_atom, const char* binary_data,
	uint32_t bytecount, uint64_t atomic_data_offset)
{
	if (target_atom == NULL) return;

	if (atomic_data_offset + bytecount + target_atom->AtomicLength <= MAXDATA_PAYLOAD) {
		memcpy(target_atom->AtomicData + atomic_data_offset, binary_data, bytecount );
		target_atom->AtomicLength += bytecount;
	} else {
		fprintf(stdout, "AtomicParsley warning: some data was longer than the allotted space and was skipped\n");
	}
	return;
}

/*----------------------
APar_Verify__udta_meta_hdlr__atom

    only test if the atom is present for now, it will be created just before writeout time - to insure it only happens once.
----------------------*/
void APar_Verify__udta_meta_hdlr__atom() {
	bool Create__udta_meta_hdlr__atom = false;

	if (metadata_style == ITUNES_STYLE && hdlrAtom == NULL) {
		hdlrAtom = APar_FindAtom("moov.udta.meta.hdlr", false, VERSIONED_ATOM, 0);
		if (hdlrAtom == NULL) {
			Create__udta_meta_hdlr__atom = true;
		}
	}
	if (Create__udta_meta_hdlr__atom ) {

		//if Quicktime (Player at the least) is used to create any type of mp4 file, the entire udta hierarchy is missing. If iTunes doesn't find
		//this "moov.udta.meta.hdlr" atom (and its data), it refuses to let any information be changed & the dreaded "Album Artwork Not Modifiable"
		//shows up. It's because this atom is missing. Oddly, QT Player can see the info, but this only works for mp4/m4a files.

		hdlrAtom = APar_FindAtom("moov.udta.meta.hdlr", true, VERSIONED_ATOM, 0);

		APar_MetaData_atom_QuickInit(hdlrAtom->AtomicNumber, 0, 0);
		APar_Unified_atom_Put(hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0x6D646972, 32); //'mdir'
		APar_Unified_atom_Put(hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0x6170706C, 32); //'appl'
		APar_Unified_atom_Put(hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32);
		APar_Unified_atom_Put(hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32);
		APar_Unified_atom_Put(hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16);
	}
	return;
}

/*----------------------
APar_MetaData_atomGenre_Set
	atomPayload - the desired string value of the genre

    genre is special in that it gets carried on 2 atoms. A standard genre (as listed in ID3v1GenreList) is represented as a number on a 'gnre' atom
		any value other than those, and the genre is placed as a string onto a 'Šgen' atom. Only one or the other can be present. So if atomPayload is a
		non-NULL value, first try and match the genre into the ID3v1GenreList standard genres. Try to remove the other type of genre atom, then find or
		create the new genre atom and put the data manually onto the atom.
----------------------*/
void APar_MetaData_atomGenre_Set(const char* atomPayload) {
	if (metadata_style == ITUNES_STYLE) {
		const char* standard_genre_atom = "moov.udta.meta.ilst.gnre";
		const char* std_genre_data_atom = "moov.udta.meta.ilst.gnre.data";
		const char* custom_genre_atom = "moov.udta.meta.ilst.Šgen";
		const char* cstm_genre_data_atom = "moov.udta.meta.ilst.Šgen.data";

		if ( strlen(atomPayload) == 0) {
			APar_RemoveAtom(std_genre_data_atom, VERSIONED_ATOM, 0); //find the atom; don't create if it's "" to remove
			APar_RemoveAtom(cstm_genre_data_atom, VERSIONED_ATOM, 0); //find the atom; don't create if it's "" to remove
		} else {

			uint8_t genre_number = StringGenreToInt(atomPayload);
			AtomicInfo* genreAtom;

			APar_Verify__udta_meta_hdlr__atom();
			modified_atoms = true;

			if (genre_number != 0) {
				//first find if a custom genre atom ("Šgen") exists; erase the custom-string genre atom in favor of the standard genre atom

				AtomicInfo* verboten_genre_atom = APar_FindAtom(custom_genre_atom, false, SIMPLE_ATOM, 0);

				if (verboten_genre_atom != NULL) {
					if (strlen(verboten_genre_atom->AtomicName) > 0) {
						if (strncmp(verboten_genre_atom->AtomicName, "Šgen", 4) == 0) {
							APar_RemoveAtom(cstm_genre_data_atom, VERSIONED_ATOM, 0);
						}
					}
				}

				genreAtom = APar_FindAtom(std_genre_data_atom, true, VERSIONED_ATOM, 0);
				APar_MetaData_atom_QuickInit(genreAtom->AtomicNumber, AtomFlags_Data_Binary, 0);
				APar_Unified_atom_Put(genreAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 8);
				APar_Unified_atom_Put(genreAtom, NULL, UTF8_iTunesStyle_256glyphLimited, genre_number, 8);

			} else {

				AtomicInfo* verboten_genre_atom = APar_FindAtom(standard_genre_atom, false, SIMPLE_ATOM, 0);

				if (verboten_genre_atom != NULL) {
					if (verboten_genre_atom->AtomicNumber > 5 && verboten_genre_atom->AtomicNumber < atom_number) {
						if (strncmp(verboten_genre_atom->AtomicName, "gnre", 4) == 0) {
							APar_RemoveAtom(std_genre_data_atom, VERSIONED_ATOM, 0);
						}
					}
				}
				genreAtom = APar_FindAtom(cstm_genre_data_atom, true, VERSIONED_ATOM, 0);
				APar_MetaData_atom_QuickInit(genreAtom->AtomicNumber, AtomFlags_Data_Text, 0);
				APar_Unified_atom_Put(genreAtom, atomPayload, UTF8_iTunesStyle_256glyphLimited, 0, 0);
			}
		}
		APar_FlagMovieHeader();
	} //end if (metadata_style == ITUNES_STYLE)
	return;
}

/*----------------------
APar_MetaData_atomLyrics_Set
	lyricsPath - the path that was provided to a (hopefully) existent txt file

	lyrics will be read from a file because they can contain multiple lines. Lines are stored with old Mac-style line endings (single carriage return).
----------------------*/
void APar_MetaData_atomLyrics_Set(const char* lyricsPath) {
	if (metadata_style == ITUNES_STYLE) {
		TestFileExistence(lyricsPath, true);
		uint64_t file_len = findFileSize(lyricsPath);

		APar_Verify__udta_meta_hdlr__atom();
		modified_atoms = true;

		AtomicInfo* lyricsData_atom = APar_FindAtom("moov.udta.meta.ilst.Šlyr.data", true, VERSIONED_ATOM, 0);
		APar_MetaData_atom_QuickInit(lyricsData_atom->AtomicNumber, AtomFlags_Data_Text, 0, file_len + 1);

		FILE* lyrics_file = APar_OpenFile(lyricsPath, "rb");
		uint64_t remaining = file_len;
		char* dest = lyricsData_atom->AtomicData + 4;
		char* sol;
		while (remaining && (sol = fgets(dest, remaining + 1, lyrics_file))) {
			size_t len = strlen(sol); //NUL bytes in the file will cause parts of the text to be skipped
			//normalize different EOL styles to carriage returns
			if (sol[len-1] == '\n') {
				if (sol[len-2] == '\r') sol[--len] = '\0';
				else sol[len-1] = '\r';
			}
			remaining -= len;
			dest += len;
		}
		lyricsData_atom->AtomicLength += dest - (lyricsData_atom->AtomicData + 4);
		fclose(lyrics_file);

		APar_FlagMovieHeader();
	} //end if (metadata_style == ITUNES_STYLE)
	return;
}

/*----------------------
APar_MetaData_atomArtwork_Init
	atom_num - the AtomicNumber of the atom in the parsedAtoms array (probably newly created)
	artworkPath - the path that was provided on a (hopefully) existant jpg/png file

    artwork will be inited differently because we need to test a) that the file exists and b) get its size in bytes. This info will be used at the size
		of the 'data' atom under 'covr' - and the path will be carried on AtomicData until write-out time, when the binary contents of the original will be
		copied onto the atom.
----------------------*/
void APar_MetaData_atomArtwork_Init(short atom_num, const char* artworkPath) {
	TestFileExistence(artworkPath, true);
	uint64_t picture_size = findFileSize(artworkPath);

	if (picture_size > 0) {
		APar_MetaData_atom_QuickInit(atom_num, APar_TestArtworkBinaryData(artworkPath), 0, picture_size );
		FILE* artfile = APar_OpenFile(artworkPath, "rb");
		uint32_t bytes_read = APar_ReadFile(parsedAtoms[atom_num].AtomicData + 4, artfile, picture_size); //+4 for the 4 null bytes
		if (bytes_read > 0) parsedAtoms[atom_num].AtomicLength += bytes_read;
		fclose(artfile);
	}
	return;
}

/*----------------------
APar_MetaData_atomArtwork_Set
	artworkPath - the path that was provided on a (hopefully) existant jpg/png file
	env_PicOptions - picture embedding preferences from a 'export PIC_OPTIONS=foo' setting

    artwork gets stored under a single 'covr' atom, but with many 'data' atoms - each 'data' atom contains the binary data for each picture.
		When the 'covr' atom is found, we create a sparse atom at the end of the existing 'data' atoms, and then perform any of the image manipulation
		features on the image. The path of the file (either original, modified artwork, or both) are returned to use for possible atom creation
----------------------*/
void APar_MetaData_atomArtwork_Set(const char* artworkPath, char* env_PicOptions) {
	if (metadata_style == ITUNES_STYLE) {
		const char* artwork_atom = "moov.udta.meta.ilst.covr";
		if (strcmp(artworkPath, "REMOVE_ALL") == 0) {
			APar_RemoveAtom(artwork_atom, SIMPLE_ATOM, 0);

		} else {
			APar_Verify__udta_meta_hdlr__atom();

			modified_atoms = true;
			AtomicInfo* desiredAtom = APar_FindAtom(artwork_atom, true, SIMPLE_ATOM, 0);
			AtomicInfo sample_data_atom = { 0 };

#if defined (DARWIN_PLATFORM)
			// used on Darwin adding a 2nd image (the original)
			short parent_atom = desiredAtom->AtomicNumber;
#endif

			APar_CreateSurrogateAtom(&sample_data_atom, "data", 6, VERSIONED_ATOM, 0, NULL, 0);
			desiredAtom = APar_CreateSparseAtom(&sample_data_atom, desiredAtom, APar_FindLastChild_of_ParentAtom(desiredAtom->AtomicNumber) );

#if defined (DARWIN_PLATFORM)
			//determine if any picture preferences will impact the picture file in any way
			myPicturePrefs = APar_ExtractPicPrefs(env_PicOptions);

			char* resized_filepath = (char*)calloc(1, sizeof(char)*MAXPATHLEN+1);

			if ( ResizeGivenImage(artworkPath , myPicturePrefs, resized_filepath) ) {
				APar_MetaData_atomArtwork_Init(desiredAtom->AtomicNumber, resized_filepath);

				if (myPicturePrefs.addBOTHpix) {
					//create another sparse atom to hold the new image data
					desiredAtom = APar_CreateSparseAtom(&sample_data_atom, desiredAtom, APar_FindLastChild_of_ParentAtom(parent_atom) );
					APar_MetaData_atomArtwork_Init(desiredAtom->AtomicNumber, artworkPath);
					if (myPicturePrefs.removeTempPix) remove(resized_filepath);
				}
			} else {
				APar_MetaData_atomArtwork_Init(desiredAtom->AtomicNumber, artworkPath);
			}
			free(resized_filepath);
			resized_filepath=NULL;
#else
			//perhaps some libjpeg based resizing/modification for non-Mac OS X based platforms
			APar_MetaData_atomArtwork_Init(desiredAtom->AtomicNumber, artworkPath);
#endif
		}
		APar_FlagMovieHeader();
	} ////end if (metadata_style == ITUNES_STYLE)
	return;
}

/*----------------------
APar_sprintf_atompath
	dest_path - the destination string that will hold the final path
	target_atom_name - the name of the atom that will be used to fill the %s portion of the tokenized path
	track_index - the index into the trak[X] that needs to be found; fills the %u portion of the tokenized path
	udta_container - the area in which 'udta' will be: either movie level (the easiest) or track level (which requires an index into a specific 'trak' atom)
	dest_len - the amount of malloc'ed bytes for dest_path

    Fill a destinaiton path with the fully expressed atom path taking track indexes into consideration
----------------------*/
void APar_sprintf_atompath(char* dest_path, const char* target_atom_name, uint8_t track_index, uint8_t udta_container, uint16_t dest_len) {
	memset(dest_path, 0, dest_len);
	if (udta_container == MOVIE_LEVEL_ATOM) {
		memcpy(dest_path, "moov.udta.", 10);
		memcpy(dest_path +10, target_atom_name, 4);
	} else {
		sprintf(dest_path, "moov.trak[%u].udta.%s", track_index, target_atom_name);
	}
	return;
}

/*----------------------
APar_3GP_Keyword_atom_Format
	keywords_globbed - the globbed string of keywords ('foo1,foo2,foo_you')
	keyword_count - count of keywords in the above globbed string
	set_UTF16_text - whether to encode as utf16
	formed_keyword_struct - the char string that will hold the converted keyword struct (manually formatted)

    3gp keywords are a little more complicated. Since they will be entered separated by some form of punctuation, they need to be separated out
		They also will possibly be converted to utf16 - and they NEED to start with the BOM then. Prefacing each keyword is the 8bit length of the string
		And each keyword needs to be NULL terminated. Technically it would be possible to even have mixed encodings (not supported here).
----------------------*/
uint32_t APar_3GP_Keyword_atom_Format(char* keywords_globbed, uint8_t keyword_count, bool set_UTF16_text, char* &formed_keyword_struct) {
	uint64_t formed_string_offset = 0;
	uint64_t string_len = 0;

	char* a_keyword = strsep(&keywords_globbed,",");

	for (uint8_t i=1; i <= keyword_count; i++) {
		string_len = strlen(a_keyword);
		if (set_UTF16_text) {

			uint32_t glyphs_req_bytes = mbstowcs(NULL, a_keyword, string_len+1) * 2; //passing NULL pre-calculates the size of wchar_t needed;

			formed_keyword_struct[formed_string_offset+1] = (char)(0xFE); //BOM
			formed_keyword_struct[formed_string_offset+2] = (char)(0xFF); //BOM
			formed_string_offset+=3; //BOM + keyword length that has yet to be set

			int bytes_converted = UTF8ToUTF16BE((unsigned char*)(formed_keyword_struct + formed_string_offset), glyphs_req_bytes, (unsigned char*)a_keyword, string_len);

			if (bytes_converted > 1) {
				formed_keyword_struct[formed_string_offset-3] = (uint8_t)bytes_converted + 4; //keyword length is NOW set
				formed_string_offset += bytes_converted + 2; //NULL terminator
			}
		} else {

			uint32_t string_len = strlen(a_keyword);
			formed_keyword_struct[formed_string_offset] = (uint8_t)string_len + 1; //add the terminating NULL
			formed_string_offset++;
			memcpy(formed_keyword_struct + formed_string_offset, a_keyword, string_len );
			formed_string_offset+= (string_len +1);
		}
		a_keyword = strsep(&keywords_globbed,",");
	}
	return formed_string_offset;
}

/*----------------------
APar_uuid_atom_Init
	atom_path - the parent hierarchy of the desired atom (with the location of the specific uuid atom supplied as '=%s')
	uuidName - the name of the atom (possibly provided in a forbidden utf8 - only latin1 aka iso8859 is acceptable)
	dataType - for now text is only supported; really its atom version/flags as used by iTunes
	uuidValue - the string that will get embedded onto the atom
	shellAtom - flag to denote whether the atom may possibly come as utf8 encoded

    uuid atoms are user-supplied/user-defined atoms that allow for extended tagging support. Because a uuid atom is malleable, and defined by the utility
		that created it, any information carried by a uuid is arbitrary, and cannot be guaranteed by a non-originating utility. In AtomicParsley uuid atoms,
		the data is presented much like an iTunes-style atom - except that the information gets carried directly on the uuid atom - no 'data' child atom
		exists. A uuid atom is a special longer type of traditional atom. As a traditional atom, it name is 'uuid' - and the 4 bytes after that represent
		its uuid name. Because the data will be directly sitting on the atom, a different means of finding these atoms exists, as well as creating the
		acutal uuidpath itself. Once created however, placing information on it is very much like any other atom - done via APar_Unified_atom_Put

		//4bytes atom length, 4 bytes 'uuid', 16bytes uuidv5, 4bytes name of uuid in AP namespace, 4bytes versioning, 4bytes NULL, Xbytes data
----------------------*/
AtomicInfo* APar_uuid_atom_Init(const char* atom_path, const char* uuidName, const uint32_t dataType, const char* uuidValue, bool shellAtom) {
	AtomicInfo* desiredAtom = NULL;
	char uuid_path[256];
	char uuid_binary_str[20];
	char uuid_4char_name[10];
	memset(uuid_path, 0, sizeof(uuid_path));
	memset(uuid_binary_str, 0, sizeof(uuid_binary_str));
	memset(uuid_4char_name, 0, sizeof(uuid_4char_name));
	uint16_t path_len = 0;

	if (shellAtom) {
		UTF8Toisolat1((unsigned char*)&uuid_4char_name, 4, (unsigned char*)uuidName, strlen(uuidName) );
	} else {
		memcpy(uuid_4char_name, uuidName, 4);
	}

	APar_generate_uuid_from_atomname(uuid_4char_name, uuid_binary_str);
	APar_endian_uuid_bin_str_conversion(uuid_binary_str);

	//this will only append (and knock off) %s (anything) at the end of a string
	path_len = strlen(atom_path);
	memcpy(uuid_path, atom_path, path_len-2);
	memcpy(uuid_path + (path_len-2), uuid_binary_str, 16);

#if defined(DEBUG_V)
	fprintf(stdout, "debug: APar_uuid_atom_Init desired atom '%s' converted to uuidv5: ", uuidName);
	APar_print_uuid( (ap_uuid_t*)(uuid_path + (path_len-2)) );
#endif

	if ( uuidValue == NULL || strlen(uuidValue) == 0) {
		APar_RemoveAtom(uuid_path, EXTENDED_ATOM, 0); //find the atom; don't create if it's "" to remove
		APar_FlagMovieHeader();
		return NULL;

	} else {
		if ( !(dataType == AtomFlags_Data_Text || dataType == AtomFlags_Data_uuid_binary) ) { //the only supported types
			fprintf(stdout, "AP warning: only text or file types are allowed on uuid atom %s (%u-%u); skipping\n", uuidName, dataType, AtomFlags_Data_Text);
			return NULL;
		}
		//uuid atoms won't have 'data' child atoms - they will carry the data directly as opposed to traditional iTunes-style metadata that does store the information on 'data' atoms. But user-defined is user-defined, so that is how it will be defined here.
		modified_atoms = true;

		desiredAtom = APar_FindAtom(uuid_path, true, EXTENDED_ATOM, 0, true);
		desiredAtom->uuid_ap_atomname = (char*)calloc(1, sizeof(char)*10);       //only useful to print out the atom tree midway through an operation
		memcpy(desiredAtom->uuid_ap_atomname, uuid_4char_name, 4);               //only useful to print out the atom tree midway through an operation

		if (dataType == AtomFlags_Data_Text) APar_MetaData_atom_QuickInit(desiredAtom->AtomicNumber, dataType, 20); //+20 including the 4 NULL bytes preceding any string we set
		//NOTE: setting a file into a uuid atom  (dataType == AtomFlags_Data_uuid_binary) is handled in main.cpp - the length of the file extension, description and file
		//all add up to the amount to malloc AtomicData to, so handle that separately.

		parsedAtoms[desiredAtom->AtomicNumber].AtomicClassification = EXTENDED_ATOM;
		APar_FlagMovieHeader();
	}
	return desiredAtom;
}

/*----------------------
APar_MetaData_atom_QuickInit
	atom_num - the position in the parsedAtoms array (either found in the file or a newly created sparse atom) so AtomicData can be initialized
	atomFlags - the AtomicVerFlags for the iTunes-style metadata atom
	supplemental_length - iTunes-style metadata for 'data' atoms is >= 16bytes long; AtomicParsley created uuid atoms will be +4bytes directly on that atom
	allotment - the bytes of AtomicData to malloc (defaults to MAXDATA_PAYLOAD + 1 (+50) unless changed - like uuids from file)

    Metadata_QuickInit will initialize a pre-found atom to MAXDATA_PAYLOAD so that it can carry info on AtomicData
----------------------*/
void APar_MetaData_atom_QuickInit(short atom_num, const uint32_t atomFlags, uint32_t supplemental_length, uint32_t allotment) {
	//this will skip the finding of atoms and just malloc the AtomicData; used by genre & artwork

	parsedAtoms[atom_num].AtomicData = (char*)calloc(1, sizeof(char)* allotment+50 );
	if (parsedAtoms[atom_num].AtomicData == NULL) {
		fprintf(stdout, "AP error: there was insufficient memory available for allocation. Exiting.%c\n", '\a');
		exit(1);
	}

	parsedAtoms[atom_num].AtomicLength = 16 + supplemental_length; // 4bytes atom length, 4 bytes atom length, 4 bytes version/flags, 4 bytes NULL
	parsedAtoms[atom_num].AtomicVerFlags = atomFlags;
	parsedAtoms[atom_num].AtomicContainerState = CHILD_ATOM;
	parsedAtoms[atom_num].AtomicClassification = VERSIONED_ATOM;

	return;
}

/*----------------------
APar_MetaData_atom_Init
	atom_path - the hierarchical path to the specific atom carrying iTunes-style metadata that will be found (and created if necessary)
	MD_Payload - the information to be carried (also used as a test if NULL to remove the atom)
	atomFlags - the AtomicVerFlags for the atom (text, integer or unsigned integer)

    Metadata_Init will search for and create the necessary hierarchy so that the atom can be initialized to carry the payload data on AtomicData.
		This will provide a shell of an atom with 4bytes length, 4bytes name, 4bytes version/flags, 4bytes NULL + any other data
----------------------*/
AtomicInfo* APar_MetaData_atom_Init(const char* atom_path, const char* MD_Payload, const uint32_t atomFlags) {
	//this will handle the vanilla iTunes-style metadata atoms; genre will be handled elsewehere because it gets carried on 2 different atoms, and artwork gets special treatment because it can have multiple child data atoms
	if (metadata_style != ITUNES_STYLE) return NULL;
	bool retain_atom = true;

	if ( strlen(MD_Payload) == 0 ) {
		retain_atom = false;
	}

	if (retain_atom) {
		APar_Verify__udta_meta_hdlr__atom();
	}

	AtomicInfo* desiredAtom = APar_FindAtom(atom_path, retain_atom, VERSIONED_ATOM, 0); //finds the atom; if not present, creates the atom
	if (desiredAtom == NULL) return NULL;
	modified_atoms = true;

	if (! retain_atom) {
		AtomicInfo* parent_atom = &parsedAtoms[ APar_FindParentAtom(desiredAtom->AtomicNumber, desiredAtom->AtomicLevel) ];
		if (desiredAtom->AtomicNumber > 0 && parent_atom->AtomicNumber > 0) {
			APar_EliminateAtom(parent_atom->AtomicNumber, desiredAtom->NextAtomNumber);
			APar_FlagMovieHeader();
			return NULL;
		}

	} else {
		parsedAtoms[desiredAtom->AtomicNumber].AtomicData = (char*)malloc(sizeof(char)* MAXDATA_PAYLOAD + 1 ); //puts a hard limit on the length of strings (the spec doesn't)
		memset(parsedAtoms[desiredAtom->AtomicNumber].AtomicData, 0, sizeof(char)* MAXDATA_PAYLOAD + 1 );

		parsedAtoms[desiredAtom->AtomicNumber].AtomicLength = 16; // 4bytes atom length, 4 bytes atom length, 4 bytes version/flags, 4 bytes NULL
		parsedAtoms[desiredAtom->AtomicNumber].AtomicVerFlags = atomFlags;
		parsedAtoms[desiredAtom->AtomicNumber].AtomicContainerState = CHILD_ATOM;
		parsedAtoms[desiredAtom->AtomicNumber].AtomicClassification = VERSIONED_ATOM;
		APar_FlagMovieHeader();
	}
	return desiredAtom;
}

/*----------------------
APar_UserData_atom_Init
	userdata_atom_name - the name of the atom to be set ('titl', 'loci', 'cprt')
	atom_payload - the information to be carried (also used as a test if NULL to remove the atom)
	udta_container - determines whether to create an atom path at movie level or track level
	track_idx - provide the track for the target atom if at track level
	userdata_lang - the language for the tag (multiple tags with the same name, but different languages are supported for some of these atoms)

    UserData_Init can be called multiple times from a single cli invocation (if used to target at atom at track level for all tracks). Since it can be called
		repeatedly, the atom path is created here based on the container for 'udta': 'moov' for movie level or 'trak' at track level. If there is no payload that
		atom path is found & if found to exists is removed. If the payload does contain something, the created atom path is created, initialized & the language
		setting is set (for internal AP use) for that atom.

			NOTE: the language setting (if supported - yrrc doesn't) occurs in different places in 3GP atoms. Most occur right after atom flags/versioning - but
			in rtng/clsf they occur later. The language is instead stored in binary form amid the data for the atom, but is also put into the parsedAtoms[] array
			(that information is only used in finding atoms not the actual writing of atoms out). Both (storing the language in AtomicData in binary form & put in
			the parsedAtoms[] AtomicInfo array) forms are required to implement multiple language support for 3gp atoms.

			NOTE2: Perhaps there is something wrong with Apple's implementation of 3gp metadata, or I'm loosing my mind. The exact same utf8 string that shows up in a
			3gp file as ??? - ??? shows up *perfect* in an mp4 or mov container. Encoded as utf16 same problem a sample string using Polish glyphs in utf8 has some
			gylphs missing with lang=eng. The same string with 'lang=pol', and different glyphs are missing. The problem occurs using unicode.org's ConvertUTF8toUTF16
			or using libxmls's UTF8ToUTF16BE (when converting to utf16) in the same places - except for the copyright symbol which unicode.org's ConvertUTF16toUTF8 didn't
			properly convert - which was the reason why libxml's functions are now used. And at no point can I get the audio protected P-in-a-circle glyph to show up in
			utf8 or utf16. To summarize, either I am completely overlooking some interplay (of lang somehow changing the utf8 or utf16 standard), the unicode translations
			are off (which in the case of utf8 is embedded directly on Mac OS X, so that can't be it), or Apple's 3gp implementation is off.

			TODO NOTE: the track modification date should change if set at track level because of this
----------------------*/
AtomicInfo* APar_UserData_atom_Init(const char* userdata_atom_name, const char* atom_payload, uint8_t udta_container, uint8_t track_idx, uint16_t userdata_lang) {
	uint8_t atom_type = PACKED_LANG_ATOM;
	uint8_t total_tracks = 0;
	uint8_t a_track = 0;//unused
	AtomicInfo* desiredAtom = NULL;
	char* userdata_atom_path = NULL;

	if (userdata_lang == 0)  atom_type = VERSIONED_ATOM;

	APar_FindAtomInTrack(total_tracks, a_track, NULL); //With track_num set to 0, it will return the total trak atom into total_tracks here.

	if (track_idx > total_tracks || (track_idx == 0 && udta_container == SINGLE_TRACK_ATOM) ) {
		APar_assert(false, 5, userdata_atom_name);
		return NULL;
	}

	userdata_atom_path = (char*)malloc(sizeof(char)* 400 );

	if (udta_container == MOVIE_LEVEL_ATOM) {
		APar_sprintf_atompath(userdata_atom_path, userdata_atom_name, 0xFF, MOVIE_LEVEL_ATOM, 400);
	} else if (udta_container == ALL_TRACKS_ATOM) {
		APar_sprintf_atompath(userdata_atom_path, userdata_atom_name, track_idx, ALL_TRACKS_ATOM, 400);
	} else {
		APar_sprintf_atompath(userdata_atom_path, userdata_atom_name, track_idx, SINGLE_TRACK_ATOM, 400);
	}

	if ( strlen(atom_payload) == 0) {

		APar_RemoveAtom(userdata_atom_path, atom_type, atom_type == VERSIONED_ATOM ? 1 : userdata_lang); //find the atom; don't create if it's "" to remove
		free(userdata_atom_path); userdata_atom_path = NULL;
		return NULL;
	} else {
		modified_atoms = true;

		desiredAtom = APar_FindAtom(userdata_atom_path, true, atom_type, userdata_lang);

		desiredAtom->AtomicData = (char*)calloc(1, sizeof(char)* MAXDATA_PAYLOAD ); //puts a hard limit on the length of strings (the spec doesn't)

		desiredAtom->AtomicLength = 12; // 4bytes atom length, 4 bytes atom length, 4 bytes version/flags (NULLs)
		desiredAtom->AtomicVerFlags = 0;
		desiredAtom->AtomicContainerState = CHILD_ATOM;
		desiredAtom->AtomicClassification = atom_type;
		desiredAtom->AtomicLanguage = userdata_lang;
		APar_FlagTrackHeader(desiredAtom);
	}
	free(userdata_atom_path); userdata_atom_path = NULL;
	return desiredAtom;
}

/*----------------------
APar_reverseDNS_atom_Init
	rDNS_atom_name - the name of the descriptor for the reverseDNS atom form (like iTunNORM)
	rDNS_payload - the information to be carried (also used as a test if NULL to remove the atom)
	atomFlags - text, integer, binary flag of the data payload
	rDNS_domain - the reverse domain itself (like net.sourceforge.atomicparsley or com.apple.iTunes)

    FILL IN
----------------------*/
AtomicInfo* APar_reverseDNS_atom_Init(const char* rDNS_atom_name, const char* rDNS_payload, const uint32_t* atomFlags, const char* rDNS_domain) {
	AtomicInfo* desiredAtom = NULL;
	char* reverseDNS_atompath = (char*)calloc(1, sizeof(char)*2001);

	if (metadata_style != ITUNES_STYLE) {
		free(reverseDNS_atompath); reverseDNS_atompath = NULL;
		return NULL;
	}

	sprintf(reverseDNS_atompath, "moov.udta.meta.ilst.----.name:[%s]", rDNS_atom_name); //moov.udta.meta.ilst.----.name:[iTunNORM]

	if ( rDNS_payload != NULL ) {
		if (strlen(rDNS_payload) == 0) {
			APar_RemoveAtom(reverseDNS_atompath, VERSIONED_ATOM, 0, rDNS_domain);
			free(reverseDNS_atompath); reverseDNS_atompath = NULL;
			return NULL;
		}
		APar_Verify__udta_meta_hdlr__atom();
	} else {
		APar_RemoveAtom(reverseDNS_atompath, VERSIONED_ATOM, 0, rDNS_domain);
		APar_FlagMovieHeader();
		free(reverseDNS_atompath); reverseDNS_atompath = NULL;
		return NULL;
	}

	desiredAtom = APar_FindAtom(reverseDNS_atompath, false, VERSIONED_ATOM, 0, false, rDNS_domain); //finds the atom; do NOT create it if not found - manually create the hierarchy

	if (desiredAtom == NULL) {
		AtomicInfo* ilst_atom = APar_FindAtom("moov.udta.meta.ilst", true, SIMPLE_ATOM, 0);
		short last_iTunes_list_descriptor = APar_FindLastChild_of_ParentAtom(ilst_atom->AtomicNumber); //the *last* atom contained by ilst - even if its the 4th 'data' atom

		short rDNS_four_dash_parent = APar_InterjectNewAtom("----", PARENT_ATOM, SIMPLE_ATOM, 8, 0, 0, ilst_atom->AtomicLevel+1, last_iTunes_list_descriptor);

		short rDNS_mean_atom = APar_InterjectNewAtom("mean", CHILD_ATOM, VERSIONED_ATOM, 12, AtomFlags_Data_Binary, 0, ilst_atom->AtomicLevel+2, rDNS_four_dash_parent);
		uint32_t domain_len = strlen(rDNS_domain);
		parsedAtoms[rDNS_mean_atom].ReverseDNSdomain = (char*)calloc(1, sizeof(char)*101);
		memcpy( parsedAtoms[rDNS_mean_atom].ReverseDNSdomain, rDNS_domain, domain_len );
		APar_atom_Binary_Put(&parsedAtoms[rDNS_mean_atom], rDNS_domain, domain_len, 0);

		short rDNS_name_atom = APar_InterjectNewAtom("name", CHILD_ATOM, VERSIONED_ATOM, 12, AtomFlags_Data_Binary, 0, ilst_atom->AtomicLevel+2, rDNS_mean_atom);
		uint32_t name_len = strlen(rDNS_atom_name);
		parsedAtoms[rDNS_name_atom].ReverseDNSname = (char*)calloc(1, sizeof(char)*101);
		memcpy( parsedAtoms[rDNS_name_atom].ReverseDNSname, rDNS_atom_name, name_len );
		APar_atom_Binary_Put(&parsedAtoms[rDNS_name_atom], rDNS_atom_name, name_len, 0);

		AtomicInfo proto_rDNS_data_atom = { 0 };
		APar_CreateSurrogateAtom(&proto_rDNS_data_atom, "data", ilst_atom->AtomicLevel+2, VERSIONED_ATOM, 0, NULL, 0);
		desiredAtom = APar_CreateSparseAtom(&proto_rDNS_data_atom, ilst_atom, rDNS_name_atom);
		APar_MetaData_atom_QuickInit(desiredAtom->AtomicNumber, *atomFlags, 0, MAXDATA_PAYLOAD);
	}	else {
		if (strcmp(rDNS_domain, "com.apple.iTunes") == 0) { //for the iTunes domain, only support 1 'data' entry
			APar_MetaData_atom_QuickInit(desiredAtom->NextAtomNumber, *atomFlags, 0, MAXDATA_PAYLOAD);
			desiredAtom = &parsedAtoms[desiredAtom->NextAtomNumber];

		} else { //now create a 'data' atom at the end of the hierarchy (allowing multiple entries)
			short rDNSparent_idx = APar_FindParentAtom(desiredAtom->AtomicNumber, desiredAtom->AtomicLevel);
			short last_child = APar_FindLastChild_of_ParentAtom(rDNSparent_idx);
			AtomicInfo proto_rDNS_data_atom = { 0 };
			APar_CreateSurrogateAtom(&proto_rDNS_data_atom, "data", parsedAtoms[last_child].AtomicLevel, VERSIONED_ATOM, 0, NULL, 0);
			desiredAtom = APar_CreateSparseAtom(&proto_rDNS_data_atom, &parsedAtoms[rDNSparent_idx], last_child);
			APar_MetaData_atom_QuickInit(desiredAtom->AtomicNumber, *atomFlags, 0, MAXDATA_PAYLOAD);
		}
	}
	APar_FlagMovieHeader();
	free(reverseDNS_atompath); reverseDNS_atompath = NULL;
	modified_atoms = true;
	return desiredAtom;
}

AtomicInfo* APar_ID32_atom_Init(const char* frameID_str, char meta_area, const char* lang_str, uint16_t id32_lang) {
	uint8_t total_tracks = 0;
	uint8_t a_track = 0;//unused
	AtomicInfo* meta_atom = NULL;
	AtomicInfo* hdlr_atom = NULL;
	char* id32_trackpath = NULL;
	AtomicInfo* ID32_atom = NULL;
	bool non_referenced_data = false;
	bool remove_ID32_atom = false;

	APar_FindAtomInTrack(total_tracks, a_track, NULL); //With track_num set to 0, it will return the total trak atom into total_tracks here.

	if (meta_area > 0) {
		if ((uint8_t)meta_area > total_tracks) {
			APar_assert(false, 6, frameID_str);
			return NULL;
		}
	}

	id32_trackpath = (char*)calloc(1, sizeof(char)*100);

	if (meta_area == 0-FILE_LEVEL_ATOM) {
		meta_atom = APar_FindAtom("meta", false, DUAL_STATE_ATOM, 0);
	} else if (meta_area == 0-MOVIE_LEVEL_ATOM) {
		meta_atom = APar_FindAtom("moov.meta", false, DUAL_STATE_ATOM, 0);
	//} else if (meta_area = 0) {
		//setting id3tags for all tracks will *not* be supported;
	} else if (meta_area > 0) {
		sprintf(id32_trackpath, "moov.trak[%u].meta", meta_area);
		meta_atom = APar_FindAtom(id32_trackpath, false, DUAL_STATE_ATOM, 0);
	}

	if (meta_atom != NULL) {
		hdlr_atom = APar_FindChildAtom(meta_atom->AtomicNumber, "hdlr");
		if (hdlr_atom != NULL) {
			if (hdlr_atom->ancillary_data != 0x49443332) {
				memset(id32_trackpath, 0, 5); //well, it won't be used for anything else since the handler type doesn't match, might as well convert the handler type using it
				UInt32_TO_String4(hdlr_atom->ancillary_data, id32_trackpath);
				APar_assert(false, 7, id32_trackpath);
				free(id32_trackpath);

				return NULL;
			}
		}
	}

	//its possible the ID32 atom targeted already exists - finding it in the traditional form (not external, and not locally referenced) is easiest. Locally referenced isn't.
	if (meta_area == 0-FILE_LEVEL_ATOM) {
		ID32_atom = APar_FindAtom("meta.ID32", false, PACKED_LANG_ATOM, id32_lang);
	} else if (meta_area == 0-MOVIE_LEVEL_ATOM) {
		ID32_atom = APar_FindAtom("moov.meta.ID32", false, PACKED_LANG_ATOM, id32_lang);
	//} else if (meta_area = 0) {
		//setting id3tags for all tracks will *not* be supported;
	} else if (meta_area > 0) {
		sprintf(id32_trackpath, "moov.trak[%u].meta.ID32", meta_area);
		ID32_atom = APar_FindAtom(id32_trackpath, false, PACKED_LANG_ATOM, id32_lang);
	}

	if (ID32_atom != NULL) {
		free(id32_trackpath);
		id32_trackpath = NULL;
		if (ID32_atom->ID32_TagInfo == NULL) {
			APar_ID32_ScanID3Tag(source_file, ID32_atom);
		}
		return ID32_atom; //and that completes finding the ID32 atom and verifying that it was local and in a traditionally represented form.

	} else {
		if (meta_atom != NULL) {
			//if the primary item atom is present, it points to either a local data reference (flag of 0x000001) or external data which is unsupported. Either way skip it.
			//..or probably another test would be if a data REFERENCE atom were present.... but you would have to match item_IDs - which are found in pitm (required for ID32).
			if (APar_FindChildAtom(meta_atom->AtomicNumber, "pitm") != NULL) {
				non_referenced_data = false;
				APar_assert(false, 8, frameID_str);
				free(id32_trackpath);
				return NULL;
			} else { //the inline/3gpp 'ID32' atom calls for referenced content to carry a 'pitm' atom. No worries - its just a 'meta'.
				non_referenced_data = true;
			}
		} else {
			//no 'meta' atom? Great - a blank slate. There won't be any jumping through a multitude of atoms to determine referencing
			non_referenced_data = true;
		}
	}

	if (frameID_str == NULL) {
		remove_ID32_atom = true;
	} else if (strlen(frameID_str) == 0) {
		remove_ID32_atom = true;
	}
	//this only gets executed if a pre-existing satisfactory ID32 atom was not found. Being able to find it by atom.path by definition means it was not referenced.
	if (non_referenced_data && !remove_ID32_atom) {
		if (meta_atom == NULL) {
			if (meta_area == 0-FILE_LEVEL_ATOM) {
				meta_atom = APar_FindAtom("meta", true, VERSIONED_ATOM, 0);
			} else if (meta_area == 0-MOVIE_LEVEL_ATOM) {
				meta_atom = APar_FindAtom("moov.meta", true, VERSIONED_ATOM, 0);
			//} else if (meta_area = 0) {
				//setting id3tags for all tracks will *not* be supported;
			} else if (meta_area > 0) {
				sprintf(id32_trackpath, "moov.trak[%u].meta", meta_area);
				meta_atom = APar_FindAtom(id32_trackpath, true, VERSIONED_ATOM, 0);
			}
		}

		//create the required hdlr atom
		if (hdlr_atom == NULL) {
			if (meta_area == 0-FILE_LEVEL_ATOM) {
				hdlr_atom = APar_FindAtom("meta.hdlr", true, VERSIONED_ATOM, 0);
			} else if (meta_area == 0-MOVIE_LEVEL_ATOM) {
				hdlr_atom = APar_FindAtom("moov.meta.hdlr", true, VERSIONED_ATOM, 0);
			//} else if (meta_area = 0) {
				//setting id3tags for all tracks will *not* be supported;
			} else if (meta_area > 0) {
				sprintf(id32_trackpath, "moov.trak[%u].meta.hdlr", meta_area);
				hdlr_atom = APar_FindAtom(id32_trackpath, true, VERSIONED_ATOM, 0);
			}
			if (hdlr_atom == NULL) {
				fprintf(stdout, "Uh, problem\n");
				exit(0);
			}
			APar_MetaData_atom_QuickInit(hdlr_atom->AtomicNumber, 0, 0);
			APar_Unified_atom_Put(hdlr_atom, "ID32", UTF8_iTunesStyle_256glyphLimited, 0, 0);
			APar_Unified_atom_Put(hdlr_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32);
			APar_Unified_atom_Put(hdlr_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32);
			APar_Unified_atom_Put(hdlr_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32);
			APar_Unified_atom_Put(hdlr_atom, "AtomicParsley ID3v2 Handler", UTF8_3GP_Style, 0, 0);
			hdlr_atom->ancillary_data = 0x49443332;
		}

		//and finally create the ID32 atom
		if (meta_area == 0-FILE_LEVEL_ATOM) {
			ID32_atom = APar_FindAtom("meta.ID32", true, PACKED_LANG_ATOM, id32_lang);
		} else if (meta_area == 0-MOVIE_LEVEL_ATOM) {
			ID32_atom = APar_FindAtom("moov.meta.ID32", true, PACKED_LANG_ATOM, id32_lang);
		//} else if (meta_area = 0) {
			//setting id3tags for all tracks will *not* be supported;
		} else if (meta_area > 0) {
			sprintf(id32_trackpath, "moov.trak[%u].meta.ID32", meta_area);
			ID32_atom = APar_FindAtom(id32_trackpath, true, PACKED_LANG_ATOM, id32_lang);
		}

		if (id32_trackpath != NULL) {
			free(id32_trackpath);
			id32_trackpath = NULL;
		}

		ID32_atom->AtomicLength = 12; // 4bytes atom length, 4 bytes atom length, 4 bytes version/flags (NULLs), 2 bytes lang
		ID32_atom->AtomicVerFlags = 0;
		ID32_atom->AtomicContainerState = CHILD_ATOM;
		ID32_atom->AtomicClassification = PACKED_LANG_ATOM;
		ID32_atom->AtomicLanguage = id32_lang;

		APar_ID3Tag_Init(ID32_atom);
		//search for the desired frame

		//add the frame to an empty frame, copy data onto the id32_atom structure as required
		//set modified _atoms to true

		return ID32_atom;
	} else if (remove_ID32_atom) {
		if (meta_area == 0-FILE_LEVEL_ATOM) {
			APar_RemoveAtom("meta.ID32", PACKED_LANG_ATOM, id32_lang);
		} else if (meta_area == 0-MOVIE_LEVEL_ATOM) {
			APar_RemoveAtom("moov.meta.ID32", PACKED_LANG_ATOM, id32_lang);
		//} else if (meta_area = 0) {
			//setting id3tags for all tracks will *not* be supported;
		} else if (meta_area > 0) {
			sprintf(id32_trackpath, "moov.trak[%u].meta.ID32", meta_area);
			APar_RemoveAtom(id32_trackpath, PACKED_LANG_ATOM, id32_lang);
		}
	}
	return NULL;
}

void APar_RenderAllID32Atoms() {
	short atom_idx = 0;
	short meta_idx = -1;
	//loop through each atom in the struct array (which holds the offset info/data)
 	while (true) {
		if (memcmp(parsedAtoms[atom_idx].AtomicName, "ID32", 4) == 0) {
			if (parsedAtoms[atom_idx].ID32_TagInfo != NULL) {
				uint64_t id32tag_max_length = APar_GetTagSize(&parsedAtoms[atom_idx]);
				if (id32tag_max_length > 0) {
					parsedAtoms[atom_idx].AtomicData = (char*)calloc(1, sizeof(char)* id32tag_max_length + (16*parsedAtoms[atom_idx].ID32_TagInfo->ID3v2_FrameCount) );
					APar_Unified_atom_Put(&parsedAtoms[atom_idx], NULL, 0, parsedAtoms[atom_idx].AtomicLanguage, 16);
					parsedAtoms[atom_idx].AtomicLength = APar_Render_ID32_Tag(&parsedAtoms[atom_idx], id32tag_max_length + (16*parsedAtoms[atom_idx].ID32_TagInfo->ID3v2_FrameCount) ) + 14;
					if (parsedAtoms[atom_idx].AtomicLength < 12+10) {
						meta_idx = APar_FindParentAtom(parsedAtoms[atom_idx].AtomicNumber, parsedAtoms[atom_idx].AtomicLevel);
						APar_EliminateAtom(parsedAtoms[atom_idx].AtomicNumber, parsedAtoms[atom_idx].NextAtomNumber);
					} else {
						APar_FlagTrackHeader(&parsedAtoms[atom_idx]);
					}
				} else {
					meta_idx = APar_FindParentAtom(parsedAtoms[atom_idx].AtomicNumber, parsedAtoms[atom_idx].AtomicLevel);
					APar_EliminateAtom(parsedAtoms[atom_idx].AtomicNumber, parsedAtoms[atom_idx].NextAtomNumber);
				}
			}
		}
		if (meta_idx > 0) {
			if (memcmp(parsedAtoms[meta_idx].AtomicName, "meta", 4) == 0) {
				if ( APar_ReturnChildrenAtoms(meta_idx, 0) == 1 ) {
					AtomicInfo* meta_handler = &parsedAtoms[ parsedAtoms[meta_idx].NextAtomNumber ];
					if (memcmp(meta_handler->AtomicName, "hdlr", 4) == 0 && meta_handler->ancillary_data == 0x49443332) {
						APar_EliminateAtom(meta_idx, meta_handler->NextAtomNumber);
					}
				}
			}
		}
		atom_idx = parsedAtoms[atom_idx].NextAtomNumber;
		if (parsedAtoms[atom_idx].AtomicNumber == 0) break;
		meta_idx = -1;
	}
	return;
}

/*----------------------
APar_TestVideoDescription
	video_desc_atom - the avc1 atom after stsd that contains the height/width
	ISObmff_file - the reopened source file

    read in the height, width, profile & level of an avc (non-drm) track. If the the macroblocks are between 300 & 1200, return a non-zero number to allow the ipod
		uuid to be written

    NOTE: this requires the deep scan cli flag to break out stsd from its normal monolithic form
----------------------*/
uint16_t APar_TestVideoDescription(AtomicInfo* video_desc_atom, FILE* ISObmff_file) {
	uint16_t video_width = 0;
	uint16_t video_height = 0;
	uint16_t video_macroblocks = 0;
	uint8_t video_profile = 0;
	uint8_t video_level = 0;
	AtomicInfo* avcC_atom = NULL;

	if (ISObmff_file == NULL) return 0;

	char* avc1_contents = (char*)calloc(1, sizeof(char)* (size_t)video_desc_atom->AtomicLength);
	if (avc1_contents == NULL) {
		fclose(ISObmff_file);
		return 0;
	}

	APar_readX(avc1_contents, ISObmff_file, video_desc_atom->AtomicStart, video_desc_atom->AtomicLength); //actually reads in avcC as well, but is unused
	video_width = UInt16FromBigEndian(avc1_contents+32); // well, iTunes only allows 640 max but the avc wiki says it *could* go up to 720, so I won't bother to check it
	video_height = UInt16FromBigEndian(avc1_contents+34);
	video_macroblocks = (video_width / 16) * (video_height / 16);

	avcC_atom = APar_FindChildAtom(video_desc_atom->AtomicNumber, "avcC");
	if (avcC_atom != NULL) {
		uint64_t avcC_offset = avcC_atom->AtomicStart - video_desc_atom->AtomicStart;
		video_profile = *(avc1_contents+avcC_offset+9);
		video_level = *(avc1_contents+avcC_offset+11);
	}

	if (video_profile == 66 && video_level <= 30) {
		if (video_macroblocks > 300 && video_macroblocks <= 1200) {

			if (video_level <= 30 && avcC_atom != NULL) {
				avcC_atom->AtomicData = (char*)calloc(1, sizeof(char)* (size_t)avcC_atom->AtomicLength);
				APar_readX(avcC_atom->AtomicData, ISObmff_file, avcC_atom->AtomicStart+8, avcC_atom->AtomicLength-8);
				if (video_macroblocks > 396 && video_macroblocks <= 792) {
					*(avcC_atom->AtomicData + 3) = 21;
				} else if (video_macroblocks > 792) {
					*(avcC_atom->AtomicData + 3) = 22;
				}
			}

			fclose(ISObmff_file);
			return video_macroblocks;
		} else {
			fprintf(stdout, "AtomicParsley warning: the AVC track macroblocks were not in the required range (300-1200). Skipping.\n");
		}
	} else {
		fprintf(stdout, "AtomicParsley warning: the AVC track profile/level was too high. The ipod hi-res uuid was not added.\n");
	}

	fclose(ISObmff_file);
	return 0;
}

/*----------------------
APar_TestVideoDescription
	atom_path - pointer to the string containing the atom.path already targeted to the right track

		Find/Create the ipod hi-res (1200 macroblock) uuid for the avc1 track & set up its default parameters

    NOTE: this requires the deep scan cli flag to break out stsd from its normal monolithic form
----------------------*/
void APar_Generate_iPod_uuid(char* atom_path) {
	AtomicInfo* ipod_uuid_atom = NULL;

	ipod_uuid_atom = APar_FindAtom(atom_path, false, EXTENDED_ATOM, 0, true);
	if (ipod_uuid_atom == NULL) {
		ipod_uuid_atom = APar_FindAtom(atom_path, true, EXTENDED_ATOM, 0, true);
		if (ipod_uuid_atom == NULL) {
			fprintf(stdout, "An error occured trying to create the ipod uuid atom for the avc track\n");
			return;
		}
		ipod_uuid_atom->AtomicData = (char*)calloc(1, sizeof(char)* 60 );
		ipod_uuid_atom->AtomicContainerState = CHILD_ATOM;
		ipod_uuid_atom->AtomicClassification = EXTENDED_ATOM;
		ipod_uuid_atom->uuid_style = UUID_OTHER;
		ipod_uuid_atom->AtomicLength = 24;
		APar_Unified_atom_Put(ipod_uuid_atom, NULL, UTF8_iTunesStyle_Unlimited, 1, 32);
		modified_atoms = true;

		APar_FlagTrackHeader(ipod_uuid_atom);
		APar_FlagMovieHeader();

		track_codecs.has_avc1 = true; //only used on Mac OS X when setting the ipod uuid *only* (otherwise it gets set properly)
	} else {
		fprintf(stdout, "the ipod higher-resolution uuid is already present.\n");
	}

	return;
}

///////////////////////////////////////////////////////////////////////////////////////
//                            offset calculations                                    //
///////////////////////////////////////////////////////////////////////////////////////

//determine if our mdat atom has moved at all...
uint64_t APar_DetermineMediaData_AtomPosition() {
	uint64_t mdat_position = 0;
	short thisAtomNumber = 0;

	//loop through each atom in the struct array (which holds the offset info/data)
 	while (parsedAtoms[thisAtomNumber].NextAtomNumber != 0) {

		if ( strncmp(parsedAtoms[thisAtomNumber].AtomicName, "mdat", 4) == 0 && parsedAtoms[thisAtomNumber].AtomicLevel == 1 ) {
			if (parsedAtoms[thisAtomNumber].AtomicLength <= 1 || parsedAtoms[thisAtomNumber].AtomicLength > 75) {
				break;
			}
		} else if (parsedAtoms[thisAtomNumber].AtomicLevel == 1 && parsedAtoms[thisAtomNumber].AtomicLengthExtended == 0) {
			mdat_position +=parsedAtoms[thisAtomNumber].AtomicLength;
		} else {
			//part of the pseudo 64-bit support
			mdat_position +=parsedAtoms[thisAtomNumber].AtomicLengthExtended;
		}
		thisAtomNumber = parsedAtoms[thisAtomNumber].NextAtomNumber;
	}
	return mdat_position;
}

uint32_t APar_SimpleSumAtoms(short stop_atom) {
	uint32_t byte_sum = 0;
	//first, find the first mdat after this initial 'tfhd' atom to get the sum relative to that atom
	while (true) {
		if ( strncmp(parsedAtoms[stop_atom].AtomicName, "mdat", 4) == 0) {
			stop_atom--; //don't include the fragment's mdat, just the atoms prior to it
			break;
		} else {
			if (parsedAtoms[stop_atom].NextAtomNumber != 0) {
				stop_atom = parsedAtoms[stop_atom].NextAtomNumber;
			} else {
				break;
			}
		}
	}
	byte_sum += 8; //the 'tfhd' points to the byte in mdat where the fragment data is - NOT the atom itself (should always be +8bytes with a fragment)
	while (true) {
		if (parsedAtoms[stop_atom].AtomicLevel == 1) {
			byte_sum+= (parsedAtoms[stop_atom].AtomicLength == 1 ? parsedAtoms[stop_atom].AtomicLengthExtended : parsedAtoms[stop_atom].AtomicLength);
			//fprintf(stdout, "%i %s (%u)\n", stop_atom, parsedAtoms[stop_atom].AtomicName, parsedAtoms[stop_atom].AtomicLength);
		}
		if (stop_atom == 0) {
			break;
		} else {
			stop_atom = APar_FindPrecedingAtom(stop_atom);
		}
	}
	return byte_sum;
}

/*----------------------
APar_QuickSumAtomicLengths
	target_atom - pointer to the atom that will have its position within the *new* file determined; NOTE: only level 1 or level 2 atoms in moov

    fill
----------------------*/
uint64_t APar_QuickSumAtomicLengths(AtomicInfo* target_atom) {
	uint64_t atom_pos = 0;
	short atom_idx = 0;
	short current_level = 0;
	if (target_atom == NULL) return atom_pos;
	if (target_atom->AtomicLevel > 2) return atom_pos;

	atom_idx = APar_FindPrecedingAtom(target_atom->AtomicNumber);
	current_level = target_atom->AtomicLevel;

	while (true) {
		if (parsedAtoms[atom_idx].AtomicLevel <= target_atom->AtomicLevel) {
			if (parsedAtoms[atom_idx].AtomicContainerState >= DUAL_STATE_ATOM || parsedAtoms[atom_idx].AtomicLevel == 2) {
				atom_pos += (parsedAtoms[atom_idx].AtomicLength == 1 ? parsedAtoms[atom_idx].AtomicLengthExtended : parsedAtoms[atom_idx].AtomicLength);
			} else if (parsedAtoms[atom_idx].AtomicContainerState <= SIMPLE_PARENT_ATOM) {
				if (target_atom->AtomicLevel == 1) {
					atom_pos += (parsedAtoms[atom_idx].AtomicLength == 1 ? parsedAtoms[atom_idx].AtomicLengthExtended : parsedAtoms[atom_idx].AtomicLength);
				} else {
					atom_pos += 8;
				}
			}
		}
		if (atom_idx == 0) break;
		atom_idx = APar_FindPrecedingAtom(atom_idx);
	}
	return atom_pos;
}

/*----------------------
APar_Constituent_mdat_data
	desired_data_pos - the position in the file where the desired data begins
	desired_data_len - the length of the desired data contained within the file

    fill
----------------------*/
AtomicInfo* APar_Constituent_mdat_data(uint64_t desired_data_pos, uint64_t desired_data_len) {
	AtomicInfo* target_mdat = NULL;
	short eval_atom = 0;

 	while (parsedAtoms[eval_atom].NextAtomNumber != 0) {

		if ( memcmp(parsedAtoms[eval_atom].AtomicName, "mdat", 4) == 0 && parsedAtoms[eval_atom].AtomicLevel == 1 ) {
			if (parsedAtoms[eval_atom].AtomicLength == 1) {
				if ( (parsedAtoms[eval_atom].AtomicStart + parsedAtoms[eval_atom].AtomicLengthExtended >= desired_data_pos + desired_data_len) &&
				      parsedAtoms[eval_atom].AtomicStart < desired_data_pos) {
					target_mdat = &parsedAtoms[eval_atom];
					break;
				}
			} else {
				if ( (parsedAtoms[eval_atom].AtomicStart + parsedAtoms[eval_atom].AtomicLength >= desired_data_pos + desired_data_len) &&
				     parsedAtoms[eval_atom].AtomicStart < desired_data_pos) {
					target_mdat = &parsedAtoms[eval_atom];
					break;
				}
			}
		}
		eval_atom = parsedAtoms[eval_atom].NextAtomNumber;
	}
	return target_mdat;
}

/*----------------------
APar_Readjust_iloc_atom
	iloc_number - the target iloc atom index in parsedAtoms

    fill
----------------------*/
bool APar_Readjust_iloc_atom(short iloc_number) {
	bool iloc_changed = false;

	parsedAtoms[iloc_number].AtomicData = (char*)calloc(1, sizeof(char)* (size_t)(parsedAtoms[iloc_number].AtomicLength) );
	APar_readX(parsedAtoms[iloc_number].AtomicData, source_file, parsedAtoms[iloc_number].AtomicStart+12, parsedAtoms[iloc_number].AtomicLength-12);

	uint8_t offset_size = ( *parsedAtoms[iloc_number].AtomicData >> 4) & 0x0F;
	uint8_t length_size = *parsedAtoms[iloc_number].AtomicData & 0x0F;
	uint8_t base_offset_size = ( *(parsedAtoms[iloc_number].AtomicData+1) >> 4) & 0x0F;
	uint16_t item_count = UInt16FromBigEndian(parsedAtoms[iloc_number].AtomicData+2);
	uint64_t aggregate_offset = 4;
	char* base_offset_ptr = NULL;

#if defined(DEBUG_V)
	fprintf(stdout, "debug: AP_Readjust_iloc_atom   Offset %X, len %X, base %X, item count %u\n", offset_size, length_size, base_offset_size, item_count);
#endif

	for (uint16_t an_item=1; an_item <= item_count; an_item++) {
#ifdef DEBUG_V
		uint16_t an_item_ID =
#endif
		UInt16FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset);
		uint16_t a_data_ref_idx = UInt16FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset+2);
		uint64_t base_offset = 0;
		uint64_t curr_container_pos = 0;
		uint64_t extent_len_sum = 0;

		aggregate_offset +=4;

		if (a_data_ref_idx != 0) {
			continue;
		}

		if (base_offset_size == 4 || base_offset_size == 8) {
			base_offset_ptr = parsedAtoms[iloc_number].AtomicData+aggregate_offset;
			if (base_offset_size == 4) {
				base_offset = UInt32FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset);
				aggregate_offset +=4;
			} else {
				base_offset = UInt32FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset);
				aggregate_offset +=8;
			}
		}

		if (base_offset > 0) {
			uint16_t this_item_extent_count = UInt16FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset);
			aggregate_offset +=2;

			for (uint16_t an_extent=1; an_extent <= this_item_extent_count; an_extent++) {
				uint64_t this_extent_offset = 0;
				uint64_t this_extent_length = 0;

				if (offset_size == 4 || offset_size == 8) {
					if (offset_size == 4) {
						this_extent_offset = UInt32FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset);
						aggregate_offset +=4;
					} else {
						this_extent_offset = UInt32FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset);
						aggregate_offset +=8;
					}
				}
				if (length_size == 4 || length_size == 8) {
					if (length_size == 4) {
						this_extent_length = UInt32FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset);
						aggregate_offset +=4;
					} else {
						this_extent_length = UInt32FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset);
						aggregate_offset +=8;
					}
					extent_len_sum+= this_extent_length;
				}
			} //for loop extent

#if defined(DEBUG_V)
			fprintf(stdout, "debug: AP_Readjust_iloc_atom  iloc's %u index at base offset: %u, total bytes %u\n", an_item_ID, base_offset, extent_len_sum);
#endif
			AtomicInfo* container_atom = APar_Constituent_mdat_data(base_offset, 0x013077 );

			if (container_atom != NULL) {
				curr_container_pos = APar_QuickSumAtomicLengths(container_atom);
				uint64_t exisiting_offset_into_atom = base_offset - container_atom->AtomicStart;
				uint64_t new_item_offset = curr_container_pos + exisiting_offset_into_atom;

#if defined(DEBUG_V)
				fprintf(stdout, "debug: AP_Readjust_iloc_atom  item is contained on mdat started @ %u (now at %u)\n", container_atom->AtomicStart, curr_container_pos);
				fprintf(stdout, "debug: AP_Readjust_iloc_atom  item is %u bytes offset into atom (was %u, now %u)\n", exisiting_offset_into_atom, base_offset, new_item_offset);
#endif
				if (base_offset_size == 4) {
					UInt32_TO_String4(new_item_offset, base_offset_ptr);
				} else {
					UInt64_TO_String8(new_item_offset, base_offset_ptr);
				}
				iloc_changed = true;
			}
		}
	}

	return iloc_changed;
}

bool APar_Readjust_CO64_atom(uint64_t mdat_position, short co64_number) {
	bool co64_changed = false;

	if (parsedAtoms[co64_number].ancillary_data != 1) {
		return co64_changed;
	}

	parsedAtoms[co64_number].AtomicData = (char*)calloc(1, sizeof(char)* (size_t)(parsedAtoms[co64_number].AtomicLength) );
	APar_readX(parsedAtoms[co64_number].AtomicData, source_file, parsedAtoms[co64_number].AtomicStart+12, parsedAtoms[co64_number].AtomicLength-12);

	parsedAtoms[co64_number].AtomicVerFlags = 0;
	bool deduct = false;
	//readjust

	char* co64_entries = (char *)malloc(sizeof(char)*4 + 1);
	memset(co64_entries, 0, sizeof(char)*4 + 1);

	memcpy(co64_entries, parsedAtoms[co64_number].AtomicData, 4);
	uint32_t entries = UInt32FromBigEndian(co64_entries);

	char* a_64bit_entry = (char *)malloc(sizeof(char)*8 + 1);
	memset(a_64bit_entry, 0, sizeof(char)*8 + 1);

	for(uint32_t i=1; i<=entries; i++) {
		//read 8 bytes of the atom into a 8 char uint64_t a_64bit_entry to eval it
		for (int c = 0; c <=7; c++ ) {
			//first co64 entry (32-bit uint32_t) is the number of entries; every other one is an actual offset value
			a_64bit_entry[c] = parsedAtoms[co64_number].AtomicData[4 + (i-1)*8 + c];
		}
		uint64_t this_entry = UInt64FromBigEndian(a_64bit_entry);

		if (i == 1 && mdat_supplemental_offset == 0) { //for the first chunk, and only for the first *ever* entry, make the global mdat supplemental offset
			if (this_entry - removed_bytes_tally > mdat_position) {
				mdat_supplemental_offset = (uint64_t)mdat_position - ((uint64_t)this_entry - (uint64_t)removed_bytes_tally);
				bytes_into_mdat = this_entry - bytes_before_mdat - removed_bytes_tally;
				deduct=true;
			} else {
				mdat_supplemental_offset = mdat_position - (this_entry - removed_bytes_tally);
				bytes_into_mdat = this_entry - bytes_before_mdat - removed_bytes_tally;
			}

			if (mdat_supplemental_offset == 0) {
				break;
			}
		}

		if (mdat_supplemental_offset != 0) {
			co64_changed = true;
		}

		if (deduct) { //crap, uint32_t's were so nice to flip over by themselves to subtract nicely. going from 32-bit to 64-bit prevents that flipping
			this_entry += mdat_supplemental_offset - (bytes_into_mdat * -1); // + bytes_into_mdat;
		} else {
			this_entry += mdat_supplemental_offset + bytes_into_mdat; //this is where we add our new mdat offset difference
		}
		UInt64_TO_String8(this_entry, a_64bit_entry);
		//and put the data back into AtomicData...
		for (int d = 0; d <=7; d++ ) {
			//first stco entry is the number of entries; every other one is an actual offset value
			parsedAtoms[co64_number].AtomicData[4 + (i-1)*8 + d] = a_64bit_entry[d];
		}
	}

	free(a_64bit_entry);
	free(co64_entries);
	a_64bit_entry=NULL;
	co64_entries=NULL;
	//end readjustment
	return co64_changed;
}

bool APar_Readjust_TFHD_fragment_atom(uint64_t mdat_position, short tfhd_number) {
	static bool tfhd_changed = false;
	static bool determined_offset = false;
	static uint64_t base_offset = 0;

	parsedAtoms[tfhd_number].AtomicData = (char*)calloc(1, sizeof(char)* (size_t)(parsedAtoms[tfhd_number].AtomicLength) );
	APar_readX(parsedAtoms[tfhd_number].AtomicData, source_file, parsedAtoms[tfhd_number].AtomicStart+12, parsedAtoms[tfhd_number].AtomicLength-12);

	char* tfhd_atomFlags_scrap = (char *)malloc(sizeof(char)*10);
	memset(tfhd_atomFlags_scrap, 0, 10);
	//parsedAtoms[tfhd_number].AtomicVerFlags = APar_read32(tfhd_atomFlags_scrap, source_file, parsedAtoms[tfhd_number].AtomicStart+8);

	if (parsedAtoms[tfhd_number].AtomicVerFlags & 0x01) {
		// seems the atomflags suggest bitpacking, but the spec doesn't specify it;
		// if the 1st bit is set...
#if 0 /* not used */
		memset(tfhd_atomFlags_scrap, 0, 10);
		memcpy(tfhd_atomFlags_scrap, parsedAtoms[tfhd_number].AtomicData, 4);

		uint32_t track_ID = UInt32FromBigEndian(tfhd_atomFlags_scrap); //unused
#endif
		uint64_t tfhd_offset = UInt64FromBigEndian(parsedAtoms[tfhd_number].AtomicData +4);

		if (!determined_offset) {
			determined_offset = true;
			base_offset = APar_SimpleSumAtoms(tfhd_number) - tfhd_offset;
			if (base_offset != 0) {
				tfhd_changed = true;
			}
		}

		tfhd_offset += base_offset;
		UInt64_TO_String8(tfhd_offset, parsedAtoms[tfhd_number].AtomicData +4);
	}
	return tfhd_changed;
}

bool APar_Readjust_STCO_atom(uint64_t mdat_position, short stco_number) {
	bool stco_changed = false;

	if (parsedAtoms[stco_number].ancillary_data != 1) {
		return stco_changed;
	}

	parsedAtoms[stco_number].AtomicData = (char*)calloc(1, sizeof(char)* (size_t)(parsedAtoms[stco_number].AtomicLength) );
	APar_readX(parsedAtoms[stco_number].AtomicData, source_file, parsedAtoms[stco_number].AtomicStart+12, parsedAtoms[stco_number].AtomicLength-12);

	parsedAtoms[stco_number].AtomicVerFlags = 0;
	//readjust

	char* stco_entries = (char *)malloc(sizeof(char)*4 + 1);
	memset(stco_entries, 0, sizeof(char)*4 + 1);

	memcpy(stco_entries, parsedAtoms[stco_number].AtomicData, 4);
	uint32_t entries = UInt32FromBigEndian(stco_entries);

	char* an_entry = (char *)malloc(sizeof(char)*4 + 1);
	memset(an_entry, 0, sizeof(char)*4 + 1);

	for(uint32_t i=1; i<=entries; i++) {
		//read 4 bytes of the atom into a 4 char uint32_t an_entry to eval it
		for (int c = 0; c <=3; c++ ) {
			//first stco entry is the number of entries; every other one is an actual offset value
			an_entry[c] = parsedAtoms[stco_number].AtomicData[i*4 + c];
		}

		uint32_t this_entry = UInt32FromBigEndian(an_entry);

		if (i == 1 && mdat_supplemental_offset == 0) { //for the first chunk, and only for the first *ever* entry, make the global mdat supplemental offset

			mdat_supplemental_offset = (uint64_t)(mdat_position - (this_entry - removed_bytes_tally) );
			bytes_into_mdat = this_entry - bytes_before_mdat - removed_bytes_tally;

			if (mdat_supplemental_offset == 0) {
				break;
			}
		}

		if (mdat_supplemental_offset != 0) {
			stco_changed = true;
		}

		this_entry += mdat_supplemental_offset + bytes_into_mdat;
		UInt32_TO_String4(this_entry, an_entry);
		//and put the data back into AtomicData...
		for (int d = 0; d <=3; d++ ) {
			//first stco entry is the number of entries; every other one is an actual offset value
			parsedAtoms[stco_number].AtomicData[i*4 + d] = an_entry[d];
		}
	}

	free(an_entry);
	free(stco_entries);
	an_entry=NULL;
	stco_entries=NULL;
	//end readjustment
	return stco_changed;
}

///////////////////////////////////////////////////////////////////////////////////////
//                            Reorder / Padding                                      //
///////////////////////////////////////////////////////////////////////////////////////

/*----------------------
APar_CreatePadding
	padding_length - the new length of padding

    Create a 'free' atom at a pre-determined area of a given length & set that atom as the global padding store atom
----------------------*/
void APar_CreatePadding(uint64_t padding_length) {
	AtomicInfo* next_atom = &parsedAtoms[ parsedAtoms[dynUpd.consolidated_padding_insertion].NextAtomNumber ];
	if (padding_length > 2000 && next_atom->AtomicLevel > 1) {
		short padding_atom = APar_InterjectNewAtom("free", CHILD_ATOM, SIMPLE_ATOM, 2000, 0, 0,
		                       (next_atom->AtomicLevel == 1 ? 1 : parsedAtoms[dynUpd.consolidated_padding_insertion].AtomicLevel), dynUpd.consolidated_padding_insertion );
		dynUpd.padding_store = &parsedAtoms[padding_atom];

		if (dynUpd.first_mdat_atom != NULL && padding_length - 2000 >= 8) {
			short padding_res_atom = APar_InterjectNewAtom("free", CHILD_ATOM, SIMPLE_ATOM, padding_length - 2000, 0, 0, 1,
			                                                APar_FindPrecedingAtom(dynUpd.first_mdat_atom->AtomicNumber) );
			dynUpd.padding_resevoir = &parsedAtoms[padding_res_atom];
		}
	} else {
		short padding_atom = APar_InterjectNewAtom("free", CHILD_ATOM, SIMPLE_ATOM, padding_length, 0, 0,
		                       (next_atom->AtomicLevel == 1 ? 1 : parsedAtoms[dynUpd.consolidated_padding_insertion].AtomicLevel), dynUpd.consolidated_padding_insertion );
		dynUpd.padding_store = &parsedAtoms[padding_atom];
	}
	return;
}

/*----------------------
APar_AdjustPadding
	new_padding_length - the new length of padding

    Adjust the consolidated padding store atom to the new size - creating&splitting if necessary.
----------------------*/
void APar_AdjustPadding(uint64_t new_padding_length) {
	uint64_t avail_padding = 0;

	if ( (psp_brand || force_existing_hierarchy) && (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV) ) return;

	if (dynUpd.padding_store == NULL) {
		if (new_padding_length >= 8) {
			APar_CreatePadding(new_padding_length);
		} else {
			return;
		}
	}

	if (new_padding_length < 8) {
		APar_EliminateAtom(dynUpd.padding_store->AtomicNumber, dynUpd.padding_store->NextAtomNumber);
		dynUpd.updage_by_padding = false;
	}

	if (dynUpd.padding_store != NULL) avail_padding += dynUpd.padding_store->AtomicLength;
	if (dynUpd.padding_resevoir != NULL) avail_padding += dynUpd.padding_resevoir->AtomicLength;

	if ((new_padding_length > avail_padding && new_padding_length < 2000) || dynUpd.padding_store->AtomicLevel == 1 ) {
		free(dynUpd.padding_store->AtomicData);
		dynUpd.padding_store->AtomicData = (char*)calloc(1, sizeof(char)*new_padding_length );
		dynUpd.padding_store->AtomicLength = new_padding_length;
	} else {
		free(dynUpd.padding_store->AtomicData);
		dynUpd.padding_store->AtomicData = (char*)calloc(1, sizeof(char)*2007 );
		dynUpd.padding_store->AtomicLength = 2000;

		/* since new_padding_length is an unsigned value, if its value is less than
		 * 1992, subtraction of 2000 from it will result in "underflow".  That is
		 * what is causing the reported issue of atom detected larger than
		 * filesize.
		 */
		if (new_padding_length < 2008) {
			dynUpd.padding_store->AtomicLength = new_padding_length;
			return;
		}

		if (dynUpd.padding_resevoir == NULL && dynUpd.first_mdat_atom != NULL) {
			short pad_res_atom = APar_InterjectNewAtom("free", CHILD_ATOM, SIMPLE_ATOM, new_padding_length - 2000, 0, 0, 1,
			                                              APar_FindPrecedingAtom(dynUpd.first_mdat_atom->AtomicNumber) );
			dynUpd.padding_resevoir = &parsedAtoms[pad_res_atom];
			dynUpd.padding_resevoir->AtomicLength = new_padding_length - 2000;
		} else if (dynUpd.padding_resevoir != NULL) {
			free(dynUpd.padding_resevoir->AtomicData);
			dynUpd.padding_resevoir->AtomicData = (char*)calloc(1, sizeof(char)*(new_padding_length - 2000) );
			dynUpd.padding_resevoir->AtomicLength = new_padding_length - 2000;
		}
	}

	return;
}

/*----------------------
APar_PaddingAmount
	target_amount - the amount of padding that is requested
	limit_by_prefs - whether to limit the amount of padding to the environmental preferences

    When a file will completely rewritten, limit the amount of padding according to the environmental prefs: min & max with a default amount substitued when padding is
		found to fall outside those values. When a file will be updated by dynamic updating, the consolidated padding will be initially set to the amount that was present
		before any modifications to tags. This amount of padding will in all likelyhood change when the final determination of whether a dynamic update can occur.
----------------------*/
uint64_t APar_PaddingAmount(uint64_t target_amount, bool limit_by_prefs) {
	uint64_t padding_allowance = 0;
	if (limit_by_prefs) {
		if (pad_prefs.maximum_present_padding_size == 0) {
			return 0;
		} else if (target_amount >= pad_prefs.maximum_present_padding_size) {
			padding_allowance = pad_prefs.maximum_present_padding_size;
		} else if (target_amount <= pad_prefs.minimum_required_padding_size) {
			padding_allowance = pad_prefs.default_padding_size;
		} else {
			padding_allowance = target_amount;
		}
	} else {
		padding_allowance = target_amount;
	}
	if (padding_allowance < 8 ) return 0;
	if (!alter_original) return pad_prefs.default_padding_size;
	return padding_allowance;
}

/*----------------------
APar_DetermineDynamicUpdate

    Find where the first 'mdat' atom will eventually wind up in any new file. If this movement is within the bounds of padding prefs, allow a dynamic update. Adjust
		the amount of padding to accommodate the difference in positions (persuant to padding prefs). Otherwise prevent it, and make sure the default amount of padding
		exists for the file for a full rewrite.
----------------------*/
void APar_DetermineDynamicUpdate() {
	if (!force_existing_hierarchy && moov_atom_was_mooved)  {
		dynUpd.prevent_dynamic_update = true;
		return;
	}
	uint64_t mdat_pos = 0;
  //uint64_t moov_udta_pos = APar_QuickSumAtomicLengths(dynUpd.moov_udta_atom);
	//uint64_t moov_last_trak_pos = APar_QuickSumAtomicLengths(dynUpd.last_trak_child_atom);
	//uint64_t moov_meta_pos = APar_QuickSumAtomicLengths(dynUpd.moov_meta_atom);
	uint64_t moov_pos = APar_QuickSumAtomicLengths(dynUpd.moov_atom);
	uint64_t root_meta_pos = APar_QuickSumAtomicLengths(dynUpd.file_meta_atom);

	if (root_meta_pos > 0 && root_meta_pos != dynUpd.file_meta_atom->AtomicStart) {

	} else if (moov_pos > 0 && moov_pos != dynUpd.moov_atom->AtomicStart) { //this could reflect either a root meta being moved, or moov came after mdat & was rearranged
		dynUpd.initial_update_atom = &parsedAtoms[0];

	} else if (moov_pos > 0) {
		dynUpd.initial_update_atom = dynUpd.moov_atom;
	}


	if (dynUpd.first_mdat_atom == NULL && alter_original) {
		//work this out later

	} else if (dynUpd.first_mdat_atom != NULL) {
		mdat_pos = APar_QuickSumAtomicLengths(dynUpd.first_mdat_atom);
		if (mdat_pos >= dynUpd.first_mdat_atom->AtomicStart) {
			uint64_t offset_increase = mdat_pos - dynUpd.first_mdat_atom->AtomicStart;
			if (offset_increase > dynUpd.padding_bytes) {
				if ( (psp_brand || force_existing_hierarchy) && (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV) ) {
					dynUpd.updage_by_padding = true;
				} else {
					dynUpd.prevent_dynamic_update = true;
				}
			} else {
				uint64_t padding_remaining = dynUpd.padding_bytes - offset_increase;
				uint64_t padding_allowed = APar_PaddingAmount(padding_remaining, true);

				if (padding_remaining == padding_allowed) {
					if (alter_original) {
						dynUpd.updage_by_padding = true;
					}
					APar_AdjustPadding(padding_allowed);
				} else {
					dynUpd.updage_by_padding = false;
					APar_AdjustPadding(padding_allowed);
				}
			}

		} else {
			uint64_t padding_replenishment = dynUpd.first_mdat_atom->AtomicStart - mdat_pos;
			uint64_t padding_allowed = APar_PaddingAmount(padding_replenishment, true);

			if (padding_replenishment == padding_allowed) {
				if (alter_original) {
					dynUpd.updage_by_padding = true;
				}
				APar_AdjustPadding(padding_allowed);
			} else {
				dynUpd.updage_by_padding = false;
				APar_AdjustPadding(padding_allowed);
			}
		}
	}
	if (!dynUpd.updage_by_padding && dynUpd.padding_bytes < pad_prefs.default_padding_size) {
		APar_AdjustPadding(pad_prefs.default_padding_size); //if there is a full rewrite, add a default amount of padding
	}
	APar_DetermineAtomLengths();
	return;
}

/*----------------------
APar_ConsolidatePadding

    Work through all the atoms that were determined to be 'padding' in APar_FindPadding, sum up their lengths and remove them from the hieararchy. No bytes are added
		or removed from the file because the total length of the padding is reformed as a single consolidated 'free' atom. This free atom will either form after the iTunes
		handler or (probably) at level 1 (probably before mdat).
		When doing a dynamic update in situ, the length of this consolidated padding atom will change (later) if metadata is altered.
----------------------*/
void APar_ConsolidatePadding() {
	uint64_t bytes_o_padding = 0;
	if (dynUpd.consolidated_padding_insertion == 0) return;
	FreeAtomListing* padding_entry = dynUpd.first_padding_atom;

	while (true) {
		if (padding_entry == NULL) break;

		APar_EliminateAtom(padding_entry->free_atom->AtomicNumber, padding_entry->free_atom->NextAtomNumber);

		FreeAtomListing* next_entry = padding_entry->next_free_listing;
		free(padding_entry);
		padding_entry = next_entry;
	}

	bytes_o_padding = APar_PaddingAmount(dynUpd.padding_bytes, !alter_original);

	if (bytes_o_padding >= 8) {
		if ( !(psp_brand || force_existing_hierarchy) && (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV) ) {
			APar_CreatePadding(bytes_o_padding);
		}
	}
	return;
}

/*----------------------
APar_FindPadding
	listing_purposes_only - controls whether to just track free/skip atoms, or actually consolidate

    This is called before all the lengths of all atoms gets redetermined. The atoms at this point are already moved via optimization, and so the location of where
		to place padding can be ascertained. It is not known howevever where the ultimate positions of these landmark atoms will be - which can change according to padding
		prefs (which gets applied to all the padding in APar_ConsolidatePadding(). Any free/skip atoms found to exist between 'moov' & the first 'mdat' atom will be
		be considered padding (excepting anything under 'stsd' which will be monolithing during *setting* of metadata tags). Each padding atom will be noted. A place where
		padding can reside will also be found - the presence of iTunes tags will ultimately deposit all accumulated padding in in the iTunes hierarchy.
----------------------*/
void APar_FindPadding(bool listing_purposes_only) {
	AtomicInfo* DynamicUpdateRangeStart = NULL;
	AtomicInfo* DynamicUpdateRangeEnd = dynUpd.first_mdat_atom; //could be NULL (a missing 'mdat' would mean externally referenced media that would not be self-contained)
	short eval_atom = 0;

	if (dynUpd.moov_atom != NULL) {
		DynamicUpdateRangeStart = &parsedAtoms[dynUpd.moov_atom->NextAtomNumber];
	}
	if (DynamicUpdateRangeStart == NULL) return;

	eval_atom = DynamicUpdateRangeStart->AtomicNumber;
	while (true) {
		if (memcmp(parsedAtoms[eval_atom].AtomicName, "free", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "skip", 4) == 0) {
			if (dynUpd.first_padding_atom == NULL) {
				dynUpd.first_padding_atom = (FreeAtomListing*)malloc(sizeof(FreeAtomListing));
				dynUpd.first_padding_atom->free_atom = &parsedAtoms[eval_atom];
				dynUpd.first_padding_atom->next_free_listing = NULL;
			} else {
				FreeAtomListing* free_listing = (FreeAtomListing*)malloc(sizeof(FreeAtomListing));
				free_listing->free_atom = &parsedAtoms[eval_atom];
				free_listing->next_free_listing = NULL;
				if (dynUpd.first_padding_atom->next_free_listing == NULL) {
					dynUpd.first_padding_atom->next_free_listing = free_listing;
				} else if (dynUpd.last_padding_atom != NULL) {
					dynUpd.last_padding_atom->next_free_listing = free_listing;
				}
				dynUpd.last_padding_atom = free_listing;
			}
			dynUpd.padding_bytes += (parsedAtoms[eval_atom].AtomicLength == 1 ? parsedAtoms[eval_atom].AtomicLengthExtended : parsedAtoms[eval_atom].AtomicLength);
		}
		eval_atom = parsedAtoms[eval_atom].NextAtomNumber;
		if (eval_atom == 0) break;
		if (DynamicUpdateRangeEnd != NULL) {
			if (DynamicUpdateRangeEnd->AtomicNumber == eval_atom) break;
		}
	}

	//search for a suitable location where padding can accumulate
	if (dynUpd.moov_udta_atom != NULL) {
		if (dynUpd.iTunes_list_handler_atom == NULL) {
			dynUpd.iTunes_list_handler_atom = APar_FindAtom("moov.udta.meta.hdlr", false, VERSIONED_ATOM, 0);
			if (dynUpd.iTunes_list_handler_atom != NULL) {
				if (parsedAtoms[dynUpd.iTunes_list_handler_atom->NextAtomNumber].AtomicLevel >= dynUpd.iTunes_list_handler_atom->AtomicLevel) {
					dynUpd.consolidated_padding_insertion = dynUpd.iTunes_list_handler_atom->AtomicNumber;
				}
			}
		}
	}
	if (dynUpd.consolidated_padding_insertion == 0) {
		if (dynUpd.first_mdat_atom != NULL && !(dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV)) {
			dynUpd.consolidated_padding_insertion = APar_FindPrecedingAtom(dynUpd.first_mdat_atom->AtomicNumber);
		} else {
			dynUpd.consolidated_padding_insertion = APar_FindLastAtom();
			dynUpd.optimization_flags |= PADDING_AT_EOF;
		}
	}

	//if the place where padding will eventually wind up is just before a padding atom, that padding atom will be erased by APar_ConsolidatePadding when its
	//AtomicNumber becomes -1; so work back through the hierarchy for the first non-padding atom
	while (true) {
		if (memcmp(parsedAtoms[dynUpd.consolidated_padding_insertion].AtomicName, "free", 4) == 0 ||
	      memcmp(parsedAtoms[dynUpd.consolidated_padding_insertion].AtomicName, "skip", 4) == 0) {
			dynUpd.consolidated_padding_insertion = APar_FindPrecedingAtom(dynUpd.consolidated_padding_insertion);
		} else {
			break;
		}
	}
	return;
}

void APar_LocateAtomLandmarks() {
	short total_file_level_atoms = APar_ReturnChildrenAtoms (0, 0);
	short eval_atom = 0;

	dynUpd.optimization_flags = 0;
	dynUpd.padding_bytes = 0;                  //this *won't* get filled here; it is only tracked for the purposes of dynamic updating
	dynUpd.consolidated_padding_insertion = 0; //this will eventually hold the point where to insert a new atom that will accumulate padding

	dynUpd.last_trak_child_atom = NULL;
	dynUpd.moov_atom = NULL;
	dynUpd.moov_udta_atom = NULL;
	dynUpd.iTunes_list_handler_atom = NULL;    //this *won't* get filled here; it is only tracked for the purposes of dynamic updating
	dynUpd.moov_meta_atom = NULL;
	dynUpd.file_meta_atom = NULL;
	dynUpd.first_mdat_atom = NULL;
	dynUpd.first_movielevel_metadata_tagging_atom = NULL;
	dynUpd.initial_update_atom = NULL;
	dynUpd.first_otiose_freespace_atom = NULL;
	dynUpd.first_padding_atom = NULL;          //this *won't* get filled here; it is only tracked for the purposes of dynamic updating
	dynUpd.last_padding_atom = NULL;           //this *won't* get filled here; it is only tracked for the purposes of dynamic updating
	dynUpd.padding_store = NULL;               //this *won't* get filled here; it gets filled in APar_ConsolidatePadding
	dynUpd.padding_resevoir = NULL;

	//scan through all top level atoms; fragmented files won't be optimized
	for(uint8_t iii = 1; iii <= total_file_level_atoms; iii++) {
		eval_atom = APar_ReturnChildrenAtoms (0, iii);
		//fprintf(stdout, "file level children - %s\n", parsedAtoms[eval_atom].AtomicName);

		if ( memcmp(parsedAtoms[eval_atom].AtomicName, "moof", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "mfra", 4) == 0) {
			move_moov_atom = false; //moov reordering won't be occurring on fragmented files, but it should have moov first anyway (QuickTime does at least)
		}

		if ( memcmp(parsedAtoms[eval_atom].AtomicName, "mdat", 4) == 0 ) {
			if (dynUpd.first_mdat_atom == NULL) {
				dynUpd.first_mdat_atom = &parsedAtoms[eval_atom];
			}
		}

		if (dynUpd.first_otiose_freespace_atom == NULL) {
			if ( (memcmp(parsedAtoms[eval_atom].AtomicName, "free", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "skip", 4) == 0) &&
		        dynUpd.first_mdat_atom == NULL && dynUpd.moov_atom == NULL ) {
				dynUpd.first_otiose_freespace_atom = &parsedAtoms[eval_atom]; //the scourge of libmp4v2
			}
		}

		if ( memcmp(parsedAtoms[eval_atom].AtomicName, "moov", 4) == 0 ) {
			dynUpd.moov_atom = &parsedAtoms[eval_atom];
			if (dynUpd.first_mdat_atom != NULL) {
				dynUpd.optimization_flags |= MEDIADATA__PRECEDES__MOOV; //or mdat could be entirely missing as well; check later
			}
		}

		if ( memcmp(parsedAtoms[eval_atom].AtomicName, "meta", 4) == 0 ) {
			dynUpd.file_meta_atom = &parsedAtoms[eval_atom];
			if (dynUpd.moov_atom != NULL) {
				dynUpd.optimization_flags |= ROOT_META__PRECEDES__MOOV;
			}//meta before moov would require more rewrite of the portion of the file than I want to do
		} //but it wouldn't be all that difficult to accommodate rewriting everything from ftyp down to the first mdat, but currently its limited to last 'trak' child to mdat
	}
	return;
}

/*----------------------
APar_Optimize
	mdat_test_only - the only info desired (if true, when printing the tree) is to know whether mdat precedes moov (and nullifing the concept of padding)

		The visual:
		ftyp
		moov
		    trak
				trak
				trak
				udta
				    meta
						    hdlr
								free (functions as padding store when there are iTunes tags present)
								ilst
				meta
				    hdlr
		meta
		    hdlr
		free (functions as padding store when there are *no* iTunes tags present)
		mdat

----------------------*/
void APar_Optimize(bool mdat_test_only) {
	short last_child_of_moov = 0;
	short eval_atom = 0;

	APar_LocateAtomLandmarks();

	/* -----------move moov to precede any media data (mdat)--------- */
	if (move_moov_atom && (dynUpd.first_mdat_atom != NULL && (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV))) {    //first_mdat_atom > 0 && moov_atom > 0 && moov_atom > first_mdat_atom) {
		if (mdat_test_only) {
			moov_atom_was_mooved = true; //this is all the interesting info required (during APar_PrintAtomicTree)
			APar_FindPadding(mdat_test_only);
			return;
		} else {
			APar_MoveAtom(dynUpd.moov_atom->AtomicNumber, dynUpd.first_mdat_atom->AtomicNumber);
			moov_atom_was_mooved = true;
		}
	}

	/* -----------move a file/root level 'meta' to follow 'moov', but precede any media data(mdat)--------- */
	if (dynUpd.file_meta_atom != NULL && (dynUpd.optimization_flags & ROOT_META__PRECEDES__MOOV)) {
		last_child_of_moov = APar_FindLastChild_of_ParentAtom(dynUpd.moov_atom->AtomicNumber);
		APar_MoveAtom(dynUpd.file_meta_atom->AtomicNumber, parsedAtoms[last_child_of_moov].NextAtomNumber);
	}

	/* -----------optiizing the layout of movie-level atoms--------- */
	if (dynUpd.moov_atom != NULL) { //it can't be null, but just in case...
		uint8_t extra_atom_count = 0;
		AtomicInfo* last_trak_atom = NULL;
		short total_moov_child_atoms = APar_ReturnChildrenAtoms(dynUpd.moov_atom->AtomicNumber, 0);
		AtomicInfo** other_track_level_atom = (AtomicInfo**)malloc(total_moov_child_atoms * sizeof(AtomicInfo*));
		for (uint8_t xi = 0; xi < total_moov_child_atoms; xi++) {
			other_track_level_atom[xi] = NULL;
		}

		for(uint8_t moov_i = 1; moov_i <= total_moov_child_atoms; moov_i ++) {
			eval_atom = APar_ReturnChildrenAtoms(dynUpd.moov_atom->AtomicNumber, moov_i);
			if ( memcmp(parsedAtoms[eval_atom].AtomicName, "udta", 4) == 0 && parsedAtoms[eval_atom].AtomicLevel == 2) {
				dynUpd.moov_udta_atom = &parsedAtoms[eval_atom];
				if (dynUpd.first_movielevel_metadata_tagging_atom == NULL) dynUpd.first_movielevel_metadata_tagging_atom = &parsedAtoms[eval_atom];
			} else if ( memcmp(parsedAtoms[eval_atom].AtomicName, "meta", 4) == 0 && parsedAtoms[eval_atom].AtomicLevel == 2) {
				dynUpd.moov_meta_atom = &parsedAtoms[eval_atom];
				if (dynUpd.first_movielevel_metadata_tagging_atom == NULL) dynUpd.first_movielevel_metadata_tagging_atom = &parsedAtoms[eval_atom];
			} else if (memcmp(parsedAtoms[eval_atom].AtomicName, "trak", 4) == 0) {
				last_trak_atom = &parsedAtoms[eval_atom];
				if (dynUpd.first_movielevel_metadata_tagging_atom != NULL) {
					if (dynUpd.moov_meta_atom != NULL) dynUpd.optimization_flags |= MOOV_META__PRECEDES__TRACKS;
					else if (dynUpd.moov_udta_atom != NULL) dynUpd.optimization_flags |= MOOV_UDTA__PRECEDES__TRACKS;
				}
			} else if (!(memcmp(parsedAtoms[eval_atom].AtomicName, "free", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "skip", 4) == 0)) {
				if (dynUpd.moov_meta_atom != NULL || dynUpd.moov_udta_atom != NULL) {
					other_track_level_atom[extra_atom_count] = &parsedAtoms[eval_atom]; //anything else gets noted because it *follows* moov.meta or moov.udta and needs to precede it
					extra_atom_count++;
				}
			}
		}

		if (last_trak_atom != NULL) {
			short last_child_of_last_track = APar_FindLastChild_of_ParentAtom(last_trak_atom->AtomicNumber);
			if (last_child_of_last_track > 0) {
				dynUpd.last_trak_child_atom = &parsedAtoms[last_child_of_last_track];
			}
		}

		/* -----------moving extra movie-level atoms (![trak,free,skip,meta,udta]) to precede the first metadata tagging hierarchy (moov.meta or moov.udta)--------- */
		if (extra_atom_count > 0 && dynUpd.first_movielevel_metadata_tagging_atom != NULL) {
			for (uint8_t xxi = 0; xxi < extra_atom_count; xxi++) {
				APar_MoveAtom((*other_track_level_atom + xxi)->AtomicNumber, dynUpd.first_movielevel_metadata_tagging_atom->AtomicNumber);
			}
		}

		/* -----------moving udta or meta to follow the trak atom--------- */
		if (dynUpd.optimization_flags & MOOV_META__PRECEDES__TRACKS) {
			if (last_child_of_moov == 0) last_child_of_moov = APar_FindLastChild_of_ParentAtom(dynUpd.moov_atom->AtomicNumber);
			APar_MoveAtom(dynUpd.moov_meta_atom->AtomicNumber, parsedAtoms[last_child_of_moov].NextAtomNumber);
		} else if (dynUpd.optimization_flags & MOOV_UDTA__PRECEDES__TRACKS) {
			if (last_child_of_moov == 0) last_child_of_moov = APar_FindLastChild_of_ParentAtom(dynUpd.moov_atom->AtomicNumber);
			APar_MoveAtom(dynUpd.moov_udta_atom->AtomicNumber, parsedAtoms[last_child_of_moov].NextAtomNumber);
		}

		free(other_track_level_atom);
		other_track_level_atom = NULL;
	}

	if (mdat_test_only) {
		APar_FindPadding(mdat_test_only);
		return;
	}

	/* -----------delete free/skip atoms that precede media data or meta data--------- */
	if (dynUpd.first_otiose_freespace_atom != NULL && !alter_original) { //as a courtesty, for a full rewrite, eliminate L1 pre-mdat free/skip atoms
		AtomicInfo* free_space = dynUpd.first_otiose_freespace_atom;
		while (true) {
			if (free_space->AtomicLevel > 1) break;
			if (memcmp(free_space->AtomicName, "free", 4) != 0) break; //only get the consecutive 'free' space
			AtomicInfo* nextatom = &parsedAtoms[free_space->NextAtomNumber];
			APar_EliminateAtom(free_space->AtomicNumber, free_space->NextAtomNumber);
			free_space = nextatom;
		}
	}
	return;
}

///////////////////////////////////////////////////////////////////////////////////////
//                          Determine Atom Length                                    //
///////////////////////////////////////////////////////////////////////////////////////

/*----------------------
APar_DetermineNewFileLength

    Sum up level 1 atoms (excludes any extranous EOF null bytes that iTunes occasionally writes - or used to)
----------------------*/
void APar_DetermineNewFileLength() {
	new_file_size = 0;
	short thisAtomNumber = 0;
	while (true) {
		if (parsedAtoms[thisAtomNumber].AtomicLevel == 1) {
			if (parsedAtoms[thisAtomNumber].AtomicLengthExtended == 0) {
				//normal 32-bit number when AtomicLengthExtended == 0 (for run-o-the-mill mdat & mdat.length=0)
				new_file_size += parsedAtoms[thisAtomNumber].AtomicLength; //used in progressbar
			} else {
				//pseudo 64-bit mdat length
				new_file_size += parsedAtoms[thisAtomNumber].AtomicLengthExtended; //used in progressbar
			}
			if (parsedAtoms[thisAtomNumber].AtomicLength == 0) {
				new_file_size += file_size - parsedAtoms[thisAtomNumber].AtomicStart; //used in progressbar; mdat.length = 1
			}
		}
		if (parsedAtoms[thisAtomNumber].NextAtomNumber == 0) {
			break;
		}
		thisAtomNumber = parsedAtoms[thisAtomNumber].NextAtomNumber;
	}
	return;
}

/*----------------------
APar_DetermineAtomLengths

Working backwards from the last atom in the tree, for each parent atom, add the
lengths of all the children atoms. All atom lengths for all parent atoms are
recalculated from the lenghts of children atoms directly under the parent. Even
atoms in hieararchies that were not touched are recalculated. Accommodations
are made for certain dual-state atoms that are parents & contain
data/versioning - the other dual-state atoms do not need to be listed because
they exist within the stsd hierarchy which is only parsed when viewing the tree
(for setting tags, it remains monolithic).

----------------------*/
void APar_DetermineAtomLengths() {
	short rev_atom_loop = APar_FindLastAtom();
	//fprintf(stdout, "Last atom is named %s, num:%i\n", parsedAtoms[last_atom].AtomicName, parsedAtoms[last_atom].AtomicNumber);

	while (true) {
		short next_atom = 0;
		uint64_t atom_size = 0;
		short previous_atom = 0; //only gets used in testing for atom under stsd

		//fprintf(stdout, "current atom is named %s, num:%i\n", parsedAtoms[rev_atom_loop].AtomicName, parsedAtoms[rev_atom_loop].AtomicNumber);

		if (rev_atom_loop == 0) {
			break; //we seem to have hit the first atom
		} else {
			previous_atom = APar_FindPrecedingAtom(rev_atom_loop);
		}

		uint32_t _atom_ = UInt32FromBigEndian(parsedAtoms[rev_atom_loop].AtomicName);
		switch (_atom_) {
			//the enumerated atoms here are all of DUAL_STATE_ATOM type
			case 0x6D657461 : //'meta'
				atom_size += 12;
				break;

			case 0x73747364 : //'stsd'
				atom_size += 16;
				break;

			case 0x64726566 : //'dref'
				atom_size += 16;
				break;

			case 0x69696E66 : //'iinf'
				atom_size += 14;
				break;

			//accommodate parsing of atoms under stsd when required
			case 0x6D703473 : { //mp4s
				if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) {
					atom_size += 16;
				} else {
					atom_size += 8;
				}
				break;
			}
			case 0x73727470 :		//srtp
			case 0x72747020 : { //'rtp '
				if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) {
					atom_size += 24;
				} else {
					atom_size += 8;
				}
				break;
			}
			case 0x616C6163 :		//alac
			case 0x6D703461 :		//mp4a
			case 0x73616D72 :		//samr
			case 0x73617762 :		//sawb
			case 0x73617770 :		//sawp
			case 0x73657663 :		//sevc
			case 0x73716370 :		//sqcp
			case 0x73736D76 :		//ssmv
			case 0x64726D73 : { //drms
				if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) {
					atom_size += 36;
				} else {
					atom_size += 8;
				}
				break;
			}
			case 0x74783367  : { //tx3g
				if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) {
					atom_size += 46;
				} else {
					atom_size += 8;
				}
				break;
			}
			case 0x6D6A7032 :		//mjp2
			case 0x6D703476 :		//mp4v
			case 0x61766331 :		//avc1
			case 0x6A706567 :		//jpeg
			case 0x73323633 :		//s263
			case 0x64726D69 : { //drmi
				if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) {
					atom_size += 86;
				} else {
					atom_size += 8;
				}
				break;
			}

			default :
				if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM) {
					atom_size += parsedAtoms[rev_atom_loop].AtomicLength;
				} else {
					atom_size += 8; //all atoms have *at least* 4bytes length & 4 bytes name
				}
				break;
		}

		if (parsedAtoms[rev_atom_loop].NextAtomNumber != 0) {
			next_atom = parsedAtoms[rev_atom_loop].NextAtomNumber;
		}

		while (parsedAtoms[next_atom].AtomicLevel > parsedAtoms[rev_atom_loop].AtomicLevel) { // eval all child atoms....
			//fprintf(stdout, "\ttest child atom %s, level:%i (sum %u)\n", parsedAtoms[next_atom].AtomicName, parsedAtoms[next_atom].AtomicLevel, atom_size);
			if (parsedAtoms[rev_atom_loop].AtomicLevel == ( parsedAtoms[next_atom].AtomicLevel - 1) ) { // only child atoms 1 level down
				atom_size += parsedAtoms[next_atom].AtomicLength;
				//fprintf(stdout, "\t\teval child atom %s, level:%i (sum %u)\n", parsedAtoms[next_atom].AtomicName, parsedAtoms[next_atom].AtomicLevel, atom_size);
				//fprintf(stdout, "\t\teval %s's child atom %s, level:%i (sum %u, added %u)\n", parsedAtoms[previous_atom].AtomicName, parsedAtoms[next_atom].AtomicName, parsedAtoms[next_atom].AtomicLevel, atom_size, parsedAtoms[next_atom].AtomicLength);
			} else if (parsedAtoms[next_atom].AtomicLevel < parsedAtoms[rev_atom_loop].AtomicLevel) {
				break;
			}
			next_atom = parsedAtoms[next_atom].NextAtomNumber; //increment to eval next atom
			parsedAtoms[rev_atom_loop].AtomicLength = atom_size;
		}

		if (_atom_ == 0x75647461 && parsedAtoms[rev_atom_loop].AtomicLevel > parsedAtoms[ parsedAtoms[rev_atom_loop].NextAtomNumber ].AtomicLevel) { //udta with no child atoms; get here by erasing the last asset in a 3gp file, and it won't quite erase because udta thinks its the former AtomicLength
			parsedAtoms[rev_atom_loop].AtomicLength = 8;
		}
		if (_atom_ == 0x6D657461 && parsedAtoms[rev_atom_loop].AtomicLevel != parsedAtoms[ parsedAtoms[rev_atom_loop].NextAtomNumber ].AtomicLevel - 1) { //meta with no child atoms; get here by erasing the last existing uuid atom.
			parsedAtoms[rev_atom_loop].AtomicLength = 12;
		}
		if (_atom_ == 0x696C7374 && parsedAtoms[rev_atom_loop].AtomicLevel != parsedAtoms[ parsedAtoms[rev_atom_loop].NextAtomNumber ].AtomicLevel - 1) { //ilst with no child atoms; get here by erasing the last piece of iTunes style metadata
			parsedAtoms[rev_atom_loop].AtomicLength = 8;
		}

		rev_atom_loop = APar_FindPrecedingAtom(parsedAtoms[rev_atom_loop].AtomicNumber);

	}
	APar_DetermineNewFileLength();
	//APar_SimpleAtomPrintout();
	//APar_PrintAtomicTree();
	return;
}

///////////////////////////////////////////////////////////////////////////////////////
//                          Atom Writing Functions                                   //
///////////////////////////////////////////////////////////////////////////////////////

/*----------------------
APar_ValidateAtoms

    A gaggle of tests go on here - to TRY to make sure that files are not corrupted.

		1. because there is a limit to the number of atoms, test to make sure we haven't hit MAX_ATOMS (probably only likely on a 300MB fragmented file ever 2 secs)
		2. test that the atom name is at least 4 letters long. So far, only quicktime atoms have NULLs in their names.
		3. For files over 300k, make sure that no atom can present larger than the filesize (which would be bad); handy for when the file isn't parsed correctly
		4. Test to make sure 'mdat' is at file-level. That is the only place it should ever be.
		5. If its is a child atom that was set (and resides in memory), then its AtomicData should != NULL.
		6. (A crude) Test to see if 'trak' atoms have only a 'udta' child. If setting a copyright notice on a track at index built with some compilers faux 'trak's are made
		7. If the file shunk below 90% (after accounting for additions or removals), error out - something went awry.
----------------------*/
void APar_ValidateAtoms() {
	bool atom_name_with_4_characters = true;
	short iter = 0;
	uint64_t simple_tally = 0;
	uint8_t atom_ftyp_count = 0;
	uint16_t external_data = 0;

	//test1
	if (atom_number > MAX_ATOMS) {
		fprintf(stderr, "AtomicParsley error: amount of atoms exceeds internal limit. Aborting.\n");
		exit(1);
	}

	while (true) {
		//test2
		// there are valid atom names that are 0x00000001 - but I haven't seen them in MPEG-4 files, but they could show up, so this isn't a hard error
		if ( strlen(parsedAtoms[iter].AtomicName) < 4 && parsedAtoms[iter].AtomicClassification != EXTENDED_ATOM) {
			atom_name_with_4_characters = false;
		}

		//test3
		//test for atoms that are going to be greater than out current file size; problem is we could be adding a 1MB pix to a 200k 3gp file; only fail for a file > 300k file; otherwise there would have to be more checks (like artwork present, but a zealous tagger could make moov.lengt > filzesize)
		if (parsedAtoms[iter].AtomicLength > file_size && file_size > 300000) {
			if (parsedAtoms[iter].AtomicData == NULL) {
				fprintf(stderr, "AtomicParsley error: an atom was detected that presents as larger than filesize. Aborting. %c\n", '\a');
#if defined (_WIN32) && !defined (__CYGWIN__) /* apparently, long long is forbidden there*/
				fprintf(stderr, "atom %s is %I64u\n bytes long which is greater than the filesize of %I64u\n", parsedAtoms[iter].AtomicName, parsedAtoms[iter].AtomicLength, file_size);
#else
				fprintf(stderr, "atom %s is %" PRIu64 " bytes long which is greater than the filesize of %" PRIu64 "\n", parsedAtoms[iter].AtomicName, parsedAtoms[iter].AtomicLength, file_size);
#endif
				exit(1); //its conceivable to repair such an off length by the surrounding atoms constrained by file_size - just not anytime soon; probly would catch a foobar2000 0.9 tagged file
			}
		}

		if (parsedAtoms[iter].AtomicLevel == 1) {
			if (parsedAtoms[iter].AtomicLength == 0 && strncmp(parsedAtoms[iter].AtomicName, "mdat", 4) == 0) {
				simple_tally = file_size - parsedAtoms[iter].AtomicStart;
			} else {
				simple_tally += parsedAtoms[iter].AtomicLength == 1 ? parsedAtoms[iter].AtomicLengthExtended : parsedAtoms[iter].AtomicLength;
			}
		}

		//test4
		if (strncmp(parsedAtoms[iter].AtomicName, "mdat", 4) == 0 && parsedAtoms[iter].AtomicLevel != 1) {
			fprintf(stderr, "AtomicParsley error: mdat atom was found not at file level. Aborting. %c\n", '\a');
			exit(1); //the error which forced this was some bad atom length redetermination; probably won't be fixed
		}
		//test5
		if (parsedAtoms[iter].AtomicStart == 0 && parsedAtoms[iter].AtomicData == NULL &&
		    parsedAtoms[iter].AtomicNumber > 0 && parsedAtoms[iter].AtomicContainerState == CHILD_ATOM) {
			fprintf(stderr, "AtomicParsley error: a '%s' atom was rendered to NULL length. Aborting. %c\n", parsedAtoms[iter].AtomicName, '\a');
			exit(1); //data was not written to AtomicData for this new atom.
		}

		//test6
		if (memcmp(parsedAtoms[iter].AtomicName, "trak", 4) == 0 && parsedAtoms[iter+1].NextAtomNumber != 0) { //prevent writing any malformed tracks
			if (!(memcmp(parsedAtoms[ parsedAtoms[iter].NextAtomNumber ].AtomicName, "tkhd", 4) == 0 ||
			    memcmp(parsedAtoms[ parsedAtoms[iter].NextAtomNumber ].AtomicName, "tref", 4) == 0 ||
					memcmp(parsedAtoms[ parsedAtoms[iter].NextAtomNumber ].AtomicName, "mdia", 4) == 0 ||
					memcmp(parsedAtoms[ parsedAtoms[iter].NextAtomNumber ].AtomicName, "edts", 4) == 0 ||
					memcmp(parsedAtoms[ parsedAtoms[iter].NextAtomNumber ].AtomicName, "meta", 4) == 0 ||
					memcmp(parsedAtoms[ parsedAtoms[iter].NextAtomNumber ].AtomicName, "tapt", 4) == 0 )) {
				if (APar_ReturnChildrenAtoms(iter, 0) < 2) {
					//a 'trak' must contain 'tkhd' & 'mdia' at the very least
					fprintf(stderr, "AtomicParsley error: incorrect track structure containing atom %s. %c\n", parsedAtoms[ parsedAtoms[iter].NextAtomNumber ].AtomicName, '\a');
					exit(1);
				}
			}
		}

		if (memcmp(parsedAtoms[iter].AtomicName, "ftyp", 4) == 0) {
			atom_ftyp_count++;
		}

		if ( (memcmp(parsedAtoms[iter].AtomicName, "stco", 4) == 0 || memcmp(parsedAtoms[iter].AtomicName, "co64", 4) == 0) && parsedAtoms[iter].ancillary_data != 1) {
			external_data++;
		}
		iter=parsedAtoms[iter].NextAtomNumber;
		if (iter == 0) {
			break;
		}
	}

	//test7
	double perdiff = (double)((double)(simple_tally) * 100.0 / (double)(file_size-removed_bytes_tally) );
	int percentage_difference = (int)lroundf(perdiff);

	if (percentage_difference < 90 && file_size > 300000) { //only kick in when files are over 300k & 90% of the size
		fprintf(stderr, "AtomicParsley error: total existing atoms present as larger than filesize. Aborting. %c\n", '\a');
		//APar_PrintAtomicTree();
		fprintf(stdout, "%i %" PRIu64 "\n", percentage_difference, simple_tally);
		exit(1);
	}

	if (atom_ftyp_count != 1) {
		fprintf(stdout, "AtomicParsley error: unresolved looping of atoms. Aborting. %c\n", '\a');
		exit(1);
	}

	if (!atom_name_with_4_characters) {
		fprintf(stdout, "AtomicParsley warning: atom(s) were detected with atypical names containing NULLs\n");
	}

	if (external_data > 0) {
		fprintf(stdout, "AtomicParsley warning: externally referenced data found.");
	}

	return;
}

void APar_DeriveNewPath(const char *filePath, char* temp_path, int output_type, const char* file_kind, const char* forced_suffix, bool random_filename = true) {
	const char* suffix = NULL;
	const char* file_name = NULL;
	size_t file_name_len = 0;
	bool relative_path = false;

	if (forced_suffix == NULL) {
		suffix = strrchr(filePath, '.');
	} else {
		suffix = forced_suffix;
	}

	size_t filepath_len = strlen(filePath);
	size_t base_len = filepath_len-strlen(suffix);
	if (output_type >= 0) {
		memcpy(temp_path, filePath, base_len);
		memcpy(temp_path + base_len, file_kind, strlen(file_kind));

	} else if (output_type == -1) { //make the output file invisible by prefacing the filename with '.'
#if defined (_WIN32) && !defined (__CYGWIN__)
		memcpy(temp_path, filePath, base_len);
		memcpy(temp_path + base_len, file_kind, strlen(file_kind));
#else
		file_name = strrchr(filePath, '/');
		if (file_name != NULL) {
			file_name_len = strlen(file_name);
			memcpy(temp_path, filePath, filepath_len-file_name_len+1);
		} else {
			if( getcwd(temp_path, MAXPATHLEN) == NULL ) {
				printf("Error getting working directory: %s\n", strerror(errno));
				exit(1);
			}
			file_name = (char*)filePath;
			file_name_len = strlen(file_name);
			memcpy(temp_path + strlen(temp_path), "/", 1);
			relative_path = true;
		}
		memcpy(temp_path + strlen(temp_path), ".", 1);
		memcpy(temp_path + strlen(temp_path), (relative_path ? file_name : file_name+1), file_name_len - strlen(suffix) -1);
		memcpy(temp_path + strlen(temp_path), file_kind, strlen(file_kind));
#endif
	}

	if (random_filename) {
		char randstring[6];
		srand((int) time(NULL)); //Seeds rand()
		int randNum = rand()%100000;
		sprintf(randstring, "%i", randNum);
		memcpy(temp_path + strlen(temp_path), randstring, strlen(randstring));
	}

	if (forced_suffix_type == FORCE_M4B_TYPE) {
		memcpy(temp_path + strlen(temp_path), ".m4b", 4);
	} else {
		memcpy(temp_path + strlen(temp_path), suffix, strlen(suffix) );
	}
	return;
}

void APar_MetadataFileDump(const char* ISObasemediafile) {
	char* dump_file_name=(char*)malloc( sizeof(char)* (strlen(ISObasemediafile) +12 +1) );
	memset(dump_file_name, 0, sizeof(char)* (strlen(ISObasemediafile) +12 +1) );

	FILE* dump_file;
	AtomicInfo* userdata_atom = APar_FindAtom("moov.udta", false, SIMPLE_ATOM, 0);

	//make sure that the atom really exists
	if (userdata_atom != NULL) {
		char* dump_buffer=(char*)malloc( sizeof(char)* userdata_atom->AtomicLength +1 );
		memset(dump_buffer, 0, sizeof(char)* userdata_atom->AtomicLength +1 );

		APar_DeriveNewPath(ISObasemediafile, dump_file_name, 1, "-dump-", ".raw");
		dump_file = APar_OpenFile(dump_file_name, "wb");
		if (dump_file != NULL) {
			//body of atom writing here

			APar_readX(dump_buffer, source_file, userdata_atom->AtomicStart, (size_t)userdata_atom->AtomicLength);

			fwrite(dump_buffer, (size_t)userdata_atom->AtomicLength, 1, dump_file);
			fclose(dump_file);

			fprintf(stdout, " Metadata dumped to %s\n", dump_file_name);
		}
		free(dump_buffer);
		dump_buffer=NULL;

	} else {
		fprintf(stdout, "AtomicParsley error: no moov.udta atom was found to dump out to file.\n");
	}

	return;
}

void APar_UpdateModTime(AtomicInfo* container_header_atom) {
	container_header_atom->AtomicData = (char*)calloc(1, sizeof(char)* (size_t)container_header_atom->AtomicLength );
	APar_readX(container_header_atom->AtomicData, source_file, container_header_atom->AtomicStart+12, container_header_atom->AtomicLength-12);

	uint32_t current_time = APar_get_mpeg4_time();
	if ((container_header_atom->AtomicVerFlags & 0xFFFFFF) == 1) {
		UInt64_TO_String8(current_time, container_header_atom->AtomicData+8);
	} else {
		UInt32_TO_String4(current_time, container_header_atom->AtomicData+4);
	}
	return;
}

void APar_ShellProgressBar(uint64_t bytes_written) {
	if (dynUpd.updage_by_padding) {
		return;
	}
	static int update_count = 0;

	if (update_count++ < 5) {
		return;
	}
	update_count = 0;

	double dispprog = (double)bytes_written / (double)new_file_size
						* max_display_width;
	int display_progress = (int)lroundf(dispprog);
	double percomp = 100.0 * (double)bytes_written / (double)new_file_size;
	int percentage_complete = (int)lroundf(percomp);

	char *p = file_progress_buffer;
	strcpy(p, " Progress: ");
	p += strlen(p);

	memset(p, '=', display_progress);
	p += display_progress;

	sprintf(p, ">%3d%% ", percentage_complete);
	p += strlen(p);

	memset(p, '-', max_display_width - display_progress);
	p += max_display_width - display_progress;
	p[0] = '|';
	p[1] = '\0';

	fprintf(stdout, "%s\r", file_progress_buffer);
	fflush(stdout);
}

void APar_MergeTempFile(FILE* dest_file, FILE *src_file, uint64_t src_file_size, uint64_t dest_position, char* &buffer) {
	uint64_t file_pos = 0;
	while (file_pos <= src_file_size) {
		if (file_pos + max_buffer <= src_file_size ) {
			APar_readX(buffer, src_file, file_pos, max_buffer);

			//fseek(dest_file, dest_position + file_pos, SEEK_SET);
#if defined(_WIN32)
			fpos_t file_offset = dest_position + file_pos;
#elif defined(__GLIBC__)
			fpos_t file_offset = {0};
			file_offset.__pos = dest_position + file_pos;
#else
			off_t file_offset = dest_position + file_pos;
#endif
			fsetpos(dest_file, &file_offset);
			fwrite(buffer, max_buffer, 1, dest_file);
			file_pos += max_buffer;

		} else {
			APar_readX(buffer, src_file, file_pos, src_file_size - file_pos);
			//fprintf(stdout, "buff starts with %s\n", buffer+4);
#if defined(_WIN32)
			fpos_t file_offset = dest_position + file_pos;
#elif defined(__GLIBC__)
			fpos_t file_offset = {0};
			file_offset.__pos = dest_position + file_pos;
#else
			off_t file_offset = dest_position + file_pos;
#endif
			fsetpos(dest_file, &file_offset );
			fwrite(buffer, src_file_size - file_pos, 1, dest_file);
			file_pos += src_file_size - file_pos;
			break;
		}
	}
	if (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV) {
#if defined (_WIN32) && !defined (__CYGWIN__)
		fflush(dest_file);
		SetEndOfFile((HANDLE)_get_osfhandle(_fileno(dest_file)));
#else
		if(ftruncate(fileno(dest_file), src_file_size+dest_position) == -1) {
			perror("Failed to truncate file: ");
			exit(1);
		}
#endif
	}
	return;
}

#ifdef __linux__
/* use kernel provided zero-copy interface to improve throughput
 * around the data passthru portions of our operation; no sense
 * copying multiple GB of data around in memory if we can avoid it */
uint64_t splice_copy(int sfd, int ofd, uint64_t block_size,
	uint64_t src_offset, uint64_t dest_offset, uint64_t tally)
{
	int pfd[2];
	int res;
	uint64_t lim = LONG_MAX;
	loff_t spos = src_offset;
	loff_t dpos = dest_offset;
	long didread;
	long didwrite;
	uint64_t bytes_written = 0;

	res = pipe(pfd);
	if (res < 0) {
		return 0;
	}

	while (block_size) {
		long toread = MIN(block_size, lim);

		/* splice source data into pipe.
		 * This will typically be 64k at a time */
		didread = splice(sfd, &spos, pfd[1], NULL, toread,
			SPLICE_F_MORE|SPLICE_F_MOVE);

		if (didread <= 0) {
			if (errno == EINVAL || errno == ENOSYS) {
				/* splice is not supported by source */
				break;
			}
			fprintf(stderr, "splice(read): %ld of %lu (%s)\n",
				didread, toread, strerror(errno));
			break;
		}

		block_size -= didread;

		while (didread > 0) {
			/* splice from pipe into dest */
			didwrite = splice(pfd[0], NULL, ofd, &dpos, didread,
					SPLICE_F_MORE|SPLICE_F_MOVE);

			if (didwrite <= 0) {
				if (errno == EINVAL || errno == ENOSYS) {
					/* splice is not supported by dest */
					break;
				}
				fprintf(stderr, "splice(write): %ld of %lu (%s)\n",
						didwrite, didread, strerror(errno));
				break;
			}

			bytes_written += didwrite;
			didread -= didwrite;
		}
		APar_ShellProgressBar(tally + bytes_written);
	}

	close(pfd[0]);
	close(pfd[1]);
	return bytes_written;
}
#endif

uint64_t block_copy(FILE *source_file, FILE *out_file,
	char *&buffer,
	uint64_t tally, uint64_t block_size,
	uint64_t src_offset, uint64_t dest_offset)
{
	uint64_t toread = block_size;
	uint64_t bytes_written = 0;
	size_t didread;
	size_t didwrite;

#ifdef __linux__
	if (block_size > 65536) {
		fflush(out_file);

		bytes_written = splice_copy(fileno(source_file), fileno(out_file),
											block_size, src_offset, dest_offset, tally);

		if (bytes_written != 0) {
			return bytes_written;
		}
	}
#endif

	fseeko(source_file, src_offset, SEEK_SET);
	fseeko(out_file, dest_offset, SEEK_SET);

	while (toread) {
		char *bpos;

		didread = fread(buffer, 1, MIN(max_buffer, toread), source_file);
		if (didread == 0) {
			fprintf(stderr, "read: eof=%d err=%d %s\n",
					feof(source_file),
					ferror(source_file),
					strerror(errno));
			break;
		}
		toread -= didread;

		bpos = buffer;

		while (didread) {
			didwrite = fwrite(bpos, 1, didread, out_file);
			didread -= didwrite;
			bpos += didwrite;
			bytes_written += didwrite;

			APar_ShellProgressBar(tally + bytes_written);
		}
	}
	return bytes_written;
}

uint64_t APar_WriteAtomically(FILE* source_file, FILE* temp_file,
	bool from_file, char* &buffer, uint64_t bytes_written_tally,
	short this_atom)
{
	uint64_t bytes_written = 0;

	if (parsedAtoms[this_atom].AtomicLength > 1 && parsedAtoms[this_atom].AtomicLength < 8) { //prevents any spurious atoms from appearing
		return bytes_written;
	}

	//write the length of the atom first... taken from our tree in memory
	UInt32_TO_String4(parsedAtoms[this_atom].AtomicLength, twenty_byte_buffer);
	fseeko(temp_file, bytes_written_tally, SEEK_SET);
	fwrite(twenty_byte_buffer, 4, 1, temp_file);
	bytes_written += 4;

	//since we have already writen the length out to the file, it can be changed now with impunity
	if (parsedAtoms[this_atom].AtomicLength == 0) { //the spec says if an atom has a length of 0, it extends to EOF
		parsedAtoms[this_atom].AtomicLength = file_size - parsedAtoms[this_atom].AtomicLength;
	} else if (parsedAtoms[this_atom].AtomicLength == 1) {
		//part of the pseudo 64-bit support
		parsedAtoms[this_atom].AtomicLength = parsedAtoms[this_atom].AtomicLengthExtended;
	} else if (parsedAtoms[this_atom].AtomicContainerState == DUAL_STATE_ATOM) {
		if (memcmp(parsedAtoms[this_atom].AtomicName, "dref", 4) == 0) {
			parsedAtoms[this_atom].AtomicLength = 16;
		} else if (memcmp(parsedAtoms[this_atom].AtomicName, "iinf", 4) == 0) {
			parsedAtoms[this_atom].AtomicLength = 14;
		}
	}

	if (deep_atom_scan && parsedAtoms[this_atom].AtomicContainerState == DUAL_STATE_ATOM) {
		uint32_t atom_val = UInt32FromBigEndian(parsedAtoms[this_atom].AtomicName);
		if (atom_val == 0x73747364 ) { //stsd
			parsedAtoms[this_atom].AtomicLength = 16;
		} else if (atom_val == 0x6D703473 ) { //mp4s
			parsedAtoms[this_atom].AtomicLength = 16;

		} else if (atom_val == 0x73727470 ) { //srtp
			parsedAtoms[this_atom].AtomicLength = 24;
		} else if (atom_val == 0x72747020 && parsedAtoms[this_atom].AtomicLevel == 7) { //'rtp '
			parsedAtoms[this_atom].AtomicLength = 24;

		} else if (atom_val == 0x616C6163 && parsedAtoms[this_atom].AtomicLevel == 7) { //alac
			parsedAtoms[this_atom].AtomicLength = 36;
		} else if (atom_val == 0x6D703461 ) { //mp4a
			parsedAtoms[this_atom].AtomicLength = 36;
		} else if (atom_val == 0x73616D72 ) { //samr
			parsedAtoms[this_atom].AtomicLength = 36;
		} else if (atom_val == 0x73617762 ) { //sawb
			parsedAtoms[this_atom].AtomicLength = 36;
		} else if (atom_val == 0x73617770 ) { //sawp
			parsedAtoms[this_atom].AtomicLength = 36;
		} else if (atom_val == 0x73657663 ) { //sevc
			parsedAtoms[this_atom].AtomicLength = 36;
		} else if (atom_val == 0x73716370 ) { //sqcp
			parsedAtoms[this_atom].AtomicLength = 36;
		} else if (atom_val == 0x73736D76 ) { //ssmv
			parsedAtoms[this_atom].AtomicLength = 36;

		} else if (atom_val == 0x74783367 ) { //tx3g
			parsedAtoms[this_atom].AtomicLength = 46;

		} else if (atom_val == 0x6D6A7032 ) { //mjp2
			parsedAtoms[this_atom].AtomicLength = 86;
		} else if (atom_val == 0x6D703476 ) { //mp4v
			parsedAtoms[this_atom].AtomicLength = 86;
		} else if (atom_val == 0x61766331 ) { //avc1
			parsedAtoms[this_atom].AtomicLength = 86;
		} else if (atom_val == 0x6A706567 ) { //jpeg
			parsedAtoms[this_atom].AtomicLength = 86;
		} else if (atom_val == 0x73323633 ) { //s263
			parsedAtoms[this_atom].AtomicLength = 86;
		}
	}

	if (from_file) {
		// here we read in the original atom into the buffer. If the length is
		// greater than our buffer length, we loop, reading in chunks of the
		// atom's data into the buffer, and immediately write it out, reusing
		// the buffer.
		//
		bytes_written += block_copy(source_file, temp_file, buffer,
			bytes_written_tally,
			parsedAtoms[this_atom].AtomicLength - bytes_written,
			bytes_written + parsedAtoms[this_atom].AtomicStart,
			bytes_written_tally + bytes_written);

		return bytes_written;

	} else { // we are going to be writing not from the file, but directly from the tree (in memory).
		uint64_t atom_name_len = 4;

		//fprintf(stdout, "Writing atom %s from memory %u\n", parsedAtoms[this_atom].AtomicName, parsedAtoms[this_atom].AtomicClassification);
		fseeko(temp_file, bytes_written_tally + bytes_written, SEEK_SET);

		if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM) {
			fwrite("uuid", 4, 1, temp_file);
			atom_name_len = 16; //total of 20 bytes for a uuid atom
			//fprintf(stdout, "%u\n", parsedAtoms[this_atom].AtomicLength);
			if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[this_atom].uuid_style == UUID_OTHER) bytes_written += 4;
		}

		fwrite(parsedAtoms[this_atom].AtomicName, atom_name_len, 1, temp_file);
		bytes_written += atom_name_len;
		if (parsedAtoms[this_atom].AtomicClassification == VERSIONED_ATOM || parsedAtoms[this_atom].AtomicClassification == PACKED_LANG_ATOM) {
			UInt32_TO_String4(parsedAtoms[this_atom].AtomicVerFlags, twenty_byte_buffer);
			fwrite(twenty_byte_buffer, 4, 1, temp_file);
			bytes_written += 4;
		}

		uint64_t atom_data_size = 0;
		switch (parsedAtoms[this_atom].AtomicContainerState) {
			case PARENT_ATOM :
			case SIMPLE_PARENT_ATOM : {
				atom_data_size = 0;
				break;
			}
			case DUAL_STATE_ATOM : {
				switch ( UInt32FromBigEndian(parsedAtoms[this_atom].AtomicName) ) {
					case 0x6D657461 : { //meta
						break;
					}
					case 0x73747364 : { //stsd
						atom_data_size = parsedAtoms[this_atom].AtomicLength - 12;
						break;
					}
					case 0x73636869 : { //schi (code only executes when deep_atom_scan = true; otherwise schi is contained by the monolithic/unparsed 'stsd')
						atom_data_size = parsedAtoms[this_atom].AtomicLength - 12;
					}
				}
				break;
			}
			case UNKNOWN_ATOM_TYPE :
			case CHILD_ATOM : {
				if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[this_atom].uuid_style == UUID_AP_SHA1_NAMESPACE) {
					//4bytes length, 4 bytes 'uuid', 4bytes name, 4bytes NULL (AP writes its own uuid atoms - not those copied - iTunes style with atom versioning)
					atom_data_size = parsedAtoms[this_atom].AtomicLength - (16 + 12); //16 uuid; 16 = 4bytes * ('uuid', ap_uuid_name, verflag, 4 NULL bytes)
				} else if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[this_atom].uuid_style != UUID_DEPRECATED_FORM) {
					atom_data_size = parsedAtoms[this_atom].AtomicLength - (16 + 8);
				} else if (parsedAtoms[this_atom].AtomicClassification == VERSIONED_ATOM || parsedAtoms[this_atom].AtomicClassification == PACKED_LANG_ATOM) {
					//4bytes legnth, 4bytes name, 4bytes flag&versioning (language would be 2 bytes, but because its in different places, it gets stored as data)
					atom_data_size = parsedAtoms[this_atom].AtomicLength - 12;
				} else {
					//just 4bytes length, 4bytes name and then whatever data
					atom_data_size = parsedAtoms[this_atom].AtomicLength - 8;
				}
				break;
			}
		}

		if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[this_atom].uuid_style == UUID_AP_SHA1_NAMESPACE) {
			//AP writes uuid atoms much like iTunes style metadata; with version/flags to connote what type of data is being carried
		  //4bytes atom length, 4 bytes 'uuid', 16bytes uuidv5, 4bytes name of uuid in AP namespace, 4bytes versioning, 4bytes NULL, Xbytes data
			fwrite(parsedAtoms[this_atom].uuid_ap_atomname, 4, 1, temp_file);
			bytes_written += 4;

			UInt32_TO_String4(parsedAtoms[this_atom].AtomicVerFlags, twenty_byte_buffer);
			fwrite(twenty_byte_buffer, 4, 1, temp_file);
			bytes_written += 4;
		}

		if (atom_data_size > 0) {
			fwrite(parsedAtoms[this_atom].AtomicData, atom_data_size, 1, temp_file);
			bytes_written += atom_data_size;

			APar_ShellProgressBar(bytes_written_tally + bytes_written);
		}
	}
	return bytes_written;
}

/*----------------------
APar_copy_gapless_padding
	mp4file - destination file
	last_atom_pos - the last byte in the destination file that is contained by any atom (in parsedAtoms[] array)
	buffer - a buffer that will be used to set & write out from the NULLs used in gapless padding

    Add the discovered amount of already present gapless void padding at the end of the file (which is *not* contained by any atom at all) back into the destination
		file.

		Update: it would seem that this gapless void padding at the end of the file is not critical to gapless playback. In my 1 test of the thing, it seemed to work
		regardless of whether this NULL space was present or not, 'pgap' seemed to work. But, since Apple put it in for some reason, it will be left there unless explicity
		directed not to (via AP_PADDING). Although tying ordinary padding to this gapless padding may reduce flexibility - the assumption is that someone interested in
		squeezing out wasted space would want to eliminate this wasted space too (and so far, it does seem wasted).

		NOTE: Apple seems not to have seen this portion of the ISO 14496-12 Annex A, section A.2, para 6:
		"All the data within a conforming file is encapsulated in boxes (called atoms in predecessors of this file format). There is no data outside the box structure."
		And yet, Apple (donators of the file format) has caused iTunes to create non-conforming files with iTunes 7.x because of this NULL data outside of any box/atom
		structure.

----------------------*/
void APar_copy_gapless_padding(FILE* mp4file, uint64_t last_atom_pos, char* buffer) {
	uint64_t gapless_padding_bytes_written = 0;
	while (gapless_padding_bytes_written < gapless_void_padding) {
		if (gapless_padding_bytes_written + max_buffer <= gapless_void_padding ) {
			memset(buffer, 0, max_buffer);

			fseeko(mp4file, last_atom_pos + gapless_padding_bytes_written,
				SEEK_SET);
			fwrite(buffer, max_buffer, 1, mp4file);
			gapless_padding_bytes_written += max_buffer;

		} else { //less then 512k of gapless padding (here's hoping we get here always)
			memset(buffer, 0, gapless_void_padding - gapless_padding_bytes_written);

			fseeko(mp4file, last_atom_pos + gapless_padding_bytes_written, SEEK_SET);
			fwrite(buffer, gapless_void_padding - gapless_padding_bytes_written, 1, mp4file);
			gapless_padding_bytes_written += gapless_void_padding - gapless_padding_bytes_written;
			break;
		}
	}
}

void APar_WriteFile(const char* ISObasemediafile, const char* outfile, bool rewrite_original) {
	char* temp_file_name=(char*)calloc(1, sizeof(char)* 3500 );
	char* file_buffer=(char*)calloc(1, sizeof(char)* max_buffer + 1 );
	FILE* temp_file;
	uint64_t temp_file_bytes_written = 0;
	short thisAtomNumber = 0;
	char* originating_file = NULL;
	bool free_modified_name = false;

	APar_RenderAllID32Atoms();

	if (!(psp_brand || force_existing_hierarchy)) {
		APar_Optimize(false);
	} else {
		APar_LocateAtomLandmarks();
	}

	APar_FindPadding(false);
	APar_ConsolidatePadding();
	APar_DetermineAtomLengths();

	if (!complete_free_space_erasure) {
		APar_DetermineDynamicUpdate();
	}

	if (!rewrite_original || dynUpd.prevent_dynamic_update) {
		dynUpd.updage_by_padding = false;
	}

	APar_ValidateAtoms();

	//whatever atoms/space comes before mdat has to be added/removed before this point, or chunk offsets (in stco, co64, tfhd) won't be properly determined
	uint64_t mdat_position = APar_DetermineMediaData_AtomPosition();

	if (dynUpd.updage_by_padding) {
		APar_DeriveNewPath(ISObasemediafile, temp_file_name, 0, "-data-", NULL); //APar_DeriveNewPath(ISObasemediafile, temp_file_name, -1, "-data-", NULL);
		temp_file = APar_OpenFile(temp_file_name, "wb");
#if defined (_WIN32) && !defined (__CYGWIN__)
		char* invisi_command=(char*)malloc(sizeof(char)*2*MAXPATHLEN);
		sprintf (invisi_command,"ATTRIB +S +H \"%s\"",temp_file_name);

		if ( IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) {
			wchar_t* invisi_command_long = Convert_multibyteUTF8_to_wchar(invisi_command);

			_wsystem(invisi_command_long);

			free(invisi_command_long);
			invisi_command_long = NULL;
		} else {
			system(invisi_command);
		}
		free(invisi_command);
		invisi_command = NULL;
#endif

	} else if (!outfile) {
		APar_DeriveNewPath(ISObasemediafile, temp_file_name, 0, "-temp-", NULL);
		temp_file = APar_OpenFile(temp_file_name, "wb");

#if defined (DARWIN_PLATFORM)
		APar_SupplySelectiveTypeCreatorCodes(ISObasemediafile, temp_file_name, forced_suffix_type); //provide type/creator codes for ".mp4" for randomly named temp files
#endif

	} else {
		//case-sensitive compare means "The.m4a" is different from "THe.m4a"; on certiain Mac OS X filesystems a case-preservative but case-insensitive FS exists &
		//AP probably will have a problem there. Output to a uniquely named file as I'm not going to poll the OS for the type of FS employed on the target drive.
		if (strcmp(ISObasemediafile,outfile) == 0) {
			//er, nice try but you were trying to ouput to the exactly named file of the original. Y'all ain't so slick
			APar_DeriveNewPath(ISObasemediafile, temp_file_name, 0, "-temp-", NULL);
			temp_file = APar_OpenFile(temp_file_name, "wb");

#if defined (DARWIN_PLATFORM)
			APar_SupplySelectiveTypeCreatorCodes(ISObasemediafile, temp_file_name, forced_suffix_type); //provide type/creator codes for ".mp4" for a fall-through randomly named temp files
#endif

		} else {
			temp_file = APar_OpenFile(outfile, "wb");

#if defined (DARWIN_PLATFORM)
			APar_SupplySelectiveTypeCreatorCodes(ISObasemediafile, outfile, forced_suffix_type); //provide type/creator codes for ".mp4" for a user-defined output file
#endif

			}
	}

	// turn off buffering on the output, since we have a big buffer ourselves
	if (temp_file) {
		setbuf(temp_file, NULL);
	}
	setbuf(source_file, NULL);

	if (temp_file != NULL) { //body of atom writing here

		if (dynUpd.updage_by_padding) {
			thisAtomNumber = dynUpd.initial_update_atom->AtomicNumber;
			fprintf(stdout, "\n Updating metadata... ");
		} else {
			fprintf(stdout, "\n Started writing to %s.\n",
				outfile ? outfile : "temp file");
		}

		while (true) {

			AtomicInfo* thisAtom = &parsedAtoms[thisAtomNumber];
			if (thisAtom->AtomicNumber == -1) break;

			//the loop where the critical determination is made
			if (memcmp(thisAtom->AtomicName, "mdat", 4) == 0 && dynUpd.updage_by_padding) break;

			if (thisAtom->ancillary_data == 0x666C6167) { //'flag'
				APar_UpdateModTime(thisAtom);
			}

			if (memcmp(thisAtom->AtomicName, "stco", 4) == 0) {
				bool readjusted_stco = APar_Readjust_STCO_atom(mdat_position, thisAtomNumber);

				temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, !readjusted_stco, file_buffer, temp_file_bytes_written, thisAtomNumber);

			} else if (memcmp(thisAtom->AtomicName, "co64", 4) == 0) {
				bool readjusted_co64 = APar_Readjust_CO64_atom(mdat_position, thisAtomNumber);

				temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, !readjusted_co64, file_buffer, temp_file_bytes_written, thisAtomNumber);

			} else if (memcmp(thisAtom->AtomicName, "tfhd", 4) == 0) {
				bool readjusted_tfhd = APar_Readjust_TFHD_fragment_atom(mdat_position, thisAtomNumber);

				temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, !readjusted_tfhd, file_buffer, temp_file_bytes_written, thisAtomNumber);

			} else if (memcmp(thisAtom->AtomicName, "iloc", 4) == 0) {
				bool readjusted_iloc = APar_Readjust_iloc_atom(thisAtomNumber);

				temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, !readjusted_iloc, file_buffer, temp_file_bytes_written, thisAtomNumber);

			} else if (thisAtom->AtomicData != NULL || memcmp(thisAtom->AtomicName, "meta", 4) == 0) {
				temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, false, file_buffer, temp_file_bytes_written, thisAtomNumber);

			} else {
				//write out parent atoms (the standard kind that are only offset & name from the tree in memory (total: 8bytes)
				if ( thisAtom->AtomicContainerState <= SIMPLE_PARENT_ATOM ) {
					temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, false, file_buffer, temp_file_bytes_written, thisAtomNumber);
				//or its a child (they invariably contain some sort of data.
				} else {
					temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, true, file_buffer, temp_file_bytes_written, thisAtomNumber);
				}
			}
			if (thisAtom->NextAtomNumber == 0) { //if (parsedAtoms[thisAtomNumber].NextAtomNumber == 0) {
				//fprintf(stdout, "The loop is buh-rokin\n");
				break;
			}

			//prevent any looping back to atoms already written
			thisAtom->AtomicNumber = -1;
			thisAtomNumber = thisAtom->NextAtomNumber;
		}
		if (!dynUpd.updage_by_padding) {
			if (gapless_void_padding > 0 && pad_prefs.default_padding_size > 0) { //only when some sort of padding is wanted will the gapless null padding be copied
				APar_copy_gapless_padding(temp_file, temp_file_bytes_written, file_buffer);
			}
			fprintf(stdout, "\n Finished writing to %s.\n",
				outfile ? outfile : "temp file");
			fclose(temp_file);
		}

	} else {
		fprintf(stdout, "AtomicParsley error: an error occurred while trying to create a temp file.\n");
		exit(1);
	}

	if (dynUpd.updage_by_padding && rewrite_original) {
		fclose(temp_file);
		uint64_t metadata_len = findFileSize(temp_file_name);

		temp_file = APar_OpenFile(temp_file_name, "rb");
		fclose(source_file);
		source_file = APar_OpenFile(ISObasemediafile, "r+b");
		if (source_file == NULL) {
			fclose(temp_file);
			remove(temp_file_name);
			fprintf(stdout, "AtomicParsley error: the original file was no longer found.\nExiting.\n");
			exit(1);
		} else if (!(dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV) && metadata_len != (dynUpd.first_mdat_atom->AtomicStart - dynUpd.initial_update_atom->AtomicStart)) {
			fclose(temp_file);
			remove(temp_file_name);
			fprintf(stdout,
				"AtomicParsley error: the insufficient space to retag the source file (%" PRIu64 "!=%" PRIu64 ").\nExiting.\n", metadata_len, dynUpd.first_mdat_atom->AtomicStart - dynUpd.initial_update_atom->AtomicStart);
			exit(1);
		}

		APar_MergeTempFile(source_file, temp_file, temp_file_bytes_written, dynUpd.initial_update_atom->AtomicStart, file_buffer);

		fclose(source_file);
		fclose(temp_file);
		remove(temp_file_name);

	} else if (rewrite_original && !outfile) { //disable overWrite when writing out to a specifically named file; presumably the enumerated output file was meant to be the final destination
		fclose(source_file);

#if defined (_WIN32) && !defined (__CYGWIN__) /* native Windows requires removing the file first; rename() on POSIX does the removing automatically as needed */
		if ( IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) {
			wchar_t* utf16_filepath = Convert_multibyteUTF8_to_wchar(ISObasemediafile);

			_wremove(utf16_filepath);

			free(utf16_filepath);
			utf16_filepath = NULL;
		} else {
			remove(ISObasemediafile);
		}
#endif

		int err = 0;

		if (forced_suffix_type != NO_TYPE_FORCING) {
			originating_file = (char*)calloc( 1, sizeof(char)* 3500 );
			free_modified_name = true;
			if (forced_suffix_type == FORCE_M4B_TYPE) { //using --stik Audiobook with --overWrite will change the original file's extension
				uint16_t filename_len = strlen(ISObasemediafile);
				const char* suffix = strrchr(ISObasemediafile, '.');
				memcpy(originating_file, ISObasemediafile, filename_len+1 );
				memcpy(originating_file + (filename_len - strlen(suffix) ), ".m4b", 5 );
			}
		} else {
			originating_file = (char*)ISObasemediafile;
		}

#if defined (_WIN32) && !defined (__CYGWIN__)
		if ( IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) {
			wchar_t* utf16_filepath = Convert_multibyteUTF8_to_wchar(originating_file);
			wchar_t* temp_utf16_filepath = Convert_multibyteUTF8_to_wchar(temp_file_name);

			err = _wrename(temp_utf16_filepath, utf16_filepath);

			free(utf16_filepath);
			free(temp_utf16_filepath);
			utf16_filepath = NULL;
			temp_utf16_filepath = NULL;
		} else
#endif
		{
			err = rename(temp_file_name, originating_file);
		}

		if (err != 0) {
			switch (errno) {

				case ENAMETOOLONG: {
					fprintf (stdout, "Some or all of the orginal path was too long.");
					exit (-1);
				}
				case ENOENT: {
					fprintf (stdout, "Some part of the original path was missing.");
					exit (-1);
				}
				case EACCES: {
					fprintf (stdout, "Unable to write to a directory lacking write permission.");
					exit (-1);
				}
				case ENOSPC: {
					fprintf (stdout, "Out of space.");
					exit (-1);
				}
			}
		}
	}

	free(temp_file_name);
	if (free_modified_name) free(originating_file);
	temp_file_name=NULL;
	free(file_buffer);
	file_buffer = NULL;

	return;
}

// vim:ts=2:sw=2:noet: