diff --git a/.efrocachemap b/.efrocachemap
index a60f082c..164c9f2c 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -4068,26 +4068,26 @@
"build/assets/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/2d/ef/5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/b0/8a/55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/86/5b/2af4d1e26a1a8073c89acb06e599",
- "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/b9/b3/2fa4ee60a830770b0fdfd27d0662",
- "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/58/5d/aafcd4e057f4048e0ab4b097b614",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/49/96/45734d046f665c33cf041c5e0751",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/49/e3/e76596a602fec3be164171b678f8",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/24/6c/b0e065c2d5ee684ef782e940c815",
- "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/af/ef/f3c900b2b76b3483ba67feab931f",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/65/9b/b420fd2a80e6293340e93bace338",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a5/47/434af2120591774986a41e84bc1d",
- "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/08/48/6c1af81372a988542ff82704eb95",
- "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/7a/d1/cf9ef6bdf9d7c0fd99deedb54170",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a6/30/1279e53155667db8b5c9a2bbf7d0",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/4c/a5/6a2334a97a6c48dc04403e29274e",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/9f/60/6159ef28401646f9e654c29e0673",
- "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/09/cb/4eeb647b2bb083ab7056a1231bd7",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/08/dd/37874122d5d465c62deac533b648",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/6e/fd/00e796c943776ae69971931d249b",
- "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/9a/e4/2fea9c0d36d3b7238eda94783b6f",
- "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/fc/31/d7c0f7629642530d41c4d209ecef",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/98/41/f5401e303ed81ad91d7f4022e572",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/4f/3e/a371bc2c1946057f5053342643e0",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/07/f3/286b2cf0e1b50530b18965cb8b80",
+ "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/76/ca/9a12f8c55e4b80e3b7125864c5e8",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/55/5d/9677204cb8a81f44cdf36cbe16ed",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/ed/d9/5d087b681d88ce9fbf847e2e18ac",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/9f/2f/efef4e5a2e1a6faf675eba4d371a",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/0c/8b/e07ebcb43cab26c8b7c086d364ee",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/98/a7/b52e3fc93f750bc906e13b0f73f4",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/95/f2/31a57f21956ca9db25b291d9e73f",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/98/30/6664dbd278148938969de4340336",
+ "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/b1/c4/acfecf05f122c2748e976d03d2a1",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/fe/19/1e894d75b761b3e5b1e352e69bb4",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/3b/ba/086f1125c06960c1c7e5a713e1a4",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/70/77/a779069bfaf1b77f57606e84e8c2",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/b1/cf/fdf91926b6c70fe52f06224cae39",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/52/99/7a6bcb5ab85bf4a1d075d1c751fb",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/ec/d7/aff376d79fd0a3778b4e3cdbad0f",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/7b/36/09f5279721eb692d7132b9217abe",
+ "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/48/23/d3ecc696eb84836db814a0f03746",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/f7/a1/beffa3b641e5f0212eeaadbf94c0",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/4b/ec/3fe45fb55be8d95a853536387269",
"build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/84/aa/534f35b6499762739646ea173382",
"build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/25/2f/3bd787d6debb2c4073fd6c2e8098",
"build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/84/aa/534f35b6499762739646ea173382",
@@ -4104,14 +4104,14 @@
"build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/47/61/eca0961c54b2eae2cf65fac7848d",
"build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/06/5c/90c3a49e16a004e2db71909af919",
"build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/47/61/eca0961c54b2eae2cf65fac7848d",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/72/70/aab14b866e1c3562421a757abfeb",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/0a/df/c8bb059be642ad2a905ef2da6679",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/1d/47/3441b90233f32aca5657acc410f7",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/3d/54/f255fe1f8eced421101a4ae392bf",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/34/5d/f1544e97b2677c9ef3aef62e569e",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/9b/f7/e821d0e43e2f7a34980b6556e69d",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/07/af/60dfd76561968bee5a00d2530ba4",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/65/e9/338ef36cbefafd0f718bb42417d1",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/de/a9/add7fc92d73241fb32df336c4012",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/b5/88/dc175d964962a3fec5b604d6a5cc",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/a2/20/867922da68023be227e26d04d96c",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/ce/9d/6df11d4bb08bfbb239fcf9b1bc10",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/a0/42/96482e90951d3c141820cb413fc6",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/56/cc/980a9c4a5b0d11d5a84ab895b354",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/7a/3d/7a4a4f0bb522c2ab7912dcd3d1cc",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/47/f0/aebfc9f112aaf48b69e2dbb3b823",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/f8/85/fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/f8/cd/3af311ac63147882590123b78318",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/3e/7a/203e2a5d2b5bb42cfe3fd2fe16c2",
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 147886bb..ddecd372 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -82,6 +82,7 @@
aname
anamorphosis
andr
+ andrarch
androidaddr
androideabi
androidstudiocode
@@ -658,6 +659,7 @@
cyaml
cycledelay
cygwinccompiler
+ dandroid
darwiin
darwiinremote
datab
@@ -743,6 +745,7 @@
distro
distroot
distros
+ dlibtype
dline
dliwk
dlldir
@@ -1555,6 +1558,7 @@
libegl
libext
libffi
+ libfile
libgen
libinst
liblzma
@@ -1634,6 +1638,7 @@
locs
logcallobj
logcat
+ loggingpath
logincode
loginid
logintoken
@@ -1989,6 +1994,8 @@
onslaughtplug
opcode
opdir
+ opealsoft
+ openalsoft
opendiff
openssh
operasinger
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7fe3663a..d6d47450 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
+### 1.7.22 (build 21167, api 8, 2023-07-12)
+
### 1.7.23 (build 21165, api 8, 2023-07-11)
-- Network security improvements.
+- Network security improvements. (Thanks Dliwk!)
+- You can now double click a chat message to copy it (Thanks Vishal332008!)
+- Android's audio library has been updated to the latest version (and is now
+ much easier for me to keep up to date). Please holler if you run into anything
+ wonky related to audio.
+- Updated our C json handling code to the latest version of cJSON. Should fix
+ some potential vulnerabilities.
### 1.7.22 (build 21165, api 8, 2023-07-11)
diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml
index f7bfac48..2ff36119 100644
--- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml
@@ -58,6 +58,7 @@
alsa
alsoft
anchorx
+ andrarch
androideabi
animcurve
aniso
@@ -396,6 +397,7 @@
curv
cutef
cvar
+ dandroid
data
databytes
datac
@@ -457,6 +459,7 @@
displaytime
displaytimer
dlfcn
+ dlibtype
dlife
dliwk
dllpath
@@ -913,6 +916,7 @@
libballistica
libbz
libbzip
+ libfile
libutf
libuuid
lifecyclelog
@@ -934,6 +938,7 @@
lockstr
locktype
logcallobj
+ loggingpath
logincode
loginid
logmsg
@@ -1177,7 +1182,9 @@
ooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooo
opcode
+ opealsoft
openal
+ openalsoft
opengl
opensl
oper
diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py
index afdec83a..59f62065 100644
--- a/src/assets/ba_data/python/baenv.py
+++ b/src/assets/ba_data/python/baenv.py
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
-TARGET_BALLISTICA_BUILD = 21165
+TARGET_BALLISTICA_BUILD = 21167
TARGET_BALLISTICA_VERSION = '1.7.22'
_g_env_config: EnvConfig | None = None
diff --git a/src/ballistica/base/audio/al_sys.h b/src/ballistica/base/audio/al_sys.h
index 316d3f3c..f1d017dd 100644
--- a/src/ballistica/base/audio/al_sys.h
+++ b/src/ballistica/base/audio/al_sys.h
@@ -17,6 +17,10 @@
#include
#endif
+#if BA_OSTYPE_ANDROID
+#include
+#endif
+
#define CHECK_AL_ERROR _check_al_error(__FILE__, __LINE__)
#if BA_DEBUG_BUILD
#define DEBUG_CHECK_AL_ERROR CHECK_AL_ERROR
diff --git a/src/ballistica/base/audio/audio_server.cc b/src/ballistica/base/audio/audio_server.cc
index 4d571b53..03d9178f 100644
--- a/src/ballistica/base/audio/audio_server.cc
+++ b/src/ballistica/base/audio/audio_server.cc
@@ -25,9 +25,10 @@ namespace ballistica::base {
extern std::string g_rift_audio_device_name;
#endif
-// FIXME: move these to platform.
-extern "C" void opensl_pause_playback();
-extern "C" void opensl_resume_playback();
+#if BA_OSTYPE_ANDROID
+LPALCDEVICEPAUSESOFT alcDevicePauseSOFT;
+LPALCDEVICERESUMESOFT alcDeviceResumeSOFT;
+#endif
const int kAudioProcessIntervalNormal{500};
const int kAudioProcessIntervalFade{50};
@@ -41,7 +42,7 @@ struct AudioServer::Impl {
~Impl() = default;
#if BA_ENABLE_AUDIO
- ALCcontext* alc_context_{};
+ ALCcontext* alc_context{};
#endif
};
@@ -203,10 +204,21 @@ void AudioServer::OnAppStartInThread() {
"No audio devices found. Do you have speakers/headphones/etc. "
"connected?");
}
- impl_->alc_context_ = alcCreateContext(device, nullptr);
- BA_PRECONDITION(impl_->alc_context_);
- BA_PRECONDITION(alcMakeContextCurrent(impl_->alc_context_));
+ impl_->alc_context = alcCreateContext(device, nullptr);
+ BA_PRECONDITION(impl_->alc_context);
+ BA_PRECONDITION(alcMakeContextCurrent(impl_->alc_context));
CHECK_AL_ERROR;
+
+#if BA_OSTYPE_ANDROID
+ if (alcIsExtensionPresent(device, "ALC_SOFT_pause_device")) {
+ alcDevicePauseSOFT = reinterpret_cast(
+ alcGetProcAddress(device, "alcDevicePauseSOFT"));
+ alcDeviceResumeSOFT = reinterpret_cast(
+ alcGetProcAddress(device, "alcDeviceResumeSOFT"));
+ } else {
+ FatalError("ALC_SOFT pause/resume functionality not found.");
+ }
+#endif
}
ALfloat listener_pos[] = {0.0f, 0.0f, 0.0f};
@@ -250,8 +262,8 @@ AudioServer::~AudioServer() {
{
ALCdevice* device;
BA_PRECONDITION_LOG(alcMakeContextCurrent(nullptr));
- device = alcGetContextsDevice(impl_->alc_context_);
- alcDestroyContext(impl_->alc_context_);
+ device = alcGetContextsDevice(impl_->alc_context);
+ alcDestroyContext(impl_->alc_context);
assert(alcGetError(device) == ALC_NO_ERROR);
alcCloseDevice(device);
}
@@ -288,7 +300,7 @@ void AudioServer::SetPaused(bool pause) {
// On android lets tell open-sl to stop its processing.
#if BA_OSTYPE_ANDROID
- opensl_pause_playback();
+ alcDevicePauseSOFT(alcGetContextsDevice(impl_->alc_context));
#endif // BA_OSTYPE_ANDROID
paused_ = true;
@@ -304,12 +316,12 @@ void AudioServer::SetPaused(bool pause) {
// Conceptual/AudioSessionProgrammingGuide/Cookbook/
// Cookbook.html#//apple_ref/doc/uid/TP40007875-CH6-SW38
#if BA_ENABLE_AUDIO
- alcMakeContextCurrent(impl_->alc_context_); // hmm is this necessary?..
+ alcMakeContextCurrent(impl_->alc_context); // hmm is this necessary?..
#endif
#endif
// On android lets tell openal-soft to stop processing.
#if BA_OSTYPE_ANDROID
- opensl_resume_playback();
+ alcDeviceResumeSOFT(alcGetContextsDevice(impl_->alc_context));
#endif // BA_OSTYPE_ANDROID
paused_ = false;
diff --git a/src/ballistica/core/core.cc b/src/ballistica/core/core.cc
index c2d38850..effbeb24 100644
--- a/src/ballistica/core/core.cc
+++ b/src/ballistica/core/core.cc
@@ -178,7 +178,6 @@ void CoreFeatureSet::LifecycleLog(const char* msg, double offset_seconds) {
return;
}
char buffer[128];
- // It's not safe to use Log until
snprintf(buffer, sizeof(buffer), "LIFE: %s @ %.3fs.", msg,
g_core->GetAppTimeSeconds() + offset_seconds);
Log(LogLevel::kInfo, buffer);
diff --git a/src/ballistica/core/python/core_python.cc b/src/ballistica/core/python/core_python.cc
index a92ca1c7..3ff2d5cc 100644
--- a/src/ballistica/core/python/core_python.cc
+++ b/src/ballistica/core/python/core_python.cc
@@ -22,6 +22,13 @@ void CorePython::ApplyBaEnvConfig() {
g_core->platform->SetBaEnvVals(envcfg);
}
+static void CheckPyInitStatus(const char* where, const PyStatus& status) {
+ if (PyStatus_Exception(status)) {
+ FatalError(std::string("Error in ") + where + ": "
+ + (status.err_msg ? status.err_msg : "(nullptr err_msg)") + ".");
+ }
+}
+
void CorePython::InitPython() {
assert(g_core->InMainThread());
assert(g_buildconfig.monolithic_build());
@@ -49,8 +56,7 @@ void CorePython::InitPython() {
// windows-specific file encodings, etc.)
preconfig.utf8_mode = 1;
- PyStatus status = Py_PreInitialize(&preconfig);
- BA_PRECONDITION(!PyStatus_Exception(status));
+ CheckPyInitStatus("Py_PreInitialize", Py_PreInitialize(&preconfig));
// Configure as isolated if we include our own Python and as standard
// otherwise.
@@ -71,12 +77,22 @@ void CorePython::InitPython() {
// In cases where we bundle Python, set up all paths explicitly.
// https://docs.python.org/3/c-api/init_config.html#path-configuration
if (g_buildconfig.contains_python_dist()) {
- PyConfig_SetBytesString(&config, &config.base_exec_prefix, "");
- PyConfig_SetBytesString(&config, &config.base_executable, "");
- PyConfig_SetBytesString(&config, &config.base_prefix, "");
- PyConfig_SetBytesString(&config, &config.exec_prefix, "");
- PyConfig_SetBytesString(&config, &config.executable, "");
- PyConfig_SetBytesString(&config, &config.prefix, "");
+ CheckPyInitStatus(
+ "pyconfig base_exec_prefix set",
+ PyConfig_SetBytesString(&config, &config.base_exec_prefix, ""));
+ CheckPyInitStatus(
+ "pyconfig base_executable set",
+ PyConfig_SetBytesString(&config, &config.base_executable, ""));
+ CheckPyInitStatus(
+ "pyconfig base_prefix set",
+ PyConfig_SetBytesString(&config, &config.base_prefix, ""));
+ CheckPyInitStatus(
+ "pyconfig exec_prefix set",
+ PyConfig_SetBytesString(&config, &config.exec_prefix, ""));
+ CheckPyInitStatus("pyconfig executable set",
+ PyConfig_SetBytesString(&config, &config.executable, ""));
+ CheckPyInitStatus("pyconfig prefix set",
+ PyConfig_SetBytesString(&config, &config.prefix, ""));
// Note: we're using utf-8 mode above so Py_DecodeLocale will convert
// from utf-8.
@@ -121,8 +137,8 @@ void CorePython::InitPython() {
}
// Init Python.
- status = Py_InitializeFromConfig(&config);
- BA_PRECONDITION_FATAL(!PyStatus_Exception(status));
+ CheckPyInitStatus("Py_InitializeFromConfig",
+ Py_InitializeFromConfig(&config));
PyConfig_Clear(&config);
}
diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc
index 3bed55a3..2393a2a2 100644
--- a/src/ballistica/shared/ballistica.cc
+++ b/src/ballistica/shared/ballistica.cc
@@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica {
// These are set automatically via script; don't modify them here.
-const int kEngineBuildNumber = 21165;
+const int kEngineBuildNumber = 21167;
const char* kEngineVersion = "1.7.22";
auto MonolithicMain(const core::CoreConfig& core_config) -> int {
diff --git a/src/ballistica/shared/generic/json.cc b/src/ballistica/shared/generic/json.cc
index bedbba5c..0ad22347 100644
--- a/src/ballistica/shared/generic/json.cc
+++ b/src/ballistica/shared/generic/json.cc
@@ -2,7 +2,7 @@
// Derived from code licensed as follows:
/*
- Copyright (c) 2009 Dave Gamble
+ Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -26,1088 +26,2708 @@
/* cJSON */
/* JSON parser in C. */
+// ericf note: Changes here from vanilla cJSON:
+// - Lives under ballistica C++ namespace.
+// - Changed some sprintfs to snprintfs to keep compiler quiet.
+// - Removed ENABLE_LOCALES conditionals (want consistent decimal parsing).
+// - Changed some c headers to c++ ones -> , etc.
+// - Removed a few warnings and workarounds for gcc/msvc
+// (will reenable if I see said warnings but assuming they're old).
+// - Using std::isnan and std::isinf in place of potentially homespun
+// versions.
+
#include "ballistica/shared/generic/json.h"
#include
-#include
-
-#if BA_OSTYPE_LINUX
#include
+#include
+#include
+#include
#include
+
+/* define our own boolean type */
+#ifdef true
+#undef true
#endif
+#define true ((cJSON_bool)1)
+
+#ifdef false
+#undef false
+#endif
+#define false ((cJSON_bool)0)
namespace ballistica {
-// Should tidy this up but don't want to risk breaking it at the moment.
-#pragma clang diagnostic push
-#pragma ide diagnostic ignored "hicpp-signed-bitwise"
-#pragma ide diagnostic ignored "bugprone-narrowing-conversions"
-#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"
+typedef struct {
+ const unsigned char* json;
+ size_t position;
+} error;
+static error global_error = {NULL, 0};
-static const char* ep;
-
-auto cJSON_GetErrorPtr() -> const char* { return ep; }
-
-static auto cJSON_strcasecmp(const char* s1, const char* s2) -> int {
- if (!s1) return (s1 == s2) ? 0 : 1;
- if (!s2) return 1;
- for (; tolower(*s1) == tolower(*s2); ++s1, ++s2)
- if (*s1 == 0) return 0;
- return tolower(*(const unsigned char*)s1)
- - tolower(*(const unsigned char*)s2);
+const char* cJSON_GetErrorPtr(void) {
+ return (const char*)(global_error.json + global_error.position);
}
-static void* (*cJSON_malloc)(size_t sz) = malloc;
-static void (*cJSON_free)(void* ptr) = free;
-
-static auto cJSON_strdup(const char* str) -> char* {
- size_t len;
- char* copy;
-
- len = strlen(str) + 1;
- if (!(copy = static_cast(cJSON_malloc(len)))) {
- return nullptr;
+char* cJSON_GetStringValue(const cJSON* const item) {
+ if (!cJSON_IsString(item)) {
+ return NULL;
}
- memcpy(copy, str, len);
+
+ return item->valuestring;
+}
+
+double cJSON_GetNumberValue(const cJSON* const item) {
+ if (!cJSON_IsNumber(item)) {
+ return (double)NAN;
+ }
+
+ return item->valuedouble;
+}
+
+/* This is a safeguard to prevent copy-pasters from using incompatible C and
+ * header files */
+#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) \
+ || (CJSON_VERSION_PATCH != 16)
+#error cJSON.h and cJSON.c have different versions. Make sure that both have the same.
+#endif
+
+const char* cJSON_Version(void) {
+ static char version[15];
+ snprintf(version, sizeof(version), "%i.%i.%i", CJSON_VERSION_MAJOR,
+ CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);
+
+ return version;
+}
+
+/* Case insensitive string comparison, doesn't consider two NULL pointers equal
+ * though */
+static int case_insensitive_strcmp(const unsigned char* string1,
+ const unsigned char* string2) {
+ if ((string1 == NULL) || (string2 == NULL)) {
+ return 1;
+ }
+
+ if (string1 == string2) {
+ return 0;
+ }
+
+ for (; tolower(*string1) == tolower(*string2); (void)string1++, string2++) {
+ if (*string1 == '\0') {
+ return 0;
+ }
+ }
+
+ return tolower(*string1) - tolower(*string2);
+}
+
+typedef struct internal_hooks {
+ void* (*allocate)(size_t size);
+ void (*deallocate)(void* pointer);
+ void* (*reallocate)(void* pointer, size_t size);
+} internal_hooks;
+
+#if defined(_MSC_VER)
+/* work around MSVC error C2322: '...' address of dllimport '...' is not static
+ */
+static void* internal_malloc(size_t size) { return malloc(size); }
+static void internal_free(void* pointer) { free(pointer); }
+static void* internal_realloc(void* pointer, size_t size) {
+ return realloc(pointer, size);
+}
+#else
+#define internal_malloc malloc
+#define internal_free free
+#define internal_realloc realloc
+#endif
+
+/* strlen of character literals resolved at compile time */
+#define static_strlen(string_literal) (sizeof(string_literal) - sizeof(""))
+
+static internal_hooks global_hooks = {internal_malloc, internal_free,
+ internal_realloc};
+
+static unsigned char* cJSON_strdup(const unsigned char* string,
+ const internal_hooks* const hooks) {
+ size_t length = 0;
+ unsigned char* copy = NULL;
+
+ if (string == NULL) {
+ return NULL;
+ }
+
+ length = strlen((const char*)string) + sizeof("");
+ copy = (unsigned char*)hooks->allocate(length);
+ if (copy == NULL) {
+ return NULL;
+ }
+ memcpy(copy, string, length);
+
return copy;
}
void cJSON_InitHooks(cJSON_Hooks* hooks) {
- if (!hooks) { /* Reset hooks */
- cJSON_malloc = malloc;
- cJSON_free = free;
+ if (hooks == NULL) {
+ /* Reset hooks */
+ global_hooks.allocate = malloc;
+ global_hooks.deallocate = free;
+ global_hooks.reallocate = realloc;
return;
}
- cJSON_malloc = (hooks->malloc_fn) ? hooks->malloc_fn : malloc;
- cJSON_free = (hooks->free_fn) ? hooks->free_fn : free;
+ global_hooks.allocate = malloc;
+ if (hooks->malloc_fn != NULL) {
+ global_hooks.allocate = hooks->malloc_fn;
+ }
+
+ global_hooks.deallocate = free;
+ if (hooks->free_fn != NULL) {
+ global_hooks.deallocate = hooks->free_fn;
+ }
+
+ /* use realloc only if both free and malloc are used */
+ global_hooks.reallocate = NULL;
+ if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) {
+ global_hooks.reallocate = realloc;
+ }
}
/* Internal constructor. */
-static auto cJSON_New_Item() -> cJSON* {
- auto* node = static_cast(cJSON_malloc(sizeof(cJSON)));
- if (node) memset(node, 0, sizeof(cJSON));
+static cJSON* cJSON_New_Item(const internal_hooks* const hooks) {
+ cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));
+ if (node) {
+ memset(node, '\0', sizeof(cJSON));
+ }
+
return node;
}
/* Delete a cJSON structure. */
-void cJSON_Delete(cJSON* c) {
- cJSON* next;
- while (c) {
- next = c->next;
- if (!(c->type & cJSON_IsReference) && c->child) cJSON_Delete(c->child);
- if (!(c->type & cJSON_IsReference) && c->valuestring)
- cJSON_free(c->valuestring);
- if (c->string) cJSON_free(c->string);
- cJSON_free(c);
- c = next;
+void cJSON_Delete(cJSON* item) {
+ cJSON* next = NULL;
+ while (item != NULL) {
+ next = item->next;
+ if (!(item->type & cJSON_IsReference) && (item->child != NULL)) {
+ cJSON_Delete(item->child);
+ }
+ if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) {
+ global_hooks.deallocate(item->valuestring);
+ }
+ if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) {
+ global_hooks.deallocate(item->string);
+ }
+ global_hooks.deallocate(item);
+ item = next;
}
}
+/* get the decimal point character of the current locale */
+static unsigned char get_decimal_point(void) { return '.'; }
+
+typedef struct {
+ const unsigned char* content;
+ size_t length;
+ size_t offset;
+ size_t depth; /* How deeply nested (in arrays/objects) is the input at the
+ current offset. */
+ internal_hooks hooks;
+} parse_buffer;
+
+/* check if the given size is left to read in a given parse buffer (starting
+ * with 1) */
+#define can_read(buffer, size) \
+ ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length))
+/* check if the buffer can be accessed at the given index (starting with 0) */
+#define can_access_at_index(buffer, index) \
+ ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length))
+#define cannot_access_at_index(buffer, index) \
+ (!can_access_at_index(buffer, index))
+/* get a pointer to the buffer at the position */
+#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset)
+
/* Parse the input text to generate a number, and populate the result into item.
*/
-static auto parse_number(cJSON* item, const char* num) -> const char* {
- double n = 0, sign = 1, scale = 0;
- int subscale = 0, signsubscale = 1;
+static cJSON_bool parse_number(cJSON* const item,
+ parse_buffer* const input_buffer) {
+ double number = 0;
+ unsigned char* after_end = NULL;
+ unsigned char number_c_string[64];
+ unsigned char decimal_point = get_decimal_point();
+ size_t i = 0;
- if (*num == '-') {
- sign = -1;
- num++;
- } /* Has sign? */
- if (*num == '0') num++; /* is zero */
- if (*num >= '1' && *num <= '9') do
- n = (n * 10.0f) + (*num++ - '0');
- while (*num >= '0' && *num <= '9'); /* Number? */
- if (*num == '.' && num[1] >= '0' && num[1] <= '9') {
- num++;
- do {
- n = (n * 10.0f) + (*num++ - '0');
- scale--;
- } while (*num >= '0' && *num <= '9');
- } /* Fractional part? */
- if (*num == 'e' || *num == 'E') /* Exponent? */
- {
- num++;
- if (*num == '+')
- num++;
- else if (*num == '-') {
- signsubscale = -1;
- num++; /* With sign? */
- }
- while (*num >= '0' && *num <= '9')
- subscale = (subscale * 10) + (*num++ - '0'); /* Number? */
+ if ((input_buffer == NULL) || (input_buffer->content == NULL)) {
+ return false;
}
- n = sign * n
- * pow(10.0f,
- (scale + subscale * signsubscale)); /* number = +/- number.fraction
- * 10^+/- exponent */
+ /* copy the number into a temporary buffer and replace '.' with the decimal
+ * point of the current locale (for strtod) This also takes care of '\0' not
+ * necessarily being available for marking the end of the input */
+ for (i = 0; (i < (sizeof(number_c_string) - 1))
+ && can_access_at_index(input_buffer, i);
+ i++) {
+ switch (buffer_at_offset(input_buffer)[i]) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '+':
+ case '-':
+ case 'e':
+ case 'E':
+ number_c_string[i] = buffer_at_offset(input_buffer)[i];
+ break;
+
+ case '.':
+ number_c_string[i] = decimal_point;
+ break;
+
+ default:
+ goto loop_end;
+ }
+ }
+loop_end:
+ number_c_string[i] = '\0';
+
+ number = strtod((const char*)number_c_string, (char**)&after_end);
+ if (number_c_string == after_end) {
+ return false; /* parse_error */
+ }
+
+ item->valuedouble = number;
+
+ /* use saturation in case of overflow */
+ if (number >= INT_MAX) {
+ item->valueint = INT_MAX;
+ } else if (number <= (double)INT_MIN) {
+ item->valueint = INT_MIN;
+ } else {
+ item->valueint = (int)number;
+ }
- item->valuedouble = n;
- item->valueint = (int)n;
item->type = cJSON_Number;
- return num;
+
+ input_buffer->offset += (size_t)(after_end - number_c_string);
+ return true;
+}
+
+/* don't ask me, but the original cJSON_SetNumberValue returns an integer or
+ * double */
+double cJSON_SetNumberHelper(cJSON* object, double number) {
+ if (number >= INT_MAX) {
+ object->valueint = INT_MAX;
+ } else if (number <= (double)INT_MIN) {
+ object->valueint = INT_MIN;
+ } else {
+ object->valueint = (int)number;
+ }
+
+ return object->valuedouble = number;
+}
+
+char* cJSON_SetValuestring(cJSON* object, const char* valuestring) {
+ char* copy = NULL;
+ /* if object's type is not cJSON_String or is cJSON_IsReference, it should not
+ * set valuestring */
+ if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) {
+ return NULL;
+ }
+ if (strlen(valuestring) <= strlen(object->valuestring)) {
+ strcpy(object->valuestring, valuestring);
+ return object->valuestring;
+ }
+ copy = (char*)cJSON_strdup((const unsigned char*)valuestring, &global_hooks);
+ if (copy == NULL) {
+ return NULL;
+ }
+ if (object->valuestring != NULL) {
+ cJSON_free(object->valuestring);
+ }
+ object->valuestring = copy;
+
+ return copy;
+}
+
+typedef struct {
+ unsigned char* buffer;
+ size_t length;
+ size_t offset;
+ size_t depth; /* current nesting depth (for formatted printing) */
+ cJSON_bool noalloc;
+ cJSON_bool format; /* is this print a formatted print */
+ internal_hooks hooks;
+} printbuffer;
+
+/* realloc printbuffer if necessary to have at least "needed" bytes more */
+static unsigned char* ensure(printbuffer* const p, size_t needed) {
+ unsigned char* newbuffer = NULL;
+ size_t newsize = 0;
+
+ if ((p == NULL) || (p->buffer == NULL)) {
+ return NULL;
+ }
+
+ if ((p->length > 0) && (p->offset >= p->length)) {
+ /* make sure that offset is valid */
+ return NULL;
+ }
+
+ if (needed > INT_MAX) {
+ /* sizes bigger than INT_MAX are currently not supported */
+ return NULL;
+ }
+
+ needed += p->offset + 1;
+ if (needed <= p->length) {
+ return p->buffer + p->offset;
+ }
+
+ if (p->noalloc) {
+ return NULL;
+ }
+
+ /* calculate new buffer size */
+ if (needed > (INT_MAX / 2)) {
+ /* overflow of int, use INT_MAX if possible */
+ if (needed <= INT_MAX) {
+ newsize = INT_MAX;
+ } else {
+ return NULL;
+ }
+ } else {
+ newsize = needed * 2;
+ }
+
+ if (p->hooks.reallocate != NULL) {
+ /* reallocate with realloc if available */
+ newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize);
+ if (newbuffer == NULL) {
+ p->hooks.deallocate(p->buffer);
+ p->length = 0;
+ p->buffer = NULL;
+
+ return NULL;
+ }
+ } else {
+ /* otherwise reallocate manually */
+ newbuffer = (unsigned char*)p->hooks.allocate(newsize);
+ if (!newbuffer) {
+ p->hooks.deallocate(p->buffer);
+ p->length = 0;
+ p->buffer = NULL;
+
+ return NULL;
+ }
+
+ memcpy(newbuffer, p->buffer, p->offset + 1);
+ p->hooks.deallocate(p->buffer);
+ }
+ p->length = newsize;
+ p->buffer = newbuffer;
+
+ return newbuffer + p->offset;
+}
+
+/* calculate the new length of the string in a printbuffer and update the offset
+ */
+static void update_offset(printbuffer* const buffer) {
+ const unsigned char* buffer_pointer = NULL;
+ if ((buffer == NULL) || (buffer->buffer == NULL)) {
+ return;
+ }
+ buffer_pointer = buffer->buffer + buffer->offset;
+
+ buffer->offset += strlen((const char*)buffer_pointer);
+}
+
+/* securely comparison of floating-point variables */
+static cJSON_bool compare_double(double a, double b) {
+ double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b);
+ return (fabs(a - b) <= maxVal * DBL_EPSILON);
}
/* Render the number nicely from the given item into a string. */
-static auto print_number(cJSON* item) -> char* {
- char* str;
+static cJSON_bool print_number(const cJSON* const item,
+ printbuffer* const output_buffer) {
+ unsigned char* output_pointer = NULL;
double d = item->valuedouble;
- if (fabs(((double)item->valueint) - d) <= DBL_EPSILON && d <= INT_MAX
- && d >= INT_MIN) {
- size_t sz{21};
- str = (char*)cJSON_malloc(sz); /* 2^64+1 can be represented in 21 chars. */
- if (str) snprintf(str, sz, "%d", item->valueint);
+ int length = 0;
+ size_t i = 0;
+ unsigned char number_buffer[26] = {
+ 0}; /* temporary buffer to print the number into */
+ unsigned char decimal_point = get_decimal_point();
+ double test = 0.0;
+
+ if (output_buffer == NULL) {
+ return false;
+ }
+
+ /* This checks for NaN and Infinity */
+ if (std::isnan(d) || std::isinf(d)) {
+ length = snprintf((char*)number_buffer, sizeof(number_buffer), "null");
+ } else if (d == (double)item->valueint) {
+ length = snprintf((char*)number_buffer, sizeof(number_buffer), "%d",
+ item->valueint);
} else {
- size_t sz{64};
- str = (char*)cJSON_malloc(sz); /* This is a nice tradeoff. */
- if (str) {
- if (fabs(floor(d) - d) <= DBL_EPSILON && fabs(d) < 1.0e60)
- snprintf(str, sz, "%.0f", d);
- else if (fabs(d) < 1.0e-6 || fabs(d) > 1.0e9)
- snprintf(str, sz, "%e", d);
- else
- snprintf(str, sz, "%f", d);
+ /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits
+ */
+ length = snprintf((char*)number_buffer, sizeof(number_buffer), "%1.15g", d);
+
+ /* Check whether the original double can be recovered */
+ if ((sscanf((char*)number_buffer, "%lg", &test) != 1)
+ || !compare_double((double)test, d)) {
+ /* If not, print with 17 decimal places of precision */
+ length =
+ snprintf((char*)number_buffer, sizeof(number_buffer), "%1.17g", d);
}
}
- return str;
+
+ /* sprintf failed or buffer overrun occurred */
+ if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) {
+ return false;
+ }
+
+ /* reserve appropriate space in the output */
+ output_pointer = ensure(output_buffer, (size_t)length + sizeof(""));
+ if (output_pointer == NULL) {
+ return false;
+ }
+
+ /* copy the printed number to the output and replace locale
+ * dependent decimal point with '.' */
+ for (i = 0; i < ((size_t)length); i++) {
+ if (number_buffer[i] == decimal_point) {
+ output_pointer[i] = '.';
+ continue;
+ }
+
+ output_pointer[i] = number_buffer[i];
+ }
+ output_pointer[i] = '\0';
+
+ output_buffer->offset += (size_t)length;
+
+ return true;
}
-static auto parse_hex4(const char* str) -> unsigned {
- unsigned h = 0;
- if (*str >= '0' && *str <= '9')
- h += (*str) - '0';
- else if (*str >= 'A' && *str <= 'F')
- h += 10 + (*str) - 'A';
- else if (*str >= 'a' && *str <= 'f')
- h += 10 + (*str) - 'a';
- else
- return 0;
- h = h << 4;
- str++;
- if (*str >= '0' && *str <= '9')
- h += (*str) - '0';
- else if (*str >= 'A' && *str <= 'F')
- h += 10 + (*str) - 'A';
- else if (*str >= 'a' && *str <= 'f')
- h += 10 + (*str) - 'a';
- else
- return 0;
- h = h << 4;
- str++;
- if (*str >= '0' && *str <= '9')
- h += (*str) - '0';
- else if (*str >= 'A' && *str <= 'F')
- h += 10 + (*str) - 'A';
- else if (*str >= 'a' && *str <= 'f')
- h += 10 + (*str) - 'a';
- else
- return 0;
- h = h << 4;
- str++;
- if (*str >= '0' && *str <= '9')
- h += (*str) - '0';
- else if (*str >= 'A' && *str <= 'F')
- h += 10 + (*str) - 'A';
- else if (*str >= 'a' && *str <= 'f')
- h += 10 + (*str) - 'a';
- else
- return 0;
+/* parse 4 digit hexadecimal number */
+static unsigned parse_hex4(const unsigned char* const input) {
+ unsigned int h = 0;
+ size_t i = 0;
+
+ for (i = 0; i < 4; i++) {
+ /* parse digit */
+ if ((input[i] >= '0') && (input[i] <= '9')) {
+ h += (unsigned int)input[i] - '0';
+ } else if ((input[i] >= 'A') && (input[i] <= 'F')) {
+ h += (unsigned int)10 + input[i] - 'A';
+ } else if ((input[i] >= 'a') && (input[i] <= 'f')) {
+ h += (unsigned int)10 + input[i] - 'a';
+ } else /* invalid */
+ {
+ return 0;
+ }
+
+ if (i < 3) {
+ /* shift left to make place for the next nibble */
+ h = h << 4;
+ }
+ }
+
return h;
}
-/* Parse the input text into an unescaped cstring, and populate item. */
-static const unsigned char firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0,
- 0xF0, 0xF8, 0xFC};
-static auto parse_string(cJSON* item, const char* str) -> const char* {
- const char* ptr = str + 1;
- char* ptr2;
- char* out;
- size_t len = 0;
- unsigned uc, uc2;
- if (*str != '\"') {
- ep = str;
- return nullptr;
- } /* not a string! */
+/* converts a UTF-16 literal to UTF-8
+ * A literal can be one or two sequences of the form \uXXXX */
+static unsigned char utf16_literal_to_utf8(
+ const unsigned char* const input_pointer,
+ const unsigned char* const input_end, unsigned char** output_pointer) {
+ long unsigned int codepoint = 0;
+ unsigned int first_code = 0;
+ const unsigned char* first_sequence = input_pointer;
+ unsigned char utf8_length = 0;
+ unsigned char utf8_position = 0;
+ unsigned char sequence_length = 0;
+ unsigned char first_byte_mark = 0;
- while (*ptr != '\"' && *ptr && ++len) {
- if (*ptr++ == '\\') {
- ptr++; /* Skip escaped quotes. */
+ if ((input_end - first_sequence) < 6) {
+ /* input ends unexpectedly */
+ goto fail;
+ }
+
+ /* get the first utf16 sequence */
+ first_code = parse_hex4(first_sequence + 2);
+
+ /* check that the code is valid */
+ if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) {
+ goto fail;
+ }
+
+ /* UTF16 surrogate pair */
+ if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) {
+ const unsigned char* second_sequence = first_sequence + 6;
+ unsigned int second_code = 0;
+ sequence_length = 12; /* \uXXXX\uXXXX */
+
+ if ((input_end - second_sequence) < 6) {
+ /* input ends unexpectedly */
+ goto fail;
+ }
+
+ if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) {
+ /* missing second half of the surrogate pair */
+ goto fail;
+ }
+
+ /* get the second utf16 sequence */
+ second_code = parse_hex4(second_sequence + 2);
+ /* check that the code is valid */
+ if ((second_code < 0xDC00) || (second_code > 0xDFFF)) {
+ /* invalid second half of the surrogate pair */
+ goto fail;
+ }
+
+ /* calculate the unicode codepoint from the surrogate pair */
+ codepoint =
+ 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF));
+ } else {
+ sequence_length = 6; /* \uXXXX */
+ codepoint = first_code;
+ }
+
+ /* encode as UTF-8
+ * takes at maximum 4 bytes to encode:
+ * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
+ if (codepoint < 0x80) {
+ /* normal ascii, encoding 0xxxxxxx */
+ utf8_length = 1;
+ } else if (codepoint < 0x800) {
+ /* two bytes, encoding 110xxxxx 10xxxxxx */
+ utf8_length = 2;
+ first_byte_mark = 0xC0; /* 11000000 */
+ } else if (codepoint < 0x10000) {
+ /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */
+ utf8_length = 3;
+ first_byte_mark = 0xE0; /* 11100000 */
+ } else if (codepoint <= 0x10FFFF) {
+ /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */
+ utf8_length = 4;
+ first_byte_mark = 0xF0; /* 11110000 */
+ } else {
+ /* invalid unicode codepoint */
+ goto fail;
+ }
+
+ /* encode as utf8 */
+ for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0;
+ utf8_position--) {
+ /* 10xxxxxx */
+ (*output_pointer)[utf8_position] =
+ (unsigned char)((codepoint | 0x80) & 0xBF);
+ codepoint >>= 6;
+ }
+ /* encode first byte */
+ if (utf8_length > 1) {
+ (*output_pointer)[0] =
+ (unsigned char)((codepoint | first_byte_mark) & 0xFF);
+ } else {
+ (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F);
+ }
+
+ *output_pointer += utf8_length;
+
+ return sequence_length;
+
+fail:
+ return 0;
+}
+
+/* Parse the input text into an unescaped cinput, and populate item. */
+static cJSON_bool parse_string(cJSON* const item,
+ parse_buffer* const input_buffer) {
+ const unsigned char* input_pointer = buffer_at_offset(input_buffer) + 1;
+ const unsigned char* input_end = buffer_at_offset(input_buffer) + 1;
+ unsigned char* output_pointer = NULL;
+ unsigned char* output = NULL;
+
+ /* not a string */
+ if (buffer_at_offset(input_buffer)[0] != '\"') {
+ goto fail;
+ }
+
+ {
+ /* calculate approximate size of the output (overestimate) */
+ size_t allocation_length = 0;
+ size_t skipped_bytes = 0;
+ while (((size_t)(input_end - input_buffer->content) < input_buffer->length)
+ && (*input_end != '\"')) {
+ /* is escape sequence */
+ if (input_end[0] == '\\') {
+ if ((size_t)(input_end + 1 - input_buffer->content)
+ >= input_buffer->length) {
+ /* prevent buffer overflow when last input character is a backslash */
+ goto fail;
+ }
+ skipped_bytes++;
+ input_end++;
+ }
+ input_end++;
+ }
+ if (((size_t)(input_end - input_buffer->content) >= input_buffer->length)
+ || (*input_end != '\"')) {
+ goto fail; /* string ended unexpectedly */
+ }
+
+ /* This is at most how much we need for the output */
+ allocation_length =
+ (size_t)(input_end - buffer_at_offset(input_buffer)) - skipped_bytes;
+ output = (unsigned char*)input_buffer->hooks.allocate(allocation_length
+ + sizeof(""));
+ if (output == NULL) {
+ goto fail; /* allocation failure */
}
}
- // This is how long we need for the string, roughly.
- out = (char*)cJSON_malloc(len + 1);
- if (!out) return nullptr;
+ output_pointer = output;
+ /* loop through the string literal */
+ while (input_pointer < input_end) {
+ if (*input_pointer != '\\') {
+ *output_pointer++ = *input_pointer++;
+ }
+ /* escape sequence */
+ else {
+ unsigned char sequence_length = 2;
+ if ((input_end - input_pointer) < 1) {
+ goto fail;
+ }
- ptr = str + 1;
- ptr2 = out;
- while (*ptr != '\"' && *ptr) {
- if (*ptr != '\\') {
- *ptr2++ = *ptr++;
- } else {
- ptr++;
- switch (*ptr) {
+ switch (input_pointer[1]) {
case 'b':
- *ptr2++ = '\b';
+ *output_pointer++ = '\b';
break;
case 'f':
- *ptr2++ = '\f';
+ *output_pointer++ = '\f';
break;
case 'n':
- *ptr2++ = '\n';
+ *output_pointer++ = '\n';
break;
case 'r':
- *ptr2++ = '\r';
+ *output_pointer++ = '\r';
break;
case 't':
- *ptr2++ = '\t';
+ *output_pointer++ = '\t';
break;
- case 'u': /* transcode utf16 to utf8. */
- uc = parse_hex4(ptr + 1);
- ptr += 4; /* get the unicode char. */
-
- if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) {
- break; // check for invalid.
- }
-
- // UTF16 surrogate pairs.
- if (uc >= 0xD800 && uc <= 0xDBFF) {
- if (ptr[1] != '\\' || ptr[2] != 'u')
- break; /* missing second-half of surrogate. */
- uc2 = parse_hex4(ptr + 3);
- ptr += 6;
- if (uc2 < 0xDC00 || uc2 > 0xDFFF)
- break; /* invalid second-half of surrogate. */
- uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF));
- }
-
- len = 4;
- if (uc < 0x80) {
- len = 1;
- } else if (uc < 0x800) {
- len = 2;
- } else if (uc < 0x10000) {
- len = 3;
- }
- ptr2 += len;
-
- switch (len) {
- case 4: // NOLINT(bugprone-branch-clone)
- *--ptr2 = static_cast((uc | 0x80) & 0xBF);
- uc >>= 6;
- case 3:
- *--ptr2 = static_cast((uc | 0x80) & 0xBF);
- uc >>= 6;
- case 2:
- *--ptr2 = static_cast((uc | 0x80) & 0xBF);
- uc >>= 6;
- case 1:
- *--ptr2 = static_cast(uc | firstByteMark[len]);
- default:
- break;
- }
- ptr2 += len;
+ case '\"':
+ case '\\':
+ case '/':
+ *output_pointer++ = input_pointer[1];
break;
+
+ /* UTF-16 literal */
+ case 'u':
+ sequence_length =
+ utf16_literal_to_utf8(input_pointer, input_end, &output_pointer);
+ if (sequence_length == 0) {
+ /* failed to convert UTF16-literal to UTF-8 */
+ goto fail;
+ }
+ break;
+
default:
- *ptr2++ = *ptr;
- break;
+ goto fail;
}
- ptr++;
+ input_pointer += sequence_length;
}
}
- *ptr2 = 0;
- if (*ptr == '\"') {
- ptr++;
- }
- item->valuestring = out;
+
+ /* zero terminate the output */
+ *output_pointer = '\0';
+
item->type = cJSON_String;
- return ptr;
+ item->valuestring = (char*)output;
+
+ input_buffer->offset = (size_t)(input_end - input_buffer->content);
+ input_buffer->offset++;
+
+ return true;
+
+fail:
+ if (output != NULL) {
+ input_buffer->hooks.deallocate(output);
+ }
+
+ if (input_pointer != NULL) {
+ input_buffer->offset = (size_t)(input_pointer - input_buffer->content);
+ }
+
+ return false;
}
/* Render the cstring provided to an escaped version that can be printed. */
-static auto print_string_ptr(const char* str) -> char* {
- const char* ptr;
- char *ptr2, *out;
- size_t len = 0;
- unsigned char token;
+static cJSON_bool print_string_ptr(const unsigned char* const input,
+ printbuffer* const output_buffer) {
+ const unsigned char* input_pointer = NULL;
+ unsigned char* output = NULL;
+ unsigned char* output_pointer = NULL;
+ size_t output_length = 0;
+ /* numbers of additional characters needed for escaping */
+ size_t escape_characters = 0;
- if (!str) {
- return cJSON_strdup("");
+ if (output_buffer == NULL) {
+ return false;
}
- ptr = str;
- while ((token = static_cast(*ptr)) && ++len) {
- if (strchr("\"\\\b\f\n\r\t", token)) {
- len++;
- } else if (token < 32) {
- len += 5;
+
+ /* empty string */
+ if (input == NULL) {
+ output = ensure(output_buffer, sizeof("\"\""));
+ if (output == NULL) {
+ return false;
}
- ptr++;
+ strcpy((char*)output, "\"\"");
+
+ return true;
}
- out = (char*)cJSON_malloc(len + 3);
- if (!out) {
- return nullptr;
+ /* set "flag" to 1 if something needs to be escaped */
+ for (input_pointer = input; *input_pointer; input_pointer++) {
+ switch (*input_pointer) {
+ case '\"':
+ case '\\':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ /* one character escape sequence */
+ escape_characters++;
+ break;
+ default:
+ if (*input_pointer < 32) {
+ /* UTF-16 escape sequence uXXXX */
+ escape_characters += 5;
+ }
+ break;
+ }
+ }
+ output_length = (size_t)(input_pointer - input) + escape_characters;
+
+ output = ensure(output_buffer, output_length + sizeof("\"\""));
+ if (output == NULL) {
+ return false;
}
- ptr2 = out;
- ptr = str;
- *ptr2++ = '\"';
- while (*ptr) {
- if ((unsigned char)*ptr > 31 && *ptr != '\"' && *ptr != '\\') {
- *ptr2++ = *ptr++;
+ /* no characters have to be escaped */
+ if (escape_characters == 0) {
+ output[0] = '\"';
+ memcpy(output + 1, input, output_length);
+ output[output_length + 1] = '\"';
+ output[output_length + 2] = '\0';
+
+ return true;
+ }
+
+ output[0] = '\"';
+ output_pointer = output + 1;
+ /* copy the string */
+ for (input_pointer = input; *input_pointer != '\0';
+ (void)input_pointer++, output_pointer++) {
+ if ((*input_pointer > 31) && (*input_pointer != '\"')
+ && (*input_pointer != '\\')) {
+ /* normal character, copy */
+ *output_pointer = *input_pointer;
} else {
- *ptr2++ = '\\';
- switch (token = static_cast(*ptr++)) {
+ /* character needs to be escaped */
+ *output_pointer++ = '\\';
+ switch (*input_pointer) {
case '\\':
- *ptr2++ = '\\';
+ *output_pointer = '\\';
break;
case '\"':
- *ptr2++ = '\"';
+ *output_pointer = '\"';
break;
case '\b':
- *ptr2++ = 'b';
+ *output_pointer = 'b';
break;
case '\f':
- *ptr2++ = 'f';
+ *output_pointer = 'f';
break;
case '\n':
- *ptr2++ = 'n';
+ *output_pointer = 'n';
break;
case '\r':
- *ptr2++ = 'r';
+ *output_pointer = 'r';
break;
case '\t':
- *ptr2++ = 't';
+ *output_pointer = 't';
break;
default:
- snprintf(ptr2, 20, "u%04x", token);
- ptr2 += 5;
- break; /* escape and print */
+ /* escape and print as unicode codepoint */
+ snprintf((char*)output_pointer, 32, "u%04x", *input_pointer);
+ output_pointer += 4;
+ break;
}
}
}
- *ptr2++ = '\"';
- *ptr2 = 0;
- return out;
+ output[output_length + 1] = '\"';
+ output[output_length + 2] = '\0';
+
+ return true;
}
-/* Invote print_string_ptr (which is useful) on an item. */
-static auto print_string(cJSON* item) -> char* {
- return print_string_ptr(item->valuestring);
+
+/* Invoke print_string_ptr (which is useful) on an item. */
+static cJSON_bool print_string(const cJSON* const item, printbuffer* const p) {
+ return print_string_ptr((unsigned char*)item->valuestring, p);
}
/* Predeclare these prototypes. */
-static auto parse_value(cJSON* item, const char* value) -> const char*;
-static auto print_value(cJSON* item, int depth, int fmt) -> char*;
-static auto parse_array(cJSON* item, const char* value) -> const char*;
-static auto print_array(cJSON* item, int depth, int fmt) -> char*;
-static auto parse_object(cJSON* item, const char* value) -> const char*;
-static auto print_object(cJSON* item, int depth, int fmt) -> char*;
+static cJSON_bool parse_value(cJSON* const item,
+ parse_buffer* const input_buffer);
+static cJSON_bool print_value(const cJSON* const item,
+ printbuffer* const output_buffer);
+static cJSON_bool parse_array(cJSON* const item,
+ parse_buffer* const input_buffer);
+static cJSON_bool print_array(const cJSON* const item,
+ printbuffer* const output_buffer);
+static cJSON_bool parse_object(cJSON* const item,
+ parse_buffer* const input_buffer);
+static cJSON_bool print_object(const cJSON* const item,
+ printbuffer* const output_buffer);
/* Utility to jump whitespace and cr/lf */
-static auto skip(const char* in) -> const char* {
- while (in && *in && (unsigned char)*in <= 32) in++;
- return in;
+static parse_buffer* buffer_skip_whitespace(parse_buffer* const buffer) {
+ if ((buffer == NULL) || (buffer->content == NULL)) {
+ return NULL;
+ }
+
+ if (cannot_access_at_index(buffer, 0)) {
+ return buffer;
+ }
+
+ while (can_access_at_index(buffer, 0)
+ && (buffer_at_offset(buffer)[0] <= 32)) {
+ buffer->offset++;
+ }
+
+ if (buffer->offset == buffer->length) {
+ buffer->offset--;
+ }
+
+ return buffer;
+}
+
+/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */
+static parse_buffer* skip_utf8_bom(parse_buffer* const buffer) {
+ if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) {
+ return NULL;
+ }
+
+ if (can_access_at_index(buffer, 4)
+ && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3)
+ == 0)) {
+ buffer->offset += 3;
+ }
+
+ return buffer;
+}
+
+cJSON* cJSON_ParseWithOpts(const char* value, const char** return_parse_end,
+ cJSON_bool require_null_terminated) {
+ size_t buffer_length;
+
+ if (NULL == value) {
+ return NULL;
+ }
+
+ /* Adding null character size due to require_null_terminated. */
+ buffer_length = strlen(value) + sizeof("");
+
+ return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end,
+ require_null_terminated);
}
/* Parse an object - create a new root, and populate. */
-auto cJSON_ParseWithOpts(const char* value, const char** return_parse_end,
- int require_null_terminated) -> cJSON* {
- cJSON* c = cJSON_New_Item();
- ep = nullptr;
- if (!c) {
- return nullptr; /* memory fail */
+cJSON* cJSON_ParseWithLengthOpts(const char* value, size_t buffer_length,
+ const char** return_parse_end,
+ cJSON_bool require_null_terminated) {
+ parse_buffer buffer = {0, 0, 0, 0, {0, 0, 0}};
+ cJSON* item = NULL;
+
+ /* reset error position */
+ global_error.json = NULL;
+ global_error.position = 0;
+
+ if (value == NULL || 0 == buffer_length) {
+ goto fail;
}
- const char* end = parse_value(c, skip(value));
- if (!end) {
- cJSON_Delete(c);
- return nullptr;
- } /* parse failure. ep is set. */
+ buffer.content = (const unsigned char*)value;
+ buffer.length = buffer_length;
+ buffer.offset = 0;
+ buffer.hooks = global_hooks;
+
+ item = cJSON_New_Item(&global_hooks);
+ if (item == NULL) /* memory fail */
+ {
+ goto fail;
+ }
+
+ if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) {
+ /* parse failure. ep is set. */
+ goto fail;
+ }
/* if we require null-terminated JSON without appended garbage, skip and then
* check for a null terminator */
if (require_null_terminated) {
- end = skip(end);
- if (*end) {
- cJSON_Delete(c);
- ep = end;
- return nullptr;
+ buffer_skip_whitespace(&buffer);
+ if ((buffer.offset >= buffer.length)
+ || buffer_at_offset(&buffer)[0] != '\0') {
+ goto fail;
}
}
- if (return_parse_end) *return_parse_end = end;
- return c;
+ if (return_parse_end) {
+ *return_parse_end = (const char*)buffer_at_offset(&buffer);
+ }
+
+ return item;
+
+fail:
+ if (item != NULL) {
+ cJSON_Delete(item);
+ }
+
+ if (value != NULL) {
+ error local_error;
+ local_error.json = (const unsigned char*)value;
+ local_error.position = 0;
+
+ if (buffer.offset < buffer.length) {
+ local_error.position = buffer.offset;
+ } else if (buffer.length > 0) {
+ local_error.position = buffer.length - 1;
+ }
+
+ if (return_parse_end != NULL) {
+ *return_parse_end = (const char*)local_error.json + local_error.position;
+ }
+
+ global_error = local_error;
+ }
+
+ return NULL;
}
+
/* Default options for cJSON_Parse */
-auto cJSON_Parse(const char* value) -> cJSON* {
- return cJSON_ParseWithOpts(value, nullptr, 0);
+cJSON* cJSON_Parse(const char* value) {
+ return cJSON_ParseWithOpts(value, 0, 0);
+}
+
+cJSON* cJSON_ParseWithLength(const char* value, size_t buffer_length) {
+ return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0);
+}
+
+#define cjson_min(a, b) (((a) < (b)) ? (a) : (b))
+
+static unsigned char* print(const cJSON* const item, cJSON_bool format,
+ const internal_hooks* const hooks) {
+ static const size_t default_buffer_size = 256;
+ printbuffer buffer[1];
+ unsigned char* printed = NULL;
+
+ memset(buffer, 0, sizeof(buffer));
+
+ /* create buffer */
+ buffer->buffer = (unsigned char*)hooks->allocate(default_buffer_size);
+ buffer->length = default_buffer_size;
+ buffer->format = format;
+ buffer->hooks = *hooks;
+ if (buffer->buffer == NULL) {
+ goto fail;
+ }
+
+ /* print the value */
+ if (!print_value(item, buffer)) {
+ goto fail;
+ }
+ update_offset(buffer);
+
+ /* check if reallocate is available */
+ if (hooks->reallocate != NULL) {
+ printed =
+ (unsigned char*)hooks->reallocate(buffer->buffer, buffer->offset + 1);
+ if (printed == NULL) {
+ goto fail;
+ }
+ buffer->buffer = NULL;
+ } else /* otherwise copy the JSON over to a new buffer */
+ {
+ printed = (unsigned char*)hooks->allocate(buffer->offset + 1);
+ if (printed == NULL) {
+ goto fail;
+ }
+ memcpy(printed, buffer->buffer,
+ cjson_min(buffer->length, buffer->offset + 1));
+ printed[buffer->offset] = '\0'; /* just to be sure */
+
+ /* free the buffer */
+ hooks->deallocate(buffer->buffer);
+ }
+
+ return printed;
+
+fail:
+ if (buffer->buffer != NULL) {
+ hooks->deallocate(buffer->buffer);
+ }
+
+ if (printed != NULL) {
+ hooks->deallocate(printed);
+ }
+
+ return NULL;
}
/* Render a cJSON item/entity/structure to text. */
-auto cJSON_Print(cJSON* item) -> char* { return print_value(item, 0, 1); }
-auto cJSON_PrintUnformatted(cJSON* item) -> char* {
- return print_value(item, 0, 0);
+char* cJSON_Print(const cJSON* item) {
+ return (char*)print(item, true, &global_hooks);
+}
+
+char* cJSON_PrintUnformatted(const cJSON* item) {
+ return (char*)print(item, false, &global_hooks);
+}
+
+char* cJSON_PrintBuffered(const cJSON* item, int prebuffer, cJSON_bool fmt) {
+ printbuffer p = {0, 0, 0, 0, 0, 0, {0, 0, 0}};
+
+ if (prebuffer < 0) {
+ return NULL;
+ }
+
+ p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer);
+ if (!p.buffer) {
+ return NULL;
+ }
+
+ p.length = (size_t)prebuffer;
+ p.offset = 0;
+ p.noalloc = false;
+ p.format = fmt;
+ p.hooks = global_hooks;
+
+ if (!print_value(item, &p)) {
+ global_hooks.deallocate(p.buffer);
+ return NULL;
+ }
+
+ return (char*)p.buffer;
+}
+
+cJSON_bool cJSON_PrintPreallocated(cJSON* item, char* buffer, const int length,
+ const cJSON_bool format) {
+ printbuffer p = {0, 0, 0, 0, 0, 0, {0, 0, 0}};
+
+ if ((length < 0) || (buffer == NULL)) {
+ return false;
+ }
+
+ p.buffer = (unsigned char*)buffer;
+ p.length = (size_t)length;
+ p.offset = 0;
+ p.noalloc = true;
+ p.format = format;
+ p.hooks = global_hooks;
+
+ return print_value(item, &p);
}
/* Parser core - when encountering text, process appropriately. */
-static auto parse_value(cJSON* item, const char* value) -> const char* {
- if (!value) {
- return nullptr; /* Fail on null. */
- }
- if (!strncmp(value, "null", 4)) {
- item->type = cJSON_NULL;
- return value + 4;
- }
- if (!strncmp(value, "false", 5)) {
- item->type = cJSON_False;
- return value + 5;
- }
- if (!strncmp(value, "true", 4)) {
- item->type = cJSON_True;
- item->valueint = 1;
- return value + 4;
- }
- if (*value == '\"') {
- return parse_string(item, value);
- }
- if (*value == '-' || (*value >= '0' && *value <= '9')) {
- return parse_number(item, value);
- }
- if (*value == '[') {
- return parse_array(item, value);
- }
- if (*value == '{') {
- return parse_object(item, value);
+static cJSON_bool parse_value(cJSON* const item,
+ parse_buffer* const input_buffer) {
+ if ((input_buffer == NULL) || (input_buffer->content == NULL)) {
+ return false; /* no input */
}
- ep = value;
- return nullptr; /* failure. */
+ /* parse the different types of values */
+ /* null */
+ if (can_read(input_buffer, 4)
+ && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4)
+ == 0)) {
+ item->type = cJSON_NULL;
+ input_buffer->offset += 4;
+ return true;
+ }
+ /* false */
+ if (can_read(input_buffer, 5)
+ && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5)
+ == 0)) {
+ item->type = cJSON_False;
+ input_buffer->offset += 5;
+ return true;
+ }
+ /* true */
+ if (can_read(input_buffer, 4)
+ && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4)
+ == 0)) {
+ item->type = cJSON_True;
+ item->valueint = 1;
+ input_buffer->offset += 4;
+ return true;
+ }
+ /* string */
+ if (can_access_at_index(input_buffer, 0)
+ && (buffer_at_offset(input_buffer)[0] == '\"')) {
+ return parse_string(item, input_buffer);
+ }
+ /* number */
+ if (can_access_at_index(input_buffer, 0)
+ && ((buffer_at_offset(input_buffer)[0] == '-')
+ || ((buffer_at_offset(input_buffer)[0] >= '0')
+ && (buffer_at_offset(input_buffer)[0] <= '9')))) {
+ return parse_number(item, input_buffer);
+ }
+ /* array */
+ if (can_access_at_index(input_buffer, 0)
+ && (buffer_at_offset(input_buffer)[0] == '[')) {
+ return parse_array(item, input_buffer);
+ }
+ /* object */
+ if (can_access_at_index(input_buffer, 0)
+ && (buffer_at_offset(input_buffer)[0] == '{')) {
+ return parse_object(item, input_buffer);
+ }
+
+ return false;
}
/* Render a value to text. */
-static auto print_value(cJSON* item, int depth, int fmt) -> char* {
- char* out = nullptr;
- if (!item) {
- return nullptr;
+static cJSON_bool print_value(const cJSON* const item,
+ printbuffer* const output_buffer) {
+ unsigned char* output = NULL;
+
+ if ((item == NULL) || (output_buffer == NULL)) {
+ return false;
}
- switch ((item->type) & 255) {
+
+ switch ((item->type) & 0xFF) {
case cJSON_NULL:
- out = cJSON_strdup("null");
- break;
+ output = ensure(output_buffer, 5);
+ if (output == NULL) {
+ return false;
+ }
+ strcpy((char*)output, "null");
+ return true;
+
case cJSON_False:
- out = cJSON_strdup("false");
- break;
+ output = ensure(output_buffer, 6);
+ if (output == NULL) {
+ return false;
+ }
+ strcpy((char*)output, "false");
+ return true;
+
case cJSON_True:
- out = cJSON_strdup("true");
- break;
+ output = ensure(output_buffer, 5);
+ if (output == NULL) {
+ return false;
+ }
+ strcpy((char*)output, "true");
+ return true;
+
case cJSON_Number:
- out = print_number(item);
- break;
+ return print_number(item, output_buffer);
+
+ case cJSON_Raw: {
+ size_t raw_length = 0;
+ if (item->valuestring == NULL) {
+ return false;
+ }
+
+ raw_length = strlen(item->valuestring) + sizeof("");
+ output = ensure(output_buffer, raw_length);
+ if (output == NULL) {
+ return false;
+ }
+ memcpy(output, item->valuestring, raw_length);
+ return true;
+ }
+
case cJSON_String:
- out = print_string(item);
- break;
+ return print_string(item, output_buffer);
+
case cJSON_Array:
- out = print_array(item, depth, fmt);
- break;
+ return print_array(item, output_buffer);
+
case cJSON_Object:
- out = print_object(item, depth, fmt);
- break;
+ return print_object(item, output_buffer);
+
default:
- break;
+ return false;
}
- return out;
}
/* Build an array from input text. */
-static auto parse_array(cJSON* item, const char* value) -> const char* {
- cJSON* child;
- if (*value != '[') {
- ep = value;
- return nullptr;
- } /* not an array! */
+static cJSON_bool parse_array(cJSON* const item,
+ parse_buffer* const input_buffer) {
+ cJSON* head = NULL; /* head of the linked list */
+ cJSON* current_item = NULL;
+
+ if (input_buffer->depth >= CJSON_NESTING_LIMIT) {
+ return false; /* to deeply nested */
+ }
+ input_buffer->depth++;
+
+ if (buffer_at_offset(input_buffer)[0] != '[') {
+ /* not an array */
+ goto fail;
+ }
+
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (can_access_at_index(input_buffer, 0)
+ && (buffer_at_offset(input_buffer)[0] == ']')) {
+ /* empty array */
+ goto success;
+ }
+
+ /* check if we skipped to the end of the buffer */
+ if (cannot_access_at_index(input_buffer, 0)) {
+ input_buffer->offset--;
+ goto fail;
+ }
+
+ /* step back to character in front of the first element */
+ input_buffer->offset--;
+ /* loop through the comma separated array elements */
+ do {
+ /* allocate next item */
+ cJSON* new_item = cJSON_New_Item(&(input_buffer->hooks));
+ if (new_item == NULL) {
+ goto fail; /* allocation failure */
+ }
+
+ /* attach next item to list */
+ if (head == NULL) {
+ /* start the linked list */
+ current_item = head = new_item;
+ } else {
+ /* add to the end and advance */
+ current_item->next = new_item;
+ new_item->prev = current_item;
+ current_item = new_item;
+ }
+
+ /* parse next value */
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (!parse_value(current_item, input_buffer)) {
+ goto fail; /* failed to parse value */
+ }
+ buffer_skip_whitespace(input_buffer);
+ } while (can_access_at_index(input_buffer, 0)
+ && (buffer_at_offset(input_buffer)[0] == ','));
+
+ if (cannot_access_at_index(input_buffer, 0)
+ || buffer_at_offset(input_buffer)[0] != ']') {
+ goto fail; /* expected end of array */
+ }
+
+success:
+ input_buffer->depth--;
+
+ if (head != NULL) {
+ head->prev = current_item;
+ }
item->type = cJSON_Array;
- value = skip(value + 1);
- if (*value == ']') {
- return value + 1; /* empty array. */
+ item->child = head;
+
+ input_buffer->offset++;
+
+ return true;
+
+fail:
+ if (head != NULL) {
+ cJSON_Delete(head);
}
- item->child = child = cJSON_New_Item();
- if (!item->child) {
- return nullptr; /* memory fail */
- }
- value = skip(
- parse_value(child, skip(value))); /* skip any spacing, get the value. */
- if (!value) return nullptr;
-
- while (*value == ',') {
- cJSON* new_item;
- if (!(new_item = cJSON_New_Item())) {
- return nullptr; /* memory fail */
- }
- child->next = new_item;
- new_item->prev = child;
- child = new_item;
- value = skip(parse_value(child, skip(value + 1)));
- if (!value) {
- return nullptr; /* memory fail */
- }
- }
-
- if (*value == ']') {
- return value + 1; /* end of array */
- }
- ep = value;
- return nullptr; /* malformed. */
+ return false;
}
/* Render an array to text */
-static auto print_array(cJSON* item, int depth, int fmt) -> char* {
- char** entries;
- char *out = nullptr, *ptr, *ret;
- size_t len = 5;
- cJSON* child = item->child;
- int numentries = 0, i = 0, fail = 0;
+static cJSON_bool print_array(const cJSON* const item,
+ printbuffer* const output_buffer) {
+ unsigned char* output_pointer = NULL;
+ size_t length = 0;
+ cJSON* current_element = item->child;
- /* How many entries in the array? */
- while (child) {
- numentries++;
- child = child->next;
- }
- /* Explicitly handle numentries==0 */
- if (!numentries) {
- out = (char*)cJSON_malloc(3);
- if (out) {
- strcpy(out, "[]"); // NOLINT
- }
- return out;
- }
- /* Allocate an array to hold the values for each */
- entries = (char**)cJSON_malloc(numentries * sizeof(char*));
- if (!entries) {
- return nullptr;
- }
- memset(entries, 0, numentries * sizeof(char*));
- /* Retrieve all the results: */
- child = item->child;
- while (child && !fail) {
- ret = print_value(child, depth + 1, fmt);
- entries[i++] = ret;
- if (ret)
- len += strlen(ret) + 2 + (fmt ? 1 : 0);
- else
- fail = 1;
- child = child->next;
- }
-
- /* If we didn't fail, try to malloc the output string */
- if (!fail) {
- out = (char*)cJSON_malloc(len);
- }
- /* If that fails, we fail. */
- if (!out) {
- fail = 1;
- }
-
- /* Handle failure. */
- if (fail) {
- for (i = 0; i < numentries; i++)
- if (entries[i]) cJSON_free(entries[i]);
- cJSON_free(entries);
- return nullptr;
+ if (output_buffer == NULL) {
+ return false;
}
/* Compose the output array. */
- *out = '[';
- ptr = out + 1;
- *ptr = 0;
- for (i = 0; i < numentries; i++) {
- strcpy(ptr, entries[i]); // NOLINT
- ptr += strlen(entries[i]);
- if (i != numentries - 1) {
- *ptr++ = ',';
- if (fmt) *ptr++ = ' ';
- *ptr = 0;
- }
- cJSON_free(entries[i]);
+ /* opening square bracket */
+ output_pointer = ensure(output_buffer, 1);
+ if (output_pointer == NULL) {
+ return false;
}
- cJSON_free(entries);
- *ptr++ = ']';
- *ptr = 0;
- return out;
+
+ *output_pointer = '[';
+ output_buffer->offset++;
+ output_buffer->depth++;
+
+ while (current_element != NULL) {
+ if (!print_value(current_element, output_buffer)) {
+ return false;
+ }
+ update_offset(output_buffer);
+ if (current_element->next) {
+ length = (size_t)(output_buffer->format ? 2 : 1);
+ output_pointer = ensure(output_buffer, length + 1);
+ if (output_pointer == NULL) {
+ return false;
+ }
+ *output_pointer++ = ',';
+ if (output_buffer->format) {
+ *output_pointer++ = ' ';
+ }
+ *output_pointer = '\0';
+ output_buffer->offset += length;
+ }
+ current_element = current_element->next;
+ }
+
+ output_pointer = ensure(output_buffer, 2);
+ if (output_pointer == NULL) {
+ return false;
+ }
+ *output_pointer++ = ']';
+ *output_pointer = '\0';
+ output_buffer->depth--;
+
+ return true;
}
/* Build an object from the text. */
-static auto parse_object(cJSON* item, const char* value) -> const char* {
- cJSON* child;
- if (*value != '{') {
- ep = value;
- return nullptr;
- } /* not an object! */
+static cJSON_bool parse_object(cJSON* const item,
+ parse_buffer* const input_buffer) {
+ cJSON* head = NULL; /* linked list head */
+ cJSON* current_item = NULL;
- item->type = cJSON_Object;
- value = skip(value + 1);
- if (*value == '}') return value + 1; /* empty array. */
+ if (input_buffer->depth >= CJSON_NESTING_LIMIT) {
+ return false; /* to deeply nested */
+ }
+ input_buffer->depth++;
- item->child = child = cJSON_New_Item();
- if (!item->child) return nullptr;
- value = skip(parse_string(child, skip(value)));
- if (!value) return nullptr;
- child->string = child->valuestring;
- child->valuestring = nullptr;
- if (*value != ':') {
- ep = value;
- return nullptr;
- } /* fail! */
- value = skip(parse_value(
- child, skip(value + 1))); /* skip any spacing, get the value. */
- if (!value) return nullptr;
-
- while (*value == ',') {
- cJSON* new_item;
- if (!(new_item = cJSON_New_Item())) return nullptr; /* memory fail */
- child->next = new_item;
- new_item->prev = child;
- child = new_item;
- value = skip(parse_string(child, skip(value + 1)));
- if (!value) return nullptr;
- child->string = child->valuestring;
- child->valuestring = nullptr;
- if (*value != ':') {
- ep = value;
- return nullptr;
- } /* fail! */
- value = skip(parse_value(
- child, skip(value + 1))); /* skip any spacing, get the value. */
- if (!value) return nullptr;
+ if (cannot_access_at_index(input_buffer, 0)
+ || (buffer_at_offset(input_buffer)[0] != '{')) {
+ goto fail; /* not an object */
}
- if (*value == '}') return value + 1; /* end of array */
- ep = value;
- return nullptr; /* malformed. */
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (can_access_at_index(input_buffer, 0)
+ && (buffer_at_offset(input_buffer)[0] == '}')) {
+ goto success; /* empty object */
+ }
+
+ /* check if we skipped to the end of the buffer */
+ if (cannot_access_at_index(input_buffer, 0)) {
+ input_buffer->offset--;
+ goto fail;
+ }
+
+ /* step back to character in front of the first element */
+ input_buffer->offset--;
+ /* loop through the comma separated array elements */
+ do {
+ /* allocate next item */
+ cJSON* new_item = cJSON_New_Item(&(input_buffer->hooks));
+ if (new_item == NULL) {
+ goto fail; /* allocation failure */
+ }
+
+ /* attach next item to list */
+ if (head == NULL) {
+ /* start the linked list */
+ current_item = head = new_item;
+ } else {
+ /* add to the end and advance */
+ current_item->next = new_item;
+ new_item->prev = current_item;
+ current_item = new_item;
+ }
+
+ /* parse the name of the child */
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (!parse_string(current_item, input_buffer)) {
+ goto fail; /* failed to parse name */
+ }
+ buffer_skip_whitespace(input_buffer);
+
+ /* swap valuestring and string, because we parsed the name */
+ current_item->string = current_item->valuestring;
+ current_item->valuestring = NULL;
+
+ if (cannot_access_at_index(input_buffer, 0)
+ || (buffer_at_offset(input_buffer)[0] != ':')) {
+ goto fail; /* invalid object */
+ }
+
+ /* parse the value */
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (!parse_value(current_item, input_buffer)) {
+ goto fail; /* failed to parse value */
+ }
+ buffer_skip_whitespace(input_buffer);
+ } while (can_access_at_index(input_buffer, 0)
+ && (buffer_at_offset(input_buffer)[0] == ','));
+
+ if (cannot_access_at_index(input_buffer, 0)
+ || (buffer_at_offset(input_buffer)[0] != '}')) {
+ goto fail; /* expected end of object */
+ }
+
+success:
+ input_buffer->depth--;
+
+ if (head != NULL) {
+ head->prev = current_item;
+ }
+
+ item->type = cJSON_Object;
+ item->child = head;
+
+ input_buffer->offset++;
+ return true;
+
+fail:
+ if (head != NULL) {
+ cJSON_Delete(head);
+ }
+
+ return false;
}
/* Render an object to text. */
-static auto print_object(cJSON* item, int depth, int fmt) -> char* {
- char *out = nullptr, *ptr, *ret, *str;
- size_t len = 7;
- int i = 0;
- int j;
- cJSON* child = item->child;
- int numentries = 0, fail = 0;
- /* Count the number of entries. */
- while (child) {
- numentries++;
- child = child->next;
- }
- // Explicitly handle empty object case.
- if (!numentries) {
- out = (char*)cJSON_malloc(static_cast(fmt ? depth + 4 : 3));
- if (!out) {
- return nullptr;
- }
- ptr = out;
- *ptr++ = '{';
- if (fmt) {
- *ptr++ = '\n';
- for (i = 0; i < depth - 1; i++) *ptr++ = '\t';
- }
- *ptr++ = '}';
- *ptr = 0;
- return out;
+static cJSON_bool print_object(const cJSON* const item,
+ printbuffer* const output_buffer) {
+ unsigned char* output_pointer = NULL;
+ size_t length = 0;
+ cJSON* current_item = item->child;
+
+ if (output_buffer == NULL) {
+ return false;
}
- // Allocate space for the names and the objects.
- char** entries = (char**)cJSON_malloc(numentries * sizeof(char*));
- if (!entries) return nullptr;
- char** names = (char**)cJSON_malloc(numentries * sizeof(char*));
- if (!names) {
- cJSON_free(entries);
- return nullptr;
+ /* Compose the output: */
+ length = (size_t)(output_buffer->format ? 2 : 1); /* fmt: {\n */
+ output_pointer = ensure(output_buffer, length + 1);
+ if (output_pointer == NULL) {
+ return false;
}
- memset(entries, 0, sizeof(char*) * numentries);
- memset(names, 0, sizeof(char*) * numentries);
- // Collect all the results into our arrays.
- child = item->child;
- depth++;
- if (fmt) len += depth;
- while (child) {
- names[i] = str = print_string_ptr(child->string);
- entries[i++] = ret = print_value(child, depth, fmt);
- if (str && ret)
- len += strlen(ret) + strlen(str) + 2 + (fmt ? 2 + depth : 0);
- else
- fail = 1;
+ *output_pointer++ = '{';
+ output_buffer->depth++;
+ if (output_buffer->format) {
+ *output_pointer++ = '\n';
+ }
+ output_buffer->offset += length;
+
+ while (current_item) {
+ if (output_buffer->format) {
+ size_t i;
+ output_pointer = ensure(output_buffer, output_buffer->depth);
+ if (output_pointer == NULL) {
+ return false;
+ }
+ for (i = 0; i < output_buffer->depth; i++) {
+ *output_pointer++ = '\t';
+ }
+ output_buffer->offset += output_buffer->depth;
+ }
+
+ /* print key */
+ if (!print_string_ptr((unsigned char*)current_item->string,
+ output_buffer)) {
+ return false;
+ }
+ update_offset(output_buffer);
+
+ length = (size_t)(output_buffer->format ? 2 : 1);
+ output_pointer = ensure(output_buffer, length);
+ if (output_pointer == NULL) {
+ return false;
+ }
+ *output_pointer++ = ':';
+ if (output_buffer->format) {
+ *output_pointer++ = '\t';
+ }
+ output_buffer->offset += length;
+
+ /* print value */
+ if (!print_value(current_item, output_buffer)) {
+ return false;
+ }
+ update_offset(output_buffer);
+
+ /* print comma if not last */
+ length = ((size_t)(output_buffer->format ? 1 : 0)
+ + (size_t)(current_item->next ? 1 : 0));
+ output_pointer = ensure(output_buffer, length + 1);
+ if (output_pointer == NULL) {
+ return false;
+ }
+ if (current_item->next) {
+ *output_pointer++ = ',';
+ }
+
+ if (output_buffer->format) {
+ *output_pointer++ = '\n';
+ }
+ *output_pointer = '\0';
+ output_buffer->offset += length;
+
+ current_item = current_item->next;
+ }
+
+ output_pointer = ensure(
+ output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2);
+ if (output_pointer == NULL) {
+ return false;
+ }
+ if (output_buffer->format) {
+ size_t i;
+ for (i = 0; i < (output_buffer->depth - 1); i++) {
+ *output_pointer++ = '\t';
+ }
+ }
+ *output_pointer++ = '}';
+ *output_pointer = '\0';
+ output_buffer->depth--;
+
+ return true;
+}
+
+/* Get Array size/item / object item. */
+int cJSON_GetArraySize(const cJSON* array) {
+ cJSON* child = NULL;
+ size_t size = 0;
+
+ if (array == NULL) {
+ return 0;
+ }
+
+ child = array->child;
+
+ while (child != NULL) {
+ size++;
child = child->next;
}
- // Try to allocate the output string.
- if (!fail) out = (char*)cJSON_malloc(len);
- if (!out) fail = 1;
+ /* FIXME: Can overflow here. Cannot be fixed without breaking the API */
- // Handle failure.
- if (fail) {
- for (i = 0; i < numentries; i++) {
- if (names[i]) cJSON_free(names[i]);
- if (entries[i]) cJSON_free(entries[i]);
+ return (int)size;
+}
+
+static cJSON* get_array_item(const cJSON* array, size_t index) {
+ cJSON* current_child = NULL;
+
+ if (array == NULL) {
+ return NULL;
+ }
+
+ current_child = array->child;
+ while ((current_child != NULL) && (index > 0)) {
+ index--;
+ current_child = current_child->next;
+ }
+
+ return current_child;
+}
+
+cJSON* cJSON_GetArrayItem(const cJSON* array, int index) {
+ if (index < 0) {
+ return NULL;
+ }
+
+ return get_array_item(array, (size_t)index);
+}
+
+static cJSON* get_object_item(const cJSON* const object, const char* const name,
+ const cJSON_bool case_sensitive) {
+ cJSON* current_element = NULL;
+
+ if ((object == NULL) || (name == NULL)) {
+ return NULL;
+ }
+
+ current_element = object->child;
+ if (case_sensitive) {
+ while ((current_element != NULL) && (current_element->string != NULL)
+ && (strcmp(name, current_element->string) != 0)) {
+ current_element = current_element->next;
+ }
+ } else {
+ while ((current_element != NULL)
+ && (case_insensitive_strcmp(
+ (const unsigned char*)name,
+ (const unsigned char*)(current_element->string))
+ != 0)) {
+ current_element = current_element->next;
}
- cJSON_free(names);
- cJSON_free(entries);
- return nullptr;
}
- // Compose the output.
- *out = '{';
- ptr = out + 1;
- if (fmt) *ptr++ = '\n';
- *ptr = 0;
- for (i = 0; i < numentries; i++) {
- if (fmt)
- for (j = 0; j < depth; j++) *ptr++ = '\t';
- strcpy(ptr, names[i]); // NOLINT
- ptr += strlen(names[i]);
- *ptr++ = ':';
- if (fmt) *ptr++ = '\t';
- strcpy(ptr, entries[i]); // NOLINT
- ptr += strlen(entries[i]);
- if (i != numentries - 1) *ptr++ = ',';
- if (fmt) *ptr++ = '\n';
- *ptr = 0;
- cJSON_free(names[i]);
- cJSON_free(entries[i]);
+ if ((current_element == NULL) || (current_element->string == NULL)) {
+ return NULL;
}
- cJSON_free(names);
- cJSON_free(entries);
- if (fmt)
- for (i = 0; i < depth - 1; i++) *ptr++ = '\t';
- *ptr++ = '}';
- *ptr = 0;
- return out;
+ return current_element;
}
-// Get Array size/item / object item.
-auto cJSON_GetArraySize(cJSON* array) -> int {
- cJSON* c = array->child;
- int i = 0;
- while (c) {
- i++;
- c = c->next;
- }
- return i;
-}
-auto cJSON_GetArrayItem(cJSON* array, int item) -> cJSON* {
- cJSON* c = array->child;
- while (c && item > 0) {
- item--;
- c = c->next;
- }
- return c;
-}
-auto cJSON_GetObjectItem(cJSON* object, const char* string) -> cJSON* {
- cJSON* c = object->child;
- while (c && cJSON_strcasecmp(c->string, string)) c = c->next;
- return c;
+cJSON* cJSON_GetObjectItem(const cJSON* const object,
+ const char* const string) {
+ return get_object_item(object, string, false);
}
-// Utility for array list handling.
+cJSON* cJSON_GetObjectItemCaseSensitive(const cJSON* const object,
+ const char* const string) {
+ return get_object_item(object, string, true);
+}
+
+cJSON_bool cJSON_HasObjectItem(const cJSON* object, const char* string) {
+ return cJSON_GetObjectItem(object, string) ? 1 : 0;
+}
+
+/* Utility for array list handling. */
static void suffix_object(cJSON* prev, cJSON* item) {
prev->next = item;
item->prev = prev;
}
-// Utility for handling references.
-static auto create_reference(cJSON* item) -> cJSON* {
- cJSON* ref = cJSON_New_Item();
- if (!ref) return nullptr;
- memcpy(ref, item, sizeof(cJSON));
- ref->string = nullptr;
- ref->type |= cJSON_IsReference;
- ref->next = ref->prev = nullptr;
- return ref;
+
+/* Utility for handling references. */
+static cJSON* create_reference(const cJSON* item,
+ const internal_hooks* const hooks) {
+ cJSON* reference = NULL;
+ if (item == NULL) {
+ return NULL;
+ }
+
+ reference = cJSON_New_Item(hooks);
+ if (reference == NULL) {
+ return NULL;
+ }
+
+ memcpy(reference, item, sizeof(cJSON));
+ reference->string = NULL;
+ reference->type |= cJSON_IsReference;
+ reference->next = reference->prev = NULL;
+ return reference;
}
-// Add item to array/object.
-void cJSON_AddItemToArray(cJSON* array, cJSON* item) {
- cJSON* c = array->child;
- if (!item) return;
- if (!c) {
+static cJSON_bool add_item_to_array(cJSON* array, cJSON* item) {
+ cJSON* child = NULL;
+
+ if ((item == NULL) || (array == NULL) || (array == item)) {
+ return false;
+ }
+
+ child = array->child;
+ /*
+ * To find the last item in array quickly, we use prev in array
+ */
+ if (child == NULL) {
+ /* list is empty, start new one */
array->child = item;
+ item->prev = item;
+ item->next = NULL;
} else {
- while (c && c->next) c = c->next;
- suffix_object(c, item);
+ /* append to the end */
+ if (child->prev) {
+ suffix_object(child->prev, item);
+ array->child->prev = item;
+ }
}
-}
-void cJSON_AddItemToObject(cJSON* object, const char* string, cJSON* item) {
- if (!item) return;
- if (item->string) cJSON_free(item->string);
- item->string = cJSON_strdup(string);
- cJSON_AddItemToArray(object, item);
-}
-void cJSON_AddItemReferenceToArray(cJSON* array, cJSON* item) {
- cJSON_AddItemToArray(array, create_reference(item));
-}
-void cJSON_AddItemReferenceToObject(cJSON* object, const char* string,
- cJSON* item) {
- cJSON_AddItemToObject(object, string, create_reference(item));
+
+ return true;
}
-auto cJSON_DetachItemFromArray(cJSON* array, int which) -> cJSON* {
- cJSON* c = array->child;
- while (c && which > 0) {
- c = c->next;
- which--;
- }
- if (!c) return nullptr;
- if (c->prev) c->prev->next = c->next;
- if (c->next) c->next->prev = c->prev;
- if (c == array->child) array->child = c->next;
- c->prev = c->next = nullptr;
- return c;
+/* Add item to array/object. */
+cJSON_bool cJSON_AddItemToArray(cJSON* array, cJSON* item) {
+ return add_item_to_array(array, item);
}
+
+#if defined(__clang__) \
+ || (defined(__GNUC__) \
+ && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
+#pragma GCC diagnostic push
+#endif
+#ifdef __GNUC__
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+/* helper function to cast away const */
+static void* cast_away_const(const void* string) { return (void*)string; }
+#if defined(__clang__) \
+ || (defined(__GNUC__) \
+ && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
+#pragma GCC diagnostic pop
+#endif
+
+static cJSON_bool add_item_to_object(cJSON* const object,
+ const char* const string,
+ cJSON* const item,
+ const internal_hooks* const hooks,
+ const cJSON_bool constant_key) {
+ char* new_key = NULL;
+ int new_type = cJSON_Invalid;
+
+ if ((object == NULL) || (string == NULL) || (item == NULL)
+ || (object == item)) {
+ return false;
+ }
+
+ if (constant_key) {
+ new_key = (char*)cast_away_const(string);
+ new_type = item->type | cJSON_StringIsConst;
+ } else {
+ new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);
+ if (new_key == NULL) {
+ return false;
+ }
+
+ new_type = item->type & ~cJSON_StringIsConst;
+ }
+
+ if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) {
+ hooks->deallocate(item->string);
+ }
+
+ item->string = new_key;
+ item->type = new_type;
+
+ return add_item_to_array(object, item);
+}
+
+cJSON_bool cJSON_AddItemToObject(cJSON* object, const char* string,
+ cJSON* item) {
+ return add_item_to_object(object, string, item, &global_hooks, false);
+}
+
+/* Add an item to an object with constant string as key */
+cJSON_bool cJSON_AddItemToObjectCS(cJSON* object, const char* string,
+ cJSON* item) {
+ return add_item_to_object(object, string, item, &global_hooks, true);
+}
+
+cJSON_bool cJSON_AddItemReferenceToArray(cJSON* array, cJSON* item) {
+ if (array == NULL) {
+ return false;
+ }
+
+ return add_item_to_array(array, create_reference(item, &global_hooks));
+}
+
+cJSON_bool cJSON_AddItemReferenceToObject(cJSON* object, const char* string,
+ cJSON* item) {
+ if ((object == NULL) || (string == NULL)) {
+ return false;
+ }
+
+ return add_item_to_object(object, string,
+ create_reference(item, &global_hooks),
+ &global_hooks, false);
+}
+
+cJSON* cJSON_AddNullToObject(cJSON* const object, const char* const name) {
+ cJSON* null = cJSON_CreateNull();
+ if (add_item_to_object(object, name, null, &global_hooks, false)) {
+ return null;
+ }
+
+ cJSON_Delete(null);
+ return NULL;
+}
+
+cJSON* cJSON_AddTrueToObject(cJSON* const object, const char* const name) {
+ cJSON* true_item = cJSON_CreateTrue();
+ if (add_item_to_object(object, name, true_item, &global_hooks, false)) {
+ return true_item;
+ }
+
+ cJSON_Delete(true_item);
+ return NULL;
+}
+
+cJSON* cJSON_AddFalseToObject(cJSON* const object, const char* const name) {
+ cJSON* false_item = cJSON_CreateFalse();
+ if (add_item_to_object(object, name, false_item, &global_hooks, false)) {
+ return false_item;
+ }
+
+ cJSON_Delete(false_item);
+ return NULL;
+}
+
+cJSON* cJSON_AddBoolToObject(cJSON* const object, const char* const name,
+ const cJSON_bool boolean) {
+ cJSON* bool_item = cJSON_CreateBool(boolean);
+ if (add_item_to_object(object, name, bool_item, &global_hooks, false)) {
+ return bool_item;
+ }
+
+ cJSON_Delete(bool_item);
+ return NULL;
+}
+
+cJSON* cJSON_AddNumberToObject(cJSON* const object, const char* const name,
+ const double number) {
+ cJSON* number_item = cJSON_CreateNumber(number);
+ if (add_item_to_object(object, name, number_item, &global_hooks, false)) {
+ return number_item;
+ }
+
+ cJSON_Delete(number_item);
+ return NULL;
+}
+
+cJSON* cJSON_AddStringToObject(cJSON* const object, const char* const name,
+ const char* const string) {
+ cJSON* string_item = cJSON_CreateString(string);
+ if (add_item_to_object(object, name, string_item, &global_hooks, false)) {
+ return string_item;
+ }
+
+ cJSON_Delete(string_item);
+ return NULL;
+}
+
+cJSON* cJSON_AddRawToObject(cJSON* const object, const char* const name,
+ const char* const raw) {
+ cJSON* raw_item = cJSON_CreateRaw(raw);
+ if (add_item_to_object(object, name, raw_item, &global_hooks, false)) {
+ return raw_item;
+ }
+
+ cJSON_Delete(raw_item);
+ return NULL;
+}
+
+cJSON* cJSON_AddObjectToObject(cJSON* const object, const char* const name) {
+ cJSON* object_item = cJSON_CreateObject();
+ if (add_item_to_object(object, name, object_item, &global_hooks, false)) {
+ return object_item;
+ }
+
+ cJSON_Delete(object_item);
+ return NULL;
+}
+
+cJSON* cJSON_AddArrayToObject(cJSON* const object, const char* const name) {
+ cJSON* array = cJSON_CreateArray();
+ if (add_item_to_object(object, name, array, &global_hooks, false)) {
+ return array;
+ }
+
+ cJSON_Delete(array);
+ return NULL;
+}
+
+cJSON* cJSON_DetachItemViaPointer(cJSON* parent, cJSON* const item) {
+ if ((parent == NULL) || (item == NULL)) {
+ return NULL;
+ }
+
+ if (item != parent->child) {
+ /* not the first element */
+ item->prev->next = item->next;
+ }
+ if (item->next != NULL) {
+ /* not the last element */
+ item->next->prev = item->prev;
+ }
+
+ if (item == parent->child) {
+ /* first element */
+ parent->child = item->next;
+ } else if (item->next == NULL) {
+ /* last element */
+ parent->child->prev = item->prev;
+ }
+
+ /* make sure the detached item doesn't point anywhere anymore */
+ item->prev = NULL;
+ item->next = NULL;
+
+ return item;
+}
+
+cJSON* cJSON_DetachItemFromArray(cJSON* array, int which) {
+ if (which < 0) {
+ return NULL;
+ }
+
+ return cJSON_DetachItemViaPointer(array,
+ get_array_item(array, (size_t)which));
+}
+
void cJSON_DeleteItemFromArray(cJSON* array, int which) {
cJSON_Delete(cJSON_DetachItemFromArray(array, which));
}
-#pragma clang diagnostic push
-#pragma ide diagnostic ignored "ConstantFunctionResult"
-auto cJSON_DetachItemFromObject(cJSON* object, const char* string) -> cJSON* {
- int i = 0;
- cJSON* c = object->child;
- while (c && cJSON_strcasecmp(c->string, string)) {
- i++;
- c = c->next;
- }
- if (c) return cJSON_DetachItemFromArray(object, i);
- return nullptr;
+cJSON* cJSON_DetachItemFromObject(cJSON* object, const char* string) {
+ cJSON* to_detach = cJSON_GetObjectItem(object, string);
+
+ return cJSON_DetachItemViaPointer(object, to_detach);
}
-#pragma clang diagnostic pop
+cJSON* cJSON_DetachItemFromObjectCaseSensitive(cJSON* object,
+ const char* string) {
+ cJSON* to_detach = cJSON_GetObjectItemCaseSensitive(object, string);
+
+ return cJSON_DetachItemViaPointer(object, to_detach);
+}
void cJSON_DeleteItemFromObject(cJSON* object, const char* string) {
cJSON_Delete(cJSON_DetachItemFromObject(object, string));
}
-// Replace array/object items with new ones.
-void cJSON_ReplaceItemInArray(cJSON* array, int which, cJSON* newitem) {
- cJSON* c = array->child;
- while (c && which > 0) {
- c = c->next;
- which--;
- }
- if (!c) return;
- newitem->next = c->next;
- newitem->prev = c->prev;
- if (newitem->next) newitem->next->prev = newitem;
- if (c == array->child)
- array->child = newitem;
- else
- newitem->prev->next = newitem;
- c->next = c->prev = nullptr;
- cJSON_Delete(c);
-}
-void cJSON_ReplaceItemInObject(cJSON* object, const char* string,
- cJSON* newitem) {
- int i = 0;
- cJSON* c = object->child;
- while (c && cJSON_strcasecmp(c->string, string)) {
- i++;
- c = c->next;
- }
- if (c) {
- newitem->string = cJSON_strdup(string);
- cJSON_ReplaceItemInArray(object, i, newitem);
- }
+void cJSON_DeleteItemFromObjectCaseSensitive(cJSON* object,
+ const char* string) {
+ cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string));
}
-// Create basic types.
-auto cJSON_CreateNull() -> cJSON* {
- cJSON* item = cJSON_New_Item();
- if (item) item->type = cJSON_NULL;
+/* Replace array/object items with new ones. */
+cJSON_bool cJSON_InsertItemInArray(cJSON* array, int which, cJSON* newitem) {
+ cJSON* after_inserted = NULL;
+
+ if (which < 0) {
+ return false;
+ }
+
+ after_inserted = get_array_item(array, (size_t)which);
+ if (after_inserted == NULL) {
+ return add_item_to_array(array, newitem);
+ }
+
+ newitem->next = after_inserted;
+ newitem->prev = after_inserted->prev;
+ after_inserted->prev = newitem;
+ if (after_inserted == array->child) {
+ array->child = newitem;
+ } else {
+ newitem->prev->next = newitem;
+ }
+ return true;
+}
+
+cJSON_bool cJSON_ReplaceItemViaPointer(cJSON* const parent, cJSON* const item,
+ cJSON* replacement) {
+ if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL)
+ || (item == NULL)) {
+ return false;
+ }
+
+ if (replacement == item) {
+ return true;
+ }
+
+ replacement->next = item->next;
+ replacement->prev = item->prev;
+
+ if (replacement->next != NULL) {
+ replacement->next->prev = replacement;
+ }
+ if (parent->child == item) {
+ if (parent->child->prev == parent->child) {
+ replacement->prev = replacement;
+ }
+ parent->child = replacement;
+ } else { /*
+ * To find the last item in array quickly, we use prev in array.
+ * We can't modify the last item's next pointer where this item was
+ * the parent's child
+ */
+ if (replacement->prev != NULL) {
+ replacement->prev->next = replacement;
+ }
+ if (replacement->next == NULL) {
+ parent->child->prev = replacement;
+ }
+ }
+
+ item->next = NULL;
+ item->prev = NULL;
+ cJSON_Delete(item);
+
+ return true;
+}
+
+cJSON_bool cJSON_ReplaceItemInArray(cJSON* array, int which, cJSON* newitem) {
+ if (which < 0) {
+ return false;
+ }
+
+ return cJSON_ReplaceItemViaPointer(
+ array, get_array_item(array, (size_t)which), newitem);
+}
+
+static cJSON_bool replace_item_in_object(cJSON* object, const char* string,
+ cJSON* replacement,
+ cJSON_bool case_sensitive) {
+ if ((replacement == NULL) || (string == NULL)) {
+ return false;
+ }
+
+ /* replace the name in the replacement */
+ if (!(replacement->type & cJSON_StringIsConst)
+ && (replacement->string != NULL)) {
+ cJSON_free(replacement->string);
+ }
+ replacement->string =
+ (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
+ if (replacement->string == NULL) {
+ return false;
+ }
+
+ replacement->type &= ~cJSON_StringIsConst;
+
+ return cJSON_ReplaceItemViaPointer(
+ object, get_object_item(object, string, case_sensitive), replacement);
+}
+
+cJSON_bool cJSON_ReplaceItemInObject(cJSON* object, const char* string,
+ cJSON* newitem) {
+ return replace_item_in_object(object, string, newitem, false);
+}
+
+cJSON_bool cJSON_ReplaceItemInObjectCaseSensitive(cJSON* object,
+ const char* string,
+ cJSON* newitem) {
+ return replace_item_in_object(object, string, newitem, true);
+}
+
+/* Create basic types: */
+cJSON* cJSON_CreateNull(void) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item) {
+ item->type = cJSON_NULL;
+ }
+
return item;
}
-auto cJSON_CreateTrue() -> cJSON* {
- cJSON* item = cJSON_New_Item();
- if (item) item->type = cJSON_True;
+
+cJSON* cJSON_CreateTrue(void) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item) {
+ item->type = cJSON_True;
+ }
+
return item;
}
-auto cJSON_CreateFalse() -> cJSON* {
- cJSON* item = cJSON_New_Item();
- if (item) item->type = cJSON_False;
+
+cJSON* cJSON_CreateFalse(void) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item) {
+ item->type = cJSON_False;
+ }
+
return item;
}
-auto cJSON_CreateBool(int b) -> cJSON* {
- cJSON* item = cJSON_New_Item();
- if (item) item->type = b ? cJSON_True : cJSON_False;
+
+cJSON* cJSON_CreateBool(cJSON_bool boolean) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item) {
+ item->type = boolean ? cJSON_True : cJSON_False;
+ }
+
return item;
}
-auto cJSON_CreateNumber(double num) -> cJSON* {
- cJSON* item = cJSON_New_Item();
+
+cJSON* cJSON_CreateNumber(double num) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
if (item) {
item->type = cJSON_Number;
item->valuedouble = num;
- item->valueint = (int)num;
+
+ /* use saturation in case of overflow */
+ if (num >= INT_MAX) {
+ item->valueint = INT_MAX;
+ } else if (num <= (double)INT_MIN) {
+ item->valueint = INT_MIN;
+ } else {
+ item->valueint = (int)num;
+ }
}
+
return item;
}
-auto cJSON_CreateString(const char* string) -> cJSON* {
- cJSON* item = cJSON_New_Item();
+
+cJSON* cJSON_CreateString(const char* string) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
if (item) {
item->type = cJSON_String;
- item->valuestring = cJSON_strdup(string);
+ item->valuestring =
+ (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
+ if (!item->valuestring) {
+ cJSON_Delete(item);
+ return NULL;
+ }
}
- return item;
-}
-auto cJSON_CreateArray() -> cJSON* {
- cJSON* item = cJSON_New_Item();
- if (item) item->type = cJSON_Array;
- return item;
-}
-auto cJSON_CreateObject() -> cJSON* {
- cJSON* item = cJSON_New_Item();
- if (item) item->type = cJSON_Object;
+
return item;
}
-// Create Arrays.
-auto cJSON_CreateIntArray(const int* numbers, int count) -> cJSON* {
- int i;
- cJSON *n, *p = nullptr, *a = cJSON_CreateArray();
- for (i = 0; a && i < count; i++) {
+cJSON* cJSON_CreateStringReference(const char* string) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item != NULL) {
+ item->type = cJSON_String | cJSON_IsReference;
+ item->valuestring = (char*)cast_away_const(string);
+ }
+
+ return item;
+}
+
+cJSON* cJSON_CreateObjectReference(const cJSON* child) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item != NULL) {
+ item->type = cJSON_Object | cJSON_IsReference;
+ item->child = (cJSON*)cast_away_const(child);
+ }
+
+ return item;
+}
+
+cJSON* cJSON_CreateArrayReference(const cJSON* child) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item != NULL) {
+ item->type = cJSON_Array | cJSON_IsReference;
+ item->child = (cJSON*)cast_away_const(child);
+ }
+
+ return item;
+}
+
+cJSON* cJSON_CreateRaw(const char* raw) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item) {
+ item->type = cJSON_Raw;
+ item->valuestring =
+ (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks);
+ if (!item->valuestring) {
+ cJSON_Delete(item);
+ return NULL;
+ }
+ }
+
+ return item;
+}
+
+cJSON* cJSON_CreateArray(void) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item) {
+ item->type = cJSON_Array;
+ }
+
+ return item;
+}
+
+cJSON* cJSON_CreateObject(void) {
+ cJSON* item = cJSON_New_Item(&global_hooks);
+ if (item) {
+ item->type = cJSON_Object;
+ }
+
+ return item;
+}
+
+/* Create Arrays: */
+cJSON* cJSON_CreateIntArray(const int* numbers, int count) {
+ size_t i = 0;
+ cJSON* n = NULL;
+ cJSON* p = NULL;
+ cJSON* a = NULL;
+
+ if ((count < 0) || (numbers == NULL)) {
+ return NULL;
+ }
+
+ a = cJSON_CreateArray();
+
+ for (i = 0; a && (i < (size_t)count); i++) {
n = cJSON_CreateNumber(numbers[i]);
- if (!i)
+ if (!n) {
+ cJSON_Delete(a);
+ return NULL;
+ }
+ if (!i) {
a->child = n;
- else
+ } else {
suffix_object(p, n);
+ }
p = n;
}
+
+ if (a && a->child) {
+ a->child->prev = n;
+ }
+
return a;
}
-auto cJSON_CreateFloatArray(const float* numbers, int count) -> cJSON* {
- int i;
- cJSON *n, *p = nullptr, *a = cJSON_CreateArray();
- for (i = 0; a && i < count; i++) {
- n = cJSON_CreateNumber(numbers[i]);
- if (!i)
+
+cJSON* cJSON_CreateFloatArray(const float* numbers, int count) {
+ size_t i = 0;
+ cJSON* n = NULL;
+ cJSON* p = NULL;
+ cJSON* a = NULL;
+
+ if ((count < 0) || (numbers == NULL)) {
+ return NULL;
+ }
+
+ a = cJSON_CreateArray();
+
+ for (i = 0; a && (i < (size_t)count); i++) {
+ n = cJSON_CreateNumber((double)numbers[i]);
+ if (!n) {
+ cJSON_Delete(a);
+ return NULL;
+ }
+ if (!i) {
a->child = n;
- else
+ } else {
suffix_object(p, n);
+ }
p = n;
}
+
+ if (a && a->child) {
+ a->child->prev = n;
+ }
+
return a;
}
-auto cJSON_CreateDoubleArray(const double* numbers, int count) -> cJSON* {
- int i;
- cJSON *n, *p = nullptr, *a = cJSON_CreateArray();
- for (i = 0; a && i < count; i++) {
+
+cJSON* cJSON_CreateDoubleArray(const double* numbers, int count) {
+ size_t i = 0;
+ cJSON* n = NULL;
+ cJSON* p = NULL;
+ cJSON* a = NULL;
+
+ if ((count < 0) || (numbers == NULL)) {
+ return NULL;
+ }
+
+ a = cJSON_CreateArray();
+
+ for (i = 0; a && (i < (size_t)count); i++) {
n = cJSON_CreateNumber(numbers[i]);
- if (!i)
+ if (!n) {
+ cJSON_Delete(a);
+ return NULL;
+ }
+ if (!i) {
a->child = n;
- else
+ } else {
suffix_object(p, n);
+ }
p = n;
}
+
+ if (a && a->child) {
+ a->child->prev = n;
+ }
+
return a;
}
-auto cJSON_CreateStringArray(const char** strings, int count) -> cJSON* {
- int i;
- cJSON *n, *p = nullptr, *a = cJSON_CreateArray();
- for (i = 0; a && i < count; i++) {
+
+cJSON* cJSON_CreateStringArray(const char* const* strings, int count) {
+ size_t i = 0;
+ cJSON* n = NULL;
+ cJSON* p = NULL;
+ cJSON* a = NULL;
+
+ if ((count < 0) || (strings == NULL)) {
+ return NULL;
+ }
+
+ a = cJSON_CreateArray();
+
+ for (i = 0; a && (i < (size_t)count); i++) {
n = cJSON_CreateString(strings[i]);
- if (!i)
+ if (!n) {
+ cJSON_Delete(a);
+ return NULL;
+ }
+ if (!i) {
a->child = n;
- else
+ } else {
suffix_object(p, n);
+ }
p = n;
}
+
+ if (a && a->child) {
+ a->child->prev = n;
+ }
+
return a;
}
-// Duplication.
-auto cJSON_Duplicate(cJSON* item, int recurse) -> cJSON* {
- cJSON *newitem, *cptr, *nptr = nullptr, *newchild;
+/* Duplication */
+cJSON* cJSON_Duplicate(const cJSON* item, cJSON_bool recurse) {
+ cJSON* newitem = NULL;
+ cJSON* child = NULL;
+ cJSON* next = NULL;
+ cJSON* newchild = NULL;
+
/* Bail on bad ptr */
- if (!item) return nullptr;
+ if (!item) {
+ goto fail;
+ }
/* Create new item */
- newitem = cJSON_New_Item();
- if (!newitem) return nullptr;
+ newitem = cJSON_New_Item(&global_hooks);
+ if (!newitem) {
+ goto fail;
+ }
/* Copy over all vars */
newitem->type = item->type & (~cJSON_IsReference);
newitem->valueint = item->valueint;
newitem->valuedouble = item->valuedouble;
if (item->valuestring) {
- newitem->valuestring = cJSON_strdup(item->valuestring);
+ newitem->valuestring =
+ (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks);
if (!newitem->valuestring) {
- cJSON_Delete(newitem);
- return nullptr;
+ goto fail;
}
}
if (item->string) {
- newitem->string = cJSON_strdup(item->string);
+ newitem->string =
+ (item->type & cJSON_StringIsConst)
+ ? item->string
+ : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks);
if (!newitem->string) {
- cJSON_Delete(newitem);
- return nullptr;
+ goto fail;
}
}
/* If non-recursive, then we're done! */
- if (!recurse) return newitem;
- /* Walk the ->next chain for the child. */
- cptr = item->child;
- while (cptr) {
- newchild = cJSON_Duplicate(
- cptr, 1); /* Duplicate (with recurse) each item in the ->next chain */
- if (!newchild) {
- cJSON_Delete(newitem);
- return nullptr;
- }
- if (nptr) {
- nptr->next = newchild;
- newchild->prev = nptr;
- nptr = newchild;
- } /* If newitem->child already set, then crosswire ->prev and ->next and
- move on */
- else {
- newitem->child = newchild;
- nptr = newchild;
- } /* Set newitem->child and move to it */
- cptr = cptr->next;
+ if (!recurse) {
+ return newitem;
}
+ /* Walk the ->next chain for the child. */
+ child = item->child;
+ while (child != NULL) {
+ newchild = cJSON_Duplicate(
+ child,
+ true); /* Duplicate (with recurse) each item in the ->next chain */
+ if (!newchild) {
+ goto fail;
+ }
+ if (next != NULL) {
+ /* If newitem->child already set, then crosswire ->prev and ->next and
+ * move on */
+ next->next = newchild;
+ newchild->prev = next;
+ next = newchild;
+ } else {
+ /* Set newitem->child and move to it */
+ newitem->child = newchild;
+ next = newchild;
+ }
+ child = child->next;
+ }
+ if (newitem && newitem->child) {
+ newitem->child->prev = newchild;
+ }
+
return newitem;
+
+fail:
+ if (newitem != NULL) {
+ cJSON_Delete(newitem);
+ }
+
+ return NULL;
+}
+
+static void skip_oneline_comment(char** input) {
+ *input += static_strlen("//");
+
+ for (; (*input)[0] != '\0'; ++(*input)) {
+ if ((*input)[0] == '\n') {
+ *input += static_strlen("\n");
+ return;
+ }
+ }
+}
+
+static void skip_multiline_comment(char** input) {
+ *input += static_strlen("/*");
+
+ for (; (*input)[0] != '\0'; ++(*input)) {
+ if (((*input)[0] == '*') && ((*input)[1] == '/')) {
+ *input += static_strlen("*/");
+ return;
+ }
+ }
+}
+
+static void minify_string(char** input, char** output) {
+ (*output)[0] = (*input)[0];
+ *input += static_strlen("\"");
+ *output += static_strlen("\"");
+
+ for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) {
+ (*output)[0] = (*input)[0];
+
+ if ((*input)[0] == '\"') {
+ (*output)[0] = '\"';
+ *input += static_strlen("\"");
+ *output += static_strlen("\"");
+ return;
+ } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) {
+ (*output)[1] = (*input)[1];
+ *input += static_strlen("\"");
+ *output += static_strlen("\"");
+ }
+ }
}
void cJSON_Minify(char* json) {
char* into = json;
- while (*json) {
- if (*json == ' ')
- json++; // NOLINT(bugprone-branch-clone)
- else if (*json == '\t')
- json++; // Whitespace characters.
- else if (*json == '\r')
- json++;
- else if (*json == '\n')
- json++;
- else if (*json == '/' && json[1] == '/')
- while (*json && *json != '\n')
- json++; // double-slash comments, to end of line.
- else if (*json == '/' && json[1] == '*') {
- while (*json && !(*json == '*' && json[1] == '/')) json++;
- json += 2;
- } // multiline comments.
- else if (*json == '\"') {
- *into++ = *json++;
- while (*json && *json != '\"') {
- if (*json == '\\') *into++ = *json++;
- *into++ = *json++;
- }
- *into++ = *json++;
- } // string literals, which are \" sensitive.
- else {
- *into++ = *json++; // All other characters.
+
+ if (json == NULL) {
+ return;
+ }
+
+ while (json[0] != '\0') {
+ switch (json[0]) {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ json++;
+ break;
+
+ case '/':
+ if (json[1] == '/') {
+ skip_oneline_comment(&json);
+ } else if (json[1] == '*') {
+ skip_multiline_comment(&json);
+ } else {
+ json++;
+ }
+ break;
+
+ case '\"':
+ minify_string(&json, (char**)&into);
+ break;
+
+ default:
+ into[0] = json[0];
+ json++;
+ into++;
}
}
- *into = 0; // and null-terminate.
+
+ /* and null-terminate. */
+ *into = '\0';
}
-#pragma clang diagnostic pop
+cJSON_bool cJSON_IsInvalid(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Invalid;
+}
+
+cJSON_bool cJSON_IsFalse(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_False;
+}
+
+cJSON_bool cJSON_IsTrue(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & 0xff) == cJSON_True;
+}
+
+cJSON_bool cJSON_IsBool(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & (cJSON_True | cJSON_False)) != 0;
+}
+cJSON_bool cJSON_IsNull(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_NULL;
+}
+
+cJSON_bool cJSON_IsNumber(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Number;
+}
+
+cJSON_bool cJSON_IsString(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_String;
+}
+
+cJSON_bool cJSON_IsArray(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Array;
+}
+
+cJSON_bool cJSON_IsObject(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Object;
+}
+
+cJSON_bool cJSON_IsRaw(const cJSON* const item) {
+ if (item == NULL) {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Raw;
+}
+
+cJSON_bool cJSON_Compare(const cJSON* const a, const cJSON* const b,
+ const cJSON_bool case_sensitive) {
+ if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) {
+ return false;
+ }
+
+ /* check if type is valid */
+ switch (a->type & 0xFF) {
+ case cJSON_False:
+ case cJSON_True:
+ case cJSON_NULL:
+ case cJSON_Number:
+ case cJSON_String:
+ case cJSON_Raw:
+ case cJSON_Array:
+ case cJSON_Object:
+ break;
+
+ default:
+ return false;
+ }
+
+ /* identical objects are equal */
+ if (a == b) {
+ return true;
+ }
+
+ switch (a->type & 0xFF) {
+ /* in these cases and equal type is enough */
+ case cJSON_False:
+ case cJSON_True:
+ case cJSON_NULL:
+ return true;
+
+ case cJSON_Number:
+ if (compare_double(a->valuedouble, b->valuedouble)) {
+ return true;
+ }
+ return false;
+
+ case cJSON_String:
+ case cJSON_Raw:
+ if ((a->valuestring == NULL) || (b->valuestring == NULL)) {
+ return false;
+ }
+ if (strcmp(a->valuestring, b->valuestring) == 0) {
+ return true;
+ }
+
+ return false;
+
+ case cJSON_Array: {
+ cJSON* a_element = a->child;
+ cJSON* b_element = b->child;
+
+ for (; (a_element != NULL) && (b_element != NULL);) {
+ if (!cJSON_Compare(a_element, b_element, case_sensitive)) {
+ return false;
+ }
+
+ a_element = a_element->next;
+ b_element = b_element->next;
+ }
+
+ /* one of the arrays is longer than the other */
+ if (a_element != b_element) {
+ return false;
+ }
+
+ return true;
+ }
+
+ case cJSON_Object: {
+ cJSON* a_element = NULL;
+ cJSON* b_element = NULL;
+ cJSON_ArrayForEach(a_element, a) {
+ /* TODO This has O(n^2) runtime, which is horrible! */
+ b_element = get_object_item(b, a_element->string, case_sensitive);
+ if (b_element == NULL) {
+ return false;
+ }
+
+ if (!cJSON_Compare(a_element, b_element, case_sensitive)) {
+ return false;
+ }
+ }
+
+ /* doing this twice, once on a and b to prevent true comparison if a
+ * subset of b
+ * TODO: Do this the proper way, this is just a fix for now */
+ cJSON_ArrayForEach(b_element, b) {
+ a_element = get_object_item(a, b_element->string, case_sensitive);
+ if (a_element == NULL) {
+ return false;
+ }
+
+ if (!cJSON_Compare(b_element, a_element, case_sensitive)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+void* cJSON_malloc(size_t size) { return global_hooks.allocate(size); }
+
+void cJSON_free(void* object) { global_hooks.deallocate(object); }
} // namespace ballistica
diff --git a/src/ballistica/shared/generic/json.h b/src/ballistica/shared/generic/json.h
index cde7ff73..63a68432 100644
--- a/src/ballistica/shared/generic/json.h
+++ b/src/ballistica/shared/generic/json.h
@@ -4,7 +4,7 @@
#define BALLISTICA_SHARED_GENERIC_JSON_H_
/*
- Copyright (c) 2009 Dave Gamble
+ Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -27,152 +27,282 @@
#include "ballistica/shared/ballistica.h"
+// ericf note: Changes here from vanilla cJSON:
+// - Placed under ballistica namespace.
+// - Removed extern "C" braces.
+// - Removed CJSON_CDECL, CJSON_PUBLIC, and other visibility controls.
+// - In general, just treating this like our other internal C++ source.
+// - Also added a few simple C++ wrappers (see bottom of this file)
+
namespace ballistica {
-#pragma clang diagnostic push
-#pragma ide diagnostic ignored "OCUnusedMacroInspection"
-
-// #ifdef __cplusplus
-// extern "C" {
-// #endif
+/* project version */
+#define CJSON_VERSION_MAJOR 1
+#define CJSON_VERSION_MINOR 7
+#define CJSON_VERSION_PATCH 16
/* cJSON Types: */
-#define cJSON_False 0u
-#define cJSON_True 1u
-#define cJSON_NULL 2u
-#define cJSON_Number 3u
-#define cJSON_String 4u
-#define cJSON_Array 5u
-#define cJSON_Object 6u
+#define cJSON_Invalid (0)
+#define cJSON_False (1 << 0)
+#define cJSON_True (1 << 1)
+#define cJSON_NULL (1 << 2)
+#define cJSON_Number (1 << 3)
+#define cJSON_String (1 << 4)
+#define cJSON_Array (1 << 5)
+#define cJSON_Object (1 << 6)
+#define cJSON_Raw (1 << 7) /* raw json */
-#define cJSON_IsReference 256u
+#define cJSON_IsReference 256
+#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON {
- struct cJSON *next,
- *prev; /* next/prev allow you to walk array/object chains. Alternatively,
- use GetArraySize/GetArrayItem/GetObjectItem */
- struct cJSON*
- child; /* An array or object item will have a child pointer pointing to a
- chain of the items in the array/object. */
+ /* next/prev allow you to walk array/object chains. Alternatively, use
+ * GetArraySize/GetArrayItem/GetObjectItem */
+ struct cJSON* next;
+ struct cJSON* prev;
+ /* An array or object item will have a child pointer pointing to a chain of
+ * the items in the array/object. */
+ struct cJSON* child;
- uint32_t type; /* The type of the item, as above. */
+ /* The type of the item, as above. */
+ int type;
- char* valuestring; /* The item's string, if type==cJSON_String */
- int valueint; /* The item's number, if type==cJSON_Number */
- double valuedouble; /* The item's number, if type==cJSON_Number */
+ /* The item's string, if type==cJSON_String and type == cJSON_Raw */
+ char* valuestring;
+ /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
+ int valueint;
+ /* The item's number, if type==cJSON_Number */
+ double valuedouble;
- char* string; /* The item's name string, if this item is the child of, or is
- in the list of subitems of an object. */
+ /* The item's name string, if this item is the child of, or is in the list of
+ * subitems of an object. */
+ char* string;
} cJSON;
typedef struct cJSON_Hooks {
+ /* malloc/free are CDECL on Windows regardless of the default calling
+ * convention of the compiler, so ensure the hooks allow passing those
+ * functions directly. */
void* (*malloc_fn)(size_t sz);
void (*free_fn)(void* ptr);
} cJSON_Hooks;
-/* Supply malloc, realloc and free functions to cJSON */
-extern void cJSON_InitHooks(cJSON_Hooks* hooks);
+typedef int cJSON_bool;
+/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse
+ * them. This is to prevent stack overflows. */
+#ifndef CJSON_NESTING_LIMIT
+#define CJSON_NESTING_LIMIT 1000
+#endif
+
+/* returns the version of cJSON as a string */
+const char* cJSON_Version(void);
+
+/* Supply malloc, realloc and free functions to cJSON */
+void cJSON_InitHooks(cJSON_Hooks* hooks);
+
+/* Memory Management: the caller is always responsible to free the results from
+ * all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib
+ * free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is
+ * cJSON_PrintPreallocated, where the caller has full responsibility of the
+ * buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate.
- * Call cJSON_Delete when finished. */
-extern auto cJSON_Parse(const char* value) -> cJSON*;
-/* Render a cJSON entity to text for transfer/storage. Free the char* when
- * finished. */
-extern auto cJSON_Print(cJSON* item) -> char*;
-/* Render a cJSON entity to text for transfer/storage without any formatting.
- * Free the char* when finished. */
-extern auto cJSON_PrintUnformatted(cJSON* item) -> char*;
+ */
+cJSON* cJSON_Parse(const char* value);
+cJSON* cJSON_ParseWithLength(const char* value, size_t buffer_length);
+/* ParseWithOpts allows you to require (and check) that the JSON is null
+ * terminated, and to retrieve the pointer to the final byte parsed. */
+/* If you supply a ptr in return_parse_end and parsing fails, then
+ * return_parse_end will contain a pointer to the error so will match
+ * cJSON_GetErrorPtr(). */
+cJSON* cJSON_ParseWithOpts(const char* value, const char** return_parse_end,
+ cJSON_bool require_null_terminated);
+cJSON* cJSON_ParseWithLengthOpts(const char* value, size_t buffer_length,
+ const char** return_parse_end,
+ cJSON_bool require_null_terminated);
+
+/* Render a cJSON entity to text for transfer/storage. */
+char* cJSON_Print(const cJSON* item);
+/* Render a cJSON entity to text for transfer/storage without any formatting. */
+char* cJSON_PrintUnformatted(const cJSON* item);
+/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess
+ * at the final size. guessing well reduces reallocation. fmt=0 gives
+ * unformatted, =1 gives formatted */
+char* cJSON_PrintBuffered(const cJSON* item, int prebuffer, cJSON_bool fmt);
+/* Render a cJSON entity to text using a buffer already allocated in memory with
+ * given length. Returns 1 on success and 0 on failure. */
+/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will
+ * use, so to be safe allocate 5 bytes more than you actually need */
+cJSON_bool cJSON_PrintPreallocated(cJSON* item, char* buffer, const int length,
+ const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
-extern void cJSON_Delete(cJSON* c);
+void cJSON_Delete(cJSON* item);
/* Returns the number of items in an array (or object). */
-extern auto cJSON_GetArraySize(cJSON* array) -> int;
-/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful.
- */
-extern auto cJSON_GetArrayItem(cJSON* array, int item) -> cJSON*;
+int cJSON_GetArraySize(const cJSON* array);
+/* Retrieve item number "index" from array "array". Returns NULL if
+ * unsuccessful. */
+cJSON* cJSON_GetArrayItem(const cJSON* array, int index);
/* Get item "string" from object. Case insensitive. */
-extern auto cJSON_GetObjectItem(cJSON* object, const char* string) -> cJSON*;
-
+cJSON* cJSON_GetObjectItem(const cJSON* const object, const char* const string);
+cJSON* cJSON_GetObjectItemCaseSensitive(const cJSON* const object,
+ const char* const string);
+cJSON_bool cJSON_HasObjectItem(const cJSON* object, const char* string);
/* For analysing failed parses. This returns a pointer to the parse error.
* You'll probably need to look a few chars back to make sense of it. Defined
* when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
-extern auto cJSON_GetErrorPtr() -> const char*;
+const char* cJSON_GetErrorPtr(void);
+
+/* Check item type and return its value */
+char* cJSON_GetStringValue(const cJSON* const item);
+double cJSON_GetNumberValue(const cJSON* const item);
+
+/* These functions check the type of an item */
+cJSON_bool cJSON_IsInvalid(const cJSON* const item);
+cJSON_bool cJSON_IsFalse(const cJSON* const item);
+cJSON_bool cJSON_IsTrue(const cJSON* const item);
+cJSON_bool cJSON_IsBool(const cJSON* const item);
+cJSON_bool cJSON_IsNull(const cJSON* const item);
+cJSON_bool cJSON_IsNumber(const cJSON* const item);
+cJSON_bool cJSON_IsString(const cJSON* const item);
+cJSON_bool cJSON_IsArray(const cJSON* const item);
+cJSON_bool cJSON_IsObject(const cJSON* const item);
+cJSON_bool cJSON_IsRaw(const cJSON* const item);
/* These calls create a cJSON item of the appropriate type. */
-extern auto cJSON_CreateNull() -> cJSON*;
-extern auto cJSON_CreateTrue() -> cJSON*;
-extern auto cJSON_CreateFalse() -> cJSON*;
-extern auto cJSON_CreateBool(int b) -> cJSON*;
-extern auto cJSON_CreateNumber(double num) -> cJSON*;
-extern auto cJSON_CreateString(const char* string) -> cJSON*;
-extern auto cJSON_CreateArray() -> cJSON*;
-extern auto cJSON_CreateObject() -> cJSON*;
+cJSON* cJSON_CreateNull(void);
+cJSON* cJSON_CreateTrue(void);
+cJSON* cJSON_CreateFalse(void);
+cJSON* cJSON_CreateBool(cJSON_bool boolean);
+cJSON* cJSON_CreateNumber(double num);
+cJSON* cJSON_CreateString(const char* string);
+/* raw json */
+cJSON* cJSON_CreateRaw(const char* raw);
+cJSON* cJSON_CreateArray(void);
+cJSON* cJSON_CreateObject(void);
-/* These utilities create an Array of count items. */
-extern auto cJSON_CreateIntArray(const int* numbers, int count) -> cJSON*;
-extern auto cJSON_CreateFloatArray(const float* numbers, int count) -> cJSON*;
-extern auto cJSON_CreateDoubleArray(const double* numbers, int count) -> cJSON*;
-extern auto cJSON_CreateStringArray(const char** strings, int count) -> cJSON*;
+/* Create a string where valuestring references a string so
+ * it will not be freed by cJSON_Delete */
+cJSON* cJSON_CreateStringReference(const char* string);
+/* Create an object/array that only references it's elements so
+ * they will not be freed by cJSON_Delete */
+cJSON* cJSON_CreateObjectReference(const cJSON* child);
+cJSON* cJSON_CreateArrayReference(const cJSON* child);
+
+/* These utilities create an Array of count items.
+ * The parameter count cannot be greater than the number of elements in the
+ * number array, otherwise array access will be out of bounds.*/
+cJSON* cJSON_CreateIntArray(const int* numbers, int count);
+cJSON* cJSON_CreateFloatArray(const float* numbers, int count);
+cJSON* cJSON_CreateDoubleArray(const double* numbers, int count);
+cJSON* cJSON_CreateStringArray(const char* const* strings, int count);
/* Append item to the specified array/object. */
-extern void cJSON_AddItemToArray(cJSON* array, cJSON* item);
-extern void cJSON_AddItemToObject(cJSON* object, const char* string,
- cJSON* item);
+cJSON_bool cJSON_AddItemToArray(cJSON* array, cJSON* item);
+cJSON_bool cJSON_AddItemToObject(cJSON* object, const char* string,
+ cJSON* item);
+/* Use this when string is definitely const (i.e. a literal, or as good as), and
+ * will definitely survive the cJSON object. WARNING: When this function was
+ * used, make sure to always check that (item->type & cJSON_StringIsConst) is
+ * zero before writing to `item->string` */
+cJSON_bool cJSON_AddItemToObjectCS(cJSON* object, const char* string,
+ cJSON* item);
/* Append reference to item to the specified array/object. Use this when you
* want to add an existing cJSON to a new cJSON, but don't want to corrupt your
* existing cJSON. */
-extern void cJSON_AddItemReferenceToArray(cJSON* array, cJSON* item);
-extern void cJSON_AddItemReferenceToObject(cJSON* object, const char* string,
- cJSON* item);
+cJSON_bool cJSON_AddItemReferenceToArray(cJSON* array, cJSON* item);
+cJSON_bool cJSON_AddItemReferenceToObject(cJSON* object, const char* string,
+ cJSON* item);
/* Remove/Detach items from Arrays/Objects. */
-extern auto cJSON_DetachItemFromArray(cJSON* array, int which) -> cJSON*;
-extern void cJSON_DeleteItemFromArray(cJSON* array, int which);
-extern auto cJSON_DetachItemFromObject(cJSON* object, const char* string)
- -> cJSON*;
-extern void cJSON_DeleteItemFromObject(cJSON* object, const char* string);
+cJSON* cJSON_DetachItemViaPointer(cJSON* parent, cJSON* const item);
+cJSON* cJSON_DetachItemFromArray(cJSON* array, int which);
+void cJSON_DeleteItemFromArray(cJSON* array, int which);
+cJSON* cJSON_DetachItemFromObject(cJSON* object, const char* string);
+cJSON* cJSON_DetachItemFromObjectCaseSensitive(cJSON* object,
+ const char* string);
+void cJSON_DeleteItemFromObject(cJSON* object, const char* string);
+void cJSON_DeleteItemFromObjectCaseSensitive(cJSON* object, const char* string);
/* Update array items. */
-extern void cJSON_ReplaceItemInArray(cJSON* array, int which, cJSON* newitem);
-extern void cJSON_ReplaceItemInObject(cJSON* object, const char* string,
- cJSON* newitem);
+cJSON_bool cJSON_InsertItemInArray(
+ cJSON* array, int which,
+ cJSON* newitem); /* Shifts pre-existing items to the right. */
+cJSON_bool cJSON_ReplaceItemViaPointer(cJSON* const parent, cJSON* const item,
+ cJSON* replacement);
+cJSON_bool cJSON_ReplaceItemInArray(cJSON* array, int which, cJSON* newitem);
+cJSON_bool cJSON_ReplaceItemInObject(cJSON* object, const char* string,
+ cJSON* newitem);
+cJSON_bool cJSON_ReplaceItemInObjectCaseSensitive(cJSON* object,
+ const char* string,
+ cJSON* newitem);
/* Duplicate a cJSON item */
-extern auto cJSON_Duplicate(cJSON* item, int recurse) -> cJSON*;
+cJSON* cJSON_Duplicate(const cJSON* item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new
-memory that will need to be released. With recurse!=0, it will duplicate any
-children connected to the item. The item->next and ->prev pointers are always
-zero on return from Duplicate. */
+ * memory that will need to be released. With recurse!=0, it will duplicate any
+ * children connected to the item. The item->next and ->prev pointers are always
+ * zero on return from Duplicate. */
+/* Recursively compare two cJSON items for equality. If either a or b is NULL or
+ * invalid, they will be considered unequal. case_sensitive determines if object
+ * keys are treated case sensitive (1) or case insensitive (0) */
+cJSON_bool cJSON_Compare(const cJSON* const a, const cJSON* const b,
+ const cJSON_bool case_sensitive);
-/* ParseWithOpts allows you to require (and check) that the JSON is null
- * terminated, and to retrieve the pointer to the final byte parsed. */
-extern auto cJSON_ParseWithOpts(const char* value,
- const char** return_parse_end,
- int require_null_terminated) -> cJSON*;
+/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from
+ * strings. The input pointer json cannot point to a read-only address area,
+ * such as a string constant, but should point to a readable and writable
+ * address area. */
+void cJSON_Minify(char* json);
-extern void cJSON_Minify(char* json);
-
-/* Macros for creating things quickly. */
-#define cJSON_AddNullToObject(object, name) \
- cJSON_AddItemToObject(object, name, cJSON_CreateNull())
-#define cJSON_AddTrueToObject(object, name) \
- cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
-#define cJSON_AddFalseToObject(object, name) \
- cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
-#define cJSON_AddBoolToObject(object, name, b) \
- cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))
-#define cJSON_AddNumberToObject(object, name, n) \
- cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
-#define cJSON_AddStringToObject(object, name, s) \
- cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
+/* Helper functions for creating and adding items to an object at the same time.
+ * They return the added item or NULL on failure. */
+cJSON* cJSON_AddNullToObject(cJSON* const object, const char* const name);
+cJSON* cJSON_AddTrueToObject(cJSON* const object, const char* const name);
+cJSON* cJSON_AddFalseToObject(cJSON* const object, const char* const name);
+cJSON* cJSON_AddBoolToObject(cJSON* const object, const char* const name,
+ const cJSON_bool boolean);
+cJSON* cJSON_AddNumberToObject(cJSON* const object, const char* const name,
+ const double number);
+cJSON* cJSON_AddStringToObject(cJSON* const object, const char* const name,
+ const char* const string);
+cJSON* cJSON_AddRawToObject(cJSON* const object, const char* const name,
+ const char* const raw);
+cJSON* cJSON_AddObjectToObject(cJSON* const object, const char* const name);
+cJSON* cJSON_AddArrayToObject(cJSON* const object, const char* const name);
/* When assigning an integer value, it needs to be propagated to valuedouble
* too. */
-#define cJSON_SetIntValue(object, val) \
- ((object) ? (object)->valueint = (object)->valuedouble = (val) : (val))
+#define cJSON_SetIntValue(object, number) \
+ ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
+/* helper for the cJSON_SetNumberValue macro */
+double cJSON_SetNumberHelper(cJSON* object, double number);
+#define cJSON_SetNumberValue(object, number) \
+ ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
+/* Change the valuestring of a cJSON_String object, only takes effect when type
+ * of object is cJSON_String */
+char* cJSON_SetValuestring(cJSON* object, const char* valuestring);
-// ericf addition: c++ wrapper for this stuff.
+/* If the object is not a boolean type this does nothing and returns
+ * cJSON_Invalid else it returns the new type*/
+#define cJSON_SetBoolValue(object, boolValue) \
+ ((object != NULL && ((object)->type & (cJSON_False | cJSON_True))) \
+ ? (object)->type = ((object)->type & (~(cJSON_False | cJSON_True))) \
+ | ((boolValue) ? cJSON_True : cJSON_False) \
+ : cJSON_Invalid)
+
+/* Macro for iterating over an array or object */
+#define cJSON_ArrayForEach(element, array) \
+ for (element = (array != NULL) ? (array)->child : NULL; element != NULL; \
+ element = element->next)
+
+/* malloc/free objects using the malloc/free functions that have been set with
+ * cJSON_InitHooks */
+void* cJSON_malloc(size_t size);
+void cJSON_free(void* object);
+
+// ericf addition: simple c++ wrappers for a few things here.
// NOTE: once added to a dict/list/etc, the underlying cJSON's
// lifecycle is dependent on its parent, not this object.
@@ -221,19 +351,6 @@ class JsonDict : public JsonObject {
}
};
-// class JsonNumber : public JsonObject {
-// public:
-// JsonNumber(double val) { set_obj(cJSON_CreateNumber(val)); }
-// };
-
-// class JsonString : public JsonObject {
-// public:
-// JsonString(const std::string& s) { set_obj(cJSON_CreateString(s.c_str()));
-// } JsonString(const char* s) { set_obj(cJSON_CreateString(s)); }
-// };
-
-#pragma clang diagnostic pop
-
} // namespace ballistica
#endif // BALLISTICA_SHARED_GENERIC_JSON_H_
diff --git a/src/ballistica/shared/generic/utf8.cc b/src/ballistica/shared/generic/utf8.cc
index 6687f1fa..b2cc83b0 100644
--- a/src/ballistica/shared/generic/utf8.cc
+++ b/src/ballistica/shared/generic/utf8.cc
@@ -14,7 +14,7 @@
with these routines reserved for higher performance on data known to be
valid.
*/
-#include "utf8.h"
+#include "ballistica/shared/generic/utf8.h"
#if _WIN32 || _WIN64
#include
diff --git a/tools/batools/build.py b/tools/batools/build.py
index cd31fe69..4277c4dc 100644
--- a/tools/batools/build.py
+++ b/tools/batools/build.py
@@ -2,13 +2,10 @@
#
"""General functionality related to running builds."""
-# pylint: disable=too-many-lines
-
from __future__ import annotations
import os
import sys
-import datetime
import subprocess
from enum import Enum
from dataclasses import dataclass
@@ -63,29 +60,6 @@ PY_REQUIREMENTS = [
PyRequirement(pipname='filelock', minversion=[3, 12, 0]),
]
-# Parts of full-tests suite we only run on particular days. This runs in
-# listed order so should be randomized by hand to avoid clustering
-# similar tests too much.
-SPARSE_TEST_BUILDS: list[list[str]] = [
- ['ios.pylibs.debug', 'android.pylibs.arm'],
- ['linux.package', 'android.pylibs.arm64'],
- ['windows.package', 'mac.pylibs'],
- ['tvos.pylibs', 'android.pylibs.x86'],
- ['android.pylibs.arm.debug'],
- ['windows.package.server'],
- ['ios.pylibs', 'android.pylibs.arm64.debug'],
- ['linux.package.server'],
- ['android.pylibs.x86.debug', 'mac.package'],
- ['mac.package.server.arm64', 'android.pylibs.x86_64'],
- ['windows.package.oculus'],
- ['mac.package.server.x86_64', 'android.pylibs.x86_64.debug'],
- ['mac.pylibs.debug', 'android.package'],
-]
-
-# Currently only doing sparse-tests in core; not spinoffs (whole word
-# will get subbed out in spinoffs so this will evaluate to False).
-DO_SPARSE_TEST_BUILDS = 'ballistica' + 'kit' == 'ballisticakit'
-
class PrefabTarget(Enum):
"""Types of prefab builds able to be run."""
@@ -309,314 +283,6 @@ def archive_old_builds(
)
-def gen_fulltest_buildfile_android() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
- # pylint: disable=too-many-branches
-
- # Its a pretty big time-suck building all architectures for all of
- # our subplatforms, so lets usually just build a single one. We'll
- # rotate it though and occasionally do all 4 at once just to be
- # safe.
- dayoffset = datetime.datetime.now().timetuple().tm_yday
-
- # Let's only do a full 'prod' once every two times through the loop.
- # (it really should never catch anything that individual platforms
- # don't)
- modes = ['arm', 'arm64', 'x86', 'x86_64']
- modes += modes
- modes.append('prod')
-
- # By default we cycle through build architectures for each flavor.
- # However, for minor flavor with low risk of platform-dependent
- # breakage we stick to a single one to keep disk space costs lower.
- # (build files amount to several gigs per mode per flavor)
- #
- # UPDATE: Now that we have CPU time to spare, we simply always do
- # 'arm64' or 'prod' depending on build type; this results in 1 or 4
- # architectures worth of build files per flavor instead of 8 (prod +
- # 4 singles) and keeps our daily runs identical.
- lightweight_flavors = {'template', 'arcade', 'demo', 'iircade'}
-
- lines = []
- for _i, flavor in enumerate(
- sorted(os.listdir('ballisticakit-android/BallisticaKit/src'))
- ):
- if flavor == 'main' or flavor.startswith('.'):
- continue
-
- if flavor in lightweight_flavors:
- mode = 'arm64'
- else:
- # mode = modes[(dayoffset + i) % len(modes)]
- mode = 'prod'
- lines.append(
- 'ANDROID_PLATFORM='
- + flavor
- + ' ANDROID_MODE='
- + mode
- + ' make android-cloud-build'
- )
-
- # Now add sparse tests that land on today.
- if DO_SPARSE_TEST_BUILDS:
- extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)]
- extras = [e for e in extras if e.startswith('android.')]
- cspre = 'tools/cloudshell linbeast --env ba-android-alldeps --'
-
- # This is currently broken; turning off.
- # Update: should be working again; hooray!
- do_py_android = True
-
- for extra in extras:
- if extra == 'android.pylibs.arm':
- if do_py_android:
- lines.append(
- f'{cspre} tools/pcommand' f' python_build_android arm'
- )
- elif extra == 'android.pylibs.arm.debug':
- if do_py_android:
- lines.append(
- f'{cspre} tools/pcommand'
- f' python_build_android_debug arm'
- )
- elif extra == 'android.pylibs.arm64':
- if do_py_android:
- lines.append(
- f'{cspre} tools/pcommand python_build_android arm64'
- )
- elif extra == 'android.pylibs.arm64.debug':
- if do_py_android:
- lines.append(
- f'{cspre} tools/pcommand'
- f' python_build_android_debug arm64'
- )
- elif extra == 'android.pylibs.x86':
- if do_py_android:
- lines.append(
- f'{cspre} tools/pcommand' f' python_build_android x86'
- )
- elif extra == 'android.pylibs.x86.debug':
- if do_py_android:
- lines.append(
- f'{cspre} tools/pcommand'
- f' python_build_android_debug x86'
- )
- elif extra == 'android.pylibs.x86_64':
- if do_py_android:
- lines.append(
- f'{cspre} tools/pcommand'
- f' python_build_android x86_64'
- )
- elif extra == 'android.pylibs.x86_64.debug':
- if do_py_android:
- lines.append(
- f'{cspre} tools/pcommand'
- f' python_build_android_debug x86_64'
- )
- elif extra == 'android.package':
- lines.append('make android-package-cloud')
- else:
- raise RuntimeError(f'Unknown extra: {extra}')
-
- os.makedirs('build', exist_ok=True)
- with open(
- 'build/fulltest_buildfile_android', 'w', encoding='utf-8'
- ) as outfile:
- outfile.write('\n'.join(lines))
-
-
-def gen_fulltest_buildfile_windows() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
-
- dayoffset = datetime.datetime.now().timetuple().tm_yday
-
- lines: list[str] = []
-
- # We want to do one regular, one headless, and one oculus build, but
- # let's switch up 32 or 64 bit based on the day. Also occasionally
- # throw a release build in but stick to mostly debug builds to keep
- # build times speedier.
- pval1 = 'Win32' if dayoffset % 2 == 0 else 'x64'
- pval2 = 'Win32' if (dayoffset + 1) % 2 == 0 else 'x64'
- pval3 = 'Win32' if (dayoffset + 2) % 2 == 0 else 'x64'
- cfg1 = 'Release' if dayoffset % 7 == 0 else 'Debug'
- cfg2 = 'Release' if (dayoffset + 1) % 7 == 0 else 'Debug'
- cfg3 = 'Release' if (dayoffset + 2) % 7 == 0 else 'Debug'
-
- lines.append(
- f'WINDOWS_PROJECT=Generic WINDOWS_PLATFORM={pval1} '
- f'WINDOWS_CONFIGURATION={cfg1} make windows-cloud-build'
- )
- lines.append(
- f'WINDOWS_PROJECT=Headless WINDOWS_PLATFORM={pval2} '
- f'WINDOWS_CONFIGURATION={cfg2} make windows-cloud-build'
- )
- lines.append(
- f'WINDOWS_PROJECT=Oculus WINDOWS_PLATFORM={pval3} '
- f'WINDOWS_CONFIGURATION={cfg3} make windows-cloud-build'
- )
-
- # Now add sparse tests that land on today.
- if DO_SPARSE_TEST_BUILDS:
- extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)]
- extras = [e for e in extras if e.startswith('windows.')]
- for extra in extras:
- if extra == 'windows.package':
- lines.append('make windows-package')
- elif extra == 'windows.package.server':
- lines.append('make windows-server-package')
- elif extra == 'windows.package.oculus':
- lines.append('make windows-oculus-package')
- else:
- raise RuntimeError(f'Unknown extra: {extra}')
-
- os.makedirs('build', exist_ok=True)
- with open(
- 'build/fulltest_buildfile_windows', 'w', encoding='utf-8'
- ) as outfile:
- outfile.write('\n'.join(lines))
-
-
-def gen_fulltest_buildfile_apple() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
- # pylint: disable=too-many-branches
-
- dayoffset = datetime.datetime.now().timetuple().tm_yday
-
- # noinspection PyListCreation
- lines = []
-
- # pybuildapple = 'tools/pcommand python_build_apple'
- pybuildapple = (
- 'tools/cloudshell --env tools fromini -- '
- 'tools/pcommand python_build_apple'
- )
-
- # iOS stuff
- lines.append('make ios-cloud-build')
- lines.append('make ios-new-cloud-build')
- if DO_SPARSE_TEST_BUILDS:
- extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)]
- extras = [e for e in extras if e.startswith('ios.')]
- for extra in extras:
- if extra == 'ios.pylibs':
- lines.append(f'{pybuildapple} ios')
- elif extra == 'ios.pylibs.debug':
- lines.append(f'{pybuildapple}_debug ios')
- else:
- raise RuntimeError(f'Unknown extra: {extra}')
-
- # tvOS stuff
- lines.append('make tvos-cloud-build')
- if DO_SPARSE_TEST_BUILDS:
- extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)]
- extras = [e for e in extras if e.startswith('tvos.')]
- for extra in extras:
- if extra == 'tvos.pylibs':
- lines.append(f'{pybuildapple} tvos')
- elif extra == 'tvos.pylibs.debug':
- lines.append(f'{pybuildapple}_debug tvos')
- else:
- raise RuntimeError(f'Unknown extra: {extra}')
-
- # macOS stuff
- lines.append('make mac-cloud-build')
- # (throw release build in the mix to hopefully catch opt-mode-only errors).
- lines.append('MAC_CONFIGURATION=Release make mac-appstore-cloud-build')
- lines.append('make mac-new-cloud-build')
- lines.append('CMAKE_CLOUDSHELL_HOST=fromini make cmake-cloud-server-build')
- lines.append('CMAKE_CLOUDSHELL_HOST=fromini make cmake-cloud-build')
- if DO_SPARSE_TEST_BUILDS:
- extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)]
- extras = [e for e in extras if e.startswith('mac.')]
- for extra in extras:
- if extra == 'mac.package':
- lines.append(
- 'BA_MAC_DISK_IMAGE_SKIP_NOTARIZATION=1'
- ' make mac-package-cloud'
- # 'make mac-package-cloud'
- )
- elif extra == 'mac.package.server.x86_64':
- lines.append('make mac-server-package-x86-64')
- elif extra == 'mac.package.server.arm64':
- lines.append('make mac-server-package-arm64')
- elif extra == 'mac.pylibs':
- lines.append(f'{pybuildapple} mac')
- elif extra == 'mac.pylibs.debug':
- lines.append(f'{pybuildapple}_debug mac')
- else:
- raise RuntimeError(f'Unknown extra: {extra}')
-
- os.makedirs('build', exist_ok=True)
- with open(
- 'build/fulltest_buildfile_apple', 'w', encoding='utf-8'
- ) as outfile:
- outfile.write('\n'.join(lines))
-
-
-def gen_fulltest_buildfile_linux() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
-
- dayoffset = datetime.datetime.now().timetuple().tm_yday
-
- targets = ['build', 'server-build']
- lines = []
- for target in targets:
- lines.append(f'make cmake-cloud-{target}')
-
- if DO_SPARSE_TEST_BUILDS:
- extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)]
- extras = [e for e in extras if e.startswith('linux.')]
- for extra in extras:
- if extra == 'linux.package':
- lines.append('make linux-package')
- elif extra == 'linux.package.server':
- lines.append('make linux-server-package')
- else:
- raise RuntimeError(f'Unknown extra: {extra}')
-
- os.makedirs('build', exist_ok=True)
- with open(
- 'build/fulltest_buildfile_linux', 'w', encoding='utf-8'
- ) as outfile:
- outfile.write('\n'.join(lines))
-
-
-def gen_fulltest_buildfile_spinoff() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
- from batools.featureset import FeatureSet
-
- # Run a spinoff test with each of our feature-sets individually.
- # Note that there will likely be redundant tests with the same final
- # resolved sets of feature sets. We can filter those out later if it
- # seems worthwhile.
- targets = sorted(f.name for f in FeatureSet.get_all_for_project('.'))
- lines = []
- for target in targets:
- lines.append(f'SPINOFF_TEST_TARGET={target} make spinoff-test-cloud')
-
- os.makedirs('build', exist_ok=True)
- with open(
- 'build/fulltest_buildfile_spinoff', 'w', encoding='utf-8'
- ) as outfile:
- outfile.write('\n'.join(lines))
-
-
def get_current_prefab_platform(wsl_gives_windows: bool = True) -> str:
"""Get an identifier for the platform running this build.
diff --git a/tools/batools/pcommand.py b/tools/batools/pcommand.py
index acfe5756..e84651d5 100644
--- a/tools/batools/pcommand.py
+++ b/tools/batools/pcommand.py
@@ -240,56 +240,6 @@ def printcolors() -> None:
)
-def gen_fulltest_buildfile_android() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
- import batools.build
-
- batools.build.gen_fulltest_buildfile_android()
-
-
-def gen_fulltest_buildfile_windows() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
- import batools.build
-
- batools.build.gen_fulltest_buildfile_windows()
-
-
-def gen_fulltest_buildfile_apple() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
- import batools.build
-
- batools.build.gen_fulltest_buildfile_apple()
-
-
-def gen_fulltest_buildfile_linux() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
- import batools.build
-
- batools.build.gen_fulltest_buildfile_linux()
-
-
-def gen_fulltest_buildfile_spinoff() -> None:
- """Generate fulltest command list for jenkins.
-
- (so we see nice pretty split-up build trees)
- """
- import batools.build
-
- batools.build.gen_fulltest_buildfile_spinoff()
-
-
def python_version_android_base() -> None:
"""Print built Python base version."""
from efrotools.pybuild import PY_VER_ANDROID
diff --git a/tools/batools/spinoff/_main.py b/tools/batools/spinoff/_main.py
index 164189a0..b9191772 100644
--- a/tools/batools/spinoff/_main.py
+++ b/tools/batools/spinoff/_main.py
@@ -109,7 +109,7 @@ def _main() -> None:
if '--soft' in sys.argv:
return
raise CleanError(
- 'This only works on dst projects;'
+ 'Spinoff only works from dst projects;'
' you appear to be in a src project.'
" To silently no-op in this case, pass '--soft'."
)
diff --git a/tools/efrotools/openalbuild.py b/tools/efrotools/openalbuild.py
new file mode 100644
index 00000000..bfbfa061
--- /dev/null
+++ b/tools/efrotools/openalbuild.py
@@ -0,0 +1,137 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""Functionality to build the openal library."""
+
+from __future__ import annotations
+
+import os
+import subprocess
+from typing import TYPE_CHECKING
+
+from efro.error import CleanError
+
+if TYPE_CHECKING:
+ pass
+
+# Arch names we take and their official android versions.
+ARCHS = {
+ 'arm': 'armeabi-v7a',
+ 'arm64': 'arm64-v8a',
+ 'x86': 'x86',
+ 'x86_64': 'x86_64',
+}
+
+MODES = {'debug', 'release'}
+
+
+def _build_dir(arch: str, mode: str) -> str:
+ """Build dir given an arch and mode."""
+ return f'build/openal_build_android_{arch}_{mode}'
+
+
+def build(arch: str, mode: str) -> None:
+ """Do the thing."""
+ from efrotools import replace_exact
+
+ if arch not in ARCHS:
+ raise CleanError(f"Invalid arch '{arch}'.")
+
+ if mode not in MODES:
+ raise CleanError(f"Invalid mode '{mode}'.")
+
+ # Get ndk path.
+ ndk_path = (
+ subprocess.run(
+ ['tools/pcommand', 'android_sdk_utils', 'get-ndk-path'],
+ check=True,
+ capture_output=True,
+ )
+ .stdout.decode()
+ .strip()
+ )
+
+ # Grab from git and build.
+ builddir = _build_dir(arch, mode)
+ subprocess.run(['rm', '-rf', builddir], check=True)
+ subprocess.run(['mkdir', '-p', os.path.dirname(builddir)], check=True)
+ subprocess.run(
+ ['git', 'clone', 'https://github.com/kcat/openal-soft.git', builddir],
+ check=True,
+ )
+
+ commit = 'd3875f3' # Version 1.23.1
+ subprocess.run(['git', 'checkout', commit], check=True, cwd=builddir)
+
+ # One bit of filtering: by default, openalsoft sends all sorts of
+ # log messages to the android log. This is reasonable since its
+ # possible to filter by tag/level. However I'd prefer it to send
+ # only the ones that it would send to stderr so I don't always have
+ # to worry about filtering.
+ loggingpath = f'{builddir}/core/logging.cpp'
+ with open(loggingpath, encoding='utf-8') as infile:
+ txt = infile.read()
+
+ txt = replace_exact(
+ txt,
+ ' __android_log_print(android_severity(level),'
+ ' "openal", "%s", str);',
+ ' // ericf tweak; only send logs to'
+ ' android that we\'d send to stderr.\n'
+ ' if (gLogLevel >= level) {\n'
+ ' __android_log_print(android_severity(level),'
+ ' "openal", "%s", str);\n'
+ ' }',
+ )
+ with open(loggingpath, 'w', encoding='utf-8') as outfile:
+ outfile.write(txt)
+
+ subprocess.run(
+ [
+ 'cmake',
+ '.',
+ f'-DANDROID_ABI={ARCHS[arch]}',
+ '-DANDROID_NATIVE_API_LEVEL=21',
+ f'-DCMAKE_BUILD_TYPE={mode}',
+ '-DLIBTYPE=STATIC',
+ '-DCMAKE_TOOLCHAIN_FILE='
+ f'{ndk_path}/build/cmake/android.toolchain.cmake',
+ ],
+ cwd=builddir,
+ check=True,
+ )
+ subprocess.run(['make'], cwd=builddir, check=True)
+
+ print('SUCCESS!')
+
+
+def gather() -> None:
+ """Gather the things. Assumes all have been built."""
+
+ # Sanity-check; make sure everything appears to be built.
+ for arch in ARCHS:
+ for mode in MODES:
+ builddir = _build_dir(arch, mode)
+ libfile = os.path.join(builddir, 'libopenal.a')
+ if not os.path.exists(libfile):
+ raise CleanError(f"Built lib not found: '{libfile}'.")
+
+ outdir = 'src/external/openal-android'
+ subprocess.run(['rm', '-rf', outdir], check=True)
+
+ subprocess.run(['mkdir', '-p', f'{outdir}/include'], check=True)
+
+ builddir = _build_dir('arm', 'debug') # Doesn't matter here.
+ subprocess.run(
+ ['cp', '-r', f'{builddir}/include/AL', f'{outdir}/include'],
+ check=True,
+ )
+
+ for arch, andrarch in ARCHS.items():
+ for mode in MODES:
+ builddir = _build_dir(arch, mode)
+ installdir = f'{outdir}/lib/{andrarch}_{mode}'
+ subprocess.run(['mkdir', '-p', installdir], check=True)
+ subprocess.run(
+ ['cp', f'{builddir}/libopenal.a', installdir], check=True
+ )
+ print('OpenAL gather successful!')
diff --git a/tools/efrotools/pcommand2.py b/tools/efrotools/pcommand2.py
index 82dc2bd3..77c0fbf6 100644
--- a/tools/efrotools/pcommand2.py
+++ b/tools/efrotools/pcommand2.py
@@ -40,3 +40,30 @@ def sortlines() -> None:
val = sys.argv[2]
lines = val.splitlines()
print('\n'.join(sorted(lines, key=lambda l: l.lower())))
+
+
+def openal_build_android() -> None:
+ """Build openalsoft for android."""
+ from efro.error import CleanError
+ from efrotools.openalbuild import build
+
+ args = sys.argv[2:]
+ if len(args) != 2:
+ raise CleanError(
+ 'Expected one arg: arm, arm64, x86, x86_64'
+ ' and one arg: debug, release'
+ )
+
+ build(args[0], args[1])
+
+
+def openal_gather() -> None:
+ """Gather built opealsoft libs into src."""
+ from efro.error import CleanError
+ from efrotools.openalbuild import gather
+
+ args = sys.argv[2:]
+ if args:
+ raise CleanError('No args expected.')
+
+ gather()
diff --git a/tools/pcommand b/tools/pcommand
index 5307b4d9..094a4a5a 100755
--- a/tools/pcommand
+++ b/tools/pcommand
@@ -54,6 +54,8 @@ from efrotools.pcommand import (
from efrotools.pcommand2 import (
with_build_lock,
sortlines,
+ openal_build_android,
+ openal_gather,
)
from batools.pcommand import (
resize_image,
@@ -64,11 +66,6 @@ from batools.pcommand import (
androidaddr,
push_ipa,
printcolors,
- gen_fulltest_buildfile_android,
- gen_fulltest_buildfile_windows,
- gen_fulltest_buildfile_apple,
- gen_fulltest_buildfile_linux,
- gen_fulltest_buildfile_spinoff,
prune_includes,
python_version_android,
python_version_apple,