JavaScript is required to use Bungie.net

Group Avatar

BungieNetPlatform

"Updates, discussions, and documentation of the BungieNetPlatform API."

Request Join
originally posted in:BungieNetPlatform
3/3/2015 6:09:45 AM
7

How to Read the Manifest (Reference)

So you might have just discovered the Destiny APIs and/or have already spent some time banging your head against the table trying to make sense of the Manifest file. Back when I initially started playing around with it, I was trying to figure this out using comments pulled off of various threads in this Group, and even then, I think I still had to figure out some things on my own using PHP. The manifest can be located at: http://www.bungie.net/platform/Destiny/Manifest/ It will give you something like this: [spoiler]array ( 'version' => '43982.15.02.02.1746-3', 'mobileAssetContentPath' => '/common/destiny_content/sqlite/asset/asset_sql_content_7c23eefeaa0003dbc141793135dae141.content', 'mobileGearAssetDataBases' => array ( 0 => array ( 'version' => 0, 'path' => '/common/destiny_content/sqlite/asset/asset_sql_content_7c23eefeaa0003dbc141793135dae141.content', ), 1 => array ( 'version' => 1, 'path' => '/common/destiny_content/sqlite/asset/asset_sql_content_5eed6e9a4cb8e84eeb081b93acf267c4.content', ), ), 'mobileWorldContentPaths' => array ( 'en' => '/common/destiny_content/sqlite/en/world_sql_content_41764675ffa70c7fca0a6873b77aabeb.content', 'fr' => '/common/destiny_content/sqlite/fr/world_sql_content_5c56a7c5c166383195afdcdaa64f4575.content', 'es' => '/common/destiny_content/sqlite/es/world_sql_content_a784ce4972713d7d2f665749a4727d7b.content', 'de' => '/common/destiny_content/sqlite/de/world_sql_content_f0263aae5495546025628408dbdf2fc0.content', 'it' => '/common/destiny_content/sqlite/it/world_sql_content_1726e5fda614c91686b38b6a5debefb0.content', 'ja' => '/common/destiny_content/sqlite/ja/world_sql_content_554c8560f931fedb73e9454481fb4bd4.content', 'pt-br' => '/common/destiny_content/sqlite/pt-br/world_sql_content_12e4e5a8fd13e8efa699396f80a93740.content', ), )[/spoiler] The main files you will probably want to look at are the "mobileWorldContentPaths" (the other 2 I believe are for getting assets needed to render 3D models). Each of these files are an SQLite database file that has been put into a Zip file. Extracting them in PHP is pretty straightforward (get_url is a function I wrote that basically does a cURL request which includes stuff like cookies and my API key etc, file_get_contents can be used here, but it can be much slower than using cURL): [spoiler]$cacheFilePath = 'cache/'.pathinfo($path, PATHINFO_BASENAME); file_put_contents($cacheFilePath.'.zip', get_url($path)); $zip = new ZipArchive(); if ($zip->open($cacheFilePath.'.zip') === TRUE) { $zip->extractTo($_SERVER['DOCUMENT_ROOT'].'/cache'); $zip->close(); }[/spoiler] Now if you are like me, and don't have an SQLite viewer on hand, you have an SQLite file that you don't know what tables it contains or what columns are in said tables. Basically each table will generally have two columns, "id" or "key" and "json". Most of the time, "id" is not the entry's hash so you can't just query for a given entry like you can the /Manifest/{type}/{hash} API. So instead, we need to extract these SQLite files further into something we can actually look at and be able to access. Note: $dbtype is referring to the section in the manifest the file is from (gear, asset, world). [spoiler]if ($db = new SQLite3($dbfile)) { $result = $db->query("SELECT name FROM sqlite_master WHERE type='table'"); while($row = $result->fetchArray()) { $result2 = $db->query('SELECT * FROM '.$row['name']); $data = array(); while ($col = $result2->fetchArray(true)) { $json = json_decode($col['json'], true); if (isset($col['id'])) { $data[$col['id']] = $json; } else if (isset($col['key'])) { $data[$col['key']] = $json; } else { $data[] = $json; } } if (!file_exists($dbtype)) mkdir($dbtype); file_put_contents($dbtype.'/'.$row['name'].'.json', json_encode($data)); } }[/spoiler] You should now have some json files you can open up and view. When loading these json files, you should load them in so that the {dataType}Hash value becomes the key so you can reference them directly, though not all tables use a hash to identify objects. If anyone has solutions for doing this using other languages/frameworks, feel free to post it below so we can have a consolidated guide in one place.

