AI added possibility to add two additional locks.
Some checks failed
NukiHub / Build esp32-gl-s10 (release) (push) Waiting to run
NukiHub / Build esp32-c3 (release) (push) Failing after 21m55s
NukiHub / Build esp32-c5 (release) (push) Failing after 14m56s
NukiHub / Build esp32-c61 (release) (push) Failing after 9m31s
NukiHub / Build esp32 (release) (push) Failing after 3h0m37s
NukiHub / Build esp32-c6 (release) (push) Failing after 14m45s
NukiHub / Build esp32-nopsram (release) (push) Has been cancelled
NukiHub / Build esp32-p4 (release) (push) Has been cancelled
NukiHub / Build esp32-p4c5 (release) (push) Has been cancelled
NukiHub / Build esp32-s3 (release) (push) Has been cancelled
NukiHub / Build esp32-s3-nopsram (release) (push) Has been cancelled
NukiHub / Build esp32-s3-oct (release) (push) Has been cancelled
NukiHub / Build esp32-solo1 (release) (push) Has been cancelled
NukiHub / Build esp32-h2 (release) (push) Has been cancelled

* only ESP32-C3
* only Smart Lock Go
This commit is contained in:
2026-03-20 17:39:07 +01:00
parent 213e0e8692
commit ea23bf142a
17 changed files with 1945 additions and 1226 deletions

View File

@@ -185,6 +185,28 @@ Before enabling pairing mode using the button on the Nuki Lock Ultra / Nuki Lock
When pairing is successful, the web interface should show "Paired: Yes".<br>
## Pairing additional locks (Slot 2 and Slot 3)
Nuki Hub supports up to 3 locks on a single ESP/device IP.
To pair additional locks:
- Go to "Nuki Configuration" and enable "Nuki Lock slot 2 enabled" and/or "Nuki Lock slot 3 enabled", then Save.
- Go to "MQTT Configuration" and set "Lock Slot 2 MQTT Name" / "Lock Slot 3 MQTT Name", then Save.
- Put the corresponding lock into Bluetooth pairing mode using the lock button.
- Pairing for slot 2/3 is automatic while the slot is enabled and not yet paired.
How this is shown:
- On the info page, check "NUKI LOCK SLOT 2" and "NUKI LOCK SLOT 3" sections.
- MQTT topics are published per slot name:
- `<mqtt_path>/<slot_1_mqtt_name>/...`
- `<mqtt_path>/<slot_2_mqtt_name>/...`
- `<mqtt_path>/<slot_3_mqtt_name>/...`
Unpairing slot 2/3 in web interface:
- Open "Credentials".
- Use "Unpair Nuki Lock Slot 2" or "Unpair Nuki Lock Slot 3".
- Enter the shown 4 digit confirmation code.
## Hybrid mode
Hybrid mode allows you to use the official Nuki MQTT implementation on a Nuki Lock 3.0 Pro, Nuki Lock 4.0, Nuki Lock 4.0 Pro, Nuki Lock 5.0 Pro, Nuki Lock Go or Nuki Lock Ultra in conjunction with Nuki Hub.<br>
@@ -265,6 +287,9 @@ In a browser navigate to the IP address assigned to the ESP32.
- MQTT User: If using authentication on the MQTT broker set to a username with read/write rights on the MQTT broker, set to # to clear
- MQTT Password : If using authentication on the MQTT broker set to the password belonging to a username with read/write rights on the MQTT broker, set to # to clear
- MQTT Nuki Hub Path: Set to the preferred MQTT root topic for Nuki Hub, defaults to "nukihub". Make sure this topic is unique when using multiple ESP32 Nuki Hub devices
- Lock Slot 1 MQTT Name: Set the MQTT topic name for lock slot 1 (default: `lock`)
- Lock Slot 2 MQTT Name: Set the MQTT topic name for lock slot 2 (default: `lock2`)
- Lock Slot 3 MQTT Name: Set the MQTT topic name for lock slot 3 (default: `lock3`)
- Enable Home Assistant auto discovery: Enable Home Assistant MQTT auto discovery. Will automatically create entities in Home Assistant for Nuki Hub and connected Nuki Lock and/or Opener when enabled.
#### Advanced MQTT Configuration
@@ -289,6 +314,8 @@ In a browser navigate to the IP address assigned to the ESP32.
#### Basic Nuki Configuration
- Nuki Smartlock enabled: Enable if you want Nuki Hub to connect to a Nuki Lock (All versions)
- Nuki Lock slot 2 enabled: Enable if you want Nuki Hub to connect to a second Nuki Lock
- Nuki Lock slot 3 enabled: Enable if you want Nuki Hub to connect to a third Nuki Lock
- Nuki Smartlock Ultra/Go/5th gen enabled: Enable if you want Nuki Hub to connect to a Nuki Lock Ultra/Go/5th gen Pro
- Nuki Opener enabled: Enable if you want Nuki Hub to connect to a Nuki Opener
@@ -366,9 +393,9 @@ Note: All of the following requires the Nuki security code / PIN to be set, see
- PIN Code: Fill with the Nuki Security Code of the Nuki Lock and/or Nuki Opener. Required for functions that require the security code to be sent to the lock/opener such as setting lock permissions/adding keypad codes, viewing the activity log or changing the Nuki device configuration. Set to "#" to remove the security code from the Nuki Hub configuration.
- PIN Code Ultra/5th gen: Fill with the 6-digit Nuki Security Code of the Nuki Lock Ultra/Go/5th gen Pro. Required for pairing (and many other functions)
#### Unpair Nuki Lock / Unpair Nuki Opener
#### Unpair Nuki Lock / Unpair Nuki Lock Slot 2 / Unpair Nuki Lock Slot 3 / Unpair Nuki Opener
- Type [4 DIGIT CODE] to confirm unpair: Set to the shown randomly generated code to unpair the Nuki Lock or Opener from the Nuki Hub.
- Type [4 DIGIT CODE] to confirm unpair: Set to the shown randomly generated code to unpair the selected Nuki device from the Nuki Hub.
#### Factory reset Nuki Hub

View File

@@ -5,8 +5,8 @@
#define NUKI_HUB_VERSION "9.15"
#define NUKI_HUB_VERSION_INT (uint32_t)915
#define NUKI_HUB_BUILD "unknownbuildnr"
#define NUKI_HUB_DATE "2026-01-09"
#define NUKI_HUB_DATE "2026-01-09"
#define NUKI_HUB_DATE "2026-03-20"
#define NUKI_HUB_DATE "2026-03-20"
#define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest"
#define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json"

View File

@@ -19,7 +19,7 @@ HomeAssistantDiscovery::HomeAssistantDiscovery(NetworkDevice* device, Preference
_hostname = _preferences->getString(preference_hostname, "");
}
void HomeAssistantDiscovery::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad)
void HomeAssistantDiscovery::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad, const char* topicOverride)
{
uint64_t savedDevId = _preferences->getULong64(preference_nukihub_id, 0);
uint64_t curDevId;
@@ -96,12 +96,11 @@ void HomeAssistantDiscovery::setupHASS(int type, uint32_t nukiId, char* nukiName
}
else if(type == 1)
{
if(_preferences->getUInt(preference_nuki_id_lock, 0) != nukiId)
if(topicOverride == nullptr && _preferences->getUInt(preference_nuki_id_lock, 0) != nukiId)
{
return;
}
String lockTopic = _baseTopic;
lockTopic.concat("/lock");
String lockTopic = (topicOverride != nullptr) ? String(topicOverride) : _baseTopic + "/lock";
publishHASSConfig((char*)"SmartLock", lockTopic.c_str(), nukiName, uidString, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, publishAuthData, (char*)"lock", (char*)"unlock", (char*)"unlatch");
Log->println("HomeAssistant setup for lock completed.");
}

View File

@@ -7,7 +7,7 @@ class HomeAssistantDiscovery
{
public:
explicit HomeAssistantDiscovery(NetworkDevice* device, Preferences* preferences, char* buffer, size_t bufferSize);
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad);
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad, const char* topicOverride = nullptr);
void disableHASS();
void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
void publishHassTopic(const String& mqttDeviceType,
@@ -67,4 +67,4 @@ private:
char* _buffer;
const size_t _bufferSize;
};
};

View File

@@ -5,6 +5,34 @@
#include "PreferencesKeys.h"
#include <DuoAuthLib.h>
#include <TOTP-RC6236-generator.hpp>
#include <ctype.h>
static String normalizeMqttLockName(String name)
{
name.trim();
name.toLowerCase();
return name;
}
static bool isValidMqttLockName(const String& name)
{
if(name.length() < 3 || name.length() > 32)
{
return false;
}
for(size_t i = 0; i < name.length(); i++)
{
const char c = name.charAt(i);
if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-')
{
continue;
}
return false;
}
return true;
}
ImportExport::ImportExport(Preferences *preferences)
: _preferences(preferences)
@@ -657,6 +685,22 @@ void ImportExport::exportNukiHubJson(JsonDocument &json, bool redacted, bool pai
json[key] = text;
memset(text, 0, sizeof(text));
}
JsonArray locks = json["locks"].to<JsonArray>();
JsonObject lock1 = locks.add<JsonObject>();
lock1["slot"] = 1;
lock1["enabled"] = _preferences->getBool(preference_lock_enabled, true) ? "1" : "0";
lock1["mqtt_name"] = _preferences->getString(preference_mqtt_lock_name_1, "lock");
JsonObject lock2 = locks.add<JsonObject>();
lock2["slot"] = 2;
lock2["enabled"] = _preferences->getBool(preference_lock_enabled_2, false) ? "1" : "0";
lock2["mqtt_name"] = _preferences->getString(preference_mqtt_lock_name_2, "lock2");
JsonObject lock3 = locks.add<JsonObject>();
lock3["slot"] = 3;
lock3["enabled"] = _preferences->getBool(preference_lock_enabled_3, false) ? "1" : "0";
lock3["mqtt_name"] = _preferences->getString(preference_mqtt_lock_name_3, "lock3");
}
JsonDocument ImportExport::importJson(JsonDocument &doc)
@@ -677,9 +721,97 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
const std::vector<char*> intPrefs = debugPreferences.getPreferencesIntKeys();
const std::vector<char*> uintPrefs = debugPreferences.getPreferencesUIntKeys();
const std::vector<char*> uint64Prefs = debugPreferences.getPreferencesUInt64Keys();
bool hasLocksArray = false;
String importedLockNames[3] = {
normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_1, "lock")),
normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_2, "lock2")),
normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_3, "lock3"))
};
bool importedLockEnabled[3] = {
_preferences->getBool(preference_lock_enabled, true),
_preferences->getBool(preference_lock_enabled_2, false),
_preferences->getBool(preference_lock_enabled_3, false)
};
if(!doc["locks"].isNull() && doc["locks"].is<JsonArray>())
{
hasLocksArray = true;
JsonArray locks = doc["locks"].as<JsonArray>();
if(locks.size() == 0 || locks.size() > 3)
{
json["_import_error"] = "Invalid locks array size (must be 1..3).";
return json;
}
for(JsonVariant v : locks)
{
if(!v.is<JsonObject>())
{
json["_import_error"] = "Invalid locks entry (must be object).";
return json;
}
JsonObject o = v.as<JsonObject>();
int slot = 0;
if(!o["slot"].isNull())
{
slot = o["slot"].as<int>();
}
if(slot < 1 || slot > 3)
{
json["_import_error"] = "Invalid lock slot in import (must be 1..3).";
return json;
}
if(o["mqtt_name"].isNull())
{
json["_import_error"] = "Missing locks[].mqtt_name.";
return json;
}
String lockName = normalizeMqttLockName(o["mqtt_name"].as<String>());
if(!isValidMqttLockName(lockName))
{
json["_import_error"] = "Invalid locks[].mqtt_name (allowed: a-z,0-9,_,-; length 3..32).";
return json;
}
importedLockNames[slot - 1] = lockName;
if(!o["enabled"].isNull())
{
if(o["enabled"].is<bool>())
{
importedLockEnabled[slot - 1] = o["enabled"].as<bool>();
}
else
{
const String enabledVal = o["enabled"].as<String>();
importedLockEnabled[slot - 1] = (enabledVal == "1" || enabledVal == "true");
}
}
}
if(importedLockNames[0] == importedLockNames[1] ||
importedLockNames[0] == importedLockNames[2] ||
importedLockNames[1] == importedLockNames[2])
{
json["_import_error"] = "Duplicate locks[].mqtt_name is not allowed.";
return json;
}
}
for(const auto& key : keysPrefs)
{
if(hasLocksArray &&
(strcmp(key, preference_mqtt_lock_name_1) == 0 ||
strcmp(key, preference_mqtt_lock_name_2) == 0 ||
strcmp(key, preference_mqtt_lock_name_3) == 0 ||
strcmp(key, preference_lock_enabled) == 0 ||
strcmp(key, preference_lock_enabled_2) == 0 ||
strcmp(key, preference_lock_enabled_3) == 0))
{
continue;
}
if(doc[key].isNull())
{
continue;
@@ -760,6 +892,21 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
}
}
if(hasLocksArray)
{
_preferences->putString(preference_mqtt_lock_name_1, importedLockNames[0]);
_preferences->putString(preference_mqtt_lock_name_2, importedLockNames[1]);
_preferences->putString(preference_mqtt_lock_name_3, importedLockNames[2]);
_preferences->putBool(preference_lock_enabled, importedLockEnabled[0]);
_preferences->putBool(preference_lock_enabled_2, importedLockEnabled[1]);
_preferences->putBool(preference_lock_enabled_3, importedLockEnabled[2]);
if(importedLockEnabled[1] || importedLockEnabled[2])
{
_preferences->putBool(preference_opener_enabled, false);
}
json["locks"] = "changed";
}
for(const auto& key : bytePrefs)
{
if(!doc[key].isNull() && doc[key].is<JsonVariant>())
@@ -1144,4 +1291,4 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
}
return json;
}
}

