From 6c7ac5e74933c873f63e2637f2b68b4812cd043d Mon Sep 17 00:00:00 2001
From: nakst <>
Date: Mon, 20 Sep 2021 17:08:54 +0100
Subject: [PATCH] EsButtonSetIconFromBits; icons for 2048 game

---
 apps/2048.ini       |   5 ++-
 apps/test.cpp       |   2 +-
 desktop/api.cpp     |  45 +++++++++++++++----
 desktop/desktop.cpp |  34 +++++++++++++-
 desktop/gui.cpp     | 105 ++++++++++++++++++++++++++++++--------------
 desktop/os.header   |   6 ++-
 desktop/syscall.cpp |   6 +--
 desktop/text.cpp    |   4 +-
 desktop/theme.cpp   |   2 +-
 res/2048_icon16.png | Bin 0 -> 1019 bytes
 res/2048_icon32.png | Bin 0 -> 1724 bytes
 shared/strings.cpp  |   2 +-
 util/api_table.ini  |   4 +-
 util/build_core.c   |   2 +-
 14 files changed, 163 insertions(+), 54 deletions(-)
 create mode 100644 res/2048_icon16.png
 create mode 100644 res/2048_icon32.png

diff --git a/apps/2048.ini b/apps/2048.ini
index f06f8e5..d4f3e0d 100644
--- a/apps/2048.ini
+++ b/apps/2048.ini
@@ -1,7 +1,10 @@
 [general]
 name=2048
-icon=icon_applications_other
 use_single_instance=1
 
 [build]
 source=apps/2048.cpp
