text:
// cc -o vmacho vmacho.c -Wall -O3
// cl vmacho.c /O2 /W3
#define _CRT_SECURE_NO_WARNINGS
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>              // fopen, fclose, ftell, fseek, fflush, fprintf, stdin, stdout, stderr
#include <stdlib.h>             // malloc, free
#include <string.h>             // memset, strcmp, strerror

#define LOG(str, ...) do { fprintf(stderr, str "\n", ##__VA_ARGS__); } while(0)

#define MH_MAGIC            0xfeedface
#define MH_MAGIC_64         0xfeedfacf
#define LC_SEGMENT          0x1
#define LC_SEGMENT_64       0x19
#define SEC_TYPE_MASK       0x000000ff
#define SEC_TYPE_ZEROFILL   0x1

typedef uint32_t vm_prot_t;

typedef struct
{
    uint32_t magic;
    uint32_t cputype;
    uint32_t cpusubtype;
    uint32_t filetype;
    uint32_t ncmds;
    uint32_t sizeofcmds;
    uint32_t flags;
} mach_hdr32_t;

typedef struct
{
    uint32_t magic;
    uint32_t cputype;
    uint32_t cpusubtype;
    uint32_t filetype;
    uint32_t ncmds;
    uint32_t sizeofcmds;
    uint32_t flags;
    uint32_t reserved;
} mach_hdr64_t;

typedef struct
{
    uint32_t cmd;
    uint32_t cmdsize;
} mach_lc_t;

typedef struct
{
    uint32_t  cmd;
    uint32_t  cmdsize;
    char      segname[16];
    uint32_t  vmaddr;
    uint32_t  vmsize;
    uint32_t  fileoff;
    uint32_t  filesize;
    vm_prot_t maxprot;
    vm_prot_t initprot;
    uint32_t  nsects;
    uint32_t  flags;
} mach_seg32_t;

typedef struct
{
    uint32_t  cmd;
    uint32_t  cmdsize;
    char      segname[16];
    uint64_t  vmaddr;
    uint64_t  vmsize;
    uint64_t  fileoff;
    uint64_t  filesize;
    vm_prot_t maxprot;
    vm_prot_t initprot;
    uint32_t  nsects;
    uint32_t  flags;
} mach_seg64_t;

typedef struct
{
    char sectname[16];
    char segname[16];
    uint32_t addr;
    uint32_t size;
    uint32_t offset;
    uint32_t align;
    uint32_t reloff;
    uint32_t nreloc;
    uint32_t flags;
    uint32_t reserved[2];
} mach_sect32_t;

typedef struct
{
    char sectname[16];
    char segname[16];
    uint64_t addr;
    uint64_t size;
    uint32_t offset;
    uint32_t align;
    uint32_t reloff;
    uint32_t nreloc;
    uint32_t flags;
    uint32_t reserved[3];
} mach_sect64_t;

typedef enum
{
    Mode_Binary,
    Mode_HeadlessArray,
    Mode_NamedArray,
} vmacho_mode_t;

// 0 = success
// 1 = not a segment
// N = fatal error
static int get_mapped_segment_range(mach_lc_t *cmd, bool use_sections, uint64_t *mapbase, uint64_t *mapoff, uint64_t *mapsize, uint64_t *vmbase, uint64_t *vmsize)
{
    uint64_t lowest    = ~0,
             highest   =  0,
             offset    =  0,
             vmlowest  = ~0,
             vmhighest =  0;
    if(cmd->cmd == LC_SEGMENT)
    {
        mach_seg32_t *seg = (mach_seg32_t*)cmd;
        uint32_t vmaddr   = seg->vmaddr,
                 vmsize   = seg->vmsize,
                 fileoff  = seg->fileoff,
                 filesize = seg->filesize;
        if(!use_sections)
        {
            lowest    = vmaddr;
            highest   = vmaddr + filesize;
            offset    = fileoff;
            vmlowest  = vmaddr;
            vmhighest = vmaddr + vmsize;
        }
        else
        {
            for(uint32_t i = 0, max = seg->nsects; i < max; ++i)
            {
                mach_sect32_t *sec = (mach_sect32_t*)(seg + 1) + i;
                uint32_t lo = sec->addr,
                         hi = lo + sec->size;
                if(hi < lo || lo < vmaddr || hi > vmaddr + vmsize)
                {
                    return 2;
                }
                if((sec->flags & SEC_TYPE_MASK) != SEC_TYPE_ZEROFILL)
                {
                    if(hi > vmaddr + filesize)
                    {
                        return 2;
                    }
                    if(lo < lowest)
                    {
                        lowest = lo;
                    }
                    if(hi > highest)
                    {
                        highest = hi;
                    }
                }
                if(lo < vmlowest)
                {
                    vmlowest = lo;
                }
                if(hi > vmhighest)
                {
                    vmhighest = hi;
                }
            }
            // Zero mapped sections
            if(lowest > highest)
            {
                lowest  = vmaddr;
                highest = vmaddr;
                offset  = 0;
            }
            else
            {
                offset = fileoff + (lowest - vmaddr);
            }
            if(vmlowest > vmhighest)
            {
                vmlowest  = vmaddr;
                vmhighest = vmaddr;
            }
        }
    }
    else if(cmd->cmd == LC_SEGMENT_64)
    {
        mach_seg64_t *seg = (mach_seg64_t*)cmd;
        uint64_t vmaddr   = seg->vmaddr,
                 vmsize   = seg->vmsize,
                 fileoff  = seg->fileoff,
                 filesize = seg->filesize;
        if(!use_sections)
        {
            lowest    = vmaddr;
            highest   = vmaddr + filesize;
            offset    = fileoff;
            vmlowest  = vmaddr;
            vmhighest = vmaddr + vmsize;
        }
        else
        {
            for(uint32_t i = 0, max = seg->nsects; i < max; ++i)
            {
                mach_sect64_t *sec = (mach_sect64_t*)(seg + 1) + i;
                uint64_t lo = sec->addr,
                         hi = lo + sec->size;
                if(hi < lo || lo < vmaddr || hi > vmaddr + vmsize)
                {
                    return 2;
                }
                if((sec->flags & SEC_TYPE_MASK) != SEC_TYPE_ZEROFILL)
                {
                    if(hi > vmaddr + filesize)
                    {
                        return 2;
                    }
                    if(lo < lowest)
                    {
                        lowest = lo;
                    }
                    if(hi > highest)
                    {
                        highest = hi;
                    }
                }
                if(lo < vmlowest)
                {
                    vmlowest = lo;
                }
                if(hi > vmhighest)
                {
                    vmhighest = hi;
                }
            }
            // Zero mapped sections
            if(lowest > highest)
            {
                lowest  = vmaddr;
                highest = vmaddr;
                offset  = 0;
            }
            else
            {
                offset = fileoff + (lowest - vmaddr);
            }
            if(vmlowest > vmhighest)
            {
                vmlowest  = vmaddr;
                vmhighest = vmaddr;
            }
        }
    }
    else
    {
        return 1;
    }
    *mapbase = lowest;
    *mapoff  = offset;
    *mapsize = highest - lowest;
    if(vmbase) *vmbase = vmlowest;
    if(vmsize) *vmsize = vmhighest - vmlowest;
    return 0;
}