View File

@@ -821,17 +821,38 @@ bool NukiNetwork::reconnect(bool force)
if(_preferences->getBool(preference_reset_mqtt_topics, false))
{
char mqttLockPath[181] = {0};
char mqttLockPath1[181] = {0};
char mqttLockPath2[181] = {0};
char mqttLockPath3[181] = {0};
char mqttOpenerPath[181] = {0};
char mqttOldOpenerPath[181] = {0};
char mqttOldOpenerPath2[181] = {0};
String mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
mqttPath.concat("/lock");
mqttPath.concat("/");
mqttPath.concat(_preferences->getString(preference_mqtt_lock_name_1, "lock"));
size_t len = mqttPath.length();
for(int i=0; i < len; i++)
{
mqttLockPath[i] = mqttPath.charAt(i);
mqttLockPath1[i] = mqttPath.charAt(i);
}
mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
mqttPath.concat("/");
mqttPath.concat(_preferences->getString(preference_mqtt_lock_name_2, "lock2"));
len = mqttPath.length();
for(int i=0; i < len; i++)
{
mqttLockPath2[i] = mqttPath.charAt(i);
}
mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
mqttPath.concat("/");
mqttPath.concat(_preferences->getString(preference_mqtt_lock_name_3, "lock3"));
len = mqttPath.length();
for(int i=0; i < len; i++)
{
mqttLockPath3[i] = mqttPath.charAt(i);
}
mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
@@ -867,7 +888,9 @@ bool NukiNetwork::reconnect(bool force)
for(const auto& topic : mqttTopicsKeys)
{
removeTopic(_maintenancePathPrefix, topic);
removeTopic(mqttLockPath, topic);
removeTopic(mqttLockPath1, topic);
removeTopic(mqttLockPath2, topic);
removeTopic(mqttLockPath3, topic);
removeTopic(mqttOpenerPath, topic);
if (len > 5)
{
@@ -1536,9 +1559,9 @@ void NukiNetwork::removeTopic(const String& mqttPath, const String& mqttTopic)
#endif
}
void NukiNetwork::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad)
void NukiNetwork::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad, const char* topicOverride)
{
_hadiscovery->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad);
_hadiscovery->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, topicOverride);
}
void NukiNetwork::disableHASS()

View File

@@ -71,7 +71,7 @@ public:
void advertisingModeToString(const Nuki::AdvertisingMode advmode, char* str);
void timeZoneIdToString(const Nuki::TimeZoneId timeZoneId, char* str);
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad);
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad, const char* topicOverride = nullptr);
void disableHASS();
void publishHassTopic(const String& mqttDeviceType,
const String& mqttDeviceName,
@@ -183,4 +183,4 @@ private:
int8_t _lastRssi = 127;
#endif
};
};

View File

@@ -14,12 +14,38 @@ extern bool forceEnableWebServer;
extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
NukiNetworkLock::NukiNetworkLock(NukiNetwork* network, NukiOfficial* nukiOfficial, Preferences* preferences, char* buffer, size_t bufferSize)
static String normalizedLockMqttName(Preferences* preferences, const char* prefKey)
{
String in = preferences->getString(prefKey, "lock");
in.trim();
in.toLowerCase();
String out;
out.reserve(in.length());
for(size_t i = 0; i < in.length(); i++)
{
const char c = in.charAt(i);
if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-')
{
out.concat(c);
}
}
if(out.length() < 3 || out.length() > 32)
{
return String("lock");
}
return out;
}
NukiNetworkLock::NukiNetworkLock(NukiNetwork* network, NukiOfficial* nukiOfficial, Preferences* preferences, char* buffer, size_t bufferSize, uint8_t lockSlot)
: _network(network),
_nukiOfficial(nukiOfficial),
_preferences(preferences),
_buffer(buffer),
_bufferSize(bufferSize)
_bufferSize(bufferSize),
_lockSlot(lockSlot)
{
_nukiPublisher = new NukiPublisher(network, _mqttPath);
_nukiOfficial->setPublisher(_nukiPublisher);
@@ -42,7 +68,8 @@ void NukiNetworkLock::initialize()
}
String mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
mqttPath.concat("/lock");
mqttPath.concat("/");
mqttPath.concat(normalizedLockMqttName(_preferences, mqttNamePreferenceKey()));
size_t len = mqttPath.length();
for(int i=0; i < len; i++)
@@ -151,7 +178,7 @@ void NukiNetworkLock::initialize()
if(_nukiOfficial->getOffEnabled())
{
_nukiOfficial->setUid(_preferences->getUInt(preference_nuki_id_lock, 0));
_nukiOfficial->setUid(_preferences->getUInt(nukiIdPreferenceKey(), 0));
for(const auto& offTopic : _nukiOfficial->getOffTopics())
{
@@ -252,7 +279,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
Log->print("Lock action received: ");
Log->println(data);
LockActionResult lockActionResult = LockActionResult::Failed;
if(_lockActionReceivedCallback != NULL)
if(_lockActionReceivedCallback)
{
lockActionResult = _lockActionReceivedCallback(data);
}
@@ -348,7 +375,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
return;
}
if(_configUpdateReceivedCallback != NULL)
if(_configUpdateReceivedCallback)
{
_configUpdateReceivedCallback(data);
}
@@ -363,7 +390,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
return;
}
if(_keypadJsonCommandReceivedReceivedCallback != NULL)
if(_keypadJsonCommandReceivedReceivedCallback)
{
_keypadJsonCommandReceivedReceivedCallback(data);
}
@@ -378,7 +405,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
return;
}
if(_timeControlCommandReceivedReceivedCallback != NULL)
if(_timeControlCommandReceivedReceivedCallback)
{
_timeControlCommandReceivedReceivedCallback(data);
}
@@ -393,7 +420,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
return;
}
if(_authCommandReceivedReceivedCallback != NULL)
if(_authCommandReceivedReceivedCallback)
{
_authCommandReceivedReceivedCallback(data);
}
@@ -1011,9 +1038,8 @@ void NukiNetworkLock::publishKeypad(const std::list<NukiLock::KeypadEntry>& entr
bool topicPerEntry = _preferences->getBool(preference_keypad_topic_per_entry, false);
uint index = 0;
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_lock_path);
baseTopic.concat("/lock");
itoa(_preferences->getUInt(nukiIdPreferenceKey(), 0), uidString, 16);
String baseTopic = _mqttPath;
JsonDocument json;
for(const auto& entry : entries)
@@ -1216,9 +1242,8 @@ void NukiNetworkLock::publishTimeControl(const std::list<NukiLock::TimeControlEn
uint index = 0;
char str[50];
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_lock_path);
baseTopic.concat("/lock");
itoa(_preferences->getUInt(nukiIdPreferenceKey(), 0), uidString, 16);
String baseTopic = _mqttPath;
JsonDocument json;
for(const auto& entry : timeControlEntries)
@@ -1349,9 +1374,8 @@ void NukiNetworkLock::publishAuth(const std::list<NukiLock::AuthorizationEntry>&
uint index = 0;
char str[50];
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_lock_path);
baseTopic.concat("/lock");
itoa(_preferences->getUInt(nukiIdPreferenceKey(), 0), uidString, 16);
String baseTopic = _mqttPath;
JsonDocument json;
for(const auto& entry : authEntries)
@@ -1529,22 +1553,22 @@ void NukiNetworkLock::publishStatusUpdated(const bool statusUpdated)
_nukiPublisher->publishBool(mqtt_topic_lock_status_updated, statusUpdated, true);
}
void NukiNetworkLock::setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char *))
void NukiNetworkLock::setLockActionReceivedCallback(std::function<LockActionResult(const char* value)> lockActionReceivedCallback)
{
_lockActionReceivedCallback = lockActionReceivedCallback;
}
void NukiNetworkLock::setOfficialUpdateReceivedCallback(void (*officialUpdateReceivedCallback)(const char *, const char *))
void NukiNetworkLock::setOfficialUpdateReceivedCallback(std::function<void(const char* path, const char* value)> officialUpdateReceivedCallback)
{
_officialUpdateReceivedCallback = officialUpdateReceivedCallback;
}
void NukiNetworkLock::setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char *))
void NukiNetworkLock::setConfigUpdateReceivedCallback(std::function<void(const char* value)> configUpdateReceivedCallback)
{
_configUpdateReceivedCallback = configUpdateReceivedCallback;
}
void NukiNetworkLock::setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled))
void NukiNetworkLock::setKeypadCommandReceivedCallback(std::function<void(const char* command, const uint& id, const String& name, const String& code, const int& enabled)> keypadCommandReceivedReceivedCallback)
{
if(_disableNonJSON)
{
@@ -1553,21 +1577,47 @@ void NukiNetworkLock::setKeypadCommandReceivedCallback(void (*keypadCommandRecei
_keypadCommandReceivedReceivedCallback = keypadCommandReceivedReceivedCallback;
}
void NukiNetworkLock::setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char *))
void NukiNetworkLock::setKeypadJsonCommandReceivedCallback(std::function<void(const char* value)> keypadJsonCommandReceivedReceivedCallback)
{
_keypadJsonCommandReceivedReceivedCallback = keypadJsonCommandReceivedReceivedCallback;
}
void NukiNetworkLock::setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char *))
void NukiNetworkLock::setTimeControlCommandReceivedCallback(std::function<void(const char* value)> timeControlCommandReceivedReceivedCallback)
{
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback;
}
void NukiNetworkLock::setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char *))
void NukiNetworkLock::setAuthCommandReceivedCallback(std::function<void(const char* value)> authCommandReceivedReceivedCallback)
{
_authCommandReceivedReceivedCallback = authCommandReceivedReceivedCallback;
}
const char* NukiNetworkLock::nukiIdPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_nuki_id_lock_2;
case 3:
return preference_nuki_id_lock_3;
default:
return preference_nuki_id_lock;
}
}
const char* NukiNetworkLock::mqttNamePreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_mqtt_lock_name_2;
case 3:
return preference_mqtt_lock_name_3;
default:
return preference_mqtt_lock_name_1;
}
}
void NukiNetworkLock::buildMqttPath(const char* path, char* outPath)
{
int offset = 0;
@@ -1627,7 +1677,7 @@ const uint8_t NukiNetworkLock::queryCommands()
void NukiNetworkLock::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad)
{
_network->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad);
_network->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, _mqttPath);
}
@@ -1647,4 +1697,4 @@ const char* NukiNetworkLock::getAuthName()
return _authEntries[getAuthId()].c_str();
}
return _authName;
}
}

View File

@@ -15,11 +15,12 @@
#include "NukiOfficial.h"
#include "NukiPublisher.h"
#include "EspMillis.h"
#include <functional>
class NukiNetworkLock : public MqttReceiver
{
public:
explicit NukiNetworkLock(NukiNetwork* network, NukiOfficial* nukiOfficial, Preferences* preferences, char* buffer, size_t bufferSize);
explicit NukiNetworkLock(NukiNetwork* network, NukiOfficial* nukiOfficial, Preferences* preferences, char* buffer, size_t bufferSize, uint8_t lockSlot = 1);
virtual ~NukiNetworkLock();
void initialize();
@@ -48,13 +49,13 @@ public:
void publishAuthCommandResult(const char* result);
void publishOffAction(const int value);
void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value));
void setOfficialUpdateReceivedCallback(void (*officialUpdateReceivedCallback)(const char* path, const char* value));
void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* value));
void setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled));
void setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char* value));
void setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char* value));
void setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char* value));
void setLockActionReceivedCallback(std::function<LockActionResult(const char* value)> lockActionReceivedCallback);
void setOfficialUpdateReceivedCallback(std::function<void(const char* path, const char* value)> officialUpdateReceivedCallback);
void setConfigUpdateReceivedCallback(std::function<void(const char* value)> configUpdateReceivedCallback);
void setKeypadCommandReceivedCallback(std::function<void(const char* command, const uint& id, const String& name, const String& code, const int& enabled)> keypadCommandReceivedReceivedCallback);
void setKeypadJsonCommandReceivedCallback(std::function<void(const char* value)> keypadJsonCommandReceivedReceivedCallback);
void setTimeControlCommandReceivedCallback(std::function<void(const char* value)> timeControlCommandReceivedReceivedCallback);
void setAuthCommandReceivedCallback(std::function<void(const char* value)> authCommandReceivedReceivedCallback);
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override;
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad);
@@ -65,11 +66,11 @@ public:
private:
const bool comparePrefixedPath(const char* fullPath, const char* subPath);
const char* nukiIdPreferenceKey() const;
const char* mqttNamePreferenceKey() const;
void publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry);
void (*_officialUpdateReceivedCallback)(const char* path, const char* value) = nullptr;
String concat(String a, String b);
void buildMqttPath(const char* path, char* outPath);
@@ -102,14 +103,16 @@ private:
char _nukiName[33];
char _authName[33];
uint8_t _lockSlot = 1;
char* _buffer;
size_t _bufferSize;
LockActionResult (*_lockActionReceivedCallback)(const char* value) = nullptr;
void (*_configUpdateReceivedCallback)(const char* value) = nullptr;
void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;
void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr;
void (*_timeControlCommandReceivedReceivedCallback)(const char* value) = nullptr;
void (*_authCommandReceivedReceivedCallback)(const char* value) = nullptr;
};
std::function<LockActionResult(const char* value)> _lockActionReceivedCallback = nullptr;
std::function<void(const char* path, const char* value)> _officialUpdateReceivedCallback = nullptr;
std::function<void(const char* value)> _configUpdateReceivedCallback = nullptr;
std::function<void(const char* command, const uint& id, const String& name, const String& code, const int& enabled)> _keypadCommandReceivedReceivedCallback = nullptr;
std::function<void(const char* value)> _keypadJsonCommandReceivedReceivedCallback = nullptr;
std::function<void(const char* value)> _timeControlCommandReceivedReceivedCallback = nullptr;
std::function<void(const char* value)> _authCommandReceivedReceivedCallback = nullptr;
};

