Beispiel #1
0
    def srams_for_game(self, filename: str) -> List[str]:
        with self.__lock:
            # First, see if we already cached this file.
            if filename in self.__cache:
                return self.__cache[filename]

            valid_srams: List[str] = []
            with open(filename, "rb") as fp:
                # First, grab the file size, see if there are any srams at all for this file.
                data = FileBytes(fp)

                # If it's a Naomi ROM, so SRAMs must be 32kb in size.
                rom = NaomiRom(data)
                if rom.valid:
                    # Grab currently known SRAMs
                    srams: List[str] = []
                    for directory in self.__directories:
                        srams.extend(
                            os.path.join(directory, f)
                            for f in os.listdir(directory))

                    # Figure out which of these is valid for this ROM type.
                    for sram in srams:
                        try:
                            size = os.path.getsize(sram)
                        except Exception:
                            size = 0

                        if size == NaomiSettingsPatcher.SRAM_SIZE:
                            valid_srams.append(sram)

            self.__cache[filename] = valid_srams
            return valid_srams
Beispiel #2
0
    def eeprom_info(self) -> Optional[NaomiSettingsInfo]:
        # Parse the ROM header so we can narrow our search.
        naomi = self.__rom or NaomiRom(self.__data)
        self.__rom = naomi

        # Only look at main executables.
        executable = naomi.main_executable
        for sec in executable.sections:
            # Constrain the search to the section that we jump to, since that will always
            # be where our trojan is.
            if executable.entrypoint >= sec.load_address and executable.entrypoint < (
                    sec.load_address + sec.length):
                if sec.length > NaomiSettingsPatcher.MAX_TROJAN_SIZE:
                    # This can't possibly be the trojan, just skip it for speed.
                    continue
                try:
                    # Grab the old entrypoint from the existing modification since the ROM header
                    # entrypoint will be the old trojan EXE.
                    _, _, debug, date = get_config(self.__data,
                                                   start=sec.offset,
                                                   end=sec.offset + sec.length)

                    return NaomiSettingsInfo(debug, date)
                except Exception:
                    continue

        return None
Beispiel #3
0
    def has_sram(self) -> bool:
        if self.__has_sram is None:
            # Need to see if there is an attached SRAM section in this exe.
            naomi = self.__rom or NaomiRom(self.__data)
            self.__rom = naomi

            for section in naomi.main_executable.sections:
                if section.load_address == self.SRAM_LOCATION and section.length == self.SRAM_SIZE:
                    self.__has_sram = True
                    break
            else:
                self.__has_sram = False
        return self.__has_sram
Beispiel #4
0
    def get_naomi_settings(
        self,
        filename: str,
        settingsdata: Optional[bytes],
        region: NaomiRomRegionEnum = NaomiRomRegionEnum.REGION_JAPAN,
        patches: Optional[List[str]] = None,
    ) -> Tuple[Optional[NaomiSettingsWrapper], bool]:
        settings: Optional[NaomiSettingsWrapper] = None
        patches = patches or []
        with open(filename, "rb") as fp:
            data = FileBytes(fp)

            # Check to make sure its not already got an SRAM section. If it
            # does, disallow the following section from being created.
            patcher = NaomiSettingsPatcher(data, get_default_trojan())
            if patcher.rom.valid:
                # First, try to load any previously configured EEPROM.
                if settingsdata is not None:
                    if len(settingsdata) != NaomiSettingsPatcher.EEPROM_SIZE:
                        raise Exception("We don't support non-EEPROM settings!")

                    settings = self.__naomi_manager.from_eeprom(settingsdata)
                else:
                    # Second, if we didn't configure one, see if there's a previously configured
                    # one in the ROM itself.
                    settingsdata = patcher.get_eeprom()
                    if settingsdata is not None:
                        if len(settingsdata) != NaomiSettingsPatcher.EEPROM_SIZE:
                            raise Exception("We don't support non-EEPROM settings!")

                        settings = self.__naomi_manager.from_eeprom(settingsdata)
                    else:
                        # Finally, attempt to patch with any patches that fit in the first
                        # chunk, so the defaults we get below match any force settings
                        # patches we did to the header.
                        for patch in patches:
                            with open(patch, "r") as pp:
                                differences = pp.readlines()
                            differences = [d.strip() for d in differences if d.strip()]
                            try:
                                data = BinaryDiff.patch(data, differences, ignore_size_differences=True)
                            except BinaryDiffException:
                                # Patch was for something not in the header.
                                pass
                        rom = NaomiRom(data)
                        if rom.valid:
                            settings = self.__naomi_manager.from_rom(rom, region)

        return settings, settingsdata is not None
Beispiel #5
0
    def settings_for_game(self, filename: str) -> List[str]:
        with self.__lock:
            # First, see if we already cached this file.
            if filename in self.__cache:
                return self.__cache[filename]

            valid_settings: List[str] = []

            # Now, try to treat it as a Naomi ROM
            with open(filename, "rb") as fp:
                data = FileBytes(fp)
                rom = NaomiRom(data)
                if rom.valid:
                    valid_settings = sorted([os.path.join(self.__naomi_directory, f) for f, _ in self.__naomi_manager.files_for_rom(rom).items()])

            self.__cache[filename] = valid_settings
            return valid_settings
Beispiel #6
0
    def get_sram(self) -> Optional[bytes]:
        # Parse the ROM header so we can narrow our search.
        naomi = self.__rom or NaomiRom(self.__data)
        self.__rom = naomi

        # Only look at main executables.
        executable = naomi.main_executable
        for sec in executable.sections:
            # Check and see if it is a SRAM chunk.
            if sec.load_address == self.SRAM_LOCATION and sec.length == self.SRAM_SIZE:
                # It is!
                self.__has_sram = True
                return self.__data[sec.offset:(sec.offset + sec.length)]

        # Couldn't find a section that matched.
        self.__has_sram = False
        return None
Beispiel #7
0
    def put_sram(self, sram: bytes, *, verbose: bool = False) -> None:
        # First, parse the ROM we were given.
        naomi = self.__rom or NaomiRom(self.__data)

        # Now make sure the SRAM is valid.
        if len(sram) != self.SRAM_SIZE:
            raise NaomiSettingsPatcherException(
                "Invalid SRAM size to attach to a Naomi ROM!")

        # Patch the section directly onto the ROM.
        self.__has_sram = True
        self.__data = add_or_update_section(self.__data,
                                            self.SRAM_LOCATION,
                                            sram,
                                            header=naomi,
                                            verbose=verbose)

        # Also, write back the new ROM.
        self.__rom = naomi
Beispiel #8
0
    def game_name(self, filename: str, region: CabinetRegionEnum) -> str:
        with self.__lock:
            local_key = f"{region.value}-{filename}"
            if local_key in self.__names:
                return self.__names[local_key]

            # Grab enough of the header for a match
            with open(filename, "rb") as fp:
                data = fp.read(0x1000)
                length = os.fstat(fp.fileno()).st_size

            # Now, check and see if we have a checksum match
            crc = zlib.crc32(data, 0)
            checksum = f"{region.value}-{crc}-{length}"
            if checksum in self.__checksums:
                self.__names[local_key] = self.__checksums[checksum]
                return self.__names[local_key]

            # Now, see if we can figure out from the header
            rom = NaomiRom(data)
            if rom.valid:
                # Arbitrarily choose USA region as default
                naomi_region = {
                    CabinetRegionEnum.REGION_JAPAN:
                    NaomiRomRegionEnum.REGION_JAPAN,
                    CabinetRegionEnum.REGION_USA:
                    NaomiRomRegionEnum.REGION_USA,
                    CabinetRegionEnum.REGION_EXPORT:
                    NaomiRomRegionEnum.REGION_EXPORT,
                    CabinetRegionEnum.REGION_KOREA:
                    NaomiRomRegionEnum.REGION_KOREA,
                    CabinetRegionEnum.REGION_AUSTRALIA:
                    NaomiRomRegionEnum.REGION_AUSTRALIA,
                }.get(region, NaomiRomRegionEnum.REGION_JAPAN)
                self.__names[local_key] = rom.names[naomi_region]
                self.__checksums[checksum] = self.__names[local_key]
                return self.__names[local_key]

            # Finally, fall back to filename, getting rid of extensions and underscores
            self.__names[local_key] = os.path.splitext(
                os.path.basename(filename))[0].replace('_', ' ')
            self.__checksums[checksum] = self.__names[local_key]
            return self.__names[local_key]
