From 48b69b777532539cbe3d93f92807eacd650b9f1d Mon Sep 17 00:00:00 2001 From: Poleric Date: Mon, 1 Jun 2026 22:33:12 +0800 Subject: [PATCH] feat: implement game logic --- .gitmodules | 3 + pyproject.toml | 9 ++- ref/StarResonanceData | 1 + scripts/example_game.py | 16 +++++ scripts/sniff_items.py | 36 ++++++++++ main.py => src/inventory_wars/__init__.py | 0 src/inventory_wars/const.py | 85 +++++++++++++++++++++++ src/inventory_wars/game.py | 77 ++++++++++++++++++++ src/inventory_wars/models.py | 50 +++++++++++++ src/inventory_wars/sniffer.py | 51 ++++++++++++++ uv.lock | 81 ++++++++++++++++++++- 11 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 .gitmodules create mode 160000 ref/StarResonanceData create mode 100644 scripts/example_game.py create mode 100644 scripts/sniff_items.py rename main.py => src/inventory_wars/__init__.py (100%) create mode 100644 src/inventory_wars/const.py create mode 100644 src/inventory_wars/game.py create mode 100644 src/inventory_wars/models.py create mode 100644 src/inventory_wars/sniffer.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bec1c50 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ref/StarResonanceData"] + path = ref/StarResonanceData + url = https://github.com/Poleric/StarResonanceData diff --git a/pyproject.toml b/pyproject.toml index c1c9f37..696bd4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,18 +5,23 @@ authors = [ { name = "Poleric", email = "laffey@dizzynight.moe" } ] description = "A library of tools to handle and process packets from BPSR." -readme = "README.md" +#readme = "README.md" requires-python = ">=3.14" dependencies = [ "polars>=1.41.2", + "protobuf>=7.35.0", "pyside6>=6.11.1", "scapy>=2.7.0", + "sqlalchemy>=2.0.50", "star-resonance-tracer>=0.1.0", ] - [project.urls] Homepage = "https://git.dizzynight.moe/DizzyNight/inventory-wars" +[build-system] +requires = ["uv_build>=0.11.15,<0.12"] +build-backend = "uv_build" + [tool.uv.sources] star-resonance-tracer = { git = "https://github.com/Poleric/star-resonance-tracer-py" } diff --git a/ref/StarResonanceData b/ref/StarResonanceData new file mode 160000 index 0000000..3ac74fc --- /dev/null +++ b/ref/StarResonanceData @@ -0,0 +1 @@ +Subproject commit 3ac74fc3c3fa8bb88df92a645ea69378ffe26584 diff --git a/scripts/example_game.py b/scripts/example_game.py new file mode 100644 index 0000000..d77e49d --- /dev/null +++ b/scripts/example_game.py @@ -0,0 +1,16 @@ +import logging +from sqlalchemy import create_engine +from sqlalchemy.orm import Session + +from star_resonance_tracer.proto.enum_chit_chat_channel_type_pb2 import ChitChatChannelType + +from inventory_wars.game import Game +from inventory_wars.models import Base + +logging.basicConfig(level=logging.INFO) + +engine = create_engine("sqlite:///app.db") +Base.metadata.create_all(engine) + +game = Game(Session(engine), item_id=1040202, listening_channels=[ChitChatChannelType.ChannelTeam]) +game.start() diff --git a/scripts/sniff_items.py b/scripts/sniff_items.py new file mode 100644 index 0000000..65509f3 --- /dev/null +++ b/scripts/sniff_items.py @@ -0,0 +1,36 @@ +from star_resonance_tracer.proto.enum_chit_chat_msg_type_pb2 import ChitChatMsgType +from star_resonance_tracer.proto.stru_place_holder_item_pb2 import PlaceHolderItem +from star_resonance_tracer.sniffer import Sniffer + +from inventory_wars.const import ChitChatNtf, decode_placeholder, HypertextVariant +from inventory_wars.sniffer import start_sniffing +from star_resonance_tracer.proto.serv_chit_chat_ntf_pb2 import ChitChatNtf as ChitChatNtfPb + + +def on_chit_chat_msg(event: ChitChatNtfPb.NotifyNewestChitChatMsgs) -> None: + req = event.vRequest + + if req.chatMsg.msgInfo.msgType is not ChitChatMsgType.ChatMsgHypertext: + return + + hypertext = req.chatMsg.msgInfo.chatHypertext + if hypertext.configId != HypertextVariant.ITEM_SHARING.value: + return + + for placeholder in hypertext.hypertextContents: + placeholder_content = decode_placeholder(placeholder) + match placeholder_content: + case PlaceHolderItem() as item: + print(item) + + + +sniffer = Sniffer() +sniffer.set_service_type( + ChitChatNtf.ServiceId.value, + ChitChatNtf.Method.NotifyNewestChitChatMsgs.value, + ChitChatNtfPb.NotifyNewestChitChatMsgs +) +sniffer.on_service(ChitChatNtfPb.NotifyNewestChitChatMsgs, on_chit_chat_msg) + +start_sniffing(sniffer) \ No newline at end of file diff --git a/main.py b/src/inventory_wars/__init__.py similarity index 100% rename from main.py rename to src/inventory_wars/__init__.py diff --git a/src/inventory_wars/const.py b/src/inventory_wars/const.py new file mode 100644 index 0000000..3cee5eb --- /dev/null +++ b/src/inventory_wars/const.py @@ -0,0 +1,85 @@ +from enum import Enum + +import polars as pl +from google.protobuf.message import Message +from star_resonance_tracer.proto.enum_place_holder_type_pb2 import PlaceHolderType +from star_resonance_tracer.proto.stru_place_holder_buff_pb2 import PlaceHolderBuff +from star_resonance_tracer.proto.stru_place_holder_fish_item_pb2 import PlaceHolderFishItem +from star_resonance_tracer.proto.stru_place_holder_fish_personal_total_pb2 import PlaceHolderFishPersonalTotal +from star_resonance_tracer.proto.stru_place_holder_fish_rank_pb2 import PlaceHolderFishRank +from star_resonance_tracer.proto.stru_place_holder_item_pb2 import PlaceHolderItem +from star_resonance_tracer.proto.stru_place_holder_master_mode_pb2 import PlaceHolderMasterMode +from star_resonance_tracer.proto.stru_place_holder_pb2 import PlaceHolder +from star_resonance_tracer.proto.stru_place_holder_player_pb2 import PlaceHolderPlayer +from star_resonance_tracer.proto.stru_place_holder_scene_position_pb2 import PlaceHolderScenePosition +from star_resonance_tracer.proto.stru_place_holder_str_pb2 import PlaceHolderStr +from star_resonance_tracer.proto.stru_place_holder_timestamp_pb2 import PlaceHolderTimestamp +from star_resonance_tracer.proto.stru_place_holder_union_pb2 import PlaceHolderUnion +from star_resonance_tracer.proto.stru_place_holder_val_pb2 import PlaceHolderVal + +__all__ = ( + "ChitChatNtf", + "HypertextVariant", + "decode_placeholder", + "get_item_name" +) + +PLACEHOLDER_MAPPING: dict[PlaceHolderType, type[Message]] = { + PlaceHolderType.PlaceHolderTypeVal: PlaceHolderVal, + PlaceHolderType.PlaceHolderTypePlayer: PlaceHolderPlayer, + PlaceHolderType.PlaceHolderTypeItem: PlaceHolderItem, + PlaceHolderType.PlaceHolderTypeUnion: PlaceHolderUnion, + PlaceHolderType.PlaceHolderTypeBuff: PlaceHolderBuff, + PlaceHolderType.PlaceHolderTypeTimestamp: PlaceHolderTimestamp, + PlaceHolderType.PlaceHolderTypeString: PlaceHolderStr, + PlaceHolderType.PlaceHolderTypeFishPersonalTotal: PlaceHolderFishPersonalTotal, + PlaceHolderType.PlaceHolderTypeFishItem: PlaceHolderFishItem, + PlaceHolderType.PlaceHolderTypeFishRank: PlaceHolderFishRank, + PlaceHolderType.PlaceHolderTypeMasterMode: PlaceHolderMasterMode, + PlaceHolderType.PlaceHolderTypeScenePosition: PlaceHolderScenePosition, +} + +type SupportedPlaceholders = ( + PlaceHolderVal + | PlaceHolderPlayer + | PlaceHolderItem + | PlaceHolderUnion + | PlaceHolderBuff + | PlaceHolderTimestamp + | PlaceHolderStr + | PlaceHolderFishPersonalTotal + | PlaceHolderFishItem + | PlaceHolderFishRank + | PlaceHolderMasterMode + | PlaceHolderScenePosition +) + + +class ChitChatNtf(Enum): + ServiceId = 0x9d4a768 + + class Method(Enum): + NotifyNewestChitChatMsgs = 0x1 + + +class HypertextVariant(Enum): + ITEM_SHARING = 3000001 + + +def decode_placeholder(placeholder: PlaceHolder) -> SupportedPlaceholders: + decoder = PLACEHOLDER_MAPPING.get(placeholder.type) + if decoder is None: + raise NotImplementedError + + # All compiled protobuf messages support ``FromString`` + return decoder.FromString(placeholder.bytesContent) # type: ignore[attr-defined] + + +ITEM_NAME = pl.read_json("./ref/StarResonanceData/ztable/ItemTable.json").transpose().unnest() + + +def get_item_name(item_config_id: int) -> str | None: + try: + return ITEM_NAME.filter(pl.col.Id == item_config_id).select("Name").item() + except ValueError: + return None diff --git a/src/inventory_wars/game.py b/src/inventory_wars/game.py new file mode 100644 index 0000000..26ecf1c --- /dev/null +++ b/src/inventory_wars/game.py @@ -0,0 +1,77 @@ +from datetime import datetime + +from sqlalchemy.orm import Session + +import logging +from star_resonance_tracer.proto.stru_place_holder_item_pb2 import PlaceHolderItem +from star_resonance_tracer.sniffer import Sniffer + +from inventory_wars.models import User, ItemShare, Event +from inventory_wars.const import ChitChatNtf, HypertextVariant, decode_placeholder +from star_resonance_tracer.proto.serv_chit_chat_ntf_pb2 import ChitChatNtf as ChitChatNtfPb +from star_resonance_tracer.proto.enum_chit_chat_channel_type_pb2 import ChitChatChannelType +from star_resonance_tracer.proto.enum_chit_chat_msg_type_pb2 import ChitChatMsgType + +from inventory_wars.sniffer import start_sniffing + +logger = logging.getLogger(__name__) + + +class Game: + def __init__(self, session: Session, *, item_id: int, listening_channels: list[ChitChatChannelType] | None = None): + self.session = session + self.listening_channels = listening_channels or [] + self.event = Event(item_id=item_id) + + def start(self) -> None: + self.event.timestamp = datetime.now() + + self.session.add(self.event) + self.session.commit() + + logger.info(f"Started {self.event.id} at {self.event.timestamp} for item {self.event.item_id}") + + sniffer = Sniffer() + sniffer.set_service_type( + ChitChatNtf.ServiceId.value, + ChitChatNtf.Method.NotifyNewestChitChatMsgs.value, + ChitChatNtfPb.NotifyNewestChitChatMsgs + ) + sniffer.on_service(ChitChatNtfPb.NotifyNewestChitChatMsgs, self.on_chit_chat_msg) + + start_sniffing(sniffer) + + def on_chit_chat_msg(self, event: ChitChatNtfPb.NotifyNewestChitChatMsgs) -> None: + req = event.vRequest + if req.channelType not in self.listening_channels: + return + + if req.chatMsg.msgInfo.msgType is not ChitChatMsgType.ChatMsgHypertext: + return + + hypertext = req.chatMsg.msgInfo.chatHypertext + if hypertext.configId != HypertextVariant.ITEM_SHARING.value: + return + + for placeholder in hypertext.hypertextContents: + placeholder_content = decode_placeholder(placeholder) + match placeholder_content: + case PlaceHolderItem() as item: + if item.configId != self.event.item_id: + continue + + user = User(id=req.chatMsg.sendCharInfo.charID, username=req.chatMsg.sendCharInfo.name) + item_share = ItemShare( + timestamp=datetime.fromtimestamp(req.chatMsg.timestamp), + user_id=user.id, + event_id=self.event.id, + count=item.ItemDetail.count, + raw=item.SerializeToString() + ) + + self.session.merge(user) + self.session.add(item_share) + self.session.commit() + + logger.info(f"{user.username} guessed {self.event.item_id} " + f"with {item_share.count} at {item_share.timestamp}") diff --git a/src/inventory_wars/models.py b/src/inventory_wars/models.py new file mode 100644 index 0000000..d385b55 --- /dev/null +++ b/src/inventory_wars/models.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from datetime import datetime + +from sqlalchemy import DateTime, ForeignKey, UniqueConstraint, BLOB + +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship + +__all__ = ( + "Base", + "User", + "Event", + "ItemShare" +) + + +class Base(DeclarativeBase): + pass + + +class User(Base): + __tablename__ = "user" + + id: Mapped[int] = mapped_column(primary_key=True) + username: Mapped[str] + + shares: Mapped[set[ItemShare]] = relationship(back_populates="user") + + +class Event(Base): + __tablename__ = "event" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + item_id: Mapped[int] + timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=False)) + + shares: Mapped[set[ItemShare]] = relationship(back_populates="event") + + +class ItemShare(Base): + __tablename__ = "item_share" + + timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=False), primary_key=True) + user_id: Mapped[int] = mapped_column(ForeignKey("user.id")) + event_id: Mapped[int] = mapped_column(ForeignKey("event.id")) + count: Mapped[int] + raw: Mapped[bytes] = mapped_column(BLOB()) + + user: Mapped[User] = relationship(back_populates="shares") + event: Mapped[Event] = relationship(back_populates="shares") diff --git a/src/inventory_wars/sniffer.py b/src/inventory_wars/sniffer.py new file mode 100644 index 0000000..cb3995f --- /dev/null +++ b/src/inventory_wars/sniffer.py @@ -0,0 +1,51 @@ +import logging +import os + +from star_resonance_tracer.sniffer import Sniffer +from star_resonance_tracer.connection import Connection, PidBasedConnectionDetector +from star_resonance_tracer.utils import TCPReassembler +from collections import defaultdict +from scapy.config import conf +from scapy.layers.inet import TCP, IP +from scapy.packet import Packet, Raw +from scapy.sendrecv import sniff + +__all__ = ( + "start_sniffing", +) + +conf.layers.filter([TCP, IP]) +logger = logging.getLogger(__name__) + + +def start_sniffing(sniffer: Sniffer) -> None: + executable_name = "StarSEA" + + detector = PidBasedConnectionDetector() + assert detector.add_from_executable_name(executable_name) + reassemblers: dict[Connection, TCPReassembler] = defaultdict(TCPReassembler) + + def on_packet(packet: Packet): + if TCP not in packet or Raw not in packet: + return + + tcp = packet[TCP] + ip = packet[IP] + + connection = Connection.from_tuple(ip.src, tcp.sport, ip.dst, tcp.dport) + payload = bytes(packet[Raw]) + + try: + payload = reassemblers[connection].push(tcp.seq, payload) + except KeyError: + return + + if not payload: + return + + try: + sniffer.process_packet(payload) + except Exception: + logger.exception("Silently caught exception") + + sniff(filter=detector.as_bpf_filter(), prn=on_packet, store=False) diff --git a/uv.lock b/uv.lock index feab484..d809e15 100644 --- a/uv.lock +++ b/uv.lock @@ -11,22 +11,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" }, ] +[[package]] +name = "greenlet" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, + { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, + { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, + { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/8e8e8417b7bf28639a5a56356ef934d0375e1d0c70a57e04d7701e870ffe/greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", size = 236862, upload-time = "2026-05-20T13:09:10.498Z" }, + { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, + { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, + { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, + { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, + { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ae/4e623a7e6d4d2a5f4cb8e4c82de4169fc637942caae68d6e676b8a128ac5/greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", size = 236853, upload-time = "2026-05-20T13:15:37.301Z" }, + { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, + { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, + { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" }, +] + [[package]] name = "inventory-wars" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "polars" }, + { name = "protobuf" }, { name = "pyside6" }, { name = "scapy" }, + { name = "sqlalchemy" }, { name = "star-resonance-tracer" }, ] [package.metadata] requires-dist = [ { name = "polars", specifier = ">=1.41.2" }, + { name = "protobuf", specifier = ">=7.35.0" }, { name = "pyside6", specifier = ">=6.11.1" }, { name = "scapy", specifier = ">=2.7.0" }, + { name = "sqlalchemy", specifier = ">=2.0.50" }, { name = "star-resonance-tracer", git = "https://github.com/Poleric/star-resonance-tracer-py" }, ] @@ -164,6 +207,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/d1/f15ca0e1666faae02c945f48e745ea35f8fcd8243b176109b4e2c4251f47/shiboken6-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:7c8d9af17db4495d4fa5b1c393f218311c4855546b9dfa6a0bd21bcd66b55e9d", size = 1784170, upload-time = "2026-05-13T09:47:07.617Z" }, ] +[[package]] +name = "sqlalchemy" +version = "2.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" }, + { url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" }, + { url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" }, + { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, +] + [[package]] name = "star-resonance-tracer" version = "0.1.4" @@ -175,6 +245,15 @@ dependencies = [ { name = "zstandard" }, ] +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + [[package]] name = "zstandard" version = "0.25.0"