Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/joybus/joybus.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
#include <joybus/host/n64_rumble_pak.h>
#include <joybus/target/gcn_controller.h>
#include <joybus/target/n64_controller.h>
#include <joybus/target/pixelfx_gameid.h>
73 changes: 73 additions & 0 deletions include/joybus/target/pixelfx_gameid.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* @defgroup joybus_target_pixelfx_gameid PixelFX Game ID Target
* @ingroup joybus_target
*
* Joybus target which listens for the PixelFX game ID command. N64 flash carts
* and Swiss on the GameCube send it over the controller port so that attached
* devices know which game is running. The command carries 10 bytes of game ID
* and expects no reply, so this target never sends one.
*
* The ID bytes depend on the console. An N64 sends CRC1 and CRC2 from the ROM
* header, the media format and the country code. A GameCube sends the 8-byte
* boot hash, the console ID and the country code. All zeros clears the game ID.
*
* An N64 sends the command to the first controller port only. Swiss sends it to every port.
*
* @{
*/

#pragma once

#include <stdint.h>

#include <joybus/target.h>

struct joybus_target_pixelfx_gameid;

/// Macro to cast from a generic Joybus target to a PixelFX game ID target
#define JOYBUS_TARGET_PIXELFX_GAMEID(target) ((struct joybus_target_pixelfx_gameid *)(target))

/// Size of a PixelFX game ID, in bytes
#define JOYBUS_PIXELFX_GAMEID_SIZE 10

/**
* Callback type for game ID received events.
*
* Runs in interrupt context, so it must return quickly and must not block.
* Copy or flag the ID and act on it from the main loop.
*
* @param target the target that received the game ID, with the ID in its game_id field
*/
typedef void (*joybus_target_pixelfx_gameid_received_cb)(struct joybus_target_pixelfx_gameid *target);

/**
* PixelFX game ID Joybus target.
*/
struct joybus_target_pixelfx_gameid {
/// Base target interface
struct joybus_target base;

/// The last game ID received, all zeros until one arrives
uint8_t game_id[JOYBUS_PIXELFX_GAMEID_SIZE];

/// Callback for game ID received events
joybus_target_pixelfx_gameid_received_cb on_received;
};

/**
* Initialize a PixelFX game ID target.
*
* @param target the target to initialize
*/
void joybus_target_pixelfx_gameid_init(struct joybus_target_pixelfx_gameid *target);

/**
* Set the game ID received callback for the target.
*
* @param target the target to set the callback for
* @param callback the callback function
*/
void joybus_target_pixelfx_gameid_set_received_cb(struct joybus_target_pixelfx_gameid *target,
joybus_target_pixelfx_gameid_received_cb callback);

/** @} */
1 change: 1 addition & 0 deletions libjoybus.slcc
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ source:
- path: src/target/gcn_controller.c
- path: src/target/n64_controller.c
- path: src/target/n64_rumble_pak.c
- path: src/target/pixelfx_gameid.c
48 changes: 48 additions & 0 deletions src/target/pixelfx_gameid.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <string.h>

#include <joybus/attributes.h>
#include <joybus/commands.h>
#include <joybus/errors.h>
#include <joybus/target.h>
#include <joybus/target/pixelfx_gameid.h>

JOYBUS_RAM_FUNC
static int pixelfx_gameid_byte_received(struct joybus_target *target, const uint8_t *command, uint8_t bytes_read,
joybus_target_response_cb send_response, void *user_data)
{
struct joybus_target_pixelfx_gameid *gameid = JOYBUS_TARGET_PIXELFX_GAMEID(target);

// Decline every other command
if (command[0] != JOYBUS_CMD_PIXELFX_GAMEID)
return -JOYBUS_ERR_NOT_SUPPORTED;

// Wait until the full command is received
if (bytes_read < JOYBUS_CMD_PIXELFX_GAMEID_TX)
return JOYBUS_CMD_PIXELFX_GAMEID_TX - bytes_read;

// Store the game ID and notify, the sender expects no reply
memcpy(gameid->game_id, &command[1], JOYBUS_PIXELFX_GAMEID_SIZE);
if (gameid->on_received)
gameid->on_received(gameid);

return 0;
}

static const struct joybus_target_api pixelfx_gameid_api = {
.byte_received = pixelfx_gameid_byte_received,
};

void joybus_target_pixelfx_gameid_init(struct joybus_target_pixelfx_gameid *target)
{
// Start from a clean state
memset(target, 0, sizeof(*target));

// Set the base target API implementation
target->base.api = &pixelfx_gameid_api;
}

void joybus_target_pixelfx_gameid_set_received_cb(struct joybus_target_pixelfx_gameid *target,
joybus_target_pixelfx_gameid_received_cb callback)
{
target->on_received = callback;
}
3 changes: 3 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ add_libjoybus_test(test_n64_controller target/test_n64_controller.c)

# N64 rumble pak tests
add_libjoybus_test(test_n64_rumble_pak target/test_n64_rumble_pak.c)

# PixelFX game ID target tests
add_libjoybus_test(test_pixelfx_gameid target/test_pixelfx_gameid.c)
64 changes: 64 additions & 0 deletions test/target/test_pixelfx_gameid.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include <string.h>

#include <joybus/commands.h>
#include <joybus/errors.h>
#include <joybus/target.h>
#include <joybus/target/pixelfx_gameid.h>

#include "unity.h"

#include "harness.h"

// The game ID target under test
static struct joybus_target_pixelfx_gameid gameid;

// Spy for the received callback
static int received_count;
static void on_received(struct joybus_target_pixelfx_gameid *target)
{
received_count++;
}

void setUp(void)
{
joybus_target_pixelfx_gameid_init(&gameid);
joybus_target_pixelfx_gameid_set_received_cb(&gameid, on_received);
harness_reset(JOYBUS_TARGET(&gameid));
received_count = 0;
}

void tearDown(void)
{
}

// Test that a game ID command stores the ID, fires the callback, and sends no reply
static void test_gameid_received(void)
{
// F-ZERO X, from the PixelFX README
uint8_t command[] = {JOYBUS_CMD_PIXELFX_GAMEID, 0xB3, 0x0E, 0xD9, 0x78, 0x30, 0x03, 0xC9, 0xF9, 0x43, 0x45};
TEST_ASSERT_EQUAL(0, send_command(command, sizeof(command)));

TEST_ASSERT_EQUAL_HEX8_ARRAY(&command[1], gameid.game_id, JOYBUS_PIXELFX_GAMEID_SIZE);
TEST_ASSERT_EQUAL(1, received_count);
TEST_ASSERT_EQUAL(0, response.count);
}

// Test that any other command is declined on its first byte
static void test_other_command_declined(void)
{
uint8_t command[] = {JOYBUS_CMD_IDENTIFY};
TEST_ASSERT_EQUAL(-JOYBUS_ERR_NOT_SUPPORTED, send_command(command, sizeof(command)));

TEST_ASSERT_EQUAL(0, received_count);
TEST_ASSERT_EQUAL(0, response.count);
}

int main(void)
{
UNITY_BEGIN();

RUN_TEST(test_gameid_received);
RUN_TEST(test_other_command_declined);

return UNITY_END();
}