Beispiel #9
0
    def put_eeprom(self,
                   eeprom: bytes,
                   *,
                   enable_debugging: bool = False,
                   verbose: bool = False) -> None:
        # First, parse the ROM we were given.
        naomi = self.__rom or NaomiRom(self.__data)

        # Now make sure the EEPROM is valid.
        if len(eeprom) == self.EEPROM_SIZE:
            # First, we need to modify the settings trojan with this ROM's load address and
            # the EEPROM we want to add. Make sure the EEPRom we were given is valid.
            if not NaomiEEPRom.validate(eeprom, serial=naomi.serial):
                raise NaomiSettingsPatcherException(
                    "EEPROM is incorrectly formed!")
            if naomi.serial != eeprom[3:7] or naomi.serial != eeprom[21:25]:
                raise NaomiSettingsPatcherException(
                    "EEPROM is not for this game!")

        else:
            raise NaomiSettingsPatcherException(
                "Invalid EEPROM size to attach to a Naomi ROM!")

        # Now we need to add an EXE init section to the ROM.
        if self.__trojan is None or not self.__trojan:
            raise NaomiSettingsPatcherException(
                "Cannot have an empty trojan when attaching EEPROM settings!")

        # Patch the trojan onto the ROM, updating the settings in the trojan accordingly.
        self.__data = add_or_update_trojan(
            self.__data,
            self.__trojan,
            1 if enable_debugging else 0,
            0,
            datachunk=eeprom,
            header=naomi,
            verbose=verbose,
        )

        # Also, write back the new ROM.
        self.__rom = naomi
        self.__has_eeprom = True
Beispiel #10
0
    def get_eeprom(self) -> Optional[bytes]:
        # Parse the ROM header so we can narrow our search.
        naomi = self.__rom or NaomiRom(self.__data)
        self.__rom = naomi

        # Only look at main executables.
        executable = naomi.main_executable
        for sec in executable.sections:
            # Constrain the search to the section that we jump to, since that will always
            # be where our trojan is.
            if executable.entrypoint >= sec.load_address and executable.entrypoint < (
                    sec.load_address + sec.length):
                if sec.length > NaomiSettingsPatcher.MAX_TROJAN_SIZE:
                    # This can't possibly be the trojan, just skip it for speed.
                    continue
                try:
                    # Grab the old entrypoint from the existing modification since the ROM header
                    # entrypoint will be the old trojan EXE.
                    get_config(self.__data,
                               start=sec.offset,
                               end=sec.offset + sec.length)

                    # Returns the requested EEPRom that should be written prior to the game starting.
                    for i in range(
                            sec.offset,
                            sec.offset + sec.length - (self.EEPROM_SIZE - 1)):
                        if NaomiEEPRom.validate(
                                self.__data[i:(i + self.EEPROM_SIZE)],
                                serial=naomi.serial):
                            self.__has_eeprom = True
                            return self.__data[i:(i + self.EEPROM_SIZE)]
                except Exception:
                    pass

        # Couldn't find a section that matched.
        self.__has_eeprom = False
        return None
Beispiel #11
0
    def game_name(self, filename: str, region: str) -> str:
        with self.__lock:
            if filename in self.__names:
                return self.__names[filename]

            # Grab enough of the header for a match
            with open(filename, "rb") as fp:
                data = fp.read(0x1000)
                length = os.fstat(fp.fileno()).st_size

            # Now, check and see if we have a checksum match
            crc = zlib.crc32(data, 0)
            checksum = f"{crc}-{length}"
            if checksum in self.__checksums:
                self.__names[filename] = self.__checksums[checksum]
                return self.__names[filename]

            # Now, see if we can figure out from the header
            rom = NaomiRom(data)
            if rom.valid:
                # Arbitrarily choose USA region as default
                naomi_region = {
                    'japan': NaomiRom.REGION_JAPAN,
                    'usa': NaomiRom.REGION_USA,
                    'export': NaomiRom.REGION_EXPORT,
                    'korea': NaomiRom.REGION_KOREA,
                    'australia': NaomiRom.REGION_AUSTRALIA,
                }.get(region.lower(), NaomiRom.REGION_USA)
                self.__names[filename] = rom.names[naomi_region]
                self.__checksums[checksum] = self.__names[filename]
                return self.__names[filename]

            # Finally, fall back to filename, getting rid of extensions and underscores
            self.__names[filename] = os.path.splitext(os.path.basename(filename))[0].replace('_', ' ')
            self.__checksums[checksum] = self.__names[filename]
            return self.__names[filename]
Beispiel #12
0
def main() -> int:
    # Create the argument parser
    parser = argparse.ArgumentParser(
        description="Utility for attaching a trojan to a commercial Naomi ROM.",
    )
    parser.add_argument(
        'bin',
        metavar='BIN',
        type=str,
        help='The binary file we should attach the trojan to.',
    )
    parser.add_argument(
        'exe',
        metavar='EXE',
        type=str,
        help=
        'The executable binary blob we should attach to the end of the commercial ROM.',
    )
    parser.add_argument(
        '--offset',
        type=str,
        default='0x0c021000',
        help=
        'Where to attach the springboard, in main memory. This is the hook for the trojan.',
    )
    parser.add_argument(
        '--output-file',
        metavar='BIN',
        type=str,
        help=
        'A different file to output to instead of updating the binary specified directly.',
    )

    # Grab what we're doing
    args = parser.parse_args()

    # Grab the rom, parse it
    with open(args.bin, "rb") as fp:
        data = fp.read()
    naomi = NaomiRom(data)

    # Grab the attachment. This should be an executable binary blob. The easiest way to get
    # one of these is to compile a program using the toolchain in the homebrew/ directory,
    # and then copy out the naomi.bin file from the build/ directory and use that. Note that
    # in order to use this method, you will want to change the two executable start
    # addresses in naomi.ld to 0x0D000000 or any non-relative jumps will go to the wrong
    # code. Note also that if the game uses this address, you will run into trouble. You
    # might need to code a different springboard to a safe memory region if you run into
    # trouble using 0x0D000000.
    with open(args.exe, "rb") as fp:
        exe = fp.read()

    # Grab the springboard file. This was made by compiling the following
    # SH-4 code and then extracting the executable bytes.
    """
    .section .text
    .globl start

start:
    # Jump to location 0x0D000000
    mov #13,r0
    shll16 r0
    shll8 r0
    jmp @r0
    nop
    """
    springfile = bytes(
        [0x0d, 0xe0, 0x28, 0x40, 0x18, 0x40, 0x2b, 0x40, 0x09, 0x00])

    # We need to add a EXE init section to the ROM
    executable = naomi.main_executable
    patchoffset = executable.sections[0].load_address - executable.sections[
        0].offset
    if len(executable.sections) >= 8:
        print("ROM already has the maximum number of init sections!",
              file=sys.stderr)
        return 1

    for sec in executable.sections:
        if sec.load_address == 0x0D000000:
            print("ROM already is trojan'd, cowardly giving up!",
                  file=sys.stderr)
            return 1

    # Add a new section to the end of the rom for this binary data
    executable.sections.append(
        NaomiRomSection(
            offset=len(data),
            load_address=0x0D000000,
            length=len(exe),
        ))
    naomi.main_executable = executable

    # Now, just append it to the end of the file
    newdata = change(
        naomi.data + data[naomi.HEADER_LENGTH:] + exe,
        springfile,
        int(args.offset, 16) - patchoffset,
    )

    if args.output_file:
        print(f"Added trojan to the end of {args.output_file}.",
              file=sys.stderr)
        with open(args.output_file, "wb") as fp:
            fp.write(newdata)
    else:
        print(f"Added trojan to the end of {args.bin}.", file=sys.stderr)
        with open(args.bin, "wb") as fp:
            fp.write(newdata)

    return 0
