Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement EEPROM Filesystem and test ROM #125

Merged
merged 29 commits into from Jun 28, 2021
Merged

Conversation

meeq
Copy link
Contributor

@meeq meeq commented Feb 24, 2021

EEPROM allows up to either 512 bytes or 2048 bytes to be stored, retrieved, and persisted across resets and power-offs.

EEPROM FS allows homebrew to list out distinct "files" that map to one or more blocks of EEPROM and read or write to these files.

The impetus and proof-of-concept for this was porting a PC game that used stdio calls. With the EEPROM filesystem, all I had to do was configure the game’s save path to eeprom://Profile.dat and the game was able to read and write save data without making any changes to the original file handling code.

This is intended to be a companion to DragonFS that allows games to easily save data such as structs and arrays using a friendly file-like interface. Just like DragonFS, EEPROM FS can be used either with POSIX functions through a path prefix (e.g. eeprom:/) or using the eepfs_* functions.

Originally, this PR included stdio hooks to access EEPROM files, but this turned out to be a leaky abstraction that had some rough edge-cases. To simplify the implementation, EEPROM FS currently only supports reading/writing/erasing entire files using the eepfs_* functions.

Due to the limitations of the storage medium, there are a few considerations to keep in mind:

  • All file names and file sizes must be declared before initialization
    • It is not possible to add or remove files or change their size
    • All files will always exist at their declared size
    • When a file is "erased", all blocks are zeroed out
  • Files must start on a block boundary
    • In practice, this limits the number of files to the EEPROM's block capacity minus 1 (the first block of EEPROM is reserved by EEPROM FS)
    • Files that are not aligned to the EEPROM block size (8-bytes) will cause the unaligned bytes to be wasted as padding.
  • Changes made by you (the homebrew developer) between builds/versions of your homebrew may require EEPROM data to be wiped or migrated.
    • EEPROM FS reserves the first block of EEPROM for a "signature" which is used to check if the configured filesystem is roughly compatible with the saved data. This means that if the number of files or the total file size of your configured filesystem changes then the data in EEPROM may no longer line up with the new filesystem and needs to be erased (easy) or migrated (non-trivial).
    • It is possible (but not recommended) to skip this signature check, but the data read through EEPROM FS may be nonsensical.

See examples/eepromfstest for some contrived examples of storing and loading data on both 4k (64-block) and 16k (256-block) EEPROM.

The example detects which EEPROM (if any) is available, offers to wipe the existing data. and then attempts to run through reading, then writing, then reading back again with three different data structures:

  • A list of "high scores" (an array of structs)
  • A simple "global settings" struct
  • A "saved game state" struct

For the 16k EEPROM, the saved game state struct is used to demonstrate how multiple save "slots" could be accomplished.

Note that cen64 is currently hard-coded to report a 4k EEPROM even if it is not specified on the command-line, but will fail the read/write tests unless you explicitly set the save type with -eep4k or -eep16k and a path the save the EEPROM data.

This is intended to be a companion to DragonFS that allows games to
easily save data such as structs and arrays using a friendly file-like
interface. Just like DragonFS, EEPROM FS can be used either with POSIX
functions through a path prefix (e.g. `eeprom:/`) or using the `eepfs_*`
functions.

EEPROM allows up to either 512 bytes or 2048 bytes to be stored,
retrieved, and persisted across resets and power-offs. EEPROM FS allows
homebrew to list out distinct "files" that map to one or more blocks of
EEPROM and read or write to these files. Due to the limitations of the
storage medium, there are a few limitations to keep in mind:

* All file names and file sizes must be declared before initialization
* It is not possible to add or remove files or change their size
  * All files will always exist at their declared size
  * When a file is "erased", all blocks are zeroed out
* Files must start on a block boundary
  * In practice, this limits the number of files to the EEPROM's block
    capacity - 1 (the first block of EEPROM is reserved by EEPROM FS)
  * Files that are not aligned to the EEPROM block size (8-bytes) will
    cause the unaligned bytes to be wasted as padding.
