From c00b6e0750e2466d7c1b9bd2725172424a65930d Mon Sep 17 00:00:00 2001
From: Stuart Carnie <stuart.carnie@gmail.com>
Date: Sun, 9 Dec 2018 19:12:57 -0700
Subject: [PATCH] fix: Fix use of freed memory in menu animations

`menu_animation_update` enumerates `menu_animation.list` to process each
`tween`. It was observed that some tweens execute a callback that
pushes more animations via `menu_animation_push`. During the push, if
the tween `list` does not have enough space, a `realloc` occurs,
potentially invalidating the existing list. The remaining pointer access
in menu_animation_update is therefore invalid. Best case is the memory
is unused and thus does not affect the program. Worst case is memory
corruption.
---
 driver.c                                 |   3 +
 libretro-common/include/array/dynarray.h | 965 +++++++++++++++++++++++
 menu/menu_animation.c                    | 155 ++--
 menu/menu_animation.h                    |   4 +
 menu/menu_driver.c                       |   6 +
 menu/menu_driver.h                       |   2 +
 6 files changed, 1035 insertions(+), 100 deletions(-)
 create mode 100644 libretro-common/include/array/dynarray.h

diff --git a/driver.c b/driver.c
index a282df040c..7e7d609955 100644
--- a/driver.c
+++ b/driver.c
@@ -435,7 +435,10 @@ void driver_uninit(int flags)
 
 #ifdef HAVE_MENU
    if (flags & DRIVER_MENU_MASK)
+   {
       menu_driver_ctl(RARCH_MENU_CTL_DEINIT, NULL);
+      menu_driver_free();
+   }
 #endif
 
    if ((flags & DRIVER_LOCATION_MASK) && !location_driver_ctl(RARCH_LOCATION_CTL_OWNS_DRIVER, NULL))