Posting in language:

 

Play nice. Take a minute to review our Code of Conduct before submitting your post. Cancel Edit Create Fireteam Post

  • Know this thread is super old and with Destiny 2 coming out, very few will be interested in this. But for practice I got the manifest working in Node.js. Any comments/critiques are more than welcome! Since formatting isn't preserved here, I just posted a link to the code. [url]https://github.com/lrwong/destiny_testing/blob/master/api_testing/public/javascripts/server/manifest.js[/url]

    Posting in language:

     

    Play nice. Take a minute to review our Code of Conduct before submitting your post. Cancel Edit Create Fireteam Post

    1 Reply
    • Here's what I have in Ruby: [spoiler] require 'sqlite3' require 'fileutils' db = SQLite3::Database.new db_file output_dir = File.basename(db_file, '.content') db.execute("SELECT name FROM sqlite_master WHERE type='table'").map(&:first).each do |table_name| db.execute("SELECT * FROM #{table_name}").each do |row| id = row[0].to_i id = (4294967296 + id) if id < 0 data[id] = JSON.parse(row[1]) end FileUtils::mkdir_p(output_dir) File.open(File.expand_path("#{table_name}.json", output_dir)) do |f| f.write(data.to_json) end end [/spoiler]

      Posting in language:

       

      Play nice. Take a minute to review our Code of Conduct before submitting your post. Cancel Edit Create Fireteam Post

    • Anyone done this in Python?

      Posting in language:

       

      Play nice. Take a minute to review our Code of Conduct before submitting your post. Cancel Edit Create Fireteam Post

      2 Replies
      • Edited by purin: 5/2/2015 1:24:17 AM
        Note - You don't actually need to extract the JSON to get the hash for a given row - you CAN use the id column. OP is correct that they don't always match - but this is for a very simple reason. itemHash appears to be an unsigned 32bit integer, but SQLite is interpreting the value as a signed int. See this query: SELECT * FROM DestinyInventoryItemDefinition WHERE CAST(SUBSTR(json, 13,10) AS INT) != id AND CAST(SUBSTR(json, 13,10) AS INT) != 4294967296 + id No rows are returned, because either the itemHash matched the id (first case of WHERE clause) or converting the unsigned itemHash to a signed int causes it to match the id (second case of the WHERE clause). So, if you're using a C-like language with casts, just make your itemHash an unsigned 32 bit int, cast it to a signed int, and then use that value as the id. If you're using a weakly-typed language like PHP, or if your language doesn't allow you to specify 32-bit ints, you can just convert it manually: SELECT * FROM DestinyInventoryItemDefinition WHERE id + 4294967296 = <ItemHash> OR id = <itemHash> Just replace <ItemHash> above with the item hash you're looking for. And obviously, replace "DestinyInventoryItemDefinition" with the table you're interested in. Should be significantly more efficient than parsing the entire database, and should also be much faster for lookup.

        Posting in language:

         

        Play nice. Take a minute to review our Code of Conduct before submitting your post. Cancel Edit Create Fireteam Post

        5 Replies
        • Thanks for this - was very useful! As you requested, here's a class I put together to replicate what you're doing, but in C#. It's not a class I'm using, and it's not a perfect replication; My class doesn't produce seperated json files, but creates a single large json object that contains the entire manifest. I did construct the code to be easy for someone to produce those files however. It's also not "perfect" code, as it's just a throwaway class I've created purely for this thread, in the hope it'll be useful - so don't be judging ;) It requires JSON.net, but you could quite happily get around that if you desire. [spoiler] using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.SQLite; using Newtonsoft.Json.Linq; using System.Net; using System.IO; using System.Reflection; using System.IO.Compression; namespace ThrowAwayCode { public class Manifest { /// <summary> /// Queriable Json respresentative of the bungie Manifest /// </summary> public dynamic ManifestData { get; set; } /// <summary> /// static method to fetch the latest manifest file from bungie, unzip it, and pass back a populated Manifest class /// </summary> /// <returns>Manifest class that represents the current "mobileWorldContentPaths" manifest</returns> public static Manifest LoadManifest() { //get the manifest details string requestURL = "https://www.bungie.net/Platform/Destiny/Manifest"; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestURL); //request.Headers.Add("X-API-Key", apiKey); //not currently required request.KeepAlive = true; request.Host = "www.bungie.net"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); string responseText; using (TextReader tr = new StreamReader(response.GetResponseStream())) { responseText = tr.ReadToEnd(); } dynamic json = JObject.Parse(responseText); //find and delete the existing manifest if it already exists - you probably don't want to do this, but this is throwaway ;) string manifestPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string jsonVersion = json.Response.version; string manifestFile = Path.Combine(manifestPath, jsonVersion + ".manifest"); if (File.Exists(manifestFile)) File.Delete(manifestFile); try { //get the current manifest file stream string manifestURL = json.Response.mobileWorldContentPaths.en; request = (HttpWebRequest)WebRequest.Create("https://www.bungie.net" + manifestURL); using (Stream stream = request.GetResponse().GetResponseStream()) { //need to use a memory stream as the Microsoft ZipArchive doesn't seem to like non-seekable streams using (MemoryStream ms = new MemoryStream()) { stream.CopyTo(ms); ms.Seek(0, SeekOrigin.Begin); //extract the manifest file from it's zip container ZipArchive zippedManifest = new ZipArchive(ms); zippedManifest.Entries[0].ExtractToFile(manifestFile); } } //time to turn the manifest into a class! return new Manifest(manifestFile); } catch (Exception ex) { throw; } } //used to map the database to json public struct ManifestBranch { public string TableName; public List<dynamic> Json; public List<string> JsonString; } /// <summary> /// where the magic happens... /// Takes a SQLite manifest file, and returns a Manifest class with all the data organised into queriable json. /// </summary> /// <param name="manifestFile">the bungie manifest SQLite database file</param> public Manifest(string manifestFile) { using (var sqConn = new SQLiteConnection("Data Source=" + manifestFile + ";Version=3;")) { sqConn.Open(); //build a list of all the tables List<string> tableNames = new List<string>(); using (var sqCmd = new SQLiteCommand("SELECT name FROM sqlite_master WHERE type='table'",sqConn)) { using (var sqReader = sqCmd.ExecuteReader()) { while(sqReader.Read()) tableNames.Add((string)sqReader["name"]); } } //get the json for each row, in each table, and store it in a lovely array List<ManifestBranch> manifestData = new List<ManifestBranch>(); foreach(var tableName in tableNames) { var manifestBranch = new ManifestBranch { TableName = tableName, Json = new List<dynamic>(), JsonString=new List<string>() }; using (var sqCmd = new SQLiteCommand("SELECT * FROM " + tableName,sqConn)) { using (var sqReader = sqCmd.ExecuteReader()) { while (sqReader.Read()) { byte[] jsonData = (byte[])sqReader["json"]; string jsonString = Encoding.ASCII.GetString(jsonData); manifestBranch.Json.Add(JObject.Parse(jsonString)); //you don't need to do this unless you want queriable json at this level ;) manifestBranch.JsonString.Add(jsonString); } } } manifestData.Add(manifestBranch); } //this next bit takes all of the json, for all of the tables, and wraps it up nicely //into a single json string, which is then made dynamic and thus queriable ^_^ string fullJson = "{\"manifest\":["; foreach(var manifestBranch in manifestData) { fullJson += "{\"" + manifestBranch.TableName + "\":[" + string.Join(",", manifestBranch.JsonString) + "]},"; } fullJson = fullJson.TrimEnd(',') + "]}"; this.ManifestData = JObject.Parse(fullJson); //instead of the above, you can just loop through each branch and create individual files to directly replicate lowlines code example //however, it's probably best to do this further up, where we're looping through each table, rather than down here at the end ;) sqConn.Close(); } } } } [/spoiler] thanks for the original post!

          Posting in language:

           

          Play nice. Take a minute to review our Code of Conduct before submitting your post. Cancel Edit Create Fireteam Post

          2 Replies
          • For the /Manifest/{type}/{hash} endpoint, what are the options for the {type}? I haven't been able to find an explanation for this anywhere, and I did search through the list of globals that has been posted on this forum. Thanks!

            Posting in language:

             

            Play nice. Take a minute to review our Code of Conduct before submitting your post. Cancel Edit Create Fireteam Post

            4 Replies
            • Here's a little something you might like to use if you're comparing versions of the manifest. http://pastebin.com/N1Br9CX0 [quote]$v1 = ManifestVersion::Parse('44107.15.02.12.0906-4'); $v2 = ManifestVersion::Parse('44107.15.02.12.0906-3'); echo $v1->CompareTo($v2); //1[/quote]

              Posting in language:

               

              Play nice. Take a minute to review our Code of Conduct before submitting your post. Cancel Edit Create Fireteam Post

            You are not allowed to view this content.
            ;
            preload icon
            preload icon
            preload icon