├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── TODO.md └── src ├── libOL ├── Block.cpp ├── Block.h ├── BlockReader.h ├── Blowfish │ ├── Blowfish.cpp │ └── Blowfish.h ├── ChunkHeader.cpp ├── ChunkHeader.h ├── Chunks.cpp ├── Chunks.h ├── Constants.h ├── EntityAttribute.h ├── Header.cpp ├── Header.h ├── Packet.cpp ├── Packet.h ├── PacketDecoders.h ├── PacketParser.h ├── ParseException.cpp ├── ParseException.h ├── PayloadHeader.cpp ├── PayloadHeader.h ├── Rofl.cpp ├── Rofl.h ├── Value.cpp └── Value.h └── libOL_test └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | ######################### 2 | # .gitignore file for Xcode4 / OS X Source projects 3 | # 4 | # Version 2.0 5 | # For latest version, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects 6 | # 7 | # 2013 updates: 8 | # - fixed the broken "save personal Schemes" 9 | # 10 | # NB: if you are storing "built" products, this WILL NOT WORK, 11 | # and you should use a different .gitignore (or none at all) 12 | # This file is for SOURCE projects, where there are many extra 13 | # files that we want to exclude 14 | # 15 | ######################### 16 | 17 | ##### 18 | # OS X temporary files that should never be committed 19 | 20 | .DS_Store 21 | *.swp 22 | *.lock 23 | profile 24 | 25 | 26 | #### 27 | # Xcode temporary files that should never be committed 28 | # 29 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this... 30 | 31 | *~.nib 32 | 33 | 34 | #### 35 | # Xcode build files - 36 | # 37 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData" 38 | 39 | DerivedData/ 40 | 41 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build" 42 | 43 | build/ 44 | 45 | 46 | ##### 47 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) 48 | # 49 | # This is complicated: 50 | # 51 | # SOMETIMES you need to put this file in version control. 52 | # Apple designed it poorly - if you use "custom executables", they are 53 | # saved in this file. 54 | # 99% of projects do NOT use those, so they do NOT want to version control this file. 55 | # ..but if you're in the 1%, comment out the line "*.pbxuser" 56 | 57 | *.pbxuser 58 | *.mode1v3 59 | *.mode2v3 60 | *.perspectivev3 61 | # NB: also, whitelist the default ones, some projects need to use these 62 | !default.pbxuser 63 | !default.mode1v3 64 | !default.mode2v3 65 | !default.perspectivev3 66 | 67 | 68 | #### 69 | # Xcode 4 - semi-personal settings 70 | # 71 | # 72 | # OPTION 1: --------------------------------- 73 | # throw away ALL personal settings (including custom schemes! 74 | # - unless they are "shared") 75 | # 76 | # NB: this is exclusive with OPTION 2 below 77 | xcuserdata 78 | 79 | # OPTION 2: --------------------------------- 80 | # get rid of ALL personal settings, but KEEP SOME OF THEM 81 | # - NB: you must manually uncomment the bits you want to keep 82 | # 83 | # NB: this is exclusive with OPTION 1 above 84 | # 85 | #xcuserdata/**/* 86 | 87 | # (requires option 2 above): Personal Schemes 88 | # 89 | #!xcuserdata/**/xcschemes/* 90 | 91 | #### 92 | # XCode 4 workspaces - more detailed 93 | # 94 | # Workspaces are important! They are a core feature of Xcode - don't exclude them :) 95 | # 96 | # Workspace layout is quite spammy. For reference: 97 | # 98 | # /(root)/ 99 | # /(project-name).xcodeproj/ 100 | # project.pbxproj 101 | # /project.xcworkspace/ 102 | # contents.xcworkspacedata 103 | # /xcuserdata/ 104 | # /(your name)/xcuserdatad/ 105 | # UserInterfaceState.xcuserstate 106 | # /xcsshareddata/ 107 | # /xcschemes/ 108 | # (shared scheme name).xcscheme 109 | # /xcuserdata/ 110 | # /(your name)/xcuserdatad/ 111 | # (private scheme).xcscheme 112 | # xcschememanagement.plist 113 | # 114 | # 115 | 116 | #### 117 | # Xcode 4 - Deprecated classes 118 | # 119 | # Allegedly, if you manually "deprecate" your classes, they get moved here. 120 | # 121 | # We're using source-control, so this is a "feature" that we do not want! 122 | 123 | *.moved-aside 124 | 125 | #### 126 | # CLion Project files 127 | 128 | .idea/ 129 | 130 | #### 131 | # CMake 132 | # Ignore CMake-generated files 133 | CMakeCache.txt 134 | CMakeFiles 135 | CMakeScripts 136 | Makefile 137 | cmake_install.cmake 138 | install_manifest.txt 139 | LibOL.xcodeproj 140 | 141 | #### 142 | # UNKNOWN: recommended by others, but I can't discover what these files are 143 | # 144 | # ...none. Everything is now explained. -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ### Configuration 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | project(libOL) 5 | 6 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 7 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 8 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 9 | 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Werror -Wextra -std=c++11") 11 | set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") 12 | 13 | if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin" AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -Wno-deprecated-declarations") 15 | endif() 16 | 17 | include_directories("${PROJECT_SOURCE_DIR}/src") 18 | 19 | ### libOL 20 | set(LIBOL_SOURCES 21 | src/libOL/Blowfish/Blowfish.cpp 22 | src/libOL/ChunkHeader.cpp 23 | src/libOL/Chunks.cpp 24 | src/libOL/Header.cpp 25 | src/libOL/PayloadHeader.cpp 26 | src/libOL/Rofl.cpp 27 | src/libOL/Block.cpp 28 | src/libOL/Value.cpp 29 | src/libOL/Packet.cpp 30 | src/libOL/ParseException.cpp 31 | ) 32 | 33 | if(CMAKE_GENERATOR MATCHES "Xcode") 34 | FILE(GLOB_RECURSE LIBOL_INC "src/libOL/*.h") 35 | SET(LIBOL_SOURCES ${LIBOL_SOURCES} ${LIBOL_INC}) 36 | endif() 37 | 38 | add_library(OL ${LIBOL_SOURCES}) 39 | 40 | ### zlib 41 | find_package(ZLIB REQUIRED) 42 | if (ZLIB_FOUND) 43 | include_directories(${ZLIB_INCLUDE_DIRS}) 44 | target_link_libraries(OL ${ZLIB_LIBRARIES}) 45 | endif(ZLIB_FOUND) 46 | 47 | ### OpenSSL 48 | find_package(OpenSSL REQUIRED) 49 | if (OPENSSL_FOUND) 50 | include_directories(${OPENSSL_INCLUDE_DIRS}) 51 | target_link_libraries(OL ${OPENSSL_LIBRARIES}) 52 | endif(OPENSSL_FOUND) 53 | 54 | ### Test libOL 55 | set(LIBOL_TEST_SOURCES 56 | src/libOL_test/main.cpp 57 | ) 58 | 59 | if(CMAKE_GENERATOR MATCHES "Xcode") 60 | FILE(GLOB_RECURSE LIBOL_TEST_INC "src/libOL_test/*.h") 61 | SET(LIBOL_TEST_SOURCES ${LIBOL_TEST_SOURCES} ${LIBOL_TEST_INC}) 62 | endif() 63 | 64 | add_executable(libOL_test ${LIBOL_TEST_SOURCES}) 65 | target_link_libraries(libOL_test OL) 66 | 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Andrew Toulouse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ``` 4 | % git clone https://github.com/loldevs/libOL.git 5 | % mkdir libOL/build 6 | % cd libOL/build 7 | % cmake .. # add -G Xcode if you prefer 8 | ``` 9 | 10 | # FAQ 11 | 12 | Ask a question via the issues tool, and we'll add the interesting ones here! 13 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * Decryption 2 | * Using system zlib 3 | * Keyframe / Chunk decoding 4 | * Figure out if the API could be expressed in a better way (I'm new to C++!) 5 | * Make more C-friendly? 6 | * Use spectator dumps instead of old replay files 7 | * Probably at some point 8 | * REST API client library 9 | * RTMP client 10 | * XMPP wrapper 11 | -------------------------------------------------------------------------------- /src/libOL/Block.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #include "Block.h" 5 | #include "ParseException.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace libol { 11 | bool get_bit(uint8_t byte, int n) { 12 | return (byte >> (7 - n)) & 1; 13 | } 14 | 15 | Block::Stream Block::createStream(size_t offset) { 16 | Stream stream(*this, offset); 17 | return stream; 18 | } 19 | 20 | Block Block::decode(std::ifstream& ifs) { 21 | Block block; 22 | 23 | block.offset = ifs.tellg(); 24 | 25 | block.header.marker = ifs.get(); 26 | 27 | block.channel = block.header.marker & 0xf; 28 | 29 | // Bit 1: Time format 30 | block.header.timeIsAbs = !get_bit(block.header.marker, 0); 31 | if(block.header.timeIsAbs) { 32 | ifs.read(reinterpret_cast(&block.header.timeAbs), sizeof(block.header.timeAbs)); 33 | } else { 34 | block.header.timeDiff = ifs.get(); 35 | } 36 | 37 | // Bit 4: Size size 38 | block.header.sizeIs32 = !get_bit(block.header.marker, 3); 39 | if(block.header.sizeIs32) { 40 | ifs.read(reinterpret_cast(&block.header.size32), sizeof(block.header.size32)); 41 | block.size = block.header.size32; 42 | } else { 43 | block.header.size8 = ifs.get(); 44 | block.size = (unsigned) block.header.size8; 45 | } 46 | 47 | // Bit 2: Has type 48 | block.header.hasExplicitType = !get_bit(block.header.marker, 1); 49 | if(block.header.hasExplicitType) { 50 | block.header.type = ifs.get(); 51 | } 52 | 53 | // Bit 3: Blockdata size 54 | block.header.paramIs32 = !get_bit(block.header.marker, 2); 55 | if(block.header.paramIs32) { 56 | ifs.read(reinterpret_cast(&block.header.param32), sizeof(block.header.param32)); 57 | } else { 58 | block.header.param8 = ifs.get(); 59 | } 60 | 61 | // Read content 62 | block.content.resize(block.size); 63 | ifs.read(reinterpret_cast(block.content.data()), block.size); 64 | 65 | return block; 66 | } 67 | 68 | Block Block::decode(uint8_t* buf, size_t& pos, size_t len) { 69 | Block block; 70 | 71 | block.offset = pos; 72 | 73 | REQUIRE(pos + 1 <= len); 74 | block.header.marker = buf[pos++]; 75 | 76 | block.channel = block.header.marker & 0xf; 77 | 78 | // Bit 1: Time format 79 | block.header.timeIsAbs = !get_bit(block.header.marker, 0); 80 | if(block.header.timeIsAbs) { 81 | REQUIRE(pos + 4 <= len); 82 | memcpy(&block.header.timeAbs, buf + pos, sizeof(block.header.timeAbs)); 83 | pos += 4; 84 | } else { 85 | REQUIRE(pos + 1 <= len); 86 | block.header.timeDiff = buf[pos++]; 87 | } 88 | 89 | // Bit 4: Size size 90 | block.header.sizeIs32 = !get_bit(block.header.marker, 3); 91 | if(block.header.sizeIs32) { 92 | REQUIRE(pos + 4 <= len); 93 | memcpy(&block.header.size32, buf + pos, sizeof(block.header.size32)); 94 | pos += 4; 95 | block.size = block.header.size32; 96 | } else { 97 | REQUIRE(pos + 1 <= len); 98 | block.header.size8 = buf[pos++]; 99 | block.size = (unsigned) block.header.size8; 100 | } 101 | 102 | // Bit 2: Has type 103 | block.header.hasExplicitType = !get_bit(block.header.marker, 1); 104 | if(block.header.hasExplicitType) { 105 | REQUIRE(pos + 1 < len); 106 | block.header.type = buf[pos++]; 107 | } 108 | 109 | // Bit 3: Blockdata size 110 | block.header.paramIs32 = !get_bit(block.header.marker, 2); 111 | if(block.header.paramIs32) { 112 | REQUIRE(pos + 4 <= len); 113 | memcpy(&block.header.param32, buf + pos, sizeof(block.header.param32)); 114 | pos += 4; 115 | } else { 116 | REQUIRE(pos + 1 <= len); 117 | block.header.param8 = buf[pos++]; 118 | } 119 | 120 | // Read content 121 | block.content.resize(block.size); 122 | REQUIRE(pos + block.size <= len); 123 | memcpy(block.content.data(), buf + pos, block.size); 124 | pos += block.size; 125 | 126 | return block; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/libOL/Block.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__Block__ 5 | #define __libol__Block__ 6 | 7 | #include "ParseException.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace libol { 15 | class Block { 16 | public: 17 | size_t offset; 18 | 19 | struct BlockHeader { 20 | uint8_t marker; 21 | 22 | bool timeIsAbs; 23 | float timeAbs; 24 | uint8_t timeDiff; 25 | 26 | bool sizeIs32; 27 | uint8_t size8; 28 | uint32_t size32; 29 | 30 | bool hasExplicitType; 31 | uint8_t type; 32 | 33 | bool paramIs32; 34 | uint8_t param8; 35 | uint32_t param32; 36 | }; 37 | 38 | BlockHeader header; 39 | 40 | uint8_t channel; 41 | float time; 42 | uint8_t type; 43 | uint32_t entityId; // (?) 44 | uint32_t size; 45 | 46 | std::vector content; 47 | 48 | template 49 | void read(T* dest, size_t offset) { 50 | REQUIRE(offset + sizeof(T) <= this->size); 51 | memcpy(dest, this->content.data() + offset, sizeof(T)); 52 | } 53 | template 54 | void read(T* dest, size_t offset, size_t count) { 55 | size_t length = count * sizeof(T); 56 | REQUIRE(offset + length <= this->size); 57 | memcpy(dest, this->content.data() + offset, length); 58 | } 59 | 60 | class Stream { 61 | Block& block; 62 | size_t pos; 63 | public: 64 | Stream(Block& block, size_t pos = 0) : 65 | block(block), 66 | pos(pos) 67 | {} 68 | 69 | size_t tellg() { return pos; } 70 | void seekg(size_t offset) { pos = offset; } 71 | void ignore(size_t bytes) { pos += bytes; } 72 | uint8_t get() { uint8_t data; read(&data); return data; } 73 | 74 | template 75 | T read() { 76 | T val; 77 | this->block.read(&val, pos); 78 | pos += sizeof(T); 79 | return val; 80 | } 81 | template 82 | void read(T* dest) { 83 | this->block.read(dest, pos); 84 | pos += sizeof(T); 85 | } 86 | template 87 | void read(T* dest, size_t count){ 88 | this->block.read(dest, pos, count); 89 | pos += sizeof(T) * count; 90 | } 91 | }; 92 | 93 | Stream createStream(size_t offset = 0); 94 | 95 | static Block decode(std::ifstream& ifs); 96 | static Block decode(uint8_t* buf, size_t& pos, size_t len); 97 | }; 98 | } 99 | 100 | #endif /* defined(__libol__Block__) */ 101 | -------------------------------------------------------------------------------- /src/libOL/BlockReader.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__BlockReader__ 5 | #define __libol__BlockReader__ 6 | 7 | #include "Block.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace libol { 13 | class BlockReader { 14 | float lastTime; 15 | uint8_t lastType; 16 | uint32_t lastEntId; 17 | 18 | void processBlock(Block& block) { 19 | if (block.header.timeIsAbs) { 20 | lastTime = block.header.timeAbs; 21 | } else { 22 | lastTime += block.header.timeDiff / 1000.0; 23 | } 24 | block.time = lastTime; 25 | 26 | if (block.header.hasExplicitType) { 27 | lastType = block.header.type; 28 | } 29 | block.type = lastType; 30 | 31 | if (block.header.paramIs32) { 32 | lastEntId = block.header.param32; 33 | } else { 34 | lastEntId += block.header.param8; 35 | } 36 | block.entityId = lastEntId; 37 | } 38 | public: 39 | std::vector readBlocksFromStream(std::ifstream& ifs) { 40 | std::vector result; 41 | 42 | while (true) { 43 | Block block = Block::decode(ifs); 44 | ifs.peek(); // provoke eof 45 | if (!ifs.eof()) { 46 | processBlock(block); 47 | result.push_back(block); 48 | } else 49 | break; 50 | } 51 | 52 | return result; 53 | } 54 | 55 | std::vector readBlocksFromBuffer(uint8_t* data, size_t len) { 56 | std::vector result; 57 | 58 | size_t pos = 0; 59 | 60 | while (pos < len - 1) { 61 | Block block = Block::decode(data, pos, len); 62 | processBlock(block); 63 | result.push_back(block); 64 | } 65 | 66 | return result; 67 | } 68 | }; 69 | } 70 | 71 | #endif /* defined(__libol__BlockReader__) */ 72 | -------------------------------------------------------------------------------- /src/libOL/Blowfish/Blowfish.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Luke Granger-Brown. 2 | // Distributed under the MIT License. 3 | 4 | #include "Blowfish.h" 5 | 6 | #include 7 | #include 8 | 9 | extern "C" { 10 | #include 11 | } 12 | 13 | #define BLOCK_SIZE 8 14 | 15 | static std::vector actuallyDoTheCrypto(std::vector bytes, std::vector key, bool amEncrypting) { 16 | // initialise the blowfish state 17 | //std::unique_ptr blowfish(new BF_KEY); 18 | auto blowfish = new BF_KEY; 19 | BF_set_key(blowfish, key.size(), &key[0]); 20 | 21 | // allocate the output buffer 22 | std::vector out; 23 | out.resize(bytes.size()); 24 | 25 | // loop through and do the crypto 26 | // XXX: this code should probably be rewritten 27 | auto size = bytes.size(); 28 | auto encDecFlag = amEncrypting ? BF_ENCRYPT : BF_DECRYPT; 29 | for (std::vector::size_type i = 0; i < size; i += BLOCK_SIZE) { 30 | BF_ecb_encrypt(&bytes[i], &out[i], blowfish, encDecFlag); 31 | } 32 | 33 | return out; 34 | } 35 | 36 | namespace libol { 37 | namespace Blowfish { 38 | std::vector rawDecrypt(std::vector bytes, std::vector key) { 39 | return ::actuallyDoTheCrypto(bytes, key, false); 40 | } 41 | 42 | /** 43 | * \throws std::invalid_argument if the padding is invalid 44 | */ 45 | std::vector decrypt(std::vector bytes, std::vector key) { 46 | auto data = rawDecrypt(bytes, key); 47 | 48 | if (data.size() == 0) { 49 | // don't strip the padding from something with no length... 50 | return data; 51 | } 52 | 53 | // how much padding do we need to remove? 54 | uint8_t paddingBytes = *(data.end() - 1); 55 | 56 | // erm, what? 57 | if (paddingBytes > data.size() || paddingBytes > BLOCK_SIZE || paddingBytes == 0) { 58 | throw std::invalid_argument("The padding was invalid"); 59 | } 60 | 61 | // remove it! 62 | data.erase(data.end() - paddingBytes, data.end()); 63 | 64 | return data; 65 | } 66 | std::vector rawEncrypt(std::vector bytes, std::vector key) { 67 | return ::actuallyDoTheCrypto(bytes, key, true); 68 | } 69 | std::vector encrypt(std::vector in_bytes, std::vector key) { 70 | auto bytes = in_bytes; 71 | auto size = bytes.size(); 72 | 73 | // this needs to be a multiple of BLOCK_SIZE 74 | auto paddingSize = BLOCK_SIZE - (size % BLOCK_SIZE); 75 | 76 | // there must be at least some padding 77 | if (paddingSize == 0) paddingSize = BLOCK_SIZE; 78 | 79 | // allocate the new size 80 | size += (BLOCK_SIZE - (size % BLOCK_SIZE)); 81 | bytes.reserve(size); 82 | 83 | // now write the padding 84 | bytes.push_back(paddingSize); 85 | 86 | return rawEncrypt(bytes, key); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/libOL/Blowfish/Blowfish.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Luke Granger-Brown. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__Blowfish__ 5 | #define __libol__Blowfish__ 6 | 7 | #include 8 | #include 9 | 10 | namespace libol { 11 | namespace Blowfish { 12 | std::vector decrypt(std::vector bytes, std::vector key); 13 | std::vector encrypt(std::vector bytes, std::vector key); 14 | } 15 | } 16 | 17 | #endif /* defined(__libol__Blowfish__) */ 18 | -------------------------------------------------------------------------------- /src/libOL/ChunkHeader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #include "ChunkHeader.h" 5 | 6 | #include 7 | 8 | namespace libol { 9 | 10 | inline void decodeInPlace(std::ifstream& ifs, ChunkHeader& header) { 11 | ifs.read(reinterpret_cast(&header.chunkId), sizeof(header.chunkId)); 12 | ifs.read(reinterpret_cast(&header.chunkType), sizeof(header.chunkType)); 13 | ifs.read(reinterpret_cast(&header.chunkLength), sizeof(header.chunkLength)); 14 | ifs.read(reinterpret_cast(&header.nextChunkId), sizeof(header.nextChunkId)); 15 | ifs.read(reinterpret_cast(&header.offset), sizeof(header.offset)); 16 | } 17 | 18 | std::vector ChunkHeader::decodeMultiple(std::ifstream& ifs, int count) { 19 | auto headers = std::vector(count); 20 | for (int i = 0; i < count; i++) { 21 | libol::decodeInPlace(ifs, headers[i]); 22 | } 23 | return headers; 24 | } 25 | 26 | ChunkHeader ChunkHeader::decode(std::ifstream& ifs) { 27 | ChunkHeader chunkHeader; 28 | libol::decodeInPlace(ifs, chunkHeader); 29 | return chunkHeader; 30 | } 31 | } -------------------------------------------------------------------------------- /src/libOL/ChunkHeader.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__ChunkHeader__ 5 | #define __libol__ChunkHeader__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace libol { 12 | class ChunkHeader { 13 | public: 14 | int32_t chunkId; 15 | uint8_t chunkType; 16 | int32_t chunkLength; 17 | int32_t nextChunkId; 18 | int32_t offset; 19 | 20 | static std::vector decodeMultiple(std::ifstream& ifs, int count); 21 | static ChunkHeader decode(std::ifstream& ifs); 22 | }; 23 | } 24 | 25 | #endif /* defined(__libol__ChunkHeader__) */ 26 | -------------------------------------------------------------------------------- /src/libOL/Chunks.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #include "Chunks.h" 5 | 6 | #include 7 | 8 | extern "C" { 9 | #include 10 | } 11 | 12 | #include "Blowfish/Blowfish.h" 13 | 14 | namespace libol { 15 | namespace Chunks { 16 | std::vector decryptAndDecompress(std::vector bytes, std::vector key) { 17 | auto decrypted = Blowfish::decrypt(bytes, key); 18 | 19 | std::vector decompressed; 20 | decompressed.resize(decrypted.size()); 21 | 22 | z_stream stream; 23 | stream.next_in = (Bytef *)decrypted.data(); 24 | stream.avail_in = decrypted.size(); 25 | stream.total_out = 0; 26 | stream.zalloc = Z_NULL; 27 | stream.zfree = Z_NULL; 28 | 29 | if (inflateInit2(&stream, (16 + MAX_WBITS)) != Z_OK) { 30 | throw std::runtime_error("zlib: inflateInit2 not Z_OK"); 31 | } 32 | 33 | bool done = false; 34 | while (!done) { 35 | // If total_out has reached decompressed's capacity, make room for more 36 | if (stream.total_out >= decompressed.capacity()) { 37 | // Resize output to 150% of previous size 38 | decompressed.resize(decompressed.capacity() + (decompressed.capacity() << 1)); 39 | } 40 | 41 | stream.next_out = (Bytef *)(&decompressed[0] + stream.total_out); 42 | stream.avail_out = decompressed.capacity() - stream.total_out; 43 | int err = inflate(&stream, Z_SYNC_FLUSH); 44 | if (err == Z_STREAM_END) { 45 | done = true; 46 | } else if (err != Z_OK) { 47 | throw std::runtime_error("zlib: inflate not Z_OK"); 48 | } 49 | } 50 | 51 | if (inflateEnd(&stream) != Z_OK) { 52 | throw std::runtime_error("zlib: inflateEnd not Z_OK"); 53 | } 54 | 55 | decompressed.resize(stream.total_out); 56 | return decompressed; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/libOL/Chunks.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__Chunks__ 5 | #define __libol__Chunks__ 6 | 7 | #include 8 | #include 9 | 10 | namespace libol { 11 | namespace Chunks { 12 | std::vector decryptAndDecompress(std::vector bytes, std::vector key); 13 | } 14 | } 15 | 16 | #endif /* defined(__libol__Chunks__) */ 17 | -------------------------------------------------------------------------------- /src/libOL/Constants.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__Constants__ 5 | #define __libol__Constants__ 6 | 7 | #include 8 | 9 | namespace libol { 10 | struct PacketType { 11 | typedef uint16_t Id; 12 | 13 | enum : Id { 14 | SetOwnership = 0x07, 15 | EndSpawn = 0x11, 16 | SetAbilityLevel = 0x15, 17 | GoldReward = 0x22, 18 | SummonerData = 0x2A, 19 | ChampionRespawn = 0x2F, 20 | SetLevel = 0x3F, 21 | AttentionPing = 0x40, 22 | PlayEmote = 0x42, 23 | PlayerStats = 0x46, 24 | ChampionSpawn = 0x4C, 25 | SetDeathTimer = 0x5E, 26 | MovementGroup = 0x61, 27 | StartSpawn, 28 | DamageDone = 0x65, 29 | ItemPurchase = 0x6F, 30 | SummonerDisconnect = 0x98, 31 | TurretSpawn = 0x9D, 32 | SetItemStacks = 0x9F, 33 | SetHealth = 0xAE, 34 | AttributeGroup = 0xC4, 35 | SetTeam = 0xE0, 36 | GoldGain = 0xE4, 37 | ExtendedType = 0xFE, 38 | SetInventory = 0x10C 39 | }; 40 | }; 41 | 42 | struct SummonerSpell { 43 | enum Id : uint32_t { 44 | Revive = 0x05C8B3A5, 45 | Smite = 0x065E8695, 46 | Exhaust = 0x08A8BAE4, 47 | Barrier = 0x0CCFB982, 48 | Teleport = 0x004F1364, 49 | Ghost = 0x064ACC95, 50 | Heal = 0x0364AF1C, 51 | Cleanse = 0x064D2094, 52 | Clarity = 0x03657421, 53 | Ignite = 0x06364F24, 54 | Promote = 0x0410FF72, 55 | Clair = 0x09896765, 56 | Flash = 0x06496EA8, 57 | Test = 0x0103D94C, 58 | }; 59 | 60 | static std::string getName(uint32_t id) { 61 | switch(id) { 62 | case Revive: return "Revive"; 63 | case Smite: return "Smite"; 64 | case Exhaust: return "Exhaust"; 65 | case Barrier: return "Barrier"; 66 | case Teleport: return "Teleport"; 67 | case Ghost: return "Ghost"; 68 | case Heal: return "Heal"; 69 | case Cleanse: return "Cleanse"; 70 | case Clarity: return "Clarity"; 71 | case Ignite: return "Ignite"; 72 | case Promote: return "Promote"; 73 | case Clair: return "Clair"; 74 | case Flash: return "Flash"; 75 | case Test: return "Test"; 76 | default: return "UnknownSpell"; 77 | } 78 | } 79 | }; 80 | 81 | struct AttentionPingType { 82 | enum Id : uint8_t { 83 | Default = 0xb0, 84 | Danger = 0xb2, 85 | EnemyMissing, 86 | OnMyWay, 87 | Retreat, 88 | AssistanceNeeded 89 | }; 90 | 91 | static std::string getName(uint8_t id) { 92 | switch(id) { 93 | case Default: return "Default"; 94 | case Danger: return "Danger"; 95 | case EnemyMissing: return "EnemyMissing"; 96 | case OnMyWay: return "OnMyWay"; 97 | case Retreat: return "Retreat"; 98 | case AssistanceNeeded: return "AssistanceNeeded"; 99 | default: return "UnknownPingType"; 100 | } 101 | } 102 | }; 103 | 104 | struct Team { 105 | enum : uint8_t { 106 | Blue, 107 | Purple 108 | }; 109 | 110 | static std::string getName(uint8_t id) { 111 | switch(id) { 112 | case Blue: return "Blue"; 113 | case Purple: return "Purple"; 114 | default: return "UnknownTeam"; 115 | } 116 | } 117 | }; 118 | 119 | struct Channel { 120 | enum : uint8_t { 121 | Handshake = 0, 122 | C2S = 1, 123 | Gameplay = 2, 124 | S2C = 3, 125 | LowPriority = 4, 126 | Communication = 5, 127 | LoadingScreen = 7, 128 | }; 129 | }; 130 | } 131 | 132 | #endif /* defined(__libol__Constants__) */ 133 | -------------------------------------------------------------------------------- /src/libOL/EntityAttribute.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__EntityAttribute__ 5 | #define __libol__EntityAttribute__ 6 | 7 | #include "Block.h" 8 | #include "Value.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace libol { 16 | class EntityAttribute { 17 | typedef std::pair AttrId; 18 | 19 | std::map> attributes; 20 | 21 | template 22 | void defineAttribute(uint8_t groupBit, uint8_t attrBit, std::string name) { 23 | AttrId id {groupBit, attrBit}; 24 | 25 | attributes[id] = [name] (Object& obj, Block::Stream& stream) { 26 | obj.setv(name, stream.read()); 27 | }; 28 | } 29 | 30 | EntityAttribute() { 31 | // Group 1 32 | defineAttribute(1, 1, "CurrentGold"); 33 | defineAttribute(1, 2, "TotalGold"); 34 | 35 | // Group 2 36 | defineAttribute(2, 6, "BaseAttackDamage"); 37 | defineAttribute(2, 7, "BaseAbilityPower"); 38 | defineAttribute(2, 9, "CritChance"); 39 | defineAttribute(2, 10, "Amor"); 40 | defineAttribute(2, 11, "MagicResistance"); 41 | defineAttribute(2, 12, "Hp5"); 42 | defineAttribute(2, 13, "Mp5"); 43 | defineAttribute(2, 14, "Range"); 44 | defineAttribute(2, 15, "BonusAttackDamage"); 45 | defineAttribute(2, 16, "PctBonusAttackDamage"); 46 | defineAttribute(2, 17, "BonusAbilityPower"); 47 | defineAttribute(2, 20, "PctAttackSpeed"); 48 | defineAttribute(2, 23, "CooldownReduction"); 49 | defineAttribute(2, 26, "AmorPenetration"); 50 | defineAttribute(2, 27, "PctAmorPenetration"); 51 | defineAttribute(2, 28, "MagicPenetration"); 52 | defineAttribute(2, 29, "PctMagicPenetration"); 53 | defineAttribute(2, 30, "PctLifeSteal"); 54 | defineAttribute(2, 31, "PctSpellVamp"); 55 | defineAttribute(2, 32, "Tenacity"); 56 | 57 | // Group 3 58 | 59 | // Group 4 60 | defineAttribute(4, 1, "CurrentHealth"); 61 | defineAttribute(4, 2, "CurrentMana"); 62 | defineAttribute(4, 3, "MaxHealth"); 63 | defineAttribute(4, 4, "MaxMana"); 64 | defineAttribute(4, 5, "Experience"); 65 | defineAttribute(4, 10, "VisionRange"); 66 | defineAttribute(4, 11, "MovementSpeed"); 67 | defineAttribute(4, 12, "ModelSize"); 68 | defineAttribute(4, 15, "Level"); 69 | } 70 | public: 71 | static bool read(Object* obj, Block::Stream& stream, uint8_t groupBit, uint8_t attrBit) { 72 | static EntityAttribute reader; 73 | AttrId id {groupBit + 1, attrBit + 1}; 74 | 75 | if(!reader.attributes.count(id)) return false; 76 | try { 77 | reader.attributes[id](*obj, stream); 78 | } catch(...) { 79 | // TODO: investigate, minions have different mask? 80 | return false; 81 | } 82 | return true; 83 | } 84 | }; 85 | } 86 | 87 | #endif /* defined(__libol__EntityAttribute__) */ 88 | -------------------------------------------------------------------------------- /src/libOL/Header.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #include "Header.h" 5 | 6 | #include 7 | 8 | namespace libol { 9 | Header Header::decode(std::ifstream& ifs) { 10 | Header header; 11 | ifs.read(reinterpret_cast(header.magic.data()), header.magic.size()); 12 | ifs.read(reinterpret_cast(header.signature.data()), header.signature.size()); 13 | ifs.read(reinterpret_cast(&header.headerlength), sizeof(header.headerlength)); 14 | ifs.read(reinterpret_cast(&header.fileLength), sizeof(header.fileLength)); 15 | ifs.read(reinterpret_cast(&header.metadataOffset), sizeof(header.metadataOffset)); 16 | ifs.read(reinterpret_cast(&header.metadataLength), sizeof(header.metadataLength)); 17 | ifs.read(reinterpret_cast(&header.payloadHeaderOffset), sizeof(header.payloadHeaderOffset)); 18 | ifs.read(reinterpret_cast(&header.payloadHeaderLength), sizeof(header.payloadHeaderLength)); 19 | ifs.read(reinterpret_cast(&header.payloadOffset), sizeof(header.payloadOffset)); 20 | return header; 21 | } 22 | } -------------------------------------------------------------------------------- /src/libOL/Header.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__Header__ 5 | #define __libol__Header__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace libol { 12 | class Header { 13 | public: 14 | std::array magic; 15 | std::array signature; 16 | uint16_t headerlength; 17 | uint32_t fileLength; 18 | uint32_t metadataOffset; 19 | uint32_t metadataLength; 20 | uint32_t payloadHeaderOffset; 21 | uint32_t payloadHeaderLength; 22 | uint32_t payloadOffset; 23 | 24 | static Header decode(std::ifstream& ifs); 25 | }; 26 | } 27 | 28 | #endif /* defined(__libol__Header__) */ 29 | -------------------------------------------------------------------------------- /src/libOL/Packet.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #include "Packet.h" 5 | #include "PacketParser.h" 6 | 7 | 8 | namespace libol { 9 | Packet Packet::decode(Block& block) { 10 | return PacketParser::getInstance().decode(block); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/libOL/Packet.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__Packet__ 5 | #define __libol__Packet__ 6 | 7 | #include "Value.h" 8 | #include "Constants.h" 9 | #include "Block.h" 10 | #include 11 | 12 | namespace libol { 13 | struct Packet { 14 | ~Packet() { 15 | data.destroy(); 16 | }; 17 | 18 | float timestamp; 19 | PacketType::Id type; 20 | uint32_t entityId; 21 | 22 | bool isDecoded; 23 | std::string typeName; 24 | Value data; 25 | 26 | static Packet decode(Block& block); 27 | }; 28 | } 29 | 30 | #endif /* defined(__libol__Packet__) */ 31 | -------------------------------------------------------------------------------- /src/libOL/PacketDecoders.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__PacketDecoders__ 5 | #define __libol__PacketDecoders__ 6 | 7 | #include "Value.h" 8 | #include "Block.h" 9 | #include "Constants.h" 10 | #include "EntityAttribute.h" 11 | #include "ParseException.h" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace libol { 18 | class SetAbilityLevelPkt { 19 | public: 20 | static const PacketType::Id type = PacketType::SetAbilityLevel; 21 | static std::string name() { return "SetAbilityLevel"; } 22 | 23 | static Value decode(Block& block) { 24 | REQUIRE(block.size == 0x3); 25 | 26 | Object data = Object(); 27 | 28 | data.setv("abilityId", block.content[0]); 29 | data.setv("level", block.content[1]); 30 | 31 | //assert(block.content[2] == 0x00); 32 | 33 | return Value::create(data); 34 | } 35 | }; 36 | 37 | class GoldRewardPkt { 38 | public: 39 | static const PacketType::Id type = PacketType::GoldReward; 40 | static std::string name() { return "GoldReward"; } 41 | 42 | static Value decode(Block& block) { 43 | REQUIRE(block.size == 0xc); 44 | 45 | Object data = Object(); 46 | 47 | auto stream = block.createStream(); 48 | 49 | data.setv("receiverEntId", stream.read()); 50 | data.setv("killedEntId", stream.read()); 51 | data.setv("amount", stream.read()); 52 | 53 | return Value::create(data); 54 | } 55 | }; 56 | 57 | class GoldGainPkt { 58 | public: 59 | static const PacketType::Id type = PacketType::GoldGain; 60 | static std::string name() { return "GoldGain"; } 61 | 62 | static Value decode(Block& block) { 63 | REQUIRE(block.size == 0x8); 64 | 65 | Object data = Object(); 66 | 67 | auto stream = block.createStream(); 68 | 69 | data.setv("receiverEntId", stream.read()); 70 | data.setv("amount", stream.read()); 71 | 72 | return Value::create(data); 73 | } 74 | }; 75 | 76 | class SetInventoryPkt { 77 | public: 78 | static const PacketType::Id type = PacketType::SetInventory; 79 | static std::string name() { return "SetInventory"; } 80 | 81 | static Value decode(Block& block) { 82 | REQUIRE(block.size == 0x98); 83 | 84 | Object data = Object(); 85 | std::array items; 86 | 87 | auto stream = block.createStream(0x2); 88 | 89 | for(size_t i = 0; i < items.size(); i++) { 90 | items[i].setv("itemId", stream.read()); 91 | items[i].setv("slotId", stream.get()); 92 | items[i].setv("stacks", stream.get()); 93 | items[i].setv("charges", stream.get()); 94 | } 95 | 96 | for(size_t i = 0; i < items.size(); i++) { 97 | items[i].setv("cooldown", stream.read()); 98 | } 99 | 100 | for(size_t i = 0; i < items.size(); i++) { 101 | items[i].setv("baseCooldown", stream.read()); 102 | } 103 | 104 | Array itemsArr = Array(); 105 | for(auto& item : items) 106 | itemsArr.pushv(item); 107 | data.setv("items", itemsArr); 108 | 109 | return Value::create(data); 110 | } 111 | }; 112 | 113 | class ItemPurchasePkt { 114 | public: 115 | static const PacketType::Id type = PacketType::ItemPurchase; 116 | static std::string name() { return "ItemPurchase"; } 117 | 118 | static Value decode(Block& block) { 119 | REQUIRE(block.size == 0x8); 120 | 121 | Object data = Object(); 122 | 123 | auto stream = block.createStream(); 124 | 125 | data.setv("itemId", stream.read()); 126 | data.setv("slot", stream.get()); 127 | data.setv("stacks", stream.read()); 128 | 129 | //assert(block.content[0x7] == 0x40); 130 | 131 | return Value::create(data); 132 | } 133 | }; 134 | 135 | class ChampionSpawnPkt { 136 | public: 137 | static const PacketType::Id type = PacketType::ChampionSpawn; 138 | static std::string name() { return "ChampionSpawn"; } 139 | 140 | static Value decode(Block& block) { 141 | REQUIRE(block.size == 0xC3); 142 | 143 | Object data = Object(); 144 | 145 | auto stream = block.createStream(); 146 | 147 | data.setv("entityId", stream.read()); 148 | data.setv("clientId", stream.read()); 149 | 150 | stream.ignore(0xA); // unknown 151 | 152 | // Summoner name 153 | char summonerChars[0x81]; 154 | stream.read(summonerChars, 0x80); 155 | summonerChars[0x80] = 0x00; 156 | data.setv("summonerName", std::string(summonerChars)); 157 | 158 | // Champion name 159 | char championChars[0x11]; 160 | stream.read(championChars, 0x10); 161 | championChars[0x10] = 0x00; 162 | data.setv("championName", std::string(championChars)); 163 | 164 | stream.ignore(0x21); // unknown 165 | 166 | return Value::create(data); 167 | } 168 | }; 169 | 170 | class SummonerDataPkt { 171 | public: 172 | static const PacketType::Id type = PacketType::SummonerData; 173 | static std::string name() { return "SummonerData"; } 174 | 175 | static Value decode(Block& block) { 176 | REQUIRE(block.size == 0x212); 177 | 178 | Object data = Object(); 179 | 180 | auto stream = block.createStream(); 181 | 182 | Array runes = Array(); 183 | for(size_t i = 0; i < 30; i++) { 184 | runes.pushv(stream.read()); 185 | } 186 | data.setv("runes", runes); 187 | 188 | uint32_t spell1Id = stream.read(); 189 | uint32_t spell2Id = stream.read(); 190 | data.setv("spell1", spell1Id); 191 | data.setv("spell1Name", SummonerSpell::getName(spell1Id)); 192 | data.setv("spell2", spell2Id); 193 | data.setv("spell2Name", SummonerSpell::getName(spell2Id)); 194 | 195 | Array masteries = Array(); 196 | size_t masteryCount = 0; 197 | while(masteryCount++ < 79) { 198 | auto pos = stream.tellg(); 199 | stream.ignore(2); 200 | uint8_t test = stream.get(); 201 | stream.seekg(pos); 202 | if(test != 0x03) break; 203 | 204 | Object entry = Object(); 205 | uint8_t id = stream.get(); 206 | entry.setv("id", id); 207 | uint8_t tree = stream.get(); 208 | entry.setv("tree", tree); 209 | entry.setv("talentId", 4100 + (tree - 0x74) * 0x64 + ((id >> 4) - 0x03) * 0x0A + (id & 0x0F)); 210 | EXPECT(stream.get() == 0x03); 211 | EXPECT(stream.get() == 0x00); 212 | entry.setv("pointsSpent", stream.get()); 213 | masteries.pushv(entry); 214 | } 215 | data.setv("masteries", masteries); 216 | 217 | data.setv("level", block.content[0x210]); 218 | 219 | return Value::create(data); 220 | } 221 | }; 222 | 223 | class PlayerStatsPkt { 224 | public: 225 | static const PacketType::Id type = PacketType::PlayerStats; 226 | static std::string name() { return "PlayerStats"; } 227 | 228 | static Value decode(Block& block) { 229 | bool hasJungleStats; 230 | switch(block.size) { 231 | case 0x128: 232 | hasJungleStats = false; 233 | break; 234 | case 0x130: 235 | hasJungleStats = true; 236 | break; 237 | default: 238 | throw ParseException("PlayerStats: unknown size " + std::to_string(block.size)); 239 | } 240 | 241 | Object data = Object(); 242 | 243 | auto stream = block.createStream(0x4); 244 | 245 | data.setv("assists", stream.read()); 246 | stream.ignore(0x4); 247 | data.setv("kills", stream.read()); 248 | stream.ignore(0x4); 249 | data.setv("doubleKills", stream.read()); 250 | stream.ignore(3 * 0x4); 251 | data.setv("unrealKills", stream.read()); 252 | data.setv("goldEarned", stream.read()); 253 | data.setv("goldSpent", stream.read()); 254 | stream.ignore(10 * 0x4); 255 | data.setv("currentKillingSpree", stream.read()); 256 | data.setv("largestCriticalStrike", stream.read()); 257 | data.setv("largestKillingSpree", stream.read()); 258 | data.setv("largestMultiKill", stream.read()); 259 | stream.ignore(0x4); 260 | data.setv("longestTimeSpentLiving", stream.read()); 261 | data.setv("magicDamageDealt", stream.read()); 262 | data.setv("magicDamageDealtToChampions", stream.read()); 263 | data.setv("magicDamageTaken", stream.read()); 264 | data.setv("minionsKilled", stream.read()); 265 | stream.ignore(0x2); // Padding 266 | data.setv("neutralMinionsKilled", stream.read()); 267 | if(hasJungleStats) { 268 | data.setv("neutralMinionsKilledInEnemyJungle", stream.read()); 269 | data.setv("neutralMinionsKilledInTeamJungle", stream.read()); 270 | } 271 | stream.ignore(0x4); 272 | data.setv("deaths", stream.read()); 273 | data.setv("pentaKills", stream.read()); 274 | data.setv("physicalDamageDealt", stream.read()); 275 | data.setv("physicalDamageDealtToChampions", stream.read()); 276 | data.setv("physicalDamageTaken", stream.read()); 277 | stream.ignore(0x4); 278 | data.setv("quadraKills", stream.read()); 279 | stream.ignore(9 * 0x4); 280 | data.setv("teamId", stream.read()); 281 | stream.ignore(4 * 0x4); 282 | data.setv("totalDamageDealt", stream.read()); 283 | data.setv("totalDamageDealtToChamptions", stream.read()); 284 | data.setv("totalDamageTaken", stream.read()); 285 | data.setv("totalHeal", stream.read()); 286 | data.setv("totalTimeCrowdControlDealt", stream.read()); 287 | data.setv("totalTimeSpentDead", stream.read()); 288 | data.setv("totalUnitsHealed", stream.read()); 289 | data.setv("tripleKills", stream.read()); 290 | data.setv("trueDamageDealt", stream.read()); 291 | data.setv("trueDamageDealtToChamptions", stream.read()); 292 | data.setv("trueDamageTaken", stream.read()); 293 | data.setv("towerKills", stream.read()); 294 | data.setv("inhibitorKills", stream.read()); 295 | stream.ignore(0x4); 296 | data.setv("wardsKilled", stream.read()); 297 | data.setv("wardsPlaced", stream.read()); 298 | stream.ignore(2 * 0x4); 299 | stream.ignore(0x2); // Padding 300 | 301 | return Value::create(data); 302 | } 303 | }; 304 | 305 | class MovementGroupPkt { 306 | public: 307 | static const PacketType::Id type = PacketType::MovementGroup; 308 | static std::string name() { return "MovementGroup"; } 309 | 310 | static Value decode(Block& block) { 311 | Object data = Object(); 312 | 313 | auto stream = block.createStream(); 314 | 315 | data.setv("timestamp", stream.read()); // in ms from start 316 | uint16_t numUpdates = stream.read(); 317 | 318 | Array updates = Array(); 319 | while(numUpdates--) { 320 | Object update = Object(); 321 | 322 | uint8_t numCoords = stream.get(); // includes the 2 start coords 323 | update.setv("entityId", stream.read()); 324 | 325 | if(numCoords % 2) { 326 | stream.ignore(1); 327 | numCoords--; 328 | } 329 | 330 | std::vector bitmask; // defines if a coord is relative for non-start coords 331 | if(numCoords > 2) { 332 | size_t bmSize = std::floor((numCoords - 3) / 8.f) + 1; 333 | for (size_t i = 0; i < bmSize; i++) 334 | bitmask.push_back(stream.get()); 335 | } 336 | 337 | int16_t startX = stream.read(); 338 | int16_t startY = stream.read(); 339 | Object start = Object(); 340 | start.setv("x", startX); 341 | start.setv("y", startY); 342 | update.setv("position", start); 343 | 344 | Array waypoints = Array(); 345 | for(size_t i = 0; i < (size_t)(numCoords - 2); i++) { 346 | Object point = Object(); 347 | 348 | if(bitmask[floor(i / 8.f)] & (1 << i % 8)) 349 | point.setv("x", startX + stream.read()); 350 | else 351 | point.setv("x", stream.read()); 352 | 353 | i++; 354 | if(bitmask[floor(i / 8.f)] & (1 << i % 8)) 355 | point.setv("y", startY + stream.read()); 356 | else 357 | point.setv("y", stream.read()); 358 | 359 | waypoints.pushv(point); 360 | } 361 | update.setv("waypoints", waypoints); 362 | 363 | updates.pushv(update); 364 | } 365 | data.setv("updates", updates); 366 | 367 | return Value::create(data); 368 | } 369 | }; 370 | 371 | class SetOwnershipPkt { 372 | public: 373 | static const PacketType::Id type = PacketType::SetOwnership; 374 | static std::string name() { return "SetOwnership"; } 375 | 376 | static Value decode(Block& block) { 377 | REQUIRE(block.size == 0x4); 378 | 379 | Object data = Object(); 380 | 381 | auto stream = block.createStream(); 382 | 383 | data.setv("ownerEntId", stream.read()); 384 | 385 | return Value::create(data); 386 | } 387 | }; 388 | 389 | class AttentionPingPkt { 390 | public: 391 | static const PacketType::Id type = PacketType::AttentionPing; 392 | static std::string name() { return "AttentionPing"; } 393 | 394 | static Value decode(Block& block) { 395 | REQUIRE(block.size == 0x11); 396 | 397 | Object data = Object(); 398 | 399 | auto stream = block.createStream(); 400 | 401 | data.setv("x", stream.read()); 402 | data.setv("y", stream.read()); 403 | data.setv("targetEntId", stream.read()); 404 | data.setv("playerEntId", stream.read()); 405 | 406 | uint8_t type = stream.read(); 407 | data.setv("type", type); 408 | data.setv("typeName", AttentionPingType::getName(type)); 409 | 410 | return Value::create(data); 411 | } 412 | }; 413 | 414 | class PlayEmotePkt { 415 | public: 416 | static const PacketType::Id type = PacketType::PlayEmote; 417 | static std::string name() { return "PlayEmote"; } 418 | 419 | static Value decode(Block& block) { 420 | REQUIRE(block.size == 0x1); 421 | 422 | Object data = Object(); 423 | 424 | auto stream = block.createStream(); 425 | 426 | data.setv("type", stream.read()); // TODO: find out enum 427 | 428 | return Value::create(data); 429 | } 430 | }; 431 | 432 | class DamageDonePkt { 433 | public: 434 | static const PacketType::Id type = PacketType::DamageDone; 435 | static std::string name() { return "DamageDone"; } 436 | 437 | static Value decode(Block& block) { 438 | REQUIRE(block.size == 0xd); 439 | 440 | Object data = Object(); 441 | 442 | auto stream = block.createStream(); 443 | 444 | data.setv("type", stream.read()); // TODO: find out enum 445 | data.setv("receiverEntId", stream.read()); 446 | data.setv("sourceEntId", stream.read()); 447 | data.setv("amount", stream.read()); 448 | 449 | return Value::create(data); 450 | } 451 | }; 452 | 453 | class SetDeathTimerPkt { 454 | public: 455 | static const PacketType::Id type = PacketType::SetDeathTimer; 456 | static std::string name() { return "SetDeathTimer"; } 457 | 458 | static Value decode(Block& block) { 459 | REQUIRE(block.size == 0x12); 460 | 461 | Object data = Object(); 462 | 463 | auto stream = block.createStream(); 464 | 465 | data.setv("killerEntId", stream.read()); 466 | stream.ignore(8); 467 | data.setv("timer", stream.read()); 468 | stream.ignore(2); 469 | 470 | return Value::create(data); 471 | } 472 | }; 473 | 474 | class SetHealthPkt { 475 | public: 476 | static const PacketType::Id type = PacketType::SetHealth; 477 | static std::string name() { return "SetHealth"; } 478 | 479 | static Value decode(Block& block) { 480 | if(block.size == 0x2) { // TODO: understand this 481 | throw ParseException("SetHealth: size is only 2 bytes"); 482 | } 483 | 484 | REQUIRE(block.size == 0xa); 485 | 486 | Object data = Object(); 487 | 488 | auto stream = block.createStream(); 489 | 490 | stream.ignore(2); 491 | data.setv("maxHealth", stream.read()); 492 | data.setv("currentHealth", stream.read()); 493 | 494 | return Value::create(data); 495 | } 496 | }; 497 | 498 | class AttributeGroupPkt { 499 | public: 500 | static const PacketType::Id type = PacketType::AttributeGroup; 501 | static std::string name() { return "AttributeGroup"; } 502 | 503 | static Value decode(Block& block) { 504 | Object data = Object(); 505 | 506 | auto stream = block.createStream(); 507 | 508 | data.setv("timestamp", stream.read()); // in ms from start 509 | uint8_t numUpdates = stream.read(); 510 | 511 | Array updates = Array(); 512 | while(numUpdates--) { 513 | Object update = Object(); 514 | 515 | uint8_t groupMask = stream.get(); // defines which groups of attributes will follow 516 | update.setv("entityId", stream.read()); 517 | 518 | Object attr = Object(); 519 | for(uint8_t groupBit = 0; groupBit < 8; groupBit++) { 520 | if(!(groupMask & (1 << groupBit))) continue; 521 | 522 | uint32_t attrMask = stream.read(); // defines which attributes from this group will follow 523 | 524 | uint8_t groupSize = stream.get(); 525 | size_t groupStart = stream.tellg(); 526 | 527 | for(uint8_t attrBit = 0; attrBit < 32; attrBit++) { 528 | if(!(attrMask & (1 << attrBit))) continue; 529 | 530 | if(!EntityAttribute::read(&attr, stream, groupBit, attrBit)) { 531 | stream.seekg(groupStart + groupSize); 532 | break; 533 | } 534 | } 535 | if(stream.tellg() != groupStart + groupSize) // TODO: investigate 536 | stream.seekg(groupStart + groupSize); 537 | } 538 | update.setv("attributes", attr); 539 | 540 | updates.pushv(update); 541 | } 542 | data.setv("updates", updates); 543 | 544 | return Value::create(data); 545 | } 546 | }; 547 | 548 | class SetTeamPkt { 549 | public: 550 | static const PacketType::Id type = PacketType::SetTeam; 551 | static std::string name() { return "SetTeam"; } 552 | 553 | static Value decode(Block& block) { 554 | REQUIRE(block.size == 0x1); 555 | 556 | Object data = Object(); 557 | 558 | auto stream = block.createStream(); 559 | 560 | uint8_t teamId = stream.get(); 561 | data.setv("team", teamId); 562 | data.setv("teamName", Team::getName(teamId)); 563 | 564 | return Value::create(data); 565 | } 566 | }; 567 | 568 | class SetItemStacksPkt { 569 | public: 570 | static const PacketType::Id type = PacketType::SetItemStacks; 571 | static std::string name() { return "SetItemStacks"; } 572 | 573 | static Value decode(Block& block) { 574 | REQUIRE(block.size == 0x3); 575 | 576 | Object data = Object(); 577 | 578 | auto stream = block.createStream(); 579 | 580 | data.setv("slotId", stream.get()); 581 | data.setv("stacks", stream.read()); 582 | 583 | return Value::create(data); 584 | } 585 | }; 586 | 587 | class SummonerDisconnectPkt { 588 | public: 589 | static const PacketType::Id type = PacketType::SummonerDisconnect; 590 | static std::string name() { return "SummonerDisconnect"; } 591 | 592 | static Value decode(Block& block) { 593 | REQUIRE(block.size == 0x5); 594 | 595 | Object data = Object(); 596 | 597 | auto stream = block.createStream(); 598 | 599 | data.setv("entityId", stream.read()); 600 | stream.ignore(1); // unknown 601 | 602 | return Value::create(data); 603 | } 604 | }; 605 | 606 | class SetLevelPkt { 607 | public: 608 | static const PacketType::Id type = PacketType::SetLevel; 609 | static std::string name() { return "SetLevel"; } 610 | 611 | static Value decode(Block &block) { 612 | REQUIRE(block.size == 0x2); 613 | 614 | Object data = Object(); 615 | 616 | auto stream = block.createStream(); 617 | 618 | data.setv("level", stream.read()); 619 | data.setv("skillPoints", stream.read()); 620 | 621 | return Value::create(data); 622 | } 623 | }; 624 | 625 | class ChampionRespawnPkt { 626 | public: 627 | static const PacketType::Id type = PacketType::ChampionRespawn; 628 | static std::string name() { return "ChampionRespawn"; } 629 | 630 | static Value decode(Block &block) { 631 | REQUIRE(block.size == 0xc); 632 | 633 | Object data = Object(); 634 | 635 | auto stream = block.createStream(); 636 | 637 | data.setv("x", stream.read()); 638 | data.setv("y", stream.read()); 639 | data.setv("mana", stream.read()); 640 | 641 | return Value::create(data); 642 | } 643 | }; 644 | } 645 | 646 | #endif /* defined(__libol__PacketDecoders__) */ 647 | -------------------------------------------------------------------------------- /src/libOL/PacketParser.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__PacketParser__ 5 | #define __libol__PacketParser__ 6 | 7 | #include "Block.h" 8 | #include "Packet.h" 9 | #include "PacketDecoders.h" 10 | 11 | #include 12 | #include 13 | 14 | namespace libol { 15 | class PacketParser { 16 | friend struct Packet; 17 | 18 | struct PacketDecoder { 19 | std::function< std::string () > getName; 20 | std::function< Value (Block&) > decode; 21 | }; 22 | 23 | std::map decoders; 24 | 25 | template 26 | void registerPacket() { 27 | PacketType::Id type = PACKET::type; 28 | decoders[type] = PacketDecoder({PACKET::name, PACKET::decode}); 29 | } 30 | 31 | PacketParser() { 32 | registerPacket(); 33 | registerPacket(); 34 | registerPacket(); 35 | registerPacket(); 36 | registerPacket(); 37 | registerPacket(); 38 | registerPacket(); 39 | registerPacket(); 40 | registerPacket(); 41 | registerPacket(); 42 | registerPacket(); 43 | registerPacket(); 44 | registerPacket(); 45 | registerPacket(); 46 | registerPacket(); 47 | registerPacket(); 48 | registerPacket(); 49 | registerPacket(); 50 | registerPacket(); 51 | registerPacket(); 52 | registerPacket(); 53 | } 54 | 55 | Packet decode(Block& block) { 56 | Packet packet; 57 | 58 | packet.timestamp = block.time; 59 | if(block.type == PacketType::ExtendedType) { 60 | uint16_t realType; 61 | block.read(&realType, 0); 62 | packet.type = realType; 63 | } else 64 | packet.type = block.type; 65 | packet.entityId = block.entityId; 66 | 67 | packet.isDecoded = false; 68 | if(block.channel != Channel::LoadingScreen && decoders.count(packet.type)) { 69 | auto& decoder = decoders[packet.type]; 70 | try { 71 | packet.data = decoder.decode(block); 72 | packet.typeName = decoder.getName(); 73 | packet.isDecoded = true; 74 | } catch(ParseException& ex) { 75 | packet.isDecoded = false; 76 | throw; 77 | } 78 | } 79 | 80 | return packet; 81 | } 82 | 83 | static PacketParser& getInstance() { 84 | static PacketParser instance; 85 | return instance; 86 | } 87 | }; 88 | } 89 | 90 | #endif /* defined(__libol__PacketParser__) */ 91 | -------------------------------------------------------------------------------- /src/libOL/ParseException.cpp: -------------------------------------------------------------------------------- 1 | #include "ParseException.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace libol { 7 | namespace ParseExceptionUtil { 8 | void require(bool result, const char *expr, const char *file, int line) { 9 | if(result) return; 10 | std::stringstream msg; 11 | msg << "Requirement not met: " << expr << " in " << file << ":" << line; 12 | throw ParseException(msg.str()); 13 | } 14 | 15 | void expect(bool result, const char *expr, const char *file, int line) { 16 | if(result) return; 17 | std::stringstream msg; 18 | msg << "Expectation not met: " << expr << " in " << file << ":" << line; 19 | std::cout << msg.str() << std::endl; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/libOL/ParseException.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__ParseException__ 5 | #define __libol__ParseException__ 6 | 7 | #include 8 | #include 9 | 10 | /* REQUIRE(expression) 11 | * Require an expression to be true to continue 12 | * - guarantees execution of the expression 13 | * - guaranteed to throw a ParseException if the expression is false 14 | */ 15 | #define REQUIRE(expr) ParseExceptionUtil::require(expr, #expr, __FILE__, __LINE__) 16 | 17 | /* EXPECT(expression) 18 | * Expect an expression to be true but execution can continue if not 19 | * - guarantees execution of the expression 20 | * - might throw a ParseException if the expression is false, depending on configuration 21 | */ 22 | #define EXPECT(expr) ParseExceptionUtil::expect(expr, #expr, __FILE__, __LINE__) 23 | 24 | namespace libol { 25 | class ParseException : public std::runtime_error { 26 | public: 27 | ParseException(std::string cause) : std::runtime_error(cause) {}; 28 | }; 29 | 30 | namespace ParseExceptionUtil { 31 | void require(bool result, const char* expr, const char* file, int line); 32 | void expect(bool result, const char* expr, const char* file, int line); 33 | } 34 | } 35 | 36 | #endif /* defined(__libol__ParseException__) */ 37 | -------------------------------------------------------------------------------- /src/libOL/PayloadHeader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #include "PayloadHeader.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "Blowfish/Blowfish.h" 12 | 13 | static inline std::vector b64Decode(std::string const& input) { 14 | if (input.length() == 0) { 15 | return std::vector(); 16 | } 17 | assert(input.length() % 4 == 0); 18 | 19 | size_t padding = 0; 20 | if (input[input.size() - 1] == '=') { 21 | padding++; 22 | } 23 | if (input[input.size() - 1] == '=') { 24 | padding++; 25 | } 26 | 27 | std::vector result; 28 | result.reserve(3 * (input.length() / 4) - padding); 29 | 30 | int decodedBytes = 0; 31 | uint32_t bytes = 0; 32 | for (auto cursor = input.begin(); cursor < input.end(); cursor++) { 33 | char character = *cursor; 34 | if (character >= 'A' && character <= 'Z') { 35 | bytes |= character - 'A'; 36 | } else if (character >= 'a' && character <= 'z') { 37 | bytes |= character - 'a' + 26; 38 | } else if (character >= '0' && character <= '9') { 39 | bytes |= character - '0' + 52; 40 | } else if (character == '+') { 41 | bytes |= 62; 42 | } else if (character == '/') { 43 | bytes |= 63; 44 | } else if (character == '=') { 45 | auto fromEnd = input.end() - cursor; 46 | if (fromEnd == 1) { 47 | result.push_back((bytes >> 16) & 0xff); 48 | result.push_back((bytes >> 8) & 0xff); 49 | return result; 50 | } else if (fromEnd == 2) { 51 | result.push_back((bytes >> 10) & 0xff); 52 | return result; 53 | } 54 | } 55 | 56 | if (decodedBytes == 3) { 57 | result.push_back((bytes >> 16) & 0xff); 58 | result.push_back((bytes >> 8) & 0xff); 59 | result.push_back((bytes) & 0xff); 60 | bytes = 0; 61 | } 62 | 63 | decodedBytes = (decodedBytes + 1) % 4; 64 | bytes <<= 6; 65 | } 66 | 67 | return result; 68 | } 69 | 70 | namespace libol { 71 | std::vector PayloadHeader::getDecodedEncryptionKey() { 72 | auto encryptedKeyBytes = b64Decode(this->encryptionKey); 73 | 74 | auto gameIdStr = std::to_string(gameId); 75 | auto gameIdVec = std::vector{gameIdStr.begin(), gameIdStr.end()}; 76 | 77 | std::vector out = Blowfish::decrypt(encryptedKeyBytes, gameIdVec); 78 | 79 | return out; 80 | } 81 | 82 | PayloadHeader PayloadHeader::decode(std::ifstream& ifs) { 83 | PayloadHeader payloadHeader; 84 | ifs.read(reinterpret_cast(&payloadHeader.gameId), sizeof(payloadHeader.gameId)); 85 | ifs.read(reinterpret_cast(&payloadHeader.gameLength), sizeof(payloadHeader.gameLength)); 86 | ifs.read(reinterpret_cast(&payloadHeader.keyframeCount), sizeof(payloadHeader.keyframeCount)); 87 | ifs.read(reinterpret_cast(&payloadHeader.chunkCount), sizeof(payloadHeader.chunkCount)); 88 | ifs.read(reinterpret_cast(&payloadHeader.endStartupChunkId), sizeof(payloadHeader.endStartupChunkId)); 89 | ifs.read(reinterpret_cast(&payloadHeader.startGameChunkId), sizeof(payloadHeader.startGameChunkId)); 90 | ifs.read(reinterpret_cast(&payloadHeader.keyframeInterval), sizeof(payloadHeader.keyframeInterval)); 91 | ifs.read(reinterpret_cast(&payloadHeader.encryptionKeyLength), sizeof(payloadHeader.encryptionKeyLength)); 92 | 93 | payloadHeader.encryptionKey.resize(payloadHeader.encryptionKeyLength); 94 | ifs.read(reinterpret_cast(&payloadHeader.encryptionKey[0]), payloadHeader.encryptionKeyLength); 95 | return payloadHeader; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/libOL/PayloadHeader.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__PayloadHeader__ 5 | #define __libol__PayloadHeader__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace libol { 12 | class PayloadHeader { 13 | public: 14 | uint64_t gameId; 15 | uint32_t gameLength; 16 | uint32_t keyframeCount; 17 | uint32_t chunkCount; 18 | uint32_t endStartupChunkId; 19 | uint32_t startGameChunkId; 20 | uint32_t keyframeInterval; 21 | uint16_t encryptionKeyLength; 22 | std::string encryptionKey; 23 | 24 | std::vector getDecodedEncryptionKey(); 25 | 26 | static PayloadHeader decode(std::ifstream& ifs); 27 | }; 28 | } 29 | 30 | #endif /* defined(__libol__PayloadHeader__) */ 31 | -------------------------------------------------------------------------------- /src/libOL/Rofl.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #include "Rofl.h" 5 | 6 | #include 7 | 8 | #include "Chunks.h" 9 | 10 | namespace libol { 11 | Rofl Rofl::decode(std::ifstream& ifs) { 12 | Rofl file; 13 | 14 | // Header 15 | file.header = Header::decode(ifs); 16 | 17 | // Metadata 18 | ifs.seekg(file.header.metadataOffset); 19 | file.metadata.resize(file.header.metadataLength); 20 | ifs.read(reinterpret_cast(&file.metadata[0]), file.header.metadataLength); 21 | 22 | // Payload Header 23 | ifs.seekg(file.header.payloadHeaderOffset); 24 | file.payloadHeader = PayloadHeader::decode(ifs); 25 | 26 | // Chunk and Keyframe headers 27 | ifs.seekg(file.header.payloadHeaderOffset + file.header.payloadHeaderLength); 28 | file.chunkHeaders = ChunkHeader::decodeMultiple(ifs, file.payloadHeader.chunkCount); 29 | file.keyframeHeaders = ChunkHeader::decodeMultiple(ifs, file.payloadHeader.keyframeCount); 30 | 31 | return file; 32 | } 33 | 34 | void Rofl::seekToChunk(std::ifstream& ifs, ChunkHeader chunkHeader) { 35 | ifs.seekg(header.payloadOffset + 36 | payloadHeader.chunkCount * ROFL_CHUNK_HEADER_LENGTH + 37 | payloadHeader.keyframeCount * ROFL_KEYFRAME_HEADER_LENGTH + 38 | chunkHeader.offset); 39 | } 40 | 41 | std::vector Rofl::getDecryptedChunk(std::ifstream& ifs, ChunkHeader chunkHeader) { 42 | std::vector chunk; 43 | chunk.resize(chunkHeader.chunkLength); 44 | 45 | seekToChunk(ifs, chunkHeader); 46 | ifs.read(reinterpret_cast(&chunk[0]), chunkHeader.chunkLength); 47 | 48 | auto decryptionKey = payloadHeader.getDecodedEncryptionKey(); 49 | auto decrypted = libol::Chunks::decryptAndDecompress(chunk, decryptionKey); 50 | return decrypted; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/libOL/Rofl.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__Rofl__ 5 | #define __libol__Rofl__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #define ROFL_CHUNK_HEADER_LENGTH 17 16 | #define ROFL_KEYFRAME_HEADER_LENGTH 17 17 | 18 | namespace libol { 19 | class Rofl { 20 | public: 21 | Header header; 22 | std::string metadata; 23 | PayloadHeader payloadHeader; 24 | std::vector chunkHeaders; 25 | std::vector keyframeHeaders; 26 | 27 | void seekToChunk(std::ifstream& ifs, ChunkHeader chunkHeader); 28 | std::vector getDecryptedChunk(std::ifstream& ifs, ChunkHeader chunkHeader); 29 | 30 | static Rofl decode(std::ifstream& ifs); 31 | }; 32 | } 33 | 34 | #endif /* defined(__libol__Rofl__) */ 35 | -------------------------------------------------------------------------------- /src/libOL/Value.cpp: -------------------------------------------------------------------------------- 1 | #include "Value.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace libol { 7 | Value Value::create(Object &val) { 8 | Value value; 9 | value.type = OBJECT; 10 | value.value = new Object(val); 11 | return value; 12 | } 13 | 14 | Value Value::create(Array &val) { 15 | Value value; 16 | value.type = ARRAY; 17 | value.value = new Array(val); 18 | return value; 19 | } 20 | 21 | Value Value::create(float &val) { 22 | Value value; 23 | value.type = FLOAT; 24 | value.value = new float(val); 25 | return value; 26 | } 27 | 28 | Value Value::create(bool &val) { 29 | Value value; 30 | value.type = BOOL; 31 | value.value = new bool(val); 32 | return value; 33 | } 34 | 35 | Value Value::create(uint64_t &val) { 36 | if(val & ((uint64_t) 1 << 61)) 37 | throw std::overflow_error("Value: val too big for LARGE_INTEGER"); 38 | return create((int64_t &) val); 39 | } 40 | 41 | Value Value::create(uint32_t &val) { 42 | if (val >= 2147483648) { 43 | int64_t large = (int64_t) val; 44 | return create(large); 45 | } 46 | return create((int32_t &) val); 47 | } 48 | 49 | Value Value::create(uint16_t &val) { 50 | int32_t intval = (int32_t) val; 51 | return create(intval); 52 | } 53 | 54 | Value Value::create(uint8_t &val) { 55 | int32_t intval = (int32_t) val; 56 | return create(intval); 57 | } 58 | 59 | Value Value::create(int64_t &val) { 60 | Value value; 61 | value.type = LARGE_INTEGER; 62 | value.value = new int64_t(val); 63 | return value; 64 | } 65 | 66 | Value Value::create(int32_t &val) { 67 | Value value; 68 | value.type = INTEGER; 69 | value.value = new int32_t(val); 70 | return value; 71 | } 72 | 73 | Value Value::create(int16_t &val) { 74 | int32_t intval = (int32_t) val; 75 | return create(intval); 76 | } 77 | 78 | Value Value::create(int8_t &val) { 79 | int32_t intval = (int32_t) val; 80 | return create(intval); 81 | } 82 | 83 | Value Value::create(std::string &val) { 84 | Value value; 85 | value.type = STRING; 86 | value.value = new std::string(val); 87 | return value; 88 | } 89 | 90 | Value Value::create(const char *&val) { 91 | std::string str = std::string(val); 92 | return create(str); 93 | } 94 | 95 | std::string Value::toString(size_t indent) { 96 | std::stringstream result, tabs; 97 | for (size_t i = 0; i < indent; i++) 98 | tabs << "\t"; 99 | std::string indentation = tabs.str(); 100 | 101 | switch (type) { 102 | case OBJECT: { 103 | Object &obj = this->as(); 104 | result << "{"; 105 | if(obj.size()) 106 | result << std::endl; 107 | for (auto it = obj.begin(); it != obj.end();) { 108 | result << indentation << "\t\"" << it->first << "\": "; 109 | result << it->second.toString(indent + 1); 110 | if (++it != obj.end()) 111 | result << ","; 112 | result << std::endl; 113 | } 114 | if (obj.size()) 115 | result << indentation; 116 | result << "}"; 117 | break; 118 | } 119 | case ARRAY: { 120 | Array &arr = this->as(); 121 | result << "["; 122 | for (size_t i = 0; i < arr.size(); i++) { 123 | result << arr.at(i).toString(indent); 124 | if (i + 1 < arr.size()) 125 | result << ","; 126 | } 127 | result << "]"; 128 | break; 129 | } 130 | case STRING: 131 | result << "\"" << this->as() << "\""; 132 | break; 133 | case INTEGER: 134 | result << this->as(); 135 | break; 136 | case LARGE_INTEGER: 137 | result << this->as(); 138 | break; 139 | case FLOAT: 140 | result << this->as(); 141 | break; 142 | case BOOL: 143 | result << (this->as() ? "true" : "false"); 144 | break; 145 | default: 146 | result << "undefined"; 147 | break; 148 | case UNDEFINED: 149 | break; 150 | } 151 | return result.str(); 152 | } 153 | 154 | void Value::destroy() { 155 | switch (type) { 156 | case OBJECT: { 157 | Object &obj = this->as(); 158 | for (auto it = obj.begin(); it != obj.end(); it++) { 159 | it->second.destroy(); 160 | } 161 | delete &obj; 162 | break; 163 | } 164 | case ARRAY: { 165 | Array &arr = this->as(); 166 | for (size_t i = 0; i < arr.size(); i++) { 167 | arr.at(i).destroy(); 168 | } 169 | delete &arr; 170 | break; 171 | } 172 | case STRING: 173 | delete &this->as(); 174 | break; 175 | case INTEGER: 176 | delete &this->as(); 177 | break; 178 | case LARGE_INTEGER: 179 | delete &this->as(); 180 | break; 181 | case FLOAT: 182 | delete &this->as(); 183 | break; 184 | case BOOL: 185 | delete &this->as(); 186 | break; 187 | case UNDEFINED: 188 | break; 189 | } 190 | type = UNDEFINED; 191 | value = nullptr; 192 | } 193 | 194 | void Object::set(std::string name, Value value) { 195 | map.insert(std::pair {name, value}); 196 | } 197 | 198 | Value Object::get(std::string name) { 199 | return map.at(name); 200 | } 201 | 202 | size_t Object::size() { 203 | return map.size(); 204 | } 205 | 206 | void Array::push(Value value) { 207 | vector.push_back(value); 208 | } 209 | 210 | size_t Array::size() { 211 | return vector.size(); 212 | } 213 | 214 | Value Array::at(size_t index) { 215 | return vector.at(index); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/libOL/Value.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #ifndef __libol__Value__ 5 | #define __libol__Value__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace libol { 13 | class Object; 14 | class Array; 15 | 16 | class Value { 17 | public: 18 | enum { 19 | UNDEFINED, 20 | OBJECT, // Object 21 | ARRAY, // Array 22 | STRING, // std::string 23 | INTEGER, // int32_t 24 | LARGE_INTEGER, // int64_t 25 | FLOAT, // float 26 | BOOL // bool 27 | } type; 28 | void *value; 29 | 30 | Value() : type(UNDEFINED), value(nullptr) { 31 | } 32 | 33 | void destroy(); 34 | 35 | template 36 | T &as() { 37 | return *reinterpret_cast(value); 38 | } 39 | 40 | std::string toString(size_t indent = 0); 41 | 42 | static Value create(Object &val); 43 | 44 | static Value create(Array &val); 45 | 46 | static Value create(std::string &val); 47 | 48 | static Value create(const char *&val); 49 | 50 | static Value create(uint64_t &val); 51 | 52 | static Value create(uint32_t &val); 53 | 54 | static Value create(uint16_t &val); 55 | 56 | static Value create(uint8_t &val); 57 | 58 | static Value create(int64_t &val); 59 | 60 | static Value create(int32_t &val); 61 | 62 | static Value create(int16_t &val); 63 | 64 | static Value create(int8_t &val); 65 | 66 | static Value create(float &val); 67 | 68 | static Value create(bool &val); 69 | }; 70 | 71 | class Object { 72 | std::map map; 73 | public: 74 | template 75 | void setv(std::string name, T value) { 76 | set(name, Value::create(value)); 77 | } 78 | 79 | void set(std::string name, Value value); 80 | 81 | Value get(std::string name); 82 | 83 | size_t size(); 84 | 85 | std::map::iterator begin() { 86 | return map.begin(); 87 | } 88 | 89 | std::map::iterator end() { 90 | return map.end(); 91 | } 92 | }; 93 | 94 | class Array { 95 | std::vector vector; 96 | public: 97 | template 98 | void pushv(T value) { 99 | push(Value::create(value)); 100 | } 101 | 102 | void push(Value value); 103 | 104 | size_t size(); 105 | 106 | Value at(size_t index); 107 | }; 108 | } 109 | 110 | #endif /* defined(__libol__Value__) */ 111 | -------------------------------------------------------------------------------- /src/libOL_test/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Andrew Toulouse. 2 | // Distributed under the MIT License. 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define MAX_ARGUMENT_LENGTH 600 17 | 18 | int test_blocks(std::vector arguments) 19 | { 20 | assert(arguments.size() == 1); 21 | 22 | std::ifstream ifs(arguments.at(0), std::ios::binary); 23 | if (!ifs) { 24 | std::cerr << "Failed to open " << arguments.at(0) << ": " << strerror(errno) << std::endl; 25 | return 2; 26 | } 27 | 28 | libol::BlockReader reader; 29 | auto blocks = reader.readBlocksFromStream(ifs); 30 | 31 | for(auto& block : blocks) { 32 | std::cout << std::hex << "[0x" << block.offset << "]\t"; 33 | std::cout << std::dec << "time: " << block.time << "s\t"; 34 | std::cout << std::hex << "type: 0x" << (unsigned) block.type << "\t"; 35 | std::cout << "param: 0x" << block.entityId << "\t"; 36 | std::cout << "size: 0x" << block.size << std::endl; 37 | } 38 | 39 | return 0; 40 | } 41 | 42 | int test_packets(std::vector arguments) 43 | { 44 | assert(arguments.size() == 1); 45 | 46 | std::ifstream ifs(arguments.at(0), std::ios::binary); 47 | if (!ifs) { 48 | std::cerr << "Failed to open " << arguments.at(0) << ": " << strerror(errno) << std::endl; 49 | return 2; 50 | } 51 | 52 | libol::BlockReader reader; 53 | auto blocks = reader.readBlocksFromStream(ifs); 54 | 55 | for(auto block : blocks) { 56 | libol::Packet pkt = libol::Packet::decode(block); 57 | if(pkt.isDecoded) { 58 | std::cout << pkt.typeName << ": " << pkt.data.toString() << std::endl; 59 | } 60 | } 61 | 62 | return 0; 63 | } 64 | 65 | 66 | int test_rofl(std::vector arguments) 67 | { 68 | assert(arguments.size() == 1); 69 | 70 | std::ifstream ifs(arguments.at(0), std::ios::binary); 71 | if (!ifs) { 72 | std::cerr << "Failed to open " << arguments.at(0) << ": " << strerror(errno) << std::endl; 73 | return 2; 74 | } 75 | 76 | libol::Rofl rofl = libol::Rofl::decode(ifs); 77 | 78 | auto header0 = rofl.chunkHeaders[0]; 79 | auto chunk = rofl.getDecryptedChunk(ifs, header0); 80 | 81 | return 0; 82 | } 83 | 84 | int usage(std::string prog_name) { 85 | std::cerr << prog_name << " [rofl|blocks|packets] " << std::endl; 86 | return 1; 87 | } 88 | 89 | int main(int argc, const char * argv[]) 90 | { 91 | std::string executable_name = std::string(argv[0], 0, MAX_ARGUMENT_LENGTH); 92 | if (argc < 3) { 93 | return usage(executable_name); 94 | } 95 | 96 | // build argument vector 97 | std::vector arguments; 98 | for (int i = 1; i < argc; ++i) { 99 | arguments.push_back(std::string(argv[i], 0, MAX_ARGUMENT_LENGTH)); 100 | } 101 | 102 | std::string command = arguments.at(0); 103 | arguments.erase(arguments.begin()); 104 | 105 | if (command == "rofl") { 106 | return test_rofl(arguments); 107 | } else if (command == "blocks") { 108 | return test_blocks(arguments); 109 | } else if (command == "packets") { 110 | return test_packets(arguments); 111 | } 112 | 113 | return usage(executable_name); 114 | } 115 | 116 | 117 | --------------------------------------------------------------------------------