Skip to content

Commit

Permalink
Introduce mkw_files folder and RKG header classes
Browse files Browse the repository at this point in the history
NOTE: These changes are currently untested.

The intention is for there to be other file types with classes supported similarly (such as KCL, KMP, etc.), but RKG is the most important one for some further changes I want to make.
  • Loading branch information
kierio04 committed Feb 26, 2024
1 parent 70086c4 commit b51e1f2
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 25 deletions.
2 changes: 1 addition & 1 deletion scripts/Modules/mkw_classes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from .common import RegionError
from .common import vec2, vec3, mat34, quatf, eulerAngle
from .common import ExactTimer
from .common import CupId, CourseId, VehicleId, CharacterId, WheelCount, VehicleType
from .common import CupId, CourseId, VehicleId, CharacterId, ControllerId, WheelCount, VehicleType
from .common import SpecialFloor, TrickType, SurfaceProperties, RaceConfigPlayerType

from .input_mgr import InputMgr
Expand Down
6 changes: 6 additions & 0 deletions scripts/Modules/mkw_classes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,12 @@ class CharacterId(Enum):
PEACH_MENU = 45 # biker outfit
DAISY_MENU = 46 # biker outfit
ROSALINA_MENU = 47 # biker outfit

class ControllerId(Enum):
WII_WHEEL = 0
WII_REMOTE_AND_NUNCHUK = 1
CLASSIC_CONTROLLER = 2
GAMECUBE_CONTROLLER = 3

class WheelCount(Enum):
_4_WHEELS = 0
Expand Down
4 changes: 2 additions & 2 deletions scripts/Modules/mkw_classes/race_config_player.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dolphin import memory

from . import CharacterId, VehicleId, RaceConfigPlayerType
from . import CharacterId, VehicleId, ControllerId, RaceConfigPlayerType

class RaceConfigPlayer:
def __init__(self, addr):
Expand Down Expand Up @@ -40,7 +40,7 @@ def team(self):

def controller_id(self):
controller_id_ref = self.addr + 0xD0
return memory.read_u32(controller_id_ref)
return ControllerId(memory.read_u32(controller_id_ref))

def previous_score(self):
previous_score_ref = self.addr + 0xD8
Expand Down
9 changes: 9 additions & 0 deletions scripts/Modules/mkw_files/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Import all class objects so that they are accessible just by importing
# from mkw_files rather than each individual file.

# Imports are ordered in terms of dependency.
# Improper ordering can lead to circular dependencies.

# Ignore unused import warnings from the linter

from .rkg import RKGFileHeader, RKGInputDataHeader
122 changes: 122 additions & 0 deletions scripts/Modules/mkw_files/rkg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from Modules import mkw_translations as translate
import calendar

class RKGFileHeader:
"""
A class representing the header of an RKG.
Attributes:
file_id (str): "RKGD" in ASCII. Revolution Kart Ghost Data file identifier.
minutes (int): Minutes field of finishing time.
seconds (int): Seconds field of finishing time.
milliseconds (int): Milliseconds field of finishing time.
track (int): Track ghost was set on.
vehicle (str): Vehicle ghost used.
character (str): Character ghost used.
year (int): Year that the ghost was set, stored relative to the year 2000.
month (int): Month that the ghost was set.
day (int): Day that the ghost was set.
controller (int): Controller ghost was set with.
compressed (bool): Compressed flag
ghost_type (str): Ghost type ghost is stored as.
drift_type (str): Drift type ghost used.
input_data_length (int): Length of input data when decompressed and without padding.
lap_count (int): Total laps ghost drove.
lap_split_minutes (list): Minutes field for each of the 5 lap splits. Unused split times are set to 0.
lap_split_seconds (list): Seconds field for each of the 5 lap splits. Unused split times are set to 0.
lap_split_milliseconds (list): Milliseconds field for each of the 5 lap splits. Unused split times are set to 0.
country (str): Country ghost was set in, or 0xFF if sharing location disabled.
state (int): Raw State code for Town/State/Province ghost was set in, or 0xFF if sharing location disabled.
location (int): Raw Location code for Location ghost was set in. Typically 0x0000; 0xFFFF if sharing location disabled.
mii_data (list): Raw Mii Data for Mii ghost was set with.
mii_crc (int): CRC16-CCITT-Xmodem of Mii
"""

file_id: str
minutes: int
seconds: int
milliseconds: int
track: str
vehicle: str
character: str
year: int
month: int
day: int
controller: str
compressed: bool
ghost_type: str
drift_type: str
input_data_length: int
lap_count: int
lap_split_minutes: list
lap_split_seconds: list
lap_split_milliseconds: list
country: str
state: int
location: int
mii_data: list
mii_crc: int

