├── memory ├── entities.md ├── structs │ ├── README.md │ ├── BinView.h │ ├── vector.h │ ├── AbstractFieldGroup.h │ ├── AbstractEntity.h │ ├── BarrelDefinition.h │ └── TankDefinition.h ├── README.md └── userscripts │ ├── id_retriever.user.js │ └── dma.user.js ├── wasm ├── README └── rev │ ├── EHandle.h │ ├── BinView.h │ ├── EHandle.cpp │ └── BinView.cpp ├── canvas ├── shape_sizes.md ├── render_order.txt ├── scaling.md └── color_constants.md ├── extras ├── tanks.js ├── colors.js ├── stats.md ├── addons.md └── achievements.json ├── media └── README.md ├── README.md ├── protocol ├── update.md ├── connection │ ├── headless.md │ └── m28n_api.md ├── userscripts │ ├── packethook.template │ └── packethook.user.js ├── data.md ├── crypto.md ├── serverbound.md └── clientbound.md ├── entities.md ├── physics └── README.txt └── LICENSE /memory/entities.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /memory/structs/README.md: -------------------------------------------------------------------------------- 1 | # Structures 2 | 3 | Mostly reversed structures are posted here in pseudo-c struct-like format. 4 | -------------------------------------------------------------------------------- /wasm/README: -------------------------------------------------------------------------------- 1 | WASM 2 | ========== 3 | 4 | Diep's wasm is C++ compiled to wasm and javascript with LLVM and Binaryen (emscripten). 5 | -------------------------------------------------------------------------------- /canvas/shape_sizes.md: -------------------------------------------------------------------------------- 1 | # Shape Sizes 2 | 3 | The shapes are not drawn by side length. They are drawn by radius (from the center to any of the vertices). 4 | 5 | The following is the collected data: 6 | 7 | - grid square side length = 50 diep units 8 | - square, triangle = 55 diep units 9 | - pentagon = 75 diep units 10 | - small crasher = 35 diep units 11 | - big crasher = 55 diep units 12 | - alpha pentagon = 200 diep units 13 | 14 | ## Note 15 | 16 | All data here are directly taken from the canvas, not from any server packets, except for the grid square side length. 17 | -------------------------------------------------------------------------------- /memory/structs/BinView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Bin View structs are used for reading 3 | packets, byte by byte, out of a series 4 | of bytes. `BinView`s take up 12 bytes. 5 | 6 | It might be worth noting, `BinView`s 7 | are almost always initalized inside a 8 | stackframe, not the heap. 9 | 10 | In Emscripten, Pointers are 32 bit. 11 | */ 12 | struct BinView { 13 | // Packet @00 14 | // - Points to a series of bytes (packet) 15 | uint8_t* packet; 16 | 17 | // Packet Length @04 18 | // - The number of bytes in Packet 19 | int32_t packet_len; 20 | 21 | // Position @08 22 | // - Position being read in the bytes 23 | // - Think of it like reader.at in a packet Reader 24 | int32_t pos; 25 | }; 26 | -------------------------------------------------------------------------------- /memory/structs/vector.h: -------------------------------------------------------------------------------- 1 | /* 2 | Vectors store elements of data efficiently. They provide constant access time to each element via their index. 3 | Vectors are 12 bytes of size 4 | 5 | In Emscripten, Pointers are 32 bit. 6 | */ 7 | 8 | // Explains itself? Kind of a standard way of storing data, but this is its format in emscripten compiled binaries. 9 | // The start of the elements is allocated away from the vector and does not begin after the pointer to end_capacity 10 | struct vector { 11 | void* start; // Points to the start of the elements contained - @00 12 | 13 | void* end; // Denotes how much of the total space is used (end - start) - @04 14 | 15 | void* end_capacity; // Denotes the absolute space that the vector has allocated (end_capacity - start) - @08 16 | }; 17 | -------------------------------------------------------------------------------- /wasm/rev/EHandle.h: -------------------------------------------------------------------------------- 1 | #include "Entity.h" 2 | #include "Simulation.h" 3 | #include "BinView.h" 4 | 5 | // EHandle stands for Entity Handle 6 | class EHandle { 7 | private: 8 | // The id of the simulation the entity is in 9 | unsigned short simulationId; 10 | 11 | // The entity's id/index 12 | unsigned short id; 13 | 14 | // The entity's hash 15 | int hash; 16 | 17 | public: 18 | // Constructs an empty EHandle 19 | EHandle(); 20 | // Constructs an EHandle out of an entity 21 | EHandle(Entity const* entity); 22 | 23 | // Returns the entity's id 24 | unsigned short ID(); 25 | // Overwrites the EHandle from the encoded byte format 26 | void Decode(Simulation* simulation, BinView& view); 27 | // Encodes the EHandle into its byte format - appending onto a BinData 28 | void Encode(BinData &out) const; 29 | // Gets the entity being referenced by this EHandle 30 | Entity* Deref() const; 31 | }; 32 | -------------------------------------------------------------------------------- /canvas/render_order.txt: -------------------------------------------------------------------------------- 1 | --> GameRendering.Render() { 2 | RenderGrid() -> {} 3 | RenderBorders() -> {} 4 | RenderDebugCollision() -> QuadTreeSystem.DebugNodes() 5 | RenderLeaderArrow() -> RenderArrow() 6 | RenderMothershipsArrows() -> {} 7 | RenderEntities() -> RenderEntity() 8 | RenderNames() -> {} 9 | RenderHealthBars() -> {} 10 | RenderUI() -> { 11 | IsInGame?() { 12 | RenderServerStats() -> {} 13 | RenderScoreboard() -> {} 14 | RenderMinimap() -> {} 15 | RenderStatus() -> DrawBar() 16 | RenderAttributeUpgrades() -> {} 17 | RenderClassTree() -> ClassTree.Render() 18 | # RenderNetpropUsage() -> {} 19 | RenderAchievements() -> PollAchievements() 20 | } else { 21 | RenderChangelog() -> {} 22 | RenderConnecting() -> {} 23 | RenderGameMode() -> {} 24 | RenderPartyButton() -> {} 25 | RenderStats() -> {} 26 | RenderSpawnMenu() -> {} 27 | RenderWaitingForPlayers() -> {} 28 | RenderCountdown() -> {} 29 | RenderFPS() -> {} 30 | RenderConsole() -> {} 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /memory/structs/AbstractFieldGroup.h: -------------------------------------------------------------------------------- 1 | /* 2 | Abstract Field Groupsc contain an entity's 3 | field group's data. `AbstractFieldGroup` 4 | size depends on its type 5 | 6 | ID : NAME : SIZE 7 | 0 : RELATIONS : 38 8 | 2 : BARREL : 48 9 | 3 : PHYSICS : 108 10 | 4 : HEALTH : 76 11 | 6 : UNUSED : 12 | 7 : LOBBY : ? 13 | 8 : ARENA : 684 14 | 9 : NAME : 28 15 | 10: GUI : 482 16 | 11: POS : 152 17 | 12: STYLE : 112 18 | 14: SCORE : 36 19 | 15: TEAM : 36 20 | 21 | In Emscripten, Pointers are 32 bit. 22 | */ 23 | struct AbstractFieldGroup { 24 | // Self Ent Pointer @00 25 | // - points to its owner 26 | struct AbstractEntity* self; 27 | 28 | // Field State Managers 29 | // - *n* size list of fields part of the field group 30 | // - Bytes for the sole purpose of managing a field's state (not really relevant I think on the client) 31 | uint8_t field_state_managers[n]; 32 | 33 | // Field Data 34 | // - In order of the shuffled fields order 35 | // - Varying data types and values 36 | any field_data[n]; 37 | 38 | // and some more stuff over yonder 👀 39 | }; 40 | -------------------------------------------------------------------------------- /extras/tanks.js: -------------------------------------------------------------------------------- 1 | module.exports = Array.from({ 2 | 0: "Tank", 3 | 1: "Twin", 4 | 2: "Triplet", 5 | 3: "Triple Shot", 6 | 4: "Quad Tank", 7 | 5: "Octo Tank", 8 | 6: "Sniper", 9 | 7: "Machine Gun", 10 | 8: "Flank Guard", 11 | 9: "Tri-Angle", 12 | 10: "Destroyer", 13 | 11: "Overseer", 14 | 12: "Overlord", 15 | 13: "Twin Flank", 16 | 14: "Penta Shot", 17 | 15: "Assassin", 18 | 16: "Arena Closer", 19 | 17: "Necromancer", 20 | 18: "Triple Twin", 21 | 19: "Hunter", 22 | 20: "Gunner", 23 | 21: "Stalker", 24 | 22: "Ranger", 25 | 23: "Booster", 26 | 24: "Fighter", 27 | 25: "Hybrid", 28 | 26: "Manager", 29 | 27: "Mothership", 30 | 28: "Predator", 31 | 29: "Sprayer", 32 | 30: "", // Deleted : Probably Predator X 33 | 31: "Trapper", 34 | 32: "Gunner Trapper", 35 | 33: "Overtrapper", 36 | 34: "Mega Trapper", 37 | 35: "Tri-Trapper", 38 | 36: "Smasher", 39 | 37: "", // Deleted : Probably Mega Smasher 40 | 38: "Landmine", 41 | 39: "Auto Gunner", 42 | 40: "Auto 5", 43 | 41: "Auto 3", 44 | 42: "Spread Shot", 45 | 43: "Streamliner", 46 | 44: "Auto Trapper", 47 | 45: "Dominator", // Destroyer 48 | 46: "Dominator", // Gunner 49 | 47: "Dominator", // Trapper 50 | 48: "Battleship", 51 | 49: "Annihilator", 52 | 50: "Auto Smasher", 53 | 51: "Spike", 54 | 52: "Factory", 55 | 53: "", // Nameless and the "initial tank" value. Looks like the Ball tank 56 | 54: "Skimmer", 57 | 55: "Rocketeer", 58 | 59 | length: 56 60 | }); 61 | -------------------------------------------------------------------------------- /memory/README.md: -------------------------------------------------------------------------------- 1 | # Memory 2 | 3 | This folder will serve as a source of information relating to LLVM's way of organizing the memory of a wasm32 program, as well as the way Diep's developer (Zeach) structured his game's memory. 4 | 5 | ## LLVM 6 | 7 | LLVM's memory is layed out similarly to how a real computer lays out its programs. There are 4 main sections of the wasm memory, and they are present in all code compiled with LLVM -> `wasm32`. Also - for more information about the `wasm32` compilation target, read [``](https://example.com). 8 | 9 | > TODO: Make links point to header3s (with horizontal rules) down below 10 | 11 | 1. **The Heap** (`#heap`)\ 12 | Stores all dynamically allocated data. Anytime the program calls `malloc()` (or another native dynamic allocation function), the allocation is stored somewhere in this range. 13 | 2. **The Stack** (`#stack`)\ 14 | Function's variables are stored in scopes pushed onto the stack. 15 | 3. **The Data Section** (`#data`)\ 16 | Stores static data (initialized or uninitialized) that the wasm uses to execute. For example, strings or global variabes are stored here. 17 | 4. **The Void** (`#void`)\ 18 | An empty area in the memory. 19 | 20 | This following image shows their placement in the memory. The top of the image represents the higher addresses, and the bottom of the image represents address 0. 21 | 22 | > ![mem](https://user-images.githubusercontent.com/79597906/137538688-68496a01-6db1-4f3d-baf5-2ffebac56983.png)\ 23 | > Sample view of LLVM's `wasm32` memory (not to scale) 24 | 25 | > ![memtoscale](https://user-images.githubusercontent.com/79597906/137538880-907983cc-54c0-463c-ad05-a45b5e4fdb55.png)\ 26 | > To scale view 27 | 28 | ## Structures 29 | -------------------------------------------------------------------------------- /wasm/rev/BinView.h: -------------------------------------------------------------------------------- 1 | // Thank you 0x1412 and Crabby for helping with C++ 2 | #include 3 | #include 4 | #include "BinData.h" 5 | 6 | class BinView { 7 | private: 8 | // Points to a series of bytes 9 | const char* data; 10 | 11 | // The number of bytes in the data 12 | int dataLength; 13 | 14 | // Current position being read from in the data 15 | int pos; 16 | 17 | public: 18 | // Constructs a BinView out of a BinData 19 | BinView(BinData const& binData); 20 | // Constructs a BinView out of a data pointer and length 21 | BinView(char const* data, size_t dataLength); 22 | 23 | // Reads the next 8 bit integer from the data 24 | int NextUint8(); 25 | // Reads the next 32 bit integer from the data 26 | int NextUint32(); 27 | // Reads the next 32 bit floating point number from the data and promotes it to a double 28 | double NextFloat(); 29 | // Reads the next null terminated string from the data into an empty string buffer 30 | std::string NextUTF8String(); 31 | // Reads the next variable length 32 bit integer from the data 32 | int NextVarUint32(); 33 | // Reads the next signed variable length 32 bit integer from the data 34 | int NextVarInt32(); 35 | 36 | // Returns the number of bytes left in the data 37 | int BytesLeft() const; 38 | // Returns a pointer to the bytes left in the data 39 | char const* BytesLeftPtr() const; 40 | // Slices the rest of the bytes in the data into a BinData 41 | BinData SliceRest() const; 42 | 43 | // Increases the `this.pos` by `count` 44 | void Seek(int count); 45 | // Copies the next *`count`* bytes from the data into `buffer` 46 | void NextBytes(size_t count, void* outputBuffer); 47 | }; 48 | -------------------------------------------------------------------------------- /extras/colors.js: -------------------------------------------------------------------------------- 1 | module.exports = class ColorID { 2 | // Id: 0, Border = #555555 3 | static Border = 0; 4 | static 0 = "border"; 5 | 6 | // Id: 1, Cannon = #999999 7 | static Cannon = 1; 8 | static 1 = "cannon"; 9 | 10 | // Id: 2, Tank = #00b2e1 11 | static Tank = 2; 12 | static 2 = "tank"; 13 | 14 | // Id: 3, TeamBlue = #00b2e1 15 | static TeamBlue = 3; 16 | static 3 = "teamBlue"; 17 | 18 | // Id: 4, TeamRed = #f14e54 19 | static TeamRed = 4; 20 | static 4 = "teamRed"; 21 | 22 | // Id: 5, TeamPurple = #bf7ff5 23 | static TeamPurple = 5; 24 | static 5 = "teamPurple"; 25 | 26 | // Id: 6, TeamGreen = #00e16e 27 | static TeamGreen = 6; 28 | static 6 = "teamGreen"; 29 | 30 | // Id: 7, Shiny = #8aff69 31 | static Shiny = 7; 32 | static 7 = "shiny"; 33 | 34 | // Id: 8, EnemySquare = #ffe869 35 | static EnemySquare = 8; 36 | static 8 = "square"; 37 | 38 | // Id: 9, EnemyTriangle = #fc7677 39 | static EnemyTriangle = 9; 40 | static 9 = "triangle"; 41 | 42 | // Id: 10, EnemyPentagon = #768dfc 43 | static EnemyPentagon = 10; 44 | static 10 = "pentagon"; 45 | 46 | // Id: 11, EnemyCrasher = #f177dd 47 | static EnemyCrasher = 11; 48 | static 11 = "crasher"; 49 | 50 | // Id: 12, Neutral = #ffe869 51 | static Neutral = 12; 52 | static 12 = "neutral"; 53 | 54 | // Id: 13, ScoreboardBar = #43ff91 55 | static ScoreboardBar = 13; 56 | static 13 = "scoreboard"; 57 | 58 | // Id: 14, Box (Wall) = #bbbbbb 59 | static Box = 14; 60 | static 14 = "box"; 61 | 62 | // Id: 15, EnemyTank = #f14e54 63 | static EnemyTank = 15; 64 | static 15 = "enemy"; 65 | 66 | // Id: 16, NecromancerSquare = #fcc376 67 | static NecromancerSquare = 16; 68 | static 16 = "sunchip"; 69 | 70 | // Id: 17, Fallen = #c0c0c0 71 | static Fallen = 17; 72 | static 17 = "fallen"; 73 | } 74 | -------------------------------------------------------------------------------- /wasm/rev/EHandle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "EHandle.h" 3 | 4 | // Constructs an empty EHandle 5 | EHandle::EHandle() { 6 | simulationId = 0; 7 | id = 0; 8 | hash = 0; 9 | } 10 | 11 | // Constructs an EHandle out of an entity 12 | EHandle::EHandle(Entity const* entity) { 13 | if (entity == nullptr) { 14 | simulationId = 0; 15 | id = 0; 16 | hash = 0; 17 | } else { 18 | *((long long*) this) = *(long long*) entity->GetHandle(); 19 | } 20 | } 21 | 22 | // Returns the entity's id 23 | unsigned short EHandle::ID() { 24 | return id; 25 | } 26 | 27 | // Overwrites the entity handle with the encoded EHandle 28 | void EHandle::Decode(Simulation* simulation, BinView& view) { 29 | simulationId = Simulation->GetID(); 30 | 31 | hash = view.NextVarUint32(); 32 | // The following assertion is - in the actual source code - being done on line 367 in file `shared/Entity.cpp` 33 | assert(hash <= 1 << 14); 34 | if (hash == 0) return; 35 | id = view.NextVarUint32(); 36 | // The following assertion is - in the actual source code - being done on line 370 in file `shared/Entity.cpp` 37 | assert(id < 16384); 38 | } 39 | 40 | // Encodes the EHandle into its byte format - appending onto a BinData 41 | void EHandle::Encode(BinData &out) const { 42 | if (hash == 0) { 43 | out.PushVarUint32(0); 44 | } else { 45 | out.PushVarUint32(hash); 46 | out.PushVarUint32(id); 47 | } 48 | } 49 | 50 | // Gets the entity being referenced by this EHandle 51 | Entity* EHandle::Deref() const { 52 | if (hash == 0) return nullptr; 53 | 54 | Simulation* simulation = Simulation::GetSimulation(simulationId); 55 | if (simulation == nullptr) return nullptr; 56 | EntityManager* entities = simulation->Entities(); 57 | // The following assertion is - in the actual source code - being done on line 19 in file `shared/EntityManager.cpp` 58 | assert(id < 16384); 59 | 60 | Entity* entity = entities->GetEntity(id); 61 | // Following line is a *bit* inaccurate 62 | if (!entities->existingIds[id] || entity->id != id) return nullptr; 63 | return entity; 64 | } 65 | -------------------------------------------------------------------------------- /media/README.md: -------------------------------------------------------------------------------- 1 | # Screenshots & Media 2 | 3 | Memory modifying, packet reversal, headless connections, and more. 4 | 5 | --- 6 | 7 | Modification of Auto Turret count 8 | ![image](https://user-images.githubusercontent.com/79597906/128451988-cf5b430d-5a21-4da8-8f19-eff37b8833b5.png) 9 | 10 | Modification of Tank Def Addons 11 | ![](https://user-images.githubusercontent.com/79597906/128451970-420113a0-1071-45a1-9812-7e7ebc58bf2b.png) 12 | 13 | Unknown Grayscale Theme - has yet to be reproduced (probably a bug) 14 | ![](https://user-images.githubusercontent.com/79597906/128451944-3662e9f1-38d0-485a-8480-378902e3f4bb.png) 15 | 16 | Bots in public "SinX Sandbox" 17 | ![](https://user-images.githubusercontent.com/79597906/128451908-dfa05682-dee8-47de-a2a4-9e95b68db2cb.png) 18 | 19 | Entity Modifying in Memory 20 | ![](https://user-images.githubusercontent.com/79597906/128451880-b2649e00-8734-4078-bf12-d90f8a2413c6.png) 21 | 22 | Living the Spike Life 23 | ![](https://user-images.githubusercontent.com/79597906/128451856-586980ee-161a-4e6f-96e0-8d148f35a34e.png) 24 | 25 | Fat Auto Turret 26 | ![](https://user-images.githubusercontent.com/79597906/128451798-af4ad698-8cc5-4832-9557-b7fe3053fb24.png) 27 | 28 | Entity Modifying 2 29 | ![](https://user-images.githubusercontent.com/79597906/128452134-c25a9aed-75e9-4a95-b68b-9286a3c7d197.png) 30 | 31 | Shape and Player Spawns 32 | ![](https://user-images.githubusercontent.com/79597906/128452274-ac114e9f-178d-4d8d-a70c-fd08d4e216d0.png) 33 | Thank you to CX for providing their shape database 34 | 35 | Boss Movements 36 | ![](https://user-images.githubusercontent.com/79597906/128452342-215a304c-3964-4fab-8320-8a76c48e1904.png) 37 | 38 | Entity Compilation Order vs Position 39 | ![](https://user-images.githubusercontent.com/79597906/128452399-92d0209e-f1b6-42d0-9649-455bdadb104b.png) 40 | Thank you to CX for providing the shape database 41 | 42 | Flag Modification via Incoming Packets 43 | ![](https://user-images.githubusercontent.com/79597906/128452904-1f5e476c-9fb4-48f5-99ff-037dce4fb2db.png) 44 | 45 | Necromancer Drone Side Modification - Penta Naco 46 | ![](https://user-images.githubusercontent.com/79597906/155915385-19cad318-36a2-4b18-aeef-deee43a2b762.png) 47 | 48 | Visualization of Diep's Memory 49 | ![memory](https://user-images.githubusercontent.com/79597906/151162261-c0fa6cb8-4237-47b0-bc79-eec1370e3f62.png) 50 | -------------------------------------------------------------------------------- /memory/structs/AbstractEntity.h: -------------------------------------------------------------------------------- 1 | /* 2 | Abstract Entity manages all data relating to an entity 3 | `AbstractEntity` structs are 136 bytes of size 4 | 5 | In Emscripten, Pointers are 32 bit. 6 | */ 7 | 8 | struct AbstractEntity { 9 | //** Entity Node Metadata **// 10 | 11 | // Self Ent Pointer @00 12 | // - points to itself 13 | struct AbstractEntity* self; // @00 14 | 15 | // Linked List 16 | // - Pointers to the double linked list which contains all entities. 17 | struct DoubleLinkedList* entity_list; // @04 18 | 19 | // Prev Ent Pointer @08 20 | // - Points to the previous entity in the double linked list. 21 | struct AbstractEntity* prev_elem; // @08 22 | 23 | 24 | // Next Ent Pointer @0C 25 | // - Points to the next entity in the double linked list. 26 | struct AbstractEntity* next_elem; // @0C 27 | 28 | //** Empty Node Metadata 1 **// 29 | // Self Ent Pointer @10 30 | // - points to this entity 31 | struct AbstractEntity* self_ptr1; // @10 32 | uint8_t _gap_0[12]; 33 | 34 | //** Empty Node Metadata 1 **// 35 | // Self Ent Pointer @20 36 | // - points to this entity 37 | struct AbstractEntity* self_ptr2; // @20 38 | uint8_t _gap_1[12]; 39 | 40 | 41 | 42 | //** Entity Identification Section `EHandle` **// 43 | 44 | // Simulation Pointer @30 45 | // - points to the entity's simulation 46 | struct Simulation* simulation; // @30 47 | 48 | // Simulation ID 49 | uint16_t simulation_id; // @34 50 | // Entity id, part of the representation system 51 | uint16_t id; // @36 52 | // Entity hash, part of the representation system 53 | uint32_t hash; // @38 54 | 55 | //** Other Properties **// 56 | 57 | uint8_t _gap_2[4]; 58 | 59 | // The emscripten_get_now (performance.now()) of when the entity was received 60 | double entity_creation_time; // @40 61 | 62 | // 16 Field Groups, all pointing to a FieldGroup struct 63 | // 0 : RELATIONS 64 | // 1 : not present 65 | // 2 : BARREL 66 | // 3 : PHYSICS 67 | // 4 : HEALTH 68 | // 5 : not present 69 | // 6 : EXAMPLE 70 | // 7 : LOBBY 71 | // 8 : ARENA 72 | // 9 : NAME 73 | // 10: GUI 74 | // 11: POS 75 | // 12: STYLE 76 | // 13: not present 77 | // 14: SCORE 78 | // 15: TEAM 79 | struct AbstractFieldGroup* field_groups[16]; // @48 80 | }; 81 | -------------------------------------------------------------------------------- /memory/userscripts/id_retriever.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Player ID Retriever 3 | // @version 1.0.0 4 | // @description Gets your player's entity id 5 | // @namespace github.com/ABCxFF 6 | // @author ABC 7 | // 8 | // @match https://diep.io/ 9 | // @run-at document-start 10 | // @grant none 11 | // 12 | // @require https://gist.githubusercontent.com/ABCxFF/b089643396fbb933996966b5ab632821/raw/6f93ee2bac45e308067fafff21e1110132f9ca0e/wail.js 13 | // ==/UserScript== 14 | 15 | const instantiate = WebAssembly.instantiate; 16 | 17 | WebAssembly.instantiate = function(buffer, options) { 18 | const wail = new WailParser(new Uint8Array(buffer)); 19 | 20 | wail.parse(); 21 | 22 | main(options, wail); 23 | 24 | return instantiate(buffer, options); 25 | } 26 | 27 | async function main(options, { _parsedSections: parsedSections }) { 28 | const memory = Object.values(options.a).find(o => o instanceof WebAssembly.Memory); 29 | const HEAPU32 = new Uint32Array(memory.buffer); 30 | const HEAPU16 = new Uint16Array(memory.buffer); 31 | 32 | const [buildHash] = document.body.innerHTML.match(/[a-f0-9]{40}/g) 33 | const wasmjs = await fetch("https://diep.io/build_" + buildHash + ".wasm.js").then(r => r.text()); 34 | 35 | const getExportKey = name => (i => wasmjs.slice(i, wasmjs.indexOf('"', i)))(wasmjs.lastIndexOf(name + '"') + 18 + name.length); 36 | 37 | const HAS_TANK = getExportKey("_has_tank"); 38 | const hasTankIndex = parsedSections.find(s => s.id === 7).results.find(r => r.fieldStr === HAS_TANK).index 39 | const importCount = parsedSections.find(s => s.id === 2).results.reduce((a, b) => b.kind === 'func' ? a + 1 : a, 0) 40 | const hasTank = parsedSections.find(s => s.id === 10).results[hasTankIndex - importCount] 41 | 42 | const instructionOffset = hasTank.instructions.findIndex((e, i, l) => l[i + 3] && l[i + 3].immediates[0] - e.immediates[0] === 4) 43 | const cameraVector = hasTank.instructions[instructionOffset].immediates[0]; 44 | const playerIDOffset = hasTank.instructions[instructionOffset + 13].immediates[1]; 45 | 46 | let lastId = -1; 47 | setInterval(() => { 48 | let currentId = HEAPU16[(HEAPU32[HEAPU32[cameraVector >> 2] >> 2] + (playerIDOffset - 2)) >> 1]; 49 | if (lastId !== currentId) console.log(`Player ID Detected: <${lastId = currentId}, ${HEAPU16[(HEAPU32[HEAPU32[cameraVector >> 2] >> 2] + playerIDOffset) >> 1]}>`); 50 | }, 500); 51 | } 52 | -------------------------------------------------------------------------------- /canvas/scaling.md: -------------------------------------------------------------------------------- 1 | # Scaling algorithm 2 | 3 | The end goal is to calculate scalingFactor, but before that, we need to understand what FOV and windowScaling are. 4 | 5 | ## FOV 6 | Stands for field of view. The closer the number to 0, the more zoomed out we are. This value isn't calculated, but rather, sent by the server. 7 | 8 | Unlike scalingFactor, this value does not change if our window dimensions change. The formula for the FOV value from the server is 9 | 10 | ![heuy here's a formula](https://i.imgur.com/7WTK85p.png) 11 | Where the fieldFactors are accessable [here](/extras/tankdefs.json). 12 | 13 | ## windowScaling 14 | 15 | Calculated using the following: 16 | ```js 17 | function windowScaling() { 18 | const canvas = document.getElementById('canvas'); 19 | const a = canvas.height / 1080; 20 | const b = canvas.width / 1920; 21 | return b < a ? a : b; 22 | } 23 | ``` 24 | 25 | Quick note: It may be tempting to use `window.innerHeight/Width`, but the difference matters a lot if `window.devicePixelRatio` is not equal to 1. 26 | 27 | The values 1920 and 1080 are in a 16:9 ratio. 28 | 29 | To conceptualize this code, let's say that our current window is 1920 x 1080. `a` and `b` would be 1. 30 | ```js 31 | const a = 1080 / 1080; 32 | const b = 1920 / 1920; 33 | ``` 34 | If we were to increase our width to 2000, our window will be wider than 16:9. 35 | ```js 36 | const a = 1080 / 1080; 37 | const b = 2000 / 1920; 38 | return 1.0416 < 1 ? 1 : 1.0416; // this will yield 1.0416 39 | ``` 40 | This will cause `windowScaling()` to depend on the width for scaling so that changing the width will have a noticeable effect on the scaling. 41 | 42 | If our window was longer than 16:9, the scaling will depend on the height. 43 | 44 | ## scalingFactor 45 | 46 | The unit for this is `canvas pixels / Diep units`. This value is fundamental for converting between Diep units and canvas pixels. 47 | 48 | This value is calculated using `scalingFactor = fov * windowScaling()` 49 | 50 | Examples of how to convert from one another: 51 | - `10 Diep units * scalingFactor` gives us canvas pixels 52 | - `10 canvas pixels / scalingFactor` gives us Diep units 53 | 54 | Some remarks: 55 | - This value only appears when Diep draws the grid. It draws the grid by first drawing a portion of the grid onto a separate canvas (with scaling of 1 canvas pixel = 1 Diep unit), then using createPattern to draw the entire grid. Before drawing the grid onto the main canvas, however, it setTransforms the main canvas so that the horizontal and vertical scaling is equal to the scalingFactor. 56 | - The client's grid's opacity is equal to `scalingFactor * grid_base_alpha`, where `grid_base_alpha` defaults to `0.1` 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is no longer being worked on actively, progress updates will be low. 2 | 3 | See [diepcustom](https://github.com/ABCxFF/diepcustom/) for recent projects (reimplementation [in a sense] of a Diep.IO backend) 4 | 5 | --- 6 | 7 | # **:DiepInDepth** 8 | 9 | Collection of protocol, memory, and other hacky information for the browser game [diep.io](https://diep.io/). 10 | 11 | What started off as an attempt to parse game leaderboards out of packets is now a collection of information about the insides of diep.io. Includes information such as packet encoding/decoding, packet protocol, memory structures, a bit of physics, wasm parsing, game code reversal, and much more. 12 | 13 | ### **Sections** 14 | 15 | There are 7 sections that divide up the information collected. 16 | 17 | 1. Game Protocol ([`protocol/`](./protocol/)) 18 | Including incoming and outgoing packets, encoding/decoding, m28 server list api, and websocket connections 19 | 2. WebAssembly Reversal ([`wasm/`](./wasm/)) 20 | Including any means necessary, asm.js conversion, specific functions, automation of reversing constants and pointers, understanding of emscripten 21 | 3. Canvas Reversal ([`canvas/`](./canvas/)) 22 | Including shape sizes, draw sequences, scaling, and color constants 23 | 4. Memory Management ([`memory/`](./memory/)) 24 | Including storage of entities, tanks, the gui, and the structure and way things are stored 25 | 5. Extras ([`extras/`](./extras/)) 26 | Including any extra information, fun facts, misc algorithms, and any physics not provided in [Spade Squad](http://spade-squad.com) 27 | 6. Media ([`media/`](./media/)) 28 | Including screenshots and other forms of media relating to our research in diep 29 | 7. Physics ([`physics/`](./physics/)) 30 | Including information regarding the properties and nature of entities, such as movement, collisions, and sizes 31 | 32 | Before going too in depth into entity-related memory and protocol, it is highly recommended you read [`entities.md`](./entities.md) to understand how entities work. 33 | 34 | ## **Contributors** 35 | 36 | This repository was made possible with the help of the Diep In Depth team. Thank you to [ABC](https://github.com/ABCxFF), [ALPH2H](https://github.com/ALPH2H), [Excigma](https://github.com/Excigma), [HueHanaejistla](https://github.com/HueHanaejistla), [Diep7444](https://github.com/diepiodiscord), [Cazka](https://github.com/Cazka), [shlong](https://github.com/shlongisdookielol), [Pola](https://github.com/PiotrDabkowski), [Altanis](https://github.com/CoderSudaWuda), [Binary](https://github.com/binary-person), [Shädam](https://github.com/supahero1), [Nulled](https://github.com/Nulled), [Crabby](https://github.com/Craabby), [0x1412](https://github.com/skittles1412), and [CX](https://github.com/CX88) 🙏 for their work. For discussion via discord, join the [Spike Squad Discord Server](https://discord.gg/jRXwhnN7yQ) where some of us are active. 37 | 38 | 39 | If *you* have additional information you can / want to share, please, pull requests are welcome! 40 | -------------------------------------------------------------------------------- /wasm/rev/BinView.cpp: -------------------------------------------------------------------------------- 1 | #include "BinView.h" 2 | #include 3 | 4 | // Constructs a BinView out of a BinData 5 | BinView::BinView(BinData const& binData) { 6 | data = binData.GetData(); 7 | dataLength = binData.GetDataLength(); 8 | pos = 0; 9 | } 10 | 11 | // Constructs a BinView out of a data pointer and length 12 | BinView::BinView(char const* data, size_t dataLength) { 13 | this->data = data; 14 | this->dataLength = dataLength; 15 | this->pos = 0; 16 | } 17 | 18 | // Reads the next 8 bit integer from the data 19 | int BinView::NextUint8() { 20 | char out; 21 | 22 | if (pos + 1 > dataLength) out = 0; 23 | else out = data[pos++]; 24 | 25 | return out; 26 | } 27 | 28 | // Reads the next 32 bit integer from the data 29 | int BinView::NextUint32() { 30 | if (pos + 4 > dataLength) return 0; 31 | 32 | return data[pos++] | 33 | (data[pos++] << 8) | 34 | (data[pos++] << 16) | 35 | (data[pos++] << 24); 36 | } 37 | 38 | // Reads the next 32 bit floating point number from the data and promotes it to a double 39 | double BinView::NextFloat() { 40 | char out[4]; 41 | 42 | if (pos + 4 > dataLength) return 0.0; 43 | 44 | out[0] = data[pos++]; 45 | out[1] = data[pos++]; 46 | out[2] = data[pos++]; 47 | out[3] = data[pos++]; 48 | 49 | return (double) *(float*) &out; 50 | } 51 | 52 | // Reads the next null terminated string from the data into an empty string buffer 53 | std::string BinView::NextUTF8String() { 54 | std::string out; 55 | char byte; 56 | 57 | while (pos < dataLength) { 58 | byte = NextUint8(); 59 | 60 | if (byte == 0) break; 61 | 62 | out.push_back(byte); 63 | } 64 | 65 | return out; 66 | } 67 | 68 | // Reads the next variable length 32 bit integer from the data 69 | int BinView::NextVarUint32() { 70 | int byte, i, out; 71 | 72 | out = 0; 73 | i = 0; 74 | while (true) { 75 | byte = NextUint8(); 76 | out |= byte << i; 77 | i += 7; 78 | if (byte & 0x80 == 0) break; 79 | if (i >= 32) break; 80 | if (BytesLeft() <= 0) break; 81 | } 82 | 83 | return out; 84 | } 85 | 86 | // Reads the next signed variable length 32 bit integer from the data 87 | int BinView::NextVarInt32() { 88 | int unsignedValue; 89 | unsignedValue = NextVarUint32(); 90 | return (unsignedValue >> 0) ^ (0 - (unsignedValue & 1)); 91 | } 92 | 93 | // Returns the number of bytes left in the data 94 | int BinView::BytesLeft() const { 95 | return dataLength - pos; 96 | } 97 | 98 | // Returns a pointer to the bytes left in the data 99 | char const* BinView::BytesLeftPtr() const { 100 | return &data[pos]; 101 | } 102 | 103 | // Slices the rest of the bytes in the data into a BinData 104 | // - (thank you 1412 for help cracking this) 105 | BinData BinView::SliceRest() const { 106 | return { BytesLeftPtr(), BytesLeft() }; 107 | } 108 | 109 | // Increases the `this.pos` by `count` 110 | void BinView::Seek(int count) { 111 | pos += count; 112 | } 113 | 114 | // Copies the next *`count`* bytes from the data into `buffer` 115 | void BinView::NextBytes(size_t count, void* outputBuffer) { 116 | if (BytesLeft() < count) return; 117 | 118 | memcpy(outputBuffer, BytesLeftPtr(), count); 119 | Seek(count); 120 | } 121 | -------------------------------------------------------------------------------- /protocol/update.md: -------------------------------------------------------------------------------- 1 | # 0x00 Update Packet 2 | --- 3 | The one and only infamous 0x00 update packet contains almost all the information you see in the game. If you have not yet read [`/entities.md`](/entities.md), I highly advise you to read it, because it directly relates to how update packets work. 4 | 5 | The update packet contains server uptick, entity deletions (entities on screen that should be deleted), and entity upcreates (creations of new entities or updates of older entities). The rough format of the 0x00 packet is as follows: 6 | > `0x00 vu(game tick) vu(deleteCount) ...entity id deletes vu(upcreateCount) ...upcreates` 7 | 8 | Two types of things can happen in an upcreate, there can be creations and / or updates (hence the word upcreate). Creations and updates have different formats and are identified in their own way. Both of the data in creations and updates are organized in a field order, which is the per-build order of all fields. 9 | > Being 68 fields, there are always 68 indexes. These field indexes are used in the protocol and to determine the field offsets in the memory. All fields and located here 10 | Read a bit more about field orders [here](/entities.md#fields) 11 | --- 12 | ### 1. How to identify between Creation and Update 13 | 14 | The following are examples of some basic upcreates: 15 | 16 | **Creation** 17 | > ```less 18 | > 01 08 # entity id 19 | > 01 # signifies creations 20 | > 05 01 # field group table 21 | > 93 02 93 45 28 # data 22 | > ``` 23 | **Update** 24 | > ```less 25 | > 01 03 # entity id 26 | > 00 # signifies updates 27 | > 01 # field group table 28 | > 05 93 02 00 45 # field data 29 | > 01 # close field 30 | > ``` 31 | So in updates, after the entity id are bytes `00 01`, and in creations, it's just `01` which initiates the [jump table](/protocol/data.md#data-organization). 32 | 33 | ### 2. Parsing Creation 34 | 35 | > This is not up to date, will be fixed soon 36 | 37 | As stated in [`entities.md`](/entities.md), entities are defined by their entity id, entity hash, field groups, and field data, all of which are present in creations. The entity id and hash are encoded in the [entid format](/protocol/data.md#entid---vu-hash-vu-id), the field groups are encoded in a [jump table](/protocol/data.md#data-organization), then fields are retrieved from the field groups, ordered by the shuffled field order, then the data type that corresponds with each field in order is read from the packet. Here's a rough sketch of what it would look like: 38 | 39 | > ```less 40 | > 01 03 # entity id 41 | > 01 # signifies creation 42 | > 00 # field groups, read as a jump table with no values only indexes 43 | > 02 44 | > 00 45 | > 02 46 | > 01 # close table 47 | > 48 | > ...byte /* 49 | > *n* number of bytes that have all entity's data 50 | > the fields are identified from the field groups and are reordered in the way field IDs were shuffled for the current build 51 | > the bytes are then read in order & types of these organized fields 52 | > 53 | > */ 54 | > ``` 55 | 56 | Note: 57 | - To parse tabled fields, such as `leaderboardNames`, you need to know the total amount of values they have. `leaderboardNames` for example has `10`, so you would read 10 null terminating strings which are its data type. 58 | 59 | Abstract Format of a Creation: 60 | 61 | > `entid(entity id, hash) 0x01 jumpTable(fieldGroup indexes only) ...field data` 62 | 63 | ### 3. Parsing Update 64 | 65 | -------------------------------------------------------------------------------- /protocol/connection/headless.md: -------------------------------------------------------------------------------- 1 | > The following file is being updated. Information is not confirmed to be correct 2 | 3 | # Headless Connection 4 | 5 | ## M28 API 6 | 7 | To find a server, you'll need to retrieve a server id from the m28 api, from there you can generate a socket URL which you can connect to - all of which is described in the first passage of [api.md](./api.md). 8 | 9 | - Connections are always secure 10 | 11 | --- 12 | 13 | ## HTTP Headers 14 | 15 | Diep.io currently has many security measures to block out headless connections. One of these securities is strict checking of HTTP headers on incoming WebSocket connections, and so we need to override this and connect with browser-like headers. 16 | 17 | Credit to Binary: 18 | 19 | ```js 20 | const https = require('https'); 21 | https.get = new Proxy(https.get, {apply(target, thisArg, args) 22 | { 23 | if (args[0]?.headers?.Origin === "https://diep.io") // Don't interfere with other connections 24 | { 25 | args[0].headers = { 26 | 'Host': args[0].host, 27 | 'User-Agent': '', 28 | 'Pragma': '', 29 | 'Cache-Control': '', 30 | ...args[0].headers 31 | }; 32 | } 33 | return target.apply(thisArg, args); 34 | }}); 35 | ``` 36 | 37 | There are 2 rules the HTTP headers must satisfy: 38 | - The headers `User-Agent`, `Pragma` and `Cache-Control` must exist, otherwise, your connection will get rejected with a [403](https://httpstatuses.com/403) response code. They can simply be left empty or have a false value, however, Zeach may begin to check for this at any time so it is recommended to provide realistic values. 39 | - The `Host` header must be anywhere above the `Origin` and `Sec-WebSocket-Key` headers, this is true with every modern browser except for the [ws](https://www.npmjs.com/package/ws) module. Nothing happens if you fail this check, you will not get disconnected or banned. Instead, the server will send completely random packets to try to trick you into thinking that you got packet shuffling wrong. 40 | 41 | ## Initiation and Packet Encoding / Decoding 42 | 43 | The first packet send is always the [0x00 Init packet](../serverbound.md#0x00-init-packet), and this does not have to be encoded. The following packet will either be a [0x01 Invalid Build](../clientbound.md#0x01-outdated-client-packet), or an encoded [0x0D JS Int Eval packet](../clientbound.md#0x0d-int-js-challenge-packet), followed by a [0x0B PoW Challenge](../clientbound.md#0x0b-pow-challenge-packet). The basic idea of encoding and decoding packets ("Shuffling") is explained in [Packet Encoding and Decoding](../crypto.md). 44 | 45 | ## Ip Limit and Overcoming it 46 | 47 | Diep.io has a system in place that limits the amount of possible connections per ip. As of now, this limit is 2 connections per ip per server. Since the ips appear to be stored locally on the server, one can still connect to other servers (Even if servers may have multiple arenas open at the same time, the "block" would still be persistent. On the other hand, one might connect to a server in another region which would work completely normal). There are currently two known solutions to this "problem". 48 | 49 | ### Proxies 50 | 51 | Any proxies that support secure websocket connections can be used as some sort of middleman between server an ones own ip. More information on this is available [here](https://www.varonis.com/blog/what-is-a-proxy-server/) and [here](https://www.npmjs.com/package/https-proxy-agent). 52 | 53 | ### Ipv6 Subranging 54 | 55 | As of now, a /64 block of IPv6 ips can only be used for 2 connections (like regular IPv4 addresses). Therefore one will need a range of at least /63. The process of setting this up will not be explained any further here. 56 | -------------------------------------------------------------------------------- /memory/structs/BarrelDefinition.h: -------------------------------------------------------------------------------- 1 | /* 2 | Barrel Definitions have all data relating to 3 | barrels (and their bullets) `Barrel Definitions` 4 | structs are 100 bytes of size 5 | 6 | In Emscripten, Pointers are 32 bit. 7 | */ 8 | 9 | struct BarrelDefinition { 10 | // In radians, direction the barrel is facing 11 | float angle; // @00 12 | 13 | // Delay, delay before the shoot 14 | // - Half the barrels on the octo have delay .5, for example. 15 | float delay; // @04 16 | 17 | // Size of the barrel (width) 18 | // - Same as the `size` field sent to the client 19 | // - Its technically length, when rotated to 0rad, it looks like length 20 | // - The longer side, not the shorter of a barrel 21 | float size; // @08 22 | 23 | // Offset of the barrel 24 | // - Displacement off the player's body 25 | // - Think about twin's barrels, their offsets are NOT 0 26 | float offset; // @0C 27 | 28 | // Whether or not its trapezoidal 29 | // - Machine gun, stalker 30 | // - is_trapezoid =& 1 (there are other (weird) flags, idk what they are) 31 | int32_t is_trapezoid; // @10 32 | 33 | // Recoil 34 | // - Part of the recoil calculations 35 | float recoil; // @14 36 | 37 | // Width 38 | // - 42 * width to get whats sent to the client at lvl 0 39 | // - Shorter of the two sides on a barrel 40 | // - Determines bullet size 41 | float width; // @18 42 | 43 | // Bullet Type 44 | // - You can think of it lke ['bullet', 'drone', 'trap'][bullet_type i32 thing] 45 | int32_t bullet_type; // @1C 46 | 47 | // Knockback Factor 48 | // - Name pending 49 | // - Used to determine knockback applied to the bullet after collision 50 | float knockback_factor; // @20 51 | 52 | // Bullet Speed 53 | // - Part of bullet speed formulas 54 | float bullet_speed; // @24 55 | 56 | // Max Drones 57 | // - Only useful for drone tanks 58 | // - Max amount of drones you can spawn 59 | int32_t max_drones; // @28 60 | 61 | // Bullet Damage (?) 62 | // - "I think" 63 | float bullet_damage; // @2C 64 | 65 | // Bullet Base Health 66 | // - Similar to max health 67 | // - Max Health for level 0 68 | float bullet_base_health; // @30 69 | 70 | // Unknown, but for every barrel this value is 1 71 | int32_t _unknown1; // @34 72 | 73 | // Size (in relation to the barrel) of the bullet (or other) shot out. 74 | // Smaller for predator for example 75 | float bullet_size_ratio; // @38 76 | 77 | // Trapezoidal Face Dir 78 | // - Also known as `shootingAngle` 79 | // - Direction in radians that a trapezoidal barrel is facing. 80 | float trapezoidal_dir; // @3C 81 | 82 | // Base Reload 83 | // - The amount of ticks per each shot can be calculated by ceil((15 - reload stat points) * base reload); 84 | // - Defaults to 1 85 | float base_reload; // @40 86 | 87 | // Spread Multiplier 88 | // - The finding of this field was automatically generated, so we haven't looked into this much 89 | float spread_multiplier; // @44 90 | 91 | // Bullet Durability (Life Length) 92 | // - Relating to the overall life time of a bullet 93 | // - Example: Higher for trappers than for basic 94 | float bullet_durability; // @48 95 | 96 | // Unknown 97 | // 76 - 88 (excluding 88) are all ones. set by the CannonClass constructor 98 | int32_t _no_ideas[3]; 99 | 100 | // Force Fire 101 | // - The finding of this field was automatically generated, so we haven't looked into this much 102 | bool force_fire; // @58 103 | 104 | // Bullet Addon - executed on to a bullet when it is fired. 105 | int32_t bullet_addon; 106 | 107 | // Addon, determines addons to the barrel after render - for example: Traps (the only example) 108 | // - Values here change every update - they are indexes in the Function Table, for addon generation 109 | int32_t addon; // @60 110 | }; 111 | -------------------------------------------------------------------------------- /memory/structs/TankDefinition.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Tank Definitions have all data relating to 4 | tanks. `TankDefinition` structs are (probably) 5 | 244 bytes of size. 6 | 7 | In Emscripten, Pointers are 32 bit. 8 | */ 9 | 10 | // Tank Definitions are stored in a linked list, meaning each element 11 | // points to the next, they aren't all next to eachother in memory. 12 | // 13 | // The tank definitions are surprisingly not in order of id, but they 14 | // are in the same order every update - not fully sure what the order 15 | // they are in means yet. 16 | 17 | struct TankDefinition { 18 | // Points to the next tank definition, or its value is 0 19 | struct TankDefinition* next_tank; // @00 20 | 21 | // Tank ID (see extras/tanks.js) - all of these ints have the same value 22 | int32_t tank_id; // @04 23 | int32_t _tank_id2; // @08 24 | int32_t _tank_id3; // @0C 25 | 26 | // see cstr.js 27 | struct cstr name; // @10 28 | struct cstr upgrade_msg; // @1C 29 | 30 | // see vector.c 31 | // Vector of tank ids 32 | struct vector upgrades; // @28 - @30 33 | 34 | // Vector of barrel definitions - so, all to eachother (sizeof BarrelDefinition's == 100) 35 | struct vector barrels; // @34 - @3C 36 | 37 | // The level you need to be at to upgrade to this tank 38 | int32_t level_requirement; // @40 39 | 40 | // Field factor helps determine the fov, see extras/algo.md#fov 41 | float field_factor; // @44 42 | 43 | // For all tanks it is set to one, except for mothership which is 0.01 44 | // - Used in serverside knockback calculation 45 | // - Name pending 46 | float knockback_factor; // @48 47 | 48 | // Bools are one byte 49 | // For tanks that have a square body 50 | bool is_square; // @4C 51 | // True if the tank cannot shoot - example, necromancer 52 | bool no_shooting; // @4D 53 | // For tanks like manager 54 | bool can_go_invisible; // @4E 55 | // Not present in any tanks 56 | bool _unused_flag; // @4F 57 | 58 | // The minimum amount of movement required to trigger the uncloaking (increase opacity by visibility_rate) 59 | // - Always set to 0.23, with exception to landmine and manager, which is set to 0 60 | float min_movement; // @50 61 | // The opacity increase per tick while moving (for tanks that go invisible) 62 | // - 0.16 for Landmine 63 | // - 0.08 for Manager and Stalker 64 | float visibility_rate; // @54 - Always 0.8, except for landmine which is 1.6 65 | // The opacity decrease per tick 66 | // - 0.003 for Landmine 67 | // - 0.03 for Stalker and Manager 68 | float invisibility_rate; // @58 69 | 70 | // The following booleans are one byte in size 71 | // Unused boolean, always set to true 72 | bool _unknown2; // @5C 73 | // The following is only set for mothership, the assumption is that it lets the game know the entity has 16 sides 74 | bool is_hexadecagon; // @5D 75 | // The following is only set for predator, the assumption is that it allows for fov extending 76 | bool has_fov_ability; // @5E 77 | // The following is set for tanks that can no longer be selected in sandbox. They are considered dev mode tanks 78 | bool requires_devmode; // @5F 79 | 80 | // Determines stuff like auto turret, spike / smasher, and other weird stuff like if its an auto three 81 | // - Values here change every update - they are indexes in the Function Table, for addon generation 82 | // - Auto 5 + Auto 3 is possible and beautiful (: 83 | // - Pre addon is built before the barrel, post is build after 84 | int32_t pre_addon; // @60 85 | int32_t post_addon; // @64 86 | 87 | // Yeah, this is weird. The larger this property is, the smaller the border width is 88 | // - Someone should find out if this correlates with canvas or protocol, or both. 89 | // - Fairly sure more with canvas than protocol 90 | int32_t border_width; // @68 91 | 92 | // Defaults to 50, with dominator tanks it is 6000 93 | int32_t max_health; // @6C 94 | 95 | int32_t _unknown5; // @70 - not in my notes 96 | 97 | struct cstr stat_names[8]; // @74 - @D4 98 | int32_t stat_maxes[8]; // @D4 - @F4 99 | 100 | // Nothing in my notes whether this is the end or not. Ill update later 101 | }; 102 | -------------------------------------------------------------------------------- /canvas/color_constants.md: -------------------------------------------------------------------------------- 1 | # Color Constants 2 | 3 | ## Table of Contents 4 | 5 | - [Color Constants](#color-constants) 6 | - [Table of Contents](#table-of-contents) 7 | - [Colors](#colors) 8 | - [Shapes](#shapes) 9 | - [Team Colors](#team-colors) 10 | - [Others](#others) 11 | - [UI](#ui) 12 | - [Game mode buttons](#game-mode-buttons) 13 | - [Other buttons](#other-buttons) 14 | - [florr.io advert](#florrio-advert) 15 | - [digdig.io advert](#digdigio-advert) 16 | - [Gameplay colors](#gameplay-colors) 17 | - [Upgrade menu](#upgrade-menu) 18 | - [Random color facts](#random-color-facts) 19 | 20 | ## Colors 21 | 22 | If any of the sections reference a color, refer to it here. 23 | 24 | - Gray 25 | - fill: #999999 26 | - stroke: #727272 27 | - Blue 28 | - fill: #00b2e1 29 | - stroke: #0085a8 30 | - Red 31 | - fill: #f14e54 32 | - stroke: #b43a3f 33 | - Green 34 | - fill: #00e16e 35 | - stroke: #00a852 36 | - Purple 37 | - fill: #bf7ff5 38 | - stroke: #8f5fb7 39 | 40 | ## Shapes 41 | - Square 42 | - fill: #ffe869 43 | - stroke: #bfae4e 44 | - Triangle 45 | - fill: #fc7677 46 | - stroke: #bd5859 47 | - Pentagon 48 | - fill: #768dfc 49 | - stroke: #5869bd 50 | - Alpha Pentagon: same as pentagon 51 | - Crasher 52 | - fill: #f177dd 53 | - stroke: #b459a5 54 | - Small Crasher: same as small crasher 55 | 56 | ## Team Colors 57 | 58 | (Refer to the colors in the [Colors](#colors) section) 59 | 60 | ## Others 61 | 62 | - Tank barrel: gray 63 | 64 | ## UI 65 | 66 | - Text 67 | - fill: #ffffff 68 | - stroke: #000000 69 | 70 | ### Game mode buttons 71 | 72 | The dark colors are when the button is pressed. The light colors are when the cursor is hovering over the button. 73 | 74 | Note: All the darkened colors are essentially the same color but with all three color channels subtracted by 51. 75 | 76 | - FFA 77 | - fill: #8efffb 78 | - fill dark: #71ccc8 79 | - fill light: #a4fffb 80 | - Survival 81 | - fill: #b4ff8e 82 | - fill dark: #90cc71 83 | - fill light: #c3ffa4 84 | - 2 Teams 85 | - fill: #ff8e8e 86 | - fill dark: #cc7171 87 | - fill light: #ffa4a4 88 | - 4 Teams 89 | - fill: #ffeb8e 90 | - fill dark: #ccbc71 91 | - fill light: #ffefa4 92 | - Domination 93 | - fill: #8eb2ff 94 | - fill dark: #718ecc 95 | - fill light: #a4c1ff 96 | - Tag 97 | - fill: #b58eff 98 | - fill dark: #9071cc 99 | - fill light: #c3a4ff 100 | - Maze 101 | - fill: #fb8eff 102 | - fill dark: #c871cc 103 | - fill light: #fba4ff 104 | - Sandbox 105 | - fill: #fdcdac 106 | - fill dark: #caa489 107 | - fill light: #fdd7bc 108 | - Button borders 109 | - stroke: #333333 110 | 111 | ### Other buttons 112 | 113 | - Copy party link 114 | - fill: #adadad 115 | - fill dark: #8a8a8a 116 | - fill light: #bdbdbd 117 | 118 | ### florr.io advert 119 | 120 | - Flower body 121 | - fill: #ffe763 122 | - stroke: #cfbb50 123 | - Petals 124 | - fill: #ffffff 125 | - stroke: #cfcfcf 126 | 127 | ### digdig.io advert 128 | 129 | - Digger body 130 | - fill: #999999 131 | - stroke: #7c7c7c 132 | - Digger eyes 133 | - fill: #222222 134 | - Digger mouth 135 | - stroke: #222222 136 | - Digger pupil 137 | - fill: #eeeeee 138 | - Spinner 139 | - fill: #111111 140 | 141 | ### Gameplay colors 142 | 143 | Note: the gameplay colors are combined with transparency, so it may seem counterintuitive that items like the gray notification are actually completely black, but that's just black with transparency. 144 | 145 | - Grid 146 | - fill: #cdcdcd 147 | - stroke: #000000 148 | - Minimap background 149 | - fill: #cdcdcd 150 | - stroke: #555555 151 | - Minimap triangle 152 | - fill: #000000 153 | - Score bar 154 | - stroke: #43ff91 155 | - Level up bar 156 | - stroke: #ffde43 157 | - Health bar 158 | - stroke: #85e37d 159 | - Blue notification 160 | - fill: #0000ff 161 | - Gray notification 162 | - fill: #000000 163 | - Black fill background (outside area of arena and black background of score bars) 164 | - fill: #000000 165 | - White non-cheat name 166 | - fill: #ffffff 167 | - Yellow cheat name 168 | - fill: #ffff90 169 | 170 | ### Upgrade menu 171 | 172 | Random fact: the upgrade menu is actually drawn from bottom to top. 173 | 174 | - Health Regen 175 | - fill: #fcad76 176 | - Max Health 177 | - fill: #f943ff 178 | - Body Damage 179 | - fill: #8543ff 180 | - Bullet Speed 181 | - fill: #437fff 182 | - Bullet Penetration 183 | - fill: #ffde43 184 | - Bullet Damage 185 | - fill: #ff4343 186 | - Reload 187 | - fill: #82ff43 188 | - Movement Speed 189 | - fill: #43fff9 190 | 191 | ## Random color facts 192 | 193 | - When a tank or shape is hit, they quickly turn to red and back to their original color, giving them a nice hit effect. The exact hit animation color algorithm has yet to be researched. 194 | -------------------------------------------------------------------------------- /extras/stats.md: -------------------------------------------------------------------------------- 1 | # Stats 2 | 3 | In this document, the affects of skills/stats will be described. Each stat is listed in the table contents, along with what it affects and by how much. 4 | 5 | ### Table of Contents 6 | - [Table of Contents](#table-of-contents) 7 | - [Stats Breakdown](#stats-breakdown) 8 | - [Health Regen](#health-regen) 9 | - [Max Health](#max-health) 10 | - [Body Damage](#body-damage) 11 | - [Bullet Speed](#bullet-speed) 12 | - [Bullet Penetration](#bullet-penetration) 13 | - [Bullet Damage](#bullet-damage) 14 | - [Reload](#reload) 15 | - [Movement Speed](#movement-speed) 16 | 22 | 23 | ## Stats Breakdown 24 | A summary of the effects of each stat on the tank. 25 | - The variable `P` is the number of points invested in that stat. 26 | - The variable `L` is the current level of the tank. 27 | - The variable `M` is any additional tank multipliers, found in [tankdefs.json](/extras/tankdefs.json). 28 | 29 | --- 30 | 31 | ### Health Regen 32 | Regenerates more percentage of the tank's health per second: `0.1 + (0.4 * P)` 33 | - Each skill point increases the percentage by 0.4% 34 | - Base regeneration speed is 0.1% of health/sec. 35 | - "Hyper" regen takes priority after 30 seconds of not being hit, regenerating at 10% of health/sec (stacks with base) 36 | - Regeneration accelerates with time. 37 | 38 | ### Max Health 39 | Increases the tank's maximum health points: `50 + (2 * (L - 1)) + (20 * P)` 40 | - Each skill point increases the max HP by 20. 41 | - Base max HP, or the HP of a level 1 tank, is 50. 42 | - Every level up adds 2 to the max HP, so at level 45, max HP is already 138 HP. 43 | 44 | ### Body Damage 45 | Increases collision damage of tank to shape: `20 + (4 * P)` and tank to tank: `30 + (6 * P)` 46 | - Each skill point increases the damage of collisions; tank-shape by 4 points; tank-tank by 6 points. 47 | - The base damage for collsions; tank-shape is 20; tank-tank is 30. 48 | - Body damage deals 50% more damage for tank-tank than tank-shape collisions. 49 | 50 | ## Bullet Speed 51 | Increases the bullet's speed in background squares per second: `(5 + (4 * P)) * M` 52 | - Each skill point increases speed of the bullet by 4 squares/sec. 53 | - Base bullet speed is 5 squares/sec. 54 | - M is the overall multiplier for the bullet's barrel, usually 1, Destroyer is 0.699, Sniper is 1.5, etc. 55 | 56 | ### Bullet Penetration 57 | Increases the bullet's maximum health points: `(8 + (6 * P)) * M` 58 | - Each skill point increases the bullet's max HP by 6. 59 | - Base bullet's max HP is 8. 60 | - M is the overall multiplier for the bullet's barrel, usually 1, Destroyer is 2, Overlord's drones is 2, etc. 61 | 62 | ### Bullet Damage 63 | Increases the bullet's damage dealt: `(7 + (3 * P)) * M` 64 | - Each skill point increases the bullet's damage dealt by 3. 65 | - Base bullet's damage dealt is 7. 66 | - M is the overall multiplier for the bullet's barrel, usually 1, Destroyer is 3, Overlord's drones is 0.699, etc. 67 | 68 | ### Reload 69 | Will be posted a later date 70 | 71 | ### Movement Speed 72 | See for more information on movement, and the movement speed stat's effect on movement. 73 | 74 | --- 75 | 108 | -------------------------------------------------------------------------------- /entities.md: -------------------------------------------------------------------------------- 1 | # Entities 2 | 3 | This document will provide a perspective / overview on how the entire game's networking and entity management system works as a whole. 4 | 5 | From a developer standpoint, you can think of an entity as an object, it has properties that are stored/sent to the client. Entities are identified by a (not fully understood) system. In this system, there is a unique identifier for every entity, aka `id`. No two entities alive can share the same `id`. The hash part of the system tracks how many entities a specific `id` has been used for during the arena's uptime. This is for better analysis of desynchronizations. 6 | 7 | The following code was sent by Zeach in a screenshot during a conversation regarding the system. It shows how entity ids are encoded before being sent to the client. More information on entity id encodings can be found [here](/protocol/data.md#entid---vu-hash-vu-id). 8 | 9 | ```c++ 10 | inline void Encode(BinData &out) const { 11 | if(hash == 0){ 12 | out.PushVarUint32(0); 13 | } else { 14 | out.PushVarUint32(hash); 15 | out.PushVarUint32(id); 16 | } 17 | DEBUG_EntityManager("Encoded handle %d %d", int(id), int(hash)); 18 | } 19 | ``` 20 | 21 | > Disclaimer: The names and labels (except the entity system) we came up for entity related stuff are not from the game, so if you understand these concepts easier with another name, by all means, use a name that helps you understand this stuff. 22 | 23 | --- 24 | 25 | ## Fields 26 | 27 | Fields, as stated earlier, are basically the equivalent of how properties are to objects, but for entities. Fields are used to describe all things in the game. For example, the Arena entity has leaderX and leaderY fields, while also having fields that are related to other things like sides. Fields can also have multi-length values, like scoreboardNames, which is an array of scoreboard names (encoded as a xor jump table); also another property on the arena entity. For each build, all the fields are randomly shuffled (this is done in the builder) so that there is a randomly(?) selected index for each field; Being 68 fields, there are always 68 indexes. These field indexes are used in the protocol and to determine the field offsets in the memory. All fields and located [~~here~~ To be done.](about:blank) 28 | 29 | --- 30 | 31 | ## Field Groups + Purpose 32 | 33 | These fields are all organized into groups, known as field groups. For each field, there is a constant (throughout builds) id per field group. So for example, the field `maxHealth` is in the Health field group, and its field-group specific id is 2 across all updates. You can see the field groups and field ids per field [~~here~~ To be done.](about:blank). Here is the list of field group names by id (empty = deleted / not in code) 34 | ``` 35 | 0 : RELATIONSHIPS 36 | 1 : 37 | 2 : BARREL 38 | 3 : PHYSICS 39 | 4 : HEALTH 40 | 5 : 41 | 6 : UNUSED 42 | 7 : ARENA 43 | 8 : NAME 44 | 9 : GUI 45 | 10: POS 46 | 11: STYLE 47 | 12: 48 | 13: SCORE 49 | 14: TEAM 50 | ``` 51 | 52 | Field groups are also used for the organization of entities, but that will be explained more in the memory section. 53 | 54 | --- 55 | 56 | ## Parsing 57 | 58 | Only one packet is used to update entities in-game. The [0x00 Update Packet](/protocol/update.md#0x00-update-packet). Most of what will be said here will be explained more in-depth on that page. 59 | 60 | Entities can only be created, deleted, or updated in the game. In the 0x00 packet format, there are two arrays (`vu32(len), ...elems`). An array of [`entid`](/protocol/data.md#entid---vu-hash-vu-id)s to be deleted from the clients' entity storage, and an array of `upcreates`, which are either updates or creations. Creations contain full data about an entity, whereas updates only update specific fields at a time. More info, as stated earlier, is on the [0x00 Update Packet](/protocol/update.md#0x00-update-packet) page. 61 | 62 | --- 63 | 64 | ## Memory 65 | 66 | Due to how entities are parsed, all entities are stored in a double linked list, as shown [in this struct definition](/memory/structs/AbstractEntity.h). The values at offset `0x08` and `0x0C` point to the previous and next entity in the list accordingally. Surprisingly though, this linked list is only accessed (as far as we have seen) by the `0x00` packet parsers in the wasm. So how does the game access entity data without accessing the linked list? The answer lies in field groups 67 | 68 | All field groups, as explained above, have certain fields attached to them, and since only the data stored in fields need to be used to render, Zeach made the choice to store a list of all the field groups for each of the 12 groups. These [field groups](/memory/structs/AbstractFieldGroup.h) are stored in [vectors](/memory/structs/vector.h) consecutively in the memory. Right below the double linked list, there are 15 vectors which contain the field groups the game needs to access. 69 | 70 | Example: Every frame the game goes through the `PHYSICS` field group and renders all the data onto the screen, fetching its entity's POSITION and STYLE group for more information, if it exists. More info on how the game's insides work will be posted in `/wasm` or `/memory` soon™. 71 | 72 | 73 | -------------------------------------------------------------------------------- /protocol/connection/m28n_api.md: -------------------------------------------------------------------------------- 1 | # __WARNING: `m28n` API IS NO LONGER BEING USED FOR DIEP.IO AS OF JANUARY 2ND, 2022 - PLEASE SEE [`./rivet_api`](../rivet_api.md) FOR INFO ON THE NEW SYSTEM__ 2 | 3 | 4 | # [`M28N API`](https://api.n.m28.io/) 5 | 6 | The M28N api has only 3 api paths that relate to Diep: `/server`, `endpoint/diepio-${gamemode}`, and `endpoint/latency`. All but `/server` is necessary to the client when connecting to a gamemode server. 7 | 8 | First, it's worth mentioning that to connect to a server by id, append `.s.m28n.net` to its id. An example would be id=`abcd`, ws server=`wss://abcd.s.m28n.net`. 9 | 10 | --- 11 | 12 | ## [`/server`](https://api.n.m28.io/server/diepindepth) Endpoint 13 | 14 | This endpoint responds with info about a server via its ID. Unlike the other endpoints, this endpoint is part of the M28 api system and is not part of some sort of game or game server. 15 | 16 | URL -> `https://api.n.m28.io/server/{server-id}` 17 | 18 | Example request/response 19 | ```http 20 | GET /server HTTP/2 21 | 22 | HTTP/2 200 OK 23 | Host: api.n.m28.io 24 | Content-Type: application/json; charset=utf-8 25 | Content-Length: 87 26 | 27 | { 28 | "id": "bshn", 29 | "ipv4": "144.202.121.166", 30 | "ipv6": "2001:19f0:6001:3e70:5400:03ff:fe3e:fb56" 31 | } 32 | ``` 33 | 34 | --- 35 | 36 | ## DiepIO Game Server Endpoints 37 | 38 | This endpoint lists servers hosted on the m28 server api for diep.io. It is used to find a server by gamemode, one server per region is listed, so there may be more in the region than shown. 39 | 40 | URL `https://api.n.m28.io/endpoint/diepio-{gamemode}/findEach` 41 | 42 | Example request/response 43 | ```http 44 | GET /endpoint/diepio-sandbox/findEach HTTP/2 45 | 46 | HTTP/2 200 OK 47 | Host: api.n.m28.io 48 | Content-Type: application/json; charset=utf-8 49 | Content-Length: 523 50 | 51 | { 52 | "servers": { 53 | "vultr-la": { 54 | "id": "bshn", 55 | "ipv4": "144.202.121.166", 56 | "ipv6": "2001:19f0:6001:3e70:5400:03ff:fe3e:fb56" 57 | }, 58 | "vultr-miami": { 59 | "id": "bshs", 60 | "ipv4": "45.77.74.230", 61 | "ipv6": "2001:19f0:9002:16b8:5400:03ff:fe3e:fb60" 62 | }, 63 | "vultr-sydney": { 64 | "id": "bshx", 65 | "ipv4": "149.28.161.200", 66 | "ipv6": "2001:19f0:5801:1e07:5400:03ff:fe3e:fb61" 67 | }, 68 | "vultr-amsterdam": { 69 | "id": "bst3", 70 | "ipv4": "136.244.110.102", 71 | "ipv6": "2001:19f0:5001:08f5:5400:03ff:fe41:23bf" 72 | }, 73 | "vultr-singapore": { 74 | "id": "bsi0", 75 | "ipv4": "45.76.185.119", 76 | "ipv6": "2001:19f0:4400:4b74:5400:03ff:fe3e:fb63" 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | > There is also a gamemode-less request that gives all servers, regardless of gamemode, `https://api.n.m28.io/endpoint/diepio/findEach` - this is no longer being used in production. 83 | 84 | --- 85 | 86 | ## [`latency`](https://api.n.m28.io/endpoint/latency/findEach) Endpoint 87 | 88 | Similar to the others, this endpoint returns servers based on region, but they are not diep.io servers - they are latency servers. Upon connecting to these servers (via ws) and sending any data that is prefixed with a null byte, the server will echo back the same buffer. 89 | 90 | Diep.io uses these servers to find out the best region to connect to, by sending a 0x00 byte and then calculating the time between sending the byte and receiving the echo. Whichever has the least time between sending and receiving will be the region you will eventually connect to when selecting gamemode servers. 91 | 92 | URL -> `https://api.n.m28.io/endpoint/latency/findEach` 93 | 94 | Example request/response (minified for size) 95 | ```http 96 | GET /endpoint/latency/findEach HTTP/2 97 | 98 | HTTP/2 200 OK 99 | Host: api.n.m28.io 100 | Content-Type: application/json; charset=utf-8 101 | Content-Length: 1326 102 | 103 | {"servers":{"linode-fremont":{"id":"bdre","ipv4":"45.33.43.51","ipv6":null},"linode-dallas":{"id":"bot4","ipv4":"172.104.198.179","ipv6":null},"linode-singapore":{"id":"bmj2","ipv4":"172.104.35.61","ipv6":null},"linode-frankfurt":{"id":"bp4q","ipv4":"192.46.233.239","ipv6":null},"linode-newark":{"id":"ampj","ipv4":"50.116.59.175","ipv6":null},"linode-london":{"id":"boku","ipv4":"109.74.195.204","ipv6":null},"vultr-seattle":{"id":"aqfg","ipv4":"104.238.157.145","ipv6":"2001:19f0:8001:137b:5400:02ff:fe9e:201e"},"vultr-tokyo":{"id":"bees","ipv4":"66.42.32.35","ipv6":"2001:19f0:7001:5356:5400:02ff:fef8:4827"},"vultr-la":{"id":"bp97","ipv4":"104.207.153.155","ipv6":"2001:19f0:6001:3798:5400:03ff:fe2a:e5dc"},"vultr-miami":{"id":"blwx","ipv4":"207.246.71.211","ipv6":"2001:19f0:9002:2764:5400:03ff:fe18:6bba"},"vultr-frankfurt":{"id":"bemq","ipv4":"136.244.92.41","ipv6":"2a05:f480:1800:05ac:5400:02ff:fef9:d8be"},"vultr-sydney":{"id":"bnut","ipv4":"149.28.176.43","ipv6":"2401:c080:1800:4cf8:5400:03ff:fe23:13ae"},"vultr-chicago":{"id":"boy7","ipv4":"149.28.118.130","ipv6":"2001:19f0:5c01:1929:5400:03ff:fe29:83a7"},"vultr-amsterdam":{"id":"bnbb","ipv4":"45.63.43.99","ipv6":"2a05:f480:1400:0cb1:5400:03ff:fe1f:863f"},"vultr-singapore":{"id":"as8s","ipv4":"66.42.56.203","ipv6":"2001:19f0:4401:99e:5400:02ff:fe9e:f99a"}}} 104 | ``` 105 | -------------------------------------------------------------------------------- /protocol/userscripts/packethook.template: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Diep.io Packet WASM Hook 3 | // @author ABC 4 | // @version 1.0.4 5 | // @namespace %BUILD 6 | // @description %BUILD 7 | // @match *://diep.io/ 8 | // @run-at document-start 9 | // @require https://raw.githubusercontent.com/Qwokka/wail.min.js/5e32d36bd7a5e0830d1ff4b64d3587aea13f77da/wail.min.js 10 | // @grant none 11 | // ==/UserScript== 12 | "use strict"; 13 | 14 | /* 15 | Usage is explained in the console during execution. 16 | 17 | Generated for build %BUILD of the Addicting Games release branch 18 | */ 19 | 20 | class PacketHook extends EventTarget { 21 | static get CONST() { 22 | return { 23 | BUILD: "%BUILD", 24 | SEND_PACKET_INDEX: %SEND_PACKET_INDEX, 25 | RECV_PACKET_INDEX: %RECV_PACKET_INDEX, 26 | MALLOC: "%MALLOC", 27 | FREE: "%FREE", 28 | SOCKET_PTR: %SOCKET_PTR 29 | } 30 | } 31 | 32 | constructor(hook) { 33 | super(); 34 | this.HEAPU8 = new Uint8Array(0); 35 | this.HEAP32 = new Int32Array(0); 36 | this.wasm = null; 37 | this._inject(hook); 38 | this._hijack(); 39 | } 40 | _modify(bin, imports) { 41 | console.log('Modifying WASM'); 42 | 43 | const wail = new WailParser(new Uint8Array(bin)); 44 | 45 | const sendPacket = wail.getFunctionIndex(PacketHook.CONST.SEND_PACKET_INDEX); 46 | const recvPacket = wail.getFunctionIndex(PacketHook.CONST.RECV_PACKET_INDEX); 47 | 48 | const mainHook = wail.addImportEntry({ 49 | moduleStr: "hook", 50 | fieldStr: "mainHook", 51 | kind: "func", 52 | type: wail.addTypeEntry({ 53 | form: "func", 54 | params: ["i32", "i32", "i32"], 55 | returnType: "i32" 56 | }) 57 | }); 58 | wail.addExportEntry(sendPacket, { 59 | fieldStr: "sendPacket", 60 | kind: "func", 61 | }); 62 | wail.addExportEntry(recvPacket, { 63 | fieldStr: "recvPacket", 64 | kind: "func", 65 | }); 66 | 67 | 68 | wail.addCodeElementParser(null, function({ index, bytes }) { 69 | 70 | if (index === sendPacket.i32()) { 71 | return new Uint8Array([ 72 | OP_I32_CONST, 1, 73 | OP_GET_LOCAL, 1, 74 | OP_GET_LOCAL, 2, 75 | OP_CALL, ...VarUint32ToArray(mainHook.i32()), 76 | OP_IF, VALUE_TYPE_BLOCK, 77 | OP_RETURN, 78 | OP_END, 79 | ...bytes 80 | ]); 81 | } else if (index === recvPacket.i32()) { 82 | return new Uint8Array([ 83 | OP_I32_CONST, 0, 84 | OP_GET_LOCAL, 0, 85 | OP_GET_LOCAL, 1, 86 | OP_CALL, ...VarUint32ToArray(mainHook.i32()), 87 | OP_IF, VALUE_TYPE_BLOCK, 88 | OP_RETURN, 89 | OP_END, 90 | ...bytes 91 | ]); 92 | } 93 | 94 | return false; 95 | }); 96 | 97 | wail.parse(); 98 | 99 | return wail.write(); 100 | } 101 | 102 | _inject(mainHook) { 103 | const _initWasm = WebAssembly.instantiate; 104 | WebAssembly.instantiate = (bin, imports) => { 105 | bin = this._modify(bin, imports); 106 | 107 | imports.hook = { mainHook }; 108 | 109 | 110 | return _initWasm(bin, imports).then((wasm) => { 111 | this.wasm = wasm.instance; 112 | 113 | const memory = Object.values(this.wasm.exports).find(e => e instanceof WebAssembly.Memory); 114 | 115 | this.HEAPU8 = new Uint8Array(memory.buffer); 116 | this.HEAP32 = new Int32Array(memory.buffer); 117 | 118 | this.malloc = this.wasm.exports[PacketHook.CONST.MALLOC]; 119 | this.free = this.wasm.exports[PacketHook.CONST.FREE]; 120 | 121 | console.log('Module exports done!\n\t- Hook.free\n\t- Hook.malloc\n\t- Hook.send\n\t- Hook.recv\n\t- Hook.addEventListener(\'clientbound\', ({data}) => console.log(data));\n\t- Hook.addEventListener(\'serverbound\', ({data}) => console.log(data));'); 122 | 123 | return wasm 124 | }).catch(err => { 125 | console.error('Error in loading up wasm:'); 126 | 127 | throw err; 128 | }) 129 | }; 130 | } 131 | 132 | _hijack() { 133 | const that = this; 134 | window.Object.defineProperty(Object.prototype, "dynCall_v", { 135 | get() {}, 136 | set(dynCall_v) { 137 | delete Object.prototype.dynCall_v 138 | this.dynCall_v = dynCall_v; 139 | 140 | that.Module = this; 141 | console.log('Module exports done! Hook.Module'); 142 | }, 143 | configurable: true, 144 | }); 145 | } 146 | 147 | send(buf) { 148 | const { malloc, free, HEAP32, HEAPU8 } = this; 149 | 150 | buf = new Uint8Array(buf); 151 | 152 | const ptr = malloc(buf.byteLength); 153 | HEAPU8.set(buf, ptr); 154 | 155 | this.wasm.exports.sendPacket(HEAP32[PacketHook.CONST.SOCKET_PTR >> 2], ptr, buf.byteLength); 156 | 157 | free(ptr); 158 | } 159 | 160 | recv(buf) { 161 | const { malloc, free, HEAP32, HEAPU8 } = this; 162 | 163 | buf = new Uint8Array(buf); 164 | 165 | const ptr = malloc(buf.byteLength); 166 | HEAPU8.set(buf, ptr); 167 | 168 | this.wasm.exports.recvPacket(ptr, buf.byteLength) 169 | free(ptr); 170 | } 171 | } 172 | 173 | WebAssembly.instantiateStreaming = (r, i) => r.arrayBuffer().then(b => WebAssembly.instantiate(b, i)); 174 | 175 | const TYPE = ['clientbound', 'serverbound']; 176 | 177 | const Hook = window.Hook = new PacketHook(function(type, ptr, len) { 178 | Hook.dispatchEvent(new MessageEvent(TYPE[type], { 179 | data: Hook.HEAPU8.subarray(ptr, ptr + len) 180 | })); 181 | 182 | return 0; 183 | }); 184 | -------------------------------------------------------------------------------- /protocol/userscripts/packethook.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Diep.io Packet WASM Hook 3 | // @author ABC 4 | // @version 1.0.4 5 | // @namespace a3ce2b4ebba76d7dade922071a981a87a9430316 6 | // @description a3ce2b4ebba76d7dade922071a981a87a9430316 7 | // @match *://diep.io/ 8 | // @run-at document-start 9 | // @require https://raw.githubusercontent.com/Qwokka/wail.min.js/5e32d36bd7a5e0830d1ff4b64d3587aea13f77da/wail.min.js 10 | // @grant none 11 | // ==/UserScript== 12 | "use strict"; 13 | 14 | /* 15 | Usage is explained in the console during execution. 16 | 17 | Generated for build a3ce2b4ebba76d7dade922071a981a87a9430316 of the Addicting Games release branch 18 | */ 19 | 20 | class PacketHook extends EventTarget { 21 | static get CONST() { 22 | return { 23 | BUILD: "a3ce2b4ebba76d7dade922071a981a87a9430316", 24 | SEND_PACKET_INDEX: 120, 25 | RECV_PACKET_INDEX: 457, 26 | MALLOC: "ua", 27 | FREE: "R", 28 | SOCKET_PTR: 110916 29 | } 30 | } 31 | 32 | constructor(hook) { 33 | super(); 34 | this.HEAPU8 = new Uint8Array(0); 35 | this.HEAP32 = new Int32Array(0); 36 | this.wasm = null; 37 | this._inject(hook); 38 | this._hijack(); 39 | } 40 | _modify(bin, imports) { 41 | console.log('Modifying WASM'); 42 | 43 | const wail = new WailParser(new Uint8Array(bin)); 44 | 45 | const sendPacket = wail.getFunctionIndex(PacketHook.CONST.SEND_PACKET_INDEX); 46 | const recvPacket = wail.getFunctionIndex(PacketHook.CONST.RECV_PACKET_INDEX); 47 | 48 | const mainHook = wail.addImportEntry({ 49 | moduleStr: "hook", 50 | fieldStr: "mainHook", 51 | kind: "func", 52 | type: wail.addTypeEntry({ 53 | form: "func", 54 | params: ["i32", "i32", "i32"], 55 | returnType: "i32" 56 | }) 57 | }); 58 | wail.addExportEntry(sendPacket, { 59 | fieldStr: "sendPacket", 60 | kind: "func", 61 | }); 62 | wail.addExportEntry(recvPacket, { 63 | fieldStr: "recvPacket", 64 | kind: "func", 65 | }); 66 | 67 | 68 | wail.addCodeElementParser(null, function({ index, bytes }) { 69 | 70 | if (index === sendPacket.i32()) { 71 | return new Uint8Array([ 72 | OP_I32_CONST, 1, 73 | OP_GET_LOCAL, 1, 74 | OP_GET_LOCAL, 2, 75 | OP_CALL, ...VarUint32ToArray(mainHook.i32()), 76 | OP_IF, VALUE_TYPE_BLOCK, 77 | OP_RETURN, 78 | OP_END, 79 | ...bytes 80 | ]); 81 | } else if (index === recvPacket.i32()) { 82 | return new Uint8Array([ 83 | OP_I32_CONST, 0, 84 | OP_GET_LOCAL, 0, 85 | OP_GET_LOCAL, 1, 86 | OP_CALL, ...VarUint32ToArray(mainHook.i32()), 87 | OP_IF, VALUE_TYPE_BLOCK, 88 | OP_RETURN, 89 | OP_END, 90 | ...bytes 91 | ]); 92 | } 93 | 94 | return false; 95 | }); 96 | 97 | wail.parse(); 98 | 99 | return wail.write(); 100 | } 101 | 102 | _inject(mainHook) { 103 | const _initWasm = WebAssembly.instantiate; 104 | WebAssembly.instantiate = (bin, imports) => { 105 | bin = this._modify(bin, imports); 106 | 107 | imports.hook = { mainHook }; 108 | 109 | 110 | return _initWasm(bin, imports).then((wasm) => { 111 | this.wasm = wasm.instance; 112 | 113 | const memory = Object.values(this.wasm.exports).find(e => e instanceof WebAssembly.Memory); 114 | 115 | this.HEAPU8 = new Uint8Array(memory.buffer); 116 | this.HEAP32 = new Int32Array(memory.buffer); 117 | 118 | this.malloc = this.wasm.exports[PacketHook.CONST.MALLOC]; 119 | this.free = this.wasm.exports[PacketHook.CONST.FREE]; 120 | 121 | console.log('Module exports done!\n\t- Hook.free\n\t- Hook.malloc\n\t- Hook.send\n\t- Hook.recv\n\t- Hook.addEventListener(\'clientbound\', ({data}) => console.log(data));\n\t- Hook.addEventListener(\'serverbound\', ({data}) => console.log(data));'); 122 | 123 | return wasm 124 | }).catch(err => { 125 | console.error('Error in loading up wasm:'); 126 | 127 | throw err; 128 | }) 129 | }; 130 | } 131 | 132 | _hijack() { 133 | const that = this; 134 | window.Object.defineProperty(Object.prototype, "dynCall_v", { 135 | get() {}, 136 | set(dynCall_v) { 137 | delete Object.prototype.dynCall_v 138 | this.dynCall_v = dynCall_v; 139 | 140 | that.Module = this; 141 | console.log('Module exports done! Hook.Module'); 142 | }, 143 | configurable: true, 144 | }); 145 | } 146 | 147 | send(buf) { 148 | const { malloc, free, HEAP32, HEAPU8 } = this; 149 | 150 | buf = new Uint8Array(buf); 151 | 152 | const ptr = malloc(buf.byteLength); 153 | HEAPU8.set(buf, ptr); 154 | 155 | this.wasm.exports.sendPacket(HEAP32[PacketHook.CONST.SOCKET_PTR >> 2], ptr, buf.byteLength); 156 | 157 | free(ptr); 158 | } 159 | 160 | recv(buf) { 161 | const { malloc, free, HEAP32, HEAPU8 } = this; 162 | 163 | buf = new Uint8Array(buf); 164 | 165 | const ptr = malloc(buf.byteLength); 166 | HEAPU8.set(buf, ptr); 167 | 168 | this.wasm.exports.recvPacket(ptr, buf.byteLength) 169 | free(ptr); 170 | } 171 | } 172 | 173 | WebAssembly.instantiateStreaming = (r, i) => r.arrayBuffer().then(b => WebAssembly.instantiate(b, i)); 174 | 175 | const TYPE = ['clientbound', 'serverbound']; 176 | 177 | const Hook = window.Hook = new PacketHook(function(type, ptr, len) { 178 | Hook.dispatchEvent(new MessageEvent(TYPE[type], { 179 | data: Hook.HEAPU8.subarray(ptr, ptr + len) 180 | })); 181 | 182 | return 0; 183 | }); 184 | -------------------------------------------------------------------------------- /protocol/data.md: -------------------------------------------------------------------------------- 1 | # **Data** 2 | 3 | ## **Encoding Types** 4 | 5 | | Name | Description | Size | Alias | 6 | | ------------ | ---------------------------------------------------------------------------------------- | ---- | -------- | 7 | | `uint8` | An unsigned 8 bit integer | 1 | `u8` | 8 | | `varuint32` | An unsigned variable-length integer using [LEB128](https://en.wikipedia.org/wiki/LEB128) | 1-5 | `vu` | 9 | | `varint32` | A signed variable-length integer | 1-5 | `vi` | 10 | | `varfloat32` | A float32 casted to a varint | 1-5 | `vf` | 11 | | `float32` | A little-endian floating point number | 4 | `f32` | 12 | | `uint32` | A little-endian 32 bit integer | 4 | `u32` | 13 | | `stringNT` | A null terminated string | 1+ | `string` | 14 | 15 | ### **Special Types** 16 | 17 | --- 18 | 19 | ### **`color`** - `vu` 20 | 21 | A varuint encoded `netcolor` id. Sent from Zeach (besides comments) himself, the color list: 22 | 23 | ```c++ 24 | /* 25 | 18 Net Colors 26 | Once >= 18, the game rejects drawing the color 27 | */ 28 | 29 | enum class ID { 30 | Border, 31 | Cannon, 32 | Tank, 33 | TeamBlue, 34 | TeamRed, 35 | TeamPurple, 36 | TeamGreen, 37 | Shiny, 38 | EnemySquare, 39 | EnemyTriangle, 40 | EnemyPentagon, 41 | EnemyCrasher, 42 | Neutral, 43 | ScoreboardBar, 44 | Box, 45 | EnemyTank, 46 | NecromancerSquare, 47 | Fallen, 48 | 49 | kMaxColors 50 | } 51 | ``` 52 | 53 | --- 54 | 55 | ### **`tank`** - `vi` 56 | 57 | A varint encoded tank id, defaults to -1. Tanks are listed [here](/extras/tanks.js), and their in-depth definitions [here](/extras/tankdefs.json) 58 | 59 | --- 60 | 61 | ### **`bitflags`** - `vu` 62 | 63 | A varuint encoded series of bit flags with varying values. An example would be the serverbound input packet: 64 | 65 | ``` 66 | 00000001 ; u8 ; header 67 | 10011001 00010000 ; vu ; flags 68 | 10001000 10000001 10000101 00000010 ; vf ; mouse x 69 | 10001010 10100111 10000001 00001111 ; vf ; mouse y 70 | ``` 71 | 72 | The flags are read as a varuint, which for this example results in 2073. This can be expressed in binary as `0000000011001`, and each bit gets parsed as a boolean flag starting from the lowest bit to the highest bit like the following: 73 | 74 | ``` 75 | 1 ; left mouse ; (on) 76 | 0 ; up key ; (off) 77 | 0 ; left key ; (off) 78 | 1 ; down key ; (on) 79 | 1 ; right key ; (on) 80 | 0 ; god mode toggle ; (off) 81 | 0 ; suicide key ; (off) 82 | 0 ; right mouse ; (off) 83 | 0 ; instant upgrade ; (off) 84 | 0 ; use joysticks ; (off) 85 | 0 ; use gamepad ; (off) 86 | 0 ; switch class ; (off) 87 | 0 ; adblock ; (off) 88 | ``` 89 | 90 | --- 91 | 92 | ### **`entid`** - `` 93 | 94 | A way of storing hash and id is used in the system. Sent from Zeach (besides comments), the server's entid encoder: 95 | 96 | ```c++ 97 | /* 98 | In the Reader's perspective, this means: 99 | - Hash is read as a varuint32 (aka vu) 100 | - When hash is 0, stop reading 101 | - If hash is not 0, read the id as well 102 | 103 | Read Result: 104 | {hash} or {hash, id} 105 | */ 106 | inline void Encode(BinData &out) const { 107 | if(hash == 0){ 108 | out.PushVarUint32(0); 109 | } else { 110 | out.PushVarUint32(hash); 111 | out.PushVarUint32(id); 112 | } 113 | DEBUG_EntityManager("Encoded handle %d %d", int(id), int(hash)); 114 | } 115 | ``` 116 | 117 | The id: A universal identifier that is unique while the entity is alive, but once the entity is destroyed - the id may be used by another entity in the future. **IDs are unique, but can be recycled** 118 | The hash: This seems to be the total amount of times a specific id has been used/recycled. 119 | 120 | **Quick Misconception,** in Diep's console, sometimes you'll see messages relating to _"possible desyncs"_ and alongside them, you'll see two numbers inside of these ``, these are NOT ``. Unlike the order they are read, entids are logged as `` in the Diep console. 121 | 122 | --- 123 | 124 | ### **Data Organization** 125 | 126 | Inside of 0x00 packets, there is data stored in a table-like form of data, which has come to be known as a "jump table". Jump tables work differently than standard arrays. They are parsed by retrieving indexes from jumps, then parsing values based on that index. The best way to explain the format is with an example, here is an example of a field group identification jump table, in this case, indexes without any values. 127 | 128 | ``` 129 | 130 | initial index = -1 131 | 132 | 01 ; u8 ; Jump Table Start (not always present) 133 | 00 ; vu ; xored jump = 0x00 = 0 134 | ; true jump = xored jump ^ 1 = 1 135 | ; index += true jump =-1 + 1 = 0 136 | no value being read 137 | 138 | 02 ; vu ; xored jump = 0x02 = 2 139 | ; true jump = xored jump ^ 1 = 3 140 | ; index += true jump = 0 + 3 = 3 141 | no value being read 142 | 143 | 00 ; vu ; xored jump = 0x00 = 0 144 | ; true jump = xored jump ^ 1 = 1 145 | ; index += true jump = 3 + 1 = 4 146 | no value being read 147 | 148 | 05 ; vu ; xored jump = 0x05 = 5 149 | ; true jump = xored jump ^ 1 = 4 150 | ; index += true jump = 4 + 4 = 8 151 | no value being read 152 | 153 | 03 ; vu ; xored jump = 0x03 = 3 154 | ; true jump = xored jump ^ 1 = 2 155 | ; index += true jump = 8 + 2 = 10 156 | no value being read 157 | 158 | 00 ; vu ; xored jump = 0x00 = 0 159 | ; true jump = xored jump ^ 1 = 1 160 | ; index +=true jump = 10 + 1 = 11 161 | no value being read 162 | 163 | 03 ; vu ; xored jump = 0x03 = 3 164 | ; true jump = xored jump ^ 1 = 2 165 | ; index +=true jump = 11 + 2 = 13 166 | no value being read 167 | 168 | 01 ; u8 ; Jump Table End 169 | ``` 170 | 171 | The resulting indexes from this jump table were [0, 3, 4, 5, 8, 10, 11, 13]. More about what these values mean in [`clientbound.md`](/protocol/clientbound.md) 172 | 173 | And for more understanding, here are two jump table readers written in Javascript and C++ 174 | 175 | ```c++ 176 | template 177 | auto jumpTable(function read) { 178 | 179 | std::vector table; 180 | int index = -1; // Starting Index 181 | int currentJump = 0; 182 | 183 | while(1) { 184 | currentJump = this->vu() ^ 1; // Read vu() and XOR 1 to retrieve the jump 185 | 186 | if(currentJump == 0) break; // If there is no jump, exit 187 | 188 | index += currentJump; // Jump to the next index 189 | table.push_back(read(index)); // Read according to index 190 | } 191 | return table; 192 | } 193 | ``` 194 | 195 | and in javascript 196 | 197 | ```js 198 | jumpTable(read) { 199 | const table = []; 200 | let index = -1; // Starting Index 201 | let currentJump = 0; 202 | 203 | while (true) { 204 | currentJump = this.vu() ^ 1; // Read vu() and XOR 1 to retrieve the jump 205 | 206 | if (!currentJump) break; // If there is no jump, exit 207 | 208 | index += currentJump; // Jump to the next index 209 | table[table.length++] = read.call(this, index); // Read according to index 210 | } 211 | 212 | return table; 213 | } 214 | ``` 215 | 216 | Understanding this data structure takes practice, at first it may be difficult to understand, but over time it will be easily understandable and identifiable. 217 | -------------------------------------------------------------------------------- /extras/addons.md: -------------------------------------------------------------------------------- 1 | # Addon Information 2 | 3 | Raw values and other information about addons in tank definitions. 4 | 5 | > Random Notes: 6 | > - a square shaped tank's base size (half width) is `32.5√2` 7 | > - a hexadecagon shaped tank's base size (half width) is `25√2` 8 | 9 | --- 10 | 11 | ## Addons 12 | 13 | - [Addon Information](#addon-information) 14 | - [Addons](#addons) 15 | - [Dominator Base Addon](#dominator-base-addon) 16 | - [Smasher Addon](#smasher-addon) 17 | - [Landmine Addon](#landmine-addon) 18 | - [Spike Addon](#spike-addon) 19 | - [Auto Smasher Addon](#auto-smasher-addon) 20 | - [Auto Turret Addon](#auto-turret-addon) 21 | - [Auto 5 Addon](#auto-5-addon) 22 | - [Auto 3 Addon](#auto-3-addon) 23 | - [Pronounced Addon](#pronounced-addon) 24 | - [Pronounced Dominator Addon](#pronounced-dominator-addon) 25 | - [Launcher Addon](#launcher-addon) 26 | 27 | ## Dominator Base Addon 28 | 29 | What I like to call a Guard Addon, the Dominator Base Addon has one object attached to the player, that rotates separately from it - which is decided by the `absoluteRotation` flag in the `motion` field. 30 | 31 | | Key | Value | Desc | 32 | | ---------------- | ------ | ----------------------------------------------------------------------------- | 33 | | Sides | `6` | The shape of a dombase addon is a hexagon | 34 | | Size Ratio | `1.24` | The hexagon's size in game is the ratio multiplied to the tank's current size | 35 | | Radians Per Tick | `0.0` | The hexagon's rotation per tick, in radians. Aka doesn't spin | 36 | 37 | ## Smasher Addon 38 | 39 | Another Guard Addon. The Smasher Addon has one object attached to the player, that rotates separately from it. 40 | 41 | | Key | Value | Desc | 42 | | ---------------- | ------ | ----------------------------------------------------------------------------- | 43 | | Sides | `6` | The shape of a smasher addon is a hexagon | 44 | | Size Ratio | `1.15` | The hexagon's size in game is the ratio multiplied to the tank's current size | 45 | | Radians Per Tick | `0.1` | The hexagon's rotation per tick, in radians | 46 | 47 | ## Landmine Addon 48 | 49 | Another Guard Addon. The Landmine Addon has two objects attached to the player, that rotate separately from it. 50 | 51 | **Object 1:** 52 | | Key | Value | Desc | 53 | | ---------------- | ------ | ----------------------------------------------------------------------------- | 54 | | Sides | `6` | The first shape of a landmine addon is a hexagon | 55 | | Size Ratio | `1.15` | The hexagon's size in game is the ratio multiplied to the tank's current size | 56 | | Radians Per Tick | `0.1` | The hexagon's rotation per tick, in radians | 57 | 58 | **Object 2:** 59 | | Key | Value | Desc | 60 | | ---------------- | ------ | ----------------------------------------------------------------------------- | 61 | | Sides | `6` | The second shape of a landmine addon is also a hexagon | 62 | | Size Ratio | `1.15` | The hexagon's size in game is the ratio multiplied to the tank's current size | 63 | | Radians Per Tick | `0.05` | The hexagon's rotation per tick, in radians | 64 | 65 | ## Spike Addon 66 | 67 | Another Guard Addon. The Spike Addon has four objects attached to the player, that all rotate separately from it. 68 | 69 | **Object 1:** 70 | | Key | Value | Desc | 71 | | ---------------- | ------ | ------------------------------------------------------------------------------ | 72 | | Sides | `3` | The first shape of a spike addon is a triangle | 73 | | Size Ratio | `1.3` | The triangle's size in game is the ratio multiplied to the tank's current size | 74 | | Offset Angle | `0.0` | The default angle of the triangle. The offset - in radians | 75 | | Radians Per Tick | `0.17` | The triangle's rotation per tick, in radians | 76 | 77 | **Object 2:** 78 | | Key | Value | Desc | 79 | | ---------------- | ------- | ------------------------------------------------------------------------------ | 80 | | Sides | `3` | The second shape of a spike addon is also a triangle | 81 | | Size Ratio | `1.3` | The triangle's size in game is the ratio multiplied to the tank's current size | 82 | | Offset Angle | `π / 3` | The default angle of the triangle. The offset - in radians | 83 | | Radians Per Tick | `0.17` | The triangle's rotation per tick, in radians | 84 | 85 | **Object 3:** 86 | | Key | Value | Desc | 87 | | ---------------- | ------- | ------------------------------------------------------------------------------ | 88 | | Sides | `3` | The third shape of a spike addon is a triangle | 89 | | Size Ratio | `1.3` | The triangle's size in game is the ratio multiplied to the tank's current size | 90 | | Offset Angle | `π / 6` | The default angle of the triangle. The offset - in radians | 91 | | Radians Per Tick | `0.17` | The triangle's rotation per tick, in radians | 92 | 93 | **Object 4:** 94 | | Key | Value | Desc | 95 | | ---------------- | ------- | ------------------------------------------------------------------------------ | 96 | | Sides | `3` | The fourth shape of a spike addon is a triangle | 97 | | Size Ratio | `1.3` | The triangle's size in game is the ratio multiplied to the tank's current size | 98 | | Offset Angle | `π / 2` | The default angle of the triangle. The offset - in radians | 99 | | Radians Per Tick | `0.17` | The triangle's rotation per tick, in radians | 100 | 101 | ## Auto Smasher Addon 102 | 103 | The Auto Smasher Addon has both a Guard Addon, and an Auto Turret. 104 | 105 | **Object 1:** (Guard) 106 | | Key | Value | Desc | 107 | | ---------------- | ------ | ----------------------------------------------------------------------------- | 108 | | Sides | `6` | The shape of the first object in the Auto Smasher addon is a hexagon | 109 | | Size Ratio | `1.15` | The hexagon's size in game is the ratio multiplied to the tank's current size | 110 | | Radians Per Tick | `0.1` | The hexagon's rotation per tick, in radians | 111 | 112 | **Object 2:** See the [Auto Turret Addon](#auto-turret-addon) 113 | 114 | ## Auto Turret Addon 115 | 116 | Auto Turret is a mounted turret controlled by AI. Its shape was only somewhat able to be reduced from the memory. 117 | 118 | | Key | Value | Desc | 119 | | ------------------ | ------- | ------------------------------------------------------------------------------ | 120 | | Turret Width Ratio | `1.1` | The length (longer side) of the turret barrel is `1.1x` the radius of the tank | 121 | | Turret Size Ratio | `0.588` | The size of the turret barrel is `0.56x` the radius of the tank | 122 | | Turret Base Ratio | `0.5` | The radius of the base is half the radius of the tank | 123 | 124 | ## Auto 5 Addon 125 | 126 | The Auto 5 addon creates `5` auto turrets equidistant from the center of the tank's body. Distance is approximately `0.8` - not yet confirmed. 127 | 128 | ## Auto 3 Addon 129 | 130 | The Auto 3 addon creates `3` auto turrets equidistant from the center of the tank's body. Distance is approximately `0.8` - not yet confirmed. 131 | 132 | ## Pronounced Addon 133 | 134 | ## Pronounced Dominator Addon 135 | 136 | ## Launcher Addon 137 | 138 | Launcher is a smaller trapezoid shape below the front of a tank - see the below picture. It is added before the barrel.
lancher
(Skimmer's barrel has been made translucent for viewing purposes). 139 | 140 | | Key | Value | Desc | 141 | | -------------------- | ------- | -------------------------------------------------------------------------- | 142 | | Launcher Width Ratio | `1.31` | The length (longer side) of the launcher is `1.31x` the radius of the tank | 143 | | Launcher Size Ratio | `0.672` | The width of the launcher is `0.56x` the radius of the tank | 144 | -------------------------------------------------------------------------------- /protocol/crypto.md: -------------------------------------------------------------------------------- 1 | # Packet Encoding and Decoding 2 | 3 | Also known as shuffling/unshuffling, this encryption system is what used to annoy many of the people brave enough to attempt headless botting. For each packet, a newly generated XOR table is used to shuffle/unshuffle the packet. The header jump table stays constant and does not change for every packet. We have fully reverse-engineered every part of the encryption system and will discuss it below. 4 | 5 | There are 4 things you need to know to understand the system: 6 | 1. Pseudo Random Number Generators 7 | 2. Header Jump Tables 8 | 3. Content Xor Tables 9 | 4. Praise M28 10 | 11 | ## Pseudo Random Number Generators 12 | 13 | Pseudo random number generators are algorithms which generate sequences of numbers which seem random. For a more informative definition of a PRNG, check [Wikipedia](https://en.wikipedia.org/wiki/Pseudorandom_number_generator). Internal PRNGs within the game's wasm/memory are used to generate each packet's necessary values to encrypt or decrypt data. During each build, the seed and algorithm in each of the PRNGs used are modified slightly. That way, they always seem random each build, but they are actually quite predictable if you know what defines them. 14 | 15 | ### The Three Types 16 | 1. **Linear Congruential Generator**\ 17 | Shortened to LCG, this type of PRNG is probably the most well known as it is also used in Java's native [Random](https://docs.oracle.com/javase/8/docs/api/java/util/Random.html) API. A basic implementation is shown since it would take a great deal of time to explain. Do your own research ([here's a wikipedia link](https://en.wikipedia.org/wiki/Linear_congruential_generator)). 18 | ```ts 19 | class LCG implements PRNG { 20 | constructor(seed, multiplier, increment, modulus) { 21 | this.seed = seed; 22 | // these defining factors are all big ints, so that calculation is precise (the seed is an integer though) 23 | this._multiplier = multiplier; 24 | this._increment = increment; 25 | this._modulus = modulus; 26 | } 27 | next() { 28 | const nextSeed = (BigInt(this.seed >>> 0) * this._multiplier + this._increment) % this._modulus; 29 | 30 | this.seed = Number(nextSeed & 0xFFFFFFFFn) | 0; // safely convert to a 32 bit integer 31 | 32 | return this.seed; 33 | } 34 | } 35 | ``` 36 | \ 37 | 2. **Xor Shift**\ 38 | This type of PRNG XORs the seed by SHIFTed versions of itself every new generation. A basic implementation is shown and [here's a wikipedia link](https://en.wikipedia.org/wiki/Xorshift). Do your own research. 39 | ```ts 40 | class XorShift implements PRNG { 41 | constructor(seed, a, b, c) { 42 | this.seed = seed; 43 | 44 | // the actual shifts 45 | this._a = a; 46 | this._b = b; 47 | this._c = c; 48 | } 49 | next() { 50 | this.seed ^= this.seed << this._a; 51 | this.seed ^= this.seed >>> this._b; 52 | this.seed ^= this.seed << this._c; 53 | 54 | return this.seed; 55 | } 56 | } 57 | ``` 58 | \ 59 | 3. **Triple LCG**\ 60 | This type of PRNG isn't standard (or wasn't found), but we have called it the Triple LCG since it is 3 separate LCG's combined into one pseudo random number generator. As always, do your own research (in the game's wasm), but a code sample shown below. 61 | ```ts 62 | class TripleLCG implements PRNG { 63 | constructor(a, b, c) { 64 | this.lcgA = new LCG(a.seed, a.multiplier, a.increment, 0x100000000n); 65 | this.lcgB = new LCG(b.seed, b.multiplier, b.increment, 0x100000000n); 66 | this.lcgC = new LCG(c.seed, c.multiplier, c.increment, 0x100000000n); 67 | } 68 | 69 | next() { 70 | const a = this.lcgA.next(); 71 | const b = this.lcgB.next(); 72 | const c = this.lcgC.next(); 73 | 74 | return (a + b + c) | 0; 75 | } 76 | } 77 | ``` 78 | 79 | ## Header Jump Tables (s-box) 80 | > The technical, cryptographic term for this structure is a Subsitution Box, s-box for short. 81 | 82 | These are arrays of 128 bytes generated with PRNGs that are used to shuffle the headers of packets. In this section we will discuss how to generate these tables and how to use / apply them to incoming / outgoing headers. 83 | 84 | The generation of a jump table is fairly simple. First, the client will generate an uint8 array of length 128, where each value is its index. Then, the client will shuffle the array using the [Fisher Yates shuffle algorithm](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) and a PRNG. This PRNG is called the `jumpTableShuffler`, and its PRNG type as well as algorithm seeds change every new build. 85 | 86 | It is worth noting that the clientbound and serverbound jump tables differ slightly. As known, the 0x00 serverbound packet is never shuffled, and hence it should be impossible for any header to ever, after being encoded, become a 0x00 packet by accident. For this reason, only indices 1 - 127 inclusive are actually shuffled, so that 0, and only 0, jumps to 0. In the clientbound jump table though, the 0x01 packet must be preserved. So, after shuffling the entire jump table (0 - 128 inclusive), the game searches for the number 1 inside of the shuffled table, and swaps it with the element at index 1. Finally, to get the decryption jump table of any such encryption jump table, you must inverse it. A code sample is shown. 87 | ```ts 88 | function generateJumpTable(isClientBound: boolean) { 89 | const jumpTableShuffler = new PRNG(...); 90 | const table = new Uint8Array(128).map((_, i) => i); 91 | 92 | for (let i = 127; i >= 1; i--) { 93 | const index = ((jumpTableShuffler.next() >>> 0) % (isClientBound ? i + 1 : i)) + (isClientBound ? 0 : 1); 94 | 95 | const temp = table[index]; 96 | table[index] = table[i]; 97 | table[i] = temp; 98 | } 99 | 100 | if (isClientBound) table[table.indexOf(1)] = table[1] = 1; 101 | 102 | return { 103 | encryptionTable: table, 104 | decryptionTable: table.map((n, i, l) => l.indexOf(i)) 105 | } 106 | } 107 | ``` 108 | \ 109 | To apply a jump table on a packet header (to encrypt it), you must "jump" or subsitute from value to value a certain number of times, then return the final jump/substitution. Another internal PRNG, which we've named `jumpCount`, is made (one for encryption and decryption) to determine the number of times it subsitutes/jumps. A full example of this is shown. 110 | ```ts 111 | // This example is only encryption, pretty obvious how to change to decryption though. 112 | const jumpCountPRNG = new PRNG(...); 113 | 114 | function encryptHeader(header: number, isClientBound: boolean) { 115 | const { encryptionTable } = generateJumpTable(isClientBound: boolean); 116 | const jumpCount = (jumpCountPRNG.next() >>> 0) % 16; 117 | let position = header; 118 | 119 | for (let i = 0; i <= jumpCount; i++) position = encryptionTable[position]; 120 | 121 | return position; 122 | } 123 | ``` 124 | 125 | ## Content Xor Tables 126 | 127 | These are tables generated with PRNGs that are used to shuffle the content of packets. The generation of a xor table is simple and fully understood, and its application onto the packet content is even simpler. 128 | 129 | The generation of xor tables are similar to the generation of a jump table, except the values of the initial, unshuffled table are not just their index in the array but instead, each value is generated from another PRNG, which we've called the `xorTable` PRNG. The length of the xor table changes every build, and the length is not the same for serverbound and clientbound packets - an unrelated cryptographic system is used for each. As for the shuffling of the xor table, it the [Fisher Yates shuffle algorithm](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) yet again. Code sample shown 130 | ```js 131 | const XOR_TABLE_SIZE = ...; // Changes per build 132 | const xorTablePRNG = new PRNG(...); 133 | const xorTableShuffler = new PRNG(...); 134 | 135 | function generateXorTable() { 136 | const table = new Uint8Array(XOR_TABLE_SIZE).map((_, i) => xorTablePRNG.next()); 137 | 138 | for (let i = XOR_TABLE_SIZE - 1; i >= 0; i--) { 139 | const index = (xorTableShuffler.next() >>> 0) % (i + 1); 140 | 141 | const temp = table[index]; 142 | table[index] = table[i]; 143 | table[i] = temp; 144 | } 145 | 146 | // Due to how the table is applied, the same table can be used for both decryption and encryption. 147 | // Read the next passage for more information. 148 | return table; 149 | } 150 | ``` 151 | \ 152 | All you need to do to apply an xor table to a packet is XOR each byte in the xor table with the corresponding byte in the packet, excluding the header. See below. 153 | 154 | ```ts 155 | export function encryptPacket(packet: Uint8Array, isClientBound: boolean): void { 156 | // Ignored packets = Incoming 0x01, Outgoing 0x00. 157 | if (isClientbound && packet[0] === 0x01) return; 158 | if (!isClientbound && packet[0] === 0x00) return; 159 | 160 | packet[0] = encryptHeader(packet[0], isClientBound); 161 | 162 | const xorTable = generateXorTable(); 163 | for (let i = 1; i < packet.length; i++) packet[i] ^= xorTable[i % XOR_TABLE_SIZE]; 164 | } 165 | ``` 166 | 167 | ## Praise M28 168 | 169 | You'll notice that after parsing [`0x00` Update packets](./update.md), there are always a couple of seemingly random bytes at the end, unused by the actual parser. When the first of these bytes is an odd integer, all the seeds used for clientbound decryption are incremented by 28. We have called this process **Praise M28** accordingly. 170 | -------------------------------------------------------------------------------- /protocol/serverbound.md: -------------------------------------------------------------------------------- 1 | # DISCLAIMER: SINCE THE PURCHASE OF DIEP.IO BY ADDICTING GAMES, LESS RESEARCH HAS BEEN PUT INTO CHANGES BY THE NEW DEV TEAM - FOR THIS REASON, SOME OF THIS MAY BE INACCURATE ON NEWER VERSIONS 2 | 3 | # **Serverbound Packets** 4 | 5 | Also known as outgoing, these packets, after being encoded, are sent from the client to the server. These packets aren't at all complex, only with the exception of the serverbound `0x01` packet which is the most complex of the serverbound packets (but not too complex). 6 | 7 | For information on data types and encodings, see [`data.md`](/protocol/data.md) 8 | 9 | | Header | Name | Description | 10 | | -------------------------------------- | --------------- | ------------------------------------------------------------------ | 11 | | [`0x00`](#0x00-init-packet) | Init | Initiates the connection between the client and server | 12 | | [`0x01`](#0x01-input-packet) | Input | Sends client inputs including movement and mouse | 13 | | [`0x02`](#0x02-spawn-packet) | Spawn Packet | Sent when the client wants to spawn, contains chosen name | 14 | | [`0x03`](#0x03-stat-upgrade-packet) | Stat Upgrade | Upgrades the player's stats | 15 | | [`0x04`](#0x04-tank-upgrade-packet) | Tank Upgrade | Upgrades the player's tank | 16 | | [`0x05`](#0x05-heartbeat-packet) | Heartbeat | Ping pong packet | 17 | | [`0x06`](#0x06-tcp-init-packet) | TCP Init | Lets the server "acknowledge" tcp connections. Unknown | 18 | | [`0x07`](#0x07-extension-found-packet) | Extension Found | Sent when the client detects any modifications made to the game | 19 | | [`0x08`](#0x08-to-respawn-packet) | To Respawn | Sent when the client leaves the death screen, and moves to respawn | 20 | | [`0x09`](#0x09-take-tank-packet) | Take Tank | Sent when the client wants to control another tank (Dom for ex) | 21 | | [`0x0A`](#0x0a-pow-answer-packet) | PoW Answer | Sends the solved answer for the proof of work challenge | 22 | | [`0x0B`](#0x0b-js-result-packet) | JS Result | Sends the result of the js challenge sent by the server | 23 | 24 | --- 25 | 26 | ## **`0x00` Init Packet** 27 | 28 | The first packet, and the only unencoded one. This packet is sent to initiate the connection and it contains information such as the build, admin password, party link, and some other "debug values." 29 | 30 | Format: 31 | > `00 string(build hash) string(dev password) string(party code) string(player token) vu(debug val)` 32 | 33 | The dev confirmed that this format is correct, but did not give any information on the varuint at the end of the packet, only that it was only active during "debug builds". If the build hash sent by the client is not the same as the one the server is expecting, the server responds with a [`0x01` Outdated Client](/protocol/clientbound.md#0x01-outdated-client-packet) packet. 34 | 35 | --- 36 | 37 | ## **`0x01` Input Packet** 38 | 39 | The most frequently sent packet coming from the client, it includes movement flags, mouse pos, and other input data. 40 | 41 | ``` 42 | bit ; name ; desc 43 | x0001 ; fire ; Set when left mouse / spacebar is pressed down or autofire is on 44 | x0002 ; up key ; Set when the up key is pressed down 45 | x0004 ; left key ; Set when the left key is pressed down 46 | x0008 ; down key ; Set when the down key is pressed down 47 | x0010 ; right key ; Set when the right key is pressed down 48 | x0020 ; god mode toggle ; Set when god mode is toggled 49 | x0040 ; suicide key ; Set when the suicide key is pressed down 50 | x0080 ; right mouse ; Set when shift / right click is pressed down 51 | x0100 ; instant upgrade ; Set when upgrade key is pressed down 52 | x0200 ; use joysticks ; Set when user isn't using keyboard movement flags, and instead two magnitudes (x-axis and y-axis) 53 | x0400 ; use gamepad ; Set when a gamepad is being used instead of a keyboard 54 | x0800 ; switch class ; Set when switch class key is pressed down 55 | x1000 ; adblock ; Remnant of when the game detected adblockers 56 | ``` 57 | 58 | For information on how these are encoded, see [`data.md`](/protocol/data.md#bitflags---vu) where the example is actually a sample input packet. If the `use joysticks` flag is set, then two additional varfloats are appended to the packet, representing the x-axis movement and the y-axis movement. The `use gamepad` has no actual affect on the game and likely just serves for analytical purposes. 59 | 60 | Format: 61 | > `01 flags(input flags) vf(world mouse x) vf(world mouse y) joysticks?[vf(x axis) vf(y axis)]` 62 | 63 | --- 64 | 65 | ## **`0x02` Spawn Packet** 66 | 67 | This packet creates a spawning attempt; we call it an attempt / request because the server waits for you to solve a [Proof of Work challenge](/protocol/clientbound.md#0x0b-pow-challenge-packet) first before spawning you in. If you are waiting to be spawned in (due to PoW or game starting countdown / players needed), sending another one will change the name you will spawn in with. 68 | 69 | Format: 70 | > `02 stringNT(name)` 71 | 72 | --- 73 | 74 | ## **Magic Tank and Stat XOR** 75 | 76 | The next 2 packets use a shuffler that is derived from the following function: 77 | 78 | ```js 79 | function magicNum(build) { 80 | for (var i = 0, seed = 1, res = 0, timer = 0; i < 40; i++) { 81 | let nibble = parseInt(build[i], 16); 82 | res ^= ((nibble << ((seed & 1) << 2)) << (timer << 3)); 83 | timer = (timer + 1) & 3; 84 | seed ^= !timer; 85 | }; 86 | 87 | return res >>> 0; // unsigned 88 | } 89 | 90 | // Where the magic shuffler is 91 | magicNum(latest build) 92 | ``` 93 | 94 | --- 95 | 96 | ## **`0x03` Stat Upgrade Packet** 97 | 98 | This packet requests to upgrade one of the tank's stats. If you don't have an adequate amount of levels for the next stat, nothing happens. The second varint in the packet identifies the maximum upgrade level of that stat. Only if the level of the stat is less than the maximum or if the maximum is negative will an upgrade be performed. Note that the stat index is 0-indexed and numbered in reverse order of the stats as we know it (i.e. `packet stat index = 8 - normal stat index`). For example, movement speed has index 0, and bullet damage has index 2. Here's a quick example: 99 | 100 | ```js 101 | Current Stats: 4/3/2/1/4/3/2/1 102 | 103 | serverbound -> 03 vi(2 ^ stat xor) vi(-1) 104 | Current Stats: 4/3/2/1/4/4/2/1 // will always upgrade since stat max is negative 105 | 106 | serverbound -> 03 vi(5 ^ stat xor) vi(2) 107 | Current Stats: 4/3/2/1/4/4/2/1 // nothing happens since 5th stat is >= 2 108 | 109 | serverbound -> 03 vi(5 ^ stat xor) vi(3) 110 | Current Stats: 4/3/3/1/4/4/2/1 // upgrades since 5th stat is less than 3 111 | ``` 112 | 113 | Format: 114 | > `03 vi(stat index ^ stat xor) vi(stat max)` 115 | 116 | Where stat xor is (using the function up above): 117 | 118 | ```js 119 | magicNum(latest build) % STAT_COUNT; // STAT_COUNT is 8 120 | ``` 121 | 122 | --- 123 | 124 | ## **`0x04` Tank Upgrade Packet** 125 | 126 | This packet is sent to upgrade to a tank. Although it takes the tank id as a parameter in the packet, if the tank selected is not in your upgrade path, or you don't have enough levels to reach it, nothing will happen. The [tank id](/extras/tanks.js) is XOR'ed by a remainder of the magicNum, very similar to the `0x03` serverbound packet. This is similar to the stat upgrading packet: an attempt to prevent scripting or automatic upgrading of tanks. 127 | 128 | Format: 129 | > `04 vi(tank id ^ tank xor)` 130 | 131 | Where tank xor is (using the function up above): 132 | 133 | ```js 134 | magicNum(latest build) % TANK_COUNT; // TANK_COUNT is 54 135 | ``` 136 | 137 | --- 138 | 139 | ## **`0x05` Heartbeat Packet** 140 | 141 | Part of the game's latency system. Once sent, the server immediately echoes the single-byte [`0x05`](/protocol/clientbound.md#0x05-heartbeat-packet) packet back. 🏓 142 | 143 | Format: 144 | > `05` 145 | 146 | Sample Packet and Response (Decoded): 147 | 148 | ``` 149 | serverbound -> 05 150 | 151 | response: 152 | clientbound <- 05 153 | ``` 154 | 155 | --- 156 | 157 | ## **`0x06` TCP Init Packet** 158 | 159 | This packet has never been observed and has only been seen in server code images sent by M28. The following code is the only information we have on it: 160 | ```c++ 161 | }else if(cmd == 0x06){ // Acknowledged ES packet 162 | #ifndef USE_TCP_ES 163 | if(m_pGame != nullptr){ 164 | m_pGame->Simulation()->Entities()->Acknowledged(ID(), view.NextUint32()); 165 | } 166 | #endif 167 | ... code after unknown 168 | ``` 169 | 170 | He also talks about it being related to the Mobile version of the game and is involved in a TCP connection used on mobile. 171 | 172 | 173 | Format: 174 | > `06 u32(unknown) ...unknown` 175 | 176 | 177 | --- 178 | 179 | ## **`0x07` Extension Found Packet** 180 | 181 | 182 | 183 | --- 184 | 185 | ## **`0x08` To Respawn Packet** 186 | 187 | This peculiar single-byte packet is sent whenever you move past the death screen into the respawn screen. The use of this is so that if the arena is closed the server can redirect you to a new arena or disconnect you to cause the client to connect to another server. An example of this would be after an arena is closing in domination, when you move to the respawn screen you will be redirected to the next game. 188 | 189 | Format: 190 | > `08` 191 | 192 | --- 193 | 194 | ## **`0x09` Take Tank Packet** 195 | 196 | This packet is for requesting to control a tank, like a dominator. It can be sent in any gamemode, but if there is no available tank to take, then a [notification](/protocol/clientbound.md#0x03-notification-packet) with the text *"Someone has already taken that tank"* is sent. 197 | 198 | Format: 199 | > `09` 200 | 201 | --- 202 | 203 | ## **`0x0A` PoW Answer Packet** 204 | 205 | This packet is the response to the [`0x0B` PoW Challenge](/protocol/clientbound.md#0x0b-pow-challenge-packet) packet - after solving the Proof of Work challenge, the answer is sent. If the resulting answer is wrong and does not solve the given PoW prefix, the server will terminate the connection and ban/blacklist the client's ip from further communication. 206 | 207 | Format: 208 | > `0A stringNT(answer)` 209 | 210 | --- 211 | 212 | ## **`0x0B` JS Result Packet** 213 | 214 | This packet is the evaluated result of the [`0x0D` Int JS Challenge](/protocol/clientbound.md#0x0d-int-js-challenge-packet) packet. It sends the evaluation id and the result. The server will immediately close an existing connection in the case that the result is invalid (when the result does not match the expected response). This does NOT ban the client's ip address from further reconnection to the same server. In older builds, this packet could also be a response to `0x0C` JS String Challenge, which is now no longer fully existing; so this packet is able to encode any type of result, meaning that it could send a string or integer. 215 | 216 | Format: 217 | > `0B vu(id) any/vu(result)` 218 | -------------------------------------------------------------------------------- /physics/README.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Diep.io Physics 5 | ABC 6 | 7 | 8 | I will only share what I have discovered or proven myself 9 | 10 | ================================================================= 11 | 12 | ———————————————————————— §1 Introduction ———————————————————————— 13 | 14 | ———————————————————————— §2 Entity Sizes ———————————————————————— 15 | 16 | Size is a field in an entity's physics field group in diep.io. It 17 | is the radius used for collision calculation in game. All 18 | entities (except Maze Walls, Bases, and Arenas) are circles 19 | during collision calculation. 20 | 21 | §2.1 Shape Sizes (Collision Radi) 22 | Name ————————————: Rounded ——: Value ——— 23 | Square : 38.890872 : 55√2 / 2 24 | Triangle : 38.890872 : 55√2 / 2 25 | Crasher (small) : 24.748737 : 35√2 / 2 26 | Crasher (large) : 38.890872 : 55√2 / 2 27 | Pentagon : 53.033008 : 75√2 / 2 28 | Alpha Pentagon : 141.42135 : 200√2/ 2 29 | —————————————————:———————————:—————————— 30 | 31 | §2.2 Tank Sizes 32 | —————————————————:——————————————————————— 33 | Tank Size Factor : 1.01 ^ (lvl - 1) 34 | AC Size Factor : Tank Size Factor(AC lvl) * 3.5 35 | Body Size : sizeFactor * baseSize 36 | Where 37 | : baseSize for a 16 sided tank = 25√2 38 | : baseSize for a 4 sided tank = 32.5√2 39 | 40 | §2.2 Rectangular Sizes 41 | 42 | As mentioned before, rectangular entities such as Maze Walls do 43 | not work the same as entities of 1 or 3+ sides. Rectangles use 44 | two properties instead of one to determine overall size. The 45 | width property and the height property. In the game's protocol, 46 | the height property reuses the normal size property, so its 47 | purpose changes depending on side count. The width property is 48 | the entity's full width, and its height property is its full 49 | height. Keep in mind that all entities by default are rotated at 50 | 0rad (radians), so the larger the height, the wider it looks by 51 | default / without rotation. 52 | 53 | —————————————————————————— §3 Movement —————————————————————————— 54 | 55 | All movement in the game share similar properties, but the entity 56 | type ultimately determines how it moves. Firstly, all entities 57 | have a 10% friction rate - meaning that all velocity is reduced 58 | 10% each tick. The following formula defines the movement of an 59 | entity which was previously moving at the speed of 10du/t after n 60 | ticks, where n ≥ 0. 61 | 62 | ƒ(n) = 10 * 0.9^n (du/t) 63 | 64 | Friction is constant and is applied before the first physics calc 65 | ulation in a tick. 66 | 67 | §3.1 Reduction from Angle Difference 68 | 69 | I didn't research or prove this myself, so I will not explain it. 70 | 71 | §3.2 Tank Acceleration 72 | 73 | See the desmos graphs at desmos.com/calculator/begleuv7di for 74 | information, since I don't want to explain something I've already 75 | described in a visual form (I'll still put the formulas below for 76 | the sake of having them there). 77 | 78 | Base Acceleration = A_0 79 | Movement Speed Stat = m_s 80 | Level = l 81 | 82 | A_0 = 2.55 * (1.07^m_s) / 1.015^(l-1) (du/t) 83 | 84 | §3.3 Max Speed Calculation 85 | 86 | After opening the desmos graph, you will see that the equation 87 | for entity speed with A acceleration after n ticks is: 88 | 89 | ƒ(n) = 10 * A * (1 - 0.9^n) (du/t) 90 | 91 | It is very easy, after looking at this formula, to see that as 92 | parameter n approaches infinity, the result of the function will 93 | get closer to 10 * A. And so, the formula for max speed from 94 | acceleration A is 10 * A. 95 | 96 | §3.4 Shape Velocity 97 | 98 | Never looked into this fully. 99 | 100 | §3.5 Projectile Acceleration 101 | 102 | §3.5.1 Bullet (Fundamental) Speed 103 | 104 | The speed of a bullet is determined by its initial speed and its 105 | base acceleration that goes on throughout the bullets life time. 106 | The initial speed of a bullet is 30du/t + the bullet's base 107 | acceleration max speed (§3.3). See below for the formula for a 108 | bullet's base acceleration: 109 | 110 | Bullet Acceleration = b_A 111 | Bullet Speed Stat = b_s 112 | Bullet Definition Speed (see /extras/tankdefs.js) = B_S 113 | 114 | b_A = (2 + b_s * 0.3) * B_S (du/t) 115 | 116 | and so bullet initial speed is equal to A * 10 + 30 (du/t). 117 | 118 | The bullet acceleration and initial speed is present in all 119 | projectiles, with Traps being an exception (see §3.5.2). In 120 | Drones, the bullet acceleration and initial speed are used to 121 | determine speed the drone travels at each direction. 122 | 123 | §3.5.2 Trap Speed 124 | 125 | Traps are just bullets without base acceleration, which is why 126 | they stop moving shortly after they fire. 127 | 128 | §3.6 Recoil 129 | 130 | Recoil is the acceleration applied to a tank after it shoots a 131 | bullet. Recoil's direction is the opposite of the barrel's angle, 132 | and the actual acceleration applied at that angle is equal to 133 | this equation: 134 | 135 | Bullet Definition Recoil (see /extras/tankdefs.js) = B_R 136 | Bullet Recoil (Acceleration) = b_R 137 | 138 | b_R = 2 * B_R (t/s) 139 | 140 | §3.6 Rectangular Knockback 141 | 142 | (soon) 143 | 144 | ————————————————————————— §4 Knockback —————————————————————————— 145 | 146 | §4.1 General Knockback 147 | 148 | There are two properties that determine the knockback an entity 149 | receives after a collision. We have named them `pushFactor` and 150 | `absorbtionFactor`. The knockback applied to an entity is simple. 151 | 152 | Entity1's absorbtionFactor = e1_aF 153 | Entity2's pushFactor = e2_pF 154 | Entity1's Knockback Receival (Acceleration) = e1_A 155 | 156 | e1_A = e_aF * e2_pF (t/s) 157 | 158 | Where e1_A is the knockback in form of acceleration applied to 159 | Entity1 after colliding with Entity2. See §4.3 for a list of 160 | entities along with their respective pushFactor and 161 | absorbtionFactor. 162 | 163 | §4.2 Maze Wall Knockback 164 | 165 | Haven't fully looked into yet. 166 | 167 | §4.3 Entity pushFactor and absorbtionFactor values 168 | 169 | §4.3.1 Constants 170 | 171 | — Entity "Type" ———————————: pushFactor ———: absorbtionFactor ——— 172 | default : 8.0 : 1.0 173 | Mothership (tank too) : 8.0 : 0.01 174 | Bosses : 8.0 : 0.01 175 | Arena Closers : 8.0 : 0.0 176 | Maze Walls : 2.0 : 0.0 177 | Crasher (small) : 12.0 : 2.0 178 | Crasher (large) : 12.0 : 0.1 179 | Pentagon : 11.0 : 0.5 180 | Alpha Pentagon : 11.0 : 0.05 181 | Drone (factory+necro too) : 4.0 : 182 | ———————————————————————————:———————————————:————————————————————— 183 | 184 | §4.3.2 Bullet Factors 185 | 186 | Bullet Damage Stat = b_DS 187 | Bullet Damage (see /extras/tankdefs.js) = B_D 188 | Bullet Absorbtion Factor (see /extras/tankdefs.js) = B_aF 189 | 190 | absorbtionFactor = B_aF 191 | pushFactor = (7 / 3 + b_DS) * B_D * absorbtionFactor 192 | 193 | Absorbtion factor is almost always 1 except for a couple of 194 | special cases. Notice, the `* absorbtionFactor` in the pushFactor 195 | formula means that pushFactor scales with absorbtionFactor. 196 | 197 | ——————————————————————————— §5 Damage ——————————————————————————— 198 | 199 | Damage works very similarly to knockback, except it differs a bit 200 | when entities kill eachother. Like knockback, each entity has a 201 | predefined amount of damage that they can deal per tick. But, 202 | the if the amount of damage being done in a tick is more than the 203 | health of the enemy, then both the damage per tick of the enemy 204 | and main entity have to be decreased. This was first documented 205 | by Aznatf and the Spade Squad. 206 | 207 | > If the HP of an object is lower then the DPL (DPL'') of the 208 | > opponent the corresping DPL of it is proportional to the ratio 209 | > of its health and the opponent's DPL (DPL''). 210 | > - Spade Squad 211 | 212 | $5.1 Example 213 | 214 | Let entity 1 deal 6 damage per tick, and let it have 10 health. 215 | Let entity 2 deal 4 damage per tick, and let it have 3 health. 216 | 217 | After one collision, it is obvious that entity 2 will die, and 218 | entity 1 will survive... but with how much health? 219 | 220 | Well, since entity 2 has only 3 health, entity 1's damager per 221 | tick cannot be fully applied,so entity 1 deals 3 damage per tick. 222 | Now, we measure what percent of the maximum damager per tick was 223 | applied. 224 | 225 | 3 / 6 = 1 / 2 226 | 227 | So entity 1 dealed half of its damage per tick to kill entity 2, 228 | therefore entity 2, according to the law stated above, must deal 229 | half of its damage per tick to entity 1, before dying. 4 * 1 / 2 230 | damage per tick is dealt onto entity 1 finally. 231 | 232 | §5.1.1 Summary of Example 233 | 234 | Entity 1 = { health = 10, damage per tick = 6 } 235 | Entity 2 = { health = 3, damage per tick = 4 } 236 | 237 | Entity 1 deals half of its maximum damage per tick, since 3 is 6 238 | divided by 2. 239 | 240 | Entity 2 deals half of its maximum damage per tick, since entity 241 | 1 did. 242 | 243 | After collision: 244 | 245 | Entity 1 = { health = 8, damage per tick = 6 } 246 | Entity 2 = { health = 0, damage per tick = 4 } 247 | 248 | §5.2 Entity damagePerTick values. 249 | 250 | §5.2.1 Constants 251 | 252 | — Entity "Type" ———————————: damagePerTick —————————————————————— 253 | default : 8.0 254 | Pentagon : 12.0 255 | Alpha Pentagon : 20.0 256 | Crasher (small) : 4.0 257 | Team Base : 5.0 258 | ———————————————————————————:————————————————————————————————————— 259 | 260 | §5.2.2 Bullet Damage per Tick 261 | 262 | Bullet Damage Stat = b_DS 263 | Bullet Damage (see /extras/tankdefs.js) = B_D 264 | 265 | damagePerTick = (7 + B_D * 3) * b_DS; 266 | 267 | §5.2.3 Tank Damage per Tick 268 | 269 | Body Damage Stat = b_DS 270 | 271 | damagePerTick = b_DS * 6 + 20 272 | 273 | §5.2.3.1 Spike 274 | 275 | Praise spike, this tank has 50% more damage per tick. Calculated 276 | as 277 | 278 | damagePerTick = b_DS * 9 + 30 279 | 280 | §5.3 Bullet Damage Reduction 281 | 282 | All bullets (traps and drones included) have a 75% damage 283 | reduction. This means that after colliding with a entity with *N* 284 | damage per tick,a bullet can only recieve maximum *N* / 4 damage. 285 | 286 | §5.4 Tank on Tank Collisions [Outlier] 287 | 288 | When two tanks collide with eachother, both of the entities deal 289 | 50% more damage per tick. 290 | 291 | ———————————————————————————— §6 Misc ———————————————————————————— 292 | 293 | $6.1 Barrel Reload 294 | 295 | §6.2 Bullet Life Length 296 | 297 | §6.3 Death Animation 298 | 299 | ———————————————————————————— LICENSE ———————————————————————————— 300 | 301 | Copyright 2021 ABCxFF 302 | 303 | Licensed under the Apache License, Version 2.0 (the "License"); 304 | you may not use this file except in compliance with the License. 305 | You may obtain a copy of the License at 306 | 307 | http://www.apache.org/licenses/LICENSE-2.0 308 | 309 | Unless required by applicable law or agreed to in writing, 310 | software distributed under the License is distributed on an "AS 311 | IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 312 | express or implied. See the License for the specific language 313 | governing permissions and limitations under the License. 314 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 ABCxFF 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /protocol/clientbound.md: -------------------------------------------------------------------------------- 1 | # DISCLAIMER: SINCE THE PURCHASE OF DIEP.IO BY ADDICTING GAMES, LESS RESEARCH HAS BEEN PUT INTO CHANGES BY THE NEW DEV TEAM - FOR THIS REASON, SOME OF THIS MAY BE INACCURATE ON NEWER VERSIONS# **Clientbound Packets** 2 | 3 | Also known as incoming packets, after being encoded, they are sent from the server to the client. Most of these packets aren't too complex once you understand the basics of a reader, with the exception of the clientbound [`0x00`](#0x00-update-packet) packet. 4 | 5 | For information on data types and encodings, see [`data.md`](/protocol/data.md) 6 | 7 | | Header | Name | Description | 8 | | --------------------------------------- | ----------------- | ------------------------------------------------------------ | 9 | | [`0x00`](#0x00-update-packet) | Update | Creates, updates, and deletes objects and entities | 10 | | [`0x01`](#0x01-outdated-client-packet) | Outdated Client | Response to invalid build in the init packet | 11 | | [`0x02`](#0x02-compressed-packet) | Compressed Packet | LZ4 compressed packet of any header | 12 | | [`0x03`](#0x03-notification-packet) | Notification | Sends notifications in game | 13 | | [`0x04`](#0x04-server-info-packet) | Server Info | Send information about the server, host & region | 14 | | [`0x05`](#0x05-heartbeat-packet) | Heartbeat | Ping pong packet | 15 | | [`0x06`](#0x06-party-code-packet) | Party Code | Sends the party code if available | 16 | | [`0x07`](#0x07-accept-packet) | Accept | Sent after initial handshake, on client acceptance | 17 | | [`0x08`](#0x08-achievement-packet) | Achievement | Updates clientside achievements from the server | 18 | | [`0x09`](#0x09-invalid-party-packet) | Invalid Party | Sent when the party in the init packet is invalid | 19 | | [`0x0A`](#0x0a-player-count-packet) | Player Count | Global count of clients connected | 20 | | [`0x0B`](#0x0b-pow-challenge-packet) | PoW Challenge | Sends a required proof of work challenge | 21 | | [`0x0C`](#0x0c-unnamed-packet) | Unnamed | Unnamed, Unused packet | 22 | | [`0x0D`](#0x0d-int-js-challenge-packet) | Int JS Challenge | Sends (obfuscated) js code to be evaluated. Result is an int | 23 | 24 | --- 25 | 26 | ## **`0x00` Update Packet** 27 | 28 | Contains created/updated/deleted entities and current ingame time. There is too much to explain here, it'd be easier just putting it in [`update.md`](/protocol/update.md). 29 | 30 | --- 31 | 32 | ## **`0x01` Outdated Client Packet** 33 | 34 | Sent when the client's build (which is sent in the [`0x00` Init Packet](/protocol/serverbound.md#0x00-init-packet)) is not the same as the server's. The server sends the latest build, and when the client receives it, the page reloads. This packet is by nature unencoded, meaning its data is sent raw, unencoded. This is since if you have an invalid build you won't be able to decode packets properly. 35 | 36 | Format: 37 | > `01 stringNT(new build)` 38 | 39 | Sample Packet and Response: 40 | 41 | ```js 42 | clientbound <- 01 stringNT("c94fc18cf6171f8d502f5c979488e143f1e5f74f") 43 | 44 | response: // Reversed from source, see /wasm/ for more information on reversal 45 | function reload(version /* new build, read as a string from the packet */) { 46 | if (window["setLoadingStatus"]) window["setLoadingStatus"]("Updating..."); 47 | setTimeout(function() { 48 | window.location.reload(true) 49 | }, 2e3) 50 | } 51 | } 52 | reload() 53 | ``` 54 | 55 | --- 56 | 57 | ## **`0x02` Compressed Packet** 58 | 59 | When a clientbound packet is big enough, the server sends you a compressed version of the packet instead. The compression algorithm used is LZ4, and it is explained [here](https://fastcompression.blogspot.com/2011/05/lz4-explained.html). 60 | 61 | At the start of the packet there is a u32 specifying the final length of the decompressed packet, so you know the size of the buffer to allocate and can check at the end if there was an error while decompressing (though this should never happen) 62 | 63 | Format: 64 | > `02 u32(decompressed output length) (LZ4 block)` 65 | 66 | The decompressed result will include the packet header, you should feed this into your parsing function recursively. The decompressed result is not [encoded](/protocol/crypto.md). Currently only [Update](#0x00-update-packet) and [JS Challenge](#0x0d-int-js-challenge-packet) packets can get large enough to be compressed. 67 | 68 | --- 69 | 70 | ## **`0x03` Notification Packet** 71 | 72 | This packet sends data that trigger the notifications you see in-game, for example, messages like "The Guardian has spawned" and "You've killed Example". 73 | 74 | The Red Green Blue values are encoded as a u32. For example, rgb(33, 130, 67) would be the same as u32(0x218243) where each byte is a color. The fourth byte is not used, there is no alpha channel. The time the notification appears in milliseconds is encoded as a float, and the final value part of this packet is the notification identifier. 75 | 76 | If a notification with the same identifier as the new one already exists (unless the identifier is an empty string) then the previous notification disappears immediately without waiting for its timer, only one notification may exist at a time with a given identifier. This is used to make sure duplicate notifications such as toggles do not spam your screen. The identifiers used in the game are the following, though note that there are many more types of notifications without one: 77 | 78 | - `autofire` 79 | - `autospin` 80 | - `godmode_toggle` 81 | - `gamepad_enabled` 82 | - `adblock` 83 | - [`cant_claim_info`](/protocol/serverbound.md#0x09-take-tank-packet) 84 | 85 | Format: 86 | > `03 stringNT(message) u32(RGB) float(duration) stringNT(identifier)` 87 | 88 | --- 89 | 90 | It's worth noting that not all notifications are sent through this packet. The autofire, autospin, gamepad_enabled, and adblock notifications are fully clientside while the rest are received from the server. 91 | 92 | ## **`0x04` Server Info Packet** 93 | 94 | This tells the client which gamemode is selected, as well which server, and sets the server region which is displayed next to the ping when holding L. 95 | 96 | Format: 97 | > `04 stringNT(gamemode) stringNT(server uuid) stringNT(host-region)` 98 | 99 | Sample Packet (Decoded): 100 | 101 | ``` 102 | clientbound <- 04 stringNT("sandbox") stringNT("742a37-f24a-4030-b4bd-2fa3515965af") stringNT("vultr-amsterdam") 103 | ``` 104 | 105 | --- 106 | 107 | ## **`0x05` Heartbeat Packet** 108 | 109 | Part of the game's latency system. Once received, the client immediately echoes the single-byte [`0x05` packet](/protocol/serverbound.md#0x05-heartbeat-packet) back. 🏓 110 | 111 | Format: 112 | > `05` 113 | 114 | Sample Packet and Response (Decoded): 115 | 116 | ``` 117 | clientbound <- 05 118 | 119 | response: 120 | serverbound -> 05 121 | ``` 122 | 123 | --- 124 | 125 | ## **`0x06` Party Code Packet** 126 | 127 | This packet is only sent in the 2 Teams, 4 Teams, Domination and Sandbox gamemodes. When this packet is sent the `Copy party link` button appears, otherwise the button is not shown. It contains the un-swapped party code for the arena and team you are in. 128 | 129 | Format: 130 | > `06 ...bytes(party code)` 131 | 132 | Sample Packet and Response (Decoded): 133 | 134 | ``` 135 | clientbound <- 06 2D CD 03 54 C3 136 | 137 | response: show the Copy party link button 138 | ``` 139 | 140 | ### Building a party link 141 | 142 | 1. Write the M28 server ID (`brlm` in this example) as a stringNT and append the party code: 143 | > stringNT("brlm") bytes(0x2D 0xCD 0x03 0x54 0xC3) 144 | > 145 | > 62 72 6C 6D 00 2D CD 03 54 C3 146 | 147 | 2. Swap the nibbles in every byte: 148 | > 26 27 C6 D6 00 D2 DC 30 45 3C 149 | 150 | The final link is `diep.io/#2627C6D600D2DC30453C` 151 | 152 | --- 153 | 154 | ## **`0x07` Accept Packet** 155 | 156 | This packet is sent once the game server has accepted the client (correct build, valid or no party). As of Feb 25, the server only accepts the client once the client solves a [JS Challange](#0x0d-int-js-challenge-packet) and [PoW](#0x0b-pow-challenge-packet) challenge. 157 | 158 | Format: 159 | > `07` 160 | 161 | --- 162 | 163 | ## **`0x08` Achievement Packet** 164 | 165 | Out of all the packets, this one has been researched the least. An array of achievement hash strings which when received, update the localStorage so that you obtain the hash's corresponding achievement. The length of this array is read as a varuint. 166 | 167 | Format: 168 | > `08 vu(hash count):array( ...stringNT(achievement hash) )` 169 | 170 | Sample Packet (Decoded): 171 | 172 | ``` 173 | clientbound <- 08 vu(6) stringNT("9898db9ff6d3c1b3_1") stringNT("300ddd6f1fb3d69d_1") stringNT("8221180ec6d53232_1") stringNT("33e4cb47afd5602f_1") stringNT("6d671cfa6dceb09_1") stringNT("256245339c3742d2_1") 174 | ``` 175 | 176 | --- 177 | 178 | ## **`0x09` Invalid Party Packet** 179 | 180 | This single byte packet is sent if the party code you specified in the [Init Packet](/protocol/serverbound.md#0x00-init-packet) is invalid. You will get this instead of the [`0x07`](#0x07-accept-packet) packet, only after solving a [JS Challange](#0x0d-int-js-challenge-packet) and [PoW](#0x0b-pow-challenge-packet) challenge. 181 | 182 | Format: 183 | > `09` 184 | 185 | Sample Packet and Response (Decoded): 186 | 187 | ```js 188 | clientbound <- 09 189 | 190 | response: 191 | window.alert('Invalid party ID'); 192 | ``` 193 | 194 | --- 195 | 196 | ## **`0x0A` [Unused] Player Count Packet** 197 | 198 | > This packet was no longer used by the server for a long time, but now it is ignored by the client as well. This means that spoofing the following packet will have no affect. (Similar to the now gone [`0x0C` String JS Challenge](#0x0c-unnamed-packet)) 199 | 200 | This packet would have sent the total client count encoded in a varuint. This would have updated the text on the bottom right of the screen "*n* players". This packet was not sent globally across all servers at the same time, and the exact ticks before a player count update are unknown. 201 | 202 | Format: 203 | > `0A vu(client count)` 204 | 205 | Sample Packet (Decoded): 206 | 207 | ``` 208 | clientbound <- 0A vu(3364) 209 | ``` 210 | 211 | --- 212 | 213 | ## **`0x0B` PoW Challenge Packet** 214 | 215 | The packet that initiates the Proof of Work convos that are active throughout the connection. Response is an serverbound [`0x0A` PoW Answer Packet](/protocol/serverbound.md#0x0a-pow-answer-packet). More info on how PoW works [here](/protocol/pow.md) 216 | 217 | Format: 218 | > `0B vu(difficulty) stringNT(prefix)` 219 | 220 | Something worth noting is that the prefix is always 16 chars long. Here's a sample: 221 | 222 | ```js 223 | clientbound <- 0B vu(20) stringNT("5X6qqhhfkp4v5zf2") 224 | 225 | response: 226 | m28.pow.solve("5X6qqhhfkp4v5zf2", 20).then(solveStr => { 227 | serverbound -> 0A stringNT(solveStr); 228 | }); 229 | ``` 230 | 231 | --- 232 | 233 | ## **`0x0C` Unnamed Packet** 234 | 235 | This packet has never been observed, and while the packet's format has been reversed, it's never used and does not affect the client. On an older version this was the same as the [`0x0D` Int JS Challenge](#0x0d-int-js-challenge-packet) packet, except it expected a string as a response. So we can't be sure if this packet is still the same as it was and is just not fully present due to [Emscripten](https://github.com/emscripten-core/emscripten) simplifications, or if it changed completely. 236 | 237 | Format: 238 | > `0C vu(unknown)` 239 | 240 | --- 241 | 242 | ## **`0x0D` Int JS Challenge Packet** 243 | 244 | This packet is sent only once, during the client -> server acceptance handshake. It sends highly obfuscated code to be evaluated by the client with the purpose of filtering out headless clients from clients on the web - part of diep.io's anti botting system. The result of this code is always an uint32 and is sent back to the client through the serverbound [`0x0B` JS Result](/protocol/serverbound.md#0x0b-js-result-packet) packet. 245 | 246 | The code sent is obfuscated with [obfuscator.io](https://obfuscator.io/), almost all settings turned on max. This packet checks for global objects and specific properties on global objects, if all the checks pass their intended result, the code ends up returning the correct uint32 result, which the game server recognizes and continues (or completes) the process of accepting the client. 247 | 248 | > Fun fact: 249 | > - There are only 200 unique obfuscated evaluation codes that can be sent to the client, and they are reused across all servers and are generated during the building process (per update). 250 | 251 | Format: 252 | > `0D vu(id) stringNT(code)` 253 | 254 | Sample Packet and Response (Decoded): 255 | ```js 256 | clientbound <- 0D vu(0) stringNT(very long code) 257 | 258 | response: 259 | try { 260 | var f = new Function(UTF8ToString(code)); 261 | f()(function(v) { 262 | _game_js_challenge_response(id, v) 263 | }) 264 | } catch (e) { 265 | console.error(e); 266 | _game_js_challenge_response(id, 0) 267 | } 268 | 269 | serverbound -> 0B vu(0) vu(result) 270 | ``` 271 | 272 | Here's [wonderful explanation by Shädam](https://github.com/supahero1/diep.io/tree/master/eval_packet) of deobfuscation which has information on code flow and deobfuscation of the eval packet's code. 273 | -------------------------------------------------------------------------------- /extras/achievements.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "A moment to cherish forever", 4 | "desc": "Destroy your first tank", 5 | "conds": [ 6 | { 7 | "event": "kill", 8 | "tags": { 9 | "victim.isTank": true 10 | } 11 | } 12 | ] 13 | }, 14 | { 15 | "name": "Git gud", 16 | "desc": "Destroy 10 tanks", 17 | "conds": [ 18 | { 19 | "type": "counter", 20 | "event": "kill", 21 | "threshold": 10, 22 | "tags": { 23 | "victim.isTank": true 24 | } 25 | } 26 | ] 27 | }, 28 | { 29 | "name": "Gitting gud", 30 | "desc": "Destroy 100 tanks", 31 | "conds": [ 32 | { 33 | "type": "counter", 34 | "event": "kill", 35 | "threshold": 100, 36 | "tags": { 37 | "victim.isTank": true 38 | } 39 | } 40 | ] 41 | }, 42 | { 43 | "name": "Got gud", 44 | "desc": "Destroy 1000 tanks", 45 | "conds": [ 46 | { 47 | "type": "counter", 48 | "event": "kill", 49 | "threshold": 1000, 50 | "tags": { 51 | "victim.isTank": true 52 | } 53 | } 54 | ] 55 | }, 56 | { 57 | "name": "Starting out", 58 | "desc": "Destroy something", 59 | "conds": [ 60 | { 61 | "type": "counter", 62 | "event": "kill", 63 | "threshold": 1 64 | } 65 | ] 66 | }, 67 | { 68 | "name": "Getting good", 69 | "desc": "Destroy 500 things", 70 | "conds": [ 71 | { 72 | "type": "counter", 73 | "event": "kill", 74 | "threshold": 500 75 | } 76 | ] 77 | }, 78 | { 79 | "name": "Destroy EVERYTHING", 80 | "desc": "Destroy 10000 things", 81 | "conds": [ 82 | { 83 | "type": "counter", 84 | "event": "kill", 85 | "threshold": 10000 86 | } 87 | ] 88 | }, 89 | { 90 | "name": "Gotta start somewhere", 91 | "desc": "Destroy 10 squares", 92 | "conds": [ 93 | { 94 | "type": "counter", 95 | "event": "kill", 96 | "tags": { 97 | "victim.arenaMobID": "square" 98 | }, 99 | "threshold": 10 100 | } 101 | ] 102 | }, 103 | { 104 | "name": "Square hater", 105 | "desc": "Destroy 500 squares", 106 | "conds": [ 107 | { 108 | "type": "counter", 109 | "event": "kill", 110 | "tags": { 111 | "victim.arenaMobID": "square" 112 | }, 113 | "threshold": 500 114 | } 115 | ] 116 | }, 117 | { 118 | "name": "These squares gotta go", 119 | "desc": "Destroy 10000 squares", 120 | "conds": [ 121 | { 122 | "type": "counter", 123 | "event": "kill", 124 | "tags": { 125 | "victim.arenaMobID": "square" 126 | }, 127 | "threshold": 10000 128 | } 129 | ] 130 | }, 131 | { 132 | "name": "It hurts when I do this", 133 | "desc": "Ram into something", 134 | "conds": [ 135 | { 136 | "event": "kill", 137 | "tags": { 138 | "weapon.isTank": true 139 | } 140 | } 141 | ] 142 | }, 143 | { 144 | "name": "Who needs bullets anyway", 145 | "desc": "Ram into 100 things", 146 | "conds": [ 147 | { 148 | "type": "counter", 149 | "event": "kill", 150 | "tags": { 151 | "weapon.isTank": true 152 | }, 153 | "threshold": 100 154 | } 155 | ] 156 | }, 157 | { 158 | "name": "Look mom, no cannons!", 159 | "desc": "Ram into 3000 things", 160 | "conds": [ 161 | { 162 | "type": "counter", 163 | "event": "kill", 164 | "tags": { 165 | "weapon.isTank": true 166 | }, 167 | "threshold": 3000 168 | } 169 | ] 170 | }, 171 | { 172 | "name": "They ain't a real tank", 173 | "desc": "Destroy 100 factory drones", 174 | "conds": [ 175 | { 176 | "type": "counter", 177 | "event": "kill", 178 | "tags": { 179 | "victim.arenaMobID": "factoryDrone" 180 | }, 181 | "threshold": 100 182 | } 183 | ] 184 | }, 185 | { 186 | "name": "2fast4u", 187 | "desc": "Upgrade Movement Speed to its maximum value", 188 | "conds": [ 189 | { 190 | "event": "statUpgraded", 191 | "tags": { 192 | "id": 0, 193 | "isMaxLevel": true 194 | } 195 | } 196 | ] 197 | }, 198 | { 199 | "name": "Ratatatatatatatata", 200 | "desc": "Upgrade Reload to its maximum value", 201 | "conds": [ 202 | { 203 | "event": "statUpgraded", 204 | "tags": { 205 | "id": 1, 206 | "isMaxLevel": true 207 | } 208 | } 209 | ] 210 | }, 211 | { 212 | "name": "More dangerous than it looks", 213 | "desc": "Upgrade Bullet Damage to its maximum value", 214 | "conds": [ 215 | { 216 | "event": "statUpgraded", 217 | "tags": { 218 | "id": 2, 219 | "isMaxLevel": true 220 | } 221 | } 222 | ] 223 | }, 224 | { 225 | "name": "There's no stopping it!", 226 | "desc": "Upgrade Bullet Penetration to its maximum value", 227 | "conds": [ 228 | { 229 | "event": "statUpgraded", 230 | "tags": { 231 | "id": 3, 232 | "isMaxLevel": true 233 | } 234 | } 235 | ] 236 | }, 237 | { 238 | "name": "Mach 4", 239 | "desc": "Upgrade Bullet Speed to its maximum value", 240 | "conds": [ 241 | { 242 | "event": "statUpgraded", 243 | "tags": { 244 | "id": 4, 245 | "isMaxLevel": true 246 | } 247 | } 248 | ] 249 | }, 250 | { 251 | "name": "Don't touch me", 252 | "desc": "Upgrade Body Damage to its maximum value", 253 | "conds": [ 254 | { 255 | "event": "statUpgraded", 256 | "tags": { 257 | "id": 5, 258 | "isMaxLevel": true 259 | } 260 | } 261 | ] 262 | }, 263 | { 264 | "name": "Indestructible", 265 | "desc": "Upgrade Max Health to its maximum value", 266 | "conds": [ 267 | { 268 | "event": "statUpgraded", 269 | "tags": { 270 | "id": 6, 271 | "isMaxLevel": true 272 | } 273 | } 274 | ] 275 | }, 276 | { 277 | "name": "Self-repairing", 278 | "desc": "Upgrade Health Regen to its maximum value", 279 | "conds": [ 280 | { 281 | "event": "statUpgraded", 282 | "tags": { 283 | "id": 7, 284 | "isMaxLevel": true 285 | } 286 | } 287 | ] 288 | }, 289 | { 290 | "name": "Fire power", 291 | "desc": "Upgrade to Twin", 292 | "conds": [ 293 | { 294 | "event": "classChange", 295 | "tags": { 296 | "class": 1 297 | } 298 | } 299 | ] 300 | }, 301 | { 302 | "name": "Eat those bullets", 303 | "desc": "Upgrade to Machine Gun", 304 | "conds": [ 305 | { 306 | "event": "classChange", 307 | "tags": { 308 | "class": 7 309 | } 310 | } 311 | ] 312 | }, 313 | { 314 | "name": "Snipin'", 315 | "desc": "Upgrade to Sniper", 316 | "conds": [ 317 | { 318 | "event": "classChange", 319 | "tags": { 320 | "class": 6 321 | } 322 | } 323 | ] 324 | }, 325 | { 326 | "name": "Ain't no one sneaking up on ME", 327 | "desc": "Upgrade to Flank Guard", 328 | "conds": [ 329 | { 330 | "event": "classChange", 331 | "tags": { 332 | "class": 8 333 | } 334 | } 335 | ] 336 | }, 337 | { 338 | "name": "Three at the same time", 339 | "desc": "Upgrade to Triple Shot", 340 | "conds": [ 341 | { 342 | "event": "classChange", 343 | "tags": { 344 | "class": 3 345 | } 346 | } 347 | ] 348 | }, 349 | { 350 | "name": "I've got places to be", 351 | "desc": "Upgrade to Tri-Angle", 352 | "conds": [ 353 | { 354 | "event": "classChange", 355 | "tags": { 356 | "class": 9 357 | } 358 | } 359 | ] 360 | }, 361 | { 362 | "name": "BOOM, you're dead", 363 | "desc": "Upgrade to Destroyer", 364 | "conds": [ 365 | { 366 | "event": "classChange", 367 | "tags": { 368 | "class": 10 369 | } 370 | } 371 | ] 372 | }, 373 | { 374 | "name": "Drones are love", 375 | "desc": "Upgrade to Overseer", 376 | "conds": [ 377 | { 378 | "event": "classChange", 379 | "tags": { 380 | "class": 11 381 | } 382 | } 383 | ] 384 | }, 385 | { 386 | "name": "C + E", 387 | "desc": "Upgrade to Quad Tank", 388 | "conds": [ 389 | { 390 | "event": "classChange", 391 | "tags": { 392 | "class": 4 393 | } 394 | } 395 | ] 396 | }, 397 | { 398 | "name": "Now you really ain't sneaking up on me", 399 | "desc": "Upgrade to Twin Flank", 400 | "conds": [ 401 | { 402 | "event": "classChange", 403 | "tags": { 404 | "class": 13 405 | } 406 | } 407 | ] 408 | }, 409 | { 410 | "name": "Insert uncreative achievement name here", 411 | "desc": "Upgrade to Assassin", 412 | "conds": [ 413 | { 414 | "event": "classChange", 415 | "tags": { 416 | "class": 15 417 | } 418 | } 419 | ] 420 | }, 421 | { 422 | "name": "Huntin'", 423 | "desc": "Upgrade to Hunter", 424 | "conds": [ 425 | { 426 | "event": "classChange", 427 | "tags": { 428 | "class": 19 429 | } 430 | } 431 | ] 432 | }, 433 | { 434 | "name": "Eat those pellets!", 435 | "desc": "Upgrade to Gunner", 436 | "conds": [ 437 | { 438 | "event": "classChange", 439 | "tags": { 440 | "class": 20 441 | } 442 | } 443 | ] 444 | }, 445 | { 446 | "name": "BUILD A WALL", 447 | "desc": "Upgrade to Trapper", 448 | "conds": [ 449 | { 450 | "event": "classChange", 451 | "tags": { 452 | "class": 31 453 | } 454 | } 455 | ] 456 | }, 457 | { 458 | "name": "Can't bother using both hands to play?", 459 | "desc": "Upgrade to Auto 3", 460 | "conds": [ 461 | { 462 | "event": "classChange", 463 | "tags": { 464 | "class": 41 465 | } 466 | } 467 | ] 468 | }, 469 | { 470 | "name": "Where did my cannon go?", 471 | "desc": "Upgrade to Smasher", 472 | "conds": [ 473 | { 474 | "event": "classChange", 475 | "tags": { 476 | "class": 36 477 | } 478 | } 479 | ] 480 | }, 481 | { 482 | "name": "Try some other classes too", 483 | "desc": "Upgrade to Sniper 100 times", 484 | "conds": [ 485 | { 486 | "event": "classChange", 487 | "type": "counter", 488 | "threshold": 100, 489 | "tags": { 490 | "class": 6 491 | } 492 | } 493 | ] 494 | }, 495 | { 496 | "name": "Drones are life", 497 | "desc": "Upgrade to Overseer 100 times", 498 | "conds": [ 499 | { 500 | "event": "classChange", 501 | "type": "counter", 502 | "threshold": 100, 503 | "tags": { 504 | "class": 11 505 | } 506 | } 507 | ] 508 | }, 509 | { 510 | "name": "That was tough", 511 | "desc": "Kill a boss", 512 | "conds": [ 513 | { 514 | "event": "kill", 515 | "tags": { 516 | "victim.isBoss": true 517 | } 518 | } 519 | ] 520 | }, 521 | { 522 | "name": "Boss Hunter", 523 | "desc": "Kill 10 bosses", 524 | "conds": [ 525 | { 526 | "event": "kill", 527 | "type": "counter", 528 | "threshold": 10, 529 | "tags": { 530 | "victim.isBoss": true 531 | } 532 | } 533 | ] 534 | }, 535 | { 536 | "name": "Eh, you're trying", 537 | "desc": "Reach 1k points", 538 | "conds": [ 539 | { 540 | "event": "score", 541 | "tags": { 542 | "total": ">=1000" 543 | } 544 | } 545 | ] 546 | }, 547 | { 548 | "name": "Starting to git gud", 549 | "desc": "Reach 10k points", 550 | "conds": [ 551 | { 552 | "event": "score", 553 | "tags": { 554 | "total": ">=10000" 555 | } 556 | } 557 | ] 558 | }, 559 | { 560 | "name": "You aren't that bad at this", 561 | "desc": "Reach 100k points", 562 | "conds": [ 563 | { 564 | "event": "score", 565 | "tags": { 566 | "total": ">=100000" 567 | } 568 | } 569 | ] 570 | }, 571 | { 572 | "name": "Okay you're pretty good", 573 | "desc": "Reach 1m points", 574 | "conds": [ 575 | { 576 | "event": "score", 577 | "tags": { 578 | "total": ">=1000000" 579 | } 580 | } 581 | ] 582 | }, 583 | { 584 | "name": "Jackpot!", 585 | "desc": "Get 20k points in a single kill", 586 | "conds": [ 587 | { 588 | "event": "score", 589 | "tags": { 590 | "delta": ">=20000" 591 | } 592 | } 593 | ] 594 | }, 595 | { 596 | "name": "LAAAAAAAAAAAAG", 597 | "desc": "Play with over 1000 ms of latency", 598 | "conds": [ 599 | { 600 | "event": "latency", 601 | "tags": { 602 | "value": ">=1000" 603 | } 604 | } 605 | ] 606 | }, 607 | { 608 | "name": "Shiny!", 609 | "desc": "???", 610 | "conds": [ 611 | { 612 | "event": "kill", 613 | "tags": { 614 | "victim.isShiny": true 615 | } 616 | } 617 | ] 618 | }, 619 | { 620 | "name": "There are other classes?", 621 | "desc": "Get to level 45 as a basic tank", 622 | "conds": [ 623 | { 624 | "event": "levelUp", 625 | "tags": { 626 | "level": 45, 627 | "class": 0 628 | } 629 | } 630 | ] 631 | } 632 | ] 633 | -------------------------------------------------------------------------------- /memory/userscripts/dma.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Diep Memory Analyzer 3 | // @version 1.0.0 4 | // @description Script dedicated to reverse engineering memory structs and analyzing the memory 5 | // @namespace github.com/ABCxFF 6 | // @author ABC 7 | // 8 | // @match *://diep.io/ 9 | // @run-at document-start 10 | // @grant none 11 | // 12 | // @require https://gist.githubusercontent.com/ABCxFF/b089643396fbb933996966b5ab632821/raw/6f93ee2bac45e308067fafff21e1110132f9ca0e/wail.js 13 | // ==/UserScript== 14 | 15 | // Bring back to instantiate 16 | WebAssembly.instantiateStreaming = (r, i) => r.arrayBuffer().then(b => WebAssembly.instantiate(b, i)); 17 | 18 | const CONFIG = { 19 | GLOBAL_KEY: "DMA", // window[GLOBAL_KEY], localStorage[GLOBAL_KEY + "Store"] 20 | WATCH_MEM: false, 21 | MEM_HISTORY_SIZE: 1, 22 | EXPORT_KEY: location.origin === 'https://diep.io' ? 'a' : 'env' 23 | } 24 | 25 | !((exports) => { 26 | // EventEmitter 27 | !((listeners) => { 28 | exports.__l = listeners; 29 | exports.on = (event, cb, prepend=false) => { 30 | 31 | if (!listeners[event]) listeners[event] = []; 32 | 33 | if (prepend) listeners[event].unshift(cb); 34 | else listeners[event].push(cb); 35 | 36 | return listeners[event].length - 1; // index 37 | } 38 | 39 | exports.off = (event, index) => { 40 | if (!listeners[event] || index < 0) return; 41 | 42 | listeners[event].splice(index, 1); 43 | } 44 | 45 | exports.emit = (event, ...data) => { // made to be somewhat efficient 46 | if (!listeners[event] || !listeners[event].length) return; 47 | let len = listeners[event].length; 48 | 49 | for (let i = 0; i !== len; ++i) listeners[event][i](...data); 50 | } 51 | })({}); 52 | // LocalStore 53 | const storage = (() => { 54 | const key = CONFIG.GLOBAL_KEY + "Store"; 55 | 56 | const storage = exports.storage = {}; 57 | 58 | storage.store = (data=storage._cache) => localStorage.setItem(key, JSON.stringify(storage._cache = data)); 59 | storage.fetch = () => storage._cache = JSON.parse(localStorage.getItem(key) || "{}"); 60 | storage.wipe = () => storage.store({}); 61 | 62 | storage._cache = storage.fetch(); 63 | 64 | return storage; 65 | })(); 66 | 67 | // Hook into Module 68 | Object.defineProperty(Object.prototype, "dynCall_v", { 69 | get() { }, 70 | set(dynCall_v) { 71 | delete Object.prototype.dynCall_v 72 | this.dynCall_v = dynCall_v; 73 | exports.Module = this; 74 | exports.malloc = this._malloc; 75 | exports.free = this._free; 76 | 77 | // Wipe old settings, display them to the user if they care 78 | exports.emit("load"); 79 | }, 80 | configurable: true, 81 | }); 82 | 83 | // Hook into WebAssembly instantiation 84 | !(_inst => WebAssembly.instantiate = (wasm, imports) => { 85 | if (imports && imports[CONFIG.EXPORT_KEY]) { 86 | const loader = {raw: new Uint8Array(new Uint8Array(wasm)), buffer: new Uint8Array(wasm), imports}; 87 | const hexChars = [48,49,50,51,52,53,54,55,56,57,97,98,99,100,101,102]; 88 | let build = "unknown"; 89 | let K = 0; 90 | scan: for (let i = 0; i < loader.raw.length; ++i) { 91 | if (hexChars.includes(loader.raw[i])) { 92 | let s = i; 93 | for (let j = 0; j < 40; ++j, ++i) { 94 | if (!hexChars.includes(loader.raw[i])) continue scan; 95 | } 96 | if (hexChars.includes(loader.raw[i++])) { 97 | while (hexChars.includes(loader.raw[i++])) {}; 98 | continue scan; 99 | } 100 | 101 | if (K++ === 0) continue scan; 102 | build = new TextDecoder().decode(loader.raw.subarray(s, i - 1)); 103 | break scan; 104 | } 105 | } 106 | exports.BUILD = build; 107 | 108 | if (exports.BUILD !== storage.fetch().build) { 109 | prompt(CONFIG.GLOBAL_KEY + ": New build, wiping settings\n\nbuild_" + exports.BUILD, JSON.stringify(storage.fetch())); 110 | 111 | storage.wipe(); 112 | storage.fetch().build = exports.BUILD; 113 | storage.store(); 114 | } 115 | 116 | exports.emit("precompile", loader); 117 | exports.wasm = {} 118 | exports.wasm.loader = loader; 119 | WebAssembly.instantiate = _inst; 120 | 121 | return _inst(loader.buffer, loader.imports).then((results) => { 122 | const memory = Object.values(results.instance.exports).find(e => e instanceof WebAssembly.Memory); 123 | 124 | exports.HEAPU8 = new Uint8Array(memory.buffer); 125 | exports.HEAP8 = new Int8Array(memory.buffer); 126 | exports.HEAPU16 = new Uint16Array(memory.buffer); 127 | exports.HEAP16 = new Int16Array(memory.buffer); 128 | exports.HEAPU32 = new Uint32Array(memory.buffer); 129 | exports.HEAP32 = new Int32Array(memory.buffer); 130 | exports.HEAPF32 = new Float32Array(memory.buffer); 131 | exports.HEAPF64 = new Float64Array(memory.buffer); 132 | exports.HEAPU64 = new BigUint64Array(memory.buffer); 133 | exports.HEAP64 = new BigInt64Array(memory.buffer); 134 | exports.malloc = (exports.Module||{})._malloc; 135 | exports.free = (exports.Module||{})._free; 136 | 137 | exports.emit("compile", results.instance.exports, results); 138 | 139 | return results; 140 | }); 141 | } 142 | 143 | return _inst(wasm, imports); 144 | })(WebAssembly.instantiate.bind(WebAssembly.instantiate)); 145 | 146 | // These allow you to export functions 147 | exports.addExportEntry = (name, index) => { 148 | const exportEntries = storage.fetch().exports || []; 149 | 150 | exportEntries.push({name, index}); 151 | storage.fetch().exports = exportEntries; 152 | storage.store(); 153 | 154 | return exportEntries.length - 1; 155 | } 156 | 157 | exports.removeExportEntry = (name, index) => { 158 | if (typeof index === "undefined" && typeof name === "number" && name >= 0) { 159 | const exportEntries = storage.fetch().exports || []; 160 | 161 | exportEntries.splice(name, 1); 162 | 163 | storage.store(); 164 | return true; 165 | } else if (typeof index === "undefined") return false; 166 | 167 | const exportEntries = storage.fetch().exports || []; 168 | 169 | return exports.removeExportEntry(exportEntries.findIndex(e => e.name === name && e.index === index)) 170 | } 171 | 172 | // These allow you to log / dispatch events on the calling of functions 173 | exports.addLogPoint = (logName, index) => { 174 | const loggers = storage.fetch().loggers || []; 175 | 176 | loggers.push({name: logName, index}); 177 | storage.fetch().loggers = loggers; 178 | storage.store(); 179 | 180 | return loggers.length - 1; 181 | } 182 | 183 | exports.removeLogPoint = (logName, index) => { 184 | if (typeof index === "undefined" && typeof logName === "number" && logName >= 0) { 185 | const loggers = storage.fetch().loggers || []; 186 | 187 | loggers.splice(logName, 1); 188 | 189 | storage.store(); 190 | 191 | return true; 192 | } else if (typeof index === "undefined") return false; 193 | 194 | const loggers = storage.fetch().loggers || []; 195 | 196 | return exports.removeLogPoint(loggers.findIndex(e => e.name === logName && e.index === index)); 197 | } 198 | 199 | // makes the exports accessible 200 | exports.on("compile", (wasmExports, results) => { 201 | exports.wasm.results = results; 202 | for (const name in wasmExports) if (name.slice(0, 5) === "diep.") exports.wasm[name.slice(5)] = wasmExports[name]; 203 | }); 204 | 205 | // add exports, log points, and getter watchers 206 | exports.on("precompile", (loader) => { 207 | const decompilerWail = new WailParser(loader.buffer); 208 | decompilerWail.parse(); 209 | 210 | const parsed = window.parsedSections = decompilerWail._parsedSections; 211 | 212 | const getBySection = (code) => parsed.find(s => s.id === code); 213 | const importSection = getBySection(SECTION_IMPORT); 214 | const typeSection = getBySection(SECTION_TYPE); 215 | const funcSection = getBySection(SECTION_FUNCTION); 216 | const codeSection = getBySection(SECTION_CODE); 217 | 218 | const wail = new WailParser(loader.buffer); 219 | 220 | const data = storage.fetch(); 221 | 222 | const exportEntries = data.exports || []; 223 | const loggers = (data.loggers || []).map(({index, name}) => ({name, _index: index, index: wail.getFunctionIndex(index)})); 224 | 225 | let names = []; 226 | for (let i = 0; i < exportEntries.length; ++i) { 227 | 228 | const {name, index} = exportEntries[i]; 229 | 230 | if (names.includes(name)) { 231 | console.warn(`[${CONFIG.GLOBAL_KEY}] Duplicate export name ${name}:${index}, removing.`); 232 | 233 | exports.removeExportEntry(i); 234 | 235 | continue; 236 | } 237 | wail.addExportEntry(wail.getFunctionIndex(index), { 238 | fieldStr: "diep." + name, 239 | kind: "func", 240 | }); 241 | 242 | names.push(name); 243 | } 244 | 245 | const importFuncCount = importSection.results.findIndex(imp => imp.kind !== "func"); 246 | 247 | const loggerImports = loader.imports[CONFIG.GLOBAL_KEY.toLowerCase() + ".logger"] = {}; 248 | 249 | for (const {index, _index, name} of loggers) { 250 | const params = typeSection.results[funcSection.results[_index - importFuncCount].funcType].params; 251 | 252 | loggerImports[name] = (...params) => { 253 | exports.emit(name, params, _index) 254 | } 255 | 256 | const loggerIndex = wail.addImportEntry({ 257 | moduleStr: CONFIG.GLOBAL_KEY.toLowerCase() + ".logger", 258 | fieldStr: name, 259 | kind: "func", 260 | type: wail.addTypeEntry({ 261 | form: "func", 262 | params, 263 | }) 264 | }); 265 | 266 | wail.addCodeElementParser(index, ({bytes}) => { 267 | const callerBytes = []; 268 | 269 | for (let i = 0; i < params.length; ++i) callerBytes.push(OP_GET_LOCAL, ...VarUint32ToArray(i)); 270 | 271 | callerBytes.push(OP_CALL, ...VarUint32ToArray(loggerIndex.i32())); 272 | 273 | const buffer = new Uint8Array(callerBytes.length + bytes.length); 274 | buffer.set(callerBytes, 0); 275 | buffer.set(bytes, callerBytes.length); 276 | 277 | return buffer; 278 | }); 279 | } 280 | 281 | loader.imports[CONFIG.EXPORT_KEY][CONFIG.GLOBAL_KEY.toLowerCase() + ".patch"] = () => {}; 282 | 283 | wail.addImportEntry({ 284 | moduleStr: CONFIG.EXPORT_KEY, 285 | fieldStr: CONFIG.GLOBAL_KEY.toLowerCase() + ".patch", 286 | kind: "func", 287 | type: wail.addTypeEntry({ 288 | form: "func", 289 | params: [], 290 | }) 291 | }); 292 | 293 | if (!CONFIG.WATCH_MEM) { 294 | wail.parse(); 295 | 296 | loader.buffer = wail.write(); 297 | return; 298 | } 299 | /* 300 | Bits Desc 301 | DESCRIPTOR FLAGS 302 | 01000000 isWrite. When set, the address is being written to 303 | 00100000 isFloat. When set, it is a float 304 | 00010000 isSign. When set, the value is signed 305 | SIZE FLAGS 306 | 00001000 64bit 307 | 00000100 32bit 308 | 00000010 16bit 309 | 00000001 8bit 310 | */ 311 | 312 | const readHandler = wail.addImportEntry({ 313 | moduleStr: CONFIG.GLOBAL_KEY.toLowerCase() + ".watch", 314 | fieldStr: "read", 315 | kind: "func", 316 | type: wail.addTypeEntry({ 317 | form: "func", 318 | // addr, offset,, flags 319 | params: ["i32", "i32", "i32"], 320 | returnType: "i32" 321 | }) 322 | }); 323 | const writeHandler = wail.addImportEntry({ 324 | moduleStr: CONFIG.GLOBAL_KEY.toLowerCase() + ".watch", 325 | fieldStr: "write", 326 | kind: "func", 327 | type: wail.addTypeEntry({ 328 | form: "func", 329 | // addr, value, offset, flags 330 | params: ["i32", "i64", "i32", "i32"], 331 | returnType: "i32" 332 | }) 333 | }); 334 | const frozenAddresses = []; 335 | const debugAddresses = []; 336 | const BUILDER = { 337 | 0b00100000: "F", 338 | 0b00010000: "", 339 | 0b1000: "64", 340 | 0b0100: "32", 341 | 0b0010: "16", 342 | 0b0001: "8", 343 | } 344 | const SHIFTS = { 345 | 0b1000: 3, 346 | 0b0100: 2, 347 | 0b0010: 1, 348 | 0b0001: 0, 349 | } 350 | exports.freezeAddress = (addr) => { 351 | frozenAddresses.push(addr) 352 | } 353 | exports.unfreezeAddress = (addr) => { 354 | frozenAddresses.splice(frozenAddresses.indexOf(addr), 1); 355 | } 356 | 357 | exports.debugAddress = (addr, type="*") => { 358 | if (!["*", "w", "r"].includes(type)) throw new TypeError("Invalid type. Must be one of '*' 'w' 'r'") 359 | debugAddresses.push({addr,type}) 360 | } 361 | exports.undebugAddress = (addr) => { 362 | debugAddresses.splice(debugAddresses.findIndex(o => o.addr === addr), 1); 363 | } 364 | const readObj = {}; // less allocation 365 | const writeObj = {}; // less allocation 366 | loader.imports[CONFIG.GLOBAL_KEY.toLowerCase() + ".watch"] = { 367 | read(addr, offset, flags) { 368 | if (debugAddresses.findIndex(o => o.addr === addr + offset && (o.type === "*" || o.type === "r")) !== -1) debugger; 369 | if (!exports.watch.ACTIVATED) return addr; 370 | 371 | readObj.addr = addr; 372 | readObj.offset = offset; 373 | readObj.flags = flags; 374 | exports.emit("watch:read", readObj); 375 | 376 | return addr; 377 | }, 378 | write(addr, value, offset, flags) { 379 | if (debugAddresses.findIndex(o => o.addr === addr + offset && (o.type === "*" || o.type === "w")) !== -1) debugger; 380 | 381 | if (!frozenAddresses.includes(addr + offset)) { 382 | exports.HEAPU64[1016 >> 3] = value; 383 | } else { 384 | const heap = exports["HEAP" + (BUILDER[flags & 0b00110000] ?? "U") + BUILDER[flags & 0b1111]]; 385 | heap[1016 >> SHIFTS[flags & 0b1111]] = heap[(addr + offset) >> SHIFTS[flags & 0b1111]]; 386 | } 387 | if (!exports.watch.ACTIVATED) return addr; 388 | 389 | writeObj.addr = addr; 390 | writeObj.offset = offset; 391 | writeObj.value = value; 392 | writeObj.flags = flags; 393 | 394 | exports.emit("watch:write", writeObj); 395 | 396 | return addr; 397 | } 398 | }; 399 | 400 | const readWatchInstrModifier = function(instrBytes) { 401 | const reader = new BufferReader(instrBytes); 402 | 403 | const opcode = reader.readUint8(); 404 | 405 | const align = reader.readVarUint32(); 406 | const offset = reader.readVarUint32(); 407 | 408 | let flags = 0; 409 | 410 | switch (opcode) { 411 | case OP_I32_LOAD8_S: 412 | case OP_I64_LOAD8_S: 413 | flags |= 0b00010000; // signed 414 | case OP_I32_LOAD8_U: 415 | case OP_I64_LOAD8_U: 416 | flags |= 0b00000001; // 8bit 417 | break; 418 | case OP_I32_LOAD16_S: 419 | case OP_I64_LOAD16_S: 420 | flags |= 0b00010000; // signed 421 | case OP_I64_LOAD16_U: 422 | case OP_I32_LOAD16_U: 423 | flags |= 0b00000010; // 16bit 424 | break; 425 | case OP_I32_LOAD: 426 | flags |= 0b00010100; // signed, 32bit 427 | break; 428 | case OP_F32_LOAD: 429 | flags |= 0b00100100; // float, 32bit 430 | break; 431 | case OP_I64_LOAD32_S: 432 | flags |= 0b00010000; // signed 433 | case OP_I64_LOAD32_U: 434 | flags |= 0b00000100; // signed, 32bit 435 | break; 436 | case OP_I64_LOAD: 437 | flags |= 0b00011000; // signed, 64bit 438 | break; 439 | case OP_F64_LOAD: 440 | flags |= 0b00101000; // float, 64bit 441 | break; 442 | } 443 | 444 | reader.copyBuffer([ 445 | OP_I32_CONST, ...VarSint32ToArray(offset),// OP_I32_ADD,// ...(offset === 0 ? [] : [OP_I32_CONST, ...VarUint32ToArray(offset), OP_I32_ADD]), 446 | OP_I32_CONST, ...VarSint32ToArray(flags), 447 | OP_CALL, ...VarUint32ToArray(readHandler.i32()), 448 | 449 | opcode, align, ...VarUint32ToArray(offset) 450 | ]); 451 | 452 | return reader.write(); 453 | } 454 | 455 | const LOAD_OP_TABLE = { 456 | [OP_I32_STORE8]: OP_I32_LOAD8_U, 457 | [OP_I64_STORE8]: OP_I64_LOAD8_U, 458 | [OP_I32_STORE16]: OP_I32_LOAD16_U, 459 | [OP_I64_STORE16]: OP_I64_LOAD16_U, 460 | [OP_F32_STORE]: OP_F32_LOAD, 461 | [OP_I32_STORE]: OP_I32_LOAD, 462 | [OP_I64_STORE32]: OP_I64_LOAD32_U, 463 | [OP_F64_STORE]: OP_F64_LOAD, 464 | [OP_I64_STORE]: OP_I64_LOAD 465 | } 466 | 467 | const writeWatchInstrModifier = function(instrBytes) { 468 | const reader = new BufferReader(instrBytes); 469 | 470 | const opcode = reader.readUint8(); 471 | 472 | const align = reader.readVarUint32(); 473 | const offset = reader.readVarUint32(); 474 | 475 | let flags = 0b01000000; // write 476 | 477 | switch (opcode) { 478 | case OP_I32_STORE8: 479 | case OP_I64_STORE8: 480 | flags |= 0b00000001; // 8bit 481 | break; 482 | case OP_I32_STORE16: 483 | case OP_I64_STORE16: 484 | flags |= 0b00000010; // 16bit 485 | break; 486 | case OP_F32_STORE: 487 | flags |= 0b00100100; // float, 32 bit 488 | break; 489 | case OP_I64_STORE32: 490 | case OP_I32_STORE: 491 | flags |= 0b00000100; // 32bit 492 | break; 493 | case OP_F64_STORE: 494 | flags |= 0b00101000; // float, 64bit 495 | break; 496 | case OP_I64_STORE: 497 | flags |= 0b00001000; // 64bit 498 | break; 499 | } 500 | 501 | switch (opcode) { 502 | case OP_F32_STORE: 503 | reader.copyBuffer([OP_I32_REINTERPRET_F32]); 504 | case OP_I32_STORE8: 505 | case OP_I32_STORE16: 506 | case OP_I32_STORE: 507 | reader.copyBuffer([OP_I64_EXTEND_U_I32]) 508 | break; 509 | case OP_F64_STORE: 510 | reader.copyBuffer([OP_I64_REINTERPRET_F64]); 511 | case OP_I64_STORE8: 512 | case OP_I64_STORE16: 513 | case OP_I64_STORE32: 514 | case OP_I64_STORE: 515 | break; 516 | } 517 | 518 | 519 | reader.copyBuffer([ 520 | OP_I32_CONST, ...VarSint32ToArray(offset),// OP_I32_ADD,// ...(offset === 0 ? [] : [OP_I32_CONST, ...VarUint32ToArray(offset), OP_I32_ADD]), 521 | OP_I32_CONST, ...VarSint32ToArray(flags), 522 | OP_CALL, ...VarUint32ToArray(writeHandler.i32()), 523 | 524 | OP_I32_CONST, ...VarSint32ToArray(1016), 525 | LOAD_OP_TABLE[opcode], ...VarUint32ToArray(align), 0, 526 | 527 | opcode, align, ...VarUint32ToArray(offset) 528 | ]); 529 | 530 | return reader.write(); 531 | } 532 | 533 | wail.addInstructionParser(OP_I32_LOAD, readWatchInstrModifier); 534 | wail.addInstructionParser(OP_I64_LOAD, readWatchInstrModifier); 535 | wail.addInstructionParser(OP_F32_LOAD, readWatchInstrModifier); 536 | wail.addInstructionParser(OP_F64_LOAD, readWatchInstrModifier); 537 | wail.addInstructionParser(OP_I32_LOAD8_S, readWatchInstrModifier); 538 | wail.addInstructionParser(OP_I32_LOAD8_U, readWatchInstrModifier); 539 | wail.addInstructionParser(OP_I32_LOAD16_S, readWatchInstrModifier); 540 | wail.addInstructionParser(OP_I32_LOAD16_U, readWatchInstrModifier); 541 | wail.addInstructionParser(OP_I64_LOAD8_S, readWatchInstrModifier); 542 | wail.addInstructionParser(OP_I64_LOAD8_U, readWatchInstrModifier); 543 | wail.addInstructionParser(OP_I64_LOAD16_S, readWatchInstrModifier); 544 | wail.addInstructionParser(OP_I64_LOAD16_U, readWatchInstrModifier); 545 | wail.addInstructionParser(OP_I64_LOAD32_S, readWatchInstrModifier); 546 | wail.addInstructionParser(OP_I64_LOAD32_U, readWatchInstrModifier); 547 | 548 | wail.addInstructionParser(OP_I32_STORE, writeWatchInstrModifier); 549 | wail.addInstructionParser(OP_I64_STORE, writeWatchInstrModifier); 550 | wail.addInstructionParser(OP_F32_STORE, writeWatchInstrModifier); 551 | wail.addInstructionParser(OP_F64_STORE, writeWatchInstrModifier); 552 | wail.addInstructionParser(OP_I32_STORE8, writeWatchInstrModifier); 553 | wail.addInstructionParser(OP_I32_STORE16, writeWatchInstrModifier); 554 | wail.addInstructionParser(OP_I64_STORE8, writeWatchInstrModifier); 555 | wail.addInstructionParser(OP_I64_STORE16, writeWatchInstrModifier); 556 | wail.addInstructionParser(OP_I64_STORE32, writeWatchInstrModifier); 557 | 558 | wail.parse(); 559 | 560 | loader.buffer = wail.write(); 561 | }); 562 | 563 | // add in heap 564 | // exports.on("precompile", () => { 565 | // const { 566 | // HEAPU8, HEAP8, HEAPU16, HEAP16, 567 | // HEAPU32, HEAP32, 568 | // HEAPF32, HEAPF64, 569 | // _malloc: malloc, _free: free 570 | // } = exports.Module; 571 | // console.log(HEAPU8) 572 | // // Add in HEAPU64 and HEAP64 (bigints) 573 | // const HEAPU64 = exports.Module.HEAPU64 = new BigUint64Array(HEAPU8.buffer); 574 | // const HEAP64 = exports.Module.HEAP64 = new BigInt64Array(HEAPU8.buffer); 575 | 576 | // // Might as well make it accessable too 577 | // exports.HEAPU8 = HEAPU8; 578 | // exports.HEAP8 = HEAP8; 579 | // exports.HEAPU16 = HEAPU16; 580 | // exports.HEAP16 = HEAP16; 581 | // exports.HEAPU32 = HEAPU32; 582 | // exports.HEAP32 = HEAP32; 583 | // exports.HEAPF32 = HEAPF32; 584 | // exports.HEAPF64 = HEAPF64; 585 | // exports.HEAP64 = HEAP64; 586 | // exports.HEAPU64 = HEAPU64; 587 | // exports.malloc = malloc; 588 | // exports.free = free; 589 | // }); 590 | 591 | // CX's $, modified 592 | exports.$ = () => {throw "Exports not ready yet"}; 593 | exports.on("compile", () => { 594 | const { 595 | HEAPU8, HEAP8, HEAPU16, HEAP16, 596 | HEAPU32, HEAP32, 597 | HEAPF32, HEAPF64, 598 | HEAP64, HEAPU64, 599 | _malloc: malloc, _free: free 600 | } = exports; 601 | 602 | const Decoder = new TextDecoder(); 603 | const Encoder = new TextEncoder(); 604 | 605 | const getter = ({ ptr }, prop) => { 606 | switch (prop) { 607 | case 'at': return ptr 608 | case 'u8': return HEAPU8[ptr] 609 | case 'i8': return HEAP8[ptr] 610 | case 'u16': return HEAPU16[ptr >> 1] 611 | case 'i16': return HEAP16[ptr >> 1] 612 | case 'u32': return HEAPU32[ptr >> 2] 613 | case 'i32': return HEAP32[ptr >> 2] 614 | case 'u64': return HEAPU64[ptr >> 3] 615 | case 'f32': return HEAPF32[ptr >> 2] 616 | case ':f32': return HEAPF32[(ptr) >> 2] 617 | case 'f64': return HEAPF64[ptr >> 3] 618 | case 'i64': return HEAP64[ptr >> 3]; 619 | case 'u64': return HEAPU64[ptr >> 3]; 620 | case 'utf8': return Decoder.decode(HEAPU8.subarray(ptr, HEAPU8.indexOf(0, ptr))); 621 | case 'cstr': { 622 | let strAt = ptr; 623 | let length = HEAPU8[ptr + 11] 624 | if (length === 0x80) { 625 | length = HEAP32[(ptr + 4) >> 2]; 626 | strAt = HEAP32[ptr >> 2]; 627 | } 628 | 629 | return Decoder.decode(HEAPU8.subarray(strAt, strAt + length)) 630 | } 631 | case 'raw': { 632 | return (size=64) => HEAPU8.subarray(ptr, ptr + size); 633 | } 634 | case 'vector': { 635 | const vector = [] 636 | for (let i = HEAPU32[ptr >> 2]; i < HEAPU32[(ptr >> 2) + 1]; i += 4) 637 | vector.push(exports.$(i)) 638 | return vector; 639 | } 640 | case '$vector': { 641 | const $vector = [] 642 | for (let i = HEAPU32[ptr >> 2]; i < HEAPU32[(ptr >> 2) + 1]; i += 4) 643 | $vector.push(exports.$(HEAPU32[i >> 2])) 644 | return $vector; 645 | } 646 | case '$': return exports.$(HEAPU32[ptr >> 2]) 647 | } 648 | const offset = parseInt(prop, 10); 649 | 650 | if (!Number.isNaN(offset)) 651 | return exports.$(ptr + offset) 652 | } 653 | 654 | const setter = ({ ptr }, prop, to) => { 655 | if (to === undefined || to === null) return; 656 | 657 | switch (prop) { 658 | case 'u8': 659 | HEAPU8[ptr] = to; 660 | break; 661 | case 'i8': 662 | HEAP8[ptr] = to; 663 | break; 664 | case 'u16': 665 | HEAPU16[ptr >> 1] = to; 666 | break; 667 | case 'i16': 668 | HEAP16[ptr >> 1] = to; 669 | break; 670 | case '$': 671 | case 'u32': 672 | HEAPU32[ptr >> 2] = to; 673 | break; 674 | case 'i32': 675 | HEAP32[ptr >> 2] = to; 676 | break; 677 | case 'u64': 678 | HEAPU64[ptr >> 3] = to; 679 | break; 680 | case 'f32': 681 | HEAPF32[ptr >> 2] = to; 682 | break; 683 | case 'f64': 684 | HEAPF64[ptr >> 3] = to; 685 | break; 686 | case 'i64': 687 | HEAP64[ptr >> 3] = to; 688 | break; 689 | case 'u64': 690 | HEAPU64[ptr >> 3] = to; 691 | break; 692 | case 'utf8': { 693 | const buf = Encoder.encode(to.toString()); 694 | HEAPU8.set(buf, ptr); 695 | HEAPU8[ptr + buf.byteLength] = 0; 696 | } 697 | break; 698 | case 'cstr': { 699 | const textBuf = Encoder.encode(to.toString()); 700 | const len = textBuf.length; 701 | 702 | if (HEAPU8[ptr + 11] === 0x80) free(HEAP32[ptr >> 2]); 703 | 704 | HEAPU8.set(new Uint8Array(12), ptr); // clear 705 | 706 | if (len < 11) { 707 | HEAPU8.set(textBuf, ptr); 708 | 709 | HEAPU8[ptr + 11] = len; 710 | } else { 711 | const strPtr = malloc(len + 1); 712 | 713 | HEAPU8.set(textBuf, strPtr); 714 | HEAPU8[strPtr + len] = 0; 715 | HEAP32.set([strPtr, len, -0x7FFFFFE0], ptr >> 2); 716 | } 717 | } 718 | break; 719 | default: 720 | console.warn("Invalid property " + prop); 721 | break; 722 | } 723 | 724 | return true; 725 | } 726 | 727 | exports.$ = ptr => new Proxy({ ptr }, { get: getter, set: setter }) 728 | }); 729 | 730 | // scanners 731 | exports.scan = (() => { 732 | const scan = (arr, needle, max=500) => { 733 | const results = []; 734 | if (needle.length <= 0 || arr.length < needle.length) return results; 735 | while (arr[0] === "*") arr.shift(); 736 | while (arr[arr.length - 1] === "*") arr.pop(); 737 | const needleLen = needle.length; 738 | const len = arr.length - needleLen; 739 | 740 | jumper: for (let i = arr.indexOf(needle[0]); i <= len && i !== -1 && results.length < max; i = arr.indexOf(needle[0], i + 1)) { 741 | for (let j = 1; j < needleLen; ++j) if (needle[j] !== arr[i + j] && needle[j] !== "*") continue jumper; 742 | 743 | results.push(i); 744 | }; 745 | 746 | return results; 747 | }; 748 | 749 | exports.on("compile", () => { 750 | const { 751 | HEAPU8, HEAP8, HEAPU16, HEAP16, 752 | HEAPU32, HEAP32, 753 | HEAPF32, HEAPF64, 754 | HEAP64, HEAPU64, 755 | } = exports; 756 | 757 | const Encoder = new TextEncoder(); 758 | 759 | const arrayify = (v) => [v].flat(); 760 | 761 | exports.scan.i8 = (vals, max=500) => scan(HEAP16, arrayify(vals).map(v => (v << 24) >> 24), max).map(i => i << 1); 762 | exports.scan.u8 = (vals, max=500) => scan(HEAPU16, arrayify(vals).map(v => (v << 24) >>> 24), max).map(i => i << 1); 763 | 764 | exports.scan.i16 = (vals, max=500) => scan(HEAP16, arrayify(vals).map(v => (v << 16) >> 16), max).map(i => i << 1); 765 | exports.scan.u16 = (vals, max=500) => scan(HEAPU16, arrayify(vals).map(v => (v << 16) >>> 16), max).map(i => i << 1); 766 | 767 | exports.scan.i32 = (vals, max=500) => scan(HEAP32, arrayify(vals).map(v => v & 0), max).map(i => i << 2); 768 | exports.scan.u32 = (vals, max=500) => scan(HEAPU32, arrayify(vals).map(v => v >>> 0), max).map(i => i << 2); 769 | 770 | exports.scan.f32 = (vals, max=500) => scan(HEAPF32, arrayify(vals).map(Math.fround), max).map(i => i << 2); 771 | exports.scan.f64 = (vals, max=500) => scan(HEAPF64, arrayify(vals), max).map(i => i << 3); 772 | 773 | exports.scan.i64 = (vals, max=500) => scan(HEAPF64, arrayify(vals).map(v => v & 0x8000000000000000n ? ~(v & 0x7FFFFFFFFFFFFFFFn) : v & 0x7FFFFFFFFFFFFFFFn), max).map(i => i << 3); 774 | exports.scan.u64 = (vals, max=500) => scan(HEAPF64, arrayify(vals).map(v => v & 0x8000000000000000n ? ~(v & 0x7FFFFFFFFFFFFFFFn) : v & 0x7FFFFFFFFFFFFFFFn), max).map(i => i << 3); 775 | 776 | exports.scan.raw = (bytes, max=500) => scan(HEAPU8, bytes, max); 777 | exports.scan.utf8 = (string, max=500, full=true) => { 778 | const rawBuf = Encoder.encode(string); 779 | const ntBuf = new Uint8Array(rawBuf.byteLength + (full & 1)); 780 | ntBuf.set(rawBuf); 781 | 782 | return scan(HEAPU8, ntBuf, max); 783 | } 784 | }) 785 | 786 | return {_internal: scan}; 787 | })(); 788 | 789 | const getCallstack = (err=Error()) => err.stack.slice(err.stack.indexOf("\n") + 1); 790 | 791 | // read write watch interface 792 | !((watch) => { 793 | watch.ACTIVATED = false; 794 | 795 | const history = watch.history = Array(CONFIG.MEM_HISTORY_SIZE) 796 | const tracks = watch.tracks = { 797 | total: 0, 798 | reads: 0, 799 | writes: 0 800 | } 801 | 802 | const BUILDER = { 803 | 0b00100000: "F", 804 | 0b00010000: "", 805 | 0b1000: "64", 806 | 0b0100: "32", 807 | 0b0010: "16", 808 | 0b0001: "8", 809 | } 810 | const SHIFTS = { 811 | 0b1000: 3, 812 | 0b0100: 2, 813 | 0b0010: 1, 814 | 0b0001: 0, 815 | } 816 | const watcher = (obj) => { 817 | const flags = obj.flags; 818 | 819 | const isWrite = flags & 0b01000000; 820 | 821 | if (isWrite) tracks.writes += 1; 822 | else tracks.reads += 1; 823 | tracks.total += 1; 824 | 825 | 826 | const data = {}; 827 | 828 | data.base = obj.addr; 829 | data.offset = obj.offset; 830 | data.addr = obj.addr + obj.offset; 831 | 832 | data.action = isWrite ? "write" : "read"; 833 | 834 | data.type = ((BUILDER[flags & 0b00110000] ?? "U") || "S") + BUILDER[flags & 0b1111] 835 | 836 | 837 | 838 | // data.callstack = getCallstack(Error()); - MAJOR LAG 839 | 840 | // messy, but faster than not messy 841 | data.value = exports.Module["HEAP" + (BUILDER[flags & 0b00110000] ?? "U") + BUILDER[flags & 0b1111]][(isWrite ? 1016 : data.addr) >> SHIFTS[flags & 0b1111]]; 842 | 843 | for (let i = 0; i < recorders.length; ++i) recorders[i].update(data); 844 | 845 | history.unshift(data); 846 | 847 | history.length = CONFIG.MEM_HISTORY_SIZE; 848 | 849 | return 850 | } 851 | 852 | const recorders = watch.recorders = []; 853 | 854 | exports.on("watch:read", watcher); 855 | exports.on("watch:write", watcher); 856 | 857 | exports.watch.record = (amount=100, type="*") => { // all, read, or write 858 | type = type === "*" || type === "all" ? "total" : type === "read" ? "reads" : "writes"; 859 | 860 | return new Promise((r) => { 861 | // maybe a class would be better... idc 862 | let i = 0; 863 | 864 | const recorder = {type, end: tracks[type] + amount, list: Array(amount)}; 865 | 866 | recorder.delete = () => { 867 | 868 | recorders.splice(recorders.indexOf(recorder), 1); 869 | 870 | delete recorder.update; 871 | delete recorder.delete; 872 | r(recorder); 873 | } 874 | 875 | if (type === "total") { 876 | recorder.update = (obj) => { 877 | recorder.list[i++] = obj; 878 | 879 | if (tracks.total >= recorder.end) recorder.delete(); 880 | } 881 | } else if (type === "reads") { 882 | recorder.update = (obj) => { 883 | if (obj.action !== "read") return; 884 | 885 | recorder.list[i++] = obj; 886 | 887 | if (tracks.reads >= recorder.end) recorder.delete(); 888 | } 889 | } else { 890 | recorder.update = (obj) => { 891 | if (obj.action !== "write") return; 892 | 893 | recorder.list[i++] = obj; 894 | 895 | if (tracks.reads >= recorder.end) recorder.delete(); 896 | } 897 | } 898 | 899 | recorders.push(recorder); 900 | }); 901 | } 902 | })(exports.watch = {}) 903 | 904 | })(window[CONFIG.GLOBAL_KEY] = {}); 905 | --------------------------------------------------------------------------------