int main(int argc, const char **argv)
{
    int retval = -1;
    void *file = NULL,
         *mem  = NULL;
    size_t flen = 0,
           mlen = 0,
           fmax = 0,
           smax = 0;
    FILE *infile  = NULL,
         *outfile = NULL;
    vmacho_mode_t mode = Mode_Binary;
    const char *oflags = "wbx",
               *aname  = NULL;
    bool use_sections  = true;
    int r;

    int aoff = 1;
    for(; aoff < argc; ++aoff)
    {
        if(argv[aoff][0] != '-' || argv[aoff][1] == '\0')
        {
            break;
        }
        int curoff = aoff;
        for(size_t i = 1; argv[curoff][i] != '\0'; ++i)
        {
            char c = argv[curoff][i];
            switch(c)
            {
                case 'c':
                    mode = Mode_HeadlessArray;
                    break;
                case 'C':
                    if(argc - aoff < 4) // Don't want curoff here
                    {
                        LOG("-%c requires an argument", c);
                        goto out;
                    }
                    mode = Mode_NamedArray;
                    aname = argv[++aoff];
                    break;
                case 'f':
                    oflags = "wb";
                    break;
                case 'm':
                case 'M':
                    if(argc - aoff < 4) // Don't want curoff here
                    {
                        LOG("-%c requires an argument", c);
                        goto out;
                    }
                    const char *num = argv[++aoff];
                    char *end = NULL;
                    unsigned long long l = strtoull(num, &end, 0);
                    if(*num == '\0' || *end != '\0')
                    {
                        LOG("Invalid argument to -%c: %s", c, num);
                        goto out;
                    }
                    if(c == 'm')
                        fmax = (size_t)l;
                    else
                        smax = (size_t)l;
                    break;
                case 's':
                    use_sections = false;
                    break;
                default:
                    LOG("Bad option: -%c", c);
                    goto out;
            }
        }
    }
    if(argc - aoff != 2)
    {
        fprintf(stderr, "Usage: %s [-cf] [-C name] [-m max] [-M max] in out\n"
                        "    -c      Output as headless C array\n"
                        "    -C name Output as named C array\n"
                        "    -f      Force (overwrite existing files)\n"
                        "    -m max  Enforce max size of bytes for total file mapping\n"
                        "    -M max  Enforce max size of bytes for total runtime size\n"
                        "    -s      Use only segments for mapping, ignore sections\n"
                        , argv[0]);
        goto out;
    }

    infile = strcmp(argv[aoff], "-") == 0 ? stdin : fopen(argv[aoff], "rb");
    if(!infile)
    {
        LOG("fopen(%s): %s", argv[aoff], strerror(errno));
        goto out;
    }
    long cur = ftell(infile);
    if(cur < 0)
    {
        LOG("ftell(cur): %s", strerror(errno));
        goto out;
    }
    r = fseek(infile, 0, SEEK_END);
    if(r != 0)
    {
        LOG("fseek(end): %s", strerror(errno));
        goto out;
    }
    long end = ftell(infile);
    if(end < 0)
    {
        LOG("ftell(end): %s", strerror(errno));
        goto out;
    }
    flen = (size_t)(end - cur);
    r = fseek(infile, cur, SEEK_SET);
    if(r != 0)
    {
        LOG("fseek(cur): %s", strerror(errno));
        goto out;
    }

    if(flen < sizeof(uint32_t))
    {
        LOG("File too short for magic.");
        goto out;
    }
    file = malloc(flen);
    if(!file)
    {
        LOG("malloc(file): %s", strerror(errno));
        goto out;
    }
    if(fread(file, 1, flen, infile) != flen)
    {
        LOG("fread: %s", strerror(errno));
        goto out;
    }

    uintptr_t ufile = (uintptr_t)file;
    uint32_t magic = *(uint32_t*)file;
    mach_lc_t *lcs = NULL;
    uint32_t sizeofcmds = 0;
    if(magic == MH_MAGIC)
    {
        mach_hdr32_t *hdr = file;
        if(flen < sizeof(*hdr) + hdr->sizeofcmds)
        {
            LOG("File too short for load commands.");
            goto out;
        }
        lcs = (mach_lc_t*)(hdr + 1);
        sizeofcmds = hdr->sizeofcmds;
    }
    else if(magic == MH_MAGIC_64)
    {
        mach_hdr64_t *hdr = file;
        if(flen < sizeof(*hdr) + hdr->sizeofcmds)
        {
            LOG("File too short for load commands.");
            goto out;
        }
        lcs = (mach_lc_t*)(hdr + 1);
        sizeofcmds = hdr->sizeofcmds;
    }
    else
    {
        LOG("Bad magic: %08llx", (unsigned long long)magic);
        goto out;
    }

    uint64_t lowest    = ~0,
             highest   =  0,
             vmlowest  = ~0,
             vmhighest =  0;
    for(mach_lc_t *cmd = lcs, *end = (mach_lc_t*)((uintptr_t)cmd + sizeofcmds); cmd < end; cmd = (mach_lc_t*)((uintptr_t)cmd + cmd->cmdsize))
    {
        if((uintptr_t)cmd + sizeof(*cmd) > (uintptr_t)end || (uintptr_t)cmd + cmd->cmdsize > (uintptr_t)end || (uintptr_t)cmd + cmd->cmdsize < (uintptr_t)cmd)
        {
            LOG("Bad LC: 0x%llx", (unsigned long long)((uintptr_t)cmd - ufile));
            goto out;
        }

        uint64_t vmaddr  = 0,
                 fileoff = 0,
                 size    = 0,
                 vmbase  = 0,
                 vmsize  = 0;
        r = get_mapped_segment_range(cmd, use_sections, &vmaddr, &fileoff, &size, &vmbase, &vmsize);
        switch(r)
        {
            case 0:
                break;
            case 1:
                continue;
            default:
                LOG("get_mapped_segment_range returned error: %d", r);
                goto out;
        }
        if(vmsize)
        {
            if(vmbase < vmlowest)
                vmlowest = vmbase;
            if(vmbase + vmsize > vmhighest)
                vmhighest = vmbase + vmsize;
        }
        if(!size)
        {
            continue;
        }
        uint64_t off = fileoff + size;
        if(off > flen || off < fileoff)
        {
            LOG("Bad segment: 0x%llx", (unsigned long long)((uintptr_t)cmd - ufile));
            goto out;
        }

        uint64_t start = vmaddr;
        if(start < lowest)
        {
            lowest = start;
        }
        uint64_t end = start + size;
        if(end > highest)
        {
            highest = end;
        }
    }
    if(highest < lowest)
    {
        LOG("Bad memory layout, lowest: 0x%llx, highest: 0x%llx", (unsigned long long)lowest, (unsigned long long)highest);
        goto out;
    }
    mlen = (size_t)(highest - lowest);
    if(fmax > 0 && mlen > fmax)
    {
        LOG("Filemap size is too large: max 0x%zx, have 0x%zx", fmax, mlen);
        goto out;
    }
    if(smax > 0 && (vmhighest - vmlowest) > smax)
    {
        LOG("Runtime size is too large: max 0x%zx, have 0x%zx", smax, (size_t)(vmhighest - vmlowest));
        goto out;
    }
    mem = malloc(mlen);
    if(!mem)
    {
        LOG("malloc: %s", strerror(errno));
        goto out;
    }
    memset(mem, 0, mlen);
    for(mach_lc_t *cmd = lcs, *end = (mach_lc_t*)((uintptr_t)cmd + sizeofcmds); cmd < end; cmd = (mach_lc_t*)((uintptr_t)cmd + cmd->cmdsize))
    {
        uint64_t vmaddr  = 0,
                 fileoff = 0,
                 size    = 0;
        r = get_mapped_segment_range(cmd, use_sections, &vmaddr, &fileoff, &size, NULL, NULL);
        switch(r)
        {
            case 0:
                break;
            case 1:
                continue;
            default:
                LOG("get_mapped_segment_range returned error: %d", r);
                goto out;
        }
        if(!size)
        {
            continue;
        }
        memcpy((void*)((uintptr_t)mem + (vmaddr - lowest)), (void*)(ufile + fileoff), size);
    }

    outfile = strcmp(argv[aoff + 1], "-") == 0 ? stdout : fopen(argv[aoff + 1], oflags);
    if(!outfile)
    {
        LOG("fopen(%s): %s", argv[aoff + 1], strerror(errno));
        goto out;
    }

    if(mode == Mode_Binary)
    {
        if(fwrite(mem, 1, mlen, outfile) != mlen)
        {
            LOG("fwrite: %s", strerror(errno));
            goto out;
        }
    }
    else
    {
        r = 0;
        if(mode == Mode_NamedArray)
        {
            r = fprintf(outfile, "unsigned char %s[] = {\n", aname);
        }
        if(r >= 0)
        {
            uint8_t *u = mem;
            for(size_t i = 0; i < mlen; ++i)
            {
                r = fprintf(outfile, "%s0x%02x,%c", i % 0x10 == 0 ? "    " : "", u[i], (i % 0x10 == 0xf || i == mlen - 1) ? '\n' : ' ');
                if(r < 0) break;
            }
        }
        if(mode == Mode_NamedArray && r >= 0)
        {
            r = fprintf(outfile, "};\n");
        }
        if(r < 0)
        {
            LOG("fprintf: %s", strerror(errno));
            goto out;
        }
    }
    fflush(outfile); // In case of stdout

    LOG("Done, base address: 0x%llx", (unsigned long long)lowest);
    retval = 0;

out:;
    if(outfile && outfile != stdout) fclose(outfile);
    if(mem) free(mem);
    if(file) free(file);
    if(infile && infile != stdin) fclose(infile);
    return retval;
}


text:
INSTALL  ?= install
CFLAGS   ?= -Wall -O3

PREFIX   ?= /usr/local
BINDIR   ?= $(PREFIX)/bin

SRC  := $(wildcard *.c)
BINS := $(SRC:%.c=%)

# mesu and strerror need some extra CFLAGS
mesu_CFLAGS     := -framework CoreFoundation
strerror_CFLAGS := -framework CoreFoundation -framework Security

all: $(BINS)

%: %.c
	$(CC) $(CFLAGS) $< -o $@ $($@_CFLAGS) $(LDFLAGS)

install: all
	$(INSTALL) -d $(DESTDIR)$(BINDIR)
	$(INSTALL) -m755 $(BINS) $(DESTDIR)$(BINDIR)

clean:
	rm -r $(BINS)

uninstall: clean
	rm -rf $(addprefix $(DESTDIR)$(BINDIR)/, $(BINS))

.PHONY: all clean install

repo_name: Siguza/misc

repo_language: C

mime_type: text/plain


text:
// gcc -o rand rand.c -Wall -O3
#include <stdbool.h>            // bool, true, false
#include <stdint.h>             // uint32_t, UINT32_MAX
#include <stdio.h>              // stderr, fprintf, printf, putchar
#include <stdlib.h>             // arc4random_uniform
#include <string.h>             // strcmp

int main(int argc, const char **argv)
{
    uint32_t max = UINT32_MAX;
    bool string = false;
    int off = 1;

    if(argc > off && strcmp(argv[off], "-s") == 0)
    {
        string = true;
        max = 32;
        ++off;
    }
    if(argc > off)
    {
        char *end;
        long long l = strtoll(argv[off], &end, 0);
        if(argv[off][0] == '\0' || end[0] != '\0')
        {
            fprintf(stderr, "Invalid input: %s\n", argv[off]);
            return 1;
        }
        else if(l > UINT32_MAX)
        {
            fprintf(stderr, "Too large: %s\n", argv[off]);
            return 1;
        }
        max = l;
        ++off;
    }

    if(string)
    {
        for(uint32_t i = 0; i < max; ++i)
        {
            uint32_t c = arc4random_uniform(62);
            putchar(c + (c < 10 ? '0' : c < 36 ? ('A' - 10) : ('a' - 36)));
        }
        putchar('\n');
    }
    else
    {
        uint32_t min = 0;
        if(argc > off)
        {
            char *end;
            long long l = strtoll(argv[off], &end, 0);
            if(argv[off][0] == '\0' || end[0] != '\0')
            {
                fprintf(stderr, "Invalid input: %s\n", argv[off]);
                return 1;
            }
            else if(l > UINT32_MAX)
            {
                fprintf(stderr, "Too large: %s\n", argv[off]);
                return 1;
            }
            min = l;
        }
        printf("%u\n", arc4random_uniform(max - min) + min);
    }

    return 0;
}


text:
// cc -o clz clz.c -Wall -O3
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char **argv)
{
    int off = 1;
    bool big = false;
    for(; off < argc; ++off)
    {
        if(argv[off][0] != '-' || (argv[off][1] >= '0' && argv[off][1] <= '9')) break;
        if(strcmp(argv[off], "-l") == 0) big = true;
        else
        {
            fprintf(stderr, "[!] Invalid argument: %s\n", argv[off]);
            return 1;
        }
    }
    if(argc - off < 1)
    {
        fprintf(stderr, "Usage:\n"
                        "    %s [-l] number\n"
                        , argv[0]);
        return 1;
    }
    unsigned long long l = strtoull(argv[off], NULL, 0);
    int result;
    if(big)
    {
        result = __builtin_clzll(l);
    }
    else
    {
        if(l > UINT_MAX)
        {
            fprintf(stderr, "[!] Number too big, use -l.\n");
            return 1;
        }
        result = __builtin_clz((unsigned int)l);
    }
    printf("%u\n", result);
    return 0;
}


text:
# misc

Stuff too small for its own repo.

### Tools

-   `bindump`
    Prints a 64bit command line arg in binary.
-   `clz`
    Clang's `__builtin_clz`, but for the command line.
-   `dsc_syms`
    Prints symbol addresses of a dyld_shared_cache in radare2 format.
-   `mesu`
    Parses an Apple OTA update plist and prints it nicely.
-   `rand`
    Generates random numbers or strings.
-   `strerror`
    Prints description for a Darwin error code.
    Simply calls `strerror`, `mach_error_string` or `SecCopyErrorMessageString` with the given command line argument.
-   `vmacho`
    Extracts a Mach-O into a raw, headless binary.
-   `xref`
    Parses an arm64 Mach-O and tries to find xrefs to a specified address.


text:
# The Archive Userland Themes

