diff --git a/.idea/runConfigurations/COF_Finder.xml b/.idea/runConfigurations/COF_Finder.xml
new file mode 100644
index 00000000..57c85f9e
--- /dev/null
+++ b/.idea/runConfigurations/COF_Finder.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/src/com/riiablo/codec/CofFinder.java b/tools/src/com/riiablo/codec/CofFinder.java
new file mode 100644
index 00000000..dec8b39c
--- /dev/null
+++ b/tools/src/com/riiablo/codec/CofFinder.java
@@ -0,0 +1,117 @@
+package com.riiablo.codec;
+
+import com.badlogic.gdx.Application;
+import com.badlogic.gdx.ApplicationAdapter;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.assets.AssetManager;
+import com.badlogic.gdx.backends.headless.HeadlessApplication;
+import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;
+import com.badlogic.gdx.files.FileHandle;
+import com.badlogic.gdx.utils.Array;
+import com.riiablo.Riiablo;
+import com.riiablo.mpq.MPQFileHandleResolver;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+public class CofFinder extends ApplicationAdapter {
+ private static final String TAG = "CofFinder";
+
+ public static void main(String[] args) {
+ HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
+ new HeadlessApplication(new CofFinder(args), config);
+ }
+
+ String[] cofs;
+
+ CofFinder(String[] args) {
+ Riiablo.home = new FileHandle(args[0]);
+ if (args.length > 1) {
+ this.cofs = Arrays.copyOfRange(args, 1, args.length);
+ }
+ }
+
+ @Override
+ public void create() {
+ Gdx.app.setLogLevel(Application.LOG_DEBUG);
+
+ Riiablo.home = Gdx.files.absolute(Riiablo.home.path());
+ Riiablo.assets = new AssetManager();
+ Riiablo.mpqs = new MPQFileHandleResolver();
+
+ String[] cofd2Names = {
+ "chars_cof", "cmncof_a1", "cmncof_a2", "cmncof_a3",
+ "cmncof_a4", "cmncof_a5", "cmncof_a6", "cmncof_a7",
+ };
+ Array> cofd2s = new Array<>();
+ for (String cofd2Name : cofd2Names) {
+ String path = "data\\global\\" + cofd2Name + ".d2";
+ FileHandle handle = Riiablo.mpqs.resolve(path);
+ COFD2 cofd2 = COFD2.loadFromFile(handle);
+ cofd2s.add(Pair.of(cofd2Name, cofd2));
+ }
+
+ String[] cofDirs = { "chars", "objects", "monsters" };
+
+ if (cofs == null) {
+ for (Pair pair : cofd2s) {
+ dump(pair.getValue(), pair.getKey());
+ }
+ } else {
+ for (String cof : cofs) {
+ Gdx.app.log(TAG, cof);
+ int i = 0;
+ for (Pair pair : cofd2s) {
+ if (lookup(cof, pair.getValue(), pair.getKey()) != null) i++;
+ }
+
+ for (String cofDir : cofDirs) {
+ if (lookup(cof, "data\\global\\" + cofDir + "\\") != null) i++;
+ }
+ if (i == 0) Gdx.app.log(TAG, cof + " was not found in any file!");
+ }
+ }
+
+ Gdx.app.exit();
+ }
+
+ private void dump(COFD2 lib, String libName) {
+ if (lib == null) return;
+ FileHandle handle = Gdx.files.local(libName + ".tmp");
+ Gdx.app.log(TAG, "Writing " + lib.getNumEntries() + " to " + handle);
+ PrintWriter writer = null;
+ try {
+ writer = new PrintWriter(handle.write(false));
+ for (COFD2.Entry entry : lib.entries) {
+ writer.println(entry.cofName + '\t' + entry.cofSize + '\t' + entry.cof.toString());
+ }
+ } catch (Throwable t) {
+ Gdx.app.error(TAG, t.getMessage(), t);
+ } finally {
+ IOUtils.closeQuietly(writer);
+ }
+ }
+
+ private COF lookup(String cof, COFD2 lib, String libName) {
+ if (lib == null) return null;
+ COF resolved = lib.lookup(cof);
+ if (resolved != null) Gdx.app.log(TAG, " " + libName + " : " + resolved);
+ return resolved;
+ }
+
+ private COF lookup(String cof, String path) {
+ String fileName = cof.substring(0, 2) + "\\cof\\" + cof + ".cof";
+ FileHandle handle = Riiablo.mpqs.resolve(path + fileName);
+ COF resolved = handle != null ? COF.loadFromFile(handle) : null;
+ if (resolved != null) Gdx.app.log(TAG, " " + handle + " : " + resolved);
+ return resolved;
+ }
+
+ @Override
+ public void dispose() {
+ Riiablo.assets.dispose();
+ }
+}