Beispiel #13
0
def main() -> int:
    # Create the argument parser
    parser = argparse.ArgumentParser(description=(
        "Utility for attaching an SRAM dump to an Atomiswave conversion Naomi ROM. "
        "Use this to set up preferred settings in an emulator, and then send those "
        "settings to your Naomi when you netboot."), )
    parser.add_argument(
        'bin',
        metavar='BIN',
        type=str,
        help='The binary file we should attach the SRAM to.',
    )
    parser.add_argument(
        'sram',
        metavar='SRAM',
        type=str,
        help='The SRAM file we should attach to the binary.',
    )
    parser.add_argument(
        '--output-file',
        metavar='BIN',
        type=str,
        help=
        'A different file to output to instead of updating the binary specified directly.',
    )

    # Grab what we're doing
    args = parser.parse_args()

    # Grab the rom, parse it
    with open(args.bin, "rb") as fp:
        data = fp.read()
    naomi = NaomiRom(data)

    # Grab the SRAM
    with open(args.sram, "rb") as fp:
        sram = fp.read()
    if len(sram) != SRAM_SIZE:
        print(f"SRAM file is not the right size, should be {SRAM_SIZE} bytes!",
              file=sys.stderr)
        return 1

    # First, find out if there's already an SRAM portion to the file
    executable = naomi.main_executable
    for section in executable.sections:
        if section.load_address == SRAM_LOCATION:
            # This is a SRAM load chunk
            if section.length != SRAM_SIZE:
                print("Found SRAM init section, but it is the wrong size!",
                      file=sys.stderr)
                return 1

            # We can just update the data to overwrite this section
            newdata = data[:section.offset] + sram + data[(section.offset +
                                                           section.length):]
            break
    else:
        # We need to add a SRAM init section to the ROM
        if len(executable.sections) >= 8:
            print("ROM already has the maximum number of init sections!",
                  file=sys.stderr)
            return 1

        # Add a new section to the end of the rom for this SRAM section
        executable.sections.append(
            NaomiRomSection(
                offset=len(data),
                load_address=SRAM_LOCATION,
                length=SRAM_SIZE,
            ))
        naomi.main_executable = executable

        # Now, just append it to the end of the file
        newdata = naomi.data + data[naomi.HEADER_LENGTH:] + sram

    if args.output_file:
        print(f"Added SRAM init to the end of {args.output_file}.",
              file=sys.stderr)
        with open(args.output_file, "wb") as fp:
            fp.write(newdata)
    else:
        print(f"Added SRAM init to the end of {args.bin}.", file=sys.stderr)
        with open(args.bin, "wb") as fp:
            fp.write(newdata)

    return 0
Beispiel #14
0
def main() -> int:
    # Create the argument parser
    parser = argparse.ArgumentParser(
        description=
        "Command-Line Utility for patching different game defaults into a Naomi ROM.",
    )
    parser.add_argument(
        'rom',
        metavar='ROM',
        type=str,
        help='The ROM we should generate a patch for.',
    )
    parser.add_argument(
        'eeprom',
        metavar='EEPROM',
        type=str,
        help='The EEPROM settings file we should use to generate the patch.',
    )
    parser.add_argument(
        '--output-file',
        metavar='BIN',
        type=str,
        default=None,
        help=
        'A different file to output to instead of updating the ROM specified directly.',
    )
    parser.add_argument(
        '--patch-file',
        metavar='PATCH',
        type=str,
        default=None,
        help='Write changed bytes to a patch instead of generating a new ROM.',
    )
    parser.add_argument(
        '--settings-directory',
        metavar='DIR',
        type=str,
        default=os.path.join(root, 'naomi', 'settings', 'definitions'),
        help=
        'The directory containing settings definition files. Defaults to %(default)s.',
    )

    # Grab what we're doing
    args = parser.parse_args()

    if args.output_file and args.patch_file:
        raise Exception("Cannot write both a patch and a new ROM!")

    # First, try to open the EEPRom file.
    with open(args.eeprom, "rb") as fp:
        eeprom = NaomiEEPRom(fp.read())
        manager = NaomiSettingsManager(args.settings_directory)
        defaults = manager.from_serial(eeprom.serial)
        defaulteeprom = NaomiEEPRom(manager.to_eeprom(defaults))

    with open(args.rom, "rb" if args.output_file else "rb+") as fp:
        data = FileBytes(cast(BinaryIO, fp))
        original = data.clone()
        rom = NaomiRom(data)

        defaultbytes = defaulteeprom.game.data
        updatedbytes = eeprom.game.data

        if len(defaultbytes) != len(updatedbytes):
            raise Exception("EEPROM sections aren't the same length!")

        for exe in [rom.main_executable, rom.test_executable]:
            for section in exe.sections:
                start = section.offset
                end = section.offset + section.length
                print(f"Searching {start} to {end}...")

                while True:
                    found = data.search(defaultbytes, start=start, end=end)

                    if found is not None:
                        print(f"Patching offset {found}!")
                        data[found:(found + len(updatedbytes))] = updatedbytes
                        start = found + 1
                    else:
                        # Done!
                        break

        if args.patch_file:
            print(
                f"Generating EEPROM settings patch and writing to {args.patch_file}."
            )
            changes = [
                "# Description: patch default game settings",
                *BinaryDiff.diff(original, data)
            ]
            with open(args.patch_file, "w") as fps:
                fps.write(os.linesep.join(changes) + os.linesep)
        else:
            if args.output_file:
                print(
                    f"Patched default game EEPROM settings to {args.output_file}."
                )
                with open(args.output_file, "wb") as fp:
                    data.write_changes(fp)
            else:
                print(f"Patched default game EEPROM settings to {args.rom}.")
                data.write_changes()

    return 0
