Repository: rolandoislas/drc-sim Branch: master Commit: c32586c8fa58 Files: 49 Total size: 111.0 KB Directory structure: gitextract_j_pnps2_/ ├── .gitignore ├── .travis.yml ├── Dockerfile ├── MANIFEST.in ├── drc-info.py ├── drc-sim-backend ├── install.sh ├── license.txt ├── readme.md ├── resources/ │ ├── bin/ │ │ └── drcsimbackend.desktop │ └── config/ │ └── get_psk.conf ├── setup.py ├── src/ │ ├── __init__.py │ └── server/ │ ├── __init__.py │ ├── data/ │ │ ├── __init__.py │ │ ├── args.py │ │ ├── config.py │ │ ├── config_general.py │ │ ├── constants.py │ │ ├── resource.py │ │ └── struct/ │ │ ├── __init__.py │ │ ├── command.py │ │ └── input.py │ ├── ui/ │ │ ├── __init__.py │ │ ├── cli/ │ │ │ ├── __init__.py │ │ │ └── cli_main.py │ │ └── gui/ │ │ ├── __init__.py │ │ ├── frame/ │ │ │ ├── __init__.py │ │ │ ├── frame_about.py │ │ │ ├── frame_get_key.py │ │ │ ├── frame_log.py │ │ │ ├── frame_run_server.py │ │ │ └── frame_tab.py │ │ └── gui_main.py │ └── util/ │ ├── __init__.py │ ├── drc_sim_c.py │ ├── interface_util.py │ ├── logging/ │ │ ├── __init__.py │ │ ├── logger.py │ │ ├── logger_backend.py │ │ ├── logger_cli.py │ │ ├── logger_gui.py │ │ └── logger_wpa.py │ ├── os_util.py │ ├── process_util.py │ ├── status_sending_thread.py │ └── wpa_supplicant.py └── tests/ ├── packets/ │ └── .gitignore └── test_parse.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ __pycache__/ *.pyc .idea/ .pypy/ build/ dist/ *.egg-info/ setup.cfg temp/ .drc-sim/ region_dump.json *.bin .cache/ ================================================ FILE: .travis.yml ================================================ language: python python: - "3.4" - "3.5" - "3.6" dist: trusty sudo: required install: - sudo apt-get -qq update - sudo ./install.sh local before_script: - export PYTHONPATH=$(pwd) script: pytest ================================================ FILE: Dockerfile ================================================ FROM debian:jessie ADD drc-sim-backend /root/ ADD setup.py /root/ ADD src/ /root/src/ ADD resources/ /root/resources/ ADD MANIFEST.in /root/ ADD install.sh /root/ RUN apt-get update \ && cd /root/ \ && ./install.sh local ENV TERM xterm ENTRYPOINT ["drc-sim-backend", "--cli"] CMD ["-h"] ================================================ FILE: MANIFEST.in ================================================ recursive-include src * ================================================ FILE: drc-info.py ================================================ import codecs import json import select import socket import sys import time from threading import Thread from src.server.data.struct import input, command PORT_WII_MSG = 50010 PORT_WII_VID = 50020 PORT_WII_AUD = 50021 PORT_WII_HID = 50022 PORT_WII_CMD = 50023 sock_cmd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock_cmd.bind(("192.168.1.10", PORT_WII_CMD)) sock_msg = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock_msg.bind(("192.168.1.10", PORT_WII_MSG)) sock_hid = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock_hid.bind(("192.168.1.10", PORT_WII_HID)) sock_vid = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock_vid.bind(("192.168.1.10", PORT_WII_VID)) sock_aud = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock_aud.bind(("192.168.1.10", PORT_WII_AUD)) json_dump = {} def print_packet(sock, name): data = sock.recv(2048) print("%s: %s" % (name, codecs.encode(data, "hex").decode())) def print_packet_cmd(sock): data = sock.recv(2048) print("cmd: %s" % codecs.encode(data, "hex").decode()) header = command.header.parse(data) if header.packet_type != 2: # Only accept response packets return size = 8 # header size if header.cmd_id == 1: data_string = codecs.encode(data[size:], "hex").decode() print("cmd 1: %s" % data_string) json_dump["1"] = data_string elif header.cmd_id == 0: data_string = codecs.encode(data[size + command.header_cmd0.sizeof():], "hex").decode() print("cmd 0 %d %d: %s" % (header.id_primary, header.id_secondary, data_string)) if "0" not in json_dump: json_dump["0"] = {} if str(header.id_primary) not in json_dump["0"]: json_dump["0"][str(header.id_primary)] = {} json_dump["0"][str(header.id_primary)][str(header.id_secondary)] = data_string def send_cmd(data): sock_cmd.sendto(data, ("192.168.1.11", PORT_WII_CMD + 100)) def send_command_from_string(command_string, sid): send_data = command.header.parse(codecs.decode(command_string, "hex")) send_data.seq_id = sid if send_data.cmd_id == 1: send_data.mic_enabled = 0 # floods logs with audio data if enabled send_data = command.header.build(send_data) send_cmd(send_data) sid += 1 sid %= 65535 time.sleep(1) return sid def cmd_request(): sid = 0 while True: data = { 0: {0: {0: "000000000c0005087e0115880040000000000000", # TODO construct these 10: "000000000d0005007e0101780040000a0000000100"}, 4: {4: "000000000c0005007e0109780040040400000000", 10: "000000000d0005117e012fc80040040a0000000100", 11: "000000000c0005017e0107180040040b00000000"}, 5: {6: "000000000c0005007e0101a80040050600000000", 12: "00000000110005007e0102f80040050c000000050e0300870f", 16: "0000010030000580010000000000000000000000803e0000000100029e0000000000000070000000404003002d0000" "018000400000000000", # FIXME 0 5 16 is a CMD 1 broadcast - missing actual request 24: "00000000160005007e0101c8004005180000000a54313936333030303030"} }, 1: "000001003000051a010000000000000000000000803e000000010002000000000000000070000000404003002d00000" "10000000000000000" } for cmd in data.keys(): if isinstance(data[cmd], str): print("Sending command %d" % cmd) sid = send_command_from_string(data[cmd], sid) else: for primary_id in data[cmd].keys(): for secondary_id in data[cmd][primary_id].keys(): print("Sending command %d %d %d" % (cmd, primary_id, secondary_id)) sid = send_command_from_string(data[cmd][primary_id][secondary_id], sid) def print_hid(sock): data = sock.recv(2048) input_parsed = input.input_data.parse(data) print(input_parsed) if __name__ == '__main__': hid = len(sys.argv) > 1 and sys.argv[1] == "--hid" if not hid: send_thread = Thread(target=cmd_request) send_thread.daemon = True send_thread.start() while True: try: rlist, wlist, xlist = select.select((sock_cmd, sock_msg, sock_hid, sock_vid, sock_aud), (), (), 1) if rlist: for s in rlist: if s == sock_hid and hid: print_hid(s) if hid: continue if s == sock_aud: print_packet(s, "aud") elif s == sock_vid: print_packet(s, "vid") elif s == sock_cmd: print_packet_cmd(s) elif s == sock_msg: print_packet(s, "msg") except KeyboardInterrupt: if not hid: dump = open("region_dump.json", "w") dump.write(json.dumps(json_dump, indent=4)) dump.close() print("Wrote dump to region_dump.json") sys.exit(0) ================================================ FILE: drc-sim-backend ================================================ #!/usr/bin/env python3 import os import sys from src.server.data import constants from src.server.data.args import Args from src.server.data.config_general import ConfigGeneral from src.server.ui.cli.cli_main import CliMain from src.server.util.logging.logger import Logger from src.server.util.logging.logger_backend import LoggerBackend from src.server.util.logging.logger_cli import LoggerCli from src.server.util.logging.logger_gui import LoggerGui from src.server.util.logging.logger_wpa import LoggerWpa from src.server.util.os_util import OsUtil def init_loggers(): """ Initialize loggers with a specified log level if they have the argument. :return: None """ loggers = (Logger, LoggerBackend, LoggerGui, LoggerCli, LoggerWpa) for logger in loggers: if Args.args.debug: logger.set_level(Logger.DEBUG) elif Args.args.extra: logger.set_level(Logger.EXTRA) elif Args.args.finer: logger.set_level(Logger.FINER) elif Args.args.verbose: logger.set_level(Logger.VERBOSE) else: logger.set_level(Logger.INFO) def start(): """ Main loop. It can be GUI or CLI based on args. Dies if an error makes it way here or main loop stops. :return: None """ ui = None try: if Args.args.cli: Logger.info("Enabling CLI") ui = CliMain() else: Logger.info("Enabling GUI") from src.server.ui.gui.gui_main import GuiMain ui = GuiMain() ui.start() except KeyboardInterrupt: if ui: ui.stop() except Exception as e: if ui: ui.stop() Logger.exception(e) Logger.info("Exiting") def log_level(): """ Log at every level to display the levels that are enabled. :return: None """ # Logger info Logger.debug("Debug logging enabled") Logger.extra("Extra debug logging enabled") Logger.finer("Finer debug logging enabled") Logger.verbose("Verbose logging enabled") if LoggerWpa.get_level() <= Logger.FINER: LoggerWpa.warn("At this log level SSIDs are logged!") def check_root(): """ Exit if not root :return: """ if os.getuid() != 0: Logger.throw("Not running as root!") sys.exit() else: Logger.extra("I am root!") def main(): """ Main entry point. Parses arguments, loads configuration files, initialized loggers and starts the main loop. :return: None """ Args.parse_args() ConfigGeneral.load() ConfigGeneral.save() init_loggers() check_root() Logger.info("Initializing drc-sim-backend version %s", constants.VERSION) Logger.info("Using \"%s\" as home folder.", constants.PATH_ROOT) log_level() OsUtil.log_info(Logger) start() if __name__ == '__main__': main() ================================================ FILE: install.sh ================================================ #!/usr/bin/env bash # drc-sim(-backend): Wii U gamepad emulator. # # drc-sim-backend install script # https://github.com/rolandoislas/drc-sim # # Changelog # # June 1, 2017 - 1.1 # Add output on error for make, cmake, and setup.py # Add setup.py outputs to install.txt during install and it is read from for an uninstall # Move init script, desktop launcher, and icon to setup.py # Add version number # Add pkg-info - wpa_supplicant compile fails without it # Remove virtualenv # June 1, 2017 - 1.1.1 # Fix Make, cmake, and setup.py not returning on errors # Fix current directory not being restored on a git update failure # June 2, 2017 - 1.1.2 # Detect and install to virtualenv # June 3, 2017 - 1.1.3 # Use python3 from virtualenv if found VERSION="1.1.3" REPO_DRC_SIM="https://github.com/rolandoislas/drc-sim.git" REPO_WPA_SUPPLICANT_DRC="https://github.com/rolandoislas/drc-hostap.git" REPO_DRC_SIM_C="https://github.com/rolandoislas/drc-sim-c.git" INSTALL_DIR="/opt/drc_sim/" dependencies=() branch_drc_sim="" # Checks to see if OS has apt-get and sets dependencies # Exits otherwise check_os() { if command -v apt-get &> /dev/null; then echo "Command apt-get found." # Backend dependencies dependencies=("python3" "python3-pip" "net-tools" "wireless-tools" "sysvinit-utils" "psmisc" "rfkill" "isc-dhcp-client" "ifmetric" "python3-tk" "gksu") # Wpa supplicant compile dependencies dependencies+=("git" "libssl-dev" "libnl-genl-3-dev" "gcc" "make" "pkg-config") # DRC Sim Server C++ dependencies+=("libavcodec-dev" "libswscale-dev" "libjpeg-dev" "cmake") else echo "The command apt-get was not found. This OS is not supported." exit 1 fi } # Check to see if the script is running as root # Exits if not root check_root() { if [[ ${EUID} -ne 0 ]]; then echo "Install script must be executed with root privileges." exit 1 fi } # Checks and installs pre-defined decencies array # Exits on failed dependency install_dependencies() { echo "Installing dependencies." for dependency in "${dependencies[@]}" do installed="$(dpkg -s ${dependency} 2>&1)" if [[ ${installed} =~ "Status: install ok installed" ]]; then echo "${dependency} [INSTALLED]" else echo "${dependency} [INSTALLING]" if command apt-get -y install ${dependency} &> /dev/null; then echo "${dependency} [INSTALLED]" else echo "${dependency} [FAILED]" exit 1 fi fi done } # Update git directory while stashing changed return 1 # Returns 1 on failure update_git() { cur_dir="${PWD}" cd "${1}" &> /dev/null || return 1 if [[ -d "${1}" ]]; then echo "Found existing git directory ${1}" if command git stash --include-untracked &> /dev/null; then echo "Stashed git changes" echo "Updating git repo" if command git pull &> /dev/null; then echo "Updated git repo" else cd "${cur_dir}" &> /dev/null || return 1 return 1 fi else cd "${cur_dir}" &> /dev/null || return 1 return 1 fi fi cd "${cur_dir}" &> /dev/null || return 1 return 0 } # Clones a git repo to the install path # If the directory exists it is removed # Param $1: git repo url get_git() { git_dir="${INSTALL_DIR}${2}" if update_git ${git_dir}; then return 0 else # Remove directory for a clean clone if [[ -d "${git_dir}" ]]; then rm -rf "${git_dir}" fi fi # Clone echo "Cloning ${1} into ${git_dir}" if command git clone ${1} ${git_dir} &> /dev/null; then echo "Cloned ${1}" else echo "Failed to clone ${1}" exit 1 fi } # Compiles wpa_supplicant after fetching it from git compile_wpa() { get_git ${REPO_WPA_SUPPLICANT_DRC} "wpa" echo "Compiling wpa_supplicant_drc" compile_dir="${INSTALL_DIR}wpa/wpa_supplicant/" cur_dir="${PWD}" cd "${compile_dir}" &> /dev/null || return 1 cp ../conf/wpa_supplicant.config ./.config &> /dev/null || return 1 compile_log="${compile_dir}make.log" echo "Compile log at ${compile_log}" if ! make &> ${compile_log}; then cat "${compile_log}"; return 1; fi echo "Installing wpa_supplicant_drc and wpa_cli_drc to /usr/local/bin" cp wpa_supplicant /usr/local/bin/wpa_supplicant_drc &> /dev/null || return 1 cp wpa_cli /usr/local/bin/wpa_cli_drc &> /dev/null || return 1 cd "${cur_dir}" &> /dev/null || return 1 return 0 } # Compiles drc_sim_c after fetching it from git compile_drc_sim_c() { get_git ${REPO_DRC_SIM_C} "drc_sim_c" echo "Compiling drc_sim_c" compile_dir="${INSTALL_DIR}drc_sim_c/" cur_dir="${PWD}" cd "${compile_dir}" &> /dev/null || return 1 compile_log="${compile_dir}make.log" cmake_log="${compile_dir}cmake.log" echo "Compile log at ${compile_log}" if ! cmake "${compile_dir}" &> "${cmake_log}"; then cat "${cmake_log}"; return 1; fi if ! make &> "${compile_log}"; then cat "${compile_log}"; return 1; fi echo "Installing drc_sim_c to /usr/local/bin" make install &> /dev/null || return 1 cd "${cur_dir}" &> /dev/null || return 1 return 0 } # Installs drc-sim in a virtualenv install_drc_sim() { echo "Installing DRC Sim Server GUI/CLI Utility" # Paths drc_dir="${INSTALL_DIR}drc/" cur_dir="${PWD}" # Fix virtualenv paths prefix="" python="python3" if [ ! -z "${VIRTUAL_ENV}" ]; then echo "Installing into virtualenv: ${VIRTUAL_ENV}" prefix="--prefix ${VIRTUAL_ENV}" python="${VIRTUAL_ENV}/bin/${python}" fi # Get source if [[ "${branch_drc_sim}" != "local" ]]; then # Get repo get_git ${REPO_DRC_SIM} "drc" else # Copy local if [[ ! -f "${cur_dir}/setup.py" ]]; then echo "Cannot perform local install. Missing source files at ${cur_dir}." return 1 fi if [[ ! -d "${INSTALL_DIR}" ]]; then mkdir "${INSTALL_DIR}" &> /dev/null || return 1 fi rm -rf ${drc_dir} &> /dev/null || return 1 mkdir ${drc_dir} &> /dev/null || return 1 cp -R "${cur_dir}/." "${drc_dir%/*}" &> /dev/null || return 1 fi # Install python dependencies echo "Installing setuptools" ${python} -m pip install setuptools &> /dev/null || return 1 # Remove an existing install of drc-sim echo "Attempting to remove previous installations" ${python} -m pip uninstall -y drcsim &> /dev/null || \ echo "Failed to remove the previous installation. Attempting to install anyway." # Set the directory cd "${drc_dir}" &> /dev/null || return 1 # Branch to checkout if [[ "${branch_drc_sim}" != "local" ]]; then echo "Using branch \"${branch_drc_sim}\" for drc-sim install" git checkout ${branch_drc_sim} &> /dev/null || return 1 else echo "Using current directory as install source" fi # Install echo "Installing drc-sim" echo "Downloading Python packages. This may take a while." if ! ${python} "${drc_dir}setup.py" install ${prefix} --record "${drc_dir}/install.txt" &> \ "/tmp/drc-sim-py-install.log"; then cat "/tmp/drc-sim-py-install.log" return 1 fi cd "${cur_dir}" &> /dev/null || return 1 # Update icon cache update-icon-caches /usr/share/icons/* &> /dev/null || echo "Failed to update icon cache." } # Echos the general info print_info() { echo "Drc-sim installer (script version ${VERSION})" printf "\thttps://github.com/rolandoislas/drc-sim\n" } # Uninstalls DRC Sim then exists uninstall() { drc_install_log="${INSTALL_DIR}drc/install.txt" echo "Uninstalling DRC Sim Server" # Remove setup.py files if [[ -f "${drc_install_log}" ]]; then echo "Files to remove:" cat ${drc_install_log} read -p "Remove these files? [Y/N]" reponse if [[ ${reponse} =~ [Yy](es)* ]]; then tr '\n' '\0' < ${drc_install_log} | xargs -0 sudo rm -f -- echo "Removed Python installed files" else echo "Not removing Python installed files" echo "Install canceled" exit 2 fi else echo "Could not clean Python installed files. Missing ${drc_install_log}" fi # Launcher (.desktop) to_remove=("/usr/share/applications/drc-sim-backend.desktop" "/usr/share/applications/drcsimbackend.desktop" "/usr/share/icons/hicolor/512x512/apps/drcsimbackend.png") for item in "${to_remove[@]}"; do if [[ -f "${item}" ]]; then rm -f ${item} &> /dev/null fi done # Install dir echo "Removing install directory" rm -rf ${INSTALL_DIR} &> /dev/null || echo "Failed to remove install directory." # TODO uninstall packages printf "\nNOT removing package dependencies\n" printf "${dependencies[*]}\n\n" # Done echo "Uninstalled DRC Sim Server" exit 0 } # Parses args check_args() { branch_drc_sim=${1:-master} # Help if [[ "${1}" == "help" ]] || [[ "${1}" == "-h" ]]; then echo "Usage: [argument]" echo " Defaults to install." echo " Arguments:" echo " -h, help : help menu" echo " branch : branch to use for drc-sim (master, develop, local) master is used by default" echo " uninstall : uninstall DRC Sim" exit 1 # Uninstall elif [[ "${1}" == "uninstall" ]]; then uninstall # Install branch elif [[ "${branch_drc_sim}" != "develop" ]] && [[ "${branch_drc_sim}" != "master" ]] && [[ "${branch_drc_sim}" != "local" ]]; then echo "Invalid branch \"${1}\"" check_args "help" fi } # Check if command return value is non-zero and exit with message. # If the command exited with a zero exit value the success message will be echoed pass_fail() { if $1; then echo $2 else echo $3 exit 1 fi } # Echo post install message and exit post_install() { echo "Install finished" echo "\"DRC SIM Server\" will now appear in GUI application menus." echo "It can also be launched via \"drc-sim-backend\"." exit 0 } # Install drc_sim install() { install_dependencies pass_fail compile_wpa "Compiled wpa_supplicant" "Failed to compile wpa_supplicant" pass_fail compile_drc_sim_c "Compiled drc_sim_c" "Failed to compile drc_sim_c" pass_fail install_drc_sim "Installed drc-sim" "Failed to install drc-sim" post_install } main() { print_info check_root check_os check_args "$@" install } main "$@" ================================================ FILE: license.txt ================================================ Copyright (C) 2017 Rolando Islas This file is part of DRC Sim Server. DRC Sim Server is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. DRC Sim Server is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DRC Sim Server. If not, see . --------------------------------------------------------------------------- GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: readme.md ================================================ DRC Sim Server --- Stable: [![Build Status](https://travis-ci.org/rolandoislas/drc-sim.svg?branch=master)](https://travis-ci.org/rolandoislas/drc-sim) Dev: [![Build Status](https://travis-ci.org/rolandoislas/drc-sim.svg?branch=develop)](https://travis-ci.org/rolandoislas/drc-sim) DRC Sim Server is a utility for pairing a computer to a Wii U to emulate a gamepad. It needs a [client] for full functionality. See the [wiki] for more info. # Installation [Installation instructions] are available on the wiki. # Credits [drc-sim] \(original\) by [memahaxx] - The original Python codebase [libdrc documentation] by memahaxx - Gamepad and Wii U software and hardware details [drc-sim-keyboard] by justjake - The readme that got me set up initially # Additional Software [wpa_supplicant] modified by memahaxx [drc_sim_c] drc-sim rewritten in C++ [netifaces] Python network interfaces library [pexpect] Python process interaction library [drc-sim]: https://bitbucket.org/memahaxx/drc-sim [drc-sim-keyboard]: https://github.com/justjake/drc-sim-keyboard [Installation instructions]: https://github.com/rolandoislas/drc-sim/wiki/Install [client]: https://github.com/rolandoislas/drc-sim-client/wiki/Home [wiki]: https://github.com/rolandoislas/drc-sim/wiki/Home [wpa_supplicant]: https://github.com/rolandoislas/drc-hostap [drc_sim_c]: https://github.com/rolandoislas/drc-sim-c [memahaxx]: https://bitbucket.org/memahaxx/ [libdrc documentation]: http://libdrc.org/docs/index.html [netifaces]: https://pypi.python.org/pypi/netifaces [pexpect]: https://pypi.python.org/pypi/pexpect ================================================ FILE: resources/bin/drcsimbackend.desktop ================================================ [Desktop Entry] Version=1.0 Name=DRC SIM Server Comment=Wii U Gamepad Emulator Backend Server Exec=gksu /usr/local/bin/drc-sim-backend Icon=drcsimbackend Terminal=false Type=Application Categories=Game;Network; ================================================ FILE: resources/config/get_psk.conf ================================================ ctrl_interface=/var/run/wpa_supplicant_drc update_config=1 ================================================ FILE: setup.py ================================================ #!/usr/bin/env python3 from distutils.core import setup, Command from setuptools import find_packages from src.server.data import constants class CompileDrcSimC(Command): pass # TODO compile drc_sim_c class CompileWpaSupplicantDrc(Command): pass # TODO compile wpa_supplicant_drc setup(name='drcsim', version=constants.VERSION, description='Wii U gamepad simulator.', install_requires=['netifaces>=0.10.5', 'pexpect>=4.2.1'], packages=find_packages(), include_package_data=True, data_files=[ ('resources/config', [ 'resources/config/get_psk.conf' ]), ('resources/image', [ 'resources/image/clover.gif', 'resources/image/diamond.gif', 'resources/image/heart.gif', 'resources/image/spade.gif', 'resources/image/icon.gif' ]), ('/usr/share/applications', [ 'resources/bin/drcsimbackend.desktop' ]), ('/usr/share/icons/hicolor/512x512/apps', [ 'resources/image/drcsimbackend.png' ]) ], scripts=['drc-sim-backend'], cmdclass={ "compile_drc_sim_c": CompileDrcSimC, "compile_wpa_supplicant_drc": CompileWpaSupplicantDrc } ) ================================================ FILE: src/__init__.py ================================================ ================================================ FILE: src/server/__init__.py ================================================ ================================================ FILE: src/server/data/__init__.py ================================================ ================================================ FILE: src/server/data/args.py ================================================ import argparse import sys from src.server.data import constants class Args: args = None def __init__(self): pass @staticmethod def parse_args(): arg_parser = argparse.ArgumentParser(description="%s provides an easy launcher for drc_sim_c and " "wpa_supplicant_drc" % constants.NAME) # Logging arg_parser.add_argument("-d", "--debug", action="store_const", const=True, default=False, help="debug output") arg_parser.add_argument("-e", "--extra", action="store_const", const=True, default=False, help="extra debug output") arg_parser.add_argument("-f", "--finer", action="store_const", const=True, default=False, help="finer debug output") arg_parser.add_argument("-v", "--verbose", action="store_const", const=True, default=False, help="verbose debug output") arg_parser.add_argument("-c", "--cli", action="store_const", const=True, default=False, help="disable gui") # Disable server arg_parser.add_argument("--disable-server", "--disable_server", action="store_const", const=True, default=False, help="dev: disable packet handling and serving") # CLI args = ["-c", "--cli", "-h", "--help"] found = False for arg in args: if arg in sys.argv: found = True if found: subparsers = arg_parser.add_subparsers() # Run Server run_server = subparsers.add_parser("run_server") run_server.add_argument("wii_u_interface", type=str) run_server.add_argument("normal_interface", type=str) run_server.add_argument("-region", type=str, default="none") # Get Key get_key = subparsers.add_parser("get_key") get_key.add_argument("wii_u_interface", type=str) get_key.add_argument("wps_pin", type=str) Args.args = arg_parser.parse_args() # Add sub arguments Args.args.run_server = False Args.args.get_key = False if "run_server" in sys.argv: Args.args.run_server = True elif "get_key" in sys.argv: Args.args.get_key = True ================================================ FILE: src/server/data/config.py ================================================ import configparser import os class Config: def __init__(self): self.path = "" self.config = configparser.ConfigParser(allow_no_value=True) def load(self, path): self.path = os.path.expanduser(path) self.config.read(self.path) def get_boolean(self, section, option, default, comment=""): try: value = self.config.getboolean(section, option) self.add_value(section, option, value, comment, default=default) return value except (ValueError, configparser.NoSectionError, configparser.NoOptionError): self.add_value(section, option, default, comment, default=default) return default def add_value(self, section, option, value, comment, min_val="", max_val="", default=""): value = str(value) min_val = str(min_val) if min_val else None max_val = str(max_val) if max_val else None default = str(default) if not self.config.has_section(section): self.config.add_section(section) if comment: self.config.set(section, "# " + comment.replace("\n", "\n# ")) comment_str = "min: " + min_val + " " if min_val else "" comment_str += "max: " + max_val + " " if max_val else "" comment_str += "default: " + default + " " if default else "" self.config.set(section, "# " + comment_str) if self.config.has_option(section, option): self.config.remove_option(section, option) self.config.set(section, option, value) def get_float(self, section, option, min_val, max_val, default, comment=""): try: value = self.config.getfloat(section, option) self.add_value(section, option, value, comment, min_val, max_val, default) return self.get_min_max(value, min_val, max_val) except (ValueError, configparser.NoSectionError, configparser.NoOptionError): self.add_value(section, option, default, comment, min_val, max_val, default) return default def get_int(self, section, option, min_val, max_val, default, comment=""): try: value = self.config.getint(section, option) self.add_value(section, option, value, comment, min_val, max_val, default) return self.get_min_max(value, min_val, max_val) except (ValueError, configparser.NoSectionError, configparser.NoOptionError): self.add_value(section, option, default, comment, min_val, max_val, default) return default @staticmethod def get_min_max(value, min_val, max_val): if min_val and value < min_val: return min_val elif max_val and value > max_val: return max_val else: return value def save(self): path_parts = os.path.split(self.path) path = "" for index in range(0, len(path_parts)): if index < len(path_parts) - 1: path += os.path.sep + path_parts[index] if os.name == "nt": path = path.replace(os.path.sep, "", 1) path = os.path.abspath(path) if not os.path.exists(path): os.makedirs(path) file_out = open(self.path, "w") self.config.write(file_out) file_out.close() ================================================ FILE: src/server/data/config_general.py ================================================ from src.server.data.config import Config class ConfigGeneral: scan_timeout = None config = Config() stream_audio = None input_delay = None video_quality = None stream_video = None def __init__(self): pass @classmethod def load(cls): cls.config.load("~/.drc-sim/server.conf") # Audio cls.stream_audio = cls.config.get_boolean("AUDIO", "stream", True, "Stream audio to clients") # Input cls.input_delay = cls.config.get_int("INPUT", "delay", 0, 1000, 100, "Amount of time in milliseconds to send " "input to the Wii U") # Video cls.video_quality = cls.config.get_int("VIDEO", "quality", 0, 100, 75, "Quality of video stream.\n" "5/10/15 low - 75 lan - 100 loopback\n" "There is latency at 100.") cls.stream_video = cls.config.get_boolean("VIDEO", "stream", True, "Stream video to clients") # General cls.scan_timeout = cls.config.get_int("GENERAL", "scan_timeout", 0, 60 * 5, 60 * 2, "Sets the time " "allowed to scan for the " "Wii U") @classmethod def save(cls): cls.config.save() ================================================ FILE: src/server/data/constants.py ================================================ import os # Info VERSION = "2.0" NAME = "DRC SIM Server" # Paths PATH_ROOT = os.path.expanduser("~/.drc-sim/") PATH_LOG_DIR = os.path.join(PATH_ROOT, "log/") PATH_CONF_CONNECT = os.path.join(PATH_ROOT, "connect_to_wii_u.conf") PATH_LOG_WPA = os.path.join(PATH_LOG_DIR, "wpa_supplicant_drc.log") PATH_CONF_NETWORK_MANAGER = "/etc/NetworkManager/NetworkManager.conf" PATH_TMP = "/tmp/drc-sim/" PATH_CONF_CONNECT_TMP = os.path.join(PATH_TMP, "get_psk.conf") PATH_LOG_DRC_SIM_C = os.path.join(PATH_LOG_DIR, "drc_sim_c.log") ================================================ FILE: src/server/data/resource.py ================================================ import os import pkg_resources import sys from src.server.util.logging.logger import Logger def join(*args): return os.path.join(*args) class Resource: def __init__(self, in_path): pre = "resources/" Logger.debug("Loading resource \"%s\"", join(pre, in_path)) current_dir = os.path.dirname(__file__).split(os.sep) # Check local files first file_path = "/" if len(current_dir) >= 3: for path in range(0, len(current_dir) - 3): file_path = join(file_path, current_dir[path]) file_path = join(file_path, pre, in_path) if os.path.exists(file_path): try: with open(file_path) as f: self.resource = f.read() except UnicodeDecodeError: Logger.debug("Opening resource as binary.") with open(file_path, "rb") as f: self.resource = f.read() Logger.extra("Found resource in local resource directory.") return # Check /usr/local - pip installs to here file_path = "/usr/local/resources/" + in_path if os.path.exists(file_path): try: with open(file_path) as f: self.resource = f.read() except UnicodeDecodeError: Logger.debug("Opening resource as binary.") with open(file_path, "rb") as f: self.resource = f.read() Logger.extra("Found resource in /usr/local/ resource directory.") return # Attempt to get from package - setup.py installs here try: self.resource = pkg_resources.resource_string(pkg_resources.Requirement.parse("drcsim"), join(pre, in_path)) Logger.extra("Found resource in package.") except FileNotFoundError: Logger.throw("Could not find resource: %s" % join(pre, in_path)) sys.exit() ================================================ FILE: src/server/data/struct/__init__.py ================================================ ================================================ FILE: src/server/data/struct/command.py ================================================ import construct header_cmd0 = construct.Struct( 'magic' / construct.Int8ub, 'unk_0' / construct.Int8ub, 'unk_1' / construct.Int8ub, 'unk_2' / construct.Int8ub, 'unk_3' / construct.Int8ub, 'flags' / construct.Int8ub, 'id_primary' / construct.Int8ub, 'id_secondary' / construct.Int8ub, 'error_code' / construct.Int16ub, 'payload_size_cmd0' / construct.Int16ub ) header_cmd1 = construct.Struct( "f1" / construct.Int8ub, "unknown_0" / construct.Int16ub, "f3" / construct.Int8ub, "mic_enabled" / construct.Int8ub, "mic_mute" / construct.Int8ub, "mic_volume" / construct.Int16ub, "mic_volume_2" / construct.Int16ub, "unknown_a" / construct.Int8ub, "unknown_b" / construct.Int8ub, "mic_freq" / construct.Int16ub, "cam_enable" / construct.Int8ub, "cam_power" / construct.Int8ub, "cam_power_freq" / construct.Int8ub, "cam_auto_expo" / construct.Int8ub, "cam_expo_abs" / construct.Int32ub, "cam_brightness" / construct.Int16ub, "cam_contrast" / construct.Int16ub, "cam_gain" / construct.Int16ub, "cam_hue" / construct.Int16ub, "cam_saturation" / construct.Int16ub, "cam_sharpness" / construct.Int16ub, "cam_gamma" / construct.Int16ub, "cam_key_frame" / construct.Int8ub, "cam_white_balance_auto" / construct.Int8ub, "cam_white_balance" / construct.Int32ub, "cam_multiplier" / construct.Int16ub, "cam_multiplier_limit" / construct.Int16ub, construct.Padding(2) ) header_cmd2 = construct.Struct( 'JDN_base' / construct.Int16ul, construct.Padding(2), 'seconds' / construct.Int32ul ) header = construct.Struct( 'packet_type' / construct.Int16ul, 'cmd_id' / construct.Int16ul, 'payload_size' / construct.Int16ul, 'seq_id' / construct.Int16ul, construct.Embedded( construct.Switch(lambda ctx: ctx.cmd_id, { 0: construct.If( lambda ctx: ctx.payload_size >= header_cmd0.sizeof(), header_cmd0), 1: construct.If( lambda ctx: ctx.payload_size == header_cmd1.sizeof(), header_cmd1), 2: construct.If( lambda ctx: ctx.payload_size == header_cmd2.sizeof(), header_cmd2) }, default=construct.Pass ) ) ) ================================================ FILE: src/server/data/struct/input.py ================================================ import construct accelerometer_data = construct.Struct( "accel_x" / construct.Int16sl, "accel_y" / construct.Int16sl, "accel_z" / construct.Int16sl ) gyroscope_data = construct.BitStruct( "gyro_roll" / construct.BitsInteger(24), "gyro_yaw" / construct.BitsInteger(24), "gyro_pitch" / construct.BitsInteger(24) ) magnet_data = construct.Struct( construct.Padding(6) ) touchscreen_coords_data = construct.BitStruct( "touch_pad" / construct.Bit, "touch_extra" / construct.BitsInteger(3), "touch_value" / construct.BitsInteger(12) ) touchscreen_points_data = construct.Struct( "coords" / construct.Array(2, touchscreen_coords_data) ) touchscreen_data = construct.Struct( "points" / construct.Array(10, touchscreen_points_data) ) input_data = construct.Struct( "sequence_id" / construct.Int16ub, "buttons" / construct.Int16ub, "power_status" / construct.Int8ub, "battery_charge" / construct.Int8ub, "left_stick_x" / construct.Int16ub, "left_stick_y" / construct.Int16ub, "right_stick_x" / construct.Int16ub, "right_stick_y" / construct.Int16ub, "audio_volume" / construct.Int8ub, construct.Embedded(accelerometer_data), construct.Embedded(gyroscope_data), construct.Embedded(magnet_data), construct.Embedded(touchscreen_data), "unkown_0" / construct.BytesInteger(4), "extra_buttons" / construct.Int8ub, "unknown_1" / construct.BytesInteger(46), "fw_version_neg" / construct.Int8ub ) ================================================ FILE: src/server/ui/__init__.py ================================================ ================================================ FILE: src/server/ui/cli/__init__.py ================================================ ================================================ FILE: src/server/ui/cli/cli_main.py ================================================ import os import time from src.server.data import constants from src.server.data.args import Args from src.server.data.resource import Resource from src.server.util.drc_sim_c import DrcSimC from src.server.util.interface_util import InterfaceUtil from src.server.util.logging.logger_cli import LoggerCli from src.server.util.process_util import ProcessUtil from src.server.util.wpa_supplicant import WpaSupplicant class CliMain: def __init__(self): self.getting_key = False self.drc_sim_c = None self.wpa_supplicant = None def start(self): if Args.args.run_server: self.run_server() elif Args.args.get_key: self.get_key() else: self.stop() def stop(self): LoggerCli.info("Stopping") ProcessUtil.call(["killall", "dhclient"]) self.getting_key = False if self.drc_sim_c: self.drc_sim_c.stop() if self.wpa_supplicant: self.wpa_supplicant.stop() def run_server(self): LoggerCli.info("Starting server") normal_interface = Args.args.normal_interface wii_u_interface = Args.args.wii_u_interface self.check_interfaces(normal_interface, wii_u_interface) self.prompt_unmanaged(wii_u_interface) self.wpa_supplicant = WpaSupplicant() self.wpa_supplicant.connect(constants.PATH_CONF_CONNECT, wii_u_interface) self.wpa_supplicant.add_status_change_listener(self.status_changed) InterfaceUtil.dhclient(wii_u_interface) InterfaceUtil.set_metric(normal_interface, 0) InterfaceUtil.set_metric(wii_u_interface, 1) self.drc_sim_c = DrcSimC() self.drc_sim_c.set_region(Args.args.region) self.drc_sim_c.add_status_change_listener(self.drc_sim_c_status_changed) self.drc_sim_c.start() while self.drc_sim_c.running: time.sleep(1) def drc_sim_c_status_changed(self, status): if status == DrcSimC.STOPPED: self.stop() @staticmethod def check_interfaces(normal_interface, wii_u_interface): if normal_interface == wii_u_interface: LoggerCli.throw(Exception("The Wii U and normal interfaces cannot be the same.")) try: InterfaceUtil.get_mac(normal_interface) InterfaceUtil.get_mac(wii_u_interface) except ValueError: LoggerCli.throw(Exception("Invalid interface selected.")) def status_changed(self, status): LoggerCli.info("Connection status changed to %s.", status) if status in (WpaSupplicant.TERMINATED, WpaSupplicant.NOT_FOUND, WpaSupplicant.DISCONNECTED, WpaSupplicant.FAILED_START): self.stop() def status_changed_key(self, status): LoggerCli.info("Connection status changed to %s.", status) if status == WpaSupplicant.DISCONNECTED: LoggerCli.info("Successfully received PSK from the Wii U.") self.stop() elif status in (WpaSupplicant.TERMINATED, WpaSupplicant.NOT_FOUND, WpaSupplicant.FAILED_START): self.stop() def get_key(self): LoggerCli.info("Getting key") wii_u_interface = Args.args.wii_u_interface try: InterfaceUtil.get_mac(wii_u_interface) except ValueError: LoggerCli.throw(Exception("Invalid interface selected.")) if len(Args.args.wps_pin) != 4: LoggerCli.throw(Exception("WPS PIN should be 4 digits")) self.prompt_unmanaged(wii_u_interface) self.create_temp_config_file() self.wpa_supplicant = WpaSupplicant() self.wpa_supplicant.get_psk(constants.PATH_CONF_CONNECT_TMP, wii_u_interface, Args.args.wps_pin) self.wpa_supplicant.add_status_change_listener(self.status_changed_key) self.getting_key = True while self.getting_key: time.sleep(1) @staticmethod def prompt_unmanaged(interface): if not InterfaceUtil.is_managed_by_network_manager(interface): return LoggerCli.info("The interface \"%s\" is managed by Network Manager. It must be set to " "unmanaged to function with DRC Sim. Network manager will not be able to " "use this interface after it is set to unmanaged.", interface) response = input("Set %s as unmanaged? (y/n)" % interface) LoggerCli.debug(response) if response in ("y", "yes", "Y", "Yes", "YES"): InterfaceUtil.set_unmanaged_by_network_manager(interface) else: LoggerCli.throw(Exception("Interface is managed by Network Manager.")) @classmethod def create_temp_config_file(cls): if not os.path.exists(constants.PATH_TMP): os.mkdir(constants.PATH_TMP) tmp_conf = open(constants.PATH_CONF_CONNECT_TMP, "w") tmp_conf.write(Resource("config/get_psk.conf").resource) tmp_conf.close() ================================================ FILE: src/server/ui/gui/__init__.py ================================================ ================================================ FILE: src/server/ui/gui/frame/__init__.py ================================================ ================================================ FILE: src/server/ui/gui/frame/frame_about.py ================================================ from tkinter import Label from src.server.data import constants from src.server.ui.gui.frame.frame_tab import FrameTab class FrameAbout(FrameTab): def __init__(self, master=None, **kw): super().__init__(master, **kw) self.text_name = Label(self, text=constants.NAME) self.text_version = Label(self, text="v" + constants.VERSION) self.text_license = Label(self, text="%s is free software: you can\n" % constants.NAME + "redistribute it and/or modify it under the\n" "terms of the GNU General Public License as\n" "published by the Free Software Foundation,\n" "either version 2 of the License, or\n" "(at your option) any later version." ) self.text_name.grid(row=0, column=0) self.text_version.grid(row=1, column=0) self.text_license.grid(row=2, column=0) self.grid_columnconfigure(0, weight=1) def activate(self): pass def deactivate(self): pass def kill_other_tabs(self): return False ================================================ FILE: src/server/ui/gui/frame/frame_get_key.py ================================================ from tkinter import PhotoImage, Button, END, messagebox from tkinter.ttk import Entry, Combobox, Label from src.server.data import constants from src.server.data.resource import Resource from src.server.ui.cli.cli_main import CliMain from src.server.ui.gui.frame.frame_tab import FrameTab from src.server.util.interface_util import InterfaceUtil from src.server.util.logging.logger_gui import LoggerGui from src.server.util.wpa_supplicant import WpaSupplicant class FrameGetKey(FrameTab): def __init__(self, master=None, **kw): FrameTab.__init__(self, master, **kw) self.wpa_supplicant = None self.getting_psk = False # Widgets button_size = 50 # Spade self.button_spade = Button(self, width=button_size, height=button_size) self.button_spade.image = self.get_image("image/spade.gif", button_size, button_size) self.button_spade.config(image=self.button_spade.image) self.button_spade.number = 0 # Heart self.button_heart = Button(self, width=button_size, height=button_size) self.button_heart.image = self.get_image("image/heart.gif", button_size, button_size) self.button_heart.config(image=self.button_heart.image) self.button_heart.number = 1 # Diamond self.button_diamond = Button(self, width=button_size, height=button_size) self.button_diamond.image = self.get_image("image/diamond.gif", button_size, button_size) self.button_diamond.config(image=self.button_diamond.image) self.button_diamond.number = 2 # Clover self.button_clover = Button(self, width=button_size, height=button_size) self.button_clover.image = self.get_image("image/clover.gif", button_size, button_size) self.button_clover.config(image=self.button_clover.image) self.button_clover.number = 3 # Delete self.button_delete = Button(self, text="Delete") # Code self.entry_pair_code = Entry(self, state="readonly") # Status Message self.status_message = Label(self, state="readonly") # interface dropdown self.dropdown_wii_u = Combobox(self, state="readonly") # Events self.button_spade.bind("", self.button_clicked) self.button_heart.bind("", self.button_clicked) self.button_diamond.bind("", self.button_clicked) self.button_clover.bind("", self.button_clicked) self.button_delete.bind("", self.button_delete_clicked) # Grid self.button_spade.grid(column=0, row=0) self.button_heart.grid(column=1, row=0) self.button_diamond.grid(column=2, row=0) self.button_clover.grid(column=3, row=0) self.button_delete.grid(column=4, row=0) self.entry_pair_code.grid(column=0, row=1, columnspan=5) self.status_message.grid(column=0, row=3, columnspan=5) self.dropdown_wii_u.grid(column=0, row=2, columnspan=5) # noinspection PyUnusedLocal def button_delete_clicked(self, event): self.set_code_text(self.entry_pair_code.get()[:len(self.entry_pair_code.get()) - 1]) def button_clicked(self, event): if self.getting_psk: messagebox.showerror("Running", "A pairing attempt is already im progress.") return number = str(event.widget.number) LoggerGui.debug("A suit button was clicked") # Don't log numbers as the code can be derived from that code = self.entry_pair_code.get() code += number self.set_code_text(code) wii_u_interface = self.dropdown_wii_u.get() if not wii_u_interface: messagebox.showerror("No Interface", "An interface must be selected.") self.activate() return try: InterfaceUtil.get_mac(wii_u_interface) except ValueError: messagebox.showerror("Interface Error", "The selected Interface is no longer available.") self.activate() return if InterfaceUtil.is_managed_by_network_manager(wii_u_interface): set_unmanaged = messagebox.askokcancel( "Managed Interface", "This interface is managed by Network Manager. To use it with DRC Sim it needs " "to be set to unmanaged. Network Manager will not be able to control the interface" " after this.\nSet %s to unmanaged?" % wii_u_interface) if set_unmanaged: InterfaceUtil.set_unmanaged_by_network_manager(wii_u_interface) else: messagebox.showerror("Managed Interface", "Selected Wii U interface is managed by Network Manager.") self.activate() return if len(code) == 4: self.getting_psk = True self.set_code_text("") self.get_psk(code, wii_u_interface) def get_psk(self, code, interface): LoggerGui.debug("Attempting to get PSK") # Don't log code CliMain.create_temp_config_file() self.wpa_supplicant = WpaSupplicant() self.wpa_supplicant.add_status_change_listener(self.wpa_status_changed) self.wpa_supplicant.get_psk(constants.PATH_CONF_CONNECT_TMP, interface, code) def wpa_status_changed(self, status): LoggerGui.debug("Wpa status changed to %s", status) if status == WpaSupplicant.NOT_FOUND: self.deactivate() self.activate() messagebox.showerror("Scan", "No Wii U found.") elif status == WpaSupplicant.TERMINATED: self.deactivate() self.activate() messagebox.showerror("Auth Fail", "Could not authenticate. Check the entered PIN.") elif status == WpaSupplicant.FAILED_START: self.deactivate() self.activate() messagebox.showerror("Error", "An unexpected error occurred.") elif status == WpaSupplicant.DISCONNECTED: self.deactivate() self.activate() messagebox.showerror("Auth Saved", "Successfully paired with Wii U.") elif status == WpaSupplicant.SCANNING: self.status_message["text"] = "Scanning" elif status == WpaSupplicant.CONNECTING: self.status_message["text"] = "Connecting" def activate(self): LoggerGui.debug("FrameTab activate called") self.getting_psk = False self.set_code_text("") if not self.wpa_supplicant or not self.wpa_supplicant.get_status(): self.status_message["text"] = "" self.dropdown_wii_u["values"] = InterfaceUtil.get_wiiu_compatible_interfaces() def deactivate(self): LoggerGui.debug("FrameTab deactivate called") self.status_message["text"] = "" self.getting_psk = False if self.wpa_supplicant: self.wpa_supplicant.stop() self.wpa_supplicant = None @staticmethod def get_image(location, width, height): image = PhotoImage(data=Resource(location).resource) orig_width = image.width() orig_height = image.height() image = image.zoom(width, height) image = image.subsample(orig_width, orig_height) return image def set_code_text(self, text): self.entry_pair_code.config(state="normal") self.entry_pair_code.delete(0, END) self.entry_pair_code.insert(0, text) self.entry_pair_code.config(state="readonly") def kill_other_tabs(self): return True ================================================ FILE: src/server/ui/gui/frame/frame_log.py ================================================ import os import subprocess from tkinter import Button, CENTER, messagebox from src.server.data import constants from src.server.ui.gui.frame.frame_tab import FrameTab class FrameLog(FrameTab): def __init__(self, master=None, **kw): super().__init__(master, **kw) self.button_log = Button(self, text="Open Log in Console") self.button_log.bind("", self.button_clicked) self.button_log.place(relx=0.5, rely=0.5, anchor=CENTER) self.log = None # noinspection PyUnusedLocal def button_clicked(self, event): tail = ["x-terminal-emulator", "-e", "tail", "-f"] for file in ("drcsim", "cli", "gui", "wpa", "backend", "drc_sim_c"): tail.append(os.path.join(constants.PATH_LOG_DIR, file + ".log")) self.deactivate() try: self.log = subprocess.Popen(tail, stdout=open(os.devnull, "w"), stderr=subprocess.PIPE) except FileNotFoundError: messagebox.showerror("Log Error", "Could not open log window.") def activate(self): pass def deactivate(self): pass def kill_other_tabs(self): return False ================================================ FILE: src/server/ui/gui/frame/frame_run_server.py ================================================ import os from tkinter import messagebox from tkinter.ttk import Label, Button, Combobox from src.server.data import constants from src.server.ui.gui.frame.frame_tab import FrameTab from src.server.util.drc_sim_c import DrcSimC from src.server.util.interface_util import InterfaceUtil from src.server.util.logging.logger_gui import LoggerGui from src.server.util.wpa_supplicant import WpaSupplicant class FrameRunServer(FrameTab): def __init__(self, master=None, **kw): """ GUI tab that handles interface and region selection and starting the server. :param master: root window :param kw: args """ FrameTab.__init__(self, master, **kw) self.wii_u_interface = None self.normal_interface = None self.drc_sim_c = None self.wpa_supplicant = None LoggerGui.extra("Initializing FrameRunServer") # Create Widgets self.label_wpa = Label(self, text="Wii U Connection:") self.label_backend = Label(self, text="Server Status:") self.label_wpa_status = Label(self) self.label_backend_status = Label(self) self.button_start = Button(self, text="Start") self.button_stop = Button(self, text="Stop") self.label_wiiu_interface = Label(self, text="Wii U Interface") self.label_normal_interface = Label(self, text="Normal Interface") self.dropdown_wiiu_interface = Combobox(self, state="readonly") self.dropdown_normal_interface = Combobox(self, state="readonly") self.label_interface_info = Label(self) self.label_region = Label(self, text="Region") self.dropdown_region = Combobox(self, state="readonly") # Events self.button_start.bind("", self.start_server) self.button_stop.bind("", self.stop_server) # Position widgets self.label_wpa.grid(column=0, row=0, sticky="e") self.label_backend.grid(column=0, row=1, sticky="e") self.label_wpa_status.grid(column=1, row=0, sticky="w") self.label_backend_status.grid(column=1, row=1, sticky="w") self.label_wiiu_interface.grid(column=0, row=2) self.label_normal_interface.grid(column=0, row=3) self.dropdown_wiiu_interface.grid(column=1, row=2, columnspan=2) self.dropdown_normal_interface.grid(column=1, row=3, columnspan=2) self.label_region.grid(column=0, row=4) self.dropdown_region.grid(column=1, row=4, columnspan=2) self.button_start.grid(column=1, row=5) self.button_stop.grid(column=2, row=5) self.label_interface_info.grid(column=0, row=6, columnspan=3) LoggerGui.extra("Initialized FrameRunServer") def start_server(self, event=None): """ Try to start wpa_supplicant and connect to a Wii U. :param event: Determines if this was a user initiated start. :return: None """ if event: LoggerGui.debug("User clicked start server button") LoggerGui.debug("Start server called") if self.label_backend_status["text"] != DrcSimC.STOPPED and \ (self.label_wpa_status["text"] not in (WpaSupplicant.DISCONNECTED, WpaSupplicant.TERMINATED)): messagebox.showerror("Running", "Server is already running") return if not os.path.exists(constants.PATH_CONF_CONNECT): messagebox.showerror("Auth Error", "No auth details found. Use the \"Get Key\" tab to pair with a Wii U.") self.activate() return self.normal_interface = self.dropdown_normal_interface.get() self.wii_u_interface = self.dropdown_wiiu_interface.get() if not self.normal_interface or not self.wii_u_interface: messagebox.showerror("Interface Error", "Two interfaces need to be selected.") self.activate() return if self.normal_interface == self.wii_u_interface: messagebox.showerror("Interface Error", "The selected normal and Wii U interfaces must be different.") self.activate() return try: InterfaceUtil.get_mac(self.normal_interface) InterfaceUtil.get_mac(self.wii_u_interface) except ValueError: messagebox.showerror("Interface Error", "The selected Interface is no longer available.") self.activate() return if InterfaceUtil.is_managed_by_network_manager(self.wii_u_interface): set_unmanaged = messagebox.askokcancel( "Managed Interface", "This interface is managed by Network Manager. To use it with DRC Sim it needs " "to be set to unmanaged. Network Manager will not be able to control the interface" " after this.\nSet %s to unmanaged?" % self.wii_u_interface) if set_unmanaged: InterfaceUtil.set_unmanaged_by_network_manager(self.wii_u_interface) else: messagebox.showerror("Managed Interface", "Selected Wii U interface is managed by Network Manager.") self.activate() return LoggerGui.debug("Starting wpa supplicant") self.wpa_supplicant = WpaSupplicant() self.wpa_supplicant.add_status_change_listener(self.wpa_status_changed) self.wpa_supplicant.connect(constants.PATH_CONF_CONNECT, self.wii_u_interface) self.label_backend_status.config(text="WAITING") def wpa_status_changed(self, status): """ Handles wpa status changes. Initializes backend server if a connection is made. :param status: status message :return: None """ LoggerGui.debug("Wpa changed status to %s", status) self.label_wpa_status.config(text=status) if status == WpaSupplicant.CONNECTED: LoggerGui.debug("Routing") InterfaceUtil.dhclient(self.wii_u_interface) InterfaceUtil.set_metric(self.normal_interface, 0) InterfaceUtil.set_metric(self.wii_u_interface, 1) LoggerGui.debug("Starting backend") self.drc_sim_c = DrcSimC() self.drc_sim_c.add_status_change_listener(self.backend_status_changed) self.drc_sim_c.set_region(self.dropdown_region.get()) self.drc_sim_c.start() self.label_interface_info.config(text="Server IP: " + InterfaceUtil.get_ip(self.normal_interface) + "\n" + os.uname()[1]) elif status in (WpaSupplicant.DISCONNECTED, WpaSupplicant.TERMINATED): self.stop_server() elif status == WpaSupplicant.NOT_FOUND: self.stop_server() messagebox.showerror("Scan Error", "No Wii U found.") elif status == WpaSupplicant.FAILED_START: self.stop_server() messagebox.showerror("Cannot Connect", "Failed to start wpa_supplicant_drc. This could mean there is a " "configuration error or wpa_supplicant_drc is not installed. " "Check %s for details." % constants.PATH_LOG_WPA) def backend_status_changed(self, status): """ Handles backend status changes. :param status: status message :return: None """ LoggerGui.debug("Backend status changed to %s", status) self.label_backend_status.config(text=status) if status == DrcSimC.STOPPED: self.stop_server() def stop_server(self, event=None): """ Stops active threads. :param event: Determines if this is a user initiated stop :return: None """ if event: LoggerGui.debug("User clicked stop server button") LoggerGui.debug("Stop server called") if event and (self.label_wpa_status["text"] in (WpaSupplicant.DISCONNECTED, WpaSupplicant.TERMINATED) and self.label_backend_status["text"] == DrcSimC.STOPPED): messagebox.showerror("Stop", "Server is not running.") return if self.drc_sim_c: self.drc_sim_c.stop() self.drc_sim_c = None if self.wpa_supplicant: self.wpa_supplicant.stop() self.wpa_supplicant = None self.activate() def activate(self): """ Initializes the frame. :return: None """ LoggerGui.debug("FrameRunServer activated") self.dropdown_wiiu_interface["values"] = InterfaceUtil.get_wiiu_compatible_interfaces() self.dropdown_normal_interface["values"] = InterfaceUtil.get_all_interfaces() self.dropdown_region["values"] = ["NONE", "NA"] self.label_wpa_status["text"] = self.wpa_supplicant.get_status() \ if self.wpa_supplicant and self.wpa_supplicant.get_status() else WpaSupplicant.DISCONNECTED self.label_backend_status["text"] = self.drc_sim_c.get_status() \ if self.drc_sim_c and self.drc_sim_c.get_status() else DrcSimC.STOPPED self.button_start.config(state="normal") self.button_stop.config(state="normal") self.label_interface_info.config(text="") def deactivate(self): """ De-initializes the frame. :return: None """ LoggerGui.debug("FrameRunServer deactivated") self.stop_server() def kill_other_tabs(self): return True ================================================ FILE: src/server/ui/gui/frame/frame_tab.py ================================================ from tkinter.ttk import Frame class FrameTab(Frame): def __init__(self, master=None, **kw): Frame.__init__(self, master, **kw) def activate(self): raise NotImplementedError() def deactivate(self): raise NotImplementedError() def kill_other_tabs(self): raise NotImplementedError() ================================================ FILE: src/server/ui/gui/gui_main.py ================================================ import tkinter from tkinter.ttk import Notebook from src.server.data.resource import Resource from src.server.ui.gui.frame.frame_about import FrameAbout from src.server.ui.gui.frame.frame_get_key import FrameGetKey from src.server.ui.gui.frame.frame_log import FrameLog from src.server.ui.gui.frame.frame_run_server import FrameRunServer from src.server.util.logging.logger_gui import LoggerGui class GuiMain: def __init__(self): """ Main Gui Entrance """ tkinter.Tk.report_callback_exception = self.throw # Main window self.destroyed = False LoggerGui.info("Initializing GUI") self.main_window = tkinter.Tk() self.main_window.wm_title("DRC Sim Server") icon = tkinter.PhotoImage(data=Resource("image/icon.gif").resource) self.main_window.tk.call("wm", "iconphoto", self.main_window, icon) self.main_window.protocol("WM_DELETE_WINDOW", self.on_closing) self.main_window.resizable(False, False) # Notebook self.tab_id = None self.notebook = Notebook(self.main_window, width=300, height=150) self.notebook.grid(column=0, row=0) self.notebook.bind("<>", self.on_tab_changed) # Run Server Frame self.frame_run_server = FrameRunServer(self.notebook) self.notebook.add(self.frame_run_server, text="Run Server") # Get Key Frame self.frame_get_key = FrameGetKey(self.notebook) self.notebook.add(self.frame_get_key, text="Get Key") # Log Frame self.frame_log = FrameLog(self.notebook) self.notebook.add(self.frame_log, text="Log") # About Frame self.frame_about = FrameAbout(self.notebook) self.notebook.add(self.frame_about, text="About") @staticmethod def throw(*args): """ Throw exceptions from Tkinter :param args: arguments :return: None """ for arg in args: if isinstance(arg, Exception): LoggerGui.throw(arg) def after(self): """ Empty loop to catch KeyboardInterrupt :return: None """ self.main_window.after(1000, self.after) def start(self): """ Start the main window loop :return: """ LoggerGui.info("Opening GUI") self.after() self.main_window.mainloop() LoggerGui.info("GUI Closed") def stop(self): """ Convenience function to call on_closing() :return: None """ self.on_closing() def on_closing(self): """ Close the main window and current tab :return: None """ if self.destroyed: return self.destroyed = True LoggerGui.info("Closing GUI") if self.tab_id in self.notebook.children: self.notebook.children[self.tab_id].deactivate() try: self.main_window.destroy() except Exception as e: LoggerGui.exception(e) # noinspection PyUnusedLocal def on_tab_changed(self, event): """ Close the previous tab and initialize a new one :param event: tab event :return: None """ tab_id = self.notebook.select() tab_index = self.notebook.index(tab_id) tab_name = self.notebook.tab(tab_index, "text") LoggerGui.debug("Notebook tab changed to \"%s\" with id %d", tab_name, tab_index) self.tab_id = tab_id.split(".")[len(tab_id.split(".")) - 1] # Parse notebook/tab id to only tab id if self.notebook.children[self.tab_id].kill_other_tabs(): for tab in self.notebook.children: if tab != self.tab_id: self.notebook.children[tab].deactivate() self.notebook.children[self.tab_id].activate() ================================================ FILE: src/server/util/__init__.py ================================================ ================================================ FILE: src/server/util/drc_sim_c.py ================================================ import subprocess from threading import Thread import time from src.server.data import constants from src.server.data.args import Args from src.server.data.config_general import ConfigGeneral from src.server.util.logging.logger_backend import LoggerBackend from src.server.util.process_util import ProcessUtil from src.server.util.status_sending_thread import StatusSendingThread class DrcSimC(StatusSendingThread): UNKNOWN = "UNKNOWN" STOPPED = "STOPPED" RUNNING = "RUNNING" def __init__(self): """ Helper for interacting with drc_sim_c. """ super().__init__() self.running = False self.status = self.UNKNOWN self.drc_sim_c_process = None self.status_check_thread = None self.region = "none" def set_region(self, region): self.region = region def start(self): if Args.args.disable_server: return self.running = True self.kill_drc_sim_c() LoggerBackend.debug("Starting drc_sim_c") command = ["drc_sim_c", "-region", self.region, "-video-quality", str(ConfigGeneral.video_quality), "-input-delay", str(ConfigGeneral.input_delay)] if not ConfigGeneral.stream_video: command.append("--no-video") if not ConfigGeneral.stream_audio: command.append("--no-audio") if Args.args.debug: command.append("-d") if Args.args.extra: command.append("-e") if Args.args.finer: command.append("-f") if Args.args.verbose: command.append("-v") self.drc_sim_c_process = subprocess.Popen(command, stdout=open(constants.PATH_LOG_DRC_SIM_C, "w"), stderr=subprocess.STDOUT) LoggerBackend.debug("Starting status check thread") self.status_check_thread = Thread(target=self.check_status, name="drc_sim_c Status Check Thread") self.status_check_thread.start() self.set_status(self.RUNNING) def check_status(self): while self.running: if self.drc_sim_c_process.poll(): self.set_status(self.STOPPED) time.sleep(1) def stop(self): """ Stops any background thread that is running :return: None """ self.running = False LoggerBackend.debug("Stopping drc_sim_c") if self.drc_sim_c_process and self.drc_sim_c_process.poll() is None: self.drc_sim_c_process.terminate() self.kill_drc_sim_c() # reset self.clear_status_change_listeners() LoggerBackend.debug("Stopped drc_sim_c") @staticmethod def kill_drc_sim_c(): ProcessUtil.call(["killall", "drc_sim_c"]) ================================================ FILE: src/server/util/interface_util.py ================================================ import netifaces import os from src.server.data import constants from src.server.util.logging.logger import Logger from src.server.util.os_util import OsUtil from src.server.util.process_util import ProcessUtil class InterfaceUtil: def __init__(self): pass @classmethod def get_wiiu_compatible_interfaces(cls): """ Returns a list of interfaces that can operate on the 5GHz spectrum :return: array of interface names """ all_interfaces = cls.get_all_interfaces() compatible_interfaces = [] for interface in all_interfaces: if cls.is_interface_wiiu_compatible(interface): compatible_interfaces.append(interface) return compatible_interfaces @classmethod def get_all_interfaces(cls): """ Gets a list of all system network interfaces :return: array of interface names """ interfaces = [] for interface in netifaces.interfaces(): interfaces.append(interface) return interfaces @classmethod def is_interface_wiiu_compatible(cls, interface): if not OsUtil.is_linux(): Logger.extra("Ignoring interface compatibility check for %s", interface) return False frequency_info = ProcessUtil.get_output(["iwlist", interface, "frequency"]) return "5." in frequency_info # TODO check for 802.11n compliance @classmethod def get_ip(cls, interface): addresses = netifaces.ifaddresses(interface) if netifaces.AF_INET in addresses: return addresses[netifaces.AF_INET][0]["addr"] return "" @classmethod def get_mac(cls, interface): addresses = netifaces.ifaddresses(interface) if netifaces.AF_LINK in addresses: return addresses[netifaces.AF_LINK][0]["addr"] return "0" @classmethod def set_metric(cls, interface, metric): ProcessUtil.call(["ifmetric", interface, str(metric)]) @classmethod def dhclient(cls, interface): ProcessUtil.call(["killall", "dhclient"]) ProcessUtil.call(["dhclient", interface]) @classmethod def is_managed_by_network_manager(cls, interface): if not os.path.exists(constants.PATH_CONF_NETWORK_MANAGER): Logger.debug("Network manager config not found.") return False conf = open(constants.PATH_CONF_NETWORK_MANAGER) conf_data = conf.readlines() conf.close() managed = False for line in conf_data: if line.startswith("unmanaged-devices=") and "mac:" + cls.get_mac(interface) not in line: managed = True # Ensure configs with duplicates raise an unmanaged prompt if "unmanaged-devices=" not in " ".join(conf_data): managed = True Logger.debug("Interface \"%s\" managed by network manager: %s", interface, managed) return managed @classmethod def set_unmanaged_by_network_manager(cls, interface): Logger.debug("Adding interface \"%s-%s\" as an unmanaged interface to network manager", interface, cls.get_mac(interface)) with open(constants.PATH_CONF_NETWORK_MANAGER, "r") as conf_read: conf = conf_read.read().splitlines() added = False entry = "mac:" + cls.get_mac(interface) # Add Entry for line in range(0, len(conf)): # Add keyfile plugin if it's not enabled if conf[line].startswith("plugins=") and "keyfile" not in conf[line]: conf[line] += ",keyfile" # Add unmanaged device if conf[line].startswith("unmanaged-devices=") and entry not in conf[line]: conf[line] += ";" + entry added = True # Add the initial unmanaged entry if it was not present if not added: conf.append("[keyfile]") conf.append("unmanaged-devices=" + entry) # Write with open(constants.PATH_CONF_NETWORK_MANAGER, "w") as conf_write: for line in conf: conf_write.write(line + "\n") # Restart the service ProcessUtil.call(["service", "network-manager", "restart"]) ProcessUtil.call(["service", "networking", "restart"]) ================================================ FILE: src/server/util/logging/__init__.py ================================================ ================================================ FILE: src/server/util/logging/logger.py ================================================ import logging import os import shutil from src.server.data import constants from src.server.util.os_util import OsUtil class Logger: level = logging.INFO INFO = logging.INFO DEBUG = logging.DEBUG EXTRA = logging.DEBUG - 1 FINER = logging.DEBUG - 2 VERBOSE = logging.DEBUG - 3 logger = logging.getLogger("drcsim") console_handler = None file_handler = None def __init__(self, name=None): if not Logger.console_handler or not Logger.file_handler: Logger.logger, Logger.console_handler, Logger.file_handler = self.create_logger(name) # Level names logging.addLevelName(Logger.EXTRA, "EXTRA") logging.addLevelName(Logger.FINER, "FINER") logging.addLevelName(Logger.VERBOSE, "VERBOSE") @classmethod def info(cls, message, *args): cls.logger.info(message, *args) @classmethod def debug(cls, message, *args): cls.logger.debug(message, *args) @classmethod def extra(cls, message, *args): cls.logger.log(cls.EXTRA, message, *args) @classmethod def verbose(cls, message, *args): cls.logger.log(cls.VERBOSE, message, *args) @classmethod def set_level(cls, level): cls.level = level cls.logger.setLevel(cls.level) cls.console_handler.setLevel(cls.level) cls.file_handler.setLevel(cls.level) @classmethod def warn(cls, message, *args): cls.logger.warning(message, *args) @classmethod def throw(cls, exception, message=None, *args): cls.logger.error(str("=" * 10 + " [ CRASH ] " + "=" * 10)) OsUtil.log_info(cls.logger) if message: cls.logger.error(message, *args) if isinstance(exception, Exception): cls.logger.exception(exception) else: cls.logger.error(exception) cls.logger.error(str("=" * 10 + " [ CRASH ] " + "=" * 10)) if cls != Logger: raise exception @classmethod def finer(cls, message, *args): cls.logger.log(cls.FINER, message, *args) @classmethod def exception(cls, exception, *args): cls.logger.log(cls.EXTRA, exception, *args, exc_info=1) @classmethod def get_level(cls): return cls.level @staticmethod def create_logger(name): logger = logging.getLogger(name) format_str = "%(asctime)s %(levelname)s:%(name)s %(message)s" # Console output console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter(format_str)) logger.addHandler(console_handler) # File output log_path = os.path.join(constants.PATH_LOG_DIR, logger.name + ".log") if not os.path.exists(constants.PATH_ROOT): os.mkdir(constants.PATH_ROOT) if not os.path.exists(constants.PATH_LOG_DIR): os.mkdir(constants.PATH_LOG_DIR) if os.path.exists(log_path): shutil.copyfile(log_path, log_path.replace(".log", "-1.log")) os.remove(log_path) file_handler = logging.FileHandler(log_path) file_handler.setFormatter(logging.Formatter(format_str)) logger.addHandler(file_handler) return logger, console_handler, file_handler Logger("drcsim") ================================================ FILE: src/server/util/logging/logger_backend.py ================================================ from src.server.util.logging.logger import Logger class LoggerBackend(Logger): def __init__(self, name=None): Logger.__init__(self, name) LoggerBackend.logger, LoggerBackend.console_handler, LoggerBackend.file_handler = self.create_logger(name) LoggerBackend("backend") ================================================ FILE: src/server/util/logging/logger_cli.py ================================================ from src.server.util.logging.logger import Logger class LoggerCli(Logger): def __init__(self, name=None): Logger.__init__(self, name) LoggerCli.logger, LoggerCli.console_handler, LoggerCli.file_handler = self.create_logger(name) LoggerCli("cli") ================================================ FILE: src/server/util/logging/logger_gui.py ================================================ from src.server.util.logging.logger import Logger class LoggerGui(Logger): def __init__(self, name=None): Logger.__init__(self, name) LoggerGui.logger, LoggerGui.console_handler, LoggerGui.file_handler = self.create_logger(name) LoggerGui("gui") ================================================ FILE: src/server/util/logging/logger_wpa.py ================================================ from src.server.util.logging.logger import Logger class LoggerWpa(Logger): def __init__(self, name=None): Logger.__init__(self, name) LoggerWpa.logger, LoggerWpa.console_handler, LoggerWpa.file_handler = self.create_logger(name) LoggerWpa("wpa") ================================================ FILE: src/server/util/os_util.py ================================================ import os import platform as _platform class OsUtil: platform = os.name name = _platform.system() release = _platform.release() distro = _platform.linux_distribution() def __init__(self): pass @classmethod def is_windows(cls): return "windows" in cls.name.lower() @classmethod def log_info(cls, logger): if not cls.is_linux(): logger.warn("This OS is not supported") # Log info logger.debug("OS platform: %s", cls.platform) logger.debug("OS name: %s", cls.name) if cls.is_linux(): logger.debug("OS distro: %s", cls.distro) logger.debug("OS release: %s", cls.release) @classmethod def is_linux(cls): return "linux" in cls.name.lower() ================================================ FILE: src/server/util/process_util.py ================================================ import subprocess import errno from src.server.util.logging.logger import Logger class ProcessUtil: def __init__(self): pass @classmethod def get_output(cls, command, silent=False): """ Wraps a subprocess.check_output call. Checks for process errors and command not found errors. :param silent: If silent is true the command will not be logged :param command: array of strings - same as check_output :return: string of output, error, or None if the command is not found """ try: if not silent: Logger.extra("Attempting to execute command %s", command) output = subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True) except OSError as e: output = "" if e.errno == errno.ENOENT: Logger.warn("\"%s\" may not be installed", command[0]) else: Logger.exception(e) cls.log_failed_command(command) except subprocess.CalledProcessError as e: output = e.output cls.log_failed_command(command, output.strip()) Logger.verbose("Command \"%s\" output %s", command, output) return output @classmethod def call(cls, command): """ Same as subprocess.call but outputs to logger :param command: string array - same as call :return: None """ cls.get_output(command) @classmethod def log_failed_command(cls, command, output=None): Logger.extra("Failed to execute command \"%s\" and got output \"%s\"", command, output) ================================================ FILE: src/server/util/status_sending_thread.py ================================================ class StatusSendingThread: def __init__(self): """ Helper for registering callback status. """ self.status_change_listeners = [] self.status = None def set_status(self, status): """ Set and notify callbacks if the status does not match the current status. :param status: status message :return: None """ if self.status != status: self.status = status for listener in self.status_change_listeners: listener(status) def get_status(self): """ Status getter :return: status string """ return self.status def add_status_change_listener(self, callback): """ Add a callback that will be called on status change. :param callback: callable function :return: None """ self.status_change_listeners.append(callback) def clear_status_change_listeners(self): """ Cleat the status callbacks. :return: None """ self.status_change_listeners = [] ================================================ FILE: src/server/util/wpa_supplicant.py ================================================ import re import subprocess import time from threading import Thread import pexpect from src.server.data import constants from src.server.data.config_general import ConfigGeneral from src.server.util.logging.logger_wpa import LoggerWpa from src.server.util.process_util import ProcessUtil from src.server.util.status_sending_thread import StatusSendingThread class WpaSupplicant(StatusSendingThread): UNKNOWN = "UNKNOWN" CONNECTING = "CONNECTING" CONNECTED = "CONNECTED" TERMINATED = "TERMINATED" DISCONNECTED = "DISCONNECTED" SCANNING = "SCANNING" NOT_FOUND = "NOT_FOUND" FAILED_START = "FAILED_START" def __init__(self): """ Helper for interacting with wpa_supplicant_drc and wpa_cli_drc. """ super().__init__() self.time_scan = 0 self.time_start = 0 self.mac_addr_regex = re.compile('^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') self.wiiu_ap_regex = re.compile('^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})(\s*\d*\s*-*\d*\s*)' '(\[WPA2-PSK-CCMP\])?' '(\[ESS\])(\s*)(WiiU|\\\\x00)(.+)$') # \x00 is escaped (\\x00) self.running = False self.status = self.UNKNOWN self.status_check_thread = None self.wpa_supplicant_process = None self.psk_thread = None self.psk_thread_cli = None def connect(self, conf_path, interface, status_check=True): """ Starts a thread that connects to the Wii U. Status: FAILED_START: wpa_supplicant_drc did not initialize SCANNING: wpa_supplicant_drc is scanning CONNECTED: wpa_supplicant_drc is connected to an AP CONNECTING: wpa_supplicant_drc is authenticating TERMINATED: wpa_supplicant_drc was found by the T-1000 Cyberdyne Systems Model 101 NOT_FOUND: wpa_supplicant_drc could not find a Wii U AP UNKNOWN: wpa_supplicant_drc is in a state that is unhandled - it will be logged :return: None """ LoggerWpa.debug("Connect called") self.running = True self.unblock_wlan() self.kill_wpa() command = ["wpa_supplicant_drc", "-Dnl80211", "-i", interface, "-c", conf_path] if LoggerWpa.get_level() == LoggerWpa.FINER: command.append("-d") elif LoggerWpa.get_level() == LoggerWpa.VERBOSE: command.append("-dd") LoggerWpa.debug("Starting wpa supplicant") self.wpa_supplicant_process = subprocess.Popen(command, stdout=open(constants.PATH_LOG_WPA, "w"), stderr=subprocess.STDOUT) LoggerWpa.debug("Started wpa supplicant") if status_check: LoggerWpa.debug("Starting status check thread") self.status_check_thread = Thread(target=self.check_status, name="WPA Status Check Thread") self.status_check_thread.start() def check_status(self): """ Thread that checks WPA status every second Updates :return: None """ while self.running: wpa_status = self.wpa_cli("status") scan_results = self.wpa_cli("scan_results") not_started_message = "Failed to connect to non-global ctrl_ifname" LoggerWpa.finer("Scan Results: %s", scan_results) LoggerWpa.finer("Status: %s", wpa_status) # process is dead or wpa_supplicant has not started if self.wpa_supplicant_process.poll() or not_started_message in scan_results: LoggerWpa.finer("%d seconds until start timeout", 30 - self.time_start) # wait for wpa_supplicant to initialize if self.time_start >= 5: status = self.FAILED_START else: status = self.status self.time_start += 1 # scanning elif not self.scan_contains_wii_u(scan_results) or "wpa_state=SCANNING" in wpa_status: LoggerWpa.finer("%d seconds until scan timeout", ConfigGeneral.scan_timeout - self.time_scan) # disconnect if self.time_scan == -1: status = self.DISCONNECTED # timeout scan elif self.time_scan >= ConfigGeneral.scan_timeout: status = self.NOT_FOUND else: status = self.SCANNING self.time_scan += 1 elif "wpa_state=COMPLETED" in wpa_status: status = self.CONNECTED self.time_scan = -1 # forces a disconnect - might need to be handled better elif "wpa_state=AUTHENTICATING" in wpa_status or "wpa_state=ASSOCIATING" in wpa_status: status = self.CONNECTING elif "wpa_state=DISCONNECTED" in wpa_status: status = self.DISCONNECTED if self.time_scan != -1: status = self.FAILED_START else: LoggerWpa.extra("WPA status: %s", wpa_status) status = self.UNKNOWN self.set_status(status) time.sleep(1) @staticmethod def kill_wpa(): """ Makes a system call to kill wpa_supplicant_drc :return: None """ ProcessUtil.call(["killall", "wpa_supplicant_drc"]) @staticmethod def unblock_wlan(): """ Make a system call to unblock wlan :return: None """ ProcessUtil.call(["rfkill", "unblock", "wlan"]) @staticmethod def wpa_cli(command): """ Makes a system call to wpa_cli_drc :param command: command to pass to wpa_cli_drc :return: command output """ if isinstance(command, str): command = [command] return ProcessUtil.get_output(["wpa_cli_drc", "-p", "/var/run/wpa_supplicant_drc"] + command, silent=True) def stop(self): """ Stops any background thread that is running :return: None """ if not self.running: LoggerWpa.debug("Ignored stop request: already stopped") return self.running = False if self.psk_thread_cli and self.psk_thread_cli.isalive(): LoggerWpa.debug("Stopping psk pexpect spawn") self.psk_thread_cli.sendline("quit") self.psk_thread_cli.close(True) LoggerWpa.debug("Stopping wpa process") if self.wpa_supplicant_process and self.wpa_supplicant_process.poll() is None: self.wpa_supplicant_process.terminate() self.kill_wpa() # reset self.clear_status_change_listeners() self.time_start = 0 self.time_scan = 0 LoggerWpa.debug("Wpa stopped") def scan_contains_wii_u(self, scan_results): """ Check if string contains Wii U SSID :param scan_results: string :return: boolean """ for line in scan_results.splitlines(): if self.wiiu_ap_regex.match(line): return True return False def scan_is_empty(self, scan_results): """ Check if the scan has no MAC addresses :param scan_results: string :return: boolean """ for line in scan_results.split("\n"): if self.mac_addr_regex.match(line): return False return True def get_psk(self, conf_path, interface, code): """ Starts a thread to connect and attempt to obtain a Wii U's PSK Status: FAILED_START: there was an error attempting to parse CLI output - exception is logged NOT_FOUND: wpa_supplicant_drc did not find any Wii U APs TERMINATED: wpa_supplicant_drc could not authenticate with any SSIDs DISCONNECTED: auth details were saved SCANNING: scan has started CONNECTING: attempting to authenticate with a Wii U :return: None """ self.connect(conf_path, interface, status_check=False) self.psk_thread = Thread(target=self.get_psk_thread, kwargs={"code": code}, name="PSK Thread") self.psk_thread.start() def get_psk_thread(self, code): """ Thread that attempts to authenticate with a Wii U. Updates status :param code: WPS PIN :return: None """ try: LoggerWpa.debug("CLI expect starting") self.psk_thread_cli = pexpect.spawn("wpa_cli_drc -p /var/run/wpa_supplicant_drc") LoggerWpa.debug("CLI expect Waiting for init") self.psk_thread_cli.expect("Interactive mode") # Scan for Wii U SSIDs scan_tries = 5 wii_u_bssids = [] self.set_status(self.SCANNING) while self.running and scan_tries > 0: self.psk_thread_cli.sendline("scan") LoggerWpa.debug("CLI expect waiting for scan results available event") scan_wait_tries = 60 while self.running: try: self.psk_thread_cli.expect("<3>CTRL-EVENT-SCAN-RESULTS", timeout=1) break except pexpect.TIMEOUT: scan_wait_tries -= 1 if scan_wait_tries <= 0: raise pexpect.TIMEOUT("Scan timeout") self.psk_thread_cli.sendline("scan_results") LoggerWpa.debug("CLI expect waiting for scan results") self.psk_thread_cli.expect("bssid / frequency / signal level / flags / ssid") for line in range(0, 100): # up to 100 APs try: self.psk_thread_cli.expect(self.mac_addr_regex.pattern, timeout=1) except pexpect.TIMEOUT: break scan_results = self.psk_thread_cli.before.decode() LoggerWpa.finer("CLI expect - scan results: %s", scan_results) for line in scan_results.splitlines(): if self.wiiu_ap_regex.match(line): wii_u_bssids.append(line.split()[0]) if len(wii_u_bssids) == 0: scan_tries -= 1 else: scan_tries = 0 # Check for found Wii U ssids if len(wii_u_bssids) == 0: LoggerWpa.debug("No Wii U SSIDs found") self.set_status(self.NOT_FOUND) return # attempt to pair with any wii u bssid self.set_status(self.CONNECTING) for bssid in wii_u_bssids: self.psk_thread_cli.sendline("wps_pin %s %s" % (bssid, code + "5678")) LoggerWpa.debug("CLI expect waiting for wps_pin input confirmation") self.psk_thread_cli.expect(code + "5678") LoggerWpa.debug("CLI expect waiting for authentication") try: connect_wait_tries = 60 while self.running: try: self.psk_thread_cli.expect("<3>WPS-CRED-RECEIVED", timeout=1) break except pexpect.TIMEOUT: connect_wait_tries -= 1 if connect_wait_tries <= 0: raise pexpect.TIMEOUT("Connect Timeout") # save conf LoggerWpa.debug("PSK obtained") # Save to temp config before reading from it self.psk_thread_cli.sendline("save_config") self.psk_thread_cli.expect("OK", timeout=5) self.save_connect_conf(bssid) self.set_status(self.DISCONNECTED) return except pexpect.TIMEOUT: LoggerWpa.debug("CLI expect BSSID auth failed") self.psk_thread_cli.sendline("reconnect") self.psk_thread_cli.expect("OK") # Timed out except pexpect.TIMEOUT as e: LoggerWpa.debug("PSK get attempt ended with an error.") LoggerWpa.exception(e) self.set_status(self.FAILED_START) # Unexpected EOF except pexpect.EOF as e: if self.running: # Thread was not killed LoggerWpa.exception(e) return # Failed to authenticate LoggerWpa.debug("Could not authenticate with any SSIDs") self.set_status(self.TERMINATED) @staticmethod def save_connect_conf(bssid): """ Modify the temp get_psk configuration to be a connect configurraton and save it to ~/.drc-sim/connect_to_wii_u.conf. :param bssid: Wii U BSSID :return: None """ LoggerWpa.debug("Saving connection config") # add additional connect information to config conf = open(constants.PATH_CONF_CONNECT_TMP, "r") lines = conf.readlines() conf.close() for line in lines: if "update_config=1" in line: lines.insert(lines.index(line) + 1, "ap_scan=1\n") break for line in lines: if "network={" in line: lines.insert(lines.index(line) + 1, "\tscan_ssid=1\n") lines.insert(lines.index(line) + 2, "\tbssid=" + bssid + "\n") break save_conf = open(constants.PATH_CONF_CONNECT, "w") save_conf.writelines(lines) save_conf.close() LoggerWpa.info("Authenticated with the Wii U") ================================================ FILE: tests/packets/.gitignore ================================================ !*.bin ================================================ FILE: tests/test_parse.py ================================================ import os def test_video_parse(): """ Reads dumped video packets and sends them to the video handler :return: None """ '''ConfigServer.load() handler = VideoHandler() with open(os.path.join(os.path.dirname(__file__), "packets/video.bin"), "rb") as video_packets: read = True while read: packet = b"" while b"|\n" not in packet: read_byte = video_packets.read(1) if not read_byte: return packet += read_byte packet = packet.replace(b"|\n", b"") handler.update(packet, True)''' pass