diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 0042015..0000000 --- a/.flake8 +++ /dev/null @@ -1,29 +0,0 @@ -[flake8] -ignore = - # E501 line too long - E501, - # W391 newlines at EOF - W391, - # E241 multiple spaces after comma - E241, - # E302 expected 2 newlines - E302, - # W503 line break before binary operator (to be deprecated) - W503, - # E265 block comment should start with '# ' - E265, - # E123 closing bracket does not match indentation of opening bracket's line - E123, - # E124 closing bracket does not match visual indentation - E124, - # E221 multiple spaces before operator - E221, - # E201 whitespace after '[' - E201, - # E741 ambiguous variable name 'I' - E741, - - -per-file-ignores = - # F401 import without use - */__init__.py: F401, diff --git a/.gitignore b/.gitignore index ad16919..6ad846b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,8 @@ .idea/ -__pycache__/ +__pycache__ *.pyc *.egg-info/ build/ dist/ - -.pytest_cache/ -.mypy_cache/ - -*.pickle diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..2b8b271 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include README.md +include LICENSE.md +include mem_edit/VERSION diff --git a/README.md b/README.md index 443daff..53789b7 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ -# mem_edit +# mem_edit **mem_edit** is a multi-platform memory editing library written in Python. **Homepage:** https://mpxd.net/code/jan/mem_edit -* PyPI: https://pypi.org/project/mem-edit/ -* Github mirror: https://github.com/anewusername/mem_edit **Capabilities:** * Scan all readable memory used by a process. @@ -20,7 +18,7 @@ ## Installation **Dependencies:** -* python >=3.11 +* python 3 (written and tested with 3.7) * ctypes * typing (for type annotations) diff --git a/mem_edit/LICENSE.md b/mem_edit/LICENSE.md deleted file mode 120000 index 7eabdb1..0000000 --- a/mem_edit/LICENSE.md +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.md \ No newline at end of file diff --git a/mem_edit/README.md b/mem_edit/README.md deleted file mode 120000 index 32d46ee..0000000 --- a/mem_edit/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file diff --git a/mem_edit/VERSION.py b/mem_edit/VERSION.py new file mode 100644 index 0000000..e4f476e --- /dev/null +++ b/mem_edit/VERSION.py @@ -0,0 +1,4 @@ +""" VERSION defintion. THIS FILE IS MANUALLY PARSED BY setup.py and REQUIRES A SPECIFIC FORMAT """ +__version__ = ''' +0.6 +'''.strip() diff --git a/mem_edit/__init__.py b/mem_edit/__init__.py index fcedd6f..a4a0178 100644 --- a/mem_edit/__init__.py +++ b/mem_edit/__init__.py @@ -17,14 +17,15 @@ from .utils import MemEditError __author__ = 'Jan Petykiewicz' -__version__ = '0.8' + +from .VERSION import __version__ version = __version__ # legacy compatibility system = platform.system() if system == 'Windows': - from .windows import Process as Process + from .windows import Process elif system == 'Linux': - from .linux import Process as Process + from .linux import Process else: raise MemEditError('Only Linux and Windows are currently supported.') diff --git a/mem_edit/abstract.py b/mem_edit/abstract.py index f21a528..e086d4e 100644 --- a/mem_edit/abstract.py +++ b/mem_edit/abstract.py @@ -1,8 +1,8 @@ """ Abstract class for cross-platform memory editing. """ -from typing import Self -from collections.abc import Generator + +from typing import List, Tuple, Optional, Union, Generator from abc import ABCMeta, abstractmethod from contextlib import contextmanager import copy @@ -122,7 +122,7 @@ class Process(metaclass=ABCMeta): """ @abstractmethod - def __init__(self, process_id: int) -> None: + def __init__(self, process_id: int): """ Constructing a Process object prepares the process with specified process_id for memory editing. Finding the `process_id` for the process you want to edit is often @@ -135,7 +135,7 @@ class Process(metaclass=ABCMeta): pass @abstractmethod - def close(self) -> None: + def close(self): """ Detach from the process, removing our ability to edit it and letting other debuggers attach to it instead. @@ -147,11 +147,7 @@ class Process(metaclass=ABCMeta): pass @abstractmethod - def write_memory( - self, - base_address: int, - write_buffer: ctypes_buffer_t, - ) -> None: + def write_memory(self, base_address: int, write_buffer: ctypes_buffer_t): """ Write the given buffer to the process's address space, starting at `base_address`. @@ -164,11 +160,7 @@ class Process(metaclass=ABCMeta): pass @abstractmethod - def read_memory( - self, - base_address: int, - read_buffer: ctypes_buffer_t, - ) -> ctypes_buffer_t: + def read_memory(self, base_address: int, read_buffer: ctypes_buffer_t) -> ctypes_buffer_t: """ Read into the given buffer from the process's address space, starting at `base_address`. @@ -184,7 +176,7 @@ class Process(metaclass=ABCMeta): pass @abstractmethod - def list_mapped_regions(self, writeable_only: bool = True) -> list[tuple[int, int]]: + def list_mapped_regions(self, writeable_only=True) -> List[Tuple[int, int]]: """ Return a list of `(start_address, stop_address)` for the regions of the address space accessible to (readable and possibly writable by) the process. @@ -200,18 +192,18 @@ class Process(metaclass=ABCMeta): pass @abstractmethod - def get_path(self) -> str | None: + def get_path(self) -> str: """ Return the path to the executable file which was run to start this process. Returns: - A string containing the path, or None if no path was found. + A string containing the path. """ pass @staticmethod @abstractmethod - def list_available_pids() -> list[int]: + def list_available_pids() -> List[int]: """ Return a list of all process ids (pids) accessible on this system. @@ -222,7 +214,7 @@ class Process(metaclass=ABCMeta): @staticmethod @abstractmethod - def get_pid_by_name(target_name: str) -> int | None: + def get_pid_by_name(target_name: str) -> Optional[int]: """ Attempt to return the process id (pid) of a process which was run with an executable file with the provided name. If no process is found, return None. @@ -242,11 +234,10 @@ class Process(metaclass=ABCMeta): """ pass - def deref_struct_pointer( - self, - base_address: int, - targets: list[tuple[int, ctypes_buffer_t]], - ) -> list[ctypes_buffer_t]: + def deref_struct_pointer(self, + base_address: int, + targets: List[Tuple[int, ctypes_buffer_t]], + ) -> List[ctypes_buffer_t]: """ Take a pointer to a struct and read out the struct members: ``` @@ -272,12 +263,11 @@ class Process(metaclass=ABCMeta): values = [self.read_memory(base + offset, buffer) for offset, buffer in targets] return values - def search_addresses( - self, - addresses: list[int], - needle_buffer: ctypes_buffer_t, - verbatim: bool = True, - ) -> list[int]: + def search_addresses(self, + addresses: List[int], + needle_buffer: ctypes_buffer_t, + verbatim: bool = True, + ) -> List[int]: """ Search for the provided value at each of the provided addresses, and return the addresses where it is found. @@ -308,12 +298,11 @@ class Process(metaclass=ABCMeta): found.append(address) return found - def search_all_memory( - self, - needle_buffer: ctypes_buffer_t, - writeable_only: bool = True, - verbatim: bool = True, - ) -> list[int]: + def search_all_memory(self, + needle_buffer: ctypes_buffer_t, + writeable_only: bool = True, + verbatim: bool = True, + ) -> List[int]: """ Search the entire memory space accessible to the process for the provided value. @@ -341,12 +330,12 @@ class Process(metaclass=ABCMeta): self.read_memory(start, region_buffer) found += [offset + start for offset in search(needle_buffer, region_buffer)] except OSError: - logger.exception(f'Failed to read in range 0x{start} - 0x{stop}') + logger.error('Failed to read in range 0x{} - 0x{}'.format(start, stop)) return found @classmethod @contextmanager - def open_process(cls: type[Self], process_id: int) -> Generator[Self, None, None]: + def open_process(cls, process_id: int) -> Generator['Process', None, None]: """ Context manager which automatically closes the constructed Process: ``` diff --git a/mem_edit/linux.py b/mem_edit/linux.py index 94509bd..aa25a49 100644 --- a/mem_edit/linux.py +++ b/mem_edit/linux.py @@ -2,6 +2,7 @@ Implementation of Process class for Linux """ +from typing import List, Tuple, Optional from os import strerror import os import os.path @@ -9,7 +10,6 @@ import signal import ctypes import ctypes.util import logging -from pathlib import Path from .abstract import Process as AbstractProcess from .utils import ctypes_buffer_t, MemEditError @@ -35,61 +35,54 @@ _ptrace.argtypes = (ctypes.c_ulong,) * 4 _ptrace.restype = ctypes.c_long -def ptrace( - command: int, - pid: int = 0, - arg1: int = 0, - arg2: int = 0, - ) -> int: +def ptrace(command: int, pid: int = 0, arg1: int = 0, arg2: int = 0) -> int: """ - Call ptrace() with the provided pid and arguments. See `man ptrace`. + Call ptrace() with the provided pid and arguments. See the ```man ptrace```. """ - logger.debug(f'ptrace({command}, {pid}, {arg1}, {arg2})') + logger.debug('ptrace({}, {}, {}, {})'.format(command, pid, arg1, arg2)) result = _ptrace(command, pid, arg1, arg2) if result == -1: err_no = ctypes.get_errno() if err_no: - raise MemEditError(f'ptrace({command}, {pid}, {arg1}, {arg2})' - + f' failed with error {err_no}: {strerror(err_no)}') + raise MemEditError('ptrace({}, {}, {}, {})'.format(command, pid, arg1, arg2) + + ' failed with error {}: {}'.format(err_no, strerror(err_no))) return result class Process(AbstractProcess): - pid: int | None + pid = None - def __init__(self, process_id: int) -> None: + def __init__(self, process_id: int): ptrace(ptrace_commands['PTRACE_SEIZE'], process_id) self.pid = process_id - def close(self) -> None: - if self.pid is None: - return + def close(self): os.kill(self.pid, signal.SIGSTOP) os.waitpid(self.pid, 0) ptrace(ptrace_commands['PTRACE_DETACH'], self.pid, 0, 0) os.kill(self.pid, signal.SIGCONT) self.pid = None - def write_memory(self, base_address: int, write_buffer: ctypes_buffer_t) -> None: - with Path(f'/proc/{self.pid}/mem').open('rb+') as mem: + def write_memory(self, base_address: int, write_buffer: ctypes_buffer_t): + with open('/proc/{}/mem'.format(self.pid), 'rb+') as mem: mem.seek(base_address) mem.write(write_buffer) def read_memory(self, base_address: int, read_buffer: ctypes_buffer_t) -> ctypes_buffer_t: - with Path(f'/proc/{self.pid}/mem').open('rb+') as mem: + with open('/proc/{}/mem'.format(self.pid), 'rb+') as mem: mem.seek(base_address) mem.readinto(read_buffer) return read_buffer - def get_path(self) -> str | None: + def get_path(self) -> str: try: - with Path(f'/proc/{self.pid}/cmdline').open('rb') as ff: - return ff.read().decode().split('\x00')[0] + with open('/proc/{}/cmdline', 'rb') as f: + return f.read().decode().split('\x00')[0] except FileNotFoundError: - return None + return '' @staticmethod - def list_available_pids() -> list[int]: + def list_available_pids() -> List[int]: pids = [] for pid_str in os.listdir('/proc'): try: @@ -99,26 +92,26 @@ class Process(AbstractProcess): return pids @staticmethod - def get_pid_by_name(target_name: str) -> int | None: + def get_pid_by_name(target_name: str) -> Optional[int]: for pid in Process.list_available_pids(): try: - logger.debug(f'Checking name for pid {pid}') - with Path(f'/proc/{pid}/cmdline').open('rb') as cmdline: + logger.debug('Checking name for pid {}'.format(pid)) + with open('/proc/{}/cmdline'.format(pid), 'rb') as cmdline: path = cmdline.read().decode().split('\x00')[0] except FileNotFoundError: continue - name = Path(path).name - logger.debug(f'Name was "{name}"') + name = os.path.basename(path) + logger.debug('Name was "{}"'.format(name)) if path is not None and name == target_name: return pid - logger.info(f'Found no process with name {target_name}') + logger.info('Found no process with name {}'.format(target_name)) return None - def list_mapped_regions(self, writeable_only: bool = True) -> list[tuple[int, int]]: + def list_mapped_regions(self, writeable_only: bool = True) -> List[Tuple[int, int]]: regions = [] - with Path(f'/proc/{self.pid}/maps').open('r') as maps: + with open('/proc/{}/maps'.format(self.pid), 'r') as maps: for line in maps: bounds, privileges = line.split()[0:2] diff --git a/mem_edit/utils.py b/mem_edit/utils.py index 96d0180..2c9c022 100644 --- a/mem_edit/utils.py +++ b/mem_edit/utils.py @@ -11,25 +11,21 @@ Utility functions and types: Check if two buffers (ctypes objects) store equal values: ctypes_equal(a, b) """ + +from typing import List, Union import ctypes -ctypes_buffer_t = ( - ctypes._SimpleCData - | ctypes.Array - | ctypes.Structure - | ctypes.Union - ) +ctypes_buffer_t = Union[ctypes._SimpleCData, ctypes.Array, ctypes.Structure, ctypes.Union] class MemEditError(Exception): pass -def search_buffer_verbatim( - needle_buffer: ctypes_buffer_t, - haystack_buffer: ctypes_buffer_t, - ) -> list[int]: +def search_buffer_verbatim(needle_buffer: ctypes_buffer_t, + haystack_buffer: ctypes_buffer_t, + ) -> List[int]: """ Search for a buffer inside another buffer, using a direct (bitwise) comparison @@ -54,10 +50,9 @@ def search_buffer_verbatim( return found -def search_buffer( - needle_buffer: ctypes_buffer_t, - haystack_buffer: ctypes_buffer_t, - ) -> list[int]: +def search_buffer(needle_buffer: ctypes_buffer_t, + haystack_buffer: ctypes_buffer_t, + ) -> List[int]: """ Search for a buffer inside another buffer, using `ctypes_equal` for comparison. Much slower than `search_buffer_verbatim`. @@ -78,14 +73,13 @@ def search_buffer( return found -def ctypes_equal( - a: ctypes_buffer_t, - b: ctypes_buffer_t, - ) -> bool: +def ctypes_equal(a: ctypes_buffer_t, + b: ctypes_buffer_t, + ) -> bool: """ Check if the values stored inside two ctypes buffers are equal. """ - if not type(a) == type(b): # noqa: E721 + if not type(a) == type(b): return False if isinstance(a, ctypes.Array): @@ -93,10 +87,10 @@ def ctypes_equal( elif isinstance(a, ctypes.Structure) or isinstance(a, ctypes.Union): for attr_name, attr_type in a._fields_: a_attr, b_attr = (getattr(x, attr_name) for x in (a, b)) - if isinstance(a, (ctypes.Array, ctypes.Structure, ctypes.Union, ctypes._SimpleCData)): + if isinstance(a, ctypes_buffer_t): if not ctypes_equal(a_attr, b_attr): return False - elif a_attr != b_attr: + elif not a_attr == b_attr: return False return True diff --git a/mem_edit/windows.py b/mem_edit/windows.py index 8dfd9b8..b945058 100644 --- a/mem_edit/windows.py +++ b/mem_edit/windows.py @@ -2,9 +2,10 @@ Implementation of Process class for Windows """ +from typing import List, Tuple, Optional from math import floor from os import strerror -from pathlib import Path +import os.path import ctypes import ctypes.wintypes import logging @@ -24,10 +25,10 @@ privileges = { 'PROCESS_VM_WRITE': 0x0020, } privileges['PROCESS_RW'] = ( - privileges['PROCESS_QUERY_INFORMATION'] - | privileges['PROCESS_VM_OPERATION'] - | privileges['PROCESS_VM_READ'] - | privileges['PROCESS_VM_WRITE'] + privileges['PROCESS_QUERY_INFORMATION'] | + privileges['PROCESS_VM_OPERATION'] | + privileges['PROCESS_VM_READ'] | + privileges['PROCESS_VM_WRITE'] ) # Memory region states @@ -49,13 +50,13 @@ page_protections = { } # Custom (combined) permissions page_protections['PAGE_READABLE'] = ( - page_protections['PAGE_EXECUTE_READ'] - | page_protections['PAGE_EXECUTE_READWRITE'] - | page_protections['PAGE_READWRITE'] + page_protections['PAGE_EXECUTE_READ'] | + page_protections['PAGE_EXECUTE_READWRITE'] | + page_protections['PAGE_READWRITE'] ) page_protections['PAGE_READWRITEABLE'] = ( - page_protections['PAGE_EXECUTE_READWRITE'] - | page_protections['PAGE_READWRITE'] + page_protections['PAGE_EXECUTE_READWRITE'] | + page_protections['PAGE_READWRITE'] ) # Memory types @@ -90,9 +91,7 @@ class MEMORY_BASIC_INFORMATION64(ctypes.Structure): ('__alignment2', ctypes.wintypes.DWORD), ] - PTR_SIZE = ctypes.sizeof(ctypes.c_void_p) -MEMORY_BASIC_INFORMATION: type[ctypes.Structure] if PTR_SIZE == 8: # 64-bit python MEMORY_BASIC_INFORMATION = MEMORY_BASIC_INFORMATION64 elif PTR_SIZE == 4: # 32-bit python @@ -134,9 +133,9 @@ class SYSTEM_INFO(ctypes.Structure): class Process(AbstractProcess): - process_handle: int | None + process_handle = None - def __init__(self, process_id: int) -> None: + def __init__(self, process_id: int): process_handle = ctypes.windll.kernel32.OpenProcess( privileges['PROCESS_RW'], False, @@ -144,15 +143,15 @@ class Process(AbstractProcess): ) if not process_handle: - raise MemEditError(f'Couldn\'t open process {process_id}') + raise MemEditError('Couldn\'t open process {}'.format(process_id)) self.process_handle = process_handle - def close(self) -> None: + def close(self): ctypes.windll.kernel32.CloseHandle(self.process_handle) self.process_handle = None - def write_memory(self, base_address: int, write_buffer: ctypes_buffer_t) -> None: + def write_memory(self, base_address: int, write_buffer: ctypes_buffer_t): try: ctypes.windll.kernel32.WriteProcessMemory( self.process_handle, @@ -161,8 +160,8 @@ class Process(AbstractProcess): ctypes.sizeof(write_buffer), None ) - except (BufferError, ValueError, TypeError) as err: - raise MemEditError(f'Error with handle {self.process_handle}: {self._get_last_error()}') from err + except (BufferError, ValueError, TypeError): + raise MemEditError('Error with handle {}: {}'.format(self.process_handle, self._get_last_error())) def read_memory(self, base_address: int, read_buffer: ctypes_buffer_t) -> ctypes_buffer_t: try: @@ -173,68 +172,68 @@ class Process(AbstractProcess): ctypes.sizeof(read_buffer), None ) - except (BufferError, ValueError, TypeError) as err: - raise MemEditError(f'Error with handle {self.process_handle}: {self._get_last_error()}') from err + except (BufferError, ValueError, TypeError): + raise MemEditError('Error with handle {}: {}'.format(self.process_handle, self._get_last_error())) return read_buffer @staticmethod - def _get_last_error() -> tuple[int, str]: + def _get_last_error() -> Tuple[int, str]: err = ctypes.windll.kernel32.GetLastError() return err, strerror(err) - def get_path(self) -> str | None: + def get_path(self) -> str: max_path_len = 260 name_buffer = (ctypes.c_char * max_path_len)() rval = ctypes.windll.psapi.GetProcessImageFileNameA( - self.process_handle, - name_buffer, - max_path_len, - ) + self.process_handle, + name_buffer, + max_path_len) - if rval <= 0: + if rval > 0: + return name_buffer.value.decode() + else: return None - return name_buffer.value.decode() @staticmethod - def list_available_pids() -> list[int]: + def list_available_pids() -> List[int]: # According to EnumProcesses docs, you can't find out how many processes there are before # fetching the list. As a result, we grab 100 on the first try, and if we get a full list # of 100, repeatedly double the number until we get fewer than we asked for. - nn = 100 + n = 100 returned_size = ctypes.wintypes.DWORD() returned_size_ptr = ctypes.byref(returned_size) while True: - pids = (ctypes.wintypes.DWORD * nn)() + pids = (ctypes.wintypes.DWORD * n)() size = ctypes.sizeof(pids) pids_ptr = ctypes.byref(pids) success = ctypes.windll.Psapi.EnumProcesses(pids_ptr, size, returned_size_ptr) if not success: - raise MemEditError(f'Failed to enumerate processes: nn={nn}') + raise MemEditError('Failed to enumerate processes: n={}'.format(n)) num_returned = floor(returned_size.value / ctypes.sizeof(ctypes.wintypes.DWORD)) - if nn != num_returned: + if n == num_returned: + n *= 2 + continue + else: break - nn *= 2 return pids[:num_returned] @staticmethod - def get_pid_by_name(target_name: str) -> int | None: + def get_pid_by_name(target_name: str) -> Optional[int]: for pid in Process.list_available_pids(): try: - logger.debug(f'Checking name for pid {pid}') + logger.debug('Checking name for pid {}'.format(pid)) with Process.open_process(pid) as process: path = process.get_path() - if path is None: - continue - name = Path(path).name - logger.debug(f'Name was "{name}"') + name = os.path.basename(path) + logger.debug('Name was "{}"'.format(name)) if path is not None and name == target_name: return pid except ValueError: @@ -242,10 +241,10 @@ class Process(AbstractProcess): except MemEditError as err: logger.debug(repr(err)) - logger.info(f'Found no process with name {target_name}') + logger.info('Found no process with name {}'.format(target_name)) return None - def list_mapped_regions(self, writeable_only: bool = True) -> list[tuple[int, int]]: + def list_mapped_regions(self, writeable_only: bool = True) -> List[Tuple[int, int]]: sys_info = SYSTEM_INFO() sys_info_ptr = ctypes.byref(sys_info) ctypes.windll.kernel32.GetSystemInfo(sys_info_ptr) @@ -253,7 +252,7 @@ class Process(AbstractProcess): start = sys_info.lpMinimumApplicationAddress stop = sys_info.lpMaximumApplicationAddress - def get_mem_info(address: int) -> MEMORY_BASIC_INFORMATION: + def get_mem_info(address): """ Query the memory region starting at or before 'address' to get its size/type/state/permissions. """ @@ -269,9 +268,10 @@ class Process(AbstractProcess): if success != mbi_size: if success == 0: - raise MemEditError('Failed VirtualQueryEx with handle ' - + f'{self.process_handle}: {self._get_last_error()}') - raise MemEditError('VirtualQueryEx output too short!') + raise MemEditError('Failed VirtualQueryEx with handle ' + + '{}: {}'.format(self.process_handle, self._get_last_error())) + else: + raise MemEditError('VirtualQueryEx output too short!') return mbi diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 132f221..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,87 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "mem_edit" -description = "Multi-platform library for memory editing" -readme = "README.md" -license = { file = "LICENSE.md" } -authors = [ - { name="Jan Petykiewicz", email="jan@mpxd.net" }, - ] -homepage = "https://mpxd.net/code/jan/mem_edit" -repository = "https://mpxd.net/code/jan/mem_edit" -keywords = [ - "memory", - "edit", - "editing", - "ReadProcessMemory", - "WriteProcessMemory", - "proc", - "mem", - "ptrace", - "multiplatform", - "scan", - "scanner", - "search", - "debug", - "cheat", - "trainer", - ] -classifiers = [ - "Programming Language :: Python :: 3", - "Development Status :: 5 - Production/Stable", - "Environment :: Other Environment", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU Affero General Public License v3", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Topic :: Software Development", - "Topic :: Software Development :: Debuggers", - "Topic :: Software Development :: Testing", - "Topic :: System", - "Topic :: Games/Entertainment", - "Topic :: Utilities", - ] -requires-python = ">=3.11" -dynamic = ["version"] -dependencies = [ - ] - -[tool.hatch.version] -path = "mem_edit/__init__.py" - - -[tool.ruff] -exclude = [ - ".git", - "dist", - ] -line-length = 145 -indent-width = 4 -lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -lint.select = [ - "NPY", "E", "F", "W", "B", "ANN", "UP", "SLOT", "SIM", "LOG", - "C4", "ISC", "PIE", "PT", "RET", "TCH", "PTH", "INT", - "ARG", "PL", "R", "TRY", - "G010", "G101", "G201", "G202", - "Q002", "Q003", "Q004", - ] -lint.ignore = [ - #"ANN001", # No annotation - "ANN002", # *args - "ANN003", # **kwargs - "ANN401", # Any - "ANN101", # self: Self - "SIM108", # single-line if / else assignment - "RET504", # x=y+z; return x - "PIE790", # unnecessary pass - "ISC003", # non-implicit string concatenation - "C408", # dict(x=y) instead of {'x': y} - "PLR09", # Too many xxx - "PLR2004", # magic number - "PLC0414", # import x as x - "TRY003", # Long exception message - ] - diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b5a234e --- /dev/null +++ b/setup.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +from setuptools import setup, find_packages + + +with open('README.md', 'rt') as f: + long_description = f.read() + +with open('mem_edit/VERSION.py', 'rt') as f: + version = f.readlines()[2].strip() + +setup(name='mem_edit', + version=version, + description='Multi-platform library for memory editing', + long_description=long_description, + long_description_content_type='text/markdown', + author='Jan Petykiewicz', + author_email='jan@mpxd.net', + url='https://mpxd.net/code/jan/mem_edit', + keywords=[ + 'memory', + 'edit', + 'editing', + 'ReadProcessMemory', + 'WriteProcessMemory', + 'proc', + 'mem', + 'ptrace', + 'multiplatform', + 'scan', + 'scanner', + 'search', + 'debug', + 'cheat', + 'trainer', + ], + classifiers=[ + 'Programming Language :: Python :: 3', + 'Development Status :: 4 - Beta', + 'Environment :: Other Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Affero General Public License v3', + 'Operating System :: POSIX :: Linux', + 'Operating System :: Microsoft :: Windows', + 'Topic :: Software Development', + 'Topic :: Software Development :: Debuggers', + 'Topic :: Software Development :: Testing', + 'Topic :: System', + 'Topic :: Games/Entertainment', + 'Topic :: Utilities', + ], + packages=find_packages(), + package_data={ + 'mem_edit': [] + }, + install_requires=[ + 'typing', + ], + extras_require={ + }, + ) +