diff --git a/Project.xml b/Project.xml
index e0677b026..a298c919f 100644
--- a/Project.xml
+++ b/Project.xml
@@ -111,6 +111,7 @@
+
diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx
index 123267a49..197fa28e8 100644
--- a/source/funkin/util/Constants.hx
+++ b/source/funkin/util/Constants.hx
@@ -70,7 +70,7 @@ class Constants
public static final URL_KICKSTARTER:String = 'https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/';
/**
- * GIT REPO DATA
+ * REPOSITORY DATA
*/
// ==============================
@@ -86,6 +86,11 @@ class Constants
public static final GIT_HASH:String = funkin.util.macro.GitCommit.getGitCommitHash();
#end
+ /**
+ * The current library versions, as provided by hmm.
+ */
+ public static final LIBRARY_VERSIONS:Array = funkin.util.macro.HaxelibVersions.getLibraryVersions();
+
/**
* COLORS
*/
diff --git a/source/funkin/util/logging/CrashHandler.hx b/source/funkin/util/logging/CrashHandler.hx
index a21732048..e32ba2c42 100644
--- a/source/funkin/util/logging/CrashHandler.hx
+++ b/source/funkin/util/logging/CrashHandler.hx
@@ -123,6 +123,17 @@ class CrashHandler
fullContents += '=====================\n';
+ fullContents += 'Haxelibs: \n';
+
+ for (lib in Constants.LIBRARY_VERSIONS)
+ {
+ fullContents += '- ${lib}\n';
+ }
+
+ fullContents += '\n';
+
+ fullContents += '=====================\n';
+
fullContents += '\n';
fullContents += message;
diff --git a/source/funkin/util/macro/HaxelibVersions.hx b/source/funkin/util/macro/HaxelibVersions.hx
new file mode 100644
index 000000000..f0317c397
--- /dev/null
+++ b/source/funkin/util/macro/HaxelibVersions.hx
@@ -0,0 +1,67 @@
+package funkin.util.macro;
+
+import haxe.io.Path;
+
+class HaxelibVersions
+{
+ public static macro function getLibraryVersions():haxe.macro.Expr.ExprOf>
+ {
+ #if !display
+ return macro $v{formatHmmData(readHmmData())};
+ #else
+ // `#if display` is used for code completion. In this case returning an
+ // empty string is good enough; We don't want to call functions on every hint.
+ var commitHash:String = "";
+ return macro $v{commitHashSplice};
+ #end
+ }
+
+ #if (debug && macro)
+ static function readHmmData():hmm.HmmConfig
+ {
+ return hmm.HmmConfig.HmmConfigs.readHmmJsonOrThrow();
+ }
+
+ static function formatHmmData(hmmData:hmm.HmmConfig):Array
+ {
+ var result:Array = [];
+
+ for (library in hmmData.dependencies)
+ {
+ switch (library)
+ {
+ case Haxelib(name, version):
+ result.push('${name} haxelib(${o(version)})');
+ case Git(name, url, ref, dir):
+ result.push('${name} git(${url}/${o(dir, '')}:${o(ref)})');
+ case Mercurial(name, url, ref, dir):
+ result.push('${name} mercurial(${url}/${o(dir, '')}:${o(ref)})');
+ case Dev(name, path):
+ result.push('${name} dev(${path})');
+ }
+ }
+
+ return result;
+ }
+
+ static function o(option:haxe.ds.Option, defaultValue:String = 'None'):String
+ {
+ switch (option)
+ {
+ case Some(value):
+ return value;
+ case None:
+ return defaultValue;
+ }
+ }
+
+ static function readLibraryCurrentVersion(libraryName:String):String
+ {
+ var path = Path.join([Path.addTrailingSlash(Sys.getCwd()), '.haxelib', libraryName, '.current']);
+ // This is compile time so we should always have Sys available.
+ var result = sys.io.File.getContent(path);
+
+ return result;
+ }
+ #end
+}