$(call rwildcard, $(dir_source), *.s *.c)))
.PHONY: all
-all: a9lh modules external
+all: a9lh modules external host/langemu.conf
.PHONY: modules
modules:
.PHONY: a9lh
a9lh: $(dir_out)/arm9loaderhax.bin
+host/langemu.conf:
+ echo "Generating langemu.conf - may take a bit"
+ cd host && ./generate_langemu_conf.sh
+ mkdir -p $(dir_out)/corbenik/etc
+ cp host/langemu.conf $(dir_out)/corbenik/etc/langemu.conf
+
.PHONY: clean
clean:
make -C modules clean
make -C external clean
+ rm host/langemu.conf
rm -rf $(dir_out) $(dir_build)
.PHONY: $(dir_out)/arm9loaderhax.bin
// Structure of config file
struct config_file {
char magic[4]; // "OVAN" for shits and giggles again.
+
uint32_t config_ver; // Config file version.
uint8_t options[256]; // Options in the menu - deliberately large to avoid config version bumps.
uint64_t patch_ids[256]; // What patches are enabled by UUID. 256 is an arbitrary limit - contact me if you hit it.
}__attribute__((packed));
-/*
-extern struct config_file config;
-
-enum type {
- boolean_val, // Toggle
- ranged_val // N1 - N2, left and right to pick.
- mask_val // Bitmask allowed values.
-};
-
-struct range_str {
- int a, b;
-};
-
-struct option {
- int config_index;
- char name_text[64];
- enum type allowed;
- uint32_t a, b;
-}__attribute__((packed));
-
-
-static struct options[] = {
- { 0, "Signature Patch", boolean_val, 0 },
- { 1, "FIRM Protection", boolean_val, 0 },
- { 2, "SysModule Replacement", boolean_val, 0 },
- { 3, "Service Replacement", boolean_val, 0 },
- { 4, "ARM9 Thread", boolean_val, 0 },
-
- { 5, "Autoboot", boolean_val, 0 },
- { 6, "Silence w/ Autoboot", boolean_val, 0 },
- { 7, "Step through with button", boolean_val, 0 },
-
- { 8, "Don't draw background color", boolean_val, 0 },
- { 9, "Preserve framebuffer data", boolean_val, 0 },
-
- { 10, "Hide Help from menu", boolean_val, 0 },
-
- { 11, "Loader: CPU L2 enable", boolean_val, 0 },
- { 12, "Loader: CPU 800Mhz mode", boolean_val, 0 },
- { 13, "Loader: Language Emulation", boolean_val, 0 },
-
- { 14, "No dependency tracking", boolean_val, 0 },
- { 15, "Allow unsafe options", boolean_val, 0 },
-}
-*/
-#define OPTION_SIGPATCH 0 // Use builtin signature patch.
-#define OPTION_FIRMPROT 1 // Protect firmware from writes.
-#define OPTION_LOADER 2 // Use builtin loader module replacer.
-#define OPTION_SERVICES 3 // Inject services (including backdoor for 11)
-#define OPTION_ARM9THREAD 4 // Use builtin ARM9 thread injector.
-
-#define OPTION_AUTOBOOT 5 // Skip menu unless L is held.
-#define OPTION_SILENCE 6 // Don't print debug information.
-#define OPTION_TRACE 7 // Pause for A key on each step.
-
-#define OPTION_TRANSP_BG 8 // Background color is not drawn under text.
-#define OPTION_NO_CLEAR_BG 9 // Framebuffer is preserved from whatever ran before us.
-#define OPTION_READ_ME 10 // Remove Help/Readme from menu.
-
#define OPTION_LOADER_CPU_L2 11 // Enable L2 cache.
#define OPTION_LOADER_CPU_800MHZ 12 // Enable 800Mhz mode.
#define OPTION_LOADER_LANGEMU 13 // Enable 800Mhz mode.
-#define IGNORE_PATCH_DEPS 14 // Ignore patch UUID dependencies. Not recommended.
-#define IGNORE_BROKEN_SHIT 15 // Allow enabling patches which are marked as 'incompatible'. Chances are there's a reason.
-
-//#define HEADER_COLOR 12 // Color of header text.
-//#define BG_COLOR 13 // Color of background.
-//#define TEXT_COLOR 14 // Color of most text.
-//#define ARROW_COLOR 15 // Color of Arrow.
-
#endif
char path[] = "/corbenik/etc/locale.conf"; // The locale config file.
- char progIdStr[17]; // Sizeof titleid as string + null terminator
- progIdStr[sizeof(progIdStr)-1] = 0; // Set null term
-
- // Hexdump progId
- for(int i=0; i < 8; i++) {
- static const char hexDigits[] = "0123456789ABCDEF";
- progIdStr[i*2] = hexDigits[(((uint8_t*)&progId)[i] >> 4) & 0xf];
- progIdStr[i*2+1] = hexDigits[ ((uint8_t*)&progId)[i] & 0xf];
+ char progid_str[16];
+
+ // Hexdump.
+ for(int i=0; i < 16; i += 2) {
+ progid_str[i] = ("0123456789ABCDEF")[ (((u8*)&progId)[0] >> 4) & 0xf ];
+ progid_str[i+1] = ("0123456789ABCDEF")[ ((u8*)&progId)[0] & 0xf ];
}
+ static const char *regions[] = {"JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"};
+ static const char *languages[] = {"JA", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"};
+
IFile file;
- Result ret = fileOpen(&file, ARCHIVE_SDMC, path, FS_OPEN_READ);
- if(R_SUCCEEDED(ret))
+ Result eof = fileOpen(&file, ARCHIVE_SDMC, path, FS_OPEN_READ);
+ if(R_SUCCEEDED(eof))
{
- char buf[6];
+ char c;
+ char buf_prog[16];
+ char country_tmp[16];
+ char lang_tmp[16];
u64 total;
// TODO - Open and seek file properly
- ret = IFile_Read(&file, &total, buf, 6);
- IFile_Close(&file);
-
- if(!R_SUCCEEDED(ret) || total < 6) return -1;
+ int i = 0;
+ int state = 0; // State. 0 = get_titleid, 1 = get_region, 2 = get_language, 3 = process entry
+ while(1) {
+ eof = IFile_Read(&file, &total, &c, 1); // Read character.
- // TODO - Split by nl/spaces strtok style. This is not acceptable code.
- // Entries should be of the format:
- // <tid> <region> <lang>
- // No attempt will be made to fix dumbass use of CRLF or CR-only, but newline
- // characters should be treated as spaces.
-
- for(u32 i = 0; i < 7; ++i)
- {
- // TODO - Permit alternative names. They're using fixed strings for ease of use;
- // we need to permit names like 'Japan', 'Europe', etc
- // Maybe read locale data from the FS? http://cldr.unicode.org/
- static const char *regions[] = {"JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"};
+ if (c == ' ' || c == '\n' || c == '\r' || c == '\t') // Skip space characters.
+ continue;
- if(memcmp(buf, regions[i], 3) == 0)
- {
- *regionId = (u8)i;
- break;
+ if (c == '#') { // Comment! Skip until a \n or \r.
+ while(c != '\n' && c != '\r') {
+ eof = IFile_Read(&file, &total, &c, 1); // Read character.
}
- }
-
- for(u32 i = 0; i < 12; ++i)
- {
- // TODO - Same as above.
- static const char *languages[] = {"JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"};
+ continue; // Resume loop.
+ }
+
+ switch(state) {
+ case 0:
+ // Read titleID.
+ for(i = 0; i < 16; i++) {
+ buf_prog[i] = c;
+ if (i != 15)
+ eof = IFile_Read(&file, &total, &c, 1); // Read character.
+ }
+ state = 1;
+ break;
+ case 1:
+ // Read country name. Must be <16 chars, past that is ignored.
+ i = 0;
+ while (c != ' ' && c != '\n' && c != '\r' && c != '\t') { // While we're not reading a space...
+ if (i < 15) {
+ country_tmp[i] = c;
+ i++;
+ }
+ eof = IFile_Read(&file, &total, &c, 1); // Read character unless last one.
+ }
+ state = 2;
+ break;
+ case 2:
+ // Read language name. See country name, code is basically identical.
+ i = 0;
+ while (c != ' ' && c != '\n' && c != '\r' && c != '\t') { // While we're not reading a space...
+ if (i < 15) { // Only read in if <16.
+ lang_tmp[i] = c;
+ i++;
+ }
+ eof = IFile_Read(&file, &total, &c, 1); // Read character unless last one.
+ }
+ state = 3;
+ break;
+ case 3:
+ // Process entry, return and apply if matched.
+ if(!memcmp(progid_str, buf_prog, 16)) {
+ // TitleID matched. Apply language emulation; but first, process language and country.
+ for(i = 0; i < 7; i++) {
+ if(!memcmp(country_tmp, regions[i], 3)) {
+ *regionId = (u8)i;
+ }
+ }
- if(memcmp(buf + 4, languages[i], 2) == 0)
- {
- *languageId = (u8)i;
+ for(u32 i = 0; i < 12; i++) {
+ if(!memcmp(lang_tmp, languages[i], 2)) {
+ *languageId = (u8)i;
+ }
+ }
+ state = 4; // Processed. Go on.
break;
- }
+ }
+ state = 0; // Nope. Move onto the next one.
+ break;
+ }
+
+ if(!R_SUCCEEDED(eof) || total == 0 || state == 4)
+ break;
}
}
- return ret;
+ IFile_Close(&file); // Read to memory.
+ return eof;
}
static u8 *getCfgOffsets(u8 *code, u32 size, u32 *CFGUHandleOffset)
--- /dev/null
+#!/bin/bash
+
+# Downloads the XML from 3dsdb, parses it, and using this information generates
+# a langemu config for single-language single-region games, which can have langemu
+# without any ill consequences.
+
+# Fetch XML.
+wget "http://3dsdb.com/xml.php" -O 3ds.tmp
+
+# Extract all needed fields: titleID, region, language
+grep '<titleid>' 3ds.tmp | sed -e 's|[[:space:]]*||g' -e 's|</*titleid>||g' > titleid.tmp
+grep '<region>' 3ds.tmp | sed -e 's|[[:space:]]*||g' -e 's|</*region>||g' > region.tmp
+grep '<languages>' 3ds.tmp | sed -e 's|[[:space:]]*||g' -e 's|</*languages>||g' > languages.tmp
+
+ENTS=0
+
+# Open all three files and read into full file, checking validity as we go
+while true; do
+ read -r titleid <&3 || break
+ read -r region <&4 || break
+ read -r languages <&5 || break
+
+ if [ "${#titleid}" == "16" ]; then
+ # Length of TID is correct.
+ echo "$languages" | grep ',' 2>&1 >/dev/null
+ R=$?
+ if [ ! $R = 0 ]; then
+ # Only one language found, since no comma.
+ # Output an entry.
+ echo "$titleid $region $languages" | tr [:lower:] [:upper:] | sed -e 's|GER|EUR|g' -e 's|ITA|EUR|g' -e 's|FRA|EUR|g' -e 's|UKV|EUR|g' -e 's|NLD|EUR|g' -e 's|WLD|JPN|g' >> langemu.tmp
+ ENTS=$((ENTS + 1))
+ fi
+ fi
+done 3<titleid.tmp 4<region.tmp 5<languages.tmp
+
+echo "# Autogenerated - $(date) - $ENTS entries" > langemu.conf
+cat langemu.tmp | sort | uniq >> langemu.conf
+rm -f *.tmp
void regenerate_config() {
f_mkdir(PATH_CFW);
- f_mkdir(PATH_CFW "/lib");
- f_mkdir(PATH_FIRMWARES);
- f_mkdir(PATH_PATCHES);
- f_mkdir(PATH_TEMP);
- f_mkdir(PATH_KEYS);
- f_mkdir(PATH_EXEFS);
+ f_mkdir(PATH_CONFIG);
fprintf(BOTTOM_SCREEN, "Created directory structure.\n");
// Structure of config file
struct config_file {
char magic[4]; // "OVAN" for shits and giggles again.
+
uint32_t config_ver; // Config file version.
uint8_t options[256]; // Options in the menu - deliberately large to avoid config version bumps.
#define PATH_CONFIG_DIR PATH_CFW "/etc" // Config file directory.
#define PATH_CONFIG PATH_CONFIG_DIR "/main.conf" // Config file.
-#define PATH_LOCEMU PATH_CONFIG_DIR "/locale.conf" // Locale emulation config
+#define PATH_LOCEMU PATH_CONFIG_DIR "/langemu.conf" // Locale emulation config
#define PATH_CPU_CFG PATH_CONFIG_DIR "/cpu.conf" // CPU settings config
#define PATH_PATCHES PATH_CFW "/bin" // Patch binary folder.