def __init__(self, raw: list):
"""
Initialises the FileHeader object.
"""

self.file_id = str(''.join([chr(i) for i in raw[0x00:0x03]]))
self.minutes = raw[0x04] >> 1
self.seconds = ((raw[0x04] & 0x01) << 6) + (raw[0x05] >> 2)
self.milliseconds = ((raw[0x05] & 0x03) << 8) + raw[0x06]
self.track = translate.course(raw[0x07] >> 2)
self.vehicle = translate.vehicle(raw[0x08] >> 2)
self.character = translate.character((raw[0x08] & 0x03) << 4) + (raw[0x09] >> 4)
self.year = ((raw[0x09] & 0x0F) << 3) + (raw[0x0A] >> 5)
self.month = calendar.month_name[(raw[0x0A] & 0x1E) >> 1]
self.day = ((raw[0x0A] & 0x01) << 4) + (raw[0x0B] >> 4)
self.controller = translate.controller(raw[0x0B] & 0x0F)
self.compressed = ((raw[0x0C] & 0x1000) >> 3) != 0
self.ghost_type = translate.ghost_type(((raw[0x0C] & 0x01) << 6) + (raw[0x0D] >> 2))
self.drift_type = translate.drift_type((raw[0x0D] & 0x10) >> 1)
self.input_data_length = (raw[0x0E] << 8) + raw[0x0F]
self.lap_count = raw[0x10]
self.read_lap_splits(raw)
self.country = str(translate.country(raw[0x34]))
self.state = raw[0x35]
self.location = (raw[0x36] << 8) + raw[0x37]
self.mii_data = raw[0x3C:0x85]
self.mii_crc = (raw[0x86] << 8) + raw[0x87]

def read_lap_splits(self, raw: list) -> list:
"""
Reads Minutes, Seconds, and Milliseconds fields from raw lap splits and parses into lists.
"""

for lap in range(5):
offset = 0x11 + lap*0x03
self.lap_split_minutes.append(raw[offset] >> 1)
self.lap_split_seconds.append(((raw[offset] & 0x01) << 6) + (raw[offset+0x01] >> 2))
self.lap_split_milliseconds.append(((raw[offset+0x01] & 0x03) << 8) + (raw[offset+0x02]))

class RKGInputDataHeader:
"""
A class representing the header of the controller input data within the RKG.
Attributes:
face_button_inputs (int): Count of A, B/R, L button inputs in RKG.
direction_inputs (int): Count of joystick inputs in RKG.
trick_inputs (int): Count of D-pad inputs in RKG.
"""

face_button_inputs: int
direction_inputs: int
trick_inputs: int

def __init__(self, raw: list):
"""
Initialises the InputDataHeader object.
"""

offset = 0x88

self.face_button_inputs = (raw[offset] << 8) + raw[offset+0x01]
self.direction_inputs = (raw[offset+0x02] << 8) + raw[offset+0x03]
self.trick_inputs = (raw[offset+0x04] << 8) + raw[offset+0x05]
128 changes: 110 additions & 18 deletions scripts/Modules/mkw_translations.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
# NOTE (xi): unfinished, currently only for the use of testing other scripts
# that rely on this module

from .mkw_classes import RaceConfig, RaceConfigScenario
from .mkw_classes import RaceConfigPlayer, RaceConfigSettings

def vehicle_id(playerIdx=0):
race_config_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario())
race_config_player = RaceConfigPlayer(addr=race_config_scenario.player(playerIdx=0))

def vehicle(vehicle_id):
"""
Translates vehicle_id to a plain vehicle name.
"""
vehicles = ["Standard Kart S", "Standard Kart M", "Standard Kart L",
"Booster Seat", "Classic Dragster", "Offroader", "Mini Beast",
"Wild Wing", "Flame Flyer", "Cheep Charger", "Super Blooper",
Expand All @@ -18,13 +15,13 @@ def vehicle_id(playerIdx=0):
"Wario Bike", "Quacker", "Zip Zip", "Shooting Star",
"Magikruiser", "Sneakster", "Spear", "Jet Bubble",
"Dolphin Dasher", "Phantom"]
return vehicles[race_config_player.vehicle_id().value]


def character_id():
race_config_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario())
race_config_player = RaceConfigPlayer(addr=race_config_scenario.player(playerIdx=0))

return vehicles[vehicle_id]

