From: Joann Mõndresku Date: Sun, 19 May 2024 19:44:35 +0000 (+0300) Subject: Co-op join/leave. Auto-join existing public ones X-Git-Url: https://git.based.quest/%7B%7B.Site.BaseURL%7D%7D?a=commitdiff_plain;h=8ba50b9ffa73b2b26d7a0967b2df3f06a704ecf2;p=reEgg.git Co-op join/leave. Auto-join existing public ones --- diff --git a/app.py b/app.py index 47f127f..549871a 100644 --- a/app.py +++ b/app.py @@ -24,6 +24,8 @@ contracts.load_contracts() db_store.create_backups_db() db_store.create_contracts_db() +contracts_motd = "Welcome to reEgg Server Emulator\nLeggacy contracts available every Monday/Friday" + def calculate_backup_checksum(SaveBackup): # WIP - Need to figure out what the 0 and 61 still are. # 61 - best fit is SaveBackup.mission.missions array length, which same time makes zero sense @@ -110,16 +112,26 @@ def ei_daily_gift_info(): GiftResponse.seconds_to_next_day = 86400 - DateInfo.seconds return base64.b64encode(GiftResponse.SerializeToString()) +def populate_contracts_response(obj): + if obj is None: + obj = EIProto.ContractsResponse() + obj.warning_message = contracts_motd + for contract in contracts.get_active_contracts(): + c = obj.contracts.add() + c.CopyFrom(contract) + return obj + +@app.route('/ei/get_contracts', methods=['POST']) +def ei_get_contracts(): + return base64.b64encode(populate_contracts_response(None).SerializeToString()) + @app.route('/ei/get_periodicals', methods=['POST']) def ei_periodicals_request(): PeriodicalResp = EIProto.PeriodicalsResponse() - PeriodicalResp.contracts.warning_message = "Welcome to reEgg Server Emulator\nLeggacy contracts available every Monday/Friday" + populate_contracts_response(PeriodicalResp.contracts) for evt in events.get_active_events(): e = PeriodicalResp.events.events.add() e.CopyFrom(evt) - for contract in contracts.get_active_contracts(): - c = PeriodicalResp.contracts.contracts.add() - c.CopyFrom(contract) return base64.b64encode(PeriodicalResp.SerializeToString()) @app.route('/ei/query_coop', methods=['POST']) @@ -136,8 +148,10 @@ def ei_query_coop(): if QueryCoopResp.exists: if QueryCoop.league != db_query: QueryCoopResp.different_league = True - # TODO: Ask contract defs for max coop allowed. - #print(QueryCoopResp) + else: + ContractInfo = contracts.get_contract_by_identifier(QueryCoop.contract_identifier) + if db_store.is_coop_full(QueryCoop.coop_identifier, ContractInfo.max_coop_size): + QueryCoopResp.full = True return base64.b64encode(QueryCoopResp.SerializeToString()) @app.route('/ei/create_coop', methods=['POST']) @@ -208,17 +222,93 @@ def ei_update_coop_status(): Resp.finalized = True return base64.b64encode(Resp.SerializeToString()) +@app.route('/ei/join_coop', methods=['POST']) +def ei_join_coop(): + data = base64.b64decode(request.form["data"].replace(" ", "+")) + JoinCoopRequest = EIProto.JoinCoopRequest() + JoinCoopRequest.ParseFromString(data) + JoinResponse = EIProto.JoinCoopResponse() + JoinResponse.coop_identifier = JoinCoopRequest.coop_identifier + db_query = db_store.is_coop_identifier_used(JoinCoopRequest.coop_identifier) + if db_query is None: + JoinResponse.success = False + JoinResponse.message = "That co-op doesn't exist." + return base64.b64encode(JoinResponse.SerializeToString()) + if db_query != JoinCoopRequest.league: + JoinResponse.success = False + JoinResponse.message = "You can't join a " + ("Elite" if db_query == 1 else "Standard") + " contract." + return base64.b64encode(JoinResponse.SerializeToString()) + BaseInfo = db_store.get_contract_info(JoinCoopRequest.coop_identifier) + ContribInfo = db_store.get_coop_contributors(JoinCoopRequest.coop_identifier) + ContractInfo = contracts.get_contract_by_identifier(BaseInfo[2]) + if len(ContribInfo) - 1 >= ContractInfo.max_coop_size: + JoinResponse.success = False + JoinResponse.message = "Co-op is full!" + return base64.b64encode(JoinResponse.SerializeToString()) + if BaseInfo[2] != JoinCoopRequest.contract_identifier: + JoinResponse.success = False + JoinResponse.message = "This co-op is not made for this contract." + return base64.b64encode(JoinResponse.SerializeToString()) + # TODO: bans from coops + db_store.insert_coop_contribution(JoinCoopRequest.coop_identifier, JoinCoopRequest.user_id, JoinCoopRequest.user_name, JoinCoopRequest.soul_power) + JoinResponse.success = True + JoinResponse.banned = False + JoinResponse.seconds_remaining = (BaseInfo[4] + int(ContractInfo.length_seconds)) - int(time.time()) + return base64.b64encode(JoinResponse.SerializeToString()) + @app.route('/ei/auto_join_coop', methods=['POST']) def ei_auto_join_coop(): data = base64.b64decode(request.form["data"].replace(" ", "+")) AutoJoinCoopRequest = EIProto.AutoJoinCoopRequest() AutoJoinCoopRequest.ParseFromString(data) - print(AutoJoinCoopRequest) Resp = EIProto.JoinCoopResponse() + Contract = contracts.get_contract_by_identifier(AutoJoinCoopRequest.contract_identifier) + if Contract is None: + Resp.success = False + Resp.message = "Invalid contract." + return base64.b64encode(Resp.SerializeToString()) + coops = db_store.get_public_coops(AutoJoinCoopRequest.contract_identifier) Resp.success = False - Resp.message = "Unable to find any public co-ops to join." + for coop in coops: + coop_identifier = coop[0] + # TODO: Ban check + if not db_store.is_coop_full(coop_identifier, Contract.max_coop_size): + Resp.success = True + db_store.insert_coop_contribution(coop_identifier, AutoJoinCoopRequest.user_id, AutoJoinCoopRequest.user_name, AutoJoinCoopRequest.soul_power) + BaseInfo = db_store.get_contract_info(coop_identifier) + Resp.coop_identifier = coop_identifier + Resp.banned = False + Resp.seconds_remaining = (BaseInfo[4] + int(Contract.length_seconds)) - int(time.time()) + break + if Resp.success == False: + Resp.message = "No public contracts found." + # TODO: Auto-create co-op if none found return base64.b64encode(Resp.SerializeToString()) +@app.route('/ei/leave_coop', methods=['POST']) +def ei_leave_coop(): + data = base64.b64decode(request.form["data"].replace(" ", "+")) + LeaveCoopRequest = EIProto.LeaveCoopRequest() + LeaveCoopRequest.ParseFromString(data) + db_store.erase_coop_contribution(LeaveCoopRequest.coop_identifier, LeaveCoopRequest.player_identifier) + # TODO: Deepdive ghidra to see what this expects? + return "" + +@app.route('/ei/update_coop_permissions', methods=['POST']) +def ei_update_coop_permissions(): + data = base64.b64decode(request.form["data"].replace(" ", "+")) + PermUpdateReq = EIProto.UpdateCoopPermissionsRequest() + PermUpdateReq.ParseFromString(data) + PermUpdateResp = EIProto.UpdateCoopPermissionsResponse() + BaseInfo = db_store.get_contract_info(PermUpdateReq.coop_identifier) + if BaseInfo[5] != PermUpdateReq.requesting_user_id: + PermUpdateResp.success = False + PermUpdateResp.message = "Only the co-op creator can change the permissions." + return base64.b64encode(PermUpdateResp.SerializeToString()) + db_store.change_coop_public_state(PermUpdateReq.coop_identifier, PermUpdateReq.public) + PermUpdateResp.success = True + return base64.b64encode(PermUpdateResp.SerializeToString()) + @app.route('/ei/', methods=['POST']) def ei_unidentified_routes(subpath): print("UNIMPLEMENTED REQ: /ei/" + subpath) diff --git a/db_store.py b/db_store.py index bdb9c62..d1cbe18 100644 --- a/db_store.py +++ b/db_store.py @@ -24,7 +24,7 @@ def create_contracts_db(): con = get_connection("contracts") cur = con.cursor() try: - cur.execute("CREATE TABLE Contracts(ID INTEGER PRIMARY KEY AUTOINCREMENT, CoopName TEXT, ContractName TEXT, League SMALLINT, ContractStamp BIGINT, OwnerDevice TEXT)") + cur.execute("CREATE TABLE Contracts(ID INTEGER PRIMARY KEY AUTOINCREMENT, CoopName TEXT, ContractName TEXT, League SMALLINT, ContractStamp BIGINT, OwnerDevice TEXT, Public BOOL)") cur.execute("CREATE TABLE ContractMember(DeviceID TEXT, CoopName TEXT, DisplayName TEXT, LastVisit BIGINT, Contribution BIGINT, ContribRate BIGINT, SoulPower DOUBLE, BoostTokens INTEGER, TimeCheats INT, Banned BOOL, LeftCoop BOOL)") cur.execute("CREATE TABLE ContractGift(DeviceID TEXT, RewardType TEXT, Quantity INTEGER)") except: @@ -50,9 +50,32 @@ def is_coop_full(coop_identifier, max_members): cur = con.cursor() res = cur.execute('SELECT COUNT(DeviceID) FROM ContractMember WHERE CoopName="' + coop_identifier + '"') retval = res.fetchone() - print(retval) con.close() - return False + if retval is None: + return False + return retval[0] >= max_members + +def get_public_coops(contract_identifier): + con = get_connection("contracts") + cur = con.cursor() + res = cur.execute('SELECT CoopName FROM Contracts WHERE Public=1 AND ContractName="' + contract_identifier + '"') + ret = res.fetchall() + con.close() + if ret is None: + return [] + else: + return ret + +def change_coop_public_state(coop_identifier, public): + if not is_coop_identifier_used(coop_identifier): + return False + con = get_connection("contracts") + cur = con.cursor() + cur.execute("UPDATE Contracts SET Public=? WHERE CoopName=?", (public, coop_identifier)) + con.commit() + con.close() + return True + def is_coop_identifier_used(coop_identifier): if not coop_identifier.isalnum(): @@ -62,7 +85,7 @@ def is_coop_identifier_used(coop_identifier): res = cur.execute('SELECT League FROM Contracts WHERE CoopName="' + coop_identifier + '"') retval = res.fetchone() con.close() - return retval + return retval[0] if retval is not None else None def create_coop_contract(coop_identifier, contract_id, league, stamp, device_id, display_name): if is_coop_identifier_used(coop_identifier): @@ -106,6 +129,32 @@ def update_coop_contribution(coop_identifier, device_id, contribution, rate, sou con.close() return True +def insert_coop_contribution(coop_identifier, device_id, device_name, soul_power): + if not is_coop_identifier_used(coop_identifier): + return False + if not device_id.isalnum(): + return False + con = get_connection("contracts") + cur = con.cursor() + stamp = int(time.time()) + values = (device_id, coop_identifier, device_name, int(time.time()), 0, 0, soul_power) + cur.execute("INSERT INTO ContractMember(DeviceID, CoopName, DisplayName, LastVisit, Contribution, ContribRate, SoulPower) VALUES(?, ?, ?, ?, ?, ?, ?)", values) + con.commit() + con.close() + return True + +def erase_coop_contribution(coop_identifier, device_id): + if not is_coop_identifier_used(coop_identifier): + return + if not device_id.isalnum(): + return + con = get_connection("contracts") + cur = con.cursor() + cur.execute("DELETE FROM ContractMember WHERE DeviceID=? AND CoopName=?", (device_id, coop_identifier)) + con.commit() + con.close() + return + def get_coop_memberships(device_id): if not device_id.isalnum(): return None