* Changes made by you (the homebrew developer) between builds/versions
  of your homebrew may require EEPROM data to be wiped or migrated.
  * EEPROM FS reserves the first block of EEPROM for a "signature"
    which is used to check if the configured filesystem is roughly
    compatible with the saved data. This means that if the number of
    files or the total file size of your configured filesystem changes
    then the data in EEPROM may no longer line up with the new
    filesystem and needs to be erased (easy) or migrated (non-trivial).
  * It is possible (but not recommended) to disable this signature
    check, but the data read through EEPROM FS may be nonsensical.

See `examples/eepromfstest` for some contrived examples of storing and
loading data on both 4k (64-block) and 16k (256-block) EEPROM.

The example detects which EEPROM (if any) is available, offers to wipe
the existing data. and then attempts to run through reading, then
writing, then reading back again with three different data structures:

* A list of "high scores" (an array of structs)
* A simple "global settings" struct
* A "saved game state" struct

For the 16k EEPROM, the saved game state struct is used to demonstrate
how multiple save "slots" could be accomplished.

Note that cen64 is currently hard-coded to report a 4k EEPROM even if
it is not specified on the command-line, but will fail the read/write
tests unless you explicitly set the save type with `-eep4k` or `-eep16k`
and a path the save the EEPROM data:

https://github.com/n64dev/cen64/blob/89e47d2968af2227017e52008d6c893e58b300c5/si/controller.c#L124-L132
Thanks for your feedback in Discord, @rasky
@meeq
Copy link
Contributor Author

meeq commented Feb 28, 2021

Updated test ROM based on abfbe0a: eepromfstest.zip

Copy link
Collaborator

@rasky rasky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've done a first round of general architecture review (following our Discord discussion last night). I haven't actually reviewed all the function implementations yet, I'm still focusing on the API design and overall structure.

I would say the other main issue I see with this API (which is not your fault at all) is that it provides synchronous blocking access to EEPROM. Reading/writing the EEPROM is very slow (~15ms per block), and doing it while blocking the CPU means that the game would not be allowed to perform audio / video operations in background as the EEPROM is accessed. This is actually quite common on some consoles (eg: Playstation 1), but nonetheless is a problem.

libdragon doesn't support threading and most APIs are blocking, so I can see that we're not providing any foundation to solve this problem. I'm actually going to submit soon a very simple async / future / completion "library", which is a small set of helper functions to provide a unified API for asynchronous operations. This is how it would work for this library:

    async_t op = async_new(my_callback, NULL);
    eepfs_write("scores.dat", &scores, &op);

or if you want to block later:

    async_t op = async_new(NULL, NULL);
    eepfs_write("scores.dat", &scores, &op);
    [...]
    while (!async_is_finished(&op)) {}

or even easier:

    eepfs_write("scores.dat", &scores, ASYNC_BLOCK);

To support this, internally eepfs (and controller.c in the first place) would have to refactored to use chained interrupts to perform operations.