def character(character_id):
"""
Translates vehicle_id to a plain character name.
"""
characters = ["Mario", "Baby Peach", "Waluigi", "Bowser", "Baby Daisy",
"Dry Bones", "Baby Mario", "Luigi", "Toad", "Donkey Kong", "Yoshi",
"Wario", "Baby Luigi", "Toadette", "Koopa Troopa", "Daisy", "Peach",
Expand All @@ -39,14 +36,109 @@ def character_id():
"Mii Outfit A (F | Heavy)", "Mii Outfit B (M | Heavy)",
"Mii Outfit B (F | Heavy)", "Mii Outfit C (M | Heavy)",
"Mii Outfit C (F | Heavy)"]
return characters[race_config_player.character_id().value]

return characters[character_id]

def course(course_id):
"""
Translates vehicle_id to a plain course name.
"""
courses = ["Mario Circuit", "Moo Moo Meadows", "Mushroom Gorge", "Grumble Volcano",
"Toad's Factory", "Coconut Mall", "DK Summit", "Wario's Gold Mine",
"Luigi Circuit", "Daisy Circuit", "Moonview Highway", "Maple Treeway",
"Bowser's Castle", "Rainbow Road", "Dry Dry Ruins", "Koopa Cape",
"GCN Peach Beach", "GCN Mario Circuit", "GCN Waluigi Stadium", "GCN DK Mountain",
"DS Yoshi Falls", "DS Desert Hills", "DS Peach Gardens", "DS Delfino Square",
"SNES Mario Circuit 3", "SNES Ghost Valley 2", "N64 Mario Raceway", "N64 Sherbet Land",
"N64 Bowser's Castle", "N64 DK's Jungle Parkway", "GBA Bowser Castle 3", "GBA Shy Guy Beach"]
return courses[course_id]

def course_slot_abbreviation():
race_config_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario())
race_config_settings = RaceConfigSettings(addr=race_config_scenario.settings())
def course_abbreviation(course_id):
"""
Translates vehicle_id to a plain course abbreviation.
"""
courses = ["MC", "MMM", "MG", "GV", "TF", "CM", "DKS", "WGM", "LC",
"DC", "MH", "MT", "BC", "RR", "DDR", "KC", "rPB", "rMC", "rWS", "rDKM",
"rYF", "rDH", "rPG", "rDS", "rMC3", "rGV2", "rMR", "rSL", "rBC", "rDKJP",
"rBC3", "rSGB"]
return courses[race_config_settings.course_id().value]

return courses[course_id]

def controller(controller_id):
"""
Translates controller_id to a plain controller name.
"""
controllers = ["Wii Wheel", "Wii Remote + Nunchuk", "Classic Controller", "GameCube Controller"]

return controllers[controller_id]

def ghost_type(ghost_type_id):
"""
Translates ghost_type_id to a plain ghost type name.
"""
ghost_types = [0x00, "Personal Best", "World Record", "Continental Record",
"Rival Ghost", "Special Ghost", "Ghost Race", "Friend Ghost 1",
"Friend Ghost 2", "Friend Ghost 3", "Friend Ghost 4", "Friend Ghost 5",
"Friend Ghost 6", "Friend Ghost 7", "Friend Ghost 8", "Friend Ghost 9",
"Friend Ghost 10", "Friend Ghost 11", "Friend Ghost 12", "Friend Ghost 13",
"Friend Ghost 14", "Friend Ghost 15", "Friend Ghost 16", "Friend Ghost 17",
"Friend Ghost 18", "Friend Ghost 19", "Friend Ghost 20", "Friend Ghost 21",
"Friend Ghost 22", "Friend Ghost 23", "Friend Ghost 24", "Friend Ghost 25",
"Friend Ghost 26", "Friend Ghost 27", "Friend Ghost 28", "Friend Ghost 29",
"Friend Ghost 30", "Normal Staff Ghost", "Expert Staff Ghost"]

return ghost_types[ghost_type_id]

def drift_type(drift_type_id):
"""
Translates drift_type_id to a plain drift type name.
"""
drift_types = ["Manual", "Automatic"]

return drift_types[drift_type_id]