View File

@@ -12,9 +12,7 @@
#include "util/NukiHelper.h"
#include "util/NukiRetryHandler.h"
NukiWrapper* nukiInst = nullptr;
NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences, char* buffer, size_t bufferSize)
NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences, char* buffer, size_t bufferSize, uint8_t lockSlot)
: _deviceName(deviceName),
_deviceId(deviceId),
_bleScanner(scanner),
@@ -23,6 +21,7 @@ NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId,
_nukiOfficial(nukiOfficial),
_gpio(gpio),
_preferences(preferences),
_lockSlot(lockSlot),
_buffer(buffer),
_bufferSize(bufferSize)
{
@@ -30,23 +29,48 @@ NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId,
Log->print("Device id lock: ");
Log->println(_deviceId->get());
nukiInst = this;
memset(&_lastKeyTurnerState, sizeof(NukiLock::KeyTurnerState), 0);
memset(&_lastBatteryReport, sizeof(NukiLock::BatteryReport), 0);
memset(&_batteryReport, sizeof(NukiLock::BatteryReport), 0);
memset(&_keyTurnerState, sizeof(NukiLock::KeyTurnerState), 0);
_keyTurnerState.lockState = NukiLock::LockState::Undefined;
network->setLockActionReceivedCallback(nukiInst->onLockActionReceivedCallback);
network->setOfficialUpdateReceivedCallback(nukiInst->onOfficialUpdateReceivedCallback);
network->setConfigUpdateReceivedCallback(nukiInst->onConfigUpdateReceivedCallback);
network->setKeypadCommandReceivedCallback(nukiInst->onKeypadCommandReceivedCallback);
network->setKeypadJsonCommandReceivedCallback(nukiInst->onKeypadJsonCommandReceivedCallback);
network->setTimeControlCommandReceivedCallback(nukiInst->onTimeControlCommandReceivedCallback);
network->setAuthCommandReceivedCallback(nukiInst->onAuthCommandReceivedCallback);
network->setLockActionReceivedCallback([this](const char* value)
{
return onLockActionReceived(value);
});
network->setOfficialUpdateReceivedCallback([this](const char* topic, const char* value)
{
onOfficialUpdateReceived(topic, value);
});
network->setConfigUpdateReceivedCallback([this](const char* value)
{
onConfigUpdateReceived(value);
});
network->setKeypadCommandReceivedCallback([this](const char* command, const uint& id, const String& name, const String& code, const int& enabled)
{
onKeypadCommandReceived(command, id, name, code, enabled);
});
network->setKeypadJsonCommandReceivedCallback([this](const char* value)
{
onKeypadJsonCommandReceived(value);
});
network->setTimeControlCommandReceivedCallback([this](const char* value)
{
onTimeControlCommandReceived(value);
});
network->setAuthCommandReceivedCallback([this](const char* value)
{
onAuthCommandReceived(value);
});
_gpio->addCallback(NukiWrapper::gpioActionCallback);
if(_lockSlot == 1)
{
_gpio->addCallback([this](const GpioAction& action, const int& pin)
{
onGpioActionReceived(action, pin);
});
}
}
@@ -156,9 +180,9 @@ void NukiWrapper::readSettings()
_intervalKeypad = _preferences->getInt(preference_query_interval_keypad);
_keypadEnabled = _preferences->getBool(preference_keypad_info_enabled);
_publishAuthData = _preferences->getBool(preference_publish_authdata);
_maxKeypadCodeCount = _preferences->getUInt(preference_lock_max_keypad_code_count);
_maxTimeControlEntryCount = _preferences->getUInt(preference_lock_max_timecontrol_entry_count);
_maxAuthEntryCount = _preferences->getUInt(preference_lock_max_auth_entry_count);
_maxKeypadCodeCount = _preferences->getUInt(keypadMaxCountPreferenceKey());
_maxTimeControlEntryCount = _preferences->getUInt(timeControlMaxCountPreferenceKey());
_maxAuthEntryCount = _preferences->getUInt(authMaxCountPreferenceKey());
_restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost);
_nrOfRetries = _preferences->getInt(preference_command_nr_of_retries, 200);
_retryDelay = _preferences->getInt(preference_command_retry_delay);
@@ -487,7 +511,7 @@ void NukiWrapper::lockngounlatch()
const bool NukiWrapper::isPinValid()
{
return _preferences->getInt(preference_lock_pin_status, (int)NukiPinState::NotConfigured) == (int)NukiPinState::Valid;
return _preferences->getInt(pinStatusPreferenceKey(), (int)NukiPinState::NotConfigured) == (int)NukiPinState::Valid;
}
void NukiWrapper::setPin(const uint16_t pin)
@@ -513,15 +537,15 @@ const uint32_t NukiWrapper::getUltraPin()
void NukiWrapper::unpair()
{
_nukiLock.unPairNuki();
_preferences->remove(preference_lock_log_num);
_preferences->remove(lockLogPreferenceKey());
Preferences nukiBlePref;
nukiBlePref.begin("NukiHub", false);
nukiBlePref.begin(_deviceName.c_str(), false);
nukiBlePref.clear();
nukiBlePref.end();
_deviceId->assignNewId();
if (!_forceId)
{
_preferences->remove(preference_nuki_id_lock);
_preferences->remove(nukiIdPreferenceKey());
}
_paired = false;
}
@@ -643,7 +667,7 @@ void NukiWrapper::updateConfig()
if(_nukiConfigValid)
{
if(!_forceId && (_preferences->getUInt(preference_nuki_id_lock, 0) == 0 || _retryConfigCount == 10))
if(!_forceId && (_preferences->getUInt(nukiIdPreferenceKey(), 0) == 0 || _retryConfigCount == 10))
{
char uidString[20];
itoa(_nukiConfig.nukiId, uidString, 16);
@@ -652,10 +676,10 @@ void NukiWrapper::updateConfig()
Log->print(" / ");
Log->print(uidString);
Log->println(")");
_preferences->putUInt(preference_nuki_id_lock, _nukiConfig.nukiId);
_preferences->putUInt(nukiIdPreferenceKey(), _nukiConfig.nukiId);
}
if(_preferences->getUInt(preference_nuki_id_lock, 0) == _nukiConfig.nukiId)
if(_preferences->getUInt(nukiIdPreferenceKey(), 0) == _nukiConfig.nukiId)
{
_hasKeypad = _nukiConfig.hasKeypad == 1 || _nukiConfig.hasKeypadV2 == 1;
_firmwareVersion = std::to_string(_nukiConfig.firmwareVersion[0]) + "." + std::to_string(_nukiConfig.firmwareVersion[1]) + "." + std::to_string(_nukiConfig.firmwareVersion[2]);
@@ -673,7 +697,7 @@ void NukiWrapper::updateConfig()
updateAuth(false);
}
const int pinStatus = _preferences->getInt(preference_lock_pin_status, (int)NukiPinState::NotConfigured);
const int pinStatus = _preferences->getInt(pinStatusPreferenceKey(), (int)NukiPinState::NotConfigured);
Nuki::CmdResult result = (Nuki::CmdResult)-1;
@@ -687,7 +711,7 @@ void NukiWrapper::updateConfig()
Log->println("Nuki Lock PIN is invalid or not set");
if(pinStatus != 2)
{
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::Invalid);
_preferences->putInt(pinStatusPreferenceKey(), (int)NukiPinState::Invalid);
}
}
else
@@ -695,7 +719,7 @@ void NukiWrapper::updateConfig()
Log->println("Nuki Lock PIN is valid");
if(pinStatus != 1)
{
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::Valid);
_preferences->putInt(pinStatusPreferenceKey(), (int)NukiPinState::Valid);
}
}
}
@@ -1024,7 +1048,7 @@ void NukiWrapper::updateKeypad(bool retrieved)
if(keypadCount > _maxKeypadCodeCount)
{
_maxKeypadCodeCount = keypadCount;
_preferences->putUInt(preference_lock_max_keypad_code_count, _maxKeypadCodeCount);
_preferences->putUInt(keypadMaxCountPreferenceKey(), _maxKeypadCodeCount);
}
_network->publishKeypad(entries, _maxKeypadCodeCount);
@@ -1094,7 +1118,7 @@ void NukiWrapper::updateTimeControl(bool retrieved)
if(timeControlCount > _maxTimeControlEntryCount)
{
_maxTimeControlEntryCount = timeControlCount;
_preferences->putUInt(preference_lock_max_timecontrol_entry_count, _maxTimeControlEntryCount);
_preferences->putUInt(timeControlMaxCountPreferenceKey(), _maxTimeControlEntryCount);
}
_network->publishTimeControl(timeControlEntries, _maxTimeControlEntryCount);
@@ -1161,7 +1185,7 @@ void NukiWrapper::updateAuth(bool retrieved)
if(authCount > _maxAuthEntryCount)
{
_maxAuthEntryCount = authCount;
_preferences->putUInt(preference_lock_max_auth_entry_count, _maxAuthEntryCount);
_preferences->putUInt(authMaxCountPreferenceKey(), _maxAuthEntryCount);
}
_network->publishAuth(authEntries, _maxAuthEntryCount);
@@ -1182,11 +1206,6 @@ void NukiWrapper::postponeBleWatchdog()
_disableBleWatchdogTs = espMillis() + 15000;
}
LockActionResult NukiWrapper::onLockActionReceivedCallback(const char *value)
{
return nukiInst->onLockActionReceived(value);
}
LockActionResult NukiWrapper::onLockActionReceived(const char *value)
{
NukiLock::LockAction action;
@@ -1218,7 +1237,7 @@ LockActionResult NukiWrapper::onLockActionReceived(const char *value)
{
if(!_nukiOfficial->getOffConnected())
{
nukiInst->_nextLockAction = action;
_nextLockAction = action;
}
else
{
@@ -1233,7 +1252,7 @@ LockActionResult NukiWrapper::onLockActionReceived(const char *value)
}
else
{
nukiInst->_nextLockAction = action;
_nextLockAction = action;
}
}
return LockActionResult::Success;
@@ -1242,16 +1261,6 @@ LockActionResult NukiWrapper::onLockActionReceived(const char *value)
return LockActionResult::AccessDenied;
}
void NukiWrapper::onOfficialUpdateReceivedCallback(const char *topic, const char *value)
{
nukiInst->onOfficialUpdateReceived(topic, value);
}
void NukiWrapper::onConfigUpdateReceivedCallback(const char *value)
{
nukiInst->onConfigUpdateReceived(value);
}
const bool NukiWrapper::offConnected()
{
return _nukiOfficial->getOffConnected();
@@ -2285,32 +2294,6 @@ void NukiWrapper::onConfigUpdateReceived(const char *value)
return;
}
void NukiWrapper::onKeypadCommandReceivedCallback(const char *command, const uint &id, const String &name, const String &code, const int& enabled)
{
nukiInst->onKeypadCommandReceived(command, id, name, code, enabled);
}
void NukiWrapper::onKeypadJsonCommandReceivedCallback(const char *value)
{
nukiInst->onKeypadJsonCommandReceived(value);
}
void NukiWrapper::onTimeControlCommandReceivedCallback(const char *value)
{
nukiInst->onTimeControlCommandReceived(value);
}
void NukiWrapper::onAuthCommandReceivedCallback(const char *value)
{
nukiInst->onAuthCommandReceived(value);
}
void NukiWrapper::gpioActionCallback(const GpioAction &action, const int& pin)
{
nukiInst->onGpioActionReceived(action, pin);
}
void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
{
switch(action)
@@ -2318,7 +2301,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::Lock:
if(!_nukiOfficial->getOffConnected())
{
nukiInst->lock();
lock();
}
else
{
@@ -2330,7 +2313,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::Unlock:
if(!_nukiOfficial->getOffConnected())
{
nukiInst->unlock();
unlock();
}
else
{
@@ -2342,7 +2325,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::Unlatch:
if(!_nukiOfficial->getOffConnected())
{
nukiInst->unlatch();
unlatch();
}
else
{
@@ -2354,7 +2337,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::LockNgo:
if(!_nukiOfficial->getOffConnected())
{
nukiInst->lockngo();
lockngo();
}
else
{
@@ -2366,7 +2349,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::LockNgoUnlatch:
if(!_nukiOfficial->getOffConnected())
{
nukiInst->lockngounlatch();
lockngounlatch();
}
else
{
@@ -3997,7 +3980,7 @@ void NukiWrapper::notify(Nuki::EventType eventType)
}
else if(eventType == Nuki::EventType::ERROR_BAD_PIN)
{
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::Invalid);
_preferences->putInt(pinStatusPreferenceKey(), (int)NukiPinState::Invalid);
}
else if(eventType == Nuki::EventType::BLE_ERROR_ON_DISCONNECT)
{
@@ -4116,4 +4099,82 @@ void NukiWrapper::updateTime()
{
return _nukiLock.updateTime(nukiTime);
});
}
}
const char* NukiWrapper::nukiIdPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_nuki_id_lock_2;
case 3:
return preference_nuki_id_lock_3;
default:
return preference_nuki_id_lock;
}
}
const char* NukiWrapper::pinStatusPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_pin_status_2;
case 3:
return preference_lock_pin_status_3;
default:
return preference_lock_pin_status;
}
}
const char* NukiWrapper::keypadMaxCountPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_max_keypad_code_count_2;
case 3:
return preference_lock_max_keypad_code_count_3;
default:
return preference_lock_max_keypad_code_count;
}
}
const char* NukiWrapper::timeControlMaxCountPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_max_timecontrol_entry_count_2;
case 3:
return preference_lock_max_timecontrol_entry_count_3;
default:
return preference_lock_max_timecontrol_entry_count;
}
}
const char* NukiWrapper::authMaxCountPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_max_auth_entry_count_2;
case 3:
return preference_lock_max_auth_entry_count_3;
default:
return preference_lock_max_auth_entry_count;
}
}
const char* NukiWrapper::lockLogPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_log_num_2;
case 3:
return preference_lock_log_num_3;
default:
return preference_lock_log_num;
}
}

