# This file is a part of pyctr.
#
# Copyright (c) 2017-2023 Ian Burgwin
# This file is licensed under The MIT License (MIT).
# You can find the full license text in LICENSE in the root of this project.
from enum import IntEnum
from typing import TYPE_CHECKING
from .save import BlockIDNotFoundError, ConfigSaveReader, KNOWN_BLOCKS
if TYPE_CHECKING:
from typing import BinaryIO
from fs.base import FS
from ...common import FilePath
[docs]
class SystemModel(IntEnum):
Old3DS = 0
Old3DSXL = 1
New3DS = 2
Old2DS = 3
New3DSXL = 4
New2DSXL = 5
[docs]
class ConfigSaveBlockParser:
"""
Parses config blocks to provide easy access to information in the config save.
https://www.3dbrew.org/wiki/Config_Savegame
"""
def __init__(self, save: 'ConfigSaveReader'):
self.save = save
@property
def username(self) -> str:
"""
Profile username. Can be up to 10 characters long.
Block ID: 0x000A0000
"""
username_raw = self.save.get_block(0x000A0000)
username = username_raw.data.decode('utf-16le')
# sometimes there seems to be garbage after the null terminator
# so we can't just do a trim
null_term_pos = username.find('\0')
if null_term_pos >= 0:
username = username[:null_term_pos]
return username
@username.setter
def username(self, value: str):
username_raw = value.encode('utf-16le').ljust(KNOWN_BLOCKS[0x000A0000]["size"], b'\0')
self.save.set_block(0x000A0000, username_raw)
@property
def user_time_offset(self) -> int:
"""
The offset to the Raw RTC in milliseconds.
Block ID: 0x00030001
"""
time_offset_raw = self.save.get_block(0x00030001)
return int.from_bytes(time_offset_raw.data, 'little')
@user_time_offset.setter
def user_time_offset(self, value: int):
time_offset_raw = value.to_bytes(KNOWN_BLOCKS[0x00030001]["size"], "little")
self.save.set_block(0x00030001, time_offset_raw)
@property
def system_model(self) -> SystemModel:
"""
System model.
Block ID: 0x000F0004
"""
system_model_raw = self.save.get_block(0x000F0004)
return SystemModel(system_model_raw.data[0])
@system_model.setter
def system_model(self, value: 'int | SystemModel'):
# this field is actually 4 bytes
# just in case, we'll preserve the next 3 bytes (their use is unknown)
try:
system_model_raw = bytearray(self.save.get_block(0x000F0004).data)
except BlockIDNotFoundError:
system_model_raw = bytearray(4)
system_model_raw[0] = value
self.save.set_block(0x000F0004, system_model_raw)
[docs]
@classmethod
def load(cls, fp: 'BinaryIO'):
return cls(ConfigSaveReader.load(fp))
[docs]
@classmethod
def from_file(cls, fn: 'FilePath', *, fs: 'FS | None'):
return cls(ConfigSaveReader.from_file(fn, fs=fs))