diff --git a/.gitignore b/.gitignore index 5dd284f9..04bc2055 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ dist build .idea *.sfc -*_Spoiler.txt \ No newline at end of file +*_Spoiler.txt +bundle/components.wxs +*.wixobj +README.html diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 2a6f7193..f44dee52 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -7,6 +7,7 @@ import sys from Main import main from Gui import guiMain +from Utils import is_bundled, close_console class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): @@ -190,6 +191,15 @@ if __name__ == '__main__': ''') args = parser.parse_args() + if is_bundled and len(sys.argv) == 1 : + # for the bundled builds, if we have no arguments, the user + # probably wants the gui. Users of the bundled build who want the command line + # interface shouuld specify at least one option, possibly setting a value to a + # default if they like all the defaults + close_console() + guiMain() + sys.exit(0) + # ToDo: Validate files further than mere existance if not args.jsonout and not os.path.isfile(args.rom): input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom) diff --git a/Gui.py b/Gui.py index feee07c7..17214273 100644 --- a/Gui.py +++ b/Gui.py @@ -1,14 +1,19 @@ from Main import main, __version__ as ESVersion +from Utils import is_bundled, local_path, output_path, open_file from argparse import Namespace import random - -from tkinter import Checkbutton, OptionMenu, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, Entry, Spinbox, Button, filedialog, messagebox +import subprocess +import os +import sys +from tkinter import Checkbutton, OptionMenu, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Entry, Spinbox, Button, filedialog, messagebox, PhotoImage def guiMain(args=None): mainWindow = Tk() mainWindow.wm_title("Entrance Shuffle %s" % ESVersion) + set_icon(mainWindow) + topFrame = Frame(mainWindow) rightHalfFrame = Frame(topFrame) checkBoxFrame = Frame(rightHalfFrame) @@ -164,6 +169,7 @@ def guiMain(args=None): heartbeepFrame.pack(expand=True, anchor=E) bottomFrame = Frame(mainWindow) + farBottomFrame = Frame(mainWindow) seedLabel = Label(bottomFrame, text='Seed #') seedVar = StringVar() @@ -212,15 +218,29 @@ def guiMain(args=None): generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom) + def open_output(): + open_file(output_path('')) + + openOutputButton = Button(farBottomFrame, text='Open Output Directory', command=open_output) + + if os.path.exists(local_path('README.html')): + def open_readme(): + open_file(local_path('README.html')) + openReadmeButton = Button(farBottomFrame, text='Open Documentation', command=open_readme) + openReadmeButton.pack(side=LEFT) + seedLabel.pack(side=LEFT) seedEntry.pack(side=LEFT) - countLabel.pack(side=LEFT) + countLabel.pack(side=LEFT, padx=(5,0)) countSpinbox.pack(side=LEFT) - generateButton.pack(side=LEFT) + generateButton.pack(side=LEFT, padx=(5,0)) + + openOutputButton.pack(side=RIGHT) drowDownFrame.pack(side=LEFT) rightHalfFrame.pack(side=RIGHT) topFrame.pack(side=TOP) + farBottomFrame.pack(side=BOTTOM, fill=X, padx=5, pady=5) bottomFrame.pack(side=BOTTOM) if args is not None: @@ -254,6 +274,11 @@ def guiMain(args=None): mainWindow.mainloop() +def set_icon(window): + er16 = PhotoImage(file=local_path('data/ER16.gif')) + er32 = PhotoImage(file=local_path('data/ER32.gif')) + er48 = PhotoImage(file=local_path('data/ER32.gif')) + window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) if __name__ == '__main__': guiMain() diff --git a/Main.py b/Main.py index 8b49044e..e98db109 100644 --- a/Main.py +++ b/Main.py @@ -8,6 +8,7 @@ from Items import ItemFactory from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, fill_restrictive, flood_items from collections import OrderedDict from ItemList import generate_itempool +from Utils import output_path import random import time import logging @@ -105,10 +106,10 @@ def main(args, seed=None): if args.jsonout: print(json.dumps({'patch': rom.patches, 'spoiler': world.spoiler.to_json()})) else: - rom.write_to_file(args.jsonout or '%s.sfc' % outfilebase) + rom.write_to_file(args.jsonout or output_path('%s.sfc' % outfilebase)) if args.create_spoiler and not args.jsonout: - world.spoiler.to_file('%s_Spoiler.txt' % outfilebase) + world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) logger.info('Done. Enjoy.') logger.debug('Total Time: %s' % (time.clock() - start)) diff --git a/README.md b/README.md index 06009eb8..7c9f5a2f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is a entrance randomizer for _The Legend of Zelda: A Link to the Past_ for the SNES. See http://vt.alttp.run for more details on the normal randomizer. -## Installation +# Installation Clone this repository and then run ```EntranceRandomizer.py``` (requires Python 3). @@ -11,40 +11,9 @@ Alternatively, run ```Gui.py``` for a simple graphical user interface. For releases, a Windows standalone executable is available for users without Python 3. -## Options +# Settings - -``` --h, --help -``` - -Show the help message and exit. - -``` ---create_spoiler -``` - -Output a Spoiler File (default: False) - -``` ---logic [{noglitches,minorglitches}] -``` - -Select Enforcement of Item Requirements. - -### No Glitches - -The game can be completed without knowing how to perform glitches of any kind. - -### Minor Glitches - -May require Fake Flippers, Bunny Revival. (default: noglitches) - -``` ---mode [{standard,open,swordless}] -``` - -Select game mode. (default: open) +## Game Mode ### Standard @@ -74,13 +43,20 @@ Special notes: - The magic barrier to Hyrule Castle Tower can be broken with a Hammer. - The Hammer can be used to activate the Ether and Bombos tablets. -``` ---goal [{ganon,pedestal,dungeons,triforcehunt,crystals}] -``` +## Game Logic +This determines the Item Requirements for each location. -Select completion goal. +### No Glitches -### Ganon (Default) +The game can be completed without knowing how to perform glitches of any kind. + +### Minor Glitches + +May require Fake Flippers, Bunny Revival. + +## Game Goal + +### Ganon Standard game completion requiring you to collect the 7 crystals, defeat Agahnim 2 and then beat Ganon. @@ -109,18 +85,14 @@ Standard game completion requiring you to collect the 7 crystals and then beat G This is only noticeably different if the --shuffleganon option is enabled. -``` ---difficulty [{easy,normal,hard,expert,insane}] -``` - -Select game difficulty. Affects available itempool. (default: normal) +## Game Difficulty ### Easy This setting doubles the number of swords, shields, armors, and bottles in the item pool. Within dungeons, the number of items found will be displayed on screen if there is no timer. -### Normal (Default) +### Normal This is the default setting that has an item pool most similar to the original The Legend of Zelda: A Link to the Past. @@ -142,13 +114,9 @@ pool is less helpful, and the player can find no armor, only a Master Sword, and This setting is a modest step up from Expert. The main difference is that the player will never find any additional health. -``` ---timer [{none,display,timed,timed-ohko,ohko,timed-countdown}] -``` +## Timer Setting -Select the timer setting. - -### None (Default) +### None Does not invoke a timer. @@ -180,11 +148,9 @@ decreased with Red Clocks found in chests that will be added to the itempool. Th is to finish the game without the timer reaching zero, but the game will continue uninterrupted if the player runs out of time. -``` ---progressive [{on,off,random}] -``` +## Progressive equipment -Select the setting for progressive equipment. +Determines if Sword, Shield, and gloves are progressive (upgrading in sequence) or not. ### On (Default) @@ -204,14 +170,12 @@ will simply do nothing. This setting makes swords, shields, armor, and gloves randomly either progressive or not. Each category is independently randomized. -``` ---algorithm [{freshness,flood,vt21,vt22,vt25,vt26,balanced}] -``` +## Item Distribution Algorithm -Select item filling algorithm. +Determines how the items are shuffled. -### Balanced (Default) -This is a variation of vt26 that aims to strike a balance between the overworld heavy vt25 and the dungeon heavy vt26 algorithm. +### Balanced +This is a variation of VT26 that aims to strike a balance between the overworld heavy VT25 and the dungeon heavy VT26 algorithm. It does this by reshuffling the remaining locations after placing dungeon items. ### VT26 @@ -223,14 +187,14 @@ the sheer number of chests in that single location. Items and locations are shuffled and placed from the top of the lists. The only thing preventing an item from being placed into a spot is if is absolutely impossible to be there given the previous made placement choices. Leads to very uniform but guaranteed solvable distributions. +### VT22 +The ordinary VT v8.22 algorithm. Fixes issues in placement in VT21 by discarding all previously skipped and unfilled locations +after 2/3 of the progression items were placed to prevent stale late game locations from soaking up the same items all the time. + ### VT21 The ordinary VT v8.21 algorithm. Unbiased placement of items into unlocked locations, placing items that unlock new locations first. May lead to distributions that seem a bit wonky (high likelyhood of ice rod in Turtle Rock, for instance) -### VT22 -The ordinary VT v8.21 algorithm. Fixes issues in placement in VT21 by discarding all previously skipped and unfilled locations -after 2/3 of the progression items were placed to prevent stale late game locations from soaking up the same items all the time. - ### Flood Pushes out items starting from Link's House and is slightly biased to placing progression items with less restrictions. Use for relatively simple distributions. @@ -238,11 +202,9 @@ Pushes out items starting from Link's House and is slightly biased to placing pr Alternative approach to VT22 to improve on VT21 flaws. Locations that are skipped because they are currently unreachable increase in staleness, decreasing the likelihood of receiving a progress item. -``` ---shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}] -``` +## Entrance Shuffle Algorithm -Select Entrance Shuffling Algorithm. +Determines how locations are shuffled. ### Default @@ -253,7 +215,7 @@ Is the Vanilla layout. Shuffles Dungeon Entrances/Exits between each other and keeps all 4-entrance dungeons confined to one location. Outside Light World Death Mountain, interiors are shuffled but still connect the same points on the overworld. On Death Mountain, entrances are connected more freely. -### Full (Default) +### Full Mixes cave and dungeon entrances freely. @@ -273,6 +235,118 @@ Madness, but without the light/dark world restrictions. Gives access to Mirror a The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla. +## Heartbeep Sound Rate + +Select frequency of beeps when on low health. Can completely disable them. + +## Create Spoiler Log + +Output a Spoiler File. + +## Do not Create Patched Rom + +If set, will not produce a patched rom as output. Useful in conjunction with the spoiler log option to batch +generate spoilers for statistical analysis. + +## Enable L/R button quickswapping + +Use to enable quick item swap with L/R buttons + +## Instant Menu + +As an alternative to quickswap, opens menu instantly. + +## Keysanity + +This setting allows dungeon specific items (Small Key, Big Key, Map, Compass) to be distributed anywhere in the world and not just +in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that +is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but +the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell +for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize. + +## Place Dungeon Items + +If not set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world. +This may lead to different amount of itempool items being placed in a dungeon than you are used to. + +## Only Ensure Seed Beatable + +If set, will only ensure the goal can be achieved, but not necessarily that all locations are reachable. Currently only affects VT25, VT26 and balanced algorithms. + +## Include Ganon's Tower and Pyramid Hole in Shuffle pool + +If set, Ganon's Tower is included in the dungeon shuffle pool and the Pyramid Hole/Exit pair is included in the Holes shuffle pool. Ganon can not be defeated until the primary goal is fulfilled. This setting removes any bias against Ganon's Tower that some algorithms may have. + +## Seed + +Can be used to set a seed number to generate. Using the same seed with same settings on the same version of the entrance randomizer will always yield an identical output. + +## Count + +Use to batch generate multiple seeds with same settings. If a seed number is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with the same seed number given will produce the same 10 (different) roms each time). + +# Command Line Options + +``` +-h, --help +``` + +Show the help message and exit. + +``` +--create_spoiler +``` + +Output a Spoiler File (default: False) + +``` +--logic [{noglitches,minorglitches}] +``` + +Select the game logic (default: noglitches) + +``` +--mode [{standard,open,swordless}] +``` + +Select the game mode. (default: open) + +``` +--goal [{ganon,pedestal,dungeons,triforcehunt,crystals}] +``` + +Select the game completion goal. (default: ganon) + +``` +--difficulty [{easy,normal,hard,expert,insane}] +``` + +Select the game difficulty. Affects available itempool. (default: normal) + +``` +--timer [{none,display,timed,timed-ohko,ohko,timed-countdown}] +``` + +Select the timer setting. (default: none) + +``` +--progressive [{on,off,random}] +``` + +Select the setting for progressive equipment. (default: on) + +``` +--algorithm [{freshness,flood,vt21,vt22,vt25,vt26,balanced}] +``` + +Select item distribution algorithm. (default: balanced) + +``` +--shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}] +``` + +Select entrance shuffle algorithm. (default: full) + ``` --rom ROM ``` @@ -289,14 +363,13 @@ Select level of logging for output. (default: info) --seed SEED ``` -Define seed number to generate. (default: None) Using the same seed with same settings on the same version of the entrance randomizer will always yield an identical output. +Define seed number to generate. (default: None) ``` --count COUNT ``` -Use to batch generate multiple seeds with same settings. -If --seed is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with --seed given will produce the same 10 (different) roms each time). (default: None) +Set the count option (default: None) ``` --quickswap @@ -321,11 +394,7 @@ Disables game music, resulting in the game sound being just the SFX. (default: F --keysanity ``` -This setting allows dungeon specific items (Small Key, Big Key, Map, Compass) to be distributed anywhere in the world and not just -in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that -is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but -the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell -for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize. +Enable Keysanity (default: False) ``` --nodungeonitems @@ -350,22 +419,19 @@ Use to select a different sprite sheet to use for Link. Path to a binary file of --beatableonly ``` -If set, will only ensure the goal can be achieved, but not necessarily that all locations are reachable. Currently only affects VT25, VT26 and balanced algorithms. +Enables the "Only Ensure Seed Beatable" option (default: False) ``` --shuffleganon ``` -If set, Ganon's Tower is included in the dungeon shuffle pool and the Pyramid Hole/Exit pair is included in the Holes shuffle pool. Ganon can not be defeated until the primary goal is fulfilled. -This setting removes any bias against Ganon's Tower that some algorithms may have. - -Note: This option is under development and may sometimes lead to dungeon and crystal distributions that cannot be solved. If this is the case, the generation will fail. Simply retry with a different seed number if you run into this issue. +Enables the "Include Ganon's Tower and Pyramid Hole in Shuffle pool" option. (default: false) ``` --suppress_rom ``` -If set, will not produce a patched rom as output. Useful to batch generate spoilers for statistical analysis. +Enables the "Do not Create Patched Rom" option. (default: False) ``` --gui diff --git a/Rom.py b/Rom.py index bc27fa32..24fda4a6 100644 --- a/Rom.py +++ b/Rom.py @@ -2,6 +2,7 @@ from Dungeons import dungeon_music_addresses from Text import string_to_alttp_text, text_addresses, Credits from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts +from Utils import local_path import random import json import hashlib @@ -66,7 +67,7 @@ class LocalRom(object): self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer)))) # load randomizer patches - patches = json.load(open('data/base2current.json', 'r')) + patches = json.load(open(local_path('data/base2current.json'), 'r')) for patch in patches: if isinstance(patch, dict): for baseaddress, values in patch.items(): diff --git a/Utils.py b/Utils.py new file mode 100644 index 00000000..30703287 --- /dev/null +++ b/Utils.py @@ -0,0 +1,73 @@ +import os +import sys + +def is_bundled(): + return getattr(sys, 'frozen', False) + +def local_path(path): + if local_path.cached_path is not None: + return os.path.join(local_path.cached_path, path) + + if is_bundled(): + # we are running in a bundle + local_path.cached_path = sys._MEIPASS + else: + # we are running in a normal Python environment + local_path.cached_path = os.path.dirname(os.path.abspath(__file__)) + + return os.path.join(local_path.cached_path, path) + +local_path.cached_path = None + +def output_path(path): + if output_path.cached_path is not None: + return os.path.join(output_path.cached_path, path) + + if not is_bundled(): + output_path.cached_path = '.' + return os.path.join(output_path.cached_path, path) + else: + # has been packaged, so cannot use CWD for output. + if sys.platform == 'win32': + #windows + import ctypes.wintypes + CSIDL_PERSONAL = 5 # My Documents + SHGFP_TYPE_CURRENT = 0 # Get current, not default value + + buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) + ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf) + + documents = buf.value + + elif sys.platform == 'darwin': + from AppKit import NSSearchPathForDirectoriesInDomains + # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains + NSDocumentDirectory = 9 + NSUserDomainMask = 1 + # True for expanding the tilde into a fully qualified path + documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0] + else: + raise NotImplementedError('Not supported yet') + + output_path.cached_path = os.path.join(documents, 'ALttPEntranceRandomizer') + if not os.path.exists(output_path.cached_path): + os.mkdir(output_path.cached_path) + return os.path.join(output_path.cached_path, path) + +output_path.cached_path = None + +def open_file(filename): + if sys.platform == 'win32': + os.startfile(filename) + else: + open_Command = 'open' if sys.platform == 'darwin' else 'xdg-open' + subprocess.call([open_command, filename]) + +def close_console(): + if sys.platform == 'win32': + #windows + import ctypes.wintypes + try: + ctypes.windll.kernel32.FreeConsole() + except: + pass diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..a36537ac --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,28 @@ +version: '{build}' +pull_requests: + do_not_increment_build_number: true +environment: + ProjectVersion: build$(APPVEYOR_BUILD_VERSION) + matrix: + - PYTHON: C:\PYTHON36 +install: + - ps: 'if(Test-Path env:APPVEYOR_REPO_TAG_NAME) {$env:ProjectVersion=$env:APPVEYOR_REPO_TAG_NAME}' + - '%PYTHON%\python.exe --version' + - '%PYTHON%\Scripts\pip install pyinstaller' + - '%PYTHON%\Scripts\pip install markdown' + - '%PYTHON%\python.exe -m markdown README.md > README.html' + - '%PYTHON%\Scripts\pyinstaller bundle\EntranceRandomizer.spec' + - 'mkdir dist\EntranceRandomizer\ext' + - 'move dist\EntranceRandomizer\*.pyd dist\EntranceRandomizer\ext' + - 'move dist\EntranceRandomizer\tcl*.dll dist\EntranceRandomizer\ext' + - 'move dist\EntranceRandomizer\tk*.dll dist\EntranceRandomizer\ext' + - ps: '$env:ER_Version= &"$env:PYTHON\python.exe" -c "import Main; import re; print(re.match(''[0-9]+\\.[0-9]+\\.[0-9]+'',Main.__version__).group(0))"' + - '"%WIX%\bin\heat.exe" dir "dist\EntranceRandomizer" -sfrag -srd -suid -dr INSTALLDIR -cg ERFiles -ag -template fragment -t bundle\components.xslt -out build\components.wxs' + - '"%WIX%\bin\candle.exe" -out build\ bundle\*.wxs build\*.wxs' + - '"%WIX%\bin\light.exe" -ext WixUIExtension build\*.wixobj -o dist\EntranceRandomizer-Installer-%ProjectVersion%-win32.msi -b dist\EntranceRandomizer' +build: off +artifacts: +- path: dist/EntranceRandomizer*.msi + name: EntranceRandomizer-Installer-$(ProjectVersion)-win32.msi +- path: dist/EntranceRandomizer/ + name: EntranceRandomizer-Raw-$(ProjectVersion)-win32.zip diff --git a/bundle/EntranceRandomizer.spec b/bundle/EntranceRandomizer.spec new file mode 100644 index 00000000..c01d6155 --- /dev/null +++ b/bundle/EntranceRandomizer.spec @@ -0,0 +1,39 @@ +# -*- mode: python -*- +from PyInstaller.compat import is_win +block_cipher = None + +# Todo: the runtime hooks should only be installed on windows +a = Analysis(['../EntranceRandomizer.py'], + pathex=['bundle'], + binaries=[], + datas=[('../data/', 'data/'), ('../README.html', '.')], + hiddenimports=[], + hookspath=[], + runtime_hooks=['bundle/_rt_hook.py'], + excludes=['lzma', 'bz2'], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) + +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name='EntranceRandomizer', + debug=False, + strip=False, + upx=False, + icon='data/ER.ico', + console=is_win ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name='EntranceRandomizer') +app = BUNDLE(coll, + name ='EntranceRandomizer.app', + icon = 'data/ER.icns', + bundle_identifier = None) diff --git a/bundle/_rt_hook.py b/bundle/_rt_hook.py new file mode 100644 index 00000000..17ea446b --- /dev/null +++ b/bundle/_rt_hook.py @@ -0,0 +1,4 @@ +import sys +import os + +sys.path.append(os.path.join(sys._MEIPASS, "ext")) diff --git a/bundle/components.xslt b/bundle/components.xslt new file mode 100644 index 00000000..1b4c7132 --- /dev/null +++ b/bundle/components.xslt @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundle/installer.wxs b/bundle/installer.wxs new file mode 100644 index 00000000..f5d111d7 --- /dev/null +++ b/bundle/installer.wxs @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + INSTALLDIR + + + + + 1 + 1 + + + diff --git a/data/ER.icns b/data/ER.icns new file mode 100644 index 00000000..e3a6fbfa Binary files /dev/null and b/data/ER.icns differ diff --git a/data/ER.ico b/data/ER.ico new file mode 100644 index 00000000..b9b60360 Binary files /dev/null and b/data/ER.ico differ diff --git a/data/ER16.gif b/data/ER16.gif new file mode 100644 index 00000000..5806c8fd Binary files /dev/null and b/data/ER16.gif differ diff --git a/data/ER32.gif b/data/ER32.gif new file mode 100644 index 00000000..b72c0bcc Binary files /dev/null and b/data/ER32.gif differ diff --git a/data/ER48.gif b/data/ER48.gif new file mode 100644 index 00000000..06d7af55 Binary files /dev/null and b/data/ER48.gif differ