private static File syncAssets(File assetDir, String indexName) throws JsonSyntaxException, JsonIOException, IOException {
        Logger.logInfo("Syncing Assets:");
        final File objects = new File(assetDir, "objects");
        AssetIndex index = JsonFactory.loadAssetIndex(new File(assetDir, "indexes/{INDEX}.json".replace("{INDEX}", indexName)));

        if (!index.virtual)
            return assetDir;

        final File targetDir = new File(assetDir, "virtual/" + indexName);

        final ConcurrentSkipListSet<File> old = new ConcurrentSkipListSet();
        old.addAll(FTBFileUtils.listFiles(targetDir));

        Benchmark.reset("threading");
        Parallel.TaskHandler th = new Parallel.ForEach(index.objects.entrySet())
            .withFixedThreads(2*OSUtils.getNumCores())
            //.configurePoolSize(2*2*OSUtils.getNumCores(), 10)
            .apply( new Parallel.F<Entry<String, Asset>, Void>() {
                public Void apply(Entry<String, Asset> e) {
                    Asset asset = e.getValue();
                    File local = new File(targetDir, e.getKey());
                    File object = new File(objects, asset.hash.substring(0, 2) + "/" + asset.hash);

                    old.remove(local);

                    try {
                        if (local.exists() && !DownloadUtils.fileSHA(local).equals(asset.hash)) {
                            Logger.logInfo("  Changed: " + e.getKey());
                            FTBFileUtils.copyFile(object, local, true);
                        } else if (!local.exists()) {
                            Logger.logInfo("  Added: " + e.getKey());
                            FTBFileUtils.copyFile(object, local);
                        }
                        } catch (Exception ex) {
                        Logger.logError("Asset checking failed: ", ex);
                    }
                    return null;
                }
            });
        try {
            th.shutdown();
            th.wait(60, TimeUnit.SECONDS);
        } catch (Exception ex) {
            Logger.logError("Asset checking failed: ", ex);
        }
        Benchmark.logBenchAs("threading", "parallel asset(virtual) check");

        for (File f : old) {
            String name = f.getAbsolutePath().replace(targetDir.getAbsolutePath(), "");
            Logger.logInfo("  Removed: " + name.substring(1));
            f.delete();
        }

        return targetDir;
    }