Beispiel #15
0
def main() -> int:
    # Create the argument parser
    parser = argparse.ArgumentParser(
        description="Utility for printing information about a ROM file.", )
    parser.add_argument(
        'bin',
        metavar='BIN',
        type=str,
        help='The binary file we should generate info for.',
    )

    # Grab what we're doing
    args = parser.parse_args()

    # Grab the rom, parse it
    with open(args.bin, "rb") as fp:
        data = FileBytes(fp)

        # Create a text LUT
        region_lut: Dict[NaomiRomRegionEnum, str] = {
            NaomiRomRegionEnum.REGION_JAPAN: "Japan",
            NaomiRomRegionEnum.REGION_USA: "USA",
            NaomiRomRegionEnum.REGION_EXPORT: "Export",
            NaomiRomRegionEnum.REGION_KOREA: "Korea",
            NaomiRomRegionEnum.REGION_AUSTRALIA: "Australia",
        }

        # First, assume its a Naomi ROM
        naomi = NaomiRom(data)
        if naomi.valid:
            print("NAOMI ROM")
            print("=========")
            print(f"Publisher:       {naomi.publisher}")
            print(
                f"Japan Title:     {naomi.names[NaomiRomRegionEnum.REGION_JAPAN]}"
            )
            print(
                f"USA Title:       {naomi.names[NaomiRomRegionEnum.REGION_USA]}"
            )
            print(
                f"Export Title:    {naomi.names[NaomiRomRegionEnum.REGION_EXPORT]}"
            )
            print(
                f"Korea Title:     {naomi.names[NaomiRomRegionEnum.REGION_KOREA]}"
            )
            print(
                f"Australia Title: {naomi.names[NaomiRomRegionEnum.REGION_AUSTRALIA]}"
            )
            print(f"Publish Date:    {naomi.date}")
            print(f"Serial Number:   {naomi.serial.decode('ascii')}")
            print(f"ROM Size:        {len(data)} bytes")
            print("")

            print("Supported Configurations")
            print("------------------------")
            print(
                f"Regions:         {', '.join(region_lut[r] for r in naomi.regions)}"
            )
            print(
                f"Players:         {', '.join(str(p) for p in naomi.players)}")
            print(
                f"Monitor:         {', '.join(str(f) + 'khz' for f in naomi.frequencies)}"
            )
            print(
                f"Orientation:     {', '.join(o for o in naomi.orientations)}")
            print(f"Service Type:    {naomi.servicetype}")
            print("")

            print("Main Executable Sections")
            print("------------------------")
            for section in naomi.main_executable.sections:
                print(f"ROM Offset:      {hex(section.offset)}")
                print(f"Memory Offset:   {hex(section.load_address)}")
                print(f"Section Length:  {section.length} bytes")
                print("")
            print(f"Entrypoint:      {hex(naomi.main_executable.entrypoint)}")
            print("")

            print("Test Executable Sections")
            print("------------------------")
            for section in naomi.test_executable.sections:
                print(f"ROM Offset:      {hex(section.offset)}")
                print(f"Memory Offset:   {hex(section.load_address)}")
                print(f"Section Length:  {section.length} bytes")
                print("")
            print(f"Entrypoint:      {hex(naomi.test_executable.entrypoint)}")
            print("")

            print("Per-Region EEPROM Defaults")
            print("--------------------------")
            for region, default in naomi.defaults.items():
                print(f"{region_lut[region]}")
                if not default.apply_settings:
                    print("Override:        disabled")
                else:
                    print("Override:        enabled")
                    print(
                        f"Force vertical:  {'yes' if default.force_vertical else 'no'}"
                    )
                    print(
                        f"Force silent:    {'yes' if default.force_silent else 'no'}"
                    )
                    print(f"Chute type:      {default.chute}")
                    if default.coin_setting < 27:
                        setting = f"#{default.coin_setting}"
                    elif default.coin_setting == 27:
                        setting = "free play"
                    elif default.coin_setting == 28:
                        setting = "manual assignment"
                    print(f"Coin setting:    {setting}")
                    if default.coin_setting == 28:
                        print(f"Coin 1 rate:     {default.coin_1_rate}")
                        print(f"Coin 2 rate:     {default.coin_2_rate}")
                        print(f"Credit rate:     {default.credit_rate}")
                        print(f"Bonus:           {default.bonus}")
                    for i, text in enumerate(default.sequences):
                        print(f"Sequence {i + 1}:      {text}")
                print("")

            return 0

    # Couldn't figure out ROM type
    print("Couldn't determine ROM type!", file=sys.stderr)
    return 1
Beispiel #16
0
    def rom(self) -> NaomiRom:
        # Grab the entire rom as a parsed structure.
        naomi = self.__rom or NaomiRom(self.__data)
        self.__rom = naomi

        return naomi
Beispiel #17
0
    def serial(self) -> bytes:
        # Parse the ROM header so we can grab the game serial code.
        naomi = self.__rom or NaomiRom(self.__data)
        self.__rom = naomi

        return naomi.serial
Beispiel #18
0
def add_or_update_trojan(
    data: Union[bytes, FileBytes],
    trojan: bytes,
    debug: int,
    options: int,
    datachunk: Optional[bytes] = None,
    header: Optional[NaomiRom] = None,
    verbose: bool = False,
) -> Union[bytes, FileBytes]:
    # Note that if an external header is supplied, it will be modified to match the updated
    # data.
    if isinstance(data, FileBytes):
        data = data.clone()
    if header is None:
        header = NaomiRom(data)

    # Grab a safe-to-mutate cop of the trojan, get its current config.
    executable = header.main_executable
    exe = trojan[:]
    _, location, _, _ = get_config(exe)

    for sec in executable.sections:
        if sec.load_address == location:
            # Grab the old entrypoint from the existing modification since the ROM header
            # entrypoint will be the old trojan EXE.
            entrypoint, _, _, _ = get_config(data,
                                             start=sec.offset,
                                             end=sec.offset + sec.length)
            exe = patch_bytesequence(exe, 0xAA, struct.pack("<I", entrypoint))
            if datachunk:
                exe = patch_bytesequence(exe, 0xBB, datachunk)
            exe = patch_bytesequence(exe, 0xCF, struct.pack("<I", options))
            exe = patch_bytesequence(exe, 0xDD, struct.pack("<I", debug))

            # We can reuse this section, but first we need to get rid of the old patch.
            if sec.offset + sec.length == len(data):
                # We can just stick the new file right on top of where the old was.
                if verbose:
                    print("Overwriting old section in existing ROM section.")

                # Cut off the old section, add our new section, make sure the length is correct.
                if isinstance(data, bytes):
                    data = data[:sec.offset] + exe
                elif isinstance(data, FileBytes):
                    data.truncate(sec.offset)
                    data.append(exe)
                else:
                    raise Exception("Logic error!")
                sec.length = len(exe)
            else:
                # It is somewhere in the middle of an executable, zero it out and
                # then add this section to the end of the ROM.
                if verbose:
                    print(
                        "Zeroing out old section in existing ROM section and attaching new section to the end of the file."
                    )

                # Patch the executable with the correct settings and entrypoint.
                data = change(data, b"\0" * sec.length, sec.offset)

                # Repoint the section at the new section
                sec.offset = len(data)
                sec.length = len(exe)
                sec.load_address = location

                # Add the section to the end of the ROM.
                data = data + exe
            break
    else:
        if len(executable.sections) >= 8:
            raise NaomiSettingsPatcherException(
                "ROM already has the maximum number of init sections!")

        # Add a new section to the end of the rom for this binary data.
        if verbose:
            print(
                "Attaching section to a new ROM section at the end of the file."
            )

        executable.sections.append(
            NaomiRomSection(
                offset=len(data),
                load_address=location,
                length=len(exe),
            ))

        # Patch the executable with the correct settings and entrypoint.
        exe = patch_bytesequence(exe, 0xAA,
                                 struct.pack("<I", executable.entrypoint))
        if datachunk:
            exe = patch_bytesequence(exe, 0xBB, datachunk)
        exe = patch_bytesequence(exe, 0xCF, struct.pack("<I", options))
        exe = patch_bytesequence(exe, 0xDD, struct.pack("<I", debug))
        data = data + exe

    # Generate new header and attach executable to end of data.
    executable.entrypoint = location
    header.main_executable = executable

    # Now, return the updated ROM with the new header and appended data.
    if isinstance(data, bytes):
        return header.data + data[header.HEADER_LENGTH:]
    elif isinstance(data, FileBytes):
        data[:header.HEADER_LENGTH] = header.data
        return data
    else:
        raise Exception("Logic error!")
