From 9af3bb53fb77dbe121c0711bdf6616608b1f55d9 Mon Sep 17 00:00:00 2001
From: Constantin P <papizh.konstantin@demlabs.net>
Date: Mon, 21 Mar 2022 17:48:35 +0000
Subject: [PATCH] Features 5769

---
 3rdparty/libconfini/AUTHORS                   |    1 +
 3rdparty/libconfini/COPYING                   |  675 ++
 3rdparty/libconfini/README                    |  211 +
 3rdparty/libconfini/README.md                 |    1 +
 3rdparty/libconfini/confini.c                 | 5763 +++++++++++++++++
 3rdparty/libconfini/confini.h                 |  589 ++
 cmake/OS_Detection.cmake                      |    1 +
 dap-sdk/core/CMakeLists.txt                   |   10 +-
 .../notify_server/include/dap_notify_srv.h    |    1 +
 .../server/notify_server/src/dap_notify_srv.c |   13 +
 modules/chain/dap_chain_ledger.c              |   40 +
 modules/net/dap_chain_net.c                   |  211 +-
 12 files changed, 7388 insertions(+), 128 deletions(-)
 create mode 100644 3rdparty/libconfini/AUTHORS
 create mode 100644 3rdparty/libconfini/COPYING
 create mode 100644 3rdparty/libconfini/README
 create mode 100644 3rdparty/libconfini/README.md
 create mode 100644 3rdparty/libconfini/confini.c
 create mode 100644 3rdparty/libconfini/confini.h