This repository contains themes for the macOS note editor [*The Archive*](https://zettelkasten.de/the-archive/).

Theme files are located in `~/Library/Application Support/TheArchive/Themes/`. You can get there by hitting the "Open Theme Directory" button from the app's settings. Feel free to play around with the theme files there.

Q: _What if I break a built-in theme?_
A: Don't worry, *The Archive* will re-create the themes for you on launch after you deleted them from the "Themes" directory.

## How to Submit Themes

If you're tech-savvy, create a pull request here on GitHub. The community will review your theme, may suggest changes, and then we'll merge it into this main collection.

If you don't know what this GitHub stuff is all about, [join our forums](https://forum.zettelkasten.de/) and create a new thread for your theme there for discussion. We'll take care of integrating your changes into this repository for you once the community approves.

## How to Write Themes

Themes are JSON files, ending in `.theme.json`. The expected attributes and object configurations are described in [the syntax definition.](Syntax.md)

## License

All themes are free to use and published here under the "Unlicense". Attribution to the respective theme authors would be nice, you know, but we don't want to even try to force you for theme colors. See the [LICENSE](LICENSE) file for details.

repo_name: Zettelkasten-Method/The-Archive-Themes

file_name: Gray Matter (Dark).theme.json


text:
# Theme Syntax Definition

The basic wireframe of a theme file is this:

    {
        "name": "Solarized",
        "style": "Light", // or "Dark"
        "author": [
            // List of authors
        ],
        "version": "1.0",

        "editor": {
            // Editor attributes
        },

        "savedSearchesSidebar": {
            // Saved Searches sidebar attributes (optional)
        },

        "resultsList": {
            // List of notes/search results (optional)
        },

        "styles": {
            // Highlighter attributes
        }
    }

**Note:** The `//` should indicate a comment. Please keep in mind that JSON does not in fact support comment syntax like this. So make sure you don't put these inside your theme files during theme development.

## Root-Level Keys

List of all root-level attributes:

- `name`: String
- `style`: String, either "Light" or "Dark"; used to filter themes for dark mode
- `author`: Array of author detail objects
- `version`: String
- `editor`: a settings Object for the text editor; see details below
- `savedSearchesSidebar`: a settings Object for the Saved Searches sidebar; see details below
- `resultsList`: a settings Object for the search results sidebar, showing the note titles; see details below
- `styles`: a settings Object for text and Markdown highlighting; see details below

When I say "object" or "settings object", I mean a `{...}` delimited collection of sub-attributes.

Example of the preamble part, consisting of `name`, `style`, `author`, and `version`:

    "name": "My Theme",
    "style": "Light", // or "Dark"
    "author": [
        {
            // Free-form object; consider "name", "url", "email"
            "name": "Your Name",
            "twitter": "@you",
            "url": "http://yourdomain.free-exposure.com/"
        },
        {
            "name": "Maybe another author"
        }
    ],
    "version": "1.2",
    // ...

## `editor` Settings

This is a special set of styles that apply to the app text editor itself:

- `backgroundColor`: hexadecimal color string for the app's background, like `"#93a1a1"`
- `caretColor`: hexadecimal color string for the insertion point in text fields, like `"#93a1a1"`
- `highlight`: settings object for search highlights, if enabled by the user; accepts `color`, `backgroundColor`, and `unfocusedBackgroundColor`
- `selection`: settings for user-selected text; accepts `color`, `unfocusedColor`, `backgroundColor`, and `unfocusedBackgroundColor`

The difference between `backgroundColor` and `unfocusedBackgroundColor` is that the latter will be used when the user clicks into another component, for example the Omnibar, to indicate the text editor is not receiving key events. In other words: use this to help users figure out if typing on the keyboard would overwrite the highlighted part.

### Example

    // ...
    "editor": {
        "backgroundColor": "#ffffff",
        "caretColor": "#000000",
        "highlight": {
            "color": "#000000"
            "backgroundColor": "#cccccc",
            "unfocusedBackgroundColor": "#dddddd"
        },
        "selection": {
            "color": "#000000"
            "backgroundColor": "#cccccc",
            "unfocusedBackgroundColor": "#dddddd"
        },
    },
    // ...

## `savedSearchesSidebar` Settings

This is an optional set of colors to style the "Saved Searches" sidebar differently. By default, it inherits colors from the editor to blend in. To keep the color variety limited and not produce a rainbow of effectful color combinations, consider reusing editor colors for search highlights or similar here if in doubt.

The accepted settings to override the default colors:

- `color`: optional hexadecimal color string for the label text; defaults to `style.base.color`
- `backgroundColor`: optional hexadecimal color string for the sidebar background; defaults to `editor.backgroundColor`
- `highlightedBackgroundColor`: optional hexadecimal color string for item background when they are clicked on to provide visual feedback; defaults to `backgroundColor`

### Example

    // ...
    "savedSearchesSidebar": {
        "color": "#333333",
        "backgroundColor": "#0850AA",
        "highlightedBackgroundColor: "#154B8F"
    },
    // ...

## `resultsList` Settings

This is also optional. The colors are used to style the list of search results, or note titles. By default, it inherits colors from the editor to blend in.

The accepted settings to override the default colors:

- `color`: optional hexadecimal color string for the search result title text; defaults to `style.base.color`
- `backgroundColor`: optional hexadecimal color string for the list item's background; defaults to `editor.backgroundColor`
- `alternateBackgroundColor`: optional hexadecimal color string for the list item's background when "Alternate Row Colors" is enabled by the user; defaults to `editor.backgroundColor`
- `border`: optional, use to configure the visible border between the list of results and the editor. You may want to deactivate a visible border when the background colors provide enough contrast, for example. Accepted values: `false`, to deactivate; `true`, to use the macOS system default color; or a hexadecimal color string. Defaults to `true`.
- `selection`: optional settings for user-selected notes in the list; similar to text selection in the `editor` block (see above). Accepts `color`, `unfocusedColor`, `backgroundColor`, and `unfocusedBackgroundColor`. The "unfocused" variants are used to help users figure out when hitting keys on their keyboard will affect the notes selection, for example. If you don't provide these variants, the default (focused) variants are used.

### Example

This is from the _Solarized (Light)_ theme:

    // ...
    "resultsList": {
        "color": "#657b83",
        "backgroundColor": "#FDF6E4",
        "alternateBackgroundColor": "#F2EBDA",
        "selection": {
            "color": "#FDF6E4",
            "backgroundColor": "#657b83",
            "unfocusedColor": "#586e75",
            "unfocusedBackgroundColor": "#E0DAC9"
        },
        "border": "#EAE4D2",
    },
    // ...

- The background is the same color as the editor; the alternate is a slightly darker variation;
- the text color is the default editor text color, too, so the sidebar would blend in with the editor seamlessly;
- a border is set to have a visual separator;
- the selection colors are similar to text selection colors.

Note that other themes, like _Solarized (Dark)_, omit borders and use a darker background for the whole result list for maximum contrast. That works just as well, visually.

## `styles` Settings

These are Markdown highlighter settings.

### Block Styles

Here's a list of all blocks you can configure:

- `base`
- `h1`
- `h2`
- `h3`
- `h4`
- `h5`
- `h6`
- `horizontalRule`
- `codeBlock`
- `inlineCode`
- `blockquote`
- `list`
- `comment`
- `referenceDefinition` (unused at the moment)
- `table` (unused at the moment)
- `header` (unused at the moment)

#### Supported attributes by all block styles

- `font`: override of the app's or parent block's base font setting; accepted attributes are `name` (String, required), `size` (points as a float), and `style` (one of "regular", "italic", "bold")
- `color`: hexadecimal color string, like "#93a1a1"
- `backgroundColor`: hexadecimal color string, like "#93a1a1"
- `syntax`: settings for the Markdown syntax element characters; accepts with `color` and `backgroundColor`
- `indent`: settings to indent the whole block visually; can be "none", "inherit", or `{"points": 12.34}` (measured in the base font size) or `{"characters": 2}`

##### Overriding fonts

You can override the app's font setting, which is not recommended, by the way, like this:

    "codeBlock": {
        "font": {
            "name": "Monaco",
            "size": "12"
        }
    }, // ...

Assuming the app uses a non-monospace font for text, this would style inline code in monospace. Some people prefer a mix of fonts for legibility. But you have to provide `name` _and_ `size` to make it work. (We'll change this in the future to be a bit more convenient.)

### Inline styles

- `inlineCode`
- `emphasis`
- `strong`
- `image`
- `link`
- `comment`
- `footnote`

#### Supported attributes by all inline styles

- `font`: override of the app's or parent block's base font setting; accepted attributes are `name` (a string, if you want to replace the font), `size` (points as a float), or `style` (one of "regular", "italic", "bold")
- `color`: hexadecimal color string, like `"#93a1a1"`
- `backgroundColor`: hexadecimal color string, like `"#93a1a1"`
- `syntax`: settings for the Markdown syntax element characters; accepts with `color` and `backgroundColor`

#### Example

    "emphasis": {
        "color": "#93a1a1",
        "font": { "style": "italic" },
    }, // ...


text:
{
    "name": "Gray Matter",
    "style": "Light",
    "author": [
        {
            "name": "Philip Belesky",
            "url": "http://philipbelesky.com/",
            "basedOn": "https://github.com/philipbelesky/gray-matter"
        },
    ],
    "version": "0.10",
    "editor": {
        "backgroundColor": "#F5F5F5",
        "caretColor": "#29BEEA",
        "selection": {
            "color": "#000000",
            "backgroundColor": "#C3E8F3",
            "unfocusedBackgroundColor": "#C3E8F3",
        },
        "highlight": {
            "backgroundColor": "#E5E5E5",
            "unfocusedBackgroundColor": "#E5E5E5"
        }
    },
    "savedSearchesSidebar": {
        "color": "#3C3C3C",
        "backgroundColor": "#EDEDED",
        "highlightedBackgroundColor": "#C3E8F3",
        "border": false,
        "iconColors": {
            "default": "#B9B9B9"
        },
    },
    "resultsList": {
        "color": "#3C3C3C",
        "backgroundColor": "#EDEDED",
        "alternateBackgroundColor": "#F5F5F5",
        "selection": {
            "color": "#000000",
            "unfocusedColor": "#000000",
            "backgroundColor": "#C3E8F3",
            "unfocusedBackgroundColor": "#E5E5E5"
        },
        "border": "#EDEDED",
    },
    "styles": {
        "base": {
            "color": "#3C3C3C",
        },
        "h1": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#B9B9B9" },
        },
        "h2": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#B9B9B9" },
        },
        "h3": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#B9B9B9" },
        },
        "h4": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#B9B9B9" },
        },
        "h5": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#B9B9B9" },
        },
        "h6": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#B9B9B9" },
        },
        "horizontalRule": {
            "color": "#B9B9B9",
            "syntax": { "color" : "#B9B9B9" }
        },
        "codeBlock": {
            "color": "#3C3C3C",
            "backgroundColor": "#EDEDED",
        },
        "inlineCode": {
            "color": "#3C3C3C",
            "backgroundColor": "#EDEDED",
        },
        "blockquote": {
            "syntax": { "color" : "#B9B9B9" },
        },
        "list": {
            "syntax": { "color" : "#B9B9B9" },
        },
        "emphasis": {
            "font": { "style": "italic" },
            "syntax": { "color" : "#B9B9B9" }
        },
        "strong": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#B9B9B9" }
        },
        "image": {
            "color": "#3C3C3C",
            "syntax": { "color" : "#B9B9B9" },
        },
        "footnote": {
            "color": "#B9B9B9",
            "syntax": { "color" : "#B9B9B9" }
        },
        "link": {
            "color": "#3C3C3C",
            "syntax": { "color" : "#B9B9B9" }
        },
        "referenceDefinition": {
            "color": "#3C3C3C",
            "syntax": { "color" : "#B9B9B9" }
        },
        "comment": {
            "color": "#B9B9B9",
            "font": { "style": "regular" }
        }
    }
}


text:
{
    "name": "Gray Matter",
    "style": "Dark",
    "author": [
        {
            "name": "Philip Belesky",
            "url": "http://philipbelesky.com/",
            "basedOn": "https://github.com/philipbelesky/gray-matter"
        },
    ],
    "version": "0.10",
    "editor": {
        "backgroundColor": "#1A191A",
        "caretColor": "#29BEEA",
        "selection": {
            "color": "#FFFFFF",
            "backgroundColor": "#1C434D",
            "unfocusedBackgroundColor": "#1C434D",
        },
        "highlight": {
            "backgroundColor": "#2D2D2D",
            "unfocusedBackgroundColor": "#2D2D2D"
        }
    },
    "savedSearchesSidebar": {
        "color": "#BEBEBE",
        "backgroundColor": "#222122",
        "highlightedBackgroundColor": "#1C434D",
        "border": false,
        "iconColors": {
            "default": "#616161"
        },
    },
    "resultsList": {
        "color": "#BEBEBE",
        "backgroundColor": "#222122",
        "alternateBackgroundColor": "#1A191A",
        "selection": {
            "color": "#FFFFFF",
            "unfocusedColor": "#FFFFFF",
            "backgroundColor": "#1C434D",
            "unfocusedBackgroundColor": "#2D2D2D"
        },
        "border": "#222122",
    },
    "styles": {
        "base": {
            "color": "#BEBEBE",
        },
        "h1": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#616161" },
        },
        "h2": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#616161" },
        },
        "h3": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#616161" },
        },
        "h4": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#616161" },
        },
        "h5": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#616161" },
        },
        "h6": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#616161" },
        },
        "horizontalRule": {
            "color": "#616161",
            "syntax": { "color" : "#616161" }
        },
        "codeBlock": {
            "color": "#BEBEBE",
            "backgroundColor": "#222122",
        },
        "inlineCode": {
            "color": "#BEBEBE",
            "backgroundColor": "#222122",
        },
        "blockquote": {
            "syntax": { "color" : "#616161" },
        },
        "list": {
            "syntax": { "color" : "#616161" },
        },
        "emphasis": {
            "font": { "style": "italic" },
            "syntax": { "color" : "#616161" }
        },
        "strong": {
            "font": { "style": "bold" },
            "syntax": { "color" : "#616161" }
        },
        "image": {
            "color": "#BEBEBE",
            "syntax": { "color" : "#616161" },
        },
        "footnote": {
            "color": "#616161",
            "syntax": { "color" : "#616161" }
        },
        "link": {
            "color": "#BEBEBE",
            "syntax": { "color" : "#616161" }
        },
        "referenceDefinition": {
            "color": "#BEBEBE",
            "syntax": { "color" : "#616161" }
        },
        "comment": {
            "color": "#616161",
            "font": { "style": "regular" }
        }
    }
}


