diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 7b7874b1..f7c2ee77 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -patreon: staplebutter +patreon: Arisotura custom: ["https://paypal.me/Arisotura", "http://melonds.kuribo64.net/donate.php"] diff --git a/.github/azure-workflows/build-mac-arm64.yml b/.github/azure-workflows/build-mac-arm64.yml deleted file mode 100644 index 721c6acc..00000000 --- a/.github/azure-workflows/build-mac-arm64.yml +++ /dev/null @@ -1,26 +0,0 @@ -trigger: -- master - -pool: - name: Default - demands: - - agent.name -equals MacStadium-ARM64-Mac - -workspace: - clean: all - -steps: -- script: mkdir $(Pipeline.Workspace)/build - displayName: 'Create build environment' - -- script: arch -arm64 cmake $(Build.SourcesDirectory) -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DMACOS_BUNDLE_LIBS=ON -DMACOS_BUILD_DMG=ON -DUSE_QT6=ON - displayName: 'Configure' - workingDirectory: $(Pipeline.Workspace)/build - -- script: arch -arm64 make -j$(sysctl -n hw.logicalcpu) - displayName: 'Make' - workingDirectory: $(Pipeline.Workspace)/build - -- publish: $(Pipeline.Workspace)/build/melonDS.dmg - artifact: melonDS.dmg - diff --git a/.github/azure-workflows/build-mac-x86_64.yml b/.github/azure-workflows/build-mac-x86_64.yml deleted file mode 100644 index 3151c256..00000000 --- a/.github/azure-workflows/build-mac-x86_64.yml +++ /dev/null @@ -1,24 +0,0 @@ -trigger: -- master - -pool: - vmImage: macOS-10.15 - -steps: -- script: brew install llvm sdl2 qt@6 libslirp libarchive libepoxy - displayName: 'Install dependencies' - -- script: mkdir $(Pipeline.Workspace)/build - displayName: 'Create build environment' - -- script: cmake $(Build.SourcesDirectory) -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DMACOS_BUNDLE_LIBS=ON -DMACOS_BUILD_DMG=ON -DUSE_QT6=ON - displayName: 'Configure' - workingDirectory: $(Pipeline.Workspace)/build - -- script: make -j$(sysctl -n hw.logicalcpu) - displayName: 'Make' - workingDirectory: $(Pipeline.Workspace)/build - -- publish: $(Pipeline.Workspace)/build/melonDS.dmg - artifact: melonDS.dmg - diff --git a/.github/workflows/build-macos-universal.yml b/.github/workflows/build-macos-universal.yml new file mode 100644 index 00000000..0f0089f3 --- /dev/null +++ b/.github/workflows/build-macos-universal.yml @@ -0,0 +1,73 @@ +name: CMake Build (macOS Universal) + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + prepare: + runs-on: [self-hosted, macOS, ARM64] + + steps: + - name: Clean workspace + run: rm -rf ${{runner.workspace}}/build + + - uses: actions/checkout@v3 + + + build-arm64: + needs: prepare + runs-on: [self-hosted, macOS, ARM64] + env: + homebrew_prefix: /opt/homebrew + + steps: + - name: Create build directory + run: mkdir -p ${{runner.workspace}}/build/arm64 + + - name: Configure + working-directory: ${{runner.workspace}}/build/arm64 + run: arch -arm64 ${{env.homebrew_prefix}}/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="${{env.homebrew_prefix}}/opt/qt@6;${{env.homebrew_prefix}}/opt/libarchive" -DPKG_CONFIG_EXECUTABLE=${{env.homebrew_prefix}}/bin/pkg-config -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON + + - name: Make + working-directory: ${{runner.workspace}}/build/arm64 + run: arch -arm64 make -j$(sysctl -n hw.logicalcpu) + + build-x86_64: + needs: prepare + runs-on: [self-hosted, macOS, ARM64] + env: + homebrew_prefix: /usr/local + + steps: + - name: Create build directory + run: mkdir -p ${{runner.workspace}}/build/x86_64 + + - name: Configure + working-directory: ${{runner.workspace}}/build/x86_64 + run: arch -x86_64 ${{env.homebrew_prefix}}/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="${{env.homebrew_prefix}}/opt/qt@6;${{env.homebrew_prefix}}/opt/libarchive" -DPKG_CONFIG_EXECUTABLE=${{env.homebrew_prefix}}/bin/pkg-config -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON + + - name: Make + working-directory: ${{runner.workspace}}/build/x86_64 + run: arch -x86_64 make -j$(sysctl -n hw.logicalcpu) + + universal-binary: + needs: [build-arm64, build-x86_64] + runs-on: [self-hosted, macOS, ARM64] + + steps: + - name: Merge binaries + run: $GITHUB_WORKSPACE/tools/mac-universal.py ${{runner.workspace}}/build/arm64/melonDS.app ${{runner.workspace}}/build/x86_64/melonDS.app ${{runner.workspace}}/build/universal/melonDS.app + + - name: Create DMG + run: hdiutil create -fs HFS+ -volname melonDS -srcfolder ${{runner.workspace}}/build/universal/melonDS.app -ov -format UDBZ ${{runner.workspace}}/build/universal/melonDS.dmg + + - uses: actions/upload-artifact@v3 + with: + name: macOS-universal + path: ${{runner.workspace}}/build/universal/melonDS.dmg + diff --git a/.github/workflows/build-ubuntu-aarch64.yml b/.github/workflows/build-ubuntu-aarch64.yml index c9a602c9..c5004aa2 100644 --- a/.github/workflows/build-ubuntu-aarch64.yml +++ b/.github/workflows/build-ubuntu-aarch64.yml @@ -33,7 +33,7 @@ jobs: rm /etc/apt/sources.list mv /etc/apt/sources.list{.new,} apt update - DEBIAN_FRONTEND=noninteractive apt install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu {libsdl2,qtbase5,libslirp,libarchive,libepoxy}-dev:arm64 cmake dpkg-dev + DEBIAN_FRONTEND=noninteractive apt install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu {libsdl2,qtbase5,qtmultimedia5,libslirp,libarchive,libepoxy}-dev:arm64 cmake dpkg-dev - name: Configure shell: bash run: | diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index bbbec5c7..b9580e0c 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -19,7 +19,7 @@ jobs: run: | sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list sudo apt update - sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default libslirp0 libslirp-dev libarchive-dev libepoxy-dev --allow-downgrades + sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev libepoxy-dev --allow-downgrades - name: Create build environment run: mkdir ${{runner.workspace}}/build - name: Configure diff --git a/CMakeLists.txt b/CMakeLists.txt index 297f71d1..223e8085 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,10 +73,8 @@ if (ENABLE_LTO) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") -set(CMAKE_C_FLAGS_RELEASE "-O3") -set(CMAKE_CXX_FLAGS_RELEASE "-O3") +string(REPLACE "-O2" "-O3" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") +string(REPLACE "-O2" "-O3" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") if (NOT APPLE) set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s") diff --git a/README.md b/README.md index 4e28439e..76128dc9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

+

melonDS

@@ -6,9 +6,9 @@
- - - + + +

