diff --git a/main.py b/main.py new file mode 100644 index 0000000..c764cf8 --- /dev/null +++ b/main.py @@ -0,0 +1,200 @@ +import os +import shutil +import tkinter +from tkinter import messagebox, filedialog, font +from tkinter.ttk import Progressbar, Button +from tooltip import CreateToolTip +from functools import partial + +runningpath = os.path.dirname(__file__) +appdata = os.environ['LOCALAPPDATA'] + +if os.path.isdir(f'{appdata}\\Roblox'): + print('Roblox directory in AppData installed: continuing.') +else: + tkinter.messagebox.showerror("Roblox directory in AppData directory does not seem to exist. Program halted.") + exit() + +if os.path.isdir(f'{appdata}\\Roblox\\Versions'): + print('Roblox versions directory seem to exist: checking if they actually have crap in them.') + rblxversions = [f.path for f in os.scandir(f'{appdata}\\Roblox\\Versions') if f.is_dir()] + if not rblxversions: + tkinter.messagebox.showerror("There seem to be no currently installed Roblox versions, " + "therefore making it very unlikely any Roblox versions are currently installed. " + "Program halted.") + exit() +else: + tkinter.messagebox.showerror("Roblox versions directory doesn't seem to exist, " + "therefore making it very unlikely any Roblox versions are currently installed. " + "Program halted.") + +for x in rblxversions: + if os.path.isfile(f'{x}\\RobloxPlayerBeta.exe'): + print(f"I found the Roblox engine player directory! {x}") + rblxclient = x + elif os.path.isfile(f'{x}\\RobloxStudioBeta.exe'): + print(f"I found the Roblox engine studio directory! {x}") + rblxstudio = x + +try: + rblxclient +except NameError: + tkinter.messagebox.showerror("I couldn't find any Roblox versions that contain the Roblox engine player. Program halted.") + +rblxsounds = f'{rblxclient}\\content\\sounds' +if not os.path.isdir(rblxsounds): + tkinter.messagebox.showerror("Umm... That's odd, I couldn't find the directory where the sounds are stored.. Program halted, I guess?") + +# Whoo! We made it to the sound files! +expected_sounds = { + "uuhhh": f'{rblxsounds}\\uuhhh.mp3', + "snap": f'{rblxsounds}\\snap.mp3', + "impact_water": f'{rblxsounds}\\impact_water.mp3', + "impact_explosion_03": f'{rblxsounds}\\impact_explosion_03.mp3', + "action_swim": f'{rblxsounds}\\action_swim.mp3', + "action_jump_land": f'{rblxsounds}\\action_jump_land.mp3', + "action_jump": f'{rblxsounds}\\action_jump.mp3', + "action_get_up": f'{rblxsounds}\\action_get_up.mp3', + "action_footsteps_plastic": f'{rblxsounds}\\action_footsteps_plastic.mp3', + "action_falling": f'{rblxsounds}\\action_falling.mp3' +} +expected_backups = { + "uuhhh": f'{rblxsounds}\\uuhhh.mp3.bak', + "snap": f'{rblxsounds}\\snap.mp3.bak', + "impact_water": f'{rblxsounds}\\impact_water.mp3.bak', + "impact_explosion_03": f'{rblxsounds}\\impact_explosion_03.mp3.bak', + "action_swim": f'{rblxsounds}\\action_swim.mp3.bak', + "action_jump_land": f'{rblxsounds}\\action_jump_land.mp3.bak', + "action_jump": f'{rblxsounds}\\action_jump.mp3.bak', + "action_get_up": f'{rblxsounds}\\action_get_up.mp3.bak', + "action_footsteps_plastic": f'{rblxsounds}\\action_footsteps_plastic.mp3.bak', + "action_falling": f'{rblxsounds}\\action_falling.mp3.bak' +} +sounds = {} +replacable_sounds = {} + +for key, value in expected_sounds.items(): + if os.path.isfile(value): + sounds[key] = value + +GuiWindow = tkinter.Tk() +GuiWindow.title('Roblox Sound Replacer') +GuiWindow.resizable(False, False) + +fontlist = tkinter.font.families() +if "Segoe UI" in fontlist: + fontuse = 'Segoe UI' + print("Using Segoe UI font, as it is installed.") +else: + fontuse = 'Arial' + print("Using Arial font, as Segoe UI is not installed.") +labelRow = 0 + +sounds_info = { + "uuhhh": "The Roblox death sound file.", + "snap": "i dont think this sound is used ingame", + "impact_water": "The sound used for landing in water.", + "impact_explosion_03": "This is an explosion sound. It *might* be used in a few games.", + "action_swim": "The sound used for swimming.", + "action_jump_land": "The sound used for landing.", + "action_jump": "The sound used for jumping.", + "action_get_up": "The (rarely used) sound used when the character gets up.", + "action_footsteps_plastic": "The sound used for walking on Plastic material.", + "action_falling": "The sound used for falling.", + "bass": "i dont know and its also not an mp3 file so im not gonna touch that" +} +sounds_textboxes = {} +sounds_browsers = {} + + +def browsefiles(keyparam): + filename = filedialog.askopenfilename(initialdir=runningpath, + title="Select a File", + filetypes=(("*.mp3 files", + "*.mp3*"), + ("all files", + "*.*"))) + sounds_textboxes[keyparam].insert(0, filename) + + +for key, value in sounds.items(): + labelRow = labelRow + 1 + currentLabel = tkinter.Label(GuiWindow, text=key, font=(fontuse, 12)) + currentLabel.grid(column=1, row=labelRow) + CreateToolTip(currentLabel, sounds_info[key]) + labelRow = labelRow + 1 + currentTextbox = tkinter.ttk.Entry(GuiWindow) + sounds_textboxes[key] = currentTextbox + currentTextbox.grid(column=1, row=labelRow, sticky='ew') + labelRow = labelRow + 1 + button_explore = Button(GuiWindow, + text="Browse files", + command=partial(browsefiles, key)) + button_explore.grid(column=1, row=labelRow, sticky='ew') + labelRow = labelRow + 1 + tkinter.ttk.Separator(GuiWindow).grid(column=1, row=labelRow, sticky='ew', pady=5) + +for key, value in expected_sounds.items(): + if os.path.isfile(f'{runningpath}\\{key}.mp3'): + replacable_sounds[key] = f'{runningpath}\\{key}.mp3' + try: + sounds_textboxes[key].insert(0, f'{runningpath}\\{key}.mp3') + except KeyError: + pass + + +def soundreplace(): + for xkey, y in sounds_textboxes.items(): + currentvaluetext = y.get() + if not currentvaluetext == '': + replacable_sounds[xkey] = currentvaluetext + GuiButton['state'] = 'disabled' + progressleap = 100 / len(replacable_sounds.values()) + for xkey, y in replacable_sounds.items(): + if xkey in sounds: + shutil.move(sounds[xkey], f'{sounds[xkey]}.bak') + try: + shutil.copyfile(y, sounds[xkey]) + except FileNotFoundError: + tkinter.messagebox.showerror('File not found', + f'File {y} is missing and therefore could not be copied. ' + f'Action cancelled.') + GuiButton['state'] = 'normal' + progress['value'] = 0 + return + progress['value'] = progress['value'] + progressleap + if progress['value'] == 100: + GuiButton['state'] = 'normal' + tkinter.messagebox.showinfo('Success', 'All sound files have been replaced!') + progress['value'] = 0 + + +def soundrevert(): + GuiRevertButton['state'] = 'disabled' + progressleap = 100 / len(expected_backups.values()) + for xkey, y in expected_backups.items(): + progress['value'] = progress['value'] + progressleap + if os.path.isfile(y): + try: + os.remove(expected_sounds[xkey]) + except FileNotFoundError: + pass # File does not exist. + os.rename(y, expected_sounds[xkey]) + if progress['value'] == 100: + GuiRevertButton['state'] = 'normal' + tkinter.messagebox.showinfo('Success', 'All sound files have been reverted!') + progress['value'] = 0 + + +GuiButton = tkinter.ttk.Button(GuiWindow, text='Replace', command=soundreplace) +GuiButton.grid(column=1, row=labelRow + 2) +CreateToolTip(GuiButton, 'Replaces sound files accordingly.') +GuiRevertButton = tkinter.ttk.Button(GuiWindow, text='Revert', command=soundrevert) +GuiRevertButton.grid(column=1, row=labelRow + 3) +CreateToolTip(GuiRevertButton, 'Reverts any changes to sound files accordingly.') +progress = Progressbar(GuiWindow, orient='horizontal', + length=400, mode='determinate') +progress.grid(column=1, row=labelRow + 4, pady=5) + +print('Launching Tk window') +GuiWindow.mainloop() diff --git a/metadata/file_version_info.txt b/metadata/file_version_info.txt new file mode 100644 index 0000000..1c3bc14 --- /dev/null +++ b/metadata/file_version_info.txt @@ -0,0 +1,44 @@ +# UTF-8 +# +# For more details about fixed file info 'ffi' see: +# http://msdn.microsoft.com/en-us/library/ms646997.aspx + +VSVersionInfo( + ffi=FixedFileInfo( + # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) + # Set not needed items to zero 0. Must always contain 4 elements. + filevers=(1,0,0,0), + prodvers=(1,0,0,0), + # Contains a bitmask that specifies the valid bits 'flags'r + mask=0x3f, + # Contains a bitmask that specifies the Boolean attributes of the file. + flags=0x0, + # The operating system for which this file was designed. + # 0x4 - NT and there is no need to change it. + OS=0x40004, + # The general type of file. + # 0x1 - the file is an application. + fileType=0x1, + # The function of the file. + # 0x0 - the function is not defined for this fileType + subtype=0x0, + # Creation date and time stamp. + date=(0, 0) + ), + kids=[ + StringFileInfo( + [ + StringTable( + u'040904B0', + [StringStruct(u'CompanyName', u'AtlasC0R3'), + StringStruct(u'FileDescription', u'Roblox Sound Replacer'), + StringStruct(u'FileVersion', u'1.0.0.0'), + StringStruct(u'InternalName', u'Roblox Sound Replacer'), + StringStruct(u'LegalCopyright', u''), + StringStruct(u'OriginalFilename', u''), + StringStruct(u'ProductName', u'Roblox Sound Replacer'), + StringStruct(u'ProductVersion', u'1.0.0.0')]) + ]), + VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) + ] +) \ No newline at end of file diff --git a/metadata/logo.ico b/metadata/logo.ico new file mode 100644 index 0000000..85ec99f Binary files /dev/null and b/metadata/logo.ico differ diff --git a/metadata/logo.png b/metadata/logo.png new file mode 100644 index 0000000..b3370fa Binary files /dev/null and b/metadata/logo.png differ diff --git a/metadata/metadata.yml b/metadata/metadata.yml new file mode 100644 index 0000000..aff380b --- /dev/null +++ b/metadata/metadata.yml @@ -0,0 +1,5 @@ +Version: 1.0.0.0 +CompanyName: AtlasC0R3 +FileDescription: Roblox Sound Replacer +InternalName: Roblox Sound Replacer +ProductName: Roblox Sound Replacer \ No newline at end of file diff --git a/tooltip.py b/tooltip.py new file mode 100644 index 0000000..8db5314 --- /dev/null +++ b/tooltip.py @@ -0,0 +1,55 @@ +import tkinter + + +class CreateToolTip(object): + """ + create a tooltip for a given widget + """ + + def __init__(self, widget, text='widget info'): + self.waittime = 500 # miliseconds + self.wraplength = 180 # pixels + self.widget = widget + self.text = text + self.widget.bind("", self.enter) + self.widget.bind("", self.leave) + self.widget.bind("", self.leave) + self.id = None + self.tw = None + + def enter(self, event=None): + self.schedule() + + def leave(self, event=None): + self.unschedule() + self.hidetip() + + def schedule(self): + self.unschedule() + self.id = self.widget.after(self.waittime, self.showtip) + + def unschedule(self): + unscheduleid = self.id + self.id = None + if unscheduleid: + self.widget.after_cancel(unscheduleid) + + def showtip(self, event=None): + x, y, cx, cy = self.widget.bbox("insert") + x += self.widget.winfo_rootx() + 25 + y += self.widget.winfo_rooty() + 20 + # creates a toplevel window + self.tw = tkinter.Toplevel(self.widget) + # Leaves only the label and removes the app window + self.tw.wm_overrideredirect(True) + self.tw.wm_geometry("+%d+%d" % (x, y)) + label = tkinter.Label(self.tw, text=self.text, justify='left', + background="#ffffff", relief='solid', borderwidth=1, + wraplength=self.wraplength) + label.pack(ipadx=1) + + def hidetip(self): + tw = self.tw + self.tw = None + if tw: + tw.destroy()