GithubHelp home page GithubHelp logo

penguloader / penguloader Goto Github PK

View Code? Open in Web Editor NEW
343.0 8.0 55.0 1.92 MB

✨ Pengu Loader is a small JavaScript plug-in loader, unleashes the power of customization from your League Client.

Home Page: https://pengu.lol

License: Do What The F*ck You Want To Public License

C++ 52.63% C# 25.98% HTML 0.23% TypeScript 20.65% CSS 0.36% Batchfile 0.15%
league-client league-loader lcu lcu-api devtools javascript lol league-of-legends wtfpl es6 esm pengu-loader made-in-vietnam

penguloader's Introduction


Pengu Loader

A JavaScript plugin loader for the League of Legends Client


Table of Contents

About

Pengu Loader, previously known as League Loader, is a plugin loader specifically designed for the League of Legends Client.

Pengu Loader enables you to load JavaScript plugins into the Client as dependencies. This allows you to personalize the Client's appearance, load custom content, add new features, and enhance your overall experience. With Pengu Loader, you can build a smarter and more customized Client that suits your needs and preferences.

Features

  • Customize the League Client with plugins
  • Personalize and theme your Client
  • Support for modern JavaScript features
  • Built-in and remote DevTools support
  • Simplified LCU API usage
  • Deep support for RCP hooks

Getting Started

Please visit the homepage to begin:

Documentation

Contributing

To contribute to the project, follow these steps:

  1. Fork the repository (click here to fork now)
  2. Create your feature branch feat/<branch-name>
  3. Commit your changes
  4. Push to the branch
  5. Submit a new Pull Request

Ways you can contribute

  • Documentation and website: The documentation can always be improved. If you find something that is not documented or could be enhanced, create a PR for it. Check out the PenguLoader organization for more information.
  • Additional Base/Starter plugins: Share your plugins along with a detailed guide to help beginners get started easily.
  • Core features: Ensure you have extensive experience with CEF and low-level programming skills.
  • JavaScript features: Extensive web development knowledge is required.

Project structure

  • loader - The main loader menu UI, written in C# and WPF XAML.
  • core - The core module (DLL) that hooks into libCEF to enable the plugin's magic.
  • plugins: The built-in plugins/preload scripts that support loading user plugins and enable RCP hooking.

Build from source

Prerequisites

  • Visual Studio 2017+
    • Desktop development with C++
    • .NET desktop development
    • Windows 8.1 SDK
  • NodeJS 18+ and pnpm 8+

Initial Steps

  • Clone the repository:

    git clone https://github.com/PenguLoader/PenguLoader
    
  • Update submodules:

    cd PenguLoader
    git submodule update --init --recursive
    

Build Steps for preload scripts:

  • Make sure you have NodeJS and pnpm installed.

  • Navigate to the plugins directory:

    cd plugins
    
  • Install dependencies and build:

    pnpm install
    pnpm build
    

Build Steps for Pengu Loader

  • Open pengu-loader.sln
  • Right-click on the solution -> Restore Nuget Packages
  • Set the architecture to x64 and build mode to Release
  • Right-click on each project -> Build

For developing and debugging purpose, you should set build mode to Debug and build preload scripts with pnpm build-dev.

License

FOSSA Status

penguloader's People

Contributors

bakaft avatar deepsourcebot avatar fossabot avatar franndzs avatar nomi-san avatar user344 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

penguloader's Issues

Feature: RiotClient

Being able to navigate and investigate the RiotClient should be another option. There is a lot of assets and other stuff in this part of the client

image

Todo: Support acrylic/vibrancy effects

💣 the problem

In CEF, there is no regular way to make transparent a windowd rendering. Docs said:

  // Background color used for the browser before a document is loaded and when
  // no document color is specified. The alpha component must be either fully
  // opaque (0xFF) or fully transparent (0x00). If the alpha component is fully
  // opaque then the RGB components will be used as the background color. If the
  // alpha component is fully transparent for a windowed browser then the
  // default value of opaque white be used. If the alpha component is fully
  // transparent for a windowless (off-screen) browser then transparent painting
  // will be enabled.
  ///
  cef_color_t background_color;