def country(country_code):
"""
Translates country_code to a plain country name.
"""
countries = [0x00, "Japan", 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
"Anguilla", "Antigua and Barbuda", "Argentina", "Aruba", "Bahamas", "Barbados", "Belize", "Bolivia",
"Brazil", "British Virgin Islands", "Canada", "Cayman Islands", "Chile", "Colombia", "Costa Rica", "Dominica",
"Dominican Republic", "Ecuador", "El Salvador", "French Guiana", "Grenada", "Guadeloupe", "Guatemala", "Guyana",
"Haiti", "Honduras", "Jamaica", "Martinique", "Mexico", "Montserrat", "Netherlands Antilles", "Nicaragua",
"Panama", "Paraguay", "Peru", "St. Kitts and Nevis", "St. Lucia", "St. Vincent and the Grenadines", "Suriname", "Trinidad and Tobago",
"Turks and Caicos Islands", "United States", "Uruguay", "US Virgin Islands", "Venezuela", 0x35, 0x36, 0x37,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
"Albania", "Australia", "Austria", "Belgium", "Bosnia and Herzegovina", "Botswana", "Bulgaria", "Croatia",
"Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece",
"Hungary", "Iceland", "Ireland", "Italy", "Latvia", "Lesotho", "Lichtenstein", "Lithuania",
"Luxembourg", "F.Y.R. of Macedonia", "Malta", "Montenegro", "Mozambique", "Namibia", "Netherlands", "New Zealand",
"Norway", "Poland", "Portugal", "Romania", "Russia", "Serbia", "Slovakia", "Slovenia",
"South Africa", "Spain", "Swaziland", "Sweden", "Switzerland", "Turkey", "United Kingdom", "Zambia",
"Zimbabwe", "Azerbaijan", "Mauritania", "Mali", "Niger", "Chad", "Sudan", "Eritrea",
"Djibouti", "Somalia", 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
"Taiwan", 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
"South Korea", 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
"Hong Kong", "Macao", 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
"Indonesia", "Singapore", "Thailand", "Phillipines", "Malaysia", 0x9B, 0x9C, 0x9F,
"China", 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
"U.A.E.", "India", "Egypt", "Oman", "Qatar", "Kuwait", "Saudi Arabia", "Syria",
"Bahrain", "Jordan", 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF]

return countries[country_code]

def subregion(state_code):
"""
Translates state_code to a plain town/state/province name.
There are over 1600 region IDs, so this isn't going to be made any time soon.
"""
pass
4 changes: 2 additions & 2 deletions scripts/Modules/ttk_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# 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()
course = mkw_translations.course_abbreviation()
text_file_paths = {
"Player": "MKW_Inputs/MKW_Player_Inputs_" + course + ".csv",
"Ghost": "MKW_Inputs/MKW_Ghost_Inputs_" + course + ".csv",
Expand All @@ -17,7 +17,7 @@ def text_file_path(path_name: str) -> str:
return text_file_paths[path_name]

# rkgs that player and ghost inputs will be written to
course = mkw_translations.course_slot_abbreviation()
course = mkw_translations.course_abbreviation()
rkg_file_path = {
"Player": "MKW_Inputs/MKW_Player_Inputs_" + course + ".rkg",
"Ghost": "MKW_Inputs/MKW_Ghost_Inputs_" + course + ".rkg"
Expand Down
7 changes: 6 additions & 1 deletion scripts/RMC/Misc. Scripts - Macros/start_slide_left.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
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.mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigPlayer, RaceConfigSettings
from Modules.framesequence import FrameSequence
import os

race_config_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario())
race_config_player = RaceConfigPlayer(addr=race_config_scenario.player(playerIdx=0))
vehicle_id = race_config_player.vehicle_id().value

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",
Expand All @@ -27,7 +32,7 @@ def on_frame_advance():

def main() -> None:
global player_inputs
player_inputs = FrameSequence(check_vehicle(translate.vehicle_id()))
player_inputs = FrameSequence(check_vehicle(translate.vehicle(vehicle_id)))

gui.add_osd_message("Startslide: {} ".format(len(player_inputs) > 0))

Expand Down
7 changes: 6 additions & 1 deletion scripts/RMC/Misc. Scripts - Macros/start_slide_right.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
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.mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigPlayer, RaceConfigSettings
from Modules.framesequence import FrameSequence
import os

race_config_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario())
race_config_player = RaceConfigPlayer(addr=race_config_scenario.player(playerIdx=0))
vehicle_id = race_config_player.vehicle_id().value

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",
Expand All @@ -27,7 +32,7 @@ def on_frame_advance():

def main() -> None:
global player_inputs
player_inputs = FrameSequence(check_vehicle(translate.vehicle_id()))
player_inputs = FrameSequence(check_vehicle(translate.vehicle(vehicle_id)))

gui.add_osd_message("Startslide: {} ".format(len(player_inputs) > 0))

Expand Down

0 comments on commit b51e1f2

Please sign in to comment.