Beispiel #19
0
def main() -> int:
    parser = argparse.ArgumentParser(
        description=
        "Provide an on-target menu for selecting games. Currently only works with Naomi."
    )
    parser.add_argument(
        "ip",
        metavar="IP",
        type=str,
        help="The IP address that the NetDimm is configured on.",
    )
    parser.add_argument(
        "romdir",
        metavar="ROMDIR",
        type=str,
        default=os.path.join(root, 'roms'),
        help=
        'The directory of ROMs to select a game from. Defaults to %(default)s.',
    )
    parser.add_argument(
        '--region',
        metavar="REGION",
        type=str,
        default=None,
        help=
        'The region of the Naomi which we are running the menu on. Defaults to "japan".',
    )
    parser.add_argument(
        '--exe',
        metavar='EXE',
        type=str,
        default=os.path.join(root, 'homebrew', 'netbootmenu',
                             'netbootmenu.bin'),
        help=
        'The menu executable that we should send to display games on the Naomi. Defaults to %(default)s.',
    )
    parser.add_argument(
        '--menu-settings-file',
        metavar='SETTINGS',
        type=str,
        default=os.path.join(root, '.netdimm_menu_settings.yaml'),
        help=
        'The settings file we will use to store persistent settings. Defaults to %(default)s.',
    )
    parser.add_argument(
        "--patchdir",
        metavar="PATCHDIR",
        type=str,
        default=os.path.join(root, 'patches'),
        help=
        'The directory of patches we might want to apply to games. Defaults to %(default)s.',
    )
    parser.add_argument(
        '--force-analog',
        action="store_true",
        help=
        "Force-enable analog control inputs. Use this if you have no digital controls and cannot set up analog options in the test menu.",
    )
    parser.add_argument(
        '--force-players',
        type=int,
        default=0,
        help=
        "Force set the number of players for this cabinet. Valid values are 1-4. Use this if you do not want to set the player number in the system test menu.",
    )
    parser.add_argument(
        '--force-use-filenames',
        action="store_true",
        help=
        "Force-enable using filenames for ROM display instead of the name in the ROM.",
    )
    parser.add_argument(
        '--persistent',
        action="store_true",
        help=
        "Don't exit after successfully booting game. Instead, wait for power cycle and then send the menu again.",
    )
    parser.add_argument(
        '--debug-mode',
        action="store_true",
        help="Enable extra debugging information on the Naomi.",
    )
    parser.add_argument(
        '--fallback-font',
        metavar="FILE",
        type=str,
        default=None,
        help=
        "Any truetype font that should be used as a fallback if the built-in font can't render a character.",
    )
    parser.add_argument(
        '--verbose',
        action="store_true",
        help="Display verbose debugging information.",
    )

    args = parser.parse_args()
    verbose = args.verbose

    # Load the settings file
    settings = settings_load(args.menu_settings_file, args.ip)

    if args.region is not None:
        region = {
            "japan": NaomiRomRegionEnum.REGION_JAPAN,
            "usa": NaomiRomRegionEnum.REGION_USA,
            "export": NaomiRomRegionEnum.REGION_EXPORT,
            "korea": NaomiRomRegionEnum.REGION_KOREA,
        }.get(args.region, NaomiRomRegionEnum.REGION_JAPAN)
        settings.system_region = region
        settings_save(args.menu_settings_file, args.ip, settings)

    if args.force_analog:
        # Force the setting on, as a safeguard against cabinets that have no digital controls.
        settings.enable_analog = True
        settings_save(args.menu_settings_file, args.ip, settings)

    if args.force_use_filenames:
        settings.use_filenames = True
        settings_save(args.menu_settings_file, args.ip, settings)

    force_players = None
    if args.force_players is not None:
        if args.force_players >= 1 and args.force_players <= 4:
            force_players = args.force_players

    # Intentionally rebuild the menu every loop if we are in persistent mode, so that
    # changes to the ROM directory can be reflected on subsequent menu sends.
    while True:
        # First, load the rom directory, list out the contents and figure out which ones are naomi games.
        games: List[Tuple[str, str, bytes]] = []
        romdir = os.path.abspath(args.romdir)
        success: bool = True
        for filename in [
                f for f in os.listdir(romdir)
                if os.path.isfile(os.path.join(romdir, f))
        ]:
            # Grab the header so we can parse it.
            with open(os.path.join(romdir, filename), "rb") as fp:
                data = FileBytes(fp)

                if verbose:
                    print(f"Discovered file {filename}.")

                # Validate that it is a Naomi ROM.
                if len(data) < NaomiRom.HEADER_LENGTH:
                    if verbose:
                        print("Not long enough to be a ROM!")
                    continue
                rom = NaomiRom(data)
                if not rom.valid:
                    if verbose:
                        print("Not a Naomi ROM!")
                    continue

                # Get the name of the game.
                if settings.use_filenames:
                    name = os.path.splitext(filename)[0].replace("_", " ")
                else:
                    name = rom.names[settings.system_region]
                serial = rom.serial

                if verbose:
                    print(
                        f"Added {name} with serial {serial.decode('ascii')} to ROM list."
                    )

                games.append((os.path.join(romdir, filename), name, serial))

        # Alphabetize them.
        games = sorted(games, key=lambda g: g[1])

        # Now, create the settings section.
        last_game_id: int = 0
        gamesconfig = b""
        for index, (filename, name, serial) in enumerate(games):
            namebytes = name.encode('utf-8')[:127]
            while len(namebytes) < 128:
                namebytes = namebytes + b"\0"
            gamesconfig += namebytes + serial + struct.pack("<I", index)
            if filename == settings.last_game_file:
                last_game_id = index

        fallback_data = None
        if args.fallback_font is not None:
            with open(args.fallback_font, "rb") as fp:
                fallback_data = fp.read()

        config = struct.pack(
            "<IIIIIIIIBBBBBBBBBBBBIII",
            SETTINGS_SIZE,
            len(games),
            1 if settings.enable_analog else 0,
            1 if args.debug_mode else 0,
            last_game_id,
            settings.system_region.value,
            1 if settings.use_filenames else 0,
            1 if settings.disable_sound else 0,
            settings.joy1_calibration[0],
            settings.joy1_calibration[1],
            settings.joy2_calibration[0],
            settings.joy2_calibration[1],
            settings.joy1_calibration[2],
            settings.joy1_calibration[3],
            settings.joy1_calibration[4],
            settings.joy1_calibration[5],
            settings.joy2_calibration[2],
            settings.joy2_calibration[3],
            settings.joy2_calibration[4],
            settings.joy2_calibration[5],
            SETTINGS_SIZE +
            len(gamesconfig) if fallback_data is not None else 0,
            len(fallback_data) if fallback_data is not None else 0,
            force_players if (force_players is not None) else 0,
        )
        if len(config) < SETTINGS_SIZE:
            config = config + (b"\0" * (SETTINGS_SIZE - len(config)))
        config = config + gamesconfig
        if fallback_data is not None:
            config = config + fallback_data

        # Now, load up the menu ROM and append the settings to it.
        if success:
            with open(args.exe, "rb") as fp:
                menudata = add_or_update_section(FileBytes(fp),
                                                 0x0D000000,
                                                 config,
                                                 verbose=verbose)

                try:
                    # Now, connect to the net dimm, send the menu and then start communicating with it.
                    print("Connecting to net dimm...")
                    netdimm = NetDimm(args.ip, log=print if verbose else None)
                    print("Sending menu to net dimm...")
                    netdimm.send(menudata, disable_crc_check=True)
                    netdimm.reboot()
                except NetDimmException:
                    # Mark failure so we don't try to communicate below.
                    success = False

                    if args.persistent:
                        print("Sending failed, will try again...")
                    else:
                        print("Sending failed...")

        # Now, talk to the net dimm and exchange packets to handle settings and game selection.
        selected_file = None

        if success:
            print("Talking to net dimm to wait for ROM selection...")
            time.sleep(5)

            last_game_selection: Optional[int] = None
            last_game_patches: List[Tuple[str, str]] = []
            last_game_parsed_settings: Optional[NaomiSettingsWrapper] = None

            try:
                # Always show game send progress.
                netdimm = NetDimm(args.ip, log=print)
                with netdimm.connection():
                    while True:
                        msg = receive_message(netdimm, verbose=verbose)
                        if msg:
                            if msg.id == MESSAGE_SELECTION:
                                index = struct.unpack("<I", msg.data)[0]
                                filename = games[index][0]
                                print(
                                    f"Requested {games[index][1]} be loaded..."
                                )

                                # Save the menu position.
                                settings.last_game_file = filename
                                settings_save(args.menu_settings_file, args.ip,
                                              settings)

                                # Wait a second for animation on the Naomi. This assumes that the
                                # below section takes a relatively short amount of time (well below
                                # about 1 second) to patch and such. If you are on a platform with
                                # limited speed and attempting to do extra stuff such as unzipping,
                                # this can fail. It is recommended in this case to spawn off a new
                                # thread that sends a MESSAGE_UNPACK_PROGRESS with no data once a
                                # second starting directly after this time.sleep() call. When you
                                # are finished patching and ready to send, kill the thread before
                                # the MESSAGE_LOAD_PROGRESS message is sent below and then let the
                                # message send normally.
                                time.sleep(1.0)

                                # First, grab a handle to the data itself.
                                fp = open(filename, "rb")
                                gamedata = FileBytes(fp)
                                gamesettings = settings.game_settings.get(
                                    filename, GameSettings.default())

                                # Now, patch with selected patches.
                                patchman = PatchManager([args.patchdir])
                                for patchfile in gamesettings.enabled_patches:
                                    print(
                                        f"Applying patch {patchman.patch_name(patchfile)} to game..."
                                    )
                                    with open(patchfile, "r") as pp:
                                        differences = pp.readlines()
                                        differences = [
                                            d.strip() for d in differences
                                            if d.strip()
                                        ]
                                        try:
                                            gamedata = BinaryDiff.patch(
                                                gamedata, differences)
                                        except Exception as e:
                                            print(
                                                f"Could not patch {filename} with {patchfile}: {str(e)}",
                                                file=sys.stderr)

                                # Now, attach any eeprom settings.
                                if gamesettings.force_settings and gamesettings.eeprom is not None:
                                    print(
                                        f"Applying EEPROM settings to {filename}..."
                                    )
                                    patcher = NaomiSettingsPatcher(
                                        gamedata, get_default_trojan())
                                    try:
                                        patcher.put_eeprom(
                                            gamesettings.eeprom,
                                            enable_debugging=args.debug_mode,
                                            verbose=verbose,
                                        )
                                        gamedata = patcher.data
                                    except Exception as e:
                                        print(
                                            f"Could not apply EEPROM settings to {filename}: {str(e)}",
                                            file=sys.stderr)

                                # Finally, send it!
                                send_message(netdimm,
                                             Message(
                                                 MESSAGE_LOAD_PROGRESS,
                                                 struct.pack(
                                                     "<ii", len(gamedata), 0)),
                                             verbose=verbose)
                                selected_file = gamedata
                                break

                            elif msg.id == MESSAGE_LOAD_SETTINGS:
                                index = struct.unpack("<I", msg.data)[0]
                                filename = games[index][0]
                                print(
                                    f"Requested settings for {games[index][1]}..."
                                )
                                send_message(netdimm,
                                             Message(MESSAGE_LOAD_SETTINGS_ACK,
                                                     msg.data),
                                             verbose=verbose)

                                # Grab the configured settings for this game.
                                gamesettings = settings.game_settings.get(
                                    filename, GameSettings.default())
                                last_game_selection = index

                                # First, gather up the patches which might be applicable.
                                patchman = PatchManager([args.patchdir])
                                patchfiles = patchman.patches_for_game(
                                    filename)
                                patches = sorted([(p, patchman.patch_name(p))
                                                  for p in patchfiles],
                                                 key=lambda p: p[1])
                                last_game_patches = patches

                                # Grab any EEPROM settings which might be applicable.
                                parsedsettings = None
                                with open(filename, "rb") as fp:
                                    data = FileBytes(fp)

                                    eepromdata = gamesettings.eeprom
                                    if eepromdata is None:
                                        # Possibly they edited the ROM directly, still let them edit the settings.
                                        patcher = NaomiSettingsPatcher(
                                            data, get_default_trojan())
                                        if patcher.has_eeprom:
                                            eepromdata = patcher.get_eeprom()

                                    manager = NaomiSettingsManager(
                                        get_default_settings_directory())
                                    if eepromdata is None:
                                        # We need to make them up from scratch.
                                        parsedsettings = manager.from_rom(
                                            patcher.rom,
                                            region=settings.system_region)
                                    else:
                                        # We have an eeprom to edit.
                                        parsedsettings = manager.from_eeprom(
                                            eepromdata)

                                # Now, create the message back to the Naomi.
                                response = struct.pack("<IB", index,
                                                       len(patches))
                                for patch in patches:
                                    response += struct.pack(
                                        "<B", 1 if
                                        (patch[0]
                                         in gamesettings.enabled_patches) else
                                        0)
                                    patchname = patch[1].encode('utf-8')[:255]
                                    response += struct.pack(
                                        "<B", len(patchname)) + patchname

                                def make_setting(
                                        setting: Setting,
                                        setting_map: Dict[str, int]) -> bytes:
                                    if setting.read_only is True:
                                        # We don't encode this setting since its not visible.
                                        return struct.pack(
                                            "<BI", 0, setting.current
                                            or setting.default or 0)

                                    settingname = setting.name.encode(
                                        'utf-8')[:255]
                                    if len(settingname) == 0:
                                        # We can't display this setting, it has no name!
                                        return struct.pack(
                                            "<BI", 0, setting.current
                                            or setting.default or 0)

                                    settingdata = struct.pack(
                                        "<B", len(settingname)) + settingname
                                    if setting.values is not None:
                                        settingdata += struct.pack(
                                            "<I", len(setting.values))
                                        for val, label in setting.values.items(
                                        ):
                                            settingdata += struct.pack(
                                                "<I", val)
                                            valname = label.encode(
                                                'utf-8')[:255]
                                            settingdata += struct.pack(
                                                "<B", len(valname)) + valname
                                    else:
                                        settingdata += struct.pack("<I", 0)

                                    settingdata += struct.pack(
                                        "<I", setting.current
                                        or setting.default or 0)

                                    if setting.read_only is False:
                                        settingdata += struct.pack(
                                            "<i", READ_ONLY_NEVER)
                                    elif isinstance(setting.read_only,
                                                    ReadOnlyCondition):
                                        settingdata += struct.pack(
                                            "<iII", setting_map[
                                                setting.read_only.name], 1
                                            if setting.read_only.negate else 0,
                                            len(setting.read_only.values))
                                        for val in setting.read_only.values:
                                            settingdata += struct.pack(
                                                "<I", val)
                                    else:
                                        raise Exception("Logic error!")

                                    return settingdata

                                if parsedsettings is not None:
                                    # Remember the settings we parsed so we can save them later.
                                    last_game_parsed_settings = parsedsettings

                                    # Now add data for the force settings toggle.
                                    totalsettings = len(
                                        parsedsettings.system.settings) + len(
                                            parsedsettings.game.settings)
                                    response += struct.pack(
                                        "<B", 1 if
                                        (totalsettings > 0
                                         and gamesettings.force_settings) else
                                        0)

                                    # Construct system settings.
                                    response += struct.pack(
                                        "<B",
                                        len(parsedsettings.system.settings))
                                    for setting in parsedsettings.system.settings:
                                        response += make_setting(
                                            setting, {
                                                s.name: i
                                                for (i, s) in
                                                enumerate(parsedsettings.
                                                          system.settings)
                                            })

                                    # Construct game settings
                                    response += struct.pack(
                                        "<B",
                                        len(parsedsettings.game.settings))
                                    for setting in parsedsettings.game.settings:
                                        response += make_setting(
                                            setting, {
                                                s.name: i
                                                for (i, s) in
                                                enumerate(parsedsettings.game.
                                                          settings)
                                            })
                                else:
                                    # This game has a SRAM chunk attached (atomiswave game), don't try to send settings.
                                    response += struct.pack("<BBB", 0, 0, 0)

                                # Send settings over.
                                send_message(netdimm,
                                             Message(
                                                 MESSAGE_LOAD_SETTINGS_DATA,
                                                 response),
                                             verbose=verbose)

                            elif msg.id == MESSAGE_SAVE_SETTINGS_DATA:
                                index, patchlen = struct.unpack(
                                    "<IB", msg.data[0:5])
                                msgdata = msg.data[5:]

                                if index == last_game_selection:
                                    filename = games[index][0]
                                    gamesettings = settings.game_settings.get(
                                        filename, GameSettings.default())
                                    last_game_selection = None

                                    print(
                                        f"Received updated settings for {games[index][1]}..."
                                    )

                                    # Grab the updated patches.
                                    if patchlen > 0:
                                        patches_enabled = list(
                                            struct.unpack(
                                                "<" + ("B" * patchlen),
                                                msgdata[0:(1 * patchlen)]))
                                        msgdata = msgdata[(1 * patchlen):]

                                        if patchlen == len(last_game_patches):
                                            new_patches: Set[str] = set()
                                            for i in range(patchlen):
                                                if patches_enabled[i] != 0:
                                                    new_patches.add(
                                                        last_game_patches[i]
                                                        [0])
                                            gamesettings.enabled_patches = new_patches
                                    last_game_patches = []

                                    # Grab system settings.
                                    force_settings, settinglen = struct.unpack(
                                        "<BB", msgdata[0:2])
                                    msgdata = msgdata[2:]

                                    if settinglen > 0:
                                        settings_values = list(
                                            struct.unpack(
                                                "<" + ("I" * settinglen),
                                                msgdata[0:(4 * settinglen)]))
                                        msgdata = msgdata[(4 * settinglen):]

                                        if last_game_parsed_settings is not None:
                                            if len(settings_values) == len(
                                                    last_game_parsed_settings.
                                                    system.settings):
                                                for i, setting in enumerate(
                                                        last_game_parsed_settings
                                                        .system.settings):
                                                    setting.current = settings_values[
                                                        i]

                                    # Grab game settings.
                                    settinglen = struct.unpack(
                                        "<B", msgdata[0:1])[0]
                                    msgdata = msgdata[1:]

                                    if settinglen > 0:
                                        settings_values = list(
                                            struct.unpack(
                                                "<" + ("I" * settinglen),
                                                msgdata[0:(4 * settinglen)]))
                                        msgdata = msgdata[(4 * settinglen):]

                                        if last_game_parsed_settings is not None:
                                            if len(settings_values) == len(
                                                    last_game_parsed_settings.
                                                    game.settings):
                                                for i, setting in enumerate(
                                                        last_game_parsed_settings
                                                        .game.settings):
                                                    setting.current = settings_values[
                                                        i]

                                    if last_game_parsed_settings is not None:
                                        manager = NaomiSettingsManager(
                                            get_default_settings_directory())
                                        gamesettings.eeprom = manager.to_eeprom(
                                            last_game_parsed_settings)
                                        gamesettings.force_settings = force_settings != 0
                                    else:
                                        gamesettings.force_settings = False

                                    last_game_parsed_settings = None

                                    # Save the final updates.
                                    settings.game_settings[
                                        filename] = gamesettings
                                    settings_save(args.menu_settings_file,
                                                  args.ip, settings)
                                send_message(
                                    netdimm,
                                    Message(MESSAGE_SAVE_SETTINGS_ACK),
                                    verbose=verbose)

                            elif msg.id == MESSAGE_SAVE_CONFIG:
                                if len(msg.data) == SETTINGS_SIZE:
                                    (
                                        _,
                                        _,
                                        analogsetting,
                                        _,
                                        _,
                                        regionsetting,
                                        filenamesetting,
                                        soundsetting,
                                        *rest,
                                    ) = struct.unpack("<IIIIIIIIBBBBBBBBBBBB",
                                                      msg.data[:44])
                                    print("Requested configuration save...")

                                    joy1 = [
                                        rest[0], rest[1], rest[4], rest[5],
                                        rest[6], rest[7]
                                    ]
                                    joy2 = [
                                        rest[2], rest[3], rest[8], rest[9],
                                        rest[10], rest[11]
                                    ]

                                    settings.enable_analog = analogsetting != 0
                                    settings.use_filenames = filenamesetting != 0
                                    settings.system_region = NaomiRomRegionEnum(
                                        regionsetting)
                                    settings.disable_sound = soundsetting != 0
                                    settings.joy1_calibration = joy1
                                    settings.joy2_calibration = joy2
                                    settings_save(args.menu_settings_file,
                                                  args.ip, settings)

                                    send_message(
                                        netdimm,
                                        Message(MESSAGE_SAVE_CONFIG_ACK),
                                        verbose=verbose)
                            elif msg.id == MESSAGE_HOST_STDOUT:
                                print(msg.data.decode('utf-8'), end="")
                            elif msg.id == MESSAGE_HOST_STDERR:
                                print(msg.data.decode('utf-8'),
                                      end="",
                                      file=sys.stderr)

            except NetDimmException:
                # Mark failure so we don't try to wait for power down below.
                success = False

                if args.persistent:
                    print("Communicating failed, will try again...")
                else:
                    print("Communicating failed...")

        if success and selected_file is not None:
            try:
                # Always show game send progress.
                netdimm = NetDimm(args.ip, log=print)

                # Only want to send so many progress packets.
                old_percent = -1
                old_time = time.time()

                def progress_callback(loc: int, size: int) -> None:
                    nonlocal old_percent
                    nonlocal old_time

                    new_percent = int((loc / size) * 100)
                    new_time = time.time()
                    if new_percent != old_percent or (new_time -
                                                      old_time) > 2.0:
                        write_scratch1_register(netdimm, loc)
                        old_percent = new_percent
                        old_time = new_time

                # Finally, send it!.
                netdimm.send(selected_file,
                             disable_crc_check=True,
                             disable_now_loading=True,
                             progress_callback=progress_callback)
                netdimm.reboot()

                # And clean up.
                selected_file.handle.close()
                selected_file = None
            except NetDimmException as e:
                # Mark failure so we don't try to wait for power down below.
                print(str(e))
                success = False

                if args.persistent:
                    print("Sending game failed, will try again...")
                else:
                    print("Sending game failed...")

        if args.persistent:
            if success:
                # Wait for cabinet to disappear again before we start the process over.
                print(
                    "Waiting for cabinet to be power cycled to resend menu...")
                failure_count: int = 0
                on_windows: bool = platform.system() == "Windows"

                while True:
                    # Constantly ping the net dimm to see if it is still alive.
                    with open(os.devnull, 'w') as DEVNULL:
                        try:
                            if on_windows:
                                call = ["ping", "-n", "1", "-w", "1", args.ip]
                            else:
                                call = ["ping", "-c1", "-W1", args.ip]
                            subprocess.check_call(call,
                                                  stdout=DEVNULL,
                                                  stderr=DEVNULL)
                            alive = True
                        except subprocess.CalledProcessError:
                            alive = False

                    # We start with the understanding that the host is up, but if we
                    # miss a ping its not that big of a deal. We just want to know that
                    # we missed multiple pings as that tells us the host is truly gone.
                    if alive:
                        failure_count = 0
                    else:
                        failure_count += 1
                        if failure_count >= 5:
                            # We failed 5 pings in a row, so let's assume the host is
                            # dead.
                            break

                    time.sleep(2.0 if failure_count == 0 else 1.0)

            # Now, wait for the cabinet to come back so we can send the menu again.
            print("Waiting for cabinet to be ready to receive the menu...")
            while True:
                try:
                    netdimm = NetDimm(args.ip, log=print if verbose else None)
                    info = netdimm.info()
                    if info is not None:
                        break
                except NetDimmException:
                    # Failed to talk to the net dimm, its still down.
                    pass
        else:
            # We sent the game, now exit!
            break

    return 0