diff --git a/3rdparty/libconfini/AUTHORS b/3rdparty/libconfini/AUTHORS
new file mode 100644
index 0000000000..513363c831
--- /dev/null
+++ b/3rdparty/libconfini/AUTHORS
@@ -0,0 +1 @@
+madmurphy <madmurphy333 AT gmail DOT com>
diff --git a/3rdparty/libconfini/COPYING b/3rdparty/libconfini/COPYING
new file mode 100644
index 0000000000..10926e87f1
--- /dev/null
+++ b/3rdparty/libconfini/COPYING
@@ -0,0 +1,675 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
diff --git a/3rdparty/libconfini/README b/3rdparty/libconfini/README
new file mode 100644
index 0000000000..5320380169
--- /dev/null
+++ b/3rdparty/libconfini/README
@@ -0,0 +1,211 @@
+libconfini {#readme}
+====================
+
+**libconfini** is the ultimate and most consistent INI file parser library
+written in C. Originally designed for parsing configuration files written by
+other programs, it focuses on standardization and parsing exactness and is at
+ease with almost every type of file containing key/value pairs.
+
+The library is fast and suitable for embedded systems. Its algorithms are
+written from scratch and do not depend on any external library, except for the
+C standard headers **stdio.h**, **stdlib.h**, **stdbool.h** and **stdint.h**
+(and for extreme platforms the code [can be also compiled as “bare metal”][1],
+with few or no strings attached to the C Standard Library).
+
+Rather than storing the parsed data, **libconfini** gives the developer the
+freedom to choose what to do with them through a custom callback invoked for
+each INI node read. The API has been designed to be powerful, flexible and
+simple to use.
+
+With **libconfini** you will find in INI files the same serialization power
+that you would normally find in other heavily structured formats (such as JSON,
+YAML, TOML), but with the advantage of using the most human-friendly
+configuration format ever invented (thanks to their informal status, INI files
+are indeed more fluid, expressive and human-friendly than formats explicitly
+designed with the same purpose, such as YAML and TOML). The library's main goal
+is to be uncommonly powerful in the most tedious and error-prone task when
+parsing a text file in C: string handling. Thanks to this, the programmer is
+left free to organize the content parsed as (s)he pleases, assisted by a rich
+set of fine-tuning tools.
+
+
+Features
+--------
+
+* Typed data support (each value can be parsed as a boolean, a number, a
+  string, an array)
+* Single/double quotes support in _Bash style_ (i.e. quotes can be opened and
+  closed multiple times within the same value)
+* Multi-line support
+* Comment support
+* Disabled entry support
+* INI sectioning support (single-level sectioning, as in `[foo]`; absolute
+  nesting, as in `[foo.bar]`; relative nesting, as in `[.bar]`)
+* Automatic sanitization of values, key names and section paths
+* Comparison functions designed just for INI source code (capable, for example,
+  to recognize the equality between <code>"Hello world"</code> and
+  <code>"He"l'lo' world</code>, or between <code>foo bar</code> and
+  <code>foo&nbsp;&nbsp;&nbsp;&nbsp;bar</code>)
+* Callback pattern
+* Thread-safety (each parsing process is fully reentrant)
+* Highly optimized code (single memory allocation for each parsing, heuristic
+  programming, optimization flags)
+* Function modularity (each public function is independent from all other
+  public functions)
+* K.I.S.S. (no public functions are automatically invoked during the parsing --
+  for example, not even single/double quotes are automatically removed from
+  values unless the developer explicitly decides to use the formatting
+  functions provided by the API)
+* Robust and cross-platform file access (UTF-8 support; protection against null
+  byte injection; support of all code representations of new lines -- i.e.
+  ubiquitous support of Classic Mac OS' `CR`, Unix' `LF`, Windows' `CR`+`LF`,
+  RISC OS Open's `LF`+`CR`)
+
+
+Sample usage
+------------
+
+log.ini:
+
+`````````````````````````````````````` ini
+[users]
+have_visited = ronnie, lilly82, robrob
+
+[last_update]
+date = 12.03.2017
+``````````````````````````````````````
+
+example.c:
+
+``````````````````````````````````````````````````````````````````````````` c
+#include <confini.h>
+
+static int callback (IniDispatch * disp, void * v_other) {
+
+  #define IS_KEY(SECTION, KEY) \
+    (ini_array_match(SECTION, disp->append_to, '.', disp->format) && \
+    ini_string_match_ii(KEY, disp->data, disp->format))
+
+  if (disp->type == INI_KEY) {
+
+    if (IS_KEY("users", "have_visited")) {
+
+      /* No need to parse this field as an array right now */
+      printf("People who have visited: %s\n", disp->value);
+
+    } else if (IS_KEY("last_update", "date")) {
+
+      printf("Last update: %s\n", disp->value);
+
+    }
+
+  }
+
+  #undef IS_KEY
+
+  return 0;
+
+}
+
+int main () {
+
+  if (load_ini_path("log.ini", INI_DEFAULT_FORMAT, NULL, callback, NULL)) {
+
+    fprintf(stderr, "Sorry, something went wrong :-(\n");
+    return 1;
+
+  }
+
+  return 0;
+
+}
+```````````````````````````````````````````````````````````````````````````
+
+Output:
+
+````````````````````````````````````````````````
+People who have visited: ronnie, lilly82, robrob
+Last update: 12.03.2017
+````````````````````````````````````````````````
+
+If you are using C++, under `examples/cplusplus/map.cpp` you can find a snippet
+for storing an INI file into a class that relies on a `std::unordered_map`
+object.
+
+For more details, please read the [Library Functions Manual][2] (`man
+libconfini`) -- a standalone HTML version is available [here][3] -- and the
+manual of the header file (`man confini.h`). The code is available on
+[GitHub][4] under [madmurphy/libconfini][5]).
+
+
+Installation
+------------
+
+Despite the small footprint, **libconfini** has been conceived as a shared
+library (but it can be used as a static library as well). An automatic list of
+the distributions that ship the library already compiled is available [here][6].
+
+If a pre-compiled package for your platform is not available, on most Unix-like
+systems it is possible to install **libconfini** using the following common
+steps:
+
+`````````````````` sh
+./configure
+make
+make install-strip
+``````````````````
+
+If the `strip` utility is not available on your machine, use `make install`
+instead (it will produce larger binaries)
+
+For a minimum installation without development files (i.e. static libraries,
+headers, documentation, examples, etc.) use `./configure --disable-devel`.
+
+If the `configure` script is missing you need to generate it by running the
+`bootstrap` script. By default, `bootstrap` will also run the `configure`
+script immediately after having generated it, so you may type the `make`
+command directly after `bootstrap`. To list different options use `./bootstrap
+--help`.
+
+If you are using **Microsoft Windows**, a batch script for compiling
+**libconfini** with [**MinGW**][7] without **Bash** is available
+(`mgwmake.bat`). If you are interested in using **GNU Make** for compiling
+**libconfini**, you can integrate **MinGW** with [**MSYS**][8], or you can
+directly use [**MSYS2**][9] and [its official port][10] of the library.
+Alternatively, [an unofficial port][11] of **libconfini** for [**Cygwin**][12]
+is available.
+
+For further information, see [INSTALL][13].
+
+
+Bindings
+--------
+
+[Danilo Spinella][14] maintains [a D binding package][15] of **libconfini**.
+
+
+Free software
+-------------
+
+This library is free software. You can redistribute it and/or modify it under
+the terms of the GPL license version 3 or any later version. See [COPYING][16]
+for details.
+
+
+  [1]: https://madmurphy.github.io/libconfini/html/baremetal.html
+  [2]: https://madmurphy.github.io/libconfini/html/libconfini.html
+  [3]: https://madmurphy.github.io/libconfini/manual.html
+  [4]: https://github.com/
+  [5]: https://github.com/madmurphy/libconfini
+  [6]: https://repology.org/project/libconfini/versions
+  [7]: http://www.mingw.org/
+  [8]: http://www.mingw.org/wiki/MSYS
+  [9]: https://www.msys2.org/
+  [10]: https://packages.msys2.org/base/mingw-w64-libconfini
+  [11]: https://github.com/fd00/yacp/tree/master/libconfini
+  [12]: https://www.cygwin.com/
+  [13]: https://madmurphy.github.io/libconfini/html/install.html
+  [14]: https://danyspin97.org/
+  [15]: https://github.com/DanySpin97/libconfini-d
+  [16]: https://github.com/madmurphy/libconfini/blob/master/COPYING
+
diff --git a/3rdparty/libconfini/README.md b/3rdparty/libconfini/README.md
new file mode 100644
index 0000000000..100b93820a
--- /dev/null
+++ b/3rdparty/libconfini/README.md
@@ -0,0 +1 @@
+README
\ No newline at end of file
diff --git a/3rdparty/libconfini/confini.c b/3rdparty/libconfini/confini.c
new file mode 100644
index 0000000000..57f7bbcfd8
--- /dev/null
+++ b/3rdparty/libconfini/confini.c
@@ -0,0 +1,5763 @@
+/*  -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-  */
+/*  Please make sure that the TAB width in your editor is set to 4 spaces  */
+
+/**
+
+	@file		confini.c
+	@brief		libconfini functions
+	@author		Stefano Gioffr&eacute;
+	@copyright	GNU General Public License, version 3 or any later version
+	@version	1.16.3
+	@date		2016-2021
+	@see		https://madmurphy.github.io/libconfini
+
+**/
+
+
+           /*/|
+          (_|_)      _ _ _                      __ _       _
+                    | (_) |__   ___ ___  _ __  / _(_)_ __ (_)
+                    | | | '_ \ / __/ _ \| '_ \| |_| | '_ \| |
+                    | | | |_) | (_| (_) | | | |  _| | | | | |
+                    |_|_|_.__/ \___\___/|_| |_|_| |_|_| |_|_|      _ _
+                                                                  ( | )
+                                                                  |/*/
+
+
+
+/**
+
+
+	@def        INIFORMAT_TABLE_AS(_____)
+
+	Content of the table:
+
+	- Bits 1-19: INI syntax
+	- Bits 20-22: INI semantics
+	- Bits 23-24: Human syntax (disabled entries)
+
+
+
+	@typedef    int (* IniStatsHandler) (
+	                IniStatistics * statistics,
+	                void * user_data
+	            )
+
+	@param      statistics
+	                A pointer to the #IniStatistics to handle
+	@param      user_data
+	                The custom argument previously passed to the caller function
+
+
+
+	@typedef    int (* IniDispHandler) (
+	                IniDispatch *dispatch,
+	                void * user_data
+	            )
+
+	@param      dispatch
+	                A pointer to the #IniDispatch to handle
+	@param      user_data
+	                The custom argument previously passed to the caller function
+
+
+
+	@typedef    int (* IniStrHandler) (
+	                char * ini_string,
+	                size_t string_length,
+	                size_t string_num,
+	                IniFormat format,
+	                void * user_data
+	            )
+
+	@param      ini_string
+	                The INI string to handle
+	@param      string_length
+	                The length of the INI string in bytes
+	@param      string_num
+	                The unique number that identifies @p ini_string within a
+	                sequence of INI strings; it equals zero if @p ini_string is the
+	                first or the only member of the sequence
+	@param      format
+	                The format of the INI file from which @p ini_string has been
+	                extracted
+	@param      user_data
+	                The custom argument previously passed to the caller function
+
+
+
+	@typedef    int (* IniSubstrHandler) (
+	                const char * ini_string,
+	                size_t fragm_offset,
+	                size_t fragm_length,
+	                size_t fragm_num,
+	                IniFormat format,
+	                void * user_data
+	            )
+
+	@param      ini_string
+	                The INI string containing the fragment to handle
+	@param      fragm_offset
+	                The offset of the selected fragment in bytes
+	@param      fragm_length
+	                The length of the selected fragment in bytes
+	@param      fragm_num
+	                The unique number that identifies the selected fragment within a
+	                sequence of fragments of @p ini_string; it equals zero if the
+	                fragment is the first or the only member of the sequence
+	@param      format
+	                The format of the INI file from which @p ini_string has been
+	                extracted
+	@param      user_data
+	                The custom argument previously passed to the caller function
+
+
+
+	@struct     IniFormat
+
+	@version    1.0
+	@date       October 6th, 2018 (version 1.7.0 of the library)
+
+	@property   IniFormat::delimiter_symbol
+	                The key-value delimiter character (ASCII only allowed); if set
+	                to `\0`, any space is delimiter
+	                (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`); if, within the format
+	                given, `IniFormat::delimiter_symbol` matches a metacharacter
+	                (`'\\'`, `'\''`, `'\"'`), its role as metacharacter will have
+	                higher priority than its role as delimiter symbol (i.e., the
+	                format will have no key-value delimiter); you may use the
+	                #IniDelimiters `enum` for this.
+	@property   IniFormat::case_sensitive
+	                If set to `true`, string comparisons will be always
+	                case-sensitive.
+	@property   IniFormat::semicolon_marker
+	                The role of the semicolon character (use `enum`
+	                #IniCommentMarker for this).
+	@property   IniFormat::hash_marker
+	                The role of the hash character (use `enum` #IniCommentMarker for
+	                this).
+	@property   IniFormat::section_paths
+	                Defines whether and how the format supports sections (use `enum`
+	                #IniSectionPaths for this).
+	@property   IniFormat::multiline_nodes
+	                Defines which class of entries are allowed to be multi-line (use
+	                `enum` #IniMultiline for this).
+	@property   IniFormat::no_spaces_in_names
+	                If set to `true`, key and section names containing spaces (even
+	                within quotes) will be dispatched as #INI_UNKNOWN. Note that
+	                setting #IniFormat::delimiter_symbol to #INI_ANY_SPACE will not
+	                automatically set this option to `true` (spaces will still be
+	                allowed within quotes, and in section names independently of
+	                quotes).
+	@property   IniFormat::no_single_quotes
+	                If set to `true`, the single-quote character (`'`) will be
+	                considered as a normal character.
+	@property   IniFormat::no_double_quotes
+	                If set to `true`, the double-quote character (`"`) will be
+	                considered as a normal character.
+	@property   IniFormat::implicit_is_not_empty
+	                If set to `true`, implicit keys (see @ref libconfini) will
+	                be always dispatched using the values provided by the global
+	                variables #INI_GLOBAL_IMPLICIT_VALUE and
+	                #INI_GLOBAL_IMPLICIT_V_LEN for the fields #IniDispatch::value
+	                and to #IniDispatch::v_len respectively; if set to `false`,
+	                implicit keys will be considered to be empty keys.
+	@property   IniFormat::do_not_collapse_values
+	                If set to `true`, sequences of one or more spaces in values
+	                (`/\s+/`) will be dispatched verbatim.
+	@property   IniFormat::preserve_empty_quotes
+	                If set to `true`, and if single/double quotes are
+	                metacharacters, ensures that, within values, empty strings
+	                enclosed between quotes (`""` or `''`) will not be collapsed
+	                together with the spaces that surround them. This option is
+	                useful for values containing space-delimited arrays, in order to
+	                preserve their empty members -- as in, for instance:
+	                `coordinates = "" ""`. Note that, in section and key names,
+	                empty strings enclosed between quotes are _always_ collapsed
+	                together with their surrounding spaces.
+	@property   IniFormat::disabled_after_space
+	                If set to `true`, what follows `/[#;]\s/` is allowed to be
+	                parsed as a disabled entry.
+	@property   IniFormat::disabled_can_be_implicit
+	                If set to `false`, comments that do not contain a key-value
+	                delimiter will never be parsed as disabled keys, but always as
+	                simple comments (even if the format supports implicit keys).
+
+
+
+	@struct     IniStatistics
+
+	@property   IniStatistics::format
+	                The format of the INI file (see #IniFormat)
+	@property   IniStatistics::bytes
+	                The size of the parsed file in bytes
+	@property   IniStatistics::members
+	                The size of the parsed file in number of members (nodes) -- this
+	                number always equals the number of dispatches that will be
+	                produced by #load_ini_file(), #load_ini_path() or
+	                #strip_ini_cache()
+
+
+
+	@struct     IniDispatch
+
+	@property   IniDispatch::format
+	                The format of the INI file (see #IniFormat)
+	@property   IniDispatch::type
+	                The dispatch type (see `enum` #IniNodeType)
+	@property   IniDispatch::data
+	                It can contain a comment, a section path or a key name,
+	                depending on #IniDispatch::type; it cannot be `NULL`
+	@property   IniDispatch::value
+	                It can contain the value of a key element, an empty string or it
+	                can point to the address pointed by the global variable
+	                #INI_GLOBAL_IMPLICIT_VALUE (_the latter is the only case in
+	                which `IniDispatch::value` can be `NULL`_)
+	@property   IniDispatch::append_to
+	                The current section path; it cannot be `NULL`
+	@property   IniDispatch::d_len
+	                The length of the string #IniDispatch::data
+	@property   IniDispatch::v_len
+	                The length of the string #IniDispatch::value
+	@property   IniDispatch::at_len
+	                The length of the string #IniDispatch::append_to
+	@property   IniDispatch::dispatch_id
+	                The dispatch number (the first dispatch is number zero)
+
+
+
+	@def        INI_DISABLED_FLAG
+
+	Example #1:
+
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
+	if (dispatch->type & INI_DISABLED_FLAG) {
+	    printf("This is not a real INI node...\n");
+	}
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	Example #2:
+
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
+	if ((dispatch->type | INI_DISABLED_FLAG) == INI_DISABLED_KEY) {
+	    printf("Hey! This is either an INI_KEY or an INI_DISABLED_KEY!\n");
+	}
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	See also #IniNodeType.
+
+
+**/
+
+
+
+		/*\
+		|*|
+		|*|     LOCAL ENVIRONMENT
+		|*|    ________________________________
+		\*/
+
+
+
+		/*  PREPROCESSOR PREAMBLE  */
+
+
+/*  String concatenation facilities  */
+#define __PP_CAT__(STEM, ...) STEM ## __VA_ARGS__
+#define __PP_UCAT__(STEM, ...) STEM ## _ ## __VA_ARGS__
+#define __PP_EVALUCAT__(STEM, ...) __PP_UCAT__(STEM, __VA_ARGS__)
+
+/*  Conditionals  */
+#define __PP_IIF__(CONDITION, ...) \
+	__PP_EVALUCAT__(__PP_UCAT__(__COND, CONDITION), _)(__VA_ARGS__, , )
+#define __COND_0__(IF_TRUE, IF_FALSE, ...) IF_FALSE
+#define __COND_1__(IF_TRUE, ...) IF_TRUE
+
+
+		/*  PREPROCESSOR ENVIRONMENT  */
+
+
+#ifndef CONFINI_IO_FLAVOR
+/**
+
+	@brief          The I/O API to use (possibly overridden via
+	                `-DCONFINI_IO_FLAVOR=[FLAVOR]`)
+
+	Possible values are `CONFINI_STANDARD` and `CONFINI_POSIX`
+
+**/
+#define CONFINI_IO_FLAVOR CONFINI_STANDARD
+#endif
+
+
+
+		/*  PREPROCESSOR GLUE  */
+
+
+#define _CONFINI_PRIVATE_ITEM_(GROUP, ITEM) \
+	__PP_EVALUCAT__(__PP_CAT__(_, GROUP), __PP_CAT__(ITEM, _))
+#define _CONFINI_CURRENT_FLAVOR_GET_(NAME) \
+	_CONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, NAME)
+#define _CONFINI_IS_FLAVOR_(NAME) \
+	_CONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == \
+		_CONFINI_PRIVATE_ITEM_(NAME, FLAVOR)
+
+
+
+		/*  AVAILABLE I/O FLAVORS  */
+
+
+/*  `-DCONFINI_IO_FLAVOR=CONFINI_STANDARD` (C99 Standard, default)  */
+#define _CONFINI_STANDARD_SEOF_FN_(FILEPTR) fseek(FILEPTR, 0, SEEK_END)
+#define _CONFINI_STANDARD_FT_FN_(FILEPTR) ftell(FILEPTR)
+#define _CONFINI_STANDARD_FT_T_ long signed int
+/*  Any unique non-zero integer to identify this I/O API  */
+#define _CONFINI_STANDARD_FLAVOR_ 1
+
+/*  `-DCONFINI_IO_FLAVOR=CONFINI_POSIX`  */
+#define _CONFINI_POSIX_SEOF_FN_(FILEPTR) fseeko(FILEPTR, 0, SEEK_END)
+#define _CONFINI_POSIX_FT_FN_(FILEPTR) ftello(FILEPTR)
+#define _CONFINI_POSIX_FT_T_ off_t
+/*  Any unique non-zero integer to identify this I/O API  */
+#define _CONFINI_POSIX_FLAVOR_ 2
+
+/*  Define `_POSIX_C_SOURCE` macro when `CONFINI_IO_FLAVOR == CONFINI_POSIX`  */
+#if _CONFINI_IS_FLAVOR_(CONFINI_POSIX)
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#endif
+
+/*  It is possible to add other I/O APIs here. Feel free to contribute!  */
+
+
+
+		/*  CHECKS  */
+
+
+#if _CONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == 0
+#error Unsupported I/O API defined in macro CONFINI_IO_FLAVOR
+#endif
+
+
+
+		/*  HEADERS  */
+
+#include <stdlib.h>
+#include "confini.h"
+
+
+
+		/*  DEPRECATED FUNCTIONS AND VARIABLES  */
+
+
+#ifdef ini_global_set_lowercase_mode
+#undef ini_global_set_lowercase_mode
+#endif
+
+#ifdef INI_GLOBAL_LOWERCASE_MODE
+#undef INI_GLOBAL_LOWERCASE_MODE
+#endif
+
+
+
+		/*  ALIASES  */
+
+
+#define _CONFINI_FALSE_ 0
+#define _CONFINI_TRUE_ 1
+#define _CONFINI_CHARBOOL_ unsigned char
+#define _CONFINI_SIMPLE_SPACE_ '\x20'
+#define _CONFINI_HT_ '\t'
+#define _CONFINI_FF_ '\f'
+#define _CONFINI_VT_ '\v'
+#define _CONFINI_CR_ '\r'
+#define _CONFINI_LF_ '\n'
+#define _CONFINI_BACKSLASH_ '\\'
+#define _CONFINI_OPEN_SECTION_ '['
+#define _CONFINI_CLOSE_SECTION_ ']'
+#define _CONFINI_SUBSECTION_ '.'
+#define _CONFINI_SEMICOLON_ ';'
+#define _CONFINI_HASH_ '#'
+#define _CONFINI_D_QUOTES_ '"'
+#define _CONFINI_S_QUOTES_ '\''
+#define _CONFINI_SEEK_EOF_(FILEPTR) \
+	_CONFINI_CURRENT_FLAVOR_GET_(SEOF_FN)(FILEPTR)
+#define _CONFINI_FTELL_(FILEPTR) \
+	_CONFINI_CURRENT_FLAVOR_GET_(FT_FN)(FILEPTR)
+#define _CONFINI_OFF_T_ \
+	_CONFINI_CURRENT_FLAVOR_GET_(FT_T)
+
+
+
+/*  The character that will replace sequences of one or more spaces (`/\s+/`)  */
+#define _CONFINI_COLLAPSED_ _CONFINI_SIMPLE_SPACE_
+
+
+/*
+
+	These may be any characters in theory... But after left-trimming each node
+	leading spaces work pretty well as markers...
+
+*/
+/*  Internal marker of comment blocks  */
+#define _CONFINI_BC_INT_MARKER_ _CONFINI_SIMPLE_SPACE_
+/*  Internal marker of inline comments  */
+#define _CONFINI_IC_INT_MARKER_ _CONFINI_HT_
+
+
+
+		/*  FUNCTIONAL MACROS AND CONSTANTS  */
+
+
+/*
+
+	Check whether a character can be escaped within a given format
+
+*/
+#define _CONFINI_IS_ESC_CHAR_(CHR, FMT) ( \
+		CHR == _CONFINI_BACKSLASH_ ? \
+			!INIFORMAT_HAS_NO_ESC(FMT) \
+		: CHR == _CONFINI_D_QUOTES_ ? \
+			!FMT.no_double_quotes \
+		: \
+			CHR == _CONFINI_S_QUOTES_ && !FMT.no_single_quotes \
+	)
+
+
+/*
+
+	Check whether a character represents any marker within a given format
+
+*/
+#define _CONFINI_IS_ANY_MARKER_(CHR, FMT) ( \
+		CHR == _CONFINI_HASH_ ? \
+			FMT.hash_marker != INI_IS_NOT_A_MARKER \
+		: \
+			CHR == _CONFINI_SEMICOLON_ && \
+			FMT.semicolon_marker != INI_IS_NOT_A_MARKER \
+	)
+
+
+/*
+
+	Check whether a character represents any marker except `INI_IGNORE` within a
+	given format
+
+*/
+#define _CONFINI_IS_N_I_MARKER_(CHR, FMT) ( \
+		CHR == _CONFINI_HASH_ ? \
+			FMT.hash_marker != INI_IS_NOT_A_MARKER && \
+			FMT.hash_marker != INI_IGNORE \
+		: \
+			CHR == _CONFINI_SEMICOLON_ && \
+			FMT.semicolon_marker != INI_IS_NOT_A_MARKER && \
+			FMT.semicolon_marker != INI_IGNORE \
+	)
+
+
+/*
+
+	Check whether a character represents a marker of type `INI_DISABLED_OR_COMMENT`
+	within a given format
+
+*/
+#define _CONFINI_IS_DIS_MARKER_(CHR, FMT) ( \
+		CHR == _CONFINI_HASH_ ? \
+			FMT.hash_marker == INI_DISABLED_OR_COMMENT \
+		: \
+			CHR == _CONFINI_SEMICOLON_ && \
+			FMT.semicolon_marker == INI_DISABLED_OR_COMMENT \
+	)
+
+
+/*
+
+	Check whether a character represents a marker of type `INI_ONLY_COMMENT` within
+	a given format
+
+*/
+#define _CONFINI_IS_COM_MARKER_(CHR, FMT) ( \
+		CHR == _CONFINI_HASH_ ? \
+			FMT.hash_marker == INI_ONLY_COMMENT \
+		: \
+			CHR == _CONFINI_SEMICOLON_ && \
+			FMT.semicolon_marker == INI_ONLY_COMMENT \
+	)
+
+
+/*
+
+	Check whether a character represents a marker of type `INI_IGNORE` within a
+	given format
+
+*/
+#define _CONFINI_IS_IGN_MARKER_(CHR, FMT) ( \
+		CHR == _CONFINI_HASH_ ? \
+			FMT.hash_marker == INI_IGNORE \
+		: \
+			CHR == _CONFINI_SEMICOLON_ && \
+			FMT.semicolon_marker == INI_IGNORE \
+	)
+
+
+/*
+
+	Maybe in the future there will be support for UTF-8 case folding (although
+	probably not -- see "Code considerations" in the Manual), but for now only
+	ASCII...
+
+*/
+#define _CONFINI_CHR_CASEFOLD_(CHR) \
+	(CHR > 0x40 && CHR < 0x5b ? CHR | 0x60 : CHR)
+
+
+/*
+
+	Constants related to `_CONFINI_SPACES_`
+
+*/
+#define _CONFINI_EOL_IDX_ 0
+#define _CONFINI_SPALEN_ 6
+
+
+/*
+
+	The list of space characters -- do not change its order or its content!
+
+*/
+static const char _CONFINI_SPACES_[_CONFINI_SPALEN_] = {
+	_CONFINI_LF_,
+	_CONFINI_CR_,
+	_CONFINI_VT_,
+	_CONFINI_FF_,
+	_CONFINI_HT_,
+	_CONFINI_SIMPLE_SPACE_
+};
+
+
+/*
+
+	Possible depths of `_CONFINI_SPACES_` (see function #is_some_space()).
+
+	Please, consider the following three constants as belonging together to a
+	virtual opaque `enum`.
+
+*/
+#define _CONFINI_WITH_EOL_ -1
+#define _CONFINI_NO_EOL_ 1
+#define _CONFINI_JUST_S_T_ 3
+
+
+/**
+
+	@brief          A list of possible string representations of boolean pairs
+
+	There may be infinite pairs here. Each pair must be organized according to the
+	following order:
+
+	1. Signifier of `false`
+	2. Signifier of `true`
+
+	@note   Everything **must** be lowercase in this list.
+
+**/
+static const char * const INI_BOOLEANS[][2] = {
+	{ "no", "yes" },
+	{ "false", "true" },
+	{ "off", "on" }
+};
+
+
+/*
+
+	Other constants related to `INI_BOOLEANS`
+
+*/
+#define _CONFINI_BOOLLEN_ (sizeof(INI_BOOLEANS) / sizeof(const char * const [2]))
+
+
+
+		/*  ABSTRACT UTILITIES  */
+
+
+/**
+
+	@brief          Check whether a character is a space
+	@param          chr             The target character
+	@param          depth           What is actually considered a space (possible
+	                                values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`,
+	                                `_CONFINI_JUST_S_T_`)
+	@return         A boolean: `true` if the character matches, `false` otherwise
+
+**/
+static inline _CONFINI_CHARBOOL_ is_some_space (
+	const char chr,
+	const int_least8_t depth
+) {
+	register int_least8_t idx = depth;
+	while (++idx < _CONFINI_SPALEN_ && chr != _CONFINI_SPACES_[idx]);
+	return idx < _CONFINI_SPALEN_;
+}
+
+
+/**
+
+	@brief          Soft left trim -- does not change the buffer
+	@param          str             The target string
+	@param          offs            The offset where to start the left trim
+	@param          depth           What is actually considered a space (possible
+	                                values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`,
+	                                `_CONFINI_JUST_S_T_`)
+	@return         The offset of the first non-space character
+
+**/
+static inline size_t ltrim_s (
+	const char * const str,
+	const size_t offs,
+	const int_least8_t depth
+) {
+	register size_t idx = offs;
+	while (is_some_space(str[idx++], depth));
+	return idx - 1;
+}
+
+
+/**
+
+	@brief          Hard left trim -- **does** change the buffer
+	@param          str             The target string
+	@param          offs            The offset where to start the left trim
+	@param          depth           What is actually considered a space (possible
+	                                values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`,
+	                                `_CONFINI_JUST_S_T_`)
+	@return         The offset of the first non-space character
+
+**/
+static inline size_t ltrim_h (
+	char * const str,
+	const size_t offs,
+	const int_least8_t depth
+) {
+	register size_t idx = offs;
+	while (is_some_space(str[idx], depth)) { str[idx++] = '\0'; }
+	return idx;
+}
+
+
+/**
+
+	@brief          Shifting left trim -- **does** change the buffer
+	@param          str             The target string
+	@param          offs            The offset where to start the left trim
+	@param          depth           What is actually considered a space (possible
+	                                values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`,
+	                                `_CONFINI_JUST_S_T_`)
+	@return         The new length of the string
+
+**/
+static inline size_t ltrim_hh (
+	char * const str,
+	const size_t offs,
+	const int_least8_t depth
+) {
+	register size_t idx_d = offs, idx_s = offs;
+	while (is_some_space(str[idx_s++], depth));
+	if (--idx_s - idx_d) {
+		while ((str[idx_d++] = str[idx_s++]));
+		for (idx_s = idx_d; str[idx_s]; str[idx_s++] = '\0');
+		return idx_d - 1;
+	}
+	while (str[idx_s++]);
+	return idx_s - 1;
+}
+
+
+/**
+
+	@brief          Soft right trim -- does not change the buffer
+	@param          str             The target string
+	@param          len             The length of the string
+	@param          depth           What is actually considered a space (possible
+	                                values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`,
+	                                `_CONFINI_JUST_S_T_`)
+	@return         The length of the string until the last non-space character
+
+**/
+static inline size_t rtrim_s (
+	const char * const str,
+	const size_t len,
+	const int_least8_t depth
+) {
+	register size_t idx = len + 1;
+	while (--idx > 0 && is_some_space(str[idx - 1], depth));
+	return idx;
+}
+
+
+/**
+
+	@brief          Hard right trim -- **does** change the buffer
+	@param          str             The target string
+	@param          len             The length of the string
+	@param          depth           What is actually considered a space (possible
+	                                values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`,
+	                                `_CONFINI_JUST_S_T_`)
+	@return         The new length of the string
+
+**/
+static inline size_t rtrim_h (
+	char * const str,
+	const size_t len,
+	const int_least8_t depth
+) {
+	register size_t idx = len;
+	while (idx > 0 && is_some_space(str[idx - 1], depth)) { str[--idx] = '\0'; }
+	return idx;
+}
+
+
+/**
+
+	@brief          Unparsed soft right trim (right trim of `/(?:\s|\\[\n\r])+$/`)
+	                -- does not change the buffer
+	@param          str             The target string
+	@param          len             The length of the string
+	@return         The length of the string until the last non-space character
+
+**/
+static inline size_t urtrim_s (
+	const char * const str,
+	const size_t len
+) {
+
+	register uint_least8_t abcd = 1;
+	register size_t idx = len;
+
+
+	/* \                                /\
+	\ */     continue_urtrim:          /* \
+	 \/     ______________________     \ */
+
+
+	if (idx < 1) {
+
+		return idx;
+
+	}
+
+	switch (str[--idx]) {
+
+		case _CONFINI_VT_:
+		case _CONFINI_FF_:
+		case _CONFINI_HT_:
+		case _CONFINI_SIMPLE_SPACE_:
+
+			abcd = 1;
+			goto continue_urtrim;
+
+		case _CONFINI_LF_:
+		case _CONFINI_CR_:
+
+			abcd = 3;
+			goto continue_urtrim;
+
+		case _CONFINI_BACKSLASH_:
+
+			if (abcd >>= 1) {
+
+				goto continue_urtrim;
+
+			}
+
+	}
+
+	return idx + 1;
+
+}
+
+
+/**
+
+	@brief          Convert an ASCII string to lower case
+	@param          str             The target string
+	@return         Nothing
+
+**/
+static inline void string_tolower (
+	char * const str
+) {
+	for (register char * chrptr = str; *chrptr; chrptr++) {
+		*chrptr = _CONFINI_CHR_CASEFOLD_(*chrptr);
+	}
+}
+
+
+
+		/*  CONCRETE UTILITIES  */
+
+
+/**
+
+	@brief          Unparsed hard left trim (left trim of
+	                `/^(?:\s+|\\[\n\r]|''|"")+/`) -- **does** change the buffer
+	@param          srcstr          The target string (it may contain multi-line
+	                                escape sequences)
+	@param          offs            The offset where to start the left trim
+	@param          format          The format of the INI file
+	@return         The offset of the first non-trivial character
+
+**/
+static inline size_t qultrim_h (
+	char * const srcstr,
+	const size_t offs,
+	const IniFormat format
+) {
+
+	/*
+
+	Mask `abcd` (8 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     We are in an odd sequence of backslashes
+		FLAG_32     Erase the previous character
+		FLAG_64     Erase this character
+		FLAG_128    Continue the loop
+
+	*/
+
+	register uint_least8_t abcd =
+		(format.no_double_quotes ? 130 : 128) | format.no_single_quotes;
+
+	size_t idx = offs;
+
+	do {
+
+		abcd =
+
+			!(abcd & 28) && is_some_space(srcstr[idx], _CONFINI_NO_EOL_) ?
+				(abcd & 223) | 64
+			: !(abcd & 12) && (
+				srcstr[idx] == _CONFINI_LF_ ||
+				srcstr[idx] == _CONFINI_CR_
+			) ?
+				(
+					abcd & 16 ?
+						(abcd & 239) | 96
+					:
+						abcd | 64
+				)
+			: !(abcd & 22) && srcstr[idx] == _CONFINI_D_QUOTES_ ?
+				(
+					abcd & 8 ?
+						(abcd & 231) | 96
+					:
+						(abcd & 159) | 8
+				)
+			: !(abcd & 25) && srcstr[idx] == _CONFINI_S_QUOTES_ ?
+				(
+					abcd & 4 ?
+						(abcd & 235) | 96
+					:
+						(abcd & 159) | 4
+				)
+			: srcstr[idx] == _CONFINI_BACKSLASH_ ?
+				(abcd & 159) ^ 16
+			:
+				abcd & 31;
+
+
+		if (abcd & 32) {
+
+			srcstr[idx - 1] = '\0';
+
+		}
+
+		if (abcd & 64) {
+
+			srcstr[idx] = '\0';
+
+		}
+
+		idx++;
+
+	} while (abcd & 128);
+
+	return abcd & 28 ? idx - 2 : idx - 1;
+
+}
+
+
+/**
+
+	@brief          Soft left trim within an unparsed disabled entry (left trim of
+	                `/(?:(?:^|\\?[\n\r])[ \t\v\f]*(?:#(?:[ \t\v\f]|''|"")*)?)+/`)
+	                -- does not change the buffer
+	@param          srcstr          The target string (it may contain multi-line
+	                                escape sequences)
+	@param          offs            The offset where to start the left trim
+	@param          format          The format of the INI file
+	@return         The offset of the first non-trivial character
+
+**/
+static inline size_t dqultrim_s (
+	const char * const srcstr,
+	const size_t offs,
+	const IniFormat format
+) {
+
+	/*
+
+	Mask `abcd` (7 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     We are in an odd sequence of backslashes
+		FLAG_32     A new line has just begun
+		FLAG_64     Continue the left trim
+
+	*/
+
+
+	register uint_least8_t abcd =
+		(format.no_double_quotes ? 98 : 96) | format.no_single_quotes;
+
+	register size_t idx = offs;
+
+	do {
+
+		abcd =
+
+			is_some_space(srcstr[idx], _CONFINI_NO_EOL_) ?
+				(
+					abcd & 28 ?
+						abcd & 63
+					:
+						abcd
+				)
+			: srcstr[idx] == _CONFINI_LF_ || srcstr[idx] == _CONFINI_CR_ ?
+				(
+					abcd & 12 ?
+						(abcd & 47) | 32
+					:
+						(abcd & 111) | 32
+				)
+			: srcstr[idx] == _CONFINI_BACKSLASH_ ?
+				(
+					abcd & 28 ?
+						(abcd & 31) | 16
+					:
+						(abcd & 95) | 16
+				)
+			: (abcd & 32) && _CONFINI_IS_ANY_MARKER_(srcstr[idx], format) ?
+				abcd & 79
+			: !(abcd & 54) && srcstr[idx] == _CONFINI_D_QUOTES_ ?
+				(abcd & 95) ^ 8
+			: !(abcd & 57) && srcstr[idx] == _CONFINI_S_QUOTES_ ?
+				(abcd & 95) ^ 4
+			: srcstr[idx] ?
+				abcd & 31
+			:
+				abcd & 19;
+
+
+		idx++;
+
+	} while (abcd & 64);
+
+	return abcd & 28 ? idx - 2 : idx - 1;
+
+}
+
+
+/**
+
+	@brief          Get the position of the first occurrence out of quotes of a
+	                given character, stopping after a given number of charcters
+	@param          str             The string where to search
+	@param          chr             The character to to search
+	@param          len             The maximum number of characters to read
+	@param          format          The format of the INI file
+	@return         The offset of the first occurrence of @p chr, or @p len if
+	                @p chr has not been not found
+
+**/
+static inline size_t getn_metachar_pos (
+	const char * const str,
+	const char chr,
+	const size_t len,
+	const IniFormat format
+) {
+
+	size_t idx = 0;
+
+	/*
+
+	Mask `abcd` (5 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     We are in an odd sequence of backslashes
+
+	*/
+
+	for (
+
+		register uint_least8_t abcd =
+			(format.no_double_quotes << 1) | format.no_single_quotes;
+
+			idx < len && (
+				(abcd & 12) || (
+					chr ?
+						str[idx] != chr
+					:
+						!is_some_space(str[idx], _CONFINI_WITH_EOL_)
+				)
+			);
+
+		abcd =
+			str[idx] == _CONFINI_BACKSLASH_ ? abcd ^ 16
+			: !(abcd & 22) && str[idx] == _CONFINI_D_QUOTES_ ? abcd ^ 8
+			: !(abcd & 25) && str[idx] == _CONFINI_S_QUOTES_ ? abcd ^ 4
+			: abcd & 15,
+		idx++
+
+	);
+
+	return idx;
+
+}
+
+
+/**
+
+	@brief          Get the position of the first occurrence out of quotes of a
+	                given character
+	@param          str             The string where to search
+	@param          chr             The character to to search
+	@param          format          The format of the INI file
+	@return         The offset of the first occurrence of @p chr or the length of
+	                @p str if @p chr has not been not found
+
+**/
+static inline size_t get_metachar_pos (
+	const char * const str,
+	const char chr,
+	const IniFormat format
+) {
+
+	size_t idx = 0;
+
+	/*
+
+	Mask `abcd` (5 bits used):
+
+		--> As in #getn_metachar_pos()
+
+	*/
+
+	for (
+
+		register uint_least8_t abcd =
+			(format.no_double_quotes << 1) | format.no_single_quotes;
+
+			str[idx] && (
+				(abcd & 12) || (
+					chr ?
+						str[idx] != chr
+					:
+						!is_some_space(str[idx], _CONFINI_NO_EOL_)
+				)
+			);
+
+		abcd =
+			str[idx] == _CONFINI_BACKSLASH_ ? abcd ^ 16
+			: !(abcd & 22) && str[idx] == _CONFINI_D_QUOTES_ ? abcd ^ 8
+			: !(abcd & 25) && str[idx] == _CONFINI_S_QUOTES_ ? abcd ^ 4
+			: abcd & 15,
+		idx++
+
+	);
+
+	return idx;
+
+}
+
+
+/**
+
+	@brief          Replace `/\\(\n\r?|\r\n?)[\t \v\f]*[#;]/` or `/\\(\n\r?|\r\n?)/`
+	                with `"$1"`
+	@param          srcstr          The target string (it may contain multi-line
+	                                escape sequences)
+	@param          len             Length of the string
+	@param          is_disabled     The string represents a disabled entry
+	@param          format          The format of the INI file
+	@return         The new length of the string
+
+**/
+static size_t unescape_cr_lf (
+	char * const srcstr,
+	const size_t len,
+	const _CONFINI_CHARBOOL_ is_disabled,
+	const IniFormat format
+) {
+
+	register size_t idx_s = 0, idx_d = 0;
+	register _CONFINI_CHARBOOL_ eol_i = _CONFINI_EOL_IDX_;
+	register _CONFINI_CHARBOOL_ is_escaped = _CONFINI_FALSE_;
+	size_t probe;
+
+	while (idx_s < len) {
+
+		if (
+			is_escaped && (
+				srcstr[idx_s] == _CONFINI_SPACES_[eol_i] ||
+				srcstr[idx_s] == _CONFINI_SPACES_[eol_i ^= 1]
+			)
+		) {
+
+			srcstr[idx_d - 1] = srcstr[idx_s++];
+
+			if (srcstr[idx_s] == _CONFINI_SPACES_[eol_i ^ 1]) {
+
+				srcstr[idx_d++] = srcstr[idx_s++];
+
+			}
+
+			if (is_disabled) {
+
+				probe = ltrim_s(srcstr, idx_s, _CONFINI_NO_EOL_);
+
+				if (_CONFINI_IS_DIS_MARKER_(srcstr[probe], format)) {
+
+					idx_s = probe + 1;
+
+				}
+
+			}
+
+			is_escaped = _CONFINI_FALSE_;
+
+		} else {
+
+			is_escaped =
+				srcstr[idx_s] == _CONFINI_BACKSLASH_ ?
+					!is_escaped
+				:
+					_CONFINI_FALSE_;
+
+			srcstr[idx_d++] = srcstr[idx_s++];
+
+		}
+
+	}
+
+	for (
+		idx_s = idx_d;
+			idx_s < len;
+		srcstr[idx_s++] = '\0'
+	);
+
+	return idx_d;
+
+}
+
+
+/**
+
+	@brief          Sanitize a section path
+	@param          secpath         The section path
+	@param          format          The format of the INI file
+	@return         The new length of the string
+
+	Out of quotes, similar to ECMAScript
+	`secpath.replace(/\.*\s*$|(?:\s*(\.))+\s*|^\s+/g, "$1").replace(/\s+/g, " ")`
+
+	A section path can start with a dot (append), but cannot end with a dot. Spaces
+	surrounding dots will be removed. Fragments surrounded by single or double
+	quotes (if these are enabled) are prevented from changes.
+
+**/
+static size_t sanitize_section_path (
+	char * const secpath,
+	const IniFormat format
+) {
+
+	/*
+
+	Mask `abcd` (12 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     We are in an odd sequence of backslashes
+		FLAG_32     These are initial spaces
+		FLAG_64     This is a space out of quotes
+		FLAG_128    This is a dot out of quotes
+		FLAG_256    This is anything *but* an opening single/double quote
+		FLAG_512    Don't ignore the last two characters
+		FLAG_1024   Don't overwrite the previous character
+		FLAG_2048   Path contains at least one name
+
+	*/
+
+	register uint_least16_t abcd =
+		(format.no_double_quotes ? 1826 : 1824) | format.no_single_quotes;
+
+	register size_t idx_s = 0, idx_d = 0;
+
+	for (; secpath[idx_s]; idx_s++) {
+
+		/*  Revision #2  */
+
+		abcd =
+
+			!(abcd & 12) && is_some_space(secpath[idx_s], _CONFINI_WITH_EOL_) ?
+				(
+					abcd & 224 ?
+						(abcd & 3055) | 832
+					:
+						(abcd & 4079) | 1856
+				)
+			: !(abcd & 12) && secpath[idx_s] == _CONFINI_SUBSECTION_ ?
+				(
+					abcd & (abcd & 32 ? 128 : 192) ?
+						(abcd & 2959) | 896
+					:
+						(abcd & 3983) | 1920
+				)
+			: !(abcd & 25) && secpath[idx_s] == _CONFINI_S_QUOTES_ ?
+				(
+					~abcd & 4 ?
+						(abcd & 3839) | 1540
+					: abcd & 256 ?
+						(abcd & 3867) | 3840
+					:
+						(abcd & 3579) | 1280
+				)
+			: !(abcd & 22) && secpath[idx_s] == _CONFINI_D_QUOTES_ ?
+				(
+					~abcd & 8 ?
+						(abcd & 3839) | 1544
+					: abcd & 256 ?
+						(abcd & 3863) | 3840
+					:
+						(abcd & 3575) | 1280
+				)
+			: secpath[idx_s] == _CONFINI_BACKSLASH_ ?
+				((abcd & 3871) | 3840) ^ 16
+			:
+				(abcd & 3855) | 3840;
+
+
+		if (abcd & 512) {
+
+			secpath[
+				abcd & 1024 ?
+					idx_d++
+				: idx_d ?
+					idx_d - 1
+				:
+					idx_d
+			] =
+				!(~abcd & 384) ?
+					_CONFINI_SUBSECTION_
+				: !(~abcd & 320) ?
+					_CONFINI_COLLAPSED_
+				:
+					secpath[idx_s];
+
+		} else if (idx_d) {
+
+			idx_d--;
+
+		}
+
+	}
+
+	for (
+
+		idx_s =
+			idx_d && (abcd & 2048) && (abcd & 192) ?
+				--idx_d
+			:
+				idx_d;
+
+			secpath[idx_s];
+
+		secpath[idx_s++] = '\0'
+
+	);
+
+	return idx_d;
+
+}
+
+
+/**
+
+	@brief          Out of quotes similar to ECMAScript
+	                `ini_string.replace(/''|""/g, "").replace(/^[\n\r]\s*|\s+/g,
+	                " ")`
+	@param          ini_string       The string to collapse -- multi-line escape
+	                                 sequences must be already unescaped at
+	                                 this stage
+	@param          format           The format of the INI file
+	@return         The new length of the string
+
+**/
+static size_t collapse_everything (
+	char * const ini_string,
+	const IniFormat format
+) {
+
+	/*
+
+	Mask `abcd` (9 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     We are in an odd sequence of backslashes
+		FLAG_32     This is *not* a space out of quotes
+		FLAG_64     This is an opening single/double quote
+		FLAG_128    Don't ignore this character
+		FLAG_256    Jump this character and the one before this
+
+	*/
+
+	register size_t idx_s = 0, idx_d = 0;
+
+	register uint_least16_t abcd =
+		(is_some_space(*ini_string, _CONFINI_WITH_EOL_) ? 128 : 160) |
+		(format.no_double_quotes << 1) |
+		format.no_single_quotes;
+
+	for (; ini_string[idx_s]; idx_s++) {
+
+		/*  Revision #2  */
+
+		abcd =
+
+			!(abcd & 12) && is_some_space(ini_string[idx_s], _CONFINI_WITH_EOL_) ?
+				(
+					abcd & 32 ?
+						(abcd & 143) | 128
+					:
+						abcd & 47
+				)
+			: !(abcd & 25) && ini_string[idx_s] == _CONFINI_S_QUOTES_ ?
+				(
+					~abcd & 4 ?
+						(abcd & 239) | 196
+					: abcd & 64 ?
+						(abcd & 299) | 256
+					:
+						(abcd & 171) | 160
+				)
+			: !(abcd & 22) && ini_string[idx_s] == _CONFINI_D_QUOTES_ ?
+				(
+					~abcd & 8 ?
+						(abcd & 239) | 200
+					: abcd & 64 ?
+						(abcd & 295) | 256
+					:
+						(abcd & 167) | 160
+				)
+			: ini_string[idx_s] == _CONFINI_BACKSLASH_ ?
+				((abcd & 191) | 160) ^ 16
+			:
+				(abcd & 175) | 160;
+
+
+		if (abcd & 256) {
+
+			idx_d--;
+
+		} else if (abcd & 128) {
+
+			ini_string[idx_d++] =
+				abcd & 44 ?
+					ini_string[idx_s]
+				:
+					_CONFINI_COLLAPSED_;
+
+		}
+
+	}
+
+	for (
+
+		idx_s =
+			!(abcd & 32) && idx_d ?
+				--idx_d
+			:
+				idx_d;
+
+			ini_string[idx_s];
+
+		ini_string[idx_s++] = '\0'
+
+	);
+
+	return idx_d;
+
+}
+
+
+/**
+
+	@brief          Out of quotes similar to ECMAScript
+	                `ini_string.replace(/\s+/g, " ")`
+	@param          ini_string      The string to collapse -- multi-line escape
+	                                sequences must be already unescaped at this
+	                                stage
+	@param          format          The format of the INI file
+	@return         The new length of the string
+
+**/
+static size_t collapse_spaces (
+	char * const ini_string,
+	const IniFormat format
+) {
+
+	/*
+
+	Mask `abcd` (7 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     We are in an odd sequence of backslashes
+		FLAG_32     This is a space out of quotes
+		FLAG_64     Jump this character
+
+	*/
+
+	register uint_least8_t abcd =
+		(format.no_double_quotes ? 34 : 32) | format.no_single_quotes;
+
+	register size_t idx_s = 0;
+	size_t idx_d = 0;
+
+	for (; ini_string[idx_s]; idx_s++) {
+
+		/*  Revision #1  */
+
+		abcd =
+
+			!(abcd & 12) && is_some_space(ini_string[idx_s], _CONFINI_WITH_EOL_) ?
+				(
+					abcd & 32 ?
+						(abcd & 111) | 64
+					:
+						(abcd & 47) | 32
+				)
+			: !(abcd & 25) && ini_string[idx_s] == _CONFINI_S_QUOTES_ ?
+				(abcd & 15) ^ 4
+			: !(abcd & 22) && ini_string[idx_s] == _CONFINI_D_QUOTES_ ?
+				(abcd & 15) ^ 8
+			: ini_string[idx_s] == _CONFINI_BACKSLASH_ ?
+				(abcd & 31) ^ 16
+			:
+				abcd & 15;
+
+		if (~abcd & 64) {
+
+			ini_string[idx_d++] =
+				abcd & 32 ?
+					_CONFINI_COLLAPSED_
+				:
+					ini_string[idx_s];
+
+		}
+
+	}
+
+	for (
+
+		idx_s =
+			(abcd & 32) && idx_d ?
+				--idx_d
+			:
+				idx_d;
+
+			ini_string[idx_s];
+
+		ini_string[idx_s++] = '\0'
+
+	);
+
+	return idx_d;
+
+}
+
+
+/**
+
+	@brief          Similar to ECMAScript `str.replace(/''|""/g, "")`
+	@param          str             The string to collapse
+	@param          format          The format of the INI file
+	@return         The new length of the string
+
+**/
+static size_t collapse_empty_quotes (
+	char * const str,
+	const IniFormat format
+) {
+
+	/*
+
+	Mask `abcd` (7 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     We are in an odd sequence of backslashes
+		FLAG_32     This is an opening single/double quote
+		FLAG_64     These are empty quotes
+
+	*/
+
+	register uint_least8_t abcd =
+		(format.no_double_quotes << 1) | format.no_single_quotes;
+
+	register size_t lshift = ltrim_s(str, 0, _CONFINI_WITH_EOL_), idx = lshift;
+
+	for (; str[idx]; idx++) {
+
+		/*  Revision #1  */
+
+		abcd =
+
+			str[idx] == _CONFINI_BACKSLASH_ ?
+				(abcd & 31) ^ 16
+			: !(abcd & 22) && str[idx] == _CONFINI_D_QUOTES_ ?
+				(
+					~abcd & 40 ?
+						((abcd & 47) | 32) ^ 8
+					:
+						(abcd & 71) | 64
+				)
+			: !(abcd & 25) && str[idx] == _CONFINI_S_QUOTES_ ?
+				(
+					~abcd & 36 ?
+						((abcd & 47) | 32) ^ 4
+					:
+						(abcd & 75) | 64
+				)
+			:
+				abcd & 15;
+
+
+		str[idx - lshift] = str[idx];
+
+		if (abcd & 64) {
+
+			lshift += 2;
+
+		}
+
+	}
+
+	for (idx -= lshift; str[idx]; str[idx++] = '\0');
+
+	return rtrim_h(str, idx - lshift, _CONFINI_WITH_EOL_);
+
+}
+
+
+/**
+
+	@brief          Remove all comment initializers (`#` and/or `;`) from the
+	                beginning of each line of a comment
+	@param          srcstr          The comment to parse (it may contain multi-line
+	                                escape sequences)
+	@param          len             The length of @p srcstr
+	@param          format          The format of the INI file
+	@return         The new length of the string
+
+	- Multi-line comments: `srcstr.replace(/^[#;]+|(\n\r?|\r\n?)[\t \v\f]*[#;]+/g,
+      "$1")`
+	- Single-line comments: `srcstr.replace(/^[#;]+/, "")`
+
+	The argument @p srcstr may begin with a comment initializer (`#` or `;`
+	depending on the format), or with the character that immediately follows it.
+
+**/
+static size_t uncomment (
+	char * const srcstr,
+	size_t len,
+	const IniFormat format
+) {
+
+	register size_t idx_s = 0, idx_d = 0;
+
+	if (format.multiline_nodes == INI_MULTILINE_EVERYWHERE) {
+
+		/*  The comment can be multi-line  */
+
+		/*
+
+		Mask `abcd` (6 bits used):
+
+			FLAG_1      Don't erase any character
+			FLAG_2      We are in an odd sequence of backslashes
+			FLAG_4      This new line character is escaped
+			FLAG_8      This character is a comment character and follows
+			            `/(\n\s*|\r\s*)/`
+			FLAG_16     This character is a part of a group of spaces that follow
+			            a new line (`/(\n|\r)[\t \v\f]+/`)
+			FLAG_32     This character is *not* a new line character (`/[\r\n]/`)
+
+		*/
+
+		for (register uint_least8_t abcd = 8; idx_s < len; idx_s++) {
+
+			abcd =
+
+				srcstr[idx_s] == _CONFINI_BACKSLASH_ ?
+					((abcd & 35) | 32) ^ 2
+				: srcstr[idx_s] == _CONFINI_LF_ || srcstr[idx_s] == _CONFINI_CR_ ?
+					(abcd << 1) & 4
+				: !(abcd & 32) && _CONFINI_IS_ANY_MARKER_(srcstr[idx_s], format) ?
+					(abcd & 40) | 8
+				: !(abcd & 40) && is_some_space(srcstr[idx_s], _CONFINI_NO_EOL_) ?
+					(abcd & 57) | 16
+				:
+					(abcd & 33) | 32;
+
+
+			if (!(abcd & 25)) {
+
+				srcstr[abcd & 4 ? idx_d - 1 : idx_d++] = srcstr[idx_s];
+
+			} else if (!(abcd & 28)) {
+
+				idx_d++;
+
+			}
+
+		}
+
+	} else {
+
+		/*  The comment cannot be multi-line  */
+
+		while (idx_s < len && _CONFINI_IS_ANY_MARKER_(srcstr[idx_s], format)) {
+
+			idx_s++;
+
+		}
+
+		if (!idx_s) {
+
+			return len;
+
+		}
+
+		while (idx_s < len) {
+
+			srcstr[idx_d++] = srcstr[idx_s++];
+		}
+
+	}
+
+	for (
+		idx_s = idx_d;
+			idx_s < len;
+		srcstr[idx_s++] = '\0'
+	);
+
+	return idx_d;
+
+}
+
+
+/**
+
+	@brief          Try to determine the type of a member **assuming it is active**
+	@param          srcstr          String containing an individual node (it may
+	                                contain multi-line escape sequences)
+	@param          len             Length of the node
+	@param          allow_implicit  A boolean: `true` if keys without a key-value
+	                                delimiter are allowed, `false` otherwise
+	@param          format          The format of the INI file
+	@return         The active node type (see confini.h)
+
+**/
+static uint_least8_t get_type_as_active (
+	const char * const srcstr,
+	const size_t len,
+	const _CONFINI_CHARBOOL_ allow_implicit,
+	const IniFormat format
+) {
+
+	const _CONFINI_CHARBOOL_ invalid_delimiter =
+		_CONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format);
+
+	if (
+		!len || _CONFINI_IS_ANY_MARKER_(*srcstr, format) || (
+			*((unsigned char *) srcstr) == format.delimiter_symbol &&
+			!invalid_delimiter
+		)
+	) {
+
+		return INI_UNKNOWN;
+
+	}
+
+	register uint_least16_t abcd;
+	register size_t idx;
+
+	if (
+		format.section_paths != INI_NO_SECTIONS &&
+		*srcstr == _CONFINI_OPEN_SECTION_
+	) {
+
+		if (format.no_spaces_in_names) {
+
+			/*
+
+				Search for the CLOSE SECTION character and possible spaces in names
+				-- i.e., ECMAScript `/[^\.\s]\s+[^\.\s]/g.test(srcstr)`. The
+				algorithm is made more complex by the fact that LF and CR characters
+				are still escaped at this stage.
+
+			*/
+
+			/*
+
+			Mask `abcd` (10 bits used):
+
+				FLAG_1      Single quotes are not metacharacters (const)
+				FLAG_2      Double quotes are not metacharacters (const)
+				FLAG_4      Only one level of nesting is allowed (const)
+				FLAG_8      Unescaped single quotes are odd right now
+				FLAG_16     Unescaped double quotes are odd right now
+				FLAG_32     We are in an odd sequence of backslashes
+				FLAG_64     This is a space
+				FLAG_128    What follows cannot contain spaces
+				FLAG_256    Continue the loop
+				FLAG_512    Section path is *not* valid
+
+			*/
+
+			idx = 1;
+
+			abcd =
+				(format.section_paths == INI_ONE_LEVEL_ONLY ? 772 : 768) |
+				(format.no_double_quotes << 1) |
+				format.no_single_quotes;
+
+			do {
+
+				/*  Revision #2  */
+
+				abcd =
+
+					idx >= len ?
+						abcd & 767
+					: !(abcd & 42) && srcstr[idx] == _CONFINI_D_QUOTES_ ?
+						(abcd & 991) ^ 16
+					: !(abcd & 49) && srcstr[idx] == _CONFINI_S_QUOTES_ ?
+						(abcd & 991) ^ 8
+					: srcstr[idx] == _CONFINI_LF_ || srcstr[idx] == _CONFINI_CR_ ?
+						(abcd & 991) | 64
+					: is_some_space(srcstr[idx], _CONFINI_NO_EOL_) ?
+						(
+							~abcd & 32 ?
+								abcd | 64
+							: ~abcd & 192 ?
+								(abcd & 991) | 192
+							:
+								(abcd & 767) | 128
+						)
+					: !(abcd & 28) && srcstr[idx] == _CONFINI_SUBSECTION_ ?
+						(
+							~abcd & 224 ?
+								abcd & 799
+							:
+								abcd & 767
+						)
+					: !(abcd & 24) && srcstr[idx] == _CONFINI_CLOSE_SECTION_ ?
+						(
+							~abcd & 224 ?
+								abcd & 159
+							:
+								abcd & 767
+						)
+					: srcstr[idx] == _CONFINI_BACKSLASH_ ?
+						(
+							~abcd & 32 ?
+								abcd | 32
+							: ~abcd & 192 ?
+								(abcd & 991) | 128
+							:
+								abcd & 735
+						)
+					: ~abcd & 192 ?
+						(abcd & 927) | 128
+					:
+						(abcd & 671) | 128;
+
+
+				idx++;
+
+			} while (abcd & 256);
+
+			if (abcd & 512) {
+
+				return INI_UNKNOWN;
+
+			}
+
+		} else if (
+			(idx = getn_metachar_pos(
+				srcstr,
+				_CONFINI_CLOSE_SECTION_,
+				len,
+				format
+			) + 1) > len
+		) {
+
+			return INI_UNKNOWN;
+
+		}
+
+		/*
+
+			Scan for possible non-space characters following the CLOSE SECTION
+			character: if found the node cannot represent a section path (but it can
+			possibly represent a key). Empty quotes will be tolerated.
+
+		*/
+
+		/*
+
+		Recycling variable `abcd` (6 bits used)...:
+
+			FLAG_1      Single quotes are not metacharacters (const)
+			FLAG_2      Double quotes are not metacharacters (const)
+			FLAG_4      Unescaped single quotes are odd right now
+			FLAG_8      Unescaped double quotes are odd right now
+			FLAG_16     We are in an odd sequence of backslashes
+			FLAG_32     Continue the loop
+
+		*/
+
+		abcd = (format.no_double_quotes ? 34 : 32) | format.no_single_quotes;
+
+
+		/* \                                /\
+		\ */     non_space_check:          /* \
+		 \/     ______________________     \ */
+
+
+		if (idx >= len) {
+
+			return INI_SECTION;
+
+		}
+
+		switch (srcstr[idx++]) {
+
+			case _CONFINI_VT_:
+			case _CONFINI_FF_:
+			case _CONFINI_HT_:
+			case _CONFINI_SIMPLE_SPACE_:
+
+				if (abcd & 28) {
+
+					break;
+
+				}
+
+				abcd &= 47;
+				goto non_space_check;
+
+			case _CONFINI_LF_:
+			case _CONFINI_CR_:
+
+				if (abcd & 12) {
+
+					break;
+
+				}
+
+				abcd &= 47;
+				goto non_space_check;
+
+			case _CONFINI_BACKSLASH_:
+
+				if (abcd & 28) {
+
+					break;
+
+				}
+
+				abcd |= 16;
+				goto non_space_check;
+
+			case _CONFINI_S_QUOTES_:
+
+				if (abcd & 25) {
+
+					break;
+
+				}
+
+				abcd = (abcd & 47) ^ 4;
+				goto non_space_check;
+
+			case _CONFINI_D_QUOTES_:
+
+				if (abcd & 22) {
+
+					break;
+
+				}
+
+				abcd = (abcd & 47) ^ 8;
+				goto non_space_check;
+
+		}
+
+	}
+
+	/*
+
+		It can be just a key or `INI_UNKNOWN`...
+
+	*/
+
+	if (invalid_delimiter && !allow_implicit) {
+
+		return INI_UNKNOWN;
+
+	}
+
+	/*
+
+	Recycling variable `abcd` (2 bits used)...:
+
+		FLAG_1      The delimiter **must** be present
+		FLAG_2      Search for spaces in names
+
+	*/
+
+	abcd = (format.no_spaces_in_names << 1) | !allow_implicit;
+
+	if (abcd) {
+
+		idx = getn_metachar_pos(
+			srcstr,
+			(char) format.delimiter_symbol,
+			len,
+			format
+		);
+
+		if ((abcd & 1) && idx == len) {
+
+			return INI_UNKNOWN;
+
+		}
+
+		if (abcd & 2) {
+
+			idx = urtrim_s(srcstr, idx);
+
+			do {
+
+				if (is_some_space(srcstr[--idx], _CONFINI_WITH_EOL_)) {
+
+					return INI_UNKNOWN;
+
+				}
+
+			} while (idx);
+
+		}
+
+	}
+
+	return INI_KEY;
+
+}
+
+
+/**
+
+	@brief          Examine a (single-/multi-line) segment and check whether
+	                it contains more than just one node
+	@param          srcstr          Segment to examine (it may contain multi-line
+	                                escape sequences)
+	@param          format          The format of the INI file
+	@return         Number of entries found
+
+**/
+static size_t further_cuts (
+	char * const srcstr,
+	const IniFormat format
+) {
+
+	/*  Abandon hope all ye who enter here  */
+
+	/*
+
+	Shared flags of mask `abcd` (8 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Do not allow disabled entries after space (const)
+		FLAG_8      Formats supports multi-line entries everywhere (const)
+		FLAG_16     Formats supports multi-line entries everywhere except in
+		            comments (const)
+		FLAG_32     Unescaped single quotes are odd right now
+		FLAG_64     Unescaped double quotes are odd right now
+		FLAG_128    We are in an odd sequence of backslashes
+
+	*/
+
+	register uint_least16_t abcd =
+		((format.disabled_after_space << 2) ^ 4) |
+		(format.no_double_quotes << 1) |
+		format.no_single_quotes | (
+			format.multiline_nodes == INI_MULTILINE_EVERYWHERE ?
+				8
+			: format.multiline_nodes == INI_BUT_COMMENTS ?
+				16
+			:
+				0
+		);
+
+	register size_t idx;
+	size_t focus_at, unparsable_at, search_at = 0, num_entries = 0;
+
+
+	/* \                                /\
+	\ */     search_for_cuts:          /* \
+	 \/     ______________________     \ */
+
+
+	if (!srcstr[search_at]) {
+
+		return num_entries;
+
+	}
+
+	unparsable_at = 0;
+
+	abcd =
+
+		_CONFINI_IS_DIS_MARKER_(srcstr[search_at], format) && (
+			!(abcd & 4) ||
+			!is_some_space(srcstr[search_at + 1], _CONFINI_NO_EOL_)
+		) ?
+			(abcd & 31) | 2560
+		: _CONFINI_IS_IGN_MARKER_(srcstr[search_at], format) ?
+			(abcd & 8 ? (abcd & 31) | 1024 : abcd & 31)
+		: (abcd & 8) && (
+			srcstr[search_at] == _CONFINI_IC_INT_MARKER_ ||
+			_CONFINI_IS_ANY_MARKER_(srcstr[search_at], format)
+		) ?
+			(abcd & 31) | 3072
+		:
+			(abcd & 31) | 2048;
+
+
+	if (abcd & 2048) {
+
+		num_entries++;
+
+	}
+
+	if (abcd & 1536) {
+
+		/*
+
+			Node starts with `/[;#]/` and can be a disabled entry in any format, or
+			a simple comment or a block that must be ignored in multi-line formats
+
+		*/
+
+		/*
+
+		Mask `abcd` (14 bits used):
+
+			FLAG_256    This or the previous character was not a space
+			FLAG_512    We are in a disabled entry or a comment (semi-const)
+			FLAG_1024   We are in a simple comment or in a block that must be
+			            ignored and format supports multi-line entries (semi-const)
+			FLAG_2048   We are *not* in a block that must be ignored (semi-const)
+			FLAG_4096   We have *not* just found an inline comment nested within a
+			            disabled entry
+			FLAG_8192   We had previously found an inline comment nested in this
+			            segment, but the entry that preceded it had been checked and
+			            did not seem to represent a valid disabled entry
+
+		NOTE:   As for FLAG_1-FLAG_16 I keep the values already assigned at the
+		        beginning of the function; all other flags are already set to zero.
+		        For the meaning of flags FLAG_1-FLAG_128 see the beginning of the
+		        function.
+
+		*/
+
+		idx = ltrim_s(srcstr, search_at + 1, _CONFINI_NO_EOL_) - 1;
+
+
+		/* \                                /\
+		\ */     inactive_cut:             /* \
+		 \/     ______________________     \ */
+
+
+		switch (srcstr[++idx]) {
+
+			case '\0':
+
+				/*  End of string  */
+
+				if (~abcd & 8) {
+
+					/*
+
+						Check if this is a valid disabled entry. If it is not,
+						search for line breaks.
+
+						If the code has reached this point it means that according
+						to the format disabled entries can be multi-line but
+						comments cannot, and #get_type_as_active() has never been
+						invoked on this entry.
+
+					*/
+
+					focus_at = dqultrim_s(srcstr, search_at, format);
+
+					if (
+						srcstr[focus_at] && !get_type_as_active(
+							srcstr + focus_at,
+							idx - focus_at,
+							format.disabled_can_be_implicit,
+							format
+						)
+					) {
+
+						srcstr[search_at] = _CONFINI_BC_INT_MARKER_;
+						unparsable_at = search_at + 1;
+
+					}
+
+				}
+
+				break;
+
+			case _CONFINI_LF_:
+			case _CONFINI_CR_:
+
+				/*
+
+					Line break has been found in a multi-line disabled entry or
+					a comment. Search for `/\\(?:\n\r?|\r\n?)\s*[^;#]/`.
+
+				*/
+
+				focus_at = dqultrim_s(srcstr, search_at, format);
+				idx = ltrim_s(srcstr, idx + 1, _CONFINI_WITH_EOL_);
+
+				if (
+					idx <= focus_at && (abcd & 2048) && (
+						(
+							_CONFINI_IS_DIS_MARKER_(srcstr[idx], format) && !(
+								(abcd & 24) && (
+									(~abcd & 516) || !is_some_space(
+										srcstr[idx + 1],
+										_CONFINI_NO_EOL_
+									)
+								)
+							)
+						) || _CONFINI_IS_COM_MARKER_(srcstr[idx], format)
+					)
+				) {
+
+					/*
+
+						See issue #16
+
+						This entry is composed of one or more empty lines
+						followed by a simple a comment. The whole block will be
+						marked as a comment.
+
+					*/
+
+					srcstr[search_at] = _CONFINI_BC_INT_MARKER_;
+
+					if (abcd & 8) {
+
+						abcd = (abcd & 15871) | 8192;
+						goto inactive_cut;
+
+					}
+
+					unparsable_at = search_at + 1;
+					break;
+
+				}
+
+				if (
+					abcd & 512 ?
+						!(abcd & 24) || (
+							(
+								!_CONFINI_IS_DIS_MARKER_(srcstr[idx], format) || (
+									(abcd & 4) && is_some_space(
+										srcstr[idx + 1],
+										_CONFINI_NO_EOL_
+									)
+								)
+							) && /*  see issue #16  */ idx > focus_at &&
+							get_type_as_active(
+								srcstr + focus_at,
+								idx - focus_at,
+								format.disabled_can_be_implicit,
+								format
+							)
+						) || !_CONFINI_IS_ANY_MARKER_(srcstr[idx], format)
+					: abcd & 2048 ?
+						!(abcd & 8) ||
+						!_CONFINI_IS_N_I_MARKER_(srcstr[idx], format)
+					:
+						!_CONFINI_IS_IGN_MARKER_(srcstr[idx], format)
+				) {
+
+					/*  Split this line from the previous entry  */
+
+					rtrim_h(srcstr, idx, _CONFINI_WITH_EOL_);
+					search_at = qultrim_h(srcstr, idx, format);
+					goto search_for_cuts;
+
+				}
+
+				/*
+
+					No case break here, keep it like this! `case /[ \t\v\f]/` must
+					follow (switch case fallthrough).
+
+				*/
+
+			case _CONFINI_VT_:
+			case _CONFINI_FF_:
+			case _CONFINI_HT_:
+			case _CONFINI_SIMPLE_SPACE_:
+
+				abcd = (abcd & 15999) | 4096;
+				goto inactive_cut;
+
+			case _CONFINI_BACKSLASH_:
+
+				abcd = (abcd | 4352) ^ 128;
+				goto inactive_cut;
+
+			default:
+
+				abcd =
+
+					!(abcd & 1376) && (~abcd & 8200) &&
+					_CONFINI_IS_ANY_MARKER_(srcstr[idx], format) ?
+						(abcd & 12159) | 256
+					: !(abcd & 162) && srcstr[idx] == _CONFINI_D_QUOTES_ ?
+						((abcd & 16255) | 4352) ^ 64
+					: !(abcd & 193) && srcstr[idx] == _CONFINI_S_QUOTES_ ?
+						((abcd & 16255) | 4352) ^ 32
+					:
+						(abcd & 16255) | 4352;
+
+
+				if (abcd & 4096) {
+
+					/*  Nothing special has been found  */
+					goto inactive_cut;
+
+				}
+
+				if (~abcd & 8192) {
+
+					/*
+
+						An inline comment has been found in a (supposedly) disabled
+						entry.
+
+					*/
+
+					focus_at = dqultrim_s(srcstr, search_at, format);
+
+					if (
+						get_type_as_active(
+							srcstr + focus_at,
+							idx - focus_at,
+							format.disabled_can_be_implicit,
+							format
+						)
+					) {
+
+						if (!_CONFINI_IS_IGN_MARKER_(srcstr[idx], format)) {
+
+							srcstr[idx] = _CONFINI_IC_INT_MARKER_;
+							num_entries++;
+
+						}
+
+						srcstr[idx - 1] = '\0';
+
+						if (abcd & 8) {
+
+							abcd &= 15871;
+							goto inactive_cut;
+
+						}
+
+						unparsable_at = idx + 1;
+
+					} else {
+
+						srcstr[search_at] = _CONFINI_BC_INT_MARKER_;
+
+						if (abcd & 8) {
+
+							abcd = (abcd & 15871) | 8192;
+							goto inactive_cut;
+
+						}
+
+						unparsable_at = search_at + 1;
+
+					}
+
+				}
+
+				/*  No case break here (last case)  */
+
+		}
+
+	} else if (_CONFINI_IS_ANY_MARKER_(srcstr[search_at], format)) {
+
+		/*
+
+			Node starts with `/[;#]/` but cannot be multi-line or represent a
+			disabled entry
+
+		*/
+
+		unparsable_at = search_at + 1;
+
+	} else {
+
+		/*
+
+			Node is active: search for inline comments
+
+		*/
+
+		/*
+
+		Recycling variable `abcd` (11 bits used)...:
+
+			FLAG_256    Comment marker follows an escaped new line made of only one
+			            character (i.e., `"\\\n"` or `"\\\r"` but neither `"\\\r\n"`
+			            nor `"\\\n\r"`)
+			FLAG_512    This was neither a hash nor a semicolon character
+			FLAG_1024   This was not a space
+
+		NOTE:   As for FLAG_1-FLAG_16 I keep the values already assigned at the
+		        beginning of the function; all other flags are already set to zero
+		        (see previous usage of `abcd` within this function), with the only
+		        exception of FLAG_2048, which I am going to overwrite immediately.
+		        For the meaning of flags FLAG_1-FLAG_128 see the beginning of the
+		        function.
+
+		*/
+
+		abcd = (abcd & 2047) | 1536;
+		idx = search_at;
+
+
+		/* \                                /\
+		\ */     active_cut:               /* \
+		 \/     ______________________     \ */
+
+
+		switch (srcstr[++idx]) {
+
+			case '\0':
+
+				/*  End of string  */
+				break;
+
+			case _CONFINI_VT_:
+			case _CONFINI_FF_:
+			case _CONFINI_HT_:
+			case _CONFINI_SIMPLE_SPACE_:
+
+				abcd = (abcd & 639) | 512;
+				goto active_cut;
+
+			case _CONFINI_LF_:
+			case _CONFINI_CR_:
+
+				abcd = (abcd & 639) | ((abcd << 1) & 256) | 512;
+				goto active_cut;
+
+			case _CONFINI_BACKSLASH_:
+
+				abcd = ((abcd & 1791) | 1536) ^ 128;
+				goto active_cut;
+
+			default:
+
+				abcd =
+
+					_CONFINI_IS_ANY_MARKER_(srcstr[idx], format) ?
+						abcd & 1407
+					: !(abcd & 162) && srcstr[idx] == _CONFINI_D_QUOTES_ ?
+						((abcd & 1791) | 1536) ^ 64
+					: !(abcd & 193) && srcstr[idx] == _CONFINI_S_QUOTES_ ?
+						((abcd & 1791) | 1536) ^ 32
+					:
+						(abcd & 1791) | 1536;
+
+
+				if (abcd & 1760) {
+
+					/*  Nothing special has been found  */
+					goto active_cut;
+
+				}
+
+				/*
+
+					Inline comment has been found in an active entry.
+
+				*/
+
+				if (abcd & 256) {
+
+					/*
+
+						Remove the backslash if the comment immediately follows an
+						escaped new line expressed by one chararacter
+						(`/\\[\r\n]/`). In case of CR + LF or LF + CR
+						(`/\\\n\r|\\\r\n/`) the backslash will be removed later by
+						#strip_ini_cache().
+
+					*/
+
+					srcstr[idx - 2] = '\0';
+
+				}
+
+				srcstr[idx - 1] = '\0';
+
+				if (!_CONFINI_IS_IGN_MARKER_(srcstr[idx], format)) {
+
+					srcstr[idx] = _CONFINI_IC_INT_MARKER_;
+
+					if (abcd & 8) {
+
+						search_at = idx;
+						goto search_for_cuts;
+
+					}
+
+					num_entries++;
+
+				} else if (abcd & 8) {
+
+					search_at = idx;
+					goto search_for_cuts;
+
+				}
+
+				unparsable_at = idx + 1;
+				/*  No case break here (last case)  */
+
+		}
+
+	}
+
+	if (unparsable_at) {
+
+		/*
+
+			Cut unparsable multi-line comments
+
+		*/
+
+		for (idx = unparsable_at; srcstr[idx]; idx++) {
+
+			if (srcstr[idx] == _CONFINI_LF_ || srcstr[idx] == _CONFINI_CR_) {
+
+				search_at = qultrim_h(srcstr, idx, format);
+				goto search_for_cuts;
+
+			}
+
+		}
+
+	}
+
+	return num_entries;
+
+}
+
+/** @startfnlist **/
+
+
+
+		/*\
+		|*|
+		|*|     GLOBAL ENVIRONMENT
+		|*|    ________________________________
+		\*/
+
+
+
+		/*  LIBRARY'S MAIN FUNCTIONS  */
+
+
+                                                   /** @utility{strip_ini_cache} **/
+/**
+
+	@brief          Parse and tokenize a buffer containing an INI file, then
+	                dispatch its content to a custom callback
+	@param          ini_source      The buffer containing the INI file to tokenize
+	@param          ini_length      The length of @p ini_source without counting the
+	                                NUL terminator (if any -- se below)
+	@param          format          The format of the INI file
+	@param          f_init          The function that will be invoked before the
+	                                first dispatch, or `NULL`
+	@param          f_foreach       The function that will be invoked for each
+	                                dispatch, or `NULL`
+	@param          user_data       A custom argument, or `NULL`
+	@return         Zero for success, otherwise an error code (see `enum`
+	                #ConfiniInterruptNo)
+
+	The @p ini_source parameter must be a valid pointer to a buffer of size
+	@p ini_length + 1 and cannot be `NULL`. The @p ini_source string does not need
+	to be NUL-terminated, but _it does need one extra byte where to append a NUL
+	terminator_ -- in fact, as soon as this function is invoked,
+	`ini_source[ini_length]` will be immediately set to `\0`.
+
+	In most cases, as when using `strlen()` for computing @p ini_length, this is not
+	a concern, since `ini_source[ini_length]` will always be `\0` by the very
+	definition of `strlen()`, and will only get overwritten with the same value.
+	However, if you are passing a substring of a string, for example the fragment
+	`foo=bar` of the string `foo=barracuda`, you must expect the string to be
+	immediately truncated into `foo=bar\0acuda`.
+
+	In other words, @p ini_source must point to a memory location where at least
+	`ini_length + 1` bytes are freely usable.
+
+	The user given function @p f_init (see #IniStatsHandler data type) will be
+	invoked with two arguments: `statistics` (a pointer to an #IniStatistics
+	structure containing some properties about the file read) and `user_data` (the
+	custom argument @p user_data previously passed). If @p f_init returns a non-zero
+	value the caller function will be interrupted.
+
+	The user given function @p f_foreach (see #IniDispHandler data type) will be
+	invoked with two arguments: `dispatch` (a pointer to an #IniDispatch structure
+	containing the parsed member of the INI file) and `user_data` (the custom
+	argument @p user_data previously passed). If @p f_foreach returns a non-zero
+	value the caller function will be interrupted.
+
+	After invoking `strip_ini_cache()`, the buffer pointed by the @p ini_source
+	parameter must be considered as a _corrupted buffer_ and should be freed or
+	overwritten. For more information about this function, please refer to the
+	@ref libconfini.
+
+	The parsing algorithms used by **libconfini** are able to parse any type of file
+	encoded in 8-bit code units, as long as the characters that match the regular
+	expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in
+	ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of
+	platform-specific conventions.
+
+	@note   In order to be null-byte-injection-safe, before dispatching the parsed
+	        content this function strips all `NUL` characters possibly present in
+	        the buffer (with the exception of the last one).
+
+	Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR,
+	#CONFINI_EOOR.
+
+	@include topics/strip_ini_cache.c
+
+**/
+int strip_ini_cache (
+	register char * const ini_source,
+	const size_t ini_length,
+	const IniFormat format,
+	const IniStatsHandler f_init,
+	const IniDispHandler f_foreach,
+	void * const user_data
+) {
+
+	const _CONFINI_CHARBOOL_ valid_delimiter =
+		!_CONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format);
+
+	_CONFINI_CHARBOOL_ tmp_bool;
+	register size_t idx, tmp_fast_size_t_1, tmp_fast_size_t_2;
+	size_t tmp_size_t_1, tmp_size_t_2;
+
+	ini_source[ini_length] = '\0';
+
+	/*
+
+		PART ONE: Examine and isolate each segment
+
+	*/
+
+	#define __ISNT_ESCAPED__ tmp_bool
+	#define __LSHIFT__ tmp_fast_size_t_1
+	#define __EOL_N__ tmp_fast_size_t_2
+	#define __NL_AT__ tmp_size_t_1
+	#define __N_MEMBERS__ tmp_size_t_2
+
+	/*  UTF-8 BOM  */
+	__LSHIFT__ =
+		*((unsigned char *) ini_source) == 0xEF &&
+		*((unsigned char *) ini_source + 1) == 0xBB &&
+		*((unsigned char *) ini_source + 2) == 0xBF
+		? 3 : 0;
+
+	for (
+
+		__N_MEMBERS__ = 0,
+		__EOL_N__ = _CONFINI_EOL_IDX_,
+		__ISNT_ESCAPED__ = _CONFINI_TRUE_,
+		__NL_AT__ = 0,
+		idx = __LSHIFT__;
+
+			idx < ini_length;
+
+		idx++
+
+	) {
+
+		ini_source[idx - __LSHIFT__] = ini_source[idx];
+
+		if (
+			ini_source[idx] == _CONFINI_SPACES_[__EOL_N__] ||
+			ini_source[idx] == _CONFINI_SPACES_[__EOL_N__ ^= 1]
+		) {
+
+			if (format.multiline_nodes == INI_NO_MULTILINE || __ISNT_ESCAPED__) {
+
+				ini_source[idx - __LSHIFT__] = '\0';
+				__N_MEMBERS__ += further_cuts(
+					ini_source + qultrim_h(ini_source, __NL_AT__, format),
+					format
+				);
+				__NL_AT__ = idx - __LSHIFT__ + 1;
+
+			} else if (ini_source[idx + 1] == _CONFINI_SPACES_[__EOL_N__ ^ 1]) {
+
+				idx++;
+				ini_source[idx - __LSHIFT__] = ini_source[idx];
+
+			}
+
+			__ISNT_ESCAPED__ = _CONFINI_TRUE_;
+
+		} else if (ini_source[idx] == _CONFINI_BACKSLASH_) {
+
+			__ISNT_ESCAPED__ = !__ISNT_ESCAPED__;
+
+		} else if (ini_source[idx]) {
+
+			__ISNT_ESCAPED__ = _CONFINI_TRUE_;
+
+		} else {
+
+			/*  Remove `NUL` characters from the buffer (if any)  */
+			__LSHIFT__++;
+
+		}
+
+	}
+
+	const size_t real_length = idx - __LSHIFT__;
+
+	while (idx > real_length) {
+
+		ini_source[--idx] = '\0';
+
+	}
+
+	__N_MEMBERS__ += further_cuts(
+		ini_source + qultrim_h(ini_source, __NL_AT__, format),
+		format
+	);
+
+	/*  Debug  */
+
+	/*
+
+	for (size_t tmp = 0; tmp < ini_length + 1; tmp++) {
+		putchar(ini_source[tmp] == 0 ? '$' : ini_source[tmp]);
+	}
+	putchar('\n');
+
+	*/
+
+	IniStatistics this_doc = {
+		.format = format,
+		.bytes = ini_length,
+		.members = __N_MEMBERS__
+	};
+
+	if (f_init && f_init(&this_doc, user_data)) {
+
+		return CONFINI_IINTR;
+
+	}
+
+	#undef __N_MEMBERS__
+	#undef __NL_AT__
+	#undef __EOL_N__
+	#undef __LSHIFT__
+	#undef __ISNT_ESCAPED__
+
+	/*
+
+		PART TWO: Dispatch the parsed input
+
+	*/
+
+	if (!f_foreach) {
+
+		return CONFINI_SUCCESS;
+
+	}
+
+	#define __NODE_AT__ tmp_fast_size_t_1
+	#define __CURR_PARENT_LEN__ tmp_fast_size_t_2
+	#define __SUBPARENT_LEN__ tmp_size_t_1
+	#define __REAL_PARENT_LEN__ tmp_size_t_2
+	#define __PARENT_IS_DISABLED__ tmp_bool
+
+	__REAL_PARENT_LEN__ = __CURR_PARENT_LEN__ = __SUBPARENT_LEN__  = 0;
+
+	char
+		* curr_parent_str = ini_source + real_length,
+		* subparent_str = curr_parent_str,
+		* real_parent_str = curr_parent_str;
+
+	IniDispatch dsp = {
+		.format = format,
+		.dispatch_id = 0
+	};
+
+	__PARENT_IS_DISABLED__ = _CONFINI_FALSE_;
+
+	for (__NODE_AT__ = 0, idx = 0; idx <= real_length; idx++) {
+
+		if (ini_source[idx]) {
+
+			continue;
+
+		}
+
+		if (
+			!ini_source[__NODE_AT__] ||
+			_CONFINI_IS_IGN_MARKER_(ini_source[__NODE_AT__], format)
+		) {
+
+			__NODE_AT__ = idx + 1;
+			continue;
+
+		}
+
+		if (dsp.dispatch_id >= this_doc.members) {
+
+			return CONFINI_EOOR;
+
+		}
+
+		dsp.data = ini_source + __NODE_AT__;
+		dsp.d_len = idx - __NODE_AT__;
+
+		/*  We won't need `__NODE_AT__` for a while, so let's recycle it...  */
+		#undef __NODE_AT__
+		#define __ITER__ tmp_fast_size_t_1
+
+		/*  Set `dsp.value` to an empty string  */
+		dsp.value = ini_source + idx;
+		dsp.v_len = 0;
+
+		if (
+			_CONFINI_IS_DIS_MARKER_(*dsp.data, format) && (
+				format.disabled_after_space ||
+				!is_some_space(dsp.data[1], _CONFINI_NO_EOL_)
+			)
+		) {
+
+			__ITER__ = dqultrim_s(dsp.data, 0, format);
+
+			dsp.type = get_type_as_active(
+				dsp.data + __ITER__,
+				dsp.d_len - __ITER__,
+				format.disabled_can_be_implicit,
+				format
+			);
+
+			if (dsp.type) {
+
+				dsp.data += __ITER__;
+				dsp.d_len -= __ITER__;
+
+				/*
+
+				// Not strictly needed...
+				for (; __ITER__ > 0; dsp.data[--__ITER__] = '\0');
+
+				*/
+
+			}
+
+			dsp.type |= INI_DISABLED_FLAG;
+
+		} else {
+
+			switch (*dsp.data) {
+
+				default:
+
+					if (!_CONFINI_IS_ANY_MARKER_(*dsp.data, format)) {
+
+						dsp.type = get_type_as_active(
+							dsp.data,
+							dsp.d_len,
+							_CONFINI_TRUE_,
+							format
+						);
+
+						break;
+
+					}
+
+					/*
+
+						No case break here, keep it like this!
+						`case _CONFINI_BC_INT_MARKER_` must follow
+						(switch case fallthrough).
+
+					*/
+
+				case _CONFINI_BC_INT_MARKER_:
+
+					/*
+
+					// Not strictly needed...
+					*dsp.data = '\0';
+
+					*/
+
+					dsp.type = INI_COMMENT;
+					break;
+
+				case _CONFINI_IC_INT_MARKER_:
+
+					/*
+
+					// Not strictly needed...
+					*dsp.data = '\0';
+
+					*/
+
+					dsp.type = INI_INLINE_COMMENT;
+					/*  No case break here (last case)  */
+
+			}
+
+		}
+
+		if (__CURR_PARENT_LEN__ && __SUBPARENT_LEN__) {
+
+			__ITER__ = 0;
+
+			do {
+
+				curr_parent_str[__CURR_PARENT_LEN__ + __ITER__] =
+					subparent_str[__ITER__];
+
+			} while (__ITER__++ < __SUBPARENT_LEN__);
+
+			__CURR_PARENT_LEN__ += __SUBPARENT_LEN__;
+			subparent_str = curr_parent_str + __CURR_PARENT_LEN__;
+			__SUBPARENT_LEN__ = 0;
+
+		}
+
+		if (__PARENT_IS_DISABLED__ && !(dsp.type & INI_DISABLED_FLAG)) {
+
+			real_parent_str[__REAL_PARENT_LEN__] = '\0';
+			__CURR_PARENT_LEN__ = __REAL_PARENT_LEN__;
+			curr_parent_str = real_parent_str;
+			__PARENT_IS_DISABLED__ = _CONFINI_FALSE_;
+
+		} else if (!__PARENT_IS_DISABLED__ && dsp.type == INI_DISABLED_SECTION) {
+
+			__REAL_PARENT_LEN__ = __CURR_PARENT_LEN__;
+			real_parent_str = curr_parent_str;
+			__PARENT_IS_DISABLED__ = _CONFINI_TRUE_;
+
+		}
+
+		dsp.append_to = curr_parent_str;
+		dsp.at_len = __CURR_PARENT_LEN__;
+
+		if (dsp.type == INI_COMMENT || dsp.type == INI_INLINE_COMMENT) {
+
+			dsp.d_len = uncomment(++dsp.data, dsp.d_len - 1, format);
+
+		} else if (format.multiline_nodes != INI_NO_MULTILINE) {
+
+			dsp.d_len = unescape_cr_lf(
+				dsp.data,
+				dsp.d_len,
+				dsp.type & INI_DISABLED_FLAG,
+				format
+			);
+
+		}
+
+		switch (dsp.type) {
+
+			/*
+
+			case INI_UNKNOWN:
+
+				// Do nothing
+
+				break;
+
+			*/
+
+			case INI_SECTION:
+			case INI_DISABLED_SECTION:
+
+				*dsp.data++ = '\0';
+
+				__ITER__ = getn_metachar_pos(
+					dsp.data,
+					_CONFINI_CLOSE_SECTION_,
+					dsp.d_len,
+					format
+				);
+
+				while (dsp.data[__ITER__]) {
+
+					dsp.data[__ITER__++] = '\0';
+
+				}
+
+				dsp.d_len =
+					format.section_paths == INI_ONE_LEVEL_ONLY ?
+						collapse_everything(dsp.data, format)
+					:
+						sanitize_section_path(dsp.data, format);
+
+				if (
+					format.section_paths == INI_ONE_LEVEL_ONLY ||
+					*dsp.data != _CONFINI_SUBSECTION_
+				) {
+
+					/*
+
+						Append to root (this is an absolute path)
+
+					*/
+
+					curr_parent_str = dsp.data;
+					__CURR_PARENT_LEN__ = dsp.d_len;
+					subparent_str = ini_source + idx;
+					__SUBPARENT_LEN__ = 0;
+					dsp.append_to = subparent_str;
+					dsp.at_len = 0;
+
+				} else if (
+					format.section_paths == INI_ABSOLUTE_ONLY ||
+					!__CURR_PARENT_LEN__
+				) {
+
+					/*
+
+						Append to root and remove the leading dot (parent is root or
+						relative paths are not allowed)
+
+					*/
+
+					curr_parent_str = ++dsp.data;
+					__CURR_PARENT_LEN__ = --dsp.d_len;
+					subparent_str = ini_source + idx;
+					__SUBPARENT_LEN__ = 0;
+					dsp.append_to = subparent_str;
+					dsp.at_len = 0;
+
+				} else if (dsp.d_len != 1) {
+
+					/*
+
+						Append to the current parent (this is a relative path
+						and parent is not root)
+
+					*/
+
+					subparent_str = dsp.data;
+					__SUBPARENT_LEN__ = dsp.d_len;
+
+				}
+
+				if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) {
+
+					string_tolower(dsp.data);
+
+				}
+
+				break;
+
+			case INI_KEY:
+			case INI_DISABLED_KEY:
+
+				if (
+					valid_delimiter &&
+					(__ITER__ = getn_metachar_pos(
+						dsp.data,
+						(char) dsp.format.delimiter_symbol,
+						dsp.d_len,
+						format
+					)) < dsp.d_len
+				) {
+
+					dsp.data[__ITER__] = '\0';
+					dsp.value = dsp.data + __ITER__ + 1;
+
+					switch (
+						(format.preserve_empty_quotes << 1) |
+						format.do_not_collapse_values
+					) {
+
+						case 0:	dsp.v_len = collapse_everything(dsp.value, format); break;
+
+						case 1:	dsp.v_len = collapse_empty_quotes(dsp.value, format); break;
+
+						case 2:	dsp.v_len = collapse_spaces(dsp.value, format); break;
+
+						case 4:
+
+							dsp.value += ltrim_h(dsp.value, 0, _CONFINI_WITH_EOL_);
+
+							dsp.v_len = rtrim_h(
+								dsp.value,
+								dsp.d_len + dsp.data - dsp.value,
+								_CONFINI_WITH_EOL_
+							);
+
+							/*  No case break here (last case)  */
+
+					}
+
+				} else if (format.implicit_is_not_empty) {
+
+					dsp.value = INI_GLOBAL_IMPLICIT_VALUE;
+					dsp.v_len = INI_GLOBAL_IMPLICIT_V_LEN;
+
+				}
+
+				dsp.d_len = collapse_everything(dsp.data, format);
+
+				if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) {
+
+					string_tolower(dsp.data);
+
+				}
+
+				break;
+
+			case INI_COMMENT:
+			case INI_INLINE_COMMENT:
+
+				dsp.append_to = ini_source + idx;
+				dsp.at_len = 0;
+				/*  No case break here (last case)  */
+
+		}
+
+		if (f_foreach(&dsp, user_data)) {
+
+			return CONFINI_FEINTR;
+
+		}
+
+		dsp.dispatch_id++;
+
+		/*  Restore `__NODE_AT__` on **exactly** the same variable as before  */
+		#undef __ITER__
+		#define __NODE_AT__ tmp_fast_size_t_1
+		__NODE_AT__ = idx + 1;
+
+	}
+
+	#undef __PARENT_IS_DISABLED__
+	#undef __REAL_PARENT_LEN__
+	#undef __SUBPARENT_LEN__
+	#undef __CURR_PARENT_LEN__
+	#undef __NODE_AT__
+
+	return CONFINI_SUCCESS;
+
+}
+
+
+                                                     /** @utility{load_ini_file} **/
+/**
+
+	@brief          Parse an INI file and dispatch its content to a custom callback
+	                using a `FILE` structure as argument
+	@param          ini_file        The `FILE` handle pointing to the INI file to
+	                                parse
+	@param          format          The format of the INI file
+	@param          f_init          The function that will be invoked before the
+	                                first dispatch, or `NULL`
+	@param          f_foreach       The function that will be invoked for each
+	                                dispatch, or `NULL`
+	@param          user_data       A custom argument, or `NULL`
+	@return         Zero for success, otherwise an error code (see `enum`
+	                #ConfiniInterruptNo)
+
+	@note   This function is absent if the `--without-io-api` option was passed to
+	        the `configure` script when the library was compiled
+
+	The @p ini_file parameter must be a `FILE` handle with read privileges. On some
+	platforms, such as Microsoft Windows, it might be necessary to add the binary
+	specifier to the mode string (`"b"`) in order to prevent discrepancies between
+	the physical size of the file and its computed size. Adding the binary specifier
+	guarantees portability across all platforms:
+
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
+	FILE * my_file = fopen("example.conf", "rb");
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	For the two parameters @p f_init and @p f_foreach see function
+	#strip_ini_cache().
+
+	The parsing algorithms used by **libconfini** are able to parse any type of file
+	encoded in 8-bit code units, as long as the characters that match the regular
+	expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in
+	ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of
+	platform-specific conventions.
+
+	@note   In order to be null-byte-injection safe, `NUL` characters, if present in
+	        the file, will be removed from the dispatched strings.
+
+	Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR,
+	#CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, #CONFINI_EFBIG.
+
+	@include topics/load_ini_file.c
+
+**/
+int load_ini_file (
+	FILE * const ini_file,
+	const IniFormat format,
+	const IniStatsHandler f_init,
+	const IniDispHandler f_foreach,
+	void * const user_data
+) {
+
+	_CONFINI_OFF_T_ file_size;
+
+	if (
+		_CONFINI_SEEK_EOF_(ini_file) ||
+		(file_size = _CONFINI_FTELL_(ini_file)) < 0
+	) {
+
+		return CONFINI_EBADF;
+
+	}
+
+	if (file_size > SIZE_MAX) {
+
+		return CONFINI_EFBIG;
+
+	}
+
+	char * const cache = (char *) malloc((size_t) file_size + 1);
+
+	if (!cache) {
+
+		return CONFINI_ENOMEM;
+
+	}
+
+	rewind(ini_file);
+
+	if (fread(cache, 1, (size_t) file_size, ini_file) < file_size) {
+
+		free(cache);
+		return CONFINI_EIO;
+
+	}
+
+	const int return_value = strip_ini_cache(
+		cache,
+		(size_t) file_size,
+		format,
+		f_init,
+		f_foreach,
+		user_data
+	);
+
+	free(cache);
+	return return_value;
+
+}
+
+
+                                                     /** @utility{load_ini_path} **/
+/**
+
+	@brief          Parse an INI file and dispatch its content to a custom callback
+	                using a path as argument
+	@param          path            The path of the INI file
+	@param          format          The format of the INI file
+	@param          f_init          The function that will be invoked before the
+	                                first dispatch, or `NULL`
+	@param          f_foreach       The function that will be invoked for each
+	                                dispatch, or `NULL`
+	@param          user_data       A custom argument, or `NULL`
+	@return         Zero for success, otherwise an error code (see `enum`
+	                #ConfiniInterruptNo)
+
+	@note   This function is absent if the `--without-io-api` option was passed to
+	        the `configure` script when the library was compiled
+
+	For the two parameters @p f_init and @p f_foreach see function
+	#strip_ini_cache().
+
+	The parsing algorithms used by **libconfini** are able to parse any type of file
+	encoded in 8-bit code units, as long as the characters that match the regular
+	expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in
+	ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of
+	platform-specific conventions.
+
+	@note   In order to be null-byte-injection safe, `NUL` characters, if present in
+	        the file, will be removed from the dispatched strings.
+
+	Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR,
+	#CONFINI_ENOENT, #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF,
+	#CONFINI_EFBIG.
+
+	@include topics/load_ini_path.c
+
+**/
+int load_ini_path (
+	const char * const path,
+	const IniFormat format,
+	const IniStatsHandler f_init,
+	const IniDispHandler f_foreach,
+	void * const user_data
+) {
+
+	FILE * const ini_file = fopen(path, "rb");
+
+	if (!ini_file) {
+
+		return CONFINI_ENOENT;
+
+	}
+
+	_CONFINI_OFF_T_ file_size;
+
+	if (
+		_CONFINI_SEEK_EOF_(ini_file) ||
+		(file_size = _CONFINI_FTELL_(ini_file)) < 0
+	) {
+
+		return CONFINI_EBADF;
+
+	}
+
+	if (file_size > SIZE_MAX) {
+
+		return CONFINI_EFBIG;
+
+	}
+
+	char * const cache = (char *) malloc((size_t) file_size + 1);
+
+	if (!cache) {
+
+		return CONFINI_ENOMEM;
+
+	}
+
+	rewind(ini_file);
+
+	if (fread(cache, 1, (size_t) file_size, ini_file) < file_size) {
+
+		free(cache);
+		return CONFINI_EIO;
+
+	}
+
+	/*  No checks here, as there is nothing we can do about it...  */
+	fclose(ini_file);
+
+	const int return_value = strip_ini_cache(
+		cache,
+		(size_t) file_size,
+		format,
+		f_init,
+		f_foreach,
+		user_data
+	);
+
+	free(cache);
+	return return_value;
+
+}
+
+
+
+		/*  OTHER UTILITIES (NOT REQUIRED BY LIBCONFINI'S MAIN FUNCTIONS)  */
+
+
+                                               /** @utility{ini_string_match_ss} **/
+/**
+
+	@brief          Compare two simple strings and check whether they match
+	@param          simple_string_a     The first simple string
+	@param          simple_string_b     The second simple string
+	@param          format              The format of the INI file
+	@return         A boolean: `true` if the two strings match, `false` otherwise
+
+	Simple strings are user-given strings or the result of #ini_string_parse(). The
+	@p format argument is used for the following fields:
+
+	- `format.case_sensitive`
+
+**/
+bool ini_string_match_ss (
+	const char * const simple_string_a,
+	const char * const simple_string_b,
+	const IniFormat format
+) {
+
+	register size_t idx = 0;
+
+	if (format.case_sensitive) {
+
+		do {
+
+			if (simple_string_a[idx] != simple_string_b[idx]) {
+
+				return _CONFINI_FALSE_;
+
+			}
+
+		} while (simple_string_a[idx++]);
+
+		return _CONFINI_TRUE_;
+
+	}
+
+	do {
+
+		if (
+			_CONFINI_CHR_CASEFOLD_(simple_string_a[idx]) !=
+			_CONFINI_CHR_CASEFOLD_(simple_string_b[idx])
+		) {
+
+			return _CONFINI_FALSE_;
+
+		}
+
+	} while (simple_string_a[idx++]);
+
+	return _CONFINI_TRUE_;
+
+}
+
+
+                                               /** @utility{ini_string_match_si} **/
+/**
+
+	@brief          Compare a simple string and an INI string and and check whether
+	                they match
+	@param          ini_string      The INI string escaped according to
+	                                @p format
+	@param          simple_string   The simple string
+	@param          format          The format of the INI file
+	@return         A boolean: `true` if the two strings match, `false` otherwise
+
+	This function grants that the result of the comparison between a simple string
+	and an INI string
+
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
+	printf(
+	    "%s\n",
+	    ini_string_match_si(my_simple_string, my_ini_string, format) ?
+	        "They match"
+	    :
+	        "They don't match"
+	);
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	will always match the result of the _literal_ comparison between the simple
+	string and the INI string after the latter has been parsed by
+	#ini_string_parse() when `format.do_not_collapse_values` is set to `false`.
+
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
+	ini_string_parse(my_ini_string, format);
+
+	printf(
+	    "%s\n",
+	    ini_string_match_ss(my_simple_string, my_ini_string, format) ?
+	        "They match"
+	    :
+	        "They don't match"
+	);
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	INI strings are the strings typically dispatched by #load_ini_file(),
+	#load_ini_path() or #strip_ini_cache(), which may contain quotes and the three
+	escape sequences `\\`, `\'` and `\"`. Simple strings are user-given strings or
+	the result of #ini_string_parse().
+
+	In order to be suitable for both names and values, **this function always
+	considers sequences of one or more spaces out of quotes in the INI string as
+	collapsed**, even when `format.do_not_collapse_values` is set to `true`.
+
+	The @p format argument is used for the following fields:
+
+	- `format.case_sensitive`
+	- `format.no_double_quotes`
+	- `format.no_single_quotes`
+	- `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`)
+
+	@include topics/ini_string_match_si.c
+
+**/
+bool ini_string_match_si (
+	const char * const simple_string,
+	const char * const ini_string,
+	const IniFormat format
+) {
+
+	/*
+
+	Mask `abcd` (8 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Format supports escape sequences (const)
+		FLAG_8      Unescaped single quotes are odd right now
+		FLAG_16     Unescaped double quotes are odd right now
+		FLAG_32     This is an escaped single/double quote in a format that supports
+		            single/double quotes
+		FLAG_64     This is a space
+		FLAG_128    Skip this character
+
+	*/
+
+	register uint_least8_t abcd =
+		INIFORMAT_HAS_NO_ESC(format) ?
+			67
+		:
+			(format.no_double_quotes ? 70 : 68) | format.no_single_quotes;
+
+	register size_t idx_i = 0;
+	size_t idx_s = 0, nbacksl = 0;
+
+
+	/* \                                /\
+	\ */     si_match:                 /* \
+	 \/     ______________________     \ */
+
+
+	if ((abcd & 4) && ini_string[idx_i] == _CONFINI_BACKSLASH_) {
+
+		for (
+			abcd &= 63, nbacksl++;
+				ini_string[++idx_i] == _CONFINI_BACKSLASH_;
+			nbacksl++
+		);
+
+	}
+
+	/*  Keep this algorithm identical to #ini_get_bool_i()  */
+
+	abcd =
+
+		!(abcd & 10) && ini_string[idx_i] == _CONFINI_D_QUOTES_ ?
+			(
+				nbacksl & 1 ?
+					(abcd & 63) | 32
+				:
+					((abcd & 223) | 128) ^ 16
+			)
+		: !(abcd & 17) && ini_string[idx_i] == _CONFINI_S_QUOTES_ ?
+			(
+				nbacksl & 1 ?
+					(abcd & 63) | 32
+				:
+					((abcd & 223) | 128) ^ 8
+			)
+		: (abcd & 24) || !is_some_space(ini_string[idx_i], _CONFINI_WITH_EOL_) ?
+			abcd & 31
+		: abcd & 64 ?
+			(abcd & 223) | 128
+		:
+			(abcd & 95) | 64;
+
+
+	if (nbacksl) {
+
+		nbacksl = (abcd & 32 ? nbacksl + 2 : nbacksl + 3) >> 1;
+
+		while (--nbacksl) {
+
+			if (simple_string[idx_s++] != _CONFINI_BACKSLASH_) {
+
+				return _CONFINI_FALSE_;
+
+			}
+
+		}
+
+	}
+
+	if (
+		(abcd & 128) || (
+			(abcd & 64) && !simple_string[idx_s]
+		)
+	) {
+
+		idx_i++;
+		goto si_match;
+
+	}
+
+	if (
+		abcd & 64 ?
+			simple_string[idx_s] != _CONFINI_COLLAPSED_ ||
+			!simple_string[idx_s + 1]
+		: format.case_sensitive ?
+			ini_string[idx_i] != simple_string[idx_s]
+		:
+			_CONFINI_CHR_CASEFOLD_(ini_string[idx_i]) !=
+			_CONFINI_CHR_CASEFOLD_(simple_string[idx_s])
+	) {
+
+		return _CONFINI_FALSE_;
+
+	}
+
+	idx_s++;
+
+	if (ini_string[idx_i++]) {
+
+		goto si_match;
+
+	}
+
+	return _CONFINI_TRUE_;
+
+}
+
+
+                                               /** @utility{ini_string_match_ii} **/
+/**
+
+	@brief          Compare two INI strings and check whether they match
+	@param          ini_string_a    The first INI string unescaped according to
+	                                @p format
+	@param          ini_string_b    The second INI string unescaped according to
+	                                @p format
+	@param          format          The format of the INI file
+	@return         A boolean: `true` if the two strings match, `false` otherwise
+
+	This function grants that the result of the comparison between two INI strings
+
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
+	printf(
+	    "%s\n",
+	    ini_string_match_ii(my_ini_string_1, my_ini_string_2, format) ?
+	        "They match"
+	    :
+	        "They don't match"
+	);
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	will always match the result of the _literal_ comparison between the same two
+	INI strings after these have been parsed by #ini_string_parse() when
+	`format.do_not_collapse_values` is set to `false`.
+
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
+	ini_string_parse(my_ini_string_1, format);
+	ini_string_parse(my_ini_string_2, format);
+
+	printf("%s\n",
+	    ini_string_match_ss(my_ini_string_1, my_ini_string_2, format) ?
+	        "They match"
+	    :
+	        "They don't match"
+	);
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	INI strings are the strings typically dispatched by #load_ini_file(),
+	#load_ini_path() or #strip_ini_cache(), which may contain quotes and the three
+	escape sequences `\\`, `\'` and `\"`.
+
+	In order to be suitable for both names and values, **this function always
+	considers sequences of one or more spaces out of quotes in both strings as
+	collapsed**, even when `format.do_not_collapse_values` is set to `true`.
+
+	The @p format argument is used for the following fields:
+
+	- `format.case_sensitive`
+	- `format.no_double_quotes`
+	- `format.no_single_quotes`
+	- `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`)
+
+**/
+bool ini_string_match_ii (
+	const char * const ini_string_a,
+	const char * const ini_string_b,
+	const IniFormat format
+) {
+
+	const _CONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format);
+	register _CONFINI_CHARBOOL_ side = 1;
+	register _CONFINI_CHARBOOL_ turn_allowed = _CONFINI_TRUE_;
+	uint_least8_t abcd_pair[2];
+	const char * ptr_pair[2] = { ini_string_a, ini_string_b };
+	size_t nbacksl_pair[2];
+
+	/*
+
+	Masks `abcd_pair[0]` and `abcd_pair[1]` (7 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     We are in an odd sequence of backslashes and format supports
+		            escape sequences
+		FLAG_32     This is a space
+		FLAG_64     Skip this character
+
+	*/
+
+	abcd_pair[1] = abcd_pair[0] =
+		32 | (format.no_double_quotes << 1) | format.no_single_quotes;
+
+
+	/* \                                /\
+	\ */     ii_match:                 /* \
+	 \/     ______________________     \ */
+
+
+	nbacksl_pair[side] = 0;
+
+	if (has_escape && *ptr_pair[side] == _CONFINI_BACKSLASH_) {
+
+		for (
+			nbacksl_pair[side]++;
+				*(++ptr_pair[side]) == _CONFINI_BACKSLASH_;
+			nbacksl_pair[side]++
+		);
+
+		abcd_pair[side] =
+			nbacksl_pair[side] & 1 ?
+				(abcd_pair[side] & 31) | 16
+			:
+				abcd_pair[side] & 15;
+
+		if (
+			(
+				(abcd_pair[side] & 9) ||
+				*ptr_pair[side] != _CONFINI_S_QUOTES_
+			) && (
+				(abcd_pair[side] & 6) ||
+				*ptr_pair[side] != _CONFINI_D_QUOTES_
+			)
+		) {
+
+			nbacksl_pair[side]++;
+
+		}
+
+	} else {
+
+		abcd_pair[side] =
+
+			!(abcd_pair[side] & 25) &&
+			*ptr_pair[side] == _CONFINI_S_QUOTES_ ?
+				((abcd_pair[side] & 111) | 64) ^ 4
+			: !(abcd_pair[side] & 22) &&
+			*ptr_pair[side] == _CONFINI_D_QUOTES_ ?
+				((abcd_pair[side] & 111) | 64) ^ 8
+			: !(abcd_pair[side] & 12) &&
+			is_some_space(*ptr_pair[side], _CONFINI_WITH_EOL_) ?
+				(abcd_pair[side] & 111) | 96
+			: *ptr_pair[side] ?
+				abcd_pair[side] & 47
+			:
+				abcd_pair[side] & 15;
+
+
+		if (abcd_pair[side] & 64) {
+
+			ptr_pair[side]++;
+			goto ii_match;
+
+		}
+
+	}
+
+	if (side && turn_allowed) {
+
+		side ^= 1;
+		goto ii_match;
+
+	}
+
+	turn_allowed = _CONFINI_TRUE_;
+
+	if (nbacksl_pair[0] || nbacksl_pair[1]) {
+
+		if (nbacksl_pair[0] >> 1 != nbacksl_pair[1] >> 1) {
+
+			return _CONFINI_FALSE_;
+
+		}
+
+		side = 1;
+		goto ii_match;
+
+	}
+
+	if (
+		(
+			abcd_pair[side ^ 1] & 32 ?
+				!(abcd_pair[side] & 32)
+			:
+				abcd_pair[(side ^= 1) ^ 1] & 32
+		) && *ptr_pair[side]
+	) {
+
+		if (*ptr_pair[side]++ != _CONFINI_COLLAPSED_) {
+
+			return _CONFINI_FALSE_;
+
+		}
+
+		abcd_pair[side ^ 1] &= 95;
+		turn_allowed = _CONFINI_FALSE_;
+		goto ii_match;
+
+	}
+
+	if (
+		format.case_sensitive ?
+			*ptr_pair[0] != *ptr_pair[1]
+		:
+			_CONFINI_CHR_CASEFOLD_(*ptr_pair[0]) !=
+			_CONFINI_CHR_CASEFOLD_(*ptr_pair[1])
+	) {
+
+		return _CONFINI_FALSE_;
+
+	}
+
+	if (*ptr_pair[0]) {
+
+		ptr_pair[0]++;
+
+	}
+
+	if (*ptr_pair[1]) {
+
+		ptr_pair[1]++;
+
+	}
+
+	if (*ptr_pair[0] || *ptr_pair[1]) {
+
+		abcd_pair[0] &= 95;
+		abcd_pair[1] &= 95;
+		side = 1;
+		goto ii_match;
+
+	}
+
+	return _CONFINI_TRUE_;
+
+}
+
+
+                                                   /** @utility{ini_array_match} **/
+/**
+
+	@brief          Compare two INI arrays and check whether they match
+	@param          ini_string_a    The first INI array
+	@param          ini_string_b    The second INI array
+	@param          delimiter       The delimiter between the array members -- if
+	                                zero (see #INI_ANY_SPACE), any space is
+	                                delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
+	@param          format          The format of the INI file
+	@return         A boolean: `true` if the two arrays match, `false` otherwise
+
+	This function grants that the result of the comparison between two INI arrays
+	will always match the the _literal_ comparison between the individual members
+	of both arrays after these have been parsed, one by one, by #ini_string_parse()
+	(with `format.do_not_collapse_values` set to `false`).
+
+	This function can be used, with `'.'` as delimiter, to compare section paths.
+
+	INI strings are the strings typically dispatched by #load_ini_file(),
+	#load_ini_path() or #strip_ini_cache(), which may contain quotes and the three
+	escape sequences `\\`, `\'` and `\"`.
+
+	In order to be suitable for both names and values, **this function always
+	considers sequences of one or more spaces out of quotes in both strings as
+	collapsed**, even when `format.do_not_collapse_values` is set to `true`.
+
+	The @p format argument is used for the following fields:
+
+	- `format.case_sensitive`
+	- `format.no_double_quotes`
+	- `format.no_single_quotes`
+	- `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`)
+
+**/
+bool ini_array_match (
+	const char * const ini_string_a,
+	const char * const ini_string_b,
+	const char delimiter,
+	const IniFormat format
+) {
+
+	if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) {
+
+		/*  We have no delimiters (array has only one member)  */
+		return ini_string_match_ii(ini_string_a, ini_string_b, format);
+
+	}
+
+	const _CONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format);
+	register _CONFINI_CHARBOOL_ side = 1;
+	register _CONFINI_CHARBOOL_ turn_allowed = _CONFINI_TRUE_;
+	uint_least8_t abcd_pair[2];
+	size_t nbacksl_pair[2];
+
+	const char * ptr_pair[2] = {
+		ini_string_a + ltrim_s(ini_string_a, 0, _CONFINI_WITH_EOL_),
+		ini_string_b + ltrim_s(ini_string_b, 0, _CONFINI_WITH_EOL_)
+	};
+
+	/*
+
+	Masks `abcd_pair[0]` and `abcd_pair[1]` (8 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     We are in an odd sequence of backslashes and format supports
+		            escape sequences
+		FLAG_32     This is a space
+		FLAG_64     This is a delimiter
+		FLAG_128    Skip this character
+
+	*/
+
+	abcd_pair[1] = abcd_pair[0] =
+		32 | (format.no_double_quotes << 1) | format.no_single_quotes;
+
+
+	/* \                                /\
+	\ */     delimited_match:          /* \
+	 \/     ______________________     \ */
+
+
+	nbacksl_pair[side] = 0;
+
+	if (has_escape && *ptr_pair[side] == _CONFINI_BACKSLASH_) {
+
+		for (
+			nbacksl_pair[side]++;
+				*(++ptr_pair[side]) == _CONFINI_BACKSLASH_;
+			nbacksl_pair[side]++
+		);
+
+		abcd_pair[side] =
+			nbacksl_pair[side] & 1 ?
+				(abcd_pair[side] & 31) | 16
+			:
+				abcd_pair[side] & 15;
+
+		if (
+			(
+				(abcd_pair[side] & 9) || *ptr_pair[side] != _CONFINI_S_QUOTES_
+			) && (
+				(abcd_pair[side] & 6) || *ptr_pair[side] != _CONFINI_D_QUOTES_
+			)
+		) {
+
+			nbacksl_pair[side]++;
+
+		}
+
+	} else {
+
+		abcd_pair[side] =
+
+			!(abcd_pair[side] & 12) && is_some_space(
+				*ptr_pair[side],
+				_CONFINI_WITH_EOL_
+			) ?
+				(
+					delimiter || (abcd_pair[side] & 64) ?
+						(abcd_pair[side] & 239) | 160
+					:
+						(abcd_pair[side] & 111) | 96
+				)
+			: delimiter && !(abcd_pair[side] & 12) && *ptr_pair[side] == delimiter ?
+				(abcd_pair[side] & 111) | 96
+			: !(abcd_pair[side] & 25) && *ptr_pair[side] == _CONFINI_S_QUOTES_ ?
+				((abcd_pair[side] & 175) | 128) ^ 4
+			: !(abcd_pair[side] & 22) && *ptr_pair[side] == _CONFINI_D_QUOTES_ ?
+				((abcd_pair[side] & 175) | 128) ^ 8
+			: *ptr_pair[side] ?
+				abcd_pair[side] & 47
+			: delimiter ?
+				abcd_pair[side] & 15
+			:
+				(abcd_pair[side] & 79) ^ 64;
+
+
+		if (abcd_pair[side] & 128) {
+
+			ptr_pair[side]++;
+			goto delimited_match;
+
+		}
+
+	}
+
+	if (side && turn_allowed) {
+
+		side ^= 1;
+		goto delimited_match;
+
+	}
+
+	turn_allowed = _CONFINI_TRUE_;
+
+	if (nbacksl_pair[0] || nbacksl_pair[1]) {
+
+		if (nbacksl_pair[0] >> 1 != nbacksl_pair[1] >> 1) {
+
+			return _CONFINI_FALSE_;
+
+		}
+
+		side = 1;
+		goto delimited_match;
+
+	}
+
+	if ((abcd_pair[0] ^ abcd_pair[1]) & 64) {
+
+		return _CONFINI_FALSE_;
+
+	}
+
+	if (
+		!(
+			abcd_pair[side ^ 1] & 32 ?
+				abcd_pair[side] & 96
+			:
+				(abcd_pair[(side ^= 1) ^ 1] & 96) ^ 32
+		) && *ptr_pair[side]
+	) {
+
+		if (*ptr_pair[side]++ != _CONFINI_COLLAPSED_) {
+
+			return _CONFINI_FALSE_;
+
+		}
+
+		abcd_pair[side ^ 1] &= 223;
+		turn_allowed = _CONFINI_FALSE_;
+		goto delimited_match;
+
+	}
+
+	if (~abcd_pair[0] & 64) {
+
+		if (
+			format.case_sensitive ?
+				*ptr_pair[0] != *ptr_pair[1]
+			:
+				_CONFINI_CHR_CASEFOLD_(*ptr_pair[0]) !=
+				_CONFINI_CHR_CASEFOLD_(*ptr_pair[1])
+		) {
+
+			return _CONFINI_FALSE_;
+
+		}
+
+		abcd_pair[0] &= 223;
+		abcd_pair[1] &= 223;
+
+	}
+
+	if (*ptr_pair[0]) {
+
+		ptr_pair[0]++;
+
+	}
+
+	if (*ptr_pair[1]) {
+
+		ptr_pair[1]++;
+
+	}
+
+	if (*ptr_pair[0] || *ptr_pair[1]) {
+
+		side = 1;
+		goto delimited_match;
+
+	}
+
+	return _CONFINI_TRUE_;
+
+}
+
+
+                                                       /** @utility{ini_unquote} **/
+/**
+
+	@brief          Unescape `\'`, `\"`, and `\\` and remove all unescaped quotes
+	                (when single/double quotes are considered metacharacters in
+	                respect to the format given)
+	@param          ini_string      The string to be unescaped
+	@param          format          The format of the INI file
+	@return         The new length of the string
+
+	This function is very similar to #ini_string_parse(), except that does not
+	bother collapsing the sequences of more than one space that might result from
+	removing empty quotes. Its purpose is to be used to parse key and section names,
+	since these are always dispatched as already collapsed. In order to parse
+	values, or array parts listed in values, please use #ini_string_parse().
+
+	If you only need to compare @p ini_string with another string, consider to use
+	#ini_string_match_si() and #ini_string_match_ii() instead of parsing the former
+	and perform a simple comparison afterwards. These two functions are in fact able
+	to check directly for equality between unparsed INI strings without actually
+	modifiyng them.
+
+	Usually @p ini_string comes from an #IniDispatch (but any other string may be
+	used as well). If the string does not contain quotes, or if quotes are
+	considered to be normal characters, no changes will be made.
+
+	@note   If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
+	        no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus
+	        the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE.
+
+	The @p format argument is used for the following fields:
+
+	- `format.no_single_quotes`
+	- `format.no_double_quotes`
+	- `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`)
+
+	@include topics/ini_string_parse.c
+
+**/
+size_t ini_unquote (
+	char * const ini_string,
+	const IniFormat format
+) {
+
+	if (INI_IS_IMPLICIT_SUBSTR(ini_string)) {
+
+		return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string;
+
+	}
+
+	register size_t idx = 0;
+
+	if (INIFORMAT_HAS_NO_ESC(format)) {
+
+		/*
+
+			There are no escape sequences... I will just return the length of the
+			string...
+
+		*/
+
+		while (ini_string[idx++]);
+
+		return idx - 1;
+
+	}
+
+	size_t lshift = 0, nbacksl = 0;
+
+	/*
+
+	Mask `abcd` (6 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Unescaped single quotes are odd right now
+		FLAG_8      Unescaped double quotes are odd right now
+		FLAG_16     This is an unescaped single quote and format supports single
+		            quotes
+		FLAG_32     This is an unescaped double quote and format supports double
+		            quotes
+
+	*/
+
+	for (
+
+		register uint_least8_t
+			abcd = (format.no_double_quotes << 1) | format.no_single_quotes;
+
+			ini_string[idx];
+
+		idx++
+
+	) {
+
+		abcd =
+
+			!(abcd & 6) && ini_string[idx] == _CONFINI_D_QUOTES_ ?
+				(abcd & 47) | 32
+			: !(abcd & 9) && ini_string[idx] == _CONFINI_S_QUOTES_ ?
+				(abcd & 31) | 16
+			:
+				abcd & 15;
+
+
+		if (!(nbacksl & 1) && (abcd & 48)) {
+
+			abcd ^= (abcd >> 2) & 12;
+			lshift++;
+			continue;
+
+		}
+
+		if (ini_string[idx] == _CONFINI_BACKSLASH_) {
+
+			nbacksl++;
+
+		} else {
+
+			if ((nbacksl & 1) && (abcd & 48)) {
+
+				lshift++;
+
+			}
+
+			lshift += nbacksl >> 1;
+			nbacksl = 0;
+
+		}
+
+		if (lshift) {
+
+			ini_string[idx - lshift] = ini_string[idx];
+
+		}
+
+	}
+
+	lshift += nbacksl >> 1;
+
+	for (idx -= lshift; ini_string[idx]; ini_string[idx++] = '\0');
+
+	return idx - lshift;
+
+}
+
+
+                                                  /** @utility{ini_string_parse} **/
+/**
+
+	@brief          Unescape `\'`, `\"`, and `\\` and remove all unescaped quotes
+	                (when single/double quotes are considered metacharacters in
+	                respect to the format given); if the format allows it, sequences
+	                of one or more spaces out of quotes will be collapsed
+	@param          ini_string      The string to be unescaped
+	@param          format          The format of the INI file
+	@return         The new length of the string
+
+	This function is meant to be used to parse values. In order to parse key and
+	section names please use #ini_unquote().
+
+	If you only need to compare @p ini_string with another string, consider to use
+	#ini_string_match_si() and #ini_string_match_ii() instead of parsing the former
+	and perform a simple comparison afterwards. These two functions are in fact able
+	to check directly for equality between unparsed INI strings without actually
+	modifying them.
+
+	Usually @p ini_string comes from an #IniDispatch (but any other string may be
+	used as well). If `format.do_not_collapse_values` is set to non-zero, spaces
+	surrounding empty quotes will be collapsed together with the latter.
+
+	@note   If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
+	        no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus
+	        the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE.
+
+	The @p format argument is used for the following fields:
+
+	- `format.no_single_quotes`
+	- `format.no_double_quotes`
+	- `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`)
+	- `format.do_not_collapse_values`
+
+	@include topics/ini_string_parse.c
+
+**/
+size_t ini_string_parse (char * const ini_string, const IniFormat format) {
+
+	if (INI_IS_IMPLICIT_SUBSTR(ini_string)) {
+
+		return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string;
+
+	}
+
+	/*
+
+	Mask `abcd` (8 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Do not collapse spaces within members (const)
+		FLAG_8      Unescaped single quotes are odd right now
+		FLAG_16     Unescaped double quotes are odd right now
+		FLAG_32     This is an *escaped* single/double quote and format supports
+		            single/double quotes
+		FLAG_64     This is a space
+		FLAG_128    Skip this character
+
+	*/
+
+	register uint_least8_t abcd =
+		(format.do_not_collapse_values ? 68 : 64) |
+		(format.no_double_quotes << 1) |
+		format.no_single_quotes;
+
+	size_t idx, lshift;
+
+	if (format.multiline_nodes == INI_NO_MULTILINE) {
+
+		switch (abcd) {
+
+			case 64 | 2 | 1 :
+
+				/*
+
+					There are no escape sequences, but spaces might still need to
+					be collapsed.
+
+				*/
+
+				for (idx = 0, lshift = 0; ini_string[idx]; idx++) {
+
+					abcd =
+						!is_some_space(ini_string[idx], _CONFINI_WITH_EOL_) ?
+							3
+						: abcd & 64 ?
+							195
+						:
+							67;
+
+					if (abcd & 128) {
+
+						lshift++;
+
+					} else {
+
+						ini_string[idx - lshift] =
+							abcd & 64 ?
+								_CONFINI_COLLAPSED_
+							:
+								ini_string[idx];
+
+					}
+
+				}
+
+				for (
+
+					idx -=
+						(abcd & 64) && lshift < idx ?
+							++lshift
+						:
+							lshift;
+
+						ini_string[idx];
+
+					ini_string[idx++] = '\0'
+
+				);
+
+				return idx - lshift;
+
+			case 64 | 4 | 2 | 1 :
+
+				/*
+
+					There are no escape sequences and spaces do not need to be
+					collapsed, but left and right trim might still be necessary...
+
+				*/
+
+				return rtrim_h(
+					ini_string,
+					ltrim_hh(ini_string, 0, _CONFINI_WITH_EOL_),
+					_CONFINI_WITH_EOL_
+				);
+
+		}
+
+	}
+
+	/*
+
+		There might be escape sequences...
+
+	*/
+
+	size_t nbacksl = 0;
+
+	for (idx = lshift = 0; ini_string[idx]; idx++) {
+
+		abcd =
+
+			!(abcd & 10) && ini_string[idx] == _CONFINI_D_QUOTES_ ?
+				(
+					nbacksl & 1 ?
+						(abcd & 63) | 32
+					:
+						((abcd & 223) | 128) ^ 16
+				)
+			: !(abcd & 17) && ini_string[idx] == _CONFINI_S_QUOTES_ ?
+				(
+					nbacksl & 1 ?
+						(abcd & 63) | 32
+					:
+						((abcd & 223) | 128) ^ 8
+				)
+			: (abcd & 28) || !is_some_space(ini_string[idx], _CONFINI_WITH_EOL_) ?
+				abcd & 31
+			: abcd & 64 ?
+				(abcd & 223) | 128
+			:
+				(abcd & 95) | 64;
+
+
+		if (abcd & 128) {
+
+			lshift++;
+			continue;
+
+		}
+
+		if (ini_string[idx] == _CONFINI_BACKSLASH_) {
+
+			nbacksl++;
+
+		} else {
+
+			if (abcd & 32) {
+
+				lshift++;
+
+			}
+
+			lshift += nbacksl >> 1;
+			nbacksl = 0;
+
+		}
+
+		ini_string[idx - lshift] =
+			abcd & 64 ?
+				_CONFINI_COLLAPSED_
+			:
+				ini_string[idx];
+
+	}
+
+	lshift += nbacksl >> 1;
+
+	for (
+
+		idx -=
+			(abcd & 64) && lshift < idx ?
+				++lshift
+			:
+				lshift;
+
+			ini_string[idx];
+
+		ini_string[idx++] = '\0'
+
+	);
+
+	return
+		(abcd & 28) ^ 4 ?
+			idx - lshift
+		:
+			rtrim_h(ini_string, idx - lshift, _CONFINI_WITH_EOL_);
+
+}
+
+
+                                              /** @utility{ini_array_get_length} **/
+/**
+
+	@brief          Get the length of a stringified INI array in number of members
+	@param          ini_string      The stringified array (it can be `NULL`)
+	@param          delimiter       The delimiter between the array members -- if
+	                                zero (see #INI_ANY_SPACE), any space is
+	                                delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
+	@param          format          The format of the INI file
+	@return         The length of the INI array in number of members
+
+	Usually @p ini_string comes from an #IniDispatch (but any other string may be
+	used as well).
+
+	@note   If @p delimiter matches a metacharacter within the format given (`'\\'`,
+	        `'\''` or `'\"'`), its role as metacharacter will have higher priority
+	        than its role as delimiter (i.e., the array will have no delimiters and
+	        will contain only one member).
+
+**/
+size_t ini_array_get_length (
+	const char * const ini_string,
+	const char delimiter,
+	const IniFormat format
+) {
+
+	if (!ini_string) {
+
+		return 0;
+
+	}
+
+	if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) {
+
+		/*  We have no delimiters (array has only one member)  */
+		return 1;
+
+	}
+
+	/*
+
+	Mask `abcd` (8 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Delimiter is not any space (const)
+		FLAG_8      Unescaped single quotes are odd right now
+		FLAG_16     Unescaped double quotes are odd right now
+		FLAG_32     We are in an odd sequence of backslashes
+		FLAG_64     This is a space
+		FLAG_128    This is a delimiter
+
+	*/
+
+	register uint_least8_t abcd =
+		(delimiter ? 64 : 68) |
+		(format.no_double_quotes << 1) |
+		format.no_single_quotes;
+
+	size_t counter = 0;
+
+	for (size_t idx = 0; ini_string[idx]; idx++) {
+
+		/*  Revision #1  */
+
+		abcd =
+
+			!(abcd & 28) && ini_string[idx] == delimiter ?
+				(abcd & 159) | 128
+			: !(abcd & 24) && is_some_space(ini_string[idx], _CONFINI_WITH_EOL_) ?
+				(
+					(abcd & 68) ^ 4 ?
+						(abcd & 95) | 64
+					:
+						(abcd & 223) | 192
+				)
+			: ini_string[idx] == _CONFINI_BACKSLASH_ ?
+				(abcd & 63) ^ 32
+			: !(abcd & 42) && ini_string[idx] == _CONFINI_D_QUOTES_ ?
+				(abcd & 31) ^ 16
+			: !(abcd & 49) && ini_string[idx] == _CONFINI_S_QUOTES_ ?
+				(abcd & 31) ^ 8
+			:
+				abcd & 31;
+
+
+		if (abcd & 128) {
+
+			counter++;
+
+		}
+
+	}
+
+	return
+		!counter || (~abcd & 68) ?
+			counter + 1
+		:
+			counter;
+
+}
+
+
+                                                 /** @utility{ini_array_foreach} **/
+/**
+
+	@brief          Call a custom function for each member of a stringified INI
+	                array, without modifying the content of the buffer -- useful for
+	                read-only (`const`) stringified arrays
+	@param          ini_string      The stringified array (it can be `NULL`)
+	@param          delimiter       The delimiter between the array members -- if
+	                                zero (see #INI_ANY_SPACE), any space is
+	                                delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
+	@param          format          The format of the INI file
+	@param          f_foreach       The function that will be invoked for each array
+	                                member
+	@param          user_data       A custom argument, or `NULL`
+	@return         Zero for success, otherwise an error code (see `enum`
+	                #ConfiniInterruptNo)
+
+	Usually @p ini_string comes from an #IniDispatch (but any other string may be
+	used as well).
+
+	The user given function @p f_foreach (see #IniSubstrHandler data type) will be
+	invoked with six arguments: `ini_string`, `memb_offset` (the offset of the
+	member in bytes), `memb_length` (the length of the member in bytes), `memb_num`
+	(the offset of the member in number of members), `format` (the format of the INI
+	file), `user_data` (the custom argument @p user_data previously passed). If
+	@p f_foreach returns a non-zero value the function #ini_array_foreach() will be
+	interrupted.
+
+	@note   If @p delimiter matches a metacharacter within the format given (`'\\'`,
+	        `'\''` or `'\"'`), its role as metacharacter will have higher priority
+	        than its role as delimiter (i.e., the array will have no delimiters and
+	        will contain only one member).
+
+	Possible return values are: #CONFINI_SUCCESS, #CONFINI_FEINTR.
+
+	@include topics/ini_array_foreach.c
+
+**/
+int ini_array_foreach (
+	const char * const ini_string,
+	const char delimiter,
+	const IniFormat format,
+	const IniSubstrHandler f_foreach,
+	void * const user_data
+) {
+
+	if (!ini_string) {
+
+		return CONFINI_SUCCESS;
+
+	}
+
+	/*
+
+	Mask `abcd` (8 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Delimiter is not any space (const)
+		FLAG_8      Unescaped single quotes are odd until now
+		FLAG_16     Unescaped double quotes are odd until now
+		FLAG_32     We are in an odd sequence of backslashes
+		FLAG_64     This is not a delimiter
+		FLAG_128    Stop the loop
+
+	*/
+
+	register size_t idx;
+	size_t offs = ltrim_s(ini_string, 0, _CONFINI_WITH_EOL_);
+
+	if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) {
+
+		/*  We have no delimiters (array has only one member)  */
+
+		idx = 0;
+
+		while (ini_string[idx++]);
+
+		return f_foreach(
+			ini_string,
+			offs,
+			rtrim_s(ini_string + offs, idx - offs - 1, _CONFINI_WITH_EOL_),
+			0,
+			format,
+			user_data
+		) ? CONFINI_FEINTR : CONFINI_SUCCESS;
+
+	}
+
+	register uint_least8_t abcd =
+		(delimiter ? 4 : 0) |
+		(format.no_double_quotes << 1) |
+		format.no_single_quotes;
+
+	size_t memb_num = 0;
+
+	idx = offs;
+
+	do {
+
+		abcd =
+
+			(
+				delimiter ?
+					ini_string[idx] == delimiter
+				:
+					is_some_space(ini_string[idx], _CONFINI_WITH_EOL_)
+			) ?
+				abcd & 159
+			: ini_string[idx] == _CONFINI_BACKSLASH_ ?
+				(abcd | 64) ^ 32
+			: !(abcd & 42) && ini_string[idx] == _CONFINI_D_QUOTES_ ?
+				((abcd & 223) | 64) ^ 16
+			: !(abcd & 49) && ini_string[idx] == _CONFINI_S_QUOTES_ ?
+				((abcd & 223) | 64) ^ 8
+			: ini_string[idx] ?
+				(abcd & 223) | 64
+			:
+				128;
+
+
+		if (!(abcd & 88)) {
+
+			if (
+				f_foreach(
+					ini_string,
+					offs,
+					rtrim_s(ini_string + offs, idx - offs, _CONFINI_WITH_EOL_),
+					memb_num++,
+					format,
+					user_data
+				)
+			) {
+
+				return CONFINI_FEINTR;
+
+			}
+
+			offs =
+				abcd & 128 ?
+					idx + 1
+				:
+					ltrim_s(ini_string, idx + 1, _CONFINI_WITH_EOL_);
+
+		}
+
+		idx = abcd & 216 ? idx + 1 : offs;
+
+	} while (
+		!(abcd & 128) && (
+			(abcd & 92) || ini_string[idx]
+		)
+	);
+
+	return CONFINI_SUCCESS;
+
+}
+
+
+                                                   /** @utility{ini_array_shift} **/
+/**
+
+	@brief          Shift the location pointed by @p ini_strptr to the next member
+	                of the INI array (without modifying the content of the buffer),
+	                or to `NULL` if the INI array has no more members -- useful for
+	                read-only (`const`) stringified arrays
+	@param          ini_strptr      The memory location of the stringified array --
+	                                it cannot be `NULL`, but it can point to `NULL`
+	@param          delimiter       The delimiter between the array members -- if
+	                                zero (see #INI_ANY_SPACE), any space is
+	                                delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
+	@param          format          The format of the INI file
+	@return         The length of the array member that has been left behind
+
+	Usually @p ini_strptr comes from an #IniDispatch (but any other string may be
+	used as well).
+
+	@note   If @p delimiter matches a metacharacter within the format given (`'\\'`,
+	        `'\''` or `'\"'`), its role as metacharacter will have higher priority
+	        than its role as delimiter (i.e., the array will have no delimiters and
+	        will contain only one member).
+
+	@include topics/ini_array_shift.c
+
+**/
+size_t ini_array_shift (
+	const char ** const ini_strptr,
+	const char delimiter,
+	const IniFormat format
+) {
+
+	size_t toklen = 0;
+
+	if (*ini_strptr && !_CONFINI_IS_ESC_CHAR_(delimiter, format)) {
+
+		if (!delimiter) {
+
+			toklen = ltrim_s(*ini_strptr, 0, _CONFINI_WITH_EOL_);
+
+		}
+
+		toklen += get_metachar_pos(*ini_strptr + toklen, delimiter, format);
+		*ini_strptr += toklen;
+		toklen = rtrim_s(*ini_strptr - toklen, toklen, _CONFINI_WITH_EOL_);
+
+		if (**ini_strptr) {
+
+			*ini_strptr += ltrim_s(*ini_strptr, 1, _CONFINI_WITH_EOL_);
+
+			if (delimiter || **ini_strptr) {
+
+				return toklen;
+
+			}
+
+		}
+
+	}
+
+	*ini_strptr = (char *) 0;
+	return toklen;
+
+}
+
+
+                                                /** @utility{ini_array_collapse} **/
+/**
+
+	@brief          Compress the distribution of the data in a stringified INI array
+	                by removing all the white spaces that surround its delimiters,
+	                empty quotes, collapsable spaces, etc
+	@param          ini_string      The stringified array
+	@param          delimiter       The delimiter between the array members --
+	                                if zero (`INI_ANY_SPACE`) any space is
+	                                delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
+	@param          format          The format of the INI file
+	@return         The new length of the stringified array
+
+	Out of quotes similar to ECMAScript `ini_string.replace(new
+	RegExp("^\\s+|\\s*(?:(" + delimiter + ")\\s*|($))", "g"), "$1$2")`. If
+	#INI_ANY_SPACE (`0`) is used as delimiter, one or more different spaces
+	(`/[\t \v\f\n\r]+/`) will be always collapsed to one space, independently of
+	what the format says.
+
+	Usually @p ini_string comes from an #IniDispatch (but any other string may be
+	used as well).
+
+	This function can be useful before invoking `memcpy()` using @p ini_string as
+	source, when saving memory is a priority.
+
+	The @p format argument is used for the following fields:
+
+	- `format.no_single_quotes`
+	- `format.no_double_quotes`
+	- `format.do_not_collapse_values`
+	- `format.preserve_empty_quotes`
+
+	Examples:
+
+	1. Using comma as delimiter:
+	   - Before: `&nbsp;first&nbsp;&nbsp; ,&nbsp;&nbsp;&nbsp; second&nbsp;&nbsp;
+	     ,&nbsp;&nbsp; third&nbsp;&nbsp; ,&nbsp; etc.&nbsp;&nbsp;`
+	   - After: `first,second,third,etc.`
+	2. Using `INI_ANY_SPACE` as delimiter:
+	   - Before: `&nbsp;&nbsp;first&nbsp;&nbsp;&nbsp; second&nbsp;&nbsp;&nbsp;
+	     third&nbsp;&nbsp;&nbsp;&nbsp; etc.&nbsp;&nbsp;&nbsp;`
+	   - After: `first second third etc.`
+
+	@note   If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
+	        no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus
+	        the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE.
+
+	@note   If @p delimiter matches a metacharacter within the format given (`'\\'`,
+	        `'\''` or `'\"'`), its role as metacharacter will have higher priority
+	        than its role as delimiter (i.e., the array will have no delimiters and
+	        will contain only one member).
+
+	@include topics/ini_array_collapse.c
+
+	@note   The actual space occupied by the array might get reduced further after
+	        each member is parsed by #ini_string_parse().
+
+**/
+size_t ini_array_collapse (
+	char * const ini_string,
+	const char delimiter,
+	const IniFormat format
+) {
+
+	if (INI_IS_IMPLICIT_SUBSTR(ini_string)) {
+
+		return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string;
+
+	}
+
+	if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) {
+
+		/*  We have no delimiters (array has only one member)  */
+
+		switch (
+			(format.preserve_empty_quotes << 1) |
+			format.do_not_collapse_values
+		) {
+
+			case 0: return collapse_everything(ini_string, format);
+			case 1: return collapse_empty_quotes(ini_string, format);
+			case 2: return collapse_spaces(ini_string, format);
+
+			case 3:
+
+				return rtrim_h(
+					ini_string,
+					ltrim_hh(ini_string, 0, _CONFINI_WITH_EOL_),
+					_CONFINI_WITH_EOL_
+				);
+
+		}
+
+	}
+
+	/*
+
+	Mask `abcd` (16 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Do not collapse spaces within members (const)
+		FLAG_8      Preserve empty quotes (const)
+		FLAG_16     Any space is delimiter (const)
+		FLAG_32     Unescaped single quotes are odd right now
+		FLAG_64     Unescaped double quotes are odd right now
+		FLAG_128    We are in an odd sequence of backslashes
+		FLAG_256    This is *not* a delimiter out of quotes
+		FLAG_512    This is *not* a space out of quotes
+		FLAG_1024   These are some quotes
+		FLAG_2048   These are some quotes or among the last spaces are some empty
+		            quotes
+		FLAG_4096   Save current `idx_d` in `fallback`
+		FLAG_8192   Restore `idx_d` from `fallback` before writing
+		FLAG_16384  Decrease `idx_d` before writing
+		FLAG_32768  Keep increasing `idx_d` after writing
+
+	*/
+
+	size_t idx_s = 0, idx_d = 0, fallback = 0;
+
+	register uint_least16_t abcd =
+		(delimiter ? 0 : 16) |
+		(format.preserve_empty_quotes << 3) |
+		(format.do_not_collapse_values << 2) |
+		(format.no_double_quotes << 1) |
+		format.no_single_quotes;
+
+
+	for (; ini_string[idx_s]; idx_s++) {
+
+		/*  Revision #1  */
+
+		abcd =
+
+			!(abcd & 112) && ini_string[idx_s] == delimiter ?
+				(
+					(abcd & 536) && ((abcd & 1560) ^ 8) &&
+					((abcd & 1560) ^ 1544) && ((abcd & 1304) ^ 1032) ?
+						(abcd & 33407) | 33280
+					:
+						(abcd & 41599) | 41472
+				)
+			: !(abcd & 96) && is_some_space(ini_string[idx_s], _CONFINI_WITH_EOL_) ?
+				(
+					!((abcd & 1816) ^ 1800) ?
+						(abcd & 43391) | 40960
+					: !(~abcd & 1560) ?
+						(abcd & 41087) | 40960
+					: !((abcd & 536) ^ 528) || !((abcd & 1560) ^ 536) ||
+					!((abcd & 1560) ^ 1048) ?
+						(abcd & 32895) | 32768
+					: !(abcd & 540) || !((abcd & 1564) ^ 8) ||
+					!((abcd & 536) ^ 16) || !((abcd & 1560) ^ 24) ?
+						abcd & 2431
+					: ((abcd & 540) ^ 4) && ((abcd & 796) ^ 12) &&
+					((abcd & 1564) ^ 12) && ((abcd & 1308) ^ 1032) ?
+						(abcd & 39295) | 36864
+					:
+						(abcd & 35199) | 32768
+				)
+			: !(abcd & 193) && ini_string[idx_s] == _CONFINI_S_QUOTES_ ?
+				(
+					!((abcd & 3896) ^ 8) ?
+						(abcd & 44927) | 44064
+					: !((abcd & 3896) ^ 2056) ?
+						(abcd & 36735) | 36128
+					: !((abcd & 1056) ^ 32) ?
+						(abcd & 33631) | 33536
+					: !(abcd & 40) || !(~abcd & 1064) ?
+						((abcd & 36735) | 35840) ^ 32
+					: ((abcd & 1064) ^ 1032) && ((abcd & 1064) ^ 1056) ?
+						(abcd & 40831) | 39968
+					:
+						((abcd & 20351) | 19456) ^ 32
+				)
+			: !(abcd & 162) && ini_string[idx_s] == _CONFINI_D_QUOTES_ ?
+				(
+					!((abcd & 3928) ^ 8) ?
+						(abcd & 44927) | 44096
+					: !((abcd & 3928) ^ 2056) ?
+						(abcd & 36735) | 36160
+					: !((abcd & 1088) ^ 64) ?
+						(abcd & 33599) | 33536
+					: !(abcd & 72) || !(~abcd & 1096) ?
+						((abcd & 36735) | 35840) ^ 64
+					: ((abcd & 1096) ^ 1088) && ((abcd & 1096) ^ 1032) ?
+						(abcd & 40831) | 40000
+					:
+						((abcd & 20351) | 19456) ^ 64
+				)
+			: ini_string[idx_s] == _CONFINI_BACKSLASH_ ?
+				(
+					(abcd & 888) && ((abcd & 1144) ^ 1032) &&
+					((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ?
+						((abcd & 33791) | 33536) ^ 128
+					:
+						((abcd & 41983) | 41728) ^ 128
+				)
+			: (abcd & 888) && ((abcd & 1144) ^ 1032) &&
+			((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ?
+				(abcd & 33663) | 33536
+			:
+				(abcd & 41855) | 41728;
+
+
+		ini_string[
+			abcd & 16384 ?
+				--idx_d
+			: abcd & 8192 ?
+				(idx_d = fallback)
+			: abcd & 4096 ?
+				(fallback = idx_d)
+			:
+				idx_d
+		] =
+			(abcd & 1636) && ((abcd & 1392) ^ 16) ?
+				ini_string[idx_s]
+			:
+				_CONFINI_COLLAPSED_;
+
+
+		if (abcd & 32768) {
+
+			idx_d++;
+
+		}
+
+	}
+
+	for (
+
+		idx_s =
+			((abcd & 16) && !idx_d) || (!(~abcd & 1040) && idx_d < 4) ?
+				(idx_d = 0)
+			: !(abcd & 536) || !(~abcd & 1544) || !((abcd & 1560) ^ 8) ||
+			!((abcd & 1304) ^ 1032) ?
+				(idx_d = fallback)
+			: !((abcd & 1624) ^ 1104) || !((abcd & 1592) ^ 1072) ?
+				(idx_d -= 2)
+			: ((abcd & 1552) ^ 16) && ((abcd & 632) ^ 16) &&
+			((abcd & 1624) ^ 1616) && ((abcd & 1592) ^ 1584) ?
+				idx_d
+			:
+				--idx_d;
+
+			ini_string[idx_s];
+
+		ini_string[idx_s++] = '\0'
+
+	);
+
+	return idx_d;
+
+}
+
+
+                                                   /** @utility{ini_array_break} **/
+/**
+
+	@brief          Replace the first delimiter found (together with the spaces that
+	                surround it) with `\0`
+	@param          ini_string      The stringified array (it can be `NULL`)
+	@param          delimiter       The delimiter between the array members -- if
+	                                zero (see #INI_ANY_SPACE), any space is
+	                                delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
+	@param          format          The format of the INI file
+	@return         A pointer to the remaining INI array or `NULL` if the remaining
+	                array is empty
+
+	Usually @p ini_string comes from an #IniDispatch (but any other string may be
+	used as well).
+
+	Similarly to `strtok_r()` this function can be used only once for a given
+	string.
+
+	@note   If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
+	        no-op and will return `NULL`.
+
+	@note   If @p delimiter matches a metacharacter within the format given (`'\\'`,
+	        `'\''` or `'\"'`), its role as metacharacter will have higher priority
+	        than its role as delimiter (i.e., the array will have no delimiters and
+			will contain only one member).
+
+	@include topics/ini_array_break.c
+
+**/
+char * ini_array_break (
+	char * const ini_string,
+	const char delimiter,
+	const IniFormat format
+) {
+
+	if (ini_string && !INI_IS_IMPLICIT_SUBSTR(ini_string)) {
+
+		char * remnant;
+
+		if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) {
+
+			/*  We have no delimiters (array has only one member)  */
+
+			remnant = ini_string;
+
+			while (*remnant++);
+
+			rtrim_h(ini_string, remnant - ini_string - 1, _CONFINI_WITH_EOL_);
+
+		} else {
+
+			remnant = ini_string + get_metachar_pos(ini_string, delimiter, format);
+
+			if (*remnant) {
+
+				*remnant = '\0';
+				rtrim_h(ini_string, remnant - ini_string, _CONFINI_WITH_EOL_);
+				remnant += ltrim_h(remnant, 1, _CONFINI_WITH_EOL_);
+
+				if (delimiter || *remnant) {
+
+					return remnant;
+
+				}
+
+			}
+
+		}
+
+	}
+
+	return (char *) 0;
+
+}
+
+
+                                                 /** @utility{ini_array_release} **/
+/**
+
+	@brief          Replace the first delimiter found (together with the spaces that
+	                surround it) with `\0`, then shifts the location pointed by
+	                @p ini_strptr to the next member of the INI array, or to `NULL`
+	                if the INI array has no more members
+	@param          ini_strptr      The memory location of the stringified array --
+	                                it cannot be `NULL`, but it can point to `NULL`
+	@param          delimiter       The delimiter between the array members -- if
+	                                zero (see #INI_ANY_SPACE), any space is
+	                                delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
+	@param          format          The format of the INI file
+	@return         The array member that has been released
+
+	Usually @p ini_strptr comes from an #IniDispatch (but any other string may be
+	used as well).
+
+	Similarly to `strtok_r()` this function can be used only once for a given
+	string.
+
+	@note   If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
+	        no-op and will set @p ini_strptr to `NULL`.
+
+	@note   If @p delimiter matches a metacharacter within the format given (`'\\'`,
+	        `'\''` or `'\"'`), its role as metacharacter will have higher priority
+	        than its role as delimiter (i.e., the array will have no delimiters and
+	        will contain only one member).
+
+	@include topics/ini_array_release.c
+
+**/
+char * ini_array_release (
+	char ** const ini_strptr,
+	const char delimiter,
+	const IniFormat format
+) {
+
+	char * const token = *ini_strptr;
+
+	if (
+		token &&
+		!INI_IS_IMPLICIT_SUBSTR(token) &&
+		!_CONFINI_IS_ESC_CHAR_(delimiter, format)
+	) {
+
+		*ini_strptr += get_metachar_pos(*ini_strptr, delimiter, format);
+
+		if (**ini_strptr) {
+
+			**ini_strptr = '\0';
+			rtrim_h(token, *ini_strptr - token, _CONFINI_WITH_EOL_);
+			*ini_strptr += ltrim_h(*ini_strptr, 1, _CONFINI_WITH_EOL_);
+
+			if (delimiter || **ini_strptr) {
+
+				return token;
+
+			}
+
+		}
+
+	}
+
+	*ini_strptr = (char *) 0;
+	return token;
+
+}
+
+
+                                                   /** @utility{ini_array_split} **/
+/**
+
+	@brief          Split a stringified INI array into NUL-separated members and
+	                call a custom function for each member
+	@param          ini_string      The stringified array (it cannot be `NULL`)
+	@param          delimiter       The delimiter between the array members -- if
+	                                zero (see #INI_ANY_SPACE), any space is
+	                                delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
+	@param          format          The format of the INI file
+	@param          f_foreach       The function that will be invoked for each array
+	                                member
+	@param          user_data       A custom argument, or `NULL`
+	@return         Zero for success, otherwise an error code (see `enum`
+	                #ConfiniInterruptNo)
+
+	Usually @p ini_string comes from an #IniDispatch (but any other string may be
+	used as well).
+
+	The user given function @p f_foreach (see #IniStrHandler data type) will be
+	invoked with five arguments: `member` (the member of the array), `memb_length`
+	(the length of the member in bytes), `memb_num` (the offset of the member in
+	number of members), `format` (the format of the INI file), `user_data` (the
+	custom argument @p user_data previously passed). If @p f_foreach returns a
+	non-zero value the function #ini_array_split() will be interrupted.
+
+	Similarly to `strtok_r()` this function can be used only once for a given
+	string.
+
+	@note   If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE or is `NULL` this
+	        function is no-op and will return an error code.
+
+	@note   If @p delimiter matches a metacharacter within the format given (`'\\'`,
+	        `'\''` or `'\"'`), its role as metacharacter will have higher priority
+	        than its role as delimiter (i.e., the array will have no delimiters and
+	        will contain only one member).
+
+	Possible return values are: #CONFINI_SUCCESS, #CONFINI_EROADDR, #CONFINI_FEINTR.
+
+	@include topics/ini_array_split.c
+
+**/
+int ini_array_split (
+	char * const ini_string,
+	const char delimiter,
+	const IniFormat format,
+	const IniStrHandler f_foreach,
+	void * const user_data
+) {
+
+	if (!ini_string || INI_IS_IMPLICIT_SUBSTR(ini_string)) {
+
+		return CONFINI_EROADDR;
+
+	}
+
+	/*
+
+	Mask `abcd` (8 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Delimiter is not any space (const)
+		FLAG_8      Unescaped single quotes are odd until now
+		FLAG_16     Unescaped double quotes are odd until now
+		FLAG_32     We are in an odd sequence of backslashes
+		FLAG_64     This is not a delimiter
+		FLAG_128    Stop the loop
+
+	*/
+
+	register size_t idx;
+	size_t offs = ltrim_h(ini_string, 0, _CONFINI_WITH_EOL_);
+
+	if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) {
+
+		/*  We have no delimiters (array has only one member)  */
+
+		idx = 0;
+
+		while (ini_string[idx++]);
+
+		return f_foreach(
+			ini_string + offs,
+			rtrim_h(ini_string + offs, idx - offs - 1, _CONFINI_WITH_EOL_),
+			0,
+			format,
+			user_data
+		) ? CONFINI_FEINTR : CONFINI_SUCCESS;
+
+	}
+
+	register uint_least8_t
+		abcd =
+			(delimiter ? 4 : 0) |
+			(format.no_double_quotes << 1) |
+			format.no_single_quotes;
+
+	size_t memb_num = 0;
+
+	idx = offs;
+
+	do {
+
+		abcd =
+
+			(
+				delimiter ?
+					ini_string[idx] == delimiter
+				:
+					is_some_space(ini_string[idx], _CONFINI_WITH_EOL_)
+			) ?
+				abcd & 159
+			: ini_string[idx] == _CONFINI_BACKSLASH_ ?
+				(abcd | 64) ^ 32
+			: !(abcd & 42) && ini_string[idx] == _CONFINI_D_QUOTES_ ?
+				((abcd & 223) | 64) ^ 16
+			: !(abcd & 49) && ini_string[idx] == _CONFINI_S_QUOTES_ ?
+				((abcd & 223) | 64) ^ 8
+			: ini_string[idx] ?
+				(abcd & 223) | 64
+			:
+				128;
+
+
+		if (!(abcd & 88)) {
+
+			ini_string[idx] = '\0';
+
+			if (
+				f_foreach(
+					ini_string + offs,
+					rtrim_h(ini_string + offs, idx - offs, _CONFINI_WITH_EOL_),
+					memb_num++,
+					format,
+					user_data
+				)
+			) {
+
+				return CONFINI_FEINTR;
+
+			}
+
+			offs =
+				abcd & 128 ?
+					idx + 1
+				:
+					ltrim_h(ini_string, idx + 1, _CONFINI_WITH_EOL_);
+
+		}
+
+		idx = abcd & 216 ? idx + 1 : offs;
+
+	} while (
+		!(abcd & 128) && (
+			(abcd & 92) || ini_string[idx]
+		)
+	);
+
+	return CONFINI_SUCCESS;
+
+}
+
+
+/**
+
+	@brief          Set the value of the global variable
+	                #INI_GLOBAL_LOWERCASE_MODE
+	@deprecated     Deprecated since version 1.15.0 (it will be removed in version
+	                2.0.0)
+	@param          lowercase       The new value
+	@return         Nothing
+
+	If @p lowercase is `true`, key and section names in case-insensitive INI formats
+	will be dispatched lowercase, verbatim otherwise (default value: `true`).
+
+	@warning    This function changes the value of one or more global variables. In
+	            order to be thread-safe this function should be used only once at
+	            beginning of execution, or otherwise a mutex logic must be
+	            introduced.
+
+**/
+void ini_global_set_lowercase_mode (
+	const bool lowercase
+) {
+
+	INI_GLOBAL_LOWERCASE_MODE = lowercase;
+
+}
+
+
+                                     /** @utility{ini_global_set_implicit_value} **/
+/**
+
+	@brief          Set the value to be to be assigned to implicit keys
+	@param          implicit_value      The string to be used as implicit value
+	                                    (usually `"YES"`, `"TRUE"`, or `"ON"`, or
+	                                    any other string; it can be `NULL`)
+	@param          implicit_v_len      The length of @p implicit_value (without
+	                                    counting the NUL terminator; use `0` for
+	                                    both an empty string and `NULL`)
+	@return         Nothing
+
+	@warning    This function changes the value of one or more global variables. In
+	            order to be thread-safe this function should be used only once at
+	            beginning of execution, or otherwise a mutex logic must be
+	            introduced.
+
+	@include topics/ini_global_set_implicit_value.c
+
+**/
+void ini_global_set_implicit_value (
+	char * const implicit_value,
+	const size_t implicit_v_len
+) {
+
+	INI_GLOBAL_IMPLICIT_VALUE = implicit_value;
+	INI_GLOBAL_IMPLICIT_V_LEN = implicit_v_len;
+
+}
+
+
+                                                          /** @utility{ini_fton} **/
+/**
+
+	@brief          Calculate the #IniFormatNum of an #IniFormat
+	@param          source          The #IniFormat to compute
+	@return         The unique unsigned integer that identifies the format given
+
+**/
+IniFormatNum ini_fton (
+	const IniFormat source
+) {
+
+	#define __INIFORMAT_ID__(NAME, OFFSET, SIZE, DEFVAL) (source.NAME << OFFSET) |
+
+	return INIFORMAT_TABLE_AS(__INIFORMAT_ID__) 0;
+
+	#undef __INIFORMAT_ID__
+
+}
+
+
+                                                          /** @utility{ini_ntof} **/
+/**
+
+	@brief          Construct a new #IniFormat according to an #IniFormatNum
+	@param          format_num      The #IniFormatNum to parse
+	@return         The new #IniFormat constructed
+
+	@note   If @p format_num `>` `16777215` it will be truncated to 24 bits.
+
+**/
+IniFormat ini_ntof (
+	const IniFormatNum format_num
+) {
+
+	#define __MAX_1_BITS__ 1
+	#define __MAX_2_BITS__ 3
+	#define __MAX_3_BITS__ 7
+	#define __MAX_4_BITS__ 15
+	#define __MAX_5_BITS__ 31
+	#define __MAX_6_BITS__ 63
+	#define __MAX_7_BITS__ 127
+	#define __MAX_8_BITS__ 255
+	#define __INIFORMAT_PROPERTIES__(NAME, OFFSET, SIZE, DEFVAL) \
+		(unsigned char) ((format_num >> OFFSET) & __MAX_##SIZE##_BITS__),
+
+	return (IniFormat) { INIFORMAT_TABLE_AS(__INIFORMAT_PROPERTIES__) };
+
+	#undef __INIFORMAT_PROPERTIES__
+	#undef __MAX_8_BITS__
+	#undef __MAX_7_BITS__
+	#undef __MAX_6_BITS__
+	#undef __MAX_5_BITS__
+	#undef __MAX_4_BITS__
+	#undef __MAX_3_BITS__
+	#undef __MAX_2_BITS__
+	#undef __MAX_1_BITS__
+
+}
+
+
+                                                      /** @utility{ini_get_bool} **/
+/**
+
+	@brief          Check whether a simple string matches one of the booleans listed
+	                in the private constant #INI_BOOLEANS (case-insensitive)
+	@param          simple_string   A string to check (it can be `NULL`)
+	@param          when_fail       The value that is returned if no matching
+	                                boolean is found
+	@return         The matching boolean (`0` or `1`) or @p when_fail if
+	                @p simple_string does not contain a valid INI boolean
+
+	@include miscellanea/typed_ini.c
+
+**/
+int ini_get_bool (
+	const char * const simple_string,
+	const int when_fail
+) {
+
+	if (!simple_string) {
+
+		return when_fail;
+
+	}
+
+	register _CONFINI_CHARBOOL_ bool_idx;
+	register size_t pair_idx, chr_idx;
+
+	for (pair_idx = 0; pair_idx < _CONFINI_BOOLLEN_; pair_idx++) {
+
+		bool_idx = 0;
+
+		do {
+
+			chr_idx = 0;
+
+			while (
+				_CONFINI_CHR_CASEFOLD_(simple_string[chr_idx]) ==
+				INI_BOOLEANS[pair_idx][bool_idx][chr_idx]
+			) {
+
+				if (!simple_string[chr_idx++]) {
+
+					return (int) bool_idx;
+
+				}
+
+			}
+
+			bool_idx ^= 1;
+
+		} while (bool_idx);
+
+	}
+
+	return when_fail;
+
+}
+
+
+                                                    /** @utility{ini_get_bool_i} **/
+/**
+
+	@brief          Check whether an INI string matches one of the booleans listed
+	                in the private constant #INI_BOOLEANS (case-insensitive)
+	@param          ini_string      A string to check (it can be `NULL`)
+	@param          when_fail       The value that is returned if no matching
+	                                boolean is found
+	@param          format          The format of the INI file
+	@return         The matching boolean (`0` or `1`) or @p when_fail if
+	                @p ini_string does not contain a valid INI boolean
+
+	Usually @p ini_string comes from an #IniDispatch (but any other string may be
+	used as well).
+
+	The @p format argument is used for the following fields:
+
+	- `format.no_double_quotes`
+	- `format.no_single_quotes`
+
+	@include miscellanea/typed_ini.c
+
+**/
+int ini_get_bool_i (
+	const char * const ini_string,
+	const int when_fail,
+	const IniFormat format
+) {
+
+	if (!ini_string) {
+
+		return when_fail;
+
+	}
+
+	register size_t chr_idx_i;
+
+	/*
+
+	Mask `abcd` (8 bits used):
+
+		FLAG_1      Single quotes are not metacharacters (const)
+		FLAG_2      Double quotes are not metacharacters (const)
+		FLAG_4      Format supports escape sequences (const)
+		FLAG_8      Unescaped single quotes are odd right now
+		FLAG_16     Unescaped double quotes are odd right now
+		FLAG_32     This is an escaped single/double quote in a format that supports
+		            single/double quotes
+		FLAG_64     This is a space
+		FLAG_128    Skip this character
+
+	*/
+
+	register uint_least8_t abcd =
+		INIFORMAT_HAS_NO_ESC(format) ?
+			67
+		:
+			68 |
+			(format.no_double_quotes << 1) |
+			format.no_single_quotes;
+
+	register _CONFINI_CHARBOOL_ bool_idx;
+	size_t pair_idx, chr_idx_s, nbacksl;
+
+	for (pair_idx = 0; pair_idx < _CONFINI_BOOLLEN_; pair_idx++) {
+
+		bool_idx = 0;
+
+
+		/* \                                /\
+		\ */     pair_match:               /* \
+		 \/     ______________________     \ */
+
+
+		abcd = (abcd & 7) | 64;
+		chr_idx_s = chr_idx_i = nbacksl = 0;
+
+		do {
+
+			if ((abcd & 4) && ini_string[chr_idx_i] == _CONFINI_BACKSLASH_) {
+
+				for (
+					abcd &= 63, nbacksl++;
+						ini_string[++chr_idx_i] == _CONFINI_BACKSLASH_;
+					nbacksl++
+				);
+
+			}
+
+			/*  Keep this algorithm identical to #ini_string_match_si()  */
+
+			abcd =
+
+				!(abcd & 10) && ini_string[chr_idx_i] == _CONFINI_D_QUOTES_ ?
+					(
+						nbacksl & 1 ?
+							(abcd & 63) | 32
+						:
+							((abcd & 223) | 128) ^ 16
+					)
+				: !(abcd & 17) && ini_string[chr_idx_i] == _CONFINI_S_QUOTES_ ?
+					(
+						nbacksl & 1 ?
+							(abcd & 63) | 32
+						:
+							((abcd & 223) | 128) ^ 8
+					)
+				: (abcd & 24) || !is_some_space(
+					ini_string[chr_idx_i],
+					_CONFINI_WITH_EOL_
+				) ?
+					abcd & 31
+				: abcd & 64 ?
+					(abcd & 223) | 128
+				:
+					(abcd & 95) | 64;
+
+
+			if (nbacksl) {
+
+				nbacksl = (abcd & 32 ? nbacksl + 2 : nbacksl + 3) >> 1;
+
+				while (--nbacksl) {
+
+					if (
+						INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s++] !=
+						_CONFINI_BACKSLASH_
+					) {
+
+						goto next_bool;
+
+					}
+
+				}
+
+			}
+
+			if (
+				(abcd & 128) || (
+					(abcd & 64) && !INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s]
+				)
+			) {
+
+				continue;
+
+			}
+
+			if (
+				abcd & 64 ?
+					INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s] != _CONFINI_COLLAPSED_ ||
+					!INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s + 1]
+				:
+					_CONFINI_CHR_CASEFOLD_(ini_string[chr_idx_i]) !=
+					_CONFINI_CHR_CASEFOLD_(INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s])
+			) {
+
+				goto next_bool;
+
+			}
+
+			chr_idx_s++;
+
+		} while (ini_string[chr_idx_i++]);
+
+		return (int) bool_idx;
+
+
+		/* \                                /\
+		\ */     next_bool:                /* \
+		 \/     ______________________     \ */
+
+
+		if ((bool_idx ^= 1)) {
+
+			goto pair_match;
+
+		}
+
+	}
+
+	return when_fail;
+
+}
+
+
+
+		/*  LINKS - In case you don't have `#include <stdlib.h>` in your source  */
+
+
+/**
+
+	@alias{ini_get_int}
+	    Pointer to
+	    [`atoi()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoi)
+	@alias{ini_get_lint}
+	    Pointer to
+	    [`atol()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atol)
+	@alias{ini_get_llint}
+	    Pointer to
+	    [`atoll()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoll)
+	@alias{ini_get_double}
+	    Pointer to
+	    [`atof()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atof)
+
+**/
+
+int (* const ini_get_int) (const char * ini_string) = atoi;
+
+long int (* const ini_get_lint) (const char * ini_string) = atol;
+
+long long int (* const ini_get_llint) (const char * ini_string) = atoll;
+
+double (* const ini_get_double) (const char * ini_string) = atof;
+
+/*  Legacy support -- please **do not use this**!  */
+#ifdef ini_get_float
+#undef ini_get_float
+#endif
+
+/**
+
+	@brief          Legacy support for parsing a `double` data type -- please _do
+	                not use this function_: use `ini_get_double()` instead
+	@deprecated     Deprecated since version 1.12.0 (it will be removed in version
+	                2.0.0) -- please use #ini_get_double() instead
+	@param          ini_string      The string to parse as a `double`
+
+**/
+double (* const ini_get_float) (const char * ini_string) = atof;
+
+
+
+		/*  GLOBAL VARIABLES  */
+
+
+bool INI_GLOBAL_LOWERCASE_MODE = _CONFINI_FALSE_;
+
+char * INI_GLOBAL_IMPLICIT_VALUE = (char *) 0;
+
+size_t INI_GLOBAL_IMPLICIT_V_LEN = 0;
+
+
+
+/** @endfnlist **/
+
+
+/*  EOF  */
+
diff --git a/3rdparty/libconfini/confini.h b/3rdparty/libconfini/confini.h
new file mode 100644
index 0000000000..b4498e346b
--- /dev/null
+++ b/3rdparty/libconfini/confini.h
@@ -0,0 +1,589 @@
+/*  -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-  */
+
+/**
+
+    @file       confini.h
+    @brief      libconfini header
+    @author     Stefano Gioffr&eacute;
+    @copyright  GNU General Public License, version 3 or any later version
+    @version    1.16.3
+    @date       2016-2021
+    @see        https://madmurphy.github.io/libconfini
+
+**/
+
+
+#ifndef __CONFINI_H__
+#define __CONFINI_H__
+
+
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+/*  PRIVATE (HEADER-SCOPED) MACROS  */
+
+
+#ifdef _LIBCONFINI_NOCCWARN_
+#define _LIBCONFINI_DO_PRAGMA_(PBODY)
+#define _LIBCONFINI_WARNING_(MSG)
+#elif defined(_MSC_VER)
+#define _LIBCONFINI_DO_PRAGMA_(PBODY) __pragma(PBODY)
+#define _LIBCONFINI_WARNING_(MSG) _LIBCONFINI_DO_PRAGMA_(message("*WARNING*: " MSG))
+#else
+#define _LIBCONFINI_DO_PRAGMA_(PBODY) _Pragma(#PBODY)
+#define _LIBCONFINI_WARNING_(MSG) _LIBCONFINI_DO_PRAGMA_(GCC warning #MSG)
+#endif
+
+#define __INIFORMAT_TABLE_CB_FIELDS__(NAME, OFFSET, SIZE, DEFVAL) \
+    unsigned char NAME:SIZE;
+#define __INIFORMAT_TABLE_CB_DEFAULT__(NAME, OFFSET, SIZE, DEFVAL) DEFVAL,
+#define __INIFORMAT_TABLE_CB_ZERO__(NAME, OFFSET, SIZE, DEFVAL) 0,
+#define _LIBCONFINI_INIFORMAT_TYPE_ \
+    struct IniFormat { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_FIELDS__) }
+#define _LIBCONFINI_DEFAULT_FORMAT_ \
+    { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_DEFAULT__) }
+#define _LIBCONFINI_UNIXLIKE_FORMAT_ \
+    { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_ZERO__) }
+
+
+
+/*  PUBLIC MACROS  */
+
+
+/**
+    @brief  Call a user-given macro (that accepts four arguments) for each row
+            of the table
+**/
+/*
+    NOTE: The following table and the order of its rows **define** (and link
+    together) both the #IniFormat and #IniFormatNum data types declared in this
+    header
+*/
+#define INIFORMAT_TABLE_AS(_____)                 /*  IniFormat table  *\
+
+        NAME                      BIT  SIZE DEFAULT
+                                                                      */\
+ _____( delimiter_symbol,         0,   7,   INI_EQUALS                ) \
+ _____( case_sensitive,           7,   1,   false                     )/*
+                                                                      */\
+ _____( semicolon_marker,         8,   2,   INI_DISABLED_OR_COMMENT   ) \
+ _____( hash_marker,              10,  2,   INI_DISABLED_OR_COMMENT   ) \
+ _____( section_paths,            12,  2,   INI_ABSOLUTE_AND_RELATIVE ) \
+ _____( multiline_nodes,          14,  2,   INI_MULTILINE_EVERYWHERE  )/*
+                                                                      */\
+ _____( no_single_quotes,         16,  1,   false                     ) \
+ _____( no_double_quotes,         17,  1,   false                     ) \
+ _____( no_spaces_in_names,       18,  1,   false                     ) \
+ _____( implicit_is_not_empty,    19,  1,   false                     ) \
+ _____( do_not_collapse_values,   20,  1,   false                     ) \
+ _____( preserve_empty_quotes,    21,  1,   false                     ) \
+ _____( disabled_after_space,     22,  1,   false                     ) \
+ _____( disabled_can_be_implicit, 23,  1,   false                     )
+
+
+
+/**
+    @brief  Check whether a format does _not_ support escape sequences
+**/
+#define INIFORMAT_HAS_NO_ESC(FORMAT) \
+    (FORMAT.multiline_nodes == INI_NO_MULTILINE && \
+    FORMAT.no_double_quotes && FORMAT.no_single_quotes)
+
+
+/**
+    @brief  Check whether a given `char *` data type points to the global
+            variable #INI_GLOBAL_IMPLICIT_VALUE or to any fragment of it
+**/
+#define INI_IS_IMPLICIT_SUBSTR(CHAR_PTR) \
+    (CHAR_PTR >= INI_GLOBAL_IMPLICIT_VALUE && CHAR_PTR <= \
+    INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN)
+
+
+
+/*  PUBLIC TYPEDEFS  */
+
+
+/**
+    @brief  24-bit bitfield representing the format of an INI file (INI
+            dialect)
+**/
+typedef _LIBCONFINI_INIFORMAT_TYPE_ IniFormat;
+
+
+/**
+    @brief  Global statistics about an INI file
+**/
+typedef struct IniStatistics {
+    const IniFormat format;
+    const size_t bytes;
+    const size_t members;
+} IniStatistics;
+
+
+/**
+    @brief  Dispatch of a single INI node
+**/
+typedef struct IniDispatch {
+    const IniFormat format;
+    uint_least8_t type;
+    char * data;
+    char * value;
+    const char * append_to;
+    size_t d_len;
+    size_t v_len;
+    size_t at_len;
+    size_t dispatch_id;
+} IniDispatch;
+
+
+/**
+    @brief  The unique ID of an INI format (24-bit maximum)
+**/
+typedef uint_least32_t IniFormatNum;
+
+
+/**
+    @brief  Callback function for handling an #IniStatistics structure
+**/
+typedef int (* IniStatsHandler) (
+    IniStatistics * statistics,
+    void * user_data
+);
+
+
+/**
+    @brief  Callback function for handling an #IniDispatch structure
+**/
+typedef int (* IniDispHandler) (
+    IniDispatch * dispatch,
+    void * user_data
+);
+
+
+/**
+    @brief  Callback function for handling an INI string belonging to a
+            sequence of INI strings
+**/
+typedef int (* IniStrHandler) (
+    char * ini_string,
+    size_t string_length,
+    size_t string_num,
+    IniFormat format,
+    void * user_data
+);
+
+
+/**
+    @brief  Callback function for handling a selected fragment of an INI string
+**/
+typedef int (* IniSubstrHandler) (
+    const char * ini_string,
+    size_t fragm_offset,
+    size_t fragm_length,
+    size_t fragm_num,
+    IniFormat format,
+    void * user_data
+);
+
+
+
+/*  PUBLIC FUNCTIONS  */
+
+
+extern int strip_ini_cache (
+    register char * const ini_source,
+    const size_t ini_length,
+    const IniFormat format,
+    const IniStatsHandler f_init,
+    const IniDispHandler f_foreach,
+    void * const user_data
+);
+
+
+extern int load_ini_file (
+    FILE * const ini_file,
+    const IniFormat format,
+    const IniStatsHandler f_init,
+    const IniDispHandler f_foreach,
+    void * const user_data
+);
+
+
+extern int load_ini_path (
+    const char * const path,
+    const IniFormat format,
+    const IniStatsHandler f_init,
+    const IniDispHandler f_foreach,
+    void * const user_data
+);
+
+
+extern bool ini_string_match_ss (
+    const char * const simple_string_a,
+    const char * const simple_string_b,
+    const IniFormat format
+);
+
+
+extern bool ini_string_match_si (
+    const char * const simple_string,
+    const char * const ini_string,
+    const IniFormat format
+);
+
+
+extern bool ini_string_match_ii (
+    const char * const ini_string_a,
+    const char * const ini_string_b,
+    const IniFormat format
+);
+
+
+extern bool ini_array_match (
+    const char * const ini_string_a,
+    const char * const ini_string_b,
+    const char delimiter,
+    const IniFormat format
+);
+
+
+extern size_t ini_unquote (
+    char * const ini_string,
+    const IniFormat format
+);
+
+
+extern size_t ini_string_parse (
+    char * const ini_string,
+    const IniFormat format
+);
+
+
+extern size_t ini_array_get_length (
+    const char * const ini_string,
+    const char delimiter,
+    const IniFormat format
+);
+
+
+extern int ini_array_foreach (
+    const char * const ini_string,
+    const char delimiter,
+    const IniFormat format,
+    const IniSubstrHandler f_foreach,
+    void * const user_data
+);
+
+
+extern size_t ini_array_shift (
+    const char ** const ini_strptr,
+    const char delimiter,
+    const IniFormat format
+);
+
+
+extern size_t ini_array_collapse (
+    char * const ini_string,
+    const char delimiter,
+    const IniFormat format
+);
+
+
+extern char * ini_array_break (
+    char * const ini_string,
+    const char delimiter,
+    const IniFormat format
+);
+
+
+extern char * ini_array_release (
+    char ** const ini_strptr,
+    const char delimiter,
+    const IniFormat format
+);
+
+
+extern int ini_array_split (
+    char * const ini_string,
+    const char delimiter,
+    const IniFormat format,
+    const IniStrHandler f_foreach,
+    void * const user_data
+);
+
+
+extern void ini_global_set_lowercase_mode (
+    const bool lowercase
+);
+#define ini_global_set_lowercase_mode \
+    _LIBCONFINI_WARNING_("deprecated function `ini_global_set_lowercase_mode()`") \
+    ini_global_set_lowercase_mode
+
+
+extern void ini_global_set_implicit_value (
+    char * const implicit_value,
+    const size_t implicit_v_len
+);
+
+
+extern IniFormatNum ini_fton (
+    const IniFormat format
+);
+
+
+extern IniFormat ini_ntof (
+    const IniFormatNum format_id
+);
+
+
+extern int ini_get_bool (
+    const char * const simple_string,
+    const int when_fail
+);
+
+
+extern int ini_get_bool_i (
+    const char * const ini_string,
+    const int when_fail,
+    const IniFormat format
+);
+
+
+
+/*  PUBLIC LINKS  */
+
+
+extern int (* const ini_get_int) (
+    const char * ini_string
+);
+
+
+extern long int (* const ini_get_lint) (
+    const char * ini_string
+);
+
+
+extern long long int (* const ini_get_llint) (
+    const char * ini_string
+);
+
+
+extern double (* const ini_get_double) (
+    const char * ini_string
+);
+
+
+/*
+    Legacy support -- please **do not use these functions**!
+*/
+#define ini_get_float \
+    _LIBCONFINI_WARNING_("function `ini_get_float()` is deprecated for parsing a `double` data type; use `ini_get_double()` instead") \
+    ini_get_double
+
+
+
+/*  PUBLIC CONSTANTS AND VARIABLES  */
+
+
+/**
+    @brief  Error mask (flags not present in user-generated interruptions)
+**/
+#define CONFINI_ERROR 252
+
+
+/**
+    @brief  Error codes
+**/
+enum ConfiniInterruptNo {
+    CONFINI_SUCCESS = 0,    /**< There have been no interruptions, everything
+                                 went well [value=0] **/
+    CONFINI_IINTR = 1,      /**< Interrupted by the user during `f_init()`
+                                 [value=1] **/
+    CONFINI_FEINTR = 2,     /**< Interrupted by the user during `f_foreach()`
+                                 [value=2] **/
+    CONFINI_ENOENT = 4,     /**< File inaccessible [value=4] **/
+    CONFINI_ENOMEM = 5,     /**< Error allocating virtual memory [value=5] **/
+    CONFINI_EIO = 6,        /**< Error reading the file [value=6] **/
+    CONFINI_EOOR = 7,       /**< Out-of-range error: callbacks are more than
+                                 expected [value=7] **/
+    CONFINI_EBADF = 8,      /**< The stream specified is not a seekable stream
+                                 [value=8] **/
+    CONFINI_EFBIG = 9,      /**< File too large [value=9] **/
+    CONFINI_EROADDR = 10    /**< Address is read-only [value=10] **/
+};
+
+
+/**
+    @brief  Disabled flag (i.e. third bit, present only in non-active node
+            types)
+**/
+#define INI_DISABLED_FLAG 4
+
+
+/**
+    @brief  INI node types and possible values of #IniDispatch::type
+
+    See also #INI_DISABLED_FLAG.
+**/
+enum IniNodeType {
+    INI_UNKNOWN = 0,            /**< This is a node impossible to categorize
+                                     [value=0] **/
+    INI_VALUE = 1,              /**< Not used by **libconfini** (values are
+                                     dispatched together with keys) -- but
+                                     available for user's implementations
+                                     [value=1] **/
+    INI_KEY = 2,                /**< This is a key [value=2] **/
+    INI_SECTION = 3,            /**< This is a section path [value=3] **/
+    INI_COMMENT = 4,            /**< This is a comment [value=4] **/
+    INI_INLINE_COMMENT = 5,     /**< This is an inline comment [value=5] **/
+    INI_DISABLED_KEY = 6,       /**< This is a disabled key [value=6] **/
+    INI_DISABLED_SECTION = 7    /**< This is a disabled section path
+                                     [value=7] **/
+};
+
+
+/**
+    @brief  Common array and key-value delimiters (but a delimiter may also be
+            any other ASCII character not present in this list)
+**/
+enum IniDelimiters {
+    INI_ANY_SPACE = 0,  /**< In multi-line INIs:
+                             `/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`, in
+                             non-multi-line INIs: `/[\t \v\f])+/` **/
+    INI_EQUALS = '=',   /**< Equals character (`=`) **/
+    INI_COLON = ':',    /**< Colon character (`:`) **/
+    INI_DOT = '.',      /**< Dot character (`.`) **/
+    INI_COMMA = ','     /**< Comma character (`,`) **/
+};
+
+
+/**
+    @brief  Possible values of #IniFormat::semicolon_marker and
+            #IniFormat::hash_marker (i.e., meaning of `/\s+;/` and `/\s+#/` in
+            respect to a format)
+**/
+enum IniCommentMarker {
+    INI_DISABLED_OR_COMMENT = 0,    /**< This marker opens a comment or a
+                                         disabled entry **/
+    INI_ONLY_COMMENT = 1,           /**< This marker opens a comment **/
+    INI_IGNORE = 2,                 /**< This marker opens a comment that has
+                                         been marked for deletion and must not
+                                         be dispatched or counted **/
+    INI_IS_NOT_A_MARKER = 3         /**< This is not a marker at all, but a
+                                         normal character instead **/
+};
+
+
+/**
+    @brief  Possible values of #IniFormat::section_paths
+**/
+enum IniSectionPaths {
+    INI_ABSOLUTE_AND_RELATIVE = 0,  /**< Section paths starting with a dot
+                                         express nesting to the current parent,
+                                         to root otherwise **/
+    INI_ABSOLUTE_ONLY = 1,          /**< Section paths starting with a dot will
+                                         be cleaned of their leading dot and
+                                         appended to root **/
+    INI_ONE_LEVEL_ONLY = 2,         /**< Format supports sections, but the dot
+                                         does not express nesting and is not a
+                                         meta-character **/
+    INI_NO_SECTIONS = 3             /**< Format does *not* support sections --
+                                         `/\[[^\]]*\]/g`, if any, will be
+                                         treated as keys! **/
+};
+
+
+/**
+    @brief  Possible values of #IniFormat::multiline_nodes
+**/
+enum IniMultiline {
+    INI_MULTILINE_EVERYWHERE = 0,       /**< Comments, section paths and keys
+                                             -- disabled or not -- are allowed
+                                             to be multi-line **/
+    INI_BUT_COMMENTS = 1,               /**< Only section paths and keys --
+                                             disabled or not -- are allowed to
+                                             be multi-line **/
+    INI_BUT_DISABLED_AND_COMMENTS = 2,  /**< Only active section paths and
+                                             active keys are allowed to be
+                                             multi-line **/
+    INI_NO_MULTILINE = 3                /**< Multi-line escape sequences are
+                                             disabled **/
+};
+
+
+/**
+    @brief  A model format for standard INI files
+**/
+static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_FORMAT_;
+
+
+/**
+    @brief  A model format for Unix-like .conf files (where space characters
+            are delimiters between keys and values)
+**/
+/*  All fields are set to `0` here.  */
+static const IniFormat INI_UNIXLIKE_FORMAT = _LIBCONFINI_UNIXLIKE_FORMAT_;
+
+
+/**
+
+    @brief          If set to `true`, key and section names in case-insensitive
+                    INI formats will be dispatched lowercase, verbatim
+                    otherwise (default value: `false`)
+    @deprecated     Deprecated since version 1.15.0 (it will be removed in
+                    version 2.0.0)
+**/
+extern bool INI_GLOBAL_LOWERCASE_MODE;
+#define INI_GLOBAL_LOWERCASE_MODE \
+    _LIBCONFINI_WARNING_("global variable `INI_GLOBAL_LOWERCASE_MODE` is deprecated") \
+    INI_GLOBAL_LOWERCASE_MODE
+
+
+/**
+    @brief  Value to be assigned to implicit keys (default value: `NULL`)
+**/
+extern char * INI_GLOBAL_IMPLICIT_VALUE;
+
+
+/**
+    @brief  Length of the value assigned to implicit keys (default value: `0`)
+**/
+extern size_t INI_GLOBAL_IMPLICIT_V_LEN;
+
+
+
+/*  CLEAN THE PRIVATE ENVIRONMENT  */
+
+
+#undef _LIBCONFINI_UNIXLIKE_FORMAT_
+#undef _LIBCONFINI_DEFAULT_FORMAT_
+#undef _LIBCONFINI_INIFORMAT_TYPE_
+#undef __INIFORMAT_TABLE_CB_ZERO__
+#undef __INIFORMAT_TABLE_CB_DEFAULT__
+#undef __INIFORMAT_TABLE_CB_FIELDS__
+
+
+
+/*  END OF `__CONFINI_H__`  */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
+
+/*  EOF  */
+
diff --git a/cmake/OS_Detection.cmake b/cmake/OS_Detection.cmake
index b7c014f4b6..1740a9a0fe 100644
--- a/cmake/OS_Detection.cmake
+++ b/cmake/OS_Detection.cmake
@@ -151,5 +151,6 @@ if(WIN32)
 
     include_directories(3rdparty/uthash/src/)
     include_directories(3rdparty/wepoll/)
