'OHRRPGCE CUSTOM - Distribute Game menu '(C) Copyright 1997-2023 James Paige, Ralph Versteegen, and the OHRRPGCE Developers 'Dual licensed under the GNU GPL v2+ and MIT Licenses. Read LICENSE.txt for terms and disclaimer of liability. #include "config.bi" #include "udts.bi" #include "const.bi" #include "allmodex.bi" #include "common.bi" #include "loading.bi" #include "customsubs.bi" #include "flexmenu.bi" #include "slices.bi" #include "cglobals.bi" #include "uiconst.bi" #include "scrconst.bi" #include "vbcompat.bi" 'DATESERIAL, NOW #include "editorkit.bi" TYPE FnGatherFiles as FUNCTION (build_variant as string, basename as string, destdir as string, distinfo as DistribState) as bool DECLARE FUNCTION distribute_game_as_windows_zip (dest_override as string = "") as string DECLARE FUNCTION distribute_game_as_windows_installer (dest_override as string = "") as string DECLARE FUNCTION distribute_game_as_linux_tarball (which_arch as string, dest_override as string = "") as string DECLARE FUNCTION distribute_game_as_web_zip (dest_override as string = "") as string DECLARE FUNCTION gather_common_files (basename as string, destdir as string, distinfo as DistribState, newline as string) as bool DECLARE FUNCTION gather_files_for_windows (buildname as string, basename as string, destdir as string, distinfo as DistribState) as bool DECLARE FUNCTION gather_files_for_linux (which_arch as string, basename as string, destdir as string, distinfo as DistribState) as bool DECLARE FUNCTION gather_files_for_mac (which_arch as string, basename as string, destdir as string, distinfo as DistribState) as bool DECLARE FUNCTION gather_files_for_web (buildname as string, basename as string, destdir as string, distinfo as DistribState) as bool DECLARE FUNCTION add_windows_gameplayer(basename as string, destdir as string) as string DECLARE FUNCTION add_linux_gameplayer(which_arch as string, basename as string, destdir as string) as string DECLARE FUNCTION add_mac_gameplayer(which_arch as string, basename as string, destdir as string) as string DECLARE FUNCTION add_web_gameplayer(basename as string, destdir as string) as string DECLARE FUNCTION package_game (build_variant as string, byref destname as string, dest_override as string = "", use_subdir as bool, gather_files as FnGatherFiles, byref basename as string = "") as string DECLARE FUNCTION agreed_to_download(agree_file as string, description as string) as bool DECLARE FUNCTION download_gameplayer_if_needed(url as string, destfile as string, agree_filename as string, description as string) as bool DECLARE FUNCTION sanity_check_buildinfo(buildinfo_file as string) as bool DECLARE FUNCTION valid_arch(which_arch as string, options as string) as bool DECLARE FUNCTION dist_prompt_overwrite_file(destname as string) as bool DECLARE FUNCTION dist_make_temp_dir(dirname as string = "package") as string DECLARE FUNCTION gameplayer_dir() as string DECLARE FUNCTION find_or_download_innosetup () as string DECLARE FUNCTION find_innosetup () as string DECLARE FUNCTION win_or_wine_drive(letter as string) as string DECLARE FUNCTION win_or_wine_spawn_and_wait (cmd as string, args as string="") as string DECLARE FUNCTION write_innosetup_script (basename as string, gamename as string, isstmp as string) as string DECLARE FUNCTION win_path (filename as string) as string DECLARE FUNCTION copy_or_relump (src_rpg_or_rpgdir as string, dest_rpg as string) as bool DECLARE FUNCTION find_and_copy_windows_gameplayer (basename as string, destdir as string) as bool DECLARE SUB insert_windows_exe_icon (exe_name as string, ico_name as string) DECLARE SUB needed_windows_libs(gameplayer as string, byref files as string vector, buildinfo() as string) DECLARE FUNCTION copy_linux_gameplayer (gameplayer as string, basename as string, destdir as string) as bool DECLARE FUNCTION distribute_game_as_debian_package (which_arch as string, dest_override as string = "") as string DECLARE FUNCTION get_debian_package_version() as string DECLARE FUNCTION get_debian_package_name() as string DECLARE SUB write_linux_menu_file(title as string, filename as string, basename as string) DECLARE SUB write_linux_desktop_file(title as string, filename as string, basename as string) DECLARE SUB write_debian_binary_file (filename as string) DECLARE SUB write_debian_control_file(controlfile as string, basename as string, pkgver as string, size_in_kibibytes as integer, byref distinfo as DistribState, deb_arch as string) DECLARE SUB write_debian_copyright_file (filename as string, license_binary as string) DECLARE FUNCTION gzip_file (filename as string) as bool DECLARE FUNCTION gunzip_file (filename as string) as bool DECLARE FUNCTION create_zipfile(zipfile as string, start_in_dir as string, files as string) as bool DECLARE FUNCTION extract_zipfile(zipfile as string, into_dir as string) as bool DECLARE FUNCTION create_tarball(tarball as string, start_in_dir as string, files as string) as bool DECLARE FUNCTION extract_tarball(tarball as string, into_dir as string, files as string = "") as bool DECLARE FUNCTION create_ar_archive(start_in_dir as string, archive as string, files as string) as bool DECLARE FUNCTION create_win_installer(installer as string, isstmp as string, basename as string, distinfo as DistribState) as bool DECLARE SUB fix_deb_group_permissions(start_at_dir as string) DECLARE SUB write_debian_postrm_script (filename as string) DECLARE SUB write_debian_postinst_script (filename as string) DECLARE FUNCTION dist_find_helper_app(appname as string) as string DECLARE FUNCTION can_make_tarballs () as bool DECLARE FUNCTION can_run_windows_exes () as bool DECLARE FUNCTION can_make_debian_packages () as bool DECLARE FUNCTION can_make_mac_packages () as bool DECLARE SUB edit_distrib_info () DECLARE SUB sanitize_distinfo OVERLOAD () DECLARE SUB sanitize_distinfo OVERLOAD (distinfo as DistribState) DECLARE FUNCTION sanitize_pkgname(s as string) as string DECLARE FUNCTION sanitize_email(s as string) as string DECLARE FUNCTION sanitize_url(s as string) as string DECLARE FUNCTION sanitize_url_chunk(byval s as string) as string DECLARE SUB export_readme_text_file (LE as string=LINE_END, byval wrap as integer=72) DECLARE SUB write_readme_text_file (filename as string, LE as string=LINE_END, byval wrap as integer=72) DECLARE FUNCTION get_game_license_text_file() as string DECLARE SUB maybe_write_license_text_file (filename as string) DECLARE FUNCTION is_known_license(license_code as string) as bool DECLARE FUNCTION generate_copyright_line(distinfo as DistribState) as string DECLARE FUNCTION distribute_game_as_mac_app (which_arch as string, dest_override as string = "") as string DECLARE FUNCTION fix_mac_app_executable_bit_on_windows(zipfile as string, exec_path_in_zip as string) as bool DECLARE SUB dist_basicstatus (s as string) DECLARE FUNCTION extract_web_data_files(js_file as string, data_file as string, output_dir as string) as bool DECLARE FUNCTION recreate_web_data_file(js_file as string, data_file as string, from_dir as string) as bool DECLARE FUNCTION web_data_cleanup(basename as string, output_dir as string) as bool DECLARE SUB itch_io_options_menu() DECLARE FUNCTION itch_game_url(distinfo as DistribState) as string DECLARE FUNCTION itch_target(distinfo as DistribState) as string DECLARE FUNCTION itch_gametarg(distinfo as DistribState) as string DECLARE FUNCTION itch_butler_is_logged_in() as bool DECLARE FUNCTION itch_butler_setup() as bool DECLARE FUNCTION itch_butler_download() as bool DECLARE SUB itch_butler_upload(distinfo as DistribState) DECLARE FUNCTION itch_butler_error_check(out_s as string, err_s as string) as bool DECLARE FUNCTION steamworks_zip() as string DECLARE SUB download_steamworks() DECLARE FUNCTION add_steamworks_lib(libname as string, outdir as string) as string DECLARE FUNCTION distribute_game_for_steam_linux (which_arch as string) as string DECLARE FUNCTION distribute_game_for_steam_windows () as string DECLARE FUNCTION dist_yesno(capt as string, byval defaultval as bool=YES, byval escval as bool=NO) as bool DECLARE SUB dist_info (msg as zstring ptr, errlvl as errorLevelEnum = errDebug) DIM SHARED auto_choose_default as bool = NO '#################################### Menu ##################################### TYPE DistribMenu EXTENDS EditorKit known_licenses(9) as StringEnumOption = {(@"COPYRIGHT"), (@"PUBLICDOMAIN"), (@"GPL"), (@"MIT"), (@"CC-BY"), (@"CC-BY-SA"), (@"CC-BY-ND"), (@"CC-BY-NC"), (@"CC-BY-NC-SA"), (@"CC-BY-NC-ND")} distinfo as DistribState tools_for_mac as bool tools_for_win_installer as bool tools_for_linux as bool tools_for_debian as bool butler_logged_in as bool have_steamworks_libs as bool DECLARE SUB refresh_tools() DECLARE SUB def_dist_str(title as zstring ptr, byref datum as string, helpkey_suffix as string, multline_hint as bool = NO) DECLARE FUNCTION def_steam_export(title as zstring ptr) as bool DECLARE SUB toplevel_menu() DECLARE SUB distinfo_menu() DECLARE SUB itch_io_menu() DECLARE SUB steam_menu() DECLARE SUB contents_menu() DECLARE VIRTUAL SUB define_items() END TYPE SUB DistribMenu.refresh_tools() tools_for_mac = can_make_mac_packages() tools_for_win_installer = can_run_windows_exes() tools_for_linux = can_make_tarballs() tools_for_debian = can_make_debian_packages() END SUB #DEFINE presave save_current_game 0 SUB DistribMenu.toplevel_menu() helpkey = "distribute_game" IF defitem_act("Edit distribution info...") THEN enter_submenu "distinfo" IF defitem_act("Customize package contents...") THEN enter_submenu "contents" IF defitem_act("Export README text file") THEN export_readme_text_file #IFDEF MINIMAL_OS defunselectable "Packaging is unsupported on this platform" defunselectable "Download a desktop copy of the OHRRPGCE." #ELSE IF defitem_act("Export Windows .zip") THEN presave : distribute_game_as_windows_zip IF tools_for_win_installer THEN IF defitem_act("Export Windows Installer") THEN presave : distribute_game_as_windows_installer ELSE 'Unlike others, can't autoinstall the needed tools defdisabled "Can't Export Windows Installer" defunselectable " (requires Windows or wine)" END IF IF defitem_act("Export Mac OS X App Bundle (64bit)") THEN presave : distribute_game_as_mac_app "x86_64" IF NOT tools_for_mac THEN defunselectable " (requires tar+gzip)" END IF IF defitem_act("Export Linux Tarball (64bit)") THEN presave : distribute_game_as_linux_tarball "x86_64" IF NOT tools_for_linux THEN defunselectable " (requires tar+gzip)" END IF IF defitem_act("Export Debian Linux Package (64bit)") THEN presave : distribute_game_as_debian_package "x86_64" IF NOT tools_for_debian THEN defunselectable " (requires ar+tar+gzip)" END IF IF defitem_act("Export Web Browser .zip") THEN presave : distribute_game_as_web_zip IF defitem_act("Upload this game to itch.io...") THEN enter_submenu "itch_io" IF defitem_act("Package for Steam...") THEN enter_submenu "steam" section "Obsolete targets" IF defitem_act("Export Mac OS X App Bundle (old 32bit Macs)") THEN presave : distribute_game_as_mac_app "x86" IF NOT tools_for_mac THEN defunselectable " (requires tar+gzip)" END IF IF defitem_act("Export Linux Tarball (32bit)") THEN presave : distribute_game_as_linux_tarball "x86" IF NOT tools_for_linux THEN defunselectable " (requires tar+gzip)" END IF IF defitem_act("Export Debian Linux Package (32bit)") THEN presave : distribute_game_as_debian_package "x86" IF NOT tools_for_debian THEN defunselectable " (requires ar+tar+gzip)" END IF #ENDIF spacer defunselectable " Game file: " & decode_filename(trimpath(sourcerpg)) DIM iconbase as string = trimextension(sourcerpg) DIM iconname as string = decode_filename(trimpath(iconbase)) defunselectable " Windows icon: " & iconname & ".ico" IF isfile(iconbase & ".ico") = NO THEN set_caption "not found" : set_disabled defunselectable " Mac icon: " & iconname & ".icns" IF isfile(iconbase & ".icns") = NO THEN set_caption "not found" : set_disabled 'Any of the distribute options might have downloaded a tool IF want_activate AND NOT want_exit THEN refresh_tools END SUB SUB DistribMenu.def_dist_str(title as zstring ptr, byref datum as string, helpkey_suffix as string, multline_hint as bool = NO) defstr title, datum set_helpkey "edit_distrib_info_" & helpkey_suffix IF multline_hint THEN set_tooltip "Press ENTER to edit multiple lines" multiline_editable IF edited THEN sanitize_distinfo distinfo dont_write END IF END SUB SUB DistribMenu.distinfo_menu () helpkey = "edit_distrib_info" def_dist_str "Package name:", distinfo.pkgname, "pkgname" def_dist_str "Game name:", distinfo.gamename, "gamename" def_dist_str "Author:", distinfo.author, "author" def_dist_str "Email:", distinfo.email, "email" def_dist_str "Description:", distinfo.description, "description", YES def_dist_str "More Description:", distinfo.more_description, "more_description", YES def_dist_str "Website:", distinfo.website, "website" def_dist_str "Copyright year:", distinfo.copyright_year, "copyright_year" set_tooltip generate_copyright_line(distinfo) defitem "License:" edit_str_enum distinfo.license, known_licenses() set_helpkey "edit_distrib_info_license" set_tooltip generate_copyright_line(distinfo) 'Save when exiting this submenu IF want_exit THEN save_distrib_state distinfo END SUB 'Check, normalise, and add a file/dir to the extra_files list FUNCTION add_extra_file(path as string, distinfo as DistribState) as bool IF LEN(path) = 0 THEN RETURN NO 'Cancelled browse() path = simplify_path_further(path) #IFDEF __FB_WIN32__ 'Unix path separators for better portability replacestr path, "\", "/" #ENDIF IF isdir(path) THEN DIM file_count as integer count_directory_size(path, file_count, 1000) IF file_count >= 1000 THEN dist_info path & " contains over 1000 files, refusing to add it.", errInfo RETURN NO END IF ELSEIF isfile(path) THEN ELSE RETURN NO END IF a_append distinfo.extra_files(), path RETURN YES END FUNCTION SUB DistribMenu.contents_menu () helpkey = "edit_distrib_contents" STATIC license_file as string IF refresh THEN license_file = get_game_license_text_file() section "Packages will include:" defunselectable distinfo.pkgname & ".rpg" set_caption " (Always, except Mac)" defunselectable distinfo.pkgname & ".exe/" & distinfo.pkgname & ".app/" & distinfo.pkgname set_caption " (Win/Mac/Linux)" defunselectable "(Software libraries)" set_caption " (As needed)" defunselectable "LICENSE-binary.txt" set_caption " (Always)" IF license_file <> "" THEN defbool "LICENSE.txt (" & distinfo.license & "):", distinfo.omit_license captions_yesno "Omit", "Include" END IF defbool "README-" & distinfo.pkgname & ".txt (Autogenerated):", distinfo.omit_readme captions_yesno "Omit", "Include" subsection " Custom extra files:" FOR idx as integer = 0 TO UBOUND(distinfo.extra_files) DIM path as string = distinfo.extra_files(idx) DIM info as string = "" defitem path IF refresh THEN 'Can't do this every frame SELECT CASE get_file_type(path) CASE fileTypeFile info = " (" & filesize(path) & ")" CASE fileTypeDirectory DIM file_count as integer DIM file_size as integer = count_directory_size(path, file_count, 1000) info = " (" & file_count & " files, " & format_filesize(file_size) & ")" CASE fileTypeNonexistent set_caption " (MISSING)" set_disabled CASE fileTypeError set_caption " (UNREADABLE)" set_disabled CASE ELSE 'fileTypeOther (e.g. symlink) set_caption " (INVALID)" set_disabled END SELECT END IF DIM pathinfo as string IF trimfilename(path) <> "" THEN pathinfo = path & " -> " & trimpath(path) & !"\n" set_tooltip pathinfo & "Press DELETE to remove" IF delete_action() THEN a_pop distinfo.extra_files(), idx edited = YES EXIT FOR END IF SELECT CASE get_file_type(path) CASE fileTypeFile, fileTypeDirectory defunselectable info END SELECT NEXT IF defitem_act("Add file...") THEN IF add_extra_file(browse(browseAny, , "*", "browse_for_distrib_extra"), distinfo) THEN edited = YES END IF IF defitem_act("Add directory...") THEN IF add_extra_file(browse(browseDir, , , "browse_for_folder"), distinfo) THEN edited = YES END IF IF want_exit THEN save_distrib_state distinfo END SUB SUB DistribMenu.itch_io_menu () helpkey = "distribute_game_itch_io" defstr "Your itch.io username:", distinfo.itch_user, 63 IF edited THEN valuestr = sanitize_url_chunk(valuestr) defstr "itch.io game name:", distinfo.itch_gamename, 63 caption_default_or_str "" IF edited THEN valuestr = sanitize_url_chunk(valuestr) defbool "Also upload web build:", distinfo.itch_upload_web captions_yesno "Yes", "No" IF distinfo.itch_upload_web THEN defunselectable " In itch.io website you will need to manually set:" defunselectable " Kind of project: HTML" defunselectable " Click ""this file will be played in the browser"" on " & distinfo.pkgname & "-web.zip" defunselectable " If you see ""Failed to find index.html"" error on first play," defunselectable " go and click ""this file will be played in the browser"" again." defunselectable " itch.io seems to have a bug where the wrong file zip gets" defunselectable " selected the first time you choose that option." END IF IF butler_logged_in THEN IF defitem_act("Upload to itch.io now") THEN presave : itch_butler_upload(distinfo) ELSE IF defitem_act("Set up itch.io butler tool now") THEN itch_butler_setup() butler_logged_in = itch_butler_is_logged_in() END IF END IF spacer defunselectable " Game package name: " & distinfo.pkgname IF LEN(distinfo.itch_user) = 0 THEN defunselectable " Enter username to estimate URL" ELSE defunselectable " " & itch_game_url(distinfo) defunselectable " butler target: " & itch_target(distinfo) END IF IF want_exit THEN save_distrib_state distinfo END SUB FUNCTION DistribMenu.def_steam_export(title as zstring ptr) as bool defitem title IF distinfo.steam_appid = 0 ORELSE have_steamworks_libs = NO THEN set_disabled RETURN activate END FUNCTION SUB DistribMenu.steam_menu() helpkey = "distribute_game_steam" defint "Steam appid:", distinfo.steam_appid, 0, INT_MAX IF edited THEN save_distrib_state distinfo set_helpkey "edit_distrib_info_steam_appid" IF def_steam_export("Export Windows build for Steam") THEN presave : distribute_game_for_steam_windows IF def_steam_export("Export Linux 64-bit build for Steam") THEN presave : distribute_game_for_steam_linux "x86_64" IF def_steam_export("Export Linux 32-bit (obsolete) build for Steam") THEN presave : distribute_game_for_steam_linux "x86" IF distinfo.steam_appid = 0 THEN defunselectable " (Missing appid)" END IF IF have_steamworks_libs = NO THEN defunselectable " (Missing steamworks)" IF defitem_act("Download Steamworks libraries...") THEN download_steamworks() have_steamworks_libs = isfile(steamworks_zip()) END IF END IF END SUB SUB DistribMenu.define_items() SELECT CASE submenu CASE "": toplevel_menu CASE "distinfo": distinfo_menu CASE "contents": contents_menu CASE "itch_io": itch_io_menu CASE "steam": steam_menu END SELECT END SUB SUB distribute_game_menu () DIM menu as DistribMenu load_distrib_state menu.distinfo menu.refresh_tools() menu.butler_logged_in = itch_butler_is_logged_in() menu.have_steamworks_libs = isfile(steamworks_zip()) menu.run() 'Submenus already save menu.distinfo when leaving 'Revert genCurrentDebugMode to the author's choice in genDebugMode... I think this is only needed 'if using Test Game at the same time gen(genCurrentDebugMode) = gen(genDebugMode) END SUB '################################################################################ SUB sanitize_distinfo(distinfo as DistribState) distinfo.pkgname = sanitize_pkgname(distinfo.pkgname) distinfo.gamename = special_char_sanitize(exclude(distinfo.gamename, "/\""" & CHR(10))) distinfo.author = special_char_sanitize(exclude(distinfo.author, "<>@""" & CHR(10))) distinfo.email = sanitize_email(distinfo.email) distinfo.website = sanitize_url(distinfo.website) distinfo.copyright_year = exclusive(distinfo.copyright_year, "0123456789 -,") END SUB SUB sanitize_distinfo() DIM distinfo as DistribState load_distrib_state distinfo sanitize_distinfo distinfo save_distrib_state distinfo END SUB FUNCTION sanitize_pkgname(s as string) as string RETURN LCASE(special_char_sanitize(exclude(s, "<>?./\ ""'" + CHR(10)))) END FUNCTION FUNCTION sanitize_email(s as string) as string '--This e-mail address sanitization is far from perfect, but good enough for most cases RETURN special_char_sanitize(exclusive(s, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_@.+")) END FUNCTION FUNCTION sanitize_url(s as string) as string '--This website address sanitization is far from perfect, but probably good enough for most cases RETURN special_char_sanitize(exclusive(s, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.:/_+%:;?@=&'")) END FUNCTION FUNCTION sanitize_url_chunk(byval s as string) as string replacestr s, " ", "-" replacestr s, "_", "-" RETURN special_char_sanitize(exclusive(LCASE(s), "abcdefghijklmnopqrstuvwxyz0123456789-")) END FUNCTION SUB export_readme_text_file (LE as string=LINE_END, byval wrap as integer=72) DIM distinfo as DistribState load_distrib_state distinfo DIM txtfile as string = trimfilename(sourcerpg) & SLASH & "README-" & distinfo.pkgname & ".txt" IF dist_prompt_overwrite_file(txtfile) = NO THEN RETURN write_readme_text_file txtfile, LE dist_info "Created " & trimpath(txtfile), errInfo END SUB SUB write_readme_text_file (filename as string, LE as string=LINE_END, byval wrap as integer=72) 'LE is passed instead of using LINE_END directly so we can override it easily. DIM LF as string = CHR(10) DIM distinfo as DistribState load_distrib_state distinfo '--Construct the file DIM s as string = "" s &= distinfo.gamename & LF s &= generate_copyright_line(distinfo) & LF s &= LF & distinfo.description IF NOT ends_with(s, LF) THEN s &= LF IF LEN(TRIM(distinfo.more_description)) THEN s &= LF & distinfo.more_description END IF IF NOT ends_with(s, LF) THEN s &= LF s &= LF IF LEN(distinfo.website) THEN s &= distinfo.website & LF IF LEN(distinfo.email) THEN s &= distinfo.email & LF '--format the lines s = wordwrap(s, wrap) IF LF <> LE THEN 'If we want this text to have DOS/Windows line endings, convert it now replacestr(s, LF, LE) END IF write_file filename, s END SUB FUNCTION get_game_license_text_file() as string DIM distinfo as DistribState load_distrib_state distinfo IF distinfo.license = "COPYRIGHT" THEN RETURN "" IF distinfo.license = "PUBLICDOMAIN" THEN RETURN "" DIM helpdir as string = get_help_dir() DIM lic_file as string = helpdir & SLASH & "license_" & LCASE(distinfo.license) & ".txt" IF isfile(lic_file) THEN RETURN lic_file dist_info lic_file & " does not exist" RETURN "" END FUNCTION ' For some types of licenses, include a text copy SUB maybe_write_license_text_file (filename as string) DIM lic_file as string = get_game_license_text_file() IF lic_file <> "" THEN writeablecopyfile lic_file, filename END SUB ' Create /usr/share/doc/$basename/copyright file according to requirements ' https://www.debian.org/doc/debian-policy/ch-docs.html#copyright-information SUB write_debian_copyright_file (filename as string, license_binary as string) DIM LF as string = CHR(10) DIM distinfo as DistribState load_distrib_state distinfo '--Construct the file DIM s as string = "" s &= distinfo.gamename & LF s &= generate_copyright_line(distinfo) & LF IF distinfo.license = "GPL" THEN s &= LF & "See /usr/share/common-licenses/GPL-3 for license terms" & LF ELSE DIM lic_file as string = get_game_license_text_file() IF lic_file <> "" THEN s &= LF & string_from_file(lic_file) 'Normalises to Unix lineendings END IF END IF s &= LF & "The license governing the executable follows:" & LF s &= "=============================================================================" & LF s &= string_from_file(license_binary) write_file filename, s END SUB FUNCTION is_known_license(license_code as string) as bool 'duplicated known_licenses because global string arrays are a pain in the ass DIM known_licenses(9) as string = {"COPYRIGHT", "PUBLICDOMAIN", "GPL", "MIT", "CC-BY", "CC-BY-SA", "CC-BY-ND", "CC-BY-NC", "CC-BY-NC-SA", "CC-BY-NC-ND"} RETURN a_find(known_licenses(), license_code) > -1 END FUNCTION FUNCTION generate_copyright_line(distinfo as DistribState) as string DIM c_y_by as string = RTRIM("(C) Copyright " & distinfo.copyright_year & " " & distinfo.author) SELECT CASE distinfo.license CASE "COPYRIGHT": RETURN c_y_by CASE "PUBLICDOMAIN": RETURN "Copyright released by " & distinfo.author & " into the Public Domain." CASE "GPL": RETURN c_y_by & ". This game is distributed under the terms of the GNU General Public License." CASE "MIT": RETURN c_y_by & ". This game is distributed under the terms of the MIT license." CASE "CC-BY": RETURN c_y_by & ". This game is licensed under a Creative Commons Attribution 3.0 Unported License." CASE "CC-BY-SA": RETURN c_y_by & ". This game is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License." CASE "CC-BY-ND": RETURN c_y_by & ". This game is licensed under a Creative Commons Attribution-NoDerivs 3.0 Unported License." CASE "CC-BY-NC": RETURN c_y_by & ". This game is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License." CASE "CC-BY-NC-SA": RETURN c_y_by & ". This game is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License." CASE "CC-BY-NC-ND": RETURN c_y_by & ". This game is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License." CASE ELSE: showerror distinfo.license & " is not in the list of licenses that this program understands" RETURN "" END SELECT END FUNCTION ' Check which_arch is in options. ' options should be a comma delimited string like "x86, x86_64" FUNCTION valid_arch(which_arch as string, options as string) as bool ' Prevent x86 from matching against x86_64 IF INSTR(options & ",", which_arch & ",") THEN RETURN YES dist_info "Unknown arch """ & which_arch & """. The only supported values for this target are " & options END FUNCTION FUNCTION dist_prompt_overwrite_file(destname as string) as bool IF isfile(destname) THEN IF dist_yesno(decode_filename(trimpath(destname)) & " already exists. Overwrite it?") = NO THEN RETURN NO 'Okay to overwrite! (but actually do the overwriting later on) END IF RETURN YES END FUNCTION FUNCTION dist_make_temp_dir(dirname as string = "package") as string DIM pkgtmp as string = trimfilename(sourcerpg) & SLASH & dirname & ".tmp" IF isdir(pkgtmp) THEN killdir pkgtmp, YES END IF IF makedir(pkgtmp) THEN dist_info "ERROR: unable to create temporary folder " & pkgtmp RETURN "" END IF RETURN pkgtmp END FUNCTION 'Find/create the folder that we are going to download ohrrpgce-player-* archives into FUNCTION gameplayer_dir() as string DIM dldir as string = settings_dir & SLASH & "_gameplayer" IF NOT isdir(dldir) THEN IF makedir(dldir) THEN dist_info "ERROR: Unable to create """ & dldir & """ directory" RETURN "" END IF END IF RETURN dldir END FUNCTION SUB dist_basicstatus (s as string) basic_textbox s, uilook(uiText), vpage setvispage vpage, NO END SUB FUNCTION copy_or_relump (src_rpg_or_rpgdir as string, dest_rpg as string) as bool 'Return true on success, false on fail DIM extension as string = LCASE(justextension(src_rpg_or_rpgdir)) IF extension = "rpgdir" THEN dist_basicstatus "LUMPING DATA: please wait..." IF NOT write_rpg_or_rpgdir(src_rpg_or_rpgdir, dest_rpg) THEN 'Already showed error RETURN NO END IF ELSE 'simple case for regular .rpg files IF confirmed_copy(src_rpg_or_rpgdir, dest_rpg) = NO THEN dist_info "ERROR: failed to copy " & src_rpg_or_rpgdir END IF END IF RETURN YES END FUNCTION 'Run game to extract its embedded buildinfo.ini into destdir FUNCTION extract_buildinfo(gameplayer as string, destdir as string) as bool DIM spawn_ret as string DIM olddir as string = CURDIR CHDIR destdir safekill "buildinfo.ini" spawn_ret = spawn_and_wait(gameplayer, "--dump-embed buildinfo.ini") DIM exists as bool = isfile("buildinfo.ini") CHDIR olddir IF LEN(spawn_ret) THEN dist_info spawn_ret IF exists = NO THEN dist_info "ERROR: Couldn't extract buildinfo.ini from game.exe" RETURN exists END FUNCTION #IFDEF __FB_WIN32__ FUNCTION find_and_copy_windows_gameplayer (basename as string, destdir as string) as bool 'This is used only when not using a downloaded Windows build (on Windows) 'Returns true on success, false on failure DIM game_exe as string = exepath & SLASH & "game.exe" IF isfile(game_exe) = NO THEN dist_info "game.exe wasn't found in the same folder as custom.exe. Downloading instead." RETURN NO END IF REDIM buildinfo() as string IF extract_buildinfo(game_exe, destdir) THEN lines_from_file buildinfo(), destdir & SLASH & "buildinfo.ini" safekill destdir & SLASH & "buildinfo.ini" debuginfo "Found game.exe, version: " & read_ini_str(buildinfo(), "long_version") END IF 'Check buildname DIM buildname as string buildname = read_ini_str(buildinfo(), "buildname", "unknown (obsolete version?)") IF buildname <> "sdl2" THEN IF buildname = "" THEN buildname = "blank (unofficial build)" IF dist_yesno("ERROR: game.exe next to custom.exe doesn't appear to be a default (sdl2) official build (its buildname is " & buildname & "). Download an official build instead? (Recommended)") THEN RETURN NO END IF 'Vector of all files to copy DIM otherf as string vector v_new otherf v_append otherf, "game.exe" v_append otherf, "LICENSE-binary.txt" needed_windows_libs game_exe, otherf, buildinfo() FOR i as integer = 0 TO v_len(otherf) - 1 DIM dest_fname as string = otherf[i] IF otherf[i] = "game.exe" THEN dest_fname = basename & ".exe" IF confirmed_copy(exepath & SLASH & otherf[i], destdir & SLASH & dest_fname) = NO THEN v_free otherf dist_info otherf[i] & " missing/unreadable. Using your existing game.exe failed, so downloading instead." RETURN NO END IF NEXT i v_free otherf RETURN YES END FUNCTION #ENDIF SUB insert_windows_exe_icon (exe_name as string, ico_name as string) IF NOT isfile(exe_name) THEN debuginfo exe_name & " not found, ignoring attempt to change its icon" : EXIT SUB IF NOT isfile(ico_name) THEN debuginfo ico_name & " does not exist" : EXIT SUB DIM rcedit as string = find_windows_helper_app("rcedit", YES) IF rcedit = "" THEN dist_info "WARNING: rcedit not found, can't set the icon": EXIT SUB DIM args as string = escape_filename(exe_name) & " --set-icon " & escape_filename(ico_name) DIM spawn_ret as string spawn_ret = win_or_wine_spawn_and_wait(rcedit, args) IF LEN(spawn_ret) > 0 THEN dist_info "WARNING: rcedit failed when trying to set the icon: " & spawn_ret : EXIT SUB END SUB #IFDEF __FB_WIN32__ SUB needed_windows_libs(gameplayer as string, byref files as string vector, buildinfo() as string) 'Add .dlls to files needed by the gfx/music backends in buildinfo(). This is 'used only when not using a downloaded ohrrpgce-player-* zip (on Windows). 'Matches needed_windows_libs in ohrpackage.py. DIM as string gfx(), music() split read_ini_str(buildinfo(), "gfx", "sdl2"), gfx() FOR idx as integer = 0 TO UBOUND(gfx) SELECT CASE gfx(idx) CASE "directx": v_append_once files, "gfx_directx.dll" CASE "sdl": v_append_once files, "SDL.dll" CASE "sdl2": v_append_once files, "SDL2.dll" CASE "alleg": v_append_once files, "alleg40.dll" CASE "fb": 'None END SELECT NEXT split read_ini_str(buildinfo(), "music", "sdl2"), music() FOR idx as integer = 0 TO UBOUND(music) SELECT CASE music(idx) CASE "sdl": v_append_once files, "SDL.dll" v_append_once files, "SDL_mixer.dll" CASE "sdl2": v_append_once files, "SDL2.dll" v_append_once files, "SDL2_mixer.dll" CASE "native", "native2": v_append_once files, "audiere.dll" CASE "allegro": v_append_once files, "alleg40.dll" CASE "silence": 'None END SELECT NEXT END SUB #ENDIF FUNCTION copy_linux_gameplayer (gameplayer as string, basename as string, destdir as string) as bool 'Used only for debian. Returns true on success. IF confirmed_copy(gameplayer, destdir & SLASH & basename) = NO THEN RETURN NO #IFDEF __FB_UNIX__ '--just in case we are playing with a debug build, '--strip the copy of the binary that goes in the distribution file. safe_shell "strip " & escape_filename(destdir & SLASH & basename) '--fix the permissions safe_shell "chmod +x " & escape_filename(destdir & SLASH & basename) #ENDIF RETURN YES END FUNCTION FUNCTION add_windows_gameplayer(basename as string, destdir as string) as string 'Add game.exe and all other files for the Windows game player (including LICENSE-binary.txt) in destdir. 'On Windows, try to copy game.exe and libraries. Otherwise, download an ohrrpgce-player package. 'game.exe is renamed to $basename.exe, and its full path is returned. 'Returns "" for failure. DIM destexe as string = destdir & SLASH & basename & ".exe" #IFDEF __FB_WIN32__ IF find_and_copy_windows_gameplayer(basename, destdir) THEN RETURN destexe #ENDIF DIM dldir as string = gameplayer_dir() IF dldir = "" THEN RETURN "" '--Decide which url to download DIM url as string DIM dlfile as string IF version_branch = "wip" THEN '--If running a nightly wip, download the default build of the latest nightly wip '--(Before 2020-11-17 nightlies downloaded ohrrpgce-player-win-wip.zip instead, '--which was assumed to be gfx_directx+sdl[+fb], music_sdl) '--(Was for a while in 2022-23 called ohrrpgce-player-win-sdl2-wip.zip) dlfile = "ohrrpgce-player-win-wip-sdl2.zip" url = "http://hamsterrepublic.com/ohrrpgce/nightly/" & dlfile ELSE '--Use this stable release '--(Previously, always downloaded the latest stable release from '--http://hamsterrepublic.com/dl/ '-- gorgonzola and older downloaded ohrrpgce-player-win.zip (a music_sdl build) '-- hróðvitnir downloaded ohrrpgce-player-win-minimal-sdl2.zip '--see web/ohrstable.sh for info on server symlinks) dlfile = "ohrrpgce-player-win" & version_release_tag & "-sdl2.zip" url = "http://hamsterrepublic.com/ohrrpgce/archive/" & dlfile END IF DIM destzip as string = dldir & SLASH & dlfile '--Prompt & download if missing or out of date IF NOT download_gameplayer_if_needed(url, destzip, "win.download.agree", "the Windows OHRRPGCE game player") THEN RETURN "" IF extract_zipfile(destzip, destdir) = NO THEN RETURN "" 'Sanity check contents IF sanity_check_buildinfo(destdir & SLASH & "buildinfo.ini") = NO THEN RETURN "" IF NOT isfile(destdir & SLASH & "game.exe") THEN dist_info "ERROR: Failed to unzip game.exe" : RETURN "" IF renamefile(destdir & SLASH & "game.exe", destexe) = NO THEN dist_info "Couldn't rename game.exe" : RETURN "" RETURN destexe END FUNCTION FUNCTION sanity_check_buildinfo(buildinfo_file as string) as bool 'Return YES if the sanity check has passed DIM ver as integer = read_ini_int(buildinfo_file, "packaging_version", 0) IF ver > 1 THEN dist_info "ERROR: The buildinfo version in this downloaded game player is " & ver & " but your copy of the OHRRPGCE only supports version 1. This means you should upgrade to a newer version if you want to keep using the Distribute Game feature.": RETURN NO ELSEIF ver < 1 THEN 'Including a missing file dist_info "ERROR: The buildinfo version in this downloaded game player is missing or invalid. Please report this as a bug to the OHRRPGCE developers": RETURN NO END IF RETURN YES END FUNCTION FUNCTION add_linux_gameplayer(which_arch as string, basename as string, destdir as string) as string 'Download a precompiled Linux player package, 'extract it to destdir (creating it but not clearing if already existing), 'and return the full path to game.sh therein. ' 'Returns "" for failure. DIM destexe as string = destdir & SLASH & basename /' 'Don't try to use a local build, it's too much trouble to gather up and check all the necessary files, 'which won't even exist if installed from a .deb or "scons install". 'This old code needs update. #IFDEF __GNU_LINUX__ '--If this is Linux, we might already have the correct version of ohrrpgce-game DIM installed_player as string = exepath & SLASH & "ohrrpgce-game" debuginfo "Checking for " & installed_player IF isfile(installed_player) THEN DIM playerversion as string IF run_and_get_output(installed_player & " --version", playerversion) = 0 THEN 'If it's a portable build it's probably a release build too IF INSTR(playerversion, which_arch & " ") ANDALSO INSTR(playerversion, "portable") THEN debuginfo "Using installed binary " & installed_player IF copy_linux_gameplayer(installed_player, "ohrrpgce-game", destdir) THEN RETURN destdir & "/ohrrpgce-game" ELSE debuginfo installed_player & " isn't right arch or not a portable build, don't use it for distribute" END IF ELSE debuginfo "Couldn't run " & installed_player END IF END IF #ENDIF '/ DIM dldir as string = gameplayer_dir() IF dldir = "" THEN RETURN "" '--Decide which url to download DIM url as string DIM dlfile as string IF version_branch = "wip" THEN 'If using any wip release, get the latest wip release dlfile = "ohrrpgce-player-linux-wip-" & which_arch & ".zip" url = "http://hamsterrepublic.com/ohrrpgce/nightly/" & dlfile ELSE 'Use this stable release dlfile = "ohrrpgce-player-linux" & version_release_tag & "-" & which_arch & ".zip" url = "http://hamsterrepublic.com/ohrrpgce/archive/" & dlfile END IF DIM destzip as string = dldir & SLASH & dlfile '--Prompt & download if missing or out of date IF NOT download_gameplayer_if_needed(url, destzip, "linux.download.agree", "the Linux OHRRPGCE game player") THEN RETURN "" IF extract_zipfile(destzip, destdir) = NO THEN RETURN "" 'Sanity check contents IF sanity_check_buildinfo(destdir & SLASH & "buildinfo.ini") = NO THEN RETURN "" IF NOT isfile(destdir & SLASH & "game.sh") THEN dist_info "ERROR: Failed to unzip game.sh" : RETURN "" IF renamefile(destdir & SLASH & "game.sh", destexe) = NO THEN dist_info "ERROR: Couldn't rename game.sh" : RETURN "" RETURN destexe END FUNCTION 'isstmp: directory containing files to package FUNCTION create_win_installer(installer as string, isstmp as string, basename as string, distinfo as DistribState) as bool DIM iscc as string = find_or_download_innosetup() IF iscc = "" THEN RETURN NO DIM iss_script as string iss_script = write_innosetup_script(basename, distinfo.gamename, isstmp) DIM args as string 'FIXME: The following does not escape all problem characters that could occur in iss_script, 'but I'm not sure what the best way to do that is, so leaving it for now. args = """" & win_path(iss_script) & """" DIM spawn_ret as string spawn_ret = win_or_wine_spawn_and_wait(iscc, args) IF LEN(spawn_ret) THEN dist_info "ERROR: iscc.exe failed: " & spawn_ret : RETURN NO safekill installer 'Move the new installer to the correct location IF renamefile(isstmp & SLASH & "Output" & SLASH & "setup.exe", installer) = NO THEN dist_info "ERROR: iscc.exe completed but installer was not created" RETURN NO END IF RETURN YES END FUNCTION FUNCTION write_innosetup_script (basename as string, gamename as string, isstmp as string) as string DIM iss_script as string = isstmp & SLASH & "innosetup_script.iss" DIM s as string DIM E as string = !"\r\n" ' E is End of line DIM appversion as string = MID(DATE, 7, 4) & "." & MID(DATE, 1, 2) & "." & MID(DATE, 4, 2) s &= "; Inno Setup script generated by OHRRPGCE custom" & E s &= E & "[Setup]" & E s &= "AppName=" & gamename & E s &= "AppVersion=" & appversion & E 'According to the docs AppVerName is not required if both AppVerName and AppName are given, 'but apparently older Innosetup require it s &= "AppVerName=" & gamename & " version " & appversion & E s &= "DefaultDirName={pf}\" & gamename & E s &= "DefaultGroupName=" & gamename & E s &= "SolidCompression=yes" & E s &= "OutputBaseFilename=setup" & E s &= "InfoAfterFile=README-" & basename & ".txt" & E IF isfile(isstmp & SLASH & "LICENSE.txt") THEN s &= "LicenseFile=LICENSE.txt" & E END IF s &= E & "[Languages]" & E s &= "Name: ""eng""; MessagesFile: ""compiler:Default.isl""" & E s &= E & "[Files]" & E s &= "Source: ""*""; Excludes: ""innosetup_script.iss""; DestDir: ""{app}""; Flags: ignoreversion recursesubdirs" & E s &= E & "[Icons]" & E s &= "Name: ""{userdesktop}\" & gamename & """; Filename: ""{app}\" & basename & ".exe""; WorkingDir: ""{app}"";" & E s &= "Name: ""{group}\" & gamename & """; Filename: ""{app}\" & basename & ".exe""; WorkingDir: ""{app}"";" & E 'debuginfo s write_file iss_script, s RETURN iss_script END FUNCTION FUNCTION win_path (filename as string) as string #IFDEF __FB_WIN32__ 'This is a do-nothing on real Windows RETURN filename #ELSE 'When using wine, paths that start with $HOME can be translated to Z: IF LEFT(filename, 1) <> "/" THEN dist_info "ERROR: Unable to translate path for wine: " & filename RETURN filename END IF DIM winepath as string = "z:" & filename replacestr winepath, "/", "\" RETURN winepath #ENDIF END FUNCTION FUNCTION find_or_download_innosetup () as string DIM iscc as string = find_innosetup() IF iscc = "" THEN IF dist_yesno("Inno Setup 5 is required to create windows installation packages. Would you like to download it from jrsoftware.org now?") THEN 'Download 5.4.3 (from 2011), the last version to run on non-NT Windows. 'In future we could switch to 5.6.x (2019), the last version to support Win 2000 and XP. 'As a separate problem, the following link redirects to https, and wget fails to create a SSL 'connection on Win XP and older. DIM is_exe as string = settings_dir & SLASH & "is.exe" 'download_file "http://www.jrsoftware.org/download.php/is.exe", is_exe 'Latest version download_file "http://files.jrsoftware.org/is/5/isetup-5.4.3.exe", is_exe DIM spawn_ret as string spawn_ret = win_or_wine_spawn_and_wait(is_exe) safekill is_exe IF LEN(spawn_ret) THEN dist_info "ERROR: Inno Setup installer failed: " & spawn_ret : RETURN "" '--re-search for iscc now that it may have been installed iscc = find_innosetup() END IF IF iscc = "" THEN dist_info "Cancelling export. Inno Setup 5 is not available." : RETURN "" END IF RETURN iscc END FUNCTION FUNCTION find_innosetup () as string DIM c_drive as string = win_or_wine_drive("c") DIM iscc as string iscc = c_drive & SLASH & "Program Files" & SLASH & "Inno Setup 5" & SLASH & "ISCC.exe" IF isfile(iscc) THEN RETURN iscc iscc = c_drive & SLASH & "Program Files (x86)" & SLASH & "Inno Setup 5" & SLASH & "ISCC.exe" IF isfile(iscc) THEN RETURN iscc RETURN "" 'Not found END FUNCTION FUNCTION win_or_wine_drive(letter as string) as string #IFDEF __FB_WIN32__ RETURN letter & ":" #ELSE RETURN environ("HOME") & "/.wine/dosdevices/" & letter & ":" #ENDIF END FUNCTION FUNCTION win_or_wine_spawn_and_wait (cmd as string, args as string="") as string 'For running Windows programs only. On Windows run natively, on Linux Unix Mac, try to run with Wine 'Currently only needed for installing and running Innosetup and rcedit (for icons). 'Hopefully we won't ever need it for anything else DIM spawn_ret as string #IFDEF __FB_WIN32__ 'On Windows this is nice and simple RETURN spawn_and_wait(cmd, args) #ELSE 'args is probably insufficiently escaped, but not sure how to fix that DIM wine_args as string = escape_filename(cmd) & " " & escape_string(args, "\") RETURN spawn_and_wait("wine", wine_args) #ENDIF END FUNCTION FUNCTION distribute_game_as_debian_package (which_arch as string, dest_override as string = "") as string debuginfo " distribute_game_as_debian_package():" IF NOT valid_arch(which_arch, "x86, x86_64") THEN RETURN "" DIM deb_arch as string SELECT CASE which_arch CASE "x86": deb_arch = "i386" CASE "x86_64": deb_arch = "amd64" END SELECT DIM distinfo as DistribState load_distrib_state distinfo DIM basename as string = get_debian_package_name() DIM pkgver as string = get_debian_package_version() DIM debname as string = trimfilename(sourcerpg) IF dest_override <> "" THEN debname = dest_override debname &= SLASH & basename & "_" & pkgver & "_" & deb_arch & ".deb" IF dist_prompt_overwrite_file(debname) = NO THEN RETURN "" DIM debtmp as string = dist_make_temp_dir() IF debtmp = "" THEN RETURN "" debuginfo "Prepare package data files..." makedir debtmp & SLASH & "usr" makedir debtmp & SLASH & "usr" & SLASH & "share" makedir debtmp & SLASH & "usr" & SLASH & "share" & SLASH & "games" DIM bindir as string = debtmp & SLASH & "usr" & SLASH & "games" makedir bindir DIM datadir as string = debtmp & SLASH & "usr" & SLASH & "share" & SLASH & "games" & SLASH & basename makedir datadir DO '--single pass loop for breaking debuginfo "Copy rpg file" IF copy_or_relump(sourcerpg, datadir & SLASH & basename & ".rpg") = NO THEN EXIT DO debuginfo "Copy linux game player" DIM gameplayer as string DIM extractdir as string = debtmp & SLASH "extract.tmp" ' Extracts a bunch of files which we mostly ignore, including the path to game.sh it returns IF add_linux_gameplayer(which_arch, "dummy", extractdir) = "" THEN EXIT DO gameplayer = extractdir & SLASH & "linux" & SLASH & which_arch & SLASH & "ohrrpgce-game" IF isfile(gameplayer) = NO THEN dist_info "ERROR: didn't find ohrrpgce-game in downloaded zip" : EXIT DO IF copy_linux_gameplayer(gameplayer, basename, bindir) = NO THEN EXIT DO debuginfo "Create menu file" DIM menudir as string = debtmp & SLASH & "usr" & SLASH & "share" & SLASH & "menu" makedir menudir write_linux_menu_file distinfo.gamename, menudir & SLASH & basename, basename debuginfo "Create desktop file" DIM applicationsdir as string = debtmp & SLASH & "usr" & SLASH & "share" & SLASH & "applications" makedir applicationsdir write_linux_desktop_file distinfo.gamename, applicationsdir & SLASH & basename & ".desktop", basename debuginfo "Create docs" DIM docsdir as string = debtmp & SLASH & "usr" & SLASH & "share" & SLASH & "doc" makedir docsdir DIM gamedocsdir as string = docsdir & SLASH & basename makedir gamedocsdir write_readme_text_file gamedocsdir & SLASH & "README.txt", CHR(10) IF gzip_file(gamedocsdir & SLASH & "README.txt") = NO THEN EXIT DO 'Write the copyright file, which contains game copyright and license and LICENSE-binary.txt write_debian_copyright_file gamedocsdir & SLASH & "copyright", extractdir & SLASH & "LICENSE-binary.txt" debuginfo "Calculate Installed-Size" DIM size_in_kibibytes as integer = count_directory_size(debtmp & SLASH & "usr") / 1024 debuginfo "Create debian-binary version file" write_debian_binary_file debtmp & SLASH & "debian-binary" debuginfo "Create debian control file" write_debian_control_file debtmp & SLASH & "control", basename, pkgver, size_in_kibibytes, distinfo, deb_arch IF NOT isfile(debtmp & SLASH & "control") THEN dist_info "Couldn't create debian control file" : EXIT DO write_debian_postinst_script debtmp & SLASH & "postinst" write_debian_postrm_script debtmp & SLASH & "postrm" fix_deb_group_permissions debtmp & SLASH & "control" fix_deb_group_permissions debtmp & SLASH & "postinst" fix_deb_group_permissions debtmp & SLASH & "postrm" IF create_tarball(debtmp & SLASH & "control.tar.gz", debtmp, "control postinst postrm") = NO THEN EXIT DO fix_deb_group_permissions debtmp & SLASH & "usr" IF create_tarball(debtmp & SLASH & "data.tar.gz", debtmp, "usr") = NO THEN EXIT DO 'Remove old deb safekill debname 'Create new deb IF create_ar_archive(debtmp, debname, "debian-binary control.tar.gz data.tar.gz") = NO THEN EXIT DO dist_info trimpath(debname) & " was successfully created!", errInfo EXIT DO 'this loop is only ever one pass LOOP '--Cleanup temp files killdir debtmp, YES RETURN debname END FUNCTION SUB write_debian_postinst_script (filename as string) DIM LF as string = CHR(10) DIM fh as integer = FREEFILE OPEN filename for output as #fh PUT #fh, , "#!/bin/sh" & LF PUT #fh, , "set -e" & LF PUT #fh, , "if [ ""$1"" = ""configure"" ] && [ -x ""`which update-menus 2>/dev/null`"" ]; then" & LF PUT #fh, , " update-menus" & LF PUT #fh, , "fi" & LF CLOSE #fh #IFDEF __FB_UNIX__ safe_shell "chmod +x " & escape_filename(filename) #ENDIF END SUB SUB write_debian_postrm_script (filename as string) DIM LF as string = CHR(10) DIM fh as integer = FREEFILE OPEN filename for output as #fh PUT #fh, , "#!/bin/sh" & LF PUT #fh, , "set -e" & LF PUT #fh, , "if [ -x ""`which update-menus 2>/dev/null`"" ]; then" & LF PUT #fh, , " update-menus" & LF PUT #fh, , "fi" & LF CLOSE #fh #IFDEF __FB_UNIX__ safe_shell "chmod +x " & escape_filename(filename) #ENDIF END SUB SUB fix_deb_group_permissions(start_at_dir as string) #IFDEF __FB_UNIX__ 'This is needed because the user's umask might have given group write access to the files safe_shell "chmod -R g-w " & escape_filename(start_at_dir) #ENDIF END SUB SUB write_linux_menu_file(title as string, filename as string, basename as string) DIM fh as integer = FREEFILE OPEN filename for output as #fh PUT #fh, , "?package(" & basename & "): needs=""X11"" title=""" & title & """ command=""/usr/games/" & basename & """ section=""Games/Adventure""" & CHR(10) CLOSE #fh END SUB SUB write_linux_desktop_file(title as string, filename as string, basename as string) DIM LF as string = CHR(10) DIM fh as integer = FREEFILE OPEN filename for output as #fh PUT #fh, , "[Desktop Entry]" & LF PUT #fh, , "Name=" & title & LF PUT #fh, , "Exec=/usr/games/" & basename & LF PUT #fh, , "Terminal=false" & LF PUT #fh, , "Type=Application" & LF PUT #fh, , "Categories=Application;Game;" & LF CLOSE #fh END SUB FUNCTION dist_find_helper_app(appname as string) as string DIM path as string = find_helper_app(appname, YES) IF path = "" THEN dist_info "ERROR: " & missing_helper_message(appname & DOTEXE) RETURN path END FUNCTION FUNCTION create_ar_archive(start_in_dir as string, archive as string, files as string) as bool '--Returns YES if successful, or NO if failed ' start_in_dir only applies to the ar command. The archive filename should still be either absolute or relative to the default CURDIR 'files is a list of space-separated filenames and directory names to include in the tarball 'if they contain spaces they must be quoted DIM ar as string = dist_find_helper_app("ar") IF ar = "" THEN RETURN NO DIM args as string args = " qc" #IFNDEF __FB_DARWIN__ 'Non-mac platforms can use the D arg for deterministic mode args &= "D" #ENDIF args &= " " & escape_filename(archive) & " " & files 'debug ar & " " & args DIM spawn_ret as string DIM olddir as string = CURDIR CHDIR start_in_dir spawn_ret = spawn_and_wait(ar, args) CHDIR olddir IF LEN(spawn_ret) THEN dist_info spawn_ret : RETURN NO IF NOT isfile(archive) THEN dist_info "Could not create " & archive : RETURN NO RETURN YES END FUNCTION 'Create (replacing) zipfile, returns true on success. 'files is a list of space-separated filenames and directory names or wildcards to include in the zip 'relative to start_in_dir. If they contain spaces/etc they must be quoted FUNCTION create_zipfile(zipfile as string, start_in_dir as string, files as string) as bool DIM zip as string = dist_find_helper_app("zip") IF zip = "" THEN RETURN NO safekill zipfile DIM spawn_ret as string DIM args as string zipfile = absolute_path(zipfile) '-6 is default, -8 may be 2x slower, -9 much slower again args = "-r -8 " & escape_filename(zipfile) & " " & files DIM olddir as string = CURDIR CHDIR start_in_dir spawn_ret = spawn_and_wait(zip, args) CHDIR olddir IF LEN(spawn_ret) THEN dist_info spawn_ret : RETURN NO IF NOT isfile(zipfile) THEN dist_info "Could not create " & zipfile : RETURN NO RETURN YES END FUNCTION FUNCTION extract_zipfile (zipfile as string, into_dir as string) as bool DIM unzip as string = dist_find_helper_app("unzip") IF unzip = "" THEN RETURN NO IF NOT isdir(into_dir) THEN makedir into_dir DIM args as string = "-o " & escape_filename(zipfile) & " -d " & escape_filename(into_dir) DIM spawn_ret as string = spawn_and_wait(unzip, args) IF LEN(spawn_ret) > 0 THEN dist_info "ERROR: unzip failed: " & spawn_ret : RETURN NO RETURN YES END FUNCTION FUNCTION create_tarball(tarball as string, start_in_dir as string, files as string) as bool 'Create a .tar.gz file (requiring that extension). Returns true on success. ' start_in_dir only applies to the tar command. The tarball filename should still be either absolute or relative to the default CURDIR 'files is a list of space-separated filenames and directory names to include in the tarball 'if they contain spaces they must be quoted DIM tar as string = dist_find_helper_app("tar") IF tar = "" THEN RETURN NO DIM gzip as string = dist_find_helper_app("gzip") IF gzip = "" THEN RETURN NO DIM as string tarversion, errstr IF run_and_get_output(tar & " --version", tarversion, errstr) THEN dist_info !"ERROR: couldn't run tar\n" & errstr RETURN NO END IF DIM gnutar as bool = INSTR(tarversion, "GNU tar") 'bsdtar (frontend to libarchive) is a modern tar implementation, shipped on 'FreeBSD, NetBSD, OSX, Win10 19xx+. Its arguments are quite different from gnutar. 'DIM bsdtar as bool = INSTR(tarversion, "libarchive") 'There are also other tars, like the one used by OpenBSD, and busybox #IFDEF __FB_WIN32__ IF gnutar = NO THEN 'We need gnutar to be able to set the correct permissions (so I assume), 'see below. So download it. (Note that we look in support before PATH) debuginfo tar & " isn't gnutar: " & tarversion tar = install_windows_helper_app("tar") IF tar = "" THEN dist_info "ERROR: gnutar is not available": RETURN NO gnutar = YES END IF #ENDIF DIM more_args as string = "" IF gnutar THEN #IFNDEF __FB_WIN32__ 'These arguments are broken on Windows tar.exe for some stupid reason '("root: invalid owner". Using uid/gid 0 instead doesn't work either.) 'Instead the current username gets put in the .tar. Presumably it doesn't 'matter, since the root user would be installing a .deb anyway. more_args = " --owner=root --group=root" #ENDIF ELSE 'ELSEIF bsdtar THEN more_args = " --uname=root --gname=root" END IF #IFDEF __FB_WIN32__ IF gnutar THEN 'This is a hack to replace tar.exe's horrendous default permissions, and to (clumsily) mark the executables with the executable bit '(Both bsdtar and gnutar use modes 777 for .exe files and 666 for all others. 'Unfortunately bsdtar doesn't have a way to override the mode. more_args &= " --mode=755" 'This is a workaround for the dumbest misfeature: some versions of tar.exe '(the one in msys, but not the one we distribute) will treat the name of the tarball 'as being a remote filename if it contains a ':'... even on Windows!!!!11asjdajd more_args &= " --force-local" END IF #ENDIF DIM uncompressed as string = trimextension(tarball) 'Remove just .gz DIM args as string args = " -c " & more_args & " -f " & escape_filename(uncompressed) & " " & files DIM olddir as string = CURDIR CHDIR start_in_dir DIM spawn_ret as string spawn_ret = spawn_and_wait(tar, args) CHDIR olddir IF LEN(spawn_ret) THEN dist_info spawn_ret : RETURN NO IF gzip_file(uncompressed) = NO THEN RETURN NO RETURN YES END FUNCTION FUNCTION extract_tarball(tarball as string, into_dir as string, files as string = "") as bool '--Returns YES if successful, or NO if failed 'The tarball must already be decompressed. Don't pass in a .tar.gz (this is inconsistent 'with create_tarball, I know. ) ' into_dir only applies to the tar command. The tarball filename should still be either absolute or relative to the default CURDIR 'files is a list of space-separated files and directories to extract from the tarball, or blank for all. 'If they contain spaces they must be quoted DIM tar as string = dist_find_helper_app("tar") IF tar = "" THEN RETURN NO DIM spawn_ret as string DIM args as string args = " -x -f " & escape_filename(tarball) & " " & files DIM olddir as string = CURDIR CHDIR into_dir spawn_ret = spawn_and_wait(tar, args) CHDIR olddir IF LEN(spawn_ret) THEN dist_info spawn_ret : RETURN NO RETURN YES END FUNCTION FUNCTION gzip_file (filename as string) as bool 'Returns YES on success, NO on failure DIM gzip as string = dist_find_helper_app("gzip") IF gzip = "" THEN RETURN NO safekill filename & ".gz" DIM args as string args = escape_filename(filename) DIM spawn_ret as string spawn_ret = spawn_and_wait(gzip, args) IF LEN(spawn_ret) THEN dist_info spawn_ret : RETURN NO IF NOT isfile(filename & ".gz") THEN dist_info "ERROR: gzip completed but " & filename & ".gz was not created" RETURN NO END IF RETURN YES END FUNCTION FUNCTION gunzip_file (filename as string) as bool 'Extracts a .gz file, creating a new file minus the .gz extension next to it. 'Doesn't delete the .gz. 'Returns YES on success, NO on failure DIM gzip as string = dist_find_helper_app("gzip") IF gzip = "" THEN RETURN NO DIM args as string 'Note, the gzip.exe we ship originally shipped didn't support -k. Version 1.3.12-1 does. args = " -d -f -k " & escape_filename(filename) DIM spawn_ret as string spawn_ret = spawn_and_wait(gzip, args) IF LEN(spawn_ret) THEN dist_info spawn_ret : RETURN NO IF NOT isfile(trimextension(filename)) THEN dist_info "ERROR: gzip -d completed but " & filename & " was not uncompressed" RETURN NO END IF RETURN YES END FUNCTION SUB write_debian_binary_file (filename as string) DIM fh as integer = FREEFILE OPEN filename for binary as #fh PUT #fh, ,"2.0" & CHR(10) CLOSE #fh END SUB SUB write_debian_control_file(controlfile as string, basename as string, pkgver as string, size_in_kibibytes as integer, byref distinfo as DistribState, deb_arch as string) CONST LF as string = CHR(10) DIM author as string = distinfo.author IF author = "" THEN author = "Anonymous" DIM email as string = distinfo.email IF email = "" THEN email = "anonymous_author@no.email.specified" DIM website as string = distinfo.website IF LEN(website) > 0 THEN IF NOT starts_with(website, "http://") ANDALSO NOT starts_with(website, "https://") THEN website = "http://" & website END IF END IF DIM fh as integer = FREEFILE OPEN controlfile for output as #fh PUT #fh, , "Package: " & basename & LF PUT #fh, , "Priority: optional" & LF PUT #fh, , "Section: games" & LF PUT #fh, , "Maintainer: """ & author & """ <" & email & ">" & LF PUT #fh, , "Architecture: " & deb_arch & LF PUT #fh, , "Version: " & pkgver & LF PUT #fh, , "Installed-Size: " & size_in_kibibytes & LF 'FIXME: the Depends: line could vary depending on gfx and music backends 'This minimum libc version is taken from "scons portable=1" output (see nightly build logs) PUT #fh, , "Depends: libc6 (>= 2.14), libsdl2-mixer-2.0-0 (>= 2.0.1), libsdl2-2.0-0 (>= 2.0.5), libx11-6, libxext6, libxpm4, libxrandr2, libxrender1" & LF IF LEN(website) > 0 THEN PUT #fh, , "Homepage: " & website & LF END IF PUT #fh, , "Description: " & special_char_sanitize(distinfo.gamename) & LF IF LEN(TRIM(distinfo.description)) > 0 THEN PUT #fh, , " ." & LF PUT #fh, , " " & TRIM(exclude(distinfo.description, LF)) & LF END IF CLOSE #fh END SUB 'Much stricter version of sanitize_pkgname. 'distinfo.pkgname sanitized to contain only lowercase a-z and non-adjacent non-leading non-trailing - dashes FUNCTION get_debian_package_name() as string DIM distinfo as DistribState load_distrib_state distinfo DIM s as string s = distinfo.pkgname s = LCASE(s) s = exclude(s, "'") DIM result as string = "" DIM ch as string DIM dash as bool = NO FOR i as integer = 1 TO LEN(s) ch = MID(s, i, 1) IF ch >= "a" ANDALSO ch <= "z" THEN result &= ch dash = NO ELSE IF NOT dash ANDALSO LEN(result) > 0 ANDALSO i <> LEN(s) THEN result &= "-" dash = YES END IF END IF NEXT i RETURN result END FUNCTION FUNCTION get_debian_package_version() as string DIM d as string = DATE RETURN MID(d, 7, 4) & "." & MID(d, 1, 2) & "." & MID(d, 4, 2) '--if we wanted the hour we would add this too '& "." & MID(TIME, 1, 2) END FUNCTION FUNCTION can_run_windows_exes () as bool #IFDEF __FB_WIN32__ '--Of course we can always run exe files on Windows RETURN YES #ELSE '--Unixen and Macs can only run exe files with wine IF find_helper_app("wine") = "" THEN RETURN NO IF NOT isdir(environ("HOME") & "/.wine/dosdevices/c:") THEN RETURN NO RETURN YES #ENDIF END FUNCTION FUNCTION can_make_tarballs () as bool '--check to see if we can find the tools needed to create a .tar.gz tarball IF find_helper_app("tar") = "" THEN RETURN NO IF find_helper_app("gzip") = "" THEN RETURN NO RETURN YES END FUNCTION FUNCTION can_make_debian_packages () as bool '--check to see if we can find the tools needed to create a .deb package IF find_helper_app("ar") = "" THEN RETURN NO IF find_helper_app("tar") = "" THEN RETURN NO IF find_helper_app("gzip") = "" THEN RETURN NO RETURN YES END FUNCTION FUNCTION can_make_mac_packages () as bool '--check to see if we can find the tools needed to compress a mac .app package IF find_helper_app("tar") = "" THEN RETURN NO IF find_helper_app("gzip") = "" THEN RETURN NO RETURN YES END FUNCTION FUNCTION gather_files_for_mac (which_arch as string, basename as string, destdir as string, distinfo as DistribState) as bool DIM app as string app = add_mac_gameplayer(which_arch, basename, destdir) IF app = "" THEN RETURN NO 'dist_info "ERROR: OHRRPGCE-Game.app for " & which_arch & " is not available" : EXIT DO debuginfo "Copy rpg file" 'Renaming the .rpg file to pkgname only for consistency with all other packagers DIM resources as string resources = app & SLASH & "Contents" & SLASH & "Resources" IF copy_or_relump(sourcerpg, resources & SLASH & basename & ".rpg") = NO THEN RETURN NO write_file resources & SLASH & "bundledgame", basename DIM icns_file as string = trimextension(sourcerpg) & ".icns" IF isfile(icns_file) THEN confirmed_copy(icns_file, resources & SLASH & "game.icns") END IF RETURN gather_common_files(basename, destdir, distinfo, !"\n") END FUNCTION FUNCTION distribute_game_as_mac_app (which_arch as string, dest_override as string = "") as string IF NOT valid_arch(which_arch, "x86, x86_64") THEN RETURN "" DIM destname as string = "$pkgname" IF which_arch = "x86" THEN 'This is an obsolete arch, add a suffix destname &= "-mac-32bit.zip" ELSE destname &= "-mac.zip" END IF DIM basename as string destname = package_game(which_arch, destname, dest_override, NO, @gather_files_for_mac, basename) IF destname <> "" THEN 'Package as a .zip file. NOTE: When created on Unix, .zip files contain file permissions 'including the all-important +x bit in the "external attributes", but on Windows they don't. 'On older versions of MacOS we could rely on a quirk of the old Mac Finder, which would set 'the +x bit on every file when extracting files from a Windows/DOS .zip file. Newer Mac 'archivers do not do this. Instead we use the zip_exec tool to add the +x bit manually. 'See fix_mac_app_executable_bit_on_windows(). An alternate unused solution to actually 'create Mac .zip packages with correct file permissions from Windows, is in 'prepare_mac_app_zip() DIM warnxbit as bool = YES #IFDEF __FB_WIN32__ 'So fix the executable bit on windows. DIM execname as string = basename & ".app/Contents/MacOS/ohrrpgce-game" IF fix_mac_app_executable_bit_on_windows(destname, execname) = NO THEN dist_info "WARNING: Couldn't set the executable bit on the executable the zip file. The app likely won't run on recent macOS." warnxbit = NO END IF #ENDIF 'I have seen someone -- on Linux, even -- wipe the +x flag on ohrrpgce-game 'by unzipping and rezipping. IF warnxbit THEN dist_info "Note: Don't unzip and re-zip " & trimpath(destname) & ": that might wipe critical metadata from " _ & basename & ".app and prevent it from running!", errInfo END IF END IF RETURN destname END FUNCTION FUNCTION fix_mac_app_executable_bit_on_windows(zipfile as string, exec_path_in_zip as string) as bool 'Return YES on success, NO on failure DIM zip_exec as string = find_helper_app("zip_exec", YES) IF zip_exec = "" THEN dist_info "ERROR: zip_exec is not available": RETURN NO DIM cmd as string cmd = escape_filename(zip_exec) & " " & escape_filename(zipfile) & " " & escape_filename(exec_path_in_zip) 'debug cmd DIM cmd_stdout as string DIM res as integer = run_and_get_output(cmd, cmd_stdout) 'zip_exec itself doesn't return an exit code. It prints an error message to stdout IF res ORELSE INSTR(cmd_stdout, "error:") THEN dist_info cmd & !" failed:\n" & cmd_stdout & IIF(res, !"\nexitcode=" & res, "") RETURN NO END IF RETURN YES END FUNCTION /' This works, but isn't used. Won't be needed now that we have zip_exec. See comment above. FUNCTION prepare_mac_app_zip(zipfile as string, gamename as string) as bool '--Renames OHRRPGCE-Game.app to gamename.app inside a zip file, without extracting it DIM unzip as string = find_helper_app("unzip", YES) IF unzip = "" THEN dist_info "ERROR: unzip is not available": RETURN NO DIM ziptool as string = find_helper_app("ziptool", YES) IF ziptool = "" THEN dist_info "ERROR: ziptool is not available": RETURN NO 'First get list of files/directories DIM as string stdout_s, stderr_s IF run_and_get_output(unzip & " -Z -1 " & escape_filename(zipfile), stdout_s, stderr_s) THEN dist_info !"Couldn't examine zip file:\n" & stderr_s RETURN NO END IF DIM files() as string split stdout_s, files() 'Rename each entry DIM renamed as integer = 0 DIM args as string FOR idx as integer = 0 TO UBOUND(files) DIM newname as string = files(idx) IF replacestr(newname, "OHRRPGCE-Game.app", gamename & ".app") THEN renamed += 1 args += " rename " & idx & " " & escape_filename(newname) END IF NEXT IF renamed < 5 THEN 'Sanity check dist_info "Looks like contents of " & zipfile & " isn't right" RETURN NO END IF IF safe_shell(ziptool & " " & escape_filename(zipfile) & args) THEN dist_info "Modifying " & zipfile & " with ziptool failed" RETURN NO END IF RETURN YES END FUNCTION '/ 'Copies all the needed files for a Mac package into destdir. Returns true on success FUNCTION add_mac_gameplayer(which_arch as string, basename as string, destdir as string) as string DIM dldir as string = gameplayer_dir() IF dldir = "" THEN RETURN "" '--Decide which url to download DIM url as string DIM dlfile as string 'Until ichorescent, used to download ohrrpgce-mac-minimal-$ARCH.tar.gz (nightlies) 'or http://hamsterrepublic.com/dl/ohrrpgce-mac-minimal-$ARCH.tar.gz (stable) IF version_branch = "wip" THEN 'If using any wip release, get the latest wip release dlfile = "ohrrpgce-player-mac-wip-" & which_arch & ".tar.gz" url = "http://hamsterrepublic.com/ohrrpgce/nightly/" & dlfile ELSE 'Use this stable release dlfile = "ohrrpgce-player-mac" & version_release_tag & "-" & which_arch & ".tar.gz" url = "http://hamsterrepublic.com/ohrrpgce/archive/" & dlfile END IF DIM destgz as string = dldir & SLASH & dlfile DIM desttar as string = trimextension(destgz) '--Prompt & download if missing or out of date IF NOT download_gameplayer_if_needed(url, destgz, "mac.download.agree", "the Mac OHRRPGCE game player") THEN RETURN "" DIM game_app as string = destdir & SLASH & "OHRRPGCE-Game.app" DIM dest_app as string = destdir & SLASH & basename & ".app" 'Extract (Would be nice if we had a tar.exe that could invoke gzip) IF gunzip_file(destgz) = NO THEN RETURN "" DIM extracted as bool = extract_tarball(desttar, destdir) safekill desttar IF extracted = NO THEN RETURN "" 'Sanity checks IF sanity_check_buildinfo(destdir & SLASH & "buildinfo.ini") = NO THEN RETURN "" IF NOT isdir(game_app) THEN dist_info "ERROR: Failed to untar OHRRPGCE-Game.app" : RETURN "" IF NOT isfile(game_app & SLASH & "Contents" & SLASH & "MacOS" & SLASH & "ohrrpgce-game") THEN dist_info "ERROR: Failed to completely untar OHRRPGCE-Game.app" : RETURN "" IF renamefile(game_app, dest_app) = NO THEN dist_info "Couldn't rename OHRRPGCE-Game.app" : RETURN "" RETURN dest_app END FUNCTION 'Create an archive named fname (ending in either .zip, .tar.gz, or .exe for a Windows installer). 'build_variant: arch or buildname (varies by target) 'fname: filename (without path) to write; '$pkgname' in it is replaced with distinfo.pkgname. 'dest_override: directory to place it, or next to the .rpg if blank. 'use_subdir: whether the game should be placed in a directory in the archive named $pkgname 'gather_files: a function called to copy all files into a temp directory. 'basename: passed back for convenience FUNCTION package_game (build_variant as string, fname as string, dest_override as string = "", use_subdir as bool, gather_files as FnGatherFiles, byref basename as string = "") as string DIM distinfo as DistribState load_distrib_state distinfo basename = distinfo.pkgname DIM destname as string = trimfilename(sourcerpg) IF dest_override <> "" THEN destname = dest_override destname &= SLASH & fname replacestr destname, "$pkgname", distinfo.pkgname debuginfo "--package_game: " & destname IF dist_prompt_overwrite_file(destname) = NO THEN RETURN "" DIM pkgtmp as string = dist_make_temp_dir() IF pkgtmp = "" THEN RETURN "" DIM ret as string = "" DO DIM subdir as string IF use_subdir THEN subdir = basename IF makedir(pkgtmp & SLASH & subdir) THEN EXIT DO END IF IF gather_files(build_variant, basename, pkgtmp & SLASH & subdir, distinfo) = NO THEN EXIT DO DIM files_arg as string IF use_subdir THEN files_arg = escape_filename(subdir) ELSE files_arg = "*" IF ends_with(fname, ".tar.gz") THEN IF create_tarball(destname, pkgtmp, files_arg) = NO THEN EXIT DO ELSEIF ends_with(fname, ".zip") THEN IF create_zipfile(destname, pkgtmp, files_arg) = NO THEN EXIT DO ELSEIF ends_with(fname, ".exe") THEN IF create_win_installer(destname, pkgtmp, basename, distinfo) = NO THEN EXIT DO ELSE showbug "" EXIT DO END IF dist_info decode_filename(trimpath(destname)) & " was successfully created!", errInfo ret = destname EXIT DO LOOP '--Cleanup temp files killdir pkgtmp, YES RETURN ret END FUNCTION 'Add files common to all targets, and remove common unwanted ones FUNCTION gather_common_files(basename as string, destdir as string, distinfo as DistribState, newline as string) as bool IF NOT isfile(destdir & SLASH & "LICENSE-binary.txt") THEN dist_info "ERROR: LICENSE-binary.txt missing" : RETURN NO 'All files in the ohrrpgce-player-* archives should be distributed with games (except in .debs) except these two safekill destdir & SLASH & "README-player-only.txt" safekill destdir & SLASH & "buildinfo.ini" IF distinfo.omit_readme = NO THEN write_readme_text_file destdir & SLASH & "README-" & basename & ".txt", newline END IF IF distinfo.omit_license = NO THEN maybe_write_license_text_file destdir & SLASH & "LICENSE.txt" END IF FOR idx as integer = 0 TO UBOUND(distinfo.extra_files) 'src is relative to the .rpg, which is in the current directory DIM src as string = distinfo.extra_files(idx) DIM dest as string = destdir & SLASH & trimpath(src) DIM res as bool = NO IF isdir(src) THEN res = confirmed_copydirectory(src, dest) ELSEIF real_isfile(src) THEN res = confirmed_copy(src, dest) END IF IF res = NO THEN IF dist_yesno("Extra file/dir " & src & " is missing, unreadable or couldn't be copied. Continue anyway?", NO, NO) = NO THEN RETURN NO END IF END IF NEXT idx RETURN YES END FUNCTION 'Copies all the needed files for a windows distribution into destdir. Returns true on success. FUNCTION gather_files_for_windows (buildname as string, basename as string, destdir as string, distinfo as DistribState) as bool DIM gameplayer as string gameplayer = add_windows_gameplayer(basename, destdir) IF gameplayer = "" THEN RETURN NO IF copy_or_relump(sourcerpg, destdir & SLASH & basename & ".rpg") = NO THEN RETURN NO insert_windows_exe_icon gameplayer, trimextension(sourcerpg) & ".ico" 'Write readme with DOS/Window line endings RETURN gather_common_files(basename, destdir, distinfo, !"\r\n") END FUNCTION FUNCTION gather_files_for_steam_windows (which_arch as string, basename as string, destdir as string, distinfo as DistribState) as bool IF gather_files_for_windows(which_arch, basename, destdir, distinfo) = NO THEN RETURN NO IF add_steamworks_lib("steam_api.dll", destdir) = "" THEN RETURN NO write_file destdir & SLASH & "steam_appid.txt", STR(distinfo.steam_appid) RETURN YES END FUNCTION FUNCTION distribute_game_as_windows_zip (dest_override as string = "") as string RETURN package_game("sdl2", "$pkgname.zip", dest_override, NO, @gather_files_for_windows) END FUNCTION FUNCTION distribute_game_as_windows_installer (dest_override as string = "") as string RETURN package_game("sdl2", "$pkgname-setup.exe", dest_override, NO, @gather_files_for_windows) END FUNCTION FUNCTION distribute_game_for_steam_windows () as string RETURN package_game("sdl2", "$pkgname-steam-windows.zip", , NO, @gather_files_for_steam_windows) END FUNCTION 'Copies all the needed files for a linux tarball into destdir. Returns true on success FUNCTION gather_files_for_linux (which_arch as string, basename as string, destdir as string, distinfo as DistribState) as bool IF add_linux_gameplayer(which_arch, basename, destdir) = "" THEN RETURN NO IF copy_or_relump(sourcerpg, destdir & SLASH & basename & ".rpg") = NO THEN RETURN NO RETURN gather_common_files(basename, destdir, distinfo, !"\n") END FUNCTION FUNCTION gather_files_for_steam_linux (which_arch as string, basename as string, destdir as string, distinfo as DistribState) as bool IF gather_files_for_linux(which_arch, basename, destdir, distinfo) = NO THEN RETURN NO DIM libname as string libname = IIF(which_arch = "x86", "linux32", "linux64") & SLASH "libsteam_api.so" IF add_steamworks_lib(libname, destdir & SLASH & "linux" & SLASH & which_arch) = "" THEN RETURN NO write_file destdir & SLASH & "steam_appid.txt", STR(distinfo.steam_appid) RETURN YES END FUNCTION FUNCTION distribute_game_as_linux_tarball (which_arch as string, dest_override as string = "") as string IF NOT valid_arch(which_arch, "x86, x86_64") THEN RETURN "" 'use_subdir = YES RETURN package_game(which_arch, "$pkgname-linux-" & which_arch & ".tar.gz", dest_override, YES, @gather_files_for_linux) END FUNCTION FUNCTION distribute_game_for_steam_linux (which_arch as string) as string IF NOT valid_arch(which_arch, "x86, x86_64") THEN RETURN "" 'use_subdir = NO DIM basename as string VAR ret = package_game(which_arch, "$pkgname-steam-linux-" & which_arch & ".zip", , NO, @gather_files_for_steam_linux, basename) IF ret <> "" THEN dist_info "In Steamworks, create a Launch Option for Linux+SteamOS that runs '" & basename & "'", errInfo END IF RETURN ret END FUNCTION FUNCTION dist_yesno(capt as string, byval defaultval as bool=YES, byval escval as bool=NO) as bool IF auto_choose_default THEN RETURN defaultval RETURN yesno(capt, defaultval, escval) END FUNCTION SUB dist_info (msg as zstring ptr, errlvl as errorLevelEnum = errDebug) IF auto_choose_default = NO THEN IF errlvl = errInfo THEN errlvl = errShowInfo IF errlvl = errDebug THEN errlvl = errShowDebug END IF debugc errlvl, msg END SUB SUB auto_export_distribs (distrib_type as string) debuginfo "Auto-export: " & distrib_type auto_choose_default = YES IF distrib_type = "zip" ORELSE distrib_type = "all" THEN distribute_game_as_windows_zip END IF IF distrib_type = "win" ORELSE distrib_type = "all" THEN IF can_run_windows_exes() THEN distribute_game_as_windows_installer ELSE dist_info "auto distrib: windows installer export unavailable" END IF END IF IF distrib_type = "mac32" ORELSE distrib_type = "all" THEN IF can_make_mac_packages() THEN distribute_game_as_mac_app "x86" ELSE dist_info "auto distrib: mac 32-bit app export unavailable" END IF END IF IF distrib_type = "mac64" ORELSE distrib_type = "mac" ORELSE distrib_type = "all" THEN IF can_make_mac_packages() THEN distribute_game_as_mac_app "x86_64" ELSE dist_info "auto distrib: mac 64-bit app export unavailable" END IF END IF IF distrib_type = "debian32" ORELSE distrib_type = "all" THEN IF can_make_debian_packages() THEN distribute_game_as_debian_package "x86" ELSE dist_info "auto distrib: debian 32bit package export unavailable" END IF END IF IF distrib_type = "debian64" ORELSE distrib_type = "debian" ORELSE distrib_type = "all" THEN IF can_make_debian_packages() THEN distribute_game_as_debian_package "x86_64" ELSE dist_info "auto distrib: debian 64bit package export unavailable" END IF END IF IF distrib_type = "tarball32" ORELSE distrib_type = "all" THEN IF can_make_tarballs() THEN distribute_game_as_linux_tarball "x86" ELSE dist_info "auto distrib: linux 32bit tarball export unavailable" END IF END IF IF distrib_type = "tarball64" ORELSE distrib_type = "tarball" ORELSE distrib_type = "all" THEN IF can_make_tarballs() THEN distribute_game_as_linux_tarball "x86_64" ELSE dist_info "auto distrib: linux 64bit tarball export unavailable" END IF END IF IF distrib_type = "web" ORELSE distrib_type = "all" THEN distribute_game_as_web_zip END IF auto_choose_default = NO END SUB /' 'Ask whether to download the latest version of a file if it's been days_per_check since the last prompt FUNCTION occasionally_prompt_to_download(destfile as string, description as string) DIM last_check_key as string = "downloads.last_check." & trimpath(destfile) DIM check_interval as integer = read_config_int("downloads.days_per_check", 7) DIM last_check as double = VAL(read_config_str(last_check_key)) '0.0 if invalid DIM old_mtime as double = FILEDATETIME(destfile) last_check = large(last_check, old_mtime) IF last_check + check_interval < NOW THEN RETURN dist_yesno("You are running " & short_version & !"\n" & _ "You have a " & description & " from " & format_date(old_mtime) & !"\n" & _ "Do you want to download the latest " & description & "?", _ NO, NO) END IF RETURN NO END FUNCTION '/ 'Ask the user for permission the first time we download (subsequent updates don't ask) FUNCTION agreed_to_download(agree_file as string, description as string) as bool IF NOT isfile(agree_file) THEN IF dist_yesno("Is it okay to download " & description & " now?") = NO THEN RETURN NO touchfile agree_file END IF RETURN YES END FUNCTION 'Download a game player package, with appropriate prompts. 'For 'wip' versions, downloads a new nightly build if destfile is older than 'Custom, otherwise downloads only if missing. 'Returns true if we have destfile and it's either up-to-date or the user agreed 'to use an older copy. FUNCTION download_gameplayer_if_needed(url as string, destfile as string, agree_filename as string, description as string) as bool DIM dldir as string = settings_dir & SLASH & "_gameplayer" DIM as double old_mtime, new_mtime DIM download as bool = NO IF isfile(destfile) = NO THEN download = YES ELSEIF version_branch = "wip" THEN 'We could extract buildinfo.ini from the archive instead of using the mtime, 'but both wget and curl should set the mtime to match the original mtime on 'the server, so this should work. Just not sure about timezones... 'but since we don't include the time this should download if the archive is from yesterday. old_mtime = FILEDATETIME(destfile) 'Could also use FILEDATETIME(EXEPATH) instead of version_date DIM builddate as double = DATESERIAL(version_date \ 10000, (version_date \ 100) MOD 100, version_date MOD 100) debuginfo trimpath(destfile) & " mtime " & format_date(old_mtime) debuginfo "Custom version_date " & format_date(builddate) debuginfo CUSTOMEXE & " mtime " & format_date(FILEDATETIME(EXEPATH)) 'Always download if older than Custom IF old_mtime < builddate THEN download = YES END IF IF download THEN 'Ask for permission the first time we download IF agreed_to_download(dldir & SLASH & agree_filename, description & " from HamsterRepublic.com") = NO THEN RETURN NO IF version_branch = "wip" THEN dist_info "The latest nightly WIP version of the OHRRPGCE will be used, even if that is newer than the version you are currently using.", errInfo END IF IF download_file(url, destfile) = NO THEN IF isfile(destfile) THEN 'A previous download, which hasn't been replaced. 'This should only happen when downloading a nightly build RETURN dist_yesno(!"Downloading the latest version failed.\n" & _ "You are running " & short_version & !"\n" & _ "You still have " & description & " from " & FORMAT(old_mtime, "yyyy-mm-dd") & !" (" & version_branch & !")\n" & _ "Continue with this old version?", _ NO, NO) ELSE RETURN NO END IF END IF END IF IF isfile(destfile) THEN 'Log that we've used this file, so that we can cleanup the least recently used 'downloads (TODO: not implemented yes) which are the top-most files in the '.ini due to using shuffle_to_end=YES write_ini_prefixed_str(dldir & SLASH & "lastused.ini", trimpath(destfile), STR(NOW), YES, YES) new_mtime = FILEDATETIME(destfile) debuginfo destfile & " mtime after download " & format_date(new_mtime) RETURN YES END IF END FUNCTION FUNCTION itch_butler_setup() as bool ' Returns NO if setup failed DIM butler_path as string = find_helper_app("butler") IF butler_path = "" THEN IF NOT itch_butler_download() THEN RETURN NO butler_path = find_helper_app("butler") IF butler_path = "" THEN RETURN NO 'Shouldn't happen END IF 'If we got this far, butler should be installed. Try to log in IF NOT itch_butler_is_logged_in() THEN dist_info "The itch.io butler tool will try to launch your web browser to log in. When finished, you can come back to this window." DIM spawn_ret as string = spawn_and_wait(butler_path, "login") IF LEN(spawn_ret) > 0 THEN dist_info "ERROR: butler login command reported failure: " & spawn_ret : RETURN NO dist_info "After you are finished with the web browser, come back here..." END IF RETURN YES END FUNCTION FUNCTION itch_butler_platform_version() as string DIM prefix as string DIM suffix as string #IFNDEF __FB_X86__ dist_info "Unfortunately, butler is only available for x86 and x86_64." RETURN "" #ENDIF #IFDEF __FB_WIN32__ prefix = "windows" #ELSEIF DEFINED(__FB_DARWIN__) prefix = "darwin" #IFNDEF __FB_64BIT__ IF dist_yesno("No butler download for 32-bit Mac is available. Download the 64-bit version instead?") = NO THEN RETURN "" #ENDIF #ELSEIF DEFINED(__GNU_LINUX__) prefix = "linux" #ELSE dist_info "Unfortunately, butler is only available for Windows, Mac and GNU/Linux." RETURN "" #ENDIF #IF defined(__FB_64BIT__) or defined(__FB_DARWIN__) suffix = "amd64" #ELSE suffix = "386" #ENDIF RETURN prefix & "-" & suffix END FUNCTION 'Somewhat similar to install_windows_helper_app FUNCTION itch_butler_download() as bool DIM support_dir as string = get_support_dir() DIM butler_path as string = support_dir & SLASH & "butler" DOTEXE DIM butler_platform as string = itch_butler_platform_version() IF butler_platform = "" THEN RETURN NO 'Not available '--Ask the user for permission the first time we download (subsequent updates don't ask) DIM agree_file as string = support_dir & SLASH & "itch.butler.download.agree" IF agreed_to_download(agree_file, "the itch.io butler tool") = NO THEN RETURN NO DIM destzip as string = support_dir & SLASH & "butler.zip" '--Actually download the dang file DIM url as string = "https://broth.itch.ovh/butler/" & butler_platform & "/LATEST/archive/default" IF NOT download_file(url, destzip) THEN dist_info "ERROR: Failed to download itch.io butler.zip" : RETURN NO END IF '-- remove the old copy (if it exists) safekill butler_path IF extract_zipfile(destzip, support_dir) = NO THEN RETURN NO IF NOT isfile(butler_path) THEN dist_info "ERROR: Didn't find itch.io butler tool in the downloaded zip" : RETURN NO RETURN YES END FUNCTION FUNCTION itch_game_url(distinfo as DistribState) as string RETURN "https://" & sanitize_url_chunk(distinfo.itch_user) & ".itch.io/" & itch_gametarg(distinfo) END FUNCTION FUNCTION itch_target(distinfo as DistribState) as string RETURN sanitize_url_chunk(distinfo.itch_user) & "/" & itch_gametarg(distinfo) END FUNCTION FUNCTION itch_gametarg(distinfo as DistribState) as string RETURN sanitize_url_chunk(IIF(LEN(distinfo.itch_gamename) = 0, distinfo.pkgname, distinfo.itch_gamename)) END FUNCTION FUNCTION itch_butler_is_logged_in() as bool ' Can't be logged in if it ain't installed IF find_helper_app("butler") = "" THEN RETURN NO DIM butler_creds as string #IFDEF __FB_WIN32__ butler_creds = ENVIRON("USERPROFILE") & "\.config\itch\butler_creds" #ELSEIF DEFINED(__FB_DARWIN__) butler_creds = ENVIRON("HOME") & "/Library/Application Support/itch/butler_creds" #ELSE butler_creds = ENVIRON("HOME") & "/.config/itch/butler_creds" #ENDIF 'Note this creds file can be years old and still works. TODO: What if you want 'to switch user? RETURN isfile(butler_creds) END FUNCTION FUNCTION itch_butler_error_check(out_s as string, err_s as string) as bool 'Returns YES if there is an error 'Generic error checking for butler calls 'Special error checking may also be required separately DIM ret as bool = NO IF LEN(err_s) > 0 THEN dist_info !"butler status error:\n" & err_s ret = YES END IF IF INSTR(LCASE(out_s), " api error ") THEN dist_info !"butler api error\n" & out_s ret = YES END IF RETURN ret END FUNCTION SUB itch_butler_upload(distinfo as DistribState) debuginfo "itch_butler_upload" dist_basicstatus "Checking butler status..." 'When this function is called we've already checked butler is present and logged in DIM butler as string = find_helper_app("butler") BUG_IF(butler = "", "butler missing") DIM target as string = itch_target(distinfo) DIM out_s as string DIM err_s as string run_and_get_output butler & " status " & target, out_s, err_s IF INSTR(LCASE(out_s), "invalid game") THEN dist_info "The target game " & target & " doesn't exist yet. Create a new game named """ & itch_gametarg(distinfo) & """ on your itch.io account. That is where you can add screenshots and a description. Then come back here and try again." EXIT SUB END IF IF itch_butler_error_check(out_s, err_s) THEN EXIT SUB IF dist_yesno("This will replace the older version of this game at " & itch_game_url(distinfo) & ", are you sure you want to continue?", NO) = NO THEN EXIT SUB 'Needs to be on the same filesystem as other other dist_make_temp_dir directories DIM itch_temp_dir as string = dist_make_temp_dir("itch_io_exports") IF itch_temp_dir = "" THEN EXIT SUB debuginfo "Exporting files to " & itch_temp_dir auto_choose_default = YES dist_basicstatus "Exporting for Windows..." DIM win_zip as string = distribute_game_as_windows_zip(itch_temp_dir) IF win_zip = "" THEN dist_info "Aborting itch.io upload" : EXIT SUB dist_basicstatus "Exporting for Mac..." DIM mac_app as string = distribute_game_as_mac_app("x86_64", itch_temp_dir) IF mac_app = "" THEN dist_info "Aborting itch.io upload" : EXIT SUB dist_basicstatus "Exporting for Linux..." DIM linux_tarball as string = distribute_game_as_linux_tarball("x86_64", itch_temp_dir) IF linux_tarball = "" THEN dist_info "Aborting itch.io upload" : EXIT SUB DIM web_zip as string IF distinfo.itch_upload_web THEN dist_basicstatus "Exporting for Web..." web_zip = distribute_game_as_web_zip(itch_temp_dir) IF web_zip = "" THEN dist_info "Aborting itch.io upload" : EXIT SUB END IF auto_choose_default = NO debuginfo "Upload exports with butler..." dim did_which as string = "" dist_basicstatus "Upload " & itch_gametarg(distinfo) & " (Windows) ..." run_and_get_output butler & " push " & escape_filename(win_zip) & " " & target & ":windows", out_s, err_s IF itch_butler_error_check(out_s, err_s) THEN EXIT SUB did_which &= "Windows" dist_basicstatus "Upload " & itch_gametarg(distinfo) & " (Mac) ..." run_and_get_output butler & " push " & escape_filename(mac_app) & " " & target & ":mac", out_s, err_s IF itch_butler_error_check(out_s, err_s) THEN EXIT SUB did_which &= ", Mac" dist_basicstatus "Upload " & itch_gametarg(distinfo) & " (Linux) ..." run_and_get_output butler & " push " & escape_filename(linux_tarball) & " " & target & ":linux", out_s, err_s IF itch_butler_error_check(out_s, err_s) THEN EXIT SUB did_which &= ", Linux" IF distinfo.itch_upload_web THEN dist_basicstatus "Upload " & itch_gametarg(distinfo) & " (Web) ..." run_and_get_output butler & " push " & escape_filename(web_zip) & " " & target & ":web", out_s, err_s IF itch_butler_error_check(out_s, err_s) THEN EXIT SUB did_which &= ", Web" END IF dist_basicstatus "Cleaning up temp files..." killdir itch_temp_dir dist_info "Upload of " & itch_gametarg(distinfo) & " to " & itch_game_url(distinfo) & " finished (" & did_which & ")" END SUB CONST steamworks_version = 155 FUNCTION steamworks_zip() as string RETURN get_support_dir(NO) & SLASH & "steamworks_v" & steamworks_version & ".zip" END FUNCTION SUB download_steamworks() DIM dlfile as string = steamworks_zip() DIM fname as string = trimpath(dlfile) DIM download_url as string = "http://HamsterRepublic.com/ohrrpgce/support/" & fname IF yesno("Download the redistributable libraries for Steam from " & url_hostname(download_url) & "?") THEN IF NOT download_file(download_url, dlfile) THEN visible_debug "Unable to download " & download_url END IF END IF END SUB 'Extract a library from the steamworks zip into destdir. 'libname: the filename and its parent directory, inside the steamworks .zip FUNCTION add_steamworks_lib(libname as string, destdir as string) as string DIM outfile as string = destdir & SLASH & trimpath(libname) DIM unzip as string = dist_find_helper_app("unzip") IF unzip = "" THEN RETURN "" DIM arglist as string ' -q quiet -o overwrite -j junk directories arglist = " -qoj " & escape_filename(steamworks_zip()) & " " & escape_filename("sdk/redistributable_bin/" & libname) & " -d " & escape_filename(destdir) DIM spawn_ret as string spawn_ret = spawn_and_wait(unzip, arglist) IF NOT isfile(outfile) THEN visible_debug "Unable to unzip " & libname & " from " & steamworks_zip() RETURN "" END IF RETURN outfile END FUNCTION FUNCTION distribute_game_as_web_zip (dest_override as string = "") as string RETURN package_game("", "$pkgname-web.zip", dest_override, NO, @gather_files_for_web) END FUNCTION 'Copies all the needed files for a web distribution into destdir. Returns true on success. FUNCTION gather_files_for_web (buildname as string, basename as string, destdir as string, distinfo as DistribState) as bool DIM gameplayer as string gameplayer = add_web_gameplayer(basename, destdir) IF gameplayer = "" THEN RETURN NO IF copy_or_relump(sourcerpg, join_path(destdir, basename & ".rpg")) = NO THEN RETURN NO DIM js_file as string = join_path(destdir, "ohrrpgce-game.js") DIM data_file as string = join_path(destdir, "ohrrpgce-game.data") DIM output_dir as string = join_path(destdir, "data") IF NOT extract_web_data_files(js_file, data_file, output_dir) THEN dist_info "ERROR: Failed to extract web data files" : RETURN NO END IF IF NOT web_data_cleanup(basename, destdir) THEN dist_info "ERROR: Failed to clean up web data files" : RETURN NO END IF IF NOT recreate_web_data_file(js_file, data_file, output_dir) THEN dist_info "ERROR: Failed to recreate web data file" : RETURN NO END IF killdir output_dir RETURN gather_common_files(basename, destdir, distinfo, !"\n") END FUNCTION FUNCTION extract_web_data_files(js_file as string, data_file as string, output_dir as string) as bool 'js-file is the minified javascript file to search for the loadPackage metadata ' it may fail if this is not minified, but that is okay, it is always minified :) ' 'data_file is the package of data files to extract ' 'output_dir will be created, and all the data files are dumped in there, preserving directory structure IF NOT isdir(output_dir) THEN IF makedir(output_dir) THEN dist_info "ERROR: Failed to create output dir " & output_dir : RETURN NO END IF END IF IF NOT diriswriteable(output_dir) THEN dist_info "ERROR: Unable to write to output dir " & output_dir : RETURN NO END IF DIM js_text as string = string_from_file(js_file) DIM success as bool DIM loadPackage_text as string = extract_string_chunk(js_text, "loadPackage(", ")", success) IF NOT success THEN dist_info "ERROR: Failed to extract loadPackage() from " & js_file : RETURN NO END IF DIM remains as string = loadPackage_text DIM fh_in as integer DIM fh_out as integer DIM filename as string DIM out_filename as string DIM start_str as string DIM start_pos as integer DIM end_str as string DIM end_pos as integer DIM found as bool DIM foundat as integer DIM foundcount as integer = 0 DIM parsefail as bool = NO DIM buflen as integer REDIM buf(0) as ubyte fh_in = FREEFILE OPEN data_file FOR BINARY ACCESS READ as #fh_in debug "Parsing web data files..." DO filename = extract_string_chunk(remains, """filename"":""", """", found, foundat) IF NOT found THEN EXIT DO foundcount += 1 remains = MID(remains, foundat + len(filename)) start_str = "?" end_str = "?" start_str = extract_string_chunk(remains, """start"":", ",", found, foundat) IF NOT found THEN parsefail = YES : EXIT DO IF parse_int(start_str, @start_pos, YES) = NO THEN parsefail = YES : EXIT DO remains = MID(remains, foundat + len(start_str)) end_str = extract_string_chunk(remains, """end"":", "}", found, foundat) IF NOT found THEN parsefail = YES : EXIT DO IF parse_int(end_str, @end_pos, YES) = NO THEN parsefail = YES : EXIT DO remains = MID(remains, foundat + len(end_str)) debuginfo "Found data filename=" & filename & " from " & start_str & " to " & end_str out_filename = join_path(output_dir, trim_path_root(filename)) makedir_recurse trimfilename(out_filename) fh_out = FREEFILE OPEN out_filename FOR BINARY ACCESS WRITE as #fh_out buflen = end_pos - start_pos REDIM buf(buflen - 1) as ubyte GET #fh_in, 1 + start_pos, buf() PUT #fh_out, 1, buf() CLOSE #fh_out debuginfo "Wrote data file to " & out_filename LOOP CLOSE #fh_in IF parsefail THEN dist_info "ERROR: Failed to parse offsets for data file " & filename & " (""" & start_str & """,""" & end_str & """) from " & js_file : RETURN NO END IF IF foundcount = 0 THEN dist_info "ERROR: Failed to find any filenames in loadPackage() " & js_file : RETURN NO END IF RETURN YES END FUNCTION FUNCTION recreate_web_data_file(js_file as string, data_file as string, from_dir as string) as bool 'js-file is the minified javascript file to insert the new loadPackage metadata 'data_file is the package of data files to recreate. The old existing file will be deleted. 'from_dir contains the files and folders to add to the data package. It isn't deleted afterwards. The caller is responsible for that. 'Returns true for success REDIM filelist(-1 TO -1) as string recursefiles from_dir, ALLFILES, YES, filelist() DIM howmany AS integer = UBOUND(filelist) DIM size(howmany) as integer DIM cur_offset as integer = 0 DIM offset(howmany) as integer DIM pkgpath(howmany) as string DIM fh_in as integer DIM fh_out as integer REDIM buf(0) as ubyte 'Remove the old data file killfile data_file 'Open the new data file for writing fh_out = FREEFILE OPEN data_file FOR BINARY ACCESS WRITE as #fh_out 'Build a list of files and loop through it FOR i as integer = 0 TO UBOUND(filelist) pkgpath(i) = filelist(i) replacestr(pkgpath(i), trim_trailing_slashes(from_dir), "", 1) size(i) = FILELEN(filelist(i)) offset(i) = cur_offset cur_offset += size(i) debuginfo "Repackaging web data file " & filelist(i) & " -> " & pkgpath(i) & " (size=" & size(i) & ", offset=" & offset(i) & ")" 'Now read the file and write it to the data package fh_in = FREEFILE OPEN filelist(i) FOR BINARY ACCESS READ as #fh_in REDIM buf(size(i)) as ubyte GET #fh_in, 1, buf() PUT #fh_out, 1 + offset(i), buf() CLOSE #fh_in NEXT i CLOSE #fh_out 'Now we generate the json metadata DIM metadata as string = "{""files"":[" FOR i as integer = 0 TO UBOUND(filelist) IF i <> 0 THEN metadata &= "," metadata &= "{""filename"":""" & escape_string(path_with_only_forward_slashes(pkgpath(i)), """") & """," metadata &= """start"":" & offset(i) & "," metadata &= """end"":" & offset(i) + size(i) & "}" NEXT i metadata &= "],""remote_package_size"":" & FILELEN(data_file) & "}" 'Now insert the metadata into the javascript file DIM js_text as string = string_from_file(js_file) DIM replace_ok as bool js_text = replace_string_chunk(js_text, "loadPackage(", ")", metadata, replace_ok) IF NOT replace_ok THEN dist_info "ERROR: Failed to to update loadPackage() metadata in " & js_file : RETURN NO END IF string_to_file(js_text, js_file) RETURN YES END FUNCTION FUNCTION add_web_gameplayer(basename as string, destdir as string) as string 'Download a precompiled web player package, 'extract it to destdir (creating it but not clearing if already existing), 'and return the full path to index.html therein. ' 'Returns "" for failure. DIM destexe as string = destdir & SLASH & "index.html" DIM dldir as string = gameplayer_dir() IF dldir = "" THEN RETURN "" '--Decide which url to download DIM url as string DIM dlfile as string IF version_branch = "wip" THEN 'If using any wip release, get the latest wip release dlfile = "ohrrpgce-player-web-wip.zip" url = "http://hamsterrepublic.com/ohrrpgce/nightly/" & dlfile ELSE 'Use this stable release dlfile = "ohrrpgce-player-web" & version_release_tag & ".zip" url = "http://hamsterrepublic.com/ohrrpgce/archive/" & dlfile END IF DIM destzip as string = dldir & SLASH & dlfile '--Prompt & download if missing or out of date IF NOT download_gameplayer_if_needed(url, destzip, "web.download.agree", "the Web OHRRPGCE game player") THEN RETURN "" IF extract_zipfile(destzip, destdir) = NO THEN RETURN "" 'Sanity check contents IF sanity_check_buildinfo(destdir & SLASH & "buildinfo.ini") = NO THEN RETURN "" IF NOT isfile(destdir & SLASH & "ohrrpgce-game.html") THEN dist_info "ERROR: Failed to unzip ohrrpgce-game.html" : RETURN "" IF NOT isfile(destdir & SLASH & "ohrrpgce-game.js") THEN dist_info "ERROR: Failed to unzip ohrrpgce-game.js" : RETURN "" IF renamefile(destdir & SLASH & "ohrrpgce-game.html", destexe) = NO THEN dist_info "ERROR: Couldn't rename ohrrpgce-game.html" : RETURN "" RETURN destexe END FUNCTION FUNCTION web_data_cleanup(basename as string, output_dir as string) as bool 'Assumes we have already unpacked the web data files into output_dir/data/* 'Deletes unwanted files, and moves RPG files into the correct place 'Returns true on success, false on failure DIM data_dir as string = join_path(output_dir, "data") IF NOT isdir(data_dir) THEN dist_info "ERROR: Could not find temp data dir " & data_dir : RETURN NO END IF 'Remove sample games folder killdir join_path(data_dir, "games"), YES 'Move the RPG file(s) DIM src_rpg as string DIM dest_rpg as string REDIM rpgfiles(0) as string findfiles output_dir, "*.rpg", , , rpgfiles() IF UBOUND(rpgfiles) = -1 THEN dist_info "ERROR: Could not find " & data_dir & SLASH & "*.rpg" : RETURN NO END IF FOR i as integer = 0 TO UBOUND(rpgfiles) src_rpg = join_path(output_dir, rpgfiles(i)) dest_rpg = join_path(data_dir, LCASE(rpgfiles(i))) debuginfo "Move " & src_rpg & " -> " & dest_rpg IF renamefile(src_rpg, dest_rpg) = NO THEN dist_info "ERROR: Could not move " & src_rpg & " to " & dest_rpg : RETURN NO END IF NEXT i 'Create the ohrrpgce_arguments.txt file DIM rpg_name as string = LCASE(basename & ".rpg") string_to_file rpg_name, join_path(data_dir, "ohrrpgce_arguments.txt") 'Delete the midi soundfont if it is unused IF game_uses_midi_or_bam() THEN debuginfo "This game uses midi or bam, keep the soundfont" ELSE debuginfo "This game uses midi or bam, discard the soundfont" killdir join_path(data_dir, "soundfonts"), YES killdir join_path(data_dir, "etc"), YES END IF RETURN YES END FUNCTION