| 
									
										
										
										
											2021-05-16 01:16:51 +02:00
										 |  |  | import base64 | 
					
						
							| 
									
										
										
										
											2022-10-17 01:08:31 +02:00
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | import pickle | 
					
						
							| 
									
										
										
										
											2022-10-17 01:08:31 +02:00
										 |  |  | import typing | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  | import uuid | 
					
						
							| 
									
										
										
										
											2022-10-17 01:08:31 +02:00
										 |  |  | import zipfile | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | import zlib | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | from io import BytesIO | 
					
						
							| 
									
										
										
										
											2024-04-02 16:45:07 +02:00
										 |  |  | from flask import request, flash, redirect, url_for, session, render_template, abort | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:42 +02:00
										 |  |  | from markupsafe import Markup | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | from pony.orm import commit, flush, select, rollback | 
					
						
							|  |  |  | from pony.orm.core import TransactionIntegrityError | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  | import schema | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-17 01:08:31 +02:00
										 |  |  | import MultiServer | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:42 +02:00
										 |  |  | from NetUtils import SlotType | 
					
						
							| 
									
										
										
										
											2022-09-30 00:36:30 +02:00
										 |  |  | from Utils import VersionException, __version__ | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  | from worlds import GamesPackage | 
					
						
							| 
									
										
										
										
											2022-09-30 00:36:30 +02:00
										 |  |  | from worlds.Files import AutoPatchRegister | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  | from worlds.AutoWorld import data_package_checksum | 
					
						
							| 
									
										
										
										
											2022-10-17 01:08:31 +02:00
										 |  |  | from . import app | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | from .models import Seed, Room, Slot, GameDataPackage | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  | banned_extensions = (".sfc", ".z64", ".n64", ".nes", ".smc", ".sms", ".gb", ".gbc", ".gba") | 
					
						
							|  |  |  | allowed_options_extensions = (".yaml", ".json", ".yml", ".txt", ".zip") | 
					
						
							|  |  |  | allowed_generation_extensions = (".archipelago", ".zip") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  | games_package_schema = schema.Schema({ | 
					
						
							|  |  |  |     "item_name_groups": {str: [str]}, | 
					
						
							|  |  |  |     "item_name_to_id": {str: int}, | 
					
						
							|  |  |  |     "location_name_groups": {str: [str]}, | 
					
						
							|  |  |  |     "location_name_to_id": {str: int}, | 
					
						
							|  |  |  |     schema.Optional("checksum"): str, | 
					
						
							|  |  |  |     schema.Optional("version"): int, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | def allowed_options(filename: str) -> bool: | 
					
						
							|  |  |  |     return filename.endswith(allowed_options_extensions) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def allowed_generation(filename: str) -> bool: | 
					
						
							|  |  |  |     return filename.endswith(allowed_generation_extensions) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def banned_file(filename: str) -> bool: | 
					
						
							|  |  |  |     return filename.endswith(banned_extensions) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  | def process_multidata(compressed_multidata, files={}): | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  |     game_data: GamesPackage | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  |     decompressed_multidata = MultiServer.Context.decompress(compressed_multidata) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     slots: typing.Set[Slot] = set() | 
					
						
							|  |  |  |     if "datapackage" in decompressed_multidata: | 
					
						
							|  |  |  |         # strip datapackage from multidata, leaving only the checksums | 
					
						
							|  |  |  |         game_data_packages: typing.List[GameDataPackage] = [] | 
					
						
							|  |  |  |         for game, game_data in decompressed_multidata["datapackage"].items(): | 
					
						
							|  |  |  |             if game_data.get("checksum"): | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  |                 original_checksum = game_data.pop("checksum") | 
					
						
							|  |  |  |                 game_data = games_package_schema.validate(game_data) | 
					
						
							|  |  |  |                 game_data = {key: value for key, value in sorted(game_data.items())} | 
					
						
							|  |  |  |                 game_data["checksum"] = data_package_checksum(game_data) | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  |                 game_data_package = GameDataPackage(checksum=game_data["checksum"], | 
					
						
							|  |  |  |                                                     data=pickle.dumps(game_data)) | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  |                 if original_checksum != game_data["checksum"]: | 
					
						
							|  |  |  |                     raise Exception(f"Original checksum {original_checksum} != " | 
					
						
							|  |  |  |                                     f"calculated checksum {game_data['checksum']} " | 
					
						
							|  |  |  |                                     f"for game {game}.") | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  |                 decompressed_multidata["datapackage"][game] = { | 
					
						
							|  |  |  |                     "version": game_data.get("version", 0), | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  |                     "checksum": game_data["checksum"], | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     commit()  # commit game data package | 
					
						
							|  |  |  |                     game_data_packages.append(game_data_package) | 
					
						
							|  |  |  |                 except TransactionIntegrityError: | 
					
						
							|  |  |  |                     del game_data_package | 
					
						
							|  |  |  |                     rollback() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if "slot_info" in decompressed_multidata: | 
					
						
							|  |  |  |         for slot, slot_info in decompressed_multidata["slot_info"].items(): | 
					
						
							|  |  |  |             # Ignore Player Groups (e.g. item links) | 
					
						
							|  |  |  |             if slot_info.type == SlotType.group: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             slots.add(Slot(data=files.get(slot, None), | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  |                            player_name=slot_info.name, | 
					
						
							|  |  |  |                            player_id=slot, | 
					
						
							|  |  |  |                            game=slot_info.game)) | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  |         flush()  # commit slots | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     compressed_multidata = compressed_multidata[0:1] + zlib.compress(pickle.dumps(decompressed_multidata), 9) | 
					
						
							|  |  |  |     return slots, compressed_multidata | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-28 13:57:41 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  | def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, sid=None): | 
					
						
							|  |  |  |     if not owner: | 
					
						
							|  |  |  |         owner = session["_id"] | 
					
						
							|  |  |  |     infolist = zfile.infolist() | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  |     if all(allowed_options(file.filename) or file.is_dir() for file in infolist): | 
					
						
							|  |  |  |         flash(Markup("Error: Your .zip file only contains options files. " | 
					
						
							| 
									
										
										
										
											2022-12-06 00:40:51 +01:00
										 |  |  |                      'Did you mean to <a href="/generate">generate a game</a>?')) | 
					
						
							|  |  |  |         return | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  |     spoiler = "" | 
					
						
							| 
									
										
										
										
											2022-11-20 13:39:52 -06:00
										 |  |  |     files = {} | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  |     multidata = None | 
					
						
							| 
									
										
										
										
											2022-11-20 13:39:52 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Load files. | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  |     for file in infolist: | 
					
						
							| 
									
										
										
										
											2022-03-18 04:53:09 +01:00
										 |  |  |         handler = AutoPatchRegister.get_handler(file.filename) | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  |         if banned_file(file.filename): | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  |             return "Uploaded data contained a rom file, which is likely to contain copyrighted material. " \ | 
					
						
							|  |  |  |                    "Your file was deleted." | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-20 13:39:52 -06:00
										 |  |  |         # AP Container | 
					
						
							|  |  |  |         elif handler: | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  |             data = zfile.open(file, "r").read() | 
					
						
							| 
									
										
										
										
											2022-11-20 13:39:52 -06:00
										 |  |  |             patch = handler(BytesIO(data)) | 
					
						
							|  |  |  |             patch.read() | 
					
						
							|  |  |  |             files[patch.player] = data | 
					
						
							| 
									
										
										
										
											2022-07-20 12:48:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-20 13:39:52 -06:00
										 |  |  |         # Spoiler | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  |         elif file.filename.endswith(".txt"): | 
					
						
							|  |  |  |             spoiler = zfile.open(file, "r").read().decode("utf-8-sig") | 
					
						
							| 
									
										
										
										
											2022-03-18 04:53:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-20 13:39:52 -06:00
										 |  |  |         # Multi-data | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  |         elif file.filename.endswith(".archipelago"): | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 multidata = zfile.open(file).read() | 
					
						
							|  |  |  |             except: | 
					
						
							|  |  |  |                 flash("Could not load multidata. File may be corrupted or incompatible.") | 
					
						
							| 
									
										
										
										
											2021-11-22 17:57:23 +01:00
										 |  |  |                 multidata = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-20 13:39:52 -06:00
										 |  |  |         # Minecraft | 
					
						
							|  |  |  |         elif file.filename.endswith(".apmc"): | 
					
						
							|  |  |  |             data = zfile.open(file, "r").read() | 
					
						
							|  |  |  |             metadata = json.loads(base64.b64decode(data).decode("utf-8")) | 
					
						
							|  |  |  |             files[metadata["player_id"]] = data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Factorio | 
					
						
							|  |  |  |         elif file.filename.endswith(".zip"): | 
					
						
							| 
									
										
										
										
											2023-10-10 14:20:08 -07:00
										 |  |  |             try: | 
					
						
							|  |  |  |                 _, _, slot_id, *_ = file.filename.split('_')[0].split('-', 3) | 
					
						
							|  |  |  |             except ValueError: | 
					
						
							|  |  |  |                 flash("Error: Unexpected file found in .zip: " + file.filename) | 
					
						
							|  |  |  |                 return | 
					
						
							| 
									
										
										
										
											2022-11-20 13:39:52 -06:00
										 |  |  |             data = zfile.open(file, "r").read() | 
					
						
							|  |  |  |             files[int(slot_id[1:])] = data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # All other files using the standard MultiWorld.get_out_file_name_base method | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2023-10-10 14:20:08 -07:00
										 |  |  |             try: | 
					
						
							|  |  |  |                 _, _, slot_id, *_ = file.filename.split('.')[0].split('_', 3) | 
					
						
							|  |  |  |             except ValueError: | 
					
						
							|  |  |  |                 flash("Error: Unexpected file found in .zip: " + file.filename) | 
					
						
							|  |  |  |                 return | 
					
						
							| 
									
										
										
										
											2022-11-20 13:39:52 -06:00
										 |  |  |             data = zfile.open(file, "r").read() | 
					
						
							|  |  |  |             files[int(slot_id[1:])] = data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Load multi data. | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  |     if multidata: | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  |         slots, multidata = process_multidata(multidata, files) | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-18 01:02:26 +02:00
										 |  |  |         seed = Seed(multidata=multidata, spoiler=spoiler, slots=slots, owner=owner, meta=json.dumps(meta), | 
					
						
							|  |  |  |                     id=sid if sid else uuid.uuid4()) | 
					
						
							|  |  |  |         flush()  # create seed | 
					
						
							|  |  |  |         for slot in slots: | 
					
						
							|  |  |  |             slot.seed = seed | 
					
						
							|  |  |  |         return seed | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         flash("No multidata was found in the zip file, which is required.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  | @app.route("/uploads", methods=["GET", "POST"]) | 
					
						
							| 
									
										
										
										
											2020-07-04 23:50:18 +02:00
										 |  |  | def uploads(): | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  |     if request.method == "POST": | 
					
						
							|  |  |  |         # check if the POST request has a file part. | 
					
						
							|  |  |  |         if "file" not in request.files: | 
					
						
							|  |  |  |             flash("No file part in POST request.") | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  |             uploaded_file = request.files["file"] | 
					
						
							|  |  |  |             # If the user does not select file, the browser will still submit an empty string without a file name. | 
					
						
							|  |  |  |             if uploaded_file.filename == "": | 
					
						
							|  |  |  |                 flash("No selected file.") | 
					
						
							|  |  |  |             elif uploaded_file and allowed_generation(uploaded_file.filename): | 
					
						
							|  |  |  |                 if zipfile.is_zipfile(uploaded_file): | 
					
						
							|  |  |  |                     with zipfile.ZipFile(uploaded_file, "r") as zfile: | 
					
						
							| 
									
										
										
										
											2022-01-18 08:23:38 +01:00
										 |  |  |                         try: | 
					
						
							|  |  |  |                             res = upload_zip_to_db(zfile) | 
					
						
							|  |  |  |                         except VersionException: | 
					
						
							|  |  |  |                             flash(f"Could not load multidata. Wrong Version detected.") | 
					
						
							|  |  |  |                         else: | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  |                             if res is str: | 
					
						
							| 
									
										
										
										
											2022-01-18 08:23:38 +01:00
										 |  |  |                                 return res | 
					
						
							|  |  |  |                             elif res: | 
					
						
							|  |  |  |                                 return redirect(url_for("view_seed", seed=res.id)) | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  |                     uploaded_file.seek(0)  # offset from is_zipfile check | 
					
						
							| 
									
										
										
										
											2022-01-01 17:18:48 +01:00
										 |  |  |                     # noinspection PyBroadException | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  |                     try: | 
					
						
							| 
									
										
										
										
											2023-12-12 20:12:16 -06:00
										 |  |  |                         multidata = uploaded_file.read() | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  |                         slots, multidata = process_multidata(multidata) | 
					
						
							| 
									
										
										
										
											2022-01-08 21:21:29 +01:00
										 |  |  |                     except Exception as e: | 
					
						
							|  |  |  |                         flash(f"Could not load multidata. File may be corrupted or incompatible. ({e})") | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2023-04-06 15:24:03 -07:00
										 |  |  |                         seed = Seed(multidata=multidata, slots=slots, owner=session["_id"]) | 
					
						
							| 
									
										
										
										
											2021-05-14 15:25:57 +02:00
										 |  |  |                         flush()  # place into DB and generate ids | 
					
						
							| 
									
										
										
										
											2021-11-25 20:48:58 +01:00
										 |  |  |                         return redirect(url_for("view_seed", seed=seed.id)) | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2021-10-16 20:11:26 +02:00
										 |  |  |                 flash("Not recognized file format. Awaiting a .archipelago file or .zip containing one.") | 
					
						
							| 
									
										
										
										
											2022-01-30 20:06:03 -05:00
										 |  |  |     return render_template("hostGame.html", version=__version__) | 
					
						
							| 
									
										
										
										
											2020-12-04 18:22:12 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @app.route('/user-content', methods=['GET']) | 
					
						
							|  |  |  | def user_content(): | 
					
						
							| 
									
										
										
										
											2020-06-26 19:29:33 +02:00
										 |  |  |     rooms = select(room for room in Room if room.owner == session["_id"]) | 
					
						
							| 
									
										
										
										
											2020-12-04 23:25:49 +01:00
										 |  |  |     seeds = select(seed for seed in Seed if seed.owner == session["_id"]) | 
					
						
							| 
									
										
										
										
											2020-12-04 18:22:12 -05:00
										 |  |  |     return render_template("userContent.html", rooms=rooms, seeds=seeds) | 
					
						
							| 
									
										
										
										
											2024-04-02 16:45:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @app.route("/disown_seed/<suuid:seed>", methods=["GET"]) | 
					
						
							|  |  |  | def disown_seed(seed): | 
					
						
							|  |  |  |     seed = Seed.get(id=seed) | 
					
						
							|  |  |  |     if not seed: | 
					
						
							|  |  |  |         return abort(404) | 
					
						
							|  |  |  |     if seed.owner !=  session["_id"]: | 
					
						
							|  |  |  |         return abort(403) | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     seed.owner = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return redirect(url_for("user_content")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @app.route("/disown_room/<suuid:room>", methods=["GET"]) | 
					
						
							|  |  |  | def disown_room(room): | 
					
						
							|  |  |  |     room = Room.get(id=room) | 
					
						
							|  |  |  |     if not room: | 
					
						
							|  |  |  |         return abort(404) | 
					
						
							|  |  |  |     if room.owner != session["_id"]: | 
					
						
							|  |  |  |         return abort(403) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     room.owner = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return redirect(url_for("user_content")) |