text:
# CC0 1.0 Universal

## Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.

For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.

### 1. Copyright and Related Rights.

A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:

  i. the right to reproduce, adapt, distribute, perform, display, communicate,
  and translate a Work;

  ii. moral rights retained by the original author(s) and/or performer(s);

  iii. publicity and privacy rights pertaining to a person's image or likeness
  depicted in a Work;

  iv. rights protecting against unfair competition in regards to a Work,
  subject to the limitations in paragraph 4(a), below;

  v. rights protecting the extraction, dissemination, use and reuse of data in
  a Work;

  vi. database rights (such as those arising under Directive 96/9/EC of the
  European Parliament and of the Council of 11 March 1996 on the legal
  protection of databases, and under any national implementation thereof,
  including any amended or successor version of such directive); and

  vii. other similar, equivalent or corresponding rights throughout the world
  based on applicable law or treaty, and any national implementations thereof.

### 2. Waiver.

To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.

### 3. Public License Fallback.

Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.

### 4. Limitations and Disclaimers.

  a. No trademark or patent rights held by Affirmer are waived, abandoned,
  surrendered, licensed or otherwise affected by this document.

  b. Affirmer offers the Work as-is and makes no representations or warranties
  of any kind concerning the Work, express, implied, statutory or otherwise,
  including without limitation warranties of title, merchantability, fitness
  for a particular purpose, non infringement, or the absence of latent or
  other defects, accuracy, or the present or absence of errors, whether or not
  discoverable, all to the greatest extent permissible under applicable law.

  c. Affirmer disclaims responsibility for clearing rights of other persons
  that may apply to the Work or any use thereof, including without limitation
  any person's Copyright and Related Rights in the Work. Further, Affirmer
  disclaims responsibility for obtaining any necessary consents, permissions
  or other rights required for any use of the Work.

  d. Affirmer understands and acknowledges that Creative Commons is not a
  party to this document and has no duty or obligation with respect to this
  CC0 or use of the Work.

For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>

repo_name: ambster-public/awesome-qlik


text:
# Contribution Guidelines

## Adding to this list

Please ensure your pull request adheres to the following guidelines:

- Search previous suggestions before making a new one, as yours may be a duplicate.
- Make sure the extension or resource is useful before submitting.
- Make an individual pull request for each suggestion.
- Use title-casing (AP style).
- Use the following format: `[Extension/Resource Name](Link) - Description`
- Additions should be added to the correct alphabetical position of the relevant category.
- New categories or improvements to the existing categorization are welcome.
- Check your spelling and grammar.
- Make sure your text editor is set to remove trailing whitespace.
- The pull request and commit should have a useful title.
- The body of your commit message should contain a link to the repository.

Thank you for your suggestions!


text:
# Awesome Qlik [![Awesome](https://awesome.re/badge.svg)](https://awesome.re) [<img src="https://webapps.qlik.com/WarsawMarathon/qlik.jpg" width="250" align="right" alt="Qlik logo">](https://qlik.com)