And here is its implementation:

SkColor CefContext::GetBackgroundColor(
    const CefBrowserSettings* browser_settings,
    cef_state_t windowless_state) const {
  bool is_windowless = windowless_state == STATE_ENABLED
                           ? true
                           : (windowless_state == STATE_DISABLED
                                  ? false
                                  : !!settings_.windowless_rendering_enabled);

  // Default to opaque white if no acceptable color values are found.
  SkColor sk_color = SK_ColorWHITE;

I found a pull request that supports windowed mode transparency, but it's declined.
https://bitbucket.org/chromiumembedded/cef/pull-requests/136

In Electron, they patches the Chromium source code to ensure transparency.

if (background_color_ == SK_ColorTRANSPARENT)
  EnsureTransparentBackground(...);

Then create a BrowserWindow with transparent background and ensure flags --enable-transparent-visuals is already enabled. To apply native Windows effects like mica or acrylic/blurbehind, just use:

  • SetWindowCompositionAttribute
  • DwmSetWindowAttribute

There is two solutions to resolve the problem above:'

  • Use off-screen rendering
  • Patch CEF code in runtime

Hot Reload

Hey! Just started developing some plugins with this tool, so firstly I wanna say what an awesome tool it is, thank you @nomi-san for the awesome work, but there's definitely something I feel is missing, some kind of hot reload for CSS and/or JS.

See, for CSS I have found the following code reloads it:

function reloadCSS() {
  const links = document.getElementsByTagName('link');

  Array.from(links)
    .filter(link => link.rel.toLowerCase() === 'stylesheet' && link.href)
    .forEach(link => {
      const url = new URL(link.href, location.href);
      url.searchParams.set('forceReload', Date.now());
      link.href = url.href;
    });
}

So maybe it's possible to do the same thing with JS plugins? For all I know you inject a require call in the CEF renderer, to require the plugins, maybe you could require a JS file to inject tags that executes the plugins, then use the same CSS reload approach but calling a plugin with

<script src="https://plugins/plugin.js?forceReload=123820149801" />

Bootstrapper failed with error 0x5

This error is caused by creating LeagueClientUx process in the bootstrapper
and CreateProcess failed with last error 0x5 (ERROR_ACCESS_DENIED - Access is denied).

image

You may have come across these reasons:

  1. LeagueClientUx.exe has a higher file permission than your current user.
  2. UAC is prompted when launching LeagueClientUx.exe but no permissions are granted.
  3. Both RiotClient and LeagueClient are running with admin privileges, while rundll32.exe is not (system or AV denied).

Symlink mode (introduced in v1.1.0) should solve this problem without touching the system.

  • Allow enable of symlink mode in the config file
UseSymlink=true

NtCreateUserProcess returns XXXXXXXX

Commonly 0xC0000039 (STATUS_OBJECT_PATH_INVALID), this error status usually occurs on Windows 7/8.
NtCreateUserProcess is an undocumented function, so there is no good info about usage.

image

I just found the best way to detach the debugger when create process with flag DEBUG_ONLY_THIS_PROCESS to avoid IFEO recursion.

CreateProcessW(NULL, commandLine, NULL, NULL, FALSE,
  CREATE_SUSPENDED | DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi);

HANDLE hDebug;
if (0 <= NtQueryInformationProcess(pi.hProcess, ProcessDebugObjectHandle, &hDebug, sizeof(HANDLE), 0))
{
  NtRemoveProcessDebug(pi.hProcess, hDebug);
  NtClose(hDebug);
}

Feature: a fallback injection method using symlink

We got so many reports that user can not write IFEO registry keys due to an Unauthorized Exception. e.g: #74 #63 #64. This may be caused by many reasons(Anti-virus, no admin privilege...etc), so we need to find a fallback method just in case of it.

Luckily we still have the Dll Proxing way working(by creating a symlink named d3d9.dll/dinput.dll/dwrite.dll pointing to core.dll besides LeagueClient.exe . But the problem is we need to make a GUI to do this thing to make it user-friendly.

  • Implement GUI for symlink (windows only now)
  • Mutliple LeagueClient support

Grouping plugins by author

The idea

is to let devs create a folder that represents their username or similar unique ID. Inside that folder they can put their plugins.

Current project structure:

plugins/
  best-plugin-ever.js
  plugin-name-1.js
  plugin-name-2/
    index.js
config
core.dll
datastore
"Pengu Loader.exe"

I think there're some devs who'd like to have the following structure

plugins/
  best-plugin-ever.js
  some-author/ <- Group by author
    plugin-name-1.js
    plugin-name-2/
      index.js
config
core.dll
datastore
"Pengu Loader.exe"

Extra folder inside plugins allows to group plugins from the same author.

Feature: PluginFS API

Community devs often need to access the file system to complete certain operations, such as opening a folder for users to add or delete files, outputting logs to a file, and so on.

So a PluginFS API is needed, and it should be plugin scoped meaning that a plugin only have permission on it's own folder.
In this case top-level js will not have any permission because it don't have its own folder.

Actually I've done some job on that using std::filesystem from C++17, so I think if is ok we can move to C++17 to gain more convenience and improved cross-platform compatibility.

  • PluginFS.read
  • PluginFS.write
  • PluginFS.mkdir
  • PluginFS.stat
  • PluginFS.ls
  • PluginFS.rm
  • Scoped filesystem

Bug: Application Error on v1.0.6-dev branch

This problem only occurs when closing the Client with "Optimize Client" option is disabled.

Faulting application name: LeagueClientUx.exe, version: 13.14.520.6878, time stamp: 0x64b0ba1c
Faulting module name: libcef.dll, version: 91.1.23.0, time stamp: 0x60ef17ce
Exception code: 0x80000003
Fault offset: 0x0000000002a397d4
Faulting process id: 0x43ac
Faulting application start time: 0x01d9b9de1eff1c2a

Stream Bugged

with pengu loader when streaming a game in discord with doesn't switch to the game process it stays in the client process and you have to reload the stream constantly.

rcp.whenReady should support for late init

That was lacking in v1.1.0.

export function init(ctx) {
  setTimeout(async () => {
    const api = await ctx.rcp.whenReady('rcp-some-lib')
    // fix me (currently never fires)
  }, 10000)
}

SSL_ERROR_SYSCALL bug

This bug will crash your Client when plugins start loading.
There is no way to fix this bug, but we can avoid it by following these cases:

  1. With top-level import from CDN, use https:// instead of //.
-import axios from '//esm.run/axios'
+import axios from 'https://esm.run/axios'
  1. Always use relative path for plugin import.
-import utils from '//plugins/your-plugin/utils'
+import utils from './utils'
  1. [update later]

Experiment: TypeScript and JSX Support 🗿

There are two transpilers could be used to transpile TS code within the browser.

Sucrase is blazing fast and commonly is used to replace ts-node for nodejs runtime, it just transpiles your code into JS and ignores all types (alse does type checks). Sucrase is built without nodejs deps, that means it should work fine in browser env.

plugins/
  |__plugin-1/
    |__index.ts  👈 entry
    |__.cache     ? cache transpiled if needed

How about import and ESM?

We already have CSS modules, so TS module is not impossible. That's actually what Vite did in dev mode. But there will have some troubles with named exports.

graph LR
A[index.ts] --> B["assets handler"] --> C["module [JS code]"]

How about JSX?

If you take Deno long enough, you will see that the code below is pretty familiar.

import { h, render } from 'https://esm.sh/[email protected]'

export function load() {
  const root = /*...*/
  render(root, () => <div>Some text</div>)
}

Failed to perform activation

При установке/распаковке программы появляется одна и та же ошибка, не даёт активировать программу и собственно воспользоваться ей. прошу пояснить как её исправить, защитник виндовс отключен, антивирус тем более, удалял и переустанавливал программу на несколько разных дисков, в названии папки только латиница , вот.
Screenshot_4
Устанавливал на отдельный жесткий диск, и на жеёсткий диск где находится лига.

run error


Pengu Loader

启动失败。
请捕获错误消息,然后点击“是”进行报告。

ERR: 尝试执行未经授权的操作。
在 Microsoft.Win32.RegistryKey.Win32Error(Int32 errorCode, String str)

在 Microsoft.Win32.RegistryKey.SetValue(String name, Object value, RegistryValueKind valueKind)

在 PenguLoader.Main.IFEO.SetDebugger(String target, String debugger)

在 PenguLoader.Views.MainPage.set_IsActivated(Boolean value)


是(Y) 否(N)

Plugin Browser

I dont know if this is a smart idea to implement due to the projects size so just putting this out here due to how easy it would be to implement

Folder Support within Plugins

Hi, to make things easier for the user when using other peoples plugins, could you add support for loading JS within a folder within plugins.

For example, I want to put all my JS files in a folder named 909 within plugins (//plugins/909/example.js).

This way its easier to install and use multiple different plugins without them all being cluttered.

End goal is for me to keep all assets and JS, CSS files, etc in the 909 plugin so its just drag and drop single folder to install

Todo: Plugins packager

End-user now can use single packaged DLL which contains pre-built plugins and pre-set settings.

  • Packager
  • ESM supports
  • Load from GitHub via RawGitHack CDN

Chinese Language Add Request

zh:{welcome:"\u55e8\uff01 %name%\uff0c\u6b64\u5f39\u7a97\u662f\u8ba9\u4e86\u8ba9\u60a8\u77e5\u9053\u63d2\u4ef6\u5df2\u7ecf\u6210\u529f\u88ab\u8f7d\u5165\u3002",checkout_links:"\u52a0\u5165\u6211\u4eec\u7684 %discord% \u6765\u83b7\u5f97\u66f4\u591a\u6709\u8da3\u7684\u63d2\u4ef6\u548c\u4e3b\u9898\u3002\u8bbf\u95ee %github% \u4ed3\u5e93\u4ee5\u4e86\u89e3\u6709\u5173\u521b\u5efa\u63d2\u4ef6\u7684\u66f4\u591a\u4fe1\u606f🤘",devtools_tip:"\u5c1d\u8bd5\u6309\u4e0b %key% \u952e\u6765\u542f\u52a8\u5f00\u53d1\u8005\u5de5\u5177 😉",donotshowagain:"\u4e0d\u518d\u663e\u793a",has_new_ver:"\u68c0\u6d4b\u5230\u65b0\u7248\u672c\u53d1\u5e03",update_tip:"\u60a8\u5e94\u8be5\u5173\u95ed\u82f1\u96c4\u8054\u76df\u5ba2\u6237\u7aef\u5e76\u8fd0\u884c Pengu Loader \u6765\u83b7\u53d6\u5b83\u3002"}

image

Is it possible to access the iFrames ?

Hey,

I'm trying to make a theme for the client but I'm facing a difficulty trying to modify the TFT page. The page is in an iframe and its src is https://prod.embed.rgpub.io/wwpub-lunar-gala-2023/en_US/ , I found out it's not possible to modify the elements' style that are inside an Iframe if its src is different that the current origin.

It returns : VM9420:1 Uncaught DOMException: Blocked a frame with origin "https://127.0.0.1:28773" from accessing a cross-origin frame.
at :1:73

I modified the config.cfg file in the league-loader folder to :

[Main]
LeaguePath=C:\Riot Games\League of Legends
DisableWebSecurity=1
IgnoreCertificateErrors=1

But same error. If anyone knows a work-around that would be helpful ! There are a lot of iframes used in the client

Can you make it so we have the Riot client API port/auth-token in global variables or similar ?

Hello again !

In ranked champ select the player names are hidden. I want to make a plug-in that unhides them. I found that it's possible to retrieve the real usernames by using the Riot client API, which is different to the LCU, with different endpoints. (The interesting one being /chat/v5/participants/champ-select)

It uses a different port, and use an auth-token, which changes everytime the client starts up.

On Window you can retrieve the port by using in a terminal the command : wmic PROCESS WHERE name='LeagueClientUx.exe' GET commandline

In Mac OS you can do : ps -A | grep LeagueClientUx

I searched a little bit, and apparently it's not possible to do these commands in javascript without nodejs.
So I was wondering, I'm not sure exactly how league-loader works, but I very much suppose there's some sort of "pre-load" that uses C/C++ or another low-level language, which could be used to retrieve the Riot client API/auth-token, and made them available in global variables in the DevTools

I put a red arrow on the two informations here :
62M6f2S

ES modules & async plugins

After //plugins/ domain is supported, all entry plugins will be loaded asynchronously using native import.
But that doesn't mean require will be removed, because too many plugins are now using it.

When loading a plugin with require, the root plugin will push to the path stack its absolute path. Then sub modules will know their path even they are required by relative path. In current implementation, only a static path stack is used, so there is a bug that occurs when loading modules asynchronously.

https://github.com/nomi-san/league-loader/blob/39fb814ca2b86c3b435c294c5dcc59f001d2b958/d3d9/src/renderer/ext_code.h#L103-L107

To keep legacy require and import working together, the require must be known its path. In ES module, import.meta is all we need to get the path, but we cannot use it inside require due to syntax error. Here is a possible way that patches plugin code before it loads.

-const mod = require('./my-mod');
+const mod = require('./my-mod', import.meta);

Finally, web browser runtime doesn't allow to import module without file extension .js. In VSCode, the autocompletion always uses Node import type, now that is solved by 39fb814.

import './a'   // => ./a.js
import './a/'  // => ./a/index.js

Simple Optimization

Can't edit heroes in advance
Need a config file to edit advance hero replacement
It is not convenient to use the mouse wheel to select heroes to find them

Todo: Super Low Spec Mode

Enable this option to disable all transition and animation effects in League Client.

  • Super Low Spec Mode option
  • Beat terrible #shadow-root 😢
* {
  -webkit-transition: none !important;
  -webkit-animation: none !important;
  transition: none !important;
  animation: none !important;
}

Can you help me detecting when connecting to lobby ?

Hey,

First of all thank you for this tool it's very useful.

I want to detect when I'm connecting to the chat in champ-select. I don't have much knowledge, but I found a few things on the internet and they work for everything but what I want.

I am trying to use MutationObserver API to detect when an element of class chat-message is created, so upon arriving in the champ-select I run this in the console :

function listenForChatMessages() {
  var targetNode = document.getElementById('chat-messages-frame').contentDocument.body;
  var config = { attributes: true, childList: true, subtree: true };
  var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        console.log(mutation)
      if (mutation.type === 'childList') {
        var newNodes = mutation.addedNodes;
        for (var i = 0; i < newNodes.length; i++) {
          var node = newNodes[i];
          if (node.classList && node.classList.contains('chat-message')) {
            //Do something here
            console.log("New chat message element detected!");
          }
        }
      }
    }
  };
  var observer = new MutationObserver(callback);
  observer.observe(targetNode, config);
}

And then I send a message to see if it caught it, and it didn't.
I tried using the MutationObserver in league-loader for capturing other things and it did work, here specifically it won't.

I hoped you could help me, not necessarily using MutationObserver, but anything that can detect when I'm connected to the chat in champ-select will do.

The code example is just one of about ten other ways I tried to do it but that didn't work, I'm running out of ideas. I even tried using ChatGPT to generate code, and it did, and again it worked for everything but for the chat messages :(

Thank you !

Press F to say goodbye CommonJS

We have decided to remove the require() function and support import instead. This change will allow plugins to load faster asynchronously.

While we are able to support both CommonJS and ESM by patching require() in CommonJS scripts, but we have encountered an issue with scripts that use module.exports to export their data. When converting to export in ESM, it will cause a syntax error if module.exports is not used at the top level.

Previously, the import statement only supported loading JavaScript modules, but now we have extended this feature to support loading JSON and raw data as well as require(). This means that you can now use import to load JSON files or other non-JavaScript data formats, and our backend will automatically transform the response into the appropriate format.

import sub from './sub';
import config from './config.json';

await todo();  // top-level await is fine

Todo: Allow using local assets

Hosting assets such as CSSs, fonts, images and media on server takes a lot of time.
Now you can import them on local filesystem without any cost.

  • HTTPS based scheme: //assets/ https://assets/
  • Allow query params
<link rel="stylesheet/css" href="//assets/theme.css" />
<img src="//assets/banner.png" />
<video src="//assets/animated.webm" autoplay></video>
assets/
  |__theme.css
  |__banner.png
  |__animated.webm
plugins/
  |...

To prevent caching, just add trailing query params:

const url = `//assets/some-resource?v=${Date.now()}`;

Todo: RiotClient APIs request

As same as local resources request, but no credentials needed.

//riotclient/<riot-client-api>

For example:

let participants = await fetch('//riotclient/chat/v5/participants/champ-select');

Implementation notes:

graph TD
A["//riotclient/:api"] --> B["https://riot@$rc_pass:127.0.0.1:$rc_port/:api"] --> C["access-control-allow-origin: *"]
  • rc_port: Riot Client port
  • rc_pass: Riot Client auth token

Ref: #13 @teisseire117

Shared TS types & enhancement

Idea

I need types for life cycle hooks (entry points) like init or load. As far as I know there're two ways to get typesafety with typescript:

  • explicit by importing them directly from 'pengu-loader'. In this case the package should be published on npmjs or
  • implicit like sveltekit does.

Explicit

Exported interfaces from 'pengu-loader'

export interface InitFn {
  (context: Context): void;
}

Interfaces that can be exported from 'pengu-loader', too

export interface Context {
  socket: Socket;
  rcp: Rcp;
}

export interface Socket {
  observe<Data = any>(api: string, listener: Listener<Data>): DisconnectFn;
  disconnect: DisconnectFn;
}

export interface Listener<Data = any> {
  (event: Event<Data>): void;
}

export interface Event<Data = any> {
  data: Data; // or `data?: Data`
  uri: string;
  eventType: EventType;
}

export interface DisconnectFn {
  (): void;
}

export type EventType = 'Create' | 'Update' | 'Delete';

Usage

import { InitFn } from 'pengu-loader';

interface Data { id: string; gamerName: string }

export const init: InitFn = ctx => {
  ctx.socket.observe<Data>('some/api', event => {
    console.log(event.data.id, event.data.gamerName);
  });
};

Implicit

At this point we need some dev server running in the background that generates interfaces silently. I think about vite.

Usage

interface Data { id: string; gamerName: string }

export const init = ctx => {
  ctx.socket.observe<Data>('some/api', event => {
    console.log(event.data.id, event.data.gamerName);
  });
};

Obviously the first option is way simpler than the second one.

It'd be great to get some feedback, whether that fits the philosophy etc. or not.

Is it possible to prevent the Client from automatically bringing the application in foreground ?

I've tried looking over the .js code to see if something was getting my attention but there's a lot more code than I expected and I can't really find what I'm looking for.

I'm looking for the code that's responsible for bring the application in the foreground when we have to accept queue. Because I made a plug-in to automatically accept it (I posted it here #4 (comment)) but if the applications pops up then it makes it not really useful.

If anyone has ideas

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.