@@ -36,12 +36,12 @@ As for the rest, the interface should be pretty straightforward. If you have a q ### Linux 1. Install dependencies: - * Ubuntu 22.04: `sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev libslirp-dev libarchive-dev libepoxy-dev` - * Older Ubuntu: `sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default libslirp-dev libarchive-dev libepoxy-dev` - * Arch Linux: `sudo pacman -S base-devel cmake git libpcap sdl2 qt5-base libslirp libarchive libepoxy` + * Ubuntu 22.04: `sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtmultimedia5-dev libslirp-dev libarchive-dev libepoxy-dev` + * Older Ubuntu: `sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtmultimedia5-dev libslirp-dev libarchive-dev libepoxy-dev` + * Arch Linux: `sudo pacman -S base-devel cmake git libpcap sdl2 qt5-base qt5-multimedia libslirp libarchive libepoxy` 3. Download the melonDS repository and prepare: ```bash - git clone https://github.com/Arisotura/melonDS + git clone https://github.com/melonDS-emu/melonDS cd melonDS ``` @@ -61,11 +61,11 @@ As for the rest, the interface should be pretty straightforward. If you have a q ``` 5. Download the melonDS repository and prepare: ```bash - git clone https://github.com/Arisotura/melonDS + git clone https://github.com/melonDS-emu/melonDS cd melonDS ``` #### Dynamic builds (with DLLs) -5. Install dependencies: `pacman -S make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5,libslirp,libarchive,libepoxy}` +5. Install dependencies: `pacman -S make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5-base,qt5-svg,qt5-multimedia,libslirp,libarchive,libepoxy}` 6. Compile: ```bash cmake -B build -G "MSYS Makefiles" @@ -89,7 +89,7 @@ If everything went well, melonDS should now be in the `build` folder. 2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libslirp libarchive libepoxy` 3. Download the melonDS repository and prepare: ```zsh - git clone https://github.com/Arisotura/melonDS + git clone https://github.com/melonDS-emu/melonDS cd melonDS ``` 4. Compile: diff --git a/res/melon.plist.in b/res/melon.plist.in index 20d385a0..1804d9e9 100644 --- a/res/melon.plist.in +++ b/res/melon.plist.in @@ -18,10 +18,14 @@ ${melonDS_VERSION} NSHumanReadableCopyright Licensed under GPLv3 + NSPrincipalClass + NSApplication NSHighResolutionCapable NSMicrophoneUsageDescription We need microphone access so you can use the emulated DS microphone + NSCameraUsageDescription + Camera access is needed for emulation of the DSi's cameras CFBundleDocumentTypes diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 797f04ba..11c48f00 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,6 +106,8 @@ if (ENABLE_JIT) endif() add_subdirectory(teakra EXCLUDE_FROM_ALL) +# Workaround for building teakra with -O0 on Windows either failing or hanging forever +target_compile_options(teakra PRIVATE "$<$:-Og>") target_link_libraries(core PRIVATE teakra) find_library(m MATH_LIBRARY) diff --git a/src/DSi.cpp b/src/DSi.cpp index db634407..301bd570 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -94,6 +94,7 @@ bool Init() #endif if (!DSi_I2C::Init()) return false; + if (!DSi_CamModule::Init()) return false; if (!DSi_AES::Init()) return false; if (!DSi_DSP::Init()) return false; @@ -121,6 +122,7 @@ void DeInit() #endif DSi_I2C::DeInit(); + DSi_CamModule::DeInit(); DSi_AES::DeInit(); DSi_DSP::DeInit(); @@ -142,6 +144,7 @@ void Reset() for (int i = 0; i < 8; i++) NDMAs[i]->Reset(); DSi_I2C::Reset(); + DSi_CamModule::Reset(); DSi_DSP::Reset(); SDMMC->CloseHandles(); @@ -169,6 +172,11 @@ void Reset() GPU::DispStat[1] |= (1<<6); } +void Stop() +{ + DSi_CamModule::Stop(); +} + void DoSavestate(Savestate* file) { file->Section("DSIG"); @@ -241,7 +249,7 @@ void DoSavestate(Savestate* file) NDMAs[i]->DoSavestate(file); DSi_AES::DoSavestate(file); - DSi_Camera::DoSavestate(file); + DSi_CamModule::DoSavestate(file); DSi_DSP::DoSavestate(file); DSi_I2C::DoSavestate(file); SDMMC->DoSavestate(file); @@ -511,30 +519,24 @@ void SetupDirectBoot() ARM9Write32(0x02FFE000+i, tmp); } - FILE* nand = Platform::OpenLocalFile(Platform::GetConfigString(Platform::DSi_NANDPath), "r+b"); - if (nand) + if (DSi_NAND::Init(&DSi::ARM7iBIOS[0x8308])) { - if (DSi_NAND::Init(nand, &DSi::ARM7iBIOS[0x8308])) - { - u8 userdata[0x1B0]; - DSi_NAND::ReadUserData(userdata); - for (u32 i = 0; i < 0x128; i+=4) - ARM9Write32(0x02000400+i, *(u32*)&userdata[0x88+i]); + u8 userdata[0x1B0]; + DSi_NAND::ReadUserData(userdata); + for (u32 i = 0; i < 0x128; i+=4) + ARM9Write32(0x02000400+i, *(u32*)&userdata[0x88+i]); - u8 hwinfoS[0xA4]; - u8 hwinfoN[0x9C]; - DSi_NAND::ReadHardwareInfo(hwinfoS, hwinfoN); + u8 hwinfoS[0xA4]; + u8 hwinfoN[0x9C]; + DSi_NAND::ReadHardwareInfo(hwinfoS, hwinfoN); - for (u32 i = 0; i < 0x14; i+=4) - ARM9Write32(0x02000600+i, *(u32*)&hwinfoN[0x88+i]); + for (u32 i = 0; i < 0x14; i+=4) + ARM9Write32(0x02000600+i, *(u32*)&hwinfoN[0x88+i]); - for (u32 i = 0; i < 0x18; i+=4) - ARM9Write32(0x02FFFD68+i, *(u32*)&hwinfoS[0x88+i]); + for (u32 i = 0; i < 0x18; i+=4) + ARM9Write32(0x02FFFD68+i, *(u32*)&hwinfoS[0x88+i]); - DSi_NAND::DeInit(); - } - - fclose(nand); + DSi_NAND::DeInit(); } u8 nwifiver = SPI_Firmware::GetNWifiVersion(); @@ -707,19 +709,14 @@ bool LoadNAND() { printf("Loading DSi NAND\n"); - FILE* nand = Platform::OpenLocalFile(Platform::GetConfigString(Platform::DSi_NANDPath), "r+b"); - if (!nand) - { - printf("Failed to open DSi NAND\n"); - return false; - } - - if (!DSi_NAND::Init(nand, &DSi::ARM7iBIOS[0x8308])) + if (!DSi_NAND::Init(&DSi::ARM7iBIOS[0x8308])) { printf("Failed to load DSi NAND\n"); return false; } + FILE* nand = DSi_NAND::GetFile(); + // Make sure NWRAM is accessible. // The Bits are set to the startup values in Reset() and we might // still have them on default (0) or some bits cleared by the previous @@ -2241,11 +2238,14 @@ u8 ARM9IORead8(u32 addr) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return 0; - return DSi_Camera::Read8(addr); + return DSi_CamModule::Read8(addr); } - if (addr >= 0x04004300 && addr <= 0x04004400) - return DSi_DSP::Read16(addr); + if ((addr & 0xFFFFFF00) == 0x04004300) + { + if (!(SCFG_EXT[0] & (1<<18))) return 0; + return DSi_DSP::Read8(addr); + } return NDS::ARM9IORead8(addr); } @@ -2273,11 +2273,14 @@ u16 ARM9IORead16(u32 addr) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return 0; - return DSi_Camera::Read16(addr); + return DSi_CamModule::Read16(addr); } - if (addr >= 0x04004300 && addr <= 0x04004400) - return DSi_DSP::Read32(addr); + if ((addr & 0xFFFFFF00) == 0x04004300) + { + if (!(SCFG_EXT[0] & (1<<18))) return 0; + return DSi_DSP::Read16(addr); + } return NDS::ARM9IORead16(addr); } @@ -2335,7 +2338,13 @@ u32 ARM9IORead32(u32 addr) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return 0; - return DSi_Camera::Read32(addr); + return DSi_CamModule::Read32(addr); + } + + if ((addr & 0xFFFFFF00) == 0x04004300) + { + if (!(SCFG_EXT[0] & (1<<18))) return 0; + return DSi_DSP::Read32(addr); } return NDS::ARM9IORead32(addr); @@ -2356,7 +2365,7 @@ void ARM9IOWrite8(u32 addr, u8 val) return; case 0x04004006: - if (!(SCFG_EXT[1] & (1 << 31))) /* no access to SCFG Registers if disabled*/ + if (!(SCFG_EXT[0] & (1 << 31))) /* no access to SCFG Registers if disabled*/ return; SCFG_RST = (SCFG_RST & 0xFF00) | val; DSi_DSP::SetRstLine(val & 1); @@ -2366,7 +2375,7 @@ void ARM9IOWrite8(u32 addr, u8 val) case 0x04004041: case 0x04004042: case 0x04004043: - if (!(SCFG_EXT[1] & (1 << 31))) /* no access to SCFG Registers if disabled*/ + if (!(SCFG_EXT[0] & (1 << 31))) /* no access to SCFG Registers if disabled*/ return; MapNWRAM_A(addr & 3, val); return; @@ -2378,7 +2387,7 @@ void ARM9IOWrite8(u32 addr, u8 val) case 0x04004049: case 0x0400404A: case 0x0400404B: - if (!(SCFG_EXT[1] & (1 << 31))) /* no access to SCFG Registers if disabled*/ + if (!(SCFG_EXT[0] & (1 << 31))) /* no access to SCFG Registers if disabled*/ return; MapNWRAM_B((addr - 0x04) & 7, val); return; @@ -2390,7 +2399,7 @@ void ARM9IOWrite8(u32 addr, u8 val) case 0x04004051: case 0x04004052: case 0x04004053: - if (!(SCFG_EXT[1] & (1 << 31))) /* no access to SCFG Registers if disabled*/ + if (!(SCFG_EXT[0] & (1 << 31))) /* no access to SCFG Registers if disabled*/ return; MapNWRAM_C((addr-0x0C) & 7, val); return; @@ -2399,13 +2408,13 @@ void ARM9IOWrite8(u32 addr, u8 val) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return; - return DSi_Camera::Write8(addr, val); + return DSi_CamModule::Write8(addr, val); } - if (addr >= 0x04004300 && addr <= 0x04004400) + if ((addr & 0xFFFFFF00) == 0x04004300) { - DSi_DSP::Write8(addr, val); - return; + if (!(SCFG_EXT[0] & (1<<18))) return; + return DSi_DSP::Write8(addr, val); } return NDS::ARM9IOWrite8(addr, val); @@ -2459,13 +2468,13 @@ void ARM9IOWrite16(u32 addr, u16 val) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return; - return DSi_Camera::Write16(addr, val); + return DSi_CamModule::Write16(addr, val); } - if (addr >= 0x04004300 && addr <= 0x04004400) + if ((addr & 0xFFFFFF00) == 0x04004300) { - DSi_DSP::Write16(addr, val); - return; + if (!(SCFG_EXT[0] & (1<<18))) return; + return DSi_DSP::Write16(addr, val); } return NDS::ARM9IOWrite16(addr, val); @@ -2609,7 +2618,13 @@ void ARM9IOWrite32(u32 addr, u32 val) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return; - return DSi_Camera::Write32(addr, val); + return DSi_CamModule::Write32(addr, val); + } + + if ((addr & 0xFFFFFF00) == 0x04004300) + { + if (!(SCFG_EXT[0] & (1<<18))) return; + return DSi_DSP::Write32(addr, val); } return NDS::ARM9IOWrite32(addr, val); @@ -2681,7 +2696,7 @@ u16 ARM7IORead16(u32 addr) case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (ConsoleID >> 32) & 0xFFFF; case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return ConsoleID >> 48; case 0x04004D08: return 0; - + case 0x4004700: return DSi_DSP::SNDExCnt; } diff --git a/src/DSi.h b/src/DSi.h index 4ccddc02..ef60b309 100644 --- a/src/DSi.h +++ b/src/DSi.h @@ -56,6 +56,7 @@ extern u32 NWRAMMask[2][3]; bool Init(); void DeInit(); void Reset(); +void Stop(); void DoSavestate(Savestate* file); diff --git a/src/DSi_Camera.cpp b/src/DSi_Camera.cpp index e83434f8..20f15cfe 100644 --- a/src/DSi_Camera.cpp +++ b/src/DSi_Camera.cpp @@ -20,17 +20,25 @@ #include #include "DSi.h" #include "DSi_Camera.h" +#include "Platform.h" -DSi_Camera* DSi_Camera0; // 78 / facing outside -DSi_Camera* DSi_Camera1; // 7A / selfie cam +namespace DSi_CamModule +{ -u16 DSi_Camera::ModuleCnt; -u16 DSi_Camera::Cnt; +Camera* Camera0; // 78 / facing outside +Camera* Camera1; // 7A / selfie cam -u8 DSi_Camera::FrameBuffer[640*480*4]; -u32 DSi_Camera::FrameLength; -u32 DSi_Camera::TransferPos; +u16 ModuleCnt; +u16 Cnt; + +u32 CropStart, CropEnd; + +// pixel data buffer holds a maximum of 512 words, regardless of how long scanlines are +u32 DataBuffer[512]; +u32 BufferReadPos, BufferWritePos; +u32 BufferNumLines; +Camera* CurCamera; // note on camera data/etc intervals // on hardware those are likely affected by several factors @@ -41,133 +49,359 @@ const u32 kIRQInterval = 1120000; // ~30 FPS const u32 kTransferStart = 60000; -bool DSi_Camera::Init() +bool Init() { - DSi_Camera0 = new DSi_Camera(0); - DSi_Camera1 = new DSi_Camera(1); + Camera0 = new Camera(0); + Camera1 = new Camera(1); return true; } -void DSi_Camera::DeInit() +void DeInit() { - delete DSi_Camera0; - delete DSi_Camera1; + delete Camera0; + delete Camera1; } -void DSi_Camera::Reset() +void Reset() { - DSi_Camera0->ResetCam(); - DSi_Camera1->ResetCam(); + Camera0->Reset(); + Camera1->Reset(); ModuleCnt = 0; // CHECKME Cnt = 0; - memset(FrameBuffer, 0, 640*480*4); - TransferPos = 0; - FrameLength = 256*192*2; // TODO: make it check frame size, data type, etc + CropStart = 0; + CropEnd = 0; + + memset(DataBuffer, 0, 512*sizeof(u32)); + BufferReadPos = 0; + BufferWritePos = 0; + BufferNumLines = 0; + CurCamera = nullptr; NDS::ScheduleEvent(NDS::Event_DSi_CamIRQ, true, kIRQInterval, IRQ, 0); } -void DSi_Camera::DoSavestate(Savestate* file) +void Stop() +{ + Camera0->Stop(); + Camera1->Stop(); +} + +void DoSavestate(Savestate* file) { file->Section("CAMi"); file->Var16(&ModuleCnt); file->Var16(&Cnt); - file->VarArray(FrameBuffer, sizeof(FrameBuffer)); + /*file->VarArray(FrameBuffer, sizeof(FrameBuffer)); file->Var32(&TransferPos); - file->Var32(&FrameLength); + file->Var32(&FrameLength);*/ - DSi_Camera0->DoCamSavestate(file); - DSi_Camera1->DoCamSavestate(file); + Camera0->DoSavestate(file); + Camera1->DoSavestate(file); } -void DSi_Camera::IRQ(u32 param) +void IRQ(u32 param) { - DSi_Camera* activecam = nullptr; + Camera* activecam = nullptr; - // TODO: check which camera has priority if both are activated - // (or does it just jumble both data sources together, like it - // does for, say, overlapping VRAM?) - if (DSi_Camera0->IsActivated()) activecam = DSi_Camera0; - else if (DSi_Camera1->IsActivated()) activecam = DSi_Camera1; + // TODO: cameras don't have any priority! + // activating both together will jumble the image data together + if (Camera0->IsActivated()) activecam = Camera0; + else if (Camera1->IsActivated()) activecam = Camera1; if (activecam) { - RequestFrame(activecam->Num); + activecam->StartTransfer(); if (Cnt & (1<<11)) NDS::SetIRQ(0, NDS::IRQ_DSi_Camera); if (Cnt & (1<<15)) - NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, kTransferStart, Transfer, 0); + { + BufferReadPos = 0; + BufferWritePos = 0; + BufferNumLines = 0; + CurCamera = activecam; + NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, kTransferStart, TransferScanline, 0); + } } NDS::ScheduleEvent(NDS::Event_DSi_CamIRQ, true, kIRQInterval, IRQ, 0); } -void DSi_Camera::RequestFrame(u32 cam) +void TransferScanline(u32 line) { - if (!(Cnt & (1<<13))) printf("CAMERA: !! REQUESTING YUV FRAME\n"); + u32* dstbuf = &DataBuffer[BufferWritePos]; + int maxlen = 512 - BufferWritePos; - // TODO: picture size, data type, cropping, etc - // generate test pattern - // TODO: get picture from platform (actual camera, video file, whatever source) - for (u32 y = 0; y < 192; y++) + u32 tmpbuf[512]; + int datalen = CurCamera->TransferScanline(tmpbuf, 512); + + // TODO: must be tweaked such that each block has enough time to transfer + u32 delay = datalen*4 + 16; + + int copystart = 0; + int copylen = datalen; + + if (Cnt & (1<<14)) { - for (u32 x = 0; x < 256; x++) + // crop picture + + int ystart = (CropStart >> 16) & 0x1FF; + int yend = (CropEnd >> 16) & 0x1FF; + if (line < ystart || line > yend) { - u16* px = (u16*)&FrameBuffer[((y*256) + x) * 2]; + if (!CurCamera->TransferDone()) + NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, delay, TransferScanline, line+1); - if ((x & 0x8) ^ (y & 0x8)) - *px = 0x8000; - else - *px = 0xFC00 | ((y >> 3) << 5); + return; } + + int xstart = (CropStart >> 1) & 0x1FF; + int xend = (CropEnd >> 1) & 0x1FF; + + copystart = xstart; + copylen = xend+1 - xstart; + + if ((copystart + copylen) > datalen) + copylen = datalen - copystart; + if (copylen < 0) + copylen = 0; } -} -void DSi_Camera::Transfer(u32 pos) -{ - u32 numscan = (Cnt & 0x000F) + 1; - u32 numpix = numscan * 256; // CHECKME - - // TODO: present data - //printf("CAM TRANSFER POS=%d/%d\n", pos, 0x6000*2); - - DSi::CheckNDMAs(0, 0x0B); - - pos += numpix; - if (pos >= 0x6000*2) // HACK + if (copylen > maxlen) { - // transfer done + copylen = maxlen; + Cnt |= (1<<4); + } + + if (Cnt & (1<<13)) + { + // convert to RGB + + for (u32 i = 0; i < copylen; i++) + { + u32 val = tmpbuf[copystart + i]; + + int y1 = val & 0xFF; + int u = (val >> 8) & 0xFF; + int y2 = (val >> 16) & 0xFF; + int v = (val >> 24) & 0xFF; + + u -= 128; v -= 128; + + int r1 = y1 + ((v * 91881) >> 16); + int g1 = y1 - ((v * 46793) >> 16) - ((u * 22544) >> 16); + int b1 = y1 + ((u * 116129) >> 16); + + int r2 = y2 + ((v * 91881) >> 16); + int g2 = y2 - ((v * 46793) >> 16) - ((u * 22544) >> 16); + int b2 = y2 + ((u * 116129) >> 16); + + r1 = std::clamp(r1, 0, 255); g1 = std::clamp(g1, 0, 255); b1 = std::clamp(b1, 0, 255); + r2 = std::clamp(r2, 0, 255); g2 = std::clamp(g2, 0, 255); b2 = std::clamp(b2, 0, 255); + + u32 col1 = (r1 >> 3) | ((g1 >> 3) << 5) | ((b1 >> 3) << 10) | 0x8000; + u32 col2 = (r2 >> 3) | ((g2 >> 3) << 5) | ((b2 >> 3) << 10) | 0x8000; + + dstbuf[i] = col1 | (col2 << 16); + } } else { - // keep going + // return raw data - // TODO: must be tweaked such that each block has enough time to transfer - u32 delay = numpix*2 + 16; - - NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, delay, Transfer, pos); + memcpy(dstbuf, &tmpbuf[copystart], copylen*sizeof(u32)); } + + u32 numscan = Cnt & 0x000F; + if (BufferNumLines >= numscan) + { + BufferReadPos = 0; // checkme + BufferWritePos = 0; + BufferNumLines = 0; + DSi::CheckNDMAs(0, 0x0B); + } + else + { + BufferWritePos += copylen; + if (BufferWritePos > 512) BufferWritePos = 512; + BufferNumLines++; + } + + if (CurCamera->TransferDone()) + return; + + NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, delay, TransferScanline, line+1); } -DSi_Camera::DSi_Camera(u32 num) +u8 Read8(u32 addr) +{ + // + + printf("unknown DSi cam read8 %08X\n", addr); + return 0; +} + +u16 Read16(u32 addr) +{ + switch (addr) + { + case 0x04004200: return ModuleCnt; + case 0x04004202: return Cnt; + } + + printf("unknown DSi cam read16 %08X\n", addr); + return 0; +} + +u32 Read32(u32 addr) +{ + switch (addr) + { + case 0x04004204: + { + u32 ret = DataBuffer[BufferReadPos]; + if (Cnt & (1<<15)) + { + if (BufferReadPos < 511) + BufferReadPos++; + // CHECKME!!!! + // also presumably we should set bit4 in Cnt if there's no new data to be read + } + + return ret; + } + + case 0x04004210: return CropStart; + case 0x04004214: return CropEnd; + } + + printf("unknown DSi cam read32 %08X\n", addr); + return 0; +} + +void Write8(u32 addr, u8 val) +{ + // + + printf("unknown DSi cam write8 %08X %02X\n", addr, val); +} + +void Write16(u32 addr, u16 val) +{ + switch (addr) + { + case 0x04004200: + { + u16 oldcnt = ModuleCnt; + ModuleCnt = val; + + if ((ModuleCnt & (1<<1)) && !(oldcnt & (1<<1))) + { + // reset shit to zero + // CHECKME + + Cnt = 0; + } + + if ((ModuleCnt & (1<<5)) && !(oldcnt & (1<<5))) + { + // TODO: reset I2C?? + } + } + return; + + case 0x04004202: + { + // TODO: during a transfer, clearing bit15 does not reflect immediately + // maybe it needs to finish the trasnfer or atleast the current block + + // checkme + u16 oldmask; + if (Cnt & 0x8000) + { + val &= 0x8F20; + oldmask = 0x601F; + } + else + { + val &= 0xEF2F; + oldmask = 0x0010; + } + + Cnt = (Cnt & oldmask) | (val & ~0x0020); + if (val & (1<<5)) + { + Cnt &= ~(1<<4); + BufferReadPos = 0; + BufferWritePos = 0; + } + + if ((val & (1<<15)) && !(Cnt & (1<<15))) + { + // start transfer + //DSi::CheckNDMAs(0, 0x0B); + } + } + return; + + case 0x04004210: + if (Cnt & (1<<15)) return; + CropStart = (CropStart & 0x01FF0000) | (val & 0x03FE); + return; + case 0x04004212: + if (Cnt & (1<<15)) return; + CropStart = (CropStart & 0x03FE) | ((val & 0x01FF) << 16); + return; + case 0x04004214: + if (Cnt & (1<<15)) return; + CropEnd = (CropEnd & 0x01FF0000) | (val & 0x03FE); + return; + case 0x04004216: + if (Cnt & (1<<15)) return; + CropEnd = (CropEnd & 0x03FE) | ((val & 0x01FF) << 16); + return; + } + + printf("unknown DSi cam write16 %08X %04X\n", addr, val); +} + +void Write32(u32 addr, u32 val) +{ + switch (addr) + { + case 0x04004210: + if (Cnt & (1<<15)) return; + CropStart = val & 0x01FF03FE; + return; + case 0x04004214: + if (Cnt & (1<<15)) return; + CropEnd = val & 0x01FF03FE; + return; + } + + printf("unknown DSi cam write32 %08X %08X\n", addr, val); +} + + + +Camera::Camera(u32 num) { Num = num; } -DSi_Camera::~DSi_Camera() +Camera::~Camera() { } -void DSi_Camera::DoCamSavestate(Savestate* file) +void Camera::DoSavestate(Savestate* file) { char magic[5] = "CAMx"; magic[3] = '0' + Num; @@ -185,13 +419,13 @@ void DSi_Camera::DoCamSavestate(Savestate* file) file->Var16(&MiscCnt); file->Var16(&MCUAddr); - // TODO: MCUData?? - file->VarArray(MCURegs, 0x8000); } -void DSi_Camera::ResetCam() +void Camera::Reset() { + Platform::Camera_Stop(Num); + DataPos = 0; RegAddr = 0; RegData = 0; @@ -202,9 +436,23 @@ void DSi_Camera::ResetCam() ClocksCnt = 0; StandbyCnt = 0x4029; // checkme MiscCnt = 0; + + MCUAddr = 0; + memset(MCURegs, 0, 0x8000); + + // default state is preview mode (checkme) + MCURegs[0x2104] = 3; + + TransferY = 0; + memset(FrameBuffer, 0, (640*480/2)*sizeof(u32)); } -bool DSi_Camera::IsActivated() +void Camera::Stop() +{ + Platform::Camera_Stop(Num); +} + +bool Camera::IsActivated() { if (StandbyCnt & (1<<14)) return false; // standby if (!(MiscCnt & (1<<9))) return false; // data transfer not enabled @@ -213,40 +461,121 @@ bool DSi_Camera::IsActivated() } -void DSi_Camera::I2C_Start() +void Camera::StartTransfer() { -} + TransferY = 0; -u8 DSi_Camera::I2C_Read(bool last) -{ - u8 ret; - - if (DataPos < 2) + u8 state = MCURegs[0x2104]; + if (state == 3) // preview { - printf("DSi_Camera: WHAT??\n"); - ret = 0; + FrameWidth = *(u16*)&MCURegs[0x2703]; + FrameHeight = *(u16*)&MCURegs[0x2705]; + FrameReadMode = *(u16*)&MCURegs[0x2717]; + FrameFormat = *(u16*)&MCURegs[0x2755]; + } + else if (state == 7) // capture + { + FrameWidth = *(u16*)&MCURegs[0x2707]; + FrameHeight = *(u16*)&MCURegs[0x2709]; + FrameReadMode = *(u16*)&MCURegs[0x272D]; + FrameFormat = *(u16*)&MCURegs[0x2757]; } else { - if (DataPos & 0x1) + FrameWidth = 0; + FrameHeight = 0; + FrameReadMode = 0; + FrameFormat = 0; + } + + Platform::Camera_CaptureFrame(Num, FrameBuffer, 640, 480, true); +} + +bool Camera::TransferDone() +{ + return TransferY >= FrameHeight; +} + +int Camera::TransferScanline(u32* buffer, int maxlen) +{ + if (TransferY >= FrameHeight) + return 0; + + if (FrameWidth > 640 || FrameHeight > 480 || + FrameWidth < 2 || FrameHeight < 2 || + (FrameWidth & 1)) + { + // TODO work out something for these cases? + printf("CAM%d: invalid resolution %dx%d\n", Num, FrameWidth, FrameHeight); + //memset(buffer, 0, width*height*sizeof(u16)); + return 0; + } + + // TODO: non-YUV pixel formats and all + + int retlen = FrameWidth >> 1; + int sy = (TransferY * 480) / FrameHeight; + if (FrameReadMode & (1<<1)) + sy = 479 - sy; + + if (FrameReadMode & (1<<0)) + { + for (int dx = 0; dx < retlen; dx++) { - ret = RegData & 0xFF; - RegAddr += 2; // checkme + if (dx >= maxlen) break; + + int sx = (dx * 640) / FrameWidth; + + u32 val = FrameBuffer[sy*320 + sx]; + buffer[dx] = val; } - else + } + else + { + for (int dx = 0; dx < retlen; dx++) { - RegData = I2C_ReadReg(RegAddr); - ret = RegData >> 8; + if (dx >= maxlen) break; + + int sx = 319 - ((dx * 640) / FrameWidth); + + u32 val = FrameBuffer[sy*320 + sx]; + buffer[dx] = (val & 0xFF00FF00) | ((val >> 16) & 0xFF) | ((val & 0xFF) << 16); } } + TransferY++; + + return retlen; +} + + +void Camera::I2C_Start() +{ + DataPos = 0; +} + +u8 Camera::I2C_Read(bool last) +{ + u8 ret; + + if (DataPos & 0x1) + { + ret = RegData & 0xFF; + RegAddr += 2; // checkme + } + else + { + RegData = I2C_ReadReg(RegAddr); + ret = RegData >> 8; + } + if (last) DataPos = 0; else DataPos++; return ret; } -void DSi_Camera::I2C_Write(u8 val, bool last) +void Camera::I2C_Write(u8 val, bool last) { if (DataPos < 2) { @@ -275,7 +604,7 @@ void DSi_Camera::I2C_Write(u8 val, bool last) else DataPos++; } -u16 DSi_Camera::I2C_ReadReg(u16 addr) +u16 Camera::I2C_ReadReg(u16 addr) { switch (addr) { @@ -287,6 +616,23 @@ u16 DSi_Camera::I2C_ReadReg(u16 addr) case 0x0018: return StandbyCnt; case 0x001A: return MiscCnt; + case 0x098C: return MCUAddr; + case 0x0990: + case 0x0992: + case 0x0994: + case 0x0996: + case 0x0998: + case 0x099A: + case 0x099C: + case 0x099E: + { + addr -= 0x0990; + u16 ret = MCU_Read((MCUAddr & 0x7FFF) + addr); + if (!(MCUAddr & (1<<15))) + ret |= (MCU_Read((MCUAddr & 0x7FFF) + addr+1) << 8); + return ret; + } + case 0x301A: return ((~StandbyCnt) & 0x4000) >> 12; } @@ -294,7 +640,7 @@ u16 DSi_Camera::I2C_ReadReg(u16 addr) return 0; } -void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) +void Camera::I2C_WriteReg(u16 addr, u16 val) { switch (addr) { @@ -312,18 +658,47 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) return; case 0x0016: ClocksCnt = val; - printf("ClocksCnt=%04X\n", val); + //printf("ClocksCnt=%04X\n", val); return; case 0x0018: - // TODO: this shouldn't be instant, but uh - val &= 0x003F; - val |= ((val & 0x0001) << 14); - StandbyCnt = val; - printf("CAM%d STBCNT=%04X (%04X)\n", Num, StandbyCnt, val); + { + bool wasactive = IsActivated(); + // TODO: this shouldn't be instant, but uh + val &= 0x003F; + val |= ((val & 0x0001) << 14); + StandbyCnt = val; + //printf("CAM%d STBCNT=%04X (%04X)\n", Num, StandbyCnt, val); + bool isactive = IsActivated(); + if (isactive && !wasactive) Platform::Camera_Start(Num); + else if (wasactive && !isactive) Platform::Camera_Stop(Num); + } return; case 0x001A: - MiscCnt = val & 0x0B7B; - printf("CAM%d MISCCNT=%04X (%04X)\n", Num, MiscCnt, val); + { + bool wasactive = IsActivated(); + MiscCnt = val & 0x0B7B; + //printf("CAM%d MISCCNT=%04X (%04X)\n", Num, MiscCnt, val); + bool isactive = IsActivated(); + if (isactive && !wasactive) Platform::Camera_Start(Num); + else if (wasactive && !isactive) Platform::Camera_Stop(Num); + } + return; + + case 0x098C: + MCUAddr = val; + return; + case 0x0990: + case 0x0992: + case 0x0994: + case 0x0996: + case 0x0998: + case 0x099A: + case 0x099C: + case 0x099E: + addr -= 0x0990; + MCU_Write((MCUAddr & 0x7FFF) + addr, val&0xFF); + if (!(MCUAddr & (1<<15))) + MCU_Write((MCUAddr & 0x7FFF) + addr+1, val>>8); return; } @@ -331,117 +706,122 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) } -u8 DSi_Camera::Read8(u32 addr) -{ - // +// TODO: not sure at all what is the accessible range +// or if there is any overlap in the address range - printf("unknown DSi cam read8 %08X\n", addr); - return 0; +u8 Camera::MCU_Read(u16 addr) +{ + addr &= 0x7FFF; + + return MCURegs[addr]; } -u16 DSi_Camera::Read16(u32 addr) +void Camera::MCU_Write(u16 addr, u8 val) { + addr &= 0x7FFF; + switch (addr) { - case 0x04004200: return ModuleCnt; - case 0x04004202: return Cnt; - } - - printf("unknown DSi cam read16 %08X\n", addr); - return 0; -} - -u32 DSi_Camera::Read32(u32 addr) -{ - switch (addr) - { - case 0x04004204: - { - // TODO - return 0xFC00801F; - /*if (!(Cnt & (1<<15))) return 0; // CHECKME - u32 ret = *(u32*)&FrameBuffer[TransferPos]; - TransferPos += 4; - if (TransferPos >= FrameLength) TransferPos = 0; - dorp += 4; - //if (dorp >= (256*4*2)) - if (TransferPos == 0) - { - dorp = 0; - Cnt &= ~(1<<4); - } - return ret;*/ - } - } - - printf("unknown DSi cam read32 %08X\n", addr); - return 0; -} - -void DSi_Camera::Write8(u32 addr, u8 val) -{ - // - - printf("unknown DSi cam write8 %08X %02X\n", addr, val); -} - -void DSi_Camera::Write16(u32 addr, u16 val) -{ - switch (addr) - { - case 0x04004200: - { - u16 oldcnt = ModuleCnt; - ModuleCnt = val; - - if ((ModuleCnt & (1<<1)) && !(oldcnt & (1<<1))) - { - // reset shit to zero - // CHECKME - - Cnt = 0; - } - - if ((ModuleCnt & (1<<5)) && !(oldcnt & (1<<5))) - { - // TODO: reset I2C?? - } - } + case 0x2103: // SEQ_CMD + MCURegs[addr] = 0; + if (val == 2) MCURegs[0x2104] = 7; // capture mode + else if (val == 1) MCURegs[0x2104] = 3; // preview mode + else if (val != 5 && val != 6) + printf("CAM%d: atypical SEQ_CMD %04X\n", Num, val); return; - case 0x04004202: - { - // checkme - u16 oldmask; - if (Cnt & 0x8000) - { - val &= 0x8F20; - oldmask = 0x601F; - } - else - { - val &= 0xEF2F; - oldmask = 0x0010; - } - - Cnt = (Cnt & oldmask) | (val & ~0x0020); - if (val & (1<<5)) Cnt &= ~(1<<4); - - if ((val & (1<<15)) && !(Cnt & (1<<15))) - { - // start transfer - //DSi::CheckNDMAs(0, 0x0B); - } - } + case 0x2104: // SEQ_STATE, read-only return; } - printf("unknown DSi cam write16 %08X %04X\n", addr, val); + MCURegs[addr] = val; } -void DSi_Camera::Write32(u32 addr, u32 val) + +void Camera::InputFrame(u32* data, int width, int height, bool rgb) { - // + // TODO: double-buffering? - printf("unknown DSi cam write32 %08X %08X\n", addr, val); + if (width == 640 && height == 480 && !rgb) + { + memcpy(FrameBuffer, data, (640*480/2)*sizeof(u32)); + return; + } + + if (rgb) + { + for (int dy = 0; dy < 480; dy++) + { + int sy = (dy * height) / 480; + + for (int dx = 0; dx < 640; dx+=2) + { + int sx; + + sx = (dx * width) / 640; + u32 pixel1 = data[sy*width + sx]; + + sx = ((dx+1) * width) / 640; + u32 pixel2 = data[sy*width + sx]; + + int r1 = (pixel1 >> 16) & 0xFF; + int g1 = (pixel1 >> 8) & 0xFF; + int b1 = pixel1 & 0xFF; + + int r2 = (pixel2 >> 16) & 0xFF; + int g2 = (pixel2 >> 8) & 0xFF; + int b2 = pixel2 & 0xFF; + + int y1 = ((r1 * 19595) + (g1 * 38470) + (b1 * 7471)) >> 16; + int u1 = ((b1 - y1) * 32244) >> 16; + int v1 = ((r1 - y1) * 57475) >> 16; + + int y2 = ((r2 * 19595) + (g2 * 38470) + (b2 * 7471)) >> 16; + int u2 = ((b2 - y2) * 32244) >> 16; + int v2 = ((r2 - y2) * 57475) >> 16; + + u1 += 128; v1 += 128; + u2 += 128; v2 += 128; + + y1 = std::clamp(y1, 0, 255); u1 = std::clamp(u1, 0, 255); v1 = std::clamp(v1, 0, 255); + y2 = std::clamp(y2, 0, 255); u2 = std::clamp(u2, 0, 255); v2 = std::clamp(v2, 0, 255); + + // huh + u1 = (u1 + u2) >> 1; + v1 = (v1 + v2) >> 1; + + FrameBuffer[(dy*640 + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24); + } + } + } + else + { + for (int dy = 0; dy < 480; dy++) + { + int sy = (dy * height) / 480; + + for (int dx = 0; dx < 640; dx+=2) + { + int sx = (dx * width) / 640; + + FrameBuffer[(dy*640 + dx) / 2] = data[(sy*width + sx) / 2]; + } + } + } } + +} + + + + + + + + + + + + + + diff --git a/src/DSi_Camera.h b/src/DSi_Camera.h index e5926bb0..bf18e597 100644 --- a/src/DSi_Camera.h +++ b/src/DSi_Camera.h @@ -22,38 +22,55 @@ #include "types.h" #include "Savestate.h" -class DSi_Camera +namespace DSi_CamModule +{ + +class Camera; + +extern Camera* Camera0; +extern Camera* Camera1; + +bool Init(); +void DeInit(); +void Reset(); +void Stop(); + +void DoSavestate(Savestate* file); + +void IRQ(u32 param); + +void TransferScanline(u32 line); + +u8 Read8(u32 addr); +u16 Read16(u32 addr); +u32 Read32(u32 addr); +void Write8(u32 addr, u8 val); +void Write16(u32 addr, u16 val); +void Write32(u32 addr, u32 val); + +class Camera { public: - static bool Init(); - static void DeInit(); - static void Reset(); + Camera(u32 num); + ~Camera(); - static void DoSavestate(Savestate* file); + void DoSavestate(Savestate* file); - static void IRQ(u32 param); - static void RequestFrame(u32 cam); - - static void Transfer(u32 pos); - - DSi_Camera(u32 num); - ~DSi_Camera(); - - void DoCamSavestate(Savestate* file); - - void ResetCam(); + void Reset(); + void Stop(); bool IsActivated(); + void StartTransfer(); + bool TransferDone(); + + // lengths in words + int TransferScanline(u32* buffer, int maxlen); + void I2C_Start(); u8 I2C_Read(bool last); void I2C_Write(u8 val, bool last); - static u8 Read8(u32 addr); - static u16 Read16(u32 addr); - static u32 Read32(u32 addr); - static void Write8(u32 addr, u8 val); - static void Write16(u32 addr, u16 val); - static void Write32(u32 addr, u32 val); + void InputFrame(u32* data, int width, int height, bool rgb); u32 Num; @@ -73,20 +90,17 @@ private: u16 MiscCnt; u16 MCUAddr; - u16* MCUData; - u8 MCURegs[0x8000]; - static u16 ModuleCnt; - static u16 Cnt; + u8 MCU_Read(u16 addr); + void MCU_Write(u16 addr, u8 val); - static u8 FrameBuffer[640*480*4]; - static u32 TransferPos; - static u32 FrameLength; + u16 FrameWidth, FrameHeight; + u16 FrameReadMode, FrameFormat; + int TransferY; + u32 FrameBuffer[640*480/2]; // YUYV framebuffer, two pixels per word }; - -extern DSi_Camera* DSi_Camera0; -extern DSi_Camera* DSi_Camera1; +} #endif // DSI_CAMERA_H diff --git a/src/DSi_DSP.cpp b/src/DSi_DSP.cpp index 0525366e..74170b73 100644 --- a/src/DSi_DSP.cpp +++ b/src/DSi_DSP.cpp @@ -214,6 +214,11 @@ inline bool IsDSPCoreEnabled() return (DSi::SCFG_Clock9 & (1<<1)) && SCFG_RST && (!(DSP_PCFG & (1<<0))); } +inline bool IsDSPIOEnabled() +{ + return (DSi::SCFG_Clock9 & (1<<1)) && SCFG_RST; +} + bool DSPCatchUp() { //asm volatile("int3"); @@ -390,10 +395,8 @@ u16 PDataDMAReadMMIO() u8 Read8(u32 addr) { - if (!(DSi::SCFG_EXT[0] & (1<<18))) - return 0; - - if (!DSPCatchUp()) return 0; + if (!IsDSPIOEnabled()) return 0; + DSPCatchUp(); addr &= 0x3F; // mirroring wheee @@ -419,10 +422,9 @@ u8 Read8(u32 addr) } u16 Read16(u32 addr) { - if (!(DSi::SCFG_EXT[0] & (1<<18))) - return 0; - - if (!DSPCatchUp()) return 0; + //printf("DSP READ16 %d %08X %08X\n", IsDSPCoreEnabled(), addr, NDS::GetPC(0)); + if (!IsDSPIOEnabled()) return 0; + DSPCatchUp(); addr &= 0x3E; // mirroring wheee @@ -463,8 +465,6 @@ u16 Read16(u32 addr) } u32 Read32(u32 addr) { - if (!(DSi::SCFG_EXT[0] & (1<<18))) return 0; - addr &= 0x3C; return Read16(addr); // *shrug* (doesn't do anything unintended due to the // 4byte spacing between regs while they're all 16bit) @@ -472,9 +472,8 @@ u32 Read32(u32 addr) void Write8(u32 addr, u8 val) { - if (!(DSi::SCFG_EXT[0] & (1<<18))) return; - - if (!DSPCatchUp()) return; + if (!IsDSPIOEnabled()) return; + DSPCatchUp(); addr &= 0x3F; switch (addr) @@ -494,9 +493,9 @@ void Write8(u32 addr, u8 val) } void Write16(u32 addr, u16 val) { - if (!(DSi::SCFG_EXT[0] & (1<<18))) return; - - if (!DSPCatchUp()) return; + //printf("DSP WRITE16 %d %08X %08X %08X\n", IsDSPCoreEnabled(), addr, val, NDS::GetPC(0)); + if (!IsDSPIOEnabled()) return; + DSPCatchUp(); addr &= 0x3E; switch (addr) @@ -506,6 +505,8 @@ void Write16(u32 addr, u16 val) case 0x08: DSP_PCFG = val; + if (DSP_PCFG & (1<<0)) + TeakraCore->Reset(); if (DSP_PCFG & (1<<4)) PDataDMAStart(); else @@ -547,8 +548,6 @@ void Write16(u32 addr, u16 val) void Write32(u32 addr, u32 val) { - if (!(DSi::SCFG_EXT[0] & (1<<18))) return; - addr &= 0x3C; Write16(addr, val & 0xFFFF); } diff --git a/src/DSi_I2C.cpp b/src/DSi_I2C.cpp index 5d13b2a6..5889bef0 100644 --- a/src/DSi_I2C.cpp +++ b/src/DSi_I2C.cpp @@ -169,7 +169,6 @@ u32 Device; bool Init() { if (!DSi_BPTWL::Init()) return false; - if (!DSi_Camera::Init()) return false; return true; } @@ -177,7 +176,6 @@ bool Init() void DeInit() { DSi_BPTWL::DeInit(); - DSi_Camera::DeInit(); } void Reset() @@ -188,7 +186,6 @@ void Reset() Device = -1; DSi_BPTWL::Reset(); - DSi_Camera::Reset(); } void DoSavestate(Savestate* file) @@ -200,12 +197,11 @@ void DoSavestate(Savestate* file) file->Var32(&Device); DSi_BPTWL::DoSavestate(file); - // cameras are savestated from the DSi_Camera module } void WriteCnt(u8 val) { - //printf("I2C: write CNT %02X, %08X\n", val, NDS::GetPC(1)); + //printf("I2C: write CNT %02X, %02X, %08X\n", val, Data, NDS::GetPC(1)); // TODO: check ACK flag // TODO: transfer delay @@ -224,8 +220,8 @@ void WriteCnt(u8 val) switch (Device) { case 0x4A: Data = DSi_BPTWL::Read(islast); break; - case 0x78: Data = DSi_Camera0->I2C_Read(islast); break; - case 0x7A: Data = DSi_Camera1->I2C_Read(islast); break; + case 0x78: Data = DSi_CamModule::Camera0->I2C_Read(islast); break; + case 0x7A: Data = DSi_CamModule::Camera1->I2C_Read(islast); break; case 0xA0: case 0xE0: Data = 0xFF; break; default: @@ -250,8 +246,8 @@ void WriteCnt(u8 val) switch (Device) { case 0x4A: DSi_BPTWL::Start(); break; - case 0x78: DSi_Camera0->I2C_Start(); break; - case 0x7A: DSi_Camera1->I2C_Start(); break; + case 0x78: DSi_CamModule::Camera0->I2C_Start(); break; + case 0x7A: DSi_CamModule::Camera1->I2C_Start(); break; case 0xA0: case 0xE0: ack = false; break; default: @@ -267,8 +263,8 @@ void WriteCnt(u8 val) switch (Device) { case 0x4A: DSi_BPTWL::Write(Data, islast); break; - case 0x78: DSi_Camera0->I2C_Write(Data, islast); break; - case 0x7A: DSi_Camera1->I2C_Write(Data, islast); break; + case 0x78: DSi_CamModule::Camera0->I2C_Write(Data, islast); break; + case 0x7A: DSi_CamModule::Camera1->I2C_Write(Data, islast); break; case 0xA0: case 0xE0: ack = false; break; default: diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp index e24ed313..912fee42 100644 --- a/src/DSi_NAND.cpp +++ b/src/DSi_NAND.cpp @@ -49,8 +49,48 @@ UINT FF_ReadNAND(BYTE* buf, LBA_t sector, UINT num); UINT FF_WriteNAND(BYTE* buf, LBA_t sector, UINT num); -bool Init(FILE* nandfile, u8* es_keyY) +bool Init(u8* es_keyY) { + CurFile = nullptr; + + std::string nandpath = Platform::GetConfigString(Platform::DSi_NANDPath); + std::string instnand = nandpath + Platform::InstanceFileSuffix(); + + FILE* nandfile = Platform::OpenLocalFile(instnand, "r+b"); + if ((!nandfile) && (Platform::InstanceID() > 0)) + { + FILE* orig = Platform::OpenLocalFile(nandpath, "rb"); + if (!orig) + { + printf("Failed to open DSi NAND\n"); + return false; + } + + fseek(orig, 0, SEEK_END); + long len = ftell(orig); + fseek(orig, 0, SEEK_SET); + + nandfile = Platform::OpenLocalFile(instnand, "w+b"); + if (nandfile) + { + u8* tmpbuf = new u8[0x10000]; + for (long i = 0; i < len; i+=0x10000) + { + long blklen = 0x10000; + if ((i+blklen) > len) blklen = len-i; + + fread(tmpbuf, blklen, 1, orig); + fwrite(tmpbuf, blklen, 1, nandfile); + } + delete[] tmpbuf; + } + + fclose(orig); + fclose(nandfile); + + nandfile = Platform::OpenLocalFile(instnand, "r+b"); + } + if (!nandfile) return false; @@ -138,10 +178,17 @@ void DeInit() f_unmount("0:"); ff_disk_close(); + if (CurFile) fclose(CurFile); CurFile = nullptr; } +FILE* GetFile() +{ + return CurFile; +} + + void GetIDs(u8* emmc_cid, u64& consoleid) { memcpy(emmc_cid, eMMC_CID, 16); diff --git a/src/DSi_NAND.h b/src/DSi_NAND.h index 6feb2d0f..a23e62f6 100644 --- a/src/DSi_NAND.h +++ b/src/DSi_NAND.h @@ -34,9 +34,11 @@ enum TitleData_BannerSav, }; -bool Init(FILE* nand, u8* es_keyY); +bool Init(u8* es_keyY); void DeInit(); +FILE* GetFile(); + void GetIDs(u8* emmc_cid, u64& consoleid); void ReadHardwareInfo(u8* dataS, u8* dataN); diff --git a/src/DSi_NDMA.cpp b/src/DSi_NDMA.cpp index 3c61e676..ca834eb1 100644 --- a/src/DSi_NDMA.cpp +++ b/src/DSi_NDMA.cpp @@ -132,7 +132,7 @@ void DSi_NDMA::WriteCnt(u32 val) // * microphone (ARM7 0C) // * NDS-wifi?? (ARM7 07, likely not working) - if (StartMode <= 0x03 || StartMode == 0x05 || (StartMode >= 0x0B && StartMode <= 0x0F) || + if (StartMode <= 0x03 || StartMode == 0x05 || (StartMode >= 0x0C && StartMode <= 0x0F) || (StartMode >= 0x20 && StartMode <= 0x23) || StartMode == 0x25 || StartMode == 0x27 || (StartMode >= 0x2C && StartMode <= 0x2F)) printf("UNIMPLEMENTED ARM%d NDMA%d START MODE %02X, %08X->%08X LEN=%d BLK=%d CNT=%08X\n", CPU?7:9, Num, StartMode, SrcAddr, DstAddr, TotalLength, BlockLength, Cnt); diff --git a/src/DSi_SD.cpp b/src/DSi_SD.cpp index c6932d47..e603347a 100644 --- a/src/DSi_SD.cpp +++ b/src/DSi_SD.cpp @@ -136,7 +136,10 @@ void DSi_SDHost::Reset() else sd = nullptr; - mmc = new DSi_MMCStorage(this, true, Platform::GetConfigString(Platform::DSi_NANDPath)); + std::string nandpath = Platform::GetConfigString(Platform::DSi_NANDPath); + std::string instnand = nandpath + Platform::InstanceFileSuffix(); + + mmc = new DSi_MMCStorage(this, true, instnand); mmc->SetCID(DSi::eMMC_CID); Ports[0] = sd; diff --git a/src/NDS.cpp b/src/NDS.cpp index 2ff8ffe2..966b2529 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -176,6 +176,7 @@ bool RunningGame; void DivDone(u32 param); void SqrtDone(u32 param); void RunTimer(u32 tid, s32 cycles); +void UpdateWifiTimings(); void SetWifiWaitCnt(u16 val); void SetGBASlotTimings(); @@ -692,6 +693,9 @@ void Stop() Platform::StopEmu(); GPU::Stop(); SPU::Stop(); + + if (ConsoleType == 1) + DSi::Stop(); } bool DoSavestate_Scheduler(Savestate* file) @@ -722,8 +726,8 @@ bool DoSavestate_Scheduler(Savestate* file) DSi_SDHost::FinishRX, DSi_SDHost::FinishTX, DSi_NWifi::MSTimer, - DSi_Camera::IRQ, - DSi_Camera::Transfer, + DSi_CamModule::IRQ, + DSi_CamModule::TransferScanline, DSi_DSP::DSPCatchUpU32, nullptr @@ -892,9 +896,7 @@ bool DoSavestate(Savestate* file) InitTimings(); SetGBASlotTimings(); - u16 tmp = WifiWaitCnt; - WifiWaitCnt = 0xFFFF; - SetWifiWaitCnt(tmp); // force timing table update + UpdateWifiTimings(); } for (int i = 0; i < 8; i++) @@ -918,6 +920,9 @@ bool DoSavestate(Savestate* file) if (!file->Saving) { GPU::SetPowerCnt(PowerControl9); + + SPU::SetPowerCnt(PowerControl7 & 0x0001); + Wifi::SetPowerCnt(PowerControl7 & 0x0002); } #ifdef JIT_ENABLED @@ -1198,6 +1203,25 @@ void ScheduleEvent(u32 id, bool periodic, s32 delay, void (*func)(u32), u32 para Reschedule(evt->Timestamp); } +void ScheduleEvent(u32 id, u64 timestamp, void (*func)(u32), u32 param) +{ + if (SchedListMask & (1<Timestamp = timestamp; + evt->Func = func; + evt->Param = param; + + SchedListMask |= (1<Timestamp); +} + void CancelEvent(u32 id) { SchedListMask &= ~(1<InputFrame(data, width, height, rgb); + case 1: return DSi_CamModule::Camera1->InputFrame(data, width, height, rgb); + } + } +} + void MicInputFrame(s16* data, int samples) { return SPI_TSC::MicInputFrame(data, samples); @@ -1323,15 +1362,29 @@ void MapSharedWRAM(u8 val) } +void UpdateWifiTimings() +{ + if (PowerControl7 & 0x0002) + { + const int ntimings[4] = {10, 8, 6, 18}; + u16 val = WifiWaitCnt; + + SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 16, ntimings[val & 0x3], (val & 0x4) ? 4 : 6); + SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 16, ntimings[(val>>3) & 0x3], (val & 0x20) ? 4 : 10); + } + else + { + SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 32, 1, 1); + SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 32, 1, 1); + } +} + void SetWifiWaitCnt(u16 val) { if (WifiWaitCnt == val) return; WifiWaitCnt = val; - - const int ntimings[4] = {10, 8, 6, 18}; - SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 16, ntimings[val & 0x3], (val & 0x4) ? 4 : 6); - SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 16, ntimings[(val>>3) & 0x3], (val & 0x20) ? 4 : 10); + UpdateWifiTimings(); } void SetGBASlotTimings() @@ -1941,8 +1994,8 @@ void debug(u32 param) //for (int i = 0; i < 9; i++) // printf("VRAM %c: %02X\n", 'A'+i, GPU::VRAMCNT[i]); - /*FILE* - shit = fopen("debug/construct.bin", "wb"); + FILE* + shit = fopen("debug/inazuma.bin", "wb"); fwrite(ARM9->ITCM, 0x8000, 1, shit); for (u32 i = 0x02000000; i < 0x02400000; i+=4) { @@ -1954,9 +2007,14 @@ void debug(u32 param) u32 val = ARM7Read32(i); fwrite(&val, 4, 1, shit); } - fclose(shit);*/ + for (u32 i = 0x06000000; i < 0x06040000; i+=4) + { + u32 val = ARM7Read32(i); + fwrite(&val, 4, 1, shit); + } + fclose(shit); - FILE* + /*FILE* shit = fopen("debug/directboot9.bin", "wb"); for (u32 i = 0x02000000; i < 0x04000000; i+=4) { @@ -1964,13 +2022,13 @@ void debug(u32 param) fwrite(&val, 4, 1, shit); } fclose(shit); - shit = fopen("debug/directboot7.bin", "wb"); + shit = fopen("debug/camera7.bin", "wb"); for (u32 i = 0x02000000; i < 0x04000000; i+=4) { u32 val = DSi::ARM7Read32(i); fwrite(&val, 4, 1, shit); } - fclose(shit); + fclose(shit);*/ } @@ -2396,6 +2454,7 @@ u8 ARM7Read8(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; if (addr & 0x1) return Wifi::Read(addr-1) >> 8; return Wifi::Read(addr) & 0xFF; } @@ -2460,6 +2519,7 @@ u16 ARM7Read16(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; return Wifi::Read(addr); } break; @@ -2523,6 +2583,7 @@ u32 ARM7Read32(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; return Wifi::Read(addr) | (Wifi::Read(addr+2) << 16); } break; @@ -2614,7 +2675,8 @@ void ARM7Write8(u32 addr, u8 val) return; } - if (ARM7->R[15] > 0x00002F30) // ARM7 BIOS bug + //if (ARM7->R[15] > 0x00002F30) // ARM7 BIOS bug + if (addr >= 0x01000000) printf("unknown arm7 write8 %08X %02X @ %08X\n", addr, val, ARM7->R[15]); } @@ -2662,6 +2724,7 @@ void ARM7Write16(u32 addr, u16 val) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return; Wifi::Write(addr, val); return; } @@ -2691,7 +2754,8 @@ void ARM7Write16(u32 addr, u16 val) return; } - printf("unknown arm7 write16 %08X %04X @ %08X\n", addr, val, ARM7->R[15]); + if (addr >= 0x01000000) + printf("unknown arm7 write16 %08X %04X @ %08X\n", addr, val, ARM7->R[15]); } void ARM7Write32(u32 addr, u32 val) @@ -2738,6 +2802,7 @@ void ARM7Write32(u32 addr, u32 val) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return; Wifi::Write(addr, val & 0xFFFF); Wifi::Write(addr+2, val >> 16); return; @@ -2771,7 +2836,8 @@ void ARM7Write32(u32 addr, u32 val) return; } - printf("unknown arm7 write32 %08X %08X @ %08X\n", addr, val, ARM7->R[15]); + if (addr >= 0x01000000) + printf("unknown arm7 write32 %08X %08X @ %08X\n", addr, val, ARM7->R[15]); } bool ARM7GetMemRegion(u32 addr, bool write, MemRegion* region) @@ -2931,7 +2997,8 @@ u8 ARM9IORead8(u32 addr) return (u8)(emuID[idx]); } - printf("unknown ARM9 IO read8 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read8 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3077,7 +3144,8 @@ u16 ARM9IORead16(u32 addr) return GPU3D::Read16(addr); } - printf("unknown ARM9 IO read16 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read16 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3220,7 +3288,8 @@ u32 ARM9IORead32(u32 addr) return GPU3D::Read32(addr); } - printf("unknown ARM9 IO read32 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read32 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3748,6 +3817,7 @@ u8 ARM7IORead8(u32 addr) case 0x04000241: return WRAMCnt; case 0x04000300: return PostFlag7; + case 0x04000304: return PowerControl7; } if (addr >= 0x04000400 && addr < 0x04000520) @@ -3755,7 +3825,8 @@ u8 ARM7IORead8(u32 addr) return SPU::Read8(addr); } - printf("unknown ARM7 IO read8 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read8 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -3830,7 +3901,9 @@ u16 ARM7IORead16(u32 addr) case 0x040001C2: return SPI::ReadData(); case 0x04000204: return ExMemCnt[1]; - case 0x04000206: return WifiWaitCnt; + case 0x04000206: + if (!(PowerControl7 & (1<<1))) return 0; + return WifiWaitCnt; case 0x04000208: return IME[1]; case 0x04000210: return IE[1] & 0xFFFF; @@ -3846,7 +3919,8 @@ u16 ARM7IORead16(u32 addr) return SPU::Read16(addr); } - printf("unknown ARM7 IO read16 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read16 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -3912,6 +3986,7 @@ u32 ARM7IORead32(u32 addr) case 0x04000210: return IE[1]; case 0x04000214: return IF[1]; + case 0x04000304: return PowerControl7; case 0x04000308: return ARM7BIOSProt; case 0x04100000: @@ -3945,7 +4020,8 @@ u32 ARM7IORead32(u32 addr) return SPU::Read32(addr); } - printf("unknown ARM7 IO read32 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read32 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -4140,6 +4216,7 @@ void ARM7IOWrite16(u32 addr, u16 val) return; } case 0x04000206: + if (!(PowerControl7 & (1<<1))) return; SetWifiWaitCnt(val); return; @@ -4155,7 +4232,15 @@ void ARM7IOWrite16(u32 addr, u16 val) PostFlag7 = val & 0x01; return; - case 0x04000304: PowerControl7 = val; return; + case 0x04000304: + { + u16 change = PowerControl7 ^ val; + PowerControl7 = val & 0x0003; + SPU::SetPowerCnt(val & 0x0001); + Wifi::SetPowerCnt(val & 0x0002); + if (change & 0x0002) UpdateWifiTimings(); + } + return; case 0x04000308: if (ARM7BIOSProt == 0) @@ -4277,7 +4362,15 @@ void ARM7IOWrite32(u32 addr, u32 val) case 0x04000210: IE[1] = val; UpdateIRQ(1); return; case 0x04000214: IF[1] &= ~val; UpdateIRQ(1); return; - case 0x04000304: PowerControl7 = val & 0xFFFF; return; + case 0x04000304: + { + u16 change = PowerControl7 ^ val; + PowerControl7 = val & 0x0003; + SPU::SetPowerCnt(val & 0x0001); + Wifi::SetPowerCnt(val & 0x0002); + if (change & 0x0002) UpdateWifiTimings(); + } + return; case 0x04000308: if (ARM7BIOSProt == 0) diff --git a/src/NDS.h b/src/NDS.h index 80a1c6d7..824c2bc9 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -260,9 +260,11 @@ void SetKeyMask(u32 mask); bool IsLidClosed(); void SetLidClosed(bool closed); +void CamInputFrame(int cam, u32* data, int width, int height, bool rgb); void MicInputFrame(s16* data, int samples); void ScheduleEvent(u32 id, bool periodic, s32 delay, void (*func)(u32), u32 param); +void ScheduleEvent(u32 id, u64 timestamp, void (*func)(u32), u32 param); void CancelEvent(u32 id); void debug(u32 p); diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index a6e16dfb..cdc26ef1 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -1584,6 +1584,9 @@ bool LoadROM(const u8* romdata, u32 romlen) if (CartInserted) EjectCart(); + memset(&Header, 0, sizeof(Header)); + memset(&Banner, 0, sizeof(Banner)); + CartROMSize = 0x200; while (CartROMSize < romlen) CartROMSize <<= 1; @@ -1602,7 +1605,15 @@ bool LoadROM(const u8* romdata, u32 romlen) memcpy(CartROM, romdata, romlen); memcpy(&Header, CartROM, sizeof(Header)); - memcpy(&Banner, CartROM + Header.BannerOffset, sizeof(Banner)); + + u8 unitcode = Header.UnitCode; + bool dsi = (unitcode & 0x02) != 0; + + size_t bannersize = dsi ? 0x23C0 : 0xA40; + if (Header.BannerOffset >= 0x200 && Header.BannerOffset < (CartROMSize - bannersize)) + { + memcpy(&Banner, CartROM + Header.BannerOffset, bannersize); + } printf("Game code: %.4s\n", Header.GameCode); @@ -1611,9 +1622,6 @@ bool LoadROM(const u8* romdata, u32 romlen) (u32)Header.GameCode[1] << 8 | (u32)Header.GameCode[0]; - u8 unitcode = Header.UnitCode; - bool dsi = (unitcode & 0x02) != 0; - u32 arm9base = Header.ARM9ROMOffset; bool homebrew = (arm9base < 0x4000) || (gamecode == 0x23232323); diff --git a/src/Platform.h b/src/Platform.h index 4106977b..f2997ef8 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -32,6 +32,10 @@ void DeInit(); void StopEmu(); +// instance ID, for local multiplayer +int InstanceID(); +std::string InstanceFileSuffix(); + // configuration values enum ConfigEntry @@ -77,7 +81,6 @@ enum ConfigEntry Firm_Color, Firm_Message, Firm_MAC, - Firm_RandomizeMAC, AudioBitrate, }; @@ -144,6 +147,8 @@ void Mutex_Lock(Mutex* mutex); void Mutex_Unlock(Mutex* mutex); bool Mutex_TryLock(Mutex* mutex); +void Sleep(u64 usecs); + // functions called when the NDS or GBA save files need to be written back to storage // savedata and savelen are always the entire save memory buffer and its full length @@ -156,8 +161,16 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen // packet type: DS-style TX header (12 bytes) + original 802.11 frame bool MP_Init(); void MP_DeInit(); -int MP_SendPacket(u8* data, int len); -int MP_RecvPacket(u8* data, bool block); +void MP_Begin(); +void MP_End(); +int MP_SendPacket(u8* data, int len, u64 timestamp); +int MP_RecvPacket(u8* data, u64* timestamp); +int MP_SendCmd(u8* data, int len, u64 timestamp); +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid); +int MP_SendAck(u8* data, int len, u64 timestamp); +int MP_RecvHostPacket(u8* data, u64* timestamp); +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask); + // LAN comm interface // packet type: Ethernet (802.3) @@ -166,7 +179,15 @@ void LAN_DeInit(); int LAN_SendPacket(u8* data, int len); int LAN_RecvPacket(u8* data); -void Sleep(u64 usecs); + +// interface for camera emulation +// camera numbers: +// 0 = DSi outer camera +// 1 = DSi inner camera +// other values reserved for future camera addon emulation +void Camera_Start(int num); +void Camera_Stop(int num); +void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv); } diff --git a/src/SPI.cpp b/src/SPI.cpp index 6ecb86c4..e990b3a7 100644 --- a/src/SPI.cpp +++ b/src/SPI.cpp @@ -215,7 +215,8 @@ void LoadDefaultFirmware() // wifi access points // TODO: WFC ID?? - FILE* f = Platform::OpenLocalFile("wfcsettings.bin", "rb"); + FILE* f = Platform::OpenLocalFile("wfcsettings.bin"+Platform::InstanceFileSuffix(), "rb"); + if (!f) f = Platform::OpenLocalFile("wfcsettings.bin", "rb"); if (f) { u32 apdata = userdata - 0xA00; @@ -259,7 +260,7 @@ void LoadDefaultFirmware() } } -void LoadFirmwareFromFile(FILE* f) +void LoadFirmwareFromFile(FILE* f, bool makecopy) { fseek(f, 0, SEEK_END); @@ -271,7 +272,9 @@ void LoadFirmwareFromFile(FILE* f) fread(Firmware, 1, FirmwareLength, f); // take a backup - std::string fwBackupPath = FirmwarePath + ".bak"; + std::string fwBackupPath; + if (!makecopy) fwBackupPath = FirmwarePath + ".bak"; + else fwBackupPath = FirmwarePath; FILE* bf = Platform::OpenLocalFile(fwBackupPath, "rb"); if (!bf) { @@ -333,15 +336,24 @@ void Reset() else FirmwarePath = Platform::GetConfigString(Platform::FirmwarePath); + bool makecopy = false; + std::string origpath = FirmwarePath; + FirmwarePath += Platform::InstanceFileSuffix(); + FILE* f = Platform::OpenLocalFile(FirmwarePath, "rb"); if (!f) + { + f = Platform::OpenLocalFile(origpath, "rb"); + makecopy = true; + } + if (!f) { printf("Firmware not found! Generating default firmware.\n"); FirmwarePath = ""; } else { - LoadFirmwareFromFile(f); + LoadFirmwareFromFile(f, makecopy); fclose(f); } } @@ -385,28 +397,28 @@ void Reset() *(u16*)&Firmware[userdata+0x72] = CRC16(&Firmware[userdata], 0x70, 0xFFFF); - if (firmoverride) + //if (firmoverride) { u8 mac[6]; - bool rep; + bool rep = false; - if (Platform::GetConfigBool(Platform::Firm_RandomizeMAC)) - { - mac[0] = 0x00; - mac[1] = 0x09; - mac[2] = 0xBF; - mac[3] = rand()&0xFF; - mac[4] = rand()&0xFF; - mac[5] = rand()&0xFF; - rep = true; - } - else - { + memcpy(mac, &Firmware[0x36], 6); + + if (firmoverride) rep = Platform::GetConfigArray(Platform::Firm_MAC, mac); + + int inst = Platform::InstanceID(); + if (inst > 0) + { + rep = true; + mac[3] += inst; + mac[4] += inst*0x44; + mac[5] += inst*0x10; } if (rep) { + mac[0] &= 0xFC; // ensure the MAC isn't a broadcast MAC memcpy(&Firmware[0x36], mac, 6); *(u16*)&Firmware[0x2A] = CRC16(&Firmware[0x2C], *(u16*)&Firmware[0x2C], 0x0000); @@ -593,7 +605,12 @@ void Write(u8 val, u32 hold) } else { - FILE* f = Platform::OpenLocalFile("wfcsettings.bin", "wb"); + char wfcfile[50] = {0}; + int inst = Platform::InstanceID(); + if (inst > 0) snprintf(wfcfile, 49, "wfcsettings.bin", Platform::InstanceID()); + else strncpy(wfcfile, "wfcsettings.bin", 49); + + FILE* f = Platform::OpenLocalFile(wfcfile, "wb"); if (f) { u32 cutoff = 0x7F400 & FirmwareMask; diff --git a/src/SPU.cpp b/src/SPU.cpp index cba05586..9f245a20 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -184,6 +184,12 @@ void DoSavestate(Savestate* file) } +void SetPowerCnt(u32 val) +{ + // TODO +} + + void SetInterpolation(int type) { InterpType = type; diff --git a/src/SPU.h b/src/SPU.h index 1e20c117..1f28c2f8 100644 --- a/src/SPU.h +++ b/src/SPU.h @@ -31,6 +31,8 @@ void Stop(); void DoSavestate(Savestate* file); +void SetPowerCnt(u32 val); + // 0=none 1=linear 2=cosine 3=cubic void SetInterpolation(int type); diff --git a/src/Wifi.cpp b/src/Wifi.cpp index 4e3bc17d..c2614e73 100644 --- a/src/Wifi.cpp +++ b/src/Wifi.cpp @@ -26,43 +26,59 @@ #include "ARM.h" #include "GPU.h" + namespace Wifi { //#define WIFI_LOG printf #define WIFI_LOG(...) {} +#define PRINT_MAC(pf, mac) printf("%s: %02X:%02X:%02X:%02X:%02X:%02X\n", pf, (mac)[0], (mac)[1], (mac)[2], (mac)[3], (mac)[4], (mac)[5]); + u8 RAM[0x2000]; u16 IO[0x1000>>1]; #define IOPORT(x) IO[(x)>>1] +#define IOPORT8(x) ((u8*)IO)[x] + +// destination MACs for MP frames +const u8 MPCmdMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x00}; +const u8 MPReplyMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x10}; +const u8 MPAckMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x03}; + +const int kTimerInterval = 8; +const u32 kTimeCheckMask = ~(kTimerInterval - 1); + +bool Enabled; +bool PowerOn; + +s32 TimerError; u16 Random; +// general, always-on microsecond counter +u64 USTimestamp; + u64 USCounter; u64 USCompare; bool BlockBeaconIRQ14; u32 CmdCounter; -u16 BBCnt; -u8 BBWrite; u8 BBRegs[0x100]; u8 BBRegsRO[0x100]; u8 RFVersion; -u16 RFCnt; -u16 RFData1; -u16 RFData2; u32 RFRegs[0x40]; struct TXSlot { + bool Valid; u16 Addr; u16 Length; u8 Rate; u8 CurPhase; - u32 CurPhaseTime; + int CurPhaseTime; u32 HalfwordTimeMask; }; @@ -70,16 +86,17 @@ TXSlot TXSlots[6]; u8 RXBuffer[2048]; u32 RXBufferPtr; -u32 RXTime; +int RXTime; u32 RXHalfwordTimeMask; -u16 RXEndAddr; u32 ComStatus; // 0=waiting for packets 1=receiving 2=sending u32 TXCurSlot; u32 RXCounter; int MPReplyTimer; -int MPNumReplies; +u16 MPClientMask, MPClientFail; + +u8 MPClientReplies[15*1024]; bool MPInited; bool LANInited; @@ -87,6 +104,11 @@ bool LANInited; int USUntilPowerOn; bool ForcePowerOn; +// MULTIPLAYER SYNC APPARATUS +bool IsMPClient; +u64 NextSync; // for clients: timestamp for next sync point +u64 RXTimestamp; + // multiplayer host TX sequence: // 1. preamble // 2. IRQ7 @@ -112,21 +134,26 @@ bool ForcePowerOn; // 4 = switching from TX to RX // 5 = MP host data sent, waiting for replies (RFPINS=0x0084) // 6 = RX -// 7 = ?? +// 7 = switching from RX reply to TX ack // 8 = MP client sending reply, MP host sending ack (RFPINS=0x0046) // 9 = idle // wifi TODO: -// * power saving -// * RXSTAT, multiplay reply errors +// * RXSTAT // * TX errors (if applicable) bool Init() { - MPInited = false; - LANInited = false; + //MPInited = false; + //LANInited = false; + + Platform::MP_Init(); + MPInited = true; + + Platform::LAN_Init(); + LANInited = true; WifiAP::Init(); @@ -148,6 +175,9 @@ void Reset() memset(RAM, 0, 0x2000); memset(IO, 0, 0x1000); + Enabled = false; + PowerOn = false; + Random = 1; memset(BBRegs, 0, 0x100); @@ -200,19 +230,39 @@ void Reset() memset(&IOPORT(0x018), 0xFF, 6); memset(&IOPORT(0x020), 0xFF, 6); + // TODO: find out what the initial values are + IOPORT(W_PowerUS) = 0x0001; + + USTimestamp = 0; + USCounter = 0; USCompare = 0; BlockBeaconIRQ14 = false; + memset(TXSlots, 0, sizeof(TXSlots)); ComStatus = 0; TXCurSlot = -1; RXCounter = 0; + memset(RXBuffer, 0, sizeof(RXBuffer)); + RXBufferPtr = 0; + RXTime = 0; + RXHalfwordTimeMask = 0xFFFFFFFF; + MPReplyTimer = 0; - MPNumReplies = 0; + MPClientMask = 0; + MPClientFail = 0; + memset(MPClientReplies, 0, sizeof(MPClientReplies)); CmdCounter = 0; + USUntilPowerOn = 0; + ForcePowerOn = false; + + IsMPClient = false; + NextSync = 0; + RXTimestamp = 0; + WifiAP::Reset(); } @@ -228,8 +278,13 @@ void DoSavestate(Savestate* file) file->VarArray(RAM, 0x2000); file->VarArray(IO, 0x1000); + file->Bool32(&Enabled); + file->Bool32(&PowerOn); + file->Var16(&Random); + file->Var32((u32*)&TimerError); + file->VarArray(BBRegs, 0x100); file->VarArray(BBRegsRO, 0x100); @@ -240,17 +295,107 @@ void DoSavestate(Savestate* file) file->Var64(&USCompare); file->Bool32(&BlockBeaconIRQ14); + file->Var32(&CmdCounter); + + file->Var64(&USTimestamp); + + for (int i = 0; i < 6; i++) + { + TXSlot* slot = &TXSlots[i]; + + file->Bool32(&slot->Valid); + file->Var16(&slot->Addr); + file->Var16(&slot->Length); + file->Var8(&slot->Rate); + file->Var8(&slot->CurPhase); + file->Var32((u32*)&slot->CurPhaseTime); + file->Var32(&slot->HalfwordTimeMask); + } + + file->VarArray(RXBuffer, sizeof(RXBuffer)); + file->Var32(&RXBufferPtr); + file->Var32((u32*)&RXTime); + file->Var32(&RXHalfwordTimeMask); + file->Var32(&ComStatus); file->Var32(&TXCurSlot); file->Var32(&RXCounter); file->Var32((u32*)&MPReplyTimer); - file->Var32((u32*)&MPNumReplies); + file->Var16(&MPClientMask); + file->Var16(&MPClientFail); - file->Var32(&CmdCounter); + file->VarArray(MPClientReplies, sizeof(MPClientReplies)); + + file->Var32((u32*)&USUntilPowerOn); + file->Bool32(&ForcePowerOn); + + file->Bool32(&IsMPClient); + file->Var64(&NextSync); + file->Var64(&RXTimestamp); } +void ScheduleTimer(bool first) +{ + if (first) TimerError = 0; + + s32 cycles = 33513982 * kTimerInterval; + cycles -= TimerError; + s32 delay = (cycles + 999999) / 1000000; + TimerError = (delay * 1000000) - cycles; + + NDS::ScheduleEvent(NDS::Event_Wifi, !first, delay, USTimer, 0); +} + +void UpdatePowerOn() +{ + bool on = Enabled; + + if (NDS::ConsoleType == 1) + { + // TODO for DSi: + // * W_POWER_US doesn't work (atleast on DWM-W024) + // * other registers like GPIO_WIFI may also control wifi power/clock + // * turning wifi off via POWCNT2 while sending breaks further attempts at sending frames + } + else + { + on = on && ((IOPORT(W_PowerUS) & 0x1) == 0); + } + + if (on == PowerOn) + return; + + PowerOn = on; + if (on) + { + printf("WIFI: ON\n"); + + ScheduleTimer(true); + + Platform::MP_Begin(); + } + else + { + printf("WIFI: OFF\n"); + + NDS::CancelEvent(NDS::Event_Wifi); + + Platform::MP_End(); + } +} + +void SetPowerCnt(u32 val) +{ + Enabled = val & (1<<1); + UpdatePowerOn(); +} + + +void PowerDown(); +void StartTX_Beacon(); + void SetIRQ(u32 irq) { u32 oldflags = IOPORT(W_IF) & IOPORT(W_IE); @@ -269,7 +414,8 @@ void SetIRQ13() if (!(IOPORT(W_PowerTX) & 0x0002)) { IOPORT(0x034) = 0x0002; - // TODO: 03C + //PowerDown(); + // FIXME!! IOPORT(W_RFPins) = 0x0046; IOPORT(W_RFStatus) = 9; } @@ -318,21 +464,33 @@ void SetIRQ15() void SetStatus(u32 status) { - // TODO, eventually: states 2/4, also find out what state 7 is + // TODO, eventually: states 2/4/7 u16 rfpins[10] = {0x04, 0x84, 0, 0x46, 0, 0x84, 0x87, 0, 0x46, 0x04}; IOPORT(W_RFStatus) = status; IOPORT(W_RFPins) = rfpins[status]; } -bool MACEqual(u8* a, u8* b) +void PowerDown() +{ + IOPORT(W_TXReqRead) &= ~0x000F; + IOPORT(W_PowerState) |= 0x0200; + + // if the RF hardware is powered down while still sending or receiving, + // the current frame is completed before going idle + if (!ComStatus) + { + SetStatus(9); + } +} + + +bool MACEqual(const u8* a, const u8* b) { return (*(u32*)&a[0] == *(u32*)&b[0]) && (*(u16*)&a[4] == *(u16*)&b[4]); } -// TODO: set RFSTATUS/RFPINS - int PreambleLen(int rate) { if (rate == 1) return 192; @@ -340,6 +498,16 @@ int PreambleLen(int rate) return 192; } +u32 NumClients(u16 bitmask) +{ + u32 ret = 0; + for (int i = 1; i < 16; i++) + { + if (bitmask & (1<Addr + 0x4]; @@ -347,6 +515,19 @@ void IncrementTXCount(TXSlot* slot) *(u16*)&RAM[slot->Addr + 0x4] = cnt; } +void ReportMPReplyErrors(u16 clientfail) +{ + // TODO: do these trigger any IRQ? + + for (int i = 1; i < 16; i++) + { + if (!(clientfail & (1<Addr = (IOPORT(W_TXSlotCmd) & 0x0FFF) << 1; slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; @@ -401,9 +583,11 @@ void StartTX_Beacon() IOPORT(W_TXBusy) |= 0x0010; } -// TODO eventually: there is a small delay to firing TX void FireTX() { + if (!(IOPORT(W_RXCnt) & 0x8000)) + return; + u16 txbusy = IOPORT(W_TXBusy); u16 txreq = IOPORT(W_TXReqRead); @@ -443,39 +627,6 @@ void FireTX() } } -void SendMPReply(u16 clienttime, u16 clientmask) -{ - TXSlot* slot = &TXSlots[5]; - - // mark the last packet as success. dunno what the MSB is, it changes. - if (IOPORT(W_TXSlotReply2) & 0x8000) - *(u16*)&RAM[slot->Addr] = 0x0001; - - IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); - IOPORT(W_TXSlotReply1) = 0; - - // this seems to be set upon IRQ0 - // TODO: how does it behave if the packet addr is changed before it gets sent? (maybe just not possible) - if (IOPORT(W_TXSlotReply2) & 0x8000) - { - slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; - //*(u16*)&RAM[slot->Addr + 0x4] = 0x0001; - IncrementTXCount(slot); - } - - u16 clientnum = 0; - for (int i = 1; i < IOPORT(W_AIDLow); i++) - { - if (clientmask & (1<CurPhase = 0; - slot->CurPhaseTime = 16 + ((clienttime + 10) * clientnum); - - IOPORT(W_TXBusy) |= 0x0080; -} - void SendMPDefaultReply() { u8 reply[12 + 32]; @@ -488,25 +639,92 @@ void SendMPDefaultReply() // TODO reply[0x8] = 0x14; - *(u16*)&reply[0xC + 0x00] = 0x0158; - *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? - *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); - *(u16*)&reply[0xC + 0x06] = IOPORT(W_BSSID1); - *(u16*)&reply[0xC + 0x08] = IOPORT(W_BSSID2); - *(u16*)&reply[0xC + 0x0A] = IOPORT(W_MACAddr0); - *(u16*)&reply[0xC + 0x0C] = IOPORT(W_MACAddr1); - *(u16*)&reply[0xC + 0x0E] = IOPORT(W_MACAddr2); - *(u16*)&reply[0xC + 0x10] = 0x0903; - *(u16*)&reply[0xC + 0x12] = 0x00BF; - *(u16*)&reply[0xC + 0x14] = 0x1000; - *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; - *(u32*)&reply[0xC + 0x18] = 0; + *(u16*)&reply[0xC + 0x00] = 0x0158; + *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? + *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); + *(u16*)&reply[0xC + 0x06] = IOPORT(W_BSSID1); + *(u16*)&reply[0xC + 0x08] = IOPORT(W_BSSID2); + *(u16*)&reply[0xC + 0x0A] = IOPORT(W_MACAddr0); + *(u16*)&reply[0xC + 0x0C] = IOPORT(W_MACAddr1); + *(u16*)&reply[0xC + 0x0E] = IOPORT(W_MACAddr2); + *(u16*)&reply[0xC + 0x10] = 0x0903; + *(u16*)&reply[0xC + 0x12] = 0x00BF; + *(u16*)&reply[0xC + 0x14] = 0x1000; + *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; + *(u32*)&reply[0xC + 0x18] = 0; - int txlen = Platform::MP_SendPacket(reply, 12+28); - WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); + int txlen = Platform::MP_SendReply(reply, 12+28, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); } -void SendMPAck() +void SendMPReply(u16 clienttime, u16 clientmask) +{ + TXSlot* slot = &TXSlots[5]; + + // mark the last packet as success. dunno what the MSB is, it changes. + //if (slot->Valid) + if (IOPORT(W_TXSlotReply2) & 0x8000) + *(u16*)&RAM[slot->Addr] = 0x0001; + + // CHECKME!! + // can the transfer rate for MP replies be set, or is it determined from the CMD transfer rate? + // how does it work for default empty replies? + slot->Rate = 2; + + IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); + IOPORT(W_TXSlotReply1) = 0; + + if (!(IOPORT(W_TXSlotReply2) & 0x8000)) + { + slot->Valid = false; + } + else + { + slot->Valid = true; + + slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; + slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; + + // the packet is entirely ignored if it lasts longer than the maximum reply time + u32 duration = PreambleLen(slot->Rate) + (slot->Length * (slot->Rate==2 ? 4:8)); + if (duration > clienttime) + slot->Valid = false; + } + + //if (RAM[slot->Addr+4] > 0) + // printf("REPLY RETRY COUNTER %d (%04X)\n", RAM[slot->Addr+4], IOPORT(W_TXSlotReply2)); + + // this seems to be set upon IRQ0 + // TODO: how does it behave if the packet addr is changed before it gets sent? (maybe just not possible) + if (slot->Valid) + { + //*(u16*)&RAM[slot->Addr + 0x4] = 0x0001; + IncrementTXCount(slot); + + slot->CurPhase = 0; + int txlen = Platform::MP_SendReply(&RAM[slot->Addr], 12 + slot->Length, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/%d bytes of MP reply\n", txlen, 12 + slot->Length); + } + else + { + slot->CurPhase = 10; + + SendMPDefaultReply(); + } + + u16 clientnum = 0; + for (int i = 1; i < IOPORT(W_AIDLow); i++) + { + if (clientmask & (1<CurPhaseTime = 16 + ((clienttime + 10) * clientnum) + PreambleLen(slot->Rate); + + IOPORT(W_TXBusy) |= 0x0080; +} + +void SendMPAck(u16 clientfail) { u8 ack[12 + 32]; @@ -516,41 +734,32 @@ void SendMPAck() if (TXSlots[1].Rate == 2) ack[0x8] = 0x14; else ack[0x8] = 0xA; - *(u16*)&ack[0xC + 0x00] = 0x0218; - *(u16*)&ack[0xC + 0x02] = 0; - *(u16*)&ack[0xC + 0x04] = 0x0903; - *(u16*)&ack[0xC + 0x06] = 0x00BF; - *(u16*)&ack[0xC + 0x08] = 0x0300; - *(u16*)&ack[0xC + 0x0A] = IOPORT(W_BSSID0); - *(u16*)&ack[0xC + 0x0C] = IOPORT(W_BSSID1); - *(u16*)&ack[0xC + 0x0E] = IOPORT(W_BSSID2); - *(u16*)&ack[0xC + 0x10] = IOPORT(W_MACAddr0); - *(u16*)&ack[0xC + 0x12] = IOPORT(W_MACAddr1); - *(u16*)&ack[0xC + 0x14] = IOPORT(W_MACAddr2); - *(u16*)&ack[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; - *(u16*)&ack[0xC + 0x18] = 0x0033; // ??? - *(u16*)&ack[0xC + 0x1A] = 0; - *(u32*)&ack[0xC + 0x1C] = 0; + *(u16*)&ack[0xC + 0x00] = 0x0218; + *(u16*)&ack[0xC + 0x02] = 0; + *(u16*)&ack[0xC + 0x04] = 0x0903; + *(u16*)&ack[0xC + 0x06] = 0x00BF; + *(u16*)&ack[0xC + 0x08] = 0x0300; + *(u16*)&ack[0xC + 0x0A] = IOPORT(W_BSSID0); + *(u16*)&ack[0xC + 0x0C] = IOPORT(W_BSSID1); + *(u16*)&ack[0xC + 0x0E] = IOPORT(W_BSSID2); + *(u16*)&ack[0xC + 0x10] = IOPORT(W_MACAddr0); + *(u16*)&ack[0xC + 0x12] = IOPORT(W_MACAddr1); + *(u16*)&ack[0xC + 0x14] = IOPORT(W_MACAddr2); + *(u16*)&ack[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; + *(u16*)&ack[0xC + 0x18] = 0x0033; // ??? + *(u16*)&ack[0xC + 0x1A] = clientfail; + *(u32*)&ack[0xC + 0x1C] = 0; - int txlen = Platform::MP_SendPacket(ack, 12+32); - WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); + int txlen = Platform::MP_SendAck(ack, 12+32, USTimestamp); + WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); } -u32 NumClients(u16 bitmask) -{ - u32 ret = 0; - for (int i = 1; i < 16; i++) - { - if (bitmask & (1<CurPhaseTime--; + slot->CurPhaseTime -= kTimerInterval; if (slot->CurPhaseTime > 0) { if (slot->CurPhase == 1) @@ -560,19 +769,28 @@ bool ProcessTX(TXSlot* slot, int num) } else if (slot->CurPhase == 2) { - MPReplyTimer--; - if (MPReplyTimer == 0 && MPNumReplies > 0) + MPReplyTimer -= kTimerInterval; + if (MPReplyTimer <= 0 && MPClientMask != 0) { - if (CheckRX(true)) + int nclient = 1; + while (!(MPClientMask & (1 << nclient))) nclient++; + + u32 curclient = 1 << nclient; + + /*if (CheckRX(1)) { - ComStatus |= 0x1; + // we received a reply, mark it as such + // TODO: is any received packet considered a good reply? + // hardware probably requires a specific frame-control and/or destination MAC + + MPClientFail &= ~curclient; } + else printf("REPLY %04X NOT RECEIVED\n");*/ + if (!(MPClientFail & curclient)) + MPClientReplyRX(nclient); - // TODO: properly handle reply errors - // also, if the reply is too big to fit within its window, what happens? - - MPReplyTimer = 10 + IOPORT(W_CmdReplyTime); - MPNumReplies--; + MPReplyTimer += 10 + IOPORT(W_CmdReplyTime); + MPClientMask &= ~curclient; } } @@ -592,31 +810,16 @@ bool ProcessTX(TXSlot* slot, int num) SetStatus(8); - // if no reply is configured, send a default empty reply - if (!(IOPORT(W_TXSlotReply2) & 0x8000)) - { - SendMPDefaultReply(); + //slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; + //slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; - slot->Addr = 0; - slot->Length = 28; - slot->Rate = 2; // TODO - slot->CurPhase = 4; - slot->CurPhaseTime = 28*4; - slot->HalfwordTimeMask = 0xFFFFFFFF; - IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; - break; - } - - slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; - slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; + /*u8 rate = RAM[slot->Addr + 0x8]; + if (rate == 0x14) slot->Rate = 2; + else slot->Rate = 1;*/ // TODO: duration should be set by hardware // doesn't seem to be important //RAM[slot->Addr + 0xC + 2] = 0x00F0; - - u8 rate = RAM[slot->Addr + 0x8]; - if (rate == 0x14) slot->Rate = 2; - else slot->Rate = 1; } else SetStatus(3); @@ -625,17 +828,32 @@ bool ProcessTX(TXSlot* slot, int num) if (slot->Rate == 2) { len *= 4; - slot->HalfwordTimeMask = 0x7; + slot->HalfwordTimeMask = 0x7 & kTimeCheckMask; } else { len *= 8; - slot->HalfwordTimeMask = 0xF; + slot->HalfwordTimeMask = 0xF & kTimeCheckMask; } slot->CurPhase = 1; slot->CurPhaseTime = len; + u16 framectl = *(u16*)&RAM[slot->Addr + 0xC]; + if (framectl & (1<<14)) + { + // WEP frame + // TODO: what happens when sending a WEP frame while WEP processing is off? + // TODO: some form of actual WEP processing? + // for now we just set the WEP FCS to a nonzero value, because some games require it + + if (IOPORT(W_WEPCnt) & (1<<15)) + { + u32 wep_fcs = (slot->Addr + 0xC + slot->Length - 7) & ~0x1; + *(u32*)&RAM[wep_fcs] = 0x22334466; + } + } + u64 oldts; if (num == 4) { @@ -644,28 +862,66 @@ bool ProcessTX(TXSlot* slot, int num) *(u64*)&RAM[slot->Addr + 0xC + 24] = USCounter; } - //u32 noseqno = 0; - //if (num == 1) noseqno = (IOPORT(W_TXSlotCmd) & 0x4000); + u32 noseqno = 0; + if (num == 1) noseqno = (IOPORT(W_TXSlotCmd) & 0x4000); - //if (!noseqno) + if (!noseqno) { *(u16*)&RAM[slot->Addr + 0xC + 22] = IOPORT(W_TXSeqNo) << 4; IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; } + if ((num != 5) && (RAM[slot->Addr+4] > 0)) + printf("SLOT %d RETRY COUNTER %d\n", RAM[slot->Addr+4]); + // set TX addr IOPORT(W_RXTXAddr) = slot->Addr >> 1; - // send - int txlen = Platform::MP_SendPacket(&RAM[slot->Addr], 12 + slot->Length); - WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", - txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], - *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + if (num == 1) + { + // send + int txlen = Platform::MP_SendCmd(&RAM[slot->Addr], 12 + slot->Length, USTimestamp); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + } + else if (num == 5) + { + // send + /*int txlen = Platform::MP_SendReply(&RAM[slot->Addr], 12 + slot->Length, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]);*/ + } + else //if (num != 5) + { + // send + int txlen = Platform::MP_SendPacket(&RAM[slot->Addr], 12 + slot->Length, USTimestamp); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + } // if the packet is being sent via LOC1..3, send it to the AP // any packet sent via CMD/REPLY/BEACON isn't going to have much use outside of local MP if (num == 0 || num == 2 || num == 3) + { + if ((framectl & 0x00FF) == 0x0010) + { + u16 aid = *(u16*)&RAM[slot->Addr + 0xC + 24 + 4]; + if (aid) printf("[HOST] syncing client %04X, sync=%016llX\n", aid, USTimestamp); + } + else if ((framectl & 0x00FF) == 0x00C0) + { + if (IsMPClient) + { + printf("[CLIENT] deauth\n"); + IsMPClient = false; + } + } + WifiAP::SendPacket(&RAM[slot->Addr], 12 + slot->Length); + } if (num == 4) { @@ -674,10 +930,25 @@ bool ProcessTX(TXSlot* slot, int num) } break; + case 10: // preamble done (default empty MP reply) + { + SetIRQ(7); + SetStatus(8); + + //SendMPDefaultReply(); + + //slot->Addr = 0; + //slot->Length = 28; + slot->CurPhase = 4; + slot->CurPhaseTime = 28*4; + slot->HalfwordTimeMask = 0xFFFFFFFF; + } + break; + case 1: // transmit done { - // for the MP reply slot, this is set later - if (num != 5) + // for the MP CMD and reply slots, this is set later + if (num != 1 && num != 5) *(u16*)&RAM[slot->Addr] = 0x0001; RAM[slot->Addr + 5] = 0; @@ -690,12 +961,21 @@ bool ProcessTX(TXSlot* slot, int num) } SetStatus(5); - u16 clientmask = *(u16*)&RAM[slot->Addr + 12 + 24 + 2]; - MPNumReplies = NumClients(clientmask); - MPReplyTimer = 16; + u16 clientmask = *(u16*)&RAM[slot->Addr + 12 + 24 + 2] & 0xFFFE; + //MPNumReplies = NumClients(clientmask); + MPReplyTimer = 16 + PreambleLen(slot->Rate); + MPClientMask = clientmask; + MPClientFail = clientmask; + u16 res = 0; + if (clientmask) + res = Platform::MP_RecvReplies(MPClientReplies, USTimestamp, clientmask); + MPClientFail &= ~res; + + // TODO: 112 likely includes the ack preamble, which needs adjusted + // for long-preamble settings slot->CurPhase = 2; - slot->CurPhaseTime = 112 + ((10 + IOPORT(W_CmdReplyTime)) * MPNumReplies); + slot->CurPhaseTime = 112 + ((10 + IOPORT(W_CmdReplyTime)) * NumClients(clientmask)); break; } @@ -750,7 +1030,10 @@ bool ProcessTX(TXSlot* slot, int num) if (slot->Rate == 2) slot->CurPhaseTime = 32 * 4; else slot->CurPhaseTime = 32 * 8; - SendMPAck(); + ReportMPReplyErrors(MPClientFail); + + // send + SendMPAck(MPClientFail); slot->CurPhase = 3; } @@ -762,11 +1045,15 @@ bool ProcessTX(TXSlot* slot, int num) IOPORT(W_TXBusy) &= ~(1<<1); IOPORT(W_TXSlotCmd) &= 0x7FFF; // confirmed - // seems this is set to indicate which clients failed to reply - *(u16*)&RAM[slot->Addr + 0x2] = 0; + if (!MPClientFail) + *(u16*)&RAM[slot->Addr] = 0x0001; + else + *(u16*)&RAM[slot->Addr] = 0x0005; + + // this is set to indicate which clients failed to reply + *(u16*)&RAM[slot->Addr + 0x2] = MPClientFail; IncrementTXCount(slot); - SetIRQ(12); IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; if (IOPORT(W_TXStatCnt) & 0x2000) @@ -776,12 +1063,19 @@ bool ProcessTX(TXSlot* slot, int num) } SetStatus(1); + // TODO: retry the whole cycle if some clients failed to respond + // AND if there is enough time left in CMDCOUNT + // (games seem to always configure CMDCOUNT such that there is no time for retries) + SetIRQ(12); + FireTX(); } return true; case 4: // MP default reply transfer finished { + IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; + IOPORT(W_TXBusy) &= ~0x80; SetStatus(1); FireTX(); @@ -804,126 +1098,21 @@ inline void IncrementRXAddr(u16& addr, u16 inc = 2) } } -bool CheckRX(bool block) +void StartRX() { - if (!(IOPORT(W_RXCnt) & 0x8000)) - return false; - - if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) - return false; - - u16 framelen; - u16 framectl; - u8 txrate; - bool bssidmatch; - u16 rxflags; - - for (;;) - { - int rxlen = Platform::MP_RecvPacket(RXBuffer, block); - if (rxlen == 0) rxlen = WifiAP::RecvPacket(RXBuffer); - if (rxlen == 0) return false; - if (rxlen < 12+24) continue; - - framelen = *(u16*)&RXBuffer[10]; - if (framelen != rxlen-12) - { - printf("bad frame length\n"); - continue; - } - framelen -= 4; - - framectl = *(u16*)&RXBuffer[12+0]; - txrate = RXBuffer[8]; - - u32 a_src, a_dst, a_bss; - rxflags = 0x0010; - switch (framectl & 0x000C) - { - case 0x0000: // management - a_src = 10; - a_dst = 4; - a_bss = 16; - if ((framectl & 0x00F0) == 0x0080) - rxflags |= 0x0001; - break; - - case 0x0004: // control - printf("blarg\n"); - continue; - - case 0x0008: // data - switch (framectl & 0x0300) - { - case 0x0000: // STA to STA - a_src = 10; - a_dst = 4; - a_bss = 16; - break; - case 0x0100: // STA to DS - a_src = 10; - a_dst = 16; - a_bss = 4; - break; - case 0x0200: // DS to STA - a_src = 16; - a_dst = 4; - a_bss = 10; - break; - case 0x0300: // DS to DS - printf("blarg\n"); - continue; - } - // TODO: those also trigger on other framectl values - // like 0208 -> C - framectl &= 0xE7FF; - if (framectl == 0x0228) rxflags |= 0x000C; // MP host frame - else if (framectl == 0x0218) rxflags |= 0x000D; // MP ack frame - else if (framectl == 0x0118) rxflags |= 0x000E; // MP reply frame - else if (framectl == 0x0158) rxflags |= 0x000F; // empty MP reply frame - else rxflags |= 0x0008; - break; - } - - if (MACEqual(&RXBuffer[12 + a_src], (u8*)&IOPORT(W_MACAddr0))) - continue; // oops. we received a packet we just sent. - - bssidmatch = MACEqual(&RXBuffer[12 + a_bss], (u8*)&IOPORT(W_BSSID0)); - //if (!(IOPORT(W_BSSID0) & 0x0001) && !(RXBuffer[12 + a_bss] & 0x01) && - if (!MACEqual(&RXBuffer[12 + a_dst], (u8*)&IOPORT(W_MACAddr0)) && - !(RXBuffer[12 + a_dst] & 0x01)) - { - printf("received packet %04X but it didn't pass the MAC check\n", framectl); - continue; - } - - break; - } - - WIFI_LOG("wifi: received packet FC:%04X SN:%04X CL:%04X RXT:%d CMT:%d\n", - framectl, *(u16*)&RXBuffer[12+4+6+6+6], *(u16*)&RXBuffer[12+4+6+6+6+2+2], framelen*4, IOPORT(W_CmdReplyTime)); - - // make RX header - - if (bssidmatch) rxflags |= 0x8000; - - *(u16*)&RXBuffer[0] = rxflags; - *(u16*)&RXBuffer[2] = 0x0040; // ??? - *(u16*)&RXBuffer[6] = txrate; - *(u16*)&RXBuffer[8] = framelen; - *(u16*)&RXBuffer[10] = 0x4080; // min/max RSSI. dunno - + u16 framelen = *(u16*)&RXBuffer[8]; RXTime = framelen; + u16 txrate = *(u16*)&RXBuffer[6]; if (txrate == 0x14) { RXTime *= 4; - RXHalfwordTimeMask = 0x7; + RXHalfwordTimeMask = 0x7 & kTimeCheckMask; } else { RXTime *= 8; - RXHalfwordTimeMask = 0xF; + RXHalfwordTimeMask = 0xF & kTimeCheckMask; } u16 addr = IOPORT(W_RXBufWriteCursor) << 1; @@ -934,6 +1123,443 @@ bool CheckRX(bool block) SetIRQ(6); SetStatus(6); + ComStatus |= 1; +} + +void FinishRX() +{ + ComStatus &= ~0x1; + RXCounter = 0; + + if (!ComStatus) + { + if (IOPORT(W_PowerState) & 0x0300) + SetStatus(9); + else + SetStatus(1); + } + + // TODO: RX stats + + u16 framectl = *(u16*)&RXBuffer[12]; + + // check the frame's destination address + // note: the hardware always checks the first address field, regardless of the frame type/etc + // similarly, the second address field is used to send acks to non-broadcast frames + + u8* dstmac = &RXBuffer[12 + 4]; + if (!(dstmac[0] & 0x01)) + { + if (!MACEqual(dstmac, (u8*)&IOPORT(W_MACAddr0))) + return; + } + + // reject the frame if it's a WEP frame and WEP is off + // TODO: check if sending WEP frames with WEP off works at all? + + if (framectl & (1<<14)) + { + if (!(IOPORT(W_WEPCnt) & (1<<15))) + return; + } + + // apply RX filtering + // TODO: + // * RXFILTER bits 0, 9, 10, 12 not fully understood + // * port 0D8 also affects reception of frames + // * MP CMD frames with a duplicate sequence number are ignored + + u16 rxflags = 0x0010; + + switch ((framectl >> 2) & 0x3) + { + case 0: // management + { + u8* bssid = &RXBuffer[12 + 16]; + if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) + rxflags |= 0x8000; + + u16 subtype = (framectl >> 4) & 0xF; + if (subtype == 0x8) // beacon + { + if (!(rxflags & 0x8000)) + { + if (!(IOPORT(W_RXFilter) & (1<<0))) + return; + } + + rxflags |= 0x0001; + } + else if ((subtype <= 0x5) || + (subtype >= 0xA && subtype <= 0xC)) + { + if (!(rxflags & 0x8000)) + { + // CHECKME! + if (!(IOPORT(W_RXFilter) & (3<<9))) + return; + } + } + } + break; + + case 1: // control + { + if ((framectl & 0xF0) == 0xA0) // PS-poll + { + u8* bssid = &RXBuffer[12 + 4]; + if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) + rxflags |= 0x8000; + + if (!(rxflags & 0x8000)) + { + if (!(IOPORT(W_RXFilter) & (1<<11))) + return; + } + + rxflags |= 0x0005; + } + else + return; + } + break; + + case 2: // data + { + u16 fromto = (framectl >> 8) & 0x3; + if (IOPORT(W_RXFilter2) & (1<> 4) & 0xF) + { + case 0x0: break; + + case 0x1: + if ((rxflags & 0xF) == 0xD) + { + if (!(rxfilter & (1<<7))) return; + } + else if ((rxflags & 0xF) != 0xE) + { + if (!(rxfilter & (1<<1))) return; + } + break; + + case 0x2: + if ((rxflags & 0xF) != 0xC) + { + if (!(rxfilter & (1<<2))) return; + } + break; + + case 0x3: + if (!(rxfilter & (1<<3))) return; + break; + + case 0x4: break; + + case 0x5: + if ((rxflags & 0xF) == 0xF) + { + if (!(rxfilter & (1<<8))) return; + } + else + { + if (!(rxfilter & (1<<4))) return; + } + break; + + case 0x6: + if (!(rxfilter & (1<<5))) return; + break; + + case 0x7: + if (!(rxfilter & (1<<6))) return; + break; + + default: + return; + } + } + break; + } + + // build the RX header + + u16 headeraddr = IOPORT(W_RXBufWriteCursor) << 1; + *(u16*)&RAM[headeraddr] = rxflags; + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = 0x0040; // ??? + IncrementRXAddr(headeraddr, 4); + *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[6]; // TX rate + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[8]; // frame length + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = 0x4080; // RSSI + + // signal successful reception + + u16 addr = IOPORT(W_RXTXAddr) << 1; + if (addr & 0x2) IncrementRXAddr(addr); + IOPORT(W_RXBufWriteCursor) = (addr & ~0x3) >> 1; + + SetIRQ(0); + + if ((rxflags & 0x800F) == 0x800C) + { + // reply to CMD frames + + u16 clientmask = *(u16*)&RXBuffer[0xC + 26]; + if (IOPORT(W_AIDLow) && (clientmask & (1 << IOPORT(W_AIDLow)))) + { + SendMPReply(*(u16*)&RXBuffer[0xC + 24], clientmask); + } + else + { + // send a blank + // this is just so the host can have something to receive, instead of hitting a timeout + // in the case this client wasn't ready to send a reply + // TODO: also send this if we have RX disabled + + Platform::MP_SendReply(nullptr, 0, USTimestamp, 0); + } + } + else if ((rxflags & 0x800F) == 0x8001) + { + // when receiving a beacon with the right BSSID, the beacon's timestamp + // is copied to USCOUNTER + + u32 len = *(u16*)&RXBuffer[8]; + u16 txrate = *(u16*)&RXBuffer[6]; + len *= ((txrate==0x14) ? 4 : 8); + len -= 76; // CHECKME: is this offset fixed? + + u64 timestamp = *(u64*)&RXBuffer[12 + 24]; + timestamp += (u64)len; + + USCounter = timestamp; + } +} + +void MPClientReplyRX(int client) +{ + if (IOPORT(W_PowerState) & 0x0300) + return; + + if (!(IOPORT(W_RXCnt) & 0x8000)) + return; + + if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) + return; + + int framelen; + u8 txrate; + + u8* reply = &MPClientReplies[(client-1)*1024]; + framelen = *(u16*)&reply[10]; + + txrate = reply[8]; + + // TODO: what are the maximum crop values? + u16 framectl = *(u16*)&reply[12]; + if (framectl & (1<<14)) + { + framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; + if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); + } + else + framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; + + if (framelen < 0) framelen = 0; + + // TODO rework RX system so we don't need this (by reading directly into MPClientReplies) + memcpy(RXBuffer, reply, 12+framelen); + + *(u16*)&RXBuffer[6] = txrate; + *(u16*)&RXBuffer[8] = framelen; + + RXTimestamp = 0; + StartRX(); +} + +bool CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames +{ + if (IOPORT(W_PowerState) & 0x0300) + return false; + + if (!(IOPORT(W_RXCnt) & 0x8000)) + return false; + + if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) + return false; + + int rxlen; + int framelen; + u16 framectl; + u8 txrate; + u64 timestamp; + + for (;;) + { + timestamp = 0; + + if (type == 0) + { + rxlen = Platform::MP_RecvPacket(RXBuffer, ×tamp); + if (rxlen <= 0) + rxlen = WifiAP::RecvPacket(RXBuffer); + } + else + { + rxlen = Platform::MP_RecvHostPacket(RXBuffer, ×tamp); + if (rxlen < 0) + { + // host is gone + // TODO: make this more resilient + IsMPClient = false; + } + } + + if (rxlen <= 0) return false; + if (rxlen < 12+24) continue; + + framelen = *(u16*)&RXBuffer[10]; + if (framelen != rxlen-12) + { + printf("bad frame length %d/%d\n", framelen, rxlen-12); + continue; + } + + framectl = *(u16*)&RXBuffer[12+0]; + txrate = RXBuffer[8]; + + // TODO: what are the maximum crop values? + if (framectl & (1<<14)) + { + framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; + if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); + } + else + framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; + + if (framelen < 0) framelen = 0; + + break; + } + + WIFI_LOG("wifi: received packet FC:%04X SN:%04X CL:%04X RXT:%d CMT:%d\n", + framectl, *(u16*)&RXBuffer[12+4+6+6+6], *(u16*)&RXBuffer[12+4+6+6+6+2+2], framelen*4, IOPORT(W_CmdReplyTime)); + + *(u16*)&RXBuffer[6] = txrate; + *(u16*)&RXBuffer[8] = framelen; + + bool macgood = (RXBuffer[12 + 4] & 0x01) || MACEqual(&RXBuffer[12 + 4], (u8*)&IOPORT(W_MACAddr0)); + + if (((framectl & 0x00FF) == 0x0010) && timestamp && macgood) + { + // if receiving an association response: get the sync value from the host + + u16 aid = *(u16*)&RXBuffer[12+24+4]; + + if (aid) + { + printf("[CLIENT %01X] host sync=%016llX\n", aid&0xF, timestamp); + + IsMPClient = true; + USTimestamp = timestamp; + NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); + } + + RXTimestamp = 0; + StartRX(); + } + else if (((framectl & 0x00FF) == 0x00C0) && timestamp && macgood && IsMPClient) + { + IsMPClient = false; + NextSync = 0; + + RXTimestamp = 0; + StartRX(); + } + else if (macgood && IsMPClient) + { + // if we are being a MP client, we need to delay this frame until we reach the + // timestamp it came with + // we also need to determine how far we can run after having received this frame + + RXTimestamp = timestamp; + if (RXTimestamp < USTimestamp) RXTimestamp = USTimestamp; + NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); + + if (MACEqual(&RXBuffer[12 + 4], MPCmdMAC)) + { + u16 clienttime = *(u16*)&RXBuffer[12+24]; + u16 clientmask = *(u16*)&RXBuffer[12+26]; + + // include the MP reply time window + NextSync += 112 + ((clienttime + 10) * NumClients(clientmask)); + } + } + else + { + // otherwise, just start receiving this frame now + + RXTimestamp = 0; + StartRX(); + } + return true; } @@ -942,7 +1568,7 @@ void MSTimer() { if (IOPORT(W_USCompareCnt)) { - if (USCounter == USCompare) + if ((USCounter & ~0x3FF) == USCompare) { BlockBeaconIRQ14 = false; SetIRQ14(0); @@ -964,16 +1590,34 @@ void MSTimer() void USTimer(u32 param) { - WifiAP::USTimer(); + USTimestamp += kTimerInterval; + + if (IsMPClient && (!ComStatus)) + { + if (RXTimestamp && (USTimestamp >= RXTimestamp)) + { + RXTimestamp = 0; + StartRX(); + } + + if (USTimestamp >= NextSync) + { + // TODO: not do this every tick if it fails to receive a frame! + CheckRX(2); + } + } + + if (!(USTimestamp & 0x3FF & kTimeCheckMask)) + WifiAP::MSTimer(); bool switchOffPowerSaving = false; if (USUntilPowerOn < 0) { - USUntilPowerOn++; + USUntilPowerOn += kTimerInterval; - switchOffPowerSaving = USUntilPowerOn == 0 && (IOPORT(W_PowerUnk) & 0x0001 || ForcePowerOn); + switchOffPowerSaving = (USUntilPowerOn >= 0) && (IOPORT(W_PowerUnk) & 0x0001 || ForcePowerOn); } - if (USUntilPowerOn == 0 && (IOPORT(W_PowerState) & 0x0002 || switchOffPowerSaving)) + if ((USUntilPowerOn >= 0) && (IOPORT(W_PowerState) & 0x0002 || switchOffPowerSaving)) { IOPORT(W_PowerState) = 0; IOPORT(W_RFPins) = 1; @@ -983,35 +1627,50 @@ void USTimer(u32 param) if (IOPORT(W_USCountCnt)) { - USCounter++; + USCounter += kTimerInterval; u32 uspart = (USCounter & 0x3FF); if (IOPORT(W_USCompareCnt)) { u32 beaconus = (IOPORT(W_BeaconCount1) << 10) | (0x3FF - uspart); - if (beaconus == IOPORT(W_PreBeacon)) SetIRQ15(); + if ((beaconus & kTimeCheckMask) == (IOPORT(W_PreBeacon) & kTimeCheckMask)) + SetIRQ15(); } - if (!uspart) MSTimer(); + if (!(uspart & kTimeCheckMask)) + MSTimer(); } if (IOPORT(W_CmdCountCnt) & 0x0001) { if (CmdCounter > 0) { - CmdCounter--; + if (CmdCounter < kTimerInterval) + CmdCounter = 0; + else + CmdCounter -= kTimerInterval; } } if (IOPORT(W_ContentFree) != 0) - IOPORT(W_ContentFree)--; - - if (!(IOPORT(W_PowerState) & 0x300)) { - if (ComStatus == 0) + if (IOPORT(W_ContentFree) < kTimerInterval) + IOPORT(W_ContentFree) = 0; + else + IOPORT(W_ContentFree) -= kTimerInterval; + } + + if (ComStatus == 0) + { + u16 txbusy = IOPORT(W_TXBusy); + if (txbusy) { - u16 txbusy = IOPORT(W_TXBusy); - if (txbusy) + if (IOPORT(W_PowerState) & 0x0300) + { + ComStatus = 0; + TXCurSlot = -1; + } + else { ComStatus = 0x2; if (txbusy & 0x0080) TXCurSlot = 5; @@ -1021,105 +1680,94 @@ void USTimer(u32 param) else if (txbusy & 0x0002) TXCurSlot = 1; else if (txbusy & 0x0001) TXCurSlot = 0; } + } + else + { + if ((!IsMPClient) || (USTimestamp > NextSync)) + { + if ((!(RXCounter & 0x1FF & kTimeCheckMask)) && (!ComStatus)) + { + CheckRX(0); + } + } + + RXCounter += kTimerInterval; + } + } + + if (ComStatus & 0x2) + { + bool finished = ProcessTX(&TXSlots[TXCurSlot], TXCurSlot); + if (finished) + { + if (IOPORT(W_PowerState) & 0x0300) + { + IOPORT(W_TXBusy) = 0; + SetStatus(9); + } + + // transfer finished, see if there's another slot to do + // checkme: priority order of beacon/reply + // TODO: for CMD, check CMDCOUNT + u16 txbusy = IOPORT(W_TXBusy); + if (txbusy & 0x0080) TXCurSlot = 5; + else if (txbusy & 0x0010) TXCurSlot = 4; + else if (txbusy & 0x0008) TXCurSlot = 3; + else if (txbusy & 0x0004) TXCurSlot = 2; + else if (txbusy & 0x0002) TXCurSlot = 1; + else if (txbusy & 0x0001) TXCurSlot = 0; else { - if ((!(RXCounter & 0x1FF))) - { - if (CheckRX(false)) - ComStatus = 0x1; - } - - RXCounter++; + TXCurSlot = -1; + ComStatus = 0; + RXCounter = 0; } } - - if (ComStatus & 0x2) + } + if (ComStatus & 0x1) + { + RXTime -= kTimerInterval; + if (!(RXTime & RXHalfwordTimeMask)) { - bool finished = ProcessTX(&TXSlots[TXCurSlot], TXCurSlot); - if (finished) + u16 addr = IOPORT(W_RXTXAddr) << 1; + if (addr < 0x1FFF) *(u16*)&RAM[addr] = *(u16*)&RXBuffer[RXBufferPtr]; + + IncrementRXAddr(addr); + IOPORT(W_RXTXAddr) = addr >> 1; + RXBufferPtr += 2; + + if (RXTime <= 0) // finished receiving { - // transfer finished, see if there's another slot to do - // checkme: priority order of beacon/reply - // TODO: for CMD, check CMDCOUNT - u16 txbusy = IOPORT(W_TXBusy); - if (txbusy & 0x0080) TXCurSlot = 5; - else if (txbusy & 0x0010) TXCurSlot = 4; - else if (txbusy & 0x0008) TXCurSlot = 3; - else if (txbusy & 0x0004) TXCurSlot = 2; - else if (txbusy & 0x0002) TXCurSlot = 1; - else if (txbusy & 0x0001) TXCurSlot = 0; - else - { - TXCurSlot = -1; - ComStatus = 0; - RXCounter = 0; - } + FinishRX(); } - } - if (ComStatus & 0x1) - { - RXTime--; - if (!(RXTime & RXHalfwordTimeMask)) + else if (addr == (IOPORT(W_RXBufReadCursor) << 1)) { - u16 addr = IOPORT(W_RXTXAddr) << 1; - if (addr < 0x1FFF) *(u16*)&RAM[addr] = *(u16*)&RXBuffer[RXBufferPtr]; + // TODO: properly check the crossing of the read cursor + // (for example, if it is outside of the RX buffer) - IncrementRXAddr(addr); - RXBufferPtr += 2; - - if (RXTime == 0) // finished receiving + printf("wifi: RX buffer full (buf=%04X/%04X rd=%04X wr=%04X rxtx=%04X power=%04X com=%d rxcnt=%04X filter=%04X/%04X frame=%04X/%04X len=%d)\n", + (IOPORT(W_RXBufBegin)>>1)&0xFFF, (IOPORT(W_RXBufEnd)>>1)&0xFFF, + IOPORT(W_RXBufReadCursor), IOPORT(W_RXBufWriteCursor), + IOPORT(W_RXTXAddr), IOPORT(W_PowerState), ComStatus, + IOPORT(W_RXCnt), IOPORT(W_RXFilter), IOPORT(W_RXFilter2), + *(u16*)&RXBuffer[0], *(u16*)&RXBuffer[12], *(u16*)&RXBuffer[8]); + RXTime = 0; + SetStatus(1); + if (TXCurSlot == 0xFFFFFFFF) { - if (addr & 0x2) IncrementRXAddr(addr); - - // copy the RX header - u16 headeraddr = IOPORT(W_RXBufWriteCursor) << 1; - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[0]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[2]; IncrementRXAddr(headeraddr, 4); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[6]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[8]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[10]; - - IOPORT(W_RXBufWriteCursor) = (addr & ~0x3) >> 1; - - SetIRQ(0); - SetStatus(1); - - WIFI_LOG("wifi: finished receiving packet %04X\n", *(u16*)&RXBuffer[12]); - ComStatus &= ~0x1; RXCounter = 0; - - if ((RXBuffer[0] & 0x0F) == 0x0C) - { - u16 clientmask = *(u16*)&RXBuffer[0xC + 26]; - if (IOPORT(W_AIDLow) && (RXBuffer[0xC + 4] & 0x01) && (clientmask & (1 << IOPORT(W_AIDLow)))) - { - SendMPReply(*(u16*)&RXBuffer[0xC + 24], *(u16*)&RXBuffer[0xC + 26]); - } - } } - - if (addr == (IOPORT(W_RXBufReadCursor) << 1)) + // TODO: proper error management + if ((!ComStatus) && (IOPORT(W_PowerState) & 0x0300)) { - printf("wifi: RX buffer full\n"); - RXTime = 0; - SetStatus(1); - if (TXCurSlot == 0xFFFFFFFF) - { - ComStatus &= ~0x1; - RXCounter = 0; - } - // TODO: proper error management + SetStatus(9); } - - IOPORT(W_RXTXAddr) = addr >> 1; } } } - // TODO: make it more accurate, eventually - // in the DS, the wifi system has its own 22MHz clock and doesn't use the system clock - NDS::ScheduleEvent(NDS::Event_Wifi, true, 33, USTimer, 0); + ScheduleTimer(false); } @@ -1157,15 +1805,13 @@ void RFTransfer_Type3() } -// TODO: wifi waitstates - u16 Read(u32 addr) -{//printf("WIFI READ %08X\n", addr); +{ if (addr >= 0x04810000) return 0; addr &= 0x7FFE; - //printf("WIFI: read %08X\n", addr); + if (addr >= 0x4000 && addr < 0x6000) { return *(u16*)&RAM[addr & 0x1FFE]; @@ -1213,7 +1859,6 @@ u16 Read(u32 addr) if (activeread) { u32 rdaddr = IOPORT(W_RXBufReadAddr); - u16 ret = *(u16*)&RAM[rdaddr]; rdaddr += 2; @@ -1243,6 +1888,20 @@ u16 Read(u32 addr) case W_TXBusy: return IOPORT(W_TXBusy) & 0x001F; // no bit for MP replies. odd + + case W_CMDStat0: + case W_CMDStat1: + case W_CMDStat2: + case W_CMDStat3: + case W_CMDStat4: + case W_CMDStat5: + case W_CMDStat6: + case W_CMDStat7: + { + u16 ret = IOPORT(addr&0xFFF); + IOPORT(addr&0xFFF) = 0; + return ret; + } } //printf("WIFI: read %08X\n", addr); @@ -1250,12 +1909,12 @@ u16 Read(u32 addr) } void Write(u32 addr, u16 val) -{//printf("WIFI WRITE %08X %04X\n", addr, val); +{ if (addr >= 0x04810000) return; addr &= 0x7FFE; - //printf("WIFI: write %08X %04X\n", addr, val); + if (addr >= 0x4000 && addr < 0x6000) { *(u16*)&RAM[addr & 0x1FFE] = val; @@ -1290,9 +1949,7 @@ void Write(u32 addr, u16 val) { //printf("mode reset shutdown %08x\n", NDS::ARM7->R[15]); IOPORT(0x27C) = 0x000A; - IOPORT(W_RFPins) = 0x0004; - IOPORT(W_RFStatus) = 9; - IOPORT(W_PowerState) |= 0x200; + PowerDown(); } if (val & 0x2000) @@ -1353,6 +2010,13 @@ void Write(u32 addr, u16 val) printf("wifi: force-setting IF %04X\n", val); return; + case W_AIDLow: + IOPORT(W_AIDLow) = val & 0x000F; + return; + case W_AIDFull: + IOPORT(W_AIDFull) = val & 0x07FF; + return; + case W_PowerState: //printf("writing power state %x %08x\n", val, NDS::ARM7->R[15]); IOPORT(W_PowerState) |= val & 0x0002; @@ -1376,6 +2040,7 @@ void Write(u32 addr, u16 val) return; case W_PowerForce: //if ((val&0x8001)==0x8000) printf("WIFI: forcing power %04X\n", val); + val &= 0x8001; //printf("writing power force %x %08x\n", val, NDS::ARM7->R[15]); if (val == 0x8001) @@ -1384,8 +2049,7 @@ void Write(u32 addr, u16 val) IOPORT(0x034) = 0x0002; IOPORT(W_PowerState) = 0x0200; IOPORT(W_TXReqRead) = 0; - IOPORT(W_RFPins) = 0x0046; - IOPORT(W_RFStatus) = 9; + PowerDown(); } if (val == 1 && IOPORT(W_PowerState) & 0x0002) { @@ -1403,29 +2067,9 @@ void Write(u32 addr, u16 val) } break; case W_PowerUS: - // schedule timer event when the clock is enabled - // TODO: check whether this resets USCOUNT (and also which other events can reset it) - if ((IOPORT(W_PowerUS) & 0x0001) && !(val & 0x0001)) - { - printf("WIFI ON\n"); - NDS::ScheduleEvent(NDS::Event_Wifi, false, 33, USTimer, 0); - if (!MPInited) - { - Platform::MP_Init(); - MPInited = true; - } - if (!LANInited) - { - Platform::LAN_Init(); - LANInited = true; - } - } - else if (!(IOPORT(W_PowerUS) & 0x0001) && (val & 0x0001)) - { - printf("WIFI OFF\n"); - NDS::CancelEvent(NDS::Event_Wifi); - } - break; + IOPORT(W_PowerUS) = val & 0x0003; + UpdatePowerOn(); + return; case W_PowerUnk: val &= 0x0003; //printf("writing power unk %x\n", val); @@ -1486,6 +2130,10 @@ void Write(u32 addr, u16 val) IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); IOPORT(W_TXSlotReply1) = 0; } + if (val & 0x8000) + { + FireTX(); + } val &= 0xFF0E; if (val & 0x7FFF) printf("wifi: unknown RXCNT bits set %04X\n", val); break; @@ -1570,6 +2218,7 @@ void Write(u32 addr, u16 val) case W_TXSlotCmd: // checkme: is it possible to cancel a queued transfer that hasn't started yet // by clearing bit15 here? + // TODO: "W_TXBUF_CMD.Bit15 can be set ONLY while W_CMD_COUNT is non-zero." IOPORT(addr&0xFFF) = val; FireTX(); return; @@ -1597,13 +2246,12 @@ void Write(u32 addr, u16 val) case 0x214: case 0x268: return; - + default: //printf("WIFI unk: write %08X %04X\n", addr, val); break; } - //printf("WIFI: write %08X %04X\n", addr, val); IOPORT(addr&0xFFF) = val; } diff --git a/src/Wifi.h b/src/Wifi.h index 2e156738..b9594f45 100644 --- a/src/Wifi.h +++ b/src/Wifi.h @@ -93,6 +93,7 @@ enum W_CmdTotalTime = 0x0C0, W_CmdReplyTime = 0x0C4, W_RXFilter = 0x0D0, + W_RXLenCrop = 0x0DA, W_RXFilter2 = 0x0E0, W_USCountCnt = 0x0E8, @@ -136,12 +137,43 @@ enum W_TXErrorCount = 0x1C0, W_RXCount = 0x1C4, + W_CMDStat0 = 0x1D0, + W_CMDStat1 = 0x1D2, + W_CMDStat2 = 0x1D4, + W_CMDStat3 = 0x1D6, + W_CMDStat4 = 0x1D8, + W_CMDStat5 = 0x1DA, + W_CMDStat6 = 0x1DC, + W_CMDStat7 = 0x1DE, + W_TXSeqNo = 0x210, W_RFStatus = 0x214, W_IFSet = 0x21C, W_RXTXAddr = 0x268, }; +enum +{ + Event_RXCheck = 0, + Event_IRQ15, + Event_MSTimer, + Event_RFWakeup, + Event_RX, + Event_TX, + Event_MPClientSync, + Event_RF, + Event_BB, + + Event_MAX +}; + +struct SchedEvent +{ + void (*Func)(u32 param); + u64 Timestamp; + u32 Param; +}; + extern bool MPInited; @@ -151,7 +183,7 @@ void DeInit(); void Reset(); void DoSavestate(Savestate* file); -void StartTX_Beacon(); +void SetPowerCnt(u32 val); void USTimer(u32 param); diff --git a/src/WifiAP.cpp b/src/WifiAP.cpp index e083c74d..53516396 100644 --- a/src/WifiAP.cpp +++ b/src/WifiAP.cpp @@ -91,7 +91,7 @@ void DeInit() void Reset() { // random starting point for the counter - USCounter = 0x428888017ULL; + USCounter = 0x428888000ULL; SeqNo = 0x0120; BeaconDue = false; @@ -115,18 +115,6 @@ bool MACIsBroadcast(u8* a) } -void USTimer() -{ - USCounter++; - - u32 chk = (u32)USCounter; - if (!(chk & 0x1FFFF)) - { - // send beacon every 128ms - BeaconDue = true; - } -} - void MSTimer() { USCounter += 0x400; diff --git a/src/WifiAP.h b/src/WifiAP.h index e88132da..e5ca1ed7 100644 --- a/src/WifiAP.h +++ b/src/WifiAP.h @@ -33,7 +33,6 @@ bool Init(); void DeInit(); void Reset(); -void USTimer(); void MSTimer(); // packet format: 12-byte TX header + original 802.11 frame diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp index a9b3ade7..4beefaf3 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp +++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp @@ -67,6 +67,20 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui( bool iswav = (Config::MicInputType == 3); ui->txtMicWavPath->setEnabled(iswav); ui->btnMicWavBrowse->setEnabled(iswav); + + int inst = Platform::InstanceID(); + if (inst > 0) + { + ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); + ui->cbInterpolation->setEnabled(false); + ui->cbBitrate->setEnabled(false); + for (QAbstractButton* btn : grpMicMode->buttons()) + btn->setEnabled(false); + ui->txtMicWavPath->setEnabled(false); + ui->btnMicWavBrowse->setEnabled(false); + } + else + ui->lblInstanceNum->hide(); } AudioSettingsDialog::~AudioSettingsDialog() diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.ui b/src/frontend/qt_sdl/AudioSettingsDialog.ui index d7cfadd6..8fc38d92 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.ui +++ b/src/frontend/qt_sdl/AudioSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 482 - 256 + 301 @@ -23,6 +23,13 @@ QLayout::SetFixedSize + + + + Configuring settings for instance X + + + @@ -76,7 +83,7 @@ - <html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html> + <html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html> diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 5e746984..5c520687 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -14,9 +14,11 @@ set(SOURCES_QT_SDL InputConfig/MapButton.h InputConfig/resources/ds.qrc VideoSettingsDialog.cpp + CameraSettingsDialog.cpp AudioSettingsDialog.cpp FirmwareSettingsDialog.cpp PathSettingsDialog.cpp + MPSettingsDialog.cpp WifiSettingsDialog.cpp InterfaceSettingsDialog.cpp ROMInfoDialog.cpp @@ -25,14 +27,16 @@ set(SOURCES_QT_SDL Input.cpp LAN_PCap.cpp LAN_Socket.cpp + LocalMP.cpp OSD.cpp OSD_shaders.h font.h Platform.cpp QPathInput.h ROMManager.cpp - SaveManager.cpp - + SaveManager.cpp + CameraManager.cpp + ArchiveUtil.h ArchiveUtil.cpp @@ -59,11 +63,11 @@ if (WIN32) endif() if (USE_QT6) - find_package(Qt6 COMPONENTS Core Gui Widgets Network OpenGL OpenGLWidgets REQUIRED) - set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::OpenGL Qt6::OpenGLWidgets) + find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED) + set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets) else() - find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED) - set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network) + find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia REQUIRED) + set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia) endif() set(CMAKE_AUTOMOC ON) @@ -157,6 +161,8 @@ if (PORTABLE) endif() if (APPLE) + target_sources(melonDS PRIVATE sem_timedwait.cpp) + # Copy icon into the bundle set(RESOURCE_FILES "${CMAKE_SOURCE_DIR}/res/melon.icns") target_sources(melonDS PUBLIC "${RESOURCE_FILES}") diff --git a/src/frontend/qt_sdl/CameraManager.cpp b/src/frontend/qt_sdl/CameraManager.cpp new file mode 100644 index 00000000..19cf8d4d --- /dev/null +++ b/src/frontend/qt_sdl/CameraManager.cpp @@ -0,0 +1,612 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "CameraManager.h" +#include "Config.h" + + +#if QT_VERSION >= 0x060000 + +CameraFrameDumper::CameraFrameDumper(QObject* parent) : QVideoSink(parent) +{ + cam = (CameraManager*)parent; + + connect(this, &CameraFrameDumper::videoFrameChanged, this, &CameraFrameDumper::present); +} + +void CameraFrameDumper::present(const QVideoFrame& _frame) +{ + QVideoFrame frame(_frame); + if (!frame.map(QVideoFrame::ReadOnly)) + return; + if (!frame.isReadable()) + { + frame.unmap(); + return; + } + + switch (frame.pixelFormat()) + { + case QVideoFrameFormat::Format_XRGB8888: + case QVideoFrameFormat::Format_YUYV: + cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrameFormat::Format_YUYV); + break; + + case QVideoFrameFormat::Format_UYVY: + cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height()); + break; + + case QVideoFrameFormat::Format_NV12: + cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height()); + break; + } + + frame.unmap(); +} + +#else + +CameraFrameDumper::CameraFrameDumper(QObject* parent) : QAbstractVideoSurface(parent) +{ + cam = (CameraManager*)parent; +} + +bool CameraFrameDumper::present(const QVideoFrame& _frame) +{ + QVideoFrame frame(_frame); + if (!frame.map(QAbstractVideoBuffer::ReadOnly)) + return false; + if (!frame.isReadable()) + { + frame.unmap(); + return false; + } + + switch (frame.pixelFormat()) + { + case QVideoFrame::Format_RGB32: + case QVideoFrame::Format_YUYV: + cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrame::Format_YUYV); + break; + + case QVideoFrame::Format_UYVY: + cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height()); + break; + + case QVideoFrame::Format_NV12: + cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height()); + break; + } + + frame.unmap(); + + return true; +} + +QList CameraFrameDumper::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const +{ + QList ret; + + ret.append(QVideoFrame::Format_RGB32); + ret.append(QVideoFrame::Format_YUYV); + ret.append(QVideoFrame::Format_UYVY); + ret.append(QVideoFrame::Format_NV12); + + return ret; +} + +#endif + + +CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject() +{ + this->num = num; + + startNum = 0; + + // QCamera needs to be controlled from the UI thread, hence this + connect(this, SIGNAL(camStartSignal()), this, SLOT(camStart())); + connect(this, SIGNAL(camStopSignal()), this, SLOT(camStop())); + + frameWidth = width; + frameHeight = height; + frameFormatYUV = yuv; + + int fbsize = frameWidth * frameHeight; + if (yuv) fbsize /= 2; + frameBuffer = new u32[fbsize]; + tempFrameBuffer = new u32[fbsize]; + + inputType = -1; + xFlip = false; + init(); +} + +CameraManager::~CameraManager() +{ + deInit(); + + // save settings here? + + delete[] frameBuffer; +} + +void CameraManager::init() +{ + if (inputType != -1) + deInit(); + + startNum = 0; + + inputType = Config::Camera[num].InputType; + imagePath = QString::fromStdString(Config::Camera[num].ImagePath); + camDeviceName = QString::fromStdString(Config::Camera[num].CamDeviceName); + + camDevice = nullptr; + + { + // fill the framebuffer with black + + int total = frameWidth * frameHeight; + u32 fill = 0; + if (frameFormatYUV) + { + total /= 2; + fill = 0x80008000; + } + + for (int i = 0; i < total; i++) + frameBuffer[i] = fill; + } + + if (inputType == 1) + { + // still image + + QImage img(imagePath); + if (!img.isNull()) + { + QImage imgconv = img.convertToFormat(QImage::Format_RGB32); + if (frameFormatYUV) + { + copyFrame_RGBtoYUV((u32*)img.bits(), img.width(), img.height(), + frameBuffer, frameWidth, frameHeight, + false); + } + else + { + copyFrame_Straight((u32*)img.bits(), img.width(), img.height(), + frameBuffer, frameWidth, frameHeight, + false, false); + } + } + } + else if (inputType == 2) + { + // physical camera + +#if QT_VERSION >= 0x060000 + const QList cameras = QMediaDevices::videoInputs(); + for (const QCameraDevice& cam : cameras) + { + if (QString(cam.id()) == camDeviceName) + { + camDevice = new QCamera(cam); + break; + } + } + + if (camDevice) + { + const QList supported = camDevice->cameraDevice().videoFormats(); + bool good = false; + for (const QCameraFormat& item : supported) + { + if (item.pixelFormat() != QVideoFrameFormat::Format_YUYV && + item.pixelFormat() != QVideoFrameFormat::Format_UYVY && + item.pixelFormat() != QVideoFrameFormat::Format_NV12 && + item.pixelFormat() != QVideoFrameFormat::Format_XRGB8888) + continue; + + if (item.resolution().width() != 640 && item.resolution().height() != 480) + continue; + + camDevice->setCameraFormat(item); + good = true; + break; + } + + if (!good) + { + delete camDevice; + camDevice = nullptr; + } + else + { + camDumper = new CameraFrameDumper(this); + + camSession = new QMediaCaptureSession(this); + camSession->setCamera(camDevice); + camSession->setVideoOutput(camDumper); + } + } +#else + camDevice = new QCamera(camDeviceName.toUtf8()); + if (camDevice->error() != QCamera::NoError) + { + delete camDevice; + camDevice = nullptr; + } + + if (camDevice) + { + camDevice->load(); + + const QList supported = camDevice->supportedViewfinderSettings(); + bool good = false; + for (const QCameraViewfinderSettings& item : supported) + { + if (item.pixelFormat() != QVideoFrame::Format_YUYV && + item.pixelFormat() != QVideoFrame::Format_UYVY && + item.pixelFormat() != QVideoFrame::Format_NV12 && + item.pixelFormat() != QVideoFrame::Format_RGB32) + continue; + + if (item.resolution().width() != 640 && item.resolution().height() != 480) + continue; + + camDevice->setViewfinderSettings(item); + good = true; + break; + } + + camDevice->unload(); + + if (!good) + { + delete camDevice; + camDevice = nullptr; + } + else + { + camDumper = new CameraFrameDumper(this); + camDevice->setViewfinder(camDumper); + } + } +#endif + } +} + +void CameraManager::deInit() +{ + if (inputType == 2) + { + if (camDevice) + { + camDevice->stop(); + delete camDevice; + delete camDumper; +#if QT_VERSION >= 0x060000 + delete camSession; +#endif + } + } + + camDevice = nullptr; + inputType = -1; +} + +void CameraManager::start() +{ + if (startNum == 1) return; + startNum = 1; + + if (inputType == 2) + { + emit camStartSignal(); + } +} + +void CameraManager::stop() +{ + if (startNum == 0) return; + startNum = 0; + + if (inputType == 2) + { + emit camStopSignal(); + } +} + +bool CameraManager::isStarted() +{ + return startNum != 0; +} + +void CameraManager::camStart() +{ + if (camDevice) + camDevice->start(); +} + +void CameraManager::camStop() +{ + if (camDevice) + camDevice->stop(); +} + +void CameraManager::setXFlip(bool flip) +{ + xFlip = flip; +} + +void CameraManager::captureFrame(u32* frame, int width, int height, bool yuv) +{ + frameMutex.lock(); + + if ((width == frameWidth) && + (height == frameHeight) && + (yuv == frameFormatYUV) && + (!xFlip)) + { + int len = width * height; + if (yuv) len /= 2; + memcpy(frame, frameBuffer, len * sizeof(u32)); + } + else + { + if (yuv == frameFormatYUV) + { + copyFrame_Straight(frameBuffer, frameWidth, frameHeight, + frame, width, height, + xFlip, yuv); + } + else if (yuv) + { + copyFrame_RGBtoYUV(frameBuffer, frameWidth, frameHeight, + frame, width, height, + xFlip); + } + else + { + copyFrame_YUVtoRGB(frameBuffer, frameWidth, frameHeight, + frame, width, height, + xFlip); + } + } + + frameMutex.unlock(); +} + +void CameraManager::feedFrame(u32* frame, int width, int height, bool yuv) +{ + frameMutex.lock(); + + if (width == frameWidth && height == frameHeight && yuv == frameFormatYUV) + { + int len = width * height; + if (yuv) len /= 2; + memcpy(frameBuffer, frame, len * sizeof(u32)); + } + else + { + if (yuv == frameFormatYUV) + { + copyFrame_Straight(frame, width, height, + frameBuffer, frameWidth, frameHeight, + false, yuv); + } + else if (yuv) + { + copyFrame_RGBtoYUV(frame, width, height, + frameBuffer, frameWidth, frameHeight, + false); + } + else + { + copyFrame_YUVtoRGB(frame, width, height, + frameBuffer, frameWidth, frameHeight, + false); + } + } + + frameMutex.unlock(); +} + +void CameraManager::feedFrame_UYVY(u32* frame, int width, int height) +{ + for (int y = 0; y < frameHeight; y++) + { + int sy = (y * height) / frameHeight; + + for (int x = 0; x < frameWidth; x+=2) + { + int sx = (x * width) / frameWidth; + + u32 val = frame[((sy*width) + sx) >> 1]; + + val = ((val & 0xFF00FF00) >> 8) | ((val & 0x00FF00FF) << 8); + + tempFrameBuffer[((y*frameWidth) + x) >> 1] = val; + } + } + + feedFrame(tempFrameBuffer, frameWidth, frameHeight, true); +} + +void CameraManager::feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height) +{ + for (int y = 0; y < frameHeight; y++) + { + int sy = (y * height) / frameHeight; + + for (int x = 0; x < frameWidth; x+=2) + { + int sx1 = (x * width) / frameWidth; + int sx2 = ((x+1) * width) / frameWidth; + + u32 val; + + u8 y1 = planeY[(sy*width) + sx1]; + u8 y2 = planeY[(sy*width) + sx2]; + + int uvpos = (((sy>>1)*(width>>1)) + (sx1>>1)); + u8 u = planeUV[uvpos << 1]; + u8 v = planeUV[(uvpos << 1) + 1]; + + val = y1 | (u << 8) | (y2 << 16) | (v << 24); + tempFrameBuffer[((y*frameWidth) + x) >> 1] = val; + } + } + + feedFrame(tempFrameBuffer, frameWidth, frameHeight, true); +} + +void CameraManager::copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv) +{ + if (yuv) + { + swidth /= 2; + dwidth /= 2; + } + + for (int dy = 0; dy < dheight; dy++) + { + int sy = (dy * sheight) / dheight; + + for (int dx = 0; dx < dwidth; dx++) + { + int sx = (dx * swidth) / dwidth; + if (xflip) sx = swidth-1 - sx; + + u32 val = src[(sy * swidth) + sx]; + + if (yuv) + { + if (xflip) + val = (val & 0xFF00FF00) | + ((val >> 16) & 0xFF) | + ((val & 0xFF) << 16); + } + else + val |= 0xFF000000; + + dst[(dy * dwidth) + dx] = val; + } + } +} + +void CameraManager::copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip) +{ + for (int dy = 0; dy < dheight; dy++) + { + int sy = (dy * sheight) / dheight; + + for (int dx = 0; dx < dwidth; dx+=2) + { + int sx; + + sx = (dx * swidth) / dwidth; + if (xflip) sx = swidth-1 - sx; + + u32 pixel1 = src[sy*swidth + sx]; + + sx = ((dx+1) * swidth) / dwidth; + if (xflip) sx = swidth-1 - sx; + + u32 pixel2 = src[sy*swidth + sx]; + + int r1 = (pixel1 >> 16) & 0xFF; + int g1 = (pixel1 >> 8) & 0xFF; + int b1 = pixel1 & 0xFF; + + int r2 = (pixel2 >> 16) & 0xFF; + int g2 = (pixel2 >> 8) & 0xFF; + int b2 = pixel2 & 0xFF; + + int y1 = ((r1 * 19595) + (g1 * 38470) + (b1 * 7471)) >> 16; + int u1 = ((b1 - y1) * 32244) >> 16; + int v1 = ((r1 - y1) * 57475) >> 16; + + int y2 = ((r2 * 19595) + (g2 * 38470) + (b2 * 7471)) >> 16; + int u2 = ((b2 - y2) * 32244) >> 16; + int v2 = ((r2 - y2) * 57475) >> 16; + + u1 += 128; v1 += 128; + u2 += 128; v2 += 128; + + y1 = std::clamp(y1, 0, 255); u1 = std::clamp(u1, 0, 255); v1 = std::clamp(v1, 0, 255); + y2 = std::clamp(y2, 0, 255); u2 = std::clamp(u2, 0, 255); v2 = std::clamp(v2, 0, 255); + + // huh + u1 = (u1 + u2) >> 1; + v1 = (v1 + v2) >> 1; + + dst[(dy*dwidth + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24); + } + } +} + +void CameraManager::copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip) +{ + for (int dy = 0; dy < dheight; dy++) + { + int sy = (dy * sheight) / dheight; + + for (int dx = 0; dx < dwidth; dx+=2) + { + int sx = (dx * swidth) / dwidth; + if (xflip) sx = swidth-2 - sx; + + u32 val = src[(sy*swidth + sx) / 2]; + + int y1, y2; + if (xflip) + { + y1 = (val >> 16) & 0xFF; + y2 = val & 0xFF; + } + else + { + y1 = val & 0xFF; + y2 = (val >> 16) & 0xFF; + } + int u = (val >> 8) & 0xFF; + int v = (val >> 24) & 0xFF; + + u -= 128; v -= 128; + + int r1 = y1 + ((v * 91881) >> 16); + int g1 = y1 - ((v * 46793) >> 16) - ((u * 22544) >> 16); + int b1 = y1 + ((u * 116129) >> 16); + + int r2 = y2 + ((v * 91881) >> 16); + int g2 = y2 - ((v * 46793) >> 16) - ((u * 22544) >> 16); + int b2 = y2 + ((u * 116129) >> 16); + + r1 = std::clamp(r1, 0, 255); g1 = std::clamp(g1, 0, 255); b1 = std::clamp(b1, 0, 255); + r2 = std::clamp(r2, 0, 255); g2 = std::clamp(g2, 0, 255); b2 = std::clamp(b2, 0, 255); + + u32 col1 = 0xFF000000 | (r1 << 16) | (g1 << 8) | b1; + u32 col2 = 0xFF000000 | (r2 << 16) | (g2 << 8) | b2; + + dst[dy*dwidth + dx ] = col1; + dst[dy*dwidth + dx+1] = col2; + } + } +} diff --git a/src/frontend/qt_sdl/CameraManager.h b/src/frontend/qt_sdl/CameraManager.h new file mode 100644 index 00000000..6743d19e --- /dev/null +++ b/src/frontend/qt_sdl/CameraManager.h @@ -0,0 +1,134 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef CAMERAMANAGER_H +#define CAMERAMANAGER_H + +#include +#if QT_VERSION >= 0x060000 + #include + #include + #include + #include +#else + #include + #include + #include +#endif +#include + +#include "types.h" + +class CameraManager; + + +#if QT_VERSION >= 0x060000 + +class CameraFrameDumper : public QVideoSink +{ + Q_OBJECT + +public: + CameraFrameDumper(QObject* parent = nullptr); + +public slots: + void present(const QVideoFrame& frame); + +private: + CameraManager* cam; +}; + +#else + +class CameraFrameDumper : public QAbstractVideoSurface +{ + Q_OBJECT + +public: + CameraFrameDumper(QObject* parent = nullptr); + + bool present(const QVideoFrame& frame) override; + QList supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const override; + +private: + CameraManager* cam; +}; + +#endif + + +class CameraManager : public QObject +{ + Q_OBJECT + +public: + CameraManager(int num, int width, int height, bool yuv); + ~CameraManager(); + + void init(); + void deInit(); + + void start(); + void stop(); + bool isStarted(); + + void setXFlip(bool flip); + + void captureFrame(u32* frame, int width, int height, bool yuv); + + void feedFrame(u32* frame, int width, int height, bool yuv); + void feedFrame_UYVY(u32* frame, int width, int height); + void feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height); + +signals: + void camStartSignal(); + void camStopSignal(); + +private slots: + void camStart(); + void camStop(); + +private: + int num; + + int startNum; + + int inputType; + QString imagePath; + QString camDeviceName; + + QCamera* camDevice; + CameraFrameDumper* camDumper; +#if QT_VERSION >= 0x060000 + QMediaCaptureSession* camSession; +#endif + + int frameWidth, frameHeight; + bool frameFormatYUV; + u32* frameBuffer; + u32* tempFrameBuffer; + QMutex frameMutex; + + bool xFlip; + + void copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv); + void copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip); + void copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip); +}; + +#endif // CAMERAMANAGER_H diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.cpp b/src/frontend/qt_sdl/CameraSettingsDialog.cpp new file mode 100644 index 00000000..1844e0fd --- /dev/null +++ b/src/frontend/qt_sdl/CameraSettingsDialog.cpp @@ -0,0 +1,304 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include +#include +#include + +#include "types.h" + +#include "CameraSettingsDialog.h" +#include "ui_CameraSettingsDialog.h" + + +CameraSettingsDialog* CameraSettingsDialog::currentDlg = nullptr; + +extern std::string EmuDirectory; + +extern CameraManager* camManager[2]; + + +CameraPreviewPanel::CameraPreviewPanel(QWidget* parent) : QWidget(parent) +{ + currentCam = nullptr; + updateTimer = startTimer(50); +} + +CameraPreviewPanel::~CameraPreviewPanel() +{ + killTimer(updateTimer); +} + +void CameraPreviewPanel::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + + if (!currentCam) + { + painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); + return; + } + + QImage picture(256, 192, QImage::Format_RGB32); + currentCam->captureFrame((u32*)picture.bits(), 256, 192, false); + painter.drawImage(0, 0, picture); +} + + +CameraSettingsDialog::CameraSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CameraSettingsDialog) +{ + previewPanel = nullptr; + currentCfg = nullptr; + currentCam = nullptr; + + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + for (int i = 0; i < 2; i++) + { + oldCamSettings[i] = Config::Camera[i]; + } + + ui->cbCameraSel->addItem("DSi outer camera"); + ui->cbCameraSel->addItem("DSi inner camera"); + +#if QT_VERSION >= 0x060000 + const QList cameras = QMediaDevices::videoInputs(); + for (const QCameraDevice &cameraInfo : cameras) + { + QString name = cameraInfo.description(); + QCameraDevice::Position pos = cameraInfo.position(); + if (pos != QCameraDevice::UnspecifiedPosition) + { + name += " ("; + if (pos == QCameraDevice::FrontFace) + name += "inner camera"; + else if (pos == QCameraDevice::BackFace) + name += "outer camera"; + name += ")"; + } + + ui->cbPhysicalCamera->addItem(name, QString(cameraInfo.id())); + } +#else + const QList cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo &cameraInfo : cameras) + { + QString name = cameraInfo.description(); + QCamera::Position pos = cameraInfo.position(); + if (pos != QCamera::UnspecifiedPosition) + { + name += " ("; + if (pos == QCamera::FrontFace) + name += "inner camera"; + else if (pos == QCamera::BackFace) + name += "outer camera"; + name += ")"; + } + + ui->cbPhysicalCamera->addItem(name, cameraInfo.deviceName()); + } +#endif + ui->rbPictureCamera->setEnabled(ui->cbPhysicalCamera->count() > 0); + + grpInputType = new QButtonGroup(this); + grpInputType->addButton(ui->rbPictureNone, 0); + grpInputType->addButton(ui->rbPictureImg, 1); + grpInputType->addButton(ui->rbPictureCamera, 2); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + connect(grpInputType, SIGNAL(buttonClicked(int)), this, SLOT(onChangeInputType(int))); +#else + connect(grpInputType, SIGNAL(idClicked(int)), this, SLOT(onChangeInputType(int))); +#endif + + previewPanel = new CameraPreviewPanel(this); + QVBoxLayout* previewLayout = new QVBoxLayout(); + previewLayout->addWidget(previewPanel); + ui->grpPreview->setLayout(previewLayout); + previewPanel->setMinimumSize(256, 192); + previewPanel->setMaximumSize(256, 192); + + on_cbCameraSel_currentIndexChanged(ui->cbCameraSel->currentIndex()); +} + +CameraSettingsDialog::~CameraSettingsDialog() +{ + delete ui; +} + +void CameraSettingsDialog::on_CameraSettingsDialog_accepted() +{ + for (int i = 0; i < 2; i++) + { + camManager[i]->stop(); + } + + Config::Save(); + + closeDlg(); +} + +void CameraSettingsDialog::on_CameraSettingsDialog_rejected() +{ + for (int i = 0; i < 2; i++) + { + camManager[i]->stop(); + camManager[i]->deInit(); + Config::Camera[i] = oldCamSettings[i]; + camManager[i]->init(); + } + + closeDlg(); +} + +void CameraSettingsDialog::on_cbCameraSel_currentIndexChanged(int id) +{ + if (!previewPanel) return; + + if (currentCam) + { + currentCam->stop(); + } + + currentId = id; + currentCfg = &Config::Camera[id]; + //currentCam = camManager[id]; + currentCam = nullptr; + populateCamControls(id); + currentCam = camManager[id]; + previewPanel->setCurrentCam(currentCam); + + currentCam->start(); +} + +void CameraSettingsDialog::onChangeInputType(int type) +{ + if (!currentCfg) return; + + if (currentCam) + { + currentCam->stop(); + currentCam->deInit(); + } + + currentCfg->InputType = type; + + ui->txtSrcImagePath->setEnabled(type == 1); + ui->btnSrcImageBrowse->setEnabled(type == 1); + ui->cbPhysicalCamera->setEnabled((type == 2) && (ui->cbPhysicalCamera->count()>0)); + + currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + + if (ui->cbPhysicalCamera->count() > 0) + currentCfg->CamDeviceName = ui->cbPhysicalCamera->currentData().toString().toStdString(); + + if (currentCam) + { + currentCam->init(); + currentCam->start(); + } +} + +void CameraSettingsDialog::on_txtSrcImagePath_textChanged() +{ + if (!currentCfg) return; + + if (currentCam) + { + currentCam->stop(); + currentCam->deInit(); + } + + currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + + if (currentCam) + { + currentCam->init(); + currentCam->start(); + } +} + +void CameraSettingsDialog::on_btnSrcImageBrowse_clicked() +{ + QString file = QFileDialog::getOpenFileName(this, + "Select image file...", + QString::fromStdString(EmuDirectory), + "Image files (*.png *.jpg *.jpeg *.bmp);;Any file (*.*)"); + + if (file.isEmpty()) return; + + ui->txtSrcImagePath->setText(file); +} + +void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id) +{ + if (!currentCfg) return; + + if (currentCam) + { + currentCam->stop(); + currentCam->deInit(); + } + + currentCfg->CamDeviceName = ui->cbPhysicalCamera->itemData(id).toString().toStdString(); + + if (currentCam) + { + currentCam->init(); + currentCam->start(); + } +} + +void CameraSettingsDialog::populateCamControls(int id) +{ + Config::CameraConfig& cfg = Config::Camera[id]; + + int type = cfg.InputType; + if (type < 0 || type >= grpInputType->buttons().count()) type = 0; + grpInputType->button(type)->setChecked(true); + + ui->txtSrcImagePath->setText(QString::fromStdString(cfg.ImagePath)); + + bool deviceset = false; + QString device = QString::fromStdString(cfg.CamDeviceName); + for (int i = 0; i < ui->cbPhysicalCamera->count(); i++) + { + QString itemdev = ui->cbPhysicalCamera->itemData(i).toString(); + if (itemdev == device) + { + ui->cbPhysicalCamera->setCurrentIndex(i); + deviceset = true; + break; + } + } + if (!deviceset) + ui->cbPhysicalCamera->setCurrentIndex(0); + + onChangeInputType(type); + + ui->chkFlipPicture->setChecked(cfg.XFlip); +} + +void CameraSettingsDialog::on_chkFlipPicture_clicked() +{ + if (!currentCfg) return; + + currentCfg->XFlip = ui->chkFlipPicture->isChecked(); + if (currentCam) currentCam->setXFlip(currentCfg->XFlip); +} diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.h b/src/frontend/qt_sdl/CameraSettingsDialog.h new file mode 100644 index 00000000..8572ac42 --- /dev/null +++ b/src/frontend/qt_sdl/CameraSettingsDialog.h @@ -0,0 +1,108 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef CAMERASETTINGSDIALOG_H +#define CAMERASETTINGSDIALOG_H + +#include +#include + +#include "Config.h" +#include "CameraManager.h" + +namespace Ui { class CameraSettingsDialog; } +class CameraSettingsDialog; + +class CameraPreviewPanel : public QWidget +{ + Q_OBJECT + +public: + CameraPreviewPanel(QWidget* parent); + ~CameraPreviewPanel(); + + void setCurrentCam(CameraManager* cam) + { + currentCam = cam; + } + +protected: + void paintEvent(QPaintEvent* event) override; + void timerEvent(QTimerEvent* event) override + { + repaint(); + } + +private: + int updateTimer; + CameraManager* currentCam; +}; + +class CameraSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CameraSettingsDialog(QWidget* parent); + ~CameraSettingsDialog(); + + static CameraSettingsDialog* currentDlg; + static CameraSettingsDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new CameraSettingsDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + +private slots: + void on_CameraSettingsDialog_accepted(); + void on_CameraSettingsDialog_rejected(); + + void on_cbCameraSel_currentIndexChanged(int id); + void onChangeInputType(int type); + void on_txtSrcImagePath_textChanged(); + void on_btnSrcImageBrowse_clicked(); + void on_cbPhysicalCamera_currentIndexChanged(int id); + void on_chkFlipPicture_clicked(); + +private: + Ui::CameraSettingsDialog* ui; + + QButtonGroup* grpInputType; + CameraPreviewPanel* previewPanel; + + int currentId; + Config::CameraConfig* currentCfg; + CameraManager* currentCam; + + Config::CameraConfig oldCamSettings[2]; + + void populateCamControls(int id); +}; + +#endif // CAMERASETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.ui b/src/frontend/qt_sdl/CameraSettingsDialog.ui new file mode 100644 index 00000000..bbaf45b9 --- /dev/null +++ b/src/frontend/qt_sdl/CameraSettingsDialog.ui @@ -0,0 +1,170 @@ + + + CameraSettingsDialog + + + + 0 + 0 + 605 + 341 + + + + + 0 + 0 + + + + Camera settings - melonDS + + + + QLayout::SetFixedSize + + + + + + + Configure emulated camera: + + + + + + + + + + + + + + Picture source + + + + + + + + + None (blank) + + + + + + + Browse... + + + + + + + Physical camera: + + + + + + + Image file: + + + + + + + + + + + + + Picture settings + + + + + + Flip horizontally + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Preview + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CameraSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CameraSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 7a0ec698..8b2f3d49 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -106,9 +106,10 @@ int FirmwareBirthdayDay; int FirmwareFavouriteColour; std::string FirmwareMessage; std::string FirmwareMAC; -bool RandomizeMAC; -bool SocketBindAnyAddr; +int MPAudioMode; +int MPRecvTimeout; + std::string LANDevice; bool DirectLAN; @@ -139,202 +140,211 @@ bool DSBatteryLevelOkay; int DSiBatteryLevel; bool DSiBatteryCharging; +CameraConfig Camera[2]; + const char* kConfigFile = "melonDS.ini"; +const char* kUniqueConfigFile = "melonDS.%d.ini"; ConfigEntry ConfigFile[] = { - {"Key_A", 0, &KeyMapping[0], -1}, - {"Key_B", 0, &KeyMapping[1], -1}, - {"Key_Select", 0, &KeyMapping[2], -1}, - {"Key_Start", 0, &KeyMapping[3], -1}, - {"Key_Right", 0, &KeyMapping[4], -1}, - {"Key_Left", 0, &KeyMapping[5], -1}, - {"Key_Up", 0, &KeyMapping[6], -1}, - {"Key_Down", 0, &KeyMapping[7], -1}, - {"Key_R", 0, &KeyMapping[8], -1}, - {"Key_L", 0, &KeyMapping[9], -1}, - {"Key_X", 0, &KeyMapping[10], -1}, - {"Key_Y", 0, &KeyMapping[11], -1}, + {"Key_A", 0, &KeyMapping[0], -1, true}, + {"Key_B", 0, &KeyMapping[1], -1, true}, + {"Key_Select", 0, &KeyMapping[2], -1, true}, + {"Key_Start", 0, &KeyMapping[3], -1, true}, + {"Key_Right", 0, &KeyMapping[4], -1, true}, + {"Key_Left", 0, &KeyMapping[5], -1, true}, + {"Key_Up", 0, &KeyMapping[6], -1, true}, + {"Key_Down", 0, &KeyMapping[7], -1, true}, + {"Key_R", 0, &KeyMapping[8], -1, true}, + {"Key_L", 0, &KeyMapping[9], -1, true}, + {"Key_X", 0, &KeyMapping[10], -1, true}, + {"Key_Y", 0, &KeyMapping[11], -1, true}, - {"Joy_A", 0, &JoyMapping[0], -1}, - {"Joy_B", 0, &JoyMapping[1], -1}, - {"Joy_Select", 0, &JoyMapping[2], -1}, - {"Joy_Start", 0, &JoyMapping[3], -1}, - {"Joy_Right", 0, &JoyMapping[4], -1}, - {"Joy_Left", 0, &JoyMapping[5], -1}, - {"Joy_Up", 0, &JoyMapping[6], -1}, - {"Joy_Down", 0, &JoyMapping[7], -1}, - {"Joy_R", 0, &JoyMapping[8], -1}, - {"Joy_L", 0, &JoyMapping[9], -1}, - {"Joy_X", 0, &JoyMapping[10], -1}, - {"Joy_Y", 0, &JoyMapping[11], -1}, + {"Joy_A", 0, &JoyMapping[0], -1, true}, + {"Joy_B", 0, &JoyMapping[1], -1, true}, + {"Joy_Select", 0, &JoyMapping[2], -1, true}, + {"Joy_Start", 0, &JoyMapping[3], -1, true}, + {"Joy_Right", 0, &JoyMapping[4], -1, true}, + {"Joy_Left", 0, &JoyMapping[5], -1, true}, + {"Joy_Up", 0, &JoyMapping[6], -1, true}, + {"Joy_Down", 0, &JoyMapping[7], -1, true}, + {"Joy_R", 0, &JoyMapping[8], -1, true}, + {"Joy_L", 0, &JoyMapping[9], -1, true}, + {"Joy_X", 0, &JoyMapping[10], -1, true}, + {"Joy_Y", 0, &JoyMapping[11], -1, true}, - {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1}, - {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1}, - {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1}, - {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1}, - {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1}, - {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1}, - {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1}, - {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1}, - {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1}, - {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1}, - {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1}, + {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1, true}, + {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1, true}, + {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, true}, + {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, true}, + {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1, true}, + {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, true}, + {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1, true}, + {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1, true}, + {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1, true}, + {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1, true}, + {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1, true}, - {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1}, - {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1}, - {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1}, - {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1}, - {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1}, - {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1}, - {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1}, - {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1}, - {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1}, - {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1}, - {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1}, + {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, true}, + {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, true}, + {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, true}, + {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, true}, + {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, true}, + {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, true}, + {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1, true}, + {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1, true}, + {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, true}, + {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, true}, + {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1, true}, - {"JoystickID", 0, &JoystickID, 0}, + {"JoystickID", 0, &JoystickID, 0, true}, - {"WindowWidth", 0, &WindowWidth, 256}, - {"WindowHeight", 0, &WindowHeight, 384}, - {"WindowMax", 1, &WindowMaximized, false}, + {"WindowWidth", 0, &WindowWidth, 256, true}, + {"WindowHeight", 0, &WindowHeight, 384, true}, + {"WindowMax", 1, &WindowMaximized, false, true}, - {"ScreenRotation", 0, &ScreenRotation, 0}, - {"ScreenGap", 0, &ScreenGap, 0}, - {"ScreenLayout", 0, &ScreenLayout, 0}, - {"ScreenSwap", 1, &ScreenSwap, false}, - {"ScreenSizing", 0, &ScreenSizing, 0}, - {"IntegerScaling", 1, &IntegerScaling, false}, - {"ScreenAspectTop",0, &ScreenAspectTop,0}, - {"ScreenAspectBot",0, &ScreenAspectBot,0}, - {"ScreenFilter", 1, &ScreenFilter, true}, + {"ScreenRotation", 0, &ScreenRotation, 0, true}, + {"ScreenGap", 0, &ScreenGap, 0, true}, + {"ScreenLayout", 0, &ScreenLayout, 0, true}, + {"ScreenSwap", 1, &ScreenSwap, false, true}, + {"ScreenSizing", 0, &ScreenSizing, 0, true}, + {"IntegerScaling", 1, &IntegerScaling, false, true}, + {"ScreenAspectTop",0, &ScreenAspectTop,0, true}, + {"ScreenAspectBot",0, &ScreenAspectBot,0, true}, + {"ScreenFilter", 1, &ScreenFilter, true, true}, - {"ScreenUseGL", 1, &ScreenUseGL, false}, - {"ScreenVSync", 1, &ScreenVSync, false}, - {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1}, + {"ScreenUseGL", 1, &ScreenUseGL, false, false}, + {"ScreenVSync", 1, &ScreenVSync, false, false}, + {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, false}, - {"3DRenderer", 0, &_3DRenderer, 0}, - {"Threaded3D", 1, &Threaded3D, true}, + {"3DRenderer", 0, &_3DRenderer, 0, false}, + {"Threaded3D", 1, &Threaded3D, true, false}, - {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1}, - {"GL_BetterPolygons", 1, &GL_BetterPolygons, false}, + {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, false}, + {"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false}, - {"LimitFPS", 1, &LimitFPS, true}, + {"LimitFPS", 1, &LimitFPS, true, false}, {"AudioSync", 1, &AudioSync, false}, - {"ShowOSD", 1, &ShowOSD, true}, + {"ShowOSD", 1, &ShowOSD, true, false}, - {"ConsoleType", 0, &ConsoleType, 0}, - {"DirectBoot", 1, &DirectBoot, true}, + {"ConsoleType", 0, &ConsoleType, 0, false}, + {"DirectBoot", 1, &DirectBoot, true, false}, #ifdef JIT_ENABLED - {"JIT_Enable", 1, &JIT_Enable, false}, - {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32}, - {"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true}, - {"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true}, + {"JIT_Enable", 1, &JIT_Enable, false, false}, + {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32, false}, + {"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true, false}, + {"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true, false}, #ifdef __APPLE__ - {"JIT_FastMemory", 1, &JIT_FastMemory, false}, + {"JIT_FastMemory", 1, &JIT_FastMemory, false, false}, #else - {"JIT_FastMemory", 1, &JIT_FastMemory, true}, + {"JIT_FastMemory", 1, &JIT_FastMemory, true, false}, #endif #endif - {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false}, + {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false, false}, - {"BIOS9Path", 2, &BIOS9Path, (std::string)""}, - {"BIOS7Path", 2, &BIOS7Path, (std::string)""}, - {"FirmwarePath", 2, &FirmwarePath, (std::string)""}, + {"BIOS9Path", 2, &BIOS9Path, (std::string)"", false}, + {"BIOS7Path", 2, &BIOS7Path, (std::string)"", false}, + {"FirmwarePath", 2, &FirmwarePath, (std::string)"", false}, - {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)""}, - {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)""}, - {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)""}, - {"DSiNANDPath", 2, &DSiNANDPath, (std::string)""}, + {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)"", false}, + {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)"", false}, + {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)"", false}, + {"DSiNANDPath", 2, &DSiNANDPath, (std::string)"", false}, - {"DLDIEnable", 1, &DLDIEnable, false}, - {"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin"}, - {"DLDISize", 0, &DLDISize, 0}, - {"DLDIReadOnly", 1, &DLDIReadOnly, false}, - {"DLDIFolderSync", 1, &DLDIFolderSync, false}, - {"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)""}, + {"DLDIEnable", 1, &DLDIEnable, false, false}, + {"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin", false}, + {"DLDISize", 0, &DLDISize, 0, false}, + {"DLDIReadOnly", 1, &DLDIReadOnly, false, false}, + {"DLDIFolderSync", 1, &DLDIFolderSync, false, false}, + {"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)"", false}, - {"DSiSDEnable", 1, &DSiSDEnable, false}, - {"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin"}, - {"DSiSDSize", 0, &DSiSDSize, 0}, - {"DSiSDReadOnly", 1, &DSiSDReadOnly, false}, - {"DSiSDFolderSync", 1, &DSiSDFolderSync, false}, - {"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)""}, + {"DSiSDEnable", 1, &DSiSDEnable, false, false}, + {"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin", false}, + {"DSiSDSize", 0, &DSiSDSize, 0, false}, + {"DSiSDReadOnly", 1, &DSiSDReadOnly, false, false}, + {"DSiSDFolderSync", 1, &DSiSDFolderSync, false, false}, + {"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)"", false}, - {"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false}, - {"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS"}, - {"FirmwareLanguage", 0, &FirmwareLanguage, 1}, - {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1}, - {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1}, - {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0}, - {"FirmwareMessage", 2, &FirmwareMessage, (std::string)""}, - {"FirmwareMAC", 2, &FirmwareMAC, (std::string)""}, - {"RandomizeMAC", 1, &RandomizeMAC, false}, + {"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false, true}, + {"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS", true}, + {"FirmwareLanguage", 0, &FirmwareLanguage, 1, true}, + {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1, true}, + {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1, true}, + {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0, true}, + {"FirmwareMessage", 2, &FirmwareMessage, (std::string)"", true}, + {"FirmwareMAC", 2, &FirmwareMAC, (std::string)"", true}, - {"SockBindAnyAddr", 1, &SocketBindAnyAddr, false}, - {"LANDevice", 2, &LANDevice, (std::string)""}, - {"DirectLAN", 1, &DirectLAN, false}, + {"MPAudioMode", 0, &MPAudioMode, 1, false}, + {"MPRecvTimeout", 0, &MPRecvTimeout, 25, false}, - {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false}, + {"LANDevice", 2, &LANDevice, (std::string)"", false}, + {"DirectLAN", 1, &DirectLAN, false, false}, - {"AudioInterp", 0, &AudioInterp, 0}, - {"AudioBitrate", 0, &AudioBitrate, 0}, - {"AudioVolume", 0, &AudioVolume, 256}, - {"MicInputType", 0, &MicInputType, 1}, - {"MicWavPath", 2, &MicWavPath, (std::string)""}, + {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false, false}, - {"LastROMFolder", 2, &LastROMFolder, (std::string)""}, + {"AudioInterp", 0, &AudioInterp, 0, false}, + {"AudioBitrate", 0, &AudioBitrate, 0, false}, + {"AudioVolume", 0, &AudioVolume, 256, true}, + {"MicInputType", 0, &MicInputType, 1, false}, + {"MicWavPath", 2, &MicWavPath, (std::string)"", false}, - {"RecentROM_0", 2, &RecentROMList[0], (std::string)""}, - {"RecentROM_1", 2, &RecentROMList[1], (std::string)""}, - {"RecentROM_2", 2, &RecentROMList[2], (std::string)""}, - {"RecentROM_3", 2, &RecentROMList[3], (std::string)""}, - {"RecentROM_4", 2, &RecentROMList[4], (std::string)""}, - {"RecentROM_5", 2, &RecentROMList[5], (std::string)""}, - {"RecentROM_6", 2, &RecentROMList[6], (std::string)""}, - {"RecentROM_7", 2, &RecentROMList[7], (std::string)""}, - {"RecentROM_8", 2, &RecentROMList[8], (std::string)""}, - {"RecentROM_9", 2, &RecentROMList[9], (std::string)""}, + {"LastROMFolder", 2, &LastROMFolder, (std::string)"", true}, - {"SaveFilePath", 2, &SaveFilePath, (std::string)""}, - {"SavestatePath", 2, &SavestatePath, (std::string)""}, - {"CheatFilePath", 2, &CheatFilePath, (std::string)""}, + {"RecentROM_0", 2, &RecentROMList[0], (std::string)"", true}, + {"RecentROM_1", 2, &RecentROMList[1], (std::string)"", true}, + {"RecentROM_2", 2, &RecentROMList[2], (std::string)"", true}, + {"RecentROM_3", 2, &RecentROMList[3], (std::string)"", true}, + {"RecentROM_4", 2, &RecentROMList[4], (std::string)"", true}, + {"RecentROM_5", 2, &RecentROMList[5], (std::string)"", true}, + {"RecentROM_6", 2, &RecentROMList[6], (std::string)"", true}, + {"RecentROM_7", 2, &RecentROMList[7], (std::string)"", true}, + {"RecentROM_8", 2, &RecentROMList[8], (std::string)"", true}, + {"RecentROM_9", 2, &RecentROMList[9], (std::string)"", true}, - {"EnableCheats", 1, &EnableCheats, false}, + {"SaveFilePath", 2, &SaveFilePath, (std::string)"", true}, + {"SavestatePath", 2, &SavestatePath, (std::string)"", true}, + {"CheatFilePath", 2, &CheatFilePath, (std::string)"", true}, - {"MouseHide", 1, &MouseHide, false}, - {"MouseHideSeconds", 0, &MouseHideSeconds, 5}, - {"PauseLostFocus", 1, &PauseLostFocus, false}, + {"EnableCheats", 1, &EnableCheats, false, true}, - {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true}, - {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF}, - {"DSiBatteryCharging", 1, &DSiBatteryCharging, true}, + {"MouseHide", 1, &MouseHide, false, false}, + {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false}, + {"PauseLostFocus", 1, &PauseLostFocus, false, false}, - {"", -1, nullptr, 0} + {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true}, + {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true}, + {"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true}, + + // TODO!! + // we need a more elegant way to deal with this + {"Camera0_InputType", 0, &Camera[0].InputType, 0, false}, + {"Camera0_ImagePath", 2, &Camera[0].ImagePath, (std::string)"", false}, + {"Camera0_CamDeviceName", 2, &Camera[0].CamDeviceName, (std::string)"", false}, + {"Camera0_XFlip", 1, &Camera[0].XFlip, false, false}, + {"Camera1_InputType", 0, &Camera[1].InputType, 0, false}, + {"Camera1_ImagePath", 2, &Camera[1].ImagePath, (std::string)"", false}, + {"Camera1_CamDeviceName", 2, &Camera[1].CamDeviceName, (std::string)"", false}, + {"Camera1_XFlip", 1, &Camera[1].XFlip, false, false}, + + {"", -1, nullptr, 0, false} }; -void Load() +void LoadFile(int inst) { - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + FILE* f; + if (inst > 0) { - if (!entry->Value) break; - - switch (entry->Type) - { - case 0: *(int*)entry->Value = std::get(entry->Default); break; - case 1: *(bool*)entry->Value = std::get(entry->Default); break; - case 2: *(std::string*)entry->Value = std::get(entry->Default); break; - } - - entry++; + char name[100] = {0}; + snprintf(name, 99, kUniqueConfigFile, inst+1); + f = Platform::OpenLocalFile(name, "r"); } + else + f = Platform::OpenLocalFile(kConfigFile, "r"); - FILE* f = Platform::OpenLocalFile(kConfigFile, "r"); if (!f) return; char linebuf[1024]; @@ -349,13 +359,13 @@ void Load() entryname[31] = '\0'; if (ret < 2) continue; - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) { - if (!entry->Value) break; - if (!strncmp(entry->Name, entryname, 32)) { + if ((inst > 0) && (!entry->InstanceUnique)) + break; + switch (entry->Type) { case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break; @@ -365,23 +375,52 @@ void Load() break; } - - entry++; } } fclose(f); } +void Load() +{ + + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) + { + switch (entry->Type) + { + case 0: *(int*)entry->Value = std::get(entry->Default); break; + case 1: *(bool*)entry->Value = std::get(entry->Default); break; + case 2: *(std::string*)entry->Value = std::get(entry->Default); break; + } + } + + LoadFile(0); + + int inst = Platform::InstanceID(); + if (inst > 0) + LoadFile(inst); +} + void Save() { - FILE* f = Platform::OpenLocalFile(kConfigFile, "w"); + int inst = Platform::InstanceID(); + + FILE* f; + if (inst > 0) + { + char name[100] = {0}; + snprintf(name, 99, kUniqueConfigFile, inst+1); + f = Platform::OpenLocalFile(name, "w"); + } + else + f = Platform::OpenLocalFile(kConfigFile, "w"); + if (!f) return; - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) { - if (!entry->Value) break; + if ((inst > 0) && (!entry->InstanceUnique)) + continue; switch (entry->Type) { @@ -389,8 +428,6 @@ void Save() case 1: fprintf(f, "%s=%d\r\n", entry->Name, *(bool*)entry->Value ? 1:0); break; case 2: fprintf(f, "%s=%s\r\n", entry->Name, (*(std::string*)entry->Value).c_str()); break; } - - entry++; } fclose(f); diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index f2f8ddcd..6ccae5f4 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -58,6 +58,15 @@ struct ConfigEntry int Type; // 0=int 1=bool 2=string void* Value; // pointer to the value variable std::variant Default; + bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer +}; + +struct CameraConfig +{ + int InputType; // 0=blank 1=image 2=camera + std::string ImagePath; + std::string CamDeviceName; + bool XFlip; }; @@ -141,9 +150,10 @@ extern int FirmwareBirthdayDay; extern int FirmwareFavouriteColour; extern std::string FirmwareMessage; extern std::string FirmwareMAC; -extern bool RandomizeMAC; -extern bool SocketBindAnyAddr; +extern int MPAudioMode; +extern int MPRecvTimeout; + extern std::string LANDevice; extern bool DirectLAN; @@ -173,6 +183,8 @@ extern bool DSBatteryLevelOkay; extern int DSiBatteryLevel; extern bool DSiBatteryCharging; +extern CameraConfig Camera[2]; + void Load(); void Save(); diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp index 754cc8a5..ffca5676 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp @@ -18,6 +18,7 @@ #include +#include "Platform.h" #include "Config.h" #include "FirmwareSettingsDialog.h" @@ -64,10 +65,14 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent ui->overrideFirmwareBox->setChecked(Config::FirmwareOverrideSettings); ui->txtMAC->setText(QString::fromStdString(Config::FirmwareMAC)); - ui->cbRandomizeMAC->setChecked(Config::RandomizeMAC); on_overrideFirmwareBox_toggled(); - on_cbRandomizeMAC_toggled(); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } FirmwareSettingsDialog::~FirmwareSettingsDialog() @@ -135,7 +140,6 @@ void FirmwareSettingsDialog::done(int r) std::string newMessage = ui->messageEdit->text().toStdString(); std::string newMAC = ui->txtMAC->text().toStdString(); - bool newRandomizeMAC = ui->cbRandomizeMAC->isChecked(); if ( newOverride != Config::FirmwareOverrideSettings || newName != Config::FirmwareUsername @@ -144,8 +148,7 @@ void FirmwareSettingsDialog::done(int r) || newBirthdayDay != Config::FirmwareBirthdayDay || newBirthdayMonth != Config::FirmwareBirthdayMonth || newMessage != Config::FirmwareMessage - || newMAC != Config::FirmwareMAC - || newRandomizeMAC != Config::RandomizeMAC) + || newMAC != Config::FirmwareMAC) { if (RunningSomething && QMessageBox::warning(this, "Reset necessary to apply changes", @@ -163,7 +166,6 @@ void FirmwareSettingsDialog::done(int r) Config::FirmwareMessage = newMessage; Config::FirmwareMAC = newMAC; - Config::RandomizeMAC = newRandomizeMAC; Config::Save(); @@ -210,9 +212,3 @@ void FirmwareSettingsDialog::on_overrideFirmwareBox_toggled() ui->grpUserSettings->setDisabled(disable); ui->grpWifiSettings->setDisabled(disable); } - -void FirmwareSettingsDialog::on_cbRandomizeMAC_toggled() -{ - bool disable = ui->cbRandomizeMAC->isChecked(); - ui->txtMAC->setDisabled(disable); -} diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.h b/src/frontend/qt_sdl/FirmwareSettingsDialog.h index 97bf5c07..b3695e2f 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.h +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.h @@ -124,7 +124,6 @@ private slots: void on_cbxBirthdayMonth_currentIndexChanged(int idx); void on_overrideFirmwareBox_toggled(); - void on_cbRandomizeMAC_toggled(); private: bool verifyMAC(); diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui index a97689cf..37146296 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 511 - 342 + 357 @@ -23,6 +23,13 @@ QLayout::SetFixedSize + + + + Configuring settings for instance X + + + @@ -144,9 +151,9 @@ - + - Randomize + (leave empty to use default MAC) diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp index 697e983a..92a01867 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp @@ -24,6 +24,7 @@ #include #include "types.h" +#include "Platform.h" #include "Config.h" #include "MapButton.h" @@ -123,6 +124,12 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new } setupKeypadPage(); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring mappings for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } InputConfigDialog::~InputConfigDialog() diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui index 15cb683d..0db61b15 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui @@ -7,7 +7,7 @@ 0 0 770 - 719 + 678 @@ -20,7 +20,7 @@ QLayout::SetFixedSize - + Qt::Horizontal @@ -30,49 +30,7 @@ - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Joystick: - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html> - - - - - - + 0 @@ -167,7 +125,7 @@ - 76 + 70 0 @@ -258,7 +216,7 @@ - 76 + 70 0 @@ -384,7 +342,7 @@ - 76 + 70 0 @@ -464,7 +422,7 @@ - 76 + 70 0 @@ -523,7 +481,7 @@ - 76 + 70 0 @@ -615,7 +573,7 @@ - 76 + 70 0 @@ -698,7 +656,7 @@ - 76 + 70 0 @@ -757,7 +715,7 @@ - 76 + 70 0 @@ -882,7 +840,7 @@ - 76 + 70 0 @@ -962,7 +920,7 @@ - 76 + 70 0 @@ -1021,7 +979,7 @@ - 76 + 70 0 @@ -1113,7 +1071,7 @@ - 76 + 70 0 @@ -1289,7 +1247,7 @@ - 76 + 70 0 @@ -1441,7 +1399,7 @@ - 76 + 70 0 @@ -1521,7 +1479,7 @@ - 76 + 70 0 @@ -1580,7 +1538,7 @@ - 76 + 70 0 @@ -1672,7 +1630,7 @@ - 76 + 70 0 @@ -1814,7 +1772,7 @@ - 76 + 70 0 @@ -1894,7 +1852,7 @@ - 76 + 70 0 @@ -1953,7 +1911,7 @@ - 76 + 70 0 @@ -2045,7 +2003,7 @@ - 76 + 70 0 @@ -2128,7 +2086,7 @@ - 76 + 70 0 @@ -2187,7 +2145,7 @@ - 76 + 70 0 @@ -2251,7 +2209,7 @@ - 76 + 70 0 @@ -2321,6 +2279,55 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Joystick: + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html> + + + + + + + + + Configuring mappings for instance X + + + diff --git a/src/frontend/qt_sdl/LAN_PCap.cpp b/src/frontend/qt_sdl/LAN_PCap.cpp index 57223eb8..86c218f1 100644 --- a/src/frontend/qt_sdl/LAN_PCap.cpp +++ b/src/frontend/qt_sdl/LAN_PCap.cpp @@ -114,6 +114,12 @@ bool TryLoadPCap(void* lib) bool Init(bool open_adapter) { + PCapAdapter = NULL; + PacketLen = 0; + RXNum = 0; + + NumAdapters = 0; + // TODO: how to deal with cases where an adapter is unplugged or changes config?? if (!PCapLib) { @@ -142,12 +148,6 @@ bool Init(bool open_adapter) } } - PCapAdapter = NULL; - PacketLen = 0; - RXNum = 0; - - NumAdapters = 0; - char errbuf[PCAP_ERRBUF_SIZE]; int ret; diff --git a/src/frontend/qt_sdl/LocalMP.cpp b/src/frontend/qt_sdl/LocalMP.cpp new file mode 100644 index 00000000..fb7ef7ad --- /dev/null +++ b/src/frontend/qt_sdl/LocalMP.cpp @@ -0,0 +1,634 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include +#include + +#ifdef __WIN32__ + #include +#else + #include + #include + #include + #ifdef __APPLE__ + #include "sem_timedwait.h" + #endif +#endif + +#include +#include + +#include "Config.h" +#include "LocalMP.h" + + +namespace LocalMP +{ + +u32 MPUniqueID; +u8 PacketBuffer[2048]; + +struct MPQueueHeader +{ + u16 NumInstances; + u16 InstanceBitmask; // bitmask of all instances present + u16 ConnectedBitmask; // bitmask of which instances are ready to send/receive packets + u32 PacketWriteOffset; + u32 ReplyWriteOffset; + u16 MPHostInstanceID; // instance ID from which the last CMD frame was sent + u16 MPReplyBitmask; // bitmask of which clients replied in time +}; + +struct MPPacketHeader +{ + u32 Magic; + u32 SenderID; + u32 Type; // 0=regular 1=CMD 2=reply 3=ack + u32 Length; + u64 Timestamp; +}; + +struct MPSync +{ + u32 Magic; + u32 SenderID; + u16 ClientMask; + u16 Type; + u64 Timestamp; +}; + +QSharedMemory* MPQueue; +int InstanceID; +u32 PacketReadOffset; +u32 ReplyReadOffset; + +const u32 kQueueSize = 0x20000; +const u32 kMaxFrameSize = 0x800; +const u32 kPacketStart = sizeof(MPQueueHeader); +const u32 kReplyStart = kQueueSize / 2; +const u32 kPacketEnd = kReplyStart; +const u32 kReplyEnd = kQueueSize; + +int RecvTimeout; + +int LastHostID; + + +// we need to come up with our own abstraction layer for named semaphores +// because QSystemSemaphore doesn't support waiting with a timeout +// and, as such, is unsuitable to our needs + +#ifdef __WIN32__ + +bool SemInited[32]; +HANDLE SemPool[32]; + +void SemPoolInit() +{ + for (int i = 0; i < 32; i++) + { + SemPool[i] = INVALID_HANDLE_VALUE; + SemInited[i] = false; + } +} + +void SemDeinit(int num); + +void SemPoolDeinit() +{ + for (int i = 0; i < 32; i++) + SemDeinit(i); +} + +bool SemInit(int num) +{ + if (SemInited[num]) + return true; + + char semname[64]; + sprintf(semname, "Local\\melonNIFI_Sem%02d", num); + + HANDLE sem = CreateSemaphore(nullptr, 0, 64, semname); + SemPool[num] = sem; + SemInited[num] = true; + return sem != INVALID_HANDLE_VALUE; +} + +void SemDeinit(int num) +{ + if (SemPool[num] != INVALID_HANDLE_VALUE) + { + CloseHandle(SemPool[num]); + SemPool[num] = INVALID_HANDLE_VALUE; + } + + SemInited[num] = false; +} + +bool SemPost(int num) +{ + SemInit(num); + return ReleaseSemaphore(SemPool[num], 1, nullptr) != 0; +} + +bool SemWait(int num, int timeout) +{ + return WaitForSingleObject(SemPool[num], timeout) == WAIT_OBJECT_0; +} + +void SemReset(int num) +{ + while (WaitForSingleObject(SemPool[num], 0) == WAIT_OBJECT_0); +} + +#else + +bool SemInited[32]; +sem_t* SemPool[32]; + +void SemPoolInit() +{ + for (int i = 0; i < 32; i++) + { + SemPool[i] = SEM_FAILED; + SemInited[i] = false; + } +} + +void SemDeinit(int num); + +void SemPoolDeinit() +{ + for (int i = 0; i < 32; i++) + SemDeinit(i); +} + +bool SemInit(int num) +{ + if (SemInited[num]) + return true; + + char semname[64]; + sprintf(semname, "/melonNIFI_Sem%02d", num); + + sem_t* sem = sem_open(semname, O_CREAT, 0644, 0); + SemPool[num] = sem; + SemInited[num] = true; + return sem != SEM_FAILED; +} + +void SemDeinit(int num) +{ + if (SemPool[num] != SEM_FAILED) + { + sem_close(SemPool[num]); + SemPool[num] = SEM_FAILED; + } + + SemInited[num] = false; +} + +bool SemPost(int num) +{ + SemInit(num); + return sem_post(SemPool[num]) == 0; +} + +bool SemWait(int num, int timeout) +{ + if (!timeout) + return sem_trywait(SemPool[num]) == 0; + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += timeout * 1000000; + long sec = ts.tv_nsec / 1000000000; + ts.tv_nsec -= sec * 1000000000; + ts.tv_sec += sec; + + return sem_timedwait(SemPool[num], &ts) == 0; +} + +void SemReset(int num) +{ + while (sem_trywait(SemPool[num]) == 0); +} + +#endif + + +bool Init() +{ + MPQueue = new QSharedMemory("melonNIFI"); + + if (!MPQueue->attach()) + { + printf("MP sharedmem doesn't exist. creating\n"); + if (!MPQueue->create(kQueueSize)) + { + printf("MP sharedmem create failed :(\n"); + return false; + } + + MPQueue->lock(); + memset(MPQueue->data(), 0, MPQueue->size()); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + header->PacketWriteOffset = kPacketStart; + header->ReplyWriteOffset = kReplyStart; + MPQueue->unlock(); + } + + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + + u16 mask = header->InstanceBitmask; + for (int i = 0; i < 16; i++) + { + if (!(mask & (1<InstanceBitmask |= (1<ConnectedBitmask |= (1 << i); + break; + } + } + header->NumInstances++; + + PacketReadOffset = header->PacketWriteOffset; + ReplyReadOffset = header->ReplyWriteOffset; + + MPQueue->unlock(); + + // prepare semaphores + // semaphores 0-15: regular frames; semaphore I is posted when instance I needs to process a new frame + // semaphores 16-31: MP replies; semaphore I is posted when instance I needs to process a new MP reply + + SemPoolInit(); + SemInit(InstanceID); + SemInit(16+InstanceID); + + LastHostID = -1; + + printf("MP comm init OK, instance ID %d\n", InstanceID); + + RecvTimeout = 25; + + return true; +} + +void DeInit() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + header->ConnectedBitmask &= ~(1 << InstanceID); + header->InstanceBitmask &= ~(1 << InstanceID); + header->NumInstances--; + MPQueue->unlock(); + + SemPoolDeinit(); + + MPQueue->detach(); + delete MPQueue; +} + +void SetRecvTimeout(int timeout) +{ + RecvTimeout = timeout; +} + +void Begin() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + PacketReadOffset = header->PacketWriteOffset; + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(InstanceID); + SemReset(16+InstanceID); + header->ConnectedBitmask |= (1 << InstanceID); + MPQueue->unlock(); +} + +void End() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + //SemReset(InstanceID); + //SemReset(16+InstanceID); + header->ConnectedBitmask &= ~(1 << InstanceID); + MPQueue->unlock(); +} + +void FIFORead(int fifo, void* buf, int len) +{ + u8* data = (u8*)MPQueue->data(); + + u32 offset, start, end; + if (fifo == 0) + { + offset = PacketReadOffset; + start = kPacketStart; + end = kPacketEnd; + } + else + { + offset = ReplyReadOffset; + start = kReplyStart; + end = kReplyEnd; + } + + if ((offset + len) >= end) + { + u32 part1 = end - offset; + memcpy(buf, &data[offset], part1); + memcpy(&((u8*)buf)[part1], &data[start], len - part1); + offset = start + len - part1; + } + else + { + memcpy(buf, &data[offset], len); + offset += len; + } + + if (fifo == 0) PacketReadOffset = offset; + else ReplyReadOffset = offset; +} + +void FIFOWrite(int fifo, void* buf, int len) +{ + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + u32 offset, start, end; + if (fifo == 0) + { + offset = header->PacketWriteOffset; + start = kPacketStart; + end = kPacketEnd; + } + else + { + offset = header->ReplyWriteOffset; + start = kReplyStart; + end = kReplyEnd; + } + + if ((offset + len) >= end) + { + u32 part1 = end - offset; + memcpy(&data[offset], buf, part1); + memcpy(&data[start], &((u8*)buf)[part1], len - part1); + offset = start + len - part1; + } + else + { + memcpy(&data[offset], buf, len); + offset += len; + } + + if (fifo == 0) header->PacketWriteOffset = offset; + else header->ReplyWriteOffset = offset; +} + +int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) +{ + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + u16 mask = header->ConnectedBitmask; + + // TODO: check if the FIFO is full! + + MPPacketHeader pktheader; + pktheader.Magic = 0x4946494E; + pktheader.SenderID = InstanceID; + pktheader.Type = type; + pktheader.Length = len; + pktheader.Timestamp = timestamp; + + type &= 0xFFFF; + int nfifo = (type == 2) ? 1 : 0; + FIFOWrite(nfifo, &pktheader, sizeof(pktheader)); + if (len) + FIFOWrite(nfifo, packet, len); + + if (type == 1) + { + // NOTE: this is not guarded against, say, multiple multiplay games happening on the same machine + // we would need to pass the packet's SenderID through the wifi module for that + header->MPHostInstanceID = InstanceID; + header->MPReplyBitmask = 0; + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(16 + InstanceID); + } + else if (type == 2) + { + header->MPReplyBitmask |= (1 << InstanceID); + } + + MPQueue->unlock(); + + if (type == 2) + { + SemPost(16 + header->MPHostInstanceID); + } + else + { + for (int i = 0; i < 16; i++) + { + if (mask & (1<lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + MPPacketHeader pktheader; + FIFORead(0, &pktheader, sizeof(pktheader)); + + if (pktheader.Magic != 0x4946494E) + { + printf("PACKET FIFO OVERFLOW\n"); + PacketReadOffset = header->PacketWriteOffset; + SemReset(InstanceID); + MPQueue->unlock(); + return 0; + } + + if (pktheader.SenderID == InstanceID) + { + // skip this packet + PacketReadOffset += pktheader.Length; + if (PacketReadOffset >= kPacketEnd) + PacketReadOffset += kPacketStart - kPacketEnd; + + MPQueue->unlock(); + continue; + } + + if (pktheader.Length) + { + FIFORead(0, packet, pktheader.Length); + + if (pktheader.Type == 1) + LastHostID = pktheader.SenderID; + } + + if (timestamp) *timestamp = pktheader.Timestamp; + MPQueue->unlock(); + return pktheader.Length; + } +} + +int SendPacket(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(0, packet, len, timestamp); +} + +int RecvPacket(u8* packet, u64* timestamp) +{ + return RecvPacketGeneric(packet, false, timestamp); +} + + +int SendCmd(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(1, packet, len, timestamp); +} + +int SendReply(u8* packet, int len, u64 timestamp, u16 aid) +{ + return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp); +} + +int SendAck(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(3, packet, len, timestamp); +} + +int RecvHostPacket(u8* packet, u64* timestamp) +{ + if (LastHostID != -1) + { + // check if the host is still connected + + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + u16 curinstmask = header->ConnectedBitmask; + MPQueue->unlock(); + + if (!(curinstmask & (1 << LastHostID))) + return -1; + } + + return RecvPacketGeneric(packet, true, timestamp); +} + +u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask) +{ + u16 ret = 0; + u16 myinstmask = (1 << InstanceID); + u16 curinstmask; + + { + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + curinstmask = header->ConnectedBitmask; + MPQueue->unlock(); + } + + // if all clients have left: return early + if ((myinstmask & curinstmask) == curinstmask) + return 0; + + for (;;) + { + if (!SemWait(16+InstanceID, RecvTimeout)) + { + // no more replies available + return ret; + } + + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + MPPacketHeader pktheader; + FIFORead(1, &pktheader, sizeof(pktheader)); + + if (pktheader.Magic != 0x4946494E) + { + printf("REPLY FIFO OVERFLOW\n"); + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(16+InstanceID); + MPQueue->unlock(); + return 0; + } + + if ((pktheader.SenderID == InstanceID) || // packet we sent out (shouldn't happen, but hey) + (pktheader.Timestamp < (timestamp - 32))) // stale packet + { + // skip this packet + ReplyReadOffset += pktheader.Length; + if (ReplyReadOffset >= kReplyEnd) + ReplyReadOffset += kReplyStart - kReplyEnd; + + MPQueue->unlock(); + continue; + } + + if (pktheader.Length) + { + u32 aid = (pktheader.Type >> 16); + FIFORead(1, &packets[(aid-1)*1024], pktheader.Length); + ret |= (1 << aid); + } + + myinstmask |= (1 << pktheader.SenderID); + if (((myinstmask & curinstmask) == curinstmask) || + ((ret & aidmask) == aidmask)) + { + // all the clients have sent their reply + + MPQueue->unlock(); + return ret; + } + + MPQueue->unlock(); + } +} + +} + diff --git a/src/frontend/qt_sdl/LocalMP.h b/src/frontend/qt_sdl/LocalMP.h new file mode 100644 index 00000000..51dfcb93 --- /dev/null +++ b/src/frontend/qt_sdl/LocalMP.h @@ -0,0 +1,45 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef LOCALMP_H +#define LOCALMP_H + +#include "types.h" + +namespace LocalMP +{ + +bool Init(); +void DeInit(); + +void SetRecvTimeout(int timeout); + +void Begin(); +void End(); + +int SendPacket(u8* data, int len, u64 timestamp); +int RecvPacket(u8* data, u64* timestamp); +int SendCmd(u8* data, int len, u64 timestamp); +int SendReply(u8* data, int len, u64 timestamp, u16 aid); +int SendAck(u8* data, int len, u64 timestamp); +int RecvHostPacket(u8* data, u64* timestamp); +u16 RecvReplies(u8* data, u64 timestamp, u16 aidmask); + +} + +#endif // LOCALMP_H diff --git a/src/frontend/qt_sdl/MPSettingsDialog.cpp b/src/frontend/qt_sdl/MPSettingsDialog.cpp new file mode 100644 index 00000000..e3114220 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.cpp @@ -0,0 +1,73 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include + +#include "types.h" +#include "Platform.h" +#include "Config.h" + +#include "LAN_Socket.h" +#include "LAN_PCap.h" +#include "Wifi.h" + +#include "MPSettingsDialog.h" +#include "ui_MPSettingsDialog.h" + + +MPSettingsDialog* MPSettingsDialog::currentDlg = nullptr; + +extern bool RunningSomething; + + +MPSettingsDialog::MPSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MPSettingsDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + grpAudioMode = new QButtonGroup(this); + grpAudioMode->addButton(ui->rbAudioAll, 0); + grpAudioMode->addButton(ui->rbAudioOneOnly, 1); + grpAudioMode->addButton(ui->rbAudioActiveOnly, 2); + grpAudioMode->button(Config::MPAudioMode)->setChecked(true); + + ui->sbReceiveTimeout->setValue(Config::MPRecvTimeout); +} + +MPSettingsDialog::~MPSettingsDialog() +{ + delete ui; +} + +void MPSettingsDialog::done(int r) +{ + if (r == QDialog::Accepted) + { + Config::MPAudioMode = grpAudioMode->checkedId(); + Config::MPRecvTimeout = ui->sbReceiveTimeout->value(); + + Config::Save(); + } + + QDialog::done(r); + + closeDlg(); +} + +// diff --git a/src/frontend/qt_sdl/MPSettingsDialog.h b/src/frontend/qt_sdl/MPSettingsDialog.h new file mode 100644 index 00000000..fe917e89 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.h @@ -0,0 +1,65 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef MPSETTINGSDIALOG_H +#define MPSETTINGSDIALOG_H + +#include +#include + +namespace Ui { class MPSettingsDialog; } +class MPSettingsDialog; + +class MPSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MPSettingsDialog(QWidget* parent); + ~MPSettingsDialog(); + + static MPSettingsDialog* currentDlg; + static MPSettingsDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new MPSettingsDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + +private slots: + void done(int r); + + // + +private: + Ui::MPSettingsDialog* ui; + + QButtonGroup* grpAudioMode; +}; + +#endif // MPSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/MPSettingsDialog.ui b/src/frontend/qt_sdl/MPSettingsDialog.ui new file mode 100644 index 00000000..bce0fc94 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.ui @@ -0,0 +1,142 @@ + + + MPSettingsDialog + + + + 0 + 0 + 466 + 202 + + + + Multiplayer settings - melonDS + + + + + + Audio output + + + + + + Instance 1 only + + + + + + + All instances + + + + + + + Active instance only + + + + + + + + + + Network + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + 1000 + + + + + + + Data reception timeout: + + + + + + + + 1 + 0 + + + + milliseconds + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MPSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MPSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index 7fa517d3..286032e9 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -22,6 +22,7 @@ #include "types.h" #include "Config.h" +#include "Platform.h" #include "PathSettingsDialog.h" #include "ui_PathSettingsDialog.h" @@ -43,6 +44,12 @@ PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->txtSaveFilePath->setText(QString::fromStdString(Config::SaveFilePath)); ui->txtSavestatePath->setText(QString::fromStdString(Config::SavestatePath)); ui->txtCheatFilePath->setText(QString::fromStdString(Config::CheatFilePath)); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring paths for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } PathSettingsDialog::~PathSettingsDialog() diff --git a/src/frontend/qt_sdl/PathSettingsDialog.ui b/src/frontend/qt_sdl/PathSettingsDialog.ui index 95f5acc9..295b1c44 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.ui +++ b/src/frontend/qt_sdl/PathSettingsDialog.ui @@ -7,49 +7,63 @@ 0 0 439 - 166 + 185 Path settings - melonDS - - - - true + + + + Cheat files path: - + Browse... - + Savestates path: + + + + + + + + + + true + + + + + + + Browse... + + + + true - - - - Leave a path blank to use the current ROM's path. - - - - + Qt::Horizontal @@ -59,35 +73,14 @@ - - - - Cheat files path: - - - - - - - Browse... - - - - + Browse... - - - - true - - - - + Save files path: @@ -95,9 +88,23 @@ - + - + Leave a path blank to use the current ROM's path. + + + + + + + true + + + + + + + Configuring paths for instance X diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 4306c988..f9eaf429 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -20,28 +20,7 @@ #include #include -#ifdef __WIN32__ - #define NTDDI_VERSION 0x06000000 // GROSS FUCKING HACK - #include - #include - //#include // FUCK THAT SHIT - #include - #include - #include - #define dup _dup - #define socket_t SOCKET - #define sockaddr_t SOCKADDR -#else - #include - #include - #include - #include - - #define socket_t int - #define sockaddr_t struct sockaddr - #define closesocket close -#endif - +#include #include #include #include @@ -49,32 +28,83 @@ #include #include #include +#include #include "Platform.h" #include "Config.h" #include "ROMManager.h" +#include "CameraManager.h" #include "LAN_Socket.h" #include "LAN_PCap.h" -#include - -#ifndef INVALID_SOCKET - #define INVALID_SOCKET (socket_t)-1 -#endif +#include "LocalMP.h" std::string EmuDirectory; +extern CameraManager* camManager[2]; + void emuStop(); namespace Platform { -socket_t MPSocket; -sockaddr_t MPSendAddr; -u8 PacketBuffer[2048]; +QSharedMemory* IPCBuffer = nullptr; +int IPCInstanceID; -#define NIFI_VER 1 +void IPCInit() +{ + IPCInstanceID = 0; + + IPCBuffer = new QSharedMemory("melonIPC"); + + if (!IPCBuffer->attach()) + { + printf("IPC sharedmem doesn't exist. creating\n"); + if (!IPCBuffer->create(1024)) + { + printf("IPC sharedmem create failed :(\n"); + delete IPCBuffer; + IPCBuffer = nullptr; + return; + } + + IPCBuffer->lock(); + memset(IPCBuffer->data(), 0, IPCBuffer->size()); + IPCBuffer->unlock(); + } + + IPCBuffer->lock(); + u8* data = (u8*)IPCBuffer->data(); + u16 mask = *(u16*)&data[0]; + for (int i = 0; i < 16; i++) + { + if (!(mask & (1<unlock(); + + printf("IPC: instance ID %d\n", IPCInstanceID); +} + +void IPCDeInit() +{ + if (IPCBuffer) + { + IPCBuffer->lock(); + u8* data = (u8*)IPCBuffer->data(); + *(u16*)&data[0] &= ~(1<unlock(); + + IPCBuffer->detach(); + delete IPCBuffer; + } + IPCBuffer = nullptr; +} void Init(int argc, char** argv) @@ -110,10 +140,13 @@ void Init(int argc, char** argv) confdir = config.absolutePath() + "/melonDS/"; EmuDirectory = confdir.toStdString(); #endif + + IPCInit(); } void DeInit() { + IPCDeInit(); } @@ -123,6 +156,22 @@ void StopEmu() } +int InstanceID() +{ + return IPCInstanceID; +} + +std::string InstanceFileSuffix() +{ + int inst = IPCInstanceID; + if (inst == 0) return ""; + + char suffix[16] = {0}; + snprintf(suffix, 15, ".%d", inst+1); + return suffix; +} + + int GetConfigInt(ConfigEntry entry) { const int imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; @@ -169,7 +218,6 @@ bool GetConfigBool(ConfigEntry entry) case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0; case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0; - case Firm_RandomizeMAC: return Config::RandomizeMAC != 0; case Firm_OverrideSettings: return Config::FirmwareOverrideSettings != 0; } @@ -372,6 +420,11 @@ bool Mutex_TryLock(Mutex* mutex) return ((QMutex*) mutex)->try_lock(); } +void Sleep(u64 usecs) +{ + QThread::usleep(usecs); +} + void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) { @@ -386,149 +439,61 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen } + bool MP_Init() { - int opt_true = 1; - int res; - -#ifdef __WIN32__ - WSADATA wsadata; - if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) - { - return false; - } -#endif // __WIN32__ - - MPSocket = socket(AF_INET, SOCK_DGRAM, 0); - if (MPSocket < 0) - { - return false; - } - - res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - -#if defined(BSD) || defined(__APPLE__) - res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } -#endif - - sockaddr_t saddr; - saddr.sa_family = AF_INET; - *(u32*)&saddr.sa_data[2] = htonl(Config::SocketBindAnyAddr ? INADDR_ANY : INADDR_LOOPBACK); - *(u16*)&saddr.sa_data[0] = htons(7064); - res = bind(MPSocket, &saddr, sizeof(sockaddr_t)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - - res = setsockopt(MPSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - - MPSendAddr.sa_family = AF_INET; - *(u32*)&MPSendAddr.sa_data[2] = htonl(INADDR_BROADCAST); - *(u16*)&MPSendAddr.sa_data[0] = htons(7064); - - return true; + return LocalMP::Init(); } void MP_DeInit() { - if (MPSocket >= 0) - closesocket(MPSocket); - -#ifdef __WIN32__ - WSACleanup(); -#endif // __WIN32__ + return LocalMP::DeInit(); } -int MP_SendPacket(u8* data, int len) +void MP_Begin() { - if (MPSocket < 0) - return 0; - - if (len > 2048-8) - { - printf("MP_SendPacket: error: packet too long (%d)\n", len); - return 0; - } - - *(u32*)&PacketBuffer[0] = htonl(0x4946494E); // NIFI - PacketBuffer[4] = NIFI_VER; - PacketBuffer[5] = 0; - *(u16*)&PacketBuffer[6] = htons(len); - memcpy(&PacketBuffer[8], data, len); - - int slen = sendto(MPSocket, (const char*)PacketBuffer, len+8, 0, &MPSendAddr, sizeof(sockaddr_t)); - if (slen < 8) return 0; - return slen - 8; + return LocalMP::Begin(); } -int MP_RecvPacket(u8* data, bool block) +void MP_End() { - if (MPSocket < 0) - return 0; - - fd_set fd; - struct timeval tv; - - FD_ZERO(&fd); - FD_SET(MPSocket, &fd); - tv.tv_sec = 0; - tv.tv_usec = block ? 5000 : 0; - - if (!select(MPSocket+1, &fd, 0, 0, &tv)) - { - return 0; - } - - sockaddr_t fromAddr; - socklen_t fromLen = sizeof(sockaddr_t); - int rlen = recvfrom(MPSocket, (char*)PacketBuffer, 2048, 0, &fromAddr, &fromLen); - if (rlen < 8+24) - { - return 0; - } - rlen -= 8; - - if (ntohl(*(u32*)&PacketBuffer[0]) != 0x4946494E) - { - return 0; - } - - if (PacketBuffer[4] != NIFI_VER) - { - return 0; - } - - if (ntohs(*(u16*)&PacketBuffer[6]) != rlen) - { - return 0; - } - - memcpy(data, &PacketBuffer[8], rlen); - return rlen; + return LocalMP::End(); } +int MP_SendPacket(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendPacket(data, len, timestamp); +} +int MP_RecvPacket(u8* data, u64* timestamp) +{ + return LocalMP::RecvPacket(data, timestamp); +} + +int MP_SendCmd(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendCmd(data, len, timestamp); +} + +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid) +{ + return LocalMP::SendReply(data, len, timestamp, aid); +} + +int MP_SendAck(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendAck(data, len, timestamp); +} + +int MP_RecvHostPacket(u8* data, u64* timestamp) +{ + return LocalMP::RecvHostPacket(data, timestamp); +} + +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask) +{ + return LocalMP::RecvReplies(data, timestamp, aidmask); +} bool LAN_Init() { @@ -573,9 +538,20 @@ int LAN_RecvPacket(u8* data) return LAN_Socket::RecvPacket(data); } -void Sleep(u64 usecs) + +void Camera_Start(int num) { - QThread::usleep(usecs); + return camManager[num]->start(); +} + +void Camera_Stop(int num) +{ + return camManager[num]->stop(); +} + +void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv) +{ + return camManager[num]->captureFrame(frame, width, height, yuv); } } diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp index 499c176a..89f74e5f 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp @@ -23,6 +23,7 @@ #include "DSi_I2C.h" #include "NDS.h" #include "Config.h" +#include "Platform.h" #include "types.h" @@ -65,6 +66,12 @@ PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent), } ui->sliderDSiBatteryLevel->setValue(dsiBatterySliderPos); + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Setting battery levels for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); + inited = true; } diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui index e0e7c6e8..77af2254 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui @@ -7,7 +7,7 @@ 0 0 562 - 279 + 288 @@ -23,37 +23,7 @@ QLayout::SetFixedSize - - - - DS Battery - - - - - - Low - - - - - - - Battery Level - - - - - - - Okay - - - - - - - + Qt::Horizontal @@ -63,7 +33,7 @@ - + DSi Battery @@ -219,6 +189,49 @@ + + + + DS Battery + + + + + + Low + + + + + + + Battery Level + + + + + + + Okay + + + + + + + + + + + 0 + 0 + + + + Configuring settings for instance X + + + diff --git a/src/frontend/qt_sdl/ROMInfoDialog.cpp b/src/frontend/qt_sdl/ROMInfoDialog.cpp index 0a71dc8b..e82ec4be 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.cpp +++ b/src/frontend/qt_sdl/ROMInfoDialog.cpp @@ -75,12 +75,12 @@ ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMI ui->iconTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle)); - ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle, 128)); - ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle, 128)); - ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle, 128)); - ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle, 128)); - ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle, 128)); - ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle, 128)); + ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle)); + ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle)); + ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle)); + ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle)); + ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle)); + ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle)); if (NDSCart::Banner.Version > 1) ui->chineseTitle->setText(QString::fromUtf16(NDSCart::Banner.ChineseTitle)); diff --git a/src/frontend/qt_sdl/ROMInfoDialog.ui b/src/frontend/qt_sdl/ROMInfoDialog.ui index 0c65cab3..1c9d844b 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.ui +++ b/src/frontend/qt_sdl/ROMInfoDialog.ui @@ -6,8 +6,8 @@ 0 0 - 427 - 434 + 559 + 532 @@ -22,12 +22,6 @@ - - - 0 - 0 - - Titles @@ -350,12 +344,6 @@ - - - 0 - 0 - - Filesystem @@ -441,12 +429,6 @@ - - - 0 - 0 - - General info @@ -668,7 +650,7 @@ - + @@ -742,43 +724,11 @@ - - - - Qt::Horizontal - - - - 55 - 20 - - - - - + Qt::Horizontal - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - @@ -788,6 +738,13 @@ + + + + Qt::Horizontal + + + diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index 304862eb..716a4543 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -326,6 +326,7 @@ bool LoadState(std::string filename) std::string savefile = filename.substr(LastSep(filename)+1); savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); + savefile += Platform::InstanceFileSuffix(); NDSSave->SetPath(savefile, true); } @@ -350,6 +351,7 @@ bool SaveState(std::string filename) { std::string savefile = filename.substr(LastSep(filename)+1); savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); + savefile += Platform::InstanceFileSuffix(); NDSSave->SetPath(savefile, false); } @@ -432,6 +434,7 @@ void Reset() { std::string oldsave = NDSSave->GetPath(); std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav"); + newsave += Platform::InstanceFileSuffix(); if (oldsave != newsave) NDSSave->SetPath(newsave, false); } @@ -440,6 +443,7 @@ void Reset() { std::string oldsave = GBASave->GetPath(); std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav"); + newsave += Platform::InstanceFileSuffix(); if (oldsave != newsave) GBASave->SetPath(newsave, false); } @@ -562,7 +566,11 @@ bool LoadROM(QStringList filepath, bool reset) u8* savedata = nullptr; std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav"); + std::string origsav = savname; + savname += Platform::InstanceFileSuffix(); + FILE* sav = Platform::OpenFile(savname, "rb", true); + if (!sav) sav = Platform::OpenFile(origsav, "rb", true); if (sav) { fseek(sav, 0, SEEK_END); @@ -711,7 +719,11 @@ bool LoadGBAROM(QStringList filepath) u8* savedata = nullptr; std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav"); + std::string origsav = savname; + savname += Platform::InstanceFileSuffix(); + FILE* sav = Platform::OpenFile(savname, "rb", true); + if (!sav) sav = Platform::OpenFile(origsav, "rb", true); if (sav) { fseek(sav, 0, SEEK_END); diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp index a4c9cfd0..8087ee65 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.cpp +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -31,7 +31,7 @@ #include "ui_TitleImportDialog.h" -FILE* TitleManagerDialog::curNAND = nullptr; +bool TitleManagerDialog::NANDInited = false; TitleManagerDialog* TitleManagerDialog::currentDlg = nullptr; extern std::string EmuDirectory; @@ -136,6 +136,8 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid) bool TitleManagerDialog::openNAND() { + NANDInited = false; + FILE* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb"); if (!bios7i) return false; @@ -145,28 +147,21 @@ bool TitleManagerDialog::openNAND() fread(es_keyY, 16, 1, bios7i); fclose(bios7i); - curNAND = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b"); - if (!curNAND) - return false; - - if (!DSi_NAND::Init(curNAND, es_keyY)) + if (!DSi_NAND::Init(es_keyY)) { - fclose(curNAND); - curNAND = nullptr; return false; } + NANDInited = true; return true; } void TitleManagerDialog::closeNAND() { - if (curNAND) + if (NANDInited) { DSi_NAND::DeInit(); - - fclose(curNAND); - curNAND = nullptr; + NANDInited = false; } } diff --git a/src/frontend/qt_sdl/TitleManagerDialog.h b/src/frontend/qt_sdl/TitleManagerDialog.h index 201e5e8b..cba70470 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.h +++ b/src/frontend/qt_sdl/TitleManagerDialog.h @@ -45,7 +45,7 @@ public: explicit TitleManagerDialog(QWidget* parent); ~TitleManagerDialog(); - static FILE* curNAND; + static bool NANDInited; static bool openNAND(); static void closeNAND(); diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp index 19cece6f..9bf265e9 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp +++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp @@ -50,12 +50,12 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - LAN_Socket::Init(); haspcap = LAN_PCap::Init(false); ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)"); - ui->cbBindAnyAddr->setChecked(Config::SocketBindAnyAddr); + ui->lblAdapterMAC->setText("(none)"); + ui->lblAdapterIP->setText("(none)"); int sel = 0; for (int i = 0; i < LAN_PCap::NumAdapters; i++) @@ -88,7 +88,6 @@ void WifiSettingsDialog::done(int r) if (r == QDialog::Accepted) { - Config::SocketBindAnyAddr = ui->cbBindAnyAddr->isChecked(); Config::DirectLAN = ui->rbDirectMode->isChecked(); int sel = ui->cbxDirectAdapter->currentIndex(); diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.ui b/src/frontend/qt_sdl/WifiSettingsDialog.ui index 08970595..444e1d5f 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.ui +++ b/src/frontend/qt_sdl/WifiSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 572 - 273 + 217 @@ -26,92 +26,10 @@ - Local + Network mode - - - <html><head/><body><p>Enabling this allows (theoretically) playing local multiplayer games over a local network. It may or may not help make for a better connection in general.</p></body></html> - - - Bind socket to any address - - - - - - - - - - Online - - - - - - Direct mode settings - - - - - - Network adapter: - - - - - - - - 0 - 0 - - - - - 300 - 0 - - - - <html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html> - - - - - - - MAC address: - - - - - - - [PLACEHOLDER] - - - - - - - IP address: - - - - - - - [PLACEHOLDER] - - - - - - - <html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html> @@ -121,7 +39,7 @@ - + <html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html> @@ -134,6 +52,69 @@ + + + + Direct mode settings + + + + + + Network adapter: + + + + + + + + 0 + 0 + + + + + 300 + 0 + + + + <html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html> + + + + + + + MAC address: + + + + + + + [PLACEHOLDER] + + + + + + + IP address: + + + + + + + [PLACEHOLDER] + + + + + + diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 73962aa1..da01214c 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -57,9 +58,11 @@ #include "EmuSettingsDialog.h" #include "InputConfig/InputConfigDialog.h" #include "VideoSettingsDialog.h" +#include "CameraSettingsDialog.h" #include "AudioSettingsDialog.h" #include "FirmwareSettingsDialog.h" #include "PathSettingsDialog.h" +#include "MPSettingsDialog.h" #include "WifiSettingsDialog.h" #include "InterfaceSettingsDialog.h" #include "ROMInfoDialog.h" @@ -80,6 +83,7 @@ #include "SPU.h" #include "Wifi.h" #include "Platform.h" +#include "LocalMP.h" #include "Config.h" #include "Savestate.h" @@ -88,6 +92,7 @@ #include "ROMManager.h" #include "ArchiveUtil.h" +#include "CameraManager.h" // TODO: uniform variable spelling @@ -104,6 +109,7 @@ bool videoSettingsDirty; SDL_AudioDeviceID audioDevice; int audioFreq; +bool audioMuted; SDL_cond* audioSync; SDL_mutex* audioSyncLock; @@ -114,10 +120,13 @@ u32 micExtBufferWritePos; u32 micWavLength; s16* micWavBuffer; +CameraManager* camManager[2]; +bool camStarted[2]; + const struct { int id; float ratio; const char* label; } aspectRatios[] = { { 0, 1, "4:3 (native)" }, - { 4, (16.f / 10) / (4.f / 3), "16:10 (3DS)"}, + { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, { 1, (16.f / 9) / (4.f / 3), "16:9" }, { 2, (21.f / 9) / (4.f / 3), "21:9" }, { 3, 0, "window" } @@ -126,6 +135,7 @@ const struct { int id; float ratio; const char* label; } aspectRatios[] = void micCallback(void* data, Uint8* stream, int len); + void audioCallback(void* data, Uint8* stream, int len) { len /= (sizeof(s16) * 2); @@ -141,7 +151,7 @@ void audioCallback(void* data, Uint8* stream, int len) SDL_CondSignal(audioSync); SDL_UnlockMutex(audioSyncLock); - if (num_in < 1) + if ((num_in < 1) || audioMuted) { memset(stream, 0, len*sizeof(s16)*2); return; @@ -161,6 +171,23 @@ void audioCallback(void* data, Uint8* stream, int len) Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume); } +void audioMute() +{ + int inst = Platform::InstanceID(); + audioMuted = false; + + switch (Config::MPAudioMode) + { + case 1: // only instance 1 + if (inst > 0) audioMuted = true; + break; + + case 2: // only currently focused instance + if (!mainWindow->isActiveWindow()) audioMuted = true; + break; + } +} + void micOpen() { @@ -700,7 +727,11 @@ void EmuThread::run() if (winUpdateFreq < 1) winUpdateFreq = 1; - sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + else + sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); changeWindowTitle(melontitle); } } @@ -715,7 +746,11 @@ void EmuThread::run() EmuStatus = EmuRunning; - sprintf(melontitle, "melonDS " MELONDS_VERSION); + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "melonDS " MELONDS_VERSION); + else + sprintf(melontitle, "melonDS (%d)", inst+1); changeWindowTitle(melontitle); SDL_Delay(75); @@ -942,7 +977,7 @@ void ScreenHandler::screenSetupLayout(int w, int h) QSize ScreenHandler::screenGetMinSize(int factor = 1) { bool isHori = (Config::ScreenRotation == 1 || Config::ScreenRotation == 3); - int gap = Config::ScreenGap; + int gap = Config::ScreenGap * factor; int w = 256 * factor; int h = 192 * factor; @@ -976,9 +1011,9 @@ QSize ScreenHandler::screenGetMinSize(int factor = 1) else // hybrid { if (isHori) - return QSize(h+gap+h, 3*w +(4*gap) / 3); + return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0)); else - return QSize(3*w +(4*gap) / 3, h+gap+h); + return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h); } } @@ -1406,6 +1441,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) setWindowTitle("melonDS " MELONDS_VERSION); setAttribute(Qt::WA_DeleteOnClose); setAcceptDrops(true); + setFocusPolicy(Qt::ClickFocus); + + int inst = Platform::InstanceID(); QMenuBar* menubar = new QMenuBar(); { @@ -1538,19 +1576,30 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actEnableCheats->setCheckable(true); connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); - actSetupCheats = menu->addAction("Setup cheat codes"); - actSetupCheats->setMenuRole(QAction::NoRole); - connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); + //if (inst == 0) + { + actSetupCheats = menu->addAction("Setup cheat codes"); + actSetupCheats->setMenuRole(QAction::NoRole); + connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); - menu->addSeparator(); - actROMInfo = menu->addAction("ROM info"); - connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); + menu->addSeparator(); + actROMInfo = menu->addAction("ROM info"); + connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); - actRAMInfo = menu->addAction("RAM search"); - connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); + actRAMInfo = menu->addAction("RAM search"); + connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); - actTitleManager = menu->addAction("Manage DSi titles"); - connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + actTitleManager = menu->addAction("Manage DSi titles"); + connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + } + + { + menu->addSeparator(); + QMenu* submenu = menu->addMenu("Multiplayer"); + + actMPNewInstance = submenu->addAction("Launch new instance"); + connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); + } } { QMenu* menu = menubar->addMenu("Config"); @@ -1559,7 +1608,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); #ifdef __APPLE__ - QAction* actPreferences = menu->addAction("Preferences..."); + actPreferences = menu->addAction("Preferences..."); connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); actPreferences->setMenuRole(QAction::PreferencesRole); #endif @@ -1570,18 +1619,24 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actVideoSettings = menu->addAction("Video settings"); connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings); + actCameraSettings = menu->addAction("Camera settings"); + connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); + actAudioSettings = menu->addAction("Audio settings"); connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); + actMPSettings = menu->addAction("Multiplayer settings"); + connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); + actWifiSettings = menu->addAction("Wifi settings"); connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); - actInterfaceSettings = menu->addAction("Interface settings"); - connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); - actFirmwareSettings = menu->addAction("Firmware settings"); connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); + actInterfaceSettings = menu->addAction("Interface settings"); + connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); + actPathSettings = menu->addAction("Path settings"); connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); @@ -1737,6 +1792,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) resize(Config::WindowWidth, Config::WindowHeight); + if (Config::FirmwareUsername == "Arisotura") + actMPNewInstance->setText("Fart"); + #ifdef Q_OS_MAC QPoint screenCenter = screen()->availableGeometry().center(); QRect frameGeo = frameGeometry(); @@ -1816,6 +1874,19 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actLimitFramerate->setChecked(Config::LimitFPS); actAudioSync->setChecked(Config::AudioSync); + + if (inst > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); + +#ifdef __APPLE__ + actPreferences->setEnabled(false); +#endif // __APPLE__ + } } MainWindow::~MainWindow() @@ -2005,6 +2076,16 @@ void MainWindow::dropEvent(QDropEvent* event) } } +void MainWindow::focusInEvent(QFocusEvent* event) +{ + audioMute(); +} + +void MainWindow::focusOutEvent(QFocusEvent* event) +{ + audioMute(); +} + void MainWindow::onAppStateChanged(Qt::ApplicationState state) { if (state == Qt::ApplicationInactive) @@ -2677,6 +2758,23 @@ void MainWindow::onOpenTitleManager() TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); } +void MainWindow::onMPNewInstance() +{ + //QProcess::startDetached(QApplication::applicationFilePath()); + QProcess newinst; + newinst.setProgram(QApplication::applicationFilePath()); + newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); + +#ifdef __WIN32__ + newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) + { + args->flags |= CREATE_NEW_CONSOLE; + }); +#endif + + newinst.startDetached(); +} + void MainWindow::onOpenEmuSettings() { emuThread->emuPause(); @@ -2737,6 +2835,27 @@ void MainWindow::onOpenVideoSettings() connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings); } +void MainWindow::onOpenCameraSettings() +{ + emuThread->emuPause(); + + camStarted[0] = camManager[0]->isStarted(); + camStarted[1] = camManager[1]->isStarted(); + if (camStarted[0]) camManager[0]->stop(); + if (camStarted[1]) camManager[1]->stop(); + + CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this); + connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished); +} + +void MainWindow::onCameraSettingsFinished(int res) +{ + if (camStarted[0]) camManager[0]->start(); + if (camStarted[1]) camManager[1]->start(); + + emuThread->emuUnpause(); +} + void MainWindow::onOpenAudioSettings() { AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this); @@ -2811,6 +2930,22 @@ void MainWindow::onAudioSettingsFinished(int res) micOpen(); } +void MainWindow::onOpenMPSettings() +{ + emuThread->emuPause(); + + MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); + connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); +} + +void MainWindow::onMPSettingsFinished(int res) +{ + audioMute(); + LocalMP::SetRecvTimeout(Config::MPRecvTimeout); + + emuThread->emuUnpause(); +} + void MainWindow::onOpenWifiSettings() { emuThread->emuPause(); @@ -2821,12 +2956,6 @@ void MainWindow::onOpenWifiSettings() void MainWindow::onWifiSettingsFinished(int res) { - if (Wifi::MPInited) - { - Platform::MP_DeInit(); - Platform::MP_Init(); - } - Platform::LAN_DeInit(); Platform::LAN_Init(); @@ -3081,7 +3210,7 @@ bool MelonApplication::event(QEvent *event) int main(int argc, char** argv) { - srand(time(NULL)); + srand(time(nullptr)); qputenv("QT_SCALE_FACTOR", "1"); @@ -3139,6 +3268,7 @@ int main(int argc, char** argv) SANITIZE(Config::ScreenAspectBot, 0, 4); #undef SANITIZE + audioMuted = false; audioSync = SDL_CreateCond(); audioSyncLock = SDL_CreateMutex(); @@ -3164,11 +3294,17 @@ int main(int argc, char** argv) micDevice = 0; - memset(micExtBuffer, 0, sizeof(micExtBuffer)); micExtBufferWritePos = 0; micWavBuffer = nullptr; + camStarted[0] = false; + camStarted[1] = false; + camManager[0] = new CameraManager(0, 640, 480, true); + camManager[1] = new CameraManager(1, 640, 480, true); + camManager[0]->setXFlip(Config::Camera[0].XFlip); + camManager[1]->setXFlip(Config::Camera[1].XFlip); + ROMManager::EnableCheats(Config::EnableCheats != 0); Frontend::Init_Audio(audioFreq); @@ -3192,6 +3328,8 @@ int main(int argc, char** argv) emuThread->start(); emuThread->emuPause(); + audioMute(); + QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged); if (argc > 1) @@ -3219,6 +3357,9 @@ int main(int argc, char** argv) if (micWavBuffer) delete[] micWavBuffer; + delete camManager[0]; + delete camManager[1]; + Config::Save(); SDL_Quit(); @@ -3249,7 +3390,8 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho if (argv_w) LocalFree(argv_w); - /*if (AttachConsole(ATTACH_PARENT_PROCESS)) + //if (AttachConsole(ATTACH_PARENT_PROCESS)) + /*if (AllocConsole()) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index ed015fe9..51b07fc7 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -254,6 +254,9 @@ protected: void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + signals: void screenLayoutChange(); @@ -283,6 +286,7 @@ private slots: void onROMInfo(); void onRAMInfo(); void onOpenTitleManager(); + void onMPNewInstance(); void onOpenEmuSettings(); void onEmuSettingsDialogFinished(int res); @@ -290,14 +294,18 @@ private slots: void onOpenInputConfig(); void onInputConfigFinished(int res); void onOpenVideoSettings(); + void onOpenCameraSettings(); + void onCameraSettingsFinished(int res); void onOpenAudioSettings(); - void onOpenFirmwareSettings(); - void onOpenPathSettings(); void onUpdateAudioSettings(); void onAudioSettingsFinished(int res); + void onOpenMPSettings(); + void onMPSettingsFinished(int res); void onOpenWifiSettings(); void onWifiSettingsFinished(int res); + void onOpenFirmwareSettings(); void onFirmwareSettingsFinished(int res); + void onOpenPathSettings(); void onPathSettingsFinished(int res); void onOpenInterfaceSettings(); void onInterfaceSettingsFinished(int res); @@ -374,12 +382,18 @@ public: QAction* actROMInfo; QAction* actRAMInfo; QAction* actTitleManager; + QAction* actMPNewInstance; QAction* actEmuSettings; +#ifdef __APPLE__ + QAction* actPreferences; +#endif QAction* actPowerManagement; QAction* actInputConfig; QAction* actVideoSettings; + QAction* actCameraSettings; QAction* actAudioSettings; + QAction* actMPSettings; QAction* actWifiSettings; QAction* actFirmwareSettings; QAction* actPathSettings; diff --git a/src/frontend/qt_sdl/sem_timedwait.cpp b/src/frontend/qt_sdl/sem_timedwait.cpp new file mode 100644 index 00000000..38b3c16f --- /dev/null +++ b/src/frontend/qt_sdl/sem_timedwait.cpp @@ -0,0 +1,488 @@ +/* + * s e m _ t i m e d w a i t + * + * Function: + * Implements a version of sem_timedwait(). + * + * Description: + * Not all systems implement sem_timedwait(), which is a version of + * sem_wait() with a timeout. Mac OS X is one example, at least up to + * and including version 10.6 (Leopard). If such a function is needed, + * this code provides a reasonable implementation, which I think is + * compatible with the standard version, although possibly less + * efficient. It works by creating a thread that interrupts a normal + * sem_wait() call after the specified timeout. + * + * Call: + * + * The Linux man pages say: + * + * #include + * + * int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); + * + * sem_timedwait() is the same as sem_wait(), except that abs_timeout + * specifies a limit on the amount of time that the call should block if + * the decrement cannot be immediately performed. The abs_timeout argument + * points to a structure that specifies an absolute timeout in seconds and + * nanoseconds since the Epoch (00:00:00, 1 January 1970). This structure + * is defined as follows: + * + * struct timespec { + * time_t tv_sec; Seconds + * long tv_nsec; Nanoseconds [0 .. 999999999] + * }; + * + * If the timeout has already expired by the time of the call, and the + * semaphore could not be locked immediately, then sem_timedwait() fails + * with a timeout error (errno set to ETIMEDOUT). + * If the operation can be performed immediately, then sem_timedwait() + * never fails with a timeout error, regardless of the value of abs_timeout. + * Furthermore, the validity of abs_timeout is not checked in this case. + * + * Limitations: + * + * The mechanism used involves sending a SIGUSR2 signal to the thread + * calling sem_timedwait(). The handler for this signal is set to a null + * routine which does nothing, and with any flags for the signal + * (eg SA_RESTART) cleared. Note that this effective disabling of the + * SIGUSR2 signal is a side-effect of using this routine, and means it + * may not be a completely transparent plug-in replacement for a + * 'normal' sig_timedwait() call. Since OS X does not declare the + * sem_timedwait() call in its standard include files, the relevant + * declaration (shown above in the man pages extract) will probably have + * to be added to any code that uses this. + * + * Compiling: + * This compiles and runs cleanly on OS X (10.6) with gcc with the + * -Wall -ansi -pedantic flags. On Linux, using -ansi causes a sweep of + * compiler complaints about the timespec structure, but it compiles + * and works fine with just -Wall -pedantic. (Since Linux provides + * sem_timedwait() anyway, this really isn't needed on Linux.) However, + * since Linux provides sem_timedwait anyway, the sem_timedwait() + * code in this file is only compiled on OS X, and is a null on other + * systems. + * + * Testing: + * This file contains a test program that exercises the sem_timedwait + * code. It is compiled if the pre-processor variable TEST is defined. + * For more details, see the comments for the test routine at the end + * of the file. + * + * Author: Keith Shortridge, AAO. + * + * History: + * 8th Sep 2009. Original version. KS. + * 24th Sep 2009. Added test that the calling thread still exists before + * trying to set the timed-out flag. KS. + * 2nd Oct 2009. No longer restores the original SIGUSR2 signal handler. + * See comments in the body of the code for more details. + * Prototypes for now discontinued internal routines removed. + * 12th Aug 2010. Added the cleanup handler, so that this code no longer + * leaks resources if the calling thread is cancelled. KS. + * 21st Sep 2011. Added copyright notice below. Modified header comments + * to describe the use of SIGUSR2 more accurately in the + * light of the 2/10/09 change above. Now undefs DEBUG + * before defining it, to avoid any possible clash. KS. + * 14th Feb 2012. Tidied out a number of TABs that had got into the + * code. KS. + * 6th May 2013. Copyright notice modified to one based on the MIT licence, + * which is more permissive than the previous notice. KS. + * + * Copyright (c) Australian Astronomical Observatory (AAO), (2013). + * 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. + */ + +#ifdef __APPLE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sem_timedwait.h" + +/* Some useful definitions - TRUE, FALSE, and DEBUG */ + +#undef TRUE +#define TRUE 1 +#undef FALSE +#define FALSE 0 +#undef DEBUG +#define DEBUG printf + +/* A structure of type timeoutDetails is passed to the thread used to + * implement the timeout. + */ + +typedef struct { + struct timespec delay; /* Specifies the delay, relative to now */ + pthread_t callingThread; /* The thread doing the sem_wait call */ + volatile short *timedOutShort; /* Address of a flag set to indicate that + * the timeout was triggered. */ +} timeoutDetails; + +/* A structure of type cleanupDetails is passed to the thread cleanup + * routine which is called at the end of the routine or if the thread calling + * it is cancelled. + */ + +typedef struct { + pthread_t *threadIdAddr; /* Address of the variable that holds + * the Id of the timeout thread. */ + struct sigaction *sigHandlerAddr; /* Address of the old signal action + * handler. */ + volatile short *timedOutShort; /* Address of a flag set to indicate that + * the timeout was triggered. */ +} cleanupDetails; + +/* Forward declarations of internal routines */ + +static void* timeoutThreadMain (void* passedPtr); +static int triggerSignal (int Signal, pthread_t Thread); +static void ignoreSignal (int Signal); +static void timeoutThreadCleanup (void* passedPtr); + +/* -------------------------------------------------------------------------- */ +/* + * s e m _ t i m e d w a i t + * + * This is the main code for the sem_timedwait() implementation. + */ + +int sem_timedwait ( + sem_t *sem, + const struct timespec *abs_timeout) +{ + int result = 0; /* Code returned by this routine 0 or -1 */ + + /* "Under no circumstances shall the function fail if the semaphore + * can be locked immediately". So we try to get it quickly to see if we + * can avoid all the timeout overheads. + */ + + if (sem_trywait(sem) == 0) { + + /* Yes, got it immediately. */ + + result = 0; + + } else { + + /* No, we've got to do it with a sem_wait() call and a thread to run + * the timeout. First, work out the time from now to the specified + * timeout, which we will pass to the timeout thread in a way that can + * be used to pass to nanosleep(). So we need this in seconds and + * nanoseconds. Along the way, we check for an invalid passed time, + * and for one that's already expired. + */ + + if ((abs_timeout->tv_nsec < 0) || (abs_timeout->tv_nsec > 1000000000)) { + + /* Passed time is invalid */ + + result = -1; + errno = EINVAL; + + } else { + + struct timeval currentTime; /* Time now */ + long secsToWait,nsecsToWait; /* Seconds and nsec to delay */ + gettimeofday (¤tTime,NULL); + secsToWait = abs_timeout->tv_sec - currentTime.tv_sec; + nsecsToWait = (abs_timeout->tv_nsec - (currentTime.tv_usec * 1000)); + while (nsecsToWait < 0) { + nsecsToWait += 1000000000; + secsToWait--; + } + if ((secsToWait < 0) || ((secsToWait == 0) && (nsecsToWait < 0))) { + + /* Time has passed. Report an immediate timeout. */ + + result = -1; + errno = ETIMEDOUT; + + } else { + + /* We're going to have to do a sem_wait() with a timeout thread. + * The thread will wait the specified time, then will issue a + * SIGUSR2 signal that will interrupt the sem_wait() call. + * We pass the thread the id of the current thread, the delay, + * and the address of a flag to set on a timeout, so we can + * distinguish an interrupt caused by the timeout thread from + * one caused by some other signal. + */ + + volatile short timedOut; /* Flag to set on timeout */ + timeoutDetails details; /* All the stuff the thread must know */ + struct sigaction oldSignalAction; /* Current signal setting */ + pthread_t timeoutThread; /* Id of timeout thread */ + cleanupDetails cleaningDetails; /* What the cleanup routine needs */ + int oldCancelState; /* Previous cancellation state */ + int ignoreCancelState; /* Used in call, but ignored */ + int createStatus; /* Status of pthread_create() call */ + + /* If the current thread is cancelled (and CML does do this) + * we don't want to leave our timer thread running - if we've + * started the thread we want to make sure we join it in order + * to release its resources. So we set a cleanup handler to + * do this. We pass it the address of the structure that will + * hold all it needs to know. While we set all this up, + * we prevent ourselves being cancelled, so all this data is + * coherent. + */ + + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE,&oldCancelState); + timeoutThread = (pthread_t) 0; + cleaningDetails.timedOutShort = &timedOut; + cleaningDetails.threadIdAddr = &timeoutThread; + cleaningDetails.sigHandlerAddr = &oldSignalAction; + pthread_cleanup_push (timeoutThreadCleanup,&cleaningDetails); + + /* Set up the details for the thread. Clear the timeout flag, + * record the current SIGUSR2 action settings so we can restore + * them later. + */ + + details.delay.tv_sec = secsToWait; + details.delay.tv_nsec = nsecsToWait; + details.callingThread = pthread_self(); + details.timedOutShort = &timedOut; + timedOut = FALSE; + sigaction (SIGUSR2,NULL,&oldSignalAction); + + /* Start up the timeout thread. Once we've done that, we can + * restore the previous cancellation state. + */ + + createStatus = pthread_create(&timeoutThread,NULL, + timeoutThreadMain, (void*)&details); + pthread_setcancelstate (oldCancelState,&ignoreCancelState); + + if (createStatus < 0) { + + /* Failed to create thread. errno will already be set properly */ + + result = -1; + + } else { + + /* Thread created OK. This is where we wait for the semaphore. + */ + + if (sem_wait(sem) == 0) { + + /* Got the semaphore OK. We return zero, and all's well. */ + + result = 0; + + } else { + + /* If we got a -1 error from sem_wait(), it may be because + * it was interrupted by a timeout, or failed for some + * other reason. We check for the expected timeout + * condition, which is an 'interrupted' status and the + * timeout flag set by the timeout thread. We report that as + * a timeout error. Anything else is some other error and + * errno is already set properly. + */ + + result = -1; + if (errno == EINTR) { + if (timedOut) errno = ETIMEDOUT; + } + } + + } + + /* The cleanup routine - timeoutThreadCleanup() - packages up + * any tidying up that is needed, including joining with the + * timer thread. This will be called if the current thread is + * cancelled, but we need it to happen anyway, so we set the + * execute flag true here as we remove it from the list of + * cleanup routines to be called. So normally, this line amounts + * to calling timeoutThreadCleanup(). + */ + + pthread_cleanup_pop (TRUE); + } + } + } + return (result); +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d C l e a n u p + * + * This internal routine tidies up at the end of a sem_timedwait() call. + * It is set as a cleanup routine for the current thread (not the timer + * thread) so it is executed even if the thread is cancelled. This is + * important, as we need to tidy up the timeout thread. If we took the + * semaphore (in other words, if we didn't timeout) then the timer thread + * will still be running, sitting in its nanosleep() call, and we need + * to cancel it. If the timer thread did signal a timeout then it will + * now be closing down. In either case, we need to join it (using a call + * to pthread_join()) or its resources will never be released. + * The single argument is a pointer to a cleanupDetails structure that has + * all the routine needs to know. + */ + +static void timeoutThreadCleanup (void* passedPtr) +{ + /* Get what we need from the structure we've been passed. */ + + cleanupDetails *detailsPtr = (cleanupDetails*) passedPtr; + short timedOut = *(detailsPtr->timedOutShort); + pthread_t timeoutThread = *(detailsPtr->threadIdAddr); + + /* If we created the thread, stop it - doesn't matter if it's no longer + * running, pthread_cancel can handle that. We make sure we wait for it + * to complete, because it is this pthread_join() call that releases any + * memory the thread may have allocated. Note that cancelling a thread is + * generally not a good idea, because of the difficulty of cleaning up + * after it, but this is a very simple thread that does nothing but call + * nanosleep(), and that we can cancel quite happily. + */ + + if (!timedOut) pthread_cancel(timeoutThread); + pthread_join(timeoutThread,NULL); + + /* The code originally restored the old action handler, which generally + * was the default handler that caused the task to exit. Just occasionally, + * there seem to be cases where the signal is still queued and ready to + * trigger even though the thread that presumably sent it off just before + * it was cancelled has finished. I had thought that once we'd joined + * that thread, we could be sure of not seeing the signal, but that seems + * not to be the case, and so restoring a handler that will allow the task + * to crash is not a good idea, and so the line below has been commented + * out. + * + * sigaction (SIGUSR2,detailsPtr->sigHandlerAddr,NULL); + */ +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d M a i n + * + * This internal routine is the main code for the timeout thread. + * The single argument is a pointer to a timeoutDetails structure that has + * all the thread needs to know - thread to signal, delay time, and the + * address of a flag to set if it triggers a timeout. + */ + +static void* timeoutThreadMain (void* passedPtr) +{ + void* Return = (void*) 0; + + /* We grab all the data held in the calling thread right now. In some + * cases, we find that the calling thread has vanished and released + * its memory, including the details structure, by the time the timeout + * expires, and then we get an access violation when we try to set the + * 'timed out' flag. + */ + + timeoutDetails details = *((timeoutDetails*) passedPtr); + struct timespec requestedDelay = details.delay; + + /* We do a nanosleep() for the specified delay, and then trigger a + * timeout. Note that we allow for the case where the nanosleep() is + * interrupted, and restart it for the remaining time. If the + * thread that is doing the sem_wait() call gets the semaphore, it + * will cancel this thread, which is fine as we aren't doing anything + * other than a sleep and a signal. + */ + + for (;;) { + struct timespec remainingDelay; + if (nanosleep (&requestedDelay,&remainingDelay) == 0) { + break; + } else if (errno == EINTR) { + requestedDelay = remainingDelay; + } else { + Return = (void*) errno; + break; + } + } + + /* We've completed the delay without being cancelled, so we now trigger + * the timeout by sending a signal to the calling thread. And that's it, + * although we set the timeout flag first to indicate that it was us + * that interrupted the sem_wait() call. One precaution: before we + * try to set the timed-out flag, make sure the calling thread still + * exists - this may not be the case if things are closing down a bit + * messily. We check this quickly using a zero test signal. + */ + + if (pthread_kill(details.callingThread,0) == 0) { + *(details.timedOutShort) = TRUE; + if (triggerSignal (SIGUSR2,details.callingThread) < 0) { + Return = (void*) errno; + } + } + + return Return; +} + +/* -------------------------------------------------------------------------- */ +/* + * t r i g g e r S i g n a l + * + * This is a general purpose routine that sends a specified signal to + * a specified thread, setting up a signal handler that does nothing, + * and then giving the signal. The only effect will be to interrupt any + * operation that is currently blocking - in this case, we expect this to + * be a sem_wait() call. + */ + +static int triggerSignal (int Signal, pthread_t Thread) +{ + int Result = 0; + struct sigaction SignalDetails; + SignalDetails.sa_handler = ignoreSignal; + SignalDetails.sa_flags = 0; + (void) sigemptyset(&SignalDetails.sa_mask); + if ((Result = sigaction(Signal,&SignalDetails,NULL)) == 0) { + Result = pthread_kill(Thread,Signal); + } + return Result; +} + +/* -------------------------------------------------------------------------- */ +/* + * i g n o r e S i g n a l + * + * And this is the signal handler that does nothing. (It clears its argument, + * but this has no effect and prevents a compiler warning about an unused + * argument.) + */ + +static void ignoreSignal (int Signal) { + Signal = 0; +} + +#endif diff --git a/src/frontend/qt_sdl/sem_timedwait.h b/src/frontend/qt_sdl/sem_timedwait.h new file mode 100644 index 00000000..42ae201e --- /dev/null +++ b/src/frontend/qt_sdl/sem_timedwait.h @@ -0,0 +1,8 @@ +#ifndef __SEM_TIMEDWAIT_H +#define __SEM_TIMEDWAIT_H + +#ifdef __APPLE__ +int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); +#endif + +#endif diff --git a/src/teakra/src/teakra.cpp b/src/teakra/src/teakra.cpp index 76bc79fe..95599204 100644 --- a/src/teakra/src/teakra.cpp +++ b/src/teakra/src/teakra.cpp @@ -50,7 +50,7 @@ struct Teakra::Impl { } void Reset() { - shared_memory.raw.fill(0); + //shared_memory.raw.fill(0); // BAD!!!! miu.Reset(); apbp_from_cpu.Reset(); apbp_from_dsp.Reset(); diff --git a/tools/mac-libs.rb b/tools/mac-libs.rb index 64b02b95..e5d4dd57 100755 --- a/tools/mac-libs.rb +++ b/tools/mac-libs.rb @@ -7,7 +7,7 @@ $app_name = "melonDS" $build_dmg = false $build_dir = "" $bundle = "" -$fallback_rpaths = ["/usr/local/lib", "/opt/local/lib"] +$fallback_rpaths = [] def frameworks_dir File.join($bundle, "Contents", "Frameworks") @@ -56,7 +56,7 @@ def expand_load_path(lib, path) file = $fallback_rpaths .map { |it| File.join(it, file_name) } .find { |it| File.exist? it } - if file == nil + if file == nil path = File.join(File.dirname(lib), file_name) file = path if File.exist? path end @@ -89,15 +89,17 @@ def install_name_tool(exec, action, path1, path2 = nil) args = ["-#{action.to_s}", path1] args << path2 if path2 != nil - out, status = Open3.capture2e("install_name_tool", *args, exec) - if status != 0 - puts out - exit status + Open3.popen3("install_name_tool", *args, exec) do |stdin, stdout, stderr, thread| + print stdout.read + err = stderr.read + unless err.match? "code signature" + print err + end end end def strip(lib) - out, _ = Open3.capture2("strip", "-Sx", lib) + out, _ = Open3.capture2("strip", "-no_code_signature_warning", "-Sx", lib) print out end @@ -182,6 +184,18 @@ qt_major = $1 qt_dir = $2 qt_dir = File.absolute_path("#{qt_dir}/../../..") +for lib in get_load_libs(executable) do + next if system_lib? lib + + path = File.dirname(lib) + + if path.match? ".framework" + path = path.sub(/\/[^\/]+\.framework.*/, "") + end + + $fallback_rpaths << path unless $fallback_rpaths.include? path +end + $fallback_rpaths << File.join(qt_dir, "lib") plugin_paths = [ diff --git a/tools/mac-universal.py b/tools/mac-universal.py new file mode 100755 index 00000000..c526f21c --- /dev/null +++ b/tools/mac-universal.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +""" +Based on Dolphin's BuildMacOSUniversalBinary.py +""" + +import filecmp +import glob +import os +import shutil +import sys +import subprocess + + +def lipo(path0, path1, dst): + if subprocess.call(["lipo", "-create", "-output", dst, path0, path1]) != 0: + print(f"WARNING: {path0} and {path1} cannot be lipo'd") + + shutil.copy(path0, dst) + + +def recursive_merge_binaries(src0, src1, dst): + """ + Merges two build trees together for different architectures into a single + universal binary. + + The rules for merging are: + + 1) Files that exist in either src tree are copied into the dst tree + 2) Files that exist in both trees and are identical are copied over + unmodified + 3) Files that exist in both trees and are non-identical are lipo'd + 4) Symlinks are created in the destination tree to mirror the hierarchy in + the source trees + """ + + # Check that all files present in the folder are of the same type and that + # links link to the same relative location + for newpath0 in glob.glob(src0+"/*"): + filename = os.path.basename(newpath0) + newpath1 = os.path.join(src1, filename) + if not os.path.exists(newpath1): + continue + + if os.path.islink(newpath0) and os.path.islink(newpath1): + if os.path.relpath(newpath0, src0) == os.path.relpath(newpath1, src1): + continue + + if os.path.isdir(newpath0) and os.path.isdir(newpath1): + continue + + # isfile() can be true for links so check that both are not links + # before checking if they are both files + if (not os.path.islink(newpath0)) and (not os.path.islink(newpath1)): + if os.path.isfile(newpath0) and os.path.isfile(newpath1): + continue + + raise Exception(f"{newpath0} and {newpath1} cannot be " + + "merged into a universal binary because they are of " + + "incompatible types. Perhaps the installed libraries" + + " are from different versions for each architecture") + + for newpath0 in glob.glob(src0+"/*"): + filename = os.path.basename(newpath0) + newpath1 = os.path.join(src1, filename) + new_dst_path = os.path.join(dst, filename) + if os.path.islink(newpath0): + # Symlinks will be fixed after files are resolved + continue + + if not os.path.exists(newpath1): + if os.path.isdir(newpath0): + shutil.copytree(newpath0, new_dst_path) + else: + shutil.copy(newpath0, new_dst_path) + + continue + + if os.path.isdir(newpath1): + os.makedirs(new_dst_path) + recursive_merge_binaries(newpath0, newpath1, new_dst_path) + continue + + if filecmp.cmp(newpath0, newpath1): + shutil.copy(newpath0, new_dst_path) + else: + lipo(newpath0, newpath1, new_dst_path) + + # Loop over files in src1 and copy missing things over to dst + for newpath1 in glob.glob(src1+"/*"): + filename = os.path.basename(newpath1) + newpath0 = os.path.join(src0, filename) + new_dst_path = os.path.join(dst, filename) + if (not os.path.exists(newpath0)) and (not os.path.islink(newpath1)): + if os.path.isdir(newpath1): + shutil.copytree(newpath1, new_dst_path) + else: + shutil.copy(newpath1, new_dst_path) + + # Fix up symlinks for path0 + for newpath0 in glob.glob(src0+"/*"): + filename = os.path.basename(newpath0) + new_dst_path = os.path.join(dst, filename) + if os.path.islink(newpath0): + relative_path = os.path.relpath(os.path.realpath(newpath0), src0) + os.symlink(relative_path, new_dst_path) + # Fix up symlinks for path1 + for newpath1 in glob.glob(src1+"/*"): + filename = os.path.basename(newpath1) + new_dst_path = os.path.join(dst, filename) + newpath0 = os.path.join(src0, filename) + if os.path.islink(newpath1) and not os.path.exists(newpath0): + relative_path = os.path.relpath(os.path.realpath(newpath1), src1) + os.symlink(relative_path, new_dst_path) + + +if __name__ == "__main__": + recursive_merge_binaries(sys.argv[1], sys.argv[2], sys.argv[3]) +