> Curated list of awesome [Qlik](https://qlik.com) guides, blogs, extensions, and other resources.

Qlik is a [leading](https://www.qlik.com/us/gartner-magic-quadrant-business-intelligence) business intelligence platform delivering solutions for data visualisation, analytics, and reporting. Running on the same engine, its main products are [QlikView](https://www.qlik.com/us/products/qlikview) (for guided analytics) and [Qlik Sense](https://www.qlik.com/us/products/qlik-sense) (for self-service BI).

## Contents

- [Official Resources and Documentation](#official-resources-and-documentation)
- [Community Resources and Blogs](#community-resources-and-blogs)
- [Books](#books)
- [Extensions](#extensions)

## Official Resources and Documentation
- [Qlik Blog](https://blog.qlik.com)
- [Qlik Help](https://help.qlik.com)

## Community Resources and Blogs

- [Living QlikView](http://livingqlikview.com/) - Aaron Couron's blog with in-depth articles, technical tips and tricks, and reviews of third-party applications and resources that help to progress with QlikView and Qlik Sense.
- [Qlik Community](https://community.qlik.com) - Qlik Community: product forums, resources, services, events, groups, and blogs on the Qlik platform.
- [QlikCentral](https://qlikcentral.com/) - A blog where Richard Pearce shares insight and technical examples he develops in QlikView and Qlik Sense.
- [QlikFix](http://www.qlikfix.com) - A blog dedicated to QlikView and Qlik Sense tips, tricks and tutorials from Barry Harmsen.
- [QlikView Cookbook](https://qlikviewcookbook.com/) - A technical 'how-to' blog for QlikView developers, by Rob Wunderlich.
- [Qlik Tips](https://www.qliktips.com) - An all-things-Qlik blog by Stephen Redmond.
- [Quick Intelligence](https://www.quickintelligence.co.uk/blog/) - A blog where a skilled team of Qlik developers lead by Steve Dark, providing tips on working in Qlik Sense and QlikView.
- Reddit subreddits:
  - [/r/Qlik](https://www.reddit.com/r/qlik/) - Subreddit about Qlik.
  - [/r/QlikSense](https://www.reddit.com/r/qliksense/) - Subreddit about Qlik Sense.
  - [/r/QlikView](https://www.reddit.com/r/qlikview/) - Subreddit about QlikView.

## Books

### About Qlik Sense

- [Learning Qlik Sense: The Official Guide](https://books.google.com/books/about/Learning_Qlik_Sense_The_Official_Guide.html?id=4zvlCwAAQBAJ&redir_esc=y) - By Christopher Ilacqua, Henric Cronström et al.
- [Qlik Sense Cookbook](https://books.google.com/books/about/Qlik_Sense_Cookbook.html?id=07xouwEACAAJ&redir_esc=y ) - By Pablo Labbe, Philip Hand et al.
- [Qlik Sense for Beginners](https://books.google.com/books/about/Qlik_Sense_for_Beginners.html?id=Dy3nBAAAQBAJ&redir_esc=y) - By Mark O'Donovan.
- [Qlik Sense: Advanced Data Visualization for Your Organization](https://books.google.ru/books?id=kPNFDwAAQBAJ&redir_esc=y) - By Dr. Christopher Ilacqua et al.
- [Predictive Analytics Using Rattle and Qlik Sense](https://www.amazon.com/Predictive-Analytics-using-Rattle-Sense/dp/1784395803) - By Ferran Garcia Pagans.
- [Mastering Qlik Sense](https://www.amazon.com/Mastering-Qlik-Sense-self-service-Intelligence/dp/1783554029) - By Martin Mahler and Juan Ignacio Vitantonio.
- [Implementing Qlik Sense](https://books.google.com/books/about/Implementing_Qlik_Sense.html?id=6nZaswEACAAJ&redir_esc=y) - By Ganapati Hegde and Kaushik Solanki.

### About QlikView

- [QlikView Essentials](https://books.google.com/books/about/QlikView_Essentials.html?id=5wMcDAAAQBAJ&redir_esc=y) - By Chandraish Sinha.
- [Creating Stunning Dashboards with QlikView](https://www.amazon.com/Creating-Stunning-Dashboards-QlikView-Villafuerte/dp/1782175733
) - By Julián Villafuerte.
- [QlikView for Finance](https://books.google.com/books/about/QlikView_for_Finance.html?id=pNZOCwAAQBAJ&redir_esc=y) - By B. Diane Blackwood.
- [QlikView Unlocked](https://books.google.com/books/about/QlikView_Unlocked.html?id=vvaoCwAAQBAJ&redir_esc=y) - By Roger Stone and Andrew Dove.
- [Practical QlikView](https://www.amazon.com/Practical-QlikView-Mark-ODonovan-ebook/dp/B007QMMDL4) - By Mark O'Donovan.
- [QlikView 11 for Developers](https://www.amazon.com/QlikView-Developers-Effective-techniques-Intelligence/dp/1849686068) - By Miguel García and Barry Harmsen.
- [QlikView Scripting](https://books.google.com/books/about/QlikView_Scripting.html?id=wng3ngEACAAJ&redir_esc=y) - By Matt Floyd.
- [QlikView Server and Publisher](https://books.google.com/books/about/QlikView_Server_and_Publisher.html?id=gDOhAgAAQBAJ&redir_esc=y) - By Stephen Redmond.

## Extensions

- [AnyChart](https://qlik.anychart.com)
- [trueChart](https://www.truechart.com)
- [VizLib](https://www.vizlib.com)

## Contributing

Your contributions are always welcome! Please take a look at the [contribution guidelines](https://github.com/ambster-public/awesome-qlik/blob/master/contributing.md) first.

## License

[![CC0](https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](https://creativecommons.org/publicdomain/zero/1.0)

To the extent possible under law, [ambster](https://github.com/ambster-public) has waived all copyright and related or neighboring rights to this work.

> Qlik is a registered trademark of Qlik Technologies, Inc.


text:
# 分布式服务器架构下的3v3团队对抗游戏

【Warning】 本仓库作为本人的毕设课题，主要是初学者的学习/实践，谨慎用于正式用途。

## 仓库内容

1. 一个异步多线程的服务器框架。
2. 使用Lua语言开发的几个服务器业务逻辑
3. 一套基于Unity的对话系统套件
4. 一个以类似喷射战士的3v3对抗为核心玩法的游戏

## 异步多线程的分布式服务器框架

服务器框架位于`ServerCore`目录

### 框架特点

1. 采用libuv处理各种各样的异步操作，文件io，tcp，udp，工作队列等
2. 支持Protobuf和Json的序列化和反序列化，Protobuf支持动态读取.proto文件获取类型信息
3. 底层支持Mysql和Redis数据库
4. Session和Service的解耦合设计
5. 内置Lua虚拟机，将系统接口导出LuaAPI，支持Lua和C++混合开发，[查看导出的LuaAPI](https://github.com/ZerlenZhang/distributed-architecture-of-moba-game-server/wiki/Lua%E6%8E%A5%E5%8F%A3%E6%8C%87%E5%BC%95)
6. 其他工具：日志、时间戳、计时器等

### 核心类关系图
![Image text](https://github.com/ZerlenZhang/distributed-architecture-of-moba-game-server/raw/master/Images/UML_1.png)

`CmdPackage` 在网络层之上的一层自定义协议，本项目所有网络消息都是这个类型的。serviceType标识此消息是属于哪一类服务，cmdType标识此消息具体含义

`Session` 是对Socket的封装，提供发送CmdPackage的接口，上层无需关系是Tcp链接还是Udp链接

`Service` 是对服务器功能的抽象，服务器可以通过注册Service来获得处理某一类CmdPackage的功能。

`Netbus` 是框架的最底层，直接接触链接的建立，网络消息的发送和接受，它处理了字节流和CmdPackage之间的互相转化

## 服务器业务逻辑
服务器业务逻辑代码位于`ServerApp`目录

### 服务器启动示意图
![Image text](https://github.com/ZerlenZhang/distributed-architecture-of-moba-game-server/raw/master/Images/bushu.png)

### 已实现的功能
1. 支持用户的注册、登录、匹配和退出匹配、英雄选择、帧同步的服务器逻辑
2. 其他值得一提的是：时间原因，项目中未使用Redis，实际上是可以正常使用的

## 基于Unity的对话系统套件
对话系统代码主要位于`PurificationPioneer\Assets\3rd\DialogSystem`目录

### 对话系统示意图
![Image text](https://github.com/ZerlenZhang/distributed-architecture-of-moba-game-server/raw/master/Images/DialogStructure.png)

`DialogAsset` 是一种ScriptableObject类型的资产，支持可视化的编辑对话内容. 对话内容支持：对话、旁白、选择、音乐音乐、简单屏幕效果、逻辑分支、变量控制、自定义消息等等

`DialogSystem` 一方面，它作为一种容器存放当前物体需要使用到的DialogAsset，一方面，也作为对话系统的引擎，提供将DialogAsset进行演出的接口

`DialogTrigger` 可以方便的设置如何开启对话、如何使用DialogSystem中的DialogAsset列表、在什么情况下触发器正常工作等。

此外，系统还有DialogVarAsset、DialogProgresss、DialogCharacter等工具方便使用

## 3v3团队对抗游戏客户端

游戏客户端参考喷射战士的玩法，多人比拼在一定时间内涂色面积大的一方胜出
![Image text](https://github.com/ZerlenZhang/distributed-architecture-of-moba-game-server/raw/master/Images/Client.png)

### 客户端目前实现的内容
1. 用户注册、登录、匹配、英雄选择、帧同步战斗、战斗结束的结算与返回
2. 制作了三个可用的角色，分别具有不同的涂色方式
3. 制作了英雄图鉴、故事模式
4. 支持PC端和安卓两个平台

## 分布式服务器快速上手
1、在MobaServer.exe 同目录下
2、创建Server.lua,添加如下代码
~~~
Debug.LogInit("ServerLogs","server",true);
Netbus.TcpListen(8900,
function(s)

        local ip,port = Session.GetAddress(s);

        print("new client come ["..ip..":"..port.."]");

   end);
print("Server tcp listen at 8900");
~~~
3、创建ServerBat.bat,记事本打开并添加如下内容
~~~
【添加Lua搜索路径，Lua启动脚本相对路径（相对搜索路径）】
MobaServer.exe ./ Server.lua
~~~
4、创建Client.lua,添加如下代码
~~~
Debug.LogInit("ClientLogs","client",true);
Netbus.TcpConnect("127.0.0.1",8900,
		--链接成功的回调
        function(session)
            if session then
                print("succeed to connect to [127.0.0.1:8900]");

                return;
            end
            print("failed to connect to [127.0.0.1:8900]")
        end);
~~~
5、创建ClientBat.bat，记事本打开添加如下内容
~~~
MobaServer.exe ./ Client.lua
~~~
6、先运行ServerBat，在运行ClientBat


text:
#include <HiredisWrapper.h>
#include <uv.h>
#include "rediswarpper.h"
#include "../utils/logger/logger.h"

#include "../utils/cache_alloc/small_alloc.h"

#define my_alloc small_alloc
#define my_free small_free

static char* my_strdup(const char* src)
{
	auto len = strlen(src) + 1;
	auto dst = (char*)my_alloc(len);
	strcpy(dst, src);
	return dst;
}

static void free_my_strdup(char* str)
{
	my_free(str);
}

#pragma region 信息结构体

//connect必须信息
struct connect_req
{
	char* ip;
	int port;

	RedisConnectCallback open_cb;

	char* error;
	RedisContext* context;
};

//query必须信息
struct query_req
{
	RedisContext* context;
	char* cmd;
	RedisQueryCallback query_cb;
	char* error;
	RedisReply* myReply;
};

#pragma endregion

#pragma region 回调

static void connect_work(uv_work_t* req)
{
	auto pInfo = (connect_req*)req->data;
	timeval timeout = {5};
	pInfo->context->pConn = RedisConnectWithTimeout(pInfo->ip, pInfo->port, timeout);
	if (pInfo->context->pConn->err)
	{
		pInfo->error = my_strdup(pInfo->context->pConn->errstr);
		RedisFree(pInfo->context->pConn);
		pInfo->context->pConn = NULL;
		return;
	}
}

static void on_connect_complete(uv_work_t* req, int status)
{
	auto pInfo = (connect_req*)req->data;

	//log_debug("Redis 数据库链接成功");

	if (pInfo)
	{
		if (pInfo->open_cb)
			pInfo->open_cb(pInfo->error, pInfo->context);

		if (pInfo->ip)
			free_my_strdup(pInfo->ip);
		if (pInfo->error)
			free_my_strdup(pInfo->error);

		my_free(pInfo);
	}

	my_free(req);
}

static void close_work(uv_work_t* req)
{
	auto pConn = (RedisContext*)req->data;

	//加锁，等待正在进行的查询结束
	uv_mutex_lock(&pConn->lock);
	//log_debug("close 加锁");
	RedisFree(pConn->pConn);
	pConn->pConn = NULL;
	//log_debug("close 释放");
	uv_mutex_unlock(&pConn->lock);
}

static void on_close_complete(uv_work_t* req, int status)
{
	auto info = (RedisContext*)req->data;

	if (info)
	{
		if (info->udata && info->autoFreeUdata)
			free(info->udata);
		my_free(info);

	}
	my_free(req);
	log_debug("Redis 数据库断开链接");
}

static void query_work(uv_work_t* req)
{
	auto pInfo = (query_req*)req->data;

	if (pInfo->context->isClosed)
	{// 已经关闭此链接，就不要再操作了
		log_debug("链接已经关闭，查询无效");
		return;
	}
	//线程锁
	uv_mutex_lock(&pInfo->context->lock);
	//log_debug("query 加锁");

	auto replay = (redisReply*)RedisCommand(pInfo->context->pConn, pInfo->cmd);

	if (replay)
	{
		if (replay->type == REDIS_REPLY_ERROR)
		{
			pInfo->error = my_strdup(replay->str);
			FreeReplyObject(replay);
		}
		else
		{
			pInfo->myReply->reply = replay;
		}

	}
	//log_debug("query 释放");
	uv_mutex_unlock(&pInfo->context->lock);

}

static void on_query_complete(uv_work_t* req, int status)
{
	auto pInfo = (query_req*)req->data;

	if (pInfo)
	{
		if(pInfo->query_cb)
			pInfo->query_cb(pInfo->error, pInfo->myReply);

		if (pInfo->cmd)
			free_my_strdup(pInfo->cmd);
		if (pInfo->error)
			free_my_strdup(pInfo->error);

		if (pInfo->myReply)
		{
			if(pInfo->myReply->reply)
				FreeReplyObject(pInfo->myReply->reply);

			if (pInfo->myReply->udata && pInfo->myReply->autoFreeUdata)
				free(pInfo->myReply->udata);
			my_free(pInfo->myReply);
		}
		my_free(pInfo);
	}
	my_free(req);
}

#pragma endregion

#pragma region redis_wrapper
void redis_wrapper::connect(char* ip, int port, RedisConnectCallback callback,void* udata,bool autoFreeUdata)
{
	auto w = (uv_work_t * )my_alloc(sizeof(uv_work_t));
	memset(w, 0, sizeof(uv_work_t));

	auto info = (connect_req*)my_alloc(sizeof(connect_req));
	memset(info, 0, sizeof(connect_req));

	auto lockContext = (RedisContext*)my_alloc(sizeof(RedisContext));
	memset(lockContext, 0, sizeof(RedisContext));
	uv_mutex_init(&lockContext->lock);//初始化信号量
	lockContext->udata = udata;
	lockContext->autoFreeUdata = autoFreeUdata;

	info->ip = my_strdup(ip);
	info->port = port;
	info->open_cb = callback;
	info->context = lockContext;

	w->data = info;

	uv_queue_work(uv_default_loop(), w, connect_work, on_connect_complete);
}

void redis_wrapper::close_redis(RedisContext* conntext)
{
	if (conntext->isClosed)
	{// 已经关了
		return;
	}

	auto w = (uv_work_t*)my_alloc(sizeof(uv_work_t));
	memset(w, 0, sizeof(uv_work_t));

	conntext->isClosed = true;

	w->data = conntext;

	uv_queue_work(uv_default_loop(), w, close_work, on_close_complete);
}

void redis_wrapper::query(RedisContext* context, char* sql, RedisQueryCallback callback,void* udata,bool autoFreeUdata)
{
	auto w = (uv_work_t*)my_alloc(sizeof(uv_work_t));
	memset(w, 0, sizeof(uv_work_t));

	auto pInfo = (query_req*)my_alloc(sizeof(query_req));
	memset(pInfo, 0, sizeof(query_req));

	auto myReply = (RedisReply*)my_alloc(sizeof(RedisReply));
	memset(myReply, 0, sizeof(RedisReply));
	myReply->udata = udata;
	myReply->autoFreeUdata = autoFreeUdata;

	pInfo->context = context;
	pInfo->cmd = my_strdup(sql);
	pInfo->query_cb = callback;
	pInfo->myReply = myReply;

	w->data = pInfo;

	uv_queue_work(uv_default_loop(), w, query_work, on_query_complete);

}

#pragma endregion


text:
#include "mysqlwarpper.h"
#include "../utils/logger/logger.h"

#include "../utils/cache_alloc/small_alloc.h"

#define my_alloc small_alloc
#define my_free small_free

static char* my_strdup(const char* src)
{
	auto len = strlen(src) + 1;
	auto dst = (char*)my_alloc(len);
	strcpy(dst, src);
	return dst;
}

static void free_strdup(char* str)
{
	my_free(str);
}

//connect必须信息
struct connect_req
{
	char* ip;
	int port;
	char* dbName;
	char* uName;
	char* password;

	MysqlConnectCallback open_cb;

	char* error;
	MysqlContext* context;
};

//query必须信息
struct query_req
{
	MysqlContext* context;
	char* cmd;
	void(*query_cb)(const char* err, MysqlResult* result);

	char* error;
	MysqlResult* myReply;
};

#pragma region 回调

static void connect_work(uv_work_t* req)
{
	auto pInfo = (connect_req*)req->data;

	//加锁，避免尚未链接数据库就有线程进行数据库读写操作
	uv_mutex_lock(&pInfo->context->lock);
	//log_debug("connect 加锁");

	pInfo->context->pConn = mysql_init(NULL);

	if (!pInfo->context->pConn)
	{
		#pragma region 释放内存
		pInfo->error = my_strdup("链接数据库失败――mysql_init(NULL) 返回 NULL, ");
		if (pInfo->open_cb)
			pInfo->open_cb(pInfo->error, pInfo->context);
		if (pInfo->ip)
			free_strdup(pInfo->ip);
		if (pInfo->dbName)
			free_strdup(pInfo->dbName);
		if (pInfo->uName)
			free_strdup(pInfo->uName);
		if (pInfo->password)
			free_strdup(pInfo->password);
		if (pInfo->error)
			free_strdup(pInfo->error);
		auto context = pInfo->context;
		if (context)
		{
			if (context->udata && context->autoFreeUserData)
				free(context->udata);
			log_debug("1");
			uv_mutex_unlock(&context->lock);
			my_free(context);
		}

		log_debug("2");
		my_free(pInfo);
		my_free(req);

		#pragma endregion

		return;
	}

	auto result = mysql_real_connect(
		pInfo->context->pConn,
		pInfo->ip,
		pInfo->uName,
		pInfo->password,
		pInfo->dbName,
		pInfo->port,
		NULL,
		0);

	if (result == NULL)
	{
		pInfo->error = my_strdup(mysql_error(pInfo->context->pConn));
	}

	//log_debug("connect 释放");
	uv_mutex_unlock(&pInfo->context->lock);
	//my_free(req);
}

static void on_connect_complete(uv_work_t* req, int status)
{
	auto pInfo = (connect_req*)req->data;

	//log_debug("Mysql 数据库链接成功");
	if (pInfo)
	{
		if(pInfo->open_cb)
			pInfo->open_cb(pInfo->error, pInfo->context);
		if (pInfo->ip)
			free_strdup(pInfo->ip);
		if (pInfo->dbName)
			free_strdup(pInfo->dbName);
		if (pInfo->uName)
			free_strdup(pInfo->uName);
		if (pInfo->password)
			free_strdup(pInfo->password);
		if (pInfo->error)
			free_strdup(pInfo->error);
		my_free(pInfo);
	}

	my_free(req);
}

static void close_work(uv_work_t* req)
{
	auto pConn = (MysqlContext*)req->data;

	//加锁，等待正在进行的查询结束
	uv_mutex_lock(&pConn->lock);
	//log_debug("close 加锁");
	mysql_close(pConn->pConn);
	pConn->pConn = NULL;
	//log_debug("close 释放");
	uv_mutex_unlock(&pConn->lock);
}

static void on_close_complete(uv_work_t* req, int status)
{
	auto context = (MysqlContext *) req->data;
	if (context)
	{
		if (context->udata && context->autoFreeUserData)
			free(context->udata);
		my_free(context);
	}
	my_free(req);
	log_debug("Mysql 数据库断开连接");
}

static void query_work(uv_work_t* req)
{
	auto pInfo = (query_req*)req->data;

	if (pInfo->context->isClosed)
	{// 已经关闭此链接，就不要再操作了
		//log_debug("链接已经关闭，查询无效");
		return;
	}

	//线程锁
	uv_mutex_lock(&pInfo->context->lock);
	//log_debug("query 加锁");

	auto ret = mysql_query(pInfo->context->pConn, pInfo->cmd);

	if (ret != 0)
	{
		pInfo->error = my_strdup(mysql_error(pInfo->context->pConn));
		//释放线程锁
		//log_debug("query 释放");
		uv_mutex_unlock(&pInfo->context->lock);
		return;
	}

	auto result = mysql_store_result(pInfo->context->pConn);

	pInfo->myReply->result = result;

	//释放线程锁
	//log_debug("query 释放");
	uv_mutex_unlock(&pInfo->context->lock);
}

static void on_query_complete(uv_work_t* req, int status)
{
	auto pInfo = (query_req*)req->data;

	if (pInfo)
	{
		if(pInfo->query_cb)
			pInfo->query_cb(pInfo->error, pInfo->myReply);
		if (pInfo->cmd)
			free_strdup(pInfo->cmd);
		if (pInfo->error)
			free_strdup(pInfo->error);
		if (pInfo->myReply)
		{
			if (pInfo->myReply->udata && pInfo->myReply->autoFreeUserData)
				free(pInfo->myReply->udata);
			if (pInfo->myReply->result)
				mysql_free_result(pInfo->myReply->result);
			my_free(pInfo->myReply);
		}
		my_free(pInfo);
	}
	my_free(req);
}

#pragma endregion

void mysql_wrapper::connect(char* ip, int port, char* dbName, char* uName, char* password, MysqlConnectCallback open_cb,void* udata, bool autoFreeUdata)
{
	auto w = (uv_work_t * )my_alloc(sizeof(uv_work_t));
	memset(w, 0, sizeof(uv_work_t));

	#pragma region info(connect_req)

	auto info = (connect_req*)my_alloc(sizeof(connect_req));
	memset(info, 0, sizeof(connect_req));

	info->ip = my_strdup(ip);
	info->port = port;
	info->dbName = my_strdup(dbName);
	info->uName = my_strdup(uName);
	info->password = my_strdup(password);
	info->open_cb = open_cb;

	#pragma region lockContext(MysqlContext)

	auto lockContext = (MysqlContext*)my_alloc(sizeof(MysqlContext));
	memset(lockContext, 0, sizeof(MysqlContext));
	lockContext->autoFreeUserData = autoFreeUdata;
	lockContext->udata = udata;

	uv_mutex_init(&lockContext->lock);//初始化信号量

	#pragma endregion

	info->context = lockContext;

	#pragma endregion

	w->data = info;

	uv_queue_work(uv_default_loop(), w, connect_work, on_connect_complete);
}

void mysql_wrapper::close(MysqlContext* conntext)
{
	if (conntext->isClosed)
	{// 已经关了
		log_warning("Mysql 已经关了");
		return;
	}

	auto w = (uv_work_t*)my_alloc(sizeof(uv_work_t));
	memset(w, 0, sizeof(uv_work_t));

	conntext->isClosed = true;

	w->data = conntext;

	uv_queue_work(uv_default_loop(), w, close_work, on_close_complete);
}

void mysql_wrapper::query(MysqlContext* context, char* sql, MysqlQueryCallback callback, void* udata,bool autoFreeUdata)
{
	auto w = (uv_work_t*)my_alloc(sizeof(uv_work_t));
	memset(w, 0, sizeof(uv_work_t));

	auto pInfo = (query_req*)my_alloc(sizeof(query_req));
	memset(pInfo, 0, sizeof(query_req));

	auto mysqlResult = (MysqlResult*)my_alloc(sizeof(MysqlResult));
	memset(mysqlResult, 0, sizeof(MysqlResult));

	mysqlResult->autoFreeUserData = autoFreeUdata;

	mysqlResult->udata = udata;

	pInfo->context = context;
	pInfo->cmd = my_strdup(sql);
	pInfo->query_cb = callback;
	pInfo->myReply = mysqlResult;

	w->data = pInfo;

	uv_queue_work(uv_default_loop(), w, query_work, on_query_complete);

}


text:
#ifndef __REDIS_WARPPER_H__
#define __REDIS_WARPPER_H__
#include <hiredis.h>
#include <uv.h>

struct RedisContext
{
	//MYSQL* pConn;
	bool isClosed = false;
	uv_mutex_t lock;
	redisContext* pConn;

	void* udata;
	bool autoFreeUdata;
};

struct RedisReply
{
	redisReply* reply;
	void* udata;
	bool autoFreeUdata;
};

typedef void(*RedisConnectCallback)(const char* error, RedisContext* context);
typedef void(*RedisQueryCallback)(const char* err, RedisReply* result);

class redis_wrapper
{
public:
	static void connect(char* ip, int port,
		RedisConnectCallback callBack=NULL, void* udata = NULL, bool autoFreeUdata=true);

	static void close_redis(RedisContext* context);

	static void query(RedisContext* context,
		char* sql,
		RedisQueryCallback callback = NULL,void* udata=NULL,bool autoFreeUdata=true);
};

#endif // !__REDIS_WARPPER_H__


text:
#ifndef __MYSQLWARPPER_H__
#define __MYSQLWARPPER_H__
#include <uv.h>
#include <mysql.h>

//控制线程安全-加锁
struct MysqlContext
{
	MYSQL* pConn;
	bool isClosed = false;
	uv_mutex_t lock;
	void* udata;
	//默认再uData不为空时free
	bool autoFreeUserData;
};

struct MysqlResult
{
	MYSQL_RES* result;
	void* udata;
	//默认再uData不为空时free
	bool autoFreeUserData;
};

typedef void(*MysqlQueryCallback)(const char* err, MysqlResult* result);
typedef void(*MysqlConnectCallback)(const char* error, MysqlContext* context);

class mysql_wrapper
{
public:
	static void connect(char* ip, int port, char* dbName,
		char* uName, char* password,
		MysqlConnectCallback,void* udata=NULL, bool autoFreeUdata = true);

	static void close(MysqlContext* context);

	static void query(MysqlContext* context,
		char* sql,
		MysqlQueryCallback callback=NULL,void* udata=NULL,bool autoFreeUdata=true);
};

#endif // !__MYSQLWARPPER_H__


text:
#include "mysql_export_to_lua.h"

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include "tolua++.h"
#ifdef __cplusplus
}
#endif // __cplusplus

#include "tolua_fix.h"

#include "../lua_wrapper/lua_wrapper.h"
#include "../database/mysqlwarpper.h"
#include "../utils/logger/logger.h"

static void push_mysql_row(MYSQL_ROW row, int num)
{
	auto lua = lua_wrapper::lua_state();
	lua_newtable(lua);
	auto index = 1;
	for (auto i = 0; i < num; i++)
	{
		if (row[i] == NULL)
		{
			lua_pushnil(lua);
		}
		else
		{
			lua_pushstring(lua, row[i]);
		}
		lua_rawseti(lua, -2, index);
		++index;
	}
}

#pragma region Callback
static void
on_open_cb(const char* err, MysqlContext* context) {
	if (err) {
		lua_pushstring(lua_wrapper::lua_state(), err);
		lua_pushnil(lua_wrapper::lua_state());
	}
	else {
		lua_pushnil(lua_wrapper::lua_state());
		tolua_pushuserdata(lua_wrapper::lua_state(), context);
	}

	lua_wrapper::ExeScriptHandle((int)context->udata, 2);
	lua_wrapper::RemoveScriptHandle((int)context->udata);
}

static void
on_lua_query_cb(const char* err, MysqlResult* result)
{
	auto lua = lua_wrapper::lua_state();
	if (err)
	{
		lua_pushstring(lua, err);
		lua_pushnil(lua);
	}
	else
	{
		lua_pushnil(lua);
		if (result->result)
		{// 把查询到的结果push成一个表
			lua_newtable(lua);
			auto index = 1;
			auto num = mysql_num_fields(result->result);
			MYSQL_ROW row;
			while (row = mysql_fetch_row(result->result))
			{
				push_mysql_row(row, num);
				lua_rawseti(lua, -2, index);
				++index;
			}
		}
		else
		{
			lua_pushnil(lua_wrapper::lua_state());
		}
	}

	lua_wrapper::ExeScriptHandle((int)result->udata, 2);
	lua_wrapper::RemoveScriptHandle((int)result->udata);
}
#pragma endregion

#pragma region Lua_Callback
static int
lua_mysql_connect(lua_State* tolua_S) {
	if (6 != lua_gettop(tolua_S))
	{
		log_error("函数调用错误");
		return 0;
	}
	char* ip = (char*)tolua_tostring(tolua_S, 1, 0);
	if (ip == NULL) {
		return 0;
	}

	int port = (int)tolua_tonumber(tolua_S, 2, 0);

	char* db_name = (char*)tolua_tostring(tolua_S, 3, 0);
	if (db_name == NULL) {
		return 0;
	}

	char* uname = (char*)tolua_tostring(tolua_S, 4, 0);
	if (uname == NULL) {
		return 0;
	}

	char* upwd = (char*)tolua_tostring(tolua_S, 5, 0);
	if (upwd == NULL) {
		return 0;
	}

	int handler = toluafix_ref_function(tolua_S, 6, 0);
	mysql_wrapper::connect(ip, port, db_name, uname, upwd, on_open_cb, (void*)handler,false);
	return 0;
}

static int
lua_mysql_close(lua_State* tolua_S) {
	if (1 != lua_gettop(tolua_S))
	{
		log_error("函数调用错误");
		return 0;
	}
	void* context = tolua_touserdata(tolua_S, 1, 0);
	if (context) {
		mysql_wrapper::close((MysqlContext*)context);
	}
	else
	{
		log_error("Mysql 关闭异常");
	}

	return 0;
}

static int
lua_mysql_query(lua_State* tolua_S) {
	if (3 != lua_gettop(tolua_S))
	{
		log_error("函数调用错误");
		return 0;
	}
	auto context = tolua_touserdata(tolua_S, 1, 0);
	if (!context)
		return 0;
	auto sql = tolua_tostring(tolua_S, 2, 0);
	if (!sql)
		return 0;
	auto handle = toluafix_ref_function(tolua_S, 3, 0);
	if (!handle)
		return 0;
	mysql_wrapper::query(
		(MysqlContext*)context,
		(char*)sql,
		on_lua_query_cb,
		(void*)handle,
		false);
	return 0;
}

#pragma endregion
int
register_mysql_export(lua_State* tolua_S) {
	lua_getglobal(tolua_S, "_G");
	if (lua_istable(tolua_S, -1)) {
		tolua_open(tolua_S);
		tolua_module(tolua_S, "Mysql", 0);
		tolua_beginmodule(tolua_S, "Mysql");

		tolua_function(tolua_S, "Connect", lua_mysql_connect);
		tolua_function(tolua_S, "Close", lua_mysql_close);
		tolua_function(tolua_S, "Query", lua_mysql_query);
		tolua_endmodule(tolua_S);
	}
	lua_pop(tolua_S, 1);
	return 0;
}


text:
#include "cmd_package_proto_export_to_lua.h"

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include "tolua++.h"
#ifdef __cplusplus
}
#endif // __cplusplus

#include "tolua_fix.h"
#include "../netbus/protocol/CmdPackageProtocol.h"

#include <google/protobuf/message.h>
using google::protobuf::Message;
#include<map>
#include<string>
#include "../utils/logger/logger.h"
using std::map;
using std::string;
extern const string& GetProtoDir();
extern void push_proto_message_tolua(const Message* message);
static int lua_init(lua_State* lua)
{
	auto count = lua_gettop(lua);
	int protoType;
	if (count == 1)
	{
		protoType = (int)lua_tointeger(lua, 1);
		if (protoType < 0 || protoType>1)
		{
			log_debug("异常Init: %d", protoType);
			return 0;
		}
		CmdPackageProtocol::Init((ProtoType)protoType);
	}
	else if (count == 2)
	{
		protoType = (int)lua_tointeger(lua, 1);
		if (protoType < 0 || protoType>1)
		{
			log_debug("异常Init: %d", protoType);
			return 0;
		}
		auto dir = luaL_checkstring(lua, 2);
		if (dir)
		{
			CmdPackageProtocol::Init((ProtoType)protoType, dir);
		}
	}
	else
	{
		log_error("函数调用错误");
	}

	return 0;
}
static int lua_prototype(lua_State* lua)
{
	if (0 != lua_gettop(lua))
	{
		log_error("函数调用错误");
		return 0;
	}
	lua_pushinteger(lua, (int)CmdPackageProtocol::ProtoType());
	return 1;
}

static int lua_register_protobuf(lua_State* lua)
{
	if(1!=lua_gettop(lua))
	{
		log_error("ProtoManager.RegisterCmdMap_函数调用错误");
		return 0;
	}
	map<int, map<int,string>*> cmdMap;

	auto n = luaL_len(lua, 1);
	if (n < 0)
	{
		log_warning("ProtoManager.RegisterCmdMap_注册 0 个名字")
		return 0;

	}

	auto len = luaL_len(lua, 1);

	lua_pushnil(lua);
	// 现在的栈：-1 => nil; index => table
	//index = index - 1;
	while (lua_next(lua, 1))
	{
		// 现在的栈：-1 => value; -2 => key; index => table
		// 拷贝一份 key 到栈顶，然后对它做 lua_tostring 就不会改变原始的 key 值了
		lua_pushvalue(lua, -2);
		// 现在的栈：-1 => key; -2 => value; -3 => key; index => table

		#pragma region 获取serviceType

		int stype = lua_tointeger(lua, -1);
		lua_pop(lua, 1);

		#pragma endregion

		#pragma region 获取CmdMaps

		if (!lua_istable(lua, 3))
		{
			log_error("有一项不是table，key: %d", stype);
			continue;
		}

		lua_pushnil(lua);
		// 现在的栈：-1 => nil; index => table
		//index = index - 1;
		while (lua_next(lua, 3))
		{
			// 现在的栈：-1 => value; -2 => key; index => table
			// 拷贝一份 key 到栈顶，然后对它做 lua_tostring 就不会改变原始的 key 值了
			lua_pushvalue(lua, -2);
			// 现在的栈：-1 => key; -2 => value; -3 => key; index => table
			const char* key = lua_tostring(lua, -1);
			int ctype = lua_tointeger(lua, -2);

#pragma region 添加到字典中

			if (cmdMap.find(stype) == cmdMap.end())
			{
				auto newCmdMap = new map<int, string>;
				cmdMap[stype] = newCmdMap;
			}
			(*cmdMap[stype])[ctype] = key;

#pragma endregion

			// 弹出 value 和拷贝的 key，留下原始的 key 作为下一次 lua_next 的参数
			lua_pop(lua, 2);
			// 现在的栈：-1 => key; index => table
		}

		lua_pop(lua,1);

		#pragma endregion

	}

	CmdPackageProtocol::RegisterProtobufCmdMap(cmdMap);
	return 0;
}

void register_package_proto_export(lua_State* tolua_S)
{
	lua_getglobal(tolua_S, "_G");
	if (lua_istable(tolua_S, -1)) {
		tolua_open(tolua_S);
		tolua_module(tolua_S, "ProtoManager", 0);
		tolua_beginmodule(tolua_S, "ProtoManager");

		tolua_function(tolua_S, "Init", lua_init);
		tolua_function(tolua_S, "ProtoType", lua_prototype);
		tolua_function(tolua_S, "RegisterCmdMap", lua_register_protobuf);
		tolua_endmodule(tolua_S);
	}
	lua_pop(tolua_S, 1);
}

static int lua_raw_readbody(lua_State* lua)
{
	if (1 != lua_gettop(lua))
	{
		log_error("函数调用错误");
		return 0;
	}
	auto raw = (RawPackage*)tolua_touserdata(lua, 1, 0);
	if (!raw)
	{
		log_error("RawCmd.ReadBody_包体为空");
		return 0;
	}
	CmdPackage* pkg;
	if (CmdPackageProtocol::DecodeBytesToCmdPackage(raw->body, raw->rawLen, pkg))
	{
		if (pkg->body)
		{
			switch (CmdPackageProtocol::ProtoType())
			{
			case ProtoType::Json:
				lua_pushstring(lua, (const char*)pkg->body);
				break;
			case ProtoType::Protobuf:
				push_proto_message_tolua((Message*)pkg->body);
				break;
			default:
				break;
			}
		}
		else {
			lua_pushnil(lua);
		}
		CmdPackageProtocol::FreeCmdPackage(pkg);

	}
	return 1;
}

static int lua_raw_readheader(lua_State* lua)
{
	auto argc = lua_gettop(lua);
	if (argc != 1)
	{
		log_error("函数调用错误");
		return 0;
	}
	auto raw = (RawPackage*)tolua_touserdata(lua, 1, NULL);
	if (raw)
	{
		lua_pushinteger(lua, raw->serviceType);
		lua_pushinteger(lua, raw->cmdType);
		lua_pushinteger(lua, raw->userTag);
		return 3;
	}
	log_error("RawCmd.ReadHeader_包体为空");
	return 0;
}

static int lua_set_utag(lua_State* lua)
{
	auto argc = lua_gettop(lua);
	if (argc != 2)
	{
		log_error("函数调用错误");
		return 0;
	}
	auto raw = (RawPackage*)tolua_touserdata(lua, 1, NULL);
	if (raw)
	{
		auto utag = (unsigned int)luaL_checkinteger(lua, 2);
		raw->userTag = utag;

		//设置元数据
		auto utagPointer = raw->body + 4;
		utagPointer[0] = utag & 0xff;
		utagPointer[1] = (utag & 0xff00) >> 8;
		utagPointer[2] = (utag & 0xff0000) >> 16;
		utagPointer[3] = (utag & 0xff000000) >> 24;

		return 0;
	}
	log_error("RawCmd.ReadHeader_包体为空");
	return 0;
}

void register_rawcmd_export(lua_State* tolua_S)
{
	lua_getglobal(tolua_S, "_G");
	if (lua_istable(tolua_S, -1)) {
		tolua_open(tolua_S);
		tolua_module(tolua_S, "RawCmd", 0);
		tolua_beginmodule(tolua_S, "RawCmd");

		tolua_function(tolua_S, "ReadHeader", lua_raw_readheader);
		tolua_function(tolua_S, "SetUTag", lua_set_utag);
		tolua_function(tolua_S, "ReadBody", lua_raw_readbody);
		tolua_endmodule(tolua_S);
	}
	lua_pop(tolua_S, 1);
}


text:
#include "service_export_to_lua.h"

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include "tolua++.h"
#ifdef __cplusplus
}
#endif // __cplusplus

#include "tolua_fix.h"
#include <google/protobuf/message.h>
using namespace google::protobuf;

#include "lua_wrapper.h"
#include "../utils/logger/logger.h"
#include "../netbus/service/ServiceManager.h"
#include "../netbus/protocol/CmdPackageProtocol.h"

#pragma region Service模块全局表
#define SERVICE_FUNCTION_MAPPING "service_function_mapping"

static void init_service_function_map(lua_State* lua)
{
	lua_pushstring(lua, SERVICE_FUNCTION_MAPPING);
	lua_newtable(lua);
	lua_rawset(lua, LUA_REGISTRYINDEX);
}

static unsigned int s_function_ref_id = 0;
static unsigned int
save_service_function(lua_State* L, int lo, int def)
{
	// function at lo
	if (!lua_isfunction(L, lo)) return 0;

	s_function_ref_id++;

	lua_pushstring(L, SERVICE_FUNCTION_MAPPING);
	lua_rawget(L, LUA_REGISTRYINDEX);                           /* stack: fun ... refid_fun */
	lua_pushinteger(L, s_function_ref_id);                      /* stack: fun ... refid_fun refid */
	lua_pushvalue(L, lo);                                       /* stack: fun ... refid_fun refid fun */

	lua_rawset(L, -3);                  /* refid_fun[refid] = fun, stack: fun ... refid_ptr */
	lua_pop(L, 1);                                              /* stack: fun ... */

	return s_function_ref_id;

	// lua_pushvalue(L, lo);                                           /* stack: ... func */
	// return luaL_ref(L, LUA_REGISTRYINDEX);
}

static void
get_service_function(lua_State* L, int refid)
{
	lua_pushstring(L, SERVICE_FUNCTION_MAPPING);
	lua_rawget(L, LUA_REGISTRYINDEX);                           /* stack: ... refid_fun */
	lua_pushinteger(L, refid);                                  /* stack: ... refid_fun refid */
	lua_rawget(L, -2);                                          /* stack: ... refid_fun fun */
	lua_remove(L, -2);                                          /* stack: ... fun */
}

static bool
push_service_function(int nHandler)
{
	get_service_function(lua_wrapper::lua_state(), nHandler);                  /* L: ... func */
	if (!lua_isfunction(lua_wrapper::lua_state(), -1))
	{
		log_error("[LUA ERROR] function refid '%d' does not reference a Lua function", nHandler);
		lua_pop(lua_wrapper::lua_state(), 1);
		return false;
	}
	return true;
}

static int
exe_function(int numArgs)
{
	int functionIndex = -(numArgs + 1);
	if (!lua_isfunction(lua_wrapper::lua_state(), functionIndex))
	{
		log_error("value at stack [%d] is not function", functionIndex);
		lua_pop(lua_wrapper::lua_state(), numArgs + 1); // remove function and arguments
		return 0;
	}

	int traceback = 0;
	lua_getglobal(lua_wrapper::lua_state(), "__G__TRACKBACK__");                         /* L: ... func arg1 arg2 ... G */
	if (!lua_isfunction(lua_wrapper::lua_state(), -1))
	{
		lua_pop(lua_wrapper::lua_state(), 1);                                            /* L: ... func arg1 arg2 ... */
	}
	else
	{
		lua_insert(lua_wrapper::lua_state(), functionIndex - 1);                         /* L: ... G func arg1 arg2 ... */
		traceback = functionIndex - 1;
	}

	int error = 0;
	error = lua_pcall(lua_wrapper::lua_state(), numArgs, 1, traceback);                  /* L: ... [G] ret */
	if (error)
	{
		if (traceback == 0)
		{
			log_error("[LUA ERROR] %s", lua_tostring(lua_wrapper::lua_state(), -1));        /* L: ... error */
			lua_pop(lua_wrapper::lua_state(), 1); // remove error message from stack
		}
		else                                                            /* L: ... G error */
		{
			lua_pop(lua_wrapper::lua_state(), 2); // remove __G__TRACKBACK__ and error message from stack
		}
		return 0;
	}

	// get return value
	int ret = 0;
	if (lua_isnumber(lua_wrapper::lua_state(), -1))
	{
		ret = (int)lua_tointeger(lua_wrapper::lua_state(), -1);
	}
	else if (lua_isboolean(lua_wrapper::lua_state(), -1))
	{
		ret = (int)lua_toboolean(lua_wrapper::lua_state(), -1);
	}
	// remove return value from stack
	lua_pop(lua_wrapper::lua_state(), 1);                                                /* L: ... [G] */

	if (traceback)
	{
		lua_pop(lua_wrapper::lua_state(), 1); // remove __G__TRACKBACK__ from stack      /* L: ... */
	}

	return ret;
}

static int
execute_service_function(int nHandler, int numArgs) {
	int ret = 0;
	if (push_service_function(nHandler))                                /* L: ... arg1 arg2 ... func */
	{
		if (numArgs > 0)
		{
			lua_insert(lua_wrapper::lua_state(), -(numArgs + 1));                        /* L: ... func arg1 arg2 ... */
		}
		ret = exe_function(numArgs);
	}
	lua_settop(lua_wrapper::lua_state(), 0);
	return ret;
}
#pragma endregion

void
push_proto_message_tolua(const Message* message) {
	lua_State* state = lua_wrapper::lua_state();
	if (!message) {
		// printf("PushProtobuf2LuaTable failed, message is NULL");
		return;
	}
	const Reflection* reflection = message->GetReflection();

	// 顶层table
	lua_newtable(state);

	const Descriptor* descriptor = message->GetDescriptor();
	for (int32_t index = 0; index < descriptor->field_count(); ++index) {
		const FieldDescriptor* fd = descriptor->field(index);
		const std::string& name = fd->name();

		// key
		lua_pushstring(state, name.c_str());

		bool bReapeted = fd->is_repeated();

		if (bReapeted) {
			// repeated这层的table
			lua_newtable(state);
			int size = reflection->FieldSize(*message, fd);
			for (int i = 0; i < size; ++i) {
				char str[32] = { 0 };
				switch (fd->cpp_type()) {
				case FieldDescriptor::CPPTYPE_DOUBLE:
					lua_pushnumber(state, reflection->GetRepeatedDouble(*message, fd, i));
					break;
				case FieldDescriptor::CPPTYPE_FLOAT:
					lua_pushnumber(state, (double)reflection->GetRepeatedFloat(*message, fd, i));
					break;
				case FieldDescriptor::CPPTYPE_INT64:
					sprintf(str, "%lld", (long long)reflection->GetRepeatedInt64(*message, fd, i));
					lua_pushstring(state, str);
					break;
				case FieldDescriptor::CPPTYPE_UINT64:

					sprintf(str, "%llu", (unsigned long long)reflection->GetRepeatedUInt64(*message, fd, i));
					lua_pushstring(state, str);
					break;
				case FieldDescriptor::CPPTYPE_ENUM: // 与int32一样处理
					lua_pushinteger(state, reflection->GetRepeatedEnum(*message, fd, i)->number());
					break;
				case FieldDescriptor::CPPTYPE_INT32:
					lua_pushinteger(state, reflection->GetRepeatedInt32(*message, fd, i));
					break;
				case FieldDescriptor::CPPTYPE_UINT32:
					lua_pushinteger(state, reflection->GetRepeatedUInt32(*message, fd, i));
					break;
				case FieldDescriptor::CPPTYPE_STRING:
				{
					std::string value = reflection->GetRepeatedString(*message, fd, i);
					lua_pushlstring(state, value.c_str(), value.size());
				}
				break;
				case FieldDescriptor::CPPTYPE_BOOL:
					lua_pushboolean(state, reflection->GetRepeatedBool(*message, fd, i));
					break;
				case FieldDescriptor::CPPTYPE_MESSAGE:
					push_proto_message_tolua(&(reflection->GetRepeatedMessage(*message, fd, i)));
					break;
				default:
					break;
				}

				lua_rawseti(state, -2, i + 1); // lua's index start at 1
			}

		}
		else {
			char str[32] = { 0 };
			switch (fd->cpp_type()) {

			case FieldDescriptor::CPPTYPE_DOUBLE:
				lua_pushnumber(state, reflection->GetDouble(*message, fd));
				break;
			case FieldDescriptor::CPPTYPE_FLOAT:
				lua_pushnumber(state, (double)reflection->GetFloat(*message, fd));
				break;
			case FieldDescriptor::CPPTYPE_INT64:

				sprintf(str, "%lld", (long long)reflection->GetInt64(*message, fd));
				lua_pushstring(state, str);
				break;
			case FieldDescriptor::CPPTYPE_UINT64:

				sprintf(str, "%llu", (unsigned long long)reflection->GetUInt64(*message, fd));
				lua_pushstring(state, str);
				break;
			case FieldDescriptor::CPPTYPE_ENUM: // 与int32一样处理
				lua_pushinteger(state, (int)reflection->GetEnum(*message, fd)->number());
				break;
			case FieldDescriptor::CPPTYPE_INT32:
				lua_pushinteger(state, reflection->GetInt32(*message, fd));
				break;
			case FieldDescriptor::CPPTYPE_UINT32:
				lua_pushinteger(state, reflection->GetUInt32(*message, fd));
				break;
			case FieldDescriptor::CPPTYPE_STRING:
			{
				std::string value = reflection->GetString(*message, fd);
				lua_pushlstring(state, value.c_str(), value.size());
			}
			break;
			case FieldDescriptor::CPPTYPE_BOOL:
				lua_pushboolean(state, reflection->GetBool(*message, fd));
				break;
			case FieldDescriptor::CPPTYPE_MESSAGE:
				push_proto_message_tolua(&(reflection->GetMessage(*message, fd)));
				break;
			default:
				break;
			}
		}

		lua_rawset(state, -3);
	}
}

//第一个参数：sType
//第二个参数：表：{OnSessionRecvCmd，OnSessionDisconnected}
static int lua_register_service(lua_State* lua)
{
	if (2 != lua_gettop(lua))
	{
		log_error("函数调用错误");
		return 0;
	}
	auto  serviceType = tolua_tonumber(lua, 1, 0);
	if (!lua_istable(lua, 2))
	{// 如果不是表，直接返回
		lua_pushboolean(lua, 0);
		return 1;
	}

	lua_getfield(lua, 2, "OnSessionRecvCmd");
	lua_getfield(lua, 2, "OnSessionDisconnected");
	lua_getfield(lua, 2, "OnSessionConnected");
	//栈： 3:OnSessionRecvCmd  4:OnSessionDisconnected

	auto recvHandle = save_service_function(lua, 3, 0);
	auto disHandle = save_service_function(lua, 4, 0);
	auto connHandle = save_service_function(lua, 5, 0);

	if (recvHandle || disHandle)
	{// 两个函数不都为零

		auto ls = new LuaService();
		ls->luaDisconnectFuncHandle = disHandle;
		ls->luaRecvCmdPackageHandle = recvHandle;
		ls->luaConnFuncHandle = connHandle;
		ls->luaRecvRawPackageHandle = 0;

		ls->useRawPackage = false;

		auto ret = ServiceManager::RegisterService(serviceType, ls);

		lua_pushboolean(lua, ret ? 1 : 0);
		return 1;
	}
	lua_pushboolean(lua, 0);
	return 1;
}

static int lua_register_raw_service(lua_State* lua)
{
	if (2 != lua_gettop(lua))
	{
		log_error("函数调用错误");
		return 0;
	}
	auto  serviceType = tolua_tonumber(lua, 1, 0);
	if (!lua_istable(lua, 2))
	{// 如果不是表，直接返回
		lua_pushboolean(lua, 0);
		return 1;
	}

	lua_getfield(lua, 2, "OnSessionRecvRaw");
	lua_getfield(lua, 2, "OnSessionDisconnected");
	lua_getfield(lua, 2, "OnSessionConnected");
	//栈： 3:OnSessionRecvCmd  4:OnSessionDisconnected

	auto recvHandle = save_service_function(lua, 3, 0);
	auto disHandle = save_service_function(lua, 4, 0);
	auto connHandle = save_service_function(lua, 5, 0);

	if (recvHandle || disHandle)
	{// 两个函数不都为零

		auto ls = new LuaService();
		ls->luaDisconnectFuncHandle = disHandle;
		ls->luaRecvCmdPackageHandle = 0;
		ls->luaConnFuncHandle = connHandle;
		ls->luaRecvRawPackageHandle = recvHandle;

		ls->useRawPackage = true;

		auto ret = ServiceManager::RegisterService(serviceType, ls);

		lua_pushboolean(lua, ret ? 1 : 0);
		return 1;
	}
	lua_pushboolean(lua, 0);
	return 1;
}

int register_service_export(lua_State* tolua_S)
{
	//初始化service全局表
	init_service_function_map(tolua_S);

	lua_getglobal(tolua_S, "_G");
	if (lua_istable(tolua_S, -1)) {
		tolua_open(tolua_S);
		tolua_module(tolua_S, "Service", 0);
		tolua_beginmodule(tolua_S, "Service");

		tolua_function(tolua_S, "Register", lua_register_service);
		tolua_function(tolua_S, "RegisterRaw", lua_register_raw_service);

		tolua_endmodule(tolua_S);
	}
	lua_pop(tolua_S, 1);
	return 0;
}

#pragma region LuaService
bool LuaService::OnSessionRecvRawPackage(const AbstractSession* session, const RawPackage* package) const
{
	auto lua = lua_wrapper::lua_state();
	tolua_pushuserdata(lua, (void*)session);
	tolua_pushuserdata(lua, (void*)package);

	if (luaRecvRawPackageHandle == 0)
	{
		log_error("OnSessionRecvCmdPackage：luaRecvRawPackageHandle==0");
		return false;
	}
	execute_service_function(this->luaRecvRawPackageHandle, 2);
	return true;
}

bool LuaService::OnSessionRecvCmdPackage(const AbstractSession* session, const CmdPackage* package) const
{
	// 如果是Protobuf就把内容解析后传给Lua，如果是json就不变
	// { 1： serviceType, 2: cmdType, 3 uTag, 4:body }
	auto index = 1;
	auto lua = lua_wrapper::lua_state();
	tolua_pushuserdata(lua, (void*)session);
	lua_newtable(lua);
	lua_pushinteger(lua, package->serviceType);
	lua_rawseti(lua, -2, index++);
	lua_pushinteger(lua, package->cmdType);
	lua_rawseti(lua, -2, index++);
	lua_pushinteger(lua, package->userTag);
	lua_rawseti(lua, -2, index++);

	if (!package->body)
	{
		lua_pushnil(lua);
	}
	else
	{
		switch (CmdPackageProtocol::ProtoType())
		{
		case ProtoType::Json:
			lua_pushstring(lua, (char*)package->body);
			break;
		case ProtoType::Protobuf:
			push_proto_message_tolua((Message*)package->body);
			break;
		}
	}
	lua_rawseti(lua, -2, index++);
	if (luaRecvCmdPackageHandle == 0)
	{
		log_error("OnSessionRecvCmdPackage：luaRecvCmdPackageHandle==0");
		return false;
	}
	execute_service_function(this->luaRecvCmdPackageHandle, 2);
	return true;
}

bool LuaService::OnSessionDisconnected(const AbstractSession* session, const int& serviceType) const
{
	tolua_pushuserdata(lua_wrapper::lua_state(),(void*) session);
	tolua_pushnumber(lua_wrapper::lua_state(), serviceType);

	if (luaDisconnectFuncHandle == 0)
	{
		log_error("OnSessionRecvCmdPackage：luaDisconnectFuncHandle==0");
		return false;
	}
	execute_service_function(this->luaDisconnectFuncHandle, 2);
	return true;
}

void LuaService::OnSessionConnected(const AbstractSession* session, const int& serviceType) const
{
	tolua_pushuserdata(lua_wrapper::lua_state(), (void*)session);
	tolua_pushnumber(lua_wrapper::lua_state(), serviceType);
	if(this->luaConnFuncHandle)
		execute_service_function(this->luaConnFuncHandle, 2);
}

#pragma endregion