From 4ad31d641b508167119b4a41fd8f4e0722e9810e Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Fri, 26 Jun 2020 20:12:30 -0400 Subject: [PATCH] Add CSPRNG option: --securerandom --- Bosses.py | 2 +- Dungeons.py | 2 +- EntranceRandomizer.py | 3 ++- EntranceShuffle.py | 2 +- Fill.py | 2 +- Gui.py | 2 +- ItemList.py | 2 +- Main.py | 8 +++++++- Plando.py | 2 +- RaceRandom.py | 44 +++++++++++++++++++++++++++++++++++++++++++ Rom.py | 4 ++-- 11 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 RaceRandom.py diff --git a/Bosses.py b/Bosses.py index 823d1e24..871dae01 100644 --- a/Bosses.py +++ b/Bosses.py @@ -1,5 +1,5 @@ import logging -import random +import RaceRandom as random from BaseClasses import Boss from Fill import FillError diff --git a/Dungeons.py b/Dungeons.py index 3e412f70..02765e65 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -1,4 +1,4 @@ -import random +import RaceRandom as random from BaseClasses import Dungeon from Bosses import BossFactory diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 46908a9b..d4cac8af 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -2,7 +2,7 @@ import argparse import os import logging -import random +import RaceRandom as random import textwrap import sys @@ -255,6 +255,7 @@ def start(): parser.add_argument('--shufflepalette', default=False, action='store_true') parser.add_argument('--shufflepots', default=False, action='store_true') parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255)) + parser.add_argument('--securerandom', default=False, action='store_true') parser.add_argument('--outputpath') args = parser.parse_args() diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 57e895a2..cf8d4740 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1,4 +1,4 @@ -import random +import RaceRandom as random # ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave. from collections import defaultdict diff --git a/Fill.py b/Fill.py index bfc1145e..89a4731b 100644 --- a/Fill.py +++ b/Fill.py @@ -1,4 +1,4 @@ -import random +import RaceRandom as random import logging from BaseClasses import CollectionState diff --git a/Gui.py b/Gui.py index da8550df..0f976d6e 100755 --- a/Gui.py +++ b/Gui.py @@ -2,7 +2,7 @@ from argparse import Namespace from glob import glob import json -import random +import RaceRandom as random import os import shutil from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, BOTH, Entry, Spinbox, Button, filedialog, messagebox, ttk diff --git a/ItemList.py b/ItemList.py index 6da66e68..fbf82154 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1,6 +1,6 @@ from collections import namedtuple import logging -import random +import RaceRandom as random from BaseClasses import Region, RegionType, Shop, ShopType, Location from Bosses import place_bosses diff --git a/Main.py b/Main.py index 1e3860b6..5083feb0 100644 --- a/Main.py +++ b/Main.py @@ -4,7 +4,7 @@ from itertools import zip_longest import json import logging import os -import random +import RaceRandom as random import time from BaseClasses import World, CollectionState, Item, Region, Location, Shop @@ -23,6 +23,9 @@ __version__ = '0.6.3-pre' def main(args, seed=None): start = time.perf_counter() + if args.securerandom: + random.use_secure() + # initialize the world world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) logger = logging.getLogger('') @@ -33,6 +36,9 @@ def main(args, seed=None): world.seed = int(seed) random.seed(world.seed) + if args.securerandom: + world.seed = None + world.crystals_needed_for_ganon = random.randint(0, 7) if args.crystals_ganon == 'random' else int(args.crystals_ganon) world.crystals_needed_for_gt = random.randint(0, 7) if args.crystals_gt == 'random' else int(args.crystals_gt) diff --git a/Plando.py b/Plando.py index 1f2992bf..c8d7f9e6 100755 --- a/Plando.py +++ b/Plando.py @@ -3,7 +3,7 @@ import argparse import hashlib import logging import os -import random +import RaceRandom as random import time import sys diff --git a/RaceRandom.py b/RaceRandom.py new file mode 100644 index 00000000..127d966d --- /dev/null +++ b/RaceRandom.py @@ -0,0 +1,44 @@ +import random as _random +from functools import update_wrapper + +__all__ = ["use_secure"] + +_prng_inst = _random.Random() +_cprng_inst = _random.SystemRandom() + +_mode = "prng" + +def use_secure(secure=True): + # pylint: disable=global-statement + global _mode + _mode = "cprng" if secure else "prng" + +def _wrap(name): + def somefunc(*args, **kwargs): + return getattr(_cprng_inst if _mode == "cprng" else _prng_inst, name)(*args, **kwargs) + update_wrapper(somefunc, getattr(_prng_inst, name)) + + return somefunc + +# These are for intellisense purposes only, and will be overwritten below +choice = _prng_inst.choice +gauss = _prng_inst.gauss +getrandbits = _prng_inst.getrandbits +randint = _prng_inst.randint +random = _prng_inst.random +randrange = _prng_inst.randrange +sample = _prng_inst.sample +seed = _prng_inst.seed +shuffle = _prng_inst.shuffle +uniform = _prng_inst.uniform + +for func_name in dir(_random): + if not callable(getattr(_random, func_name)): + continue + if not callable(getattr(_prng_inst, func_name, None)): + continue + if func_name.startswith('_'): + continue + + globals()[func_name] = _wrap(func_name) + __all__.append(func_name) diff --git a/Rom.py b/Rom.py index e51359c9..c797b927 100644 --- a/Rom.py +++ b/Rom.py @@ -3,7 +3,7 @@ import json import hashlib import logging import os -import random +import RaceRandom as random import struct import subprocess @@ -1043,7 +1043,7 @@ def patch_rom(world, player, rom): # set rom name # 21 bytes from Main import __version__ - rom.name = bytearray('ER_{0}_{1:09}\0'.format(__version__[0:7],world.seed), 'utf8') + rom.name = bytearray('ER_{0}_{1}\0'.format(__version__[0:7], world.seed), 'utf8') assert len(rom.name) <= 21 rom.write_bytes(0x7FC0, rom.name)