diff --git a/libretro-common/include/array/dynarray.h b/libretro-common/include/array/dynarray.h
new file mode 100644
index 0000000000..261ae9462e
--- /dev/null
+++ b/libretro-common/include/array/dynarray.h
@@ -0,0 +1,965 @@
+/*
+ * A header-only typesafe dynamic array implementation for plain C,
+ * kinda like C++ std::vector. This code is compatible with C++, but should
+ * only be used with POD (plain old data) types, as it uses memcpy() etc
+ * instead of copy/move construction/assignment.
+ * It requires a new type (created with the DA_TYPEDEF(ELEMENT_TYPE, ARRAY_TYPE_NAME)
+ * macro) for each kind of element you want to put in a dynamic array; however
+ * the "functions" to manipulate the array are actually macros and the same
+ * for all element types.
+ * The array elements are accessed via dynArr.p[i] or da_get(dynArr, i)
+ * - the latter checks whether i is a valid index and asserts if not.
+ *
+ * One thing to keep in mind is that, because of using macros, the arguments to
+ * the "functions" are usually evaluated more than once, so you should avoid
+ * putting things with side effect (like function-calls with side effects or i++)
+ * into them. Notable exceptions are the value arguments (v) of da_push()
+ * and da_insert(), so it's still ok to do da_push(arr, fun_with_sideffects());
+ * or da_insert(a, 3, x++);
+ *
+ * The function-like da_* macros are short aliases of dg_dynarr_* macros.
+ * If the short names clash with anything in your code or other headers
+ * you are using, you can, before #including this header, do
+ *  #define DG_DYNARR_NO_SHORTNAMES
+ * and use the long dg_dynarr_* forms of the macros instead.
+ *
+ * Using this library in your project:
+ *   Put this file somewhere in your project.
+ *   In *one* of your .c/.cpp files, do
+ *     #define DG_DYNARR_IMPLEMENTATION
+ *     #include "DG_dynarr.h"
+ *   to create the implementation of this library in that file.
+ *   You can just #include "DG_dynarr.h" (without the #define) in other source
+ *   files to use it there.
+ *
+ * See below this comment block for a usage example.
+ *
+ * You can #define your own allocators, assertion and the amount of runtime
+ * checking of indexes, see CONFIGURATION section in the code for more information.
+ *
+ * 
+ * This is heavily inspired by Sean Barrett's stretchy_buffer.h
+ * ( see: https://github.com/nothings/stb/blob/master/stretchy_buffer.h )
+ * However I wanted to have a struct that holds the array pointer and the length
+ * and capacity, so that struct always remains at the same address while the
+ * array memory might be reallocated.
+ * I can live with arr.p[i] instead of arr[i], but I like how he managed to use
+ * macros to create an API that doesn't force the user to specify the stored
+ * type over and over again, so I stole some of his tricks :-)
+ *
+ * This has been tested with GCC 4.8 and clang 3.8 (-std=gnu89, -std=c99 and as C++;
+ * -std=c89 works if you convert the C++-style comments to C comments) and
+ * Microsoft Visual Studio 6 and 2010 (32bit) and 2013 (32bit and 64bit).
+ * I guess it works with all (recentish) C++ compilers and C compilers supporting
+ * C99 or even C89 + C++ comments (otherwise converting the comments should help).
+ * 
+ * (C) 2016 Daniel Gibson
+ *
+ * LICENSE
+ *   This software is dual-licensed to the public domain and under the following
+ *   license: you are granted a perpetual, irrevocable license to copy, modify,
+ *   publish, and distribute this file as you see fit.
+ *   No warranty implied; use at your own risk.
+ *
+ * So you can do whatever you want with this code, including copying it
+ * (or parts of it) into your own source.
+ * No need to mention me or this "license" in your code or docs, even though
+ * it would be appreciated, of course.
+ */
+#if 0 // Usage Example:
+ #define DG_DYNARR_IMPLEMENTATION // this define is only needed in *one* .c/.cpp file!
+ #include "DG_dynarr.h"
+ 
+ DA_TYPEDEF(int, MyIntArrType); // creates MyIntArrType - a dynamic array for ints
+ 
+ void printIntArr(MyIntArrType* arr, const char* name)
+ {
+     // note that arr is a pointer here, so use *arr in the da_*() functions.
+     printf("%s = {", name);
+     if(da_count(*arr) > 0)
+         printf(" %d", arr->p[0]);
+     for(int i=1; i<da_count(*arr); ++i)
+         printf(", %d", arr->p[i]);
+     printf(" }\n");
+ }
+
+ void myFunction()
+ {
+     MyIntArrType a1 = {0}; // make sure to zero out the struct
+     // instead of = {0}; you could also call da_init(a1);
+
+     da_push(a1, 42);
+     assert(da_count(a1) == 1 && a1.p[0] == 42);
+
+     int* addedElements = da_addn_uninit(a1, 3);
+     assert(da_count(a1) == 4);
+     for(size_t i=0; i<3; ++i)
+         addedElements[i] = i+5;
+
+     printIntArr(&a1, "a1"); // "a1 = { 42, 5, 6, 7 }"
+
+     MyIntArrType a2;
+     da_init(a2);
+
+     da_addn(a2, a1.p, da_count(a1)); // copy all elements from a1 to a2
+     assert(da_count(a2) == 4);
+
+     da_insert(a2, 1, 11);
+     printIntArr(&a2, "a2"); // "a2 = { 42, 11, 5, 6, 7 }"
+
+     da_delete(a2, 2);
+     printIntArr(&a2, "a2"); // "a2 = { 42, 11, 6, 7 }"
+
+     da_deletefast(a2, 0);
+     printIntArr(&a2, "a2"); // "a2 = { 7, 11, 6 }"
+
+     da_push(a1, 3);
+     printIntArr(&a1, "a1"); // "a1 = { 42, 5, 6, 7, 3 }"
+
+     int x=da_pop(a1);
+     printf("x = %d\n", x);  // "x = 3"
+     printIntArr(&a1, "a1"); // "a1 = { 42, 5, 6, 7 }"
+     
+     da_free(a1); // make sure not to leak memory!
+     da_free(a2);
+ }
+#endif // 0 (usage example)
+
+#ifndef DG__DYNARR_H
+#define DG__DYNARR_H
+
+// ######### CONFIGURATION #########
+
+// following: some #defines that you can tweak to your liking
+
+// you can reduce some overhead by defining DG_DYNARR_INDEX_CHECK_LEVEL to 2, 1 or 0
+#ifndef DG_DYNARR_INDEX_CHECK_LEVEL
+
+	// 0: (almost) no index checking
+	// 1: macros "returning" something return a.p[0] or NULL if the index was invalid
+	// 2: assertions in all macros taking indexes that make sure they're valid
+	// 3: 1 and 2
+	#define DG_DYNARR_INDEX_CHECK_LEVEL 3
+
+#endif // DG_DYNARR_INDEX_CHECK_LEVEL
+
+// you can #define your own DG_DYNARR_ASSERT(condition, msgstring)
+// that will be used for all assertions in this code.
+#ifndef DG_DYNARR_ASSERT
+	#include <assert.h>
+	#define DG_DYNARR_ASSERT(cond, msg)  assert((cond) && msg)
+#endif
+
+// you can #define DG_DYNARR_OUT_OF_MEMORY to some code that will be executed
+// if allocating memory fails
+// it's needed only before the #define DG_DYNARR_IMPLEMENTATION #include of
+// this header, so the following is here only for reference and commented out
+/*
+ #ifndef DG_DYNARR_OUT_OF_MEMORY
+	#define DG_DYNARR_OUT_OF_MEMORY  DG_DYNARR_ASSERT(0, "Out of Memory!");
+ #endif
+*/
+
+// By default, C's malloc(), realloc() and free() is used to allocate/free heap memory
+// (see beginning of "#ifdef DG_DYNARR_IMPLEMENTATION" block below).
+// You can #define DG_DYNARR_MALLOC, DG_DYNARR_REALLOC and DG_DYNARR_FREE yourself
+// to provide alternative implementations like Win32 Heap(Re)Alloc/HeapFree
+// it's needed only before the #define DG_DYNARR_IMPLEMENTATION #include of
+// this header, so the following is here only for reference and commented out
+/*
+ #define DG_DYNARR_MALLOC(elemSize, numElems)  malloc(elemSize*numElems)
+
+ // oldNumElems is not used for C's realloc, but maybe you need it for
+ // your allocator to copy the old elements over
+ #define DG_DYNARR_REALLOC(ptr, elemSize, oldNumElems, newCapacity) \
+ 	realloc(ptr, elemSize*newCapacity);
+
+ #define DG_DYNARR_FREE(ptr)  free(ptr)
+*/
+
+// if you want to prepend something to the non inline (DG_DYNARR_INLINE) functions,
+// like "__declspec(dllexport)" or whatever, #define DG_DYNARR_DEF
+#ifndef DG_DYNARR_DEF
+	// by defaults it's empty.
+	#define DG_DYNARR_DEF
+#endif
+
+// some functions are inline, in case your compiler doesn't like "static inline"
+// but wants "__inline__" or something instead, #define DG_DYNARR_INLINE accordingly.
+#ifndef DG_DYNARR_INLINE
+	// for pre-C99 compilers you might have to use something compiler-specific (or maybe only "static")
+	#ifdef _MSC_VER
+		#define DG_DYNARR_INLINE static __inline
+	#else
+		#define DG_DYNARR_INLINE static inline
+	#endif
+#endif
+
+
+
+// ############### Short da_* aliases for the long names ###############
+
+#ifndef DG_DYNARR_NO_SHORTNAMES
+
+// this macro is used to create an array type (struct) for elements of TYPE
+// use like DA_TYPEDEF(int, MyIntArrType); MyIntArrType ia = {0}; da_push(ia, 42); ...
+#define DA_TYPEDEF(TYPE, NewArrayTypeName) \
+	DG_DYNARR_TYPEDEF(TYPE, NewArrayTypeName)
+
+// makes sure the array is initialized and can be used.
+// either do YourArray arr = {0}; or YourArray arr; da_init(arr);
+#define da_init(a) \
+	dg_dynarr_init(a)
+
+/*
+ * This allows you to provide an external buffer that'll be used as long as it's big enough
+ * once you add more elements than buf can hold, fresh memory will be allocated on the heap
+ * Use like:
+ * DA_TYPEDEF(double, MyDoubleArrType);
+ * MyDoubleArrType arr;
+ * double buf[8];
+ * dg_dynarr_init_external(arr, buf, 8);
+ * dg_dynarr_push(arr, 1.23);
+ * ...
+ */
+#define da_init_external(a, buf, buf_cap) \
+	dg_dynarr_init_external(a, buf, buf_cap)
+
+// use this to free the memory allocated by dg_dynarr once you don't need the array anymore
+// Note: it is safe to add new elements to the array after da_free()
+//       it will allocate new memory, just like it would directly after da_init()
+#define da_free(a) \
+	dg_dynarr_free(a)
+
+
+// add an element to the array (appended at the end)
+#define da_push(a, v) \
+	dg_dynarr_push(a, v)
+
+// add an element to the array (appended at the end)
+// does the same as push, just for consistency with addn (like insert and insertn)
+#define da_add(a, v) \
+	dg_dynarr_add(a, v)
+
+// append n elements to a and initialize them from array vals, doesn't return anything
+// ! vals (and all other args) are evaluated multiple times !
+#define da_addn(a, vals, n) \
+	dg_dynarr_addn(a, vals, n)
+
+// add n elements to the end of the array and zeroes them with memset()
+// returns pointer to first added element, NULL if out of memory (array is empty then)
+#define da_addn_zeroed(a, n) \
+	dg_dynarr_addn_zeroed(a, n)
+
+// add n elements to the end of the array, will remain uninitialized
+// returns pointer to first added element, NULL if out of memory (array is empty then)
+#define da_addn_uninit(a, n) \
+	dg_dynarr_addn_uninit(a, n)
+
+
+// insert a single value v at index idx
+#define da_insert(a, idx, v) \
+	dg_dynarr_insert(a, idx, v)
+
+// insert n elements into a at idx, initialize them from array vals
+// doesn't return anything
+// ! vals (and all other args) is evaluated multiple times ! 
+#define da_insertn(a, idx, vals, n) \
+	dg_dynarr_insertn(a, idx, vals, n)
+
+// insert n elements into a at idx and zeroe them with memset() 
+// returns pointer to first inserted element or NULL if out of memory
+#define da_insertn_zeroed(a, idx, n) \
+	dg_dynarr_insertn_zeroed(a, idx, n)
+
+// insert n uninitialized elements into a at idx;
+// returns pointer to first inserted element or NULL if out of memory
+#define da_insertn_uninit(a, idx, n) \
+	dg_dynarr_insertn_uninit(a, idx, n)
+
+// set a single value v at index idx - like "a.p[idx] = v;" but with checks (unless disabled)
+#define da_set(a, idx, v) \
+	dg_dynarr_set(a, idx, v)
+
+// overwrite n elements of a, starting at idx, with values from array vals
+// doesn't return anything
+// ! vals (and all other args) is evaluated multiple times ! 
+#define da_setn(a, idx, vals, n) \
+	dg_dynarr_setn(a, idx, vals, n)
+
+// delete the element at idx, moving all following elements (=> keeps order)
+#define da_delete(a, idx) \
+	dg_dynarr_delete(a, idx)
+
+// delete n elements starting at idx, moving all following elements (=> keeps order)
+#define da_deleten(a, idx, n) \
+	dg_dynarr_deleten(a, idx, n)
+
+// delete the element at idx, move the last element there (=> doesn't keep order)
+#define da_deletefast(a, idx) \
+	dg_dynarr_deletefast(a, idx)
+
+// delete n elements starting at idx, move the last n elements there (=> doesn't keep order)
+#define da_deletenfast(a, idx, n) \
+	dg_dynarr_deletenfast(a, idx, n)
+
+// removes all elements from the array, but does not free the buffer
+// (if you want to free the buffer too, just use da_free())
+#define da_clear(a) \
+	dg_dynarr_clear(a)
+
+// sets the logical number of elements in the array
+// if cnt > dg_dynarr_count(a), the logical count will be increased accordingly
+// and the new elements will be uninitialized
+#define da_setcount(a, cnt) \
+	dg_dynarr_setcount(a, cnt)
+
+// make sure the array can store cap elements without reallocating
+// logical count remains unchanged
+#define da_reserve(a, cap) \
+	dg_dynarr_reserve(a, cap)
+
+// this makes sure a only uses as much memory as for its elements
+// => maybe useful if a used to contain a huge amount of elements,
+//    but you deleted most of them and want to free some memory
+// Note however that this implies an allocation and copying the remaining
+// elements, so only do this if it frees enough memory to be worthwhile!
+#define da_shrink_to_fit(a) \
+	dg_dynarr_shrink_to_fit(a)
+
+
+// removes and returns the last element of the array
+#define da_pop(a) \
+	dg_dynarr_pop(a)
+
+// returns the last element of the array
+#define da_last(a) \
+	dg_dynarr_last(a)
+
+// returns the pointer *to* the last element of the array
+// (in contrast to dg_dynarr_end() which returns a pointer *after* the last element)
+// returns NULL if array is empty
+#define da_lastptr(a) \
+	dg_dynarr_lastptr(a)
+
+// get element at index idx (like a.p[idx]), but with checks
+// (unless you disabled them with #define DG_DYNARR_INDEX_CHECK_LEVEL 0)
+#define da_get(a, idx) \
+	dg_dynarr_get(a,idx)
+
+// get pointer to element at index idx (like &a.p[idx]), but with checks
+// and it returns NULL if idx is invalid
+#define da_getptr(a, idx) \
+	dg_dynarr_getptr(a, idx)
+
+// returns a pointer to the first element of the array
+// (together with dg_dynarr_end() you can do C++-style iterating)
+#define da_begin(a) \
+	dg_dynarr_begin(a)
+
+// returns a pointer to the past-the-end element of the array
+// Allows C++-style iterating, in case you're into that kind of thing:
+// for(T *it=da_begin(a), *end=da_end(a); it!=end; ++it) foo(*it);
+// (see da_lastptr() to get a pointer *to* the last element)
+#define da_end(a) \
+	dg_dynarr_end(a)
+
+
+// returns (logical) number of elements currently in the array
+#define da_count(a) \
+	dg_dynarr_count(a)
+
+// get the current reserved capacity of the array
+#define da_capacity(a) \
+	dg_dynarr_capacity(a)
+
+// returns 1 if the array is empty, else 0
+#define da_empty(a) \
+	dg_dynarr_empty(a)
+
+// returns 1 if the last (re)allocation when inserting failed (Out Of Memory)
+//   or if the array has never allocated any memory yet, else 0
+// deleting the contents when growing fails instead of keeping old may seem
+// a bit uncool, but it's simple and OOM should rarely happen on modern systems
+// anyway - after all you need to deplete both RAM and swap/pagefile.sys
+#define da_oom(a) \
+	dg_dynarr_oom(a)
+
+
+// sort a using the given qsort()-comparator cmp
+// (just a slim wrapper around qsort())
+#define da_sort(a, cmp) \
+	dg_dynarr_sort(a, cmp)
+
+#endif // DG_DYNARR_NO_SHORTNAMES
+
+
+// ######### Implementation of the actual macros (using the long names) ##########
+
+// use like DG_DYNARR_TYPEDEF(int, MyIntArrType); MyIntArrType ia = {0}; dg_dynarr_push(ia, 42); ...
+#define DG_DYNARR_TYPEDEF(TYPE, NewArrayTypeName) \
+	typedef struct { TYPE* p; dg__dynarr_md md; } NewArrayTypeName;
+
+// makes sure the array is initialized and can be used.
+// either do YourArray arr = {0}; or YourArray arr; dg_dynarr_init(arr);
+#define dg_dynarr_init(a) \
+	dg__dynarr_init((void**)&(a).p, &(a).md, NULL, 0)
+
+// this allows you to provide an external buffer that'll be used as long as it's big enough
+// once you add more elements than buf can hold, fresh memory will be allocated on the heap
+#define dg_dynarr_init_external(a, buf, buf_cap) \
+	dg__dynarr_init((void**)&(a).p, &(a).md, (buf), (buf_cap))
+
+// use this to free the memory allocated by dg_dynarr
+// Note: it is safe to add new elements to the array after dg_dynarr_free()
+//       it will allocate new memory, just like it would directly after dg_dynarr_init()
+#define dg_dynarr_free(a) \
+	dg__dynarr_free((void**)&(a).p, &(a).md)
+
+
+// add an element to the array (appended at the end)
+#define dg_dynarr_push(a, v) \
+	(dg__dynarr_maybegrowadd(dg__dynarr_unp(a), 1) ? (((a).p[(a).md.cnt++] = (v)),0) : 0)
+
+// add an element to the array (appended at the end)
+// does the same as push, just for consistency with addn (like insert and insertn)
+#define dg_dynarr_add(a, v) \
+	dg_dynarr_push((a), (v))
+
+// append n elements to a and initialize them from array vals, doesn't return anything
+// ! vals (and all other args) are evaluated multiple times !
+#define dg_dynarr_addn(a, vals, n) do { \
+	DG_DYNARR_ASSERT((vals)!=NULL, "Don't pass NULL als vals to dg_dynarr_addn!"); \
+	if((vals)!=NULL && dg__dynarr_add(dg__dynarr_unp(a), n, 0)) { \
+	  size_t i_=(a).md.cnt-(n), v_=0; \
+	  while(i_<(a).md.cnt)  (a).p[i_++]=(vals)[v_++]; \
+	} } DG__DYNARR_WHILE0
+
+// add n elements to the end of the array and zeroe them with memset()
+// returns pointer to first added element, NULL if out of memory (array is empty then)
+#define dg_dynarr_addn_zeroed(a, n) \
+	(dg__dynarr_add(dg__dynarr_unp(a), (n), 1) ? &(a).p[(a).md.cnt-(size_t)(n)] : NULL)
+
+// add n elements to the end of the array, which are uninitialized
+// returns pointer to first added element, NULL if out of memory (array is empty then)
+#define dg_dynarr_addn_uninit(a, n) \
+	(dg__dynarr_add(dg__dynarr_unp(a), (n), 0) ? &(a).p[(a).md.cnt-(size_t)(n)] : NULL)
+
+// insert a single value v at index idx
+#define dg_dynarr_insert(a, idx, v) \
+	(dg__dynarr_checkidxle((a),(idx)), \
+	 dg__dynarr_insert(dg__dynarr_unp(a), (idx), 1, 0), \
+	 (a).p[dg__dynarr_idx((a).md, (idx))] = (v))
+
+// insert n elements into a at idx, initialize them from array vals
+// doesn't return anything
+// ! vals (and all other args) is evaluated multiple times ! 
+#define dg_dynarr_insertn(a, idx, vals, n) do { \
+	DG_DYNARR_ASSERT((vals)!=NULL, "Don't pass NULL as vals to dg_dynarr_insertn!"); \
+	dg__dynarr_checkidxle((a),(idx)); \
+	if((vals)!=NULL && dg__dynarr_insert(dg__dynarr_unp(a), (idx), (n), 0)){ \
+		size_t i_=(idx), v_=0, e_=(idx)+(n); \
+		while(i_ < e_)  (a).p[i_++] = (vals)[v_++]; \
+	}} DG__DYNARR_WHILE0
+
+// insert n elements into a at idx and zeroe them with memset() 
+// returns pointer to first inserted element or NULL if out of memory
+#define dg_dynarr_insertn_zeroed(a, idx, n) \
+	(dg__dynarr_checkidxle((a),(idx)), \
+	 dg__dynarr_insert(dg__dynarr_unp(a), (idx), (n), 1) \
+	  ? &(a).p[dg__dynarr_idx((a).md, (idx))] : NULL)
+
+// insert n uninitialized elements into a at idx;
+// returns pointer to first inserted element or NULL if out of memory
+#define dg_dynarr_insertn_uninit(a, idx, n) \
+	(dg__dynarr_checkidxle((a),(idx)), \
+	 dg__dynarr_insert(dg__dynarr_unp(a), idx, n, 0) \
+	  ? &(a).p[dg__dynarr_idx((a).md, (idx))] : NULL)
+
+// set a single value v at index idx - like "a.p[idx] = v;" but with checks (unless disabled)
+#define dg_dynarr_set(a, idx, v) \
+	(dg__dynarr_checkidx((a),(idx)), \
+	 (a).p[dg__dynarr_idx((a).md, (idx))] = (v))
+
+// overwrite n elements of a, starting at idx, with values from array vals
+// doesn't return anything
+// ! vals (and all other args) is evaluated multiple times ! 
+#define dg_dynarr_setn(a, idx, vals, n) do { \
+	DG_DYNARR_ASSERT((vals)!=NULL, "Don't pass NULL as vals to dg_dynarr_setn!"); \
+	size_t idx_=(idx); size_t end_=idx_+(size_t)n; \
+	dg__dynarr_checkidx((a),idx_); dg__dynarr_checkidx((a),end_-1); \
+	if((vals)!=NULL && idx_ < (a).md.cnt && end_ <= (a).md.cnt) { \
+		size_t v_=0; \
+		while(idx_ < end_)  (a).p[idx_++] = (vals)[v_++]; \
+	}} DG__DYNARR_WHILE0
+
+
+// delete the element at idx, moving all following elements (=> keeps order)
+#define dg_dynarr_delete(a, idx) \
+	(dg__dynarr_checkidx((a),(idx)), dg__dynarr_delete(dg__dynarr_unp(a), (idx), 1))
+
+// delete n elements starting at idx, moving all following elements (=> keeps order)
+#define dg_dynarr_deleten(a, idx, n) \
+	(dg__dynarr_checkidx((a),(idx)), dg__dynarr_delete(dg__dynarr_unp(a), (idx), (n)))
+	// TODO: check whether idx+n < count?
+
+// delete the element at idx, move the last element there (=> doesn't keep order)
+#define dg_dynarr_deletefast(a, idx) \
+	(dg__dynarr_checkidx((a),(idx)), dg__dynarr_deletefast(dg__dynarr_unp(a), (idx), 1))
+
+// delete n elements starting at idx, move the last n elements there (=> doesn't keep order)
+#define dg_dynarr_deletenfast(a, idx, n) \
+	(dg__dynarr_checkidx((a),(idx)), dg__dynarr_deletefast(dg__dynarr_unp(a), idx, n))
+	// TODO: check whether idx+n < count?
+
+// removes all elements from the array, but does not free the buffer
+// (if you want to free the buffer too, just use dg_dynarr_free())
+#define dg_dynarr_clear(a) \
+	((a).md.cnt=0)
+
+// sets the logical number of elements in the array
+// if cnt > dg_dynarr_count(a), the logical count will be increased accordingly
+// and the new elements will be uninitialized
+#define dg_dynarr_setcount(a, n) \
+	(dg__dynarr_maybegrow(dg__dynarr_unp(a), (n)) ? ((a).md.cnt = (n)) : 0)
+
+// make sure the array can store cap elements without reallocating
+// logical count remains unchanged
+#define dg_dynarr_reserve(a, cap) \
+	dg__dynarr_maybegrow(dg__dynarr_unp(a), (cap))
+
+// this makes sure a only uses as much memory as for its elements
+// => maybe useful if a used to contain a huge amount of elements,
+//    but you deleted most of them and want to free some memory
+// Note however that this implies an allocation and copying the remaining
+// elements, so only do this if it frees enough memory to be worthwhile!
+#define dg_dynarr_shrink_to_fit(a) \
+	dg__dynarr_shrink_to_fit(dg__dynarr_unp(a))
+
+
+#if (DG_DYNARR_INDEX_CHECK_LEVEL == 1) || (DG_DYNARR_INDEX_CHECK_LEVEL == 3)
+
+	// removes and returns the last element of the array
+	#define dg_dynarr_pop(a) \
+		(dg__dynarr_check_notempty((a), "Don't pop an empty array!"), \
+		 (a).p[((a).md.cnt > 0) ? (--(a).md.cnt) : 0])
+
+	// returns the last element of the array
+	#define dg_dynarr_last(a) \
+		(dg__dynarr_check_notempty((a), "Don't call da_last() on an empty array!"), \
+		 (a).p[((a).md.cnt > 0) ? ((a).md.cnt-1) : 0])
+
+#elif (DG_DYNARR_INDEX_CHECK_LEVEL == 0) || (DG_DYNARR_INDEX_CHECK_LEVEL == 2)
+
+	// removes and returns the last element of the array
+	#define dg_dynarr_pop(a) \
+		(dg__dynarr_check_notempty((a), "Don't pop an empty array!"), \
+		 (a).p[--(a).md.cnt])
+
+	// returns the last element of the array
+	#define dg_dynarr_last(a) \
+		(dg__dynarr_check_notempty((a), "Don't call da_last() on an empty array!"), \
+		 (a).p[(a).md.cnt-1])
+
+#else // invalid DG_DYNARR_INDEX_CHECK_LEVEL
+	#error Invalid index check level DG_DYNARR_INDEX_CHECK_LEVEL (must be 0-3) !
+#endif // DG_DYNARR_INDEX_CHECK_LEVEL
+
+// returns the pointer *to* the last element of the array
+// (in contrast to dg_dynarr_end() which returns a pointer *after* the last element)
+// returns NULL if array is empty
+#define dg_dynarr_lastptr(a) \
+	(((a).md.cnt > 0) ? ((a).p + (a).md.cnt - 1) : NULL)
+
+// get element at index idx (like a.p[idx]), but with checks
+// (unless you disabled them with #define DG_DYNARR_INDEX_CHECK_LEVEL 0)
+#define dg_dynarr_get(a, idx) \
+	(dg__dynarr_checkidx((a),(idx)), (a).p[dg__dynarr_idx((a).md, (idx))])
+
+// get pointer to element at index idx (like &a.p[idx]), but with checks
+// (unless you disabled them with #define DG_DYNARR_INDEX_CHECK_LEVEL 0)
+// if index-checks are disabled, it returns NULL on invalid index (else it asserts() before returning)
+#define dg_dynarr_getptr(a, idx) \
+	(dg__dynarr_checkidx((a),(idx)), \
+	 ((size_t)(idx) < (a).md.cnt) ? ((a).p+(size_t)(idx)) : NULL)
+
+// returns a pointer to the first element of the array
+// (together with dg_dynarr_end() you can do C++-style iterating)
+#define dg_dynarr_begin(a) \
+	((a).p)
+
+// returns a pointer to the past-the-end element of the array
+// Allows C++-style iterating, in case you're into that kind of thing:
+// for(T *it=dg_dynarr_begin(a), *end=dg_dynarr_end(a); it!=end; ++it) foo(*it);
+// (see dg_dynarr_lastptr() to get a pointer *to* the last element)
+#define dg_dynarr_end(a) \
+	((a).p + (a).md.cnt)
+
+
+// returns (logical) number of elements currently in the array
+#define dg_dynarr_count(a) \
+	((a).md.cnt)
+
+// get the current reserved capacity of the array
+#define dg_dynarr_capacity(a) \
+	((a).md.cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB)
+
+// returns 1 if the array is empty, else 0
+#define dg_dynarr_empty(a) \
+	((a).md.cnt == 0)
+
+// returns 1 if the last (re)allocation when inserting failed (Out Of Memory)
+//   or if the array has never allocated any memory yet, else 0
+// deleting the contents when growing fails instead of keeping old may seem
+// a bit uncool, but it's simple and OOM should rarely happen on modern systems
+// anyway - after all you need to deplete both RAM and swap/pagefile.sys
+// or deplete the address space, which /might/ happen with 32bit applications
+// but probably not with 64bit (at least in the foreseeable future)
+#define dg_dynarr_oom(a) \
+	((a).md.cap == 0)
+
+
+// sort a using the given qsort()-comparator cmp
+// (just a slim wrapper around qsort())
+#define dg_dynarr_sort(a, cmp) \
+	qsort((a).p, (a).md.cnt, sizeof((a).p[0]), (cmp))
+
+
+// ######### Implementation-Details that are not part of the API ##########
+
+#include <stdlib.h> // size_t, malloc(), free(), realloc()
+#include <string.h> // memset(), memcpy(), memmove()
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+	size_t cnt; // logical number of elements
+	size_t cap; // cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB is actual capacity (in elements, *not* bytes!)
+		// if(cap & DG__DYNARR_SIZE_T_MSB) the current memory is not allocated by dg_dynarr,
+		// but was set with dg_dynarr_init_external()
+		// that's handy to give an array a base-element storage on the stack, for example
+		// TODO: alternatively, we could introduce a flag field to this struct and use that,
+		//       so we don't have to calculate & everytime cap is needed
+} dg__dynarr_md;
+
+// I used to have the following in an enum, but MSVC assumes enums are always 32bit ints
+static const size_t DG__DYNARR_SIZE_T_MSB = ((size_t)1) << (sizeof(size_t)*8 - 1);
+static const size_t DG__DYNARR_SIZE_T_ALL_BUT_MSB = (((size_t)1) << (sizeof(size_t)*8 - 1))-1;
+
+// "unpack" the elements of an array struct for use with helper functions
+// (to void** arr, dg__dynarr_md* md, size_t itemsize)
+#define dg__dynarr_unp(a) \
+	(void**)&(a).p, &(a).md, sizeof((a).p[0])
+
+// MSVC warns about "conditional expression is constant" when using the
+// do { ... } while(0) idiom in macros.. 
+#ifdef _MSC_VER
+  #if _MSC_VER >= 1400 // MSVC 2005 and newer
+    // people claim MSVC 2005 and newer support __pragma, even though it's only documented
+    // for 2008+ (https://msdn.microsoft.com/en-us/library/d9x1s805%28v=vs.90%29.aspx)
+    // the following workaround is based on
+    // http://cnicholson.net/2009/03/stupid-c-tricks-dowhile0-and-c4127/
+    #define DG__DYNARR_WHILE0 \
+      __pragma(warning(push)) \
+      __pragma(warning(disable:4127)) \
+      while(0) \
+      __pragma(warning(pop))
+  #else // older MSVC versions don't support __pragma - I heard this helps for them
+    #define DG__DYNARR_WHILE0  while(0,0)
+  #endif
+
+#else // other compilers
+
+	#define DG__DYNARR_WHILE0  while(0)
+
+#endif // _MSC_VER
+
+
+#if (DG_DYNARR_INDEX_CHECK_LEVEL == 2) || (DG_DYNARR_INDEX_CHECK_LEVEL == 3)
+
+	#define dg__dynarr_checkidx(a,i) \
+		DG_DYNARR_ASSERT((size_t)i < a.md.cnt, "index out of bounds!")
+
+	// special case for insert operations: == cnt is also ok, insert will append then
+	#define dg__dynarr_checkidxle(a,i) \
+		DG_DYNARR_ASSERT((size_t)i <= a.md.cnt, "index out of bounds!")
+
+	#define dg__dynarr_check_notempty(a, msg) \
+		DG_DYNARR_ASSERT(a.md.cnt > 0, msg)
+
+#elif (DG_DYNARR_INDEX_CHECK_LEVEL == 0) || (DG_DYNARR_INDEX_CHECK_LEVEL == 1)
+
+	// no assertions that check if index is valid
+	#define dg__dynarr_checkidx(a,i) (void)0
+	#define dg__dynarr_checkidxle(a,i) (void)0
+
+	#define dg__dynarr_check_notempty(a, msg) (void)0
+
+#else // invalid DG_DYNARR_INDEX_CHECK_LEVEL
+	#error Invalid index check level DG_DYNARR_INDEX_CHECK_LEVEL (must be 0-3) !
+#endif // DG_DYNARR_INDEX_CHECK_LEVEL
+
+
+#if (DG_DYNARR_INDEX_CHECK_LEVEL == 1) || (DG_DYNARR_INDEX_CHECK_LEVEL == 3)
+
+	// the given index, if valid, else 0
+	#define dg__dynarr_idx(md,i) \
+		(((size_t)(i) < md.cnt) ? (size_t)(i) : 0)
+
+#elif (DG_DYNARR_INDEX_CHECK_LEVEL == 0) || (DG_DYNARR_INDEX_CHECK_LEVEL == 2)
+
+	// don't check and default to 0 if invalid, but just use the given value
+	#define dg__dynarr_idx(md,i) (size_t)(i)
+
+#else // invalid DG_DYNARR_INDEX_CHECK_LEVEL
+	#error Invalid index check level DG_DYNARR_INDEX_CHECK_LEVEL (must be 0-3) !
+#endif // DG_DYNARR_INDEX_CHECK_LEVEL
+
+// the functions allocating/freeing memory are not implemented inline, but
+// in the #ifdef DG_DYNARR_IMPLEMENTATION section
+// one reason is that dg__dynarr_grow has the most code in it, the other is
+// that windows has weird per-dll heaps so free() or realloc() should be
+// called from code in the same dll that allocated the memory - these kind
+// of wrapper functions that end up compiled into the exe or *one* dll
+// (instead of inline functions compiled into everything) should ensure that.
+
+DG_DYNARR_DEF void
+dg__dynarr_free(void** p, dg__dynarr_md* md);
+
+DG_DYNARR_DEF void
+dg__dynarr_shrink_to_fit(void** arr, dg__dynarr_md* md, size_t itemsize);
+
+// grow array to have enough space for at least min_needed elements
+// if it fails (OOM), the array will be deleted, a.p will be NULL, a.md.cap and a.md.cnt will be 0
+// and the functions returns 0; else (on success) it returns 1
+DG_DYNARR_DEF int
+dg__dynarr_grow(void** arr, dg__dynarr_md* md, size_t itemsize, size_t min_needed);
+
+
+// the following functions are implemented inline, because they're quite short
+// and mosty implemented in functions so the macros don't get too ugly
+
+DG_DYNARR_INLINE void
+dg__dynarr_init(void** p, dg__dynarr_md* md, void* buf, size_t buf_cap)
+{
+	*p = buf;
+	md->cnt = 0;
+	if(buf == NULL)  md->cap = 0;
+	else md->cap = (DG__DYNARR_SIZE_T_MSB | buf_cap);
+}
+
+DG_DYNARR_INLINE int
+dg__dynarr_maybegrow(void** arr, dg__dynarr_md* md, size_t itemsize, size_t min_needed)
+{
+	if((md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) >= min_needed)  return 1;
+	else return dg__dynarr_grow(arr, md, itemsize, min_needed);
+}
+
+DG_DYNARR_INLINE int
+dg__dynarr_maybegrowadd(void** arr, dg__dynarr_md* md, size_t itemsize, size_t num_add)
+{
+	size_t min_needed = md->cnt+num_add;
+	if((md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) >= min_needed)  return 1;
+	else return dg__dynarr_grow(arr, md, itemsize, min_needed);
+}
+
+DG_DYNARR_INLINE int
+dg__dynarr_insert(void** arr, dg__dynarr_md* md, size_t itemsize, size_t idx, size_t n, int init0)
+{
+	// allow idx == md->cnt to append
+	size_t oldCount = md->cnt;
+	size_t newCount = oldCount+n;
+	if(idx <= oldCount && dg__dynarr_maybegrow(arr, md, itemsize, newCount))
+	{
+		unsigned char* p = (unsigned char*)*arr; // *arr might have changed in dg__dynarr_grow()!
+		// move all existing items after a[idx] to a[idx+n]
+		if(idx < oldCount)  memmove(p+(idx+n)*itemsize, p+idx*itemsize, itemsize*(oldCount - idx));
+
+		// if the memory is supposed to be zeroed, do that
+		if(init0)  memset(p+idx*itemsize, 0, n*itemsize);
+
+		md->cnt = newCount;
+		return 1;
+	}
+	return 0;
+}
+
+DG_DYNARR_INLINE int
+dg__dynarr_add(void** arr, dg__dynarr_md* md, size_t itemsize, size_t n, int init0)
+{
+	size_t cnt = md->cnt;
+	if(dg__dynarr_maybegrow(arr, md, itemsize, cnt+n))
+	{
+		unsigned char* p = (unsigned char*)*arr; // *arr might have changed in dg__dynarr_grow()!
+		// if the memory is supposed to be zeroed, do that
+		if(init0)  memset(p+cnt*itemsize, 0, n*itemsize);
+
+		md->cnt += n;
+		return 1;
+	}
+	return 0;
+}
+
+DG_DYNARR_INLINE void
+dg__dynarr_delete(void** arr, dg__dynarr_md* md, size_t itemsize, size_t idx, size_t n)
+{
+	size_t cnt = md->cnt;
+	if(idx < cnt)
+	{
+		if(idx+n >= cnt)  md->cnt = idx; // removing last element(s) => just reduce count
+		else
+		{
+			unsigned char* p = (unsigned char*)*arr;
+			// move all items following a[idx+n] to a[idx]
+			memmove(p+itemsize*idx, p+itemsize*(idx+n), itemsize*(cnt - (idx+n)));
+			md->cnt -= n;
+		}
+	}
+}
+
+DG_DYNARR_INLINE void
+dg__dynarr_deletefast(void** arr, dg__dynarr_md* md, size_t itemsize, size_t idx, size_t n)
+{
+	size_t cnt = md->cnt;
+	if(idx < cnt)
+	{
+		if(idx+n >= cnt)  md->cnt = idx; // removing last element(s) => just reduce count
+		else
+		{
+			unsigned char* p = (unsigned char*)*arr;
+			// copy the last n items to a[idx] - but handle the case that
+			// the array has less than n elements left after the deleted elements
+			size_t numItemsAfterDeleted = cnt - (idx+n);
+			size_t m = (n < numItemsAfterDeleted) ? n : numItemsAfterDeleted;
+			memcpy(p+itemsize*idx, p+itemsize*(cnt - m), itemsize*m);
+			md->cnt -= n;
+		}
+	}
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // DG__DYNARR_H
+
+
+// ############## Implementation of non-inline functions ##############
+
+#ifdef DG_DYNARR_IMPLEMENTATION
+
+// by default, C's malloc(), realloc() and free() is used to allocate/free heap memory.
+// you can #define DG_DYNARR_MALLOC, DG_DYNARR_REALLOC and DG_DYNARR_FREE
+// to provide alternative implementations like Win32 Heap(Re)Alloc/HeapFree
+// 
+#ifndef DG_DYNARR_MALLOC
+	#define DG_DYNARR_MALLOC(elemSize, numElems)  malloc(elemSize*numElems)
+
+	// oldNumElems is not used here, but maybe you need it for your allocator
+	// to copy the old elements over
+	#define DG_DYNARR_REALLOC(ptr, elemSize, oldNumElems, newCapacity) \
+		realloc(ptr, elemSize*newCapacity);
+
+	#define DG_DYNARR_FREE(ptr)  free(ptr)
+#endif
+
+// you can #define DG_DYNARR_OUT_OF_MEMORY to some code that will be executed
+// if allocating memory fails
+#ifndef DG_DYNARR_OUT_OF_MEMORY
+	#define DG_DYNARR_OUT_OF_MEMORY  DG_DYNARR_ASSERT(0, "Out of Memory!");
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DG_DYNARR_DEF void
+dg__dynarr_free(void** p, dg__dynarr_md* md)
+{
+	// only free memory if it doesn't point to external memory
+	if(!(md->cap & DG__DYNARR_SIZE_T_MSB))
+	{
+		DG_DYNARR_FREE(*p);
+		*p = NULL;
+		md->cap = 0;
+	}
+	md->cnt = 0;
+}
+
+
+DG_DYNARR_DEF int
+dg__dynarr_grow(void** arr, dg__dynarr_md* md, size_t itemsize, size_t min_needed)
+{
+	size_t cap = md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB;
+
+	DG_DYNARR_ASSERT(min_needed > cap, "dg__dynarr_grow() should only be called if storage actually needs to grow!");
+
+	if(min_needed < DG__DYNARR_SIZE_T_MSB)
+	{
+		size_t newcap = (cap > 4) ? (2*cap) : 8; // allocate for at least 8 elements
+		// make sure not to set DG__DYNARR_SIZE_T_MSB (unlikely anyway)
+		if(newcap >= DG__DYNARR_SIZE_T_MSB)  newcap = DG__DYNARR_SIZE_T_MSB-1;
+		if(min_needed > newcap)  newcap = min_needed;
+
+		// the memory was allocated externally, don't free it, just copy contents
+		if(md->cap & DG__DYNARR_SIZE_T_MSB)
+		{
+			void* p = DG_DYNARR_MALLOC(itemsize, newcap);
+			if(p != NULL)  memcpy(p, *arr, itemsize*md->cnt);
+			*arr = p;
+		}
+		else
+		{
+			void* p = DG_DYNARR_REALLOC(*arr, itemsize, md->cnt, newcap);
+			if(p == NULL)  DG_DYNARR_FREE(*arr); // realloc failed, at least don't leak memory
+			*arr = p;
+		}
+
+		// TODO: handle OOM by setting highest bit of count and keeping old data?
+
+		if(*arr)  md->cap = newcap;
+		else
+		{
+			md->cap = 0;
+			md->cnt = 0;
+			
+			DG_DYNARR_OUT_OF_MEMORY ;
+			
+			return 0;
+		}
+		return 1;
+	}
+	DG_DYNARR_ASSERT(min_needed < DG__DYNARR_SIZE_T_MSB, "Arrays must stay below SIZE_T_MAX/2 elements!");
+	return 0;
+}
+
+DG_DYNARR_DEF void
+dg__dynarr_shrink_to_fit(void** arr, dg__dynarr_md* md, size_t itemsize)
+{
+	// only do this if we allocated the memory ourselves
+	if(!(md->cap & DG__DYNARR_SIZE_T_MSB))
+	{
+		size_t cnt = md->cnt;
+		if(cnt == 0)  dg__dynarr_free(arr, md);
+		else if((md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) > cnt)
+		{
+			void* p = DG_DYNARR_MALLOC(itemsize, cnt);
+			if(p != NULL)
+			{
+				memcpy(p, *arr, cnt*itemsize);
+				md->cap = cnt;
+				DG_DYNARR_FREE(*arr);
+				*arr = p;
+			}
+		}
+	}
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // DG_DYNARR_IMPLEMENTATION
diff --git a/menu/menu_animation.c b/menu/menu_animation.c
index 559aa7e5bf..b0e4cd9cae 100644
--- a/menu/menu_animation.c
+++ b/menu/menu_animation.c
@@ -23,6 +23,10 @@
 #include <retro_miscellaneous.h>
 #include <features/features_cpu.h>
 
+#define DG_DYNARR_IMPLEMENTATION
+#include <array/dynarray.h>
+#undef DG_DYNARR_IMPLEMENTATION
+
 #include "menu_animation.h"
 #include "../configuration.h"
 #include "../performance_counters.h"
@@ -31,7 +35,6 @@
 
 struct tween
 {
-   bool        alive;
    float       duration;
    float       running_since;
    float       initial_value;
@@ -43,14 +46,13 @@ struct tween
    void        *userdata;
 };
 
+DA_TYPEDEF(struct tween, tween_array_t);
+
 struct menu_animation
 {
-   struct tween *list;
-   bool need_defrag;
-
-   size_t capacity;
-   size_t size;
-   size_t first_dead;
+   tween_array_t list;
+   tween_array_t pending;
+   bool in_update;
 };
 
 typedef struct menu_animation menu_animation_t;
@@ -314,12 +316,22 @@ static void menu_animation_ticker_generic(uint64_t idx,
    *width = max_width;
 }
 
+void menu_animation_init(void)
+{
+   da_init(anim.list);
+   da_init(anim.pending);
+}
+
+void menu_animation_free(void)
+{
+   da_free(anim.list);
+   da_free(anim.pending);
+}
+
 bool menu_animation_push(menu_animation_ctx_entry_t *entry)
 {
    struct tween t;
-   struct tween *target = NULL;
 
-   t.alive              = true;
    t.duration           = entry->duration;
    t.running_since      = 0;
    t.initial_value      = *entry->subject;
@@ -446,66 +458,24 @@ bool menu_animation_push(menu_animation_ctx_entry_t *entry)
    /* ignore born dead tweens */
    if (!t.easing || t.duration == 0 || t.initial_value == t.target_value)
       return false;
-
-   if (anim.first_dead < anim.size && !anim.list[anim.first_dead].alive)
-      target = &anim.list[anim.first_dead++];
+   
+   if (anim.in_update)
+      da_push(anim.pending, t);
    else
-   {
-      if (anim.size >= anim.capacity)
-      {
-         anim.capacity++;
-         anim.list = (struct tween*)realloc(anim.list,
-               anim.capacity * sizeof(struct tween));
-      }
-
-      target = &anim.list[anim.size++];
-   }
-
-   *target = t;
-
-   anim.need_defrag = true;
+      da_push(anim.list, t);
 
    return true;
 }
 
-static int menu_animation_defrag_cmp(const void *a, const void *b)
-{
-   const struct tween *ta = (const struct tween *)a;
-   const struct tween *tb = (const struct tween *)b;
-
-   return tb->alive - ta->alive;
-}
-
-/* defragments and shrinks the tween list when possible */
-static void menu_animation_defrag()
-{
-   size_t i;
-
-   qsort(anim.list, anim.size, sizeof(anim.list[0]), menu_animation_defrag_cmp);
-
-   for (i = anim.size-1; i > 0; i--)
-   {
-      if (anim.list[i].alive)
-         break;
-
-      anim.size--;
-   }
-
-   anim.first_dead = anim.size;
-   anim.need_defrag = false;
-}
-
 bool menu_animation_update(float delta_time)
 {
    unsigned i;
-   unsigned active_tweens = 0;
+   
+   anim.in_update = true;
 
-   for(i = 0; i < anim.size; i++)
+   for(i = 0; i < da_count(anim.list); i++)
    {
-      struct tween *tween = &anim.list[i];
-      if (!tween || !tween->alive)
-         continue;
-
+      struct tween *tween = da_getptr(anim.list, i);
       tween->running_since += delta_time;
 
       *tween->subject = tween->easing(
@@ -517,32 +487,25 @@ bool menu_animation_update(float delta_time)
       if (tween->running_since >= tween->duration)
       {
          *tween->subject = tween->target_value;
-         tween->alive    = false;
-         anim.need_defrag = true;
 
          if (tween->cb)
             tween->cb(tween->userdata);
+         
+         da_delete(anim.list, i);
+         i--;
       }
-
-      if (tween->running_since < tween->duration)
-         active_tweens += 1;
    }
-
-   if (anim.need_defrag)
-      menu_animation_defrag();
-
-   if (!active_tweens)
+   
+   if (da_count(anim.pending) > 0)
    {
-      anim.size           = 0;
-      anim.first_dead     = 0;
-      anim.need_defrag    = false;
-      return false;
+      da_addn(anim.list, anim.pending.p, da_count(anim.pending));
+      da_clear(anim.pending);
    }
 
+   anim.in_update = false;
+   animation_is_active = da_count(anim.list) > 0;
 
-   animation_is_active = true;
-
-   return true;
+   return animation_is_active;
 }
 
 bool menu_animation_ticker(const menu_animation_ctx_ticker_t *ticker)
@@ -626,18 +589,14 @@ bool menu_animation_kill_by_tag(menu_animation_ctx_tag *tag)
    if (!tag || *tag == (uintptr_t)-1)
       return false;
 
-   for (i = 0; i < anim.size; ++i)
+   for (i = 0; i < da_count(anim.list); ++i)
    {
-      if (anim.list[i].tag != *tag)
+      struct tween *t = da_getptr(anim.list, i);
+      if (t->tag != *tag)
          continue;
-
-      anim.list[i].alive   = false;
-      anim.list[i].subject = NULL;
-
-      if (i < anim.first_dead)
-         anim.first_dead = i;
-
-      anim.need_defrag = true;
+      
+      da_delete(anim.list, i);
+      --i;
    }
 
    return true;
@@ -648,24 +607,19 @@ void menu_animation_kill_by_subject(menu_animation_ctx_subject_t *subject)
    unsigned i, j,  killed = 0;
    float            **sub = (float**)subject->data;
 
-   for (i = 0; i < anim.size && killed < subject->count; ++i)
+   for (i = 0; i < da_count(anim.list) && killed < subject->count; ++i)
    {
-      if (!anim.list[i].alive)
-         continue;
+      struct tween *t = da_getptr(anim.list, i);
 
       for (j = 0; j < subject->count; ++j)
       {
-         if (anim.list[i].subject != sub[j])
+         if (t->subject != sub[j])
             continue;
 
-         anim.list[i].alive   = false;
-         anim.list[i].subject = NULL;
-
-         if (i < anim.first_dead)
-            anim.first_dead = i;
+         da_delete(anim.list, i);
+         --i;
 
          killed++;
-         anim.need_defrag = true;
          break;
       }
    }
@@ -684,13 +638,14 @@ bool menu_animation_ctl(enum menu_animation_ctl_state state, void *data)
          {
             size_t i;
 
-            for (i = 0; i < anim.size; i++)
+            for (i = 0; i < da_count(anim.list); i++)
             {
-               if (anim.list[i].subject)
-                  anim.list[i].subject = NULL;
+               struct tween *t = da_getptr(anim.list, i);
+               if (t->subject)
+                  t->subject = NULL;
             }
 
-            free(anim.list);
+            da_free(anim.list);
 
             memset(&anim, 0, sizeof(menu_animation_t));
          }
diff --git a/menu/menu_animation.h b/menu/menu_animation.h
index d1bc513489..d5a50acedd 100644
--- a/menu/menu_animation.h
+++ b/menu/menu_animation.h
@@ -131,6 +131,10 @@ void menu_timer_start(menu_timer_t *timer, menu_timer_ctx_entry_t *timer_entry);
 
 void menu_timer_kill(menu_timer_t *timer);
 
+void menu_animation_init(void);
+
+void menu_animation_free(void);
+
 bool menu_animation_update(float delta_time);
 
 bool menu_animation_get_ideal_delta_time(menu_animation_ctx_delta_t *delta);
diff --git a/menu/menu_driver.c b/menu/menu_driver.c
index fbbfe10227..8560e89234 100644
--- a/menu/menu_driver.c
+++ b/menu/menu_driver.c
@@ -2099,11 +2099,17 @@ static bool menu_driver_context_reset(bool video_is_threaded)
 
 bool menu_driver_init(bool video_is_threaded)
 {
+   menu_animation_init();
    if (menu_driver_init_internal(video_is_threaded))
       return menu_driver_context_reset(video_is_threaded);
    return false;
 }
 
+void menu_driver_free(void)
+{
+   menu_animation_free();
+}
+
 void menu_driver_navigation_set(bool scroll)
 {
    if (menu_driver_ctx->navigation_set)
diff --git a/menu/menu_driver.h b/menu/menu_driver.h
index 33f111a8ca..dcded3f3b3 100644
--- a/menu/menu_driver.h
+++ b/menu/menu_driver.h
@@ -678,6 +678,8 @@ bool menu_driver_push_list(menu_ctx_displaylist_t *disp_list);
 
 bool menu_driver_init(bool video_is_threaded);
 
+void menu_driver_free(void);
+
 void menu_driver_set_thumbnail_system(char *s, size_t len);
 
 void menu_driver_set_thumbnail_content(char *s, size_t len);