View File

@@ -15,7 +15,7 @@
class NukiWrapper : public Nuki::SmartlockEventHandler
{
public:
NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences, char* buffer, size_t bufferSize);
NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences, char* buffer, size_t bufferSize, uint8_t lockSlot = 1);
virtual ~NukiWrapper();
void initialize();
@@ -53,14 +53,6 @@ public:
void notify(Nuki::EventType eventType) override;
private:
static LockActionResult onLockActionReceivedCallback(const char* value);
static void onOfficialUpdateReceivedCallback(const char* topic, const char* value);
static void onConfigUpdateReceivedCallback(const char* value);
static void onKeypadCommandReceivedCallback(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
static void onKeypadJsonCommandReceivedCallback(const char* value);
static void onTimeControlCommandReceivedCallback(const char* value);
static void onAuthCommandReceivedCallback(const char* value);
static void gpioActionCallback(const GpioAction& action, const int& pin);
LockActionResult onLockActionReceived(const char* value);
void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
void onOfficialUpdateReceived(const char* topic, const char* value);
@@ -85,6 +77,12 @@ private:
void readConfig();
void readAdvancedConfig();
const char* nukiIdPreferenceKey() const;
const char* pinStatusPreferenceKey() const;
const char* keypadMaxCountPreferenceKey() const;
const char* timeControlMaxCountPreferenceKey() const;
const char* authMaxCountPreferenceKey() const;
const char* lockLogPreferenceKey() const;
std::string _deviceName;
NukiDeviceId* _deviceId = nullptr;
@@ -167,6 +165,7 @@ private:
std::string _firmwareVersion = "";
std::string _hardwareVersion = "";
volatile NukiLock::LockAction _nextLockAction = (NukiLock::LockAction)0xff;
uint8_t _lockSlot = 1;
char* _buffer;
const size_t _bufferSize;

View File

@@ -104,6 +104,8 @@
//REQUIRE SERVICES RELOAD
#define preference_lock_enabled (char*)"lockena"
#define preference_lock_enabled_2 (char*)"lockena2"
#define preference_lock_enabled_3 (char*)"lockena3"
#define preference_opener_enabled (char*)"openerena"
#define preference_debug_connect (char*)"dbgConnect"
#define preference_debug_communication (char*)"dbgCommu"
@@ -145,6 +147,9 @@
#define preference_mqtt_user (char*)"mqttuser"
#define preference_mqtt_password (char*)"mqttpass"
#define preference_mqtt_lock_path (char*)"mqttpath"
#define preference_mqtt_lock_name_1 (char*)"mqttlcknm1"
#define preference_mqtt_lock_name_2 (char*)"mqttlcknm2"
#define preference_mqtt_lock_name_3 (char*)"mqttlcknm3"
#define preference_mqtt_hass_enabled (char*)"hassena"
#define preference_mqtt_hass_discovery (char*)"hassdiscovery"
#define preference_disable_non_json (char*)"disnonjson"
@@ -170,23 +175,37 @@
#define preference_updater_build (char*)"updBuild"
#define preference_updater_date (char*)"updDate"
#define preference_lock_max_auth_entry_count (char*)"maxauth"
#define preference_lock_max_auth_entry_count_2 (char*)"maxauth2"
#define preference_lock_max_auth_entry_count_3 (char*)"maxauth3"
#define preference_opener_max_auth_entry_count (char*)"opmaxauth"
#define preference_started_before (char*)"run"
#define preference_config_version (char*)"confVersion"
#define preference_device_id_lock (char*)"deviceId"
#define preference_device_id_lock_2 (char*)"deviceId2"
#define preference_device_id_lock_3 (char*)"deviceId3"
#define preference_device_id_opener (char*)"deviceIdOp"
#define preference_nuki_id_lock (char*)"nukiId"
#define preference_nuki_id_lock_2 (char*)"nukiId2"
#define preference_nuki_id_lock_3 (char*)"nukiId3"
#define preference_nuki_id_opener (char*)"nukidOp"
#define preference_lock_pin_status (char*)"lockpin"
#define preference_lock_pin_status_2 (char*)"lockpin2"
#define preference_lock_pin_status_3 (char*)"lockpin3"
#define preference_opener_pin_status (char*)"openerpin"
#define preference_lock_max_keypad_code_count (char*)"maxkpad"
#define preference_lock_max_keypad_code_count_2 (char*)"maxkpad2"
#define preference_lock_max_keypad_code_count_3 (char*)"maxkpad3"
#define preference_opener_max_keypad_code_count (char*)"opmaxkpad"
#define preference_lock_max_timecontrol_entry_count (char*)"maxtc"
#define preference_lock_max_timecontrol_entry_count_2 (char*)"maxtc2"
#define preference_lock_max_timecontrol_entry_count_3 (char*)"maxtc3"
#define preference_opener_max_timecontrol_entry_count (char*)"opmaxtc"
#define preference_latest_version (char*)"latest"
#define preference_reset_mqtt_topics (char*)"rstMqtt"
#define preference_nukihub_id (char*)"nukihubId"
#define preference_lock_log_num (char*)"lckLgNm"
#define preference_lock_log_num_2 (char*)"lckLgNm2"
#define preference_lock_log_num_3 (char*)"lckLgNm3"
#define preference_opener_log_num (char*)"opLgNm"
//OBSOLETE
@@ -206,6 +225,8 @@ inline void initPreferences(Preferences* preferences)
preferences->putBool(preference_started_before, true);
preferences->putBool(preference_lock_enabled, true);
preferences->putBool(preference_lock_enabled_2, false);
preferences->putBool(preference_lock_enabled_3, false);
uint32_t aclPrefs[17] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs));
uint32_t basicLockConfigAclPrefs[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -218,6 +239,9 @@ inline void initPreferences(Preferences* preferences)
preferences->putBytes(preference_conf_opener_advanced_acl, (byte*)(&advancedOpenerConfigAclPrefs), sizeof(advancedOpenerConfigAclPrefs));
preferences->putString(preference_mqtt_lock_path, "nukihub");
preferences->putString(preference_mqtt_lock_name_1, "lock");
preferences->putString(preference_mqtt_lock_name_2, "lock2");
preferences->putString(preference_mqtt_lock_name_3, "lock3");
preferences->putString(preference_time_server, "pool.ntp.org");
preferences->putString(preference_cred_duo_host, "");
preferences->putString(preference_cred_duo_ikey, "");
@@ -536,11 +560,11 @@ class DebugPreferences
private:
std::vector<char*> _keys =
{
preference_started_before, preference_config_version, preference_device_id_lock, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_opener,
preference_started_before, preference_config_version, preference_device_id_lock, preference_device_id_lock_2, preference_device_id_lock_3, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_lock_2, preference_nuki_id_lock_3, preference_nuki_id_opener,
preference_mqtt_broker, preference_mqtt_broker_port, preference_mqtt_user, preference_mqtt_password, preference_mqtt_log_enabled, preference_check_updates,
preference_webserver_enabled, preference_lock_enabled, preference_lock_pin_status, preference_mqtt_lock_path, preference_opener_enabled, preference_opener_pin_status,
preference_opener_continuous_mode, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count, preference_update_time, preference_time_server,
preference_lock_max_timecontrol_entry_count, preference_opener_max_timecontrol_entry_count, preference_enable_bootloop_reset,
preference_webserver_enabled, preference_lock_enabled, preference_lock_enabled_2, preference_lock_enabled_3, preference_lock_pin_status, preference_lock_pin_status_2, preference_lock_pin_status_3, preference_mqtt_lock_path, preference_mqtt_lock_name_1, preference_mqtt_lock_name_2, preference_mqtt_lock_name_3, preference_opener_enabled, preference_opener_pin_status,
preference_opener_continuous_mode, preference_lock_max_keypad_code_count, preference_lock_max_keypad_code_count_2, preference_lock_max_keypad_code_count_3, preference_opener_max_keypad_code_count, preference_update_time, preference_time_server,
preference_lock_max_timecontrol_entry_count, preference_lock_max_timecontrol_entry_count_2, preference_lock_max_timecontrol_entry_count_3, preference_opener_max_timecontrol_entry_count, preference_enable_bootloop_reset,
preference_mqtt_hass_discovery, preference_mqtt_hass_cu_url, preference_buffer_size, preference_ip_dhcp_enabled, preference_ip_address,
preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server, preference_network_hardware, preference_http_auth_type, preference_lock_gemini_pin,
preference_rssi_publish_interval, preference_hostname, preference_network_timeout, preference_restart_on_disconnect, preference_hybrid_reboot_on_disconnect,
@@ -554,7 +578,7 @@ private:
preference_update_from_mqtt, preference_show_secrets, preference_ble_tx_power, preference_webserial_enabled, preference_find_best_rssi, preference_lock_gemini_enabled,
preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr, preference_network_custom_irq,
preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi,
preference_network_custom_pwr, preference_network_custom_mdio, preference_lock_max_auth_entry_count, preference_opener_max_auth_entry_count,
preference_network_custom_pwr, preference_network_custom_mdio, preference_lock_max_auth_entry_count, preference_lock_max_auth_entry_count_2, preference_lock_max_auth_entry_count_3, preference_opener_max_auth_entry_count,
preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, preference_wifi_ssid, preference_wifi_pass,
preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_mqtt_hass_enabled, preference_hass_device_discovery, preference_retain_gpio,
preference_debug_connect, preference_debug_communication, preference_debug_readable_data, preference_debug_hex_data, preference_debug_command,
@@ -567,13 +591,13 @@ private:
};
std::vector<char*> _redact =
{
preference_mqtt_user, preference_mqtt_password, preference_cred_user, preference_cred_password, preference_nuki_id_lock, preference_nuki_id_opener, preference_wifi_pass,
preference_mqtt_user, preference_mqtt_password, preference_cred_user, preference_cred_password, preference_nuki_id_lock, preference_nuki_id_lock_2, preference_nuki_id_lock_3, preference_nuki_id_opener, preference_wifi_pass,
preference_lock_gemini_pin, preference_cred_duo_host, preference_cred_duo_ikey, preference_cred_duo_skey, preference_cred_duo_user, preference_bypass_proxy,
preference_totp_secret, preference_bypass_secret, preference_admin_secret
};
std::vector<char*> _boolPrefs =
{
preference_started_before, preference_mqtt_log_enabled, preference_check_updates, preference_lock_enabled, preference_opener_enabled, preference_opener_continuous_mode,
preference_started_before, preference_mqtt_log_enabled, preference_check_updates, preference_lock_enabled, preference_lock_enabled_2, preference_lock_enabled_3, preference_opener_enabled, preference_opener_continuous_mode,
preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry, preference_enable_bootloop_reset, preference_webserver_enabled, preference_update_time,
preference_restart_on_disconnect, preference_keypad_control_enabled, preference_keypad_info_enabled, preference_keypad_publish_code, preference_show_secrets,
preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_register_opener_as_app, preference_ip_dhcp_enabled,
@@ -594,8 +618,8 @@ private:
std::vector<char*> _intPrefs =
{
preference_config_version, preference_mqtt_broker_port, preference_command_retry_delay, preference_query_interval_hybrid_lockstate,
preference_lock_pin_status, preference_opener_pin_status, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count,
preference_lock_max_timecontrol_entry_count, preference_opener_max_timecontrol_entry_count, preference_buffer_size, preference_network_hardware,
preference_lock_pin_status, preference_lock_pin_status_2, preference_lock_pin_status_3, preference_opener_pin_status, preference_lock_max_keypad_code_count, preference_lock_max_keypad_code_count_2, preference_lock_max_keypad_code_count_3, preference_opener_max_keypad_code_count,
preference_lock_max_timecontrol_entry_count, preference_lock_max_timecontrol_entry_count_2, preference_lock_max_timecontrol_entry_count_3, preference_opener_max_timecontrol_entry_count, preference_buffer_size, preference_network_hardware,
preference_rssi_publish_interval, preference_network_timeout, preference_restart_ble_beacon_lost, preference_query_interval_lockstate,
preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_command_nr_of_retries,
preference_task_size_network, preference_task_size_nuki, preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries,
@@ -608,7 +632,7 @@ private:
};
std::vector<char*> _uintPrefs =
{
preference_device_id_lock, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_opener
preference_device_id_lock, preference_device_id_lock_2, preference_device_id_lock_3, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_lock_2, preference_nuki_id_lock_3, preference_nuki_id_opener
};
std::vector<char*> _uint64Prefs =
{
@@ -643,4 +667,4 @@ public:
{
return _uint64Prefs;
}
};
};

View File

@@ -24,6 +24,33 @@ extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
extern bool timeSynced;
static bool isValidMqttLockName(const String& value)
{
if(value.length() < 3 || value.length() > 32)
{
return false;
}
for(size_t i = 0; i < value.length(); i++)
{
const char c = value.charAt(i);
if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-')
{
continue;
}
return false;
}
return true;
}
static String normalizeMqttLockName(String value)
{
value.trim();
value.toLowerCase();
return value;
}
#if defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE) || defined(CONFIG_ESP_WIFI_REMOTE_ENABLED)
#include "esp_hosted.h"
static esp_hosted_coprocessor_fwver_t slave_version_struct = {
@@ -43,6 +70,8 @@ static esp_hosted_coprocessor_fwver_t host_version_struct = {
#include <NetworkClientSecure.h>
#include "ArduinoJson.h"
#include <freertos/queue.h>
extern NukiWrapper* nuki2;
extern NukiWrapper* nuki3;
typedef struct
{
@@ -1341,11 +1370,19 @@ void WebCfgServer::initialize()
}
else if (value == "unpairlock")
{
return processUnpair(request, resp, false);
return processUnpair(request, resp, false, 1);
}
else if (value == "unpairlock2")
{
return processUnpair(request, resp, false, 2);
}
else if (value == "unpairlock3")
{
return processUnpair(request, resp, false, 3);
}
else if (value == "unpairopener")
{
return processUnpair(request, resp, true);
return processUnpair(request, resp, true, 1);
}
else if (value == "factoryreset")
{
@@ -2894,6 +2931,43 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
restartServicesReconnect = true;
}
}
else if(key == "MQTTLOCKNAME1" || key == "MQTTLOCKNAME2" || key == "MQTTLOCKNAME3")
{
value = normalizeMqttLockName(value);
if(!isValidMqttLockName(value))
{
message = "Invalid MQTT lock name. Allowed: a-z, 0-9, _ and -, length 3..32.";
return false;
}
const char* prefKey = preference_mqtt_lock_name_1;
if(key == "MQTTLOCKNAME2")
{
prefKey = preference_mqtt_lock_name_2;
}
else if(key == "MQTTLOCKNAME3")
{
prefKey = preference_mqtt_lock_name_3;
}
if(_preferences->getString(prefKey, "") != value)
{
const String n1 = (key == "MQTTLOCKNAME1") ? value : normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_1, "lock"));
const String n2 = (key == "MQTTLOCKNAME2") ? value : normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_2, "lock2"));
const String n3 = (key == "MQTTLOCKNAME3") ? value : normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_3, "lock3"));
if(n1 == n2 || n1 == n3 || n2 == n3)
{
message = "Duplicate MQTT lock name is not allowed.";
return false;
}
_preferences->putString(prefKey, value);
Log->print("Setting changed: ");
Log->println(key);
restartServicesReconnect = true;
}
}
else if(key == "MQTTCA")
{
if (!SPIFFS.begin(true))
@@ -4669,6 +4743,26 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
restartServicesReconnect = true;
}
}
else if(key == "LOCKENA2")
{
if(_preferences->getBool(preference_lock_enabled_2, false) != (value == "1"))
{
_preferences->putBool(preference_lock_enabled_2, (value == "1"));
Log->print("Setting changed: ");
Log->println(key);
restartServicesReconnect = true;
}
}
else if(key == "LOCKENA3")
{
if(_preferences->getBool(preference_lock_enabled_3, false) != (value == "1"))
{
_preferences->putBool(preference_lock_enabled_3, (value == "1"));
Log->print("Setting changed: ");
Log->println(key);
restartServicesReconnect = true;
}
}
else if(key == "GEMINIENA")
{
if(_preferences->getBool(preference_lock_gemini_enabled, false) != (value == "1"))
@@ -5051,6 +5145,15 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
_preferences->putBool(preference_mfa_reconfigure, true);
}
if(_preferences->getBool(preference_lock_enabled_2, false) || _preferences->getBool(preference_lock_enabled_3, false))
{
if(_preferences->getBool(preference_opener_enabled, false))
{
_preferences->putBool(preference_opener_enabled, false);
restartServicesReconnect = true;
}
}
if(configChanged)
{
message = "Configuration saved, reboot required to apply some settings";
@@ -5106,7 +5209,12 @@ bool WebCfgServer::processImport(PsychicRequest *request, PsychicResponse* resp,
return configChanged;
}
_importExport->importJson(doc);
JsonDocument importResult = _importExport->importJson(doc);
if(!importResult["_import_error"].isNull())
{
message = importResult["_import_error"].as<String>();
return false;
}
configChanged = true;
}
@@ -5427,6 +5535,32 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
response.print("</table>");
response.print("<br><button type=\"submit\">OK</button></form>");
}
if(_preferences->getBool(preference_lock_enabled_2, false))
{
response.print("<br><br><h3>Unpair Nuki Lock Slot 2</h3>");
response.print("<form class=\"adapt\" method=\"post\" action=\"/post\">");
response.print("<input type=\"hidden\" name=\"page\" value=\"unpairlock2\">");
response.print("<table>");
String message = "Type ";
message.concat(_confirmCode);
message.concat(" to confirm unpair");
printInputField(&response, "CONFIRMTOKEN", message.c_str(), "", 10, "");
response.print("</table>");
response.print("<br><button type=\"submit\">OK</button></form>");
}
if(_preferences->getBool(preference_lock_enabled_3, false))
{
response.print("<br><br><h3>Unpair Nuki Lock Slot 3</h3>");
response.print("<form class=\"adapt\" method=\"post\" action=\"/post\">");
response.print("<input type=\"hidden\" name=\"page\" value=\"unpairlock3\">");
response.print("<table>");
String message = "Type ";
message.concat(_confirmCode);
message.concat(" to confirm unpair");
printInputField(&response, "CONFIRMTOKEN", message.c_str(), "", 10, "");
response.print("</table>");
response.print("<br><button type=\"submit\">OK</button></form>");
}
if(_nukiOpener != nullptr)
{
response.print("<br><br><h3>Unpair Nuki Opener</h3>");
@@ -5516,6 +5650,9 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request, PsychicResp
printInputField(&response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, "", false, true);
printInputField(&response, "MQTTPASS", "MQTT Password", "*", 40, "", true, true);
printInputField(&response, "MQTTPATH", "MQTT Nuki Hub Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, "");
printInputField(&response, "MQTTLOCKNAME1", "Lock Slot 1 MQTT Name (a-z,0-9,_,-; 3-32)", _preferences->getString(preference_mqtt_lock_name_1, "lock").c_str(), 32, "");
printInputField(&response, "MQTTLOCKNAME2", "Lock Slot 2 MQTT Name (a-z,0-9,_,-; 3-32)", _preferences->getString(preference_mqtt_lock_name_2, "lock2").c_str(), 32, "");
printInputField(&response, "MQTTLOCKNAME3", "Lock Slot 3 MQTT Name (a-z,0-9,_,-; 3-32)", _preferences->getString(preference_mqtt_lock_name_3, "lock3").c_str(), 32, "");
printCheckBox(&response, "ENHADISC", "Enable Home Assistant auto discovery", _preferences->getBool(preference_mqtt_hass_enabled), "chkHass");
response.print("</table><br>");
@@ -6160,6 +6297,8 @@ esp_err_t WebCfgServer::buildNukiConfigHtml(PsychicRequest *request, PsychicResp
response.print("<h3>Basic Nuki Configuration</h3>");
response.print("<table>");
printCheckBox(&response, "LOCKENA", "Nuki Lock enabled", _preferences->getBool(preference_lock_enabled, true), "");
printCheckBox(&response, "LOCKENA2", "Nuki Lock slot 2 enabled", _preferences->getBool(preference_lock_enabled_2, false), "");
printCheckBox(&response, "LOCKENA3", "Nuki Lock slot 3 enabled", _preferences->getBool(preference_lock_enabled_3, false), "");
printCheckBox(&response, "GEMINIENA", "Nuki Smartlock Ultra/Go/5th gen enabled", _preferences->getBool(preference_lock_gemini_enabled, false), "");
printCheckBox(&response, "OPENA", "Nuki Opener enabled", _preferences->getBool(preference_opener_enabled, false), "");
response.print("</table><br>");
@@ -6865,6 +7004,42 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
}
}
response.print("\n\n------------ NUKI LOCK SLOT 2 ------------");
response.print("\nEnabled: ");
response.print(_preferences->getBool(preference_lock_enabled_2, false) ? "Yes" : "No");
response.print("\nMQTT name: ");
response.print(_preferences->getString(preference_mqtt_lock_name_2, "lock2"));
response.print("\nNuki Hub device ID: ");
response.print(_preferences->getUInt(preference_device_id_lock_2, 0));
response.print("\nNuki device ID: ");
response.print(_preferences->getUInt(preference_nuki_id_lock_2, 0) > 0 ? "***" : "Not set");
response.print("\nValid PIN set: ");
response.print(pinStateToString((NukiPinState)_preferences->getInt(preference_lock_pin_status_2, (int)NukiPinState::NotConfigured)));
response.print("\nKeypad highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_keypad_code_count_2, 0));
response.print("\nTimecontrol highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_timecontrol_entry_count_2, 0));
response.print("\nAuthorizations highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_auth_entry_count_2, 0));
response.print("\n\n------------ NUKI LOCK SLOT 3 ------------");
response.print("\nEnabled: ");
response.print(_preferences->getBool(preference_lock_enabled_3, false) ? "Yes" : "No");
response.print("\nMQTT name: ");
response.print(_preferences->getString(preference_mqtt_lock_name_3, "lock3"));
response.print("\nNuki Hub device ID: ");
response.print(_preferences->getUInt(preference_device_id_lock_3, 0));
response.print("\nNuki device ID: ");
response.print(_preferences->getUInt(preference_nuki_id_lock_3, 0) > 0 ? "***" : "Not set");
response.print("\nValid PIN set: ");
response.print(pinStateToString((NukiPinState)_preferences->getInt(preference_lock_pin_status_3, (int)NukiPinState::NotConfigured)));
response.print("\nKeypad highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_keypad_code_count_3, 0));
response.print("\nTimecontrol highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_timecontrol_entry_count_3, 0));
response.print("\nAuthorizations highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_auth_entry_count_3, 0));
response.print("\n\n------------ NUKI OPENER ------------");
if(_nukiOpener == nullptr || !_preferences->getBool(preference_opener_enabled, false))
{
@@ -7050,7 +7225,7 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
return response.endSend();
}
esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener)
esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener, uint8_t lockSlot)
{
String value = "";
if(request->hasParam("CONFIRMTOKEN"))
@@ -7067,12 +7242,74 @@ esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, PsychicResponse*
return buildConfirmHtml(request, resp, "Confirm code is invalid.", 3, true);
}
esp_err_t res = buildConfirmHtml(request, resp, opener ? "Unpairing Nuki Opener." : "Unpairing Nuki Lock.", 3, true);
if(!opener && _nuki != nullptr)
String confirmMsg = opener ? "Unpairing Nuki Opener." : "Unpairing Nuki Lock.";
if(!opener && lockSlot == 2)
{
_nuki->unpair();
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::NotConfigured);
confirmMsg = "Unpairing Nuki Lock Slot 2.";
}
else if(!opener && lockSlot == 3)
{
confirmMsg = "Unpairing Nuki Lock Slot 3.";
}
esp_err_t res = buildConfirmHtml(request, resp, confirmMsg, 3, true);
if(!opener)
{
auto clearLockSlot = [&](const uint8_t slot)
{
const char* prefPinKey = preference_lock_pin_status;
const char* prefNukiIdKey = preference_nuki_id_lock;
const char* prefDeviceIdKey = preference_device_id_lock;
const char* prefLogKey = preference_lock_log_num;
const char* bleNs = "NukiHub";
if(slot == 2)
{
prefPinKey = preference_lock_pin_status_2;
prefNukiIdKey = preference_nuki_id_lock_2;
prefDeviceIdKey = preference_device_id_lock_2;
prefLogKey = preference_lock_log_num_2;
bleNs = "NukiHub2";
}
else if(slot == 3)
{
prefPinKey = preference_lock_pin_status_3;
prefNukiIdKey = preference_nuki_id_lock_3;
prefDeviceIdKey = preference_device_id_lock_3;
prefLogKey = preference_lock_log_num_3;
bleNs = "NukiHub3";
}
Preferences nukiBlePref;
nukiBlePref.begin(bleNs, false);
nukiBlePref.clear();
nukiBlePref.end();
_preferences->remove(prefLogKey);
_preferences->remove(prefNukiIdKey);
_preferences->putInt(prefPinKey, (int)NukiPinState::NotConfigured);
_preferences->putUInt(prefDeviceIdKey, esp_random());
};
if(lockSlot == 1 && _nuki != nullptr)
{
_nuki->unpair();
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::NotConfigured);
}
else if(lockSlot == 2 && nuki2 != nullptr)
{
nuki2->unpair();
_preferences->putInt(preference_lock_pin_status_2, (int)NukiPinState::NotConfigured);
}
else if(lockSlot == 3 && nuki3 != nullptr)
{
nuki3->unpair();
_preferences->putInt(preference_lock_pin_status_3, (int)NukiPinState::NotConfigured);
}
else
{
clearLockSlot(lockSlot);
}
}
if(opener && _nukiOpener != nullptr)
{
@@ -7215,6 +7452,14 @@ esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request, PsychicResp
{
_nuki->unpair();
}
if(nuki2 != nullptr)
{
nuki2->unpair();
}
if(nuki3 != nullptr)
{
nuki3->unpair();
}
if(_nukiOpener != nullptr)
{
_nukiOpener->unpair();

View File

@@ -75,7 +75,7 @@ private:
#endif
esp_err_t buildInfoHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildCustomNetworkConfigHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener);
esp_err_t processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener, uint8_t lockSlot = 1);
esp_err_t processUpdate(PsychicRequest *request, PsychicResponse* resp);
esp_err_t processFactoryReset(PsychicRequest *request, PsychicResponse* resp);
void printTextarea(PsychicStreamResponse *response, const char *token, const char *description, const char *value, const size_t& maxLength, const bool& enabled = true, const bool& showLengthRestriction = false);

View File

@@ -52,22 +52,36 @@ bool nuki_hub_https_server_enabled = false;
#include "ImportExport.h"
NukiNetworkLock* networkLock = nullptr;
NukiNetworkLock* networkLock2 = nullptr;
NukiNetworkLock* networkLock3 = nullptr;
NukiNetworkOpener* networkOpener = nullptr;
BleScanner::Scanner* bleScanner = nullptr;
NukiWrapper* nuki = nullptr;
NukiWrapper* nuki2 = nullptr;
NukiWrapper* nuki3 = nullptr;
NukiOfficial* nukiOfficial = nullptr;
NukiOfficial* nukiOfficial2 = nullptr;
NukiOfficial* nukiOfficial3 = nullptr;
NukiOpenerWrapper* nukiOpener = nullptr;
NukiDeviceId* deviceIdLock = nullptr;
NukiDeviceId* deviceIdLock2 = nullptr;
NukiDeviceId* deviceIdLock3 = nullptr;
NukiDeviceId* deviceIdOpener = nullptr;
Gpio* gpio = nullptr;
SerialReader* serialReader = nullptr;
bool bleDone = false;
bool lockEnabled = false;
bool lockEnabled2 = false;
bool lockEnabled3 = false;
bool openerEnabled = false;
bool wifiConnected = false;
bool rebootLock = false;
bool rebootLock2 = false;
bool rebootLock3 = false;
uint8_t lockRestartControllerCount = 0;
uint8_t lockRestartControllerCount2 = 0;
uint8_t lockRestartControllerCount3 = 0;
uint8_t openerRestartControllerCount = 0;
char16_t buffer_size = CHAR_BUFFER_SIZE;
@@ -703,14 +717,33 @@ void startNuki(bool lock)
{
if (lock)
{
nukiOfficial = new NukiOfficial(preferences);
networkLock = new NukiNetworkLock(network, nukiOfficial, preferences, CharBuffer::get(), buffer_size);
if(!disableNetwork)
if(lockEnabled)
{
networkLock->initialize();
nukiOfficial = new NukiOfficial(preferences);
networkLock = new NukiNetworkLock(network, nukiOfficial, preferences, CharBuffer::get(), buffer_size, 1);
if(!disableNetwork)
{
networkLock->initialize();
}
}
if(lockEnabled2)
{
nukiOfficial2 = new NukiOfficial(preferences);
networkLock2 = new NukiNetworkLock(network, nukiOfficial2, preferences, CharBuffer::get(), buffer_size, 2);
if(!disableNetwork)
{
networkLock2->initialize();
}
}
if(lockEnabled3)
{
nukiOfficial3 = new NukiOfficial(preferences);
networkLock3 = new NukiNetworkLock(network, nukiOfficial3, preferences, CharBuffer::get(), buffer_size, 3);
if(!disableNetwork)
{
networkLock3->initialize();
}
}
lockStarted = true;
}
else
@@ -730,7 +763,14 @@ void restartServices(bool reconnect)
{
bleDone = false;
lockEnabled = preferences->getBool(preference_lock_enabled);
lockEnabled2 = preferences->getBool(preference_lock_enabled_2, false);
lockEnabled3 = preferences->getBool(preference_lock_enabled_3, false);
openerEnabled = preferences->getBool(preference_opener_enabled);
if(lockEnabled2 || lockEnabled3)
{
openerEnabled = false;
}
importExport->readSettings();
network->readSettings();
gpio->setPins();
@@ -775,13 +815,25 @@ void restartServices(bool reconnect)
Log->println("Deleting nuki");
delete nuki;
nuki = nullptr;
delete nuki2;
nuki2 = nullptr;
delete nuki3;
nuki3 = nullptr;
if (reconnect)
{
lockStarted = false;
delete networkLock;
networkLock = nullptr;
delete networkLock2;
networkLock2 = nullptr;
delete networkLock3;
networkLock3 = nullptr;
delete nukiOfficial;
nukiOfficial = nullptr;
delete nukiOfficial2;
nukiOfficial2 = nullptr;
delete nukiOfficial3;
nukiOfficial3 = nullptr;
}
Log->println("Deleting nuki done");
}
@@ -830,7 +882,7 @@ void restartServices(bool reconnect)
}
vTaskDelay(2000 / portTICK_PERIOD_MS);
if(lockEnabled || openerEnabled)
if(lockEnabled || lockEnabled2 || lockEnabled3 || openerEnabled)
{
#if defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE) || defined(CONFIG_ESP_WIFI_REMOTE_ENABLED)
hostedInitBLE();
@@ -843,7 +895,7 @@ void restartServices(bool reconnect)
Log->println("Restarting BLE Scanner done");
}
if(lockEnabled)
if(lockEnabled || lockEnabled2 || lockEnabled3)
{
Log->println("Restarting Nuki lock");
@@ -852,9 +904,24 @@ void restartServices(bool reconnect)
startNuki(true);
}
nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences, CharBuffer::get(), buffer_size);
nuki->initialize();
bleScanner->whitelist(nuki->getBleAddress());
if(lockEnabled && networkLock != nullptr && nukiOfficial != nullptr)
{
nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences, CharBuffer::get(), buffer_size, 1);
nuki->initialize();
bleScanner->whitelist(nuki->getBleAddress());
}
if(lockEnabled2 && networkLock2 != nullptr && nukiOfficial2 != nullptr)
{
nuki2 = new NukiWrapper("NukiHub2", deviceIdLock2, bleScanner, networkLock2, nukiOfficial2, gpio, preferences, CharBuffer::get(), buffer_size, 2);
nuki2->initialize();
bleScanner->whitelist(nuki2->getBleAddress());
}
if(lockEnabled3 && networkLock3 != nullptr && nukiOfficial3 != nullptr)
{
nuki3 = new NukiWrapper("NukiHub3", deviceIdLock3, bleScanner, networkLock3, nukiOfficial3, gpio, preferences, CharBuffer::get(), buffer_size, 3);
nuki3->initialize();
bleScanner->whitelist(nuki3->getBleAddress());
}
Log->println("Restarting Nuki lock done");
}
@@ -1014,7 +1081,18 @@ void networkTask(void *pvParameters)
if(connected && lockStarted)
{
rebootLock = networkLock->update();
if(networkLock != nullptr)
{
rebootLock = networkLock->update();
}
if(networkLock2 != nullptr)
{
rebootLock2 = networkLock2->update();
}
if(networkLock3 != nullptr)
{
rebootLock3 = networkLock3->update();
}
if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset();
@@ -1148,7 +1226,10 @@ void nukiTask(void *pvParameters)
vTaskDelay(20 / portTICK_PERIOD_MS);
}
bool needsPairing = (lockStarted && !nuki->isPaired()) || (openerStarted && !nukiOpener->isPaired());
bool needsPairing = ((lockStarted && nuki != nullptr && !nuki->isPaired()) ||
(lockStarted && nuki2 != nullptr && !nuki2->isPaired()) ||
(lockStarted && nuki3 != nullptr && !nuki3->isPaired()) ||
(openerStarted && !nukiOpener->isPaired()));
if (needsPairing)
{
@@ -1161,10 +1242,18 @@ void nukiTask(void *pvParameters)
else if (!whiteListed)
{
whiteListed = true;
if(lockEnabled)
if(lockEnabled && nuki != nullptr)
{
bleScanner->whitelist(nuki->getBleAddress());
}
if(lockEnabled2 && nuki2 != nullptr)
{
bleScanner->whitelist(nuki2->getBleAddress());
}
if(lockEnabled3 && nuki3 != nullptr)
{
bleScanner->whitelist(nuki3->getBleAddress());
}
if(openerEnabled)
{
bleScanner->whitelist(nukiOpener->getBleAddress());
@@ -1173,35 +1262,104 @@ void nukiTask(void *pvParameters)
if(lockStarted)
{
if (nuki->restartController() > 0)
if(nuki != nullptr)
{
if (lockRestartControllerCount > 3)
if (nuki->restartController() > 0)
{
if (nuki->restartController() == 1)
if (lockRestartControllerCount > 3)
{
restartEsp(RestartReason::BLEError);
if (nuki->restartController() == 1)
{
restartEsp(RestartReason::BLEError);
}
else if (nuki->restartController() == 2)
{
restartEsp(RestartReason::BLEBeaconWatchdog);
}
}
else if (nuki->restartController() == 2)
else
{
restartEsp(RestartReason::BLEBeaconWatchdog);
lockRestartControllerCount += 1;
restartServices(false);
continue;
}
}
else
{
lockRestartControllerCount += 1;
restartServices(false);
continue;
if (lockRestartControllerCount > 0 && nuki->hasConnected())
{
lockRestartControllerCount = 0;
}
nuki->update(rebootLock);
rebootLock = false;
}
}
else
if(nuki2 != nullptr)
{
if (lockRestartControllerCount > 0 && nuki->hasConnected())
if (nuki2->restartController() > 0)
{
lockRestartControllerCount = 0;
if (lockRestartControllerCount2 > 3)
{
if (nuki2->restartController() == 1)
{
restartEsp(RestartReason::BLEError);
}
else if (nuki2->restartController() == 2)
{
restartEsp(RestartReason::BLEBeaconWatchdog);
}
}
else
{
lockRestartControllerCount2 += 1;
restartServices(false);
continue;
}
}
else
{
if (lockRestartControllerCount2 > 0 && nuki2->hasConnected())
{
lockRestartControllerCount2 = 0;
}
nuki->update(rebootLock);
rebootLock = false;
nuki2->update(rebootLock2);
rebootLock2 = false;
}
}
if(nuki3 != nullptr)
{
if (nuki3->restartController() > 0)
{
if (lockRestartControllerCount3 > 3)
{
if (nuki3->restartController() == 1)
{
restartEsp(RestartReason::BLEError);
}
else if (nuki3->restartController() == 2)
{
restartEsp(RestartReason::BLEBeaconWatchdog);
}
}
else
{
lockRestartControllerCount3 += 1;
restartServices(false);
continue;
}
}
else
{
if (lockRestartControllerCount3 > 0 && nuki3->hasConnected())
{
lockRestartControllerCount3 = 0;
}
nuki3->update(rebootLock3);
rebootLock3 = false;
}
}
}
if(openerStarted)
@@ -1400,7 +1558,7 @@ void setupTasks(bool ota)
xTaskCreatePinnedToCore(networkTask, "ntw", preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), NULL, 3, &networkTaskHandle, (espCores > 1) ? 1 : 0);
}
#ifndef NUKI_HUB_UPDATER
if(!network->isApOpen() && (lockEnabled || openerEnabled))
if(!network->isApOpen() && (lockEnabled || lockEnabled2 || lockEnabled3 || openerEnabled))
{
xTaskCreatePinnedToCore(nukiTask, "nuki", preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), NULL, 2, &nukiTaskHandle, 0);
}
@@ -1702,6 +1860,8 @@ void setup()
uint32_t devIdOpener = preferences->getUInt(preference_device_id_opener);
deviceIdLock = new NukiDeviceId(preferences, preference_device_id_lock);
deviceIdLock2 = new NukiDeviceId(preferences, preference_device_id_lock_2);
deviceIdLock3 = new NukiDeviceId(preferences, preference_device_id_lock_3);
deviceIdOpener = new NukiDeviceId(preferences, preference_device_id_opener);
if(deviceIdLock->get() != 0 && devIdOpener == 0)
@@ -1723,20 +1883,29 @@ void setup()
network->initialize();
lockEnabled = preferences->getBool(preference_lock_enabled);
lockEnabled2 = preferences->getBool(preference_lock_enabled_2, false);
lockEnabled3 = preferences->getBool(preference_lock_enabled_3, false);
openerEnabled = preferences->getBool(preference_opener_enabled);
if(lockEnabled2 || lockEnabled3)
{
openerEnabled = false;
}
if(network->isApOpen())
{
forceEnableWebServer = true;
doOta = false;
lockEnabled = false;
lockEnabled2 = false;
lockEnabled3 = false;
openerEnabled = false;
#ifndef NUKI_HUB_UPDATER
serialReader = new SerialReader(importExport, network);
#endif
}
if(lockEnabled || openerEnabled)
if(lockEnabled || lockEnabled2 || lockEnabled3 || openerEnabled)
{
bleScanner = new BleScanner::Scanner();
// Scan interval and window according to Nuki recommendations:
@@ -1746,13 +1915,27 @@ void setup()
bleScannerStarted = true;
}
Log->println(lockEnabled ? F("Nuki Lock enabled") : F("Nuki Lock disabled"));
if(lockEnabled)
const bool anyLockEnabled = lockEnabled || lockEnabled2 || lockEnabled3;
Log->println(anyLockEnabled ? F("Nuki Lock enabled") : F("Nuki Lock disabled"));
if(anyLockEnabled)
{
startNuki(true);
nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences, CharBuffer::get(), buffer_size);
nuki->initialize();
if(lockEnabled && networkLock != nullptr && nukiOfficial != nullptr)
{
nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences, CharBuffer::get(), buffer_size, 1);
nuki->initialize();
}
if(lockEnabled2 && networkLock2 != nullptr && nukiOfficial2 != nullptr)
{
nuki2 = new NukiWrapper("NukiHub2", deviceIdLock2, bleScanner, networkLock2, nukiOfficial2, gpio, preferences, CharBuffer::get(), buffer_size, 2);
nuki2->initialize();
}
if(lockEnabled3 && networkLock3 != nullptr && nukiOfficial3 != nullptr)
{
nuki3 = new NukiWrapper("NukiHub3", deviceIdLock3, bleScanner, networkLock3, nukiOfficial3, gpio, preferences, CharBuffer::get(), buffer_size, 3);
nuki3->initialize();
}
}
Log->println(openerEnabled ? F("Nuki Opener enabled") : F("Nuki Opener disabled"));

