Terrario, a Terraria rewrite for the calculator
Posté le 10/07/2020 16:05
2021 Casio Awards winner, thanks everyone!
Hi. I noticed a while ago there weren't any games like Terraria or Minecraft available for Casio calculators. For the past while I've been working on rewriting Terraria in C for the SH4 calculators using gint. I'm not sure when if ever I'll finish it, since it is a fairly big project, so I've decided to put it here for now.
Here are a few screenshots of the progress so far (some may be out-of-date):
Main menu
Gameplay
Inventory
Crafting
Equipment
A visualisation of a generated world (click for full detail)
The game runs at 30FPS. Worlds are 1000x250 tiles large (640x250 on the 35+E II / GIII).
The control scheme and a crafting guide can be found in the game's About menu.
This forum page is updated regularly with the latest release of the game, as well as a changelog in the comments.
If you aren't sure what an item does, feel free to search it up on the
official Terraria wiki.
Most recent update:
Jungle and a bunch of content.
Up next:
Who knows?
The attached file contains the latest build of the game, as well as instructions and a screenshot compiling script and map tool.
The source code repository as well as early builds of the game can be found at
this GitHub repo and
its Gitea mirror. Obviously, expect bugs in these early builds, though I take care to remove the major ones I find before releasing.
Due to the very large world, the save files for this game are big. Make sure you have at least 450kB of storage space before installing the addin (300kB on Graph 35+E II), and try to keep at least 300kB free afterwards. Tampering with the files in the TERRARIO folder will corrupt the save, so don't do that. The game will warn you if you have low storage space available, so that you can optimise your storage.
NOTE: You must have a Graph 35+ E, Graph 35+E II, fx9860GII, or fx9750GIII model calculator to run this game.
Fichier joint
Citer : Posté le 12/07/2020 08:54 | #
These .src files from SimLo's documentation are actually written for Renesas' original SuperH assembler, which is part of the tool chain shipped with the fx-9860G SDK. We usually consider this toolchain to be of lower quality than GCC, so I haven't used it in years. You can absolutely use assembler with the fxSDK, it calls sh-elf-as for the job, but the syntax is slightly different.
Here are a couple instructions that should get you through:
• .SECTION P,CODE,ALIGN=4 becomes .text followed by .align 4
• .export becomes .global
• Instead of the SYSCALL macro, you can use the C preprocessor as in gint's core/syscalls.S (save your file as *.S instead of *.s for GCC to run the preprocessor)
• Don't use ? in symbol names. "." is often used for local symbols (eg ?try becomes .try). Also a colon is missing after ?exit
• Instead of mov.l #0x80010070, r2 do mov.l .syscall_addr and later on, after the code, .syscall_addr: .long 0x80010070
• .POOL and .END can be removed, .DATA.L becomes .long
Citer : Posté le 12/07/2020 09:07 | #
Cheers, I might try implementing it after multi-file saves.
Ajouté le 13/07/2020 à 12:15 :
Does BFile_FindFirst work with folders? I'm giving it a path to a folder (\\fls0\TERRARIO) and it appears to crash the addin if the folder exists.
Citer : Posté le 13/07/2020 12:43 | #
The BFile_FindFirst() work fine but it's a bit weird to use.
Without any code we can not help you
But there is a piece of code that I use in Vhex (my kernel) to "mount" the SMEM.
Basically, I just dump the File Hierarchy of the SMEM to avoid interaction with Casio's OS (for many raisons) and I use BFile_Find*() for this
(NOTE: look the void dump_smem_level() function)
#include <kernel/fs/smemfs.h>
#include <kernel/util/atomic.h>
#include <kernel/util/casio.h>
#include <kernel/devices/earlyterm.h>
#include <kernel/driver.h>
#include <string.h>
/*******************************************/
/** **/
/** USB Power Graphic III SMEM driver part **/
/** **/
/** (extract from <kernel/fs/smemfs.h>) **/
/*******************************************/
// Internal superblock use by the USB3 abstractions
struct smemfs_USB3_superblock
{
struct smemfs_USB3_inode *root_inode;
struct smemfs_USB3_inode *fake_root_inode;
};
// Internal struct used to store SMEM dump
struct smemfs_USB3_inode
{
// File name
char name[32];
// Internal file's informations
int type;
size_t fsize;
size_t dsize;
// Internal abstraction informations
struct smemfs_USB3_inode *child;
struct smemfs_USB3_inode *sibling;
struct smemfs_USB3_inode *parent;
};
// convert wide character string (16bits) into ASCII string
static size_t wide_char_convert(char *pathname, uint16_t *pathname_wc)
{
size_t i;
i = -1;
while (pathname_wc[++i] != 0x0000 && pathname_wc[i] != 0xffff)
pathname[i] = pathname_wc[i] & 0x00ff;
pathname[i] = '\0';
return (i);
}
// Dump one level of the SMEM Filesystem Hierarchy
// @note: send buffer to avoid recursif definition
static void dump_smem_level(struct smemfs_USB3_inode *parent,
struct smemfs_USB3_inode **sibling, uint16_t *buffer)
{
struct casio_file_info file_info;
struct smemfs_USB3_inode *inode;
int handle;
int i;
// Generate search path
i = 7;
memcpy(buffer, u"\\\\fls0\\", 14);
if (parent != NULL) {
for (int j = 0 ; parent->name[j] != '\0' ; ) {
buffer[i] = (uint16_t)(parent->name[j]);
i = i + 1;
j = j + 1;
}
buffer[i++] = '\\';
}
buffer[i + 0] = '*';
buffer[i + 1] = 0x0000;
// Find the first file
// @note: the search buffer and the buffer which will content
// the file name is the same. But it's not used at the same time, so
// we can use this tricky way to save some stack
if (casio_Bfile_FindFirst(buffer, &handle, buffer, &file_info) != 0)
return;
// Get all inode stored in this level
i = 0;
do {
// Try to alloc new inode
// TODO: return error code !
*sibling = smemfs_USB3_alloc_inode();
if (*sibling == NULL)
break;
// Get first inode
if (i == 0)
inode = *sibling;
i = 1;
// Convert wide char into char
wide_char_convert((*sibling)->name, buffer);
// Dump file informations
(*sibling)->type = file_info.type;
(*sibling)->fsize = file_info.size.file;
(*sibling)->dsize = file_info.size.data;
// Link node and get next sibling
(*sibling)->parent = parent;
sibling = &(*sibling)->sibling;
} while (casio_Bfile_FindNext(handle, buffer, &file_info) == 0);
// Close casio BfileFind* handle
casio_Bfile_FindClose(handle);
// Now let's check all file to find directories
while (inode != NULL)
{
// Check directory type
if (inode->type == DT_DIRECTORY)
dump_smem_level(inode, &inode->child, buffer);
// Get next inode
inode = inode->sibling;
}
}
// DEBUG function
// TODO: remove ?
static void proto_ls(struct smemfs_USB3_inode *inode, int level)
{
if (inode == NULL)
return;
for (int i = 0 ; i < level ; ++i)
earlyterm_write(" ");
earlyterm_write("%s\n", inode->name);
if (inode->child != NULL)
proto_ls(inode->child, level + 1);
proto_ls(inode->sibling, level);
}
//
// smemfs_USB3_mount() - Mount the file system (sync)
// @note:
// We don't known how the file system work, so we should use
// Casio's "Bfile_*" sycalls to dump all internal informations
// to avoid OS switch (Vhex -> Casio -> Vhex)
//
void *smemfs_USB3_mount(void)
{
extern struct smemfs_USB3_superblock smemfs_USB3_superblock;
uint16_t buffer[64];
void *root_inode;
// Get current root inode
atomic_start();
root_inode = smemfs_USB3_superblock.root_inode;
atomic_stop();
// Check useless mount
if (root_inode != NULL)
return (root_inode);
// We should use internal Casio's `Bfile_*` syscall
// to dump SMEM content
drivers_uninstall(0);
// Generate fake root inode
smemfs_USB3_superblock.fake_root_inode = (void*)0xdeadbeff;
// Dump SMEM files organisation
smemfs_USB3_superblock.root_inode = NULL;
dump_smem_level(smemfs_USB3_superblock.root_inode,
&smemfs_USB3_superblock.root_inode, buffer);
// Get the "fake" root inode
root_inode = smemfs_USB3_superblock.fake_root_inode;
//DEBUG
//proto_ls(smemfs_USB3_superblock.root_inode, 0);
//earlyterm_write("g@m3rz\n");
//while (1);
// Restore all drivers
drivers_install(0);
// Return the sector table to simulate the root inode.
return (root_inode);
}
Citer : Posté le 13/07/2020 12:47 | #
It is supposed to. To briefly quote the documentation, "The Bfile_FindFirst examines subdirectory names as well as filenames." and there is a DT_DIRECTORY file type in the return structure.
This might be a gint bug in the sense that performing that search can change hardware state in a way that gint does not expect, causing problems when returning from the switch. If you can narrow it down to some kind of MWE, please file an issue, I will gladly look at it.
Citer : Posté le 13/07/2020 12:47 | #
The current code for my save function (supposed to just create TERRARIO if it doesn't exist):
{
uint16_t filePath[30] = { 0 };
int handle;
uint16_t foundPath[30];
struct BFile_FileInfo fileInfo;
int error;
makeFilePath(filePath, "\\\\fls0\\TERRARIO");
error = BFile_FindFirst((const uint16_t*)filePath, &handle, foundPath, &fileInfo);
BFile_FindClose(handle);
if(error == -1) BFile_Create((const uint16_t*)filePath, BFile_Folder, NULL);
}
Citer : Posté le 13/07/2020 12:49 | #
As a quick aside, you can get UTF-16 string literals with the u prefix:
Citer : Posté le 13/07/2020 12:51 | #
oh cool, didn't know that, thought that was only in C++
Ajouté le 13/07/2020 à 14:56 :
Turns out it might be an emulator problem? After the umpteenth failure I decided to put it on my calculator and it looks like it works, the folder gets created. The emulator's been nothing but trouble to be honest, it really doesn't play well with gint but it's convenient to quickly test stuff.
Citer : Posté le 13/07/2020 15:00 | #
And no crash if the folder exists? Interesting. I admit I haven't thoroughly tested applications on the fx-9860G Manager so there might be quirks that I'm not aware of.
Citer : Posté le 13/07/2020 15:07 | #
I'm doing an error check to make sure I don't try creating the folder if it exists, but the emulator just doesn't like making folders - I get either a reboot and no folder or a hang and no folder after a manual reset. It doesn't have any problems with files, though.
Ajouté le 13/07/2020 à 16:06 :
I've managed to get world saves working, using 64x64 tile regions and saving in \\fls0\TERRARIO\regX.dat. Each .dat file is 8192 bytes, and I'm just padding them out if they overlap the edge of the world. I also tried it on the emulator and it worked after a few tries
Citer : Posté le 13/07/2020 16:46 | #
Good job! The crash definitely looks like a problem with gint's switch mechanism. Similar things happened before when writing to files, until I discovered that BFile was leaving the DMA running so I had to wait for it before leaving the switch. In another occurrence, writing to files causes TLB pages to be evicted, so I have to account for that as well. This is probably a similar case, I will look at it hopefully in the next few days.
Citer : Posté le 14/07/2020 11:58 | #
FINALLY finished with the update, pushed everything and made a new release. I haven't implemented the in-addin optimisation (right now you just get an error if space runs out) but I'll definitely think about in in the next update. I fixed all the bugs I saw but there might be a couple left with the save/load mechanic. I also discovered that grayscale means you can have rather good-looking antialiased text
Citer : Posté le 14/07/2020 12:04 | #
Nice! I would suggest leaving a direct link to the g1a file in your main post if you want people to try it out. You can make screenshots of the screen by writing the VRAM to a file (in mono use gint_vram, in gray use gvram(), or if you have the very latest commits, gray_getvram()).
Wait, seriously? I never really believed this would work and I eventually forgot about trying it. xD Do you have any photo of this?
Citer : Posté le 14/07/2020 12:10 | #
Right, added the g1a, I'll have to remember to update that too. I updated to the latest version today, didn't realise you could use the vram for screenshots! I'll see if I can make a thing to get one.
Citer : Posté le 14/07/2020 12:23 | #
Oh I forgot to mention that in gray mode the VRAM switches when you call dupdate(), so you have to save the VRAM between the time you finish drawing and the time you perform the dupdate(). In practice this means you need to save the frame before you show it. If you want to interactively save the frame that is currently visible on-screen while in gray mode, you can render the next frame and save the VRAM after calling dupdate() (because by this time the next frame has been sent to screen and the desired frame is back into the VRAM).
Citer : Posté le 14/07/2020 12:54 | #
I'm copying 1024 bytes from each VRAM pointer into a char buffer using memcpy, then into a file, but there isn't anything in either file when I look at their contents Am I copying the data the wrong way? I looked at Monochromelib's documentation for the 1024 bytes figure.
{
uint32_t* light;
uint32_t* dark;
uint16_t* pathLight = u"\\\\fls0\\light.vram";
uint16_t* pathDark = u"\\\\fls0\\dark.vram";
int descriptor;
int size = 1024;
char bufLight[size];
char bufDark[size];
dgray_getvram(&light, &dark);
memcpy(bufLight, light, size);
memcpy(bufDark, dark, size);
BFile_Remove(pathLight);
BFile_Create(pathLight, BFile_File, &size);
BFile_Remove(pathDark);
BFile_Create(pathDark, BFile_File, &size);
descriptor = BFile_Open(pathLight, BFile_WriteOnly);
BFile_Write(descriptor, bufLight, size);
BFile_Close(descriptor);
descriptor = BFile_Open(pathDark, BFile_WriteOnly);
BFile_Write(descriptor, bufDark, size);
BFile_Close(descriptor);
}
Citer : Posté le 14/07/2020 13:00 | #
This seems correct. I thought files extensions were limited to three characters, though if they're created it's probably not a problem. The VRAMs are already in the heap or static RAM area, you don't need to copy them to a buffer.
Please check that you're taking the screenshot with the correct timing relative to the dupdate() calls (see my message above). Also, do any of the BFile functions return an error code?
Citer : Posté le 14/07/2020 13:09 | #
Ah, I was calling it right after dupdate. Moved it to before and it looks like it worked, I'll make a python script to combine the two.
Citer : Posté le 14/07/2020 13:38 | # | Fichier joint
Made it, here's a screenshot of the main menu I'll make the script enlarge the images a bit.
Citer : Posté le 14/07/2020 13:48 | #
You can post the image as it, and then ask to display it bigger: [img=<width>|pixelated]https://url-to-img[/img]
It's better if <width> is a multiple of the original width. Ex: [img=256|pixelated]https://url-to-img[/img]
Citer : Posté le 14/07/2020 13:56 | #
That's what I was doing with the photos, I just think it's always better to have them a good size already in case they get filtered when they're upscaled and look bad.
I've made it so that the [SHIFT] button takes a capture of the VRAMs, I'll put the script in with the source code for if people want to take screenshots of their worlds.
Ajouté le 18/07/2020 à 03:19 :
Small update - the player is now animated. I planned on releasing this with the next big one but after implementing it I thought it looked good enough for its own release.
Ajouté le 20/07/2020 à 10:41 :
I've managed to squeeze an even larger world into 250kB - 1000x250 tiles, just above 1/20th the size of a small Terraria world!
I managed this by removing the status byte from the tile struct and instead calculating it when the tile is rendered. I feel that a minimal performance decrease is worth having double the world size. I still have 2 bits free in the struct, possibly for stuff like hammered tiles.
Another benefit is that world generation is now much faster due to not precalculating the tile statuses anymore.
Citer : Posté le 20/07/2020 10:58 | #
Did you consider some compression or lazy loading from storage memory? It may improve a bit the world size, even if it can add some latencies when moving to another world part.