Example #1
0
  public static void main(String[] args) {
    LogUtils.configure();

    FieldTypeMap ftm = StructDatabase.getInstance().getFieldTypeMap();
    Set<AssetFieldType> fieldNodes = new HashSet<>();
    Set<UnityVersion> versions = new TreeSet<>();
    Set<Integer> classIDs = new TreeSet<>();

    for (Map.Entry<Pair<Integer, UnityVersion>, AssetFieldType> entry : ftm.entrySet()) {
      versions.add(entry.getKey().getRight());
      classIDs.add(entry.getKey().getLeft());
      fieldNodes.add(entry.getValue());
    }

    L.log(Level.INFO, "Class IDs: {0}", classIDs.size());
    L.log(Level.INFO, "Versions: {0}", versions.size());
    L.log(Level.INFO, "Fields: {0}", fieldNodes.size());

    System.out.println();
    System.out.print("        |");

    for (Integer classID : classIDs) {
      System.out.print(StringUtils.leftPad(String.valueOf(classID), 4));
      System.out.print(" |");
    }

    System.out.println();
    System.out.print("--------|");
    System.out.print(StringUtils.repeat("-----|", classIDs.size()));
    System.out.println();

    for (UnityVersion rev : versions) {
      System.out.print(rev);
      System.out.print(" |");

      for (Integer classID : classIDs) {
        System.out.print("  ");
        if (ftm.containsKey(new Pair(classID, rev))) {
          System.out.print("x");
        } else {
          System.out.print(" ");
        }
        System.out.print("  |");
      }

      System.out.println();
    }

    System.out.println();

    for (Integer classID : classIDs) {
      String className = ClassID.getNameForID(classID);
      String classIDStr = StringUtils.rightPad(String.valueOf(classID), 3);
      if (className == null) {
        System.out.printf("%s : ???\n", classIDStr);
      } else {
        System.out.printf("%s : %s\n", classIDStr, className);
      }
    }
  }
Example #2
0
/**
 * Debug command that deserializes and writes copies of asset files for validation.
 *
 * @author Nico Bergemann <barracuda415 at yahoo.de>
 */
@Parameters(
    commandNames = "debug-asset-test",
    commandDescription = "Test asset file deserialization and writing.")
public class DebugAssetTest extends MultiFileCommand {

  private static final Logger L = LogUtils.getLogger();

  @Override
  public void handleFile(Path file) throws IOException {
    AssetFile asset = new AssetFile();
    asset.load(file);

    try {
      testDeserialize(asset);
    } catch (Throwable t) {
      L.log(
          Level.WARNING,
          "{0} failed deserialization test: {1}",
          new Object[] {asset.getSourceFile(), t.getMessage()});
    }

    try {
      testWrite(asset);
    } catch (Throwable t) {
      L.log(
          Level.WARNING,
          "{0} failed write test: {1}",
          new Object[] {asset.getSourceFile(), t.getMessage()});
    }
  }

  private void testDeserialize(AssetFile asset) throws IOException {
    for (ObjectData obj : asset.objects()) {
      obj.instance();
    }
  }

  private void testWrite(AssetFile asset) throws IOException {
    Path tmpFile = Files.createTempFile("disunity", null);

    try {
      asset.save(tmpFile);

      if (!FileUtils.contentEquals(asset.getSourceFile().toFile(), tmpFile.toFile())) {
        throw new IOException("Files are not equal");
      }
    } finally {
      Files.deleteIfExists(tmpFile);
    }
  }
}
Example #3
0
/**
 * Extractor for asset files.
 *
 * @author Nico Bergemann <barracuda415 at yahoo.de>
 */
public class AssetExtractor {

  private static final Logger L = LogUtils.getLogger();

