<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: RayT</title>
    <description>The latest articles on DEV Community by RayT (@jellyraytian).</description>
    <link>https://dev.to/jellyraytian</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4223125%2F8b783782-a0ae-42fb-a1c0-b443b20b08d0.jpg</url>
      <title>DEV Community: RayT</title>
      <link>https://dev.to/jellyraytian</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jellyraytian"/>
    <language>en</language>
    <item>
      <title>Inside Plants vs. Zombies 2: Understanding RSB, RTON, and Rijndael Encryption</title>
      <dc:creator>RayT</dc:creator>
      <pubDate>Mon, 02 Nov 2026 21:57:41 +0000</pubDate>
      <link>https://dev.to/jellyraytian/inside-plants-vs-zombies-2-understanding-rsb-rton-and-rijndael-encryption-24oa</link>
      <guid>https://dev.to/jellyraytian/inside-plants-vs-zombies-2-understanding-rsb-rton-and-rijndael-encryption-24oa</guid>
      <description>&lt;p&gt;&lt;em&gt;Ray Tian — University of Waterloo, Honours Mathematics (Co-op)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I explain how the resource files of Plants vs. Zombies 2 are layered (RSB, RTON, Rijndael), build a small RTON decoder from scratch, and check the container layers of a real build.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;In middle school (around Grades 8–9), I started looking at the data files of the Chinese version of &lt;em&gt;Plants vs. Zombies 2&lt;/em&gt;. A new mode, Penny's Pursuit, had just come out, and I wanted to understand how the game stores its level data, for example how the spawns of a level can be described as data. That curiosity led me into binary file formats.&lt;/p&gt;

&lt;p&gt;This article explains three separate layers that a PVZ2 asset passes through:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0by0obdgfhobdmd7fp1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0by0obdgfhobdmd7fp1n.png" alt="Resource chain of the analysed build; decryption is out of scope" width="800" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Decryption keys and decryption steps are deliberately left out of this article.&lt;/p&gt;

&lt;p&gt;The formats were mapped out by the modding community; the sources are in References. My part is the explanation, a small decoder I built to learn the RTON layer, and a record of what I verified on the container layers of a real build. The code is on &lt;a href="https://github.com/Jelly-RayTian/pvz2-format-exploration" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did this analysis locally, for learning. No game files, account data or session tokens are published, and the test data for my decoder is synthetic; the code is in the appendices.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Understanding RSB
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 What a "resource bundle" is
&lt;/h3&gt;

&lt;p&gt;A game ships thousands of small assets (images, sound, data). Reading them one by&lt;br&gt;
one from the filesystem is slow on a phone, so the engine packs them into a few big&lt;br&gt;
files. An RSB is such a container. The unpacking tools you see in the community read&lt;br&gt;
the container's &lt;em&gt;index&lt;/em&gt; and then write each entry out as its own file.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.2 RSB is not ZIP
&lt;/h3&gt;

&lt;p&gt;Both are "many files in one file", but they differ in structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A ZIP has a central directory of entries and compresses each file individually.&lt;/li&gt;
&lt;li&gt;An RSB, as implemented by the community tool Pvz2Tool, has a fixed-size &lt;strong&gt;header of
offsets&lt;/strong&gt;, a &lt;strong&gt;file list&lt;/strong&gt;, and a table of &lt;strong&gt;resource groups (RSGP)&lt;/strong&gt;. Compression
is applied to &lt;em&gt;parts of a group&lt;/em&gt;, not to each file separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[Source: Pvz2Tool, &lt;code&gt;RsbHeadInfo.kt&lt;/code&gt;, &lt;code&gt;RsgpHeadInfo.kt&lt;/code&gt;.]&lt;/p&gt;
&lt;h3&gt;
  
  
  2.3 The header
&lt;/h3&gt;