+
+[embed]
+$Icons/16=res/2048_icon16.png
+$Icons/32=res/2048_icon32.png
diff --git a/apps/test.cpp b/apps/test.cpp
index 99d9820..f3fee21 100644
--- a/apps/test.cpp
+++ b/apps/test.cpp
@@ -130,7 +130,7 @@ const EsStyle stylePanel = {
 int TestCanvasMessage(EsElement *, EsMessage *message) {
 	if (message->type == ES_MSG_PAINT) {
 		size_t dataBytes;
-		const void *data = EsEmbeddedFileGet("test", -1, &dataBytes);
+		const void *data = EsBundleFind(nullptr, "test", -1, &dataBytes);
 		if (data) EsDrawVectorFile(message->painter, EsPainterBoundsClient(message->painter), data, dataBytes);
 
 		uint32_t cornerRadii[4] = { 10, 20, 30, 40 };
diff --git a/desktop/api.cpp b/desktop/api.cpp
index d24e4f9..2793b76 100644
--- a/desktop/api.cpp
+++ b/desktop/api.cpp
@@ -76,6 +76,7 @@ struct EsFileStore {
 		EsHandle handle;
 
 		struct {
+			const EsBundle *bundle;
 			char *path;
 			size_t pathBytes;
 		};
@@ -115,6 +116,21 @@ struct Work {
 	EsGeneric context;
 };
 
+struct EsBundle {
+	const BundleHeader *base;
+	ptrdiff_t bytes;
+};
+
+const EsBundle bundleDefault = {
+	.base = (const BundleHeader *) BUNDLE_FILE_MAP_ADDRESS,
+	.bytes = -1,
+};
+
+const EsBundle bundleDesktop = {
+	.base = (const BundleHeader *) BUNDLE_FILE_DESKTOP_MAP_ADDRESS,
+	.bytes = -1,
+};
+
 struct {
 	Array<EsSystemConfigurationGroup> systemConfigurationGroups;
 	EsMutex systemConfigurationMutex;
@@ -161,7 +177,7 @@ void UndoManagerDestroy(EsUndoManager *manager);
 int TextGetStringWidth(EsElement *element, const EsTextStyle *style, const char *string, size_t stringBytes);
 struct APIInstance *InstanceSetup(EsInstance *instance);
 EsTextStyle TextPlanGetPrimaryStyle(EsTextPlan *plan);
-EsFileStore *FileStoreCreateFromEmbeddedFile(const char *path, size_t pathBytes);
+EsFileStore *FileStoreCreateFromEmbeddedFile(const EsBundle *bundle, const char *path, size_t pathBytes);
 EsFileStore *FileStoreCreateFromPath(const char *path, size_t pathBytes);
 EsFileStore *FileStoreCreateFromHandle(EsHandle handle);
 void FileStoreCloseHandle(EsFileStore *fileStore);
@@ -678,7 +694,7 @@ EsFileStore *FileStoreCreateFromHandle(EsHandle handle) {
 	return fileStore;
 }
 
-EsFileStore *FileStoreCreateFromEmbeddedFile(const char *name, size_t nameBytes) {
+EsFileStore *FileStoreCreateFromEmbeddedFile(const EsBundle *bundle, const char *name, size_t nameBytes) {
 	EsFileStore *fileStore = (EsFileStore *) EsHeapAllocate(sizeof(EsFileStore) + nameBytes, false);
 	if (!fileStore) return nullptr;
 	EsMemoryZero(fileStore, sizeof(EsFileStore));
@@ -687,6 +703,7 @@ EsFileStore *FileStoreCreateFromEmbeddedFile(const char *name, size_t nameBytes)
 	fileStore->error = ES_SUCCESS;
 	fileStore->path = (char *) (fileStore + 1);
 	fileStore->pathBytes = nameBytes;
+	fileStore->bundle = bundle;
 	EsMemoryCopy(fileStore->path, name, nameBytes);
 	return fileStore;
 }
@@ -1732,18 +1749,24 @@ void EsInstanceSetActiveUndoManager(EsInstance *_instance, EsUndoManager *manage
 	EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_REDO), !manager->redoStack.Length());
 }
 
-const void *EsEmbeddedFileGet(const char *_name, ptrdiff_t nameBytes, size_t *byteCount) {
+const void *EsBundleFind(const EsBundle *bundle, const char *_name, ptrdiff_t nameBytes, size_t *byteCount) {
+	if (!bundle) {
+		bundle = &bundleDefault;
+	}
+
 	if (nameBytes == -1) {
 		nameBytes = EsCStringLength(_name);
 	}
 
-	const BundleHeader *header = (const BundleHeader *) BUNDLE_FILE_MAP_ADDRESS;
-
-	if (nameBytes > 9 && 0 == EsMemoryCompare(_name, "$Desktop/", 9)) {
-		header = (const BundleHeader *) BUNDLE_FILE_DESKTOP_MAP_ADDRESS;
-		_name += 9, nameBytes -= 9;
+	if (bundle->bytes != -1) {
+		if ((size_t) bundle->bytes < sizeof(BundleHeader) 
+				|| (size_t) (bundle->bytes - sizeof(BundleHeader)) / sizeof(BundleFile) < bundle->base->fileCount
+				|| bundle->base->signature != BUNDLE_SIGNATURE || bundle->base->version != 1) {
+			return nullptr;
+		}
 	}
 
+	const BundleHeader *header = bundle->base;
 	const BundleFile *files = (const BundleFile *) (header + 1);
 	uint64_t name = CalculateCRC64(_name, nameBytes);
 
@@ -1753,6 +1776,12 @@ const void *EsEmbeddedFileGet(const char *_name, ptrdiff_t nameBytes, size_t *by
 				*byteCount = files[i].bytes;
 			}
 
+			if (bundle->bytes != -1) {
+				if (files[i].offset >= (size_t) bundle->bytes || files[i].bytes > (size_t) (bundle->bytes - files[i].offset)) {
+					return nullptr;
+				}
+			}
+
 			return (const uint8_t *) header + files[i].offset;
 		}
 	}
diff --git a/desktop/desktop.cpp b/desktop/desktop.cpp
index 6d1956d..5d7f7d2 100644
--- a/desktop/desktop.cpp
+++ b/desktop/desktop.cpp
@@ -1261,9 +1261,41 @@ void InstanceBlankTabCreate(EsMessage *message) {
 		if (application->hidden) continue;
 
 		EsButton *button = EsButtonCreate(buttonGroup, ES_CELL_H_FILL | ES_ELEMENT_NO_FOCUS_ON_CLICK, ES_STYLE_BUTTON_GROUP_ITEM, application->cName);
-		EsButtonSetIcon(button, (EsStandardIcon) application->iconID ?: ES_ICON_APPLICATION_DEFAULT_ICON);
 		button->userData = application;
 
+		if (application->iconID) {
+			EsButtonSetIcon(button, (EsStandardIcon) application->iconID);
+		} else {
+			EsButtonSetIcon(button, ES_ICON_APPLICATION_DEFAULT_ICON);
+
+			// TODO Load the icon asynchronously.
+			// TODO Load the correct icon size.
+			// TODO Reload the icon if the UI scale factor changes.
+			// TODO Cache the icon bits.
+			// TODO Generic icon and thumbnail cache in the API, based off the one from File Manager?
+
+			size_t fileBytes;
+			void *file = EsFileMap(application->cExecutable, -1, &fileBytes, ES_MAP_OBJECT_READ_ONLY);
+			EsBundle bundle = { .base = (const BundleHeader *) file, .bytes = (ptrdiff_t) fileBytes };
+
+			if (file) {
+				size_t icon32Bytes;
+				const void *icon32 = EsBundleFind(&bundle, EsLiteral("$Icons/32"), &icon32Bytes);
+
+				if (icon32) {
+					uint32_t width, height;
+					uint32_t *bits = (uint32_t *) EsImageLoad(icon32, icon32Bytes, &width, &height, 4);
+
+					if (bits) {
+						EsButtonSetIconFromBits(button, bits, width, height, width * 4);
+						EsHeapFree(bits);
+					}
+				}
+
+				EsObjectUnmap(file);
+			}
+		}
+
 		EsButtonOnCommand(button, [] (EsInstance *, EsElement *element, EsCommand *) {
 			ApplicationInstance *instance = ApplicationInstanceFindByWindowID(element->window->id);
 
diff --git a/desktop/gui.cpp b/desktop/gui.cpp
index 384ee25..c2aae2b 100644
--- a/desktop/gui.cpp
+++ b/desktop/gui.cpp
@@ -295,6 +295,15 @@ struct EsButton : EsElement {
 	EsCommand *command;
 	EsCommandCallback onCommand;
 	EsElement *checkBuddy;
+	EsImageDisplay *imageDisplay;
+};
+
+struct EsImageDisplay : EsElement {
+	void *source;
+	size_t sourceBytes;
+
+	uint32_t *bits;
+	size_t width, height, stride;
 };
 
 struct ScrollPane {
@@ -3729,6 +3738,14 @@ int ProcessButtonMessage(EsElement *element, EsMessage *message) {
 			ES_RECT_2S(message->painter->width, message->painter->height), 
 			button->label, button->labelBytes, button->iconID, 
 			(button->flags & ES_BUTTON_DROPDOWN) ? ES_DRAW_CONTENT_MARKER_DOWN_ARROW : ES_FLAGS_DEFAULT);
+	} else if (message->type == ES_MSG_PAINT_ICON) {
+		if (button->imageDisplay) {
+			EsRectangle imageSize = ES_RECT_2S(button->imageDisplay->width, button->imageDisplay->height);
+			EsRectangle bounds = EsRectangleFit(EsPainterBoundsClient(message->painter), imageSize, true);
+			EsImageDisplayPaint(button->imageDisplay, message->painter, bounds);
+		} else {
+			return 0;
+		}
 	} else if (message->type == ES_MSG_GET_WIDTH) {
 		if (!button->measurementCache.Get(message, &button->state)) {
 			EsTextStyle textStyle;
@@ -3760,6 +3777,11 @@ int ProcessButtonMessage(EsElement *element, EsMessage *message) {
 			elements.FindAndDeleteSwap(button, true);
 			button->command->elements = elements.array;
 		}
+
+		if (button->imageDisplay) {
+			EsElementDestroy(button->imageDisplay);
+			button->imageDisplay = nullptr;
+		}
 	} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) {
 	} else if (message->type == ES_MSG_MOUSE_LEFT_CLICK) {
 		if (button->flags & ES_BUTTON_CHECKBOX) {
@@ -3885,10 +3907,28 @@ EsButton *EsButtonCreate(EsElement *parent, uint64_t flags, const EsStyle *style
 void EsButtonSetIcon(EsButton *button, uint32_t iconID) {
 	EsMessageMutexCheck();
 
+	if (button->imageDisplay) {
+		EsElementDestroy(button->imageDisplay);
+		button->imageDisplay = nullptr;
+	}
+
 	button->iconID = iconID;
 	button->Repaint(true);
 }
 
+void EsButtonSetIconFromBits(EsButton *button, const uint32_t *bits, size_t width, size_t height, size_t stride) {
+	EsMessageMutexCheck();
+
+	if (!button->imageDisplay) {
+		button->imageDisplay = EsImageDisplayCreate(button);
+	}
+
+	if (button->imageDisplay) {
+		EsImageDisplayLoadBits(button->imageDisplay, bits, width, height, stride);
+		button->Repaint(true);
+	}
+}
+
 void EsButtonOnCommand(EsButton *button, EsCommandCallback onCommand, EsCommand *command) {
 	EsMessageMutexCheck();
 
@@ -4991,44 +5031,43 @@ EsSplitter *EsSplitterCreate(EsElement *parent, uint64_t flags, const EsStyle *s
 // 	clipboard
 // 	zoom/pan
 
-struct EsImageDisplay : EsElement {
-	void *source;
-	size_t sourceBytes;
+void EsImageDisplayPaint(EsImageDisplay *display, EsPainter *painter, EsRectangle bounds) {
+	if (!display->bits && !display->source) {
+		return;
+	}
 
-	uint32_t *bits;
-	size_t width, height, stride;
-};
+	if (!display->bits && display->source) {
+		uint32_t width, height;
+		uint8_t *bits = EsImageLoad((uint8_t *) display->source, display->sourceBytes, &width, &height, 4);
+
+		if (bits) {
+			display->bits = (uint32_t *) bits;
+			display->width = width;
+			display->height = height;
+			display->stride = width * 4;
+		}
+
+		if (~display->flags & UI_STATE_CHECK_VISIBLE) {
+			if (display->window->checkVisible.Add(display)) {
+				display->state |= UI_STATE_CHECK_VISIBLE;
+			}
+		}
+	}
+
+	EsPaintTarget source = {};
+	source.bits = display->bits;
+	source.width = display->width;
+	source.height = display->height;
+	source.stride = display->stride;
+	source.fullAlpha = ~display->flags & ES_IMAGE_DISPLAY_FULLY_OPAQUE;
+	EsDrawPaintTarget(painter, &source, bounds, ES_RECT_4(0, display->width, 0, display->height), 0xFF);
+}
 
 int ProcessImageDisplayMessage(EsElement *element, EsMessage *message) {
 	EsImageDisplay *display = (EsImageDisplay *) element;
 
-	if (message->type == ES_MSG_PAINT && (display->bits || display->source)) {
-		if (!display->bits && display->source) {
-			uint32_t width, height;
-			uint8_t *bits = EsImageLoad((uint8_t *) display->source, display->sourceBytes, &width, &height, 4);
-
-			if (bits) {
-				display->bits = (uint32_t *) bits;
-				display->width = width;
-				display->height = height;
-				display->stride = width * 4;
-			}
-
-			if (~display->flags & UI_STATE_CHECK_VISIBLE) {
-				if (display->window->checkVisible.Add(display)) {
-					display->state |= UI_STATE_CHECK_VISIBLE;
-				}
-			}
-		}
-
-		EsPaintTarget source = {};
-		source.bits = display->bits;
-		source.width = display->width;
-		source.height = display->height;
-		source.stride = display->stride;
-		EsDrawPaintTarget(message->painter, &source, 
-				EsPainterBoundsInset(message->painter), 
-				ES_RECT_4(0, display->width, 0, display->height), 0xFF);
+	if (message->type == ES_MSG_PAINT) {
+		EsImageDisplayPaint(display, message->painter, EsPainterBoundsInset(message->painter));
 	} else if (message->type == ES_MSG_GET_WIDTH) {
 		message->measure.width = display->width;
 	} else if (message->type == ES_MSG_GET_HEIGHT) {
diff --git a/desktop/os.header b/desktop/os.header
index ae2dd4e..8740abf 100644
--- a/desktop/os.header
+++ b/desktop/os.header
@@ -20,6 +20,7 @@ opaque_type EsUndoManager      none;
 opaque_type EsHeap             none;
 opaque_type EsFileStore        none;
 opaque_type EsUserTask         none;
+opaque_type EsBundle           none;
 
 type_name uint8_t EsNodeType;
 type_name intptr_t EsError;
@@ -570,6 +571,7 @@ define ES_LIST_DISPLAY_MARKER_TYPE_MASK (0xFF << 0)
 
 define ES_IMAGE_DISPLAY_DECODE_WHEN_NEEDED (1 << 0) // The image is only kept in its decoded state when the display is on-screen.
 define ES_IMAGE_DISPLAY_MANUAL_SIZE        (1 << 1) // The display will be manually sized; its size does not depend on the loaded image.
+define ES_IMAGE_DISPLAY_FULLY_OPAQUE       (1 << 2) // The loaded image will always be fully opaque.
 
 define ES_COMMAND_SYSTEM_START		(0xF0000000)
 define ES_COMMAND_DELETE		(0xF0000001)
@@ -1947,7 +1949,7 @@ function void EsINIZeroTerminate(EsINIState *s);
 
 // File systems.
 
-function const void *EsEmbeddedFileGet(STRING name, size_t *byteCount = ES_NULL);
+function const void *EsBundleFind(const EsBundle *bundle, STRING name, size_t *byteCount = ES_NULL); // Pass null as the bundle to use the current application's bundle.
 
 function ptrdiff_t EsDirectoryEnumerateChildren(STRING path, EsDirectoryChild **buffer); // Free buffer with EsHeapFree. Returns number of children.
 
@@ -2390,6 +2392,7 @@ private function void _EsUISetFont(EsFontFamily id);
 function EsButton *EsButtonCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL, STRING label = BLANK_STRING);
 
 function void EsButtonSetIcon(EsButton *button, uint32_t iconID);
+function void EsButtonSetIconFromBits(EsButton *button, const uint32_t *bits, size_t width, size_t height, size_t stride);
 function void EsButtonSetCheck(EsButton *button, EsCheckState checkState = ES_CHECK_CHECKED, bool sendUpdatedMessage = true);
 function EsCheckState EsButtonGetCheck(EsButton *button);
 function void EsButtonOnCommand(EsButton *button, EsCommandCallback callback, EsCommand *command = ES_NULL); // TODO Public property?
@@ -2450,6 +2453,7 @@ function void EsIconDisplaySetIcon(EsIconDisplay *display, uint32_t iconID);
 function EsImageDisplay *EsImageDisplayCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL);
 function void EsImageDisplayLoadBits(EsImageDisplay *display, const uint32_t *bits, size_t width, size_t height, size_t stride);
 function void EsImageDisplayLoadFromMemory(EsImageDisplay *display, const void *buffer, size_t bufferBytes);
+function void EsImageDisplayPaint(EsImageDisplay *display, EsPainter *painter, EsRectangle bounds);
 
 function EsTextDisplay *EsTextDisplayCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL, STRING label = BLANK_STRING);
 function void EsTextDisplaySetContents(EsTextDisplay *display, STRING contents = BLANK_STRING);
diff --git a/desktop/syscall.cpp b/desktop/syscall.cpp
index 61ff817..667795c 100644
--- a/desktop/syscall.cpp
+++ b/desktop/syscall.cpp
@@ -352,7 +352,7 @@ void *EsFileStoreReadAll(EsFileStore *file, size_t *fileSize) {
 		return EsFileReadAll(file->path, file->pathBytes, fileSize, &file->error);
 	} else if (file->type == FILE_STORE_EMBEDDED_FILE) {
 		size_t _fileSize;
-		const void *data = EsEmbeddedFileGet(file->path, file->pathBytes, &_fileSize);
+		const void *data = EsBundleFind(file->bundle, file->path, file->pathBytes, &_fileSize);
 		void *copy = EsHeapAllocate(_fileSize, false);
 		if (!copy) return nullptr;
 		if (fileSize) *fileSize = _fileSize;
@@ -404,7 +404,7 @@ EsFileOffsetDifference EsFileStoreGetSize(EsFileStore *file) {
 		}
 	} else if (file->type == FILE_STORE_EMBEDDED_FILE) {
 		size_t size;
-		EsEmbeddedFileGet(file->path, file->pathBytes, &size);
+		EsBundleFind(file->bundle, file->path, file->pathBytes, &size);
 		return size;
 	} else {
 		EsAssert(false);
@@ -421,7 +421,7 @@ void *EsFileStoreMap(EsFileStore *file, size_t *fileSize, uint32_t flags) {
 	} else if (file->type == FILE_STORE_PATH) {
 		return EsFileMap(file->path, file->pathBytes, fileSize, flags);
 	} else if (file->type == FILE_STORE_EMBEDDED_FILE) {
-		return (void *) EsEmbeddedFileGet(file->path, file->pathBytes, fileSize);
+		return (void *) EsBundleFind(file->bundle, file->path, file->pathBytes, fileSize);
 	} else {
 		EsAssert(false);
 		return nullptr;
diff --git a/desktop/text.cpp b/desktop/text.cpp
index c74a153..419d98f 100644
--- a/desktop/text.cpp
+++ b/desktop/text.cpp
@@ -512,7 +512,7 @@ void FontInitialise() {
 					size_t fileIndex = weight - 1 + italic * 9;
 
 					if (item->valueBytes && item->value[0] == ':') {
-						entry.files[fileIndex] = FileStoreCreateFromEmbeddedFile(item->value + 1, item->valueBytes - 1);
+						entry.files[fileIndex] = FileStoreCreateFromEmbeddedFile(&bundleDesktop, item->value + 1, item->valueBytes - 1);
 					} else {
 						entry.files[fileIndex] = FileStoreCreateFromPath(item->value, item->valueBytes);
 					}
@@ -1373,7 +1373,7 @@ bool EsDrawStandardIcon(EsPainter *painter, uint32_t id, int size, EsRectangle r
 
 	if (!cacheEntry->data) {
 		if (!iconManagement.standardPack) {
-			iconManagement.standardPack = (const uint8_t *) EsEmbeddedFileGet(EsLiteral("$Desktop/Icons.dat"), &iconManagement.standardPackSize);
+			iconManagement.standardPack = (const uint8_t *) EsBundleFind(&bundleDesktop, EsLiteral("Icons.dat"), &iconManagement.standardPackSize);
 		}
 
 		iconManagement.buffer = (char *) EsHeapAllocate((iconManagement.bufferAllocated = 131072), false);
diff --git a/desktop/theme.cpp b/desktop/theme.cpp
index 62b393f..0d79c1d 100644
--- a/desktop/theme.cpp
+++ b/desktop/theme.cpp
@@ -1318,7 +1318,7 @@ const char *GetConstantString(const char *cKey) {
 
 bool ThemeInitialise() {
 	EsBuffer data = {};
-	data.in = (const uint8_t *) EsEmbeddedFileGet(EsLiteral("$Desktop/Theme.dat"), &data.bytes);
+	data.in = (const uint8_t *) EsBundleFind(&bundleDesktop, EsLiteral("Theme.dat"), &data.bytes);
 
 	const ThemeHeader *header = (const ThemeHeader *) EsBufferRead(&data, sizeof(ThemeHeader));
 
diff --git a/res/2048_icon16.png b/res/2048_icon16.png
new file mode 100644
index 0000000000000000000000000000000000000000..1898486ade55950131e397805e9c9b6b6fc9f4bd
GIT binary patch
literal 1019
zcmV<X0|fkuP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0004mX+uL$Nkc;*
zaB^>EX>4Tx04R}tkv&MmKpe$iTeT_`2Q!E`WT;LS#ELj-6^c+H)C#RSm|XfHG-*gu
zTpR`0f`cE6RR<SmT^(EnLGS~_&CN;CMN0f%QfLw5!Ery{-Fw`<1A_H3)2xnhK+|nA
zolJ<?+=>`}MGpi-5J#8HEMr!ZQt%yL_XzOyF2=L`&;2?2)V#%jfJi*U4AUlFC!X50
z4bJ<-0xQWX@j3CBNf#u3<htVV8|R|SL7pj?ne-g7Kr9y9SZQNcGBx5!;;5?WlpoA`
zta9Gstd*;*c~AbrNM2uF;yTS?B(Z=+ND!f*iVc)uBSx!EiiI@oCw%-vu3sXTLaq%k
za?GOw4YKP8|AXJ%T7~$8mlR0=oiC2_F#?2lfkw@7zK<QJaRP*%fh)b`uhfC*Ptt2G
zEqVm>Zvz+CElt@2E_Z;zCqp)6R|?S#77D=o8GTa@7`O#`SH0ev`#607GSpS-1~@nb
zMvIia=JW3E_TK(I)9mjDVb*e>s|sF}00006VoOIv0RI600RN!9r;`8x010qNS#tmY
z79{`x79{~mQY7#I000McNliru<p~rH00YaR{+Iv&0r*KoK~y-)rIRsC6hRn;zkyvj
z*mL#v?h0!YO$14_Kw2APVXSPdG*B2zVnJnNXerc23u$faEljM8!C06m6cjc#<W7!+
z<3!k<nP)L~>|&|gOg1zBH}C!bJowMg0Bml3e^pq!U}V66P}r3Lqi{&!cT^Nb(c1LI
z^NR-H)0YihnZ7_2#ek4T=&)!A*clW&|Ih*8_PIWDS7%HFaCDS2HXh;S%bSNPR1qtQ
z{ayeZHpc-7fV{waf5O;KKSzT0=@?-!h%AX&zBL2D=Em0&aG2+WdJT&UUabEF;K6*0
zc0EQWsLARP;nKVWSnKfkAYH=CA4`>*IE~oH?WHK(Nq}>LgDzlbBrTS1UIHT;Q!$<0
zJ_y=b{kW;t+Y;ctLxV2k-&O(emmkqc+5mKR`>eeA48Y?%bF>=?0-W<F6-Z)xX8=G;
zpy4dN#IL;`08SpSV8hyqg~B<CT8p!@4*_9o8NIz6V<1bXcyjLsLS$)z+wWoo&Q}yT
z=g2$TxR)yc^cPoo{Udby?rcgkoeI|WJ0x3A0kFmOzoy_tXYd<<312=K=j#;CV-S%q
zZCY2If^!@sDea|aV1$}Y?x$x!hIC>q$Q9UA8I#wR$qR@5xKRS)I9_}E?)}0rNsQ7{
pi9{t8VV$hi)~M{y|6`ih{{Sj032>#Vz1IK$002ovPDHLkV1kQa$glta

literal 0
HcmV?d00001

diff --git a/res/2048_icon32.png b/res/2048_icon32.png
new file mode 100644
index 0000000000000000000000000000000000000000..170676832300834d405e80bbc7d5ef3e8900aec5
GIT binary patch
literal 1724
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
S7mnrt0000<MNUMnLSTX*`x++z

literal 0
HcmV?d00001

diff --git a/shared/strings.cpp b/shared/strings.cpp
index a445f4c..b4e74e8 100644
--- a/shared/strings.cpp
+++ b/shared/strings.cpp
@@ -33,7 +33,7 @@ DEFINE_INTERFACE_STRING(CommonFileMakeCopy, "Make a copy");
 DEFINE_INTERFACE_STRING(CommonFileVersionHistory, "Version history" ELLIPSIS);
 DEFINE_INTERFACE_STRING(CommonFileShowInFileManager, "Show in File Manager" ELLIPSIS);
 DEFINE_INTERFACE_STRING(CommonFileMenuFileSize, "Size:");
-DEFINE_INTERFACE_STRING(CommonFileMenuFileLocation, "Location:");
+DEFINE_INTERFACE_STRING(CommonFileMenuFileLocation, "Where:");
 DEFINE_INTERFACE_STRING(CommonFileUnchanged, "(All changes saved.)");
 
 DEFINE_INTERFACE_STRING(CommonSearchOpen, "Search");
diff --git a/util/api_table.ini b/util/api_table.ini
index 9f7f741..501df37 100644
--- a/util/api_table.ini
+++ b/util/api_table.ini
@@ -366,7 +366,7 @@ EsElementStartTransition=364
 EsToolbarAddFileMenu=365
 EsFileWriteAllFromHandle=366
 EsFileWriteAllGatherFromHandle=367
-EsEmbeddedFileGet=368
+EsButtonSetIconFromBits=368
 EsFileStoreWriteAll=369
 EsInstanceOpenComplete=370
 EsInstanceSaveComplete=371
@@ -378,6 +378,7 @@ EsMountPointEnumerate=376
 EsMountPointGetVolumeInformation=377
 EsListViewInvalidateAll=378
 EsListViewGetFocusedItem=379
+EsBundleFind=380
 EsPathQueryInformation=381
 EsListViewCreateInlineTextbox=382
 EsTextboxStartEdit=383
@@ -458,3 +459,4 @@ EsPanelRadioGroupGetChecked=457
 EsTextboxEnableSmartQuotes=458
 EsBufferWriteInt8=459
 EsInstanceGetStartupRequest=460
+EsImageDisplayPaint=461
diff --git a/util/build_core.c b/util/build_core.c
index 8947e49..39e05b4 100644
--- a/util/build_core.c
+++ b/util/build_core.c
@@ -858,7 +858,7 @@ void OutputSystemConfiguration() {
 				FilePrintFormat(file, "%s=|Fonts:/%.*s.dat\n", fontLines[i].key, (int) fontLines[i].valueBytes - 4, fontLines[i].value);
 #endif
 			} else {
-				FilePrintFormat(file, "%s=:$Desktop/%s\n", fontLines[i].key, fontLines[i].value);
+				FilePrintFormat(file, "%s=:%s\n", fontLines[i].key, fontLines[i].value);
 			}
 		} else {
 			size_t bytes = EsINIFormat(fontLines + i, buffer, sizeof(buffer));