From 6a25f344a0c7f78ea4ae6cd6d6b4dd578b6babbb Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:54:51 -0500 Subject: [PATCH 01/21] Create placeholder --- server/placeholder | 1 + 1 file changed, 1 insertion(+) create mode 100644 server/placeholder diff --git a/server/placeholder b/server/placeholder new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/server/placeholder @@ -0,0 +1 @@ + From 5f69448a9c33e270bd19597244f18457d17a4b8a Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:55:08 -0500 Subject: [PATCH 02/21] Create toytags.json --- server/toytags.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 server/toytags.json diff --git a/server/toytags.json b/server/toytags.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/server/toytags.json @@ -0,0 +1 @@ +[] From be2d71fb75a17d4e88d87d40d8bd152cfeac411c Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:55:30 -0500 Subject: [PATCH 03/21] Update toytags.json --- server/toytags.json | 1 + 1 file changed, 1 insertion(+) diff --git a/server/toytags.json b/server/toytags.json index fe51488..f2cb873 100644 --- a/server/toytags.json +++ b/server/toytags.json @@ -1 +1,2 @@ +//This is where all toy tag data is stored. [] From 35dee190bde1edcded43b6fce9d9da1df7d6d538 Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:56:24 -0500 Subject: [PATCH 04/21] Update and rename index.html to server/index.html --- index.html | 279 ---------------------------------- server/index.html | 374 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 374 insertions(+), 279 deletions(-) delete mode 100644 index.html create mode 100644 server/index.html diff --git a/index.html b/index.html deleted file mode 100644 index 12061d4..0000000 --- a/index.html +++ /dev/null @@ -1,279 +0,0 @@ - - - -
- - - -
- -
-

LD ToyPad Emulator

-

- → Please don't spam click, or things may break and you'll have to restart the server! ←
- After changing a position, you have to wait for about 2 seconds, otherwise the game can't keep up.
- If there are too many characters, reload the page, add 7 characters and click "Remove all".
- Make sure not to exceed the item limit on each position when switching or placing objects (left/right: 3, middle: 1)!!! -

-
- -
- Position: - - - - - - -
- -
- -
- -
- Position: - - - - - - -
- -
- -
-

Currently Placed

-

Click an entry to remove it from the ToyPad.

-
-
- -
-
- - - - \ No newline at end of file diff --git a/server/index.html b/server/index.html new file mode 100644 index 0000000..df40e66 --- /dev/null +++ b/server/index.html @@ -0,0 +1,374 @@ + + + +
+ + + +
+ + +
+
+

LD ToyPad Emulator

+

+ → Please don't spam click, or things may break and you'll have to restart the server! ←
+ After changing a position, you have to wait for about 2 seconds, otherwise the game can't keep up.
+ If there are too many characters, reload the page, add 7 characters and click "Remove all".
+ Make sure not to exceed the item limit on each position when switching or placing objects (left/right: 3, + middle: 1)!!! +

+
+ +
+ +
+ +
+ +
+ +
+
+

Toy Box

+

Drag an entry to move it to the pad

