diff --git a/SCsub b/SCsub new file mode 100644 index 0000000..5ee7396 --- /dev/null +++ b/SCsub @@ -0,0 +1,24 @@ +Import("env") +Export("env") + +os_wii = [ + "os_wii.cpp", + "file_access_wii.cpp", + "dir_access_wii.cpp", + "ip_wii.cpp", + "drivers/audio/audio_driver_ogc.cpp", +] + +bld = Builder(action=env["ENV"]["DEVKITPRO"] + "/tools/bin/elf2dol $SOURCE $TARGET") +env.Append(BUILDERS={"Dol": bld}) + +if env["PROGSUFFIX"].endswith(".exe"): + env["PROGSUFFIX"] = env["PROGSUFFIX"][0:-4] + ".elf" +else: + env["PROGSUFFIX"] = env["PROGSUFFIX"] + ".elf" + +prog = env.add_program("#bin/godot", ["godot_wii.cpp"] + os_wii) +if env["target"] == "release": + dolsuff = env["PROGSUFFIX"][0:-4] + ".dol" + dol = env.Dol("#bin/godot" + dolsuff, "#bin/godot" + env["PROGSUFFIX"]) + Depends(dol, prog) diff --git a/detect.py b/detect.py new file mode 100644 index 0000000..7850d00 --- /dev/null +++ b/detect.py @@ -0,0 +1,217 @@ +import os, platform + +tool_prefix = "powerpc-eabi-" + +def is_active(): + return True + +def get_name(): + return "Nintendo Wii" + +def can_build(): + disabled = False + + # Check the minimal dependencies + devkitpro = os.environ.get("DEVKITPRO", "/opt/devkitpro") + devkitppc = os.environ.get("DEVKITA64", "/opt/devkitpro/devkitPPC") + + if not os.path.exists(devkitpro): + print("DevkitPro not found. Wii disabled.") + disabled = True + else: + if not os.path.exists(devkitppc): + print("DEVKITPPC environment variable is not set correctly.") + if not os.path.exists("{}/devkitPPC".format(devkitpro)): + print("DevkitPPC not found. Nintendo Wii disabled.") + disabled = True + if not os.path.exists("{}/portlibs/wii/bin/{}pkg-config".format(devkitpro, tool_prefix)): + print(tool_prefix + "pkg-config not found. Nintendo Wii disabled.") + disabled = True + + if os.system("pkg-config --version > /dev/null"): + print("pkg-config not found. Nintendo Wii disabled.") + disabled = True + + return not disabled + +def get_flags(): + return [ + ("tools", False), # Editor is not yet supported on Wii + + # Unsupported on Wii + ("module_bullet_enabled", False), # Bullet is unsupported due to missing semaphore.h + ("module_mbedtls_enabled", False), # mbedtls has not been ported to Wii + ("module_mobile_vr_enabled", False), # Wii is not mobile, nor capable of VR by default + ("module_theora_enabled", False), # Undefined reference to `oggpack_*` + ("module_upnp_enabled", False), # Unsupported, may require porting + ("module_webm_enabled", False), # WebM is unsupported due to missing semaphore.h + ("module_websocket_enabled", False), # WebSocket is unsupported due to missing netinet/in.h (wslay) + + # Found in portlibs: + ("builtin_freetype", False), # ppc-freetype + ("builtin_libogg", False), # ppc-libogg + ("builtin_libpng", False), # ppc-libpng + ("builtin_libvorbis", False), # ppc-libvorbis + ("builtin_opus", False), # ppc-libopus + ppc-opusfile + ("builtin_pcre2_with_jit", False), # pcre2 JIT is unsupported + ("builtin_zlib", False), # ppc-zlib + + # Not found in portlibs, but may be possible + ("builtin_mbedtls", False), # mbedtls needs to be ported to Wii + ] + +def get_opts(): + from SCons.Variables import EnumVariable + return [ + EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), + ] + +def create(env): + return env.Clone(tools=["mingw"]) + +def configure(env): + # Workaround for MinGW. See: + # http://www.scons.org/wiki/LongCmdLinesOnWin32 + if os.name == "nt": + + import subprocess + + def mySubProcess(cmdline, env): + # print("SPAWNED : " + cmdline) + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + proc = subprocess.Popen( + cmdline, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=startupinfo, + shell=False, + env=env, + ) + data, err = proc.communicate() + rv = proc.wait() + if rv: + print("=====") + print(err.decode("utf-8")) + print("=====") + return rv + + def mySpawn(sh, escape, cmd, args, env): + + newargs = " ".join(args[1:]) + cmdline = cmd + " " + newargs + + rv = 0 + if len(cmdline) > 32000 and cmd.endswith("ar"): + cmdline = cmd + " " + args[1] + " " + args[2] + " " + for i in range(3, len(args)): + rv = mySubProcess(cmdline + args[i], env) + if rv: + break + else: + rv = mySubProcess(cmdline, env) + + return rv + + env["SPAWN"] = mySpawn + + # Set compilers + env["CC"] = tool_prefix + "gcc" + env["CXX"] = tool_prefix + "g++" + env["LD"] = tool_prefix + "ld" + + dkp = os.environ.get("DEVKITPRO", "/opt/devkitpro") + dkppc = os.environ.get("DEVKITPPC", "{}/devkitPPC".format(dkp)) + + env["ENV"]["DEVKITPRO"] = dkp + updated_path = "{}/portlibs/wii/bin:{}/bin:".format(dkp, dkppc) + os.environ["PATH"] + env["ENV"]["PATH"] = updated_path + os.environ["PATH"] = updated_path # os environment has to be updated for subprocess calls + + arch = ["-mrvl", "-mcpu=750", "-meabi", "-mhard-float", "-fdata-sections", "-fno-rtti", "-fno-exceptions"] + env.Prepend(CCFLAGS=arch + ["-ffunction-sections"]) + + env.Prepend(CPPPATH=["{}/powerpc-eabi/include".format(dkppc)]) + env.Prepend(CPPFLAGS=["-isystem", "{}/libogc/include".format(dkp)]) + + env.Prepend(LIBPATH=["{}/portlibs/ppc/lib".format(dkp), "{}/portlibs/wii/lib".format(dkp), "{}/libogc/lib/wii".format(dkp)]) + env.Prepend(LINKFLAGS=["-mrvl", "-mcpu=750", "-meabi", "-mhard-float", "-T", "platform/wii/pck_embed.ld", "-Wl,--gc-sections"]) + + if env["target"] == "release": + # -O3 -ffast-math is identical to -Ofast. We need to split it out so we can selectively disable + # -ffast-math in code for which it generates wrong results. + if env["optimize"] == "speed": # optimize for speed (default) + env.Prepend(CCFLAGS=["-O3", "-ffast-math"]) + else: # optimize for size + env.Prepend(CCFLAGS=["-Os"]) + + if env["debug_symbols"] == "yes": + env.Prepend(CCFLAGS=["-g1"]) + if env["debug_symbols"] == "full": + env.Prepend(CCFLAGS=["-g2"]) + + elif env["target"] == "release_debug": + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + if env["optimize"] == "speed": # optimize for speed (default) + env.Prepend(CCFLAGS=["-O2", "-ffast-math"]) + else: # optimize for size + env.Prepend(CCFLAGS=["-Os"]) + + if env["debug_symbols"] == "yes": + env.Prepend(CCFLAGS=["-g1"]) + if env["debug_symbols"] == "full": + env.Prepend(CCFLAGS=["-g2"]) + + elif env["target"] == "debug": + env.Append(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) + env.Prepend(CCFLAGS=["-g3"]) + # env.Append(LINKFLAGS=['-rdynamic']) + + ## Architecture + + env["bits"] = "32" + + if env["use_lto"]: + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto=" + str(env.GetOption("num_jobs"))]) + env["AR"] = tool_prefix + "gcc-ar" + env["RANLIB"] = tool_prefix + "gcc-ranlib" + else: + env["AR"] = tool_prefix + "ar" + env["RANLIB"] = tool_prefix + "ranlib" + + # Dependencies + + # freetype depends on libpng and zlib, so bundling one of them while keeping others + # as shared libraries leads to weird issues + if env["builtin_freetype"] or env["builtin_libpng"] or env["builtin_zlib"]: + env["builtin_freetype"] = True + env["builtin_libpng"] = True + env["builtin_zlib"] = True + + if not env["builtin_freetype"]: + env.ParseConfig(tool_prefix + "pkg-config freetype2 --cflags --libs") + + if not env["builtin_libpng"]: + env.ParseConfig(tool_prefix + "pkg-config libpng --cflags --libs") + + if not env["builtin_zstd"]: + env.ParseConfig(tool_prefix + "pkg-config libzstd --cflags --libs") + + if env["module_mbedtls_enabled"]: + env.Append(CPPDEFINES=["MBEDTLS_NO_PLATFORM_ENTROPY"]) + + ## Flags + + # Linkflags below this line should typically stay the last ones + + env.Prepend(CPPPATH=["#platform/wii"]) + env.Prepend(CPPDEFINES=[ + "HOMEBREW_ENABLED", + "WII_ENABLED", + "GEKKO", + "NO_THREADS", + "NO_SAFE_CAST" + ]) + env.Append(LIBS=["wiiuse", "bte", "fat", "ogc", "m", "ogg", "vorbis", "theora"]) diff --git a/dir_access_wii.cpp b/dir_access_wii.cpp new file mode 100644 index 0000000..4a06210 --- /dev/null +++ b/dir_access_wii.cpp @@ -0,0 +1,295 @@ +/**************************************************************************/ +/* dir_access_wii.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "dir_access_wii.h" + +#include +#include +#include + +Error DirAccessWii::list_dir_begin() { + list_dir_end(); //close any previous dir opening! + + //char real_current_dir_name[2048]; //is this enough?! + //getcwd(real_current_dir_name,2048); + //chdir(current_path.utf8().get_data()); + dir_stream = opendir(current_dir.utf8().get_data()); + //chdir(real_current_dir_name); + if (!dir_stream) + return ERR_CANT_OPEN; //error! + + return OK; +} + +bool DirAccessWii::file_exists(String p_file) { + GLOBAL_LOCK_FUNCTION + + if (p_file.is_rel_path()) + p_file = current_dir.plus_file(p_file); + + p_file = fix_path(p_file); + + struct stat flags; + bool success = (stat(p_file.utf8().get_data(), &flags) == 0); + + if (success && S_ISDIR(flags.st_mode)) { + success = false; + } + + return success; +} + +bool DirAccessWii::dir_exists(String p_dir) { + GLOBAL_LOCK_FUNCTION + + if (p_dir.is_rel_path()) + p_dir = get_current_dir().plus_file(p_dir); + + p_dir = fix_path(p_dir); + + struct stat flags; + bool success = (stat(p_dir.utf8().get_data(), &flags) == 0); + + return (success && S_ISDIR(flags.st_mode)); +} + +uint64_t DirAccessWii::get_modified_time(String p_file) { + if (p_file.is_rel_path()) + p_file = current_dir.plus_file(p_file); + + p_file = fix_path(p_file); + + struct stat flags; + bool success = (stat(p_file.utf8().get_data(), &flags) == 0); + + if (success) { + return flags.st_mtime; + } else { + ERR_FAIL_V(0); + }; + return 0; +}; + +String DirAccessWii::get_next() { + if (!dir_stream) + return ""; + + dirent *entry = readdir(dir_stream); + + if (entry == NULL) { + list_dir_end(); + return ""; + } + + String fname = fix_unicode_name(entry->d_name); + + // Look at d_type to determine if the entry is a directory, unless + // its type is unknown (the file system does not support it) or if + // the type is a link, in that case we want to resolve the link to + // known if it points to a directory. stat() will resolve the link + // for us. + if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) { + String f = current_dir.plus_file(fname); + + struct stat flags; + if (stat(f.utf8().get_data(), &flags) == 0) { + _cisdir = S_ISDIR(flags.st_mode); + } else { + _cisdir = false; + } + } else { + _cisdir = (entry->d_type == DT_DIR); + } + + _cishidden = (fname != "." && fname != ".." && fname.begins_with(".")); + + return fname; +} + +bool DirAccessWii::current_is_dir() const { + return _cisdir; +} + +bool DirAccessWii::current_is_hidden() const { + return _cishidden; +} + +void DirAccessWii::list_dir_end() { + if (dir_stream) + closedir(dir_stream); + dir_stream = 0; + _cisdir = false; +} + +int DirAccessWii::get_drive_count() { + return 0; +} + +String DirAccessWii::get_drive(int p_drive) { + return ""; +} + +Error DirAccessWii::change_dir(String p_dir) { + GLOBAL_LOCK_FUNCTION + + p_dir = fix_path(p_dir); + + // prev_dir is the directory we are changing out of + String prev_dir; + char real_current_dir_name[2048]; + ERR_FAIL_COND_V(getcwd(real_current_dir_name, 2048) == NULL, ERR_BUG); + if (prev_dir.parse_utf8(real_current_dir_name)) + prev_dir = real_current_dir_name; //no utf8, maybe latin? + + // try_dir is the directory we are trying to change into + String try_dir = ""; + if (p_dir.is_rel_path()) { + String next_dir = current_dir.plus_file(p_dir); + next_dir = next_dir.simplify_path(); + try_dir = next_dir; + } else { + try_dir = p_dir; + } + + bool worked = (chdir(try_dir.utf8().get_data()) == 0); // we can only give this utf8 + if (!worked) { + return ERR_INVALID_PARAMETER; + } + + String base = _get_root_path(); + if (base != String() && !try_dir.begins_with(base)) { + ERR_FAIL_COND_V(getcwd(real_current_dir_name, 2048) == NULL, ERR_BUG); + String new_dir; + new_dir.parse_utf8(real_current_dir_name); + + if (!new_dir.begins_with(base)) { + try_dir = current_dir; //revert + } + } + + // the directory exists, so set current_dir to try_dir + current_dir = try_dir; + ERR_FAIL_COND_V(chdir(prev_dir.utf8().get_data()) != 0, ERR_BUG); + return OK; +} + +String DirAccessWii::get_current_dir() { + String base = _get_root_path(); + if (base != "") { + String bd = current_dir.replace_first(base, ""); + if (bd.begins_with("/")) + return _get_root_string() + bd.substr(1, bd.length()); + else + return _get_root_string() + bd; + } + return current_dir; +} + +Error DirAccessWii::make_dir(String p_dir) { + GLOBAL_LOCK_FUNCTION + + if (p_dir.is_rel_path()) + p_dir = get_current_dir().plus_file(p_dir); + + p_dir = fix_path(p_dir); + + bool success = (mkdir(p_dir.utf8().get_data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0); + int err = errno; + + if (success) { + return OK; + }; + + if (err == EEXIST) { + return ERR_ALREADY_EXISTS; + }; + + return ERR_CANT_CREATE; +} + +uint64_t DirAccessWii::get_space_left() { + struct statvfs vfs; + if (statvfs(current_dir.utf8().get_data(), &vfs) != 0) { + return 0; + }; + + return vfs.f_bfree * vfs.f_bsize; +}; + +Error DirAccessWii::rename(String p_from, String p_to) { + if (p_from.is_rel_path()) + p_from = get_current_dir().plus_file(p_from); + + p_from = fix_path(p_from); + + if (p_to.is_rel_path()) + p_to = get_current_dir().plus_file(p_to); + + p_to = fix_path(p_to); + + return ::rename(p_from.utf8().get_data(), p_to.utf8().get_data()) == 0 ? OK : FAILED; +} + +Error DirAccessWii::remove(String p_name) { + if (p_name.is_rel_path()) + p_name = get_current_dir().plus_file(p_name); + + p_name = fix_path(p_name); + + struct stat flags; + if ((stat(p_name.utf8().get_data(), &flags) != 0)) + return FAILED; + + if (S_ISDIR(flags.st_mode)) + return ::rmdir(p_name.utf8().get_data()) == 0 ? OK : FAILED; + else + return ::unlink(p_name.utf8().get_data()) == 0 ? OK : FAILED; +} + +String DirAccessWii::get_filesystem_type() const { + return ""; //TODO this should be implemented +} + +DirAccessWii::DirAccessWii() { + dir_stream = 0; + _cisdir = false; + + /* determine drive count */ + + // set current directory to an absolute path of the current directory + char real_current_dir_name[2048]; + ERR_FAIL_COND(getcwd(real_current_dir_name, 2048) == NULL); + if (current_dir.parse_utf8(real_current_dir_name)) + current_dir = real_current_dir_name; + + change_dir(current_dir); +} + +DirAccessWii::~DirAccessWii() { + list_dir_end(); +} diff --git a/dir_access_wii.h b/dir_access_wii.h new file mode 100644 index 0000000..c873291 --- /dev/null +++ b/dir_access_wii.h @@ -0,0 +1,81 @@ +/**************************************************************************/ +/* dir_access_wii.h */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once +#ifndef DIR_ACCESS_WII_H +#define DIR_ACCESS_WII_H + +#include "core/os/dir_access.h" + +#include + +class DirAccessWii : public DirAccess { + DIR *dir_stream; + + String current_dir; + bool _cisdir; + bool _cishidden; + +protected: + virtual String fix_unicode_name(const char *p_name) const { return String::utf8(p_name); } + +public: + virtual Error list_dir_begin(); + virtual String get_next(); + virtual bool current_is_dir() const; + virtual bool current_is_hidden() const; + + virtual void list_dir_end(); + + virtual int get_drive_count(); + virtual String get_drive(int p_drive); + + virtual Error change_dir(String p_dir); + virtual String get_current_dir(); + virtual Error make_dir(String p_dir); + + virtual bool file_exists(String p_file); + virtual bool dir_exists(String p_dir); + virtual uint64_t get_space_left(); + + virtual uint64_t get_modified_time(String p_file); + + virtual Error rename(String p_from, String p_to); + virtual Error remove(String p_name); + + virtual bool is_link(String p_file) { return false; } + virtual String read_link(String p_file) { return p_file; } + virtual Error create_link(String p_source, String p_target) { return FAILED; } + + virtual String get_filesystem_type() const; + + DirAccessWii(); + virtual ~DirAccessWii(); +}; + +#endif // DIR_ACCESS_WII_H diff --git a/drivers/audio/audio_driver_ogc.cpp b/drivers/audio/audio_driver_ogc.cpp new file mode 100644 index 0000000..0693a88 --- /dev/null +++ b/drivers/audio/audio_driver_ogc.cpp @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* audio_driver_ogc.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "audio_driver_ogc.h" + +Error AudioDriverOGC::init() { + return OK; +} + +void AudioDriverOGC::start() { +} + +int AudioDriverOGC::get_mix_rate() const { + return 0; +} + +AudioDriver::SpeakerMode AudioDriverOGC::get_speaker_mode() const { + return SpeakerMode::SPEAKER_MODE_STEREO; +} + +void AudioDriverOGC::lock() {} +void AudioDriverOGC::unlock() {} +void AudioDriverOGC::finish() {} diff --git a/drivers/audio/audio_driver_ogc.h b/drivers/audio/audio_driver_ogc.h new file mode 100644 index 0000000..a604216 --- /dev/null +++ b/drivers/audio/audio_driver_ogc.h @@ -0,0 +1,47 @@ +/**************************************************************************/ +/* audio_driver_ogc.h */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef AUDIO_DRIVER_OGC_H +#define AUDIO_DRIVER_OGC_H + +#include "servers/audio_server.h" + +class AudioDriverOGC : public AudioDriver { +public: + virtual const char *get_name() const { return "OGC"; } + + virtual Error init(); + virtual void start(); + virtual int get_mix_rate() const; + virtual SpeakerMode get_speaker_mode() const; + virtual void lock(); + virtual void unlock(); + virtual void finish(); +}; + +#endif // AUDIO_DRIVER_OGC_H diff --git a/export/export.cpp b/export/export.cpp new file mode 100644 index 0000000..a4f429b --- /dev/null +++ b/export/export.cpp @@ -0,0 +1,419 @@ +/**************************************************************************/ +/* export.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "export.h" + +#include "core/os/os.h" +#include "editor/editor_export.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "platform/wii/logo.gen.h" + +class EditorExportPlatformWii : public EditorExportPlatform { + GDCLASS(EditorExportPlatformWii, EditorExportPlatform); + + int version_code; + + Ref logo; + +protected: + virtual void get_export_options(List *r_options); + +public: + virtual String get_name() const { return "Wii Homebrew"; } + virtual String get_os_name() const { return "Wii Homebrew"; } + virtual Ref get_logo() const { return logo; } + + virtual List get_binary_extensions(const Ref &p_preset) const { + List list; + list.push_back("elf"); + list.push_back("dol"); + return list; + } + virtual void get_preset_features(const Ref &p_preset, List *r_features) {} + virtual void get_platform_features(List *r_features) {} + virtual void resolve_platform_feature_priorities(const Ref &p_preset, Set &p_features) {} + virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0); + + virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const; + + EditorExportPlatformWii(); + ~EditorExportPlatformWii(); +}; + +void EditorExportPlatformWii::get_export_options(List *r_options) { + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.elf,*.dol"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.elf,*.dol"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/exec_path", PROPERTY_HINT_PLACEHOLDER_TEXT, "apps/your_app/boot.elf"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/coder", PROPERTY_HINT_PLACEHOLDER_TEXT, "Your Name"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/release_date", PROPERTY_HINT_PLACEHOLDER_TEXT, "YYYYmmddHHMMSS"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_description"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/long_description"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/arguments"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/ahb_access"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "binary_format/embed_pck"), false)); +} + +bool EditorExportPlatformWii::can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { + String err; + bool valid = false; + + bool dvalid = false; + bool rvalid = false; + + if (p_preset->get("custom_template/debug") != "") { + dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); + if (!dvalid) { + err += TTR("Custom debug template not found.") + "\n"; + } + } + if (p_preset->get("custom_template/release") != "") { + rvalid = FileAccess::exists(p_preset->get("custom_template/release")); + if (!rvalid) { + err += TTR("Custom release template not found.") + "\n"; + } + } + + valid = dvalid || rvalid; + r_missing_templates = !valid; + + return valid; +} + +Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { + // Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data + + if (p_path.ends_with(".dol")) { + print_error("Cannot patch a .DOL yet! Sorry!"); + return ERR_FILE_CORRUPT; // TODO: Implement .DOL patching + } + + FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); + if (!f) { + return ERR_CANT_OPEN; + } + + // Read and check ELF magic number + { + uint32_t magic = f->get_32(); + if (magic != 0x464c457f) { // 0x7F + "ELF" + f->close(); + return ERR_FILE_CORRUPT; + } + } + + // Read program architecture bits from class field + + int bits = f->get_8() * 32; + + if (bits == 32 && p_embedded_size >= 0x100000000) { + f->close(); + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "32-bit executables cannot have embedded data >= 4 GiB."); + } + + // Get info about the section header table + + f->endian_swap = true; // Wii ELFs are big endian + + int64_t section_table_pos; + int64_t section_header_size; + if (bits == 32) { + section_header_size = 40; + f->seek(0x20); + section_table_pos = f->get_32(); + f->seek(0x30); + } else { // 64 + section_header_size = 64; + f->seek(0x28); + section_table_pos = f->get_64(); + f->seek(0x3c); + } + int num_sections = f->get_16(); + int string_section_idx = f->get_16(); + + // Load the strings table + uint8_t *strings; + { + // Jump to the strings section header + f->seek(section_table_pos + string_section_idx * section_header_size); + + // Read strings data size and offset + int64_t string_data_pos; + int64_t string_data_size; + if (bits == 32) { + f->seek(f->get_position() + 0x10); + string_data_pos = f->get_32(); + string_data_size = f->get_32(); + } else { // 64 + f->seek(f->get_position() + 0x18); + string_data_pos = f->get_64(); + string_data_size = f->get_64(); + } + + // Read strings data + f->seek(string_data_pos); + strings = (uint8_t *)memalloc(string_data_size); + if (!strings) { + f->close(); + return ERR_OUT_OF_MEMORY; + } + f->get_buffer(strings, string_data_size); + } + + // Search for the "pck" section + + bool found = false; + for (int i = 0; i < num_sections; ++i) { + int64_t section_header_pos = section_table_pos + i * section_header_size; + f->seek(section_header_pos); + + uint32_t name_offset = f->get_32(); + if (strcmp((char *)strings + name_offset, "pck") == 0) { + // "pck" section found, let's patch! + + if (bits == 32) { + f->seek(section_header_pos + 0x10); + f->store_32(p_embedded_start); + f->store_32(p_embedded_size); + } else { // 64 + f->seek(section_header_pos + 0x18); + f->store_64(p_embedded_start); + f->store_64(p_embedded_size); + } + + found = true; + break; + } + } + + memfree(strings); + f->close(); + + return found ? OK : ERR_FILE_CORRUPT; +} + +Error EditorExportPlatformWii::export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + if (!DirAccess::exists(p_path.get_base_dir())) { + return ERR_FILE_BAD_PATH; + } + + // Step 1: Copy export template and icon.png + + String custom_debug = p_preset->get("custom_template/debug"); + String custom_release = p_preset->get("custom_template/release"); + + String template_path = p_debug ? custom_debug : custom_release; + template_path = template_path.strip_edges(); + + if (template_path != String() && !FileAccess::exists(template_path)) { + EditorNode::get_singleton()->show_warning(TTR("Template file not found:") + "\n" + template_path); + return ERR_FILE_NOT_FOUND; + } + + if (template_path.ends_with(".dol") && p_preset->get("binary_format/embed_pck")) { + // TODO: Implement embedding pck into .dol templates + EditorNode::get_singleton()->show_warning(TTR("\"Embed Pck\" is currently not supported for .DOL templates.")); + return ERR_INVALID_PARAMETER; + } + + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + String binname = "boot."; + binname += template_path.get_extension(); + String actual_path = p_path.get_base_dir() + "/" + binname; + // TODO: Elf2dol + Error err = da->copy(template_path, actual_path); + String icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); + icon_path = icon_path.strip_edges(); + if (err == OK && icon_path != String()) { + icon_path = ProjectSettings::get_singleton()->globalize_path(icon_path); + if (da->file_exists(icon_path)) { + err = da->copy(icon_path, actual_path.get_base_dir() + "/icon.png"); + } + } + memdelete(da); + + // Step 2: Save .pck + + if (err == OK) { + String pck_path; + if (p_preset->get("binary_format/embed_pck")) { + pck_path = actual_path; + } else { + pck_path = actual_path.get_basename() + ".pck"; + } + + int64_t embedded_pos; + int64_t embedded_size; + err = save_pack(p_preset, pck_path, NULL, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size); + if (err == OK && p_preset->get("binary_format/embed_pck")) { + //if(embedded_size >= 0x100000000) + //{ + // TODO: Determine the size to error out at + //} + + err = fixup_embedded_pck(actual_path, embedded_pos, embedded_size); + } + } + + // Step 3: Write meta.xml + + if (err == OK) { + FileAccess *meta_xml = FileAccess::open(actual_path.get_base_dir() + "/meta.xml", FileAccess::WRITE, &err); + if (err == OK) { + String xml_string = "\n"; + xml_string += "get("application/version")) + "\">\n"; + xml_string += " " + (String)(p_preset->get("application/name")) + "\n"; + xml_string += " " + (String)(p_preset->get("application/coder")) + "\n"; + xml_string += " " + (String)(p_preset->get("application/version")) + "\n"; + xml_string += " " + (String)(p_preset->get("application/release_date")) + "\n"; + xml_string += " " + (String)(p_preset->get("application/short_description")) + "\n"; + xml_string += " " + (String)(p_preset->get("application/long_description")) + "\n"; + if (p_preset->get("application/ahb_access")) + xml_string += " \n"; + + String args = p_preset->get("application/arguments"); + xml_string += " \n"; + xml_string += " " + ((String)p_preset->get("application/exec_path")) + "\n"; + if (args.strip_edges() != String()) { + String arg; + bool in_quote = false; + bool double_quote = false; + bool escaping = false; + for (int i = 0; i < args.size() - 1; i++) { + char nextchar = args.get(i); + switch (nextchar) { + case ' ': + if (in_quote) { + if (escaping) { + arg += "\\"; + escaping = false; + } + arg += nextchar; + } else if (escaping) { + arg += nextchar; + escaping = false; + } else { + xml_string += " " + arg + "\n"; + arg.clear(); + } + break; + case '"': + if (escaping) { + arg += nextchar; + escaping = false; + } else if (in_quote) { + if (double_quote) + in_quote = false; + else + arg += nextchar; + } else { + in_quote = true; + double_quote = true; + } + break; + case '\'': + if (escaping) { + arg += nextchar; + escaping = false; + } else if (in_quote) { + if (!double_quote) + in_quote = false; + else + arg += nextchar; + } else { + in_quote = true; + double_quote = false; + } + break; + case '\\': + if (escaping) { + arg += nextchar; + escaping = false; + } else { + escaping = true; + continue; + } + break; + default: + if (escaping) { + arg += "\\"; + escaping = false; + } + arg += nextchar; + break; + } + } + if (!arg.empty()) { + if (escaping) + arg += '\\'; + + xml_string += " " + arg + "\n"; + arg.clear(); + } + } + xml_string += " \n"; + xml_string += ""; + + meta_xml->store_string(xml_string); + meta_xml->close(); + memdelete(meta_xml); + } + } + + return err; +} + +EditorExportPlatformWii::EditorExportPlatformWii() { + Ref img = memnew(Image(_wii_logo)); + logo.instance(); + logo->create_from_image(img); +} + +EditorExportPlatformWii::~EditorExportPlatformWii() { +} + +void register_wii_exporter() { + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = "*.exe"; + } + + EDITOR_DEF("export/wii/elf2dol", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/wii/elf2dol", PROPERTY_HINT_GLOBAL_FILE, exe_ext)); + + Ref exporter = Ref(memnew(EditorExportPlatformWii)); + EditorExport::get_singleton()->add_export_platform(exporter); +} diff --git a/export/export.h b/export/export.h new file mode 100644 index 0000000..ccfc90a --- /dev/null +++ b/export/export.h @@ -0,0 +1,35 @@ +/**************************************************************************/ +/* export.h */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once +#ifndef WII_EXPORT_H +#define WII_EXPORT_H + +void register_wii_exporter(); + +#endif // WII_EXPORT_H diff --git a/file_access_wii.cpp b/file_access_wii.cpp new file mode 100644 index 0000000..764fe6c --- /dev/null +++ b/file_access_wii.cpp @@ -0,0 +1,275 @@ +/**************************************************************************/ +/* file_access_wii.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "file_access_wii.h" + +#include +#include +#include +#include + +FileAccessWii::FileAccessWii() : + fp(NULL), last_error(OK), flags(0) { +} + +FileAccessWii::~FileAccessWii() { + close(); +} + +void FileAccessWii::check_errors() const { + ERR_FAIL_COND_MSG(!fp, "File must be opened before use."); + + if (feof(fp)) { + last_error = ERR_FILE_EOF; + } +} + +Error FileAccessWii::_open(const String &p_path, int p_mode_flags) { + if (fp) + fclose(fp); + fp = NULL; + + path_src = p_path; + path = fix_path(p_path); + + const char *mode; + + switch (p_mode_flags) { + case READ: + mode = "rb"; + break; + case WRITE: + mode = "wb"; + break; + case READ_WRITE: + mode = "rb+"; + break; + case WRITE_READ: + mode = "wb+"; + break; + default: + return ERR_INVALID_PARAMETER; + } + + struct stat st; + int err = stat(path.utf8().get_data(), &st); + if (!err) { + switch (st.st_mode & S_IFMT) { + case S_IFLNK: + case S_IFREG: + break; + default: + return ERR_FILE_CANT_OPEN; + } + } + + fp = fopen(path.utf8().get_data(), mode); + + if (fp == NULL) { + switch (errno) { + case ENOENT: { + last_error = ERR_FILE_NOT_FOUND; + } break; + default: { + last_error = ERR_FILE_CANT_OPEN; + } break; + } + return last_error; + } + + // Set close on exec to avoid leaking it to subprocesses. + int fd = fileno(fp); + + if (fd != -1) { + int opts = fcntl(fd, F_GETFD); + fcntl(fd, F_SETFD, opts | FD_CLOEXEC); + } + + last_error = OK; + flags = p_mode_flags; + return OK; +} + +void FileAccessWii::close() { + if (!fp) + return; + + fclose(fp); + fp = NULL; +} + +bool FileAccessWii::is_open() const { + return (fp != NULL); +} + +String FileAccessWii::get_path() const { + return path_src; +} + +String FileAccessWii::get_path_absolute() const { + return path; +} + +void FileAccessWii::seek(uint64_t p_position) { + ERR_FAIL_COND_MSG(!fp, "File must be opened before use."); + + last_error = OK; + if (fseek(fp, p_position, SEEK_SET)) + check_errors(); +} + +void FileAccessWii::seek_end(int64_t p_position) { + ERR_FAIL_COND_MSG(!fp, "File must be opened before use."); + + if (fseek(fp, p_position, SEEK_END)) + check_errors(); +} + +uint64_t FileAccessWii::get_position() const { + ERR_FAIL_COND_V_MSG(!fp, 0, "File must be opened before use."); + + long pos = ftell(fp); + if (pos < 0) { + check_errors(); + ERR_FAIL_V(0); + } + return pos; +} + +uint64_t FileAccessWii::get_len() const { + ERR_FAIL_COND_V_MSG(!fp, 0, "File must be opened before use."); + + long pos = ftell(fp); + ERR_FAIL_COND_V(pos < 0, 0); + ERR_FAIL_COND_V(fseek(fp, 0, SEEK_END), 0); + long size = ftell(fp); + ERR_FAIL_COND_V(size < 0, 0); + ERR_FAIL_COND_V(fseek(fp, pos, SEEK_SET), 0); + + return size; +} + +bool FileAccessWii::eof_reached() const { + return last_error == ERR_FILE_EOF; +} + +uint8_t FileAccessWii::get_8() const { + ERR_FAIL_COND_V_MSG(!fp, 0, "File must be opened before use."); + uint8_t b; + if (fread(&b, 1, 1, fp) == 0) { + check_errors(); + b = '\0'; + } + return b; +} + +uint64_t FileAccessWii::get_buffer(uint8_t *p_dst, int p_length) const { + ERR_FAIL_COND_V_MSG(!fp, -1, "File must be opened before use."); + int read = fread(p_dst, 1, p_length, fp); + check_errors(); + return read; +} + +Error FileAccessWii::get_error() const { + return last_error; +} + +void FileAccessWii::flush() { + ERR_FAIL_COND_MSG(!fp, "File must be opened before use."); + fflush(fp); +} + +void FileAccessWii::store_8(uint8_t p_dest) { + ERR_FAIL_COND_MSG(!fp, "File must be opened before use."); + ERR_FAIL_COND(fwrite(&p_dest, 1, 1, fp) != 1); +} + +void FileAccessWii::store_buffer(const uint8_t *p_src, int p_length) { + ERR_FAIL_COND_MSG(!fp, "File must be opened before use."); + ERR_FAIL_COND(!p_src); + ERR_FAIL_COND((int)fwrite(p_src, 1, p_length, fp) != p_length); +} + +bool FileAccessWii::file_exists(const String &p_path) { + int err; + struct stat st; + String filename = fix_path(p_path); + + // Does the name exist at all? + err = stat(filename.utf8().get_data(), &st); + if (err) + return false; + + // See if we have access to the file + if (access(filename.utf8().get_data(), F_OK)) + return false; + + // See if this is a regular file + switch (st.st_mode & S_IFMT) { + case S_IFLNK: + case S_IFREG: + return true; + default: + return false; + } +} + +uint64_t FileAccessWii::_get_modified_time(const String &p_file) { + String file = fix_path(p_file); + struct stat flags; + int err = stat(file.utf8().get_data(), &flags); + + if (!err) { + return flags.st_mtime; + } else { + ERR_FAIL_V_MSG(0, "Failed to get modified time for: " + p_file + "."); + }; +} + +uint32_t FileAccessWii::_get_unix_permissions(const String &p_file) { + String file = fix_path(p_file); + struct stat flags; + int err = stat(file.utf8().get_data(), &flags); + + if (!err) { + return flags.st_mode & 0x7FF; //only permissions + } else { + ERR_FAIL_V_MSG(0, "Failed to get unix permissions for: " + p_file + "."); + }; +} + +Error FileAccessWii::_set_unix_permissions(const String &p_file, uint32_t p_permissions) { + String file = fix_path(p_file); + + int err = chmod(file.utf8().get_data(), p_permissions); + if (!err) { + return OK; + } + + return FAILED; +} diff --git a/file_access_wii.h b/file_access_wii.h new file mode 100644 index 0000000..98d92e7 --- /dev/null +++ b/file_access_wii.h @@ -0,0 +1,81 @@ +/**************************************************************************/ +/* file_access_wii.h */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once +#ifndef FILE_ACCESS_WII_H +#define FILE_ACCESS_WII_H + +#include "core/os/file_access.h" + +#include + +class FileAccessWii : public FileAccess { +protected: + FILE *fp; + int flags; + mutable Error last_error; + String path; + String path_src; + + void check_errors() const; + +public: + virtual Error _open(const String &p_path, int p_mode_flags); // Open file + virtual void close(); // Close file + virtual bool is_open() const; // Is file open? + + virtual String get_path() const; + virtual String get_path_absolute() const; + + virtual void seek(uint64_t p_position); // Seek to position in file + virtual void seek_end(int64_t p_position = 0); // Seek to position from end of file + virtual uint64_t get_position() const; // Get current file position + virtual uint64_t get_len() const; // Get size of file + + virtual bool eof_reached() const; // Reached end of file? + + virtual uint8_t get_8() const; // Read a byte + virtual uint64_t get_buffer(uint8_t *p_dst, int p_length) const; // Read a buffer of bytes + + virtual Error get_error() const; // Get last error + + virtual void flush(); // Flush buffer to file + virtual void store_8(uint8_t p_dest); // Store byte + virtual void store_buffer(const uint8_t *p_src, int p_length); // Store a buffer of bytes + + virtual bool file_exists(const String &p_path); // Does file at p_path exist? + + virtual uint64_t _get_modified_time(const String &p_file); + virtual uint32_t _get_unix_permissions(const String &p_file); + virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions); + + FileAccessWii(); + ~FileAccessWii(); +}; + +#endif // FILE_ACCESS_WII_H diff --git a/godot_wii.cpp b/godot_wii.cpp new file mode 100644 index 0000000..ceeb8e1 --- /dev/null +++ b/godot_wii.cpp @@ -0,0 +1,67 @@ +/**************************************************************************/ +/* godot_wii.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "main/main.h" +#include "os_wii.h" + +#include +#include +#include +#include +#include + +static void *xfb = NULL; +static GXRModeObj *rmode = NULL; + +int main(int argc, char *argv[]) { + // This function initialises the attached controllers + WPAD_Init(); + + OS_Wii os; + + Error err; + if (argc > 1) { + err = Main::setup(argv[0], argc - 1, argv + 1); + } else if (argc == 1) { + err = Main::setup(argv[0], 0, NULL); + } else { + err = Main::setup("/godot_app.elf", 0, NULL); + } + + if (err != OK) { + exit(255); + } + + if (Main::start()) { + os.run(); + } + + Main::cleanup(); + + return 0; +} diff --git a/ip_wii.cpp b/ip_wii.cpp new file mode 100644 index 0000000..3060a0f --- /dev/null +++ b/ip_wii.cpp @@ -0,0 +1,80 @@ +/**************************************************************************/ +/* ip_wii.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "ip_wii.h" + +#include + +void IP_Wii::_resolve_hostname(List &r_addresses, const String &p_hostname, Type p_type) const { + if (p_type == IP::TYPE_IPV6) { + ERR_PRINT("Wii does not support IPv6"); + return; + } + + hostent *ent = net_gethostbyname(p_hostname.utf8().get_data()); + + if (!ent || ent->h_length == 0) { + ERR_PRINT("Failed to resolve \"" + p_hostname + "\""); + return; + } + + IP_Address ip; + ip.set_ipv4(reinterpret_cast(ent->h_addr_list[0])); + r_addresses.push_back(ip); +} + +void IP_Wii::get_local_interfaces(Map *r_interfaces) const { + // TODO: what the fuck counts as a network interface + + Map::Element *E = r_interfaces->find("wii"); + if (!E) { + Interface_Info info; + info.name = "wii"; + info.name_friendly = "wii"; + info.index = "1"; + E = r_interfaces->insert("wii", info); + ERR_FAIL_COND(!E); + } + + Interface_Info &info = E->get(); + IP_Address ip; + u32 ip_num = net_gethostip(); + ip.set_ipv4(reinterpret_cast(ip_num)); + info.ip_addresses.push_front(ip); +} + +IP *IP_Wii::_create_wii() { + return memnew(IP_Wii); +} + +void IP_Wii::make_default() { + _create = _create_wii; +} + +IP_Wii::IP_Wii() { +} diff --git a/ip_wii.h b/ip_wii.h new file mode 100644 index 0000000..79d96eb --- /dev/null +++ b/ip_wii.h @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* ip_wii.h */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once +#ifndef IP_WII_H +#define IP_WII_H + +#include "core/io/ip.h" + +class IP_Wii : public IP { + GDCLASS(IP_Wii, IP); + + virtual void _resolve_hostname(List &r_addresses, const String &p_hostname, IP::Type p_type) const; + + static IP *_create_wii(); + +public: + virtual void get_local_interfaces(Map *r_interfaces) const; + + static void make_default(); + IP_Wii(); +}; + +#endif // IP_WII_H diff --git a/os_wii.cpp b/os_wii.cpp new file mode 100644 index 0000000..d678a63 --- /dev/null +++ b/os_wii.cpp @@ -0,0 +1,266 @@ +/**************************************************************************/ +/* os_wii.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "os_wii.h" + +//#include "drivers/gx/rasterizer_gx.h" +#include "main/main.h" + +#include +#include +#include +#include +#include // ticks_to_microseconds +#include +#include + +#include "core/os/file_access.h" + +#include "drivers/audio/audio_driver_ogc.h" +#include "dir_access_wii.h" +#include "file_access_wii.h" +#include "ip_wii.h" + +static uint64_t _clock_start = 0; +static void _setup_clock() { + _clock_start = SYS_Time(); +} + +void OS_Wii::initialize_core() { + // TODO: DevKitPro crash handler? + + if (!fatInitDefault()) { + printerr("fatInitdefault failed during OS_Wii::initialize_core(). File system functions may not work."); + } + + FileAccess::make_default(FileAccess::ACCESS_RESOURCES); + FileAccess::make_default(FileAccess::ACCESS_USERDATA); + FileAccess::make_default(FileAccess::ACCESS_FILESYSTEM); + //FileAccessBufferedFA::make_default(); + DirAccess::make_default(DirAccess::ACCESS_RESOURCES); + DirAccess::make_default(DirAccess::ACCESS_USERDATA); + DirAccess::make_default(DirAccess::ACCESS_FILESYSTEM); + + if (net_init() < 0) { + ERR_PRINT("net_init() failed! Networking may not function!"); + } + IP_Wii::make_default(); + + _setup_clock(); +} + +Error OS_Wii::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { + main_loop = NULL; + +// RasterizerGX::make_current(); + + video_driver_index = p_video_driver; + + visual_server = memnew(VisualServerRaster); + //if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { + // visual_server = memnew(VisualServerWrapMT(visual_server, false)); + //} + + visual_server->init(); + + AudioDriverManager::initialize(p_audio_driver); + + input = memnew(InputDefault); + + return OK; +} + +void OS_Wii::set_main_loop(MainLoop *p_main_loop) { + main_loop = p_main_loop; + input->set_main_loop(p_main_loop); +} + +void OS_Wii::delete_main_loop() { + if (main_loop) + memdelete(main_loop); + main_loop = NULL; +} + +void OS_Wii::finalize() { + if (main_loop) + memdelete(main_loop); + main_loop = NULL; + + visual_server->finish(); + memdelete(visual_server); + + memdelete(input); +} + +void OS_Wii::finalize_core() { + net_deinit(); +} + +bool OS_Wii::_check_internal_feature_support(const String &p_feature) { + return false; +} + +//#define _break(...) printf(__VA_ARGS__);while(1){WPAD_ScanPads();u32 pressed = WPAD_ButtonsDown(0);if(pressed & WPAD_BUTTON_HOME)break;VIDEO_WaitVSync();} +#define _break(...) printf(__VA_ARGS__) +void OS_Wii::run() { + _break("running!\n"); + force_quit = false; + + if (!main_loop) + return; + + main_loop->init(); + + _break("main loop init!\n"); + + while (!force_quit) { + // TODO: Process input events + + if (Main::iteration()) + break; + } + + main_loop->finish(); +} + +void OS_Wii::set_video_mode(const VideoMode &p_video_mode, int p_screen) { + video_mode = p_video_mode; +} +OS::VideoMode OS_Wii::get_video_mode(int p_screen) const { + return video_mode; +} + +void OS_Wii::get_fullscreen_mode_list(List *p_list, int p_screen) const { + p_list->push_back(video_mode); +} + +Size2 OS_Wii::get_window_size() const { + return Size2(video_mode.width, video_mode.height); +} + +Error OS_Wii::execute(const String &p_path, const List &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { + return ERR_UNAVAILABLE; //TODO: Fix or remove +} + +Error OS_Wii::kill(const OS::ProcessID &p_pid) { + return ERR_UNAVAILABLE; //TODO: Fix or remove +} + +bool OS_Wii::is_process_running(const ProcessID &p_pid) const { + return false; +} + +OS::Date OS_Wii::get_date(bool utc) const { + time_t t = time(NULL); + struct tm *lt; + if (utc) + lt = gmtime(&t); + else + lt = localtime(&t); + Date ret; + ret.year = 1900 + lt->tm_year; + // Index starting at 1 to match OS_Unix::get_date + // and Windows SYSTEMTIME and tm_mon follows the typical structure + // of 0-11, noted here: http://www.cplusplus.com/reference/ctime/tm/ + ret.month = (Month)(lt->tm_mon + 1); + ret.day = lt->tm_mday; + ret.weekday = (Weekday)lt->tm_wday; + ret.dst = lt->tm_isdst; + + return ret; +} + +OS::Time OS_Wii::get_time(bool utc) const { + time_t t = time(NULL); + struct tm *lt; + if (utc) + lt = gmtime(&t); + else + lt = localtime(&t); + Time ret; + ret.hour = lt->tm_hour; + ret.min = lt->tm_min; + ret.sec = lt->tm_sec; + get_time_zone_info(); + return ret; +} + +OS::TimeZoneInfo OS_Wii::get_time_zone_info() const { + time_t t = time(NULL); + struct tm *lt = localtime(&t); + char name[16]; + strftime(name, 16, "%Z", lt); + name[15] = 0; + TimeZoneInfo ret; + ret.name = name; + + char bias_buf[16]; + strftime(bias_buf, 16, "%z", lt); + int bias; + bias_buf[15] = 0; + sscanf(bias_buf, "%d", &bias); + + // convert from ISO 8601 (1 minute=1, 1 hour=100) to minutes + int hour = (int)bias / 100; + int minutes = bias % 100; + if (bias < 0) + ret.bias = hour * 60 - minutes; + else + ret.bias = hour * 60 + minutes; + + return ret; +} + +void OS_Wii::delay_usec(uint32_t p_usec) const { + struct timespec rem = { static_cast(p_usec / 1000000), (static_cast(p_usec) % 1000000) * 1000 }; + while (nanosleep(&rem, &rem) == EINTR) { + } +} + +uint64_t OS_Wii::get_ticks_usec() const { + uint64_t longtime = SYS_Time(); + longtime -= _clock_start; + + return ticks_to_microsecs(longtime); +} + +bool OS_Wii::can_draw() const { + return true; +} + +OS_Wii::OS_Wii() { + video_mode.width = 640; + video_mode.height = 480; + video_mode.fullscreen = true; + video_mode.resizable = false; + + AudioDriverManager::add_driver(&wii_audio_driver); +} + +OS_Wii::~OS_Wii() { +} \ No newline at end of file diff --git a/os_wii.h b/os_wii.h new file mode 100644 index 0000000..8d4eae1 --- /dev/null +++ b/os_wii.h @@ -0,0 +1,117 @@ +/**************************************************************************/ +/* os_wii.h */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once +#ifndef OS_WII_H +#define OS_WII_H + +#include "drivers/audio/audio_driver_ogc.h" +#include "drivers/unix/os_unix.h" +#include "main/input_default.h" +#include "servers/visual/visual_server_raster.h" +#include "servers/visual/visual_server_wrap_mt.h" + +class OS_Wii : public OS { + int video_driver_index; + MainLoop *main_loop; + VisualServer *visual_server; + InputDefault *input; + VideoMode video_mode; + AudioDriverOGC wii_audio_driver; + + bool force_quit; + +protected: + virtual void initialize_core(); + virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); + + virtual void set_main_loop(MainLoop *p_main_loop); + virtual void delete_main_loop(); + + virtual void finalize(); + virtual void finalize_core(); + + virtual bool _check_internal_feature_support(const String &p_feature); + +public: + static FILE *log_file; + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") { printf((const char *)p_alert.c_str()); } + virtual String get_stdin_string(bool p_block = true) { return ""; } + + virtual Point2 get_mouse_position() const { return Point2(); } + virtual int get_mouse_button_state() const { return 0; } + virtual void set_window_title(const String &p_title) {} + + virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); + virtual VideoMode get_video_mode(int p_screen = 0) const; + virtual void get_fullscreen_mode_list(List *p_list, int p_screen = 0) const; + + virtual int get_video_driver_count() const { return 1; } + virtual const char *get_video_driver_name(int p_driver) const { return "GX"; } + virtual int get_current_video_driver() const { return video_driver_index; } + + virtual int get_audio_driver_count() const { return 1; } + virtual const char *get_audio_driver_name(int p_driver) const { return "Dummy"; } + + virtual Size2 get_window_size() const; + virtual bool is_window_always_on_top() const { return true; } + + virtual Error execute(const String &p_path, const List &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL, bool p_open_console = false); + virtual Error kill(const ProcessID &p_pid); + virtual bool is_process_running(const ProcessID &p_pid) const; + + // TODO: Can Wii do execute/shell stuff? Probably not but check later + + virtual bool has_environment(const String &p_var) const { return false; } + virtual String get_environment(const String &p_var) const { return ""; } + virtual bool set_environment(const String &p_var, const String &p_value) const { return false; } + + virtual String get_name() const { return "Wii"; } + + virtual MainLoop *get_main_loop() const { return main_loop; } + + virtual Date get_date(bool utc) const; + virtual Time get_time(bool utc) const; + virtual TimeZoneInfo get_time_zone_info() const; + + virtual void delay_usec(uint32_t p_usec) const; + virtual uint64_t get_ticks_usec() const; + + virtual bool can_draw() const; + + // TODO: Virtual keyboard? + // halotroop2288: Must be implemented as a scene. + + void run(); + + OS_Wii(); + ~OS_Wii(); +}; + +#endif // OS_WII_H diff --git a/pck_embed.ld b/pck_embed.ld new file mode 100644 index 0000000..8199379 --- /dev/null +++ b/pck_embed.ld @@ -0,0 +1,12 @@ +SECTIONS +{ + /* Add a zero-sized section; the exporter will patch it to enclose the data appended to the executable (embedded PCK) */ + pck 0 (INFO) : + { + /* binutils >= 2.30 allow it being zero-sized, but needs something between the braces to keep the section */ + . = ALIGN(8); + } +} +INSERT AFTER .rodata; + +INCLUDE rvl.ld; /* Wii linker script at $DEVKITPRO/devkitPPC/powerpc-eabi/lib/rvl.ld */ diff --git a/platform_config.h b/platform_config.h new file mode 100644 index 0000000..6b4a18b --- /dev/null +++ b/platform_config.h @@ -0,0 +1,30 @@ +/**************************************************************************/ +/* platform_config.h */ +/**************************************************************************/ +/* This file is part of: */ +/* HOMEBRODOT */ +/**************************************************************************/ +/* Copyright (c) 2023-present Homebrodot contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include +#include