Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea475b528f | ||
|
|
2036e3c5b3 | ||
|
|
584d9f11e8 | ||
|
|
df020281f1 | ||
|
|
78c1b8869e | ||
|
|
87d5e8340b | ||
|
|
e6423d2f43 | ||
|
|
fac44a12b0 | ||
|
|
99ca7b1674 | ||
|
|
e066724a2f | ||
|
|
dce032de31 | ||
|
|
2f578b2bc4 | ||
|
|
0c1656e6ab |
43
addon_examples/leap_example.py
Normal file
43
addon_examples/leap_example.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
Example of how to control a viewer over LEAP
|
||||
|
||||
Must launch the viewer with `outleap-agent` LEAP script.
|
||||
See https://github.com/SaladDais/outleap/ for more info on LEAP / outleap.
|
||||
"""
|
||||
|
||||
import outleap
|
||||
from outleap.scripts.inspector import LEAPInspectorGUI
|
||||
|
||||
from hippolyzer.lib.proxy.addon_utils import send_chat, BaseAddon, show_message
|
||||
from hippolyzer.lib.proxy.commands import handle_command
|
||||
from hippolyzer.lib.proxy.region import ProxiedRegion
|
||||
from hippolyzer.lib.proxy.sessions import Session
|
||||
|
||||
|
||||
# Path found using `outleap-inspector`
|
||||
FPS_PATH = outleap.UIPath("/main_view/menu_stack/status_bar_container/status/time_and_media_bg/FPSText")
|
||||
|
||||
|
||||
class LEAPExampleAddon(BaseAddon):
|
||||
@handle_command()
|
||||
async def show_ui_inspector(self, session: Session, _region: ProxiedRegion):
|
||||
"""Spawn a GUI for inspecting the UI state"""
|
||||
if not session.leap_client:
|
||||
show_message("No LEAP client connected?")
|
||||
return
|
||||
LEAPInspectorGUI(session.leap_client).show()
|
||||
|
||||
@handle_command()
|
||||
async def say_fps(self, session: Session, _region: ProxiedRegion):
|
||||
"""Say your current FPS in chat"""
|
||||
if not session.leap_client:
|
||||
show_message("No LEAP client connected?")
|
||||
return
|
||||
|
||||
window_api = outleap.LLWindowAPI(session.leap_client)
|
||||
fps = (await window_api.get_info(path=FPS_PATH))['value']
|
||||
|
||||
send_chat(f"LEAP says I'm running at {fps} FPS!")
|
||||
|
||||
|
||||
addons = [LEAPExampleAddon()]
|
||||
@@ -9,6 +9,7 @@ from typing import Optional
|
||||
|
||||
import mitmproxy.ctx
|
||||
import mitmproxy.exceptions
|
||||
import outleap
|
||||
|
||||
from hippolyzer.lib.base import llsd
|
||||
from hippolyzer.lib.proxy.addons import AddonManager
|
||||
@@ -112,6 +113,7 @@ def start_proxy(session_manager: SessionManager, extra_addons: Optional[list] =
|
||||
|
||||
udp_proxy_port = session_manager.settings.SOCKS_PROXY_PORT
|
||||
http_proxy_port = session_manager.settings.HTTP_PROXY_PORT
|
||||
leap_port = session_manager.settings.LEAP_PORT
|
||||
if proxy_host is None:
|
||||
proxy_host = session_manager.settings.PROXY_BIND_ADDR
|
||||
|
||||
@@ -143,6 +145,10 @@ def start_proxy(session_manager: SessionManager, extra_addons: Optional[list] =
|
||||
coro = asyncio.start_server(server.handle_connection, proxy_host, udp_proxy_port)
|
||||
async_server = loop.run_until_complete(coro)
|
||||
|
||||
leap_server = outleap.LEAPBridgeServer(session_manager.leap_client_connected)
|
||||
coro = asyncio.start_server(leap_server.handle_connection, proxy_host, leap_port)
|
||||
async_leap_server = loop.run_until_complete(coro)
|
||||
|
||||
event_manager = MITMProxyEventManager(session_manager, flow_context)
|
||||
loop.create_task(event_manager.run())
|
||||
|
||||
@@ -169,6 +175,8 @@ def start_proxy(session_manager: SessionManager, extra_addons: Optional[list] =
|
||||
# Close the server
|
||||
print("Closing SOCKS server")
|
||||
async_server.close()
|
||||
print("Shutting down LEAP server")
|
||||
async_leap_server.close()
|
||||
print("Shutting down addons")
|
||||
AddonManager.shutdown()
|
||||
print("Waiting for SOCKS server to close")
|
||||
|
||||
@@ -59,17 +59,15 @@ class Event:
|
||||
continue
|
||||
if one_shot:
|
||||
self.unsubscribe(instance, *inner_args, **kwargs)
|
||||
if instance(args, *inner_args, **kwargs):
|
||||
if instance(args, *inner_args, **kwargs) and not one_shot:
|
||||
self.unsubscribe(instance, *inner_args, **kwargs)
|
||||
|
||||
def get_subscriber_count(self):
|
||||
def __len__(self):
|
||||
return len(self.subscribers)
|
||||
|
||||
def clear_subscribers(self):
|
||||
self.subscribers.clear()
|
||||
return self
|
||||
|
||||
__iadd__ = subscribe
|
||||
__isub__ = unsubscribe
|
||||
__call__ = notify
|
||||
__len__ = get_subscriber_count
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import calendar
|
||||
import datetime
|
||||
import struct
|
||||
import typing
|
||||
import uuid
|
||||
import zlib
|
||||
|
||||
from llbase.llsd import *
|
||||
from llsd import *
|
||||
# So we can directly reference the original wrapper funcs where necessary
|
||||
import llbase.llsd
|
||||
import llsd as base_llsd
|
||||
from llsd.base import is_string, is_unicode
|
||||
|
||||
from hippolyzer.lib.base.datatypes import *
|
||||
|
||||
|
||||
class HippoLLSDBaseFormatter(llbase.llsd.LLSDBaseFormatter):
|
||||
class HippoLLSDBaseFormatter(base_llsd.base.LLSDBaseFormatter):
|
||||
UUID: callable
|
||||
ARRAY: callable
|
||||
|
||||
@@ -24,12 +29,12 @@ class HippoLLSDBaseFormatter(llbase.llsd.LLSDBaseFormatter):
|
||||
return self.ARRAY(v.data())
|
||||
|
||||
|
||||
class HippoLLSDXMLFormatter(llbase.llsd.LLSDXMLFormatter, HippoLLSDBaseFormatter):
|
||||
class HippoLLSDXMLFormatter(base_llsd.serde_xml.LLSDXMLFormatter, HippoLLSDBaseFormatter):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
class HippoLLSDXMLPrettyFormatter(llbase.llsd.LLSDXMLPrettyFormatter, HippoLLSDBaseFormatter):
|
||||
class HippoLLSDXMLPrettyFormatter(base_llsd.serde_xml.LLSDXMLPrettyFormatter, HippoLLSDBaseFormatter):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@@ -42,7 +47,7 @@ def format_xml(val: typing.Any):
|
||||
return HippoLLSDXMLFormatter().format(val)
|
||||
|
||||
|
||||
class HippoLLSDNotationFormatter(llbase.llsd.LLSDNotationFormatter, HippoLLSDBaseFormatter):
|
||||
class HippoLLSDNotationFormatter(base_llsd.serde_notation.LLSDNotationFormatter, HippoLLSDBaseFormatter):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@@ -84,7 +89,7 @@ def _format_binary_recurse(something) -> bytes:
|
||||
return b'1'
|
||||
else:
|
||||
return b'0'
|
||||
elif is_integer(something):
|
||||
elif isinstance(something, int):
|
||||
try:
|
||||
return b'i' + struct.pack('!i', something)
|
||||
except (OverflowError, struct.error) as exc:
|
||||
@@ -129,7 +134,7 @@ def _format_binary_recurse(something) -> bytes:
|
||||
(type(something), something))
|
||||
|
||||
|
||||
class HippoLLSDBinaryParser(llbase.llsd.LLSDBinaryParser):
|
||||
class HippoLLSDBinaryParser(base_llsd.serde_binary.LLSDBinaryParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._dispatch[ord('u')] = lambda: UUID(bytes=self._getc(16))
|
||||
@@ -162,11 +167,11 @@ def parse_binary(data: bytes):
|
||||
|
||||
|
||||
def parse_xml(data: bytes):
|
||||
return llbase.llsd.parse_xml(data)
|
||||
return base_llsd.parse_xml(data)
|
||||
|
||||
|
||||
def parse_notation(data: bytes):
|
||||
return llbase.llsd.parse_notation(data)
|
||||
return base_llsd.parse_notation(data)
|
||||
|
||||
|
||||
def zip_llsd(val: typing.Any):
|
||||
@@ -189,6 +194,6 @@ def parse(data: bytes):
|
||||
else:
|
||||
return parse_notation(data)
|
||||
except KeyError as e:
|
||||
raise llbase.llsd.LLSDParseError('LLSD could not be parsed: %s' % (e,))
|
||||
raise base_llsd.LLSDParseError('LLSD could not be parsed: %s' % (e,))
|
||||
except TypeError as e:
|
||||
raise llbase.llsd.LLSDParseError('Input stream not of type bytes. %s' % (e,))
|
||||
raise base_llsd.LLSDParseError('Input stream not of type bytes. %s' % (e,))
|
||||
|
||||
@@ -77,9 +77,6 @@ class Circuit:
|
||||
)
|
||||
return self._send_prepared_message(message, transport)
|
||||
|
||||
# Temporary alias
|
||||
send_message = send
|
||||
|
||||
def send_reliable(self, message: Message, transport=None) -> asyncio.Future:
|
||||
"""send() wrapper that always sends reliably and allows `await`ing ACK receipt"""
|
||||
if not message.synthetic:
|
||||
|
||||
@@ -20,7 +20,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
"""
|
||||
from logging import getLogger
|
||||
|
||||
from llbase import llsd
|
||||
import llsd
|
||||
|
||||
from hippolyzer.lib.base.message.data import msg_details
|
||||
|
||||
|
||||
@@ -157,7 +157,6 @@ class UDPMessageDeserializer:
|
||||
reader.seek(current_template.get_msg_freq_num_len() + msg.offset)
|
||||
|
||||
for tmpl_block in current_template.blocks:
|
||||
LOG.debug("Parsing %s:%s" % (msg.name, tmpl_block.name))
|
||||
# EOF?
|
||||
if not len(reader):
|
||||
# Seems like even some "Single" blocks are optional?
|
||||
@@ -180,7 +179,6 @@ class UDPMessageDeserializer:
|
||||
|
||||
for i in range(repeat_count):
|
||||
current_block = Block(tmpl_block.name)
|
||||
LOG.debug("Adding block %s" % current_block.name)
|
||||
msg.add_block(current_block)
|
||||
|
||||
for tmpl_variable in tmpl_block.variables:
|
||||
|
||||
@@ -42,7 +42,7 @@ class MITMProxyEventManager:
|
||||
"UpdateNotecardAgentInventory", "UpdateNotecardTaskInventory",
|
||||
"UpdateScriptAgent", "UpdateScriptTask",
|
||||
"UpdateSettingsAgentInventory", "UpdateSettingsTaskInventory",
|
||||
"UploadBakedTexture",
|
||||
"UploadBakedTexture", "UploadAgentProfileImage",
|
||||
}
|
||||
|
||||
def __init__(self, session_manager: SessionManager, flow_context: HTTPFlowContext):
|
||||
|
||||
@@ -189,7 +189,7 @@ class EventQueueManager:
|
||||
# over the EQ. That will allow us to shove our own event onto the response once it comes in,
|
||||
# otherwise we have to wait until the EQ legitimately returns 200 due to a new event.
|
||||
# May or may not work in OpenSim.
|
||||
circuit.send_message(Message(
|
||||
circuit.send(Message(
|
||||
'PlacesQuery',
|
||||
Block('AgentData', AgentID=session.agent_id, SessionID=session.id, QueryID=UUID()),
|
||||
Block('TransactionData', TransactionID=UUID()),
|
||||
|
||||
@@ -9,6 +9,8 @@ import weakref
|
||||
from typing import *
|
||||
from weakref import ref
|
||||
|
||||
from outleap import LEAPClient
|
||||
|
||||
from hippolyzer.lib.base.datatypes import UUID
|
||||
from hippolyzer.lib.base.helpers import proxify
|
||||
from hippolyzer.lib.base.message.message import Message
|
||||
@@ -50,6 +52,7 @@ class Session(BaseClientSession):
|
||||
self.http_message_handler: MessageHandler[HippoHTTPFlow, str] = MessageHandler()
|
||||
self.objects = ProxyWorldObjectManager(self, session_manager.settings, session_manager.name_cache)
|
||||
self.inventory = ProxyInventoryManager(proxify(self))
|
||||
self.leap_client: Optional[LEAPClient] = None
|
||||
# Base path of a newview type cache directory for this session
|
||||
self.cache_dir: Optional[str] = None
|
||||
self._main_region = None
|
||||
@@ -187,6 +190,7 @@ class SessionManager:
|
||||
self.message_logger: Optional[BaseMessageLogger] = None
|
||||
self.addon_ctx: Dict[str, Any] = {}
|
||||
self.name_cache = ProxyNameCache()
|
||||
self.pending_leap_clients: List[LEAPClient] = []
|
||||
|
||||
def create_session(self, login_data) -> Session:
|
||||
session = Session.from_login_data(login_data, self)
|
||||
@@ -203,12 +207,23 @@ class SessionManager:
|
||||
if session.pending and session.id == session_id:
|
||||
logging.info("Claimed %r" % session)
|
||||
session.pending = False
|
||||
# TODO: less crap way of tying a LEAP client to a session
|
||||
while self.pending_leap_clients:
|
||||
leap_client = self.pending_leap_clients.pop(-1)
|
||||
# Client may have gone bad since it connected
|
||||
if not leap_client.connected:
|
||||
continue
|
||||
logging.info("Assigned LEAP client to session")
|
||||
session.leap_client = leap_client
|
||||
break
|
||||
return session
|
||||
return None
|
||||
|
||||
def close_session(self, session: Session):
|
||||
logging.info("Closed %r" % session)
|
||||
session.objects.clear()
|
||||
if session.leap_client:
|
||||
session.leap_client.disconnect()
|
||||
self.sessions.remove(session)
|
||||
|
||||
def resolve_cap(self, url: str) -> Optional["CapData"]:
|
||||
@@ -218,6 +233,9 @@ class SessionManager:
|
||||
return cap_data
|
||||
return CapData()
|
||||
|
||||
async def leap_client_connected(self, leap_client: LEAPClient):
|
||||
self.pending_leap_clients.append(leap_client)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class SelectionModel:
|
||||
|
||||
@@ -25,6 +25,7 @@ class EnvSettingDescriptor(SettingDescriptor):
|
||||
class ProxySettings(Settings):
|
||||
SOCKS_PROXY_PORT: int = EnvSettingDescriptor(9061, "HIPPO_UDP_PORT", int)
|
||||
HTTP_PROXY_PORT: int = EnvSettingDescriptor(9062, "HIPPO_HTTP_PORT", int)
|
||||
LEAP_PORT: int = EnvSettingDescriptor(9063, "HIPPO_LEAP_PORT", int)
|
||||
PROXY_BIND_ADDR: str = EnvSettingDescriptor("127.0.0.1", "HIPPO_BIND_HOST", str)
|
||||
REMOTELY_ACCESSIBLE: bool = SettingDescriptor(False)
|
||||
USE_VIEWER_OBJECT_CACHE: bool = SettingDescriptor(False)
|
||||
|
||||
@@ -108,4 +108,7 @@ CAP_TEMPLATES: List[CAPTemplate] = [
|
||||
CAPTemplate(cap_name='ViewerBenefits', method='GET', body=b'', query=set(), path=''),
|
||||
CAPTemplate(cap_name='SetDisplayName', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>display_name</key>\n <array>\n <string>OLD_DISPLAY_NAME</string>\n <string>NEW_DISPLAY_NAME</string>\n </array>\n </map>\n</llsd>\n', query=set(), path=''),
|
||||
CAPTemplate(cap_name='ObjectMediaNavigate', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>current_url</key>\n <string></string>\n <key>object_id</key>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n <key>texture_index</key>\n <integer></integer>\n </map>\n</llsd>\n', query=set(), path=''),
|
||||
CAPTemplate(cap_name='AgentProfile', method='GET', body=b'', query=set(), path='/<SOME_ID>'),
|
||||
CAPTemplate(cap_name='InterestList', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>mode</key>\n <string>360</string>\n </map>\n</llsd>', query=set(), path='/'),
|
||||
CAPTemplate(cap_name='RegionObjects', method='GET', body=b'', query=set(), path=''),
|
||||
]
|
||||
|
||||
@@ -28,13 +28,14 @@ Jinja2==3.0.3
|
||||
kaitaistruct==0.9
|
||||
lazy-object-proxy==1.6.0
|
||||
ldap3==2.9.1
|
||||
llbase==1.2.11
|
||||
llsd~=1.0.0
|
||||
lxml==4.6.4
|
||||
MarkupSafe==2.0.1
|
||||
mitmproxy==8.0.0
|
||||
msgpack==1.0.3
|
||||
multidict==5.2.0
|
||||
numpy==1.21.4
|
||||
outleap~=0.4.1
|
||||
parso==0.8.3
|
||||
passlib==1.7.4
|
||||
prompt-toolkit==3.0.23
|
||||
@@ -66,4 +67,4 @@ wcwidth==0.2.5
|
||||
Werkzeug==2.0.2
|
||||
wsproto==1.0.0
|
||||
yarl==1.7.2
|
||||
zstandard==0.15.2
|
||||
zstandard==0.15.2
|
||||
7
setup.py
7
setup.py
@@ -25,7 +25,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
here = path.abspath(path.dirname(__file__))
|
||||
|
||||
version = '0.12.1'
|
||||
version = '0.12.2'
|
||||
|
||||
with open(path.join(here, 'README.md')) as readme_fh:
|
||||
readme = readme_fh.read()
|
||||
@@ -75,13 +75,14 @@ setup(
|
||||
entry_points={
|
||||
'console_scripts': {
|
||||
'hippolyzer-gui = hippolyzer.apps.proxy_gui:gui_main',
|
||||
'hippolyzer-cli = hippolyzer.apps.proxy:main'
|
||||
'hippolyzer-cli = hippolyzer.apps.proxy:main',
|
||||
}
|
||||
},
|
||||
zip_safe=False,
|
||||
python_requires='>=3.8',
|
||||
install_requires=[
|
||||
'llbase>=1.2.5',
|
||||
'llsd<1.1.0',
|
||||
'outleap<1.0',
|
||||
'defusedxml',
|
||||
'aiohttp<4.0.0',
|
||||
'recordclass<0.15',
|
||||
|
||||
@@ -113,7 +113,7 @@ executables = [
|
||||
|
||||
setup(
|
||||
name="hippolyzer_gui",
|
||||
version="0.12.1",
|
||||
version="0.12.2",
|
||||
description="Hippolyzer GUI",
|
||||
options=options,
|
||||
executables=executables,
|
||||
|
||||
Reference in New Issue
Block a user