Parse and generate Rocket League replays with Haskell
I am happy to announce version 1.0.0 of Rattletrap, a Rocket League replay parser and generator! It is a command-line utility for converting replays to and from JSON. Binaries are available for every platform Rocket League supports, including Windows, macOS, and Linux.
> rattletrap decode input.replay replay.json
That will read the raw replay from
input.replay and write the JSON to
replay.json. It will parse most replays in about 2 seconds. The JSON output
is minified, so if you want to read it you should probably pretty-print it. But
be warned, the output is large! A 1 MB replay turns into about 100 MB of pretty
Why might you want to parse Rocket League replays? Parsing can be useful for getting stats about a match. Anything you see on the scoreboard in-game is also in the replay. You can even analyze boost usage or watch the replay in your browser! I’m sure there are other neat things to be done with the replay data. I’d love to see what you come up with!
> rattletrap encode replay.json output.replay
That reads some JSON from
replay.json and writes a replay to
Generating is a lot slower than parsing. It takes about 15 seconds to generate
the average replay. If the input JSON came from
rattletrap decode, then the
output replay will be identical to the input. In other words, replays can be
converted to and from JSON without losing any information.
> rattletrap decode original.replay original.json > your-program-here original.json modified.json > rattletrap encode modified.json modified.replay
Those three commands parse a replay into JSON, modify that JSON, then generate a replay from it. By using Rattletrap to handle the annoying replay file format, you can work with easy-to-use JSON.
Why would you want to modify a replay? It’s not quite as useful as parsing one, but you can still have some fun. For example, you could remove your wheels or wear unusual items. You can also force every car to look the same. I would like to see something that stitches replays together to make a highlight reel, or something that makes cinematic camera paths (“smooths”) for videos.
That’s pretty much all there is to know about using Rattletrap. I’m excited to see what you can do with it!
Rattletrap is not the first Rocket League replay parser. There are at least a dozen of them. I borrowed heavily from the other parsers, particularly jjbott’s C# parser and Galile0’s Python one. I literally could not have done this without them. So thanks y’all!
Rattletrap is written in Haskell. Because it’s written in Haskell, Rattletrap is fast. Compared to the Python parser, Rattletrap is about 30 times faster. And compared to the C# parser, it’s about 6 times faster.
I was able to make Rattletrap so fast thanks to the great tools available in
Haskell. In particular, Stack makes profiling as easy as
stack build --profile. Haskell’s runtime system gives you a window into your
program’s behavior with
+RTS -p. That will give you a raw profiling file that
you can turn into a chart with ghc-prof-flamegraph.
This graph shows that Rattletrap spends less than 10% of its time generating JSON. Also there don’t appear to be any obvious hot spots. You can learn more about flame graphs from FP Complete.
Profiling memory usage is just as easy as profiling CPU usage. After building
with profiling enabled, run your program with
+RTS -hc. That will generate a
heap profile that you can visualize with
This graph shows the memory usage over time for parsing a 1 MB replay. It
reaches a peak of about 30 MB and most of that is used by
values. That makes sense because it’s the most common value in a replay. For
example, this replay has about 600,000
CompressedWord values. The Pusher blog
goes into more detail about memory profiling.
Rattletrap has never crashed at runtime, and I doubt it ever will. It does have some known failure modes, like if it finds some game data it hasn’t seen before. But aside from those it should never fail at runtime. I’m confident in it because Haskell is statically typed and its test suite has very high code coverage.
This coverage report was generated with
stack test --coverage. It shows that
79% of expressions and 42% of top-level definitions are covered by the test
suite. Those numbers are a little misleading for two reasons: One, they
consider derived instances like
deriving Eq to not be covered; and two, the
failure modes I mentioned above aren’t exercised in the test suite.
Rattletrap’s effective code coverage is much higher. You can read more about
code coverage from Real World Haskell.
Because Haskell is such a high-level language, Rattletrap is a pretty small project. It weighs in at about 3,000 lines of code. For comparison, the C# parser and generator is about 4,300 lines of code. The Python parser is only about 1,000 lines of code, but it is not able to generate replays.
If you’re wondering what real-world Haskell looks like, I encourage you to read the source. I avoided using any advanced language features or weird operators, so it should be approachable even if you don’t know Haskell. To give you a taste of what you’re in for, here is the code for parsing a player’s camera settings:
getCamSettingsAttribute = do fov <- getFloat32Bits height <- getFloat32Bits angle <- getFloat32Bits distance <- getFloat32Bits stiffness <- getFloat32Bits swivelSpeed <- getFloat32Bits return (CamSettingsAttribute fov height angle distance stiffness swivelSpeed)
I have really enjoyed working on this project. It has allowed me to dive deep into different areas of Haskell. And in the end I wrote a tool that I use every day to make sense of my replays. I hope you like it!