diff --git a/MultiMystery.py b/MultiMystery.py index 961afe78..66f558ca 100644 --- a/MultiMystery.py +++ b/MultiMystery.py @@ -15,8 +15,11 @@ Configuration can be found in host.yaml import os import subprocess import sys +import threading +import concurrent.futures -def feedback(text:str): + +def feedback(text: str): print(text) input("Press Enter to ignore and probably crash.") @@ -133,38 +136,58 @@ if __name__ == "__main__": 2: "7z", 3: "bz2"}[zip_format] + ziplock = threading.Lock() + + def pack_file(file: str): - zf.write(os.path.join(output_path, file), file) - print(f"Packed {file} into zipfile {zipname}") + with ziplock: + zf.write(os.path.join(output_path, file), file) + print(f"Packed {file} into zipfile {zipname}") + def remove_zipped_file(file: str): os.remove(os.path.join(output_path, file)) print(f"Removed {file} which is now present in the zipfile") + zipname = os.path.join(output_path, f"ER_{seedname}.{typical_zip_ending}") print(f"Creating zipfile {zipname}") ipv4 = (host if host else get_public_ipv4()) + ":" + str(port) - with zipfile.ZipFile(zipname, "w", compression=compression, compresslevel=9) as zf: - for file in os.listdir(output_path): - if file.endswith(".sfc") and seedname in file: - if zip_diffs: - diff = os.path.split(create_patch_file(os.path.join(output_path, file), ipv4))[1] - pack_file(diff) - if zip_diffs == 2: - remove_zipped_file(diff) - if zip_roms: - pack_file(file) - if zip_roms == 2 and player_name.lower() not in file.lower(): - remove_zipped_file(file) - if zip_multidata and os.path.exists(os.path.join(output_path, multidataname)): - pack_file(multidataname) - if zip_multidata == 2: - remove_zipped_file(multidataname) - if zip_spoiler and create_spoiler: - pack_file(spoilername) - if zip_spoiler == 2: - remove_zipped_file(spoilername) + + + def _handle_file(file: str): + if zip_diffs: + # the main reason for using threading, the patch is created using bsdiff4, which frees the GIL + diff = os.path.split(create_patch_file(os.path.join(output_path, file), ipv4))[1] + pack_file(diff) + if zip_diffs == 2: + remove_zipped_file(diff) + if zip_roms: + pack_file(file) + if zip_roms == 2 and player_name.lower() not in file.lower(): + remove_zipped_file(file) + + + with concurrent.futures.ThreadPoolExecutor() as pool: + futures = [] + with zipfile.ZipFile(zipname, "w", compression=compression, compresslevel=9) as zf: + for file in os.listdir(output_path): + if file.endswith(".sfc") and seedname in file: + futures.append(pool.submit(_handle_file, file)) + + if zip_multidata and os.path.exists(os.path.join(output_path, multidataname)): + pack_file(multidataname) + if zip_multidata == 2: + remove_zipped_file(multidataname) + + if zip_spoiler and create_spoiler: + pack_file(spoilername) + if zip_spoiler == 2: + remove_zipped_file(spoilername) + + for future in futures: + future.result() # make sure we close the zip AFTER any packing is done if os.path.exists(os.path.join(output_path, multidataname)): if os.path.exists("BerserkerMultiServer.exe"): diff --git a/Rom.py b/Rom.py index ac7bdffb..fffa42b2 100644 --- a/Rom.py +++ b/Rom.py @@ -1002,6 +1002,9 @@ def patch_rom(world, rom, player, team, enemized): 'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10), 'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20), 'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0), + # doors-specific items + 'Big Key (Agahnims Tower)': (0x367, 0x08), 'Compass (Agahnims Tower)': (0x365, 0x08), 'Map (Agahnims Tower)': (0x369, 0x08), + # end of doors-specific items 'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02), 'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10), 'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80), diff --git a/easy.yaml b/easy.yaml index 89fb5d1e..d3788e61 100644 --- a/easy.yaml +++ b/easy.yaml @@ -1,21 +1,45 @@ -#More general info here: https://docs.google.com/document/d/1r7qs1-MK7YbFf2d-mEUeTy2wHykIf1ALG9pLtVvUbSw/edit -description: Easy/Open/Normal #please describe your options. Especially useful when you have multiple yamls for different occasions -name: PleaseEnterNameHere #your name ingame, space and "_" gets replaced with a dash "-" -glitches_required: - none: 1 # the "regular" glitch-free mode - overworld_glitches: 0 # puts overworld glitches like fake flipper, water-walk, link-state and boots clipping in logic - no_logic: 0 # no logic at all, careful with this in multiworld as it will create item loops that put other players into glitches required -item_placement: basic #this is based on Entrance Randomizer, which does not (yet?) support advanced -map_shuffle: #to shuffle dungeon maps into the outside world and other dungeons, as well as other player's worlds in multiworld +# What is this file? +# This file contains options which allow you to configure your multiworld experience while allowing others +# to play how they want as well. +# +# How do I use it? +# The options in this file are weighted. This means the higher number you assign to a value, the more +# chances you have for that option to be chosen. For example, an option like this: +# +# map_shuffle: +# on: 5 +# off: 15 +# +# Means you have 5 chances for map shuffle to not occur, and 15 chances for map shuffle to be turned on + +# I've never seen a file like this before. What characters am I allowed to use? +# This is a .yaml file. You are allowed to use most characters. +# To test if your yaml is valid or not, you can use this website: +# http://www.yamllint.com/ + +description: Your Description Here # Used to describe your yaml. Useful if you have multiple files +name: YourName # Your name in-game. Spaces and underscores will be replaced with dashes +glitches_required: # Determine the logic required to complete the seed + none: 1 # No glitches required + overworld_glitches: 0 # Assumes the player knows how to perform overworld glitches like fake flipper, water walk, etc + no_logic: 0 # Items are places completely at random and with no regard for logic. Your fire rod could be on Trinexx +item_placement: basic # This is based on Entrance Randomizer, which does not (yet?) support advanced +meta_ignore: # Nullify options specified in the meta.yaml file. Adding an option here guarantees it will not occur in your seed, even if the .yaml file specifies it + world_state: + - inverted # Never play inverted seeds + - retro # Never play retro seeds + weapons: + - swordless # Never play a swordless seed +map_shuffle: # Shuffle dungeon maps into the world and other dungeons, including other players' worlds on: 0 off: 1 -compass_shuffle: #same for compass +compass_shuffle: # Shuffle compasses into the world and other dungeons, including other players' worlds on: 0 off: 1 -smallkey_shuffle: #same for small keys +smallkey_shuffle: # Shuffle small keys into the world and other dungeons, including other players' worlds on: 0 off: 1 -bigkey_shuffle: #same for big keys +bigkey_shuffle: # Shuffle big keys into the world and other dungeons, including other players' worlds on: 0 off: 1 dungeon_items: # alternative to the 4 shuffles above this, does nothing until the respective 4 shuffles are deleted @@ -23,97 +47,97 @@ dungeon_items: # alternative to the 4 shuffles above this, does nothing until th none: 1 # shuffle none of the 4 mcsb: 0 # shuffle all of the 4, any combination of m, c, s and b will shuffle the respective item, or not if it's missing, so you can add more options here accessibility: - items: 0 # item accessibility means you can get all inventory items. So a key could lock itself, but you can fill your inventory - locations: 1 # location accessibility means you can access every location in your seed and get all 216 checks - none: 0 # no accessibility means your seed is "beatable only", meaning any items you do not need to beat the game can be unreachable. This can mean you have to defeat ganon with a lamp and master sword. -progressive: #not available in bonta's multiworld at this time. If you want this option, make sure the host uses the correct Multiworld - on: 1 # progressive items, you will always get progressive items like swords in their order: figher sword -> master sword -> tempered sword -> golden sword - off: 0 # turns progressive items off, so you can find, for example, silver arrows before a bow - random: 0 # rolls a 50/50 chance for each potentially progressive item. So, for example, you can have progressive swords but non-progressive mittens -entrance_shuffle: - none: 1 # no entrance shuffle + items: 0 # Guarantees you will be able to acquire all items, but you may not be able to access all locations + locations: 1 # Guarantees you will be able to access all locations, and therefore all items + none: 0 # Guarantees only that the game is beatable. You may not be able to access all locations or acquire all items +progressive: # Enable or disable progressive items (swords, shields, bow) + on: 1 # All items progressive + off: 0 # No items progressive + random: 0 # Randomly decides for all items. Swords could be progressive, shields might not be +entrance_shuffle: # Documentation: https://alttpr.com/en/options#entrance_shuffle + none: 1 # Vanilla game map. All entrances and exits lead to their original locations. You probably want this option dungeonssimple: 0 # shuffle just dungeons amongst each other, swapping dungeons entirely, so Hyrule Castle is always 1 dungeon dungeonsfull: 0 # shuffle any dungeon entrance with any dungeon interior, so Hyrule Castle can be 4 different dungeons - simple: 0 #dungeons are shuffled with each other and the other entrances are shuffled with each other - restricted: 0 #dungeons still shuffle along each other but connects other entrances more feely with each other while keeping entrances in one world - full: 0 # mixes caves and dungeons freely, except for confining all entrances to one world - crossed: 0 #introduces cross world connectors - insanity: 0 #any entrance can lead to any other entrance + simple: 0 # Entrances are grouped together before being randomized. Simple uses the most strict grouping rules + restricted: 0 # Less strict than simple + full: 0 # Less strict than restricted + crossed: 0 # Less strict than full + insanity: 0 # Very few grouping rules. Good luck. goals: - ganon: 5 #beat GT and then Ganon - fast_ganon: 4 # Just kill Ganon - dungeons: 1 # All Dungeons, including GT, and Agahnims Tower - pedestal: 0 # Pull the win out of the Pedestal - triforce-hunt: 0 # Collect 20 of 30 Triforce pieces then hand them in in front of Hyrule Castle + ganon: 1 # Climb GT, defeat Agahnim 2, then kill Ganon + fast_ganon: 0 # Only killing Ganon is required. The hole is always open. Items may still be placed in GT, however + dungeons: 0 # Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2) + pedestal: 0 # Pull the Triforce from the Master Sword pedestal + triforce-hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the world, then turn them in to Murahadala in front of Hyrule Castle tower_open: # Crystals required to open GT - '0': 0 - '1': 0 - '2': 0 - '3': 0 - '4': 0 - '5': 0 - '6': 0 - '7': 0 - random: 1 + '0': 8 + '1': 7 + '2': 6 + '3': 5 + '4': 4 + '5': 3 + '6': 2 + '7': 1 + random: 0 ganon_open: # Crystals required to hurt Ganon - '0': 0 - '1': 0 - '2': 0 - '3': 0 - '4': 0 - '5': 0 - '6': 0 - '7': 0 - random: 1 + '0': 8 + '1': 7 + '2': 6 + '3': 5 + '4': 4 + '5': 3 + '6': 2 + '7': 1 + random: 0 world_state: - standard: 1 # Do standard escape to bring Zelda to Sanctuary - open: 9 # Start with the ability to skip the standard opening and go where you want - inverted: 0 # You start in the Dark World, the Light World has changes to it's Map and requires a Moon Pearl to not be Bunny - retro: 0 # Keys are universal, you have to buy a quiver, there are take any caves and some other changes. Makes it more like Z1 + standard: 1 # Begin the game by rescuing Zelda from her cell and escorting her to the Sanctuary. + open: 1 # Begin the game from your choice of Link's House or the Sanctuary + inverted: 0 # Begin in the Dark World. The Moon Pearl is required to avoid bunny-state in Light World, and the Light World game map is altered + retro: 0 # Small keys are universal, you must buy a quiver, take-any caves and an old-man cave are added to the world. You may need to find your sword from the old man's cave hints: - 'on': 1 # Hint tiles can give useful item location hints on occasion - 'off': 0 # You get gameplay hints, but not location/item hints -weapons: # this means swords - randomized: 5 # Your swords can be anywhere - assured: 2 # You start with a sword, the rest are anywhere - vanilla: 3 # Your swords are in vanilla locations in your own game (Uncle, Pyramid Fairy, Smiths, Pedestal) - swordless: 0 # You don't have a sword. A hammer can be used like a Master Sword in certain situations + 'on': 1 # Hint tiles sometimes give item location hints + 'off': 0 # Hint tiles provide gameplay tips +weapons: # Specifically, swords + randomized: 0 # Swords are placed randomly throughout the world + assured: 1 # Begin with a sword, the rest are placed randomly throughout the world + vanilla: 0 # Swords are placed in vanilla locations in your own game (Uncle, Pyramid Fairy, Smiths, Pedestal) + swordless: 0 # Your swords are replaced by rupees. Gameplay changes have been made to accommodate this change. item_pool: - normal: 1 - hard: 0 - expert: 0 - crowd_control: 0 + normal: 1 # Item availability remains unchanged from the vanilla game + hard: 0 # Reduced upgrade availability (max: 14 hearts, green mail, tempered sword, fire shield, no silvers unless swordless) + expert: 0 # Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless) + crowd_control: 0 # Unless you know what you're doing, leave this at 0 item_functionality: - normal: 1 - hard: 0 - expert: 0 + normal: 1 # Vanilla game item functionality + hard: 0 # Reduced helpfulness of items (potions less effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs do not stun, silvers disabled outside ganon) + expert: 0 # Vastly helpfulness of items (potions barely effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs and hookshot do not stun, silvers disabled outside ganon) boss_shuffle: - none: 1 - simple: 0 # existing bosses gets shuffled around - full: 0 # all bosses exist once, except 3 can appear twice - random: 0 # any boss can appear any number of times + none: 1 # No boss shuffle + simple: 0 # Existing bosses except Ganon and Agahnim are shuffled throughout dungeons + full: 0 # Replace GT bosses with random bosses, then follow simple logic + random: 0 # Choose from one of the above options enemy_shuffle: - none: 1 - shuffled: 0 # enemies get shuffled around - random: 0 # any enemy can appear any number of times + none: 1 # Vanilla enemy placement + shuffled: 0 # Enemies are randomized + random: 0 # Choose one of the above enemy_damage: - default: 1 - shuffled: 0 # damage tables get shuffled, however armor effects are not - random: 0 # all damages are completely shuffled, including armor effects, making it possible red mail is worse than green + default: 1 # Vanilla enemy damage + shuffled: 0 # Enemies do a randomized amount of damage + random: 0 # Choose one of the above enemy_health: - default: 1 - easy: 0 - hard: 0 - expert: 0 -pot_shuffle: # Shuffle pots, their contents and whatever is hiding under them. Broken with any door shuffle that is not vanilla, do not combine - on: 0 - off: 1 -beemizer: # replace items with bees, that will attack you - 0: 1 - 1: 0 # max 15 hearts - 2: 0 # max 10 hearts - 3: 0 - 4: 0 + default: 1 # Vanilla enemy HP + easy: 0 # Enemies have reduced health + hard: 0 # Enemies have increased health + expert: 0 # Enemies have greatly increased health +pot_shuffle: + 'on': 0 # Keys, items, and buttons hidden under pots in dungeons are shuffled with other pots in their supertile + 'off': 1 # Default pot item locations +beemizer: # Remove items from the global item pool and replace them with single bees and bee traps + 0: 1 # No bee traps are placed + 1: 0 # 25% of the non-essential item pool is replaced with bee traps + 2: 0 # 60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees + 3: 0 # 100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees + 4: 0 # 100% of the non-essential item pool is replaced with bee traps timer: none: 1 timed: 0 @@ -128,39 +152,39 @@ remote_items: # Warning: currently broken. Stores all your items on the server, on: 0 # intended for racing, as the item information is missing from the ROM off: 1 rom: - sprite: - random: 1 - randomonhit: 1 - link: 1 # to get other sprite names, open up gui/Creator, select a sprite and write down the sprite name as it is there - disablemusic: off # turn on for V30 MSU packs - extendedmsu: off #turn on to have V31 extended MSU support - quickswap: - on: 1 # press L/R to swap items without opening the menu - off: 0 - menuspeed: + sprite: # Enter the name of your preferred sprite and weight it appropriately + random: 0 + randomonhit: 0 + link: 1 + disablemusic: off # If "on", all in-game music will be disabled + extendedmsu: on # If "on", V31 extended MSU support will be available + quickswap: # Enable switching items by pressing the L+R shoulder buttons + on: 0 + off: 1 + menuspeed: # Control how fast the item menu opens and closes normal: 1 instant: 0 double: 0 triple: 0 quadruple: 0 half: 0 - heartcolor: + heartcolor: # Control the color of your health hearts red: 1 - blue: 1 - green: 1 - yellow: 1 + blue: 0 + green: 0 + yellow: 0 random: 0 - heartbeep: + heartbeep: # Control the frequency of the low-health beeping double: 0 normal: 1 half: 0 quarter: 0 off: 0 - ow_palettes: - default: 1 - random: 1 # shuffle the palette of overworld colors - blackout: 0 # makes everything blank, making it almost a blind playthrough - uw_palettes: - default: 1 - random: 1 # shuffle the palette of dungeon/cave colors - blackout: 0 # makes everything blank, making it almost a blind playthrough + ow_palettes: # Change the colors of the overworld + default: 1 # No changes + random: 0 # Shuffle the colors + blackout: 0 # Never use this + uw_palettes: # Change the colors of caves and dungeons + default: 1 # No changes + random: 0 # Shuffle the colors + blackout: 0 # Never use this diff --git a/host.yaml b/host.yaml index 90ffaaa1..ad3b4ef0 100644 --- a/host.yaml +++ b/host.yaml @@ -52,7 +52,7 @@ multi_mystery_options: zip_spoiler: 0 #include the multidata file in the zip, 2 -> delete the non-zipped one, which also means the server won't autostart zip_multidata: 0 - #zip algorithm to use + #zip algorithm to use. zip is recommended for patch files, 7z is recommended for roms. All of them get the job done. zip_format: 1 # 1 -> zip, 2 -> 7z, 3->bz2 #create roms flagged as race roms race: 0 \ No newline at end of file diff --git a/inno_setup.iss b/inno_setup.iss index e74dc446..56f998a7 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -1,116 +1,133 @@ -#define sourcepath "build\exe.win-amd64-3.8\" -#define MyAppName "BerserkerMultiWorld" -#define MyAppExeName "BerserkerMultiClient.exe" -#define MyAppIcon "icon.ico" - -[Setup] -; NOTE: The value of AppId uniquely identifies this application. -; Do not use the same AppId value in installers for other applications. -AppId={{6D826EE0-49BE-4B36-BACE-09C6971CD85C}} -AppName={#MyAppName} -AppVerName={#MyAppName} -DefaultDirName={commonappdata}\{#MyAppName} -DisableProgramGroupPage=yes -OutputDir=setups -OutputBaseFilename=Setup {#MyAppName} -Compression=lzma2 -SolidCompression=yes -LZMANumBlockThreads=8 -ArchitecturesInstallIn64BitMode=x64 -ChangesAssociations=yes -ArchitecturesAllowed=x64 -AllowNoIcons=yes -SetupIconFile={#MyAppIcon} -UninstallDisplayIcon={app}\{#MyAppExeName} -LicenseFile= LICENSE -WizardStyle= modern -SetupLogging=yes - -[Languages] -Name: "english"; MessagesFile: "compiler:Default.isl" - -[Tasks] -Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; - - -[Dirs] -NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; - -[Files] -Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external -Source: "{#sourcepath}*"; Excludes: "*.key, *.log, *.hpkey"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall -; NOTE: Don't use "Flags: ignoreversion" on any shared system files - -[Icons] -Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; -Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; -Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon -Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon - -[Run] -Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." -; Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent - -[UninstallDelete] -Type: dirifempty; Name: "{app}" - -[Registry] - -Root: HKCR; Subkey: ".bmbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "{#MyAppName} Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\{#MyAppExeName},0"; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: "" - - - -[Code] -// See: https://stackoverflow.com/a/51614652/2287576 -function IsVCRedist64BitNeeded(): boolean; -var - strVersion: string; -begin - if (RegQueryStringValue(HKEY_LOCAL_MACHINE, - 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Version', strVersion)) then - begin - // Is the installed version at least 14.24 ? - Log('VC Redist x64 Version : found ' + strVersion); - Result := (CompareStr(strVersion, 'v14.24.28127.4') < 0); - end - else - begin - // Not even an old version installed - Log('VC Redist x64 is not already installed'); - Result := True; - end; -end; - -var ROMFilePage: TInputFileWizardPage; -var R : longint; - -procedure InitializeWizard(); -begin - ROMFilePage := - CreateInputFilePage( - wpLicense, - 'Select ROM File', - 'Where is your Zelda no Densetsu - Kamigami no Triforce (Japan).sfc located?', - 'Select the file, then click Next.'); - - ROMFilePage.Add( - 'Location of ROM file:', - 'SNES ROM files|*.sfc|All files|*.*', - '.sfc'); -end; - -function GetROMPath(Param: string): string; -begin - if Assigned(RomFilePage) then begin - R := CompareStr(GetMD5OfFile(ROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') - if R <> 0 then - MsgBox('ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); - Result := ROMFilePage.Values[0] - end - else - Result := ''; - end; +#define sourcepath "build\exe.win-amd64-3.8\" +#define MyAppName "BerserkerMultiWorld" +#define MyAppExeName "BerserkerMultiClient.exe" +#define MyAppIcon "icon.ico" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +AppId={{6D826EE0-49BE-4B36-BACE-09C6971CD85C}} +AppName={#MyAppName} +AppVerName={#MyAppName} +DefaultDirName={commonappdata}\{#MyAppName} +DisableProgramGroupPage=yes +OutputDir=setups +OutputBaseFilename=Setup {#MyAppName} +Compression=lzma2 +SolidCompression=yes +LZMANumBlockThreads=8 +ArchitecturesInstallIn64BitMode=x64 +ChangesAssociations=yes +ArchitecturesAllowed=x64 +AllowNoIcons=yes +SetupIconFile={#MyAppIcon} +UninstallDisplayIcon={app}\{#MyAppExeName} +LicenseFile= LICENSE +WizardStyle= modern +SetupLogging=yes + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; + + +[Dirs] +NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; + +[Files] +Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external +Source: "{#sourcepath}*"; Excludes: "*.key, *.log, *.hpkey"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; +Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." + +[UninstallDelete] +Type: dirifempty; Name: "{app}" + +[Registry] + +Root: HKCR; Subkey: ".bmbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "{#MyAppName} Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\{#MyAppExeName},0"; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: "" + + + +[Code] +// See: https://stackoverflow.com/a/51614652/2287576 +function IsVCRedist64BitNeeded(): boolean; +var + strVersion: string; +begin + if (RegQueryStringValue(HKEY_LOCAL_MACHINE, + 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Version', strVersion)) then + begin + // Is the installed version at least 14.24 ? + Log('VC Redist x64 Version : found ' + strVersion); + Result := (CompareStr(strVersion, 'v14.24.28127.4') < 0); + end + else + begin + // Not even an old version installed + Log('VC Redist x64 is not already installed'); + Result := True; + end; +end; + +var ROMFilePage: TInputFileWizardPage; +var R : longint; +var rom: string; + +procedure InitializeWizard(); +begin + rom := FileSearch('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', WizardDirValue()); + if Length(rom) > 0 then + begin + log('existing ROM found'); + log(IntToStr(CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173'))); + if CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173') = 0 then + begin + log('existing ROM verified'); + exit; + end; + log('existing ROM failed verification'); + end; + rom := '' + ROMFilePage := + CreateInputFilePage( + wpLicense, + 'Select ROM File', + 'Where is your Zelda no Densetsu - Kamigami no Triforce (Japan).sfc located?', + 'Select the file, then click Next.'); + + ROMFilePage.Add( + 'Location of ROM file:', + 'SNES ROM files|*.sfc|All files|*.*', + '.sfc'); +end; + +function GetROMPath(Param: string): string; +begin + if Length(rom) > 0 then + Result := rom + else if Assigned(RomFilePage) then + begin + R := CompareStr(GetMD5OfFile(ROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') + if R <> 0 then + MsgBox('ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := ROMFilePage.Values[0] + end + else + Result := ''; + end; diff --git a/setup.py b/setup.py index 7ead2143..d5e170b9 100644 --- a/setup.py +++ b/setup.py @@ -100,5 +100,11 @@ extra_data = ["LICENSE", "data", "EnemizerCLI", "host.yaml", "QUsb2Snes", "meta. for data in extra_data: installfile(Path(data)) +os.makedirs(buildfolder / "Players", exist_ok=True) +shutil.copyfile("easy.yaml", buildfolder / "Players" / "easy.yaml") + +qusb2sneslog = buildfolder / "QUsb2Snes" / "log.txt" +if os.path.exists(qusb2sneslog): + os.remove(qusb2sneslog) manifest_creation()