/ deps / KittyMemoryEx / KittyUtils.hpp
KittyUtils.hpp
  1  #pragma once
  2  
  3  #include <sys/mman.h>
  4  #include <sys/types.h>
  5  #include <sys/stat.h>
  6  
  7  #include <regex.h>
  8  #include <stdio.h>
  9  #include <fcntl.h>
 10  #include <unistd.h>
 11  #include <dirent.h>
 12  
 13  #include <errno.h>
 14  #include <inttypes.h>
 15  
 16  #include <cstring>
 17  #include <cstdint>
 18  #include <cstdarg>
 19  
 20  #include <string>
 21  #include <sstream>
 22  #include <iomanip>
 23  #include <memory>
 24  #include <algorithm>
 25  #include <vector>
 26  #include <utility>
 27  #include <map>
 28  #include <random>
 29  #include <functional>
 30  #include <mutex>
 31  #include <cctype>
 32  
 33  #ifdef __ANDROID__
 34  #include <sys/system_properties.h>
 35  #endif
 36  
 37  /**
 38   * @brief Returns the memory page size.
 39   */
 40  inline size_t KTGetPageSize()
 41  {
 42      static size_t pageSize = 0;
 43      if (pageSize == 0)
 44          pageSize = (sysconf(_SC_PAGE_SIZE));
 45  
 46      return pageSize;
 47  }
 48  
 49  #define KT_PAGE_SIZE (KTGetPageSize())
 50  #define KT_PAGE_START(x) (uintptr_t(x) & ~(KT_PAGE_SIZE - 1))
 51  #define KT_PAGE_END(x) (KT_PAGE_START(uintptr_t(x) + KT_PAGE_SIZE - 1))
 52  #define KT_PAGE_OFFSET(x) (uintptr_t(x) - KT_PAGE_START(x))
 53  #define KT_PAGE_LEN(x) (size_t(KT_PAGE_SIZE - KT_PAGE_OFFSET(x)))
 54  
 55  #define KT_ALIGN_UP(ptr, align) (((uintptr_t)(ptr) + (align) - 1) & ~((align) - 1))
 56  #define KT_ALIGN_DOWN(ptr, align) (((uintptr_t)(ptr)) & ~((uintptr_t)(align) - 1))
 57  
 58  #if defined(__ANDROID__) && defined(kUSE_LOGCAT)
 59  
 60  #include <android/log.h>
 61  #define KITTY_LOG_TAG "KittyMemoryEx"
 62  
 63  #ifdef kITTYMEMORY_DEBUG
 64  #define KITTY_LOGD(fmt, ...) ((void)__android_log_print(ANDROID_LOG_DEBUG, KITTY_LOG_TAG, fmt, ##__VA_ARGS__))
 65  #else
 66  #define KITTY_LOGD(fmt, ...)                                                                                           \
 67      do                                                                                                                 \
 68      {                                                                                                                  \
 69      } while (0)
 70  #endif
 71  
 72  #define KITTY_LOGI(fmt, ...) ((void)__android_log_print(ANDROID_LOG_INFO, KITTY_LOG_TAG, fmt, ##__VA_ARGS__))
 73  #define KITTY_LOGE(fmt, ...) ((void)__android_log_print(ANDROID_LOG_ERROR, KITTY_LOG_TAG, fmt, ##__VA_ARGS__))
 74  #define KITTY_LOGW(fmt, ...) ((void)__android_log_print(ANDROID_LOG_WARN, KITTY_LOG_TAG, fmt, ##__VA_ARGS__))
 75  
 76  #else
 77  
 78  #ifdef kITTYMEMORY_DEBUG
 79  #define KITTY_LOGD(fmt, ...) printf("D: " fmt "\n", ##__VA_ARGS__)
 80  #else
 81  #define KITTY_LOGD(fmt, ...)                                                                                           \
 82      do                                                                                                                 \
 83      {                                                                                                                  \
 84      } while (0)
 85  #endif
 86  
 87  #define KITTY_LOGI(fmt, ...) printf("I: " fmt "\n", ##__VA_ARGS__)
 88  #define KITTY_LOGE(fmt, ...) fprintf(stderr, "E: " fmt "\n", ##__VA_ARGS__)
 89  #define KITTY_LOGW(fmt, ...) printf("W: " fmt "\n", ##__VA_ARGS__)
 90  
 91  #endif
 92  
 93  #define KT_EINTR_RETRY(exp)                                                                                            \
 94      ({                                                                                                                 \
 95          __typeof__(exp) _rc;                                                                                           \
 96          do                                                                                                             \
 97          {                                                                                                              \
 98              _rc = (exp);                                                                                               \
 99          } while (_rc == -1 && errno == EINTR);                                                                         \
100          _rc;                                                                                                           \
101      })
102  
103  #include <elf.h>
104  #ifdef __LP64__
105  #define KT_ELFCLASS_BITS 64
106  #define KT_ELF_EICLASS 2
107  #define KT_ElfW(x) Elf64_##x
108  #define KT_ELFW(x) ELF64_##x
109  #else
110  #define KT_ELFCLASS_BITS 32
111  #define KT_ELF_EICLASS 1
112  #define KT_ElfW(x) Elf32_##x
113  #define KT_ELFW(x) ELF32_##x
114  #endif
115  #define KT_ELF_ST_BIND(val) (((unsigned char)(val)) >> 4)
116  #define KT_ELF_ST_TYPE(val) ((val) & 0xf)
117  #define KT_ELF_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf))
118  #define KT_ELF_ST_VISIBILITY(o) ((o) & 0x03)
119  
120  /**
121   * @brief Provides general utility functions.
122   */
123  namespace KittyUtils
124  {
125  
126  #ifdef __ANDROID__
127      /**
128       * @brief Provides utility functions for Android.
129       */
130      namespace Android
131      {
132          inline int getUserId()
133          {
134              uid_t uid = getuid(); // Linux UID
135              return uid / 100000;  // approximate Android user ID
136          }
137  
138          /**
139           * @brief Get an Android system property as a typed value.
140           *
141           * Supports std::string, bool, integral, and floating-point types.
142           * Returns `defaultValue` if the property is missing or conversion fails.
143           *
144           * @tparam T Desired type
145           * @param key Property name (e.g., "ro.build.version.sdk")
146           * @param defaultValue Value returned if property not found or conversion fails
147           * @return Property value converted to T
148           */
149          template <typename T>
150          inline T getSystemProperty(const std::string &key, T defaultValue)
151          {
152              static_assert(
153                  std::is_same_v<T, std::string> || std::is_same_v<T, bool> || std::is_integral_v<T> ||
154                      std::is_floating_point_v<T>,
155                  "getSystemProperty: unsupported type. Supported types: string, bool, integral, floating-point.");
156  
157              char value[PROP_VALUE_MAX] = {0};
158              int len = __system_property_get(key.c_str(), value);
159              if (len <= 0)
160                  return defaultValue;
161  
162              if constexpr (std::is_same_v<T, std::string>)
163              {
164                  return std::string(value, len);
165              }
166              else if constexpr (std::is_same_v<T, bool>)
167              {
168                  for (int i = 0; i < len; ++i)
169                      value[i] = std::tolower(value[i]);
170  
171                  if (std::strcmp(value, "1") == 0 || std::strcmp(value, "true") == 0 || std::strcmp(value, "y") == 0 ||
172                      std::strcmp(value, "yes") == 0)
173                      return true;
174  
175                  if (std::strcmp(value, "0") == 0 || std::strcmp(value, "false") == 0 || std::strcmp(value, "n") == 0 ||
176                      std::strcmp(value, "no") == 0)
177                      return false;
178  
179                  return defaultValue;
180              }
181              else if constexpr (std::is_integral_v<T>)
182              {
183                  char *end = nullptr;
184                  long long result = std::strtoll(value, &end, 0);
185                  if (end == value)
186                      return defaultValue;
187                  return static_cast<T>(result);
188              }
189              else if constexpr (std::is_floating_point_v<T>)
190              {
191                  char *end = nullptr;
192                  double result = std::strtod(value, &end);
193                  if (end == value)
194                      return defaultValue;
195                  return static_cast<T>(result);
196              }
197  
198              // unsupported type
199              return defaultValue;
200          }
201  
202          /**
203           * @brief Returns the version of the Android operating system.
204           */
205          int getVersion();
206  
207          /**
208           * @brief Returns the SDK version of the Android operating system.
209           */
210          int getSDK();
211  
212          /**
213           * @brief Returns true if Android operating system supports 64bit.
214           */
215          bool is64BitSupported();
216  
217          /**
218           * @brief Get the base external storage directory.
219           *
220           * Usually:
221           * /storage/emulated/0
222           *
223           * Uses the EXTERNAL_STORAGE environment variable.
224           *
225           * @return Absolute path to external storage root, falls back to "/sdcard" if not defined.
226           */
227          inline std::string getExternalStorage()
228          {
229              const char *storage = std::getenv("EXTERNAL_STORAGE");
230              return (storage && storage[0] != '\0') ? storage : "/sdcard";
231          }
232  
233          /**
234           * @brief Get the Android app-specific internal data directory.
235           *
236           * Equivalent to:
237           * Context.getDataDir()
238           *
239           * Example:
240           * /data/user/<user_id>/<package_name>
241           *
242           * @param packageName Android application package name.
243           * @return Absolute path to app internal data directory.
244           */
245          std::string getAppInternalDataDir(const std::string &packageName);
246  
247          /**
248           * @brief Get the Android app-specific internal files directory.
249           *
250           * Equivalent to:
251           * Context.getFilesDir()
252           *
253           * Example:
254           * /data/user/<user_id>/<package_name>/files
255           *
256           * @param packageName Android application package name.
257           * @return Absolute path to app internal files directory.
258           */
259          std::string getAppInternalFilesDir(const std::string &packageName);
260  
261          /**
262           * @brief Get the Android app-specific internal cache directory.
263           * the system usually sets the TMPDIR environment variable.
264           * If TMPDIR is not set, it falls back to `/data/user/<user_id>/<package_name>/cache`
265           *
266           * Equivalent to:
267           * Context.getCacheDir()
268           *
269           * Example:
270           * /data/user/<user_id>/<package_name>/cache
271           *
272           * @param packageName Android application package name.
273           * @return Absolute path to app internal cache directory.
274           */
275          std::string getAppInternalCacheDir(const std::string &packageName);
276  
277          /**
278           * @brief Get the Android app-specific external data directory.
279           *
280           * Example:
281           * /storage/emulated/0/Android/data/<package>
282           *
283           * @param packageName Android application package name.
284           * @return Absolute path to app external data.
285           */
286          inline std::string getAppExternalDataDir(const std::string &packageName)
287          {
288              return getExternalStorage() + "/Android/data/" + packageName;
289          }
290  
291          /**
292           * @brief Get the Android app-specific external files directory.
293           *
294           * Equivalent to:
295           * Context.getExternalFilesDir(null)
296           *
297           * Example:
298           * /storage/emulated/0/Android/data/<package>/files
299           *
300           * @param packageName Android application package name.
301           * @return Absolute path to external files directory.
302           */
303          inline std::string getAppExternalFilesDir(const std::string &packageName)
304          {
305              return getAppExternalDataDir(packageName) + "/files";
306          }
307  
308          /**
309           * @brief Get the Android app-specific external cache directory.
310           *
311           * Equivalent to:
312           * Context.getExternalCacheDir()
313           *
314           * Example:
315           * /storage/emulated/0/Android/data/<package>/cache
316           *
317           * @param packageName Android application package name.
318           * @return Absolute path to external cache directory.
319           */
320          inline std::string getAppExternalCacheDir(const std::string &packageName)
321          {
322              return getAppExternalDataDir(packageName) + "/cache";
323          }
324  
325          /**
326           * @brief Get the Android app-specific external media directory (Android 10+).
327           *
328           * Example:
329           * /storage/emulated/0/Android/media/<package>
330           *
331           * @param packageName Android application package name.
332           * @return Absolute path to external media directory.
333           */
334          inline std::string getAppExternalMediaDir(const std::string &packageName)
335          {
336              return getExternalStorage() + "/Android/media/" + packageName;
337          }
338  
339          /**
340           * @brief Get the Android app-specific OBB directory.
341           *
342           * Example:
343           * /storage/emulated/0/Android/obb/<package>
344           *
345           * @param packageName Android application package name.
346           * @return Absolute path to OBB directory.
347           */
348          inline std::string getAppObbDir(const std::string &packageName)
349          {
350              return getExternalStorage() + "/Android/obb/" + packageName;
351          }
352      } // namespace Android
353  #endif
354  
355      /**
356       * @brief Untags a heap pointer by removing the top byte (TBI).
357       * @note Currently only implemented for android 11+ arm64
358       *
359       * @param p The heap pointer to be untagged.
360       * @return The untagged pointer.
361       */
362      inline uintptr_t untagHeepPtr(uintptr_t p)
363      {
364  #if defined(__LP64__) && defined(__ANDROID__)
365          return (p & ((static_cast<uintptr_t>(1) << 56) - 1));
366  #else
367          return p;
368  #endif
369      }
370  
371      inline void *untagHeepPtr(void *p)
372      {
373          return reinterpret_cast<void *>(untagHeepPtr(uintptr_t(p)));
374      }
375  
376      inline const void *untagHeepPtr(const void *p)
377      {
378          return reinterpret_cast<const void *>(untagHeepPtr(uintptr_t(p)));
379      }
380  
381      /**
382       * @brief Provides utility functions for paths.
383       */
384      namespace Path
385      {
386          /**
387           * @brief Extracts the file name from a given file path.
388           *
389           * @param filePath The full path of the file.
390           *
391           * @return file name.
392           */
393          std::string fileName(const std::string &filePath);
394  
395          /**
396           * @brief Extracts the directory from a given file path.
397           *
398           * @param filePath The full path of the file.
399           *
400           * @return The directory path.
401           */
402          std::string fileDirectory(const std::string &filePath);
403  
404          /**
405           * @brief Extracts the file extension from a given file path.
406           *
407           * @param filePath The full path of the file.
408           *
409           * @return The file extension.
410           */
411          std::string fileExtension(const std::string &filePath);
412      } // namespace Path
413  
414      /**
415       * @brief Provides utility functions for strings.
416       */
417      namespace String
418      {
419          /**
420           * @brief Helper to compare two characters case-insensitively.
421           */
422          inline bool charEqualsIgnoreCase(char a, char b)
423          {
424              return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b));
425          }
426  
427          /**
428           * @brief Checks if a string starts with a given prefix.
429           *
430           * @param str The string to check.
431           * @param prefix The prefix to look for.
432           * @param sensitive Whether the comparison should be case-sensitive (default is true).
433           *
434           * @return true if str starts with prefix, false otherwise.
435           */
436          bool startsWith(const std::string &str, const std::string &prefix, bool sensitive = true);
437  
438          /**
439           * @brief Checks if a string starts with any of the given prefixes.
440           *
441           * @param str The string to check.
442           * @param prefixes A list of prefixes to look for.
443           * @param sensitive Whether the comparison should be case-sensitive (default is true).
444           *
445           * @return true if str starts with at least one prefix in prefixes, false otherwise.
446           */
447          bool startsWith(const std::string &str, const std::vector<std::string> &prefixes, bool sensitive = true);
448  
449          /**
450           * @brief Checks if a string contains a given substring.
451           *
452           * @param str The string to check.
453           * @param substring The substring to look for.
454           * @param sensitive Whether the comparison should be case-sensitive (default is true).
455           *
456           * @return true if str contains substring, false otherwise.
457           */
458          bool contains(const std::string &str, const std::string &substring, bool sensitive = true);
459  
460          /**
461           * @brief Checks if a string contains any of the given substrings.
462           *
463           * @param str The string to check.
464           * @param substrings A list of substrings to look for.
465           * @param sensitive Whether the comparison should be case-sensitive (default is true).
466           *
467           * @return true if str contains at least one substring in substrings, false otherwise.
468           */
469          bool contains(const std::string &str, const std::vector<std::string> &substrings, bool sensitive = true);
470  
471          /**
472           * @brief Checks if a string ends with a given suffix.
473           *
474           * @param str The string to check.
475           * @param suffix The suffix to look for.
476           * @param sensitive Whether the comparison should be case-sensitive (default is true).
477           *
478           * @return true if str ends with suffix, false otherwise.
479           */
480          bool endsWith(const std::string &str, const std::string &suffix, bool sensitive = true);
481  
482          /**
483           * @brief Checks if a string ends with any of the given suffixes.
484           *
485           * @param str The string to check.
486           * @param suffixes A list of suffixes to look for.
487           * @param sensitive Whether the comparison should be case-sensitive (default is true).
488           *
489           * @return true if str ends with at least one suffix in suffixes, false otherwise.
490           */
491          bool endsWith(const std::string &str, const std::vector<std::string> &suffixes, bool sensitive = true);
492  
493          /**
494           * @brief Trims whitespace from the beginning and end of a string.
495           *
496           * @param str The string to be trimmed.
497           */
498          void trim(std::string &str);
499  
500          /**
501           * @brief Checks if the provided string is a valid hexadecimal representation.
502           *
503           * This function validates if the given string is a valid hexadecimal number.
504           * A valid hexadecimal number can contain characters '0'-'9' and 'A-F' or 'a-f'.
505           *
506           * @param hex The string to validate as a hexadecimal number.
507           * @return true if the string is a valid hexadecimal number, false otherwise.
508           */
509          bool isValidHex(const std::string &hex);
510  
511          /**
512           * @brief Validates a hexadecimal string.
513           *
514           * @param hex The hexadecimal string to validate.
515           * @return True if the string was validated, false otherwise.
516           */
517          bool validateHex(std::string &hex);
518  
519          /**
520           * @brief Formats a string using a printf-style format.
521           *
522           * @param fmt The format string.
523           * @param ... Variable arguments to be formatted.
524           * @return The formatted string.
525           */
526          std::string fmt(const char *fmt, ...);
527      } // namespace String
528  
529      /**
530       * @brief Generates a random number of type T within a specified range.
531       *
532       * @tparam T The type of the number.
533       * @param min The minimum range.
534       * @param min The maximum range.
535       * @return A random number.
536       */
537      template <typename T>
538      T randInt(T min, T max)
539      {
540          using param_type = typename std::uniform_int_distribution<T>::param_type;
541  
542          static std::mutex mtx;
543          std::lock_guard<std::mutex> lock(mtx);
544  
545          static std::mt19937 gen{std::random_device{}()};
546  
547          std::uniform_int_distribution<T> dist;
548          return dist(gen, param_type{min, max});
549      }
550  
551      /**
552       * @brief Generates a random bytes of a specified length.
553       *
554       * @param length The length of the random bytes to generate.
555       * @return Vector containing random byte values in the range [0, 255].
556       */
557      std::vector<uint8_t> randomBytes(std::size_t length);
558  
559      /**
560       * @brief Generates a random string of a specified length.
561       *
562       * @param length The length of the random string to generate.
563       * @return A random string.
564       */
565      std::string randomString(size_t length);
566  
567      /**
568       * @brief Provides utility functions for data.
569       */
570      namespace Data
571      {
572          /**
573           * @brief Converts a hexadecimal string to a binary data buffer.
574           * @note data buffer must be large enough to fit.
575           *
576           * @param in The hexadecimal string to convert.
577           * @param data Pointer to the destination buffer where the binary data will be stored.
578           *
579           * @return True if the conversion was successful, false otherwise.
580           */
581          bool fromHex(std::string in, void *data);
582  
583          /**
584           * @brief Converts binary data to a hexadecimal string.
585           *
586           * @param data Pointer to the source binary data.
587           * @param dataLength Length of the binary data.
588           *
589           * @return A hexadecimal string representation of the binary data.
590           */
591          std::string toHex(const void *data, const size_t dataLength);
592  
593          /**
594           * @brief Converts a binary representation of a type T to a hexadecimal string.
595           *
596           * @tparam T The type of the binary data.
597           * @param data The instance of type T to convert.
598           *
599           * @return A hexadecimal string representation of the binary data.
600           */
601          template <typename T>
602          std::string toHex(const T &data)
603          {
604              return toHex(&data, sizeof(T));
605          }
606  
607          /**
608           * @brief Hex dumps the memory block at the specified address.
609           *
610           * @tparam rowSize The size of each row in the hex dump. Default is 8 bytes.
611           * @tparam showASCII Whether to include ASCII representation of the memory block. Defult is true.
612           *
613           * @param address Pointer to the start of the memory block to dump.
614           * @param len Length of the memory block to dump.
615           *
616           * @return A string containing the hex dump of the memory block.
617           *
618           * @details This function generates a human-readable hex dump of a memory block.
619           * It prints the address, hexadecimal values, and ASCII representation of the block.
620           * The dump is formatted into rows of specified size, and each row includes the offset,
621           * byte values, and ASCII characters. The ASCII representation only includes printable
622           * characters, and non-printable characters are represented by '.'.
623           */
624          template <size_t rowSize = 8, bool showASCII = true>
625          std::string hexDump(const void *address, size_t len)
626          {
627              if (!address || len == 0 || rowSize == 0)
628                  return "";
629  
630              const unsigned char *data = static_cast<const unsigned char *>(address);
631  
632              std::stringstream ss;
633              ss << std::hex << std::uppercase << std::setfill('0');
634  
635              size_t i, j;
636  
637              for (i = 0; i < len; i += rowSize)
638              {
639                  // offset
640                  ss << std::setw(8) << i << ": ";
641  
642                  // row bytes
643                  for (j = 0; (j < rowSize) && ((i + j) < len); j++)
644                      ss << std::setw(2) << static_cast<unsigned int>(data[i + j]) << " ";
645  
646                  // fill row empty space
647                  for (; j < rowSize; j++)
648                      ss << "   ";
649  
650                  // ASCII
651                  if (showASCII)
652                  {
653                      ss << " ";
654  
655                      for (j = 0; (j < rowSize) && ((i + j) < len); j++)
656                      {
657                          if (std::isprint(data[i + j]))
658                              ss << data[i + j];
659                          else
660                              ss << '.';
661                      }
662                  }
663  
664                  ss << std::endl;
665              }
666  
667              return ss.str();
668          }
669      } // namespace Data
670  
671      /**
672       * @brief Provides utility functions for handling ZIP files.
673       */
674      namespace Zip
675      {
676          /**
677           * @brief Structure to hold ZIP entry info.
678           */
679          struct ZipEntryInfo
680          {
681              std::string fileName;
682              uint64_t compressedSize = 0;
683              uint64_t uncompressedSize = 0;
684              uint16_t compressionMethod = 0;
685              uint32_t crc32 = 0;
686              uint16_t modTime = 0;
687              uint16_t modDate = 0;
688              uint64_t dataOffset = 0;
689          };
690  
691          /**
692           * @brief Structure to hold memory mapped ZIP entry info.
693           */
694          struct ZipEntryMMap
695          {
696              void *mappingBase = nullptr;
697              size_t mappingSize = 0;
698              uint8_t *data = nullptr;
699              uint64_t size = 0;
700          };
701  
702          /**
703           * @brief Finds the central directory of a ZIP file.
704           *
705           * @param data Pointer to the ZIP file data.
706           * @param fileSize Size of the ZIP file in bytes.
707           * @param cdOffset Pointer to store the offset of the central directory.
708           * @param totalEntries Pointer to store the total number of entries in the ZIP file.
709           *
710           * @return True if the central directory is found, false otherwise.
711           */
712          bool findCentralDirectory(const uint8_t *data, uint64_t fileSize, uint64_t *cdOffset, uint64_t *totalEntries);
713  
714          /**
715           * @brief Lists all entries in a ZIP file.
716           *
717           * @param zipPath Path to the ZIP file.
718           *
719           * @return A vector of ZipEntryInfo objects containing information about each entry.
720           */
721          std::vector<ZipEntryInfo> listEntriesInZip(const std::string &zipPath);
722  
723          /**
724           * @brief Finds the ZipEntryInfo for an entry by its data offset.
725           *
726           * @param zipPath Path to the ZIP file.
727           * @param dataOffset Offset of the entry in the ZIP file.
728           * @param out Pointer to store the ZipEntryInfo object if found.
729           *
730           * @return True if the entry info is found, false otherwise.
731           */
732          bool findEntryInfoByDataOffset(const std::string &zipPath, uint64_t dataOffset, ZipEntryInfo *out);
733  
734          /**
735           * @brief Maps an entry in a ZIP file by its data offset.
736           *
737           * @param zipPath Path to the ZIP file.
738           * @param dataOffset Offset of the entry in the ZIP file.
739           * @param out Pointer to store the ZipEntryMMap object if found.
740           *
741           * @return True if the entry is mapped, false otherwise.
742           */
743          bool mmapEntryByDataOffset(const std::string &zipPath, uint64_t dataOffset, ZipEntryMMap *out);
744      } // namespace Zip
745  
746  } // namespace KittyUtils