+    include_directories(3rdparty/libconfini/)
     #include_directories(libdap-chain-net-srv-vpn/)
 endif()
diff --git a/dap-sdk/core/CMakeLists.txt b/dap-sdk/core/CMakeLists.txt
index 5956ea5531..37dac848db 100755
--- a/dap-sdk/core/CMakeLists.txt
+++ b/dap-sdk/core/CMakeLists.txt
@@ -21,18 +21,20 @@ if(WIN32)
       src/*.c 
       src/etc/*.c 
       src/rpmalloc/*.c
+      ../../3rdparty/libconfini/*.c
   )
   file(GLOB CORE_HEADERS 
-      include/*.h 
+      include/*.h
+      ../../3rdparty/libconfini/*.h
   )
 endif()
 
 add_library(${PROJECT_NAME} STATIC ${CORE_SRCS} ${CORE_HEADERS})
 
 target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdparty/uthash/src)
-#if (WIN32)
-#    target_include_directories(${PROJECT_NAME} PRIVATE ../../3rdparty/wepoll)
-#endif()
+if (WIN32)
+    target_include_directories(${PROJECT_NAME} PRIVATE ../../3rdparty/libconfini)
+endif()
 
 #This paths will be used by project-dependent project libraries
 target_include_directories(${PROJECT_NAME} INTERFACE include/ src/rpmalloc/)
diff --git a/dap-sdk/net/server/notify_server/include/dap_notify_srv.h b/dap-sdk/net/server/notify_server/include/dap_notify_srv.h
index b6ace1bd8e..dadd0220f2 100644
--- a/dap-sdk/net/server/notify_server/include/dap_notify_srv.h
+++ b/dap-sdk/net/server/notify_server/include/dap_notify_srv.h
@@ -26,4 +26,5 @@
 int dap_notify_server_init();
 void dap_notify_server_deinit();
 int dap_notify_server_send_f_inter(uint32_t a_worker_id, const char * a_format,...);
+int dap_notify_server_send_mt(const char * a_data);
 int dap_notify_server_send_f_mt(const char * a_format,...);
diff --git a/dap-sdk/net/server/notify_server/src/dap_notify_srv.c b/dap-sdk/net/server/notify_server/src/dap_notify_srv.c
index 6bfda796f7..0a2474b3dc 100644
--- a/dap-sdk/net/server/notify_server/src/dap_notify_srv.c
+++ b/dap-sdk/net/server/notify_server/src/dap_notify_srv.c
@@ -132,6 +132,19 @@ int dap_notify_server_send_f_inter(uint32_t a_worker_id, const char * a_format,.
     return dap_events_socket_queue_ptr_send_to_input(l_input,l_str);
 }
 
+/**
+ * @brief dap_notify_server_send_fmt_mt
+ * @param a_format
+ * @return
+ */
+int dap_notify_server_send_mt(const char *a_data)
+{
+    if(!s_notify_server_queue) // If not initialized - nothing to notify
+        return 0;
+    return dap_events_socket_queue_ptr_send(s_notify_server_queue, dap_strdup(a_data));
+}
+
+
 /**
  * @brief dap_notify_server_send_fmt_mt
  * @param a_format
diff --git a/modules/chain/dap_chain_ledger.c b/modules/chain/dap_chain_ledger.c
index a5e276d477..07c1616ed5 100644
--- a/modules/chain/dap_chain_ledger.c
+++ b/modules/chain/dap_chain_ledger.c
@@ -56,6 +56,9 @@
 #include "dap_chain_mempool.h"
 #include "dap_chain_global_db.h"
 #include "dap_chain_ledger.h"
+#include "json-c/json.h"
+#include "json-c/json_object.h"
+#include "dap_notify_srv.h"
 
 #define LOG_TAG "dap_chain_ledger"
 
@@ -217,6 +220,9 @@ static size_t s_treshold_txs_max = 10000;
 static bool s_debug_more = false;
 static bool s_token_supply_limit_disable = false;
 
+struct json_object *wallet_info_json_collect(dap_ledger_t *a_ledger, dap_ledger_wallet_balance_t* a_bal);
+static void wallet_info_notify();
+
 /**
  * @brief dap_chain_ledger_init
  * current function version set s_debug_more parameter, if it define in config, and returns 0
@@ -290,6 +296,32 @@ void dap_chain_ledger_load_end(dap_ledger_t *a_ledger)
 }
 
 
+struct json_object *wallet_info_json_collect(dap_ledger_t *a_ledger, dap_ledger_wallet_balance_t *a_bal) {
+    struct json_object *l_json = json_object_new_object();
+    json_object_object_add(l_json, "class", json_object_new_string("Wallet"));
+    struct json_object *l_network = json_object_new_object();
+    json_object_object_add(l_network, "name", json_object_new_string(a_ledger->net_name));
+    char *pos = strrchr(a_bal->key, ' ');
+    if (pos) {
+        char *l_addr_str = DAP_NEW_S_SIZE(char, pos - a_bal->key + 1);
+        memcpy(l_addr_str, a_bal->key, pos - a_bal->key);
+        json_object_object_add(l_network, "address", json_object_new_string(l_addr_str));
+    } else {
+        json_object_object_add(l_network, "address", json_object_new_string("Unknown"));
+    }
+    struct json_object *l_token = json_object_new_object();
+    json_object_object_add(l_token, "name", json_object_new_string(a_bal->token_ticker));
+    char *l_balance_coins = dap_chain_balance_to_coins(a_bal->balance);
+    char *l_balance_datoshi = dap_chain_balance_print(a_bal->balance);
+    json_object_object_add(l_token, "full_balance", json_object_new_string(l_balance_coins));
+    json_object_object_add(l_token, "datoshi", json_object_new_string(l_balance_datoshi));
+    DAP_DELETE(l_balance_coins);
+    DAP_DELETE(l_balance_datoshi);
+    json_object_object_add(l_network, "tokens", l_token);
+    json_object_object_add(l_json, "networks", l_network);
+    return l_json;
+}
+
 /**
  * @brief dap_chain_ledger_token_check
  * @param a_ledger
@@ -1151,6 +1183,10 @@ void dap_chain_ledger_load_cache(dap_ledger_t *a_ledger)
         l_balance_item->balance = *(uint128_t *)l_objs[i].value;
         HASH_ADD_KEYPTR(hh, l_ledger_pvt->balance_accounts, l_balance_item->key,
                         strlen(l_balance_item->key), l_balance_item);
+        /* notify dashboard */
+        struct json_object *l_json = wallet_info_json_collect(a_ledger, l_balance_item);
+        dap_notify_server_send_mt(json_object_get_string(l_json));
+        json_object_put(l_json);
     }
     dap_chain_global_db_objs_delete(l_objs, l_objs_count);
     DAP_DELETE(l_gdb_group);