  /**
   * Tries to get the name of an object by deserializing it and looking for the field "m_Name". If
   * it exists, return its value.
   *
   * @param asset asset file
   * @param path object path
   * @return Name string of the object or null if it doesn't have a name or if the deserialization
   *     failed.
   */
  public static String getObjectName(AssetFile asset, ObjectPath path) {
    Deserializer deser = new Deserializer(asset);
    String name = null;

    try {
      UnityObject obj = deser.deserialize(path);
      name = obj.getValue("m_Name");
    } catch (OutOfMemoryError ex) {
      // Deserializer choked on an array size and clogged the heap, try
      // to clean up this mess
      deser = null;
      System.gc();
    } catch (Throwable ex) {
    }

    return name;
  }

  private final AssetFile asset;
  private final Map<String, AssetExtractHandler> extractHandlerMap = new HashMap<>();
  private ClassFilter cf;
  private Path outputDir;

  public AssetExtractor(AssetFile asset) {
    this.asset = asset;

    addHandler("AudioClip", new AudioClipHandler());
    addHandler("Shader", new TextAssetHandler("shader"));
    addHandler("SubstanceArchive", new SubstanceArchiveHandler());
    addHandler("Texture2D", new Texture2DHandler());
    addHandler("Cubemap", new Texture2DHandler());
    addHandler("Font", new FontHandler());
    addHandler("TextAsset", new TextAssetHandler("txt"));
    addHandler("MovieTexture", new MovieTextureHandler());
    addHandler("Mesh", new MeshHandler());
  }

  public final void addHandler(String className, AssetExtractHandler handler) {
    handler.setClassName(className);
    extractHandlerMap.put(className, handler);
  }

  public final AssetExtractHandler getHandler(String className) {
    return extractHandlerMap.get(className);
  }

  public final void clearHandlers() {
    extractHandlerMap.clear();
  }

  public Path getOutputDir() {
    return outputDir;
  }

  public void setOutputDir(Path outputDir) {
    this.outputDir = outputDir;
  }

  public ClassFilter getClassFilter() {
    return cf;
  }

  public void setClassFilter(ClassFilter cf) {
    this.cf = cf;
  }

  public void extract(boolean raw) throws IOException {
    List<ObjectPath> paths = asset.getPaths();
    Deserializer deser = new Deserializer(asset);

    for (AssetExtractHandler extractHandler : extractHandlerMap.values()) {
      extractHandler.setAssetFile(asset);
      extractHandler.setOutputDir(outputDir);
    }

    for (ObjectPath path : paths) {
      // skip filtered classes
      if (cf != null && !cf.accept(path)) {
        continue;
      }

      String className = ClassID.getNameForID(path.getClassID(), true);

      // write just the serialized object data or parsed and extracted content?
      if (raw) {
        String assetFileName = String.format("%06d.bin", path.getPathID());
        Path classDir = outputDir.resolve(className);
        if (Files.notExists(classDir)) {
          Files.createDirectories(classDir);
        }

        Path assetFile = classDir.resolve(assetFileName);

        L.log(Level.INFO, "Writing {0} {1}", new Object[] {className, assetFileName});

        ByteBuffer bbAsset = asset.getPathBuffer(path);

        try {
          ByteBufferUtils.save(assetFile, bbAsset);
        } catch (Exception ex) {
          L.log(Level.WARNING, "Can't write " + path + " to " + assetFile, ex);
        }
      } else {
        AssetExtractHandler handler = getHandler(className);

        if (handler != null) {
          UnityObject obj;

          try {
            obj = deser.deserialize(path);
          } catch (Exception ex) {
            L.log(Level.WARNING, "Can't deserialize " + path, ex);
            continue;
          }

          try {
            handler.setObjectPath(path);
            handler.extract(obj);
          } catch (Exception ex) {
            L.log(Level.WARNING, "Can't extract " + path, ex);
          }
        }
      }
    }
  }