View File

@@ -1,363 +1,344 @@
0x2f, 0x2a, 0x0d, 0x0a, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x68,
0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x73, 0x63, 0x61,
0x70, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x0d, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x6e, 0x2e, 0x6a, 0x73,
0x64, 0x65, 0x6c, 0x69, 0x76, 0x72, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x70, 0x6d, 0x2f, 0x40,
0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x64, 0x65, 0x76, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x63,
0x73, 0x73, 0x40, 0x31, 0x2e, 0x31, 0x2e, 0x32, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x6d, 0x69, 0x6e,
0x2e, 0x63, 0x73, 0x73, 0x0d, 0x0a, 0x2a, 0x2f, 0x0d, 0x0a, 0x0d, 0x0a, 0x2f, 0x2a, 0x0d, 0x0a,
0x20, 0x2a, 0x20, 0x55, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f,
0x6d, 0x70, 0x61, 0x63, 0x74, 0x20, 0x2f, 0x20, 0x6d, 0x69, 0x6e, 0x69, 0x66, 0x79, 0x20, 0x74,
0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6e,
0x79, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x20,
0x6f, 0x6e, 0x65, 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20,
0x74, 0x6f, 0x20, 0x73, 0x72, 0x63, 0x2f, 0x57, 0x65, 0x62, 0x43, 0x66, 0x67, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x68, 0x20, 0x61,
0x73, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x63, 0x73, 0x73, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x54,
0x4f, 0x44, 0x4f, 0x3a, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68,
0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x20, 0x75, 0x70, 0x6f, 0x6e, 0x20,
0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3a, 0x29, 0x0d, 0x0a, 0x2a, 0x2f, 0x0d,
0x0a, 0x0d, 0x0a, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e, 0x73, 0x3a, 0x20,
0x27, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x27, 0x2c, 0x2d, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2d, 0x73,
0x79, 0x73, 0x74, 0x65, 0x6d, 0x2c, 0x42, 0x6c, 0x69, 0x6e, 0x6b, 0x4d, 0x61, 0x63, 0x53, 0x79,
0x73, 0x74, 0x65, 0x6d, 0x46, 0x6f, 0x6e, 0x74, 0x2c, 0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20,
0x55, 0x49, 0x27, 0x2c, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x6f, 0x2c, 0x4f, 0x78, 0x79, 0x67, 0x65,
0x6e, 0x2c, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2c, 0x43, 0x61, 0x6e, 0x74, 0x61, 0x72, 0x65,
0x6c, 0x6c, 0x2c, 0x27, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x27, 0x2c, 0x27,
0x48, 0x65, 0x6c, 0x76, 0x65, 0x74, 0x69, 0x63, 0x61, 0x20, 0x4e, 0x65, 0x75, 0x65, 0x27, 0x2c,
0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x2c, 0x27, 0x41, 0x70, 0x70, 0x6c,
0x65, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x45, 0x6d, 0x6f, 0x6a, 0x69, 0x27, 0x2c, 0x27,
0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x45, 0x6d, 0x6f, 0x6a, 0x69, 0x27, 0x2c,
0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c,
0x27, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e,
0x74, 0x2d, 0x6d, 0x6f, 0x6e, 0x6f, 0x3a, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x61, 0x73,
0x2c, 0x6d, 0x6f, 0x6e, 0x61, 0x63, 0x6f, 0x2c, 0x27, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x20,
0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x4c, 0x69, 0x62, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72,
0x20, 0x4e, 0x65, 0x77, 0x27, 0x2c, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x2c, 0x6d, 0x6f,
0x6e, 0x6f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b, 0x0d, 0x0a,
0x2f, 0x2a, 0x0a, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x68, 0x74,
0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x73, 0x63, 0x61, 0x70,
0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, 0x20,
0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x6e, 0x2e, 0x6a, 0x73, 0x64, 0x65,
0x6c, 0x69, 0x76, 0x72, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x70, 0x6d, 0x2f, 0x40, 0x65, 0x78,
0x61, 0x6d, 0x70, 0x6c, 0x65, 0x64, 0x65, 0x76, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x63, 0x73, 0x73,
0x40, 0x31, 0x2e, 0x31, 0x2e, 0x32, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x6d, 0x69, 0x6e, 0x2e, 0x63,
0x73, 0x73, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x55, 0x73, 0x61,
0x67, 0x65, 0x3a, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x20, 0x2f,
0x20, 0x6d, 0x69, 0x6e, 0x69, 0x66, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x64,
0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x0a,
0x20, 0x2a, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x2d, 0x6c, 0x69, 0x6e, 0x65,
0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x72, 0x63, 0x2f, 0x57,
0x65, 0x62, 0x43, 0x66, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x73, 0x74,
0x61, 0x6e, 0x74, 0x73, 0x2e, 0x68, 0x20, 0x61, 0x73, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x63,
0x73, 0x73, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x4f, 0x44, 0x4f, 0x3a, 0x20, 0x61, 0x75, 0x74, 0x6f,
0x6d, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
0x73, 0x20, 0x75, 0x70, 0x6f, 0x6e, 0x20, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x20,
0x3a, 0x29, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x7b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e,
0x73, 0x3a, 0x20, 0x27, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x27, 0x2c, 0x2d, 0x61, 0x70, 0x70, 0x6c,
0x65, 0x2d, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2c, 0x42, 0x6c, 0x69, 0x6e, 0x6b, 0x4d, 0x61,
0x63, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x46, 0x6f, 0x6e, 0x74, 0x2c, 0x27, 0x53, 0x65, 0x67,
0x6f, 0x65, 0x20, 0x55, 0x49, 0x27, 0x2c, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x6f, 0x2c, 0x4f, 0x78,
0x79, 0x67, 0x65, 0x6e, 0x2c, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2c, 0x43, 0x61, 0x6e, 0x74,
0x61, 0x72, 0x65, 0x6c, 0x6c, 0x2c, 0x27, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x53, 0x61, 0x6e, 0x73,
0x27, 0x2c, 0x27, 0x48, 0x65, 0x6c, 0x76, 0x65, 0x74, 0x69, 0x63, 0x61, 0x20, 0x4e, 0x65, 0x75,
0x65, 0x27, 0x2c, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x2c, 0x27, 0x41,
0x70, 0x70, 0x6c, 0x65, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x45, 0x6d, 0x6f, 0x6a, 0x69,
0x27, 0x2c, 0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x45, 0x6d, 0x6f, 0x6a,
0x69, 0x27, 0x2c, 0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x53, 0x79, 0x6d,
0x62, 0x6f, 0x6c, 0x27, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66,
0x6f, 0x6e, 0x74, 0x2d, 0x6d, 0x6f, 0x6e, 0x6f, 0x3a, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c,
0x61, 0x73, 0x2c, 0x6d, 0x6f, 0x6e, 0x61, 0x63, 0x6f, 0x2c, 0x27, 0x55, 0x62, 0x75, 0x6e, 0x74,
0x75, 0x20, 0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x4c, 0x69, 0x62, 0x65, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x43, 0x6f, 0x75, 0x72, 0x69,
0x65, 0x72, 0x20, 0x4e, 0x65, 0x77, 0x27, 0x2c, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x2c,
0x6d, 0x6f, 0x6e, 0x6f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x3a, 0x20, 0x23,
0x31, 0x61, 0x31, 0x61, 0x31, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e,
0x63, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x66,
0x36, 0x66, 0x38, 0x66, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x62, 0x67, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x65, 0x35, 0x65, 0x37, 0x65, 0x62, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20,
0x23, 0x30, 0x30, 0x37, 0x30, 0x66, 0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x30, 0x33, 0x36, 0x36, 0x64, 0x36,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74,
0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x66, 0x66, 0x65, 0x31,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74,
0x78, 0x3a, 0x20, 0x23, 0x30, 0x63, 0x34, 0x30, 0x34, 0x37, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d,
0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x28, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x73, 0x2d,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2d, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x3a, 0x20, 0x64, 0x61,
0x72, 0x6b, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3a, 0x72, 0x6f, 0x6f, 0x74,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x3a, 0x20,
0x23, 0x65, 0x65, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b, 0x0d,
0x31, 0x61, 0x31, 0x61, 0x31, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x66, 0x36, 0x66,
0x38, 0x66, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x33, 0x3a, 0x20, 0x23, 0x65, 0x35, 0x65, 0x37, 0x65, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x37,
0x30, 0x66, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b,
0x2d, 0x32, 0x3a, 0x20, 0x23, 0x30, 0x33, 0x36, 0x36, 0x64, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x66, 0x66,
0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31,
0x3a, 0x20, 0x23, 0x37, 0x39, 0x66, 0x66, 0x65, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x30, 0x63, 0x34, 0x30,
0x34, 0x37, 0x0a, 0x7d, 0x0a, 0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x28, 0x70, 0x72, 0x65,
0x66, 0x65, 0x72, 0x73, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2d, 0x73, 0x63, 0x68, 0x65, 0x6d,
0x65, 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3a,
0x72, 0x6f, 0x6f, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32,
0x3a, 0x20, 0x23, 0x65, 0x65, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x32, 0x3a, 0x20, 0x23, 0x31, 0x31, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x32,
0x32, 0x32, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e,
0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x33, 0x32, 0x39, 0x31, 0x66, 0x66, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c,
0x6b, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x37, 0x30, 0x66, 0x33, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74,
0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39,
0x32, 0x38, 0x63, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2a, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x30, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x69, 0x6d, 0x67, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x2c, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x70, 0x2c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2c,
0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x75, 0x6c, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x2d, 0x32, 0x3a, 0x20, 0x23, 0x31, 0x31, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x32, 0x32,
0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x33, 0x32, 0x39, 0x31, 0x66, 0x66, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32,
0x3a, 0x20, 0x23, 0x30, 0x30, 0x37, 0x30, 0x66, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23,
0x66, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e,
0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x32, 0x38, 0x63, 0x61, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63,
0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a,
0x7d, 0x0a, 0x0a, 0x2a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69,
0x6e, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e,
0x67, 0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6d, 0x67, 0x2c, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x2c, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x70, 0x2c, 0x74, 0x61, 0x62, 0x6c, 0x65,
0x2c, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x75, 0x6c, 0x20, 0x7b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x62, 0x75, 0x74,
0x74, 0x6f, 0x6e, 0x2c, 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x73,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e,
0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e, 0x73, 0x29, 0x0d, 0x0a, 0x7d,
0x0d, 0x0a, 0x0d, 0x0a, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x30, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20,
0x37, 0x35, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64,
0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x36,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f,
0x77, 0x2d, 0x78, 0x3a, 0x20, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3a, 0x20, 0x6e, 0x6f,
0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x66,
0x6c, 0x6f, 0x77, 0x2d, 0x77, 0x72, 0x61, 0x70, 0x3a, 0x20, 0x61, 0x6e, 0x79, 0x77, 0x68, 0x65,
0x72, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f,
0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e,
0x2c, 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x73, 0x65, 0x6c, 0x65,
0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61,
0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66,
0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e, 0x73, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x62, 0x6f, 0x64,
0x79, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20,
0x30, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x37, 0x35, 0x30, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x72, 0x65, 0x6d, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69,
0x75, 0x73, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65,
0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2d, 0x78, 0x3a, 0x20, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3a,
0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65,
0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2d, 0x77, 0x72, 0x61, 0x70, 0x3a, 0x20, 0x61, 0x6e, 0x79, 0x77,
0x68, 0x65, 0x72, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72,
0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62,
0x67, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a,
0x20, 0x31, 0x2e, 0x30, 0x33, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c,
0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x0d,
0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x3a, 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f,
0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63,
0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x29,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x31, 0x2c, 0x68, 0x32, 0x2c, 0x68, 0x33, 0x2c,
0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68, 0x36, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x2e, 0x38,
0x37, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x31, 0x2c, 0x68,
0x32, 0x2c, 0x68, 0x33, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31,
0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d,
0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x32, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a,
0x20, 0x38, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65,
0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f,
0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d,
0x32, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68,
0x36, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d,
0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x2e, 0x33, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d,
0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x31, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x2e, 0x32, 0x35, 0x72, 0x65, 0x6d,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x32, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x38, 0x35,
0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x33, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31,
0x2e, 0x35, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x34, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65,
0x3a, 0x20, 0x31, 0x2e, 0x32, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x68, 0x35, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73,
0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x68, 0x36, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73,
0x69, 0x7a, 0x65, 0x3a, 0x20, 0x2e, 0x38, 0x37, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31,
0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x61, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61,
0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x29, 0x20, 0x21, 0x69, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x61,
0x62, 0x62, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f,
0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x61, 0x62,
0x62, 0x72, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0d, 0x0a, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x61, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2c, 0x62, 0x75, 0x74, 0x74,
0x6f, 0x6e, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75,
0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65,
0x3d, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79,
0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65,
0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x36, 0x70, 0x78,
0x20, 0x31, 0x32, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f,
0x77, 0x72, 0x61, 0x70, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74,
0x78, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a,
0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d,
0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x62, 0x6f,
0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x61, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62,
0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61,
0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65,
0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65,
0x73, 0x65, 0x74, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74,
0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75,
0x6c, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
0x3a, 0x20, 0x2e, 0x35, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f,
0x72, 0x3a, 0x20, 0x6e, 0x6f, 0x74, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x0d, 0x0a,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2e, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x63,
0x75, 0x73, 0x2c, 0x2e, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72,
0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x62, 0x75,
0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x66, 0x6f,
0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62,
0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x66,
0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d,
0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a,
0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65,
0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64,
0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x29,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, 0x6c, 0x61,
0x70, 0x73, 0x65, 0x3a, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d,
0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x64, 0x2c, 0x74, 0x68, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73,
0x6f, 0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x33, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61,
0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d, 0x0d,
0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x68, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d,
0x0a, 0x74, 0x72, 0x3a, 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x65, 0x76,
0x65, 0x6e, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x62, 0x67, 0x2d, 0x32, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x65, 0x78, 0x74,
0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x2c, 0x74,
0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70,
0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x20, 0x31, 0x32, 0x70, 0x78,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f,
0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61,
0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20,
0x31, 0x2e, 0x30, 0x33, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e,
0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x0a, 0x7d, 0x0a,
0x0a, 0x3a, 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76,
0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62,
0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64,
0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x29, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64,
0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62,
0x6f, 0x78, 0x2d, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67,
0x3a, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78, 0x0d, 0x0a, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x69, 0x6d, 0x67, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d,
0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x64, 0x3e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20,
0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62,
0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74,
0x64, 0x3e, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74,
0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x64, 0x3e,
0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a,
0x20, 0x30, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2e, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e,
0x67, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x72, 0x65, 0x64, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61,
0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x6e, 0x64,
0x20, 0x28, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x36, 0x30, 0x30,
0x70, 0x78, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70,
0x74, 0x20, 0x74, 0x64, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61,
0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x74, 0x65,
0x78, 0x74, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5d, 0x2c,
0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70,
0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74,
0x20, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74,
0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64,
0x61, 0x70, 0x74, 0x20, 0x74, 0x64, 0x3a, 0x68, 0x61, 0x73, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x5d, 0x29,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64,
0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63,
0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x5d, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x65, 0x6d,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68,
0x74, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x65, 0x6d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x61,
0x62, 0x6c, 0x65, 0x20, 0x74, 0x64, 0x3a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x63, 0x68, 0x69,
0x6c, 0x64, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f,
0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64,
0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x64, 0x3a, 0x6c, 0x61, 0x73,
0x74, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x23,
0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61, 0x6e,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x70, 0x78, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61,
0x76, 0x20, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65,
0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65,
0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f,
0x6c, 0x69, 0x64, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66,
0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74,
0x3a, 0x20, 0x62, 0x6f, 0x6c, 0x64, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64,
0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x36, 0x72, 0x65, 0x6d, 0x20, 0x30, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a,
0x20, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69, 0x6e,
0x65, 0x61, 0x72, 0x2d, 0x67, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f, 0x20,
0x6c, 0x65, 0x66, 0x74, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74,
0x20, 0x35, 0x30, 0x25, 0x2c, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35,
0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c, 0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29, 0x20,
0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30,
0x25, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x32, 0x73,
0x20, 0x65, 0x61, 0x73, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c,
0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63,
0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x2d,
0x67, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f, 0x20, 0x6c, 0x65, 0x66, 0x74,
0x2c, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x20,
0x35, 0x30, 0x25, 0x2c, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x35,
0x2c, 0x32, 0x35, 0x35, 0x2c, 0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29, 0x20, 0x72,
0x69, 0x67, 0x68, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30, 0x25,
0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c,
0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x70, 0x6f,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61,
0x6c, 0x6c, 0x20, 0x2e, 0x34, 0x35, 0x73, 0x20, 0x65, 0x61, 0x73, 0x65, 0x0d, 0x0a, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x61, 0x63, 0x74,
0x69, 0x76, 0x65, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x31, 0x2c,
0x68, 0x32, 0x2c, 0x68, 0x33, 0x2c, 0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68, 0x36, 0x20, 0x7b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x6f, 0x70, 0x3a,
0x20, 0x2e, 0x38, 0x37, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x31, 0x2c, 0x68,
0x32, 0x2c, 0x68, 0x33, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f,
0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x32, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x38, 0x70,
0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f,
0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20,
0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x0a, 0x7d,
0x0a, 0x0a, 0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68, 0x36, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20,
0x2e, 0x33, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x31, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x2e, 0x32,
0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x32, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x38, 0x35,
0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x33, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x35, 0x72,
0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x34, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66,
0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x32, 0x35, 0x72, 0x65,
0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x35, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a,
0x0a, 0x68, 0x36, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73,
0x69, 0x7a, 0x65, 0x3a, 0x20, 0x2e, 0x38, 0x37, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a,
0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76,
0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x0a, 0x7d, 0x0a,
0x0a, 0x61, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c,
0x6b, 0x2d, 0x32, 0x29, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b,
0x0a, 0x7d, 0x0a, 0x0a, 0x61, 0x62, 0x62, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0a, 0x7d, 0x0a, 0x0a, 0x61,
0x62, 0x62, 0x72, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0a, 0x7d, 0x0a, 0x0a,
0x61, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2c,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f,
0x6e, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65,
0x73, 0x65, 0x74, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d,
0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69,
0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61,
0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x20, 0x31, 0x32, 0x70, 0x78, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a,
0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78,
0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f,
0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70,
0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x77, 0x72, 0x61, 0x70, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72,
0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72,
0x64, 0x65, 0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64,
0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x3a, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x61,
0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64,
0x5d, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75,
0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65, 0x73, 0x65, 0x74,
0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x5b, 0x64,
0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x2e, 0x35, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x6e, 0x6f, 0x74,
0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x62, 0x75, 0x74,
0x74, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x2e, 0x62, 0x75, 0x74, 0x74, 0x6f,
0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x66,
0x6f, 0x63, 0x75, 0x73, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65,
0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74,
0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x68, 0x6f,
0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72,
0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x68, 0x6f,
0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73,
0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a,
0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20,
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c,
0x6c, 0x61, 0x70, 0x73, 0x65, 0x3a, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25,
0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x64, 0x2c, 0x74, 0x68, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69,
0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x29,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e,
0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64,
0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x68,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e,
0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32,
0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x72, 0x3a, 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, 0x69, 0x6c,
0x64, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61,
0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x78, 0x74,
0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77,
0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x2c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x61,
0x72, 0x65, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e,
0x67, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x20, 0x31, 0x32, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20,
0x2e, 0x35, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e,
0x62, 0x67, 0x2d, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70,
0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x62, 0x67, 0x2d, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64,
0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x3a, 0x20,
0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69,
0x7a, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78,
0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6d, 0x67, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a,
0x0a, 0x74, 0x64, 0x3e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x64, 0x3e, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72,
0x65, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d,
0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67,
0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a,
0x74, 0x64, 0x3e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x20,
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x65, 0x64,
0x0a, 0x7d, 0x0a, 0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20,
0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x28, 0x6d, 0x61, 0x78, 0x2d,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x70, 0x78, 0x29, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x64, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79,
0x70, 0x65, 0x3d, 0x74, 0x65, 0x78, 0x74, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x2c, 0x2e,
0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x2e,
0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30,
0x30, 0x25, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61,
0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x64, 0x3a, 0x68, 0x61, 0x73, 0x28, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x5d,
0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74,
0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x68, 0x65, 0x63,
0x6b, 0x62, 0x6f, 0x78, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x2e,
0x35, 0x65, 0x6d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e,
0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x64, 0x3a, 0x66,
0x69, 0x72, 0x73, 0x74, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74,
0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x64,
0x3a, 0x6c, 0x61, 0x73, 0x74, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x74, 0x6f, 0x70,
0x3a, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x23,
0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61, 0x6e,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77,
0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x70, 0x78, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x7b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f,
0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a,
0x20, 0x31, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d,
0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x62, 0x6f, 0x6c, 0x64, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x36, 0x72, 0x65, 0x6d,
0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69,
0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31,
0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69,
0x6e, 0x65, 0x61, 0x72, 0x2d, 0x67, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f,
0x20, 0x6c, 0x65, 0x66, 0x74, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e,
0x74, 0x20, 0x35, 0x30, 0x25, 0x2c, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32,
0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c, 0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29,
0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30,
0x25, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x32, 0x73, 0x20,
0x65, 0x61, 0x73, 0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20,
0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75,
0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x2d, 0x67, 0x72, 0x61, 0x64, 0x69,
0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x2c, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x20, 0x35, 0x30, 0x25, 0x2c, 0x72,
0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c,
0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d,
0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30, 0x25, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a,
0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x68, 0x6f, 0x76,
0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f,
0x75, 0x6e, 0x64, 0x2d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6c, 0x65,
0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69,
0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x34, 0x35, 0x73, 0x20, 0x65, 0x61, 0x73,
0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x61,
0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x31, 0x35, 0x73,
0x20, 0x65, 0x61, 0x73, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c,
0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6c, 0x69, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20,
0x2e, 0x35, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70,
0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63,
0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31,
0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61,
0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e,
0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
0x72, 0x67, 0x69, 0x6e, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66,
0x37, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65,
0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x65, 0x61, 0x73, 0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76,
0x20, 0x61, 0x20, 0x6c, 0x69, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x73, 0x74,
0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69,
0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a, 0x0a,
0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61,
0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72,
0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61,
0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31,
0x30, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x23, 0x66, 0x37, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77,
0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x74, 0x61, 0x6c,
0x69, 0x63, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79,
0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2e, 0x74,
0x64, 0x62, 0x74, 0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x2d, 0x61, 0x6c,
0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x2e, 0x6e, 0x61, 0x76, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20,
0x33, 0x37, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74,
0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d,
0x69, 0x63, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x74, 0x64, 0x62, 0x74, 0x6e,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67,
0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76,
0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x6e, 0x61, 0x76, 0x65, 0x6e, 0x74,
0x72, 0x79, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20,
0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69,
0x64, 0x74, 0x68, 0x3a, 0x20, 0x33, 0x37, 0x35, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d,

File diff suppressed because it is too large Load Diff