@@ -2176,6 +2212,10 @@ static int s_balance_cache_update(dap_ledger_t *a_ledger, dap_ledger_wallet_bala
         return -1;
     }
     DAP_DELETE(l_gdb_group);
+    /* Notify dashboard */
+    struct json_object *l_json = wallet_info_json_collect(a_ledger, a_balance);
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
     return 0;
 }
 
diff --git a/modules/net/dap_chain_net.c b/modules/net/dap_chain_net.c
index c67902413a..2c3c67a903 100644
--- a/modules/net/dap_chain_net.c
+++ b/modules/net/dap_chain_net.c
@@ -101,6 +101,9 @@
 #include "dap_chain_node_dns_client.h"
 #include "dap_module.h"
 
+#include "json-c/json.h"
+#include "json-c/json_object.h"
+
 #include <stdio.h>
 #include <sys/types.h>
 #include <dirent.h>
@@ -239,8 +242,8 @@ static const dap_chain_node_client_callbacks_t s_node_link_callbacks={
 static bool s_net_states_proc(dap_proc_thread_t *a_thread, void *a_arg);
 
 // Notify about net states
-static void s_net_states_notify(dap_chain_net_t * l_net );
-static void s_net_links_notify(dap_chain_net_t * a_net );
+struct json_object *net_states_json_collect(dap_chain_net_t * l_net);
+static void s_net_states_notify(dap_chain_net_t * l_net);
 
 // Prepare link success/error endpoints
 static void s_net_state_link_prepare_success(dap_worker_t * a_worker,dap_chain_node_info_t * a_node_info, void * a_arg);
@@ -598,14 +601,15 @@ static void s_net_state_link_replace_error(dap_worker_t *a_worker, dap_chain_nod
     inet_ntop(AF_INET, &a_node_info->hdr.ext_addr_v4, l_node_addr_str, sizeof (a_node_info->hdr.ext_addr_v4));
     log_it(L_WARNING,"Link " NODE_ADDR_FP_STR " (%s) replace error with code %d", NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address),
                                                                                  l_node_addr_str,a_errno );
-    dap_notify_server_send_f_mt("{"
-                                "\"class\":\"NetLinkReplaceError\","
-                                "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\","
-                                "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\","
-                                "\"address\":\""NODE_ADDR_FP_STR"\","
-                                "\"error\": %d"
-                                "}", l_net->pub.id.uint64, a_node_info->hdr.cell_id.uint64,
-                                NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address), a_errno);
+    struct json_object *l_json = net_states_json_collect(l_net);
+    char l_err_str[128] = { };
+    dap_snprintf(l_err_str, sizeof(l_err_str)
+                 , "Link " NODE_ADDR_FP_STR " [%s] replace errno %d"
+                 , NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address), l_node_addr_str, a_errno);
+    json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str));
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
+
     DAP_DELETE(a_node_info);
     dap_chain_node_info_t *l_link_node_info = NULL;
     for (int i = 0; i < 1000; i++) {
@@ -659,14 +663,14 @@ static void s_net_state_link_replace_success(dap_worker_t *a_worker, dap_chain_n
     pthread_rwlock_wrlock(&l_net_pvt->rwlock);
     l_net_pvt->net_links = dap_list_append(l_net_pvt->net_links, l_new_link);
     pthread_rwlock_unlock(&l_net_pvt->rwlock);
-
-    dap_notify_server_send_f_mt("{"
-                                "\"class\":\"NetLinkReplaceSuccess\","
-                                "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\","
-                                "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\","
-                                "\"address\":\""NODE_ADDR_FP_STR"\""
-                                "}", l_net->pub.id.uint64, a_node_info->hdr.cell_id.uint64,
-                                NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address));
+    struct json_object *l_json = net_states_json_collect(l_net);
+    char l_err_str[128] = { };
+    dap_snprintf(l_err_str, sizeof(l_err_str)
+                 , "Link " NODE_ADDR_FP_STR " replace success"
+                 , NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address));
+    json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str));
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
     DAP_DELETE(l_dns_request);
 }
 