Beispiel #20
0
def main() -> int:
    # Create the argument parser
    parser = argparse.ArgumentParser(
        description=
        "Utility for creating a Naomi ROM out of various parameters and sections.",
    )
    parser.add_argument(
        'bin',
        metavar='BIN',
        type=str,
        help='The binary file we should generate.',
    )
    parser.add_argument(
        '-s',
        '--serial',
        type=str,
        default=None,
        help='Three digit ascii serial code for this game.',
    )
    parser.add_argument(
        '-p',
        '--publisher',
        type=str,
        default=None,
        help='The publisher of this ROM.',
    )
    parser.add_argument(
        '-t',
        '--title',
        type=str,
        default=None,
        help=
        ('The name of this ROM. Use this instead of individual ROM region titles '
         'to globally set the name of the ROM.'),
    )
    parser.add_argument(
        '--title.japan',
        dest="title_japan",
        type=str,
        default=None,
        help='The name of this ROM in Japanese region.',
    )
    parser.add_argument(
        '--title.usa',
        dest="title_usa",
        type=str,
        default=None,
        help='The name of this ROM in USA region.',
    )
    parser.add_argument(
        '--title.export',
        dest="title_export",
        type=str,
        default=None,
        help='The name of this ROM in Export region.',
    )
    parser.add_argument(
        '--title.korea',
        dest="title_korea",
        type=str,
        default=None,
        help='The name of this ROM in Korea region.',
    )
    parser.add_argument(
        '--title.australia',
        dest="title_australia",
        type=str,
        default=None,
        help='The name of this ROM in Australia region.',
    )
    parser.add_argument(
        '-d',
        '--date',
        type=str,
        default=None,
        help='The date this ROM was created, in the form YYYY-MM-DD',
    )
    parser.add_argument(
        '-e',
        '--entrypoint',
        type=str,
        required=True,
        help=
        'The executable entrypoint in main RAM after loading all sections.',
    )
    parser.add_argument(
        '-c',
        '--section',
        type=str,
        action='append',
        help=
        ('An executable section that will be loaded into RAM. Must be specified '
         'in the form of /path/to/file,0x12345678 where the hexidecimal number is '
         'the load offset in main RAM.'))
    parser.add_argument(
        '-n',
        '--test-entrypoint',
        type=str,
        required=True,
        help='The test mode entrypoint in main RAM after loading all sections.',
    )
    parser.add_argument(
        '-i',
        '--test-section',
        type=str,
        action='append',
        help=
        ('A test mode section that will be loaded into RAM. Must be specified '
         'in the form of /path/to/file,0x12345678 where the hexidecimal number is '
         'the load offset in main RAM.'))
    parser.add_argument(
        '-b',
        '--pad-before-data',
        type=str,
        default=None,
        help=
        'Pad the ROM to this size in hex before appending any arbitrary data.',
    )
    parser.add_argument(
        '-a',
        '--pad-after-data',
        type=str,
        default=None,
        help=
        'Pad the ROM to this size in hex after appending any arbitrary data.',
    )
    parser.add_argument(
        '-f',
        '--filedata',
        metavar="FILE",
        type=str,
        action='append',
        help='Append this arbitrary file data to the end of the ROM.',
    )

    # Grab what we're doing
    args = parser.parse_args()

    # Create the ROM header itself
    header = NaomiRom.default()

    # Start by attaching basic info
    header.publisher = args.publisher or "NOBODY"
    title = [
        args.title or "NO TITLE",
        args.title or "NO TITLE",
        args.title or "NO TITLE",
        args.title or "NO TITLE",
        args.title or "NO TITLE",
    ]
    if args.title_japan:
        title[header.REGION_JAPAN] = args.title_japan
    if args.title_usa:
        title[header.REGION_JAPAN] = args.title_usa
    if args.title_export:
        title[header.REGION_JAPAN] = args.title_export
    if args.title_korea:
        title[header.REGION_JAPAN] = args.title_korea
    if args.title_australia:
        title[header.REGION_JAPAN] = args.title_australia
    header.names = title

    # I am not sure whether anyone will want to overwrite these
    header.sequencetexts = [
        "CREDIT TO START",
        "CREDIT TO CONTINUE",
    ]
    header.regions = [
        header.REGION_JAPAN,
        header.REGION_USA,
        header.REGION_EXPORT,
        header.REGION_KOREA,
        header.REGION_AUSTRALIA,
    ]

    if args.date is not None:
        year, month, day = args.date.split('-')
        header.date = datetime.date(int(year), int(month), int(day))
    else:
        header.date = datetime.date.today()

    if args.serial is not None:
        if len(args.serial) != 3:
            raise Exception("Serial must be 3 ascii digits!")
        header.serial = b"B" + args.serial.encode('ascii')
    else:
        header.serial = b"B999"

    # TODO: Command line args for setting supported number of players,
    # screen orientation, coin service type, supported monitor frequencies,
    # and defaults for various EEPROM settings such as free-play.

    # Construct executable and test loader sections.
    main_sections: List[NaomiRomSection] = []
    test_sections: List[NaomiRomSection] = []
    romoffset = len(header.data)
    romdata = b''

    # Calculate locations for appended executable chunks.
    for file_and_offset in args.section or []:
        filename, offset = file_and_offset.rsplit(',', 1)
        with open(filename, "rb") as fpb:
            sectiondata = fpb.read()

        main_sections.append(
            NaomiRomSection(
                offset=romoffset,
                length=len(sectiondata),
                load_address=int(offset, 16),
            ))
        romoffset += len(sectiondata)
        romdata += sectiondata

    # Calculate locations for appended test mode chunks.
    for file_and_offset in args.test_section or []:
        filename, offset = file_and_offset.rsplit(',', 1)
        with open(filename, "rb") as fpb:
            sectiondata = fpb.read()

        test_sections.append(
            NaomiRomSection(
                offset=romoffset,
                length=len(sectiondata),
                load_address=int(offset, 16),
            ))
        romoffset += len(sectiondata)
        romdata += sectiondata

    # Create the executable itself.
    header.main_executable = NaomiExecutable(sections=main_sections,
                                             entrypoint=int(
                                                 args.entrypoint, 16))
    header.test_executable = NaomiExecutable(sections=test_sections,
                                             entrypoint=int(
                                                 args.test_entrypoint, 16))

    # Now, pad the ROM out to any requested padding.
    if args.pad_before_data and romoffset < int(args.pad_before_data, 16):
        amount = int(args.pad_before_data, 16) - romoffset
        romdata += b'\0' * amount
        romoffset += amount

    # Now, append any requested data chunks.
    for filename in args.filedata or []:
        with open(filename, "rb") as fpb:
            filedata = fpb.read()
        romoffset += len(filedata)
        romdata += filedata

    # Now, pad the ROM out to any requested padding.
    if args.pad_after_data and romoffset < int(args.pad_after_data, 16):
        amount = int(args.pad_after_data, 16) - romoffset
        romdata += b'\0' * amount
        romoffset += amount

    # Finally, write out the pieces
    with open(args.bin, "wb") as fpb:
        fpb.write(header.data)
        fpb.write(romdata)

    return 0
