diff --git a/steam-runtime-tools/glib-backports-internal.h b/steam-runtime-tools/glib-backports-internal.h
index 5b71d59eafbc85e2fcbf89b0592dfc4eeec62559..613beab70c59d7b336c0ced4a90d25e4ca72055d 100644
--- a/steam-runtime-tools/glib-backports-internal.h
+++ b/steam-runtime-tools/glib-backports-internal.h
@@ -157,3 +157,11 @@ gboolean my_g_ptr_array_find_with_equal_func (GPtrArray *haystack,
 #define g_warning_once g_warning
 #endif
 #endif
+
+#if !GLIB_CHECK_VERSION(2, 68, 0)
+#define g_string_replace(s,f,r,l) my_g_string_replace (s, f, r, l)
+guint my_g_string_replace (GString *string,
+                           const gchar *find,
+                           const gchar *replace,
+                           guint limit);
+#endif
diff --git a/steam-runtime-tools/glib-backports.c b/steam-runtime-tools/glib-backports.c
index c929274d37aeabe356a6a01503d2e13c60ec57be..59573a3bb9dcfa0bcbb42ea1db505e3332cae4ef 100644
--- a/steam-runtime-tools/glib-backports.c
+++ b/steam-runtime-tools/glib-backports.c
@@ -7,6 +7,7 @@
  *  Copyright 2013-2019 Collabora Ltd.
  *  Copyright 2018 Georges Basile Stavracas Neto
  *  Copyright 2018 Philip Withnall
+ *  Copyright 2021 Joshua Lee
  *  g_execvpe implementation based on GNU libc execvp:
  *   Copyright 1991, 92, 95, 96, 97, 98, 99 Free Software Foundation, Inc.
  * SPDX-License-Identifier: LGPL-2.1-or-later
@@ -586,3 +587,54 @@ my_g_ptr_array_find_with_equal_func (GPtrArray     *haystack,
   return FALSE;
 }
 #endif
+
+#if !GLIB_CHECK_VERSION(2, 68, 0)
+/**
+ * g_string_replace:
+ * @string: a #GString
+ * @find: the string to find in @string
+ * @replace: the string to insert in place of @find
+ * @limit: the maximum instances of @find to replace with @replace, or `0` for
+ * no limit
+ *
+ * Replaces the string @find with the string @replace in a #GString up to
+ * @limit times. If the number of instances of @find in the #GString is
+ * less than @limit, all instances are replaced. If @limit is `0`,
+ * all instances of @find are replaced.
+ *
+ * Returns: the number of find and replace operations performed.
+ *
+ * Since: 2.68
+ */
+guint
+my_g_string_replace (GString     *string,
+                     const gchar *find,
+                     const gchar *replace,
+                     guint        limit)
+{
+  gsize f_len, r_len, pos;
+  gchar *cur, *next;
+  guint n = 0;
+
+  g_return_val_if_fail (string != NULL, 0);
+  g_return_val_if_fail (find != NULL, 0);
+  g_return_val_if_fail (replace != NULL, 0);
+
+  f_len = strlen (find);
+  r_len = strlen (replace);
+  cur = string->str;
+
+  while ((next = strstr (cur, find)) != NULL)
+    {
+      pos = next - string->str;
+      g_string_erase (string, pos, f_len);
+      g_string_insert (string, pos, replace);
+      cur = string->str + pos + r_len;
+      n++;
+      if (n == limit)
+        break;
+    }
+
+  return n;
+}
+#endif
diff --git a/tests/utils.c b/tests/utils.c
index 1e9140bf1d9fa9d6e6968456bd52fdb269693eb2..b297c0745f1ce7ff3d7c10a1fd9f0e189ebc662f 100644
--- a/tests/utils.c
+++ b/tests/utils.c
@@ -29,6 +29,7 @@
 #include <glib/gstdio.h>
 #include <glib-object.h>
 
+#include "steam-runtime-tools/glib-backports-internal.h"
 #include "steam-runtime-tools/input-device-internal.h"
 #include "steam-runtime-tools/utils-internal.h"
 #include "test-utils.h"
@@ -339,6 +340,38 @@ filter_gameoverlayrenderer (Fixture *f,
   g_free (filtered_preload);
 }
 
+static void
+test_gstring_replace (Fixture *f,
+                      gconstpointer context)
+{
+  static const struct
+  {
+    const char *string;
+    const char *original;
+    const char *replacement;
+    const char *expected;
+  }
+  tests[] =
+  {
+    { "/usr/$LIB/libMangoHud.so", "$LIB", "lib32", "/usr/lib32/libMangoHud.so" },
+    { "food for foals", "o", "", "fd fr fals" },
+    { "aaa", "a", "aaa", "aaaaaaaaa" },
+    { "aaa", "a", "", "" },
+    { "aaa", "aa", "bb", "bba" },
+  };
+  gsize i;
+
+  for (i = 0; i < G_N_ELEMENTS (tests); i++)
+    {
+      g_autoptr(GString) buffer = g_string_new (tests[i].string);
+
+      g_string_replace (buffer, tests[i].original, tests[i].replacement, 0);
+      g_assert_cmpstr (buffer->str, ==, tests[i].expected);
+      g_assert_cmpuint (buffer->len, ==, strlen (tests[i].expected));
+      g_assert_cmpuint (buffer->allocated_len, >=, strlen (tests[i].expected) + 1);
+    }
+}
+
 static void
 test_same_file (Fixture *f,
                 gconstpointer context)
@@ -472,6 +505,8 @@ main (int argc,
               filter_gameoverlayrenderer, teardown);
   g_test_add ("/utils/get-path-after", Fixture, NULL,
               setup, test_get_path_after, teardown);
+  g_test_add ("/utils/gstring-replace", Fixture, NULL,
+              setup, test_gstring_replace, teardown);
   g_test_add ("/utils/same-file", Fixture, NULL,
               setup, test_same_file, teardown);
   g_test_add ("/utils/str_is_integer", Fixture, NULL,