  public void split() throws IOException {
    List<ObjectPath> pathTable = asset.getPaths();
    TypeTree typeTree = asset.getTypeTree();

    // assets with just one object can't be split any further
    if (pathTable.size() == 1) {
      L.warning("Asset doesn't contain sub-assets!");
      return;
    }

    for (ObjectPath path : pathTable) {
      // skip filtered classes
      if (cf != null && !cf.accept(path)) {
        continue;
      }

      String className = ClassID.getNameForID(path.getClassID(), true);

      AssetFile subAsset = new AssetFile();
      subAsset.getHeader().setFormat(asset.getHeader().getFormat());

      ObjectPath subFieldPath = new ObjectPath();
      subFieldPath.setClassID1(path.getClassID1());
      subFieldPath.setClassID2(path.getClassID2());
      subFieldPath.setLength(path.getLength());
      subFieldPath.setOffset(0);
      subFieldPath.setPathID(1);
      subAsset.getPaths().add(subFieldPath);

      TypeTree subTypeTree = subAsset.getTypeTree();
      subTypeTree.setEngineVersion(typeTree.getEngineVersion());
      subTypeTree.setVersion(-2);
      subTypeTree.setFormat(typeTree.getFormat());
      subTypeTree.getFields().put(path.getClassID(), typeTree.getFields().get(path.getClassID()));

      subAsset.setDataBuffer(asset.getPathBuffer(path));

      Path subAssetDir = outputDir.resolve(className);
      if (Files.notExists(subAssetDir)) {
        Files.createDirectories(subAssetDir);
      }

      // probe asset name
      String subAssetName = getObjectName(asset, path);
      if (subAssetName != null) {
        // remove any chars that could cause troubles on various file systems
        subAssetName = FilenameSanitizer.sanitizeName(subAssetName);
      } else {
        // use numeric names
        subAssetName = String.format("%06d", path.getPathID());
      }
      subAssetName += ".asset";

      Path subAssetFile = subAssetDir.resolve(subAssetName);
      if (Files.notExists(subAssetFile)) {
        L.log(Level.INFO, "Writing {0}", subAssetFile);
        subAsset.save(subAssetFile);
      }
    }
  }
}
/** @author Nico Bergemann <barracuda415 at yahoo.de> */
public class FixReferencesAction extends Action {

  private static final Logger L = LogUtils.getLogger();

  @Override
  public boolean supportsAssets() {
    return true;
  }

  @Override
  public boolean supportsAssetBundes() {
    return false;
  }

  @Override
  public void processAsset(AssetFile asset) throws IOException {
    Path assetFile = asset.getSourceFile();
    Path assetDir = assetFile.getParent();
    String assetFileName = assetFile.getFileName().toString();
    String assetPath;

    if (assetDir == null) {
      assetPath = "";
    } else {
      assetPath = assetDir.toAbsolutePath().toString();
      assetPath = FilenameUtils.separatorsToUnix(assetPath) + "/";
    }

    // fix path for all assets with (shared)assets extension
    boolean changed = false;
    for (AssetRef ref : asset.getReferences()) {
      Path refFile = Paths.get(ref.getFilePath());
      String refExt = FilenameUtils.getExtension(refFile.getFileName().toString());
      if (refExt.endsWith("assets") && Files.notExists(refFile)) {
        String filePathOld = ref.getFilePath();
        String filePathNew = assetPath + FilenameUtils.getName(ref.getFilePath());
        Path refFileNew = Paths.get(filePathNew);

        if (Files.exists(refFileNew)) {
          L.log(Level.FINE, "Fixed reference: {0} -> {1}", new Object[] {filePathOld, filePathNew});
          ref.setFilePath(filePathNew);
          changed = true;
        } else {
          L.log(Level.FINE, "Fixed reference not found: {0}", refFileNew);
        }
      }
    }

    if (!changed) {
      L.fine("No references changed, skipping saving");
      return;
    }

    // create backup by renaming the original file
    Path assetFileBackup = assetFile.resolveSibling(assetFileName + ".bak");
    Files.move(assetFile, assetFileBackup, StandardCopyOption.REPLACE_EXISTING);

    // save asset
    asset.save(assetFile);
  }
}