I'm just mentioning this as a general comment, I'm sure this PR can go in the way it is right now, with the only gotcha that we might have to change the API down the road.

};
eepfs_config_t eeprom_16k_config = {
.files = eeprom_16k_files,
.num_files = EEPFS_NUM_FILES(eeprom_16k_files),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than storing a separate counter of elements, another option is to have the files array to be terminated with a NULL entry. This might be easier to use for user code.

Otherwise, I'm not sure I like this macro. This macro is typically called countof() or ARRAY_SIZE, and unfortunately it's not part of standard C yet. Anyway, I'd let users figure it out by themselves, rather than providing a EEPFS_NUM_FILES macro which seems that contains something specific of eepfs. I think the above code is more clear if you just put a straight 6 here. The macro made me wonder what was hidden behind it (turns out, nothing).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EEPFS_NUM_FILES macro has been removed.

I considered the NULL sentinel value idea, but could not come up with a simple and practical way to ensure the array is properly terminated.

eepfs_config_t eeprom_16k_config = {
.files = eeprom_16k_files,
.num_files = EEPFS_NUM_FILES(eeprom_16k_files),
.stdio_prefix = "eeprom:/",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with having this configuration here is that the whole stdio filesystem code is always linked to the binary, irrespective of game code actually using it or not.

I suggest you move this functionality into a separate eepfs_mount() function. This way, if the game code only calls eepfs_read/write, the stdio functionality will not be linked in the final binary.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The game code will highly likely use the dfs already and link against stdio. On the other hand, the devs might want to develop a custom filesystem for their needs and may also want to simply use the eeprom and they will have to ship it with stdio even though they don't need dfs. So I agree keeping the binding out of eepfs_init is a good idea.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was referring to the stdio-related code in eepromfs. Might be small but everything counts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attaching the filesystem to stdio is now done through a separate eepfs_attach function

* @see #eepfs_check_signature
*/
const bool skip_signature_check;
} eepfs_config_t;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these functions declare their members as const. This makes the structures more complex to handle for user code (they need to go through the hoops to initialize them) and I'm not sure what kind of guarantee or safety is providing. I think it's enough to document that the config structure should not be modified after eepfs_init() is called (which, if you ask me, is already clear when you first read this header file).

Also, if you agree on moving out stdio_prefix and removing num_files (using the NULL member as sentinel), this structure just contains files and skip_signature_check. At which point, I would just pass those two arguments to eepfs_init(). I would expect most user code to declare their eepfs_entry_t array as a global const variable and then use it. I think this simplifies the overall design.

Copy link
Contributor Author

@meeq meeq Feb 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding const everywhere:

No objection to removing const from the public struct declarations. My heuristic is to const everything unless there's a good reason not to, but you're right that it's unnecessary and potentially annoying here.

Regarding stdio_prefix:

No objection to removing this argument and moving it to its own function.

Regarding num_files:

I think that a NULL member as a sentinel is potentially more confusing and error-prone than requiring an explicit length.

I cannot think of a good way of enforcing the presence of the sentinel, and its absence would lead to very unexpected results.

Typically C APIs that accept an array expect callers to also provide a length; the big exception is strings, which have a long and sordid history of issues related to the accidental or intentional omission of \0.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eepfs_config_t has been removed.

The option to disable the signature check has been removed to simplify the API. Upon further reflection, there is no good reason to skip the signature verification. If the signature does not match, it's likely that the filesystem will return unexpected garbage data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As part of integrating this PR back into the PC game port that originally spawned the idea for EEPROM FS, I have encountered an objection to removing the const qualifier on eepfs_entry_t's path member:

ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]

In order to make eepfs_config_t compatible with C++, path needs to be const char *

include/eepromfs.h Outdated Show resolved Hide resolved
include/eepromfs.h Show resolved Hide resolved
include/eepromfs.h Outdated Show resolved Hide resolved
include/eepromfs.h Outdated Show resolved Hide resolved
src/eepromfs.c Outdated
*
* @return The actual number of bytes read or a negative value on failure.
*/
static int eepfs_cursor_read(eepfs_file_t * const file, void * const dest, const size_t len)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in general you may want to keep a cached 8-byte block in eepfs_file_t and only read/write it when it's complete (and / or the user seeks away).

This is not a real problem if you use the eepfs_read / eepfs_write functions where everything is read/written in a single shot, but it may become critical for people using the filesystem API. The EEPROM is extremely slow to access (~15ms per block), and can become worn. The stdio API is normally buffered, so people will assume that it's OK to, eg., read 1 byte at a time. Instead, without an internal buffering of a block, this will be extremely slow (and writing 1 byte at a time would reduce the EEPROM life).

I think that if we provide a filesystem API, the onus is on us to provide a buffering layer that enforces correct practices.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think of it: maybe fopen in newlib already provides a buffering layer? I'm not sure how that interacts with eepfs and I'm sure it's not designed around the concept of underlying fixed size blocks, but maybe it's something to take into account.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked newlib sources and it looks like newlib by default adds a 1Kb buffer to all stdio files. I'm not sure on how that interacts with seeking for instance, but for simple cases it looks like this is useful for us because it means that we would probably get a single read/write call for the whole file as soon as the buffer is flushed (at fclose time, or in case of explicit fflush -- and then I don't know if seeking also causes a flush). This is something I'd love to be investigated and documented though.

