#Trying to port Odin to the Nintendo 3DS

1 messages · Page 1 of 1 (latest)

clever hull
#

There is a toolchain for coding stuff for the 3DS, which comes with its own compiler, linker, standard library, other libraries, etc. The compiler it comes with is called arm-none-eabi-gcc.

And there is this library called libctru, which is responsible for most stuff interfacing with the 3DS.
I managed to make it work properly, by making a Odin module using the foreign keyword, to interface with the function definitions of the library. The game loop works and it can respond to input.

However, the problem is Odin itself. I can't print anything that it ends up crashing the 3DS (or causing a segfault). I think the Odin's fmt library wasn't made to interface properly with the 3DS.

this is the build script i have so far:

#!/bin/bash

odin build . \
    -target-features:soft-float \
    -microarch:generic \
        -target:linux_arm32 \
        -build-mode:object \
        -use-single-module \
    -linker:lld \

$DEVKITARM/bin/arm-none-eabi-gcc -o odin-teste.elf \
    odin-teste.o \
    -specs=3dsx.specs \
    -g \
    -Wall \
    -O2 \
    -mword-relocations \
    -ffunction-sections \
    -D__3DS__ \
        -march=armv6k \
        -mtune=mpcore \
        -mfloat-abi=hard \
        -mtp=soft \
        -Wl,-Map,odin-teste.map \
        -L$DEVKITPRO/libctru/lib \
        -lctru -lm

3dsxtool odin-teste.elf odin-teste.3dsx

Honestly, it might be because of the target i chose, linux_arm32. The 3DS is ARM, so this is the closest I could find. But this doesn't mean the 3DS works like Linux does.

Theres also freestanding_arm32, which could be the recommended option, but the problem is that it can't recognize printf from the fmt anymore, and it can't find the entry point anymore, for some reason.

When making the build script, I took as a reference a basic example makefile to build a project on the 3DS:
https://github.com/devkitPro/3ds-examples/blob/master/templates/application/Makefile

GitHub

Examples for 3DS using devkitARM, libctru, citro3d and citro2d - devkitPro/3ds-examples

thorn radish
#
  1. Odin isn't designed really to work on anything that isn't a modern desktop device
  2. Yes you will have to use freestanding. And yes you will have to reimplement the relevant parts of the core library. Copy it and start interfacing with the 3DS libraries directly in the os-specific code. There's no way for Odin to know how to interface with the 3DS it isn't implemented you have to implement it yourself.
#

also does the 3DS really not have floating-point? that doesn't sound right

#

I mean I don't know a ton about the hardware but software floating-point is going to be really really slow? Unless you have to initialize floating point in some way. You'll probably need to make a bootstrapping sort of bootloader in C to initialize everything

#

Something like this isn't trivial and isn't supported so beyond this not many people are going to be able to help you with this.

clever hull
#

yeah i know. i just wanted to have an idea of how it would be.

#

i got quite close though.

clever hull
thorn radish
#

🤷 you'd probably need to know more about the hardware to get this to work as well. I'd honestly just recommend using C and taking inspiration from Odin in your code practices (or C++ if you need basic generic templates)

clever hull
#

yeah, i was mainly using C for my project, i wanted to try porting odin more as of a curiosity, and since nobody has done it before.

ocean spear
#

What I did to get Odin compiling to run on pico2 is actually still use C to deal with the entry point and printf stuff. Then I used freestanding arm32 target to make an object file and link that to the c project.

silver idol
#

yep, fmt relies on Linux syscalls directly

#

You need to make it work with standard printf that comes with the 3ds library

ocean spear
#

You can use fmt.bprintf to print into a byte array.

#

And then you can print those bytes using 3DS libs.

#

I also had to tweak the compiler to avoid hardware floats but 3DS might be different

clever hull
#

well, in C, you can use the stdlib's printf and it works just fine. maybe it's just that i need odin to use the stdlib that comes with the compiler instead of the builtin one, or whatever is being used.

#

if that's even possible, maybe odin's stdlib is hardcoded in.

#

maybe fmt.printf directly uses the syscall instead of using the stdlib

#

in short, there isn't really a 3DS specific library for printing. if that were the case, it would actually make stuff easier lol

silver idol
#

you mean the 3ds library doesn't have printf?

clever hull
#

libctru is more for initializing 3DS specific stuff, like screens, graphics, interfacing with the SD card, accessing camera, on-screen keyboard, etc.

#

probably there is a way of "manually" printing, but i'm not sure

exotic osprey
#

core:fmt uses os.write which uses the write syscall on linux

#

On freestanding you don't have os and you'll need to write into your own buffer with something like bprintf and use your target's write equivalent functionality

clever hull
#

hmm

exotic osprey
#

Which is prob gonna be linking with the 3ds libraries and writing foreign {} blocks with definitions

#

Or patch core to use the 3ds libs

clever hull
#

maybe using the foreign keyword for the stdlib provided by the toolchain?

#

and then use the printf defined there

ocean spear
exotic osprey
#

Yes

ocean spear
#

My print.odin for my pico layer.

package pico

/*
Odin `core:fmt` like wrapper using printf.
Uses real fmt.bprintf with PRINTF_BUFFER_SIZE bytes.
*/

import "core:fmt"

@(private)
PRINTF_BUFFER_SIZE :: #config(PRINTF_BUFFER_SIZE, 1024) // 1KiB

@(private = "file")
buffer: [PRINTF_BUFFER_SIZE]byte

foreign _ {
    @(private, link_name = "stdio_put_string")
    _pico_put_string :: proc(text: ^byte, text_len: int, newline: bool, cr_translation: bool) -> i32 ---

    @(private, link_name = "stdio_flush")
    _pico_flush :: proc() ---

    @(private, link_name = "panic")
    _pico_panic :: proc(format: cstring, #c_vararg args: ..any) -> ! ---
}

// Utilizing fmt.bprintf to print via pico stdio_put_string.
// - 256 character limit (configured by PRINTF_BUFFER_SIZE).
printf :: proc "contextless" (format: string, args: ..any) {
    context = default_context()

    str := fmt.bprintf(buffer[:], format, ..args)
    _pico_put_string(&buffer[0], len(str), false, false)
}

// Utilizing fmt.bprintfln to print via pico stdio_put_string.
// - 256 character limit (configured by PRINTF_BUFFER_SIZE).
printfln :: proc "contextless" (format: string, args: ..any) {
    context = default_context()

    str := fmt.bprintf(buffer[:], format, ..args)
    _pico_put_string(&buffer[0], len(str), true, false)
}

// Utilizing fmt.bprintf to print via pico panic.
// - 256 character limit (configured by PRINTF_BUFFER_SIZE).
panicf :: proc "contextless" (format: string, args: ..any) -> ! {
    context = default_context()

    str := fmt.bprintf(buffer[:], format, ..args)
    _pico_put_string(&buffer[0], len(str), false, false)
    _pico_flush()

    _pico_panic("%s", cstring(&buffer[0]))
}

// Utility function to echo the expression and print its value.
echo :: proc "contextless" (value: any, expr := #caller_expression(value)) {
    printf("{} => {}\n", expr, value)
}