diff --git a/ports/discroom/Disc Room.sh b/ports/discroom/Disc Room.sh new file mode 100644 index 0000000000..30ad0ea92d --- /dev/null +++ b/ports/discroom/Disc Room.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} + +if [ -d "/opt/system/Tools/PortMaster/" ]; then + controlfolder="/opt/system/Tools/PortMaster" +elif [ -d "/opt/tools/PortMaster/" ]; then + controlfolder="/opt/tools/PortMaster" +elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then + controlfolder="$XDG_DATA_HOME/PortMaster" +else + controlfolder="/roms/ports/PortMaster" +fi + +source $controlfolder/control.txt +export PORT_32BIT="Y" + +get_controls +[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" + +GAMEDIR="/$directory/ports/discroom" + +# Exports +export LD_LIBRARY_PATH="/usr/lib32:$GAMEDIR/libs:$LD_LIBRARY_PATH" +export GMLOADER_DEPTH_DISABLE=0 +export GMLOADER_SAVEDIR="$GAMEDIR/gamedata/" +export GMLOADER_PLATFORM="os_windows" +export TOOLDIR="$GAMEDIR/tools" +export PATH=$PATH:$TOOLDIR +export PATCHER_FILE="$GAMEDIR/patch/patchscript" +export PATCHER_GAME="Disc Room" +export PATCHER_TIME="3 to 5 minutes" +export PATCHDIR=$GAMEDIR + +# We log the execution of the script into log.txt +> "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1 + +# Permissions +$ESUDO chmod +x "$GAMEDIR/gmloader" +$ESUDO chmod +x "$TOOLDIR/splash" + +cd "$GAMEDIR" + +# Run install if needed +if [ ! -f "$GAMEDIR/gamedata/game.droid" ]; then + if [ -f "$controlfolder/utils/patcher.txt" ]; then + source "$controlfolder/utils/patcher.txt" + $ESUDO kill -9 $(pidof gptokeyb) + else + pm_message "This port requires the latest version of PortMaster." + fi +else + pm_message "Patching process already completed. Skipping." +fi + +if [ ! -f "$GAMEDIR/gamedata/config.ini" ]; then + mv "$GAMEDIR/config.ini.default" "$GAMEDIR/gamedata/config.ini" +fi + +$GPTOKEYB "gmloader" & + +pm_platform_helper "$GAMEDIR/gmloader" +./gmloader game.apk + +pm_finish \ No newline at end of file diff --git a/ports/discroom/README.md b/ports/discroom/README.md new file mode 100644 index 0000000000..f2cfdb15a7 --- /dev/null +++ b/ports/discroom/README.md @@ -0,0 +1,18 @@ +## Notes + +Special thanks to [Terri](http://terriv.games/), [Dose](https://www.doseone.xyz/), [Kitty](https://x.com/kittycalis), and [JW](https://www.jwaaaap.com/) for making this awesome game! + +Source: https://store.steampowered.com/app/1229580/Disc_Room/ + +## Controls + +| Button | Action | +|--|--| +|D-pad / L-stick|Movement| +|A|Confirm / Ability| +|B |Back| +|Start|Pause / Menu| +|LB|Menu Left / FFW| +|RB|Menu Right / FFW| + + diff --git a/ports/discroom/cover.png b/ports/discroom/cover.png new file mode 100644 index 0000000000..11b7537920 Binary files /dev/null and b/ports/discroom/cover.png differ diff --git a/ports/discroom/discroom/compress.txt b/ports/discroom/discroom/compress.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ports/discroom/discroom/game.apk b/ports/discroom/discroom/game.apk new file mode 100644 index 0000000000..458420789d Binary files /dev/null and b/ports/discroom/discroom/game.apk differ diff --git a/ports/discroom/discroom/gamedata/place game files here b/ports/discroom/discroom/gamedata/place game files here new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ports/discroom/discroom/gamedata/splash.png b/ports/discroom/discroom/gamedata/splash.png new file mode 100644 index 0000000000..dce1e2acd5 Binary files /dev/null and b/ports/discroom/discroom/gamedata/splash.png differ diff --git a/ports/discroom/discroom/gmloader b/ports/discroom/discroom/gmloader new file mode 100644 index 0000000000..168fc6ec09 Binary files /dev/null and b/ports/discroom/discroom/gmloader differ diff --git a/ports/discroom/discroom/libc++_shared.so b/ports/discroom/discroom/libc++_shared.so new file mode 100644 index 0000000000..fb45ef9052 Binary files /dev/null and b/ports/discroom/discroom/libc++_shared.so differ diff --git a/ports/discroom/discroom/libs/libcrypto.so.1.1 b/ports/discroom/discroom/libs/libcrypto.so.1.1 new file mode 100644 index 0000000000..12578440aa Binary files /dev/null and b/ports/discroom/discroom/libs/libcrypto.so.1.1 differ diff --git a/ports/discroom/discroom/libs/libssl.so.1.1 b/ports/discroom/discroom/libs/libssl.so.1.1 new file mode 100644 index 0000000000..f94835c02f Binary files /dev/null and b/ports/discroom/discroom/libs/libssl.so.1.1 differ diff --git a/ports/discroom/discroom/libs/libzip.so.4 b/ports/discroom/discroom/libs/libzip.so.4 new file mode 100644 index 0000000000..bc3ef2300d Binary files /dev/null and b/ports/discroom/discroom/libs/libzip.so.4 differ diff --git a/ports/discroom/discroom/patch/discroomgog.xdelta b/ports/discroom/discroom/patch/discroomgog.xdelta new file mode 100644 index 0000000000..10f6b54c3b Binary files /dev/null and b/ports/discroom/discroom/patch/discroomgog.xdelta differ diff --git a/ports/discroom/discroom/patch/discroomsteam.xdelta b/ports/discroom/discroom/patch/discroomsteam.xdelta new file mode 100644 index 0000000000..e7d802f6a1 Binary files /dev/null and b/ports/discroom/discroom/patch/discroomsteam.xdelta differ diff --git a/ports/discroom/discroom/patch/patchscript b/ports/discroom/discroom/patch/patchscript new file mode 100644 index 0000000000..f27cfebe1a --- /dev/null +++ b/ports/discroom/discroom/patch/patchscript @@ -0,0 +1,111 @@ +#!/bin/bash + +# Set the log file +LOGFILE="$PATCHDIR/patchlog.txt" + +# Redirect output and error to the log file +> "$LOGFILE" && exec > >(tee "$LOGFILE") 2>&1 + +echo "PATCHDIR is set to: $PATCHDIR" + +# Exports +export TOOLDIR="$PATCHDIR/tools" +export TMPDIR="$PATCHDIR/tmp" +export PATH=$PATH:TOOLDIR +export LD_LIBRARY_PATH="/usr/lib:$PATCHDIR/libs:$TOOLDIR/libs:$LD_LIBRARY_PATH" + +# Permissions +chmod 777 "$TOOLDIR/gmKtool.py" +chmod 777 "$TOOLDIR/oggdec" +chmod 777 "$TOOLDIR/oggenc" +chmod 777 "$TOOLDIR/xdelta3" + +cd "$PATCHDIR" + +process_game() { + # Array of files and patterns to delete + files_to_delete=( + "./gamedata/*.exe" + "./gamedata/*.dll" + "./gamedata/*.ico" + "./gamedata/gog*" + "./gamedata/unins000*" +) + + # Loop through patterns and delete matching files + for pattern in "${files_to_delete[@]}"; do + # Expand the pattern into matching files + matches=$(find ./gamedata/ -path "$pattern" -type f 2>/dev/null) + + if [[ -z "$matches" ]]; then + echo "No files found for pattern: $pattern" + else + for file in $matches; do + rm "$file" + echo "Deleted $file" + done + fi +done + + # If "gamedata/data.win" exists and matches the checksum of the GOG or Steam versions + if [ -f "./gamedata/data.win" ]; then + checksum=$(md5sum "./gamedata/data.win" | awk '{print $1}') + + # Checksum for the GOG version + if [ "$checksum" = "2194950ef1e18110653065efcb940244" ]; then + $ESUDO tools/xdelta3 -d -s gamedata/data.win -f ./patch/discroomgog.xdelta gamedata/game.droid && \ + rm gamedata/data.win + echo "GOG data.win has been patched" + # Checksum for the Steam version + elif [ "$checksum" = "eb904bb033d7674894384151bd33a5be" ]; then + $ESUDO tools/xdelta3 -d -s gamedata/data.win -f ./patch/discroomsteam.xdelta gamedata/game.droid && \ + rm gamedata/data.win + echo "Steam data.win has been patched" + else + echo "Error: MD5 checksum of data.win does not match any expected version." + fi + else + echo "Error: Missing data.win in gamedata folder or game has been patched." + fi + + # Compress audio + if [ -f "$PATCHDIR/compress.txt" ]; then + echo "Compressing audio. The process will take 5-10 minutes" > $CUR_TTY + mkdir -p "$TMPDIR" + ./tools/gmKtool.py -vv -m 262144 -b 64 -d "$TMPDIR" "$PATCHDIR/gamedata/game.droid" + + if [ $? -eq 0 ]; then + mv $TMPDIR/* "$PATCHDIR/gamedata" + rm "$PATCHDIR/compress.txt" + rmdir "$TMPDIR" + echo "Audio compression applied successfully." > $CUR_TTY + else + echo "Audio compression failed." > $CUR_TTY + rm -rf "$TMPDIR" + fi + fi + + sleep 3 + + # Check for .ogg files and move to APK + if [ -n "$(ls ./gamedata/*.ogg 2>/dev/null)" ]; then + mkdir -p ./assets + mv ./gamedata/*.ogg ./assets/ + echo "Moved .ogg files to ./assets/" + + zip -r -0 ./game.apk ./assets/ + echo "Zipped contents to ./game.apk" + + rm -rf ./assets + echo "Deleted assets directory" + else + echo "No .ogg files found" + fi + + #Delete "Place game files here" and echo patching complete + rm -f "./gamedata/place game files here" + echo "Patching is complete!" +} + +# Call the function +process_game diff --git a/ports/discroom/discroom/tools/gmKtool.py b/ports/discroom/discroom/tools/gmKtool.py new file mode 100644 index 0000000000..d30ebae2fb --- /dev/null +++ b/ports/discroom/discroom/tools/gmKtool.py @@ -0,0 +1,602 @@ +#!/usr/bin/env python3 + +""" + name: K-dog tool + description: compress wav data into ogg data in Gamemaker data.win files + author: kotzebuedog + usage: ./gm-Ktool.py data.win -d ./repacked -a 0 -a 1 -m 524288 + Will compress all wav data > 512 KB in audiogroup 0 (data.win) and 1 (audiogroup1.dat) + The updated files will be written in ./repacked + -d, -a and -m are optionnal +""" + +from pathlib import Path +import os +from subprocess import Popen, PIPE +import threading + +import argparse + +from struct import pack,unpack + +MIN_SIZE = 1024*1024 # 1 MB + +class IFFdata: + + def __init__(self, fin_path, verbose=0): + self.filein_path = Path(fin_path) + self.filein = None + self.filein_size = 0 # includes FORM (4B) and size (4B) + + self.fileout_path = None + self.fileout = None + self.fileout_size = 0 # includes FORM (4B) and size (4B) + + self.verbose = verbose + self.chunk_list = None + + self.__init_chunk_list() + + def _vprint(self, msg): + if self.verbose > 0: + print(msg) + + def _vvprint(self, msg): + if self.verbose > 1: + print(msg) + + def _vvvprint(self, msg): + if self.verbose > 2: + print(msg) + + def _pretty_size(self,size): + + units = ['B ','KB','MB','GB'] + + n = size + + while n > 1024: + n = n / 1024 + units = units[1:] + + return f"{int(n):#4} {units[0]}" + + def _open_filein(self): + + try: + self.filein = open(self.filein_path,'rb') + self.filein.seek(0, os.SEEK_END) + self.filein_size = self.filein.tell() + self.filein.seek(0) + + except (FileNotFoundError, PermissionError, OSError, IOError): + self._vprint(f"Error opening file {self.filein_path}") + exit(1) + + def _open_fileout(self): + + try: + self.fileout = open(self.fileout_path,'wb') + self.filout_size = 0 + + except (FileNotFoundError, PermissionError, OSError, IOError): + self._vprint("Error opening file") + exit(1) + + def __find_next_chunk(self): + # Save chunk begin offset + offset = self.filein.tell() + + # Read chunk token + token = self.filein.read(4).decode('ascii') + + # Read chunk size (this doesn't include token (4B) and size (4B)) + size = unpack(' 0: + padding = alignement - misalignement + + return padding + + def _write_to_file_otherchunk(self,token): + self.filein.seek(self.chunk_list[token]["offset"]) + size = self.chunk_list[token]["size"] + self._vvprint(f"Writing {token}") + + if self.chunk_list[token]["rebuild"] == 0: + self._vvprint("Direct copy") + + self.fileout.write(self.filein.read(size + 8)) # We copy also token (4B) and size(4B) + self.fileout_size += size + 8 + + else: + self._vvprint("Rebuild needed") + self.fileout.write(token.encode('ascii')) + self.fileout.write(pack(' 0): + process.stdin.write(self.filein.read(remainder_size)) # Write remainder bytes of data bytes to stdin pipe of Oggenc sub-process. + + process.stdin.close() # Close stdin pipe - closing stdin finish encoding the data, and closes Oggenc sub-process. + + def _write_to_file_audo_ogg(self, audo_entry): + chunksize = 0 + oggenc_process = ( + Popen(["oggenc","-b",f"{self.bitrate}","-"],bufsize=1024,stdin=PIPE, stdout=PIPE, stderr=PIPE ) + ) + + thread = threading.Thread(target=self.__thread_writer, args=(oggenc_process,audo_entry)) + thread.start() + + while thread.is_alive(): + ogg_chunk = oggenc_process.stdout.read(1024) # Read chunk with arbitrary size from stdout pipe + chunksize += len(ogg_chunk) + self.fileout.write(ogg_chunk) # Write the encoded chunk to the "in-memory file". + + + # Read the last encoded chunk. + ogg_chunk = oggenc_process.stdout.read() # Read chunk with arbitrary size from stdout pipe + self.fileout.write(ogg_chunk) # Write the encoded chunk to the "in-memory file". + chunksize += len(ogg_chunk) + + oggenc_process.wait() # Wait for oggenc sub-process to end + + return chunksize + + def get_audo(self): + return self.audo + + def audo_get_entry(self,n,filein_path): + with open(filein_path, 'wb') as fout: + self.filein.seek(self.audo[n]["offset"] + 4) + fout.write(self.filein.read(self.audo[n]["size"])) + +class GMaudiogroup(GMIFFDdata): + + def __init__(self, fin_path, verbose, bitrate, audiogroup_id): + super().__init__(fin_path, verbose, bitrate, audiogroup_id) + + def write_changes(self, OUT_DIR): + self._vprint(f"Writing {self.filein_path.name}") + self.fileout_path = OUT_DIR / self.filein_path.name + self._open_fileout() + + if self.chunk_list["FORM"]["rebuild"] == 1: + + for _,token in enumerate(self.chunk_list): + if token == "AUDO": + self._write_to_file_audo() + else: + self._write_to_file_otherchunk(token) + + self.fileout.seek(4) + self.fileout.write(pack('> 6, + "isCompressed" : (flags_raw & 0x02) >> 1, + "isEmbedeed" : flags_raw & 0x01 } + + sondkey = f"{i:#04}" + self.sond[sondkey] = { + "name_offset": name_offset, + "name" : name, + "flags_raw" : flags_raw, + "flags" : flags, + "type_offset": type_offset, + "type" : type, + "file_offset": file_offset, + "file" : file, + "effect" : effect, + "volume" : volume, + "pitch" : pitch, + "audiogroup" : audiogroup, + "audiofile" : audiofile, + "rebuild" : 0 + } + self._vvvprint(f"SOND entry {i:#04}: {self.sond[sondkey]}") + + self.__init_audiogroup_dat(audiogroup) + + def __init_audiogroup_dat(self, audiogroup): + if audiogroup > 0 and not f"{audiogroup}" in self.audiogroup_dat.keys(): + self.audiogroup_dat[f"{audiogroup}"] = GMaudiogroup(self.filein_path.parents[0] / f"audiogroup{audiogroup}.dat" , self.verbose, self.bitrate, audiogroup) + + def __sond_get_raw_entry(self,key): + + return pack(' compressed) + self.sond[sond_key]["flags"]["isCompressed"] = 1 + self.sond[sond_key]["flags"]["isEmbedded"] = 0 + self.__sond_update_flags_raw(sond_key) + + # toggle rebuild and compress because we will update data + self.sond[sond_key]["rebuild"] = 1 + + def __write_to_file_sond(self): + self.filein.seek(self.chunk_list["SOND"]["offset"]) + size = self.chunk_list["SOND"]["size"] + self._vvprint("Writing SOND") + + if self.chunk_list["SOND"]["rebuild"] == 0: + self._vvprint("Direct copy SOND") + self.fileout.write(self.filein.read(size + 8)) + self.fileout_size += size + 8 + + else: + self._vvprint("Rebuild SOND") + self.fileout.write(self.filein.read(12)) # Token, size, nb entries should be the same + self.fileout_size += 12 + self.fileout.write(self.filein.read(len(self.sond.keys()) * 4)) # offsets don't change + self.fileout_size += len(self.sond.keys()) * 4 + + for n, key in enumerate(self.sond.keys()): + if self.sond[key]["rebuild"] == 0: + self._vvvprint(f"Direct copy SOND entry {key}") + # We copy the entry from the input file + self.fileout.write(self.filein.read(36)) # same entry (36B) + + else: + self._vvvprint(f"Rebuild SOND entry {key}") + self.filein.seek(36,1) # we jump this chunk on the input file (36B) + self.fileout.write(self.__sond_get_raw_entry(key)) + + self.fileout_size += 36 + + padding = self._get_padding(16) + + self.fileout.write(b'\x00' * padding ) + self.fileout_size += padding + + def get_sond(self): + return self.sond + + def audio_enable_compress(self ,minsize): + + # Iter each entry in SOND + for _,sond_key in enumerate(self.sond): + audiogroup_id = self.sond[sond_key]["audiogroup"] # it is an audiogroup ID (eg 0 or 1) + audiofile_id = self.sond[sond_key]['audiofile'] + + if audiofile_id == 0xffffffff: + # AUDO entry doesn't exist + continue + + audiofile = f"{audiofile_id:#04}" # it is a file number (eg 0001) + + if ( audiogroup_id in self.audiogroup_filter or len(self.audiogroup_filter) == 0) and self.sond[sond_key]["flags"]["isCompressed"] == 0 : + size = self._audo_get_size(audiogroup_id, audiofile) + + if size >= minsize: + self.__sond_set_compress(sond_key,audiofile) + self._audo_set_compress(audiogroup_id, audiofile) + + self._vvprint(f"audo {audiofile} in audiogroup {audiogroup_id} ({self.sond[sond_key]['name']}) with size {self._pretty_size(size)} will be compressed") + + + if self.get_total_updated_entries() > 0: + # toggle rebuild because we will update data + self._vprint(f"{self.get_total_updated_entries()} wav entrie(s) will be compressed") + + def write_changes(self, OUT_DIR): + self._vprint(f"Writing {self.filein_path.name}") + + self.fileout_path = OUT_DIR / self.filein_path.name + self._open_fileout() + + if self.chunk_list["FORM"]["rebuild"] == 1: + + for _,token in enumerate(self.chunk_list): + if token == "SOND": + self.__write_to_file_sond() + elif token == "AUDO": + self._write_to_file_audo() + else: + self._write_to_file_otherchunk(token) + + self.fileout.seek(4) + self.fileout.write(pack(' + + + ./Disc Room.sh + Disc Room + Are you ready to get sliced in half? The year is 2089 and a giant disc has appeared in orbit of Jupiter. Step into the oversized space suit of a brave scientist and explore this sprawling intergalactic slaughterhouse. +Explore the expansive orbiting disc ship, moving room to room surviving and completing increasingly complex goals set forth. Use unlocked abilities to outmaneuver dozens of different disc types while solving mysterious puzzles. +And remember: what kills you only makes you stronger. + + 20201022T000000 + Terri, Dose, Kitty, JW + Devolver Digital + Action + ./discroom/cover.png + + \ No newline at end of file diff --git a/ports/discroom/port.json b/ports/discroom/port.json new file mode 100644 index 0000000000..17005726ce --- /dev/null +++ b/ports/discroom/port.json @@ -0,0 +1,31 @@ +{ + "version": 3, + "name": "discroom.zip", + "items": [ + "Disc Room.sh", + "discroom" + ], + "items_opt": [], + "attr": { + "title": "Disc Room", + "porter": [ + "Fraxinus88" + ], + "desc": "Are you ready to get sliced in half? The year is 2089 and a giant disc has appeared in orbit of Jupiter. Step into the oversized space suit of a brave scientist and explore this sprawling intergalactic slaughterhouse.\nExplore the expansive orbiting disc ship, moving room to room surviving and completing increasingly complex goals set forth. Use unlocked abilities to outmaneuver dozens of different disc types while solving mysterious puzzles.\nAnd remember: what kills you only makes you stronger.", + "desc_md": "Are you ready to get sliced in half? The year is 2089 and a giant disc has appeared in orbit of Jupiter. Step into the oversized space suit of a brave scientist and explore this sprawling intergalactic slaughterhouse.\nExplore the expansive orbiting disc ship, moving room to room surviving and completing increasingly complex goals set forth. Use unlocked abilities to outmaneuver dozens of different disc types while solving mysterious puzzles.\nAnd remember: what kills you only makes you stronger.\n\n**Note: Currently only works with the Steam/GOG Version of the game!**", + "inst": "Purchase the game from steam or gog.\nDownload the game and add the game data to discroom/assets.", + "inst_md": "Purchase the game from [steam](https://store.steampowered.com/app/1229580/Disc_Room/) or [gog](https://www.gog.com/en/game/disc_room).\nDownload the windows version of the game and add the game data to discroom/assets.", + "genres": [ + "action" + ], + "image": null, + "rtr": false, + "exp": false, + "runtime": null, + "reqs": [], + "arch": [ + "armhf" + ], + "min_glibc": "" + } +} \ No newline at end of file diff --git a/ports/discroom/screenshot.png b/ports/discroom/screenshot.png new file mode 100644 index 0000000000..ea618e3014 Binary files /dev/null and b/ports/discroom/screenshot.png differ