Repository: Divy1211/BinaryFileParser (language: python)

Issue:
Fixed __repr__ condition



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 06537b0..cb529a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
 venv/*
 dist/*
+build/*
 src/binary_file_parser.egg-info/*
 docs/*
-*.aoe2scenario
-mkdocs.yml
\ No newline at end of file
+mkdocs.yml
+pub.bat
+utils.py
+asp_test/*
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
index 9a35ceb..a6517e7 100644
--- a/.idea/.gitignore
+++ b/.idea/.gitignore
@@ -9,4 +9,5 @@
 /*.iml
 /misc.xml
 /vcs.xml
-/sonarlint
\ No newline at end of file
+/sonarlint
+/discord.xml
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index a553bd5..494c389 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -19,6 +19,8 @@
           <option value="E305" />
           <option value="E303" />
           <option value="E251" />
+          <option value="E222" />
+          <option value="E221" />
         </list>
       </option>
     </inspection_tool>
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..1f800e3
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,38 @@
+## 0.2.2
+
+- Added `OptionW` data types. They also work with `StackedAttrArrayW`
+- Added `StackedAttrArrayW` data types.
+- Added `ret`, `ref`, and `com` as type casting functions. Recommended use is only inside the classbody, as they cause overhead in `on_xxx` functions
+- Changed `Manager` to only allow using `RetrieverRef`s
+- Added `DebugByteStream` which logs the bytes it reads for each specified retriever to `bfp.log`
+- Changed `DefaultValueError` to `DefaultAttributeError`, used when trying to instantiate a `BaseStruct` from default without having default values
+- Added a `_dbg_repr` to base struct which can print partially initialised `BaseStruct` objects
+
+## 0.2.1
+
+- `RetrieverRef` can now accept an arbitrary number of retrievers to be able to reference data from sub structs.
+- `RetrieverRef` can now accept an `int` as part of its arguments to index lists where required
+- `RetrieverRef` needs to be given at least one argument
+- `RetrieverRef` can now accept `Retriever` or `Combiner` or other `Ref`s as arguments
+- `RetrieverCombiner` must be given at least one argument
+- `RetrieverCombiner` can now accept `Retriever` or `Ref`s or other `Combiner`s as arguments
+- `VersionError` now also subclasses `AttributeError`
+- `DefaultValueError` now also subclasses `ValueError`
+- Diffing structs now returns a dict of name-value pairs instead of just a list of retrievers.
+  - Note: This is an expensive operation and should only be used for debug purposes. diff's behaviour is subject to change!
+- Added `StrArray8s`, `StrArray16s`, `StrArray32s` and `StrArray64s` datatypes
+- Added `Manager` superclass for creating grouped retriever references and functions on them. Use this to provide a more coherent API
+    for struct modification when the internal struct is messy
+- Added docstrings to datatypes, explaining their expected layouts
+
+## 0.2.0
+
+- For performance reasons, a `parent` reference is no longer available. To synchronise duplicate data in different parts of a file, the data source higher in the struct hierarchy is to be made responsible for performing the synchronisation.
+- `RefList` has been removed since parent references are no longer available.
+- `struct_ver` is now a read-only property and the functionality for changing a struct's version is no longer available and will be re-added in a future update.
+- A `Retriever`'s repeat value is interpreted as follows:
+  - repeat = 1 (default) means its data type is read once.
+  - repeat = 0 or > 1 means it is a list of its data type.
+  - repeat = -1 means the value does not exist and is skipped. (set to `None`)
+  - repeat set via `set_repeat` will read a list of its data type, even for repeat = 1.
+    - Note: If a single value is only conditionally present, then its repeat should default to 1, and should be set to -1 dynamically when it is intended to be absent for the desired behaviour. Setting a repeat to 1 dynamically will make it a singleton list!
diff --git a/LICENSE b/LICENSE
index 8dc6318..e72bfdd 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,674 @@
-MIT License
-
-Copyright (c) 2022 Alian713
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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
+<https://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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
\ No newline at end of file
diff --git a/README.md b/README.md
index 078e7a0..d1fda9b 100644
--- a/README.md
+++ b/README.md
@@ -24,23 +24,25 @@ This is a very basic script to give you an idea of how to use this library. Chec
 
 ```py
 from binary_file_parser import BaseStruct, Retriever
-from binary_file_parser.types import uint32, uint64, str32, FixedLenStr
+from binary_file_parser.types import int32, uint64, str32, FixedLenStr
+
 
 class Spam(BaseStruct):
-    file_version: str = Retriever(FixedLenStr[4], default = 0)
-    creator_name: str = Retriever(str32, default = 0)
+    file_version: str = Retriever(FixedLenStr[4], default = "1.00")
+    creator_name: str = Retriever(str32, default = "bfp")
     saved_timestamp: int = Retriever(uint64, default = 0)
     eggs: int = Retriever(int32, default = 0)
 
+
 # read a binary file that has the above format
-file = Spam.from_file("path/to/file")
+file = Spam._from_file("path/to/file")
 
 # modify the creator name
 file.creator_name = "Alian713"
 file.eggs = 20
 
 # write the modified data to a new file
-file.to_file("path/to/write/to")
+file._to_file("path/to/write/to")
 ```
 
 ## A Slightly More Complex Example
@@ -53,7 +55,7 @@ The main magic of this library is that:
 from __future__ import annotations
 
 from binary_file_parser import BaseStruct, Retriever
-from binary_file_parser.types import FixedLenArray, uint8
+from binary_file_parser.types import FixedLenArray, uint32, uint8
 
 
 class Pixel(BaseStruct):
@@ -75,6 +77,7 @@ class Pixel(BaseStruct):
         self.blue = blue
         self.alpha = alpha
 
+
 class Img(BaseStruct):
     @staticmethod
     def _set_width(retriever: Retriever, obj: Img):
@@ -92,11 +95,11 @@ class Img(BaseStruct):
         # up to date
         obj._height = obj.height
         Img.pixels.dtype.length = obj._width = obj.width
-    
+
     _width: int = Retriever(uint32, default = 100, on_read = [_set_width], on_write = [_update_dims])
     _height: int = Retriever(uint32, default = 200, on_set = [_set_height])
     pixels: list[list[Pixel]] = Retriever(
-        FixedLenArray[Pixel, 100], default = [Pixel(0, 0, 0) for _ in range(100)], repeat = 200
+        FixedLenArray[Pixel, 100], default_factory = lambda _: [Pixel(0, 0, 0) for _ in range(100)], repeat = 200
     )
 
     @property
@@ -107,10 +110,10 @@ class Img(BaseStruct):
     def height(self) -> int:
         return len(self.pixels)
 
+
 # Make a new image from all defaults
 a = Img()
-# Note: Mutable defaults will be shallow copied, (this means all rows of pixels in the above example are the same!)
-# If you have larger structs or nested structs, use default_factory (coming soon:tm:) instead
+# Note: Mutable defaults will be shallow copied, use default_factory when such is not intended
 ```
 
 ## About the Author
diff --git a/pyproject.toml b/pyproject.toml
index cbbd7b2..b494270 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "binary-file-parser"
-version = "0.1.2"
+version = "0.2.2"
 description = "Read/Write binary files after describing their specifications in code (similar to an ORM table schema)"
 readme = "README.md"
 authors = [{ name = "Divy1211", email = "divy1211.dc@gmail.com" }]
@@ -17,13 +17,13 @@ classifiers = [
 ]
 keywords = ["binary", "file", "parser"]
 dependencies = [
-    "alive-progress>=2.4.1",
+    "alive-progress",
 ]
 requires-python = ">=3.10"
 
 [project.optional-dependencies]
-dev = ["pytest", "pylint"]
-docs = ["mkdocs"]
+dev = ["pytest", "pylint", "mypy", "flake8"]
+docs = ["mkdocs", "mkdocstrings"]
 depl = ["build", "twine"]
 
 [project.urls]
diff --git a/requirements.dev.txt b/requirements.dev.txt
index 5918b15..01b98b9 100644
--- a/requirements.dev.txt
+++ b/requirements.dev.txt
@@ -1,6 +1,8 @@
 twine
 pytest
 pylint
+flake8
+mypy
 mkdocs
 mkdocstrings
 
diff --git a/src/binary_file_parser/__init__.py b/src/binary_file_parser/__init__.py
index 700cb2a..e530ca3 100644
--- a/src/binary_file_parser/__init__.py
+++ b/src/binary_file_parser/__init__.py
@@ -1,10 +1,7 @@
-from .errors import CompressionError
-from .errors import VersionError
-from .errors import ParsingError
+__authour__ = "Alian713"
+__version__ = "0.2.2"
+__version_info__ = (0, 2, 2)
 
-from .retrievers import BaseStruct
-from .retrievers import Retriever
-from .retrievers import RetrieverRef
-from .retrievers import RetrieverCombiner
-
-from .utils import Version
+from .errors import *
+from .retrievers import *
+from .types import BaseStruct, ByteStream, DebugByteStream, Manager, Parseable, Version
diff --git a/src/binary_file_parser/errors/CompressionError.py b/src/binary_file_parser/errors/CompressionError.py
deleted file mode 100644
index 5476f3c..0000000
--- a/src/binary_file_parser/errors/CompressionError.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from binary_file_parser.errors.ParsingError import ParsingError
-
-
-class CompressionError(ParsingError):
-    pass
diff --git a/src/binary_file_parser/errors/DefaultValueError.py b/src/binary_file_parser/errors/DefaultValueError.py
deleted file mode 100644
index 56ab92e..0000000
--- a/src/binary_file_parser/errors/DefaultValueError.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from binary_file_parser.errors.ParsingError import ParsingError
-
-
-class DefaultValueError(ParsingError):
-    pass
diff --git a/src/binary_file_parser/errors/VersionError.py b/src/binary_file_parser/errors/VersionError.py
deleted file mode 100644
index 78157a1..0000000
--- a/src/binary_file_parser/errors/VersionError.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from binary_file_parser.errors.ParsingError import ParsingError
-
-
-class VersionError(ParsingError):
-    pass
diff --git a/src/binary_file_parser/errors/__init__.py b/src/binary_file_parser/errors/__init__.py
index d89d913..6694613 100644
--- a/src/binary_file_parser/errors/__init__.py
+++ b/src/binary_file_parser/errors/__init__.py
@@ -1,4 +1,4 @@
-from .CompressionError import CompressionError
-from .ParsingError import ParsingError
-from .VersionError import VersionError
-from .DefaultValueError import DefaultValueError
+from .compression_error import CompressionError
+from .parsing_error import ParsingError
+from .version_error import VersionError
+from .default_value_error import DefaultAttributeError
diff --git a/src/binary_file_parser/errors/compression_error.py b/src/binary_file_parser/errors/compression_error.py
new file mode 100644
index 0000000..5a4ca5e
--- /dev/null
+++ b/src/binary_file_parser/errors/compression_error.py
@@ -0,0 +1,5 @@
+from binary_file_parser.errors.parsing_error import ParsingError
+
+
+class CompressionError(ParsingError):
+    pass
diff --git a/src/binary_file_parser/errors/default_value_error.py b/src/binary_file_parser/errors/default_value_error.py
new file mode 100644
index 0000000..fbad1ba
--- /dev/null
+++ b/src/binary_file_parser/errors/default_value_error.py
@@ -0,0 +1,5 @@
+from binary_file_parser.errors.parsing_error import ParsingError
+
+
+class DefaultAttributeError(AttributeError, ParsingError):
+    pass
diff --git a/src/binary_file_parser/errors/ParsingError.py b/src/binary_file_parser/errors/parsing_error.py
similarity index 100%
rename from src/binary_file_parser/errors/ParsingError.py
rename to src/binary_file_parser/errors/parsing_error.py
diff --git a/src/binary_file_parser/errors/version_error.py b/src/binary_file_parser/errors/version_error.py
new file mode 100644
index 0000000..7fb659e
--- /dev/null
+++ b/src/binary_file_parser/errors/version_error.py
@@ -0,0 +1,5 @@
+from binary_file_parser.errors.parsing_error import ParsingError
+
+
+class VersionError(AttributeError, ParsingError):
+    pass
diff --git a/src/binary_file_parser/logger.py b/src/binary_file_parser/logger.py
new file mode 100644
index 0000000..2d22674
--- /dev/null
+++ b/src/binary_file_parser/logger.py
@@ -0,0 +1,13 @@
+import logging
+
+
+formatter = logging.Formatter("[%(asctime)s][%(levelname)s]: %(message)s")
+
+file_handler = logging.FileHandler("bfp.log", mode = "w", encoding = "utf-8", delay = True)
+file_handler.setFormatter(formatter)
+
+logger = logging.getLogger(__name__)
+if logger.hasHandlers():
+    logger.handlers.clear()
+logger.addHandler(file_handler)
+logger.setLevel(logging.DEBUG)
diff --git a/src/binary_file_parser/retrievers/RetreiverRef.py b/src/binary_file_parser/retrievers/RetreiverRef.py
deleted file mode 100644
index 3a64583..0000000
--- a/src/binary_file_parser/retrievers/RetreiverRef.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from __future__ import annotations
-
-from contextlib import suppress
-from typing import Type, TypeVar, Generic
-
-from binary_file_parser.errors import VersionError
-
-from binary_file_parser.retrievers.base_struct import BaseStruct
-from binary_file_parser.retrievers.Retriever import Retriever
-from binary_file_parser.retrievers.RetreiverCombiner import RetrieverCombiner
-
-
-T = TypeVar("T")
-
-
-class RetrieverRef(Generic[T]):
-    """
-    Create a new reference to an existing retriever
-    """
-    def __init__(self, retriever: Retriever | RetrieverCombiner):
-        """
-        :param retriever: The retriever to reference
-        """
-        self.retriever = retriever
-
-    def __set_name__(self, owner: Type[BaseStruct], name: str) -> None:
-        self.name = name
-        owner._add_ref(self)
-
-    def __set__(self, instance: BaseStruct, value: T) -> None:
-        with suppress(VersionError):
-            setattr(instance, self.retriever.p_name, value)
-            return
-        raise VersionError(
-            f"{self.name!r} is not supported in your struct version {instance.struct_ver}"
-        )
-
-    def __get__(self, instance: BaseStruct, owner: Type[BaseStruct]) -> RetrieverRef | T:
-        if instance is None:
-            return self
-        with suppress(VersionError):
-            return getattr(instance, self.retriever.p_name)
-        raise VersionError(
-            f"{self.name!r} is not supported in your struct version {instance.struct_ver}"
-        )
diff --git a/src/binary_file_parser/retrievers/__init__.py b/src/binary_file_parser/retrievers/__init__.py
index e4ceec2..5ee602f 100644
--- a/src/binary_file_parser/retrievers/__init__.py
+++ b/src/binary_file_parser/retrievers/__init__.py
@@ -1,5 +1,6 @@
-from .base_struct import BaseStruct
-from .MapValidate import MapValidate
-from .Retriever import Retriever
-from .RetreiverRef import RetrieverRef
-from .RetreiverCombiner import RetrieverCombiner
+
+from .map_validate import MapValidate
+from .retriever import Retriever
+from .retriever_ref import RetrieverRef
+from .retriever_combiner import RetrieverCombiner
+from .type_cast import ret, ref, com
diff --git a/src/binary_file_parser/retrievers/base_struct.py b/src/binary_file_parser/retrievers/base_struct.py
deleted file mode 100644
index be1614c..0000000
--- a/src/binary_file_parser/retrievers/base_struct.py
+++ /dev/null
@@ -1,413 +0,0 @@
-from __future__ import annotations
-
-import copy
-from io import StringIO
-from contextlib import suppress
-from typing import TYPE_CHECKING
-
-from alive_progress import alive_it
-
-from binary_file_parser.errors import CompressionError
-from binary_file_parser.errors import ParsingError
-from binary_file_parser.errors import VersionError
-from binary_file_parser.types import ByteStream, RefList
-from binary_file_parser.types import Parseable
-from binary_file_parser.utils import indentify, Version
-
-if TYPE_CHECKING:
-    from binary_file_parser.retrievers.Retriever import Retriever
-    from binary_file_parser.retrievers.RetreiverCombiner import RetrieverCombiner
-    from binary_file_parser.retrievers.RetreiverRef import RetrieverRef
-
-
-class BaseStruct(Parseable):
-    """
-    Base class for defining a file format as a structure
-    """
-    __slots__ = "_struct_ver", "_parent", "idx"
-
-    _retrievers: list[Retriever] = []
-    _refs: list[RetrieverRef] = []
-    _combiners: list[RetrieverCombiner] = []
-
-    @classmethod
-    def _add_retriever(cls, retriever: Retriever):
-        cls._retrievers.append(retriever)
-
-    @classmethod
-    def _add_ref(cls, ref: RetrieverRef):
-        cls._refs.append(ref)
-
-    @classmethod
-    def _add_combiner(cls, combiner: RetrieverCombiner):
-        cls._combiners.append(combiner)
-
-    def __init__(
-        self,
-        struct_ver: Version = Version((0,)),
-        parent: BaseStruct = None,
-        idx: int = -1,
-        initialise_defaults: bool = True,
-        **retriever_inits,
-    ):
-        """
-        :param idx:
-        :param struct_ver: The struct version to create
-        :param parent: If this struct is nested within another, define the containing struct as parent
-        :param initialise_defaults:
-            If set to false, skip initialisation of struct values from default. This is only set to false when reading
-            a file
-        :param retriever_inits:
-            Use this to provide initial values to retrievers
-        """
-        self._struct_ver = struct_ver
-        self._parent = parent
-        self.idx = idx
-
-        size = 0
-        for retriever in self._retrievers:
-            if not retriever.supported(struct_ver):
-                continue
-
-            if initialise_defaults:
-                init = retriever_inits.get(retriever.p_name, None)
-                if init is None:
-                    init = retriever.from_default(self)
-                setattr(self, retriever.p_name, init)
-
-            # size += retriever.default.size if retriever.dtype.is_struct else retriever.dtype.size
-        super().__init__(size)
-
-    def __init_subclass__(cls, **kwargs):
-        """
-        Note: subclasses of BaseStruct which intend to act as ABCs need to override this
-        """
-        cls._retrievers, BaseStruct._retrievers = cls._retrievers.copy(), []
-        cls._refs, BaseStruct._refs = cls._refs.copy(), []
-        cls._combiners, BaseStruct._combiners = cls._combiners.copy(), []
-
-    def __deepcopy__(self, memo):
-        cls = self.__class__
-        cloned_struct = cls.__new__(cls)
-        memo[id(self)] = cloned_struct
-
-        for attr in list(self.__slots__) + list(self.__dict__):
-            if attr == '_parent':
-                value = None
-            else:
-                value = copy.deepcopy(getattr(self, attr), memo)
-            setattr(cloned_struct, attr, value)
-
-        return cloned_struct
-
-    @property
-    def is_struct(self) -> bool:
-        return True
-
-    @property
-    def root(self) -> BaseStruct:
-        """
-        The top level object of which this struct is a part of
-        """
-        child = self
-        while child.parent is not None:
-            child = self.parent
-        return child
-
-    def on_struct_ver_change(self, new_struct_ver: Version):
-        """
-        This method is called when a struct's version is changed. Use this to implement any custom logic required to
-        support a version change on a struct. The default method implements a trial and error algorithm where if an
-        unsupported retriever after the version change used to have the same value as its default, then it is discarded
-        without any errors, However, if a value other than the default was assigned to it, an error is raised
-
-        :param new_struct_ver: The new struct version (the self.struct_ver attribute will automatically be updated)
-
-        :raises VersionError: When the version change causes a non default valued retriever to become unsupported
-        """
-        pass
-
-    @property
-    def struct_ver(self):
-        return self._struct_ver
-
-    @struct_ver.setter
-    def struct_ver(self, new_struct_ver: Version):
-        for retriever in self._retrievers:
-            if (
-                not retriever.supported(self.struct_ver)
-                or not (retriever.dtype.is_struct or retriever.dtype.is_iterable)
-                or retriever.is_self_versioned
-            ):
-                continue
-            if (obj := getattr(self, retriever.p_name)) is not None:
-                obj.struct_ver = new_struct_ver
-        self.on_struct_ver_change(new_struct_ver)
-        self._struct_ver = new_struct_ver
-
-    @property
-    def parent(self):
-        return self._parent
-
-    @parent.setter
-    def parent(self, new_parent: BaseStruct):
-        for retriever in self._retrievers:
-            if (
-                not retriever.supported(self.struct_ver)
-                or not (retriever.dtype.is_struct or retriever.dtype.is_iterable)
-            ):
-                continue
-            if (obj := getattr(self, retriever.p_name)) is not None:
-                obj.parent = new_parent
-        self._parent = new_parent
-
-    @property
-    def retriever_name_value_map(self) -> dict[str]:
-        map_ = {}
-        for retriever in self._retrievers:
-            if retriever.supported(self.struct_ver):
-                map_[retriever.p_name] = getattr(self, retriever.p_name)
-        return map_
-
-    @classmethod
-    def get_version(cls, stream: ByteStream, struct_ver: Version = Version((0,)), parent: BaseStruct = None) -> Version:
-        """
-        If defined, the struct will be versioned and values in the struct which are not supported in the version that is
-        read will be skipped
-
-        :param stream: The stream to read the struct version from
-        :param struct_ver: The struct version of the parent
-        :param parent:
-        :return: A tuple of ints indicating the version eg: v1.47 should return (1, 47)
-        :raises VersionError: - When unimplemented
-        """
-        raise VersionError("Un-versioned File")
-
-    @classmethod
-    def decompress(cls, bytes_: bytes) -> bytes:
-        """
-        If remaining_compressed is set to True in a Retriever, this method is used to decompress the bytes remaining
-        before they are read and parsed into a struct object
-
-        :param bytes_: The remaining (compressed) bytes of the compressed section of the struct
-        :return: decompressed bytes
-        :raises CompressionError: - When unimplemented
-        """
-        raise CompressionError(
-            "Unable to read object from file. "
-            "A Structure with compressed section needs to implement 'decompress' classmethod."
-        )
-
-    @classmethod
-    def compress(cls, bytes_: bytes) -> bytes:
-        """
-        If remaining_compressed is set to True in a Retriever, this method is used to compress the bytes before they are
-        written to file from the struct object
-
-        :param bytes_: The remaining (uncompressed) bytes of the compressed section of the struct
-        :return: compressed bytes
-        :raises CompressionError: - When unimplemented
-        """
-        raise CompressionError(
-            "Unable to write object to file. "
-            "A Structure with compressed section needs to implement 'compress' classmethod."
-        )
-
-    def map(self) -> BaseStruct:
-        """
-        This method is called after a struct is read from bytes to allow any modifications post reading
-
-        :return: A BaseStruct instance
-        """
-        return self
-
-    @classmethod
-    def from_stream(
-        cls, stream: ByteStream, *, struct_ver: Version = Version((0,)), strict: bool = False,
-        show_progress: bool = False, parent: BaseStruct = None
-    ) -> BaseStruct:
-        """
-        Create a struct object from a ByteStream
-
-        :param stream: The stream to create the struct object from
-        :param struct_ver: The version of the structure to create. Overwritten if `get_version` is defined
-        :param strict: Raise an error if struct parsing finishes successfully but the stream has left over bytes
-        :param show_progress: When true, display a progress bar
-        :param parent: If this struct is nested within another, define the containing struct as parent
-        :return: An instance of a subtype of BaseStruct
-        """
-        with suppress(VersionError):
-            struct_ver = cls.get_version(stream, struct_ver, parent)
-
-        instance = cls(struct_ver = struct_ver, parent = parent, initialise_defaults = False)
-        retriever_ls = cls._retrievers
-        if show_progress:
-            retriever_ls = alive_it(
-                retriever_ls,
-                dual_line = True,
-                title = "         Reading File",
-                stats = False,
-                finalize = lambda bar: bar.title("Finished Reading File")
-            )
-
-        for retriever in retriever_ls:
-            if show_progress:
-                retriever_ls.text = f"            -> {retriever.p_name.title().replace('_', ' ')}"
-            if retriever.remaining_compressed:
-                stream = ByteStream.from_bytes(cls.decompress(stream.remaining()))
-            retriever.from_stream(instance, stream)
-
-        file_len = len(stream.content)
-
-        if stream.progress != file_len and strict:
-            raise ParsingError(
-                f"{file_len - stream.progress} bytes are left after parsing all retrievers successfully:\n"
-                f"{stream.remaining()}"
-            )
-
-        return instance.map()
-
-    @classmethod
-    def from_bytes(cls, bytes_: bytes, *, struct_ver: Version = Version((0,)), strict = False) -> BaseStruct:
-        """
-        Create a struct object from bytes
-
-        :param bytes_: The bytes to create the struct object from
-        :param struct_ver: The version of the structure to create. Overwritten if `get_version` is defined
-        :param strict: Raise an error if struct parsing finishes successfully but there are unused bytes left over
-        :return: An instance of a subtype of BaseStruct
-        """
-        stream = ByteStream.from_bytes(bytes_)
-        return cls.from_stream(stream, struct_ver = struct_ver, strict = strict)
-
-    @classmethod
-    def from_file(cls, file_name: str, *, file_version: Version = Version((0,)), strict = True) -> BaseStruct:
-        """
-        Create a struct object from file
-
-        :param file_name: The path of the file to create the struct object from
-        :param file_version: The version of the structure to create. Overwritten if `get_version` is defined
-        :param strict: Raise an error if struct parsing finishes successfully but the stream has left over bytes
-        :return: An instance of a subtype of BaseStruct
-        """
-        stream = ByteStream.from_file(file_name)
-        return cls.from_stream(stream, struct_ver = file_version, strict = strict, show_progress = True)
-
-    @classmethod
-    def to_bytes(cls, instance: BaseStruct, *, show_progress = False) -> bytes:
-        """
-        Convert the struct object to bytes
-
-        :param instance: The struct object to convert to bytes
-        :param show_progress: When true, display a progress bar
-        :return: bytes
-        """
-        length = len(instance._retrievers)
-
-        bytes_ = [b""] * length
-        compress_idx = length
-        retriever_ls = instance._retrievers
-        if show_progress:
-            retriever_ls = alive_it(
-                retriever_ls,
-                dual_line = True,
-                title = "         Writing File",
-                stats = False,
-                finalize = lambda bar: bar.title("Finished Writing File")
-            )
-
-        for i, retriever in enumerate(retriever_ls):
-            if show_progress:
-                retriever_ls.text = f"            <- {retriever.p_name.title().replace('_', ' ')}"
-            if retriever.remaining_compressed:
-                compress_idx = i
-            try:
-                bytes_[i] = retriever.to_bytes(instance)
-            except Exception:
-                print(retriever.p_name, getattr(instance, retriever.p_name))
-                raise
-
-        compressed = b""
-        if compress_idx != length:
-            compressed = cls.compress(b"".join(bytes_[compress_idx:]))
-
-        return b"".join(bytes_[:compress_idx]) + compressed
-
-    def to_file(self, file_name: str):
-        """
-        Write the bytes of the struct object to a file
-
-        :param file_name: The name of the file to write to
-        """
-        with open(file_name, "wb") as file:
-            file.write(self.to_bytes(self, show_progress = True))
-
-    def diff(self, other: BaseStruct) -> list[Retriever]:
-        """
-        Recursively builds a list of retrievers that have different values for the two objects.
-
-        :param other: The object to diff with
-        :return: list of retrievers that have different values for the two objects
-        """
-        if (
-            not isinstance(other, BaseStruct)
-            or type(self) is not type(other)
-        ):
-            raise TypeError(f"Cannot diff '{type(self)}' with an object of type '{type(other)}'")
-        if self.struct_ver != other.struct_ver:
-            raise VersionError(
-                f"Cannot diff structs with different versions '{self.struct_ver}' and '{other.struct_ver}'"
-            )
-
-        diff_retrievers: list[Retriever] = []
-        for retriever in self._retrievers:
-            if not retriever.supported(self.struct_ver):
-                continue
-            if (val1 := getattr(self, retriever.p_name)) == (val2 := getattr(other, retriever.p_name)):
-                continue
-            if retriever.dtype.is_struct and not isinstance(val1, RefList): # todo: implement a diff on reflists
-                diff_retrievers.extend(val1.diff(val2))
-            else:
-                diff_retrievers.append(retriever)
-        return diff_retrievers
-
-    def __repr__(self) -> str:
-        repr_builder = StringIO()
-        repr_builder.write(f"{self.__class__.__name__}(")
-        for retriever in self._retrievers:
-            if not retriever.supported(self.struct_ver):
-                continue
-
-            obj = getattr(self, retriever.p_name)
-            if isinstance(obj, list):
-                sub_obj_repr_str = (
-                    "[\n    "
-                    + ",\n    ".join(map(lambda x: indentify(repr(x)), obj))
-                    + ",\n]"
-                )
-            else:
-                sub_obj_repr_str = f"{obj!r}"
-
-            repr_builder.write(f"\n    {retriever.p_name} = {indentify(sub_obj_repr_str)},")
-        repr_builder.write("\n)")
-        return repr_builder.getvalue()
-
-    def __eq__(self, other: object) -> bool:
-        if (
-            not isinstance(other, BaseStruct)
-            or type(self) is not type(other)
-            or self.struct_ver != other.struct_ver
-        ):
-            return False
-
-        for retriever in self._retrievers:
-            if not retriever.supported(self.struct_ver):
-                continue
-            if getattr(self, retriever.p_name) != getattr(other, retriever.p_name):
-                return False
-        return True
-
-    # todo: write val <-> data (names) to file
-    # todo: write hex (decompressed) to file
-    # todo: file/header/decompressed in both hex/val <-> data
-    # todo: to_json
diff --git a/src/binary_file_parser/retrievers/MapValidate.py b/src/binary_file_parser/retrievers/map_validate.py
similarity index 100%
rename from src/binary_file_parser/retrievers/MapValidate.py
rename to src/binary_file_parser/retrievers/map_validate.py
diff --git a/src/binary_file_parser/retrievers/Retriever.py b/src/binary_file_parser/retrievers/retriever.py
similarity index 72%
rename from src/binary_file_parser/retrievers/Retriever.py
rename to src/binary_file_parser/retrievers/retriever.py
index 3a0be1c..b6b14d5 100644
--- a/src/binary_file_parser/retrievers/Retriever.py
+++ b/src/binary_file_parser/retrievers/retriever.py
@@ -2,16 +2,11 @@
 
 import sys
 from io import BytesIO
-from typing import Any, Type, Callable, TypeVar
+from typing import Any, Callable, Type, TypeVar
 
-from binary_file_parser.errors import VersionError, DefaultValueError
-from binary_file_parser.types import ByteStream
-from binary_file_parser.types import Parseable
-
-from binary_file_parser.retrievers.MapValidate import MapValidate
-from binary_file_parser.retrievers.base_struct import BaseStruct
-from binary_file_parser.types.RefList import RefList
-from binary_file_parser.utils import Version
+from binary_file_parser.errors import DefaultAttributeError, VersionError
+from binary_file_parser.types import BaseStruct, ByteStream, DebugByteStream, Parseable, Version
+from binary_file_parser.retrievers.map_validate import MapValidate
 
 T = TypeVar("T")
 RetrieverSub = TypeVar("RetrieverSub", bound = "Retriever")
@@ -23,7 +18,7 @@ class Retriever(MapValidate):
     Represents a binary object in a struct and the restrictions/dependencies associated with it
     """
     __slots__ = (
-        "atype", "dtype", "min_ver", "max_ver", "default", "default_factory",
+        "dtype", "min_ver", "max_ver", "default", "default_factory",
         "_repeat", "remaining_compressed", "on_read", "on_write"
     )
 
@@ -33,10 +28,9 @@ def __init__(
         min_ver: Version = Version((-1,)),
         max_ver: Version = Version((sys.maxsize,)),
         *,
-        default = None,
-        default_factory: Callable[[Version, BaseStruct], Any] = None,
+        default: object = None,
+        default_factory: Callable[[Version], Any] = None,
         repeat: int = 1,
-        atype: Type[RefList] = RefList,
         remaining_compressed: bool = False,
         on_read: list[Callable[[RetrieverSub, BaseStructSub], None]] | None = None,  # todo: add implementations for common on_x operations
         on_write: list[Callable[[RetrieverSub, BaseStructSub], None]] | None = None,
@@ -44,7 +38,7 @@ def __init__(
         validators: list[Callable[[RetrieverSub, BaseStructSub, T], tuple[bool, str]]] | None = None,
         on_get: list[Callable[[RetrieverSub, BaseStructSub], None]] | None = None,
         on_set: list[Callable[[RetrieverSub, BaseStructSub], None]] | None = None,
-    ):
+    ) -> object:
         """
         :param dtype: The type of the value to read
         :param min_ver:
@@ -64,8 +58,6 @@ def __init__(
                 0: skips reading value entirely, set property to []
                 1: single value is read and assigned to the property
                 >1: a list of values is read and assigned to the property
-        :param atype:
-            The array like type to use when constructing lists for retrievers with dynamic repeats
         :param remaining_compressed:
             If set to true, the decompress/compress methods are used on the remaining bytes before reading/writing the
             remaining retriever properties
@@ -83,7 +75,6 @@ def __init__(
         """
         super().__init__(mappers, validators, on_get, on_set)
         self.dtype = dtype
-        self.atype = atype
         self.min_ver = min_ver
         self.max_ver = max_ver
 
@@ -92,7 +83,7 @@ def __init__(
             or default is not None
             and isinstance(default, BaseStruct)
         ):
-            raise DefaultValueError(
+            raise DefaultAttributeError(
                 "Using mutable types for default values is not allowed. Use a default_factory instead!"
             )
 
@@ -103,24 +94,6 @@ def __init__(
         self.on_read = on_read or []
         self.on_write = on_write or []
 
-        # if self._repeat == 1:
-        #     self.validators.append(lambda retriever, instance, x: dtype.is_valid(x))
-        # else:
-        #     self.validators.append(lambda retriever, instance, iterable: all(map(dtype.is_valid, iterable)))
-
-    @property
-    def is_self_versioned(self) -> bool:
-        """
-        True if the dtype of this retriever is a struct that implements its own versioning.
-        """
-        try:
-            self.dtype.__class__.get_version(stream = ByteStream.from_bytes(b"\x00"*8))
-            return True
-        except EOFError:
-            return True
-        except (VersionError, AttributeError):
-            return False
-
     def supported(self, ver: Version) -> bool:
         """
         Determine if this retriever property is supported in the specified version
@@ -140,14 +113,6 @@ def __set__(self, instance: BaseStruct, value: T) -> None:
                 f"{self.p_name!r} is not supported in your scenario version {instance.struct_ver}"
             )
 
-        # def set_parent(obj):
-        #     if isinstance(obj, BaseStruct):
-        #         obj.parent = instance
-        #     elif isinstance(obj, list):
-        #         for sub_obj in obj:
-        #             set_parent(sub_obj)
-        #
-        # set_parent(value)
         super().__set__(instance, value)
 
     def __get__(self, instance: BaseStruct, owner: Type[BaseStruct]) -> Retriever | T:
@@ -161,8 +126,6 @@ def __get__(self, instance: BaseStruct, owner: Type[BaseStruct]) -> Retriever |
         try:
             return super().__get__(instance, owner)
         except AttributeError:
-            if self.default is None and self.default_factory is not None:
-                raise ValueError(f"No default value specified for retriever {self.p_name!r}")
             return self.from_default(instance)
 
     @property
@@ -202,31 +165,15 @@ def from_default(self, instance: BaseStruct):
         if self.default is not None:
             if repeat == 1:
                 return self.default
-            return self.atype(
-                [self.default]*repeat,
-                instance.struct_ver, instance
-            )
+            return [self.default]*repeat
 
         if self.default_factory is not None:
             if repeat == 1:
-                val = self.default_factory(instance.struct_ver, instance)
-                if isinstance(val, list):
-                    return self.atype(val, instance.struct_ver, instance)
+                val = self.default_factory(instance.struct_ver)
                 return val
-            return self.atype(
-                (self.default_factory(instance.struct_ver, instance) for _ in range(repeat)),
-                instance.struct_ver, instance
-            )
-
-        if self.dtype.is_struct:
-            if repeat == 1:
-                return self.dtype(struct_ver = instance.struct_ver, parent = instance).map()
-            return self.atype(
-                (self.dtype(struct_ver = instance.struct_ver, parent = instance).map() for _ in range(repeat)),
-                instance.struct_ver, instance
-            )
+            return [self.default_factory(instance.struct_ver) for _ in range(repeat)]
 
-        raise DefaultValueError(
+        raise DefaultAttributeError(
             f"Unable to auto-initialise '{self.p_name}' as a default value is not provided"
         )
 
@@ -239,30 +186,32 @@ def from_stream(self, instance: BaseStruct, stream: ByteStream) -> None:
         """
         if not self.supported(instance.struct_ver):
             return
+        DebugByteStream.reader_ret = self.p_name
+
+        def call_on_reads():
+            for func in self.on_read:
+                func(self, instance)
 
         repeat = self.repeat(instance)
         if repeat == -1:
             setattr(instance, self.p_name, None)
+            call_on_reads()
             return
 
         def getobj():
-            if self.dtype.is_struct:
-                self.dtype: BaseStruct  # type: ignore
-                return self.dtype.from_stream(stream, struct_ver = instance.struct_ver, parent = instance)
-            return self.dtype.from_stream(stream, struct_ver = instance.struct_ver)
+            return self.dtype._from_stream(stream, struct_ver = instance.struct_ver)
 
         is_not_dynamic_repeat = not hasattr(instance, self.r_name)
         if repeat == 1 and is_not_dynamic_repeat:
             setattr(instance, self.p_name, getobj())
+            call_on_reads()
             return
 
         ls: list = [None] * repeat
         for i in range(repeat):
             ls[i] = getobj()
-        setattr(instance, self.p_name, self.atype(ls, instance.struct_ver, instance))
-
-        for func in self.on_read:
-            func(self, instance)
+        setattr(instance, self.p_name, ls)
+        call_on_reads()
 
     def to_bytes(self, instance: BaseStruct) -> bytes:
         """
@@ -283,7 +232,7 @@ def to_bytes(self, instance: BaseStruct) -> bytes:
 
         is_not_dynamic_repeat = not hasattr(instance, self.r_name)
         if repeat == 1 and is_not_dynamic_repeat:
-            return self.dtype.to_bytes(getattr(instance, self.p_name))
+            return self.dtype._to_bytes(getattr(instance, self.p_name))
 
         ls: list = getattr(instance, self.p_name)
         if len(ls) != repeat:
@@ -291,6 +240,9 @@ def to_bytes(self, instance: BaseStruct) -> bytes:
 
         bytes_ = BytesIO()
         for value in getattr(instance, self.p_name):
-            bytes_.write(self.dtype.to_bytes(value))
+            bytes_.write(self.dtype._to_bytes(value))
 
         return bytes_.getvalue()
+
+    def __str__(self) -> str:
+        return self.p_name
diff --git a/src/binary_file_parser/retrievers/RetreiverCombiner.py b/src/binary_file_parser/retrievers/retriever_combiner.py
similarity index 63%
rename from src/binary_file_parser/retrievers/RetreiverCombiner.py
rename to src/binary_file_parser/retrievers/retriever_combiner.py
index 7500077..0eca5ef 100644
--- a/src/binary_file_parser/retrievers/RetreiverCombiner.py
+++ b/src/binary_file_parser/retrievers/retriever_combiner.py
@@ -1,13 +1,15 @@
 from __future__ import annotations
 
 from contextlib import suppress
-from typing import Type, TypeVar, Generic
+from typing import Generic, Type, TYPE_CHECKING, TypeVar
 
-from binary_file_parser.utils import Version
 from binary_file_parser.errors import VersionError
+from binary_file_parser.types.version import Version
 
-from binary_file_parser.retrievers.base_struct import BaseStruct
-from binary_file_parser.retrievers.Retriever import Retriever
+if TYPE_CHECKING:
+    from binary_file_parser.retrievers.retriever import Retriever
+    from binary_file_parser.retrievers.retriever_ref import RetrieverRef
+    from binary_file_parser.types.base_struct import BaseStruct
 
 
 T = TypeVar("T")
@@ -18,11 +20,11 @@ class RetrieverCombiner(Generic[T]):
     Creates a single attribute to access values from multiple retrievers which are mutually exclusive among different
     struct versions
     """
-    def __init__(self, retrievers: list[Retriever] = None) -> None:
+    def __init__(self, *retrievers: Retriever | RetrieverRef | RetrieverCombiner) -> None:
         """
-        :param retrievers: A list of retrievers to combine
+        :param retrievers: The retrievers to combine
         """
-        self.retrievers = retrievers or []
+        self.retrievers = retrievers
 
     def __set_name__(self, owner: Type[BaseStruct], name: str) -> None:
         self.name = name
@@ -47,6 +49,12 @@ def __get__(self, instance: BaseStruct, owner: Type[BaseStruct]) -> RetrieverCom
             f"{self.name!r} is not supported in your struct version {instance.struct_ver}"
         )
 
+    # todo: fix this
+    @property
+    def p_name(self):
+        return self.name
+
+    # todo: this doesn't work for refs
     def get_p_name(self, struct_ver: Version) -> str:
         """
         Find out the name of the retriever which holds the value referenced by this retriever combiner for the provided
@@ -59,3 +67,13 @@ def get_p_name(self, struct_ver: Version) -> str:
         raise VersionError(
             f"{self.name!r} is not supported in your struct version {struct_ver}"
         )
+
+    # todo: this doesn't work for refs
+    def set_repeat(self, instance: BaseStruct, repeat: int) -> None:
+        for retriever in self.retrievers:
+            if retriever.supported(instance.struct_ver):
+                retriever.set_repeat(instance, repeat)
+                return
+        raise VersionError(
+            f"{self.name!r} is not supported in your struct version {instance.struct_ver}"
+        )
diff --git a/src/binary_file_parser/retrievers/retriever_ref.py b/src/binary_file_parser/retrievers/retriever_ref.py
new file mode 100644
index 0000000..e34f0fb
--- /dev/null
+++ b/src/binary_file_parser/retrievers/retriever_ref.py
@@ -0,0 +1,73 @@
+from __future__ import annotations
+
+from contextlib import suppress
+from operator import attrgetter, itemgetter
+from typing import Any, Callable, Type, TYPE_CHECKING, TypeVar, Generic
+
+from binary_file_parser.errors import VersionError
+
+if TYPE_CHECKING:
+    from binary_file_parser.types.base_struct import BaseStruct
+    from binary_file_parser.retrievers.retriever import Retriever
+    from binary_file_parser.retrievers.retriever_combiner import RetrieverCombiner
+
+
+T = TypeVar("T")
+
+
+class RetrieverRef(Generic[T]):
+    """
+    Create a new reference to an existing retriever, combiner or another ref
+    """
+    def __init__(self, *retrievers: Retriever | RetrieverCombiner | RetrieverRef | int):
+        """
+        :param retrievers: The retrievers or the list indexes to the final property to reference
+        """
+        if len(retrievers) == 0:
+            raise TypeError("Cannot create an empty reference")
+
+        self.retrievers = retrievers
+        self.getter: list[Callable[[BaseStruct], Any]] = []
+        self.last: int | str = -1
+
+    def __set_name__(self, owner: Type[BaseStruct], name: str) -> None:
+        self.name = name
+
+        self.getter = [
+            itemgetter(retriever) if isinstance(retriever, int) else attrgetter(retriever.p_name)
+            for retriever in self.retrievers
+        ]
+        last_retriever = self.retrievers[-1]
+        self.last = last_retriever if isinstance(last_retriever, int) else last_retriever.p_name
+        owner._add_ref(self)
+
+    def __set__(self, instance: BaseStruct, value: T) -> None:
+        item = instance._struct
+        with suppress(VersionError):
+            for get in self.getter[:-1]:
+                item = get(item)
+            if isinstance(self.last, int):
+                item[self.last] = value
+            else:
+                setattr(item, self.last, value)
+            return
+        raise VersionError(
+            f"{self.name!r} is not supported in your struct version {item.struct_ver}"
+        )
+
+    def __get__(self, instance: BaseStruct, owner: Type[BaseStruct]) -> RetrieverRef | T:
+        if instance is None:
+            return self
+        item = instance._struct
+        with suppress(VersionError):
+            for get in self.getter:
+                item = get(item)
+            return item
+        raise VersionError(
+            f"{self.name!r} is not supported in your struct version {item.struct_ver}"
+        )
+
+    # todo: fix this
+    @property
+    def p_name(self):
+        return self.name
diff --git a/src/binary_file_parser/retrievers/type_cast.py b/src/binary_file_parser/retrievers/type_cast.py
new file mode 100644
index 0000000..fe0b32b
--- /dev/null
+++ b/src/binary_file_parser/retrievers/type_cast.py
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from binary_file_parser import Retriever, RetrieverRef, RetrieverCombiner
+
+def ret(t) -> Retriever:
+    """Cast a Retriever to the right type when the type checker doesn't understand class level access"""
+    return t
+
+def ref(t) -> RetrieverRef:
+    """Cast a RetrieverRef to the right type when the type checker doesn't understand class level access"""
+    return t
+
+def com(t) -> RetrieverCombiner:
+    """Cast a RetrieverCombiner to the right type when the type checker doesn't understand class level access"""
+    return t
diff --git a/src/binary_file_parser/types/Array.py b/src/binary_file_parser/types/Array.py
deleted file mode 100644
index 101b6e3..0000000
--- a/src/binary_file_parser/types/Array.py
+++ /dev/null
@@ -1,186 +0,0 @@
-from __future__ import annotations
-
-import struct
-from typing import Type
-
-from binary_file_parser.types.ByteStream import ByteStream
-from binary_file_parser.types.Parseable import Parseable
-from binary_file_parser.types.RefList import RefList
-from binary_file_parser.utils import Version
-
-ParseableType = Type[Parseable] | Parseable
-
-class BaseArray(Parseable):
-    __slots__ = ("atype", "dtype", "struct_symbol", "length")
-
-    @property
-    def is_iterable(self) -> bool:
-        return True
-
-    def __init__(self, size: int, dtype: ParseableType, struct_symbol: str, atype: Type[RefList] = RefList):
-        super().__init__(size)
-        self.dtype = dtype
-        self.atype = atype
-        self.struct_symbol = struct_symbol
-        self.length = -1
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> list:
-        ls = [None] * self.length
-        for i in range(self.length):
-            ls[i] = self.dtype.from_stream(stream, struct_ver = struct_ver)
-        return self.atype(ls)
-
-    def from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> list:
-        return self.from_stream(ByteStream.from_bytes(bytes_), struct_ver = struct_ver)
-
-    def to_bytes(self, value: list) -> bytes:
-        ls = [b""]*self.length
-        for i, val in enumerate(value):
-            ls[i] = self.dtype.to_bytes(val)
-        return b"".join(ls)
-
-
-class Array(BaseArray):
-    __slots__ = ()
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> list:
-        self.length = struct.unpack(self.struct_symbol, stream.get(self.size))[0]
-        return super().from_stream(stream, struct_ver = struct_ver)
-
-    def to_bytes(self, value: list) -> bytes:
-        self.length = len(value)
-        length_bytes = struct.pack(self.struct_symbol, self.length)
-        return length_bytes+super().to_bytes(value)
-
-class Array8(Array):
-    __slots__ = ()
-
-    def __class_getitem__(cls, item: ParseableType) -> Array8:
-        return cls(1, item, '<B')
-
-class Array16(Array):
-    __slots__ = ()
-
-    def __class_getitem__(cls, item: ParseableType) -> Array16:
-        return cls(2, item, '<H')
-
-class Array32(Array):
-    __slots__ = ()
-
-    def __class_getitem__(cls, item: ParseableType) -> Array32:
-        return cls(4, item, '<I')
-
-class Array64(Array):
-    __slots__ = ()
-
-    def __class_getitem__(cls, item: ParseableType) -> Array64:
-        return cls(8, item, '<Q')
-
-
-class FixedLenArray(BaseArray):
-    __slots__ = ()
-
-    def __init__(self, size: int, dtype: ParseableType, struct_symbol: str, length: int, atype: Type[RefList] = RefList):
-        super().__init__(size, dtype, struct_symbol, atype)
-        self.length = length
-
-    def is_valid(self, value: list) -> tuple[bool, str]:
-        if len(value) == self.length:
-            return True, ""
-        return False, f"%s must have a fixed length of {value}"
-
-    def to_bytes(self, value: list) -> bytes:
-        valid, msg = self.is_valid(value)
-        if not valid:
-            raise TypeError(msg)
-        return super().to_bytes(value)
-
-    def __class_getitem__(cls, item: tuple[ParseableType, int]) -> FixedLenArray:
-        return cls(4, item[0], '<I', item[1])
-
-
-class StackedArrays(BaseArray):
-    __slots__ = "num_arrays"
-
-    def __init__(self, size: int, dtype: ParseableType, struct_symbol: str, num_arrays: int = -1, atype: Type[RefList] = RefList):
-        super().__init__(size, dtype, struct_symbol, atype)
-        self.num_arrays = num_arrays
-
-    def is_valid(self, value: list[list]) -> tuple[bool, str]:
-        if self.num_arrays == -1:
-            return True, ""
-
-        num_arrays = len(value)
-        if num_arrays == self.num_arrays:
-            return True, ""
-        return False, f"%s expected {self.num_arrays} but found {num_arrays} stacked arrays"
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> list[list]:
-        num_arrays = self.num_arrays
-        if num_arrays == -1:
-            num_arrays = struct.unpack(self.struct_symbol, stream.get(self.size))[0]
-
-        lengths: list[int] = [struct.unpack(self.struct_symbol, stream.get(self.size))[0] for _ in range(num_arrays)]
-        ls: list[list] = [[] for _ in range(num_arrays)]
-
-        for i, length in enumerate(lengths):
-            self.length = length
-            ls[i] = super().from_stream(stream, struct_ver = struct_ver)
-
-        return self.atype(ls)
-
-    def to_bytes(self, value: list[list]) -> bytes:
-        valid, msg = self.is_valid(value)
-        if not valid:
-            raise TypeError(msg)
-
-        length_bytes = b""
-        num_arrays = self.num_arrays
-        if num_arrays == -1:
-            num_arrays = len(value)
-            length_bytes = struct.pack(self.struct_symbol, num_arrays)
-
-        bytes_: list[bytes] = [b""]*(2*num_arrays)
-        lengths = [len(ls) for ls in value]
-        for i, length in enumerate(lengths):
-            self.length = length
-            bytes_[i] = struct.pack(self.struct_symbol, length)
-            bytes_[num_arrays+i] = super().to_bytes(value[i])
-
-        return length_bytes+b"".join(bytes_)
-
-
-class StackedArray8s(StackedArrays):
-    __slots__ = ()
-
-    def __class_getitem__(cls, item: ParseableType | tuple[ParseableType, int]) -> StackedArrays:
-        if isinstance(item, tuple):
-            return cls(4, item[0], '<B', item[1])
-        return cls(4, item, '<B')
-
-
-class StackedArray16s(StackedArrays):
-    __slots__ = ()
-
-    def __class_getitem__(cls, item: ParseableType | tuple[ParseableType, int]) -> StackedArrays:
-        if isinstance(item, tuple):
-            return cls(4, item[0], '<H', item[1])
-        return cls(4, item, '<H')
-
-
-class StackedArray32s(StackedArrays):
-    __slots__ = ()
-
-    def __class_getitem__(cls, item: ParseableType | tuple[ParseableType, int]) -> StackedArrays:
-        if isinstance(item, tuple):
-            return cls(4, item[0], '<I', item[1])
-        return cls(4, item, '<I')
-
-
-class StackedArray64s(StackedArrays):
-    __slots__ = ()
-
-    def __class_getitem__(cls, item: ParseableType | tuple[ParseableType, int]) -> StackedArrays:
-        if isinstance(item, tuple):
-            return cls(4, item[0], '<Q', item[1])
-        return cls(4, item, '<Q')
diff --git a/src/binary_file_parser/types/Bool.py b/src/binary_file_parser/types/Bool.py
deleted file mode 100644
index fda6c99..0000000
--- a/src/binary_file_parser/types/Bool.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import struct
-
-from binary_file_parser.types.ByteStream import ByteStream
-from binary_file_parser.types.Parseable import Parseable
-from binary_file_parser.utils import Version
-
-
-class Bool(Parseable):
-    __slots__ = "struct_symbol"
-
-    def __init__(self, size: int, struct_symbol: str):
-        super().__init__(size)
-        self.struct_symbol = struct_symbol
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> bool:
-        return self.from_bytes(stream.get(self.size), struct_ver = struct_ver)
-
-    def from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> bool:
-        return not not struct.unpack(self.struct_symbol, bytes_)[0]
-
-    def to_bytes(self, value: bool) -> bytes:
-        return struct.pack(self.struct_symbol, 1 if value else 0)
-
-bool8 = Bool(1, "<B")
-bool16 = Bool(2, "<H")
-bool32 = Bool(4, "<I")
-bool64 = Bool(8, "<Q")
diff --git a/src/binary_file_parser/types/Bytes.py b/src/binary_file_parser/types/Bytes.py
deleted file mode 100644
index 5e98e52..0000000
--- a/src/binary_file_parser/types/Bytes.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser.types.ByteStream import ByteStream
-from binary_file_parser.types.Parseable import Parseable
-from binary_file_parser.utils import Version
-
-
-class Bytes(Parseable):
-    __slots__ = ()
-
-    def is_valid(self, value: bytes) -> tuple[bool, str]:
-        if len(value) == self.size:
-            return True, ""
-        return False, f"number of bytes in %s must equal {self.size}"
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> bytes:
-        return stream.get(self.size)
-
-    def from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> bytes:
-        return bytes_
-
-    def to_bytes(self, value: bytes) -> bytes:
-        return value
-
-    def __class_getitem__(cls, item: int) -> Bytes:
-        return cls(item)
diff --git a/src/binary_file_parser/types/Float.py b/src/binary_file_parser/types/Float.py
deleted file mode 100644
index e70ed64..0000000
--- a/src/binary_file_parser/types/Float.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import struct
-
-from binary_file_parser.types.ByteStream import ByteStream
-from binary_file_parser.types.Parseable import Parseable
-from binary_file_parser.utils import Version
-
-
-class Float(Parseable):
-    __slots__ = "struct_symbol"
-
-    def __init__(self, size: int, struct_symbol: str):
-        super().__init__(size)
-        self.struct_symbol = struct_symbol
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> float:
-        return self.from_bytes(stream.get(self.size))
-
-    def from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> float:
-        return struct.unpack(self.struct_symbol, bytes_)[0]
-
-    def to_bytes(self, value: float) -> bytes:
-        return struct.pack(self.struct_symbol, value)
-
-float16 = Float(2, "e")
-float32 = Float(4, "f")
-float64 = Float(8, "d")
diff --git a/src/binary_file_parser/types/Parseable.py b/src/binary_file_parser/types/Parseable.py
deleted file mode 100644
index 9486439..0000000
--- a/src/binary_file_parser/types/Parseable.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from abc import ABC, abstractmethod
-from typing import TypeVar
-
-from binary_file_parser.types.ByteStream import ByteStream
-from binary_file_parser.utils import Version
-
-T = TypeVar("T")
-class Parseable(ABC):
-    __slots__ = "size"
-
-    def __init__(self, size: int):
-        self.size = size
-
-    @staticmethod
-    def is_valid(value: T) -> tuple[bool, str]:
-        """
-        Unused atm
-        :param value:
-        :return:
-        """
-        return True, ""
-
-    @property
-    def is_struct(self) -> bool:
-        """
-        If a class has its own retrievers and implements a from_default method, then this should return True
-
-        :return: True if the object has multiple retrievers, False otherwise
-        """
-        return False
-
-    @property
-    def is_iterable(self) -> bool:
-        """
-        If the datatype is an iterator (container) like datatype
-
-        :return: True if the object is an iterator, False otherwise
-        """
-        return False
-
-    @abstractmethod
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> T:
-        ...
-
-    @abstractmethod
-    def from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> T:
-        ...
-
-    @abstractmethod
-    def to_bytes(self, value: T) -> bytes:
-        ...
diff --git a/src/binary_file_parser/types/RefList.py b/src/binary_file_parser/types/RefList.py
deleted file mode 100644
index 65c08ed..0000000
--- a/src/binary_file_parser/types/RefList.py
+++ /dev/null
@@ -1,100 +0,0 @@
-from __future__ import annotations
-
-import copy
-from typing import Callable, Generic, Iterable, overload, Type, TYPE_CHECKING, TypeVar, SupportsIndex
-
-from binary_file_parser.utils import Version
-
-if TYPE_CHECKING:
-    from binary_file_parser.retrievers import BaseStruct
-
-T = TypeVar("T")
-
-
-def ref_listify(cls: Type[RefList], struct_ver: Version, parent: BaseStruct) -> Callable[[T], T]:
-    def listify(i):
-        if not isinstance(i, list):
-            return i
-        return cls(i, struct_ver, parent)
-
-    return listify
-
-
-class RefList(list, Generic[T]):
-    def __init__(self, iterable: Iterable[T], struct_ver = Version((0,)), parent: BaseStruct = None):
-        # todo: set idxs on structs where relevant
-        # todo: set own idx for nested lists
-        # todo: stop recursive set struct vers and parents if same
-        iterable = map(ref_listify(self.__class__, struct_ver, parent), iterable)
-        super().__init__(iterable)
-        self.struct_ver = struct_ver
-        self.parent = parent
-
-    def __deepcopy__(self, memo):
-        cls = self.__class__
-
-        cloned_list = copy.deepcopy(list(self), memo)
-
-        reflist = cls(cloned_list, copy.deepcopy(self.struct_ver), None)
-        memo[id(self)] = reflist
-
-        return reflist
-
-    def set_sub_attrs(self, obj: T):
-        if getattr(obj, 'is_struct', False) or isinstance(obj, RefList):
-            obj.parent = self.parent
-            obj.struct_ver = self.struct_ver
-
-    @property
-    def struct_ver(self) -> Version:
-        return self._struct_ver
-
-    @struct_ver.setter
-    def struct_ver(self, new_struct_ver: Version):
-        self._struct_ver = new_struct_ver
-        for obj in self:
-            if getattr(obj, 'is_struct', False) or isinstance(obj, RefList):
-                obj.struct_ver = self.struct_ver
-
-    @property
-    def parent(self) -> BaseStruct:
-        return self._parent
-
-    @parent.setter
-    def parent(self, new_parent: BaseStruct):
-        self._parent = new_parent
-        for obj in self:
-            if getattr(obj, 'is_struct', False) or isinstance(obj, RefList):
-                obj.parent = self.parent
-
-    def append(self, obj: T) -> None:
-        super().append(obj)
-        self.set_sub_attrs(obj)
-
-    def extend(self, iterable: Iterable[T]) -> None:
-        super().extend(iterable)
-        for obj in iterable:
-            self.set_sub_attrs(obj)
-
-    def insert(self, index: SupportsIndex, obj: T) -> None:
-        super().insert(index, obj)
-        self.set_sub_attrs(obj)
-
-    @overload
-    def __setitem__(self, i: SupportsIndex, o: T) -> None: ...
-
-    @overload
-    def __setitem__(self, s: slice, o: Iterable[T]) -> None: ...
-
-    def __setitem__(self, i: SupportsIndex | slice, o: T | Iterable[T]) -> None:
-        self.set_sub_attrs(o)
-        super().__setitem__(i, o)
-
-    def __add__(self, x: RefList[T]) -> RefList[T]:
-        return RefList(super().__add__(x), self.struct_ver, self.parent)
-
-    def __iadd__(self: RefList[T], x: Iterable[T]) -> RefList[T]:
-        return RefList(super().__iadd__(x), self.struct_ver, self.parent)
-
-    def __radd__(self, other: RefList[T]) -> RefList[T]:
-        return self.__add__(other)
diff --git a/src/binary_file_parser/types/Str.py b/src/binary_file_parser/types/Str.py
deleted file mode 100644
index b28e423..0000000
--- a/src/binary_file_parser/types/Str.py
+++ /dev/null
@@ -1,100 +0,0 @@
-from __future__ import annotations
-
-import struct
-from abc import ABC
-
-from binary_file_parser.types.ByteStream import ByteStream
-from binary_file_parser.types.Parseable import Parseable
-from binary_file_parser.utils import Version
-
-
-class BaseStr(Parseable, ABC):
-    __slots__ = ()
-
-    def from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> str:
-        try:
-            return bytes_.decode("utf-8")
-        except UnicodeDecodeError:
-            return bytes_.decode("latin-1")
-
-    def to_bytes(self, value: str) -> bytes:
-        # TODO: plain bytes
-        try:
-            bytes_ = value.encode("utf-8")
-        except UnicodeEncodeError:
-            bytes_ = value.encode("latin-1")
-
-        return bytes_
-
-
-class CStr(BaseStr):
-    __slots__ = ()
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> str:
-        bytes_ = b""
-        while (byte := stream.get(1)) != b"\x00":
-            bytes_ += byte
-        return self.from_bytes(bytes_)
-
-    def to_bytes(self, value: str) -> bytes:
-        if not value.endswith("\x00"):
-            value += "\x00"
-        return super().to_bytes(value)
-
-
-class Str(BaseStr):
-    __slots__ = "struct_symbol"
-
-    def __init__(self, size: int, struct_symbol: str):
-        super().__init__(size)
-        self.struct_symbol = struct_symbol
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> str:
-        length: int = struct.unpack(self.struct_symbol, stream.get(self.size))[0]
-        return self.from_bytes(stream.get(length))
-
-    def to_bytes(self, value: str) -> bytes:
-        bytes_ = super().to_bytes(value)
-        length = struct.pack(self.struct_symbol, len(bytes_))
-        return length+bytes_
-
-
-class NullTermStr(Str):
-    __slots__ = ()
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> str:
-        return super().from_stream(stream, struct_ver = struct_ver)[:-1]
-
-    def to_bytes(self, value: str) -> bytes:
-        if not value.endswith("\x00"):
-            value += "\x00"
-        return super().to_bytes(value)
-
-
-class FixedLenStr(BaseStr):
-    __slots__ = "length"
-
-    def __init__(self, size: int, length: int):
-        super().__init__(size)
-        self.length = length
-
-    def is_valid(self, value: str) -> tuple[bool, str]:
-        if len(value) == self.length:
-            return True, ""
-        return False, f"%s must have a fixed length of {value}"
-
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> str:
-        return self.from_bytes(stream.get(self.length))
-
-    def __class_getitem__(cls, item: int) -> FixedLenStr:
-        return cls(4, item)
-
-c_str = CStr(4)
-str8 = Str(1, "<B")
-str16 = Str(2, "<H")
-str32 = Str(4, "<I")
-str64 = Str(8, "<Q")
-nt_str8 = NullTermStr(1, "<B")
-nt_str16 = NullTermStr(2, "<H")
-nt_str32 = NullTermStr(4, "<I")
-nt_str64 = NullTermStr(8, "<Q")
diff --git a/src/binary_file_parser/types/__init__.py b/src/binary_file_parser/types/__init__.py
index 1a8d148..aba4831 100644
--- a/src/binary_file_parser/types/__init__.py
+++ b/src/binary_file_parser/types/__init__.py
@@ -1,13 +1,8 @@
-from .Array import (
-    Array8, Array16, Array32, Array64, FixedLenArray, StackedArray8s, StackedArray16s, StackedArray32s, StackedArray64s
-)
-from .Bool import bool8, bool16, bool32, bool64
-from .Bytes import Bytes
-from .ByteStream import ByteStream
-from .Float import float16, float32, float64
-from .Int import int8, int16, int32, int64, uint8, uint16, uint32, uint64
-from .Parseable import Parseable
-from .RefList import RefList
-from .Str import c_str, str8, str16, str32, str64, nt_str8, nt_str16, nt_str32, nt_str64, FixedLenStr
+from .le import *
 
-void = Bytes[0]
+from .base_struct import BaseStruct
+from .byte_stream import ByteStream
+from .debug_byte_stream import DebugByteStream
+from .manager import Manager
+from .parseable import Parseable
+from .version import Version
diff --git a/src/binary_file_parser/types/base_struct.py b/src/binary_file_parser/types/base_struct.py
new file mode 100644
index 0000000..d88e44c
--- /dev/null
+++ b/src/binary_file_parser/types/base_struct.py
@@ -0,0 +1,394 @@
+from __future__ import annotations
+
+from contextlib import suppress
+from typing import Type, TYPE_CHECKING
+
+from alive_progress import alive_it
+
+from binary_file_parser.errors import CompressionError, ParsingError, VersionError
+from binary_file_parser.types.parseable import Parseable
+from binary_file_parser.types.byte_stream import ByteStream
+from binary_file_parser.types.version import Version
+from binary_file_parser.utils import TabbedStringIO
+
+if TYPE_CHECKING:
+    from binary_file_parser.retrievers import Retriever, RetrieverCombiner, RetrieverRef
+
+
+class BaseStruct(Parseable):
+    """
+    Base class for defining a file format as a structure. Most methods of this class are protected to be hidden from the
+    end user API
+    """
+    __slots__ = "_struct_ver"
+
+    _retrievers: list[Retriever] = []
+    _refs: list[RetrieverRef] = []
+    _combiners: list[RetrieverCombiner] = []
+
+    @classmethod
+    def _add_retriever(cls, retriever: Retriever):
+        cls._retrievers.append(retriever)
+
+    @classmethod
+    def _add_ref(cls, ref: RetrieverRef):
+        cls._refs.append(ref)
+
+    @classmethod
+    def _add_combiner(cls, combiner: RetrieverCombiner):
+        cls._combiners.append(combiner)
+
+    def __init__(self, struct_ver: Version = Version((0,)), initialise_defaults: bool = True, **retriever_inits):
+        """
+        :param struct_ver: The struct version to create
+        :param initialise_defaults:
+            If set to false, skip initialisation of struct values from default. This is only set to false when reading
+            a file
+        :param retriever_inits:
+            Use this to provide initial values to retrievers
+        """
+        self._struct_ver = struct_ver
+
+        size = 0
+        if not initialise_defaults:
+            super().__init__(size)
+            return
+
+        for retriever in self._retrievers:
+            if not retriever.supported(struct_ver):
+                continue
+
+            init = retriever_inits.get(retriever.p_name, None)
+            if init is None:
+                init = retriever.from_default(self)
+            setattr(self, retriever.p_name, init)
+
+            # size += retriever.dtype._size
+        super().__init__(size)
+
+    def __init_subclass__(cls, **kwargs):
+        """
+        Note: subclasses of BaseStruct which intend to act as ABCs need to override this
+        """
+        cls._retrievers, BaseStruct._retrievers = cls._retrievers.copy(), []
+        cls._refs, BaseStruct._refs = cls._refs.copy(), []
+        cls._combiners, BaseStruct._combiners = cls._combiners.copy(), []
+
+    @property
+    def _struct(self):
+        return self
+
+    @property
+    def struct_ver(self) -> Version:
+        """The struct_ver of this struct used when retrievers are versioned"""
+        return self._struct_ver
+
+    @classmethod
+    def _get_version(cls, stream: ByteStream, struct_ver: Version = Version((0,))) -> Version:
+        """
+        If defined, the struct will be versioned and the retrievers in the struct which are not supported will be
+        skipped during read/write and will raise a VersionError on access.
+
+        :param stream: The stream to read the struct version from
+        :param struct_ver: The struct version of the parent
+
+        :return: A tuple of ints indicating the version eg: v1.47 should return (1, 47)
+
+        :raises VersionError: - When unimplemented
+        """
+        raise VersionError("Un-versioned File")
+
+    @classmethod
+    def _decompress(cls, bytes_: bytes) -> bytes:
+        """
+        If remaining_compressed is set to True in a Retriever, this method is used to decompress the bytes remaining
+        before they are read and parsed into a struct object
+
+        :param bytes_: The remaining (compressed) bytes of the compressed section of the struct
+
+        :return: decompressed bytes
+
+        :raises CompressionError: - When unimplemented
+        """
+        raise CompressionError(
+            "Unable to read object from file. "
+            "A Structure with a compressed section needs to implement 'decompress' classmethod."
+        )
+
+    @classmethod
+    def _compress(cls, bytes_: bytes) -> bytes:
+        """
+        If remaining_compressed is set to True in a Retriever, this method is used to compress the bytes before they are
+        written to file from the struct object
+
+        :param bytes_: The remaining (uncompressed) bytes of the compressed section of the struct
+
+        :return: compressed bytes
+
+        :raises CompressionError: - When unimplemented
+        """
+        raise CompressionError(
+            "Unable to write object to file. "
+            "A Structure with a compressed section needs to implement 'compress' classmethod."
+        )
+
+    @classmethod
+    def _from_stream(
+        cls, stream: ByteStream, *, struct_ver: Version = Version((0,)), strict: bool = False,
+        show_progress: bool = False
+    ) -> BaseStruct:
+        """
+        Create a struct object from a ByteStream
+
+        :param stream: The stream to create the struct object from
+        :param struct_ver: The version of the structure to create. Overwritten if `get_version` is defined
+        :param strict: Raise an error if struct parsing finishes successfully but the stream has left over bytes
+        :param show_progress: When true, display a progress bar
+
+        :return: An instance of a subtype of BaseStruct
+        """
+        with suppress(VersionError):
+            struct_ver = cls._get_version(stream, struct_ver)
+
+        instance = cls(struct_ver = struct_ver, initialise_defaults = False)
+        retriever_ls = cls._retrievers
+        if show_progress:
+            retriever_ls = alive_it(
+                retriever_ls,
+                dual_line = True,
+                title = "         Reading File",
+                stats = False,
+                finalize = lambda bar: bar.title("Finished Reading File")
+            )
+
+        for retriever in retriever_ls:
+            if show_progress:
+                retriever_ls.text = f"            -> {retriever.p_name.title().replace('_', ' ')}"
+            if retriever.remaining_compressed:
+                stream = stream.__class__.from_bytes(cls._decompress(stream.remaining()))
+            retriever.from_stream(instance, stream)
+
+        file_len = len(stream.content)
+
+        if stream.progress != file_len and strict:
+            raise ParsingError(
+                f"{file_len - stream.progress} bytes are left after parsing all retrievers successfully"
+            )
+
+        return instance
+
+    @classmethod
+    def _from_bytes(
+        cls, bytes_: bytes, *, struct_ver: Version = Version((0,)), strict = False,
+        show_progress: bool = False, stream_cls: Type[ByteStream] = ByteStream
+    ) -> BaseStruct:
+        """
+        Create a struct object from bytes
+
+        :param bytes_: The bytes to create the struct object from
+        :param struct_ver: The version of the structure to create. Overwritten if `get_version` is defined
+        :param strict: Raise an error if struct parsing finishes successfully but there are unused bytes left over
+        :param show_progress: When true, display a progress bar
+
+        :return: An instance of a subtype of BaseStruct
+        """
+        stream = stream_cls.from_bytes(bytes_)
+        return cls._from_stream(stream, struct_ver = struct_ver, strict = strict, show_progress = show_progress)
+
+    @classmethod
+    def _from_file(
+        cls, file_name: str, *, file_version: Version = Version((0,)), strict = True,
+        show_progress: bool = True, stream_cls: Type[ByteStream] = ByteStream
+    ) -> BaseStruct:
+        """
+        Create a struct object from file
+
+        :param file_name: The path of the file to create the struct object from
+        :param file_version: The version of the structure to create. Overwritten if `get_version` is defined
+        :param strict: Raise an error if struct parsing finishes successfully but the stream has left over bytes
+        :param show_progress: When true, display a progress bar
+
+        :return: An instance of a subtype of BaseStruct
+        """
+        stream = stream_cls.from_file(file_name)
+        return cls._from_stream(stream, struct_ver = file_version, strict = strict, show_progress = show_progress)
+
+    @classmethod
+    def _from_compressed_file(
+        cls, file_name: str, *, file_version: Version = Version((0,)), strict = True,
+        show_progress: bool = True, stream_cls: Type[ByteStream] = ByteStream
+    ) -> BaseStruct:
+        """
+        Create a struct object from file
+
+        :param file_name: The path of the file to create the struct object from
+        :param file_version: The version of the structure to create. Overwritten if `get_version` is defined
+        :param strict: Raise an error if struct parsing finishes successfully but the stream has left over bytes
+        :param show_progress: When true, display a progress bar
+
+        :return: An instance of a subtype of BaseStruct
+        """
+        stream = stream_cls.from_file(file_name)
+        stream = stream_cls.from_bytes(cls._decompress(stream.remaining()))
+        return cls._from_stream(stream, struct_ver = file_version, strict = strict, show_progress = show_progress)
+
+    def _to_bytes(self, *, show_progress = False) -> bytes:
+        """
+        Convert the struct object to bytes
+
+        :param instance: The struct object to convert to bytes
+        :param show_progress: When true, display a progress bar
+
+        :return: bytes
+        """
+        length = len(self._retrievers)
+
+        bytes_ = [b""] * length
+        compress_idx = length
+        retriever_ls = self._retrievers
+        if show_progress:
+            retriever_ls = alive_it(
+                retriever_ls,
+                dual_line = True,
+                title = "         Writing File",
+                stats = False,
+                finalize = lambda bar: bar.title("Finished Writing File"),
+            )
+
+        for i, retriever in enumerate(retriever_ls):
+            if show_progress:
+                retriever_ls.text = f"            <- {retriever.p_name.title().replace('_', ' ')}"
+            if retriever.remaining_compressed:
+                compress_idx = i
+            bytes_[i] = retriever.to_bytes(self)
+
+        compressed = b""
+        if compress_idx != length:
+            compressed = self._compress(b"".join(bytes_[compress_idx:]))
+
+        return b"".join(bytes_[:compress_idx]) + compressed
+
+    def _to_file(self, file_name: str, *, show_progress: bool = True):
+        """
+        Write the bytes of the struct object to a file
+
+        :param file_name: The name of the file to write to
+        :param show_progress: When true, display a progress bar
+        """
+        with open(file_name, "wb") as file:
+            file.write(self._to_bytes(show_progress = show_progress))
+
+    def _to_compressed_file(self, file_name: str, *, show_progress: bool = True):
+        """
+        Write the bytes of the struct object to a file
+
+        :param file_name: The name of the file to write to
+        :param show_progress: When true, display a progress bar
+        """
+        with open(file_name, "wb") as file:
+            file.write(self._compress(self._to_bytes(show_progress = show_progress)))
+
+    def _diff(self, other: BaseStruct) -> dict[str, tuple | dict]:
+        """
+        Get a dictionary of retriever names to tuples values for which the two provided structs differ.
+
+        :param other: The object to diff with
+
+        :return: dictionary with differing retriever names as keys and their values as tuples.
+        """
+        if (
+            not isinstance(other, BaseStruct)
+            or type(self) is not type(other)
+        ):
+            raise TypeError(f"Cannot diff '{type(self)}' with an object of type '{type(other)}'")
+
+        diff_retrievers: dict[str, tuple | dict] = {}
+        for retriever in self._retrievers:
+            diff = (getattr(self, retriever.p_name, None), getattr(other, retriever.p_name, None))
+            match diff:
+                case None, None: pass
+                case _, None:
+                    diff_retrievers[retriever.p_name] = ("...", None)
+                case None, _:
+                    diff_retrievers[retriever.p_name] = (None, "...")
+                case val1, val2:
+                    if isinstance(val1, BaseStruct):
+                        sub_diff = val1._diff(val2)
+                        if len(sub_diff) > 0:
+                            diff_retrievers[retriever.p_name] = sub_diff
+                    elif val1 != val2:
+                        diff_retrievers[retriever.p_name] = diff
+
+        return diff_retrievers
+
+    def _dbg_repr(self) -> str:
+        return self.__repr__(get = lambda obj, attr: getattr(obj, attr, None))
+
+    def __repr__(self, ident: int = 0, get = getattr) -> str:
+        builder = TabbedStringIO(ident)
+        builder.write(f"{self.__class__.__name__}(")
+
+        len_gt_zero = False
+        with builder.tabbed():
+            if self.struct_ver != Version((0,)):
+                builder.writeln(f"struct_ver = {self.struct_ver},")
+                len_gt_zero = True
+            for retriever in self._retrievers:
+                if not retriever.supported(self.struct_ver):
+                    continue
+                obj = get(self, retriever.p_name)
+                if isinstance(obj, BaseStruct):
+                    builder.writeln(f"{retriever.p_name} = {obj.__repr__(builder.ident, get)},")
+                elif isinstance(obj, list):
+                    builder.writeln(f"{retriever.p_name} = {_ls_repr(obj, builder.ident)},")
+                else:
+                    builder.writeln(f"{retriever.p_name} = {obj!r},")
+                len_gt_zero = True
+
+        if len_gt_zero:
+            builder.writeln()
+
+        builder.write(")")
+        return builder.getvalue()
+
+    def __eq__(self, other: object) -> bool:
+        if (
+            not isinstance(other, BaseStruct)
+            or type(self) is not type(other)
+            or self.struct_ver != other.struct_ver
+        ):
+            return False
+
+        if id(self) == id(other):
+            return True
+
+        for retriever in self._retrievers:
+            if not retriever.supported(self.struct_ver):
+                continue
+            if getattr(self, retriever.p_name) != getattr(other, retriever.p_name):
+                return False
+        return True
+
+    # todo: write val <-> data (names) to file
+    # todo: write hex (decompressed) to file
+    # todo: file/header/decompressed in both hex/val <-> data
+
+def _ls_repr(ls: list, ident: int = 0, get = getattr) -> str:
+    builder = TabbedStringIO(ident)
+    builder.write("[")
+
+    with builder.tabbed():
+        for item in ls:
+            if isinstance(item, BaseStruct):
+                builder.writeln(item.__repr__(builder.ident, get))
+            elif isinstance(item, list):
+                builder.writeln(_ls_repr(item, builder.ident, get))
+            else:
+                builder.writeln(repr(item))
+            builder.write(",")
+
+    if len(ls) > 0:
+        builder.writeln()
+
+    builder.write("]")
+    return builder.getvalue()
diff --git a/src/binary_file_parser/types/ByteStream.py b/src/binary_file_parser/types/byte_stream.py
similarity index 97%
rename from src/binary_file_parser/types/ByteStream.py
rename to src/binary_file_parser/types/byte_stream.py
index f7ea98c..db87a39 100644
--- a/src/binary_file_parser/types/ByteStream.py
+++ b/src/binary_file_parser/types/byte_stream.py
@@ -19,6 +19,7 @@ def from_file(cls, filepath: str) -> ByteStream:
         Create a ByteStream from file
 
         :param filepath: The path of the file to create the stream from
+
         :return: ByteStream object
         """
         with open(filepath, 'rb') as f:
@@ -31,6 +32,7 @@ def from_bytes(cls, bytes_: bytes) -> ByteStream:
         Create a ByteStream from bytes
 
         :param bytes_:
+
         :return: ByteStream object
         """
         return cls(bytes_)
@@ -40,13 +42,15 @@ def get(self, n: int) -> bytes:
         Get the specified number of bytes from the stream and advance the reading position forward
 
         :param n: The number of bytes to return from the stream
+
         :return: The requested bytes
+
         :raises EOFError: if the number of bytes requested is greater than the remaining number of bytes in the stream
         """
         if n <= 0:
             return b''
         result = self.content[self.progress:self.progress + n]
-        if not result:
+        if len(result) < n:
             remaining = len(self.remaining())
             raise EOFError(f"End of file reached. (Requested: {n} bytes, only {remaining} left.)")
         self.progress += n
@@ -57,13 +61,15 @@ def peek(self, n: int) -> bytes:
         Get the specified number of bytes from the stream without advancing the reading position forward
 
         :param n: The number of bytes to return from the stream
+
         :return: The requested bytes
+
         :raises EOFError: if the number of bytes requested is greater than the remaining number of bytes in the stream
         """
         if n <= 0:
             return b''
         result = self.content[self.progress:self.progress + n]
-        if not result:
+        if len(result) < n:
             remaining = len(self.remaining())
             raise EOFError(f"End of file reached. (Requested: {n} bytes, only {remaining} left.)")
         return result
diff --git a/src/binary_file_parser/types/debug_byte_stream.py b/src/binary_file_parser/types/debug_byte_stream.py
new file mode 100644
index 0000000..43fd92e
--- /dev/null
+++ b/src/binary_file_parser/types/debug_byte_stream.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+
+from binary_file_parser.logger import logger
+from binary_file_parser.types.byte_stream import ByteStream
+
+
+class DebugByteStream(ByteStream):
+    """A stream of bytes which can be used to get or peek n number of bytes at a time"""
+    __slots__ = ()
+    reader_ret: str | None = None
+    show_rets: set[str] = set()
+    show_all: bool = False
+
+    def get(self, n: int) -> bytes:
+        bytes_ = super().get(n)
+        if self.reader_ret in self.show_rets or self.show_all:
+            logger.debug(f"Retriever '{self.reader_ret}' consumed {n} bytes: {bytes_!r}")
+        return bytes_
+
+    def peek(self, n: int) -> bytes:
+        bytes_ = super().peek(n)
+        if self.reader_ret in self.show_rets or self.show_all:
+            logger.debug(f"Retriever '{self.reader_ret}' peeked {n} bytes: {bytes_!r}")
+        return bytes_
diff --git a/src/binary_file_parser/types/le/__init__.py b/src/binary_file_parser/types/le/__init__.py
new file mode 100644
index 0000000..f5d744a
--- /dev/null
+++ b/src/binary_file_parser/types/le/__init__.py
@@ -0,0 +1,16 @@
+from .array import (
+    Array8, Array16, Array32, Array64, FixedLenArray, StackedArray8s, StackedArray16s, StackedArray32s, StackedArray64s,
+    StackedAttrArray8, StackedAttrArray16, StackedAttrArray32, StackedAttrArray64,
+)
+from .bool import bool8, bool16, bool32, bool64
+from .bytes import Bytes, void
+
+from .float import float16, float32, float64
+from .int import int8, int16, int32, int64, uint8, uint16, uint32, uint64
+
+from .option import Option8, Option16, Option32, Option64
+
+from .string import (
+    c_str, str8, str16, str32, str64, nt_str8, nt_str16, nt_str32, nt_str64, FixedLenStr, FixedLenNTStr, StrArray8,
+    StrArray16, StrArray32, StrArray64
+)
diff --git a/src/binary_file_parser/types/le/array.py b/src/binary_file_parser/types/le/array.py
new file mode 100644
index 0000000..f80b1d5
--- /dev/null
+++ b/src/binary_file_parser/types/le/array.py
@@ -0,0 +1,380 @@
+from __future__ import annotations
+
+import itertools
+import struct
+from contextlib import suppress
+from typing import Type, TYPE_CHECKING
+
+from binary_file_parser.errors import VersionError
+from binary_file_parser.types.le.option import Option
+from binary_file_parser.types.parseable import Parseable
+from binary_file_parser.types.version import Version
+from binary_file_parser.types.byte_stream import ByteStream
+
+if TYPE_CHECKING:
+    from binary_file_parser.types.base_struct import BaseStruct
+    ParseableType = Type[Parseable] | Parseable
+
+class BaseArray(Parseable):
+    __slots__ = ("dtype", "struct_symbol", "length")
+
+    def __init__(self, size: int, dtype: ParseableType, struct_symbol: str):
+        super().__init__(size)
+        self.dtype = dtype
+        self.struct_symbol = struct_symbol
+        self.length = -1
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> list:
+        return [
+            self.dtype._from_stream(stream, struct_ver = struct_ver)
+            for _ in range(self.length)
+        ]
+
+    def _from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> list:
+        return self._from_stream(ByteStream.from_bytes(bytes_), struct_ver = struct_ver)
+
+    def _to_bytes(self, value: list) -> bytes:
+        return b"".join(
+            self.dtype._to_bytes(val)
+            for val in value
+        )
+
+class Array(BaseArray):
+    __slots__ = ()
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> list:
+        self.length = struct.unpack(self.struct_symbol, stream.get(self._size))[0]
+        return super()._from_stream(stream, struct_ver = struct_ver)
+
+    def _to_bytes(self, value: list) -> bytes:
+        self.length = len(value)
+        length_bytes = struct.pack(self.struct_symbol, self.length)
+        return length_bytes+super()._to_bytes(value)
+
+class Array8(Array):
+    """
+    Represents an array whose length is indicated by a uint8 followed by that many elements of the indicated type.
+    Usage:
+
+    >>> Array8[int32]
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType) -> Array8:
+        return cls(1, item, '<B')
+
+class Array16(Array):
+    """
+    Represents an array whose length is indicated by a ``uint16`` followed by that many elements of the indicated type.
+    Usage:
+
+    >>> Array16[int32]
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType) -> Array16:
+        return cls(2, item, '<H')
+
+class Array32(Array):
+    """
+    Represents an array whose length is indicated by a ``uint32`` followed by that many elements of the indicated type.
+    Usage:
+
+    >>> Array32[int32]
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType) -> Array32:
+        return cls(4, item, '<I')
+
+class Array64(Array):
+    """
+    Represents an array whose length is indicated by a ``uint64`` followed by that many elements of the indicated type.
+    Usage:
+
+    >>> Array64[int32]
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType) -> Array64:
+        return cls(8, item, '<Q')
+
+
+class FixedLenArray(BaseArray):
+    """
+    Represents an array whose length is known. This length is not read from/written to bytes
+    Usage:
+
+    >>> FixedLenArray[int32, 5]
+    """
+    __slots__ = ()
+
+    def __init__(self, size: int, dtype: ParseableType, struct_symbol: str, length: int):
+        super().__init__(size, dtype, struct_symbol)
+        self.length = length
+
+    def _to_bytes(self, value: list) -> bytes:
+        if len(value) != self.length:
+            raise TypeError(f"Expected FixedLenArray[{self.length}], found array with length: {len(value)}")
+        return super()._to_bytes(value)
+
+    def __class_getitem__(cls, item: tuple[ParseableType, int]) -> FixedLenArray:
+        return cls(item[1], item[0], '<I', item[1])
+
+
+class StackedArrays(BaseArray):
+    __slots__ = "num_arrays"
+
+    def __init__(self, size: int, dtype: ParseableType, struct_symbol: str, num_arrays: int = -1):
+        super().__init__(size, dtype, struct_symbol)
+        self.num_arrays = num_arrays
+
+    def _read_with_length(self, stream: ByteStream, *, struct_ver: Version = Version((0,)), length: int) -> list:
+        self.length = length
+        return super()._from_stream(stream, struct_ver = struct_ver)
+
+    def _write_with_length(self, val: list, length: int) -> bytes:
+        self.length = length
+        return super()._to_bytes(val)
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> list[list]:
+        num_arrays = self.num_arrays
+        if num_arrays == -1:
+            num_arrays = struct.unpack(self.struct_symbol, stream.get(self._size))[0]
+
+        lengths: list[int] = [
+            struct.unpack(self.struct_symbol, stream.get(self._size))[0]
+            for _ in range(num_arrays)
+        ]
+
+        return [
+            self._read_with_length(stream, struct_ver = struct_ver, length = length)
+            for length in lengths
+        ]
+
+    def _to_bytes(self, value: list[list]) -> bytes:
+        if self.num_arrays != -1 and len(value) != self.num_arrays:
+            raise TypeError(f"Expected {self.num_arrays} StackedArrays, found {len(value)}")
+
+        length_bytes = b""
+        num_arrays = self.num_arrays
+        if num_arrays == -1:
+            num_arrays = len(value)
+            length_bytes = struct.pack(self.struct_symbol, num_arrays)
+
+        len_bytes = (
+            struct.pack(self.struct_symbol, len(ls))
+            for ls in value
+        )
+
+        ls_bytes = (
+            self._write_with_length(ls, len(ls))
+            for ls in value
+        )
+
+        return length_bytes + b"".join(itertools.chain(
+            len_bytes,
+            ls_bytes
+        ))
+
+
+class StackedArray8s(StackedArrays):
+    """
+    Represents a 2D array where the number of rows is indicated by a ``uint8``, followed by that many ``uint8s``,
+    indicating the length of each row, followed by that many elements for each row.
+
+    >>> StackedArray8s[int32]
+    >>> StackedArray8s[int32, 4] # indicate the number of rows as fixed. This excludes it from being read from/written to bytes
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType | tuple[ParseableType, int]) -> StackedArrays:
+        if isinstance(item, tuple):
+            return cls(item[1], item[0], '<B', item[1])
+        return cls(1, item, '<B')
+
+
+class StackedArray16s(StackedArrays):
+    """
+    Represents a 2D array where the number of rows is indicated by a ``uint16``, followed by that many ``uint16s``,
+    indicating the length of each row, followed by that many elements for each row.
+
+    >>> StackedArray16s[int32]
+    >>> StackedArray16s[int32, 4] # indicate the number of rows as fixed. This excludes it from being read from/written to bytes
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType | tuple[ParseableType, int]) -> StackedArrays:
+        if isinstance(item, tuple):
+            return cls(item[1], item[0], '<H', item[1])
+        return cls(2, item, '<H')
+
+
+class StackedArray32s(StackedArrays):
+    """
+    Represents a 2D array where the number of rows is indicated by a ``uint32``, followed by that many ``uint32s``,
+    indicating the length of each row, followed by that many elements for each row.
+
+    >>> StackedArray32s[int32]
+    >>> StackedArray32s[int32, 4] # indicate the number of rows as fixed. This excludes it from being read from/written to bytes
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType | tuple[ParseableType, int]) -> StackedArrays:
+        if isinstance(item, tuple):
+            return cls(4, item[0], '<I', item[1])
+        return cls(4, item, '<I')
+
+
+class StackedArray64s(StackedArrays):
+    """
+    Represents a 2D array where the number of rows is indicated by a ``uint64``, followed by that many ``uint64s``,
+    indicating the length of each row, followed by that many elements for each row.
+
+    >>> StackedArray64s[int32]
+    >>> StackedArray64s[int32, 4] # indicate the number of rows as fixed. This excludes it from being read from/written to bytes
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType | tuple[ParseableType, int]) -> StackedArrays:
+        if isinstance(item, tuple):
+            return cls(item[1], item[0], '<Q', item[1])
+        return cls(8, item, '<Q')
+
+
+class StackedAttrArray(BaseArray):
+    def __init__(self, size: int, dtype: Type[BaseStruct | Option], struct_symbol: str, length: int = -1):
+        super().__init__(size, dtype, struct_symbol)
+        self.length = length
+        self.stype = dtype
+
+    def _read_opt(self, stream: ByteStream, struct_ver: Version, length: int) -> list:
+        exists = struct.unpack(
+            f"<{self.stype.struct_symbol[1:] * length}",
+            stream.get(self.stype._size * length)
+        )
+        return [
+            self.stype.dtype._from_stream(stream, struct_ver = struct_ver) if does_exist else None
+            for does_exist in exists
+        ]
+
+    def _write_opt(self, value: list, length: int) -> bytes:
+        exist_bytes = struct.pack(
+            f"<{self.stype.struct_symbol[1:] * length}",
+            *map(lambda x: x is not None,  value)
+        )
+        ls_bytes = (
+            self.stype.dtype._to_bytes(ls)
+            for ls in value
+            if ls is not None
+        )
+        return exist_bytes + b"".join(ls_bytes)
+
+    def _read_with_ver(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> BaseStruct:
+        instance = self.stype(struct_ver, initialise_defaults = False)
+        with suppress(VersionError):
+            instance._get_version(stream, struct_ver)
+        return instance
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> list:
+        length = self.length
+        if length == -1:
+            length = struct.unpack(self.struct_symbol, stream.get(self._size))[0]
+
+        if isinstance(self.stype, Option):
+            return self._read_opt(stream, struct_ver, length)
+
+        instances = [
+            self._read_with_ver(stream, struct_ver = struct_ver)
+            for _ in range(length)
+        ]
+
+        for retriever in self.stype._retrievers:
+            for instance in instances:
+                retriever.from_stream(instance, stream)
+
+        return instances
+
+    def _to_bytes(self, value: list) -> bytes:
+        if self.length != -1 and len(value) != self.length:
+            raise TypeError(f"Expected an array of length {self.length}, found array with length: {len(value)}")
+
+        length_bytes = b""
+        length = self.length
+        if length == -1:
+            length = len(value)
+            length_bytes = struct.pack(self.struct_symbol, length)
+
+        if isinstance(self.stype, Option):
+            return length_bytes+self._write_opt(value, length)
+
+        ls_bytes = (
+            retriever.to_bytes(instance)
+            for retriever in self.stype._retrievers
+            for instance in value
+        )
+
+        return length_bytes+b"".join(ls_bytes)
+
+class StackedAttrArray8(StackedAttrArray):
+    """
+    Represents an array of struct objects where the number of objects is indicated by a ``uint8``, followed by a list of
+    that length for each attribute of the base struct
+
+    >>> StackedAttrArray8[BaseStruct]
+    >>> StackedAttrArray8[BaseStruct, 4] # indicate the number of objects as fixed. This excludes it from being read from/written to bytes
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: Type[BaseStruct | Option] | tuple[Type[BaseStruct | Option], int]) -> StackedAttrArray:
+        if isinstance(item, tuple):
+            return cls(item[1], item[0], '<B', item[1])
+        return cls(1, item, '<B')
+
+
+class StackedAttrArray16(StackedAttrArray):
+    """
+    Represents an array of struct objects where the number of objects is indicated by a ``uint16``, followed by a list of
+    that length for each attribute of the base struct
+
+    >>> StackedAttrArray16[BaseStruct]
+    >>> StackedAttrArray16[BaseStruct, 4] # indicate the number of objects as fixed. This excludes it from being read from/written to bytes
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: Type[BaseStruct | Option] | tuple[Type[BaseStruct | Option], int]) -> StackedAttrArray:
+        if isinstance(item, tuple):
+            return cls(item[1], item[0], '<H', item[1])
+        return cls(2, item, '<H')
+
+
+class StackedAttrArray32(StackedAttrArray):
+    """
+    Represents an array of struct objects where the number of objects is indicated by a ``uint32``, followed by a list of
+    that length for each attribute of the base struct
+
+    >>> StackedAttrArray32[BaseStruct]
+    >>> StackedAttrArray32[BaseStruct, 4] # indicate the number of objects as fixed. This excludes it from being read from/written to bytes
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: Type[BaseStruct | Option] | tuple[Type[BaseStruct | Option], int]) -> StackedAttrArray:
+        if isinstance(item, tuple):
+            return cls(4, item[0], '<I', item[1])
+        return cls(4, item, '<I')
+
+
+class StackedAttrArray64(StackedAttrArray):
+    """
+    Represents an array of struct objects where the number of objects is indicated by a ``uint64``, followed by a list of
+    that length for each attribute of the base struct
+
+    >>> StackedAttrArray64[BaseStruct]
+    >>> StackedAttrArray64[BaseStruct, 4] # indicate the number of objects as fixed. This excludes it from being read from/written to bytes
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: Type[BaseStruct | Option] | tuple[Type[BaseStruct | Option], int]) -> StackedAttrArray:
+        if isinstance(item, tuple):
+            return cls(item[1], item[0], '<Q', item[1])
+        return cls(8, item, '<Q')
diff --git a/src/binary_file_parser/types/le/bool.py b/src/binary_file_parser/types/le/bool.py
new file mode 100644
index 0000000..a95d004
--- /dev/null
+++ b/src/binary_file_parser/types/le/bool.py
@@ -0,0 +1,28 @@
+import struct
+
+from binary_file_parser.types.byte_stream import ByteStream
+from binary_file_parser.types.parseable import Parseable
+from binary_file_parser.types.version import Version
+
+
+class Bool(Parseable):
+    __slots__ = "struct_symbol"
+
+    def __init__(self, size: int, struct_symbol: str):
+        super().__init__(size)
+        self.struct_symbol = struct_symbol
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> bool:
+        return self._from_bytes(stream.get(self._size), struct_ver = struct_ver)
+
+    def _from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> bool:
+        return not not struct.unpack(self.struct_symbol, bytes_)[0]
+
+    def _to_bytes(self, value: bool) -> bytes:
+        return struct.pack(self.struct_symbol, 1 if value else 0)
+
+
+bool8 = Bool(1, "<B")
+bool16 = Bool(2, "<H")
+bool32 = Bool(4, "<I")
+bool64 = Bool(8, "<Q")
diff --git a/src/binary_file_parser/types/le/bytes.py b/src/binary_file_parser/types/le/bytes.py
new file mode 100644
index 0000000..f0f6aef
--- /dev/null
+++ b/src/binary_file_parser/types/le/bytes.py
@@ -0,0 +1,31 @@
+from __future__ import annotations
+
+from binary_file_parser.types.byte_stream import ByteStream
+from binary_file_parser.types.parseable import Parseable
+from binary_file_parser.types.version import Version
+
+
+class Bytes(Parseable):
+    """
+    Represents raw bytes. Usage:
+
+    >>> Bytes[20]
+    """
+    __slots__ = ()
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> bytes:
+        return stream.get(self._size)
+
+    def _from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> bytes:
+        return bytes_
+
+    def _to_bytes(self, value: bytes) -> bytes:
+        if len(value) != self._size:
+            raise TypeError(f"Expected Bytes[{self._size}], found Bytes[{len(value)}]")
+        return value
+
+    def __class_getitem__(cls, size: int) -> Bytes:
+        return cls(size)
+
+void = Bytes[0]
+"""Read/Write no data"""
diff --git a/src/binary_file_parser/types/le/float.py b/src/binary_file_parser/types/le/float.py
new file mode 100644
index 0000000..d151b9b
--- /dev/null
+++ b/src/binary_file_parser/types/le/float.py
@@ -0,0 +1,26 @@
+import struct
+
+from binary_file_parser.types.parseable import Parseable
+from binary_file_parser.types.byte_stream import ByteStream
+from binary_file_parser.types.version import Version
+
+
+class Float(Parseable):
+    __slots__ = "struct_symbol"
+
+    def __init__(self, size: int, struct_symbol: str):
+        super().__init__(size)
+        self.struct_symbol = struct_symbol
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> float:
+        return self._from_bytes(stream.get(self._size))
+
+    def _from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> float:
+        return struct.unpack(self.struct_symbol, bytes_)[0]
+
+    def _to_bytes(self, value: float) -> bytes:
+        return struct.pack(self.struct_symbol, value)
+
+float16 = Float(2, "<e")
+float32 = Float(4, "<f")
+float64 = Float(8, "<d")
diff --git a/src/binary_file_parser/types/Int.py b/src/binary_file_parser/types/le/int.py
similarity index 50%
rename from src/binary_file_parser/types/Int.py
rename to src/binary_file_parser/types/le/int.py
index 0393e94..e46fdf2 100644
--- a/src/binary_file_parser/types/Int.py
+++ b/src/binary_file_parser/types/le/int.py
@@ -1,8 +1,8 @@
 import struct
 
-from binary_file_parser.types.ByteStream import ByteStream
-from binary_file_parser.types.Parseable import Parseable
-from binary_file_parser.utils import Version
+from binary_file_parser.types.parseable import Parseable
+from binary_file_parser.types.byte_stream import ByteStream
+from binary_file_parser.types.version import Version
 
 
 class Int(Parseable):
@@ -12,13 +12,13 @@ def __init__(self, size: int, struct_symbol: str):
         super().__init__(size)
         self.struct_symbol = struct_symbol
 
-    def from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> int:
-        return self.from_bytes(stream.get(self.size), struct_ver = struct_ver)
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> int:
+        return self._from_bytes(stream.get(self._size), struct_ver = struct_ver)
 
-    def from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> int:
+    def _from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> int:
         return struct.unpack(self.struct_symbol, bytes_)[0]
 
-    def to_bytes(self, value: int) -> bytes:
+    def _to_bytes(self, value: int) -> bytes:
         return struct.pack(self.struct_symbol, value)
 
 int8 = Int(1, "<b")
diff --git a/src/binary_file_parser/types/le/option.py b/src/binary_file_parser/types/le/option.py
new file mode 100644
index 0000000..55f4716
--- /dev/null
+++ b/src/binary_file_parser/types/le/option.py
@@ -0,0 +1,82 @@
+from __future__ import annotations
+
+import struct
+from typing import Generic, Type, TYPE_CHECKING, TypeVar
+
+from binary_file_parser.types.parseable import Parseable
+from binary_file_parser.types.version import Version
+from binary_file_parser.types.byte_stream import ByteStream
+
+if TYPE_CHECKING:
+    ParseableType = Type[Parseable] | Parseable
+
+T = TypeVar("T")
+class Option(Parseable, Generic[T]):
+    __slots__ = ("dtype", "struct_symbol")
+
+    def __init__(self, size: int, dtype: ParseableType, struct_symbol: str):
+        super().__init__(size)
+        self.dtype = dtype
+        self.struct_symbol = struct_symbol
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> T | None:
+        exists = struct.unpack(self.struct_symbol, stream.get(self._size))[0]
+        if not exists:
+            return None
+        return self.dtype._from_stream(stream, struct_ver = struct_ver)
+
+    def _from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))):
+        return self._from_stream(ByteStream.from_bytes(bytes_), struct_ver = struct_ver)
+
+    def _to_bytes(self, value: T | None) -> bytes:
+        if value is None:
+            return struct.pack(self.struct_symbol, False)
+        return struct.pack(self.struct_symbol, True) + self.dtype._to_bytes(value)
+
+class Option8(Option):
+    """
+    Represents an optional type, the inner type is read only if the following ``uint8`` is non-zero
+    Usage:
+
+    >>> Option8[int32]
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType) -> Option8:
+        return cls(1, item, '<B')
+
+class Option16(Option):
+    """
+    Represents an optional type, the inner type is read only if the following ``uint16`` is non-zero
+    Usage:
+
+    >>> Option16[int32]
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType) -> Option16:
+        return cls(2, item, '<H')
+
+class Option32(Option):
+    """
+    Represents an optional type, the inner type is read only if the following ``uint32`` is non-zero
+    Usage:
+
+    >>> Option32[int32]
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType) -> Option32:
+        return cls(4, item, '<I')
+
+class Option64(Option):
+    """
+    Represents an optional type, the inner type is read only if the following ``uint64`` is non-zero
+    Usage:
+
+    >>> Option64[int32]
+    """
+    __slots__ = ()
+
+    def __class_getitem__(cls, item: ParseableType) -> Option64:
+        return cls(8, item, '<Q')
\ No newline at end of file
diff --git a/src/binary_file_parser/types/le/string.py b/src/binary_file_parser/types/le/string.py
new file mode 100644
index 0000000..a99ddf9
--- /dev/null
+++ b/src/binary_file_parser/types/le/string.py
@@ -0,0 +1,202 @@
+from __future__ import annotations
+
+import struct
+from abc import ABC
+from functools import partial
+from itertools import takewhile
+from operator import ne
+from typing import Type, TYPE_CHECKING
+
+from binary_file_parser.types.byte_stream import ByteStream
+from binary_file_parser.types.parseable import Parseable
+from binary_file_parser.types.version import Version
+
+
+if TYPE_CHECKING:
+    ParseableType = Type[Parseable] | Parseable
+
+class BaseStr(Parseable, ABC):
+    __slots__ = ()
+
+    def _from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> str:
+        try:
+            return bytes_.decode("utf-8")
+        except UnicodeDecodeError:
+            return bytes_.decode("latin-1")
+
+    def _to_bytes(self, value: str) -> bytes:
+        try:
+            return value.encode("utf-8")
+        except UnicodeEncodeError:
+            return value.encode("latin-1")
+
+class CStr(BaseStr):
+    __slots__ = ()
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> str:
+        bytes_ = b""
+        while (byte := stream.get(1)) != b"\x00":
+            bytes_ += byte
+        return self._from_bytes(bytes_)
+
+    def _to_bytes(self, value: str) -> bytes:
+        if not value.endswith("\x00"):
+            value += "\x00"
+        return super()._to_bytes(value)
+
+
+class Str(BaseStr):
+    __slots__ = "struct_symbol"
+
+    def __init__(self, size: int, struct_symbol: str):
+        super().__init__(size)
+        self.struct_symbol = struct_symbol
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> str:
+        length: int = struct.unpack(self.struct_symbol, stream.get(self._size))[0]
+        return self._from_bytes(stream.get(length))
+
+    def _to_bytes(self, value: str) -> bytes:
+        bytes_ = super()._to_bytes(value)
+        length = struct.pack(self.struct_symbol, len(bytes_))
+        return length+bytes_
+
+
+class NullTermStr(Str):
+    __slots__ = ()
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> str:
+        return super()._from_stream(stream, struct_ver = struct_ver).removesuffix("\x00")
+
+    def _to_bytes(self, value: str) -> bytes:
+        if not value.endswith("\x00"):
+            value += "\x00"
+        return super()._to_bytes(value)
+
+
+class FixedLenStr(BaseStr):
+    __slots__ = "length"
+
+    def __init__(self, size: int, length: int):
+        super().__init__(size)
+        self.length = length
+
+    def _to_bytes(self, value: str) -> bytes:
+        if len(value) != self.length:
+            raise TypeError(f"Expected FixedLenStr[{self.length}], found string with length: {len(value)}")
+        return super()._to_bytes(value)
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> str:
+        return self._from_bytes(stream.get(self.length))
+
+    def __class_getitem__(cls, item: int) -> FixedLenStr:
+        return cls(4, item)
+
+class FixedLenNTStr(FixedLenStr):
+    __slots__ = ()
+
+    def _to_bytes(self, value: str) -> bytes:
+        value = f"{value:\x00<{self.length}}"
+        return super()._to_bytes(value)
+
+    def _from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> str:
+        value = super()._from_bytes(bytes_, struct_ver = struct_ver)
+        return ''.join(takewhile(partial(ne, "\x00"), value))
+
+
+class StrArray(BaseStr):
+    __slots__ = "struct_symbol", "num_strings"
+
+    def __init__(self, size: int, struct_symbol: str, num_strings: int = -1):
+        super().__init__(size)
+        self.struct_symbol = struct_symbol
+        self.num_strings = num_strings
+
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> list[str]:
+        num_strings = self.num_strings
+        if num_strings == -1:
+            num_strings = struct.unpack(self.struct_symbol, stream.get(self._size))[0]
+
+        lengths: list[int] = [struct.unpack(self.struct_symbol, stream.get(self._size))[0] for _ in range(num_strings)]
+        ls: list[str] = [""]*num_strings
+
+        for i, length in enumerate(lengths):
+            ls[i] = super()._from_bytes(stream.get(length), struct_ver = struct_ver)
+
+        return ls
+
+    def _to_bytes(self, value: list[str]) -> bytes:
+        if self.num_strings != -1 and len(value) != self.num_strings:
+            raise TypeError(f"Expected {self.num_strings} StackedStrings, found {len(value)}")
+
+        length_bytes = b""
+        num_strings = self.num_strings
+        if num_strings == -1:
+            num_strings = len(value)
+            length_bytes = struct.pack(self.struct_symbol, num_strings)
+
+        bytes_: list[bytes] = [b""]*(2*num_strings)
+        for i in range(num_strings):
+            bytes_[num_strings+i] = super()._to_bytes(value[i])
+            bytes_[i] = struct.pack(self.struct_symbol, len(bytes_[num_strings+i]))
+
+        return length_bytes+b"".join(bytes_)
+
+    def __getitem__(self, item: int) -> StrArray:
+        return self.__class__(self._size, self.struct_symbol, item)
+
+
+c_str = CStr(4)
+"""A C style null terminated string"""
+str8 = Str(1, "<B")
+"""A string whose length in bytes is indicated by a ``uint8`` at the start"""
+str16 = Str(2, "<H")
+"""A string whose length in bytes is indicated by a ``uint16`` at the start"""
+str32 = Str(4, "<I")
+"""A string whose length in bytes is indicated by a ``uint32`` at the start"""
+str64 = Str(8, "<Q")
+"""A string whose length in bytes is indicated by a ``uint64`` at the start"""
+nt_str8 = NullTermStr(1, "<B")
+"""A string whose length in bytes is indicated by a ``uint8`` at the start. Removes a null character from the end after
+decoding the string if one is present. Also appends one on write if one is not already present"""
+nt_str16 = NullTermStr(2, "<H")
+"""A string whose length in bytes is indicated by a ``uint16`` at the start. Removes a null character from the end after
+decoding the string if one is present. Also appends one on write if one is not already present"""
+nt_str32 = NullTermStr(4, "<I")
+"""A string whose length in bytes is indicated by a ``uint32`` at the start. Removes a null character from the end after
+decoding the string if one is present. Also appends one on write if one is not already present"""
+nt_str64 = NullTermStr(8, "<Q")
+"""A string whose length in bytes is indicated by a ``uint64`` at the start. Removes a null character from the end after
+decoding the string if one is present. Also appends one on write if one is not already present"""
+StrArray8 = StrArray(1, '<B', -1)
+"""
+Represents a list of ``str8``s where the first ``uint8`` indicates the number of ``str8``s followed by that many
+``uint8``s indicating the length of each string in bytes, followed by that many bytes for each string. Usage:
+
+>>> StrArray8
+>>> StrArray8[3] # indicate the number of strings as fixed. This excludes it from being read from/written to bytes
+"""
+StrArray16 = StrArray(2, '<H', -1)
+"""
+Represents a list of ``str16``s where the first ``uint16`` indicates the number of ``str16``s followed by that many
+``uint16``s indicating the length of each string in bytes, followed by that many bytes for each string. Usage:
+
+>>> StrArray16
+>>> StrArray16[3] # indicate the number of strings as fixed. This excludes it from being read from/written to bytes
+"""
+StrArray32 = StrArray(4, '<I', -1)
+"""
+Represents a list of ``str32``s where the first ``uint32`` indicates the number of ``str32``s followed by that many
+``uint32``s indicating the length of each string in bytes, followed by that many bytes for each string. Usage:
+
+>>> StrArray32
+>>> StrArray32[3] # indicate the number of strings as fixed. This excludes it from being read from/written to bytes
+"""
+StrArray64 = StrArray(8, '<Q', -1)
+"""
+Represents a list of ``str64``s where the first ``uint64`` indicates the number of ``str64``s followed by that many
+``uint64``s indicating the length of each string in bytes, followed by that many bytes for each string. Usage:
+
+>>> StrArray64
+>>> StrArray64[3] # indicate the number of strings as fixed. This excludes it from being read from/written to bytes
+"""
diff --git a/src/binary_file_parser/types/manager.py b/src/binary_file_parser/types/manager.py
new file mode 100644
index 0000000..3daf37f
--- /dev/null
+++ b/src/binary_file_parser/types/manager.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from binary_file_parser.retrievers import RetrieverRef
+    from binary_file_parser.types.base_struct import BaseStruct
+
+class Manager:
+    """
+    Superclass for creating grouped retriever references and functions on them. Use this to provide a more coherent API
+    for struct modification when the internal struct is messy
+    """
+    __slots__ = '_struct'
+
+    _refs: list[RetrieverRef] = []
+
+    @classmethod
+    def _add_ref(cls, ref: RetrieverRef):
+        cls._refs.append(ref)
+
+    def __init__(self, struct: BaseStruct):
+        self._struct = struct
+
+    # todo: pretty print and degug stuff. same as base struct. perhaps create a superclass.
diff --git a/src/binary_file_parser/types/parseable.py b/src/binary_file_parser/types/parseable.py
new file mode 100644
index 0000000..e99de9d
--- /dev/null
+++ b/src/binary_file_parser/types/parseable.py
@@ -0,0 +1,24 @@
+from abc import ABC, abstractmethod
+from typing import TypeVar
+
+from binary_file_parser.types.byte_stream import ByteStream
+from binary_file_parser.types.version import Version
+
+T = TypeVar("T")
+class Parseable(ABC):
+    __slots__ = "_size"
+
+    def __init__(self, size: int):
+        self._size = size
+
+    @abstractmethod
+    def _from_stream(self, stream: ByteStream, *, struct_ver: Version = Version((0,))) -> T:
+        ...
+
+    @abstractmethod
+    def _from_bytes(self, bytes_: bytes, *, struct_ver: Version = Version((0,))) -> T:
+        ...
+
+    @abstractmethod
+    def _to_bytes(self, value: T) -> bytes:
+        ...
diff --git a/src/binary_file_parser/types/version.py b/src/binary_file_parser/types/version.py
new file mode 100644
index 0000000..91996af
--- /dev/null
+++ b/src/binary_file_parser/types/version.py
@@ -0,0 +1,35 @@
+from __future__ import annotations
+
+from typing import Iterable
+
+
+class Version(tuple[int, ...]):
+    """
+    - Functions equivalently to ``tuple[int, ...]``, e.g.: ``Version((1, 2, 3))`` is exactly the same as ``(1, 2, 3)``
+    - Exists solely for custom ``str()`` and ``repr()`` so ``Version((1, 2, 3))`` is printed as ``v1.2.3``
+    """
+    def __repr__(self):
+        return f"Version({super().__repr__()})"
+
+    def __str__(self):
+        return "v"+'.'.join(map(str, self))
+
+    @classmethod
+    def from_ints(cls, *ints: int) -> Version:
+        return Version(ints)
+
+    @classmethod
+    def from_str(cls, version_str: str) -> Version:
+        return Version(map(int, version_str.split('.')))
+
+    def __init__(self, tup: Iterable[int]):
+        ...
+
+    def __add__(self, other: tuple[int, ...]):
+        if not isinstance(other, tuple):
+            raise TypeError("Cannot add non tuple to version")
+        if len(other) == 0:
+            return self
+        if not all(map(lambda x: isinstance(x, int), other)):
+            raise TypeError("tuple must be ints to version")
+        return Version(tuple(self) + other)
diff --git a/src/binary_file_parser/utils.py b/src/binary_file_parser/utils.py
index cf0cfcb..ae0d912 100644
--- a/src/binary_file_parser/utils.py
+++ b/src/binary_file_parser/utils.py
@@ -1,29 +1,20 @@
-from __future__ import annotations
+import contextlib
+from io import StringIO
 
-from typing import Iterable
 
+class TabbedStringIO(StringIO):
+    def __init__(self, ident: int = 0):
+        super().__init__()
+        self.ident = ident
 
-class Version(tuple[int, ...]):
-    """
-    - Functions equivalently to ``tuple[int, ...]``, e.g.: ``Version((1, 2, 3))`` is exactly the same as ``(1, 2, 3)``
-    - Exists solely for custom ``str()`` and ``repr()`` so ``Version((1, 2, 3))`` is printed as ``v1.2.3``
-    """
-    def __repr__(self):
-        return f"Version({super().__repr__()})"
+    @contextlib.contextmanager
+    def tabbed(self):
+        self.ident += 4
+        yield
+        self.ident -= 4
 
-    def __str__(self):
-        return "v"+'.'.join(map(str, self))
+    def _tab(self) -> str:
+        return ' '*self.ident
 
-    @classmethod
-    def from_ints(cls, *ints: int) -> Version:
-        return Version(ints)
-
-    @classmethod
-    def from_str(cls, version_str: str) -> Version:
-        return Version(map(int, version_str.split('.')))
-
-    def __init__(self, tup: Iterable[int]):
-        ...
-
-def indentify(repr_str: str, indent = 4) -> str:
-    return f"\n{' '*indent}".join(repr_str.splitlines())
+    def writeln(self, string: str = ""):
+        self.write("\n" + self._tab() + string)
diff --git a/testing/utils.py b/utils.py
similarity index 100%
rename from testing/utils.py
rename to utils.py


Test patch:
diff --git a/testing/sections/BackgroundImage.py b/testing/sections/BackgroundImage.py
deleted file mode 100644
index 2b036df..0000000
--- a/testing/sections/BackgroundImage.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import Bytes, FixedLenArray, int16, int32, str16, uint32
-
-
-class BitMapInfoHeader(BaseStruct):
-    @staticmethod
-    def set_colours_repeat(retriever: Retriever, instance: BitMapInfoHeader):
-        BitMapInfoHeader.colours.set_repeat(instance, instance.num_colours)
-
-    @staticmethod
-    def update_num_colours(retriever: Retriever, instance: BitMapInfoHeader):
-        instance.num_colours = len(instance.colours)
-
-    header_size: int = Retriever(int32, default = 0)
-    width: int = Retriever(uint32, default = 0)
-    height: int = Retriever(uint32, default = 0)
-    planes: int = Retriever(int16, default = 0)
-    num_bits: int = Retriever(int16, default = 0)
-    compression: int = Retriever(uint32, default = 0)
-    image_size: int = Retriever(uint32, default = 0)
-    x_pixels_per_meter: int = Retriever(uint32, default = 0)
-    y_pixels_per_meter: int = Retriever(uint32, default = 0)
-    num_colours: int = Retriever(uint32, default = 0, on_set = [set_colours_repeat], on_write = [update_num_colours])
-    num_important_colours: int = Retriever(uint32, default = 0)
-    colours: list[int] = Retriever(uint32, default = 0, repeat = 0)
-
-    def __init__(
-        self,
-        struct_ver: Version = Version((1, 47)),
-        parent: BaseStruct = None,
-        initialise_defaults = True,
-        **retriever_inits
-    ):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class BackgroundImage(BaseStruct):
-    @staticmethod
-    def set_img_repeat(retriever: Retriever, instance: BackgroundImage):
-        BackgroundImage.data.set_repeat(instance, instance.width * instance.height)
-
-    @staticmethod
-    def set_bmp_header_repeat(retriever: Retriever, instance: BackgroundImage):
-        BackgroundImage.info_header.set_repeat(instance, 1 if instance.width != 0 != instance.height else -1)
-
-    file_name: str = Retriever(str16, default = "")
-    version: int = Retriever(uint32, default = 3)
-    width: int = Retriever(uint32, default = 0, on_set = [set_bmp_header_repeat, set_img_repeat])
-    height: int = Retriever(int32, default = 0, on_set = [set_bmp_header_repeat, set_img_repeat])
-    orientation: int = Retriever(int16, default = 1)
-    info_header: BitMapInfoHeader = Retriever(BitMapInfoHeader, default_factory = lambda sv, p: BitMapInfoHeader(sv, p))
-    data: list[bytes] = Retriever(Bytes[1], default = b"\x00")
-
-    def __init__(
-        self,
-        struct_ver: Version = Version((1, 47)),
-        parent: BaseStruct = None,
-        initialise_defaults = True,
-        **retriever_inits
-    ):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/Cinematics.py b/testing/sections/Cinematics.py
deleted file mode 100644
index 6f92b59..0000000
--- a/testing/sections/Cinematics.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import str16
-
-
-class Cinematics(BaseStruct):
-    pregame: str = Retriever(str16, default = "")
-    victory: str = Retriever(str16, default = "")
-    loss: str = Retriever(str16, default = "")
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/DataHeader.py b/testing/sections/DataHeader.py
deleted file mode 100644
index a54e390..0000000
--- a/testing/sections/DataHeader.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import bool32, Bytes, FixedLenStr, float32, str16, uint32
-
-
-class PlayerData1(BaseStruct):
-    active: bool = Retriever(bool32, default = False)
-    human: bool = Retriever(bool32, default = False)
-    civilization_1_36: int = Retriever(uint32, default = 36, min_ver = Version((1, 36)), max_ver = Version((1, 40)))
-    architecture_set_1_40: int = Retriever(uint32, default = 36, min_ver = Version((1, 40)), max_ver = Version((1, 40)))
-    civilization_1_41: int = Retriever(uint32, default = 38, min_ver = Version((1, 41)), max_ver = Version((1, 42)))
-    architecture_set_1_41: int = Retriever(uint32, default = 38, min_ver = Version((1, 41)), max_ver = Version((1, 42)))
-    civilization_1_43: int = Retriever(uint32, default = 40, min_ver = Version((1, 43)), max_ver = Version((1, 45)))
-    architecture_set_1_43: int = Retriever(uint32, default = 40, min_ver = Version((1, 43)), max_ver = Version((1, 45)))
-    civilization_1_46: int = Retriever(uint32, default = 43, min_ver = Version((1, 46)))
-    architecture_set_1_46: int = Retriever(uint32, default = 43, min_ver = Version((1, 46)))
-    cty_mode: int = Retriever(uint32, default = 4)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class DataHeader(BaseStruct):
-    next_unit_id: int = Retriever(uint32, default = 0)
-    version: float = Retriever(float32, default = 1.4700000286102295)
-    tribe_names: list[str] = Retriever(FixedLenStr[256], default = "0"*256, repeat = 16)
-    player_name_str_ids: list[int] = Retriever(uint32, default = 4294967294, repeat = 16)
-    player_data1: list[PlayerData1] = Retriever(PlayerData1, default_factory = lambda sv, p: PlayerData1(sv, p), repeat = 16)
-    lock_civilizations: list[bool] = Retriever(bool32, default = False, repeat = 16)
-    unknown: bytes = Retriever(Bytes[9], default = b"\x00"+b"\x00"*8, max_ver = Version((1, 45)))
-    unknown_1_46: bytes = Retriever(Bytes[9], default = b"\x01"+b"\x00"*8, min_ver = Version((1, 46)))
-    file_name: str = Retriever(str16, default = "MadeWithAoE2SP.aoe2scenario")
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/Diplomacy.py b/testing/sections/Diplomacy.py
deleted file mode 100644
index 8acdf7c..0000000
--- a/testing/sections/Diplomacy.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import bool32, bool8, Bytes, FixedLenArray, int8, uint32
-
-
-class Diplomacy(BaseStruct):
-    player_stances: list[list[int]] = Retriever(FixedLenArray[uint32, 16], default_factory = lambda _, __: [3]*16, repeat = 16)
-    individual_victories: bytes = Retriever(Bytes[11520], default = b"\x00" * 11520)
-    """unused (?)"""
-    separator: int = Retriever(uint32, default = 4294967197)
-    allied_victories: list[bool] = Retriever(bool32, default = False, repeat = 16)
-    lock_teams_in_game: bool = Retriever(bool8, default = False)
-    lock_teams_in_lobby: bool = Retriever(bool8, default = False)
-    random_start_points: bool = Retriever(bool8, default = False)
-    max_num_teams: int = Retriever(int8, default = 4)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/FileData.py b/testing/sections/FileData.py
deleted file mode 100644
index f174465..0000000
--- a/testing/sections/FileData.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import Array32, bool32, Bytes, nt_str32, str16, str32
-
-
-class AiFile2(BaseStruct):
-    file_name: str = Retriever(nt_str32, default = "")
-    per_content: str = Retriever(nt_str32, default = "")
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-class FileData(BaseStruct):
-    @staticmethod
-    def set_ai_files_repeat(retriever: Retriever, instance: FileData):
-        if not instance.ai_files_present:
-            FileData.ai_files.set_repeat(instance, 0)
-
-    @staticmethod
-    def update_ai_files_present(retriever: Retriever, instance: FileData):
-        instance.ai_files_present = len(instance.ai_files) > 0
-
-    unknown2: bytes = Retriever(Bytes[4], default = b"\x00" * 4, max_ver = Version((1, 45)))
-    script_file_path: str = Retriever(str16, default = "")
-    script_file_content: str = Retriever(str32, default = "")
-    ai_files_present: bool = Retriever(bool32, default = False, on_set = [set_ai_files_repeat], on_write = [update_ai_files_present])
-    unknown4: bytes = Retriever(Bytes[4], default = b"\x00"*4)
-    ai_files: list[AiFile2] = Retriever(Array32[AiFile2], default_factory = lambda sv, p: AiFile2(sv, p), repeat = 0)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/FileHeader.py b/testing/sections/FileHeader.py
deleted file mode 100644
index 5c73838..0000000
--- a/testing/sections/FileHeader.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import Array32, bool32, FixedLenStr, int32, nt_str32, uint32
-
-
-class FileHeader(BaseStruct):
-    @staticmethod
-    def update_num_triggers(retriever: Retriever, instance: FileHeader):
-        instance.num_triggers = len(instance.parent.trigger_data.triggers)
-
-    @staticmethod
-    def set_timestamp_of_last_save_repeat(retriever: Retriever, instance: FileHeader):
-        if instance.savable < 2:
-            FileHeader.timestamp_of_last_save.set_repeat(instance, 0)
-
-    file_version: str = Retriever(FixedLenStr[4], default = "1.47")
-    header_len: int = Retriever(uint32, default = 0)
-    savable: int = Retriever(int32, default = 6, on_set = [set_timestamp_of_last_save_repeat])
-    timestamp_of_last_save: int = Retriever(uint32, default = 1610675127)
-    scenario_instructions: str = Retriever(nt_str32, default = "")
-    individual_victories_used: bool = Retriever(bool32, default = False, max_ver = Version((1, 37)))
-    num_players: int = Retriever(uint32, default = 2)
-    unknown1: int = Retriever(uint32, default = 1000)
-    """always (?) 1k"""
-    unknown2: int = Retriever(uint32, default = 1)
-    """always (?) 1"""
-    required_dats: list[int] = Retriever(Array32[uint32], default_factory = lambda _, __: [2, 3, 4, 5, 6, 7])
-    # todo: update this list with proper default versioning
-    # 2 - AoK
-    # 3 - AoC
-    # 4 - FE
-    # 5 - AK
-    # 6 - RoR
-    # 7 - TLK
-    # 8 - LotW
-    # 9 - DotD
-    # 10 - DI
-    creator: str = Retriever(nt_str32, default = "AoE2SP")
-    num_triggers: int = Retriever(uint32, default = 0, on_write = [update_num_triggers])
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/GlobalVictory.py b/testing/sections/GlobalVictory.py
deleted file mode 100644
index 88d0e41..0000000
--- a/testing/sections/GlobalVictory.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import uint32
-
-
-class GlobalVictory(BaseStruct):
-    separator: int = Retriever(uint32, default = 4294967197)
-    conquest: int = Retriever(uint32, default = 1)
-    """If conquest is checked in 'Custom' GlobalVictory menu"""
-    capture_num_monuments: int = Retriever(uint32, default = 0)
-    """Ruins in aoe1"""
-    collect_num_relics: int = Retriever(uint32, default = 0)
-    """Artifacts in aoe1. The value for number of relics that is set when 'relics' is checked in 'Custom' GlobalVictory menu"""
-    discovery: int = Retriever(uint32, default = 0)
-    """What does this do?"""
-    explore_map_percent: int = Retriever(uint32, default = 0)
-    """The percentage of the map that needs to be explored for winning in 'Custom' GlobalVictory menu"""
-    collect_gold: int = Retriever(uint32, default = 0)
-    meet_all_conditions: int = Retriever(uint32, default = 0)
-    """When set to 1, all custom conditions must be satisfied to win"""
-    mode: int = Retriever(uint32, default = 0)
-    min_score: int = Retriever(uint32, default = 900)
-    time_limit: int = Retriever(uint32, default = 9000)
-    """in 10ths of a year"""
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/MapData.py b/testing/sections/MapData.py
deleted file mode 100644
index bce8265..0000000
--- a/testing/sections/MapData.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import bool8, Bytes, int16, int32, str16, uint32, uint8
-
-
-class View(BaseStruct):
-    x: int = Retriever(int32, default = -1)
-    y: int = Retriever(int32, default = -1)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class Terrain(BaseStruct):
-    terrain_id: int = Retriever(uint8, default = 0)
-    elevation: int = Retriever(uint8, default = 0)
-    unused: bytes = Retriever(Bytes[3], default = b"\x00\xff\xff")
-    layer: int = Retriever(int16, default = -1)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class MapData(BaseStruct):
-    @staticmethod
-    def set_terrain_data_repeat(retriever: Retriever, instance: MapData):
-        MapData.tiles.set_repeat(instance, instance.width * instance.height)
-
-    @staticmethod
-    def update_script_file_path(retriever: Retriever, instance: MapData):
-        instance.parent.file_data.script_file_path = instance.script_name+".xs" if instance.script_name else ""
-
-    @staticmethod
-    def update_width_height(retriever: Retriever, instance: MapData):
-        instance.width = instance.height = int(len(instance.tiles) ** 0.5)
-
-    string_starter1: bytes = Retriever(Bytes[2], default = b"\x60\x0a")
-    water_definition: str = Retriever(str16, default = "")
-    string_starter2: bytes = Retriever(Bytes[2], default = b"\x60\x0a")
-    colour_mood: str = Retriever(str16, default = "Empty")
-    string_starter3: bytes = Retriever(Bytes[2], default = b"\x60\x0a", min_ver = Version((1, 40)))
-    script_name: str = Retriever(str16, default = "", on_write = [update_script_file_path], min_ver = Version((1, 40)))
-    lock_coop_alliances_1_41: bool = Retriever(bool8, default = False, min_ver = Version((1, 41)), max_ver = Version((1, 41)))
-    collide_and_correct: bool = Retriever(bool8, default = False)
-    villager_force_drop: bool = Retriever(bool8, default = False, min_ver = Version((1, 37)))
-    player_views: list[View] = Retriever(View, default_factory = lambda sv, p: View(sv, p), min_ver = Version((1, 40)), repeat = 16)
-    lock_coop_alliances_1_42: bool = Retriever(bool8, default = False, min_ver = Version((1, 42)))
-    ai_map_type: int = Retriever(uint32, default = 0, min_ver = Version((1, 42)), max_ver = Version((1, 46)))
-    population_caps: list[int] = Retriever(uint32, default = 200, repeat = 16, min_ver = Version((1, 44)))
-    secondary_game_mode = Retriever(Bytes[4], default = b"\x00"*4, min_ver = Version((1, 45)))
-    unknown3 = Retriever(Bytes[4], default = b"\x0d\xf0\xad\xde")
-    unknown4 = Retriever(Bytes[4], default = b"\x02"+b"\x00"*3)
-    no_waves_on_shore: bool = Retriever(bool8, default = False)
-    width: int = Retriever(uint32, default = 120, on_set = [set_terrain_data_repeat], on_write = [update_width_height])
-    height: int = Retriever(uint32, default = 120, on_set = [set_terrain_data_repeat])
-    tiles: list[Terrain] = Retriever(Terrain, default_factory = lambda sv, p: Terrain(sv, p), repeat = 14_400)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/Messages.py b/testing/sections/Messages.py
deleted file mode 100644
index 4687110..0000000
--- a/testing/sections/Messages.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import str16, uint32
-
-
-class Messages(BaseStruct):
-    instructions_str_id: int = Retriever(uint32, default = 4294967294)
-    hints_str_id: int = Retriever(uint32, default = 4294967294)
-    victory_str_id: int = Retriever(uint32, default = 4294967294)
-    loss_str_id: int = Retriever(uint32, default = 4294967294)
-    history_str_id: int = Retriever(uint32, default = 4294967294)
-    scouts_str_id: int = Retriever(uint32, default = 4294967294)
-    instructions: str = Retriever(str16, default = "")
-    hints: str = Retriever(str16, default = "")
-    victory: str = Retriever(str16, default = "This scenario was created using AoE2ScenarioParser! Hopefully you enjoyed!")
-    loss: str = Retriever(str16, default = "This scenario was created using AoE2ScenarioParser! Hopefully you enjoyed!")
-    history: str = Retriever(str16, default = "")
-    scouts: str = Retriever(str16, default = "")
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/Options.py b/testing/sections/Options.py
deleted file mode 100644
index d5b9caf..0000000
--- a/testing/sections/Options.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import bool32, Bytes, StackedArray32s, uint32, uint8
-from testing.sections.MapData import View
-
-
-class Options(BaseStruct):
-    @staticmethod
-    def update_num_triggers(retriever: Retriever, instance: Options):
-        instance.num_triggers = len(instance.parent.trigger_data.triggers)
-
-    disabled_tech_ids: list[list[int]] = Retriever(StackedArray32s[uint32, 16], default_factory = lambda _, __: [[] for _ in range(16)])
-    disabled_unit_ids: list[list[int]] = Retriever(StackedArray32s[uint32, 16], default_factory = lambda _, __: [[] for _ in range(16)])
-    disabled_building_ids: list[list[int]] = Retriever(StackedArray32s[uint32, 16], default_factory = lambda _, __: [[] for _ in range(16)])
-    combat_mode: bool = Retriever(bool32, default = False)
-    naval_mode: bool = Retriever(bool32, default = False)
-    all_techs: bool = Retriever(bool32, default = False)
-    starting_ages: list[int] = Retriever(uint32, default = 2, repeat = 16)
-    separator: bytes = Retriever(Bytes[4], default = b"\x9d\xff\xff\xff")
-    player1_view: View = Retriever(View, default_factory = lambda sv, p: View(sv, p))
-    ai_map_type_unused_1_45: int = Retriever(uint32, default = 2, max_ver = Version((1, 45)))
-    ai_map_type_unused_1_46: int = Retriever(uint32, default = 22, min_ver = Version((1, 46)), max_ver = Version((1, 46)))
-    ai_map_type: int = Retriever(uint32, default = 0, min_ver = Version((1, 47)))
-    unknown3: bytes = Retriever(Bytes[1], default = b"\x00")
-    base_priorities: list[int] = Retriever(uint8, default = 0, repeat = 8)
-    unknown2: bytes = Retriever(Bytes[7], default = b"\x00"*7)
-    num_triggers: int = Retriever(uint32, default = 0, on_write = [update_num_triggers])
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/PlayerData2.py b/testing/sections/PlayerData2.py
deleted file mode 100644
index d9297ba..0000000
--- a/testing/sections/PlayerData2.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import Bytes, int32, str16, str32, uint32, uint8
-
-
-class AiFile(BaseStruct):
-    unknown: bytes = Retriever(Bytes[8], default = b"\x00"*8)
-    per_content: list[str] = Retriever(str32, default = "")
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class Resources(BaseStruct):
-    # todo: corresponding index to be accessed/updated for individual structs in list
-    @staticmethod
-    def update_player_data4_gold(retriever: Retriever, instance: Resources):
-        for i in range(8):
-            instance.parent.parent.unit_data.player_data4[i].gold = instance.parent.resources[i].gold
-
-    @staticmethod
-    def update_player_data4_wood(retriever: Retriever, instance: Resources):
-        for i in range(8):
-            instance.parent.parent.unit_data.player_data4[i].wood = instance.parent.resources[i].wood
-
-    @staticmethod
-    def update_player_data4_food(retriever: Retriever, instance: Resources):
-        for i in range(8):
-            instance.parent.parent.unit_data.player_data4[i].food = instance.parent.resources[i].food
-
-    @staticmethod
-    def update_player_data4_stone(retriever: Retriever, instance: Resources):
-        for i in range(8):
-            instance.parent.parent.unit_data.player_data4[i].stone = instance.parent.resources[i].stone
-
-    @staticmethod
-    def update_player_data4_ore_x(retriever: Retriever, instance: Resources):
-        for i in range(8):
-            instance.parent.parent.unit_data.player_data4[i].ore_x = instance.parent.resources[i].ore_x
-
-    @staticmethod
-    def update_player_data4_trade_goods(retriever: Retriever, instance: Resources):
-        for i in range(8):
-            instance.parent.parent.unit_data.player_data4[i].trade_goods = instance.parent.resources[i].trade_goods
-
-    gold: int = Retriever(int32, default = 0, on_write = [update_player_data4_gold])
-    wood: int = Retriever(int32, default = 0, on_write = [update_player_data4_wood])
-    food: int = Retriever(int32, default = 0, on_write = [update_player_data4_food])
-    stone: int = Retriever(int32, default = 0, on_write = [update_player_data4_stone])
-    ore_x: int = Retriever(int32, default = 0, on_write = [update_player_data4_ore_x])
-    """unused"""
-    trade_goods: int = Retriever(int32, default = 0, on_write = [update_player_data4_trade_goods])
-    """unused"""
-    player_colour: int = Retriever(int32, default = 0)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class PlayerData2(BaseStruct):
-    strings: list[str] = Retriever(str16, default = "", repeat = 32)
-    ai_names: list[str] = Retriever(str16, default = "", repeat = 16)
-    ai_files: list[AiFile] = Retriever(AiFile, default_factory = lambda sv, p: AiFile(sv, p), repeat = 16)
-    ai_types: list[int] = Retriever(uint8, default = 1, repeat = 16)
-    separator: int = Retriever(uint32, default = 4294967197)
-    resources: list[Resources] = Retriever(Resources, default_factory = lambda sv, p: Resources(sv, p), repeat = 16)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/ScenarioSections.py b/testing/sections/ScenarioSections.py
deleted file mode 100644
index 25af8bd..0000000
--- a/testing/sections/ScenarioSections.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import zlib
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import Bytes, ByteStream
-from testing.sections.BackgroundImage import BackgroundImage
-from testing.sections.Cinematics import Cinematics
-from testing.sections.DataHeader import DataHeader
-from testing.sections.Diplomacy import Diplomacy
-from testing.sections.FileData import FileData
-from testing.sections.FileHeader import FileHeader
-from testing.sections.GlobalVictory import GlobalVictory
-from testing.sections.MapData import MapData
-from testing.sections.Messages import Messages
-from testing.sections.Options import Options
-from testing.sections.PlayerData2 import PlayerData2
-from testing.sections.TriggerData import TriggerData
-from testing.sections.UnitData import UnitData
-
-
-class ScenarioSections(BaseStruct):
-    file_header: FileHeader = Retriever(FileHeader, default_factory = lambda sv, p: FileHeader(sv, p))
-    data_header: DataHeader = Retriever(DataHeader, default_factory = lambda sv, p: DataHeader(sv, p), remaining_compressed = True)
-    messages: Messages = Retriever(Messages, default_factory = lambda sv, p: Messages(sv, p))
-    cinematics: Cinematics = Retriever(Cinematics, default_factory = lambda sv, p: Cinematics(sv, p))
-    background_image: BackgroundImage = Retriever(BackgroundImage, default_factory = lambda sv, p: BackgroundImage(sv, p))
-    player_data2: PlayerData2 = Retriever(PlayerData2, default_factory = lambda sv, p: PlayerData2(sv, p))
-    global_victory  : GlobalVictory = Retriever(GlobalVictory, default_factory = lambda sv, p: GlobalVictory(sv, p))
-    diplomacy: Diplomacy = Retriever(Diplomacy, default_factory = lambda sv, p: Diplomacy(sv, p))
-    options: Options = Retriever(Options, default_factory = lambda sv, p: Options(sv, p))
-    map_data: MapData = Retriever(MapData, default_factory = lambda sv, p: MapData(sv, p))
-    unit_data: UnitData = Retriever(UnitData, default_factory = lambda sv, p: UnitData(sv, p))
-    trigger_data: TriggerData = Retriever(TriggerData, default_factory = lambda sv, p: TriggerData(sv, p))
-    file_data: FileData = Retriever(FileData, default_factory = lambda sv, p: FileData(sv, p), min_ver = Version((1, 40)))
-    unknown1: bytes = Retriever(Bytes[8], default = b"\x00"*8, max_ver = Version((1, 37)))
-
-    @classmethod
-    def decompress(cls, bytes_: bytes) -> bytes:
-        return zlib.decompress(bytes_, -zlib.MAX_WBITS)
-
-    @classmethod
-    def compress(cls, bytes_: bytes) -> bytes:
-        deflate_obj = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed = deflate_obj.compress(bytes_) + deflate_obj.flush()
-        return compressed
-
-    @classmethod
-    def get_version(
-        cls,
-        stream: ByteStream,
-        struct_ver: Version = Version((0,)),
-        parent: BaseStruct = None,
-    ) -> Version:
-        ver_str = stream.peek(4).decode("ASCII")
-        return Version(map(int, ver_str.split(".")))
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/TriggerData.py b/testing/sections/TriggerData.py
deleted file mode 100644
index 75a979b..0000000
--- a/testing/sections/TriggerData.py
+++ /dev/null
@@ -1,266 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import (
-    Array32, bool32, bool8, Bytes, ByteStream, float64, int32, int8, nt_str32, str32, uint32,
-)
-
-attr_usage_ids = {
-    "_message": {3, 20, 26, 37, 44, 45, 48, 51, 55, 56, 59, 60, 65, 66},
-    "_sound_name": {3, 4, 20},
-}
-
-
-class Effect(BaseStruct):
-    @staticmethod
-    def set_sel_obj_ids_repeat(retriever: Retriever, instance: Effect):
-        repeat = 0 if instance._num_objects_selected == -1 else instance._num_objects_selected
-        Effect._selected_object_ids.set_repeat(instance, repeat)
-
-    @staticmethod
-    def remove_null_term(retriever: Retriever, instance: Effect):
-        setattr(instance, retriever.s_name, getattr(instance, retriever.s_name).removesuffix("\x00"))
-
-    @staticmethod
-    def append_null_term_if_used(retriever: Retriever, instance: Effect):
-        if instance.type in attr_usage_ids[retriever.p_name]:
-            val = getattr(instance, retriever.s_name)
-            if len(val) > 0 and val[-1] != "\x00":
-                setattr(instance, retriever.s_name, val+"\x00")
-
-    @staticmethod
-    def update_num_obj_sel(retreiver: Retriever, instance: Effect):
-        instance.num_objects_selected = len(instance._selected_object_ids)
-
-    type: int = Retriever(int32, default = -1)
-    _static_value_2_2_1_36: int = Retriever(int32, default = 46, max_ver = Version((2, 2, 1, 37)))
-    _static_value_2_4_1_40: int = Retriever(int32, default = 48, min_ver = Version((2, 4, 1, 40)), max_ver = Version((2, 4, 1, 41)))
-    _static_value_2_4_1_42: int = Retriever(int32, default = 49, min_ver = Version((2, 4, 1, 42)), max_ver = Version((2, 4, 1, 43)))
-    _static_value_2_4_1_44: int = Retriever(int32, default = 52, min_ver = Version((2, 4, 1, 44)))
-    _ai_script_goal: int = Retriever(int32, default = -1)
-    _quantity: int = Retriever(int32, default = -1)
-    _tribute_list: int = Retriever(int32, default = -1)
-    _diplomacy: int = Retriever(int32, default = -1)
-    _num_objects_selected: int = Retriever(int32, default = -1, on_set = [set_sel_obj_ids_repeat], on_write = [update_num_obj_sel])
-    _legacy_location_object_reference: int = Retriever(int32, default = -1)
-    _object_list_unit_id: int = Retriever(int32, default = -1)
-    _source_player: int = Retriever(int32, default = -1)
-    _target_player: int = Retriever(int32, default = -1)
-    _technology: int = Retriever(int32, default = -1)
-    _string_id: int = Retriever(int32, default = -1)
-    _unknown2: int = Retriever(int32, default = -1)
-    _display_time: int = Retriever(int32, default = -1)
-    _trigger_id: int = Retriever(int32, default = -1)
-    _location_x: int = Retriever(int32, default = -1)
-    _location_y: int = Retriever(int32, default = -1)
-    _area_x1: int = Retriever(int32, default = -1)
-    _area_y1: int = Retriever(int32, default = -1)
-    _area_x2: int = Retriever(int32, default = -1)
-    _area_y2: int = Retriever(int32, default = -1)
-    _object_group: int = Retriever(int32, default = -1)
-    _object_type: int = Retriever(int32, default = -1)
-    _instruction_panel_position: int = Retriever(int32, default = -1)
-    _attack_stance: int = Retriever(int32, default = -1)
-    _time_unit: int = Retriever(int32, default = -1)
-    _enabled: int = Retriever(int32, default = -1)
-    _food: int = Retriever(int32, default = -1)
-    _wood: int = Retriever(int32, default = -1)
-    _stone: int = Retriever(int32, default = -1)
-    _gold: int = Retriever(int32, default = -1)
-    _item_id: int = Retriever(int32, default = -1)
-    _flash_object: int = Retriever(int32, default = -1)
-    _force_research_technology: int = Retriever(int32, default = -1)
-    _visibility_state: int = Retriever(int32, default = -1)
-    _scroll: int = Retriever(int32, default = -1)
-    _operation: int = Retriever(int32, default = -1)
-    _object_list_unit_id2: int = Retriever(int32, default = -1)
-    _button_location: int = Retriever(int32, default = -1)
-    _ai_signal_value: int = Retriever(int32, default = -1)
-    _unknown3: int = Retriever(int32, default = -1)
-    _object_attributes: int = Retriever(int32, default = -1)
-    _variable: int = Retriever(int32, default = -1)
-    _timer: int = Retriever(int32, default = -1)
-    _facet: int = Retriever(int32, default = -1)
-    _location_object_reference: int = Retriever(int32, default = -1)
-    _play_sound: int = Retriever(int32, default = -1)
-    _player_colour: int = Retriever(int32, default = -1, min_ver = Version((2, 4, 1, 40)))
-    _unknown4: int = Retriever(int32, default = -1, min_ver = Version((2, 4, 1, 40)))
-    _colour_mood: int = Retriever(int32, default = -1, min_ver = Version((2, 4, 1, 42)))
-    _reset_timer: int = Retriever(int32, default = -1, min_ver = Version((2, 4, 1, 44)))
-    _object_state: int = Retriever(int32, default = -1, min_ver = Version((2, 4, 1, 44)))
-    _action_type: int = Retriever(int32, default = -1, min_ver = Version((2, 4, 1, 44)))
-    _message: str = Retriever(str32, default = "", on_read = [remove_null_term], on_write = [append_null_term_if_used])
-    _sound_name: str = Retriever(str32, default = "", on_read = [remove_null_term], on_write = [append_null_term_if_used])
-    _selected_object_ids: list[int] = Retriever(int32, default = -1, repeat = 0)
-
-    # def map(self) -> BaseStruct:
-    #     from testing.sections.effects import Effect as EffectCls
-    #     return EffectCls._make_effect(self)
-
-    def __init__(self, struct_ver: Version = Version((3, 5, 1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-class Condition(BaseStruct):
-    condition_type: int = Retriever(int32, default = 0)
-    static_value_2_4_1_36: int = Retriever(int32, default = 21, max_ver = Version((2, 2, 1, 37)))
-    static_value_2_4_1_40: int = Retriever(int32, default = 24, min_ver = Version((2, 4, 1, 40)), max_ver = Version((2, 4, 1, 41)))
-    static_value_2_4_1_42: int = Retriever(int32, default = 25, min_ver = Version((2, 4, 1, 42)), max_ver = Version((2, 5, 1, 45)))
-    static_value_3_0_1_46: int = Retriever(int32, default = 28, min_ver = Version((3, 0, 1, 46)))
-    quantity: int = Retriever(int32, default = -1)
-    attribute: int = Retriever(int32, default = -1)
-    unit_object: int = Retriever(int32, default = -1)
-    next_object: int = Retriever(int32, default = -1)
-    object_list: int = Retriever(int32, default = -1)
-    source_player: int = Retriever(int32, default = -1)
-    technology: int = Retriever(int32, default = -1)
-    timer: int = Retriever(int32, default = -1)
-    unknown1: int = Retriever(int32, default = -1)
-    area_x1: int = Retriever(int32, default = -1)
-    area_y1: int = Retriever(int32, default = -1)
-    area_x2: int = Retriever(int32, default = -1)
-    area_y2: int = Retriever(int32, default = -1)
-    object_group: int = Retriever(int32, default = -1)
-    object_type: int = Retriever(int32, default = -1)
-    ai_signal: int = Retriever(int32, default = -1)
-    inverted: int = Retriever(int32, default = -1)
-    unknown2: int = Retriever(int32, default = -1)
-    variable: int = Retriever(int32, default = -1)
-    comparison: int = Retriever(int32, default = -1)
-    target_player: int = Retriever(int32, default = -1)
-    unit_ai_action: int = Retriever(int32, default = -1, min_ver = Version((2, 4, 1, 40)))
-    unknown4: int = Retriever(int32, default = -1, min_ver = Version((2, 4, 1, 40)))
-    object_state: int = Retriever(int32, default = -1, min_ver = Version((2, 4, 1, 42)))
-    timer_id: int = Retriever(int32, default = -1, min_ver = Version((3, 0, 1, 46)))
-    victory_timer_type: int = Retriever(int32, default = -1, min_ver = Version((3, 0, 1, 46)))
-    include_changeable_weapon_objects: int = Retriever(int32, default = -1, min_ver = Version((3, 0, 1, 46)))
-    xs_function: str = Retriever(str32, default = "", min_ver = Version((2, 4, 1, 40)))
-
-    def __init__(self, struct_ver: Version = Version((3, 5, 1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class Trigger(BaseStruct):
-    @staticmethod
-    def set_effects_repeat(retriever: Retriever, instance: Trigger):
-        Trigger.effects.set_repeat(instance, instance.num_effects)
-
-    @staticmethod
-    def set_effect_display_orders_repeat(retriever: Retriever, instance: Trigger):
-        Trigger.effect_display_orders.set_repeat(instance, instance.num_effects)
-
-    @staticmethod
-    def set_conditions_repeat(retriever: Retriever, instance: Trigger):
-        Trigger.conditions.set_repeat(instance, instance.num_conditions)
-
-    @staticmethod
-    def set_condition_display_orders_repeat(retriever: Retriever, instance: Trigger):
-        Trigger.condition_display_orders.set_repeat(instance, instance.num_conditions)
-
-    @staticmethod
-    def update_num_effects(retriever: Retriever, instance: Trigger):
-        instance.num_effects = len(instance.effects)
-
-        if len(instance.effect_display_orders) != instance.num_effects:
-            highest_display_order = max(instance.effect_display_orders)
-            if highest_display_order != instance.num_effects-1:
-                raise ValueError("effect display order array out of sync")
-            instance.effect_display_orders.extend(range(highest_display_order+1, instance.num_effects))
-
-    @staticmethod
-    def update_num_conditions(retriever: Retriever, instance: Trigger):
-        instance.num_conditions = len(instance.conditions)
-
-        if len(instance.condition_display_orders) != instance.num_conditions:
-            highest_display_order = max(instance.condition_display_orders)
-            if highest_display_order != instance.num_conditions-1:
-                raise ValueError("condition display order array out of sync")
-            instance.condition_display_orders.extend(range(highest_display_order+1, instance.num_conditions))
-
-    enabled: bool = Retriever(bool32, default = True)
-    looping: bool = Retriever(bool8, default = False)
-    description_str_id_id_2_4_1_40: int = Retriever(int32, default = -1, max_ver = Version((2, 4, 1, 40)))
-    description_str_id_id_2_4_1_41: int = Retriever(int32, default = 0, min_ver = Version((2, 4, 1, 41)))
-    display_as_objective: bool = Retriever(bool8, default = False)
-    objective_description_order: int = Retriever(uint32, default = 0)
-    make_header: bool = Retriever(bool8, default = False)
-    short_description_str_id_2_4_1_40: int = Retriever(int32, default = -1, max_ver = Version((2, 4, 1, 40)))
-    short_description_str_id_2_4_1_41: int = Retriever(int32, default = 0, min_ver = Version((2, 4, 1, 41)))
-    display_on_screen: bool = Retriever(bool8, default = False)
-    unknown: bytes = Retriever(Bytes[5], default = b"\x00"*5)
-    mute_objectives: bool = Retriever(bool8, default = False)
-    description: str = Retriever(nt_str32, default = "")
-    name: str = Retriever(nt_str32, default = "Trigger 0")
-    short_description: str = Retriever(nt_str32, default = "")
-    num_effects: int = Retriever(uint32, default = 0, on_set = [set_effects_repeat, set_effect_display_orders_repeat], on_write = [update_num_effects])
-    """originally int32"""
-    effects: list[Effect] = Retriever(Effect, default_factory = lambda sv, p: Effect(sv, p), repeat = 0)
-    effect_display_orders: list[int] = Retriever(uint32, default = 0, repeat = 0)
-    """originally int32"""
-    num_conditions: int = Retriever(uint32, default = 0, on_set = [set_conditions_repeat, set_condition_display_orders_repeat], on_write = [update_num_conditions])
-    """originally int32"""
-    conditions: list[Condition] = Retriever(Condition, default_factory = lambda sv, p: Condition(sv, p), repeat = 0)
-    condition_display_orders: list[int] = Retriever(uint32, default = 0, repeat = 0)
-    """originally int32"""
-
-    def __init__(self, struct_ver: Version = Version((3, 5, 1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class Variable(BaseStruct):
-    id: int = Retriever(uint32, default = 0)
-    name: str = Retriever(nt_str32, default = "_Variable0")
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class VariableData(BaseStruct):
-    variables: list[Variable] = Retriever(Array32[Variable], default_factory = lambda sv, p: Variable(sv, p))
-    unused: bytes = Retriever(Bytes[9], default = b"\x00"*9, min_ver = Version((3, 0, 1, 46)))
-    unknown: bytes = Retriever(Bytes[8], default = b"\x00"*8, min_ver = Version((3, 5, 1, 47)))
-
-    def __init__(self, struct_ver: Version = Version((3, 5, 1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-
-class TriggerData(BaseStruct):
-    @staticmethod
-    def set_triggers_repeat(retriever: Retriever, instance: TriggerData):
-        TriggerData.triggers.set_repeat(instance, instance.num_triggers)
-
-    @staticmethod
-    def set_display_orders_repeat(retriever: Retriever, instance: TriggerData):
-        TriggerData.trigger_display_orders.set_repeat(instance, instance.num_triggers)
-
-    @staticmethod
-    def update_num_triggers(retriever: Retriever, instance: TriggerData):
-        instance.num_triggers = len(instance.triggers)
-
-        if len(instance.trigger_display_orders) != instance.num_triggers:
-            highest_display_order = max(instance.trigger_display_orders)
-            if highest_display_order != instance.num_triggers-1:
-                raise ValueError("trigger display order array out of sync")
-            instance.trigger_display_orders.extend(range(highest_display_order+1, instance.num_triggers))
-
-    trigger_version: float = Retriever(float64, default = 3.5)
-    trigger_instruction_start: int = Retriever(int8, default = 0)
-    num_triggers: int = Retriever(uint32, default = 0, on_set = [set_triggers_repeat, set_display_orders_repeat], on_write = [update_num_triggers])
-    """originally int32"""
-    triggers: list[Trigger] = Retriever(Trigger, default_factory = lambda sv, p: Trigger(sv, p), repeat = 0)
-    trigger_display_orders: list[int] = Retriever(uint32, default = 0, repeat = 0)
-    unknown: bytes = Retriever(Bytes[1028], default = b"\x00"*1028)
-    variable_data: VariableData = Retriever(VariableData, default_factory = lambda sv, p: VariableData(sv, p))
-
-    @classmethod
-    def get_version(
-        cls,
-        stream: ByteStream,
-        struct_ver: Version = Version((0,)),
-        parent: BaseStruct = None,
-    ) -> Version:
-        ver_str = str(float64.from_bytes(stream.peek(8)))
-        return Version(map(int, ver_str.split("."))) + struct_ver
-
-    def __init__(self, struct_ver: Version = Version((3, 5, 1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/UnitData.py b/testing/sections/UnitData.py
deleted file mode 100644
index 811127f..0000000
--- a/testing/sections/UnitData.py
+++ /dev/null
@@ -1,122 +0,0 @@
-from __future__ import annotations
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.types import (
-    Array16, Array32, bool8, Bytes, FixedLenArray, float32, int16, int32, nt_str16, uint16, uint32, uint8,
-)
-
-
-class PlayerData4(BaseStruct):
-    food: float = Retriever(float32, default = 0.0)
-    """duplicate"""
-    wood: float = Retriever(float32, default = 0.0)
-    """duplicate"""
-    gold: float = Retriever(float32, default = 0.0)
-    """duplicate"""
-    stone: float = Retriever(float32, default = 0.0)
-    """duplicate"""
-    ore_x: float = Retriever(float32, default = 0.0)
-    """duplicate"""
-    trade_goods: float = Retriever(float32, default = 0.0)
-    population_limit: float = Retriever(float32, default = 200.0)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class ViewF(BaseStruct):
-    x: float = Retriever(float32, default = 60.0)
-    y: float = Retriever(float32, default = 60.0)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-class ViewI(BaseStruct):
-    x: int = Retriever(int16, default = 60)
-    y: int = Retriever(int16, default = 60)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class PlayerData3(BaseStruct):
-    @staticmethod
-    def set_unknown2_repeat(retriever: Retriever, instance: PlayerData3):
-        PlayerData3.unknown2.set_repeat(instance, 7 if instance.victory_version == 2.0 else -1)
-
-    @staticmethod
-    def set_num_wwc2_repeat(retriever: Retriever, instance: PlayerData3):
-        if instance.victory_version != 2.0:
-            PlayerData3.num_ww_campaign2.set_repeat(instance, -1)
-
-    @staticmethod
-    def set_gte_repeat(retriever: Retriever, instance: PlayerData3):
-        PlayerData3.grand_theft_empires.set_repeat(instance, instance.num_grand_theft_empires)
-
-    @staticmethod
-    def set_wwc2_repeat(retriever: Retriever, instance: PlayerData3):
-        PlayerData3.ww_campaign2.set_repeat(instance, instance.num_ww_campaign2)
-
-    @staticmethod
-    def update_num_gte(retriever: Retriever, instance: PlayerData3):
-        instance.num_grand_theft_empires = len(instance.grand_theft_empires)
-
-    @staticmethod
-    def update_num_wwc2(retriever: Retriever, instance: PlayerData3):
-        instance.num_ww_campaign2 = len(instance.ww_campaign2)
-
-    constant_name: str = Retriever(nt_str16, default = "Scenario Editor Phantom")
-    editor_view: ViewF = Retriever(ViewF, default_factory = lambda sv, p: ViewF(sv, p))
-    initial_view: ViewI = Retriever(ViewI, default_factory = lambda sv, p: ViewI(sv, p))
-    aok_allied_victory: bool = Retriever(bool8, default = False)
-    diplomacy_stances_interaction: list[int] = Retriever(Array16[uint8], default_factory = lambda _, __: [3, 0, 3, 3, 3, 3, 3, 3, 3])
-    diplomacy_stances_ai_system: list[int] = Retriever(FixedLenArray[uint32, 9], default_factory = lambda _, __: [0, 1, 4, 4, 4, 4, 4, 4, 4])
-    colour: int = Retriever(uint32, default = 0)
-    victory_version: float = Retriever(float32, default = 2.0, on_set = [set_unknown2_repeat, set_num_wwc2_repeat])
-    num_grand_theft_empires: int = Retriever(uint16, default = 0, on_set = [set_gte_repeat], on_write = [update_num_gte])
-    unknown2: list[int] = Retriever(uint8, default = 0, repeat = 7)
-    grand_theft_empires: list[bytes] = Retriever(Bytes[44], default = b"\x00"*44)
-    """unknown structure"""
-    num_ww_campaign2: int = Retriever(uint8, default = 0, on_set = [set_wwc2_repeat], on_write = [update_num_wwc2])
-    unknown3: int = Retriever(uint8, default = 0, repeat = 7)
-    ww_campaign2: list[bytes] = Retriever(Bytes[32], default = b"", repeat = 0)
-    """unknown structure"""
-    unknown4: int = Retriever(int32, default = -1)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class Unit(BaseStruct):
-    x: float = Retriever(float32, default = 0.5)
-    y: float = Retriever(float32, default = 0.5)
-    z: float = Retriever(float32, default = 0)
-    reference_id: int = Retriever(int32, default = 0)
-    const: int = Retriever(uint16, default = 4)
-    status: int = Retriever(uint8, default = 2)
-    rotation: float = Retriever(float32, default = 0)
-    initial_animation_frame: int = Retriever(uint16, default = 0)
-    garrisoned_in_reference_id: int = Retriever(int32, default = -1)
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
-
-
-class UnitData(BaseStruct):
-    @staticmethod
-    def set_units_repeat(retriever: Retriever, instance: UnitData):
-        UnitData.units.set_repeat(instance, instance.num_unit_lists)
-
-    @staticmethod
-    def update_num_unit_lists(retriever: Retriever, instance: UnitData):
-        instance.num_unit_lists = len(instance.units)
-        instance.num_players = len(instance.units)
-
-    num_unit_lists: int = Retriever(uint32, default = 9, on_set = [set_units_repeat], on_write = [update_num_unit_lists])
-    player_data4: list[PlayerData4] = Retriever(PlayerData4, default_factory = lambda sv, p: PlayerData4(sv, p), repeat = 8)
-    num_players: int = Retriever(uint32, default = 9)
-    player_data3: list[PlayerData3] = Retriever(PlayerData3, default_factory = lambda sv, p: PlayerData3(sv, p), repeat = 8)
-    units: list[list[Unit]] = Retriever(Array32[Unit], default_factory = lambda _, __: [])
-
-    def __init__(self, struct_ver: Version = Version((1, 47)), parent: BaseStruct = None, initialise_defaults = True, **retriever_inits):
-        super().__init__(struct_ver, parent, initialise_defaults = initialise_defaults, **retriever_inits)
diff --git a/testing/sections/effects/Effect.py b/testing/sections/effects/Effect.py
deleted file mode 100644
index ccaa01c..0000000
--- a/testing/sections/effects/Effect.py
+++ /dev/null
@@ -1,66 +0,0 @@
-from __future__ import annotations
-
-from io import StringIO
-from typing import Type
-
-from binary_file_parser import BaseStruct, Retriever, Version
-from binary_file_parser.utils import indentify
-from testing.sections.TriggerData import Effect as EffectStruct
-
-class Effect(EffectStruct):
-    def __init__(
-        self,
-        struct_version: Version = Version((3, 5, 1, 47)),
-        parent: BaseStruct = None,
-        local_vars = None,
-        **retriever_inits,
-    ):
-        if len(retriever_inits) > 1:
-            super().__init__(struct_version, parent, **retriever_inits)
-            return
-
-        for ref in self._refs:
-            name = (
-                ref.retriever.p_name
-                if isinstance(ref.retriever, Retriever)
-                else ref.retriever.get_p_name(struct_version)
-            )
-            retriever_inits[name] = local_vars[ref.name]
-        super().__init__(struct_version, parent, **retriever_inits)
-
-    @staticmethod
-    def _make_effect(struct: EffectStruct) -> Effect:
-        from testing.sections.effects.cd import ChangeDiplomacy
-        from testing.sections.effects.rt import ResearchTechnology
-        effect_cls: Type[Effect] = {
-            1: ChangeDiplomacy,
-            2: ResearchTechnology,
-        }[struct.type]
-
-        return effect_cls(
-            **{ref.name: None for ref in effect_cls._refs},
-            struct_version = struct.struct_ver,
-            parent = struct.parent,
-            **struct.retriever_name_value_map,
-        )
-
-    def __repr__(self):
-        repr_builder = StringIO()
-        repr_builder.write(f"{self.__class__.__name__}(")
-        for retriever in self._refs:
-            obj = getattr(self, retriever.name)
-            if isinstance(obj, list):
-                sub_obj_repr_str = (
-                    "[\n    "
-                    + ",\n    ".join(map(lambda x: indentify(repr(x)), obj))
-                    + ",\n]"
-                )
-            else:
-                sub_obj_repr_str = f"{obj!r}"
-
-            repr_builder.write(f"\n    {retriever.name} = {indentify(sub_obj_repr_str)},")
-        repr_builder.write("\n)")
-        return repr_builder.getvalue()
-
-    def __init_subclass__(cls, **kwargs):
-        cls._refs, Effect._refs = cls._refs.copy(), []
diff --git a/testing/sections/effects/__init__.py b/testing/sections/effects/__init__.py
deleted file mode 100644
index 6d25847..0000000
--- a/testing/sections/effects/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from .Effect import Effect
-
-from .cd import ChangeDiplomacy
-from .rt import ResearchTechnology
-
diff --git a/testing/sections/effects/cd.py b/testing/sections/effects/cd.py
deleted file mode 100644
index 4e51f93..0000000
--- a/testing/sections/effects/cd.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from binary_file_parser.retrievers import RetrieverRef
-from testing.sections.effects.Effect import Effect
-from testing.sections.effects.tmp import EffectType
-from testing.sections.TriggerData import Effect as EffectStruct
-
-
-class ChangeDiplomacy(Effect):
-    diplomacy_stance = RetrieverRef(EffectStruct._diplomacy)
-    source_player = RetrieverRef(EffectStruct._source_player)
-    target_player = RetrieverRef(EffectStruct._target_player)
-
-    def __init__(
-        self,
-        source_player,
-        diplomacy_stance,
-        target_player,
-        **kwargs,
-    ):
-        """
-        Change the source_player's diplomacy_stance towards the target_player
-        Args:
-            source_player: The player to change the diplomacy stance for
-            diplomacy_stance: The new diplomacy stance
-            target_player: The player to change the diplomacy stance towards
-        """
-        kwargs["type"] = EffectType.CHANGE_DIPLOMACY
-        super().__init__(local_vars = locals(), **kwargs)
diff --git a/testing/sections/effects/rt.py b/testing/sections/effects/rt.py
deleted file mode 100644
index 191dbb4..0000000
--- a/testing/sections/effects/rt.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from binary_file_parser.retrievers import RetrieverRef
-from testing.sections.effects.Effect import Effect
-from testing.sections.effects.tmp import EffectType
-from testing.sections.TriggerData import Effect as EffectStruct
-
-
-class ResearchTechnology(Effect):
-    source_player = RetrieverRef(EffectStruct._source_player)
-    technology = RetrieverRef(EffectStruct._technology)
-    force = RetrieverRef(EffectStruct._force_research_technology)
-
-    def __init__(
-        self,
-        source_player,
-        technology,
-        force: bool,
-        **kwargs,
-    ):
-        """
-        Research a technology for the source_player
-
-        Args:
-            source_player: The player to research the technology for
-            technology: The technology to research
-            force: If the technology should be researched even if it's not available to the civilization of the
-                source_player
-        """
-        kwargs["type"] = EffectType.RESEARCH_TECHNOLOGY
-        super().__init__(local_vars=locals(), **kwargs)
\ No newline at end of file
diff --git a/testing/sections/effects/tmp.py b/testing/sections/effects/tmp.py
deleted file mode 100644
index 24133a5..0000000
--- a/testing/sections/effects/tmp.py
+++ /dev/null
@@ -1,677 +0,0 @@
-from enum import IntEnum
-
-
-class EffectType(IntEnum):
-    """
-    This enum class provides the integer values used to reference the effects in the game. Used in every effect to
-    indicate which type of effect it is
-
-    **Examples**
-
-    >>> EffectType.RESEARCH_TECHNOLOGY
-    >>> 2
-    """
-    NONE = 0
-    """Attributes for the **none** effect are: \n
-    ... none... Just like Conditions... People these days... """
-    CHANGE_DIPLOMACY = 1
-    """Attributes for the **change_diplomacy** effect are: \n
-    - diplomacy
-    - source_player
-    - target_player"""
-    RESEARCH_TECHNOLOGY = 2
-    """Attributes for the **research_technology** effect are: \n
-    - source_player
-    - technology
-    - force_research_technology"""
-    SEND_CHAT = 3
-    """Attributes for the **send_chat** effect are: \n
-    - source_player
-    - string_id
-    - message
-    - sound_name"""
-    PLAY_SOUND = 4
-    """Attributes for the **play_sound** effect are: \n
-    - source_player
-    - location_x
-    - location_y
-    - location_object_reference
-    - sound_name"""
-    TRIBUTE = 5
-    """Attributes for the **tribute** effect are: \n
-    - quantity
-    - tribute_list
-    - source_player
-    - target_player"""
-    UNLOCK_GATE = 6
-    """Attributes for the **unlock_gate** effect are: \n
-    - selected_object_ids"""
-    LOCK_GATE = 7
-    """Attributes for the **lock_gate** effect are: \n
-    - selected_object_ids"""
-    ACTIVATE_TRIGGER = 8
-    """Attributes for the **activate_trigger** effect are: \n
-    - trigger_id"""
-    DEACTIVATE_TRIGGER = 9
-    """Attributes for the **deactivate_trigger** effect are: \n
-    - trigger_id"""
-    AI_SCRIPT_GOAL = 10
-    """Attributes for the **deactivate_trigger** effect are: \n
-    - ai_script_goal"""
-    CREATE_OBJECT = 11
-    """Attributes for the **create_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - location_x
-    - location_y
-    - facet"""
-    TASK_OBJECT = 12
-    """Attributes for the **task_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - location_x
-    - location_y
-    - location_object_reference
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - action_type
-    - selected_object_ids"""
-    DECLARE_VICTORY = 13
-    """Attributes for the **declare_victory** effect are: \n
-    - source_player
-    - enabled"""
-    KILL_OBJECT = 14
-    """Attributes for the **kill_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    REMOVE_OBJECT = 15
-    """Attributes for the **remove_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - object_state
-    - selected_object_ids"""
-    CHANGE_VIEW = 16
-    """Attributes for the **change_view** effect are: \n
-    - quantity
-    - source_player
-    - location_x
-    - location_y
-    - scroll"""
-    UNLOAD = 17
-    """Attributes for the **unload** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - location_x
-    - location_y
-    - location_object_reference
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    CHANGE_OWNERSHIP = 18
-    """Attributes for the **change_ownership** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - target_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - flash_object
-    - selected_object_ids"""
-    PATROL = 19
-    """Attributes for the **patrol** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - location_x
-    - location_y
-    - location_object_reference
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    DISPLAY_INSTRUCTIONS = 20
-    """Attributes for the **display_instructions** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - string_id
-    - display_time
-    - instruction_panel_position
-    - play_sound
-    - message
-    - sound_name"""
-    CLEAR_INSTRUCTIONS = 21
-    """Attributes for the **clear_instructions** effect are: \n
-    - instruction_panel_position"""
-    FREEZE_OBJECT = 22
-    """Attributes for the **freeze_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    USE_ADVANCED_BUTTONS = 23
-    """Attributes for the **use_advanced_buttons** effect are: \n
-    None. \n
-    Please don't use this effect. Please."""
-    DAMAGE_OBJECT = 24
-    """Attributes for the **damage_object** effect are: \n
-    - quantity
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    PLACE_FOUNDATION = 25
-    """Attributes for the **place_foundation** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - location_x
-    - location_y"""
-    CHANGE_OBJECT_NAME = 26
-    """Attributes for the **change_object_name** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - string_id
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - message
-    - selected_object_ids"""
-    CHANGE_OBJECT_HP = 27
-    """Attributes for the **change_object_hp** effect are: \n
-    - quantity
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - operation
-    - selected_object_ids"""
-    CHANGE_OBJECT_ATTACK = 28
-    """Attributes for the **change_object_attack** effect are: \n
-    - armour_attack_quantity
-    - armour_attack_class
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - operation
-    - selected_object_ids"""
-    STOP_OBJECT = 29
-    """Attributes for the **stop_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    ATTACK_MOVE = 30
-    """Attributes for the **attack_move** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - location_x
-    - location_y
-    - location_object_reference
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    CHANGE_OBJECT_ARMOR = 31
-    """Attributes for the **change_object_armor** effect are: \n
-    - armour_attack_quantity
-    - armour_attack_class
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - operation
-    - selected_object_ids"""
-    CHANGE_OBJECT_RANGE = 32
-    """Attributes for the **change_object_range** effect are: \n
-    - quantity
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - operation
-    - selected_object_ids"""
-    CHANGE_OBJECT_SPEED = 33
-    """Attributes for the **change_object_speed** effect are: \n
-    - quantity
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    HEAL_OBJECT = 34
-    """Attributes for the **heal_object** effect are: \n
-    - quantity
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    TELEPORT_OBJECT = 35
-    """Attributes for the **teleport_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - location_x
-    - location_y
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - selected_object_ids"""
-    CHANGE_OBJECT_STANCE = 36
-    """Attributes for the **change_object_stance** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - attack_stance
-    - selected_object_ids"""
-    DISPLAY_TIMER = 37
-    """Attributes for the **display_timer** effect are: \n
-    - string_id
-    - display_time
-    - time_unit
-    - timer
-    - reset_timer
-    - message"""
-    ENABLE_DISABLE_OBJECT = 38
-    """Attributes for the **enable_disable_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - enabled"""
-    ENABLE_DISABLE_TECHNOLOGY = 39
-    """Attributes for the **enable_disable_technology** effect are: \n
-    - source_player
-    - technology
-    - enabled"""
-    CHANGE_OBJECT_COST = 40
-    """Attributes for the **change_object_cost** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - food
-    - wood
-    - stone
-    - gold"""
-    SET_PLAYER_VISIBILITY = 41
-    """Attributes for the **set_player_visibility** effect are: \n
-    - source_player
-    - target_player
-    - visibility_state"""
-    CHANGE_OBJECT_ICON = 42
-    """Attributes for the **change_object_icon** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - object_list_unit_id_2
-    - selected_object_ids"""
-    REPLACE_OBJECT = 43
-    """Attributes for the **replace_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - target_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_group
-    - object_type
-    - object_list_unit_id_2
-    - selected_object_ids"""
-    CHANGE_OBJECT_DESCRIPTION = 44
-    """Attributes for the **change_object_description** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - string_id
-    - message"""
-    CHANGE_PLAYER_NAME = 45
-    """Attributes for the **change_player_name** effect are: \n
-    - source_player
-    - string_id
-    - message"""
-    CHANGE_TRAIN_LOCATION = 46
-    """Attributes for the **change_train_location** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - object_list_unit_id_2
-    - button_location"""
-    CHANGE_RESEARCH_LOCATION = 47
-    """Attributes for the **change_research_location** effect are: \n
-    - source_player
-    - technology
-    - object_list_unit_id_2
-    - button_location"""
-    CHANGE_CIVILIZATION_NAME = 48
-    """Attributes for the **change_civilization_name** effect are: \n
-    - source_player
-    - string_id
-    - message"""
-    CREATE_GARRISONED_OBJECT = 49
-    """Attributes for the **create_garrisoned_object** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - object_list_unit_id_2
-    - selected_object_ids"""
-    ACKNOWLEDGE_AI_SIGNAL = 50
-    """Attributes for the **acknowledge_ai_signal** effect are: \n
-    - ai_signal_value"""
-    MODIFY_ATTRIBUTE = 51
-    """Attributes for the **modify_attribute** effect are: \n
-    - quantity
-    - object_list_unit_id
-    - source_player
-    - operation
-    - object_attributes
-    - message"""
-    MODIFY_RESOURCE = 52
-    """Attributes for the **modify_resource** effect are: \n
-    - quantity
-    - tribute_list
-    - source_player
-    - operation"""
-    MODIFY_RESOURCE_BY_VARIABLE = 53
-    """Attributes for the **modify_resource_by_variable** effect are: \n
-    - tribute_list
-    - source_player
-    - operation
-    - variable"""
-    SET_BUILDING_GATHER_POINT = 54
-    """Attributes for the **set_building_gather_point** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - location_x
-    - location_y
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - selected_object_ids"""
-    SCRIPT_CALL = 55
-    """Attributes for the **script_call** effect are: \n
-    - string_id
-    - message"""
-    CHANGE_VARIABLE = 56
-    """Attributes for the **change_variable** effect are: \n
-    - quantity
-    - operation
-    - variable
-    - message"""
-    CLEAR_TIMER = 57
-    """Attributes for the **clear_timer** effect are: \n
-    - timer"""
-    CHANGE_OBJECT_PLAYER_COLOR = 58
-    """Attributes for the **change_object_player_color** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - player_color
-    - selected_object_ids"""
-    CHANGE_OBJECT_CIVILIZATION_NAME = 59
-    """Attributes for the **change_object_civilization_name** effect are: \n
-    - string_id
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - selected_object_ids"""
-    CHANGE_OBJECT_PLAYER_NAME = 60
-    """Attributes for the **change_object_player_name** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - string_id
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - selected_object_ids"""
-    DISABLE_UNIT_TARGETING = 61
-    """Attributes for the **disable_unit_targeting** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - selected_object_ids"""
-    ENABLE_UNIT_TARGETING = 62
-    """Attributes for the **enable_unit_targeting** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - selected_object_ids"""
-    CHANGE_TECHNOLOGY_COST = 63
-    """Attributes for the **change_technology_cost** effect are: \n
-    - source_player
-    - technology
-    - food
-    - wood
-    - stone
-    - gold"""
-    CHANGE_TECHNOLOGY_RESEARCH_TIME = 64
-    """Attributes for the **change_technology_research_time** effect are: \n
-    - quantity
-    - source_player
-    - technology"""
-    CHANGE_TECHNOLOGY_NAME = 65
-    """Attributes for the **change_technology_name** effect are: \n
-    - source_player
-    - technology
-    - string_id
-    - message"""
-    CHANGE_TECHNOLOGY_DESCRIPTION = 66
-    """Attributes for the **change_technology_description** effect are: \n
-    - source_player
-    - technology
-    - string_id
-    - message"""
-    ENABLE_TECHNOLOGY_STACKING = 67
-    """Attributes for the **enable_technology_stacking** effect are: \n
-    - source_player
-    - technology"""
-    DISABLE_TECHNOLOGY_STACKING = 68
-    """Attributes for the **disable_technology_stacking** effect are: \n
-    - source_player
-    - technology"""
-    ACKNOWLEDGE_MULTIPLAYER_AI_SIGNAL = 69
-    """Attributes for the **acknowledge_multiplayer_ai_signal** effect are: \n
-    - ai_signal_value
-    """
-    DISABLE_OBJECT_SELECTION = 70
-    """Attributes for the **disable_object_selection** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - selected_object_ids"""
-    ENABLE_OBJECT_SELECTION = 71
-    """Attributes for the **enable_object_selection** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - selected_object_ids"""
-    CHANGE_COLOR_MOOD = 72
-    """Attributes for the **change_color_mood** effect are: \n
-    - quantity
-    - color_mood
-    
-    **Version notice**: \n
-    This condition was added in: 1.42
-    """
-    ENABLE_OBJECT_DELETION = 73
-    """Attributes for the **change_color_mood** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - item_id
-    - selected_object_ids
-    
-    **Version notice**: \n
-    This condition was added in: 1.46
-    """
-    DISABLE_OBJECT_DELETION = 74
-    """Attributes for the **change_color_mood** effect are: \n
-    - object_list_unit_id
-    - source_player
-    - area_x1
-    - area_y1
-    - area_x2
-    - area_y2
-    - item_id
-    - selected_object_ids
-    
-    **Version notice**: \n
-    This condition was added in: 1.46
-    """
-
-
-empty_attributes = {
-    "effect_type": -1,
-    "ai_script_goal": -1,
-    "armour_attack_quantity": -1,
-    "armour_attack_class": -1,
-    "quantity": -1,
-    "tribute_list": -1,
-    "diplomacy": -1,
-    "object_list_unit_id": -1,
-    "source_player": -1,
-    "target_player": -1,
-    "technology": -1,
-    "string_id": -1,
-    "display_time": -1,
-    "trigger_id": -1,
-    "location_x": -1,
-    "location_y": -1,
-    "location_object_reference": -1,
-    "area_x1": -1,
-    "area_y1": -1,
-    "area_x2": -1,
-    "area_y2": -1,
-    "object_group": -1,
-    "object_type": -1,
-    "instruction_panel_position": -1,
-    "attack_stance": -1,
-    "time_unit": -1,
-    "enabled": -1,
-    "food": -1,
-    "wood": -1,
-    "stone": -1,
-    "gold": -1,
-    "item_id": -1,
-    "flash_object": -1,
-    "force_research_technology": -1,
-    "visibility_state": -1,
-    "scroll": -1,
-    "operation": -1,
-    "object_list_unit_id_2": -1,
-    "button_location": -1,
-    "ai_signal_value": -1,
-    "object_attributes": -1,
-    "variable": -1,
-    "timer": -1,
-    "facet": -1,
-    "play_sound": -1,
-    "player_color": -1,
-    "color_mood": -1,
-    "reset_timer": -1,
-    "object_state": -1,
-    "action_type": -1,
-    "message": "",
-    "sound_name": "",
-    "selected_object_ids": -1,
-}
diff --git a/testing/test.py b/testing/test.py
deleted file mode 100644
index 54855d0..0000000
--- a/testing/test.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from testing.sections.ScenarioSections import ScenarioSections
-
-def main():
-    scx = ScenarioSections.from_file(r"./scxs/1_47.aoe2scenario")
-    # scx.to_file(rf"C:\Users\LENOVO PC\Games\Age of Empires 2 DE\76561198276345085\resources\_common\scenario\ff.aoe2scenario")
-
-
-
-if __name__ == "__main__":
-    main()