You've already forked pico-loader
mirror of
https://github.com/LNH-team/pico-loader.git
synced 2026-01-09 16:28:35 -08:00
Compare commits
1 Commits
develop
...
feature/ho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c8e98a03f |
@@ -95,3 +95,8 @@ bool dldi_patchTo(dldi_header_t* stub)
|
||||
{
|
||||
return sDldiDriver.PatchTo(stub);
|
||||
}
|
||||
|
||||
void dldi_copyTo(void* target)
|
||||
{
|
||||
memcpy(target, sDldiBuffer, sizeof(sDldiBuffer));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
bool dldi_init();
|
||||
bool dldi_patchTo(dldi_header_t* stub);
|
||||
void dldi_copyTo(void* target);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@@ -10,5 +10,6 @@ extern u8 __bss_size[];
|
||||
pload_header7_t gLoaderHeader
|
||||
{
|
||||
.entryPoint = (void*)&_start,
|
||||
.apiVersion = PICO_LOADER_API_VERSION
|
||||
.apiVersion = PICO_LOADER_API_VERSION,
|
||||
.launcherPath = "/_picoboot.nds"
|
||||
};
|
||||
@@ -220,7 +220,6 @@ void NdsLoader::Load(BootMode bootMode)
|
||||
if (bootMode == BootMode::Normal)
|
||||
{
|
||||
bootType = _romHeader.IsDsiWare() ? BOOT_TYPE_NAND : BOOT_TYPE_CARD;
|
||||
HandleIQueRegionFreePatching();
|
||||
}
|
||||
else if (bootMode == BootMode::Multiboot)
|
||||
{
|
||||
@@ -297,9 +296,6 @@ void NdsLoader::Load(BootMode bootMode)
|
||||
{
|
||||
SetupDsiDeviceList();
|
||||
|
||||
// Set twl wram locking (REG_MBK9) settings from rom header
|
||||
REG_MBK9 = _romHeader.mbk9Setting[0] | (_romHeader.mbk9Setting[1] << 8) | (_romHeader.mbk9Setting[2] << 16);
|
||||
|
||||
u32 scfgExt7 = 0x93FBFB00 | (_romHeader.arm7ScfgExt7 & 0x40407);
|
||||
REG_SCFG_EXT = scfgExt7;
|
||||
REG_SCFG_CLK = 0x187;
|
||||
@@ -348,6 +344,7 @@ void NdsLoader::Load(BootMode bootMode)
|
||||
if (isHomebrew)
|
||||
{
|
||||
InsertArgv();
|
||||
HandleHomebrewPatching();
|
||||
}
|
||||
|
||||
HandleDldiPatching();
|
||||
@@ -433,6 +430,22 @@ void NdsLoader::InsertArgv()
|
||||
HOMEBREW_ARGV->length = argSize;
|
||||
}
|
||||
|
||||
void NdsLoader::HandleHomebrewPatching()
|
||||
{
|
||||
sendToArm9(IPC_COMMAND_ARM9_APPLY_HOMEBREW_PATCHES);
|
||||
sendToArm9(16 * 1024); // required dldi space
|
||||
void* dldiSpace = (void*)receiveFromArm9();
|
||||
char* launcherPath = (char*)receiveFromArm9();
|
||||
if (dldiSpace != nullptr)
|
||||
{
|
||||
dldi_copyTo(dldiSpace);
|
||||
}
|
||||
if (launcherPath != nullptr)
|
||||
{
|
||||
strncpy(launcherPath, _launcherPath, 256);
|
||||
}
|
||||
}
|
||||
|
||||
void NdsLoader::ApplyArm7Patches()
|
||||
{
|
||||
sendToArm9(IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES);
|
||||
@@ -1017,15 +1030,6 @@ bool NdsLoader::TryDecryptSecureArea()
|
||||
return true;
|
||||
}
|
||||
|
||||
void NdsLoader::HandleIQueRegionFreePatching()
|
||||
{
|
||||
if ((_romHeader.flags & 0x80) == 0x80)
|
||||
{
|
||||
_romHeader.flags &= ~0x80;
|
||||
_romHeader.headerCrc = swi_getCrc16(0xFFFF, (void*)&_romHeader, 0x15E);
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleRegion NdsLoader::GetRomRegion(u32 gameCode)
|
||||
{
|
||||
u8 gameRegionCode = (gameCode >> 24) & 0xFF;
|
||||
|
||||
@@ -26,6 +26,13 @@ public:
|
||||
_savePath = savePath;
|
||||
}
|
||||
|
||||
/// @brief Sets the \p launcherPath to use.
|
||||
/// @param launcherPath The launcher path to use.
|
||||
void SetLauncherPath(const TCHAR* launcherPath)
|
||||
{
|
||||
_launcherPath = launcherPath;
|
||||
}
|
||||
|
||||
/// @brief Sets the argv arguments to pass to the rom.
|
||||
/// @param arguments The argv arguments.
|
||||
/// @param argumentsLength The length of the argv arguments.
|
||||
@@ -41,8 +48,9 @@ public:
|
||||
|
||||
private:
|
||||
FIL _romFile;
|
||||
const TCHAR* _romPath;
|
||||
const TCHAR* _savePath;
|
||||
const TCHAR* _romPath = nullptr;
|
||||
const TCHAR* _savePath = nullptr;
|
||||
const TCHAR* _launcherPath = nullptr;
|
||||
u32 _argumentsLength = 0;
|
||||
const char* _arguments = nullptr;
|
||||
nds_header_twl_t _romHeader;
|
||||
@@ -71,9 +79,9 @@ private:
|
||||
char driveLetter, const char* deviceName, const char* path, u8 flags, u8 accessRights);
|
||||
void SetupDsiDeviceList();
|
||||
void InsertArgv();
|
||||
void HandleHomebrewPatching();
|
||||
bool TrySetupDsiWareSave();
|
||||
bool TryDecryptSecureArea();
|
||||
void HandleIQueRegionFreePatching();
|
||||
ConsoleRegion GetRomRegion(u32 gameCode);
|
||||
UserLanguage GetLanguageByRomRegion(ConsoleRegion romRegion);
|
||||
u32 GetSupportedLanguagesByRegion(ConsoleRegion region);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
#define DLDI_MAGIC 0xBF8DA5ED
|
||||
#define DLDI_DRIVER_MAGIC_NONE 0x49444C44
|
||||
|
||||
@@ -222,6 +222,7 @@ extern "C" void loaderMain()
|
||||
sLoader.SetRomPath(gLoaderHeader.loadParams.romPath);
|
||||
handleSavePath();
|
||||
sLoader.SetArguments(gLoaderHeader.loadParams.arguments, gLoaderHeader.loadParams.argumentsLength);
|
||||
sLoader.SetLauncherPath(gLoaderHeader.launcherPath);
|
||||
sLoader.Load(BootMode::Normal);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "errorDisplay/ErrorDisplay.h"
|
||||
#include "LoaderInfo.h"
|
||||
#include "jumpToArm9EntryPoint.h"
|
||||
#include "patches/homebrew/BootstubPatchCode.h"
|
||||
#include "HomebrewBootstub.h"
|
||||
|
||||
#define HANDSHAKE_PART0 0xA
|
||||
#define HANDSHAKE_PART1 0xB
|
||||
@@ -231,6 +233,37 @@ static void handleBootCommand()
|
||||
bootArm9();
|
||||
}
|
||||
|
||||
static void handleApplyHomebrewPatches(u32 dldiRequiredSpace)
|
||||
{
|
||||
// Insert bootstub
|
||||
PatchHeap patchHeap;
|
||||
PatchCodeCollection patchCodeCollection;
|
||||
void* patchSpace = (void*)&HOMEBREW_BOOTSTUB[1];
|
||||
auto romHeader = (const nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader;
|
||||
if (!romHeader->IsTwlRom())
|
||||
{
|
||||
patchSpace = (u8*)patchSpace - 0x2F00000 + 0x2300000;
|
||||
}
|
||||
patchHeap.AddFreeSpace(patchSpace, 32 * 1024 - sizeof(homebrew_bootstub_t));
|
||||
|
||||
void* dldi = patchHeap.Alloc(dldiRequiredSpace);
|
||||
char* launcherPath = (char*)patchHeap.Alloc(256);
|
||||
|
||||
auto bootstubPatchCode = patchCodeCollection.AddUniquePatchCode<BootstubPatchCode>(
|
||||
patchHeap, dldi, launcherPath, &sLoaderInfo,
|
||||
sLoaderPlatform->CreateSdReadPatchCode(patchCodeCollection, patchHeap));
|
||||
|
||||
HOMEBREW_BOOTSTUB->bootSig = HOMEBREW_BOOTSTUB_BOOTSIG;
|
||||
HOMEBREW_BOOTSTUB->arm9Reboot = (void*)bootstubPatchCode->GetArm9RebootFunction();
|
||||
HOMEBREW_BOOTSTUB->arm7Reboot = (void*)bootstubPatchCode->GetArm7RebootFunction();
|
||||
HOMEBREW_BOOTSTUB->bootSize = 32 * 1024 - sizeof(homebrew_bootstub_t);
|
||||
|
||||
patchCodeCollection.CopyAllToTarget();
|
||||
|
||||
ipc_sendWordDirect((u32)dldi);
|
||||
ipc_sendWordDirect((u32)launcherPath);
|
||||
}
|
||||
|
||||
static void handleArm7Command(u32 command)
|
||||
{
|
||||
switch (command)
|
||||
@@ -295,6 +328,12 @@ static void handleArm7Command(u32 command)
|
||||
handleBootCommand();
|
||||
break;
|
||||
}
|
||||
case IPC_COMMAND_ARM9_APPLY_HOMEBREW_PATCHES:
|
||||
{
|
||||
u32 dldiRequiredSpace = receiveFromArm7();
|
||||
handleApplyHomebrewPatches(dldiRequiredSpace);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ bool CardiTryReadCardDmaPatch::FindPatchTarget(PatchContext& patchContext)
|
||||
static u32 getArmBlAddress(const u32* instructionPointer)
|
||||
{
|
||||
u32 blInstruction = *instructionPointer;
|
||||
return (u32)instructionPointer + 8 + ((int)((blInstruction & 0xFFFFFF) << 8) >> 6) + ((blInstruction >> 24) == 0xFA ? 1 : 0);
|
||||
return (u32)instructionPointer + 8 + ((int)((blInstruction & 0xFFFFFF) << 8) >> 6);
|
||||
}
|
||||
|
||||
void CardiTryReadCardDmaPatch::ApplyPatch(PatchContext& patchContext)
|
||||
|
||||
38
arm9/source/patches/homebrew/BootstubPatchCode.h
Normal file
38
arm9/source/patches/homebrew/BootstubPatchCode.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "sections.h"
|
||||
#include "LoaderInfo.h"
|
||||
#include "patches/PatchCode.h"
|
||||
|
||||
DEFINE_SECTION_SYMBOLS(patch_bootstub);
|
||||
|
||||
extern IReadSectorsPatchCode::ReadSectorsFunc patch_bootstub_arm9reboot_readSdSectors_address;
|
||||
extern const void* patch_bootstub_arm9reboot_dldi_address;
|
||||
extern const char* patch_bootstub_arm9reboot_launcher_path;
|
||||
extern loader_info_t patch_bootstub_arm9reboot_loader_info;
|
||||
|
||||
extern "C" void patch_bootstub_arm9reboot(void);
|
||||
extern "C" void patch_bootstub_arm7reboot(void);
|
||||
|
||||
class BootstubPatchCode : public PatchCode
|
||||
{
|
||||
public:
|
||||
BootstubPatchCode(PatchHeap& patchHeap, void* dldi, const char* launcherPath, const loader_info_t* loaderInfo,
|
||||
const IReadSectorsPatchCode* readSectorsPatchCode)
|
||||
: PatchCode(SECTION_START(patch_bootstub), SECTION_SIZE(patch_bootstub), patchHeap)
|
||||
{
|
||||
patch_bootstub_arm9reboot_dldi_address = dldi;
|
||||
patch_bootstub_arm9reboot_readSdSectors_address = readSectorsPatchCode->GetReadSectorsFunction();
|
||||
patch_bootstub_arm9reboot_launcher_path = launcherPath;
|
||||
patch_bootstub_arm9reboot_loader_info = *loaderInfo;
|
||||
}
|
||||
|
||||
const void* GetArm9RebootFunction() const
|
||||
{
|
||||
return GetAddressAtTarget((void*)patch_bootstub_arm9reboot);
|
||||
}
|
||||
|
||||
const void* GetArm7RebootFunction() const
|
||||
{
|
||||
return GetAddressAtTarget((void*)patch_bootstub_arm7reboot);
|
||||
}
|
||||
};
|
||||
237
arm9/source/patches/homebrew/BootstubPatchCode.s
Normal file
237
arm9/source/patches/homebrew/BootstubPatchCode.s
Normal file
@@ -0,0 +1,237 @@
|
||||
.syntax unified
|
||||
.section "patch_bootstub", "ax"
|
||||
.cpu arm946e-s
|
||||
.arm
|
||||
|
||||
.global patch_bootstub_arm9reboot
|
||||
.type patch_bootstub_arm9reboot, %function
|
||||
patch_bootstub_arm9reboot:
|
||||
// disable irqs
|
||||
ldr r0,= 0x04000208
|
||||
strb r0, [r0]
|
||||
|
||||
// sync with arm7
|
||||
// make arm7 jump to arm7_after_arm9_sync
|
||||
ldr r0,= 0x02FFFE34
|
||||
adr r1, arm7_after_arm9_sync
|
||||
str r1, [r0]
|
||||
|
||||
ldr r0,= 0x04000180
|
||||
ldr r1,= 0x0C04000C // LIBNDS_RESET_CODE
|
||||
str r1, [r0, #8]
|
||||
|
||||
// wait for 1 from arm7
|
||||
1:
|
||||
ldrb r1, [r0]
|
||||
cmp r1, #1
|
||||
bne 1b
|
||||
|
||||
// send 1 to arm7
|
||||
ldr r1,= 0x100
|
||||
strh r1, [r0]
|
||||
|
||||
// wait for 0 from arm7
|
||||
1:
|
||||
ldrb r1, [r0]
|
||||
cmp r1, #0
|
||||
bne 1b
|
||||
|
||||
// send 0 to arm7
|
||||
strh r1, [r0]
|
||||
|
||||
.type arm9_after_arm7_sync, %function
|
||||
arm9_after_arm7_sync:
|
||||
// disable irqs
|
||||
ldr r0,= 0x04000208
|
||||
strb r0, [r0]
|
||||
|
||||
// map vram ABCD to lcdc
|
||||
mov r0, #0x04000000
|
||||
ldr r1,= 0x80808080
|
||||
str r1, [r0, #0x240]
|
||||
|
||||
ldr r0,= 0x04000204
|
||||
ldrh r1, [r0]
|
||||
bic r1, r1, #0x880 // map slot 1 and 2 to arm9
|
||||
strh r1, [r0]
|
||||
|
||||
ldr r9, patch_bootstub_arm9reboot_readSdSectors_address
|
||||
ldr sp,= 0x02000000 + (16 * 1024) // the platform code needs a valid stack pointer
|
||||
|
||||
// load pico loader arm9
|
||||
adr r5, patch_bootstub_arm9reboot_loader_info
|
||||
ldmia r5!, {r4,r6,r7} // clusterShift, database, clusterMap[0]
|
||||
mov r7, #0x06800000
|
||||
mov r11, pc
|
||||
b loadData
|
||||
|
||||
// load pico loader arm7
|
||||
adr r5, patch_bootstub_arm9reboot_loader_info + 52
|
||||
ldr r7,= 0x06840000
|
||||
mov r11, pc
|
||||
b loadData
|
||||
|
||||
ldr r7,= 0x06840000
|
||||
adr r5, patch_bootstub_arm9reboot_loader_info
|
||||
ldrh r0, [r5, #2] // loader_info_t::picoLoaderBootDrive
|
||||
strh r0, [r7, #8] // pload_header7_t::bootDrive
|
||||
|
||||
ldr r0, patch_bootstub_arm9reboot_dldi_address
|
||||
str r0, [r7, #4]
|
||||
|
||||
// set loadParams->romPath
|
||||
ldr r0, patch_bootstub_arm9reboot_launcher_path
|
||||
mov r1, #256
|
||||
add r3, r7, #0xC
|
||||
1:
|
||||
ldr r2, [r0], #4
|
||||
str r2, [r3], #4
|
||||
subs r1, r1, #4
|
||||
bne 1b
|
||||
|
||||
// map vram CD to arm7
|
||||
ldr r0,= 0x04000240
|
||||
ldr r1,= 0x8A82
|
||||
strh r1, [r0, #2]
|
||||
|
||||
// start arm7 by sending 3
|
||||
ldr r0,= 0x04000180
|
||||
mov r1, #0x300
|
||||
strh r1, [r0]
|
||||
|
||||
// invalidate icache
|
||||
mov r0, #0
|
||||
mcr p15, 0, r0, c7, c5, 0
|
||||
|
||||
mov pc, #0x06800000
|
||||
|
||||
loadData_loop:
|
||||
sub r3, r3, #2
|
||||
add r0, r6, r3, lsl r4 // start sector
|
||||
mov r1, r7 // dst
|
||||
mov r2, r2, lsl r4 // sector count
|
||||
add r7, r7, r2, lsl #9
|
||||
|
||||
blx r9 // read sectors
|
||||
|
||||
loadData:
|
||||
ldmia r5!, {r2, r3} // ncl, startSector
|
||||
cmp r2, #0
|
||||
bne loadData_loop
|
||||
bx r11
|
||||
|
||||
.balign 4
|
||||
|
||||
.pool
|
||||
|
||||
.global patch_bootstub_arm9reboot_readSdSectors_address
|
||||
patch_bootstub_arm9reboot_readSdSectors_address:
|
||||
.word 0
|
||||
|
||||
.global patch_bootstub_arm9reboot_dldi_address
|
||||
patch_bootstub_arm9reboot_dldi_address:
|
||||
.word 0
|
||||
|
||||
.global patch_bootstub_arm9reboot_launcher_path
|
||||
patch_bootstub_arm9reboot_launcher_path:
|
||||
.word 0
|
||||
|
||||
.global patch_bootstub_arm9reboot_loader_info
|
||||
patch_bootstub_arm9reboot_loader_info:
|
||||
.space 0x70 // sizeof(loader_info_t)
|
||||
|
||||
.balign 4
|
||||
|
||||
.cpu arm7tdmi
|
||||
|
||||
.global patch_bootstub_arm7reboot
|
||||
.type patch_bootstub_arm7reboot, %function
|
||||
patch_bootstub_arm7reboot:
|
||||
// disable irqs
|
||||
mov r0, #0x04000000
|
||||
strb r0, [r0, #0x208]
|
||||
|
||||
bl copy_arm7_code
|
||||
|
||||
// sync with arm9
|
||||
// make arm9 jump to arm9_after_arm7_sync
|
||||
ldr r0,= 0x02FFFE24
|
||||
adr r1, arm9_after_arm7_sync
|
||||
str r1, [r0]
|
||||
|
||||
ldr r0,= 0x04000180
|
||||
ldr r1,= 0x0C04000C // LIBNDS_RESET_CODE
|
||||
str r1, [r0, #8]
|
||||
|
||||
mov pc, #0x03800000 // arm7 private ram
|
||||
|
||||
.type arm7_after_arm9_sync, %function
|
||||
arm7_after_arm9_sync:
|
||||
// disable irqs
|
||||
mov r0, #0x04000000
|
||||
strb r0, [r0, #0x208]
|
||||
|
||||
bl copy_arm7_code
|
||||
mov r0, #0x03800000
|
||||
add r0, r0, #(arm7_iwram_region_after_arm9_sync - arm7_iwram_region)
|
||||
bx r0
|
||||
|
||||
copy_arm7_code:
|
||||
adr r0, arm7_iwram_region
|
||||
adr r1, arm7_iwram_region_end
|
||||
mov r2, #0x03800000 // arm7 private ram
|
||||
1:
|
||||
cmp r0, r1
|
||||
ldrne r3, [r0], #4
|
||||
strne r3, [r2], #4
|
||||
bne 1b
|
||||
2:
|
||||
bx lr
|
||||
|
||||
.balign 4
|
||||
.pool
|
||||
|
||||
// Code loaded to arm7 private wram to avoid main memory contention
|
||||
arm7_iwram_region:
|
||||
// wait for 1 from arm9
|
||||
1:
|
||||
ldrb r1, [r0]
|
||||
cmp r1, #1
|
||||
bne 1b
|
||||
|
||||
// send 1 to arm9
|
||||
ldr r1,= 0x100
|
||||
strh r1, [r0]
|
||||
|
||||
// wait for 0 from arm9
|
||||
1:
|
||||
ldrb r1, [r0]
|
||||
cmp r1, #0
|
||||
bne 1b
|
||||
|
||||
// send 0 to arm9
|
||||
strh r1, [r0]
|
||||
|
||||
arm7_iwram_region_after_arm9_sync:
|
||||
ldr r0,= 0x04000180
|
||||
|
||||
// wait for 3 from arm9
|
||||
1:
|
||||
ldrb r1, [r0]
|
||||
cmp r1, #3
|
||||
bne 1b
|
||||
|
||||
// boot pico loader arm7
|
||||
mov r0, #0x06000000
|
||||
ldr r0, [r0]
|
||||
bx r0
|
||||
|
||||
.balign 4
|
||||
.pool
|
||||
.balign 4
|
||||
|
||||
arm7_iwram_region_end:
|
||||
|
||||
.balign 4
|
||||
|
||||
.end
|
||||
13
common/HomebrewBootstub.h
Normal file
13
common/HomebrewBootstub.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#define HOMEBREW_BOOTSTUB_BOOTSIG 0x62757473746F6F62ULL
|
||||
|
||||
struct homebrew_bootstub_t
|
||||
{
|
||||
u64 bootSig;
|
||||
void* arm9Reboot;
|
||||
void* arm7Reboot;
|
||||
u32 bootSize;
|
||||
};
|
||||
|
||||
#define HOMEBREW_BOOTSTUB ((homebrew_bootstub_t*)0x02FF4000)
|
||||
@@ -12,3 +12,4 @@
|
||||
#define IPC_COMMAND_ARM9_DISPLAY_ERROR 0xB
|
||||
#define IPC_COMMAND_ARM9_SWITCH_TO_DS_MODE 0xD
|
||||
#define IPC_COMMAND_ARM9_BOOT 0xF
|
||||
#define IPC_COMMAND_ARM9_APPLY_HOMEBREW_PATCHES 0x10
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief The Pico Loader API version supported by this header file.
|
||||
#define PICO_LOADER_API_VERSION 1
|
||||
#define PICO_LOADER_API_VERSION 2
|
||||
|
||||
/// @brief The minimum Pico Loader API version that supports the launcherPath setting.
|
||||
#define PICO_LOADER_LAUNCHER_PATH_MIN_API_VERSION 2
|
||||
|
||||
/// @brief Enum to specify the drive to boot from.
|
||||
typedef enum
|
||||
@@ -52,4 +55,22 @@ typedef struct
|
||||
|
||||
/// @brief The load params, see \see pload_params_t.
|
||||
pload_params_t loadParams;
|
||||
|
||||
// === api version 2 ===
|
||||
|
||||
/// @brief The path of the rom to return to when exiting an application.
|
||||
char launcherPath[256];
|
||||
} pload_header7_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static inline bool pload_supportsLauncherPath(const pload_header7_t* header)
|
||||
{
|
||||
return header->apiVersion >= PICO_LOADER_LAUNCHER_PATH_MIN_API_VERSION;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user