+
+
    +
      +
    • +

      Drag here to delete

      +
    • +
    +
    +
    + +
    +

    The Toypad

    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    + + + + + + + + + + From 08aa1128a157f8a49a8f800b36b4198a305b2383 Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:02:32 -0500 Subject: [PATCH 05/21] Reworked Graphical Interface > Added: > > fs to read and write to toytags.json > > Socket.io for communications between index.js and the html script. > Pad Index data is now also stored locally allowing data to be retrieved even after a refresh. > Tag data is now stored locally and called upon instead of creating new data with every action. > > This includes vehicles with vehicle upgrades which are now saved as well > Combined all tag movement into one function '/characterPlace' which checks for type before placing. --- index.js | 267 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 244 insertions(+), 23 deletions(-) diff --git a/index.js b/index.js index d11621e..60fdb7e 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,35 @@ /* - Copyright © 2021 Berny23 This file is part of "ToyPad Emulator for Lego Dimensions" which is released under the "MIT" license. See file "LICENSE" or go to "https://choosealicense.com/licenses/mit" for full license details. - */ const ld = require ('node-ld') +const fs = require('fs'); const path = require('path'); +const { DH_CHECK_P_NOT_PRIME } = require('constants'); + const express = require('express'); const app = express(); +const http = require('http'); +const server = http.createServer(app); +const { Server } = require("socket.io"); +const io = new Server(server); + +const toytagFileName = './server/json/toytags.json'; var tp = new ld.ToyPadEmu() tp.registerDefaults() +initalizeToyTagsJSON(); + function createVehicle(id, upgrades, uid){ upgrades = upgrades || [0,0]; var token = new Buffer(180); token.fill(0); token.uid = uid; + console.log(upgrades); token.writeUInt32LE(upgrades[0],0x23*4); token.writeUInt16LE(id,0x24*4); token.writeUInt32LE(upgrades[1],0x25*4); @@ -35,49 +45,260 @@ function createCharacter(id, uid){ return token; } +function getNameFromID(id){ + if(id < 1000) + dbfilename = './server/json/charactermap.json'; + else + dbfilename = './server/json/tokenmap.json'; + var name = "test"; + const data = fs.readFileSync(dbfilename, 'utf8'); + const databases = JSON.parse(data); + databases.forEach(db => { + if(id == db.id){ + name = db.name + } + }); + + return name; +} + +function getJSONFromUID(uid){ + const data = fs.readFileSync(toytagFileName, 'utf8'); + const databases = JSON.parse(data); + var entry; + databases.forEach(db => { + if(db.uid==uid) + entry = db; + }); + return entry; +} + +function updatePadIndex(uid, index){ + console.log('Planning to set UID: ' + uid + ' to index ' + index); + const data = fs.readFileSync(toytagFileName, 'utf8'); + const databases = JSON.parse(data); + databases.forEach(db => { + if(uid == db.uid){ + db.index = index; + } + }); + fs.writeFileSync(toytagFileName, JSON.stringify(databases, null, 4), function(){ + console.log('Set UID: ' + uid + ' to index ' + index); + }) +} + +function getUIDFromIndex(index){ + const data = fs.readFileSync(toytagFileName, 'utf8'); + const databases = JSON.parse(data); + var uid; + databases.forEach(db => { + if(index == db.index){ + uid = db.uid; + } + }); + return uid; +} + +function writeJSONData(uid, datatype, data){ + console.log('Planning to set '+ datatype + ' of ' + uid + ' to ' + data); + const tags = fs.readFileSync(toytagFileName, 'utf8'); + const databases = JSON.parse(tags); + databases.forEach(db => { + if(uid == db.uid){ + db[datatype] = data; + return; + } + }); + fs.writeFileSync(toytagFileName, JSON.stringify(databases, null, 4), function(){ + console.log('Set '+ datatype + ' of ' + uid + ' to ' + data); + }) +} + +function initalizeToyTagsJSON(){ + const data = fs.readFileSync(toytagFileName, 'utf8'); + const databases = JSON.parse(data); + databases.forEach(db => { + db.index = "-1"; + }); + fs.writeFileSync(toytagFileName, JSON.stringify(databases, null, 4), function(){ + console.log("Initalized toytags.JSON"); + }) +} + +//When the game calls 'CMD_WRITE', writes the given data to the toytag in the top position. +tp.hook(tp.CMD_WRITE, (req, res) => { + var ind = req.payload[0]; + var page = req.payload[1]; + var data = req.payload.slice(2); + var uid = getUIDFromIndex('2'); + console.log('REQUEST (CMD_WRITE): index:', ind, 'page', page, 'data', data); + + if(page == 24 || page == 36){ + writeJSONData(uid,"id",data.readInt16LE(0)); + var name = getNameFromID(data.readInt16LE(0)); + writeJSONData(uid,"name",name); + writeJSONData(uid,"type","vehicle"); + //writeVehicleData(uid, "uid", tp.randomUID()) + } + else if(page == 23 || page == 35) + writeJSONData(uid, "vehicleUpgradesP23", data.readUInt32LE(0)); + else if (page == 25 || page == 37){ + writeJSONData(uid, "vehicleUpgradesP25", data.readUInt32LE(0)); + io.emit("refreshTokens"); + } + + res.payload = new Buffer('00', 'hex'); + var token = tp._tokens.find(t => t.index == ind); + if (token){ + req.payload.copy(token.token, 4 * page, 2, 6); + } + +}); + app.use(express.json()); +app.use(express.static(path.join(__dirname, 'server'))) + app.get('/', (request, response) => { - response.sendFile(path.join(__dirname, '/index.html')); + response.sendFile(path.join(__dirname, 'server/index.html')); }); app.post('/character', (request, response) => { - console.log('Placing character: ' + request.body.id); + console.log('Creating character: ' + request.body.id); var uid = tp.randomUID(); var character = createCharacter(request.body.id, uid); - tp.place(character, request.body.position, request.body.index, character.uid); - console.log('Character placed: ' + request.body.id); - response.send(uid); + var name = getNameFromID(request.body.id, "character"); + + console.log("name: " + name, " uid: " + character.uid, " id: " + character.id) + + fs.readFile(toytagFileName, 'utf8', (err, data) => { + if(err){ + console.log(err) + } + else{ + const tags = JSON.parse(data.toString()); + + tags.push({ + name: name, + id: character.id, + uid: character.uid, + index: "-1", + type: 'character', + vehicleUpgradesP23: 0, + vehicleUpgradesP25: 0 + }); + + fs.writeFile(toytagFileName, JSON.stringify(tags, null, 4), 'utf8', (err) => { + if (err) { + console.log(`Error writing file: ${err}`); + } else { + console.log(`File is written successfully!`); + } + }); + } + }) + + console.log('Character created: ' + request.body.id); + response.send(); }); +app.post('/characterPlace', (request, response) => { + console.log('Placing tag: ' + request.body.id); + var entry = getJSONFromUID(request.body.uid); + + console.log(entry.type); + + if(entry.type == "character"){ + var character = createCharacter(request.body.id, request.body.uid); + tp.place(character, request.body.position, request.body.index, character.uid); + console.log('Character tag: ' + request.body.id); + updatePadIndex(character.uid, request.body.index); + response.send(); + } + else{ + var vehicle = createVehicle(request.body.id,[entry.vehicleUpgradesP23, entry.vehicleUpgradesP25],request.body.uid); + tp.place(vehicle, request.body.position, request.body.index, vehicle.uid); + console.log('Vehicle tag: ' + request.body.id); + updatePadIndex(vehicle.uid, request.body.index); + response.send(); + } +}) + app.post('/vehicle', (request, response) => { console.log('Placing vehicle: ' + request.body.id); var uid = tp.randomUID(); var vehicle = createVehicle(request.body.id, [0xEFFFFFFF, 0xEFFFFFFF], uid); - tp.place(vehicle, request.body.position, request.body.index, vehicle.uid); - console.log('Vehicle placed: ' + request.body.id); - response.send(uid); -}); + var name = getNameFromID(request.body.id, "vehicle"); -app.put('/character', (request, response) => { - console.log('Changing character: "' + request.body.uid + '" to position ' + request.body.position + ', index ' + request.body.index); - var character = createCharacter(request.body.id, request.body.uid); - tp.place(character, request.body.position, request.body.index, character.uid); - console.log('Character changed: "' + request.body.uid + '" to position ' + request.body.position + ', index ' + request.body.index); -}); + console.log("name: " + name, " uid: " + vehicle.uid, " id: " + vehicle.id) + + fs.readFile(toytagFileName, 'utf8', (err, data) => { + if(err){ + console.log(err) + } + else{ + const tags = JSON.parse(data.toString()); + var entry = { + name: name, + id: request.body.id, + uid: vehicle.uid, + index: "-1", + type: 'vehicle', + vehicleUpgradesP23: 0xEFFFFFFF, + vehicleUpgradesP25: 0xEFFFFFFF + } -app.put('/vehicle', (request, response) => { - console.log('Changing vehicle: "' + request.body.uid + '" to position ' + request.body.position + ', index ' + request.body.index); - var vehicle = createVehicle(request.body.id, [0xEFFFFFFF, 0xEFFFFFFF], request.body.uid); - tp.place(vehicle, request.body.position, request.body.index, vehicle.uid); - console.log('Vehicle changed: "' + request.body.uid + '" to position ' + request.body.position + ', index ' + request.body.index); + console.log(entry) + tags.push(entry); + + fs.writeFile(toytagFileName, JSON.stringify(tags, null, 4), 'utf8', (err) => { + if (err) { + console.log(`Error writing file: ${err}`); + } else { + console.log(`File is written successfully!`); + } + }); + } + }) + console.log('Vehicle placed: ' + request.body.id); + response.send(uid); }); app.delete('/remove', (request, response) => { console.log('Removing item: ' + request.body.index); + console.log('DEBUG: pad-from-token: ', tp._tokens.filter(v => v.index == request.body.index)[0].pad); tp.remove(request.body.index); console.log('Item removed: ' + request.body.index); + updatePadIndex(request.body.uid, "-1"); response.send(true); }); -app.listen(80, () => console.log('Server running on port 80')); \ No newline at end of file +io.on('connection', (socket) => { + socket.on('deleteToken', (uid) => { + console.log('IO Recieved: Deleting entry '+ uid + ' from JSON'); + const tags = fs.readFileSync(toytagFileName, 'utf8'); + const databases = JSON.parse(tags); + var index = -1; + var i = 0; + databases.forEach(db => { + if(uid == db.uid){ + index = i; + return; + } + i++; + }); + console.log('Entry to delete: ', index) + if (index > -1) { + databases.splice(index, 1); + } + fs.writeFileSync(toytagFileName, JSON.stringify(databases, null, 4), function(){ + if (index > -1) + console.log('Token not found'); + else + console.log('Deleted ', uid, ' from JSON'); + }) + io.emit("refreshTokens"); + }); +}); + +server.listen(80, () => console.log('Server running on port 80')); From 0c64bd05e91180633ddaa7394cf53c3cc24c5f30 Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:05:23 -0500 Subject: [PATCH 06/21] Updated demo image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d99157..67ebc6d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Allows you to connect an emulated ToyPad to your PC or video-game console. - Mobile-friendly web interface ## Demo -![](https://i.imgur.com/Hg12EDL.jpg) +![](https://imgur.com/a/f2A6Op8) Video for Cemu emulator: https://www.youtube.com/watch?v=7CBa9u2ip-Y From cab099bf9c993ca0044ade8be77b71a029f1bceb Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:06:03 -0500 Subject: [PATCH 07/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67ebc6d..21b1b6e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Allows you to connect an emulated ToyPad to your PC or video-game console. - Mobile-friendly web interface ## Demo -![](https://imgur.com/a/f2A6Op8) +![](https://i.imgur.com/iyWVObT.png) Video for Cemu emulator: https://www.youtube.com/watch?v=7CBa9u2ip-Y From 7e5d64d093a84f719d1968fc3e6e3c19c33a3196 Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:06:54 -0500 Subject: [PATCH 08/21] Rename server/toytags.json to server/json/toytags.json --- server/{ => json}/toytags.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename server/{ => json}/toytags.json (100%) diff --git a/server/toytags.json b/server/json/toytags.json similarity index 100% rename from server/toytags.json rename to server/json/toytags.json From a1ffe66ef188a61edd08cf61e70c12735a58783d Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:07:42 -0500 Subject: [PATCH 09/21] Add files via upload --- server/json/charactermap.json | 88 ++++++++++++ server/json/tokenmap.json | 247 ++++++++++++++++++++++++++++++++++ server/json/upgrademap.json | 224 ++++++++++++++++++++++++++++++ 3 files changed, 559 insertions(+) create mode 100644 server/json/charactermap.json create mode 100644 server/json/tokenmap.json create mode 100644 server/json/upgrademap.json diff --git a/server/json/charactermap.json b/server/json/charactermap.json new file mode 100644 index 0000000..bf1d3bc --- /dev/null +++ b/server/json/charactermap.json @@ -0,0 +1,88 @@ +[ + { "id": 1, "name": "Batman", "world": "DC Comics" }, + { "id": 2, "name": "Gandalf", "world": "Lord of the Rings" }, + { "id": 3, "name": "Wyldstyle", "world": "The LEGO Movie" }, + { "id": 4, "name": "Aquaman", "world": "DC Comics" }, + { "id": 5, "name": "Bad Cop", "world": "The LEGO Movie" }, + { "id": 6, "name": "Bane", "world": "DC Comics" }, + { "id": 7, "name": "Bart", "world": "The Simpsons" }, + { "id": 8, "name": "Benny", "world": "The LEGO Movie" }, + { "id": 9, "name": "Chell", "world": "Portal 2" }, + { "id": 10, "name": "Cole", "world": "Ninjago" }, + { "id": 11, "name": "Cragger", "world": "Legends of Chima" }, + { "id": 12, "name": "Cyborg", "world": "DC Comics" }, + { "id": 13, "name": "Cyberman", "world": "Doctor Who" }, + { "id": 14, "name": "Doc Brown", "world": "Back to the Future" }, + { "id": 15, "name": "The Doctor", "world": "Doctor Who" }, + { "id": 16, "name": "Emmet", "world": "The LEGO Movie" }, + { "id": 17, "name": "Eris", "world": "Legends of Chima" }, + { "id": 18, "name": "Gimli", "world": "Lord of the Rings" }, + { "id": 19, "name": "Smeagol", "world": "Lord of the Rings" }, + { "id": 20, "name": "Harley Quinn", "world": "DC Comics" }, + { "id": 21, "name": "Homer", "world": "The Simpsons" }, + { "id": 22, "name": "Jay", "world": "Ninjago" }, + { "id": 23, "name": "Joker", "world": "DC Comics" }, + { "id": 24, "name": "Kai", "world": "Ninjago" }, + { "id": 25, "name": "ACU", "world": "Jurrasic Park" }, + { "id": 26, "name": "Gamer Kid", "world": "Midway Arcade" }, + { "id": 27, "name": "Krusty", "world": "The Simpsons" }, + { "id": 28, "name": "Laval", "world": "Legends of Chima" }, + { "id": 29, "name": "Legolas", "world": "Lord of the Rings" }, + { "id": 30, "name": "Lloyd", "world": "Ninjago" }, + { "id": 31, "name": "Marty Mcfly", "world": "Back to the Future" }, + { "id": 32, "name": "Nya", "world": "Ninjago" }, + { "id": 33, "name": "Owen", "world": "Jurrasic Park" }, + { "id": 34, "name": "Peter Venkman", "world": "Ghostbusters" }, + { "id": 35, "name": "Slimer", "world": "Ghostbusters" }, + { "id": 36, "name": "Scooby Doo", "world": "Scooby-Doo" }, + { "id": 37, "name": "SenseiWu", "world": "Ninjago" }, + { "id": 38, "name": "Shaggy", "world": "Scooby-Doo" }, + { "id": 39, "name": "Stay Puft", "world": "Ghostbusters" }, + { "id": 40, "name": "Superman", "world": "DC Comics" }, + { "id": 41, "name": "Unikitty", "world": "The LEGO Movie" }, + { "id": 42, "name": "Wicked Witch", "world": "Wizard of OZ" }, + { "id": 43, "name": "Superwoman", "world": "DC Comics" }, + { "id": 44, "name": "Zane", "world": "Ninjago" }, + { "id": 45, "name": "Green Arrow", "world": "DC Comics" }, + { "id": 46, "name": "Supergirl", "world": "DC Comics" }, + { "id": 47, "name": "Abby Yates", "world": "Ghostbuster 2016" }, + { "id": 48, "name": "Finn", "world": "Adventure Time" }, + { "id": 49, "name": "Ethan Hunt", "world": "Mission Impossible" }, + { "id": 50, "name": "Lumpy Space Princess", "world": "Adventure Time" }, + { "id": 51, "name": "Jake", "world": "Adventure Time" }, + { "id": 52, "name": "Harry Potter", "world": "Harry Potter" }, + { "id": 53, "name": "Voldemort", "world": "Harry Potter" }, + { "id": 54, "name": "Michael Knight", "world":"Knight Rider"}, + { "id": 55, "name": "B.A. Baracus", "world": "A-Team" }, + { "id": 56, "name": "Newt", "world": "Fantastic Beasts" }, + { "id": 57, "name": "Sonic", "world": "Sonic" }, + { "id": 58, "name": "Future Update", "world": "N/A" }, + { "id": 59, "name": "Gizmo", "world": "Gremlins" }, + { "id": 60, "name": "Stripe", "world": "Gremlins" }, + { "id": 61, "name": "E.T.", "world": "E.T." }, + { "id": 62, "name": "Tina", "world": "Fantastic Beasts" }, + { "id": 63, "name": "Marceline", "world": "Adventure Time" }, + { "id": 64, "name": "Bat Girl", "world":"LEGO Batman Movie"}, + { "id": 65, "name": "Robin", "world":"LEGO Batman Movie"}, + { "id": 66, "name": "Sloth", "world":"Goonies" }, + { "id": 67, "name": "Hermione Granger", "world":"Harry Potter" }, + { "id": 68, "name": "Chase McCain", "world":"LEGO City" }, + { "id": 69, "name": "Excalibur Batman","world":"lego Batman Movie"}, + { "id": 70, "name": "Raven", "world":"Teen Titans Go"}, + { "id": 71, "name": "Beast Boy", "world":"Teen Titans Go"}, + { "id": 72, "name": "Beetle Juice", "world":"Beetle Juice"}, + { "id": 73, "name": "Lord Vortech", "world":"Lego Dimensions"}, + { "id": 74, "name": "Blossom", "world":"Power Puff Girls"}, + { "id": 75, "name": "Bubbles", "world":"Power Puff Girls"}, + { "id": 76, "name": "Buttercup", "world":"Power Puff Girls"}, + { "id": 77, "name": "Star Fire", "world":"Teen Titans Go"}, + { "id": 78, "name": "Test 15", "world":"15"}, + { "id": 79, "name": "Test 16", "world":"16"}, + { "id": 80, "name": "Test 17", "world":"17"}, + { "id": 81, "name": "Test 16", "world":"18"}, + { "id": 82, "name": "Test 17", "world":"19"}, + { "id": 83, "name": "Test 18", "world":"20"}, + { "id": 768, "name": "Unknown", "world":"Unknown"}, + { "id": 769, "name": "Supergirl Red Lantern", "world":"Dc Comics"}, + { "id": 770, "name": "Unknown", "world":"Unknown"} +] diff --git a/server/json/tokenmap.json b/server/json/tokenmap.json new file mode 100644 index 0000000..7ea717c --- /dev/null +++ b/server/json/tokenmap.json @@ -0,0 +1,247 @@ +[ + { "id": 1000, "upgrademap": 0, "rebuild": 0, "name": "Police Car" }, + { "id": 1001, "upgrademap": 0, "rebuild": 1, "name": "* Aerial Squad Car" }, + { "id": 1002, "upgrademap": 0, "rebuild": 2, "name": "* Missile Striker" }, + { "id": 1003, "upgrademap": 0, "rebuild": 0, "name": "Gravity Sprinter" }, + { "id": 1004, "upgrademap": 0, "rebuild": 1, "name": "* Street Shredder" }, + { "id": 1005, "upgrademap": 0, "rebuild": 2, "name": "* Sky Clobberer" }, + { "id": 1006, "upgrademap": 0, "rebuild": 0, "name": "Batmobile" }, + { "id": 1007, "upgrademap": 0, "rebuild": 1, "name": "* Batblaster" }, + { "id": 1008, "upgrademap": 0, "rebuild": 2, "name": "* Sonic Batray" }, + { "id": 1009, "upgrademap": 0, "rebuild": 0, "name": "Benny's Spaceship" }, + { "id": 1010, "upgrademap": 0, "rebuild": 1, "name": "* Lasercraft" }, + { "id": 1011, "upgrademap": 0, "rebuild": 2, "name": "* The Annihilator" }, + { "id": 1012, "upgrademap": 0, "rebuild": 0, "name": "Delorean" }, + { "id": 1013, "upgrademap": 0, "rebuild": 1, "name": "* Ultra Time Machine" }, + { "id": 1014, "upgrademap": 0, "rebuild": 2, "name": "* Electric Time Machine" }, + { "id": 1015, "upgrademap": 0, "rebuild": 0, "name": "Hoverboard" }, + { "id": 1016, "upgrademap": 0, "rebuild": 1, "name": "* Cyclone Board" }, + { "id": 1017, "upgrademap": 0, "rebuild": 2, "name": "* Ultimate Hoverjet" }, + { "id": 1018, "upgrademap": 0, "rebuild": 0, "name": "Eagle interceptor" }, + { "id": 1019, "upgrademap": 0, "rebuild": 1, "name": "* Eagle Skyblazer" }, + { "id": 1020, "upgrademap": 0, "rebuild": 2, "name": "* Eagle Swoop Diver" }, + { "id": 1021, "upgrademap": 0, "rebuild": 0, "name": "Cragger's Fireship" }, + { "id": 1022, "upgrademap": 0, "rebuild": 1, "name": "* Croc Command Sub" }, + { "id": 1023, "upgrademap": 0, "rebuild": 2, "name": "* Swamp Skimmer" }, + { "id": 1024, "upgrademap": 0, "rebuild": 0, "name": "Cyber Guard" }, + { "id": 1025, "upgrademap": 0, "rebuild": 1, "name": "* Cyber-Wrecker" }, + { "id": 1026, "upgrademap": 0, "rebuild": 2, "name": "* Laser Robot Walker" }, + { "id": 1027, "upgrademap": 0, "rebuild": 0, "name": "K9" }, + { "id": 1028, "upgrademap": 0, "rebuild": 1, "name": "* K9 Ruff Rover" }, + { "id": 1029, "upgrademap": 0, "rebuild": 2, "name": "* K9 Laser Cutter" }, + { "id": 1030, "upgrademap": 0, "rebuild": 0, "name": "TARDIS" }, + { "id": 1031, "upgrademap": 0, "rebuild": 1, "name": "* Laser-Pulse TARDIS" }, + { "id": 1032, "upgrademap": 0, "rebuild": 2, "name": "* Energy-Burst TARDIS" }, + { "id": 1033, "upgrademap": 0, "rebuild": 0, "name": "Emmet's Excavator" }, + { "id": 1034, "upgrademap": 0, "rebuild": 1, "name": "* The Destroydozer" }, + { "id": 1035, "upgrademap": 0, "rebuild": 2, "name": "* Destruct-o-Mech" }, + { "id": 1036, "upgrademap": 0, "rebuild": 0, "name": "Winged Monkey" }, + { "id": 1037, "upgrademap": 0, "rebuild": 1, "name": "* Battle Monkey" }, + { "id": 1038, "upgrademap": 0, "rebuild": 2, "name": "* Commander Monkey" }, + { "id": 1039, "upgrademap": 0, "rebuild": 0, "name": "Axe Chariot" }, + { "id": 1040, "upgrademap": 0, "rebuild": 1, "name": "* Axe Hurler" }, + { "id": 1041, "upgrademap": 0, "rebuild": 2, "name": "* Soaring Chariot" }, + { "id": 1042, "upgrademap": 0, "rebuild": 0, "name": "Shelob the Great" }, + { "id": 1043, "upgrademap": 0, "rebuild": 1, "name": "* 8-Legged Stalker" }, + { "id": 1044, "upgrademap": 0, "rebuild": 2, "name": "* Poison Slinger" }, + { "id": 1045, "upgrademap": 0, "rebuild": 0, "name": "Homer's Car" }, + { "id": 1046, "upgrademap": 0, "rebuild": 1, "name": "* Homercraft" }, + { "id": 1047, "upgrademap": 0, "rebuild": 2, "name": "* SubmaHomer" }, + { "id": 1048, "upgrademap": 0, "rebuild": 0, "name": "Taunt-o-Vision" }, + { "id": 1049, "upgrademap": 0, "rebuild": 1, "name": "* Blast Cam" }, + { "id": 1050, "upgrademap": 0, "rebuild": 2, "name": "* The MechaHomer" }, + { "id": 1051, "upgrademap": 0, "rebuild": 0, "name": "Velociraptor" }, + { "id": 1052, "upgrademap": 0, "rebuild": 1, "name": "* Spike Attack Raptor" }, + { "id": 1053, "upgrademap": 0, "rebuild": 2, "name": "* Venom Raptor" }, + { "id": 1054, "upgrademap": 0, "rebuild": 0, "name": "Gyro Sphere" }, + { "id": 1055, "upgrademap": 0, "rebuild": 1, "name": "* Sonic Beam Gyrosphere" }, + { "id": 1056, "upgrademap": 0, "rebuild": 2, "name": "* Speed Boost Gyrosphere" }, + { "id": 1057, "upgrademap": 0, "rebuild": 0, "name": "Clown Bike" }, + { "id": 1058, "upgrademap": 0, "rebuild": 1, "name": "* Cannon Bike" }, + { "id": 1059, "upgrademap": 0, "rebuild": 2, "name": "* Anti-Gravity Rocket Bike" }, + { "id": 1060, "upgrademap": 0, "rebuild": 0, "name": "Mighty Lion Rider" }, + { "id": 1061, "upgrademap": 0, "rebuild": 1, "name": "* Lion Blazer" }, + { "id": 1062, "upgrademap": 0, "rebuild": 2, "name": "* Fire Lion" }, + { "id": 1063, "upgrademap": 0, "rebuild": 0, "name": "Arrow Launcher" }, + { "id": 1064, "upgrademap": 0, "rebuild": 1, "name": "* Seeking Shooter" }, + { "id": 1065, "upgrademap": 0, "rebuild": 2, "name": "* Triple Ballista" }, + { "id": 1066, "upgrademap": 0, "rebuild": 0, "name": "Mystery Machine" }, + { "id": 1067, "upgrademap": 0, "rebuild": 1, "name": "* Mystery Tow" }, + { "id": 1068, "upgrademap": 0, "rebuild": 2, "name": "* Mystery Monster" }, + { "id": 1069, "upgrademap": 0, "rebuild": 0, "name": "Boulder Bomber" }, + { "id": 1070, "upgrademap": 0, "rebuild": 1, "name": "* Boulder Blaster" }, + { "id": 1071, "upgrademap": 0, "rebuild": 2, "name": "* Cyclone Jet" }, + { "id": 1072, "upgrademap": 0, "rebuild": 0, "name": "Storm Fighter" }, + { "id": 1073, "upgrademap": 0, "rebuild": 1, "name": "* Lightning Jet" }, + { "id": 1074, "upgrademap": 0, "rebuild": 2, "name": "* Electro-Shooter" }, + { "id": 1075, "upgrademap": 0, "rebuild": 0, "name": "Blade Bike" }, + { "id": 1076, "upgrademap": 0, "rebuild": 1, "name": "* Flying Fire Bike" }, + { "id": 1077, "upgrademap": 0, "rebuild": 2, "name": "* Blades of Fire" }, + { "id": 1078, "upgrademap": 0, "rebuild": 0, "name": "Samurai Mech" }, + { "id": 1079, "upgrademap": 0, "rebuild": 1, "name": "* Samurai Shooter" }, + { "id": 1080, "upgrademap": 0, "rebuild": 2, "name": "* Soaring Samurai Mech" }, + { "id": 1081, "upgrademap": 0, "rebuild": 0, "name": "Companion Cube" }, + { "id": 1082, "upgrademap": 0, "rebuild": 1, "name": "* Laser Deflector" }, + { "id": 1083, "upgrademap": 0, "rebuild": 2, "name": "* Gold Heart Emitter" }, + { "id": 1084, "upgrademap": 0, "rebuild": 0, "name": "Sentry Turret" }, + { "id": 1085, "upgrademap": 0, "rebuild": 1, "name": "* Turret Striker" }, + { "id": 1086, "upgrademap": 0, "rebuild": 2, "name": "* Flying Turret Carrier" }, + { "id": 1087, "upgrademap": 0, "rebuild": 0, "name": "Scooby Snack" }, + { "id": 1088, "upgrademap": 0, "rebuild": 1, "name": "* Scooby Fire Snack" }, + { "id": 1089, "upgrademap": 0, "rebuild": 2, "name": "* Scooby Ghost Snack" }, + { "id": 1090, "upgrademap": 0, "rebuild": 0, "name": "Cloud Cukko Car" }, + { "id": 1091, "upgrademap": 0, "rebuild": 1, "name": "* X-Stream Soaker" }, + { "id": 1092, "upgrademap": 0, "rebuild": 2, "name": "* Rainbow Cannon" }, + { "id": 1093, "upgrademap": 0, "rebuild": 0, "name": "Invisible Jet" }, + { "id": 1094, "upgrademap": 0, "rebuild": 1, "name": "* Stealth Laser Shooter" }, + { "id": 1095, "upgrademap": 0, "rebuild": 2, "name": "* Torpedo Bomber" }, + { "id": 1096, "upgrademap": 0, "rebuild": 0, "name": "Ninja Copter" }, + { "id": 1097, "upgrademap": 0, "rebuild": 1, "name": "* Glaciator" }, + { "id": 1098, "upgrademap": 0, "rebuild": 2, "name": "* Freeze Fighter" }, + { "id": 1099, "upgrademap": 0, "rebuild": 0, "name": "Traveling Time Train" }, + { "id": 1100, "upgrademap": 0, "rebuild": 1, "name": "* (Traveling Time Train - rebuilt 1)" }, + { "id": 1101, "upgrademap": 0, "rebuild": 2, "name": "* (Traveling Time Train - rebuilt 2)" }, + { "id": 1102, "upgrademap": 0, "rebuild": 0, "name": "Aqua Watercraft" }, + { "id": 1103, "upgrademap": 0, "rebuild": 1, "name": "* (Aqua Watercraft - rebuilt 1)" }, + { "id": 1104, "upgrademap": 0, "rebuild": 2, "name": "* (Aqua Watercraft - rebuilt 2)" }, + { "id": 1105, "upgrademap": 0, "rebuild": 0, "name": "Drill Driver" }, + { "id": 1106, "upgrademap": 0, "rebuild": 1, "name": "* (Drill Driver - rebuilt 1)" }, + { "id": 1107, "upgrademap": 0, "rebuild": 2, "name": "* (Drill Driver - rebuilt 2)" }, + { "id": 1108, "upgrademap": 0, "rebuild": 0, "name": "Quinn-mobile" }, + { "id": 1109, "upgrademap": 0, "rebuild": 1, "name": "* (Quinn-mobile - rebuilt 1)" }, + { "id": 1110, "upgrademap": 0, "rebuild": 2, "name": "* (Quinn-mobile - rebuilt 2)" }, + { "id": 1111, "upgrademap": 0, "rebuild": 0, "name": "The Jokers Chopper" }, + { "id": 1112, "upgrademap": 0, "rebuild": 1, "name": "* (The Jokers Chopper - rebuilt 1)" }, + { "id": 1113, "upgrademap": 0, "rebuild": 2, "name": "* (The Jokers Chopper - rebuilt 2)" }, + { "id": 1114, "upgrademap": 0, "rebuild": 0, "name": "Hover Pod" }, + { "id": 1115, "upgrademap": 0, "rebuild": 1, "name": "* (Hover Pod - rebuilt 1)" }, + { "id": 1116, "upgrademap": 0, "rebuild": 2, "name": "* (Hover Pod - rebuilt 2)" }, + { "id": 1117, "upgrademap": 0, "rebuild": 0, "name": "Dalek" }, + { "id": 1118, "upgrademap": 0, "rebuild": 1, "name": "* (Dalek - rebuilt 1)" }, + { "id": 1119, "upgrademap": 0, "rebuild": 2, "name": "* (Dalek - rebuilt 2)" }, + { "id": 1120, "upgrademap": 0, "rebuild": 0, "name": "Ecto-1" }, + { "id": 1121, "upgrademap": 0, "rebuild": 1, "name": "* (Ecto-1 - rebuilt 1)" }, + { "id": 1122, "upgrademap": 0, "rebuild": 2, "name": "* (Ecto-1 - rebuilt 2)" }, + { "id": 1123, "upgrademap": 0, "rebuild": 0, "name": "Ghost Trap" }, + { "id": 1124, "upgrademap": 0, "rebuild": 1, "name": "* (Ghost Trap - rebuilt 1)" }, + { "id": 1125, "upgrademap": 0, "rebuild": 2, "name": "* (Ghost Trap - rebuilt 2)" }, + { "id": 1126, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1127, "upgrademap": 0, "rebuild": 1, "name": "unknown" }, + { "id": 1128, "upgrademap": 0, "rebuild": 2, "name": "unknown" }, + { "id": 1129, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1130, "upgrademap": 0, "rebuild": 1, "name": "unknown" }, + { "id": 1131, "upgrademap": 0, "rebuild": 2, "name": "unknown" }, + { "id": 1132, "upgrademap": 0, "rebuild": 0, "name": "Llyod's Golden Dragon" }, + { "id": 1133, "upgrademap": 0, "rebuild": 1, "name": "* (Golden Dragon - rebuilt 1)" }, + { "id": 1134, "upgrademap": 0, "rebuild": 2, "name": "* (Golden Dragon - rebuilt 2)" }, + { "id": 1135, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1136, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1137, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1138, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1139, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1140, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1141, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1142, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1143, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1144, "upgrademap": 0, "rebuild": 0, "name": "Mega Flight Dragon" }, + { "id": 1145, "upgrademap": 0, "rebuild": 1, "name": "* (Mega Flight Dragon - rebuilt 1)" }, + { "id": 1146, "upgrademap": 0, "rebuild": 2, "name": "* (Mega Flight Dragon - rebuilt 2)" }, + { "id": 1147, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1148, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1149, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1150, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1151, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1152, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1153, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1154, "upgrademap": 0, "rebuild": 0, "name": "unknown" }, + { "id": 1155, "upgrademap": 0, "rebuild": 0, "name": "Flying White Dragon" }, + { "id": 1156, "upgrademap": 0, "rebuild": 1, "name": "* Golden Fire Dragon" }, + { "id": 1157, "upgrademap": 0, "rebuild": 2, "name": "* Ultra Destruction Dragon" }, + { "id": 1158, "upgrademap": 0, "rebuild": 0, "name": "Arcade Machine" }, + { "id": 1159, "upgrademap": 0, "rebuild": 1, "name": "* 8-bit Shooter" }, + { "id": 1160, "upgrademap": 0, "rebuild": 2, "name": "* The Pixelator" }, + { "id": 1161, "upgrademap": 0, "rebuild": 0, "name": "G-61555 Spy Hunter" }, + { "id": 1162, "upgrademap": 0, "rebuild": 1, "name": "* The Interdiver" }, + { "id": 1163, "upgrademap": 0, "rebuild": 2, "name": "* Aerial Spyhunter" }, + { "id": 1164, "upgrademap": 0, "rebuild": 0, "name": "Slime Shooter" }, + { "id": 1165, "upgrademap": 0, "rebuild": 1, "name": "* Slime Exploder" }, + { "id": 1166, "upgrademap": 0, "rebuild": 2, "name": "* Slime Streamer" }, + { "id": 1167, "upgrademap": 0, "rebuild": 0, "name": "Terror Dog" }, + { "id": 1168, "upgrademap": 0, "rebuild": 1, "name": "* Terror Dog Destroyer" }, + { "id": 1169, "upgrademap": 0, "rebuild": 2, "name": "* Soaring Terror Dog" }, + { "id": 1170, "upgrademap": 0, "rebuild": 0, "name": "Ancient War Elephant" }, + { "id": 1171, "upgrademap": 0, "rebuild": 1, "name": "* Cosmic Squid" }, + { "id": 1172, "upgrademap": 0, "rebuild": 2, "name": "* Psychic Submarine" }, + { "id": 1173, "upgrademap": 0, "rebuild": 0, "name": "BMO" }, + { "id": 1174, "upgrademap": 0, "rebuild": 1, "name": "* DOGMO" }, + { "id": 1175, "upgrademap": 0, "rebuild": 2, "name": "* SNAKEMO" }, + { "id": 1176, "upgrademap": 0, "rebuild": 0, "name": "Jakemoblie" }, + { "id": 1177, "upgrademap": 0, "rebuild": 1, "name": "* Snail Dude Jake" }, + { "id": 1178, "upgrademap": 0, "rebuild": 2, "name": "* Hover Jake" }, + { "id": 1179, "upgrademap": 0, "rebuild": 0, "name": "Lumpy Car" }, + { "id": 1180, "upgrademap": 0, "rebuild": 1, "name": "* Lumpy Truck" }, + { "id": 1181, "upgrademap": 0, "rebuild": 2, "name": "* Lumpy Land Whale" }, + { "id": 1182, "upgrademap": 0, "rebuild": 0, "name": "Lunatic Amp" }, + { "id": 1183, "upgrademap": 0, "rebuild": 1, "name": "* Lunatic Amp 2" }, + { "id": 1184, "upgrademap": 0, "rebuild": 2, "name": "* Lunatic Amp 3" }, + { "id": 1185, "upgrademap": 0, "rebuild": 0, "name": "B.A.s Van" }, + { "id": 1186, "upgrademap": 0, "rebuild": 1, "name": "* Fool Smasher" }, + { "id": 1187, "upgrademap": 0, "rebuild": 2, "name": "* The Pain Plane" }, + { "id": 1188, "upgrademap": 0, "rebuild": 0, "name": "Phone Home" }, + { "id": 1189, "upgrademap": 0, "rebuild": 1, "name": "* Phone Home 2" }, + { "id": 1190, "upgrademap": 0, "rebuild": 2, "name": "* Phone Home 3" }, + { "id": 1191, "upgrademap": 0, "rebuild": 0, "name": "Niffler" }, + { "id": 1192, "upgrademap": 0, "rebuild": 1, "name": "* Niffler 2" }, + { "id": 1193, "upgrademap": 0, "rebuild": 2, "name": "* Niffler 3" }, + { "id": 1194, "upgrademap": 0, "rebuild": 0, "name": "Swooping Evil" }, + { "id": 1195, "upgrademap": 0, "rebuild": 1, "name": "* Swooping Evil 2" }, + { "id": 1196, "upgrademap": 0, "rebuild": 2, "name": "* Swooping Evil 3" }, + { "id": 1197, "upgrademap": 0, "rebuild": 0, "name": "Ecto 1" }, + { "id": 1198, "upgrademap": 0, "rebuild": 1, "name": "* Ectozer" }, + { "id": 1199, "upgrademap": 0, "rebuild": 2, "name": "* Ecto1 Rebuild 2" }, + { "id": 1200, "upgrademap": 0, "rebuild": 2, "name": "Flash 'n' Finish" }, + { "id": 1201, "upgrademap": 0, "rebuild": 0, "name": "* Rampage Record Player" }, + { "id": 1202, "upgrademap": 0, "rebuild": 1, "name": "* Stripe's Throne" }, + { "id": 1203, "upgrademap": 0, "rebuild": 2, "name": "R.C. Racer" }, + { "id": 1204, "upgrademap": 0, "rebuild": 0, "name": "* Gadet-o-matic" }, + { "id": 1205, "upgrademap": 0, "rebuild": 1, "name": "* Scarlet Scorpion" }, + { "id": 1206, "upgrademap": 0, "rebuild": 0, "name": "Hogwarts Express" }, + { "id": 1207, "upgrademap": 0, "rebuild": 2, "name": "* Soaring Steam Plane" }, + { "id": 1208, "upgrademap": 0, "rebuild": 1, "name": "* Steam Warrior" }, + { "id": 1209, "upgrademap": 0, "rebuild": 2, "name": "Enchanted Car" }, + { "id": 1210, "upgrademap": 0, "rebuild": 0, "name": "* Shark Sub" }, + { "id": 1211, "upgrademap": 0, "rebuild": 1, "name": "* Monstrous Mouth" }, + { "id": 1212, "upgrademap": 0, "rebuild": 2, "name": "IMF Scrambler" }, + { "id": 1213, "upgrademap": 0, "rebuild": 0, "name": "* Shock Cycle" }, + { "id": 1214, "upgrademap": 0, "rebuild": 1, "name": "* IMF Covert Jet" }, + { "id": 1215, "upgrademap": 0, "rebuild": 2, "name": "IMF Sport Car" }, + { "id": 1216, "upgrademap": 0, "rebuild": 0, "name": "* IMF Tank" }, + { "id": 1217, "upgrademap": 0, "rebuild": 1, "name": "* IMF-Splorer" }, + { "id": 1218, "upgrademap": 0, "rebuild": 0, "name": "Sonic Speedster" }, + { "id": 1219, "upgrademap": 0, "rebuild": 1, "name": "* Sonic Speedster 2" }, + { "id": 1220, "upgrademap": 0, "rebuild": 2, "name": "* Sonic Speedster 3" }, + { "id": 1221, "upgrademap": 0, "rebuild": 0, "name": "The Tornado" }, + { "id": 1222, "upgrademap": 0, "rebuild": 1, "name": "* The Tornado 1" }, + { "id": 1223, "upgrademap": 0, "rebuild": 2, "name": "* The Tornado 2" }, + { "id": 1224, "upgrademap": 0, "rebuild": 0, "name": "K.I.T.T" }, + { "id": 1225, "upgrademap": 0, "rebuild": 1, "name": "* K.I.T.T. JET" }, + { "id": 1226, "upgrademap": 0, "rebuild": 2, "name": "* GOLIATH ARMORED SEMI" }, + { "id": 1227, "upgrademap": 0, "rebuild": 0, "name": "Police" }, + { "id": 1228, "upgrademap": 0, "rebuild": 1, "name": "* Hovercraft" }, + { "id": 1229, "upgrademap": 0, "rebuild": 2, "name": "* Plane" }, + { "id": 1230, "upgrademap": 0, "rebuild": 0, "name": "BIONIC STEED" }, + { "id": 1231, "upgrademap": 0, "rebuild": 1, "name": "* BAT RAPTOR" }, + { "id": 1232, "upgrademap": 0, "rebuild": 2, "name": "* ULTRA BAT" }, + { "id": 1233, "upgrademap": 0, "rebuild": 0, "name": "BAT WING" }, + { "id": 1234, "upgrademap": 0, "rebuild": 1, "name": "* BLACK THUNDER" }, + { "id": 1235, "upgrademap": 0, "rebuild": 2, "name": "* BAT TANK" }, + { "id": 1236, "upgrademap": 0, "rebuild": 0, "name": "Skeleton Organ" }, + { "id": 1237, "upgrademap": 0, "rebuild": 1, "name": "* Jukebox" }, + { "id": 1238, "upgrademap": 0, "rebuild": 2, "name": "* Skele-Turkey" }, + { "id": 1239, "upgrademap": 0, "rebuild": 0, "name": "Pirate Ship" }, + { "id": 1240, "upgrademap": 0, "rebuild": 1, "name": "* Fanged Fortune" }, + { "id": 1241, "upgrademap": 0, "rebuild": 2, "name": "* Inferno" }, + { "id": 1242, "upgrademap": 0, "rebuild": 0, "name": "Buckbeak" }, + { "id": 1243, "upgrademap": 0, "rebuild": 1, "name": "* Giant Owl" }, + { "id": 1244, "upgrademap": 0, "rebuild": 2, "name": "* Fierce Falcon" } +] diff --git a/server/json/upgrademap.json b/server/json/upgrademap.json new file mode 100644 index 0000000..1edc43a --- /dev/null +++ b/server/json/upgrademap.json @@ -0,0 +1,224 @@ +{ + "speed":["Model Boost A"] +} + +"Speed":[{ + "name":"Model Boost Ability", + "desc":"Inital model speed has a boost for a short time.", + "cost":"cost":15000 +},{ + "name":"Boost Buff 1", + "desc":"Increased boost speed", + "cost":"15,000", +},{ + "name":"Movement Speed Buff 1", + "desc":"Increased model movement", + "cost":"15,000", +},{ + "name":"Jump Ability", + "desc":"Model can Jump ", + "cost":"20,000", +},{ + "name":"Boost Buff 2", + "desc":"Increased boost speed", + "cost":"20,000 Studs", +},{ + "name":"Movement Speed Buff 2", + "desc":"Increased model movement.", + "cost":"20,000 Studs", +}] + +"Power":[{ + "name":"Color Changing Bolts Ability", + "desc":"Gives the ability to fire tintable bolts", + "cost":"15,000", +},{ + "name":"Fire Speed Buff 1", + "desc":"Increased weapon fire rate", + "cost":"15,000 Studs", +},{ + "name":"Armor Buff", + "desc":"Model upgraded to have5 Armor Hearts ", + "cost":"15,000", +},{ + "name":"Instant Tow Bar", + "desc":"Tow Bars complete instantnly with vehicles that have them ", + "cost":"20,000", +},{ + "name":"Fire Speed Buff 2", + "desc":"Increased weapon fire rate", + "cost":"20,000 Studs", +},{ + "name":"Armor Buff", + "desc":"Model upgraded to have 6 armor hearts.", + "cost":"20,000 Studs", +}] +"Weapons":[{ + "name":"Mine Weapon", + "desc":"Mines are deployed", + "cost":"15,000 Studs", +},{ + "name":"Explode Ability", + "desc":"Model explodes after a count down (toggle off/on)", + "cost":"15,000 Studs", +}] +"Extras":[{ + "name":"Bolt Color Chooser", + "desc":"Allows you to tint bolt colors", + "cost":"15,000 Studs", +},{ + "name":"Enemy Hearts Ability", + "desc":"Enemy characters now drop hearts ", + "cost":"15,000", +},{ + "name":"Regenerate Armor Ability", + "desc":"Hearts on the model regenerate", + "cost":"15,000 Studs", +}] +"Colors":[{ + "name":"Skin 1", + "desc":"Changes the appearance of the model", + "cost":"15,000 Studs", +},{ + "name":"Tire Color Chooser", + "desc":"Allows you to pick tire colors", + "cost":"15,000 Studs", +},{ + "name":"Flame Color Choser", + "desc":"Choose exhaust flame color.", + "cost":"15,000 Studs", +}], +"Weapons", +{ + "name":"Spinning Weapon", + "desc":"Spinning objects are now fired to destroy distant threats ", + "cost":"20,000", +}, +"Weapons", +{ + "name":"Electric Shield", + "desc":"An electric field surrounds the model", + "cost":"20,000 Studs", +}, +"Extras 1", +{ + "name":"", + "desc":"", + "cost":"15,000 Studs", +}, +"Extras", +{ + "name":"Guardian Ability", + "desc":"Model will protect you by attacking enemies (needs an active weapon selected).", + "cost":"20,000 Studs", +}, +"Extras", +{ + "name":"Character Studs Ability", + "desc":"Enemies now give off lots of studs when they are defeated.", + "cost":"20,000 Studs", +}, +"Colors 1", +{ + "name":"", + "desc":" ", + "cost":"15,000 Studs", +} +"", +"Colors 2", +{ + "name":"", + "desc":"", + "cost":"50,000", +}, +"Colors 3", +{ + "name":"", + "desc":"", + "cost":"50,000", +}, +"Speed 1", +{ + "name":"Instant Accelerator Switch Ability", + "desc":"Accelerator Switches complete instantly when driven on.", + "cost":"50,000 Studs", +}, +"Speed 2", +{ + "name":"Boost Buff 3", + "desc":"Increased boost speed", + "cost":"50,000 Studs", +}, +"Speed 3", +{ + "name":"Movement Speed Buff 3", + "desc":"Increased model movement.", + "cost":"50,000 Studs", +}, +"Power 1", +{ + "name":"Instant Sonar Smash", + "desc":"Sonar smashes complete instantly rather than shaking.", + "cost":"50,000", +}, +"Power 2", +"", +{ + "name":"", + "desc":"", + "cost":"50,000", +}, +"Power 3", +{ + "name":"", + "desc":"", + "cost":"50,000", +}, +"Weapons 1", +{ + "name":"Swarm Weapon", + "desc":"Objects swarm around the model.", + "cost":"50,000 Studs", +}, +"Weapons 3", +{ + "name":"Speaker Attack Ability", + "desc":"Music makes nearby enemies dance for a set time.", + "cost":"50,000 Studs", +}, +"Extras 1", +{ + "name":"Engine Sound Chooser", + "desc":"Change the engine sounds.", + "cost":"50,000 Studs", +}, +"Extras 2", +{ + "name":"Armor Hearts Multiplier", + "desc":"Heart multiplier for player's health.", + "cost":"50,000 Studs", +}, +"Extras 3", +{ + "name":"Stud Magnet Ability", + "desc":"Studs are attracted to the vehicle", + "cost":"50,000 Studs", +}, +"Colors 1", +{ + "name":"Skin 3", + "desc":"Changes the appearance of the model.", + "cost":"50,000 Studs", +}, +"Colors 2", +{ + "name":"Model Color Chooser", + "desc":"Allows you to pick model color.", + "cost":"50,000 Studs", +}, +"Colors 3", +{ + "name":"", + "desc":"", + "cost":"50,000", +} \ No newline at end of file From ff59fc16570d9b448fec314cbe506ad9e9c43bab Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:08:41 -0500 Subject: [PATCH 10/21] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 21b1b6e..0ffb2c6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ Allows you to connect an emulated ToyPad to your PC or video-game console. - Supports most if not all of the consoles the game is available for - Confirmed working on Cemu and real Wii U - Can be configured easily by following the instructions below -- Mobile-friendly web interface ## Demo ![](https://i.imgur.com/iyWVObT.png) From 9612d0aeb38f84ce43417ea100735dc23d8d1ea2 Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:09:38 -0500 Subject: [PATCH 11/21] Added socket.io --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5293a53..8b07675 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "license": "ISC", "dependencies": { "express": "^4.17.1", - "node-ld": "^1.1.6" + "node-ld": "^1.1.6", + "socket.io": "^4.2.0" } } From eb1edf04d88aa7e57a8ba54ad03a04fb98c43f54 Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:53:29 -0500 Subject: [PATCH 12/21] Added comments --- index.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 60fdb7e..9086f4b 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,7 @@ const fs = require('fs'); const path = require('path'); const { DH_CHECK_P_NOT_PRIME } = require('constants'); +//Setup Webserver const express = require('express'); const app = express(); const http = require('http'); @@ -17,26 +18,36 @@ const server = http.createServer(app); const { Server } = require("socket.io"); const io = new Server(server); +//File where tag info will be saved const toytagFileName = './server/json/toytags.json'; var tp = new ld.ToyPadEmu() tp.registerDefaults() -initalizeToyTagsJSON(); - +initalizeToyTagsJSON(); //Run in case there were any leftovers from a previous run. + +//Create a token JSON object from provided vehicle data +/* Vehicle Data Explained: + * All data is transfered through a series of buffers. The data from these buffers needs to written to specific points (pages) in the token's + * buffer for it to be read properly. + * + * For vehicles: + * Page 24 is the ID of the vehicle + * Pages 23 & 25 are the upgrade data + */ function createVehicle(id, upgrades, uid){ upgrades = upgrades || [0,0]; var token = new Buffer(180); token.fill(0); token.uid = uid; - console.log(upgrades); token.writeUInt32LE(upgrades[0],0x23*4); token.writeUInt16LE(id,0x24*4); token.writeUInt32LE(upgrades[1],0x25*4); - token.writeUInt16BE(1,0x26*4); + token.writeUInt16BE(1,0x26*4); //Page 26 is used for verification of somekind. return token; } +//Create a token JSON object from provided character data function createCharacter(id, uid){ var token = new Buffer(180) token.fill(0); // Game really only cares about 0x26 being 0 and D4 returning an ID @@ -45,6 +56,7 @@ function createCharacter(id, uid){ return token; } +//This finds a character or vehicles name from the ID provided. function getNameFromID(id){ if(id < 1000) dbfilename = './server/json/charactermap.json'; @@ -62,6 +74,7 @@ function getNameFromID(id){ return name; } +//This finds and returns an JSON entry from toytags.json with the matching uid. function getJSONFromUID(uid){ const data = fs.readFileSync(toytagFileName, 'utf8'); const databases = JSON.parse(data); @@ -73,6 +86,7 @@ function getJSONFromUID(uid){ return entry; } +//This updates the pad index of a tag in toytags.json, so that info can be accessed locally. function updatePadIndex(uid, index){ console.log('Planning to set UID: ' + uid + ' to index ' + index); const data = fs.readFileSync(toytagFileName, 'utf8'); @@ -87,6 +101,7 @@ function updatePadIndex(uid, index){ }) } +//This searches toytags.json and returns to UID of the entry with the matching index. function getUIDFromIndex(index){ const data = fs.readFileSync(toytagFileName, 'utf8'); const databases = JSON.parse(data); @@ -99,6 +114,7 @@ function getUIDFromIndex(index){ return uid; } +//This updates the provided datatype, of the entry with the matching uid, with the provided data. function writeJSONData(uid, datatype, data){ console.log('Planning to set '+ datatype + ' of ' + uid + ' to ' + data); const tags = fs.readFileSync(toytagFileName, 'utf8'); @@ -114,6 +130,8 @@ function writeJSONData(uid, datatype, data){ }) } + +//This sets all saved index values to '-1' (meaning unplaced). function initalizeToyTagsJSON(){ const data = fs.readFileSync(toytagFileName, 'utf8'); const databases = JSON.parse(data); @@ -126,6 +144,21 @@ function initalizeToyTagsJSON(){ } //When the game calls 'CMD_WRITE', writes the given data to the toytag in the top position. +/* Writing Tags Explained: + * A write occurs in three seperate function calls, and will repreat until either the write is canceled in game, + * or all three calls successfully write data. + * + * To appease the game all data is passed through and copied to the token in the top pad. But during this we can intercept what is being written + * and save the data locally as well. This lets us call that data back when we want to use that tag again. + * + * payload[1] tells what page is being written, and everything after is the data. + * page 24 - ID + * page 23 - Vehicle Upgrade Pt 1 + * page 26 - Vehicle Upgrades Pt 2 + * **When writing the pages requested for the write are sometimes ofset by 12, not sure why. + * + * This data is copied to the JSON for future use. + */ tp.hook(tp.CMD_WRITE, (req, res) => { var ind = req.payload[0]; var page = req.payload[1]; @@ -133,18 +166,18 @@ tp.hook(tp.CMD_WRITE, (req, res) => { var uid = getUIDFromIndex('2'); console.log('REQUEST (CMD_WRITE): index:', ind, 'page', page, 'data', data); + //The ID is stored in page if(page == 24 || page == 36){ writeJSONData(uid,"id",data.readInt16LE(0)); var name = getNameFromID(data.readInt16LE(0)); writeJSONData(uid,"name",name); writeJSONData(uid,"type","vehicle"); - //writeVehicleData(uid, "uid", tp.randomUID()) } else if(page == 23 || page == 35) writeJSONData(uid, "vehicleUpgradesP23", data.readUInt32LE(0)); else if (page == 25 || page == 37){ writeJSONData(uid, "vehicleUpgradesP25", data.readUInt32LE(0)); - io.emit("refreshTokens"); + io.emit("refreshTokens"); //Refreshes the html's tag gui. } res.payload = new Buffer('00', 'hex'); @@ -155,14 +188,18 @@ tp.hook(tp.CMD_WRITE, (req, res) => { }); + + app.use(express.json()); app.use(express.static(path.join(__dirname, 'server'))) +//**Website requests** app.get('/', (request, response) => { response.sendFile(path.join(__dirname, 'server/index.html')); }); +//Create a new Character and save that data to toytags.json app.post('/character', (request, response) => { console.log('Creating character: ' + request.body.id); var uid = tp.randomUID(); @@ -202,6 +239,7 @@ app.post('/character', (request, response) => { response.send(); }); +//This is called when a token is placed or move onto a position on the toypad. app.post('/characterPlace', (request, response) => { console.log('Placing tag: ' + request.body.id); var entry = getJSONFromUID(request.body.uid); @@ -224,6 +262,7 @@ app.post('/characterPlace', (request, response) => { } }) +//Create a new vehicle and save that data to toytags.json app.post('/vehicle', (request, response) => { console.log('Placing vehicle: ' + request.body.id); var uid = tp.randomUID(); @@ -264,6 +303,7 @@ app.post('/vehicle', (request, response) => { response.send(uid); }); +//This is called when a tag is removed from the board or needs to be moved. app.delete('/remove', (request, response) => { console.log('Removing item: ' + request.body.index); console.log('DEBUG: pad-from-token: ', tp._tokens.filter(v => v.index == request.body.index)[0].pad); @@ -273,7 +313,9 @@ app.delete('/remove', (request, response) => { response.send(true); }); +//IO calls io.on('connection', (socket) => { + //Removes a entry from toytags.json socket.on('deleteToken', (uid) => { console.log('IO Recieved: Deleting entry '+ uid + ' from JSON'); const tags = fs.readFileSync(toytagFileName, 'utf8'); From e8e0a9a93d830f3534ae15d1c613f18beec72eaa Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:53:52 -0500 Subject: [PATCH 13/21] Update index.js --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 9086f4b..2a143e4 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ /* - Copyright © 2021 Berny23 + Copyright © 2021 Berny23 & Cort1237 This file is part of "ToyPad Emulator for Lego Dimensions" which is released under the "MIT" license. See file "LICENSE" or go to "https://choosealicense.com/licenses/mit" for full license details. From 00a0063561670b4d8ebe01f58040ec04a49bbf20 Mon Sep 17 00:00:00 2001 From: cort1237 <59704055+cort1237@users.noreply.github.com> Date: Mon, 20 Sep 2021 19:03:42 -0500 Subject: [PATCH 14/21] Changed warning --- server/index.html | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/server/index.html b/server/index.html index df40e66..2ee0c1a 100644 --- a/server/index.html +++ b/server/index.html @@ -1,6 +1,6 @@