 04dd91be82
			
		
	
	04dd91be82
	
	
	
		
			
			* Add ZeroMQ external submodule * ZeroMQ libzmq building on macOS * Added RPC namespace, settings and logging * Added request queue handling and new classes * Add C++ interface to ZeroMQ * Added start of ZeroMQ RPC Server implementation. * Request construction and callback request handling * Read and write memory implementation * Add ID to request format and send reply * Add RPC setting to macOS UI * Fixed initialization order bug and added exception handling * Working read-write through Python * Update CMakeLists for libzmq to resolve target name conflict on Windows * Platform-specific CMake definitions for Windows/non-Windows * Add comments * Revert "Add RPC setting to macOS UI" * Always run RPC server instead of configurable * Add Python scripting example. Updated .gitignore * Rename member variables to remove trailing underscore * Finally got libzmq external project building on macOS * Add missing dependency during libzmq build * Adding more missing dependencies [skip ci] * Only build what is required from libzmq * Extra length checks on client input * Call InvalidateCacheRange after memory write * Revert MinGW change. Fix clang-format. Improve error handling in request/reply. Allow any length of data read/write in Python. * Re-organized RPC static global state into a proper class. [skip ci] * Make sure libzmq always builds in Release mode * Renamed Request to Packet since Request and Reply are the same thing * Moved request fulfillment out of Packet and into RPCServer * Change request thread from sleep to condition variable * Remove non-blocking polling from ZMQ server code. Receive now blocks and terminates properly without sleeping. This change significantly improves script speed. * Move scripting files to dist/ instead of src/ * C++ code review changes for jroweboy [skip ci] * Python code review changes for jroweboy [skip ci] * Add docstrings and tests to citra.py [skip ci] * Add host OS check for libzmq build * Revert "Add host OS check for libzmq build" * Fixed a hang when emulation is stopped and restarted due to improper destruction order of ZMQ objects [skip ci] * Add scripting directory to archive packaging [skip ci] * Specify C/CXX compiler variables on MinGW build * Only specify compiler on Linux mingw * Use gcc and g++ on Windows mingw * Specify generator for mingw * Don't specify toolchain on windows mingw * Changed citra.py to support Python 3 instead of Python 2 * Fix bug where RPC wouldn't restart after Stop/Start emulation * Added copyright to headers and reorganized includes and forward declarations
		
			
				
	
	
		
			95 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			95 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import zmq
 | |
| import struct
 | |
| import random
 | |
| import binascii
 | |
| 
 | |
| CURRENT_REQUEST_VERSION = 1
 | |
| MAX_REQUEST_DATA_SIZE = 32
 | |
| 
 | |
| REQUEST_TYPE_READ_MEMORY = 1
 | |
| REQUEST_TYPE_WRITE_MEMORY = 2
 | |
| 
 | |
| CITRA_PORT = "45987"
 | |
| 
 | |
| class Citra:
 | |
|     def __init__(self, address="127.0.0.1", port=CITRA_PORT):
 | |
|         self.context = zmq.Context()
 | |
|         self.socket = self.context.socket(zmq.REQ)
 | |
|         self.socket.connect("tcp://" + address + ":" + port)
 | |
| 
 | |
|     def is_connected(self):
 | |
|         return self.socket is not None
 | |
| 
 | |
|     def _generate_header(self, request_type, data_size):
 | |
|         request_id = random.getrandbits(32)
 | |
|         return (struct.pack("IIII", CURRENT_REQUEST_VERSION, request_id, request_type, data_size), request_id)
 | |
| 
 | |
|     def _read_and_validate_header(self, raw_reply, expected_id, expected_type):
 | |
|         reply_version, reply_id, reply_type, reply_data_size = struct.unpack("IIII", raw_reply[:4*4])
 | |
|         if (CURRENT_REQUEST_VERSION == reply_version and
 | |
|             expected_id == reply_id and
 | |
|             expected_type == reply_type and
 | |
|             reply_data_size == len(raw_reply[4*4:])):
 | |
|             return raw_reply[4*4:]
 | |
|         return None
 | |
| 
 | |
|     def read_memory(self, read_address, read_size):
 | |
|         """
 | |
|         >>> c.read_memory(0x100000, 4)
 | |
|         b'\\x07\\x00\\x00\\xeb'
 | |
|         """
 | |
|         result = bytes()
 | |
|         while read_size > 0:
 | |
|             temp_read_size = min(read_size, MAX_REQUEST_DATA_SIZE)
 | |
|             request_data = struct.pack("II", read_address, temp_read_size)
 | |
|             request, request_id = self._generate_header(REQUEST_TYPE_READ_MEMORY, len(request_data))
 | |
|             request += request_data
 | |
|             self.socket.send(request)
 | |
| 
 | |
|             raw_reply = self.socket.recv()
 | |
|             reply_data = self._read_and_validate_header(raw_reply, request_id, REQUEST_TYPE_READ_MEMORY)
 | |
| 
 | |
|             if reply_data:
 | |
|                 result += reply_data
 | |
|                 read_size -= len(reply_data)
 | |
|                 read_address += len(reply_data)
 | |
|             else:
 | |
|                 return None
 | |
| 
 | |
|         return result
 | |
| 
 | |
|     def write_memory(self, write_address, write_contents):
 | |
|         """
 | |
|         >>> c.write_memory(0x100000, b"\\xff\\xff\\xff\\xff")
 | |
|         True
 | |
|         >>> c.read_memory(0x100000, 4)
 | |
|         b'\\xff\\xff\\xff\\xff'
 | |
|         >>> c.write_memory(0x100000, b"\\x07\\x00\\x00\\xeb")
 | |
|         True
 | |
|         >>> c.read_memory(0x100000, 4)
 | |
|         b'\\x07\\x00\\x00\\xeb'
 | |
|         """
 | |
|         write_size = len(write_contents)
 | |
|         while write_size > 0:
 | |
|             temp_write_size = min(write_size, MAX_REQUEST_DATA_SIZE - 8)
 | |
|             request_data = struct.pack("II", write_address, temp_write_size)
 | |
|             request_data += write_contents[:temp_write_size]
 | |
|             request, request_id = self._generate_header(REQUEST_TYPE_WRITE_MEMORY, len(request_data))
 | |
|             request += request_data
 | |
|             self.socket.send(request)
 | |
| 
 | |
|             raw_reply = self.socket.recv()
 | |
|             reply_data = self._read_and_validate_header(raw_reply, request_id, REQUEST_TYPE_WRITE_MEMORY)
 | |
| 
 | |
|             if None != reply_data:
 | |
|                 write_address += temp_write_size
 | |
|                 write_size -= temp_write_size
 | |
|                 write_contents = write_contents[temp_write_size:]
 | |
|             else:
 | |
|                 return False
 | |
|         return True
 | |
| 
 | |
| if "__main__" == __name__:
 | |
|     import doctest
 | |
|     doctest.testmod(extraglobs={'c': Citra()})
 |