Beispiel #21
0
def add_or_update_section(
    data: Union[bytes, FileBytes],
    location: int,
    newsection: bytes,
    header: Optional[NaomiRom] = None,
    verbose: bool = False,
) -> Union[bytes, FileBytes]:
    # Note that if an external header is supplied, it will be modified to match the updated
    # data.
    if isinstance(data, FileBytes):
        data = data.clone()
    if header is None:
        header = NaomiRom(data)

    # First, find out if there's already a section in this header.
    executable = header.main_executable
    for section in executable.sections:
        if section.load_address == location:
            # This is a section chunk
            if section.length != len(newsection):
                raise NaomiSettingsPatcherException(
                    "Found section in executable, but it is the wrong size!")

            if verbose:
                print("Overwriting old section in existing ROM section.")

            # We can just update the data to overwrite this section
            if isinstance(data, bytes):
                data = data[:section.offset] + newsection + data[
                    (section.offset + section.length):]
            elif isinstance(data, FileBytes):
                data[section.offset:(section.offset +
                                     section.length)] = newsection
            else:
                raise Exception("Logic error!")
            break
    else:
        # We need to add an init section to the ROM
        if len(executable.sections) >= 8:
            raise NaomiSettingsPatcherException(
                "ROM already has the maximum number of sections!")

        # Add a new section to the end of the rom for this binary data.
        if verbose:
            print(
                "Attaching section to a new ROM section at the end of the file."
            )

        # Add a new section to the end of the rom for this section
        executable.sections.append(
            NaomiRomSection(offset=len(data),
                            load_address=location,
                            length=len(newsection)))
        header.main_executable = executable

        # Now, just append it to the end of the file
        if isinstance(data, bytes):
            data = header.data + data[header.HEADER_LENGTH:] + newsection
        elif isinstance(data, FileBytes):
            data[:header.HEADER_LENGTH] = header.data
            data.append(newsection)
        else:
            raise Exception("Logic error!")

    # Return the updated data.
    return data