src/eepromfs.c Outdated
block_byte_offset++;
}
/* Write the block and move on to the next one */
eeprom_write(current_block, eeprom_buf);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the above comment on caching the current block, I would defer actual writing to when the user has issued a write commond on a different block, so that each block is normally written only once.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we may also need a flush function to gracefully write pending writes in order not to loose information IMO.

sig_bytes[5] = num_files;
sig_bytes[6] = total_bytes >> 8;
sig_bytes[7] = total_bytes & 0xFF;
return signature;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This signature is checking for consistent filesystem structure, and I totally see its value.

Another common issue with EEPROM access (not only in N64 world) is that it's basically required to have some sort of consistency checking on actual data being read/written because those things wear out and consistency is never absolutely guaranteed. I think Nintendo was also advising developers to absolutely keep internal checksums in EEPROM data.

This can be done in a followup PR if you want, as it's an additional feature at the end of the day. I'm not sure how it would affect the API though. Some considerations:

  • Reading a 8-byte block of the EEPROM takes ~15ms. This is very slow. Reading a full 128K EEPROM would require almost 4 seconds, so it doesn't sound like validating the content is something that can be done at init time.
  • If each file had a 16bit CRC associated with it (either appended at the start/end of actual data, or in a separate table to be kept in the EEPROM), it would be easy to read/update it when eepfs_read / eepfs_write are called. That is a good point to do it because the code is already going through all the content bytes.
  • For the stdio filesystem, it's less clear how this would work. Given the stdio API, users might read / write a file only partially, seek through it, etc. so there's not good point where the CRC can be calculated over the whole content. I thought of something quite convoluted/"smart" that might work, but not sure if it's worth the effort:
    • Each file has a 16bit "checksum" associated with it. This can be stored in the header of the filesystem or next to each file, it doesn't matter.
    • This "checksum" is calculated block by block: calculate the CRC16 of each 8 byte block, then xor all the resulting CRC16 together. This means that you can calculate this magic checksum in any order, as long as all blocks of the files are processed.
    • When reading, the cursor API keeps a bitmask of processed blocks. As soon as a new block is fetched, if it's the first time, the CRC16 is calculated and a XOR checksum is updated. Also the block is flagged as "done" in the bitmask. As soon as the bitmask is full (all blocks are processed), all I/O functions start returning error if the checksum doesn't match. As a last ditch, fclose() would read all missing blocks and return error if the checksum doesn't match (though nobody checks fclose... so is it worth it? Maybe yes).
    • When writing, something similar happens. At fclose time, all blocks that were not written are read once, so that their CRC can be calculated and the final checksum can be updated.
    • Maybe checksum checks can be disabled altogether at either mount time, or open time (ab)using a POSIX flag. One option would be to reuse O_EXCL (which is mapped to x in fopen format strings) to signal "I don't want checksum calculation while reading / writing".
      • if the checkum is "appended" at the end of each file, one option would be for O_EXCL to switch to "raw mode" where the checksum is returned as part of the actual file content, and performing zero calculations on it. This would also to document to always use O_EXCL if the game code wants to perform its own checksum on data and completely bypass the filesystem support.

Copy link
Contributor Author

@meeq meeq Mar 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting problem that I'll definitely give some more thought and research into.

I considered doing some sort of checksum for each file (or each block) but ultimately punted on the idea due to the new realities of EEPROM save reliability in the age of emulators and flash carts. The vast majority of people running games/software built by libdragon are likely not actually using a cartridge with real EEPROM.

There are some programmable cartridges out there (the N64 Blaster by RetroStage, and the N64 Project by RetroCircuits) which support EEPROM saves, but it's unclear whether they actually use EEPROM (further investigation needed).

I think it's a worthwhile follow-up to explore how best to effectively implement checksums with minimal overhead. Hopefully it will be possible to drop-in without much (or any) of a noticeable change on the API side.