&lt;p&gt;All fields are 32-bit little-endian integers. The first fields of the RSB header, as&lt;br&gt;
read by Pvz2Tool, are in this order:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;magic&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0x72736231&lt;/code&gt;, which are the bytes &lt;code&gt;31 62 73 72&lt;/code&gt; = ASCII &lt;code&gt;1bsr&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;version&lt;/td&gt;
&lt;td&gt;an integer (the tool accepts up to 4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;(zero)&lt;/td&gt;
&lt;td&gt;must be 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;headLength&lt;/td&gt;
&lt;td&gt;header size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;fileList_Length&lt;/td&gt;
&lt;td&gt;size of the file-name list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;fileList_BeginOffset&lt;/td&gt;
&lt;td&gt;where that list starts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6–7&lt;/td&gt;
&lt;td&gt;(zero)&lt;/td&gt;
&lt;td&gt;must be 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;rsgpList_Length&lt;/td&gt;
&lt;td&gt;size of the group-name list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;rsgpList_BeginOffset&lt;/td&gt;
&lt;td&gt;where it starts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;rsgp_Number&lt;/td&gt;
&lt;td&gt;how many resource groups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;rsgpInfo_BeginOffset&lt;/td&gt;
&lt;td&gt;where the group-info records start (each record is &lt;code&gt;0xCC&lt;/code&gt; bytes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;composite / autopool / ptx tables&lt;/td&gt;
&lt;td&gt;more &lt;code&gt;Number&lt;/code&gt; + &lt;code&gt;BeginOffset&lt;/code&gt; pairs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The idea to take away is &lt;em&gt;offset + length&lt;/em&gt;: the header does not contain the data, it&lt;br&gt;
contains &lt;strong&gt;where to find the data&lt;/strong&gt;. Everything a parser needs is reachable by&lt;br&gt;
jumping to an offset and reading a known number of bytes.&lt;/p&gt;

&lt;p&gt;[Source: Pvz2Tool &lt;code&gt;RsbHeadInfo.kt&lt;/code&gt;. I verified the magic value by converting the&lt;br&gt;
constant &lt;code&gt;1920164401&lt;/code&gt; to bytes myself: little-endian gives &lt;code&gt;1bsr&lt;/code&gt;.]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version differences are real.&lt;/strong&gt; The same source notes in a comment that the PTX&lt;br&gt;
info record is &lt;code&gt;0x10&lt;/code&gt; bytes normally but &lt;code&gt;0x18&lt;/code&gt; in the "Chinese-version second&lt;br&gt;
generation RSB". A parser that works for one build can fail on another; this is why I&lt;br&gt;
mark my own reproduction by exact version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fowfhxe5m7fkz8mmhpedh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fowfhxe5m7fkz8mmhpedh.png" alt="Byte layout of an RSB: header, group records, RSGP blocks" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2.4 Resource groups (RSGP)
&lt;/h3&gt;

&lt;p&gt;Each group record points to an RSGP block with its own header, magic &lt;code&gt;0x72736770&lt;/code&gt;&lt;br&gt;
(&lt;code&gt;70 67 73 72&lt;/code&gt; = ASCII &lt;code&gt;pgsr&lt;/code&gt;). It stores a version, flags, a file offset, and two&lt;br&gt;
&lt;strong&gt;parts&lt;/strong&gt;, each as &lt;code&gt;Offset&lt;/code&gt;, &lt;code&gt;ZSize&lt;/code&gt; (compressed size) and &lt;code&gt;Size&lt;/code&gt; (uncompressed&lt;br&gt;
size), plus its own file list. Pvz2Tool's README describes zlib compression and 4 KB&lt;br&gt;
alignment.&lt;/p&gt;

&lt;p&gt;So the recovery procedure for one file is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the RSB header → find the group table.&lt;/li&gt;
&lt;li&gt;For each group, jump to its RSGP block → read the part offsets and sizes.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;ZSize&lt;/code&gt; differs from &lt;code&gt;Size&lt;/code&gt;, inflate the bytes with zlib.&lt;/li&gt;
&lt;li&gt;Use the group's file list to cut the decompressed region into named files with
their paths.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the package used for this article, the RSB is wrapped in an extra &lt;code&gt;.smf&lt;/code&gt; container and the resource groups do not use zlib compression; see §6.&lt;/p&gt;

&lt;p&gt;I am deliberately not claiming what every &lt;code&gt;flags&lt;/code&gt; bit means; I have not verified it.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.5 Checking the layout on a real RSB
&lt;/h3&gt;

&lt;p&gt;Everything above came from other people's source code. To see whether it holds on a real file, I parsed the 147,292,160-byte RSB from §6 with a short script (Appendix D). What came out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The header says version 4 and 30 resource groups. The group records are 204 (&lt;code&gt;0xCC&lt;/code&gt;) bytes each and start at offset 383,936 (&lt;code&gt;0x5DBC0&lt;/code&gt;). &lt;code&gt;headLength&lt;/code&gt; is 397,312 (&lt;code&gt;0x61000&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The first group starts exactly at &lt;code&gt;headLength&lt;/code&gt;. Every group starts where the previous one ends, and the last group ends exactly at the end of the file. So &lt;code&gt;headLength + (sum of group sizes) = 397,312 + 146,894,848 = 147,292,160&lt;/code&gt;: the header accounts for the whole file. A parser can use this as a cheap sanity check.&lt;/li&gt;
&lt;li&gt;Every group offset and size is a multiple of 4096, which matches the "4 KB alignment" in Pvz2Tool's README.&lt;/li&gt;
&lt;li&gt;24 groups use only &lt;code&gt;part0&lt;/code&gt; and 6 use only &lt;code&gt;part1&lt;/code&gt;. I do not know what the two parts mean in this build, so I do not interpret them.&lt;/li&gt;
&lt;li&gt;19 groups have a first part that begins with the two bytes &lt;code&gt;10 00&lt;/code&gt;: &lt;code&gt;Packages&lt;/code&gt; and the 18 &lt;code&gt;WorldPackages_*&lt;/code&gt; groups. That is the encrypted-RTON marker from §5.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Offset&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;part0&lt;/th&gt;
&lt;th&gt;part1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Credits_1200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;397,312&lt;/td&gt;
&lt;td&gt;679,936&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;675,840&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Credits_Common&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,077,248&lt;/td&gt;
&lt;td&gt;348,160&lt;/td&gt;
&lt;td&gt;344,064&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LawnStrings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,425,408&lt;/td&gt;
&lt;td&gt;3,047,424&lt;/td&gt;
&lt;td&gt;3,043,328&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MainMenu_Background_1200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4,472,832&lt;/td&gt;
&lt;td&gt;2,695,168&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2,691,072&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MainMenu_Background_Effect_1200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7,168,000&lt;/td&gt;
&lt;td&gt;22,405,120&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;22,401,024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MainMenu_Background_Effect_Common&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;29,573,120&lt;/td&gt;
&lt;td&gt;413,696&lt;/td&gt;
&lt;td&gt;409,600&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Packages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;29,986,816&lt;/td&gt;
&lt;td&gt;67,760,128&lt;/td&gt;
&lt;td&gt;67,354,624&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UI_AlwaysLoaded_Dynamic_1200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;97,746,944&lt;/td&gt;
&lt;td&gt;7,471,104&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;7,467,008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UI_Card_Plant_BG_1200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;105,218,048&lt;/td&gt;
&lt;td&gt;3,743,744&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3,739,648&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UI_PVZ1_Temp_1200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;108,961,792&lt;/td&gt;
&lt;td&gt;34,975,744&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;34,971,648&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UI_PVZ1_Temp_Common&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;143,937,536&lt;/td&gt;
&lt;td&gt;1,945,600&lt;/td&gt;
&lt;td&gt;1,941,504&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_beach&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;145,883,136&lt;/td&gt;
&lt;td&gt;40,960&lt;/td&gt;
&lt;td&gt;36,864&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_cowboy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;145,924,096&lt;/td&gt;
&lt;td&gt;24,576&lt;/td&gt;
&lt;td&gt;20,480&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_dark&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;145,948,672&lt;/td&gt;
&lt;td&gt;45,056&lt;/td&gt;
&lt;td&gt;40,960&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_dino&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;145,993,728&lt;/td&gt;
&lt;td&gt;49,152&lt;/td&gt;
&lt;td&gt;45,056&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_egypt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,042,880&lt;/td&gt;
&lt;td&gt;20,480&lt;/td&gt;
&lt;td&gt;16,384&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_eighties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,063,360&lt;/td&gt;
&lt;td&gt;40,960&lt;/td&gt;
&lt;td&gt;36,864&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_future&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,104,320&lt;/td&gt;
&lt;td&gt;28,672&lt;/td&gt;
&lt;td&gt;24,576&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_heian&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,132,992&lt;/td&gt;
&lt;td&gt;28,672&lt;/td&gt;
&lt;td&gt;24,576&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_iceage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,161,664&lt;/td&gt;
&lt;td&gt;36,864&lt;/td&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_kongfu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,198,528&lt;/td&gt;
&lt;td&gt;20,480&lt;/td&gt;
&lt;td&gt;16,384&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_lostcity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,219,008&lt;/td&gt;
&lt;td&gt;49,152&lt;/td&gt;
&lt;td&gt;45,056&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_mausoleum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,268,160&lt;/td&gt;
&lt;td&gt;24,576&lt;/td&gt;
&lt;td&gt;20,480&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_modern&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,292,736&lt;/td&gt;
&lt;td&gt;36,864&lt;/td&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_pirate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,329,600&lt;/td&gt;
&lt;td&gt;28,672&lt;/td&gt;
&lt;td&gt;24,576&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_renai&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,358,272&lt;/td&gt;
&lt;td&gt;28,672&lt;/td&gt;
&lt;td&gt;24,576&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_skycity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,386,944&lt;/td&gt;
&lt;td&gt;20,480&lt;/td&gt;
&lt;td&gt;16,384&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_steam&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,407,424&lt;/td&gt;
&lt;td&gt;24,576&lt;/td&gt;
&lt;td&gt;20,480&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WorldPackages_tutorial&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,432,000&lt;/td&gt;
&lt;td&gt;8,192&lt;/td&gt;
&lt;td&gt;4,096&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__MANIFESTGROUP__DYNAMIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;146,440,192&lt;/td&gt;
&lt;td&gt;851,968&lt;/td&gt;
&lt;td&gt;847,872&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  3. Understanding RTON
&lt;/h2&gt;

&lt;p&gt;RTON is a &lt;strong&gt;binary serialization&lt;/strong&gt; format: a compact encoding of JSON-like structured&lt;br&gt;
data (objects, arrays, strings, numbers, booleans). A JSON file spells everything as&lt;br&gt;
text; RTON spells each value as &lt;em&gt;a one-byte type tag followed by its payload&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.1 Layout
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"RTON"  | version (uint32 LE, usually 1) | entries … | FF | "DONE"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3.2 Type tags (subset I implement)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;00&lt;/code&gt; / &lt;code&gt;01&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;false / true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;08&lt;/code&gt; / &lt;code&gt;0A&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;int8 / uint8, 1 byte&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;10&lt;/code&gt; / &lt;code&gt;12&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;int16 / uint16, 2 bytes (byte order assumed little-endian)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;20&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int32, 4 bytes (byte order assumed little-endian)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;21&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int32 zero, no payload (listed as unconfirmed in the notes; supported provisionally)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;24&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;unsigned varint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;25&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signed varint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;81&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string: varint byte-length, then bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;90&lt;/code&gt; / &lt;code&gt;91&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;define a cached string / refer to cache entry by index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;85&lt;/code&gt; … &lt;code&gt;FF&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;object … end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;86 FD&lt;/code&gt; count … &lt;code&gt;FE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;array&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;[Source: h3x4n1um/RETON format notes. The notes themselves mark several other tags&lt;br&gt;
(&lt;code&gt;09&lt;/code&gt;, &lt;code&gt;0B&lt;/code&gt;, … zero-value variants, RTID &lt;code&gt;83&lt;/code&gt;) as uncertain, so I leave them out.]&lt;/p&gt;
&lt;h3&gt;
  
  
  3.3 Varints
&lt;/h3&gt;

&lt;p&gt;A varint stores a number in as few bytes as needed: each byte carries 7 payload bits,&lt;br&gt;
and the top bit says "another byte follows", least-significant group first.&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;FE 01&lt;/code&gt; → &lt;code&gt;0x7E + (0x01 &amp;lt;&amp;lt; 7)&lt;/code&gt; = 126 + 128 = &lt;strong&gt;254&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The signed form maps the decoded value &lt;code&gt;n&lt;/code&gt; to &lt;code&gt;n/2&lt;/code&gt; if even and &lt;code&gt;-(n+1)/2&lt;/code&gt; if odd, so&lt;br&gt;
&lt;code&gt;25 03&lt;/code&gt; → &lt;code&gt;n = 3&lt;/code&gt; → &lt;strong&gt;−2&lt;/strong&gt;. Small magnitudes, positive or negative, stay short.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.4 String cache
&lt;/h3&gt;

&lt;p&gt;The first time a key such as &lt;code&gt;"Value"&lt;/code&gt; appears it is written with &lt;code&gt;90&lt;/code&gt; and added to a&lt;br&gt;
table; later occurrences are just &lt;code&gt;91 index&lt;/code&gt;. This is why files with many repeated&lt;br&gt;
keys stay small — and why a decoder must keep state while it reads.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.5 How a parser turns bytes into JSON
&lt;/h3&gt;

&lt;p&gt;A parser keeps a position &lt;code&gt;pos&lt;/code&gt;. Loop: read one byte (tag) → the tag decides how many&lt;br&gt;
more bytes to read and how to interpret them → advance &lt;code&gt;pos&lt;/code&gt;. Objects and arrays are&lt;br&gt;
recursion: seeing &lt;code&gt;85&lt;/code&gt;, call "read an object" until &lt;code&gt;FF&lt;/code&gt;. When finished, &lt;code&gt;pos&lt;/code&gt; must&lt;br&gt;
equal the file length — a cheap but powerful correctness check.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.6 A sample RTON, byte by byte
&lt;/h3&gt;

&lt;p&gt;This is a complete 237-byte RTON that I built with a small encoder. Every value in it is made up, but its shape follows the typical object file: a version, then an array of objects with &lt;code&gt;uid&lt;/code&gt;, &lt;code&gt;objclass&lt;/code&gt; and &lt;code&gt;objdata&lt;/code&gt;. An annotator I wrote (Appendix E) explains each line. Indentation shows nesting; offsets are in hex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000  52 54 4f 4e                    magic "RTON"
0004  01 00 00 00                    version = 1 (uint32 LE)
0008  90 07 76 65 72 73 69 6f ..+1     key, define cache[0] "version"
0011  24 01                            unsigned varint = 1
0013  90 07 6f 62 6a 65 63 74 ..+1     key, define cache[1] "objects"
001C  86 fd 02                         array start, 2 item(s)
001F  85                                 object start
0020  90 03 75 69 64                       key, define cache[2] "uid"
0025  90 05 31 2e 30 2e 31                 define cache[3] = "1.0.1"
002C  90 08 6f 62 6a 63 6c 61 ..+2         key, define cache[4] "objclass"
0036  90 08 44 65 6d 6f 44 61 ..+2         define cache[5] = "DemoData"
0040  90 07 6f 62 6a 64 61 74 ..+1         key, define cache[6] "objdata"
0049  85                                   object start
004A  90 04 6e 61 6d 65                      key, define cache[7] "name"
0050  90 04 64 65 6d 6f                      define cache[8] = "demo"
0056  90 05 69 74 65 6d 73                   key, define cache[9] "items"
005D  86 fd 00                               array start, 0 item(s)
0060  fe                                     array end
0061  90 04 66 6c 61 67                      key, define cache[10] "flag"
0067  01                                     true
0068  90 05 63 6f 75 6e 74                   key, define cache[11] "count"
006F  24 f4 03                               unsigned varint = 500
0072  90 06 6f 72 69 67 69 6e                key, define cache[12] "origin"
007A  25 95 01                               signed varint = -75
007D  90 04 7a 65 72 6f                      key, define cache[13] "zero"
0083  21                                     int32 zero (no payload) = 0
0084  90 03 62 6f 78                         key, define cache[14] "box"
0089  85                                     object start
008A  90 01 78                                 key, define cache[15] "x"
008D  21                                       int32 zero (no payload) = 0
008E  90 01 79                                 key, define cache[16] "y"
0091  25 95 01                                 signed varint = -75
0094  90 01 77                                 key, define cache[17] "w"
0097  21                                       int32 zero (no payload) = 0
0098  90 01 68                                 key, define cache[18] "h"
009B  24 c8 01                                 unsigned varint = 200
009E  ff                                     object end
009F  ff                                   object end
00A0  ff                                 object end
00A1  85                                 object start
00A2  91 02                                key, recall cache[2] = "uid"
00A4  90 05 31 2e 30 2e 32                 define cache[19] = "1.0.2"
00AB  91 04                                key, recall cache[4] = "objclass"
00AD  91 05                                recall cache[5] = "DemoData"
00AF  91 06                                key, recall cache[6] = "objdata"
00B1  85                                   object start
00B2  91 07                                  key, recall cache[7] = "name"
00B4  90 05 64 65 6d 6f 32                   define cache[20] = "demo2"
00BB  91 09                                  key, recall cache[9] = "items"
00BD  86 fd 02                               array start, 2 item(s)
00C0  24 01                                    unsigned varint = 1
00C2  24 02                                    unsigned varint = 2
00C4  fe                                     array end
00C5  91 0a                                  key, recall cache[10] = "flag"
00C7  00                                     false
00C8  91 0b                                  key, recall cache[11] = "count"
00CA  24 07                                  unsigned varint = 7
00CC  91 0c                                  key, recall cache[12] = "origin"
00CE  21                                     int32 zero (no payload) = 0
00CF  91 0d                                  key, recall cache[13] = "zero"
00D1  21                                     int32 zero (no payload) = 0
00D2  91 0e                                  key, recall cache[14] = "box"
00D4  85                                     object start
00D5  91 0f                                    key, recall cache[15] = "x"
00D7  24 05                                    unsigned varint = 5
00D9  91 10                                    key, recall cache[16] = "y"
00DB  25 01                                    signed varint = -1
00DD  91 11                                    key, recall cache[17] = "w"
00DF  21                                       int32 zero (no payload) = 0
00E0  91 12                                    key, recall cache[18] = "h"
00E2  24 09                                    unsigned varint = 9
00E4  ff                                     object end
00E5  ff                                   object end
00E6  ff                                 object end
00E7  fe                               array end
00E8  ff                             object end
00E9  44 4f 4e 45                    footer "DONE"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things worth noticing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first object defines 21 strings with &lt;code&gt;90&lt;/code&gt;. The second object &lt;em&gt;recalls&lt;/em&gt; the keys and the repeated value &lt;code&gt;"DemoData"&lt;/code&gt; with &lt;code&gt;91&lt;/code&gt; and an index, so it is much shorter. This is the string cache at work.&lt;/li&gt;
&lt;li&gt;An empty array is just &lt;code&gt;86 FD 00 FE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zero&lt;/code&gt; and &lt;code&gt;x&lt;/code&gt; are &lt;code&gt;21&lt;/code&gt;: one byte, no payload.&lt;/li&gt;
&lt;li&gt;A negative number such as &lt;code&gt;-75&lt;/code&gt; is stored as &lt;code&gt;25&lt;/code&gt; followed by a varint: &lt;code&gt;95 01&lt;/code&gt; is 149, which is odd, so it decodes to -(149 + 1) / 2 = -75.&lt;/li&gt;
&lt;li&gt;Fixed-width integers (&lt;code&gt;08&lt;/code&gt;, &lt;code&gt;10&lt;/code&gt;, &lt;code&gt;20&lt;/code&gt;) appear in my tests rather than here, because this sample uses the compact varint forms.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Building a Minimal RTON Decoder
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it was built.&lt;/strong&gt; I wrote the decoder with the help of an AI coding assistant (Claude Code), starting from the community format notes and my own earlier ideas about the format, and went through every function myself (§4.1).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design.&lt;/strong&gt; A &lt;code&gt;Reader&lt;/code&gt; class holds the bytes and a cursor &lt;code&gt;pos&lt;/code&gt;. Every &lt;code&gt;read_*&lt;/code&gt;&lt;br&gt;
method consumes bytes and advances &lt;code&gt;pos&lt;/code&gt;; &lt;code&gt;read_value&lt;/code&gt; looks at the tag and&lt;br&gt;
dispatches. Objects and arrays call &lt;code&gt;read_value&lt;/code&gt; recursively, so nesting works&lt;br&gt;
without special cases. The decoder rejects anything it does not recognise instead of&lt;br&gt;
guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests first.&lt;/strong&gt; Twelve fixtures in &lt;code&gt;tests/fixtures.py&lt;/code&gt; are hand-computed byte strings&lt;br&gt;
with their expected Python values, each annotated byte by byte. A further eleven&lt;br&gt;
tests cover errors: bad magic, bad version, missing footer, trailing bytes, truncated&lt;br&gt;
string, bad cache index, unsupported tag, missing array end, runaway varint,&lt;br&gt;
excessive nesting, invalid UTF-8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result.&lt;/strong&gt; &lt;code&gt;python -m unittest&lt;/code&gt; runs 14 tests, all passing (run on 2 Nov 2026).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope.&lt;/strong&gt; Supported tags: &lt;code&gt;00 01 08 0A 10 12 20 21 24 25 81 85 86 90 91&lt;/code&gt;. Floats, 64-bit integers, UTF-8 strings (&lt;code&gt;82&lt;/code&gt;, &lt;code&gt;92&lt;/code&gt;, &lt;code&gt;93&lt;/code&gt;) and RTID are the next things to add. The synthetic fixtures check the decoder against my reading of the spec. This article does not decode real game files.&lt;/p&gt;
&lt;h3&gt;
  
  
  4.1 Reading the decoder, function by function
&lt;/h3&gt;

&lt;p&gt;The full code is in Appendix A. This is what each part does and why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Reader&lt;/code&gt;.&lt;/strong&gt; Holds &lt;code&gt;data&lt;/code&gt;, the cursor &lt;code&gt;pos&lt;/code&gt;, and the string &lt;code&gt;cache&lt;/code&gt;. Keeping &lt;code&gt;cache&lt;/code&gt; on the object, not in a global, means two decodes cannot interfere with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;read_bytes(n)&lt;/code&gt;.&lt;/strong&gt; The only place that touches &lt;code&gt;data&lt;/code&gt; and moves &lt;code&gt;pos&lt;/code&gt;. It checks &lt;code&gt;pos + n&lt;/code&gt; against the length first, so a truncated file raises &lt;code&gt;RtonError("truncated ...")&lt;/code&gt; instead of quietly returning fewer bytes. Everything else is built on it, so the bounds check exists exactly once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;read_int(size, signed)&lt;/code&gt;.&lt;/strong&gt; Converts &lt;code&gt;size&lt;/code&gt; bytes with &lt;code&gt;int.from_bytes(..., "little", signed=...)&lt;/code&gt;. This is where the little-endian assumption lives, in one place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;read_uvarint()&lt;/code&gt;.&lt;/strong&gt; The loop from §3.3: take the low 7 bits (&lt;code&gt;byte &amp;amp; 0x7F&lt;/code&gt;), shift them into place, stop when the high bit is 0 (&lt;code&gt;byte &amp;lt; 0x80&lt;/code&gt;), otherwise add 7 to the shift. It gives up past 63 bits so a file full of &lt;code&gt;FF&lt;/code&gt; bytes cannot loop forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;read_svarint()&lt;/code&gt;.&lt;/strong&gt; Reads an unsigned varint &lt;code&gt;n&lt;/code&gt; and applies the mapping: &lt;code&gt;n // 2&lt;/code&gt; if even, &lt;code&gt;-(n + 1) // 2&lt;/code&gt; if odd.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;read_string(tag)&lt;/code&gt;.&lt;/strong&gt; Three tags share one function. &lt;code&gt;81&lt;/code&gt; reads a length-prefixed string. &lt;code&gt;90&lt;/code&gt; does the same and appends the result to &lt;code&gt;cache&lt;/code&gt;. &lt;code&gt;91&lt;/code&gt; reads an index and returns &lt;code&gt;cache[index]&lt;/code&gt;, raising &lt;code&gt;RtonError&lt;/code&gt; if the index is out of range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;read_value(tag, depth)&lt;/code&gt;.&lt;/strong&gt; A chain of &lt;code&gt;if&lt;/code&gt; checks, one per supported tag. Anything not listed ends in &lt;code&gt;raise RtonError("unsupported tag ...")&lt;/code&gt;: the decoder refuses to guess. &lt;code&gt;depth&lt;/code&gt; is passed down so deeply nested input stops at 64 levels instead of exhausting Python's stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;read_object&lt;/code&gt; and &lt;code&gt;read_array&lt;/code&gt;.&lt;/strong&gt; They call &lt;code&gt;read_value&lt;/code&gt; for each child, so nesting needs no special code. An object reads a key (always a string tag) and then a value until it meets &lt;code&gt;FF&lt;/code&gt;. An array requires &lt;code&gt;FD&lt;/code&gt;, reads a count, reads that many values, and requires &lt;code&gt;FE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;decode(data)&lt;/code&gt;.&lt;/strong&gt; Checks the magic and version, reads the top-level object, then requires &lt;code&gt;DONE&lt;/code&gt; and that &lt;code&gt;pos&lt;/code&gt; equals the file length. The last check catches files with trailing bytes.&lt;/p&gt;

&lt;p&gt;The design choice that matters most: every function either returns a correct value or raises. There is no "best effort" path, so a wrong tag shows up as an error with an offset, not as quietly wrong data.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Understanding Rijndael Encryption
&lt;/h2&gt;
&lt;h3&gt;
  
  
  5.1 Rijndael vs. AES
&lt;/h3&gt;

&lt;p&gt;Rijndael is a family of block ciphers designed by Joan Daemen and Vincent Rijmen. AES&lt;br&gt;
is the standardised subset: block size fixed at 128 bits, keys of 128/192/256 bits.&lt;br&gt;
Rijndael itself allows block sizes 128, 192 or 256 bits independently of the key&lt;br&gt;
size. So "AES" and "Rijndael" are not interchangeable when the block size is not&lt;br&gt;
128 bits.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.2 Rijndael in this game: what is publicly known
&lt;/h3&gt;

&lt;p&gt;Community research has documented two things about the encrypted RTON files of this game: they start with the two marker bytes &lt;code&gt;10 00&lt;/code&gt;, and they use Rijndael in CBC mode with a &lt;strong&gt;192-bit (24-byte) block&lt;/strong&gt;. A practical consequence is that a standard AES library, which fixes the block at 128 bits, cannot be used for them as it is; you need a Rijndael implementation that supports the other block size.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.3 Keys and IVs: what they are, and what this article leaves out
&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;key&lt;/em&gt; is the secret value the cipher is parameterised by; Rijndael accepts 128-, 192- or 256-bit keys. An &lt;em&gt;IV&lt;/em&gt; (initialization vector) is a starting value for CBC and has the same size as the block. A key can be written as raw bytes, as ASCII text or as hex, and mixing these up is a classic source of bugs: 32 ASCII characters are 32 bytes, while 32 hex characters are only 16.&lt;/p&gt;

&lt;p&gt;This article does not publish, derive or search for any key, and it does not walk through decrypting any game file. Its subject is the structure of the containers and the decoder for the &lt;em&gt;decrypted&lt;/em&gt; form of the format. Where a step would need a key, I say so and stop.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.4 What the design teaches
&lt;/h3&gt;

&lt;p&gt;Whatever the algorithm, a client application that decrypts its own data has to hold the key somewhere on the device. Client-side encryption therefore deters casual reading, but it is not a strong protection, and anything that really matters belongs on a server. That is a lesson about system design; it is not a how-to.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.5 My own exploration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I did back then (from memory).&lt;/strong&gt; In middle school I opened the APK with MT Manager on my phone and browsed its contents, and I learned that the game's data files were encrypted and that community tools could read them. I did not write those tools. I no longer remember their names or the order of my steps, and this article does not repeat any of them.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.6 CBC, step by step
&lt;/h3&gt;

&lt;p&gt;CBC (cipher block chaining) cuts the data into blocks of the cipher's block size, 24 bytes here. Decrypting works like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plaintext[i] = Decrypt(key, ciphertext[i]) XOR ciphertext[i-1]      with ciphertext[-1] = IV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each block is decrypted on its own with the raw cipher and then XORed with the &lt;em&gt;previous ciphertext block&lt;/em&gt;; for the first block the "previous block" is the IV. Three consequences are useful in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The IV only matters for the first block.&lt;/strong&gt; A wrong IV garbles block 0 but leaves every later block correct, which is a handy way to tell a wrong IV from a wrong key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decrypting block i needs only two ciphertext blocks&lt;/strong&gt;, so long inputs can be processed in pieces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identical plaintext blocks encrypt differently&lt;/strong&gt;, because each is mixed with the previous ciphertext. That is the reason to use CBC rather than encrypting block by block (ECB).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This section describes the mode, not any particular file.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Verifying the container layers on a real build
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Input.&lt;/strong&gt; One Chinese-version Android APK (&lt;code&gt;baokai_4.2.7_1885_715_dj2.0-4.0.1.apk&lt;/code&gt;, 2,271,163,986 bytes, SHA-256 &lt;code&gt;7c09515d…90a5d7&lt;/code&gt;), downloaded from the game's website &lt;a href="https://pvz2.hrgame.com.cn/" rel="noopener noreferrer"&gt;https://pvz2.hrgame.com.cn/&lt;/a&gt; on 2 Nov 2026 and analysed locally. I look only at the container layers (the wrapper, the RSB header and the group table). I do not decrypt or decode the game's own data here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An extra layer I did not expect.&lt;/strong&gt; The RSB files in this APK are not stored bare: &lt;code&gt;assets/dynamic.rsb.smf&lt;/code&gt; begins with the ASCII magic &lt;code&gt;RSLB&lt;/code&gt;. It is a block container: a 32-byte header, a table of 36-byte block records, then one raw LZMA1 stream per block (32 MiB blocks, LZMA properties &lt;code&gt;lc=3 lp=0 pb=2&lt;/code&gt;). I found the layout in a community decompressor's comments and wrote my own decompressor; it produced exactly the 147,292,160 bytes the header declares. So the chain for this build is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APK -&amp;gt; .rsb.smf (RSLB, LZMA blocks) -&amp;gt; RSB -&amp;gt; RSGP -&amp;gt; RTON files (encrypted; not covered here)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Result on this APK&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unwrap &lt;code&gt;dynamic.rsb.smf&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;147,292,160 bytes; starts with &lt;code&gt;1bsr&lt;/code&gt;; matches the declared size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read RSB header&lt;/td&gt;
&lt;td&gt;version 4, 30 resource groups, group records &lt;code&gt;0xCC&lt;/code&gt; bytes, PTX records &lt;code&gt;0x18&lt;/code&gt; bytes (the "second-generation" variant noted in Pvz2Tool)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compression&lt;/td&gt;
&lt;td&gt;In every one of the 30 groups &lt;code&gt;ZSize == Size&lt;/code&gt;: &lt;strong&gt;no zlib was involved in this build.&lt;/strong&gt; I did not exercise zlib at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read one RSGP (&lt;code&gt;WorldPackages_beach&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;magic &lt;code&gt;pgsr&lt;/code&gt;, version 4; its first part begins with &lt;code&gt;10 00&lt;/code&gt;, the encrypted-RTON marker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What's next.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the RSGP file-name table to recover full paths.&lt;/li&gt;
&lt;li&gt;Add floats, 64-bit integers, UTF-8 strings and RTID to the decoder, with new synthetic fixtures.&lt;/li&gt;
&lt;li&gt;Exercise zlib on a build whose groups are compressed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.1 The RSLB container, field by field
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;.smf&lt;/code&gt; file is 69,183,497 bytes and expands to 147,292,160. All numbers are little-endian. The header:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Offset&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value in this file&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;magic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RSLB&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x04&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;version&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x08&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;total uncompressed size&lt;/td&gt;
&lt;td&gt;147,292,160&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;packed size&lt;/td&gt;
&lt;td&gt;69,183,465 (= file size − 32)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x18&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;block size&lt;/td&gt;
&lt;td&gt;33,554,432 (32 MiB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x1C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;block count&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After the header comes a table of 36-byte records, one per block. Decoded for this file:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Block&lt;/th&gt;
&lt;th&gt;Uncompressed start&lt;/th&gt;
&lt;th&gt;Uncompressed size&lt;/th&gt;
&lt;th&gt;Data offset&lt;/th&gt;
&lt;th&gt;Compressed size&lt;/th&gt;
&lt;th&gt;LZMA dictionary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;33,554,432&lt;/td&gt;
&lt;td&gt;212&lt;/td&gt;
&lt;td&gt;10,267,186&lt;/td&gt;
&lt;td&gt;33,554,432&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;33,554,432&lt;/td&gt;
&lt;td&gt;33,554,432&lt;/td&gt;
&lt;td&gt;10,267,398&lt;/td&gt;
&lt;td&gt;22,168,257&lt;/td&gt;
&lt;td&gt;33,554,432&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;67,108,864&lt;/td&gt;
&lt;td&gt;33,554,432&lt;/td&gt;
&lt;td&gt;32,435,655&lt;/td&gt;
&lt;td&gt;18,241,763&lt;/td&gt;
&lt;td&gt;33,554,432&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;100,663,296&lt;/td&gt;
&lt;td&gt;33,554,432&lt;/td&gt;
&lt;td&gt;50,677,418&lt;/td&gt;
&lt;td&gt;14,198,046&lt;/td&gt;
&lt;td&gt;33,554,432&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;134,217,728&lt;/td&gt;
&lt;td&gt;13,074,432&lt;/td&gt;
&lt;td&gt;64,875,464&lt;/td&gt;
&lt;td&gt;4,308,033&lt;/td&gt;
&lt;td&gt;13,631,488&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each block's data begins with 5 bytes of LZMA properties (&lt;code&gt;5D&lt;/code&gt; plus a 4-byte dictionary size) followed by a raw LZMA1 stream with no end marker; the table supplies the size to stop at. The properties byte &lt;code&gt;0x5D&lt;/code&gt; = 93 unpacks as &lt;code&gt;lc = 93 mod 9 = 3&lt;/code&gt;, &lt;code&gt;lp = (93 div 9) mod 5 = 0&lt;/code&gt;, &lt;code&gt;pb = 93 div 45 = 2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Checks that came out right: block 0's data starts at 212 = &lt;code&gt;0x20&lt;/code&gt; + 5 × 36, right after the table; each block starts where the previous one ends (212 + 10,267,186 = 10,267,398); the five uncompressed sizes add up to 147,292,160; and the five compressed sizes plus the 212-byte header and table add up to the file size. The last block is shorter and uses a smaller dictionary; I do not know why.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 What went wrong first
&lt;/h3&gt;

&lt;p&gt;I kept the failures because they show what the checks are for. The scripts were written and run with the AI assistant described in §4.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One stream or many?&lt;/strong&gt; The first attempt decompressed the &lt;code&gt;.smf&lt;/code&gt; as a single LZMA stream after the header. It started well (&lt;code&gt;1bsr&lt;/code&gt;, so the idea was right) and then failed with "Corrupt input data" after about 33.6 MB. The container is chunked into 32 MiB blocks; the fix was to read the block table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A wrong header offset.&lt;/strong&gt; The first end-to-end script read the first part's offset from the wrong position in the RSGP header. The &lt;code&gt;10 00&lt;/code&gt; marker check failed at once. I fixed the offset and added an assertion that the RSGP header and the RSB's group record agree.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Neither would have been caught by synthetic tests alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Reproducing the container checks
&lt;/h3&gt;

&lt;p&gt;You need an Android APK of the game that you are entitled to use and Python 3. The scripts are in the appendices.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An APK is a zip file. Take out &lt;code&gt;assets/dynamic.rsb.smf&lt;/code&gt; (&lt;code&gt;unzip app.apk assets/dynamic.rsb.smf&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;python3 rslb.py dynamic.rsb.smf dynamic.rsb&lt;/code&gt; (Appendix C). For the build I analysed, expect 147,292,160 bytes starting with &lt;code&gt;1bsr&lt;/code&gt;. Other builds will differ.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;python3 rsb_probe.py dynamic.rsb&lt;/code&gt; (Appendix D). Expect version 4 and 30 groups, with &lt;code&gt;Credits_1200&lt;/code&gt; first.&lt;/li&gt;
&lt;li&gt;Or run steps 1 to 3 and the consistency checks in one go with &lt;code&gt;python3 inspect_build.py app.apk&lt;/code&gt; (Appendix F).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a step gives a different number, trust your number and check which build you have: that is the point of recording the SHA-256 of the APK.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Lessons Learned
&lt;/h2&gt;

&lt;p&gt;This project gave me a more complete picture of how mobile apps are packaged. What I analysed here was an Android APK: at bottom it is a zip archive holding native libraries (&lt;code&gt;.so&lt;/code&gt;), &lt;code&gt;assets&lt;/code&gt; and configuration. In Grade 9 I also looked at iOS app packages (IPA) and learned how Apple's code signing works; that is outside this article's experiments.&lt;/p&gt;

&lt;p&gt;It was also my first fairly systematic contact with encryption concepts: key and IV lengths, how block ciphers differ, and how AES relates to Rijndael (here the block size is not the AES one, so a standard AES library does not fit).&lt;/p&gt;

&lt;p&gt;On method, writing tests from the spec before the code paid off: the fixtures forced me to work out varints and the string cache by hand before any decoder code existed. Annotating a sample file byte by byte also taught me to read structured binary data.&lt;/p&gt;

&lt;p&gt;Next I want to treat the network protocol (encrypted HTTP requests) as a separate study: finish reproducible experiments first, then write a second article.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. FAQ and glossary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is the RSB itself encrypted?&lt;/strong&gt; Not in this build. After the &lt;code&gt;.smf&lt;/code&gt; wrapper is removed, the RSB header parses normally and the groups are stored uncompressed. The RTON files inside carry the &lt;code&gt;10 00&lt;/code&gt; marker, which marks them as encrypted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Rijndael the same as AES?&lt;/strong&gt; AES is Rijndael restricted to a 128-bit block. This game uses a 192-bit block, so a standard AES library does not fit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this article show how to decrypt the game's data?&lt;/strong&gt; No. It covers the container layers and a decoder for the decrypted form of the format, tested on synthetic samples. It contains no keys and no decryption steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the decoder work on real PVZ2 files?&lt;/strong&gt; This article does not test that. It handles the documented subset on synthetic samples. Floats, 64-bit integers, UTF-8 strings and RTID are not implemented, and I do not know which of those other files use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the decoder original work?&lt;/strong&gt; It is my implementation of a documented format, written with an AI assistant and read and understood by me. It is not a new discovery; see §4 and the references.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Glossary&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Magic number&lt;/strong&gt;: fixed bytes at the start of a file that identify its type (&lt;code&gt;1bsr&lt;/code&gt;, &lt;code&gt;pgsr&lt;/code&gt;, &lt;code&gt;RTON&lt;/code&gt;, &lt;code&gt;RSLB&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offset / length&lt;/strong&gt;: where data starts and how many bytes it spans; the basis of almost every binary format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSB&lt;/strong&gt;: a PopCap resource bundle holding many files in resource groups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSGP&lt;/strong&gt;: one resource group inside an RSB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SMF / RSLB&lt;/strong&gt;: the wrapper seen in this build that stores an RSB as LZMA-compressed blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RTON&lt;/strong&gt;: PopCap's binary serialization format for JSON-like data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Varint&lt;/strong&gt;: a variable-length integer; small numbers take fewer bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block cipher / block size&lt;/strong&gt;: an encryption algorithm that works on fixed-size chunks; Rijndael allows 128, 192 or 256 bits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IV (initialization vector)&lt;/strong&gt;: the starting value that CBC mixes into the first block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CBC&lt;/strong&gt;: cipher block chaining; each block is mixed with the previous ciphertext block.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;h3x4n1um, &lt;em&gt;RETON&lt;/em&gt; — RTON format notes. &lt;a href="https://github.com/h3x4n1um/RETON" rel="noopener noreferrer"&gt;https://github.com/h3x4n1um/RETON&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DreamMoonCai, &lt;em&gt;Pvz2Tool&lt;/em&gt; (MIT) — RSB/RSGP header definitions. &lt;a href="https://github.com/DreamMoonCai/Pvz2Tool" rel="noopener noreferrer"&gt;https://github.com/DreamMoonCai/Pvz2Tool&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;apples1949, &lt;em&gt;PvZ2ModdingGuide&lt;/em&gt; — community guide. &lt;a href="https://github.com/apples1949/PvZ2ModdingGuide" rel="noopener noreferrer"&gt;https://github.com/apples1949/PvZ2ModdingGuide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;J. Daemen, V. Rijmen, &lt;em&gt;The Design of Rijndael&lt;/em&gt; (Springer).&lt;/li&gt;
&lt;li&gt;NIST, FIPS 197, &lt;em&gt;Advanced Encryption Standard&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Python &lt;code&gt;zlib&lt;/code&gt;, &lt;code&gt;hashlib&lt;/code&gt; documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Appendix A: the decoder (&lt;code&gt;mini_rton/decoder.py&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;MIT licence, copyright 2026 Ray Tian. Only the tags listed in §4 are supported; anything else raises &lt;code&gt;RtonError&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A minimal RTON decoder covering a documented subset of the format.

Format notes: https://github.com/h3x4n1um/RETON (see docs/rton-scope.md).
Supported tags: 00 01 08 0A 10 12 20 24 25 81 85 86(FD..FE) 90 91.
Anything else raises RtonError instead of guessing.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;MAGIC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RTON&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;FOOTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DONE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;TAG_FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TAG_TRUE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;
&lt;span class="n"&gt;TAG_INT8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TAG_UINT8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x0A&lt;/span&gt;
&lt;span class="n"&gt;TAG_INT16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TAG_UINT16&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x12&lt;/span&gt;
&lt;span class="n"&gt;TAG_INT32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x20&lt;/span&gt;
&lt;span class="n"&gt;TAG_INT32_ZERO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x21&lt;/span&gt;
&lt;span class="n"&gt;TAG_UVARINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TAG_SVARINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x25&lt;/span&gt;
&lt;span class="n"&gt;TAG_STRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x81&lt;/span&gt;
&lt;span class="n"&gt;TAG_OBJECT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x85&lt;/span&gt;
&lt;span class="n"&gt;TAG_ARRAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x86&lt;/span&gt;
&lt;span class="n"&gt;TAG_CACHE_DEFINE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TAG_CACHE_RECALL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x91&lt;/span&gt;
&lt;span class="n"&gt;TAG_ARRAY_BEGIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TAG_ARRAY_END&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xFD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFE&lt;/span&gt;
&lt;span class="n"&gt;TAG_OBJECT_END&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;

&lt;span class="n"&gt;MAX_DEPTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Raised for malformed or unsupported input.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A cursor over bytes: every read advances `pos`.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;# strings defined by tag 0x90, indexed by 0x91
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;truncated: need &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes at offset &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_u8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;little&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_uvarint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;7 payload bits per byte, low group first; high bit = more bytes.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_u8&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x7F&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;shift&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mh"&gt;0x80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
            &lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;varint too long at offset &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_svarint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_uvarint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_string_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_uvarint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;UnicodeDecodeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid string bytes before offset &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Read a string-valued tag (81, 90, 91) whose tag byte is already consumed.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_STRING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_string_body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_CACHE_DEFINE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_string_body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_CACHE_RECALL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_uvarint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache index &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; out of range (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; entries)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected string tag, got 0x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at offset &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_DEPTH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nesting too deep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_FALSE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_TRUE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_INT8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_UINT8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_INT16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_UINT16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_INT32&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_INT32_ZERO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# no payload; listed as unconfirmed in the format notes
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_UVARINT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_uvarint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_SVARINT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_svarint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TAG_CACHE_DEFINE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TAG_CACHE_RECALL&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_OBJECT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_ARRAY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsupported tag 0x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at offset &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Read key/value pairs until FF. The opening 85 is already consumed.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_u8&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_OBJECT_END&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
            &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_u8&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;86 FD &amp;lt;count&amp;gt; &amp;lt;values...&amp;gt; FE. The opening 86 is already consumed.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_u8&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;TAG_ARRAY_BEGIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected FD after array tag, got 0x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_uvarint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_u8&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_u8&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;TAG_ARRAY_END&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected FE at array end, got 0x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Decode RTON bytes into Python objects (dict at the top level).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;MAGIC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bad magic: not an RTON file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsupported version &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing DONE footer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RtonError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; trailing bytes after footer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Appendix B: synthetic test fixtures (&lt;code&gt;tests/fixtures.py&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Every byte is annotated or built by a small encoder. The error-handling tests are omitted here for length.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Hand-built RTON byte strings, each with its expected decoded value.

Every byte is annotated so you can check it against docs/rton-scope.md.
These are synthetic: they contain no game data.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;52544F4E&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;01000000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# "RTON" + version 1 (uint32 LE)
&lt;/span&gt;&lt;span class="n"&gt;FOOTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;444F4E45&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# "DONE"
&lt;/span&gt;
&lt;span class="c1"&gt;# name -&amp;gt; (bytes, expected decoded Python value)
&lt;/span&gt;&lt;span class="n"&gt;FIXTURES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# {}   -- just the end-of-object marker FF
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;

    &lt;span class="c1"&gt;# {"a": true, "b": false}
&lt;/span&gt;    &lt;span class="c1"&gt;#   81 01 61   string, 1 byte: "a"      01  true
&lt;/span&gt;    &lt;span class="c1"&gt;#   81 01 62   string, 1 byte: "b"      00  false
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81 01 61 01  81 01 62 00  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;# {"name": "Zombie"}
&lt;/span&gt;    &lt;span class="c1"&gt;#   81 04 6E616D65         "name"
&lt;/span&gt;    &lt;span class="c1"&gt;#   81 06 5A6F6D626965     "Zombie"
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81 04 6E616D65  81 06 5A6F6D626965  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Zombie&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;# {"n": -2, "u": 200}
&lt;/span&gt;    &lt;span class="c1"&gt;#   08 FE   int8 0xFE = -2 (two's complement)
&lt;/span&gt;    &lt;span class="c1"&gt;#   0A C8   uint8 0xC8 = 200
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;small_ints&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81 01 6E 08 FE  81 01 75 0A C8  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;u&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;# {"a": {"a": 1}}  -- cache define (90) then recall (91 00) inside a nested object
&lt;/span&gt;    &lt;span class="c1"&gt;#   90 01 61  define "a" (index 0)   85  object start
&lt;/span&gt;    &lt;span class="c1"&gt;#   91 00     recall index 0 ("a")   08 01  int8 1
&lt;/span&gt;    &lt;span class="c1"&gt;#   FF FF     inner end, outer end
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nested_cache_recall&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;90 01 61  85  91 00  08 01  FF  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;# {"a": [1, 2]}  -- 86 FD &amp;lt;count=2&amp;gt; 08 01 08 02 FE
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;array&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81 01 61  86 FD 02  08 01  08 02  FE  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;# {"w": -300, "i": 70000}
&lt;/span&gt;    &lt;span class="c1"&gt;#   10 D4 FE  int16 LE: 0xFED4 = -300
&lt;/span&gt;    &lt;span class="c1"&gt;#   20 70 11 01 00  int32 LE: 0x00011170 = 70000
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wide_ints&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81 01 77 10 D4 FE  81 01 69 20 70 11 01 00  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;i&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;70000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;# {"mX": 0, "mY": -150}  -- 21 = int32 zero (no payload);
&lt;/span&gt;    &lt;span class="c1"&gt;# 25 AB 02 -&amp;gt; varint 0x2B + (2 &amp;lt;&amp;lt; 7) = 299 (odd) -&amp;gt; -(299+1)/2 = -150
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;int32_zero_and_signed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81 02 6D 58 21  81 02 6D 59 25 AB 02  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;# A made-up sample with the same *shape* as a typical object file: a version, then an array of
&lt;/span&gt;    &lt;span class="c1"&gt;# objects (uid / objclass / objdata). The second object reuses keys and values through the string
&lt;/span&gt;    &lt;span class="c1"&gt;# cache (91). Built with a small encoder; annotated byte by byte in the article (section 3.6).
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;showcase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;52 54 4f 4e 01 00 00 00 90 07 76 65 72 73 69 6f 6e 24 01 90 07 6f 62 6a 65 63 74 73 86 fd 02 85&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;90 03 75 69 64 90 05 31 2e 30 2e 31 90 08 6f 62 6a 63 6c 61 73 73 90 08 44 65 6d 6f 44 61 74 61&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;90 07 6f 62 6a 64 61 74 61 85 90 04 6e 61 6d 65 90 04 64 65 6d 6f 90 05 69 74 65 6d 73 86 fd 00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fe 90 04 66 6c 61 67 01 90 05 63 6f 75 6e 74 24 f4 03 90 06 6f 72 69 67 69 6e 25 95 01 90 04 7a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;65 72 6f 21 90 03 62 6f 78 85 90 01 78 21 90 01 79 25 95 01 90 01 77 21 90 01 68 24 c8 01 ff ff&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ff 85 91 02 90 05 31 2e 30 2e 32 91 04 91 05 91 06 85 91 07 90 05 64 65 6d 6f 32 91 09 86 fd 02&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;24 01 24 02 fe 91 0a 00 91 0b 24 07 91 0c 21 91 0d 21 91 0e 85 91 0f 24 05 91 10 25 01 91 11 21&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;91 12 24 09 ff ff ff fe ff 44 4f 4e 45&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;objects&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;uid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.0.1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;objclass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DemoData&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;objdata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;demo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flag&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zero&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;box&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;uid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.0.2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;objclass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DemoData&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;objdata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;demo2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flag&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zero&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;box&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;}}}]},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;# --- varint fixtures ---
&lt;/span&gt;    &lt;span class="c1"&gt;# {"Value": 61}: key via cache-define 90, value unsigned varint 24 3D (0x3D = 61)
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;varint_cached_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;90 05 56616C7565  24 3D  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;# {"n": -2}: signed varint 25 03 -&amp;gt; n=3 is odd -&amp;gt; -(3+1)/2 = -2
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signed_varint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81 01 6E  25 03  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;# {"x": 254}: varint FE 01 -&amp;gt; 0x7E + (1 &amp;lt;&amp;lt; 7) = 254
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;varint_two_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81 01 78  24 FE 01  FF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FOOTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;254&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The scripts for unwrapping the &lt;code&gt;.smf&lt;/code&gt;, reading the RSB table, annotating an RTON and inspecting a build (Appendices C to F) are in the original post and in the &lt;a href="https://github.com/Jelly-RayTian/pvz2-format-exploration" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;: &lt;a href="https://jelly-raytian.github.io/systems/pvz2-resource-analysis/" rel="noopener noreferrer"&gt;https://jelly-raytian.github.io/systems/pvz2-resource-analysis/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://jelly-raytian.github.io/systems/pvz2-resource-analysis/" rel="noopener noreferrer"&gt;https://jelly-raytian.github.io/systems/pvz2-resource-analysis/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>gamedev</category>
      <category>cryptography</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