@@ -694,7 +698,14 @@ static void s_node_link_callback_connected(dap_chain_node_client_t * a_node_clie
     pthread_rwlock_wrlock(&l_net_pvt->rwlock);
     l_net_pvt->links_connected_count++;
     a_node_client->is_connected = true;
-    s_net_links_notify(l_net);
+    struct json_object *l_json = net_states_json_collect(l_net);
+    char l_err_str[128] = { };
+    dap_snprintf(l_err_str, sizeof(l_err_str)
+                 , "Established connection with link " NODE_ADDR_FP_STR
+                 , NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address));
+    json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str));
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
     if(l_net_pvt->state == NET_STATE_LINKS_CONNECTING ){
         l_net_pvt->state = NET_STATE_LINKS_ESTABLISHED;
         dap_proc_queue_add_callback_inter(a_node_client->stream_worker->worker->proc_queue_input,s_net_states_proc,l_net );
@@ -785,15 +796,10 @@ static void s_node_link_callback_stage(dap_chain_node_client_t * a_node_client,d
     if( s_debug_more)
         log_it(L_INFO,"%s."NODE_ADDR_FP_STR" stage %s",l_net->pub.name,NODE_ADDR_FP_ARGS_S(a_node_client->remote_node_addr),
                                                         dap_client_stage_str(a_stage));
-    dap_notify_server_send_f_mt("{"
-                                "\"class\":\"NetLinkStage\","
-                                "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\","
-                                "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\","
-                                "\"address\":\""NODE_ADDR_FP_STR"\","
-                                "\"state\":\"%s\""
-                                "}", a_node_client->net->pub.id.uint64, a_node_client->info->hdr.cell_id.uint64,
-                                NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address),
-                                dap_chain_node_client_state_to_str(a_node_client->state) );
+    struct json_object *l_json = net_states_json_collect(l_net);
+    json_object_object_add(l_json, "errorMessage", json_object_new_string(" "));
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
 }
 
 /**
@@ -807,15 +813,16 @@ static void s_node_link_callback_error(dap_chain_node_client_t * a_node_client,
     dap_chain_net_t * l_net = (dap_chain_net_t *) a_arg;
     log_it(L_WARNING, "Can't establish link with %s."NODE_ADDR_FP_STR, l_net->pub.name,
            NODE_ADDR_FP_ARGS_S(a_node_client->remote_node_addr));
-    dap_notify_server_send_f_mt("{"
-                                "\"class\":\"NetLinkError\","
-                                "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\","
-                                "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\","
-                                "\"address\":\""NODE_ADDR_FP_STR"\","
-                                "\"error\":\%d"
-                                "}", a_node_client->net->pub.id.uint64, a_node_client->info->hdr.cell_id.uint64,
-                                NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address),
-                                a_error);
+    struct json_object *l_json = net_states_json_collect(l_net);
+    char l_node_addr_str[INET_ADDRSTRLEN] = {};
+    inet_ntop(AF_INET, &a_node_client->info->hdr.ext_addr_v4, l_node_addr_str, sizeof (a_node_client->info->hdr.ext_addr_v4));
+    char l_err_str[128] = { };
+    dap_snprintf(l_err_str, sizeof(l_err_str)
+                 , "Link " NODE_ADDR_FP_STR " [%s] can't be established, errno %d"
+                 , NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address), l_node_addr_str, a_error);
+    json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str));
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
 }
 
 /**
@@ -828,13 +835,10 @@ static void s_node_link_callback_delete(dap_chain_node_client_t * a_node_client,
     dap_chain_net_t * l_net = (dap_chain_net_t *) a_arg;
     dap_chain_net_pvt_t * l_net_pvt = PVT(l_net);
     if (!a_node_client->keep_connection) {
-        dap_notify_server_send_f_mt("{"
-                                    "\"class\":\"NetLinkDelete\","
-                                    "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\","
-                                    "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\","
-                                    "\"address\":\""NODE_ADDR_FP_STR"\""
-                                    "}", a_node_client->net->pub.id.uint64, a_node_client->info->hdr.cell_id.uint64,
-                                    NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address));
+        struct json_object *l_json = net_states_json_collect(l_net);
+        json_object_object_add(l_json, "errorMessage", json_object_new_string("Link deleted"));
+        dap_notify_server_send_mt(json_object_get_string(l_json));
+        json_object_put(l_json);
         return;
     } else if (a_node_client->is_connected){
         a_node_client->is_connected = false;
@@ -852,13 +856,10 @@ static void s_node_link_callback_delete(dap_chain_node_client_t * a_node_client,
         }
     }
     pthread_rwlock_unlock(&l_net_pvt->rwlock);
-    dap_notify_server_send_f_mt("{"
-                                "\"class\":\"NetLinkRestart\","
-                                "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\","
-                                "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\","
-                                "\"address\":\""NODE_ADDR_FP_STR"\""
-                                "}", a_node_client->net->pub.id.uint64, a_node_client->info->hdr.cell_id.uint64,
-                                NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address));
+    struct json_object *l_json = net_states_json_collect(l_net);
+    json_object_object_add(l_json, "errorMessage", json_object_new_string("Link restart"));
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
     // Then a_alient wiil be destroyed in a right way
 }
 
@@ -900,13 +901,14 @@ static void s_net_state_link_prepare_success(dap_worker_t * a_worker,dap_chain_n
         dap_proc_queue_add_callback_inter( a_worker->proc_queue_input,s_net_states_proc,l_net );
     }
     pthread_rwlock_unlock(&l_net_pvt->rwlock);
-    dap_notify_server_send_f_mt("{"
-                                "\"class\":\"NetLinkPrepareSuccess\","
-                                "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\","
-                                "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\","
-                                "\"address\":\""NODE_ADDR_FP_STR"\""
-                                "}", l_net->pub.id.uint64, a_node_info->hdr.cell_id.uint64,
-                                NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address));
+    struct json_object *l_json = net_states_json_collect(l_net);
+    char l_err_str[128] = { };
+    dap_snprintf(l_err_str, sizeof(l_err_str)
+                 , "Link " NODE_ADDR_FP_STR " prepared"
+                 , NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address));
+    json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str));
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
     DAP_DELETE(l_dns_request);
 }
 
@@ -927,15 +929,14 @@ static void s_net_state_link_prepare_error(dap_worker_t * a_worker,dap_chain_nod
     log_it(L_WARNING,"Link " NODE_ADDR_FP_STR " (%s) prepare error with code %d", NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address),
                                                                                  l_node_addr_str,a_errno );
 
-    dap_notify_server_send_f_mt("{"
-                                "\"class\":\"NetLinkPrepareError\","
-                                "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\","
-                                "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\","
-                                "\"address\":\""NODE_ADDR_FP_STR"\","
-                                "\"error\": %d"
-                                "}", l_net->pub.id.uint64, a_node_info->hdr.cell_id.uint64,
-                                NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address),a_errno);
-
+    struct json_object *l_json = net_states_json_collect(l_net);
+    char l_err_str[128] = { };
+    dap_snprintf(l_err_str, sizeof(l_err_str)
+                 , "Link " NODE_ADDR_FP_STR " [%s] can't be prepared, errno %d"
+                 , NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address), l_node_addr_str, a_errno);
+    json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str));
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
     pthread_rwlock_wrlock(&l_net_pvt->rwlock);
     if(l_net_pvt->links_dns_requests)
         l_net_pvt->links_dns_requests--;
@@ -955,68 +956,30 @@ static void s_net_state_link_prepare_error(dap_worker_t * a_worker,dap_chain_nod
     DAP_DELETE(l_dns_request);
 }
 
-/**
- * @brief s_net_states_notify
- * @param l_net
- */
-static void s_net_states_notify(dap_chain_net_t * l_net )
-{
-    dap_notify_server_send_f_mt("{"
-                                "\"class\":\"NetStates\","
-                                "\"net_id\": \"0x%016" DAP_UINT64_FORMAT_X "\","
-                                "\"state\": \"%s\","
-                                "\"state_target\":\"%s\""
-                                "}", l_net->pub.id.uint64,
-                                       dap_chain_net_state_to_str( PVT(l_net)->state),
-                                       dap_chain_net_state_to_str(PVT(l_net)->state_target));
-
+struct json_object *net_states_json_collect(dap_chain_net_t * l_net) {
+    struct json_object *l_json = json_object_new_object();
+    json_object_object_add(l_json, "class"            , json_object_new_string("NetStates"));
+    json_object_object_add(l_json, "name"             , json_object_new_string((const char*)l_net->pub.name));
+    json_object_object_add(l_json, "networkState"     , json_object_new_string(dap_chain_net_state_to_str(PVT(l_net)->state)));
+    json_object_object_add(l_json, "targetState"      , json_object_new_string(dap_chain_net_state_to_str(PVT(l_net)->state_target)));
+    json_object_object_add(l_json, "linksCount"       , json_object_new_int(dap_list_length(PVT(l_net)->net_links)));
+    json_object_object_add(l_json, "activeLinksCount" , json_object_new_int(PVT(l_net)->links_connected_count));
+    char l_node_addr_str[24] = {'\0'};
+    dap_snprintf(l_node_addr_str, sizeof(l_node_addr_str), NODE_ADDR_FP_STR, NODE_ADDR_FPS_ARGS(PVT(l_net)->node_addr));
+    json_object_object_add(l_json, "nodeAddress"     , json_object_new_string(l_node_addr_str));
+    return l_json;
 }
 
 /**
- * @brief s_net_links_notify
+ * @brief s_net_states_notify
  * @param l_net
  */
-static void s_net_links_notify(dap_chain_net_t * a_net )
-{
-    dap_chain_net_pvt_t * l_net_pvt = PVT(a_net);
-    dap_string_t * l_str_reply = dap_string_new("[");
-
-    size_t i =0;
-    for (dap_list_t * l_item = l_net_pvt->net_links; l_item;  l_item = l_item->next ) {
-        dap_chain_node_client_t * l_node_client = ((struct net_link *)l_item->data)->link;
-
-        if(l_node_client){
-            dap_chain_node_info_t * l_info = l_node_client->info;
-            char l_ext_addr_v4[INET_ADDRSTRLEN]={};
-            char l_ext_addr_v6[INET6_ADDRSTRLEN]={};
-            inet_ntop(AF_INET,&l_info->hdr.ext_addr_v4,l_ext_addr_v4,sizeof (l_info->hdr.ext_addr_v4));
-            inet_ntop(AF_INET6,&l_info->hdr.ext_addr_v6,l_ext_addr_v6,sizeof (l_info->hdr.ext_addr_v6));
-
-            dap_string_append_printf(l_str_reply,"{"
-                                        "id:%zu,"
-                                        "address:\""NODE_ADDR_FP_STR"\","
-                                        "alias:\"%s\","
-                                        "cell_id:0x%016"DAP_UINT64_FORMAT_X","
-                                        "ext_ipv4:\"%s\","
-                                        "ext_ipv6:\"%s\","
-                                        "ext_port:%hu"
-                                        "state:\"%s\""
-                                    "}", i,NODE_ADDR_FP_ARGS_S(l_info->hdr.address), l_info->hdr.alias, l_info->hdr.cell_id.uint64,
-                                     l_ext_addr_v4, l_ext_addr_v6,l_info->hdr.ext_port
-                                     , dap_chain_node_client_state_to_str(l_node_client->state) );
-        }
-        i++;
-    }
-
-
-    dap_notify_server_send_f_mt("{"
-                                "\"class\":\"NetLinks\","
-                                "\"net_id\":\"0x%016"DAP_UINT64_FORMAT_X"\","
-                                "\"links\":\"%s\"]"
-                            "}", a_net->pub.id.uint64,
-                                       l_str_reply->str);
-    dap_string_free(l_str_reply,true);
-
+static void s_net_states_notify(dap_chain_net_t * l_net)
+{   
+    struct json_object *l_json = net_states_json_collect(l_net);
+    json_object_object_add(l_json, "errorMessage", json_object_new_string(" ")); // regular notify has no error
+    dap_notify_server_send_mt(json_object_get_string(l_json));
+    json_object_put(l_json);
 }
 
 /**
-- 
GitLab