@anacierdem
Copy link
Collaborator

@rasky thanks for the review, I'm rather slow at reviewing C code (and I'm pretty sure it will not be as good as yours) so it is really appreciated. @meeq Thanks for the implementation! 🥇 Very few are opening PRs and we don't have the time to collect them from the forks unfortunately. We are trying hard to improve libdragon as much as possible so please be patient with the reviews, it is for the greater good.

Copy link
Collaborator

@anacierdem anacierdem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is ok to merge as is. I have a few comments to clear some things up but I will not insist on any of them. The API is ok to begin with and is extensible with callbacks or async handles later on. const pointers are also easy to update if we want so.

src/eepromfs.c Outdated Show resolved Hide resolved
src/eepromfs.c Outdated
if ( cursor + bytes_to_read >= file->max_cursor )
{
/* It will, let's shorten it */
bytes_to_read = file->max_cursor - cursor;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should return an error here as well. The buffer will not be filled by len bytes and that may cause confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I borrowed this pattern from dragonfs.c:

libdragon/src/dragonfs.c

Lines 949 to 957 in 87bcff5

/* Bounds check to make sure we don't read past the end */
if(file->loc + to_read > file->size)
{
/* It will, lets shorten it */
to_read = file->size - file->loc;
}
if (!to_read)
return 0;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a really old code and we don't have to duplicate that, but for the sake of consistency I think this is ok for now.

src/eepromfs.c Outdated
while ( bytes_to_read > 0 && block_byte_offset < EEPROM_BLOCK_SIZE )
{
*(ptr++) = eeprom_buf[block_byte_offset];
bytes_read++;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As eepfs_file_t's contents are writable now, we may just mutate it here to track position. We already do that at the end so that might be easier to read.

src/eepromfs.c Outdated Show resolved Hide resolved
src/eepromfs.c Outdated
if ( cursor + bytes_to_write >= file->max_cursor )
{
/* It will, let's shorten it */
bytes_to_write = file->max_cursor - cursor;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again we may consider an error here

src/eepromfs.c Outdated
new_cursor = 0;
}

file->cursor = new_cursor;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can set the cursor in place as it is not a const anymore.

@anacierdem
Copy link
Collaborator

Another note: I don't think backwards compatiblity is much of an issue for an open source library on an archaic system, but still the first 3 bytes of the signature can act as a version to convert between different versions if someone really has to keep saves between versions. So maybe we;

  • should not wipe the system on init if it is found to be incompatible
  • should add a mechanism to read the first sector, e.g with a custom path and special handling maybe? current eepfs_get_file does not allow reading it at all.

@meeq
Copy link
Contributor Author

meeq commented Mar 7, 2021

  • Removed signature verification from eepfs_init; it is now the responsibility of the homebrew developer to call eepfs_verify_signature and eepfs_wipe. Example has been updated accordingly.

  • divide_ceil macro has been made safe(r)

  • Added documentation about slow EEPROM writes. From my research, reading EEPROM does not incur the 15ms wait, just writing.

I believe this is in a good-enough state to ship. I know there was some discussion around buffering reads/writes, but I don't think that is necessary to get this merged. It shouldn't impact the API, so it can be addressed later.

@rasky
Copy link
Collaborator

rasky commented Mar 7, 2021 via email

Copy link
Collaborator

@rasky rasky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to go for me!

@meeq
Copy link
Contributor Author

meeq commented Mar 8, 2021

I want to add proper tests to this before merging. I encountered some issues I haven't fully debugged while integrating this branch back into a larger project that heavily uses fread/fwrite

@meeq
Copy link
Contributor Author

meeq commented Jun 23, 2021

@rasky @anacierdem I have removed the stdio hooks and added some tests.

Please let me know if you want to see any other changes. Thanks!

tests/test_eepromfs.c Outdated Show resolved Hide resolved
Copy link
Collaborator

@rasky rasky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done another round, I think we're very close now!

src/controller.c Outdated Show resolved Hide resolved
src/eepromfs.c Outdated Show resolved Hide resolved
ASSERT_EQUAL_HEX(pif_out[1], (uint64_t)0x000000fe00000000, "pif response mismatch");
ASSERT(eeprom_type == EEPROM_NONE, "eeprom type changed?");
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how I feel about this test. This sounds more like a test to help emulator authors. Libdragon tests should be tests of libdragon functionality to make sure it keeps working over time. For instance, I would expect here a test of eeprom_read_bytes / eeprom_write_bytes. This wouldn't required to export pif_execute BTW.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eeprom_read_bytes and eeprom_write_bytes get exercised by the EEPROM filesystem tests, so I didn't see a need to include them here

tests/test_eepromfs.c Show resolved Hide resolved
@rasky
Copy link
Collaborator

rasky commented Jun 26, 2021

Approved. Just missing doc update to eeprom_read_bytes and eeprom_write_bytes.

@anacierdem anacierdem merged commit 403a749 into DragonMinded:trunk Jun 28, 2021
anacierdem added a commit to anacierdem/libdragon-docker that referenced this pull request Jul 27, 2021
Changed

- Improve fastpath of dfs_read (DragonMinded/libdragon#133)
- Refactor n64tool (DragonMinded/libdragon#153, DragonMinded/libdragon#155)

Fixed

- Zero-initialize the token array to avoid -Werror=maybe-uninitialized (DragonMinded/libdragon#134)
- Initialize arguments to main libdragon entrypoint (DragonMinded/libdragon#136)
- SD support fixes and dragonfs fopen fix (DragonMinded/libdragon#137)
- lib/include paths in tests Makefile (DragonMinded/libdragon#138)
- Reenable test_timer_ticks for emulators (DragonMinded/libdragon#140)
- n64tool: return error in case the seek offset required backward seek (DragonMinded/libdragon#144)
- Add missing extern "C" in debug.h (DragonMinded/libdragon#146)
- Ensure C++ global constructors are not garbage collected by ld (DragonMinded/libdragon#148)
- Fix clipped RDP rectangle drawing (DragonMinded/libdragon#147)

Added

- restart_timer and new_timer_stopped functions (DragonMinded/libdragon#131)
- dfs_rom_addr (DragonMinded/libdragon#133)
- Implement EEPROM Filesystem and test ROM (DragonMinded/libdragon#125)
- ed64romconfig binary (DragonMinded/libdragon#153, DragonMinded/libdragon#155)
- Support for RTC status/read/write commands (DragonMinded/libdragon#152)
anacierdem added a commit to anacierdem/libdragon-docker that referenced this pull request Jul 27, 2021
Changed

- Improve fastpath of dfs_read (DragonMinded/libdragon#133)
- Refactor n64tool (DragonMinded/libdragon#153, DragonMinded/libdragon#155)
  - It no longer support byte-swapping and only generates a z64 file.

Fixed

- Zero-initialize the token array to avoid -Werror=maybe-uninitialized (DragonMinded/libdragon#134)
- Initialize arguments to main libdragon entrypoint (DragonMinded/libdragon#136)
- SD support fixes and dragonfs fopen fix (DragonMinded/libdragon#137)
- lib/include paths in tests Makefile (DragonMinded/libdragon#138)
- Reenable test_timer_ticks for emulators (DragonMinded/libdragon#140)
- n64tool: return error in case the seek offset required backward seek (DragonMinded/libdragon#144)
- Add missing extern "C" in debug.h (DragonMinded/libdragon#146)
- Ensure C++ global constructors are not garbage collected by ld (DragonMinded/libdragon#148)
- Fix clipped RDP rectangle drawing (DragonMinded/libdragon#147)

Added

- restart_timer and new_timer_stopped functions (DragonMinded/libdragon#131)
- dfs_rom_addr (DragonMinded/libdragon#133)
- Implement EEPROM Filesystem and test ROM (DragonMinded/libdragon#125)
- ed64romconfig binary (DragonMinded/libdragon#153, DragonMinded/libdragon#155)
- Support for RTC status/read/write commands (DragonMinded/libdragon#152)
anacierdem added a commit to anacierdem/libdragon-docker that referenced this pull request Jul 28, 2021
Changed

- Removed make, download, init, buildDragon, prepareDragon, and installDependencies NPM scripts
  - Update the necessary vscode and travis configuration
  - Update the readme to match - this also fixes #31
- Improve fastpath of dfs_read (DragonMinded/libdragon#133)
- Refactor n64tool (DragonMinded/libdragon#153, DragonMinded/libdragon#155)
  - It no longer support byte-swapping and only generates a z64 file.
  - Change test bench Makefile to reflect latest changes

Fixed

- Zero-initialize the token array to avoid -Werror=maybe-uninitialized (DragonMinded/libdragon#134)
- Initialize arguments to main libdragon entrypoint (DragonMinded/libdragon#136)
- SD support fixes and dragonfs fopen fix (DragonMinded/libdragon#137)
- lib/include paths in tests Makefile (DragonMinded/libdragon#138)
- Reenable test_timer_ticks for emulators (DragonMinded/libdragon#140)
- n64tool: return error in case the seek offset required backward seek (DragonMinded/libdragon#144)
- Add missing extern "C" in debug.h (DragonMinded/libdragon#146)
- Ensure C++ global constructors are not garbage collected by ld (DragonMinded/libdragon#148)
- Fix clipped RDP rectangle drawing (DragonMinded/libdragon#147)
- Enable byte swap flag for the make action and update documentation accordingly
- Skip the second parameter to the libdragon command as well
- Enable --byte-swap flag for the make action

Added

- restart_timer and new_timer_stopped functions (DragonMinded/libdragon#131)
- dfs_rom_addr (DragonMinded/libdragon#133)
- Implement EEPROM Filesystem and test ROM (DragonMinded/libdragon#125)
- ed64romconfig binary (DragonMinded/libdragon#153, DragonMinded/libdragon#155)
- Support for RTC status/read/write commands (DragonMinded/libdragon#152)
- Generic libdragon NPM script
anacierdem added a commit to anacierdem/libdragon-docker that referenced this pull request Jul 28, 2021
Changed

- Removed make, download, init, buildDragon, prepareDragon, and installDependencies NPM scripts
  - Update the necessary vscode and travis configuration
  - Update the readme to match - this also fixes #31
- Improve fastpath of dfs_read (DragonMinded/libdragon#133)
- Refactor n64tool (DragonMinded/libdragon#153, DragonMinded/libdragon#155)
  - It no longer support byte-swapping and only generates a z64 file.
  - Change test bench Makefile to reflect latest changes

Fixed

- Zero-initialize the token array to avoid -Werror=maybe-uninitialized (DragonMinded/libdragon#134)
- Initialize arguments to main libdragon entrypoint (DragonMinded/libdragon#136)
- SD support fixes and dragonfs fopen fix (DragonMinded/libdragon#137)
- lib/include paths in tests Makefile (DragonMinded/libdragon#138)
- Reenable test_timer_ticks for emulators (DragonMinded/libdragon#140)
- n64tool: return error in case the seek offset required backward seek (DragonMinded/libdragon#144)
- Add missing extern "C" in debug.h (DragonMinded/libdragon#146)
- Ensure C++ global constructors are not garbage collected by ld (DragonMinded/libdragon#148)
- Fix clipped RDP rectangle drawing (DragonMinded/libdragon#147)
- Enable byte swap flag for the make action and update documentation accordingly
- Skip the second parameter to the libdragon command as well
- Enable --byte-swap flag for the make action

Added

- restart_timer and new_timer_stopped functions (DragonMinded/libdragon#131)
- dfs_rom_addr (DragonMinded/libdragon#133)
- Implement EEPROM Filesystem and test ROM (DragonMinded/libdragon#125)
- ed64romconfig binary (DragonMinded/libdragon#153, DragonMinded/libdragon#155)
- Support for RTC status/read/write commands (DragonMinded/libdragon#152)
- Generic libdragon NPM script
@meeq meeq deleted the eepromfs branch October 25, 2021 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants