diff --git a/.gitignore b/.gitignore
index 2c0ddba..2950980 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,25 @@
-/scripts/MKW_Inputs/*.csv
+scripts/MKW_Inputs/**/*.csv
+scripts/MKW_Inputs/**/*.rkg
+scripts/AGC_Data/**/*.agc
+scripts/external/**/*.ini
+scripts/Settings/**/*.ini
+scripts/FrameDumps/**/*.txt
+scripts/FrameDumps/**/*.rawtxt
+scripts/FrameDumps/**/*.ini
+scripts/FrameDumps/**/*.mp4
+scripts/FrameDumps/**/*.mp3
+scripts/FrameDumps/**/*.wav
+scripts/FrameDumps/*.png
+
+scripts/Data Dump/**
+/scripts/Ghost/**/*.rkg
+/scripts/Ghost/**/*.csv
+**/__pycache__
+**/*test*.py
+
+
+scripts/RMC/test.py
+scripts/RMC/test codecallbacl.py
+
+/scripts/Input_optimizer
+scripts/RMC/test tp lap.py
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..17da246
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "python.analysis.extraPaths": [
+ "./scripts/python-stubs"
+ ],
+ "editor.detectIndentation": false
+}
\ No newline at end of file
diff --git a/scripts/AGC_Data/AGC files goes here.txt b/scripts/AGC_Data/AGC files goes here.txt
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/FrameDumps/Extra/author_display_util.py b/scripts/FrameDumps/Extra/author_display_util.py
new file mode 100644
index 0000000..ba9b238
--- /dev/null
+++ b/scripts/FrameDumps/Extra/author_display_util.py
@@ -0,0 +1,93 @@
+import moviepy
+import numpy as np
+from PIL import Image
+from PIL import ImageDraw
+from PIL import ImageFont
+import sys
+import os
+import time
+import subprocess
+import math
+
+from Extra import common
+
+def make_list_author(c, main_folder):
+ #return a dict so that result[frame] contain an ordered list of author that contributed on that frame
+ #also return a list of all author
+ result = {}
+ raw_author_list = []
+ author_file = os.path.join(main_folder, c.get('author_list_filename'))
+ if os.path.isfile(author_file):
+ with open(author_file, 'r', encoding='utf-8') as f:
+ entry_list = f.read().split('\n')
+ for entry in entry_list:
+ temp = entry.split(':')
+ author_name = temp[0]
+ raw_author_list.append(author_name)
+ frames_entry = temp[1].split(',')
+ for token in frames_entry:
+ temp = token.split('-')
+ for frame in range(int(temp[0]), int(temp[-1])+1):
+ if not frame in result.keys():
+ result[frame] = []
+ result[frame].append(author_name)
+ return raw_author_list, result
+
+
+def add_author_display(image, frame_dict, config, main_folder, raw_author_list, author_dict):
+ ad_config = config['Author display']
+ state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter'])
+ author_display_layer = Image.new('RGBA', image.size, (0,0,0,0))
+ ID = ImageDraw.Draw(author_display_layer)
+ font_folder = os.path.join(main_folder, 'Fonts')
+ top_left_text = ad_config.get('top_left').split(',')
+ top_left = round(float(top_left_text[0])*image.width), round(float(top_left_text[1])*image.height)
+ font_size = ad_config.getint('font_size')
+ font = ImageFont.truetype(os.path.join(font_folder, ad_config.get('font')), font_size)
+ active_text_color = common.get_color(ad_config.get('active_text_color'))
+ inactive_text_color = common.get_color(ad_config.get('inactive_text_color'))
+ outline_width = ad_config.getint('outline_width')
+ active_outline_color = common.get_color(ad_config.get('active_outline_color'))
+ inactive_outline_color = common.get_color(ad_config.get('inactive_outline_color'))
+ curframe = int(frame_dict['frame_of_input'])
+ spacing = 4
+ current_h = top_left[1]
+
+ for author_name in raw_author_list:
+ if curframe in author_dict.keys() and author_name in author_dict[curframe]:
+ ID.text( (top_left[0], current_h), author_name, fill = active_text_color, font = font, stroke_width = outline_width, stroke_fill = active_outline_color)
+ else:
+ ID.text( (top_left[0], current_h), author_name, fill = inactive_text_color , font = font, stroke_width = outline_width, stroke_fill = inactive_outline_color)
+ current_h += spacing + font_size
+
+
+ if ad_config.getboolean('fade_animation'):
+ author_display_layer = common.fade_image_manually(author_display_layer, frame_dict, ad_config)
+
+ # TODO
+ # if you change the delay to 0f (encoder line 97&98) the anim starts 1f early
+ # once shad's GUI is done, this can be parameterized easily. It's hard to notice so I didn't bother.
+
+ if ad_config.getboolean('fly_animation'):
+
+ if state == 1 and state_counter <= 10:
+ return None
+
+ offset = common.fly_in(frame_dict, image.height)
+ if state == 4 and 192 < state_counter <= 201:
+ image.alpha_composite(author_display_layer, (round(0 - image.width*offset), 0))
+ return None
+
+ ITEM_POSITION = 381/1440
+ AUTHOR_DISPLAY_POSITION = (1 - float(top_left_text[1]))
+ height_correction = round((AUTHOR_DISPLAY_POSITION - ITEM_POSITION)*image.height)
+
+ if offset is not None:
+ if ad_config.get('fly_in_direction') == 'bottom':
+ image.alpha_composite(author_display_layer, (0, image.height - top_left[1] - height_correction - offset))
+ else:
+ image.alpha_composite(author_display_layer, (0, offset - round(ITEM_POSITION*image.height)))
+ else:
+ image.alpha_composite(author_display_layer, (0,0))
+ else:
+ image.alpha_composite(author_display_layer, (0,0))
diff --git a/scripts/FrameDumps/Extra/common.py b/scripts/FrameDumps/Extra/common.py
new file mode 100644
index 0000000..d1afa97
--- /dev/null
+++ b/scripts/FrameDumps/Extra/common.py
@@ -0,0 +1,147 @@
+import moviepy
+import numpy as np
+from PIL import Image
+from PIL import ImageDraw
+from PIL import ImageFont
+from PIL import ImageFilter
+import sys
+import os
+import time
+import subprocess
+import math
+
+def get_color(color_text):
+ n = int(color_text, 16)
+ l = []
+ for _ in range(4):
+ l.append(n%256)
+ n//=256
+ l.reverse()
+ return tuple(l)
+
+def color_white_part(image, color):
+
+ if image.mode != 'RGBA':
+ image = image.convert('RGBA') #needed, crashes otherwise if image doesnt have transparency like analog_5_3_part1
+
+ color = tuple(color[:3])
+ pixels = image.load()
+ for y in range(image.height):
+ for x in range(image.width):
+ r, _, _, a = pixels[x, y]
+
+ if r == 255:
+ pixels[x, y] = (color[0], color[1], color[2], a)
+ else:
+ brightness = r / 255
+ new_r = int(color[0] * brightness)
+ new_g = int(color[1] * brightness)
+ new_b = int(color[2] * brightness)
+ pixels[x, y] = (new_r, new_g, new_b, a)
+
+ return image
+
+def get_filter_list(config_text):
+ raw_list = config_text.split(',')
+ filters = []
+ for raw_text in raw_list:
+ if raw_text in ['BLUR', 'blur', 'Blur']:
+ filters.append(ImageFilter.BLUR)
+ elif raw_text in ['CONTOUR', 'contour', 'Countour']:
+ filters.append(ImageFilter.CONTOUR)
+ elif raw_text in ['DETAIL', 'detail', 'Detail']:
+ filters.append(ImageFilter.DETAIL)
+ elif raw_text in ['EDGE_ENHANCE', 'Edge_enhance', 'edge_enhance', 'Edge_Enhance']:
+ filters.append(ImageFilter.EDGE_ENHANCE)
+ elif raw_text in ['EDGE_ENHANCE_MORE', 'Edge_enhance_more', 'edge_enhance_more', 'Edge_Enhance_More', 'Edge_enhance_More']:
+ filters.append(ImageFilter.EDGE_ENHANCE_MORE)
+ elif raw_text in ['EMBOSS', 'Emboss', 'emboss']:
+ filters.append(ImageFilter.EMBOSS)
+ elif raw_text in ['FIND_EDGES', 'Find_edges', 'find_edges', 'Find_Edges']:
+ filters.append(ImageFilter.FIND_EDGES)
+ elif raw_text in ['SHARPEN', 'Sharpen', 'sharpen']:
+ filters.append(ImageFilter.SHARPEN)
+ elif raw_text in ['SMOOTH', 'Smooth', 'smooth']:
+ filters.append(ImageFilter.SMOOTH)
+ elif raw_text in ['SMOOTH_MORE', 'Smooth_more', 'smooth_more', 'Smooth_More']:
+ filters.append(ImageFilter.SMOOTH_MORE)
+ return filters
+
+def get_resampler(resample_filter):
+ if resample_filter in ['nearest', 'Nearest', 'NEAREST']:
+ return Image.Resampling.NEAREST
+ elif resample_filter in ['bicubic', 'Bicubic', 'BICUBIC']:
+ return Image.Resampling.BICUBIC
+ elif resample_filter in ['bilinear', 'Bilinear', 'BILINEAR']:
+ return Image.Resampling.BILINEAR
+ elif resample_filter in ['box', 'Box', 'BOX']:
+ return Image.Resampling.BOX
+ elif resample_filter in ['hamming', 'Hamming', 'HAMMING']:
+ return Image.Resampling.HAMMING
+ else:
+ return Image.Resampling.LANCZOS
+
+
+def is_layer_active(frame_dict, config):
+ #Return a boolean if the layer should be visible on screen
+ fade_in_len = config.getint('fade_in_duration', 20)
+ fade_out_len = config.getint('fade_out_duration', 120)
+ start = config.getint('start_frame', 0)
+ try:
+ end = config.getint('end_frame')
+ except:
+ end = None
+ state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter'])
+ curframe = int(frame_dict['frame_of_input'])
+
+ if state < 1 or curframe <= start:
+ return False
+ if end and curframe >= end + fade_out_len:
+ return False
+ if end is None and state >= 4 and state_counter >= fade_out_len:
+ return False
+ return True
+
+def fade_image_manually(image, frame_dict, config):
+ fade_in_len = max(1,config.getint('fade_in_duration', 20))
+ fade_out_len = max(1,config.getint('fade_out_duration', 120))
+ start = config.getint('start_frame', 0)
+ try:
+ end = config.getint('end_frame')
+ except:
+ end = None
+ state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter'])
+ curframe = int(frame_dict['frame_of_input'])
+
+ if state >= 1 and start <= curframe < fade_in_len + start :
+ #fade in
+ alpha = (curframe - start)/fade_in_len
+ r,g,b,a = image.split()
+ a = a.point(lambda x:x*alpha)
+ image = Image.merge("RGBA", (r,g,b,a))
+ if end is None and state >= 4 and state_counter < fade_out_len:
+ #fade out on stage 4
+ alpha = 1 - state_counter/fade_out_len
+ r,g,b,a = image.split()
+ a = a.point(lambda x:x*alpha)
+ image = Image.merge("RGBA", (r,g,b,a))
+ if end and end < curframe <= fade_out_len + end :
+ #fade out on frame > end
+ alpha = 1 - (curframe - end)/fade_out_len
+ r,g,b,a = image.split()
+ a = a.point(lambda x:x*alpha)
+ image = Image.merge("RGBA", (r,g,b,a))
+ return image
+
+def fly_in(frame_dict, height):
+ state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter'])
+
+ if state == 1 and 10 < state_counter < 21:
+ DISPLACEMENT_RATIOS = [0.0757, 0.1549, 0.225, 0.4333, 0.382, 0.3402, 0.308, 0.284, 0.269, 0.265]
+ idx = state_counter - 11
+ return round(DISPLACEMENT_RATIOS[idx]*height)
+ if state == 4 and 192 < state_counter < 202:
+ FLY_OUT_RATE = 1/10
+ idx = state_counter - 192
+ return FLY_OUT_RATE * idx
+ return None
diff --git a/scripts/FrameDumps/Extra/config_util.py b/scripts/FrameDumps/Extra/config_util.py
new file mode 100644
index 0000000..6c26ec2
--- /dev/null
+++ b/scripts/FrameDumps/Extra/config_util.py
@@ -0,0 +1,341 @@
+import moviepy
+import numpy as np
+from PIL import Image
+from PIL import ImageDraw
+from PIL import ImageFont
+import sys
+import os
+import time
+import subprocess
+import math
+
+import configparser
+
+
+def create_config(filename):
+ config = configparser.ConfigParser(allow_no_value=True)
+ config.optionxform = str
+ config.add_section('README')
+ config.set('README', 'Visit : https://docs.google.com/document/d/e/2PACX-1vTXoEveB_1MZ3WizOpEWvZ-oyJMgg-3pRLGiNu-5vo853BMcrr8RY69REcTsheurI9qS2kfqrx1BZkT/pub\n\n' )
+
+ config.add_section('Encoding options')
+ config.set('Encoding options', '\n#Valid options are "normal", "discord", "youtube"')
+ '''
+ config.set('Encoding options', '#"normal" is no particular encoding settings, for people who want to do more video editing afterward')
+ config.set('Encoding options', '#"discord" is for size based encoding, allowing for a specific filesize output')
+ config.set('Encoding options', '#"youtube" is for directly uploading on YT. uses encode settings from https://gist.github.com/wuziq/b86f8551902fa1d477980a8125970877')
+ '''
+ config.set('Encoding options', '#You can also set an integer, to only render the .png of the corresponding frame (useful for trying different display options quickly)')
+ config.set('Encoding options', 'encode_style', 'discord')#
+ config.set('Encoding options', '\n#Video output size in mb. only used with discord encoding style')
+ config.set('Encoding options', 'output_file_size', '9.5')#in MB
+ config.set('Encoding options', '\n#range from 0 to 51. Only used with normal encoding style')
+ config.set('Encoding options', 'crf_value', '0')#
+ config.set('Encoding options', '\n#Choice "ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow"')
+ config.set('Encoding options', '#it affect the size of the output file, and the performances. it also affect quality when using discord encoding style')
+ config.set('Encoding options', 'preset', 'slow')#
+ config.set('Encoding options', '\n#Threads amount to use for ffmpeg')
+ config.set('Encoding options', 'threads', '4')#
+ config.set('Encoding options', '\n#Height of the output video in pixel')
+ config.set('Encoding options', 'resize_resolution', '1080')#
+ config.set('Encoding options', '\n#Choice "stretch", "crop", "fill", "none"')
+ config.set('Encoding options', 'resize_style', 'none') #stretch, crop, fill, none
+ config.set('Encoding options', '\n#Choice "nearest", "box", "bilinear", "hamming", "bicubic", "lanczos"')
+ config.set('Encoding options', '#visit https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters')
+ config.set('Encoding options', 'scaling_option', 'lanczos') #https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters
+ config.set('Encoding options', '\n#filename for the output file. .mp4 extension required')
+ config.set('Encoding options', 'output_filename', 'output.mp4')#
+ config.set('Encoding options', '\n#time in seconds for the video fade in')
+ config.set('Encoding options', 'video_fade_in', '0')#
+ config.set('Encoding options', '\n#time in seconds for the video fade out')
+ config.set('Encoding options', 'video_fade_out', '0')#
+ config.set('Encoding options', '\n#Choice "blur", "contour", "detail", "edge_enhance", "edge_enhance_more", "emboss", "find_edges", "sharpen", "smooth", "smooth_more"')
+ config.set('Encoding options', '#You can use several effects by separating them with a ",". DO NOT USE SPACES')
+ config.set('Encoding options', 'special_effects', 'none') #https://pillow.readthedocs.io/en/stable/reference/ImageFilter.html#module-PIL.ImageFilter
+
+ config.set('Encoding options', '\n\n##############################################################################\n')
+
+ config.add_section('Audio options')
+ config.set('Audio options', '\n#Choice "dsp", "dtk", "none"')
+ config.set('Audio options', 'audiodump_target', 'none') #dsp, dtk, none
+ config.set('Audio options', '\n#multiplicator on the in game volume')
+ config.set('Audio options', 'audiodump_volume', '1.0')#
+ config.set('Audio options', '\n#Must be a file in the same folder as this config file')
+ config.set('Audio options', 'bgm_filename', '')#
+ config.set('Audio options', '\n#multiplicator on the bgm volume')
+ config.set('Audio options', 'bgm_volume', '1')#
+ config.set('Audio options', '\n#Time in second the BGM should start at in the output video. You can use negative values to skip the beginning of the BGM')
+ config.set('Audio options', 'bgm_offset', '0')#
+ config.set('Audio options', '\n#time in second for the audio fade in')
+ config.set('Audio options', 'fade_in', '0')#
+ config.set('Audio options', '\n#time in second for the audio fade out')
+ config.set('Audio options', 'fade_out', '0')#
+
+ config.set('Audio options', '\n\n##############################################################################\n')
+
+ config.add_section('Infodisplay')
+ config.set('Infodisplay', '\n#draw the infodisplay')
+ config.set('Infodisplay', 'show_infodisplay', 'True')#
+ config.set('Infodisplay', '\n#Font filename. You must put the font in the Fonts folder.')
+ config.set('Infodisplay', 'font', 'MKW_Font')#
+ config.set('Infodisplay', '\n#font size in pixel on the final output resolution for fonts other than the mkw font.')
+ config.set('Infodisplay', 'font_size', '48')#
+ config.set('Infodisplay', '\n#Scaling factor for MKW Font if used')
+ config.set('Infodisplay', 'mkw_font_scaling', '3')#
+ config.set('Infodisplay', '\n#vertical spacing in pixel between lines')
+ config.set('Infodisplay', 'spacing', '4')#
+ config.set('Infodisplay', '\n#frame OF INPUT this section will appear on')
+ config.set('Infodisplay', 'start_frame', '0')#
+ config.set('Infodisplay', '\n#frame OF INPUT this section will disappear on. Leave empty for race end')
+ config.set('Infodisplay', 'end_frame', '' )#
+ config.set('Infodisplay', '\n#put a fade in and fade out animation to the infodisplay')
+ config.set('Infodisplay', 'fade_animation', 'True')#
+ config.set('Infodisplay', '\n#duration of the fade in animation in the infodisplay')
+ config.set('Infodisplay', 'fade_in_duration', '20')#
+ config.set('Infodisplay', '\n#duration of the fade out animation in the infodisplay')
+ config.set('Infodisplay', 'fade_out_duration', '120')#
+ config.set('Infodisplay', '\n#put a fly in and fly out animation to the infodisplay')
+ config.set('Infodisplay', 'fly_animation', 'False')#
+ config.set('Infodisplay', '\n#Choose to have the Info Display fly in from the top or bottom. (when fly_animation is enabled)')
+ config.set('Infodisplay', 'fly_in_direction', 'bottom')#
+ config.set('Infodisplay', '\n#Anchor for infodisplay text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen')
+ config.set('Infodisplay', 'anchor', '0.2,0.1')#
+ config.set('Infodisplay', '\n#Choice "left", "middle", "right"')
+ config.set('Infodisplay', 'anchor_style', 'middle')#
+ config.set('Infodisplay', '\n# True for having the value, then the text, False for having the text, then the value')
+ config.set('Infodisplay', 'invert_text', 'True')#
+ config.set('Infodisplay', '\n#size of the outline of the font in pixel')
+ config.set('Infodisplay', 'outline_width', '3')#
+ config.set('Infodisplay', '\n#color of the outline of the font')
+ config.set('Infodisplay', 'outline_color', '000000FF')#
+
+ config.set('Infodisplay', '\n#reimplements pretty speedometer using fade/fly in animations. set it to "xyz", "xz","iv"; or "off" to disable it.')
+ config.set('Infodisplay', 'pretty_speedometer_type', 'off')#Might be deleted later, since we can have several infodisplay, which gives more options
+ config.set('Infodisplay', 'pretty_speedometer_color', 'F2E622FF')#
+
+ config.set('Infodisplay', '\n#parameters for the XYZ speed (delta position)')
+ config.set('Infodisplay', 'show_speed_xyz', 'True')
+ config.set('Infodisplay', 'text_speed_xyz', '. Speed')
+ config.set('Infodisplay', 'color_speed_xyz', 'FF0000FF')
+
+ config.set('Infodisplay', '\n#parameters for the XZ speed (delta position)')
+ config.set('Infodisplay', 'show_speed_xz', 'False')
+ config.set('Infodisplay', 'text_speed_xz', '. Speed XZ')
+ config.set('Infodisplay', 'color_speed_xz', 'FF0000FF')
+
+ config.set('Infodisplay', '\n#parameters for the Y speed (delta position)')
+ config.set('Infodisplay', 'show_speed_y', 'False')
+ config.set('Infodisplay', 'text_speed_y', '. Speed Y')
+ config.set('Infodisplay', 'color_speed_y', 'FF0000FF')
+
+ config.set('Infodisplay', '\n#parameters for the XYZ internal velocity')
+ config.set('Infodisplay', 'show_iv_xyz', 'True')
+ config.set('Infodisplay', 'text_iv_xyz', '. IV')
+ config.set('Infodisplay', 'color_iv_xyz', '00FF00FF')
+
+ config.set('Infodisplay', '\n#parameters for the XZ internal velocity')
+ config.set('Infodisplay', 'show_iv_xz', 'False')
+ config.set('Infodisplay', 'text_iv_xz', '. IV XZ')
+ config.set('Infodisplay', 'color_iv_xz', '00FF00FF')
+
+ config.set('Infodisplay', '\n#parameters for the Y internal velocity')
+ config.set('Infodisplay', 'show_iv_y', 'False')
+ config.set('Infodisplay', 'text_iv_y', '. IV Y')
+ config.set('Infodisplay', 'color_iv_y', '00FF00FF')
+
+ config.set('Infodisplay', '\n#parameters for the XYZ external velocity')
+ config.set('Infodisplay', 'show_ev_xyz', 'True')
+ config.set('Infodisplay', 'text_ev_xyz', '. EV')
+ config.set('Infodisplay', 'color_ev_xyz', '0000FFFF')
+
+ config.set('Infodisplay', '\n#parameters for the XZ external velocity')
+ config.set('Infodisplay', 'show_ev_xz', 'False')
+ config.set('Infodisplay', 'text_ev_xz', '. EV XZ ')
+ config.set('Infodisplay', 'color_ev_xz', '0000FFFF')
+
+ config.set('Infodisplay', '\n#parameters for the Y external velocity')
+ config.set('Infodisplay', 'show_ev_y', 'False')
+ config.set('Infodisplay', 'text_ev_y', '. EV Y')
+ config.set('Infodisplay', 'color_ev_y', '0000FFFF')
+
+ config.set('Infodisplay', '\n#display custom text using the mkw font. the anchors x axis will be the center of the text, the y axis will be the top')
+ config.set('Infodisplay', 'enable_custom_text', 'False')
+
+ config.set('Infodisplay', '\n#to add multiple texts, copy these lines below and increment the number at the end of the parameters')
+ config.set('Infodisplay', 'custom_text_anchor_1', '0.2.01')
+ config.set('Infodisplay', 'custom_text_scaling_1', '2.5')
+ config.set('Infodisplay', 'custom_text_1', 'your text here')
+ config.set('Infodisplay', 'custom_text_color_1', 'F2E622FF')
+
+ config.set('Infodisplay', '\n\n##############################################################################\n')
+
+ config.add_section('Speed display')
+ config.set('Speed display', '\n#draw the speed display')
+ config.set('Speed display', 'show_speed_display', 'True')
+ config.set('Speed display', '\n#frame OF INPUT this section will appear on')
+ config.set('Speed display', 'start_frame', '0')#
+ config.set('Speed display', '\n#frame OF INPUT this section will disappear on. Leave empty for race end')
+ config.set('Speed display', 'end_frame', '')#
+ config.set('Speed display', '\n#put a fade in and fade out animation to the Speed display')
+ config.set('Speed display', 'fade_animation', 'True')#
+ config.set('Speed display', '\n#duration of the fade in animation in the Speed display')
+ config.set('Speed display', 'fade_in_duration', '20')#
+ config.set('Speed display', '\n#duration of the fade out animation in the Speed display')
+ config.set('Speed display', 'fade_out_duration', '120')#
+ config.set('Speed display', '\n#put a fly in and fly out animation to the speed display')
+ config.set('Speed display', 'fly_animation', 'False')#
+ config.set('Speed display', '\n#Choose to have the Speed Display fly in from the top or bottom. (when fly_animation is enabled)')
+ config.set('Speed display', 'fly_in_direction', 'bottom')#
+ config.set('Speed display', '\n#Top left anchor for speed display text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen')
+ config.set('Speed display', 'top_left', '0.7, 0.5')#
+ config.set('Speed display', '\n#Activating this will make the circle rotate with your facing yaw, so it always face up')
+ config.set('Speed display', 'rotate_with_yaw', 'True')#
+ config.set('Speed display', '\n#Radius of the circle in pixel on the final output resolution')
+ config.set('Speed display', 'circle_radius', '200')#
+ config.set('Speed display', '\n#Color of the interior of the circle')
+ config.set('Speed display', 'circle_color', 'FFFFFF80')#
+ config.set('Speed display', '\n#color of the border of the circle')
+ config.set('Speed display', 'circle_outline_color', '000000FF')#
+ config.set('Speed display', '\n#size of the border of the circle')
+ config.set('Speed display', 'circle_outline_width', '4')#
+ config.set('Speed display', '\n#draw the XZ axis when rotating with yaw. Draw the sideways and forward axis when not rotating with yaw')
+ config.set('Speed display', 'draw_axis', 'True')#
+ config.set('Speed display', '\n#color of the axis')
+ config.set('Speed display', 'axis_color', '000000FF')#
+ config.set('Speed display', '\n#width of the axis in pixel on the final output resolution')
+ config.set('Speed display', 'axis_width', '2')#
+ config.set('Speed display', '\n#Draw a pieslice corresponding to the facing yaw')
+ config.set('Speed display', 'draw_pieslice', 'False')#
+ config.set('Speed display', '\n#color of that pieslice')
+ config.set('Speed display', 'pieslice_color', 'FFFF00FF')
+ config.set('Speed display', '\n#width of the arrows')
+ config.set('Speed display', 'arrow_width', '6')#
+ config.set('Speed display', '\n#size of the border of the arrows')
+ config.set('Speed display', 'arrow_outline_width', '1')#
+ config.set('Speed display', '\n#color of the border of the arrows')
+ config.set('Speed display', 'arrow_outline_color', '000000FF')#
+ config.set('Speed display', '\n#maximum speed corresponding to a full arrow. this parameter also scale the length of the arrow')
+ config.set('Speed display', 'cap_speed', '125')#
+ config.set('Speed display', '\n#arrow corresponding to speed (delta position)')
+ config.set('Speed display', 'show_speed', 'True')
+ config.set('Speed display', 'color_speed', 'FF0000FF')
+ config.set('Speed display', '\n#arrow corresponding to internal velocity')
+ config.set('Speed display', 'show_iv', 'True')
+ config.set('Speed display', 'color_iv', '00FF00FF')
+ config.set('Speed display', '\n#arrow corresponding to external velocity')
+ config.set('Speed display', 'show_ev', 'True')
+ config.set('Speed display', 'color_ev', '0000FFFF')
+
+ config.set('Speed display', '\n\n##############################################################################\n')
+
+ config.add_section('Input display')
+ config.set('Input display', '\n#draw the input display')
+ config.set('Input display', 'show_input_display', 'True')
+ config.set('Input display', '\n#frame OF INPUT this section will appear on')
+ config.set('Input display', 'start_frame', '0')#
+ config.set('Input display', '\n#frame OF INPUT this section will disappear on. Leave empty for race end')
+ config.set('Input display', 'end_frame', '')#
+ config.set('Input display', '\n#put a fade in and fade out animation to the Input display')
+ config.set('Input display', 'fade_animation', 'True')#
+ config.set('Input display', '\n#duration of the fade in animation in the Input display')
+ config.set('Input display', 'fade_in_duration', '20')#
+ config.set('Input display', '\n#duration of the fade out animation in the Input display')
+ config.set('Input display', 'fade_out_duration', '120')#
+ config.set('Input display', '\n#put a fly in and fly out animation to the input display')
+ config.set('Input display', 'fly_animation', 'False')#
+ config.set('Input display', '\n#Choose to have the Input Display fly in from the top or bottom. (when fly_animation is enabled)')
+ config.set('Input display', 'fly_in_direction', 'bottom')#
+ config.set('Input display', '\n#Top left anchor for input display text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen')
+ config.set('Input display', 'top_left', '0.03,0.7')#
+ config.set('Input display', '\n#width options equivalent to pyrkg and other input display tools. image quality has also been improved')
+ config.set('Input display', '#all pairs of widths 3-7 and outline widths 2-4 are possible, except (7,4)')
+ config.set('Input display', 'width', '3')#
+ config.set('Input display', 'outline_width', '3')#
+ config.set('Input display', '\n#coloring options for the input display parts. alpha channel has no effect but must use RGBA format')
+ config.set('Input display', 'color_shoulder_left', 'FFFFFFFF')
+ config.set('Input display', 'color_shoulder_right', 'FFFFFFFF')
+ config.set('Input display', 'color_dpad', 'FFFFFFFF')
+ config.set('Input display', 'color_analog', 'FFFFFFFF')
+ config.set('Input display', 'color_a_button', 'FFFFFFFF')
+ config.set('Input display', 'color_stick_text', 'FFFFFFFF')
+ config.set('Input display', '\n#multiplier on the size of input display. Default size is 250x400 in pixel on the output resolution')
+ config.set('Input display', 'scaling', '1')#
+ config.set('Input display', '\n#Choice "nearest", "box", "bilinear", "hamming", "bicubic", "lanczos"')
+ config.set('Input display', '#visit #https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters')
+ config.set('Input display', 'scaling_option', 'lanczos') #https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters
+ config.set('Input display', '\n#draw the bounding box. The file itself can be modified in the Input_display folder')
+ config.set('Input display', 'draw_box', 'True')#
+ config.set('Input display', '\n#Draw the +7,-7 text corresponding to the stick input')
+ config.set('Input display', 'draw_stick_text', 'True')#
+ config.set('Input display', '\n#size of stick text. the default size is 36')
+ config.set('Input display', 'stick_text_size', '36')#
+ config.set('Input display', '\n#Choice "blur", "contour", "detail", "edge_enhance", "edge_enhance_more", "emboss", "find_edges", "sharpen", "smooth", "smooth_more"')
+ config.set('Input display', '#You can use several effects by separating them with a ",". DO NOT USE SPACES')
+ config.set('Input display', 'special_effects', 'none')#
+ config.set('Input display', '\n\n##############################################################################\n')
+
+ config.add_section('Author display')
+ config.set('Author display', '\n#draw the author display')
+ config.set('Author display', 'show_author_display', 'False')
+ config.set('Author display', '\n#frame OF INPUT this section will appear on')
+ config.set('Author display', 'start_frame', '0')#
+ config.set('Author display', '\n#frame OF INPUT this section will disappear on. Leave empty for race end')
+ config.set('Author display', 'end_frame', '')#
+ config.set('Author display', '\n#put a fade in and fade out animation to the Author display')
+ config.set('Author display', 'fade_animation', 'True')#
+ config.set('Author display', '\n#duration of the fade in animation in the Author display')
+ config.set('Author display', 'fade_in_duration', '20')#
+ config.set('Author display', '\n#duration of the fade out animation in the Author display')
+ config.set('Author display', 'fade_out_duration', '120')#
+ config.set('Author display', '\n#put a fly in and fly out animation to the author display')
+ config.set('Author display', 'fly_animation', 'False')#
+ config.set('Author display', '\n#Choose to have the Author Display fly in from the top or bottom. (when fly_animation is enabled)')
+ config.set('Author display', 'fly_in_direction', 'top')#
+ config.set('Author display', '\n#Top left anchor for author display text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen')
+ config.set('Author display', 'top_left', '0.1,0.4')#
+ config.set('Author display', '\n#Must be a file in the same folder as this config file. Mandatory for the author display to work')
+ config.set('Author display', 'author_list_filename', 'authors.txt')#
+ config.set('Author display', '\n#Font filename. You must put the font in the Font folder.')
+ config.set('Author display', 'font', 'FOT-Rodin Pro EB.otf')#
+ config.set('Author display', '\n#font size in pixel on the final output resolution')
+ config.set('Author display', 'font_size', '48')#
+ config.set('Author display', '\n#color used for the text when the author has input on this frame')
+ config.set('Author display', 'active_text_color', 'FFFFFFFF')#
+ config.set('Author display', '\n#color used for the text when the author does not have input on this frame')
+ config.set('Author display', 'inactive_text_color', 'FFFFFF55')#
+ config.set('Author display', '\n#outline width for the font used')
+ config.set('Author display', 'outline_width', '3')#
+ config.set('Author display', '\n#color of the outline when the author has input on this frame')
+ config.set('Author display', 'active_outline_color', '000000FF')#
+ config.set('Author display', '\n#color of the outline when the author has input on this frame')
+ config.set('Author display', 'inactive_outline_color', '00000055')#
+ config.set('Author display', '\n\n##############################################################################\n')
+
+ config.add_section('Extra display')
+ config.set('Extra display', '\n#this is a debug feature, ignore it\n')
+ config.set('Extra display', '\n#draw the Extra display')
+ config.set('Extra display', 'show_extra_display', 'True')#
+ config.set('Extra display', '\n#Font filename. You must put the font in the Fonts folder.')
+ config.set('Extra display', 'font', 'CONSOLA.TTF')#
+ config.set('Extra display', '\n#font size in pixel on the final output resolution for fonts other than the mkw font.')
+ config.set('Extra display', 'font_size', '48')#
+ config.set('Extra display', '\n#Scaling factor for MKW Font if used')
+ config.set('Extra display', 'mkw_font_scaling', '3')#
+ config.set('Extra display', '\n#vertical spacing in pixel between lines')
+ config.set('Extra display', 'spacing', '4')#
+ config.set('Extra display', '\n#Anchor for Extra display text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen')
+ config.set('Extra display', 'anchor', '0.05,0.05')#
+ config.set('Extra display', '\n#Choice "left", "middle", "right"')
+ config.set('Extra display', 'anchor_style', 'middle')#
+ config.set('Extra display', '\n#size of the outline of the font in pixel')
+ config.set('Extra display', 'outline_width', '3')#
+ config.set('Extra display', '\n#color of the outline of the font')
+ config.set('Extra display', 'outline_color', '000000FF')#
+
+ with open(filename, 'w') as f:
+ config.write(f)
+
+def get_config(config_filename):
+ config = configparser.ConfigParser()
+ config.read(config_filename)
+ return config
diff --git a/scripts/FrameDumps/Extra/extradisplay_util.py b/scripts/FrameDumps/Extra/extradisplay_util.py
new file mode 100644
index 0000000..2c5c1b2
--- /dev/null
+++ b/scripts/FrameDumps/Extra/extradisplay_util.py
@@ -0,0 +1,96 @@
+import moviepy
+import numpy as np
+from PIL import Image
+from PIL import ImageDraw
+from PIL import ImageFont
+import sys
+import os
+import time
+import subprocess
+import math
+
+from Extra import common
+
+#credit to https://github.com/nkfr26/mkwii-text-generator-tkinter
+width_offset_table = {"A" : -8, "B" :-16, "C" :-21, "D" :-16,
+ "E" :-16, "F" :-21, "G" :-18, "H" :-16,
+ "I" :-14, "J" :-16, "K" :-15, "L" :-14,
+ "M" :-17, "N" :-16, "O" :-13, "P" :-15,
+ "Q" :-12, "R" :-12, "S" :-16, "T" :-15,
+ "U" :-16, "V" :-12, "W" :-22, "X" :-22,
+ "Y" :-24, "Z" :-12, "+" :-32, "-" :-32,
+ "/" :-28, ":" : -1, "." : -1, "0" : -1,
+ "1" : -1, "2" : -1, "3" : -1, "4" : -1,
+ "5" : -1, "6" : -1, "7" : -1, "8" : -1,
+ "9" : -1, "&" :-32}
+
+
+
+def add_mkw_text(line_layer, x, text, mkw_font_dict, color, mkw_scaling):
+ for letter in text:
+ if letter in mkw_font_dict.keys():
+ cur_letter = mkw_font_dict[letter].copy()
+ channels = list(cur_letter.split())
+ for i in range(4):
+ channels[i] = channels[i].point(lambda x : round(x*color[i]/255))
+ cur_letter = Image.merge("RGBA", tuple(channels))
+ line_layer.alpha_composite(cur_letter, (x,0))
+ x += cur_letter.size[0] + round(width_offset_table[letter]*4*mkw_scaling)
+ elif letter == '>' :
+ x += round(10*mkw_scaling)
+ elif letter == '<':
+ x -= round(10*mkw_scaling)
+ else:
+ x += round(268*mkw_scaling) + round(4*mkw_scaling*-1) #Make spaces the exact same width as numbers
+ return x
+
+
+def add_mkw_font_line(id_layer, text, color, mkw_font_dict, anchor, mkw_scaling, anchor_style):
+ w,h = anchor
+ line_layer = Image.new('RGBA', (id_layer.size[0], round(mkw_scaling*336)+1), (0,0,0,0))
+
+
+ left_x = 0
+ middle_x = add_mkw_text(line_layer, left_x, text[0], mkw_font_dict, color, mkw_scaling)
+ right_x = add_mkw_text(line_layer, middle_x, text[1], mkw_font_dict, color, mkw_scaling)
+
+ if anchor_style == 'left':
+ offset = left_x
+ elif anchor_style == 'middle':
+ offset = middle_x
+ else:
+ offset = right_x
+ w -= offset
+
+ id_layer.alpha_composite(line_layer, (w,h))
+
+
+def add_extradisplay(image, extra_text, ex_config, font_folder, mkw_font_dict):
+
+ infodisplay_layer = Image.new('RGBA', image.size, (0,0,0,0))
+ ID = ImageDraw.Draw(infodisplay_layer)
+ mkw_scaling = eval(ex_config.get('mkw_font_scaling'))/12
+ top_left_text = ex_config.get('anchor').split(',')
+ top_left = round(float(top_left_text[0])*image.width), round(float(top_left_text[1])*image.height)
+ current_h = top_left[1]
+ spacing = ex_config.getint('spacing')
+ font_size = ex_config.getint('font_size')
+ font_filename = os.path.join(font_folder, ex_config.get('font'))
+ if os.path.isfile(font_filename):
+ font = ImageFont.truetype(font_filename, font_size)
+ else:
+ font = None
+ outline_width = ex_config.getint('outline_width')
+ outline_color = common.get_color(ex_config.get('outline_color'))
+ color = common.get_color(ex_config.get('color', 'FFFF00FF'))
+
+ for text in extra_text.split('\n'):
+ if font is None:
+ add_mkw_font_line(infodisplay_layer, text, color, mkw_font_dict, (top_left[0], current_h), mkw_scaling, 'left')
+ current_h += round(336*mkw_scaling) + spacing
+ else:
+ ID.text( (top_left[0], current_h), text, fill = color, font = font, stroke_width = outline_width, stroke_fill = outline_color)
+ current_h += spacing + font_size
+
+ image.alpha_composite(infodisplay_layer, (0,0))
+
diff --git a/scripts/FrameDumps/Extra/infodisplay_util.py b/scripts/FrameDumps/Extra/infodisplay_util.py
new file mode 100644
index 0000000..dd6d1a4
--- /dev/null
+++ b/scripts/FrameDumps/Extra/infodisplay_util.py
@@ -0,0 +1,220 @@
+import moviepy
+import numpy as np
+from PIL import Image
+from PIL import ImageDraw
+from PIL import ImageFont
+import sys
+import os
+import time
+import subprocess
+import math
+
+from Extra import common
+
+#credit to https://github.com/nkfr26/mkwii-text-generator-tkinter
+width_offset_table = {"A" : -8, "B" :-16, "C" :-21, "D" :-16,
+ "E" :-16, "F" :-21, "G" :-18, "H" :-16,
+ "I" :-14, "J" :-16, "K" :-15, "L" :-14,
+ "M" :-17, "N" :-16, "O" :-13, "P" :-15,
+ "Q" :-12, "R" :-12, "S" :-16, "T" :-15,
+ "U" :-16, "V" :-12, "W" :-22, "X" :-22,
+ "Y" :-24, "Z" :-12, "+" :-32, "-" :-32,
+ "/" :-28, ":" : -1, "." : -1, "0" : -1,
+ "1" : -1, "2" : -1, "3" : -1, "4" : -1,
+ "5" : -1, "6" : -1, "7" : -1, "8" : -1,
+ "9" : -1, "&" :-32}
+
+
+def make_text_key(d, c, font, key):
+ text_key = 'text'+key[4:]
+
+ if key == 'show_speed_xyz':
+ val = (float(d['spd_x']) ** 2 + float(d['spd_y']) ** 2 + float(d['spd_z']) ** 2)**0.5
+ elif key == 'show_speed_xz':
+ val = (float(d['spd_x']) ** 2 + float(d['spd_z']) ** 2)**0.5
+ elif key == 'show_speed_y':
+ val = float(d['spd_y'])
+ elif key == 'show_iv_xyz':
+ val = (float(d['iv_x']) ** 2 + float(d['iv_y']) ** 2 + float(d['iv_z']) ** 2)**0.5
+ elif key == 'show_iv_xz':
+ val = (float(d['iv_x']) ** 2 + float(d['iv_z']) ** 2)**0.5
+ elif key == 'show_iv_y':
+ val = float(d['iv_y'])
+ elif key == 'show_ev_xyz':
+ val = (float(d['ev_x']) ** 2 + float(d['ev_y']) ** 2 + float(d['ev_z']) ** 2)**0.5
+ elif key == 'show_ev_xz':
+ val = (float(d['ev_x']) ** 2 + float(d['ev_z']) ** 2)**0.5
+ elif key == 'show_ev_y':
+ val = float(d['ev_y'])
+ elif key == 'show_frame_count':
+ val = float(d['frame_of_input'])
+
+ prefix_text = c[text_key]
+ if prefix_text[0] in ['.', '"']:
+ prefix_text = prefix_text[1:]
+ if prefix_text[-1] in ['.', '"']:
+ prefix_text = prefix_text[:-1]
+ return f'{prefix_text}',f'{val:.2f}'
+
+
+def add_mkw_text(line_layer, x, text, mkw_font_dict, color, mkw_scaling):
+ for letter in text:
+ if letter in mkw_font_dict.keys():
+ cur_letter = mkw_font_dict[letter].copy()
+ channels = list(cur_letter.split())
+ for i in range(4):
+ channels[i] = channels[i].point(lambda x : round(x*color[i]/255))
+ cur_letter = Image.merge("RGBA", tuple(channels))
+ line_layer.alpha_composite(cur_letter, (x,0))
+ x += cur_letter.size[0] + round(width_offset_table[letter]*4*mkw_scaling)
+ elif letter == '>' :
+ x += round(10*mkw_scaling)
+ elif letter == '<':
+ x -= round(10*mkw_scaling)
+ else:
+ x += round(268*mkw_scaling) + round(4*mkw_scaling*-1) #Make spaces the exact same width as numbers
+ return x
+
+
+def add_mkw_font_line(id_layer, prefix, value, color, mkw_font_dict, anchor, mkw_scaling, anchor_style, invert_design):
+ w,h = anchor
+ if value == None or prefix == None:
+ custom_text_layer = Image.new('RGBA', (id_layer.size[0], round(mkw_scaling*336)+1), (0,0,0,0))
+ x = add_mkw_text(custom_text_layer, 0, prefix or value, mkw_font_dict, color, mkw_scaling)
+ if anchor_style == 'right':
+ id_layer.alpha_composite(custom_text_layer, (w-x,h)) # for pretty speedometer
+ else:
+ id_layer.alpha_composite(custom_text_layer, (w-x//2,h)) # for custom text
+ return None
+
+ line_layer = Image.new('RGBA', (id_layer.size[0], round(mkw_scaling*336)+1), (0,0,0,0))
+
+ if invert_design and not prefix == '<>':
+ text = value,prefix
+ else:
+ text = prefix,value
+
+ left_x = 0
+ middle_x = add_mkw_text(line_layer, left_x, text[0], mkw_font_dict, color, mkw_scaling)
+ right_x = add_mkw_text(line_layer, middle_x, text[1], mkw_font_dict, color, mkw_scaling)
+
+ if prefix == '<>':
+ id_layer.alpha_composite(line_layer, (w-(middle_x+right_x)//2,h))
+ return None
+
+ if anchor_style == 'left':
+ offset = left_x
+ elif anchor_style == 'middle':
+ offset = middle_x
+ else:
+ offset = right_x
+ w -= offset
+
+ id_layer.alpha_composite(line_layer, (w,h))
+
+
+def add_infodisplay(image, id_dict, id_config, font_folder, mkw_font_dict):
+ state, state_counter = int(id_dict['state']), int(id_dict['state_counter'])
+ infodisplay_layer = Image.new('RGBA', image.size, (0,0,0,0))
+ ID = ImageDraw.Draw(infodisplay_layer)
+ mkw_scaling = eval(id_config.get('mkw_font_scaling'))/12
+ top_left_text = id_config.get('anchor').split(',')
+ top_left = round(float(top_left_text[0])*image.width), round(float(top_left_text[1])*image.height)
+ current_h = top_left[1]
+ spacing = id_config.getint('spacing')
+ font_size = id_config.getint('font_size')
+ font_filename = os.path.join(font_folder, id_config.get('font'))
+ if os.path.isfile(font_filename):
+ font = ImageFont.truetype(font_filename, font_size)
+ else:
+ font = None
+ outline_width = id_config.getint('outline_width')
+ outline_color = common.get_color(id_config.get('outline_color'))
+ anchor_style = id_config.get('anchor_style')
+ invert = id_config.getboolean('invert_text')
+
+
+ if id_config.get('pretty_speedometer_type') in ('xyz', 'xz', 'iv'):
+ custom_color = common.get_color(id_config.get('pretty_speedometer_color'))
+ if id_config.get('pretty_speedometer_type') == 'xyz':
+ val = (float(id_dict['spd_x']) ** 2 + float(id_dict['spd_y']) ** 2 + float(id_dict['spd_z']) ** 2)**0.5
+ elif id_config.get('pretty_speedometer_type') == 'xz':
+ val = (float(id_dict['spd_x']) ** 2 + float(id_dict['spd_z']) ** 2)**0.5
+ else:
+ val = (float(id_dict['iv_x']) ** 2 + float(id_dict['iv_y']) ** 2 + float(id_dict['iv_z']) ** 2)**0.5
+ value_text = f'{val:.2f}'
+ add_mkw_font_line(infodisplay_layer, None, value_text.upper(), custom_color, mkw_font_dict[0.2375], (round(0.932*image.width), round(0.862*image.height)), 0.2375, 'right', False)
+
+
+ if id_config.getboolean('enable_custom_text'):
+ i = 1
+ while f'custom_text_{i}' in id_config:
+ custom_text_anchor = id_config.get(f'custom_text_anchor_{i}').split(',')
+ custom_anchor = round(float(custom_text_anchor[0])*image.width), round(float(custom_text_anchor[1])*image.height)
+ custom_scaling = eval(id_config.get(f'custom_text_scaling_{i}'))/12
+ custom_text = id_config.get(f'custom_text_{i}')
+ custom_color = common.get_color(id_config.get(f'custom_text_color_{i}'))
+ add_mkw_font_line(infodisplay_layer, custom_text.upper(), None, custom_color, mkw_font_dict[custom_scaling], (custom_anchor[0], custom_anchor[1]), custom_scaling, 'centered', False)
+ i += 1
+
+
+ for key in id_config.keys():
+ if len(key) > 4 and key[:4] == 'show' and key != 'show_infodisplay' and id_config.getboolean(key):
+ prefix_text,value_text = make_text_key(id_dict, id_config, font, key)
+ color_text = id_config.get('color'+key[4:])
+ color = common.get_color(color_text)
+
+ if font is None:
+ add_mkw_font_line(infodisplay_layer, prefix_text.upper(), value_text.upper(), color, mkw_font_dict[mkw_scaling], (top_left[0], current_h), mkw_scaling, anchor_style, invert)
+ current_h += round(336*mkw_scaling) + spacing
+ else:
+ if invert:
+ text = value_text + prefix_text
+ if anchor_style == 'left':
+ offset = 0
+ elif anchor_style == 'middle':
+ offset = -ID.textlength(value_text, font, font_size = font_size)
+ else:
+ offset = -ID.textlength(text, font, font_size = font_size)
+ else:
+ text = prefix_text + value_text
+ if anchor_style == 'left':
+ offset = 0
+ elif anchor_style == 'middle':
+ offset = -ID.textlength(prefix_text, font, font_size = font_size)
+ else:
+ offset = -ID.textlength(text, font, font_size = font_size)
+
+ ID.text( (top_left[0]+offset, current_h), text, fill = color, font = font, stroke_width = outline_width, stroke_fill = outline_color)
+ current_h += spacing + font_size
+
+
+ if id_config.getboolean('fade_animation'):
+ infodisplay_layer = common.fade_image_manually(infodisplay_layer, id_dict, id_config)
+
+
+ if id_config.getboolean('fly_animation'):
+
+ if state == 1 and state_counter <= 10:
+ return None
+
+ offset = common.fly_in(id_dict, image.height)
+
+ if state == 4 and 192 < state_counter < 202:
+ image.alpha_composite(infodisplay_layer, (round(0 - image.width*offset), 0))
+ return None
+
+ ITEM_POSITION = 381/1440
+ INFODISPLAY_POSITION = (1 - float(top_left_text[1]))
+ height_correction = round((INFODISPLAY_POSITION - ITEM_POSITION)*image.height)
+
+ if offset is not None:
+ if id_config.get('fly_in_direction') == 'bottom':
+ image.alpha_composite(infodisplay_layer, (0, image.height - top_left[1] - height_correction - offset))
+ else:
+ image.alpha_composite(infodisplay_layer, (0, offset - round(ITEM_POSITION*image.height)))
+ else:
+ image.alpha_composite(infodisplay_layer, (0,0))
+ else:
+ image.alpha_composite(infodisplay_layer, (0,0))
+
diff --git a/scripts/FrameDumps/Extra/input_display_util.py b/scripts/FrameDumps/Extra/input_display_util.py
new file mode 100644
index 0000000..ccf6b61
--- /dev/null
+++ b/scripts/FrameDumps/Extra/input_display_util.py
@@ -0,0 +1,142 @@
+import moviepy
+import numpy as np
+from PIL import Image
+from PIL import ImageDraw
+from PIL import ImageFont
+from PIL import ImageFilter
+import sys
+import os
+import time
+import subprocess
+import math
+from Extra import common
+
+def make_input_display(raw_input_text, config_id, font, recolored_images, w, ow):
+ args = raw_input_text.split(',')
+ A = int(args[0])
+ B = int(args[1])
+ L = int(args[2])
+ X = int(args[5])
+ Y = int(args[6])
+ T = int(args[7])
+
+ color_stick_text_tmp = common.get_color(config_id.get('color_stick_text'))
+ color_stick_text = tuple(color_stick_text_tmp[:3])
+
+ if config_id.getboolean('draw_box'):
+ output = recolored_images['background'].copy()
+ else:
+ output = Image.new('RGBA', (400,250), (255, 0, 0, 0))
+
+ if A == 0:
+ recolored_key = f'button_{w}_{ow}|color_a_button'
+ output.alpha_composite(recolored_images[recolored_key], (267,114))
+ elif A ==1 :
+ recolored_key = f'button_filled_{w}_{ow}|color_a_button'
+ output.alpha_composite(recolored_images[recolored_key], (267,114))
+
+ if B == 0:
+ recolored_key = f'shoulder_{w}_{ow}|color_shoulder_right'
+ output.alpha_composite(recolored_images[recolored_key], (236, 46))
+ elif B ==1 :
+ recolored_key = f'shoulder_filled_{w}_{ow}|color_shoulder_right'
+ output.alpha_composite(recolored_images[recolored_key], (236,46))
+
+ if L == 0:
+ recolored_key = f'shoulder_{w}_{ow}|color_shoulder_left'
+ output.alpha_composite(recolored_images[recolored_key], (56,46))
+ elif L ==1 :
+ recolored_key = f'shoulder_filled_{w}_{ow}|color_shoulder_left'
+ output.alpha_composite(recolored_images[recolored_key], (56,46))
+
+ recolored_key = f'dpad_fill_{T}'
+ output.alpha_composite(recolored_images[recolored_key], (57,107))
+
+ recolored_key = f'dpad_{w}_{ow}|color_dpad'
+ output.alpha_composite(recolored_images[recolored_key], (47,97))
+
+
+ recolored_key = f'analog_base_{w}_{ow}|color_analog'
+ output.alpha_composite(recolored_images[recolored_key], (152,94))
+
+ recolored_key = f'analog_outer_{w}_{ow}|color_analog'
+ output.alpha_composite(recolored_images[recolored_key], (155 + X * 3,97 - Y * 3))
+
+ if config_id.getboolean('draw_box'):
+ recolored_key = f'analog_bg_part1_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 34 + X * 3,97 + 20 - Y * 3))
+ recolored_key = f'analog_bg_part2_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 27 + X * 3,97 + 25 - Y * 3))
+ recolored_key = f'analog_bg_part3_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 21 + X * 3,97 + 31 - Y * 3))
+ recolored_key = f'analog_bg_part4_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 27 + X * 3,97 + 64 - Y * 3))
+ recolored_key = f'analog_bg_part5_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 34 + X * 3,97 + 70 - Y * 3))
+
+ else:
+ recolored_key = f'analog_part1_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 34 + X * 3,97 + 20 - Y * 3))
+ recolored_key = f'analog_part2_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 27 + X * 3,97 + 25 - Y * 3))
+ recolored_key = f'analog_part3_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 21 + X * 3,97 + 31 - Y * 3))
+ recolored_key = f'analog_part4_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 27 + X * 3,97 + 64 - Y * 3))
+ recolored_key = f'analog_part5_{w}_{ow}|color_analog'
+ output.paste(recolored_images[recolored_key], (155 + 34 + X * 3,97 + 70 - Y * 3))
+
+ scaling = config_id.getfloat('scaling')
+ if config_id.getboolean('draw_stick_text'):
+ stick_text_size = config_id.getint('stick_text_size')
+ ID = ImageDraw.Draw(output)
+ text = f'({"+" if X>0 else (" " if X==0 else '')}{X},{"+" if Y>0 else (" " if Y==0 else '')}{Y})'
+ ID.text((132 + round(36 - stick_text_size)*scaling, 198 + (36 - stick_text_size)//scaling), text, font = font, fill = color_stick_text, stroke_width = 3 if ow >= 3 else 2, stroke_fill = (0,0,0))
+
+ if scaling != 1.0:
+ resample_filter = common.get_resampler(config_id.get('scaling_option'))
+ output = output.resize((round(400*scaling), round(250*scaling)), resample_filter)
+
+ for filtre in common.get_filter_list(config_id.get('special_effects')):
+ output = output.filter(filtre)
+
+ return output
+
+def add_input_display(image, frame_dict, config, font_folder, recolored_images, w, ow):
+ state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter'])
+ stick_text_size = config['Input display'].getint('stick_text_size')
+ font = ImageFont.truetype(os.path.join(font_folder, 'CONSOLA.TTF'), stick_text_size)
+ input_display = make_input_display(frame_dict['input'], config['Input display'], font, recolored_images, w, ow)
+ top_left_text = config['Input display'].get('top_left').split(',')
+ top_left = round(float(top_left_text[0])*image.width), round(float(top_left_text[1])*image.height)
+
+
+ if config['Input display'].getboolean('fade_animation'):
+ input_display = common.fade_image_manually(input_display, frame_dict, config['Input display'])
+
+
+ if config['Input display'].getboolean('fly_animation'):
+
+ if state == 1 and state_counter <= 10:
+ return None
+
+ offset = common.fly_in(frame_dict, image.height)
+
+ if state == 4 and 192 < state_counter < 202:
+ image.alpha_composite(input_display, (round(top_left[0] - image.width*offset), top_left[1]))
+ return None
+
+ ITEM_POSITION = 381/1440
+ INPUT_DISPLAY_POSITION = (1 - float(top_left_text[1]))
+ height_correction = round((INPUT_DISPLAY_POSITION - ITEM_POSITION)*image.height)
+
+ if offset is not None:
+ if config['Input display'].get('fly_in_direction') == 'bottom':
+ image.alpha_composite(input_display, (top_left[0], image.height - height_correction - offset))
+ else:
+ image.alpha_composite(input_display, (top_left[0], top_left[1] - round(ITEM_POSITION*image.height) + offset))
+ else:
+ image.alpha_composite(input_display, top_left)
+ else:
+ image.alpha_composite(input_display, top_left)
+
diff --git a/scripts/FrameDumps/Extra/speed_display_util.py b/scripts/FrameDumps/Extra/speed_display_util.py
new file mode 100644
index 0000000..7f27e13
--- /dev/null
+++ b/scripts/FrameDumps/Extra/speed_display_util.py
@@ -0,0 +1,156 @@
+import moviepy
+import numpy as np
+from PIL import Image
+from PIL import ImageDraw
+from PIL import ImageFont
+import sys
+import os
+import time
+import subprocess
+import math
+
+from Extra import common
+
+def get_color(color_text):
+ n = int(color_text, 16)
+ l = []
+ for _ in range(4):
+ l.append(n%256)
+ n//=256
+ l.reverse()
+ return tuple(l)
+
+def rotate_point(point, angle, center):
+ angle *= -math.pi/180
+ x,y = point
+ x0, y0 = center
+ x -= x0
+ y -= y0
+ x,y = x*math.cos(angle) - y*math.sin(angle), x*math.sin(angle) + y*math.cos(angle)
+ x += x0
+ y += y0
+ return int(x),int(y)
+
+def add_arrow(im, startpoint, vector, color, width, outline_color, outline_width, radius):
+ arrow_image = Image.new('RGBA', (radius*2+1, radius*2+1), (255, 0, 0, 0))
+ ID = ImageDraw.Draw(arrow_image)
+ endpoint = (startpoint[0]+vector[0], startpoint[1]+vector[1])
+ t = 0.85
+ angle = 8
+ mid_point = (startpoint[0]+vector[0]*t, startpoint[1]+vector[1]*t)
+ left_point = rotate_point(mid_point, angle, startpoint)
+ right_point = rotate_point(mid_point, -angle, startpoint)
+ xy = [startpoint, endpoint]
+ lyr = [left_point, endpoint, right_point]
+ ID.line(xy, outline_color, outline_width+width, 'curve')
+ ID.line(xy, color, width, 'curve')
+ ID.line(lyr, outline_color, outline_width+width, 'curve')
+ ID.line(lyr, color, width, 'curve')
+
+ im.alpha_composite(arrow_image, (0,0))
+
+
+def normalize_speed(vec, cap, ratio):
+ magn = (vec[0]**2+vec[1]**2)**0.5
+ if magn > cap:
+ return vec[0]*ratio*cap/magn, vec[1]*ratio*cap/magn
+ else:
+ return vec[0]*ratio, vec[1]*ratio
+
+def add_speed_display(im, d, config):
+ c = config['Speed display']
+ state, state_counter = int(d['state']), int(d['state_counter'])
+ radius = c.getint('circle_radius')
+ circle_color = get_color(c.get('circle_color'))
+ circle_width = c.getint('circle_outline_width')
+ cirlce_border_color = get_color(c.get('circle_outline_color'))
+ center = (radius, radius)
+ yaw = float(d['yaw'])+180
+ speed_display_image = Image.new('RGBA', (radius*2+1, radius*2+1), (255, 0, 0, 0))
+
+ #draw circle
+ ID = ImageDraw.Draw(speed_display_image)
+ ID.circle(center, 0.95*radius, fill = circle_color, outline = cirlce_border_color, width = circle_width)
+
+ #draw axis
+ if c.getboolean('draw_axis'):
+ axis_color = get_color(c.get('axis_color'))
+ axis_width = c.getint('axis_width')
+ if c.getboolean('rotate_with_yaw'):
+ ID.line([rotate_point((0, radius), yaw, center), rotate_point((radius*2+1, radius), yaw, center)], axis_color, axis_width)
+ ID.line([rotate_point((radius, 0), yaw, center), rotate_point((radius, radius*2+1), yaw, center)], axis_color, axis_width)
+ else:
+ ID.line([center, rotate_point((radius, 0), -yaw, center)], axis_color, axis_width)
+ ID.line([center, rotate_point((0, radius), -yaw, center)], axis_color, axis_width)
+ ID.line([center, rotate_point((radius*2+1, radius), -yaw, center)], axis_color, axis_width)
+
+ #draw pieslice
+ if c.getboolean('draw_pieslice'):
+ angle = 10
+ pieslice_color = get_color(c.get('pieslice_color'))
+ if c.getboolean('rotate_with_yaw'):
+ ID.pieslice([(0.2*radius, 0.2*radius), (1.8*radius, 1.8*radius)], 270-angle, 270+angle, pieslice_color)
+ else:
+ ID.pieslice([(0.2*radius, 0.2*radius), (1.8*radius, 1.8*radius)], 270-angle+yaw, 270+angle+yaw, pieslice_color)
+
+ #Get speed vectors
+ speed_activated = []
+ for key in c.keys():
+ if len(key) > 5 and key[:4] == 'show' and key != 'show_speed_display' and c.getboolean(key):
+ speed_activated.append(key[5:])
+
+ correspondance_dict = {"speed" : (float(d['spd_x']), float(d['spd_z'])),
+ "iv" : (float(d['iv_x']), float(d['iv_z'])),
+ "ev" : (float(d['ev_x']), float(d['ev_z']))}
+
+ #Normalize to screen pixel size
+ top_speed = c.getint('cap_speed')
+ ratio = 0.95*radius/top_speed
+ speed_vec_activated = [normalize_speed(correspondance_dict[txt], top_speed, ratio) for txt in speed_activated]
+ if c.getboolean('rotate_with_yaw'):
+ for i in range(len(speed_vec_activated)):
+ speed_vec_activated[i] = rotate_point(speed_vec_activated[i], yaw, (0,0))
+
+ #draw arrows
+ arrow_width = c.getint('arrow_width')
+ arrow_outline_width = c.getint('arrow_outline_width')
+ arrow_outline_color = get_color(c.get('arrow_outline_color'))
+ for i in range(len(speed_vec_activated)):
+ color_key = 'color_'+speed_activated[i]
+ color = get_color(c.get(color_key))
+ spd_vec = speed_vec_activated[i]
+ add_arrow(speed_display_image, center, spd_vec, color, arrow_width, arrow_outline_color, arrow_outline_width, radius)
+
+ top_left_text = c.get('top_left').split(',')
+ top_left = round(float(top_left_text[0])*im.width), round(float(top_left_text[1])*im.height)
+
+
+ if c.getboolean('fade_animation'):
+ speed_display_image = common.fade_image_manually(speed_display_image, d, c)
+
+
+ if c.getboolean('fly_animation'):
+
+ if state == 1 and state_counter <= 10:
+ return None
+
+ offset = common.fly_in(d, im.height)
+
+ if state == 4 and 192 < state_counter < 202:
+ im.alpha_composite(speed_display_image, (round(top_left[0] - im.width*offset), top_left[1]))
+ return None
+
+ ITEM_POSITION = 381/1440
+ SPEED_DISPLAY_POSITION = (1 - float(top_left_text[1]))
+ height_correction = round((SPEED_DISPLAY_POSITION - ITEM_POSITION)*im.height)
+
+ if offset is not None:
+ if c.get('fly_in_direction') == 'bottom':
+ im.alpha_composite(speed_display_image, (top_left[0], im.height - height_correction - offset))
+ else:
+ im.alpha_composite(speed_display_image, (top_left[0], top_left[1] - round(ITEM_POSITION*im.height) + offset))
+ else:
+ im.alpha_composite(speed_display_image, top_left)
+ else:
+ im.alpha_composite(speed_display_image, top_left)
+
diff --git a/scripts/FrameDumps/Fonts/CONSOLA.TTF b/scripts/FrameDumps/Fonts/CONSOLA.TTF
new file mode 100644
index 0000000..556d2fd
Binary files /dev/null and b/scripts/FrameDumps/Fonts/CONSOLA.TTF differ
diff --git a/scripts/FrameDumps/Fonts/FOT-Rodin Pro EB.otf b/scripts/FrameDumps/Fonts/FOT-Rodin Pro EB.otf
new file mode 100644
index 0000000..c3f5bde
Binary files /dev/null and b/scripts/FrameDumps/Fonts/FOT-Rodin Pro EB.otf differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/+.png b/scripts/FrameDumps/Fonts/MKW_Font/+.png
new file mode 100644
index 0000000..6e31e77
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/+.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/-.png b/scripts/FrameDumps/Fonts/MKW_Font/-.png
new file mode 100644
index 0000000..aea97de
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/-.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/0.png b/scripts/FrameDumps/Fonts/MKW_Font/0.png
new file mode 100644
index 0000000..e26f641
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/0.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/1.png b/scripts/FrameDumps/Fonts/MKW_Font/1.png
new file mode 100644
index 0000000..a77835c
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/1.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/2.png b/scripts/FrameDumps/Fonts/MKW_Font/2.png
new file mode 100644
index 0000000..2ca405a
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/2.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/3.png b/scripts/FrameDumps/Fonts/MKW_Font/3.png
new file mode 100644
index 0000000..1604f0a
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/3.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/4.png b/scripts/FrameDumps/Fonts/MKW_Font/4.png
new file mode 100644
index 0000000..ffc73c4
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/4.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/5.png b/scripts/FrameDumps/Fonts/MKW_Font/5.png
new file mode 100644
index 0000000..13fd5d9
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/5.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/6.png b/scripts/FrameDumps/Fonts/MKW_Font/6.png
new file mode 100644
index 0000000..f92ab95
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/6.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/7.png b/scripts/FrameDumps/Fonts/MKW_Font/7.png
new file mode 100644
index 0000000..d6b12ed
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/7.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/8.png b/scripts/FrameDumps/Fonts/MKW_Font/8.png
new file mode 100644
index 0000000..9e7ca53
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/8.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/9.png b/scripts/FrameDumps/Fonts/MKW_Font/9.png
new file mode 100644
index 0000000..5249ba1
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/9.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/A.png b/scripts/FrameDumps/Fonts/MKW_Font/A.png
new file mode 100644
index 0000000..da22cbf
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/A.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/B.png b/scripts/FrameDumps/Fonts/MKW_Font/B.png
new file mode 100644
index 0000000..98c5cef
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/B.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/C.png b/scripts/FrameDumps/Fonts/MKW_Font/C.png
new file mode 100644
index 0000000..130d8cc
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/C.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/COLON.png b/scripts/FrameDumps/Fonts/MKW_Font/COLON.png
new file mode 100644
index 0000000..ef3ef1f
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/COLON.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/D.png b/scripts/FrameDumps/Fonts/MKW_Font/D.png
new file mode 100644
index 0000000..aca1bd1
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/D.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/E.png b/scripts/FrameDumps/Fonts/MKW_Font/E.png
new file mode 100644
index 0000000..996ae7c
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/E.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/F.png b/scripts/FrameDumps/Fonts/MKW_Font/F.png
new file mode 100644
index 0000000..5531483
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/F.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/G.png b/scripts/FrameDumps/Fonts/MKW_Font/G.png
new file mode 100644
index 0000000..73c5789
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/G.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/H.png b/scripts/FrameDumps/Fonts/MKW_Font/H.png
new file mode 100644
index 0000000..d4a62d2
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/H.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/I.png b/scripts/FrameDumps/Fonts/MKW_Font/I.png
new file mode 100644
index 0000000..7d7dacd
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/I.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/J.png b/scripts/FrameDumps/Fonts/MKW_Font/J.png
new file mode 100644
index 0000000..5d272cb
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/J.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/K.png b/scripts/FrameDumps/Fonts/MKW_Font/K.png
new file mode 100644
index 0000000..cbc75fc
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/K.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/L.png b/scripts/FrameDumps/Fonts/MKW_Font/L.png
new file mode 100644
index 0000000..329162c
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/L.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/M.png b/scripts/FrameDumps/Fonts/MKW_Font/M.png
new file mode 100644
index 0000000..6822244
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/M.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/N.png b/scripts/FrameDumps/Fonts/MKW_Font/N.png
new file mode 100644
index 0000000..d4e0df5
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/N.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/O.png b/scripts/FrameDumps/Fonts/MKW_Font/O.png
new file mode 100644
index 0000000..fefbf6b
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/O.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/P.png b/scripts/FrameDumps/Fonts/MKW_Font/P.png
new file mode 100644
index 0000000..92172a8
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/P.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/PERIOD.png b/scripts/FrameDumps/Fonts/MKW_Font/PERIOD.png
new file mode 100644
index 0000000..d3bb53e
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/PERIOD.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/Q.png b/scripts/FrameDumps/Fonts/MKW_Font/Q.png
new file mode 100644
index 0000000..13164a4
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/Q.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/R.png b/scripts/FrameDumps/Fonts/MKW_Font/R.png
new file mode 100644
index 0000000..3b5cf3c
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/R.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/S.png b/scripts/FrameDumps/Fonts/MKW_Font/S.png
new file mode 100644
index 0000000..597fac1
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/S.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/SLASH.png b/scripts/FrameDumps/Fonts/MKW_Font/SLASH.png
new file mode 100644
index 0000000..1dd1e3d
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/SLASH.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/SLASH_.png b/scripts/FrameDumps/Fonts/MKW_Font/SLASH_.png
new file mode 100644
index 0000000..a4d00a1
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/SLASH_.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/SLASH_smaller.png b/scripts/FrameDumps/Fonts/MKW_Font/SLASH_smaller.png
new file mode 100644
index 0000000..9c8747c
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/SLASH_smaller.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/T.png b/scripts/FrameDumps/Fonts/MKW_Font/T.png
new file mode 100644
index 0000000..237c0ac
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/T.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/U.png b/scripts/FrameDumps/Fonts/MKW_Font/U.png
new file mode 100644
index 0000000..dd4d3ab
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/U.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/V.png b/scripts/FrameDumps/Fonts/MKW_Font/V.png
new file mode 100644
index 0000000..7fa9b7c
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/V.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/W.png b/scripts/FrameDumps/Fonts/MKW_Font/W.png
new file mode 100644
index 0000000..ce8f706
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/W.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/X.png b/scripts/FrameDumps/Fonts/MKW_Font/X.png
new file mode 100644
index 0000000..1445631
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/X.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/Y.png b/scripts/FrameDumps/Fonts/MKW_Font/Y.png
new file mode 100644
index 0000000..152b3bb
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/Y.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/Z.png b/scripts/FrameDumps/Fonts/MKW_Font/Z.png
new file mode 100644
index 0000000..7b885d6
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/Z.png differ
diff --git a/scripts/FrameDumps/Fonts/MKW_Font/backup font upscaled x4.7z b/scripts/FrameDumps/Fonts/MKW_Font/backup font upscaled x4.7z
new file mode 100644
index 0000000..2d017ba
Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/backup font upscaled x4.7z differ
diff --git a/scripts/FrameDumps/Fonts/SMG2-Font.OTF b/scripts/FrameDumps/Fonts/SMG2-Font.OTF
new file mode 100644
index 0000000..3a00db0
Binary files /dev/null and b/scripts/FrameDumps/Fonts/SMG2-Font.OTF differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_3_2.png b/scripts/FrameDumps/Input_display/analog_base_3_2.png
new file mode 100644
index 0000000..d8af482
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_3_3.png b/scripts/FrameDumps/Input_display/analog_base_3_3.png
new file mode 100644
index 0000000..c2979d4
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_3_4.png b/scripts/FrameDumps/Input_display/analog_base_3_4.png
new file mode 100644
index 0000000..d432f0c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_4_2.png b/scripts/FrameDumps/Input_display/analog_base_4_2.png
new file mode 100644
index 0000000..386f31f
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_4_3.png b/scripts/FrameDumps/Input_display/analog_base_4_3.png
new file mode 100644
index 0000000..1f092b8
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_4_4.png b/scripts/FrameDumps/Input_display/analog_base_4_4.png
new file mode 100644
index 0000000..cf38b20
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_5_2.png b/scripts/FrameDumps/Input_display/analog_base_5_2.png
new file mode 100644
index 0000000..264de48
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_5_3.png b/scripts/FrameDumps/Input_display/analog_base_5_3.png
new file mode 100644
index 0000000..675ee1b
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_5_4.png b/scripts/FrameDumps/Input_display/analog_base_5_4.png
new file mode 100644
index 0000000..9c2590d
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_6_2.png b/scripts/FrameDumps/Input_display/analog_base_6_2.png
new file mode 100644
index 0000000..0c6e30b
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_6_3.png b/scripts/FrameDumps/Input_display/analog_base_6_3.png
new file mode 100644
index 0000000..decc1c8
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_6_4.png b/scripts/FrameDumps/Input_display/analog_base_6_4.png
new file mode 100644
index 0000000..4970d19
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_7_2.png b/scripts/FrameDumps/Input_display/analog_base_7_2.png
new file mode 100644
index 0000000..9df9d00
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_base_7_3.png b/scripts/FrameDumps/Input_display/analog_base_7_3.png
new file mode 100644
index 0000000..6ecc70a
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_3_2.png
new file mode 100644
index 0000000..c40c8cd
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_3_3.png
new file mode 100644
index 0000000..b431530
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part1_3_4.png
new file mode 100644
index 0000000..561401c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_4_2.png
new file mode 100644
index 0000000..d715d13
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_4_3.png
new file mode 100644
index 0000000..d57c043
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part1_4_4.png
new file mode 100644
index 0000000..d57c043
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_5_2.png
new file mode 100644
index 0000000..f490c99
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_5_3.png
new file mode 100644
index 0000000..0b51e7a
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part1_5_4.png
new file mode 100644
index 0000000..0b51e7a
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_6_2.png
new file mode 100644
index 0000000..0e3c316
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_6_3.png
new file mode 100644
index 0000000..0e3c316
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part1_6_4.png
new file mode 100644
index 0000000..0e3c316
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_7_2.png
new file mode 100644
index 0000000..f893e36
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_7_3.png
new file mode 100644
index 0000000..f893e36
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_3_2.png
new file mode 100644
index 0000000..3e1db1f
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_3_3.png
new file mode 100644
index 0000000..266193b
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part2_3_4.png
new file mode 100644
index 0000000..311eea2
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_4_2.png
new file mode 100644
index 0000000..32587a0
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_4_3.png
new file mode 100644
index 0000000..1658160
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part2_4_4.png
new file mode 100644
index 0000000..c687dba
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_5_2.png
new file mode 100644
index 0000000..b37f69d
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_5_3.png
new file mode 100644
index 0000000..eb58f0b
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part2_5_4.png
new file mode 100644
index 0000000..f9be293
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_6_2.png
new file mode 100644
index 0000000..60cd530
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_6_3.png
new file mode 100644
index 0000000..43763ec
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part2_6_4.png
new file mode 100644
index 0000000..aef69a6
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_7_2.png
new file mode 100644
index 0000000..4125f78
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_7_3.png
new file mode 100644
index 0000000..be66ebb
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_3_2.png
new file mode 100644
index 0000000..71476ea
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_3_3.png
new file mode 100644
index 0000000..bcd0384
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part3_3_4.png
new file mode 100644
index 0000000..6ec0a3f
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_4_2.png
new file mode 100644
index 0000000..2aff7ff
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_4_3.png
new file mode 100644
index 0000000..c54ebc6
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part3_4_4.png
new file mode 100644
index 0000000..d890334
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_5_2.png
new file mode 100644
index 0000000..d51dcf5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_5_3.png
new file mode 100644
index 0000000..a3a7a28
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part3_5_4.png
new file mode 100644
index 0000000..1210d39
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_6_2.png
new file mode 100644
index 0000000..ce0d50c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_6_3.png
new file mode 100644
index 0000000..fb2a3f2
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part3_6_4.png
new file mode 100644
index 0000000..c9b7469
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_7_2.png
new file mode 100644
index 0000000..225d364
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_7_3.png
new file mode 100644
index 0000000..ddea846
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_3_2.png
new file mode 100644
index 0000000..3bdb682
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_3_3.png
new file mode 100644
index 0000000..5c83cf7
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part4_3_4.png
new file mode 100644
index 0000000..3d26c90
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_4_2.png
new file mode 100644
index 0000000..8975ded
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_4_3.png
new file mode 100644
index 0000000..d3af557
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part4_4_4.png
new file mode 100644
index 0000000..7570183
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_5_2.png
new file mode 100644
index 0000000..30fd119
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_5_3.png
new file mode 100644
index 0000000..96a5aaf
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part4_5_4.png
new file mode 100644
index 0000000..684e415
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_6_2.png
new file mode 100644
index 0000000..4dd1079
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_6_3.png
new file mode 100644
index 0000000..09c4d36
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part4_6_4.png
new file mode 100644
index 0000000..1c30b5b
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_7_2.png
new file mode 100644
index 0000000..43f5e2c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_7_3.png
new file mode 100644
index 0000000..0288bfa
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_3_2.png
new file mode 100644
index 0000000..0a6ebec
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_3_3.png
new file mode 100644
index 0000000..2dff5f6
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part5_3_4.png
new file mode 100644
index 0000000..90b0c28
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_4_2.png
new file mode 100644
index 0000000..24fb90e
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_4_3.png
new file mode 100644
index 0000000..8aec737
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part5_4_4.png
new file mode 100644
index 0000000..8aec737
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_5_2.png
new file mode 100644
index 0000000..9d79f11
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_5_3.png
new file mode 100644
index 0000000..934b6ee
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part5_5_4.png
new file mode 100644
index 0000000..934b6ee
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_6_2.png
new file mode 100644
index 0000000..6428412
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_6_3.png
new file mode 100644
index 0000000..6428412
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part5_6_4.png
new file mode 100644
index 0000000..6428412
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_7_2.png
new file mode 100644
index 0000000..553bd59
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_7_3.png
new file mode 100644
index 0000000..553bd59
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_3_2.png b/scripts/FrameDumps/Input_display/analog_outer_3_2.png
new file mode 100644
index 0000000..b927db1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_3_3.png b/scripts/FrameDumps/Input_display/analog_outer_3_3.png
new file mode 100644
index 0000000..fb74c6c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_3_4.png b/scripts/FrameDumps/Input_display/analog_outer_3_4.png
new file mode 100644
index 0000000..0046f76
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_4_2.png b/scripts/FrameDumps/Input_display/analog_outer_4_2.png
new file mode 100644
index 0000000..460d7b9
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_4_3.png b/scripts/FrameDumps/Input_display/analog_outer_4_3.png
new file mode 100644
index 0000000..f5ce423
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_4_4.png b/scripts/FrameDumps/Input_display/analog_outer_4_4.png
new file mode 100644
index 0000000..0a14423
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_5_2.png b/scripts/FrameDumps/Input_display/analog_outer_5_2.png
new file mode 100644
index 0000000..8a85b8e
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_5_3.png b/scripts/FrameDumps/Input_display/analog_outer_5_3.png
new file mode 100644
index 0000000..cb7ad95
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_5_4.png b/scripts/FrameDumps/Input_display/analog_outer_5_4.png
new file mode 100644
index 0000000..8ed6727
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_6_2.png b/scripts/FrameDumps/Input_display/analog_outer_6_2.png
new file mode 100644
index 0000000..f996ae9
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_6_3.png b/scripts/FrameDumps/Input_display/analog_outer_6_3.png
new file mode 100644
index 0000000..6d635f9
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_6_4.png b/scripts/FrameDumps/Input_display/analog_outer_6_4.png
new file mode 100644
index 0000000..8787d78
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_7_2.png b/scripts/FrameDumps/Input_display/analog_outer_7_2.png
new file mode 100644
index 0000000..29523e8
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_outer_7_3.png b/scripts/FrameDumps/Input_display/analog_outer_7_3.png
new file mode 100644
index 0000000..04378cc
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_3_2.png b/scripts/FrameDumps/Input_display/analog_part1_3_2.png
new file mode 100644
index 0000000..a804cff
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_3_3.png b/scripts/FrameDumps/Input_display/analog_part1_3_3.png
new file mode 100644
index 0000000..e06724d
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_3_4.png b/scripts/FrameDumps/Input_display/analog_part1_3_4.png
new file mode 100644
index 0000000..077e986
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_4_2.png b/scripts/FrameDumps/Input_display/analog_part1_4_2.png
new file mode 100644
index 0000000..e7e3006
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_4_3.png b/scripts/FrameDumps/Input_display/analog_part1_4_3.png
new file mode 100644
index 0000000..6e54869
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_4_4.png b/scripts/FrameDumps/Input_display/analog_part1_4_4.png
new file mode 100644
index 0000000..6e54869
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_5_2.png b/scripts/FrameDumps/Input_display/analog_part1_5_2.png
new file mode 100644
index 0000000..831fc8b
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_5_3.png b/scripts/FrameDumps/Input_display/analog_part1_5_3.png
new file mode 100644
index 0000000..d267ff1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_5_4.png b/scripts/FrameDumps/Input_display/analog_part1_5_4.png
new file mode 100644
index 0000000..d267ff1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_6_2.png b/scripts/FrameDumps/Input_display/analog_part1_6_2.png
new file mode 100644
index 0000000..3414326
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_6_3.png b/scripts/FrameDumps/Input_display/analog_part1_6_3.png
new file mode 100644
index 0000000..3414326
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_6_4.png b/scripts/FrameDumps/Input_display/analog_part1_6_4.png
new file mode 100644
index 0000000..3414326
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_7_2.png b/scripts/FrameDumps/Input_display/analog_part1_7_2.png
new file mode 100644
index 0000000..387d5b5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part1_7_3.png b/scripts/FrameDumps/Input_display/analog_part1_7_3.png
new file mode 100644
index 0000000..387d5b5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_3_2.png b/scripts/FrameDumps/Input_display/analog_part2_3_2.png
new file mode 100644
index 0000000..96b7cc8
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_3_3.png b/scripts/FrameDumps/Input_display/analog_part2_3_3.png
new file mode 100644
index 0000000..2cc1590
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_3_4.png b/scripts/FrameDumps/Input_display/analog_part2_3_4.png
new file mode 100644
index 0000000..a82bb08
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_4_2.png b/scripts/FrameDumps/Input_display/analog_part2_4_2.png
new file mode 100644
index 0000000..e7ba201
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_4_3.png b/scripts/FrameDumps/Input_display/analog_part2_4_3.png
new file mode 100644
index 0000000..ce1f2ef
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_4_4.png b/scripts/FrameDumps/Input_display/analog_part2_4_4.png
new file mode 100644
index 0000000..ba5c3b2
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_5_2.png b/scripts/FrameDumps/Input_display/analog_part2_5_2.png
new file mode 100644
index 0000000..1f7f92f
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_5_3.png b/scripts/FrameDumps/Input_display/analog_part2_5_3.png
new file mode 100644
index 0000000..e7b9915
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_5_4.png b/scripts/FrameDumps/Input_display/analog_part2_5_4.png
new file mode 100644
index 0000000..09a53a5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_6_2.png b/scripts/FrameDumps/Input_display/analog_part2_6_2.png
new file mode 100644
index 0000000..f687f80
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_6_3.png b/scripts/FrameDumps/Input_display/analog_part2_6_3.png
new file mode 100644
index 0000000..6766005
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_6_4.png b/scripts/FrameDumps/Input_display/analog_part2_6_4.png
new file mode 100644
index 0000000..422effd
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_7_2.png b/scripts/FrameDumps/Input_display/analog_part2_7_2.png
new file mode 100644
index 0000000..bf661c9
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part2_7_3.png b/scripts/FrameDumps/Input_display/analog_part2_7_3.png
new file mode 100644
index 0000000..808ab02
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_3_2.png b/scripts/FrameDumps/Input_display/analog_part3_3_2.png
new file mode 100644
index 0000000..f909cec
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_3_3.png b/scripts/FrameDumps/Input_display/analog_part3_3_3.png
new file mode 100644
index 0000000..c33101c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_3_4.png b/scripts/FrameDumps/Input_display/analog_part3_3_4.png
new file mode 100644
index 0000000..20eba53
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_4_2.png b/scripts/FrameDumps/Input_display/analog_part3_4_2.png
new file mode 100644
index 0000000..d9cabb3
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_4_3.png b/scripts/FrameDumps/Input_display/analog_part3_4_3.png
new file mode 100644
index 0000000..fd2bb0f
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_4_4.png b/scripts/FrameDumps/Input_display/analog_part3_4_4.png
new file mode 100644
index 0000000..ce8308a
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_5_2.png b/scripts/FrameDumps/Input_display/analog_part3_5_2.png
new file mode 100644
index 0000000..0ca882c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_5_3.png b/scripts/FrameDumps/Input_display/analog_part3_5_3.png
new file mode 100644
index 0000000..7b078a7
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_5_4.png b/scripts/FrameDumps/Input_display/analog_part3_5_4.png
new file mode 100644
index 0000000..8fb4225
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_6_2.png b/scripts/FrameDumps/Input_display/analog_part3_6_2.png
new file mode 100644
index 0000000..870d12b
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_6_3.png b/scripts/FrameDumps/Input_display/analog_part3_6_3.png
new file mode 100644
index 0000000..a6d8db1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_6_4.png b/scripts/FrameDumps/Input_display/analog_part3_6_4.png
new file mode 100644
index 0000000..98a5f04
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_7_2.png b/scripts/FrameDumps/Input_display/analog_part3_7_2.png
new file mode 100644
index 0000000..7eab2ad
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part3_7_3.png b/scripts/FrameDumps/Input_display/analog_part3_7_3.png
new file mode 100644
index 0000000..06802c4
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_3_2.png b/scripts/FrameDumps/Input_display/analog_part4_3_2.png
new file mode 100644
index 0000000..9122ac0
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_3_3.png b/scripts/FrameDumps/Input_display/analog_part4_3_3.png
new file mode 100644
index 0000000..9cec05c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_3_4.png b/scripts/FrameDumps/Input_display/analog_part4_3_4.png
new file mode 100644
index 0000000..377a391
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_4_2.png b/scripts/FrameDumps/Input_display/analog_part4_4_2.png
new file mode 100644
index 0000000..fd8020f
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_4_3.png b/scripts/FrameDumps/Input_display/analog_part4_4_3.png
new file mode 100644
index 0000000..f7243c7
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_4_4.png b/scripts/FrameDumps/Input_display/analog_part4_4_4.png
new file mode 100644
index 0000000..fa6fe31
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_5_2.png b/scripts/FrameDumps/Input_display/analog_part4_5_2.png
new file mode 100644
index 0000000..c5880f1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_5_3.png b/scripts/FrameDumps/Input_display/analog_part4_5_3.png
new file mode 100644
index 0000000..67f89cb
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_5_4.png b/scripts/FrameDumps/Input_display/analog_part4_5_4.png
new file mode 100644
index 0000000..161df32
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_6_2.png b/scripts/FrameDumps/Input_display/analog_part4_6_2.png
new file mode 100644
index 0000000..53a5ab9
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_6_3.png b/scripts/FrameDumps/Input_display/analog_part4_6_3.png
new file mode 100644
index 0000000..01b193d
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_6_4.png b/scripts/FrameDumps/Input_display/analog_part4_6_4.png
new file mode 100644
index 0000000..8da0547
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_7_2.png b/scripts/FrameDumps/Input_display/analog_part4_7_2.png
new file mode 100644
index 0000000..99a51c5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part4_7_3.png b/scripts/FrameDumps/Input_display/analog_part4_7_3.png
new file mode 100644
index 0000000..fc7c20d
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_3_2.png b/scripts/FrameDumps/Input_display/analog_part5_3_2.png
new file mode 100644
index 0000000..ddb9efe
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_3_3.png b/scripts/FrameDumps/Input_display/analog_part5_3_3.png
new file mode 100644
index 0000000..0114be5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_3_4.png b/scripts/FrameDumps/Input_display/analog_part5_3_4.png
new file mode 100644
index 0000000..303cc03
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_4_2.png b/scripts/FrameDumps/Input_display/analog_part5_4_2.png
new file mode 100644
index 0000000..2695f7c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_4_3.png b/scripts/FrameDumps/Input_display/analog_part5_4_3.png
new file mode 100644
index 0000000..43a4de4
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_4_4.png b/scripts/FrameDumps/Input_display/analog_part5_4_4.png
new file mode 100644
index 0000000..43a4de4
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_5_2.png b/scripts/FrameDumps/Input_display/analog_part5_5_2.png
new file mode 100644
index 0000000..35a647c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_5_3.png b/scripts/FrameDumps/Input_display/analog_part5_5_3.png
new file mode 100644
index 0000000..a1f678c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_5_4.png b/scripts/FrameDumps/Input_display/analog_part5_5_4.png
new file mode 100644
index 0000000..a1f678c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_6_2.png b/scripts/FrameDumps/Input_display/analog_part5_6_2.png
new file mode 100644
index 0000000..8621cfc
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_6_3.png b/scripts/FrameDumps/Input_display/analog_part5_6_3.png
new file mode 100644
index 0000000..8621cfc
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_6_4.png b/scripts/FrameDumps/Input_display/analog_part5_6_4.png
new file mode 100644
index 0000000..8621cfc
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_7_2.png b/scripts/FrameDumps/Input_display/analog_part5_7_2.png
new file mode 100644
index 0000000..e7bd69e
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/analog_part5_7_3.png b/scripts/FrameDumps/Input_display/analog_part5_7_3.png
new file mode 100644
index 0000000..e7bd69e
Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/background.png b/scripts/FrameDumps/Input_display/background.png
new file mode 100644
index 0000000..66669b5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/background.png differ
diff --git a/scripts/FrameDumps/Input_display/button_3_2.png b/scripts/FrameDumps/Input_display/button_3_2.png
new file mode 100644
index 0000000..e618781
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_3_3.png b/scripts/FrameDumps/Input_display/button_3_3.png
new file mode 100644
index 0000000..f59ffa4
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/button_3_4.png b/scripts/FrameDumps/Input_display/button_3_4.png
new file mode 100644
index 0000000..8ae52c8
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/button_4_2.png b/scripts/FrameDumps/Input_display/button_4_2.png
new file mode 100644
index 0000000..9f649e1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_4_3.png b/scripts/FrameDumps/Input_display/button_4_3.png
new file mode 100644
index 0000000..3347cc2
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/button_4_4.png b/scripts/FrameDumps/Input_display/button_4_4.png
new file mode 100644
index 0000000..69dd670
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/button_5_2.png b/scripts/FrameDumps/Input_display/button_5_2.png
new file mode 100644
index 0000000..6a9b52c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_5_3.png b/scripts/FrameDumps/Input_display/button_5_3.png
new file mode 100644
index 0000000..ac2f974
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/button_5_4.png b/scripts/FrameDumps/Input_display/button_5_4.png
new file mode 100644
index 0000000..69c70a1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/button_6_2.png b/scripts/FrameDumps/Input_display/button_6_2.png
new file mode 100644
index 0000000..20d2da1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_6_3.png b/scripts/FrameDumps/Input_display/button_6_3.png
new file mode 100644
index 0000000..c4144a1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/button_6_4.png b/scripts/FrameDumps/Input_display/button_6_4.png
new file mode 100644
index 0000000..60937b1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/button_7_2.png b/scripts/FrameDumps/Input_display/button_7_2.png
new file mode 100644
index 0000000..a6d7358
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_7_3.png b/scripts/FrameDumps/Input_display/button_7_3.png
new file mode 100644
index 0000000..1387d34
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_3_2.png b/scripts/FrameDumps/Input_display/button_filled_3_2.png
new file mode 100644
index 0000000..9941476
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_3_3.png b/scripts/FrameDumps/Input_display/button_filled_3_3.png
new file mode 100644
index 0000000..3b937cb
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_3_4.png b/scripts/FrameDumps/Input_display/button_filled_3_4.png
new file mode 100644
index 0000000..6936f39
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_4_2.png b/scripts/FrameDumps/Input_display/button_filled_4_2.png
new file mode 100644
index 0000000..09b7837
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_4_3.png b/scripts/FrameDumps/Input_display/button_filled_4_3.png
new file mode 100644
index 0000000..d8c3e68
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_4_4.png b/scripts/FrameDumps/Input_display/button_filled_4_4.png
new file mode 100644
index 0000000..235d61c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_5_2.png b/scripts/FrameDumps/Input_display/button_filled_5_2.png
new file mode 100644
index 0000000..8fa72cd
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_5_3.png b/scripts/FrameDumps/Input_display/button_filled_5_3.png
new file mode 100644
index 0000000..cc95b3e
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_5_4.png b/scripts/FrameDumps/Input_display/button_filled_5_4.png
new file mode 100644
index 0000000..f482d38
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_6_2.png b/scripts/FrameDumps/Input_display/button_filled_6_2.png
new file mode 100644
index 0000000..2fc0bb0
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_6_3.png b/scripts/FrameDumps/Input_display/button_filled_6_3.png
new file mode 100644
index 0000000..b28c03b
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_6_4.png b/scripts/FrameDumps/Input_display/button_filled_6_4.png
new file mode 100644
index 0000000..eb03ad0
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_7_2.png b/scripts/FrameDumps/Input_display/button_filled_7_2.png
new file mode 100644
index 0000000..8d9faba
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/button_filled_7_3.png b/scripts/FrameDumps/Input_display/button_filled_7_3.png
new file mode 100644
index 0000000..1294996
Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_3_2.png b/scripts/FrameDumps/Input_display/dpad_3_2.png
new file mode 100644
index 0000000..104a5a5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_3_3.png b/scripts/FrameDumps/Input_display/dpad_3_3.png
new file mode 100644
index 0000000..c40c967
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_3_4.png b/scripts/FrameDumps/Input_display/dpad_3_4.png
new file mode 100644
index 0000000..95e2d5a
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_4_2.png b/scripts/FrameDumps/Input_display/dpad_4_2.png
new file mode 100644
index 0000000..72a0569
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_4_3.png b/scripts/FrameDumps/Input_display/dpad_4_3.png
new file mode 100644
index 0000000..c6272bb
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_4_4.png b/scripts/FrameDumps/Input_display/dpad_4_4.png
new file mode 100644
index 0000000..3e7c640
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_5_2.png b/scripts/FrameDumps/Input_display/dpad_5_2.png
new file mode 100644
index 0000000..a5a62e9
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_5_3.png b/scripts/FrameDumps/Input_display/dpad_5_3.png
new file mode 100644
index 0000000..fe5d31e
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_5_4.png b/scripts/FrameDumps/Input_display/dpad_5_4.png
new file mode 100644
index 0000000..3c4c629
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_6_2.png b/scripts/FrameDumps/Input_display/dpad_6_2.png
new file mode 100644
index 0000000..830d2fc
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_6_3.png b/scripts/FrameDumps/Input_display/dpad_6_3.png
new file mode 100644
index 0000000..ad71820
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_6_4.png b/scripts/FrameDumps/Input_display/dpad_6_4.png
new file mode 100644
index 0000000..bb2981f
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_7_2.png b/scripts/FrameDumps/Input_display/dpad_7_2.png
new file mode 100644
index 0000000..9f58a5d
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_7_3.png b/scripts/FrameDumps/Input_display/dpad_7_3.png
new file mode 100644
index 0000000..78260f7
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_fill_0.png b/scripts/FrameDumps/Input_display/dpad_fill_0.png
new file mode 100644
index 0000000..aa774af
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_0.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_fill_1.png b/scripts/FrameDumps/Input_display/dpad_fill_1.png
new file mode 100644
index 0000000..7e109b7
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_1.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_fill_2.png b/scripts/FrameDumps/Input_display/dpad_fill_2.png
new file mode 100644
index 0000000..2b7738f
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_2.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_fill_3.png b/scripts/FrameDumps/Input_display/dpad_fill_3.png
new file mode 100644
index 0000000..8efae37
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_3.png differ
diff --git a/scripts/FrameDumps/Input_display/dpad_fill_4.png b/scripts/FrameDumps/Input_display/dpad_fill_4.png
new file mode 100644
index 0000000..eb089d0
Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_4.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_3_2.png b/scripts/FrameDumps/Input_display/shoulder_3_2.png
new file mode 100644
index 0000000..32b20bc
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_3_3.png b/scripts/FrameDumps/Input_display/shoulder_3_3.png
new file mode 100644
index 0000000..71616ed
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_3_4.png b/scripts/FrameDumps/Input_display/shoulder_3_4.png
new file mode 100644
index 0000000..4a5950d
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_4_2.png b/scripts/FrameDumps/Input_display/shoulder_4_2.png
new file mode 100644
index 0000000..d0a2061
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_4_3.png b/scripts/FrameDumps/Input_display/shoulder_4_3.png
new file mode 100644
index 0000000..2095ba7
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_4_4.png b/scripts/FrameDumps/Input_display/shoulder_4_4.png
new file mode 100644
index 0000000..6b2a5a9
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_5_2.png b/scripts/FrameDumps/Input_display/shoulder_5_2.png
new file mode 100644
index 0000000..9bfcf7c
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_5_3.png b/scripts/FrameDumps/Input_display/shoulder_5_3.png
new file mode 100644
index 0000000..74fbc7f
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_5_4.png b/scripts/FrameDumps/Input_display/shoulder_5_4.png
new file mode 100644
index 0000000..4ffb27d
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_6_2.png b/scripts/FrameDumps/Input_display/shoulder_6_2.png
new file mode 100644
index 0000000..8ced5b6
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_6_3.png b/scripts/FrameDumps/Input_display/shoulder_6_3.png
new file mode 100644
index 0000000..7971f20
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_6_4.png b/scripts/FrameDumps/Input_display/shoulder_6_4.png
new file mode 100644
index 0000000..ab8e1e6
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_7_2.png b/scripts/FrameDumps/Input_display/shoulder_7_2.png
new file mode 100644
index 0000000..cc6ea9e
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_7_3.png b/scripts/FrameDumps/Input_display/shoulder_7_3.png
new file mode 100644
index 0000000..5d0debb
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_3_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_3_2.png
new file mode 100644
index 0000000..cc177f1
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_3_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_3_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_3_3.png
new file mode 100644
index 0000000..9042ce6
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_3_3.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_3_4.png b/scripts/FrameDumps/Input_display/shoulder_filled_3_4.png
new file mode 100644
index 0000000..a0cf6cb
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_3_4.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_4_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_4_2.png
new file mode 100644
index 0000000..53df565
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_4_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_4_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_4_3.png
new file mode 100644
index 0000000..1a3febf
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_4_3.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_4_4.png b/scripts/FrameDumps/Input_display/shoulder_filled_4_4.png
new file mode 100644
index 0000000..f748f94
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_4_4.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_5_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_5_2.png
new file mode 100644
index 0000000..3049793
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_5_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_5_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_5_3.png
new file mode 100644
index 0000000..5a7a457
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_5_3.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_5_4.png b/scripts/FrameDumps/Input_display/shoulder_filled_5_4.png
new file mode 100644
index 0000000..d442ad7
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_5_4.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_6_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_6_2.png
new file mode 100644
index 0000000..d7973f4
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_6_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_6_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_6_3.png
new file mode 100644
index 0000000..5007770
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_6_3.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_6_4.png b/scripts/FrameDumps/Input_display/shoulder_filled_6_4.png
new file mode 100644
index 0000000..107d78d
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_6_4.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_7_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_7_2.png
new file mode 100644
index 0000000..f21feb5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_7_2.png differ
diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_7_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_7_3.png
new file mode 100644
index 0000000..bd8353a
Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_7_3.png differ
diff --git a/scripts/FrameDumps/Input_display/tutorial/composition_normal.png b/scripts/FrameDumps/Input_display/tutorial/composition_normal.png
new file mode 100644
index 0000000..5338900
Binary files /dev/null and b/scripts/FrameDumps/Input_display/tutorial/composition_normal.png differ
diff --git a/scripts/FrameDumps/Input_display/tutorial/composition_shoulder_aligned.png b/scripts/FrameDumps/Input_display/tutorial/composition_shoulder_aligned.png
new file mode 100644
index 0000000..0ab88b5
Binary files /dev/null and b/scripts/FrameDumps/Input_display/tutorial/composition_shoulder_aligned.png differ
diff --git a/scripts/FrameDumps/delete_older_dumps.py b/scripts/FrameDumps/delete_older_dumps.py
new file mode 100644
index 0000000..740a57a
--- /dev/null
+++ b/scripts/FrameDumps/delete_older_dumps.py
@@ -0,0 +1,30 @@
+import sys
+import os
+
+'''
+This script simply delete all audio (.wav only) and
+video (.avi only) your Dump/Frames and Dump/Audio folder
+Don't run this script before encoding !
+'''
+
+
+print('Are you sure you want to delete all your dumps ?')
+answer = input('yes/no\n')
+if answer in ['yes', 'y', 'ye', 'YES', 'Y', 'YE']:
+ with open('dump_info.txt', 'r') as f:
+ dump_folder = f.readline().strip('\n')
+ frame_dump_path = os.path.join(dump_folder, 'Frames')
+ audio_dump_path = os.path.join(dump_folder, 'Audio')
+ for filename in os.listdir(frame_dump_path):
+ if filename[-4:] == '.avi':
+ print(f'Now deleting {filename}')
+ os.remove(os.path.join(frame_dump_path, filename))
+
+ for filename in os.listdir(audio_dump_path):
+ if filename[-4:] == '.wav':
+ print(f'Now deleting {filename}')
+ os.remove(os.path.join(audio_dump_path, filename))
+
+ print('All dumps have been deleted.')
+
+input("Press Enter to exit")
diff --git a/scripts/FrameDumps/encoder.py b/scripts/FrameDumps/encoder.py
new file mode 100644
index 0000000..93c2398
--- /dev/null
+++ b/scripts/FrameDumps/encoder.py
@@ -0,0 +1,370 @@
+import moviepy
+import numpy as np
+from PIL import Image
+from PIL import ImageDraw
+from PIL import ImageFont
+from PIL import ImageFilter
+import sys
+import os
+import time
+import subprocess
+import math
+
+from Extra import config_util
+from Extra import input_display_util
+from Extra import infodisplay_util
+from Extra import speed_display_util
+from Extra import author_display_util
+from Extra import extradisplay_util
+from Extra import common
+
+time.sleep(1) #waits a bit to make sure the framedump metadata is written
+
+current_folder = os.path.dirname(sys.argv[0])
+extra_display_folder = os.path.dirname(sys.argv[0])
+
+
+#Initializing MKW Font images
+mkw_font_folder = os.path.join(current_folder, 'Fonts', 'MKW_Font')
+MKW_FONT_IMG = {}
+for filename in os.listdir(mkw_font_folder):
+ if filename[-4:] == '.png':
+ letter = filename[:-4]
+ if letter == 'SLASH_':
+ letter = '/'
+ if letter == 'SLASH':
+ letter = '&'
+ elif letter == 'COLON':
+ letter = ':'
+ elif letter == 'PERIOD':
+ letter = '.'
+ MKW_FONT_IMG[letter] = Image.open(os.path.join(mkw_font_folder, filename))
+
+
+def make_dict(filetext):
+ d = {}
+ for line in filetext.split('\n'):
+ temp = line.split(':')
+ if len(temp) == 2:
+ d[temp[0]] = temp[1]
+ return d
+
+
+def transform_image(image, i=-1):
+ #Resample algorithm
+ resample_filter = config['Encoding options'].get('scaling_option')
+ resampler = common.get_resampler(resample_filter)
+ resize_style = config['Encoding options'].get('resize_style')
+ target_height = config['Encoding options'].getint('resize_resolution')
+ target_width = round(target_height*16/9)
+ target_resolution = (target_width, target_height)
+
+ #Convert from numpy array to PIL Image
+ image = Image.fromarray(image)
+
+ #Resize the image
+ if resize_style in ['stretch', 'Stretch', 'STRETCH']:
+ image = image.resize(target_resolution, resampler)
+ if resize_style in ['crop', 'Crop', 'CROP']:
+ dump_width, dump_height = image.size
+ if dump_width*9 >= dump_height*16:
+ ratio = target_height/dump_height
+ image = image.resize((round(dump_width*ratio), round(dump_height*ratio)), resampler)
+ image = image.crop(((image.width-target_width)//2, 0, target_width+(image.width-target_width)//2, target_height))
+ else:
+ ratio = target_width/dump_width
+ image = image.resize((round(dump_width*ratio), round(dump_height*ratio)), resampler)
+ image = image.crop(((0, (image.height-target_height)//2, target_width, target_height+image.height-target_height)//2))
+ if resize_style in ['fill', 'Fill', 'FILL']:
+ dump_width, dump_height = image.size
+ if dump_width*9 >= dump_height*16:
+ ratio = target_width/dump_width
+ image = image.resize((round(dump_width*ratio), round(dump_height*ratio)), resampler)
+ black_background = Image.new('RGB', target_resolution, (0,0,0))
+ black_background.paste(image, (0, (target_height-image.height)//2))
+ image = black_background
+ else:
+ ratio = target_height/dump_height
+ image = image.resize((round(dump_width*ratio), round(dump_height*ratio)), resampler)
+ black_background = Image.new('RGB', target_resolution, (0,0,0))
+ black_background.paste(image, ((target_width-image.width)//2), 0)
+ image = black_background
+
+
+ image = image.convert("RGBA")
+ font_folder = os.path.join(current_folder, 'Fonts')
+
+ if os.path.isfile(os.path.join(extra_display_folder, 'RAM_data', f'{i}.txt')):
+ with open(os.path.join(extra_display_folder, 'RAM_data', f'{i}.txt'), 'r') as f:
+ t = f.read()
+ if len(t)>1:
+ frame_dict = make_dict(t)
+
+ if config['Input display'].getboolean('show_input_display') and common.is_layer_active(frame_dict, config['Input display']) :
+ input_display_util.add_input_display(image, frame_dict, config, font_folder, recolored_images, w, ow)
+ if config['Speed display'].getboolean('show_speed_display') and common.is_layer_active(frame_dict, config['Speed display']) :
+ speed_display_util.add_speed_display(image, frame_dict, config)
+ if config['Author display'].getboolean('show_author_display') and common.is_layer_active(frame_dict, config['Author display']) :
+ author_display_util.add_author_display(image, frame_dict, config, current_folder, raw_author_list, author_dict)
+ for infodisplay_name in infodisplay_layers:
+ if common.is_layer_active(frame_dict, config[infodisplay_name]):
+ infodisplay_util.add_infodisplay(image, frame_dict, config[infodisplay_name], font_folder, scaled_fonts_dict)
+
+ for section in extra_display_layers:
+ if config[section].getboolean('show_extra_display'):
+ file_ext = config[section].get("file_extension")
+ filename = f'{i}.{file_ext}'
+ if os.path.isfile(os.path.join(extra_display_folder, 'RAM_data', filename)):
+ with open(os.path.join(extra_display_folder, 'RAM_data', filename), 'r') as f:
+ text = f.read()
+ extradisplay_util.add_extradisplay(image, text, config[section], font_folder, MKW_FONT_IMG)
+
+ filters = common.get_filter_list(config['Encoding options'].get('special_effects'))
+ for filtre in filters:
+ image = image.filter(filtre)
+ return np.array(image.convert("RGB"))
+
+def transform_video(get_frame, t):
+ image = get_frame(t)
+ i = round(t*59.94) -1
+ print('')
+ return transform_image(image, i)
+
+def main():
+ extra_display_folder = os.path.dirname(sys.argv[0])
+ current_folder = os.path.dirname(sys.argv[0])
+
+ if len(sys.argv) > 1:
+ config_filename = sys.argv[1]
+ else:
+ config_filename = os.path.join(extra_display_folder, 'config.ini')
+ if not os.path.isfile(config_filename):
+ config_util.create_config(config_filename)
+
+
+ global config
+ config = config_util.get_config(config_filename)
+
+
+ global w
+ global ow
+ w = config['Input display'].getint('width')
+ ow = config['Input display'].getint('outline_width')
+ bg = config['Input display'].getboolean('draw_box')
+ #Initializing input display images
+ input_display_folder = os.path.join(current_folder, 'Input_display')
+ INPUT_DISPLAY_IMG = {}
+
+ for filename in os.listdir(input_display_folder):
+ is_w_ow = (filename[-7:] == f'{w}_{ow}.png')
+ aligns_with_bg = ('bg' in filename) == bg
+ if is_w_ow and (not 'part' in filename) or is_w_ow and aligns_with_bg or filename == 'background.png' or filename[:9] == 'dpad_fill':
+ INPUT_DISPLAY_IMG[filename[:-4]] = Image.open(os.path.join(current_folder, 'Input_display', filename))
+
+
+ color_dict = {'color_shoulder_left': common.get_color(config['Input display'].get('color_shoulder_left')),
+ 'color_shoulder_right': common.get_color(config['Input display'].get('color_shoulder_right')),
+ 'color_dpad': common.get_color(config['Input display'].get('color_dpad')),
+ 'color_analog': common.get_color(config['Input display'].get('color_analog')),
+ 'color_a_button': common.get_color(config['Input display'].get('color_a_button')),
+ 'color_stick_text': common.get_color(config['Input display'].get('color_stick_text')),}
+
+ base_keys = {
+ 'shoulder': ['color_shoulder_left', 'color_shoulder_right'], 'shoulder_filled': ['color_shoulder_left',
+ 'color_shoulder_right'], 'dpad': ['color_dpad'], 'analog_outer': ['color_analog'],
+ 'analog_base': ['color_analog'], 'button': ['color_a_button'], 'button_filled': ['color_a_button']}
+
+ analog_keys = {'analog_part1': ['color_analog'], 'analog_part2': ['color_analog'],
+ 'analog_part3': ['color_analog'], 'analog_part4': ['color_analog'], 'analog_part5': ['color_analog']}
+
+ analog_bg_keys = {'analog_bg_part1': ['color_analog'], 'analog_bg_part2': ['color_analog'],
+ 'analog_bg_part3': ['color_analog'], 'analog_bg_part4': ['color_analog'], 'analog_bg_part5': ['color_analog']}
+
+ if bg:
+ base_keys.update(analog_bg_keys)
+ else:
+ base_keys.update(analog_keys)
+
+ global recolored_images
+ recolored_images = {}
+
+ for base_key, colors in base_keys.items():
+ img_key = f"{base_key}_{w}_{ow}"
+ base_img = INPUT_DISPLAY_IMG[img_key]
+ for color_name in colors:
+ color = color_dict[color_name]
+ recolored_key = f"{img_key}|{color_name}"
+ recolored_images[recolored_key] = common.color_white_part(base_img.copy(), color)
+
+ recolored_images['background'] = INPUT_DISPLAY_IMG['background']
+ recolored_images['dpad_fill_0'] = INPUT_DISPLAY_IMG['dpad_fill_0']
+ for x in range(1,5):
+ recolored_images[f'dpad_fill_{x}'] = common.color_white_part(INPUT_DISPLAY_IMG[f'dpad_fill_{x}'].copy(), color_dict['color_dpad'])
+
+
+ global raw_author_list, author_dict
+ raw_author_list, author_dict = author_display_util.make_list_author(config['Author display'], extra_display_folder)
+
+ #Parse config to see all needed infodisplay layers
+ global infodisplay_layers
+ infodisplay_layers = []
+ for section in config.sections():
+ if len(section) >= len('Infodisplay') and section[:len('Infodisplay')] == 'Infodisplay':
+ if config[section].getboolean("show_infodisplay"):
+ infodisplay_layers.append(section)
+
+ global extra_display_layers
+ extra_display_layers = []
+ for section in config.sections():
+ if len(section) >= len('Extra display') and section[:len('Extra display')] == 'Extra display':
+ if config[section].getboolean("show_extra_display"):
+ extra_display_layers.append(section)
+
+ i = 1
+ scaling_set = []
+ scaling_set.append(0.2375) # 2.85 / 12, this is for the pretty speedometer.
+ while f'custom_text_{i}' in config['Infodisplay']:
+ scaling_set.append(eval(config['Infodisplay'].get(f'custom_text_scaling_{i}'))/12)
+ i += 1
+ for id_name in infodisplay_layers:
+ scaling_set.append(eval(config[id_name].get('mkw_font_scaling'))/12)
+
+ global scaled_fonts_dict
+ scaled_fonts_dict = {}
+ for scale in scaling_set:
+ tmp_dict = {}
+ for key in MKW_FONT_IMG.keys():
+ size = MKW_FONT_IMG[key].size
+ w2,h2 = (round(size[0]*scale), round(size[1]*scale))
+ tmp_dict[key] = MKW_FONT_IMG[key].resize((w2,h2) , Image.Resampling.LANCZOS)
+ scaled_fonts_dict[scale] = tmp_dict
+
+ #Getting filenames
+ with open(os.path.join(extra_display_folder, 'dump_info.txt'), 'r') as f:
+ ignore_list = f.read().split('\n')
+ dump_folder = ignore_list[0]
+ framedump_folder = os.path.join(dump_folder, 'Frames')
+ audiodump_folder = os.path.join(dump_folder, 'Audio')
+
+ framedump_filenames = []
+ for filename in os.listdir(framedump_folder):
+ filepath = os.path.join(framedump_folder, filename)
+ if not filepath in ignore_list:
+ framedump_filenames.append(filepath)
+
+
+ framedump_clip = [moviepy.VideoFileClip(filename) for filename in framedump_filenames]
+ #Concatening directly the clip from framedump_clip to source was sometimes duplicating frames...
+ #This is why I do this workaround, which expects very few frames from all clip except the last one.
+ list_frame_beginning = []
+ for i in range(len(framedump_clip) -1):
+ for cur_frame in framedump_clip[i].iter_frames():
+ list_frame_beginning.append(cur_frame)
+ if len(framedump_clip) > 1 :
+ beg_clip = moviepy.ImageSequenceClip(list_frame_beginning, fps = 59.94)
+ source = moviepy.concatenate_videoclips([beg_clip, framedump_clip[-1]])
+ else:
+ source = framedump_clip[-1]
+
+ #Add audio dump
+ audio_dumper = config['Audio options'].get('audiodump_target')
+ audio_source = []
+ audiodump_filename = None
+
+ if audio_dumper in ['dsp', 'DSP', 'Dsp']:
+ for filename in os.listdir(audiodump_folder):
+ filepath = os.path.join(audiodump_folder, filename)
+ if not filepath in ignore_list and filepath[-len('_dspdump.wav'):] == '_dspdump.wav':
+ audiodump_filename = filepath
+
+ elif audio_dumper in ['dtk', 'DTK', 'Dtk']:
+ for filename in os.listdir(audiodump_folder):
+ filepath = os.path.join(audiodump_folder, filename)
+ if not filepath in ignore_list and filepath[-len('_dtkdump.wav'):] == '_dtkdump.wav':
+ audiodump_filename = filepath
+
+ if audiodump_filename:
+ audio_source.append(moviepy.AudioFileClip(audiodump_filename))
+ volume = config['Audio options'].getfloat('audiodump_volume')
+ audio_source[-1] = audio_source[-1].with_effects([moviepy.afx.MultiplyVolume(volume)])
+
+ #Add bgm
+ bgm_filename = config['Audio options'].get('bgm_filename')
+ if os.path.isfile(os.path.join(extra_display_folder, bgm_filename)):
+ bgm = moviepy.AudioFileClip(bgm_filename)
+ offset = config['Audio options'].getint('bgm_offset')
+ if offset <= 0:
+ audio_source.append(bgm[-offset:-offset+source.duration])
+ else:
+ audio_source.append((moviepy.audio.AudioClip.AudioClip(lambda t: [0], offset, bgm) + bgm)[:source.duration])
+ volume = config['Audio options'].getfloat('bgm_volume')
+
+ audio_source[-1] = audio_source[-1].with_effects([moviepy.afx.MultiplyVolume(volume)])
+ if audio_source:
+ fade_in = config['Audio options'].getfloat('fade_in')
+ fade_out = config['Audio options'].getfloat('fade_out')
+ source.audio = moviepy.CompositeAudioClip(audio_source).with_effects([moviepy.afx.AudioFadeOut(fade_out),
+ moviepy.afx.AudioFadeIn(fade_in)])
+
+ #Fade effect
+ fade_in = config['Encoding options'].getfloat('video_fade_in')
+ fade_out = config['Encoding options'].getfloat('video_fade_out')
+
+ #Encode
+ t = time.time()
+ encode_style = config['Encoding options'].get('encode_style')
+ output_filename = os.path.join(extra_display_folder, config['Encoding options'].get('output_filename'))
+ no_vid = False
+ if encode_style in ['Normal', 'normal', 'NORMAL', 'n', 'N', 'norm',]:
+ output_video = source.transform(transform_video, apply_to="mask").with_effects([moviepy.vfx.FadeOut(fade_out),
+ moviepy.vfx.FadeIn(fade_in)])
+ output_video.write_videofile(output_filename,
+ codec = 'libx264',
+ preset = config['Encoding options'].get('preset'),
+ threads = config['Encoding options'].getint('threads'),
+ ffmpeg_params = ['-crf', config['Encoding options'].get('crf_value')])
+ elif encode_style in ['Discord', 'DISCORD', 'discord', 'd', 'D', 'disc']:
+ output_video = source.transform(transform_video, apply_to="mask").with_effects([moviepy.vfx.FadeOut(fade_out),
+ moviepy.vfx.FadeIn(fade_in)])
+ video_length = output_video.duration
+ target_bitrate = str(int(0.90*8000000*config['Encoding options'].getfloat('output_file_size')/video_length))
+ output_video.write_videofile(output_filename,
+ codec = 'libx264',
+ preset = config['Encoding options'].get('preset'),
+ threads = config['Encoding options'].getint('threads'),
+ bitrate = target_bitrate,
+ audio_bitrate = str(int(target_bitrate)//100),
+ ffmpeg_params = ['-movflags', 'faststart']
+ )
+ elif encode_style in ['Youtube', 'youtube', 'YT', 'Yt', 'yt', 'YOUTUBE', 'y', 'Y']: #https://gist.github.com/wuziq/b86f8551902fa1d477980a8125970877
+ output_video = source.transform(transform_video, apply_to="mask").with_effects([moviepy.vfx.FadeOut(fade_out),
+ moviepy.vfx.FadeIn(fade_in)])
+ output_video.write_videofile(output_filename,
+ codec = 'libx264',
+ preset = config['Encoding options'].get('preset'),
+ threads = config['Encoding options'].getint('threads'),
+ ffmpeg_params = ['-movflags', 'faststart',
+ '-profile:v' ,'high',
+ '-bf', '2',
+ '-g', '30',
+ '-coder' ,'1',
+ '-crf' ,'16',
+ '-pix_fmt', 'yuv420p',
+ '-c:a', 'aac', '-profile:a', 'aac_low',
+ '-b:a' ,'384k'])
+
+ else:
+ no_vid = True
+ counter = 0
+ for frame in source.iter_frames():
+ counter += 1
+ if counter == int(encode_style):
+ im = Image.fromarray(transform_image(frame, counter))
+ im.show()
+ im.save(f'{counter}.png')
+ if not no_vid :
+ print('time : ', time.time() -t)
+ print('frames expected :', -1+len(os.listdir(os.path.join(current_folder, 'RAM_data'))))
+ print('frames dumped :', round(output_video.fps * output_video.duration))
+ input('Press ENTER to exit')
+main()
diff --git a/scripts/Ghost/AutoSave/autosave ghost here.txt b/scripts/Ghost/AutoSave/autosave ghost here.txt
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/Ghost/Decode RKG CSV only.py b/scripts/Ghost/Decode RKG CSV only.py
new file mode 100644
index 0000000..11045fb
--- /dev/null
+++ b/scripts/Ghost/Decode RKG CSV only.py
@@ -0,0 +1,28 @@
+import sys
+import os
+from rkg_lib import decode_RKG
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ for filename in os.listdir():
+ if filename[-4:] == '.rkg':
+ with open(filename, "rb") as f:
+ raw_data = f.read()
+ csv_filename = filename[:-4]+'.csv'
+ metadata_filename = filename[:-4]+'.txt'
+ mii_filename = filename[:-4]+'.mii'
+ metadata, inputList, mii_data = decode_RKG(raw_data)
+
+ inputList.write_to_file(csv_filename)
+ else:
+ with open(sys.argv[1], "rb") as f:
+ raw_data = f.read()
+ csv_filename = filename[:-4]+'.csv'
+ metadata_filename = filename[:-4]+'.txt'
+ mii_filename = filename[:-4]+'.mii'
+ metadata, inputList, mii_data = decode_RKG(raw_data)
+
+ inputList.write_to_file(csv_filename)
+
+
diff --git a/scripts/Ghost/Decode RKG Full.py b/scripts/Ghost/Decode RKG Full.py
new file mode 100644
index 0000000..f643dcb
--- /dev/null
+++ b/scripts/Ghost/Decode RKG Full.py
@@ -0,0 +1,48 @@
+import sys
+import os
+from rkg_lib import decode_RKG
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ for filename in os.listdir():
+ if filename[-4:] == '.rkg':
+ with open(filename, "rb") as f:
+ raw_data = f.read()
+ csv_filename = filename[:-4]+'.csv'
+ metadata_filename = filename[:-4]+'.txt'
+ mii_filename = filename[:-4]+'.mii'
+
+ try:
+ metadata, inputList, mii_data = decode_RKG(raw_data)
+ inputList.write_to_file(csv_filename)
+ with open(metadata_filename, 'w') as f:
+ f.write(str(metadata))
+ with open(mii_filename, 'wb') as f:
+ f.write(mii_data)
+ #print('decoding of '+filename+' succeded')
+ except Exception as e:
+ print('decoding of '+filename+' failed')
+ print(e)
+ a = 0/0
+ else:
+ filename = sys.argv[1]
+ if filename[-4:] == '.rkg':
+ with open(filename, "rb") as f:
+ raw_data = f.read()
+ csv_filename = filename[:-4]+'.csv'
+ metadata_filename = filename[:-4]+'.txt'
+ mii_filename = filename[:-4]+'.mii'
+
+ try:
+ metadata, inputList, mii_data = decode_RKG(raw_data)
+ inputList.write_to_file(csv_filename)
+ with open(metadata_filename, 'w') as f:
+ f.write(str(metadata))
+ with open(mii_filename, 'wb') as f:
+ f.write(mii_data)
+ #print('decoding of '+filename+' succeded')
+ except Exception as e:
+ print('decoding of '+filename+' failed')
+ print(e)
+ a = 0/0
diff --git a/scripts/Ghost/Encode CSV to RKG.py b/scripts/Ghost/Encode CSV to RKG.py
new file mode 100644
index 0000000..1983af8
--- /dev/null
+++ b/scripts/Ghost/Encode CSV to RKG.py
@@ -0,0 +1,56 @@
+import sys
+import os
+from rkg_lib import decode_RKG, FrameSequence, RKGMetaData, encode_RKG
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ for filename in os.listdir():
+ if filename[-4:] == '.csv':
+ rkg_filename = filename[:-4]+'pyencode'+'.rkg'
+ metadata_filename = filename[:-4]+'.txt'
+ mii_filename = filename[:-4]+'.mii'
+
+ inputList = FrameSequence(filename)
+
+ with open(metadata_filename, 'r') as f:
+ if f is None:
+ metadata = RKGMetaData(None, True)
+ else:
+ metadata = RKGMetaData.from_string(f.read())
+
+ with open(mii_filename, 'rb') as f:
+ if f is None:
+ mii_data = f.read()[:0x4A]
+ else:
+ mii_data = bytearray(0x4A)
+ rkg_data = encode_RKG(metadata, inputList, mii_data)
+
+ with open(rkg_filename, 'wb') as f:
+ f.write(rkg_data)
+ else:
+ filename = sys.argv[1]
+ if filename[-4:] == '.csv':
+ rkg_filename = filename[:-4]+'pyencode'+'.rkg'
+ metadata_filename = filename[:-4]+'.txt'
+ mii_filename = filename[:-4]+'.mii'
+
+ inputList = FrameSequence(filename)
+
+ with open(metadata_filename, 'r') as f:
+ if f is None:
+ metadata = RKGMetaData(None, True)
+ else:
+ metadata = RKGMetaData.from_string(f.read())
+
+ with open(mii_filename, 'rb') as f:
+ if f is None:
+ mii_data = f.read()[:0x4A]
+ else:
+ mii_data = bytearray(0x4A)
+ rkg_data = encode_RKG(metadata, inputList, mii_data)
+
+ with open(rkg_filename, 'wb') as f:
+ f.write(rkg_data)
+
+
diff --git a/scripts/Ghost/rkg_lib.py b/scripts/Ghost/rkg_lib.py
new file mode 100644
index 0000000..7c89b53
--- /dev/null
+++ b/scripts/Ghost/rkg_lib.py
@@ -0,0 +1,815 @@
+import configparser
+from math import floor
+import os
+import zlib
+from typing import List, Optional
+import csv
+
+class Frame:
+ """
+ A class representing an input combination on a frame.
+
+ Attributes:
+ accel (bool): Whether or not we press 'A' on that frame.
+ brake (bool): Whether or not we press 'B' on that frame.
+ item (bool): Whether or not we press 'L' on that frame.
+ stick_x (int): Horizontal stick input, ranging from -7 to +7.
+ stick_y (int): Vertical stick input, ranging from -7 to +7.
+ dpad_up (bool): Whether or not we press 'Up' on that frame.
+ dpad_down (bool): Whether or not we press 'Down' on that frame.
+ dpad_left (bool): Whether or not we press 'Left' on that frame.
+ dpad_right (bool): Whether or not we press 'Right' on that frame.
+ valid (bool): Whether or not the Frame is valid.
+ iter_idx (int): Tracks current iteration across the inputs
+ """
+ accel: bool
+ brake: bool
+ item: bool
+ drift: bool
+ brakedrift: bool
+
+ stick_x: int
+ stick_y: int
+
+ dpad_up: bool
+ dpad_down: bool
+ dpad_left: bool
+ dpad_right: bool
+
+ valid: bool
+
+ iter_idx: int
+
+ def __init__(self, raw: List):
+ """
+ Initializes a Frame object given a CSV line.
+
+ The structure of the list is as follows:
+ * raw[0] (str) - A
+ * raw[1] (str) - B/R
+ * raw[2] (str) - L
+ * raw[5] (str) - Horizontal stick
+ * raw[6] (str) - Vertical stick
+ * raw[7] (str) - Dpad
+ * raw[3] (str) - Drift "ghost" button
+ * raw[4] (str) - BrakeDrift "ghost" button
+ Args:
+ raw (List): CSV line to be read
+ """
+ self.valid = True
+
+ self.accel = self.read_button(raw[0])
+ self.brake = self.read_button(raw[1])
+ self.item = self.read_button(raw[2])
+ self.drift = self.read_button(raw[3])
+ self.brakedrift = self.read_button(raw[4])
+ self.stick_x = self.read_stick(raw[5])
+ self.stick_y = self.read_stick(raw[6])
+ self.read_dpad(raw[7])
+
+
+ def __iter__(self):
+ self.iter_idx = 0
+ return self
+
+ def __next__(self):
+ # If we update to Python 3.10, replace with switch statement
+ self.iter_idx += 1
+ if (self.iter_idx == 1):
+ return int(self.accel)
+ if (self.iter_idx == 2):
+ return int(self.brake)
+ if (self.iter_idx == 3):
+ return int(self.item)
+ if (self.iter_idx == 4):
+ return int(self.drift)
+ if (self.iter_idx == 5):
+ return int(self.brakedrift)
+ if (self.iter_idx == 6):
+ return self.stick_x
+ if (self.iter_idx == 7):
+ return self.stick_y
+ if (self.iter_idx == 8):
+ return self.dpad_raw()
+
+ raise StopIteration
+
+
+
+ @staticmethod
+ def default():
+ return Frame([0,0,0,0,0,0,0,0])
+
+ def __str__(self):
+ return ','.join(map(str, self))
+
+
+ def read_button(self, button: str) -> bool:
+ """
+ Parses the button input into a boolean. Sets `self.valid` to False if invalid.
+ """
+ try:
+ val = int(button)
+ except ValueError:
+ self.valid = False
+ return False
+
+ if val < 0 or val > 1:
+ self.valid = False
+
+ return bool(val)
+
+ def read_stick(self, stick: str) -> int:
+ """
+ Parses the stick input into an int. Sets `self.valid` to False if invalid.
+ """
+ try:
+ val = int(stick)
+ except ValueError:
+ self.valid = False
+ return 0
+
+ if val < -7 or val > 7:
+ self.valid = False
+
+ return val
+
+ def read_dpad(self, dpad: str) -> None:
+ """
+ Sets dpad members based on dpad input. Sets `self.valid` to False if invalid.
+ """
+ try:
+ val = int(dpad)
+ except ValueError:
+ self.valid = False
+ return
+
+ if val < 0 or val > 4:
+ self.valid = False
+
+ self.dpad_up = val == 1
+ self.dpad_down = val == 2
+ self.dpad_left = val == 3
+ self.dpad_right = val == 4
+
+ def dpad_raw(self) -> int:
+ """
+ Converts dpad values back into its raw form, for writing to the csv
+ """
+ if self.dpad_up:
+ return 1
+ if self.dpad_down:
+ return 2
+ if self.dpad_left:
+ return 3
+ if self.dpad_right:
+ return 4
+ return 0
+
+def compressInputList(rawInputList):
+ """A function that convert Raw Input List (List of Frames) to
+ Compressed Input List (List of 7-int-list, TTK .csv format)"""
+ compressedInputList = []
+ prevInputRaw = Frame(["0"]*8)
+ prevInputCompressed = [0,0,0,0,0,0,-1]
+ for rawInput in rawInputList:
+ compressedInput = [int(rawInput.accel),
+ 0,
+ int(rawInput.item),
+ rawInput.stick_x,
+ rawInput.stick_y,
+ rawInput.dpad_raw(),
+ -1]
+ if not rawInput.brake:
+ compressedInput[1] = 0
+ elif rawInput.brakedrift:
+ compressedInput[1] = 3
+ elif not prevInputRaw.brake:
+ compressedInput[1] = 1
+ elif rawInput.drift and not prevInputRaw.drift:
+ compressedInput[1] = 3-prevInputCompressed[1]
+ else:
+ compressedInput[1] = prevInputCompressed[1]
+
+ if rawInput.accel and rawInput.brake and (not rawInput.drift):
+ if prevInputRaw.accel and prevInputRaw.brake and prevInputRaw.drift:
+ compressedInput[6] = 0
+ elif not prevInputRaw.brake:
+ compressedInput[6] = 0
+
+ if ((not rawInput.accel) or (not rawInput.brake)) and rawInput.drift:
+ compressedInput[6] = 1
+
+ prevInputRaw = rawInput
+ prevInputCompressed = compressedInput
+ compressedInputList.append(compressedInput)
+ return compressedInputList
+
+def decompressInputList(compressedInputList):
+ """A function that convert Compressed Input List (List of 7-int-list, TTK .csv format) to
+ Raw Input List (List of Frames)"""
+ prevInputRaw = Frame(["0"]*8)
+ prevInputCompressed = [0,0,0,0,0,0,-1]
+ rawInputList = []
+ for compressedInput in compressedInputList:
+ accel = compressedInput[0]
+ brake = int(compressedInput[1]>0)
+ item = compressedInput[2]
+ X = compressedInput[3]
+ Y = compressedInput[4]
+ dpad = compressedInput[5]
+ brakedrift = int(compressedInput[1]==3)
+
+ if accel + brake < 2:
+ drift = 0
+ elif prevInputRaw.drift:
+ drift = 1
+ elif prevInputCompressed[1] == compressedInput[1]:
+ drift = 0
+ else:
+ drift = 1
+
+ if compressedInput[6] != -1:
+ drift = compressedInput[6]
+
+ rawInput = Frame(list(map(str, (accel, brake, item, drift, brakedrift, X, Y, dpad))))
+ prevInputRaw = rawInput
+ prevInputCompressed = compressedInput
+ rawInputList.append(rawInput)
+ return rawInputList
+
+
+
+class FrameSequence:
+ """
+ A class representing a sequence of inputs, indexed by frames.
+
+ Attributes:
+ frames (list): The sequence of frames.
+ filename (str): The name of the CSV file initializing the frame sequence.
+ """
+ frames: list
+ filename: str
+ iter_idx: int
+
+ def __init__(self, filename: Optional[str]=None):
+ self.frames = []
+ self.filename = filename
+
+ if self.filename:
+ self.read_from_file()
+
+ def __len__(self):
+ return len(self.frames)
+
+ def __getitem__(self, i):
+ if (i < len(self.frames)):
+ return self.frames[i]
+ return None
+
+ def __iter__(self):
+ self.iter_idx = -1
+ return self
+
+ def __next__(self):
+ self.iter_idx += 1
+ if (self.iter_idx < len(self.frames)):
+ return self.frames[self.iter_idx]
+ raise StopIteration
+
+ def read_from_list(self, inputs: List) -> None:
+ """
+ Constructs the frames list by using a list instead of a csv
+
+ Args:
+ input (List): The raw input data we want to store
+ Returns: None
+ """
+ for input in inputs:
+ frame = self.process(input)
+ if not frame:
+ pass
+ self.frames.append(frame)
+
+ def read_from_list_of_frames(self, inputs: List) -> None:
+ """
+ Constructs the frames list by using a list of Frames instead of a csv
+
+ Args:
+ input (List): The raw input data we want to store
+ Returns: None
+ """
+ for frame in inputs:
+ self.frames.append(frame)
+
+ def read_from_file(self) -> None:
+ """
+ Loads the CSV into a new frame sequence. Ideally called on savestate load.
+
+ Args: None
+ Returns: None
+ """
+ self.frames.clear()
+ try:
+ with open(self.filename, 'r') as f:
+ reader = csv.reader(f)
+ compressedInputList = []
+ for row in reader:
+ compressedInputList.append(list(map(int,row)))
+ for frame in decompressInputList(compressedInputList):
+ self.frames.append(frame)
+ except IOError:
+ return
+
+ def write_to_file(self, filename: str) -> bool:
+ """
+ Writes the frame sequence to a csv
+
+ Args:
+ filename (str): The path to the file we wish to write to
+ Returns:
+ A boolean indicating whether the write was successful
+ """
+ try:
+ with open(filename, 'w', newline='') as f:
+ writer = csv.writer(f, delimiter=',')
+ writer.writerows(compressInputList(self.frames))
+ except IOError:
+ return False
+ return True
+
+ def process(self, raw_frame: List) -> Optional[Frame]:
+ """
+ Processes a raw frame into an instance of
+ the Frame class. Ideally used internally.
+
+ Args:
+ raw_frame (List): Line from the CSV to process.
+
+ Returns:
+ A new Frame object initialized with the raw frame,
+ or None if the frame is invalid.
+ """
+ assert len(raw_frame) == 8
+ if len(raw_frame) != 8:
+ return None
+
+ frame = Frame(raw_frame)
+ if not frame.valid:
+ return None
+
+ return frame
+
+
+def extract_bits(data, start_bit, length):
+ byte_index = start_bit // 8
+ bit_offset = start_bit % 8
+ bits = 0
+
+ for i in range(length):
+ bit_position = bit_offset + i
+ bits <<= 1
+ bits |= (data[byte_index + (bit_position // 8)] >> (7 - (bit_position % 8))) & 1
+
+ return bits
+
+
+
+
+
+
+def crc16(i):
+ '''Argument : bytearray
+ Return : 16bit int'''
+ #https://stackoverflow.com/questions/25239423/crc-ccitt-16-bit-python-manual-calculation
+ def _update_crc(crc, c):
+ def _initial(c):
+ POLYNOMIAL = 0x1021
+ PRESET = 0
+ crc = 0
+ c = c << 8
+ for j in range(8):
+ if (crc ^ c) & 0x8000:
+ crc = (crc << 1) ^ POLYNOMIAL
+ else:
+ crc = crc << 1
+ c = c << 1
+ return crc
+ POLYNOMIAL = 0x1021
+ PRESET = 0
+ _tab = [ _initial(i) for i in range(256) ]
+ cc = 0xff & c
+ tmp = (crc >> 8) ^ cc
+ crc = (crc << 8) ^ _tab[tmp & 0xff]
+ crc = crc & 0xffff
+ return crc
+ POLYNOMIAL = 0x1021
+ PRESET = 0
+ crc = PRESET
+ for c in i:
+ crc = _update_crc(crc, c)
+ return crc
+
+
+def convertTimer(bits: int):
+ '''Convert a 24 bits int representing a Timer (m,s,ms), to a float in seconds'''
+ ms = bits & 1023
+ bits >>= 10
+ s = bits & 127
+ bits >>= 7
+ m = bits & 127
+ return m*60+s+ms/1000
+
+def convertTimerBack(split: float):
+ '''Convert a float in seconds, to a 24 bits int representing a Timer (m,s,ms)'''
+ m = floor(split/60)
+ s = floor(split%60)
+ ms = floor(split*1000)%1000
+ return ms + (s<<10) + (m <<17)
+
+def add_bits(bit_list, number, size):
+ ''' Add to a bit list another number, with a bit size'''
+ for i in range(size):
+ bit_list.append((number >> (size - i -1)) & 1)
+
+def bits_to_bytearray(bit_list):
+ ''' Convert an array of bits to an array of bytes (grouping bits by 8)'''
+ b = bytearray()
+ byte = 0
+ for i in range(len(bit_list)):
+ byte = (byte << 1) + bit_list[i]
+ if i%8 == 7:
+ b.append(byte)
+ byte = 0
+ if byte != 0:
+ b.append(byte)
+ return b
+
+class RKGMetaData:
+ def __init__(self, rkg_data, useDefault = False):
+ if not useDefault:
+ self.finish_time = convertTimer(extract_bits(rkg_data, 4*8+0, 24))
+ self.track_id = extract_bits(rkg_data, 7*8+0, 6)
+ self.character_id = extract_bits(rkg_data, 8*8+6, 6)
+ self.vehicle_id = extract_bits(rkg_data, 8*8+0, 6)
+ self.year = extract_bits(rkg_data, 9*8+4, 7)
+ self.month = extract_bits(rkg_data, 0xA*8+3, 4)
+ self.day = extract_bits(rkg_data, 0xA*8+7, 5)
+ self.controller_id = extract_bits(rkg_data, 0xB*8+4, 4)
+ self.compressed_flag = extract_bits(rkg_data, 0xC*8+4, 1)
+ self.ghost_type = extract_bits(rkg_data, 0xC*8+7, 7)
+ self.drift_id = extract_bits(rkg_data, 0xD*8+6, 1)
+ self.input_data_length = extract_bits(rkg_data, 0xE*8+0, 16)
+ self.lap_count = extract_bits(rkg_data, 0x10*8+0, 8)
+ self.lap1_split = convertTimer(extract_bits(rkg_data, 0x11*8+0, 24))
+ self.lap2_split = convertTimer(extract_bits(rkg_data, 0x14*8+0, 24))
+ self.lap3_split = convertTimer(extract_bits(rkg_data, 0x17*8+0, 24))
+ self.country_code = extract_bits(rkg_data, 0x34*8+0, 8)
+ self.state_code = extract_bits(rkg_data, 0x35*8+0, 8)
+ self.location_code = extract_bits(rkg_data, 0x36*8+0, 16)
+ else:
+ self.finish_time = 100
+ self.track_id = 0
+ self.character_id = 0
+ self.vehicle_id = 0
+ self.year = 25
+ self.month = 1
+ self.day = 1
+ self.controller_id = 3
+ self.compressed_flag = 0
+ self.ghost_type = 38
+ self.drift_id = 0
+ self.input_data_length = 908
+ self.lap_count = 3
+ self.lap1_split = 0
+ self.lap2_split = 5999.999
+ self.lap3_split = 5999.999
+ self.country_code = 255
+ self.state_code = 255
+ self.location_code = 65535
+
+ def __iter__(self):
+ return iter([self.finish_time,
+ self.track_id,
+ self.character_id,
+ self.vehicle_id,
+ self.year,
+ self.month,
+ self.day,
+ self.controller_id,
+ self.compressed_flag,
+ self.ghost_type,
+ self.drift_id,
+ self.input_data_length,
+ self.lap_count,
+ self.lap1_split,
+ self.lap2_split,
+ self.lap3_split,
+ self.country_code,
+ self.state_code,
+ self.location_code])
+
+ def __str__(self):
+ res = ""
+ res += "finish_time = " + str(self.finish_time)+"\n"
+ res += "track_id = " + str(self.track_id)+"\n"
+ res += "character_id = " + str(self.character_id)+"\n"
+ res += "vehicle_id = " + str(self.vehicle_id)+"\n"
+ res += "year = " + str(self.year)+"\n"
+ res += "month = " + str(self.month)+"\n"
+ res += "day = " + str(self.day)+"\n"
+ res += "controller_id = " + str(self.controller_id)+"\n"
+ res += "compressed_flag = " + str(self.compressed_flag)+"\n"
+ res += "ghost_type = " + str(self.ghost_type)+"\n"
+ res += "drift_id = " + str(self.drift_id)+"\n"
+ res += "input_data_length = " + str(self.input_data_length)+"\n"
+ res += "lap_count = " + str(self.lap_count)+"\n"
+ res += "lap1_split = " + str(self.lap1_split)+"\n"
+ res += "lap2_split = " + str(self.lap2_split)+"\n"
+ res += "lap3_split = " + str(self.lap3_split)+"\n"
+ res += "country_code = " + str(self.country_code)+"\n"
+ res += "state_code = " + str(self.state_code)+"\n"
+ res += "location_code = " + str(self.location_code)
+ return res
+
+ @staticmethod
+ def from_string(string):
+ lines = string.split("\n")
+ self = RKGMetaData(None, True)
+ self.finish_time = float(lines[0].split('=')[1])
+ self.track_id = int(lines[1].split('=')[1])
+ self.character_id = int(lines[2].split('=')[1])
+ self.vehicle_id = int(lines[3].split('=')[1])
+ self.year = int(lines[4].split('=')[1])
+ self.month = int(lines[5].split('=')[1])
+ self.day = int(lines[6].split('=')[1])
+ self.controller_id = int(lines[7].split('=')[1])
+ self.compressed_flag = int(lines[8].split('=')[1])
+ self.ghost_type = int(lines[9].split('=')[1])
+ self.drift_id = int(lines[10].split('=')[1])
+ self.input_data_length = int(lines[11].split('=')[1])
+ self.lap_count = int(lines[12].split('=')[1])
+ self.lap1_split = float(lines[13].split('=')[1])
+ self.lap2_split = float(lines[14].split('=')[1])
+ self.lap3_split = float(lines[15].split('=')[1])
+ self.country_code = int(lines[16].split('=')[1])
+ self.state_code = int(lines[17].split('=')[1])
+ self.location_code = int(lines[18].split('=')[1])
+ return self
+
+ def to_bytes(self):
+ bit_list = []
+ add_bits(bit_list, convertTimerBack(self.finish_time), 24)
+ add_bits(bit_list, self.track_id, 6)
+ add_bits(bit_list, 0, 2)
+ add_bits(bit_list, self.vehicle_id, 6)
+ add_bits(bit_list, self.character_id, 6)
+ add_bits(bit_list, self.year, 7)
+ add_bits(bit_list, self.month, 4)
+ add_bits(bit_list, self.day, 5)
+ add_bits(bit_list, self.controller_id, 4)
+ add_bits(bit_list, 0, 4)
+ add_bits(bit_list, self.compressed_flag, 1)
+ add_bits(bit_list, 0, 2)
+ add_bits(bit_list, self.ghost_type, 7)
+ add_bits(bit_list, self.drift_id, 1)
+ add_bits(bit_list, 0, 1)
+ add_bits(bit_list, self.input_data_length, 8*2)
+ add_bits(bit_list, self.lap_count, 8*1)
+ add_bits(bit_list, convertTimerBack(self.lap1_split), 24)
+ add_bits(bit_list, convertTimerBack(self.lap2_split), 24)
+ add_bits(bit_list, convertTimerBack(self.lap3_split), 24)
+ add_bits(bit_list, 0, 2*3*8)
+ add_bits(bit_list, 0, 8*0x14)
+ add_bits(bit_list, self.country_code, 8)
+ add_bits(bit_list, self.state_code, 8)
+ add_bits(bit_list, self.location_code, 8*2)
+ add_bits(bit_list, 0, 8*4)
+
+ res = bytearray(b'RKGD') + bits_to_bytearray(bit_list)
+ return res
+
+
+def decompress_ghost_input(ghost_src):
+ if len(ghost_src) > 0x8F and ghost_src[0x8C:0x90] == b'Yaz1':
+ uncompressed_size = (ghost_src[0x90] << 24) + (ghost_src[0x91] << 16) + (ghost_src[0x92] << 8) + ghost_src[0x93]
+ return decode_Yaz1(ghost_src, 0x9c, uncompressed_size)
+ else:
+ return list(ghost_src[0x88:])
+
+def decode_Yaz1(src, offset, uncompressed_size):
+ src_pos = offset
+ valid_bit_count = 0
+ try:
+ curr_code_byte = src[offset + src_pos]
+ except:
+ curr_code_byte = src[src_pos]
+
+ dst = []
+
+ while len(dst) < uncompressed_size:
+ if valid_bit_count == 0:
+ curr_code_byte = src[src_pos]
+ src_pos += 1
+ valid_bit_count = 8
+
+ if (curr_code_byte & 0x80) != 0:
+ dst.append(src[src_pos])
+ src_pos += 1
+ else:
+ byte1 = src[src_pos]
+ byte2 = src[src_pos + 1]
+ src_pos += 2
+ dist = ((byte1 & 0xF) << 8) | byte2
+ copy_source = len(dst) - (dist + 1)
+ num_bytes = byte1 >> 4
+ if num_bytes == 0:
+ num_bytes = src[src_pos] + 0x12
+ src_pos += 1
+ else:
+ num_bytes += 2
+
+ for _ in range(num_bytes):
+ dst.append(dst[copy_source])
+ copy_source += 1
+
+ curr_code_byte <<= 1
+ valid_bit_count -= 1
+
+ return dst
+
+
+def decode_rkg_inputs(rkg_data):
+ raw_data = decompress_ghost_input(rkg_data)
+ button_inputs = []
+ analog_inputs = []
+ trick_inputs = []
+ cur_byte = 8
+ nr_button_inputs = (raw_data[0] << 8) | raw_data[1]
+ nr_analog_inputs = (raw_data[2] << 8) | raw_data[3]
+ nr_trick_inputs = (raw_data[4] << 8) | raw_data[5]
+
+ for _ in range(nr_button_inputs):
+ if cur_byte + 1 < len(raw_data):
+ inputs = raw_data[cur_byte]
+ frames = raw_data[cur_byte + 1]
+ else:
+ inputs = 0
+ frames = 0
+ accelerator = inputs & 0x1
+ drift = (inputs & 0x2) >> 1
+ item = (inputs & 0x4) >> 2
+ pseudoAB = (inputs & 0x8) >> 3
+ breakdrift = (inputs & 0x10) >> 4
+ button_inputs += [(accelerator, drift, item, pseudoAB, breakdrift)] * frames
+ cur_byte += 2
+
+ for _ in range(nr_analog_inputs):
+ if cur_byte + 1 < len(raw_data):
+ inputs = raw_data[cur_byte]
+ frames = raw_data[cur_byte + 1]
+ else:
+ inputs = 0
+ frames = 0
+ horizontal = ((inputs >> 4) & 0xF) - 7
+ vertical = (inputs & 0xF) - 7
+ analog_inputs += [(horizontal, vertical)] * frames
+ cur_byte += 2
+
+ for _ in range(nr_trick_inputs):
+ if cur_byte + 1 < len(raw_data):
+ inputs = raw_data[cur_byte]
+ frames = raw_data[cur_byte + 1]
+ else:
+ inputs = 0
+ frames = 0
+ trick = (inputs & 0x70) >> 4
+ extra_frames = (inputs & 0x0F) << 8
+ trick_inputs += [trick] * (frames + extra_frames)
+ cur_byte += 2
+ inputList = []
+ for i in range(len(button_inputs)):
+ if i >= len(analog_inputs):
+ analog_inputs.append((0,0))
+ if i >= len(trick_inputs):
+ trick_inputs.append(0)
+ inputList.append(Frame([button_inputs[i][0],
+ button_inputs[i][1],
+ button_inputs[i][2],
+ button_inputs[i][3],
+ button_inputs[i][4],
+ analog_inputs[i][0],
+ analog_inputs[i][1],
+ trick_inputs[i]]))
+ res = FrameSequence()
+ res.read_from_list_of_frames(inputList)
+ return res
+
+
+def encodeFaceButton(aButton, bButton, lButton, pabButton, bdButton):
+ return aButton * 0x1 + bButton * 0x2 + lButton * 0x4 + pabButton * 0x8 + bdButton * 0x10
+
+def encodeDirectionInput(horizontalInput, verticalInput):
+ return ((horizontalInput+7) << 4) + verticalInput+7
+
+def encodeTrickInput(trickInput):
+ return trickInput << 4
+
+def encodeRKGInput(inputList : 'FrameSequence'):
+ data = bytearray()
+ dataIndex = 0
+ iL = inputList.frames
+ fbBytes, diBytes, tiBytes = 0, 0, 0
+
+ #Encode face buttons inputs
+ prevInput = encodeFaceButton(iL[0].accel, iL[0].brake, iL[0].item, iL[0].drift, iL[0].brakedrift)
+ amountCurrentFrames = 0x0
+ for ipt in iL:
+ currentInput = encodeFaceButton(ipt.accel, ipt.brake, ipt.item, ipt.drift, ipt.brakedrift)
+ if prevInput != currentInput or amountCurrentFrames >= 0xFF:
+ data.append(prevInput)
+ data.append(amountCurrentFrames)
+ prevInput = currentInput
+ amountCurrentFrames = 0x1
+ fbBytes = fbBytes + 1
+ else:
+ amountCurrentFrames = amountCurrentFrames + 1
+ data.append(prevInput)
+ data.append(amountCurrentFrames)
+ fbBytes = fbBytes + 1
+
+ #Encode joystick inputs
+ prevInput = encodeDirectionInput(iL[0].stick_x, iL[0].stick_y)
+ amountCurrentFrames = 0x0
+ for ipt in iL:
+ currentInput = encodeDirectionInput(ipt.stick_x, ipt.stick_y)
+ if prevInput != currentInput or amountCurrentFrames >= 0xFF:
+ data.append(prevInput)
+ data.append(amountCurrentFrames)
+ prevInput = currentInput
+ amountCurrentFrames = 0x1
+ diBytes = diBytes + 1
+ else:
+ amountCurrentFrames = amountCurrentFrames + 1
+ data.append(prevInput)
+ data.append(amountCurrentFrames)
+ diBytes = diBytes + 1
+
+ #Encode trick inputs
+ prevInput = encodeTrickInput(iL[0].dpad_raw())
+ amountCurrentFrames = 0x0
+ for ipt in iL:
+ currentInput = encodeTrickInput(ipt.dpad_raw())
+ if prevInput != currentInput or amountCurrentFrames >= 0xFFF:
+ data.append(prevInput + (amountCurrentFrames >> 8))
+ data.append(amountCurrentFrames % 0x100)
+ prevInput = currentInput
+ amountCurrentFrames = 0x1
+ tiBytes = tiBytes + 1
+ else:
+ amountCurrentFrames = amountCurrentFrames + 1
+
+ data.append(prevInput + (amountCurrentFrames >> 8))
+ data.append(amountCurrentFrames % 0x100)
+ tiBytes = tiBytes + 1
+
+ return data, fbBytes, diBytes, tiBytes
+
+
+def decode_RKG(raw_data : bytearray):
+ ''' Decode RKG data to 3 objetcs :
+ - RKGMetaData
+ - FrameSequence
+ - Mii Raw data (bytearray, size 0x4A)'''
+ if len(raw_data) < 0x88:
+ print("decode_RKG can't decode raw_data : raw_data too small")
+ else:
+ metadata = RKGMetaData(raw_data)
+ inputList = decode_rkg_inputs(raw_data)
+ mii_data = raw_data[0x3C:0x86]
+ return metadata, inputList, mii_data
+
+
+def encode_RKG(metadata : 'RKGMetaData', inputList : 'FrameSequence', mii_data : bytearray):
+ ''' Encode to a RKG raw data (bytearray) '''
+ inputData, fbBytes, diBytes, tiBytes = encodeRKGInput(inputList)
+ dataIndex = (fbBytes + diBytes + tiBytes) * 0x2
+ metadata.input_data_length = dataIndex + 0x8
+ metadata.compressed_flag = 0
+ crc16_int = crc16(mii_data)
+
+ metadata_bytes = metadata.to_bytes()
+ crc16_data = bytearray([crc16_int >> 8, crc16_int & 0xFF])
+ inputHeader = bytearray([fbBytes >> 8, fbBytes & 0xFF, diBytes >> 8, diBytes & 0xFF, tiBytes >> 8, tiBytes & 0xFF, 0, 0])
+
+ rkg_data = metadata_bytes + mii_data + crc16_data + inputHeader + inputData
+ crc32 = zlib.crc32(rkg_data)
+ arg1 = floor(crc32 / 0x1000000)
+ arg2 = floor((crc32 & 0x00FF0000) / 0x10000)
+ arg3 = floor((crc32 & 0x0000FF00) / 0x100)
+ arg4 = floor(crc32 % 0x100)
+
+
+ return rkg_data + bytearray([arg1, arg2, arg3, arg4])
+
+
diff --git a/scripts/Install_Pycore_Packages.bat b/scripts/Install_Pycore_Packages.bat
new file mode 100644
index 0000000..5a3ddcf
--- /dev/null
+++ b/scripts/Install_Pycore_Packages.bat
@@ -0,0 +1,8 @@
+python -m pip install --upgrade pip
+pip install numpy
+pip install matplotlib
+pip install pillow
+pip install pygame
+pip install moviepy
+python verify_package.py
+pause
\ No newline at end of file
diff --git a/scripts/MKW_Inputs/Backups/backup here.txt b/scripts/MKW_Inputs/Backups/backup here.txt
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/MKW_Inputs/Startslides/flame_left.csv b/scripts/MKW_Inputs/Startslides/flame_left.csv
deleted file mode 100644
index 1bbef69..0000000
--- a/scripts/MKW_Inputs/Startslides/flame_left.csv
+++ /dev/null
@@ -1,240 +0,0 @@
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,1
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-6,0,0
-1,0,0,-1,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-5,0,0
-1,0,0,-3,0,0
-1,0,0,0,0,0
-1,0,0,-1,0,0
-1,0,0,-1,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-1,0,0
-1,0,0,0,0,0
-1,0,0,-1,0,0
-1,0,0,-5,0,0
-1,0,0,-3,0,0
-1,0,0,-3,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,2,0,1
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-4,0,0
-1,0,0,-6,-1,0
-1,0,0,0,-1,0
-1,0,0,-1,0,0
-1,0,0,-6,-1,0
-1,0,0,-6,-1,0
-1,0,0,-1,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,7,0,1
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-0,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,-1,0,0
-1,0,0,-1,0,0
-1,0,0,-1,0,0
-1,0,0,-1,0,0
-1,0,0,-1,0,0
-1,0,0,2,0,1
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
diff --git a/scripts/MKW_Inputs/Startslides/flame_right.csv b/scripts/MKW_Inputs/Startslides/flame_right.csv
deleted file mode 100644
index 9a38d5f..0000000
--- a/scripts/MKW_Inputs/Startslides/flame_right.csv
+++ /dev/null
@@ -1,240 +0,0 @@
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,1
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,2,0,0
-1,0,0,2,0,0
-1,0,0,6,0,0
-1,0,0,1,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,5,0,0
-1,0,0,3,0,0
-1,0,0,0,0,0
-1,0,0,1,0,0
-1,0,0,1,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,1,0,0
-1,0,0,0,0,0
-1,0,0,1,0,0
-1,0,0,5,0,0
-1,0,0,3,0,0
-1,0,0,3,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-2,0,1
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,4,0,0
-1,0,0,6,-1,0
-1,0,0,0,-1,0
-1,0,0,1,0,0
-1,0,0,6,-1,0
-1,0,0,6,-1,0
-1,0,0,1,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,-7,0,1
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-0,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,1,0,0
-1,0,0,1,0,0
-1,0,0,1,0,0
-1,0,0,1,0,0
-1,0,0,1,0,0
-1,0,0,-2,0,1
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-2,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
diff --git a/scripts/MKW_Inputs/Startslides/spear_left.csv b/scripts/MKW_Inputs/Startslides/spear_left.csv
deleted file mode 100644
index dc40c20..0000000
--- a/scripts/MKW_Inputs/Startslides/spear_left.csv
+++ /dev/null
@@ -1,240 +0,0 @@
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,-7,0,1
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,0
-1,0,0,-7,-2,0
-1,0,0,-7,-2,0
-1,0,0,-7,-2,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-7,7,0
-0,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,-7,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,-7,0,1
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,7,0,1
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,0,0,0
-0,0,0,-7,7,0
-0,0,0,-7,7,0
-0,0,0,-7,7,0
-0,0,0,-7,7,0
-0,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,0,1
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,1
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,-7,-1,0
-1,0,0,-7,-1,0
-1,0,0,-7,-1,0
-1,0,0,-7,-1,0
-1,0,0,-7,-1,0
-1,0,0,-7,-1,0
-1,0,0,-7,-1,0
-1,0,0,-7,-1,0
-1,0,0,-7,-1,0
-1,0,0,-7,-1,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
diff --git a/scripts/MKW_Inputs/Startslides/spear_right.csv b/scripts/MKW_Inputs/Startslides/spear_right.csv
deleted file mode 100644
index 05865d4..0000000
--- a/scripts/MKW_Inputs/Startslides/spear_right.csv
+++ /dev/null
@@ -1,240 +0,0 @@
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,7,0,1
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,0,0,0
-1,0,0,7,-2,0
-1,0,0,7,-2,0
-1,0,0,7,-2,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,7,7,0
-0,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,7,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,0,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,7,0,1
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,-7,0,1
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,0,0,0
-0,0,0,7,7,0
-0,0,0,7,7,0
-0,0,0,7,7,0
-0,0,0,7,7,0
-0,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,7,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,7,0
-1,0,0,-7,0,1
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,0,0,1
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,7,-1,0
-1,0,0,7,-1,0
-1,0,0,7,-1,0
-1,0,0,7,-1,0
-1,0,0,7,-1,0
-1,0,0,7,-1,0
-1,0,0,7,-1,0
-1,0,0,7,-1,0
-1,0,0,7,-1,0
-1,0,0,7,-1,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,7,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
diff --git a/scripts/MKW_Inputs/Startslides/star_left.csv b/scripts/MKW_Inputs/Startslides/star_left.csv
deleted file mode 100644
index b549b9d..0000000
--- a/scripts/MKW_Inputs/Startslides/star_left.csv
+++ /dev/null
@@ -1,240 +0,0 @@
-0,0,0,7,0,0
-0,0,0,7,0,0
-0,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,-6,0,1
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,1
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-4,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-5,0,0
-1,0,0,-5,0,0
-1,0,0,-4,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-3,0,0
-1,0,0,-4,0,0
-1,0,0,-3,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-1,0,0
-1,0,0,-4,0,0
-1,0,0,-3,0,0
-1,0,0,-1,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,1
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,7,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,-6,7,0
-1,0,0,-6,7,0
-1,0,0,-6,7,0
-1,0,0,-6,7,0
-1,0,0,-6,7,0
diff --git a/scripts/MKW_Inputs/Startslides/star_right.csv b/scripts/MKW_Inputs/Startslides/star_right.csv
deleted file mode 100644
index c262361..0000000
--- a/scripts/MKW_Inputs/Startslides/star_right.csv
+++ /dev/null
@@ -1,240 +0,0 @@
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-0,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,6,0,1
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,1
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,4,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,5,0,0
-1,0,0,5,0,0
-1,0,0,4,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,3,0,0
-1,0,0,4,0,0
-1,0,0,3,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,1,0,0
-1,0,0,4,0,0
-1,0,0,3,0,0
-1,0,0,1,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,1
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,-7,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,0,0
-1,0,0,0,0,0
-1,0,0,0,0,0
-1,0,0,6,7,0
-1,0,0,6,7,0
-1,0,0,6,7,0
-1,0,0,6,7,0
-1,0,0,6,7,0
diff --git a/scripts/MKW_Inputs/ttk files go here.txt b/scripts/MKW_Inputs/ttk files go here.txt
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/Modules/Info EVA usage.txt b/scripts/Modules/Info EVA usage.txt
new file mode 100644
index 0000000..0b97c9b
--- /dev/null
+++ b/scripts/Modules/Info EVA usage.txt
@@ -0,0 +1,33 @@
+https://mariokartwii.com/showthread.php?tid=1954
+
+Range : 0x80000100 to 0x80001800
+
+
+0x800002CE thru 0x800002D3 FanCy Speedometer [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1492
+0x800002CE thru 0x800002D3 FanCy HUD [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1496
+0x800002E0 - 0x80000300 : PyCore Exact finish time
+0x800005FD Rapid Fire (GCN) [mdmwii] https://mariokartwii.com/showthread.php?tid=263
+0x80000A20 thru 0x80000A23 Steal-Mii [Vega] https://mariokartwii.com/showthread.php?tid=1218
+0x80000DC0 thru 0x80000DCF FanCy Speedometer [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1492
+0x80000DC0 thru 0x80000DCF FanCy HUD [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1496
+0x80000FA0 thru 0x800012FF FanCy Speedometer [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1492
+0x80000FA0 thru 0x800012FF FanCy HUD [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1496
+0x80000FC8 thru 0x80000FCF Luck Wheelie Bot [Vega] https://mariokartwii.com/showthread.php?tid=1861
+0x80000FCC thru 0x80000FCF Rapid Fire/Hop [Vega] https://mariokartwii.com/showthread.php?tid=1862
+0x80001100 thru 0x80001197 Vehicle Stats Modifier [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1334
+0x800014B0 thru 0x80001503 Draw Text To Screen [SwareJonge] https://mariokartwii.com/showthread.php?tid=1714
+0x800014B0 thru 0x8000152F Graphical Speedometer [SwareJonge] https://mariokartwii.com/showthread.php?tid=1715
+0x8000152C thru 0x8000152F Mii Cloner [mdmwii] https://mariokartwii.com/showthread.php?tid=274
+0x80001534 thru 0x80001537 Future Fly (Wii Chuck) [mdmwii] https://mariokartwii.com/showthread.php?tid=1078
+0x80001534 thru 0x80001537 Future Fly (GCN) [mdmwii] https://mariokartwii.com/showthread.php?...6&pid=1923
+0x80001550 thru 0x80001553 MarioBOT [mdmwii] https://mariokartwii.com/showthread.php?tid=54
+0x80001570 thru 0x8000157B 1st Person Camera View [JoshuaMK, mdmwii] https://mariokartwii.com/showthread.php?tid=1331
+0x80001570 thru 0x8000157F 1st Person Camera View [mdmwii] https://mariokartwii.com/showthread.php?tid=597
+0x80001600 thru 0x8000161B Future Fly (Wii Chuck) [mdmwii] https://mariokartwii.com/showthread.php?tid=1078
+0x80001600 thru 0x8000161F Future Fly (GCN) [mdmwii] https://mariokartwii.com/showthread.php?...6&pid=1923
+0x80001620 thru 0x80001627 DWC_Authdata NAND File Modifier [Vega] https://mariokartwii.com/showthread.php?tid=1075
+0x80001648 thru 0x8000164B Ultimate Region ID Cycler In Between Races [Vega] https://mariokartwii.com/showthread.php?tid=1070
+0x80001660 thru 0x80001663 Speed-O-Meter; TTs Only [mdmwii] https://mariokartwii.com/showthread.php?tid=1040
+0x80001660 thru 0x80001663 Graphical Speed/MTC/Air/Boost Meter [Vega] https://mariokartwii.com/showthread.php?tid=1112
+0x80001660 thru 0x80001663 Graphical Speed-O-Meter [Vega] https://mariokartwii.com/showthread.php?tid=1111
+0x80001660 thru 0x80001689 Graphical SpeedBar [Vega] https://mariokartwii.com/showthread.php?tid=1113
\ No newline at end of file
diff --git a/scripts/Modules/TimeDifference information.txt b/scripts/Modules/TimeDifference information.txt
new file mode 100644
index 0000000..7b66c9b
--- /dev/null
+++ b/scripts/Modules/TimeDifference information.txt
@@ -0,0 +1,58 @@
+This file contain some information about the TimeDifference scripts in infodisplay.
+
+In infodisplay.ini, you can chose to display or not each time difference calculation.
+
+In infodisplay.ini, "timediff setting" is a setting with 4 possible value :
+ "player" which will use the TimeDifference (Player -> Ghost)
+ "ghost" which will use the TimeDifference (Ghost -> Player)
+ "ahead" which will use the TimeDifference (the one ahead -> the one behind)
+ "behind" which will use the TimeDifference (the one behind -> the one ahead)
+ any other value will default to "player".
+
+In infodisplay.ini "history size" is a setting used for the TimeDifference RaceComp.
+history size = 200 means the TimeDiff RaceComp can at best detect a timedifference of 200 frames or less.
+It uses memory, so don't use an unecessary large number.
+
+Some TimeDifference calculations are not symmetrical. It means this calculation gives different result
+for the time difference between the ghost and the player, and between the player and the ghost.
+Here's an example : For the TimeDifference Absolute (Player1 -> Player2) :
+ We take Player1's speed, we take the distance between both players.
+ And we simply define the TimeDiff as the distance divided by the speed.
+ Player1 and Player2 have asymmetrical roles in the calculation.
+ Therefore : we talk about the timedifference from Player1 to Player2 (and not the timedifference between Player1 and Player2)
+
+This is how each one is calculated :
+
+-TimeDifference Absolute (P1 -> P2) (Not very relevant imo)
+ Take S1 the speed of P1
+ Take D the distance between P1 and P2
+ Return D / S1
+
+-TimeDifference Relative (P1 -> P2) (A bit more relevant maybe)
+ Take S1 the speed of P1 directed "toward" P2. (mathematically, it's a dot product)
+ Take D the distance between P1 and P2
+ Return D / S1
+
+-TimeDifference Projected (P1 -> P2) (A good one for short distances)
+ Take S1 the speed of P1
+ Take D the distance represented here : https://blounard.s-ul.eu/iMDYhZDI.png
+ Return D / S1
+
+-TimeDifference CrossPath (P1 -> P2) (Another good one for short distances)
+this one is symmetrical
+ With the notation here : https://blounard.s-ul.eu/WYbotlks.png
+ Calculate t1 = TimeDifference (P1 -> C) (in this case, all 3 above timedifference formula will give the same result)
+ Calculate t2 = TimeDifference (P2 -> C) (--------------------------------------------------------------------------)
+ Return t1-t2
+
+-TimeDifference ToFinish (P1 -> P2) (Perfectly precise when both player are going straight to the finish line at constant speed. Useless otherwise)
+this one is symmetrical
+ Calculate t1, the time needed for P1 to cross the finish line if P1 keep going at a constant speed.
+ Calculate t2, the time needed for P2 to cross the finish line if P2 keep going at a constant speed.
+ Return t1-t2
+
+-TimeDifference RaceComp (P1 -> P2) (Useful even for long distances. Based on RaceCompletion data. Has several flaws)
+this one is symmetrical
+ Store in memory the racecompletion data for both players for the last few frames.
+ Make the player ahead go "back in time" until it's behind the other player.
+ How much frame you had to go "back in time" is how much frame the player was ahead.
\ No newline at end of file
diff --git a/scripts/Modules/__init__.py b/scripts/Modules/__init__.py
index e69de29..92b50fc 100644
--- a/scripts/Modules/__init__.py
+++ b/scripts/Modules/__init__.py
@@ -0,0 +1,20 @@
+'''
+from Modules import mkw_classes
+#from Modules import marco_utils
+from Modules import settings_utils
+from Modules import input_display
+
+#from Modules import mkw_translation
+from Modules import mkw_utils
+from Modules import framesequence
+
+from Modules import ttk_config
+from Modules import rkg_lib
+
+from Modules import ttk_lib
+
+from Modules import agc_lib
+from Modules import bruteforcer_lib
+from Modules import startslide_utils
+from Modules import infodisplay_utils
+'''
diff --git a/scripts/Modules/agc_lib.py b/scripts/Modules/agc_lib.py
new file mode 100644
index 0000000..ee277b4
--- /dev/null
+++ b/scripts/Modules/agc_lib.py
@@ -0,0 +1,290 @@
+from dolphin import gui, memory, utils
+from .mkw_classes import quatf, vec3
+from .framesequence import Frame
+from .mkw_utils import chase_pointer, fps_const
+from .ttk_lib import write_player_inputs, write_ghost_inputs
+from .mkw_classes import VehiclePhysics, KartMove, RaceConfig, Timer, RaceManagerPlayer, RaceConfigPlayer, RaceConfigScenario, Controller, InputMgr
+import math
+
+class AGCFrameData:
+ """Class to represent a set of value accessible each frame in the memory
+ """
+ Position : 'vec3'
+ Rotation : 'quatf'
+ IV : 'vec3'
+ EV : 'vec3'
+ MaxIV : float
+ CurIV : float
+ ODA : float #Outside Drift Angle
+ Dir : 'vec3'
+ Dive : float
+ Input : 'Frame'
+
+ def __init__(self, usedefault=False, read_slot = 0):
+ if not usedefault:
+ self.Position = VehiclePhysics.position(read_slot)
+ self.Rotation = VehiclePhysics.main_rotation(read_slot)
+ self.IV = VehiclePhysics.internal_velocity(read_slot)
+ self.EV = VehiclePhysics.external_velocity(read_slot)
+ self.MaxIV = KartMove.soft_speed_limit(read_slot)
+ self.CurIV = KartMove.speed(read_slot)
+ self.ODA = KartMove.outside_drift_angle(read_slot)
+ self.Dir = KartMove.dir(read_slot)
+ self.Dive = KartMove.diving_rotation(read_slot)
+ self.Input = Frame.from_current_frame(read_slot)
+ else:
+ self.Position = vec3(0,0,0)
+ self.Rotation = quatf(0,0,0,0)
+ self.IV = vec3(0,0,0)
+ self.EV = vec3(0,0,0)
+ self.MaxIV = 0
+ self.CurIV = 0
+ self.ODA = 0
+ self.Dir = vec3(0,0,0)
+ self.Dive = 0
+ self.Input = Frame.default()
+
+ def __iter__(self):
+ return iter([self.Position,
+ self.Rotation,
+ self.IV,
+ self.EV,
+ self.MaxIV,
+ self.CurIV,
+ self.ODA,
+ self.Dir,
+ self.Dive,
+ self.Input])
+
+ def __str__(self):
+ return ';'.join(map(str, self))
+
+ @staticmethod
+ def read_from_string(string):
+ res = AGCFrameData(usedefault = True)
+ temp = string.split(';')
+ assert len(temp) == 10
+
+ res.Position = vec3.from_string(temp[0])
+ res.Rotation = quatf.from_string(temp[1])
+ res.IV = vec3.from_string(temp[2])
+ res.EV = vec3.from_string(temp[3])
+ res.MaxIV = float(temp[4])
+ res.CurIV = float(temp[5])
+ res.ODA = float(temp[6])
+ res.Dir = vec3.from_string(temp[7])
+ res.Dive = float(temp[8])
+ res.Input = Frame(temp[9].split((',')))
+
+ return res
+
+ def interpolate(self,other, selfi, otheri):
+ #BE CAREFUL, THIS FUNCTION MODIFY SELF ! (be sure to call on a copy before)
+ v1 = self.Position
+ v2 = other.Position
+ v = (v1*selfi)+(v2*otheri)
+ self.Position = v
+
+ def load(self, write_slot, input_only = False):
+ kma = KartMove.chain(write_slot)
+ vpa = VehiclePhysics.chain(write_slot)
+
+ if not input_only:
+ self.Position.write(vpa + 0x68)
+ self.Rotation.write(vpa + 0xF0)
+ self.IV.write(vpa + 0x14C)
+ self.EV.write(vpa + 0x74)
+ memory.write_f32(kma + 0x18, self.MaxIV)
+ memory.write_f32(kma + 0x20, self.CurIV)
+ memory.write_f32(kma + 0x9C, self.ODA)
+ self.Dir.write(kma + 0x5C)
+ memory.write_f32(kma + 0xF4, self.Dive)
+ else:
+ if write_slot == 0 :
+ write_player_inputs(self.Input)
+ else:
+ write_ghost_inputs(self.Input, write_slot)
+
+
+
+class AGCMetaData:
+ """Class for the metadata of a ghost.
+ Contain Character ID, Vehicle ID, Drift ID, and all 3 lap splits"""
+
+ def __init__(self, useDefault = False, read_id = 0):
+ if not useDefault:
+ rcp = RaceConfigPlayer(RaceConfigScenario(RaceConfig.race_scenario()).player(read_id))
+ gc_controller = Controller(InputMgr().gc_controller(read_id))
+ self.charaID = rcp.character_id().value
+ self.vehicleID = rcp.vehicle_id().value
+ self.driftID = int(gc_controller.drift_is_auto()) #ASSUME YOU USE A GCN CONTROLLER ! TODO : HANDLE ALL CONTROLLER
+ self.timer_data = [Split.from_timer(RaceManagerPlayer.lap_finish_time(read_id, lap)) for lap in range(3)]
+ else:
+ self.charaID = 0
+ self.vehicleID = 0
+ self.driftID = 0
+ self.timer_data = [Split(0),Split(0),Split(0)]
+
+ def __str__(self):
+ return ";".join(map(str, self))
+
+ def __iter__(self):
+ return iter([self.charaID, self.vehicleID, self.driftID]+self.timer_data)
+
+ @staticmethod
+ def read_from_string(string):
+ res = AGCMetaData(useDefault = True)
+ temp = string.split(";")
+
+ assert len(temp) >= 6
+
+ res.charaID = int(temp[0])
+ res.vehicleID = int(temp[1])
+ res.driftID = int(temp[2])
+ res.timer_data = [Split.from_string(temp[i]) for i in range(3, len(temp))]
+
+ return res
+
+ @staticmethod
+ def read_from_RKGMetadata(rkg_metadata):
+ res = AGCMetaData(useDefault = True)
+ res.charaID = rkg_metadata.character_id
+ res.vehicleID = rkg_metadata.vehicle_id
+ res.driftID = rkg_metadata.drift_id
+ res.timer_data = [Split(rkg_metadata.lap1_split),
+ Split(rkg_metadata.lap2_split + rkg_metadata.lap1_split),
+ Split(rkg_metadata.lap3_split + rkg_metadata.lap2_split + rkg_metadata.lap1_split)]
+
+ return res
+
+ def load(self, write_slot):
+ addr_player = RaceConfigScenario(RaceConfig.race_scenario()).player(write_slot)
+ if write_slot == 0:
+ addr_controller = InputMgr.gc_controller(0)
+ else:
+ addr_controller = InputMgr.ghost_controller(write_slot)
+
+ memory.write_u32(addr_player + 0x8, self.vehicleID)
+ memory.write_u32(addr_player + 0xC, self.charaID)
+ memory.write_u8(addr_controller + 0x51, self.driftID)
+
+ def delay_timer(self, delay):
+ delay /= fps_const
+ region = utils.get_game_id()
+ try:
+ address = {"RMCE01": 0x809BFDC0, "RMCP01": 0x809C4680,
+ "RMCJ01": 0x809C36E0, "RMCK01": 0x809B2CC0}
+ timer_addr = chase_pointer(address[region], [0x1D0, 0x16C, 0xC], 'u32') + 0x2B8
+ except KeyError:
+ raise RegionError
+
+
+ for lap in range(3):
+ lap_time = self.timer_data[lap].val
+ if lap > 0:
+ lap_time -= self.timer_data[lap-1].val #timer_data is cumulative format so you have to substract each cumulative laps
+ lap_time = max(0, lap_time - delay)
+ m, s, ms = Split(lap_time).time_format()
+ memory.write_u16(timer_addr + lap*0xC + 0x4, m)
+ memory.write_u8(timer_addr + lap*0xC + 0x6, s)
+ memory.write_u16(timer_addr + lap*0xC + 0x8, ms)
+
+
+class Split:
+ """Class for a lap split. Contain just a float, representing the split in seconds"""
+ def __init__(self, f):
+ self.val = f
+ def __str__(self):
+ return f"{self.val:.3f}"
+ def __add__(self,other):
+ return Split(max(0, self.val+other.val))
+
+ @staticmethod
+ def from_string(string):
+ return Split(float(string))
+
+ @staticmethod
+ def from_time_format(m,s,ms):
+ return Split(m*60+s+ms/1000)
+
+ @staticmethod
+ def from_timer(timer_inst):
+ return Split(timer_inst.minutes()*60+timer_inst.seconds()+timer_inst.milliseconds()/1000)
+
+ @staticmethod
+ def from_bytes(b):
+ data_int = b[0]*256*256+b[1]*256+b[2]
+ ms = data_int%1024
+ data_int = data_int//1024
+ s = data_int%128
+ data_int = data_int//128
+ m = data_int%128
+ return Split(m*60+s+ms/1000)
+
+ @staticmethod
+ def from_rkg(rkg_addr,lap):
+ timer_addr = rkg_addr+0x11+0x3*(lap-1)
+ b = memory.read_bytes(timer_addr, 3)
+ return Split.from_bytes(b)
+
+
+ def time_format(self):
+ #return m,s,ms corresponding
+ f = self.val
+ ms = round((f%1)*1000)
+ s = math.floor(f)%60
+ m = math.floor(f)//60
+ return m,s,ms
+
+ def bytes_format(self):
+ #return a bytearray of size 3 for rkg format
+ m,s,ms = self.time_format()
+ data_int = ms+s*1024+m*1024*128
+ b3 = data_int%256
+ data_int = data_int//256
+ b2 = data_int%256
+ data_int = data_int//256
+ b1 = data_int%256
+ return bytearray((b1,b2,b3))
+
+ def time_format_bytes(self):
+ #return a bytearray of size 6, for the timer format.
+ #2 first bytes are m, then s on 1 byte, then 00, then ms on 2 bytes
+ m,s,ms = self.time_format()
+ return bytearray([m//256, m%256, s%256, 0, ms//256, ms%256])
+
+
+
+
+def file_to_framedatalist(filename):
+ datalist = []
+ file = open(filename, 'r')
+ if file is None :
+ gui.add_osd_message("Error : could not load "+filename)
+ return None
+ else:
+ listlines = file.readlines()
+ metadata = AGCMetaData.read_from_string(listlines[0])
+ for i in range(1, len(listlines)):
+ datalist.append(AGCFrameData.read_from_string(listlines[i]))
+ file.close()
+ gui.add_osd_message(f"Data successfully loaded from {filename}")
+ return metadata, datalist
+
+
+def framedatalist_to_file(filename, datalist, metadata):
+ file = open(filename, 'w')
+ if file is None :
+ gui.add_osd_message("Error : could not create "+filename)
+ else:
+ file.write(str(metadata)+'\n')
+ for frame in range(max(datalist.keys())+1):
+ if frame in datalist.keys():
+ file.write(str(datalist[frame])+'\n')
+ else:
+ file.write(str(AGCFrameData(usedefault=True))+'\n')
+ file.close()
+
+
+
+
diff --git a/scripts/Modules/bruteforcer_lib.py b/scripts/Modules/bruteforcer_lib.py
new file mode 100644
index 0000000..009f003
--- /dev/null
+++ b/scripts/Modules/bruteforcer_lib.py
@@ -0,0 +1,330 @@
+from dolphin import controller, event, savestate
+
+from Modules import ttk_lib
+from Modules.framesequence import FrameSequence, Frame
+from Modules import mkw_utils as mkw_utils
+from Modules.mkw_classes import RaceManagerPlayer, VehiclePhysics
+import random
+import os
+
+def save(name):
+ b = savestate.save_to_bytes()
+ f = open(name, 'wb')
+ f.write(b)
+ f.close()
+
+def load(name):
+ f = open(name, 'rb')
+ b = f.read()
+ f.close()
+ savestate.load_from_bytes(b)
+
+def prevframe(frame, frequency):
+ return frame-1 - (frame-2)%frequency
+
+
+class Input:
+ def __init__(self, A, B=None, L=None, H=None, V=None, D=None):
+ if B is None:
+ if type(A) == int:
+ r = A
+ self.A = r%2 == 1
+ r= r//2
+ self.B = r%2 == 1
+ r= r//2
+ self.L = r%2 == 1
+ r= r//2
+ self.H = r%15
+ r= r//15
+ self.V = r%15
+ r= r//15
+ self.D = r%5
+ else :
+ raise TypeError("When calling Input(i) with 1 argument, i must be int")
+ else :
+ self.A = A
+ self.B = B
+ self.L = L
+ self.H = H
+ self.V = V
+ self.D = D
+
+ def __int__(self):
+ r = 0
+ r+= int(self.A)
+ r+= int(self.B)*2
+ r+= int(self.L)*4
+ r+= int(self.H)*8
+ r+= int(self.V)*120
+ r+= int(self.D)*1800
+ return r
+
+ def __str__(self):
+ return f"{str(self.A)}, {str(self.B)}, {str(self.L)}, {str(self.H)}, {str(self.V)}, {str(self.D)}"
+
+
+class InputIterable:
+ def __init__(self, iterable, rule=None):
+ if rule is None:
+ rule = lambda x : True
+ self.rule = rule
+ try:
+ inp = next(iterable)
+ while not rule(inp):
+ inp = next(iterable)
+ self.val = inp
+ self.iterator = iterable
+ except StopIteration:
+ self.val = None
+ self.iterator = iterable
+ print("Tried to Init a InputIterable with iterator not letting any rule(value)")
+
+ def __next__(self):
+ self.val = next(self.iterator)
+ while not self.rule(self.val):
+ self.val = next(self.iterator)
+
+
+
+
+
+class InputList:
+ """Class for List of InputRuled iterator
+ Accessing with an index not in the list will create a new InputRuled iterator
+ InputList[frame] should be an Input
+ InputList.inputlist[frame] is an InputIterable
+ ruleset must be a function : int -> rule
+ rule type is a function : Input -> bool
+ iterset must be a function : int -> itergen
+ itergen type is a function : list(Input) -> Iter(Input) """
+ def __init__(self, ruleset, iterset):
+ self.inputlist = {}
+ self.ruleset = ruleset
+ self.iterset = iterset
+
+ def __getitem__(self, index):
+ if not index in list(self.inputlist.keys()):
+ if index>0:
+ iterable = self.iterset(index)([self[index-1]])
+ self.inputlist[index] = InputIterable(iterable, self.ruleset(index))
+ else:
+ iterable = self.iterset(index)([])
+ self.inputlist[index] = InputIterable(iterable, self.ruleset(index))
+ return self.inputlist[index].val
+
+ def __str__(self):
+ return str([int(self[i]) for i in range(10)])
+
+ def update(self,frame):
+ """Update the list.
+ Return the frame of last modification"""
+ if frame<0:
+ raise ValueError('Tried to update a InputList with from a negative frame')
+ self[frame]
+ self[frame+1]
+ self[frame+2]
+ del self.inputlist[frame+1]
+ del self.inputlist[frame+2]
+ try:
+ next(self.inputlist[frame])
+ return frame
+ except StopIteration:
+ del self.inputlist[frame]
+ return self.update(frame-1)
+
+def first_input_ruled(rule):
+ for i in range(9000):
+ if rule(i):
+ return Input(i)
+
+def last_input_ruled(rule):
+ for i in range(8999, -1, -1):
+ if rule(i):
+ return Input(i)
+
+
+def simple_order_iterator(l):
+ for i in range(9000):
+ yield Input(i)
+
+def last_input_iterator(l):
+ j = 0
+ if len(l)>0:
+ j = int(l[0])
+ for i in range(9000):
+ yield Input((i+j)%9000)
+
+def _123rule(inp):
+ return int(inp)<4
+
+def basic_rule(inp):
+ #Rule for 3 possible inputs : Gi straight, turn left, turn right
+ return inp.A and (not inp.B) and (not inp.L) and (inp.H in [0,7,14]) and (inp.V==7) and (inp.D==0)
+def forward_rule(inp):
+ #Rule for 1 possible input : Press A.
+ return inp.A and (not inp.B) and (not inp.L) and (inp.H ==7) and (inp.V==7) and (inp.D==0)
+
+forward = Input(True, False, False, 7, 7, 0)
+ruleset123 = lambda x : _123rule
+
+itersetconst = lambda x : simple_order_iterator
+
+big = InputList(ruleset123, itersetconst)
+
+def run_input(inp):
+ gc_input = {}
+ #trick input
+ gc_input['Left'] = inp.D==3
+ gc_input['Right'] = inp.D==4
+ gc_input['Up'] = inp.D==1
+ gc_input['Down'] = inp.D==2
+
+ #button input
+ gc_input['A'] = inp.A
+ gc_input['B'] = inp.B
+ gc_input['L'] = inp.L
+
+ #stick input
+ match = {0 : 59,
+ 1 : 68,
+ 2 : 77,
+ 3 : 86,
+ 4 : 95,
+ 5 : 104,
+ 6 : 112,
+ 7 : 128,
+ 8 : 152,
+ 9 : 161,
+ 10 : 170,
+ 11 : 179,
+ 12 : 188,
+ 13 : 197,
+ 14 : 205}
+ gc_input['StickX'] = match[inp.H]
+ gc_input['StickY'] = match[inp.V]
+
+ #Everything else, irrelevant
+ gc_input['Z'] = False
+ gc_input['R'] = False
+ gc_input['X'] = False
+ gc_input['Y'] = False
+ gc_input['Start'] = False
+ gc_input['CStickX'] = 0
+ gc_input['CStickY'] = 0
+ gc_input['TriggerLeft'] = 0
+ gc_input['TriggerRight'] = 0
+ gc_input['AnalogA'] = 0
+ gc_input['AnalogB'] = 0
+ gc_input['Connected'] = True
+
+ controller.set_gc_buttons(0, gc_input)
+
+def makeFrame(inp):
+ f = Frame([str(int(i)) for i in [inp.A, inp.B, inp.L, inp.H-7, inp.V-7, inp.D]])
+ return f
+
+def run_input2(inp):
+ f = makeFrame(inp)
+ ttk_lib.write_player_inputs(f)
+
+
+class Randomizer_Raw_Stick:
+ ''' Class for making random modification to a FrameSequence
+ Only modify 1 stick input at a time
+
+ direction : either horizontal or vertical
+ frame is the frame of input you want to randomize
+ probability is the probability of a random modification actually happenning when calling the random method
+ modif_range is the max amplitude of the modification.
+ example with 2, you can only change a +4 stick input to {+2, +3, +4, +5, +6}'''
+ def __init__(self, direction, frame, probability = 0.1, modif_range = 15):
+ self.direction = direction
+ self.proba = probability
+ self.range = modif_range
+ self.frame = frame
+
+ def random(self, inputList : FrameSequence):
+ '''THIS FUNCTION MODIFIES inputList'''
+ if self.frame < len(inputList.frames):
+ if random.random() < self.proba:
+ baseFrame = inputList.frames[self.frame]
+ if self.direction in ["X", "x", "horizontal"]:
+ baseFrame.stick_x = random.randint(max(-7, baseFrame.stick_x - self.range), min(7, baseFrame.stick_x + self.range))
+ elif self.direction in ["Y", "y", "vertical"]:
+ baseFrame.stick_y = random.randint(max(-7, baseFrame.stick_y - self.range), min(7, baseFrame.stick_y + self.range))
+ else:
+ raise IndexError(f'Index {self.direction} is not supported yet')
+
+class Randomizer_Alternate_Stick:
+ ''' Class for making random modification to a FrameSequence
+ Modify 2 consecutive inputs with +d/-d
+ Example : +5, -3 -> +4, -2.
+
+ direction : either horizontal or vertical
+ frame is the frame of input you want to randomize
+ probability is the probability of a random modification actually happenning when calling the random method
+ modif_range is the max amplitude of the modification.
+ example with 2, you can only change a +4 stick input to {+2, +3, +4, +5, +6}
+ '''
+
+ def __init__(self, direction, frame, probability = 0.1, modif_range = 15):
+ self.direction = direction
+ self.proba = probability
+ self.range = modif_range
+ self.frame = frame
+
+ def random(self, inputList : FrameSequence):
+ '''THIS FUNCTION MODIFIES inputList'''
+ if self.frame + 1 < len(inputList.frames):
+ if random.random() < self.proba:
+ baseFrame1 = inputList.frames[self.frame]
+ baseFrame2 = inputList.frames[self.frame+1]
+ shift = random.randint(1, self.range)
+ sign = random.randint(0,1)*2 -1 #either -1 or 1
+ if self.direction in ["X", "x", "horizontal"]:
+ if sign == 1:
+ max1 = 7 - baseFrame1.stick_x
+ max2 = 7 + baseFrame2.stick_x
+ else:
+ max1 = 7 + baseFrame1.stick_x
+ max2 = 7 - baseFrame2.stick_x
+ max_all = min(max1, max2, shift)
+ baseFrame1.stick_x = baseFrame1.stick_x + sign * max_all
+ baseFrame2.stick_x = baseFrame2.stick_x - sign * max_all
+ elif self.direction in ["Y", "y", "vertical"]:
+ if sign == 1:
+ max1 = 7 - baseFrame1.stick_y
+ max2 = 7 + baseFrame2.stick_y
+ else:
+ max1 = 7 + baseFrame1.stick_y
+ max2 = 7 - baseFrame2.stick_y
+ max_all = min(max1, max2, shift)
+ baseFrame1.stick_y = baseFrame1.stick_y + sign * max_all
+ baseFrame2.stick_y = baseFrame2.stick_y - sign * max_all
+ else:
+ raise IndexError(f'Index {self.direction} is not supported yet')
+
+
+def score_racecomp(frame_limit):
+ def res():
+ frame = mkw_utils.frame_of_input()
+ if frame < frame_limit :
+ return None
+ return RaceManagerPlayer(0).race_completion()
+ return res
+
+def score_XZ_EV(frame_limit):
+ def res():
+ frame = mkw_utils.frame_of_input()
+ if frame < frame_limit :
+ return None
+ return VehiclePhysics(0).external_velocity().length_xz()
+ return res
+
+def score_Z_position(frame_limit, negative=False):
+ def res():
+ frame = mkw_utils.frame_of_input()
+ if frame < frame_limit :
+ return None
+ return VehiclePhysics(0).position().z * -1 if negative else 1
+ return res
diff --git a/scripts/Modules/default.mii b/scripts/Modules/default.mii
new file mode 100644
index 0000000..55ae733
Binary files /dev/null and b/scripts/Modules/default.mii differ
diff --git a/scripts/Modules/framesequence.py b/scripts/Modules/framesequence.py
index 99c48fc..53faac1 100644
--- a/scripts/Modules/framesequence.py
+++ b/scripts/Modules/framesequence.py
@@ -1,4 +1,9 @@
+from dolphin import gui
+
+from pathlib import Path
from typing import List, Optional
+from .mkw_classes import KartInput
+from .mkw_classes import RaceManagerPlayer, RaceInputState
import csv
@@ -22,6 +27,8 @@ class Frame:
accel: bool
brake: bool
item: bool
+ drift: bool
+ brakedrift: bool
stick_x: int
stick_y: int
@@ -43,9 +50,11 @@ def __init__(self, raw: List):
* raw[0] (str) - A
* raw[1] (str) - B/R
* raw[2] (str) - L
- * raw[3] (str) - Horizontal stick
- * raw[4] (str) - Vertical stick
- * raw[5] (str) - Dpad
+ * raw[3] (str) - Drift "ghost" button
+ * raw[4] (str) - BrakeDrift "ghost" button
+ * raw[5] (str) - Horizontal stick
+ * raw[6] (str) - Vertical stick
+ * raw[7] (str) - Dpad
Args:
raw (List): CSV line to be read
@@ -55,9 +64,12 @@ def __init__(self, raw: List):
self.accel = self.read_button(raw[0])
self.brake = self.read_button(raw[1])
self.item = self.read_button(raw[2])
- self.stick_x = self.read_stick(raw[3])
- self.stick_y = self.read_stick(raw[4])
- self.read_dpad(raw[5])
+ self.drift = self.read_button(raw[3])
+ self.brakedrift = self.read_button(raw[4])
+ self.stick_x = self.read_stick(raw[5])
+ self.stick_y = self.read_stick(raw[6])
+ self.read_dpad(raw[7])
+
def __iter__(self):
self.iter_idx = 0
@@ -73,13 +85,46 @@ def __next__(self):
if (self.iter_idx == 3):
return int(self.item)
if (self.iter_idx == 4):
- return self.stick_x
+ return int(self.drift)
if (self.iter_idx == 5):
- return self.stick_y
+ return int(self.brakedrift)
if (self.iter_idx == 6):
+ return self.stick_x
+ if (self.iter_idx == 7):
+ return self.stick_y
+ if (self.iter_idx == 8):
return self.dpad_raw()
+
raise StopIteration
+ @staticmethod
+ def from_current_frame(slot):
+ race_mgr_player = RaceManagerPlayer(slot)
+ kart_input = KartInput(addr=race_mgr_player.kart_input())
+ current_input_state = RaceInputState(addr=kart_input.current_input_state())
+ ablr = current_input_state.buttons().value
+ dpad = current_input_state.trick()
+ xstick = current_input_state.raw_stick_x() - 7
+ ystick = current_input_state.raw_stick_y() - 7
+ aButton = ablr & 1
+ bButton = (ablr & 2) >> 1
+ lButton = (ablr & 4) >> 2
+ dButton = (ablr & 8) >> 3
+ bdButton = (ablr & 16) >> 4
+
+ return Frame([aButton, bButton, lButton, dButton, bdButton, xstick, ystick, dpad])
+
+
+ @staticmethod
+ def default():
+ return Frame([0,0,0,0,0,0,0,0])
+
+ def __str__(self):
+ return ','.join(map(str, self))
+
+ def copy(self):
+ return Frame(str(self).split(','))
+
def read_button(self, button: str) -> bool:
"""
Parses the button input into a boolean. Sets `self.valid` to False if invalid.
@@ -142,7 +187,86 @@ def dpad_raw(self) -> int:
return 4
return 0
+def compressInputList(rawInputList):
+ """A function that convert Raw Input List (List of Frames) to
+ Compressed Input List (List of 7-int-list, TTK .csv format)"""
+ compressedInputList = []
+ prevInputRaw = Frame(["0"]*8)
+ prevInputCompressed = [0,0,0,0,0,0,-1]
+ for rawInput in rawInputList:
+ compressedInput = [int(rawInput.accel),
+ 0,
+ int(rawInput.item),
+ rawInput.stick_x,
+ rawInput.stick_y,
+ rawInput.dpad_raw(),
+ -1]
+ if not rawInput.brake:
+ compressedInput[1] = 0
+ elif rawInput.brakedrift:
+ compressedInput[1] = 3
+ elif not prevInputRaw.brake:
+ compressedInput[1] = 1
+ elif rawInput.drift and not prevInputRaw.drift:
+ compressedInput[1] = 3-prevInputCompressed[1]
+ else:
+ compressedInput[1] = prevInputCompressed[1]
+
+ if rawInput.accel and rawInput.brake and (not rawInput.drift):
+ if prevInputRaw.accel and prevInputRaw.brake and prevInputRaw.drift:
+ compressedInput[6] = 0
+ elif not prevInputRaw.brake:
+ compressedInput[6] = 0
+
+ if ((not rawInput.accel) or (not rawInput.brake)) and rawInput.drift:
+ compressedInput[6] = 1
+
+ prevInputRaw = rawInput
+ prevInputCompressed = compressedInput
+ compressedInputList.append(compressedInput)
+ return compressedInputList
+def decompressInputList(compressedInputList):
+ """A function that convert Compressed Input List (List of 7-int-list, TTK .csv format) to
+ Raw Input List (List of Frames)"""
+ prevInputRaw = Frame(["0"]*8)
+ prevInputCompressed = [0,0,0,0,0,0,-1]
+ rawInputList = []
+ line = 1
+ for compressedInput in compressedInputList:
+ accel = compressedInput[0]
+ brake = int(compressedInput[1]>0)
+ item = compressedInput[2]
+ X = compressedInput[3]
+ Y = compressedInput[4]
+ dpad = compressedInput[5]
+ brakedrift = int(compressedInput[1]==3)
+
+ if accel + brake < 2:
+ drift = 0
+ elif prevInputRaw.drift:
+ drift = 1
+ elif prevInputCompressed[1] == compressedInput[1]:
+ drift = 0
+ else:
+ drift = 1
+
+ if compressedInput[6] != -1:
+ drift = compressedInput[6]
+
+ rawInput = Frame(list(map(str, (accel, brake, item, drift, brakedrift, X, Y, dpad))))
+ if not rawInput.valid:
+ print(f'Error in the csv file at line {line}')
+ gui.add_osd_message(f'Error in the csv file at line {line}')
+ return rawInputList
+ prevInputRaw = rawInput
+ prevInputCompressed = compressedInput
+ rawInputList.append(rawInput)
+ line += 1
+ return rawInputList
+
+
+
class FrameSequence:
"""
A class representing a sequence of inputs, indexed by frames.
@@ -179,6 +303,12 @@ def __next__(self):
if (self.iter_idx < len(self.frames)):
return self.frames[self.iter_idx]
raise StopIteration
+
+ def copy(self) -> 'FrameSequence':
+ res = FrameSequence()
+ for frame in self.frames:
+ res.frames.append(frame.copy())
+ return res
def read_from_list(self, inputs: List) -> None:
"""
@@ -193,6 +323,17 @@ def read_from_list(self, inputs: List) -> None:
if not frame:
pass
self.frames.append(frame)
+
+ def read_from_list_of_frames(self, inputs: List) -> None:
+ """
+ Constructs the frames list by using a list of Frames instead of a csv
+
+ Args:
+ input (List): The raw input data we want to store
+ Returns: None
+ """
+ for frame in inputs:
+ self.frames.append(frame)
def read_from_file(self) -> None:
"""
@@ -204,14 +345,23 @@ def read_from_file(self) -> None:
self.frames.clear()
try:
with open(self.filename, 'r') as f:
- reader = csv.reader(f)
- for row in reader:
- frame = self.process(row)
- if not frame:
- # TODO: Handle error
- pass
-
- self.frames.append(frame)
+ compressedInputList = []
+ line = 1
+ for row in f.readlines():
+ args = row.strip('\n').split(',')
+ if len(args) not in [6, 7]:
+ print(f'Error in the csv file at line {line}')
+ gui.add_osd_message(f'Error in the csv file at line {line}')
+ return
+ try:
+ compressedInputList.append(list(map(int,args)))
+ except ValueError:
+ print(f'Error in the csv file at line {line}')
+ gui.add_osd_message(f'Error in the csv file at line {line}')
+ return
+ line += 1
+ for frame in decompressInputList(compressedInputList):
+ self.frames.append(frame)
except IOError:
return
@@ -225,9 +375,11 @@ def write_to_file(self, filename: str) -> bool:
A boolean indicating whether the write was successful
"""
try:
+ output_file = Path(filename)
+ output_file.parent.mkdir(exist_ok=True, parents=True)
with open(filename, 'w', newline='') as f:
writer = csv.writer(f, delimiter=',')
- writer.writerows(self.frames)
+ writer.writerows(compressInputList(self.frames))
except IOError:
return False
return True
@@ -244,7 +396,8 @@ def process(self, raw_frame: List) -> Optional[Frame]:
A new Frame object initialized with the raw frame,
or None if the frame is invalid.
"""
- if len(raw_frame) != 6:
+ assert len(raw_frame) == 8
+ if len(raw_frame) != 8:
return None
frame = Frame(raw_frame)
diff --git a/scripts/Modules/infodisplay_utils.py b/scripts/Modules/infodisplay_utils.py
new file mode 100644
index 0000000..8c4bceb
--- /dev/null
+++ b/scripts/Modules/infodisplay_utils.py
@@ -0,0 +1,602 @@
+import configparser
+import math
+import os
+from datetime import datetime
+
+from Modules.mkw_classes.common import SurfaceProperties, eulerAngle, quatf, vec3
+from Modules.mkw_utils import History
+from dolphin import gui, utils
+from Modules import settings_utils as setting
+from Modules import mkw_utils as mkw_utils
+from Modules import ttk_lib as ttk_lib
+from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, TimerManager
+from Modules.mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings, RaceConfigPlayer, RaceConfigPlayerType
+from Modules.mkw_classes import KartObject, KartMove, KartSettings, KartBody
+from Modules.mkw_classes import VehicleDynamics, VehiclePhysics, KartBoost, KartJump, KartSub
+from Modules.mkw_classes import KartState, KartCollide, KartInput, RaceInputState, KartObjectManager
+
+
+def make_line_text_speed(left_text_prefix, left_text_suffix, size, speed, digits):
+ """Function to generate a line of text
+ It has "left_text" as a str on the left,
+ enough spaces to make the text on the left exactly size length
+ then it has ":" followed by the speed, finished with a \n.
+ Param: str left_text
+ int size
+ float speed
+ Return str text"""
+ return left_text_prefix+" "*(size - len(left_text_prefix+left_text_suffix))+left_text_suffix + f"{speed:.{digits}f}\n"
+
+def make_text_speed(speed, speedname, player, boolspd, boolspdoriented, boolspdxyz, digits):
+ """Function to generate the text for a certain speed
+ Parameters : vec3 speed : the speed to generate the text for.
+ str speedname : the string to write before each line
+ int player : ID of the player (used for oriented speed, 0 if player)
+ bool boolspd : True if we draw the (X, Y, Z) speed
+ bool boolspdoriented : True if we draw (Forward, Sideway, Y)
+ bool boolspdxyz : True if we draw (XZ, XYZ)
+ Return str text ready to be displayed"""
+ text = ""
+ facing_yaw = mkw_utils.get_facing_angle(player).yaw
+ offset_size = 13
+ if boolspd and boolspdoriented :
+ text += make_line_text_speed(speedname,"X: ", offset_size, speed.x, digits)
+ text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits)
+ text += make_line_text_speed(speedname,"Z: ", offset_size, speed.z, digits)
+ text += make_line_text_speed(speedname,"Forward: ", offset_size, speed.forward(facing_yaw), digits)
+ text += make_line_text_speed(speedname,"Sideway: ", offset_size, speed.sideway(facing_yaw), digits)
+ elif boolspd :
+ text += make_line_text_speed(speedname,"X: ", offset_size, speed.x, digits)
+ text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits)
+ text += make_line_text_speed(speedname,"Z: ", offset_size, speed.z, digits)
+ elif boolspdoriented :
+ text += make_line_text_speed(speedname,"Forward: ", offset_size, speed.forward(facing_yaw), digits)
+ text += make_line_text_speed(speedname,"Sideway: ", offset_size, speed.sideway(facing_yaw), digits)
+ text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits)
+ if boolspdxyz :
+ text += make_line_text_speed(speedname,"XZ: ", offset_size, speed.length_xz(), digits)
+ text += make_line_text_speed(speedname,"XYZ: ", offset_size, speed.length(), digits)
+ return text
+
+def make_text_speeddiff(playerSpeed, ghostSpeed, prefix_text, digits):
+ xyz = playerSpeed.length() - ghostSpeed.length()
+ xz = playerSpeed.length_xz() - ghostSpeed.length_xz()
+ y = playerSpeed.y - ghostSpeed.y
+ return f"{prefix_text} XYZ: {xyz:.{digits}f} XZ: {xz:.{digits}f} Y: {y:.{digits}f}"
+
+def make_text_rotdiff(rotdiff, prefix_text, digits):
+ return f"{prefix_text}: {rotdiff:.{digits}f}"
+
+
+def make_text_timediff(timediff, prefix_text, prefix_size, timesize, digits):
+ timediffms = timediff/59.94
+ ms = f"{timediffms:.{digits}f}"
+ frame = f"{timediff:.{digits}f}"
+ ms += " "*(timesize - len(ms))
+ ms = ms[:timesize]
+ frame = frame[:timesize]
+ if timediff == 0:
+ extra = " (Tied)"
+ elif timediff > 0:
+ extra = " (Behind)"
+ else:
+ extra = " (Ahead)"
+ return prefix_text+":"+" "*(prefix_size - len(prefix_text))+ms+"| "+frame+extra+"\n"
+
+
+def make_text_rotation(rot, rotspd, prefix_text, prefix_size, rotsize, digits):
+ rot_text = f"{rot:.{digits}f}"
+ rotspd_text = f"{rotspd:.{digits}f}"
+ rot_text += " "*(rotsize - len(rot_text))
+ rot_text = rot_text[:rotsize]
+ rotspd_text = rotspd_text[:rotsize]
+ return prefix_text+":"+" "*(prefix_size - len(prefix_text))+rot_text+"| "+rotspd_text+"\n"
+
+def create_infodisplay(c, RaceComp_History, Angle_History):
+ text = ""
+
+ race_mgr_player = RaceManagerPlayer()
+ race_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario())
+ race_config_player = RaceConfigPlayer(addr=race_scenario.player())
+ race_settings = RaceConfigSettings(race_scenario.settings())
+ kart_object = KartObject()
+ kart_state = KartState(addr=kart_object.kart_state())
+ kart_move = KartMove(addr=kart_object.kart_move())
+ kart_body = KartBody(addr=kart_object.kart_body())
+ vehicle_dynamics = VehicleDynamics(addr=kart_body.vehicle_dynamics())
+ vehicle_physics = VehiclePhysics(addr=vehicle_dynamics.vehicle_physics())
+
+ if c.debug :
+ the_quat = VehiclePhysics.main_rotation(0)
+ the_vec = VehiclePhysics.external_velocity(0)
+
+ up = kart_move.up()
+ floor_quat = quatf.from_vectors(vec3(0,1,0), up)
+ value = the_quat @ the_vec
+ value2 = the_quat.conjugate() @ the_vec
+ angles = eulerAngle.from_quaternion((the_quat*floor_quat).normalize())
+ rpi = mkw_utils.get_relative_angles(eulerAngle.from_quaternion(the_quat), up)
+ #angles = eulerAngle.from_quaternion(floor_quat*the_quat)
+ text += f"""Debug :
+{value.x:.2f}, {value.y:.2f}, {value.z:.2f}
+{value2.x:.2f}, {value2.y:.2f}, {value2.z:.2f}
+{rpi:.4f}, {angles.yaw:.4f}, {angles.roll:.4f}\n"""
+
+ newline = False
+ if c.frame_count:
+ newline = True
+ text += f"Frame: {mkw_utils.frame_of_input()}\n"
+
+ if c.rkg_buffer_size and race_config_player.type() == RaceConfigPlayerType.REAL_LOCAL:
+ newline = True
+ value = 100*ttk_lib.get_full_rkg_size(ttk_lib.PlayerType.PLAYER)/0x13b0
+ text += f"RKG Buffer : {value:2.3f}%\n"
+
+ if newline:
+ text += '\n'
+ newline = False
+ if c.lap_splits:
+ for lap in range(1, math.floor(race_mgr_player.race_completion_max())):
+ exact_finish = mkw_utils.read_exact_finish(lap)
+ text += "Lap {}: {}".format(lap, exact_finish- (mkw_utils.read_exact_finish(lap-1) if (lap>1) else 0))
+ needed_diff = mkw_utils.calculate_extra_finish_data(exact_finish)
+ text += f" ({needed_diff[1]} / +{needed_diff[0]})"
+ text += "\n"
+ if RaceManager.state().value > 2:
+ lap = math.floor(race_mgr_player.race_completion_max())-1
+ exact_finish = mkw_utils.read_exact_finish(lap)
+ text += f"Total: {exact_finish}"
+ needed_diff = mkw_utils.calculate_extra_finish_data(exact_finish)
+ text += f" ({needed_diff[1]} / +{needed_diff[0]})"
+ text += "\n"
+ text += "\n"
+ if c.speed:
+ speed = mkw_utils.delta_position(playerIdx=0)
+ engine_speed = kart_move.speed()
+ cap = kart_move.soft_speed_limit()
+ text += make_text_speed(speed, "", 0, False, c.speed_oriented, c.speed, c.digits)
+ text += f" Engine: {engine_speed:.{c.digits}f} / {cap:.{c.digits}f}\n"
+ text += "\n"
+
+ if (c.iv or c.iv_xyz or c.iv_oriented):
+ iv = vehicle_physics.internal_velocity()
+ text += make_text_speed(iv, "IV ", 0, c.iv, c.iv_oriented, c.iv_xyz, c.digits)
+ text += "\n"
+
+ if (c.ev or c.ev_xyz or c.ev_oriented):
+ ev = vehicle_physics.external_velocity()
+ text += make_text_speed(ev, "EV ", 0, c.ev, c.ev_oriented, c.ev_xyz, c.digits)
+ text += "\n"
+
+ if (c.mrv or c.mrv_xyz or c.mrv_oriented):
+ mrv = vehicle_physics.moving_road_velocity()
+ text += make_text_speed(mrv, "MRV ", 0, c.mrv, c.mrv_oriented, c.mrv_xyz, c.digits)
+ text += "\n"
+
+ if (c.mwv or c.mwv_xyz or c.mwv_oriented):
+ mwv = vehicle_physics.moving_water_velocity()
+ text += make_text_speed(mwv, "MWV ", 0, c.mwv, c.mwv_oriented, c.mwv_xyz, c.digits)
+ text += "\n"
+
+ if c.charges or c.misc:
+ kart_settings = KartSettings(addr=kart_object.kart_settings())
+
+ if c.charges:
+ kart_boost = KartBoost(addr=kart_move.kart_boost())
+
+ mt = kart_move.mt_charge()
+ smt = kart_move.smt_charge()
+ ssmt = kart_move.ssmt_charge()
+ mt_boost = kart_move.mt_boost_timer()
+ trick_boost = kart_boost.trick_and_zipper_timer()
+ shroom_boost = kart_move.mushroom_timer()
+ if kart_settings.is_bike():
+ text += f"MT Charge: {mt} | SSMT Charge: {ssmt}\n"
+ else:
+ text += f"MT Charge: {mt} ({smt}) | SSMT Charge: {ssmt}\n"
+
+ text += f"MT: {mt_boost} | Trick: {trick_boost} | Mushroom: {shroom_boost}\n\n"
+
+ if c.cps:
+ lap_comp = race_mgr_player.lap_completion()
+ race_comp = race_mgr_player.race_completion()
+ cp = race_mgr_player.checkpoint_id()
+ kcp = race_mgr_player.max_kcp()
+ rp = race_mgr_player.respawn()
+ text += f" Lap%: {lap_comp:.{c.digits}f}\n"
+ text += f"Race%: {race_comp:.{c.digits}f}\n"
+ text += f"CP: {cp} | KCP: {kcp} | RP: {rp}\n\n"
+
+ if c.air:
+ airtime = kart_move.airtime()
+ text += f"Airtime: {airtime}\n\n"
+
+ if c.misc or c.surfaces:
+ kart_collide = KartCollide(addr=kart_object.kart_collide())
+
+ if c.misc:
+ kart_jump = KartJump(addr=kart_move.kart_jump())
+ trick_cd = kart_jump.cooldown()
+ hwg_timer = kart_state.hwg_timer()
+ gcf = kart_collide.glitchy_corner()
+ oob_timer = kart_collide.solid_oob_timer()
+ respawn_timer = kart_collide.time_before_respawn()
+ offroad_inv = kart_move.offroad_invincibility()
+ if kart_move.is_bike:
+ text += f"Wheelie Length: {kart_move.wheelie_frames()}\n"
+ text += f"Wheelie CD: {kart_move.wheelie_cooldown()} | "
+ text += f"Trick CD: {trick_cd}\n"
+ text += f"HWG: {hwg_timer} | GCF: {gcf:.{c.digits}f}\n"
+ text += f"Respawn: {respawn_timer} | OOB: {oob_timer}\n"
+ text += f"Offroad: {offroad_inv}\n\n"
+
+ if c.surfaces:
+ surface_properties = kart_collide.surface_properties()
+ is_offroad = (surface_properties.value & SurfaceProperties.OFFROAD) > 0
+ is_trickable = (surface_properties.value & SurfaceProperties.TRICKABLE) > 0
+ kcl_speed_mod = kart_move.kcl_speed_factor()
+ text += f" Offroad: {is_offroad}\n"
+ text += f"Trickable: {is_trickable}\n"
+ text += f"KCL Speed Modifier: {kcl_speed_mod * 100:.{c.digits}f}%\n\n"
+
+ if c.position:
+ pos = vehicle_physics.position()
+ text += f"X Pos: {pos.x}\n"
+ text += f"Y Pos: {pos.y}\n"
+ text += f"Z Pos: {pos.z}\n\n"
+
+ if c.rotation :
+ fac = mkw_utils.get_facing_angle(0)
+ mov = mkw_utils.get_moving_angle(0)
+ if len(Angle_History) > 1:
+ prevfac = Angle_History[1]['facing']
+ prevmov = Angle_History[1]['moving']
+ else:
+ prevfac = mkw_utils.get_facing_angle(0)
+ prevmov = mkw_utils.get_moving_angle(0)
+ facdiff = fac - prevfac
+ movdiff = mov - prevmov
+ prefix_size = 10
+ rotsize = c.digits+4
+ text += " "*(prefix_size+1)+"Rotation"+" "*(rotsize - 8)+"| Speed\n"
+ text += make_text_rotation(fac.pitch, facdiff.pitch, "Pitch", prefix_size, rotsize, c.digits)
+ text += make_text_rotation(fac.yaw, facdiff.yaw, "Yaw", prefix_size, rotsize, c.digits)
+ text += make_text_rotation(mov.yaw, movdiff.yaw, "Moving Y", prefix_size, rotsize, c.digits)
+ text += make_text_rotation(fac.roll, facdiff.roll, "Roll", prefix_size, rotsize, c.digits)
+ text += "\n"
+ if (c.dpg or c.dpg_xyz or c.dpg_oriented) and not mkw_utils.is_single_player() :
+ dpg = VehiclePhysics.position(1) - VehiclePhysics.position(0)
+ text += make_text_speed(dpg, "Dist PG ", 0, c.dpg, c.dpg_oriented, c.dpg_xyz, c.digits)
+ text += "\n"
+
+ newline = False
+ if (c.vd_spd and not mkw_utils.is_single_player()):
+ newline = True
+ text += make_text_speeddiff(mkw_utils.delta_position(playerIdx=0), mkw_utils.delta_position(playerIdx=1), 'Spd Diff', c.digits)
+ text += "\n"
+
+ if (c.vd_iv and not mkw_utils.is_single_player()):
+ newline = True
+ text += make_text_speeddiff(VehiclePhysics.internal_velocity(0), VehiclePhysics.internal_velocity(1), ' IV Diff', c.digits)
+ text += "\n"
+
+ if (c.vd_ev and not mkw_utils.is_single_player()):
+ newline = True
+ text += make_text_speeddiff(VehiclePhysics.external_velocity(0), VehiclePhysics.external_velocity(1), ' EV Diff', c.digits)
+ text += "\n"
+
+ if (not mkw_utils.is_single_player()) and (c.rd_pitch or c.rd_yaw or c.rd_roll or c.rd_movy):
+ facdiff = mkw_utils.get_facing_angle(0) - mkw_utils.get_facing_angle(1)
+ movdiff = mkw_utils.get_moving_angle(0) - mkw_utils.get_moving_angle(1)
+
+ if (c.rd_pitch and not mkw_utils.is_single_player()):
+ newline = True
+ text += make_text_rotdiff(facdiff.pitch, "Pitch diff", c.digits)
+ text += "\n"
+
+ if (c.rd_yaw and not mkw_utils.is_single_player()):
+ newline = True
+ text += make_text_rotdiff(facdiff.yaw, " Yaw diff", c.digits)
+ text += "\n"
+
+ if (c.rd_movy and not mkw_utils.is_single_player()):
+ newline = True
+ text += make_text_rotdiff(movdiff.yaw, " MovY diff", c.digits)
+ text += "\n"
+
+ if (c.rd_roll and not mkw_utils.is_single_player()):
+ newline = True
+ text += make_text_rotdiff(facdiff.roll, " Roll diff", c.digits)
+ text += "\n"
+
+ if newline:
+ text += "\n"
+ newline = False
+
+ if c.td and not mkw_utils.is_single_player():
+ size = 10
+ timesize = c.digits+4
+ p1, p2 = mkw_utils.get_timediff_settings(c.td_set)
+ s = 1 if 1-p1 else -1
+ text += "TimeDiff:"+" "*(timesize+size-16)+"Seconds | Frames\n"
+ if c.td_absolute:
+ absolute = mkw_utils.get_time_difference_absolute(p1,p2)
+ text += make_text_timediff(absolute, "Absolute", size, timesize, c.digits)
+ if c.td_relative:
+ relative = s*mkw_utils.get_time_difference_relative(p1,p2)
+ text += make_text_timediff(relative, "Relative", size, timesize, c.digits)
+ if c.td_projected:
+ projected = s*mkw_utils.get_time_difference_projected(p1,p2)
+ text += make_text_timediff(projected, "Projected", size, timesize, c.digits)
+ if c.td_crosspath:
+ crosspath = s*mkw_utils.get_time_difference_crosspath(p1,p2)
+ text += make_text_timediff(crosspath, "CrossPath", size, timesize, c.digits)
+ if c.td_tofinish:
+ tofinish = s*mkw_utils.get_time_difference_tofinish(p1,p2)
+ text += make_text_timediff(tofinish, "ToFinish", size, timesize, c.digits)
+ if c.td_racecomp:
+ racecomp = mkw_utils.get_time_difference_racecompletion(RaceComp_History)
+ text += make_text_timediff(racecomp, "RaceComp", size, timesize, c.digits)
+ text += "\n"
+
+ # TODO: figure out why classes.RaceInfoPlayer.stick_x() and
+ # classes.RaceInfoPlayer.stick_y() do not update
+ # (using these as placeholders until further notice)
+ if c.stick:
+ kart_input = KartInput(addr=race_mgr_player.kart_input())
+ current_input_state = RaceInputState(addr=kart_input.current_input_state())
+
+ stick_x = current_input_state.raw_stick_x() - 7
+ stick_y = current_input_state.raw_stick_y() - 7
+ text += f"X: {stick_x} | Y: {stick_y}\n\n"
+
+ return text
+
+
+
+def make_text_speed_fr(speed, speedname, player, boolspd, boolspdoriented, boolspdxyz, digits):
+ """french version of make_text_speed"""
+ text = ""
+ facing_yaw = mkw_utils.get_facing_angle(player).yaw
+ offset_size = 13
+ if boolspd and boolspdoriented :
+ text += make_line_text_speed(speedname,"X: ", offset_size, speed.x, digits)
+ text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits)
+ text += make_line_text_speed(speedname,"Z: ", offset_size, speed.z, digits)
+ text += make_line_text_speed(speedname,"Frontale: ", offset_size, speed.forward(facing_yaw), digits)
+ text += make_line_text_speed(speedname,"Latérale: ", offset_size, speed.sideway(facing_yaw), digits)
+ elif boolspd :
+ text += make_line_text_speed(speedname,"X: ", offset_size, speed.x, digits)
+ text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits)
+ text += make_line_text_speed(speedname,"Z: ", offset_size, speed.z, digits)
+ elif boolspdoriented :
+ text += make_line_text_speed(speedname,"Frontale: ", offset_size, speed.forward(facing_yaw), digits)
+ text += make_line_text_speed(speedname,"Latérale: ", offset_size, speed.sideway(facing_yaw), digits)
+ text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits)
+ if boolspdxyz :
+ text += make_line_text_speed(speedname,"XZ: ", offset_size, speed.length_xz(), digits)
+ text += make_line_text_speed(speedname,"XYZ: ", offset_size, speed.length(), digits)
+ return text
+
+
+def create_infodisplay_fr(c, RaceComp_History, Angle_History):
+ text = ""
+
+ race_mgr_player = RaceManagerPlayer()
+ race_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario())
+ race_settings = RaceConfigSettings(race_scenario.settings())
+ kart_object = KartObject()
+ kart_state = KartState(addr=kart_object.kart_state())
+ kart_move = KartMove(addr=kart_object.kart_move())
+ kart_body = KartBody(addr=kart_object.kart_body())
+ vehicle_dynamics = VehicleDynamics(addr=kart_body.vehicle_dynamics())
+ vehicle_physics = VehiclePhysics(addr=vehicle_dynamics.vehicle_physics())
+
+ if c.debug :
+ value = mkw_utils.delta_position(0) - VehiclePhysics.speed(0)
+ text += f"Débogage : {value.length()}\n"
+
+ if c.frame_count:
+ text += f"Image: {mkw_utils.frame_of_input()}\n\n"
+
+ if c.lap_splits:
+ # The actual max lap address does not update when crossing the finish line
+ # for the final time to finish the race. However, for whatever reason,
+ # race completion does. We use the "max" version to prevent lap times
+ # from disappearing when crossing the line backwards.
+ player_max_lap = math.floor(race_mgr_player.race_completion_max())
+ lap_count = race_settings.lap_count()
+
+ if player_max_lap >= 2 and lap_count > 1:
+ for lap in range(1, player_max_lap):
+ text += "Tour {}: {}\n".format(lap, mkw_utils.update_exact_finish(lap, 0))
+
+ if player_max_lap > lap_count:
+ text += "Dernier: {}\n".format(mkw_utils.get_unrounded_time(lap_count, 0))
+ text += "\n"
+
+ if c.speed:
+ speed = mkw_utils.delta_position(playerIdx=0)
+ engine_speed = kart_move.speed()
+ cap = kart_move.soft_speed_limit()
+ text += make_text_speed_fr(speed, "", 0, False, c.speed_oriented, c.speed, c.digits)
+ text += f" Moteur: {round(engine_speed, c.digits)} / {round(cap, c.digits)}\n"
+ text += "\n"
+
+ if (c.iv or c.iv_xyz or c.iv_oriented):
+ iv = vehicle_physics.internal_velocity()
+ text += make_text_speed_fr(iv, "VI ", 0, c.iv, c.iv_oriented, c.iv_xyz, c.digits)
+ text += "\n"
+
+ if (c.ev or c.ev_xyz or c.ev_oriented):
+ ev = vehicle_physics.external_velocity()
+ text += make_text_speed_fr(ev, "VE ", 0, c.ev, c.ev_oriented, c.ev_xyz, c.digits)
+ text += "\n"
+
+ if (c.mrv or c.mrv_xyz or c.mrv_oriented):
+ mrv = vehicle_physics.moving_road_velocity()
+ text += make_text_speed_fr(mrv, "ROUTE ", 0, c.mrv, c.mrv_oriented, c.mrv_xyz, c.digits)
+ text += "\n"
+
+ if (c.mwv or c.mwv_xyz or c.mwv_oriented):
+ mwv = vehicle_physics.moving_water_velocity()
+ text += make_text_speed_fr(mwv, "EAU ", 0, c.mwv, c.mwv_oriented, c.mwv_xyz, c.digits)
+ text += "\n"
+ if c.charges or c.misc:
+ kart_settings = KartSettings(addr=kart_object.kart_settings())
+
+ if c.charges:
+ kart_boost = KartBoost(addr=kart_move.kart_boost())
+
+ mt = kart_move.mt_charge()
+ smt = kart_move.smt_charge()
+ ssmt = kart_move.ssmt_charge()
+ mt_boost = kart_move.mt_boost_timer()
+ trick_boost = kart_boost.trick_and_zipper_timer()
+ shroom_boost = kart_move.mushroom_timer()
+ if kart_settings.is_bike():
+ text += f"Jauge MT: {mt} | Jauge SPMT: {ssmt}\n"
+ else:
+ text += f"Jauge MT: {mt} ({smt}) | Jauge SPMT: {ssmt}\n"
+
+ text += f"MT: {mt_boost} | Figure: {trick_boost} | Champi: {shroom_boost}\n\n"
+
+ if c.cps:
+ lap_comp = race_mgr_player.lap_completion()
+ race_comp = race_mgr_player.race_completion()
+ cp = race_mgr_player.checkpoint_id()
+ kcp = race_mgr_player.max_kcp()
+ rp = race_mgr_player.respawn()
+ text += f" Tour%: {round(lap_comp,c.digits)}\n"
+ text += f"Course%: {round(race_comp,c.digits)}\n"
+ text += f"PS: {cp} | PSC: {kcp} | PR: {rp}\n\n"
+
+ if c.air:
+ airtime = kart_move.airtime()
+ text += f"Temps Aérien: {airtime}\n\n"
+
+ if c.misc or c.surfaces:
+ kart_collide = KartCollide(addr=kart_object.kart_collide())
+
+ if c.misc:
+ kart_jump = KartJump(addr=kart_move.kart_jump())
+ trick_cd = kart_jump.cooldown()
+ hwg_timer = kart_state.hwg_timer()
+ oob_timer = kart_collide.solid_oob_timer()
+ respawn_timer = kart_collide.time_before_respawn()
+ offroad_inv = kart_move.offroad_invincibility()
+ if kart_move.is_bike:
+ text += f"Durée Roue Arrière: {kart_move.wheelie_frames()}\n"
+ text += f"Refroidissement Roue Arrière: {kart_move.wheelie_cooldown()} | "
+ text += f"Refroidissement Figure: {trick_cd}\n"
+ text += f"BMH: {hwg_timer} | HL: {oob_timer}\n"
+ text += f"Réinvocation: {respawn_timer}\n"
+ text += f"Hors-piste: {offroad_inv}\n\n"
+
+ if c.surfaces:
+ surface_properties = kart_collide.surface_properties()
+ is_offroad = (surface_properties.value & SurfaceProperties.OFFROAD) > 0
+ is_trickable = (surface_properties.value & SurfaceProperties.TRICKABLE) > 0
+ kcl_speed_mod = kart_move.kcl_speed_factor()
+ text += f" Hors-Piste: {is_offroad}\n"
+ text += f"Figure possible: {is_trickable}\n"
+ text += f"KCL modificateur vitesse: {round(kcl_speed_mod * 100, c.digits)}%\n\n"
+
+ if c.position:
+ pos = vehicle_physics.position()
+ text += f"X Pos: {pos.x}\n"
+ text += f"Y Pos: {pos.y}\n"
+ text += f"Z Pos: {pos.z}\n\n"
+
+ if c.rotation :
+ fac = mkw_utils.get_facing_angle(0)
+ mov = mkw_utils.get_moving_angle(0)
+ if len(Angle_History) > 1:
+ prevfac = Angle_History[1]['facing']
+ prevmov = Angle_History[1]['moving']
+ else:
+ prevfac = mkw_utils.get_facing_angle(0)
+ prevmov = mkw_utils.get_moving_angle(0)
+ facdiff = fac - prevfac
+ movdiff = mov - prevmov
+ prefix_size = 10
+ rotsize = c.digits+4
+ text += " "*(prefix_size+1)+"Rotation"+" "*(rotsize - 8)+"| Vitesse\n"
+ text += make_text_rotation(fac.pitch, facdiff.pitch, "Tangage", prefix_size, rotsize, c.digits)
+ text += make_text_rotation(fac.yaw, facdiff.yaw, "Lacet", prefix_size, rotsize, c.digits)
+ text += make_text_rotation(mov.yaw, movdiff.yaw, "Dép. Y", prefix_size, rotsize, c.digits)
+ text += make_text_rotation(fac.roll, facdiff.roll, "Roulis", prefix_size, rotsize, c.digits)
+ text += "\n"
+ if (c.dpg or c.dpg_xyz or c.dpg_oriented) and not mkw_utils.is_single_player() :
+ dpg = VehiclePhysics.position(1) - VehiclePhysics.position(0)
+ text += make_text_speed(dpg, "Dist JF ", 0, c.dpg, c.dpg_oriented, c.dpg_xyz, c.digits)
+ text += "\n"
+ if c.td and not mkw_utils.is_single_player():
+ size = 10
+ timesize = c.digits+4
+ p1, p2 = mkw_utils.get_timediff_settings(c.td_set)
+ s = 1 if 1-p1 else -1
+ text += "Temps d'écart:"+" "*(timesize+size-16)+"Secondes | Images\n"
+ if c.td_absolute:
+ absolute = mkw_utils.get_time_difference_absolute(p1,p2)
+ text += make_text_timediff(absolute, "Absolu", size, timesize, c.digits)
+ if c.td_relative:
+ relative = s*mkw_utils.get_time_difference_relative(p1,p2)
+ text += make_text_timediff(relative, "Relatif", size, timesize, c.digits)
+ if c.td_projected:
+ projected = s*mkw_utils.get_time_difference_projected(p1,p2)
+ text += make_text_timediff(projected, "Projeté", size, timesize, c.digits)
+ if c.td_crosspath:
+ crosspath = s*mkw_utils.get_time_difference_crosspath(p1,p2)
+ text += make_text_timediff(crosspath, "Croisé", size, timesize, c.digits)
+ if c.td_tofinish:
+ tofinish = s*mkw_utils.get_time_difference_tofinish(p1,p2)
+ text += make_text_timediff(tofinish, "VersFin", size, timesize, c.digits)
+ if c.td_racecomp:
+ racecomp = mkw_utils.get_time_difference_racecompletion(RaceComp_History)
+ text += make_text_timediff(racecomp, "Complétion", size, timesize, c.digits)
+ text += "\n"
+
+ # TODO: figure out why classes.RaceInfoPlayer.stick_x() and
+ # classes.RaceInfoPlayer.stick_y() do not update
+ # (using these as placeholders until further notice)
+ if c.stick:
+ kart_input = KartInput(addr=race_mgr_player.kart_input())
+ current_input_state = RaceInputState(addr=kart_input.current_input_state())
+
+ stick_x = current_input_state.raw_stick_x() - 7
+ stick_y = current_input_state.raw_stick_y() - 7
+ text += f"X: {stick_x} | Y: {stick_y}\n\n"
+
+ return text
+
+def draw_infodisplay(c, RaceComp_History, Angle_History):
+ gui.draw_text((10, 10), c.color, create_infodisplay(c, RaceComp_History, Angle_History))
+
+
+def get_font_size():
+ font_size = 14
+ script_path = utils.get_script_dir()
+ config_filename = os.path.join(script_path, '..', '..', 'Config', 'Dolphin.ini')
+ with open(config_filename, 'r') as f:
+ settings = f.readlines()
+ for text in settings:
+ temp = text.split('=')
+ if temp[0][:13] == 'ImguiFontSize':
+ font_size = int(temp[1])
+ return font_size
+def draw_infodisplay_fr(c, RaceComp_History, Angle_History):
+ text = create_infodisplay_fr(c, RaceComp_History, Angle_History)
+ lines = text.split('\n')
+ j,k = len(lines)//3, 2*len(lines)//3
+ font_size = get_font_size()
+ t1 = '\n'.join(lines[:j])
+ t2 = '\n'.join(lines[j:k])
+ t3 = '\n'.join(lines[k:])
+ gui.draw_text((10, 10), int('0xFF0055A4', 16), t1)
+ gui.draw_text((10, 10+font_size*j), int('0xFFFFFFFF', 16), t2)
+ gui.draw_text((10, 10+font_size*k), int('0xFFEF4135', 16), t3)
+
+
+def special():
+ return (datetime.now().day == 1) and (datetime.now().month == 4)
diff --git a/scripts/Modules/macro_utils.py b/scripts/Modules/macro_utils.py
new file mode 100644
index 0000000..3f0c181
--- /dev/null
+++ b/scripts/Modules/macro_utils.py
@@ -0,0 +1,95 @@
+from typing import TypedDict
+
+
+class GCInputs(TypedDict, total=False):
+ Left: bool
+ Right: bool
+ Down: bool
+ Up: bool
+ Z: bool
+ R: bool
+ L: bool
+ A: bool
+ B: bool
+ X: bool
+ Y: bool
+ Start: bool
+ StickX: int
+ StickY: int
+ CStickX: int
+ CStickY: int
+ TriggerLeft: int
+ TriggerRight: int
+ AnalogA: int
+ AnalogB: int
+ Connected: bool
+
+
+class DolphinGCController:
+ def __init__(self, controller, port=0):
+ self._controller = controller
+ self._port = port
+ self._user_inputs = controller.get_gc_buttons(port)
+
+ def current_inputs(self) -> GCInputs:
+ return self._controller.get_gc_buttons(self._port)
+
+ def user_inputs(self) -> GCInputs:
+ return self._user_inputs
+
+ def set_inputs(self, new_inputs: GCInputs):
+ current_inputs = self._controller.get_gc_buttons(self._port)
+ self._controller.set_gc_buttons(self._port, {**current_inputs, **new_inputs})
+
+
+GC_STICK_RANGES = {
+ 7: (205, 255),
+ 6: (197, 204),
+ 5: (188, 196),
+ 4: (179, 187),
+ 3: (170, 178),
+ 2: (161, 169),
+ 1: (152, 160),
+ 0: (113, 151),
+ -1: (105, 112),
+ -2: (96, 104),
+ -3: (87, 95),
+ -4: (78, 86),
+ -5: (69, 77),
+ -6: (60, 68),
+ -7: (0, 59),
+}
+
+
+def to_raw_gc_stick(mkw_stick: int):
+ try:
+ return GC_STICK_RANGES[mkw_stick][0]
+ except KeyError:
+ raise IndexError(f"Input ({mkw_stick}) outside of expected range (-7 to 7)")
+
+def to_mkwii_gc_stick(raw_stick: int):
+ for val, (start, end) in GC_STICK_RANGES.items():
+ if start <= raw_stick <= end:
+ return val
+ raise IndexError(f"Input ({raw_stick}) outside of expected range (0 to 255)")
+
+
+def convert_stick_inputs(inputs: GCInputs, mkwii_to_raw=False):
+ for mkey in ("StickX", "StickY", "CStickX", "CStickY"):
+ if mkey in inputs:
+ if mkwii_to_raw:
+ inputs[mkey] = to_raw_gc_stick(inputs[mkey])
+ else:
+ inputs[mkey] = to_mkwii_gc_stick(inputs[mkey])
+ return inputs
+
+
+class MKWiiGCController(DolphinGCController):
+ def current_inputs(self) -> GCInputs:
+ return convert_stick_inputs(super().current_inputs())
+
+ def user_inputs(self) -> GCInputs:
+ return convert_stick_inputs(super().current_inputs())
+
+ def set_inputs(self, new_inputs: GCInputs):
+ super().set_inputs(convert_stick_inputs(new_inputs, mkwii_to_raw=True))
\ No newline at end of file
diff --git a/scripts/Modules/mbp_utils.py b/scripts/Modules/mbp_utils.py
new file mode 100644
index 0000000..d9e1c3e
--- /dev/null
+++ b/scripts/Modules/mbp_utils.py
@@ -0,0 +1,171 @@
+from dolphin import utils, debug
+
+
+PAL_EV_ADDR = {
+ 0x8057db60 : "Hop Reset",
+ 0x8057db68 : "Hop Reset",
+ 0x8057db70 : "Hop Reset",
+ 0x8057dba4 : "Hop Gain",
+ 0x8057dbb0 : "Hop Gain",
+ 0x8057dbbc : "Hop Gain",
+ 0x805880f4 : 'Lean effect', #https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartMove.cc#L2323
+ 0x80588104 : 'Lean effect',
+ 0x80588114 : 'Lean effect',
+ 0x80596be4 : 'Slowfall', # Stage 1 typically
+ 0x80596bec : 'Slowfall',
+ 0x80596bf4 : 'Slowfall',
+ 0x805b52fc : 'Mult 0.998', # https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartDynamics.cc#L104
+ 0x805b5304 : 'Mult 0.998',
+ 0x805b5308 : 'Mult 0.998',
+ 0x805aec6c : 'EV to IV', # https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartDynamics.cc#L115
+ 0x805aec78 : 'EV to IV',
+ 0x805aec84 : 'EV to IV',
+ 0x805b4b90 : 'Respawn',
+ 0x805b4b94 : 'Respawn',
+ 0x805b4b98 : 'Respawn',
+ 0x805b5288 : '"Forces"', # https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartDynamics.cc#L98
+ 0x805b528c : '"Forces"', # Forces comes from gravity, but also this : https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartMove.cc#L1529
+ 0x805b5290 : '"Forces"',
+ 0x805b76f4 : "FloorCol Y 4",
+ 0x805b772c : "FloorCol Y 1",
+ 0x805b7734 : "FloorCol X",
+ 0x805b7738 : "FloorCol Z",
+ 0x805b7754 : "FloorCol Y 2", #seems to reset to 0
+ 0x805b52a4 : "FloorCol Y 3", #seems to reset to 0 https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartDynamics.cc#L101
+ 0x805b7de8 : 'Wheel EV decay', #https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartCollide.cc#L744 ?
+ 0x805b7df4 : 'Wheel EV decay',
+ 0x805b7e00 : 'Wheel EV decay',
+ 0x805b4d2c : 'Canon Entry',
+ 0x805b4d30 : 'Canon Entry',
+ 0x805b4d34 : 'Canon Entry',
+ 0x80585290 : 'Canon Duration',
+ 0x80585298 : 'Canon Duration',
+ 0x805852a0 : 'Canon Duration',
+ 0x8057ff2c : "Slow Ramp",
+ 0x8057ff34 : "Slow Ramp",
+ 0x8057ff3c : "Slow Ramp",
+ 0x80568a00 : 'Tumble launch',
+ 0x80568a0c : 'Tumble launch', #typically colliding a chainchomp
+ 0x80568a18 : 'Tumble launch',
+ 0x80568dac : 'Tumble land',
+ 0x80568db4 : 'Tumble land',
+ 0x80568dbc : 'Tumble land',
+ 0x805690e8 : 'Flip launch',
+ 0x805690f0 : 'Flip launch', #typically a cataquack setting your EV to (0,60,0)
+ 0x805690f8 : 'Flip launch',
+ 0x80569170 : 'Some Object',
+ 0x80569180 : 'Some Object',
+ 0x80569190 : 'Some Object',
+ 0x80569558 : 'Flip mid air',
+ 0x80569560 : 'Flip mid air',
+ 0x80569568 : 'Flip mid air',
+ 0x805694cc : 'Flip landing',
+ 0x805694d4 : 'Flip landing',
+ 0x805694dc : 'Flip landing',
+ 0x80569a78 : 'Squish',
+ 0x80569a80 : 'Squish',
+ 0x80569a88 : 'Squish',
+ }
+
+NTSCU_EV_ADDR = {
+ 0x805772fc : "Hop Reset",
+ 0x80577304 : "Hop Reset",
+ 0x8057730c : "Hop Reset",
+ 0x80577340 : "Hop Gain",
+ 0x8057734c : "Hop Gain",
+ 0x80577358 : "Hop Gain",
+ 0x805818d0 : 'Lean effect',
+ 0x805818e0 : 'Lean effect',
+ 0x805818f0 : 'Lean effect',
+ 0x805aa3d4 : 'Mult 0.998',
+ 0x805aa3dc : 'Mult 0.998',
+ 0x805aa3e0 : 'Mult 0.998',
+ 0x805a3d44 : 'EV to IV',
+ 0x805a3d50 : 'EV to IV',
+ 0x805a3d5c : 'EV to IV',
+ 0x805aa360 : 'Gravity',
+ 0x805aa364 : 'Gravity',
+ 0x805aa368 : 'Gravity',
+ 0x805ac80c : "SuperGrind?",
+ 0x805ac810 : "SuperGrind?",
+ 0x805acec0 : 'Wheel EV decay',
+ 0x805acecc : 'Wheel EV decay',
+ 0x805aced8 : 'Wheel EV decay',
+ }
+
+PAL_POS_ADDR = {
+ 0x805b4b84 : "Respawn 1", #TP to 0
+ 0x805b4b88 : "Respawn 1",
+ 0x805b4b8c : "Respawn 1",
+ 0x80590254 : "Respawn 2", #TP to Respawn
+ 0x80590258 : "Respawn 2",
+ 0x8059025c : "Respawn 2",
+ 0x80579be0 : "Respawn 3", #slowfall before gravity
+ 0x80579be8 : "Respawn 3",
+ 0x80579bf0 : "Respawn 3",
+ 0x805b5628 : "Speed",
+ 0x805b5634 : "Speed",
+ 0x805b5640 : "Speed",
+ 0x805b6d88 : "Collision 1", #Walls/Floor typically
+ 0x805b6d8c : "Collision 1",
+ 0x805b6d90 : "Collision 1",
+ 0x80597098 : "Collision 2", #Corner ?
+ 0x805970a4 : "Collision 2",
+ 0x805970b0 : "Collision 2",
+ 0x80597490 : "Vehicle compensation",
+ 0x805974a0 : "Vehicle compensation",
+ 0x805974b0 : "Vehicle compensation",
+ 0x80596e2c : "Collision Object", #tree in rPB for example, or wigglers, or pipes
+ 0x80596e3c : "Collision Object",
+ 0x80596e4c : "Collision Object",
+ 0x80585238 : "Canon",
+ 0x80585240 : "Canon",
+ 0x80585248 : "Canon",
+ 0x80586698 : "Reject Road?", #reject road ???
+ 0x805866a0 : "Reject Road?",
+ 0x805866a8 : "Reject Road?",
+ }
+
+
+PAL_IV_ADDR = {
+ 0x8057bc44 : "Acceleration", #When holding A or B
+ 0x8057c00c : "Soft Cap", #Include walls
+ 0x8057bffc : "Soft Cap backward", #When you exceed the soft cap with negative speed
+ 0x8057af04 : "Deceleration", #When not holding A or B(multiply by 0.98 ?)
+ 0x8057af78 : "Turn Decel", #When turning with handling
+ 0x8057affc : "Air Decel", #When in the air for 5+ frames (multiply by 0.999 ?)
+ 0x8057ac2c : "EV to IV", #https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartMove.cc#L1198
+ 0x8057b0e4 : "Slope", #when you get speed from being on a slope. Only works below 30 IV ?
+ 0x8057bc88 : "SSMT", #Multiply by 0.8 ?
+ 0x8057bcac : "0 IV lock", #When you hold B, you'll stay at 0IV for a few frame before reversing
+ 0x8057ac48 : "Backward IV decel", #If your is IV under -20, it add 0.5 to your IV (seemingly)
+ 0x80578554 : "Canon Reset", #Reset IV to 0 when entering the canon
+ 0x805850c8 : "Canon 2", # Set IV to cap every frame
+
+ }
+
+def union_dict(*args):
+ res = {}
+ for d in args:
+ for k in d.keys():
+ res[k] = d[k]
+ return res
+
+def get_addr_dict():
+ region = utils.get_game_id()
+ if region == 'RMCE01':
+ return NTSCU_EV_ADDR
+ elif region == 'RMCP01':
+ return union_dict(PAL_EV_ADDR, PAL_POS_ADDR, PAL_IV_ADDR)
+ else:
+ return {}
+
+
+def make_mbp(addr):
+ mbp_dict = { "At" : addr,
+ "BreakOnRead" : False,
+ "BreakOnWrite" : True,
+ "LogOnHit" : True,
+ "BreakOnHit" : False}
+ debug.set_memory_breakpoint(mbp_dict)
+
diff --git a/scripts/Modules/mkw_classes/__init__.py b/scripts/Modules/mkw_classes/__init__.py
index cae5be1..507f099 100644
--- a/scripts/Modules/mkw_classes/__init__.py
+++ b/scripts/Modules/mkw_classes/__init__.py
@@ -8,7 +8,7 @@
# noqa: F401
from .common import RegionError
-from .common import vec2, vec3, mat34, quatf
+from .common import vec2, vec3, mat34, quatf, eulerAngle
from .common import ExactTimer
from .common import CupId, CourseId, VehicleId, CharacterId, WheelCount, VehicleType
from .common import SpecialFloor, TrickType, SurfaceProperties, RaceConfigPlayerType
@@ -56,4 +56,4 @@
from .race_manager import RaceManager, RaceState
from .time_manager import TimerManager
from .timer import Timer
-from .race_manager_player import RaceManagerPlayer
\ No newline at end of file
+from .race_manager_player import RaceManagerPlayer
diff --git a/scripts/Modules/mkw_classes/common.py b/scripts/Modules/mkw_classes/common.py
index 1df46d7..e424200 100644
--- a/scripts/Modules/mkw_classes/common.py
+++ b/scripts/Modules/mkw_classes/common.py
@@ -23,8 +23,8 @@ def __sub__(self, other):
@staticmethod
def read(ptr) -> "vec2":
- bytes = memory.read_bytes(ptr, 0x8)
- return vec2(*struct.unpack('>' + 'f'*2, bytes))
+ bts = memory.read_bytes(ptr, 0x8)
+ return vec2(*struct.unpack('>' + 'f'*2, bts))
@dataclass
class vec3:
@@ -38,16 +38,73 @@ def __add__(self, other):
def __sub__(self, other):
return vec3(self.x - other.x, self.y - other.y, self.z - other.z)
+ def __neg__(self):
+ return vec3(-self.x, -self.y, -self.z)
+
+ def __mul__(self, other):
+ """ vec3 * vec3 -> float (dot product)
+ vec3 * float -> vec3 (scalar multiplication)"""
+ if type(other) == vec3:
+ return self.x * other.x + self.y * other.y + self.z * other.z
+ else:
+ return vec3(self.x * other, self.y * other, self.z * other)
+
+ __rmul__ = __mul__
+
+ def __matmul__(self, other):
+ """ vec3 @ vec3 -> vec3 (cross product)
+ vec3 @ float -> vec3 (scalar multiplication)"""
+ if type(other) == vec3:
+ x = self.y*other.z - self.z*other.y
+ y = self.z*other.x - self.x*other.z
+ z = self.x*other.y - self.y*other.x
+ return vec3(x,y,z)
+ else:
+ return vec3(self.x * other, self.y * other, self.z * other)
+
def length(self) -> float:
return math.sqrt(self.x**2 + self.y**2 + self.z**2)
def length_xz(self) -> float:
return math.sqrt(self.x**2 + self.z**2)
+ def forward(self, facing_yaw) -> float:
+ speed_yaw = -180/math.pi * math.atan2(self.x, self.z)
+ diff_angle_rad = (facing_yaw - speed_yaw)*math.pi/180
+ return math.sqrt(self.x**2 + self.z**2)*math.cos(diff_angle_rad)
+
+ def sideway(self, facing_yaw) -> float:
+ speed_yaw = -180/math.pi * math.atan2(self.x, self.z)
+ diff_angle_rad = (facing_yaw - speed_yaw)*math.pi/180
+ return math.sqrt(self.x**2 + self.z**2)*math.sin(diff_angle_rad)
+
@staticmethod
def read(ptr) -> "vec3":
- bytes = memory.read_bytes(ptr, 0xC)
- return vec3(*struct.unpack('>' + 'f'*3, bytes))
+ bts = memory.read_bytes(ptr, 0xC)
+ return vec3(*struct.unpack('>' + 'f'*3, bts))
+
+ def write(self, addr):
+ memory.write_bytes(addr, self.to_bytes())
+
+ @staticmethod
+ def from_bytes(bts) -> "vec3":
+ return vec3(*struct.unpack('>' + 'f'*3, bts))
+
+ def to_bytes(self) -> bytearray:
+ return bytearray(struct.pack('>fff', self.x, self.y, self.z))
+
+ def __str__(self):
+ return str(self.x)+','+str(self.y)+','+str(self.z)
+
+ @staticmethod
+ def from_string(string) -> "vec3":
+ temp = string.split(',')
+ assert len(temp) == 3
+ return vec3(float(temp[0]), float(temp[1]), float(temp[2]))
+
+
+
+
@dataclass
class mat34:
@@ -66,8 +123,8 @@ class mat34:
@staticmethod
def read(ptr) -> "mat34":
- bytes = memory.read_bytes(ptr, 0x30)
- return mat34(*struct.unpack('>' + 'f'*12, bytes))
+ bts = memory.read_bytes(ptr, 0x30)
+ return mat34(*struct.unpack('>' + 'f'*12, bts))
@dataclass
class quatf:
@@ -76,10 +133,149 @@ class quatf:
z: float = 0.0
w: float = 0.0
+ def vec(self) -> "vec3":
+ ''' Return the vec3 part of a quaternion'''
+ return vec3(self.x, self.y, self.z)
+
+ def __abs__(self):
+ ''' The norm / absolute value of the quaternion '''
+ return math.sqrt(self.x*self.x + self.y*self.y + self.z+self.z + self.w*self.w)
+
+ def normalize(self):
+ ''' Return a normalize version of self. Do not modify self '''
+ try:
+ return self * (1/abs(self))
+ except:
+ return quatf(0,0,0,0)
+
+ def __mul__(self, other) -> "quatf":
+ ''' quatf * quatf -> quatf (quaternion multiplication)
+ quatf * vec3 -> quatf (quaternion multiplication with w = 0 for the vec3)
+ quatf * float/int -> quatf (scalar mutliplication)'''
+ if type(other) == vec3:
+ q2 = quatf(other.x, other.y, other.z, 0)
+ elif type(other) == quatf:
+ q2 = other
+ elif type(other) == int or type(other) == float:
+ return quatf(self.x*other, self.y*other, self.z*other, self.w*other)
+ else:
+ raise TypeError('expected vec3 or quatf')
+ q1 = self
+ w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z
+ x = q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y
+ y = q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x
+ z = q1.w*q2.z + q1.x*q2.y - q1.y*q2.x + q1.z*q2.w
+ return quatf(x,y,z,w)
+
+ def conjugate(self):
+ return quatf(-self.x, -self.y, -self.z, self.w)
+
+ def __matmul__(self, other):
+ ''' quatf @ vec3 -> vec3 (vector rotation by a quaternion)'''
+ if not type(other) == vec3:
+ raise TypeError('expected vec3')
+ conj = self.conjugate()
+ res = self * other
+ x = res.y * conj.z + res.x * conj.w + res.w * conj.x - res.z * conj.y
+ y = res.z * conj.x + res.y * conj.w + res.w * conj.y - res.x * conj.z
+ z = res.x * conj.y + res.z * conj.w + res.w * conj.z - res.y * conj.x
+ return vec3(x, y, z)
+
@staticmethod
def read(ptr) -> "quatf":
- bytes = memory.read_bytes(ptr, 0x10)
- return quatf(*struct.unpack('>' + 'f'*4, bytes))
+ bts = memory.read_bytes(ptr, 0x10)
+ return quatf(*struct.unpack('>' + 'f'*4, bts))
+
+ def __str__(self):
+ return str(self.x)+','+str(self.y)+','+str(self.z)+','+str(self.w)
+
+ def write(self, addr):
+ memory.write_bytes(addr, self.to_bytes())
+
+ @staticmethod
+ def from_string(string) -> "quatf":
+ temp = string.split(',')
+ assert len(temp) == 4
+ return quatf(float(temp[0]), float(temp[1]), float(temp[2]), float(temp[3]))
+
+ @staticmethod
+ def from_bytes(bts) -> "quatf":
+ return quatf(*struct.unpack('>' + 'f'*4, bts))
+
+ def to_bytes(self) -> bytearray:
+ return bytearray(struct.pack('>ffff', self.x, self.y, self.z, self.w))
+
+ @staticmethod
+ def from_angles(angles):
+ #arg : angles : eulerAngle
+ cr = math.cos(angles.pitch * 0.5 / (180/math.pi))
+ sr = math.sin(angles.pitch * 0.5 / (180/math.pi))
+ cp = math.cos(angles.yaw * 0.5 / (180/math.pi))
+ sp = math.sin(angles.yaw * 0.5 / (180/math.pi))
+ cy = math.cos(angles.roll * 0.5 / (180/math.pi))
+ sy = math.sin(angles.roll * 0.5 / (180/math.pi))
+
+ return quatf(cr * sp * sy + sr * cp * cy,
+ cr * sp * cy + sr * cp * sy,
+ - cr * cp * sy + sr * sp * cy,
+ - cr * cp * cy + sr * sp * sy)
+
+ @staticmethod
+ def from_vectors(vect1, vect2):
+ #args : 2 vec3, get the rotation from vect1 to vect2
+ cross = vect1 @ vect2
+ w = vect1.length() * vect2.length() + vect1 * vect2
+ return quatf(cross.x, cross.y, cross.z, w).normalize()
+
+
+def angle_degree_format(angle):
+ return ((angle+180)%360) - 180
+
+class eulerAngle:
+ """A class for Euler Angles.
+ Angles in degrees, between -180 and 180"""
+ def __init__(self, pitch=0, yaw=0, roll=0):
+ self.pitch = angle_degree_format(pitch)
+ self.yaw = angle_degree_format(yaw)
+ self.roll = angle_degree_format(roll)
+
+ def __add__(self, other):
+ pitch = self.pitch + other.pitch
+ yaw = self.yaw + other.yaw
+ roll = self.roll + other.roll
+ return eulerAngle(pitch, yaw, roll)
+
+ def __sub__(self, other):
+ pitch = self.pitch - other.pitch
+ yaw = self.yaw - other.yaw
+ roll = self.roll - other.roll
+ return eulerAngle(pitch, yaw, roll)
+
+ def __mul__(self, other):
+ ''' angle * number -> angle '''
+ pitch = self.pitch * other
+ yaw = self.yaw * other
+ roll = self.roll * other
+ return eulerAngle(pitch, yaw, roll)
+
+ @staticmethod
+ def from_quaternion(q : quatf):
+ x1, x2 = 2*q.x*q.w-2*q.y*q.z, 1-2*q.x*q.x-2*q.z*q.z
+ y1, y2 = 2*q.y*q.w-2*q.x*q.z, 1-2*q.y*q.y-2*q.z*q.z
+ z = 2*q.x*q.y + 2*q.z*q.w
+ roll = 180/math.pi * math.asin(z) if abs(z) <= 1 else 90*z/abs(z)
+ pitch = -180/math.pi * math.atan2(x1, x2)
+ yaw = -180/math.pi * math.atan2(y1, y2)
+ return eulerAngle(pitch, yaw, roll)
+
+ def get_unit_vec3(self):
+ """ Return a vec3 of size 1, which point
+ the same direction as self """
+ y = math.sin(self.pitch*math.pi /180)
+ xz = math.cos(self.pitch*math.pi /180)
+ z = xz * math.cos(self.yaw*math.pi /180)
+ x = - xz * math.sin(self.yaw*math.pi /180)
+ return vec3(x,y,z)
@dataclass
class ExactTimer:
diff --git a/scripts/Modules/mkw_classes/kart_collide.py b/scripts/Modules/mkw_classes/kart_collide.py
index 73e8fe2..575ff08 100644
--- a/scripts/Modules/mkw_classes/kart_collide.py
+++ b/scripts/Modules/mkw_classes/kart_collide.py
@@ -64,4 +64,14 @@ def solid_oob_timer(player_idx=0) -> int:
def inst_solid_oob_timer(self) -> int:
solid_oob_timer_ref = self.addr + 0x4A
- return memory.read_u16(solid_oob_timer_ref)
\ No newline at end of file
+ return memory.read_u16(solid_oob_timer_ref)
+
+ @staticmethod
+ def glitchy_corner(player_idx=0) -> int:
+ kart_collide_ref = KartCollide.chain(player_idx)
+ glitchy_corner_ref = kart_collide_ref + 0x68
+ return memory.read_f32(glitchy_corner_ref)
+
+ def inst_glitchy_corner(self) -> int:
+ glitchy_corner_ref = self.addr + 0x68
+ return memory.read_f32(glitchy_corner_ref)
\ No newline at end of file
diff --git a/scripts/Modules/mkw_classes/race_manager.py b/scripts/Modules/mkw_classes/race_manager.py
index 61a0ef4..507782e 100644
--- a/scripts/Modules/mkw_classes/race_manager.py
+++ b/scripts/Modules/mkw_classes/race_manager.py
@@ -7,7 +7,7 @@ class RaceState(Enum):
INTRO_CAMERA = 0 # Course preview
COUNTDOWN = 1 # including starting pan
RACE = 2
- FINISHED_RACE = 3
+ FINISHED_RACE = 4
class RaceManager:
def __init__(self):
@@ -262,4 +262,4 @@ def disable_lower_respawns() -> bool:
def inst_disable_lower_respawns(self) -> bool:
"""Delfino Plaza?"""
disable_lower_respawns_ref = self.addr + 0x48
- return memory.read_u8(disable_lower_respawns_ref) > 0
\ No newline at end of file
+ return memory.read_u8(disable_lower_respawns_ref) > 0
diff --git a/scripts/Modules/mkw_classes/vehicle_physics.py b/scripts/Modules/mkw_classes/vehicle_physics.py
index bbc05f0..ed6f250 100644
--- a/scripts/Modules/mkw_classes/vehicle_physics.py
+++ b/scripts/Modules/mkw_classes/vehicle_physics.py
@@ -59,11 +59,11 @@ def inst_inverse_inertia_tensor(self) -> mat34:
def rotation_speed(player_idx=0) -> float:
vehicle_physics_ref = VehiclePhysics.chain(player_idx)
rotation_speed_ref = vehicle_physics_ref + 0x64
- return mat34.read(rotation_speed_ref)
+ return memory.read_f32(rotation_speed_ref)
def inst_rotation_speed(self) -> float:
rotation_speed_ref = self.addr + 0x64
- return mat34.read(rotation_speed_ref)
+ return memory.read_f32(rotation_speed_ref)
@staticmethod
def position(player_idx=0) -> vec3:
diff --git a/scripts/Modules/mkw_utils.py b/scripts/Modules/mkw_utils.py
index d2d2dfd..c2676be 100644
--- a/scripts/Modules/mkw_utils.py
+++ b/scripts/Modules/mkw_utils.py
@@ -1,7 +1,10 @@
-from dolphin import memory, utils
+from dolphin import memory, utils, event
+from collections import deque
-from .mkw_classes import mat34, quatf, vec3, ExactTimer
-from .mkw_classes import VehicleDynamics, VehiclePhysics, RaceManagerPlayer
+from .mkw_classes import mat34, quatf, vec3, ExactTimer, eulerAngle
+from .mkw_classes import VehicleDynamics, VehiclePhysics, RaceManagerPlayer, KartObjectManager, RaceManager, RaceState
+
+import math
# These are helper functions that don't quite fit in common.py
# This file also contains getter functions for a few global variables.
@@ -9,6 +12,47 @@
# NOTE (xi): wait for get_game_id() to be put in dolphin.memory before clearing
# these commented-out lines:
+fps_const = 59.94 #Constant for fps in mkw (59.94fps)
+
+
+class History:
+ ''' Class for storing each frame some data
+ get_data_dict must be a dict of functions that return the desired data
+ the functions must be able to take 0 arguments (Maybe if needed i'll implement an argument dict, for the argument of the corresponding functions
+
+ Example : {'position' : VehiclePhysics.position,
+ 'rotation' : VehiclePhysics.main_rotation}
+ data is a list containing dict of data corresponding
+ data[0] contain the latest added frame'''
+
+ def __init__(self, get_data_dict, max_size):
+ self.max_size = max_size
+ self.get_data = get_data_dict
+ self.data = deque()
+
+ def update(self):
+ if len(self.data) >= self.max_size:
+ self.data.pop()
+ cur_frame_dict = {}
+ for key in self.get_data.keys():
+ cur_frame_dict[key] = self.get_data[key]()
+ self.data.appendleft(cur_frame_dict)
+
+ def __getitem__(self, index):
+ return self.data[index]
+
+ def __len__(self):
+ return len(self.data)
+
+ def clear(self):
+ self.data.clear()
+
+ def __bool__(self):
+ return bool(self.data)
+
+ def __iter__(self):
+ return iter(self.data)
+
def chase_pointer(base_address, offsets, data_type):
"""This is a helper function to allow multiple ptr dereferences in
quick succession. base_address is dereferenced first, and then
@@ -42,12 +86,44 @@ def frame_of_input():
"RMCJ01": 0x809C2920, "RMCK01": 0x809B1F00}
return memory.read_u32(address[id])
+def extended_race_state():
+ ''' Return an extended RaceState.
+ -1 <-> Not in a race
+ 0 <-> Intro Camera
+ 1 <-> Countdown
+ 2 <-> In the race
+ 3 <-> Race over (waiting for other to finish)
+ 4 <-> Race over (everyone is finished)
+ 5 <-> Race over and ghost saved'''
+ if KartObjectManager.player_count()==0:
+ return -1
+ else:
+ race_mgr = RaceManager()
+ state = race_mgr.state().value
+ if state < 4:
+ return state
+ else:
+ try:
+ region = utils.get_game_id()
+ address = {"RMCE01": 0x809B8F88, "RMCP01": 0x809BD748,
+ "RMCJ01": 0x809BC7A8, "RMCK01": 0x809ABD88}
+ rkg_addr = chase_pointer(address[region], [0x18], 'u32')
+ except KeyError:
+ raise RegionError
+ if memory.read_u32(rkg_addr) == 0x524b4744 :
+ return 5
+ else:
+ return 4
+
def delta_position(playerIdx=0):
dynamics_ref = VehicleDynamics(playerIdx)
physics_ref = VehiclePhysics(addr=dynamics_ref.vehicle_physics())
return physics_ref.position() - dynamics_ref.position()
+def is_single_player():
+ return KartObjectManager().player_count() == 1
+
# Next 3 functions are used for exact finish display
def get_igt(lap, player):
@@ -80,6 +156,346 @@ def get_unrounded_time(lap, player):
t += update_exact_finish(i + 1, player)
return t
-# TODO: Rotation display helper functions
+def calculate_exact_finish(positions, lap):
+ '''
+ Calculate the exact finish time, assuming this
+ function is called the 1st frame you cross the line.
+ Store it to EVA at 0x800002E0 + lap*0x4 as a 4bytes float.
+ '''
+ if len(positions) < 3:
+ return 0
+ prevPos = positions[2]['pos']
+ pos = positions[1]['pos']
+ fl1, fl2 = get_finish_line_coordinate()
+ t = time_to_cross(prevPos, pos-prevPos, fl1, fl2)
+ if not (0<= t <= 1):
+ #Error detected. We write a default value instead
+ address = 0x800002E0 + lap*0x4
+ print('Error in Exact Finish Time')
+ memory.write_f32(address, 999.999999)
+ else:
+ exact_finish = (frame_of_input()+t-241)/fps_const
+ address = 0x800002E0 + lap*0x4
+ memory.write_f32(address, exact_finish)
+
+def read_exact_finish(lap):
+ #Read exact finish from EVA, stored with above function
+ address = 0x800002E0 + lap*0x4
+ return memory.read_f32(address)
+
+
+def calculate_extra_finish_data(exact_finish):
+ '''
+ This function returns how much exact finish time
+ you need to gain a rounded millisecond, and
+ you need to lose a rounded millisecond
+ Return in MICROSECONDS
+ '''
+ t = (exact_finish*fps_const)%1
+ frame_count = math.floor(exact_finish*fps_const)
+
+ frame_rounded_ms = math.floor(frame_count/fps_const*1000)
+ subframe_rounded_ms = math.ceil(t/fps_const*1000)
+
+ if subframe_rounded_ms < 17:
+ exact_ahead = subframe_rounded_ms/1000*fps_const - t #time in frames needed for gaining 1ms
+ else:
+ exact_ahead = 1-t - (math.floor((frame_count+1)/fps_const*1000) - frame_rounded_ms - 17)/1000*fps_const
+
+ if subframe_rounded_ms > 1 :
+ exact_behind = (subframe_rounded_ms-1)/1000*fps_const - t #time in frames needed for losing 1ms
+ else:
+ exact_behind = -t - (math.floor((frame_count-1)/fps_const*1000) - frame_rounded_ms + 17)/1000*fps_const
+
+ return (round(exact_ahead/fps_const*1000000), round(exact_behind/fps_const*1000000))
+
+#Rotation display helper functions
+def quaternion_to_euler_angle(q):
+ """Param : quatf
+ Return : eulerAngle """
+ x1, x2 = 2*q.x*q.w-2*q.y*q.z, 1-2*q.x*q.x-2*q.z*q.z
+ y1, y2 = 2*q.y*q.w-2*q.x*q.z, 1-2*q.y*q.y-2*q.z*q.z
+ z = 2*q.x*q.y + 2*q.z*q.w
+ roll = 180/math.pi * math.asin(z)
+ pitch = -180/math.pi * math.atan2(x1, x2)
+ yaw = -180/math.pi * math.atan2(y1, y2)
+ return eulerAngle(pitch, yaw, roll)
+
+def get_facing_angle(player = 0):
+ """Param : int player_id
+ Return : eulerAngle , correspond to facing angles"""
+ quaternion = VehiclePhysics(player).main_rotation()
+ return quaternion_to_euler_angle(quaternion)
+
+def speed_to_euler_angle(speed):
+ """Param : vec3 speed
+ Return : eulerAngle"""
+ s = speed
+ pitch = 180/math.pi * math.atan2(s.z, s.y) #unsure and unused
+ yaw = -180/math.pi * math.atan2(s.x, s.z)
+ roll = -180/math.pi * math.atan2(s.y, s.x)#unsure and unused
+ return eulerAngle(pitch, yaw, roll)
+
+def get_moving_angle(player):
+ """Param : int player_id
+ Return : eulerAngle , correspond to moving angles"""
+ speed = delta_position(player)
+ return speed_to_euler_angle(speed)
+
+def get_unit_vectors_from_angles(angles):
+ sp, cp = math.sin(angles.pitch/180*math.pi) , math.cos(angles.pitch/180*math.pi)
+ sy, cy = math.sin(angles.yaw/180*math.pi) , math.cos(angles.yaw/180*math.pi)
+ sr, cr = math.sin(angles.roll/180*math.pi) , math.cos(angles.roll/180*math.pi)
+
+ #Vec pointing "forward"
+ y1 = sp
+ x1 = - cp * sy
+ z1 = cp * cy
+
+ #Vec pointing "upward"
+ z2 = - cy * sp * cr - sy * sr #not sure if it's ++, +-, -+ or --
+ y2 = cp * cr
+ x2 = sy * sp * cr - cy * sr
+
+ return vec3(x1,y1,z1), vec3(x2,y2,z2)
+
+def get_relative_angles(absolute_angles, up_vector):
+ r_y = up_vector
+ r_x = r_y @ vec3(0,0,1)
+ r_z = r_x @ r_y
+
+ forward_vec, upward_vec = get_unit_vectors_from_angles(absolute_angles)
+
+ pitch = math.asin(forward_vec*r_y)
+
+ return pitch*180/math.pi
+
+
+#The time difference functions.
+"""
+time_difference_[name](P1, S1, P2, S2) is a function that takes as arguments
+P1,S1 : Player1's Position and Speed vec3.
+P2,S2 : Player2's Position and Speed vec3
+Return the time it would take for Player1 to catch Player2 (not always symmetric)
+
+get_time_difference_[name](Player1, Player2) takes as arguments
+Player1 : Player1 ID
+Player2 : Player2 ID
+Return the time it would take for Player1 to catch Player2 (not always symmetric)
+It's the function called in draw_infodisplay.py
+"""
+
+def get_physics(player1, player2):
+ """Take the Player1 and Player2 ID's, return their
+ P1, S1, P2, S2 data"""
+ P1, S1 = VehiclePhysics(player1).position(), delta_position(player1)
+ P2, S2 = VehiclePhysics(player2).position(), delta_position(player2)
+ return P1,S1,P2,S2
+
+
+def get_distance_ghost_vec():
+ """Give the distance (vec3) between the player and the ghost
+ Player to ghost vec"""
+ player_position = VehiclePhysics(0).position()
+ ghost_position = VehiclePhysics(1).position()
+ return (ghost_position - player_position)
+
+def get_distance_ghost():
+ """Give the distance(float) between the player and the ghost"""
+ return get_distance_ghost_vec().length()
+
+def time_difference_absolute(P1, P2, S1, S2):
+ s = S1.length()
+ if s != 0:
+ return (P2-P1).length() / s
+ return float('inf')
+
+def get_time_difference_absolute(player1, player2):
+ """Time difference "Absolute" (simple and bad)
+ Simply takes the distance player-ghost, and divide it by raw speed (always positive)"""
+ P1, S1, P2, S2 = get_physics(player1, player2)
+ return time_difference_absolute(P1, P2, S1, S2)
+
+def time_difference_relative(P1, P2, S1, S2):
+ L = (P2 - P1).length()
+ if L == 0:
+ return 0
+ s = S1*(P2-P1)/L
+ if s == 0:
+ return float('inf')
+ return (P2-P1).length() / s
+
+def get_time_difference_relative(player1, player2):
+ """Time difference "Relative"
+ Take distance player-ghost. Divide it by the player's speed "toward" the ghost (dot product)"""
+ P1, S1, P2, S2 = get_physics(player1, player2)
+ return time_difference_relative(P1, P2, S1, S2)
+
+def time_difference_projected(P1, P2, S1, S2):
+ s = S1.length()
+ if s == 0:
+ return float('inf')
+ return (P2-P1)*S1/(s**2)
+
+def get_time_difference_projected(player1, player2):
+ """ Time difference "Projected"
+ Take the distance between the player and the plane oriented by the player speed, covering the ghost.
+ Then divide it by the player raw speed
+ This is the 2D version because no numpy"""
+ P1, S1, P2, S2 = get_physics(player1, player2)
+ return time_difference_projected(P1, P2, S1, S2)
+
+
+def time_to_cross(A, S, B, C):
+ """If A is going at a constant speed S, how many frame will it take
+ to cross the vertical plan containing B and C
+ Param : A, S, B, C : (vec3),(vec3),(vec3),(vec3)
+ Return t (float) """
+ N = (B-C)@vec3(0,1,0) #normal vector to the plan containing B,C
+ ns = N*S
+ if ns != 0:
+ return N*(B-A)/ns
+ return float('inf')
+
+
+def time_difference_crosspath(P1, P2, S1, S2):
+ t1 = time_to_cross(P1, S1, P2, P2+S2)
+ t2 = time_to_cross(P2, S2, P1, P1+S1)
+ return t1-t2
+
+
+def get_time_difference_crosspath(player1, player2):
+ """Time difference "CrossPath"
+ Take both XZ trajectories of the player and the ghost
+ Calculate how much time it takes them to reach the crosspoint. (2D only)
+ Return the difference."""
+ P1, S1, P2, S2 = get_physics(player1, player2)
+ return time_difference_crosspath(P1, P2, S1, S2)
+
+
+
+def get_finish_line_coordinate():
+ """pointA is the position of the left side of the finish line (vec3)
+ point B ---------------------right------------------------------
+ both have 0 as their Y coordinate."""
+ game_id = utils.get_game_id()
+ address = {"RMCE01": 0x809B8F28, "RMCP01": 0x809BD6E8,
+ "RMCJ01": 0x809BC748, "RMCK01": 0x809ABD28}
+ kmp_ref = chase_pointer(address[game_id], [0x4, 0x0], 'u32')
+ offset = memory.read_u32(kmp_ref+0x24)
+ pointA = vec3(memory.read_f32(kmp_ref+0x4C+offset+0x8+0x0), 0, memory.read_f32(kmp_ref+0x4C+offset+0x8+0x4))
+ pointB = vec3(memory.read_f32(kmp_ref+0x4C+offset+0x8+0x8), 0, memory.read_f32(kmp_ref+0x4C+offset+0x8+0xC))
+ return pointA, pointB
+
+
+def expected_time_left(player_id=0):
+ """return the expected time left for the
+ player before it crosses the finish line"""
+ prevPos = VehicleDynamics.position(player_id)
+ pos = VehiclePhysics.position(player_id)
+ fl1, fl2 = get_finish_line_coordinate()
+ return time_to_cross(pos, pos-prevPos, fl1, fl2)
+
+def time_difference_tofinish(P1, P2, S1, S2):
+ A,B = get_finish_line_coordinate()
+ t1 = time_to_cross(P1, S1, A, B)
+ t2 = time_to_cross(P2, S2, A, B)
+ return t1-t2
+
+def get_time_difference_tofinish(player1, player2):
+ """Assume player and ghost are not accelerated.
+ Calculate the time to the finish line for both, and takes the difference."""
+ P1, S1, P2, S2 = get_physics(player1, player2)
+ return time_difference_tofinish(P1, P2, S1, S2)
+
+
+def find_index(value, value_list):
+ """Find the index i so value_list[i]>=value>value_list[i+1]
+ We suppose value_list[i+1] < value_list[i]
+ and value_list[0]>= value>=value_list[-1]"""
+ n = len(value_list)
+ if n == 1 :
+ return 0
+ h = n//2
+ if value <= value_list[h]:
+ return h+find_index(value, value_list[h:])
+ return find_index(value, value_list[:h])
+
+def get_time_difference_racecompletion(history):
+ """Use RaceCompletionData History to calculate the frame difference
+ The function assume that RaceCompletion is increasing every frames"""
+ if history:
+ curframe = history[0]
+ lastframe = history[-1]
+ inf = float('inf')
+ if curframe['prc'] >= curframe['grc']:
+ if curframe['grc'] > lastframe['prc']:
+ l = [dic['prc'] for dic in history]
+ i = find_index(curframe['grc'], l)
+ t = i + (curframe['grc'] - l[i])/ (l[i+1] - l[i])
+ return -t
+ return -inf
+ else:
+ if curframe['prc'] > lastframe['grc']:
+ l =[dic['grc'] for dic in history]
+ i = find_index(curframe['prc'], l)
+ t = i + (curframe['prc'] - l[i])/ (l[i+1] - l[i])
+ return t
+ return inf
+ else:
+ return float('inf')
+
+
+def get_timediff_settings(string):
+ if string == 'player':
+ return 0, 1
+ if string == 'ghost':
+ return 1, 0
+ pp, sp, pg, sg = get_physics(0,1)
+ player_is_ahead = int(sp*(pg-pp)>0)
+ if string == 'ahead':
+ return 1-player_is_ahead, player_is_ahead
+ if string == 'behind':
+ return player_is_ahead, 1-player_is_ahead
+ else:
+ print('TimeDiff setting value not recognized. Default to "player"')
+ return 0, 1
+
+def player_teleport(player_id = 0,
+ x = None, y = None, z = None,
+ pitch = None, yaw = None, roll = None):
+ '''Function to teleport the player in player_id to the
+ corresponding position and rotation.
+ Use None for parameter you don't wanna change'''
+
+ addr = VehiclePhysics.chain(player_id)
+ position = VehiclePhysics.position(player_id)
+ quaternion = VehiclePhysics.main_rotation(player_id)
+ angles = eulerAngle.from_quaternion(quaternion)
+
+ if not x is None:
+ position.x = x
+ if not y is None:
+ position.y = y
+ if not z is None:
+ position.z = z
+ if not pitch is None:
+ angles.pitch = pitch
+ if not yaw is None:
+ angles.yaw = yaw
+ if not roll is None:
+ angles.roll = roll
+
+ quaternion = quatf.from_angles(angles)
+ position.write(addr + 0x68)
+ quaternion.write(addr + 0xF0)
+
+def add_angular_ev(player_id = 0, angle = 90, magnitude = 100, y = 0):
+ addr = VehiclePhysics.chain(player_id)
+ yaw = get_facing_angle(player_id).yaw
+ z = math.cos((-yaw+angle)*math.pi/180) * magnitude
+ x = math.sin((-yaw+angle)*math.pi/180) * magnitude
+ ev = vec3(x,y,z)
+ ev.write(addr + 0x74)
-# TODO: Time difference display helper functions
\ No newline at end of file
+
diff --git a/scripts/Modules/rkg_lib.py b/scripts/Modules/rkg_lib.py
new file mode 100644
index 0000000..68f8c60
--- /dev/null
+++ b/scripts/Modules/rkg_lib.py
@@ -0,0 +1,518 @@
+import configparser
+from dolphin import gui, memory, utils
+from math import floor
+import os
+import zlib
+from .mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings
+from .mkw_classes import RaceConfigPlayer, PlayerInput, KartInput, Controller
+from .framesequence import Frame, FrameSequence
+from .mkw_utils import chase_pointer
+from .src import decompress, compress
+
+def extract_bits(data, start_bit, length):
+ byte_index = start_bit // 8
+ bit_offset = start_bit % 8
+ bits = 0
+
+ for i in range(length):
+ bit_position = bit_offset + i
+ bits <<= 1
+ bits |= (data[byte_index + (bit_position // 8)] >> (7 - (bit_position % 8))) & 1
+
+ return bits
+
+def crc16(i):
+ '''Argument : bytearray
+ Return : 16bit int'''
+ #https://stackoverflow.com/questions/25239423/crc-ccitt-16-bit-python-manual-calculation
+ def _update_crc(crc, c):
+ def _initial(c):
+ POLYNOMIAL = 0x1021
+ PRESET = 0
+ crc = 0
+ c = c << 8
+ for j in range(8):
+ if (crc ^ c) & 0x8000:
+ crc = (crc << 1) ^ POLYNOMIAL
+ else:
+ crc = crc << 1
+ c = c << 1
+ return crc
+ POLYNOMIAL = 0x1021
+ PRESET = 0
+ _tab = [ _initial(i) for i in range(256) ]
+ cc = 0xff & c
+ tmp = (crc >> 8) ^ cc
+ crc = (crc << 8) ^ _tab[tmp & 0xff]
+ crc = crc & 0xffff
+ return crc
+ POLYNOMIAL = 0x1021
+ PRESET = 0
+ crc = PRESET
+ for c in i:
+ crc = _update_crc(crc, c)
+ return crc
+
+
+def convertTimer(bits: int):
+ '''Convert a 24 bits int representing a Timer (m,s,ms), to a float in seconds'''
+ ms = bits & 1023
+ bits >>= 10
+ s = bits & 127
+ bits >>= 7
+ m = bits & 127
+ return m*60+s+ms/1000
+
+def convertTimerBack(split: float):
+ '''Convert a float in seconds, to a 24 bits int representing a Timer (m,s,ms)'''
+ m = floor(split/60)
+ s = floor(split%60)
+ ms = floor(split*1000)%1000
+ return ms + (s<<10) + (m <<17)
+
+def add_bits(bit_list, number, size):
+ ''' Add to a bit list another number, with a bit size'''
+ for i in range(size):
+ bit_list.append((number >> (size - i -1)) & 1)
+
+def bits_to_bytearray(bit_list):
+ ''' Convert an array of bits to an array of bytes (grouping bits by 8)'''
+ b = bytearray()
+ byte = 0
+ for i in range(len(bit_list)):
+ byte = (byte << 1) + bit_list[i]
+ if i%8 == 7:
+ b.append(byte)
+ byte = 0
+ if byte != 0:
+ b.append(byte)
+ return b
+
+class RKGMetaData:
+ def __init__(self, rkg_data, useDefault = False):
+ if not useDefault:
+ self.finish_time = convertTimer(extract_bits(rkg_data, 4*8+0, 24))
+ self.track_id = extract_bits(rkg_data, 7*8+0, 6)
+ self.character_id = extract_bits(rkg_data, 8*8+6, 6)
+ self.vehicle_id = extract_bits(rkg_data, 8*8+0, 6)
+ self.year = extract_bits(rkg_data, 9*8+4, 7)
+ self.month = extract_bits(rkg_data, 0xA*8+3, 4)
+ self.day = extract_bits(rkg_data, 0xA*8+7, 5)
+ self.controller_id = extract_bits(rkg_data, 0xB*8+4, 4)
+ self.compressed_flag = extract_bits(rkg_data, 0xC*8+4, 1)
+ self.ghost_type = extract_bits(rkg_data, 0xC*8+7, 7)
+ self.drift_id = extract_bits(rkg_data, 0xD*8+6, 1)
+ self.input_data_length = extract_bits(rkg_data, 0xE*8+0, 16)
+ self.lap_count = extract_bits(rkg_data, 0x10*8+0, 8)
+ self.lap1_split = convertTimer(extract_bits(rkg_data, 0x11*8+0, 24))
+ self.lap2_split = convertTimer(extract_bits(rkg_data, 0x14*8+0, 24))
+ self.lap3_split = convertTimer(extract_bits(rkg_data, 0x17*8+0, 24))
+ self.country_code = extract_bits(rkg_data, 0x34*8+0, 8)
+ self.state_code = extract_bits(rkg_data, 0x35*8+0, 8)
+ self.location_code = extract_bits(rkg_data, 0x36*8+0, 16)
+ else:
+ self.finish_time = 100
+ self.track_id = 0
+ self.character_id = 0
+ self.vehicle_id = 0
+ self.year = 25
+ self.month = 1
+ self.day = 1
+ self.controller_id = 3
+ self.compressed_flag = 1
+ self.ghost_type = 38
+ self.drift_id = 0
+ self.input_data_length = 908
+ self.lap_count = 3
+ self.lap1_split = 0
+ self.lap2_split = 5999.999
+ self.lap3_split = 5999.999
+ self.country_code = 255
+ self.state_code = 255
+ self.location_code = 65535
+
+ def __iter__(self):
+ return iter([self.finish_time,
+ self.track_id,
+ self.character_id,
+ self.vehicle_id,
+ self.year,
+ self.month,
+ self.day,
+ self.controller_id,
+ self.compressed_flag,
+ self.ghost_type,
+ self.drift_id,
+ self.input_data_length,
+ self.lap_count,
+ self.lap1_split,
+ self.lap2_split,
+ self.lap3_split,
+ self.country_code,
+ self.state_code,
+ self.location_code])
+
+ def __str__(self):
+ res = ""
+ res += "finish_time = " + str(self.finish_time)+"\n"
+ res += "track_id = " + str(self.track_id)+"\n"
+ res += "character_id = " + str(self.character_id)+"\n"
+ res += "vehicle_id = " + str(self.vehicle_id)+"\n"
+ res += "year = " + str(self.year)+"\n"
+ res += "month = " + str(self.month)+"\n"
+ res += "day = " + str(self.day)+"\n"
+ res += "controller_id = " + str(self.controller_id)+"\n"
+ res += "compressed_flag = " + str(self.compressed_flag)+"\n"
+ res += "ghost_type = " + str(self.ghost_type)+"\n"
+ res += "drift_id = " + str(self.drift_id)+"\n"
+ res += "input_data_length = " + str(self.input_data_length)+"\n"
+ res += "lap_count = " + str(self.lap_count)+"\n"
+ res += "lap1_split = " + str(self.lap1_split)+"\n"
+ res += "lap2_split = " + str(self.lap2_split)+"\n"
+ res += "lap3_split = " + str(self.lap3_split)+"\n"
+ res += "country_code = " + str(self.country_code)+"\n"
+ res += "state_code = " + str(self.state_code)+"\n"
+ res += "location_code = " + str(self.location_code)
+ return res
+
+ @staticmethod
+ def from_string(string):
+ lines = string.split("\n")
+ self = RKGMetaData(None, True)
+ self.finish_time = float(lines[0].split('=')[1])
+ self.track_id = int(lines[1].split('=')[1])
+ self.character_id = int(lines[2].split('=')[1])
+ self.vehicle_id = int(lines[3].split('=')[1])
+ self.year = int(lines[4].split('=')[1])
+ self.month = int(lines[5].split('=')[1])
+ self.day = int(lines[6].split('=')[1])
+ self.controller_id = int(lines[7].split('=')[1])
+ self.compressed_flag = int(lines[8].split('=')[1])
+ self.ghost_type = int(lines[9].split('=')[1])
+ self.drift_id = int(lines[10].split('=')[1])
+ self.input_data_length = int(lines[11].split('=')[1])
+ self.lap_count = int(lines[12].split('=')[1])
+ self.lap1_split = float(lines[13].split('=')[1])
+ self.lap2_split = float(lines[14].split('=')[1])
+ self.lap3_split = float(lines[15].split('=')[1])
+ self.country_code = int(lines[16].split('=')[1])
+ self.state_code = int(lines[17].split('=')[1])
+ self.location_code = int(lines[18].split('=')[1])
+ return self
+
+ def to_bytes(self):
+ bit_list = []
+ add_bits(bit_list, convertTimerBack(self.finish_time), 24)
+ add_bits(bit_list, self.track_id, 6)
+ add_bits(bit_list, 0, 2)
+ add_bits(bit_list, self.vehicle_id, 6)
+ add_bits(bit_list, self.character_id, 6)
+ add_bits(bit_list, self.year, 7)
+ add_bits(bit_list, self.month, 4)
+ add_bits(bit_list, self.day, 5)
+ add_bits(bit_list, self.controller_id, 4)
+ add_bits(bit_list, 0, 4)
+ add_bits(bit_list, self.compressed_flag, 1)
+ add_bits(bit_list, 0, 2)
+ add_bits(bit_list, self.ghost_type, 7)
+ add_bits(bit_list, self.drift_id, 1)
+ add_bits(bit_list, 0, 1)
+ add_bits(bit_list, self.input_data_length, 8*2)
+ add_bits(bit_list, self.lap_count, 8*1)
+ add_bits(bit_list, convertTimerBack(self.lap1_split), 24)
+ add_bits(bit_list, convertTimerBack(self.lap2_split), 24)
+ add_bits(bit_list, convertTimerBack(self.lap3_split), 24)
+ add_bits(bit_list, 0, 2*3*8)
+ add_bits(bit_list, 0, 8*0x14)
+ add_bits(bit_list, self.country_code, 8)
+ add_bits(bit_list, self.state_code, 8)
+ add_bits(bit_list, self.location_code, 8*2)
+ add_bits(bit_list, 0, 8*4)
+
+ return bytearray(b'RKGD') + bits_to_bytearray(bit_list)
+
+ @staticmethod
+ def from_current_race():
+ metadata = RKGMetaData(None, True)
+ race_config_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario())
+ race_config_settings = RaceConfigSettings(addr=race_config_scenario.settings())
+ race_config_player = RaceConfigPlayer(addr=race_config_scenario.player())
+ player_input = PlayerInput(player_idx=0)
+ kart_input = KartInput(player_input.kart_input())
+ controller = Controller(addr=kart_input.race_controller())
+
+ metadata.track_id = race_config_settings.course_id().value
+ metadata.vehicle_id = race_config_player.vehicle_id().value
+ metadata.character_id = race_config_player.character_id().value
+ metadata.drift_id = int(controller.drift_is_auto())
+
+ return metadata
+
+
+def decompress_ghost_input(ghost_src):
+ if len(ghost_src) > 0x8F and ghost_src[0x8C:0x90] == b'Yaz1':
+ uncompressed_size = (ghost_src[0x90] << 24) + (ghost_src[0x91] << 16) + (ghost_src[0x92] << 8) + ghost_src[0x93]
+ return decode_Yaz1(ghost_src, 0x9c, uncompressed_size)
+ else:
+ return list(ghost_src[0x88:])
+
+def decode_Yaz1(src, offset, uncompressed_size):
+ src_pos = offset
+ valid_bit_count = 0
+ try:
+ curr_code_byte = src[offset + src_pos]
+ except:
+ curr_code_byte = src[src_pos]
+
+ dst = []
+
+ while len(dst) < uncompressed_size:
+ if valid_bit_count == 0:
+ curr_code_byte = src[src_pos]
+ src_pos += 1
+ valid_bit_count = 8
+
+ if (curr_code_byte & 0x80) != 0:
+ dst.append(src[src_pos])
+ src_pos += 1
+ else:
+ byte1 = src[src_pos]
+ byte2 = src[src_pos + 1]
+ src_pos += 2
+ dist = ((byte1 & 0xF) << 8) | byte2
+ copy_source = len(dst) - (dist + 1)
+ num_bytes = byte1 >> 4
+ if num_bytes == 0:
+ num_bytes = src[src_pos] + 0x12
+ src_pos += 1
+ else:
+ num_bytes += 2
+
+ for _ in range(num_bytes):
+ dst.append(dst[copy_source])
+ copy_source += 1
+
+ curr_code_byte <<= 1
+ valid_bit_count -= 1
+
+ return dst
+
+
+def decode_rkg_inputs(rkg_data):
+ raw_data = decompress_ghost_input(rkg_data)
+ button_inputs = []
+ analog_inputs = []
+ trick_inputs = []
+ cur_byte = 8
+ nr_button_inputs = (raw_data[0] << 8) | raw_data[1]
+ nr_analog_inputs = (raw_data[2] << 8) | raw_data[3]
+ nr_trick_inputs = (raw_data[4] << 8) | raw_data[5]
+
+ for _ in range(nr_button_inputs):
+ if cur_byte + 1 < len(raw_data):
+ inputs = raw_data[cur_byte]
+ frames = raw_data[cur_byte + 1]
+ else:
+ inputs = 0
+ frames = 0
+ accelerator = inputs & 0x1
+ drift = (inputs & 0x2) >> 1
+ item = (inputs & 0x4) >> 2
+ pseudoAB = (inputs & 0x8) >> 3
+ breakdrift = (inputs & 0x10) >> 4
+ button_inputs += [(accelerator, drift, item, pseudoAB, breakdrift)] * frames
+ cur_byte += 2
+
+ for _ in range(nr_analog_inputs):
+ if cur_byte + 1 < len(raw_data):
+ inputs = raw_data[cur_byte]
+ frames = raw_data[cur_byte + 1]
+ else:
+ inputs = 0
+ frames = 0
+ horizontal = ((inputs >> 4) & 0xF) - 7
+ vertical = (inputs & 0xF) - 7
+ analog_inputs += [(horizontal, vertical)] * frames
+ cur_byte += 2
+
+ for _ in range(nr_trick_inputs):
+ if cur_byte + 1 < len(raw_data):
+ inputs = raw_data[cur_byte]
+ frames = raw_data[cur_byte + 1]
+ else:
+ inputs = 0
+ frames = 0
+ trick = (inputs & 0x70) >> 4
+ extra_frames = (inputs & 0x0F) << 8
+ trick_inputs += [trick] * (frames + extra_frames)
+ cur_byte += 2
+ inputList = []
+ for i in range(len(button_inputs)):
+ if i >= len(analog_inputs):
+ analog_inputs.append((0,0))
+ if i >= len(trick_inputs):
+ trick_inputs.append(0)
+ inputList.append(Frame([button_inputs[i][0],
+ button_inputs[i][1],
+ button_inputs[i][2],
+ button_inputs[i][3],
+ button_inputs[i][4],
+ analog_inputs[i][0],
+ analog_inputs[i][1],
+ trick_inputs[i]]))
+ res = FrameSequence()
+ res.read_from_list_of_frames(inputList)
+ return res
+
+
+def encodeFaceButton(aButton, bButton, lButton, pabButton, bdButton):
+ return aButton * 0x1 + bButton * 0x2 + lButton * 0x4 + pabButton * 0x8 + bdButton * 0x10
+
+def encodeDirectionInput(horizontalInput, verticalInput):
+ return ((horizontalInput+7) << 4) + verticalInput+7
+
+def encodeTrickInput(trickInput):
+ return trickInput << 4
+
+def encodeRKGFaceButtonInput(inputList : 'FrameSequence'):
+ data = bytearray()
+ iL = inputList.frames
+ fbBytes = 0
+ if len(iL) == 0 :
+ return data, fbBytes
+
+ prevInput = encodeFaceButton(iL[0].accel, iL[0].brake, iL[0].item, iL[0].drift, iL[0].brakedrift) #Not sure if it should be 0 instead
+ amountCurrentFrames = 0x0
+ for ipt in iL:
+ currentInput = encodeFaceButton(ipt.accel, ipt.brake, ipt.item, ipt.drift, ipt.brakedrift)
+ if prevInput != currentInput or amountCurrentFrames >= 0xFF:
+ data.append(prevInput)
+ data.append(amountCurrentFrames)
+ prevInput = currentInput
+ amountCurrentFrames = 0x1
+ fbBytes = fbBytes + 1
+ else:
+ amountCurrentFrames = amountCurrentFrames + 1
+ data.append(prevInput)
+ data.append(amountCurrentFrames)
+ fbBytes = fbBytes + 1
+ return data, fbBytes
+
+def encodeRKGDirectionInput(inputList : 'FrameSequence'):
+ data = bytearray()
+ iL = inputList.frames
+ diBytes = 0
+ if len(iL) == 0 :
+ return data, diBytes
+
+ prevInput = encodeDirectionInput(iL[0].stick_x, iL[0].stick_y)
+ amountCurrentFrames = 0x0
+ for ipt in iL:
+ currentInput = encodeDirectionInput(ipt.stick_x, ipt.stick_y)
+ if prevInput != currentInput or amountCurrentFrames >= 0xFF:
+ data.append(prevInput)
+ data.append(amountCurrentFrames)
+ prevInput = currentInput
+ amountCurrentFrames = 0x1
+ diBytes = diBytes + 1
+ else:
+ amountCurrentFrames = amountCurrentFrames + 1
+ data.append(prevInput)
+ data.append(amountCurrentFrames)
+ diBytes = diBytes + 1
+ return data, diBytes
+
+def encodeRKGTrickInput(inputList : 'FrameSequence'):
+ data = bytearray()
+ iL = inputList.frames
+ tiBytes = 0
+ if len(iL) == 0 :
+ return data, tiBytes
+
+ prevInput = encodeTrickInput(iL[0].dpad_raw())
+ amountCurrentFrames = 0x0
+ for ipt in iL:
+ currentInput = encodeTrickInput(ipt.dpad_raw())
+ if prevInput != currentInput or amountCurrentFrames >= 0xFFF:
+ data.append(prevInput + (amountCurrentFrames >> 8))
+ data.append(amountCurrentFrames % 0x100)
+ prevInput = currentInput
+ amountCurrentFrames = 0x1
+ tiBytes = tiBytes + 1
+ else:
+ amountCurrentFrames = amountCurrentFrames + 1
+
+ data.append(prevInput + (amountCurrentFrames >> 8))
+ data.append(amountCurrentFrames % 0x100)
+ tiBytes = tiBytes + 1
+ return data, tiBytes
+
+def encodeRKGInput(inputList : 'FrameSequence'):
+ #Encode face buttons inputs
+ fbData, fbBytes = encodeRKGFaceButtonInput(inputList)
+
+ #Encode joystick inputs
+ diData, diBytes = encodeRKGDirectionInput(inputList)
+
+ #Encode trick inputs
+ tiData, tiBytes = encodeRKGTrickInput(inputList)
+
+ return fbData+diData+tiData, fbBytes, diBytes, tiBytes
+
+
+def decode_RKG(raw_data : bytearray):
+ ''' Decode RKG data to 3 objetcs :
+ - RKGMetaData
+ - FrameSequence
+ - Mii Raw data (bytearray, size 0x4A)'''
+ if len(raw_data) < 0x88:
+ print("decode_RKG can't decode raw_data : raw_data too small")
+ else:
+ metadata = RKGMetaData(raw_data)
+ inputList = decode_rkg_inputs(raw_data)
+ mii_data = raw_data[0x3C:0x86]
+ return metadata, inputList, mii_data
+
+
+def encode_RKG(metadata : 'RKGMetaData', inputList : 'FrameSequence', mii_data : bytearray):
+ ''' Encode to a RKG raw data (bytearray) '''
+ inputData, fbBytes, diBytes, tiBytes = encodeRKGInput(inputList)
+ dataIndex = (fbBytes + diBytes + tiBytes) * 0x2
+ metadata.input_data_length = dataIndex + 0x8
+ crc16_int = crc16(mii_data)
+
+ metadata_bytes = metadata.to_bytes()
+ crc16_data = bytearray([crc16_int >> 8, crc16_int & 0xFF])
+ inputHeader = bytearray([fbBytes >> 8, fbBytes & 0xFF, diBytes >> 8, diBytes & 0xFF, tiBytes >> 8, tiBytes & 0xFF, 0, 0])
+
+ if metadata.compressed_flag:
+ compressed_input_data = compress(inputHeader + inputData)
+ rkg_data = metadata_bytes + mii_data + crc16_data + len(compressed_input_data).to_bytes(4, 'big') + compressed_input_data
+ else:
+ rkg_data = metadata_bytes + mii_data + crc16_data + inputHeader + inputData
+ crc32 = zlib.crc32(rkg_data)
+ arg1 = floor(crc32 / 0x1000000)
+ arg2 = floor((crc32 & 0x00FF0000) / 0x10000)
+ arg3 = floor((crc32 & 0x0000FF00) / 0x100)
+ arg4 = floor(crc32 % 0x100)
+
+ return rkg_data + bytearray([arg1, arg2, arg3, arg4])
+
+
+def get_RKG_data_memory():
+ ''' Return (is_available, rkg_data) : (bool, bytearray)
+ rkg_data is the uncompressed rkg created by the game when the race ends
+ It's only available after the text "A ghost has been created" is displayed.
+ is_available is a boolean saying if the text has appeared yet'''
+ region = utils.get_game_id()
+ try:
+ address = {"RMCE01": 0x809B8F88, "RMCP01": 0x809BD748,
+ "RMCJ01": 0x809BC7A8, "RMCK01": 0x809ABD88}
+ rkg_addr = chase_pointer(address[region], [0x18], 'u32')
+ except KeyError:
+ raise RegionError
+ if not memory.read_u32(rkg_addr) == 0x524b4744 :
+ return False, None
+ else:
+ return True, memory.read_bytes(rkg_addr, 0x2800)
+
diff --git a/scripts/Modules/settings_utils.py b/scripts/Modules/settings_utils.py
new file mode 100644
index 0000000..4d311e4
--- /dev/null
+++ b/scripts/Modules/settings_utils.py
@@ -0,0 +1,249 @@
+from dolphin import utils
+import configparser
+import os
+
+
+################# INFO DISPLAY CONFIG ########################
+class InfoDisplayConfigInstance():
+ def __init__(self, config : configparser.ConfigParser):
+ self.debug = config['DEBUG'].getboolean('Debug')
+ self.frame_count = config['INFO DISPLAY'].getboolean('Frame Count')
+ self.rkg_buffer_size = config['INFO DISPLAY'].getboolean('RKG Buffer Size')
+ self.lap_splits = config['INFO DISPLAY'].getboolean('Lap Splits')
+ self.speed = config['INFO DISPLAY'].getboolean('Speed')
+ self.speed_oriented = config['INFO DISPLAY'].getboolean('Oriented Speed')
+ self.iv = config['INFO DISPLAY'].getboolean('Internal Velocity (X, Y, Z)')
+ self.iv_oriented = config['INFO DISPLAY'].getboolean('Oriented Internal Velocity')
+ self.iv_xyz = config['INFO DISPLAY'].getboolean('Internal Velocity (XYZ)')
+ self.ev = config['INFO DISPLAY'].getboolean('External Velocity (X, Y, Z)')
+ self.ev_oriented = config['INFO DISPLAY'].getboolean('Oriented External Velocity')
+ self.ev_xyz = config['INFO DISPLAY'].getboolean('External Velocity (XYZ)')
+ self.mrv = config['INFO DISPLAY'].getboolean('Moving Road Velocity (X, Y, Z)')
+ self.mrv_oriented = config['INFO DISPLAY'].getboolean('Oriented Moving Road Velocity')
+ self.mrv_xyz = config['INFO DISPLAY'].getboolean('Moving Road Velocity (XYZ)')
+ self.mwv = config['INFO DISPLAY'].getboolean('Moving Water Velocity (X, Y, Z)')
+ self.mwv_oriented = config['INFO DISPLAY'].getboolean('Oriented Moving Water Velocity')
+ self.mwv_xyz = config['INFO DISPLAY'].getboolean('Moving Water Velocity (XYZ)')
+ self.charges = config['INFO DISPLAY'].getboolean('Charges and Boosts')
+ self.cps = config['INFO DISPLAY'].getboolean('Checkpoints and Completion')
+ self.air = config['INFO DISPLAY'].getboolean('Airtime')
+ self.misc = config['INFO DISPLAY'].getboolean('Miscellaneous')
+ self.surfaces = config['INFO DISPLAY'].getboolean('Surface Properties')
+ self.position = config['INFO DISPLAY'].getboolean('Position')
+ self.rotation = config['INFO DISPLAY'].getboolean('Rotation')
+ self.dpg = config['INFO DISPLAY'].getboolean('Distance Player-Ghost (X, Y, Z)')
+ self.dpg_oriented = config['INFO DISPLAY'].getboolean('Oriented Distance Player-Ghost')
+ self.dpg_xyz = config['INFO DISPLAY'].getboolean('Distance Player-Ghost (XYZ)')
+ self.vd_spd = config['INFO DISPLAY'].getboolean('Player-Ghost Speed diff')
+ self.vd_iv = config['INFO DISPLAY'].getboolean('Player-Ghost IV diff')
+ self.vd_ev = config['INFO DISPLAY'].getboolean('Player-Ghost EV diff')
+ self.rd_pitch = config['INFO DISPLAY'].getboolean('Player-Ghost Pitch diff')
+ self.rd_yaw = config['INFO DISPLAY'].getboolean('Player-Ghost Facing Yaw diff')
+ self.rd_movy = config['INFO DISPLAY'].getboolean('Player-Ghost Moving Yaw diff')
+ self.rd_roll = config['INFO DISPLAY'].getboolean('Player-Ghost Roll diff')
+ self.td_absolute = config['INFO DISPLAY'].getboolean('TimeDiff Absolute')
+ self.td_relative = config['INFO DISPLAY'].getboolean('TimeDiff Relative')
+ self.td_projected = config['INFO DISPLAY'].getboolean('TimeDiff Projected')
+ self.td_crosspath = config['INFO DISPLAY'].getboolean('TimeDiff CrossPath')
+ self.td_tofinish = config['INFO DISPLAY'].getboolean('TimeDiff ToFinish')
+ self.td_racecomp = config['INFO DISPLAY'].getboolean('TimeDiff RaceComp')
+ self.td_set = config['INFO DISPLAY']['TimeDiff Setting']
+ self.td = self.td_absolute or self.td_relative or self.td_projected or self.td_crosspath or self.td_tofinish or self.td_racecomp
+ self.stick = config['INFO DISPLAY'].getboolean('Stick')
+ self.color = int(config['INFO DISPLAY']['Text Color (ARGB)'], 16)
+ self.digits = min(7, config['INFO DISPLAY'].getint('Digits (to round to)'))
+ self.history_size = config['INFO DISPLAY'].getint('History Size')
+
+ def write_to_file(self):
+ config = configparser.ConfigParser()
+ config['DEBUG'] = {}
+ config['DEBUG']['Debug'] = str(self.debug)
+
+ config['INFO DISPLAY'] = {}
+ config['INFO DISPLAY']["Frame Count"] = str(self.frame_count)
+ config['INFO DISPLAY']["RKG Buffer Size"] = str(self.rkg_buffer_size)
+ config['INFO DISPLAY']["Lap Splits"] = str(self.lap_splits)
+ config['INFO DISPLAY']["Speed"] = str(self.speed)
+ config['INFO DISPLAY']["Oriented Speed"] = str(self.speed_oriented)
+ config['INFO DISPLAY']["Internal Velocity (X, Y, Z)"] = str(self.iv)
+ config['INFO DISPLAY']["Oriented Internal Velocity"] = str(self.iv_oriented)
+ config['INFO DISPLAY']["Internal Velocity (XYZ)"] = str(self.iv_xyz)
+ config['INFO DISPLAY']["External Velocity (X, Y, Z)"] = str(self.ev)
+ config['INFO DISPLAY']["Oriented External Velocity"] = str(self.ev_oriented)
+ config['INFO DISPLAY']["External Velocity (XYZ)"] = str(self.ev_xyz)
+ config['INFO DISPLAY']["Moving Road Velocity (X, Y, Z)"] = str(self.mrv)
+ config['INFO DISPLAY']["Oriented Moving Road Velocity"] = str(self.mrv_oriented)
+ config['INFO DISPLAY']["Moving Road Velocity (XYZ)"] = str(self.mrv_xyz)
+ config['INFO DISPLAY']["Moving Water Velocity (X, Y, Z)"] = str(self.mwv)
+ config['INFO DISPLAY']["Oriented Moving Water Velocity"] = str(self.mwv_oriented)
+ config['INFO DISPLAY']["Moving Water Velocity (XYZ)"] = str(self.mwv_xyz)
+ config['INFO DISPLAY']["Charges and Boosts"] = str(self.charges)
+ config['INFO DISPLAY']["Checkpoints and Completion"] = str(self.cps)
+ config['INFO DISPLAY']["Airtime"] = str(self.air)
+ config['INFO DISPLAY']["Miscellaneous"] = str(self.misc)
+ config['INFO DISPLAY']["Surface Properties"] = str(self.surfaces)
+ config['INFO DISPLAY']["Position"] = str(self.position)
+ config['INFO DISPLAY']["Rotation"] = str(self.rotation)
+ config['INFO DISPLAY']["Distance Player-Ghost (X, Y, Z)"] = str(self.dpg)
+ config['INFO DISPLAY']["Oriented Distance Player-Ghost"] = str(self.dpg_oriented)
+ config['INFO DISPLAY']["Distance Player-Ghost (XYZ)"] = str(self.dpg_xyz)
+ config['INFO DISPLAY']['Player-Ghost Speed diff'] = str(self.vd_spd)
+ config['INFO DISPLAY']['Player-Ghost IV diff'] = str(self.vd_iv)
+ config['INFO DISPLAY']['Player-Ghost EV diff'] = str(self.vd_ev)
+ config['INFO DISPLAY']['Player-Ghost Pitch diff'] = str(self.rd_pitch)
+ config['INFO DISPLAY']['Player-Ghost Facing Yaw diff'] = str(self.rd_yaw)
+ config['INFO DISPLAY']['Player-Ghost Moving Yaw diff'] = str(self.rd_movy)
+ config['INFO DISPLAY']['Player-Ghost Roll diff'] = str(self.rd_roll)
+ config['INFO DISPLAY']["TimeDiff Absolute"] = str(self.td_absolute)
+ config['INFO DISPLAY']["TimeDiff Relative"] = str(self.td_relative)
+ config['INFO DISPLAY']["TimeDiff Projected"] = str(self.td_projected)
+ config['INFO DISPLAY']["TimeDiff CrossPath"] = str(self.td_crosspath)
+ config['INFO DISPLAY']["TimeDiff ToFinish"] = str(self.td_tofinish)
+ config['INFO DISPLAY']["TimeDiff RaceComp"] = str(self.td_racecomp)
+ config['INFO DISPLAY']["TimeDiff Setting"] = self.td_set
+ config['INFO DISPLAY']["Stick"] = str(self.stick)
+ config['INFO DISPLAY']["Text Color (ARGB)"] = str(self.color)
+ config['INFO DISPLAY']["Digits (to round to)"] = str(self.digits)
+ config['INFO DISPLAY']["History Size"] = str(self.history_size)
+
+ file_path = os.path.join(utils.get_script_dir(), 'Settings', 'Infodisplay.ini')
+ with open(file_path, 'w') as f:
+ config.write(f)
+
+def populate_default_config_infodisplay(file_path):
+ config = configparser.ConfigParser()
+
+ config['DEBUG'] = {}
+ config['DEBUG']['Debug'] = "False"
+
+ config['INFO DISPLAY'] = {}
+ config['INFO DISPLAY']["Frame Count"] = "True"
+ config['INFO DISPLAY']["RKG Buffer Size"] = "False"
+ config['INFO DISPLAY']["Lap Splits"] = "False"
+ config['INFO DISPLAY']["Speed"] = "True"
+ config['INFO DISPLAY']["Oriented Speed"] = "False"
+ config['INFO DISPLAY']["Internal Velocity (X, Y, Z)"] = "False"
+ config['INFO DISPLAY']["Oriented Internal Velocity"] = "False"
+ config['INFO DISPLAY']["Internal Velocity (XYZ)"] = "False"
+ config['INFO DISPLAY']["External Velocity (X, Y, Z)"] = "False"
+ config['INFO DISPLAY']["Oriented External Velocity"] = "False"
+ config['INFO DISPLAY']["External Velocity (XYZ)"] = "True"
+ config['INFO DISPLAY']["Moving Road Velocity (X, Y, Z)"] = "False"
+ config['INFO DISPLAY']["Oriented Moving Road Velocity"] = "False"
+ config['INFO DISPLAY']["Moving Road Velocity (XYZ)"] = "False"
+ config['INFO DISPLAY']["Moving Water Velocity (X, Y, Z)"] = "False"
+ config['INFO DISPLAY']["Oriented Moving Water Velocity"] = "False"
+ config['INFO DISPLAY']["Moving Water Velocity (XYZ)"] = "False"
+ config['INFO DISPLAY']["Charges and Boosts"] = "True"
+ config['INFO DISPLAY']["Checkpoints and Completion"] = "True"
+ config['INFO DISPLAY']["Airtime"] = "True"
+ config['INFO DISPLAY']["Miscellaneous"] = "False"
+ config['INFO DISPLAY']["Surface Properties"] = "False"
+ config['INFO DISPLAY']["Position"] = "False"
+ config['INFO DISPLAY']["Rotation"] = "True"
+ config['INFO DISPLAY']["Distance Player-Ghost (X, Y, Z)"] = "False"
+ config['INFO DISPLAY']["Oriented Distance Player-Ghost"] = "False"
+ config['INFO DISPLAY']["Distance Player-Ghost (XYZ)"] = "False"
+ config['INFO DISPLAY']['Player-Ghost Speed diff'] = "False"
+ config['INFO DISPLAY']['Player-Ghost IV diff'] = "False"
+ config['INFO DISPLAY']['Player-Ghost EV diff'] = "False"
+ config['INFO DISPLAY']['Player-Ghost Pitch diff'] = "False"
+ config['INFO DISPLAY']['Player-Ghost Facing Yaw diff'] = "False"
+ config['INFO DISPLAY']['Player-Ghost Moving Yaw diff'] = "False"
+ config['INFO DISPLAY']['Player-Ghost Roll diff'] = "False"
+ config['INFO DISPLAY']["TimeDiff Absolute"] = "False"
+ config['INFO DISPLAY']["TimeDiff Relative"] = "False"
+ config['INFO DISPLAY']["TimeDiff Projected"] = "True"
+ config['INFO DISPLAY']["TimeDiff CrossPath"] = "False"
+ config['INFO DISPLAY']["TimeDiff ToFinish"] = "True"
+ config['INFO DISPLAY']["TimeDiff RaceComp"] = "True"
+ config['INFO DISPLAY']["TimeDiff Setting"] = "behind"
+ config['INFO DISPLAY']["Stick"] = "True"
+ config['INFO DISPLAY']["Text Color (ARGB)"] = "0xFFFFFFFF"
+ config['INFO DISPLAY']["Digits (to round to)"] = "6"
+ config['INFO DISPLAY']["History Size"] = "200"
+
+
+ with open(file_path, 'w') as f:
+ config.write(f)
+
+ return config
+
+def get_infodisplay_config():
+ config = configparser.ConfigParser()
+ file_path = os.path.join(utils.get_script_dir(), 'Settings', 'Infodisplay.ini')
+ config.read(file_path)
+ if not config.sections():
+ config = populate_default_config_infodisplay(file_path)
+ return InfoDisplayConfigInstance(config)
+
+
+################## AGC CONFIG ########################
+class AGCConfigInstance():
+ def __init__(self, config : configparser.ConfigParser):
+ self.useFrames = config['DELAY'].getboolean('Delay unit in frame')
+ self.ghost_delay = eval(config['DELAY'].get('Ghost delay'))
+ self.player_delay = eval(config['DELAY'].get('Player delay'))
+ self.ghost_path = config['PATH'].get('Ghost .agc file path')
+ self.player_path = config['PATH'].get('Player .agc file path')
+
+def populate_default_config_agc(file_path):
+ config = configparser.ConfigParser()
+
+ config['DELAY'] = {}
+ config['DELAY']['Delay unit in frame'] = "True"
+ config['DELAY']['Ghost delay'] = "0"
+ config['DELAY']['Player delay'] = "0"
+
+ config['PATH'] = {}
+ config['PATH']['Ghost .agc file path'] = "AGC_Data/ghost_data.agc"
+ config['PATH']['Player .agc file path'] = "AGC_Data/player_data.agc"
+
+ with open(file_path, 'w') as f:
+ config.write(f)
+
+ return config
+
+def get_agc_config():
+ config = configparser.ConfigParser()
+ file_path = os.path.join(utils.get_script_dir(), 'Settings', 'AGC.ini')
+ config.read(file_path)
+ if not config.sections():
+ config = populate_default_config_agc(file_path)
+ return AGCConfigInstance(config)
+
+
+################## TTK CONFIG ########################
+class TTKConfigInstance():
+ def __init__(self, config : configparser.ConfigParser):
+ #self.ttk_path = config['PATH'].get('TTK folder path')
+ self.player_filename = config['PATH'].get('Player filename')
+ self.ghost_filename = config['PATH'].get('Ghost filename')
+ self.track_suffix = config['PATH'].getboolean('Use track suffix')
+ self.ttk_backup = config['BACKUP'].getint('Backup Amount')
+
+def populate_default_config_ttk(file_path):
+ config = configparser.ConfigParser()
+
+ config['PATH'] = {}
+ #config['PATH']['TTK folder path'] = "MKW_Inputs/"
+ config['PATH']['Player filename'] = "TTK_Player_Inputs"
+ config['PATH']['Ghost filename'] = "TTK_Ghost_Inputs"
+ config['PATH']['Use track suffix'] = 'True'
+
+ config['BACKUP'] = {}
+ config['BACKUP']['Backup Amount'] = '5'
+
+ with open(file_path, 'w') as f:
+ config.write(f)
+
+ return config
+
+def get_ttk_config():
+ config = configparser.ConfigParser()
+ file_path = os.path.join(utils.get_script_dir(), 'Settings', 'TTK.ini')
+ config.read(file_path)
+ if not config.sections():
+ config = populate_default_config_ttk(file_path)
+ return TTKConfigInstance(config)
+
diff --git a/scripts/Modules/src/__init__.py b/scripts/Modules/src/__init__.py
new file mode 100644
index 0000000..e36beed
--- /dev/null
+++ b/scripts/Modules/src/__init__.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# libyaz0
+# Version 0.5
+# Copyright © 2017-2018 MasterVermilli0n / AboodXD
+
+# This file is part of libyaz0.
+
+# libyaz0 is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# libyaz0 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+################################################################
+################################################################
+
+try:
+ from . import yaz0_pyd as yaz0
+
+except:
+ try:
+ from . import yaz0_so as yaz0
+
+ except:
+ try:
+ import pyximport
+ pyximport.install()
+
+ from . import yaz0_cy as yaz0
+
+ except:
+ from . import yaz0
+
+
+def IsYazCompressed(data):
+ return data[:4] in [b'Yaz0', b'Yaz1']
+
+
+def decompress(data):
+ isYaz = IsYazCompressed(data)
+
+ if isYaz:
+ return yaz0.DecompressYaz(bytes(data))
+
+ else:
+ raise ValueError("Not Yaz0 compressed!")
+
+
+def compress(data, alignment=0, level=1):
+ compressed_data = yaz0.CompressYaz(bytes(data), level)
+
+ result = bytearray(b'Yaz1')
+ result += len(data).to_bytes(4, "big")
+ result += alignment.to_bytes(4, "big")
+ result += b'\0\0\0\0'
+ result += compressed_data
+
+ return result
+
+
+def guessFileExt(data):
+ if data[0:4] == b"FRES":
+ return ".bfres"
+
+ elif data[0:4] == b"FFNT":
+ return ".bffnt"
+
+ elif data[0:4] == b"BNTX":
+ return ".bntx"
+
+ elif data[0:4] == b"BNSH":
+ return ".bnsh"
+
+ elif data[0:4] == b"FLAN":
+ return ".bflan"
+
+ elif data[0:4] == b"FLYT":
+ return ".bflyt"
+
+ elif data[0:4] == b"Gfx2":
+ return ".gtx"
+
+ elif data[0:4] == b"SARC":
+ return ".sarc"
+
+ elif data[0:4] == b"Yaz0":
+ return ".szs"
+
+ elif data[-0x28:-0x24] == b"FLIM":
+ return ".bflim"
+
+ else: # Couldn't guess the file extension
+ return ".bin"
+
+
+__all__ = [IsYazCompressed, decompress, compress, guessFileExt]
diff --git a/scripts/Modules/src/yaz0.py b/scripts/Modules/src/yaz0.py
new file mode 100644
index 0000000..191acc4
--- /dev/null
+++ b/scripts/Modules/src/yaz0.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# libyaz0
+# Version 0.5
+# Copyright © 2017-2018 MasterVermilli0n / AboodXD
+
+# This file is part of libyaz0.
+
+# libyaz0 is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# libyaz0 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+################################################################
+################################################################
+
+import struct
+
+
+def DecompressYaz(src):
+ src_end = len(src)
+
+ dest_end = struct.unpack(">I", src[4:8])[0]
+ dest = bytearray(dest_end)
+
+ code = src[16]
+
+ src_pos = 17
+ dest_pos = 0
+
+ while src_pos < src_end and dest_pos < dest_end:
+ for _ in range(8):
+ if src_pos >= src_end or dest_pos >= dest_end:
+ break
+
+ if code & 0x80:
+ dest[dest_pos] = src[src_pos]; dest_pos += 1; src_pos += 1
+
+ else:
+ b1 = src[src_pos]; src_pos += 1
+ b2 = src[src_pos]; src_pos += 1
+
+ copy_src = dest_pos - ((b1 & 0x0f) << 8 | b2) - 1
+
+ n = b1 >> 4
+ if not n:
+ n = src[src_pos] + 0x12; src_pos += 1
+
+ else:
+ n += 2
+
+ for _ in range(n):
+ dest[dest_pos] = dest[copy_src]; dest_pos += 1; copy_src += 1
+
+ code <<= 1
+
+ else:
+ if src_pos >= src_end or dest_pos >= dest_end:
+ break
+
+ code = src[src_pos]; src_pos += 1
+
+ return bytes(dest)
+
+
+def compressionSearch(src, pos, max_len, search_range, src_end):
+ found_len = 1
+ found = 0
+
+ if pos + 2 < src_end:
+ search = pos - search_range
+ if search < 0:
+ search = 0
+
+ cmp_end = pos + max_len
+ if cmp_end > src_end:
+ cmp_end = src_end
+
+ c1 = src[pos:pos + 1]
+ while search < pos:
+ search = src.find(c1, search, pos)
+ if search == -1:
+ break
+
+ cmp1 = search + 1
+ cmp2 = pos + 1
+
+ while cmp2 < cmp_end and src[cmp1] == src[cmp2]:
+ cmp1 += 1; cmp2 += 1
+
+ len_ = cmp2 - pos
+
+ if found_len < len_:
+ found_len = len_
+ found = search
+ if found_len == max_len:
+ break
+
+ search += 1
+
+ return found, found_len
+
+
+def CompressYaz(src, level):
+ if not level:
+ search_range = 0
+
+ elif level < 9:
+ search_range = 0x10e0 * level // 9 - 0x0e0
+
+ else:
+ search_range = 0x1000
+
+ pos = 0
+ src_end = len(src)
+
+ dest = bytearray()
+ code_byte_pos = 0
+
+ max_len = 0x111
+
+ while pos < src_end:
+ code_byte_pos = len(dest)
+ dest.append(0)
+
+ for i in range(8):
+ if pos >= src_end:
+ break
+
+ found_len = 1
+
+ if search_range:
+ found, found_len = compressionSearch(src, pos, max_len, search_range, src_end)
+
+ if found_len > 2:
+ delta = pos - found - 1
+
+ if found_len < 0x12:
+ dest.append(delta >> 8 | (found_len - 2) << 4)
+ dest.append(delta & 0xFF)
+
+ else:
+ dest.append(delta >> 8)
+ dest.append(delta & 0xFF)
+ dest.append((found_len - 0x12) & 0xFF)
+
+ pos += found_len
+
+ else:
+ dest[code_byte_pos] |= 1 << (7 - i)
+ dest.append(src[pos]); pos += 1
+
+ return bytes(dest)
diff --git a/scripts/Modules/src/yaz0_cy.pyx b/scripts/Modules/src/yaz0_cy.pyx
new file mode 100644
index 0000000..c4ef964
--- /dev/null
+++ b/scripts/Modules/src/yaz0_cy.pyx
@@ -0,0 +1,204 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# libyaz0
+# Version 0.5
+# Copyright © 2017-2018 MasterVermilli0n / AboodXD
+
+# This file is part of libyaz0.
+
+# libyaz0 is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# libyaz0 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+################################################################
+################################################################
+
+from cpython cimport array
+from cython cimport view
+from libc.stdlib cimport malloc, free
+from libc.string cimport memchr
+
+
+ctypedef unsigned char u8
+ctypedef char s8
+ctypedef unsigned int u32
+
+
+cpdef bytes DecompressYaz(bytes src_):
+ cdef:
+ array.array dataArr = array.array('B', src_)
+ u8 *src = dataArr.data.as_uchars
+ u8 *src_end = src + len(src_)
+
+ u32 dest_len = (src[4] << 24 | src[5] << 16 | src[6] << 8 | src[7])
+ u8 *dest = malloc(dest_len)
+ u8 *dest_pos = dest
+ u8 *dest_end = dest + dest_len
+
+ u8 code = src[16]
+
+ u8 b1, b2
+ u8 *copy_src
+ int n
+
+ src += 17
+
+ try:
+ while src < src_end and dest < dest_end:
+ for _ in range(8):
+ if src >= src_end or dest >= dest_end:
+ break
+
+ if code & 0x80:
+ dest[0] = src[0]; dest += 1; src += 1
+
+ else:
+ b1 = src[0]; src += 1
+ b2 = src[0]; src += 1
+
+ copy_src = dest - ((b1 & 0x0f) << 8 | b2) - 1
+
+ n = b1 >> 4
+ if not n:
+ n = src[0] + 0x12; src += 1
+
+ else:
+ n += 2
+
+ for _ in range(n):
+ dest[0] = copy_src[0]; dest += 1; copy_src += 1
+
+ code <<= 1
+
+ else:
+ if src >= src_end or dest >= dest_end:
+ break
+
+ code = src[0]; src += 1
+
+ return bytes(dest_pos)
+
+ finally:
+ free(dest_pos)
+
+
+cdef (u8 *, u32) compressionSearch(u8 *src, u8 *src_pos, int max_len, u32 range_, u8 *src_end):
+ cdef:
+ u32 found_len = 1
+
+ u8 *found
+ u8 *search
+ u8 *cmp_end
+ u8 c1
+ u8 *cmp1
+ u8 *cmp2
+ int len_
+
+ if src + 2 < src_end:
+ search = src - range_
+ if search < src_pos:
+ search = src_pos
+
+ cmp_end = src + max_len
+ if cmp_end > src_end:
+ cmp_end = src_end
+
+ c1 = src[0]
+ while search < src:
+ search = memchr(search, c1, src - search)
+ if not search:
+ break
+
+ cmp1 = search + 1
+ cmp2 = src + 1
+
+ while cmp2 < cmp_end and cmp1[0] == cmp2[0]:
+ cmp1 += 1; cmp2 += 1
+
+ len_ = cmp2 - src
+
+ if found_len < len_:
+ found_len = len_
+ found = search
+ if found_len == max_len:
+ break
+
+ search += 1
+
+ return found, found_len
+
+
+cpdef bytes CompressYaz(bytes src_, u8 opt_compr):
+ cdef u32 range_
+
+ if not opt_compr:
+ range_ = 0
+
+ elif opt_compr < 9:
+ range_ = 0x10e0 * opt_compr / 9 - 0x0e0
+
+ else:
+ range_ = 0x1000
+
+ cdef:
+ array.array dataArr = array.array('B', src_)
+ u8 *src = dataArr.data.as_uchars
+ u8 *src_pos = src
+ u8 *src_end = src + len(src_)
+
+ u8 *dest = malloc(len(src_) + (len(src_) + 8) // 8)
+ u8 *dest_pos = dest
+ u8 *code_byte = dest
+
+ int max_len = 0x111
+
+ int i
+ u32 found_len, delta
+ u8 *found
+
+ try:
+ while src < src_end:
+ code_byte = dest
+ dest[0] = 0; dest += 1
+
+ for i in range(8):
+ if src >= src_end:
+ break
+
+ found_len = 1
+
+ if range_:
+ found, found_len = compressionSearch(src, src_pos, max_len, range_, src_end)
+
+ if found_len > 2:
+ delta = src - found - 1
+
+ if found_len < 0x12:
+ dest[0] = delta >> 8 | (found_len - 2) << 4; dest += 1
+ dest[0] = delta & 0xFF; dest += 1
+
+ else:
+ dest[0] = delta >> 8; dest += 1
+ dest[0] = delta & 0xFF; dest += 1
+ dest[0] = (found_len - 0x12) & 0xFF; dest += 1
+
+ src += found_len
+
+ else:
+ code_byte[0] |= 1 << (7 - i)
+ dest[0] = src[0]; dest += 1; src += 1
+
+ return bytes(dest_pos)
+
+ finally:
+ free(dest_pos)
diff --git a/scripts/Modules/src/yaz0_pyd.pyd b/scripts/Modules/src/yaz0_pyd.pyd
new file mode 100644
index 0000000..5a320b2
Binary files /dev/null and b/scripts/Modules/src/yaz0_pyd.pyd differ
diff --git a/scripts/Modules/startslide_utils.py b/scripts/Modules/startslide_utils.py
new file mode 100644
index 0000000..077ca21
--- /dev/null
+++ b/scripts/Modules/startslide_utils.py
@@ -0,0 +1,70 @@
+from enum import Enum
+from dolphin import event, gui, utils, memory # type: ignore
+from Modules import ttk_lib
+from Modules.mkw_utils import frame_of_input
+from Modules import mkw_translations as translate
+from Modules.mkw_classes import RaceManager, RaceState
+from Modules.framesequence import FrameSequence
+import os
+
+flame_slide_bikes = ("Flame Runner"),
+mach_slide_bikes = ("Mach Bike", "Sugarscoot", "Zip Zip")
+spear_slide_bikes = ("Jet Bubble", "Phantom", "Spear", "Sneakster")
+wario_slide_bikes = ("Wario Bike")
+wiggle_slide_bikes = ("Bit Bike", "Bullet Bike", "Dolphin Dasher", "Magikruiser",
+ "Quacker", "Shooting Star", "Standard Bike L", "Standard Bike M",
+ "Standard Bike S")
+
+class Direction(Enum):
+ RIGHT = "right"
+ LEFT = "left"
+
+def check_vehicle(vehicle: str):
+ global direction
+ path = utils.get_script_dir()
+
+ if vehicle in flame_slide_bikes:
+ return os.path.join(path, "Startslides", f"flame_left.csv")
+
+ elif vehicle in spear_slide_bikes:
+ return os.path.join(path, "Startslides", f"spear_left.csv")
+
+ elif vehicle in mach_slide_bikes:
+ return os.path.join(path, "Startslides", f"mach_left.csv")
+
+ elif vehicle in wario_slide_bikes:
+ return os.path.join(path, "Startslides", f"wario_left.csv")
+
+ elif vehicle in wiggle_slide_bikes:
+ return os.path.join(path, "Startslides", f"wiggle_left.csv")
+
+ else: # Karts fall here. We take any slides, just for the startboost
+ return os.path.join(path, "Startslides", f"spear_left.csv")
+
+
+def on_state_load(is_slot, slot):
+ global player_inputs
+ if memory.is_memory_accessible():
+ player_inputs = FrameSequence(check_vehicle(translate.vehicle_id()))
+ player_inputs.read_from_file()
+
+def on_frame_advance():
+ frame = frame_of_input()
+ stage = RaceManager.state()
+
+ player_input = player_inputs[frame]
+ if (player_input and stage.value == RaceState.COUNTDOWN.value):
+ ttk_lib.write_player_inputs(player_input, mirror = True if direction == Direction.RIGHT else False)
+
+
+def execute_startslide(direction_: Direction):
+ global direction
+ direction = direction_
+
+ global player_inputs
+ player_inputs = FrameSequence(check_vehicle(translate.vehicle_id()))
+
+ event.on_savestateload(on_state_load)
+ event.on_frameadvance(on_frame_advance)
+
+ gui.add_osd_message(f"Startslide: {len(player_inputs) > 0}")
diff --git a/scripts/Modules/ttk_config.py b/scripts/Modules/ttk_config.py
index f5c70c6..a5ed9ab 100644
--- a/scripts/Modules/ttk_config.py
+++ b/scripts/Modules/ttk_config.py
@@ -1,4 +1,5 @@
from . import mkw_translations
+from Modules import settings_utils as setting
# when running undo_state_backup.py, creates up to backup_amount
# backup files for your race inputs while you are TASing
@@ -7,11 +8,17 @@
# csvs that player and ghost inputs will be written to must be tucked
# inside a function because we want to support the course changing
def text_file_path(path_name: str) -> str:
- course = mkw_translations.course_slot_abbreviation()
+ ttk_settings = setting.get_ttk_config()
+
+ if ttk_settings.track_suffix:
+ course = '_' + mkw_translations.course_slot_abbreviation()
+ else:
+ course = ''
+
text_file_paths = {
- "Player": "MKW_Inputs/MKW_Player_Inputs_" + course + ".csv",
- "Ghost": "MKW_Inputs/MKW_Ghost_Inputs_" + course + ".csv",
- "Backup": "MKW_Inputs/Backups/" + course + "_" + "backup##.csv"
+ "Player": "MKW_Inputs/" + ttk_settings.player_filename + course + ".csv",
+ "Ghost": "MKW_Inputs/" + ttk_settings.ghost_filename + course + ".csv",
+ "Backup": "MKW_Inputs/Backups/" "ttk_backup##" + course +".csv"
}
return text_file_paths[path_name]
diff --git a/scripts/Modules/ttk_lib.py b/scripts/Modules/ttk_lib.py
index bd9540a..9e329ff 100644
--- a/scripts/Modules/ttk_lib.py
+++ b/scripts/Modules/ttk_lib.py
@@ -6,10 +6,13 @@
from typing import Tuple, List, Optional
import zlib
-from .framesequence import FrameSequence
+from .framesequence import FrameSequence, Frame
+from .rkg_lib import get_RKG_data_memory, decode_rkg_inputs, encodeRKGInput
+from .rkg_lib import encodeRKGDirectionInput, encodeRKGTrickInput, encodeRKGFaceButtonInput
+from .mkw_utils import chase_pointer
from . import ttk_config
-from .mkw_classes import RaceManager, RaceState
+from .mkw_classes import RaceManager, RaceState, RaceManagerPlayer
from .mkw_classes import GhostController, GhostButtonsStream
from .mkw_classes import InputMgr, GhostWriter, PlayerInput, KartInput, Controller
from .mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings
@@ -28,8 +31,10 @@ def decode_face_button(input):
A = input % 0x2
B = (input >> 1) % 0x2
L = (input >> 2) % 0x2
+ D = (input >> 3) % 0x2 #drift button
+ BD = (input >> 4) % 0x2 #breakdrift button
- return [A, B, L]
+ return [A, B, L, D, BD]
def decode_direction_input(input):
X = input >> 4
@@ -51,7 +56,40 @@ def encode_direction_input(X, Y):
def encode_trick_input(input):
return input * 0x10
-
+
+def get_rkg_size(player_type: PlayerType, input_type: ControllerInputType) -> int:
+
+ # Determine memory region to access
+ if (player_type == PlayerType.PLAYER):
+ # This assumes player is index 0
+ # For now let's assert this is a player
+ race_config = RaceConfig()
+ race_scenario = RaceConfigScenario(addr=race_config.race_scenario())
+ race_config_player = RaceConfigPlayer(addr=race_scenario.player())
+ assert(race_config_player.type() == RaceConfigPlayerType.REAL_LOCAL)
+
+ ghost_writer = GhostWriter(addr=PlayerInput.ghost_writer())
+ stream_addr = ghost_writer.button_stream(input_type.value)
+ button_stream = GhostButtonsStream(addr=stream_addr)
+ return button_stream.sequence_count()
+ else:
+ # TODO: Ghost is index=1 if you are racing a ghost, but if you watch a replay
+ # for example, the ghost is index 0. We want to support this scenario, so
+ # we'll need to determine how to set controller_idx appropriately.
+ ghost_addr = InputMgr.ghost_controller(1)
+ ghost_controller = GhostController(addr=ghost_addr)
+ stream_addr = ghost_controller.buttons_stream(input_type.value)
+ ghost_button_stream = GhostButtonsStream(addr=stream_addr)
+ return ghost_button_stream.sequence_count()
+
+
+def get_full_rkg_size(player_type: PlayerType) -> int:
+ fb_size = get_rkg_size(player_type, ControllerInputType.FACE)
+ di_size = get_rkg_size(player_type, ControllerInputType.DI)
+ ti_size = get_rkg_size(player_type, ControllerInputType.TRICK)
+ return fb_size + di_size + ti_size
+
+
# Reads binary data in-memory for the specified section
def read_raw_rkg_data(player_type: PlayerType, input_type: ControllerInputType) -> list:
ret_list = []
@@ -92,7 +130,7 @@ def read_raw_rkg_data(player_type: PlayerType, input_type: ControllerInputType)
cur_addr += 0x2
data_tuple = memory.read_u16(cur_addr)
- if (data_tuple == 0x0000 or cur_addr >= end_addr):
+ if (cur_addr >= end_addr):
break
return ret_list
@@ -131,32 +169,40 @@ def decode_rkg_data(data: list, input_type: ControllerInputType) -> List[List[in
# Transform raw RKG data into a FrameSequence
def read_full_decoded_rkg_data(player_type: PlayerType) -> Optional[FrameSequence]:
- # First make sure we're actually in a race, otherwise we need to bail out
race_state = RaceManager.state()
- if (race_state.value < RaceState.COUNTDOWN.value):
- gui.add_osd_message("Not in race!")
- return None
- elif (race_state.value == RaceState.FINISHED_RACE.value):
- gui.add_osd_message("Race is over!")
- return None
-
- # Read each of the input types
- face_data = read_raw_rkg_data(player_type, ControllerInputType.FACE)
- di_data = read_raw_rkg_data(player_type, ControllerInputType.DI)
- trick_data = read_raw_rkg_data(player_type, ControllerInputType.TRICK)
- if not face_data or not di_data or not trick_data:
- return None
-
- # Expand into a list where each index is a frame
- face_data = decode_rkg_data(face_data, ControllerInputType.FACE)
- di_data = decode_rkg_data(di_data, ControllerInputType.DI)
- trick_data = decode_rkg_data(trick_data, ControllerInputType.TRICK)
-
- # Now transform into a framesequence
- sequence_list = [face_data[x] + di_data[x] + [trick_data[x]] for x in range(len(face_data))]
- sequence = FrameSequence()
- sequence.read_from_list(sequence_list)
- return sequence
+ if player_type == PlayerType.PLAYER and race_state.value == RaceState.FINISHED_RACE.value :
+ has_saved, rkg_data = get_RKG_data_memory()
+ if has_saved :
+ return decode_rkg_inputs(rkg_data)
+ else:
+ gui.add_osd_message("Wait for Ghost data saved message!")
+ return None
+ else:
+ # First make sure we're actually in a race, otherwise we need to bail out
+ if (race_state.value < RaceState.COUNTDOWN.value):
+ gui.add_osd_message("Not in race!")
+ return None
+ elif (race_state.value == RaceState.FINISHED_RACE.value):
+ gui.add_osd_message("Race is over!")
+ return None
+
+ # Read each of the input types
+ face_data = read_raw_rkg_data(player_type, ControllerInputType.FACE)
+ di_data = read_raw_rkg_data(player_type, ControllerInputType.DI)
+ trick_data = read_raw_rkg_data(player_type, ControllerInputType.TRICK)
+ if not face_data or not di_data or not trick_data:
+ return None
+
+ # Expand into a list where each index is a frame
+ face_data = decode_rkg_data(face_data, ControllerInputType.FACE)
+ di_data = decode_rkg_data(di_data, ControllerInputType.DI)
+ trick_data = decode_rkg_data(trick_data, ControllerInputType.TRICK)
+
+ # Now transform into a framesequence
+ sequence_list = [face_data[x] + di_data[x] + [trick_data[x]] for x in range(len(face_data))]
+ sequence = FrameSequence()
+ sequence.read_from_list(sequence_list)
+ return sequence
@dataclass
class RKGTuple:
@@ -221,7 +267,26 @@ def encode_rkg_data(input_list: FrameSequence) -> Tuple[List[int], List[int]]:
all_tuples = face_tuples+di_tuples+trick_tuples
tuple_lengths = [len(x) for x in (face_tuples, di_tuples, trick_tuples)]
return all_tuples, tuple_lengths
-
+
+
+def setPlayerRKGBuffer(input_list: FrameSequence):
+ race_config = RaceConfig()
+ race_scenario = RaceConfigScenario(addr=race_config.race_scenario())
+ race_config_player = RaceConfigPlayer(addr=race_scenario.player())
+ assert(race_config_player.type() == RaceConfigPlayerType.REAL_LOCAL)
+ ghost_writer = GhostWriter(addr=PlayerInput.ghost_writer())
+
+ for input_type in ControllerInputType:
+ input_data, input_len = [encodeRKGFaceButtonInput, encodeRKGDirectionInput, encodeRKGTrickInput][input_type.value](input_list)
+ stream_addr = ghost_writer.button_stream(input_type.value)
+ button_stream = GhostButtonsStream(addr=stream_addr)
+ address = button_stream.buffer()
+ while len(input_data) bytearray:
tuples, lengths = encode_rkg_data(input_data)
@@ -272,6 +337,35 @@ def createRKGFile(input_data: FrameSequence, track_id: int,
except ValueError:
gui.add_osd_message("Attempted to parse byte > 0xFF! Aborting RKG write.")
return bytearray()
+
+
+def write_inputs_to_current_ghost_rkg(inputList):
+ '''function used for ttk activate ghost hard'''
+ data, fbBytes, diBytes, tiBytes = encodeRKGInput(inputList)
+ controller_address = KartInput(RaceManagerPlayer(1).kart_input()).race_controller()
+ address_fb = chase_pointer(controller_address + 0x94, [0x4], 'u32')
+ address_di = chase_pointer(controller_address + 0x98, [0x4], 'u32')
+ address_ti = chase_pointer(controller_address + 0x9C, [0x4], 'u32')
+
+ address_fb_ptr = memory.read_u32(controller_address + 0x94) + 0x4
+ address_di_ptr = memory.read_u32(controller_address + 0x98) + 0x4
+ address_ti_ptr = memory.read_u32(controller_address + 0x9C) + 0x4
+
+ address_fb_len = memory.read_u32(controller_address + 0x94) + 0xC
+ address_di_len = memory.read_u32(controller_address + 0x98) + 0xC
+ address_ti_len = memory.read_u32(controller_address + 0x9C) + 0xC
+ while len(data) < 0x2774:
+ data.append(0x0)
+
+ memory.write_bytes(address_fb, data[:0x2774])
+
+ memory.write_u32(address_di_ptr, address_fb + 2*fbBytes)
+ memory.write_u32(address_fb_len, 2*fbBytes)
+
+ memory.write_u32(address_ti_ptr, address_fb + 2*(fbBytes+diBytes))
+ memory.write_u32(address_di_len, 2*diBytes)
+
+ memory.write_u32(address_ti_len, 2*tiBytes)
# This is a tiny helper function that prevents slight repetition in filepath lookups
def write_to_csv(inputs: FrameSequence, player_type: PlayerType) -> None:
@@ -405,24 +499,29 @@ def controller_patch() -> None:
memory.write_u32(patch_ptr + 0x50, memcpy_branch)
memory.invalidate_icache(patch_ptr, 0x54)
-def write_ghost_inputs(inputs: FrameSequence) -> None:
+def write_ghost_inputs(inputs: Frame, ghost_id = 1) -> None:
controller_patch()
# TODO: This assumes the ghost is index 1, which is only true when racing a ghost
- controller = Controller(addr=InputMgr.ghost_controller(1))
+ controller = Controller(addr=InputMgr.ghost_controller(ghost_id))
set_buttons(inputs, controller)
-def write_player_inputs(inputs: FrameSequence) -> None:
+def write_player_inputs(inputs: Frame, mirror = False) -> None:
controller_patch()
kart_input = KartInput(addr=PlayerInput.kart_input())
controller = Controller(addr=kart_input.race_controller())
+ if mirror:
+ inputs.stick_x = - inputs.stick_x
+ inputs.stick_y = - inputs.stick_y
set_buttons(inputs, controller)
+ if mirror:
+ inputs.stick_x = - inputs.stick_x
+ inputs.stick_y = - inputs.stick_y
def set_buttons(inputs, controller : Controller):
"""This writes button data to addresses with implicit padding in structs.
This must be called only after controller_patch()"""
addr = controller.addr
- memory.write_u8(addr + 0x4d, inputs.accel + (inputs.brake << 1) +
- (inputs.item << 2) | ((inputs.accel & inputs.brake) << 3) + 0x80)
+ memory.write_u8(addr + 0x4d, inputs.accel + (inputs.brake << 1) + (inputs.item << 2) + (inputs.drift << 3) + (inputs.brakedrift << 4) + 0x80)
memory.write_u8(addr + 0x4e, inputs.stick_x + 7)
memory.write_u8(addr + 0x4f, inputs.stick_y + 7)
memory.write_u8(addr + 0x52, inputs.dpad_raw())
diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Copy_Inputs.py b/scripts/RMC/Adv. Ghost Comp/AGC_Copy_Inputs.py
new file mode 100644
index 0000000..f2b1fbd
--- /dev/null
+++ b/scripts/RMC/Adv. Ghost Comp/AGC_Copy_Inputs.py
@@ -0,0 +1,47 @@
+from dolphin import event, gui, utils
+from Modules import agc_lib as lib
+from Modules import settings_utils as setting
+from Modules.mkw_classes import RaceManager, RaceState
+from Modules import mkw_utils as mkw_utils
+import os
+from math import floor
+
+def main():
+ global c
+ c = setting.get_agc_config()
+
+ global filename
+ filename = os.path.join(utils.get_script_dir(), c.ghost_path)
+
+ global framedatalist
+ metadata, framedatalist = lib.file_to_framedatalist(filename)
+
+ global frame
+ frame = mkw_utils.frame_of_input()
+
+if __name__ == '__main__':
+ main()
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global framedatalist
+ global c
+ global frame
+
+ if not (frame == mkw_utils.frame_of_input() or frame == mkw_utils.frame_of_input()-1):
+ c = setting.get_agc_config()
+
+ delay = c.ghost_delay
+ if not c.useFrames:
+ delay *= mkw_utils.fps_const
+
+ racestate = RaceManager().state().value
+ frame = mkw_utils.frame_of_input()
+ delayed_frame = floor(delay)+frame
+
+
+ if racestate >= RaceState.COUNTDOWN.value :
+ if 0 < delayed_frame+1 < len(framedatalist):
+ framedatalist[delayed_frame+1].load(0, True)
+
diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Instant_Replay.py b/scripts/RMC/Adv. Ghost Comp/AGC_Instant_Replay.py
new file mode 100644
index 0000000..ec86c9d
--- /dev/null
+++ b/scripts/RMC/Adv. Ghost Comp/AGC_Instant_Replay.py
@@ -0,0 +1,47 @@
+from dolphin import event, gui, utils
+from Modules import agc_lib as lib
+from Modules import settings_utils as setting
+from Modules.mkw_classes import RaceManager, RaceState
+from Modules import mkw_utils as mkw_utils
+import os
+from math import floor
+
+def main():
+
+ global loaddatalist
+ loaddatalist = {}
+
+ global storedatalist
+ storedatalist = {}
+
+ global frame
+ frame = mkw_utils.frame_of_input()
+
+if __name__ == '__main__':
+ main()
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global frame
+
+ if not (frame == mkw_utils.frame_of_input() or frame == mkw_utils.frame_of_input()-1):
+ c = setting.get_agc_config()
+ loaddatalist.clear()
+ for key in storedatalist.keys():
+ loaddatalist[key] = storedatalist[key]
+ storedatalist.clear()
+
+
+ racestate = RaceManager().state().value
+ frame = mkw_utils.frame_of_input()
+
+
+ if racestate >= RaceState.COUNTDOWN.value :
+ if frame in loaddatalist.keys():
+ loaddatalist[frame].load(1)
+
+ storedatalist[frame] = lib.AGCFrameData()
+
+
+
diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Load_Ghost.py b/scripts/RMC/Adv. Ghost Comp/AGC_Load_Ghost.py
new file mode 100644
index 0000000..293a3a5
--- /dev/null
+++ b/scripts/RMC/Adv. Ghost Comp/AGC_Load_Ghost.py
@@ -0,0 +1,60 @@
+from dolphin import event, gui, utils
+from Modules import agc_lib as lib
+from Modules import settings_utils as setting
+from Modules.mkw_classes import RaceManager, RaceState
+from Modules import mkw_utils as mkw_utils
+import os
+from math import floor
+
+def main():
+ global c
+ c = setting.get_agc_config()
+
+ global filename
+ filename = os.path.join(utils.get_script_dir(), c.ghost_path)
+
+ global framedatalist
+ global metadata
+ metadata, framedatalist = lib.file_to_framedatalist(filename)
+
+ global frame
+ frame = mkw_utils.frame_of_input()
+
+if __name__ == '__main__':
+ main()
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global metadata
+ global framedatalist
+ global c
+ global frame
+
+ if not (frame == mkw_utils.frame_of_input() or frame == mkw_utils.frame_of_input()-1):
+ c = setting.get_agc_config()
+
+ delay = c.ghost_delay
+ if not c.useFrames:
+ delay *= mkw_utils.fps_const
+
+ racestate = RaceManager().state().value
+ frame = mkw_utils.frame_of_input()
+ delayed_frame = floor(delay)+frame
+ decimal_delay = delay - floor(delay)
+
+ metadata.load(1) #Force the ghost's combo and drift type
+
+ if not mkw_utils.is_single_player():
+ if racestate >= RaceState.COUNTDOWN.value :
+
+ metadata.delay_timer(delay)
+
+ if 0 < delayed_frame+1 < len(framedatalist):
+ f1 = lib.AGCFrameData.read_from_string(str(framedatalist[delayed_frame])) #Makes a copy so you can modify f1 without affecting the framedatalist
+ f2 = framedatalist[delayed_frame+1]
+ f1.interpolate(f2, 1-decimal_delay, decimal_delay)
+ f1.load(1)
+ f2.load(1, True)
+
+
diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Load_Player.py b/scripts/RMC/Adv. Ghost Comp/AGC_Load_Player.py
new file mode 100644
index 0000000..6ba9bb7
--- /dev/null
+++ b/scripts/RMC/Adv. Ghost Comp/AGC_Load_Player.py
@@ -0,0 +1,59 @@
+from dolphin import event, gui, utils
+from Modules import agc_lib as lib
+from Modules import settings_utils as setting
+from Modules.mkw_classes import RaceManager, RaceState
+from Modules import mkw_utils as mkw_utils
+import os
+from math import floor
+
+def main():
+ global c
+ c = setting.get_agc_config()
+
+ global filename
+ filename = os.path.join(utils.get_script_dir(), c.player_path)
+
+ global framedatalist
+ global metadata
+ metadata, framedatalist = lib.file_to_framedatalist(filename)
+
+ global frame
+ frame = mkw_utils.frame_of_input()
+
+
+@event.on_savestateload
+def reload_config(_,__):
+ global c
+ c = setting.get_agc_config()
+
+if __name__ == '__main__':
+ main()
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global metadata
+ global framedatalist
+ global c
+ global frame
+
+ delay = c.player_delay
+ if not c.useFrames:
+ delay *= mkw_utils.fps_const
+
+ racestate = RaceManager().state().value
+ frame = mkw_utils.frame_of_input()
+ delayed_frame = floor(delay)+frame
+ decimal_delay = delay - floor(delay)
+
+
+ if racestate >= RaceState.COUNTDOWN.value :
+
+ if 0 < delayed_frame+1 < len(framedatalist):
+ f1 = lib.AGCFrameData.read_from_string(str(framedatalist[delayed_frame])) #Makes a copy so you can modify f1 without affecting the framedatalist
+ f2 = framedatalist[delayed_frame+1]
+ f1.interpolate(f2, 1-decimal_delay, decimal_delay)
+ f1.load(0)
+ f2.load(0, True)
+
+
diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Save_Ghost.py b/scripts/RMC/Adv. Ghost Comp/AGC_Save_Ghost.py
new file mode 100644
index 0000000..91a5879
--- /dev/null
+++ b/scripts/RMC/Adv. Ghost Comp/AGC_Save_Ghost.py
@@ -0,0 +1,64 @@
+from dolphin import event, gui, utils
+import configparser
+from Modules import agc_lib as lib
+from Modules import settings_utils as setting
+from Modules.mkw_classes import RaceManager, RaceState
+from Modules import mkw_utils as mkw_utils
+import os
+
+
+def main():
+ global c
+ c = setting.get_agc_config()
+
+ global filename
+ filename = os.path.join(utils.get_script_dir(), c.ghost_path)
+
+ global framedatalist
+ framedatalist = {}
+
+ global frame
+ frame = mkw_utils.frame_of_input()
+
+ global end
+ end = False
+
+ global metadata
+ metadata = lib.AGCMetaData(read_id = 0)
+
+@event.on_scriptend
+def save(id_):
+ if utils.get_script_id() == id_:
+
+ lib.framedatalist_to_file(filename, framedatalist, metadata)
+ print("saved data")
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global framedatalist
+ global end
+ global frame
+ global c
+ global metadata
+ metadata = lib.AGCMetaData(read_id = 0)
+
+ if not (frame == mkw_utils.frame_of_input() or frame == mkw_utils.frame_of_input()-1):
+ c = setting.get_agc_config()
+ frame = mkw_utils.frame_of_input()
+
+ racestate = RaceManager().state().value
+
+ if (not end) and RaceState.RACE.value >= racestate >= RaceState.COUNTDOWN.value:
+ framedatalist[frame] = lib.AGCFrameData()
+
+ if (not end) and racestate == RaceState.FINISHED_RACE.value:
+ end = True
+ utils.cancel_script(utils.get_script_name())
+
+
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/External Display/MKDS_minimap.py b/scripts/RMC/External Display/MKDS_minimap.py
new file mode 100644
index 0000000..576f824
--- /dev/null
+++ b/scripts/RMC/External Display/MKDS_minimap.py
@@ -0,0 +1,122 @@
+from dolphin import event, gui, utils, memory
+import configparser
+import struct
+from external import external_utils as ex
+from Modules import agc_lib as lib
+from Modules import settings_utils as setting
+from Modules.mkw_classes import RaceManager, RaceState, VehiclePhysics, vec3
+from Modules import mkw_utils as mkw_utils
+import os
+
+
+def history_to_bytes(p):
+ ''' The encode will be 4 bytes for player pos_x, followed by 4 bytes for player pos_z
+ then 4 bytes for ghost pos_x, followed by 4 bytes for ghost pos_z,
+ repeat for all frames
+ first bytes correspond to recent frame, last bytes to old frames'''
+ res = bytearray()
+ for frame_data in p:
+ res = res + bytearray(struct.pack("f", frame_data['player'].x))
+ res = res + bytearray(struct.pack("f", frame_data['player'].z))
+ res = res + bytearray(struct.pack("f", frame_data['ghost'].x))
+ res = res + bytearray(struct.pack("f", frame_data['ghost'].z))
+ return bytes(res)
+
+def get_all_checkpoints():
+ ''' The encode will be 4 bytes for left point_x, followed by 4 bytes for left point_z
+ then 4 bytes for right_point_x, followed by 4 bytes for right point_z,
+ repeat for all cp
+ first bytes correspond to first cp, last bytes to last cp (i think)'''
+ game_id = utils.get_game_id()
+ address = {"RMCE01": 0x809B8F28, "RMCP01": 0x809BD6E8,
+ "RMCJ01": 0x809BC748, "RMCK01": 0x809ABD28}
+ kmp_ref = mkw_utils.chase_pointer(address[game_id], [0x4, 0x0], 'u32')
+ ckpt_offset = memory.read_u32(kmp_ref+0x24)
+ ckph_offset = memory.read_u32(kmp_ref+0x28)
+ left_list = []
+ right_list = []
+ offset = ckpt_offset
+ res = bytearray()
+ id_list = bytearray()
+ while offset < ckph_offset:
+ res = res + bytearray(struct.pack("f", memory.read_f32(kmp_ref+0x4C+offset+0x8+0x0)))
+ res = res + bytearray(struct.pack("f", memory.read_f32(kmp_ref+0x4C+offset+0x8+0x4)))
+ res = res + bytearray(struct.pack("f", memory.read_f32(kmp_ref+0x4C+offset+0x8+0x8)))
+ res = res + bytearray(struct.pack("f", memory.read_f32(kmp_ref+0x4C+offset+0x8+0xC)))
+ id_list = id_list + bytearray(struct.pack("b", memory.read_s8(kmp_ref+0x4C+offset+0x8+0x11)))
+ offset += 0x14
+ return bytes(res[:256*16]), bytes(id_list[:256])
+
+
+
+def main():
+ global end
+ end = False
+
+ HISTORY_SIZE = 240
+
+ def player_pos():
+ try:
+ return VehiclePhysics.position(0)
+ except:
+ return vec3(0,0,0)
+
+ def ghost_pos():
+ try:
+ return VehiclePhysics.position(1)
+ except:
+ return vec3(0,0,0)
+
+ global position_history
+ position_history = mkw_utils.History({'player':player_pos, 'ghost':ghost_pos},
+ HISTORY_SIZE)
+
+ global frame
+ frame = mkw_utils.frame_of_input()
+
+ global pos_writer
+ pos_writer = ex.SharedMemoryWriter('mkds minimap', HISTORY_SIZE*16)
+
+ global cp_writer
+ cp_writer = ex.SharedMemoryWriter('mkds minimap checkpoints', 256*16)
+
+ global cp_id_writer
+ cp_id_writer = ex.SharedMemoryWriter('mkds minimap checkpoints_id', 256)
+
+ global yaw_writer
+ yaw_writer = ex.SharedMemoryWriter('mkds minimap yaw', 4)
+
+ ex.start_external_script(os.path.join(utils.get_script_dir(), 'external', 'mkds_minimap_window.py'))
+
+
+if __name__ == '__main__':
+ main()
+@event.on_savestateload
+def clear_history(*_):
+ position_history.clear()
+
+@event.on_frameadvance
+def on_frame_advance():
+ global position_history
+ global frame
+ global pos_writer
+ global end
+
+ if (not end) and frame != mkw_utils.frame_of_input():
+ position_history.update()
+ cp, cp_id = get_all_checkpoints()
+ try:
+ pos_writer.write(history_to_bytes(position_history))
+ cp_writer.write(cp)
+ cp_id_writer.write(cp_id)
+ yaw_writer.write(struct.pack('f', mkw_utils.get_facing_angle(0).yaw))
+
+ except:
+ end = True
+ print('mkds minimap closed')
+
+ frame = mkw_utils.frame_of_input()
+
+
+
+
diff --git a/scripts/RMC/External Display/TAS_Input.py b/scripts/RMC/External Display/TAS_Input.py
new file mode 100644
index 0000000..16acfeb
--- /dev/null
+++ b/scripts/RMC/External Display/TAS_Input.py
@@ -0,0 +1,48 @@
+from dolphin import event, gui, utils
+from external import external_utils as ex
+from Modules import ttk_lib as lib
+from Modules.framesequence import Frame
+from Modules import mkw_utils as mkw_utils
+import os
+
+def run_input(_ = None):
+ global position_history
+ global pos_writer
+ global end
+
+ inp = input_reader.read_text().split(',')
+ if len(inp) == 8 and inp[3] == '-1':
+ #this logic is incorrect, most of the time
+ inp[3] = int(inp[0])*int(inp[1])
+ if len(inp) == 8:
+ lib.write_player_inputs(Frame(inp))
+
+def run_input_loop(_ = None):
+ while True:
+ run_input()
+ time.sleep(1)
+
+def main():
+ global end
+ end = False
+
+ global input_writer
+ input_writer = ex.SharedMemoryWriter('mkw tas input', 30)
+
+ global input_reader
+ input_reader = ex.SharedMemoryReader('mkw tas input')
+
+
+ ex.start_external_script(os.path.join(utils.get_script_dir(), 'external', 'TAS_input_window.py'))
+
+
+if __name__ == '__main__':
+ main()
+
+
+
+@event.on_framebegin
+def on_frame_begin():
+ run_input()
+
+
diff --git a/scripts/RMC/External Display/TTK_GUI.py b/scripts/RMC/External Display/TTK_GUI.py
new file mode 100644
index 0000000..748721b
--- /dev/null
+++ b/scripts/RMC/External Display/TTK_GUI.py
@@ -0,0 +1,280 @@
+from dolphin import event, utils, memory # type: ignore
+import os
+import struct
+import time
+from external import external_utils as ex
+from Modules import ttk_lib, rkg_lib
+from Modules.framesequence import FrameSequence
+from Modules.mkw_translations import course_slot_abbreviation
+from Modules.mkw_utils import frame_of_input
+from Modules.mkw_classes import RaceManager, RaceState
+
+PLAYER_STR = ["player", "ghost"]
+
+# Button functions
+
+def game_to_working_csv(source: ttk_lib.PlayerType, target: ttk_lib.PlayerType):
+ input_sequence = ttk_lib.read_full_decoded_rkg_data(source)
+ if not (input_sequence is None or len(input_sequence) == 0):
+ ttk_lib.write_to_csv(input_sequence, target)
+
+def save_player_to_player_csv():
+ game_to_working_csv(ttk_lib.PlayerType.PLAYER, ttk_lib.PlayerType.PLAYER)
+
+def save_ghost_to_player_csv():
+ game_to_working_csv(ttk_lib.PlayerType.GHOST, ttk_lib.PlayerType.PLAYER)
+ reload_inputs()
+
+def save_player_to_ghost_csv():
+ game_to_working_csv(ttk_lib.PlayerType.PLAYER, ttk_lib.PlayerType.GHOST)
+ reload_inputs()
+
+def save_ghost_to_ghost_csv():
+ game_to_working_csv(ttk_lib.PlayerType.GHOST, ttk_lib.PlayerType.GHOST)
+ reload_inputs()
+
+
+def working_csv_to_rkg(source: ttk_lib.PlayerType):
+ input_sequence = ttk_lib.get_input_sequence_from_csv(source)
+ if input_sequence is None or len(input_sequence) == 0:
+ return
+
+ filetype = [('RKG files', '*.rkg'), ('All files', '*')]
+ scriptDir = utils.get_script_dir()
+ ghostDir = os.path.join(utils.get_script_dir(), 'Ghost')
+ with open(os.path.join(scriptDir, 'Modules', "default.mii"), 'rb') as f:
+ mii_data = f.read()[:0x4A]
+
+ file_path = ex.save_dialog_box(scriptDir, filetype, ghostDir, 'Save RKG')
+ if not file_path:
+ return
+ with open(file_path, "wb") as f:
+ f.write(rkg_lib.encode_RKG(rkg_lib.RKGMetaData.from_current_race(), input_sequence, mii_data))
+
+
+def save_player_csv_to_rkg():
+ working_csv_to_rkg(ttk_lib.PlayerType.PLAYER)
+
+def save_ghost_csv_to_rkg():
+ working_csv_to_rkg(ttk_lib.PlayerType.GHOST)
+
+
+def rkg_to_working_csv(target: ttk_lib.PlayerType):
+ filetype = [('RKG files', '*.rkg'), ('All files', '*')]
+ scriptDir = utils.get_script_dir()
+ ghostDir = os.path.join(utils.get_script_dir(), 'Ghost')
+ file_path = ex.open_dialog_box(scriptDir, filetype, ghostDir, 'Open RKG')
+ if not file_path:
+ return
+
+ with open(file_path, 'rb') as f:
+ rkg_data = rkg_lib.decode_RKG(f.read())
+ input_sequence = None if rkg_data is None else rkg_data[1]
+ if input_sequence is None or len(input_sequence) == 0:
+ return
+ ttk_lib.write_to_csv(input_sequence, target)
+
+
+def save_rkg_to_player_csv():
+ rkg_to_working_csv(ttk_lib.PlayerType.PLAYER)
+ reload_inputs()
+
+def save_rkg_to_ghost_csv():
+ rkg_to_working_csv(ttk_lib.PlayerType.GHOST)
+ reload_inputs()
+
+def open_player_editor():
+ ex.open_file(player_inputs.filename)
+
+def open_ghost_editor():
+ ex.open_file(ghost_inputs.filename)
+
+def csv_to_working_csv(target: ttk_lib.PlayerType):
+ filetype = [('CSV files', '*.csv'), ('All files', '*')]
+ scriptDir = utils.get_script_dir()
+ ttkDir = os.path.join(utils.get_script_dir(), 'MKW_Inputs')
+ file_path = ex.open_dialog_box(scriptDir, filetype, ttkDir, 'Open CSV')
+ if not file_path:
+ return
+
+ input_sequence = FrameSequence(file_path)
+ input_sequence.read_from_file()
+ if input_sequence is None or len(input_sequence) == 0:
+ return
+ print(len(input_sequence))
+ ttk_lib.write_to_csv(input_sequence, target)
+
+def save_csv_to_player():
+ csv_to_working_csv(ttk_lib.PlayerType.PLAYER)
+ reload_inputs()
+
+def save_csv_to_ghost():
+ csv_to_working_csv(ttk_lib.PlayerType.GHOST)
+ reload_inputs()
+
+# This constant determines the function that gets called by each button.
+# The position of each function should match the corresponding button text in
+# the other BUTTON_LAYOUT in `external/ttk_gui_window.py`
+BUTTON_LAYOUT = [
+ [
+ [save_player_to_player_csv, save_ghost_to_player_csv],
+ [save_player_csv_to_rkg, save_rkg_to_player_csv],
+ [open_player_editor, save_csv_to_player]
+ ],
+ [
+ [save_player_to_ghost_csv, save_ghost_to_ghost_csv],
+ [save_ghost_csv_to_rkg, save_rkg_to_ghost_csv],
+ [open_ghost_editor, save_csv_to_ghost],
+ ],
+]
+
+def main():
+ # If a button function is executed immediately while emulation is not paused, it can cause
+ # pycore to freeze. To avoid this, button events are stored in this queue. If emulation
+ # is not paused, the event will be processed in `on_frame_advance` which won't freeze. If
+ # emulation is paused, the event will be processed in `on_timertick`.
+ global button_queue
+ button_queue = []
+
+ global prev_track
+ prev_track = course_slot_abbreviation()
+
+ global g_activate_ghost_hard
+ g_activate_ghost_hard = False
+
+ global shm_activate, shm_player_csv, shm_ghost_csv
+ shm_activate = ex.SharedMemoryBlock.create(name="ttk_gui_activate", buffer_size=3)
+ shm_player_csv = ex.SharedMemoryWriter(name="ttk_gui_player_csv", buffer_size=256)
+ shm_ghost_csv = ex.SharedMemoryWriter(name="ttk_gui_ghost_csv", buffer_size=256)
+
+ global player_inputs, ghost_inputs
+ player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER)
+ ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST)
+ shm_player_csv.write_text(player_inputs.filename)
+ shm_ghost_csv.write_text(ghost_inputs.filename)
+
+ ex.SharedMemoryBlock.create(name="ttk_gui_buttons", buffer_size=4)
+ global shm_buttons
+ shm_buttons = ex.SharedMemoryBlock.connect(name="ttk_gui_buttons")
+
+ ex.SharedMemoryBlock.create(name="ttk_gui_window_closed", buffer_size=2)
+ global shm_close
+ shm_close = ex.SharedMemoryBlock.connect(name="ttk_gui_window_closed")
+
+ window_script_path = os.path.join(utils.get_script_dir(), "external", "ttk_gui_window.py")
+ ex.start_external_script(window_script_path)
+
+
+
+# Function that watches for button events
+# This function should be called continuously:
+# either `on_timertick` when the game is paused
+# or `on_frameadvance` when the game isn't paused
+def listen_for_buttons():
+ # If window sent a button event, add it to queue
+ if 'shm_buttons' in globals():
+ button_event = shm_buttons.read()[:4]
+ else:
+ button_event = [False]*4
+
+ if button_event[0]:
+ button_queue.append(button_event)
+ shm_buttons.clear()
+
+ if button_queue:
+ execute_button_function()
+
+ if 'shm_close' in globals():
+ close_event = shm_close.read_text()
+ if close_event == "1":
+ utils.cancel_script(utils.get_script_name())
+ shm_close.write_text('0')
+
+# Helper that pops the oldest button event in the queue and executes it
+def execute_button_function():
+ button_event = button_queue.pop(0)
+ section_index, row_index, col_index = struct.unpack('>BBB', button_event[1:4])
+ try:
+ BUTTON_LAYOUT[section_index][row_index][col_index]()
+ except Exception as e:
+ print(f"Error executing button function: {e}")
+
+
+# Helper that reads state of activate checkboxes
+def get_activation_state():
+ return struct.unpack('>???', shm_activate.read())
+
+
+def reload_inputs():
+ global player_inputs
+ global ghost_inputs
+
+ player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER)
+ player_inputs.read_from_file()
+ shm_player_csv.write_text(player_inputs.filename)
+
+ ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST)
+ ghost_inputs.read_from_file()
+ shm_ghost_csv.write_text(ghost_inputs.filename)
+
+ """if g_activate_ghost_hard:
+ ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs)"""
+
+
+@event.on_timertick
+def on_timer_tick():
+ #Only do stuff when the game is paused to avoid crashes
+
+ """global g_activate_ghost_hard
+ try:
+ g_activate_ghost_hard
+ except NameError:
+ g_activate_ghost_hard = False"""
+
+ if utils.is_paused() and memory.is_memory_accessible():
+ """if not g_activate_ghost_hard:
+ if get_activation_state()[2]:
+ ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs)
+ g_activate_ghost_hard = get_activation_state()[2]"""
+ listen_for_buttons()
+
+
+@event.on_savestateload
+def on_state_load(is_slot, slot):
+ if memory.is_memory_accessible():
+ reload_inputs()
+
+
+@event.on_framebegin
+def on_frame_begin():
+ listen_for_buttons()
+
+ global g_activate_ghost_hard
+ activate_player, activate_ghost_soft, activate_ghost_hard = get_activation_state()
+ """if not g_activate_ghost_hard:
+ if get_activation_state()[2]:
+ ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs)"""
+ g_activate_ghost_hard = activate_ghost_hard
+
+ global prev_track
+ track = course_slot_abbreviation()
+ if track != prev_track:
+ reload_inputs()
+ prev_track = track
+
+ if RaceManager.state() not in (RaceState.COUNTDOWN, RaceState.RACE):
+ return
+
+ frame = frame_of_input()
+
+ if activate_player and player_inputs[frame]:
+ ttk_lib.write_player_inputs(player_inputs[frame])
+
+ if activate_ghost_soft and ghost_inputs[frame]:
+ ttk_lib.write_ghost_inputs(ghost_inputs[frame])
+
+ if activate_ghost_hard and ghost_inputs[frame]:
+ ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs)
+
+
+main()
diff --git a/scripts/RMC/External Display/external_info_display.py b/scripts/RMC/External Display/external_info_display.py
new file mode 100644
index 0000000..4f84078
--- /dev/null
+++ b/scripts/RMC/External Display/external_info_display.py
@@ -0,0 +1,111 @@
+"""
+Writes infodisplay text to a shared memory buffer than can be accessed by external applications
+"""
+import os
+from dolphin import event, utils, memory # type: ignore
+from external import external_utils
+from Modules import settings_utils as setting
+from Modules import mkw_utils as mkw_utils
+from Modules.infodisplay_utils import create_infodisplay
+from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, KartObjectManager, VehiclePhysics
+
+from Modules.mkw_classes.common import SurfaceProperties, eulerAngle
+from Modules.mkw_utils import History
+
+ROUND_DIGITS = 6
+TEXT_COLOR = 0xFFFFFFFF
+
+LAST_FRAME = {}
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global Frame_of_input
+ global Angle_History
+ global RaceComp_History
+ global c
+ global special_event
+ global maxLap
+
+
+ race_mgr = RaceManager()
+ newframe = Frame_of_input != mkw_utils.frame_of_input()
+ draw = race_mgr.state().value >= RaceState.COUNTDOWN.value
+ if newframe and draw:
+ Frame_of_input = mkw_utils.frame_of_input()
+ try:
+ Angle_History.update()
+ RaceComp_History.update()
+ Pos_History.update()
+ if maxLap == int(RaceManagerPlayer.race_completion_max(0))-1:
+ mkw_utils.calculate_exact_finish(Pos_History, maxLap)
+ maxLap = int(RaceManagerPlayer.race_completion_max(0))
+ except AssertionError:
+ pass
+
+ global shm_writer
+
+ if newframe:
+ Frame_of_input = mkw_utils.frame_of_input()
+
+ if draw:
+ shm_writer.write_text(create_infodisplay(c, RaceComp_History, Angle_History))
+ else:
+ RaceComp_History.clear()
+ Angle_History.clear()
+ Pos_History.clear()
+
+
+@event.on_savestateload
+def on_state_load(fromSlot: bool, slot: int):
+ global c
+ c = setting.get_infodisplay_config()
+ if memory.is_memory_accessible() and mkw_utils.extended_race_state() >= 0:
+ shm_writer.write_text(create_infodisplay(c, RaceComp_History, Angle_History))
+
+
+def main():
+ global c
+ c = setting.get_infodisplay_config()
+
+ global Frame_of_input
+ Frame_of_input = 0
+
+ def prc():
+ if KartObjectManager.player_count() > 0:
+ return RaceManagerPlayer(0).race_completion()
+ return 0
+ def grc():
+ if KartObjectManager.player_count() > 1:
+ return RaceManagerPlayer(1).race_completion()
+ return 0
+ def fa():
+ return mkw_utils.get_facing_angle(0)
+ def ma():
+ return mkw_utils.get_moving_angle(0)
+ def pos_():
+ if KartObjectManager.player_count() > 0:
+ return VehiclePhysics.position(0)
+ return None
+
+ global RaceComp_History
+ RaceComp_History = History({'prc':prc, 'grc':grc}, c.history_size)
+
+ global Angle_History
+ Angle_History = History({'facing' : fa, 'moving' : ma}, 2)
+
+ global Pos_History
+ Pos_History = History({'pos' : pos_}, 3)
+
+ global maxLap
+ maxLap = None
+
+ global shm_writer
+ shm_writer = external_utils.SharedMemoryWriter(name='infodisplay', buffer_size=4096)
+
+ window_script_path = os.path.join(utils.get_script_dir(), "external", "info_display_window.py")
+ external_utils.start_external_script(window_script_path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/External Display/live_graphing.py b/scripts/RMC/External Display/live_graphing.py
new file mode 100644
index 0000000..a05e5b0
--- /dev/null
+++ b/scripts/RMC/External Display/live_graphing.py
@@ -0,0 +1,41 @@
+import os
+import struct
+from dolphin import event, utils # type: ignore
+from Modules import mkw_classes as mkw
+from Modules import mkw_utils
+from external import external_utils
+
+
+def get_frame_data():
+ frame = mkw_utils.frame_of_input()
+ vehicle_physics = mkw.VehiclePhysics()
+ ev = vehicle_physics.external_velocity()
+ return struct.pack('>Iff', frame, ev.length(), ev.length_xz())
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global shm_writer
+ global in_race
+
+ if mkw.RaceManager().state().value >= mkw.RaceState.COUNTDOWN.value:
+ in_race = True
+ shm_writer.write(get_frame_data())
+ elif in_race:
+ in_race = False
+ shm_writer.write(b'\x00')
+
+
+def main():
+ global in_race
+ in_race = False
+
+ global shm_writer
+ shm_writer = external_utils.SharedMemoryWriter(name='graphdata', buffer_size=32)
+
+ window_script_path = os.path.join(utils.get_script_dir(), "external", "live_graphing_window.py")
+ external_utils.start_external_script(window_script_path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/Extra - Debug/Auto_Input_Optimizer.py b/scripts/RMC/Extra - Debug/Auto_Input_Optimizer.py
new file mode 100644
index 0000000..f0bfe5a
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/Auto_Input_Optimizer.py
@@ -0,0 +1,123 @@
+'''
+This script can automatically modify inputs, with random input generation.
+It is used to optimize some part of a TAS.
+-You have to MANUALLY tell the script which function it need to maximize
+ by modifying the function 'calculate_score'.
+ Example : maximize the RaceCompletion of the player by frame 4242.
+ some example are coded in bruteforcer_lib.py
+-You have to MANUALLY tell the script which random modification it can do
+ on your inputs. This is done with a list of "Randomizer" instance (class from bruteforcer_lib.py)
+ Typically those instances can do 1 random modification to a FrameSequence at a time.
+ the global variable randlist, definied in main(), is a list of all those Randomizer instance you
+ want to apply on each try.
+
+Currently, this script ALWAYS save an improvement, and NEVER saves a slower attempt.
+this might change in the future, with a monte carlo approach.
+
+THIS SCRIPT USES SAVESTATE SLOT 4
+'''
+from dolphin import event, savestate, utils
+from Modules import ttk_lib
+from Modules import mkw_utils as mkw_utils
+from Modules import mkw_classes as mkw
+from Modules.framesequence import FrameSequence, Frame
+from Modules.bruteforcer_lib import score_racecomp, score_XZ_EV
+from Modules import bruteforcer_lib as lib
+import random
+import os
+
+
+
+
+def calculate_score():
+ ''' This function has to return a score corresponding to what you want to optimize
+ from the current attempt.
+ If you want the current attempt to keep going, YOU HAVE TO RETURN NONE
+ This function is called on every newframe, so you can store data with
+ some mkw_utils.History for more complex data to calculate score for example'''
+
+ frame = mkw_utils.frame_of_input()
+ if frame < 1700 :
+ return None
+ if mkw_utils.get_facing_angle(0).yaw%360 > 180.90:
+ return -float('inf')
+ return -mkw.VehiclePhysics(0).position().z
+
+def main():
+
+ global randlist
+ randlist = []
+
+ for frame in range(1407, 1633):
+ randlist.append(lib.Randomizer_Raw_Stick("Y", frame, 1/300, 5))
+ randlist.append(lib.Randomizer_Raw_Stick("X", frame, 1/600, 5))
+ randlist.append(lib.Randomizer_Alternate_Stick("Y", frame, 1/100, 2))
+ randlist.append(lib.Randomizer_Alternate_Stick("X", frame, 1/300, 2))
+
+
+ for frame in range(1450,1490):
+ randlist.append(lib.Randomizer_Alternate_Stick("Y", frame, 1/50, 2))
+ randlist.append(lib.Randomizer_Raw_Stick("Y", frame, 1/200, 15))
+
+ for frame in range(1570,1615):
+ randlist.append(lib.Randomizer_Alternate_Stick("Y", frame, 1/50, 2))
+ randlist.append(lib.Randomizer_Raw_Stick("Y", frame, 1/200, 15))
+
+ global highscore
+ highscore = None
+
+ savestate.load_from_slot(4)
+
+ global save
+ save = savestate.save_to_bytes()
+
+ global cur_csv
+ cur_csv = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER)
+
+ global best_csv
+ best_csv = cur_csv
+
+ global attempt_counter
+ attempt_counter = 0
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global best_csv
+ global cur_csv
+ global attempt_counter
+ global highscore
+
+ if True:
+ frame = mkw_utils.frame_of_input()
+
+ if True:
+ score = calculate_score()
+ if not (score is None):
+ if (highscore is None) or (score > highscore):
+ attempt_counter = 0
+ highscore = score
+ best_csv = cur_csv
+ savefilename = os.path.join(utils.get_script_dir(), "Input_optimizer", f"{highscore}.csv")
+ if best_csv.write_to_file(savefilename):
+ print(f"saved to {savefilename}")
+ else:
+ print('error with saving')
+ cur_csv = best_csv.copy()
+ for rand in randlist:
+ rand.random(cur_csv)
+ attempt_counter+= 1
+ print(f"random attempt since last improvement : {attempt_counter}")
+ savestate.load_from_bytes(save)
+
+
+@event.on_framebegin
+def on_frame_begin():
+ frame = mkw_utils.frame_of_input()
+
+ player_input = cur_csv[frame]
+ if player_input:
+ ttk_lib.write_player_inputs(player_input)
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/Extra - Debug/Auto_Teleport.py b/scripts/RMC/Extra - Debug/Auto_Teleport.py
new file mode 100644
index 0000000..f8dac23
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/Auto_Teleport.py
@@ -0,0 +1,114 @@
+'''
+Start the script on the savestate frame !
+To use this script : You have to edit : SAVEFILENAME, TELEPORT_FRAME, LIST_OF_PARAM_EDITOR_FUNCTION,
+LIST_OF_PARAM_ITERATOR, filter_attempt.
+SAVEFILENAME : filename for the file you will save results in
+TELEPORT_FRAME : frame you want the memory edits to happen (usually a teleportation)
+LIST_OF_PARAM_EDITOR_FUNCTION : List of functions that will perform a memory edit when you get to TELEPORT_FRAME
+ Those functions MUST take only 1 argument, which is generally value of the memory edit desired.
+ Example : lambda x : mkw_utils.player_teleport(0, x=x) is a function that take an argument x, and teleport to the corresponding x position
+ You can get more fancy, and use this to modify other parameters, like the external velocity
+LIST_OF_PARAM_ITERATOR : List of iterators. Must be the same length as LIST_OF_PARAM_EDITOR_FUNCTION.
+ LIST_OF_PARAM_ITERATOR[i] must be an iterator of valid argument for the function LIST_OF_PARAM_EDITOR_FUNCTION[i]
+ The script will iterate on all combinaison of possible valid arguments given by those iterators.
+ The total amount of iteration of the script is the product of all iterators in LIST_OF_PARAM_ITERATOR
+filter_attempt : Read the comment of the function to edit it.
+'''
+from dolphin import event, savestate
+from Modules.mkw_classes import VehiclePhysics
+from Modules import mkw_utils as mkw_utils
+from itertools import product
+
+SAVEFILENAME = 'collect_data.csv'
+
+TELEPORT_FRAME = 1858
+
+LIST_OF_PARAM_EDITOR_FUNCTION = [lambda x : mkw_utils.player_teleport(0, x=x), #X position teleport function
+ lambda y : mkw_utils.player_teleport(0, y=y), #Z position teleport function
+ lambda z: mkw_utils.player_teleport(0, z=z)]#Yaw teleport function
+
+def make_range(middle, radius, step):
+ return iter(range(middle-radius, middle+radius, step))
+
+LIST_OF_PARAM_ITERATOR = [make_range(14012, 20, 2),
+ make_range(22482, 20, 2),
+ make_range(51857, 20, 2)]
+
+def filter_attempt():
+ ''' This function has to return the string corresponding to the data you want to save
+ from the current attempt. Generally, you want atleast cur_param, and eventually more data
+ You can return an empty string if you don't want to save anything from the current attempt
+ If you want the current attempt to keep going, YOU HAVE TO RETURN NONE
+ This function is called on every newframe, so you can store data with some mkw_utils.History for more complex data to save for example'''
+
+ frame = mkw_utils.frame_of_input()
+ if frame < 1900 :
+ return None
+ if VehiclePhysics.position(0).x > 15000:
+ return str(cur_param)[1:-1]+'\n'
+ else:
+ return ''
+
+def main():
+ global save
+ save = savestate.save_to_bytes()
+
+ global savestring
+ savestring = ''
+
+ global params_iterator
+ params_iterator = product(*LIST_OF_PARAM_ITERATOR)
+
+ global cur_param
+ cur_param = next(params_iterator)
+
+ global end
+ end = False
+
+ global iteration
+ iteration = 1
+
+ global frame
+ frame = mkw_utils.frame_of_input()
+
+if __name__ == '__main__':
+ main()
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global frame
+ global params_iterator
+ global save
+ global savestring
+ global cur_param
+ global end
+ global iteration
+
+ if not end:
+
+ newframe = mkw_utils.frame_of_input() != frame
+ frame = mkw_utils.frame_of_input()
+
+ if newframe:
+ save_str = filter_attempt()
+ if not (save_str is None):
+ savestring += save_str
+ with open(SAVEFILENAME, 'w') as f:
+ f.write(savestring)
+ try:
+ cur_param = next(params_iterator)
+ print('iteration_count', iteration)
+ iteration += 1
+ savestate.load_from_bytes(save)
+ except StopIteration:
+ end = True
+
+ if frame == TELEPORT_FRAME:
+ print('current param', str(cur_param))
+ for i in range(len(LIST_OF_PARAM_EDITOR_FUNCTION)):
+ f = LIST_OF_PARAM_EDITOR_FUNCTION[i]
+ arg = cur_param[i]
+ f(arg)
+
+
diff --git a/scripts/RMC/Extra - Debug/Auto_Teleport_Connected_Component.py b/scripts/RMC/Extra - Debug/Auto_Teleport_Connected_Component.py
new file mode 100644
index 0000000..2e6cdfa
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/Auto_Teleport_Connected_Component.py
@@ -0,0 +1,137 @@
+'''
+Start the script on the savestate frame !
+To use this script : You have to edit : SAVEFILENAME, TELEPORT_FRAME, LIST_OF_PARAM_EDITOR_FUNCTION,
+LIST_OF_PARAM_ITERATOR, filter_attempt.
+SAVEFILENAME : filename for the file you will save results in
+TELEPORT_FRAME : frame you want the memory edits to happen (usually a teleportation)
+LIST_OF_PARAM_EDITOR_FUNCTION : List of functions that will perform a memory edit when you get to TELEPORT_FRAME
+ Those functions MUST take only 1 argument, which is generally value of the memory edit desired.
+ Example : lambda x : mkw_utils.player_teleport(0, x=x) is a function that take an argument x, and teleport to the corresponding x position
+ You can get more fancy, and use this to modify other parameters, like the external velocity
+BASE_LIST : The first arguments of the functions in LIST_OF_PARAM_EDITOR_FUNCTION that will be tried
+STEP_LIST : The steps you will take when looking for nearby arguments for LIST_OF_PARAM_EDITOR_FUNCTION
+MAX_LIST : Put a maximum value for each argument of LIST_OF_PARAM_EDITOR_FUNCTION
+ You can use None for no max value
+MIN_LIST : same but minimum value
+filter_attempt : Read the comment of the function to edit it.
+'''
+from dolphin import event, savestate, utils
+from Modules.mkw_classes import VehiclePhysics
+from Modules import mkw_utils as mkw_utils
+from itertools import product
+from collections import deque
+
+SAVEFILENAME = 'collect_data.csv'
+
+TELEPORT_FRAME = 1858
+
+LIST_OF_PARAM_EDITOR_FUNCTION = [lambda x : mkw_utils.player_teleport(0, x=x), #X position teleport function
+ lambda y : mkw_utils.player_teleport(0, y=y), #Z position teleport function
+ lambda z: mkw_utils.player_teleport(0, z=z)]#Yaw teleport function
+
+
+BASE_LIST = [14012, 22600, 51857]
+
+STEP_LIST = [1, 0, 1]
+
+MAX_LIST = [None, None, None]
+
+MIN_LIST = [None, None, None]
+
+def filter_attempt():
+ ''' This function has to return the string corresponding to the data you want to save
+ from the current attempt. Generally, you want atleast cur_param, and eventually more data
+ You can return an empty string if you don't want to save anything from the current attempt
+ If you want the current attempt to keep going, YOU HAVE TO RETURN NONE
+ This function is called on every newframe, so you can store data with some mkw_utils.History for more complex data to save for example'''
+
+ frame = mkw_utils.frame_of_input()
+ if frame < 1900 :
+ return None
+ if VehiclePhysics.position(0).x > 15000:
+ return str(tuple([cur_coordinate[i] * STEP_LIST[i] + BASE_LIST[i] for i in range(DEPTH)]))[1:-1]+'\n'
+ else:
+ return ''
+
+def main():
+ global DEPTH
+ DEPTH = len(BASE_LIST)
+
+ global save
+ save = savestate.save_to_bytes()
+
+ global savestring
+ savestring = ''
+
+ global waiting_list
+ waiting_list = deque()
+ waiting_list.append(tuple([0]*DEPTH))
+
+ global visited_dict
+ visited_dict = {}
+ visited_dict[tuple([0]*DEPTH)] = True
+
+ global end
+ end = False
+
+ global cur_coordinate
+ cur_coordinate = tuple([0]*DEPTH)
+
+ global iteration
+ iteration = 1
+
+ global frame
+ frame = mkw_utils.frame_of_input()
+
+if __name__ == '__main__':
+ main()
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global frame
+ global params_iterator
+ global save
+ global savestring
+ global cur_coordinate
+ global end
+ global iteration
+ global waiting_list
+ global visited_dict
+
+ if not end:
+
+ newframe = mkw_utils.frame_of_input() != frame
+ frame = mkw_utils.frame_of_input()
+
+ if newframe:
+ save_str = filter_attempt()
+ if not (save_str is None):
+ savestring += save_str
+ with open(SAVEFILENAME, 'w') as f:
+ f.write(savestring)
+ if save_str != '':
+ for change in product((-1,0,1), repeat = DEPTH):
+ new_coordinate = tuple([cur_coordinate[i] + change[i]*int(STEP_LIST[i] != 0) for i in range(DEPTH)])
+ if not (new_coordinate in visited_dict.keys()):
+ #todo : check if new_coordinate doesn't go outside min and max
+ visited_dict[new_coordinate] = True
+ waiting_list.append(new_coordinate)
+ print('new coordinate added')
+ if waiting_list:
+ cur_coordinate = waiting_list.popleft()
+ print('wait list size:', len(waiting_list))
+ savestate.load_from_bytes(save)
+ else:
+ end = True
+ utils.toggle_play()
+
+
+ if newframe and frame == TELEPORT_FRAME:
+ print('current param', str(cur_coordinate))
+ for i in range(DEPTH):
+ f = LIST_OF_PARAM_EDITOR_FUNCTION[i]
+ arg = cur_coordinate[i] * STEP_LIST[i] + BASE_LIST[i]
+ f(arg)
+
+
diff --git a/scripts/RMC/Extra - Debug/Bruteforce.py b/scripts/RMC/Extra - Debug/Bruteforce.py
new file mode 100644
index 0000000..68ea0d3
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/Bruteforce.py
@@ -0,0 +1,67 @@
+from dolphin import controller, event
+from Modules import bruteforcer_lib as bfl
+from Modules import mkw_utils as utils
+from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState
+
+def main():
+ def ruleset(x):
+ if x<250:
+ return bfl.forward_rule
+ return bfl.basic_rule
+
+ iterset = lambda x : bfl.last_input_iterator
+
+ global IptList
+ IptList = bfl.InputList(ruleset, iterset)
+
+ global ss_frequency
+ ss_frequency = 60
+
+ global distance_key
+ distance_key = 0
+
+ global delay_input
+ delay_input = 1
+
+
+
+if __name__ == '__main__':
+ main()
+
+def savename(frame):
+ return str(frame)+'.rawsav'
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ frame = utils.frame_of_input()
+ if RaceManager().state().value >= RaceState.COUNTDOWN.value and not utils.is_single_player():
+ if utils.get_distance_ghost() <= distance_key :
+ if frame%ss_frequency == 1:
+ bfl.save(savename(frame))
+ bfl.run_input(IptList[frame])
+ else:
+ modif_frame = IptList.update(frame-delay_input)
+ frame_to_load = bfl.prevframe(modif_frame, ss_frequency)
+ print(f"desync at {frame}, modifying input at {modif_frame}, loading {frame_to_load}")
+ bfl.run_input(IptList[frame_to_load])
+ bfl.load(savename(frame_to_load))
+ #bfl.run_input2(bfl.forward)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scripts/RMC/Extra - Debug/EV_burst.py b/scripts/RMC/Extra - Debug/EV_burst.py
new file mode 100644
index 0000000..3dcbdca
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/EV_burst.py
@@ -0,0 +1,34 @@
+from dolphin import event, savestate, utils
+import os
+from pathlib import Path
+from Modules import mkw_utils as mkw_utils
+from Modules import framesequence as framesequence
+from Modules import ttk_lib as ttk_lib
+from Modules.mkw_translations import vehicle_id
+from Modules import mkw_classes as mkw
+
+import time
+
+def main():
+ #savestate.load_from_slot(2)
+
+ mkw_utils.add_angular_ev(0, 0, 10, 0)
+ global end
+ end = False
+
+
+
+
+@event.on_frameadvance
+def frameadvance():
+ global end
+ global g_save_file_name
+
+ if (not end) and mkw_utils.extended_race_state() > 0:
+ utils.cancel_script(utils.get_script_name())
+ end = True
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_0.998_decay.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_0.998_decay.py
new file mode 100644
index 0000000..b73c5c9
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_0.998_decay.py
@@ -0,0 +1,48 @@
+from dolphin import memory, utils, event
+
+def main():
+ global region
+ region = utils.get_game_id()
+
+ global og_values
+ og_values = {}
+
+ global address_list
+ address_list = {"RMCE01": (0x805aa3d4, 0x805aa3dc, 0x805aa3e0)}
+
+ try:
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+@event.on_scriptend
+def revert(id_):
+ if utils.get_script_id() == id_:
+ if memory.is_memory_accessible():
+ for address in og_values.keys():
+ memory.write_u32(address, og_values[address])
+ memory.invalidate_icache(address, 0x4)
+ else:
+ print('Memory not accessible.')
+ print("Couldn't reverse the effect of the script")
+
+
+@event.on_savestateload
+def reload(*_):
+ global og_values
+ region = utils.get_game_id()
+ if memory.is_memory_accessible():
+ try:
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+if __name__ == '__main__':
+ main()
+
diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_SG.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_SG.py
new file mode 100644
index 0000000..cfcb035
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_SG.py
@@ -0,0 +1,48 @@
+from dolphin import memory, utils, event
+
+def main():
+ global region
+ region = utils.get_game_id()
+
+ global og_values
+ og_values = {}
+
+ global address_list
+ address_list = {"RMCE01": (0x805ac80c, 0x805ac810)}
+
+ try:
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+@event.on_scriptend
+def revert(id_):
+ if utils.get_script_id() == id_:
+ if memory.is_memory_accessible():
+ for address in og_values.keys():
+ memory.write_u32(address, og_values[address])
+ memory.invalidate_icache(address, 0x4)
+ else:
+ print('Memory not accessible.')
+ print("Couldn't reverse the effect of the script")
+
+
+@event.on_savestateload
+def reload(*_):
+ global og_values
+ region = utils.get_game_id()
+ if memory.is_memory_accessible():
+ try:
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+if __name__ == '__main__':
+ main()
+
diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_gravity.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_gravity.py
new file mode 100644
index 0000000..db97539
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_gravity.py
@@ -0,0 +1,48 @@
+from dolphin import memory, utils, event
+
+def main():
+ global region
+ region = utils.get_game_id()
+
+ global og_values
+ og_values = {}
+
+ global address_list
+ address_list = {"RMCE01": (0x805aa360, 0x805aa364, 0x805aa368)}
+
+ try:
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+@event.on_scriptend
+def revert(id_):
+ if utils.get_script_id() == id_:
+ if memory.is_memory_accessible():
+ for address in og_values.keys():
+ memory.write_u32(address, og_values[address])
+ memory.invalidate_icache(address, 0x4)
+ else:
+ print('Memory not accessible.')
+ print("Couldn't reverse the effect of the script")
+
+
+@event.on_savestateload
+def reload(*_):
+ global og_values
+ region = utils.get_game_id()
+ if memory.is_memory_accessible():
+ try:
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+if __name__ == '__main__':
+ main()
+
diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_ground_decay.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_ground_decay.py
new file mode 100644
index 0000000..7c2aed0
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_ground_decay.py
@@ -0,0 +1,44 @@
+from dolphin import memory, utils, event
+
+def main():
+ region = utils.get_game_id()
+ global og_values
+ og_values = {}
+ try:
+ address_list = {"RMCE01": (0x805acec0, 0x805acecc, 0x805aced8)}
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+@event.on_scriptend
+def revert(id_):
+ if utils.get_script_id() == id_:
+ if memory.is_memory_accessible():
+ for address in og_values.keys():
+ memory.write_u32(address, og_values[address])
+ memory.invalidate_icache(address, 0x4)
+ else:
+ print('Memory not accessible.')
+ print("Couldn't reverse the effect of the script")
+
+
+@event.on_savestateload
+def reload(*_):
+ global og_values
+ region = utils.get_game_id()
+ if memory.is_memory_accessible():
+ try:
+ address_list = {"RMCE01": (0x805acec0, 0x805acecc, 0x805aced8)}
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+if __name__ == '__main__':
+ main()
+
diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_lean_effect.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_lean_effect.py
new file mode 100644
index 0000000..04ac3ae
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_lean_effect.py
@@ -0,0 +1,48 @@
+from dolphin import memory, utils, event
+
+def main():
+ global region
+ region = utils.get_game_id()
+
+ global og_values
+ og_values = {}
+
+ global address_list
+ address_list = {"RMCE01": (0x805818d0, 0x805818e0, 0x805818f0)}
+
+ try:
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+@event.on_scriptend
+def revert(id_):
+ if utils.get_script_id() == id_:
+ if memory.is_memory_accessible():
+ for address in og_values.keys():
+ memory.write_u32(address, og_values[address])
+ memory.invalidate_icache(address, 0x4)
+ else:
+ print('Memory not accessible.')
+ print("Couldn't reverse the effect of the script")
+
+
+@event.on_savestateload
+def reload(*_):
+ global og_values
+ region = utils.get_game_id()
+ if memory.is_memory_accessible():
+ try:
+ for address in address_list[region]:
+ og_values[address] = memory.read_u32(address)
+ memory.write_u32(address, 0x60000000)
+ memory.invalidate_icache(address, 0x4)
+ except KeyError:
+ raise RegionError
+
+if __name__ == '__main__':
+ main()
+
diff --git a/scripts/RMC/Extra - Debug/pointer_chase_backward.py b/scripts/RMC/Extra - Debug/pointer_chase_backward.py
new file mode 100644
index 0000000..70314b1
--- /dev/null
+++ b/scripts/RMC/Extra - Debug/pointer_chase_backward.py
@@ -0,0 +1,171 @@
+from dolphin import memory
+import time
+from Modules import agc_lib as lib
+
+
+
+# Knuth-Morris-Pratt string matching
+# David Eppstein, UC Irvine, 1 Mar 2002
+
+def KnuthMorrisPratt(text, pattern):
+
+ '''Yields all starting positions of copies of the pattern in the text.
+Calling conventions are similar to string.find, but its arguments can be
+lists or iterators, not just strings, it returns all matches, not just
+the first one, and it does not need the whole text in memory at once.
+Whenever it yields, it will have read the text exactly up to and including
+the match that caused the yield.'''
+
+ # allow indexing into pattern and protect against change during yield
+ pattern = list(pattern)
+
+ # build table of shift amounts
+ shifts = [1] * (len(pattern) + 1)
+ shift = 1
+ for pos in range(len(pattern)):
+ while shift <= pos and pattern[pos] != pattern[pos-shift]:
+ shift += shifts[pos-shift]
+ shifts[pos+1] = shift
+
+ # do the actual search
+ startPos = 0
+ matchLen = 0
+ for c in text:
+ while matchLen == len(pattern) or \
+ matchLen >= 0 and pattern[matchLen] != c:
+ startPos += shifts[matchLen]
+ matchLen -= shifts[matchLen]
+ matchLen += 1
+ if matchLen == len(pattern):
+ yield startPos
+
+
+def list_of_possible_list(l):
+ """Takes a list of list as an argmument.
+ Return a list containing each possible list you can form from those list
+ Ex : [ [a, b]
+ [a]
+ [c, e] ]
+ Return : [ [a,a,c] , [a,a,e], [b,a,c] etc]"""
+ res = []
+ if l :
+ tmp = list_of_possible_list(l[1:])
+ for val in l[0]:
+ for list_ in tmp:
+ res.append([val]+list_)
+ else:
+ return [[]]
+
+
+
+def scan_memory_array(addrStart, addrEnd, byte_array):
+ t1 = time.time()
+ memory_read = memory.read_bytes(addrStart, addrEnd-addrStart)
+ t2 = time.time()
+ res = [addrStart+offset for offset in KnuthMorrisPratt(memory_read, byte_array)]
+ t3 = time.time()
+ print(t2-t1, t3-t2)
+ return res
+
+def find_address_in_memory(addrStart, addrEnd, addrToFind, offsetmax):
+ res = []
+ for addr in range(addrStart, addrEnd, 4):
+ if addrToFind - offsetmax <= memory.read_u32(addr) <= addrToFind:
+ res.append(addr)
+ return res
+
+def find_address_in_memory_fast(inv_graph, addr, offsetmax):
+ res = []
+ for curaddr in range(addr, addr-offsetmax, -4):
+ if curaddr in inv_graph.keys():
+ #res = res+inv_graph[curaddr]
+ res.append(min(inv_graph[curaddr]))
+ return res
+
+def is_addr(addr):
+ return (0x80000000 <= addr <= 0x9FFFFFFF)
+
+def create_graph():
+ """Return a list where if addr is an address containing a pointer, list[addr] is the address pointed"""
+ graph = {}
+ for addr in range(0x80000000, 0x9FFFFFFF, 4):
+ val = memory.read_u32(addr)
+ if is_addr(val):
+ graph[addr] = val
+ return graph
+
+def invert_graph(graph):
+ """Return inv_graph so if graph[addr] is an address, inv_graph[graph[addr]] contain addr."""
+ inv_graph = {}
+ for addr in graph.keys():
+ if graph[addr] not in inv_graph.keys():
+ inv_graph[graph[addr]] = []
+ inv_graph[graph[addr]].append(addr)
+ return inv_graph
+
+def graphtofile(filename, graph, address):
+ """takes a graph (dic type), an address, and store to file"""
+ f = open(filename, 'w')
+ f.write(str(address)+"\n")
+ for addr in graph.keys():
+ f.write(str(addr)+','+str(graph[addr])+'\n')
+ f.close()
+
+
+def chase_backward(graph, inv_graph, startAddr, offsetmax, depth_max):
+ """Return a list of ptr chain, starting from anywhere, leading to startAddr"""
+ prev_addr_list = find_address_in_memory_fast(inv_graph, startAddr, offsetmax)
+ res = [[startAddr]]
+ if depth_max == 0:
+ 1+1
+ else:
+ for prev_addr in prev_addr_list:
+ prev_res = chase_backward(graph, inv_graph, prev_addr, offsetmax, depth_max -1)
+ for ptr_chain in prev_res:
+ res.append(ptr_chain + [startAddr - graph[prev_addr]])
+ return res
+
+
+def chase_backward_multi_graph(graph_list, inv_graph_list, startAddr_list, offsetmax, depth_max):
+ res = []
+ if depth_max:
+ for offset in range(0, offsetmax, 4):
+ b = True
+ for i in range(len(graph_list)):
+ b = b and startAddr_list[i]-offset in inv_graph_list[i].keys()
+ if b:
+ addr_list_list = []
+ for i in range(len(graph_list)):
+ addr_list_list.append(inv_graph_list[i][startAddr_list[i]-offset])
+ possible_prev_addr_list = list_of_possible_list(addr_list_list)
+ for prev_addr_list in possible_prev_addr_list:
+ prev_res = chase_backward_multi_graph(graph_list, inv_graph_list, prev_addr_list, offsetmax, depth_max-1)
+ for ptr_chain in prev_res:
+ res.append(ptr_chain + [offset])
+ return res
+
+
+def main():
+
+ s = lib.Split.from_rkg(lib.rkg_addr(), 1)
+ l = scan_memory_array(0x80F00000, 0x81FFFFFF, s.time_format_bytes())
+ print(len(l))
+ print(hex(l[0]))
+ t1 = time.time()
+ g = create_graph()
+ t2 = time.time()
+ i = invert_graph(g)
+ t3 = time.time()
+ print('create: ', t2-t1)
+ print('invert: ', t3-t2)
+ if l:
+ graphtofile('3.graph', g, l[0])
+
+
+
+if __name__ == '__main__':
+ main()
+
+
+
+
diff --git a/scripts/RMC/Freefly.py b/scripts/RMC/Freefly.py
new file mode 100644
index 0000000..0478f62
--- /dev/null
+++ b/scripts/RMC/Freefly.py
@@ -0,0 +1,74 @@
+from dolphin import event, memory
+from Modules import mkw_utils
+from Modules.framesequence import Frame
+from Modules.mkw_classes import vec3, VehiclePhysics, eulerAngle
+import math
+
+
+def main():
+ global position
+ position = VehiclePhysics.position(0)
+
+ global speed
+ speed = 80
+
+ global angles
+ angles = mkw_utils.get_facing_angle(0)
+
+ global prevInput
+ prevInput = Frame([0,0,0,0,0,0,0,0])
+
+ global frame
+ frame = mkw_utils.frame_of_input()
+
+ global yawAcc
+ yawAcc = eulerAngle(0,2,0)
+
+ global pitchAcc
+ pitchAcc = eulerAngle(2,0,0)
+
+
+@event.on_savestateload
+def on_loadstate(*_):
+ if memory.is_memory_accessible():
+ global position
+ position = VehiclePhysics.position(0)
+
+ global speed
+ speed = 80
+
+ global angles
+ angles = mkw_utils.get_facing_angle(0)
+
+@event.on_frameadvance
+def on_frame_advance():
+ global frame
+ global angles
+ global position
+ global prevInput
+ global speed
+
+ newframe = frame != mkw_utils.frame_of_input()
+ frame = mkw_utils.frame_of_input()
+
+ if newframe :
+ curInput = Frame.from_current_frame(0)
+
+ angles += yawAcc * (curInput.stick_x / 7)
+ angles -= pitchAcc * (curInput.stick_y / 7)
+
+ if curInput.item and not prevInput.item :
+ speed *= 1.1
+ if curInput.brake and not prevInput.brake:
+ speed /= 1.1
+
+ if curInput.accel:
+ position += angles.get_unit_vec3() * speed
+
+ prevInput = curInput
+
+ mkw_utils.player_teleport(0, position.x, position.y, position.z,
+ angles.pitch, angles.yaw, 0)
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/Macros/optimize_EV.py b/scripts/RMC/Macros/optimize_EV.py
new file mode 100644
index 0000000..7e68fff
--- /dev/null
+++ b/scripts/RMC/Macros/optimize_EV.py
@@ -0,0 +1,38 @@
+"""
+Usage: When holding left or right, neutral inputs will be added to optimize EV building via
+leaning. Holding a hard direction (+-7) will turn as tight as possible without
+sacrificing EV, while holding less than 7 will automatically turn as little as possible.
+Can be used for neutral gliding, supersliding, or any other lean-based EV tech.
+Enable this with the RFH script to perform supergrinding.
+"""
+from dolphin import controller, event # type: ignore
+from Modules import mkw_classes as mkw
+from Modules.macro_utils import MKWiiGCController
+
+def clamp(x: int, l: int, u: int):
+ return l if x < l else u if x > u else x
+
+@event.on_frameadvance
+def on_frame_advance():
+ if mkw.RaceManager().state() not in (mkw.RaceState.COUNTDOWN, mkw.RaceState.RACE):
+ return
+ if not mkw.KartSettings.is_bike():
+ return
+
+ ctrl = MKWiiGCController(controller)
+ user_inputs = ctrl.user_inputs()
+ kart_move = mkw.KartMove()
+
+ neutral_input = 1
+
+ # Soft drift
+ if abs(user_inputs['StickX']) < 7:
+ ctrl.set_inputs({
+ "StickX": clamp(user_inputs['StickX'], -2, 2),
+ })
+ neutral_input = -1
+
+ if abs(kart_move.lean_rot()) + kart_move.lean_rot_increase() > kart_move.lean_rot_cap():
+ ctrl.set_inputs({
+ "StickX": neutral_input * clamp(user_inputs['StickX'], -1, 1),
+ })
diff --git a/scripts/RMC/Macros/rapid_fire_hop.py b/scripts/RMC/Macros/rapid_fire_hop.py
new file mode 100644
index 0000000..4b855c3
--- /dev/null
+++ b/scripts/RMC/Macros/rapid_fire_hop.py
@@ -0,0 +1,21 @@
+"""
+Usage: Hold B to rapid fire hop. Enable the `optimize_EV` script to supergrind.
+"""
+from dolphin import controller, event # type: ignore
+from Modules import mkw_classes as mkw
+from Modules.macro_utils import MKWiiGCController
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ if mkw.RaceManager().state() != mkw.RaceState.RACE:
+ return
+
+ ctrl = MKWiiGCController(controller)
+ user_inputs = ctrl.user_inputs()
+
+ if user_inputs["B"]:
+ hop_pressed = mkw.KartState.bitfield() & 0x80 > 0
+ ctrl.set_inputs({
+ "B": not hop_pressed,
+ })
diff --git a/scripts/RMC/Macros/semi_auto_chains.py b/scripts/RMC/Macros/semi_auto_chains.py
new file mode 100644
index 0000000..9f3fa45
--- /dev/null
+++ b/scripts/RMC/Macros/semi_auto_chains.py
@@ -0,0 +1,19 @@
+"""
+Usage: Hold UP while in a wheelie to perform perfect chain wheelies automatically.
+"""
+from dolphin import controller, event # type: ignore
+from Modules import mkw_classes as mkw
+from Modules.macro_utils import MKWiiGCController
+
+@event.on_frameadvance
+def main():
+ if mkw.RaceManager().state() != mkw.RaceState.RACE:
+ return
+
+ ctrl = MKWiiGCController(controller)
+ user_inputs = ctrl.user_inputs()
+
+ if user_inputs["Up"] and mkw.KartMove.wheelie_frames() == 180:
+ ctrl.set_inputs({
+ "Up": False
+ })
diff --git a/scripts/RMC/Macros/soft_drift_assist.py b/scripts/RMC/Macros/soft_drift_assist.py
new file mode 100644
index 0000000..1cd8f08
--- /dev/null
+++ b/scripts/RMC/Macros/soft_drift_assist.py
@@ -0,0 +1,23 @@
+"""
+Usage: Hold a nonzero vertical stick input to clamp any horizontal stick inputs to optimal
+soft drift values. Useful for soft drifting without turning off Full Input Range.
+"""
+from dolphin import controller, event # type: ignore
+from Modules import mkw_classes as mkw
+from Modules.macro_utils import MKWiiGCController
+
+def clamp(x: int, l: int, u: int):
+ return l if x < l else u if x > u else x
+
+@event.on_frameadvance
+def on_frame_advance():
+ if mkw.RaceManager().state() != mkw.RaceState.RACE:
+ return
+
+ ctrl = MKWiiGCController(controller)
+ user_inputs = ctrl.user_inputs()
+
+ if user_inputs["StickY"] != 0:
+ ctrl.set_inputs({
+ "StickX": clamp(user_inputs["StickX"], -3, 3)
+ })
diff --git a/scripts/RMC/Macros/start_slide_left.py b/scripts/RMC/Macros/start_slide_left.py
new file mode 100644
index 0000000..6c264c1
--- /dev/null
+++ b/scripts/RMC/Macros/start_slide_left.py
@@ -0,0 +1,4 @@
+from Modules.startslide_utils import Direction, execute_startslide
+
+if __name__ == '__main__':
+ execute_startslide(Direction.LEFT)
\ No newline at end of file
diff --git a/scripts/RMC/Macros/start_slide_right.py b/scripts/RMC/Macros/start_slide_right.py
new file mode 100644
index 0000000..682131c
--- /dev/null
+++ b/scripts/RMC/Macros/start_slide_right.py
@@ -0,0 +1,4 @@
+from Modules.startslide_utils import Direction, execute_startslide
+
+if __name__ == '__main__':
+ execute_startslide(Direction.RIGHT)
\ No newline at end of file
diff --git a/scripts/RMC/Macros/superhop.py b/scripts/RMC/Macros/superhop.py
new file mode 100644
index 0000000..c6aa2fe
--- /dev/null
+++ b/scripts/RMC/Macros/superhop.py
@@ -0,0 +1,151 @@
+"""
+Usage: Hold B and commit to a drift to start superhopping in that direction.
+To counterhop, hold the stick in the opposite direction of the superhopping.
+To wheelie between hops, hold the UP button. Holding R will start a normal
+drift even if B is held. Vertical stick inputs will not be overwritten.
+"""
+from dolphin import controller, event, gui # type: ignore
+import dataclasses
+from Modules import mkw_classes as mkw
+from Modules.macro_utils import MKWiiGCController
+
+DEBUG_DISPLAY = False
+
+@dataclasses.dataclass
+class State:
+ stage: int = -1
+ direction: int = 0
+ hold_drift: bool = False
+
+ def __repr__(self):
+ return str(self.__dict__)
+
+ def copy(self):
+ return State(**dataclasses.asdict(self))
+
+
+@event.on_savestatesave
+def on_save_state(is_slot: bool, slot: int):
+ if is_slot:
+ savestates[slot] = state.copy()
+
+@event.on_savestateload
+def on_load_state(is_slot: bool, slot: int):
+ global state
+ if is_slot:
+ state = savestates[slot].copy()
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global state
+
+ race_mgr = mkw.RaceManager()
+ if race_mgr.state().value != mkw.RaceState.RACE.value:
+ return
+ if not mkw.KartSettings.is_bike():
+ return
+
+ ctrl = MKWiiGCController(controller)
+ user_inputs = ctrl.user_inputs()
+
+ kart_object = mkw.KartObject()
+ kart_move = mkw.KartMove(addr=kart_object.kart_move())
+
+ kart_collide = mkw.KartCollide(addr=kart_object.kart_collide())
+ floor_collision = kart_collide.surface_properties().value & 0x1000 > 0
+
+ if DEBUG_DISPLAY:
+ gui.draw_text((10, gui.get_display_size()[1] - 20), 0xFFFFFFFF, state.__repr__())
+
+
+ # Stop superhopping if R is held or B is released
+ if user_inputs['R'] or not user_inputs['B']:
+ state.stage = -1
+ state.direction = 0
+ return
+
+ # Start superhopping on first drift commit
+ if state.stage == -1 and kart_move.hop_stick_x() != 0:
+ state.direction = kart_move.hop_stick_x()
+ state.hold_drift = True
+ state.stage = 0
+
+ # Default superhopping state
+ if state.stage == 0:
+ # If vehicle is on ground, start hop sequence
+ if floor_collision:
+ state.stage = 1
+ # Otherwise, vehicle is in air so apply spindrift inputs
+ else:
+ neutral_glide = abs(kart_move.lean_rot()) + kart_move.lean_rot_increase() > kart_move.lean_rot_cap()
+ ctrl.set_inputs({
+ "A": True,
+ "B": True,
+ "StickX": 0 if neutral_glide else (7 * state.direction),
+ "Up": False,
+ })
+
+ # Hop sequence, Frame 1
+ if state.stage == 1:
+ # If next hop is drift hop, hold drift for one more frame
+ if state.hold_drift:
+ ctrl.set_inputs({
+ "A": True,
+ "B": True,
+ "StickX": 7 * state.direction,
+ "Up": False,
+ })
+ # If next hop isn't drift hop, skip
+ else:
+ state.stage = 2
+ # Frame 2: Release drift
+ if state.stage == 2:
+ ctrl.set_inputs({
+ "A": True,
+ "B": False,
+ "StickX": 2 * state.direction,
+ "Up": user_inputs["Up"],
+ })
+ # Frame 3: Hop
+ if state.stage == 3:
+ ctrl.set_inputs({
+ "A": True,
+ "B": True,
+ "StickX": 2 * state.direction,
+ "Up": False,
+ })
+ # Frame 4: Commit drift
+ if state.stage == 4:
+ counterhop = user_inputs["StickX"] * state.direction > 0
+ ctrl.set_inputs({
+ "A": True,
+ "B": True,
+ "StickX": (2 * state.direction) if counterhop else (-1 * state.direction),
+ "Up": False,
+ })
+ # Frame 5: Spindrift
+ if state.stage == 5:
+ ctrl.set_inputs({
+ "A": True,
+ "B": True,
+ "StickX": 7 * state.direction,
+ "Up": False,
+ })
+ state.hold_drift = not state.hold_drift
+ state.stage = 0
+
+ if state.stage > 0:
+ state.stage += 1
+
+
+def main():
+ global state
+ state = State()
+
+ global savestates
+ savestates = [state.copy()] * 10
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/Macros/wheelie_turn.py b/scripts/RMC/Macros/wheelie_turn.py
new file mode 100644
index 0000000..b02cba7
--- /dev/null
+++ b/scripts/RMC/Macros/wheelie_turn.py
@@ -0,0 +1,50 @@
+"""
+Usage: Hold left or right while in a wheelie to perform optimal wheelie turning. Currently
+does not account for boosts.
+"""
+from dolphin import controller, event # type: ignore
+import math
+from Modules import mkw_classes as mkw
+from Modules.macro_utils import MKWiiGCController
+
+def clamp(x: int, l: int, u: int):
+ return l if x < l else u if x > u else x
+
+@event.on_frameadvance
+def on_frame_advance():
+ if mkw.RaceManager().state() != mkw.RaceState.RACE:
+ return
+ if not mkw.KartSettings.is_bike():
+ return
+
+ ctrl = MKWiiGCController(controller)
+ user_inputs = ctrl.user_inputs()
+
+ player_stats = mkw.PlayerStats()
+ kart_move = mkw.KartMove()
+
+ current_speed = kart_move.speed()
+ top_speed = player_stats.base_speed() * 1.15
+ turn_speed = player_stats.handling_speed_multiplier()
+ A3 = player_stats.standard_accel_as(3)
+
+ formula = math.ceil(((1 - ((top_speed - A3) / top_speed)) / (1 - turn_speed)) * 7)
+
+ is_in_wheelie = mkw.KartState.bitfield(field_idx=0) & 0x20000000 > 0
+
+ if is_in_wheelie:
+ stick_val = 0
+ if top_speed < current_speed * (turn_speed + (1 - turn_speed)) + A3:
+ if formula == 1:
+ stick_val = 1
+ elif formula == 2:
+ stick_val = 2
+ else:
+ if formula == 1:
+ pass
+ elif formula == 2:
+ stick_val = 1
+
+ ctrl.set_inputs({
+ "StickX": clamp(user_inputs["StickX"], -stick_val, stick_val)
+ })
diff --git a/scripts/RMC/Misc. Scripts - Macros/semi_auto_chains.py b/scripts/RMC/Misc. Scripts - Macros/semi_auto_chains.py
deleted file mode 100644
index 5355e4b..0000000
--- a/scripts/RMC/Misc. Scripts - Macros/semi_auto_chains.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from dolphin import controller, event
-
-from Modules.mkw_classes import KartMove, RaceManager, RaceState
-
-@event.on_frameadvance
-def main():
- race_mgr = RaceManager()
- if race_mgr.state().value >= RaceState.COUNTDOWN.value:
- pressing_up = controller.get_gc_buttons(0)["Up"]
- if pressing_up and KartMove.wheelie_frames() == 180:
- controller.set_gc_buttons(
- 0, {"A": True,
- "Up": False,
- "StickX": controller.get_gc_buttons(0)["StickX"]})
diff --git a/scripts/RMC/Misc. Scripts - Macros/start_slide_left.py b/scripts/RMC/Misc. Scripts - Macros/start_slide_left.py
deleted file mode 100644
index 1cb2784..0000000
--- a/scripts/RMC/Misc. Scripts - Macros/start_slide_left.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from dolphin import event, gui, utils
-from Modules import ttk_lib
-from Modules.mkw_utils import frame_of_input
-from Modules import mkw_translations as translate
-from Modules.mkw_classes import RaceManager, RaceState, KartSettings
-from Modules.framesequence import FrameSequence
-import os
-
-flame_slide_bikes = ("Flame Runner", "Mach Bike", "Sugarscoot", "Zip Zip")
-spear_slide_bikes = ("Jet Bubble", "Phantom", "Spear", "Sneakster", "Wario Bike")
-star_slide_bikes = ("Bit Bike", "Bullet Bike", "Dolphin Dasher", "Magikruiser",
- "Quacker", "Shooting Star", "Standard Bike L", "Standard Bike M",
- "Standard Bike S")
-
-@event.on_savestateload
-def on_state_load(is_slot, slot):
- player_inputs.read_from_file()
-
-@event.on_frameadvance
-def on_frame_advance():
- frame = frame_of_input()
- stage = RaceManager.state()
-
- player_input = player_inputs[frame]
- if (player_input and stage.value == RaceState.COUNTDOWN.value):
- ttk_lib.write_player_inputs(player_input)
-
-def main() -> None:
- global player_inputs
- player_inputs = FrameSequence(check_vehicle(translate.vehicle_id()))
-
- gui.add_osd_message("Startslide: {} ".format(len(player_inputs) > 0))
-
-# Ensures the right slide for the currently selected bike is being loaded,
-# even through savestates and vehicle swaps.
-def check_vehicle(vehicle):
-
- # Returns True if the player is using a bike.
- if KartSettings.is_bike():
-
- path = utils.get_script_dir()
-
- if vehicle in flame_slide_bikes:
- return os.path.join(path, "MKW_Inputs", "Startslides", "flame_left.csv")
-
- elif vehicle in spear_slide_bikes:
- return os.path.join(path, "MKW_Inputs", "Startslides", "spear_left.csv")
-
- elif vehicle in star_slide_bikes:
- return os.path.join(path, "MKW_Inputs", "Startslides", "star_left.csv")
-
-if __name__ == '__main__':
- main()
diff --git a/scripts/RMC/Misc. Scripts - Macros/start_slide_right.py b/scripts/RMC/Misc. Scripts - Macros/start_slide_right.py
deleted file mode 100644
index f6a2033..0000000
--- a/scripts/RMC/Misc. Scripts - Macros/start_slide_right.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from dolphin import event, gui, utils
-from Modules import ttk_lib
-from Modules.mkw_utils import frame_of_input
-from Modules import mkw_translations as translate
-from Modules.mkw_classes import RaceManager, RaceState, KartSettings
-from Modules.framesequence import FrameSequence
-import os
-
-flame_slide_bikes = ("Flame Runner", "Mach Bike", "Sugarscoot", "Zip Zip")
-spear_slide_bikes = ("Jet Bubble", "Phantom", "Spear", "Sneakster", "Wario Bike")
-star_slide_bikes = ("Bit Bike", "Bullet Bike", "Dolphin Dasher", "Magikruiser",
- "Quacker", "Shooting Star", "Standard Bike L", "Standard Bike M",
- "Standard Bike S")
-
-@event.on_savestateload
-def on_state_load(is_slot, slot):
- player_inputs.read_from_file()
-
-@event.on_frameadvance
-def on_frame_advance():
- frame = frame_of_input()
- stage = RaceManager.state()
-
- player_input = player_inputs[frame]
- if (player_input and stage.value == RaceState.COUNTDOWN.value):
- ttk_lib.write_player_inputs(player_input)
-
-def main() -> None:
- global player_inputs
- player_inputs = FrameSequence(check_vehicle(translate.vehicle_id()))
-
- gui.add_osd_message("Startslide: {} ".format(len(player_inputs) > 0))
-
-# Ensures the right slide for the currently selected bike is being loaded,
-# even through savestates and vehicle swaps.
-def check_vehicle(vehicle):
-
- # Returns True if the player is using a bike.
- if KartSettings.is_bike():
-
- path = utils.get_script_dir()
-
- if vehicle in flame_slide_bikes:
- return os.path.join(path, "MKW_Inputs", "Startslides", "flame_right.csv")
-
- elif vehicle in spear_slide_bikes:
- return os.path.join(path, "MKW_Inputs", "Startslides", "spear_right.csv")
-
- elif vehicle in star_slide_bikes:
- return os.path.join(path, "MKW_Inputs", "Startslides", "star_right.csv")
-
-if __name__ == '__main__':
- main()
diff --git a/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_left.py b/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_left.py
deleted file mode 100644
index 9f4cdbb..0000000
--- a/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_left.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from dolphin import controller, event
-import math
-
-from Modules.mkw_classes import PlayerStats, KartMove
-
-@event.on_frameadvance
-def on_frame_advance():
- player_stats = PlayerStats()
- kart_move = KartMove()
-
- speed = player_stats.base_speed()
- current_speed = kart_move.speed()
- top_speed = speed * 1.15
-
- turn_speed = player_stats.handling_speed_multiplier()
-
- A3 = player_stats.standard_accel_as(3)
-
- wheelie_frames = kart_move.wheelie_frames()
-
- formula = math.ceil(((1 - ((top_speed - A3) / top_speed)) / (1 - turn_speed)) * 7)
-
- if (wheelie_frames != 181):
- stick_val = 128
- if top_speed < current_speed * (turn_speed + (1 - turn_speed)) + A3:
- if formula == 1:
- stick_val = 112
- elif formula == 2:
- stick_val = 104
- else:
- if formula == 1:
- pass
- elif formula == 2:
- stick_val = 112
- controller.set_gc_buttons(
- 0, {"A": True, "StickX": stick_val})
- else:
- controller.set_gc_buttons(
- 0, {"A": True,
- "Up": controller.get_gc_buttons(0)["Up"],
- "StickX": controller.get_gc_buttons(0)["StickX"]})
diff --git a/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_right.py b/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_right.py
deleted file mode 100644
index 0e048bc..0000000
--- a/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_right.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from dolphin import controller, event
-import math
-
-from Modules.mkw_classes import PlayerStats, KartMove
-
-@event.on_frameadvance
-def on_frame_advance():
- player_stats = PlayerStats()
- kart_move = KartMove()
-
- speed = player_stats.base_speed()
- current_speed = kart_move.speed()
- top_speed = speed * 1.15
-
- turn_speed = player_stats.handling_speed_multiplier()
-
- A3 = player_stats.standard_accel_as(3)
-
- wheelie_frames = kart_move.wheelie_frames()
-
- formula = math.ceil(((1 - ((top_speed - A3) / top_speed)) / (1 - turn_speed)) * 7)
-
- if (wheelie_frames != 181):
- stick_val = 128
- if top_speed < current_speed * (turn_speed + (1 - turn_speed)) + A3:
- if formula == 1:
- stick_val = 152
- elif formula == 2:
- stick_val = 167
- else:
- if formula == 1:
- pass
- elif formula == 2:
- stick_val = 152
- controller.set_gc_buttons(
- 0, {"A": True, "StickX": stick_val})
- else:
- controller.set_gc_buttons(
- 0, {"A": True,
- "Up": controller.get_gc_buttons(0)["Up"],
- "StickX": controller.get_gc_buttons(0)["StickX"]})
diff --git a/scripts/RMC/RKG_Loader.py b/scripts/RMC/RKG_Loader.py
new file mode 100644
index 0000000..a415e9b
--- /dev/null
+++ b/scripts/RMC/RKG_Loader.py
@@ -0,0 +1,73 @@
+from dolphin import gui, utils, event, memory
+from Modules import ttk_lib
+from Modules import agc_lib
+from Modules import mkw_utils
+from Modules.mkw_classes import RaceManager, RaceState
+from external import external_utils as ex
+from Modules.rkg_lib import decode_RKG
+import os
+import sys
+
+
+"""
+save_player_to_player_csv
+
+This script takes the player's inputs and writes them to the player csv
+"""
+
+def main() -> None:
+ gui.add_osd_message("Script started")
+ global agc_metadata
+ global input_sequence
+ global end
+
+ #Prompt the user to select a .rkg file
+ filetype = [('RKG files', '*.rkg'), ('All files', '*')]
+ scriptDir = utils.get_script_dir()
+ ghostDir = os.path.join(scriptDir, 'Ghost')
+
+ file_path = ex.open_dialog_box(scriptDir, filetype, '', 'Open a RKG File')
+
+ with open(file_path, 'rb') as f:
+ metadata, input_sequence, mii_data = decode_RKG(f.read())
+
+ agc_metadata = agc_lib.AGCMetaData.read_from_RKGMetadata(metadata)
+
+ if input_sequence:
+ end = False
+ if not mkw_utils.is_single_player():
+ if RaceManager().state().value > 0:
+ ttk_lib.write_inputs_to_current_ghost_rkg(input_sequence)
+ agc_metadata.delay_timer(0)
+ else:
+ end = True
+
+if __name__ == '__main__':
+ main()
+
+@event.on_savestateload
+def on_state_load(is_slot, slot):
+ if memory.is_memory_accessible() and not mkw_utils.is_single_player():
+ ttk_lib.write_inputs_to_current_ghost_rkg(input_sequence)
+ agc_metadata.delay_timer(0)
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global agc_metadata
+ global input_sequence
+ global end
+
+ if not end:
+ racestate = RaceManager().state().value
+
+ agc_metadata.load(1)
+
+ if not mkw_utils.is_single_player():
+ if racestate == RaceState.COUNTDOWN.value :
+ ttk_lib.write_inputs_to_current_ghost_rkg(input_sequence)
+ agc_metadata.delay_timer(0)
+
+
+
+
diff --git a/scripts/RMC/TTK - Activate/tas_toolkit.py b/scripts/RMC/TTK - Activate/tas_toolkit.py
index 73b7202..b744c88 100644
--- a/scripts/RMC/TTK - Activate/tas_toolkit.py
+++ b/scripts/RMC/TTK - Activate/tas_toolkit.py
@@ -1,4 +1,4 @@
-from dolphin import event, gui
+from dolphin import event, gui, memory
from Modules import ttk_lib
from Modules.mkw_utils import frame_of_input
from Modules.framesequence import FrameSequence
@@ -16,12 +16,18 @@
@event.on_savestateload
def on_state_load(is_slot, slot):
+ global player_inputs
+ global ghost_inputs
+ if memory.is_memory_accessible():
+ player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER)
+ ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST)
player_inputs.read_from_file()
ghost_inputs.read_from_file()
-@event.on_frameadvance
-def on_frame_advance():
+@event.on_framebegin
+def on_frame_begin():
global player_inputs, ghost_inputs
+
frame = frame_of_input()
state = RaceManager.state().value
inputs_ready = state in (RaceState.COUNTDOWN.value, RaceState.RACE.value)
@@ -39,6 +45,7 @@ def main() -> None:
global player_inputs, ghost_inputs
player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER)
ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST)
+
gui.add_osd_message(
"TTK | Player: {} | Ghost: {}".format(
diff --git a/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only.py b/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only.py
index 57f00e3..bf6d5a3 100644
--- a/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only.py
+++ b/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only.py
@@ -1,4 +1,4 @@
-from dolphin import event, gui
+from dolphin import event, gui, memory
from Modules import ttk_lib
from Modules.mkw_utils import frame_of_input
from Modules.framesequence import FrameSequence
@@ -15,11 +15,15 @@
@event.on_savestateload
def on_state_load(is_slot, slot):
+ global ghost_inputs
+ if memory.is_memory_accessible():
+ ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST)
ghost_inputs.read_from_file()
-@event.on_frameadvance
-def on_frame_advance():
+@event.on_framebegin
+def on_frame_begin():
global ghost_inputs
+
frame = frame_of_input()
state = RaceManager.state().value
inputs_ready = state in (RaceState.COUNTDOWN.value, RaceState.RACE.value)
@@ -32,6 +36,7 @@ def main() -> None:
# Load both the player and ghost input sequences
global ghost_inputs
ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST)
+
gui.add_osd_message(
"TTK | Player: {} | Ghost: {}".format(
diff --git a/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only_hard.py b/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only_hard.py
new file mode 100644
index 0000000..142eecb
--- /dev/null
+++ b/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only_hard.py
@@ -0,0 +1,48 @@
+from dolphin import event, gui, memory
+from Modules import ttk_lib
+from Modules.mkw_utils import frame_of_input
+from Modules.framesequence import FrameSequence
+from Modules.mkw_classes import RaceManager, RaceState
+
+
+"""
+tas_toolkit_ghost_only
+
+This script reads inputs from the ghost csv files, and applies it live in-game
+The inputs are reloaded on every state load
+"""
+
+@event.on_savestateload
+def on_state_load(is_slot, slot):
+ global ghost_inputs
+ if memory.is_memory_accessible():
+ ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST)
+ ghost_inputs.read_from_file()
+
+@event.on_framebegin
+def on_frame_begin():
+ global ghost_inputs
+
+ frame = frame_of_input()
+ state = RaceManager.state().value
+ inputs_ready = state in (RaceState.COUNTDOWN.value, RaceState.RACE.value, RaceState.INTRO_CAMERA.value)
+
+ if inputs_ready:
+ ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs)
+
+def main() -> None:
+ # Load both the player and ghost input sequences
+ global ghost_inputs
+ ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST)
+ ghost_inputs.read_from_file()
+
+
+ gui.add_osd_message(
+ "TTK | Player: {} | Ghost: {}".format(
+ False, len(ghost_inputs) > 0
+ )
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/TTK - Activate/tas_toolkit_player_only.py b/scripts/RMC/TTK - Activate/tas_toolkit_player_only.py
index ff480ac..c493574 100644
--- a/scripts/RMC/TTK - Activate/tas_toolkit_player_only.py
+++ b/scripts/RMC/TTK - Activate/tas_toolkit_player_only.py
@@ -1,4 +1,4 @@
-from dolphin import event, gui
+from dolphin import event, gui, memory
from Modules import ttk_lib
from Modules.mkw_utils import frame_of_input
from Modules.framesequence import FrameSequence
@@ -15,11 +15,17 @@
@event.on_savestateload
def on_state_load(is_slot, slot):
+ global player_inputs
+ if memory.is_memory_accessible():
+ player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER)
player_inputs.read_from_file()
-@event.on_frameadvance
-def on_frame_advance():
+
+@event.on_framebegin
+def on_frame_begin():
global player_inputs
+ global frame
+
frame = frame_of_input()
state = RaceManager.state().value
inputs_ready = state in (RaceState.COUNTDOWN.value, RaceState.RACE.value)
@@ -32,6 +38,7 @@ def main() -> None:
# Load both the player and ghost input sequences
global player_inputs
player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER)
+
gui.add_osd_message(
"TTK | Player: {} | Ghost: {}".format(
diff --git a/scripts/RMC/TTK - Saving/Erase_Player_Buffer_RKG.py b/scripts/RMC/TTK - Saving/Erase_Player_Buffer_RKG.py
new file mode 100644
index 0000000..c582989
--- /dev/null
+++ b/scripts/RMC/TTK - Saving/Erase_Player_Buffer_RKG.py
@@ -0,0 +1,13 @@
+from Modules import ttk_lib
+from Modules.mkw_utils import frame_of_input
+from Modules.framesequence import FrameSequence, Frame
+
+
+
+def main() -> None:
+ nulFrameSequence = FrameSequence()
+ nulFrameSequence.read_from_list_of_frames([Frame.default() for _ in range(frame_of_input())])
+ ttk_lib.setPlayerRKGBuffer(nulFrameSequence)
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/TTK - Saving/save_GUI.py b/scripts/RMC/TTK - Saving/save_GUI.py
new file mode 100644
index 0000000..0e12892
--- /dev/null
+++ b/scripts/RMC/TTK - Saving/save_GUI.py
@@ -0,0 +1,70 @@
+from dolphin import gui, utils, memory, event
+from Modules import ttk_lib
+from external import external_utils as ex
+from Modules.rkg_lib import decode_RKG, encode_RKG, RKGMetaData
+from Modules.framesequence import FrameSequence
+import os
+import sys
+import time
+
+
+
+def main() -> None:
+ gui.add_osd_message("Script started")
+
+ scriptDir = utils.get_script_dir()
+ scriptname = os.path.join(scriptDir, 'external', 'TTK_Save_GUI_window.py')
+
+ std = ex.run_external_script(scriptname)
+ args = std.split('\n')[0].split('|')
+
+ if len(args) > 1:
+ if args[0] == 'Player Inputs':
+ inputs = ttk_lib.read_full_decoded_rkg_data(ttk_lib.PlayerType.PLAYER)
+ elif args[0] == 'Ghost Inputs':
+ inputs = ttk_lib.read_full_decoded_rkg_data(ttk_lib.PlayerType.GHOST)
+ elif args[0] == 'CSV Player':
+ inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER)
+ inputs.read_from_file()
+ elif args[0] == 'CSV Ghost':
+ inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST)
+ inputs.read_from_file()
+ else:
+ if args[0][-4:] == ".rkg":
+ with open(args[0], "rb") as f:
+ inputs = decode_RKG(f.read())[1]
+ elif args[0][-4:] == ".csv":
+ inputs = FrameSequence(args[0])
+ inputs.read_from_file()
+ else:
+ raise ValueError('Invalid source file')
+ else:
+ raise ValueError('Invalid source file')
+
+ with open(os.path.join(scriptDir, 'Modules', "default.mii"), 'rb') as f:
+ mii_data = f.read()[:0x4A]
+
+ for i in range(1, len(args)):
+ arg = args[i]
+ if arg == 'csv_player':
+ ttk_lib.write_to_csv(inputs, ttk_lib.PlayerType.PLAYER)
+ elif arg == 'csv_ghost':
+ ttk_lib.write_to_csv(inputs, ttk_lib.PlayerType.GHOST)
+ elif arg[-4:] == ".rkg":
+ with open(arg, "wb") as f:
+ f.write(encode_RKG(RKGMetaData.from_current_race(), inputs, mii_data))
+ elif arg[-4:] == ".csv":
+ inputs.write_to_file(arg)
+
+
+
+if __name__ == '__main__':
+ main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_ghost_to_both_csv.py b/scripts/RMC/TTK - Saving/save_ghost_to_both_csv.py
index d25f0ba..35f9d16 100644
--- a/scripts/RMC/TTK - Saving/save_ghost_to_both_csv.py
+++ b/scripts/RMC/TTK - Saving/save_ghost_to_both_csv.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, utils, event
from Modules import ttk_lib
+import time
"""
save_ghost_to_both_csv
@@ -20,5 +21,15 @@ def main() -> None:
ttk_lib.write_to_csv(input_sequence, ttk_lib.PlayerType.GHOST)
ttk_lib.write_to_csv(input_sequence, ttk_lib.PlayerType.PLAYER)
+
+
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_ghost_to_ghost_csv.py b/scripts/RMC/TTK - Saving/save_ghost_to_ghost_csv.py
index 1593222..df5686d 100644
--- a/scripts/RMC/TTK - Saving/save_ghost_to_ghost_csv.py
+++ b/scripts/RMC/TTK - Saving/save_ghost_to_ghost_csv.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, utils, event
from Modules import ttk_lib
+import time
"""
save_ghost_to_ghost_csv
@@ -21,3 +22,11 @@ def main() -> None:
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_ghost_to_player_csv.py b/scripts/RMC/TTK - Saving/save_ghost_to_player_csv.py
index 52b73f0..27c2cbe 100644
--- a/scripts/RMC/TTK - Saving/save_ghost_to_player_csv.py
+++ b/scripts/RMC/TTK - Saving/save_ghost_to_player_csv.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, utils, event
from Modules import ttk_lib
+import time
"""
save_ghost_to_player_csv
@@ -21,3 +22,11 @@ def main() -> None:
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_ghost_to_rkg.py b/scripts/RMC/TTK - Saving/save_ghost_to_rkg.py
index 6ecb275..be7ef4f 100644
--- a/scripts/RMC/TTK - Saving/save_ghost_to_rkg.py
+++ b/scripts/RMC/TTK - Saving/save_ghost_to_rkg.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, utils, event
from Modules import ttk_lib
+import time
"""
save_ghost_to_rkg
@@ -22,3 +23,11 @@ def main() -> None:
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_player_to_both_csv.py b/scripts/RMC/TTK - Saving/save_player_to_both_csv.py
index 5b972eb..980fbfd 100644
--- a/scripts/RMC/TTK - Saving/save_player_to_both_csv.py
+++ b/scripts/RMC/TTK - Saving/save_player_to_both_csv.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, event, utils
from Modules import ttk_lib
+import time
"""
save_player_to_both_csv
@@ -22,3 +23,11 @@ def main() -> None:
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_player_to_file_csv.py b/scripts/RMC/TTK - Saving/save_player_to_file_csv.py
new file mode 100644
index 0000000..c9af753
--- /dev/null
+++ b/scripts/RMC/TTK - Saving/save_player_to_file_csv.py
@@ -0,0 +1,42 @@
+from dolphin import gui, utils, event
+from Modules import ttk_lib
+from external import external_utils as ex
+import os
+import time
+
+"""
+save_player_to_player_csv
+
+This script takes the player's inputs and writes them to the player csv
+"""
+
+def main() -> None:
+ gui.add_osd_message("Script started")
+
+ # Convert internal RKG to input list
+ input_sequence = ttk_lib.read_full_decoded_rkg_data(ttk_lib.PlayerType.PLAYER)
+
+ if (input_sequence is None or len(input_sequence) == 0):
+ gui.add_osd_message("No inputs read!")
+ return
+
+ filetype = [('CSV files', '*.csv'), ('All files', '*')]
+ scriptDir = utils.get_script_dir()
+ ttk_dir = os.path.join(scriptDir, 'MKW_Inputs')
+ write_file = ex.save_dialog_box(scriptDir, filetype, ttk_dir, 'Save as CSV File')
+
+ if write_file:
+ input_sequence.write_to_file(write_file)
+ else:
+ gui.add_osd_message("Save Aborted")
+
+if __name__ == '__main__':
+ main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_player_to_ghost_csv.py b/scripts/RMC/TTK - Saving/save_player_to_ghost_csv.py
index 57bbbba..c78f103 100644
--- a/scripts/RMC/TTK - Saving/save_player_to_ghost_csv.py
+++ b/scripts/RMC/TTK - Saving/save_player_to_ghost_csv.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, utils, event
from Modules import ttk_lib
+import time
"""
save_player_to_ghost_csv
@@ -21,3 +22,11 @@ def main() -> None:
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_player_to_player_csv.py b/scripts/RMC/TTK - Saving/save_player_to_player_csv.py
index 45cb644..79c60a0 100644
--- a/scripts/RMC/TTK - Saving/save_player_to_player_csv.py
+++ b/scripts/RMC/TTK - Saving/save_player_to_player_csv.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, event, utils
from Modules import ttk_lib
+import time
"""
save_player_to_player_csv
@@ -21,3 +22,11 @@ def main() -> None:
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_player_to_rkg.py b/scripts/RMC/TTK - Saving/save_player_to_rkg.py
index f385869..e83d111 100644
--- a/scripts/RMC/TTK - Saving/save_player_to_rkg.py
+++ b/scripts/RMC/TTK - Saving/save_player_to_rkg.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, event, utils
from Modules import ttk_lib
+import time
"""
save_player_to_rkg
@@ -22,3 +23,11 @@ def main() -> None:
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_rkg_from_ghost_csv.py b/scripts/RMC/TTK - Saving/save_rkg_from_ghost_csv.py
index 559a9ac..091aaf2 100644
--- a/scripts/RMC/TTK - Saving/save_rkg_from_ghost_csv.py
+++ b/scripts/RMC/TTK - Saving/save_rkg_from_ghost_csv.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, event, utils
from Modules import ttk_lib
+import time
"""
save_rkg_from_ghost_csv
@@ -21,3 +22,11 @@ def main() -> None:
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_rkg_from_player_csv.py b/scripts/RMC/TTK - Saving/save_rkg_from_player_csv.py
index 44ff7f3..1a732b8 100644
--- a/scripts/RMC/TTK - Saving/save_rkg_from_player_csv.py
+++ b/scripts/RMC/TTK - Saving/save_rkg_from_player_csv.py
@@ -1,5 +1,6 @@
-from dolphin import gui
+from dolphin import gui, event, utils
from Modules import ttk_lib
+import time
"""
save_rkg_from_player_csv
@@ -21,3 +22,11 @@ def main() -> None:
if __name__ == '__main__':
main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/TTK - Saving/save_rkg_to_player_csv.py b/scripts/RMC/TTK - Saving/save_rkg_to_player_csv.py
new file mode 100644
index 0000000..9d7d2fc
--- /dev/null
+++ b/scripts/RMC/TTK - Saving/save_rkg_to_player_csv.py
@@ -0,0 +1,48 @@
+from dolphin import gui, utils, event
+from Modules import ttk_lib
+from external import external_utils as ex
+from Modules.rkg_lib import decode_RKG
+import os
+import sys
+import time
+
+
+"""
+save_player_to_player_csv
+
+This script takes the player's inputs and writes them to the player csv
+"""
+
+def main() -> None:
+ gui.add_osd_message("Script started")
+
+ #Prompt the user to select a .rkg file
+ filetype = [('RKG files', '*.rkg'), ('All files', '*')]
+ scriptDir = utils.get_script_dir()
+ ghostDir = os.path.join(scriptDir, 'Ghost')
+
+ file_path = ex.open_dialog_box(scriptDir, filetype, ghostDir, 'Open a RKG File')
+
+ if not file_path:
+ gui.add_osd_message("No file selected!")
+ return
+
+ with open(file_path, 'rb') as f:
+ input_sequence = decode_RKG(f.read())[1]
+
+ if (input_sequence is None or len(input_sequence) == 0):
+ gui.add_osd_message("No inputs read!")
+ return
+
+ ttk_lib.write_to_csv(input_sequence, ttk_lib.PlayerType.PLAYER)
+
+if __name__ == '__main__':
+ main()
+ global script_end_time
+ script_end_time = time.time()
+
+
+@event.on_timertick
+def cancel():
+ if script_end_time and (time.time() - script_end_time > 0.2):
+ utils.cancel_script(utils.get_script_name())
diff --git a/scripts/RMC/_RKG_AutoSave.py b/scripts/RMC/_RKG_AutoSave.py
new file mode 100644
index 0000000..8ea7ced
--- /dev/null
+++ b/scripts/RMC/_RKG_AutoSave.py
@@ -0,0 +1,54 @@
+from dolphin import event, gui, utils
+import configparser
+import math
+import os
+from binascii import hexlify
+
+from Modules.mkw_classes.common import SurfaceProperties, eulerAngle
+from Modules.mkw_utils import History
+
+from Modules import settings_utils as setting
+from Modules import mkw_utils as mkw_utils
+from Modules import mkw_translations as mkw_translations
+from Modules.rkg_lib import get_RKG_data_memory, decode_RKG
+from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, TimerManager
+from Modules.mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings
+from Modules.mkw_classes import KartObject, KartMove, KartSettings, KartBody
+from Modules.mkw_classes import VehicleDynamics, VehiclePhysics, KartBoost, KartJump
+from Modules.mkw_classes import KartState, KartCollide, KartInput, RaceInputState
+
+
+
+
+def main():
+ global state
+ state = RaceManager.state().value
+
+if __name__ == '__main__':
+ main()
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global state
+
+ if RaceManager.state().value == 4:
+ if state != 5 :
+ is_saved, rkg_data = get_RKG_data_memory()
+
+ if is_saved:
+ state = 5
+ course_id = RaceConfigSettings(RaceConfigScenario(RaceConfig.race_scenario()).settings()).course_id()
+ metadata, inputList, mii_data = decode_RKG(rkg_data)
+ crc_string = str(hexlify(rkg_data[-4:]))[2:-2]
+ ft_string = f'_{metadata.finish_time:.3f}s_'
+ filename = os.path.join(utils.get_script_dir(), 'Ghost', 'AutoSave', mkw_translations.course_slot_abbreviation() +ft_string+crc_string+'.rkg')
+ with open(filename, 'wb') as f:
+ f.write(rkg_data)
+ gui.add_osd_message("Ghost saved to "+filename)
+ else:
+ state = 4
+ else:
+ state = RaceManager.state().value
+
+
diff --git a/scripts/RMC/_TTK_Backup.py b/scripts/RMC/_TTK_Backup.py
new file mode 100644
index 0000000..7f6a548
--- /dev/null
+++ b/scripts/RMC/_TTK_Backup.py
@@ -0,0 +1,28 @@
+from dolphin import event, gui, utils, memory
+
+from Modules import settings_utils as setting
+from Modules import ttk_lib as ttk_lib
+from Modules import mkw_translations as mkw_translations
+from Modules.mkw_classes import RaceManager, RaceState
+
+def main():
+ global backup_count
+ backup_count = 0
+
+if __name__ == '__main__':
+ main()
+
+
+@event.on_beforesavestateload
+def do_backup(_, __):
+ global backup_count
+
+
+ if memory.is_memory_accessible() and RaceManager.state().value in [1,2]:
+ config = setting.get_ttk_config()
+ backup_count += 1
+ backup_count %= config.ttk_backup
+
+ inputs = ttk_lib.read_full_decoded_rkg_data(ttk_lib.PlayerType.PLAYER)
+ ttk_lib.write_to_backup_csv(inputs, backup_count)
+
diff --git a/scripts/RMC/_draw_info_display.py b/scripts/RMC/_draw_info_display.py
index b460731..12631b4 100644
--- a/scripts/RMC/_draw_info_display.py
+++ b/scripts/RMC/_draw_info_display.py
@@ -1,274 +1,120 @@
-from dolphin import event, gui, utils
+from dolphin import event, gui, utils, memory
import configparser
import math
import os
-from Modules.mkw_classes.common import SurfaceProperties
+from Modules.mkw_classes.common import SurfaceProperties, eulerAngle
+from Modules.mkw_utils import History
-import Modules.mkw_utils as mkw_utils
-from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState
-from Modules.mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings
-from Modules.mkw_classes import KartObject, KartMove, KartSettings, KartBody
-from Modules.mkw_classes import VehicleDynamics, VehiclePhysics, KartBoost, KartJump
-from Modules.mkw_classes import KartState, KartCollide, KartInput, RaceInputState
+from external.external_utils import run_external_script
+from Modules import settings_utils as setting
+from Modules import mkw_utils as mkw_utils
+from Modules.infodisplay_utils import draw_infodisplay, create_infodisplay
+from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, KartObjectManager, VehiclePhysics
-def populate_default_config(file_path):
- config = configparser.ConfigParser()
-
- config['DEBUG'] = {}
- config['DEBUG']['Debug'] = "False"
-
- config['INFO DISPLAY'] = {}
- config['INFO DISPLAY']["Frame Count"] = "True"
- config['INFO DISPLAY']["Lap Splits"] = "False"
- config['INFO DISPLAY']["Speed"] = "True"
- config['INFO DISPLAY']["Internal Velocity (X, Y, Z)"] = "False"
- config['INFO DISPLAY']["Internal Velocity (XYZ)"] = "False"
- config['INFO DISPLAY']["External Velocity (X, Y, Z)"] = "False"
- config['INFO DISPLAY']["External Velocity (XYZ)"] = "True"
- config['INFO DISPLAY']["Moving Road Velocity (X, Y, Z)"] = "False"
- config['INFO DISPLAY']["Moving Road Velocity (XYZ)"] = "False"
- config['INFO DISPLAY']["Moving Water Velocity (X, Y, Z)"] = "False"
- config['INFO DISPLAY']["Moving Water Velocity (XYZ)"] = "False"
- config['INFO DISPLAY']["Charges and Boosts"] = "True"
- config['INFO DISPLAY']["Checkpoints and Completion"] = "True"
- config['INFO DISPLAY']["Airtime"] = "True"
- config['INFO DISPLAY']["Miscellaneous"] = "False"
- config['INFO DISPLAY']["Surface Properties"] = "False"
- config['INFO DISPLAY']["Position"] = "False"
- config['INFO DISPLAY']["Stick"] = "True"
- config['INFO DISPLAY']["Text Color (ARGB)"] = "0xFFFFFFFF"
- config['INFO DISPLAY']["Digits (to round to)"] = "6"
-
- with open(file_path, 'w') as f:
- config.write(f)
-
- return config
-class ConfigInstance():
- def __init__(self, config : configparser.ConfigParser):
- self.debug = config['DEBUG'].getboolean('Debug')
- self.frame_count = config['INFO DISPLAY'].getboolean('Frame Count')
- self.lap_splits = config['INFO DISPLAY'].getboolean('Lap Splits')
- self.speed = config['INFO DISPLAY'].getboolean('Speed')
- self.iv = config['INFO DISPLAY'].getboolean('Internal Velocity (X, Y, Z)')
- self.iv_xyz = config['INFO DISPLAY'].getboolean('Internal Velocity (XYZ)')
- self.ev = config['INFO DISPLAY'].getboolean('External Velocity (X, Y, Z)')
- self.ev_xyz = config['INFO DISPLAY'].getboolean('External Velocity (XYZ)')
- self.mrv = config['INFO DISPLAY'].getboolean('Moving Road Velocity (X, Y, Z)')
- self.mrv_xyz = config['INFO DISPLAY'].getboolean('Moving Road Velocity (XYZ)')
- self.mwv = config['INFO DISPLAY'].getboolean('Moving Water Velocity (X, Y, Z)')
- self.mwv_xyz = config['INFO DISPLAY'].getboolean('Moving Water Velocity (XYZ)')
- self.charges = config['INFO DISPLAY'].getboolean('Charges and Boosts')
- self.cps = config['INFO DISPLAY'].getboolean('Checkpoints and Completion')
- self.air = config['INFO DISPLAY'].getboolean('Airtime')
- self.misc = config['INFO DISPLAY'].getboolean('Miscellaneous')
- self.surfaces = config['INFO DISPLAY'].getboolean('Surface Properties')
- self.position = config['INFO DISPLAY'].getboolean('Position')
- self.stick = config['INFO DISPLAY'].getboolean('Stick')
- self.color = int(config['INFO DISPLAY']['Text Color (ARGB)'], 16)
- self.digits = min(7, config['INFO DISPLAY'].getint('Digits (to round to)'))
-
-def main():
- config = configparser.ConfigParser()
- file_path = os.path.join(utils.get_script_dir(), 'modules', 'infodisplay.ini')
- config.read(file_path)
- if not config.sections():
- config = populate_default_config(file_path)
-
- global c
- c = ConfigInstance(config)
-if __name__ == '__main__':
- main()
-# draw information to the screen
+@event.on_savestateload
+def on_state_load(fromSlot: bool, slot: int):
+ global c
+ c = setting.get_infodisplay_config()
+
+ RaceComp_History.clear()
+ Angle_History.clear()
+ Pos_History.clear()
+
+ global maxLap
+ maxLap = 0
+
+ global text
+ if memory.is_memory_accessible() and mkw_utils.extended_race_state() >= 0:
+ Angle_History.update()
+ RaceComp_History.update()
+ maxLap = int(RaceManagerPlayer.race_completion_max(0))
+ text = create_infodisplay(c, RaceComp_History, Angle_History)
+ else:
+ text = ''
+
-def create_infodisplay():
- text = ""
- if c.debug:
- # test values here
- text += f"{utils.get_game_id()}\n\n"
+@event.on_framepresent
+def on_present():
+ gui.draw_text((10, 10), c.color, text)
- if c.frame_count:
- text += f"Frame: {mkw_utils.frame_of_input()}\n\n"
- race_mgr_player = RaceManagerPlayer()
- race_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario())
- race_settings = RaceConfigSettings(race_scenario.settings())
- kart_object = KartObject()
- kart_state = KartState(addr=kart_object.kart_state())
- kart_move = KartMove(addr=kart_object.kart_move())
- kart_body = KartBody(addr=kart_object.kart_body())
- vehicle_dynamics = VehicleDynamics(addr=kart_body.vehicle_dynamics())
- vehicle_physics = VehiclePhysics(addr=vehicle_dynamics.vehicle_physics())
-
-
- if c.lap_splits:
- # The actual max lap address does not update when crossing the finish line
- # for the final time to finish the race. However, for whatever reason,
- # race completion does. We use the "max" version to prevent lap times
- # from disappearing when crossing the line backwards.
- player_max_lap = math.floor(race_mgr_player.race_completion_max())
- lap_count = race_settings.lap_count()
-
- if player_max_lap >= 2 and lap_count > 1:
- for lap in range(1, player_max_lap):
- text += "Lap {}: {}\n".format(lap, mkw_utils.update_exact_finish(lap, 0))
-
- if player_max_lap > lap_count:
- text += "Final: {}\n".format(mkw_utils.get_unrounded_time(lap_count, 0))
- text += "\n"
-
- if c.speed:
- speed = mkw_utils.delta_position(playerIdx=0)
- engine_speed = kart_move.speed()
- cap = kart_move.soft_speed_limit()
- text += f" XZ: {round(speed.length_xz(), c.digits)}\n"
- text += f" XYZ: {round(speed.length(), c.digits)}\n"
- text += f" Y: {round(speed.y, c.digits)}\n"
- text += f" Engine: {round(engine_speed, c.digits)} / {round(cap, c.digits)}"
- text += "\n\n"
-
- if (c.iv or c.iv_xyz):
- iv = vehicle_physics.internal_velocity()
-
- if c.iv:
- text += f" IV X: {round(iv.x,c.digits)}\n"
- text += f" IV Y: {round(iv.y,c.digits)}\n"
- text += f" IV Z: {round(iv.z,c.digits)}\n\n"
-
- if c.iv_xyz:
- text += f" IV XZ: {round(iv.length_xz(),c.digits)}\n"
- text += f" IV XYZ: {round(iv.length(),c.digits)}\n\n"
-
- if (c.ev or c.ev_xyz):
- ev = vehicle_physics.external_velocity()
-
- if c.ev:
- text += f" EV X: {round(ev.x,c.digits)}\n"
- text += f" EV Y: {round(ev.y,c.digits)}\n"
- text += f" EV Z: {round(ev.z,c.digits)}\n\n"
-
- if c.ev_xyz:
- text += f" EV XZ: {round(ev.length_xz(),c.digits)}\n"
- text += f" EV XYZ: {round(ev.length(),c.digits)}\n\n"
-
- if (c.mrv or c.mrv_xyz):
- mrv = vehicle_physics.moving_road_velocity()
-
- if c.mrv:
- text += f" MRV X: {round(mrv.x,c.digits)}\n"
- text += f" MRV Y: {round(mrv.y,c.digits)}\n"
- text += f" MRV Z: {round(mrv.z,c.digits)}\n\n"
+def main():
+ global c
+ c = setting.get_infodisplay_config()
+
+ global Frame_of_input
+ Frame_of_input = 0
+
+ def prc():
+ if KartObjectManager.player_count() > 0:
+ return RaceManagerPlayer(0).race_completion()
+ return 0
+ def grc():
+ if KartObjectManager.player_count() > 1:
+ return RaceManagerPlayer(1).race_completion()
+ return 0
+ def fa():
+ return mkw_utils.get_facing_angle(0)
+ def ma():
+ return mkw_utils.get_moving_angle(0)
+ def pos_():
+ if KartObjectManager.player_count() > 0:
+ return VehiclePhysics.position(0)
+ return None
- if c.mrv_xyz:
- text += f" MRV XZ: {round(mrv.length_xz(),c.digits)}\n"
- text += f" MRV XYZ: {round(mrv.length(),c.digits)}\n\n"
-
- if (c.mwv or c.mwv_xyz):
- mwv = vehicle_physics.moving_water_velocity()
-
- if c.mwv:
- text += f" MWV X: {round(mwv.x,c.digits)}\n"
- text += f" MWV Y: {round(mwv.y,c.digits)}\n"
- text += f" MWV Z: {round(mwv.z,c.digits)}\n\n"
-
- if c.mwv_xyz:
- text += f" MWV XZ: {round(mwv.length_xz(),c.digits)}\n"
- text += f" MWV XYZ: {round(mwv.length(),c.digits)}\n\n"
-
- if c.charges or c.misc:
- kart_settings = KartSettings(addr=kart_object.kart_settings())
-
- if c.charges:
- kart_boost = KartBoost(addr=kart_move.kart_boost())
-
- mt = kart_move.mt_charge()
- smt = kart_move.smt_charge()
- ssmt = kart_move.ssmt_charge()
- mt_boost = kart_move.mt_boost_timer()
- trick_boost = kart_boost.trick_and_zipper_timer()
- shroom_boost = kart_move.mushroom_timer()
- if kart_settings.is_bike():
- text += f"MT Charge: {mt} | SSMT Charge: {ssmt}\n"
- else:
- text += f"MT Charge: {mt} ({smt}) | SSMT Charge: {ssmt}\n"
-
- text += f"MT: {mt_boost} | Trick: {trick_boost} | Mushroom: {shroom_boost}\n\n"
-
- if c.cps:
- lap_comp = race_mgr_player.lap_completion()
- race_comp = race_mgr_player.race_completion()
- cp = race_mgr_player.checkpoint_id()
- kcp = race_mgr_player.max_kcp()
- rp = race_mgr_player.respawn()
- text += f" Lap%: {round(lap_comp,c.digits)}\n"
- text += f"Race%: {round(race_comp,c.digits)}\n"
- text += f"CP: {cp} | KCP: {kcp} | RP: {rp}\n\n"
+ global RaceComp_History
+ RaceComp_History = History({'prc':prc, 'grc':grc}, c.history_size)
- if c.air:
- airtime = kart_move.airtime()
- text += f"Airtime: {airtime}\n\n"
+ global Angle_History
+ Angle_History = History({'facing' : fa, 'moving' : ma}, 2)
- if c.misc or c.surfaces:
- kart_collide = KartCollide(addr=kart_object.kart_collide())
+ global Pos_History
+ Pos_History = History({'pos' : pos_}, 3)
- if c.misc:
- kart_jump = KartJump(addr=kart_move.kart_jump())
- trick_cd = kart_jump.cooldown()
- hwg_timer = kart_state.hwg_timer()
- oob_timer = kart_collide.solid_oob_timer()
- respawn_timer = kart_collide.time_before_respawn()
- offroad_inv = kart_move.offroad_invincibility()
- if kart_move.is_bike:
- text += f"Wheelie Length: {kart_move.wheelie_frames()}\n"
- text += f"Wheelie CD: {kart_move.wheelie_cooldown()} | "
- text += f"Trick CD: {trick_cd}\n"
- text += f"HWG: {hwg_timer} | OOB: {oob_timer}\n"
- text += f"Respawn: {respawn_timer}\n"
- text += f"Offroad: {offroad_inv}\n\n"
+ global maxLap
+ maxLap = None
- if c.surfaces:
- surface_properties = kart_collide.surface_properties()
- is_offroad = (surface_properties.value & SurfaceProperties.OFFROAD) > 0
- is_trickable = (surface_properties.value & SurfaceProperties.TRICKABLE) > 0
- kcl_speed_mod = kart_move.kcl_speed_factor()
- text += f" Offroad: {is_offroad}\n"
- text += f"Trickable: {is_trickable}\n"
- text += f"KCL Speed Modifier: {round(kcl_speed_mod * 100, c.digits)}%\n\n"
+ global text
+ text = ''
- if c.position:
- pos = vehicle_physics.position()
- text += f"X Pos: {pos.x}\n"
- text += f"Y Pos: {pos.y}\n"
- text += f"Z Pos: {pos.z}\n\n"
- # TODO: figure out why classes.RaceInfoPlayer.stick_x() and
- # classes.RaceInfoPlayer.stick_y() do not update
- # (using these as placeholders until further notice)
- if c.stick:
- kart_input = KartInput(addr=race_mgr_player.kart_input())
- current_input_state = RaceInputState(addr=kart_input.current_input_state())
-
- stick_x = current_input_state.raw_stick_x() - 7
- stick_y = current_input_state.raw_stick_y() - 7
- text += f"X: {stick_x} | Y: {stick_y}\n\n"
-
- return text
+if __name__ == '__main__':
+ main()
-@event.on_savestateload
-def on_state_load(fromSlot: bool, slot: int):
- race_mgr = RaceManager()
- if race_mgr.state().value >= RaceState.COUNTDOWN.value:
- gui.draw_text((10, 10), c.color, create_infodisplay())
-
@event.on_frameadvance
def on_frame_advance():
+ global Frame_of_input
+ global Angle_History
+ global RaceComp_History
+ global c
+ global maxLap
+ global text
+
race_mgr = RaceManager()
- if race_mgr.state().value >= RaceState.COUNTDOWN.value:
- gui.draw_text((10, 10), c.color, create_infodisplay())
+ newframe = Frame_of_input != mkw_utils.frame_of_input()
+ draw = mkw_utils.extended_race_state() >= 0
+ if newframe and draw:
+ Frame_of_input = mkw_utils.frame_of_input()
+ Angle_History.update()
+ RaceComp_History.update()
+ Pos_History.update()
+ if maxLap == int(RaceManagerPlayer.race_completion_max(0))-1:
+ mkw_utils.calculate_exact_finish(Pos_History, maxLap)
+ maxLap = int(RaceManagerPlayer.race_completion_max(0))
+
+ if draw:
+ text = create_infodisplay(c, RaceComp_History, Angle_History)
+ else:
+ RaceComp_History.clear()
+ Angle_History.clear()
+ Pos_History.clear()
+ text = ''
+
diff --git a/scripts/RMC/_draw_input_display.py b/scripts/RMC/_draw_input_display.py
deleted file mode 100644
index 513d82f..0000000
--- a/scripts/RMC/_draw_input_display.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from dolphin import gui, event
-from Modules import input_display as display
-
-from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState
-from Modules.mkw_classes import KartInput, RaceInputState, ButtonActions
-
-stick_dict = {-7: 0, -6: 60, -5: 70, -4: 80, -3: 90, -2: 100, -1: 110,
- 0: 128, 1: 155, 2: 165, 3: 175, 4: 185, 5: 195, 6: 200, 7: 255}
-
-
-@event.on_frameadvance
-def on_frame_advance():
- race_mgr = RaceManager()
- if race_mgr.state().value >= RaceState.COUNTDOWN.value:
-
- # TODO: use masks instead of the values for buttons
- race_mgr_player_addr = race_mgr.race_manager_player()
- race_mgr_player = RaceManagerPlayer(addr=race_mgr_player_addr)
- kart_input = KartInput(addr=race_mgr_player.kart_input())
- current_input_state = RaceInputState(addr=kart_input.current_input_state())
- ablr = current_input_state.buttons()
- dpad = current_input_state.trick()
- xstick = current_input_state.raw_stick_x() - 7
- ystick = current_input_state.raw_stick_y() - 7
-
- # A Button
- if ablr.value & ButtonActions.A:
- func = display.fill_pressed_button
- else:
- func = display.create_unpressed_button
- func([330, gui.get_display_size()[1] - 95], 35, 0xFFFFFFFF)
-
- # L Button
- if ablr.value & ButtonActions.L:
- func = display.fill_pressed_bumper
- else:
- func = display.create_unpressed_bumper
- func([30, gui.get_display_size()[1] - 200], 100, 50, 0xFFFFFFFF)
-
- # R Button
- if ablr.value & ButtonActions.B:
- func = display.fill_pressed_bumper
- else:
- func = display.create_unpressed_bumper
- func([280, gui.get_display_size()[1] - 200], 100, 50, 0xFFFFFFFF)
-
- # D-Pad
- # TODO: fix the module so that -35 does not have to be used here
- display.create_dpad(
- [30, gui.get_display_size()[1] - 32.5], 30, -35, 0xFFFFFFFF)
-
- direction = None
- if dpad == 1:
- direction = ["Up"]
- elif dpad == 2:
- direction = ["Down"]
- elif dpad == 3:
- direction = ["Left"]
- elif dpad == 4:
- direction = ["Right"]
-
- if direction:
- display.fill_dpad([30, gui.get_display_size()[1] - 32.5],
- 30, -35, 0xFFFFFFFF, direction)
-
- # Control Stick
- display.create_control_stick([210, gui.get_display_size()[1] - 100], 50, 30, 50,
- stick_dict.get(xstick, 0), stick_dict.get(ystick, 0), 0xFFFFFFFF)
diff --git a/scripts/RMC/alt_info_display.py b/scripts/RMC/alt_info_display.py
new file mode 100644
index 0000000..85ef4dc
--- /dev/null
+++ b/scripts/RMC/alt_info_display.py
@@ -0,0 +1,196 @@
+"""
+EPIK95
+An alternative infodisplay that I made for personal use. The text that gets displayed is created in one large
+formatted string, which makes it very easy to modify or add to the display directly instead of using a config file.
+Setting EXTERNAL_MODE to True will render the display in a separate window (requires Python to be installed).
+Setting GHOST_DISPLAY to True will render a second copy of the display with values for the ghost.
+If "Remove UI Delay" is enabled in Dolphin's graphics settings, set NO_DELAY to True to prevent flickering.
+"""
+from dolphin import event, gui, utils, memory # type: ignore
+import os
+from Modules import mkw_classes as mkw, mkw_utils
+from external import external_utils as ex
+
+EXTERNAL_MODE = False
+NO_DELAY = True
+GHOST_DISPLAY = False
+
+ROUND_DIGITS = 6
+TEXT_COLOR = 0xFFFFFFFF
+
+def round_(x, digits=ROUND_DIGITS):
+ return round(x or 0, digits)
+
+def round_str(x, digits=ROUND_DIGITS):
+ rounded = round_(x, digits)
+ return f"{rounded:.{digits}f}"
+
+def delta(current, previous):
+ if previous is None:
+ return "+?"
+ res = round_(current - previous, ROUND_DIGITS)
+ return ("+" if res > 0 else "") + round_str(res)
+
+
+def create_infodisplay(player_idx: int = 0):
+ # Instantiate classes
+ race_mgr_player = mkw.RaceManagerPlayer(player_idx)
+ race_scenario = mkw.RaceConfigScenario(addr=mkw.RaceConfig.race_scenario())
+
+ if player_idx >= race_scenario.player_count():
+ return "[invalid player index]"
+
+ race_settings = mkw.RaceConfigSettings(race_scenario.settings())
+ kart_object = mkw.KartObject(player_idx)
+ kart_move = mkw.KartMove(addr=kart_object.kart_move())
+ kart_boost = mkw.KartBoost(addr=kart_move.kart_boost())
+ kart_jump = mkw.KartJump(addr=kart_move.kart_jump())
+ kart_state = mkw.KartState(addr=kart_object.kart_state())
+ kart_collide = mkw.KartCollide(addr=kart_object.kart_collide())
+ kart_body = mkw.KartBody(addr=kart_object.kart_body())
+ vehicle_dynamics = mkw.VehicleDynamics(addr=kart_body.vehicle_dynamics())
+ vehicle_physics = mkw.VehiclePhysics(addr=vehicle_dynamics.vehicle_physics())
+
+ pos = vehicle_physics.position()
+ v = mkw_utils.delta_position(player_idx)
+ ev = vehicle_physics.external_velocity()
+ facing_angle = mkw_utils.get_facing_angle(player=0)
+
+ is_bike = mkw.KartSettings.is_bike(player_idx)
+ surface_properties = kart_collide.surface_properties().value
+
+ # Create infodisplay text
+ text = (
+
+f"""
+Frame: {mkw_utils.frame_of_input()}
+
+Position
+ X: {round_str(pos.x)}
+ Y: {round_str(pos.y)}
+ Z: {round_str(pos.z)}
+
+Velocity
+ Engine: {round_str(kart_move.speed())} / {round_str(kart_move.soft_speed_limit(), 2)}
+ XYZ: {round_str(v.length())}
+ XZ: {round_str(v.length_xz())}
+ Y: {round_str(v.y)}
+
+External Velocity
+ XYZ: {round_str(ev.length())} ({delta(ev.length(), prev_values[player_idx].get("ev_xyz"))})
+ XZ: {round_str(ev.length_xz())} ({delta(ev.length_xz(), prev_values[player_idx].get("ev_xz"))})
+ Y: {round_str(ev.y)} ({delta(ev.y, prev_values[player_idx].get("ev_y"))})
+
+Lean Rotation
+ Angle: {is_bike and round_(kart_move.lean_rot())}
+ Rate: {is_bike and round_(kart_move.lean_rot_increase())}
+ Cap: {is_bike and round_(kart_move.lean_rot_cap())}
+
+Boosts
+ MT: {kart_move.mt_charge()} / 270 -> {kart_move.mt_boost_timer()}
+ SSMT: {kart_move.ssmt_charge()} / 75 -> {kart_boost.all_mt_timer() - kart_move.mt_boost_timer()}
+ Mushroom: {kart_move.mushroom_timer()} | Trick: {kart_boost.trick_and_zipper_timer()}
+ Auto Drift: {kart_move.auto_drift_start_frame_counter()} / 12
+
+Checkpoints
+ Lap%: {round_str(race_mgr_player.lap_completion())}
+ Race%: {round_str(race_mgr_player.race_completion())}
+ CP: {race_mgr_player.checkpoint_id()} | KCP: {race_mgr_player.max_kcp()} | RP: {race_mgr_player.respawn()}
+
+Airtime: {kart_move.airtime()}
+Wallhug: {(surface_properties & mkw.SurfaceProperties.WALL) > 0}
+Barrel Roll: {kart_state.bitfield(field_idx=3) & 0x10 > 0}
+
+Surface properties: {surface_properties}
+""")
+
+ #? Other infodisplay sections that can be copy/pasted in if needed
+ _unused = (
+
+"""
+HWG Timer: {kart_state.hwg_timer()}
+Glitchy corner: {round_(kart_collide.glitchy_corner())}
+
+Cooldowns
+ Wheelie: {kart_move.wheelie_cooldown()} | Trick: {kart_jump.cooldown()}
+
+Offroad: {(surface_properties & mkw.SurfaceProperties.OFFROAD) > 0}
+OOB Timer: {kart_collide.solid_oob_timer()}
+
+ Forward: {round_str(v.forward(facing_angle.yaw))}
+ Sideways: {round_str(v.sideway(facing_angle.yaw))}
+
+Boost Panel: {kart_boost.mushroom_and_boost_panel_timer() - kart_move.mushroom_timer()}
+
+Airtime: {(kart_move.airtime() + 1) if (surface_properties & 0x1000) == 0 else 0}
+""")
+
+ # Store any values that will be needed on the next frame
+ prev_values[player_idx].update({
+ "ev_xyz": ev.length(),
+ "ev_xz": ev.length_xz(),
+ "ev_y": ev.y,
+ })
+
+ return text.strip()
+
+
+def update_infodisplay():
+ global text_0, text_1
+
+ if EXTERNAL_MODE:
+ shm_writer.write_text(create_infodisplay())
+ else:
+ text_0 = create_infodisplay(player_idx=0)
+ if GHOST_DISPLAY:
+ text_1 = create_infodisplay(player_idx=1)
+
+
+@event.on_framepresent
+def on_present():
+ if not EXTERNAL_MODE:
+ gui.draw_text((10, 10), TEXT_COLOR, text_0)
+ if GHOST_DISPLAY:
+ gui.draw_text((260, 10), TEXT_COLOR, text_1)
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ global current_frame
+ if current_frame != (mkw_utils.frame_of_input() - 1):
+ for p in prev_values:
+ p.clear()
+ current_frame = mkw_utils.frame_of_input()
+
+ if mkw_utils.extended_race_state() >= 0:
+ update_infodisplay()
+
+
+@event.on_savestateload
+def on_state_load(fromSlot: bool, slot: int):
+ if memory.is_memory_accessible() and mkw_utils.extended_race_state() >= 0:
+ update_infodisplay()
+
+def main():
+ global current_frame
+ current_frame = 0
+
+ global prev_values
+ prev_values = [ {}, {} ]
+
+ global text_0 #text for player id 0
+ text_0 = ''
+
+ global text_1 #text for player id 1
+ text_1 = ''
+
+ if EXTERNAL_MODE:
+ global shm_writer
+ shm_writer = ex.SharedMemoryWriter(name='infodisplay', buffer_size=4096)
+
+ window_script_path = os.path.join(utils.get_script_dir(), "external", "info_display_window.py")
+ ex.start_external_script(window_script_path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/RMC/draw_Speed_Display.py b/scripts/RMC/draw_Speed_Display.py
new file mode 100644
index 0000000..4971475
--- /dev/null
+++ b/scripts/RMC/draw_Speed_Display.py
@@ -0,0 +1,35 @@
+from dolphin import gui, event
+from Modules import input_display as display
+import math
+
+from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState
+from Modules.mkw_classes import VehiclePhysics
+from Modules import mkw_utils as mkw_utils
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ race_mgr = RaceManager()
+ if race_mgr.state().value >= RaceState.COUNTDOWN.value:
+
+ # TODO: use masks instead of the values for buttons
+ race_mgr_player_addr = race_mgr.race_manager_player()
+ race_mgr_player = RaceManagerPlayer(addr=race_mgr_player_addr)
+
+ yaw = mkw_utils.get_facing_angle(0).yaw + 90
+ IV = VehiclePhysics.internal_velocity(0)
+ EV = VehiclePhysics.external_velocity(0)
+
+ size = gui.get_display_size()
+ center = (128, size[1]-128)
+
+
+ # Yaw
+ gui.draw_line(center, (center[0]+math.cos(yaw/180*math.pi)*30, center[1]+math.sin(yaw/180*math.pi)*30), 0xAAFF0000, 2)
+
+ # IV
+ gui.draw_line(center, (center[0]+IV.x, center[1]+IV.z), 0xAA00FF00, 1)
+
+ # EV
+ gui.draw_line(center, (center[0]+EV.x, center[1]+EV.z), 0xAA0000FF, 1)
+
diff --git a/scripts/RMC/draw_input_display.py b/scripts/RMC/draw_input_display.py
new file mode 100644
index 0000000..1cb7624
--- /dev/null
+++ b/scripts/RMC/draw_input_display.py
@@ -0,0 +1,99 @@
+from dolphin import gui, event, memory # type: ignore
+from Modules import input_display as display
+
+from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState
+from Modules.mkw_classes import KartInput, RaceInputState, ButtonActions
+
+stick_dict = {-7: 0, -6: 60, -5: 70, -4: 80, -3: 90, -2: 100, -1: 110,
+ 0: 128, 1: 155, 2: 165, 3: 175, 4: 185, 5: 195, 6: 200, 7: 255}
+
+LEFT_OFFSET = 250
+
+def update_input():
+ global is_init
+ race_mgr = RaceManager()
+ if race_mgr.state().value >= RaceState.COUNTDOWN.value:
+ global ablr
+ global dpad
+ global xstick
+ global ystick
+ race_mgr_player_addr = race_mgr.race_manager_player()
+ race_mgr_player = RaceManagerPlayer(addr=race_mgr_player_addr)
+ kart_input = KartInput(addr=race_mgr_player.kart_input())
+ current_input_state = RaceInputState(addr=kart_input.current_input_state())
+ ablr = current_input_state.buttons()
+ dpad = current_input_state.trick()
+ xstick = current_input_state.raw_stick_x() - 7
+ ystick = current_input_state.raw_stick_y() - 7
+ is_init = True
+
+def main():
+ global is_init
+ is_init = False
+ update_input()
+
+def draw():
+
+ # A Button
+ if ablr.value & ButtonActions.A:
+ func = display.fill_pressed_button
+ else:
+ func = display.create_unpressed_button
+ func([LEFT_OFFSET + 330, gui.get_display_size()[1] - 95], 35, 0xFFFFFFFF)
+
+ # L Button
+ if ablr.value & ButtonActions.L:
+ func = display.fill_pressed_bumper
+ else:
+ func = display.create_unpressed_bumper
+ func([LEFT_OFFSET + 30, gui.get_display_size()[1] - 200], 100, 50, 0xFFFFFFFF)
+
+ # R Button
+ if ablr.value & ButtonActions.B:
+ func = display.fill_pressed_bumper
+ else:
+ func = display.create_unpressed_bumper
+ func([LEFT_OFFSET + 280, gui.get_display_size()[1] - 200], 100, 50, 0xFFFFFFFF)
+
+ # D-Pad
+ # TODO: fix the module so that -35 does not have to be used here
+ display.create_dpad(
+ [LEFT_OFFSET + 30, gui.get_display_size()[1] - 32.5], 30, -35, 0xFFFFFFFF)
+
+ direction = None
+ if dpad == 1:
+ direction = ["Up"]
+ elif dpad == 2:
+ direction = ["Down"]
+ elif dpad == 3:
+ direction = ["Left"]
+ elif dpad == 4:
+ direction = ["Right"]
+
+ if direction:
+ display.fill_dpad([LEFT_OFFSET + 30, gui.get_display_size()[1] - 32.5],
+ 30, -35, 0xFFFFFFFF, direction)
+
+ # Control Stick
+ display.create_control_stick([LEFT_OFFSET + 210, gui.get_display_size()[1] - 100], 50, 30, 50,
+ stick_dict.get(xstick, 0), stick_dict.get(ystick, 0), 0xFFFFFFFF)
+
+ gui.draw_text((LEFT_OFFSET + 195, gui.get_display_size()[1] - 40), 0xFFFFFFFF, f"{xstick}, {ystick}")
+
+
+@event.on_frameadvance
+def on_frame_advance():
+ update_input()
+
+
+@event.on_savestateload
+def on_state_load(fromSlot: bool, slot: int):
+ if memory.is_memory_accessible():
+ update_input()
+
+@event.on_framepresent
+def on_present():
+ if is_init:
+ draw()
+
+main()
diff --git a/scripts/RMC/framedump.py b/scripts/RMC/framedump.py
new file mode 100644
index 0000000..cfefd7a
--- /dev/null
+++ b/scripts/RMC/framedump.py
@@ -0,0 +1,122 @@
+from dolphin import event, savestate, utils
+from Modules import mkw_utils as mkw_utils
+from Modules.agc_lib import AGCMetaData
+from Modules.framesequence import Frame
+from external import external_utils as ex
+from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, KartObjectManager, VehiclePhysics, vec3
+
+from pathlib import Path
+import os
+import datetime
+import time
+
+def main():
+
+ global folder_path
+ folder_path = os.path.join(utils.get_script_dir(), 'FrameDumps')
+ #Create folders if needed
+ Path(os.path.join(folder_path, 'RAM_data', 'nul.nul')).parent.mkdir(exist_ok=True, parents=True)
+ for filename in os.listdir(os.path.join(folder_path, 'RAM_data')):
+ os.remove(os.path.join(folder_path, 'RAM_data', filename))
+
+ #Create a list of every file in dumps
+ frame_dump_path = os.path.join(utils.get_dump_dir(), 'Frames')
+ audio_dump_path = os.path.join(utils.get_dump_dir(), 'Audio')
+
+ global ignore_file_list
+ ignore_file_list = []
+ for filename in os.listdir(frame_dump_path):
+ ignore_file_list.append(os.path.join(frame_dump_path, filename)+'\n')
+ for filename in os.listdir(audio_dump_path):
+ ignore_file_list.append(os.path.join(audio_dump_path, filename)+'\n')
+
+ if not (utils.is_framedumping() or utils.is_audiodumping()):
+ utils.start_framedump()
+ utils.start_audiodump()
+ else:
+ print("Don't start this script when already dumping")
+ utils.cancel_script(utils.get_script_name())
+
+ global frame_counter
+ frame_counter = 0 #Count the frame of dumps, different from frame of input
+
+ global framedump_prefix
+ framedump_prefix = None
+
+ global state_counter, state
+ state_counter = 0
+ state = 0
+
+@event.on_scriptend
+def scriptend(id_):
+
+ print(framedump_prefix)
+ if utils.get_script_id() == id_:
+ if (utils.is_framedumping() and utils.is_audiodumping()):
+ utils.stop_framedump()
+ utils.stop_audiodump()
+ if utils.is_paused():
+ utils.toggle_play()
+ ex_script_path = os.path.join(folder_path, 'encoder.py')
+ ex_info_path = os.path.join(folder_path, 'dump_info.txt')
+ with open(ex_info_path, 'w') as f:
+ f.writelines([utils.get_dump_dir()+'\n'] + ignore_file_list)
+ ex.start_external_script(ex_script_path, False, False)
+
+def frame_text():
+ text = ''
+ text += f'frame_of_input:{mkw_utils.frame_of_input()}\n'
+ text += f'yaw:{mkw_utils.get_facing_angle(0).yaw}\n'
+ text += f'spd_x:{mkw_utils.delta_position(0).x}\n'
+ text += f'spd_y:{mkw_utils.delta_position(0).y}\n'
+ text += f'spd_z:{mkw_utils.delta_position(0).z}\n'
+ text += f'iv_x:{VehiclePhysics.internal_velocity(0).x}\n'
+ text += f'iv_y:{VehiclePhysics.internal_velocity(0).y}\n'
+ text += f'iv_z:{VehiclePhysics.internal_velocity(0).z}\n'
+ text += f'ev_x:{VehiclePhysics.external_velocity(0).x}\n'
+ text += f'ev_y:{VehiclePhysics.external_velocity(0).y}\n'
+ text += f'ev_z:{VehiclePhysics.external_velocity(0).z}\n'
+ text += f'mvr_x:{VehiclePhysics.moving_road_velocity(0).x}\n'
+ text += f'mvr_y:{VehiclePhysics.moving_road_velocity(0).y}\n'
+ text += f'mvr_z:{VehiclePhysics.moving_road_velocity(0).z}\n'
+ text += f'mvw_x:{VehiclePhysics.moving_water_velocity(0).x}\n'
+ text += f'mvw_y:{VehiclePhysics.moving_water_velocity(0).y}\n'
+ text += f'mvw_z:{VehiclePhysics.moving_water_velocity(0).z}\n'
+ text += f'cp:{RaceManagerPlayer(0).checkpoint_id()}\n'
+ text += f'kcp:{RaceManagerPlayer(0).max_kcp()}\n'
+ text += f'rp:{RaceManagerPlayer(0).respawn()}\n'
+ text += f'racecomp:{RaceManagerPlayer(0).race_completion()}\n'
+ text += f'lapcomp:{RaceManagerPlayer(0).lap_completion()}\n'
+ text += f'input:{Frame.from_current_frame(0)}\n'
+ text += f'state:{state}\n'
+ text += f'state_counter:{state_counter}\n'
+ return text
+
+@event.on_frameadvance
+def on_frame_advance():
+ global frame_counter
+
+ global framedump_prefix
+
+ global state_counter, state
+
+ if state == mkw_utils.extended_race_state():
+ state_counter += 1
+ else:
+ state = mkw_utils.extended_race_state()
+ state_counter = 0
+
+ if frame_counter == 0:
+ c = datetime.datetime.now()
+ framedump_prefix = f"{utils.get_game_id()}_{c.year}-{c.month:02d}-{c.day:02d}_{c.hour:02d}-{c.minute:02d}-{c.second:02d}"
+ if mkw_utils.extended_race_state() in [1,2,3,4]:
+ with open(os.path.join(folder_path, 'RAM_data', f'{frame_counter}.txt'), 'w') as f:
+ f.write(frame_text())
+ else:
+ with open(os.path.join(folder_path, 'RAM_data', f'{frame_counter}.txt'), 'w') as f:
+ f.write('')
+
+ frame_counter += 1
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/Settings/Setting files goes here.txt b/scripts/Settings/Setting files goes here.txt
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/Startslides/flame_left.csv b/scripts/Startslides/flame_left.csv
new file mode 100644
index 0000000..4381d3c
--- /dev/null
+++ b/scripts/Startslides/flame_left.csv
@@ -0,0 +1,395 @@
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,1,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,5,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,7,0,1,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
diff --git a/scripts/Startslides/mach_left.csv b/scripts/Startslides/mach_left.csv
new file mode 100644
index 0000000..87eba05
--- /dev/null
+++ b/scripts/Startslides/mach_left.csv
@@ -0,0 +1,401 @@
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+0,0,0,0,0,1,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-2,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,-3,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,7,0,1,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
diff --git a/scripts/Startslides/spear_left.csv b/scripts/Startslides/spear_left.csv
new file mode 100644
index 0000000..350adf3
--- /dev/null
+++ b/scripts/Startslides/spear_left.csv
@@ -0,0 +1,461 @@
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+0,0,0,0,0,1,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,1,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
diff --git a/scripts/Startslides/wario_left.csv b/scripts/Startslides/wario_left.csv
new file mode 100644
index 0000000..34ca424
--- /dev/null
+++ b/scripts/Startslides/wario_left.csv
@@ -0,0 +1,492 @@
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,1,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,1,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
diff --git a/scripts/Startslides/wiggle_left.csv b/scripts/Startslides/wiggle_left.csv
new file mode 100644
index 0000000..390fd37
--- /dev/null
+++ b/scripts/Startslides/wiggle_left.csv
@@ -0,0 +1,407 @@
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+0,0,0,0,0,0,-1
+0,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+0,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,1,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,-7,0,0,-1
+1,0,0,3,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,1,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,7,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
+1,0,0,0,0,0,-1
diff --git a/scripts/Update_Packages_+_Scripts.bat b/scripts/Update_Packages_+_Scripts.bat
new file mode 100644
index 0000000..ee594c8
--- /dev/null
+++ b/scripts/Update_Packages_+_Scripts.bat
@@ -0,0 +1,4 @@
+python -m pip install --upgrade pip
+pip install requests
+python update_scripts.py
+./Install_Pycore_Packages.bat
\ No newline at end of file
diff --git a/scripts/external/TAS_input_window.py b/scripts/external/TAS_input_window.py
new file mode 100644
index 0000000..57319df
--- /dev/null
+++ b/scripts/external/TAS_input_window.py
@@ -0,0 +1,101 @@
+from tkinter import *
+from tkinter import ttk
+from math import floor
+import external_utils as ex
+
+input_writer = ex.SharedMemoryWriter('mkw tas input', 30, False)
+
+def convert_drift(t):
+ if t == 'Auto':
+ return '-1'
+ else:
+ return t
+def convert_trick(t):
+ if t == 'Up':
+ return '1'
+ if t == 'Down':
+ return '2'
+ if t == 'Left':
+ return '3'
+ if t == 'Right':
+ return '4'
+ return '0'
+
+def write_state(_ = None):
+ a = ','.join([str(int(Avar.get())),
+ str(int(Bvar.get())),
+ str(int(Lvar.get())),
+ convert_drift(drift_button.get()),
+ str(int(BDvar.get())),
+ str(floor(float(x_stick.get()))),
+ str(floor(float(y_stick.get()))),
+ convert_trick(trick_button.get())])
+ input_writer.write_text(a)
+
+root = Tk()
+root.title('TAS Input')
+root.wm_attributes('-topmost', 1) #Always on top
+
+content = ttk.Frame(root, padding = 10)
+
+Avar = BooleanVar(value=False)
+Bvar = BooleanVar(value=False)
+Lvar = BooleanVar(value=False)
+Xvar = StringVar()
+Yvar = StringVar()
+Tvar = StringVar()
+Dvar = StringVar()
+BDvar = BooleanVar(value=False)
+
+a_button = ttk.Checkbutton(content, text="A", variable=Avar, onvalue=True, command=write_state)
+b_button = ttk.Checkbutton(content, text="B", variable=Bvar, onvalue=True, command=write_state)
+l_button = ttk.Checkbutton(content, text="ITEM", variable=Lvar, onvalue=True, command=write_state)
+bd_button = ttk.Checkbutton(content, text="BRAKEDRIFT", variable=BDvar, onvalue=True, command=write_state)
+
+x_label = ttk.Label(content, text='X = 0')
+def update_x_label(x):
+ write_state()
+ x_label['text'] = 'X = '+str(floor(float(x)))
+x_stick = ttk.Scale(content, orient=HORIZONTAL, length=150, from_=-6.5, to=7.5, variable=Xvar, command=update_x_label)
+
+
+y_label = ttk.Label(content, text='Y = 0')
+def update_y_label(x):
+ write_state()
+ y_label['text'] = 'Y = '+str(floor(float(x)))
+y_stick = ttk.Scale(content, orient=HORIZONTAL, length=150, from_=-6.5, to=7.5, variable=Yvar, command=update_y_label)
+
+
+trick_button = ttk.Combobox(content, textvariable=Tvar, values = ('None', 'Up', 'Left', 'Right', 'Down'), state = 'readonly')
+trick_button.bind('<>', write_state)
+trick_label = ttk.Label(content, text='Trick :')
+
+
+drift_button = ttk.Combobox(content, textvariable=Dvar, values = ('Auto', '0', '1'), state = 'readonly')
+drift_button.bind('<>', write_state)
+drift_label = ttk.Label(content, text='Drift :')
+drift_disclaimer = ttk.Label(content, text='Use Auto if unsure')
+
+drift_button.set('Auto')
+trick_button.set('None')
+x_stick.set(0.5)
+y_stick.set(0.5)
+
+content.grid(column=0, row=0)
+a_button.grid(column=0, row=3, sticky='w')
+b_button.grid(column=0, row=4, sticky='w')
+l_button.grid(column=0, row=5, sticky='w')
+x_stick.grid(column=1, row=6, columnspan=2)
+x_label.grid(column=0, row=6, sticky='w')
+y_label.grid(column=0, row=10, sticky='w')
+y_stick.grid(column=1, row=10, columnspan=2)
+trick_button.grid(column=1, row=11, sticky='w')
+trick_label.grid(column=0, row=11, sticky='w')
+drift_button.grid(column=1, row=12, sticky='w')
+drift_label.grid(column=0, row=12, sticky='w')
+drift_disclaimer.grid(column=2, row=12, sticky='e')
+bd_button.grid(column=0, row=13, sticky='w')
+
+root.mainloop()
+
+input_writer.close()
diff --git a/scripts/external/TTK_Save_GUI_window.py b/scripts/external/TTK_Save_GUI_window.py
new file mode 100644
index 0000000..ff1e06a
--- /dev/null
+++ b/scripts/external/TTK_Save_GUI_window.py
@@ -0,0 +1,135 @@
+from tkinter import *
+from tkinter import ttk, filedialog
+from math import floor
+import external_utils as ex
+from random import randint
+from datetime import datetime
+
+
+global save_button_row
+global misscount
+save_button_row = 5
+misscount = 0
+
+af_text = ['How did you get there ?', 'what.', 'Huh WTF ?', 'Keep trying', 'Almost!', 'Bad QM', 'Not this time', 'Cmon already', "It's just 20%", 'You can do it!', "You can't do it!", 'Just a little more', 'Is it really that hard to click on a button ?', 'Even my grandma can do it', 'How many messages are there ?', 'I wonder if there is something special at the end...', "(Spoiler : there isn't", ")*", "forgot the bracket, oops", 'Unless there is actually something ...', "It's starting to get very long", "You had about 1.5% chance to get that far", "There is no end btw, you just need luck", "You should get it in average every 6 tries", "Okay I'll tell you something", "Soon", "In like just a few messages", "It's a cool hidden feature", "Nothing incredible, but still cool to know", "Fact 1 : press S on the connect4 ending screen to access settings", "Fact 2 : You can play whenever you want the connect4 game !", 'Fact 2 (part 2) : Just launch "special.py" in Scripts/external', 'This is the end of the messages', 'Good luck if you are still trying to save your TTK inputs lol']
+
+def write_state(_ = None):
+ if source_combobox.get():
+ std_text = source_combobox.get()
+ if std_text == 'File':
+ std_text = source_file_text.get(1.0, END).split('\n')[0]
+ if csv_player_var.get():
+ std_text += '|' + 'csv_player'
+ if csv_ghost_var.get():
+ std_text += '|' + 'csv_ghost'
+ if dest_file_var.get():
+ std_text += '|' + dest_file_text.get(1.0, END).split('\n')[0]
+ print(std_text, end = '')
+ root.destroy()
+ else:
+ status_label['text'] = 'Select a source to save'
+def tp(_=None):
+ global save_button_row
+ global misscount
+ d = datetime.now()
+ a = randint(0,5)
+ if a == save_button_row or d.day != 1 or d.month != 4:
+ misscount = 0
+ write_state()
+ else:
+ save_button_row = a
+ misscount += 1
+ if misscount < len(af_text):
+ status_label['text'] = af_text[misscount]
+ save_button.grid(column=1, row=a)
+
+
+
+root = Tk()
+root.title('TTK Save')
+root.wm_attributes('-topmost', 1) #Always on top
+
+content = ttk.Frame(root, padding = 10)
+
+dialog_frame = ttk.Frame(content, padding = 5)
+dialog_frame.grid(column = 0, row = 0, rowspan = 6)
+
+
+
+csv_pick_var = StringVar()
+rkg_pick_var = StringVar()
+
+
+save_label = ttk.Label(dialog_frame, text = 'Save ')
+save_label.grid(column = 0, row = 0, rowspan = 4)
+
+
+source_var = StringVar()
+source_file_text = Text(dialog_frame, height = 4, width = 20, state = 'disabled')
+source_file_text.grid(column=1, row=5, sticky='w')
+source_combobox = ttk.Combobox(dialog_frame, textvariable=source_var, values = ('Player Inputs', 'Ghost Inputs', 'CSV Player', 'CSV Ghost', 'File'), state = 'readonly')
+def source_ask_open_file(_ = None):
+ if source_combobox.get() == 'File':
+ filename = filedialog.askopenfilename(parent = dialog_frame,
+ title = 'Open an input file',
+ filetypes = [('RKG Files', '*.rkg'), ('CSV Files', '*.csv'), ('All Files', '*')])
+ if filename == '':
+ source_combobox.set('Player Inputs')
+ else:
+ source_file_text['state'] = 'normal'
+ source_file_text.delete(1.0, END)
+ source_file_text.insert(END, filename)
+ source_file_text['state'] = 'disabled'
+ else:
+ source_file_text['state'] = 'normal'
+ source_file_text.delete(1.0, END)
+ source_file_text['state'] = 'disabled'
+
+source_combobox.bind('<>', source_ask_open_file)
+source_combobox.grid(column=1, row=0, rowspan = 4)
+
+to_label = ttk.Label(dialog_frame, text = ' to ')
+to_label.grid(column = 2, row = 0, rowspan = 4)
+
+csv_player_var = BooleanVar(value=False)
+csv_player_button = ttk.Checkbutton(dialog_frame, text="CSV Player", variable=csv_player_var, onvalue=True)
+csv_player_button.grid(column=4, row=0, sticky='w')
+
+csv_ghost_var = BooleanVar(value=False)
+csv_ghost_button = ttk.Checkbutton(dialog_frame, text="CSV Ghost", variable=csv_ghost_var, onvalue=True)
+csv_ghost_button.grid(column=4, row=1, sticky='w')
+
+dest_file_var = BooleanVar(value=False)
+dest_file_text = Text(dialog_frame, height = 4, width = 20, state = 'disabled')
+dest_file_text.grid(column=3, row=5, columnspan=2)
+def dest_ask_save_file(_=None):
+ if dest_file_var.get():
+ filename = filedialog.asksaveasfilename(parent = dialog_frame, defaultextension = '.rkg',
+ title = 'Save to an input file',
+ filetypes = [('RKG Files', '*.rkg'), ('CSV Files', '*.csv'), ('All Files', '*')])
+ if filename == '':
+ dest_file_var.set(False)
+ else:
+ dest_file_text['state'] = 'normal'
+ dest_file_text.delete(1.0, END)
+ dest_file_text.insert(END, filename)
+ dest_file_text['state'] = 'disabled'
+ else:
+ dest_file_text['state'] = 'normal'
+ dest_file_text.delete(1.0, END)
+ dest_file_text['state'] = 'disabled'
+
+dest_file_button = ttk.Checkbutton(dialog_frame, text="File", variable=dest_file_var, onvalue=True, command=dest_ask_save_file)
+dest_file_button.grid(column=4, row=2, sticky='w')
+
+save_button = ttk.Button(content, text='Save', command=tp)
+save_button.grid(column=2, row=save_button_row )
+
+status_label = ttk.Label(content, text='')
+status_label.grid(column=0, row=7 )
+
+
+content.grid(column=0, row=0)
+
+root.mainloop()
+
diff --git a/scripts/external/external_utils.py b/scripts/external/external_utils.py
new file mode 100644
index 0000000..a957e00
--- /dev/null
+++ b/scripts/external/external_utils.py
@@ -0,0 +1,188 @@
+from multiprocessing import shared_memory
+import atexit
+import subprocess
+import os
+import configparser
+import platform
+
+
+def start_external_script(path: str, force_exit = True, create_no_window = True, *args):
+ ''' Start an external script using the user's own python env
+ path (str) : path to the filename of the script to start
+ force_exit (bool) : Can force the external script to stop when the calling script ends.
+ create_no_window (bool) : Can prevent the external script from creating a new window.
+ *agrs : extra args to give to the external script. (access with sys.argv)'''
+ if create_no_window:
+ process = subprocess.Popen(["python", path, *args], creationflags=subprocess.CREATE_NO_WINDOW)
+ else:
+ process = subprocess.Popen(["python", path, *args])
+ if force_exit:
+ atexit.register(process.terminate)
+
+
+def run_external_script(path: str, *args):
+ output = subprocess.check_output(["python", path, *args], text=True, creationflags=subprocess.CREATE_NO_WINDOW)
+ return output
+
+#Open a file with the default application
+def open_file(path: str):
+ if platform.system() == 'Darwin': # macOS
+ subprocess.call(('open', path))
+ elif platform.system() == 'Windows': # Windows
+ os.startfile(path, 'edit')
+ else: # linux variants
+ subprocess.call(('xdg-open', path))
+
+class SharedMemoryWriter:
+ def __init__(self, name: str, buffer_size: int, create=True):
+ self._shm = shared_memory.SharedMemory(create=create, name=name, size=buffer_size)
+ atexit.register(self.close)
+
+ def write(self, bytes_: bytes):
+ if len(bytes_) > len(self._shm.buf):
+ raise ValueError("Data is too large for shared memory buffer.")
+ self._shm.buf[:len(self._shm.buf)] = bytes_ + b'\x00' * (len(self._shm.buf) - len(bytes_))
+
+ def write_text(self, text: str):
+ bytes_ = text.encode('utf-8')
+ self.write(bytes_)
+
+ def close(self):
+ self._shm.close()
+ self._shm.unlink()
+
+
+class SharedMemoryReader:
+ def __init__(self, name: str):
+ self._shm = shared_memory.SharedMemory(name=name)
+ atexit.register(self.close)
+
+ def read(self):
+ return bytes(self._shm.buf)
+
+ def read_text(self):
+ return self.read().rstrip(b'\x00').decode('utf-8')
+
+ def close(self):
+ self._shm.close()
+
+ def close_with_writer(self):
+ self._shm.close()
+ self._shm.unlink()
+
+
+class SharedMemoryBlock:
+ """ Allows both reading and writing to a shared memory block """
+ def __init__(self, _shm: shared_memory.SharedMemory):
+ self._shm = _shm
+
+ @staticmethod
+ def create(name: str, buffer_size: int):
+ """ Create new shared memory block """
+ shm = SharedMemoryBlock(shared_memory.SharedMemory(create=True, name=name, size=buffer_size))
+ atexit.register(shm.destroy)
+ return shm
+
+ @staticmethod
+ def connect(name: str):
+ """ Connect to existing shared memory block """
+ shm = SharedMemoryBlock(shared_memory.SharedMemory(name=name))
+ atexit.register(shm.disconnect)
+ return shm
+
+ def clear(self):
+ self._shm.buf[:len(self._shm.buf)] = b'\x00' * len(self._shm.buf)
+
+ def read(self):
+ return bytes(self._shm.buf)
+
+ def read_text(self):
+ return self.read().rstrip(b'\x00').decode('utf-8')
+
+ def write(self, bytes: bytes):
+ if len(bytes) > len(self._shm.buf):
+ raise ValueError("Data is too large for shared memory buffer.")
+ self.clear()
+ self._shm.buf[:len(bytes)] = bytes
+
+ def write_text(self, text: str):
+ bytes = text.encode('utf-8')
+ self.write(bytes)
+
+ def disconnect(self):
+ self._shm.close()
+
+ def destroy(self):
+ self.disconnect()
+ self._shm.unlink()
+
+
+def open_dialog_box(scriptDir, filetypes = [('All files', '*')], initialdir = '', title = '', multiple = False):
+ ''' Shortcut function to prompt a openfile dialog box
+ type_list : list of types allowed for the open dialog box
+ format : ('{File dormat description}', '*.{file extension}')
+ exemple : ('RKG files', '*.rkg') '''
+
+ #Todo : maybe having the text as a subprocess check_output argument instead of SharedMemory, but idk how to implement it
+ script_path = os.path.join(scriptDir, "external", 'open_file_dialog_box.py')
+ type_writer = SharedMemoryWriter('open_file_dialog', 1024)
+ str_list = []
+ for type_ in filetypes:
+ str_list.append(','.join(type_))
+ type_writer.write_text(';'.join(str_list) + '|' + initialdir + '|' + title + '|' + str(multiple))
+ filename = subprocess.check_output(["python", script_path], text=True, creationflags=subprocess.CREATE_NO_WINDOW)
+ type_writer.close()
+ return filename
+
+def save_dialog_box(scriptDir, filetypes = [('All files', '*')], initialdir = '', title = '', defaultextension = ''):
+ ''' Shortcut function to prompt a savefile dialog box
+ type_list : list of types allowed for the open dialog box
+ format : ('{File dormat description}', '*.{file extension}')
+ exemple : ('RKG files', '*.rkg') '''
+
+ #Todo : maybe having the text as a subprocess check_output argument instead of SharedMemory, but idk how to implement it
+ script_path = os.path.join(scriptDir, "external", 'save_file_dialog_box.py')
+ type_writer = SharedMemoryWriter('save_file_dialog', 1024)
+ str_list = []
+ for type_ in filetypes:
+ str_list.append(','.join(type_))
+ type_writer.write_text(';'.join(str_list) + '|' + initialdir + '|' + title + '|' + defaultextension)
+ filename = subprocess.check_output(["python", script_path], text=True, creationflags=subprocess.CREATE_NO_WINDOW)
+ type_writer.close()
+ return filename
+
+#The 2 following functions are used to save and restore
+#various tkinter settings
+def save_external_setting(config_file, setting_name, setting_value):
+ ''' Param : config_file : str, filename of the savefile
+ setting_name : str, unique name of the setting (used as a key in the config file)
+ setting_value : str, value of the setting'''
+
+ #create an empty file if it doesn't exist
+ if not os.path.exists(config_file):
+ with open(config_file, 'w') as f:
+ pass
+
+ config = configparser.ConfigParser()
+ config.read(config_file)
+ if not config.sections():
+ config["SETTINGS"] = {}
+ config["SETTINGS"][setting_name] = setting_value
+
+ with open(config_file, 'w') as f:
+ config.write(f)
+
+def load_external_setting(config_file, setting_name):
+ ''' Param : config_file : str, filename of the savefile
+ setting_name : str, unique name of the setting (used as a key in the config file)'''
+
+ if not os.path.exists(config_file):
+ return None
+ config = configparser.ConfigParser()
+ config.read(config_file)
+ if not config.sections():
+ return None
+ if config["SETTINGS"].get(setting_name):
+ return config["SETTINGS"].get(setting_name)
+ return None
+
diff --git a/scripts/external/info_display_window.py b/scripts/external/info_display_window.py
new file mode 100644
index 0000000..a7367d6
--- /dev/null
+++ b/scripts/external/info_display_window.py
@@ -0,0 +1,54 @@
+import tkinter
+import external_utils
+import time
+
+# Fix blurry text
+from ctypes import windll
+windll.shcore.SetProcessDpiAwareness(1)
+
+
+def main():
+ try:
+ shm_reader = external_utils.SharedMemoryReader(name='infodisplay')
+ print("Connected to shared data")
+ except FileNotFoundError:
+ raise FileNotFoundError("Shared memory buffer not found. Make sure the `external_info_display` script is enabled.")
+
+ window = tkinter.Tk()
+ window.title('Infodisplay')
+ window.config(bg="black")
+ window.geometry('330x600')
+
+ display_text = tkinter.StringVar()
+
+ label = tkinter.Label(
+ textvariable=display_text,
+ anchor='nw',
+ font=('Courier', '9'),
+ justify='left',
+ width=250,
+ fg='white',
+ bg='black',
+ )
+ label.pack(padx=5, pady=5)
+
+ try:
+ while True:
+ new_text = shm_reader.read_text()
+ if new_text and new_text != display_text.get():
+ display_text.set(new_text)
+
+ window.update_idletasks()
+ window.update()
+ time.sleep(0.01)
+
+ except KeyboardInterrupt:
+ print("Reader stopped.")
+
+ finally:
+ shm_reader.close()
+ print("Disconnected from shared data")
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/external/live_graphing_window.py b/scripts/external/live_graphing_window.py
new file mode 100644
index 0000000..13140da
--- /dev/null
+++ b/scripts/external/live_graphing_window.py
@@ -0,0 +1,106 @@
+import matplotlib
+matplotlib.use('TkAgg') # Use TkAgg backend for compatibility
+
+import matplotlib.pyplot as plt
+import struct
+import external_utils
+
+# Connect to shared memory block
+shm_reader = external_utils.SharedMemoryReader(name="graphdata")
+current_frame = 0
+max_height = 0
+
+# Initialize data storage
+x_data = []
+y_data_xyz = []
+y_data_xz = []
+
+MAX_FRAMES = 200
+MIN_HEIGHT = 75
+
+# Create a figure and axis
+fig, ax = plt.subplots(figsize=(5, 4))
+xyz_line, = ax.plot([], [], label="XYZ")
+# xyz_line.set_color('orange')
+xz_line, = ax.plot([], [], label="XZ")
+# xz_line.set_color('lightblue')
+
+
+
+# Configure plot
+ax.set_xlim(0, MAX_FRAMES)
+ax.set_ylim(0, MIN_HEIGHT) # Fixed y-axis range for sine wave
+ax.set_xlabel('Frame')
+ax.set_ylabel('Units')
+ax.set_title('External Velocity')
+ax.legend()
+ax.grid(True)
+
+# Enable interactive mode and show the window
+plt.ion()
+plt.show(block=False)
+
+# Draw and create the background after showing the window
+fig.canvas.draw()
+background = fig.canvas.copy_from_bbox(ax.bbox)
+
+# Event handler to exit when window is closed
+running = True
+def on_close(event):
+ global running
+ running = False
+fig.canvas.mpl_connect('close_event', on_close)
+
+try:
+ while running:
+ # Read new data point
+ new_data = shm_reader.read()
+ frame, ev_xyz, ev_xz = struct.unpack('>Iff', new_data[:12])
+
+ if frame == current_frame:
+ fig.canvas.flush_events()
+ continue
+ elif frame < current_frame:
+ try:
+ cutoff = x_data.index(frame)
+ x_data = x_data[:cutoff]
+ y_data_xyz = y_data_xyz[:cutoff]
+ y_data_xz = y_data_xz[:cutoff]
+ except ValueError:
+ x_data.clear()
+ y_data_xyz.clear()
+ y_data_xz.clear()
+
+ current_frame = frame
+ max_height = max(max_height, ev_xyz)
+
+ # Append new data
+ x_data.append(frame)
+ y_data_xyz.append(ev_xyz)
+ y_data_xz.append(ev_xz)
+
+ # Avoid memory overflow
+ if len(x_data) > MAX_FRAMES:
+ x_data.pop(0)
+ y_data_xyz.pop(0)
+ y_data_xz.pop(0)
+
+ # Update the line data
+ xyz_line.set_xdata(x_data)
+ xyz_line.set_ydata(y_data_xyz)
+ xz_line.set_xdata(x_data)
+ xz_line.set_ydata(y_data_xz)
+
+ # Automatically adjust axes to fit data
+ ax.set_xlim(max(0, frame - MAX_FRAMES), max(frame, MAX_FRAMES))
+ ax.set_ylim(0, max(max_height + 10, MIN_HEIGHT))
+
+ # Restore the background, draw the line, and blit
+ fig.canvas.restore_region(background)
+ ax.draw_artist(xyz_line)
+ ax.draw_artist(xz_line)
+ fig.canvas.blit(ax.bbox)
+ fig.canvas.flush_events()
+
+except KeyboardInterrupt:
+ print("Real-time plotting stopped.")
diff --git a/scripts/external/mkds_minimap_window.py b/scripts/external/mkds_minimap_window.py
new file mode 100644
index 0000000..fb7cf9e
--- /dev/null
+++ b/scripts/external/mkds_minimap_window.py
@@ -0,0 +1,173 @@
+import struct
+import external_utils as ex
+import pygame
+import math
+
+WINDOW_SIZE = 480
+SCALE = 1*WINDOW_SIZE/480*100/120/30
+FONT_SIZE = 15
+
+def bytes_to_poslist(b):
+ p_list = []
+ g_list = []
+ for i in range(0,len(b),16):
+ if i+15 < len(b):
+ struct.unpack('f', b[i:i+4])
+ p_list.append((struct.unpack('f', b[i:i+4])[0], struct.unpack('f', b[i+4:i+8])[0]))
+ g_list.append((struct.unpack('f', b[i+8:i+12])[0], struct.unpack('f', b[i+12:i+16])[0]))
+ if len(p_list) == 0:
+ p_list.append((0,0))
+ if len(g_list) == 0:
+ g_list.append((0,0))
+ return p_list, g_list
+
+def bytes_to_idlist(b):
+ res = []
+ for i in range(len(b)):
+ res.append(struct.unpack('b', b[i:i+1])[0])
+ return res
+
+
+def format_position(position, base_position, scaling, yaw = 180):
+ ''' translate and scale and rotate the position
+ from MKW coordinate to pygame screen coordinate
+ the base position is mapped to the middle of the screen '''
+ yaw *= -math.pi/180
+ x,y = position
+ x0, y0 = base_position
+ x -= x0
+ y -= y0
+ x*= SCALE * scaling
+ y*= SCALE * scaling
+ x,y = -x*math.cos(yaw) + y*math.sin(yaw), -x*math.sin(yaw) - y*math.cos(yaw)
+ x += screen.get_width()/2
+ y += screen.get_height()/2
+ return x,y
+
+def is_inbound(coordinate):
+ x,y = coordinate
+ return 0<=x<=screen.get_width() and 0<=y<=screen.get_height()
+
+memory_reader = ex.SharedMemoryReader('mkds minimap')
+cp_reader = ex.SharedMemoryReader('mkds minimap checkpoints')
+cp_id_reader = ex.SharedMemoryReader('mkds minimap checkpoints_id')
+yaw_reader = ex.SharedMemoryReader('mkds minimap yaw')
+pygame.init()
+screen = pygame.display.set_mode((480, 480), pygame.RESIZABLE)
+clock = pygame.time.Clock()
+running = True
+dt = 0
+scale_mult = 1
+draw_line = True
+draw_circle = True
+draw_help = True
+align_with_yaw = True
+help_font = pygame.font.SysFont('Courier New', FONT_SIZE)
+pygame.key.set_repeat(300, 50)
+
+
+while running:
+ # poll for events
+ # pygame.QUIT event means the user clicked X to close your window
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ running = False
+ if event.type == pygame.KEYDOWN and event.key == pygame.K_h:
+ draw_help = not draw_help
+ if event.type == pygame.KEYDOWN and event.key == pygame.K_b:
+ scale_mult *= 1.1
+ if event.type == pygame.KEYDOWN and event.key == pygame.K_n:
+ scale_mult /= 1.1
+ if event.type == pygame.KEYDOWN and event.key == pygame.K_j:
+ draw_line = not draw_line
+ if event.type == pygame.KEYDOWN and event.key == pygame.K_k:
+ draw_circle = not draw_circle
+ if event.type == pygame.KEYDOWN and event.key == pygame.K_l:
+ align_with_yaw = not align_with_yaw
+
+
+ player_positions, ghost_positions = bytes_to_poslist(memory_reader.read())
+ cp_left, cp_right = bytes_to_poslist(cp_reader.read())
+ id_list = bytes_to_idlist(cp_id_reader.read())
+ base_position = player_positions[0]
+ yaw = struct.unpack('f', yaw_reader.read()[:4])[0]
+ if not align_with_yaw:
+ yaw = 180
+ #hacky way to get rid of some unwanted data
+ for i in range(len(cp_left)-1, -1, -1):
+ if (cp_right[i][0] == 0 or cp_left[i][0] == 0):
+ cp_right.pop(i)
+ cp_left.pop(i)
+ id_list.pop(i)
+
+ # fill the screen with a color to wipe away anything from last frame
+ screen.fill("white")
+
+ #Draw help
+ if draw_help:
+ screen.blit(help_font.render('Help - Keybinds', False, (0, 255, 0)), (0,0))
+ screen.blit(help_font.render('Toggle help : h', False, (0, 255, 0)), (0,FONT_SIZE))
+ screen.blit(help_font.render('Zoom in : b', False, (0, 255, 0)), (0,FONT_SIZE*2))
+ screen.blit(help_font.render('Zoom out : n', False, (0, 255, 0)), (0,FONT_SIZE*3))
+ screen.blit(help_font.render('Draw path dots : k', False, (0, 255, 0)), (0,FONT_SIZE*4))
+ screen.blit(help_font.render('Draw path lines : j', False, (0, 255, 0)), (0,FONT_SIZE*5))
+ screen.blit(help_font.render('Rotate with facing yaw : l', False, (0, 255, 0)), (0,FONT_SIZE*6))
+ #Draw player red circle
+ player_pos = format_position(player_positions[0], base_position, scale_mult, yaw)
+ pygame.draw.circle(screen, "red", player_pos, 4)
+
+ #Draw ghost blue circle
+ ghost_pos = format_position(ghost_positions[0], base_position, scale_mult, yaw)
+ pygame.draw.circle(screen, "blue", ghost_pos, 4)
+
+ #Draw Player Path
+ for i in range(0, len(player_positions) -1):
+ if not (player_positions[i+1][0] == 0):
+ p1 = format_position(player_positions[i], base_position, scale_mult, yaw)
+ p2 = format_position(player_positions[i+1], base_position, scale_mult, yaw)
+ if draw_line:
+ pygame.draw.line(screen, "red", p1, p2)
+ if draw_circle:
+ pygame.draw.circle(screen, "red", p1, 2)
+ #Draw Ghost Path
+ for i in range(len(ghost_positions) -1):
+ if not (ghost_positions[i+1][0] == 0):
+ p1 = format_position(ghost_positions[i], base_position, scale_mult, yaw)
+ p2 = format_position(ghost_positions[i+1], base_position, scale_mult, yaw)
+ if draw_line:
+ pygame.draw.line(screen, "blue", p1, p2)
+ if draw_circle:
+ pygame.draw.circle(screen, "blue", p1, 2)
+ #Draw CP box
+ for i in range(len(cp_left)):
+ if id_list[i] == -1:
+ color = 'black'
+ elif id_list[i] == 0:
+ color = 'purple'
+ elif id_list[i] > 0:
+ color = 'red'
+ p1 = format_position(cp_left[i], base_position, scale_mult, yaw)
+ p2 = format_position(cp_left[(i+1)%len(cp_left)], base_position, scale_mult, yaw)
+ pygame.draw.line(screen, color, p1, p2) #left side of the box
+ p1 = format_position(cp_right[i], base_position, scale_mult, yaw)
+ p2 = format_position(cp_right[(i+1)%len(cp_left)], base_position, scale_mult, yaw)
+ pygame.draw.line(screen, color, p1, p2) #right side of the box
+ p1 = format_position(cp_left[i], base_position, scale_mult, yaw)
+ p2 = format_position(cp_right[i], base_position, scale_mult, yaw)
+ pygame.draw.line(screen, color, p1, p2) #cp line
+
+
+
+ # flip() the display to put your work on screen
+ pygame.display.flip()
+
+ # limits FPS to 60
+ # dt is delta time in seconds since last frame, used for framerate-
+ # independent physics.
+ dt = clock.tick(60) / 1000
+
+pygame.quit()
+memory_reader.close_with_writer()
+cp_reader.close_with_writer()
+cp_id_reader.close_with_writer()
+yaw_reader.close_with_writer()
diff --git a/scripts/external/open_file_dialog_box.py b/scripts/external/open_file_dialog_box.py
new file mode 100644
index 0000000..23f9d31
--- /dev/null
+++ b/scripts/external/open_file_dialog_box.py
@@ -0,0 +1,37 @@
+import struct
+import external_utils
+import tkinter as tk
+from tkinter import filedialog
+
+root = tk.Tk()
+root.wm_attributes('-topmost', 1)
+root.withdraw()
+
+type_reader = external_utils.SharedMemoryReader('open_file_dialog')
+reader_text = type_reader.read_text()
+type_reader.close()
+
+''' Example of types list format :
+ "RKG files,*.rkg;CSV files,*.csv;All files,*"
+ ";" separate all entries, and "," separate the name from the extension'''
+
+temp = reader_text.split('|')
+file_types = temp[0]
+initialDir = temp[1]
+title = temp[2]
+multiple = temp[3] == 'True'
+
+types_list = file_types.split(';')
+for i in range(len(types_list)):
+ type_tuple = types_list[i].split(',')
+ types_list[i] = (type_tuple[0], type_tuple[1])
+
+
+file_path = filedialog.askopenfilename(title = title,
+ filetypes = types_list,
+ initialdir = initialDir,
+ multiple = multiple)
+
+
+print(file_path, end='')
+
diff --git a/scripts/external/save_file_dialog_box.py b/scripts/external/save_file_dialog_box.py
new file mode 100644
index 0000000..b9e1264
--- /dev/null
+++ b/scripts/external/save_file_dialog_box.py
@@ -0,0 +1,37 @@
+import struct
+import external_utils
+import tkinter as tk
+from tkinter import filedialog
+
+root = tk.Tk()
+root.wm_attributes('-topmost', 1)
+root.withdraw()
+
+type_reader = external_utils.SharedMemoryReader('save_file_dialog')
+reader_text = type_reader.read_text()
+type_reader.close()
+
+''' Example of types list format :
+ "RKG files,*.rkg;CSV files,*.csv;All files,*"
+ ";" separate all entries, and "," separate the name from the extension'''
+
+temp = reader_text.split('|')
+file_types = temp[0]
+initialDir = temp[1]
+title = temp[2]
+defaultextension = temp[3]
+
+types_list = file_types.split(';')
+for i in range(len(types_list)):
+ type_tuple = types_list[i].split(',')
+ types_list[i] = (type_tuple[0], type_tuple[1])
+
+
+file_path = filedialog.asksaveasfilename(title = title,
+ filetypes = types_list,
+ initialdir = initialDir,
+ defaultextension = defaultextension)
+
+
+print(file_path, end='')
+
diff --git a/scripts/external/ttk_gui_window.py b/scripts/external/ttk_gui_window.py
new file mode 100644
index 0000000..2d2dc89
--- /dev/null
+++ b/scripts/external/ttk_gui_window.py
@@ -0,0 +1,133 @@
+import tkinter as tk
+from tkinter import ttk # lol
+import os
+import sys
+import struct
+import external_utils as ex
+from idlelib.tooltip import Hovertip
+
+# This constant determines how the buttons are arranged.
+# Ex: BUTTON_LAYOUT[section_index][row_index][column_index]
+BUTTON_LAYOUT = [
+ [
+ ["Load from Player", "Load from Ghost"],
+ ["Save to RKG", "Load from RKG"],
+ ["Open in Editor", "Load from CSV"],
+ ],
+ [
+ ["Load from Player", "Load from Ghost"],
+ ["Save to RKG", "Load from RKG"],
+ ["Open in Editor", "Load from CSV"],
+ ],
+]
+
+TOOLTIP_LAYOUT = [
+ [
+ ["Load the inputs from the Player.\nSave the inputs to the Player CSV file.", "Load the inputs from the Ghost.\nSave the inputs to the Player CSV file."],
+ ["Save the Player CSV file to a RKG file.\nTakes metadata from the current Player's state", "Load the inputs from a RKG file.\nSave the inputs to the Player CSV file."],
+ ["Open the Player CSV file with default Editor", "Load the inputs from a CSV file.\nSave the inputs to the Player CSV file."],
+ ],
+ [
+ ["Load the inputs from the Player.\nSave the inputs to the Ghost CSV file.", "Load the inputs from the Ghost.\nSave the inputs to the Ghost CSV file."],
+ ["Save the Ghost CSV file to a RKG file.\nTakes metadata from the current Ghost's state", "Load the inputs from a RKG file.\nSave the inputs to the Ghost CSV file."],
+ ["Open the Ghost CSV file with default Editor", "Load the inputs from a CSV file.\nSave the inputs to the Ghost CSV file."],
+ ],
+]
+
+ACTIVATE_CHECKBOX_LAYOUT = [ ["Activate"],
+ ["Activate Soft", "Activate Hard"] ]
+def main():
+ try:
+ shm_activate = ex.SharedMemoryBlock.connect(name="ttk_gui_activate")
+ shm_buttons = ex.SharedMemoryBlock.connect(name="ttk_gui_buttons")
+ shm_player_csv = ex.SharedMemoryReader(name="ttk_gui_player_csv")
+ shm_ghost_csv = ex.SharedMemoryReader(name="ttk_gui_ghost_csv")
+ shm_close_event = ex.SharedMemoryBlock.connect(name="ttk_gui_window_closed")
+ except FileNotFoundError as e:
+ raise FileNotFoundError(f"Shared memory buffer '{e.filename}' not found. Make sure the `TTK_GUI` script is enabled.")
+
+ window = tk.Tk()
+ window.title("TAS Toolkit GUI")
+
+ dir_path = os.path.dirname(sys.argv[0])
+ setting_filename = os.path.join(dir_path, "tkinter.ini")
+ #Load geometry
+ geometry = ex.load_external_setting(setting_filename, 'ttk_gui_geometry')
+ if geometry is None:
+ geometry = '500x250'
+ window.geometry(geometry)
+ # window.attributes('-topmost',True)
+
+ #Save geometry on exit
+ def on_closing():
+ ex.save_external_setting(setting_filename, 'ttk_gui_geometry', str(window.winfo_geometry()))
+ window.destroy()
+ shm_close_event.write_text("1")
+ window.protocol("WM_DELETE_WINDOW", on_closing)
+
+ root_frame = ttk.Frame(window)
+ root_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
+
+ # Checkboxes state
+ activate_state = [tk.BooleanVar(), tk.BooleanVar(), tk.BooleanVar()]
+ def on_checkbox_change():
+ shm_activate.write(struct.pack('>???', *[var.get() for var in activate_state]))
+
+ # File name display state
+ player_csv = tk.StringVar(value=f"File : {os.path.basename(shm_player_csv.read_text())}")
+ ghost_csv = tk.StringVar(value=f"File : {os.path.basename(shm_ghost_csv.read_text())}")
+
+ # Construct page layout
+ for section_index, section_title in enumerate(["Player Inputs", "Ghost Inputs"]):
+ section_frame = ttk.LabelFrame(root_frame, text=section_title)
+ section_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
+
+ section_activate_button_frame = ttk.Frame(section_frame)
+ section_activate_button_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=3, pady=3)
+
+ for activate_button_index in range(len(ACTIVATE_CHECKBOX_LAYOUT[section_index])):
+ button_text = ACTIVATE_CHECKBOX_LAYOUT[section_index][activate_button_index]
+ activate_state_index = activate_button_index + sum( [ len(ACTIVATE_CHECKBOX_LAYOUT[i]) for i in range(section_index) ])
+
+ ttk.Checkbutton(section_activate_button_frame, text=button_text, variable=activate_state[activate_state_index], command=on_checkbox_change) \
+ .pack(expand = True, pady=1)
+
+ ttk.Label(section_frame, textvariable=[player_csv, ghost_csv][section_index]) \
+ .pack(pady=5)
+
+ for row_index, row in enumerate(BUTTON_LAYOUT[section_index]):
+ btn_row_frame = ttk.Frame(section_frame)
+ btn_row_frame.pack(pady=5)
+
+ for col_index, btn_text in enumerate(row):
+ button_data = struct.pack('>?BBB', True, section_index, row_index, col_index)
+ def on_click(data=button_data):
+ shm_buttons.write(data)
+ button_inst = ttk.Button(btn_row_frame, text=btn_text, command=on_click, width=15)
+ button_inst.pack(side=tk.LEFT, padx=5)
+ tooltip_text = TOOLTIP_LAYOUT[section_index][row_index][col_index]
+ tooltip_inst = Hovertip(button_inst, tooltip_text, hover_delay=1200)
+
+ # Function that runs repeatedly while window is open
+ def loop_actions():
+ new_text = shm_player_csv.read_text()
+ if new_text:
+ player_csv.set(f"File : {os.path.basename(new_text)}")
+
+ new_text = shm_ghost_csv.read_text()
+ if new_text:
+ ghost_csv.set(f"File : {os.path.basename(new_text)}")
+
+ window.after(ms=10, func=loop_actions)
+
+ shm_close_event.write_text("0")
+
+ window.after(ms=0, func=loop_actions)
+ window.mainloop()
+
+ #This part of the code is only accessed when the window has been closed
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/python-stubs/dolphin.pyi b/scripts/python-stubs/dolphin.pyi
new file mode 100644
index 0000000..0591323
--- /dev/null
+++ b/scripts/python-stubs/dolphin.pyi
@@ -0,0 +1,33 @@
+"""
+Aggregator module of all dolphin-provided modules.
+It lets people import the dolphin-provided modules in a more
+intuitive way. For example, people can then do this:
+ from dolphin import event, memory
+instead of:
+ import dolphin_event as event
+ import dolphin_memory as memory
+
+Valid:
+ import dolphin
+ from dolphin import *
+ from dolphin import event
+ import dolphin_event as event
+Invalid:
+ import dolphin.event
+ from dolphin.event import ...
+""" # noqa: D400,D415 # Tries to add a . on an import example
+import dolphin_event as event
+import dolphin_memory as memory
+import dolphin_gui as gui
+import dolphin_savestate as savestate
+import dolphin_controller as controller
+import dolphin_utils as utils
+
+__all__ = [
+ "event",
+ "memory",
+ "gui",
+ "savestate",
+ "controller",
+ "utils",
+]
diff --git a/scripts/python-stubs/dolphin_controller.pyi b/scripts/python-stubs/dolphin_controller.pyi
new file mode 100644
index 0000000..0c2f48a
--- /dev/null
+++ b/scripts/python-stubs/dolphin_controller.pyi
@@ -0,0 +1,133 @@
+"""
+Module for programmatic inputs.
+
+Currently, only for GameCube, Wiimote, Nunchuck buttons and Wii IR (pointing).
+No acceleration or other extensions data yet.
+"""
+from typing import TypedDict, type_check_only
+
+
+@type_check_only
+class GCInputs(TypedDict):
+ Left: bool
+ Right: bool
+ Down: bool
+ Up: bool
+ Z: bool
+ R: bool
+ L: bool
+ A: bool
+ B: bool
+ X: bool
+ Y: bool
+ Start: bool
+ StickX: int # 0-255, 128 is neutral
+ StickY: int # 0-255, 128 is neutral
+ CStickX: int # 0-255, 128 is neutral
+ CStickY: int # 0-255, 128 is neutral
+ TriggerLeft: int # 0-255
+ TriggerRight: int # 0-255
+ AnalogA: int # 0-255
+ AnalogB: int # 0-255
+ Connected: bool
+
+
+@type_check_only
+class WiiInputs(TypedDict):
+ Left: bool
+ Right: bool
+ Down: bool
+ Up: bool
+ Plus: bool
+ Minus: bool
+ One: bool
+ Two: bool
+ A: bool
+ B: bool
+ Home: bool
+
+
+class NunchuckInputs(TypedDict):
+ C: bool
+ Z: bool
+ StickX: int # 0-255, 128 is neutral
+ StickY: int # 0-255, 128 is neutral
+
+
+def get_gc_buttons(controller_id: int, /) -> GCInputs:
+ """
+ Retrieves the current input map for the given GameCube controller.
+
+ :param controller_id: 0-based index of the controller
+ :return: dictionary describing the current input map
+ """
+
+
+def set_gc_buttons(controller_id: int, inputs: GCInputs, /) -> None:
+ """
+ Sets the current input map for the given GameCube controller.
+ The override will hold for the current frame.
+
+ :param controller_id: 0-based index of the controller
+ :param inputs: dictionary describing the input map
+ """
+
+
+def get_wii_buttons(controller_id: int, /) -> WiiInputs:
+ """
+ Retrieves the current input map for the given Wii controller.
+
+ :param controller_id: 0-based index of the controller
+ :return: dictionary describing the current input map
+ """
+
+
+def set_wii_buttons(controller_id: int, inputs: WiiInputs, /) -> None:
+ """
+ Sets the current input map for the given Wii controller.
+ The override will hold for the current frame.
+
+ :param controller_id: 0-based index of the controller
+ :param inputs: dictionary describing the input map
+ """
+
+
+def set_wii_ircamera_transform(
+ controller_id: int, x: float, y: float,
+ z: float = -2, pitch: float = 0, yaw: float = 0, roll: float = 0, /,
+) -> None:
+ """
+ Places the simulated IR camera at the specified location
+ with the specified rotation relative to the sensor bar.
+ For example, to move 2 meters away from the sensor,
+ 15 centimeters to the right and 5 centimeters down, use:
+ `set_wii_ircamera_transform(controller_id, 0.15, -0.05, -2)`.
+
+ :param controller_id: 0-based index of the controller
+ :param x: x-position of the simulated IR camera in meters
+ :param y: y-position of the simulated IR camera in meters
+ :param z: z-position of the simulated IR camera in meters.
+ Default is -2, meaning 2 meters from the simulated sensor bar.
+ :param pitch: pitch of the simulated IR camera in radians.
+ :param yaw: yaw of the simulated IR camera in radians.
+ :param roll: roll of the simulated IR camera in radians.
+ """
+
+
+def get_nunchuck_buttons(controller_id: int, /) -> NunchuckInputs:
+ """
+ Retrieves the current input map for the given Nunchuck extension.
+
+ :param controller_id: 0-based index of the controller
+ :return: dictionary describing the current input map
+ """
+
+
+def set_nunchuck_buttons(controller_id: int, inputs: NunchuckInputs, /) -> None:
+ """
+ Sets the current input map for the given Nunchuck extension.
+ The override will hold for the current frame.
+
+ :param controller_id: 0-based index of the controller
+ :param inputs: dictionary describing the input map
+ """
diff --git a/scripts/python-stubs/dolphin_event.pyi b/scripts/python-stubs/dolphin_event.pyi
new file mode 100644
index 0000000..ffbaa5c
--- /dev/null
+++ b/scripts/python-stubs/dolphin_event.pyi
@@ -0,0 +1,224 @@
+"""
+Module for awaiting or registering callbacks on all events emitted by Dolphin.
+
+The odd-looking Protocol classes are just a lot of syntax to essentially describe
+the callback's signature. See https://www.python.org/dev/peps/pep-0544/#callback-protocols
+"""
+from collections.abc import Callable
+from typing import Protocol, type_check_only
+
+
+def on_frameadvance(callback: Callable[[], None] | None) -> None:
+ """Registers a callback to be called every time the game has rendered a new frame."""
+
+
+async def frameadvance() -> None:
+ """Awaitable event that completes once the game has rendered a new frame."""
+
+
+def on_framebegin(callback: Callable[[], None] | None) -> None:
+ """Registers a callback to be called every time the game checks for controller inputs.
+ Typically happen twice per frame, before and after frameadvance.
+ Should be used for performing inputs on a specific frame """
+
+
+async def framebegin() -> None:
+ """Awaitable event that completes once the game checks for controller inputs."""
+
+
+
+@type_check_only
+class _CodebreakpointCallback(Protocol):
+ def __call__(self, addr: int) -> None:
+ """
+ Example callback stub for on_codebreakpoint.
+
+ :param addr: address that was accessed
+ """
+
+
+def on_codebreakpoint(callback: _CodebreakpointCallback | None) -> None:
+ """
+ Registers a callback to be called every time a previously added code breakpoint is hit.
+
+ :param callback:
+ :return:
+ """
+
+
+async def codebreakpoint() -> tuple[int,]:
+ """Awaitable event that completes once a previously added code breakpoint is hit."""
+
+
+@type_check_only
+class _MemorybreakpointCallback(Protocol):
+ def __call__(self, is_write: bool, addr: int, value: int) -> None:
+ """
+ Example callback stub for on_memorybreakpoint.
+
+ :param is_write: true if a value was written, false if it was read
+ :param addr: address that was accessed
+ :param value: new value at the given address
+ """
+
+
+def on_memorybreakpoint(callback: _MemorybreakpointCallback | None) -> None:
+ """
+ Registers a callback to be called every time a previously added memory breakpoint is hit.
+
+ :param callback:
+ :return:
+ """
+
+
+async def memorybreakpoint() -> tuple[bool, int, int]:
+ """Awaitable event that completes once a previously added memory breakpoint is hit."""
+
+
+@type_check_only
+class _SaveStateCallback(Protocol):
+ def __call__(self, is_slot: bool, slot: int, /) -> None:
+ """
+ Example callback stub for on_savestatesave and/or on_savestateload.
+
+ :param is_slot: true if save/load was with a savestate slot, \
+ false if save/load was from a file
+ :param slot: the slot the save/load occurred to/from. \
+ Should be disregarded if is_slot is false
+ """
+
+
+def on_savestatesave(callback: _SaveStateCallback | None) -> None:
+ """
+ Registers a callback to be called every time a savestate is saved.
+
+ :param callback:
+ :return:
+ """
+
+
+async def savestatesave() -> tuple[bool, int]:
+ """Awaitable event that completes once a savestate is saved."""
+
+
+def on_savestateload(callback: _SaveStateCallback | None) -> None:
+ """
+ Registers a callback to be called every time a savestate has been loaded.
+
+ :param callback:
+ :return:
+ """
+
+
+async def savestateload() -> tuple[bool, int]:
+ """Awaitable event that completes once a savestate has been loaded."""
+
+
+
+def on_beforesavestateload(callback: _SaveStateCallback | None) -> None:
+ """
+ Registers a callback to be called before a savestate is been loaded.
+
+ :param callback:
+ :return:
+ """
+
+
+async def beforesavestateload() -> tuple[bool, int]:
+ """Awaitable event that completes before a savestate is been loaded."""
+
+
+
+@type_check_only
+class _BoolCallback(Protocol):
+ def __call__(self, boolean: bool) -> None:
+ """
+ Example callback stub for on_focuschange.
+
+ :param boolean: correspond to the has_focus state \
+ or other boolean depending on the context
+ """
+
+def on_unpause(callback: Callable[[], None] | None) -> None:
+ """
+ Registers a callback to be called on emulation unpause.
+
+ :param callback:
+ :return:
+ """
+
+
+async def unpause() -> None:
+ """Awaitable event that completes on emulation unpause."""
+
+
+
+def on_focuschange(callback: _BoolCallback | None) -> None:
+ """
+ Registers a callback to be called on render window focus change.
+
+ :param callback:
+ :return:
+ """
+
+
+async def focuschange() -> bool:
+ """Awaitable event that completes on render window focus change."""
+
+@type_check_only
+class _GeometryCallback(Protocol):
+ def __call__(self, x: int, y:int, width:int, height:int) -> None:
+ """
+ Example callback stub for rendergeometrychange.
+
+ :param x: x coordinate of the window
+ :param y: y coordinate of the window
+ :param width: width of the window
+ :param height: height of the window
+ """
+
+def on_rendergeometrychange(callback: _GeometryCallback | None) -> None:
+ """
+ Registers a callback to be called on render window geometry change.
+
+ :param callback:
+ :return:
+ """
+
+
+async def rendergeometrychange() -> tuple[int, int, int, int]:
+ """Awaitable event that completes on render window geometry change."""
+
+
+def on_timertick(callback: Callable[[], None] | None) -> None:
+ """Registers a callback to be called 60 times per second"""
+
+
+async def frameadvance() -> None:
+ """Awaitable event that completes 60 times per second"""
+
+
+@type_check_only
+class _ScriptEndCallback(Protocol):
+ def __call__(self, id_: int) -> None:
+ """
+ Example callback stub for rendergeometrychange.
+
+ :param id_: id of the script ending (correspond with utils.get_script_id())
+ """
+
+def on_scriptend(callback: _ScriptEndCallback | None) -> None:
+ """
+ Registers a callback to be called when any script ends.
+
+ :param callback:
+ :return:
+ """
+
+
+async def scriptend() -> int:
+ """Awaitable event that completes when any script ends."""
+
+
+def system_reset() -> None:
+ """Resets the emulation."""
diff --git a/scripts/python-stubs/dolphin_gui.pyi b/scripts/python-stubs/dolphin_gui.pyi
new file mode 100644
index 0000000..100e689
--- /dev/null
+++ b/scripts/python-stubs/dolphin_gui.pyi
@@ -0,0 +1,118 @@
+"""
+Module for doing stuff with the graphical user interface.
+
+All colors are in ARGB.
+All positions are (x, y) with (0, 0) being top left. X is the horizontal axis.
+"""
+
+from typing_extensions import TypeAlias
+
+Position: TypeAlias = tuple[float, float]
+
+
+def add_osd_message(message: str, duration_ms: int = 2000, color: int = 0xFFFFFF30) -> None:
+ """
+ Adds a new message to the on-screen-display.
+
+ :param message: message to agg
+ :param duration_ms: how long the message should be visible for, in milliseconds
+ :param color: color of the message text as an int
+ """
+
+
+def clear_osd_messages() -> None:
+ """Clear all on-screen-display messages."""
+
+
+def get_display_size() -> tuple[float, float]:
+ """:return: The current display size in pixels."""
+
+def get_font_size() -> int:
+ """:return: The current font size in pixels."""
+
+
+def draw_line(a: Position, b: Position, color: int, thickness: float = 1) -> None:
+ """Draws a line from a to b."""
+
+
+def draw_rect(
+ a: Position,
+ b: Position,
+ color: int,
+ rounding: float = 0,
+ thickness: float = 1,
+) -> None:
+ """Draws a hollow rectangle from a (upper left) to b (lower right)."""
+
+
+def draw_rect_filled(a: Position, b: Position, color: int, rounding: float = 0) -> None:
+ """Draws a filled rectangle from a (upper left) to b (lower right)."""
+
+
+def draw_quad(
+ a: Position,
+ b: Position,
+ c: Position,
+ d: Position,
+ color: int,
+ thickness: float = 1,
+) -> None:
+ """
+ Draws a hollow quad through the points a, b, c and d.
+ Points should be defined in clockwise order.
+ """
+
+
+def draw_quad_filled(a: Position, b: Position, c: Position, d: Position, color: int) -> None:
+ """Draws a filled quad through the points a, b, c and d."""
+
+
+def draw_triangle(a: Position, b: Position, c: Position, color: int, thickness: float = 1) -> None:
+ """Draws a hollow triangle through the points a, b and c."""
+
+
+def draw_triangle_filled(a: Position, b: Position, c: Position, color: int) -> None:
+ """Draws a filled triangle through the points a, b and c."""
+
+
+def draw_circle(
+ center: Position,
+ radius: float,
+ color: int,
+ num_segments: int | None = None,
+ thickness: float = 1,
+) -> None:
+ """
+ Draws a hollow circle with the given center point and radius.
+ If num_segments is set to None (default), a sensible default is used.
+ """
+
+
+def draw_circle_filled(
+ center: Position, radius: float, color: int,
+ num_segments: int | None = None,
+) -> None:
+ """
+ Draws a filled circle with the given center point and radius.
+ If num_segments is set to None (default), a sensible default is used.
+ """
+
+
+def draw_text(pos: Position, color: int, text: str) -> None:
+ """Draws text at a fixed position."""
+
+
+def draw_polyline(
+ points: list[Position],
+ color: int,
+ closed: bool = False,
+ thickness: float = 1,
+) -> None:
+ """Draws a line through a list of points."""
+
+
+def draw_convex_poly_filled(points: list[Position], color: int) -> None:
+ """
+ Draws a convex polygon through a list of points.
+ Points should be defined in clockwise order.
+ """
diff --git a/scripts/python-stubs/dolphin_memory.pyi b/scripts/python-stubs/dolphin_memory.pyi
new file mode 100644
index 0000000..def2b82
--- /dev/null
+++ b/scripts/python-stubs/dolphin_memory.pyi
@@ -0,0 +1,231 @@
+"""Module for interacting with the emulated machine's memory."""
+
+
+def read_u8(addr: int, /) -> int:
+ """
+ Reads 1 byte as an unsigned integer.
+
+ :param addr: memory address to read from
+ :return: value as integer
+ """
+
+
+def read_u16(addr: int, /) -> int:
+ """
+ Reads 2 bytes as an unsigned integer.
+
+ :param addr: memory address to read from
+ :return: value as integer
+ """
+
+
+def read_u32(addr: int, /) -> int:
+ """
+ Reads 4 bytes as an unsigned integer.
+
+ :param addr: memory address to read from
+ :return: value as integer
+ """
+
+
+def read_u64(addr: int, /) -> int:
+ """
+ Reads 8 bytes as an unsigned integer.
+
+ :param addr: memory address to read from
+ :return: value as integer
+ """
+
+
+def read_s8(addr: int, /) -> int:
+ """
+ Reads 1 byte as a signed integer.
+
+ :param addr: memory address to read from
+ :return: value as integer
+ """
+
+
+def read_s16(addr: int, /) -> int:
+ """
+ Reads 2 bytes as a signed integer.
+
+ :param addr: memory address to read from
+ :return: value as integer
+ """
+
+
+def read_s32(addr: int, /) -> int:
+ """
+ Reads 4 bytes as a signed integer.
+
+ :param addr: memory address to read from
+ :return: value as integer
+ """
+
+
+def read_s64(addr: int, /) -> int:
+ """
+ Reads 8 bytes as a signed integer.
+
+ :param addr: memory address to read from
+ :return: value as integer
+ """
+
+
+def read_f32(addr: int, /) -> float:
+ """
+ Reads 4 bytes as a floating point number.
+
+ :param addr: memory address to read from
+ :return: value as floating point number
+ """
+
+
+def read_f64(addr: int, /) -> float:
+ """
+ Reads 8 bytes as a floating point number.
+
+ :param addr: memory address to read from
+ :return: value as floating point number
+ """
+
+
+def read_bytes(addr: int, size: int, /) -> bytearray:
+ """
+ Reads size bytes and outputs a bytearray of length size.
+
+ :param addr: memory address to start reading from
+ :param size: number of bytes to read
+ :return: bytearray containing the read bytes
+ """
+
+
+def invalidate_icache(addr: int, size: int, /) -> None:
+ """
+ Invalidates JIT cached code between the address and address + size, \
+ forcing the JIT to refetch instructions instead of executing from its cache.
+
+ :param addr: memory address to start invalidation at
+ :param size: size of the cache as integer
+ """
+
+
+def write_u8(addr: int, value: int, /) -> None:
+ """
+ Writes an unsigned integer to 1 byte.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as integer
+ """
+
+
+def write_u16(addr: int, value: int, /) -> None:
+ """
+ Writes an unsigned integer to 2 bytes.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as integer
+ """
+
+
+def write_u32(addr: int, value: int, /) -> None:
+ """
+ Writes an unsigned integer to 4 bytes.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as integer
+ """
+
+
+def write_u64(addr: int, value: int, /) -> None:
+ """
+ Writes an unsigned integer to 8 bytes.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as integer
+ """
+
+
+def write_s8(addr: int, value: int, /) -> None:
+ """
+ Writes a signed integer to 1 byte.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as integer
+ """
+
+
+def write_s16(addr: int, value: int, /) -> None:
+ """
+ Writes a signed integer to 2 bytes.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as integer
+ """
+
+
+def write_s32(addr: int, value: int, /) -> None:
+ """
+ Writes a signed integer to 4 bytes.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as integer
+ """
+
+
+def write_s64(addr: int, value: int, /) -> None:
+ """
+ Writes a signed integer to 8 bytes.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as integer
+ """
+
+
+def write_f32(addr: int, value: float, /) -> None:
+ """
+ Writes a floating point number to 4 bytes.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as floating point number
+ """
+
+
+def write_f64(addr: int, value: float, /) -> None:
+ """
+ Writes a floating point number to 8 bytes.
+ Overflowing values are truncated.
+
+ :param addr: memory address to read from
+ :param value: value as floating point number
+ """
+
+
+def write_bytes(addr: int, bytes: bytearray, /) -> None:
+ """
+ Writes each byte from the provided bytearray,
+ starting from addr.
+
+ :param addr: memory address to start writing to
+ :param bytes: bytearray of bytes to write
+ """
+
+def is_memory_accessible() -> bool:
+ """
+ Return a boolean value corresponding to
+ the state of the memory.
+ True means the memory is accessible,
+ False means the memory isn't accessible.
+ Trying to read/write the memory while it's not accessible
+ may result in Dolphin crashing
+ """
\ No newline at end of file
diff --git a/scripts/python-stubs/dolphin_savestate.pyi b/scripts/python-stubs/dolphin_savestate.pyi
new file mode 100644
index 0000000..205604c
--- /dev/null
+++ b/scripts/python-stubs/dolphin_savestate.pyi
@@ -0,0 +1,31 @@
+"""Module for creating and loading savestates."""
+
+
+def save_to_slot(slot: int, /) -> None:
+ """
+ Saves a savestate to the given slot.
+ The slot number must be between 0 and 99.
+ """
+
+
+def save_to_file(filename: str, /) -> None:
+ """Saves a savestate to the given file."""
+
+
+def save_to_bytes() -> bytes:
+ """Saves a savestate and returns it as bytes."""
+
+
+def load_from_slot(slot: int, /) -> None:
+ """
+ Loads a savestate from the given slot.
+ The slot number must be between 0 and 99.
+ """
+
+
+def load_from_file(filename: str, /) -> None:
+ """Loads a savestate from the given file."""
+
+
+def load_from_bytes(state_bytes: bytes, /) -> None:
+ """Loads a savestate from the given bytes."""
diff --git a/scripts/python-stubs/dolphin_utils.pyi b/scripts/python-stubs/dolphin_utils.pyi
new file mode 100644
index 0000000..fbd5a8a
--- /dev/null
+++ b/scripts/python-stubs/dolphin_utils.pyi
@@ -0,0 +1,100 @@
+"""Module for various utilities."""
+
+
+def get_script_dir() -> str:
+ """
+ Returns the path to the Scripts directory, \
+ which is found in the Load folder inside of the user directory by default.
+
+ :return: value as string
+ """
+
+
+def open_file() -> str:
+ """
+ Prompts the user to open a file.
+
+ :return: value as string
+ """
+
+
+def start_framedump() -> None:
+ """Starts a framedump."""
+
+
+def stop_framedump() -> None:
+ """Stops a framedump."""
+
+
+def is_framedumping() -> bool:
+ """
+ Checks if a framedump is occuring.
+
+ :return: value as bool
+ """
+
+
+def start_audiodump() -> None:
+ """Starts an audiodump."""
+
+
+def stop_audiodump() -> None:
+ """Stops an audiodump."""
+
+
+def is_audiodumping() -> bool:
+ """
+ Checks if an audiodump is occuring.
+
+ :return: value as bool
+ """
+
+
+def save_screenshot(filename: str | None = None) -> None:
+ """Saves a screenshot of the running game."""
+
+
+def toggle_play() -> None:
+ """Plays/Pauses the current game."""
+
+
+def is_paused() -> bool:
+ """
+ Checks if the emulation is currently paused.
+
+ :return: value as bool
+ """
+
+
+def renderer_has_focus() -> bool:
+ """
+ Checks if the render window has focus.
+
+ :return: value as bool
+ """
+
+
+def renderer_geometry() -> (int, int, int, int):
+ """
+ Get the geometry value of the rendering QtWidget
+
+ :return: (x, y, width, height) as (int, int, int, int)
+ """
+
+def cancel_script(scriptPath : str) -> None:
+ """Cancel the script with the corresponding path
+ The script cancel is delayed to when the host is free.
+ It means it won't cancel immediately, but it will cancel after
+ the current python script main() is over."""
+
+def activate_script(scriptPath : str) -> None:
+ """Activate the script with the corresponding path
+ The script activate is delayed to when the host is free.
+ It means it won't activate immediately, but it will activate after
+ the current python script main() is over."""
+
+def get_script_name() -> str:
+ """Return the filepath of the current script"""
+
+def get_script_id() -> int:
+ """Return the id of the current script"""
\ No newline at end of file
diff --git a/scripts/update_scripts.py b/scripts/update_scripts.py
new file mode 100644
index 0000000..042bcf6
--- /dev/null
+++ b/scripts/update_scripts.py
@@ -0,0 +1,20 @@
+import requests, zipfile, io, os, shutil
+
+print('Downloading new scripts')
+url = 'https://github.com/Blounard/mkw-scripts/archive/refs/heads/main.zip'
+r = requests.get(url)
+print('Extracting scripts')
+z = zipfile.ZipFile(io.BytesIO(r.content))
+z.extractall("script_update_temp")
+
+source_folder = r'script_update_temp\mkw-scripts-main\scripts'
+
+print('Replacing scripts')
+for entry in os.scandir(source_folder):
+ source_name = os.path.join(source_folder, entry.name)
+ if os.path.isfile(source_name):
+ shutil.copy(source_name, entry.name)
+ else:
+ shutil.copytree(source_name, entry.name, dirs_exist_ok=True)
+
+shutil.rmtree("script_update_temp")
diff --git a/scripts/verify_package.py b/scripts/verify_package.py
new file mode 100644
index 0000000..fe0c8b0
--- /dev/null
+++ b/scripts/verify_package.py
@@ -0,0 +1,10 @@
+import numpy
+import matplotlib
+import pygame
+import tkinter
+import os
+from PIL import Image
+import moviepy
+
+print('\n\n\nPackages installed !')
+os.system("pause")