diff --git a/ports/sonic3air/port.json b/ports/sonic3air/port.json index 8dac7ff9d5..bab2103222 100644 --- a/ports/sonic3air/port.json +++ b/ports/sonic3air/port.json @@ -16,7 +16,7 @@ "inst": "Copy a rom of Sonic 3 & Knuckles to ports/sonic3air and rename it to `Sonic_Knuckles_wSonic3.bin`.", "inst_md": null, "genres": [ - "platform" + "platformer" ], "image": null, "rtr": false, diff --git a/ports/utyellow/README.md b/ports/utyellow/README.md index 79b233fe9f..412462862a 100644 --- a/ports/utyellow/README.md +++ b/ports/utyellow/README.md @@ -1,5 +1,5 @@ ## Installation -Download the [Windows](https://gamejolt.com/games/UndertaleYellow/136925) or [Android](https://gamejolt.com/games/utyandroidp-yftxzc/866335) game from gamejolt and add the contents to `ports/utyellow/assets` (if windows) or directly to `ports/utyellow` (if android). +Download [Undertale Yellow for Windows](https://gamejolt.com/games/UndertaleYellow/136925) game from gamejolt and add the assets (mus, snd folders, data.win etc) to `ports/utyellow/assets`. Game is ready to play after a brief first-time setup. ## Default Gameplay Controls @@ -15,4 +15,5 @@ Game is ready to play after a brief first-time setup. ## Thanks TeamUndertaleYellow -- The game JohnnyOnFlame -- GMLoaderNext +Cyril aka kotzebuedog -- GMTools audio patcher Testers and Devs from the PortMaster Discord \ No newline at end of file diff --git a/ports/utyellow/Undertale Yellow.sh b/ports/utyellow/Undertale Yellow.sh index 76aa635c4d..62bda2357e 100644 --- a/ports/utyellow/Undertale Yellow.sh +++ b/ports/utyellow/Undertale Yellow.sh @@ -13,90 +13,51 @@ else fi source $controlfolder/control.txt -source $controlfolder/device_info.txt [ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" get_controls -# Setup permissions -$ESUDO chmod 666 /dev/tty1 -$ESUDO chmod 666 /dev/uinput -echo "Loading, please wait... (might take a while!)" > /dev/tty0 - # Variables GAMEDIR="/$directory/ports/utyellow" -CUR_TTY="/dev/tty0" - -# Set current virtual screen -if [ "$CFW_NAME" == "muOS" ]; then - /opt/muos/extra/muxlog & CUR_TTY="/tmp/muxlog_info" -else - CUR_TTY="/dev/tty0" -fi +# CD and set permissions cd $GAMEDIR > "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1 - -$ESUDO chmod 777 "$GAMEDIR/gmloadernext" +$ESUDO chmod +x -R $GAMEDIR/* # Exports -export LD_LIBRARY_PATH="$GAMEDIR/libs:$LD_LIBRARY_PATH" +export LD_LIBRARY_PATH="/usr/lib:$GAMEDIR/lib:$GAMEDIR/libs:$LD_LIBRARY_PATH" +export PATCHER_FILE="$GAMEDIR/tools/patchscript" +export PATCHER_GAME="$(basename "${0%.*}")" # This gets the current script filename without the extension +export PATCHER_TIME="2 to 5 minutes" +export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig" -# Run the installer file if it hasn't been run yet -install-apk() { - if [ ! -f "$GAMEDIR/installed" ]; then - echo "Performing first-run setup..." > $CUR_TTY - PATCHFILE="patch-droid.xdelta" - # Extract the APK, replace game.droid, and repack - mkdir -p "$GAMEDIR/assets" - ./utils/unzip "yellow.apk" -d "$GAMEDIR/assets/" - mv "$GAMEDIR/assets/assets/game.droid" "$GAMEDIR/game.droid" - apply_patch - mv "$GAMEDIR/game.droid" "$GAMEDIR/assets/assets/game.droid" - ./utils/zip -r -0 "yellow.apk" "assets" - rm -rf "$GAMEDIR/assets" - rm -rf game.apk - mv yellow.apk game.apk - touch "$GAMEDIR/installed" +# dos2unix in case we need it +dos2unix "$GAMEDIR/tools/gmKtool.py" +dos2unix "$GAMEDIR/tools/Klib/GMblob.py" +dos2unix "$GAMEDIR/tools/patchscript" + +# Check if patchlog.txt to skip patching +if [ ! -f patchlog.txt ]; then + if [ -f "$controlfolder/utils/patcher.txt" ]; then + source "$controlfolder/utils/patcher.txt" + $ESUDO kill -9 $(pidof gptokeyb) + else + echo "This port requires the latest version of PortMaster." > $CUR_TTY fi -} -install-win() { - if [ ! -f "$GAMEDIR/installed" ]; then - echo "Performing first-run setup..." > $CUR_TTY - PATCHFILE="patch-win.xdelta" - # Purge unneeded files - rm -rf assets/*.ini assets/*.exe - # Rename data.win - echo "Moving the game file..." > $CUR_TTY - mv "./assets/data.win" "./game.droid" - # Create a new zip file game.apk from specified directories - echo "Zipping assets into apk..." > $CUR_TTY - ./utils/zip -r -0 "game.apk" "assets" - rm -rf "$GAMEDIR/assets" - apply_patch - fi -} -apply_patch() { - if [ -f "$PATCHFILE" ]; then - echo "Applying patch..." > $CUR_TTY - $controlfolder/xdelta3 -d -s "$GAMEDIR/game.droid" "$GAMEDIR/$PATCHFILE" "$GAMEDIR/game2.droid" - rm -rf game.droid - rm -rf *.xdelta - mv game2.droid game.droid - fi -} - -if [ -f "$GAMEDIR/yellow.apk" ]; then - install-apk else - install-win + echo "Patching process already completed. Skipping." +fi + +# Display loading splash +if [ -f "$GAMEDIR/patchlog.txt" ]; then + $ESUDO ./libs/splash "splash.png" 1 + $ESUDO ./libs/splash "splash.png" 3000 fi # Assign gptokeyb and load the game -$GPTOKEYB "gmloadernext" -c "control.gptk" & -export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig" -./gmloadernext game.apk +$GPTOKEYB "gmloadernext.aarch64" -c "yellow.gptk" & +pm_platform_helper "gmloadernext.aarch64" +./gmloadernext.aarch64 -c gmloader.json -# Kill processes -$ESUDO kill -9 $(pidof gptokeyb) -$ESUDO systemctl restart oga_events & -printf "\033c" > /dev/tty0 +# Cleanup +pm_finish diff --git a/ports/utyellow/utyellow/gmloader.json b/ports/utyellow/utyellow/gmloader.json new file mode 100644 index 0000000000..1c4dca00ea --- /dev/null +++ b/ports/utyellow/utyellow/gmloader.json @@ -0,0 +1,7 @@ +{ + "save_dir" : "saves", + "apk_path" : "utyellow.port", + "show_cursor" : false, + "disable_controller" : false, + "force_platform" : "os_windows" +} \ No newline at end of file diff --git a/ports/utyellow/utyellow/gmloadernext b/ports/utyellow/utyellow/gmloadernext deleted file mode 100644 index 0215c1ac5a..0000000000 Binary files a/ports/utyellow/utyellow/gmloadernext and /dev/null differ diff --git a/ports/utyellow/utyellow/gmloadernext.aarch64 b/ports/utyellow/utyellow/gmloadernext.aarch64 new file mode 100644 index 0000000000..5742f8d7b9 Binary files /dev/null and b/ports/utyellow/utyellow/gmloadernext.aarch64 differ diff --git a/ports/utyellow/utyellow/libs/splash b/ports/utyellow/utyellow/libs/splash new file mode 100644 index 0000000000..faee12ec05 Binary files /dev/null and b/ports/utyellow/utyellow/libs/splash differ diff --git a/ports/utyellow/utyellow/patch-droid.xdelta b/ports/utyellow/utyellow/patch-droid.xdelta deleted file mode 100644 index 9ad31878cc..0000000000 Binary files a/ports/utyellow/utyellow/patch-droid.xdelta and /dev/null differ diff --git a/ports/utyellow/utyellow/patch-win.xdelta b/ports/utyellow/utyellow/patch-win.xdelta deleted file mode 100644 index bd48693769..0000000000 Binary files a/ports/utyellow/utyellow/patch-win.xdelta and /dev/null differ diff --git a/ports/utyellow/utyellow/tools/Klib/GMblob.py b/ports/utyellow/utyellow/tools/Klib/GMblob.py new file mode 100644 index 0000000000..3714c651e3 --- /dev/null +++ b/ports/utyellow/utyellow/tools/Klib/GMblob.py @@ -0,0 +1,656 @@ + +from pathlib import Path +import os +from subprocess import Popen, PIPE, DEVNULL +from struct import pack,unpack +from time import sleep +import sys + +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.buffered = False + self.chunk_list = None + + self.__init_chunk_list() + + def _vprint(self, msg): + if self.verbose > 0: + print(msg) + if not self.buffered: + sys.stdout.flush() + + def _vvprint(self, msg): + if self.verbose > 1: + print(msg) + if not self.buffered: + sys.stdout.flush() + + def _vvvprint(self, msg): + if self.verbose > 2: + print(msg) + if not self.buffered: + sys.stdout.flush() + + 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 and self.audo[key]["source"] == "infile": + self._vvprint(f"[AGRP {self.audiogroup_id}] Compress AUDO entry {key}") + + self.fileout.write(pack(' 0 and self.audo[key]["source"] == "txtp": + self._vvprint(f"[AGRP {self.audiogroup_id}] Compress TXTP external sound {key}") + + self.fileout.write(pack(' 1: + # audio is already compressed, we need to uncompress it before can compress it + oggdec_process = Popen(["oggdec", "-Q", "-o", "-", "-"], stdin=PIPE, stdout=PIPE, stderr=DEVNULL) + wavdata, _ = oggdec_process.communicate(self.filein.read(self.audo[audo_entry]["size"])) + oggenc_process.communicate(wavdata) + oggdec_process.terminate() + + else: + oggenc_process.communicate(self.filein.read(self.audo[audo_entry]["size"])) + + oggenc_process.terminate() + + return self.fileout.tell() - offset_start + + def get_audo(self): + return self.audo + + def no_write(self, no_write): + if self.audiogroup_id in no_write: + self.no_write = True + self._vprint(f"[AGRP {self.audiogroup_id}] NO_WRITE") + else: + self._vprint("[AGRP {self.audiogroup_id}] ONLY_WRITE") + + + # also no_write audiogroupN.dat files + for _,key in enumerate(self.audiogroup_dat.keys()): + self.audiogroup_dat[key].no_write(no_write) + + def only_write(self, only_write): + if not self.audiogroup_id in only_write: + self.no_write = True + self._vprint(f"[AGRP {self.audiogroup_id}] NO_WRITE") + else: + self._vprint(f"[AGRP {self.audiogroup_id}] ONLY_WRITE") + + # also only_write audiogroupN.dat files + for _,key in enumerate(self.audiogroup_dat.keys()): + self.audiogroup_dat[key].only_write(only_write) + + 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 import_sound_txtp(self, filetxtp_path, compress=0 ): + last = len(self.audo) + self.audo[f"{last:#04}"] = { "offset": -1, "size": -1, "compress": compress, "source": "txtp", "txtp": filetxtp_path } + self.chunk_list["FORM"]["rebuild"] = 1 + self.chunk_list["AUDO"]["rebuild"] = 1 + + def write_changes(self, OUT_DIR): + if self.no_write: + self._vprint(f"No write set for AGRP {self.audiogroup_id}: Will not write {self.filein_path.name}") + else: + 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(' 1: + self.set_gm_version(GMdata.GM_2024_6) + + elif len(offset_table) == 1: + self.filein.seek(offset_table[0] + 32) + if unpack(' 0: + self.set_gm_version(GMdata.GM_2024_6) + + self.sond = {} + + for i,offset in enumerate(offset_table): + self.filein.seek(offset) + + name_offset = unpack('> 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, + "audiolength": audiolength, + "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): + + if self.gm_version == GMdata.GM_2024_6: + 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 + + if self.gm_version == GMdata.GM_2024_6: + sond_entry_size = 40 + else: + sond_entry_size = 36 + + 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(sond_entry_size)) # same entry (36 / 40B) + else: + self._vvvprint(f"Rebuild SOND entry {key}") + self.filein.seek(sond_entry_size,1) # we jump this chunk on the input file (36 / 40B) + + self.fileout.write(self.__sond_get_raw_entry(key)) + + self.fileout_size += sond_entry_size + + padding = self._get_padding(16) + + self.fileout.write(b'\x00' * padding ) + self.fileout_size += padding + + def get_sond(self): + return self.sond + + def set_gm_version(self, version): + self.gm_version = version + if self.gm_version == GMdata.GM_2024_6: + self._vprint("GM 2024.6 detected") + + def audio_enable_compress(self ,minsize, recompress=False): + + # 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) + size = self._audo_get_size(audiogroup_id, audiofile) + + if ( audiogroup_id in self.audiogroup_filter or len(self.audiogroup_filter) == 0) and size >= minsize: + if self.sond[sond_key]["flags"]["isCompressed"] == 0: + self.__sond_set_compress(sond_key) + 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") + + elif recompress and size >= minsize: + self._audo_set_recompress(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 recompressed") + + if self.get_total_updated_entries() > 0: + # toggle rebuild because we will update data + self._vprint(f"{self.get_total_updated_entries()} audo entrie(s) will be compressed") + + def write_changes(self, OUT_DIR): + if self.no_write: + self._vprint(f"No write set for AGRP {self.audiogroup_id}: Will not write {self.filein_path.name}") + else: + 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(' 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 +""" +import argparse +from pathlib import Path +from Klib.GMblob import GMdata + +MIN_SIZE = 1024*1024 # 1 MB + +def main(): + + + parser = argparse.ArgumentParser(description='GameMaker K-dog tool: compress wav to ogg, recompress ogg, in Gamemaker data files') + parser.add_argument('-v','--verbose', action='count', default=0, help='Verbose level (cumulative option)') + parser.add_argument('-m','--minsize', default=MIN_SIZE, type=int, help='Minimum WAV/OGG size in bytes to target (default 1MB)') + parser.add_argument('-a','--audiogroup', nargs='?',action='append',type=int, help='Audiogroup ID to process (option can repeat). By default any.') + parser.add_argument('-b','--bitrate', default=0, help='nominal bitrate (in kbps) to encode at (oggenc -b option). 0 for auto (default)') + parser.add_argument('-r', '--recompress', default=False, action='store_true', help='Allow ogg recompression') + parser.add_argument('-y', '--yes', default=False, action='store_true', help='Overwrite the files if already present without asking (DANGEROUS, use with caution)') + parser.add_argument('-d','--destdirpath', default="./Ktool.out",help='Destination directory path (default ./Ktool.out)') + + parser.add_argument('infilepath', help='Input file path (eg: data.win)') + + args = parser.parse_args() + + if args.audiogroup: + audiogroup_filter = args.audiogroup + else: + audiogroup_filter = [] + + INFILE_PATH=Path(args.infilepath) + OUT_DIR=Path(args.destdirpath) + + if not INFILE_PATH.exists(): + print(f"{INFILE_PATH} not found") + exit(1) + + if OUT_DIR.exists(): + if OUT_DIR.is_dir(): + if any(OUT_DIR.iterdir()) and not args.yes: + answer=input(f"{OUT_DIR} already exists and contains file. Do you want to continue? (y/n)") + if not answer in 'yY': + exit(0) + else: + print(f"{OUT_DIR} is not a directory") + exit(1) + else: + OUT_DIR.mkdir() + + myiffdata = GMdata(INFILE_PATH, args.verbose, args.bitrate, audiogroup_filter) + myiffdata.audio_enable_compress(args.minsize,args.recompress) + myiffdata.write_changes(OUT_DIR) + + exit(0) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ports/utyellow/utyellow/tools/oggdec b/ports/utyellow/utyellow/tools/oggdec new file mode 100644 index 0000000000..dbce910242 Binary files /dev/null and b/ports/utyellow/utyellow/tools/oggdec differ diff --git a/ports/utyellow/utyellow/tools/oggenc b/ports/utyellow/utyellow/tools/oggenc new file mode 100644 index 0000000000..910b20d636 Binary files /dev/null and b/ports/utyellow/utyellow/tools/oggenc differ diff --git a/ports/utyellow/utyellow/tools/patches/utyellow.xdelta b/ports/utyellow/utyellow/tools/patches/utyellow.xdelta new file mode 100644 index 0000000000..85be23088a Binary files /dev/null and b/ports/utyellow/utyellow/tools/patches/utyellow.xdelta differ diff --git a/ports/utyellow/utyellow/tools/patchscript b/ports/utyellow/utyellow/tools/patchscript new file mode 100644 index 0000000000..15b4c087e4 --- /dev/null +++ b/ports/utyellow/utyellow/tools/patchscript @@ -0,0 +1,96 @@ +#!/bin/bash +# Set GAMEDIR to the current directory and set logfile +GAMEDIR="$PWD" +LOGFILE="$GAMEDIR/patchlog.txt" + +# Redirect output and error to the log file +exec > >(tee -a "$LOGFILE") 2>&1 +echo "GAMEDIR is set to: $GAMEDIR" + +# Exports +export DATADIR="$GAMEDIR/assets" +export LD_LIBRARY_PATH="/usr/lib:$GAMEDIR/lib:$GAMEDIR/tools/libs:$LD_LIBRARY_PATH" +export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig" +export TOOLDIR="$GAMEDIR/tools" +export TMPDIR="$GAMEDIR/tmp" +export PATH="$GAMEDIR/tools:$PATH" + +# Permissions +chmod 666 /dev/uinput +chmod 777 "$TOOLDIR/gmKtool.py" +chmod 777 "$TOOLDIR/oggenc" + +cd "$GAMEDIR" + +apply_xdelta() { + # Check if the data.win file exists and apply xdelta + if [ -f "$DATADIR/data.win" ]; then + echo "Applying xdelta patch" + output=$(xdelta3 -d -s "$DATADIR/data.win" -f "./tools/patches/utyellow.xdelta" "$DATADIR/game.droid" 2>&1) + if [ $? -eq 0 ]; then + echo "Patch applied successfully" + echo "$output" + rm "$DATADIR/data.win" + else + echo "Failed to apply patch" + echo "$output" + exit 1 + fi + else + echo "No data.win file found!" + exit 1 + fi +} + +compress_audio() { + # Compress audio + echo "Compressing audio..." + sleep 3 + mkdir -p "$TMPDIR" + + # Run the compression tool + PYTHON=$(which python3) + $PYTHON ./tools/gmKtool.py -vv -m 1000 -r -b 64 -d "$TMPDIR" "$DATADIR/game.droid" + + if [ $? -eq 0 ]; then + echo "Compression completed successfully." + mv "$TMPDIR/"* "$DATADIR" + echo "Moved compressed files to $DATADIR." + rmdir "$TMPDIR" + echo "Temporary directory $TMPDIR removed." + echo "Audio compression applied successfully." + else + echo "Audio compression failed." + echo "Cleaning up temporary directory $TMPDIR." + rm -rf "$TMPDIR" + fi +} + +zip_apk() { + # Zip data files into apk + zip -r -0 ./utyellow.port ./assets/ + if [ $? -eq 0 ]; then + echo "Packed data files into utyellow.port." + rm -rf ./assets + else + echo "Failed to pack data files." + exit 1 + fi +} + +process_game() { + # Do some prep + rm -rf $DATADIR/*.exe $DATADIR/.gitkeep + mv $DATADIR/splash.png . + apply_xdelta + compress_audio + sleep 1 + zip_apk + sleep 1 + + # Final completion message + echo "Patching process complete!" +} + +# Call the function +process_game diff --git a/ports/utyellow/utyellow/tools/xdelta3 b/ports/utyellow/utyellow/tools/xdelta3 new file mode 100644 index 0000000000..a5325ce8a5 Binary files /dev/null and b/ports/utyellow/utyellow/tools/xdelta3 differ diff --git a/ports/utyellow/utyellow/utils/lib/liblzma.so.5 b/ports/utyellow/utyellow/utils/lib/liblzma.so.5 deleted file mode 100644 index 210f040d33..0000000000 Binary files a/ports/utyellow/utyellow/utils/lib/liblzma.so.5 and /dev/null differ diff --git a/ports/utyellow/utyellow/utils/unzip b/ports/utyellow/utyellow/utils/unzip deleted file mode 100644 index 56670f21fe..0000000000 Binary files a/ports/utyellow/utyellow/utils/unzip and /dev/null differ diff --git a/ports/utyellow/utyellow/utils/xdelta3 b/ports/utyellow/utyellow/utils/xdelta3 deleted file mode 100644 index 813f90458b..0000000000 Binary files a/ports/utyellow/utyellow/utils/xdelta3 and /dev/null differ diff --git a/ports/utyellow/utyellow/utils/zip b/ports/utyellow/utyellow/utils/zip deleted file mode 100644 index 16b6007efd..0000000000 Binary files a/ports/utyellow/utyellow/utils/zip and /dev/null differ diff --git a/ports/utyellow/utyellow/game.apk b/ports/utyellow/utyellow/utyellow.port similarity index 100% rename from ports/utyellow/utyellow/game.apk rename to ports/utyellow/utyellow/utyellow.port diff --git a/ports/utyellow/utyellow/control.gptk b/ports/utyellow/utyellow/yellow.gptk similarity index 100% rename from ports/utyellow/utyellow/control.gptk rename to ports/utyellow/utyellow/yellow.gptk